1 /*
2    Bacula(R) - The Network Backup Solution
3 
4    Copyright (C) 2000-2020 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 /* You can include this file to your plugin to have
21  * access to some common tools and utilities provided by Bacula
22  */
23 
24 #ifndef PCOMMON_H
25 #define PCOMMON_H
26 
27 #define JT_BACKUP                'B'  /* Backup Job */
28 #define JT_RESTORE               'R'  /* Restore Job */
29 
30 #define L_FULL                   'F'  /* Full backup */
31 #define L_INCREMENTAL            'I'  /* since last backup */
32 #define L_DIFFERENTIAL           'D'  /* since last full backup */
33 
34 #ifndef DLL_IMP_EXP
35 # if defined(BUILDING_DLL)
36 #   define DLL_IMP_EXP   __declspec(dllexport)
37 # elif defined(USING_DLL)
38 #   define DLL_IMP_EXP   __declspec(dllimport)
39 # else
40 #   define DLL_IMP_EXP
41 # endif
42 #endif
43 
44 #ifdef SMARTALLOC
45 DLL_IMP_EXP void *sm_malloc(const char *fname, int lineno, unsigned int nbytes);
46 DLL_IMP_EXP void sm_free(const char *file, int line, void *fp);
47 DLL_IMP_EXP void *reallymalloc(const char *fname, int lineno, unsigned int nbytes);
48 DLL_IMP_EXP void reallyfree(const char *file, int line, void *fp);
49 
50 #ifndef bmalloc
51 # define bmalloc(s)      sm_malloc(__FILE__, __LINE__, (s))
52 # define bfree(o)        sm_free(__FILE__, __LINE__, (o))
53 #endif
54 
55 #define SM_CHECK sm_check(__FILE__, __LINE__, false)
56 
57 #ifdef malloc
58 #undef malloc
59 #undef free
60 #endif
61 
62 #define malloc(s)    sm_malloc(__FILE__, __LINE__, (s))
63 #define free(o)      sm_free(__FILE__, __LINE__, (o))
64 
65 /* Looks to be broken on scientific linux */
66 #ifdef xxxx
new(size_t size,char const * file,int line)67 inline void *operator new(size_t size, char const * file, int line)
68 {
69    void *pnew = sm_malloc(file,line, size);
70    memset((char *)pnew, 0, size);
71    return pnew;
72 }
73 
74 inline void *operator new[](size_t size, char const * file, int line)
75 {
76    void *pnew = sm_malloc(file, line, size);
77    memset((char *)pnew, 0, size);
78    return pnew;
79 }
80 
new(size_t size)81 inline void *operator new(size_t size)
82 {
83    void *pnew = sm_malloc(__FILE__, __LINE__, size);
84    memset((char *)pnew, 0, size);
85    return pnew;
86 }
87 
88 inline void *operator new[](size_t size)
89 {
90    void *pnew = sm_malloc(__FILE__, __LINE__, size);
91    memset((char *)pnew, 0, size);
92    return pnew;
93 }
94 
95 #define new   new(__FILE__, __LINE__)
96 
delete(void * buf)97 inline void operator delete(void *buf)
98 {
99    sm_free( __FILE__, __LINE__, buf);
100 }
101 
102 inline void operator delete[] (void *buf)
103 {
104   sm_free(__FILE__, __LINE__, buf);
105 }
106 
107 inline void operator delete[] (void *buf, char const * file, int line)
108 {
109   sm_free(file, line, buf);
110 }
111 
delete(void * buf,char const * file,int line)112 inline void operator delete(void *buf, char const * file, int line)
113 {
114    sm_free(file, line, buf);
115 }
116 
117 #endif
118 #endif  /* !SMARTALLOC */
119 
120 #define Dmsg(context, level,  ...) bfuncs->DebugMessage(context, __FILE__, __LINE__, level, __VA_ARGS__ )
121 #define Jmsg(context, type,  ...) bfuncs->JobMessage(context, __FILE__, __LINE__, type, 0, __VA_ARGS__ )
122 
123 
124 #ifdef USE_CMD_PARSER
125 #include "lib/cmd_parser.h"
126 #endif /* USE_CMD_PARSER */
127 
128 #ifdef USE_ADD_DRIVE
129 /* Keep drive letters for windows vss snapshot */
add_drive(char * drives,int * nCount,char * fname)130 static void add_drive(char *drives, int *nCount, char *fname) {
131    if (strlen(fname) >= 2 && B_ISALPHA(fname[0]) && fname[1] == ':') {
132       /* always add in uppercase */
133       char ch = toupper(fname[0]);
134       /* if not found in string, add drive letter */
135       if (!strchr(drives,ch)) {
136          drives[*nCount] = ch;
137          drives[*nCount+1] = 0;
138          (*nCount)++;
139       }
140    }
141 }
142 
143 /* Copy our drive list to Bacula core list */
copy_drives(char * drives,char * dest)144 static void copy_drives(char *drives, char *dest) {
145    int last = strlen(dest);     /* dest is 27 bytes long */
146    for (char *p = drives; *p && last < 26; p++) {
147       if (!strchr(dest, *p)) {
148          dest[last++] = *p;
149          dest[last] = 0;
150       }
151    }
152 }
153 #endif  /* USE_ADD_DRIVE */
154 
155 #endif  /* ! PCOMMON_H */
156 
157 #ifdef USE_JOB_LIST
158 
159 /* This class is used to store locally the job history, you can attach data
160  * to it such as snapshot names
161  * !!! Don't forget that this file may be deleted by the user. !!!
162  */
163 
164 class joblist: public SMARTALLOC
165 {
166 private:
167    bpContext *ctx;
168 
169 public:
170    char level;                  /* level of the job */
171 
172    char base[MAX_NAME_LENGTH];  /* base name */
173    char key[MAX_NAME_LENGTH];   /* group of backup */
174    char name[MAX_NAME_LENGTH];  /* job name */
175    char prev[MAX_NAME_LENGTH];  /* based on jobname */
176    char root[MAX_NAME_LENGTH];  /* root of this branch */
177    char rootdiff[MAX_NAME_LENGTH];  /* root of diff if any */
178 
179    btime_t job_time;           /* job time */
180 
init()181    void init() {
182       level = 0;
183       job_time = 0;
184       *key = *name = *prev = *root = *rootdiff = 0;
185       set_base("jobs.dat");
186       ctx = NULL;
187    }
188 
set_base(const char * b)189    void set_base(const char *b) {
190       strncpy(base, b, sizeof(base));
191    }
192 
joblist(bpContext * actx)193    joblist(bpContext *actx) { init(); ctx = actx; }
194 
joblist(bpContext * actx,const char * akey,const char * jobname,const char * prevjobname,char joblevel)195    joblist(bpContext *actx,
196         const char *akey,
197         const char *jobname,
198         const char *prevjobname,
199         char joblevel)
200    {
201       init();
202       ctx = actx;
203       if (jobname) {
204          strncpy(name, jobname, MAX_NAME_LENGTH);
205       }
206 
207       if (prevjobname) {
208          strncpy(prev, prevjobname, MAX_NAME_LENGTH);
209       }
210 
211       level = joblevel;
212 
213       if (akey) {
214          strncpy(key, akey, MAX_NAME_LENGTH);
215 
216       } else {
217          get_key_from_name();
218       }
219    }
220 
~joblist()221    ~joblist() { }
222 
223    /* Will extract the name from the full job name */
get_key_from_name()224    bool get_key_from_name() {
225       // pluginTest.2012-07-19_16.59.21_11
226       int l = strlen(name);
227       int dlen = 23; // strlen(".2012-07-19_16.59.21_11");
228 
229       if (l > dlen) {           /* we probably have a key */
230          int start = l - dlen;
231          if (name[start] == '.' &&
232              B_ISDIGIT(name[start + 1]) &&   // 2
233              B_ISDIGIT(name[start + 2]) &&   // 0
234              B_ISDIGIT(name[start + 3]) &&   // 1
235              B_ISDIGIT(name[start + 4]) &&   // 2
236              name[start + 5] == '-' &&       // -
237              B_ISDIGIT(name[start + 6]) &&   // 0
238              B_ISDIGIT(name[start + 7]))     // 7
239          {
240             bstrncpy(key, name, start + 1);
241             Dmsg(ctx, dbglvl+100, "key is %s from jobname %s\n", key, name);
242             return true;
243          }
244       }
245       Dmsg(ctx, dbglvl+100, "Unable to get key from jobname %s\n", name);
246       return false;
247    }
248 
249    bool find_job(const char *name, POOLMEM **data=NULL);   /* set root, job_time */
250    bool find_root_job();
251    void store_job(char *data);
252    void prune_jobs(char *build_cmd(void *arg, const char *data, const char *job),
253                    void *arg, alist *jobs);
254 };
255 
256 static pthread_mutex_t joblist_mutex = PTHREAD_MUTEX_INITIALIZER;
257 
find_job(const char * name,POOLMEM ** data)258 bool joblist::find_job(const char *name, POOLMEM **data)
259 {
260    BFILE fp;
261    FILE *f;
262    POOLMEM *tmp;
263    char buf[1024];
264    char curkey[MAX_NAME_LENGTH];  /* key */
265    char curjobname[MAX_NAME_LENGTH]; /* jobname */
266    char prevjob[MAX_NAME_LENGTH]; /* last jobname */
267    char rootjob[MAX_NAME_LENGTH]; /* root jobname */
268    char t[MAX_NAME_LENGTH];
269    char curlevel;
270    bool ok=false;
271 
272    *root = 0;
273    job_time = 0;
274    *rootdiff = 0;
275 
276    binit(&fp);
277    set_portable_backup(&fp);
278 
279    tmp = get_pool_memory(PM_FNAME);
280    Mmsg(tmp, "%s/%s", working, base);
281 
282    P(joblist_mutex);
283    if (bopen(&fp, tmp, O_RDONLY, 0) < 0) {
284       berrno be;
285       Jmsg(ctx, M_ERROR, "Unable to open job database %s for reading. ERR=%s\n",
286            tmp, be.bstrerror(errno));
287       goto bail_out;
288    }
289 
290    f = fdopen(fp.fid, "r");
291    if (!f) {
292       berrno be;
293       Jmsg(ctx, M_ERROR, "Unable to open job database. ERR=%s\n",
294            be.bstrerror(errno));
295       goto bail_out;
296    }
297 
298    while (!ok && fgets(buf, sizeof(buf), f) != NULL) {
299       *curkey = *curjobname = *rootjob = *prevjob = 0;
300 
301       Dmsg(ctx, dbglvl+100, "line = [%s]\n", buf);
302 
303       if (sscanf(buf, "time=%60s level=%c key=%127s name=%127s root=%127s prev=%127s",
304                  t, &curlevel, curkey, curjobname, rootjob, prevjob) != 6) {
305 
306          if (sscanf(buf, "time=%60s level=F key=%127s name=%127s",
307                         t, curkey, curjobname) != 3) {
308             Dmsg(ctx, dbglvl+100, "Bad line l=[%s]\n", buf);
309             continue;
310          }
311       }
312 
313       if (strcmp(name, curjobname) == 0 &&
314           strcmp(key, curkey) == 0)
315       {
316          job_time = str_to_uint64(t);
317          bstrncpy(root, rootjob, MAX_NAME_LENGTH);
318          if (curlevel == 'D') {
319             bstrncpy(rootdiff, curjobname, MAX_NAME_LENGTH);
320          }
321 
322          if (data) {
323             pm_strcpy(data, strstr(buf, " vol=") +  5);
324             strip_trailing_newline(*data);
325             unbash_spaces(*data);
326          }
327 
328          ok = true;
329          Dmsg(ctx, dbglvl+100, "Found job root %s -> %s -> %s\n",
330               rootdiff, root, curjobname);
331       }
332    }
333 
334    fclose(f);
335 
336 bail_out:
337    V(joblist_mutex);
338    free_pool_memory(tmp);
339    return ok;
340 
341 }
342 
343 /* Find the root job for the current job */
find_root_job()344 bool joblist::find_root_job()
345 {
346    BFILE fp;
347    FILE *f;
348    POOLMEM *tmp;
349    char buf[1024];
350    char curkey[MAX_NAME_LENGTH];  /* key */
351    char curjobname[MAX_NAME_LENGTH]; /* jobname */
352    char prevjob[MAX_NAME_LENGTH]; /* last jobname */
353    char rootjob[MAX_NAME_LENGTH]; /* root jobname */
354    char t[MAX_NAME_LENGTH];
355    char curlevel;
356    bool ok=false;
357 
358    *root = 0;
359    job_time = 0;
360 
361    if (level == 'F') {
362       bstrncpy(root, name, MAX_NAME_LENGTH);
363       return true;
364    }
365 
366    binit(&fp);
367    set_portable_backup(&fp);
368 
369    tmp = get_pool_memory(PM_FNAME);
370    Mmsg(tmp, "%s/%s", working, base);
371 
372    P(joblist_mutex);
373    if (bopen(&fp, tmp, O_RDONLY, 0) < 0) {
374       berrno be;
375       Jmsg(ctx, M_ERROR, "Unable to prune previous jobs. "
376            "Can't open %s for reading ERR=%s\n",
377            tmp, be.bstrerror(errno));
378       goto bail_out;
379    }
380 
381    f = fdopen(fp.fid, "r");
382    if (!f) {
383       berrno be;
384       Jmsg(ctx, M_ERROR, "Unable to prune previous jobs. ERR=%s\n",
385            be.bstrerror(errno));
386       goto bail_out;
387    }
388 
389    while (!ok && fgets(buf, sizeof(buf), f) != NULL) {
390       *curkey = *curjobname = *rootjob = *prevjob = 0;
391 
392       Dmsg(ctx, dbglvl+100, "line = [%s]\n", buf);
393 
394       if (sscanf(buf, "time=%60s level=%c key=%127s name=%127s root=%127s prev=%127s",
395                  t, &curlevel, curkey, curjobname, rootjob, prevjob) != 6) {
396 
397          if (sscanf(buf, "time=%60s level=F key=%127s name=%127s",
398                         t, curkey, curjobname) == 3) {
399             bstrncpy(rootjob, curjobname, MAX_NAME_LENGTH);
400             *prevjob = 0;
401             curlevel = 'F';
402 
403          } else {
404             Dmsg(ctx, dbglvl+100, "Bad line l=[%s]\n", buf);
405             continue;
406          }
407       }
408 
409       if (strcmp(key,  curkey)  == 0  &&
410           strcmp(prev, curjobname) == 0)
411       {
412          bstrncpy(root, rootjob, MAX_NAME_LENGTH);
413 
414          if (curlevel == 'D') {
415             bstrncpy(rootdiff, curjobname, MAX_NAME_LENGTH);
416          }
417          ok = true;
418          Dmsg(ctx, dbglvl+100, "Found job root %s -> %s -> %s\n",
419               rootdiff, root, curjobname);
420       }
421    }
422 
423    fclose(f);
424 
425 bail_out:
426    V(joblist_mutex);
427    free_pool_memory(tmp);
428    return true;
429 }
430 
431 /* Store the current job in the jobs.dat for a specific data list */
store_job(char * data)432 void joblist::store_job(char *data)
433 {
434    BFILE fp;
435    int l;
436    POOLMEM *tmp = NULL;
437    btime_t now;
438 
439    /* Not initialized, no need to store jobs */
440    if (*name == 0 || !level) {
441       Dmsg(ctx, dbglvl+100, "store_job fail name=%s level=%d\n", name, level);
442       return;
443    }
444 
445    find_root_job();
446 
447    binit(&fp);
448    set_portable_backup(&fp);
449 
450    P(joblist_mutex);
451 
452    tmp = get_pool_memory(PM_FNAME);
453    Mmsg(tmp, "%s/%s", working, base);
454    if (bopen(&fp, tmp, O_WRONLY|O_CREAT|O_APPEND, 0600) < 0) {
455       berrno be;
456       Jmsg(ctx, M_ERROR, "Unable to update the job history. ERR=%s\n",
457            be.bstrerror(errno));
458       goto bail_out;
459    }
460 
461    now = time(NULL);
462 
463    bash_spaces(data);
464 
465    if (level == 'F') {
466       l = Mmsg(tmp, "time=%lld level=%c key=%s name=%s vollen=%d vol=%s\n",
467                now, level, key, name, strlen(data), data);
468 
469    } else {
470       l = Mmsg(tmp, "time=%lld level=%c key=%s name=%s root=%s prev=%s vollen=%d vol=%s\n",
471                now, level, key, name, root, prev, strlen(data), data);
472    }
473 
474    if (bwrite(&fp, tmp, l) != l) {
475       berrno be;
476       Jmsg(ctx, M_ERROR, "Unable to update the job history. ERR=%s\n",
477            be.bstrerror(errno));
478    }
479 
480    bclose(&fp);
481 
482 bail_out:
483    V(joblist_mutex);
484    free_pool_memory(tmp);
485 }
486 
487 /* Prune jobs at the end of the job, this function can generate commands
488  * in order to cleanup something
489  */
prune_jobs(char * build_cmd (void * arg,const char * data,const char * job),void * arg,alist * jobs)490 void joblist::prune_jobs(char *build_cmd(void *arg, const char *data, const char *job),
491                          void *arg, alist *jobs)
492 {
493    BFILE fp, fpout;
494    FILE *f=NULL;
495    POOLMEM *tmp;
496    POOLMEM *tmpout;
497    POOLMEM *data;
498    POOLMEM *buf;
499    char curkey[MAX_NAME_LENGTH];  /* key */
500    char jobname[MAX_NAME_LENGTH]; /* jobname */
501    char prevjob[MAX_NAME_LENGTH]; /* last jobname */
502    char rootjob[MAX_NAME_LENGTH]; /* root jobname */
503    char t[MAX_NAME_LENGTH];
504    uint32_t datalen;
505    char curlevel;
506    bool keep;
507    bool ok=false;
508    int count=0, len;
509 
510    /* In Incremental, it means that the previous Full/Diff is well terminated */
511    if (level != 'I') {
512       return;
513    }
514 
515    find_root_job();
516 
517    binit(&fp);
518    set_portable_backup(&fp);
519 
520    binit(&fpout);
521    set_portable_backup(&fpout);
522 
523    tmp = get_pool_memory(PM_FNAME);
524    Mmsg(tmp, "%s/%s", working, base);
525 
526    tmpout = get_pool_memory(PM_FNAME);
527    Mmsg(tmpout, "%s/%s.swap", working, base);
528 
529    buf = get_pool_memory(PM_FNAME);
530    data = get_pool_memory(PM_FNAME);
531    *buf = *data = 0;
532 
533    P(joblist_mutex);
534    if (bopen(&fp, tmp, O_RDONLY, 0) < 0) {
535       berrno be;
536       Jmsg(ctx, M_ERROR, "Unable to prune previous jobs. "
537            "Can't open %s for reading ERR=%s\n",
538            tmp, be.bstrerror(errno));
539       goto bail_out;
540    }
541    if (bopen(&fpout, tmpout, O_CREAT|O_WRONLY, 0600) < 0) {
542       berrno be;
543       Jmsg(ctx, M_ERROR, "Unable to prune previous jobs. "
544            "Can't open %s for writing ERR=%s\n",
545            tmpout, be.bstrerror(errno));
546       goto bail_out;
547    }
548 
549    f = fdopen(fp.fid, "r");     /* we use fgets from open() */
550    if (!f) {
551       berrno be;
552       Jmsg(ctx, M_ERROR, "Unable to prune previous jobs. ERR=%s\n",
553            be.bstrerror(errno));
554       goto bail_out;
555    }
556 
557    while (fgets(buf, sizeof_pool_memory(buf), f) != NULL) {
558       *data = *curkey = *jobname = *rootjob = *prevjob = 0;
559       keep = false;
560       datalen = 0;
561 
562       len = strlen(buf);
563       if (len > 0 && buf[len -1] != '\n') {
564          /* The line is larger than the buffer, we need to capture the rest */
565          bool ok=false;
566          while (!ok) {
567             Dmsg(ctx, dbglvl+100, "Reading extra 1024 bytes, len=%d\n", len);
568             buf = check_pool_memory_size(buf, sizeof_pool_memory(buf) + 1024);
569             if (fgets(buf + len, 1023, f) == NULL) {
570                ok = true;
571             }
572             len = strlen(buf);
573             if (buf[len - 1] == '\n') {
574                ok = true;
575             }
576             if (len > 32000) {  /* sanity check */
577                ok = true;
578             }
579          }
580       }
581 
582       /* We don't capture the vol list, because our sscanf is limited to 1000 bytes  */
583       if (sscanf(buf, "time=%60s level=%c key=%127s name=%127s root=%127s prev=%127s vollen=%d vol=",
584                  t, &curlevel, curkey, jobname, rootjob, prevjob, &datalen) != 7) {
585 
586          if (sscanf(buf, "time=%60s level=F key=%127s name=%127s vollen=%d vol=",
587                     t, curkey, jobname, &datalen) == 4) {
588             *rootdiff = *rootjob = *prevjob = 0;
589             curlevel = 'F';
590 
591          } else {
592             Dmsg(ctx, dbglvl+100, "Bad line l=[%s]\n", buf);
593             keep = true;
594          }
595       }
596 
597       if (!keep) {
598          pm_strcpy(data, strstr(buf, " vol=") +  5);
599          strip_trailing_newline(data);
600          unbash_spaces(data);
601 
602          if (datalen != strlen(data)) {
603             Dmsg(ctx, dbglvl+100, "Bad data line datalen != strlen(data) %d != %d\n", datalen, strlen(data));
604             Dmsg(ctx, dbglvl+100, "v=[%s]\n", data);
605          }
606       }
607 
608 
609       if (!keep &&
610           (strcmp(key,  curkey)  != 0 ||
611            strcmp(name, jobname) == 0 ||
612            strcmp(prev, jobname) == 0 ||
613            strcmp(root, jobname) == 0 ||
614            strcmp(rootdiff, jobname) == 0))
615       {
616          keep = true;
617       }
618 
619       if (keep) {
620          if (bwrite(&fpout, buf, len) < 0) {
621             berrno be;
622             Jmsg(ctx, M_ERROR, "Unable to update the job history. ERR=%s\n",
623                  be.bstrerror(errno));
624             goto bail_out;
625          }
626 
627       } else if (build_cmd) {
628          count++;
629          Dmsg(ctx, dbglvl+100, "Can prune jobname %s\n", jobname);
630 
631          char *p2 = data;
632          for(char *p = data; *p; p++) {
633             if (*p == ',') {
634                *p = 0;
635                jobs->append(bstrdup(build_cmd(arg, p2, jobname)));
636                p2 = p + 1 ;
637             }
638          }
639          jobs->append(bstrdup(build_cmd(arg, p2, jobname)));
640 
641       } else if (jobs) {
642          jobs->append(bstrdup(data));
643       }
644    }
645 
646    ok = true;
647 
648 bail_out:
649    if (f) {
650       fclose(f);
651    }
652    if (is_bopen(&fpout)) {
653       bclose(&fpout);
654    }
655 
656    /* We can switch the file */
657    if (ok) {
658       unlink(tmp);
659 
660       if (rename(tmpout, tmp) < 0) {
661          berrno be;
662          Jmsg(ctx, M_ERROR, "Unable to update the job history. ERR=%s\n",
663               be.bstrerror(errno));
664       }
665    }
666 
667    V(joblist_mutex);
668    free_pool_memory(tmp);
669    free_pool_memory(tmpout);
670    free_pool_memory(data);
671    free_pool_memory(buf);
672 
673    Dmsg(ctx, dbglvl+100, "Pruning %d jobs\n", count);
674 }
675 
676 
677 #endif  /* ! USE_JOB_LIST */
678 
679 #ifdef USE_FULL_WRITE
680 static int32_t full_write(int fd, const char *ptr, int32_t nbytes, bool *canceled=NULL)
681 {
682    ssize_t nleft, nwritten;
683    nleft = nbytes;
684    while (nleft > 0 && (canceled == NULL || *canceled == false)) {
685       do {
686          errno = 0;
687          nwritten = write(fd, ptr, nleft);
688       } while (nwritten == -1 && errno == EINTR && (canceled == NULL || *canceled == false));
689 
690       if (nwritten <= 0) {
691          return nwritten;          /* error */
692       }
693       nleft -= nwritten;
694       ptr += nwritten;
695    }
696    return nbytes - nleft;
697 }
698 #endif
699 
700 #ifdef USE_MAKEDIR
701 /* Skip leading slash(es) */
makedir(char * path,mode_t mode)702 static bool makedir(char *path, mode_t mode)
703 {
704    struct stat statp;
705    char *p = path;
706 
707    while (IsPathSeparator(*p)) {
708       p++;
709    }
710    while ((p = first_path_separator(p))) {
711       char save_p;
712       save_p = *p;
713       *p = 0;
714       if (mkdir(path, mode) != 0) {
715          if (stat(path, &statp) != 0) {
716             *p = save_p;
717             return false;
718          } else if (!S_ISDIR(statp.st_mode)) {
719             *p = save_p;
720             return false;
721          }
722       }
723       *p = save_p;
724       while (IsPathSeparator(*p)) {
725          p++;
726       }
727    }
728    return true;
729 }
730 #endif
731