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 /*
21  * Routines for writing to a file from the Cloud device.
22  *
23  *  This is for testing purposes only.
24  #
25  *  NOTE!!! This cloud driver is not compatible with
26  *   any disk-changer script for changing Volumes.
27  *   It does however work with Bacula Virtual autochangers.
28  *
29  * Written by Kern Sibbald, May MMXVI
30  *
31  */
32 
33 #include "file_driver.h"
34 
35 static const int64_t dbglvl = DT_CLOUD|50;
36 
37 #include <fcntl.h>
38 
39 /* Imported functions */
40 const char *mode_to_str(int mode);
41 int breaddir(DIR *dirp, POOLMEM *&dname);
42 
43 /* Forward referenced functions */
44 
45 /* Const and Static definitions */
46 
47 /*
48  * Put a cache object into the cloud (i.e. local disk)
49  *  or visa-versa.
50  */
put_object(transfer * xfer,const char * in_fname,const char * out_fname,bwlimit * limit)51 bool file_driver::put_object(transfer *xfer, const char *in_fname, const char *out_fname, bwlimit *limit)
52 {
53    struct stat statbuf;
54    char *p, *f;
55    char save_separator;
56    ssize_t rbytes, wbytes;
57    uint32_t read_len;
58    int64_t obj_len;
59    FILE *infile=NULL, *outfile=NULL;
60    POOLMEM *buf = get_memory(buf_len);
61 
62    Enter(dbglvl);
63    Dmsg2(dbglvl, "Put from: %s to %s\n", in_fname, out_fname);
64 
65    /*
66     * First work on output file
67     */
68    /* Split out_fname into path + file */
69    for (p=f=const_cast<char*>(out_fname); *p; p++) {
70       if (IsPathSeparator(*p)) {
71          f = p;                       /* set pos of last slash */
72       }
73    }
74    if (!IsPathSeparator(*f)) {         /* did we find a slash? */
75       Mmsg1(xfer->m_message, "Could not find path name for output file: %s\n", out_fname);
76       goto get_out;
77    }
78    save_separator = *f;
79    *f = 0;                         /* terminate path */
80 
81    /* const_cast should not be necessary here but is due the makedir interface */
82    if (!makedir(NULL, const_cast<char*>(out_fname), 0740)) {
83       Mmsg1(xfer->m_message, "Could not makedir output directory: %s\n", out_fname);
84       *f = save_separator;
85       goto get_out;
86    }
87    *f = save_separator;
88 
89    if (lstat(out_fname, &statbuf) == -1) {
90        outfile = bfopen(out_fname, "w");
91    } else {
92       /* append to existing file */
93       outfile = bfopen(out_fname, "r+");
94    }
95 
96    if (!outfile) {
97       berrno be;
98       Mmsg2(xfer->m_message, "Could not open output file %s. ERR=%s\n",
99             out_fname, be.bstrerror());
100       goto get_out;
101    }
102 
103    /*
104     * Now work on input file
105     */
106    if (lstat(in_fname, &statbuf) == -1) {
107       berrno be;
108       Mmsg2(xfer->m_message, "Failed to stat input file %s. ERR=%s\n",
109          in_fname, be.bstrerror());
110       goto get_out;
111    }
112 
113    obj_len = statbuf.st_size;
114    Dmsg1(dbglvl, "Object length to copy is: %lld bytes.\n", obj_len);
115    if (obj_len == 0) {  /* Not yet created nothing to do */
116       goto get_out;
117    }
118 
119    infile = bfopen(in_fname, "r");
120 
121    if (!infile) {
122       berrno be;
123       Mmsg2(xfer->m_message, "Failed to open input file %s. ERR=%s\n",
124          in_fname, be.bstrerror());
125       goto get_out;
126    }
127 
128    while (obj_len > 0) {
129       if (xfer->is_canceled()) {
130          Mmsg(xfer->m_message, "Job is canceled.\n");
131          goto get_out;
132       }
133       read_len = (obj_len > buf_len) ? buf_len : obj_len;
134       Dmsg3(dbglvl+10, "obj_len=%d buf_len=%d read_len=%d\n", obj_len, buf_len, read_len);
135       rbytes = fread(buf, 1, read_len, infile);
136       Dmsg1(dbglvl+10, "Read %d bytes.\n", rbytes);
137       if (rbytes <= 0) {
138          berrno be;
139          Mmsg2(xfer->m_message, "Error reading input file %s. ERR=%s\n",
140             in_fname, be.bstrerror());
141          goto get_out;
142       }
143       wbytes = fwrite(buf, 1, rbytes, outfile);
144       Dmsg2(dbglvl+10, "Wrote: %d bytes wanted %d bytes.\n", wbytes, rbytes);
145       if (wbytes < 0) {
146          berrno be;
147          Mmsg2(xfer->m_message, "Error writing output file %s. ERR=%s\n",
148             out_fname, be.bstrerror());
149          goto get_out;
150       }
151       obj_len -= rbytes;
152       xfer->increment_processed_size(rbytes);
153       if (limit->use_bwlimit()) {
154          limit->control_bwlimit(rbytes);
155       }
156    }
157 
158 get_out:
159    free_memory(buf);
160    if (infile) {
161       fclose(infile);
162    }
163    if (outfile) {
164       if (fclose(outfile) != 0) {
165          berrno be;
166          Mmsg2(xfer->m_message, "Failed to close file %s: %s\n", out_fname, be.bstrerror());
167       }
168       /* Get stats on the result part and fill the xfer res */
169       if (lstat(out_fname, &statbuf) == -1) {
170          berrno be;
171          Mmsg2(xfer->m_message, "Failed to stat file %s: %s\n", out_fname, be.bstrerror());
172       } else {
173          xfer->m_res_size  = statbuf.st_size;
174          xfer->m_res_mtime = statbuf.st_mtime;
175       }
176    }
177    Leave(dbglvl);
178    return (xfer->m_message[0] == 0);
179 }
180 
get_cloud_object(transfer * xfer,const char * cloud_fname,const char * cache_fname)181 bool file_driver::get_cloud_object(transfer *xfer, const char *cloud_fname, const char *cache_fname)
182 {
183    return put_object(xfer, cloud_fname, cache_fname, &download_limit);
184 }
185 
truncate_cloud_volume(const char * VolumeName,ilist * trunc_parts,cancel_callback * cancel_cb,POOLMEM * & err)186 bool file_driver::truncate_cloud_volume(const char *VolumeName, ilist *trunc_parts, cancel_callback *cancel_cb, POOLMEM *&err)
187 {
188    bool rtn = true;
189    int i;
190    POOLMEM *filename = get_pool_memory(PM_FNAME);
191    for (i=1; (i <= (int)trunc_parts->last_index()); i++) {
192       if (!trunc_parts->get(i)) {
193          continue;
194       }
195       make_cloud_filename(filename, VolumeName, "part", i);
196       if (unlink(filename) != 0 && errno != ENOENT) {
197          berrno be;
198          Mmsg3(err, "truncate_cloud_volume for %s: Unable to delete %s. ERR=%s\n", VolumeName, filename, be.bstrerror());
199          rtn = false;
200       } else {
201          Mmsg2(err, "truncate_cloud_volume for %s: Unlink file %s.\n", VolumeName, filename);
202       }
203    }
204 
205    free_pool_memory(filename);
206    return rtn;
207 }
208 
clean_cloud_volume(const char * VolumeName,cleanup_cb_type * cb,cleanup_ctx_type * ctx,cancel_callback * cancel_cb,POOLMEM * & err)209 bool file_driver::clean_cloud_volume(const char *VolumeName, cleanup_cb_type *cb, cleanup_ctx_type *ctx, cancel_callback *cancel_cb, POOLMEM *&err)
210 {
211    Enter(dbglvl);
212 
213    if (cb == NULL || ctx == NULL || strlen(VolumeName) == 0) {
214       pm_strcpy(err, "Invalid argument");
215       return false;
216    }
217 
218    POOLMEM *vol_dir = get_pool_memory(PM_NAME);
219 
220    pm_strcpy(vol_dir, hostName);
221 
222    if (!IsPathSeparator(vol_dir[strlen(vol_dir)-1])) {
223       pm_strcat(vol_dir, "/");
224    }
225    pm_strcat(vol_dir, VolumeName);
226 
227    DIR* dp = NULL;
228    struct dirent *entry = NULL;
229    struct stat statbuf;
230    int name_max;
231    bool ok = false;
232    POOL_MEM dname(PM_FNAME);
233    int status = 0;
234 
235    Dmsg1(dbglvl, "Searching for parts in: %s\n", vol_dir);
236 
237    if (!(dp = opendir(vol_dir))) {
238       berrno be;
239       Mmsg2(err, "Cannot opendir to get parts list. Volume %s does not exist. ERR=%s",
240         VolumeName, be.bstrerror());
241       Dmsg1(dbglvl, "%s\n", err);
242       if (errno == ENOENT) {
243          ok=true;                  /* No volume, so no part */
244       }
245       goto get_out;
246    }
247 
248    name_max = pathconf(".", _PC_NAME_MAX);
249    if (name_max < 1024) {
250       name_max = 1024;
251    }
252 
253    entry = (struct dirent *)malloc(sizeof(struct dirent) + name_max + 1000);
254 
255    for ( ;; ) {
256       if (cancel_cb && cancel_cb->fct && cancel_cb->fct(cancel_cb->arg)) {
257          pm_strcpy(err, "Job canceled");
258          goto get_out;
259       }
260       errno = 0;
261       status = breaddir(dp, dname.addr());
262       if (status != 0) {
263          if (status > 0) {
264             Mmsg1(err, "breaddir failed: status=%d", status);
265             Dmsg1(dbglvl, "%s\n", err);
266          }
267          break;
268       }
269       /* Always ignore . and .. */
270       if (strcmp(".", dname.c_str()) == 0 || strcmp("..", dname.c_str()) == 0) {
271          continue;
272       }
273 
274       POOLMEM *part_path = get_pool_memory(PM_NAME);
275       pm_strcpy(part_path,vol_dir);
276       if (!IsPathSeparator(part_path[strlen(vol_dir)-1])) {
277          pm_strcat(part_path, "/");
278       }
279       pm_strcat(part_path, dname.c_str());
280 
281       /* Get size of part */
282       if (lstat(part_path, &statbuf) == -1) {
283          berrno be;
284          Mmsg(err, "Failed to stat file %s: %s", part_path, be.bstrerror());
285          Dmsg1(dbglvl, "%s\n", err);
286          free_pool_memory(part_path);
287          goto get_out;
288       }
289 
290       POOLMEM *clean_part_path = get_pool_memory(PM_NAME);
291       pm_strcpy(clean_part_path,VolumeName);
292       if (!IsPathSeparator(clean_part_path[strlen(VolumeName)-1])) {
293          pm_strcat(clean_part_path, "/");
294       }
295       pm_strcat(clean_part_path, dname.c_str());
296       /* Look only for part files */
297       if (!cb(clean_part_path, ctx))
298       {
299          free_pool_memory(clean_part_path);
300          free_pool_memory(part_path);
301          continue;
302       }
303 
304       free_pool_memory(clean_part_path);
305 
306       if (unlink(part_path) != 0 && errno != ENOENT) {
307          berrno be;
308          Mmsg3(err, "truncate_cloud_volume for %s: Unable to delete %s. ERR=%s\n", VolumeName, part_path, be.bstrerror());
309          free_pool_memory(part_path);
310          goto get_out;
311       } else {
312          Dmsg2(dbglvl, "clean_cloud_volume for %s: Unlink file %s.\n", VolumeName, part_path);
313       }
314 
315       free_pool_memory(part_path);
316    }
317    ok = true;
318 
319 get_out:
320    if (dp) {
321       closedir(dp);
322    }
323    if (entry) {
324       free(entry);
325    }
326 
327    free_pool_memory(vol_dir);
328 
329    return ok;
330 }
331 
make_cloud_filename(POOLMEM * & filename,const char * VolumeName,const char * file,uint32_t part)332 void file_driver::make_cloud_filename(POOLMEM *&filename,
333        const char *VolumeName, const char *file, uint32_t part)
334 {
335    Enter(dbglvl);
336    pm_strcpy(filename, hostName);
337    cloud_driver::add_vol_and_part(filename, VolumeName, file, part);
338    Dmsg1(dbglvl, "make_cloud_filename: %s\n", filename);
339 }
340 
make_cloud_filename(POOLMEM * & filename,const char * VolumeName,const char * file)341 void file_driver::make_cloud_filename(POOLMEM *&filename,
342        const char *VolumeName, const char *file)
343 {
344    Enter(dbglvl);
345    pm_strcpy(filename, hostName);
346    cloud_driver::add_vol_and_part(filename, VolumeName, file);
347    Dmsg1(dbglvl, "make_cloud_filename: %s\n", filename);
348 }
349 
350 /*
351  * Copy a single cache part to the cloud (local disk)
352  */
copy_cache_part_to_cloud(transfer * xfer)353 bool file_driver::copy_cache_part_to_cloud(transfer *xfer)
354 {
355    Enter(dbglvl);
356    POOLMEM *cloud_fname = get_pool_memory(PM_FNAME);
357    make_cloud_filename(cloud_fname, xfer->m_volume_name, "part", xfer->m_part);
358    Dmsg2(dbglvl, "Call put_object: %s, %s\n", xfer->m_cache_fname, cloud_fname);
359    bool rtn = put_object(xfer, xfer->m_cache_fname, cloud_fname, &upload_limit);
360    free_pool_memory(cloud_fname);
361    return rtn;
362 }
363 
move_cloud_part(const char * VolumeName,uint32_t apart,const char * to,cancel_callback * cancel_cb,POOLMEM * & err,int & exists)364 bool file_driver::move_cloud_part(const char *VolumeName, uint32_t apart , const char *to, cancel_callback *cancel_cb, POOLMEM *&err, int& exists)
365 {
366    Enter(dbglvl);
367    bool rtn = false;
368    POOLMEM *cloud_source_name = get_pool_memory(PM_FNAME);
369    POOLMEM *cloud_dest_name = get_pool_memory(PM_FNAME);
370    make_cloud_filename(cloud_source_name, VolumeName, "part", apart);
371    make_cloud_filename(cloud_dest_name, VolumeName, to);
372    struct stat statbuf;
373    /* Get size of part */
374    if (lstat(cloud_source_name, &statbuf) != 0) {
375       exists = 0;
376       rtn = true;
377    } else {
378       exists = 1;
379       transfer xfer(statbuf.st_size, NULL, cloud_source_name, VolumeName, apart, NULL, NULL, NULL);
380       rtn = put_object(&xfer, cloud_source_name, cloud_dest_name, &upload_limit);
381       Mmsg(err,"%s",rtn ? to:xfer.m_message);
382    }
383    free_pool_memory(cloud_dest_name);
384    free_pool_memory(cloud_source_name);
385    return rtn;
386 }
387 
388 /*
389  * Copy a single object (part) from the cloud to the cache
390  */
copy_cloud_part_to_cache(transfer * xfer)391 int file_driver::copy_cloud_part_to_cache(transfer *xfer)
392 {
393    Enter(dbglvl);
394 
395    POOL_MEM cloud_fname(PM_FNAME);
396    make_cloud_filename(cloud_fname.addr(), xfer->m_volume_name, "part", xfer->m_part);
397 
398    if (getenv("CLOUD_FILE_DRIVER_SIMULATE_DELAYED_TRANSFER") && xfer->m_debug_retry) {
399       restore_cloud_object(xfer, cloud_fname.c_str());
400       return CLOUD_DRIVER_COPY_PART_TO_CACHE_RETRY;
401    } else {
402       int ret = put_object(xfer, cloud_fname.c_str(), xfer->m_cache_fname, &download_limit);
403       if (ret) {
404          return CLOUD_DRIVER_COPY_PART_TO_CACHE_OK;
405       } else {
406          return CLOUD_DRIVER_COPY_PART_TO_CACHE_ERROR;
407       }
408    }
409    return CLOUD_DRIVER_COPY_PART_TO_CACHE_OK;
410 }
411 
restore_cloud_object(transfer * xfer,const char * cloud_fname)412 bool file_driver::restore_cloud_object(transfer *xfer, const char *cloud_fname)
413 {
414    wait_timeout = time(NULL) + atoi(getenv("CLOUD_FILE_DRIVER_SIMULATE_DELAYED_TRANSFER"));
415    /* retry only once for DELAYED_TRANSFER time */
416    xfer->m_debug_retry = false;
417    return true;
418 }
419 
is_waiting_on_server(transfer * xfer)420 bool file_driver::is_waiting_on_server(transfer *xfer)
421 {
422    (void) (xfer);
423    return (time(NULL)<wait_timeout);
424 }
425 /*
426  * NOTE: The SD Cloud resource has the following items
427 
428    RES   hdr;
429    char *host_name;
430    char *bucket_name;
431    char *access_key;
432    char *secret_key;
433    int32_t protocol;
434    int32_t uri_style;
435    uint32_t driver_type;
436    uint32_t trunc_opt;
437    uint32_t upload_opt;
438 */
439 
init(CLOUD * cloud,POOLMEM * & err)440 bool file_driver::init(CLOUD *cloud, POOLMEM *&err)
441 {
442    if (cloud->host_name == NULL) {
443       Mmsg1(err, "Failed to initialize File Cloud. ERR=Hostname not set in cloud resource %s\n", cloud->hdr.name);
444       return false;
445    }
446    /* File I/O buffer */
447    buf_len = DEFAULT_BLOCK_SIZE;
448 
449    hostName = cloud->host_name;
450    bucketName = cloud->bucket_name;
451    protocol = cloud->protocol;
452    uriStyle = cloud->uri_style;
453    accessKeyId = cloud->access_key;
454    secretAccessKey = cloud->secret_key;
455 
456    return true;
457 }
458 
start_of_job(POOLMEM * & msg)459 bool file_driver::start_of_job(POOLMEM *&msg)
460 {
461    Mmsg(msg, _("Using File cloud driver Host=%s Bucket=%s"), hostName, bucketName);
462    return true;
463 }
464 
end_of_job(POOLMEM * & msg)465 bool file_driver::end_of_job(POOLMEM *&msg)
466 {
467    return true;
468 }
469 
term(POOLMEM * & msg)470 bool file_driver::term(POOLMEM *&msg)
471 {
472    return true;
473 }
474 
get_cloud_volume_parts_list(const char * VolumeName,ilist * parts,cancel_callback * cancel_cb,POOLMEM * & err)475 bool file_driver::get_cloud_volume_parts_list(const char* VolumeName, ilist *parts, cancel_callback *cancel_cb, POOLMEM *&err)
476 {
477    Enter(dbglvl);
478 
479    if (parts == NULL || strlen(VolumeName) == 0) {
480       pm_strcpy(err, "Invalid argument");
481       return false;
482    }
483 
484    POOLMEM *vol_dir = get_pool_memory(PM_NAME);
485 
486    pm_strcpy(vol_dir, hostName);
487 
488    if (!IsPathSeparator(vol_dir[strlen(vol_dir)-1])) {
489       pm_strcat(vol_dir, "/");
490    }
491    pm_strcat(vol_dir, VolumeName);
492 
493    DIR* dp = NULL;
494    struct dirent *entry = NULL;
495    struct stat statbuf;
496    int name_max;
497    bool ok = false;
498    POOL_MEM dname(PM_FNAME);
499    int status = 0;
500 
501    Dmsg1(dbglvl, "Searching for parts in: %s\n", vol_dir);
502 
503    if (!(dp = opendir(vol_dir))) {
504       berrno be;
505       Mmsg2(err, "Cannot opendir to get parts list. Volume %s does not exist. ERR=%s",
506         VolumeName, be.bstrerror());
507       Dmsg1(dbglvl, "%s\n", err);
508       if (errno == ENOENT) {
509          ok=true;                  /* No volume, so no part */
510       }
511       goto get_out;
512    }
513 
514    name_max = pathconf(".", _PC_NAME_MAX);
515    if (name_max < 1024) {
516       name_max = 1024;
517    }
518 
519    entry = (struct dirent *)malloc(sizeof(struct dirent) + name_max + 1000);
520 
521    for ( ;; ) {
522       if (cancel_cb && cancel_cb->fct && cancel_cb->fct(cancel_cb->arg)) {
523          pm_strcpy(err, "Job canceled");
524          goto get_out;
525       }
526       errno = 0;
527       status = breaddir(dp, dname.addr());
528       if (status != 0) {
529          if (status > 0) {
530             Mmsg1(err, "breaddir failed: status=%d", status);
531             Dmsg1(dbglvl, "%s\n", err);
532          }
533          break;
534       }
535       /* Always ignore . and .. */
536       if (strcmp(".", dname.c_str()) == 0 || strcmp("..", dname.c_str()) == 0) {
537          continue;
538       }
539 
540       /* Look only for part files */
541       if (strncmp("part.", dname.c_str(), 5) != 0) {
542          continue;
543       }
544 
545       char *ext = strrchr (dname.c_str(), '.');
546       if (!ext || strlen(ext) < 2) {
547          continue;
548       }
549 
550       cloud_part *part = (cloud_part*) malloc(sizeof(cloud_part));
551 
552       /* save extension (part number) to cloud_part struct index*/
553       part->index = atoi(&ext[1]);
554 
555       POOLMEM *part_path = get_pool_memory(PM_NAME);
556       pm_strcpy(part_path,vol_dir);
557       if (!IsPathSeparator(part_path[strlen(vol_dir)-1])) {
558          pm_strcat(part_path, "/");
559       }
560       pm_strcat(part_path, dname.c_str());
561 
562       /* Get size of part */
563       if (lstat(part_path, &statbuf) == -1) {
564          berrno be;
565          Mmsg(err, "Failed to stat file %s: %s", part_path, be.bstrerror());
566          Dmsg1(dbglvl, "%s\n", err);
567          free_pool_memory(part_path);
568          free(part);
569          goto get_out;
570       }
571       free_pool_memory(part_path);
572 
573       part->size  = statbuf.st_size;
574       part->mtime = statbuf.st_mtime;
575       bmemzero(part->hash64, 64);
576 
577       parts->put(part->index, part);
578    }
579    ok = true;
580 
581 get_out:
582    if (dp) {
583       closedir(dp);
584    }
585    if (entry) {
586       free(entry);
587    }
588 
589    free_pool_memory(vol_dir);
590 
591    return ok;
592 }
593 
get_cloud_volumes_list(alist * volumes,cancel_callback * cancel_cb,POOLMEM * & err)594 bool file_driver::get_cloud_volumes_list(alist *volumes, cancel_callback *cancel_cb, POOLMEM *&err)
595 {
596    if (!volumes) {
597       pm_strcpy(err, "Invalid argument");
598       return false;
599    }
600 
601    Enter(dbglvl);
602 
603    DIR* dp = NULL;
604    struct dirent *entry = NULL;
605    struct stat statbuf;
606    int name_max;
607    bool ok = false;
608    POOLMEM *fullpath = get_pool_memory(PM_NAME);
609    POOL_MEM dname(PM_FNAME);
610    int status = 0;
611 
612    if (!(dp = opendir(hostName))) {
613       berrno be;
614       Mmsg2(err, "Cannot opendir to get volumes list. host_name %s does not exist. ERR=%s",
615         hostName, be.bstrerror());
616       Dmsg1(dbglvl, "%s\n", err);
617       if (errno == ENOENT) {
618          ok=true;                  /* No volume, so no part */
619       }
620       goto get_out;
621    }
622 
623    name_max = pathconf(".", _PC_NAME_MAX);
624    if (name_max < 1024) {
625       name_max = 1024;
626    }
627 
628    entry = (struct dirent *)malloc(sizeof(struct dirent) + name_max + 1000);
629 
630    for ( ;; ) {
631       if (cancel_cb && cancel_cb->fct && cancel_cb->fct(cancel_cb->arg)) {
632          goto get_out;
633       }
634       errno = 0;
635       status = breaddir(dp, dname.addr());
636       if (status != 0) {
637          if (status > 0) {
638             Mmsg1(err, "breaddir failed: status=%d", status);
639             Dmsg1(dbglvl, "%s\n", err);
640          }
641          break;
642       }
643       /* Always ignore . and .. */
644       if (strcmp(".", dname.c_str()) == 0 || strcmp("..", dname.c_str()) == 0) {
645          continue;
646       }
647 
648 
649       pm_strcpy(fullpath, hostName);
650       if (!IsPathSeparator(fullpath[strlen(fullpath)-1])) {
651          pm_strcat(fullpath, "/");
652       }
653       pm_strcat(fullpath, dname.c_str());
654 
655       if (lstat(fullpath, &statbuf) != 0) {
656          berrno be;
657          Dmsg2(dbglvl, "Failed to stat file %s: %s\n",
658             fullpath, be.bstrerror());
659          continue;
660       }
661 
662       if (S_ISDIR(statbuf.st_mode)) {
663          volumes->append(bstrdup(dname.c_str()));
664       }
665    }
666    ok = true;
667 
668 get_out:
669    if (dp) {
670       closedir(dp);
671    }
672    if (entry) {
673       free(entry);
674    }
675 
676    free_pool_memory(fullpath);
677 
678    return ok;
679 }
680