3 * Bacula Director -- User Agent Output Commands
4 * I.e. messages, listing database, showing resources, ...
6 * Kern Sibbald, September MM
12 Copyright (C) 2000-2006 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);
285 /* List JOBNAME=xxx */
286 } else if (strcasecmp(ua->argk[i], N_("job")) == 0 && ua->argv[i]) {
287 bstrncpy(jr.Name, ua->argv[i], MAX_NAME_LENGTH);
289 db_list_job_records(ua->jcr, ua->db, &jr, prtit, ua, llist);
293 } else if (strcasecmp(ua->argk[i], N_("files")) == 0) {
295 for (j=i+1; j<ua->argc; j++) {
296 if (strcasecmp(ua->argk[j], N_("job")) == 0 && ua->argv[j]) {
297 bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
299 db_get_job_record(ua->jcr, ua->db, &jr);
301 } else if (strcasecmp(ua->argk[j], N_("jobid")) == 0 && ua->argv[j]) {
302 jobid = str_to_int64(ua->argv[j]);
307 db_list_files_for_job(ua->jcr, ua->db, jobid, prtit, ua);
312 } else if (strcasecmp(ua->argk[i], N_("jobmedia")) == 0) {
314 for (j=i+1; j<ua->argc; j++) {
315 if (strcasecmp(ua->argk[j], N_("job")) == 0 && ua->argv[j]) {
316 bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
318 db_get_job_record(ua->jcr, ua->db, &jr);
320 } else if (strcasecmp(ua->argk[j], N_("jobid")) == 0 && ua->argv[j]) {
321 jobid = str_to_int64(ua->argv[j]);
325 db_list_jobmedia_records(ua->jcr, ua->db, jobid, prtit, ua, llist);
329 /* List for all jobs (jobid=0) */
330 db_list_jobmedia_records(ua->jcr, ua->db, 0, prtit, ua, llist);
334 } else if (strcasecmp(ua->argk[i], N_("pool")) == 0 ||
335 strcasecmp(ua->argk[i], N_("pools")) == 0) {
337 memset(&pr, 0, sizeof(pr));
339 bstrncpy(pr.Name, ua->argv[i], sizeof(pr.Name));
341 db_list_pool_records(ua->jcr, ua->db, &pr, prtit, ua, llist);
343 } else if (strcasecmp(ua->argk[i], N_("clients")) == 0) {
344 db_list_client_records(ua->jcr, ua->db, prtit, ua, llist);
347 /* List MEDIA or VOLUMES */
348 } else if (strcasecmp(ua->argk[i], N_("media")) == 0 ||
349 strcasecmp(ua->argk[i], N_("volume")) == 0 ||
350 strcasecmp(ua->argk[i], N_("volumes")) == 0) {
352 for (j=i+1; j<ua->argc; j++) {
353 if (strcasecmp(ua->argk[j], N_("job")) == 0 && ua->argv[j]) {
354 bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
356 db_get_job_record(ua->jcr, ua->db, &jr);
358 } else if (strcasecmp(ua->argk[j], N_("jobid")) == 0 && ua->argv[j]) {
359 jobid = str_to_int64(ua->argv[j]);
363 VolumeName = get_pool_memory(PM_FNAME);
364 n = db_get_job_volume_names(ua->jcr, ua->db, jobid, &VolumeName);
365 bsendmsg(ua, _("Jobid %d used %d Volume(s): %s\n"), jobid, n, VolumeName);
366 free_pool_memory(VolumeName);
369 /* if no job or jobid keyword found, then we list all media */
373 /* List a specific volume? */
375 bstrncpy(mr.VolumeName, ua->argv[i], sizeof(mr.VolumeName));
376 db_list_media_records(ua->jcr, ua->db, &mr, prtit, ua, llist);
379 /* Is a specific pool wanted? */
380 for (i=1; i<ua->argc; i++) {
381 if (strcasecmp(ua->argk[i], N_("pool")) == 0) {
382 if (!get_pool_dbr(ua, &pr)) {
383 bsendmsg(ua, _("No Pool specified.\n"));
386 mr.PoolId = pr.PoolId;
387 db_list_media_records(ua->jcr, ua->db, &mr, prtit, ua, llist);
392 /* List Volumes in all pools */
393 if (!db_get_pool_ids(ua->jcr, ua->db, &num_pools, &ids)) {
394 bsendmsg(ua, _("Error obtaining pool ids. ERR=%s\n"),
395 db_strerror(ua->db));
398 if (num_pools <= 0) {
401 for (i=0; i < num_pools; i++) {
403 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
404 bsendmsg(ua, _("Pool: %s\n"), pr.Name);
407 db_list_media_records(ua->jcr, ua->db, &mr, prtit, ua, llist);
412 /* List next volume */
413 } else if (strcasecmp(ua->argk[i], N_("nextvol")) == 0 ||
414 strcasecmp(ua->argk[i], N_("nextvolume")) == 0) {
416 j = find_arg_with_value(ua, N_("days"));
418 n = atoi(ua->argv[j]);
419 if ((n < 0) || (n > 50)) {
420 bsendmsg(ua, _("Ignoring illegal value for days.\n"));
425 } else if (strcasecmp(ua->argk[i], N_("limit")) == 0
426 || strcasecmp(ua->argk[i], N_("days")) == 0) {
429 bsendmsg(ua, _("Unknown list keyword: %s\n"), NPRT(ua->argk[i]));
435 static bool list_nextvol(UAContext *ua, int ndays)
446 memset(&mr, 0, sizeof(mr));
447 int i = find_arg_with_value(ua, "job");
449 if ((job = select_job_resource(ua)) == NULL) {
453 job = (JOB *)GetResWithName(R_JOB, ua->argv[i]);
455 Jmsg(jcr, M_ERROR, 0, _("%s is not a job name.\n"), ua->argv[i]);
456 if ((job = select_job_resource(ua)) == NULL) {
461 for (run=NULL; (run = find_next_run(run, job, runtime, ndays)); ) {
462 pool = run->pool ? run->pool : NULL;
463 if (!complete_jcr_for_job(jcr, job, pool)) {
466 mr.PoolId = jcr->PoolId;
468 jcr->store = run->storage;
470 memset(&pr, 0, sizeof(pr));
471 pr.PoolId = jcr->PoolId;
472 if (! db_get_pool_record(ua->jcr, ua->db, &pr)) {
473 strcpy(pr.Name, "*UnknownPool*");
475 if (!find_next_volume_for_append(jcr, &mr, 1, false/*no create*/)) {
476 bsendmsg(ua, _("Could not find next Volume for Job %s (%s, %s).\n"),
477 job->hdr.name, pr.Name, level_to_str(run->level));
480 _("The next Volume to be used by Job \"%s\" (%s, %s) will be %s\n"),
481 job->hdr.name, pr.Name, level_to_str(run->level), mr.VolumeName);
484 if (jcr->db && jcr->db != ua->db) {
485 db_close_database(jcr, jcr->db);
490 bsendmsg(ua, _("Could not find next Volume for Job %s.\n"),
499 * For a given job, we examine all his run records
500 * to see if it is scheduled today or tomorrow.
502 RUN *find_next_run(RUN *run, JOB *job, time_t &runtime, int ndays)
504 time_t now, future, endtime;
507 int mday, wday, month, wom, i;
512 sched = job->schedule;
513 if (sched == NULL) { /* scheduled? */
514 return NULL; /* no nothing to report */
517 /* Break down the time into components */
519 endtime = now + (ndays * 60 * 60 * 24);
526 for ( ; run; run=run->next) {
528 * Find runs in next 24 hours. Day 0 is today, so if
529 * ndays=1, look at today and tomorrow.
531 for (day = 0; day <= ndays; day++) {
532 future = now + (day * 60 * 60 * 24);
534 /* Break down the time into components */
535 localtime_r(&future, &tm);
536 mday = tm.tm_mday - 1;
540 woy = tm_woy(future);
542 is_scheduled = bit_is_set(mday, run->mday) && bit_is_set(wday, run->wday) &&
543 bit_is_set(month, run->month) && bit_is_set(wom, run->wom) &&
544 bit_is_set(woy, run->woy);
547 Dmsg2(000, "day=%d is_scheduled=%d\n", day, is_scheduled);
548 Dmsg1(000, "bit_set_mday=%d\n", bit_is_set(mday, run->mday));
549 Dmsg1(000, "bit_set_wday=%d\n", bit_is_set(wday, run->wday));
550 Dmsg1(000, "bit_set_month=%d\n", bit_is_set(month, run->month));
551 Dmsg1(000, "bit_set_wom=%d\n", bit_is_set(wom, run->wom));
552 Dmsg1(000, "bit_set_woy=%d\n", bit_is_set(woy, run->woy));
555 if (is_scheduled) { /* Jobs scheduled on that day */
557 char buf[300], num[10];
558 bsnprintf(buf, sizeof(buf), "tm.hour=%d hour=", tm.tm_hour);
559 for (i=0; i<24; i++) {
560 if (bit_is_set(i, run->hour)) {
561 bsnprintf(num, sizeof(num), "%d ", i);
562 bstrncat(buf, num, sizeof(buf));
565 bstrncat(buf, "\n", sizeof(buf));
566 Dmsg1(000, "%s", buf);
568 /* find time (time_t) job is to be run */
569 localtime_r(&future, &runtm);
570 for (i= 0; i < 24; i++) {
571 if (bit_is_set(i, run->hour)) {
573 runtm.tm_min = run->minute;
575 runtime = mktime(&runtm);
576 Dmsg2(200, "now=%d runtime=%d\n", now, runtime);
577 if ((runtime > now) && (runtime < endtime)) {
578 Dmsg2(200, "Found it level=%d %c\n", run->level, run->level);
579 return run; /* found it, return run resource */
585 } /* end for loop over runs */
590 * Fill in the remaining fields of the jcr as if it
591 * is going to run the job.
593 int complete_jcr_for_job(JCR *jcr, JOB *job, POOL *pool)
597 memset(&pr, 0, sizeof(POOL_DBR));
598 set_jcr_defaults(jcr, job);
600 jcr->pool = pool; /* override */
602 jcr->db = jcr->db=db_init_database(jcr, jcr->catalog->db_name, jcr->catalog->db_user,
603 jcr->catalog->db_password, jcr->catalog->db_address,
604 jcr->catalog->db_port, jcr->catalog->db_socket,
605 jcr->catalog->mult_db_connections);
606 if (!jcr->db || !db_open_database(jcr, jcr->db)) {
607 Jmsg(jcr, M_FATAL, 0, _("Could not open database \"%s\".\n"),
608 jcr->catalog->db_name);
610 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
614 bstrncpy(pr.Name, jcr->pool->hdr.name, sizeof(pr.Name));
615 while (!db_get_pool_record(jcr, jcr->db, &pr)) { /* get by Name */
616 /* Try to create the pool */
617 if (create_pool(jcr, jcr->db, jcr->pool, POOL_OP_CREATE) < 0) {
618 Jmsg(jcr, M_FATAL, 0, _("Pool %s not in database. %s"), pr.Name,
619 db_strerror(jcr->db));
621 db_close_database(jcr, jcr->db);
626 Jmsg(jcr, M_INFO, 0, _("Pool %s created in database.\n"), pr.Name);
629 jcr->PoolId = pr.PoolId;
630 jcr->jr.PoolId = pr.PoolId;
635 static void con_lock_release(void *arg)
640 void do_messages(UAContext *ua, const char *cmd)
644 bool do_truncate = false;
647 pthread_cleanup_push(con_lock_release, (void *)NULL);
649 while (fgets(msg, sizeof(msg), con_fd)) {
651 ua->UA_sock->msg = check_pool_memory_size(ua->UA_sock->msg, mlen+1);
652 strcpy(ua->UA_sock->msg, msg);
653 ua->UA_sock->msglen = mlen;
654 bnet_send(ua->UA_sock);
658 (void)ftruncate(fileno(con_fd), 0L);
660 console_msg_pending = FALSE;
661 ua->user_notified_msg_pending = FALSE;
662 pthread_cleanup_pop(0);
667 int qmessagescmd(UAContext *ua, const char *cmd)
669 if (console_msg_pending && ua->auto_display_messages) {
670 do_messages(ua, cmd);
675 int messagescmd(UAContext *ua, const char *cmd)
677 if (console_msg_pending) {
678 do_messages(ua, cmd);
680 bnet_fsend(ua->UA_sock, _("You have no messages.\n"));
686 * Callback routine for "printing" database file listing
688 void prtit(void *ctx, const char *msg)
690 UAContext *ua = (UAContext *)ctx;
692 bnet_fsend(ua->UA_sock, "%s", msg);
696 * Format message and send to other end.
698 * If the UA_sock is NULL, it means that there is no user
699 * agent, so we are being called from Bacula core. In
700 * that case direct the messages to the Job.
702 void bsendmsg(void *ctx, const char *fmt, ...)
705 UAContext *ua = (UAContext *)ctx;
706 BSOCK *bs = ua->UA_sock;
713 msg = get_pool_memory(PM_EMSG);
717 maxlen = sizeof_pool_memory(msg) - 1;
718 va_start(arg_ptr, fmt);
719 len = bvsnprintf(msg, maxlen, fmt, arg_ptr);
721 if (len < 0 || len >= maxlen) {
722 msg = realloc_pool_memory(msg, maxlen + maxlen/2);
730 } else { /* No UA, send to Job */
731 Jmsg(ua->jcr, M_INFO, 0, "%s", msg);
732 free_pool_memory(msg);