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;
37 /* Imported functions */
39 /* Forward referenced functions */
40 static int do_list_cmd(UAContext *ua, const char *cmd, e_list_type llist);
41 static bool list_nextvol(UAContext *ua, int ndays);
44 * Turn auto display of console messages on/off
46 int autodisplay_cmd(UAContext *ua, const char *cmd)
48 static const char *kw[] = {
53 switch (find_arg_keyword(ua, kw)) {
55 ua->auto_display_messages = true;
58 ua->auto_display_messages = false;
61 bsendmsg(ua, _("ON or OFF keyword missing.\n"));
68 * Turn GUI mode on/off
70 int gui_cmd(UAContext *ua, const char *cmd)
72 static const char *kw[] = {
77 switch (find_arg_keyword(ua, kw)) {
79 ua->jcr->gui = ua->gui = true;
82 ua->jcr->gui = ua->gui = false;
85 bsendmsg(ua, _("ON or OFF keyword missing.\n"));
93 struct showstruct {const char *res_name; int type;};
94 static struct showstruct reses[] = {
95 {NT_("directors"), R_DIRECTOR},
96 {NT_("clients"), R_CLIENT},
97 {NT_("counters"), R_COUNTER},
98 {NT_("devices"), R_DEVICE},
100 {NT_("storages"), R_STORAGE},
101 {NT_("catalogs"), R_CATALOG},
102 {NT_("schedules"), R_SCHEDULE},
103 {NT_("filesets"), R_FILESET},
104 {NT_("pools"), R_POOL},
105 {NT_("messages"), R_MSGS},
116 * show <resource-keyword-name> e.g. show directors
117 * show <resource-keyword-name>=<name> e.g. show director=HeadMan
120 int show_cmd(UAContext *ua, const char *cmd)
127 Dmsg1(20, "show: %s\n", ua->UA_sock->msg);
131 for (i=1; i<ua->argc; i++) {
133 res_name = ua->argk[i];
134 if (!ua->argv[i]) { /* was a name given? */
135 /* No name, dump all resources of specified type */
137 len = strlen(res_name);
138 for (j=0; reses[j].res_name; j++) {
139 if (strncasecmp(res_name, _(reses[j].res_name), len) == 0) {
140 type = reses[j].type;
142 res = res_head[type-r_first];
151 /* Dump a single resource with specified name */
153 len = strlen(res_name);
154 for (j=0; reses[j].res_name; j++) {
155 if (strncasecmp(res_name, _(reses[j].res_name), len) == 0) {
156 type = reses[j].type;
157 res = (RES *)GetResWithName(type, ua->argv[i]);
168 for (j=r_first; j<=r_last; j++) {
169 dump_resource(j, res_head[j-r_first], bsendmsg, ua);
173 bsendmsg(ua, _("Keywords for the show command are:\n"));
174 for (j=0; reses[j].res_name; j++) {
175 bsendmsg(ua, "%s\n", _(reses[j].res_name));
179 bsendmsg(ua, _("%s resource %s not found.\n"), res_name, ua->argv[i]);
182 bsendmsg(ua, _("Resource %s not found\n"), res_name);
185 dump_resource(recurse?type:-type, res, bsendmsg, ua);
198 * List contents of database
200 * list jobs - lists all jobs run
201 * list jobid=nnn - list job data for jobid
202 * list ujobid=uname - list job data for unique jobid
203 * list job=name - list all jobs with "name"
204 * list jobname=name - same as above
205 * list jobmedia jobid=<nn>
206 * list jobmedia job=name
207 * list files jobid=<nn> - list files saved for job nn
208 * list files job=name
209 * list pools - list pool records
210 * list jobtotals - list totals for all jobs
211 * list media - list media for given pool (deprecated)
212 * list volumes - list Volumes
213 * list clients - list clients
214 * list nextvol job=xx - list the next vol to be used by job
215 * list nextvolume job=xx - same as above.
219 /* Do long or full listing */
220 int llist_cmd(UAContext *ua, const char *cmd)
222 return do_list_cmd(ua, cmd, VERT_LIST);
225 /* Do short or summary listing */
226 int list_cmd(UAContext *ua, const char *cmd)
228 return do_list_cmd(ua, cmd, HORZ_LIST);
231 static int do_list_cmd(UAContext *ua, const char *cmd, e_list_type llist)
243 memset(&jr, 0, sizeof(jr));
244 memset(&pr, 0, sizeof(pr));
245 memset(&mr, 0, sizeof(mr));
247 Dmsg1(20, "list: %s\n", cmd);
250 bsendmsg(ua, _("Hey! DB is NULL\n"));
253 /* Scan arguments looking for things to do */
254 for (i=1; i<ua->argc; i++) {
256 if (strcasecmp(ua->argk[i], NT_("jobs")) == 0) {
257 /* Apply any limit */
258 j = find_arg_with_value(ua, NT_("limit"));
260 jr.limit = atoi(ua->argv[j]);
262 db_list_job_records(ua->jcr, ua->db, &jr, prtit, ua, llist);
265 } else if (strcasecmp(ua->argk[i], NT_("jobtotals")) == 0) {
266 db_list_job_totals(ua->jcr, ua->db, &jr, prtit, ua);
269 } else if (strcasecmp(ua->argk[i], NT_("jobid")) == 0) {
271 jobid = str_to_int64(ua->argv[i]);
274 db_list_job_records(ua->jcr, ua->db, &jr, prtit, ua, llist);
279 } else if ((strcasecmp(ua->argk[i], NT_("job")) == 0 ||
280 strcasecmp(ua->argk[i], NT_("jobname")) == 0) && ua->argv[i]) {
281 bstrncpy(jr.Name, ua->argv[i], MAX_NAME_LENGTH);
283 db_list_job_records(ua->jcr, ua->db, &jr, prtit, ua, llist);
285 /* List UJOBID=xxx */
286 } else if (strcasecmp(ua->argk[i], NT_("ujobid")) == 0 && ua->argv[i]) {
287 bstrncpy(jr.Job, ua->argv[i], MAX_NAME_LENGTH);
289 db_list_job_records(ua->jcr, ua->db, &jr, prtit, ua, llist);
292 } else if (strcasecmp(ua->argk[i], NT_("files")) == 0) {
294 for (j=i+1; j<ua->argc; j++) {
295 if (strcasecmp(ua->argk[j], NT_("ujobid")) == 0 && ua->argv[j]) {
296 bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
298 db_get_job_record(ua->jcr, ua->db, &jr);
300 } else if (strcasecmp(ua->argk[j], NT_("jobid")) == 0 && ua->argv[j]) {
301 jobid = str_to_int64(ua->argv[j]);
306 db_list_files_for_job(ua->jcr, ua->db, jobid, prtit, ua);
311 } else if (strcasecmp(ua->argk[i], NT_("jobmedia")) == 0) {
313 for (j=i+1; j<ua->argc; j++) {
314 if (strcasecmp(ua->argk[j], NT_("ujobid")) == 0 && ua->argv[j]) {
315 bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
317 db_get_job_record(ua->jcr, ua->db, &jr);
319 } else if (strcasecmp(ua->argk[j], NT_("jobid")) == 0 && ua->argv[j]) {
320 jobid = str_to_int64(ua->argv[j]);
324 db_list_jobmedia_records(ua->jcr, ua->db, jobid, prtit, ua, llist);
328 /* List for all jobs (jobid=0) */
329 db_list_jobmedia_records(ua->jcr, ua->db, 0, prtit, ua, llist);
333 } else if (strcasecmp(ua->argk[i], NT_("pool")) == 0 ||
334 strcasecmp(ua->argk[i], NT_("pools")) == 0) {
336 memset(&pr, 0, sizeof(pr));
338 bstrncpy(pr.Name, ua->argv[i], sizeof(pr.Name));
340 db_list_pool_records(ua->jcr, ua->db, &pr, prtit, ua, llist);
342 } else if (strcasecmp(ua->argk[i], NT_("clients")) == 0) {
343 db_list_client_records(ua->jcr, ua->db, prtit, ua, llist);
346 /* List MEDIA or VOLUMES */
347 } else if (strcasecmp(ua->argk[i], NT_("media")) == 0 ||
348 strcasecmp(ua->argk[i], NT_("volume")) == 0 ||
349 strcasecmp(ua->argk[i], NT_("volumes")) == 0) {
351 for (j=i+1; j<ua->argc; j++) {
352 if (strcasecmp(ua->argk[j], NT_("ujobid")) == 0 && ua->argv[j]) {
353 bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
355 db_get_job_record(ua->jcr, ua->db, &jr);
357 } else if (strcasecmp(ua->argk[j], NT_("jobid")) == 0 && ua->argv[j]) {
358 jobid = str_to_int64(ua->argv[j]);
362 VolumeName = get_pool_memory(PM_FNAME);
363 n = db_get_job_volume_names(ua->jcr, ua->db, jobid, &VolumeName);
364 bsendmsg(ua, _("Jobid %d used %d Volume(s): %s\n"), jobid, n, VolumeName);
365 free_pool_memory(VolumeName);
368 /* if no job or jobid keyword found, then we list all media */
372 /* List a specific volume? */
374 bstrncpy(mr.VolumeName, ua->argv[i], sizeof(mr.VolumeName));
375 db_list_media_records(ua->jcr, ua->db, &mr, prtit, ua, llist);
378 /* Is a specific pool wanted? */
379 for (i=1; i<ua->argc; i++) {
380 if (strcasecmp(ua->argk[i], NT_("pool")) == 0) {
381 if (!get_pool_dbr(ua, &pr)) {
382 bsendmsg(ua, _("No Pool specified.\n"));
385 mr.PoolId = pr.PoolId;
386 db_list_media_records(ua->jcr, ua->db, &mr, prtit, ua, llist);
391 /* List Volumes in all pools */
392 if (!db_get_pool_ids(ua->jcr, ua->db, &num_pools, &ids)) {
393 bsendmsg(ua, _("Error obtaining pool ids. ERR=%s\n"),
394 db_strerror(ua->db));
397 if (num_pools <= 0) {
400 for (i=0; i < num_pools; i++) {
402 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
403 bsendmsg(ua, _("Pool: %s\n"), pr.Name);
406 db_list_media_records(ua->jcr, ua->db, &mr, prtit, ua, llist);
411 /* List next volume */
412 } else if (strcasecmp(ua->argk[i], NT_("nextvol")) == 0 ||
413 strcasecmp(ua->argk[i], NT_("nextvolume")) == 0) {
415 j = find_arg_with_value(ua, NT_("days"));
417 n = atoi(ua->argv[j]);
418 if ((n < 0) || (n > 50)) {
419 bsendmsg(ua, _("Ignoring illegal value for days.\n"));
424 } else if (strcasecmp(ua->argk[i], NT_("limit")) == 0
425 || strcasecmp(ua->argk[i], NT_("days")) == 0) {
428 bsendmsg(ua, _("Unknown list keyword: %s\n"), NPRT(ua->argk[i]));
434 static bool list_nextvol(UAContext *ua, int ndays)
445 memset(&mr, 0, sizeof(mr));
446 int i = find_arg_with_value(ua, "job");
448 if ((job = select_job_resource(ua)) == NULL) {
452 job = (JOB *)GetResWithName(R_JOB, ua->argv[i]);
454 Jmsg(jcr, M_ERROR, 0, _("%s is not a job name.\n"), ua->argv[i]);
455 if ((job = select_job_resource(ua)) == NULL) {
460 for (run=NULL; (run = find_next_run(run, job, runtime, ndays)); ) {
461 pool = run->pool ? run->pool : NULL;
462 if (!complete_jcr_for_job(jcr, job, pool)) {
465 memset(&pr, 0, sizeof(pr));
466 pr.PoolId = jcr->jr.PoolId;
467 if (! db_get_pool_record(ua->jcr, ua->db, &pr)) {
468 bstrncpy(pr.Name, "*UnknownPool*", sizeof(pr.Name));
470 mr.PoolId = jcr->jr.PoolId;
472 jcr->wstore = run->storage;
474 jcr->wstore = (STORE *)job->storage->first();
476 mr.StorageId = jcr->wstore->StorageId;
477 if (!find_next_volume_for_append(jcr, &mr, 1, false/*no create*/)) {
478 bsendmsg(ua, _("Could not find next Volume for Job %s (%s, %s).\n"),
479 job->hdr.name, pr.Name, level_to_str(run->level));
482 _("The next Volume to be used by Job \"%s\" (%s, %s) will be %s\n"),
483 job->hdr.name, pr.Name, level_to_str(run->level), mr.VolumeName);
486 if (jcr->db && jcr->db != ua->db) {
487 db_close_database(jcr, jcr->db);
492 bsendmsg(ua, _("Could not find next Volume for Job %s.\n"),
501 * For a given job, we examine all his run records
502 * to see if it is scheduled today or tomorrow.
504 RUN *find_next_run(RUN *run, JOB *job, time_t &runtime, int ndays)
506 time_t now, future, endtime;
509 int mday, wday, month, wom, i;
514 sched = job->schedule;
515 if (sched == NULL) { /* scheduled? */
516 return NULL; /* no nothing to report */
519 /* Break down the time into components */
521 endtime = now + (ndays * 60 * 60 * 24);
528 for ( ; run; run=run->next) {
530 * Find runs in next 24 hours. Day 0 is today, so if
531 * ndays=1, look at today and tomorrow.
533 for (day = 0; day <= ndays; day++) {
534 future = now + (day * 60 * 60 * 24);
536 /* Break down the time into components */
537 (void)localtime_r(&future, &tm);
538 mday = tm.tm_mday - 1;
542 woy = tm_woy(future);
544 is_scheduled = bit_is_set(mday, run->mday) && bit_is_set(wday, run->wday) &&
545 bit_is_set(month, run->month) && bit_is_set(wom, run->wom) &&
546 bit_is_set(woy, run->woy);
549 Dmsg2(000, "day=%d is_scheduled=%d\n", day, is_scheduled);
550 Dmsg1(000, "bit_set_mday=%d\n", bit_is_set(mday, run->mday));
551 Dmsg1(000, "bit_set_wday=%d\n", bit_is_set(wday, run->wday));
552 Dmsg1(000, "bit_set_month=%d\n", bit_is_set(month, run->month));
553 Dmsg1(000, "bit_set_wom=%d\n", bit_is_set(wom, run->wom));
554 Dmsg1(000, "bit_set_woy=%d\n", bit_is_set(woy, run->woy));
557 if (is_scheduled) { /* Jobs scheduled on that day */
559 char buf[300], num[10];
560 bsnprintf(buf, sizeof(buf), "tm.hour=%d hour=", tm.tm_hour);
561 for (i=0; i<24; i++) {
562 if (bit_is_set(i, run->hour)) {
563 bsnprintf(num, sizeof(num), "%d ", i);
564 bstrncat(buf, num, sizeof(buf));
567 bstrncat(buf, "\n", sizeof(buf));
568 Dmsg1(000, "%s", buf);
570 /* find time (time_t) job is to be run */
571 (void)localtime_r(&future, &runtm);
572 for (i= 0; i < 24; i++) {
573 if (bit_is_set(i, run->hour)) {
575 runtm.tm_min = run->minute;
577 runtime = mktime(&runtm);
578 Dmsg2(200, "now=%d runtime=%d\n", now, runtime);
579 if ((runtime > now) && (runtime < endtime)) {
580 Dmsg2(200, "Found it level=%d %c\n", run->level, run->level);
581 return run; /* found it, return run resource */
587 } /* end for loop over runs */
593 * Fill in the remaining fields of the jcr as if it
594 * is going to run the job.
596 int complete_jcr_for_job(JCR *jcr, JOB *job, POOL *pool)
600 memset(&pr, 0, sizeof(POOL_DBR));
601 set_jcr_defaults(jcr, job);
603 jcr->pool = pool; /* override */
605 jcr->db = jcr->db=db_init_database(jcr, jcr->catalog->db_name, jcr->catalog->db_user,
606 jcr->catalog->db_password, jcr->catalog->db_address,
607 jcr->catalog->db_port, jcr->catalog->db_socket,
608 jcr->catalog->mult_db_connections);
609 if (!jcr->db || !db_open_database(jcr, jcr->db)) {
610 Jmsg(jcr, M_FATAL, 0, _("Could not open database \"%s\".\n"),
611 jcr->catalog->db_name);
613 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
617 bstrncpy(pr.Name, jcr->pool->hdr.name, sizeof(pr.Name));
618 while (!db_get_pool_record(jcr, jcr->db, &pr)) { /* get by Name */
619 /* Try to create the pool */
620 if (create_pool(jcr, jcr->db, jcr->pool, POOL_OP_CREATE) < 0) {
621 Jmsg(jcr, M_FATAL, 0, _("Pool %s not in database. %s"), pr.Name,
622 db_strerror(jcr->db));
624 db_close_database(jcr, jcr->db);
629 Jmsg(jcr, M_INFO, 0, _("Pool %s created in database.\n"), pr.Name);
632 jcr->jr.PoolId = pr.PoolId;
637 static void con_lock_release(void *arg)
642 void do_messages(UAContext *ua, const char *cmd)
646 bool do_truncate = false;
649 pthread_cleanup_push(con_lock_release, (void *)NULL);
651 while (fgets(msg, sizeof(msg), con_fd)) {
653 ua->UA_sock->msg = check_pool_memory_size(ua->UA_sock->msg, mlen+1);
654 strcpy(ua->UA_sock->msg, msg);
655 ua->UA_sock->msglen = mlen;
656 bnet_send(ua->UA_sock);
660 (void)ftruncate(fileno(con_fd), 0L);
662 console_msg_pending = FALSE;
663 ua->user_notified_msg_pending = FALSE;
664 pthread_cleanup_pop(0);
669 int qmessagescmd(UAContext *ua, const char *cmd)
671 if (console_msg_pending && ua->auto_display_messages) {
672 do_messages(ua, cmd);
677 int messagescmd(UAContext *ua, const char *cmd)
679 if (console_msg_pending) {
680 do_messages(ua, cmd);
682 bnet_fsend(ua->UA_sock, _("You have no messages.\n"));
688 * Callback routine for "printing" database file listing
690 void prtit(void *ctx, const char *msg)
692 UAContext *ua = (UAContext *)ctx;
694 bnet_fsend(ua->UA_sock, "%s", msg);
698 * Format message and send to other end.
700 * If the UA_sock is NULL, it means that there is no user
701 * agent, so we are being called from Bacula core. In
702 * that case direct the messages to the Job.
704 void bsendmsg(void *ctx, const char *fmt, ...)
707 UAContext *ua = (UAContext *)ctx;
708 BSOCK *bs = ua->UA_sock;
715 msg = get_pool_memory(PM_EMSG);
719 maxlen = sizeof_pool_memory(msg) - 1;
720 va_start(arg_ptr, fmt);
721 len = bvsnprintf(msg, maxlen, fmt, arg_ptr);
723 if (len < 0 || len >= maxlen) {
724 msg = realloc_pool_memory(msg, maxlen + maxlen/2);
732 } else { /* No UA, send to Job */
733 Jmsg(ua->jcr, M_INFO, 0, "%s", msg);
734 free_pool_memory(msg);