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