1 /*
2    BAREOS® - Backup Archiving REcovery Open Sourced
3 
4    Copyright (C) 2001-2012 Free Software Foundation Europe e.V.
5    Copyright (C) 2011-2016 Planets Communications B.V.
6    Copyright (C) 2013-2020 Bareos GmbH & Co. KG
7 
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version three of the GNU Affero General Public
10    License as published by the Free Software Foundation and included
11    in the file LICENSE.
12 
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    Affero General Public License for more details.
17 
18    You should have received a copy of the GNU Affero General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22 */
23 /*
24  * Kern Sibbald, August MMI
25  */
26 /**
27  * @file
28  * User Agent Status Command
29  */
30 
31 #include "include/bareos.h"
32 #include "dird.h"
33 #include "dird/jcr_private.h"
34 #include "dird/run_hour_validator.h"
35 #include "dird/dird_globals.h"
36 #include "dird/fd_cmds.h"
37 #include "dird/job.h"
38 #include "dird/ndmp_dma_generic.h"
39 #include "dird/next_vol.h"
40 #include "dird/sd_cmds.h"
41 #include "dird/scheduler.h"
42 #include "dird/storage.h"
43 
44 #include "cats/sql_pooling.h"
45 #include "dird/ua_db.h"
46 #include "dird/ua_output.h"
47 #include "dird/ua_select.h"
48 #include "dird/ua_status.h"
49 #include "include/auth_protocol_types.h"
50 #include "lib/edit.h"
51 #include "lib/recent_job_results_list.h"
52 #include "lib/parse_conf.h"
53 #include "lib/util.h"
54 
55 #include "include/make_unique.h"
56 
57 #include <memory>
58 
59 #define DEFAULT_STATUS_SCHED_DAYS 7
60 
61 namespace directordaemon {
62 
63 static void ListScheduledJobs(UaContext* ua);
64 static void ListRunningJobs(UaContext* ua);
65 static void ListTerminatedJobs(UaContext* ua);
66 static void ListConnectedClients(UaContext* ua);
67 static void DoDirectorStatus(UaContext* ua);
68 static void DoSchedulerStatus(UaContext* ua);
69 static bool DoSubscriptionStatus(UaContext* ua);
70 static void DoConfigurationStatus(UaContext* ua);
71 static void DoAllStatus(UaContext* ua);
72 static void StatusSlots(UaContext* ua, StorageResource* store);
73 static void StatusContentApi(UaContext* ua, StorageResource* store);
74 static void StatusContentJson(UaContext* ua, StorageResource* store);
75 
76 static char OKdotstatus[] = "1000 OK .status\n";
77 static char DotStatusJob[] = "JobId=%s JobStatus=%c JobErrors=%d\n";
78 
ClientStatus(UaContext * ua,ClientResource * client,char * cmd)79 static void ClientStatus(UaContext* ua, ClientResource* client, char* cmd)
80 {
81   switch (client->Protocol) {
82     case APT_NATIVE:
83       DoNativeClientStatus(ua, client, cmd);
84       break;
85     case APT_NDMPV2:
86     case APT_NDMPV3:
87     case APT_NDMPV4:
88       DoNdmpClientStatus(ua, client, cmd);
89       break;
90     default:
91       break;
92   }
93 }
94 
95 /**
96  * .status command
97  */
DotStatusCmd(UaContext * ua,const char * cmd)98 bool DotStatusCmd(UaContext* ua, const char* cmd)
99 {
100   StorageResource* store;
101   ClientResource* client;
102   JobControlRecord* njcr = NULL;
103   char ed1[50];
104   char* statuscmd = NULL;
105 
106   Dmsg2(20, "status=\"%s\" argc=%d\n", cmd, ua->argc);
107 
108   if (ua->argc < 2) {
109     ua->SendMsg("1900 Bad .status command, missing arguments.\n");
110     return false;
111   }
112 
113   if (Bstrcasecmp(ua->argk[1], "dir")) {
114     if (Bstrcasecmp(ua->argk[2], "current")) {
115       ua->SendMsg(OKdotstatus, ua->argk[2]);
116       foreach_jcr (njcr) {
117         if (njcr->JobId != 0
118             && ua->AclAccessOk(Job_ACL, njcr->impl->res.job->resource_name_)) {
119           ua->SendMsg(DotStatusJob, edit_int64(njcr->JobId, ed1),
120                       njcr->JobStatus, njcr->JobErrors);
121         }
122       }
123       endeach_jcr(njcr);
124     } else if (Bstrcasecmp(ua->argk[2], "last")) {
125       ua->SendMsg(OKdotstatus, ua->argk[2]);
126       if (RecentJobResultsList::Count() > 0) {
127         RecentJobResultsList::JobResult job
128             = RecentJobResultsList::GetMostRecentJobResult();
129         if (ua->AclAccessOk(Job_ACL, job.Job)) {
130           ua->SendMsg(DotStatusJob, edit_int64(job.JobId, ed1), job.JobStatus,
131                       job.Errors);
132         }
133       }
134     } else if (Bstrcasecmp(ua->argk[2], "header")) {
135       ListDirStatusHeader(ua);
136     } else if (Bstrcasecmp(ua->argk[2], "scheduled")) {
137       ListScheduledJobs(ua);
138     } else if (Bstrcasecmp(ua->argk[2], "running")) {
139       ListRunningJobs(ua);
140     } else if (Bstrcasecmp(ua->argk[2], "terminated")) {
141       ListTerminatedJobs(ua);
142     } else {
143       ua->SendMsg("1900 Bad .status command, wrong argument.\n");
144       return false;
145     }
146   } else if (Bstrcasecmp(ua->argk[1], "client")) {
147     client = get_client_resource(ua);
148     if (client) {
149       if (ua->argc == 3) { statuscmd = ua->argk[2]; }
150       Dmsg2(200, "Client=%s arg=%s\n", client->resource_name_, NPRT(statuscmd));
151       ClientStatus(ua, client, statuscmd);
152     }
153   } else if (Bstrcasecmp(ua->argk[1], "storage")) {
154     store = get_storage_resource(ua);
155     if (store) {
156       if (ua->argc == 3) { statuscmd = ua->argk[2]; }
157       StorageStatus(ua, store, statuscmd);
158     }
159   } else {
160     ua->SendMsg("1900 Bad .status command, wrong argument.\n");
161     return false;
162   }
163 
164   return true;
165 }
166 
167 /**
168  * status command
169  */
StatusCmd(UaContext * ua,const char * cmd)170 bool StatusCmd(UaContext* ua, const char* cmd)
171 {
172   StorageResource* store;
173   ClientResource* client;
174   int item, i;
175   bool autochangers_only;
176 
177   Dmsg1(20, "status:%s:\n", cmd);
178 
179   for (i = 1; i < ua->argc; i++) {
180     if (Bstrcasecmp(ua->argk[i], NT_("all"))) {
181       DoAllStatus(ua);
182       return true;
183     } else if (bstrncasecmp(ua->argk[i], NT_("dir"), 3)) {
184       DoDirectorStatus(ua);
185       return true;
186     } else if (Bstrcasecmp(ua->argk[i], NT_("client"))) {
187       client = get_client_resource(ua);
188       if (client) { ClientStatus(ua, client, NULL); }
189       return true;
190     } else if (bstrncasecmp(ua->argk[i], NT_("sched"), 5)) {
191       DoSchedulerStatus(ua);
192       return true;
193     } else if (bstrncasecmp(ua->argk[i], NT_("sub"), 3)) {
194       if (DoSubscriptionStatus(ua)) {
195         return true;
196       } else {
197         return false;
198       }
199     } else if (bstrncasecmp(ua->argk[i], NT_("conf"), 4)) {
200       DoConfigurationStatus(ua);
201       return true;
202     } else {
203       /*
204        * limit storages to autochangers if slots is given
205        */
206       autochangers_only = (FindArg(ua, NT_("slots")) > 0);
207       store = get_storage_resource(ua, false, autochangers_only);
208 
209       if (store) {
210         if (FindArg(ua, NT_("slots")) > 0) {
211           switch (ua->api) {
212             case API_MODE_OFF:
213               StatusSlots(ua, store);
214               break;
215             case API_MODE_ON:
216               StatusContentApi(ua, store);
217               break;
218             case API_MODE_JSON:
219               StatusContentJson(ua, store);
220               break;
221           }
222         } else {
223           StorageStatus(ua, store, NULL);
224         }
225       }
226 
227       return true;
228     }
229   }
230   /* If no args, ask for status type */
231   if (ua->argc == 1) {
232     char prmt[MAX_NAME_LENGTH];
233 
234     StartPrompt(ua, _("Status available for:\n"));
235     AddPrompt(ua, NT_("Director"));
236     AddPrompt(ua, NT_("Storage"));
237     AddPrompt(ua, NT_("Client"));
238     AddPrompt(ua, NT_("Scheduler"));
239     AddPrompt(ua, NT_("All"));
240     Dmsg0(20, "DoPrompt: select daemon\n");
241     if ((item = DoPrompt(ua, "", _("Select daemon type for status"), prmt,
242                          sizeof(prmt)))
243         < 0) {
244       return true;
245     }
246     Dmsg1(20, "item=%d\n", item);
247     switch (item) {
248       case 0: /* Director */
249         DoDirectorStatus(ua);
250         break;
251       case 1:
252         store = select_storage_resource(ua);
253         if (store) { StorageStatus(ua, store, NULL); }
254         break;
255       case 2:
256         client = select_client_resource(ua);
257         if (client) { ClientStatus(ua, client, NULL); }
258         break;
259       case 3:
260         DoSchedulerStatus(ua);
261         break;
262       case 4:
263         DoAllStatus(ua);
264         break;
265       default:
266         break;
267     }
268   }
269   return true;
270 }
271 
DoAllStatus(UaContext * ua)272 static void DoAllStatus(UaContext* ua)
273 {
274   StorageResource *store, **unique_store;
275   ClientResource *client, **unique_client;
276   int i, j;
277   bool found;
278   int32_t previous_JobStatus = 0;
279 
280   DoDirectorStatus(ua);
281 
282   /* Count Storage items */
283   LockRes(my_config);
284   i = 0;
285   foreach_res (store, R_STORAGE) {
286     i++;
287   }
288   unique_store = (StorageResource**)malloc(i * sizeof(StorageResource));
289   /* Find Unique Storage address/port */
290   i = 0;
291   foreach_res (store, R_STORAGE) {
292     found = false;
293     if (!ua->AclAccessOk(Storage_ACL, store->resource_name_)) { continue; }
294     for (j = 0; j < i; j++) {
295       if (bstrcmp(unique_store[j]->address, store->address)
296           && unique_store[j]->SDport == store->SDport) {
297         found = true;
298         break;
299       }
300     }
301     if (!found) {
302       unique_store[i++] = store;
303       Dmsg2(40, "Stuffing: %s:%d\n", store->address, store->SDport);
304     }
305   }
306   UnlockRes(my_config);
307 
308   previous_JobStatus = ua->jcr->JobStatus;
309 
310   /* Call each unique Storage daemon */
311   for (j = 0; j < i; j++) {
312     StorageStatus(ua, unique_store[j], NULL);
313     ua->jcr->JobStatus = previous_JobStatus;
314   }
315   free(unique_store);
316 
317   /* Count Client items */
318   LockRes(my_config);
319   i = 0;
320   foreach_res (client, R_CLIENT) {
321     i++;
322   }
323   unique_client = (ClientResource**)malloc(i * sizeof(ClientResource));
324   /* Find Unique Client address/port */
325   i = 0;
326   foreach_res (client, R_CLIENT) {
327     found = false;
328     if (!ua->AclAccessOk(Client_ACL, client->resource_name_)) { continue; }
329     for (j = 0; j < i; j++) {
330       if (bstrcmp(unique_client[j]->address, client->address)
331           && unique_client[j]->FDport == client->FDport) {
332         found = true;
333         break;
334       }
335     }
336     if (!found) {
337       unique_client[i++] = client;
338       Dmsg2(40, "Stuffing: %s:%d\n", client->address, client->FDport);
339     }
340   }
341   UnlockRes(my_config);
342 
343   previous_JobStatus = ua->jcr->JobStatus;
344 
345   /* Call each unique File daemon */
346   for (j = 0; j < i; j++) {
347     ClientStatus(ua, unique_client[j], NULL);
348     ua->jcr->JobStatus = previous_JobStatus;
349   }
350   free(unique_client);
351 }
352 
ListDirStatusHeader(UaContext * ua)353 void ListDirStatusHeader(UaContext* ua)
354 {
355   int len, cnt;
356   CatalogResource* catalog;
357   char dt[MAX_TIME_LENGTH];
358   PoolMem msg(PM_FNAME), dbdrivers(PM_FNAME);
359 
360   cnt = 0;
361   foreach_res (catalog, R_CATALOG) {
362     if (cnt) { dbdrivers.strcat(" "); }
363     dbdrivers.strcat(catalog->db_driver);
364     cnt++;
365   }
366   ua->SendMsg(_("%s Version: %s (%s) %s\n"), my_name,
367               kBareosVersionStrings.Full, kBareosVersionStrings.Date,
368               kBareosVersionStrings.GetOsInfo());
369   bstrftime_nc(dt, sizeof(dt), daemon_start_time);
370   ua->SendMsg(
371       _("Daemon started %s. Jobs: run=%d, running=%d db:%s, %s binary\n"), dt,
372       num_jobs_run, JobCount(), dbdrivers.c_str(),
373       kBareosVersionStrings.BinaryInfo);
374 
375   if (me->secure_erase_cmdline) {
376     ua->SendMsg(_(" secure erase command='%s'\n"), me->secure_erase_cmdline);
377   }
378 
379   len = ListDirPlugins(msg);
380   if (len > 0) { ua->SendMsg("%s\n", msg.c_str()); }
381 
382   if (my_config->HasWarnings()) {
383     ua->SendMsg(
384         _("\n"
385           "There are WARNINGS for the director configuration!\n"
386           "See 'status configuration' for details.\n"
387           "\n"));
388   }
389 }
390 
show_scheduled_preview(UaContext * ua,ScheduleResource * sched,PoolMem & overview,int * max_date_len,time_t time_to_check)391 static bool show_scheduled_preview(UaContext* ua,
392                                    ScheduleResource* sched,
393                                    PoolMem& overview,
394                                    int* max_date_len,
395                                    time_t time_to_check)
396 {
397   int date_len;
398   char dt[MAX_TIME_LENGTH];
399   time_t runtime;
400   RunResource* run;
401   PoolMem temp(PM_NAME);
402 
403   RunHourValidator run_hour_validator(time_to_check);
404 
405   for (run = sched->run; run; run = run->next) {
406     bool run_now;
407     int cnt = 0;
408 
409     run_now = run_hour_validator.TriggersOn(run->date_time_bitfield);
410 
411     if (run_now) {
412       /*
413        * Find time (time_t) job is to be run
414        */
415       struct tm tm;
416       Blocaltime(&time_to_check, &tm); /* Reset tm structure */
417       tm.tm_min = run->minute;         /* Set run minute */
418       tm.tm_sec = 0;                   /* Zero secs */
419 
420       /*
421        * Convert the time into a user parsable string.
422        * As we use locale specific strings for weekday and month we
423        * need to keep track of the longest data string used.
424        */
425       runtime = mktime(&tm);
426       bstrftime_wd(dt, sizeof(dt), runtime);
427       date_len = strlen(dt);
428       if (date_len > *max_date_len) {
429         if (*max_date_len == 0) {
430           /*
431            * When the datelen changes during the loop the locale generates a
432            * date string that is variable. Only thing we can do about that is
433            * start from scratch again. We invoke this by return false from this
434            * function.
435            */
436           *max_date_len = date_len;
437           PmStrcpy(overview, "");
438           return false;
439         } else {
440           /*
441            * This is the first determined length we use this until we are proven
442            * wrong.
443            */
444           *max_date_len = date_len;
445         }
446       }
447 
448       Mmsg(temp, "%-*s  %-22.22s  ", *max_date_len, dt, sched->resource_name_);
449       PmStrcat(overview, temp.c_str());
450 
451       if (run->level) {
452         if (cnt++ > 0) { PmStrcat(overview, " "); }
453         Mmsg(temp, "Level=%s", JobLevelToString(run->level));
454         PmStrcat(overview, temp.c_str());
455       }
456 
457       if (run->Priority) {
458         if (cnt++ > 0) { PmStrcat(overview, " "); }
459         Mmsg(temp, "Priority=%d", run->Priority);
460         PmStrcat(overview, temp.c_str());
461       }
462 
463       if (run->spool_data_set) {
464         if (cnt++ > 0) { PmStrcat(overview, " "); }
465         Mmsg(temp, "Spool Data=%d", run->spool_data);
466         PmStrcat(overview, temp.c_str());
467       }
468 
469       if (run->accurate_set) {
470         if (cnt++ > 0) { PmStrcat(overview, " "); }
471         Mmsg(temp, "Accurate=%d", run->accurate);
472         PmStrcat(overview, temp.c_str());
473       }
474 
475       if (run->pool) {
476         if (cnt++ > 0) { PmStrcat(overview, " "); }
477         Mmsg(temp, "Pool=%s", run->pool->resource_name_);
478         PmStrcat(overview, temp.c_str());
479       }
480 
481       if (run->storage) {
482         if (cnt++ > 0) { PmStrcat(overview, " "); }
483         Mmsg(temp, "Storage=%s", run->storage->resource_name_);
484         PmStrcat(overview, temp.c_str());
485       }
486 
487       if (run->msgs) {
488         if (cnt++ > 0) { PmStrcat(overview, " "); }
489         Mmsg(temp, "Messages=%s", run->msgs->resource_name_);
490         PmStrcat(overview, temp.c_str());
491       }
492 
493       PmStrcat(overview, "\n");
494     }
495   }
496 
497   /*
498    * If we make it till here the length of the datefield is constant or didn't
499    * change.
500    */
501   return true;
502 }
503 
504 /**
505  * Check the number of clients in the DB against the configured number of
506  * subscriptions
507  *
508  * Return true if (number of clients < number of subscriptions), else
509  * return false
510  */
DoSubscriptionStatus(UaContext * ua)511 static bool DoSubscriptionStatus(UaContext* ua)
512 {
513   int available;
514   bool retval = false;
515 
516   /*
517    * See if we need to check.
518    */
519   if (me->subscriptions == 0) {
520     ua->SendMsg(_("No subscriptions configured in director.\n"));
521     retval = true;
522     goto bail_out;
523   }
524 
525   if (me->subscriptions_used <= 0) {
526     ua->ErrorMsg(_("No clients defined.\n"));
527     goto bail_out;
528   } else {
529     available = me->subscriptions - me->subscriptions_used;
530     if (available < 0) {
531       ua->SendMsg(
532           _("Warning! No available subscriptions: %d (%d/%d) (used/total)\n"),
533           available, me->subscriptions_used, me->subscriptions);
534     } else {
535       ua->SendMsg(_("Ok: available subscriptions: %d (%d/%d) (used/total)\n"),
536                   available, me->subscriptions_used, me->subscriptions);
537       retval = true;
538     }
539   }
540 
541 bail_out:
542   return retval;
543 }
544 
DoConfigurationStatus(UaContext * ua)545 static void DoConfigurationStatus(UaContext* ua)
546 {
547   if (!ua->AclAccessOk(Command_ACL, "configure")) {
548     ua->ErrorMsg(_("%s %s: is an invalid command or needs access right to the"
549                    " \"configure\" command.\n"),
550                  ua->argk[0], ua->argk[1]);
551   } else {
552     if (my_config->HasWarnings()) {
553       ua->SendMsg(_("Deprecated configuration settings detected:\n"));
554       for (auto& warning : my_config->GetWarnings()) {
555         ua->SendMsg(" * %s\n", warning.c_str());
556       }
557     } else {
558       ua->SendMsg(_("No deprecated configuration settings detected.\n"));
559     }
560   }
561 }
562 
DoSchedulerStatus(UaContext * ua)563 static void DoSchedulerStatus(UaContext* ua)
564 {
565   int i;
566   int max_date_len = 0;
567   int days = DEFAULT_STATUS_SCHED_DAYS; /* Default days for preview */
568   bool schedule_given_by_cmdline_args = false;
569   time_t time_to_check, now, start, stop;
570   char schedulename[MAX_NAME_LENGTH];
571   const int seconds_per_day = 86400; /* Number of seconds in one day */
572   const int seconds_per_hour = 3600; /* Number of seconds in one hour */
573   ClientResource* client = NULL;
574   JobResource* job = NULL;
575   ScheduleResource* sched;
576   PoolMem overview(PM_MESSAGE);
577 
578   now = time(NULL); /* Initialize to now */
579   time_to_check = now;
580 
581   i = FindArgWithValue(ua, NT_("days"));
582   if (i >= 0) {
583     days = atoi(ua->argv[i]);
584     if (((days < -366) || (days > 366)) && !ua->api) {
585       ua->SendMsg(_(
586           "Ignoring invalid value for days. Allowed is -366 < days < 366.\n"));
587       days = DEFAULT_STATUS_SCHED_DAYS;
588     }
589   }
590 
591   /*
592    * Schedule given ?
593    */
594   i = FindArgWithValue(ua, NT_("schedule"));
595   if (i >= 0) {
596     bstrncpy(schedulename, ua->argv[i], sizeof(schedulename));
597     schedule_given_by_cmdline_args = true;
598   }
599 
600   /*
601    * Client given ?
602    */
603   i = FindArgWithValue(ua, NT_("client"));
604   if (i >= 0) { client = get_client_resource(ua); }
605 
606   /*
607    * Jobname given ?
608    */
609   i = FindArgWithValue(ua, NT_("job"));
610   if (i >= 0) {
611     job = ua->GetJobResWithName(ua->argv[i]);
612 
613     /*
614      * If a bogus jobname was given ask for it interactively.
615      */
616     if (!job) { job = select_job_resource(ua); }
617   }
618 
619   ua->SendMsg("Scheduler Jobs:\n\n");
620   ua->SendMsg("Schedule               Jobs Triggered\n");
621   ua->SendMsg("===========================================================\n");
622 
623   LockRes(my_config);
624   foreach_res (sched, R_SCHEDULE) {
625     int cnt = 0;
626 
627     if (!sched->enabled) { continue; }
628 
629     if (!ua->AclAccessOk(Schedule_ACL, sched->resource_name_)) { continue; }
630 
631     if (schedule_given_by_cmdline_args) {
632       if (!bstrcmp(sched->resource_name_, schedulename)) { continue; }
633     }
634 
635     if (job) {
636       if (job->schedule
637           && bstrcmp(sched->resource_name_, job->schedule->resource_name_)) {
638         if (cnt++ == 0) { ua->SendMsg("%s\n", sched->resource_name_); }
639         if (!job->enabled || (job->client && !job->client->enabled)) {
640           ua->SendMsg("                       %s (disabled)\n",
641                       job->resource_name_);
642         } else {
643           ua->SendMsg("                       %s\n", job->resource_name_);
644         }
645       }
646     } else {
647       foreach_res (job, R_JOB) {
648         if (!ua->AclAccessOk(Job_ACL, job->resource_name_)) { continue; }
649 
650         if (client && job->client != client) { continue; }
651 
652         if (job->schedule
653             && bstrcmp(sched->resource_name_, job->schedule->resource_name_)) {
654           if (cnt++ == 0) { ua->SendMsg("%s\n", sched->resource_name_); }
655           if (!job->enabled || (job->client && !job->client->enabled)) {
656             ua->SendMsg("                       %s (disabled)\n",
657                         job->resource_name_);
658           } else {
659             ua->SendMsg("                       %s\n", job->resource_name_);
660           }
661         }
662       }
663     }
664 
665     if (cnt > 0) { ua->SendMsg("\n"); }
666   }
667   UnlockRes(my_config);
668 
669   /*
670    * Build an overview.
671    */
672   if (days > 0) { /* future */
673     start = now;
674     stop = now + (days * seconds_per_day);
675   } else { /* past */
676     start = now + (days * seconds_per_day);
677     stop = now;
678   }
679 
680 start_again:
681   time_to_check = start;
682   while (time_to_check < stop) {
683     if (client || job) {
684       /*
685        * List specific schedule.
686        */
687       if (job) {
688         if (job->schedule && job->schedule->enabled && job->enabled
689             && job->client->enabled) {
690           if (!show_scheduled_preview(ua, job->schedule, overview,
691                                       &max_date_len, time_to_check)) {
692             goto start_again;
693           }
694         }
695       } else {
696         LockRes(my_config);
697         foreach_res (job, R_JOB) {
698           if (!ua->AclAccessOk(Job_ACL, job->resource_name_)) { continue; }
699 
700           if (job->schedule && job->schedule->enabled && job->enabled
701               && job->client == client && job->client->enabled) {
702             if (!show_scheduled_preview(ua, job->schedule, overview,
703                                         &max_date_len, time_to_check)) {
704               job = NULL;
705               UnlockRes(my_config);
706               goto start_again;
707             }
708           }
709         }
710         UnlockRes(my_config);
711         job = NULL;
712       }
713     } else {
714       /*
715        * List all schedules.
716        */
717       LockRes(my_config);
718       foreach_res (sched, R_SCHEDULE) {
719         if (!sched->enabled) { continue; }
720 
721         if (!ua->AclAccessOk(Schedule_ACL, sched->resource_name_)) { continue; }
722 
723         if (schedule_given_by_cmdline_args) {
724           if (!bstrcmp(sched->resource_name_, schedulename)) { continue; }
725         }
726 
727         if (!show_scheduled_preview(ua, sched, overview, &max_date_len,
728                                     time_to_check)) {
729           UnlockRes(my_config);
730           goto start_again;
731         }
732       }
733       UnlockRes(my_config);
734     }
735 
736     time_to_check += seconds_per_hour; /* next hour */
737   }
738 
739   ua->SendMsg("====\n\n");
740   ua->SendMsg("Scheduler Preview for %d days:\n\n", days);
741   ua->SendMsg("%-*s  %-22s  %s\n", max_date_len, _("Date"), _("Schedule"),
742               _("Overrides"));
743   ua->SendMsg(
744       "==============================================================\n");
745   ua->SendMsg(overview.c_str());
746   ua->SendMsg("====\n");
747 }
748 
DoDirectorStatus(UaContext * ua)749 static void DoDirectorStatus(UaContext* ua)
750 {
751   ListDirStatusHeader(ua);
752 
753   /*
754    * List scheduled Jobs
755    */
756   ListScheduledJobs(ua);
757 
758   /*
759    * List running jobs
760    */
761   ListRunningJobs(ua);
762 
763   /*
764    * List terminated jobs
765    */
766   ListTerminatedJobs(ua);
767 
768   ListConnectedClients(ua);
769 
770   ua->SendMsg("====\n");
771 }
772 
PrtRunhdr(UaContext * ua)773 static void PrtRunhdr(UaContext* ua)
774 {
775   if (!ua->api) {
776     ua->SendMsg(_("\nScheduled Jobs:\n"));
777     ua->SendMsg(
778         _("Level          Type     Pri  Scheduled          Name               "
779           "Volume\n"));
780     ua->SendMsg(
781         _("===================================================================="
782           "===============\n"));
783   }
784 }
785 
786 /* Scheduling packet */
787 struct sched_pkt {
788   dlink link; /* keep this as first item!!! */
789   JobResource* job;
790   int level;
791   int priority;
792   utime_t runtime;
793   PoolResource* pool;
794   StorageResource* store;
795 };
796 
PrtRuntime(UaContext * ua,sched_pkt * sp)797 static void PrtRuntime(UaContext* ua, sched_pkt* sp)
798 {
799   char dt[MAX_TIME_LENGTH];
800   const char* level_ptr;
801   bool ok = false;
802   bool CloseDb = false;
803   JobControlRecord* jcr = ua->jcr;
804   MediaDbRecord mr;
805   int orig_jobtype;
806 
807   orig_jobtype = jcr->getJobType();
808   if (sp->job->JobType == JT_BACKUP) {
809     jcr->db = NULL;
810     ok = CompleteJcrForJob(jcr, sp->job, sp->pool);
811     Dmsg1(250, "Using pool=%s\n", jcr->impl->res.pool->resource_name_);
812     if (jcr->db) { CloseDb = true; /* new db opened, remember to close it */ }
813     if (ok) {
814       mr.PoolId = jcr->impl->jr.PoolId;
815       jcr->impl->res.write_storage = sp->store;
816       SetStorageidInMr(jcr->impl->res.write_storage, &mr);
817       Dmsg0(250, "call FindNextVolumeForAppend\n");
818       /* no need to set ScratchPoolId, since we use fnv_no_create_vol */
819       ok = FindNextVolumeForAppend(jcr, &mr, 1, NULL, fnv_no_create_vol,
820                                    fnv_no_prune);
821     }
822     if (!ok) { bstrncpy(mr.VolumeName, "*unknown*", sizeof(mr.VolumeName)); }
823   }
824   bstrftime_nc(dt, sizeof(dt), sp->runtime);
825   switch (sp->job->JobType) {
826     case JT_ADMIN:
827     case JT_ARCHIVE:
828     case JT_RESTORE:
829       level_ptr = " ";
830       break;
831     default:
832       level_ptr = JobLevelToString(sp->level);
833       break;
834   }
835   if (ua->api) {
836     ua->SendMsg(_("%-14s\t%-8s\t%3d\t%-18s\t%-18s\t%s\n"), level_ptr,
837                 job_type_to_str(sp->job->JobType), sp->priority, dt,
838                 sp->job->resource_name_, mr.VolumeName);
839   } else {
840     ua->SendMsg(_("%-14s %-8s %3d  %-18s %-18s %s\n"), level_ptr,
841                 job_type_to_str(sp->job->JobType), sp->priority, dt,
842                 sp->job->resource_name_, mr.VolumeName);
843   }
844   if (CloseDb) { DbSqlClosePooledConnection(jcr, jcr->db); }
845   jcr->db = ua->db; /* restore ua db to jcr */
846   jcr->setJobType(orig_jobtype);
847 }
848 
849 /**
850  * Sort items by runtime, priority
851  */
CompareByRuntimePriority(void * item1,void * item2)852 static int CompareByRuntimePriority(void* item1, void* item2)
853 {
854   sched_pkt* p1 = (sched_pkt*)item1;
855   sched_pkt* p2 = (sched_pkt*)item2;
856 
857   if (p1->runtime < p2->runtime) {
858     return -1;
859   } else if (p1->runtime > p2->runtime) {
860     return 1;
861   }
862 
863   if (p1->priority < p2->priority) {
864     return -1;
865   } else if (p1->priority > p2->priority) {
866     return 1;
867   }
868 
869   return 0;
870 }
871 
872 /**
873  * Find all jobs to be run in roughly the next 24 hours.
874  */
ListScheduledJobs(UaContext * ua)875 static void ListScheduledJobs(UaContext* ua)
876 {
877   utime_t runtime;
878   RunResource* run;
879   JobResource* job;
880   int level, num_jobs = 0;
881   int priority;
882   bool hdr_printed = false;
883   auto sched = std::make_unique<dlist>();
884   sched_pkt* sp;
885   int days, i;
886 
887   Dmsg0(200, "enter list_sched_jobs()\n");
888 
889   days = 1;
890   i = FindArgWithValue(ua, NT_("days"));
891   if (i >= 0) {
892     days = atoi(ua->argv[i]);
893     if (((days < 0) || (days > 500)) && !ua->api) {
894       ua->SendMsg(_("Ignoring invalid value for days. Max is 500.\n"));
895       days = 1;
896     }
897   }
898 
899   /*
900    * Loop through all jobs
901    */
902   LockRes(my_config);
903   foreach_res (job, R_JOB) {
904     if (!ua->AclAccessOk(Job_ACL, job->resource_name_) || !job->enabled
905         || (job->client && !job->client->enabled)) {
906       continue;
907     }
908     for (run = NULL; (run = find_next_run(run, job, runtime, days));) {
909       UnifiedStorageResource store;
910       level = job->JobLevel;
911       if (run->level) { level = run->level; }
912       priority = job->Priority;
913       if (run->Priority) { priority = run->Priority; }
914       if (!hdr_printed) {
915         PrtRunhdr(ua);
916         hdr_printed = true;
917       }
918       sp = (sched_pkt*)malloc(sizeof(sched_pkt));
919       sp->job = job;
920       sp->level = level;
921       sp->priority = priority;
922       sp->runtime = runtime;
923       sp->pool = run->pool;
924       GetJobStorage(&store, job, run);
925       sp->store = store.store;
926       if (sp->store) {
927         Dmsg3(250, "job=%s storage=%s MediaType=%s\n", job->resource_name_,
928               sp->store->resource_name_, sp->store->media_type);
929       } else {
930         Dmsg1(250, "job=%s could not get job storage\n", job->resource_name_);
931       }
932       sched->BinaryInsertMultiple(sp, CompareByRuntimePriority);
933       num_jobs++;
934     }
935   } /* end for loop over resources */
936   UnlockRes(my_config);
937   foreach_dlist (sp, sched) {
938     PrtRuntime(ua, sp);
939   }
940   if (num_jobs == 0 && !ua->api) { ua->SendMsg(_("No Scheduled Jobs.\n")); }
941   if (!ua->api) ua->SendMsg("====\n");
942   Dmsg0(200, "Leave list_sched_jobs_runs()\n");
943 }
944 
ListRunningJobs(UaContext * ua)945 static void ListRunningJobs(UaContext* ua)
946 {
947   JobControlRecord* jcr;
948   int njobs = 0;
949   const char* msg;
950   char* emsg; /* edited message */
951   char dt[MAX_TIME_LENGTH];
952   char level[10];
953   bool pool_mem = false;
954 
955   Dmsg0(200, "enter list_run_jobs()\n");
956   if (!ua->api) ua->SendMsg(_("\nRunning Jobs:\n"));
957   foreach_jcr (jcr) {
958     if (jcr->JobId == 0) { /* this is us */
959       /* this is a console or other control job. We only show console
960        * jobs in the status output.
961        */
962       if (jcr->is_JobType(JT_CONSOLE) && !ua->api) {
963         bstrftime_nc(dt, sizeof(dt), jcr->start_time);
964         ua->SendMsg(_("Console connected at %s\n"), dt);
965       }
966       continue;
967     }
968     njobs++;
969   }
970   endeach_jcr(jcr);
971 
972   if (njobs == 0) {
973     /* Note the following message is used in regress -- don't change */
974     if (!ua->api) ua->SendMsg(_("No Jobs running.\n====\n"));
975     Dmsg0(200, "leave list_run_jobs()\n");
976     return;
977   }
978   njobs = 0;
979   if (!ua->api) {
980     ua->SendMsg(_(" JobId Level   Name                       Status\n"));
981     ua->SendMsg(
982         _("===================================================================="
983           "==\n"));
984   }
985   foreach_jcr (jcr) {
986     if (jcr->JobId == 0
987         || !ua->AclAccessOk(Job_ACL, jcr->impl->res.job->resource_name_)) {
988       continue;
989     }
990     njobs++;
991     switch (jcr->JobStatus) {
992       case JS_Created:
993         msg = _("is waiting execution");
994         break;
995       case JS_Running:
996         msg = _("is running");
997         break;
998       case JS_Blocked:
999         msg = _("is blocked");
1000         break;
1001       case JS_Terminated:
1002         msg = _("has terminated");
1003         break;
1004       case JS_Warnings:
1005         msg = _("has terminated with warnings");
1006         break;
1007       case JS_ErrorTerminated:
1008         msg = _("has erred");
1009         break;
1010       case JS_Error:
1011         msg = _("has errors");
1012         break;
1013       case JS_FatalError:
1014         msg = _("has a fatal error");
1015         break;
1016       case JS_Differences:
1017         msg = _("has verify differences");
1018         break;
1019       case JS_Canceled:
1020         msg = _("has been canceled");
1021         break;
1022       case JS_WaitFD:
1023         emsg = (char*)GetPoolMemory(PM_FNAME);
1024         if (!jcr->impl->res.client) {
1025           Mmsg(emsg, _("is waiting on Client"));
1026         } else {
1027           Mmsg(emsg, _("is waiting on Client %s"),
1028                jcr->impl->res.client->resource_name_);
1029         }
1030         pool_mem = true;
1031         msg = emsg;
1032         break;
1033       case JS_WaitSD:
1034         emsg = (char*)GetPoolMemory(PM_FNAME);
1035         if (jcr->impl->res.write_storage) {
1036           Mmsg(emsg, _("is waiting on Storage \"%s\""),
1037                jcr->impl->res.write_storage->resource_name_);
1038         } else if (jcr->impl->res.read_storage) {
1039           Mmsg(emsg, _("is waiting on Storage \"%s\""),
1040                jcr->impl->res.read_storage->resource_name_);
1041         } else {
1042           Mmsg(emsg, _("is waiting on Storage"));
1043         }
1044         pool_mem = true;
1045         msg = emsg;
1046         break;
1047       case JS_WaitStoreRes:
1048         msg = _("is waiting on max Storage jobs");
1049         break;
1050       case JS_WaitClientRes:
1051         msg = _("is waiting on max Client jobs");
1052         break;
1053       case JS_WaitJobRes:
1054         msg = _("is waiting on max Job jobs");
1055         break;
1056       case JS_WaitMaxJobs:
1057         msg = _("is waiting on max total jobs");
1058         break;
1059       case JS_WaitStartTime:
1060         emsg = (char*)GetPoolMemory(PM_FNAME);
1061         if (jcr->sched_time) {
1062           char dt[MAX_TIME_LENGTH];
1063           bstrftime_nc(dt, sizeof(dt), jcr->sched_time);
1064           Mmsg(emsg, _("is waiting for its start time at %s"), dt);
1065         } else {
1066           Mmsg(emsg, _("is waiting for its start time"));
1067         }
1068         pool_mem = true;
1069         msg = emsg;
1070         break;
1071       case JS_WaitPriority:
1072         msg = _("is waiting for higher priority jobs to finish");
1073         break;
1074       case JS_DataCommitting:
1075         msg = _("SD committing Data");
1076         break;
1077       case JS_DataDespooling:
1078         msg = _("SD despooling Data");
1079         break;
1080       case JS_AttrDespooling:
1081         msg = _("SD despooling Attributes");
1082         break;
1083       case JS_AttrInserting:
1084         msg = _("Dir inserting Attributes");
1085         break;
1086 
1087       default:
1088         emsg = (char*)GetPoolMemory(PM_FNAME);
1089         Mmsg(emsg, _("is in unknown state %c"), jcr->JobStatus);
1090         pool_mem = true;
1091         msg = emsg;
1092         break;
1093     }
1094     /*
1095      * Now report Storage daemon status code
1096      */
1097     switch (jcr->impl->SDJobStatus) {
1098       case JS_WaitMount:
1099         if (pool_mem) {
1100           FreePoolMemory(emsg);
1101           pool_mem = false;
1102         }
1103         msg = _("is waiting for a mount request");
1104         break;
1105       case JS_WaitMedia:
1106         if (pool_mem) {
1107           FreePoolMemory(emsg);
1108           pool_mem = false;
1109         }
1110         msg = _("is waiting for an appendable Volume");
1111         break;
1112       case JS_WaitFD:
1113         if (!pool_mem) {
1114           emsg = (char*)GetPoolMemory(PM_FNAME);
1115           pool_mem = true;
1116         }
1117         if (!jcr->file_bsock) {
1118           /*
1119            * client initiated connection
1120            */
1121           Mmsg(emsg, _("is waiting for Client to connect (Client Initiated "
1122                        "Connection)"));
1123         } else if (!jcr->impl->res.client || !jcr->impl->res.write_storage) {
1124           Mmsg(emsg, _("is waiting for Client to connect to Storage daemon"));
1125         } else {
1126           Mmsg(emsg, _("is waiting for Client %s to connect to Storage %s"),
1127                jcr->impl->res.client->resource_name_,
1128                jcr->impl->res.write_storage->resource_name_);
1129         }
1130         msg = emsg;
1131         break;
1132       case JS_DataCommitting:
1133         msg = _("SD committing Data");
1134         break;
1135       case JS_DataDespooling:
1136         msg = _("SD despooling Data");
1137         break;
1138       case JS_AttrDespooling:
1139         msg = _("SD despooling Attributes");
1140         break;
1141       case JS_AttrInserting:
1142         msg = _("Dir inserting Attributes");
1143         break;
1144     }
1145     switch (jcr->getJobType()) {
1146       case JT_ADMIN:
1147       case JT_ARCHIVE:
1148       case JT_RESTORE:
1149         bstrncpy(level, "      ", sizeof(level));
1150         break;
1151       default:
1152         bstrncpy(level, JobLevelToString(jcr->getJobLevel()), sizeof(level));
1153         level[7] = 0;
1154         break;
1155     }
1156 
1157     if (ua->api) {
1158       BashSpaces(jcr->comment);
1159       ua->SendMsg(_("%6d\t%-6s\t%-20s\t%s\t%s\n"), jcr->JobId, level, jcr->Job,
1160                   msg, jcr->comment);
1161       UnbashSpaces(jcr->comment);
1162     } else {
1163       ua->SendMsg(_("%6d %-6s  %-20s %s\n"), jcr->JobId, level, jcr->Job, msg);
1164       /* Display comments if any */
1165       if (*jcr->comment) {
1166         ua->SendMsg(_("               %-30s\n"), jcr->comment);
1167       }
1168     }
1169 
1170     if (pool_mem) {
1171       FreePoolMemory(emsg);
1172       pool_mem = false;
1173     }
1174   }
1175   endeach_jcr(jcr);
1176   if (!ua->api) ua->SendMsg("====\n");
1177   Dmsg0(200, "leave list_run_jobs()\n");
1178 }
1179 
ListTerminatedJobs(UaContext * ua)1180 static void ListTerminatedJobs(UaContext* ua)
1181 {
1182   char dt[MAX_TIME_LENGTH], b1[30], b2[30];
1183   char level[10];
1184 
1185   if (RecentJobResultsList::IsEmpty()) {
1186     if (!ua->api) ua->SendMsg(_("No Terminated Jobs.\n"));
1187     return;
1188   }
1189   if (!ua->api) {
1190     ua->SendMsg(_("\nTerminated Jobs:\n"));
1191     ua->SendMsg(
1192         _(" JobId  Level    Files      Bytes   Status   Finished        Name "
1193           "\n"));
1194     ua->SendMsg(
1195         _("===================================================================="
1196           "\n"));
1197   }
1198 
1199   for (const RecentJobResultsList::JobResult& je :
1200        RecentJobResultsList::Get()) {
1201     char JobName[MAX_NAME_LENGTH];
1202     const char* termstat;
1203 
1204     bstrncpy(JobName, je.Job, sizeof(JobName));
1205     /* There are three periods after the Job name */
1206     char* p;
1207     for (int i = 0; i < 3; i++) {
1208       if ((p = strrchr(JobName, '.')) != NULL) { *p = 0; }
1209     }
1210 
1211     if (!ua->AclAccessOk(Job_ACL, JobName)) { continue; }
1212 
1213     bstrftime_nc(dt, sizeof(dt), je.end_time);
1214     switch (je.JobType) {
1215       case JT_ADMIN:
1216       case JT_ARCHIVE:
1217       case JT_RESTORE:
1218         bstrncpy(level, "    ", sizeof(level));
1219         break;
1220       default:
1221         bstrncpy(level, JobLevelToString(je.JobLevel), sizeof(level));
1222         level[4] = 0;
1223         break;
1224     }
1225     switch (je.JobStatus) {
1226       case JS_Created:
1227         termstat = _("Created");
1228         break;
1229       case JS_FatalError:
1230       case JS_ErrorTerminated:
1231         termstat = _("Error");
1232         break;
1233       case JS_Differences:
1234         termstat = _("Diffs");
1235         break;
1236       case JS_Canceled:
1237         termstat = _("Cancel");
1238         break;
1239       case JS_Terminated:
1240         termstat = _("OK");
1241         break;
1242       case JS_Warnings:
1243         termstat = _("OK -- with warnings");
1244         break;
1245       default:
1246         termstat = _("Other");
1247         break;
1248     }
1249     if (ua->api) {
1250       ua->SendMsg(_("%6d\t%-6s\t%8s\t%10s\t%-7s\t%-8s\t%s\n"), je.JobId, level,
1251                   edit_uint64_with_commas(je.JobFiles, b1),
1252                   edit_uint64_with_suffix(je.JobBytes, b2), termstat, dt,
1253                   JobName);
1254     } else {
1255       ua->SendMsg(_("%6d  %-6s %8s %10s  %-7s  %-8s %s\n"), je.JobId, level,
1256                   edit_uint64_with_commas(je.JobFiles, b1),
1257                   edit_uint64_with_suffix(je.JobBytes, b2), termstat, dt,
1258                   JobName);
1259     }
1260   }
1261   if (!ua->api) ua->SendMsg(_("\n"));
1262 }
1263 
1264 
ListConnectedClients(UaContext * ua)1265 static void ListConnectedClients(UaContext* ua)
1266 {
1267   Connection* connection = NULL;
1268   alist* connections = NULL;
1269   const char* separator = "====================";
1270   char dt[MAX_TIME_LENGTH];
1271 
1272   ua->send->Decoration("\n");
1273   ua->send->Decoration("Client Initiated Connections (waiting for jobs):\n");
1274   connections = get_client_connections()->get_as_alist();
1275   ua->send->Decoration("%-20s%-20s%-20s%-40s\n", "Connect time", "Protocol",
1276                        "Authenticated", "Name");
1277   ua->send->Decoration("%-20s%-20s%-20s%-20s%-20s\n", separator, separator,
1278                        separator, separator, separator);
1279   ua->send->ArrayStart("client-connection");
1280   foreach_alist (connection, connections) {
1281     ua->send->ObjectStart();
1282     bstrftime_nc(dt, sizeof(dt), connection->ConnectTime());
1283     ua->send->ObjectKeyValue("ConnectTime", dt, "%-20s");
1284     ua->send->ObjectKeyValue("protocol_version", connection->protocol_version(),
1285                              "%-20d");
1286     ua->send->ObjectKeyValue("authenticated", connection->authenticated(),
1287                              "%-20d");
1288     ua->send->ObjectKeyValue("name", connection->name(), "%-40s");
1289     ua->send->ObjectEnd();
1290     ua->send->Decoration("\n");
1291   }
1292   ua->send->ArrayEnd("client-connection");
1293 }
1294 
ContentSendInfoApi(UaContext * ua,char type,int Slot,char * vol_name)1295 static void ContentSendInfoApi(UaContext* ua,
1296                                char type,
1297                                int Slot,
1298                                char* vol_name)
1299 {
1300   char ed1[50], ed2[50], ed3[50];
1301   PoolDbRecord pr;
1302   MediaDbRecord mr;
1303   /* Type|Slot|RealSlot|Volume|Bytes|Status|MediaType|Pool|LastW|Expire */
1304   const char* slot_api_full_format = "%c|%hd|%hd|%s|%s|%s|%s|%s|%s|%s\n";
1305 
1306   bstrncpy(mr.VolumeName, vol_name, sizeof(mr.VolumeName));
1307   if (ua->db->GetMediaRecord(ua->jcr, &mr)) {
1308     pr.PoolId = mr.PoolId;
1309     if (!ua->db->GetPoolRecord(ua->jcr, &pr)) { strcpy(pr.Name, "?"); }
1310     ua->SendMsg(slot_api_full_format, type, Slot, mr.Slot, mr.VolumeName,
1311                 edit_uint64(mr.VolBytes, ed1), mr.VolStatus, mr.MediaType,
1312                 pr.Name, edit_uint64(mr.LastWritten, ed2),
1313                 edit_uint64(mr.LastWritten + mr.VolRetention, ed3));
1314   } else { /* Media unknown */
1315     ua->SendMsg(slot_api_full_format, type, Slot, 0, mr.VolumeName, "?", "?",
1316                 "?", "?", "0", "0");
1317   }
1318 }
1319 
ContentSendInfoJson(UaContext * ua,const char * type,int Slot,char * vol_name)1320 static void ContentSendInfoJson(UaContext* ua,
1321                                 const char* type,
1322                                 int Slot,
1323                                 char* vol_name)
1324 {
1325   PoolDbRecord pr;
1326   MediaDbRecord mr;
1327 
1328   bstrncpy(mr.VolumeName, vol_name, sizeof(mr.VolumeName));
1329   if (ua->db->GetMediaRecord(ua->jcr, &mr)) {
1330     pr.PoolId = mr.PoolId;
1331     if (!ua->db->GetPoolRecord(ua->jcr, &pr)) { strcpy(pr.Name, "?"); }
1332 
1333     ua->send->ObjectStart();
1334     ua->send->ObjectKeyValue("type", type, "%s\n");
1335     ua->send->ObjectKeyValue("slotnr", Slot, "%hd\n");
1336     ua->send->ObjectKeyValue("content", "full", "%s\n");
1337     ua->send->ObjectKeyValue("mr_slotnr", mr.Slot, "%lld\n");
1338     ua->send->ObjectKeyValue("mr_volname", mr.VolumeName, "%s\n");
1339     ua->send->ObjectKeyValue("mr_volbytes", mr.VolBytes, "%lld\n");
1340     ua->send->ObjectKeyValue("mr_volstatus", mr.VolStatus, "%s\n");
1341     ua->send->ObjectKeyValue("mr_mediatype", mr.MediaType, "%s\n");
1342     ua->send->ObjectKeyValue("pr_name", pr.Name, "%s\n");
1343     ua->send->ObjectKeyValue("mr_lastwritten", mr.LastWritten, "%lld\n");
1344     ua->send->ObjectKeyValue("mr_expire", mr.LastWritten + mr.VolRetention,
1345                              "%lld\n");
1346     ua->send->ObjectEnd();
1347   } else { /* Media unknown */
1348     ua->send->ObjectStart();
1349     ua->send->ObjectKeyValue("type", type, "%s\n");
1350     ua->send->ObjectKeyValue("slotnr", Slot, "%hd\n");
1351     ua->send->ObjectKeyValue("content", "full", "%s\n");
1352     ua->send->ObjectKeyValue("mr_slotnr", (uint64_t)0, "%lld\n");
1353     ua->send->ObjectKeyValue("mr_volname", mr.VolumeName, "%s\n");
1354     ua->send->ObjectKeyValue("mr_volbytes", "?", "%s\n");
1355     ua->send->ObjectKeyValue("mr_volstatus", "?", "%s\n");
1356     ua->send->ObjectKeyValue("mr_mediatype", "?", "%s\n");
1357     ua->send->ObjectKeyValue("pr_name", "?", "%s\n");
1358     ua->send->ObjectKeyValue("mr_lastwritten", (uint64_t)0, "%lld\n");
1359     ua->send->ObjectKeyValue("mr_expire", (uint64_t)0, "%lld\n");
1360     ua->send->ObjectEnd();
1361   }
1362 }
1363 
1364 /**
1365  * Input (output of mxt-changer listall):
1366  *
1367  * Drive content:         D:Drive num:F:Slot loaded:Volume Name
1368  * D:0:F:2:vol2        or D:Drive num:E
1369  * D:1:F:42:vol42
1370  * D:3:E
1371  *
1372  * Slot content:
1373  * S:1:F:vol1             S:Slot num:F:Volume Name
1374  * S:2:E               or S:Slot num:E
1375  * S:3:F:vol4
1376  *
1377  * Import/Export tray slots:
1378  * I:10:F:vol10           I:Slot num:F:Volume Name
1379  * I:11:E              or I:Slot num:E
1380  * I:12:F:vol40
1381  *
1382  * If a drive is loaded, the slot *should* be empty
1383  *
1384  * Output:
1385  *
1386  * Drive list:       D|Drive num|Slot loaded|Volume Name
1387  * D|0|45|vol45
1388  * D|1|42|vol42
1389  * D|3||
1390  *
1391  * Slot list: Type|Slot|RealSlot|Volume|Bytes|Status|MediaType|Pool|LastW|Expire
1392  *
1393  * S|1|1|vol1|31417344|Full|LTO1-ANSI|Inc|1250858902|1282394902
1394  * S|2||||||||
1395  * S|3|3|vol4|15869952|Append|LTO1-ANSI|Inc|1250858907|1282394907
1396  */
StatusContentApi(UaContext * ua,StorageResource * store)1397 static void StatusContentApi(UaContext* ua, StorageResource* store)
1398 {
1399   vol_list_t *vl1, *vl2;
1400   changer_vol_list_t* vol_list = NULL;
1401   const char* slot_api_drive_full_format = "%c|%hd|%hd|%s\n";
1402   const char* slot_api_drive_empty_format = "%c|%hd||\n";
1403   const char* slot_api_slot_empty_format = "%c|%hd||||||||\n";
1404 
1405   if (!OpenClientDb(ua)) { return; }
1406 
1407   vol_list = get_vol_list_from_storage(ua, store, true /* listall */,
1408                                        true /* want to see all slots */);
1409   if (!vol_list) {
1410     ua->WarningMsg(_("No Volumes found, or no barcodes.\n"));
1411     goto bail_out;
1412   }
1413 
1414   foreach_dlist (vl1, vol_list->contents) {
1415     switch (vl1->slot_type) {
1416       case slot_type_t::kSlotTypeDrive:
1417         switch (vl1->slot_status) {
1418           case slot_status_t::kSlotStatusFull:
1419             ua->SendMsg(slot_api_drive_full_format, 'D',
1420                         vl1->bareos_slot_number,
1421                         vl1->currently_loaded_slot_number, vl1->VolName);
1422             break;
1423           case slot_status_t::kSlotStatusEmpty:
1424             ua->SendMsg(slot_api_drive_empty_format, 'D',
1425                         vl1->bareos_slot_number);
1426             break;
1427           default:
1428             break;
1429         }
1430         break;
1431       case slot_type_t::kSlotTypeStorage:
1432       case slot_type_t::kSlotTypeImport:
1433         switch (vl1->slot_status) {
1434           case slot_status_t::kSlotStatusFull:
1435             switch (vl1->slot_type) {
1436               case slot_type_t::kSlotTypeStorage:
1437                 ContentSendInfoApi(ua, 'S', vl1->bareos_slot_number,
1438                                    vl1->VolName);
1439                 break;
1440               case slot_type_t::kSlotTypeImport:
1441                 ContentSendInfoApi(ua, 'I', vl1->bareos_slot_number,
1442                                    vl1->VolName);
1443                 break;
1444               default:
1445                 break;
1446             }
1447             break;
1448           case slot_status_t::kSlotStatusEmpty:
1449             /*
1450              * See if this empty slot is empty because the volume is loaded
1451              * in one of the drives.
1452              */
1453             vl2 = vol_is_loaded_in_drive(store, vol_list,
1454                                          vl1->bareos_slot_number);
1455             if (vl2) {
1456               switch (vl1->slot_type) {
1457                 case slot_type_t::kSlotTypeStorage:
1458                   ContentSendInfoApi(ua, 'S', vl1->bareos_slot_number,
1459                                      vl2->VolName);
1460                   break;
1461                 case slot_type_t::kSlotTypeImport:
1462                   ContentSendInfoApi(ua, 'I', vl1->bareos_slot_number,
1463                                      vl2->VolName);
1464                   break;
1465                 default:
1466                   break;
1467               }
1468               continue;
1469             }
1470 
1471             switch (vl1->slot_type) {
1472               case slot_type_t::kSlotTypeStorage:
1473                 ua->SendMsg(slot_api_slot_empty_format, 'S',
1474                             vl1->bareos_slot_number);
1475                 break;
1476               case slot_type_t::kSlotTypeImport:
1477                 ua->SendMsg(slot_api_slot_empty_format, 'I',
1478                             vl1->bareos_slot_number);
1479                 break;
1480               default:
1481                 break;
1482             }
1483             break;
1484           default:
1485             break;
1486         }
1487         break;
1488       default:
1489         break;
1490     }
1491   }
1492 
1493 bail_out:
1494   if (vol_list) { StorageReleaseVolList(store, vol_list); }
1495   CloseSdBsock(ua);
1496 
1497   return;
1498 }
1499 
StatusContentJson(UaContext * ua,StorageResource * store)1500 static void StatusContentJson(UaContext* ua, StorageResource* store)
1501 {
1502   vol_list_t *vl1, *vl2;
1503   changer_vol_list_t* vol_list = NULL;
1504 
1505   if (!OpenClientDb(ua)) { return; }
1506 
1507   vol_list = get_vol_list_from_storage(ua, store, true /* listall */,
1508                                        true /* want to see all slots */);
1509   if (!vol_list) {
1510     ua->WarningMsg(_("No Volumes found, or no barcodes.\n"));
1511     goto bail_out;
1512   }
1513 
1514   ua->send->ArrayStart("contents");
1515   foreach_dlist (vl1, vol_list->contents) {
1516     switch (vl1->slot_type) {
1517       case slot_type_t::kSlotTypeDrive:
1518         ua->send->ObjectStart();
1519         ua->send->ObjectKeyValue("type", "drive", "%s\n");
1520         ua->send->ObjectKeyValue("slotnr", vl1->bareos_slot_number, "%hd\n");
1521         switch (vl1->slot_status) {
1522           case slot_status_t::kSlotStatusFull:
1523             ua->send->ObjectKeyValue("content", "full", "%s\n");
1524             ua->send->ObjectKeyValue(
1525                 "loaded", vl1->currently_loaded_slot_number, "%hd\n");
1526             ua->send->ObjectKeyValue("volname", vl1->VolName, "%s\n");
1527             break;
1528           case slot_status_t::kSlotStatusEmpty:
1529             ua->send->ObjectKeyValue("content", "empty", "%s\n");
1530             break;
1531           default:
1532             break;
1533         }
1534         ua->send->ObjectEnd();
1535         break;
1536       case slot_type_t::kSlotTypeStorage:
1537       case slot_type_t::kSlotTypeImport:
1538         switch (vl1->slot_status) {
1539           case slot_status_t::kSlotStatusFull:
1540             switch (vl1->slot_type) {
1541               case slot_type_t::kSlotTypeStorage:
1542                 ContentSendInfoJson(ua, "slot", vl1->bareos_slot_number,
1543                                     vl1->VolName);
1544                 break;
1545               case slot_type_t::kSlotTypeImport:
1546                 ContentSendInfoJson(ua, "import_slot", vl1->bareos_slot_number,
1547                                     vl1->VolName);
1548                 break;
1549               default:
1550                 break;
1551             }
1552             break;
1553           case slot_status_t::kSlotStatusEmpty:
1554             /*
1555              * See if this empty slot is empty because the volume is loaded
1556              * in one of the drives.
1557              */
1558             vl2 = vol_is_loaded_in_drive(store, vol_list,
1559                                          vl1->bareos_slot_number);
1560             if (vl2) {
1561               switch (vl1->slot_type) {
1562                 case slot_type_t::kSlotTypeStorage:
1563                   ContentSendInfoJson(ua, "slot", vl1->bareos_slot_number,
1564                                       vl2->VolName);
1565                   break;
1566                 case slot_type_t::kSlotTypeImport:
1567                   ContentSendInfoJson(ua, "import_slot",
1568                                       vl1->bareos_slot_number, vl2->VolName);
1569                   break;
1570                 default:
1571                   break;
1572               }
1573               continue;
1574             }
1575 
1576             switch (vl1->slot_type) {
1577               case slot_type_t::kSlotTypeStorage:
1578                 ua->send->ObjectStart();
1579                 ua->send->ObjectKeyValue("type", "slot", "%s\n");
1580                 ua->send->ObjectKeyValue("slotnr", vl1->bareos_slot_number,
1581                                          "%hd\n");
1582                 ua->send->ObjectKeyValue("content", "empty", "%s\n");
1583                 ua->send->ObjectEnd();
1584                 break;
1585               case slot_type_t::kSlotTypeImport:
1586                 ua->send->ObjectStart();
1587                 ua->send->ObjectKeyValue("type", "import_slot", "%s\n");
1588                 ua->send->ObjectKeyValue("slotnr", vl1->bareos_slot_number,
1589                                          "%hd\n");
1590                 ua->send->ObjectKeyValue("content", "empty", "%s\n");
1591                 ua->send->ObjectEnd();
1592                 break;
1593               default:
1594                 break;
1595             }
1596             break;
1597           default:
1598             break;
1599         }
1600         break;
1601       default:
1602         break;
1603     }
1604   }
1605   ua->send->ArrayEnd("contents");
1606 
1607 bail_out:
1608   if (vol_list) { StorageReleaseVolList(store, vol_list); }
1609   CloseSdBsock(ua);
1610 
1611   return;
1612 }
1613 
1614 /**
1615  * Print slots from AutoChanger
1616  */
StatusSlots(UaContext * ua,StorageResource * store)1617 static void StatusSlots(UaContext* ua, StorageResource* store)
1618 {
1619   char* slot_list;
1620   vol_list_t *vl1, *vl2;
1621   slot_number_t max_slots;
1622   changer_vol_list_t* vol_list = NULL;
1623 
1624   ua->jcr->impl->res.write_storage = store;
1625 
1626   /*
1627    * Slot | Volume | Status | MediaType | Pool
1628    */
1629   const char* slot_hformat = " %4i%c| %16s | %9s | %14s | %24s |\n";
1630 
1631   if (!OpenClientDb(ua)) { return; }
1632 
1633 
1634   max_slots = GetNumSlots(ua, store);
1635   if (max_slots <= 0) {
1636     ua->WarningMsg(_("No slots in changer to scan.\n"));
1637     return;
1638   }
1639 
1640   slot_list = (char*)malloc(NbytesForBits(max_slots));
1641   ClearAllBits(max_slots, slot_list);
1642   if (!GetUserSlotList(ua, slot_list, "slots", max_slots)) {
1643     free(slot_list);
1644     return;
1645   }
1646 
1647   vol_list = get_vol_list_from_storage(ua, store, true /* listall */,
1648                                        true /* want to see all slots */);
1649   if (!vol_list) {
1650     ua->WarningMsg(_("No Volumes found, or no barcodes.\n"));
1651     goto bail_out;
1652   }
1653   ua->SendMsg(
1654       _(" Slot |   Volume Name    |   Status  |  Media Type    |         Pool  "
1655         "           |\n"));
1656   ua->SendMsg(
1657       _("------+------------------+-----------+----------------+---------------"
1658         "-----------|\n"));
1659 
1660   /*
1661    * Walk through the list getting the media records
1662    * Slots start numbering at 1.
1663    */
1664   foreach_dlist (vl1, vol_list->contents) {
1665     vl2 = NULL;
1666     switch (vl1->slot_type) {
1667       case slot_type_t::kSlotTypeDrive:
1668         /*
1669          * We are not interested in drive slots.
1670          */
1671         continue;
1672       case slot_type_t::kSlotTypeStorage:
1673       case slot_type_t::kSlotTypeImport:
1674         if (vl1->bareos_slot_number > max_slots) {
1675           ua->WarningMsg(_("Slot %hd greater than max %hd ignored.\n"),
1676                          vl1->bareos_slot_number, max_slots);
1677           continue;
1678         }
1679         /*
1680          * Check if user wants us to look at this slot
1681          */
1682         if (!BitIsSet(vl1->bareos_slot_number - 1, slot_list)) {
1683           Dmsg1(100, "Skipping slot=%hd\n", vl1->bareos_slot_number);
1684           continue;
1685         }
1686 
1687         switch (vl1->slot_status) {
1688           case slot_status_t::kSlotStatusEmpty:
1689             if (vl1->slot_type == slot_type_t::kSlotTypeStorage) {
1690               /*
1691                * See if this empty slot is empty because the volume is loaded
1692                * in one of the drives.
1693                */
1694               vl2 = vol_is_loaded_in_drive(store, vol_list,
1695                                            vl1->bareos_slot_number);
1696               if (!vl2) {
1697                 ua->SendMsg(slot_hformat, vl1->bareos_slot_number, '*', "?",
1698                             "?", "?", "?");
1699                 continue;
1700               }
1701             } else {
1702               ua->SendMsg(slot_hformat, vl1->bareos_slot_number, '@', "?", "?",
1703                           "?", "?");
1704               continue;
1705             }
1706             FALLTHROUGH_INTENDED;
1707           case slot_status_t::kSlotStatusFull: {
1708             /*
1709              * We get here for all slots with content and for empty
1710              * slots with their volume loaded in a drive.
1711              */
1712             MediaDbRecord mr;
1713             if (vl1->slot_status == slot_status_t::kSlotStatusFull) {
1714               if (!vl1->VolName) {
1715                 Dmsg1(100, "No VolName for Slot=%hd.\n",
1716                       vl1->bareos_slot_number);
1717                 ua->SendMsg(slot_hformat, vl1->bareos_slot_number,
1718                             (vl1->slot_type == slot_type_t::kSlotTypeImport)
1719                                 ? '@'
1720                                 : '*',
1721                             "?", "?", "?", "?");
1722                 continue;
1723               }
1724 
1725               bstrncpy(mr.VolumeName, vl1->VolName, sizeof(mr.VolumeName));
1726             } else {
1727               if (!vl2 || !vl2->VolName) {
1728                 Dmsg1(100, "No VolName for Slot=%hd.\n",
1729                       vl1->bareos_slot_number);
1730                 ua->SendMsg(slot_hformat, vl1->bareos_slot_number,
1731                             (vl1->slot_type == slot_type_t::kSlotTypeImport)
1732                                 ? '@'
1733                                 : '*',
1734                             "?", "?", "?", "?");
1735                 continue;
1736               }
1737 
1738               bstrncpy(mr.VolumeName, vl2->VolName, sizeof(mr.VolumeName));
1739             }
1740 
1741             if (mr.VolumeName[0] && ua->db->GetMediaRecord(ua->jcr, &mr)) {
1742               PoolDbRecord pr;
1743               pr.PoolId = mr.PoolId;
1744               if (!ua->db->GetPoolRecord(ua->jcr, &pr)) {
1745                 strcpy(pr.Name, "?");
1746               }
1747 
1748               /*
1749                * Print information
1750                */
1751               if (vl1->slot_type == slot_type_t::kSlotTypeImport) {
1752                 ua->SendMsg(slot_hformat, vl1->bareos_slot_number, '@',
1753                             mr.VolumeName, mr.VolStatus, mr.MediaType, pr.Name);
1754               } else {
1755                 ua->SendMsg(
1756                     slot_hformat, vl1->bareos_slot_number,
1757                     ((vl1->bareos_slot_number == mr.Slot) ? (vl2 ? '%' : ' ')
1758                                                           : '*'),
1759                     mr.VolumeName, mr.VolStatus, mr.MediaType, pr.Name);
1760               }
1761             } else {
1762               ua->SendMsg(
1763                   slot_hformat, vl1->bareos_slot_number,
1764                   (vl1->slot_type == slot_type_t::kSlotTypeImport) ? '@' : '*',
1765                   mr.VolumeName, "?", "?", "?");
1766             }
1767             break;
1768           }
1769           default:
1770             break;
1771         }
1772         break;
1773       default:
1774         break;
1775     }
1776   }
1777 
1778 bail_out:
1779   if (vol_list) { StorageReleaseVolList(store, vol_list); }
1780   free(slot_list);
1781   CloseSdBsock(ua);
1782 
1783   return;
1784 }
1785 } /* namespace directordaemon */
1786