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