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