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