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