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