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