2 Bacula® - The Network Backup Solution
4 Copyright (C) 2001-2007 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);
47 static void do_client_status(UAContext *ua, CLIENT *client);
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)
64 Dmsg1(20, "status:%s:\n", cmd);
66 if ((ua->argc != 3) || (strcasecmp(ua->argk[1], "dir"))) {
67 ua->send_msg("1900 Bad .status command, missing arguments.\n");
71 if (strcasecmp(ua->argk[2], "current") == 0) {
72 ua->send_msg(OKqstatus, ua->argk[2]);
74 if (njcr->JobId != 0 && acl_access_ok(ua, Job_ACL, njcr->job->name())) {
75 ua->send_msg(DotStatusJob, edit_int64(njcr->JobId, ed1),
76 njcr->JobStatus, njcr->JobErrors);
80 } else if (strcasecmp(ua->argk[2], "last") == 0) {
81 ua->send_msg(OKqstatus, ua->argk[2]);
82 if ((last_jobs) && (last_jobs->size() > 0)) {
83 job = (s_last_job*)last_jobs->last();
84 if (acl_access_ok(ua, Job_ACL, job->Job)) {
85 ua->send_msg(DotStatusJob, edit_int64(job->JobId, ed1),
86 job->JobStatus, job->Errors);
90 ua->send_msg("1900 Bad .status command, wrong argument.\n");
97 /* This is the *old* command handler, so we must return
98 * 1 or it closes the connection
100 int qstatus_cmd(UAContext *ua, const char *cmd)
102 dot_status_cmd(ua, cmd);
109 int status_cmd(UAContext *ua, const char *cmd)
115 Dmsg1(20, "status:%s:\n", cmd);
117 for (i=1; i<ua->argc; i++) {
118 if (strcasecmp(ua->argk[i], NT_("all")) == 0) {
121 } else if (strcasecmp(ua->argk[i], NT_("dir")) == 0 ||
122 strcasecmp(ua->argk[i], NT_("director")) == 0) {
123 do_director_status(ua);
125 } else if (strcasecmp(ua->argk[i], NT_("client")) == 0) {
126 client = get_client_resource(ua);
128 do_client_status(ua, client);
132 store = get_storage_resource(ua, false/*no default*/);
134 do_storage_status(ua, store);
139 /* If no args, ask for status type */
141 char prmt[MAX_NAME_LENGTH];
143 start_prompt(ua, _("Status available for:\n"));
144 add_prompt(ua, NT_("Director"));
145 add_prompt(ua, NT_("Storage"));
146 add_prompt(ua, NT_("Client"));
147 add_prompt(ua, NT_("All"));
148 Dmsg0(20, "do_prompt: select daemon\n");
149 if ((item=do_prompt(ua, "", _("Select daemon type for status"), prmt, sizeof(prmt))) < 0) {
152 Dmsg1(20, "item=%d\n", item);
154 case 0: /* Director */
155 do_director_status(ua);
158 store = select_storage_resource(ua);
160 do_storage_status(ua, store);
164 client = select_client_resource(ua);
166 do_client_status(ua, client);
179 static void do_all_status(UAContext *ua)
181 STORE *store, **unique_store;
182 CLIENT *client, **unique_client;
186 do_director_status(ua);
188 /* Count Storage items */
191 foreach_res(store, R_STORAGE) {
194 unique_store = (STORE **) malloc(i * sizeof(STORE));
195 /* Find Unique Storage address/port */
197 foreach_res(store, R_STORAGE) {
199 if (!acl_access_ok(ua, Storage_ACL, store->name())) {
202 for (j=0; j<i; j++) {
203 if (strcmp(unique_store[j]->address, store->address) == 0 &&
204 unique_store[j]->SDport == store->SDport) {
210 unique_store[i++] = store;
211 Dmsg2(40, "Stuffing: %s:%d\n", store->address, store->SDport);
216 /* Call each unique Storage daemon */
217 for (j=0; j<i; j++) {
218 do_storage_status(ua, unique_store[j]);
222 /* Count Client items */
225 foreach_res(client, R_CLIENT) {
228 unique_client = (CLIENT **)malloc(i * sizeof(CLIENT));
229 /* Find Unique Client address/port */
231 foreach_res(client, R_CLIENT) {
233 if (!acl_access_ok(ua, Client_ACL, client->name())) {
236 for (j=0; j<i; j++) {
237 if (strcmp(unique_client[j]->address, client->address) == 0 &&
238 unique_client[j]->FDport == client->FDport) {
244 unique_client[i++] = client;
245 Dmsg2(40, "Stuffing: %s:%d\n", client->address, client->FDport);
250 /* Call each unique File daemon */
251 for (j=0; j<i; j++) {
252 do_client_status(ua, unique_client[j]);
258 void list_dir_status_header(UAContext *ua)
260 char dt[MAX_TIME_LENGTH];
261 char b1[35], b2[35], b3[35], b4[35], b5[35];
263 ua->send_msg(_("%s Version: %s (%s) %s %s %s\n"), my_name, VERSION, BDATE,
264 HOST_OS, DISTNAME, DISTVER);
265 bstrftime_nc(dt, sizeof(dt), daemon_start_time);
266 if (num_jobs_run == 1) {
267 ua->send_msg(_("Daemon started %s, 1 Job run since started.\n"), dt);
270 ua->send_msg(_("Daemon started %s, %d Jobs run since started.\n"),
273 ua->send_msg(_(" Heap: heap=%s smbytes=%s max_bytes=%s bufs=%s max_bufs=%s\n"),
274 edit_uint64_with_commas((char *)sbrk(0)-(char *)start_heap, b1),
275 edit_uint64_with_commas(sm_bytes, b2),
276 edit_uint64_with_commas(sm_max_bytes, b3),
277 edit_uint64_with_commas(sm_buffers, b4),
278 edit_uint64_with_commas(sm_max_buffers, b5));
281 static void do_director_status(UAContext *ua)
283 list_dir_status_header(ua);
286 * List scheduled Jobs
288 list_scheduled_jobs(ua);
293 list_running_jobs(ua);
296 * List terminated jobs
298 list_terminated_jobs(ua);
299 ua->send_msg(_("====\n"));
302 static void do_storage_status(UAContext *ua, STORE *store)
307 lstore.store = store;
308 pm_strcpy(lstore.store_source, _("unknown source"));
309 set_wstorage(ua->jcr, &lstore);
310 /* Try connecting for up to 15 seconds */
311 ua->send_msg(_("Connecting to Storage daemon %s at %s:%d\n"),
312 store->name(), store->address, store->SDport);
313 if (!connect_to_storage_daemon(ua->jcr, 1, 15, 0)) {
314 ua->send_msg(_("\nFailed to connect to Storage daemon %s.\n====\n"),
316 if (ua->jcr->store_bsock) {
317 bnet_close(ua->jcr->store_bsock);
318 ua->jcr->store_bsock = NULL;
322 Dmsg0(20, _("Connected to storage daemon\n"));
323 sd = ua->jcr->store_bsock;
324 bnet_fsend(sd, "status");
325 while (bnet_recv(sd) >= 0) {
326 ua->send_msg("%s", sd->msg);
328 bnet_sig(sd, BNET_TERMINATE);
330 ua->jcr->store_bsock = NULL;
334 static void do_client_status(UAContext *ua, CLIENT *client)
338 /* Connect to File daemon */
340 ua->jcr->client = client;
341 /* Release any old dummy key */
342 if (ua->jcr->sd_auth_key) {
343 free(ua->jcr->sd_auth_key);
345 /* Create a new dummy SD auth key */
346 ua->jcr->sd_auth_key = bstrdup("dummy");
348 /* Try to connect for 15 seconds */
349 ua->send_msg(_("Connecting to Client %s at %s:%d\n"),
350 client->name(), client->address, client->FDport);
351 if (!connect_to_file_daemon(ua->jcr, 1, 15, 0)) {
352 ua->send_msg(_("Failed to connect to Client %s.\n====\n"),
354 if (ua->jcr->file_bsock) {
355 bnet_close(ua->jcr->file_bsock);
356 ua->jcr->file_bsock = NULL;
360 Dmsg0(20, _("Connected to file daemon\n"));
361 fd = ua->jcr->file_bsock;
362 bnet_fsend(fd, "status");
363 while (bnet_recv(fd) >= 0) {
364 ua->send_msg("%s", fd->msg);
366 bnet_sig(fd, BNET_TERMINATE);
368 ua->jcr->file_bsock = NULL;
373 static void prt_runhdr(UAContext *ua)
375 ua->send_msg(_("\nScheduled Jobs:\n"));
376 ua->send_msg(_("Level Type Pri Scheduled Name Volume\n"));
377 ua->send_msg(_("===================================================================================\n"));
380 /* Scheduling packet */
382 dlink link; /* keep this as first item!!! */
391 static void prt_runtime(UAContext *ua, sched_pkt *sp)
393 char dt[MAX_TIME_LENGTH];
394 const char *level_ptr;
396 bool close_db = false;
400 memset(&mr, 0, sizeof(mr));
401 if (sp->job->JobType == JT_BACKUP) {
403 ok = complete_jcr_for_job(jcr, sp->job, sp->pool);
404 Dmsg1(250, "Using pool=%s\n", jcr->pool->name());
406 close_db = true; /* new db opened, remember to close it */
409 mr.PoolId = jcr->jr.PoolId;
410 mr.StorageId = sp->store->StorageId;
411 jcr->wstore = sp->store;
412 Dmsg0(250, "call find_next_volume_for_append\n");
413 ok = find_next_volume_for_append(jcr, &mr, 1, fnv_no_create_vol, fnv_no_prune);
416 bstrncpy(mr.VolumeName, "*unknown*", sizeof(mr.VolumeName));
419 bstrftime_nc(dt, sizeof(dt), sp->runtime);
420 switch (sp->job->JobType) {
426 level_ptr = level_to_str(sp->level);
429 ua->send_msg(_("%-14s %-8s %3d %-18s %-18s %s\n"),
430 level_ptr, job_type_to_str(sp->job->JobType), sp->priority, dt,
431 sp->job->name(), mr.VolumeName);
433 db_close_database(jcr, jcr->db);
435 jcr->db = ua->db; /* restore ua db to jcr */
439 * Sort items by runtime, priority
441 static int my_compare(void *item1, void *item2)
443 sched_pkt *p1 = (sched_pkt *)item1;
444 sched_pkt *p2 = (sched_pkt *)item2;
445 if (p1->runtime < p2->runtime) {
447 } else if (p1->runtime > p2->runtime) {
450 if (p1->priority < p2->priority) {
452 } else if (p1->priority > p2->priority) {
459 * Find all jobs to be run in roughly the
462 static void list_scheduled_jobs(UAContext *ua)
467 int level, num_jobs = 0;
469 bool hdr_printed = false;
474 Dmsg0(200, "enter list_sched_jobs()\n");
477 i = find_arg_with_value(ua, NT_("days"));
479 days = atoi(ua->argv[i]);
480 if ((days < 0) || (days > 50)) {
481 ua->send_msg(_("Ignoring invalid value for days. Max is 50.\n"));
486 /* Loop through all jobs */
488 foreach_res(job, R_JOB) {
489 if (!acl_access_ok(ua, Job_ACL, job->name()) || !job->enabled) {
492 for (run=NULL; (run = find_next_run(run, job, runtime, days)); ) {
494 level = job->JobLevel;
498 priority = job->Priority;
500 priority = run->Priority;
506 sp = (sched_pkt *)malloc(sizeof(sched_pkt));
509 sp->priority = priority;
510 sp->runtime = runtime;
511 sp->pool = run->pool;
512 get_job_storage(&store, job, run);
513 sp->store = store.store;
514 Dmsg3(250, "job=%s store=%s MediaType=%s\n", job->name(), sp->store->name(), sp->store->media_type);
515 sched.binary_insert_multiple(sp, my_compare);
518 } /* end for loop over resources */
520 foreach_dlist(sp, &sched) {
524 ua->send_msg(_("No Scheduled Jobs.\n"));
526 ua->send_msg(_("====\n"));
527 Dmsg0(200, "Leave list_sched_jobs_runs()\n");
530 static void list_running_jobs(UAContext *ua)
535 char *emsg; /* edited message */
536 char dt[MAX_TIME_LENGTH];
538 bool pool_mem = false;
540 Dmsg0(200, "enter list_run_jobs()\n");
541 ua->send_msg(_("\nRunning Jobs:\n"));
543 if (jcr->JobId == 0) { /* this is us */
544 /* this is a console or other control job. We only show console
545 * jobs in the status output.
547 if (jcr->JobType == JT_CONSOLE) {
548 bstrftime_nc(dt, sizeof(dt), jcr->start_time);
549 ua->send_msg(_("Console connected at %s\n"), dt);
558 /* Note the following message is used in regress -- don't change */
559 ua->send_msg(_("No Jobs running.\n====\n"));
560 Dmsg0(200, "leave list_run_jobs()\n");
564 ua->send_msg(_(" JobId Level Name Status\n"));
565 ua->send_msg(_("======================================================================\n"));
567 if (jcr->JobId == 0 || !acl_access_ok(ua, Job_ACL, jcr->job->name())) {
571 switch (jcr->JobStatus) {
573 msg = _("is waiting execution");
576 msg = _("is running");
579 msg = _("is blocked");
582 msg = _("has terminated");
584 case JS_ErrorTerminated:
585 msg = _("has erred");
588 msg = _("has errors");
591 msg = _("has a fatal error");
594 msg = _("has verify differences");
597 msg = _("has been canceled");
600 emsg = (char *) get_pool_memory(PM_FNAME);
602 Mmsg(emsg, _("is waiting on Client"));
604 Mmsg(emsg, _("is waiting on Client %s"), jcr->client->name());
610 emsg = (char *) get_pool_memory(PM_FNAME);
612 Mmsg(emsg, _("is waiting on Storage %s"), jcr->wstore->name());
613 } else if (jcr->rstore) {
614 Mmsg(emsg, _("is waiting on Storage %s"), jcr->rstore->name());
616 Mmsg(emsg, _("is waiting on Storage"));
621 case JS_WaitStoreRes:
622 msg = _("is waiting on max Storage jobs");
624 case JS_WaitClientRes:
625 msg = _("is waiting on max Client jobs");
628 msg = _("is waiting on max Job jobs");
631 msg = _("is waiting on max total jobs");
633 case JS_WaitStartTime:
634 msg = _("is waiting for its start time");
636 case JS_WaitPriority:
637 msg = _("is waiting for higher priority jobs to finish");
641 emsg = (char *)get_pool_memory(PM_FNAME);
642 Mmsg(emsg, _("is in unknown state %c"), jcr->JobStatus);
648 * Now report Storage daemon status code
650 switch (jcr->SDJobStatus) {
653 free_pool_memory(emsg);
656 msg = _("is waiting for a mount request");
660 free_pool_memory(emsg);
663 msg = _("is waiting for an appendable Volume");
667 emsg = (char *)get_pool_memory(PM_FNAME);
670 if (!jcr->client || !jcr->wstore) {
671 Mmsg(emsg, _("is waiting for Client to connect to Storage daemon"));
673 Mmsg(emsg, _("is waiting for Client %s to connect to Storage %s"),
674 jcr->client->name(), jcr->wstore->name());
679 switch (jcr->JobType) {
682 bstrncpy(level, " ", sizeof(level));
685 bstrncpy(level, level_to_str(jcr->JobLevel), sizeof(level));
690 ua->send_msg(_("%6d %-6s %-20s %s\n"),
697 free_pool_memory(emsg);
702 ua->send_msg(_("====\n"));
703 Dmsg0(200, "leave list_run_jobs()\n");
706 static void list_terminated_jobs(UAContext *ua)
708 char dt[MAX_TIME_LENGTH], b1[30], b2[30];
711 if (last_jobs->empty()) {
712 ua->send_msg(_("No Terminated Jobs.\n"));
715 lock_last_jobs_list();
716 struct s_last_job *je;
717 ua->send_msg(_("\nTerminated Jobs:\n"));
718 ua->send_msg(_(" JobId Level Files Bytes Status Finished Name \n"));
719 ua->send_msg(_("====================================================================\n"));
720 foreach_dlist(je, last_jobs) {
721 char JobName[MAX_NAME_LENGTH];
722 const char *termstat;
724 bstrncpy(JobName, je->Job, sizeof(JobName));
725 /* There are three periods after the Job name */
727 for (int i=0; i<3; i++) {
728 if ((p=strrchr(JobName, '.')) != NULL) {
733 if (!acl_access_ok(ua, Job_ACL, JobName)) {
737 bstrftime_nc(dt, sizeof(dt), je->end_time);
738 switch (je->JobType) {
741 bstrncpy(level, " ", sizeof(level));
744 bstrncpy(level, level_to_str(je->JobLevel), sizeof(level));
748 switch (je->JobStatus) {
750 termstat = _("Created");
753 case JS_ErrorTerminated:
754 termstat = _("Error");
757 termstat = _("Diffs");
760 termstat = _("Cancel");
766 termstat = _("Other");
769 ua->send_msg(_("%6d %-6s %8s %10s %-7s %-8s %s\n"),
772 edit_uint64_with_commas(je->JobFiles, b1),
773 edit_uint64_with_suffix(je->JobBytes, b2),
777 ua->send_msg(_("\n"));
778 unlock_last_jobs_list();