3 * Bacula Director -- User Agent Output Commands
4 * I.e. messages, listing database, showing resources, ...
6 * Kern Sibbald, September MM
12 Copyright (C) 2000-2005 Kern Sibbald
14 This program is free software; you can redistribute it and/or
15 modify it under the terms of the GNU General Public License
16 version 2 as amended with additional clauses defined in the
17 file LICENSE in the main source directory.
19 This program is distributed in the hope that it will be useful,
20 but WITHOUT ANY WARRANTY; without even the implied warranty of
21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 the file LICENSE for additional details.
29 /* Imported subroutines */
31 /* Imported variables */
34 extern RES_TABLE resources[];
35 extern RES **res_head;
36 extern int console_msg_pending;
38 extern brwlock_t con_lock;
40 /* Imported functions */
42 /* Forward referenced functions */
43 static int do_list_cmd(UAContext *ua, const char *cmd, e_list_type llist);
44 static bool list_nextvol(UAContext *ua, int ndays);
47 * Turn auto display of console messages on/off
49 int autodisplay_cmd(UAContext *ua, const char *cmd)
51 static const char *kw[] = {
56 switch (find_arg_keyword(ua, kw)) {
58 ua->auto_display_messages = true;
61 ua->auto_display_messages = false;
64 bsendmsg(ua, _("ON or OFF keyword missing.\n"));
71 * Turn GUI mode on/off
73 int gui_cmd(UAContext *ua, const char *cmd)
75 static const char *kw[] = {
80 switch (find_arg_keyword(ua, kw)) {
82 ua->jcr->gui = ua->gui = true;
85 ua->jcr->gui = ua->gui = false;
88 bsendmsg(ua, _("ON or OFF keyword missing.\n"));
96 struct showstruct {const char *res_name; int type;};
97 static struct showstruct reses[] = {
98 {N_("directors"), R_DIRECTOR},
99 {N_("clients"), R_CLIENT},
100 {N_("counters"), R_COUNTER},
101 {N_("devices"), R_DEVICE},
103 {N_("storages"), R_STORAGE},
104 {N_("catalogs"), R_CATALOG},
105 {N_("schedules"), R_SCHEDULE},
106 {N_("filesets"), R_FILESET},
107 {N_("pools"), R_POOL},
108 {N_("messages"), R_MSGS},
119 * show <resource-keyword-name> e.g. show directors
120 * show <resource-keyword-name>=<name> e.g. show director=HeadMan
123 int show_cmd(UAContext *ua, const char *cmd)
130 Dmsg1(20, "show: %s\n", ua->UA_sock->msg);
134 for (i=1; i<ua->argc; i++) {
136 res_name = ua->argk[i];
137 if (!ua->argv[i]) { /* was a name given? */
138 /* No name, dump all resources of specified type */
140 len = strlen(res_name);
141 for (j=0; reses[j].res_name; j++) {
142 if (strncasecmp(res_name, _(reses[j].res_name), len) == 0) {
143 type = reses[j].type;
145 res = res_head[type-r_first];
154 /* Dump a single resource with specified name */
156 len = strlen(res_name);
157 for (j=0; reses[j].res_name; j++) {
158 if (strncasecmp(res_name, _(reses[j].res_name), len) == 0) {
159 type = reses[j].type;
160 res = (RES *)GetResWithName(type, ua->argv[i]);
171 for (j=r_first; j<=r_last; j++) {
172 dump_resource(j, res_head[j-r_first], bsendmsg, ua);
176 bsendmsg(ua, _("Keywords for the show command are:\n"));
177 for (j=0; reses[j].res_name; j++) {
178 bsendmsg(ua, "%s\n", _(reses[j].res_name));
182 bsendmsg(ua, _("%s resource %s not found.\n"), res_name, ua->argv[i]);
185 bsendmsg(ua, _("Resource %s not found\n"), res_name);
188 dump_resource(recurse?type:-type, res, bsendmsg, ua);
201 * List contents of database
203 * list jobs - lists all jobs run
204 * list jobid=nnn - list job data for jobid
205 * list job=name - list job data for job
206 * list jobmedia jobid=<nn>
207 * list jobmedia job=name
208 * list files jobid=<nn> - list files saved for job nn
209 * list files job=name
210 * list pools - list pool records
211 * list jobtotals - list totals for all jobs
212 * list media - list media for given pool (deprecated)
213 * list volumes - list Volumes
214 * list clients - list clients
215 * list nextvol job=xx - list the next vol to be used by job
216 * list nextvolume job=xx - same as above.
220 /* Do long or full listing */
221 int llist_cmd(UAContext *ua, const char *cmd)
223 return do_list_cmd(ua, cmd, VERT_LIST);
226 /* Do short or summary listing */
227 int list_cmd(UAContext *ua, const char *cmd)
229 return do_list_cmd(ua, cmd, HORZ_LIST);
232 static int do_list_cmd(UAContext *ua, const char *cmd, e_list_type llist)
244 memset(&jr, 0, sizeof(jr));
245 memset(&pr, 0, sizeof(pr));
246 memset(&mr, 0, sizeof(mr));
248 Dmsg1(20, "list: %s\n", cmd);
251 bsendmsg(ua, _("Hey! DB is NULL\n"));
254 /* Scan arguments looking for things to do */
255 for (i=1; i<ua->argc; i++) {
257 if (strcasecmp(ua->argk[i], N_("jobs")) == 0) {
258 /* Apply any limit */
259 j = find_arg_with_value(ua, N_("limit"));
261 jr.limit = atoi(ua->argv[j]);
263 db_list_job_records(ua->jcr, ua->db, &jr, prtit, ua, llist);
266 } else if (strcasecmp(ua->argk[i], N_("jobtotals")) == 0) {
267 db_list_job_totals(ua->jcr, ua->db, &jr, prtit, ua);
270 } else if (strcasecmp(ua->argk[i], N_("jobid")) == 0) {
272 jobid = str_to_int64(ua->argv[i]);
275 db_list_job_records(ua->jcr, ua->db, &jr, prtit, ua, llist);
280 } else if (strcasecmp(ua->argk[i], N_("job")) == 0 && ua->argv[i]) {
281 bstrncpy(jr.Job, ua->argv[i], MAX_NAME_LENGTH);
283 db_list_job_records(ua->jcr, ua->db, &jr, prtit, ua, llist);
286 } else if (strcasecmp(ua->argk[i], N_("files")) == 0) {
288 for (j=i+1; j<ua->argc; j++) {
289 if (strcasecmp(ua->argk[j], N_("job")) == 0 && ua->argv[j]) {
290 bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
292 db_get_job_record(ua->jcr, ua->db, &jr);
294 } else if (strcasecmp(ua->argk[j], N_("jobid")) == 0 && ua->argv[j]) {
295 jobid = str_to_int64(ua->argv[j]);
300 db_list_files_for_job(ua->jcr, ua->db, jobid, prtit, ua);
305 } else if (strcasecmp(ua->argk[i], N_("jobmedia")) == 0) {
307 for (j=i+1; j<ua->argc; j++) {
308 if (strcasecmp(ua->argk[j], N_("job")) == 0 && ua->argv[j]) {
309 bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
311 db_get_job_record(ua->jcr, ua->db, &jr);
313 } else if (strcasecmp(ua->argk[j], N_("jobid")) == 0 && ua->argv[j]) {
314 jobid = str_to_int64(ua->argv[j]);
318 db_list_jobmedia_records(ua->jcr, ua->db, jobid, prtit, ua, llist);
322 /* List for all jobs (jobid=0) */
323 db_list_jobmedia_records(ua->jcr, ua->db, 0, prtit, ua, llist);
327 } else if (strcasecmp(ua->argk[i], N_("pool")) == 0 ||
328 strcasecmp(ua->argk[i], N_("pools")) == 0) {
330 memset(&pr, 0, sizeof(pr));
332 bstrncpy(pr.Name, ua->argv[i], sizeof(pr.Name));
334 db_list_pool_records(ua->jcr, ua->db, &pr, prtit, ua, llist);
336 } else if (strcasecmp(ua->argk[i], N_("clients")) == 0) {
337 db_list_client_records(ua->jcr, ua->db, prtit, ua, llist);
340 /* List MEDIA or VOLUMES */
341 } else if (strcasecmp(ua->argk[i], N_("media")) == 0 ||
342 strcasecmp(ua->argk[i], N_("volume")) == 0 ||
343 strcasecmp(ua->argk[i], N_("volumes")) == 0) {
345 for (j=i+1; j<ua->argc; j++) {
346 if (strcasecmp(ua->argk[j], N_("job")) == 0 && ua->argv[j]) {
347 bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
349 db_get_job_record(ua->jcr, ua->db, &jr);
351 } else if (strcasecmp(ua->argk[j], N_("jobid")) == 0 && ua->argv[j]) {
352 jobid = str_to_int64(ua->argv[j]);
356 VolumeName = get_pool_memory(PM_FNAME);
357 n = db_get_job_volume_names(ua->jcr, ua->db, jobid, &VolumeName);
358 bsendmsg(ua, _("Jobid %d used %d Volume(s): %s\n"), jobid, n, VolumeName);
359 free_pool_memory(VolumeName);
362 /* if no job or jobid keyword found, then we list all media */
366 /* List a specific volume? */
368 bstrncpy(mr.VolumeName, ua->argv[i], sizeof(mr.VolumeName));
369 db_list_media_records(ua->jcr, ua->db, &mr, prtit, ua, llist);
372 /* Is a specific pool wanted? */
373 for (i=1; i<ua->argc; i++) {
374 if (strcasecmp(ua->argk[i], N_("pool")) == 0) {
375 if (!get_pool_dbr(ua, &pr)) {
376 bsendmsg(ua, _("No Pool specified.\n"));
379 mr.PoolId = pr.PoolId;
380 db_list_media_records(ua->jcr, ua->db, &mr, prtit, ua, llist);
385 /* List Volumes in all pools */
386 if (!db_get_pool_ids(ua->jcr, ua->db, &num_pools, &ids)) {
387 bsendmsg(ua, _("Error obtaining pool ids. ERR=%s\n"),
388 db_strerror(ua->db));
391 if (num_pools <= 0) {
394 for (i=0; i < num_pools; i++) {
396 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
397 bsendmsg(ua, _("Pool: %s\n"), pr.Name);
400 db_list_media_records(ua->jcr, ua->db, &mr, prtit, ua, llist);
405 /* List next volume */
406 } else if (strcasecmp(ua->argk[i], N_("nextvol")) == 0 ||
407 strcasecmp(ua->argk[i], N_("nextvolume")) == 0) {
409 j = find_arg_with_value(ua, N_("days"));
411 n = atoi(ua->argv[j]);
412 if ((n < 0) || (n > 50)) {
413 bsendmsg(ua, _("Ignoring illegal value for days.\n"));
418 } else if (strcasecmp(ua->argk[i], N_("limit")) == 0
419 || strcasecmp(ua->argk[i], N_("days")) == 0) {
422 bsendmsg(ua, _("Unknown list keyword: %s\n"), NPRT(ua->argk[i]));
428 static bool list_nextvol(UAContext *ua, int ndays)
439 memset(&mr, 0, sizeof(mr));
440 int i = find_arg_with_value(ua, "job");
442 if ((job = select_job_resource(ua)) == NULL) {
446 job = (JOB *)GetResWithName(R_JOB, ua->argv[i]);
448 Jmsg(jcr, M_ERROR, 0, _("%s is not a job name.\n"), ua->argv[i]);
449 if ((job = select_job_resource(ua)) == NULL) {
454 for (run=NULL; (run = find_next_run(run, job, runtime, ndays)); ) {
455 pool = run->pool ? run->pool : NULL;
456 if (!complete_jcr_for_job(jcr, job, pool)) {
459 mr.PoolId = jcr->PoolId;
461 jcr->store = run->storage;
463 memset(&pr, 0, sizeof(pr));
464 pr.PoolId = jcr->PoolId;
465 if (! db_get_pool_record(ua->jcr, ua->db, &pr)) {
466 strcpy(pr.Name, "*UnknownPool*");
468 if (!find_next_volume_for_append(jcr, &mr, 1, false/*no create*/)) {
469 bsendmsg(ua, _("Could not find next Volume for Job %s (%s, %s).\n"),
470 job->hdr.name, pr.Name, level_to_str(run->level));
473 _("The next Volume to be used by Job \"%s\" (%s, %s) will be %s\n"),
474 job->hdr.name, pr.Name, level_to_str(run->level), mr.VolumeName);
477 if (jcr->db && jcr->db != ua->db) {
478 db_close_database(jcr, jcr->db);
483 bsendmsg(ua, _("Could not find next Volume for Job %s.\n"),
492 * For a given job, we examine all his run records
493 * to see if it is scheduled today or tomorrow.
495 RUN *find_next_run(RUN *run, JOB *job, time_t &runtime, int ndays)
497 time_t now, future, endtime;
500 int mday, wday, month, wom, i;
505 sched = job->schedule;
506 if (sched == NULL) { /* scheduled? */
507 return NULL; /* no nothing to report */
510 /* Break down the time into components */
512 endtime = now + (ndays * 60 * 60 * 24);
519 for ( ; run; run=run->next) {
521 * Find runs in next 24 hours. Day 0 is today, so if
522 * ndays=1, look at today and tomorrow.
524 for (day = 0; day <= ndays; day++) {
525 future = now + (day * 60 * 60 * 24);
527 /* Break down the time into components */
528 localtime_r(&future, &tm);
529 mday = tm.tm_mday - 1;
533 woy = tm_woy(future);
535 is_scheduled = bit_is_set(mday, run->mday) && bit_is_set(wday, run->wday) &&
536 bit_is_set(month, run->month) && bit_is_set(wom, run->wom) &&
537 bit_is_set(woy, run->woy);
540 Dmsg2(000, "day=%d is_scheduled=%d\n", day, is_scheduled);
541 Dmsg1(000, "bit_set_mday=%d\n", bit_is_set(mday, run->mday));
542 Dmsg1(000, "bit_set_wday=%d\n", bit_is_set(wday, run->wday));
543 Dmsg1(000, "bit_set_month=%d\n", bit_is_set(month, run->month));
544 Dmsg1(000, "bit_set_wom=%d\n", bit_is_set(wom, run->wom));
545 Dmsg1(000, "bit_set_woy=%d\n", bit_is_set(woy, run->woy));
548 if (is_scheduled) { /* Jobs scheduled on that day */
550 char buf[300], num[10];
551 bsnprintf(buf, sizeof(buf), "tm.hour=%d hour=", tm.tm_hour);
552 for (i=0; i<24; i++) {
553 if (bit_is_set(i, run->hour)) {
554 bsnprintf(num, sizeof(num), "%d ", i);
555 bstrncat(buf, num, sizeof(buf));
558 bstrncat(buf, "\n", sizeof(buf));
559 Dmsg1(000, "%s", buf);
561 /* find time (time_t) job is to be run */
562 localtime_r(&future, &runtm);
563 for (i= 0; i < 24; i++) {
564 if (bit_is_set(i, run->hour)) {
566 runtm.tm_min = run->minute;
568 runtime = mktime(&runtm);
569 Dmsg2(200, "now=%d runtime=%d\n", now, runtime);
570 if ((runtime > now) && (runtime < endtime)) {
571 Dmsg2(200, "Found it level=%d %c\n", run->level, run->level);
572 return run; /* found it, return run resource */
578 } /* end for loop over runs */
583 * Fill in the remaining fields of the jcr as if it
584 * is going to run the job.
586 int complete_jcr_for_job(JCR *jcr, JOB *job, POOL *pool)
590 memset(&pr, 0, sizeof(POOL_DBR));
591 set_jcr_defaults(jcr, job);
593 jcr->pool = pool; /* override */
595 jcr->db = jcr->db=db_init_database(jcr, jcr->catalog->db_name, jcr->catalog->db_user,
596 jcr->catalog->db_password, jcr->catalog->db_address,
597 jcr->catalog->db_port, jcr->catalog->db_socket,
598 jcr->catalog->mult_db_connections);
599 if (!jcr->db || !db_open_database(jcr, jcr->db)) {
600 Jmsg(jcr, M_FATAL, 0, _("Could not open database \"%s\".\n"),
601 jcr->catalog->db_name);
603 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
607 bstrncpy(pr.Name, jcr->pool->hdr.name, sizeof(pr.Name));
608 while (!db_get_pool_record(jcr, jcr->db, &pr)) { /* get by Name */
609 /* Try to create the pool */
610 if (create_pool(jcr, jcr->db, jcr->pool, POOL_OP_CREATE) < 0) {
611 Jmsg(jcr, M_FATAL, 0, _("Pool %s not in database. %s"), pr.Name,
612 db_strerror(jcr->db));
614 db_close_database(jcr, jcr->db);
619 Jmsg(jcr, M_INFO, 0, _("Pool %s created in database.\n"), pr.Name);
622 jcr->PoolId = pr.PoolId;
623 jcr->jr.PoolId = pr.PoolId;
628 static void con_lock_release(void *arg)
633 void do_messages(UAContext *ua, const char *cmd)
637 bool do_truncate = false;
640 pthread_cleanup_push(con_lock_release, (void *)NULL);
642 while (fgets(msg, sizeof(msg), con_fd)) {
644 ua->UA_sock->msg = check_pool_memory_size(ua->UA_sock->msg, mlen+1);
645 strcpy(ua->UA_sock->msg, msg);
646 ua->UA_sock->msglen = mlen;
647 bnet_send(ua->UA_sock);
651 (void)ftruncate(fileno(con_fd), 0L);
653 console_msg_pending = FALSE;
654 ua->user_notified_msg_pending = FALSE;
655 pthread_cleanup_pop(0);
660 int qmessagescmd(UAContext *ua, const char *cmd)
662 if (console_msg_pending && ua->auto_display_messages) {
663 do_messages(ua, cmd);
668 int messagescmd(UAContext *ua, const char *cmd)
670 if (console_msg_pending) {
671 do_messages(ua, cmd);
673 bnet_fsend(ua->UA_sock, _("You have no messages.\n"));
679 * Callback routine for "printing" database file listing
681 void prtit(void *ctx, const char *msg)
683 UAContext *ua = (UAContext *)ctx;
685 bnet_fsend(ua->UA_sock, "%s", msg);
689 * Format message and send to other end.
691 * If the UA_sock is NULL, it means that there is no user
692 * agent, so we are being called from Bacula core. In
693 * that case direct the messages to the Job.
695 void bsendmsg(void *ctx, const char *fmt, ...)
698 UAContext *ua = (UAContext *)ctx;
699 BSOCK *bs = ua->UA_sock;
706 msg = get_pool_memory(PM_EMSG);
710 maxlen = sizeof_pool_memory(msg) - 1;
711 va_start(arg_ptr, fmt);
712 len = bvsnprintf(msg, maxlen, fmt, arg_ptr);
714 if (len < 0 || len >= maxlen) {
715 msg = realloc_pool_memory(msg, maxlen + maxlen/2);
723 } else { /* No UA, send to Job */
724 Jmsg(ua->jcr, M_INFO, 0, "%s", msg);
725 free_pool_memory(msg);