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