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