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