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