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(000, "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;
436 memset(&mr, 0, sizeof(mr));
437 if (sp->job->JobType == JT_BACKUP) {
439 ok = complete_jcr_for_job(jcr, sp->job, sp->pool);
440 Dmsg1(250, "Using pool=%s\n", jcr->pool->name());
442 close_db = true; /* new db opened, remember to close it */
445 mr.PoolId = jcr->jr.PoolId;
446 mr.StorageId = sp->store->StorageId;
447 jcr->wstore = sp->store;
448 Dmsg0(250, "call find_next_volume_for_append\n");
449 ok = find_next_volume_for_append(jcr, &mr, 1, fnv_no_create_vol, fnv_no_prune);
452 bstrncpy(mr.VolumeName, "*unknown*", sizeof(mr.VolumeName));
455 bstrftime_nc(dt, sizeof(dt), sp->runtime);
456 switch (sp->job->JobType) {
462 level_ptr = level_to_str(sp->level);
466 ua->send_msg(_("%-14s\t%-8s\t%3d\t%-18s\t%-18s\t%s\n"),
467 level_ptr, job_type_to_str(sp->job->JobType), sp->priority, dt,
468 sp->job->name(), mr.VolumeName);
470 ua->send_msg(_("%-14s %-8s %3d %-18s %-18s %s\n"),
471 level_ptr, job_type_to_str(sp->job->JobType), sp->priority, dt,
472 sp->job->name(), mr.VolumeName);
475 db_close_database(jcr, jcr->db);
477 jcr->db = ua->db; /* restore ua db to jcr */
481 * Sort items by runtime, priority
483 static int my_compare(void *item1, void *item2)
485 sched_pkt *p1 = (sched_pkt *)item1;
486 sched_pkt *p2 = (sched_pkt *)item2;
487 if (p1->runtime < p2->runtime) {
489 } else if (p1->runtime > p2->runtime) {
492 if (p1->priority < p2->priority) {
494 } else if (p1->priority > p2->priority) {
501 * Find all jobs to be run in roughly the
504 static void list_scheduled_jobs(UAContext *ua)
509 int level, num_jobs = 0;
511 bool hdr_printed = false;
516 Dmsg0(200, "enter list_sched_jobs()\n");
519 i = find_arg_with_value(ua, NT_("days"));
521 days = atoi(ua->argv[i]);
522 if ((days < 0) || (days > 500) && !ua->api) {
523 ua->send_msg(_("Ignoring invalid value for days. Max is 500.\n"));
528 /* Loop through all jobs */
530 foreach_res(job, R_JOB) {
531 if (!acl_access_ok(ua, Job_ACL, job->name()) || !job->enabled) {
534 for (run=NULL; (run = find_next_run(run, job, runtime, days)); ) {
536 level = job->JobLevel;
540 priority = job->Priority;
542 priority = run->Priority;
548 sp = (sched_pkt *)malloc(sizeof(sched_pkt));
551 sp->priority = priority;
552 sp->runtime = runtime;
553 sp->pool = run->pool;
554 get_job_storage(&store, job, run);
555 sp->store = store.store;
556 Dmsg3(250, "job=%s store=%s MediaType=%s\n", job->name(), sp->store->name(), sp->store->media_type);
557 sched.binary_insert_multiple(sp, my_compare);
560 } /* end for loop over resources */
562 foreach_dlist(sp, &sched) {
565 if (num_jobs == 0 && !ua->api) {
566 ua->send_msg(_("No Scheduled Jobs.\n"));
568 if (!ua->api) ua->send_msg("====\n");
569 Dmsg0(200, "Leave list_sched_jobs_runs()\n");
572 static void list_running_jobs(UAContext *ua)
577 char *emsg; /* edited message */
578 char dt[MAX_TIME_LENGTH];
580 bool pool_mem = false;
582 Dmsg0(200, "enter list_run_jobs()\n");
583 if (!ua->api) ua->send_msg(_("\nRunning Jobs:\n"));
585 if (jcr->JobId == 0) { /* this is us */
586 /* this is a console or other control job. We only show console
587 * jobs in the status output.
589 if (jcr->JobType == JT_CONSOLE && !ua->api) {
590 bstrftime_nc(dt, sizeof(dt), jcr->start_time);
591 ua->send_msg(_("Console connected at %s\n"), dt);
600 /* Note the following message is used in regress -- don't change */
601 if (!ua->api) ua->send_msg(_("No Jobs running.\n====\n"));
602 Dmsg0(200, "leave list_run_jobs()\n");
607 ua->send_msg(_(" JobId Level Name Status\n"));
608 ua->send_msg(_("======================================================================\n"));
611 if (jcr->JobId == 0 || !acl_access_ok(ua, Job_ACL, jcr->job->name())) {
615 switch (jcr->JobStatus) {
617 msg = _("is waiting execution");
620 msg = _("is running");
623 msg = _("is blocked");
626 msg = _("has terminated");
628 case JS_ErrorTerminated:
629 msg = _("has erred");
632 msg = _("has errors");
635 msg = _("has a fatal error");
638 msg = _("has verify differences");
641 msg = _("has been canceled");
644 emsg = (char *) get_pool_memory(PM_FNAME);
646 Mmsg(emsg, _("is waiting on Client"));
648 Mmsg(emsg, _("is waiting on Client %s"), jcr->client->name());
654 emsg = (char *) get_pool_memory(PM_FNAME);
656 Mmsg(emsg, _("is waiting on Storage %s"), jcr->wstore->name());
657 } else if (jcr->rstore) {
658 Mmsg(emsg, _("is waiting on Storage %s"), jcr->rstore->name());
660 Mmsg(emsg, _("is waiting on Storage"));
665 case JS_WaitStoreRes:
666 msg = _("is waiting on max Storage jobs");
668 case JS_WaitClientRes:
669 msg = _("is waiting on max Client jobs");
672 msg = _("is waiting on max Job jobs");
675 msg = _("is waiting on max total jobs");
677 case JS_WaitStartTime:
678 msg = _("is waiting for its start time");
680 case JS_WaitPriority:
681 msg = _("is waiting for higher priority jobs to finish");
683 case JS_DataCommitting:
684 msg = _("SD committing Data");
686 case JS_DataDespooling:
687 msg = _("SD despooling Data");
689 case JS_AttrDespooling:
690 msg = _("SD despooling Attributes");
692 case JS_AttrInserting:
693 msg = _("Dir inserting Attributes");
697 emsg = (char *)get_pool_memory(PM_FNAME);
698 Mmsg(emsg, _("is in unknown state %c"), jcr->JobStatus);
704 * Now report Storage daemon status code
706 switch (jcr->SDJobStatus) {
709 free_pool_memory(emsg);
712 msg = _("is waiting for a mount request");
716 free_pool_memory(emsg);
719 msg = _("is waiting for an appendable Volume");
723 emsg = (char *)get_pool_memory(PM_FNAME);
726 if (!jcr->client || !jcr->wstore) {
727 Mmsg(emsg, _("is waiting for Client to connect to Storage daemon"));
729 Mmsg(emsg, _("is waiting for Client %s to connect to Storage %s"),
730 jcr->client->name(), jcr->wstore->name());
734 case JS_DataCommitting:
735 msg = _("SD committing Data");
737 case JS_DataDespooling:
738 msg = _("SD despooling Data");
740 case JS_AttrDespooling:
741 msg = _("SD despooling Attributes");
743 case JS_AttrInserting:
744 msg = _("Dir inserting Attributes");
747 switch (jcr->JobType) {
750 bstrncpy(level, " ", sizeof(level));
753 bstrncpy(level, level_to_str(jcr->JobLevel), sizeof(level));
759 ua->send_msg(_("%6d\t%-6s\t%-20s\t%s\n"),
760 jcr->JobId, level, jcr->Job, msg);
762 ua->send_msg(_("%6d %-6s %-20s %s\n"),
763 jcr->JobId, level, jcr->Job, msg);
767 free_pool_memory(emsg);
772 if (!ua->api) ua->send_msg("====\n");
773 Dmsg0(200, "leave list_run_jobs()\n");
776 static void list_terminated_jobs(UAContext *ua)
778 char dt[MAX_TIME_LENGTH], b1[30], b2[30];
781 if (last_jobs->empty()) {
782 if (!ua->api) ua->send_msg(_("No Terminated Jobs.\n"));
785 lock_last_jobs_list();
786 struct s_last_job *je;
788 ua->send_msg(_("\nTerminated Jobs:\n"));
789 ua->send_msg(_(" JobId Level Files Bytes Status Finished Name \n"));
790 ua->send_msg(_("====================================================================\n"));
792 foreach_dlist(je, last_jobs) {
793 char JobName[MAX_NAME_LENGTH];
794 const char *termstat;
796 bstrncpy(JobName, je->Job, sizeof(JobName));
797 /* There are three periods after the Job name */
799 for (int i=0; i<3; i++) {
800 if ((p=strrchr(JobName, '.')) != NULL) {
805 if (!acl_access_ok(ua, Job_ACL, JobName)) {
809 bstrftime_nc(dt, sizeof(dt), je->end_time);
810 switch (je->JobType) {
813 bstrncpy(level, " ", sizeof(level));
816 bstrncpy(level, level_to_str(je->JobLevel), sizeof(level));
820 switch (je->JobStatus) {
822 termstat = _("Created");
825 case JS_ErrorTerminated:
826 termstat = _("Error");
829 termstat = _("Diffs");
832 termstat = _("Cancel");
838 termstat = _("Other");
842 ua->send_msg(_("%6d\t%-6s\t%8s\t%10s\t%-7s\t%-8s\t%s\n"),
845 edit_uint64_with_commas(je->JobFiles, b1),
846 edit_uint64_with_suffix(je->JobBytes, b2),
850 ua->send_msg(_("%6d %-6s %8s %10s %-7s %-8s %s\n"),
853 edit_uint64_with_commas(je->JobFiles, b1),
854 edit_uint64_with_suffix(je->JobBytes, b2),
859 if (!ua->api) ua->send_msg(_("\n"));
860 unlock_last_jobs_list();