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