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