2 Bacula® - The Network Backup Solution
4 Copyright (C) 2001-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 John Walker.
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 Status Command
32 * Kern Sibbald, August MMI
41 extern void *start_heap;
43 static void list_scheduled_jobs(UAContext *ua);
44 static void list_running_jobs(UAContext *ua);
45 static void list_terminated_jobs(UAContext *ua);
46 static void do_storage_status(UAContext *ua, STORE *store, char *cmd);
47 static void do_client_status(UAContext *ua, CLIENT *client, char *cmd);
48 static void do_director_status(UAContext *ua);
49 static void do_all_status(UAContext *ua);
51 static char OKqstatus[] = "1000 OK .status\n";
52 static char DotStatusJob[] = "JobId=%s JobStatus=%c JobErrors=%d\n";
58 bool dot_status_cmd(UAContext *ua, const char *cmd)
66 Dmsg2(20, "status=\"%s\" argc=%d\n", cmd, ua->argc);
69 ua->send_msg("1900 Bad .status command, missing arguments.\n");
73 if (strcasecmp(ua->argk[1], "dir") == 0) {
74 if (strcasecmp(ua->argk[2], "current") == 0) {
75 ua->send_msg(OKqstatus, ua->argk[2]);
77 if (njcr->JobId != 0 && acl_access_ok(ua, Job_ACL, njcr->job->name())) {
78 ua->send_msg(DotStatusJob, edit_int64(njcr->JobId, ed1),
79 njcr->JobStatus, njcr->JobErrors);
83 } else if (strcasecmp(ua->argk[2], "last") == 0) {
84 ua->send_msg(OKqstatus, ua->argk[2]);
85 if ((last_jobs) && (last_jobs->size() > 0)) {
86 job = (s_last_job*)last_jobs->last();
87 if (acl_access_ok(ua, Job_ACL, job->Job)) {
88 ua->send_msg(DotStatusJob, edit_int64(job->JobId, ed1),
89 job->JobStatus, job->Errors);
92 } else if (strcasecmp(ua->argk[2], "header") == 0) {
93 list_dir_status_header(ua);
94 } else if (strcasecmp(ua->argk[2], "scheduled") == 0) {
95 list_scheduled_jobs(ua);
96 } else if (strcasecmp(ua->argk[2], "running") == 0) {
97 list_running_jobs(ua);
98 } else if (strcasecmp(ua->argk[2], "terminated") == 0) {
99 list_terminated_jobs(ua);
101 ua->send_msg("1900 Bad .status command, wrong argument.\n");
104 } else if (strcasecmp(ua->argk[1], "client") == 0) {
105 client = get_client_resource(ua);
107 Dmsg2(200, "Client=%s arg=%s\n", client->name(), NPRT(ua->argk[2]));
108 do_client_status(ua, client, ua->argk[2]);
110 } else if (strcasecmp(ua->argk[1], "storage") == 0) {
111 store = get_storage_resource(ua, false /*no default*/);
113 do_storage_status(ua, store, ua->argk[2]);
116 ua->send_msg("1900 Bad .status command, wrong argument.\n");
123 /* This is the *old* command handler, so we must return
124 * 1 or it closes the connection
126 int qstatus_cmd(UAContext *ua, const char *cmd)
128 dot_status_cmd(ua, cmd);
135 int status_cmd(UAContext *ua, const char *cmd)
141 Dmsg1(20, "status:%s:\n", cmd);
143 for (i=1; i<ua->argc; i++) {
144 if (strcasecmp(ua->argk[i], NT_("all")) == 0) {
147 } else if (strcasecmp(ua->argk[i], NT_("dir")) == 0 ||
148 strcasecmp(ua->argk[i], NT_("director")) == 0) {
149 do_director_status(ua);
151 } else if (strcasecmp(ua->argk[i], NT_("client")) == 0) {
152 client = get_client_resource(ua);
154 do_client_status(ua, client, NULL);
158 store = get_storage_resource(ua, false/*no default*/);
160 do_storage_status(ua, store, NULL);
165 /* If no args, ask for status type */
167 char prmt[MAX_NAME_LENGTH];
169 start_prompt(ua, _("Status available for:\n"));
170 add_prompt(ua, NT_("Director"));
171 add_prompt(ua, NT_("Storage"));
172 add_prompt(ua, NT_("Client"));
173 add_prompt(ua, NT_("All"));
174 Dmsg0(20, "do_prompt: select daemon\n");
175 if ((item=do_prompt(ua, "", _("Select daemon type for status"), prmt, sizeof(prmt))) < 0) {
178 Dmsg1(20, "item=%d\n", item);
180 case 0: /* Director */
181 do_director_status(ua);
184 store = select_storage_resource(ua);
186 do_storage_status(ua, store, NULL);
190 client = select_client_resource(ua);
192 do_client_status(ua, client, NULL);
205 static void do_all_status(UAContext *ua)
207 STORE *store, **unique_store;
208 CLIENT *client, **unique_client;
212 do_director_status(ua);
214 /* Count Storage items */
217 foreach_res(store, R_STORAGE) {
220 unique_store = (STORE **) malloc(i * sizeof(STORE));
221 /* Find Unique Storage address/port */
223 foreach_res(store, R_STORAGE) {
225 if (!acl_access_ok(ua, Storage_ACL, store->name())) {
228 for (j=0; j<i; j++) {
229 if (strcmp(unique_store[j]->address, store->address) == 0 &&
230 unique_store[j]->SDport == store->SDport) {
236 unique_store[i++] = store;
237 Dmsg2(40, "Stuffing: %s:%d\n", store->address, store->SDport);
242 /* Call each unique Storage daemon */
243 for (j=0; j<i; j++) {
244 do_storage_status(ua, unique_store[j], NULL);
248 /* Count Client items */
251 foreach_res(client, R_CLIENT) {
254 unique_client = (CLIENT **)malloc(i * sizeof(CLIENT));
255 /* Find Unique Client address/port */
257 foreach_res(client, R_CLIENT) {
259 if (!acl_access_ok(ua, Client_ACL, client->name())) {
262 for (j=0; j<i; j++) {
263 if (strcmp(unique_client[j]->address, client->address) == 0 &&
264 unique_client[j]->FDport == client->FDport) {
270 unique_client[i++] = client;
271 Dmsg2(40, "Stuffing: %s:%d\n", client->address, client->FDport);
276 /* Call each unique File daemon */
277 for (j=0; j<i; j++) {
278 do_client_status(ua, unique_client[j], NULL);
284 void list_dir_status_header(UAContext *ua)
286 char dt[MAX_TIME_LENGTH];
287 char b1[35], b2[35], b3[35], b4[35], b5[35];
289 ua->send_msg(_("%s Version: %s (%s) %s %s %s\n"), my_name, VERSION, BDATE,
290 HOST_OS, DISTNAME, DISTVER);
291 bstrftime_nc(dt, sizeof(dt), daemon_start_time);
292 if (num_jobs_run == 1) {
293 ua->send_msg(_("Daemon started %s, 1 Job run since started.\n"), dt);
296 ua->send_msg(_("Daemon started %s, %d Jobs run since started.\n"),
299 ua->send_msg(_(" Heap: heap=%s smbytes=%s max_bytes=%s bufs=%s max_bufs=%s\n"),
300 edit_uint64_with_commas((char *)sbrk(0)-(char *)start_heap, b1),
301 edit_uint64_with_commas(sm_bytes, b2),
302 edit_uint64_with_commas(sm_max_bytes, b3),
303 edit_uint64_with_commas(sm_buffers, b4),
304 edit_uint64_with_commas(sm_max_buffers, b5));
307 static void do_director_status(UAContext *ua)
309 list_dir_status_header(ua);
312 * List scheduled Jobs
314 list_scheduled_jobs(ua);
319 list_running_jobs(ua);
322 * List terminated jobs
324 list_terminated_jobs(ua);
325 ua->send_msg("====\n");
328 static void do_storage_status(UAContext *ua, STORE *store, char *cmd)
333 lstore.store = store;
334 pm_strcpy(lstore.store_source, _("unknown source"));
335 set_wstorage(ua->jcr, &lstore);
336 /* Try connecting for up to 15 seconds */
337 if (!ua->api) ua->send_msg(_("Connecting to Storage daemon %s at %s:%d\n"),
338 store->name(), store->address, store->SDport);
339 if (!connect_to_storage_daemon(ua->jcr, 1, 15, 0)) {
340 ua->send_msg(_("\nFailed to connect to Storage daemon %s.\n====\n"),
342 if (ua->jcr->store_bsock) {
343 bnet_close(ua->jcr->store_bsock);
344 ua->jcr->store_bsock = NULL;
348 Dmsg0(20, _("Connected to storage daemon\n"));
349 sd = ua->jcr->store_bsock;
351 sd->fsend(".status %s", cmd);
355 while (sd->recv() >= 0) {
356 ua->send_msg("%s", sd->msg);
358 sd->signal( BNET_TERMINATE);
360 ua->jcr->store_bsock = NULL;
364 static void do_client_status(UAContext *ua, CLIENT *client, char *cmd)
368 /* Connect to File daemon */
370 ua->jcr->client = client;
371 /* Release any old dummy key */
372 if (ua->jcr->sd_auth_key) {
373 free(ua->jcr->sd_auth_key);
375 /* Create a new dummy SD auth key */
376 ua->jcr->sd_auth_key = bstrdup("dummy");
378 /* Try to connect for 15 seconds */
379 if (!ua->api) ua->send_msg(_("Connecting to Client %s at %s:%d\n"),
380 client->name(), client->address, client->FDport);
381 if (!connect_to_file_daemon(ua->jcr, 1, 15, 0)) {
382 ua->send_msg(_("Failed to connect to Client %s.\n====\n"),
384 if (ua->jcr->file_bsock) {
385 bnet_close(ua->jcr->file_bsock);
386 ua->jcr->file_bsock = NULL;
390 Dmsg0(20, _("Connected to file daemon\n"));
391 fd = ua->jcr->file_bsock;
393 fd->fsend(".status %s", cmd);
397 while (fd->recv() >= 0) {
398 ua->send_msg("%s", fd->msg);
400 fd->signal(BNET_TERMINATE);
402 ua->jcr->file_bsock = NULL;
407 static void prt_runhdr(UAContext *ua)
410 ua->send_msg(_("\nScheduled Jobs:\n"));
411 ua->send_msg(_("Level Type Pri Scheduled Name Volume\n"));
412 ua->send_msg(_("===================================================================================\n"));
416 /* Scheduling packet */
418 dlink link; /* keep this as first item!!! */
427 static void prt_runtime(UAContext *ua, sched_pkt *sp)
429 char dt[MAX_TIME_LENGTH];
430 const char *level_ptr;
432 bool close_db = false;
437 orig_jobtype = jcr->JobType;
438 memset(&mr, 0, sizeof(mr));
439 if (sp->job->JobType == JT_BACKUP) {
441 ok = complete_jcr_for_job(jcr, sp->job, sp->pool);
442 Dmsg1(250, "Using pool=%s\n", jcr->pool->name());
444 close_db = true; /* new db opened, remember to close it */
447 mr.PoolId = jcr->jr.PoolId;
448 mr.StorageId = sp->store->StorageId;
449 jcr->wstore = sp->store;
450 Dmsg0(250, "call find_next_volume_for_append\n");
451 ok = find_next_volume_for_append(jcr, &mr, 1, fnv_no_create_vol, fnv_no_prune);
454 bstrncpy(mr.VolumeName, "*unknown*", sizeof(mr.VolumeName));
457 bstrftime_nc(dt, sizeof(dt), sp->runtime);
458 switch (sp->job->JobType) {
464 level_ptr = level_to_str(sp->level);
468 ua->send_msg(_("%-14s\t%-8s\t%3d\t%-18s\t%-18s\t%s\n"),
469 level_ptr, job_type_to_str(sp->job->JobType), sp->priority, dt,
470 sp->job->name(), mr.VolumeName);
472 ua->send_msg(_("%-14s %-8s %3d %-18s %-18s %s\n"),
473 level_ptr, job_type_to_str(sp->job->JobType), sp->priority, dt,
474 sp->job->name(), mr.VolumeName);
477 db_close_database(jcr, jcr->db);
479 jcr->db = ua->db; /* restore ua db to jcr */
480 jcr->JobType = orig_jobtype;
484 * Sort items by runtime, priority
486 static int my_compare(void *item1, void *item2)
488 sched_pkt *p1 = (sched_pkt *)item1;
489 sched_pkt *p2 = (sched_pkt *)item2;
490 if (p1->runtime < p2->runtime) {
492 } else if (p1->runtime > p2->runtime) {
495 if (p1->priority < p2->priority) {
497 } else if (p1->priority > p2->priority) {
504 * Find all jobs to be run in roughly the
507 static void list_scheduled_jobs(UAContext *ua)
512 int level, num_jobs = 0;
514 bool hdr_printed = false;
519 Dmsg0(200, "enter list_sched_jobs()\n");
522 i = find_arg_with_value(ua, NT_("days"));
524 days = atoi(ua->argv[i]);
525 if ((days < 0) || (days > 500) && !ua->api) {
526 ua->send_msg(_("Ignoring invalid value for days. Max is 500.\n"));
531 /* Loop through all jobs */
533 foreach_res(job, R_JOB) {
534 if (!acl_access_ok(ua, Job_ACL, job->name()) || !job->enabled) {
537 for (run=NULL; (run = find_next_run(run, job, runtime, days)); ) {
539 level = job->JobLevel;
543 priority = job->Priority;
545 priority = run->Priority;
551 sp = (sched_pkt *)malloc(sizeof(sched_pkt));
554 sp->priority = priority;
555 sp->runtime = runtime;
556 sp->pool = run->pool;
557 get_job_storage(&store, job, run);
558 sp->store = store.store;
559 Dmsg3(250, "job=%s store=%s MediaType=%s\n", job->name(), sp->store->name(), sp->store->media_type);
560 sched.binary_insert_multiple(sp, my_compare);
563 } /* end for loop over resources */
565 foreach_dlist(sp, &sched) {
568 if (num_jobs == 0 && !ua->api) {
569 ua->send_msg(_("No Scheduled Jobs.\n"));
571 if (!ua->api) ua->send_msg("====\n");
572 Dmsg0(200, "Leave list_sched_jobs_runs()\n");
575 static void list_running_jobs(UAContext *ua)
580 char *emsg; /* edited message */
581 char dt[MAX_TIME_LENGTH];
583 bool pool_mem = false;
585 Dmsg0(200, "enter list_run_jobs()\n");
586 if (!ua->api) ua->send_msg(_("\nRunning Jobs:\n"));
588 if (jcr->JobId == 0) { /* this is us */
589 /* this is a console or other control job. We only show console
590 * jobs in the status output.
592 if (jcr->JobType == JT_CONSOLE && !ua->api) {
593 bstrftime_nc(dt, sizeof(dt), jcr->start_time);
594 ua->send_msg(_("Console connected at %s\n"), dt);
603 /* Note the following message is used in regress -- don't change */
604 if (!ua->api) ua->send_msg(_("No Jobs running.\n====\n"));
605 Dmsg0(200, "leave list_run_jobs()\n");
610 ua->send_msg(_(" JobId Level Name Status\n"));
611 ua->send_msg(_("======================================================================\n"));
614 if (jcr->JobId == 0 || !acl_access_ok(ua, Job_ACL, jcr->job->name())) {
618 switch (jcr->JobStatus) {
620 msg = _("is waiting execution");
623 msg = _("is running");
626 msg = _("is blocked");
629 msg = _("has terminated");
631 case JS_ErrorTerminated:
632 msg = _("has erred");
635 msg = _("has errors");
638 msg = _("has a fatal error");
641 msg = _("has verify differences");
644 msg = _("has been canceled");
647 emsg = (char *) get_pool_memory(PM_FNAME);
649 Mmsg(emsg, _("is waiting on Client"));
651 Mmsg(emsg, _("is waiting on Client %s"), jcr->client->name());
657 emsg = (char *) get_pool_memory(PM_FNAME);
659 Mmsg(emsg, _("is waiting on Storage %s"), jcr->wstore->name());
660 } else if (jcr->rstore) {
661 Mmsg(emsg, _("is waiting on Storage %s"), jcr->rstore->name());
663 Mmsg(emsg, _("is waiting on Storage"));
668 case JS_WaitStoreRes:
669 msg = _("is waiting on max Storage jobs");
671 case JS_WaitClientRes:
672 msg = _("is waiting on max Client jobs");
675 msg = _("is waiting on max Job jobs");
678 msg = _("is waiting on max total jobs");
680 case JS_WaitStartTime:
681 msg = _("is waiting for its start time");
683 case JS_WaitPriority:
684 msg = _("is waiting for higher priority jobs to finish");
686 case JS_DataCommitting:
687 msg = _("SD committing Data");
689 case JS_DataDespooling:
690 msg = _("SD despooling Data");
692 case JS_AttrDespooling:
693 msg = _("SD despooling Attributes");
695 case JS_AttrInserting:
696 msg = _("Dir inserting Attributes");
700 emsg = (char *)get_pool_memory(PM_FNAME);
701 Mmsg(emsg, _("is in unknown state %c"), jcr->JobStatus);
707 * Now report Storage daemon status code
709 switch (jcr->SDJobStatus) {
712 free_pool_memory(emsg);
715 msg = _("is waiting for a mount request");
719 free_pool_memory(emsg);
722 msg = _("is waiting for an appendable Volume");
726 emsg = (char *)get_pool_memory(PM_FNAME);
729 if (!jcr->client || !jcr->wstore) {
730 Mmsg(emsg, _("is waiting for Client to connect to Storage daemon"));
732 Mmsg(emsg, _("is waiting for Client %s to connect to Storage %s"),
733 jcr->client->name(), jcr->wstore->name());
737 case JS_DataCommitting:
738 msg = _("SD committing Data");
740 case JS_DataDespooling:
741 msg = _("SD despooling Data");
743 case JS_AttrDespooling:
744 msg = _("SD despooling Attributes");
746 case JS_AttrInserting:
747 msg = _("Dir inserting Attributes");
750 switch (jcr->JobType) {
753 bstrncpy(level, " ", sizeof(level));
756 bstrncpy(level, level_to_str(jcr->JobLevel), sizeof(level));
762 ua->send_msg(_("%6d\t%-6s\t%-20s\t%s\n"),
763 jcr->JobId, level, jcr->Job, msg);
765 ua->send_msg(_("%6d %-6s %-20s %s\n"),
766 jcr->JobId, level, jcr->Job, msg);
770 free_pool_memory(emsg);
775 if (!ua->api) ua->send_msg("====\n");
776 Dmsg0(200, "leave list_run_jobs()\n");
779 static void list_terminated_jobs(UAContext *ua)
781 char dt[MAX_TIME_LENGTH], b1[30], b2[30];
784 if (last_jobs->empty()) {
785 if (!ua->api) ua->send_msg(_("No Terminated Jobs.\n"));
788 lock_last_jobs_list();
789 struct s_last_job *je;
791 ua->send_msg(_("\nTerminated Jobs:\n"));
792 ua->send_msg(_(" JobId Level Files Bytes Status Finished Name \n"));
793 ua->send_msg(_("====================================================================\n"));
795 foreach_dlist(je, last_jobs) {
796 char JobName[MAX_NAME_LENGTH];
797 const char *termstat;
799 bstrncpy(JobName, je->Job, sizeof(JobName));
800 /* There are three periods after the Job name */
802 for (int i=0; i<3; i++) {
803 if ((p=strrchr(JobName, '.')) != NULL) {
808 if (!acl_access_ok(ua, Job_ACL, JobName)) {
812 bstrftime_nc(dt, sizeof(dt), je->end_time);
813 switch (je->JobType) {
816 bstrncpy(level, " ", sizeof(level));
819 bstrncpy(level, level_to_str(je->JobLevel), sizeof(level));
823 switch (je->JobStatus) {
825 termstat = _("Created");
828 case JS_ErrorTerminated:
829 termstat = _("Error");
832 termstat = _("Diffs");
835 termstat = _("Cancel");
841 termstat = _("Other");
845 ua->send_msg(_("%6d\t%-6s\t%8s\t%10s\t%-7s\t%-8s\t%s\n"),
848 edit_uint64_with_commas(je->JobFiles, b1),
849 edit_uint64_with_suffix(je->JobBytes, b2),
853 ua->send_msg(_("%6d %-6s %8s %10s %-7s %-8s %s\n"),
856 edit_uint64_with_commas(je->JobFiles, b1),
857 edit_uint64_with_suffix(je->JobBytes, b2),
862 if (!ua->api) ua->send_msg(_("\n"));
863 unlock_last_jobs_list();