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