]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/stored/status.c
Eliminate dependency on man2html.
[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 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    sendit("====\n", 5, arg);
408
409    free_pool_memory(msg);
410 }
411
412 static void list_jobs_waiting_on_reservation(void sendit(const char *msg, int len, void *sarg), void *arg)
413
414    JCR *jcr;
415    char *msg;
416
417    msg = _("\nJobs waiting to reserve a drive:\n");
418    sendit(msg, strlen(msg), arg);
419
420    foreach_jcr(jcr) {
421       if (!jcr->reserve_msgs) {
422          continue;
423       }
424       send_drive_reserve_messages(jcr, sendit, arg);
425    }
426    endeach_jcr(jcr);
427
428    sendit("====\n", 5, arg);
429 }
430
431
432 static void list_terminated_jobs(void sendit(const char *msg, int len, void *sarg), void *arg)
433 {
434    char dt[MAX_TIME_LENGTH], b1[30], b2[30];
435    char level[10];
436    struct s_last_job *je;
437    const char *msg;
438
439    msg =  _("\nTerminated Jobs:\n");
440    sendit(msg, strlen(msg), arg);
441    if (last_jobs->size() == 0) {
442       sendit("====\n", 5, arg);
443       return;
444    }
445    lock_last_jobs_list();
446    msg =  _(" JobId  Level    Files      Bytes   Status   Finished        Name \n");
447    sendit(msg, strlen(msg), arg);
448    msg =  _("===================================================================\n");
449    sendit(msg, strlen(msg), arg);
450    foreach_dlist(je, last_jobs) {
451       char JobName[MAX_NAME_LENGTH];
452       const char *termstat;
453       char buf[1000];
454
455       bstrftime_nc(dt, sizeof(dt), je->end_time);
456       switch (je->JobType) {
457       case JT_ADMIN:
458       case JT_RESTORE:
459          bstrncpy(level, "    ", sizeof(level));
460          break;
461       default:
462          bstrncpy(level, level_to_str(je->JobLevel), sizeof(level));
463          level[4] = 0;
464          break;
465       }
466       switch (je->JobStatus) {
467       case JS_Created:
468          termstat = _("Created");
469          break;
470       case JS_FatalError:
471       case JS_ErrorTerminated:
472          termstat = _("Error");
473          break;
474       case JS_Differences:
475          termstat = _("Diffs");
476          break;
477       case JS_Canceled:
478          termstat = _("Cancel");
479          break;
480       case JS_Terminated:
481          termstat = _("OK");
482          break;
483       default:
484          termstat = _("Other");
485          break;
486       }
487       bstrncpy(JobName, je->Job, sizeof(JobName));
488       /* There are three periods after the Job name */
489       char *p;
490       for (int i=0; i<3; i++) {
491          if ((p=strrchr(JobName, '.')) != NULL) {
492             *p = 0;
493          }
494       }
495       bsnprintf(buf, sizeof(buf), _("%6d  %-6s %8s %10s  %-7s  %-8s %s\n"),
496          je->JobId,
497          level,
498          edit_uint64_with_commas(je->JobFiles, b1),
499          edit_uint64_with_suffix(je->JobBytes, b2),
500          termstat,
501          dt, JobName);
502       sendit(buf, strlen(buf), arg);
503    }
504    unlock_last_jobs_list();
505    sendit("====\n", 5, arg);
506 }
507
508 /*
509  * Convert Job Level into a string
510  */
511 static const char *level_to_str(int level)
512 {
513    const char *str;
514
515    switch (level) {
516    case L_BASE:
517       str = _("Base");
518    case L_FULL:
519       str = _("Full");
520       break;
521    case L_INCREMENTAL:
522       str = _("Incremental");
523       break;
524    case L_DIFFERENTIAL:
525       str = _("Differential");
526       break;
527    case L_SINCE:
528       str = _("Since");
529       break;
530    case L_VERIFY_CATALOG:
531       str = _("Verify Catalog");
532       break;
533    case L_VERIFY_INIT:
534       str = _("Init Catalog");
535       break;
536    case L_VERIFY_VOLUME_TO_CATALOG:
537       str = _("Volume to Catalog");
538       break;
539    case L_VERIFY_DISK_TO_CATALOG:
540       str = _("Disk to Catalog");
541       break;
542    case L_VERIFY_DATA:
543       str = _("Data");
544       break;
545    case L_NONE:
546       str = " ";
547       break;
548    default:
549       str = _("Unknown Job Level");
550       break;
551    }
552    return str;
553 }
554
555 /*
556  * Send to Director
557  */
558 static void bsock_sendit(const char *msg, int len, void *arg)
559 {
560    BSOCK *user = (BSOCK *)arg;
561
562    memcpy(user->msg, msg, len+1);
563    user->msglen = len+1;
564    bnet_send(user);
565 }
566
567 /*
568  * Status command from Director
569  */
570 bool status_cmd(JCR *jcr)
571 {
572    BSOCK *user = jcr->dir_bsock;
573
574    bnet_fsend(user, "\n");
575    output_status(bsock_sendit, (void *)user);
576
577    bnet_sig(user, BNET_EOD);
578    return 1;
579 }
580
581 /*
582  * .status command from Director
583  */
584 bool qstatus_cmd(JCR *jcr)
585 {
586    BSOCK *dir = jcr->dir_bsock;
587    POOL_MEM time;
588    JCR *njcr;
589    s_last_job* job;
590
591    if (sscanf(dir->msg, qstatus, time.c_str()) != 1) {
592       pm_strcpy(jcr->errmsg, dir->msg);
593       Jmsg1(jcr, M_FATAL, 0, _("Bad .status command: %s\n"), jcr->errmsg);
594       bnet_fsend(dir, _("3900 Bad .status command, missing argument.\n"));
595       bnet_sig(dir, BNET_EOD);
596       return false;
597    }
598    unbash_spaces(time);
599
600    if (strcmp(time.c_str(), "current") == 0) {
601       bnet_fsend(dir, OKqstatus, time.c_str());
602       foreach_jcr(njcr) {
603          if (njcr->JobId != 0) {
604             bnet_fsend(dir, DotStatusJob, njcr->JobId, njcr->JobStatus, njcr->JobErrors);
605          }
606       }
607       endeach_jcr(njcr);
608    } else if (strcmp(time.c_str(), "last") == 0) {
609       bnet_fsend(dir, OKqstatus, time.c_str());
610       if ((last_jobs) && (last_jobs->size() > 0)) {
611          job = (s_last_job*)last_jobs->last();
612          bnet_fsend(dir, DotStatusJob, job->JobId, job->JobStatus, job->Errors);
613       }
614    } else {
615       pm_strcpy(jcr->errmsg, dir->msg);
616       Jmsg1(jcr, M_FATAL, 0, _("Bad .status command: %s\n"), jcr->errmsg);
617       bnet_fsend(dir, _("3900 Bad .status command, wrong argument.\n"));
618       bnet_sig(dir, BNET_EOD);
619       return false;
620    }
621    bnet_sig(dir, BNET_EOD);
622    return true;
623 }
624
625 #if defined(HAVE_WIN32)
626 int bacstat = 0;
627
628 char *bac_status(char *buf, int buf_len)
629 {
630    JCR *njcr;
631    const char *termstat = _("Bacula Storage: Idle");
632    struct s_last_job *job;
633    int stat = 0;                      /* Idle */
634
635    if (!last_jobs) {
636       goto done;
637    }
638    Dmsg0(1000, "Begin bac_status jcr loop.\n");
639    foreach_jcr(njcr) {
640       if (njcr->JobId != 0) {
641          stat = JS_Running;
642          termstat = _("Bacula Storage: Running");
643          break;
644       }
645    }
646    endeach_jcr(njcr);
647
648    if (stat != 0) {
649       goto done;
650    }
651    if (last_jobs->size() > 0) {
652       job = (struct s_last_job *)last_jobs->last();
653       stat = job->JobStatus;
654       switch (job->JobStatus) {
655       case JS_Canceled:
656          termstat = _("Bacula Storage: Last Job Canceled");
657          break;
658       case JS_ErrorTerminated:
659       case JS_FatalError:
660          termstat = _("Bacula Storage: Last Job Failed");
661          break;
662       default:
663          if (job->Errors) {
664             termstat = _("Bacula Storage: Last Job had Warnings");
665          }
666          break;
667       }
668    }
669    Dmsg0(1000, "End bac_status jcr loop.\n");
670 done:
671    bacstat = stat;
672    if (buf) {
673       bstrncpy(buf, termstat, buf_len);
674    }
675    return buf;
676 }
677
678 #endif /* HAVE_WIN32 */