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  * Gluster Filesystem API device abstraction.
28  */
29 
30 #include "include/bareos.h"
31 
32 #ifdef HAVE_GFAPI
33 #include "stored/stored.h"
34 #include "stored/backends/gfapi_device.h"
35 #include "lib/edit.h"
36 #include "lib/berrno.h"
37 
38 namespace storagedaemon {
39 
40 /**
41  * Options that can be specified for this device type.
42  */
43 enum device_option_type
44 {
45   argument_none = 0,
46   argument_uri
47 };
48 
49 struct device_option {
50   const char* name;
51   enum device_option_type type;
52   int compare_size;
53 };
54 
55 static device_option device_options[] = {{"uri=", argument_uri, 4},
56                                          {NULL, argument_none}};
57 
58 /**
59  * Parse a gluster definition into something we can use for setting
60  * up the right connection to a gluster management server and get access
61  * to a gluster volume.
62  *
63  * Syntax:
64  *
65  * gluster[+transport]://[server[:port]]/volname[/dir][?socket=...]
66  *
67  * 'gluster' is the protocol.
68  *
69  * 'transport' specifies the transport type used to connect to gluster
70  * management daemon (glusterd). Valid transport types are tcp, unix
71  * and rdma. If a transport type isn't specified, then tcp type is assumed.
72  *
73  * 'server' specifies the server where the volume file specification for
74  * the given volume resides. This can be either hostname, ipv4 address
75  * or ipv6 address. ipv6 address needs to be within square brackets [ ].
76  * If transport type is 'unix', then 'server' field should not be specifed.
77  * The 'socket' field needs to be populated with the path to unix domain
78  * socket.
79  *
80  * 'port' is the port number on which glusterd is listening. This is optional
81  * and if not specified, QEMU will send 0 which will make gluster to use the
82  * default port. If the transport type is unix, then 'port' should not be
83  * specified.
84  *
85  * 'volname' is the name of the gluster volume which contains the backup images.
86  *
87  * 'dir' is an optional directory on the 'volname'
88  *
89  * Examples:
90  *
91  * gluster://1.2.3.4/testvol[/dir]
92  * gluster+tcp://1.2.3.4/testvol[/dir]
93  * gluster+tcp://1.2.3.4:24007/testvol[/dir]
94  * gluster+tcp://[1:2:3:4:5:6:7:8]/testvol[/dir]
95  * gluster+tcp://[1:2:3:4:5:6:7:8]:24007/testvol[/dir]
96  * gluster+tcp://server.domain.com:24007/testvol[/dir]
97  * gluster+unix:///testvol[/dir]?socket=/tmp/glusterd.socket
98  * gluster+rdma://1.2.3.4:24007/testvol[/dir]
99  */
parse_gfapi_devicename(char * devicename,char ** transport,char ** servername,char ** volumename,char ** dir,int * serverport)100 static inline bool parse_gfapi_devicename(char* devicename,
101                                           char** transport,
102                                           char** servername,
103                                           char** volumename,
104                                           char** dir,
105                                           int* serverport)
106 {
107   char* bp;
108 
109   /*
110    * Make sure its a URI that starts with gluster.
111    */
112   if (!bstrncasecmp(devicename, "gluster", 7)) { return false; }
113 
114   /*
115    * Parse any explicit protocol.
116    */
117   bp = strchr(devicename, '+');
118   if (bp) {
119     *transport = ++bp;
120     bp = strchr(bp, ':');
121     if (bp) {
122       *bp++ = '\0';
123     } else {
124       goto bail_out;
125     }
126   } else {
127     *transport = NULL;
128     bp = strchr(devicename, ':');
129     if (!bp) { goto bail_out; }
130   }
131 
132   /*
133    * When protocol is not UNIX parse servername and portnr.
134    */
135   if (!*transport || !Bstrcasecmp(*transport, "unix")) {
136     /*
137      * Parse servername of gluster management server.
138      */
139     bp = strchr(bp, '/');
140 
141     /*
142      * Validate URI.
143      */
144     if (!bp || !(*bp == '/')) { goto bail_out; }
145 
146     /*
147      * Skip the two //
148      */
149     *bp++ = '\0';
150     bp++;
151     *servername = bp;
152 
153     /*
154      * Parse any explicit server portnr.
155      * We search reverse in the string for a : what indicates
156      * a port specification but in that string there may not contain a ']'
157      * because then we searching in a IPv6 string.
158      */
159     bp = strrchr(bp, ':');
160     if (bp && !strchr(bp, ']')) {
161       char* port;
162 
163       *bp++ = '\0';
164       port = bp;
165       bp = strchr(bp, '/');
166       if (!bp) { goto bail_out; }
167       *bp++ = '\0';
168       *serverport = str_to_int64(port);
169       *volumename = bp;
170 
171       /*
172        * See if there is a dir specified.
173        */
174       bp = strchr(bp, '/');
175       if (bp) {
176         *bp++ = '\0';
177         *dir = bp;
178       }
179     } else {
180       *serverport = 0;
181       bp = *servername;
182 
183       /*
184        * Parse the volume name.
185        */
186       bp = strchr(bp, '/');
187       if (!bp) { goto bail_out; }
188       *bp++ = '\0';
189       *volumename = bp;
190 
191       /*
192        * See if there is a dir specified.
193        */
194       bp = strchr(bp, '/');
195       if (bp) {
196         *bp++ = '\0';
197         *dir = bp;
198       }
199     }
200   } else {
201     /*
202      * For UNIX serverport is zero.
203      */
204     *serverport = 0;
205 
206     /*
207      * Validate URI.
208      */
209     if (*bp != '/' || *(bp + 1) != '/') { goto bail_out; }
210 
211     /*
212      * Skip the two //
213      */
214     *bp++ = '\0';
215     bp++;
216 
217     /*
218      * For UNIX URIs the server part of the URI needs to be empty.
219      */
220     if (*bp++ != '/') { goto bail_out; }
221     *volumename = bp;
222 
223     /*
224      * See if there is a dir specified.
225      */
226     bp = strchr(bp, '/');
227     if (bp) {
228       *bp++ = '\0';
229       *dir = bp;
230     }
231 
232     /*
233      * Parse any socket parameters.
234      */
235     bp = strchr(bp, '?');
236     if (bp) {
237       if (bstrncasecmp(bp + 1, "socket=", 7)) {
238         *bp = '\0';
239         *servername = bp + 8;
240       }
241     }
242   }
243 
244   return true;
245 
246 bail_out:
247   return false;
248 }
249 
250 /**
251  * Create a parent directory using the gfapi.
252  */
GfapiMakedirs(glfs_t * glfs,const char * directory)253 static inline bool GfapiMakedirs(glfs_t* glfs, const char* directory)
254 {
255   int len;
256   char* bp;
257   struct stat st;
258   bool retval = false;
259   PoolMem new_directory(PM_FNAME);
260 
261   PmStrcpy(new_directory, directory);
262   len = strlen(new_directory.c_str());
263 
264   /*
265    * Strip any trailing slashes.
266    */
267   for (char* p = new_directory.c_str() + (len - 1);
268        (p >= new_directory.c_str()) && *p == '/'; p--) {
269     *p = '\0';
270   }
271 
272   if (strlen(new_directory.c_str()) &&
273       glfs_stat(glfs, new_directory.c_str(), &st) != 0) {
274     /*
275      * See if the parent exists.
276      */
277     switch (errno) {
278       case ENOENT:
279         bp = strrchr(new_directory.c_str(), '/');
280         if (bp) {
281           /*
282            * Make sure our parent exists.
283            */
284           *bp = '\0';
285           retval = GfapiMakedirs(glfs, new_directory.c_str());
286           if (!retval) { return false; }
287 
288           /*
289            * Create the directory.
290            */
291           if (glfs_mkdir(glfs, directory, 0750) == 0) { retval = true; }
292         }
293         break;
294       default:
295         break;
296     }
297   } else {
298     retval = true;
299   }
300 
301   return retval;
302 }
303 
304 /**
305  * Open a volume using gfapi.
306  */
d_open(const char * pathname,int flags,int mode)307 int gfapi_device::d_open(const char* pathname, int flags, int mode)
308 {
309   int status;
310 
311   /*
312    * Parse the gluster URI.
313    */
314   if (!gfapi_configstring_) {
315     char *bp, *next_option;
316     bool done;
317 
318     if (!dev_options) {
319       Mmsg0(errmsg, _("No device options configured\n"));
320       Emsg0(M_FATAL, 0, errmsg);
321       goto bail_out;
322     }
323 
324     gfapi_configstring_ = strdup(dev_options);
325 
326     bp = gfapi_configstring_;
327     while (bp) {
328       next_option = strchr(bp, ',');
329       if (next_option) { *next_option++ = '\0'; }
330 
331       done = false;
332       for (int i = 0; !done && device_options[i].name; i++) {
333         /*
334          * Try to find a matching device option.
335          */
336         if (bstrncasecmp(bp, device_options[i].name,
337                          device_options[i].compare_size)) {
338           switch (device_options[i].type) {
339             case argument_uri:
340               gfapi_uri_ = bp + device_options[i].compare_size;
341               done = true;
342               break;
343             default:
344               break;
345           }
346         }
347       }
348 
349       if (!done) {
350         Mmsg1(errmsg, _("Unable to parse device option: %s\n"), bp);
351         Emsg0(M_FATAL, 0, errmsg);
352         goto bail_out;
353       }
354 
355       bp = next_option;
356     }
357 
358     if (!gfapi_uri_) {
359       Mmsg0(errmsg, _("No GFAPI URI configured\n"));
360       Emsg0(M_FATAL, 0, errmsg);
361       goto bail_out;
362     }
363 
364     if (!parse_gfapi_devicename(gfapi_uri_, &transport_, &servername_,
365                                 &volumename_, &basedir_, &serverport_)) {
366       Mmsg1(errmsg, _("Unable to parse device URI %s.\n"), dev_options);
367       Emsg0(M_FATAL, 0, errmsg);
368       goto bail_out;
369     }
370   }
371 
372   /*
373    * See if we need to setup a Gluster context.
374    */
375   if (!glfs_) {
376     glfs_ = glfs_new(volumename_);
377     if (!glfs_) {
378       Mmsg1(errmsg,
379             _("Unable to create new Gluster context for volumename %s.\n"),
380             volumename_);
381       Emsg0(M_FATAL, 0, errmsg);
382       goto bail_out;
383     }
384 
385     status = glfs_set_volfile_server(glfs_, (transport_) ? transport_ : "tcp",
386                                      servername_, serverport_);
387     if (status < 0) {
388       Mmsg3(errmsg,
389             _("Unable to initialize Gluster management server for transport "
390               "%s, servername %s, serverport %d\n"),
391             (transport_) ? transport_ : "tcp", servername_, serverport_);
392       Emsg0(M_FATAL, 0, errmsg);
393       goto bail_out;
394     }
395 
396     status = glfs_init(glfs_);
397     if (status < 0) {
398       Mmsg1(errmsg, _("Unable to initialize Gluster for volumename %s.\n"),
399             volumename_);
400       Emsg0(M_FATAL, 0, errmsg);
401       goto bail_out;
402     }
403   }
404 
405   /*
406    * See if we don't have a file open already.
407    */
408   if (gfd_) {
409     glfs_close(gfd_);
410     gfd_ = NULL;
411   }
412 
413   /*
414    * See if we store in an explicit directory.
415    */
416   if (basedir_) {
417     struct stat st;
418 
419     /*
420      * Make sure the dir exists if one is defined.
421      */
422     Mmsg(virtual_filename_, "/%s", basedir_);
423     if (glfs_stat(glfs_, virtual_filename_, &st) != 0) {
424       switch (errno) {
425         case ENOENT:
426           if (!GfapiMakedirs(glfs_, virtual_filename_)) {
427             Mmsg1(errmsg,
428                   _("Specified glusterfs directory %s cannot be created.\n"),
429                   virtual_filename_);
430             Emsg0(M_FATAL, 0, errmsg);
431             goto bail_out;
432           }
433           break;
434         default:
435           goto bail_out;
436       }
437     } else {
438       if (!S_ISDIR(st.st_mode)) {
439         Mmsg1(errmsg,
440               _("Specified glusterfs directory %s is not a directory.\n"),
441               virtual_filename_);
442         Emsg0(M_FATAL, 0, errmsg);
443         goto bail_out;
444       }
445     }
446 
447     Mmsg(virtual_filename_, "/%s/%s", basedir_, getVolCatName());
448   } else {
449     Mmsg(virtual_filename_, "%s", getVolCatName());
450   }
451 
452   /*
453    * See if the O_CREAT flag is set as glfs_open doesn't support that flag and
454    * you have to call glfs_creat then.
455    */
456   if (flags & O_CREAT) {
457     gfd_ = glfs_creat(glfs_, virtual_filename_, flags, mode);
458   } else {
459     gfd_ = glfs_open(glfs_, virtual_filename_, flags);
460   }
461 
462   if (!gfd_) { goto bail_out; }
463 
464   return 0;
465 
466 bail_out:
467   /*
468    * Cleanup the Gluster context.
469    */
470   if (glfs_) {
471     glfs_fini(glfs_);
472     glfs_ = NULL;
473   }
474 
475   return -1;
476 }
477 
478 /**
479  * Read data from a volume using gfapi.
480  */
d_read(int fd,void * buffer,size_t count)481 ssize_t gfapi_device::d_read(int fd, void* buffer, size_t count)
482 {
483   if (gfd_) {
484     return glfs_read(gfd_, buffer, count, 0);
485   } else {
486     errno = EBADF;
487     return -1;
488   }
489 }
490 
491 /**
492  * Write data to a volume using gfapi.
493  */
d_write(int fd,const void * buffer,size_t count)494 ssize_t gfapi_device::d_write(int fd, const void* buffer, size_t count)
495 {
496   if (gfd_) {
497     return glfs_write(gfd_, buffer, count, 0);
498   } else {
499     errno = EBADF;
500     return -1;
501   }
502 }
503 
d_close(int fd)504 int gfapi_device::d_close(int fd)
505 {
506   if (gfd_) {
507     int status;
508 
509     status = glfs_close(gfd_);
510     gfd_ = NULL;
511     return status;
512   } else {
513     errno = EBADF;
514     return -1;
515   }
516 }
517 
d_ioctl(int fd,ioctl_req_t request,char * op)518 int gfapi_device::d_ioctl(int fd, ioctl_req_t request, char* op) { return -1; }
519 
d_lseek(DeviceControlRecord * dcr,boffset_t offset,int whence)520 boffset_t gfapi_device::d_lseek(DeviceControlRecord* dcr,
521                                 boffset_t offset,
522                                 int whence)
523 {
524   if (gfd_) {
525     return glfs_lseek(gfd_, offset, whence);
526   } else {
527     errno = EBADF;
528     return -1;
529   }
530 }
531 
d_truncate(DeviceControlRecord * dcr)532 bool gfapi_device::d_truncate(DeviceControlRecord* dcr)
533 {
534   struct stat st;
535 
536   if (gfd_) {
537     if (glfs_ftruncate(gfd_, 0) != 0) {
538       BErrNo be;
539 
540       Mmsg2(errmsg, _("Unable to truncate device %s. ERR=%s\n"), prt_name,
541             be.bstrerror());
542       Emsg0(M_FATAL, 0, errmsg);
543       return false;
544     }
545 
546     /*
547      * Check for a successful glfs_truncate() and issue work-around when
548      * truncation doesn't work.
549      *
550      * 1. close file
551      * 2. delete file
552      * 3. open new file with same mode
553      * 4. change ownership to original
554      */
555     if (glfs_fstat(gfd_, &st) != 0) {
556       BErrNo be;
557 
558       Mmsg2(errmsg, _("Unable to stat device %s. ERR=%s\n"), prt_name,
559             be.bstrerror());
560       Dmsg1(100, "%s", errmsg);
561       return false;
562     }
563 
564     if (st.st_size != 0) { /* glfs_truncate() didn't work */
565       glfs_close(gfd_);
566       glfs_unlink(glfs_, virtual_filename_);
567 
568       /*
569        * Recreate the file -- of course, empty
570        */
571       oflags = O_CREAT | O_RDWR | O_BINARY;
572       gfd_ = glfs_creat(glfs_, virtual_filename_, oflags, st.st_mode);
573       if (!gfd_) {
574         BErrNo be;
575 
576         dev_errno = errno;
577         Mmsg2(errmsg, _("Could not reopen: %s, ERR=%s\n"), virtual_filename_,
578               be.bstrerror());
579         Emsg0(M_FATAL, 0, errmsg);
580 
581         return false;
582       }
583 
584       /*
585        * Reset proper owner
586        */
587       glfs_chown(glfs_, virtual_filename_, st.st_uid, st.st_gid);
588     }
589   }
590 
591   return true;
592 }
593 
~gfapi_device()594 gfapi_device::~gfapi_device()
595 {
596   if (gfd_) {
597     glfs_close(gfd_);
598     gfd_ = NULL;
599   }
600 
601   if (!glfs_) {
602     glfs_fini(glfs_);
603     glfs_ = NULL;
604   }
605 
606   if (gfapi_configstring_) {
607     free(gfapi_configstring_);
608     gfapi_configstring_ = NULL;
609   }
610 
611   FreePoolMemory(virtual_filename_);
612 }
613 
gfapi_device()614 gfapi_device::gfapi_device()
615 {
616   gfapi_configstring_ = NULL;
617   transport_ = NULL;
618   servername_ = NULL;
619   volumename_ = NULL;
620   basedir_ = NULL;
621   serverport_ = 0;
622   glfs_ = NULL;
623   gfd_ = NULL;
624   virtual_filename_ = GetPoolMemory(PM_FNAME);
625 }
626 
627 #ifdef HAVE_DYNAMIC_SD_BACKENDS
backend_instantiate(JobControlRecord * jcr,int device_type)628 extern "C" Device* backend_instantiate(JobControlRecord* jcr, int device_type)
629 {
630   Device* dev = NULL;
631 
632   switch (device_type) {
633     case B_GFAPI_DEV:
634       dev = new gfapi_device;
635       break;
636     default:
637       Jmsg(jcr, M_FATAL, 0, _("Request for unknown devicetype: %d\n"),
638            device_type);
639       break;
640   }
641 
642   return dev;
643 }
644 
flush_backend(void)645 extern "C" void flush_backend(void) {}
646 #endif
647 } /* namespace storagedaemon */
648 #endif /* HAVE_GFAPI */
649