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