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