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
523 for (day = 0; day <= ndays; day++) {
524 future = now + (day * 60 * 60 * 24);
526 /* Break down the time into components */
527 localtime_r(&future, &tm);
528 mday = tm.tm_mday - 1;
532 woy = tm_woy(future);
534 is_scheduled = bit_is_set(mday, run->mday) && bit_is_set(wday, run->wday) &&
535 bit_is_set(month, run->month) && bit_is_set(wom, run->wom) &&
536 bit_is_set(woy, run->woy);
539 Dmsg2(000, "day=%d is_scheduled=%d\n", day, is_scheduled);
540 Dmsg1(000, "bit_set_mday=%d\n", bit_is_set(mday, run->mday));
541 Dmsg1(000, "bit_set_wday=%d\n", bit_is_set(wday, run->wday));
542 Dmsg1(000, "bit_set_month=%d\n", bit_is_set(month, run->month));
543 Dmsg1(000, "bit_set_wom=%d\n", bit_is_set(wom, run->wom));
544 Dmsg1(000, "bit_set_woy=%d\n", bit_is_set(woy, run->woy));
547 if (is_scheduled) { /* Jobs scheduled on that day */
549 char buf[300], num[10];
550 bsnprintf(buf, sizeof(buf), "tm.hour=%d hour=", tm.tm_hour);
551 for (i=0; i<24; i++) {
552 if (bit_is_set(i, run->hour)) {
553 bsnprintf(num, sizeof(num), "%d ", i);
554 bstrncat(buf, num, sizeof(buf));
557 bstrncat(buf, "\n", sizeof(buf));
558 Dmsg1(000, "%s", buf);
560 /* find time (time_t) job is to be run */
561 localtime_r(&future, &runtm);
562 for (i= 0; i < 24; i++) {
563 if (bit_is_set(i, run->hour)) {
565 runtm.tm_min = run->minute;
567 runtime = mktime(&runtm);
568 Dmsg2(200, "now=%d runtime=%d\n", now, runtime);
569 if ((runtime > now) && (runtime < endtime)) {
570 Dmsg2(200, "Found it level=%d %c\n", run->level, run->level);
571 return run; /* found it, return run resource */
577 } /* end for loop over runs */
582 * Fill in the remaining fields of the jcr as if it
583 * is going to run the job.
585 int complete_jcr_for_job(JCR *jcr, JOB *job, POOL *pool)
589 memset(&pr, 0, sizeof(POOL_DBR));
590 set_jcr_defaults(jcr, job);
592 jcr->pool = pool; /* override */
594 jcr->db = jcr->db=db_init_database(jcr, jcr->catalog->db_name, jcr->catalog->db_user,
595 jcr->catalog->db_password, jcr->catalog->db_address,
596 jcr->catalog->db_port, jcr->catalog->db_socket,
597 jcr->catalog->mult_db_connections);
598 if (!jcr->db || !db_open_database(jcr, jcr->db)) {
599 Jmsg(jcr, M_FATAL, 0, _("Could not open database \"%s\".\n"),
600 jcr->catalog->db_name);
602 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
606 bstrncpy(pr.Name, jcr->pool->hdr.name, sizeof(pr.Name));
607 while (!db_get_pool_record(jcr, jcr->db, &pr)) { /* get by Name */
608 /* Try to create the pool */
609 if (create_pool(jcr, jcr->db, jcr->pool, POOL_OP_CREATE) < 0) {
610 Jmsg(jcr, M_FATAL, 0, _("Pool %s not in database. %s"), pr.Name,
611 db_strerror(jcr->db));
613 db_close_database(jcr, jcr->db);
618 Jmsg(jcr, M_INFO, 0, _("Pool %s created in database.\n"), pr.Name);
621 jcr->PoolId = pr.PoolId;
622 jcr->jr.PoolId = pr.PoolId;
627 static void con_lock_release(void *arg)
632 void do_messages(UAContext *ua, const char *cmd)
636 bool do_truncate = false;
639 pthread_cleanup_push(con_lock_release, (void *)NULL);
641 while (fgets(msg, sizeof(msg), con_fd)) {
643 ua->UA_sock->msg = check_pool_memory_size(ua->UA_sock->msg, mlen+1);
644 strcpy(ua->UA_sock->msg, msg);
645 ua->UA_sock->msglen = mlen;
646 bnet_send(ua->UA_sock);
650 (void)ftruncate(fileno(con_fd), 0L);
652 console_msg_pending = FALSE;
653 ua->user_notified_msg_pending = FALSE;
654 pthread_cleanup_pop(0);
659 int qmessagescmd(UAContext *ua, const char *cmd)
661 if (console_msg_pending && ua->auto_display_messages) {
662 do_messages(ua, cmd);
667 int messagescmd(UAContext *ua, const char *cmd)
669 if (console_msg_pending) {
670 do_messages(ua, cmd);
672 bnet_fsend(ua->UA_sock, _("You have no messages.\n"));
678 * Callback routine for "printing" database file listing
680 void prtit(void *ctx, const char *msg)
682 UAContext *ua = (UAContext *)ctx;
684 bnet_fsend(ua->UA_sock, "%s", msg);
688 * Format message and send to other end.
690 * If the UA_sock is NULL, it means that there is no user
691 * agent, so we are being called from Bacula core. In
692 * that case direct the messages to the Job.
694 void bsendmsg(void *ctx, const char *fmt, ...)
697 UAContext *ua = (UAContext *)ctx;
698 BSOCK *bs = ua->UA_sock;
705 msg = get_pool_memory(PM_EMSG);
709 maxlen = sizeof_pool_memory(msg) - 1;
710 va_start(arg_ptr, fmt);
711 len = bvsnprintf(msg, maxlen, fmt, arg_ptr);
713 if (len < 0 || len >= maxlen) {
714 msg = realloc_pool_memory(msg, maxlen + maxlen/2);
722 } else { /* No UA, send to Job */
723 Jmsg(ua->jcr, M_INFO, 0, "%s", msg);
724 free_pool_memory(msg);