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);
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 = 1;
61 ua->auto_display_messages = 0;
64 bsendmsg(ua, _("ON or OFF keyword missing.\n"));
71 * Turn batch processing on/off
73 int gui_cmd(UAContext *ua, const char *cmd)
75 static const char *kw[] = {
80 switch (find_arg_keyword(ua, kw)) {
90 bsendmsg(ua, _("ON or OFF keyword missing.\n"));
98 struct showstruct {const char *res_name; int type;};
99 static struct showstruct reses[] = {
100 {N_("directors"), R_DIRECTOR},
101 {N_("clients"), R_CLIENT},
102 {N_("counters"), R_COUNTER},
103 {N_("devices"), R_DEVICE},
105 {N_("storages"), R_STORAGE},
106 {N_("catalogs"), R_CATALOG},
107 {N_("schedules"), R_SCHEDULE},
108 {N_("filesets"), R_FILESET},
109 {N_("pools"), R_POOL},
110 {N_("messages"), R_MSGS},
121 * show <resource-keyword-name> e.g. show directors
122 * show <resource-keyword-name>=<name> e.g. show director=HeadMan
125 int show_cmd(UAContext *ua, const char *cmd)
132 Dmsg1(20, "show: %s\n", ua->UA_sock->msg);
136 for (i=1; i<ua->argc; i++) {
138 res_name = ua->argk[i];
139 if (!ua->argv[i]) { /* was a name given? */
140 /* No name, dump all resources of specified type */
142 len = strlen(res_name);
143 for (j=0; reses[j].res_name; j++) {
144 if (strncasecmp(res_name, _(reses[j].res_name), len) == 0) {
145 type = reses[j].type;
147 res = res_head[type-r_first];
156 /* Dump a single resource with specified name */
158 len = strlen(res_name);
159 for (j=0; reses[j].res_name; j++) {
160 if (strncasecmp(res_name, _(reses[j].res_name), len) == 0) {
161 type = reses[j].type;
162 res = (RES *)GetResWithName(type, ua->argv[i]);
173 for (j=r_first; j<=r_last; j++) {
174 dump_resource(j, res_head[j-r_first], bsendmsg, ua);
178 bsendmsg(ua, _("Keywords for the show command are:\n"));
179 for (j=0; reses[j].res_name; j++) {
180 bsendmsg(ua, "%s\n", _(reses[j].res_name));
184 bsendmsg(ua, _("%s resource %s not found.\n"), res_name, ua->argv[i]);
187 bsendmsg(ua, _("Resource %s not found\n"), res_name);
190 dump_resource(recurse?type:-type, res, bsendmsg, ua);
203 * List contents of database
205 * list jobs - lists all jobs run
206 * list jobid=nnn - list job data for jobid
207 * list job=name - list job data for job
208 * list jobmedia jobid=<nn>
209 * list jobmedia job=name
210 * list files jobid=<nn> - list files saved for job nn
211 * list files job=name
212 * list pools - list pool records
213 * list jobtotals - list totals for all jobs
214 * list media - list media for given pool (deprecated)
215 * list volumes - list Volumes
216 * list clients - list clients
217 * list nextvol job=xx - list the next vol to be used by job
218 * list nextvolume job=xx - same as above.
222 /* Do long or full listing */
223 int llist_cmd(UAContext *ua, const char *cmd)
225 return do_list_cmd(ua, cmd, VERT_LIST);
228 /* Do short or summary listing */
229 int list_cmd(UAContext *ua, const char *cmd)
231 return do_list_cmd(ua, cmd, HORZ_LIST);
234 static int do_list_cmd(UAContext *ua, const char *cmd, e_list_type llist)
246 memset(&jr, 0, sizeof(jr));
247 memset(&pr, 0, sizeof(pr));
248 memset(&mr, 0, sizeof(mr));
250 Dmsg1(20, "list: %s\n", cmd);
253 bsendmsg(ua, _("Hey! DB is NULL\n"));
256 /* Scan arguments looking for things to do */
257 for (i=1; i<ua->argc; i++) {
259 if (strcasecmp(ua->argk[i], _("jobs")) == 0) {
260 /* Apply any limit */
261 j = find_arg_with_value(ua, _("limit"));
263 jr.limit = atoi(ua->argv[j]);
265 db_list_job_records(ua->jcr, ua->db, &jr, prtit, ua, llist);
268 } else if (strcasecmp(ua->argk[i], _("jobtotals")) == 0) {
269 db_list_job_totals(ua->jcr, ua->db, &jr, prtit, ua);
272 } else if (strcasecmp(ua->argk[i], _("jobid")) == 0) {
274 jobid = str_to_int64(ua->argv[i]);
277 db_list_job_records(ua->jcr, ua->db, &jr, prtit, ua, llist);
282 } else if (strcasecmp(ua->argk[i], _("job")) == 0 && ua->argv[i]) {
283 bstrncpy(jr.Job, ua->argv[i], MAX_NAME_LENGTH);
285 db_list_job_records(ua->jcr, ua->db, &jr, prtit, ua, llist);
288 } else if (strcasecmp(ua->argk[i], _("files")) == 0) {
290 for (j=i+1; j<ua->argc; j++) {
291 if (strcasecmp(ua->argk[j], _("job")) == 0 && ua->argv[j]) {
292 bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
294 db_get_job_record(ua->jcr, ua->db, &jr);
296 } else if (strcasecmp(ua->argk[j], _("jobid")) == 0 && ua->argv[j]) {
297 jobid = str_to_int64(ua->argv[j]);
302 db_list_files_for_job(ua->jcr, ua->db, jobid, prtit, ua);
307 } else if (strcasecmp(ua->argk[i], _("jobmedia")) == 0) {
309 for (j=i+1; j<ua->argc; j++) {
310 if (strcasecmp(ua->argk[j], _("job")) == 0 && ua->argv[j]) {
311 bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
313 db_get_job_record(ua->jcr, ua->db, &jr);
315 } else if (strcasecmp(ua->argk[j], _("jobid")) == 0 && ua->argv[j]) {
316 jobid = str_to_int64(ua->argv[j]);
320 db_list_jobmedia_records(ua->jcr, ua->db, jobid, prtit, ua, llist);
324 /* List for all jobs (jobid=0) */
325 db_list_jobmedia_records(ua->jcr, ua->db, 0, prtit, ua, llist);
329 } else if (strcasecmp(ua->argk[i], _("pools")) == 0) {
330 db_list_pool_records(ua->jcr, ua->db, prtit, ua, llist);
332 } else if (strcasecmp(ua->argk[i], _("clients")) == 0) {
333 db_list_client_records(ua->jcr, ua->db, prtit, ua, llist);
336 /* List MEDIA or VOLUMES */
337 } else if (strcasecmp(ua->argk[i], _("media")) == 0 ||
338 strcasecmp(ua->argk[i], _("volumes")) == 0) {
340 for (j=i+1; j<ua->argc; j++) {
341 if (strcasecmp(ua->argk[j], _("job")) == 0 && ua->argv[j]) {
342 bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
344 db_get_job_record(ua->jcr, ua->db, &jr);
346 } else if (strcasecmp(ua->argk[j], _("jobid")) == 0 && ua->argv[j]) {
347 jobid = str_to_int64(ua->argv[j]);
351 VolumeName = get_pool_memory(PM_FNAME);
352 n = db_get_job_volume_names(ua->jcr, ua->db, jobid, &VolumeName);
353 bsendmsg(ua, _("Jobid %d used %d Volume(s): %s\n"), jobid, n, VolumeName);
354 free_pool_memory(VolumeName);
357 /* if no job or jobid keyword found, then we list all media */
361 /* Is a specific pool wanted? */
362 for (i=1; i<ua->argc; i++) {
363 if (strcasecmp(ua->argk[i], _("pool")) == 0) {
364 if (!get_pool_dbr(ua, &pr)) {
365 bsendmsg(ua, _("No Pool specified.\n"));
368 mr.PoolId = pr.PoolId;
369 db_list_media_records(ua->jcr, ua->db, &mr, prtit, ua, llist);
373 /* List Volumes in all pools */
374 if (!db_get_pool_ids(ua->jcr, ua->db, &num_pools, &ids)) {
375 bsendmsg(ua, _("Error obtaining pool ids. ERR=%s\n"),
376 db_strerror(ua->db));
379 if (num_pools <= 0) {
382 for (i=0; i < num_pools; i++) {
384 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
385 bsendmsg(ua, _("Pool: %s\n"), pr.Name);
388 db_list_media_records(ua->jcr, ua->db, &mr, prtit, ua, llist);
393 /* List a specific volume */
394 } else if (strcasecmp(ua->argk[i], _("volume")) == 0) {
396 bsendmsg(ua, _("No Volume Name specified.\n"));
399 bstrncpy(mr.VolumeName, ua->argv[i], sizeof(mr.VolumeName));
400 db_list_media_records(ua->jcr, ua->db, &mr, prtit, ua, llist);
402 /* List next volume */
403 } else if (strcasecmp(ua->argk[i], _("nextvol")) == 0 ||
404 strcasecmp(ua->argk[i], _("nextvolume")) == 0) {
406 } else if (strcasecmp(ua->argk[i], _("limit")) == 0) {
409 bsendmsg(ua, _("Unknown list keyword: %s\n"), NPRT(ua->argk[i]));
415 static bool list_nextvol(UAContext *ua)
425 memset(&mr, 0, sizeof(mr));
426 int i = find_arg_with_value(ua, "job");
428 if ((job = select_job_resource(ua)) == NULL) {
432 job = (JOB *)GetResWithName(R_JOB, ua->argv[i]);
434 Jmsg(jcr, M_ERROR, 0, _("%s is not a job name.\n"), ua->argv[i]);
435 if ((job = select_job_resource(ua)) == NULL) {
440 for (run=NULL; (run = find_next_run(run, job, runtime)); ) {
441 pool = run->pool ? run->pool : NULL;
442 if (!complete_jcr_for_job(jcr, job, pool)) {
445 mr.PoolId = jcr->PoolId;
447 jcr->store = run->storage;
449 if (!find_next_volume_for_append(jcr, &mr, 0)) {
450 bsendmsg(ua, _("Could not find next Volume.\n"));
452 bsendmsg(ua, _("The next Volume to be used by Job \"%s\" will be %s\n"),
453 job->hdr.name, mr.VolumeName);
456 if (jcr->db && jcr->db != ua->db) {
457 db_close_database(jcr, jcr->db);
462 bsendmsg(ua, _("Could not find next Volume.\n"));
470 * For a given job, we examine all his run records
471 * to see if it is scheduled today or tomorrow.
473 RUN *find_next_run(RUN *run, JOB *job, time_t &runtime)
475 time_t now, tomorrow;
478 int mday, wday, month, wom, tmday, twday, tmonth, twom, i;
482 sched = job->schedule;
483 if (sched == NULL) { /* scheduled? */
484 return NULL; /* no nothing to report */
486 /* Break down current time into components */
488 localtime_r(&now, &tm);
489 mday = tm.tm_mday - 1;
495 /* Break down tomorrow into components */
496 tomorrow = now + 60 * 60 * 24;
497 localtime_r(&tomorrow, &tm);
498 tmday = tm.tm_mday - 1;
502 twoy = tm_woy(tomorrow);
509 for ( ; run; run=run->next) {
511 * Find runs in next 24 hours
513 tod = bit_is_set(mday, run->mday) && bit_is_set(wday, run->wday) &&
514 bit_is_set(month, run->month) && bit_is_set(wom, run->wom) &&
515 bit_is_set(woy, run->woy);
517 tom = bit_is_set(tmday, run->mday) && bit_is_set(twday, run->wday) &&
518 bit_is_set(tmonth, run->month) && bit_is_set(twom, run->wom) &&
519 bit_is_set(twoy, run->woy);
522 Dmsg2(000, "tod=%d tom=%d\n", tod, tom);
523 Dmsg1(000, "bit_set_mday=%d\n", bit_is_set(mday, run->mday));
524 Dmsg1(000, "bit_set_wday=%d\n", bit_is_set(wday, run->wday));
525 Dmsg1(000, "bit_set_month=%d\n", bit_is_set(month, run->month));
526 Dmsg1(000, "bit_set_wom=%d\n", bit_is_set(wom, run->wom));
527 Dmsg1(000, "bit_set_woy=%d\n", bit_is_set(woy, run->woy));
529 if (tod) { /* Jobs scheduled today (next 24 hours) */
531 char buf[300], num[10];
532 bsnprintf(buf, sizeof(buf), "tm.hour=%d hour=", tm.tm_hour);
533 for (i=0; i<24; i++) {
534 if (bit_is_set(i, run->hour)) {
535 bsnprintf(num, sizeof(num), "%d ", i);
536 bstrncat(buf, num, sizeof(buf));
539 bstrncat(buf, "\n", sizeof(buf));
540 Dmsg1(000, "%s", buf);
542 /* find time (time_t) job is to be run */
543 localtime_r(&now, &tm);
544 for (i=tm.tm_hour; i < 24; i++) {
545 if (bit_is_set(i, run->hour)) {
547 tm.tm_min = run->minute;
549 runtime = mktime(&tm);
550 Dmsg2(200, "now=%d runtime=%d\n", now, runtime);
552 Dmsg2(200, "Found it level=%d %c\n", run->level, run->level);
553 return run; /* found it, return run resource */
559 // Dmsg2(200, "runtime=%d now=%d\n", runtime, now);
560 if (tom) { /* look at jobs scheduled tomorrow */
561 localtime_r(&tomorrow, &tm);
562 for (i=0; i < 24; i++) {
563 if (bit_is_set(i, run->hour)) {
565 tm.tm_min = run->minute;
567 runtime = mktime(&tm);
568 Dmsg2(200, "now=%d runtime=%d\n", now, runtime);
569 if (runtime < tomorrow) {
570 Dmsg2(200, "Found it level=%d %c\n", run->level, run->level);
571 return run; /* found it, return run resource */
576 } /* end for loop over runs */
581 * Fill in the remaining fields of the jcr as if it
582 * is going to run the job.
584 int complete_jcr_for_job(JCR *jcr, JOB *job, POOL *pool)
588 memset(&pr, 0, sizeof(POOL_DBR));
589 set_jcr_defaults(jcr, job);
591 jcr->pool = pool; /* override */
593 jcr->db = jcr->db=db_init_database(jcr, jcr->catalog->db_name, jcr->catalog->db_user,
594 jcr->catalog->db_password, jcr->catalog->db_address,
595 jcr->catalog->db_port, jcr->catalog->db_socket,
596 jcr->catalog->mult_db_connections);
597 if (!jcr->db || !db_open_database(jcr, jcr->db)) {
598 Jmsg(jcr, M_FATAL, 0, _("Could not open database \"%s\".\n"),
599 jcr->catalog->db_name);
601 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
605 bstrncpy(pr.Name, jcr->pool->hdr.name, sizeof(pr.Name));
606 while (!db_get_pool_record(jcr, jcr->db, &pr)) { /* get by Name */
607 /* Try to create the pool */
608 if (create_pool(jcr, jcr->db, jcr->pool, POOL_OP_CREATE) < 0) {
609 Jmsg(jcr, M_FATAL, 0, _("Pool %s not in database. %s"), pr.Name,
610 db_strerror(jcr->db));
612 db_close_database(jcr, jcr->db);
617 Jmsg(jcr, M_INFO, 0, _("Pool %s created in database.\n"), pr.Name);
620 jcr->PoolId = pr.PoolId;
621 jcr->jr.PoolId = pr.PoolId;
626 static void con_lock_release(void *arg)
631 void do_messages(UAContext *ua, const char *cmd)
635 int do_truncate = FALSE;
638 pthread_cleanup_push(con_lock_release, (void *)NULL);
640 while (fgets(msg, sizeof(msg), con_fd)) {
642 ua->UA_sock->msg = check_pool_memory_size(ua->UA_sock->msg, mlen+1);
643 strcpy(ua->UA_sock->msg, msg);
644 ua->UA_sock->msglen = mlen;
645 bnet_send(ua->UA_sock);
649 ftruncate(fileno(con_fd), 0L);
651 console_msg_pending = FALSE;
652 ua->user_notified_msg_pending = FALSE;
653 pthread_cleanup_pop(0);
658 int qmessagescmd(UAContext *ua, const char *cmd)
660 if (console_msg_pending && ua->auto_display_messages) {
661 do_messages(ua, cmd);
666 int messagescmd(UAContext *ua, const char *cmd)
668 if (console_msg_pending) {
669 do_messages(ua, cmd);
671 bnet_fsend(ua->UA_sock, _("You have no messages.\n"));
677 * Callback routine for "printing" database file listing
679 void prtit(void *ctx, const char *msg)
681 UAContext *ua = (UAContext *)ctx;
683 bnet_fsend(ua->UA_sock, "%s", msg);
687 * Format message and send to other end.
689 * If the UA_sock is NULL, it means that there is no user
690 * agent, so we are being called from Bacula core. In
691 * that case direct the messages to the Job.
693 void bsendmsg(void *ctx, const char *fmt, ...)
696 UAContext *ua = (UAContext *)ctx;
697 BSOCK *bs = ua->UA_sock;
704 msg = get_pool_memory(PM_EMSG);
708 maxlen = sizeof_pool_memory(msg) - 1;
709 va_start(arg_ptr, fmt);
710 len = bvsnprintf(msg, maxlen, fmt, arg_ptr);
712 if (len < 0 || len >= maxlen) {
713 msg = realloc_pool_memory(msg, maxlen + maxlen/2);
721 } else { /* No UA, send to Job */
722 Jmsg(ua->jcr, M_INFO, 0, "%s", msg);
723 free_pool_memory(msg);