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 strcasecmp(ua->argk[i], N_("volume")) == 0 ||
344 strcasecmp(ua->argk[i], N_("volumes")) == 0) {
346 for (j=i+1; j<ua->argc; j++) {
347 if (strcasecmp(ua->argk[j], N_("job")) == 0 && ua->argv[j]) {
348 bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
350 db_get_job_record(ua->jcr, ua->db, &jr);
352 } else if (strcasecmp(ua->argk[j], N_("jobid")) == 0 && ua->argv[j]) {
353 jobid = str_to_int64(ua->argv[j]);
357 VolumeName = get_pool_memory(PM_FNAME);
358 n = db_get_job_volume_names(ua->jcr, ua->db, jobid, &VolumeName);
359 bsendmsg(ua, _("Jobid %d used %d Volume(s): %s\n"), jobid, n, VolumeName);
360 free_pool_memory(VolumeName);
363 /* if no job or jobid keyword found, then we list all media */
367 /* List a specific volume? */
369 bstrncpy(mr.VolumeName, ua->argv[i], sizeof(mr.VolumeName));
370 db_list_media_records(ua->jcr, ua->db, &mr, prtit, ua, llist);
373 /* Is a specific pool wanted? */
374 for (i=1; i<ua->argc; i++) {
375 if (strcasecmp(ua->argk[i], N_("pool")) == 0) {
376 if (!get_pool_dbr(ua, &pr)) {
377 bsendmsg(ua, _("No Pool specified.\n"));
380 mr.PoolId = pr.PoolId;
381 db_list_media_records(ua->jcr, ua->db, &mr, prtit, ua, llist);
386 /* List Volumes in all pools */
387 if (!db_get_pool_ids(ua->jcr, ua->db, &num_pools, &ids)) {
388 bsendmsg(ua, _("Error obtaining pool ids. ERR=%s\n"),
389 db_strerror(ua->db));
392 if (num_pools <= 0) {
395 for (i=0; i < num_pools; i++) {
397 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
398 bsendmsg(ua, _("Pool: %s\n"), pr.Name);
401 db_list_media_records(ua->jcr, ua->db, &mr, prtit, ua, llist);
406 /* List next volume */
407 } else if (strcasecmp(ua->argk[i], N_("nextvol")) == 0 ||
408 strcasecmp(ua->argk[i], N_("nextvolume")) == 0) {
410 } else if (strcasecmp(ua->argk[i], N_("limit")) == 0) {
413 bsendmsg(ua, _("Unknown list keyword: %s\n"), NPRT(ua->argk[i]));
419 static bool list_nextvol(UAContext *ua)
429 memset(&mr, 0, sizeof(mr));
430 int i = find_arg_with_value(ua, "job");
432 if ((job = select_job_resource(ua)) == NULL) {
436 job = (JOB *)GetResWithName(R_JOB, ua->argv[i]);
438 Jmsg(jcr, M_ERROR, 0, _("%s is not a job name.\n"), ua->argv[i]);
439 if ((job = select_job_resource(ua)) == NULL) {
444 for (run=NULL; (run = find_next_run(run, job, runtime)); ) {
445 pool = run->pool ? run->pool : NULL;
446 if (!complete_jcr_for_job(jcr, job, pool)) {
449 mr.PoolId = jcr->PoolId;
451 jcr->store = run->storage;
453 if (!find_next_volume_for_append(jcr, &mr, 0)) {
454 bsendmsg(ua, _("Could not find next Volume.\n"));
456 bsendmsg(ua, _("The next Volume to be used by Job \"%s\" will be %s\n"),
457 job->hdr.name, mr.VolumeName);
460 if (jcr->db && jcr->db != ua->db) {
461 db_close_database(jcr, jcr->db);
466 bsendmsg(ua, _("Could not find next Volume.\n"));
474 * For a given job, we examine all his run records
475 * to see if it is scheduled today or tomorrow.
477 RUN *find_next_run(RUN *run, JOB *job, time_t &runtime)
479 time_t now, tomorrow;
482 int mday, wday, month, wom, tmday, twday, tmonth, twom, i;
486 sched = job->schedule;
487 if (sched == NULL) { /* scheduled? */
488 return NULL; /* no nothing to report */
490 /* Break down current time into components */
492 localtime_r(&now, &tm);
493 mday = tm.tm_mday - 1;
499 /* Break down tomorrow into components */
500 tomorrow = now + 60 * 60 * 24;
501 localtime_r(&tomorrow, &tm);
502 tmday = tm.tm_mday - 1;
506 twoy = tm_woy(tomorrow);
513 for ( ; run; run=run->next) {
515 * Find runs in next 24 hours
517 tod = bit_is_set(mday, run->mday) && bit_is_set(wday, run->wday) &&
518 bit_is_set(month, run->month) && bit_is_set(wom, run->wom) &&
519 bit_is_set(woy, run->woy);
521 tom = bit_is_set(tmday, run->mday) && bit_is_set(twday, run->wday) &&
522 bit_is_set(tmonth, run->month) && bit_is_set(twom, run->wom) &&
523 bit_is_set(twoy, run->woy);
526 Dmsg2(000, "tod=%d tom=%d\n", tod, tom);
527 Dmsg1(000, "bit_set_mday=%d\n", bit_is_set(mday, run->mday));
528 Dmsg1(000, "bit_set_wday=%d\n", bit_is_set(wday, run->wday));
529 Dmsg1(000, "bit_set_month=%d\n", bit_is_set(month, run->month));
530 Dmsg1(000, "bit_set_wom=%d\n", bit_is_set(wom, run->wom));
531 Dmsg1(000, "bit_set_woy=%d\n", bit_is_set(woy, run->woy));
533 if (tod) { /* Jobs scheduled today (next 24 hours) */
535 char buf[300], num[10];
536 bsnprintf(buf, sizeof(buf), "tm.hour=%d hour=", tm.tm_hour);
537 for (i=0; i<24; i++) {
538 if (bit_is_set(i, run->hour)) {
539 bsnprintf(num, sizeof(num), "%d ", i);
540 bstrncat(buf, num, sizeof(buf));
543 bstrncat(buf, "\n", sizeof(buf));
544 Dmsg1(000, "%s", buf);
546 /* find time (time_t) job is to be run */
547 localtime_r(&now, &tm);
548 for (i=tm.tm_hour; i < 24; i++) {
549 if (bit_is_set(i, run->hour)) {
551 tm.tm_min = run->minute;
553 runtime = mktime(&tm);
554 Dmsg2(200, "now=%d runtime=%d\n", now, runtime);
556 Dmsg2(200, "Found it level=%d %c\n", run->level, run->level);
557 return run; /* found it, return run resource */
563 // Dmsg2(200, "runtime=%d now=%d\n", runtime, now);
564 if (tom) { /* look at jobs scheduled tomorrow */
565 localtime_r(&tomorrow, &tm);
566 for (i=0; i < 24; i++) {
567 if (bit_is_set(i, run->hour)) {
569 tm.tm_min = run->minute;
571 runtime = mktime(&tm);
572 Dmsg2(200, "now=%d runtime=%d\n", now, runtime);
573 if (runtime < tomorrow) {
574 Dmsg2(200, "Found it level=%d %c\n", run->level, run->level);
575 return run; /* found it, return run resource */
580 } /* end for loop over runs */
585 * Fill in the remaining fields of the jcr as if it
586 * is going to run the job.
588 int complete_jcr_for_job(JCR *jcr, JOB *job, POOL *pool)
592 memset(&pr, 0, sizeof(POOL_DBR));
593 set_jcr_defaults(jcr, job);
595 jcr->pool = pool; /* override */
597 jcr->db = jcr->db=db_init_database(jcr, jcr->catalog->db_name, jcr->catalog->db_user,
598 jcr->catalog->db_password, jcr->catalog->db_address,
599 jcr->catalog->db_port, jcr->catalog->db_socket,
600 jcr->catalog->mult_db_connections);
601 if (!jcr->db || !db_open_database(jcr, jcr->db)) {
602 Jmsg(jcr, M_FATAL, 0, _("Could not open database \"%s\".\n"),
603 jcr->catalog->db_name);
605 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
609 bstrncpy(pr.Name, jcr->pool->hdr.name, sizeof(pr.Name));
610 while (!db_get_pool_record(jcr, jcr->db, &pr)) { /* get by Name */
611 /* Try to create the pool */
612 if (create_pool(jcr, jcr->db, jcr->pool, POOL_OP_CREATE) < 0) {
613 Jmsg(jcr, M_FATAL, 0, _("Pool %s not in database. %s"), pr.Name,
614 db_strerror(jcr->db));
616 db_close_database(jcr, jcr->db);
621 Jmsg(jcr, M_INFO, 0, _("Pool %s created in database.\n"), pr.Name);
624 jcr->PoolId = pr.PoolId;
625 jcr->jr.PoolId = pr.PoolId;
630 static void con_lock_release(void *arg)
635 void do_messages(UAContext *ua, const char *cmd)
639 int do_truncate = FALSE;
642 pthread_cleanup_push(con_lock_release, (void *)NULL);
644 while (fgets(msg, sizeof(msg), con_fd)) {
646 ua->UA_sock->msg = check_pool_memory_size(ua->UA_sock->msg, mlen+1);
647 strcpy(ua->UA_sock->msg, msg);
648 ua->UA_sock->msglen = mlen;
649 bnet_send(ua->UA_sock);
653 ftruncate(fileno(con_fd), 0L);
655 console_msg_pending = FALSE;
656 ua->user_notified_msg_pending = FALSE;
657 pthread_cleanup_pop(0);
662 int qmessagescmd(UAContext *ua, const char *cmd)
664 if (console_msg_pending && ua->auto_display_messages) {
665 do_messages(ua, cmd);
670 int messagescmd(UAContext *ua, const char *cmd)
672 if (console_msg_pending) {
673 do_messages(ua, cmd);
675 bnet_fsend(ua->UA_sock, _("You have no messages.\n"));
681 * Callback routine for "printing" database file listing
683 void prtit(void *ctx, const char *msg)
685 UAContext *ua = (UAContext *)ctx;
687 bnet_fsend(ua->UA_sock, "%s", msg);
691 * Format message and send to other end.
693 * If the UA_sock is NULL, it means that there is no user
694 * agent, so we are being called from Bacula core. In
695 * that case direct the messages to the Job.
697 void bsendmsg(void *ctx, const char *fmt, ...)
700 UAContext *ua = (UAContext *)ctx;
701 BSOCK *bs = ua->UA_sock;
708 msg = get_pool_memory(PM_EMSG);
712 maxlen = sizeof_pool_memory(msg) - 1;
713 va_start(arg_ptr, fmt);
714 len = bvsnprintf(msg, maxlen, fmt, arg_ptr);
716 if (len < 0 || len >= maxlen) {
717 msg = realloc_pool_memory(msg, maxlen + maxlen/2);
725 } else { /* No UA, send to Job */
726 Jmsg(ua->jcr, M_INFO, 0, "%s", msg);
727 free_pool_memory(msg);