2 Bacula® - The Network Backup Solution
4 Copyright (C) 2000-2008 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.
229 /* Do long or full listing */
230 int llist_cmd(UAContext *ua, const char *cmd)
232 return do_list_cmd(ua, cmd, VERT_LIST);
235 /* Do short or summary listing */
236 int list_cmd(UAContext *ua, const char *cmd)
238 return do_list_cmd(ua, cmd, HORZ_LIST);
241 static int do_list_cmd(UAContext *ua, const char *cmd, e_list_type llist)
250 if (!open_client_db(ua))
253 memset(&jr, 0, sizeof(jr));
254 memset(&pr, 0, sizeof(pr));
255 memset(&mr, 0, sizeof(mr));
257 Dmsg1(20, "list: %s\n", cmd);
260 ua->error_msg(_("Hey! DB is NULL\n"));
263 /* Scan arguments looking for things to do */
264 for (i=1; i<ua->argc; i++) {
266 if (strcasecmp(ua->argk[i], NT_("jobs")) == 0) {
267 /* Apply any limit */
268 j = find_arg_with_value(ua, NT_("limit"));
270 jr.limit = atoi(ua->argv[j]);
272 db_list_job_records(ua->jcr, ua->db, &jr, prtit, ua, llist);
275 } else if (strcasecmp(ua->argk[i], NT_("jobtotals")) == 0) {
276 db_list_job_totals(ua->jcr, ua->db, &jr, prtit, ua);
279 } else if (strcasecmp(ua->argk[i], NT_("jobid")) == 0) {
281 jobid = str_to_int64(ua->argv[i]);
284 db_list_job_records(ua->jcr, ua->db, &jr, prtit, ua, llist);
289 } else if ((strcasecmp(ua->argk[i], NT_("job")) == 0 ||
290 strcasecmp(ua->argk[i], NT_("jobname")) == 0) && ua->argv[i]) {
291 bstrncpy(jr.Name, ua->argv[i], MAX_NAME_LENGTH);
293 db_list_job_records(ua->jcr, ua->db, &jr, prtit, ua, llist);
295 /* List UJOBID=xxx */
296 } else if (strcasecmp(ua->argk[i], NT_("ujobid")) == 0 && ua->argv[i]) {
297 bstrncpy(jr.Job, ua->argv[i], MAX_NAME_LENGTH);
299 db_list_job_records(ua->jcr, ua->db, &jr, prtit, ua, llist);
302 } else if (strcasecmp(ua->argk[i], NT_("files")) == 0) {
304 for (j=i+1; j<ua->argc; j++) {
305 if (strcasecmp(ua->argk[j], NT_("ujobid")) == 0 && ua->argv[j]) {
306 bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
308 db_get_job_record(ua->jcr, ua->db, &jr);
310 } else if (strcasecmp(ua->argk[j], NT_("jobid")) == 0 && ua->argv[j]) {
311 jobid = str_to_int64(ua->argv[j]);
316 db_list_files_for_job(ua->jcr, ua->db, jobid, prtit, ua);
321 } else if (strcasecmp(ua->argk[i], NT_("jobmedia")) == 0) {
323 for (j=i+1; j<ua->argc; j++) {
324 if (strcasecmp(ua->argk[j], NT_("ujobid")) == 0 && ua->argv[j]) {
325 bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
327 db_get_job_record(ua->jcr, ua->db, &jr);
329 } else if (strcasecmp(ua->argk[j], NT_("jobid")) == 0 && ua->argv[j]) {
330 jobid = str_to_int64(ua->argv[j]);
334 db_list_jobmedia_records(ua->jcr, ua->db, jobid, prtit, ua, llist);
338 /* List for all jobs (jobid=0) */
339 db_list_jobmedia_records(ua->jcr, ua->db, 0, prtit, ua, llist);
343 } else if (strcasecmp(ua->argk[i], NT_("joblog")) == 0) {
345 for (j=i+1; j<ua->argc; j++) {
346 if (strcasecmp(ua->argk[j], NT_("ujobid")) == 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], NT_("jobid")) == 0 && ua->argv[j]) {
352 jobid = str_to_int64(ua->argv[j]);
356 db_list_joblog_records(ua->jcr, ua->db, jobid, prtit, ua, llist);
360 /* List for all jobs (jobid=0) */
361 db_list_joblog_records(ua->jcr, ua->db, 0, prtit, ua, llist);
366 } else if (strcasecmp(ua->argk[i], NT_("pool")) == 0 ||
367 strcasecmp(ua->argk[i], NT_("pools")) == 0) {
369 memset(&pr, 0, sizeof(pr));
371 bstrncpy(pr.Name, ua->argv[i], sizeof(pr.Name));
373 db_list_pool_records(ua->jcr, ua->db, &pr, prtit, ua, llist);
375 } else if (strcasecmp(ua->argk[i], NT_("clients")) == 0) {
376 db_list_client_records(ua->jcr, ua->db, prtit, ua, llist);
379 /* List MEDIA or VOLUMES */
380 } else if (strcasecmp(ua->argk[i], NT_("media")) == 0 ||
381 strcasecmp(ua->argk[i], NT_("volume")) == 0 ||
382 strcasecmp(ua->argk[i], NT_("volumes")) == 0) {
384 for (j=i+1; j<ua->argc; j++) {
385 if (strcasecmp(ua->argk[j], NT_("ujobid")) == 0 && ua->argv[j]) {
386 bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
388 db_get_job_record(ua->jcr, ua->db, &jr);
390 } else if (strcasecmp(ua->argk[j], NT_("jobid")) == 0 && ua->argv[j]) {
391 jobid = str_to_int64(ua->argv[j]);
395 VolumeName = get_pool_memory(PM_FNAME);
396 n = db_get_job_volume_names(ua->jcr, ua->db, jobid, &VolumeName);
397 ua->send_msg(_("Jobid %d used %d Volume(s): %s\n"), jobid, n, VolumeName);
398 free_pool_memory(VolumeName);
401 /* if no job or jobid keyword found, then we list all media */
405 /* List a specific volume? */
407 bstrncpy(mr.VolumeName, ua->argv[i], sizeof(mr.VolumeName));
408 db_list_media_records(ua->jcr, ua->db, &mr, prtit, ua, llist);
411 /* Is a specific pool wanted? */
412 for (i=1; i<ua->argc; i++) {
413 if (strcasecmp(ua->argk[i], NT_("pool")) == 0) {
414 if (!get_pool_dbr(ua, &pr)) {
415 ua->error_msg(_("No Pool specified.\n"));
418 mr.PoolId = pr.PoolId;
419 db_list_media_records(ua->jcr, ua->db, &mr, prtit, ua, llist);
424 /* List Volumes in all pools */
425 if (!db_get_pool_ids(ua->jcr, ua->db, &num_pools, &ids)) {
426 ua->error_msg(_("Error obtaining pool ids. ERR=%s\n"),
427 db_strerror(ua->db));
430 if (num_pools <= 0) {
433 for (i=0; i < num_pools; i++) {
435 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
436 ua->send_msg(_("Pool: %s\n"), pr.Name);
439 db_list_media_records(ua->jcr, ua->db, &mr, prtit, ua, llist);
444 /* List next volume */
445 } else if (strcasecmp(ua->argk[i], NT_("nextvol")) == 0 ||
446 strcasecmp(ua->argk[i], NT_("nextvolume")) == 0) {
448 j = find_arg_with_value(ua, NT_("days"));
450 n = atoi(ua->argv[j]);
451 if ((n < 0) || (n > 50)) {
452 ua->warning_msg(_("Ignoring invalid value for days. Max is 50.\n"));
457 } else if (strcasecmp(ua->argk[i], NT_("limit")) == 0
458 || strcasecmp(ua->argk[i], NT_("days")) == 0) {
461 ua->error_msg(_("Unknown list keyword: %s\n"), NPRT(ua->argk[i]));
467 static bool list_nextvol(UAContext *ua, int ndays)
478 memset(&mr, 0, sizeof(mr));
479 int i = find_arg_with_value(ua, "job");
481 if ((job = select_job_resource(ua)) == NULL) {
485 job = (JOB *)GetResWithName(R_JOB, ua->argv[i]);
487 Jmsg(ua->jcr, M_ERROR, 0, _("%s is not a job name.\n"), ua->argv[i]);
488 if ((job = select_job_resource(ua)) == NULL) {
494 jcr = new_jcr(sizeof(JCR), dird_free_jcr);
495 for (run=NULL; (run = find_next_run(run, job, runtime, ndays)); ) {
496 if (!complete_jcr_for_job(jcr, job, run->pool)) {
500 if (!jcr->jr.PoolId) {
501 ua->error_msg(_("Could not find Pool for Job %s\n"), job->name());
504 memset(&pr, 0, sizeof(pr));
505 pr.PoolId = jcr->jr.PoolId;
506 if (!db_get_pool_record(jcr, jcr->db, &pr)) {
507 bstrncpy(pr.Name, "*UnknownPool*", sizeof(pr.Name));
509 mr.PoolId = jcr->jr.PoolId;
510 get_job_storage(&store, job, run);
511 mr.StorageId = store.store->StorageId;
512 if (!find_next_volume_for_append(jcr, &mr, 1, fnv_no_create_vol, fnv_prune)) {
513 ua->error_msg(_("Could not find next Volume for Job %s (Pool=%s, Level=%s).\n"),
514 job->name(), pr.Name, level_to_str(run->level));
517 _("The next Volume to be used by Job \"%s\" (Pool=%s, Level=%s) will be %s\n"),
518 job->name(), pr.Name, level_to_str(run->level), mr.VolumeName);
524 db_close_database(jcr, jcr->db);
528 ua->error_msg(_("Could not find next Volume for Job %s.\n"),
537 * For a given job, we examine all his run records
538 * to see if it is scheduled today or tomorrow.
540 RUN *find_next_run(RUN *run, JOB *job, utime_t &runtime, int ndays)
542 time_t now, future, endtime;
545 int mday, wday, month, wom, i;
550 sched = job->schedule;
551 if (sched == NULL) { /* scheduled? */
552 return NULL; /* no nothing to report */
555 /* Break down the time into components */
557 endtime = now + (ndays * 60 * 60 * 24);
564 for ( ; run; run=run->next) {
566 * Find runs in next 24 hours. Day 0 is today, so if
567 * ndays=1, look at today and tomorrow.
569 for (day = 0; day <= ndays; day++) {
570 future = now + (day * 60 * 60 * 24);
572 /* Break down the time into components */
573 (void)localtime_r(&future, &tm);
574 mday = tm.tm_mday - 1;
578 woy = tm_woy(future);
580 is_scheduled = bit_is_set(mday, run->mday) && bit_is_set(wday, run->wday) &&
581 bit_is_set(month, run->month) && bit_is_set(wom, run->wom) &&
582 bit_is_set(woy, run->woy);
585 Pmsg2(000, "day=%d is_scheduled=%d\n", day, is_scheduled);
586 Pmsg1(000, "bit_set_mday=%d\n", bit_is_set(mday, run->mday));
587 Pmsg1(000, "bit_set_wday=%d\n", bit_is_set(wday, run->wday));
588 Pmsg1(000, "bit_set_month=%d\n", bit_is_set(month, run->month));
589 Pmsg1(000, "bit_set_wom=%d\n", bit_is_set(wom, run->wom));
590 Pmsg1(000, "bit_set_woy=%d\n", bit_is_set(woy, run->woy));
593 if (is_scheduled) { /* Jobs scheduled on that day */
595 char buf[300], num[10];
596 bsnprintf(buf, sizeof(buf), "tm.hour=%d hour=", tm.tm_hour);
597 for (i=0; i<24; i++) {
598 if (bit_is_set(i, run->hour)) {
599 bsnprintf(num, sizeof(num), "%d ", i);
600 bstrncat(buf, num, sizeof(buf));
603 bstrncat(buf, "\n", sizeof(buf));
604 Pmsg1(000, "%s", buf);
606 /* find time (time_t) job is to be run */
607 (void)localtime_r(&future, &runtm);
608 for (i= 0; i < 24; i++) {
609 if (bit_is_set(i, run->hour)) {
611 runtm.tm_min = run->minute;
613 runtime = mktime(&runtm);
614 Dmsg2(200, "now=%d runtime=%lld\n", now, runtime);
615 if ((runtime > now) && (runtime < endtime)) {
616 Dmsg2(200, "Found it level=%d %c\n", run->level, run->level);
617 return run; /* found it, return run resource */
623 } /* end for loop over runs */
629 * Fill in the remaining fields of the jcr as if it
630 * is going to run the job.
632 bool complete_jcr_for_job(JCR *jcr, JOB *job, POOL *pool)
636 memset(&pr, 0, sizeof(POOL_DBR));
637 set_jcr_defaults(jcr, job);
639 jcr->pool = pool; /* override */
642 Dmsg0(100, "complete_jcr close db\n");
643 db_close_database(jcr, jcr->db);
647 Dmsg0(100, "complete_jcr open db\n");
648 jcr->db = jcr->db=db_init(jcr, jcr->catalog->db_driver, jcr->catalog->db_name,
649 jcr->catalog->db_user,
650 jcr->catalog->db_password, jcr->catalog->db_address,
651 jcr->catalog->db_port, jcr->catalog->db_socket,
652 jcr->catalog->mult_db_connections);
653 if (!jcr->db || !db_open_database(jcr, jcr->db)) {
654 Jmsg(jcr, M_FATAL, 0, _("Could not open database \"%s\".\n"),
655 jcr->catalog->db_name);
657 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
658 db_close_database(jcr, jcr->db);
663 bstrncpy(pr.Name, jcr->pool->name(), sizeof(pr.Name));
664 while (!db_get_pool_record(jcr, jcr->db, &pr)) { /* get by Name */
665 /* Try to create the pool */
666 if (create_pool(jcr, jcr->db, jcr->pool, POOL_OP_CREATE) < 0) {
667 Jmsg(jcr, M_FATAL, 0, _("Pool %s not in database. %s"), pr.Name,
668 db_strerror(jcr->db));
670 db_close_database(jcr, jcr->db);
675 Jmsg(jcr, M_INFO, 0, _("Pool %s created in database.\n"), pr.Name);
678 jcr->jr.PoolId = pr.PoolId;
683 static void con_lock_release(void *arg)
688 void do_messages(UAContext *ua, const char *cmd)
692 bool do_truncate = false;
695 pthread_cleanup_push(con_lock_release, (void *)NULL);
697 while (fgets(msg, sizeof(msg), con_fd)) {
699 ua->UA_sock->msg = check_pool_memory_size(ua->UA_sock->msg, mlen+1);
700 strcpy(ua->UA_sock->msg, msg);
701 ua->UA_sock->msglen = mlen;
706 (void)ftruncate(fileno(con_fd), 0L);
708 console_msg_pending = FALSE;
709 ua->user_notified_msg_pending = FALSE;
710 pthread_cleanup_pop(0);
715 int qmessagescmd(UAContext *ua, const char *cmd)
717 if (console_msg_pending && ua->auto_display_messages) {
718 do_messages(ua, cmd);
723 int messagescmd(UAContext *ua, const char *cmd)
725 if (console_msg_pending) {
726 do_messages(ua, cmd);
728 ua->UA_sock->fsend(_("You have no messages.\n"));
734 * Callback routine for "printing" database file listing
736 void prtit(void *ctx, const char *msg)
738 UAContext *ua = (UAContext *)ctx;
740 ua->UA_sock->fsend("%s", msg);
744 * Format message and send to other end.
746 * If the UA_sock is NULL, it means that there is no user
747 * agent, so we are being called from Bacula core. In
748 * that case direct the messages to the Job.
751 void bmsg(UAContext *ua, const char *fmt, va_list arg_ptr)
753 BSOCK *bs = ua->UA_sock;
762 msg = get_pool_memory(PM_EMSG);
766 maxlen = sizeof_pool_memory(msg) - 1;
767 va_copy(ap, arg_ptr);
768 len = bvsnprintf(msg, maxlen, fmt, ap);
770 if (len < 0 || len >= maxlen) {
771 msg = realloc_pool_memory(msg, maxlen + maxlen/2);
779 } else { /* No UA, send to Job */
780 Jmsg(ua->jcr, M_INFO, 0, "%s", msg);
781 free_pool_memory(msg);
786 #else /* no va_copy() -- brain damaged version of variable arguments */
788 void bmsg(UAContext *ua, const char *fmt, va_list arg_ptr)
790 BSOCK *bs = ua->UA_sock;
798 msg = get_memory(5000);
801 maxlen = sizeof_pool_memory(msg) - 1;
803 msg = realloc_pool_memory(msg, 5000);
806 len = bvsnprintf(msg, maxlen, fmt, arg_ptr);
807 if (len < 0 || len >= maxlen) {
808 pm_strcpy(msg, _("Message too long to display.\n"));
816 } else { /* No UA, send to Job */
817 Jmsg(ua->jcr, M_INFO, 0, "%s", msg);
818 free_pool_memory(msg);
824 void bsendmsg(void *ctx, const char *fmt, ...)
827 va_start(arg_ptr, fmt);
828 bmsg((UAContext *)ctx, fmt, arg_ptr);
833 * The following UA methods are mainly intended for GUI
837 * This is a message that should be displayed on the user's
840 void UAContext::send_msg(const char *fmt, ...)
843 va_start(arg_ptr, fmt);
844 bmsg(this, fmt, arg_ptr);
850 * This is an error condition with a command. The gui should put
851 * up an error or critical dialog box. The command is aborted.
853 void UAContext::error_msg(const char *fmt, ...)
858 if (bs && api) bs->signal(BNET_ERROR_MSG);
859 va_start(arg_ptr, fmt);
860 bmsg(this, fmt, arg_ptr);
865 * This is a warning message, that should bring up a warning
866 * dialog box on the GUI. The command is not aborted, but something
869 void UAContext::warning_msg(const char *fmt, ...)
874 if (bs && api) bs->signal(BNET_WARNING_MSG);
875 va_start(arg_ptr, fmt);
876 bmsg(this, fmt, arg_ptr);
881 * This is an information message that should probably be put
882 * into the status line of a GUI program.
884 void UAContext::info_msg(const char *fmt, ...)
889 if (bs && api) bs->signal(BNET_INFO_MSG);
890 va_start(arg_ptr, fmt);
891 bmsg(this, fmt, arg_ptr);