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