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