2 Bacula® - The Network Backup Solution
4 Copyright (C) 2000-2009 Free Software Foundation Europe e.V.
6 The main author of Bacula is Kern Sibbald, with contributions from
7 many others, a complete list can be found in the file AUTHORS.
8 This program is Free Software; you can redistribute it and/or
9 modify it under the terms of version two of the GNU General Public
10 License as published by the Free Software Foundation and included
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
23 Bacula® is a registered trademark of Kern Sibbald.
24 The licensor of Bacula is the Free Software Foundation Europe
25 (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26 Switzerland, email:ftf@fsfeurope.org.
30 * Bacula Director -- User Agent Output Commands
31 * I.e. messages, listing database, showing resources, ...
33 * Kern Sibbald, September MM
41 /* Imported subroutines */
43 /* Imported variables */
45 /* Imported functions */
47 /* Forward referenced functions */
48 static int do_list_cmd(UAContext *ua, const char *cmd, e_list_type llist);
49 static bool list_nextvol(UAContext *ua, int ndays);
52 * Turn auto display of console messages on/off
54 int autodisplay_cmd(UAContext *ua, const char *cmd)
56 static const char *kw[] = {
61 switch (find_arg_keyword(ua, kw)) {
63 ua->auto_display_messages = true;
66 ua->auto_display_messages = false;
69 ua->error_msg(_("ON or OFF keyword missing.\n"));
76 * Turn GUI mode on/off
78 int gui_cmd(UAContext *ua, const char *cmd)
80 static const char *kw[] = {
85 switch (find_arg_keyword(ua, kw)) {
87 ua->jcr->gui = ua->gui = true;
90 ua->jcr->gui = ua->gui = false;
93 ua->error_msg(_("ON or OFF keyword missing.\n"));
101 struct showstruct {const char *res_name; int type;};
102 static struct showstruct reses[] = {
103 {NT_("directors"), R_DIRECTOR},
104 {NT_("clients"), R_CLIENT},
105 {NT_("counters"), R_COUNTER},
106 {NT_("devices"), R_DEVICE},
107 {NT_("jobs"), R_JOB},
108 {NT_("storages"), R_STORAGE},
109 {NT_("catalogs"), R_CATALOG},
110 {NT_("schedules"), R_SCHEDULE},
111 {NT_("filesets"), R_FILESET},
112 {NT_("pools"), R_POOL},
113 {NT_("messages"), R_MSGS},
124 * show <resource-keyword-name> e.g. show directors
125 * show <resource-keyword-name>=<name> e.g. show director=HeadMan
128 int show_cmd(UAContext *ua, const char *cmd)
135 Dmsg1(20, "show: %s\n", ua->UA_sock->msg);
139 for (i=1; i<ua->argc; i++) {
141 res_name = ua->argk[i];
142 if (!ua->argv[i]) { /* was a name given? */
143 /* No name, dump all resources of specified type */
145 len = strlen(res_name);
146 for (j=0; reses[j].res_name; j++) {
147 if (strncasecmp(res_name, _(reses[j].res_name), len) == 0) {
148 type = reses[j].type;
150 res = res_head[type-r_first];
159 /* Dump a single resource with specified name */
161 len = strlen(res_name);
162 for (j=0; reses[j].res_name; j++) {
163 if (strncasecmp(res_name, _(reses[j].res_name), len) == 0) {
164 type = reses[j].type;
165 res = (RES *)GetResWithName(type, ua->argv[i]);
176 for (j=r_first; j<=r_last; j++) {
177 dump_resource(j, res_head[j-r_first], bsendmsg, ua);
181 ua->send_msg(_("Keywords for the show command are:\n"));
182 for (j=0; reses[j].res_name; j++) {
183 ua->error_msg("%s\n", _(reses[j].res_name));
187 ua->error_msg(_("%s resource %s not found.\n"), res_name, ua->argv[i]);
190 ua->error_msg(_("Resource %s not found\n"), res_name);
193 dump_resource(recurse?type:-type, res, bsendmsg, ua);
206 * List contents of database
208 * list jobs - lists all jobs run
209 * list jobid=nnn - list job data for jobid
210 * list ujobid=uname - list job data for unique jobid
211 * list job=name - list all jobs with "name"
212 * list jobname=name - same as above
213 * list jobmedia jobid=<nn>
214 * list jobmedia job=name
215 * list joblog jobid=<nn>
216 * list joblog job=name
217 * list files jobid=<nn> - list files saved for job nn
218 * list files job=name
219 * list pools - list pool records
220 * list jobtotals - list totals for all jobs
221 * list media - list media for given pool (deprecated)
222 * list volumes - list Volumes
223 * list clients - list clients
224 * list nextvol job=xx - list the next vol to be used by job
225 * list nextvolume job=xx - same as above.
226 * list copies jobid=x,y,z
230 /* Do long or full listing */
231 int llist_cmd(UAContext *ua, const char *cmd)
233 return do_list_cmd(ua, cmd, VERT_LIST);
236 /* Do short or summary listing */
237 int list_cmd(UAContext *ua, const char *cmd)
239 return do_list_cmd(ua, cmd, HORZ_LIST);
242 static int do_list_cmd(UAContext *ua, const char *cmd, e_list_type llist)
251 if (!open_client_db(ua))
254 memset(&jr, 0, sizeof(jr));
255 memset(&pr, 0, sizeof(pr));
256 memset(&mr, 0, sizeof(mr));
258 Dmsg1(20, "list: %s\n", cmd);
261 ua->error_msg(_("Hey! DB is NULL\n"));
264 /* Scan arguments looking for things to do */
265 for (i=1; i<ua->argc; i++) {
267 if (strcasecmp(ua->argk[i], NT_("jobs")) == 0) {
268 /* Apply any limit */
269 j = find_arg_with_value(ua, NT_("limit"));
271 jr.limit = atoi(ua->argv[j]);
273 db_list_job_records(ua->jcr, ua->db, &jr, prtit, ua, llist);
276 } else if (strcasecmp(ua->argk[i], NT_("jobtotals")) == 0) {
277 db_list_job_totals(ua->jcr, ua->db, &jr, prtit, ua);
280 } else if (strcasecmp(ua->argk[i], NT_("jobid")) == 0) {
282 jobid = str_to_int64(ua->argv[i]);
285 db_list_job_records(ua->jcr, ua->db, &jr, prtit, ua, llist);
290 } else if ((strcasecmp(ua->argk[i], NT_("job")) == 0 ||
291 strcasecmp(ua->argk[i], NT_("jobname")) == 0) && ua->argv[i]) {
292 bstrncpy(jr.Name, ua->argv[i], MAX_NAME_LENGTH);
294 db_list_job_records(ua->jcr, ua->db, &jr, prtit, ua, llist);
296 /* List UJOBID=xxx */
297 } else if (strcasecmp(ua->argk[i], NT_("ujobid")) == 0 && ua->argv[i]) {
298 bstrncpy(jr.Job, ua->argv[i], MAX_NAME_LENGTH);
300 db_list_job_records(ua->jcr, ua->db, &jr, prtit, ua, llist);
303 } else if (strcasecmp(ua->argk[i], NT_("files")) == 0) {
305 for (j=i+1; j<ua->argc; j++) {
306 if (strcasecmp(ua->argk[j], NT_("ujobid")) == 0 && ua->argv[j]) {
307 bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
309 db_get_job_record(ua->jcr, ua->db, &jr);
311 } else if (strcasecmp(ua->argk[j], NT_("jobid")) == 0 && ua->argv[j]) {
312 jobid = str_to_int64(ua->argv[j]);
317 db_list_files_for_job(ua->jcr, ua->db, jobid, prtit, ua);
322 } else if (strcasecmp(ua->argk[i], NT_("jobmedia")) == 0) {
324 for (j=i+1; j<ua->argc; j++) {
325 if (strcasecmp(ua->argk[j], NT_("ujobid")) == 0 && ua->argv[j]) {
326 bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
328 db_get_job_record(ua->jcr, ua->db, &jr);
330 } else if (strcasecmp(ua->argk[j], NT_("jobid")) == 0 && ua->argv[j]) {
331 jobid = str_to_int64(ua->argv[j]);
335 db_list_jobmedia_records(ua->jcr, ua->db, jobid, prtit, ua, llist);
339 /* List for all jobs (jobid=0) */
340 db_list_jobmedia_records(ua->jcr, ua->db, 0, prtit, ua, llist);
344 } else if (strcasecmp(ua->argk[i], NT_("joblog")) == 0) {
346 for (j=i+1; j<ua->argc; j++) {
347 if (strcasecmp(ua->argk[j], NT_("ujobid")) == 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], NT_("jobid")) == 0 && ua->argv[j]) {
353 jobid = str_to_int64(ua->argv[j]);
357 db_list_joblog_records(ua->jcr, ua->db, jobid, prtit, ua, llist);
361 /* List for all jobs (jobid=0) */
362 db_list_joblog_records(ua->jcr, ua->db, 0, prtit, ua, llist);
367 } else if (strcasecmp(ua->argk[i], NT_("pool")) == 0 ||
368 strcasecmp(ua->argk[i], NT_("pools")) == 0) {
370 memset(&pr, 0, sizeof(pr));
372 bstrncpy(pr.Name, ua->argv[i], sizeof(pr.Name));
374 db_list_pool_records(ua->jcr, ua->db, &pr, prtit, ua, llist);
376 } else if (strcasecmp(ua->argk[i], NT_("clients")) == 0) {
377 db_list_client_records(ua->jcr, ua->db, prtit, ua, llist);
380 /* List MEDIA or VOLUMES */
381 } else if (strcasecmp(ua->argk[i], NT_("media")) == 0 ||
382 strcasecmp(ua->argk[i], NT_("volume")) == 0 ||
383 strcasecmp(ua->argk[i], NT_("volumes")) == 0) {
385 for (j=i+1; j<ua->argc; j++) {
386 if (strcasecmp(ua->argk[j], NT_("ujobid")) == 0 && ua->argv[j]) {
387 bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
389 db_get_job_record(ua->jcr, ua->db, &jr);
391 } else if (strcasecmp(ua->argk[j], NT_("jobid")) == 0 && ua->argv[j]) {
392 jobid = str_to_int64(ua->argv[j]);
396 VolumeName = get_pool_memory(PM_FNAME);
397 n = db_get_job_volume_names(ua->jcr, ua->db, jobid, &VolumeName);
398 ua->send_msg(_("Jobid %d used %d Volume(s): %s\n"), jobid, n, VolumeName);
399 free_pool_memory(VolumeName);
402 /* if no job or jobid keyword found, then we list all media */
406 /* List a specific volume? */
408 bstrncpy(mr.VolumeName, ua->argv[i], sizeof(mr.VolumeName));
409 db_list_media_records(ua->jcr, ua->db, &mr, prtit, ua, llist);
412 /* Is a specific pool wanted? */
413 for (i=1; i<ua->argc; i++) {
414 if (strcasecmp(ua->argk[i], NT_("pool")) == 0) {
415 if (!get_pool_dbr(ua, &pr)) {
416 ua->error_msg(_("No Pool specified.\n"));
419 mr.PoolId = pr.PoolId;
420 db_list_media_records(ua->jcr, ua->db, &mr, prtit, ua, llist);
425 /* List Volumes in all pools */
426 if (!db_get_pool_ids(ua->jcr, ua->db, &num_pools, &ids)) {
427 ua->error_msg(_("Error obtaining pool ids. ERR=%s\n"),
428 db_strerror(ua->db));
431 if (num_pools <= 0) {
434 for (i=0; i < num_pools; i++) {
436 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
437 ua->send_msg(_("Pool: %s\n"), pr.Name);
440 db_list_media_records(ua->jcr, ua->db, &mr, prtit, ua, llist);
445 /* List next volume */
446 } else if (strcasecmp(ua->argk[i], NT_("nextvol")) == 0 ||
447 strcasecmp(ua->argk[i], NT_("nextvolume")) == 0) {
449 j = find_arg_with_value(ua, NT_("days"));
451 n = atoi(ua->argv[j]);
452 if ((n < 0) || (n > 50)) {
453 ua->warning_msg(_("Ignoring invalid value for days. Max is 50.\n"));
458 } else if (strcasecmp(ua->argk[i], NT_("copies")) == 0) {
461 for (j=i+1; j<ua->argc; j++) {
462 if (strcasecmp(ua->argk[j], NT_("jobid")) == 0 && ua->argv[j]) {
463 if (is_a_number_list(ua->argv[j])) {
464 jobids = ua->argv[j];
466 } else if (strcasecmp(ua->argk[j], NT_("limit")) == 0 && ua->argv[j]) {
467 limit = atoi(ua->argv[j]);
470 db_list_copies_records(ua->jcr,ua->db,limit,jobids,prtit,ua,llist);
471 } else if (strcasecmp(ua->argk[i], NT_("limit")) == 0
472 || strcasecmp(ua->argk[i], NT_("days")) == 0) {
475 ua->error_msg(_("Unknown list keyword: %s\n"), NPRT(ua->argk[i]));
481 static bool list_nextvol(UAContext *ua, int ndays)
492 memset(&mr, 0, sizeof(mr));
493 int i = find_arg_with_value(ua, "job");
495 if ((job = select_job_resource(ua)) == NULL) {
499 job = (JOB *)GetResWithName(R_JOB, ua->argv[i]);
501 Jmsg(ua->jcr, M_ERROR, 0, _("%s is not a job name.\n"), ua->argv[i]);
502 if ((job = select_job_resource(ua)) == NULL) {
508 jcr = new_jcr(sizeof(JCR), dird_free_jcr);
509 for (run=NULL; (run = find_next_run(run, job, runtime, ndays)); ) {
510 if (!complete_jcr_for_job(jcr, job, run->pool)) {
514 if (!jcr->jr.PoolId) {
515 ua->error_msg(_("Could not find Pool for Job %s\n"), job->name());
518 memset(&pr, 0, sizeof(pr));
519 pr.PoolId = jcr->jr.PoolId;
520 if (!db_get_pool_record(jcr, jcr->db, &pr)) {
521 bstrncpy(pr.Name, "*UnknownPool*", sizeof(pr.Name));
523 mr.PoolId = jcr->jr.PoolId;
524 get_job_storage(&store, job, run);
525 mr.StorageId = store.store->StorageId;
526 /* no need to set ScratchPoolId, since we use fnv_no_create_vol */
527 if (!find_next_volume_for_append(jcr, &mr, 1, fnv_no_create_vol, fnv_prune)) {
528 ua->error_msg(_("Could not find next Volume for Job %s (Pool=%s, Level=%s).\n"),
529 job->name(), pr.Name, level_to_str(run->level));
532 _("The next Volume to be used by Job \"%s\" (Pool=%s, Level=%s) will be %s\n"),
533 job->name(), pr.Name, level_to_str(run->level), mr.VolumeName);
539 db_close_database(jcr, jcr->db);
543 ua->error_msg(_("Could not find next Volume for Job %s.\n"),
552 * For a given job, we examine all his run records
553 * to see if it is scheduled today or tomorrow.
555 RUN *find_next_run(RUN *run, JOB *job, utime_t &runtime, int ndays)
557 time_t now, future, endtime;
560 int mday, wday, month, wom, i;
565 sched = job->schedule;
566 if (sched == NULL) { /* scheduled? */
567 return NULL; /* no nothing to report */
570 /* Break down the time into components */
572 endtime = now + (ndays * 60 * 60 * 24);
579 for ( ; run; run=run->next) {
581 * Find runs in next 24 hours. Day 0 is today, so if
582 * ndays=1, look at today and tomorrow.
584 for (day = 0; day <= ndays; day++) {
585 future = now + (day * 60 * 60 * 24);
587 /* Break down the time into components */
588 (void)localtime_r(&future, &tm);
589 mday = tm.tm_mday - 1;
593 woy = tm_woy(future);
595 is_scheduled = bit_is_set(mday, run->mday) && bit_is_set(wday, run->wday) &&
596 bit_is_set(month, run->month) && bit_is_set(wom, run->wom) &&
597 bit_is_set(woy, run->woy);
600 Pmsg2(000, "day=%d is_scheduled=%d\n", day, is_scheduled);
601 Pmsg1(000, "bit_set_mday=%d\n", bit_is_set(mday, run->mday));
602 Pmsg1(000, "bit_set_wday=%d\n", bit_is_set(wday, run->wday));
603 Pmsg1(000, "bit_set_month=%d\n", bit_is_set(month, run->month));
604 Pmsg1(000, "bit_set_wom=%d\n", bit_is_set(wom, run->wom));
605 Pmsg1(000, "bit_set_woy=%d\n", bit_is_set(woy, run->woy));
608 if (is_scheduled) { /* Jobs scheduled on that day */
610 char buf[300], num[10];
611 bsnprintf(buf, sizeof(buf), "tm.hour=%d hour=", tm.tm_hour);
612 for (i=0; i<24; i++) {
613 if (bit_is_set(i, run->hour)) {
614 bsnprintf(num, sizeof(num), "%d ", i);
615 bstrncat(buf, num, sizeof(buf));
618 bstrncat(buf, "\n", sizeof(buf));
619 Pmsg1(000, "%s", buf);
621 /* find time (time_t) job is to be run */
622 (void)localtime_r(&future, &runtm);
623 for (i= 0; i < 24; i++) {
624 if (bit_is_set(i, run->hour)) {
626 runtm.tm_min = run->minute;
628 runtime = mktime(&runtm);
629 Dmsg2(200, "now=%d runtime=%lld\n", now, runtime);
630 if ((runtime > now) && (runtime < endtime)) {
631 Dmsg2(200, "Found it level=%d %c\n", run->level, run->level);
632 return run; /* found it, return run resource */
638 } /* end for loop over runs */
644 * Fill in the remaining fields of the jcr as if it
645 * is going to run the job.
647 bool complete_jcr_for_job(JCR *jcr, JOB *job, POOL *pool)
651 memset(&pr, 0, sizeof(POOL_DBR));
652 set_jcr_defaults(jcr, job);
654 jcr->pool = pool; /* override */
657 Dmsg0(100, "complete_jcr close db\n");
658 db_close_database(jcr, jcr->db);
662 Dmsg0(100, "complete_jcr open db\n");
663 jcr->db = jcr->db=db_init(jcr, jcr->catalog->db_driver, jcr->catalog->db_name,
664 jcr->catalog->db_user,
665 jcr->catalog->db_password, jcr->catalog->db_address,
666 jcr->catalog->db_port, jcr->catalog->db_socket,
667 jcr->catalog->mult_db_connections);
668 if (!jcr->db || !db_open_database(jcr, jcr->db)) {
669 Jmsg(jcr, M_FATAL, 0, _("Could not open database \"%s\".\n"),
670 jcr->catalog->db_name);
672 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
673 db_close_database(jcr, jcr->db);
678 bstrncpy(pr.Name, jcr->pool->name(), sizeof(pr.Name));
679 while (!db_get_pool_record(jcr, jcr->db, &pr)) { /* get by Name */
680 /* Try to create the pool */
681 if (create_pool(jcr, jcr->db, jcr->pool, POOL_OP_CREATE) < 0) {
682 Jmsg(jcr, M_FATAL, 0, _("Pool %s not in database. %s"), pr.Name,
683 db_strerror(jcr->db));
685 db_close_database(jcr, jcr->db);
690 Jmsg(jcr, M_INFO, 0, _("Pool %s created in database.\n"), pr.Name);
693 jcr->jr.PoolId = pr.PoolId;
698 static void con_lock_release(void *arg)
703 void do_messages(UAContext *ua, const char *cmd)
707 bool do_truncate = false;
710 pthread_cleanup_push(con_lock_release, (void *)NULL);
712 while (fgets(msg, sizeof(msg), con_fd)) {
714 ua->UA_sock->msg = check_pool_memory_size(ua->UA_sock->msg, mlen+1);
715 strcpy(ua->UA_sock->msg, msg);
716 ua->UA_sock->msglen = mlen;
721 (void)ftruncate(fileno(con_fd), 0L);
723 console_msg_pending = FALSE;
724 ua->user_notified_msg_pending = FALSE;
725 pthread_cleanup_pop(0);
730 int qmessagescmd(UAContext *ua, const char *cmd)
732 if (console_msg_pending && ua->auto_display_messages) {
733 do_messages(ua, cmd);
738 int messagescmd(UAContext *ua, const char *cmd)
740 if (console_msg_pending) {
741 do_messages(ua, cmd);
743 ua->UA_sock->fsend(_("You have no messages.\n"));
749 * Callback routine for "printing" database file listing
751 void prtit(void *ctx, const char *msg)
753 UAContext *ua = (UAContext *)ctx;
755 ua->UA_sock->fsend("%s", msg);
759 * Format message and send to other end.
761 * If the UA_sock is NULL, it means that there is no user
762 * agent, so we are being called from Bacula core. In
763 * that case direct the messages to the Job.
766 void bmsg(UAContext *ua, const char *fmt, va_list arg_ptr)
768 BSOCK *bs = ua->UA_sock;
777 msg = get_pool_memory(PM_EMSG);
781 maxlen = sizeof_pool_memory(msg) - 1;
782 va_copy(ap, arg_ptr);
783 len = bvsnprintf(msg, maxlen, fmt, ap);
785 if (len < 0 || len >= maxlen) {
786 msg = realloc_pool_memory(msg, maxlen + maxlen/2);
794 } else { /* No UA, send to Job */
795 Jmsg(ua->jcr, M_INFO, 0, "%s", msg);
796 free_pool_memory(msg);
801 #else /* no va_copy() -- brain damaged version of variable arguments */
803 void bmsg(UAContext *ua, const char *fmt, va_list arg_ptr)
805 BSOCK *bs = ua->UA_sock;
813 msg = get_memory(5000);
816 maxlen = sizeof_pool_memory(msg) - 1;
818 msg = realloc_pool_memory(msg, 5000);
821 len = bvsnprintf(msg, maxlen, fmt, arg_ptr);
822 if (len < 0 || len >= maxlen) {
823 pm_strcpy(msg, _("Message too long to display.\n"));
831 } else { /* No UA, send to Job */
832 Jmsg(ua->jcr, M_INFO, 0, "%s", msg);
833 free_pool_memory(msg);
839 void bsendmsg(void *ctx, const char *fmt, ...)
842 va_start(arg_ptr, fmt);
843 bmsg((UAContext *)ctx, fmt, arg_ptr);
848 * The following UA methods are mainly intended for GUI
852 * This is a message that should be displayed on the user's
855 void UAContext::send_msg(const char *fmt, ...)
858 va_start(arg_ptr, fmt);
859 bmsg(this, fmt, arg_ptr);
865 * This is an error condition with a command. The gui should put
866 * up an error or critical dialog box. The command is aborted.
868 void UAContext::error_msg(const char *fmt, ...)
873 if (bs && api) bs->signal(BNET_ERROR_MSG);
874 va_start(arg_ptr, fmt);
875 bmsg(this, fmt, arg_ptr);
880 * This is a warning message, that should bring up a warning
881 * dialog box on the GUI. The command is not aborted, but something
884 void UAContext::warning_msg(const char *fmt, ...)
889 if (bs && api) bs->signal(BNET_WARNING_MSG);
890 va_start(arg_ptr, fmt);
891 bmsg(this, fmt, arg_ptr);
896 * This is an information message that should probably be put
897 * into the status line of a GUI program.
899 void UAContext::info_msg(const char *fmt, ...)
904 if (bs && api) bs->signal(BNET_INFO_MSG);
905 va_start(arg_ptr, fmt);
906 bmsg(this, fmt, arg_ptr);