]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/lib/util.c
First cut restart Jobs
[bacula/bacula] / bacula / src / lib / util.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2000-2009 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 three of the GNU Affero 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 Affero 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 Kern Sibbald.
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  *   util.c  miscellaneous utility subroutines for Bacula
30  *
31  *    Kern Sibbald, MM
32  *
33  *   Version $Id$
34  */
35
36 #include "bacula.h"
37 #include "jcr.h"
38 #include "findlib/find.h"
39
40 /*
41  * Various Bacula Utility subroutines
42  *
43  */
44
45 /* Return true of buffer has all zero bytes */
46 bool is_buf_zero(char *buf, int len)
47 {
48    uint64_t *ip;
49    char *p;
50    int i, len64, done, rem;
51
52    if (buf[0] != 0) {
53       return false;
54    }
55    ip = (uint64_t *)buf;
56    /* Optimize by checking uint64_t for zero */
57    len64 = len / sizeof(uint64_t);
58    for (i=0; i < len64; i++) {
59       if (ip[i] != 0) {
60          return false;
61       }
62    }
63    done = len64 * sizeof(uint64_t);  /* bytes already checked */
64    p = buf + done;
65    rem = len - done;
66    for (i = 0; i < rem; i++) {
67       if (p[i] != 0) {
68          return false;
69       }
70    }
71    return true;
72 }
73
74
75 /* Convert a string in place to lower case */
76 void lcase(char *str)
77 {
78    while (*str) {
79       if (B_ISUPPER(*str)) {
80          *str = tolower((int)(*str));
81        }
82        str++;
83    }
84 }
85
86 /* Convert spaces to non-space character.
87  * This makes scanf of fields containing spaces easier.
88  */
89 void
90 bash_spaces(char *str)
91 {
92    while (*str) {
93       if (*str == ' ')
94          *str = 0x1;
95       str++;
96    }
97 }
98
99 /* Convert spaces to non-space character.
100  * This makes scanf of fields containing spaces easier.
101  */
102 void
103 bash_spaces(POOL_MEM &pm)
104 {
105    char *str = pm.c_str();
106    while (*str) {
107       if (*str == ' ')
108          *str = 0x1;
109       str++;
110    }
111 }
112
113
114 /* Convert non-space characters (0x1) back into spaces */
115 void
116 unbash_spaces(char *str)
117 {
118    while (*str) {
119      if (*str == 0x1)
120         *str = ' ';
121      str++;
122    }
123 }
124
125 /* Convert non-space characters (0x1) back into spaces */
126 void
127 unbash_spaces(POOL_MEM &pm)
128 {
129    char *str = pm.c_str();
130    while (*str) {
131      if (*str == 0x1)
132         *str = ' ';
133      str++;
134    }
135 }
136
137 char *encode_time(utime_t utime, char *buf)
138 {
139    struct tm tm;
140    int n = 0;
141    time_t time = utime;
142
143 #if defined(HAVE_WIN32)
144    /*
145     * Avoid a seg fault in Microsoft's CRT localtime_r(),
146     *  which incorrectly references a NULL returned from gmtime() if
147     *  time is negative before or after the timezone adjustment.
148     */
149    struct tm *gtm;
150
151    if ((gtm = gmtime(&time)) == NULL) {
152       return buf;
153    }
154
155    if (gtm->tm_year == 1970 && gtm->tm_mon == 1 && gtm->tm_mday < 3) {
156       return buf;
157    }
158 #endif
159
160    if (localtime_r(&time, &tm)) {
161       n = sprintf(buf, "%04d-%02d-%02d %02d:%02d:%02d",
162                    tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
163                    tm.tm_hour, tm.tm_min, tm.tm_sec);
164    }
165    return buf+n;
166 }
167
168
169
170 /*
171  * Convert a JobStatus code into a human readable form
172  */
173 void jobstatus_to_ascii(int JobStatus, char *msg, int maxlen)
174 {
175    const char *jobstat;
176    char buf[100];
177
178    switch (JobStatus) {
179    case JS_Created:
180       jobstat = _("Created");
181       break;
182    case JS_Running:
183       jobstat = _("Running");
184       break;
185    case JS_Blocked:
186       jobstat = _("Blocked");
187       break;
188    case JS_Terminated:
189       jobstat = _("OK");
190       break;
191    case JS_Incomplete:
192       jobstat = _("Error: incomplete job");
193       break;
194    case JS_FatalError:
195    case JS_ErrorTerminated:
196       jobstat = _("Error");
197       break;
198    case JS_Error:
199       jobstat = _("Non-fatal error");
200       break;
201    case JS_Warnings:
202       jobstat = _("OK -- with warnings");
203       break;
204    case JS_Canceled:
205       jobstat = _("Canceled");
206       break;
207    case JS_Differences:
208       jobstat = _("Verify differences");
209       break;
210    case JS_WaitFD:
211       jobstat = _("Waiting on FD");
212       break;
213    case JS_WaitSD:
214       jobstat = _("Wait on SD");
215       break;
216    case JS_WaitMedia:
217       jobstat = _("Wait for new Volume");
218       break;
219    case JS_WaitMount:
220       jobstat = _("Waiting for mount");
221       break;
222    case JS_WaitStoreRes:
223       jobstat = _("Waiting for Storage resource");
224       break;
225    case JS_WaitJobRes:
226       jobstat = _("Waiting for Job resource");
227       break;
228    case JS_WaitClientRes:
229       jobstat = _("Waiting for Client resource");
230       break;
231    case JS_WaitMaxJobs:
232       jobstat = _("Waiting on Max Jobs");
233       break;
234    case JS_WaitStartTime:
235       jobstat = _("Waiting for Start Time");
236       break;
237    case JS_WaitPriority:
238       jobstat = _("Waiting on Priority");
239       break;
240    case JS_DataCommitting:
241       jobstat = _("SD committing Data");
242       break;
243    case JS_DataDespooling:
244       jobstat = _("SD despooling Data");
245       break;
246    case JS_AttrDespooling:
247       jobstat = _("SD despooling Attributes");
248       break;
249    case JS_AttrInserting:
250       jobstat = _("Dir inserting Attributes");
251       break;
252
253    default:
254       if (JobStatus == 0) {
255          buf[0] = 0;
256       } else {
257          bsnprintf(buf, sizeof(buf), _("Unknown Job termination status=%d"), JobStatus);
258       }
259       jobstat = buf;
260       break;
261    }
262    bstrncpy(msg, jobstat, maxlen);
263 }
264
265 /*
266  * Convert a JobStatus code into a human readable form - gui version
267  */
268 void jobstatus_to_ascii_gui(int JobStatus, char *msg, int maxlen)
269 {
270    const char *cnv = NULL;
271    switch (JobStatus) {
272    case JS_Terminated:
273       cnv = _("Completed successfully");
274       break;
275    case JS_Warnings:
276       cnv = _("Completed with warnings");
277       break;
278    case JS_ErrorTerminated:
279       cnv = _("Terminated with errors");
280       break;
281    case JS_FatalError:
282       cnv = _("Fatal error");
283       break;
284    case JS_Created:
285       cnv = _("Created, not yet running");
286       break;
287    case JS_Canceled:
288       cnv = _("Canceled by user");
289       break;
290    case JS_Differences:
291       cnv = _("Verify found differences");
292       break;
293    case JS_WaitFD:
294       cnv = _("Waiting for File daemon");
295       break;
296    case JS_WaitSD:
297       cnv = _("Waiting for Storage daemon");
298       break;
299    case JS_WaitPriority:
300       cnv = _("Waiting for higher priority jobs");
301       break;
302    case JS_AttrInserting:
303       cnv = _("Batch inserting file records");
304       break;
305    };
306
307    if (cnv) {
308       bstrncpy(msg, cnv, maxlen);
309    } else {
310      jobstatus_to_ascii(JobStatus, msg, maxlen);
311    }
312 }
313
314
315 /*
316  * Convert Job Termination Status into a string
317  */
318 const char *job_status_to_str(int stat)
319 {
320    const char *str;
321
322    switch (stat) {
323    case JS_Terminated:
324       str = _("OK");
325       break;
326    case JS_Warnings:
327       str = _("OK -- with warnings");
328       break;
329    case JS_ErrorTerminated:
330    case JS_Error:
331       str = _("Error");
332       break;
333    case JS_FatalError:
334       str = _("Fatal Error");
335       break;
336    case JS_Canceled:
337       str = _("Canceled");
338       break;
339    case JS_Differences:
340       str = _("Differences");
341       break;
342    default:
343       str = _("Unknown term code");
344       break;
345    }
346    return str;
347 }
348
349
350 /*
351  * Convert Job Type into a string
352  */
353 const char *job_type_to_str(int type)
354 {
355    const char *str = NULL;
356
357    switch (type) {
358    case JT_BACKUP:
359       str = _("Backup");
360       break;
361    case JT_MIGRATED_JOB:
362       str = _("Migrated Job");
363       break;
364    case JT_VERIFY:
365       str = _("Verify");
366       break;
367    case JT_RESTORE:
368       str = _("Restore");
369       break;
370    case JT_CONSOLE:
371       str = _("Console");
372       break;
373    case JT_SYSTEM:
374       str = _("System or Console");
375       break;
376    case JT_ADMIN:
377       str = _("Admin");
378       break;
379    case JT_ARCHIVE:
380       str = _("Archive");
381       break;
382    case JT_JOB_COPY:
383       str = _("Job Copy");
384       break;
385    case JT_COPY:
386       str = _("Copy");
387       break;
388    case JT_MIGRATE:
389       str = _("Migrate");
390       break;
391    case JT_SCAN:
392       str = _("Scan");
393       break;
394    }
395    if (!str) {
396       str = _("Unknown Type");
397    }   
398    return str;
399 }
400
401 /* Convert ActionOnPurge to string (Truncate, Erase, Destroy)
402  */
403 char *action_on_purge_to_string(int aop, POOL_MEM &ret)
404 {
405    if (aop & ON_PURGE_TRUNCATE) {
406       pm_strcpy(ret, _("Truncate"));
407    }
408    if (!aop) {
409       pm_strcpy(ret, _("None"));
410    }
411    return ret.c_str();
412 }
413
414 /*
415  * Convert Job Level into a string
416  */
417 const char *job_level_to_str(int level)
418 {
419    const char *str;
420
421    switch (level) {
422    case L_BASE:
423       str = _("Base");
424    case L_FULL:
425       str = _("Full");
426       break;
427    case L_INCREMENTAL:
428       str = _("Incremental");
429       break;
430    case L_DIFFERENTIAL:
431       str = _("Differential");
432       break;
433    case L_SINCE:
434       str = _("Since");
435       break;
436    case L_VERIFY_CATALOG:
437       str = _("Verify Catalog");
438       break;
439    case L_VERIFY_INIT:
440       str = _("Verify Init Catalog");
441       break;
442    case L_VERIFY_VOLUME_TO_CATALOG:
443       str = _("Verify Volume to Catalog");
444       break;
445    case L_VERIFY_DISK_TO_CATALOG:
446       str = _("Verify Disk to Catalog");
447       break;
448    case L_VERIFY_DATA:
449       str = _("Verify Data");
450       break;
451    case L_VIRTUAL_FULL:
452       str = _("Virtual Full");
453       break;
454    case L_NONE:
455       str = " ";
456       break;
457    default:
458       str = _("Unknown Job Level");
459       break;
460    }
461    return str;
462 }
463
464 const char *volume_status_to_str(const char *status)
465 {
466    int pos;
467    const char *vs[] = {
468       NT_("Append"),    _("Append"),
469       NT_("Archive"),   _("Archive"),
470       NT_("Disabled"),  _("Disabled"),
471       NT_("Full"),      _("Full"),
472       NT_("Used"),      _("Used"),
473       NT_("Cleaning"),  _("Cleaning"),
474       NT_("Purged"),    _("Purged"),
475       NT_("Recycle"),   _("Recycle"),
476       NT_("Read-Only"), _("Read-Only"),
477       NT_("Error"),     _("Error"),
478       NULL,             NULL};
479
480    if (status) {
481      for (pos = 0 ; vs[pos] ; pos += 2) {
482        if ( !strcmp(vs[pos],status) ) {
483          return vs[pos+1];
484        }
485      }
486    }
487
488    return _("Invalid volume status");
489 }
490
491
492 /***********************************************************************
493  * Encode the mode bits into a 10 character string like LS does
494  ***********************************************************************/
495
496 char *encode_mode(mode_t mode, char *buf)
497 {
498   char *cp = buf;
499
500   *cp++ = S_ISDIR(mode) ? 'd' : S_ISBLK(mode)  ? 'b' : S_ISCHR(mode)  ? 'c' :
501           S_ISLNK(mode) ? 'l' : S_ISFIFO(mode) ? 'f' : S_ISSOCK(mode) ? 's' : '-';
502   *cp++ = mode & S_IRUSR ? 'r' : '-';
503   *cp++ = mode & S_IWUSR ? 'w' : '-';
504   *cp++ = (mode & S_ISUID
505                ? (mode & S_IXUSR ? 's' : 'S')
506                : (mode & S_IXUSR ? 'x' : '-'));
507   *cp++ = mode & S_IRGRP ? 'r' : '-';
508   *cp++ = mode & S_IWGRP ? 'w' : '-';
509   *cp++ = (mode & S_ISGID
510                ? (mode & S_IXGRP ? 's' : 'S')
511                : (mode & S_IXGRP ? 'x' : '-'));
512   *cp++ = mode & S_IROTH ? 'r' : '-';
513   *cp++ = mode & S_IWOTH ? 'w' : '-';
514   *cp++ = (mode & S_ISVTX
515                ? (mode & S_IXOTH ? 't' : 'T')
516                : (mode & S_IXOTH ? 'x' : '-'));
517   *cp = '\0';
518   return cp;
519 }
520
521 #if defined(HAVE_WIN32)
522 int do_shell_expansion(char *name, int name_len)
523 {
524    char *src = bstrdup(name);
525
526    ExpandEnvironmentStrings(src, name, name_len);
527
528    free(src);
529
530    return 1;
531 }
532 #else
533 int do_shell_expansion(char *name, int name_len)
534 {
535    static char meta[] = "~\\$[]*?`'<>\"";
536    bool found = false;
537    int len, i, stat;
538    POOLMEM *cmd;
539    BPIPE *bpipe;
540    char line[MAXSTRING];
541    const char *shellcmd;
542
543    /* Check if any meta characters are present */
544    len = strlen(meta);
545    for (i = 0; i < len; i++) {
546       if (strchr(name, meta[i])) {
547          found = true;
548          break;
549       }
550    }
551    if (found) {
552       cmd =  get_pool_memory(PM_FNAME);
553       /* look for shell */
554       if ((shellcmd = getenv("SHELL")) == NULL) {
555          shellcmd = "/bin/sh";
556       }
557       pm_strcpy(&cmd, shellcmd);
558       pm_strcat(&cmd, " -c \"echo ");
559       pm_strcat(&cmd, name);
560       pm_strcat(&cmd, "\"");
561       Dmsg1(400, "Send: %s\n", cmd);
562       if ((bpipe = open_bpipe(cmd, 0, "r"))) {
563          *line = 0;
564          fgets(line, sizeof(line), bpipe->rfd);
565          strip_trailing_junk(line);
566          stat = close_bpipe(bpipe);
567          Dmsg2(400, "stat=%d got: %s\n", stat, line);
568       } else {
569          stat = 1;                    /* error */
570       }
571       free_pool_memory(cmd);
572       if (stat == 0) {
573          bstrncpy(name, line, name_len);
574       }
575    }
576    return 1;
577 }
578 #endif
579
580
581 /*  MAKESESSIONKEY  --  Generate session key with optional start
582                         key.  If mode is TRUE, the key will be
583                         translated to a string, otherwise it is
584                         returned as 16 binary bytes.
585
586     from SpeakFreely by John Walker */
587
588 void make_session_key(char *key, char *seed, int mode)
589 {
590    int j, k;
591    struct MD5Context md5c;
592    unsigned char md5key[16], md5key1[16];
593    char s[1024];
594
595 #define ss sizeof(s)
596
597    s[0] = 0;
598    if (seed != NULL) {
599      bstrncat(s, seed, sizeof(s));
600    }
601
602    /* The following creates a seed for the session key generator
603      based on a collection of volatile and environment-specific
604      information unlikely to be vulnerable (as a whole) to an
605      exhaustive search attack.  If one of these items isn't
606      available on your machine, replace it with something
607      equivalent or, if you like, just delete it. */
608
609 #if defined(HAVE_WIN32)
610    {
611       LARGE_INTEGER     li;
612       DWORD             length;
613       FILETIME          ft;
614       char             *p;
615
616       p = s;
617       bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)GetCurrentProcessId());
618       (void)getcwd(s + strlen(s), 256);
619       bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)GetTickCount());
620       QueryPerformanceCounter(&li);
621       bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)li.LowPart);
622       GetSystemTimeAsFileTime(&ft);
623       bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)ft.dwLowDateTime);
624       bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)ft.dwHighDateTime);
625       length = 256;
626       GetComputerName(s + strlen(s), &length);
627       length = 256;
628       GetUserName(s + strlen(s), &length);
629    }
630 #else
631    bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)getpid());
632    bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)getppid());
633    (void)getcwd(s + strlen(s), 256);
634    bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)clock());
635    bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)time(NULL));
636 #if defined(Solaris)
637    sysinfo(SI_HW_SERIAL,s + strlen(s), 12);
638 #endif
639 #if defined(HAVE_GETHOSTID)
640    bsnprintf(s + strlen(s), ss, "%lu", (uint32_t) gethostid());
641 #endif
642    gethostname(s + strlen(s), 256);
643    bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)getuid());
644    bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)getgid());
645 #endif
646    MD5Init(&md5c);
647    MD5Update(&md5c, (uint8_t *)s, strlen(s));
648    MD5Final(md5key, &md5c);
649    bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)((time(NULL) + 65121) ^ 0x375F));
650    MD5Init(&md5c);
651    MD5Update(&md5c, (uint8_t *)s, strlen(s));
652    MD5Final(md5key1, &md5c);
653 #define nextrand    (md5key[j] ^ md5key1[j])
654    if (mode) {
655      for (j = k = 0; j < 16; j++) {
656         unsigned char rb = nextrand;
657
658 #define Rad16(x) ((x) + 'A')
659         key[k++] = Rad16((rb >> 4) & 0xF);
660         key[k++] = Rad16(rb & 0xF);
661 #undef Rad16
662         if (j & 1) {
663            key[k++] = '-';
664         }
665      }
666      key[--k] = 0;
667    } else {
668      for (j = 0; j < 16; j++) {
669         key[j] = nextrand;
670      }
671    }
672 }
673 #undef nextrand
674
675 void encode_session_key(char *encode, char *session, char *key, int maxlen)
676 {
677    int i;
678    for (i=0; (i < maxlen-1) && session[i]; i++) {
679       if (session[i] == '-') {
680          encode[i] = '-';
681       } else {
682          encode[i] = ((session[i] - 'A' + key[i]) & 0xF) + 'A';
683       }
684    }
685    encode[i] = 0;
686    Dmsg3(000, "Session=%s key=%s encode=%s\n", session, key, encode);
687 }
688
689 void decode_session_key(char *decode, char *session, char *key, int maxlen)
690 {
691    int i, x;
692
693    for (i=0; (i < maxlen-1) && session[i]; i++) {
694       if (session[i] == '-') {
695          decode[i] = '-';
696       } else {
697          x = (session[i] - 'A' - key[i]) & 0xF;
698          if (x < 0) {
699             x += 16;
700          }
701          decode[i] = x + 'A';
702       }
703    }
704    decode[i] = 0;
705    Dmsg3(000, "Session=%s key=%s decode=%s\n", session, key, decode);
706 }
707
708
709
710 /*
711  * Edit job codes into main command line
712  *  %% = %
713  *  %c = Client's name
714  *  %d = Director's name
715  *  %e = Job Exit code
716  *  %i = JobId
717  *  %j = Unique Job id
718  *  %l = job level
719  *  %n = Unadorned Job name
720  *  %s = Since time
721  *  %t = Job type (Backup, ...)
722  *  %r = Recipients
723  *  %v = Volume name
724  *  %b = Job Bytes
725  *  %f = Job Files
726  *
727  *  omsg = edited output message
728  *  imsg = input string containing edit codes (%x)
729  *  to = recepients list
730  *
731  */
732 POOLMEM *edit_job_codes(JCR *jcr, char *omsg, char *imsg, const char *to, job_code_callback_t callback)
733 {
734    char *p, *q;
735    const char *str;
736    char add[50];
737    char name[MAX_NAME_LENGTH];
738    int i;
739
740    *omsg = 0;
741    Dmsg1(200, "edit_job_codes: %s\n", imsg);
742    for (p=imsg; *p; p++) {
743       if (*p == '%') {
744          switch (*++p) {
745          case '%':
746             str = "%";
747             break;
748          case 'c':
749             if (jcr) {
750                str = jcr->client_name;
751             } else {
752                str = _("*none*");
753             }
754             break;
755          case 'd':
756             str = my_name;            /* Director's name */
757             break;
758          case 'e':
759             if (jcr) {
760                str = job_status_to_str(jcr->JobStatus);
761             } else {
762                str = _("*none*");
763             }
764             break;
765          case 'i':
766             if (jcr) {
767                bsnprintf(add, sizeof(add), "%d", jcr->JobId);
768                str = add;
769             } else {
770                str = _("*none*");
771             }
772             break;
773          case 'j':                    /* Job name */
774             if (jcr) {
775                str = jcr->Job;
776             } else {
777                str = _("*none*");
778             }
779             break;
780          case 'l':
781             if (jcr) {
782                str = job_level_to_str(jcr->getJobLevel());
783             } else {
784                str = _("*none*");
785             }
786             break;
787          case 'n':
788              if (jcr) {
789                 bstrncpy(name, jcr->Job, sizeof(name));
790                 /* There are three periods after the Job name */
791                 for (i=0; i<3; i++) {
792                    if ((q=strrchr(name, '.')) != NULL) {
793                        *q = 0;
794                    }
795                 }
796                 str = name;
797              } else {
798                 str = _("*none*");
799              }
800              break;
801          case 'r':
802             str = to;
803             break;
804          case 's':                    /* since time */
805             if (jcr && jcr->stime) {
806                str = jcr->stime;
807             } else {
808                str = _("*none*");
809             }
810             break;
811          case 'f':                    /* Job Files */
812             str = edit_uint64(jcr->JobFiles, add);
813             break;
814          case 'b':                    /* Job Bytes */
815             str = edit_uint64(jcr->JobBytes, add);
816             break;
817          case 't':
818             if (jcr) {
819                str = job_type_to_str(jcr->getJobType());
820             } else {
821                str = _("*none*");
822             }
823             break;
824          case 'v':
825             if (jcr) {
826                if (jcr->VolumeName && jcr->VolumeName[0]) {
827                   str = jcr->VolumeName;
828                } else {
829                   str = "";
830                }
831             } else {
832                str = _("*none*");
833             }
834             break;
835          default:
836             str = NULL;
837             if (callback != NULL) {
838                 str = callback(jcr, p);
839             }
840
841             if (!str) {
842                 add[0] = '%';
843                 add[1] = *p;
844                 add[2] = 0;
845                 str = add;
846             }
847             break;
848          }
849       } else {
850          add[0] = *p;
851          add[1] = 0;
852          str = add;
853       }
854       Dmsg1(1200, "add_str %s\n", str);
855       pm_strcat(&omsg, str);
856       Dmsg1(1200, "omsg=%s\n", omsg);
857    }
858    return omsg;
859 }
860
861 void set_working_directory(char *wd)
862 {
863    struct stat stat_buf;
864
865    if (wd == NULL) {
866       Emsg0(M_ERROR_TERM, 0, _("Working directory not defined. Cannot continue.\n"));
867    }
868    if (stat(wd, &stat_buf) != 0) {
869       Emsg1(M_ERROR_TERM, 0, _("Working Directory: \"%s\" not found. Cannot continue.\n"),
870          wd);
871    }
872    if (!S_ISDIR(stat_buf.st_mode)) {
873       Emsg1(M_ERROR_TERM, 0, _("Working Directory: \"%s\" is not a directory. Cannot continue.\n"),
874          wd);
875    }
876    working_directory = wd;            /* set global */
877 }
878
879 const char *last_path_separator(const char *str)
880 {
881    if (*str != '\0') {
882       for (const char *p = &str[strlen(str) - 1]; p >= str; p--) {
883          if (IsPathSeparator(*p)) {
884             return p;
885          }
886       }
887    }
888    return NULL;
889 }