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