]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/stored/s3_driver.c
Tweak fix typo in comment
[bacula/bacula] / bacula / src / stored / s3_driver.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  * Routines for writing to the Cloud using S3 protocol.
21  *  NOTE!!! This cloud driver is not compatible with
22  *   any disk-changer script for changing Volumes.
23  *   It does however work with Bacula Virtual autochangers.
24  *
25  * Written by Kern Sibbald, May MMXVI
26  */
27
28 #include "s3_driver.h"
29
30 #ifdef HAVE_LIBS3
31
32 static const int dbglvl = 100;
33 static const char *S3Errors[] = {
34    "OK",
35    "InternalError",
36    "OutOfMemory",
37    "Interrupted",
38    "InvalidBucketNameTooLong",
39    "InvalidBucketNameFirstCharacter",
40    "InvalidBucketNameCharacter",
41    "InvalidBucketNameCharacterSequence",
42    "InvalidBucketNameTooShort",
43    "InvalidBucketNameDotQuadNotation",
44    "QueryParamsTooLong",
45    "FailedToInitializeRequest",
46    "MetaDataHeadersTooLong",
47    "BadMetaData",
48    "BadContentType",
49    "ContentTypeTooLong",
50    "BadMD5",
51    "MD5TooLong",
52    "BadCacheControl",
53    "CacheControlTooLong",
54    "BadContentDispositionFilename",
55    "ContentDispositionFilenameTooLong",
56    "BadContentEncoding",
57    "ContentEncodingTooLong",
58    "BadIfMatchETag",
59    "IfMatchETagTooLong",
60    "BadIfNotMatchETag",
61    "IfNotMatchETagTooLong",
62    "HeadersTooLong",
63    "KeyTooLong",
64    "UriTooLong",
65    "XmlParseFailure",
66    "EmailAddressTooLong",
67    "UserIdTooLong",
68    "UserDisplayNameTooLong",
69    "GroupUriTooLong",
70    "PermissionTooLong",
71    "TargetBucketTooLong",
72    "TargetPrefixTooLong",
73    "TooManyGrants",
74    "BadGrantee",
75    "BadPermission",
76    "XmlDocumentTooLarge",
77    "NameLookupError",
78    "FailedToConnect",
79    "ServerFailedVerification",
80    "ConnectionFailed",
81    "AbortedByCallback",
82    "AccessDenied",
83    "AccountProblem",
84    "AmbiguousGrantByEmailAddress",
85    "BadDigest",
86    "BucketAlreadyExists",
87    "BucketAlreadyOwnedByYou",
88    "BucketNotEmpty",
89    "CredentialsNotSupported",
90    "CrossLocationLoggingProhibited",
91    "EntityTooSmall",
92    "EntityTooLarge",
93    "ExpiredToken",
94    "IllegalVersioningConfigurationException",
95    "IncompleteBody",
96    "IncorrectNumberOfFilesInPostRequest",
97    "InlineDataTooLarge",
98    "InternalError",
99    "InvalidAccessKeyId",
100    "InvalidAddressingHeader",
101    "InvalidArgument",
102    "InvalidBucketName",
103    "InvalidBucketState",
104    "InvalidDigest",
105    "InvalidLocationConstraint",
106    "InvalidObjectState",
107    "InvalidPart",
108    "InvalidPartOrder",
109    "InvalidPayer",
110    "InvalidPolicyDocument",
111    "InvalidRange",
112    "InvalidRequest",
113    "InvalidSecurity",
114    "InvalidSOAPRequest",
115    "InvalidStorageClass",
116    "InvalidTargetBucketForLogging",
117    "InvalidToken",
118    "InvalidURI",
119    "KeyTooLong",
120    "MalformedACLError",
121    "MalformedPOSTRequest",
122    "MalformedXML",
123    "MaxMessageLengthExceeded",
124    "MaxPostPreDataLengthExceededError",
125    "MetadataTooLarge",
126    "MethodNotAllowed",
127    "MissingAttachment",
128    "MissingContentLength",
129    "MissingRequestBodyError",
130    "MissingSecurityElement",
131    "MissingSecurityHeader",
132    "NoLoggingStatusForKey",
133    "NoSuchBucket",
134    "NoSuchKey",
135    "NoSuchLifecycleConfiguration",
136    "NoSuchUpload",
137    "NoSuchVersion",
138    "NotImplemented",
139    "NotSignedUp",
140    "NotSuchBucketPolicy",
141    "OperationAborted",
142    "PermanentRedirect",
143    "PreconditionFailed",
144    "Redirect",
145    "RestoreAlreadyInProgress",
146    "RequestIsNotMultiPartContent",
147    "RequestTimeout",
148    "RequestTimeTooSkewed",
149    "RequestTorrentOfBucketError",
150    "SignatureDoesNotMatch",
151    "ServiceUnavailable",
152    "SlowDown",
153    "TemporaryRedirect",
154    "TokenRefreshRequired",
155    "TooManyBuckets",
156    "UnexpectedContent",
157    "UnresolvableGrantByEmailAddress",
158    "UserKeyMustBeSpecified",
159    "Unknown",
160    "HttpErrorMovedTemporarily",
161    "HttpErrorBadRequest",
162    "HttpErrorForbidden",
163    "HttpErrorNotFound",
164    "HttpErrorConflict",
165    "HttpErrorUnknown",
166    "Undefined"
167 };
168
169 #define S3ErrorsSize (sizeof(S3Errors)/sizeof(char *))
170
171 #include <fcntl.h>
172
173 /*
174  * Our Bacula context for s3_xxx callbacks
175  *   NOTE: only items needed for particular callback are set
176  */
177 class bacula_ctx {
178 public:
179    JCR *jcr;
180    transfer *xfer;
181    POOLMEM *&errMsg;
182    ilist *parts;
183    int isTruncated;
184    char* nextMarker;
185    int64_t obj_len;
186    const char *caller;
187    FILE *infile;
188    FILE *outfile;
189    alist *volumes;
190    S3Status status;
191    bwlimit *limit;        /* Used to control the bandwidth */
192    bacula_ctx(POOLMEM *&err) : jcr(NULL), xfer(NULL), errMsg(err), parts(NULL),
193                               isTruncated(0), nextMarker(NULL), obj_len(0), caller(NULL),
194                               infile(NULL), outfile(NULL), volumes(NULL), status(S3StatusOK), limit(NULL) 
195    {}
196    bacula_ctx(transfer *t) : jcr(NULL), xfer(t), errMsg(t->m_message), parts(NULL),
197                               isTruncated(0), nextMarker(NULL), obj_len(0), caller(NULL),
198                               infile(NULL), outfile(NULL), volumes(NULL), status(S3StatusOK), limit(NULL) 
199    {}   
200 };
201
202
203 /* Imported functions */
204 const char *mode_to_str(int mode);
205
206 /* Forward referenced functions */
207
208 /* Const and Static definitions */
209
210 static S3Status responsePropertiesCallback(
211    const S3ResponseProperties *properties,
212    void *callbackData);
213
214 static void responseCompleteCallback(
215    S3Status status,
216    const S3ErrorDetails *oops,
217    void *callbackData);
218
219
220 S3ResponseHandler responseHandler =
221 {
222    &responsePropertiesCallback,
223    &responseCompleteCallback
224 };
225
226
227
228
229 static S3Status responsePropertiesCallback(
230    const S3ResponseProperties *properties,
231    void *callbackData)
232 {
233    bacula_ctx *ctx = (bacula_ctx *)callbackData;
234    ASSERT(ctx);
235    if (ctx->xfer && properties) {
236       if (properties->contentLength > 0) {
237          ctx->xfer->m_res_size = properties->contentLength;
238       }
239       if (properties->lastModified > 0) {
240          ctx->xfer->m_res_mtime = properties->lastModified;
241       }
242    }
243    return S3StatusOK;
244 }
245
246 static void responseCompleteCallback(
247    S3Status status,
248    const S3ErrorDetails *oops,
249    void *callbackCtx)
250 {
251    bacula_ctx *ctx = (bacula_ctx *)callbackCtx;
252    const char *msg;
253
254    Enter(dbglvl);
255    if (ctx) {
256       ctx->status = status;      /* return completion status */
257    }
258    if (status < 0 || status > S3ErrorsSize) {
259       status = (S3Status)S3ErrorsSize;
260    }
261    msg = oops->message;
262    if (!msg) {
263       msg = S3Errors[status];
264    }
265     if ((status != S3StatusOK) && ctx->errMsg) {
266        if (oops->furtherDetails) {
267           Mmsg(ctx->errMsg, "%s ERR=%s\n"
268              "furtherDetails=%s\n", ctx->caller, msg, oops->furtherDetails);
269           Dmsg1(dbglvl, "%s", ctx->errMsg);
270        } else {
271           Mmsg(ctx->errMsg, "%s ERR=%s\n", ctx->caller, msg);
272           Dmsg1(dbglvl, "%s", ctx->errMsg);
273        }
274     }
275    return;
276 }
277
278
279
280
281 static int putObjectCallback(int buf_len, char *buf, void *callbackCtx)
282 {
283    bacula_ctx *ctx = (bacula_ctx *)callbackCtx;
284
285    ssize_t rbytes = 0;
286    int read_len;
287
288    if (ctx->xfer->is_cancelled()) {
289       Mmsg(ctx->errMsg, _("Job cancelled.\n"));
290       return -1;
291    }
292    if (ctx->obj_len) {
293       read_len = (ctx->obj_len > buf_len) ? buf_len : ctx->obj_len;
294       rbytes = fread(buf, 1, read_len, ctx->infile);
295       Dmsg5(dbglvl, "%s thread=%lu rbytes=%d bufsize=%u remlen=%lu\n",
296              ctx->caller,  pthread_self(), rbytes, buf_len, ctx->obj_len);
297       if (rbytes <= 0) {
298          berrno be;
299          Mmsg(ctx->errMsg, "%s Error reading input file: ERR=%s\n",
300             ctx->caller, be.bstrerror());
301          goto get_out;
302       }
303       ctx->obj_len -= rbytes;
304
305       if (ctx->limit) {
306          ctx->limit->control_bwlimit(rbytes);
307       }
308    }
309
310 get_out:
311    return rbytes;
312 }
313
314 S3PutObjectHandler putObjectHandler =
315 {
316    responseHandler,
317    &putObjectCallback
318 };
319
320
321 /*
322  * Put a cache object into the cloud
323  */
324 S3Status s3_driver::put_object(transfer *xfer, const char *cache_fname, const char *cloud_fname)
325 {
326    Enter(dbglvl);
327    bacula_ctx ctx(xfer);
328    ctx.limit = upload_limit.use_bwlimit() ? &upload_limit : NULL;
329    
330    struct stat statbuf;
331    if (lstat(cache_fname, &statbuf) == -1) {
332       berrno be;
333       Mmsg2(ctx.errMsg, "Failed to stat file %s. ERR=%s\n",
334          cache_fname, be.bstrerror());
335       goto get_out;
336    }
337
338    ctx.obj_len = statbuf.st_size;
339
340    if (!(ctx.infile = bfopen(cache_fname, "r"))) {
341       berrno be;
342       Mmsg2(ctx.errMsg, "Failed to open input file %s. ERR=%s\n",
343          cache_fname, be.bstrerror());
344       goto get_out;
345    }
346
347    ctx.caller = "S3_put_object";
348    S3_put_object(&s3ctx, cloud_fname, ctx.obj_len, NULL, NULL,
349                &putObjectHandler, &ctx);
350
351 get_out:
352    if (ctx.infile) {
353       fclose(ctx.infile);
354    }
355
356    /* no error so far -> retrieve uploaded part info */
357    if (ctx.errMsg[0] == 0) {
358       ilist parts;
359       get_cloud_volume_parts_list(xfer->m_dcr, cloud_fname, &parts, ctx.errMsg);
360       for (int i=1; i <= parts.last_index() ; i++) {
361          cloud_part *p = (cloud_part *)parts.get(i);
362          if (p) {
363             xfer->m_res_size = p->size;
364             xfer->m_res_mtime = p->mtime;
365             break; /* not need to go further */
366          }
367       }
368    }
369
370    return ctx.status;
371 }
372
373 static S3Status getObjectDataCallback(int buf_len, const char *buf,
374                    void *callbackCtx)
375 {
376    bacula_ctx *ctx = (bacula_ctx *)callbackCtx;
377    ssize_t wbytes;
378
379    Enter(dbglvl);
380    if (ctx->xfer->is_cancelled()) {
381        Mmsg(ctx->errMsg, _("Job cancelled.\n"));
382        return S3StatusAbortedByCallback;
383    }
384    /* Write buffer to output file */
385    wbytes = fwrite(buf, 1, buf_len, ctx->outfile);
386    if (wbytes < 0) {
387       berrno be;
388       Mmsg(ctx->errMsg, "%s Error writing output file: ERR=%s\n",
389          ctx->caller, be.bstrerror());
390       return S3StatusAbortedByCallback;
391    }
392
393    if (ctx->limit) {
394       ctx->limit->control_bwlimit(wbytes);
395    }
396    return ((wbytes < buf_len) ?
397             S3StatusAbortedByCallback : S3StatusOK);
398 }
399
400
401 bool s3_driver::get_cloud_object(transfer *xfer, const char *cloud_fname, const char *cache_fname)
402 {
403    int64_t ifModifiedSince = -1;
404    int64_t ifNotModifiedSince = -1;
405    const char *ifMatch = 0;
406    const char *ifNotMatch = 0;
407    uint64_t startByte = 0;
408    uint64_t byteCount = 0;
409    bacula_ctx ctx(xfer);
410    ctx.limit = download_limit.use_bwlimit() ? &download_limit : NULL;
411
412    Enter(dbglvl);
413    /* Initialize handlers */
414    S3GetConditions getConditions = {
415       ifModifiedSince,
416       ifNotModifiedSince,
417       ifMatch,
418       ifNotMatch
419    };
420    S3GetObjectHandler getObjectHandler = {
421      { &responsePropertiesCallback, &responseCompleteCallback },
422        &getObjectDataCallback
423    };
424
425
426    /* see if cache file already exists */
427    struct stat buf;
428    if (lstat(cache_fname, &buf) == -1) {
429        ctx.outfile = bfopen(cache_fname, "w");
430    } else {
431       /* Exists so truncate and write from beginning */
432       ctx.outfile = bfopen(cache_fname, "r+");
433    }
434
435    if (!ctx.outfile) {
436       berrno be;
437       Mmsg2(ctx.errMsg, "Could not open cache file %s. ERR=%s\n",
438               cache_fname, be.bstrerror());
439       goto get_out;
440    }
441
442
443    ctx.caller = "S3_get_object";
444    S3_get_object(&s3ctx, cloud_fname, &getConditions, startByte,
445                  byteCount, 0, &getObjectHandler, &ctx);
446
447    if (fclose(ctx.outfile) < 0) {
448       berrno be;
449       Mmsg2(ctx.errMsg, "Error closing cache file %s: %s\n",
450               cache_fname, be.bstrerror());
451    }
452
453 get_out:
454    return (ctx.errMsg[0] == 0);
455 }
456
457 /*
458  * Not thread safe
459  */
460 bool s3_driver::truncate_cloud_volume(DCR *dcr, const char *VolumeName, ilist *trunc_parts, POOLMEM *&err)
461 {
462    Enter(dbglvl);
463
464    bacula_ctx ctx(err);
465    ctx.jcr = dcr->jcr;
466
467    int last_index = (int)trunc_parts->last_index();
468    POOLMEM *cloud_fname = get_pool_memory(PM_FNAME);
469    for (int i=1; (i<=last_index); i++) {
470       if (!trunc_parts->get(i)) {
471          continue;
472       }
473       if (ctx.jcr->is_canceled()) {
474          Mmsg(err, _("Job cancelled.\n"));
475          goto get_out;
476       }
477       /* don't forget to specify the volume name is the object path */
478       make_cloud_filename(cloud_fname, VolumeName, i);
479       Dmsg1(dbglvl, "Object to truncate: %s\n", cloud_fname);
480       ctx.caller = "S3_delete_object";
481       S3_delete_object(&s3ctx, cloud_fname, 0, &responseHandler, &ctx);
482       if (ctx.status != S3StatusOK) {
483          /* error message should have been filled within response cb */
484          goto get_out;
485       }
486    }
487
488 get_out:
489    free_pool_memory(cloud_fname);
490    bfree_and_null(ctx.nextMarker);
491    return (err[0] == 0);
492 }
493
494 void s3_driver::make_cloud_filename(POOLMEM *&filename,
495         const char *VolumeName, uint32_t apart)
496 {
497    Enter(dbglvl);
498    filename[0] = 0;
499    dev->add_vol_and_part(filename, VolumeName, "part", apart);
500    Dmsg1(dbglvl, "make_cloud_filename: %s\n", filename);
501 }
502
503 bool s3_driver::retry_put_object(S3Status status)
504 {
505    return (
506       status == S3StatusFailedToConnect         ||
507       status == S3StatusConnectionFailed
508    );
509 }
510
511 /*
512  * Copy a single cache part to the cloud
513  */
514 bool s3_driver::copy_cache_part_to_cloud(transfer *xfer)
515 {
516    Enter(dbglvl);
517    POOLMEM *cloud_fname = get_pool_memory(PM_FNAME);
518    make_cloud_filename(cloud_fname, xfer->m_volume_name, xfer->m_part);   
519    uint32_t retry = max_upload_retries;
520    S3Status status = S3StatusOK;
521    do {
522       status = put_object(xfer, xfer->m_cache_fname, cloud_fname);
523       --retry;
524    } while (retry_put_object(status) && (retry>0));
525    free_pool_memory(cloud_fname);
526    return (status == S3StatusOK);
527 }
528
529 /*
530  * Copy a single object (part) from the cloud to the cache
531  */
532 bool s3_driver::copy_cloud_part_to_cache(transfer *xfer)
533 {
534    Enter(dbglvl);
535    POOLMEM *cloud_fname = get_pool_memory(PM_FNAME);
536    make_cloud_filename(cloud_fname, xfer->m_volume_name, xfer->m_part);
537    bool rtn = get_cloud_object(xfer, cloud_fname, xfer->m_cache_fname);
538    free_pool_memory(cloud_fname);
539    return rtn;
540 }
541
542 /*
543  * NOTE: See the SD Cloud resource in stored_conf.h
544 */
545
546 bool s3_driver::init(JCR *jcr, cloud_dev *adev, DEVRES *adevice)
547 {
548    S3Status status;
549
550    dev = adev;            /* copy cloud device pointer */
551    device = adevice;      /* copy device resource pointer */
552    cloud = device->cloud; /* local pointer to cloud definition */
553
554    /* Setup bucket context for S3 lib */
555    s3ctx.hostName = cloud->host_name;
556    s3ctx.bucketName = cloud->bucket_name;
557    s3ctx.protocol = (S3Protocol)cloud->protocol;
558    s3ctx.uriStyle = (S3UriStyle)cloud->uri_style;
559    s3ctx.accessKeyId = cloud->access_key;
560    s3ctx.secretAccessKey = cloud->secret_key;
561    s3ctx.authRegion = cloud->region;
562
563    /* File I/O buffer */
564    buf_len = dev->max_block_size;
565    if (buf_len == 0) {
566       buf_len = DEFAULT_BLOCK_SIZE;
567    }
568
569    if ((status = S3_initialize("s3", S3_INIT_ALL, s3ctx.hostName)) != S3StatusOK) {
570       Mmsg1(dev->errmsg, "Failed to initialize S3 lib. ERR=%s\n", S3_get_status_name(status));
571       Qmsg1(jcr, M_FATAL, 0, "%s", dev->errmsg);
572       Tmsg1(0, "%s", dev->errmsg);
573       return false;
574    }
575    return true;
576 }
577
578 bool s3_driver::start_of_job(DCR *dcr)
579 {
580    Jmsg(dcr->jcr, M_INFO, 0, _("Using S3 cloud driver Host=%s Bucket=%s\n"),
581       s3ctx.hostName, s3ctx.bucketName);
582    return true;
583 }
584
585 bool s3_driver::end_of_job(DCR *dcr)
586 {
587    return true;
588 }
589
590 /*
591  * Note, dcr may be NULL
592  */
593 bool s3_driver::term(DCR *dcr)
594 {
595    S3_deinitialize();
596    return true;
597 }
598
599
600
601 /*
602  * libs3 callback for get_cloud_volume_parts_list()
603  */
604 static S3Status partslistBucketCallback(
605    int isTruncated,
606    const char *nextMarker,
607    int numObj,
608    const S3ListBucketContent *object,
609    int commonPrefixesCount,
610    const char **commonPrefixes,
611    void *callbackCtx)
612 {
613    bacula_ctx *ctx = (bacula_ctx *)callbackCtx;
614
615    Enter(dbglvl);
616    for (int i = 0; ctx->parts && (i < numObj); i++) {
617       const S3ListBucketContent *obj = &(object[i]);
618       const char *ext=strstr(obj->key, "part.");
619       if (obj && ext!=NULL) {
620          cloud_part *part = (cloud_part*) malloc(sizeof(cloud_part));
621
622          part->index = atoi(&(ext[5]));
623          part->mtime = obj->lastModified;
624          part->size  = obj->size;
625          ctx->parts->put(part->index, part);
626       }
627    }
628
629    ctx->isTruncated = isTruncated;
630    if (ctx->nextMarker) {
631       bfree_and_null(ctx->nextMarker);
632    }
633    if (nextMarker) {
634       ctx->nextMarker = bstrdup(nextMarker);
635    }
636
637    Leave(dbglvl);
638    if (ctx->jcr->is_canceled()) {
639       Mmsg(ctx->errMsg, _("Job cancelled.\n"));
640       return S3StatusAbortedByCallback;
641    }
642    return S3StatusOK;
643 }
644
645 S3ListBucketHandler partslistBucketHandler =
646 {
647    responseHandler,
648    &partslistBucketCallback
649 };
650
651 bool s3_driver::get_cloud_volume_parts_list(DCR *dcr, const char* VolumeName, ilist *parts, POOLMEM *&err)
652 {
653    JCR *jcr = dcr->jcr;
654    Enter(dbglvl);
655
656    if (!parts || strlen(VolumeName) == 0) {
657       pm_strcpy(err, "Invalid argument");
658       return false;
659    }
660
661    bacula_ctx ctx(err);
662    ctx.jcr = jcr;
663    ctx.parts = parts;
664    ctx.isTruncated = 1; /* pass into the while loop at least once */
665    ctx.caller = "S3_list_bucket";
666    while (ctx.isTruncated!=0) {
667       ctx.isTruncated = 0;
668       S3_list_bucket(&s3ctx, VolumeName, ctx.nextMarker, NULL, 0, NULL,
669                      &partslistBucketHandler, &ctx);
670       if (ctx.status != S3StatusOK) {
671          pm_strcpy(err, S3Errors[ctx.status]);
672          bfree_and_null(ctx.nextMarker);
673          return false;
674       }
675    }
676    bfree_and_null(ctx.nextMarker);
677    return true;
678
679 }
680
681 /*
682  * libs3 callback for get_cloud_volumes_list()
683  */
684 static S3Status volumeslistBucketCallback(
685    int isTruncated,
686    const char *nextMarker,
687    int numObj,
688    const S3ListBucketContent *object,
689    int commonPrefixesCount,
690    const char **commonPrefixes,
691    void *callbackCtx)
692 {
693    bacula_ctx *ctx = (bacula_ctx *)callbackCtx;
694
695    Enter(dbglvl);
696    for (int i = 0; ctx->volumes && (i < commonPrefixesCount); i++) {
697       char *cp = bstrdup(commonPrefixes[i]);
698       cp[strlen(cp)-1] = 0;
699       ctx->volumes->append(cp);
700    }
701
702    ctx->isTruncated = isTruncated;
703    if (ctx->nextMarker) {
704       bfree_and_null(ctx->nextMarker);
705    }
706    if (nextMarker) {
707       ctx->nextMarker = bstrdup(nextMarker);
708    }
709
710    Leave(dbglvl);
711    if (ctx->jcr->is_canceled()) {
712       Mmsg(ctx->errMsg, _("Job cancelled.\n"));
713       return S3StatusAbortedByCallback;
714    }
715    return S3StatusOK;
716 }
717
718 S3ListBucketHandler volumeslistBucketHandler =
719 {
720    responseHandler,
721    &volumeslistBucketCallback
722 };
723
724 bool s3_driver::get_cloud_volumes_list(DCR *dcr, alist *volumes, POOLMEM *&err)
725 {
726    JCR *jcr = dcr->jcr;
727    Enter(dbglvl);
728
729    if (!volumes) {
730       pm_strcpy(err, "Invalid argument");
731       return false;
732    }
733
734    bacula_ctx ctx(err);
735    ctx.volumes = volumes;
736    ctx.jcr = jcr;
737    ctx.isTruncated = 1; /* pass into the while loop at least once */
738    ctx.caller = "S3_list_bucket";
739    while (ctx.isTruncated!=0) {
740       ctx.isTruncated = 0;
741       S3_list_bucket(&s3ctx, NULL, ctx.nextMarker, "/", 0, NULL,
742                      &volumeslistBucketHandler, &ctx);
743       if (ctx.status != S3StatusOK) {
744          break;
745       }
746    }
747    bfree_and_null(ctx.nextMarker);
748    return (err[0] == 0);
749 }
750
751 #ifdef really_needed
752 static S3Status listBucketCallback(
753    int isTruncated,
754    const char *nextMarker,
755    int contentsCount,
756    const S3ListBucketContent *contents,
757    int commonPrefixesCount,
758    const char **commonPrefixes,
759    void *callbackData);
760
761 S3ListBucketHandler listBucketHandler =
762 {
763    responseHandler,
764    &listBucketCallback
765 };
766
767
768 /*
769  * List content of a bucket
770  */
771 static S3Status listBucketCallback(
772    int isTruncated,
773    const char *nextMarker,
774    int numObj,
775    const S3ListBucketContent *contents,
776    int commonPrefixesCount,
777    const char **commonPrefixes,
778    void *callbackData)
779 {
780    bacula_ctx *ctx = (bacula_ctx *)callbackCtx;
781    if (print_hdr) {
782       Pmsg1(000, "\n%-22s", "      Object Name");
783       Pmsg2(000, "  %-5s  %-20s", "Size", "   Last Modified");
784       Pmsg0(000, "\n----------------------  -----  --------------------\n");
785       print_hdr = false;   /* print header once only */
786    }
787
788    for (int i = 0; i < numObj; i++) {
789       char timebuf[256];
790       char sizebuf[16];
791       const S3ListBucketContent *content = &(contents[i]);
792       time_t t = (time_t) content->lastModified;
793       strftime(timebuf, sizeof(timebuf), "%Y-%m-%dT%H:%M:%SZ", gmtime(&t));
794       sprintf(sizebuf, "%5llu", (unsigned long long) content->size);
795       Pmsg3(000, "%-22s  %s  %s\n", content->key, sizebuf, timebuf);
796    }
797    Pmsg0(000, "\n");
798    if (ctx->jcr->is_canceled()) {
799       Mmsg(ctx->errMsg, _("Job cancelled.\n"));
800       return S3StatusAbortedByCallback;
801    }
802    return S3StatusOK;
803 }
804 #endif
805
806 #endif /* HAVE_LIBS3 */