2 Bacula® - The Network Backup Solution
4 Copyright (C) 2001-2012 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 three of the GNU Affero 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 Affero 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
40 extern void *start_heap;
42 static void list_scheduled_jobs(UAContext *ua);
43 static void list_running_jobs(UAContext *ua);
44 static void list_terminated_jobs(UAContext *ua);
45 static void do_storage_status(UAContext *ua, STORE *store, char *cmd);
46 static void do_client_status(UAContext *ua, CLIENT *client, char *cmd);
47 static void do_director_status(UAContext *ua);
48 static void do_all_status(UAContext *ua);
49 void status_slots(UAContext *ua, STORE *store);
50 void status_content(UAContext *ua, STORE *store);
52 static char OKqstatus[] = "1000 OK .status\n";
53 static char DotStatusJob[] = "JobId=%s JobStatus=%c JobErrors=%d\n";
59 bool dot_status_cmd(UAContext *ua, const char *cmd)
67 Dmsg2(20, "status=\"%s\" argc=%d\n", cmd, ua->argc);
70 ua->send_msg("1900 Bad .status command, missing arguments.\n");
74 if (strcasecmp(ua->argk[1], "dir") == 0) {
75 if (strcasecmp(ua->argk[2], "current") == 0) {
76 ua->send_msg(OKqstatus, ua->argk[2]);
78 if (njcr->JobId != 0 && acl_access_ok(ua, Job_ACL, njcr->job->name())) {
79 ua->send_msg(DotStatusJob, edit_int64(njcr->JobId, ed1),
80 njcr->JobStatus, njcr->JobErrors);
84 } else if (strcasecmp(ua->argk[2], "last") == 0) {
85 ua->send_msg(OKqstatus, ua->argk[2]);
86 if ((last_jobs) && (last_jobs->size() > 0)) {
87 job = (s_last_job*)last_jobs->last();
88 if (acl_access_ok(ua, Job_ACL, job->Job)) {
89 ua->send_msg(DotStatusJob, edit_int64(job->JobId, ed1),
90 job->JobStatus, job->Errors);
93 } else if (strcasecmp(ua->argk[2], "header") == 0) {
94 list_dir_status_header(ua);
95 } else if (strcasecmp(ua->argk[2], "scheduled") == 0) {
96 list_scheduled_jobs(ua);
97 } else if (strcasecmp(ua->argk[2], "running") == 0) {
98 list_running_jobs(ua);
99 } else if (strcasecmp(ua->argk[2], "terminated") == 0) {
100 list_terminated_jobs(ua);
102 ua->send_msg("1900 Bad .status command, wrong argument.\n");
105 } else if (strcasecmp(ua->argk[1], "client") == 0) {
106 client = get_client_resource(ua);
108 Dmsg2(200, "Client=%s arg=%s\n", client->name(), NPRT(ua->argk[2]));
109 do_client_status(ua, client, ua->argk[2]);
111 } else if (strcasecmp(ua->argk[1], "storage") == 0) {
112 store = get_storage_resource(ua, false /*no default*/);
114 do_storage_status(ua, store, ua->argk[2]);
117 ua->send_msg("1900 Bad .status command, wrong argument.\n");
124 /* This is the *old* command handler, so we must return
125 * 1 or it closes the connection
127 int qstatus_cmd(UAContext *ua, const char *cmd)
129 dot_status_cmd(ua, cmd);
136 int status_cmd(UAContext *ua, const char *cmd)
142 Dmsg1(20, "status:%s:\n", cmd);
144 for (i=1; i<ua->argc; i++) {
145 if (strcasecmp(ua->argk[i], NT_("all")) == 0) {
148 } else if (strcasecmp(ua->argk[i], NT_("dir")) == 0 ||
149 strcasecmp(ua->argk[i], NT_("director")) == 0) {
150 do_director_status(ua);
152 } else if (strcasecmp(ua->argk[i], NT_("client")) == 0) {
153 client = get_client_resource(ua);
155 do_client_status(ua, client, NULL);
159 store = get_storage_resource(ua, false/*no default*/);
161 if (find_arg(ua, NT_("slots")) > 0) {
162 status_slots(ua, store);
164 do_storage_status(ua, store, NULL);
170 /* If no args, ask for status type */
172 char prmt[MAX_NAME_LENGTH];
174 start_prompt(ua, _("Status available for:\n"));
175 add_prompt(ua, NT_("Director"));
176 add_prompt(ua, NT_("Storage"));
177 add_prompt(ua, NT_("Client"));
178 add_prompt(ua, NT_("All"));
179 Dmsg0(20, "do_prompt: select daemon\n");
180 if ((item=do_prompt(ua, "", _("Select daemon type for status"), prmt, sizeof(prmt))) < 0) {
183 Dmsg1(20, "item=%d\n", item);
185 case 0: /* Director */
186 do_director_status(ua);
189 store = select_storage_resource(ua);
191 do_storage_status(ua, store, NULL);
195 client = select_client_resource(ua);
197 do_client_status(ua, client, NULL);
210 static void do_all_status(UAContext *ua)
212 STORE *store, **unique_store;
213 CLIENT *client, **unique_client;
217 do_director_status(ua);
219 /* Count Storage items */
222 foreach_res(store, R_STORAGE) {
225 unique_store = (STORE **) malloc(i * sizeof(STORE));
226 /* Find Unique Storage address/port */
228 foreach_res(store, R_STORAGE) {
230 if (!acl_access_ok(ua, Storage_ACL, store->name())) {
233 for (j=0; j<i; j++) {
234 if (strcmp(unique_store[j]->address, store->address) == 0 &&
235 unique_store[j]->SDport == store->SDport) {
241 unique_store[i++] = store;
242 Dmsg2(40, "Stuffing: %s:%d\n", store->address, store->SDport);
247 /* Call each unique Storage daemon */
248 for (j=0; j<i; j++) {
249 do_storage_status(ua, unique_store[j], NULL);
253 /* Count Client items */
256 foreach_res(client, R_CLIENT) {
259 unique_client = (CLIENT **)malloc(i * sizeof(CLIENT));
260 /* Find Unique Client address/port */
262 foreach_res(client, R_CLIENT) {
264 if (!acl_access_ok(ua, Client_ACL, client->name())) {
267 for (j=0; j<i; j++) {
268 if (strcmp(unique_client[j]->address, client->address) == 0 &&
269 unique_client[j]->FDport == client->FDport) {
275 unique_client[i++] = client;
276 Dmsg2(40, "Stuffing: %s:%d\n", client->address, client->FDport);
281 /* Call each unique File daemon */
282 for (j=0; j<i; j++) {
283 do_client_status(ua, unique_client[j], NULL);
289 void list_dir_status_header(UAContext *ua)
291 char dt[MAX_TIME_LENGTH];
292 char b1[35], b2[35], b3[35], b4[35], b5[35];
294 ua->send_msg(_("%s Version: %s (%s) %s %s %s\n"), my_name, VERSION, BDATE,
295 HOST_OS, DISTNAME, DISTVER);
296 bstrftime_nc(dt, sizeof(dt), daemon_start_time);
297 ua->send_msg(_("Daemon started %s. Jobs: run=%d, running=%d "
299 num_jobs_run, job_count(), (int)DEVELOPER_MODE, (int)BEEF);
300 ua->send_msg(_(" Heap: heap=%s smbytes=%s max_bytes=%s bufs=%s max_bufs=%s\n"),
301 edit_uint64_with_commas((char *)sbrk(0)-(char *)start_heap, b1),
302 edit_uint64_with_commas(sm_bytes, b2),
303 edit_uint64_with_commas(sm_max_bytes, b3),
304 edit_uint64_with_commas(sm_buffers, b4),
305 edit_uint64_with_commas(sm_max_buffers, b5));
307 /* TODO: use this function once for all daemons */
308 if (debug_level > 0 && bplugin_list->size() > 0) {
311 POOL_MEM msg(PM_FNAME);
312 pm_strcpy(msg, " Plugin: ");
313 foreach_alist(plugin, bplugin_list) {
314 len = pm_strcat(msg, plugin->file);
316 pm_strcat(msg, "\n ");
321 ua->send_msg("%s\n", msg.c_str());
325 static void do_director_status(UAContext *ua)
327 list_dir_status_header(ua);
330 * List scheduled Jobs
332 list_scheduled_jobs(ua);
337 list_running_jobs(ua);
340 * List terminated jobs
342 list_terminated_jobs(ua);
343 ua->send_msg("====\n");
346 static void do_storage_status(UAContext *ua, STORE *store, char *cmd)
351 lstore.store = store;
352 pm_strcpy(lstore.store_source, _("unknown source"));
353 set_wstorage(ua->jcr, &lstore);
354 /* Try connecting for up to 15 seconds */
355 if (!ua->api) ua->send_msg(_("Connecting to Storage daemon %s at %s:%d\n"),
356 store->name(), store->address, store->SDport);
357 if (!connect_to_storage_daemon(ua->jcr, 1, 15, 0)) {
358 ua->send_msg(_("\nFailed to connect to Storage daemon %s.\n====\n"),
360 if (ua->jcr->store_bsock) {
361 bnet_close(ua->jcr->store_bsock);
362 ua->jcr->store_bsock = NULL;
366 Dmsg0(20, _("Connected to storage daemon\n"));
367 sd = ua->jcr->store_bsock;
369 sd->fsend(".status %s", cmd);
373 while (sd->recv() >= 0) {
374 ua->send_msg("%s", sd->msg);
376 sd->signal( BNET_TERMINATE);
378 ua->jcr->store_bsock = NULL;
382 static void do_client_status(UAContext *ua, CLIENT *client, char *cmd)
386 /* Connect to File daemon */
388 ua->jcr->client = client;
389 /* Release any old dummy key */
390 if (ua->jcr->sd_auth_key) {
391 free(ua->jcr->sd_auth_key);
393 /* Create a new dummy SD auth key */
394 ua->jcr->sd_auth_key = bstrdup("dummy");
396 /* Try to connect for 15 seconds */
397 if (!ua->api) ua->send_msg(_("Connecting to Client %s at %s:%d\n"),
398 client->name(), client->address, client->FDport);
399 if (!connect_to_file_daemon(ua->jcr, 1, 15, 0)) {
400 ua->send_msg(_("Failed to connect to Client %s.\n====\n"),
402 if (ua->jcr->file_bsock) {
403 bnet_close(ua->jcr->file_bsock);
404 ua->jcr->file_bsock = NULL;
408 Dmsg0(20, _("Connected to file daemon\n"));
409 fd = ua->jcr->file_bsock;
411 fd->fsend(".status %s", cmd);
415 while (fd->recv() >= 0) {
416 ua->send_msg("%s", fd->msg);
418 fd->signal(BNET_TERMINATE);
420 ua->jcr->file_bsock = NULL;
425 static void prt_runhdr(UAContext *ua)
428 ua->send_msg(_("\nScheduled Jobs:\n"));
429 ua->send_msg(_("Level Type Pri Scheduled Name Volume\n"));
430 ua->send_msg(_("===================================================================================\n"));
434 /* Scheduling packet */
436 dlink link; /* keep this as first item!!! */
445 static void prt_runtime(UAContext *ua, sched_pkt *sp)
447 char dt[MAX_TIME_LENGTH];
448 const char *level_ptr;
450 bool close_db = false;
455 orig_jobtype = jcr->getJobType();
456 if (sp->job->JobType == JT_BACKUP) {
458 ok = complete_jcr_for_job(jcr, sp->job, sp->pool);
459 Dmsg1(250, "Using pool=%s\n", jcr->pool->name());
461 close_db = true; /* new db opened, remember to close it */
464 mr.PoolId = jcr->jr.PoolId;
465 jcr->wstore = sp->store;
466 set_storageid_in_mr(jcr->wstore, &mr);
467 Dmsg0(250, "call find_next_volume_for_append\n");
468 /* no need to set ScratchPoolId, since we use fnv_no_create_vol */
469 ok = find_next_volume_for_append(jcr, &mr, 1, fnv_no_create_vol, fnv_no_prune);
472 bstrncpy(mr.VolumeName, "*unknown*", sizeof(mr.VolumeName));
475 bstrftime_nc(dt, sizeof(dt), sp->runtime);
476 switch (sp->job->JobType) {
482 level_ptr = level_to_str(sp->level);
486 ua->send_msg(_("%-14s\t%-8s\t%3d\t%-18s\t%-18s\t%s\n"),
487 level_ptr, job_type_to_str(sp->job->JobType), sp->priority, dt,
488 sp->job->name(), mr.VolumeName);
490 ua->send_msg(_("%-14s %-8s %3d %-18s %-18s %s\n"),
491 level_ptr, job_type_to_str(sp->job->JobType), sp->priority, dt,
492 sp->job->name(), mr.VolumeName);
495 db_close_database(jcr, jcr->db);
497 jcr->db = ua->db; /* restore ua db to jcr */
498 jcr->setJobType(orig_jobtype);
502 * Sort items by runtime, priority
504 static int my_compare(void *item1, void *item2)
506 sched_pkt *p1 = (sched_pkt *)item1;
507 sched_pkt *p2 = (sched_pkt *)item2;
508 if (p1->runtime < p2->runtime) {
510 } else if (p1->runtime > p2->runtime) {
513 if (p1->priority < p2->priority) {
515 } else if (p1->priority > p2->priority) {
522 * Find all jobs to be run in roughly the
525 static void list_scheduled_jobs(UAContext *ua)
530 int level, num_jobs = 0;
532 bool hdr_printed = false;
537 Dmsg0(200, "enter list_sched_jobs()\n");
540 i = find_arg_with_value(ua, NT_("days"));
542 days = atoi(ua->argv[i]);
543 if (((days < 0) || (days > 500)) && !ua->api) {
544 ua->send_msg(_("Ignoring invalid value for days. Max is 500.\n"));
549 /* Loop through all jobs */
551 foreach_res(job, R_JOB) {
552 if (!acl_access_ok(ua, Job_ACL, job->name()) || !job->enabled) {
555 for (run=NULL; (run = find_next_run(run, job, runtime, days)); ) {
557 level = job->JobLevel;
561 priority = job->Priority;
563 priority = run->Priority;
569 sp = (sched_pkt *)malloc(sizeof(sched_pkt));
572 sp->priority = priority;
573 sp->runtime = runtime;
574 sp->pool = run->pool;
575 get_job_storage(&store, job, run);
576 sp->store = store.store;
577 Dmsg3(250, "job=%s store=%s MediaType=%s\n", job->name(), sp->store->name(), sp->store->media_type);
578 sched.binary_insert_multiple(sp, my_compare);
581 } /* end for loop over resources */
583 foreach_dlist(sp, &sched) {
586 if (num_jobs == 0 && !ua->api) {
587 ua->send_msg(_("No Scheduled Jobs.\n"));
589 if (!ua->api) ua->send_msg("====\n");
590 Dmsg0(200, "Leave list_sched_jobs_runs()\n");
593 static void list_running_jobs(UAContext *ua)
598 char *emsg; /* edited message */
599 char dt[MAX_TIME_LENGTH];
601 bool pool_mem = false;
603 Dmsg0(200, "enter list_run_jobs()\n");
604 if (!ua->api) ua->send_msg(_("\nRunning Jobs:\n"));
606 if (jcr->JobId == 0) { /* this is us */
607 /* this is a console or other control job. We only show console
608 * jobs in the status output.
610 if (jcr->getJobType() == JT_CONSOLE && !ua->api) {
611 bstrftime_nc(dt, sizeof(dt), jcr->start_time);
612 ua->send_msg(_("Console connected at %s\n"), dt);
621 /* Note the following message is used in regress -- don't change */
622 if (!ua->api) ua->send_msg(_("No Jobs running.\n====\n"));
623 Dmsg0(200, "leave list_run_jobs()\n");
628 ua->send_msg(_(" JobId Level Name Status\n"));
629 ua->send_msg(_("======================================================================\n"));
632 if (jcr->JobId == 0 || !acl_access_ok(ua, Job_ACL, jcr->job->name())) {
636 switch (jcr->JobStatus) {
638 msg = _("is waiting execution");
641 msg = _("is running");
644 msg = _("is blocked");
647 msg = _("has terminated");
650 msg = _("has terminated with warnings");
652 case JS_ErrorTerminated:
653 msg = _("has erred");
656 msg = _("has errors");
659 msg = _("has a fatal error");
662 msg = _("has verify differences");
665 msg = _("has been canceled");
668 emsg = (char *) get_pool_memory(PM_FNAME);
670 Mmsg(emsg, _("is waiting on Client"));
672 Mmsg(emsg, _("is waiting on Client %s"), jcr->client->name());
678 emsg = (char *) get_pool_memory(PM_FNAME);
680 Mmsg(emsg, _("is waiting on Storage \"%s\""), jcr->wstore->name());
681 } else if (jcr->rstore) {
682 Mmsg(emsg, _("is waiting on Storage \"%s\""), jcr->rstore->name());
684 Mmsg(emsg, _("is waiting on Storage"));
689 case JS_WaitStoreRes:
690 msg = _("is waiting on max Storage jobs");
692 case JS_WaitClientRes:
693 msg = _("is waiting on max Client jobs");
696 msg = _("is waiting on max Job jobs");
699 msg = _("is waiting on max total jobs");
701 case JS_WaitStartTime:
702 msg = _("is waiting for its start time");
704 case JS_WaitPriority:
705 msg = _("is waiting for higher priority jobs to finish");
707 case JS_DataCommitting:
708 msg = _("SD committing Data");
710 case JS_DataDespooling:
711 msg = _("SD despooling Data");
713 case JS_AttrDespooling:
714 msg = _("SD despooling Attributes");
716 case JS_AttrInserting:
717 msg = _("Dir inserting Attributes");
721 emsg = (char *)get_pool_memory(PM_FNAME);
722 Mmsg(emsg, _("is in unknown state %c"), jcr->JobStatus);
728 * Now report Storage daemon status code
730 switch (jcr->SDJobStatus) {
733 free_pool_memory(emsg);
736 msg = _("is waiting for a mount request");
740 free_pool_memory(emsg);
743 msg = _("is waiting for an appendable Volume");
747 emsg = (char *)get_pool_memory(PM_FNAME);
750 if (!jcr->client || !jcr->wstore) {
751 Mmsg(emsg, _("is waiting for Client to connect to Storage daemon"));
753 Mmsg(emsg, _("is waiting for Client %s to connect to Storage %s"),
754 jcr->client->name(), jcr->wstore->name());
758 case JS_DataCommitting:
759 msg = _("SD committing Data");
761 case JS_DataDespooling:
762 msg = _("SD despooling Data");
764 case JS_AttrDespooling:
765 msg = _("SD despooling Attributes");
767 case JS_AttrInserting:
768 msg = _("Dir inserting Attributes");
771 switch (jcr->getJobType()) {
774 bstrncpy(level, " ", sizeof(level));
777 bstrncpy(level, level_to_str(jcr->getJobLevel()), sizeof(level));
783 bash_spaces(jcr->comment);
784 ua->send_msg(_("%6d\t%-6s\t%-20s\t%s\t%s\n"),
785 jcr->JobId, level, jcr->Job, msg, jcr->comment);
786 unbash_spaces(jcr->comment);
788 ua->send_msg(_("%6d %-6s %-20s %s\n"),
789 jcr->JobId, level, jcr->Job, msg);
790 /* Display comments if any */
792 ua->send_msg(_(" %-30s\n"), jcr->comment);
797 free_pool_memory(emsg);
802 if (!ua->api) ua->send_msg("====\n");
803 Dmsg0(200, "leave list_run_jobs()\n");
806 static void list_terminated_jobs(UAContext *ua)
808 char dt[MAX_TIME_LENGTH], b1[30], b2[30];
811 if (last_jobs->empty()) {
812 if (!ua->api) ua->send_msg(_("No Terminated Jobs.\n"));
815 lock_last_jobs_list();
816 struct s_last_job *je;
818 ua->send_msg(_("\nTerminated Jobs:\n"));
819 ua->send_msg(_(" JobId Level Files Bytes Status Finished Name \n"));
820 ua->send_msg(_("====================================================================\n"));
822 foreach_dlist(je, last_jobs) {
823 char JobName[MAX_NAME_LENGTH];
824 const char *termstat;
826 bstrncpy(JobName, je->Job, sizeof(JobName));
827 /* There are three periods after the Job name */
829 for (int i=0; i<3; i++) {
830 if ((p=strrchr(JobName, '.')) != NULL) {
835 if (!acl_access_ok(ua, Job_ACL, JobName)) {
839 bstrftime_nc(dt, sizeof(dt), je->end_time);
840 switch (je->JobType) {
843 bstrncpy(level, " ", sizeof(level));
846 bstrncpy(level, level_to_str(je->JobLevel), sizeof(level));
850 switch (je->JobStatus) {
852 termstat = _("Created");
855 case JS_ErrorTerminated:
856 termstat = _("Error");
859 termstat = _("Diffs");
862 termstat = _("Cancel");
868 termstat = _("OK -- with warnings");
871 termstat = _("Other");
875 ua->send_msg(_("%6d\t%-6s\t%8s\t%10s\t%-7s\t%-8s\t%s\n"),
878 edit_uint64_with_commas(je->JobFiles, b1),
879 edit_uint64_with_suffix(je->JobBytes, b2),
883 ua->send_msg(_("%6d %-6s %8s %10s %-7s %-8s %s\n"),
886 edit_uint64_with_commas(je->JobFiles, b1),
887 edit_uint64_with_suffix(je->JobBytes, b2),
892 if (!ua->api) ua->send_msg(_("\n"));
893 unlock_last_jobs_list();