]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_status.c
tweak copyright
[bacula/bacula] / bacula / src / dird / ua_status.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2001-2011 Free Software Foundation Europe e.V.
5
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
11    in the file LICENSE.
12
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.
17
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
21    02110-1301, USA.
22
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.
27 */
28 /*
29  *
30  *   Bacula Director -- User Agent Status Command
31  *
32  *     Kern Sibbald, August MMI
33  *
34  */
35
36
37 #include "bacula.h"
38 #include "dird.h"
39
40 extern void *start_heap;
41
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);
51
52 static char OKqstatus[]   = "1000 OK .status\n";
53 static char DotStatusJob[] = "JobId=%s JobStatus=%c JobErrors=%d\n";
54
55 /*
56  * .status command
57  */
58
59 bool dot_status_cmd(UAContext *ua, const char *cmd)
60 {
61    STORE *store;
62    CLIENT *client;
63    JCR* njcr = NULL;
64    s_last_job* job;
65    char ed1[50];
66
67    Dmsg2(20, "status=\"%s\" argc=%d\n", cmd, ua->argc);
68
69    if (ua->argc < 3) {
70       ua->send_msg("1900 Bad .status command, missing arguments.\n");
71       return false;
72    }
73
74    if (strcasecmp(ua->argk[1], "dir") == 0) {
75       if (strcasecmp(ua->argk[2], "current") == 0) {
76          ua->send_msg(OKqstatus, ua->argk[2]);
77          foreach_jcr(njcr) {
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);
81             }
82          }
83          endeach_jcr(njcr);
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);
91             }
92          }
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);
101       } else {
102          ua->send_msg("1900 Bad .status command, wrong argument.\n");
103          return false;
104       }
105    } else if (strcasecmp(ua->argk[1], "client") == 0) {
106       client = get_client_resource(ua);
107       if (client) {
108          Dmsg2(200, "Client=%s arg=%s\n", client->name(), NPRT(ua->argk[2]));
109          do_client_status(ua, client, ua->argk[2]);
110       }
111    } else if (strcasecmp(ua->argk[1], "storage") == 0) {
112       store = get_storage_resource(ua, false /*no default*/);
113       if (store) {
114          do_storage_status(ua, store, ua->argk[2]);
115       }
116    } else {
117       ua->send_msg("1900 Bad .status command, wrong argument.\n");
118       return false;
119    }
120
121    return true;
122 }
123
124 /* This is the *old* command handler, so we must return
125  *  1 or it closes the connection
126  */
127 int qstatus_cmd(UAContext *ua, const char *cmd)
128 {
129    dot_status_cmd(ua, cmd);
130    return 1;
131 }
132
133 /*
134  * status command
135  */
136 int status_cmd(UAContext *ua, const char *cmd)
137 {
138    STORE *store;
139    CLIENT *client;
140    int item, i;
141
142    Dmsg1(20, "status:%s:\n", cmd);
143
144    for (i=1; i<ua->argc; i++) {
145       if (strcasecmp(ua->argk[i], NT_("all")) == 0) {
146          do_all_status(ua);
147          return 1;
148       } else if (strcasecmp(ua->argk[i], NT_("dir")) == 0 ||
149                  strcasecmp(ua->argk[i], NT_("director")) == 0) {
150          do_director_status(ua);
151          return 1;
152       } else if (strcasecmp(ua->argk[i], NT_("client")) == 0) {
153          client = get_client_resource(ua);
154          if (client) {
155             do_client_status(ua, client, NULL);
156          }
157          return 1;
158       } else {
159          store = get_storage_resource(ua, false/*no default*/);
160          if (store) {
161             if (find_arg(ua, NT_("slots")) > 0) {
162                status_slots(ua, store);
163             } else {
164                do_storage_status(ua, store, NULL);
165             }
166          }
167          return 1;
168       }
169    }
170    /* If no args, ask for status type */
171    if (ua->argc == 1) {
172        char prmt[MAX_NAME_LENGTH];
173
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) {
181          return 1;
182       }
183       Dmsg1(20, "item=%d\n", item);
184       switch (item) {
185       case 0:                         /* Director */
186          do_director_status(ua);
187          break;
188       case 1:
189          store = select_storage_resource(ua);
190          if (store) {
191             do_storage_status(ua, store, NULL);
192          }
193          break;
194       case 2:
195          client = select_client_resource(ua);
196          if (client) {
197             do_client_status(ua, client, NULL);
198          }
199          break;
200       case 3:
201          do_all_status(ua);
202          break;
203       default:
204          break;
205       }
206    }
207    return 1;
208 }
209
210 static void do_all_status(UAContext *ua)
211 {
212    STORE *store, **unique_store;
213    CLIENT *client, **unique_client;
214    int i, j;
215    bool found;
216
217    do_director_status(ua);
218
219    /* Count Storage items */
220    LockRes();
221    i = 0;
222    foreach_res(store, R_STORAGE) {
223       i++;
224    }
225    unique_store = (STORE **) malloc(i * sizeof(STORE));
226    /* Find Unique Storage address/port */
227    i = 0;
228    foreach_res(store, R_STORAGE) {
229       found = false;
230       if (!acl_access_ok(ua, Storage_ACL, store->name())) {
231          continue;
232       }
233       for (j=0; j<i; j++) {
234          if (strcmp(unique_store[j]->address, store->address) == 0 &&
235              unique_store[j]->SDport == store->SDport) {
236             found = true;
237             break;
238          }
239       }
240       if (!found) {
241          unique_store[i++] = store;
242          Dmsg2(40, "Stuffing: %s:%d\n", store->address, store->SDport);
243       }
244    }
245    UnlockRes();
246
247    /* Call each unique Storage daemon */
248    for (j=0; j<i; j++) {
249       do_storage_status(ua, unique_store[j], NULL);
250    }
251    free(unique_store);
252
253    /* Count Client items */
254    LockRes();
255    i = 0;
256    foreach_res(client, R_CLIENT) {
257       i++;
258    }
259    unique_client = (CLIENT **)malloc(i * sizeof(CLIENT));
260    /* Find Unique Client address/port */
261    i = 0;
262    foreach_res(client, R_CLIENT) {
263       found = false;
264       if (!acl_access_ok(ua, Client_ACL, client->name())) {
265          continue;
266       }
267       for (j=0; j<i; j++) {
268          if (strcmp(unique_client[j]->address, client->address) == 0 &&
269              unique_client[j]->FDport == client->FDport) {
270             found = true;
271             break;
272          }
273       }
274       if (!found) {
275          unique_client[i++] = client;
276          Dmsg2(40, "Stuffing: %s:%d\n", client->address, client->FDport);
277       }
278    }
279    UnlockRes();
280
281    /* Call each unique File daemon */
282    for (j=0; j<i; j++) {
283       do_client_status(ua, unique_client[j], NULL);
284    }
285    free(unique_client);
286
287 }
288
289 void list_dir_status_header(UAContext *ua)
290 {
291    char dt[MAX_TIME_LENGTH];
292    char b1[35], b2[35], b3[35], b4[35], b5[35];
293
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 "
298                   "mode=%d,%d\n"), dt,
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));
306
307    /* TODO: use this function once for all daemons */
308    if (debug_level > 0 && bplugin_list->size() > 0) {
309       int len;
310       Plugin *plugin;
311       POOL_MEM msg(PM_FNAME);
312       pm_strcpy(msg, " Plugin: ");
313       foreach_alist(plugin, bplugin_list) {
314          len = pm_strcat(msg, plugin->file);
315          if (len > 80) {
316             pm_strcat(msg, "\n   ");
317          } else {
318             pm_strcat(msg, " ");
319          }
320       }
321       ua->send_msg("%s\n", msg.c_str());
322    }
323 }
324
325 static void do_director_status(UAContext *ua)
326 {
327    list_dir_status_header(ua);
328
329    /*
330     * List scheduled Jobs
331     */
332    list_scheduled_jobs(ua);
333
334    /*
335     * List running jobs
336     */
337    list_running_jobs(ua);
338
339    /*
340     * List terminated jobs
341     */
342    list_terminated_jobs(ua);
343    ua->send_msg("====\n");
344 }
345
346 static void do_storage_status(UAContext *ua, STORE *store, char *cmd)
347 {
348    BSOCK *sd;
349    USTORE lstore;
350
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"),
359          store->name());
360       if (ua->jcr->store_bsock) {
361          bnet_close(ua->jcr->store_bsock);
362          ua->jcr->store_bsock = NULL;
363       }
364       return;
365    }
366    Dmsg0(20, _("Connected to storage daemon\n"));
367    sd = ua->jcr->store_bsock;
368    if (cmd) {
369       sd->fsend(".status %s", cmd);
370    } else {
371       sd->fsend("status");
372    }
373    while (sd->recv() >= 0) {
374       ua->send_msg("%s", sd->msg);
375    }
376    sd->signal( BNET_TERMINATE);
377    sd->close();
378    ua->jcr->store_bsock = NULL;
379    return;
380 }
381
382 static void do_client_status(UAContext *ua, CLIENT *client, char *cmd)
383 {
384    BSOCK *fd;
385
386    /* Connect to File daemon */
387
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);
392    }
393    /* Create a new dummy SD auth key */
394    ua->jcr->sd_auth_key = bstrdup("dummy");
395
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"),
401          client->name());
402       if (ua->jcr->file_bsock) {
403          bnet_close(ua->jcr->file_bsock);
404          ua->jcr->file_bsock = NULL;
405       }
406       return;
407    }
408    Dmsg0(20, _("Connected to file daemon\n"));
409    fd = ua->jcr->file_bsock;
410    if (cmd) {
411       fd->fsend(".status %s", cmd);
412    } else {
413       fd->fsend("status");
414    }
415    while (fd->recv() >= 0) {
416       ua->send_msg("%s", fd->msg);
417    }
418    fd->signal(BNET_TERMINATE);
419    fd->close();
420    ua->jcr->file_bsock = NULL;
421
422    return;
423 }
424
425 static void prt_runhdr(UAContext *ua)
426 {
427    if (!ua->api) {
428       ua->send_msg(_("\nScheduled Jobs:\n"));
429       ua->send_msg(_("Level          Type     Pri  Scheduled          Name               Volume\n"));
430       ua->send_msg(_("===================================================================================\n"));
431    }
432 }
433
434 /* Scheduling packet */
435 struct sched_pkt {
436    dlink link;                        /* keep this as first item!!! */
437    JOB *job;
438    int level;
439    int priority;
440    utime_t runtime;
441    POOL *pool;
442    STORE *store;
443 };
444
445 static void prt_runtime(UAContext *ua, sched_pkt *sp)
446 {
447    char dt[MAX_TIME_LENGTH];
448    const char *level_ptr;
449    bool ok = false;
450    bool close_db = false;
451    JCR *jcr = ua->jcr;
452    MEDIA_DBR mr;
453    int orig_jobtype;
454
455    orig_jobtype = jcr->getJobType();
456    memset(&mr, 0, sizeof(mr));
457    if (sp->job->JobType == JT_BACKUP) {
458       jcr->db = NULL;
459       ok = complete_jcr_for_job(jcr, sp->job, sp->pool);
460       Dmsg1(250, "Using pool=%s\n", jcr->pool->name());
461       if (jcr->db) {
462          close_db = true;             /* new db opened, remember to close it */
463       }
464       if (ok) {
465          mr.PoolId = jcr->jr.PoolId;
466          mr.StorageId = sp->store->StorageId;
467          jcr->wstore = sp->store;
468          Dmsg0(250, "call find_next_volume_for_append\n");
469          /* no need to set ScratchPoolId, since we use fnv_no_create_vol */
470          ok = find_next_volume_for_append(jcr, &mr, 1, fnv_no_create_vol, fnv_no_prune);
471       }
472       if (!ok) {
473          bstrncpy(mr.VolumeName, "*unknown*", sizeof(mr.VolumeName));
474       }
475    }
476    bstrftime_nc(dt, sizeof(dt), sp->runtime);
477    switch (sp->job->JobType) {
478    case JT_ADMIN:
479    case JT_RESTORE:
480       level_ptr = " ";
481       break;
482    default:
483       level_ptr = level_to_str(sp->level);
484       break;
485    }
486    if (ua->api) {
487       ua->send_msg(_("%-14s\t%-8s\t%3d\t%-18s\t%-18s\t%s\n"),
488          level_ptr, job_type_to_str(sp->job->JobType), sp->priority, dt,
489          sp->job->name(), mr.VolumeName);
490    } else {
491       ua->send_msg(_("%-14s %-8s %3d  %-18s %-18s %s\n"),
492          level_ptr, job_type_to_str(sp->job->JobType), sp->priority, dt,
493          sp->job->name(), mr.VolumeName);
494    }
495    if (close_db) {
496       db_close_database(jcr, jcr->db);
497    }
498    jcr->db = ua->db;                  /* restore ua db to jcr */
499    jcr->setJobType(orig_jobtype);
500 }
501
502 /*
503  * Sort items by runtime, priority
504  */
505 static int my_compare(void *item1, void *item2)
506 {
507    sched_pkt *p1 = (sched_pkt *)item1;
508    sched_pkt *p2 = (sched_pkt *)item2;
509    if (p1->runtime < p2->runtime) {
510       return -1;
511    } else if (p1->runtime > p2->runtime) {
512       return 1;
513    }
514    if (p1->priority < p2->priority) {
515       return -1;
516    } else if (p1->priority > p2->priority) {
517       return 1;
518    }
519    return 0;
520 }
521
522 /*
523  * Find all jobs to be run in roughly the
524  *  next 24 hours.
525  */
526 static void list_scheduled_jobs(UAContext *ua)
527 {
528    utime_t runtime;
529    RUN *run;
530    JOB *job;
531    int level, num_jobs = 0;
532    int priority;
533    bool hdr_printed = false;
534    dlist sched;
535    sched_pkt *sp;
536    int days, i;
537
538    Dmsg0(200, "enter list_sched_jobs()\n");
539
540    days = 1;
541    i = find_arg_with_value(ua, NT_("days"));
542    if (i >= 0) {
543      days = atoi(ua->argv[i]);
544      if (((days < 0) || (days > 500)) && !ua->api) {
545        ua->send_msg(_("Ignoring invalid value for days. Max is 500.\n"));
546        days = 1;
547      }
548    }
549
550    /* Loop through all jobs */
551    LockRes();
552    foreach_res(job, R_JOB) {
553       if (!acl_access_ok(ua, Job_ACL, job->name()) || !job->enabled) {
554          continue;
555       }
556       for (run=NULL; (run = find_next_run(run, job, runtime, days)); ) {
557          USTORE store;
558          level = job->JobLevel;
559          if (run->level) {
560             level = run->level;
561          }
562          priority = job->Priority;
563          if (run->Priority) {
564             priority = run->Priority;
565          }
566          if (!hdr_printed) {
567             prt_runhdr(ua);
568             hdr_printed = true;
569          }
570          sp = (sched_pkt *)malloc(sizeof(sched_pkt));
571          sp->job = job;
572          sp->level = level;
573          sp->priority = priority;
574          sp->runtime = runtime;
575          sp->pool = run->pool;
576          get_job_storage(&store, job, run);
577          sp->store = store.store;
578          Dmsg3(250, "job=%s store=%s MediaType=%s\n", job->name(), sp->store->name(), sp->store->media_type);
579          sched.binary_insert_multiple(sp, my_compare);
580          num_jobs++;
581       }
582    } /* end for loop over resources */
583    UnlockRes();
584    foreach_dlist(sp, &sched) {
585       prt_runtime(ua, sp);
586    }
587    if (num_jobs == 0 && !ua->api) {
588       ua->send_msg(_("No Scheduled Jobs.\n"));
589    }
590    if (!ua->api) ua->send_msg("====\n");
591    Dmsg0(200, "Leave list_sched_jobs_runs()\n");
592 }
593
594 static void list_running_jobs(UAContext *ua)
595 {
596    JCR *jcr;
597    int njobs = 0;
598    const char *msg;
599    char *emsg;                        /* edited message */
600    char dt[MAX_TIME_LENGTH];
601    char level[10];
602    bool pool_mem = false;
603
604    Dmsg0(200, "enter list_run_jobs()\n");
605    if (!ua->api) ua->send_msg(_("\nRunning Jobs:\n"));
606    foreach_jcr(jcr) {
607       if (jcr->JobId == 0) {      /* this is us */
608          /* this is a console or other control job. We only show console
609           * jobs in the status output.
610           */
611          if (jcr->getJobType() == JT_CONSOLE && !ua->api) {
612             bstrftime_nc(dt, sizeof(dt), jcr->start_time);
613             ua->send_msg(_("Console connected at %s\n"), dt);
614          }
615          continue;
616       }       
617       njobs++;
618    }
619    endeach_jcr(jcr);
620
621    if (njobs == 0) {
622       /* Note the following message is used in regress -- don't change */
623       if (!ua->api)  ua->send_msg(_("No Jobs running.\n====\n"));
624       Dmsg0(200, "leave list_run_jobs()\n");
625       return;
626    }
627    njobs = 0;
628    if (!ua->api) {
629       ua->send_msg(_(" JobId Level   Name                       Status\n"));
630       ua->send_msg(_("======================================================================\n"));
631    }
632    foreach_jcr(jcr) {
633       if (jcr->JobId == 0 || !acl_access_ok(ua, Job_ACL, jcr->job->name())) {
634          continue;
635       }
636       njobs++;
637       switch (jcr->JobStatus) {
638       case JS_Created:
639          msg = _("is waiting execution");
640          break;
641       case JS_Running:
642          msg = _("is running");
643          break;
644       case JS_Blocked:
645          msg = _("is blocked");
646          break;
647       case JS_Terminated:
648          msg = _("has terminated");
649          break;
650       case JS_Warnings:
651          msg = _("has terminated with warnings");
652          break;
653       case JS_ErrorTerminated:
654          msg = _("has erred");
655          break;
656       case JS_Error:
657          msg = _("has errors");
658          break;
659       case JS_FatalError:
660          msg = _("has a fatal error");
661          break;
662       case JS_Differences:
663          msg = _("has verify differences");
664          break;
665       case JS_Canceled:
666          msg = _("has been canceled");
667          break;
668       case JS_WaitFD:
669          emsg = (char *) get_pool_memory(PM_FNAME);
670          if (!jcr->client) {
671             Mmsg(emsg, _("is waiting on Client"));
672          } else {
673             Mmsg(emsg, _("is waiting on Client %s"), jcr->client->name());
674          }
675          pool_mem = true;
676          msg = emsg;
677          break;
678       case JS_WaitSD:
679          emsg = (char *) get_pool_memory(PM_FNAME);
680          if (jcr->wstore) {
681             Mmsg(emsg, _("is waiting on Storage \"%s\""), jcr->wstore->name());
682          } else if (jcr->rstore) {
683             Mmsg(emsg, _("is waiting on Storage \"%s\""), jcr->rstore->name());
684          } else {
685             Mmsg(emsg, _("is waiting on Storage"));
686          }
687          pool_mem = true;
688          msg = emsg;
689          break;
690       case JS_WaitStoreRes:
691          msg = _("is waiting on max Storage jobs");
692          break;
693       case JS_WaitClientRes:
694          msg = _("is waiting on max Client jobs");
695          break;
696       case JS_WaitJobRes:
697          msg = _("is waiting on max Job jobs");
698          break;
699       case JS_WaitMaxJobs:
700          msg = _("is waiting on max total jobs");
701          break;
702       case JS_WaitStartTime:
703          msg = _("is waiting for its start time");
704          break;
705       case JS_WaitPriority:
706          msg = _("is waiting for higher priority jobs to finish");
707          break;
708       case JS_DataCommitting:
709          msg = _("SD committing Data");
710          break;
711       case JS_DataDespooling:
712          msg = _("SD despooling Data");
713          break;
714       case JS_AttrDespooling:
715          msg = _("SD despooling Attributes");
716          break;
717       case JS_AttrInserting:
718          msg = _("Dir inserting Attributes");
719          break;
720
721       default:
722          emsg = (char *)get_pool_memory(PM_FNAME);
723          Mmsg(emsg, _("is in unknown state %c"), jcr->JobStatus);
724          pool_mem = true;
725          msg = emsg;
726          break;
727       }
728       /*
729        * Now report Storage daemon status code
730        */
731       switch (jcr->SDJobStatus) {
732       case JS_WaitMount:
733          if (pool_mem) {
734             free_pool_memory(emsg);
735             pool_mem = false;
736          }
737          msg = _("is waiting for a mount request");
738          break;
739       case JS_WaitMedia:
740          if (pool_mem) {
741             free_pool_memory(emsg);
742             pool_mem = false;
743          }
744          msg = _("is waiting for an appendable Volume");
745          break;
746       case JS_WaitFD:
747          if (!pool_mem) {
748             emsg = (char *)get_pool_memory(PM_FNAME);
749             pool_mem = true;
750          }
751          if (!jcr->client || !jcr->wstore) {
752             Mmsg(emsg, _("is waiting for Client to connect to Storage daemon"));
753          } else {
754             Mmsg(emsg, _("is waiting for Client %s to connect to Storage %s"),
755                  jcr->client->name(), jcr->wstore->name());
756         }
757         msg = emsg;
758         break;
759       case JS_DataCommitting:
760          msg = _("SD committing Data");
761          break;
762       case JS_DataDespooling:
763          msg = _("SD despooling Data");
764          break;
765       case JS_AttrDespooling:
766          msg = _("SD despooling Attributes");
767          break;
768       case JS_AttrInserting:
769          msg = _("Dir inserting Attributes");
770          break;
771       }
772       switch (jcr->getJobType()) {
773       case JT_ADMIN:
774       case JT_RESTORE:
775          bstrncpy(level, "      ", sizeof(level));
776          break;
777       default:
778          bstrncpy(level, level_to_str(jcr->getJobLevel()), sizeof(level));
779          level[7] = 0;
780          break;
781       }
782
783       if (ua->api) {
784          bash_spaces(jcr->comment);
785          ua->send_msg(_("%6d\t%-6s\t%-20s\t%s\t%s\n"),
786                       jcr->JobId, level, jcr->Job, msg, jcr->comment);
787          unbash_spaces(jcr->comment);
788       } else {
789          ua->send_msg(_("%6d %-6s  %-20s %s\n"),
790             jcr->JobId, level, jcr->Job, msg);
791          /* Display comments if any */
792          if (*jcr->comment) {
793             ua->send_msg(_("               %-30s\n"), jcr->comment);
794          }
795       }
796
797       if (pool_mem) {
798          free_pool_memory(emsg);
799          pool_mem = false;
800       }
801    }
802    endeach_jcr(jcr);
803    if (!ua->api) ua->send_msg("====\n");
804    Dmsg0(200, "leave list_run_jobs()\n");
805 }
806
807 static void list_terminated_jobs(UAContext *ua)
808 {
809    char dt[MAX_TIME_LENGTH], b1[30], b2[30];
810    char level[10];
811
812    if (last_jobs->empty()) {
813       if (!ua->api) ua->send_msg(_("No Terminated Jobs.\n"));
814       return;
815    }
816    lock_last_jobs_list();
817    struct s_last_job *je;
818    if (!ua->api) {
819       ua->send_msg(_("\nTerminated Jobs:\n"));
820       ua->send_msg(_(" JobId  Level    Files      Bytes   Status   Finished        Name \n"));
821       ua->send_msg(_("====================================================================\n"));
822    }
823    foreach_dlist(je, last_jobs) {
824       char JobName[MAX_NAME_LENGTH];
825       const char *termstat;
826
827       bstrncpy(JobName, je->Job, sizeof(JobName));
828       /* There are three periods after the Job name */
829       char *p;
830       for (int i=0; i<3; i++) {
831          if ((p=strrchr(JobName, '.')) != NULL) {
832             *p = 0;
833          }
834       }
835
836       if (!acl_access_ok(ua, Job_ACL, JobName)) {
837          continue;
838       }
839
840       bstrftime_nc(dt, sizeof(dt), je->end_time);
841       switch (je->JobType) {
842       case JT_ADMIN:
843       case JT_RESTORE:
844          bstrncpy(level, "    ", sizeof(level));
845          break;
846       default:
847          bstrncpy(level, level_to_str(je->JobLevel), sizeof(level));
848          level[4] = 0;
849          break;
850       }
851       switch (je->JobStatus) {
852       case JS_Created:
853          termstat = _("Created");
854          break;
855       case JS_FatalError:
856       case JS_ErrorTerminated:
857          termstat = _("Error");
858          break;
859       case JS_Differences:
860          termstat = _("Diffs");
861          break;
862       case JS_Canceled:
863          termstat = _("Cancel");
864          break;
865       case JS_Terminated:
866          termstat = _("OK");
867          break;
868       case JS_Warnings:
869          termstat = _("OK -- with warnings");
870          break;
871       default:
872          termstat = _("Other");
873          break;
874       }
875       if (ua->api) {
876          ua->send_msg(_("%6d\t%-6s\t%8s\t%10s\t%-7s\t%-8s\t%s\n"),
877             je->JobId,
878             level,
879             edit_uint64_with_commas(je->JobFiles, b1),
880             edit_uint64_with_suffix(je->JobBytes, b2),
881             termstat,
882             dt, JobName);
883       } else {
884          ua->send_msg(_("%6d  %-6s %8s %10s  %-7s  %-8s %s\n"),
885             je->JobId,
886             level,
887             edit_uint64_with_commas(je->JobFiles, b1),
888             edit_uint64_with_suffix(je->JobBytes, b2),
889             termstat,
890             dt, JobName);
891       }
892    }
893    if (!ua->api) ua->send_msg(_("\n"));
894    unlock_last_jobs_list();
895 }