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 */
592 * Fill in the remaining fields of the jcr as if it
593 * is going to run the job.
595 int complete_jcr_for_job(JCR *jcr, JOB *job, POOL *pool)
599 memset(&pr, 0, sizeof(POOL_DBR));
600 set_jcr_defaults(jcr, job);
602 jcr->pool = pool; /* override */
604 jcr->db = jcr->db=db_init_database(jcr, jcr->catalog->db_name, jcr->catalog->db_user,
605 jcr->catalog->db_password, jcr->catalog->db_address,
606 jcr->catalog->db_port, jcr->catalog->db_socket,
607 jcr->catalog->mult_db_connections);
608 if (!jcr->db || !db_open_database(jcr, jcr->db)) {
609 Jmsg(jcr, M_FATAL, 0, _("Could not open database \"%s\".\n"),
610 jcr->catalog->db_name);
612 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
616 bstrncpy(pr.Name, jcr->pool->hdr.name, sizeof(pr.Name));
617 while (!db_get_pool_record(jcr, jcr->db, &pr)) { /* get by Name */
618 /* Try to create the pool */
619 if (create_pool(jcr, jcr->db, jcr->pool, POOL_OP_CREATE) < 0) {
620 Jmsg(jcr, M_FATAL, 0, _("Pool %s not in database. %s"), pr.Name,
621 db_strerror(jcr->db));
623 db_close_database(jcr, jcr->db);
628 Jmsg(jcr, M_INFO, 0, _("Pool %s created in database.\n"), pr.Name);
631 jcr->jr.PoolId = pr.PoolId;
636 static void con_lock_release(void *arg)
641 void do_messages(UAContext *ua, const char *cmd)
645 bool do_truncate = false;
648 pthread_cleanup_push(con_lock_release, (void *)NULL);
650 while (fgets(msg, sizeof(msg), con_fd)) {
652 ua->UA_sock->msg = check_pool_memory_size(ua->UA_sock->msg, mlen+1);
653 strcpy(ua->UA_sock->msg, msg);
654 ua->UA_sock->msglen = mlen;
655 bnet_send(ua->UA_sock);
659 (void)ftruncate(fileno(con_fd), 0L);
661 console_msg_pending = FALSE;
662 ua->user_notified_msg_pending = FALSE;
663 pthread_cleanup_pop(0);
668 int qmessagescmd(UAContext *ua, const char *cmd)
670 if (console_msg_pending && ua->auto_display_messages) {
671 do_messages(ua, cmd);
676 int messagescmd(UAContext *ua, const char *cmd)
678 if (console_msg_pending) {
679 do_messages(ua, cmd);
681 bnet_fsend(ua->UA_sock, _("You have no messages.\n"));
687 * Callback routine for "printing" database file listing
689 void prtit(void *ctx, const char *msg)
691 UAContext *ua = (UAContext *)ctx;
693 bnet_fsend(ua->UA_sock, "%s", msg);
697 * Format message and send to other end.
699 * If the UA_sock is NULL, it means that there is no user
700 * agent, so we are being called from Bacula core. In
701 * that case direct the messages to the Job.
703 void bsendmsg(void *ctx, const char *fmt, ...)
706 UAContext *ua = (UAContext *)ctx;
707 BSOCK *bs = ua->UA_sock;
714 msg = get_pool_memory(PM_EMSG);
718 maxlen = sizeof_pool_memory(msg) - 1;
719 va_start(arg_ptr, fmt);
720 len = bvsnprintf(msg, maxlen, fmt, arg_ptr);
722 if (len < 0 || len >= maxlen) {
723 msg = realloc_pool_memory(msg, maxlen + maxlen/2);
731 } else { /* No UA, send to Job */
732 Jmsg(ua->jcr, M_INFO, 0, "%s", msg);
733 free_pool_memory(msg);