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 */