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