]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_status.c
kes Prepare to add JS_Warnings termination status.
[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    utime_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          /* no need to set ScratchPoolId, since we use fnv_no_create_vol */
474          ok = find_next_volume_for_append(jcr, &mr, 1, fnv_no_create_vol, fnv_no_prune);
475       }
476       if (!ok) {
477          bstrncpy(mr.VolumeName, "*unknown*", sizeof(mr.VolumeName));
478       }
479    }
480    bstrftime_nc(dt, sizeof(dt), sp->runtime);
481    switch (sp->job->JobType) {
482    case JT_ADMIN:
483    case JT_RESTORE:
484       level_ptr = " ";
485       break;
486    default:
487       level_ptr = level_to_str(sp->level);
488       break;
489    }
490    if (ua->api) {
491       ua->send_msg(_("%-14s\t%-8s\t%3d\t%-18s\t%-18s\t%s\n"),
492          level_ptr, job_type_to_str(sp->job->JobType), sp->priority, dt,
493          sp->job->name(), mr.VolumeName);
494    } else {
495       ua->send_msg(_("%-14s %-8s %3d  %-18s %-18s %s\n"),
496          level_ptr, job_type_to_str(sp->job->JobType), sp->priority, dt,
497          sp->job->name(), mr.VolumeName);
498    }
499    if (close_db) {
500       db_close_database(jcr, jcr->db);
501    }
502    jcr->db = ua->db;                  /* restore ua db to jcr */
503    jcr->set_JobType(orig_jobtype);
504 }
505
506 /*
507  * Sort items by runtime, priority
508  */
509 static int my_compare(void *item1, void *item2)
510 {
511    sched_pkt *p1 = (sched_pkt *)item1;
512    sched_pkt *p2 = (sched_pkt *)item2;
513    if (p1->runtime < p2->runtime) {
514       return -1;
515    } else if (p1->runtime > p2->runtime) {
516       return 1;
517    }
518    if (p1->priority < p2->priority) {
519       return -1;
520    } else if (p1->priority > p2->priority) {
521       return 1;
522    }
523    return 0;
524 }
525
526 /*
527  * Find all jobs to be run in roughly the
528  *  next 24 hours.
529  */
530 static void list_scheduled_jobs(UAContext *ua)
531 {
532    utime_t runtime;
533    RUN *run;
534    JOB *job;
535    int level, num_jobs = 0;
536    int priority;
537    bool hdr_printed = false;
538    dlist sched;
539    sched_pkt *sp;
540    int days, i;
541
542    Dmsg0(200, "enter list_sched_jobs()\n");
543
544    days = 1;
545    i = find_arg_with_value(ua, NT_("days"));
546    if (i >= 0) {
547      days = atoi(ua->argv[i]);
548      if (((days < 0) || (days > 500)) && !ua->api) {
549        ua->send_msg(_("Ignoring invalid value for days. Max is 500.\n"));
550        days = 1;
551      }
552    }
553
554    /* Loop through all jobs */
555    LockRes();
556    foreach_res(job, R_JOB) {
557       if (!acl_access_ok(ua, Job_ACL, job->name()) || !job->enabled) {
558          continue;
559       }
560       for (run=NULL; (run = find_next_run(run, job, runtime, days)); ) {
561          USTORE store;
562          level = job->JobLevel;
563          if (run->level) {
564             level = run->level;
565          }
566          priority = job->Priority;
567          if (run->Priority) {
568             priority = run->Priority;
569          }
570          if (!hdr_printed) {
571             prt_runhdr(ua);
572             hdr_printed = true;
573          }
574          sp = (sched_pkt *)malloc(sizeof(sched_pkt));
575          sp->job = job;
576          sp->level = level;
577          sp->priority = priority;
578          sp->runtime = runtime;
579          sp->pool = run->pool;
580          get_job_storage(&store, job, run);
581          sp->store = store.store;
582          Dmsg3(250, "job=%s store=%s MediaType=%s\n", job->name(), sp->store->name(), sp->store->media_type);
583          sched.binary_insert_multiple(sp, my_compare);
584          num_jobs++;
585       }
586    } /* end for loop over resources */
587    UnlockRes();
588    foreach_dlist(sp, &sched) {
589       prt_runtime(ua, sp);
590    }
591    if (num_jobs == 0 && !ua->api) {
592       ua->send_msg(_("No Scheduled Jobs.\n"));
593    }
594    if (!ua->api) ua->send_msg("====\n");
595    Dmsg0(200, "Leave list_sched_jobs_runs()\n");
596 }
597
598 static void list_running_jobs(UAContext *ua)
599 {
600    JCR *jcr;
601    int njobs = 0;
602    const char *msg;
603    char *emsg;                        /* edited message */
604    char dt[MAX_TIME_LENGTH];
605    char level[10];
606    bool pool_mem = false;
607
608    Dmsg0(200, "enter list_run_jobs()\n");
609    if (!ua->api) ua->send_msg(_("\nRunning Jobs:\n"));
610    foreach_jcr(jcr) {
611       if (jcr->JobId == 0) {      /* this is us */
612          /* this is a console or other control job. We only show console
613           * jobs in the status output.
614           */
615          if (jcr->get_JobType() == JT_CONSOLE && !ua->api) {
616             bstrftime_nc(dt, sizeof(dt), jcr->start_time);
617             ua->send_msg(_("Console connected at %s\n"), dt);
618          }
619          continue;
620       }       
621       njobs++;
622    }
623    endeach_jcr(jcr);
624
625    if (njobs == 0) {
626       /* Note the following message is used in regress -- don't change */
627       if (!ua->api)  ua->send_msg(_("No Jobs running.\n====\n"));
628       Dmsg0(200, "leave list_run_jobs()\n");
629       return;
630    }
631    njobs = 0;
632    if (!ua->api) {
633       ua->send_msg(_(" JobId Level   Name                       Status\n"));
634       ua->send_msg(_("======================================================================\n"));
635    }
636    foreach_jcr(jcr) {
637       if (jcr->JobId == 0 || !acl_access_ok(ua, Job_ACL, jcr->job->name())) {
638          continue;
639       }
640       njobs++;
641       switch (jcr->JobStatus) {
642       case JS_Created:
643          msg = _("is waiting execution");
644          break;
645       case JS_Running:
646          msg = _("is running");
647          break;
648       case JS_Blocked:
649          msg = _("is blocked");
650          break;
651       case JS_Terminated:
652          msg = _("has terminated");
653          break;
654       case JS_Warnings:
655          msg = _("has terminated with warnings");
656          break;
657       case JS_ErrorTerminated:
658          msg = _("has erred");
659          break;
660       case JS_Error:
661          msg = _("has errors");
662          break;
663       case JS_FatalError:
664          msg = _("has a fatal error");
665          break;
666       case JS_Differences:
667          msg = _("has verify differences");
668          break;
669       case JS_Canceled:
670          msg = _("has been canceled");
671          break;
672       case JS_WaitFD:
673          emsg = (char *) get_pool_memory(PM_FNAME);
674          if (!jcr->client) {
675             Mmsg(emsg, _("is waiting on Client"));
676          } else {
677             Mmsg(emsg, _("is waiting on Client %s"), jcr->client->name());
678          }
679          pool_mem = true;
680          msg = emsg;
681          break;
682       case JS_WaitSD:
683          emsg = (char *) get_pool_memory(PM_FNAME);
684          if (jcr->wstore) {
685             Mmsg(emsg, _("is waiting on Storage %s"), jcr->wstore->name());
686          } else if (jcr->rstore) {
687             Mmsg(emsg, _("is waiting on Storage %s"), jcr->rstore->name());
688          } else {
689             Mmsg(emsg, _("is waiting on Storage"));
690          }
691          pool_mem = true;
692          msg = emsg;
693          break;
694       case JS_WaitStoreRes:
695          msg = _("is waiting on max Storage jobs");
696          break;
697       case JS_WaitClientRes:
698          msg = _("is waiting on max Client jobs");
699          break;
700       case JS_WaitJobRes:
701          msg = _("is waiting on max Job jobs");
702          break;
703       case JS_WaitMaxJobs:
704          msg = _("is waiting on max total jobs");
705          break;
706       case JS_WaitStartTime:
707          msg = _("is waiting for its start time");
708          break;
709       case JS_WaitPriority:
710          msg = _("is waiting for higher priority jobs to finish");
711          break;
712       case JS_DataCommitting:
713          msg = _("SD committing Data");
714          break;
715       case JS_DataDespooling:
716          msg = _("SD despooling Data");
717          break;
718       case JS_AttrDespooling:
719          msg = _("SD despooling Attributes");
720          break;
721       case JS_AttrInserting:
722          msg = _("Dir inserting Attributes");
723          break;
724
725       default:
726          emsg = (char *)get_pool_memory(PM_FNAME);
727          Mmsg(emsg, _("is in unknown state %c"), jcr->JobStatus);
728          pool_mem = true;
729          msg = emsg;
730          break;
731       }
732       /*
733        * Now report Storage daemon status code
734        */
735       switch (jcr->SDJobStatus) {
736       case JS_WaitMount:
737          if (pool_mem) {
738             free_pool_memory(emsg);
739             pool_mem = false;
740          }
741          msg = _("is waiting for a mount request");
742          break;
743       case JS_WaitMedia:
744          if (pool_mem) {
745             free_pool_memory(emsg);
746             pool_mem = false;
747          }
748          msg = _("is waiting for an appendable Volume");
749          break;
750       case JS_WaitFD:
751          if (!pool_mem) {
752             emsg = (char *)get_pool_memory(PM_FNAME);
753             pool_mem = true;
754          }
755          if (!jcr->client || !jcr->wstore) {
756             Mmsg(emsg, _("is waiting for Client to connect to Storage daemon"));
757          } else {
758             Mmsg(emsg, _("is waiting for Client %s to connect to Storage %s"),
759                  jcr->client->name(), jcr->wstore->name());
760         }
761         msg = emsg;
762         break;
763       case JS_DataCommitting:
764          msg = _("SD committing Data");
765          break;
766       case JS_DataDespooling:
767          msg = _("SD despooling Data");
768          break;
769       case JS_AttrDespooling:
770          msg = _("SD despooling Attributes");
771          break;
772       case JS_AttrInserting:
773          msg = _("Dir inserting Attributes");
774          break;
775       }
776       switch (jcr->get_JobType()) {
777       case JT_ADMIN:
778       case JT_RESTORE:
779          bstrncpy(level, "      ", sizeof(level));
780          break;
781       default:
782          bstrncpy(level, level_to_str(jcr->get_JobLevel()), sizeof(level));
783          level[7] = 0;
784          break;
785       }
786
787       if (ua->api) {
788          ua->send_msg(_("%6d\t%-6s\t%-20s\t%s\n"),
789             jcr->JobId, level, jcr->Job, msg);
790       } else {
791          ua->send_msg(_("%6d %-6s  %-20s %s\n"),
792             jcr->JobId, level, jcr->Job, msg);
793       }
794
795       if (pool_mem) {
796          free_pool_memory(emsg);
797          pool_mem = false;
798       }
799    }
800    endeach_jcr(jcr);
801    if (!ua->api) ua->send_msg("====\n");
802    Dmsg0(200, "leave list_run_jobs()\n");
803 }
804
805 static void list_terminated_jobs(UAContext *ua)
806 {
807    char dt[MAX_TIME_LENGTH], b1[30], b2[30];
808    char level[10];
809
810    if (last_jobs->empty()) {
811       if (!ua->api) ua->send_msg(_("No Terminated Jobs.\n"));
812       return;
813    }
814    lock_last_jobs_list();
815    struct s_last_job *je;
816    if (!ua->api) {
817       ua->send_msg(_("\nTerminated Jobs:\n"));
818       ua->send_msg(_(" JobId  Level    Files      Bytes   Status   Finished        Name \n"));
819       ua->send_msg(_("====================================================================\n"));
820    }
821    foreach_dlist(je, last_jobs) {
822       char JobName[MAX_NAME_LENGTH];
823       const char *termstat;
824
825       bstrncpy(JobName, je->Job, sizeof(JobName));
826       /* There are three periods after the Job name */
827       char *p;
828       for (int i=0; i<3; i++) {
829          if ((p=strrchr(JobName, '.')) != NULL) {
830             *p = 0;
831          }
832       }
833
834       if (!acl_access_ok(ua, Job_ACL, JobName)) {
835          continue;
836       }
837
838       bstrftime_nc(dt, sizeof(dt), je->end_time);
839       switch (je->JobType) {
840       case JT_ADMIN:
841       case JT_RESTORE:
842          bstrncpy(level, "    ", sizeof(level));
843          break;
844       default:
845          bstrncpy(level, level_to_str(je->JobLevel), sizeof(level));
846          level[4] = 0;
847          break;
848       }
849       switch (je->JobStatus) {
850       case JS_Created:
851          termstat = _("Created");
852          break;
853       case JS_FatalError:
854       case JS_ErrorTerminated:
855          termstat = _("Error");
856          break;
857       case JS_Differences:
858          termstat = _("Diffs");
859          break;
860       case JS_Canceled:
861          termstat = _("Cancel");
862          break;
863       case JS_Terminated:
864          termstat = _("OK");
865          break;
866       case JS_Warnings:
867          termstat = _("OK -- with warnings");
868          break;
869       default:
870          termstat = _("Other");
871          break;
872       }
873       if (ua->api) {
874          ua->send_msg(_("%6d\t%-6s\t%8s\t%10s\t%-7s\t%-8s\t%s\n"),
875             je->JobId,
876             level,
877             edit_uint64_with_commas(je->JobFiles, b1),
878             edit_uint64_with_suffix(je->JobBytes, b2),
879             termstat,
880             dt, JobName);
881       } else {
882          ua->send_msg(_("%6d  %-6s %8s %10s  %-7s  %-8s %s\n"),
883             je->JobId,
884             level,
885             edit_uint64_with_commas(je->JobFiles, b1),
886             edit_uint64_with_suffix(je->JobBytes, b2),
887             termstat,
888             dt, JobName);
889       }
890    }
891    if (!ua->api) ua->send_msg(_("\n"));
892    unlock_last_jobs_list();
893 }