1 /*
2    Bacula(R) - The Network Backup Solution
3 
4    Copyright (C) 2000-2020 Kern Sibbald
5 
6    The original author of Bacula is Kern Sibbald, with contributions
7    from many others, a complete list can be found in the file AUTHORS.
8 
9    You may use this file and others of this release according to the
10    license defined in the LICENSE file, which includes the Affero General
11    Public License, v3.0 ("AGPLv3") and some additional permissions and
12    terms pursuant to its AGPLv3 Section 7.
13 
14    This notice must be preserved when any source code is
15    conveyed and/or propagated.
16 
17    Bacula(R) is a registered trademark of Kern Sibbald.
18  */
19 /**
20  * Major refactoring of XATTR code written by:
21  *
22  *  Radosław Korzeniewski, MMXVI
23  *  radoslaw@korzeniewski.net, radekk@inteos.pl
24  *  Inteos Sp. z o.o. http://www.inteos.pl/
25  *
26  */
27 
28 #include "bacula.h"
29 #include "filed.h"
30 #include "bxattr_freebsd.h"
31 
32 #if defined(HAVE_FREEBSD_OS)
33 
34 /* check if XATTR support is enabled */
35 #if defined(HAVE_XATTR)
36 
37 /*
38  * Define the supported XATTR streams for this OS
39  */
40 static const int os_xattr_streams[] = {
41    STREAM_XACL_FREEBSD_XATTR,
42    0
43 };
44 
45 static const int os_xattr_namespaces[] = {
46    EXTATTR_NAMESPACE_USER,
47    EXTATTR_NAMESPACE_SYSTEM,
48    -1
49 };
50 
51 static const char *os_xattr_acl_skiplist[] = {
52    "system.posix1e.acl_access",
53    "system.posix1e.acl_default",
54    "system.nfs4.acl",
55    NULL
56 };
57 
58 static const char *os_xattr_skiplist[] = {
59    NULL
60 };
61 
62 /*
63  * OS specific constructor
64  */
BXATTR_FreeBSD()65 BXATTR_FreeBSD::BXATTR_FreeBSD()
66 {
67    set_xattr_streams(os_xattr_streams);
68    set_xattr_skiplists(os_xattr_skiplist, os_xattr_acl_skiplist);
69 };
70 
71 /*
72  * Perform OS specific extended attribute backup
73  *
74  * in/out - check API at bxattr.h
75  */
os_backup_xattr(JCR * jcr,FF_PKT * ff_pkt)76 bRC_BXATTR BXATTR_FreeBSD::os_backup_xattr (JCR *jcr, FF_PKT *ff_pkt){
77 
78    bRC_BXATTR rc;
79    POOLMEM *xlist;
80    uint32_t xlen;
81    char *name;
82    uint32_t name_len;
83    POOLMEM *value;
84    uint32_t value_len;
85    POOLMEM *name_gen;
86    uint32_t name_gen_len;
87    char * namespace_str;
88    int namespace_len;
89    bool skip;
90    alist *xattr_list = NULL;
91    int xattr_count = 0;
92    uint32_t len = 0;
93    BXATTR_xattr *xattr;
94    int a;
95 
96    for (a = 0; os_xattr_namespaces[a] != -1; a++){ // loop through all available namespaces
97       /* xlist is allocated as POOLMEM by os_get_xattr_names_local */
98       rc = os_get_xattr_names_local(jcr, os_xattr_namespaces[a], &xlist, &xlen);
99       switch (rc){
100          case bRC_BXATTR_ok:
101             /* it's ok, so go further */
102             break;
103          case bRC_BXATTR_skip:
104          case bRC_BXATTR_cont:
105             /* no xattr available, so skip rest of it */
106             return bRC_BXATTR_ok;
107          default:
108             return rc;
109       }
110 
111       /* get a string representation of the namespace */
112       if (extattr_namespace_to_string(os_xattr_namespaces[a], &namespace_str) != 0){
113          Mmsg2(jcr->errmsg, _("Failed to convert %d into namespace on file \"%s\"\n"), os_xattr_namespaces[a], jcr->last_fname);
114          Dmsg2(100, "Failed to convert %d into namespace on file \"%s\"\n", os_xattr_namespaces[a], jcr->last_fname);
115          goto bail_out;
116       }
117       namespace_len = strlen(namespace_str);
118 
119       /* follow the list of xattr names and get the values */
120       for (name = xlist; (name - xlist) + 1 < xlen; name = strchr(name, '\0') + 1){
121          name_len = strlen(name);
122          name_gen = get_pool_memory(PM_FNAME);
123          name_gen = check_pool_memory_size(name_gen, name_len + namespace_len + 2);
124          bsnprintf(name_gen, name_len + namespace_len + 2, "%s.%s", namespace_str, name);
125          name_gen_len = strlen(name_gen);
126 
127          skip =  check_xattr_skiplists(jcr, ff_pkt, name_gen);
128          if (skip || name_len == 0){
129             Dmsg1(100, "Skipping xattr named %s\n", name_gen);
130             continue;
131          }
132 
133          /* value is allocated as POOLMEM by os_get_xattr_value_local */
134          rc = os_get_xattr_value_local(jcr, os_xattr_namespaces[a], name, &value, &value_len);
135          switch (rc){
136             case bRC_BXATTR_ok:
137                /* it's ok, so go further */
138                break;
139             case bRC_BXATTR_skip:
140                /* no xattr available, so skip rest of it */
141                rc = bRC_BXATTR_ok;
142                goto bail_out;
143             default:
144                /* error / fatal */
145                goto bail_out;
146          }
147 
148          /*
149           * we have a name of the extended attribute in the name variable
150           * and value of the extended attribute in the value variable
151           * so we need to build a list
152           */
153          xattr = (BXATTR_xattr*)malloc(sizeof(BXATTR_xattr));
154          xattr->name_len = name_gen_len;
155          xattr->name = name_gen;
156          xattr->value_len = value_len;
157          xattr->value = value;
158          /*       magic              name_len          name        value_len       value */
159          len += sizeof(uint32_t) + sizeof(uint32_t) + name_gen_len + sizeof(uint32_t) + value_len;
160 
161          if (xattr_list == NULL){
162             xattr_list = New(alist(10, not_owned_by_alist));
163          }
164          xattr_list->append(xattr);
165          xattr_count++;
166       }
167       if (xattr_count > 0){
168          /* serialize the stream */
169          rc = serialize_xattr_stream(jcr, len, xattr_list);
170          if (rc != bRC_BXATTR_ok){
171             Mmsg(jcr->errmsg, _("Failed to serialize extended attributes on file \"%s\"\n"), jcr->last_fname);
172             Dmsg1(100, "Failed to serialize extended attributes on file \"%s\"\n", jcr->last_fname);
173             goto bail_out;
174          } else {
175             /* send data to SD */
176             rc = send_xattr_stream(jcr, STREAM_XACL_FREEBSD_XATTR);
177          }
178       } else {
179          rc = bRC_BXATTR_ok;
180       }
181    }
182 bail_out:
183    /* free allocated data */
184    if (xattr_list != NULL){
185       foreach_alist(xattr, xattr_list){
186          if (xattr == NULL){
187             break;
188          }
189          if (xattr->name){
190             free_pool_memory(name_gen);
191          }
192          if (xattr->value){
193             free(xattr->value);
194          }
195          free(xattr);
196       }
197       delete xattr_list;
198    }
199    if (xlist != NULL){
200       free(xlist);
201    }
202 
203    return rc;
204 };
205 
206 /*
207  * Perform OS specific XATTR restore. Runtime is called only when stream is supported by OS.
208  *
209  * in/out - check API at bxattr.h
210  */
os_restore_xattr(JCR * jcr,int stream,char * content,uint32_t length)211 bRC_BXATTR BXATTR_FreeBSD::os_restore_xattr (JCR *jcr, int stream, char *content, uint32_t length){
212    return generic_restore_xattr(jcr, stream);
213 };
214 
215 /*
216  * Return a list of xattr names in newly allocated pool memory and a length of the allocated buffer.
217  * It allocates a memory with poolmem subroutines every time a function is called, so it must be freed
218  * when not needed.
219  *
220  * in/out - check API at bxattr.h
221  *
222  * As a FreeBSD uses a different attributes name schema/format then this method is a very different
223  * from a standard generic method because it uses a namespace (ns) value for os dependent optimization.
224  */
os_get_xattr_names_local(JCR * jcr,int ns,POOLMEM ** pxlist,uint32_t * xlen)225 bRC_BXATTR BXATTR_FreeBSD::os_get_xattr_names_local (JCR *jcr, int ns, POOLMEM ** pxlist, uint32_t * xlen){
226 
227    int len;
228    POOLMEM * list;
229    int a;
230    int stra;
231    POOLMEM * genlist;
232 
233    /* check input data */
234    if (jcr == NULL || xlen == NULL || pxlist == NULL){
235       return bRC_BXATTR_inval;
236    }
237    /* get the length of the extended attributes */
238    len = extattr_list_link(jcr->last_fname, ns, NULL, 0);
239    switch (len){
240       case -1: {
241          berrno be;
242 
243          switch (errno){
244             case ENOENT:
245                /* no file available, skip it */
246                return bRC_BXATTR_skip;
247             case EOPNOTSUPP:
248                /* no xattr supported on filesystem, clear a flag and skip it */
249                clear_flag(BXATTR_FLAG_NATIVE);
250                set_content(NULL);
251                return bRC_BXATTR_skip;
252             case EPERM:
253                if (ns == EXTATTR_NAMESPACE_SYSTEM){
254                   return bRC_BXATTR_cont;
255                } /* else show error */
256             default:
257                Mmsg2(jcr->errmsg, _("extattr_list_link error on file \"%s\": ERR=%s\n"), jcr->last_fname, be.bstrerror());
258                Dmsg2(100, "extattr_list_link error file=%s ERR=%s\n", jcr->last_fname, be.bstrerror());
259                return bRC_BXATTR_error;
260          }
261          break;
262       }
263       case 0:
264          /* xattr available but empty, skip it */
265          return bRC_BXATTR_skip;
266       default:
267          break;
268    }
269 
270    /*
271     * allocate memory for the extented attribute list
272     * default size is a 4k for PM_BSOCK, which should be sufficient on almost all
273     * Linux system where xattrs a limited in size to single filesystem block ~4kB
274     * so we need to check required size
275     */
276    list = get_pool_memory(PM_BSOCK);
277    list = check_pool_memory_size(list, len + 1);
278    memset(list, 0, len + 1);
279 
280    /* get the list of extended attributes names for a file */
281    len = extattr_list_link(jcr->last_fname, ns, list, len);
282    switch (len){
283    case -1: {
284       berrno be;
285 
286       switch (errno){
287       case ENOENT:
288          /* no file available, skip it, first release allocated memory */
289          free_pool_memory(list);
290          return bRC_BXATTR_skip;
291          case EPERM:
292             if (ns == EXTATTR_NAMESPACE_SYSTEM){
293                return bRC_BXATTR_cont;
294             } /* else show error */
295       default:
296          Mmsg2(jcr->errmsg, _("extattr_list_link error on file \"%s\": ERR=%s\n"), jcr->last_fname, be.bstrerror());
297          Dmsg2(100, "extattr_list_link error file=%s ERR=%s\n", jcr->last_fname, be.bstrerror());
298          free_pool_memory(list);
299          return bRC_BXATTR_error;
300       }
301       break;
302    }
303    default:
304       break;
305    }
306    /* convert FreeBSD list type to the generic one */
307    genlist = get_pool_memory(PM_BSOCK);
308    genlist = check_pool_memory_size(genlist, len + 1);
309    memset(genlist, 0, len + 1);
310    for (a = 0; a < len; a += list[a] + 1){
311       stra = list[a];
312       memcpy(genlist + a, list + a + 1, stra);
313       genlist[a + stra] = '\0';
314    }
315    free_pool_memory(list);
316    /* setup return data */
317    *pxlist = genlist;
318    *xlen = len;
319    return bRC_BXATTR_ok;
320 };
321 
322 /*
323  * Return a value of the requested attribute name and a length of the allocated buffer.
324  * It allocates a memory with poolmem subroutines every time a function is called, so it must be freed
325  * when not needed.
326  *
327  * in/out - check API at bxattr.h
328  *
329  * As a FreeBSD uses a different attributes name schema/format then this method is a very different
330  * from a standard generic method because it uses a namespace (ns) value for os dependent optimization.
331  */
os_get_xattr_value_local(JCR * jcr,int ns,char * name,char ** pvalue,uint32_t * plen)332 bRC_BXATTR BXATTR_FreeBSD::os_get_xattr_value_local (JCR *jcr, int ns, char * name, char ** pvalue, uint32_t * plen){
333 
334    int len;
335    POOLMEM * value;
336 
337    /* check input data */
338    if (jcr == NULL || name == NULL || plen == NULL || pvalue == NULL){
339       return bRC_BXATTR_inval;
340    }
341    /* get the length of the value for extended attribute */
342    len = extattr_get_link(jcr->last_fname, ns, name, NULL, 0);
343    switch (len){
344       case -1: {
345          berrno be;
346 
347          switch (errno){
348             case ENOENT:
349                /* no file available, skip it */
350                return bRC_BXATTR_skip;
351             default:
352                /* XXX: what about ENOATTR error value? */
353                Mmsg2(jcr->errmsg, _("extattr_get_link error on file \"%s\": ERR=%s\n"), jcr->last_fname, be.bstrerror());
354                Dmsg2(100, "extattr_get_link error file=%s ERR=%s\n", jcr->last_fname, be.bstrerror());
355                return bRC_BXATTR_error;
356          }
357          break;
358       }
359       default:
360          break;
361    }
362 
363    if (len > 0){
364       /*
365        * allocate memory for the extented attribute value
366        * default size is a 256B for PM_MESSAGE, so we need to check required size
367        */
368       value = get_pool_memory(PM_MESSAGE);
369       value = check_pool_memory_size(value, len + 1);
370       memset(value, 0, len + 1);
371       /* value is not empty, get a data */
372       len = extattr_get_link(jcr->last_fname, ns, name, value, len);
373       switch (len){
374       case -1: {
375          berrno be;
376 
377          switch (errno){
378          case ENOENT:
379             /* no file available, skip it, first release allocated memory */
380             free_pool_memory(value);
381             return bRC_BXATTR_skip;
382          default:
383             Mmsg2(jcr->errmsg, _("extattr_get_link error on file \"%s\": ERR=%s\n"), jcr->last_fname, be.bstrerror());
384             Dmsg2(100, "extattr_get_link error file=%s ERR=%s\n", jcr->last_fname, be.bstrerror());
385             free_pool_memory(value);
386             return bRC_BXATTR_error;
387          }
388          break;
389       }
390       default:
391          break;
392       }
393       /* ensure a value is nul terminated */
394       value[len] = '\0';
395    } else {
396       /* empty value */
397       value = NULL;
398       len = 0;
399    }
400    /* setup return data */
401    *pvalue = value;
402    *plen = len;
403    return bRC_BXATTR_ok;
404 };
405 
406 /*
407  * Low level OS specific runtime to set extended attribute on file
408  *
409  * in/out - check API at bxattr.h
410  *
411  * xattr->name should be in '<namespace>.<name>' format which
412  * function handle without problem, otherwise it returns an error
413  * TODO: it is possible to handle a different attributes name format
414  * for OS portability where default namespace 'user' can be used
415  */
os_set_xattr(JCR * jcr,BXATTR_xattr * xattr)416 bRC_BXATTR BXATTR_FreeBSD::os_set_xattr (JCR *jcr, BXATTR_xattr *xattr){
417 
418    char * name;
419    char * nspace;
420    int ns;
421    int rc;
422 
423    /* check input data */
424    if (jcr == NULL || xattr == NULL){
425       return bRC_BXATTR_inval;
426    }
427 
428    /* search for attribute namespace which is distinguished from attribute name by a dot '.' character */
429    if ((name = strchr(xattr->name, '.')) == (char *)NULL){
430       Mmsg2(jcr->errmsg, _("Failed to split %s into namespace and name part on file \"%s\"\n"), xattr->name, jcr->last_fname);
431       Dmsg2(100, "Failed to split %s into namespace and name part on file \"%s\"\n", xattr->name, jcr->last_fname);
432       return bRC_BXATTR_error;
433    }
434 
435    /* split namespace and name of the attribute */
436    nspace = xattr->name;
437    *name++ = '\0';
438 
439    /* check if namespace is valid on this system */
440    if (extattr_string_to_namespace(nspace, &ns) != 0){
441       Mmsg2(jcr->errmsg, _("Failed to convert %s into namespace on file \"%s\"\n"), nspace, jcr->last_fname);
442       Dmsg2(100, "Failed to convert %s into namespace on file \"%s\"\n", nspace, jcr->last_fname);
443       return bRC_BXATTR_error;
444    }
445 
446    /* set extattr on file */
447    rc = extattr_set_link(jcr->last_fname, ns, name, xattr->value, xattr->value_len);
448    if (rc < 0 || rc != (int)xattr->value_len){
449       berrno be;
450 
451       switch (errno){
452       case ENOENT:
453          break;
454       default:
455          Mmsg2(jcr->errmsg, _("extattr_set_link error on file \"%s\": ERR=%s\n"), jcr->last_fname, be.bstrerror());
456          Dmsg2(100, "extattr_set_link error file=%s ERR=%s\n", jcr->last_fname, be.bstrerror());
457          return bRC_BXATTR_error;
458       }
459    }
460    return bRC_BXATTR_ok;
461 };
462 
463 #endif /* HAVE_XATTR */
464 
465 #endif /* HAVE_FREEBSD_OS */
466