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