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