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