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