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