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], 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) {
331 memset(&pr, 0, sizeof(pr));
333 bstrncpy(pr.Name, ua->argv[i], sizeof(pr.Name));
335 db_list_pool_records(ua->jcr, ua->db, &pr, prtit, ua, llist);
337 } else if (strcasecmp(ua->argk[i], N_("clients")) == 0) {
338 db_list_client_records(ua->jcr, ua->db, prtit, ua, llist);
341 /* List MEDIA or VOLUMES */
342 } else if (strcasecmp(ua->argk[i], N_("media")) == 0 ||
343 strncasecmp(ua->argk[i], N_("volume"), 7) == 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 } else if (strcasecmp(ua->argk[i], N_("limit")) == 0) {
412 bsendmsg(ua, _("Unknown list keyword: %s\n"), NPRT(ua->argk[i]));
418 static bool list_nextvol(UAContext *ua)
428 memset(&mr, 0, sizeof(mr));
429 int i = find_arg_with_value(ua, "job");
431 if ((job = select_job_resource(ua)) == NULL) {
435 job = (JOB *)GetResWithName(R_JOB, ua->argv[i]);
437 Jmsg(jcr, M_ERROR, 0, _("%s is not a job name.\n"), ua->argv[i]);
438 if ((job = select_job_resource(ua)) == NULL) {
443 for (run=NULL; (run = find_next_run(run, job, runtime)); ) {
444 pool = run->pool ? run->pool : NULL;
445 if (!complete_jcr_for_job(jcr, job, pool)) {
448 mr.PoolId = jcr->PoolId;
450 jcr->store = run->storage;
452 if (!find_next_volume_for_append(jcr, &mr, 0)) {
453 bsendmsg(ua, _("Could not find next Volume.\n"));
455 bsendmsg(ua, _("The next Volume to be used by Job \"%s\" will be %s\n"),
456 job->hdr.name, mr.VolumeName);
459 if (jcr->db && jcr->db != ua->db) {
460 db_close_database(jcr, jcr->db);
465 bsendmsg(ua, _("Could not find next Volume.\n"));
473 * For a given job, we examine all his run records
474 * to see if it is scheduled today or tomorrow.
476 RUN *find_next_run(RUN *run, JOB *job, time_t &runtime)
478 time_t now, tomorrow;
481 int mday, wday, month, wom, tmday, twday, tmonth, twom, i;
485 sched = job->schedule;
486 if (sched == NULL) { /* scheduled? */
487 return NULL; /* no nothing to report */
489 /* Break down current time into components */
491 localtime_r(&now, &tm);
492 mday = tm.tm_mday - 1;
498 /* Break down tomorrow into components */
499 tomorrow = now + 60 * 60 * 24;
500 localtime_r(&tomorrow, &tm);
501 tmday = tm.tm_mday - 1;
505 twoy = tm_woy(tomorrow);
512 for ( ; run; run=run->next) {
514 * Find runs in next 24 hours
516 tod = bit_is_set(mday, run->mday) && bit_is_set(wday, run->wday) &&
517 bit_is_set(month, run->month) && bit_is_set(wom, run->wom) &&
518 bit_is_set(woy, run->woy);
520 tom = bit_is_set(tmday, run->mday) && bit_is_set(twday, run->wday) &&
521 bit_is_set(tmonth, run->month) && bit_is_set(twom, run->wom) &&
522 bit_is_set(twoy, run->woy);
525 Dmsg2(000, "tod=%d tom=%d\n", tod, tom);
526 Dmsg1(000, "bit_set_mday=%d\n", bit_is_set(mday, run->mday));
527 Dmsg1(000, "bit_set_wday=%d\n", bit_is_set(wday, run->wday));
528 Dmsg1(000, "bit_set_month=%d\n", bit_is_set(month, run->month));
529 Dmsg1(000, "bit_set_wom=%d\n", bit_is_set(wom, run->wom));
530 Dmsg1(000, "bit_set_woy=%d\n", bit_is_set(woy, run->woy));
532 if (tod) { /* Jobs scheduled today (next 24 hours) */
534 char buf[300], num[10];
535 bsnprintf(buf, sizeof(buf), "tm.hour=%d hour=", tm.tm_hour);
536 for (i=0; i<24; i++) {
537 if (bit_is_set(i, run->hour)) {
538 bsnprintf(num, sizeof(num), "%d ", i);
539 bstrncat(buf, num, sizeof(buf));
542 bstrncat(buf, "\n", sizeof(buf));
543 Dmsg1(000, "%s", buf);
545 /* find time (time_t) job is to be run */
546 localtime_r(&now, &tm);
547 for (i=tm.tm_hour; i < 24; i++) {
548 if (bit_is_set(i, run->hour)) {
550 tm.tm_min = run->minute;
552 runtime = mktime(&tm);
553 Dmsg2(200, "now=%d runtime=%d\n", now, runtime);
555 Dmsg2(200, "Found it level=%d %c\n", run->level, run->level);
556 return run; /* found it, return run resource */
562 // Dmsg2(200, "runtime=%d now=%d\n", runtime, now);
563 if (tom) { /* look at jobs scheduled tomorrow */
564 localtime_r(&tomorrow, &tm);
565 for (i=0; i < 24; i++) {
566 if (bit_is_set(i, run->hour)) {
568 tm.tm_min = run->minute;
570 runtime = mktime(&tm);
571 Dmsg2(200, "now=%d runtime=%d\n", now, runtime);
572 if (runtime < tomorrow) {
573 Dmsg2(200, "Found it level=%d %c\n", run->level, run->level);
574 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);