1 /*
2    BAREOS® - Backup Archiving REcovery Open Sourced
3 
4    Copyright (C) 2014-2014 Planets Communications B.V.
5    Copyright (C) 2014-2014 Bareos GmbH & Co. KG
6 
7    This program is Free Software; you can redistribute it and/or
8    modify it under the terms of version three of the GNU Affero General Public
9    License as published by the Free Software Foundation and included
10    in the file LICENSE.
11 
12    This program is distributed in the hope that it will be useful, but
13    WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15    General Public License for more details.
16 
17    You should have received a copy of the GNU Affero General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20    02110-1301, USA.
21 */
22 /*
23  * Marco van Wieringen, February 2014
24  */
25 /**
26  * @file
27  * Object Storage API device abstraction.
28  */
29 
30 #include "include/bareos.h"
31 
32 #ifdef HAVE_DROPLET
33 #include "stored/stored.h"
34 #include "object_store_device.h"
35 
36 namespace storagedaemon {
37 
38 /**
39  * Options that can be specified for this device type.
40  */
41 enum device_option_type
42 {
43   argument_none = 0,
44   argument_profile,
45   argument_bucket
46 };
47 
48 struct device_option {
49   const char* name;
50   enum device_option_type type;
51   int compare_size;
52 };
53 
54 static device_option device_options[] = {{"profile=", argument_profile, 8},
55                                          {"bucket=", argument_bucket, 7},
56                                          {NULL, argument_none}};
57 
58 static int droplet_reference_count = 0;
59 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
60 
61 /**
62  * Generic log function that glues libdroplet with BAREOS.
63  */
ObjectStoreLogfunc(dpl_ctx_t * ctx,dpl_log_level_t level,const char * message)64 static void ObjectStoreLogfunc(dpl_ctx_t* ctx,
65                                dpl_log_level_t level,
66                                const char* message)
67 {
68   switch (level) {
69     case DPL_DEBUG:
70       Dmsg1(100, "%s\n", message);
71       break;
72     case DPL_INFO:
73       Emsg1(M_INFO, 0, "%s\n", message);
74       break;
75     case DPL_WARNING:
76       Emsg1(M_WARNING, 0, "%s\n", message);
77       break;
78     case DPL_ERROR:
79       Emsg1(M_ERROR, 0, "%s\n", message);
80       break;
81   }
82 }
83 
84 /**
85  * Map the droplet errno's to system ones.
86  */
DropletErrnoToSystemErrno(dpl_status_t status)87 static inline int DropletErrnoToSystemErrno(dpl_status_t status)
88 {
89   switch (status) {
90     case DPL_ENOENT:
91       errno = ENOENT;
92       break;
93     case DPL_EIO:
94       errno = EIO;
95       break;
96     case DPL_ENAMETOOLONG:
97       errno = ENAMETOOLONG;
98       break;
99     case DPL_EEXIST:
100       errno = EEXIST;
101       break;
102     case DPL_EPERM:
103       errno = EPERM;
104       break;
105     default:
106       break;
107   }
108 
109   return -1;
110 }
111 
112 /**
113  * Open a volume using libdroplet.
114  */
d_open(const char * pathname,int flags,int mode)115 int object_store_device::d_open(const char* pathname, int flags, int mode)
116 {
117   dpl_status_t status;
118   dpl_vfile_flag_t dpl_flags;
119   dpl_option_t dpl_options;
120 
121 #if 1
122   Mmsg1(errmsg,
123         _("Object Storage devices are not yet supported, please disable %s\n"),
124         dev_name);
125   return -1;
126 #endif
127 
128   /*
129    * Initialize the droplet library when its not done previously.
130    */
131   P(mutex);
132   if (droplet_reference_count == 0) {
133     status = dpl_init();
134     if (status != DPL_SUCCESS) {
135       V(mutex);
136       return -1;
137     }
138 
139     dpl_set_log_func(ObjectStoreLogfunc);
140     droplet_reference_count++;
141   }
142   V(mutex);
143 
144   if (!object_configstring_) {
145     int len;
146     char *bp, *next_option;
147     bool done;
148 
149     if (!dev_options) {
150       Mmsg0(errmsg, _("No device options configured\n"));
151       Emsg0(M_FATAL, 0, errmsg);
152       return -1;
153     }
154 
155     object_configstring_ = strdup(dev_options);
156 
157     bp = object_configstring_;
158     while (bp) {
159       next_option = strchr(bp, ',');
160       if (next_option) { *next_option++ = '\0'; }
161 
162       done = false;
163       for (int i = 0; !done && device_options[i].name; i++) {
164         /*
165          * Try to find a matching device option.
166          */
167         if (bstrncasecmp(bp, device_options[i].name,
168                          device_options[i].compare_size)) {
169           switch (device_options[i].type) {
170             case argument_profile:
171               profile_ = bp + device_options[i].compare_size;
172               done = true;
173               break;
174             case argument_bucket:
175               object_bucketname_ = bp + device_options[i].compare_size;
176               done = true;
177               break;
178             default:
179               break;
180           }
181         }
182       }
183 
184       if (!done) {
185         Mmsg1(errmsg, _("Unable to parse device option: %s\n"), bp);
186         Emsg0(M_FATAL, 0, errmsg);
187         goto bail_out;
188       }
189 
190       bp = next_option;
191     }
192 
193     if (!profile_) {
194       Mmsg0(errmsg, _("No droplet profile configured\n"));
195       Emsg0(M_FATAL, 0, errmsg);
196       goto bail_out;
197     }
198 
199     /*
200      * Strip any .profile prefix from the libdroplet profile name.
201      */
202     len = strlen(profile_);
203     if (len > 8 && Bstrcasecmp(profile_ + (len - 8), ".profile")) {
204       profile_[len - 8] = '\0';
205     }
206   }
207 
208   /*
209    * See if we need to setup a new context for this device.
210    */
211   if (!ctx_) {
212     char* bp;
213 
214     /*
215      * See if this is a path.
216      */
217     bp = strrchr(object_configstring_, '/');
218     if (!bp) {
219       /*
220        * Only a profile name.
221        */
222       ctx_ = dpl_ctx_new(NULL, object_configstring_);
223     } else {
224       if (bp == object_configstring_) {
225         /*
226          * Profile in root of filesystem
227          */
228         ctx_ = dpl_ctx_new("/", bp + 1);
229       } else {
230         /*
231          * Profile somewhere else.
232          */
233         *bp++ = '\0';
234         ctx_ = dpl_ctx_new(object_configstring_, bp);
235       }
236     }
237 
238     /*
239      * If we failed to allocate a new context fail the open.
240      */
241     if (!ctx_) {
242       Mmsg1(errmsg, _("Failed to create a new context using config %s\n"),
243             dev_options);
244       return -1;
245     }
246 
247     /*
248      * Login if that is needed for this backend.
249      */
250     status = dpl_login(ctx_);
251     switch (status) {
252       case DPL_SUCCESS:
253         break;
254       case DPL_ENOTSUPP:
255         /*
256          * Backend doesn't support login which is fine.
257          */
258         break;
259       default:
260         Mmsg2(errmsg,
261               _("Failed to login for voume %s using dpl_login(): ERR=%s.\n"),
262               getVolCatName(), dpl_status_str(status));
263         return -1;
264     }
265 
266     /*
267      * If a bucketname was defined set it in the context.
268      */
269     if (object_bucketname_) { ctx_->cur_bucket = object_bucketname_; }
270   }
271 
272   /*
273    * See if we don't have a file open already.
274    */
275   if (vfd_) {
276     dpl_close(vfd_);
277     vfd_ = NULL;
278   }
279 
280   /*
281    * Create some options for libdroplet.
282    *
283    * DPL_OPTION_NOALLOC - we provide the buffer to copy the data into
284    *                      no need to let the library allocate memory we
285    *                      need to free after copying the data.
286    */
287   memset(&dpl_options, 0, sizeof(dpl_options));
288   dpl_options.mask |= DPL_OPTION_NOALLOC;
289 
290   if (flags & O_CREAT) {
291     dpl_flags = DPL_VFILE_FLAG_CREAT | DPL_VFILE_FLAG_RDWR;
292     status = dpl_open(ctx_,            /* context */
293                       getVolCatName(), /* locator */
294                       dpl_flags,       /* flags */
295                       &dpl_options,    /* options */
296                       NULL,            /* condition */
297                       NULL,            /* metadata */
298                       NULL,            /* sysmd */
299                       NULL,            /* query_params */
300                       NULL,            /* stream_status */
301                       &vfd_);
302   } else {
303     dpl_flags = DPL_VFILE_FLAG_RDWR;
304     status = dpl_open(ctx_,            /* context */
305                       getVolCatName(), /* locator */
306                       dpl_flags,       /* flags */
307                       &dpl_options,    /* options */
308                       NULL,            /* condition */
309                       NULL,            /* metadata */
310                       NULL,            /* sysmd */
311                       NULL,            /* query_params */
312                       NULL,            /* stream_status */
313                       &vfd_);
314   }
315 
316   switch (status) {
317     case DPL_SUCCESS:
318       offset_ = 0;
319       return 0;
320     default:
321       Mmsg2(errmsg, _("Failed to open %s using dpl_open(): ERR=%s.\n"),
322             getVolCatName(), dpl_status_str(status));
323       vfd_ = NULL;
324       return DropletErrnoToSystemErrno(status);
325   }
326 
327 bail_out:
328   return -1;
329 }
330 
331 /**
332  * Read data from a volume using libdroplet.
333  */
d_read(int fd,void * buffer,size_t count)334 ssize_t object_store_device::d_read(int fd, void* buffer, size_t count)
335 {
336   if (vfd_) {
337     unsigned int buflen;
338     dpl_status_t status;
339 
340     buflen = count;
341     status = dpl_pread(vfd_, count, offset_, (char**)&buffer, &buflen);
342 
343     switch (status) {
344       case DPL_SUCCESS:
345         offset_ += buflen;
346         return buflen;
347       default:
348         Mmsg2(errmsg, _("Failed to read %s using dpl_read(): ERR=%s.\n"),
349               getVolCatName(), dpl_status_str(status));
350         return DropletErrnoToSystemErrno(status);
351     }
352   } else {
353     errno = EBADF;
354     return -1;
355   }
356 }
357 
358 /**
359  * Write data to a volume using libdroplet.
360  */
d_write(int fd,const void * buffer,size_t count)361 ssize_t object_store_device::d_write(int fd, const void* buffer, size_t count)
362 {
363   if (vfd_) {
364     dpl_status_t status;
365 
366     status = dpl_pwrite(vfd_, (char*)buffer, count, offset_);
367     switch (status) {
368       case DPL_SUCCESS:
369         offset_ += count;
370         return count;
371       default:
372         Mmsg2(errmsg, _("Failed to write %s using dpl_write(): ERR=%s.\n"),
373               getVolCatName(), dpl_status_str(status));
374         return DropletErrnoToSystemErrno(status);
375     }
376   } else {
377     errno = EBADF;
378     return -1;
379   }
380 }
381 
d_close(int fd)382 int object_store_device::d_close(int fd)
383 {
384   if (vfd_) {
385     dpl_status_t status;
386 
387     status = dpl_close(vfd_);
388     switch (status) {
389       case DPL_SUCCESS:
390         vfd_ = NULL;
391         return 0;
392       default:
393         vfd_ = NULL;
394         return DropletErrnoToSystemErrno(status);
395     }
396   } else {
397     errno = EBADF;
398     return -1;
399   }
400 }
401 
d_ioctl(int fd,ioctl_req_t request,char * op)402 int object_store_device::d_ioctl(int fd, ioctl_req_t request, char* op)
403 {
404   return -1;
405 }
406 
407 /**
408  * Open a directory on the object store and find out size information for a
409  * file.
410  */
ObjectStoreGetFileSize(dpl_ctx_t * ctx,const char * filename)411 static inline size_t ObjectStoreGetFileSize(dpl_ctx_t* ctx,
412                                             const char* filename)
413 {
414   void* dir_hdl;
415   dpl_status_t status;
416   dpl_dirent_t dirent;
417   size_t filesize = -1;
418 
419   status = dpl_opendir(ctx, ".", &dir_hdl);
420   switch (status) {
421     case DPL_SUCCESS:
422       break;
423     default:
424       return -1;
425   }
426 
427   while (!dpl_eof(dir_hdl)) {
428     if (Bstrcasecmp(dirent.name, filename)) {
429       filesize = dirent.size;
430       break;
431     }
432   }
433 
434   dpl_closedir(dir_hdl);
435 
436   return filesize;
437 }
438 
d_lseek(DeviceControlRecord * dcr,boffset_t offset,int whence)439 boffset_t object_store_device::d_lseek(DeviceControlRecord* dcr,
440                                        boffset_t offset,
441                                        int whence)
442 {
443   switch (whence) {
444     case SEEK_SET:
445       offset_ = offset;
446       break;
447     case SEEK_CUR:
448       offset_ += offset;
449       break;
450     case SEEK_END: {
451       size_t filesize;
452 
453       filesize = ObjectStoreGetFileSize(ctx_, getVolCatName());
454       if (filesize >= 0) {
455         offset_ = filesize + offset;
456       } else {
457         return -1;
458       }
459       break;
460     }
461     default:
462       return -1;
463   }
464 
465   return offset_;
466 }
467 
d_truncate(DeviceControlRecord * dcr)468 bool object_store_device::d_truncate(DeviceControlRecord* dcr)
469 {
470   /*
471    * libdroplet doesn't have a truncate function so unlink the volume and create
472    * a new empty one.
473    */
474   if (vfd_) {
475     dpl_status_t status;
476     dpl_vfile_flag_t dpl_flags;
477     dpl_option_t dpl_options;
478 
479     status = dpl_close(vfd_);
480     switch (status) {
481       case DPL_SUCCESS:
482         vfd_ = NULL;
483         break;
484       default:
485         Mmsg2(errmsg, _("Failed to close %s using dpl_close(): ERR=%s.\n"),
486               getVolCatName(), dpl_status_str(status));
487         return false;
488     }
489 
490     status = dpl_unlink(ctx_, getVolCatName());
491     switch (status) {
492       case DPL_SUCCESS:
493         break;
494       default:
495         Mmsg2(errmsg, _("Failed to unlink %s using dpl_unlink(): ERR=%s.\n"),
496               getVolCatName(), dpl_status_str(status));
497         return false;
498     }
499 
500     /*
501      * Create some options for libdroplet.
502      *
503      * DPL_OPTION_NOALLOC - we provide the buffer to copy the data into
504      *                      no need to let the library allocate memory we
505      *                      need to free after copying the data.
506      */
507     memset(&dpl_options, 0, sizeof(dpl_options));
508     dpl_options.mask |= DPL_OPTION_NOALLOC;
509 
510     dpl_flags = DPL_VFILE_FLAG_CREAT | DPL_VFILE_FLAG_RDWR;
511     status = dpl_open(ctx_,            /* context */
512                       getVolCatName(), /* locator */
513                       dpl_flags,       /* flags */
514                       &dpl_options,    /* options */
515                       NULL,            /* condition */
516                       NULL,            /* metadata */
517                       NULL,            /* sysmd */
518                       NULL,            /* query_params */
519                       NULL,            /* stream_status */
520                       &vfd_);
521 
522     switch (status) {
523       case DPL_SUCCESS:
524         break;
525       default:
526         Mmsg2(errmsg, _("Failed to open %s using dpl_open(): ERR=%s.\n"),
527               getVolCatName(), dpl_status_str(status));
528         return false;
529     }
530   }
531 
532   return true;
533 }
534 
~object_store_device()535 object_store_device::~object_store_device()
536 {
537   if (ctx_) {
538     dpl_ctx_free(ctx_);
539     ctx_ = NULL;
540   }
541 
542   if (object_configstring_) { free(object_configstring_); }
543 
544   P(mutex);
545   droplet_reference_count--;
546   if (droplet_reference_count == 0) { dpl_free(); }
547   V(mutex);
548 }
549 
object_store_device()550 object_store_device::object_store_device()
551 {
552   object_configstring_ = NULL;
553   object_bucketname_ = NULL;
554   ctx_ = NULL;
555 }
556 
557 #ifdef HAVE_DYNAMIC_SD_BACKENDS
backend_instantiate(JobControlRecord * jcr,int device_type)558 extern "C" Device* backend_instantiate(JobControlRecord* jcr, int device_type)
559 {
560   Device* dev = NULL;
561 
562   switch (device_type) {
563     case B_OBJECT_STORE_DEV:
564       dev = new object_store_device;
565       break;
566     default:
567       Jmsg(jcr, M_FATAL, 0, _("Request for unknown devicetype: %d\n"),
568            device_type);
569       break;
570   }
571 
572   return dev;
573 }
574 
flush_backend(void)575 extern "C" void flush_backend(void) {}
576 #endif
577 } /* namespace storagedaemon */
578 #endif /* HAVE_DROPLET */
579