1 /*
2 Bacula(R) - The Network Backup Solution
3
4 Copyright (C) 2000-2020 Kern Sibbald
5
6 The original author of Bacula is Kern Sibbald, with contributions
7 from many others, a complete list can be found in the file AUTHORS.
8
9 You may use this file and others of this release according to the
10 license defined in the LICENSE file, which includes the Affero General
11 Public License, v3.0 ("AGPLv3") and some additional permissions and
12 terms pursuant to its AGPLv3 Section 7.
13
14 This notice must be preserved when any source code is
15 conveyed and/or propagated.
16
17 Bacula(R) is a registered trademark of Kern Sibbald.
18 */
19 /*
20 * Bacula Director -- User Agent Output Commands
21 * I.e. messages, listing database, showing resources, ...
22 *
23 * Kern Sibbald, September MM
24 */
25
26 #include "bacula.h"
27 #include "dird.h"
28
29 /* Imported subroutines */
30
31 /* Imported variables */
32
33 /* Imported functions */
34
35 /* Forward referenced functions */
36 static int do_list_cmd(UAContext *ua, const char *cmd, e_list_type llist);
37 static bool list_nextvol(UAContext *ua, int ndays);
38
39 /*
40 * Turn auto display of console messages on/off
41 */
autodisplay_cmd(UAContext * ua,const char * cmd)42 int autodisplay_cmd(UAContext *ua, const char *cmd)
43 {
44 static const char *kw[] = {
45 NT_("on"),
46 NT_("off"),
47 NULL};
48
49 switch (find_arg_keyword(ua, kw)) {
50 case 0:
51 ua->auto_display_messages = true;
52 break;
53 case 1:
54 ua->auto_display_messages = false;
55 break;
56 default:
57 ua->error_msg(_("ON or OFF keyword missing.\n"));
58 break;
59 }
60 return 1;
61 }
62
63 /*
64 * Turn GUI mode on/off
65 */
gui_cmd(UAContext * ua,const char * cmd)66 int gui_cmd(UAContext *ua, const char *cmd)
67 {
68 static const char *kw[] = {
69 NT_("on"),
70 NT_("off"),
71 NULL};
72
73 switch (find_arg_keyword(ua, kw)) {
74 case 0:
75 ua->jcr->gui = ua->gui = true;
76 break;
77 case 1:
78 ua->jcr->gui = ua->gui = false;
79 break;
80 default:
81 ua->error_msg(_("ON or OFF keyword missing.\n"));
82 break;
83 }
84 return 1;
85 }
86
87 /*
88 * Enter with Resources locked
89 */
show_disabled_jobs(UAContext * ua)90 static void show_disabled_jobs(UAContext *ua)
91 {
92 JOB *job;
93 bool first = true;
94 foreach_res(job, R_JOB) {
95 if (!acl_access_ok(ua, Job_ACL, job->name())) {
96 continue;
97 }
98 if (!job->is_enabled()) {
99 if (first) {
100 first = false;
101 ua->send_msg(_("Disabled Jobs:\n"));
102 }
103 ua->send_msg(" %s\n", job->name());
104 }
105 }
106 if (first) {
107 ua->send_msg(_("No disabled Jobs.\n"));
108 }
109 }
110
111 struct showstruct {const char *res_name; int type;};
112 static struct showstruct reses[] = {
113 {NT_("directors"), R_DIRECTOR},
114 {NT_("clients"), R_CLIENT},
115 {NT_("counters"), R_COUNTER},
116 {NT_("devices"), R_DEVICE},
117 {NT_("jobs"), R_JOB},
118 {NT_("storages"), R_STORAGE},
119 {NT_("catalogs"), R_CATALOG},
120 {NT_("schedules"), R_SCHEDULE},
121 {NT_("filesets"), R_FILESET},
122 {NT_("pools"), R_POOL},
123 {NT_("messages"), R_MSGS},
124 {NT_("statistics"), R_COLLECTOR},
125 // {NT_("consoles"), R_CONSOLE},
126 // {NT_("jobdefs"), R_JOBDEFS},
127 // {NT_{"autochangers"), R_AUTOCHANGER},
128 {NT_("all"), -1},
129 {NT_("help"), -2},
130 {NULL, 0}
131 };
132
133
134 /*
135 * Displays Resources
136 *
137 * show all
138 * show <resource-keyword-name> e.g. show directors
139 * show <resource-keyword-name>=<name> e.g. show director=HeadMan
140 * show disabled shows disabled jobs
141 *
142 */
show_cmd(UAContext * ua,const char * cmd)143 int show_cmd(UAContext *ua, const char *cmd)
144 {
145 int i, j, type, len;
146 int recurse;
147 char *res_name;
148 RES_HEAD *reshead = NULL;
149 RES *res = NULL;
150
151 Dmsg1(20, "show: %s\n", ua->UA_sock->msg);
152
153
154 LockRes();
155 for (i=1; i<ua->argc; i++) {
156 if (strcasecmp(ua->argk[i], NT_("disabled")) == 0) {
157 show_disabled_jobs(ua);
158 goto bail_out;
159 }
160
161 res = NULL;
162 reshead = NULL;
163 type = 0;
164
165 res_name = ua->argk[i];
166 if (!ua->argv[i]) { /* was a name given? */
167 /* No name, dump all resources of specified type */
168 recurse = 1;
169 len = strlen(res_name);
170 for (j=0; reses[j].res_name; j++) {
171 if (strncasecmp(res_name, reses[j].res_name, len) == 0) {
172 type = reses[j].type;
173 if (type > 0) {
174 reshead = res_head[type-r_first];
175 } else {
176 reshead = NULL;
177 }
178 break;
179 }
180 }
181
182 } else {
183 /* Dump a single resource with specified name */
184 recurse = 0;
185 len = strlen(res_name);
186 for (j=0; reses[j].res_name; j++) {
187 if (strncasecmp(res_name, reses[j].res_name, len) == 0) {
188 type = reses[j].type;
189 res = (RES *)GetResWithName(type, ua->argv[i]);
190 if (!res) {
191 type = -3;
192 }
193 break;
194 }
195 }
196 }
197
198 switch (type) {
199 /* All resources */
200 case -1:
201 for (j=r_first; j<=r_last; j++) {
202 /* Skip R_DEVICE since it is really not used or updated */
203 if (j != R_DEVICE) {
204 dump_each_resource(j, bsendmsg, ua);
205 }
206 }
207 break;
208 /* Help */
209 case -2:
210 ua->send_msg(_("Keywords for the show command are:\n"));
211 for (j=0; reses[j].res_name; j++) {
212 ua->error_msg("%s\n", reses[j].res_name);
213 }
214 goto bail_out;
215 /* Resource not found */
216 case -3:
217 ua->error_msg(_("%s resource %s not found.\n"), res_name, ua->argv[i]);
218 goto bail_out;
219 /* Resource not found */
220 case 0:
221 ua->error_msg(_("Resource %s not found\n"), res_name);
222 goto bail_out;
223 /* Dump a specific type */
224 default:
225 if (res) { /* keyword and argument, ie: show job=name */
226 dump_resource(recurse?type:-type, res, bsendmsg, ua);
227
228 } else if (reshead) { /* keyword only, ie: show job */
229 dump_each_resource(-type, bsendmsg, ua);
230 }
231 break;
232 }
233 }
234 bail_out:
235 UnlockRes();
236 return 1;
237 }
238
239 /*
240 * Check if the access is permitted for a list of jobids
241 *
242 * Not in ua_acl.c because it's using db access, and tools such
243 * as bdirjson are not linked with cats.
244 */
acl_access_jobid_ok(UAContext * ua,const char * jobids)245 bool acl_access_jobid_ok(UAContext *ua, const char *jobids)
246 {
247 char *tmp=NULL, *p;
248 bool ret=false;
249 JOB_DBR jr;
250 uint32_t jid;
251
252 if (!jobids) {
253 return false;
254 }
255
256 if (!is_a_number_list(jobids)) {
257 return false;
258 }
259
260 /* If no console resource => default console and all is permitted */
261 if (!ua || !ua->cons) {
262 Dmsg0(1400, "Root cons access OK.\n");
263 return true; /* No cons resource -> root console OK for everything */
264 }
265
266 alist *list = ua->cons->ACL_lists[Job_ACL];
267 if (!list) { /* empty list */
268 return false; /* List empty, reject everything */
269 }
270
271 /* Special case *all* gives full access */
272 if (list->size() == 1 && strcasecmp("*all*", (char *)list->get(0)) == 0) {
273 return true;
274 }
275
276 /* If we can't open the database, just say no */
277 if (!open_new_client_db(ua)) {
278 return false;
279 }
280
281 p = tmp = bstrdup(jobids);
282
283 while (get_next_jobid_from_list(&p, &jid) > 0) {
284 memset(&jr, 0, sizeof(jr));
285 jr.JobId = jid;
286
287 if (db_get_job_record(ua->jcr, ua->db, &jr)) {
288 for (int i=0; i<list->size(); i++) {
289 if (strcasecmp(jr.Name, (char *)list->get(i)) == 0) {
290 Dmsg3(1400, "ACL found %s in %d %s\n", jr.Name,
291 Job_ACL, (char *)list->get(i));
292 ret = true;
293 goto bail_out;
294 }
295 }
296 }
297 }
298
299 bail_out:
300 if (tmp) {
301 free(tmp);
302 }
303 return ret;
304 }
305
306 /*
307 * List contents of database
308 *
309 * list jobs - lists all jobs run
310 * list jobid=nnn - list job data for jobid
311 * list ujobid=uname - list job data for unique jobid
312 * list job=name - list all jobs with "name"
313 * list jobname=name - same as above
314 * list jobmedia jobid=<nn>
315 * list jobmedia job=name
316 * list joblog jobid=<nn>
317 * list joblog job=name
318 * list files [type=<deleted|all>] jobid=<nn> - list files saved for job nn
319 * list files [type=<deleted|all>] job=name
320 * list pools - list pool records
321 * list jobtotals - list totals for all jobs
322 * list media - list media for given pool (deprecated)
323 * list volumes - list Volumes
324 * list clients - list clients
325 * list nextvol job=xx - list the next vol to be used by job
326 * list nextvolume job=xx - same as above.
327 * list copies jobid=x,y,z
328 * list pluginrestoreconf jobid=x,y,z [id=k]
329 *
330 * Note: keyword "long" is before the first command on the command
331 * line results in doing a llist (long listing).
332 */
333
334 /* Do long or full listing */
llist_cmd(UAContext * ua,const char * cmd)335 int llist_cmd(UAContext *ua, const char *cmd)
336 {
337 return do_list_cmd(ua, cmd, VERT_LIST);
338 }
339
340 /* Do short or summary listing */
list_cmd(UAContext * ua,const char * cmd)341 int list_cmd(UAContext *ua, const char *cmd)
342 {
343 if (find_arg(ua, "long") > 0) {
344 return do_list_cmd(ua, cmd, VERT_LIST); /* do a long list */
345 } else {
346 return do_list_cmd(ua, cmd, HORZ_LIST); /* do a short list */
347 }
348 }
349
do_list_cmd(UAContext * ua,const char * cmd,e_list_type llist)350 static int do_list_cmd(UAContext *ua, const char *cmd, e_list_type llist)
351 {
352 POOLMEM *VolumeName;
353 int jobid=0, n;
354 int i, j;
355 JOB_DBR jr;
356 POOL_DBR pr;
357 MEDIA_DBR mr;
358
359 if (!open_new_client_db(ua))
360 return 1;
361
362 memset(&jr, 0, sizeof(jr));
363 memset(&pr, 0, sizeof(pr));
364
365 Dmsg1(20, "list: %s\n", cmd);
366
367 if (!ua->db) {
368 ua->error_msg(_("Hey! DB is NULL\n"));
369 }
370 /* Apply any limit */
371 for (j = 1; j < ua->argc ; j++) {
372 if (strcasecmp(ua->argk[j], NT_("joberrors")) == 0) {
373 jr.JobErrors = 1;
374 } else if (!ua->argv[j]) {
375 /* skip */
376 } else if (strcasecmp(ua->argk[j], NT_("order")) == 0) {
377 if ((strcasecmp(ua->argv[j], NT_("desc")) == 0) ||
378 strcasecmp(ua->argv[j], NT_("descending")) == 0) {
379 jr.order = 1;
380 } else if ((strcasecmp(ua->argv[j], NT_("asc")) == 0) ||
381 strcasecmp(ua->argv[j], NT_("ascending")) == 0) {
382 jr.order = 0;
383 } else {
384 ua->error_msg(_("Unknown order type %s\n"), ua->argv[j]);
385 return 1;
386 }
387 } else if (strcasecmp(ua->argk[j], NT_("limit")) == 0) {
388 jr.limit = atoi(ua->argv[j]);
389
390 } else if (strcasecmp(ua->argk[j], NT_("jobstatus")) == 0) {
391 if (B_ISALPHA(ua->argv[j][0])) {
392 jr.JobStatus = ua->argv[j][0]; /* TODO: Check if the code is correct */
393 }
394 } else if (strcasecmp(ua->argk[j], NT_("jobtype")) == 0) {
395 if (B_ISALPHA(ua->argv[j][0])) {
396 jr.JobType = ua->argv[j][0]; /* TODO: Check if the code is correct */
397 }
398 } else if (strcasecmp(ua->argk[j], NT_("level")) == 0) {
399 if (strlen(ua->argv[j]) > 1) {
400 jr.JobLevel = get_level_code_from_name(ua->argv[j]);
401
402 } else if (B_ISALPHA(ua->argv[j][0])) {
403 jr.JobLevel = ua->argv[j][0]; /* TODO: Check if the code is correct */
404 }
405 } else if (strcasecmp(ua->argk[j], NT_("level")) == 0) {
406
407
408 } else if (strcasecmp(ua->argk[j], NT_("client")) == 0) {
409 if (is_name_valid(ua->argv[j], NULL)) {
410 CLIENT_DBR cr;
411 memset(&cr, 0, sizeof(cr));
412 /* Both Backup & Restore wants to list jobs for this client */
413 if(get_client_dbr(ua, &cr, JT_BACKUP_RESTORE)) {
414 jr.ClientId = cr.ClientId;
415 }
416 }
417 }
418 }
419
420 /* Scan arguments looking for things to do */
421 for (i=1; i<ua->argc; i++) {
422 /* List JOBS */
423 if (strcasecmp(ua->argk[i], NT_("jobs")) == 0) {
424 db_list_job_records(ua->jcr, ua->db, &jr, prtit, ua, llist);
425
426 /* List JOBTOTALS */
427 } else if (strcasecmp(ua->argk[i], NT_("jobtotals")) == 0) {
428 db_list_job_totals(ua->jcr, ua->db, &jr, prtit, ua);
429
430 /* List JOBID=nn */
431 } else if (strcasecmp(ua->argk[i], NT_("jobid")) == 0) {
432 if (ua->argv[i]) {
433 jobid = str_to_int64(ua->argv[i]);
434 if (jobid > 0) {
435 jr.JobId = jobid;
436 db_list_job_records(ua->jcr, ua->db, &jr, prtit, ua, llist);
437 }
438 }
439
440 /* List JOB=xxx */
441 } else if ((strcasecmp(ua->argk[i], NT_("job")) == 0 ||
442 strcasecmp(ua->argk[i], NT_("jobname")) == 0) && ua->argv[i]) {
443 bstrncpy(jr.Name, ua->argv[i], MAX_NAME_LENGTH);
444 jr.JobId = 0;
445 db_list_job_records(ua->jcr, ua->db, &jr, prtit, ua, llist);
446
447 /* List UJOBID=xxx */
448 } else if (strcasecmp(ua->argk[i], NT_("ujobid")) == 0 && ua->argv[i]) {
449 bstrncpy(jr.Job, ua->argv[i], MAX_NAME_LENGTH);
450 jr.JobId = 0;
451 db_list_job_records(ua->jcr, ua->db, &jr, prtit, ua, llist);
452
453 /* List Base files */
454 } else if (strcasecmp(ua->argk[i], NT_("basefiles")) == 0) {
455 /* TODO: cleanup this block */
456 for (j=i+1; j<ua->argc; j++) {
457 if (strcasecmp(ua->argk[j], NT_("ujobid")) == 0 && ua->argv[j]) {
458 bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
459 jr.JobId = 0;
460 db_get_job_record(ua->jcr, ua->db, &jr);
461 jobid = jr.JobId;
462 } else if (strcasecmp(ua->argk[j], NT_("jobid")) == 0 && ua->argv[j]) {
463 jobid = str_to_int64(ua->argv[j]);
464 } else {
465 continue;
466 }
467 if (jobid > 0) {
468 db_list_base_files_for_job(ua->jcr, ua->db, jobid, prtit, ua);
469 }
470 }
471
472 /* List FILES */
473 } else if (strcasecmp(ua->argk[i], NT_("files")) == 0) {
474 int deleted = 0; /* see only backed up files */
475 for (j=i+1; j<ua->argc; j++) {
476 if (strcasecmp(ua->argk[j], NT_("ujobid")) == 0 && ua->argv[j]) {
477 bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
478 jr.JobId = 0;
479 db_get_job_record(ua->jcr, ua->db, &jr);
480 jobid = jr.JobId;
481
482 } else if (strcasecmp(ua->argk[j], NT_("jobid")) == 0 && ua->argv[j]) {
483 jobid = str_to_int64(ua->argv[j]);
484
485 } else if (strcasecmp(ua->argk[j], NT_("type")) == 0 && ua->argv[j]) {
486 if (strcasecmp(ua->argv[j], NT_("deleted")) == 0) {
487 deleted = 1;
488 } else if (strcasecmp(ua->argv[j], NT_("all")) == 0) {
489 deleted = -1;
490 }
491 continue; /* Type should be before the jobid... */
492 } else {
493 continue;
494 }
495 if (jobid > 0) {
496 db_list_files_for_job(ua->jcr, ua->db, jobid, deleted, prtit, ua);
497 }
498 }
499
500 /* List JOBMEDIA */
501 } else if (strcasecmp(ua->argk[i], NT_("jobmedia")) == 0) {
502 bool done = false;
503 for (j=i+1; j<ua->argc; j++) {
504 if (strcasecmp(ua->argk[j], NT_("ujobid")) == 0 && ua->argv[j]) {
505 bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
506 jr.JobId = 0;
507 db_get_job_record(ua->jcr, ua->db, &jr);
508 jobid = jr.JobId;
509 } else if (strcasecmp(ua->argk[j], NT_("jobid")) == 0 && ua->argv[j]) {
510 jobid = str_to_int64(ua->argv[j]);
511 } else {
512 continue;
513 }
514 db_list_jobmedia_records(ua->jcr, ua->db, jobid, prtit, ua, llist);
515 done = true;
516 }
517 if (!done) {
518 /* List for all jobs (jobid=0) */
519 db_list_jobmedia_records(ua->jcr, ua->db, 0, prtit, ua, llist);
520 }
521
522 /* List JOBLOG */
523 } else if (strcasecmp(ua->argk[i], NT_("joblog")) == 0) {
524 bool done = false;
525 for (j=i+1; j<ua->argc; j++) {
526 if (strcasecmp(ua->argk[j], NT_("ujobid")) == 0 && ua->argv[j]) {
527 bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
528 jr.JobId = 0;
529 db_get_job_record(ua->jcr, ua->db, &jr);
530 jobid = jr.JobId;
531 } else if (strcasecmp(ua->argk[j], NT_("jobid")) == 0 && ua->argv[j]) {
532 jobid = str_to_int64(ua->argv[j]);
533 } else {
534 continue;
535 }
536 db_list_joblog_records(ua->jcr, ua->db, jobid, prtit, ua, llist);
537 done = true;
538 }
539 if (!done) {
540 /* List for all jobs (jobid=0) */
541 db_list_joblog_records(ua->jcr, ua->db, 0, prtit, ua, llist);
542 }
543
544
545 /* List POOLS */
546 } else if (strcasecmp(ua->argk[i], NT_("pool")) == 0 ||
547 strcasecmp(ua->argk[i], NT_("pools")) == 0) {
548 POOL_DBR pr;
549 memset(&pr, 0, sizeof(pr));
550 if (ua->argv[i]) {
551 bstrncpy(pr.Name, ua->argv[i], sizeof(pr.Name));
552 }
553 db_list_pool_records(ua->jcr, ua->db, &pr, prtit, ua, llist);
554
555 } else if (strcasecmp(ua->argk[i], NT_("clients")) == 0) {
556 db_list_client_records(ua->jcr, ua->db, prtit, ua, llist);
557
558 } else if (strcasecmp(ua->argk[i], NT_("pluginrestoreconf")) == 0) {
559 ROBJECT_DBR rr;
560 memset(&rr, 0, sizeof(rr));
561 rr.FileType = FT_PLUGIN_CONFIG;
562
563 for (j=i+1; j<ua->argc; j++) {
564 if (strcasecmp(ua->argk[j], NT_("ujobid")) == 0 && ua->argv[j]) {
565 bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
566 jr.JobId = 0;
567
568 } else if (strcasecmp(ua->argk[j], NT_("jobid")) == 0 && ua->argv[j]) {
569
570 if (acl_access_jobid_ok(ua, ua->argv[j])) {
571
572 if (is_a_number(ua->argv[j])) {
573 rr.JobId = str_to_uint64(ua->argv[j]);
574
575 } else if (is_a_number_list(ua->argv[j])) {
576 /* In this case, loop directly to find if all jobids are
577 * accessible */
578 rr.JobIds = ua->argv[j];
579 }
580
581 } else {
582 ua->error_msg(_("Invalid jobid argument\n"));
583 return 1;
584 }
585
586 } else if (((strcasecmp(ua->argk[j], NT_("id")) == 0) ||
587 (strcasecmp(ua->argk[j], NT_("restoreobjectid")) == 0))
588 && ua->argv[j])
589 {
590 rr.RestoreObjectId = str_to_uint64(ua->argv[j]);
591
592 } else if (strcasecmp(ua->argk[j], NT_("objecttype")) == 0 && ua->argv[j]) {
593 if (strcasecmp(ua->argv[j], NT_("PLUGIN_CONFIG")) == 0) {
594 rr.FileType = FT_PLUGIN_CONFIG;
595
596 } else if (strcasecmp(ua->argv[j], NT_("PLUGIN_CONFIG_FILLED")) == 0) {
597 rr.FileType = FT_PLUGIN_CONFIG_FILLED;
598
599 } else if (strcasecmp(ua->argv[j], NT_("RESTORE_FIRST")) == 0) {
600 rr.FileType = FT_RESTORE_FIRST;
601
602 } else if (strcasecmp(ua->argv[j], NT_("ALL")) == 0) {
603 rr.FileType = 0;
604
605 } else {
606 ua->error_msg(_("Unknown ObjectType %s\n"), ua->argv[j]);
607 return 1;
608 }
609
610 } else {
611 continue;
612 }
613 }
614
615 if (!rr.JobId && !rr.JobIds) {
616 ua->error_msg(_("list pluginrestoreconf requires jobid argument\n"));
617 return 1;
618 }
619
620 /* Display the content of the restore object */
621 if (rr.RestoreObjectId > 0) {
622 /* Here, the JobId and the RestoreObjectId are set */
623 if (db_get_restoreobject_record(ua->jcr, ua->db, &rr)) {
624 ua->send_msg("%s\n", NPRTB(rr.object));
625 } else {
626 Dmsg0(200, "Object not found\n");
627 }
628
629 } else {
630 db_list_restore_objects(ua->jcr, ua->db, &rr, prtit, ua, llist);
631 }
632
633 db_free_restoreobject_record(ua->jcr, &rr);
634 return 1;
635
636 /* List MEDIA or VOLUMES */
637 } else if (strcasecmp(ua->argk[i], NT_("media")) == 0 ||
638 strcasecmp(ua->argk[i], NT_("volume")) == 0 ||
639 strcasecmp(ua->argk[i], NT_("volumes")) == 0) {
640 bool done = false;
641 for (j=i+1; j<ua->argc; j++) {
642 if (strcasecmp(ua->argk[j], NT_("ujobid")) == 0 && ua->argv[j]) {
643 bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
644 jr.JobId = 0;
645 db_get_job_record(ua->jcr, ua->db, &jr);
646 jobid = jr.JobId;
647 } else if (strcasecmp(ua->argk[j], NT_("jobid")) == 0 && ua->argv[j]) {
648 jobid = str_to_int64(ua->argv[j]);
649 } else {
650 continue;
651 }
652 VolumeName = get_pool_memory(PM_FNAME);
653 n = db_get_job_volume_names(ua->jcr, ua->db, jobid, &VolumeName);
654 ua->send_msg(_("Jobid %d used %d Volume(s): %s\n"), jobid, n, VolumeName);
655 free_pool_memory(VolumeName);
656 done = true;
657 }
658
659 /* if no job or jobid keyword found, then we list all media */
660 if (!done) {
661 int num_pools;
662 uint32_t *ids;
663 /* List a specific volume? */
664 if (ua->argv[i] && *ua->argv[i]) {
665 bstrncpy(mr.VolumeName, ua->argv[i], sizeof(mr.VolumeName));
666 db_list_media_records(ua->jcr, ua->db, &mr, prtit, ua, llist);
667 return 1;
668 }
669 /* Is a specific pool wanted? */
670 for (i=1; i<ua->argc; i++) {
671 if (strcasecmp(ua->argk[i], NT_("pool")) == 0) {
672 if (!get_pool_dbr(ua, &pr)) {
673 ua->error_msg(_("No Pool specified.\n"));
674 return 1;
675 }
676 mr.PoolId = pr.PoolId;
677 db_list_media_records(ua->jcr, ua->db, &mr, prtit, ua, llist);
678 return 1;
679 }
680 }
681
682 /* List Volumes in all pools */
683 if (!db_get_pool_ids(ua->jcr, ua->db, &num_pools, &ids)) {
684 ua->error_msg(_("Error obtaining pool ids. ERR=%s\n"),
685 db_strerror(ua->db));
686 return 1;
687 }
688 if (num_pools <= 0) {
689 return 1;
690 }
691 for (i=0; i < num_pools; i++) {
692 pr.PoolId = ids[i];
693 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
694 ua->send_msg(_("Pool: %s\n"), pr.Name);
695 }
696 mr.PoolId = ids[i];
697 db_list_media_records(ua->jcr, ua->db, &mr, prtit, ua, llist);
698 }
699 free(ids);
700 return 1;
701 }
702 /* List next volume */
703 } else if (strcasecmp(ua->argk[i], NT_("nextvol")) == 0 ||
704 strcasecmp(ua->argk[i], NT_("nextvolume")) == 0) {
705 n = 1;
706 j = find_arg_with_value(ua, NT_("days"));
707 if (j >= 0) {
708 n = atoi(ua->argv[j]);
709 if ((n < 0) || (n > 50)) {
710 ua->warning_msg(_("Ignoring invalid value for days. Max is 50.\n"));
711 n = 1;
712 }
713 }
714 list_nextvol(ua, n);
715 } else if (strcasecmp(ua->argk[i], NT_("copies")) == 0) {
716 char *jobids = NULL;
717 uint32_t limit=0;
718 for (j=i+1; j<ua->argc; j++) {
719 if (strcasecmp(ua->argk[j], NT_("jobid")) == 0 && ua->argv[j]) {
720 if (is_a_number_list(ua->argv[j])) {
721 jobids = ua->argv[j];
722 }
723 } else if (strcasecmp(ua->argk[j], NT_("limit")) == 0 && ua->argv[j]) {
724 limit = atoi(ua->argv[j]);
725 }
726 }
727 db_list_copies_records(ua->jcr,ua->db,limit,jobids,prtit,ua,llist);
728 } else if (strcasecmp(ua->argk[i], NT_("limit")) == 0
729 || strcasecmp(ua->argk[i], NT_("days")) == 0
730 || strcasecmp(ua->argk[i], NT_("joberrors")) == 0
731 || strcasecmp(ua->argk[i], NT_("order")) == 0
732 || strcasecmp(ua->argk[i], NT_("jobstatus")) == 0
733 || strcasecmp(ua->argk[i], NT_("client")) == 0
734 || strcasecmp(ua->argk[i], NT_("type")) == 0
735 || strcasecmp(ua->argk[i], NT_("level")) == 0
736 || strcasecmp(ua->argk[i], NT_("jobtype")) == 0
737 || strcasecmp(ua->argk[i], NT_("long")) == 0
738 ) {
739 /* Ignore it */
740 } else if (strcasecmp(ua->argk[i], NT_("snapshot")) == 0 ||
741 strcasecmp(ua->argk[i], NT_("snapshots")) == 0)
742 {
743 snapshot_list(ua, i, prtit, llist);
744 return 1;
745
746 } else {
747 ua->error_msg(_("Unknown list keyword: %s\n"), NPRT(ua->argk[i]));
748 }
749 }
750 return 1;
751 }
752
list_nextvol(UAContext * ua,int ndays)753 static bool list_nextvol(UAContext *ua, int ndays)
754 {
755 JOB *job;
756 JCR *jcr;
757 USTORE store;
758 RUN *run;
759 utime_t runtime;
760 bool found = false;
761 MEDIA_DBR mr;
762 POOL_DBR pr;
763 POOL_MEM errmsg;
764 char edl[50];
765
766 int i = find_arg_with_value(ua, "job");
767 if (i <= 0) {
768 if ((job = select_job_resource(ua)) == NULL) {
769 return false;
770 }
771 } else {
772 job = (JOB *)GetResWithName(R_JOB, ua->argv[i]);
773 if (!job) {
774 Jmsg(ua->jcr, M_ERROR, 0, _("%s is not a job name.\n"), ua->argv[i]);
775 if ((job = select_job_resource(ua)) == NULL) {
776 return false;
777 }
778 }
779 }
780
781 jcr = new_jcr(sizeof(JCR), dird_free_jcr);
782 for (run=NULL; (run = find_next_run(run, job, runtime, ndays)); ) {
783 if (!complete_jcr_for_job(jcr, job, run->pool)) {
784 found = false;
785 goto get_out;
786 }
787 if (!jcr->jr.PoolId) {
788 ua->error_msg(_("Could not find Pool for Job %s\n"), job->name());
789 continue;
790 }
791 bmemset(&pr, 0, sizeof(pr));
792 pr.PoolId = jcr->jr.PoolId;
793 if (!db_get_pool_record(jcr, jcr->db, &pr)) {
794 bstrncpy(pr.Name, "*UnknownPool*", sizeof(pr.Name));
795 }
796 mr.PoolId = jcr->jr.PoolId;
797 get_job_storage(&store, job, run);
798 set_storageid_in_mr(store.store, &mr);
799 /* no need to set ScratchPoolId, since we use fnv_no_create_vol */
800 if (!find_next_volume_for_append(jcr, &mr, 1, fnv_no_create_vol, fnv_prune, errmsg)) {
801 ua->error_msg(_("Could not find next Volume for Job %s (Pool=%s, Level=%s). %s\n"),
802 job->name(), pr.Name, level_to_str(edl, sizeof(edl), run->level), errmsg.c_str());
803 } else {
804 ua->send_msg(
805 _("The next Volume to be used by Job \"%s\" (Pool=%s, Level=%s) will be %s\n"),
806 job->name(), pr.Name, level_to_str(edl, sizeof(edl), run->level), mr.VolumeName);
807 found = true;
808 }
809 }
810
811 get_out:
812 if (jcr->db) db_close_database(jcr, jcr->db);
813 jcr->db = NULL;
814 free_jcr(jcr);
815 if (!found) {
816 ua->error_msg(_("Could not find next Volume for Job %s.\n"),
817 job->hdr.name);
818 return false;
819 }
820 return true;
821 }
822
823
824 /*
825 * For a given job, we examine all his run records
826 * to see if it is scheduled today or tomorrow.
827 */
find_next_run(RUN * run,JOB * job,utime_t & runtime,int ndays)828 RUN *find_next_run(RUN *run, JOB *job, utime_t &runtime, int ndays)
829 {
830 time_t now, future, endtime;
831 SCHED *sched;
832 struct tm tm, runtm;
833 int mday, wday, month, wom, i;
834 int woy, ldom;
835 int day;
836 bool is_scheduled;
837
838 sched = job->schedule;
839 if (!sched || !job->is_enabled() || (sched && !sched->is_enabled()) ||
840 (job->client && !job->client->is_enabled())) {
841 return NULL; /* no nothing to report */
842 }
843
844 /* Break down the time into components */
845 now = time(NULL);
846 endtime = now + (ndays * 60 * 60 * 24);
847
848 if (run == NULL) {
849 run = sched->run;
850 } else {
851 run = run->next;
852 }
853 for ( ; run; run=run->next) {
854 /*
855 * Find runs in next 24 hours. Day 0 is today, so if
856 * ndays=1, look at today and tomorrow.
857 */
858 for (day = 0; day <= ndays; day++) {
859 future = now + (day * 60 * 60 * 24);
860
861 /* Break down the time into components */
862 (void)localtime_r(&future, &tm);
863 mday = tm.tm_mday - 1;
864 wday = tm.tm_wday;
865 month = tm.tm_mon;
866 wom = mday / 7;
867 woy = tm_woy(future);
868 ldom = tm_ldom(month, tm.tm_year + 1900);
869
870 is_scheduled = (bit_is_set(mday, run->mday) &&
871 bit_is_set(wday, run->wday) &&
872 bit_is_set(month, run->month) &&
873 bit_is_set(wom, run->wom) &&
874 bit_is_set(woy, run->woy)) ||
875 (bit_is_set(month, run->month) &&
876 bit_is_set(31, run->mday) && mday == ldom);
877
878 #ifdef xxx
879 Pmsg2(000, "day=%d is_scheduled=%d\n", day, is_scheduled);
880 Pmsg1(000, "bit_set_mday=%d\n", bit_is_set(mday, run->mday));
881 Pmsg1(000, "bit_set_wday=%d\n", bit_is_set(wday, run->wday));
882 Pmsg1(000, "bit_set_month=%d\n", bit_is_set(month, run->month));
883 Pmsg1(000, "bit_set_wom=%d\n", bit_is_set(wom, run->wom));
884 Pmsg1(000, "bit_set_woy=%d\n", bit_is_set(woy, run->woy));
885 #endif
886
887 if (is_scheduled) { /* Jobs scheduled on that day */
888 #ifdef xxx
889 char buf[300], num[10];
890 bsnprintf(buf, sizeof(buf), "tm.hour=%d hour=", tm.tm_hour);
891 for (i=0; i<24; i++) {
892 if (bit_is_set(i, run->hour)) {
893 bsnprintf(num, sizeof(num), "%d ", i);
894 bstrncat(buf, num, sizeof(buf));
895 }
896 }
897 bstrncat(buf, "\n", sizeof(buf));
898 Pmsg1(000, "%s", buf);
899 #endif
900 /* find time (time_t) job is to be run */
901 (void)localtime_r(&future, &runtm);
902 for (i= 0; i < 24; i++) {
903 if (bit_is_set(i, run->hour)) {
904 runtm.tm_hour = i;
905 runtm.tm_min = run->minute;
906 runtm.tm_sec = 0;
907 runtime = mktime(&runtm);
908 Dmsg2(200, "now=%d runtime=%lld\n", now, runtime);
909 if ((runtime > now) && (runtime < endtime)) {
910 Dmsg2(200, "Found it level=%d %c\n", run->level, run->level);
911 return run; /* found it, return run resource */
912 }
913 }
914 }
915 }
916 }
917 } /* end for loop over runs */
918 /* Nothing found */
919 return NULL;
920 }
921
922 /*
923 * Fill in the remaining fields of the jcr as if it
924 * is going to run the job.
925 */
complete_jcr_for_job(JCR * jcr,JOB * job,POOL * pool)926 bool complete_jcr_for_job(JCR *jcr, JOB *job, POOL *pool)
927 {
928 POOL_DBR pr;
929
930 bmemset(&pr, 0, sizeof(POOL_DBR));
931 set_jcr_defaults(jcr, job);
932 if (pool) {
933 jcr->pool = pool; /* override */
934 }
935 if (jcr->db) {
936 Dmsg0(100, "complete_jcr close db\n");
937 db_close_database(jcr, jcr->db);
938 jcr->db = NULL;
939 }
940
941 Dmsg0(100, "complete_jcr open db\n");
942 jcr->db = db_init_database(jcr, jcr->catalog->db_driver, jcr->catalog->db_name,
943 jcr->catalog->db_user,
944 jcr->catalog->db_password, jcr->catalog->db_address,
945 jcr->catalog->db_port, jcr->catalog->db_socket,
946 jcr->catalog->db_ssl_mode, jcr->catalog->db_ssl_key,
947 jcr->catalog->db_ssl_cert, jcr->catalog->db_ssl_ca,
948 jcr->catalog->db_ssl_capath, jcr->catalog->db_ssl_cipher,
949 jcr->catalog->mult_db_connections,
950 jcr->catalog->disable_batch_insert);
951 if (!jcr->db || !db_open_database(jcr, jcr->db)) {
952 Jmsg(jcr, M_FATAL, 0, _("Could not open database \"%s\".\n"),
953 jcr->catalog->db_name);
954 if (jcr->db) {
955 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
956 db_close_database(jcr, jcr->db);
957 jcr->db = NULL;
958 }
959 return false;
960 }
961 bstrncpy(pr.Name, jcr->pool->name(), sizeof(pr.Name));
962 while (!db_get_pool_record(jcr, jcr->db, &pr)) { /* get by Name */
963 /* Try to create the pool */
964 if (create_pool(jcr, jcr->db, jcr->pool, POOL_OP_CREATE) < 0) {
965 Jmsg(jcr, M_FATAL, 0, _("Pool %s not in database. %s"), pr.Name,
966 db_strerror(jcr->db));
967 if (jcr->db) {
968 db_close_database(jcr, jcr->db);
969 jcr->db = NULL;
970 }
971 return false;
972 } else {
973 Jmsg(jcr, M_INFO, 0, _("Pool %s created in database.\n"), pr.Name);
974 }
975 }
976 jcr->jr.PoolId = pr.PoolId;
977 return true;
978 }
979
980
con_lock_release(void * arg)981 static void con_lock_release(void *arg)
982 {
983 Vw(con_lock);
984 }
985
do_messages(UAContext * ua,const char * cmd)986 void do_messages(UAContext *ua, const char *cmd)
987 {
988 char msg[2000];
989 int mlen;
990 bool do_truncate = false;
991
992 if (ua->jcr) {
993 dequeue_messages(ua->jcr);
994 }
995 Pw(con_lock);
996 pthread_cleanup_push(con_lock_release, (void *)NULL);
997 rewind(con_fd);
998 while (fgets(msg, sizeof(msg), con_fd)) {
999 mlen = strlen(msg);
1000 ua->UA_sock->msg = check_pool_memory_size(ua->UA_sock->msg, mlen+1);
1001 strcpy(ua->UA_sock->msg, msg);
1002 ua->UA_sock->msglen = mlen;
1003 ua->UA_sock->send();
1004 do_truncate = true;
1005 }
1006 if (do_truncate) {
1007 (void)ftruncate(fileno(con_fd), 0L);
1008 }
1009 console_msg_pending = false;
1010 ua->user_notified_msg_pending = false;
1011 pthread_cleanup_pop(0);
1012 Vw(con_lock);
1013 }
1014
1015
qmessagescmd(UAContext * ua,const char * cmd)1016 int qmessagescmd(UAContext *ua, const char *cmd)
1017 {
1018 if (console_msg_pending && ua->auto_display_messages) {
1019 do_messages(ua, cmd);
1020 }
1021 return 1;
1022 }
1023
messagescmd(UAContext * ua,const char * cmd)1024 int messagescmd(UAContext *ua, const char *cmd)
1025 {
1026 if (console_msg_pending) {
1027 do_messages(ua, cmd);
1028 } else {
1029 ua->UA_sock->fsend(_("You have no messages.\n"));
1030 }
1031 return 1;
1032 }
1033
1034 /*
1035 * Callback routine for "printing" database file listing
1036 */
prtit(void * ctx,const char * msg)1037 void prtit(void *ctx, const char *msg)
1038 {
1039 UAContext *ua = (UAContext *)ctx;
1040
1041 if (ua) ua->send_msg("%s", msg);
1042 }
1043
1044 /*
1045 * Format message and send to other end.
1046
1047 * If the UA_sock is NULL, it means that there is no user
1048 * agent, so we are being called from Bacula core. In
1049 * that case direct the messages to the Job.
1050 */
1051 #ifdef HAVE_VA_COPY
bmsg(UAContext * ua,const char * fmt,va_list arg_ptr)1052 void bmsg(UAContext *ua, const char *fmt, va_list arg_ptr)
1053 {
1054 BSOCK *bs = ua->UA_sock;
1055 int maxlen, len;
1056 POOLMEM *msg = NULL;
1057 va_list ap;
1058
1059 if (bs) {
1060 msg = bs->msg;
1061 }
1062 if (!msg) {
1063 msg = get_pool_memory(PM_EMSG);
1064 }
1065
1066 again:
1067 maxlen = sizeof_pool_memory(msg) - 1;
1068 va_copy(ap, arg_ptr);
1069 len = bvsnprintf(msg, maxlen, fmt, ap);
1070 va_end(ap);
1071 if (len < 0 || len >= maxlen) {
1072 msg = realloc_pool_memory(msg, maxlen + maxlen/2);
1073 goto again;
1074 }
1075
1076 if (bs) {
1077 bs->msg = msg;
1078 bs->msglen = len;
1079 bs->send();
1080 } else { /* No UA, send to Job */
1081 Jmsg(ua->jcr, M_INFO, 0, "%s", msg);
1082 free_pool_memory(msg);
1083 }
1084
1085 }
1086
1087 #else /* no va_copy() -- brain damaged version of variable arguments */
1088
bmsg(UAContext * ua,const char * fmt,va_list arg_ptr)1089 void bmsg(UAContext *ua, const char *fmt, va_list arg_ptr)
1090 {
1091 BSOCK *bs = ua->UA_sock;
1092 int maxlen, len;
1093 POOLMEM *msg = NULL;
1094
1095 if (bs) {
1096 msg = bs->msg;
1097 }
1098 if (!msg) {
1099 msg = get_memory(5000);
1100 }
1101
1102 maxlen = sizeof_pool_memory(msg) - 1;
1103 if (maxlen < 4999) {
1104 msg = realloc_pool_memory(msg, 5000);
1105 maxlen = 4999;
1106 }
1107 len = bvsnprintf(msg, maxlen, fmt, arg_ptr);
1108 if (len < 0 || len >= maxlen) {
1109 pm_strcpy(msg, _("Message too long to display.\n"));
1110 len = strlen(msg);
1111 }
1112
1113 if (bs) {
1114 bs->msg = msg;
1115 bs->msglen = len;
1116 bs->send();
1117 } else { /* No UA, send to Job */
1118 Jmsg(ua->jcr, M_INFO, 0, "%s", msg);
1119 free_pool_memory(msg);
1120 }
1121
1122 }
1123 #endif
1124
bsendmsg(void * ctx,const char * fmt,...)1125 void bsendmsg(void *ctx, const char *fmt, ...)
1126 {
1127 va_list arg_ptr;
1128 va_start(arg_ptr, fmt);
1129 bmsg((UAContext *)ctx, fmt, arg_ptr);
1130 va_end(arg_ptr);
1131 }
1132
1133 /*
1134 * The following UA methods are mainly intended for GUI
1135 * programs
1136 */
1137 /*
1138 * This is a message that should be displayed on the user's
1139 * console.
1140 */
send_msg(const char * fmt,...)1141 void UAContext::send_msg(const char *fmt, ...)
1142 {
1143 va_list arg_ptr;
1144 va_start(arg_ptr, fmt);
1145 bmsg(this, fmt, arg_ptr);
1146 va_end(arg_ptr);
1147 }
1148
1149
1150 /*
1151 * This is an error condition with a command. The gui should put
1152 * up an error or critical dialog box. The command is aborted.
1153 */
error_msg(const char * fmt,...)1154 void UAContext::error_msg(const char *fmt, ...)
1155 {
1156 BSOCK *bs = UA_sock;
1157 va_list arg_ptr;
1158
1159 if (bs && api) bs->signal(BNET_ERROR_MSG);
1160 va_start(arg_ptr, fmt);
1161 bmsg(this, fmt, arg_ptr);
1162 va_end(arg_ptr);
1163 }
1164
1165 /*
1166 * This is a warning message, that should bring up a warning
1167 * dialog box on the GUI. The command is not aborted, but something
1168 * went wrong.
1169 */
warning_msg(const char * fmt,...)1170 void UAContext::warning_msg(const char *fmt, ...)
1171 {
1172 BSOCK *bs = UA_sock;
1173 va_list arg_ptr;
1174
1175 if (bs && api) bs->signal(BNET_WARNING_MSG);
1176 va_start(arg_ptr, fmt);
1177 bmsg(this, fmt, arg_ptr);
1178 va_end(arg_ptr);
1179 }
1180
1181 /*
1182 * This is an information message that should probably be put
1183 * into the status line of a GUI program.
1184 */
info_msg(const char * fmt,...)1185 void UAContext::info_msg(const char *fmt, ...)
1186 {
1187 BSOCK *bs = UA_sock;
1188 va_list arg_ptr;
1189
1190 if (bs && api) bs->signal(BNET_INFO_MSG);
1191 va_start(arg_ptr, fmt);
1192 bmsg(this, fmt, arg_ptr);
1193 va_end(arg_ptr);
1194 }
1195