]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_status.c
ebl Add plugin list to status dir command output
[bacula/bacula] / bacula / src / dird / ua_status.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2001-2008 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 two of the GNU 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 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  *   Version $Id$
35  */
36
37
38 #include "bacula.h"
39 #include "dird.h"
40
41 extern void *start_heap;
42
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
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    if (num_jobs_run == 1) {
298       ua->send_msg(_("Daemon started %s, 1 Job run since started.\n"), dt);
299    }
300    else {
301       ua->send_msg(_("Daemon started %s, %d Jobs run since started.\n"),
302         dt, num_jobs_run);
303    }
304    ua->send_msg(_(" Heap: heap=%s smbytes=%s max_bytes=%s bufs=%s max_bufs=%s\n"),
305             edit_uint64_with_commas((char *)sbrk(0)-(char *)start_heap, b1),
306             edit_uint64_with_commas(sm_bytes, b2),
307             edit_uint64_with_commas(sm_max_bytes, b3),
308             edit_uint64_with_commas(sm_buffers, b4),
309             edit_uint64_with_commas(sm_max_buffers, b5));
310
311    /* TODO: use this function once for all daemons */
312    if (debug_level > 0 && plugin_list->size() > 0) {
313       int len;
314       Plugin *plugin;
315       POOL_MEM msg(PM_FNAME);
316       pm_strcpy(msg, " Plugin: ");
317       foreach_alist(plugin, plugin_list) {
318          len = pm_strcat(msg, plugin->file);
319          if (len > 80) {
320             pm_strcat(msg, "\n   ");
321          } else {
322             pm_strcat(msg, " ");
323          }
324       }
325       ua->send_msg("%s\n", msg.c_str());
326    }
327 }
328
329 static void do_director_status(UAContext *ua)
330 {
331    list_dir_status_header(ua);
332
333    /*
334     * List scheduled Jobs
335     */
336    list_scheduled_jobs(ua);
337
338    /*
339     * List running jobs
340     */
341    list_running_jobs(ua);
342
343    /*
344     * List terminated jobs
345     */
346    list_terminated_jobs(ua);
347    ua->send_msg("====\n");
348 }
349
350 static void do_storage_status(UAContext *ua, STORE *store, char *cmd)
351 {
352    BSOCK *sd;
353    USTORE lstore;
354
355    lstore.store = store;
356    pm_strcpy(lstore.store_source, _("unknown source"));
357    set_wstorage(ua->jcr, &lstore);
358    /* Try connecting for up to 15 seconds */
359    if (!ua->api) ua->send_msg(_("Connecting to Storage daemon %s at %s:%d\n"),
360       store->name(), store->address, store->SDport);
361    if (!connect_to_storage_daemon(ua->jcr, 1, 15, 0)) {
362       ua->send_msg(_("\nFailed to connect to Storage daemon %s.\n====\n"),
363          store->name());
364       if (ua->jcr->store_bsock) {
365          bnet_close(ua->jcr->store_bsock);
366          ua->jcr->store_bsock = NULL;
367       }
368       return;
369    }
370    Dmsg0(20, _("Connected to storage daemon\n"));
371    sd = ua->jcr->store_bsock;
372    if (cmd) {
373       sd->fsend(".status %s", cmd);
374    } else {
375       sd->fsend("status");
376    }
377    while (sd->recv() >= 0) {
378       ua->send_msg("%s", sd->msg);
379    }
380    sd->signal( BNET_TERMINATE);
381    sd->close();
382    ua->jcr->store_bsock = NULL;
383    return;
384 }
385
386 static void do_client_status(UAContext *ua, CLIENT *client, char *cmd)
387 {
388    BSOCK *fd;
389
390    /* Connect to File daemon */
391
392    ua->jcr->client = client;
393    /* Release any old dummy key */
394    if (ua->jcr->sd_auth_key) {
395       free(ua->jcr->sd_auth_key);
396    }
397    /* Create a new dummy SD auth key */
398    ua->jcr->sd_auth_key = bstrdup("dummy");
399
400    /* Try to connect for 15 seconds */
401    if (!ua->api) ua->send_msg(_("Connecting to Client %s at %s:%d\n"),
402       client->name(), client->address, client->FDport);
403    if (!connect_to_file_daemon(ua->jcr, 1, 15, 0)) {
404       ua->send_msg(_("Failed to connect to Client %s.\n====\n"),
405          client->name());
406       if (ua->jcr->file_bsock) {
407          bnet_close(ua->jcr->file_bsock);
408          ua->jcr->file_bsock = NULL;
409       }
410       return;
411    }
412    Dmsg0(20, _("Connected to file daemon\n"));
413    fd = ua->jcr->file_bsock;
414    if (cmd) {
415       fd->fsend(".status %s", cmd);
416    } else {
417       fd->fsend("status");
418    }
419    while (fd->recv() >= 0) {
420       ua->send_msg("%s", fd->msg);
421    }
422    fd->signal(BNET_TERMINATE);
423    fd->close();
424    ua->jcr->file_bsock = NULL;
425
426    return;
427 }
428
429 static void prt_runhdr(UAContext *ua)
430 {
431    if (!ua->api) {
432       ua->send_msg(_("\nScheduled Jobs:\n"));
433       ua->send_msg(_("Level          Type     Pri  Scheduled          Name               Volume\n"));
434       ua->send_msg(_("===================================================================================\n"));
435    }
436 }
437
438 /* Scheduling packet */
439 struct sched_pkt {
440    dlink link;                        /* keep this as first item!!! */
441    JOB *job;
442    int level;
443    int priority;
444    time_t runtime;
445    POOL *pool;
446    STORE *store;
447 };
448
449 static void prt_runtime(UAContext *ua, sched_pkt *sp)
450 {
451    char dt[MAX_TIME_LENGTH];
452    const char *level_ptr;
453    bool ok = false;
454    bool close_db = false;
455    JCR *jcr = ua->jcr;
456    MEDIA_DBR mr;
457    int orig_jobtype;
458
459    orig_jobtype = jcr->get_JobType();
460    memset(&mr, 0, sizeof(mr));
461    if (sp->job->JobType == JT_BACKUP) {
462       jcr->db = NULL;
463       ok = complete_jcr_for_job(jcr, sp->job, sp->pool);
464       Dmsg1(250, "Using pool=%s\n", jcr->pool->name());
465       if (jcr->db) {
466          close_db = true;             /* new db opened, remember to close it */
467       }
468       if (ok) {
469          mr.PoolId = jcr->jr.PoolId;
470          mr.StorageId = sp->store->StorageId;
471          jcr->wstore = sp->store;
472          Dmsg0(250, "call find_next_volume_for_append\n");
473          ok = find_next_volume_for_append(jcr, &mr, 1, fnv_no_create_vol, fnv_no_prune);
474       }
475       if (!ok) {
476          bstrncpy(mr.VolumeName, "*unknown*", sizeof(mr.VolumeName));
477       }
478    }
479    bstrftime_nc(dt, sizeof(dt), sp->runtime);
480    switch (sp->job->JobType) {
481    case JT_ADMIN:
482    case JT_RESTORE:
483       level_ptr = " ";
484       break;
485    default:
486       level_ptr = level_to_str(sp->level);
487       break;
488    }
489    if (ua->api) {
490       ua->send_msg(_("%-14s\t%-8s\t%3d\t%-18s\t%-18s\t%s\n"),
491          level_ptr, job_type_to_str(sp->job->JobType), sp->priority, dt,
492          sp->job->name(), mr.VolumeName);
493    } else {
494       ua->send_msg(_("%-14s %-8s %3d  %-18s %-18s %s\n"),
495          level_ptr, job_type_to_str(sp->job->JobType), sp->priority, dt,
496          sp->job->name(), mr.VolumeName);
497    }
498    if (close_db) {
499       db_close_database(jcr, jcr->db);
500    }
501    jcr->db = ua->db;                  /* restore ua db to jcr */
502    jcr->set_JobType(orig_jobtype);
503 }
504
505 /*
506  * Sort items by runtime, priority
507  */
508 static int my_compare(void *item1, void *item2)
509 {
510    sched_pkt *p1 = (sched_pkt *)item1;
511    sched_pkt *p2 = (sched_pkt *)item2;
512    if (p1->runtime < p2->runtime) {
513       return -1;
514    } else if (p1->runtime > p2->runtime) {
515       return 1;
516    }
517    if (p1->priority < p2->priority) {
518       return -1;
519    } else if (p1->priority > p2->priority) {
520       return 1;
521    }
522    return 0;
523 }
524
525 /*
526  * Find all jobs to be run in roughly the
527  *  next 24 hours.
528  */
529 static void list_scheduled_jobs(UAContext *ua)
530 {
531    time_t runtime;
532    RUN *run;
533    JOB *job;
534    int level, num_jobs = 0;
535    int priority;
536    bool hdr_printed = false;
537    dlist sched;
538    sched_pkt *sp;
539    int days, i;
540
541    Dmsg0(200, "enter list_sched_jobs()\n");
542
543    days = 1;
544    i = find_arg_with_value(ua, NT_("days"));
545    if (i >= 0) {
546      days = atoi(ua->argv[i]);
547      if (((days < 0) || (days > 500)) && !ua->api) {
548        ua->send_msg(_("Ignoring invalid value for days. Max is 500.\n"));
549        days = 1;
550      }
551    }
552
553    /* Loop through all jobs */
554    LockRes();
555    foreach_res(job, R_JOB) {
556       if (!acl_access_ok(ua, Job_ACL, job->name()) || !job->enabled) {
557          continue;
558       }
559       for (run=NULL; (run = find_next_run(run, job, runtime, days)); ) {
560          USTORE store;
561          level = job->JobLevel;
562          if (run->level) {
563             level = run->level;
564          }
565          priority = job->Priority;
566          if (run->Priority) {
567             priority = run->Priority;
568          }
569          if (!hdr_printed) {
570             prt_runhdr(ua);
571             hdr_printed = true;
572          }
573          sp = (sched_pkt *)malloc(sizeof(sched_pkt));
574          sp->job = job;
575          sp->level = level;
576          sp->priority = priority;
577          sp->runtime = runtime;
578          sp->pool = run->pool;
579          get_job_storage(&store, job, run);
580          sp->store = store.store;
581          Dmsg3(250, "job=%s store=%s MediaType=%s\n", job->name(), sp->store->name(), sp->store->media_type);
582          sched.binary_insert_multiple(sp, my_compare);
583          num_jobs++;
584       }
585    } /* end for loop over resources */
586    UnlockRes();
587    foreach_dlist(sp, &sched) {
588       prt_runtime(ua, sp);
589    }
590    if (num_jobs == 0 && !ua->api) {
591       ua->send_msg(_("No Scheduled Jobs.\n"));
592    }
593    if (!ua->api) ua->send_msg("====\n");
594    Dmsg0(200, "Leave list_sched_jobs_runs()\n");
595 }
596
597 static void list_running_jobs(UAContext *ua)
598 {
599    JCR *jcr;
600    int njobs = 0;
601    const char *msg;
602    char *emsg;                        /* edited message */
603    char dt[MAX_TIME_LENGTH];
604    char level[10];
605    bool pool_mem = false;
606
607    Dmsg0(200, "enter list_run_jobs()\n");
608    if (!ua->api) ua->send_msg(_("\nRunning Jobs:\n"));
609    foreach_jcr(jcr) {
610       if (jcr->JobId == 0) {      /* this is us */
611          /* this is a console or other control job. We only show console
612           * jobs in the status output.
613           */
614          if (jcr->get_JobType() == JT_CONSOLE && !ua->api) {
615             bstrftime_nc(dt, sizeof(dt), jcr->start_time);
616             ua->send_msg(_("Console connected at %s\n"), dt);
617          }
618          continue;
619       }       
620       njobs++;
621    }
622    endeach_jcr(jcr);
623
624    if (njobs == 0) {
625       /* Note the following message is used in regress -- don't change */
626       if (!ua->api)  ua->send_msg(_("No Jobs running.\n====\n"));
627       Dmsg0(200, "leave list_run_jobs()\n");
628       return;
629    }
630    njobs = 0;
631    if (!ua->api) {
632       ua->send_msg(_(" JobId Level   Name                       Status\n"));
633       ua->send_msg(_("======================================================================\n"));
634    }
635    foreach_jcr(jcr) {
636       if (jcr->JobId == 0 || !acl_access_ok(ua, Job_ACL, jcr->job->name())) {
637          continue;
638       }
639       njobs++;
640       switch (jcr->JobStatus) {
641       case JS_Created:
642          msg = _("is waiting execution");
643          break;
644       case JS_Running:
645          msg = _("is running");
646          break;
647       case JS_Blocked:
648          msg = _("is blocked");
649          break;
650       case JS_Terminated:
651          msg = _("has terminated");
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->get_JobType()) {
773       case JT_ADMIN:
774       case JT_RESTORE:
775          bstrncpy(level, "      ", sizeof(level));
776          break;
777       default:
778          bstrncpy(level, level_to_str(jcr->get_JobLevel()), sizeof(level));
779          level[7] = 0;
780          break;
781       }
782
783       if (ua->api) {
784          ua->send_msg(_("%6d\t%-6s\t%-20s\t%s\n"),
785             jcr->JobId, level, jcr->Job, msg);
786       } else {
787          ua->send_msg(_("%6d %-6s  %-20s %s\n"),
788             jcr->JobId, level, jcr->Job, msg);
789       }
790
791       if (pool_mem) {
792          free_pool_memory(emsg);
793          pool_mem = false;
794       }
795    }
796    endeach_jcr(jcr);
797    if (!ua->api) ua->send_msg("====\n");
798    Dmsg0(200, "leave list_run_jobs()\n");
799 }
800
801 static void list_terminated_jobs(UAContext *ua)
802 {
803    char dt[MAX_TIME_LENGTH], b1[30], b2[30];
804    char level[10];
805
806    if (last_jobs->empty()) {
807       if (!ua->api) ua->send_msg(_("No Terminated Jobs.\n"));
808       return;
809    }
810    lock_last_jobs_list();
811    struct s_last_job *je;
812    if (!ua->api) {
813       ua->send_msg(_("\nTerminated Jobs:\n"));
814       ua->send_msg(_(" JobId  Level    Files      Bytes   Status   Finished        Name \n"));
815       ua->send_msg(_("====================================================================\n"));
816    }
817    foreach_dlist(je, last_jobs) {
818       char JobName[MAX_NAME_LENGTH];
819       const char *termstat;
820
821       bstrncpy(JobName, je->Job, sizeof(JobName));
822       /* There are three periods after the Job name */
823       char *p;
824       for (int i=0; i<3; i++) {
825          if ((p=strrchr(JobName, '.')) != NULL) {
826             *p = 0;
827          }
828       }
829
830       if (!acl_access_ok(ua, Job_ACL, JobName)) {
831          continue;
832       }
833
834       bstrftime_nc(dt, sizeof(dt), je->end_time);
835       switch (je->JobType) {
836       case JT_ADMIN:
837       case JT_RESTORE:
838          bstrncpy(level, "    ", sizeof(level));
839          break;
840       default:
841          bstrncpy(level, level_to_str(je->JobLevel), sizeof(level));
842          level[4] = 0;
843          break;
844       }
845       switch (je->JobStatus) {
846       case JS_Created:
847          termstat = _("Created");
848          break;
849       case JS_FatalError:
850       case JS_ErrorTerminated:
851          termstat = _("Error");
852          break;
853       case JS_Differences:
854          termstat = _("Diffs");
855          break;
856       case JS_Canceled:
857          termstat = _("Cancel");
858          break;
859       case JS_Terminated:
860          termstat = _("OK");
861          break;
862       default:
863          termstat = _("Other");
864          break;
865       }
866       if (ua->api) {
867          ua->send_msg(_("%6d\t%-6s\t%8s\t%10s\t%-7s\t%-8s\t%s\n"),
868             je->JobId,
869             level,
870             edit_uint64_with_commas(je->JobFiles, b1),
871             edit_uint64_with_suffix(je->JobBytes, b2),
872             termstat,
873             dt, JobName);
874       } else {
875          ua->send_msg(_("%6d  %-6s %8s %10s  %-7s  %-8s %s\n"),
876             je->JobId,
877             level,
878             edit_uint64_with_commas(je->JobFiles, b1),
879             edit_uint64_with_suffix(je->JobBytes, b2),
880             termstat,
881             dt, JobName);
882       }
883    }
884    if (!ua->api) ua->send_msg(_("\n"));
885    unlock_last_jobs_list();
886 }