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