]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_status.c
Eliminate pesky compiler warnings
[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
312 static void do_director_status(UAContext *ua)
313 {
314    list_dir_status_header(ua);
315
316    /*
317     * List scheduled Jobs
318     */
319    list_scheduled_jobs(ua);
320
321    /*
322     * List running jobs
323     */
324    list_running_jobs(ua);
325
326    /*
327     * List terminated jobs
328     */
329    list_terminated_jobs(ua);
330    ua->send_msg("====\n");
331 }
332
333 static void do_storage_status(UAContext *ua, STORE *store, char *cmd)
334 {
335    BSOCK *sd;
336    USTORE lstore;
337
338    lstore.store = store;
339    pm_strcpy(lstore.store_source, _("unknown source"));
340    set_wstorage(ua->jcr, &lstore);
341    /* Try connecting for up to 15 seconds */
342    if (!ua->api) ua->send_msg(_("Connecting to Storage daemon %s at %s:%d\n"),
343       store->name(), store->address, store->SDport);
344    if (!connect_to_storage_daemon(ua->jcr, 1, 15, 0)) {
345       ua->send_msg(_("\nFailed to connect to Storage daemon %s.\n====\n"),
346          store->name());
347       if (ua->jcr->store_bsock) {
348          bnet_close(ua->jcr->store_bsock);
349          ua->jcr->store_bsock = NULL;
350       }
351       return;
352    }
353    Dmsg0(20, _("Connected to storage daemon\n"));
354    sd = ua->jcr->store_bsock;
355    if (cmd) {
356       sd->fsend(".status %s", cmd);
357    } else {
358       sd->fsend("status");
359    }
360    while (sd->recv() >= 0) {
361       ua->send_msg("%s", sd->msg);
362    }
363    sd->signal( BNET_TERMINATE);
364    sd->close();
365    ua->jcr->store_bsock = NULL;
366    return;
367 }
368
369 static void do_client_status(UAContext *ua, CLIENT *client, char *cmd)
370 {
371    BSOCK *fd;
372
373    /* Connect to File daemon */
374
375    ua->jcr->client = client;
376    /* Release any old dummy key */
377    if (ua->jcr->sd_auth_key) {
378       free(ua->jcr->sd_auth_key);
379    }
380    /* Create a new dummy SD auth key */
381    ua->jcr->sd_auth_key = bstrdup("dummy");
382
383    /* Try to connect for 15 seconds */
384    if (!ua->api) ua->send_msg(_("Connecting to Client %s at %s:%d\n"),
385       client->name(), client->address, client->FDport);
386    if (!connect_to_file_daemon(ua->jcr, 1, 15, 0)) {
387       ua->send_msg(_("Failed to connect to Client %s.\n====\n"),
388          client->name());
389       if (ua->jcr->file_bsock) {
390          bnet_close(ua->jcr->file_bsock);
391          ua->jcr->file_bsock = NULL;
392       }
393       return;
394    }
395    Dmsg0(20, _("Connected to file daemon\n"));
396    fd = ua->jcr->file_bsock;
397    if (cmd) {
398       fd->fsend(".status %s", cmd);
399    } else {
400       fd->fsend("status");
401    }
402    while (fd->recv() >= 0) {
403       ua->send_msg("%s", fd->msg);
404    }
405    fd->signal(BNET_TERMINATE);
406    fd->close();
407    ua->jcr->file_bsock = NULL;
408
409    return;
410 }
411
412 static void prt_runhdr(UAContext *ua)
413 {
414    if (!ua->api) {
415       ua->send_msg(_("\nScheduled Jobs:\n"));
416       ua->send_msg(_("Level          Type     Pri  Scheduled          Name               Volume\n"));
417       ua->send_msg(_("===================================================================================\n"));
418    }
419 }
420
421 /* Scheduling packet */
422 struct sched_pkt {
423    dlink link;                        /* keep this as first item!!! */
424    JOB *job;
425    int level;
426    int priority;
427    time_t runtime;
428    POOL *pool;
429    STORE *store;
430 };
431
432 static void prt_runtime(UAContext *ua, sched_pkt *sp)
433 {
434    char dt[MAX_TIME_LENGTH];
435    const char *level_ptr;
436    bool ok = false;
437    bool close_db = false;
438    JCR *jcr = ua->jcr;
439    MEDIA_DBR mr;
440    int orig_jobtype;
441
442    orig_jobtype = jcr->get_JobType();
443    memset(&mr, 0, sizeof(mr));
444    if (sp->job->JobType == JT_BACKUP) {
445       jcr->db = NULL;
446       ok = complete_jcr_for_job(jcr, sp->job, sp->pool);
447       Dmsg1(250, "Using pool=%s\n", jcr->pool->name());
448       if (jcr->db) {
449          close_db = true;             /* new db opened, remember to close it */
450       }
451       if (ok) {
452          mr.PoolId = jcr->jr.PoolId;
453          mr.StorageId = sp->store->StorageId;
454          jcr->wstore = sp->store;
455          Dmsg0(250, "call find_next_volume_for_append\n");
456          ok = find_next_volume_for_append(jcr, &mr, 1, fnv_no_create_vol, fnv_no_prune);
457       }
458       if (!ok) {
459          bstrncpy(mr.VolumeName, "*unknown*", sizeof(mr.VolumeName));
460       }
461    }
462    bstrftime_nc(dt, sizeof(dt), sp->runtime);
463    switch (sp->job->JobType) {
464    case JT_ADMIN:
465    case JT_RESTORE:
466       level_ptr = " ";
467       break;
468    default:
469       level_ptr = level_to_str(sp->level);
470       break;
471    }
472    if (ua->api) {
473       ua->send_msg(_("%-14s\t%-8s\t%3d\t%-18s\t%-18s\t%s\n"),
474          level_ptr, job_type_to_str(sp->job->JobType), sp->priority, dt,
475          sp->job->name(), mr.VolumeName);
476    } else {
477       ua->send_msg(_("%-14s %-8s %3d  %-18s %-18s %s\n"),
478          level_ptr, job_type_to_str(sp->job->JobType), sp->priority, dt,
479          sp->job->name(), mr.VolumeName);
480    }
481    if (close_db) {
482       db_close_database(jcr, jcr->db);
483    }
484    jcr->db = ua->db;                  /* restore ua db to jcr */
485    jcr->set_JobType(orig_jobtype);
486 }
487
488 /*
489  * Sort items by runtime, priority
490  */
491 static int my_compare(void *item1, void *item2)
492 {
493    sched_pkt *p1 = (sched_pkt *)item1;
494    sched_pkt *p2 = (sched_pkt *)item2;
495    if (p1->runtime < p2->runtime) {
496       return -1;
497    } else if (p1->runtime > p2->runtime) {
498       return 1;
499    }
500    if (p1->priority < p2->priority) {
501       return -1;
502    } else if (p1->priority > p2->priority) {
503       return 1;
504    }
505    return 0;
506 }
507
508 /*
509  * Find all jobs to be run in roughly the
510  *  next 24 hours.
511  */
512 static void list_scheduled_jobs(UAContext *ua)
513 {
514    time_t runtime;
515    RUN *run;
516    JOB *job;
517    int level, num_jobs = 0;
518    int priority;
519    bool hdr_printed = false;
520    dlist sched;
521    sched_pkt *sp;
522    int days, i;
523
524    Dmsg0(200, "enter list_sched_jobs()\n");
525
526    days = 1;
527    i = find_arg_with_value(ua, NT_("days"));
528    if (i >= 0) {
529      days = atoi(ua->argv[i]);
530      if (((days < 0) || (days > 500)) && !ua->api) {
531        ua->send_msg(_("Ignoring invalid value for days. Max is 500.\n"));
532        days = 1;
533      }
534    }
535
536    /* Loop through all jobs */
537    LockRes();
538    foreach_res(job, R_JOB) {
539       if (!acl_access_ok(ua, Job_ACL, job->name()) || !job->enabled) {
540          continue;
541       }
542       for (run=NULL; (run = find_next_run(run, job, runtime, days)); ) {
543          USTORE store;
544          level = job->JobLevel;
545          if (run->level) {
546             level = run->level;
547          }
548          priority = job->Priority;
549          if (run->Priority) {
550             priority = run->Priority;
551          }
552          if (!hdr_printed) {
553             prt_runhdr(ua);
554             hdr_printed = true;
555          }
556          sp = (sched_pkt *)malloc(sizeof(sched_pkt));
557          sp->job = job;
558          sp->level = level;
559          sp->priority = priority;
560          sp->runtime = runtime;
561          sp->pool = run->pool;
562          get_job_storage(&store, job, run);
563          sp->store = store.store;
564          Dmsg3(250, "job=%s store=%s MediaType=%s\n", job->name(), sp->store->name(), sp->store->media_type);
565          sched.binary_insert_multiple(sp, my_compare);
566          num_jobs++;
567       }
568    } /* end for loop over resources */
569    UnlockRes();
570    foreach_dlist(sp, &sched) {
571       prt_runtime(ua, sp);
572    }
573    if (num_jobs == 0 && !ua->api) {
574       ua->send_msg(_("No Scheduled Jobs.\n"));
575    }
576    if (!ua->api) ua->send_msg("====\n");
577    Dmsg0(200, "Leave list_sched_jobs_runs()\n");
578 }
579
580 static void list_running_jobs(UAContext *ua)
581 {
582    JCR *jcr;
583    int njobs = 0;
584    const char *msg;
585    char *emsg;                        /* edited message */
586    char dt[MAX_TIME_LENGTH];
587    char level[10];
588    bool pool_mem = false;
589
590    Dmsg0(200, "enter list_run_jobs()\n");
591    if (!ua->api) ua->send_msg(_("\nRunning Jobs:\n"));
592    foreach_jcr(jcr) {
593       if (jcr->JobId == 0) {      /* this is us */
594          /* this is a console or other control job. We only show console
595           * jobs in the status output.
596           */
597          if (jcr->get_JobType() == JT_CONSOLE && !ua->api) {
598             bstrftime_nc(dt, sizeof(dt), jcr->start_time);
599             ua->send_msg(_("Console connected at %s\n"), dt);
600          }
601          continue;
602       }       
603       njobs++;
604    }
605    endeach_jcr(jcr);
606
607    if (njobs == 0) {
608       /* Note the following message is used in regress -- don't change */
609       if (!ua->api)  ua->send_msg(_("No Jobs running.\n====\n"));
610       Dmsg0(200, "leave list_run_jobs()\n");
611       return;
612    }
613    njobs = 0;
614    if (!ua->api) {
615       ua->send_msg(_(" JobId Level   Name                       Status\n"));
616       ua->send_msg(_("======================================================================\n"));
617    }
618    foreach_jcr(jcr) {
619       if (jcr->JobId == 0 || !acl_access_ok(ua, Job_ACL, jcr->job->name())) {
620          continue;
621       }
622       njobs++;
623       switch (jcr->JobStatus) {
624       case JS_Created:
625          msg = _("is waiting execution");
626          break;
627       case JS_Running:
628          msg = _("is running");
629          break;
630       case JS_Blocked:
631          msg = _("is blocked");
632          break;
633       case JS_Terminated:
634          msg = _("has terminated");
635          break;
636       case JS_ErrorTerminated:
637          msg = _("has erred");
638          break;
639       case JS_Error:
640          msg = _("has errors");
641          break;
642       case JS_FatalError:
643          msg = _("has a fatal error");
644          break;
645       case JS_Differences:
646          msg = _("has verify differences");
647          break;
648       case JS_Canceled:
649          msg = _("has been canceled");
650          break;
651       case JS_WaitFD:
652          emsg = (char *) get_pool_memory(PM_FNAME);
653          if (!jcr->client) {
654             Mmsg(emsg, _("is waiting on Client"));
655          } else {
656             Mmsg(emsg, _("is waiting on Client %s"), jcr->client->name());
657          }
658          pool_mem = true;
659          msg = emsg;
660          break;
661       case JS_WaitSD:
662          emsg = (char *) get_pool_memory(PM_FNAME);
663          if (jcr->wstore) {
664             Mmsg(emsg, _("is waiting on Storage %s"), jcr->wstore->name());
665          } else if (jcr->rstore) {
666             Mmsg(emsg, _("is waiting on Storage %s"), jcr->rstore->name());
667          } else {
668             Mmsg(emsg, _("is waiting on Storage"));
669          }
670          pool_mem = true;
671          msg = emsg;
672          break;
673       case JS_WaitStoreRes:
674          msg = _("is waiting on max Storage jobs");
675          break;
676       case JS_WaitClientRes:
677          msg = _("is waiting on max Client jobs");
678          break;
679       case JS_WaitJobRes:
680          msg = _("is waiting on max Job jobs");
681          break;
682       case JS_WaitMaxJobs:
683          msg = _("is waiting on max total jobs");
684          break;
685       case JS_WaitStartTime:
686          msg = _("is waiting for its start time");
687          break;
688       case JS_WaitPriority:
689          msg = _("is waiting for higher priority jobs to finish");
690          break;
691       case JS_DataCommitting:
692          msg = _("SD committing Data");
693          break;
694       case JS_DataDespooling:
695          msg = _("SD despooling Data");
696          break;
697       case JS_AttrDespooling:
698          msg = _("SD despooling Attributes");
699          break;
700       case JS_AttrInserting:
701          msg = _("Dir inserting Attributes");
702          break;
703
704       default:
705          emsg = (char *)get_pool_memory(PM_FNAME);
706          Mmsg(emsg, _("is in unknown state %c"), jcr->JobStatus);
707          pool_mem = true;
708          msg = emsg;
709          break;
710       }
711       /*
712        * Now report Storage daemon status code
713        */
714       switch (jcr->SDJobStatus) {
715       case JS_WaitMount:
716          if (pool_mem) {
717             free_pool_memory(emsg);
718             pool_mem = false;
719          }
720          msg = _("is waiting for a mount request");
721          break;
722       case JS_WaitMedia:
723          if (pool_mem) {
724             free_pool_memory(emsg);
725             pool_mem = false;
726          }
727          msg = _("is waiting for an appendable Volume");
728          break;
729       case JS_WaitFD:
730          if (!pool_mem) {
731             emsg = (char *)get_pool_memory(PM_FNAME);
732             pool_mem = true;
733          }
734          if (!jcr->client || !jcr->wstore) {
735             Mmsg(emsg, _("is waiting for Client to connect to Storage daemon"));
736          } else {
737             Mmsg(emsg, _("is waiting for Client %s to connect to Storage %s"),
738                  jcr->client->name(), jcr->wstore->name());
739         }
740         msg = emsg;
741         break;
742       case JS_DataCommitting:
743          msg = _("SD committing Data");
744          break;
745       case JS_DataDespooling:
746          msg = _("SD despooling Data");
747          break;
748       case JS_AttrDespooling:
749          msg = _("SD despooling Attributes");
750          break;
751       case JS_AttrInserting:
752          msg = _("Dir inserting Attributes");
753          break;
754       }
755       switch (jcr->get_JobType()) {
756       case JT_ADMIN:
757       case JT_RESTORE:
758          bstrncpy(level, "      ", sizeof(level));
759          break;
760       default:
761          bstrncpy(level, level_to_str(jcr->get_JobLevel()), sizeof(level));
762          level[7] = 0;
763          break;
764       }
765
766       if (ua->api) {
767          ua->send_msg(_("%6d\t%-6s\t%-20s\t%s\n"),
768             jcr->JobId, level, jcr->Job, msg);
769       } else {
770          ua->send_msg(_("%6d %-6s  %-20s %s\n"),
771             jcr->JobId, level, jcr->Job, msg);
772       }
773
774       if (pool_mem) {
775          free_pool_memory(emsg);
776          pool_mem = false;
777       }
778    }
779    endeach_jcr(jcr);
780    if (!ua->api) ua->send_msg("====\n");
781    Dmsg0(200, "leave list_run_jobs()\n");
782 }
783
784 static void list_terminated_jobs(UAContext *ua)
785 {
786    char dt[MAX_TIME_LENGTH], b1[30], b2[30];
787    char level[10];
788
789    if (last_jobs->empty()) {
790       if (!ua->api) ua->send_msg(_("No Terminated Jobs.\n"));
791       return;
792    }
793    lock_last_jobs_list();
794    struct s_last_job *je;
795    if (!ua->api) {
796       ua->send_msg(_("\nTerminated Jobs:\n"));
797       ua->send_msg(_(" JobId  Level    Files      Bytes   Status   Finished        Name \n"));
798       ua->send_msg(_("====================================================================\n"));
799    }
800    foreach_dlist(je, last_jobs) {
801       char JobName[MAX_NAME_LENGTH];
802       const char *termstat;
803
804       bstrncpy(JobName, je->Job, sizeof(JobName));
805       /* There are three periods after the Job name */
806       char *p;
807       for (int i=0; i<3; i++) {
808          if ((p=strrchr(JobName, '.')) != NULL) {
809             *p = 0;
810          }
811       }
812
813       if (!acl_access_ok(ua, Job_ACL, JobName)) {
814          continue;
815       }
816
817       bstrftime_nc(dt, sizeof(dt), je->end_time);
818       switch (je->JobType) {
819       case JT_ADMIN:
820       case JT_RESTORE:
821          bstrncpy(level, "    ", sizeof(level));
822          break;
823       default:
824          bstrncpy(level, level_to_str(je->JobLevel), sizeof(level));
825          level[4] = 0;
826          break;
827       }
828       switch (je->JobStatus) {
829       case JS_Created:
830          termstat = _("Created");
831          break;
832       case JS_FatalError:
833       case JS_ErrorTerminated:
834          termstat = _("Error");
835          break;
836       case JS_Differences:
837          termstat = _("Diffs");
838          break;
839       case JS_Canceled:
840          termstat = _("Cancel");
841          break;
842       case JS_Terminated:
843          termstat = _("OK");
844          break;
845       default:
846          termstat = _("Other");
847          break;
848       }
849       if (ua->api) {
850          ua->send_msg(_("%6d\t%-6s\t%8s\t%10s\t%-7s\t%-8s\t%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       } else {
858          ua->send_msg(_("%6d  %-6s %8s %10s  %-7s  %-8s %s\n"),
859             je->JobId,
860             level,
861             edit_uint64_with_commas(je->JobFiles, b1),
862             edit_uint64_with_suffix(je->JobBytes, b2),
863             termstat,
864             dt, JobName);
865       }
866    }
867    if (!ua->api) ua->send_msg(_("\n"));
868    unlock_last_jobs_list();
869 }