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 = 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], N_("jobs")) == 0) {
260 /* Apply any limit */
261 j = find_arg_with_value(ua, N_("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], N_("jobtotals")) == 0) {
269 db_list_job_totals(ua->jcr, ua->db, &jr, prtit, ua);
272 } else if (strcasecmp(ua->argk[i], N_("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], N_("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], N_("files")) == 0) {
290 for (j=i+1; j<ua->argc; j++) {
291 if (strcasecmp(ua->argk[j], N_("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], N_("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], N_("jobmedia")) == 0) {
309 for (j=i+1; j<ua->argc; j++) {
310 if (strcasecmp(ua->argk[j], N_("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], N_("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], N_("pool")) == 0 ||
330 strcasecmp(ua->argk[i], N_("pools")) == 0) {
332 memset(&pr, 0, sizeof(pr));
334 bstrncpy(pr.Name, ua->argv[i], sizeof(pr.Name));
336 db_list_pool_records(ua->jcr, ua->db, &pr, prtit, ua, llist);
338 } else if (strcasecmp(ua->argk[i], N_("clients")) == 0) {
339 db_list_client_records(ua->jcr, ua->db, prtit, ua, llist);
342 /* List MEDIA or VOLUMES */
343 } else if (strcasecmp(ua->argk[i], N_("media")) == 0 ||
344 strcasecmp(ua->argk[i], N_("volume")) == 0 ||
345 strcasecmp(ua->argk[i], N_("volumes")) == 0) {
347 for (j=i+1; j<ua->argc; j++) {
348 if (strcasecmp(ua->argk[j], N_("job")) == 0 && ua->argv[j]) {
349 bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
351 db_get_job_record(ua->jcr, ua->db, &jr);
353 } else if (strcasecmp(ua->argk[j], N_("jobid")) == 0 && ua->argv[j]) {
354 jobid = str_to_int64(ua->argv[j]);
358 VolumeName = get_pool_memory(PM_FNAME);
359 n = db_get_job_volume_names(ua->jcr, ua->db, jobid, &VolumeName);
360 bsendmsg(ua, _("Jobid %d used %d Volume(s): %s\n"), jobid, n, VolumeName);
361 free_pool_memory(VolumeName);
364 /* if no job or jobid keyword found, then we list all media */
368 /* List a specific volume? */
370 bstrncpy(mr.VolumeName, ua->argv[i], sizeof(mr.VolumeName));
371 db_list_media_records(ua->jcr, ua->db, &mr, prtit, ua, llist);
374 /* Is a specific pool wanted? */
375 for (i=1; i<ua->argc; i++) {
376 if (strcasecmp(ua->argk[i], N_("pool")) == 0) {
377 if (!get_pool_dbr(ua, &pr)) {
378 bsendmsg(ua, _("No Pool specified.\n"));
381 mr.PoolId = pr.PoolId;
382 db_list_media_records(ua->jcr, ua->db, &mr, prtit, ua, llist);
387 /* List Volumes in all pools */
388 if (!db_get_pool_ids(ua->jcr, ua->db, &num_pools, &ids)) {
389 bsendmsg(ua, _("Error obtaining pool ids. ERR=%s\n"),
390 db_strerror(ua->db));
393 if (num_pools <= 0) {
396 for (i=0; i < num_pools; i++) {
398 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
399 bsendmsg(ua, _("Pool: %s\n"), pr.Name);
402 db_list_media_records(ua->jcr, ua->db, &mr, prtit, ua, llist);
407 /* List next volume */
408 } else if (strcasecmp(ua->argk[i], N_("nextvol")) == 0 ||
409 strcasecmp(ua->argk[i], N_("nextvolume")) == 0) {
411 j = find_arg_with_value(ua, N_("days"));
413 n = atoi(ua->argv[j]);
414 if ((n < 0) || (n > 50)) {
415 bsendmsg(ua, _("Ignoring illegal value for days.\n"));
420 } else if (strcasecmp(ua->argk[i], N_("limit")) == 0
421 || strcasecmp(ua->argk[i], N_("days")) == 0) {
424 bsendmsg(ua, _("Unknown list keyword: %s\n"), NPRT(ua->argk[i]));
430 static bool list_nextvol(UAContext *ua, int ndays)
441 memset(&mr, 0, sizeof(mr));
442 int i = find_arg_with_value(ua, "job");
444 if ((job = select_job_resource(ua)) == NULL) {
448 job = (JOB *)GetResWithName(R_JOB, ua->argv[i]);
450 Jmsg(jcr, M_ERROR, 0, _("%s is not a job name.\n"), ua->argv[i]);
451 if ((job = select_job_resource(ua)) == NULL) {
456 for (run=NULL; (run = find_next_run(run, job, runtime, ndays)); ) {
457 pool = run->pool ? run->pool : NULL;
458 if (!complete_jcr_for_job(jcr, job, pool)) {
461 mr.PoolId = jcr->PoolId;
463 jcr->store = run->storage;
465 memset(&pr, 0, sizeof(pr));
466 pr.PoolId = jcr->PoolId;
467 if (! db_get_pool_record(ua->jcr, ua->db, &pr)) {
468 strcpy(pr.Name, "*UnknownPool*");
470 if (!find_next_volume_for_append(jcr, &mr, 0)) {
471 bsendmsg(ua, _("Could not find next Volume for Job %s (%s, %s).\n"),
472 job->hdr.name, pr.Name, level_to_str(run->level));
475 _("The next Volume to be used by Job \"%s\" (%s, %s) will be %s\n"),
476 job->hdr.name, pr.Name, level_to_str(run->level), mr.VolumeName);
479 if (jcr->db && jcr->db != ua->db) {
480 db_close_database(jcr, jcr->db);
485 bsendmsg(ua, _("Could not find next Volume for Job %s.\n"),
494 * For a given job, we examine all his run records
495 * to see if it is scheduled today or tomorrow.
497 RUN *find_next_run(RUN *run, JOB *job, time_t &runtime, int ndays)
499 time_t now, future, endtime;
502 int mday, wday, month, wom, i;
507 sched = job->schedule;
508 if (sched == NULL) { /* scheduled? */
509 return NULL; /* no nothing to report */
512 /* Break down the time into components */
514 endtime = now + (ndays * 60 * 60 * 24);
521 for ( ; run; run=run->next) {
523 * Find runs in next 24 hours
525 for (day = 0; day <= ndays; day++) {
526 future = now + (day * 60 * 60 * 24);
528 /* Break down the time into components */
529 localtime_r(&future, &tm);
530 mday = tm.tm_mday - 1;
534 woy = tm_woy(future);
536 is_scheduled = bit_is_set(mday, run->mday) && bit_is_set(wday, run->wday) &&
537 bit_is_set(month, run->month) && bit_is_set(wom, run->wom) &&
538 bit_is_set(woy, run->woy);
541 Dmsg2(000, "day=%d is_scheduled=%d\n", day, is_scheduled);
542 Dmsg1(000, "bit_set_mday=%d\n", bit_is_set(mday, run->mday));
543 Dmsg1(000, "bit_set_wday=%d\n", bit_is_set(wday, run->wday));
544 Dmsg1(000, "bit_set_month=%d\n", bit_is_set(month, run->month));
545 Dmsg1(000, "bit_set_wom=%d\n", bit_is_set(wom, run->wom));
546 Dmsg1(000, "bit_set_woy=%d\n", bit_is_set(woy, run->woy));
549 if (is_scheduled) { /* Jobs scheduled on that day */
551 char buf[300], num[10];
552 bsnprintf(buf, sizeof(buf), "tm.hour=%d hour=", tm.tm_hour);
553 for (i=0; i<24; i++) {
554 if (bit_is_set(i, run->hour)) {
555 bsnprintf(num, sizeof(num), "%d ", i);
556 bstrncat(buf, num, sizeof(buf));
559 bstrncat(buf, "\n", sizeof(buf));
560 Dmsg1(000, "%s", buf);
562 /* find time (time_t) job is to be run */
563 localtime_r(&future, &runtm);
564 for (i= 0; i < 24; i++) {
565 if (bit_is_set(i, run->hour)) {
567 runtm.tm_min = run->minute;
569 runtime = mktime(&runtm);
570 Dmsg2(200, "now=%d runtime=%d\n", now, runtime);
571 if ((runtime > now) && (runtime < endtime)) {
572 Dmsg2(200, "Found it level=%d %c\n", run->level, run->level);
573 return run; /* found it, return run resource */
579 } /* end for loop over runs */
584 * Fill in the remaining fields of the jcr as if it
585 * is going to run the job.
587 int complete_jcr_for_job(JCR *jcr, JOB *job, POOL *pool)
591 memset(&pr, 0, sizeof(POOL_DBR));
592 set_jcr_defaults(jcr, job);
594 jcr->pool = pool; /* override */
596 jcr->db = jcr->db=db_init_database(jcr, jcr->catalog->db_name, jcr->catalog->db_user,
597 jcr->catalog->db_password, jcr->catalog->db_address,
598 jcr->catalog->db_port, jcr->catalog->db_socket,
599 jcr->catalog->mult_db_connections);
600 if (!jcr->db || !db_open_database(jcr, jcr->db)) {
601 Jmsg(jcr, M_FATAL, 0, _("Could not open database \"%s\".\n"),
602 jcr->catalog->db_name);
604 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
608 bstrncpy(pr.Name, jcr->pool->hdr.name, sizeof(pr.Name));
609 while (!db_get_pool_record(jcr, jcr->db, &pr)) { /* get by Name */
610 /* Try to create the pool */
611 if (create_pool(jcr, jcr->db, jcr->pool, POOL_OP_CREATE) < 0) {
612 Jmsg(jcr, M_FATAL, 0, _("Pool %s not in database. %s"), pr.Name,
613 db_strerror(jcr->db));
615 db_close_database(jcr, jcr->db);
620 Jmsg(jcr, M_INFO, 0, _("Pool %s created in database.\n"), pr.Name);
623 jcr->PoolId = pr.PoolId;
624 jcr->jr.PoolId = pr.PoolId;
629 static void con_lock_release(void *arg)
634 void do_messages(UAContext *ua, const char *cmd)
638 int do_truncate = FALSE;
641 pthread_cleanup_push(con_lock_release, (void *)NULL);
643 while (fgets(msg, sizeof(msg), con_fd)) {
645 ua->UA_sock->msg = check_pool_memory_size(ua->UA_sock->msg, mlen+1);
646 strcpy(ua->UA_sock->msg, msg);
647 ua->UA_sock->msglen = mlen;
648 bnet_send(ua->UA_sock);
652 ftruncate(fileno(con_fd), 0L);
654 console_msg_pending = FALSE;
655 ua->user_notified_msg_pending = FALSE;
656 pthread_cleanup_pop(0);
661 int qmessagescmd(UAContext *ua, const char *cmd)
663 if (console_msg_pending && ua->auto_display_messages) {
664 do_messages(ua, cmd);
669 int messagescmd(UAContext *ua, const char *cmd)
671 if (console_msg_pending) {
672 do_messages(ua, cmd);
674 bnet_fsend(ua->UA_sock, _("You have no messages.\n"));
680 * Callback routine for "printing" database file listing
682 void prtit(void *ctx, const char *msg)
684 UAContext *ua = (UAContext *)ctx;
686 bnet_fsend(ua->UA_sock, "%s", msg);
690 * Format message and send to other end.
692 * If the UA_sock is NULL, it means that there is no user
693 * agent, so we are being called from Bacula core. In
694 * that case direct the messages to the Job.
696 void bsendmsg(void *ctx, const char *fmt, ...)
699 UAContext *ua = (UAContext *)ctx;
700 BSOCK *bs = ua->UA_sock;
707 msg = get_pool_memory(PM_EMSG);
711 maxlen = sizeof_pool_memory(msg) - 1;
712 va_start(arg_ptr, fmt);
713 len = bvsnprintf(msg, maxlen, fmt, arg_ptr);
715 if (len < 0 || len >= maxlen) {
716 msg = realloc_pool_memory(msg, maxlen + maxlen/2);
724 } else { /* No UA, send to Job */
725 Jmsg(ua->jcr, M_INFO, 0, "%s", msg);
726 free_pool_memory(msg);