1 /*
2 BAREOS® - Backup Archiving REcovery Open Sourced
3
4 Copyright (C) 2014-2017 Planets Communications B.V.
5 Copyright (C) 2014-2021 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, which is
10 listed 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 Affero 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 * @file
24 * GlusterFS GFAPI plugin for the Bareos File Daemon
25 */
26 #include "include/bareos.h"
27 #include "filed/fd_plugins.h"
28 #include "fd_common.h"
29 #include "include/fileopts.h"
30 #include "lib/alist.h"
31 #include "lib/path_list.h"
32 #include "lib/berrno.h"
33 #include "lib/edit.h"
34
35 #include <glusterfs/api/glfs.h>
36
37
38 /* avoid missing config.h problem on Debian 8 and Ubuntu 16:
39 compat-errno.h includes not existing config.h when
40 _CONFIG_H is not defined
41 */
42 #ifndef _CONFIG_H
43 #define _CONFIG_H
44 #include <glusterfs/compat-errno.h>
45 #undef _CONFIG_H
46 #else
47 #include <glusterfs/compat-errno.h>
48 #endif
49
50 namespace filedaemon {
51
52 static const int debuglevel = 150;
53
54 #define PLUGIN_LICENSE "Bareos AGPLv3"
55 #define PLUGIN_AUTHOR "Marco van Wieringen"
56 #define PLUGIN_DATE "February 2016"
57 #define PLUGIN_VERSION "2"
58 #define PLUGIN_DESCRIPTION "Bareos GlusterFS GFAPI File Daemon Plugin"
59 #define PLUGIN_USAGE \
60 "gfapi:volume=gluster[+transport]\\://[server[:port]]/volname[/" \
61 "dir][?socket=...]:snapdir=<snapdir>:gffilelist=<file>"
62
63 #define GLFS_PATH_MAX 4096
64
65 /**
66 * Forward referenced functions
67 */
68 static bRC newPlugin(bpContext* ctx);
69 static bRC freePlugin(bpContext* ctx);
70 static bRC getPluginValue(bpContext* ctx, pVariable var, void* value);
71 static bRC setPluginValue(bpContext* ctx, pVariable var, void* value);
72 static bRC handlePluginEvent(bpContext* ctx, bEvent* event, void* value);
73 static bRC startBackupFile(bpContext* ctx, struct save_pkt* sp);
74 static bRC endBackupFile(bpContext* ctx);
75 static bRC pluginIO(bpContext* ctx, struct io_pkt* io);
76 static bRC startRestoreFile(bpContext* ctx, const char* cmd);
77 static bRC endRestoreFile(bpContext* ctx);
78 static bRC createFile(bpContext* ctx, struct restore_pkt* rp);
79 static bRC setFileAttributes(bpContext* ctx, struct restore_pkt* rp);
80 static bRC checkFile(bpContext* ctx, char* fname);
81 static bRC getAcl(bpContext* ctx, acl_pkt* ap);
82 static bRC setAcl(bpContext* ctx, acl_pkt* ap);
83 static bRC getXattr(bpContext* ctx, xattr_pkt* xp);
84 static bRC setXattr(bpContext* ctx, xattr_pkt* xp);
85
86 static bRC parse_plugin_definition(bpContext* ctx, void* value);
87 static bRC end_restore_job(bpContext* ctx, void* value);
88 static bRC setup_backup(bpContext* ctx, void* value);
89 static bRC setup_restore(bpContext* ctx, void* value);
90
91 /**
92 * Pointers to Bareos functions
93 */
94 static bFuncs* bfuncs = NULL;
95 static bInfo* binfo = NULL;
96
97 /**
98 * Plugin Information block
99 */
100 static genpInfo pluginInfo = {sizeof(pluginInfo), FD_PLUGIN_INTERFACE_VERSION,
101 FD_PLUGIN_MAGIC, PLUGIN_LICENSE,
102 PLUGIN_AUTHOR, PLUGIN_DATE,
103 PLUGIN_VERSION, PLUGIN_DESCRIPTION,
104 PLUGIN_USAGE};
105
106 /**
107 * Plugin entry points for Bareos
108 */
109 static pFuncs pluginFuncs = {
110 sizeof(pluginFuncs), FD_PLUGIN_INTERFACE_VERSION,
111
112 /* Entry points into plugin */
113 newPlugin, /* new plugin instance */
114 freePlugin, /* free plugin instance */
115 getPluginValue, setPluginValue, handlePluginEvent, startBackupFile,
116 endBackupFile, startRestoreFile, endRestoreFile, pluginIO, createFile,
117 setFileAttributes, checkFile, getAcl, setAcl, getXattr, setXattr};
118
119 /**
120 * Plugin private context
121 */
122 struct plugin_ctx {
123 int32_t backup_level; /* Backup level e.g. Full/Differential/Incremental */
124 utime_t since; /* Since time for Differential/Incremental */
125 char* plugin_options; /* Options passed to plugin */
126 char* plugin_definition; /* Previous plugin definition passed to plugin */
127 char* gfapi_volume_spec; /* Unparsed Gluster volume specification */
128 char* transport; /* Gluster transport protocol to management server */
129 char* servername; /* Gluster management server */
130 char* volumename; /* Gluster volume */
131 char* basedir; /* Basedir to start backup in */
132 char* snapdir; /* Specific snapdir to use while doing backup */
133 int serverport; /* Gluster management server portnr */
134 char flags[FOPTS_BYTES]; /* Bareos internal flags */
135 int32_t type; /* FT_xx for this file */
136 struct stat statp; /* Stat struct for next file to save */
137 bool processing_xattr; /* Set when we are processing a xattr list */
138 char* next_xattr_name; /* Next xattr name to process */
139 bool crawl_fs; /* Use local fs crawler to find files to backup */
140 char* gf_file_list; /* File with list of files generated by glusterfind to
141 backup */
142 bool is_accurate; /* Backup has accurate option enabled */
143 POOLMEM* cwd; /* Current Working Directory */
144 POOLMEM* next_filename; /* Next filename to save */
145 POOLMEM* link_target; /* Target symlink points to */
146 POOLMEM* xattr_list; /* List of xattrs */
147 #ifndef HAVE_GLFS_READDIRPLUS
148 POOLMEM* dirent_buffer; /* Temporary buffer for current dirent structure */
149 #endif
150 alist* dir_stack; /* Stack of directories when recursing */
151 htable* path_list; /* Hash table with directories created on restore. */
152 glfs_t* glfs; /* Gluster volume handle */
153 glfs_fd_t* gdir; /* Gluster directory handle */
154 glfs_fd_t* gfd; /* Gluster file handle */
155 FILE* file_list_handle; /* File handle to file with files to backup */
156 };
157
158 /**
159 * This defines the arguments that the plugin parser understands.
160 */
161 enum plugin_argument_type
162 {
163 argument_none,
164 argument_volume_spec,
165 argument_snapdir,
166 argument_gf_file_list
167 };
168
169 struct plugin_argument {
170 const char* name;
171 enum plugin_argument_type type;
172 };
173
174 static plugin_argument plugin_arguments[] = {
175 {"volume", argument_volume_spec},
176 {"snapdir", argument_snapdir},
177 {"gffilelist", argument_gf_file_list},
178 {NULL, argument_none}};
179
180 enum gluster_find_type
181 {
182 gf_type_none,
183 gf_type_new,
184 gf_type_modify,
185 gf_type_rename,
186 gf_type_delete
187 };
188
189 /**
190 * If we recurse into a subdir we push the current directory onto
191 * a stack so we can pop it after we have processed the subdir.
192 */
193 struct dir_stack_entry {
194 struct stat statp; /* Stat struct of directory */
195 glfs_fd_t* gdir; /* Gluster directory handle */
196 };
197
198 struct gluster_find_mapping {
199 const char* name;
200 enum gluster_find_type type;
201 int compare_size;
202 };
203
204 static gluster_find_mapping gluster_find_mappings[] = {
205 {"NEW ", gf_type_new, 4},
206 {"MODIFY ", gf_type_modify, 7},
207 {"RENAME ", gf_type_rename, 7},
208 {"DELETE ", gf_type_delete, 7},
209 {NULL, gf_type_none, 0}};
210
find_glustermap_eventtype(const char * gf_entry)211 static inline struct gluster_find_mapping* find_glustermap_eventtype(
212 const char* gf_entry)
213 {
214 struct gluster_find_mapping* gf_mapping;
215
216 gf_mapping = NULL;
217 for (int i = 0; gluster_find_mappings[i].name; i++) {
218 if (bstrncasecmp(gf_entry, gluster_find_mappings[i].name,
219 gluster_find_mappings[i].compare_size)) {
220 gf_mapping = &gluster_find_mappings[i];
221 break;
222 }
223 }
224
225 return gf_mapping;
226 }
227
to_hex(char ch)228 static inline int to_hex(char ch)
229 {
230 int retval;
231
232 if (B_ISDIGIT(ch)) {
233 retval = (ch - '0');
234 } else if (ch >= 'a' && ch <= 'f') {
235 retval = (ch - 'a') + 10;
236 } else if (ch >= 'A' && ch <= 'F') {
237 retval = (ch - 'A') + 10;
238 } else {
239 retval = -1;
240 }
241
242 return retval;
243 }
244
245 /**
246 * Quick and dirty version of RFC 3986 percent-decoding
247 * It decodes the entries returned by glusterfind which uses
248 * the python urllib.quote_plus method.
249 */
UrllibUnquotePlus(char * str)250 static bool UrllibUnquotePlus(char* str)
251 {
252 char *p, *q;
253 bool retval = true;
254
255 /*
256 * Set both pointers to the beginning of the string.
257 * q is the converted cursor and p is the walking cursor.
258 */
259 q = str;
260 p = str;
261
262 while (*p) {
263 switch (*p) {
264 case '%': {
265 int ch, hex;
266
267 /*
268 * See if the % is followed by at least two chars.
269 */
270 hex = to_hex(*(p + 1));
271 if (hex == -1) {
272 retval = false;
273 goto bail_out;
274 }
275 ch = hex * 16;
276 hex = to_hex(*(p + 2));
277 if (hex == -1) {
278 retval = false;
279 goto bail_out;
280 }
281 ch += hex;
282 *q++ = ch;
283 p += 2;
284 break;
285 }
286 case '+':
287 *q++ = ' ';
288 break;
289 default:
290 *q++ = *p;
291 break;
292 }
293 p++;
294 }
295
296 /*
297 * Terminate translated string.
298 */
299 *q = '\0';
300
301 bail_out:
302 return retval;
303 }
304
305 #ifdef __cplusplus
306 extern "C" {
307 #endif
308
309 /**
310 * loadPlugin() and unloadPlugin() are entry points that are exported, so Bareos
311 * can directly call these two entry points they are common to all Bareos
312 * plugins.
313 *
314 * External entry point called by Bareos to "load" the plugin
315 */
loadPlugin(bInfo * lbinfo,bFuncs * lbfuncs,genpInfo ** pinfo,pFuncs ** pfuncs)316 bRC loadPlugin(bInfo* lbinfo,
317 bFuncs* lbfuncs,
318 genpInfo** pinfo,
319 pFuncs** pfuncs)
320 {
321 bfuncs = lbfuncs; /* set Bareos funct pointers */
322 binfo = lbinfo;
323 *pinfo = &pluginInfo; /* return pointer to our info */
324 *pfuncs = &pluginFuncs; /* return pointer to our functions */
325
326 return bRC_OK;
327 }
328
329 /**
330 * External entry point to unload the plugin
331 */
unloadPlugin()332 bRC unloadPlugin() { return bRC_OK; }
333
334 #ifdef __cplusplus
335 }
336 #endif
337
338 /**
339 * The following entry points are accessed through the function pointers we
340 * supplied to Bareos. Each plugin type (dir, fd, sd) has its own set of entry
341 * points that the plugin must define.
342 *
343 * Create a new instance of the plugin i.e. allocate our private storage
344 */
newPlugin(bpContext * ctx)345 static bRC newPlugin(bpContext* ctx)
346 {
347 plugin_ctx* p_ctx;
348
349 p_ctx = (plugin_ctx*)malloc(sizeof(plugin_ctx));
350 if (!p_ctx) { return bRC_Error; }
351 memset(p_ctx, 0, sizeof(plugin_ctx));
352 ctx->pContext = (void*)p_ctx; /* set our context pointer */
353
354 /*
355 * Allocate some internal memory for:
356 * - The file we are processing
357 * - The link target of a symbolic link.
358 * - The list of xattrs.
359 */
360 p_ctx->next_filename = GetPoolMemory(PM_FNAME);
361 p_ctx->link_target = GetPoolMemory(PM_FNAME);
362 p_ctx->xattr_list = GetPoolMemory(PM_MESSAGE);
363
364 /*
365 * Resize all buffers for PATH like names to GLFS_PATH_MAX.
366 */
367 p_ctx->next_filename =
368 CheckPoolMemorySize(p_ctx->next_filename, GLFS_PATH_MAX);
369 p_ctx->link_target = CheckPoolMemorySize(p_ctx->link_target, GLFS_PATH_MAX);
370
371 /*
372 * Only register the events we are really interested in.
373 */
374 bfuncs->registerBareosEvents(ctx, 7, bEventLevel, bEventSince,
375 bEventRestoreCommand, bEventBackupCommand,
376 bEventPluginCommand, bEventEndRestoreJob,
377 bEventNewPluginOptions);
378
379 return bRC_OK;
380 }
381
382 /**
383 * Free a plugin instance, i.e. release our private storage
384 */
freePlugin(bpContext * ctx)385 static bRC freePlugin(bpContext* ctx)
386 {
387 plugin_ctx* p_ctx = (plugin_ctx*)ctx->pContext;
388 if (!p_ctx) { return bRC_Error; }
389
390 Dmsg(ctx, debuglevel, "gfapi-fd: entering freePlugin\n");
391
392 if (p_ctx->file_list_handle) { fclose(p_ctx->file_list_handle); }
393
394 if (p_ctx->path_list) {
395 FreePathList(p_ctx->path_list);
396 p_ctx->path_list = NULL;
397 }
398
399 if (p_ctx->dir_stack) {
400 p_ctx->dir_stack->destroy();
401 delete p_ctx->dir_stack;
402 }
403
404 if (p_ctx->glfs) {
405 glfs_fini(p_ctx->glfs);
406 p_ctx->glfs = NULL;
407 }
408
409 #ifndef HAVE_GLFS_READDIRPLUS
410 if (p_ctx->dirent_buffer) { FreePoolMemory(p_ctx->dirent_buffer); }
411 #endif
412
413 if (p_ctx->cwd) { FreePoolMemory(p_ctx->cwd); }
414
415 FreePoolMemory(p_ctx->xattr_list);
416 FreePoolMemory(p_ctx->link_target);
417 FreePoolMemory(p_ctx->next_filename);
418
419 if (p_ctx->snapdir) { free(p_ctx->snapdir); }
420
421 if (p_ctx->gfapi_volume_spec) { free(p_ctx->gfapi_volume_spec); }
422
423 if (p_ctx->plugin_definition) { free(p_ctx->plugin_definition); }
424
425 if (p_ctx->plugin_options) { free(p_ctx->plugin_options); }
426
427 free(p_ctx);
428 p_ctx = NULL;
429
430 Dmsg(ctx, debuglevel, "gfapi-fd: leaving freePlugin\n");
431
432 return bRC_OK;
433 }
434
435 /**
436 * Return some plugin value (none defined)
437 */
getPluginValue(bpContext * ctx,pVariable var,void * value)438 static bRC getPluginValue(bpContext* ctx, pVariable var, void* value)
439 {
440 return bRC_OK;
441 }
442
443 /**
444 * Set a plugin value (none defined)
445 */
setPluginValue(bpContext * ctx,pVariable var,void * value)446 static bRC setPluginValue(bpContext* ctx, pVariable var, void* value)
447 {
448 return bRC_OK;
449 }
450
451 /**
452 * Handle an event that was generated in Bareos
453 */
handlePluginEvent(bpContext * ctx,bEvent * event,void * value)454 static bRC handlePluginEvent(bpContext* ctx, bEvent* event, void* value)
455 {
456 bRC retval;
457 plugin_ctx* p_ctx = (plugin_ctx*)ctx->pContext;
458
459 if (!p_ctx) { return bRC_Error; }
460
461 switch (event->eventType) {
462 case bEventLevel:
463 p_ctx->backup_level = (int64_t)value;
464 retval = bRC_OK;
465 break;
466 case bEventSince:
467 p_ctx->since = (int64_t)value;
468 retval = bRC_OK;
469 break;
470 case bEventRestoreCommand:
471 retval = parse_plugin_definition(ctx, value);
472 if (retval == bRC_OK) { retval = setup_restore(ctx, value); }
473 break;
474 case bEventBackupCommand:
475 retval = parse_plugin_definition(ctx, value);
476 if (retval == bRC_OK) { retval = setup_backup(ctx, value); }
477 break;
478 case bEventPluginCommand:
479 retval = parse_plugin_definition(ctx, value);
480 break;
481 case bEventNewPluginOptions:
482 /*
483 * Free any previous value.
484 */
485 if (p_ctx->plugin_options) {
486 free(p_ctx->plugin_options);
487 p_ctx->plugin_options = NULL;
488 }
489
490 retval = parse_plugin_definition(ctx, value);
491
492 /*
493 * Save that we got a plugin override.
494 */
495 p_ctx->plugin_options = strdup((char*)value);
496 break;
497 case bEventEndRestoreJob:
498 retval = end_restore_job(ctx, value);
499 break;
500 default:
501 Jmsg(ctx, M_FATAL, "gfapi-fd: unknown event=%d\n", event->eventType);
502 Dmsg(ctx, debuglevel, "gfapi-fd: unknown event=%d\n", event->eventType);
503 retval = bRC_Error;
504 break;
505 }
506
507 return retval;
508 }
509
510 /**
511 * Get the next file to backup.
512 */
get_next_file_to_backup(bpContext * ctx)513 static bRC get_next_file_to_backup(bpContext* ctx)
514 {
515 int status;
516 struct save_pkt sp;
517 struct dirent* entry;
518 plugin_ctx* p_ctx = (plugin_ctx*)ctx->pContext;
519
520 /*
521 * See if we are actually crawling the fs ourself or depending on an external
522 * filelist.
523 */
524 if (p_ctx->crawl_fs) {
525 /*
526 * See if we just saved the directory then we are done processing this
527 * directory.
528 */
529 switch (p_ctx->type) {
530 case FT_DIREND:
531 /*
532 * See if there is anything on the dir stack to pop off and continue
533 * reading that directory.
534 */
535 if (!p_ctx->dir_stack->empty()) {
536 struct dir_stack_entry* entry;
537
538 /*
539 * Change the GLFS cwd back one dir.
540 */
541 status = glfs_chdir(p_ctx->glfs, "..");
542 if (status != 0) {
543 BErrNo be;
544
545 Jmsg(ctx, M_ERROR, "gfapi-fd: glfs_chdir(%s) failed: %s\n", "..",
546 be.bstrerror());
547 return bRC_Error;
548 }
549
550 /*
551 * Save where we are in the tree.
552 */
553 glfs_getcwd(p_ctx->glfs, p_ctx->cwd, SizeofPoolMemory(p_ctx->cwd));
554
555 /*
556 * Pop the previous directory handle and continue processing that.
557 */
558 entry = (struct dir_stack_entry*)p_ctx->dir_stack->pop();
559 memcpy(&p_ctx->statp, &entry->statp, sizeof(p_ctx->statp));
560 p_ctx->gdir = entry->gdir;
561 free(entry);
562 } else {
563 return bRC_OK;
564 }
565 break;
566 default:
567 break;
568 }
569
570 if (!p_ctx->gdir) { return bRC_Error; }
571 }
572
573 /*
574 * Loop until we know what file is next or when we are done.
575 */
576 while (1) {
577 memset(&p_ctx->statp, 0, sizeof(p_ctx->statp));
578
579 /*
580 * See if we are actually crawling the fs ourself or depending on an
581 * external filelist.
582 */
583 if (!p_ctx->crawl_fs) {
584 char* bp;
585 struct gluster_find_mapping* gf_mapping;
586
587 /*
588 * Get the next file from the filelist.
589 */
590 if (bfgets(p_ctx->next_filename, p_ctx->file_list_handle) == NULL) {
591 /*
592 * See if we hit EOF.
593 */
594 if (feof(p_ctx->file_list_handle)) { return bRC_OK; }
595
596 return bRC_Error;
597 }
598
599 /*
600 * Strip the newline.
601 */
602 StripTrailingJunk(p_ctx->next_filename);
603 Dmsg(ctx, debuglevel, "gfapi-fd: Processing glusterfind entry %s\n",
604 p_ctx->next_filename);
605
606 /*
607 * Lookup mapping to see what type of entry we are processing.
608 */
609 gf_mapping = find_glustermap_eventtype(p_ctx->next_filename);
610 if (!gf_mapping) {
611 Dmsg(ctx, debuglevel, "gfapi-fd: Unknown glusterfind entry %s\n",
612 p_ctx->next_filename);
613 continue;
614 }
615
616 switch (gf_mapping->type) {
617 case gf_type_new:
618 case gf_type_modify:
619 /*
620 * NEW and MODIFY just means we need to backup the file.
621 */
622 bstrinlinecpy(p_ctx->next_filename,
623 p_ctx->next_filename + gf_mapping->compare_size);
624 UrllibUnquotePlus(p_ctx->next_filename);
625 break;
626 case gf_type_rename:
627 /*
628 * RENAME means we clear the seen bitmap for the original name and
629 * backup the new filename.
630 */
631 bstrinlinecpy(p_ctx->next_filename,
632 p_ctx->next_filename + gf_mapping->compare_size);
633 bp = strchr(p_ctx->next_filename, ' ');
634 if (!bp) {
635 Jmsg(ctx, M_ERROR, "Illegal glusterfind RENAME entry: %s\n",
636 p_ctx->next_filename);
637 continue;
638 }
639 *bp++ = '\0';
640 if (p_ctx->is_accurate) {
641 UrllibUnquotePlus(p_ctx->next_filename);
642 bfuncs->ClearSeenBitmap(ctx, false, p_ctx->next_filename);
643 }
644 bstrinlinecpy(p_ctx->next_filename, bp);
645 UrllibUnquotePlus(p_ctx->next_filename);
646 break;
647 case gf_type_delete:
648 /*
649 * DELETE means we clear the seen bitmap for this file and continue.
650 */
651 if (p_ctx->is_accurate) {
652 bstrinlinecpy(p_ctx->next_filename,
653 p_ctx->next_filename + gf_mapping->compare_size);
654 UrllibUnquotePlus(p_ctx->next_filename);
655 bfuncs->ClearSeenBitmap(ctx, false, p_ctx->next_filename);
656 }
657 continue;
658 default:
659 Jmsg(ctx, M_ERROR, "Unrecognized glusterfind entry %s\n",
660 p_ctx->next_filename);
661 Dmsg(ctx, debuglevel, "gfapi-fd: Skipping glusterfind entry %s\n",
662 p_ctx->next_filename);
663 continue;
664 }
665
666 /*
667 * If we have a basename we should filter on that.
668 */
669 if (p_ctx->basedir && !bstrncmp(p_ctx->basedir, p_ctx->next_filename,
670 strlen(p_ctx->basedir))) {
671 Dmsg(ctx, debuglevel, "gfapi-fd: next file %s not under basedir %d\n",
672 p_ctx->next_filename, p_ctx->basedir);
673 continue;
674 }
675
676 status = glfs_stat(p_ctx->glfs, p_ctx->next_filename, &p_ctx->statp);
677 if (status != 0) {
678 BErrNo be;
679
680 switch (errno) {
681 case ENOENT:
682 /*
683 * Note: This was silently ignored before, now at least emit a
684 * warning in the job log that does not trigger the "OK -- with
685 * warnings" termination
686 */
687 Jmsg(ctx, M_WARNING,
688 "gfapi-fd: glfs_stat(%s) failed: %s (skipped)\n",
689 p_ctx->next_filename, be.bstrerror());
690 continue;
691 case GF_ERROR_CODE_STALE:
692 Jmsg(ctx, M_ERROR, "gfapi-fd: glfs_stat(%s) failed: %s (skipped)\n",
693 p_ctx->next_filename, be.bstrerror());
694 continue;
695 default:
696 Dmsg(ctx, debuglevel,
697 "gfapi-fd: glfs_stat(%s) failed: %s errno: %d\n",
698 p_ctx->next_filename, be.bstrerror(), errno);
699 Jmsg(ctx, M_FATAL, "gfapi-fd: glfs_stat(%s) failed: %s\n",
700 p_ctx->next_filename, be.bstrerror());
701 return bRC_Error;
702 }
703 }
704 } else {
705 #ifndef HAVE_GLFS_READDIRPLUS
706 entry = NULL;
707 glfs_readdirplus_r(p_ctx->gdir, &p_ctx->statp,
708 (dirent*)p_ctx->dirent_buffer, &entry);
709 #else
710 entry = glfs_readdirplus(p_ctx->gdir, &p_ctx->statp);
711 #endif
712
713 /*
714 * No more entries in this directory ?
715 */
716 if (!entry) {
717 status = glfs_stat(p_ctx->glfs, p_ctx->cwd, &p_ctx->statp);
718 if (status != 0) {
719 BErrNo be;
720
721 Jmsg(ctx, M_FATAL, "glfs_stat(%s) failed: %s\n", p_ctx->cwd,
722 be.bstrerror());
723 return bRC_Error;
724 }
725
726 glfs_closedir(p_ctx->gdir);
727 p_ctx->gdir = NULL;
728 p_ctx->type = FT_DIREND;
729
730 PmStrcpy(p_ctx->next_filename, p_ctx->cwd);
731
732 Dmsg(ctx, debuglevel, "gfapi-fd: next file to backup %s\n",
733 p_ctx->next_filename);
734
735 return bRC_More;
736 }
737
738 /*
739 * Skip `.', `..', and excluded file names.
740 */
741 if (entry->d_name[0] == '\0' ||
742 (entry->d_name[0] == '.' &&
743 (entry->d_name[1] == '\0' ||
744 (entry->d_name[1] == '.' && entry->d_name[2] == '\0')))) {
745 continue;
746 }
747
748 Mmsg(p_ctx->next_filename, "%s/%s", p_ctx->cwd, entry->d_name);
749 }
750
751 /*
752 * Determine the FileType.
753 */
754 switch (p_ctx->statp.st_mode & S_IFMT) {
755 case S_IFREG:
756 p_ctx->type = FT_REG;
757 break;
758 case S_IFLNK:
759 p_ctx->type = FT_LNK;
760 status =
761 glfs_readlink(p_ctx->glfs, p_ctx->next_filename, p_ctx->link_target,
762 SizeofPoolMemory(p_ctx->link_target));
763 if (status < 0) {
764 BErrNo be;
765
766 Jmsg(ctx, M_ERROR, "gfapi-fd: glfs_readlink(%s) failed: %s\n",
767 p_ctx->next_filename, be.bstrerror());
768 p_ctx->type = FT_NOFOLLOW;
769 }
770 p_ctx->link_target[status] = '\0';
771 break;
772 case S_IFDIR:
773 if (!p_ctx->crawl_fs) {
774 /*
775 * When we don't crawl the filesystem ourself we directly move to
776 * FT_DIREND as we don't recurse into the directory but just process a
777 * list of externally provided filenames.
778 */
779 p_ctx->type = FT_DIREND;
780 } else {
781 /*
782 * When we crawl the filesystem ourself we first do a FT_DIRBEGIN and
783 * recurse if needed and then save the directory in the FT_DIREND.
784 */
785 p_ctx->type = FT_DIRBEGIN;
786 }
787 break;
788 case S_IFCHR:
789 case S_IFBLK:
790 case S_IFIFO:
791 #ifdef S_IFSOCK
792 case S_IFSOCK:
793 #endif
794 p_ctx->type = FT_SPEC;
795 break;
796 default:
797 Jmsg(ctx, M_FATAL,
798 "gfapi-fd: Unknown filetype encountered %ld for %s\n",
799 p_ctx->statp.st_mode & S_IFMT, p_ctx->next_filename);
800 return bRC_Error;
801 }
802
803 /*
804 * See if we accept this file under the currently loaded fileset.
805 */
806 memset(&sp, 0, sizeof(sp));
807 sp.pkt_size = sizeof(sp);
808 sp.pkt_end = sizeof(sp);
809 sp.fname = p_ctx->next_filename;
810 sp.type = p_ctx->type;
811 memcpy(&sp.statp, &p_ctx->statp, sizeof(sp.statp));
812
813 if (bfuncs->AcceptFile(ctx, &sp) == bRC_Skip) {
814 Dmsg(ctx, debuglevel,
815 "gfapi-fd: file %s skipped due to current fileset settings\n",
816 p_ctx->next_filename);
817 continue;
818 }
819
820 /*
821 * If we made it here we have the next file to backup.
822 */
823 break;
824 }
825
826 Dmsg(ctx, debuglevel, "gfapi-fd: next file to backup %s\n",
827 p_ctx->next_filename);
828
829 return bRC_More;
830 }
831
832 /**
833 * Start the backup of a specific file
834 */
startBackupFile(bpContext * ctx,struct save_pkt * sp)835 static bRC startBackupFile(bpContext* ctx, struct save_pkt* sp)
836 {
837 int status;
838 plugin_ctx* p_ctx = (plugin_ctx*)ctx->pContext;
839
840 /*
841 * Save the current flags used to save the next file.
842 */
843 CopyBits(FO_MAX, sp->flags, p_ctx->flags);
844
845 switch (p_ctx->type) {
846 case FT_DIRBEGIN:
847 /*
848 * We should never get here when we don't crawl the filesystem ourself but
849 * just in case we do we test in the if that p_ctx->crawl_fs is not set.
850 *
851 * See if we are recursing if so we open the directory and process it.
852 * We also open the directory when it is the toplevel e.g. when
853 * p_ctx->gdir == NULL.
854 */
855 if (p_ctx->crawl_fs &&
856 (!p_ctx->gdir || !BitIsSet(FO_NO_RECURSION, p_ctx->flags))) {
857 /*
858 * Change into the directory and process all entries in it.
859 */
860 status = glfs_chdir(p_ctx->glfs, p_ctx->next_filename);
861 if (status != 0) {
862 BErrNo be;
863
864 Jmsg(ctx, M_ERROR, "gfapi-fd: glfs_chdir(%s) failed: %s\n",
865 p_ctx->next_filename, be.bstrerror());
866 p_ctx->type = FT_NOOPEN;
867 } else {
868 /*
869 * Push the current directory onto the directory stack so we can
870 * continue processing it later on.
871 */
872 if (p_ctx->gdir) {
873 struct dir_stack_entry* new_entry;
874
875 new_entry =
876 (struct dir_stack_entry*)malloc(sizeof(struct dir_stack_entry));
877 memcpy(&new_entry->statp, &p_ctx->statp, sizeof(new_entry->statp));
878 new_entry->gdir = p_ctx->gdir;
879 p_ctx->dir_stack->push(new_entry);
880 }
881
882 /*
883 * Open this directory for processing.
884 */
885 p_ctx->gdir = glfs_opendir(p_ctx->glfs, ".");
886 if (!p_ctx->gdir) {
887 BErrNo be;
888
889 Jmsg(ctx, M_ERROR, "gfapi-fd: glfs_opendir(%s) failed: %s\n",
890 p_ctx->next_filename, be.bstrerror());
891 p_ctx->type = FT_NOOPEN;
892
893 /*
894 * Pop the previous directory handle and continue processing that.
895 */
896 if (!p_ctx->dir_stack->empty()) {
897 struct dir_stack_entry* entry;
898
899 entry = (struct dir_stack_entry*)p_ctx->dir_stack->pop();
900 memcpy(&p_ctx->statp, &entry->statp, sizeof(p_ctx->statp));
901 p_ctx->gdir = entry->gdir;
902 free(entry);
903
904 glfs_chdir(p_ctx->glfs, "..");
905 }
906 } else {
907 glfs_getcwd(p_ctx->glfs, p_ctx->cwd, SizeofPoolMemory(p_ctx->cwd));
908 }
909 }
910 }
911
912 /*
913 * No link target and read the actual content.
914 */
915 sp->link = NULL;
916 sp->no_read = true;
917 break;
918 case FT_DIREND:
919 /*
920 * For a directory, link is the same as fname, but with trailing slash
921 * and don't read the actual content.
922 */
923 Mmsg(p_ctx->link_target, "%s/", p_ctx->next_filename);
924 sp->link = p_ctx->link_target;
925 sp->no_read = true;
926 break;
927 case FT_LNK:
928 /*
929 * Link target and don't read the actual content.
930 */
931 sp->link = p_ctx->link_target;
932 sp->no_read = true;
933 break;
934 case FT_REGE:
935 case FT_REG:
936 case FT_SPEC:
937 case FT_RAW:
938 case FT_FIFO:
939 /*
940 * No link target and read the actual content.
941 */
942 sp->link = NULL;
943 sp->no_read = false;
944 break;
945 default:
946 /*
947 * No link target and don't read the actual content.
948 */
949 sp->link = NULL;
950 sp->no_read = true;
951 break;
952 }
953
954 sp->fname = p_ctx->next_filename;
955 sp->type = p_ctx->type;
956 memcpy(&sp->statp, &p_ctx->statp, sizeof(sp->statp));
957 sp->save_time = p_ctx->since;
958
959 /*
960 * If we crawl the filesystem ourself we check the timestamps when
961 * using glusterfind the changelog gives changes since the since time
962 * we provided to it so it makes no sense to check again.
963 */
964 if (p_ctx->crawl_fs) {
965 /*
966 * For Incremental and Differential backups use checkChanges method to
967 * see if we need to backup this file.
968 */
969 switch (p_ctx->backup_level) {
970 case L_INCREMENTAL:
971 case L_DIFFERENTIAL:
972 /*
973 * When sp->type is FT_DIRBEGIN, skip calling checkChanges() because it
974 * would be useless.
975 */
976 if (sp->type == FT_DIRBEGIN) {
977 Dmsg(ctx, debuglevel,
978 "gfapi-fd: skip checkChanges() for %s because sp->type is "
979 "FT_DIRBEGIN\n",
980 p_ctx->next_filename);
981 sp->type = FT_DIRNOCHG;
982 break;
983 }
984 /*
985 * When glfs_chdir() or glfs_opendir() failed, then sp->type is
986 * FT_NOOPEN, then skip calling checkChanges() because it would be
987 * useless.
988 */
989 if (sp->type == FT_NOOPEN) {
990 Dmsg(ctx, debuglevel,
991 "gfapi-fd: skip checkChanges() for %s because sp->type is "
992 "FT_NOOPEN\n",
993 p_ctx->next_filename);
994 break;
995 }
996
997 switch (bfuncs->checkChanges(ctx, sp)) {
998 case bRC_Seen:
999 Dmsg(ctx, debuglevel,
1000 "gfapi-fd: skipping %s checkChanges returns bRC_Seen\n",
1001 p_ctx->next_filename);
1002 switch (sp->type) {
1003 case FT_DIRBEGIN:
1004 case FT_DIREND:
1005 sp->type = FT_DIRNOCHG;
1006 break;
1007 default:
1008 sp->type = FT_NOCHG;
1009 break;
1010 }
1011 break;
1012 default:
1013 break;
1014 }
1015 }
1016 }
1017
1018 return bRC_OK;
1019 }
1020
1021 /**
1022 * Done with backup of this file
1023 */
endBackupFile(bpContext * ctx)1024 static bRC endBackupFile(bpContext* ctx)
1025 {
1026 plugin_ctx* p_ctx = (plugin_ctx*)ctx->pContext;
1027
1028 if (!p_ctx) { return bRC_Error; }
1029
1030 /*
1031 * See if we need to fix the utimes.
1032 */
1033 if (BitIsSet(FO_NOATIME, p_ctx->flags)) {
1034 struct timespec times[2];
1035
1036 times[0].tv_sec = p_ctx->statp.st_atime;
1037 times[0].tv_nsec = 0;
1038 times[1].tv_sec = p_ctx->statp.st_mtime;
1039 times[1].tv_nsec = 0;
1040
1041 glfs_lutimens(p_ctx->glfs, p_ctx->next_filename, times);
1042 }
1043
1044 return get_next_file_to_backup(ctx);
1045 }
1046
1047 /**
1048 * Strip any backslashes in the string.
1049 */
StripBackSlashes(char * value)1050 static inline void StripBackSlashes(char* value)
1051 {
1052 char* bp;
1053
1054 bp = value;
1055 while (*bp) {
1056 switch (*bp) {
1057 case '\\':
1058 bstrinlinecpy(bp, bp + 1);
1059 break;
1060 default:
1061 break;
1062 }
1063
1064 bp++;
1065 }
1066 }
1067
1068 /**
1069 * Only set destination to value when it has no previous setting.
1070 */
SetStringIfNull(char ** destination,char * value)1071 static inline void SetStringIfNull(char** destination, char* value)
1072 {
1073 if (!*destination) {
1074 *destination = strdup(value);
1075 StripBackSlashes(*destination);
1076 }
1077 }
1078
1079 /**
1080 * Always set destination to value and clean any previous one.
1081 */
SetString(char ** destination,char * value)1082 static inline void SetString(char** destination, char* value)
1083 {
1084 if (*destination) { free(*destination); }
1085
1086 *destination = strdup(value);
1087 StripBackSlashes(*destination);
1088 }
1089
1090 /**
1091 * Parse the plugin definition passed in.
1092 *
1093 * The definition is in this form:
1094 *
1095 * gfapi:volume=gluster[+transport]\\://[server[:port]]/volname[/dir][?socket=...]
1096 */
parse_plugin_definition(bpContext * ctx,void * value)1097 static bRC parse_plugin_definition(bpContext* ctx, void* value)
1098 {
1099 int i;
1100 bool keep_existing;
1101 char *plugin_definition, *bp, *argument, *argument_value;
1102 plugin_ctx* p_ctx = (plugin_ctx*)ctx->pContext;
1103
1104 if (!p_ctx || !value) { return bRC_Error; }
1105
1106 /*
1107 * See if we already got some plugin definition before and its exactly the
1108 * same.
1109 */
1110 if (p_ctx->plugin_definition) {
1111 if (bstrcmp(p_ctx->plugin_definition, (char*)value)) { return bRC_OK; }
1112
1113 free(p_ctx->plugin_definition);
1114 }
1115
1116 /*
1117 * Keep track of the last processed plugin definition.
1118 */
1119 p_ctx->plugin_definition = strdup((char*)value);
1120
1121 /*
1122 * Keep overrides passed in via pluginoptions.
1123 */
1124 keep_existing = (p_ctx->plugin_options) ? true : false;
1125
1126 /*
1127 * Parse the plugin definition.
1128 * Make a private copy of the whole string.
1129 */
1130 plugin_definition = strdup((char*)value);
1131
1132 bp = strchr(plugin_definition, ':');
1133 if (!bp) {
1134 Jmsg(ctx, M_FATAL, "gfapi-fd: Illegal plugin definition %s\n",
1135 plugin_definition);
1136 Dmsg(ctx, debuglevel, "gfapi-fd: Illegal plugin definition %s\n",
1137 plugin_definition);
1138 goto bail_out;
1139 }
1140
1141 /*
1142 * Skip the first ':'
1143 */
1144 bp++;
1145 while (bp) {
1146 if (strlen(bp) == 0) { break; }
1147
1148 /*
1149 * Each argument is in the form:
1150 * <argument> = <argument_value>
1151 *
1152 * So we setup the right pointers here, argument to the beginning
1153 * of the argument, argument_value to the beginning of the argument_value.
1154 */
1155 argument = bp;
1156 argument_value = strchr(bp, '=');
1157 if (!argument_value) {
1158 Jmsg(ctx, M_FATAL, "gfapi-fd: Illegal argument %s without value\n",
1159 argument);
1160 Dmsg(ctx, debuglevel, "gfapi-fd: Illegal argument %s without value\n",
1161 argument);
1162 goto bail_out;
1163 }
1164 *argument_value++ = '\0';
1165
1166 /*
1167 * See if there are more arguments and setup for the next run.
1168 */
1169 bp = argument_value;
1170 do {
1171 bp = strchr(bp, ':');
1172 if (bp) {
1173 if (*(bp - 1) != '\\') {
1174 *bp++ = '\0';
1175 break;
1176 } else {
1177 bp++;
1178 }
1179 }
1180 } while (bp);
1181
1182 for (i = 0; plugin_arguments[i].name; i++) {
1183 if (Bstrcasecmp(argument, plugin_arguments[i].name)) {
1184 char** str_destination = NULL;
1185
1186 switch (plugin_arguments[i].type) {
1187 case argument_volume_spec:
1188 str_destination = &p_ctx->gfapi_volume_spec;
1189 break;
1190 case argument_snapdir:
1191 str_destination = &p_ctx->snapdir;
1192 break;
1193 case argument_gf_file_list:
1194 str_destination = &p_ctx->gf_file_list;
1195 break;
1196 default:
1197 break;
1198 }
1199
1200 /*
1201 * Keep the first value, ignore any next setting.
1202 */
1203 if (str_destination) {
1204 if (keep_existing) {
1205 SetStringIfNull(str_destination, argument_value);
1206 } else {
1207 SetString(str_destination, argument_value);
1208 }
1209 }
1210
1211 /*
1212 * When we have a match break the loop.
1213 */
1214 break;
1215 }
1216 }
1217
1218 /*
1219 * Got an invalid keyword ?
1220 */
1221 if (!plugin_arguments[i].name) {
1222 Jmsg(ctx, M_FATAL,
1223 "gfapi-fd: Illegal argument %s with value %s in plugin definition\n",
1224 argument, argument_value);
1225 Dmsg(ctx, debuglevel,
1226 "gfapi-fd: Illegal argument %s with value %s in plugin definition\n",
1227 argument, argument_value);
1228 goto bail_out;
1229 }
1230 }
1231
1232 free(plugin_definition);
1233
1234 return bRC_OK;
1235
1236 bail_out:
1237 free(plugin_definition);
1238 return bRC_Error;
1239 }
1240
1241 /**
1242 * Create a parent directory using the gfapi.
1243 */
GfapiMakedirs(plugin_ctx * p_ctx,const char * directory)1244 static inline bool GfapiMakedirs(plugin_ctx* p_ctx, const char* directory)
1245 {
1246 int len;
1247 char* bp;
1248 struct stat st;
1249 bool retval = false;
1250 PoolMem new_directory(PM_FNAME);
1251
1252 PmStrcpy(new_directory, directory);
1253 len = strlen(new_directory.c_str());
1254
1255 /*
1256 * Strip any trailing slashes.
1257 */
1258 for (char* p = new_directory.c_str() + (len - 1);
1259 (p >= new_directory.c_str()) && *p == '/'; p--) {
1260 *p = '\0';
1261 }
1262
1263 if (strlen(new_directory.c_str()) &&
1264 glfs_stat(p_ctx->glfs, new_directory.c_str(), &st) != 0) {
1265 /*
1266 * See if the parent exists.
1267 */
1268 switch (errno) {
1269 case ENOENT:
1270 bp = strrchr(new_directory.c_str(), '/');
1271 if (bp) {
1272 /*
1273 * Make sure our parent exists.
1274 */
1275 *bp = '\0';
1276 retval = GfapiMakedirs(p_ctx, new_directory.c_str());
1277 if (!retval) { return false; }
1278
1279 /*
1280 * Create the directory.
1281 */
1282 if (glfs_mkdir(p_ctx->glfs, directory, 0750) == 0) {
1283 if (!p_ctx->path_list) { p_ctx->path_list = path_list_init(); }
1284 PathListAdd(p_ctx->path_list, strlen(directory), directory);
1285 retval = true;
1286 }
1287 }
1288 break;
1289 default:
1290 break;
1291 }
1292 } else {
1293 retval = true;
1294 }
1295
1296 return retval;
1297 }
1298
1299 /**
1300 * Parse a gluster definition into something we can use for setting
1301 * up the right connection to a gluster management server and get access
1302 * to a gluster volume.
1303 *
1304 * Syntax:
1305 *
1306 * gluster[+transport]://[server[:port]]/volname[/dir][?socket=...]
1307 *
1308 * 'gluster' is the protocol.
1309 *
1310 * 'transport' specifies the transport type used to connect to gluster
1311 * management daemon (glusterd). Valid transport types are tcp, unix
1312 * and rdma. If a transport type isn't specified, then tcp type is assumed.
1313 *
1314 * 'server' specifies the server where the volume file specification for
1315 * the given volume resides. This can be either hostname, ipv4 address
1316 * or ipv6 address. ipv6 address needs to be within square brackets [ ].
1317 * If transport type is 'unix', then 'server' field should not be specifed.
1318 * The 'socket' field needs to be populated with the path to unix domain
1319 * socket.
1320 *
1321 * 'port' is the port number on which glusterd is listening. This is optional
1322 * and if not specified, QEMU will send 0 which will make gluster to use the
1323 * default port. If the transport type is unix, then 'port' should not be
1324 * specified.
1325 *
1326 * 'volname' is the name of the gluster volume which contains the data that
1327 * we need to be backup.
1328 *
1329 * 'dir' is an optional base directory on the 'volname'
1330 *
1331 * Examples:
1332 *
1333 * gluster://1.2.3.4/testvol[/dir]
1334 * gluster+tcp://1.2.3.4/testvol[/dir]
1335 * gluster+tcp://1.2.3.4:24007/testvol[/dir]
1336 * gluster+tcp://[1:2:3:4:5:6:7:8]/testvol[/dir]
1337 * gluster+tcp://[1:2:3:4:5:6:7:8]:24007/testvol[/dir]
1338 * gluster+tcp://server.domain.com:24007/testvol[/dir]
1339 * gluster+unix:///testvol[/dir]?socket=/tmp/glusterd.socket
1340 * gluster+rdma://1.2.3.4:24007/testvol[/dir]
1341 */
parse_gfapi_devicename(char * devicename,char ** transport,char ** servername,char ** volumename,char ** dir,int * serverport)1342 static inline bool parse_gfapi_devicename(char* devicename,
1343 char** transport,
1344 char** servername,
1345 char** volumename,
1346 char** dir,
1347 int* serverport)
1348 {
1349 char* bp;
1350
1351 /*
1352 * Make sure its a URI that starts with gluster.
1353 */
1354 if (!bstrncasecmp(devicename, "gluster", 7)) { return false; }
1355
1356 /*
1357 * Parse any explicit protocol.
1358 */
1359 bp = strchr(devicename, '+');
1360 if (bp) {
1361 *transport = ++bp;
1362 bp = strchr(bp, ':');
1363 if (bp) {
1364 *bp++ = '\0';
1365 } else {
1366 goto bail_out;
1367 }
1368 } else {
1369 *transport = NULL;
1370 bp = strchr(devicename, ':');
1371 if (!bp) { goto bail_out; }
1372 }
1373
1374 /*
1375 * When protocol is not UNIX parse servername and portnr.
1376 */
1377 if (!*transport || !Bstrcasecmp(*transport, "unix")) {
1378 /*
1379 * Parse servername of gluster management server.
1380 */
1381 bp = strchr(bp, '/');
1382
1383 /*
1384 * Validate URI.
1385 */
1386 if (!bp || *(bp + 1) != '/') { goto bail_out; }
1387
1388 /*
1389 * Skip the two //
1390 */
1391 *bp++ = '\0';
1392 bp++;
1393 *servername = bp;
1394
1395 /*
1396 * Parse any explicit server portnr.
1397 * We search reverse in the string for a : what indicates
1398 * a port specification but in that string there may not contain a ']'
1399 * because then we searching in a IPv6 string.
1400 */
1401 bp = strrchr(bp, ':');
1402 if (bp && !strchr(bp, ']')) {
1403 char* port;
1404
1405 *bp++ = '\0';
1406 port = bp;
1407 bp = strchr(bp, '/');
1408 if (!bp) { goto bail_out; }
1409 *bp++ = '\0';
1410 *serverport = str_to_int64(port);
1411 *volumename = bp;
1412
1413 /*
1414 * See if there is a dir specified.
1415 */
1416 bp = strchr(bp, '/');
1417 if (bp) {
1418 *bp++ = '\0';
1419 *dir = bp;
1420 }
1421 } else {
1422 *serverport = 0;
1423 bp = *servername;
1424
1425 /*
1426 * Parse the volume name.
1427 */
1428 bp = strchr(bp, '/');
1429 if (!bp) { goto bail_out; }
1430 *bp++ = '\0';
1431 *volumename = bp;
1432
1433 /*
1434 * See if there is a dir specified.
1435 * As we use an unix socket, we need to check if we are not using the
1436 * path specified for the unix socket.
1437 * It can happen if there is no subdirectory in the URI
1438 */
1439 char *before_parameter = strchr(bp, '?');
1440 char *before_dir = strchr(bp, '/');
1441
1442 if (before_dir && (!before_parameter || before_parameter > before_dir)) {
1443 bp = before_dir;
1444 *bp++ = '\0';
1445 *dir = bp;
1446 } else {
1447 *dir = nullptr;
1448 }
1449 }
1450 } else {
1451 /*
1452 * For UNIX serverport is zero.
1453 */
1454 *serverport = 0;
1455
1456 /*
1457 * Validate URI.
1458 */
1459 if (*bp != '/' || *(bp + 1) != '/') { goto bail_out; }
1460
1461 /*
1462 * Skip the two //
1463 */
1464 *bp++ = '\0';
1465 bp++;
1466
1467 /*
1468 * For UNIX URIs the server part of the URI needs to be empty.
1469 */
1470 if (*bp++ != '/') { goto bail_out; }
1471 *volumename = bp;
1472
1473 /*
1474 * See if there is a dir specified.
1475 */
1476 bp = strchr(bp, '/');
1477 if (bp) {
1478 *bp++ = '\0';
1479 *dir = bp;
1480 }
1481
1482 /*
1483 * Parse any socket parameters.
1484 */
1485 bp = strchr(bp, '?');
1486 if (bp) {
1487 if (bstrncasecmp(bp + 1, "socket=", 7)) {
1488 *bp = '\0';
1489 *servername = bp + 8;
1490 }
1491 }
1492 }
1493
1494 return true;
1495
1496 bail_out:
1497 return false;
1498 }
1499
1500 /**
1501 * Open a volume using GFAPI.
1502 */
connect_to_gluster(bpContext * ctx,bool is_backup)1503 static bRC connect_to_gluster(bpContext* ctx, bool is_backup)
1504 {
1505 int status;
1506 plugin_ctx* p_ctx = (plugin_ctx*)ctx->pContext;
1507
1508 if (!p_ctx->gfapi_volume_spec) { return bRC_Error; }
1509
1510 if (!parse_gfapi_devicename(p_ctx->gfapi_volume_spec, &p_ctx->transport,
1511 &p_ctx->servername, &p_ctx->volumename,
1512 &p_ctx->basedir, &p_ctx->serverport)) {
1513 return bRC_Error;
1514 }
1515
1516 /*
1517 * If we get called and we already have a handle to gfapi we should tear it
1518 * down.
1519 */
1520 if (p_ctx->glfs) {
1521 glfs_fini(p_ctx->glfs);
1522 p_ctx->glfs = NULL;
1523 }
1524
1525 p_ctx->glfs = glfs_new(p_ctx->volumename);
1526 if (!p_ctx->glfs) { goto bail_out; }
1527
1528 status = glfs_set_volfile_server(
1529 p_ctx->glfs, (p_ctx->transport) ? p_ctx->transport : "tcp",
1530 p_ctx->servername, p_ctx->serverport);
1531 if (status < 0) { goto bail_out; }
1532
1533 if (is_backup) {
1534 status = glfs_set_xlator_option(p_ctx->glfs, "*-md-cache",
1535 "cache-posix-acl", "true");
1536 if (status < 0) { goto bail_out; }
1537 }
1538
1539 if (is_backup && p_ctx->snapdir) {
1540 status = glfs_set_xlator_option(p_ctx->glfs, "*-snapview-client",
1541 "snapdir-entry-path", p_ctx->snapdir);
1542 if (status < 0) { goto bail_out; }
1543 }
1544
1545 status = glfs_init(p_ctx->glfs);
1546 if (status < 0) { goto bail_out; }
1547
1548 return bRC_OK;
1549
1550 bail_out:
1551 if (p_ctx->glfs) {
1552 glfs_fini(p_ctx->glfs);
1553 p_ctx->glfs = NULL;
1554 }
1555
1556 return bRC_Error;
1557 }
1558
1559 /**
1560 * Generic setup for performing a backup.
1561 */
setup_backup(bpContext * ctx,void * value)1562 static bRC setup_backup(bpContext* ctx, void* value)
1563 {
1564 bRC retval = bRC_Error;
1565 plugin_ctx* p_ctx = (plugin_ctx*)ctx->pContext;
1566
1567 if (!p_ctx || !value) { goto bail_out; }
1568
1569 /*
1570 * If we are already having a handle to gfapi and we are getting the
1571 * same plugin definition there is no need to tear down the whole stuff and
1572 * setup exactly the same.
1573 */
1574 if (p_ctx->glfs && bstrcmp((char*)value, p_ctx->plugin_definition)) {
1575 return bRC_OK;
1576 }
1577
1578 if (connect_to_gluster(ctx, true) != bRC_OK) { goto bail_out; }
1579
1580 /*
1581 * See if we use an external list with files to backup or should crawl the
1582 * filesystem ourself.
1583 */
1584 if (p_ctx->gf_file_list) {
1585 int accurate;
1586
1587 /*
1588 * Get the setting for accurate for this Job.
1589 */
1590 bfuncs->getBareosValue(ctx, bVarAccurate, (void*)&accurate);
1591 if (accurate) { p_ctx->is_accurate = true; }
1592
1593 p_ctx->crawl_fs = false;
1594 if ((p_ctx->file_list_handle = fopen(p_ctx->gf_file_list, "r")) ==
1595 (FILE*)NULL) {
1596 Jmsg(ctx, M_FATAL, "Failed to open %s for reading files to backup\n",
1597 p_ctx->gf_file_list);
1598 Dmsg(ctx, debuglevel, "Failed to open %s for reading files to backup\n",
1599 p_ctx->gf_file_list);
1600 goto bail_out;
1601 }
1602
1603 if (p_ctx->is_accurate) {
1604 /*
1605 * Mark all files as seen from the previous backup when this is a
1606 * incremental or differential backup. The entries we get from glusterfind
1607 * are only the changes since that earlier backup.
1608 */
1609 switch (p_ctx->backup_level) {
1610 case L_INCREMENTAL:
1611 case L_DIFFERENTIAL:
1612 if (bfuncs->SetSeenBitmap(ctx, true, NULL) != bRC_OK) {
1613 Jmsg(ctx, M_FATAL,
1614 "Failed to enable all entries in Seen bitmap, not an accurate "
1615 "backup ?\n");
1616 Dmsg(ctx, debuglevel,
1617 "Failed to enable all entries in Seen bitmap, not an accurate "
1618 "backup ?\n");
1619 goto bail_out;
1620 }
1621 break;
1622 default:
1623 break;
1624 }
1625 }
1626
1627 /*
1628 * Setup the plugin for the first entry.
1629 * As we need to get it from the gfflilelist we use
1630 * get_next_file_to_backup() to do the setup for us it retrieves the entry
1631 * and does a setup of filetype etc.
1632 */
1633 switch (get_next_file_to_backup(ctx)) {
1634 case bRC_OK:
1635 /*
1636 * get_next_file_to_backup() normally returns bRC_More to indicate that
1637 * there are more files to backup. But when using glusterfind we use an
1638 * external filelist which could be empty in that special case we get
1639 * bRC_OK back from get_next_file_to_backup() and then only in
1640 * setup_backup() we return bRC_Skip which will skip processing of any
1641 * more files to backup.
1642 */
1643 retval = bRC_Skip;
1644 break;
1645 case bRC_Error:
1646 Jmsg(ctx, M_FATAL, "Failed to get first file to backup\n");
1647 Dmsg(ctx, debuglevel, "Failed to get first file to backup\n");
1648 goto bail_out;
1649 default:
1650 retval = bRC_OK;
1651 break;
1652 }
1653 } else {
1654 p_ctx->crawl_fs = true;
1655
1656 /*
1657 * Allocate some internal memory for:
1658 * - The current working dir.
1659 * - For the older glfs_readdirplus_r() function an dirent hold buffer.
1660 */
1661 p_ctx->cwd = GetPoolMemory(PM_FNAME);
1662 p_ctx->cwd = CheckPoolMemorySize(p_ctx->cwd, GLFS_PATH_MAX);
1663 #ifndef HAVE_GLFS_READDIRPLUS
1664 p_ctx->dirent_buffer = GetPoolMemory(PM_FNAME);
1665
1666 /*
1667 * Resize the dirent buffer to 512 bytes which should be enough to hold any
1668 * dirent.
1669 */
1670 p_ctx->dirent_buffer = CheckPoolMemorySize(p_ctx->dirent_buffer, 512);
1671 #endif
1672
1673 /*
1674 * This is an alist that holds the stack of directories we have open.
1675 * We push the current directory onto this stack the moment we start
1676 * processing a sub directory and pop it from this list when we are
1677 * done processing that sub directory.
1678 */
1679 p_ctx->dir_stack = new alist(10, owned_by_alist);
1680
1681 /*
1682 * Setup the directory we need to start scanning by setting the filetype
1683 * to FT_DIRBEGIN e.g. same as recursing into directory and let the recurse
1684 * logic do the rest of the work.
1685 */
1686 p_ctx->type = FT_DIRBEGIN;
1687 if (p_ctx->basedir && strlen(p_ctx->basedir) > 0) {
1688 PmStrcpy(p_ctx->next_filename, p_ctx->basedir);
1689 } else {
1690 PmStrcpy(p_ctx->next_filename, "/");
1691 }
1692
1693 retval = bRC_OK;
1694 }
1695
1696 bail_out:
1697 return retval;
1698 }
1699
1700 /**
1701 * Generic setup for performing a restore.
1702 */
setup_restore(bpContext * ctx,void * value)1703 static bRC setup_restore(bpContext* ctx, void* value)
1704 {
1705 plugin_ctx* p_ctx = (plugin_ctx*)ctx->pContext;
1706
1707 if (!p_ctx || !value) { return bRC_Error; }
1708
1709 /*
1710 * If we are already having a handle to gfapi and we are getting the
1711 * same plugin definition there is no need to tear down the whole stuff and
1712 * setup exactly the same.
1713 */
1714 if (p_ctx->glfs && bstrcmp((char*)value, p_ctx->plugin_definition)) {
1715 return bRC_OK;
1716 }
1717
1718 return connect_to_gluster(ctx, false);
1719 }
1720
1721 /**
1722 * Bareos is calling us to do the actual I/O
1723 */
pluginIO(bpContext * ctx,struct io_pkt * io)1724 static bRC pluginIO(bpContext* ctx, struct io_pkt* io)
1725 {
1726 plugin_ctx* p_ctx = (plugin_ctx*)ctx->pContext;
1727
1728 if (!p_ctx) { return bRC_Error; }
1729
1730 io->io_errno = 0;
1731 io->lerror = 0;
1732 io->win32 = false;
1733
1734 switch (io->func) {
1735 case IO_OPEN:
1736 if (io->flags & (O_CREAT | O_WRONLY)) {
1737 p_ctx->gfd = glfs_creat(p_ctx->glfs, io->fname, io->flags, io->mode);
1738 } else {
1739 p_ctx->gfd = glfs_open(p_ctx->glfs, io->fname, io->flags);
1740 }
1741
1742 if (!p_ctx->gfd) {
1743 io->status = -1;
1744 io->io_errno = errno;
1745 goto bail_out;
1746 }
1747 io->status = 0;
1748 break;
1749 case IO_READ:
1750 if (p_ctx->gfd) {
1751 io->status = glfs_read(p_ctx->gfd, io->buf, io->count, 0);
1752 if (io->status < 0) {
1753 io->io_errno = errno;
1754 goto bail_out;
1755 }
1756 } else {
1757 io->status = -1;
1758 io->io_errno = EBADF;
1759 goto bail_out;
1760 }
1761 break;
1762 case IO_WRITE:
1763 if (p_ctx->gfd) {
1764 io->status = glfs_write(p_ctx->gfd, io->buf, io->count, 0);
1765 if (io->status < 0) {
1766 io->io_errno = errno;
1767 goto bail_out;
1768 }
1769 } else {
1770 io->status = -1;
1771 io->io_errno = EBADF;
1772 goto bail_out;
1773 }
1774 break;
1775 case IO_CLOSE:
1776 if (p_ctx->gfd) {
1777 io->status = glfs_close(p_ctx->gfd);
1778 p_ctx->gfd = NULL;
1779 if (io->status < 0) {
1780 io->io_errno = errno;
1781 goto bail_out;
1782 }
1783 } else {
1784 io->status = -1;
1785 io->io_errno = EBADF;
1786 goto bail_out;
1787 }
1788 break;
1789 case IO_SEEK:
1790 if (p_ctx->gfd) {
1791 io->status = glfs_lseek(p_ctx->gfd, io->offset, io->whence);
1792 if (io->status < 0) {
1793 io->io_errno = errno;
1794 goto bail_out;
1795 }
1796 } else {
1797 io->status = -1;
1798 io->io_errno = EBADF;
1799 goto bail_out;
1800 }
1801 break;
1802 }
1803
1804 return bRC_OK;
1805
1806 bail_out:
1807 return bRC_Error;
1808 }
1809
1810 /**
1811 * See if we need to do any postprocessing after the restore.
1812 */
end_restore_job(bpContext * ctx,void * value)1813 static bRC end_restore_job(bpContext* ctx, void* value)
1814 {
1815 bRC retval = bRC_OK;
1816 plugin_ctx* p_ctx = (plugin_ctx*)ctx->pContext;
1817
1818 if (!p_ctx) { return bRC_Error; }
1819
1820 Dmsg(ctx, debuglevel, "gfapi-fd: entering end_restore_job\n");
1821
1822 Dmsg(ctx, debuglevel, "gfapi-fd: leaving end_restore_job\n");
1823
1824 return retval;
1825 }
1826
1827 /**
1828 * Bareos is notifying us that a plugin name string was found,
1829 * and passing us the plugin command, so we can prepare for a restore.
1830 */
startRestoreFile(bpContext * ctx,const char * cmd)1831 static bRC startRestoreFile(bpContext* ctx, const char* cmd) { return bRC_OK; }
1832
1833 /**
1834 * Bareos is notifying us that the plugin data has terminated,
1835 * so the restore for this particular file is done.
1836 */
endRestoreFile(bpContext * ctx)1837 static bRC endRestoreFile(bpContext* ctx) { return bRC_OK; }
1838
1839 /**
1840 * This is called during restore to create the file (if necessary) We must
1841 * return in rp->create_status:
1842 *
1843 * CF_ERROR -- error
1844 * CF_SKIP -- skip processing this file
1845 * CF_EXTRACT -- extract the file (i.e.call i/o routines)
1846 * CF_CREATED -- created, but no content to extract (typically directories)
1847 */
createFile(bpContext * ctx,struct restore_pkt * rp)1848 static bRC createFile(bpContext* ctx, struct restore_pkt* rp)
1849 {
1850 int status;
1851 bool exists = false;
1852 struct stat st;
1853 plugin_ctx* p_ctx = (plugin_ctx*)ctx->pContext;
1854
1855 if (!p_ctx) { return bRC_Error; }
1856
1857 /*
1858 * See if the file already exists.
1859 */
1860 Dmsg(ctx, 400, "gfapi-fd: Replace=%c %d\n", (char)rp->replace, rp->replace);
1861 status = glfs_lstat(p_ctx->glfs, rp->ofname, &st);
1862 if (status == 0) {
1863 exists = true;
1864
1865 switch (rp->replace) {
1866 case REPLACE_IFNEWER:
1867 if (rp->statp.st_mtime <= st.st_mtime) {
1868 Jmsg(ctx, M_INFO, 0, _("gfapi-fd: File skipped. Not newer: %s\n"),
1869 rp->ofname);
1870 rp->create_status = CF_SKIP;
1871 goto bail_out;
1872 }
1873 break;
1874 case REPLACE_IFOLDER:
1875 if (rp->statp.st_mtime >= st.st_mtime) {
1876 Jmsg(ctx, M_INFO, 0, _("gfapi-fd: File skipped. Not older: %s\n"),
1877 rp->ofname);
1878 rp->create_status = CF_SKIP;
1879 goto bail_out;
1880 }
1881 break;
1882 case REPLACE_NEVER:
1883 /*
1884 * Set attributes if we created this directory
1885 */
1886 if (rp->type == FT_DIREND &&
1887 PathListLookup(p_ctx->path_list, rp->ofname)) {
1888 break;
1889 }
1890 Jmsg(ctx, M_INFO, 0, _("gfapi-fd: File skipped. Already exists: %s\n"),
1891 rp->ofname);
1892 rp->create_status = CF_SKIP;
1893 goto bail_out;
1894 case REPLACE_ALWAYS:
1895 break;
1896 }
1897 }
1898
1899 switch (rp->type) {
1900 case FT_LNKSAVED: /* Hard linked, file already saved */
1901 case FT_LNK:
1902 case FT_SPEC: /* Fifo, ... to be backed up */
1903 case FT_REGE: /* Empty file */
1904 case FT_REG: /* Regular file */
1905 /*
1906 * See if file already exists then we need to unlink it.
1907 */
1908 if (exists) {
1909 Dmsg(ctx, 400, "gfapi-fd: unlink %s\n", rp->ofname);
1910 status = glfs_unlink(p_ctx->glfs, rp->ofname);
1911 if (status != 0) {
1912 BErrNo be;
1913
1914 Jmsg(ctx, M_ERROR, 0,
1915 _("gfapi-fd: File %s already exists and could not be replaced. "
1916 "ERR=%s.\n"),
1917 rp->ofname, be.bstrerror());
1918 /*
1919 * Continue despite error
1920 */
1921 }
1922 } else {
1923 /*
1924 * File doesn't exist see if we need to create the parent directory.
1925 */
1926 PoolMem parent_dir(PM_FNAME);
1927 char* bp;
1928
1929 PmStrcpy(parent_dir, rp->ofname);
1930 bp = strrchr(parent_dir.c_str(), '/');
1931 if (bp) {
1932 *bp = '\0';
1933 if (strlen(parent_dir.c_str())) {
1934 if (!GfapiMakedirs(p_ctx, parent_dir.c_str())) {
1935 rp->create_status = CF_ERROR;
1936 goto bail_out;
1937 }
1938 }
1939 }
1940 }
1941
1942 /*
1943 * See if we need to perform anything special for the restore file type.
1944 */
1945 switch (rp->type) {
1946 case FT_LNKSAVED:
1947 status = glfs_link(p_ctx->glfs, rp->olname, rp->ofname);
1948 if (status != 0) {
1949 BErrNo be;
1950
1951 Jmsg(ctx, M_ERROR, "gfapi-fd: glfs_link(%s) failed: %s\n",
1952 rp->ofname, be.bstrerror());
1953 rp->create_status = CF_ERROR;
1954 } else {
1955 rp->create_status = CF_CREATED;
1956 }
1957 break;
1958 case FT_LNK:
1959 status = glfs_symlink(p_ctx->glfs, rp->olname, rp->ofname);
1960 if (status != 0) {
1961 BErrNo be;
1962
1963 Jmsg(ctx, M_ERROR, "gfapi-fd: glfs_symlink(%s) failed: %s\n",
1964 rp->ofname, be.bstrerror());
1965 rp->create_status = CF_ERROR;
1966 } else {
1967 rp->create_status = CF_CREATED;
1968 }
1969 break;
1970 case FT_SPEC:
1971 status = glfs_mknod(p_ctx->glfs, rp->olname, rp->statp.st_mode,
1972 rp->statp.st_rdev);
1973 if (status != 0) {
1974 BErrNo be;
1975
1976 Jmsg(ctx, M_ERROR, "gfapi-fd: glfs_mknod(%s) failed: %s\n",
1977 rp->ofname, be.bstrerror());
1978 rp->create_status = CF_ERROR;
1979 } else {
1980 rp->create_status = CF_CREATED;
1981 }
1982 break;
1983 default:
1984 rp->create_status = CF_EXTRACT;
1985 break;
1986 }
1987 break;
1988 case FT_DIRBEGIN:
1989 case FT_DIREND:
1990 if (!GfapiMakedirs(p_ctx, rp->ofname)) {
1991 rp->create_status = CF_ERROR;
1992 } else {
1993 rp->create_status = CF_CREATED;
1994 }
1995 break;
1996 case FT_DELETED:
1997 Jmsg(ctx, M_INFO, 0,
1998 _("gfapi-fd: Original file %s have been deleted: type=%d\n"),
1999 rp->ofname, rp->type);
2000 rp->create_status = CF_SKIP;
2001 break;
2002 default:
2003 Jmsg(ctx, M_ERROR, 0,
2004 _("gfapi-fd: Unknown file type %d; not restored: %s\n"), rp->type,
2005 rp->ofname);
2006 rp->create_status = CF_ERROR;
2007 break;
2008 }
2009
2010 bail_out:
2011 return bRC_OK;
2012 }
2013
setFileAttributes(bpContext * ctx,struct restore_pkt * rp)2014 static bRC setFileAttributes(bpContext* ctx, struct restore_pkt* rp)
2015 {
2016 int status;
2017 struct timespec times[2];
2018 plugin_ctx* p_ctx = (plugin_ctx*)ctx->pContext;
2019
2020 if (!p_ctx) { return bRC_Error; }
2021
2022 /*
2023 * Restore uid and gid.
2024 */
2025 status =
2026 glfs_lchown(p_ctx->glfs, rp->ofname, rp->statp.st_uid, rp->statp.st_gid);
2027 if (status != 0) {
2028 BErrNo be;
2029
2030 Jmsg(ctx, M_ERROR, "gfapi-fd: glfs_lchown(%s) failed: %s\n", rp->ofname,
2031 be.bstrerror());
2032 return bRC_Error;
2033 }
2034
2035 /*
2036 * Restore mode.
2037 */
2038 status = glfs_chmod(p_ctx->glfs, rp->ofname, rp->statp.st_mode);
2039 if (status != 0) {
2040 BErrNo be;
2041
2042 Jmsg(ctx, M_ERROR, "gfapi-fd: glfs_chmod(%s) failed: %s\n", rp->ofname,
2043 be.bstrerror());
2044 return bRC_Error;
2045 }
2046
2047 /*
2048 * Restore access and modification times.
2049 */
2050 times[0].tv_sec = rp->statp.st_atime;
2051 times[0].tv_nsec = 0;
2052 times[1].tv_sec = rp->statp.st_mtime;
2053 times[1].tv_nsec = 0;
2054
2055 status = glfs_lutimens(p_ctx->glfs, rp->ofname, times);
2056 if (status != 0) {
2057 BErrNo be;
2058
2059 Jmsg(ctx, M_ERROR, "gfapi-fd: glfs_lutimens(%s) failed: %s\n", rp->ofname,
2060 be.bstrerror());
2061 return bRC_Error;
2062 }
2063
2064 return bRC_OK;
2065 }
2066
2067 /**
2068 * When using Incremental dump, all previous dumps are necessary
2069 */
checkFile(bpContext * ctx,char * fname)2070 static bRC checkFile(bpContext* ctx, char* fname)
2071 {
2072 plugin_ctx* p_ctx = (plugin_ctx*)ctx->pContext;
2073
2074 if (!p_ctx) { return bRC_Error; }
2075
2076 return bRC_OK;
2077 }
2078
2079 /**
2080 * Acls are saved using extended attributes.
2081 */
2082 static const char* xattr_acl_skiplist[3] = {"system.posix_acl_access",
2083 "system.posix_acl_default", NULL};
2084
serialize_acl_stream(PoolMem * buf,uint32_t expected_serialize_len,uint32_t offset,const char * acl_name,uint32_t acl_name_length,char * xattr_value,uint32_t xattr_value_length)2085 static inline uint32_t serialize_acl_stream(PoolMem* buf,
2086 uint32_t expected_serialize_len,
2087 uint32_t offset,
2088 const char* acl_name,
2089 uint32_t acl_name_length,
2090 char* xattr_value,
2091 uint32_t xattr_value_length)
2092 {
2093 ser_declare;
2094 uint32_t content_length;
2095 char* buffer;
2096
2097 /*
2098 * Make sure the serialized stream fits in the poolmem buffer.
2099 * We allocate some more to be sure the stream is gonna fit.
2100 */
2101 buf->check_size(offset + expected_serialize_len + 10);
2102
2103 buffer = buf->c_str() + offset;
2104 SerBegin(buffer, expected_serialize_len + 10);
2105
2106 /*
2107 * Encode the ACL name including the \0
2108 */
2109 ser_uint32(acl_name_length + 1);
2110 SerBytes(acl_name, acl_name_length + 1);
2111
2112 /*
2113 * Encode the actual ACL data as stored as XATTR.
2114 */
2115 ser_uint32(xattr_value_length);
2116 SerBytes(xattr_value, xattr_value_length);
2117
2118 SerEnd(buffer, expected_serialize_len + 10);
2119 content_length = SerLength(buffer);
2120
2121 return offset + content_length;
2122 }
2123
getAcl(bpContext * ctx,acl_pkt * ap)2124 static bRC getAcl(bpContext* ctx, acl_pkt* ap)
2125 {
2126 bool skip_xattr, abort_retrieval;
2127 int current_size;
2128 int32_t xattr_value_length;
2129 uint32_t content_length = 0;
2130 uint32_t expected_serialize_len;
2131 PoolMem xattr_value(PM_MESSAGE), serialized_acls(PM_MESSAGE);
2132 plugin_ctx* p_ctx = (plugin_ctx*)ctx->pContext;
2133
2134 if (!p_ctx) { return bRC_Error; }
2135
2136 abort_retrieval = false;
2137 for (int cnt = 0; xattr_acl_skiplist[cnt] != NULL; cnt++) {
2138 skip_xattr = false;
2139 while (1) {
2140 current_size = xattr_value.MaxSize();
2141 xattr_value_length =
2142 glfs_lgetxattr(p_ctx->glfs, ap->fname, xattr_acl_skiplist[cnt],
2143 xattr_value.c_str(), current_size);
2144 if (xattr_value_length < 0) {
2145 BErrNo be;
2146
2147 switch (errno) {
2148 #if defined(ENOATTR) || defined(ENODATA)
2149 #if defined(ENOATTR)
2150 case ENOATTR:
2151 #endif
2152 #if defined(ENODATA) && ENOATTR != ENODATA
2153 case ENODATA:
2154 #endif
2155 skip_xattr = true;
2156 break;
2157 #endif
2158 #if defined(ENOTSUP) || defined(EOPNOTSUPP)
2159 #if defined(ENOTSUP)
2160 case ENOTSUP:
2161 #endif
2162 #if defined(EOPNOTSUPP) && EOPNOTSUPP != ENOTSUP
2163 case EOPNOTSUPP:
2164 #endif
2165 abort_retrieval = true;
2166 break;
2167 #endif
2168 case ERANGE:
2169 /*
2170 * Not enough room in buffer double its size and retry.
2171 */
2172 xattr_value.check_size(current_size * 2);
2173 continue;
2174 default:
2175 Jmsg(ctx, M_ERROR, "gfapi-fd: glfs_lgetxattr(%s) failed: %s\n",
2176 ap->fname, be.bstrerror());
2177 return bRC_Error;
2178 }
2179 }
2180
2181 /*
2182 * Retrieved the xattr so break the loop.
2183 */
2184 break;
2185 }
2186
2187 if (abort_retrieval) { break; }
2188
2189 if (skip_xattr) { continue; }
2190
2191 /*
2192 * Serialize the data.
2193 */
2194 expected_serialize_len =
2195 strlen(xattr_acl_skiplist[cnt]) + xattr_value_length + 4;
2196 content_length = serialize_acl_stream(
2197 &serialized_acls, expected_serialize_len, content_length,
2198 xattr_acl_skiplist[cnt], strlen(xattr_acl_skiplist[cnt]),
2199 xattr_value.c_str(), xattr_value_length);
2200 }
2201
2202 if (content_length > 0) {
2203 ap->content = (char*)malloc(content_length);
2204 memcpy(ap->content, serialized_acls.c_str(), content_length);
2205 ap->content_length = content_length;
2206 }
2207
2208 return bRC_OK;
2209 }
2210
setAcl(bpContext * ctx,acl_pkt * ap)2211 static bRC setAcl(bpContext* ctx, acl_pkt* ap)
2212 {
2213 int status;
2214 unser_declare;
2215 uint32_t acl_name_length;
2216 uint32_t xattr_value_length;
2217 PoolMem xattr_value(PM_MESSAGE), acl_name(PM_MESSAGE);
2218
2219 plugin_ctx* p_ctx = (plugin_ctx*)ctx->pContext;
2220
2221 if (!p_ctx) { return bRC_Error; }
2222
2223 UnserBegin(ap->content, ap->content_length);
2224 while (UnserLength(ap->content) < ap->content_length) {
2225 unser_uint32(acl_name_length);
2226
2227 /*
2228 * Decode the ACL name including the \0
2229 */
2230 acl_name.check_size(acl_name_length);
2231 UnserBytes(acl_name.c_str(), acl_name_length);
2232
2233 unser_uint32(xattr_value_length);
2234
2235 /*
2236 * Decode the actual ACL data as stored as XATTR.
2237 */
2238 xattr_value.check_size(xattr_value_length);
2239 UnserBytes(xattr_value.c_str(), xattr_value_length);
2240
2241 status = glfs_lsetxattr(p_ctx->glfs, ap->fname, acl_name.c_str(),
2242 xattr_value.c_str(), xattr_value_length, 0);
2243 if (status < 0) {
2244 BErrNo be;
2245
2246 Jmsg(ctx, M_ERROR, "gfapi-fd: glfs_lsetxattr(%s) failed: %s\n", ap->fname,
2247 be.bstrerror());
2248 return bRC_Error;
2249 }
2250 }
2251
2252 UnserEnd(ap->content, ap->content_length);
2253
2254 return bRC_OK;
2255 }
2256
getXattr(bpContext * ctx,xattr_pkt * xp)2257 static bRC getXattr(bpContext* ctx, xattr_pkt* xp)
2258 {
2259 char* bp;
2260 bool skip_xattr;
2261 int status, current_size;
2262 int32_t xattr_value_length;
2263 PoolMem xattr_value(PM_MESSAGE);
2264 plugin_ctx* p_ctx = (plugin_ctx*)ctx->pContext;
2265
2266 if (!p_ctx) { return bRC_Error; }
2267
2268 /*
2269 * See if we need to retrieve the xattr list.
2270 */
2271 if (!p_ctx->processing_xattr) {
2272 while (1) {
2273 current_size = SizeofPoolMemory(p_ctx->xattr_list);
2274 status = glfs_llistxattr(p_ctx->glfs, xp->fname, p_ctx->xattr_list,
2275 current_size);
2276 if (status < 0) {
2277 BErrNo be;
2278
2279 switch (errno) {
2280 #if defined(ENOTSUP) || defined(EOPNOTSUPP)
2281 #if defined(ENOTSUP)
2282 case ENOTSUP:
2283 #endif
2284 #if defined(EOPNOTSUPP) && EOPNOTSUPP != ENOTSUP
2285 case EOPNOTSUPP:
2286 #endif
2287 return bRC_OK;
2288 #endif
2289 case ERANGE:
2290 /*
2291 * Not enough room in buffer double its size and retry.
2292 */
2293 p_ctx->xattr_list =
2294 CheckPoolMemorySize(p_ctx->xattr_list, current_size * 2);
2295 continue;
2296 default:
2297 Jmsg(ctx, M_ERROR, "gfapi-fd: glfs_llistxattr(%s) failed: %s\n",
2298 xp->fname, be.bstrerror());
2299 return bRC_Error;
2300 }
2301 } else if (status == 0) {
2302 /*
2303 * Nothing to do.
2304 */
2305 return bRC_OK;
2306 }
2307
2308 /*
2309 * Retrieved the xattr list so break the loop.
2310 */
2311 break;
2312 }
2313
2314 /*
2315 * Data from llistxattr is in the following form:
2316 *
2317 * user.name1\0system.name1\0user.name2\0
2318 *
2319 * We add an extra \0 at the end so we have an unique terminator
2320 * to know when we hit the end of the list.
2321 */
2322 p_ctx->xattr_list = CheckPoolMemorySize(p_ctx->xattr_list, status + 1);
2323 p_ctx->xattr_list[status] = '\0';
2324 p_ctx->next_xattr_name = p_ctx->xattr_list;
2325 p_ctx->processing_xattr = true;
2326 }
2327
2328 while (1) {
2329 /*
2330 * On some OSes you also get the acls in the extented attribute list.
2331 * So we check if we are already backing up acls and if we do we
2332 * don't store the extended attribute with the same info.
2333 */
2334 skip_xattr = false;
2335 if (BitIsSet(FO_ACL, p_ctx->flags)) {
2336 for (int cnt = 0; xattr_acl_skiplist[cnt] != NULL; cnt++) {
2337 if (bstrcmp(p_ctx->next_xattr_name, xattr_acl_skiplist[cnt])) {
2338 skip_xattr = true;
2339 break;
2340 }
2341 }
2342 }
2343
2344 if (!skip_xattr) {
2345 current_size = xattr_value.MaxSize();
2346 xattr_value_length =
2347 glfs_lgetxattr(p_ctx->glfs, xp->fname, p_ctx->next_xattr_name,
2348 xattr_value.c_str(), current_size);
2349 if (xattr_value_length < 0) {
2350 BErrNo be;
2351
2352 switch (errno) {
2353 #if defined(ENOATTR) || defined(ENODATA)
2354 #if defined(ENOATTR)
2355 case ENOATTR:
2356 #endif
2357 #if defined(ENODATA) && ENOATTR != ENODATA
2358 case ENODATA:
2359 #endif
2360 skip_xattr = true;
2361 break;
2362 #endif
2363 #if defined(ENOTSUP) || defined(EOPNOTSUPP)
2364 #if defined(ENOTSUP)
2365 case ENOTSUP:
2366 #endif
2367 #if defined(EOPNOTSUPP) && EOPNOTSUPP != ENOTSUP
2368 case EOPNOTSUPP:
2369 #endif
2370 skip_xattr = true;
2371 break;
2372 #endif
2373 case ERANGE:
2374 /*
2375 * Not enough room in buffer double its size and retry.
2376 */
2377 xattr_value.check_size(current_size * 2);
2378 continue;
2379 default:
2380 Jmsg(ctx, M_ERROR, "gfapi-fd: glfs_lgetxattr(%s) failed: %s\n",
2381 xp->fname, be.bstrerror());
2382 return bRC_Error;
2383 }
2384 }
2385
2386 /*
2387 * Retrieved the xattr so break the loop.
2388 */
2389 break;
2390 } else {
2391 /*
2392 * No data to retrieve so break the loop.
2393 */
2394 break;
2395 }
2396 }
2397
2398 if (!skip_xattr) {
2399 xp->name = strdup(p_ctx->next_xattr_name);
2400 xp->name_length = strlen(xp->name) + 1;
2401 xp->value = (char*)malloc(xattr_value_length);
2402 memcpy(xp->value, xattr_value.c_str(), xattr_value_length);
2403 xp->value_length = xattr_value_length;
2404 }
2405
2406 /*
2407 * See if there are more xattr to process.
2408 */
2409 bp = strchr(p_ctx->next_xattr_name, '\0');
2410 if (bp) {
2411 bp++;
2412 if (*bp != '\0') {
2413 p_ctx->next_xattr_name = bp;
2414 return bRC_More;
2415 }
2416 }
2417
2418 /*
2419 * No more reset processing_xattr flag.
2420 */
2421 p_ctx->processing_xattr = false;
2422 return bRC_OK;
2423 }
2424
setXattr(bpContext * ctx,xattr_pkt * xp)2425 static bRC setXattr(bpContext* ctx, xattr_pkt* xp)
2426 {
2427 int status;
2428 plugin_ctx* p_ctx = (plugin_ctx*)ctx->pContext;
2429
2430 if (!p_ctx) { return bRC_Error; }
2431
2432 status = glfs_lsetxattr(p_ctx->glfs, xp->fname, xp->name, xp->value,
2433 xp->value_length, 0);
2434 if (status < 0) {
2435 BErrNo be;
2436
2437 Jmsg(ctx, M_ERROR, "gfapi-fd: glfs_lsetxattr(%s) failed: %s\n", xp->fname,
2438 be.bstrerror());
2439 return bRC_Error;
2440 }
2441
2442 return bRC_OK;
2443 }
2444 } /* namespace filedaemon */
2445