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.
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 if (!find_next_volume_for_append(jcr, &mr, 1, fnv_no_create_vol, fnv_prune)) {
527 ua->error_msg(_("Could not find next Volume for Job %s (Pool=%s, Level=%s).\n"),
528 job->name(), pr.Name, level_to_str(run->level));
531 _("The next Volume to be used by Job \"%s\" (Pool=%s, Level=%s) will be %s\n"),
532 job->name(), pr.Name, level_to_str(run->level), mr.VolumeName);
538 db_close_database(jcr, jcr->db);
542 ua->error_msg(_("Could not find next Volume for Job %s.\n"),
551 * For a given job, we examine all his run records
552 * to see if it is scheduled today or tomorrow.
554 RUN *find_next_run(RUN *run, JOB *job, utime_t &runtime, int ndays)
556 time_t now, future, endtime;
559 int mday, wday, month, wom, i;
564 sched = job->schedule;
565 if (sched == NULL) { /* scheduled? */
566 return NULL; /* no nothing to report */
569 /* Break down the time into components */
571 endtime = now + (ndays * 60 * 60 * 24);
578 for ( ; run; run=run->next) {
580 * Find runs in next 24 hours. Day 0 is today, so if
581 * ndays=1, look at today and tomorrow.
583 for (day = 0; day <= ndays; day++) {
584 future = now + (day * 60 * 60 * 24);
586 /* Break down the time into components */
587 (void)localtime_r(&future, &tm);
588 mday = tm.tm_mday - 1;
592 woy = tm_woy(future);
594 is_scheduled = bit_is_set(mday, run->mday) && bit_is_set(wday, run->wday) &&
595 bit_is_set(month, run->month) && bit_is_set(wom, run->wom) &&
596 bit_is_set(woy, run->woy);
599 Pmsg2(000, "day=%d is_scheduled=%d\n", day, is_scheduled);
600 Pmsg1(000, "bit_set_mday=%d\n", bit_is_set(mday, run->mday));
601 Pmsg1(000, "bit_set_wday=%d\n", bit_is_set(wday, run->wday));
602 Pmsg1(000, "bit_set_month=%d\n", bit_is_set(month, run->month));
603 Pmsg1(000, "bit_set_wom=%d\n", bit_is_set(wom, run->wom));
604 Pmsg1(000, "bit_set_woy=%d\n", bit_is_set(woy, run->woy));
607 if (is_scheduled) { /* Jobs scheduled on that day */
609 char buf[300], num[10];
610 bsnprintf(buf, sizeof(buf), "tm.hour=%d hour=", tm.tm_hour);
611 for (i=0; i<24; i++) {
612 if (bit_is_set(i, run->hour)) {
613 bsnprintf(num, sizeof(num), "%d ", i);
614 bstrncat(buf, num, sizeof(buf));
617 bstrncat(buf, "\n", sizeof(buf));
618 Pmsg1(000, "%s", buf);
620 /* find time (time_t) job is to be run */
621 (void)localtime_r(&future, &runtm);
622 for (i= 0; i < 24; i++) {
623 if (bit_is_set(i, run->hour)) {
625 runtm.tm_min = run->minute;
627 runtime = mktime(&runtm);
628 Dmsg2(200, "now=%d runtime=%lld\n", now, runtime);
629 if ((runtime > now) && (runtime < endtime)) {
630 Dmsg2(200, "Found it level=%d %c\n", run->level, run->level);
631 return run; /* found it, return run resource */
637 } /* end for loop over runs */
643 * Fill in the remaining fields of the jcr as if it
644 * is going to run the job.
646 bool complete_jcr_for_job(JCR *jcr, JOB *job, POOL *pool)
650 memset(&pr, 0, sizeof(POOL_DBR));
651 set_jcr_defaults(jcr, job);
653 jcr->pool = pool; /* override */
656 Dmsg0(100, "complete_jcr close db\n");
657 db_close_database(jcr, jcr->db);
661 Dmsg0(100, "complete_jcr open db\n");
662 jcr->db = jcr->db=db_init(jcr, jcr->catalog->db_driver, jcr->catalog->db_name,
663 jcr->catalog->db_user,
664 jcr->catalog->db_password, jcr->catalog->db_address,
665 jcr->catalog->db_port, jcr->catalog->db_socket,
666 jcr->catalog->mult_db_connections);
667 if (!jcr->db || !db_open_database(jcr, jcr->db)) {
668 Jmsg(jcr, M_FATAL, 0, _("Could not open database \"%s\".\n"),
669 jcr->catalog->db_name);
671 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
672 db_close_database(jcr, jcr->db);
677 bstrncpy(pr.Name, jcr->pool->name(), sizeof(pr.Name));
678 while (!db_get_pool_record(jcr, jcr->db, &pr)) { /* get by Name */
679 /* Try to create the pool */
680 if (create_pool(jcr, jcr->db, jcr->pool, POOL_OP_CREATE) < 0) {
681 Jmsg(jcr, M_FATAL, 0, _("Pool %s not in database. %s"), pr.Name,
682 db_strerror(jcr->db));
684 db_close_database(jcr, jcr->db);
689 Jmsg(jcr, M_INFO, 0, _("Pool %s created in database.\n"), pr.Name);
692 jcr->jr.PoolId = pr.PoolId;
697 static void con_lock_release(void *arg)
702 void do_messages(UAContext *ua, const char *cmd)
706 bool do_truncate = false;
709 pthread_cleanup_push(con_lock_release, (void *)NULL);
711 while (fgets(msg, sizeof(msg), con_fd)) {
713 ua->UA_sock->msg = check_pool_memory_size(ua->UA_sock->msg, mlen+1);
714 strcpy(ua->UA_sock->msg, msg);
715 ua->UA_sock->msglen = mlen;
720 (void)ftruncate(fileno(con_fd), 0L);
722 console_msg_pending = FALSE;
723 ua->user_notified_msg_pending = FALSE;
724 pthread_cleanup_pop(0);
729 int qmessagescmd(UAContext *ua, const char *cmd)
731 if (console_msg_pending && ua->auto_display_messages) {
732 do_messages(ua, cmd);
737 int messagescmd(UAContext *ua, const char *cmd)
739 if (console_msg_pending) {
740 do_messages(ua, cmd);
742 ua->UA_sock->fsend(_("You have no messages.\n"));
748 * Callback routine for "printing" database file listing
750 void prtit(void *ctx, const char *msg)
752 UAContext *ua = (UAContext *)ctx;
754 ua->UA_sock->fsend("%s", msg);
758 * Format message and send to other end.
760 * If the UA_sock is NULL, it means that there is no user
761 * agent, so we are being called from Bacula core. In
762 * that case direct the messages to the Job.
765 void bmsg(UAContext *ua, const char *fmt, va_list arg_ptr)
767 BSOCK *bs = ua->UA_sock;
776 msg = get_pool_memory(PM_EMSG);
780 maxlen = sizeof_pool_memory(msg) - 1;
781 va_copy(ap, arg_ptr);
782 len = bvsnprintf(msg, maxlen, fmt, ap);
784 if (len < 0 || len >= maxlen) {
785 msg = realloc_pool_memory(msg, maxlen + maxlen/2);
793 } else { /* No UA, send to Job */
794 Jmsg(ua->jcr, M_INFO, 0, "%s", msg);
795 free_pool_memory(msg);
800 #else /* no va_copy() -- brain damaged version of variable arguments */
802 void bmsg(UAContext *ua, const char *fmt, va_list arg_ptr)
804 BSOCK *bs = ua->UA_sock;
812 msg = get_memory(5000);
815 maxlen = sizeof_pool_memory(msg) - 1;
817 msg = realloc_pool_memory(msg, 5000);
820 len = bvsnprintf(msg, maxlen, fmt, arg_ptr);
821 if (len < 0 || len >= maxlen) {
822 pm_strcpy(msg, _("Message too long to display.\n"));
830 } else { /* No UA, send to Job */
831 Jmsg(ua->jcr, M_INFO, 0, "%s", msg);
832 free_pool_memory(msg);
838 void bsendmsg(void *ctx, const char *fmt, ...)
841 va_start(arg_ptr, fmt);
842 bmsg((UAContext *)ctx, fmt, arg_ptr);
847 * The following UA methods are mainly intended for GUI
851 * This is a message that should be displayed on the user's
854 void UAContext::send_msg(const char *fmt, ...)
857 va_start(arg_ptr, fmt);
858 bmsg(this, fmt, arg_ptr);
864 * This is an error condition with a command. The gui should put
865 * up an error or critical dialog box. The command is aborted.
867 void UAContext::error_msg(const char *fmt, ...)
872 if (bs && api) bs->signal(BNET_ERROR_MSG);
873 va_start(arg_ptr, fmt);
874 bmsg(this, fmt, arg_ptr);
879 * This is a warning message, that should bring up a warning
880 * dialog box on the GUI. The command is not aborted, but something
883 void UAContext::warning_msg(const char *fmt, ...)
888 if (bs && api) bs->signal(BNET_WARNING_MSG);
889 va_start(arg_ptr, fmt);
890 bmsg(this, fmt, arg_ptr);
895 * This is an information message that should probably be put
896 * into the status line of a GUI program.
898 void UAContext::info_msg(const char *fmt, ...)
903 if (bs && api) bs->signal(BNET_INFO_MSG);
904 va_start(arg_ptr, fmt);
905 bmsg(this, fmt, arg_ptr);