]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/stored/status.c
kes Print the Volume purged message only for real jobs to keep
[bacula/bacula] / bacula / src / stored / status.c
1 /*
2  *  This file handles the status command
3  *
4  *     Kern Sibbald, May MMIII
5  *
6  *   Version $Id$
7  *
8  */
9 /*
10    Copyright (C) 2003-2006 Kern Sibbald
11
12    This program is free software; you can redistribute it and/or
13    modify it under the terms of the GNU General Public License
14    version 2 as amended with additional clauses defined in the
15    file LICENSE in the main source directory.
16
17    This program is distributed in the hope that it will be useful,
18    but WITHOUT ANY WARRANTY; without even the implied warranty of
19    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
20    the file LICENSE for additional details.
21
22  */
23
24 #include "bacula.h"
25 #include "stored.h"
26
27 /* Exported variables */
28
29 /* Imported variables */
30 extern BSOCK *filed_chan;
31 extern int r_first, r_last;
32 extern struct s_res resources[];
33
34 /* Static variables */
35 static char qstatus[] = ".status %127s\n";
36
37 static char OKqstatus[]   = "3000 OK .status\n";
38 static char DotStatusJob[] = "JobId=%d JobStatus=%c JobErrors=%d\n";
39
40
41 /* Forward referenced functions */
42 static void send_blocked_status(DEVICE *dev, void sendit(const char *msg, int len, void *sarg), void *arg);
43 static void list_terminated_jobs(void sendit(const char *msg, int len, void *sarg), void *arg);
44 static void list_running_jobs(void sendit(const char *msg, int len, void *sarg), void *arg);
45 static void list_jobs_waiting_on_reservation(void sendit(const char *msg, int len, void *sarg), void *arg);
46
47 static const char *level_to_str(int level);
48
49 /*
50  * Status command from Director
51  */
52 void output_status(void sendit(const char *msg, int len, void *sarg), void *arg)
53 {
54    DEVRES *device;
55    AUTOCHANGER *changer;
56    DEVICE *dev;
57    char dt[MAX_TIME_LENGTH];
58    char *msg, b1[35], b2[35], b3[35], b4[35];
59    int bpb;
60    int len;
61
62    msg = (char *)get_pool_memory(PM_MESSAGE);
63
64    len = Mmsg(msg, _("%s Version: %s (%s) %s %s %s\n"), 
65               my_name, VERSION, BDATE, HOST_OS, DISTNAME, DISTVER);
66    sendit(msg, len, arg);
67
68    bstrftime_nc(dt, sizeof(dt), daemon_start_time);
69
70
71    len = Mmsg(msg, _("Daemon started %s, %d Job%s run since started.\n"),
72         dt, num_jobs_run, num_jobs_run == 1 ? "" : "s");
73    sendit(msg, len, arg);
74
75    len = Mmsg(msg, _(" Heap: bytes=%s max_bytes=%s bufs=%s max_bufs=%s\n"),
76          edit_uint64_with_commas(sm_bytes, b1),
77          edit_uint64_with_commas(sm_max_bytes, b2),
78          edit_uint64_with_commas(sm_buffers, b3),
79          edit_uint64_with_commas(sm_max_buffers, b4));
80    sendit(msg, len, arg);
81
82    /*
83     * List running jobs
84     */
85    list_running_jobs(sendit, arg);
86
87    /*
88     * List jobs stuck in reservation system
89     */
90    list_jobs_waiting_on_reservation(sendit, arg);
91
92    /*
93     * List terminated jobs
94     */
95    list_terminated_jobs(sendit, arg);
96
97    /*
98     * List devices
99     */
100    len = Mmsg(msg, _("\nDevice status:\n"));
101    sendit(msg, len, arg);
102
103    foreach_res(changer, R_AUTOCHANGER) {
104       len = Mmsg(msg, _("Autochanger \"%s\" with devices:\n"),
105          changer->hdr.name);
106       sendit(msg, len, arg);
107
108       foreach_alist(device, changer->device) {
109          if (device->dev) {
110             len = Mmsg(msg, "   %s\n", device->dev->print_name());
111             sendit(msg, len, arg);
112          } else {
113             len = Mmsg(msg, "   %s\n", device->hdr.name);
114             sendit(msg, len, arg);
115          }
116       }
117    }
118    foreach_res(device, R_DEVICE) {
119       dev = device->dev;
120       if (dev && dev->is_open()) {
121          if (dev->is_labeled()) {
122             len = Mmsg(msg, _("Device %s is mounted with Volume=\"%s\" Pool=\"%s\"\n"),
123                dev->print_name(), dev->VolHdr.VolumeName, 
124                dev->pool_name[0]?dev->pool_name:"*unknown*");
125             sendit(msg, len, arg);
126          } else {
127             len = Mmsg(msg, _("Device %s open but no Bacula volume is currently mounted.\n"), 
128                dev->print_name());
129             sendit(msg, len, arg);
130          }
131          send_blocked_status(dev, sendit, arg);
132          if (dev->can_append()) {
133             bpb = dev->VolCatInfo.VolCatBlocks;
134             if (bpb <= 0) {
135                bpb = 1;
136             }
137             bpb = dev->VolCatInfo.VolCatBytes / bpb;
138             len = Mmsg(msg, _("    Total Bytes=%s Blocks=%s Bytes/block=%s\n"),
139                edit_uint64_with_commas(dev->VolCatInfo.VolCatBytes, b1),
140                edit_uint64_with_commas(dev->VolCatInfo.VolCatBlocks, b2),
141                edit_uint64_with_commas(bpb, b3));
142             sendit(msg, len, arg);
143          } else {  /* reading */
144             bpb = dev->VolCatInfo.VolCatReads;
145             if (bpb <= 0) {
146                bpb = 1;
147             }
148             if (dev->VolCatInfo.VolCatRBytes > 0) {
149                bpb = dev->VolCatInfo.VolCatRBytes / bpb;
150             } else {
151                bpb = 0;
152             }
153             len = Mmsg(msg, _("    Total Bytes Read=%s Blocks Read=%s Bytes/block=%s\n"),
154                edit_uint64_with_commas(dev->VolCatInfo.VolCatRBytes, b1),
155                edit_uint64_with_commas(dev->VolCatInfo.VolCatReads, b2),
156                edit_uint64_with_commas(bpb, b3));
157             sendit(msg, len, arg);
158          }
159          len = Mmsg(msg, _("    Positioned at File=%s Block=%s\n"),
160             edit_uint64_with_commas(dev->file, b1),
161             edit_uint64_with_commas(dev->block_num, b2));
162          sendit(msg, len, arg);
163
164       } else {
165          if (dev) {
166             len = Mmsg(msg, _("Device %s is not open.\n"), dev->print_name());
167             sendit(msg, len, arg);
168             send_blocked_status(dev, sendit, arg);
169         } else {
170             len = Mmsg(msg, _("Device \"%s\" is not open or does not exist.\n"), device->hdr.name);
171             sendit(msg, len, arg);
172          }
173       }
174    }
175    sendit("====\n\n", 6, arg);
176    len = Mmsg(msg, _("In Use Volume status:\n"));
177    sendit(msg, len, arg);
178    list_volumes(sendit, arg);
179    sendit("====\n\n", 6, arg);
180
181 #ifdef xxx
182    if (debug_level > 10) {
183       bnet_fsend(user, _("====\n\n"));
184       dump_resource(R_DEVICE, resources[R_DEVICE-r_first].res_head, sendit, user);
185       bnet_fsend(user, _("====\n\n"));
186    }
187 #endif
188
189    list_spool_stats(sendit, arg);
190
191    free_pool_memory(msg);
192 }
193
194 static void send_blocked_status(DEVICE *dev, void sendit(const char *msg, int len, void *sarg), void *arg)
195 {
196    char *msg;
197    int len;
198
199    msg = (char *)get_pool_memory(PM_MESSAGE);
200
201    if (!dev) {
202       len = Mmsg(msg, _("No DEVICE structure.\n\n"));
203       sendit(msg, len, arg);
204       free_pool_memory(msg);
205       return;
206    }
207    switch (dev->dev_blocked) {
208    case BST_UNMOUNTED:
209       len = Mmsg(msg, _("    Device is BLOCKED. User unmounted.\n"));
210       sendit(msg, len, arg);
211       break;
212    case BST_UNMOUNTED_WAITING_FOR_SYSOP:
213       len = Mmsg(msg, _("    Device is BLOCKED. User unmounted during wait for media/mount.\n"));
214       sendit(msg, len, arg);
215       break;
216    case BST_WAITING_FOR_SYSOP:
217       {
218          dlist *dcrs = dev->attached_dcrs;
219          bool found_jcr = false;
220
221          if (dcrs != NULL) {
222             DCR *dcr;
223
224             for (dcr = (DCR *)dcrs->first(); dcr != NULL; dcr = (DCR *)dcrs->next(dcr)) {
225                if (dcr->jcr->JobStatus == JS_WaitMount) {
226                   len = Mmsg(msg, _("    Device is BLOCKED waiting for mount of volume \"%s\".\n"),
227                      dcr->VolumeName);
228                   sendit(msg, len, arg);
229                   found_jcr = true;
230                }
231             }
232          }
233
234          if (!found_jcr) {
235             len = Mmsg(msg, _("    Device is BLOCKED waiting for media.\n"));
236             sendit(msg, len, arg);
237          }
238       }
239       break;
240    case BST_DOING_ACQUIRE:
241       len = Mmsg(msg, _("    Device is being initialized.\n"));
242       sendit(msg, len, arg);
243       break;
244    case BST_WRITING_LABEL:
245       len = Mmsg(msg, _("    Device is blocked labeling a Volume.\n"));
246       sendit(msg, len, arg);
247       break;
248    default:
249       break;
250    }
251    /* Send autochanger slot status */
252    if (dev->is_autochanger()) {
253       if (dev->Slot > 0) {
254          len = Mmsg(msg, _("    Slot %d is loaded in drive %d.\n"), 
255             dev->Slot, dev->drive_index);
256          sendit(msg, len, arg);
257       } else if (dev->Slot == 0) {
258          len = Mmsg(msg, _("    Drive %d is not loaded.\n"), dev->drive_index);
259          sendit(msg, len, arg);
260       } else {
261          len = Mmsg(msg, _("    Drive %d status unknown.\n"), dev->drive_index);
262          sendit(msg, len, arg);
263       }
264    }
265    if (debug_level > 1) {
266       len = Mmsg(msg, _("Configured device capabilities:\n"));
267       sendit(msg, len, arg);
268
269       len = Mmsg(msg, "%sEOF %sBSR %sBSF %sFSR %sFSF %sEOM %sREM %sRACCESS %sAUTOMOUNT %sLABEL %sANONVOLS %sALWAYSOPEN\n",
270          dev->capabilities & CAP_EOF ? "" : "!", 
271          dev->capabilities & CAP_BSR ? "" : "!", 
272          dev->capabilities & CAP_BSF ? "" : "!", 
273          dev->capabilities & CAP_FSR ? "" : "!", 
274          dev->capabilities & CAP_FSF ? "" : "!", 
275          dev->capabilities & CAP_EOM ? "" : "!", 
276          dev->capabilities & CAP_REM ? "" : "!", 
277          dev->capabilities & CAP_RACCESS ? "" : "!",
278          dev->capabilities & CAP_AUTOMOUNT ? "" : "!", 
279          dev->capabilities & CAP_LABEL ? "" : "!", 
280          dev->capabilities & CAP_ANONVOLS ? "" : "!", 
281          dev->capabilities & CAP_ALWAYSOPEN ? "" : "!");
282       sendit(msg, len, arg);
283
284       len = Mmsg(msg, _("Device state:\n"));
285       sendit(msg, len, arg);
286
287       len = Mmsg(msg, "%sOPENED %sTAPE %sLABEL %sMALLOC %sAPPEND %sREAD %sEOT %sWEOT %sEOF %sNEXTVOL %sSHORT %sMOUNTED\n", 
288          dev->is_open() ? "" : "!", 
289          dev->is_tape() ? "" : "!", 
290          dev->is_labeled() ? "" : "!", 
291          dev->state & ST_MALLOC ? "" : "!", 
292          dev->can_append() ? "" : "!", 
293          dev->can_read() ? "" : "!", 
294          dev->at_eot() ? "" : "!", 
295          dev->state & ST_WEOT ? "" : "!", 
296          dev->at_eof() ? "" : "!", 
297          dev->state & ST_NEXTVOL ? "" : "!", 
298          dev->state & ST_SHORT ? "" : "!", 
299          dev->state & ST_MOUNTED ? "" : "!");
300       sendit(msg, len, arg);
301
302       len = Mmsg(msg, _("num_writers=%d block=%d\n\n"), dev->num_writers, dev->dev_blocked);
303       sendit(msg, len, arg);
304
305       len = Mmsg(msg, _("Device parameters:\n"));
306       sendit(msg, len, arg);
307
308       len = Mmsg(msg, _("Archive name: %s Device name: %s\n"), dev->archive_name(),
309          dev->name());
310       sendit(msg, len, arg);
311
312       len = Mmsg(msg, _("File=%u block=%u\n"), dev->file, dev->block_num);
313       sendit(msg, len, arg);
314
315       len = Mmsg(msg, _("Min block=%u Max block=%u\n"), dev->min_block_size, dev->max_block_size);
316       sendit(msg, len, arg);
317    }
318
319    free_pool_memory(msg);
320 }
321
322 static void list_running_jobs(void sendit(const char *msg, int len, void *sarg), void *arg)
323 {
324    bool found = false;
325    int bps, sec;
326    JCR *jcr;
327    DCR *dcr, *rdcr;
328    char JobName[MAX_NAME_LENGTH];
329    char *msg, b1[30], b2[30], b3[30];
330    int len;
331
332    msg = (char *)get_pool_memory(PM_MESSAGE);
333
334    len = Mmsg(msg, _("\nRunning Jobs:\n"));
335    sendit(msg, len, arg);
336
337    foreach_jcr(jcr) {
338       if (jcr->JobStatus == JS_WaitFD) {
339          len = Mmsg(msg, _("%s Job %s waiting for Client connection.\n"),
340             job_type_to_str(jcr->JobType), jcr->Job);
341          sendit(msg, len, arg);
342       }
343       dcr = jcr->dcr;
344       rdcr = jcr->read_dcr;
345       if ((dcr && dcr->device) || rdcr && rdcr->device) {
346          bstrncpy(JobName, jcr->Job, sizeof(JobName));
347          /* There are three periods after the Job name */
348          char *p;
349          for (int i=0; i<3; i++) {
350             if ((p=strrchr(JobName, '.')) != NULL) {
351                *p = 0;
352             }
353          }
354          if (rdcr && rdcr->device) {
355             len = Mmsg(msg, _("Reading: %s %s job %s JobId=%d Volume=\"%s\"\n"
356                             "    pool=\"%s\" device=\"%s\"\n"),
357                    job_level_to_str(jcr->JobLevel),
358                    job_type_to_str(jcr->JobType),
359                    JobName,
360                    jcr->JobId,
361                    rdcr->VolumeName,
362                    rdcr->pool_name,
363                    rdcr->dev?rdcr->dev->print_name(): 
364                             rdcr->device->device_name);
365             sendit(msg, len, arg);
366          }
367          if (dcr && dcr->device) {
368             len = Mmsg(msg, _("Writing: %s %s job %s JobId=%d Volume=\"%s\"\n"
369                             "    pool=\"%s\" device=\"%s\"\n"),
370                    job_level_to_str(jcr->JobLevel),
371                    job_type_to_str(jcr->JobType),
372                    JobName,
373                    jcr->JobId,
374                    dcr->VolumeName,
375                    dcr->pool_name,
376                    dcr->dev?dcr->dev->print_name(): 
377                             dcr->device->device_name);
378             sendit(msg, len, arg);
379          }
380          sec = time(NULL) - jcr->run_time;
381          if (sec <= 0) {
382             sec = 1;
383          }
384          bps = jcr->JobBytes / sec;
385          len = Mmsg(msg, _("    Files=%s Bytes=%s Bytes/sec=%s\n"),
386             edit_uint64_with_commas(jcr->JobFiles, b1),
387             edit_uint64_with_commas(jcr->JobBytes, b2),
388             edit_uint64_with_commas(bps, b3));
389          sendit(msg, len, arg);
390          found = true;
391 #ifdef DEBUG
392          if (jcr->file_bsock) {
393             len = Mmsg(msg, _("    FDReadSeqNo=%s in_msg=%u out_msg=%d fd=%d\n"),
394                edit_uint64_with_commas(jcr->file_bsock->read_seqno, b1),
395                jcr->file_bsock->in_msg_no, jcr->file_bsock->out_msg_no,
396                jcr->file_bsock->fd);
397             sendit(msg, len, arg);
398          } else {
399             len = Mmsg(msg, _("    FDSocket closed\n"));
400             sendit(msg, len, arg);
401          }
402 #endif
403       }
404    }
405    endeach_jcr(jcr);
406
407    if (!found) {
408       len = Mmsg(msg, _("No Jobs running.\n"));
409       sendit(msg, len, arg);
410    }
411    sendit("====\n", 5, arg);
412
413    free_pool_memory(msg);
414 }
415
416 static void list_jobs_waiting_on_reservation(void sendit(const char *msg, int len, void *sarg), void *arg)
417
418    JCR *jcr;
419    char *msg;
420
421    msg = _("\nJobs waiting to reserve a drive:\n");
422    sendit(msg, strlen(msg), arg);
423
424    foreach_jcr(jcr) {
425       if (!jcr->reserve_msgs) {
426          continue;
427       }
428       send_drive_reserve_messages(jcr, sendit, arg);
429    }
430    endeach_jcr(jcr);
431
432    sendit("====\n", 5, arg);
433 }
434
435
436 static void list_terminated_jobs(void sendit(const char *msg, int len, void *sarg), void *arg)
437 {
438    char dt[MAX_TIME_LENGTH], b1[30], b2[30];
439    char level[10];
440    struct s_last_job *je;
441    const char *msg;
442
443    msg =  _("\nTerminated Jobs:\n");
444    sendit(msg, strlen(msg), arg);
445    if (last_jobs->size() == 0) {
446       sendit("====\n", 5, arg);
447       return;
448    }
449    lock_last_jobs_list();
450    msg =  _(" JobId  Level    Files      Bytes   Status   Finished        Name \n");
451    sendit(msg, strlen(msg), arg);
452    msg =  _("===================================================================\n");
453    sendit(msg, strlen(msg), arg);
454    foreach_dlist(je, last_jobs) {
455       char JobName[MAX_NAME_LENGTH];
456       const char *termstat;
457       char buf[1000];
458
459       bstrftime_nc(dt, sizeof(dt), je->end_time);
460       switch (je->JobType) {
461       case JT_ADMIN:
462       case JT_RESTORE:
463          bstrncpy(level, "    ", sizeof(level));
464          break;
465       default:
466          bstrncpy(level, level_to_str(je->JobLevel), sizeof(level));
467          level[4] = 0;
468          break;
469       }
470       switch (je->JobStatus) {
471       case JS_Created:
472          termstat = _("Created");
473          break;
474       case JS_FatalError:
475       case JS_ErrorTerminated:
476          termstat = _("Error");
477          break;
478       case JS_Differences:
479          termstat = _("Diffs");
480          break;
481       case JS_Canceled:
482          termstat = _("Cancel");
483          break;
484       case JS_Terminated:
485          termstat = _("OK");
486          break;
487       default:
488          termstat = _("Other");
489          break;
490       }
491       bstrncpy(JobName, je->Job, sizeof(JobName));
492       /* There are three periods after the Job name */
493       char *p;
494       for (int i=0; i<3; i++) {
495          if ((p=strrchr(JobName, '.')) != NULL) {
496             *p = 0;
497          }
498       }
499       bsnprintf(buf, sizeof(buf), _("%6d  %-6s %8s %10s  %-7s  %-8s %s\n"),
500          je->JobId,
501          level,
502          edit_uint64_with_commas(je->JobFiles, b1),
503          edit_uint64_with_suffix(je->JobBytes, b2),
504          termstat,
505          dt, JobName);
506       sendit(buf, strlen(buf), arg);
507    }
508    unlock_last_jobs_list();
509    sendit("====\n", 5, arg);
510 }
511
512 /*
513  * Convert Job Level into a string
514  */
515 static const char *level_to_str(int level)
516 {
517    const char *str;
518
519    switch (level) {
520    case L_BASE:
521       str = _("Base");
522    case L_FULL:
523       str = _("Full");
524       break;
525    case L_INCREMENTAL:
526       str = _("Incremental");
527       break;
528    case L_DIFFERENTIAL:
529       str = _("Differential");
530       break;
531    case L_SINCE:
532       str = _("Since");
533       break;
534    case L_VERIFY_CATALOG:
535       str = _("Verify Catalog");
536       break;
537    case L_VERIFY_INIT:
538       str = _("Init Catalog");
539       break;
540    case L_VERIFY_VOLUME_TO_CATALOG:
541       str = _("Volume to Catalog");
542       break;
543    case L_VERIFY_DISK_TO_CATALOG:
544       str = _("Disk to Catalog");
545       break;
546    case L_VERIFY_DATA:
547       str = _("Data");
548       break;
549    case L_NONE:
550       str = " ";
551       break;
552    default:
553       str = _("Unknown Job Level");
554       break;
555    }
556    return str;
557 }
558
559 /*
560  * Send to Director
561  */
562 static void bsock_sendit(const char *msg, int len, void *arg)
563 {
564    BSOCK *user = (BSOCK *)arg;
565
566    memcpy(user->msg, msg, len+1);
567    user->msglen = len+1;
568    bnet_send(user);
569 }
570
571 /*
572  * Status command from Director
573  */
574 bool status_cmd(JCR *jcr)
575 {
576    BSOCK *user = jcr->dir_bsock;
577
578    bnet_fsend(user, "\n");
579    output_status(bsock_sendit, (void *)user);
580
581    bnet_sig(user, BNET_EOD);
582    return 1;
583 }
584
585 /*
586  * .status command from Director
587  */
588 bool qstatus_cmd(JCR *jcr)
589 {
590    BSOCK *dir = jcr->dir_bsock;
591    POOL_MEM time;
592    JCR *njcr;
593    s_last_job* job;
594
595    if (sscanf(dir->msg, qstatus, time.c_str()) != 1) {
596       pm_strcpy(jcr->errmsg, dir->msg);
597       Jmsg1(jcr, M_FATAL, 0, _("Bad .status command: %s\n"), jcr->errmsg);
598       bnet_fsend(dir, _("3900 Bad .status command, missing argument.\n"));
599       bnet_sig(dir, BNET_EOD);
600       return false;
601    }
602    unbash_spaces(time);
603
604    if (strcmp(time.c_str(), "current") == 0) {
605       bnet_fsend(dir, OKqstatus, time.c_str());
606       foreach_jcr(njcr) {
607          if (njcr->JobId != 0) {
608             bnet_fsend(dir, DotStatusJob, njcr->JobId, njcr->JobStatus, njcr->JobErrors);
609          }
610       }
611       endeach_jcr(njcr);
612    } else if (strcmp(time.c_str(), "last") == 0) {
613       bnet_fsend(dir, OKqstatus, time.c_str());
614       if ((last_jobs) && (last_jobs->size() > 0)) {
615          job = (s_last_job*)last_jobs->last();
616          bnet_fsend(dir, DotStatusJob, job->JobId, job->JobStatus, job->Errors);
617       }
618    } else {
619       pm_strcpy(jcr->errmsg, dir->msg);
620       Jmsg1(jcr, M_FATAL, 0, _("Bad .status command: %s\n"), jcr->errmsg);
621       bnet_fsend(dir, _("3900 Bad .status command, wrong argument.\n"));
622       bnet_sig(dir, BNET_EOD);
623       return false;
624    }
625    bnet_sig(dir, BNET_EOD);
626    return true;
627 }
628
629 #if defined(HAVE_WIN32)
630 int bacstat = 0;
631
632 char *bac_status(char *buf, int buf_len)
633 {
634    JCR *njcr;
635    const char *termstat = _("Bacula Storage: Idle");
636    struct s_last_job *job;
637    int stat = 0;                      /* Idle */
638
639    if (!last_jobs) {
640       goto done;
641    }
642    Dmsg0(1000, "Begin bac_status jcr loop.\n");
643    foreach_jcr(njcr) {
644       if (njcr->JobId != 0) {
645          stat = JS_Running;
646          termstat = _("Bacula Storage: Running");
647          break;
648       }
649    }
650    endeach_jcr(njcr);
651
652    if (stat != 0) {
653       goto done;
654    }
655    if (last_jobs->size() > 0) {
656       job = (struct s_last_job *)last_jobs->last();
657       stat = job->JobStatus;
658       switch (job->JobStatus) {
659       case JS_Canceled:
660          termstat = _("Bacula Storage: Last Job Canceled");
661          break;
662       case JS_ErrorTerminated:
663       case JS_FatalError:
664          termstat = _("Bacula Storage: Last Job Failed");
665          break;
666       default:
667          if (job->Errors) {
668             termstat = _("Bacula Storage: Last Job had Warnings");
669          }
670          break;
671       }
672    }
673    Dmsg0(1000, "End bac_status jcr loop.\n");
674 done:
675    bacstat = stat;
676    if (buf) {
677       bstrncpy(buf, termstat, buf_len);
678    }
679    return buf;
680 }
681
682 #endif /* HAVE_WIN32 */