]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/lib/util.c
Change copyright as per agreement with FSFE
[bacula/bacula] / bacula / src / lib / util.c
1 /*
2    Bacula(R) - The Network Backup Solution
3
4    Copyright (C) 2000-2016 Kern Sibbald
5
6    The original author of Bacula is Kern Sibbald, with contributions
7    from many 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    This notice must be preserved when any source code is 
15    conveyed and/or propagated.
16
17    Bacula(R) is a registered trademark of Kern Sibbald.
18 */
19 /*
20  *   util.c  miscellaneous utility subroutines for Bacula
21  *
22  *    Kern Sibbald, MM
23  */
24
25 #include "bacula.h"
26 #include "jcr.h"
27 #include "findlib/find.h"
28
29 /*
30  * Various Bacula Utility subroutines
31  *
32  */
33
34 /* Return true of buffer has all zero bytes */
35 bool is_buf_zero(const char *buf, int len)
36 {
37    uint64_t *ip;
38    const char *p;
39    int i, len64, done, rem;
40
41    if (buf[0] != 0) {
42       return false;
43    }
44    ip = (uint64_t *)buf;
45    /* Optimize by checking uint64_t for zero */
46    len64 = len / sizeof(uint64_t);
47    for (i=0; i < len64; i++) {
48       if (ip[i] != 0) {
49          return false;
50       }
51    }
52    done = len64 * sizeof(uint64_t);  /* bytes already checked */
53    p = buf + done;
54    rem = len - done;
55    for (i = 0; i < rem; i++) {
56       if (p[i] != 0) {
57          return false;
58       }
59    }
60    return true;
61 }
62
63
64 /* Convert a string in place to lower case */
65 void lcase(char *str)
66 {
67    while (*str) {
68       if (B_ISUPPER(*str)) {
69          *str = tolower((int)(*str));
70        }
71        str++;
72    }
73 }
74
75 /* Convert spaces to non-space character.
76  * This makes scanf of fields containing spaces easier.
77  */
78 void
79 bash_spaces(char *str)
80 {
81    while (*str) {
82       if (*str == ' ')
83          *str = 0x1;
84       str++;
85    }
86 }
87
88 /* Convert spaces to non-space character.
89  * This makes scanf of fields containing spaces easier.
90  */
91 void
92 bash_spaces(POOL_MEM &pm)
93 {
94    char *str = pm.c_str();
95    while (*str) {
96       if (*str == ' ')
97          *str = 0x1;
98       str++;
99    }
100 }
101
102
103 /* Convert non-space characters (0x1) back into spaces */
104 void
105 unbash_spaces(char *str)
106 {
107    while (*str) {
108      if (*str == 0x1)
109         *str = ' ';
110      str++;
111    }
112 }
113
114 /* Convert non-space characters (0x1) back into spaces */
115 void
116 unbash_spaces(POOL_MEM &pm)
117 {
118    char *str = pm.c_str();
119    while (*str) {
120      if (*str == 0x1)
121         *str = ' ';
122      str++;
123    }
124 }
125
126 char *encode_time(utime_t utime, char *buf)
127 {
128    struct tm tm;
129    int n = 0;
130    time_t time = utime;
131
132 #if defined(HAVE_WIN32)
133    /*
134     * Avoid a seg fault in Microsoft's CRT localtime_r(),
135     *  which incorrectly references a NULL returned from gmtime() if
136     *  time is negative before or after the timezone adjustment.
137     */
138    struct tm *gtm;
139
140    if ((gtm = gmtime(&time)) == NULL) {
141       return buf;
142    }
143
144    if (gtm->tm_year == 1970 && gtm->tm_mon == 1 && gtm->tm_mday < 3) {
145       return buf;
146    }
147 #endif
148
149    if (localtime_r(&time, &tm)) {
150       n = sprintf(buf, "%04d-%02d-%02d %02d:%02d:%02d",
151                    tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
152                    tm.tm_hour, tm.tm_min, tm.tm_sec);
153    }
154    return buf+n;
155 }
156
157
158
159 static char hexatable[]="0123456789abcdef";
160
161 /*
162  * do an hexadump of data[0:len] into buf[0:capacity]
163  * a space is inserted between every 4 bytes
164  * usage:
165  *    char buf[10];
166  *    Dmsg2("msglen=%d msg=%s", fd->msglen, hexdump(fd->msg, fd->msglen, buf, sizeof(buf));
167  * ==>
168  *    msglen=36 msg=12345678 12345678
169  */
170 char *hexdump(const char *data, int len, char *buf, int capacity)
171 {
172    char *b=buf;
173    int i=0;
174    while (i<len && capacity>2) {
175       if (i>0 && i%4==0) {
176          *(b++)=' ';
177          capacity--;
178       }
179       if (capacity>2) {
180          *(b++)=hexatable[(data[i]&0xF0)>>4];
181          *(b++)=hexatable[data[i++]&0x0F];
182       }
183       capacity-=2;
184    }
185    *b='\0';
186    return buf;
187 }
188
189 /*
190  * do an ASCII dump of data[0:len] into buf[0:capacity]
191  * non printable chars are replaced by hexa "\xx"
192  * usage:
193  *    char buf[10];
194  *    Dmsg2("msglen=%d msg=%s", fd->msglen, asciidump(fd->msg, fd->msglen, buf, sizeof(buf));
195  * ==>
196  *    msglen=5 msg=abcd\10
197  */
198 char *asciidump(const char *data, int len, char *buf, int capacity)
199 {
200    char *b=buf;
201    const unsigned char *p=(const unsigned char *)data;
202    while (len>0 && capacity>1) {
203       if (isprint(*p)) {
204          *(b++)=*(p++);
205          capacity--;
206       } else {
207          if (capacity>3) {
208             *(b++)='\\';
209             *(b++)=hexatable[((*p)&0xF0)>>4];
210             *(b++)=hexatable[(*(p++))&0x0F];
211          }
212          capacity-=3;
213       }
214       len--;
215    }
216    *b='\0';
217    return buf;
218 }
219
220 char *smartdump(const char *data, int len, char *buf, int capacity, bool *is_ascii)
221 {
222    char *b=buf;
223    int l=len;
224    int c=capacity;
225    const unsigned char *p=(const unsigned char *)data;
226    if (is_ascii != NULL) {
227       *is_ascii = false;
228    }
229    while (l>0 && c>1) {
230       if (isprint(*p)) {
231          *(b++)=*(p++);
232       } else if (isspace(*p) || *p=='\0') {
233          *(b++)=' ';
234          p++;
235       } else {
236          return hexdump(data, len, buf, capacity);
237       }
238       c--;
239       l--;
240    }
241    *b='\0';
242    if (is_ascii != NULL) {
243       *is_ascii = true;
244    }
245    return buf;
246 }
247
248 /*
249  * Convert a JobStatus code into a human readable form
250  */
251 void jobstatus_to_ascii(int JobStatus, char *msg, int maxlen)
252 {
253    const char *jobstat;
254    char buf[100];
255
256    switch (JobStatus) {
257    case JS_Created:
258       jobstat = _("Created");
259       break;
260    case JS_Running:
261       jobstat = _("Running");
262       break;
263    case JS_Blocked:
264       jobstat = _("Blocked");
265       break;
266    case JS_Terminated:
267       jobstat = _("OK");
268       break;
269    case JS_Incomplete:
270       jobstat = _("Incomplete job");
271       break;
272    case JS_FatalError:
273    case JS_ErrorTerminated:
274       jobstat = _("Error");
275       break;
276    case JS_Error:
277       jobstat = _("Non-fatal error");
278       break;
279    case JS_Warnings:
280       jobstat = _("OK -- with warnings");
281       break;
282    case JS_Canceled:
283       jobstat = _("Canceled");
284       break;
285    case JS_Differences:
286       jobstat = _("Verify differences");
287       break;
288    case JS_WaitFD:
289       jobstat = _("Waiting on FD");
290       break;
291    case JS_WaitSD:
292       jobstat = _("Wait on SD");
293       break;
294    case JS_WaitMedia:
295       jobstat = _("Wait for new Volume");
296       break;
297    case JS_WaitMount:
298       jobstat = _("Waiting for mount");
299       break;
300    case JS_WaitStoreRes:
301       jobstat = _("Waiting for Storage resource");
302       break;
303    case JS_WaitJobRes:
304       jobstat = _("Waiting for Job resource");
305       break;
306    case JS_WaitClientRes:
307       jobstat = _("Waiting for Client resource");
308       break;
309    case JS_WaitMaxJobs:
310       jobstat = _("Waiting on Max Jobs");
311       break;
312    case JS_WaitStartTime:
313       jobstat = _("Waiting for Start Time");
314       break;
315    case JS_WaitPriority:
316       jobstat = _("Waiting on Priority");
317       break;
318    case JS_DataCommitting:
319       jobstat = _("SD committing Data");
320       break;
321    case JS_DataDespooling:
322       jobstat = _("SD despooling Data");
323       break;
324    case JS_AttrDespooling:
325       jobstat = _("SD despooling Attributes");
326       break;
327    case JS_AttrInserting:
328       jobstat = _("Dir inserting Attributes");
329       break;
330
331    default:
332       if (JobStatus == 0) {
333          buf[0] = 0;
334       } else {
335          bsnprintf(buf, sizeof(buf), _("Unknown Job termination status=%d"), JobStatus);
336       }
337       jobstat = buf;
338       break;
339    }
340    bstrncpy(msg, jobstat, maxlen);
341 }
342
343 /*
344  * Convert a JobStatus code into a human readable form - gui version
345  */
346 void jobstatus_to_ascii_gui(int JobStatus, char *msg, int maxlen)
347 {
348    const char *cnv = NULL;
349    switch (JobStatus) {
350    case JS_Terminated:
351       cnv = _("Completed successfully");
352       break;
353    case JS_Warnings:
354       cnv = _("Completed with warnings");
355       break;
356    case JS_ErrorTerminated:
357       cnv = _("Terminated with errors");
358       break;
359    case JS_FatalError:
360       cnv = _("Fatal error");
361       break;
362    case JS_Created:
363       cnv = _("Created, not yet running");
364       break;
365    case JS_Canceled:
366       cnv = _("Canceled by user");
367       break;
368    case JS_Differences:
369       cnv = _("Verify found differences");
370       break;
371    case JS_WaitFD:
372       cnv = _("Waiting for File daemon");
373       break;
374    case JS_WaitSD:
375       cnv = _("Waiting for Storage daemon");
376       break;
377    case JS_WaitPriority:
378       cnv = _("Waiting for higher priority jobs");
379       break;
380    case JS_AttrInserting:
381       cnv = _("Batch inserting file records");
382       break;
383    };
384
385    if (cnv) {
386       bstrncpy(msg, cnv, maxlen);
387    } else {
388      jobstatus_to_ascii(JobStatus, msg, maxlen);
389    }
390 }
391
392 /*
393  * Convert Job Termination Status into a string
394  */
395 const char *job_status_to_str(int status, int errors)
396 {
397    const char *str;
398
399    switch (status) {
400    case JS_Terminated:
401       if (errors > 0) {
402          str = _("OK -- with warnings");
403       } else {
404          str = _("OK");
405       }
406       break;
407    case JS_Warnings:
408       str = _("OK -- with warnings");
409       break;
410    case JS_ErrorTerminated:
411    case JS_Error:
412       str = _("Error");
413       break;
414    case JS_FatalError:
415       str = _("Fatal Error");
416       break;
417    case JS_Canceled:
418       str = _("Canceled");
419       break;
420    case JS_Differences:
421       str = _("Differences");
422       break;
423    default:
424       str = _("Unknown term code");
425       break;
426    }
427    return str;
428 }
429
430
431 /*
432  * Convert Job Type into a string
433  */
434 const char *job_type_to_str(int type)
435 {
436    const char *str = NULL;
437
438    switch (type) {
439    case JT_BACKUP:
440       str = _("Backup");
441       break;
442    case JT_MIGRATED_JOB:
443       str = _("Migrated Job");
444       break;
445    case JT_VERIFY:
446       str = _("Verify");
447       break;
448    case JT_RESTORE:
449       str = _("Restore");
450       break;
451    case JT_CONSOLE:
452       str = _("Console");
453       break;
454    case JT_SYSTEM:
455       str = _("System or Console");
456       break;
457    case JT_ADMIN:
458       str = _("Admin");
459       break;
460    case JT_ARCHIVE:
461       str = _("Archive");
462       break;
463    case JT_JOB_COPY:
464       str = _("Job Copy");
465       break;
466    case JT_COPY:
467       str = _("Copy");
468       break;
469    case JT_MIGRATE:
470       str = _("Migrate");
471       break;
472    case JT_SCAN:
473       str = _("Scan");
474       break;
475    }
476    if (!str) {
477       str = _("Unknown Type");
478    }
479    return str;
480 }
481
482 /* Convert ActionOnPurge to string (Truncate, Erase, Destroy)
483  */
484 char *action_on_purge_to_string(int aop, POOL_MEM &ret)
485 {
486    if (aop & ON_PURGE_TRUNCATE) {
487       pm_strcpy(ret, _("Truncate"));
488    }
489    if (!aop) {
490       pm_strcpy(ret, _("None"));
491    }
492    return ret.c_str();
493 }
494
495 /*
496  * Convert Job Level into a string
497  */
498 const char *job_level_to_str(int level)
499 {
500    const char *str;
501
502    switch (level) {
503    case L_BASE:
504       str = _("Base");
505       break;
506    case L_FULL:
507       str = _("Full");
508       break;
509    case L_INCREMENTAL:
510       str = _("Incremental");
511       break;
512    case L_DIFFERENTIAL:
513       str = _("Differential");
514       break;
515    case L_SINCE:
516       str = _("Since");
517       break;
518    case L_VERIFY_CATALOG:
519       str = _("Verify Catalog");
520       break;
521    case L_VERIFY_INIT:
522       str = _("Verify Init Catalog");
523       break;
524    case L_VERIFY_VOLUME_TO_CATALOG:
525       str = _("Verify Volume to Catalog");
526       break;
527    case L_VERIFY_DISK_TO_CATALOG:
528       str = _("Verify Disk to Catalog");
529       break;
530    case L_VERIFY_DATA:
531       str = _("Verify Data");
532       break;
533    case L_VIRTUAL_FULL:
534       str = _("Virtual Full");
535       break;
536    case L_NONE:
537       str = " ";
538       break;
539    default:
540       str = _("Unknown Job Level");
541       break;
542    }
543    return str;
544 }
545
546 const char *volume_status_to_str(const char *status)
547 {
548    int pos;
549    const char *vs[] = {
550       NT_("Append"),    _("Append"),
551       NT_("Archive"),   _("Archive"),
552       NT_("Disabled"),  _("Disabled"),
553       NT_("Full"),      _("Full"),
554       NT_("Used"),      _("Used"),
555       NT_("Cleaning"),  _("Cleaning"),
556       NT_("Purged"),    _("Purged"),
557       NT_("Recycle"),   _("Recycle"),
558       NT_("Read-Only"), _("Read-Only"),
559       NT_("Error"),     _("Error"),
560       NULL,             NULL};
561
562    if (status) {
563      for (pos = 0 ; vs[pos] ; pos += 2) {
564        if ( !strcmp(vs[pos],status) ) {
565          return vs[pos+1];
566        }
567      }
568    }
569
570    return _("Invalid volume status");
571 }
572
573
574 /***********************************************************************
575  * Encode the mode bits into a 10 character string like LS does
576  ***********************************************************************/
577
578 char *encode_mode(mode_t mode, char *buf)
579 {
580   char *cp = buf;
581
582   *cp++ = S_ISDIR(mode) ? 'd' : S_ISBLK(mode)  ? 'b' : S_ISCHR(mode)  ? 'c' :
583           S_ISLNK(mode) ? 'l' : S_ISFIFO(mode) ? 'f' : S_ISSOCK(mode) ? 's' : '-';
584   *cp++ = mode & S_IRUSR ? 'r' : '-';
585   *cp++ = mode & S_IWUSR ? 'w' : '-';
586   *cp++ = (mode & S_ISUID
587                ? (mode & S_IXUSR ? 's' : 'S')
588                : (mode & S_IXUSR ? 'x' : '-'));
589   *cp++ = mode & S_IRGRP ? 'r' : '-';
590   *cp++ = mode & S_IWGRP ? 'w' : '-';
591   *cp++ = (mode & S_ISGID
592                ? (mode & S_IXGRP ? 's' : 'S')
593                : (mode & S_IXGRP ? 'x' : '-'));
594   *cp++ = mode & S_IROTH ? 'r' : '-';
595   *cp++ = mode & S_IWOTH ? 'w' : '-';
596   *cp++ = (mode & S_ISVTX
597                ? (mode & S_IXOTH ? 't' : 'T')
598                : (mode & S_IXOTH ? 'x' : '-'));
599   *cp = '\0';
600   return cp;
601 }
602
603 #if defined(HAVE_WIN32)
604 int do_shell_expansion(char *name, int name_len)
605 {
606    char *src = bstrdup(name);
607
608    ExpandEnvironmentStrings(src, name, name_len);
609
610    free(src);
611
612    return 1;
613 }
614 #else
615 int do_shell_expansion(char *name, int name_len)
616 {
617    static char meta[] = "~\\$[]*?`'<>\"";
618    bool found = false;
619    int len, i, stat;
620    POOLMEM *cmd;
621    BPIPE *bpipe;
622    char line[MAXSTRING];
623    const char *shellcmd;
624
625    /* Check if any meta characters are present */
626    len = strlen(meta);
627    for (i = 0; i < len; i++) {
628       if (strchr(name, meta[i])) {
629          found = true;
630          break;
631       }
632    }
633    if (found) {
634       cmd =  get_pool_memory(PM_FNAME);
635       /* look for shell */
636       if ((shellcmd = getenv("SHELL")) == NULL) {
637          shellcmd = "/bin/sh";
638       }
639       pm_strcpy(&cmd, shellcmd);
640       pm_strcat(&cmd, " -c \"echo ");
641       pm_strcat(&cmd, name);
642       pm_strcat(&cmd, "\"");
643       Dmsg1(400, "Send: %s\n", cmd);
644       if ((bpipe = open_bpipe(cmd, 0, "r"))) {
645          *line = 0;
646          fgets(line, sizeof(line), bpipe->rfd);
647          strip_trailing_junk(line);
648          stat = close_bpipe(bpipe);
649          Dmsg2(400, "stat=%d got: %s\n", stat, line);
650       } else {
651          stat = 1;                    /* error */
652       }
653       free_pool_memory(cmd);
654       if (stat == 0) {
655          bstrncpy(name, line, name_len);
656       }
657    }
658    return 1;
659 }
660 #endif
661
662
663 /*  MAKESESSIONKEY  --  Generate session key with optional start
664                         key.  If mode is TRUE, the key will be
665                         translated to a string, otherwise it is
666                         returned as 16 binary bytes.
667
668     from SpeakFreely by John Walker */
669
670 void make_session_key(char *key, char *seed, int mode)
671 {
672    int j, k;
673    struct MD5Context md5c;
674    unsigned char md5key[16], md5key1[16];
675    char s[1024];
676
677 #define ss sizeof(s)
678
679    s[0] = 0;
680    if (seed != NULL) {
681      bstrncat(s, seed, sizeof(s));
682    }
683
684    /* The following creates a seed for the session key generator
685      based on a collection of volatile and environment-specific
686      information unlikely to be vulnerable (as a whole) to an
687      exhaustive search attack.  If one of these items isn't
688      available on your machine, replace it with something
689      equivalent or, if you like, just delete it. */
690
691 #if defined(HAVE_WIN32)
692    {
693       LARGE_INTEGER     li;
694       DWORD             length;
695       FILETIME          ft;
696
697       bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)GetCurrentProcessId());
698       (void)getcwd(s + strlen(s), 256);
699       bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)GetTickCount());
700       QueryPerformanceCounter(&li);
701       bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)li.LowPart);
702       GetSystemTimeAsFileTime(&ft);
703       bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)ft.dwLowDateTime);
704       bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)ft.dwHighDateTime);
705       length = 256;
706       GetComputerName(s + strlen(s), &length);
707       length = 256;
708       GetUserName(s + strlen(s), &length);
709    }
710 #else
711    bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)getpid());
712    bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)getppid());
713    (void)getcwd(s + strlen(s), 256);
714    bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)clock());
715    bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)time(NULL));
716 #if defined(Solaris)
717    sysinfo(SI_HW_SERIAL,s + strlen(s), 12);
718 #endif
719 #if defined(HAVE_GETHOSTID)
720    bsnprintf(s + strlen(s), ss, "%lu", (uint32_t) gethostid());
721 #endif
722    gethostname(s + strlen(s), 256);
723    bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)getuid());
724    bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)getgid());
725 #endif
726    MD5Init(&md5c);
727    MD5Update(&md5c, (uint8_t *)s, strlen(s));
728    MD5Final(md5key, &md5c);
729    bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)((time(NULL) + 65121) ^ 0x375F));
730    MD5Init(&md5c);
731    MD5Update(&md5c, (uint8_t *)s, strlen(s));
732    MD5Final(md5key1, &md5c);
733 #define nextrand    (md5key[j] ^ md5key1[j])
734    if (mode) {
735      for (j = k = 0; j < 16; j++) {
736         unsigned char rb = nextrand;
737
738 #define Rad16(x) ((x) + 'A')
739         key[k++] = Rad16((rb >> 4) & 0xF);
740         key[k++] = Rad16(rb & 0xF);
741 #undef Rad16
742         if (j & 1) {
743            key[k++] = '-';
744         }
745      }
746      key[--k] = 0;
747    } else {
748      for (j = 0; j < 16; j++) {
749         key[j] = nextrand;
750      }
751    }
752 }
753 #undef nextrand
754
755 void encode_session_key(char *encode, char *session, char *key, int maxlen)
756 {
757    int i;
758    for (i=0; (i < maxlen-1) && session[i]; i++) {
759       if (session[i] == '-') {
760          encode[i] = '-';
761       } else {
762          encode[i] = ((session[i] - 'A' + key[i]) & 0xF) + 'A';
763       }
764    }
765    encode[i] = 0;
766    Dmsg3(000, "Session=%s key=%s encode=%s\n", session, key, encode);
767 }
768
769 void decode_session_key(char *decode, char *session, char *key, int maxlen)
770 {
771    int i, x;
772
773    for (i=0; (i < maxlen-1) && session[i]; i++) {
774       if (session[i] == '-') {
775          decode[i] = '-';
776       } else {
777          x = (session[i] - 'A' - key[i]) & 0xF;
778          if (x < 0) {
779             x += 16;
780          }
781          decode[i] = x + 'A';
782       }
783    }
784    decode[i] = 0;
785    Dmsg3(000, "Session=%s key=%s decode=%s\n", session, key, decode);
786 }
787
788
789
790 /*
791  * Edit job codes into main command line
792  *  %% = %
793  *  %c = Client's name
794  *  %d = Director's name
795  *  %e = Job Exit code
796  *  %i = JobId
797  *  %j = Unique Job id
798  *  %l = job level
799  *  %n = Unadorned Job name
800  *  %s = Since time
801  *  %t = Job type (Backup, ...)
802  *  %r = Recipients
803  *  %v = Volume name
804  *  %b = Job Bytes
805  *  %F = Job Files
806  *  %E = Job Errors
807  *  %R = Job ReadBytes
808  *
809  *  omsg = edited output message
810  *  imsg = input string containing edit codes (%x)
811  *  to = recepients list
812  *
813  */
814 POOLMEM *edit_job_codes(JCR *jcr, char *omsg, char *imsg, const char *to, job_code_callback_t callback)
815 {
816    char *p, *q;
817    const char *str;
818    char add[50];
819    char name[MAX_NAME_LENGTH];
820    int i;
821
822    *omsg = 0;
823    Dmsg1(200, "edit_job_codes: %s\n", imsg);
824    for (p=imsg; *p; p++) {
825       if (*p == '%') {
826          switch (*++p) {
827          case '%':
828             str = "%";
829             break;
830          case 'c':
831             if (jcr) {
832                str = jcr->client_name;
833             } else {
834                str = _("*none*");
835             }
836             break;
837          case 'd':
838             str = my_name;            /* Director's name */
839             break;
840          case 'e':
841             if (jcr) {
842                str = job_status_to_str(jcr->JobStatus, jcr->getErrors());
843             } else {
844                str = _("*none*");
845             }
846             break;
847          case 'E':                    /* Job Errors */
848             str = edit_uint64(jcr->getErrors(), add);
849             break;
850          case 'i':
851             if (jcr) {
852                bsnprintf(add, sizeof(add), "%d", jcr->JobId);
853                str = add;
854             } else {
855                str = _("*none*");
856             }
857             break;
858          case 'j':                    /* Job name */
859             if (jcr) {
860                str = jcr->Job;
861             } else {
862                str = _("*none*");
863             }
864             break;
865          case 'l':
866             if (jcr) {
867                str = job_level_to_str(jcr->getJobLevel());
868             } else {
869                str = _("*none*");
870             }
871             break;
872          case 'n':
873              if (jcr) {
874                 bstrncpy(name, jcr->Job, sizeof(name));
875                 /* There are three periods after the Job name */
876                 for (i=0; i<3; i++) {
877                    if ((q=strrchr(name, '.')) != NULL) {
878                        *q = 0;
879                    }
880                 }
881                 str = name;
882              } else {
883                 str = _("*none*");
884              }
885              break;
886          case 'r':
887             str = to;
888             break;
889          case 's':                    /* since time */
890             if (jcr && jcr->stime) {
891                str = jcr->stime;
892             } else {
893                str = _("*none*");
894             }
895             break;
896          case 'F':                    /* Job Files */
897             str = edit_uint64(jcr->JobFiles, add);
898             break;
899          case 'b':                    /* Job Bytes */
900             str = edit_uint64(jcr->JobBytes, add);
901             break;
902          case 't':
903             if (jcr) {
904                str = job_type_to_str(jcr->getJobType());
905             } else {
906                str = _("*none*");
907             }
908             break;
909          case 'v':
910             if (jcr) {
911                if (jcr->VolumeName && jcr->VolumeName[0]) {
912                   str = jcr->VolumeName;
913                } else {
914                   str = "";
915                }
916             } else {
917                str = _("*none*");
918             }
919             break;
920          case 'P':
921             edit_uint64(getpid(), add);
922             str = add;
923             break;
924          case 'R':                    /* Job ReadBytes */
925             str = edit_uint64(jcr->ReadBytes, add);
926             break;
927          default:
928             str = NULL;
929             if (callback != NULL) {
930                 str = callback(jcr, p);
931             }
932
933             if (!str) {
934                 add[0] = '%';
935                 add[1] = *p;
936                 add[2] = 0;
937                 str = add;
938             }
939             break;
940          }
941       } else {
942          add[0] = *p;
943          add[1] = 0;
944          str = add;
945       }
946       Dmsg1(1200, "add_str %s\n", str);
947       pm_strcat(&omsg, str);
948       Dmsg1(1200, "omsg=%s\n", omsg);
949    }
950    return omsg;
951 }
952
953 void set_working_directory(char *wd)
954 {
955    struct stat stat_buf;
956
957    if (wd == NULL) {
958       Emsg0(M_ERROR_TERM, 0, _("Working directory not defined. Cannot continue.\n"));
959    }
960    if (stat(wd, &stat_buf) != 0) {
961       Emsg1(M_ERROR_TERM, 0, _("Working Directory: \"%s\" not found. Cannot continue.\n"),
962          wd);
963    }
964    if (!S_ISDIR(stat_buf.st_mode)) {
965       Emsg1(M_ERROR_TERM, 0, _("Working Directory: \"%s\" is not a directory. Cannot continue.\n"),
966          wd);
967    }
968    working_directory = wd;            /* set global */
969 }
970
971 const char *last_path_separator(const char *str)
972 {
973    if (*str != '\0') {
974       for (const char *p = &str[strlen(str) - 1]; p >= str; p--) {
975          if (IsPathSeparator(*p)) {
976             return p;
977          }
978       }
979    }
980    return NULL;
981 }