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 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 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);
50 void status_slots(UAContext *ua, STORE *store);
51 void status_content(UAContext *ua, STORE *store);
53 static char OKqstatus[] = "1000 OK .status\n";
54 static char DotStatusJob[] = "JobId=%s JobStatus=%c JobErrors=%d\n";
60 bool dot_status_cmd(UAContext *ua, const char *cmd)
68 Dmsg2(20, "status=\"%s\" argc=%d\n", cmd, ua->argc);
71 ua->send_msg("1900 Bad .status command, missing arguments.\n");
75 if (strcasecmp(ua->argk[1], "dir") == 0) {
76 if (strcasecmp(ua->argk[2], "current") == 0) {
77 ua->send_msg(OKqstatus, ua->argk[2]);
79 if (njcr->JobId != 0 && acl_access_ok(ua, Job_ACL, njcr->job->name())) {
80 ua->send_msg(DotStatusJob, edit_int64(njcr->JobId, ed1),
81 njcr->JobStatus, njcr->JobErrors);
85 } else if (strcasecmp(ua->argk[2], "last") == 0) {
86 ua->send_msg(OKqstatus, ua->argk[2]);
87 if ((last_jobs) && (last_jobs->size() > 0)) {
88 job = (s_last_job*)last_jobs->last();
89 if (acl_access_ok(ua, Job_ACL, job->Job)) {
90 ua->send_msg(DotStatusJob, edit_int64(job->JobId, ed1),
91 job->JobStatus, job->Errors);
94 } else if (strcasecmp(ua->argk[2], "header") == 0) {
95 list_dir_status_header(ua);
96 } else if (strcasecmp(ua->argk[2], "scheduled") == 0) {
97 list_scheduled_jobs(ua);
98 } else if (strcasecmp(ua->argk[2], "running") == 0) {
99 list_running_jobs(ua);
100 } else if (strcasecmp(ua->argk[2], "terminated") == 0) {
101 list_terminated_jobs(ua);
103 ua->send_msg("1900 Bad .status command, wrong argument.\n");
106 } else if (strcasecmp(ua->argk[1], "client") == 0) {
107 client = get_client_resource(ua);
109 Dmsg2(200, "Client=%s arg=%s\n", client->name(), NPRT(ua->argk[2]));
110 do_client_status(ua, client, ua->argk[2]);
112 } else if (strcasecmp(ua->argk[1], "storage") == 0) {
113 store = get_storage_resource(ua, false /*no default*/);
115 do_storage_status(ua, store, ua->argk[2]);
118 ua->send_msg("1900 Bad .status command, wrong argument.\n");
125 /* This is the *old* command handler, so we must return
126 * 1 or it closes the connection
128 int qstatus_cmd(UAContext *ua, const char *cmd)
130 dot_status_cmd(ua, cmd);
137 int status_cmd(UAContext *ua, const char *cmd)
143 Dmsg1(20, "status:%s:\n", cmd);
145 for (i=1; i<ua->argc; i++) {
146 if (strcasecmp(ua->argk[i], NT_("all")) == 0) {
149 } else if (strcasecmp(ua->argk[i], NT_("dir")) == 0 ||
150 strcasecmp(ua->argk[i], NT_("director")) == 0) {
151 do_director_status(ua);
153 } else if (strcasecmp(ua->argk[i], NT_("client")) == 0) {
154 client = get_client_resource(ua);
156 do_client_status(ua, client, NULL);
160 store = get_storage_resource(ua, false/*no default*/);
162 if (find_arg(ua, NT_("slots")) > 0) {
163 status_slots(ua, store);
165 do_storage_status(ua, store, NULL);
171 /* If no args, ask for status type */
173 char prmt[MAX_NAME_LENGTH];
175 start_prompt(ua, _("Status available for:\n"));
176 add_prompt(ua, NT_("Director"));
177 add_prompt(ua, NT_("Storage"));
178 add_prompt(ua, NT_("Client"));
179 add_prompt(ua, NT_("All"));
180 Dmsg0(20, "do_prompt: select daemon\n");
181 if ((item=do_prompt(ua, "", _("Select daemon type for status"), prmt, sizeof(prmt))) < 0) {
184 Dmsg1(20, "item=%d\n", item);
186 case 0: /* Director */
187 do_director_status(ua);
190 store = select_storage_resource(ua);
192 do_storage_status(ua, store, NULL);
196 client = select_client_resource(ua);
198 do_client_status(ua, client, NULL);
211 static void do_all_status(UAContext *ua)
213 STORE *store, **unique_store;
214 CLIENT *client, **unique_client;
218 do_director_status(ua);
220 /* Count Storage items */
223 foreach_res(store, R_STORAGE) {
226 unique_store = (STORE **) malloc(i * sizeof(STORE));
227 /* Find Unique Storage address/port */
229 foreach_res(store, R_STORAGE) {
231 if (!acl_access_ok(ua, Storage_ACL, store->name())) {
234 for (j=0; j<i; j++) {
235 if (strcmp(unique_store[j]->address, store->address) == 0 &&
236 unique_store[j]->SDport == store->SDport) {
242 unique_store[i++] = store;
243 Dmsg2(40, "Stuffing: %s:%d\n", store->address, store->SDport);
248 /* Call each unique Storage daemon */
249 for (j=0; j<i; j++) {
250 do_storage_status(ua, unique_store[j], NULL);
254 /* Count Client items */
257 foreach_res(client, R_CLIENT) {
260 unique_client = (CLIENT **)malloc(i * sizeof(CLIENT));
261 /* Find Unique Client address/port */
263 foreach_res(client, R_CLIENT) {
265 if (!acl_access_ok(ua, Client_ACL, client->name())) {
268 for (j=0; j<i; j++) {
269 if (strcmp(unique_client[j]->address, client->address) == 0 &&
270 unique_client[j]->FDport == client->FDport) {
276 unique_client[i++] = client;
277 Dmsg2(40, "Stuffing: %s:%d\n", client->address, client->FDport);
282 /* Call each unique File daemon */
283 for (j=0; j<i; j++) {
284 do_client_status(ua, unique_client[j], NULL);
290 void list_dir_status_header(UAContext *ua)
292 char dt[MAX_TIME_LENGTH];
293 char b1[35], b2[35], b3[35], b4[35], b5[35];
295 ua->send_msg(_("%s Version: %s (%s) %s %s %s\n"), my_name, VERSION, BDATE,
296 HOST_OS, DISTNAME, DISTVER);
297 bstrftime_nc(dt, sizeof(dt), daemon_start_time);
298 if (num_jobs_run == 1) {
299 ua->send_msg(_("Daemon started %s, 1 Job run since started.\n"), dt);
302 ua->send_msg(_("Daemon started %s, %d Jobs run since started.\n"),
305 ua->send_msg(_(" Heap: heap=%s smbytes=%s max_bytes=%s bufs=%s max_bufs=%s\n"),
306 edit_uint64_with_commas((char *)sbrk(0)-(char *)start_heap, b1),
307 edit_uint64_with_commas(sm_bytes, b2),
308 edit_uint64_with_commas(sm_max_bytes, b3),
309 edit_uint64_with_commas(sm_buffers, b4),
310 edit_uint64_with_commas(sm_max_buffers, b5));
312 /* TODO: use this function once for all daemons */
313 if (debug_level > 0 && plugin_list->size() > 0) {
316 POOL_MEM msg(PM_FNAME);
317 pm_strcpy(msg, " Plugin: ");
318 foreach_alist(plugin, plugin_list) {
319 len = pm_strcat(msg, plugin->file);
321 pm_strcat(msg, "\n ");
326 ua->send_msg("%s\n", msg.c_str());
330 static void do_director_status(UAContext *ua)
332 list_dir_status_header(ua);
335 * List scheduled Jobs
337 list_scheduled_jobs(ua);
342 list_running_jobs(ua);
345 * List terminated jobs
347 list_terminated_jobs(ua);
348 ua->send_msg("====\n");
351 static void do_storage_status(UAContext *ua, STORE *store, char *cmd)
356 lstore.store = store;
357 pm_strcpy(lstore.store_source, _("unknown source"));
358 set_wstorage(ua->jcr, &lstore);
359 /* Try connecting for up to 15 seconds */
360 if (!ua->api) ua->send_msg(_("Connecting to Storage daemon %s at %s:%d\n"),
361 store->name(), store->address, store->SDport);
362 if (!connect_to_storage_daemon(ua->jcr, 1, 15, 0)) {
363 ua->send_msg(_("\nFailed to connect to Storage daemon %s.\n====\n"),
365 if (ua->jcr->store_bsock) {
366 bnet_close(ua->jcr->store_bsock);
367 ua->jcr->store_bsock = NULL;
371 Dmsg0(20, _("Connected to storage daemon\n"));
372 sd = ua->jcr->store_bsock;
374 sd->fsend(".status %s", cmd);
378 while (sd->recv() >= 0) {
379 ua->send_msg("%s", sd->msg);
381 sd->signal( BNET_TERMINATE);
383 ua->jcr->store_bsock = NULL;
387 static void do_client_status(UAContext *ua, CLIENT *client, char *cmd)
391 /* Connect to File daemon */
393 ua->jcr->client = client;
394 /* Release any old dummy key */
395 if (ua->jcr->sd_auth_key) {
396 free(ua->jcr->sd_auth_key);
398 /* Create a new dummy SD auth key */
399 ua->jcr->sd_auth_key = bstrdup("dummy");
401 /* Try to connect for 15 seconds */
402 if (!ua->api) ua->send_msg(_("Connecting to Client %s at %s:%d\n"),
403 client->name(), client->address, client->FDport);
404 if (!connect_to_file_daemon(ua->jcr, 1, 15, 0)) {
405 ua->send_msg(_("Failed to connect to Client %s.\n====\n"),
407 if (ua->jcr->file_bsock) {
408 bnet_close(ua->jcr->file_bsock);
409 ua->jcr->file_bsock = NULL;
413 Dmsg0(20, _("Connected to file daemon\n"));
414 fd = ua->jcr->file_bsock;
416 fd->fsend(".status %s", cmd);
420 while (fd->recv() >= 0) {
421 ua->send_msg("%s", fd->msg);
423 fd->signal(BNET_TERMINATE);
425 ua->jcr->file_bsock = NULL;
430 static void prt_runhdr(UAContext *ua)
433 ua->send_msg(_("\nScheduled Jobs:\n"));
434 ua->send_msg(_("Level Type Pri Scheduled Name Volume\n"));
435 ua->send_msg(_("===================================================================================\n"));
439 /* Scheduling packet */
441 dlink link; /* keep this as first item!!! */
450 static void prt_runtime(UAContext *ua, sched_pkt *sp)
452 char dt[MAX_TIME_LENGTH];
453 const char *level_ptr;
455 bool close_db = false;
460 orig_jobtype = jcr->getJobType();
461 memset(&mr, 0, sizeof(mr));
462 if (sp->job->JobType == JT_BACKUP) {
464 ok = complete_jcr_for_job(jcr, sp->job, sp->pool);
465 Dmsg1(250, "Using pool=%s\n", jcr->pool->name());
467 close_db = true; /* new db opened, remember to close it */
470 mr.PoolId = jcr->jr.PoolId;
471 mr.StorageId = sp->store->StorageId;
472 jcr->wstore = sp->store;
473 Dmsg0(250, "call find_next_volume_for_append\n");
474 /* no need to set ScratchPoolId, since we use fnv_no_create_vol */
475 ok = find_next_volume_for_append(jcr, &mr, 1, fnv_no_create_vol, fnv_no_prune);
478 bstrncpy(mr.VolumeName, "*unknown*", sizeof(mr.VolumeName));
481 bstrftime_nc(dt, sizeof(dt), sp->runtime);
482 switch (sp->job->JobType) {
488 level_ptr = level_to_str(sp->level);
492 ua->send_msg(_("%-14s\t%-8s\t%3d\t%-18s\t%-18s\t%s\n"),
493 level_ptr, job_type_to_str(sp->job->JobType), sp->priority, dt,
494 sp->job->name(), mr.VolumeName);
496 ua->send_msg(_("%-14s %-8s %3d %-18s %-18s %s\n"),
497 level_ptr, job_type_to_str(sp->job->JobType), sp->priority, dt,
498 sp->job->name(), mr.VolumeName);
501 db_close_database(jcr, jcr->db);
503 jcr->db = ua->db; /* restore ua db to jcr */
504 jcr->set_JobType(orig_jobtype);
508 * Sort items by runtime, priority
510 static int my_compare(void *item1, void *item2)
512 sched_pkt *p1 = (sched_pkt *)item1;
513 sched_pkt *p2 = (sched_pkt *)item2;
514 if (p1->runtime < p2->runtime) {
516 } else if (p1->runtime > p2->runtime) {
519 if (p1->priority < p2->priority) {
521 } else if (p1->priority > p2->priority) {
528 * Find all jobs to be run in roughly the
531 static void list_scheduled_jobs(UAContext *ua)
536 int level, num_jobs = 0;
538 bool hdr_printed = false;
543 Dmsg0(200, "enter list_sched_jobs()\n");
546 i = find_arg_with_value(ua, NT_("days"));
548 days = atoi(ua->argv[i]);
549 if (((days < 0) || (days > 500)) && !ua->api) {
550 ua->send_msg(_("Ignoring invalid value for days. Max is 500.\n"));
555 /* Loop through all jobs */
557 foreach_res(job, R_JOB) {
558 if (!acl_access_ok(ua, Job_ACL, job->name()) || !job->enabled) {
561 for (run=NULL; (run = find_next_run(run, job, runtime, days)); ) {
563 level = job->JobLevel;
567 priority = job->Priority;
569 priority = run->Priority;
575 sp = (sched_pkt *)malloc(sizeof(sched_pkt));
578 sp->priority = priority;
579 sp->runtime = runtime;
580 sp->pool = run->pool;
581 get_job_storage(&store, job, run);
582 sp->store = store.store;
583 Dmsg3(250, "job=%s store=%s MediaType=%s\n", job->name(), sp->store->name(), sp->store->media_type);
584 sched.binary_insert_multiple(sp, my_compare);
587 } /* end for loop over resources */
589 foreach_dlist(sp, &sched) {
592 if (num_jobs == 0 && !ua->api) {
593 ua->send_msg(_("No Scheduled Jobs.\n"));
595 if (!ua->api) ua->send_msg("====\n");
596 Dmsg0(200, "Leave list_sched_jobs_runs()\n");
599 static void list_running_jobs(UAContext *ua)
604 char *emsg; /* edited message */
605 char dt[MAX_TIME_LENGTH];
607 bool pool_mem = false;
609 Dmsg0(200, "enter list_run_jobs()\n");
610 if (!ua->api) ua->send_msg(_("\nRunning Jobs:\n"));
612 if (jcr->JobId == 0) { /* this is us */
613 /* this is a console or other control job. We only show console
614 * jobs in the status output.
616 if (jcr->getJobType() == JT_CONSOLE && !ua->api) {
617 bstrftime_nc(dt, sizeof(dt), jcr->start_time);
618 ua->send_msg(_("Console connected at %s\n"), dt);
627 /* Note the following message is used in regress -- don't change */
628 if (!ua->api) ua->send_msg(_("No Jobs running.\n====\n"));
629 Dmsg0(200, "leave list_run_jobs()\n");
634 ua->send_msg(_(" JobId Level Name Status\n"));
635 ua->send_msg(_("======================================================================\n"));
638 if (jcr->JobId == 0 || !acl_access_ok(ua, Job_ACL, jcr->job->name())) {
642 switch (jcr->JobStatus) {
644 msg = _("is waiting execution");
647 msg = _("is running");
650 msg = _("is blocked");
653 msg = _("has terminated");
656 msg = _("has terminated with warnings");
658 case JS_ErrorTerminated:
659 msg = _("has erred");
662 msg = _("has errors");
665 msg = _("has a fatal error");
668 msg = _("has verify differences");
671 msg = _("has been canceled");
674 emsg = (char *) get_pool_memory(PM_FNAME);
676 Mmsg(emsg, _("is waiting on Client"));
678 Mmsg(emsg, _("is waiting on Client %s"), jcr->client->name());
684 emsg = (char *) get_pool_memory(PM_FNAME);
686 Mmsg(emsg, _("is waiting on Storage %s"), jcr->wstore->name());
687 } else if (jcr->rstore) {
688 Mmsg(emsg, _("is waiting on Storage %s"), jcr->rstore->name());
690 Mmsg(emsg, _("is waiting on Storage"));
695 case JS_WaitStoreRes:
696 msg = _("is waiting on max Storage jobs");
698 case JS_WaitClientRes:
699 msg = _("is waiting on max Client jobs");
702 msg = _("is waiting on max Job jobs");
705 msg = _("is waiting on max total jobs");
707 case JS_WaitStartTime:
708 msg = _("is waiting for its start time");
710 case JS_WaitPriority:
711 msg = _("is waiting for higher priority jobs to finish");
713 case JS_DataCommitting:
714 msg = _("SD committing Data");
716 case JS_DataDespooling:
717 msg = _("SD despooling Data");
719 case JS_AttrDespooling:
720 msg = _("SD despooling Attributes");
722 case JS_AttrInserting:
723 msg = _("Dir inserting Attributes");
727 emsg = (char *)get_pool_memory(PM_FNAME);
728 Mmsg(emsg, _("is in unknown state %c"), jcr->JobStatus);
734 * Now report Storage daemon status code
736 switch (jcr->SDJobStatus) {
739 free_pool_memory(emsg);
742 msg = _("is waiting for a mount request");
746 free_pool_memory(emsg);
749 msg = _("is waiting for an appendable Volume");
753 emsg = (char *)get_pool_memory(PM_FNAME);
756 if (!jcr->client || !jcr->wstore) {
757 Mmsg(emsg, _("is waiting for Client to connect to Storage daemon"));
759 Mmsg(emsg, _("is waiting for Client %s to connect to Storage %s"),
760 jcr->client->name(), jcr->wstore->name());
764 case JS_DataCommitting:
765 msg = _("SD committing Data");
767 case JS_DataDespooling:
768 msg = _("SD despooling Data");
770 case JS_AttrDespooling:
771 msg = _("SD despooling Attributes");
773 case JS_AttrInserting:
774 msg = _("Dir inserting Attributes");
777 switch (jcr->getJobType()) {
780 bstrncpy(level, " ", sizeof(level));
783 bstrncpy(level, level_to_str(jcr->getJobLevel()), sizeof(level));
789 bash_spaces(jcr->comment);
790 ua->send_msg(_("%6d\t%-6s\t%-20s\t%s\t%s\n"),
791 jcr->JobId, level, jcr->Job, msg, jcr->comment);
792 unbash_spaces(jcr->comment);
794 ua->send_msg(_("%6d %-6s %-20s %s\n"),
795 jcr->JobId, level, jcr->Job, msg);
796 /* Display comments if any */
798 ua->send_msg(_(" %-30s\n"), jcr->comment);
803 free_pool_memory(emsg);
808 if (!ua->api) ua->send_msg("====\n");
809 Dmsg0(200, "leave list_run_jobs()\n");
812 static void list_terminated_jobs(UAContext *ua)
814 char dt[MAX_TIME_LENGTH], b1[30], b2[30];
817 if (last_jobs->empty()) {
818 if (!ua->api) ua->send_msg(_("No Terminated Jobs.\n"));
821 lock_last_jobs_list();
822 struct s_last_job *je;
824 ua->send_msg(_("\nTerminated Jobs:\n"));
825 ua->send_msg(_(" JobId Level Files Bytes Status Finished Name \n"));
826 ua->send_msg(_("====================================================================\n"));
828 foreach_dlist(je, last_jobs) {
829 char JobName[MAX_NAME_LENGTH];
830 const char *termstat;
832 bstrncpy(JobName, je->Job, sizeof(JobName));
833 /* There are three periods after the Job name */
835 for (int i=0; i<3; i++) {
836 if ((p=strrchr(JobName, '.')) != NULL) {
841 if (!acl_access_ok(ua, Job_ACL, JobName)) {
845 bstrftime_nc(dt, sizeof(dt), je->end_time);
846 switch (je->JobType) {
849 bstrncpy(level, " ", sizeof(level));
852 bstrncpy(level, level_to_str(je->JobLevel), sizeof(level));
856 switch (je->JobStatus) {
858 termstat = _("Created");
861 case JS_ErrorTerminated:
862 termstat = _("Error");
865 termstat = _("Diffs");
868 termstat = _("Cancel");
874 termstat = _("OK -- with warnings");
877 termstat = _("Other");
881 ua->send_msg(_("%6d\t%-6s\t%8s\t%10s\t%-7s\t%-8s\t%s\n"),
884 edit_uint64_with_commas(je->JobFiles, b1),
885 edit_uint64_with_suffix(je->JobBytes, b2),
889 ua->send_msg(_("%6d %-6s %8s %10s %-7s %-8s %s\n"),
892 edit_uint64_with_commas(je->JobFiles, b1),
893 edit_uint64_with_suffix(je->JobBytes, b2),
898 if (!ua->api) ua->send_msg(_("\n"));
899 unlock_last_jobs_list();