]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_status.c
03f55e93168c23fdd6a4235712d7d8b6b84f070b
[bacula/bacula] / bacula / src / dird / ua_status.c
1 /*
2    Bacula(R) - The Network Backup Solution
3
4    Copyright (C) 2000-2017 Kern Sibbald
5
6    The original author of Bacula is Kern Sibbald, with contributions
7    from many others, a complete list can be found in the file AUTHORS.
8
9    You may use this file and others of this release according to the
10    license defined in the LICENSE file, which includes the Affero General
11    Public License, v3.0 ("AGPLv3") and some additional permissions and
12    terms pursuant to its AGPLv3 Section 7.
13
14    This notice must be preserved when any source code is 
15    conveyed and/or propagated.
16
17    Bacula(R) is a registered trademark of Kern Sibbald.
18 */
19 /*
20  *   Bacula Director -- User Agent Status Command
21  *
22  *     Kern Sibbald, August MMI
23  */
24
25
26 #include "bacula.h"
27 #include "dird.h"
28
29 extern void *start_heap;
30 extern utime_t last_reload_time;
31
32 static void list_scheduled_jobs(UAContext *ua);
33 static void llist_scheduled_jobs(UAContext *ua);
34 static void list_running_jobs(UAContext *ua);
35 static void list_terminated_jobs(UAContext *ua);
36 static void do_storage_status(UAContext *ua, STORE *store, char *cmd);
37 static void do_client_status(UAContext *ua, CLIENT *client, char *cmd);
38 static void do_director_status(UAContext *ua);
39 static void do_all_status(UAContext *ua);
40 void status_slots(UAContext *ua, STORE *store);
41 void status_content(UAContext *ua, STORE *store);
42
43 static char OKqstatus[]   = "1000 OK .status\n";
44 static char DotStatusJob[] = "JobId=%s JobStatus=%c JobErrors=%d\n";
45
46 /*
47  * .status command
48  */
49
50 bool dot_status_cmd(UAContext *ua, const char *cmd)
51 {
52    STORE *store;
53    CLIENT *client;
54    JCR* njcr = NULL;
55    s_last_job* job;
56    char ed1[50];
57
58    Dmsg2(20, "status=\"%s\" argc=%d\n", cmd, ua->argc);
59
60    if (ua->argc < 3) {
61       ua->send_msg("1900 Bad .status command, missing arguments.\n");
62       return false;
63    }
64
65    if (strcasecmp(ua->argk[1], "dir") == 0) {
66       if (strcasecmp(ua->argk[2], "current") == 0) {
67          ua->send_msg(OKqstatus, ua->argk[2]);
68          foreach_jcr(njcr) {
69             if (!njcr->is_internal_job() && acl_access_ok(ua, Job_ACL, njcr->job->name())) {
70                ua->send_msg(DotStatusJob, edit_int64(njcr->JobId, ed1),
71                         njcr->JobStatus, njcr->JobErrors);
72             }
73          }
74          endeach_jcr(njcr);
75       } else if (strcasecmp(ua->argk[2], "last") == 0) {
76          ua->send_msg(OKqstatus, ua->argk[2]);
77          if ((last_jobs) && (last_jobs->size() > 0)) {
78             job = (s_last_job*)last_jobs->last();
79             if (acl_access_ok(ua, Job_ACL, job->Job)) {
80                ua->send_msg(DotStatusJob, edit_int64(job->JobId, ed1),
81                      job->JobStatus, job->Errors);
82             }
83          }
84       } else if (strcasecmp(ua->argk[2], "header") == 0) {
85           list_dir_status_header(ua);
86       } else if (strcasecmp(ua->argk[2], "scheduled") == 0) {
87           list_scheduled_jobs(ua);
88       } else if (strcasecmp(ua->argk[2], "running") == 0) {
89           list_running_jobs(ua);
90       } else if (strcasecmp(ua->argk[2], "terminated") == 0) {
91           list_terminated_jobs(ua);
92       } else {
93          ua->send_msg("1900 Bad .status command, wrong argument.\n");
94          return false;
95       }
96    } else if (strcasecmp(ua->argk[1], "client") == 0) {
97       client = get_client_resource(ua, JT_BACKUP_RESTORE);
98       if (client) {
99          Dmsg2(200, "Client=%s arg=%s\n", client->name(), NPRT(ua->argk[2]));
100          do_client_status(ua, client, ua->argk[2]);
101       }
102    } else if (strcasecmp(ua->argk[1], "storage") == 0) {
103       store = get_storage_resource(ua, false /*no default*/, true/*unique*/);
104       if (!store) {
105          ua->send_msg("1900 Bad .status command, wrong argument.\n");
106          return false;
107       }
108       do_storage_status(ua, store, ua->argk[2]);
109    } else {
110       ua->send_msg("1900 Bad .status command, wrong argument.\n");
111       return false;
112    }
113
114    return true;
115 }
116
117 /* Test the network between FD and SD */
118 static int do_network_status(UAContext *ua)
119 {
120    CLIENT *client = NULL;
121    USTORE  store;
122    JCR *jcr = ua->jcr;
123    char *store_address, ed1[50];
124    uint32_t store_port;
125    uint64_t nb = 50 * 1024 * 1024;
126
127    int i = find_arg_with_value(ua, "bytes");
128    if (i > 0) {
129       if (!size_to_uint64(ua->argv[i], strlen(ua->argv[i]), &nb)) {
130          return 1;
131       }
132    }
133    
134    client = get_client_resource(ua, JT_BACKUP_RESTORE);
135    if (!client) {
136       return 1;
137    }
138
139    store.store = get_storage_resource(ua, false, true);
140    if (!store.store) {
141       return 1;
142    }
143
144    jcr->client = client;
145    set_wstorage(jcr, &store);
146
147    if (!ua->api) {
148       ua->send_msg(_("Connecting to Storage %s at %s:%d\n"),
149                    store.store->name(), store.store->address, store.store->SDport);
150    }
151
152    if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
153       ua->error_msg(_("Failed to connect to Storage.\n"));
154       goto bail_out;
155    }
156
157    if (!start_storage_daemon_job(jcr, NULL, NULL)) {
158       goto bail_out;
159    }
160
161    /*
162     * Note startup sequence of SD/FD is different depending on
163     *  whether the SD listens (normal) or the SD calls the FD.
164     */
165    if (!client->sd_calls_client) {
166       if (!run_storage_and_start_message_thread(jcr, jcr->store_bsock)) {
167          goto bail_out;
168       }
169    } /* Else it's done in init_storage_job() */
170
171    if (!ua->api) {
172       ua->send_msg(_("Connecting to Client %s at %s:%d\n"),
173                    client->name(), client->address(), client->FDport);
174    }
175
176    if (!connect_to_file_daemon(jcr, 1, 15, 0)) {
177       ua->error_msg(_("Failed to connect to Client.\n"));
178       goto bail_out;
179    }
180
181    if (jcr->sd_calls_client) {
182       /*
183        * SD must call "client" i.e. FD
184        */
185       if (jcr->FDVersion < 10) {
186          Jmsg(jcr, M_FATAL, 0, _("The File daemon does not support SDCallsClient.\n"));
187          goto bail_out;
188       }
189       if (!send_client_addr_to_sd(jcr)) {
190          goto bail_out;
191       }
192       if (!run_storage_and_start_message_thread(jcr, jcr->store_bsock)) {
193          goto bail_out;
194       }
195
196       store_address = store.store->address;  /* dummy */
197       store_port = 0;                        /* flag that SD calls FD */
198
199    } else {
200       /*
201        * send Storage daemon address to the File daemon,
202        *   then wait for File daemon to make connection
203        *   with Storage daemon.
204        */
205       if (store.store->SDDport == 0) {
206          store.store->SDDport = store.store->SDport;
207       }
208
209       store_address = get_storage_address(jcr->client, store.store);
210       store_port = store.store->SDDport;
211    }
212
213    if (!send_store_addr_to_fd(jcr, store.store, store_address, store_port)) {
214       goto bail_out;
215    }
216
217    if (!ua->api) {
218       ua->info_msg(_("Running network test between Client=%s and Storage=%s with %sB ...\n"),
219                    client->name(), store.store->name(), edit_uint64_with_suffix(nb, ed1));
220    }
221
222    if (!jcr->file_bsock->fsend("testnetwork bytes=%lld\n", nb)) {
223       goto bail_out;
224    }
225
226    while (jcr->file_bsock->recv() > 0) {
227       ua->info_msg(jcr->file_bsock->msg);
228    }
229    
230 bail_out:
231    jcr->file_bsock->signal(BNET_TERMINATE);
232    jcr->store_bsock->signal(BNET_TERMINATE);
233    wait_for_storage_daemon_termination(jcr);
234
235    free_bsock(jcr->file_bsock);
236    free_bsock(jcr->store_bsock);
237
238    jcr->client = NULL;
239    free_wstorage(jcr);
240    return 1;
241 }
242
243 /* This is the *old* command handler, so we must return
244  *  1 or it closes the connection
245  */
246 int qstatus_cmd(UAContext *ua, const char *cmd)
247 {
248    dot_status_cmd(ua, cmd);
249    return 1;
250 }
251
252 /*
253  * status command
254  */
255 int status_cmd(UAContext *ua, const char *cmd)
256 {
257    STORE *store;
258    CLIENT *client;
259    int item, i;
260
261    Dmsg1(20, "status:%s:\n", cmd);
262
263    for (i=1; i<ua->argc; i++) {
264       if (strcasecmp(ua->argk[i], NT_("network")) == 0) {
265          do_network_status(ua);
266          return 1;
267       } else if (strcasecmp(ua->argk[i], NT_("schedule")) == 0 ||
268           strcasecmp(ua->argk[i], NT_("scheduled")) == 0) {
269          llist_scheduled_jobs(ua);
270          return 1;
271       } else if (strcasecmp(ua->argk[i], NT_("all")) == 0) {
272          do_all_status(ua);
273          return 1;
274       } else if (strcasecmp(ua->argk[i], NT_("dir")) == 0 ||
275                  strcasecmp(ua->argk[i], NT_("director")) == 0) {
276          do_director_status(ua);
277          return 1;
278       } else if (strcasecmp(ua->argk[i], NT_("client")) == 0) {
279          client = get_client_resource(ua, JT_BACKUP_RESTORE);
280          if (client) {
281             do_client_status(ua, client, NULL);
282          }
283          return 1;
284       } else {
285          store = get_storage_resource(ua, false/*no default*/, true/*unique*/);
286          if (store) {
287             if (find_arg(ua, NT_("slots")) > 0) {
288                status_slots(ua, store);
289             } else {
290                do_storage_status(ua, store, NULL);
291             }
292          }
293          return 1;
294       }
295    }
296    /* If no args, ask for status type */
297    if (ua->argc == 1) {
298        char prmt[MAX_NAME_LENGTH];
299
300       start_prompt(ua, _("Status available for:\n"));
301       add_prompt(ua, NT_("Director"));
302       add_prompt(ua, NT_("Storage"));
303       add_prompt(ua, NT_("Client"));
304       add_prompt(ua, NT_("Scheduled"));
305       add_prompt(ua, NT_("Network"));
306       add_prompt(ua, NT_("All"));
307       Dmsg0(20, "do_prompt: select daemon\n");
308       if ((item=do_prompt(ua, "",  _("Select daemon type for status"), prmt, sizeof(prmt))) < 0) {
309          return 1;
310       }
311       Dmsg1(20, "item=%d\n", item);
312       switch (item) {
313       case 0:                         /* Director */
314          do_director_status(ua);
315          break;
316       case 1:
317          store = select_storage_resource(ua, true/*unique*/);
318          if (store) {
319             do_storage_status(ua, store, NULL);
320          }
321          break;
322       case 2:
323          client = select_client_resource(ua, JT_BACKUP_RESTORE);
324          if (client) {
325             do_client_status(ua, client, NULL);
326          }
327          break;
328       case 3:
329          llist_scheduled_jobs(ua);
330          break;
331       case 4:
332          do_network_status(ua);
333          break;
334       case 5:
335          do_all_status(ua);
336          break;
337       default:
338          break;
339       }
340    }
341    return 1;
342 }
343
344 static void do_all_status(UAContext *ua)
345 {
346    STORE *store, **unique_store;
347    CLIENT *client, **unique_client;
348    int i, j;
349    bool found;
350
351    do_director_status(ua);
352
353    /* Count Storage items */
354    LockRes();
355    i = 0;
356    foreach_res(store, R_STORAGE) {
357       i++;
358    }
359    unique_store = (STORE **) malloc(i * sizeof(STORE));
360    /* Find Unique Storage address/port */
361    i = 0;
362    foreach_res(store, R_STORAGE) {
363       found = false;
364       if (!acl_access_ok(ua, Storage_ACL, store->name())) {
365          continue;
366       }
367       for (j=0; j<i; j++) {
368          if (strcmp(unique_store[j]->address, store->address) == 0 &&
369              unique_store[j]->SDport == store->SDport) {
370             found = true;
371             break;
372          }
373       }
374       if (!found) {
375          unique_store[i++] = store;
376          Dmsg2(40, "Stuffing: %s:%d\n", store->address, store->SDport);
377       }
378    }
379    UnlockRes();
380
381    /* Call each unique Storage daemon */
382    for (j=0; j<i; j++) {
383       do_storage_status(ua, unique_store[j], NULL);
384    }
385    free(unique_store);
386
387    /* Count Client items */
388    LockRes();
389    i = 0;
390    foreach_res(client, R_CLIENT) {
391       i++;
392    }
393    unique_client = (CLIENT **)malloc(i * sizeof(CLIENT));
394    /* Find Unique Client address/port */
395    i = 0;
396    foreach_res(client, R_CLIENT) {
397       found = false;
398       if (!acl_access_client_ok(ua, client->name(), JT_BACKUP_RESTORE)) {
399          continue;
400       }
401       for (j=0; j<i; j++) {
402          if (strcmp(unique_client[j]->address(), client->address()) == 0 &&
403              unique_client[j]->FDport == client->FDport) {
404             found = true;
405             break;
406          }
407       }
408       if (!found) {
409          unique_client[i++] = client;
410          Dmsg2(40, "Stuffing: %s:%d\n", client->address(), client->FDport);
411       }
412    }
413    UnlockRes();
414
415    /* Call each unique File daemon */
416    for (j=0; j<i; j++) {
417       do_client_status(ua, unique_client[j], NULL);
418    }
419    free(unique_client);
420
421 }
422
423 static void api_list_dir_status_header(UAContext *ua)
424 {
425    OutputWriter wt(ua->api_opts);
426    wt.start_group("header");
427    wt.get_output(
428       OT_STRING, "name",        my_name,
429       OT_STRING, "version",     VERSION " (" BDATE ")",
430       OT_STRING, "uname",       HOST_OS " " DISTNAME " " DISTVER,
431       OT_UTIME,  "started",     daemon_start_time,
432       OT_UTIME,  "reloaded",    last_reload_time,
433       OT_INT,    "jobs_run",    num_jobs_run,
434       OT_INT,    "jobs_running",job_count(),
435       OT_INT,    "nclients",    ((rblist *)res_head[R_CLIENT-r_first]->res_list)->size(),
436       OT_INT,    "nstores",     ((rblist *)res_head[R_STORAGE-r_first]->res_list)->size(),
437       OT_INT,    "npools",      ((rblist *)res_head[R_POOL-r_first]->res_list)->size(),
438       OT_INT,    "ncats",       ((rblist *)res_head[R_CATALOG-r_first]->res_list)->size(),
439       OT_INT,    "nfset",       ((rblist *)res_head[R_FILESET-r_first]->res_list)->size(),
440       OT_INT,    "nscheds",     ((rblist *)res_head[R_SCHEDULE-r_first]->res_list)->size(),
441       OT_PLUGINS,"plugins",     b_plugin_list,
442       OT_END);
443
444    ua->send_msg("%s", wt.end_group());
445 }
446
447 void list_dir_status_header(UAContext *ua)
448 {
449    char dt[MAX_TIME_LENGTH], dt1[MAX_TIME_LENGTH];
450    char b1[35], b2[35], b3[35], b4[35], b5[35];
451
452    if (ua->api > 1) {
453       api_list_dir_status_header(ua);
454       return;
455    }
456
457    ua->send_msg(_("%s %sVersion: %s (%s) %s %s %s\n"), my_name,
458             "", VERSION, BDATE, HOST_OS, DISTNAME, DISTVER);
459    bstrftime_nc(dt, sizeof(dt), daemon_start_time);
460    bstrftimes(dt1, sizeof(dt1), last_reload_time);
461    ua->send_msg(_("Daemon started %s, conf reloaded %s\n"), dt, dt1);
462    ua->send_msg(_(" Jobs: run=%d, running=%d mode=%d,%d\n"),
463       num_jobs_run, job_count(), (int)DEVELOPER_MODE, 0);
464    ua->send_msg(_(" Heap: heap=%s smbytes=%s max_bytes=%s bufs=%s max_bufs=%s\n"),
465       edit_uint64_with_commas((char *)sbrk(0)-(char *)start_heap, b1),
466       edit_uint64_with_commas(sm_bytes, b2),
467       edit_uint64_with_commas(sm_max_bytes, b3),
468       edit_uint64_with_commas(sm_buffers, b4),
469       edit_uint64_with_commas(sm_max_buffers, b5));
470    ua->send_msg(_(" Res: njobs=%d nclients=%d nstores=%d npools=%d ncats=%d"
471                   " nfsets=%d nscheds=%d\n"),
472       ((rblist *)res_head[R_JOB-r_first]->res_list)->size(),
473       ((rblist *)res_head[R_CLIENT-r_first]->res_list)->size(),
474       ((rblist *)res_head[R_STORAGE-r_first]->res_list)->size(),
475       ((rblist *)res_head[R_POOL-r_first]->res_list)->size(),
476       ((rblist *)res_head[R_CATALOG-r_first]->res_list)->size(),
477       ((rblist *)res_head[R_FILESET-r_first]->res_list)->size(),
478       ((rblist *)res_head[R_SCHEDULE-r_first]->res_list)->size());
479
480
481    /* TODO: use this function once for all daemons */
482    if (b_plugin_list && b_plugin_list->size() > 0) {
483       int len;
484       Plugin *plugin;
485       POOL_MEM msg(PM_FNAME);
486       pm_strcpy(msg, " Plugin: ");
487       foreach_alist(plugin, b_plugin_list) {
488          len = pm_strcat(msg, plugin->file);
489          if (len > 80) {
490             pm_strcat(msg, "\n   ");
491          } else {
492             pm_strcat(msg, " ");
493          }
494       }
495       ua->send_msg("%s\n", msg.c_str());
496    }
497 }
498
499 static void do_director_status(UAContext *ua)
500 {
501    list_dir_status_header(ua);
502
503    /*
504     * List scheduled Jobs
505     */
506    list_scheduled_jobs(ua);
507
508    /*
509     * List running jobs
510     */
511    list_running_jobs(ua);
512
513    /*
514     * List terminated jobs
515     */
516    list_terminated_jobs(ua);
517    ua->send_msg("====\n");
518 }
519
520 static void do_storage_status(UAContext *ua, STORE *store, char *cmd)
521 {
522    BSOCK *sd;
523    USTORE lstore;
524
525
526    if (!acl_access_ok(ua, Storage_ACL, store->name())) {
527       ua->error_msg(_("No authorization for Storage \"%s\"\n"), store->name());
528       return;
529    }
530    /*
531     * The Storage daemon is problematic because it shows information
532     *  related to multiple Job, so if there is a Client or Job
533     *  ACL restriction, we forbid all access to the Storage.
534     */
535    if (have_restricted_acl(ua, Client_ACL) ||
536        have_restricted_acl(ua, Job_ACL)) {
537       ua->error_msg(_("Restricted Client or Job does not permit access to  Storage daemons\n"));
538       return;
539    }
540    lstore.store = store;
541    pm_strcpy(lstore.store_source, _("unknown source"));
542    set_wstorage(ua->jcr, &lstore);
543    /* Try connecting for up to 15 seconds */
544    if (!ua->api) ua->send_msg(_("Connecting to Storage daemon %s at %s:%d\n"),
545       store->name(), store->address, store->SDport);
546    if (!connect_to_storage_daemon(ua->jcr, 1, 15, 0)) {
547       ua->send_msg(_("\nFailed to connect to Storage daemon %s.\n====\n"),
548          store->name());
549       free_bsock(ua->jcr->store_bsock);
550       return;
551    }
552    Dmsg0(20, "Connected to storage daemon\n");
553    sd = ua->jcr->store_bsock;
554    if (cmd) {
555       POOL_MEM devname;
556       /*
557        * For .status storage=xxx shstore list
558        *  send .status shstore list xxx-device
559        */
560       if (strcasecmp(cmd, "shstore") == 0) {
561          if (!ua->argk[3]) {
562             ua->send_msg(_("Must have three arguments\n"));
563             return;
564          }
565          pm_strcpy(devname, store->dev_name());
566          bash_spaces(devname.c_str());
567          sd->fsend(".status %s %s %s api=%d api_opts=%s",
568                    cmd, ua->argk[3], devname.c_str(),
569                    ua->api, ua->api_opts);
570       } else {
571          int i = find_arg_with_value(ua, "device");
572          if (i>0) {
573             Mmsg(devname, "device=%s", ua->argv[i]);
574             bash_spaces(devname.c_str());
575          }
576          sd->fsend(".status %s api=%d api_opts=%s %s",
577                    cmd, ua->api, ua->api_opts, devname.c_str());
578       }
579    } else {
580       sd->fsend("status");
581    }
582    while (sd->recv() >= 0) {
583       ua->send_msg("%s", sd->msg);
584    }
585    sd->signal(BNET_TERMINATE);
586    free_bsock(ua->jcr->store_bsock);
587    return;
588 }
589
590 static void do_client_status(UAContext *ua, CLIENT *client, char *cmd)
591 {
592    BSOCK *fd;
593
594    if (!acl_access_client_ok(ua, client->name(), JT_BACKUP_RESTORE)) {
595       ua->error_msg(_("No authorization for Client \"%s\"\n"), client->name());
596       return;
597    }
598    /* Connect to File daemon */
599    ua->jcr->client = client;
600    /* Release any old dummy key */
601    if (ua->jcr->sd_auth_key) {
602       free(ua->jcr->sd_auth_key);
603    }
604    /* Create a new dummy SD auth key */
605    ua->jcr->sd_auth_key = bstrdup("dummy");
606
607    /* Try to connect for 15 seconds */
608    if (!ua->api) ua->send_msg(_("Connecting to Client %s at %s:%d\n"),
609       client->name(), client->address(), client->FDport);
610    if (!connect_to_file_daemon(ua->jcr, 1, 15, 0)) {
611       ua->send_msg(_("Failed to connect to Client %s.\n====\n"),
612          client->name());
613       free_bsock(ua->jcr->file_bsock);
614       return;
615    }
616    Dmsg0(20, _("Connected to file daemon\n"));
617    fd = ua->jcr->file_bsock;
618    if (cmd) {
619       fd->fsend(".status %s api=%d api_opts=%s", cmd, ua->api, ua->api_opts);
620    } else {
621       fd->fsend("status");
622    }
623    while (fd->recv() >= 0) {
624       ua->send_msg("%s", fd->msg);
625    }
626    fd->signal(BNET_TERMINATE);
627    free_bsock(ua->jcr->file_bsock);
628
629    return;
630 }
631
632 static void prt_runhdr(UAContext *ua)
633 {
634    if (!ua->api) {
635       ua->send_msg(_("\nScheduled Jobs:\n"));
636       ua->send_msg(_("Level          Type     Pri  Scheduled          Job Name           Volume\n"));
637       ua->send_msg(_("===================================================================================\n"));
638    }
639 }
640
641 static void prt_lrunhdr(UAContext *ua)
642 {
643    if (!ua->api) {
644       ua->send_msg(_("\nScheduled Jobs:\n"));
645       ua->send_msg(_("Level          Type     Pri  Scheduled          Job Name           Schedule\n"));
646       ua->send_msg(_("=====================================================================================\n"));
647    }
648 }
649
650
651 /* Scheduling packet */
652 struct sched_pkt {
653    dlink link;                        /* keep this as first item!!! */
654    JOB *job;
655    int level;
656    int priority;
657    utime_t runtime;
658    POOL *pool;
659    STORE *store;
660 };
661
662 static void prt_runtime(UAContext *ua, sched_pkt *sp, OutputWriter *ow)
663 {
664    char dt[MAX_TIME_LENGTH];
665    const char *level_ptr;
666    bool ok = false;
667    bool close_db = false;
668    JCR *jcr = ua->jcr;
669    MEDIA_DBR mr;
670    int orig_jobtype;
671
672    orig_jobtype = jcr->getJobType();
673    if (sp->job->JobType == JT_BACKUP) {
674       jcr->db = NULL;
675       ok = complete_jcr_for_job(jcr, sp->job, sp->pool);
676       Dmsg1(250, "Using pool=%s\n", jcr->pool->name());
677       if (jcr->db) {
678          close_db = true;             /* new db opened, remember to close it */
679       }
680       if (ok) {
681          mr.PoolId = jcr->jr.PoolId;
682          jcr->wstore = sp->store;
683          set_storageid_in_mr(jcr->wstore, &mr);
684          Dmsg0(250, "call find_next_volume_for_append\n");
685          /* no need to set ScratchPoolId, since we use fnv_no_create_vol */
686          ok = find_next_volume_for_append(jcr, &mr, 1, fnv_no_create_vol, fnv_no_prune);
687       }
688       if (!ok) {
689          bstrncpy(mr.VolumeName, "*unknown*", sizeof(mr.VolumeName));
690       }
691    }
692    bstrftime_nc(dt, sizeof(dt), sp->runtime);
693    switch (sp->job->JobType) {
694    case JT_ADMIN:
695       level_ptr = "Admin";
696       break;
697    case JT_RESTORE:
698       level_ptr = "Restore";
699       break;
700    default:
701       level_ptr = level_to_str(sp->level);
702       break;
703    }
704    if (ua->api == 1) {
705       ua->send_msg(_("%-14s\t%-8s\t%3d\t%-18s\t%-18s\t%s\n"),
706          level_ptr, job_type_to_str(sp->job->JobType), sp->priority, dt,
707          sp->job->name(), mr.VolumeName);
708
709    } else if (ua->api > 1) {
710       ua->send_msg("%s",
711                    ow->get_output(OT_CLEAR,
712                       OT_START_OBJ,
713                       OT_STRING,    "name",     sp->job->name(),
714                       OT_JOBLEVEL,  "level",   sp->level,
715                       OT_JOBTYPE,   "type",    sp->job->JobType,
716                       OT_INT,       "priority",sp->priority,
717                       OT_UTIME,     "schedtime", sp->runtime,
718                       OT_STRING,    "volume",  mr.VolumeName,
719                       OT_STRING,    "pool",    jcr->pool?jcr->pool->name():"",
720                       OT_STRING,    "storage", jcr->wstore?jcr->wstore->name():"",
721                       OT_END_OBJ,
722                       OT_END));
723
724
725    } else {
726       ua->send_msg(_("%-14s %-8s %3d  %-18s %-18s %s\n"),
727          level_ptr, job_type_to_str(sp->job->JobType), sp->priority, dt,
728          sp->job->name(), mr.VolumeName);
729    }
730    if (close_db) {
731       db_close_database(jcr, jcr->db);
732    }
733    jcr->db = ua->db;                  /* restore ua db to jcr */
734    jcr->setJobType(orig_jobtype);
735 }
736
737 /*
738  * Detailed listing of all scheduler jobs
739  */
740 static void llist_scheduled_jobs(UAContext *ua)
741 {
742    utime_t runtime;
743    RUN *run;
744    JOB *job;
745    int level, num_jobs = 0;
746    int priority;
747    bool hdr_printed = false;
748    char sched_name[MAX_NAME_LENGTH];
749    char job_name[MAX_NAME_LENGTH];
750    SCHED *sched;
751    int days, i, limit;
752    time_t now = time(NULL);
753    time_t next;
754    const char *level_ptr;
755
756    Dmsg0(200, "enter list_sched_jobs()\n");
757
758    i = find_arg_with_value(ua, NT_("days"));
759    if (i >= 0) {
760      days = atoi(ua->argv[i]);
761      if (((days < 0) || (days > 3000)) && !ua->api) {
762        ua->send_msg(_("Ignoring invalid value for days. Max is 3000.\n"));
763        days = 10;
764      }
765    } else {
766       days = 10;
767    }
768
769    i = find_arg_with_value(ua, NT_("limit"));
770    if (i >= 0) {
771      limit = atoi(ua->argv[i]);
772      if (((limit < 0) || (limit > 2000)) && !ua->api) {
773        ua->send_msg(_("Ignoring invalid value for limit. Max is 2000.\n"));
774        limit = 100;
775      }
776    } else {
777       limit = 100;
778    }
779
780    i = find_arg_with_value(ua, NT_("time"));
781    if (i >= 0) {
782       now = str_to_utime(ua->argv[i]);
783       if (now == 0) {
784          ua->send_msg(_("Ignoring invalid time.\n"));
785          now = time(NULL);
786       }
787    }
788
789    i = find_arg_with_value(ua, NT_("schedule"));
790    if (i >= 0) {
791       bstrncpy(sched_name, ua->argv[i], sizeof(sched_name));
792    } else {
793       sched_name[0] = 0;
794    }
795
796    i = find_arg_with_value(ua, NT_("job"));
797    if (i >= 0) {
798       bstrncpy(job_name, ua->argv[i], sizeof(job_name));
799    } else {
800       job_name[0] = 0;
801    }
802
803    /* Loop through all jobs */
804    LockRes();
805    foreach_res(job, R_JOB) {
806       sched = job->schedule;
807       if (!sched || !job->is_enabled() || (sched && !sched->is_enabled()) ||
808          (job->client && !job->client->is_enabled())) {
809          continue;                    /* no, skip this job */
810       }
811       if (job_name[0] && strcmp(job_name, job->name()) != 0) {
812          continue;
813       }
814       for (run=sched->run; run; run=run->next) {
815          next = now;
816          for (i=0; i<days; i++) {
817             struct tm tm;
818             int mday, wday, month, wom, woy, ldom;
819             char dt[MAX_TIME_LENGTH];
820             bool ok;
821
822             /* compute values for next time */
823             (void)localtime_r(&next, &tm);
824             mday = tm.tm_mday - 1;
825             wday = tm.tm_wday;
826             month = tm.tm_mon;
827             wom = mday / 7;
828             woy = tm_woy(next);                    /* get week of year */
829             ldom = tm_ldom(month, tm.tm_year + 1900);
830
831 //#define xxx_debug
832 #ifdef xxx_debug
833             Dmsg6(000, "m=%d md=%d wd=%d wom=%d woy=%d ldom=%d\n",
834                month, mday, wday, wom, woy, ldom);
835             Dmsg6(000, "bitset bsm=%d bsmd=%d bswd=%d bswom=%d bswoy=%d bsldom=%d\n",
836                bit_is_set(month, run->month),
837                bit_is_set(mday, run->mday),
838                bit_is_set(wday, run->wday),
839                bit_is_set(wom, run->wom),
840                bit_is_set(woy, run->woy),
841                bit_is_set(31, run->mday));
842 #endif
843
844             ok = (bit_is_set(mday, run->mday) &&
845                   bit_is_set(wday, run->wday) &&
846                   bit_is_set(month, run->month) &&
847                   bit_is_set(wom, run->wom) &&
848                   bit_is_set(woy, run->woy)) ||
849                  (bit_is_set(month, run->month) &&
850                   bit_is_set(31, run->mday) && mday == ldom);
851             if (!ok) {
852                next += 24 * 60 * 60;   /* Add one day */
853                continue;
854             }
855
856             level = job->JobLevel;
857             if (run->level) {
858                level = run->level;
859             }
860             switch (job->JobType) {
861             case JT_ADMIN:
862                level_ptr = "Admin";
863                break;
864             case JT_RESTORE:
865                level_ptr = "Restore";
866                break;
867             default:
868                level_ptr = level_to_str(level);
869                break;
870             }
871             priority = job->Priority;
872             if (run->Priority) {
873                priority = run->Priority;
874             }
875             if (!hdr_printed) {
876                prt_lrunhdr(ua);
877                hdr_printed = true;
878             }
879
880             for (int j=0; j < 24; j++) {
881                if (bit_is_set(j, run->hour)) {
882                   tm.tm_hour = j;
883                   tm.tm_min = run->minute;
884                   tm.tm_sec = 0;
885                   runtime = mktime(&tm);
886                   bstrftime_dn(dt, sizeof(dt), runtime);
887                   if (ua->api) {
888                      ua->send_msg(_("%-14s\t%-8s\t%3d\t%-18s\t%-18s\t%s\n"),
889                      level_ptr, job_type_to_str(job->JobType), priority, dt,
890                      job->name(), sched->name());
891                   } else {
892                      ua->send_msg(_("%-14s %-8s %3d  %-18s %-18s %s\n"),
893                      level_ptr, job_type_to_str(job->JobType), priority, dt,
894                      job->name(), sched->name());
895                   }
896                }
897             }
898             next += 24 * 60 * 60;   /* Add one day */
899             num_jobs++;
900             if (num_jobs >= limit) {
901                goto get_out;
902             }
903          }
904       } /* end loop over run pkts */
905    } /* end for loop over resources */
906 get_out:
907    UnlockRes();
908    if (num_jobs == 0 && !ua->api) {
909       ua->send_msg(_("No Scheduled Jobs.\n"));
910    }
911    if (!ua->api) ua->send_msg("====\n");
912    Dmsg0(200, "Leave ;list_sched_jobs_runs()\n");
913 }
914
915
916 /*
917  * Sort items by runtime, priority
918  */
919 static int my_compare(void *item1, void *item2)
920 {
921    sched_pkt *p1 = (sched_pkt *)item1;
922    sched_pkt *p2 = (sched_pkt *)item2;
923    if (p1->runtime < p2->runtime) {
924       return -1;
925    } else if (p1->runtime > p2->runtime) {
926       return 1;
927    }
928    if (p1->priority < p2->priority) {
929       return -1;
930    } else if (p1->priority > p2->priority) {
931       return 1;
932    }
933    return 0;
934 }
935
936 /*
937  * Find all jobs to be run in roughly the
938  *  next 24 hours.
939  */
940 static void list_scheduled_jobs(UAContext *ua)
941 {
942    OutputWriter ow(ua->api_opts);
943    utime_t runtime;
944    RUN *run;
945    JOB *job;
946    int level, num_jobs = 0;
947    int priority;
948    bool hdr_printed = false;
949    char sched_name[MAX_NAME_LENGTH];
950    dlist sched;
951    sched_pkt *sp;
952    int days, i;
953
954    Dmsg0(200, "enter list_sched_jobs()\n");
955
956    days = 1;
957    i = find_arg_with_value(ua, NT_("days"));
958    if (i >= 0) {
959      days = atoi(ua->argv[i]);
960      if (((days < 0) || (days > 500)) && !ua->api) {
961        ua->send_msg(_("Ignoring invalid value for days. Max is 500.\n"));
962        days = 1;
963      }
964    }
965    i = find_arg_with_value(ua, NT_("schedule"));
966    if (i >= 0) {
967       bstrncpy(sched_name, ua->argv[i], sizeof(sched_name));
968    } else {
969       sched_name[0] = 0;
970    }
971
972    /* Loop through all jobs */
973    LockRes();
974    foreach_res(job, R_JOB) {
975       if (!acl_access_ok(ua, Job_ACL, job->name()) || !job->is_enabled()) {
976          continue;
977       }
978       if (sched_name[0] && job->schedule &&
979           strcasecmp(job->schedule->name(), sched_name) != 0) {
980          continue;
981       }
982       for (run=NULL; (run = find_next_run(run, job, runtime, days)); ) {
983          USTORE store;
984          level = job->JobLevel;
985          if (run->level) {
986             level = run->level;
987          }
988          priority = job->Priority;
989          if (run->Priority) {
990             priority = run->Priority;
991          }
992          if (!hdr_printed) {
993             prt_runhdr(ua);
994             hdr_printed = true;
995          }
996          sp = (sched_pkt *)malloc(sizeof(sched_pkt));
997          sp->job = job;
998          sp->level = level;
999          sp->priority = priority;
1000          sp->runtime = runtime;
1001          sp->pool = run->pool;
1002          get_job_storage(&store, job, run);
1003          sp->store = store.store;
1004          Dmsg3(250, "job=%s store=%s MediaType=%s\n", job->name(), sp->store->name(), sp->store->media_type);
1005          sched.binary_insert_multiple(sp, my_compare);
1006          num_jobs++;
1007       }
1008    } /* end for loop over resources */
1009    UnlockRes();
1010    foreach_dlist(sp, &sched) {
1011       prt_runtime(ua, sp, &ow);
1012    }
1013    if (num_jobs == 0 && !ua->api) {
1014       ua->send_msg(_("No Scheduled Jobs.\n"));
1015    }
1016    if (!ua->api) ua->send_msg("====\n");
1017    Dmsg0(200, "Leave list_sched_jobs_runs()\n");
1018 }
1019
1020 static void list_running_jobs(UAContext *ua)
1021 {
1022    JCR *jcr;
1023    int njobs = 0;
1024    int i;
1025    int32_t status;
1026    const char *msg, *msgdir;
1027    char *emsg;                        /* edited message */
1028    char dt[MAX_TIME_LENGTH];
1029    char level[10];
1030    bool pool_mem = false;
1031    OutputWriter ow(ua->api_opts);
1032    JobId_t jid = 0;
1033
1034    if ((i = find_arg_with_value(ua, "jobid")) >= 0) {
1035       jid = str_to_int64(ua->argv[i]);
1036    }
1037
1038    Dmsg0(200, "enter list_run_jobs()\n");
1039
1040    if (!ua->api) {
1041       ua->send_msg(_("\nRunning Jobs:\n"));
1042       foreach_jcr(jcr) {
1043          if (jcr->JobId == 0) {      /* this is us */
1044             /* this is a console or other control job. We only show console
1045              * jobs in the status output.
1046              */
1047             if (jcr->getJobType() == JT_CONSOLE) {
1048                bstrftime_nc(dt, sizeof(dt), jcr->start_time);
1049                ua->send_msg(_("Console connected %sat %s\n"),
1050                             (ua->UA_sock && ua->UA_sock->tls)?_("using TLS "):"",
1051                             dt);
1052             }
1053             continue;
1054          }
1055       }
1056       endeach_jcr(jcr);
1057    }
1058
1059    njobs = 0; /* count the number of job really displayed */
1060    foreach_jcr(jcr) {
1061       if (jcr->JobId == 0 || !jcr->job || !acl_access_ok(ua, Job_ACL, jcr->job->name())) {
1062          continue;
1063       }
1064       /* JobId keyword found in command line */
1065       if (jid > 0 && jcr->JobId != jid) {
1066          continue;
1067       }
1068
1069       if (++njobs == 1) {
1070          /* display the header for the first job */
1071          if (!ua->api) {
1072             ua->send_msg(_(" JobId  Type Level     Files     Bytes  Name              Status\n"));
1073             ua->send_msg(_("======================================================================\n"));
1074
1075          } else if (ua->api > 1) {
1076             ua->send_msg(ow.start_group("running", false));
1077          }
1078       }
1079       status = jcr->JobStatus;
1080       switch (status) {
1081       case JS_Created:
1082          msg = _("is waiting execution");
1083          break;
1084       case JS_Running:
1085          msg = _("is running");
1086          break;
1087       case JS_Blocked:
1088          msg = _("is blocked");
1089          break;
1090       case JS_Terminated:
1091          msg = _("has terminated");
1092          break;
1093       case JS_Warnings:
1094          msg = _("has terminated with warnings");
1095          break;
1096       case JS_Incomplete:
1097          msg = _("has terminated in incomplete state");
1098          break;
1099       case JS_ErrorTerminated:
1100          msg = _("has erred");
1101          break;
1102       case JS_Error:
1103          msg = _("has errors");
1104          break;
1105       case JS_FatalError:
1106          msg = _("has a fatal error");
1107          break;
1108       case JS_Differences:
1109          msg = _("has verify differences");
1110          break;
1111       case JS_Canceled:
1112          msg = _("has been canceled");
1113          break;
1114       case JS_WaitFD:
1115          emsg = (char *) get_pool_memory(PM_FNAME);
1116          if (!jcr->client) {
1117             Mmsg(emsg, _("is waiting on Client"));
1118          } else {
1119             Mmsg(emsg, _("is waiting on Client %s"), jcr->client->name());
1120          }
1121          pool_mem = true;
1122          msg = emsg;
1123          break;
1124       case JS_WaitSD:
1125          emsg = (char *) get_pool_memory(PM_FNAME);
1126          if (jcr->wstore) {
1127             Mmsg(emsg, _("is waiting on Storage \"%s\""), jcr->wstore->name());
1128          } else if (jcr->rstore) {
1129             Mmsg(emsg, _("is waiting on Storage \"%s\""), jcr->rstore->name());
1130          } else {
1131             Mmsg(emsg, _("is waiting on Storage"));
1132          }
1133          pool_mem = true;
1134          msg = emsg;
1135          break;
1136       case JS_WaitStoreRes:
1137          msg = _("is waiting on max Storage jobs");
1138          break;
1139       case JS_WaitClientRes:
1140          msg = _("is waiting on max Client jobs");
1141          break;
1142       case JS_WaitJobRes:
1143          msg = _("is waiting on max Job jobs");
1144          break;
1145       case JS_WaitMaxJobs:
1146          msg = _("is waiting on max total jobs");
1147          break;
1148       case JS_WaitStartTime:
1149          emsg = (char *) get_pool_memory(PM_FNAME);
1150          Mmsg(emsg, _("is waiting for its start time (%s)"),
1151               bstrftime_ny(dt, sizeof(dt), jcr->sched_time));
1152          pool_mem = true;
1153          msg = emsg;
1154          break;
1155       case JS_WaitPriority:
1156          msg = _("is waiting for higher priority jobs to finish");
1157          break;
1158       case JS_WaitDevice:
1159          msg = _("is waiting for a Shared Storage device");
1160          break;
1161       case JS_DataCommitting:
1162          msg = _("SD committing Data");
1163          break;
1164       case JS_DataDespooling:
1165          msg = _("SD despooling Data");
1166          break;
1167       case JS_AttrDespooling:
1168          msg = _("SD despooling Attributes");
1169          break;
1170       case JS_AttrInserting:
1171          msg = _("Dir inserting Attributes");
1172          break;
1173
1174       default:
1175          emsg = (char *)get_pool_memory(PM_FNAME);
1176          Mmsg(emsg, _("is in unknown state %c"), jcr->JobStatus);
1177          pool_mem = true;
1178          msg = emsg;
1179          break;
1180       }
1181       msgdir = msg;             /* Keep it to know if we update the status variable */
1182       /*
1183        * Now report Storage daemon status code
1184        */
1185       switch (jcr->SDJobStatus) {
1186       case JS_WaitMount:
1187          if (pool_mem) {
1188             free_pool_memory(emsg);
1189             pool_mem = false;
1190          }
1191          msg = _("is waiting for a mount request");
1192          break;
1193       case JS_WaitMedia:
1194          if (pool_mem) {
1195             free_pool_memory(emsg);
1196             pool_mem = false;
1197          }
1198          msg = _("is waiting for an appendable Volume");
1199          break;
1200       case JS_WaitFD:
1201          /* Special case when JobStatus=JS_WaitFD, we don't have a FD link yet 
1202           * we need to stay in WaitFD status See bee mantis #1414 */
1203          if (jcr->JobStatus != JS_WaitFD) {
1204             if (!pool_mem) {
1205                emsg = (char *)get_pool_memory(PM_FNAME);
1206                pool_mem = true;
1207             }
1208             if (!jcr->client || !jcr->wstore) {
1209                Mmsg(emsg, _("is waiting for Client to connect to Storage daemon"));
1210             } else {
1211                Mmsg(emsg, _("is waiting for Client %s to connect to Storage %s"),
1212                     jcr->client->name(), jcr->wstore->name());
1213             }
1214             msg = emsg;
1215          }
1216         break;
1217       case JS_DataCommitting:
1218          msg = _("SD committing Data");
1219          break;
1220       case JS_DataDespooling:
1221          msg = _("SD despooling Data");
1222          break;
1223       case JS_AttrDespooling:
1224          msg = _("SD despooling Attributes");
1225          break;
1226       case JS_AttrInserting:
1227          msg = _("Dir inserting Attributes");
1228          break;
1229       }
1230       if (msg != msgdir) {
1231          status = jcr->SDJobStatus;
1232       }
1233       switch (jcr->getJobType()) {
1234       case JT_ADMIN:
1235          bstrncpy(level, "Admin", sizeof(level));
1236          break;
1237       case JT_RESTORE:
1238          bstrncpy(level, "Restore", sizeof(level));
1239          break;
1240       default:
1241          bstrncpy(level, level_to_str(jcr->getJobLevel()), sizeof(level));
1242          level[7] = 0;
1243          break;
1244       }
1245
1246       if (ua->api == 1) {
1247          bash_spaces(jcr->comment);
1248          ua->send_msg(_("%6d\t%-6s\t%-20s\t%s\t%s\n"),
1249                       jcr->JobId, level, jcr->Job, msg, jcr->comment);
1250          unbash_spaces(jcr->comment);
1251
1252       } else if (ua->api > 1) {
1253          ua->send_msg("%s", ow.get_output(OT_CLEAR,
1254                          OT_START_OBJ,
1255                          OT_INT32,   "jobid",     jcr->JobId,
1256                          OT_JOBLEVEL,"level",     jcr->getJobLevel(),
1257                          OT_JOBTYPE, "type",      jcr->getJobType(),
1258                          OT_JOBSTATUS,"status",   status,
1259                          OT_STRING,  "status_desc",msg,
1260                          OT_STRING,  "comment",   jcr->comment,
1261                          OT_SIZE,    "jobbytes",  jcr->JobBytes,
1262                          OT_INT32,   "jobfiles",  jcr->JobFiles,
1263                          OT_STRING,  "job",       jcr->Job,
1264                          OT_STRING,  "name",      jcr->job->name(),
1265                          OT_STRING,  "clientname",jcr->client?jcr->client->name():"",
1266                          OT_STRING,  "fileset",   jcr->fileset?jcr->fileset->name():"",
1267                          OT_STRING,  "storage",   jcr->wstore?jcr->wstore->name():"",
1268                          OT_STRING,  "rstorage",  jcr->rstore?jcr->rstore->name():"",
1269                          OT_UTIME,   "schedtime", jcr->sched_time,
1270                          OT_UTIME,   "starttime", jcr->start_time,
1271                          OT_INT32,   "priority",  jcr->JobPriority,
1272                          OT_INT32,   "errors",    jcr->JobErrors,
1273                          OT_END_OBJ,
1274                          OT_END));
1275
1276       } else {
1277          char b1[50], b2[50], b3[50];
1278          level[4] = 0;
1279          bstrncpy(b1, job_type_to_str(jcr->getJobType()), sizeof(b1));
1280          b1[4] = 0;
1281          ua->send_msg(_("%6d  %-4s %-3s %10s %10s %-17s %s\n"),
1282             jcr->JobId, b1, level,
1283             edit_uint64_with_commas(jcr->JobFiles, b2),
1284             edit_uint64_with_suffix(jcr->JobBytes, b3),
1285             jcr->job->name(), msg);
1286       }
1287
1288       if (pool_mem) {
1289          free_pool_memory(emsg);
1290          pool_mem = false;
1291       }
1292    }
1293    endeach_jcr(jcr);
1294
1295    if (njobs == 0) {
1296       /* Note the following message is used in regress -- don't change */
1297       ua->send_msg(_("No Jobs running.\n====\n"));
1298       Dmsg0(200, "leave list_run_jobs()\n");
1299       return;
1300    } else {
1301       /* display a closing header */
1302       if (!ua->api) {
1303          ua->send_msg("====\n");
1304       } else if (ua->api > 1) {
1305          ua->send_msg(ow.end_group(false));
1306       }
1307    }
1308    Dmsg0(200, "leave list_run_jobs()\n");
1309 }
1310
1311 static void list_terminated_jobs(UAContext *ua)
1312 {
1313    char dt[MAX_TIME_LENGTH], b1[30], b2[30];
1314    char level[10];
1315    OutputWriter ow(ua->api_opts);
1316
1317    if (last_jobs->empty()) {
1318       if (!ua->api) ua->send_msg(_("No Terminated Jobs.\n"));
1319       return;
1320    }
1321    lock_last_jobs_list();
1322    struct s_last_job *je;
1323    if (!ua->api) {
1324       ua->send_msg(_("\nTerminated Jobs:\n"));
1325       ua->send_msg(_(" JobId  Level      Files    Bytes   Status   Finished        Name \n"));
1326       ua->send_msg(_("====================================================================\n"));
1327    } else if (ua->api > 1) {
1328       ua->send_msg(ow.start_group("terminated"));
1329    }
1330    foreach_dlist(je, last_jobs) {
1331       char JobName[MAX_NAME_LENGTH];
1332       const char *termstat;
1333
1334       bstrncpy(JobName, je->Job, sizeof(JobName));
1335       /* There are three periods after the Job name */
1336       char *p;
1337       for (int i=0; i<3; i++) {
1338          if ((p=strrchr(JobName, '.')) != NULL) {
1339             *p = 0;
1340          }
1341       }
1342
1343       if (!acl_access_ok(ua, Job_ACL, JobName)) {
1344          continue;
1345       }
1346
1347       bstrftime_nc(dt, sizeof(dt), je->end_time);
1348       switch (je->JobType) {
1349       case JT_ADMIN:
1350          bstrncpy(level, "Admin", sizeof(level));
1351          break;
1352       case JT_RESTORE:
1353          bstrncpy(level, "Restore", sizeof(level));
1354          break;
1355       default:
1356          bstrncpy(level, level_to_str(je->JobLevel), sizeof(level));
1357          level[4] = 0;
1358          break;
1359       }
1360       switch (je->JobStatus) {
1361       case JS_Created:
1362          termstat = _("Created");
1363          break;
1364       case JS_FatalError:
1365       case JS_ErrorTerminated:
1366          termstat = _("Error");
1367          break;
1368       case JS_Differences:
1369          termstat = _("Diffs");
1370          break;
1371       case JS_Canceled:
1372          termstat = _("Cancel");
1373          break;
1374       case JS_Terminated:
1375          termstat = _("OK");
1376          break;
1377       case JS_Warnings:
1378          termstat = _("OK -- with warnings");
1379          break;
1380       case JS_Incomplete:
1381          termstat = _("Incomplete");
1382          break;
1383       default:
1384          termstat = _("Other");
1385          break;
1386       }
1387       if (ua->api == 1) {
1388          ua->send_msg(_("%7d\t%-6s\t%8s\t%10s\t%-7s\t%-8s\t%s\n"),
1389             je->JobId,
1390             level,
1391             edit_uint64_with_commas(je->JobFiles, b1),
1392             edit_uint64_with_suffix(je->JobBytes, b2),
1393             termstat,
1394             dt, JobName);
1395       } else if (ua->api > 1) {
1396          ua->send_msg("%s",
1397                       ow.get_output(OT_CLEAR,
1398                                     OT_START_OBJ,
1399                                     OT_INT32,   "jobid",     je->JobId,
1400                                     OT_JOBLEVEL,"level",     je->JobLevel,
1401                                     OT_JOBTYPE, "type",      je->JobType,
1402                                     OT_JOBSTATUS,"status",   je->JobStatus,
1403                                     OT_STRING,  "status_desc",termstat,
1404                                     OT_SIZE,    "jobbytes",  je->JobBytes,
1405                                     OT_INT32,   "jobfiles",  je->JobFiles,
1406                                     OT_STRING,  "job",       je->Job,
1407                                     OT_UTIME,   "starttime", je->start_time,
1408                                     OT_UTIME,   "endtime",   je->end_time,
1409                                     OT_INT32,   "errors",    je->Errors,
1410                                     OT_END_OBJ,
1411                                     OT_END));
1412
1413       } else {
1414          ua->send_msg(_("%6d  %-7s %8s %10s  %-7s  %-8s %s\n"),
1415             je->JobId,
1416             level,
1417             edit_uint64_with_commas(je->JobFiles, b1),
1418             edit_uint64_with_suffix(je->JobBytes, b2),
1419             termstat,
1420             dt, JobName);
1421       }
1422    }
1423    if (!ua->api) {
1424       ua->send_msg(_("\n"));
1425    } else if (ua->api > 1) {
1426       ua->send_msg(ow.end_group(false));
1427    }
1428    unlock_last_jobs_list();
1429 }