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