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