]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_status.c
ebl Add new ScratchPool directive to Pool. Thanks to Graham
[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_ErrorTerminated:
655          msg = _("has erred");
656          break;
657       case JS_Error:
658          msg = _("has errors");
659          break;
660       case JS_FatalError:
661          msg = _("has a fatal error");
662          break;
663       case JS_Differences:
664          msg = _("has verify differences");
665          break;
666       case JS_Canceled:
667          msg = _("has been canceled");
668          break;
669       case JS_WaitFD:
670          emsg = (char *) get_pool_memory(PM_FNAME);
671          if (!jcr->client) {
672             Mmsg(emsg, _("is waiting on Client"));
673          } else {
674             Mmsg(emsg, _("is waiting on Client %s"), jcr->client->name());
675          }
676          pool_mem = true;
677          msg = emsg;
678          break;
679       case JS_WaitSD:
680          emsg = (char *) get_pool_memory(PM_FNAME);
681          if (jcr->wstore) {
682             Mmsg(emsg, _("is waiting on Storage %s"), jcr->wstore->name());
683          } else if (jcr->rstore) {
684             Mmsg(emsg, _("is waiting on Storage %s"), jcr->rstore->name());
685          } else {
686             Mmsg(emsg, _("is waiting on Storage"));
687          }
688          pool_mem = true;
689          msg = emsg;
690          break;
691       case JS_WaitStoreRes:
692          msg = _("is waiting on max Storage jobs");
693          break;
694       case JS_WaitClientRes:
695          msg = _("is waiting on max Client jobs");
696          break;
697       case JS_WaitJobRes:
698          msg = _("is waiting on max Job jobs");
699          break;
700       case JS_WaitMaxJobs:
701          msg = _("is waiting on max total jobs");
702          break;
703       case JS_WaitStartTime:
704          msg = _("is waiting for its start time");
705          break;
706       case JS_WaitPriority:
707          msg = _("is waiting for higher priority jobs to finish");
708          break;
709       case JS_DataCommitting:
710          msg = _("SD committing Data");
711          break;
712       case JS_DataDespooling:
713          msg = _("SD despooling Data");
714          break;
715       case JS_AttrDespooling:
716          msg = _("SD despooling Attributes");
717          break;
718       case JS_AttrInserting:
719          msg = _("Dir inserting Attributes");
720          break;
721
722       default:
723          emsg = (char *)get_pool_memory(PM_FNAME);
724          Mmsg(emsg, _("is in unknown state %c"), jcr->JobStatus);
725          pool_mem = true;
726          msg = emsg;
727          break;
728       }
729       /*
730        * Now report Storage daemon status code
731        */
732       switch (jcr->SDJobStatus) {
733       case JS_WaitMount:
734          if (pool_mem) {
735             free_pool_memory(emsg);
736             pool_mem = false;
737          }
738          msg = _("is waiting for a mount request");
739          break;
740       case JS_WaitMedia:
741          if (pool_mem) {
742             free_pool_memory(emsg);
743             pool_mem = false;
744          }
745          msg = _("is waiting for an appendable Volume");
746          break;
747       case JS_WaitFD:
748          if (!pool_mem) {
749             emsg = (char *)get_pool_memory(PM_FNAME);
750             pool_mem = true;
751          }
752          if (!jcr->client || !jcr->wstore) {
753             Mmsg(emsg, _("is waiting for Client to connect to Storage daemon"));
754          } else {
755             Mmsg(emsg, _("is waiting for Client %s to connect to Storage %s"),
756                  jcr->client->name(), jcr->wstore->name());
757         }
758         msg = emsg;
759         break;
760       case JS_DataCommitting:
761          msg = _("SD committing Data");
762          break;
763       case JS_DataDespooling:
764          msg = _("SD despooling Data");
765          break;
766       case JS_AttrDespooling:
767          msg = _("SD despooling Attributes");
768          break;
769       case JS_AttrInserting:
770          msg = _("Dir inserting Attributes");
771          break;
772       }
773       switch (jcr->get_JobType()) {
774       case JT_ADMIN:
775       case JT_RESTORE:
776          bstrncpy(level, "      ", sizeof(level));
777          break;
778       default:
779          bstrncpy(level, level_to_str(jcr->get_JobLevel()), sizeof(level));
780          level[7] = 0;
781          break;
782       }
783
784       if (ua->api) {
785          ua->send_msg(_("%6d\t%-6s\t%-20s\t%s\n"),
786             jcr->JobId, level, jcr->Job, msg);
787       } else {
788          ua->send_msg(_("%6d %-6s  %-20s %s\n"),
789             jcr->JobId, level, jcr->Job, msg);
790       }
791
792       if (pool_mem) {
793          free_pool_memory(emsg);
794          pool_mem = false;
795       }
796    }
797    endeach_jcr(jcr);
798    if (!ua->api) ua->send_msg("====\n");
799    Dmsg0(200, "leave list_run_jobs()\n");
800 }
801
802 static void list_terminated_jobs(UAContext *ua)
803 {
804    char dt[MAX_TIME_LENGTH], b1[30], b2[30];
805    char level[10];
806
807    if (last_jobs->empty()) {
808       if (!ua->api) ua->send_msg(_("No Terminated Jobs.\n"));
809       return;
810    }
811    lock_last_jobs_list();
812    struct s_last_job *je;
813    if (!ua->api) {
814       ua->send_msg(_("\nTerminated Jobs:\n"));
815       ua->send_msg(_(" JobId  Level    Files      Bytes   Status   Finished        Name \n"));
816       ua->send_msg(_("====================================================================\n"));
817    }
818    foreach_dlist(je, last_jobs) {
819       char JobName[MAX_NAME_LENGTH];
820       const char *termstat;
821
822       bstrncpy(JobName, je->Job, sizeof(JobName));
823       /* There are three periods after the Job name */
824       char *p;
825       for (int i=0; i<3; i++) {
826          if ((p=strrchr(JobName, '.')) != NULL) {
827             *p = 0;
828          }
829       }
830
831       if (!acl_access_ok(ua, Job_ACL, JobName)) {
832          continue;
833       }
834
835       bstrftime_nc(dt, sizeof(dt), je->end_time);
836       switch (je->JobType) {
837       case JT_ADMIN:
838       case JT_RESTORE:
839          bstrncpy(level, "    ", sizeof(level));
840          break;
841       default:
842          bstrncpy(level, level_to_str(je->JobLevel), sizeof(level));
843          level[4] = 0;
844          break;
845       }
846       switch (je->JobStatus) {
847       case JS_Created:
848          termstat = _("Created");
849          break;
850       case JS_FatalError:
851       case JS_ErrorTerminated:
852          termstat = _("Error");
853          break;
854       case JS_Differences:
855          termstat = _("Diffs");
856          break;
857       case JS_Canceled:
858          termstat = _("Cancel");
859          break;
860       case JS_Terminated:
861          termstat = _("OK");
862          break;
863       default:
864          termstat = _("Other");
865          break;
866       }
867       if (ua->api) {
868          ua->send_msg(_("%6d\t%-6s\t%8s\t%10s\t%-7s\t%-8s\t%s\n"),
869             je->JobId,
870             level,
871             edit_uint64_with_commas(je->JobFiles, b1),
872             edit_uint64_with_suffix(je->JobBytes, b2),
873             termstat,
874             dt, JobName);
875       } else {
876          ua->send_msg(_("%6d  %-6s %8s %10s  %-7s  %-8s %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       }
884    }
885    if (!ua->api) ua->send_msg(_("\n"));
886    unlock_last_jobs_list();
887 }