unison

Fork of Unison, a bi-directional file synchronization tool
git clone git://git.laack.co/unison.git
Log | Files | Refs | README | LICENSE

props_xattr.c (21175B)


      1 /* Unison file synchronizer: src/props_xattr.c */
      2 /* Copyright 2020-2022, Tõivo Leedjärv
      3 
      4     This program is free software: you can redistribute it and/or modify
      5     it under the terms of the GNU General Public License as published by
      6     the Free Software Foundation, either version 3 of the License, or
      7     (at your option) any later version.
      8 
      9     This program is distributed in the hope that it will be useful,
     10     but WITHOUT ANY WARRANTY; without even the implied warranty of
     11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12     GNU General Public License for more details.
     13 
     14     You should have received a copy of the GNU General Public License
     15     along with this program.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17 
     18 /* Conceptually, here, an extended attribute is just a name-value pair,
     19  * where name is a text string and value is a binary string. This matches
     20  * well the concept of extended attributes on some platforms (notably,
     21  * Linux and BSDs), while some platforms provide more sophisticated
     22  * extended attributes.
     23  *
     24  * The external interface is defined as follows. Every supported platform
     25  * must implement this interface. xattr format can be platform-specific,
     26  * which may prevent cross-platform synchronization but still allows
     27  * synchronization within the platform. Cross-platform synchronization may
     28  * still be possible in some cases, even if one platform will not
     29  * understand the xattrs; the attribute values are treated as blobs then.
     30  *
     31  *
     32  * SET the value of one xattr
     33  * ==========================
     34  * unit unison_xattr_set(String path, String xattrname, String xattrvalue)
     35  *
     36  *   Create the requested extended attribute on the requested file or
     37  *   directory and set the attribute value.
     38  *   If the attribute already exists then its value is overwritten.
     39  *   Symbolic links are followed.
     40  *
     41  * Input parameters
     42  *   path       - absolute path of a file or directory
     43  *   xattrname  - name of attribute to set on the path
     44  *   xattrvalue - value of attribute to set on the path
     45  *
     46  * Return value
     47  *   No return value.
     48  *
     49  * Exceptions
     50  *   There are no mandatory exception conditions.
     51  *   OCaml exception defined by macro UNSN_XATTR_NOT_SUPPORTED_EX
     52  *   MAY be raised when extended attributes are not supported on
     53  *   the requested path.
     54  *   Failure MAY voluntarily be raised for example when:
     55  *     Can't access file to set the attribute
     56  *     Error creating the attribute (invalid name, permission error, etc.)
     57  *     Error setting the attribute value
     58  *
     59  *
     60  * REMOVE one xattr
     61  * ================
     62  * unit unison_xattr_remove(String path, String xattrname)
     63  *
     64  *   Remove the requested extended attribute on the requested file or
     65  *   directory. Symbolic links are followed.
     66  *
     67  * Input parameters
     68  *   path      - absolute path of a file or directory
     69  *   xattrname - name of attribute to remove on the path
     70  *
     71  * Return value
     72  *   No return value.
     73  *
     74  * Exceptions
     75  *   There are no mandatory exception conditions.
     76  *   OCaml exception defined by macro UNSN_XATTR_NOT_SUPPORTED_EX
     77  *   MAY be raised when extended attributes are not supported on
     78  *   the requested path.
     79  *   Failure MAY voluntarily be raised for example when:
     80  *     Can't access file to remove the attribute
     81  *     Error removing the attribute
     82  *
     83  *
     84  * GET the value of one xattr
     85  * ==========================
     86  * String unison_xattr_get(String path, String xattrname)
     87  *
     88  *   Get the value of the requested extended attribute on the requested
     89  *   file or directory. The entire value is returned in full length.
     90  *   Symbolic links are followed.
     91  *
     92  * Input parameters
     93  *   path      - absolute path of a file or directory
     94  *   xattrname - name of attribute to get on the path
     95  *
     96  * Return value
     97  *   The value of the requested extended attribute, as a binary string.
     98  *
     99  * Exceptions
    100  *   OCaml exception defined by macro UNSN_XATTR_NOT_SUPPORTED_EX
    101  *   MAY be raised when extended attributes are not supported on
    102  *   the requested path.
    103  *   Failure MUST be raised when:
    104  *     The attribute value does not fit in an OCaml string on a 32-bit
    105  *     platform (approx. 16 MB)
    106  *     Can't access file to read the attribute
    107  *     Error reading the attribute value or attribute not found
    108  *
    109  *
    110  * GET the list of xattrs with value lengths
    111  * =========================================
    112  * List of (String * Int) unison_xattrs_list(String path)
    113  *
    114  *   Get the list of all extended attributes on the requested file or
    115  *   directory. Attributes names are returned together with the length
    116  *   of attribute values.
    117  *   Attributes in the list can be returned in any order and the order
    118  *   does not have to be stable (i.e. it can be different on every
    119  *   invocation on the same path).
    120  *   Symbolic links are followed.
    121  *
    122  * Input parameters
    123  *   path - absolute path of a file or directory
    124  *
    125  * Return value
    126  *   The list of name-length pairs, with each pair representing the
    127  *   name and length of value of one extended attribute.
    128  *
    129  * Exceptions
    130  *   OCaml exception defined by macro UNSN_XATTR_NOT_SUPPORTED_EX
    131  *   MUST be raised when extended attributes are not supported on
    132  *   the requested path, or should not otherwise be returned.
    133  *   Failure MAY voluntarily be raised for example when:
    134  *     Can't access file to get the attributes
    135  *     Error reading attribute values
    136  *
    137  *
    138  * Indicate platform/system capabilities
    139  * =====================================
    140  * Boolean unison_xattr_updates_ctime()
    141  *
    142  *   Indicate whether the platform/system updates file ctime when
    143  *   extended attributes change on the file. Not all platforms do this
    144  *   (Solaris/illumos are known to not update any stat times of the
    145  *   file/directory when its extended attributes are modified).
    146  *   The capabilities of the system are important to know because
    147  *   detecting updates quickly yet correctly relies on knowing when to
    148  *   get the list of xattrs with values.
    149  *
    150  * Input parameters
    151  *   none
    152  *
    153  * Return value
    154  *   True if file ctime is updated at xattr changes. False otherwise.
    155  *   For a platform that does not support ctime, true can be returned
    156  *   if xattr changes update file mtime.
    157  *
    158  * Exceptions
    159  *   none
    160  *
    161  */
    162 
    163 #include <caml/memory.h>
    164 #include <caml/alloc.h>
    165 #include <caml/fail.h>
    166 #include <caml/callback.h>
    167 
    168 
    169 #if defined(sun) || defined(__sun)  /* Solarish, all illumos-based OS,   */
    170 #define __Solaris__                 /* OpenIndiana, OmniOS, SmartOS, ... */
    171 #endif
    172 
    173 #undef UNSN_HAS_XATTR
    174 #if defined(__Solaris__) || defined(__FreeBSD__) || defined(__NetBSD__) \
    175     || defined(__APPLE__) || defined(__linux)
    176 #define UNSN_HAS_XATTR
    177 #endif
    178 
    179 #ifndef O_CLOEXEC
    180 #define O_CLOEXEC 0
    181 #endif
    182 
    183 #define UNSN_XATTR_NOT_SUPPORTED_EX "XattrNotSupported"
    184 
    185 
    186 static void unsn_xattr_not_supported()
    187 {
    188   static const value *ex = NULL;
    189 
    190   if (ex == NULL) {
    191     ex = caml_named_value(UNSN_XATTR_NOT_SUPPORTED_EX);
    192   }
    193 
    194   caml_raise_constant(*ex);
    195 }
    196 
    197 
    198 #ifndef UNSN_HAS_XATTR
    199 
    200 CAMLprim void unison_xattr_set(value path, value xattrname, value xattr)
    201 {
    202   unsn_xattr_not_supported();
    203 }
    204 
    205 CAMLprim void unison_xattr_remove(value path, value xattrname)
    206 {
    207   unsn_xattr_not_supported();
    208 }
    209 
    210 CAMLprim void unison_xattr_get(value path, value xattrname)
    211 {
    212   unsn_xattr_not_supported();
    213 }
    214 
    215 CAMLprim void unison_xattrs_list(value path)
    216 {
    217   unsn_xattr_not_supported();
    218 }
    219 
    220 CAMLprim value unison_xattr_updates_ctime(value unit)
    221 {
    222   CAMLparam0();
    223   CAMLreturn(Val_true);
    224 }
    225 
    226 #else /* UNSN_HAS_XATTR */
    227 
    228 
    229 #if defined(__Solaris__)
    230 #ifndef _ATFILE_SOURCE
    231 #define _ATFILE_SOURCE 1
    232 #endif
    233 #include <errno.h>
    234 #include <sys/types.h>
    235 #include <sys/stat.h>
    236 #include <fcntl.h>
    237 #include <dirent.h>
    238 #include <unistd.h>
    239 #include <string.h>
    240 #include <stdio.h>
    241 #endif
    242 
    243 #if defined(__FreeBSD__) || defined(__NetBSD__)
    244 #include <errno.h>
    245 #include <sys/types.h>
    246 #include <sys/extattr.h>
    247 #include <string.h>
    248 #include <stdio.h>
    249 #endif
    250 
    251 #if defined(__FreeBSD__)
    252 #define ENOTSUP EOPNOTSUPP
    253 #endif
    254 
    255 #if defined(__APPLE__)
    256 #include <errno.h>
    257 #include <sys/xattr.h>
    258 #include <string.h>
    259 #include <stdio.h>
    260 #endif
    261 
    262 #if defined(__linux)
    263 #include <errno.h>
    264 #include <sys/types.h>
    265 #include <sys/xattr.h>
    266 #include <string.h>
    267 #include <stdio.h>
    268 
    269 /* Attribute names on Linux must be mangled to make cross-platform
    270  * synchronization possible. When listing attributes, the "user."
    271  * prefix is removed for user namespace attributes and an "!" is
    272  * prepended to attribute names in all other namespaces (or more
    273  * accurately, it is prepended to the namespace name).
    274  *
    275  * When feeding the attribute names to get, set, remove and other
    276  * syscalls, the reverse is done. */
    277 
    278 #define XN_BUF_LEN 261
    279 #define XN_LEN (XN_BUF_LEN - 6)
    280 
    281 static value val_of_attrname(char *attrname)
    282 {
    283   char buf[XN_BUF_LEN] = "!";
    284 
    285   if (attrname != NULL) {
    286     if (strncmp(attrname, "user.", 5) == 0) {
    287       attrname += 5;
    288     } else {
    289       attrname = strncat(buf, attrname, XN_LEN);
    290     }
    291   }
    292 
    293   return caml_copy_string(attrname != NULL ? attrname : "");
    294 }
    295 
    296 static const char *attrname_of_val(const char *attrname, char *buf)
    297 {
    298   if (attrname != NULL) {
    299     if (attrname[0] == '!') {
    300       return attrname + 1;
    301     } else {
    302       return strncat(strcpy(buf, "user."), attrname, XN_LEN);
    303     }
    304   } else {
    305     return attrname;
    306   }
    307 }
    308 #endif /* defined(__linux) */
    309 
    310 
    311 #if defined(__linux)
    312 
    313 #define XATTRNAME_VAL(a, n) char xnb_[XN_BUF_LEN];\
    314                             const char *a = attrname_of_val(String_val(n), xnb_)
    315 #define VAL_XATTRNAME val_of_attrname
    316 
    317 #else
    318 
    319 #define XATTRNAME_VAL(a, n) const char *a = String_val(n)
    320 #define VAL_XATTRNAME caml_copy_string
    321 
    322 #endif /* defined(__linux) */
    323 
    324 
    325 static void unsn_xattr_fail(const char *fmtmsg)
    326 {
    327   char buf[512];
    328   char *errmsg;
    329 
    330 #if defined(_WIN32)
    331   errmsg = strerror(errno);
    332 #else
    333   int errnum = errno;
    334   if (strerror_r(errnum, buf, sizeof(buf)) != 0) {
    335     snprintf(buf, sizeof(buf), "(error code %d)", errnum);
    336   }
    337   errmsg = buf;
    338 #endif /* defined(_WIN32) */
    339 
    340   caml_failwith_value(caml_alloc_sprintf(fmtmsg, errmsg));
    341 }
    342 
    343 static int unsn_is_system_attr_os(const char *attrname)
    344 {
    345 #if defined(__linux)
    346   return (strncmp(attrname, "system.", 7) == 0 &&
    347           strncmp(attrname, "system.posix_acl_", 17) != 0);
    348 #elif defined(__APPLE__)
    349   return (strcmp(attrname, XATTR_FINDERINFO_NAME) == 0 ||
    350           strcmp(attrname, XATTR_RESOURCEFORK_NAME) == 0);
    351 #elif defined(__FreeBSD__) || defined(__NetBSD__)
    352   return 0;
    353 #elif defined(__Solaris__)
    354   /* Special system "extensible attributes" xattrs are defined in sys/attr.h
    355    * as VIEW_READONLY = "SUNWattr_ro" and VIEW_READWRITE = "SUNWattr_rw" */
    356   return (strcmp(attrname, ".") == 0 || strcmp(attrname, "..") == 0 ||
    357           strncmp(attrname, "SUNWattr_", 9) == 0);
    358 #endif
    359 }
    360 
    361 
    362 /************************************
    363  *            Set xattr
    364  ************************************/
    365 static int unsn_set_xattr_os(const char *path, const char *attrname,
    366                              const void *attrvalue, size_t valuesize)
    367 {
    368 #if defined(__linux)
    369   return setxattr(path, attrname, attrvalue, valuesize, 0);
    370 #elif defined(__APPLE__)
    371   return setxattr(path, attrname, attrvalue, valuesize, 0, 0);
    372 #elif defined(__FreeBSD__) || defined(__NetBSD__)
    373   return (int) extattr_set_file(path, EXTATTR_NAMESPACE_USER, attrname,
    374                                 attrvalue, valuesize);
    375 #elif defined(__Solaris__)
    376   if (pathconf(path, _PC_XATTR_ENABLED) < 1) {
    377     unsn_xattr_not_supported();
    378   }
    379 
    380   /* This is a simplified implementation that just creates/opens
    381    * the xattr and writes the value into it.
    382    *
    383    * Extended attributes in Solaris and illumos are much more
    384    * flexible. In most ways they are like normal files/directories.
    385    * They have owner/group, mode, utimes, even ACL, and can have
    386    * their own extended attributes, etc.
    387    *
    388    * This implementation does not synchronize any of those params,
    389    * as xattrs are conceptually treated as name-value pairs.
    390    * It is unknown if this will cause problems with real use cases. */
    391   int fd = attropen(path, attrname, O_CREAT|O_WRONLY|O_TRUNC|O_CLOEXEC);
    392   if (fd == -1) {
    393     unsn_xattr_fail("Error opening extended attribute for writing: %s");
    394   }
    395 
    396   ssize_t written = 0, c;
    397   do {
    398     c = write(fd, attrvalue + written, valuesize - written);
    399     written += c;
    400   } while (c > 0 && written < valuesize);
    401 
    402   close(fd);
    403 
    404   return c == -1 ? c : 0;
    405 #endif
    406 }
    407 
    408 CAMLprim value unison_xattr_set(value path, value xattrname, value xattr)
    409 {
    410   CAMLparam3(path, xattrname, xattr);
    411   const char *name = String_val(path);
    412   XATTRNAME_VAL(attr, xattrname);
    413   const char *attrvalue = String_val(xattr);
    414   unsigned int len;
    415 
    416   /* Ignore system extended attributes */
    417   if (unsn_is_system_attr_os(attr)) {
    418     CAMLreturn(Val_unit);
    419   }
    420 
    421   len = caml_string_length(xattr);
    422 
    423   int error = unsn_set_xattr_os(name, attr, attrvalue, len);
    424   if (error == -1) {
    425     if (errno == ENOTSUP) {
    426       unsn_xattr_not_supported();
    427     } else {
    428       unsn_xattr_fail("Error writing extended attribute: %s");
    429     }
    430   }
    431 
    432   CAMLreturn(Val_unit);
    433 }
    434 
    435 
    436 /************************************
    437  *           Remove xattr
    438  ************************************/
    439 static int unsn_remove_xattr_os(const char *path, const char *attrname)
    440 {
    441 #if defined(__linux)
    442   return removexattr(path, attrname);
    443 #elif defined(__APPLE__)
    444   return removexattr(path, attrname, 0);
    445 #elif defined(__FreeBSD__) || defined(__NetBSD__)
    446   return (int) extattr_delete_file(path, EXTATTR_NAMESPACE_USER, attrname);
    447 #elif defined(__Solaris__)
    448   if (pathconf(path, _PC_XATTR_ENABLED) < 1) {
    449     unsn_xattr_not_supported();
    450   }
    451 
    452   int fd = attropen(path, ".", O_RDONLY|O_CLOEXEC);
    453   if (fd == -1) {
    454     unsn_xattr_fail("Error opening extended attribute for removing: %s");
    455   }
    456   int error = unlinkat(fd, attrname, 0);
    457   close(fd);
    458 
    459   return error;
    460 #endif
    461 }
    462 
    463 CAMLprim value unison_xattr_remove(value path, value xattrname)
    464 {
    465   CAMLparam2(path, xattrname);
    466   const char *name = String_val(path);
    467   XATTRNAME_VAL(attr, xattrname);
    468 
    469   /* Ignore system extended attributes */
    470   if (unsn_is_system_attr_os(attr)) {
    471     CAMLreturn(Val_unit);
    472   }
    473 
    474   int error = unsn_remove_xattr_os(name, attr);
    475   if (error == -1 && errno == ENOTSUP) {
    476     unsn_xattr_not_supported();
    477   }
    478 
    479   CAMLreturn(Val_unit);
    480 }
    481 
    482 
    483 /************************************
    484  *         Length of xattr
    485  ************************************/
    486 static ssize_t unsn_length_xattr_os(const char *path, const char *attrname)
    487 {
    488 #if defined(__linux)
    489   return getxattr(path, attrname, NULL, 0);
    490 #elif defined(__APPLE__)
    491   return getxattr(path, attrname, NULL, 0, 0, 0);
    492 #elif defined(__FreeBSD__) || defined(__NetBSD__)
    493   return extattr_get_file(path, EXTATTR_NAMESPACE_USER, attrname, NULL, 0);
    494 #elif defined(__Solaris__)
    495   int fd = attropen(path, attrname, O_RDONLY|O_CLOEXEC);
    496   if (fd == -1) {
    497     unsn_xattr_fail("Error opening extended attribute for querying length: %s");
    498   }
    499 
    500   struct stat buf;
    501   int error;
    502 
    503   error = fstat(fd, &buf);
    504   close(fd);
    505 
    506   return error == -1 ? error : buf.st_size;
    507 #endif
    508 }
    509 
    510 
    511 /************************************
    512  *            Get xattrs
    513  ************************************/
    514 static ssize_t unsn_get_xattr_os(const char *path, const char *attrname,
    515                                  void *buf, size_t size)
    516 {
    517 #if defined(__linux)
    518   return getxattr(path, attrname, buf, size);
    519 #elif defined(__APPLE__)
    520   return getxattr(path, attrname, buf, size, 0, 0);
    521 #elif defined(__FreeBSD__) || defined(__NetBSD__)
    522   return extattr_get_file(path, EXTATTR_NAMESPACE_USER, attrname, buf, size);
    523 #elif defined(__Solaris__)
    524   int fd = attropen(path, attrname, O_RDONLY|O_CLOEXEC);
    525   if (fd == -1) {
    526     unsn_xattr_fail("Error opening extended attribute for reading: %s");
    527   }
    528 
    529   ssize_t rd = 0, c;
    530   do {
    531     c = read(fd, buf + rd, size - rd);
    532     rd += c;
    533   } while (c > 0 && rd < size);
    534 
    535   close(fd);
    536 
    537   return c == -1 ? c : rd;
    538 #endif
    539 }
    540 
    541 CAMLprim value unison_xattr_get(value path, value xattrname)
    542 {
    543   CAMLparam2(path, xattrname);
    544   CAMLlocal1(v);
    545   const char *name = String_val(path);
    546   XATTRNAME_VAL(attr, xattrname);
    547 
    548   int len = 0, tries = 0;
    549 
    550   do {
    551     if (++tries > 10) {
    552       caml_failwith("Error reading contents of extended attribute; "
    553         "it keeps changing");
    554     }
    555 
    556     len = unsn_length_xattr_os(name, attr);
    557     if (len == -1 && errno == ENOTSUP) {
    558       unsn_xattr_not_supported();
    559     } else if (len == -1) {
    560       unsn_xattr_fail("Error reading length of extended attribute: %s");
    561     }
    562 
    563     if (len == 0) {
    564       v = caml_alloc_string(0);
    565     } else if (len > 16777211) { // Max OCaml string length on 32 bit platforms
    566       caml_failwith_value(caml_alloc_sprintf(
    567         "Extended attribute value is too big (%d bytes)", len));
    568     } else {
    569       char *buf = malloc(len);
    570 
    571       len = unsn_get_xattr_os(name, attr, buf, len);
    572       if (len == -1 && errno == ENOTSUP) {
    573         free(buf);
    574         unsn_xattr_not_supported();
    575       } else if (len == -1 && errno != ERANGE) {
    576         free(buf);
    577         unsn_xattr_fail("Error reading contents of extended attribute: %s");
    578       } else if (len != -1) {
    579         v = caml_alloc_initialized_string(len, buf);
    580       }
    581 
    582       free(buf);
    583     }
    584   } while (len == -1 && errno == ERANGE);
    585   /* ERANGE error produced on Linux and Darwin; other platforms
    586    * truncate the value if buffer is too small. */
    587 
    588   CAMLreturn(v);
    589 }
    590 
    591 
    592 /************************************
    593  *           List xattrs
    594  ************************************/
    595 static void unsn_list_xattr_fail(void)
    596 {
    597   unsn_xattr_fail("Error getting list of extended attributes: %s");
    598 }
    599 
    600 #if !defined(__Solaris__)
    601 
    602 static ssize_t unsn_list_xattr_os(const char *path, char *buf, size_t size)
    603 {
    604 #if defined(__linux)
    605   return listxattr(path, buf, size);
    606 #elif defined(__APPLE__)
    607   return listxattr(path, buf, size, 0);
    608 #elif defined(__FreeBSD__) || defined(__NetBSD__)
    609   return extattr_list_file(path, EXTATTR_NAMESPACE_USER, buf, size);
    610 #endif
    611 }
    612 
    613 static ssize_t unsn_list_xattr_aux(const char *path, char **buf)
    614 {
    615   ssize_t namelen;
    616 
    617   namelen = unsn_list_xattr_os(path, NULL, 0);
    618 
    619   if (namelen == -1) {
    620     if (errno == ENOTSUP) {
    621       unsn_xattr_not_supported();
    622     }
    623     unsn_list_xattr_fail();
    624   }
    625   if (namelen == 0) {
    626     return 0;
    627   }
    628 
    629   *buf = malloc(namelen);
    630   if (*buf == NULL) {
    631     unsn_list_xattr_fail();
    632   }
    633 
    634   namelen = unsn_list_xattr_os(path, *buf, namelen);
    635   if (namelen == -1) {
    636     free(*buf);
    637     unsn_list_xattr_fail();
    638   }
    639   if (namelen == 0) {
    640     free(*buf);
    641     return 0;
    642   }
    643 
    644   return namelen;
    645 }
    646 
    647 #else
    648 
    649 static ssize_t unsn_list_xattr_aux(const char *path, DIR **dirp)
    650 {
    651   if (pathconf(path, _PC_XATTR_ENABLED) < 1) {
    652     unsn_xattr_not_supported();
    653   }
    654 
    655   if (pathconf(path, _PC_XATTR_EXISTS) < 1) {
    656     return 0;
    657   }
    658 
    659   int fd = attropen(path, ".", O_RDONLY|O_CLOEXEC);
    660   if (fd == -1) {
    661     unsn_list_xattr_fail();
    662   }
    663 
    664   *dirp = fdopendir(fd);
    665   if (*dirp == NULL) {
    666     int real_err = errno;
    667     close(fd);
    668     errno = real_err;
    669     unsn_list_xattr_fail();
    670   }
    671 
    672   return 1;
    673 }
    674 
    675 #endif /* !__Solaris__ */
    676 
    677 CAMLprim value unison_xattrs_list(value path)
    678 {
    679   CAMLparam1(path);
    680   CAMLlocal3(result, p, l);
    681   /* Use a static buffer because memory management for the path would become
    682    * much too complex. Use a constant length because PATH_MAX is not reliable
    683    * and pathconf() is out of question. */
    684   char name[32768];
    685 #if !defined(__Solaris__)
    686   char *xattrs;
    687 #else
    688   DIR *xattrs;
    689   struct dirent *dp;
    690   char *xattrname;
    691 #endif
    692   ssize_t namelen, len;
    693 
    694   if (caml_string_length(path) > 32767) {
    695     caml_failwith("The path is too long");
    696   }
    697   strcpy(name, String_val(path));
    698 
    699   result = Val_emptylist;
    700 
    701   namelen = unsn_list_xattr_aux(name, &xattrs);
    702   if (namelen == 0) {
    703     CAMLreturn(result);
    704   }
    705 
    706 #if defined(__FreeBSD__) || defined(__NetBSD__)
    707   size_t nl = 0;
    708   char xattrname[256];
    709 
    710   for (char *xattrnamep = xattrs; xattrnamep < xattrs + namelen;
    711                                   xattrnamep += nl + 1) {
    712     nl = *xattrnamep & 255;
    713     memcpy(xattrname, xattrnamep + 1, nl);
    714     xattrname[nl] = '\0';
    715 #elif !defined(__Solaris__)
    716   /* For safety */
    717   *(xattrs + namelen - 1) = '\0';
    718 
    719   for (char *xattrname = xattrs; xattrname < xattrs + namelen;
    720                                  xattrname += strlen(xattrname) + 1) {
    721 #elif defined(__Solaris__)
    722   while (dp = readdir(xattrs)) {
    723     /* Note: NULL is returned for both end of dir and an error condition.
    724      * Error conditions are silently ignored. */
    725     xattrname = dp->d_name;
    726 #endif
    727 
    728     /* Ignore system extended attributes */
    729     if (unsn_is_system_attr_os(xattrname)) {
    730       continue;
    731     }
    732 
    733     len = unsn_length_xattr_os(name, xattrname);
    734     if (len == -1) {
    735       continue; /* Ignore silently */
    736     }
    737 
    738     p = caml_alloc_tuple(2);
    739     Store_field(p, 0, VAL_XATTRNAME(xattrname));
    740     Store_field(p, 1, Val_int(len));
    741 
    742     l = caml_alloc_small(2, Tag_cons);
    743     Field(l, 0) = p;
    744     Field(l, 1) = result;
    745 
    746     result = l;
    747   }
    748 
    749 #if defined(__Solaris__)
    750   closedir(xattrs);
    751 #else
    752   free(xattrs);
    753 #endif
    754 
    755   CAMLreturn(result);
    756 }
    757 
    758 
    759 /************************************
    760  *        ctime capabilities
    761  ************************************/
    762 CAMLprim value unison_xattr_updates_ctime(value unit)
    763 {
    764   CAMLparam0();
    765 #if defined(__Solaris__)
    766   CAMLreturn(Val_false);
    767 #else
    768   CAMLreturn(Val_true);
    769 #endif
    770 }
    771 
    772 
    773 #endif /* UNSN_HAS_XATTR */