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