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