]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/lib/util.c
Tweak version date
[bacula/bacula] / bacula / src / lib / util.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2000-2011 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       break;
425    case L_FULL:
426       str = _("Full");
427       break;
428    case L_INCREMENTAL:
429       str = _("Incremental");
430       break;
431    case L_DIFFERENTIAL:
432       str = _("Differential");
433       break;
434    case L_SINCE:
435       str = _("Since");
436       break;
437    case L_VERIFY_CATALOG:
438       str = _("Verify Catalog");
439       break;
440    case L_VERIFY_INIT:
441       str = _("Verify Init Catalog");
442       break;
443    case L_VERIFY_VOLUME_TO_CATALOG:
444       str = _("Verify Volume to Catalog");
445       break;
446    case L_VERIFY_DISK_TO_CATALOG:
447       str = _("Verify Disk to Catalog");
448       break;
449    case L_VERIFY_DATA:
450       str = _("Verify Data");
451       break;
452    case L_VIRTUAL_FULL:
453       str = _("Virtual Full");
454       break;
455    case L_NONE:
456       str = " ";
457       break;
458    default:
459       str = _("Unknown Job Level");
460       break;
461    }
462    return str;
463 }
464
465 const char *volume_status_to_str(const char *status)
466 {
467    int pos;
468    const char *vs[] = {
469       NT_("Append"),    _("Append"),
470       NT_("Archive"),   _("Archive"),
471       NT_("Disabled"),  _("Disabled"),
472       NT_("Full"),      _("Full"),
473       NT_("Used"),      _("Used"),
474       NT_("Cleaning"),  _("Cleaning"),
475       NT_("Purged"),    _("Purged"),
476       NT_("Recycle"),   _("Recycle"),
477       NT_("Read-Only"), _("Read-Only"),
478       NT_("Error"),     _("Error"),
479       NULL,             NULL};
480
481    if (status) {
482      for (pos = 0 ; vs[pos] ; pos += 2) {
483        if ( !strcmp(vs[pos],status) ) {
484          return vs[pos+1];
485        }
486      }
487    }
488
489    return _("Invalid volume status");
490 }
491
492
493 /***********************************************************************
494  * Encode the mode bits into a 10 character string like LS does
495  ***********************************************************************/
496
497 char *encode_mode(mode_t mode, char *buf)
498 {
499   char *cp = buf;
500
501   *cp++ = S_ISDIR(mode) ? 'd' : S_ISBLK(mode)  ? 'b' : S_ISCHR(mode)  ? 'c' :
502           S_ISLNK(mode) ? 'l' : S_ISFIFO(mode) ? 'f' : S_ISSOCK(mode) ? 's' : '-';
503   *cp++ = mode & S_IRUSR ? 'r' : '-';
504   *cp++ = mode & S_IWUSR ? 'w' : '-';
505   *cp++ = (mode & S_ISUID
506                ? (mode & S_IXUSR ? 's' : 'S')
507                : (mode & S_IXUSR ? 'x' : '-'));
508   *cp++ = mode & S_IRGRP ? 'r' : '-';
509   *cp++ = mode & S_IWGRP ? 'w' : '-';
510   *cp++ = (mode & S_ISGID
511                ? (mode & S_IXGRP ? 's' : 'S')
512                : (mode & S_IXGRP ? 'x' : '-'));
513   *cp++ = mode & S_IROTH ? 'r' : '-';
514   *cp++ = mode & S_IWOTH ? 'w' : '-';
515   *cp++ = (mode & S_ISVTX
516                ? (mode & S_IXOTH ? 't' : 'T')
517                : (mode & S_IXOTH ? 'x' : '-'));
518   *cp = '\0';
519   return cp;
520 }
521
522 #if defined(HAVE_WIN32)
523 int do_shell_expansion(char *name, int name_len)
524 {
525    char *src = bstrdup(name);
526
527    ExpandEnvironmentStrings(src, name, name_len);
528
529    free(src);
530
531    return 1;
532 }
533 #else
534 int do_shell_expansion(char *name, int name_len)
535 {
536    static char meta[] = "~\\$[]*?`'<>\"";
537    bool found = false;
538    int len, i, stat;
539    POOLMEM *cmd;
540    BPIPE *bpipe;
541    char line[MAXSTRING];
542    const char *shellcmd;
543
544    /* Check if any meta characters are present */
545    len = strlen(meta);
546    for (i = 0; i < len; i++) {
547       if (strchr(name, meta[i])) {
548          found = true;
549          break;
550       }
551    }
552    if (found) {
553       cmd =  get_pool_memory(PM_FNAME);
554       /* look for shell */
555       if ((shellcmd = getenv("SHELL")) == NULL) {
556          shellcmd = "/bin/sh";
557       }
558       pm_strcpy(&cmd, shellcmd);
559       pm_strcat(&cmd, " -c \"echo ");
560       pm_strcat(&cmd, name);
561       pm_strcat(&cmd, "\"");
562       Dmsg1(400, "Send: %s\n", cmd);
563       if ((bpipe = open_bpipe(cmd, 0, "r"))) {
564          *line = 0;
565          fgets(line, sizeof(line), bpipe->rfd);
566          strip_trailing_junk(line);
567          stat = close_bpipe(bpipe);
568          Dmsg2(400, "stat=%d got: %s\n", stat, line);
569       } else {
570          stat = 1;                    /* error */
571       }
572       free_pool_memory(cmd);
573       if (stat == 0) {
574          bstrncpy(name, line, name_len);
575       }
576    }
577    return 1;
578 }
579 #endif
580
581
582 /*  MAKESESSIONKEY  --  Generate session key with optional start
583                         key.  If mode is TRUE, the key will be
584                         translated to a string, otherwise it is
585                         returned as 16 binary bytes.
586
587     from SpeakFreely by John Walker */
588
589 void make_session_key(char *key, char *seed, int mode)
590 {
591    int j, k;
592    struct MD5Context md5c;
593    unsigned char md5key[16], md5key1[16];
594    char s[1024];
595
596 #define ss sizeof(s)
597
598    s[0] = 0;
599    if (seed != NULL) {
600      bstrncat(s, seed, sizeof(s));
601    }
602
603    /* The following creates a seed for the session key generator
604      based on a collection of volatile and environment-specific
605      information unlikely to be vulnerable (as a whole) to an
606      exhaustive search attack.  If one of these items isn't
607      available on your machine, replace it with something
608      equivalent or, if you like, just delete it. */
609
610 #if defined(HAVE_WIN32)
611    {
612       LARGE_INTEGER     li;
613       DWORD             length;
614       FILETIME          ft;
615       char             *p;
616
617       p = s;
618       bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)GetCurrentProcessId());
619       (void)getcwd(s + strlen(s), 256);
620       bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)GetTickCount());
621       QueryPerformanceCounter(&li);
622       bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)li.LowPart);
623       GetSystemTimeAsFileTime(&ft);
624       bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)ft.dwLowDateTime);
625       bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)ft.dwHighDateTime);
626       length = 256;
627       GetComputerName(s + strlen(s), &length);
628       length = 256;
629       GetUserName(s + strlen(s), &length);
630    }
631 #else
632    bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)getpid());
633    bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)getppid());
634    (void)getcwd(s + strlen(s), 256);
635    bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)clock());
636    bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)time(NULL));
637 #if defined(Solaris)
638    sysinfo(SI_HW_SERIAL,s + strlen(s), 12);
639 #endif
640 #if defined(HAVE_GETHOSTID)
641    bsnprintf(s + strlen(s), ss, "%lu", (uint32_t) gethostid());
642 #endif
643    gethostname(s + strlen(s), 256);
644    bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)getuid());
645    bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)getgid());
646 #endif
647    MD5Init(&md5c);
648    MD5Update(&md5c, (uint8_t *)s, strlen(s));
649    MD5Final(md5key, &md5c);
650    bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)((time(NULL) + 65121) ^ 0x375F));
651    MD5Init(&md5c);
652    MD5Update(&md5c, (uint8_t *)s, strlen(s));
653    MD5Final(md5key1, &md5c);
654 #define nextrand    (md5key[j] ^ md5key1[j])
655    if (mode) {
656      for (j = k = 0; j < 16; j++) {
657         unsigned char rb = nextrand;
658
659 #define Rad16(x) ((x) + 'A')
660         key[k++] = Rad16((rb >> 4) & 0xF);
661         key[k++] = Rad16(rb & 0xF);
662 #undef Rad16
663         if (j & 1) {
664            key[k++] = '-';
665         }
666      }
667      key[--k] = 0;
668    } else {
669      for (j = 0; j < 16; j++) {
670         key[j] = nextrand;
671      }
672    }
673 }
674 #undef nextrand
675
676 void encode_session_key(char *encode, char *session, char *key, int maxlen)
677 {
678    int i;
679    for (i=0; (i < maxlen-1) && session[i]; i++) {
680       if (session[i] == '-') {
681          encode[i] = '-';
682       } else {
683          encode[i] = ((session[i] - 'A' + key[i]) & 0xF) + 'A';
684       }
685    }
686    encode[i] = 0;
687    Dmsg3(000, "Session=%s key=%s encode=%s\n", session, key, encode);
688 }
689
690 void decode_session_key(char *decode, char *session, char *key, int maxlen)
691 {
692    int i, x;
693
694    for (i=0; (i < maxlen-1) && session[i]; i++) {
695       if (session[i] == '-') {
696          decode[i] = '-';
697       } else {
698          x = (session[i] - 'A' - key[i]) & 0xF;
699          if (x < 0) {
700             x += 16;
701          }
702          decode[i] = x + 'A';
703       }
704    }
705    decode[i] = 0;
706    Dmsg3(000, "Session=%s key=%s decode=%s\n", session, key, decode);
707 }
708
709
710
711 /*
712  * Edit job codes into main command line
713  *  %% = %
714  *  %c = Client's name
715  *  %d = Director's name
716  *  %e = Job Exit code
717  *  %i = JobId
718  *  %j = Unique Job id
719  *  %l = job level
720  *  %n = Unadorned Job name
721  *  %s = Since time
722  *  %t = Job type (Backup, ...)
723  *  %r = Recipients
724  *  %v = Volume name
725  *  %b = Job Bytes
726  *  %F = Job Files
727  *
728  *  omsg = edited output message
729  *  imsg = input string containing edit codes (%x)
730  *  to = recepients list
731  *
732  */
733 POOLMEM *edit_job_codes(JCR *jcr, char *omsg, char *imsg, const char *to, job_code_callback_t callback)
734 {
735    char *p, *q;
736    const char *str;
737    char add[50];
738    char name[MAX_NAME_LENGTH];
739    int i;
740
741    *omsg = 0;
742    Dmsg1(200, "edit_job_codes: %s\n", imsg);
743    for (p=imsg; *p; p++) {
744       if (*p == '%') {
745          switch (*++p) {
746          case '%':
747             str = "%";
748             break;
749          case 'c':
750             if (jcr) {
751                str = jcr->client_name;
752             } else {
753                str = _("*none*");
754             }
755             break;
756          case 'd':
757             str = my_name;            /* Director's name */
758             break;
759          case 'e':
760             if (jcr) {
761                str = job_status_to_str(jcr->JobStatus);
762             } else {
763                str = _("*none*");
764             }
765             break;
766          case 'i':
767             if (jcr) {
768                bsnprintf(add, sizeof(add), "%d", jcr->JobId);
769                str = add;
770             } else {
771                str = _("*none*");
772             }
773             break;
774          case 'j':                    /* Job name */
775             if (jcr) {
776                str = jcr->Job;
777             } else {
778                str = _("*none*");
779             }
780             break;
781          case 'l':
782             if (jcr) {
783                str = job_level_to_str(jcr->getJobLevel());
784             } else {
785                str = _("*none*");
786             }
787             break;
788          case 'n':
789              if (jcr) {
790                 bstrncpy(name, jcr->Job, sizeof(name));
791                 /* There are three periods after the Job name */
792                 for (i=0; i<3; i++) {
793                    if ((q=strrchr(name, '.')) != NULL) {
794                        *q = 0;
795                    }
796                 }
797                 str = name;
798              } else {
799                 str = _("*none*");
800              }
801              break;
802          case 'r':
803             str = to;
804             break;
805          case 's':                    /* since time */
806             if (jcr && jcr->stime) {
807                str = jcr->stime;
808             } else {
809                str = _("*none*");
810             }
811             break;
812          case 'F':                    /* Job Files */
813             str = edit_uint64(jcr->JobFiles, add);
814             break;
815          case 'b':                    /* Job Bytes */
816             str = edit_uint64(jcr->JobBytes, add);
817             break;
818          case 't':
819             if (jcr) {
820                str = job_type_to_str(jcr->getJobType());
821             } else {
822                str = _("*none*");
823             }
824             break;
825          case 'v':
826             if (jcr) {
827                if (jcr->VolumeName && jcr->VolumeName[0]) {
828                   str = jcr->VolumeName;
829                } else {
830                   str = "";
831                }
832             } else {
833                str = _("*none*");
834             }
835             break;
836          default:
837             str = NULL;
838             if (callback != NULL) {
839                 str = callback(jcr, p);
840             }
841
842             if (!str) {
843                 add[0] = '%';
844                 add[1] = *p;
845                 add[2] = 0;
846                 str = add;
847             }
848             break;
849          }
850       } else {
851          add[0] = *p;
852          add[1] = 0;
853          str = add;
854       }
855       Dmsg1(1200, "add_str %s\n", str);
856       pm_strcat(&omsg, str);
857       Dmsg1(1200, "omsg=%s\n", omsg);
858    }
859    return omsg;
860 }
861
862 void set_working_directory(char *wd)
863 {
864    struct stat stat_buf;
865
866    if (wd == NULL) {
867       Emsg0(M_ERROR_TERM, 0, _("Working directory not defined. Cannot continue.\n"));
868    }
869    if (stat(wd, &stat_buf) != 0) {
870       Emsg1(M_ERROR_TERM, 0, _("Working Directory: \"%s\" not found. Cannot continue.\n"),
871          wd);
872    }
873    if (!S_ISDIR(stat_buf.st_mode)) {
874       Emsg1(M_ERROR_TERM, 0, _("Working Directory: \"%s\" is not a directory. Cannot continue.\n"),
875          wd);
876    }
877    working_directory = wd;            /* set global */
878 }
879
880 const char *last_path_separator(const char *str)
881 {
882    if (*str != '\0') {
883       for (const char *p = &str[strlen(str) - 1]; p >= str; p--) {
884          if (IsPathSeparator(*p)) {
885             return p;
886          }
887       }
888    }
889    return NULL;
890 }