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