props_acl.c (23017B)
1 /* Unison file synchronizer: src/props_acl.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 /* Supporting POSIX draft ACLs is not a goal, but may incidentally work 19 * on some platforms. Only NFSv4 ACLs and Windows ACLs are intended to be 20 * supported. 21 * 22 * On Solarish, both NFSv4 ACLs and POSIX draft ACLs are supported. 23 * There is even support for cross-synchronizing between NFSv4 and 24 * POSIX draft ACLs, but this support is currently disabled in props.ml 25 * by checking if the resulting ACL matches the requested ACL (the check 26 * fails with cross-synchronization). 27 * 28 * On FreeBSD and NetBSD, NFSv4 ACLs are supported. There is only limited 29 * support for synchronizing POSIX draft ACLs (no default ACLs). 30 * 31 * On Darwin, extended ACLs are supported. 32 * 33 * On Windows, NTFS ACLs are supported via SDDL format. Only explicit 34 * ACEs are synchronized, ignoring inherited ACEs completely. Users and 35 * groups are represented as SID strings in SDDL, not as names. 36 */ 37 38 /* The external interface is defined as follows. Every supported platform 39 * must implement this interface. ACL format can be platform-specific, 40 * which will prevent cross-platform synchronization but still allows 41 * synchronization within the platform. 42 * 43 * 44 * SET the ACL 45 * =========== 46 * unit unison_acl_from_text(String path, String acl) 47 * 48 * Set the requested ACL on the requested file or directory. The ACL 49 * must be in the same format as that returned by unison_acl_to_text(). 50 * Empty string ACL means <no ACL> and results in removal of any 51 * existing ACL on the requested file or directory. 52 * Symbolic links are followed. 53 * 54 * Input parameters 55 * path - absolute path of a file or directory 56 * acl - text representation of ACL to set on the path 57 * 58 * Return value 59 * No return value. 60 * 61 * Exceptions 62 * There are no mandatory exception conditions. 63 * Failure MAY voluntarily be raised for example when: 64 * Can't access file to set/remove ACL 65 * ACL not supported 66 * Error setting ACL 67 * Error removing ACL 68 * Error converting ACL from text 69 * 70 * 71 * GET the ACL 72 * =========== 73 * String unison_acl_to_text(String path) 74 * 75 * Get the current ACL on the requested file or directory. The ACL 76 * must be returned as a stable and deterministic text representation 77 * that meets the following criteria: 78 * - with multiple requests on the same file, the representation is 79 * always the same, unless the underlying ACL changes; 80 * - the same ACL on different files has the same representation. 81 * Symbolic links are followed. 82 * 83 * Input parameters 84 * path - absolute path of a file or directory 85 * 86 * Return value 87 * The text representation of the ACL; 88 * or the value of macro UNSN_ACL_EMPTY (or empty string "") meaning 89 * <no ACL> (or only trivial ACL) 90 * or the value of macro UNSN_ACL_NOT_SUPPORTED (currently "-1") if 91 * ACL is not supported on the requested path. 92 * 93 * Exceptions 94 * Failure MUST be raised when: 95 * Can't access file to get ACL 96 * Failure MAY voluntarily be raised for example when: 97 * Error getting ACL 98 * Error converting ACL to text 99 * If Failure is not raised on some error condition then an empty 100 * string "" MUST NOT be returned under any circumstances; return 101 * UNSN_ACL_NOT_SUPPORTED instead. 102 * 103 * 104 * =========== 105 * Definition of ACL format 106 * 107 * The format of ACL text representation is completely free as long as 108 * following constraints are met: 109 * - output of unison_acl_to_text() can be used as 110 * input to unison_acl_from_text() 111 * - ACL synchronization is done only on the same platform. 112 * 113 * If ACLs must be synchronized between different platforms then the 114 * currently used universal ACL format matches the definition from 115 * illumos acl(5) man page [https://illumos.org/man/5/acl]. This applies 116 * to both POSIX draft ACLs and NFSv4 ACLs. See the note on cross-platform 117 * synchronization below. 118 * 119 * ACL is always in the form 120 * 121 * acl_entry[,acl_entry]... 122 * 123 * Each acl_entry may be suffixed with a colon and userid/groupid. 124 * 125 * Examples: 126 * 127 * POSIX draft ACL 128 * 129 * user:tom:rw-,mask:rwx,group:staff:r-x:450 130 * 131 * NFSv4 ACL 132 * 133 * user:lp:rw------------:------I:allow:1300, 134 * owner@:--x-----------:------I:deny, 135 * owner@:rw-p---A-W-Co-:-------:allow, 136 * user:marks:r-------------:------I:deny:1270, 137 * group@:r-------------:-------:allow, 138 * everyone@:r-----a-R-c--s:-------:allow 139 * 140 * (note that the example is folded, but it should actually be 141 * returned as one string line without newlines) 142 * 143 * 144 * =========== 145 * On cross-platform synchronization 146 * 147 * Currently there is no canonical ACL representation created specifically 148 * for Unison. Existing platform APIs are used as much as possible, without 149 * custom formatting and parsing. 150 * A specific Unison ACL format could be truly common across platforms. 151 * 152 * If extended ACL synchronization capability is desired in the future then 153 * it is only required to change the output of unison_acl_to_text() and the 154 * input parsing in unison_acl_from_text(). 155 * The Unison archive format will not have to be changed as long as the 156 * entire ACL and any eventual metadata is encoded within one string. 157 * It is neither necessary to change the ACL code in props.ml. 158 * 159 * The issues of such cross-platform (e.g. between Windows and Unix-like) 160 * synchronization lie not in the representation format, though. It is easy 161 * enough to interpret the permission sets of NFSv4 and Windows ACLs in a 162 * similar, equivalent way. It can be much more difficult to interpret the 163 * subjects (users and groups) in a meaningful way. Purely for 164 * synchronization, this can still work on some platforms, e.g. Solaris, 165 * which allow the use of SIDs in ACL definition. 166 */ 167 168 #include <caml/memory.h> 169 #include <caml/alloc.h> 170 #include <caml/mlvalues.h> 171 #include <caml/fail.h> 172 173 174 #if defined(sun) || defined(__sun) /* Solarish, all illumos-based OS, */ 175 #define __Solaris__ /* OpenIndiana, OmniOS, SmartOS, ... */ 176 #endif 177 178 /* Primitive check only, without explicitly checking for 179 * POSIX or NFSv4. NFSv4-style ACLs are expected 180 * but POSIX draft ACLs may work to some extent. */ 181 #undef UNSN_HAS_FS_ACL 182 #if defined(__Solaris__) || defined(__FreeBSD__) || defined(__APPLE__) 183 #define UNSN_HAS_FS_ACL 184 #endif 185 186 #if defined(__NetBSD__) 187 #include <unistd.h> 188 #if defined(_PC_ACL_NFS4) 189 #define UNSN_HAS_FS_ACL 190 #endif 191 #endif 192 193 #if defined(_WIN32) 194 #define UNSN_HAS_FS_ACL 195 #endif 196 197 198 #define UNSN_ACL_NOT_SUPPORTED caml_copy_string("-1") 199 200 201 #ifndef UNSN_HAS_FS_ACL 202 203 CAMLprim value unison_acl_from_text(value path, value acl) 204 { 205 CAMLparam0(); 206 CAMLreturn(Val_unit); 207 } 208 209 CAMLprim value unison_acl_to_text(value path) 210 { 211 CAMLparam0(); 212 CAMLreturn(UNSN_ACL_NOT_SUPPORTED); 213 } 214 215 #else 216 217 218 #define UNSN_ACL_EMPTY caml_copy_string("") 219 220 221 #if defined(_WIN32) 222 223 /*#define ACL_DEBUG*/ 224 225 #ifndef UNICODE 226 #define UNICODE 227 #endif 228 #ifndef _UNICODE 229 #define _UNICODE 230 #endif 231 232 #include <windows.h> 233 #include <aclapi.h> 234 #include <sddl.h> 235 #include <strsafe.h> 236 237 #include <caml/version.h> 238 #if OCAML_VERSION < 41300 239 #define CAML_INTERNALS /* was needed from OCaml 4.06 to 4.12 */ 240 #endif 241 #include <caml/osdeps.h> 242 243 #ifdef ACL_DEBUG 244 #include <stdio.h> 245 #endif 246 247 static void unsn_acl_fail(char *msg, DWORD err) 248 { 249 DWORD flags; 250 char *sys_msg; 251 DWORD sys_len; 252 char fail_msg[160]; 253 const size_t LEN = sizeof(fail_msg) / sizeof(fail_msg[0]); 254 255 flags = 256 FORMAT_MESSAGE_ALLOCATE_BUFFER | 257 FORMAT_MESSAGE_FROM_SYSTEM | 258 FORMAT_MESSAGE_IGNORE_INSERTS; 259 260 sys_len = FormatMessageA(flags, NULL, err, 0, (char *) &sys_msg, 0, NULL); 261 if (!sys_len) { 262 StringCbPrintfA(fail_msg, LEN, "%s (Windows error code: %d)", msg, err); 263 } else { 264 /* Assume last 3 characters are ".\r\n" (doesn't matter if they aren't), 265 * and remove them. */ 266 if (sys_len > 3) { 267 sys_msg[sys_len - 3] = '\0'; 268 } 269 270 StringCbPrintfA(fail_msg, LEN, 271 "%s (Windows error code: %d) %s", msg, err, sys_msg); 272 LocalFree(sys_msg); 273 } 274 275 caml_failwith(fail_msg); 276 } 277 278 CAMLprim value unison_acl_from_text(value path, value acl) 279 { 280 CAMLparam2(path, acl); 281 wchar_t *wpath = caml_stat_strdup_to_utf16(String_val(path)); 282 wchar_t *wacl = caml_stat_strdup_to_utf16(String_val(acl)); 283 PCWSTR acl_text; 284 PSECURITY_DESCRIPTOR sd; 285 SECURITY_DESCRIPTOR_CONTROL sdc; 286 DWORD sdc_rev; 287 PSID owner = NULL, group = NULL; 288 PACL DACL; 289 BOOL DACLpresent = FALSE, isDef; 290 BOOL ok = TRUE; 291 SECURITY_INFORMATION si = 0; 292 DWORD res; 293 294 #ifdef ACL_DEBUG 295 printf_s(" ===> Setting ACL for |%ls|\n", wpath); 296 printf_s(" ---> Input ACL value |%ls|\n", wacl); 297 #endif 298 299 if (wcslen(wacl) == 0) { 300 acl_text = L"D:"; /* SDDL representation of empty ACL */ 301 } else { 302 acl_text = wacl; 303 } 304 #ifdef ACL_DEBUG 305 printf_s(" ---> Setting ACL value |%ls|\n", acl_text); 306 #endif 307 308 if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(acl_text, 309 SDDL_REVISION_1, &sd, NULL)) { 310 caml_stat_free(wpath); 311 caml_stat_free(wacl); 312 unsn_acl_fail("Error converting ACL from text", GetLastError()); 313 } 314 315 caml_stat_free(wacl); 316 317 ok = ok && GetSecurityDescriptorDacl(sd, &DACLpresent, &DACL, &isDef); 318 ok = ok && GetSecurityDescriptorControl(sd, &sdc, &sdc_rev); 319 320 if (!ok || !DACLpresent) { 321 LocalFree(sd); 322 caml_stat_free(wpath); 323 324 caml_failwith("Error converting ACL from text (no ACL info present?)"); 325 } 326 327 si |= DACL_SECURITY_INFORMATION; 328 329 if (sdc & SE_DACL_PROTECTED) { 330 si |= PROTECTED_DACL_SECURITY_INFORMATION; 331 } else { 332 si |= UNPROTECTED_DACL_SECURITY_INFORMATION; 333 } 334 335 res = SetNamedSecurityInfoW(wpath, SE_FILE_OBJECT, 336 si, owner, group, DACL, NULL); 337 338 LocalFree(sd); 339 caml_stat_free(wpath); 340 341 if (res == ERROR_ACCESS_DENIED) { 342 caml_failwith("Error setting ACL: access denied. The process may require " 343 "Administrator or \"Restore files\" privileges to set the ACL"); 344 } 345 346 if (res != ERROR_SUCCESS) { 347 unsn_acl_fail("Error setting ACL", res); 348 } 349 350 CAMLreturn(Val_unit); 351 } 352 353 CAMLprim value unison_acl_to_text(value path) 354 { 355 CAMLparam1(path); 356 CAMLlocal1(result); 357 wchar_t *wpath = caml_stat_strdup_to_utf16(String_val(path)); 358 int i, aceCnt; 359 PWSTR acl_text; 360 PSECURITY_DESCRIPTOR sd; 361 SECURITY_DESCRIPTOR_CONTROL sdc; 362 DWORD sdc_rev; 363 PSID owner, group; 364 PACL DACL; 365 ACL_SIZE_INFORMATION aclInfo; 366 PVOID ace; 367 SECURITY_INFORMATION si = DACL_SECURITY_INFORMATION; 368 DWORD res1, err; 369 BOOL res2; 370 371 #ifdef ACL_DEBUG 372 printf_s(" ===> Getting ACL for %ls\n", wpath); 373 #endif 374 375 res1 = GetNamedSecurityInfoW(wpath, SE_FILE_OBJECT, si, 376 &owner, &group, &DACL, NULL, &sd); 377 caml_stat_free(wpath); 378 379 if (res1 != ERROR_SUCCESS || sd == NULL) { 380 unsn_acl_fail("Error getting ACL", res1); 381 } 382 383 #ifdef ACL_DEBUG 384 res2 = ConvertSecurityDescriptorToStringSecurityDescriptorW(sd, 385 SDDL_REVISION_1, si, &acl_text, NULL); 386 387 if (acl_text != NULL) { 388 printf_s(" ---> Initial ACL text representation: %ls\n", acl_text); 389 390 LocalFree(acl_text); 391 } 392 #endif /* ACL_DEBUG */ 393 394 if (DACL == NULL) { 395 LocalFree(sd); 396 397 #ifdef ACL_DEBUG 398 printf_s(" ---> ACL not supported\n"); 399 #endif 400 CAMLreturn(UNSN_ACL_NOT_SUPPORTED); 401 } 402 403 if (!GetAclInformation(DACL, &aclInfo, sizeof(aclInfo), AclSizeInformation)) { 404 LocalFree(sd); 405 unsn_acl_fail("Error getting ACL information", GetLastError()); 406 } 407 aceCnt = aclInfo.AceCount; 408 409 /* Remove all inherited ACEs -- those cannot be restored in the other 410 * replica, they are inherited from the parent directory. */ 411 for (i = aclInfo.AceCount - 1; i >= 0; i--) { 412 if (!GetAce(DACL, i, &ace)) { 413 #ifdef ACL_DEBUG 414 printf_s("GetAce failed (Windows error code %d)\n", GetLastError()); 415 #endif 416 } else if (((PACE_HEADER) ace)->AceFlags & INHERITED_ACE) { 417 if (!DeleteAce(DACL, i)) { 418 #ifdef ACL_DEBUG 419 printf_s("DeleteAce failed (Windows error code %d)\n", GetLastError()); 420 #endif 421 } else { 422 aceCnt--; 423 } 424 } 425 } 426 427 /* Even when individual inherited ACEs have been removed, the entire ACL 428 * may have been marked as AUTO_INHERITED. Remove this flag to make 429 * synchronization paranoid checks more reliable. It is unknown if it 430 * can cause synchronization failures, but it doesn't matter - inherited 431 * ACLs can't be propagated in any case. */ 432 if (!SetSecurityDescriptorControl(sd, SE_DACL_AUTO_INHERITED, 0)) { 433 #ifdef ACL_DEBUG 434 unsn_acl_fail("Error in ACL control information", GetLastError()); 435 #endif 436 } 437 438 if (aceCnt == 0) { /* No explicit entries */ 439 if (!GetSecurityDescriptorControl(sd, &sdc, &sdc_rev)) { 440 LocalFree(sd); 441 unsn_acl_fail("Error getting ACL control information", GetLastError()); 442 } 443 444 if (!(sdc & SE_DACL_PROTECTED)) { /* No control flags we care about */ 445 LocalFree(sd); 446 447 #ifdef ACL_DEBUG 448 printf_s(" ---> Empty ACL (no explicit ACE, may have inherited ACE)\n"); 449 #endif 450 CAMLreturn(UNSN_ACL_EMPTY); /* Empty ACL (or only inherited) */ 451 } 452 } 453 454 res2 = ConvertSecurityDescriptorToStringSecurityDescriptor(sd, 455 SDDL_REVISION_1, si, &acl_text, NULL); 456 err = GetLastError(); 457 458 LocalFree(sd); 459 460 if (!res2 || (acl_text == NULL)) { 461 unsn_acl_fail("Error converting ACL to text", err); 462 } 463 464 #ifdef ACL_DEBUG 465 printf_s(" ---> Final ACL text representation: %ls\n", acl_text); 466 #endif 467 468 result = caml_copy_string_of_utf16(acl_text); 469 470 LocalFree(acl_text); 471 472 CAMLreturn(result); 473 } 474 475 476 #else /* defined(_WIN32) */ 477 478 479 #if defined(__Solaris__) || defined(__APPLE__) 480 #include <fcntl.h> 481 #include <sys/stat.h> 482 #endif 483 484 #if defined(__Solaris__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__) 485 #include <errno.h> 486 #include <stdio.h> 487 #include <string.h> 488 #include <sys/types.h> 489 #include <sys/acl.h> 490 #endif 491 492 #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__) 493 #include <unistd.h> 494 #endif 495 496 #if defined(__APPLE__) 497 #define UNSN_ACL_T acl_t 498 #else 499 #define UNSN_ACL_T acl_t * 500 #endif 501 502 503 static void unsn_acl_fail(const char *fmtmsg) 504 { 505 char errmsg[255]; 506 507 int errnum = errno; 508 if (strerror_r(errnum, errmsg, sizeof(errmsg)) != 0) { 509 snprintf(errmsg, sizeof(errmsg), "(error code %d)", errnum); 510 } 511 512 caml_failwith_value(caml_alloc_sprintf(fmtmsg, errmsg)); 513 } 514 515 #if defined(__FreeBSD__) || defined(__NetBSD__) 516 static acl_type_t unsn_path_acl_type(const char *path) 517 { 518 if (pathconf(path, _PC_ACL_NFS4) > 0) { /* NFSv4 ACL supported */ 519 return ACL_TYPE_NFS4; 520 } else if (pathconf(path, _PC_ACL_EXTENDED) > 0) { /* POSIX draft ACL */ 521 return ACL_TYPE_ACCESS; /* It is not possible to get or set 522 default and access ACL at the same time, 523 so fall back to access ACL only. */ 524 } else { /* ACLs not supported */ 525 return -1; 526 } 527 } 528 #elif defined(__APPLE__) 529 static acl_type_t unsn_path_acl_type(const char *path) 530 { 531 return ACL_TYPE_EXTENDED; 532 } 533 #endif 534 535 536 static void unsn_remove_acl_os(const char *path) 537 { 538 #if defined(__Solaris__) 539 struct stat st; 540 541 if (stat(path, &st) != 0) { 542 unsn_acl_fail("Can't access file to remove ACL: %s"); 543 } 544 545 if (acl_strip(path, st.st_uid, st.st_gid, st.st_mode) != 0) { 546 unsn_acl_fail("Error removing ACL: %s"); 547 } 548 #elif defined(__FreeBSD__) || defined(__NetBSD__) 549 /* FreeBSD has a acl_strip_np() function, but it would be 550 * much too complicated in this code. */ 551 /* Don't even bother checking for target ACL type, just 552 * try to remove all and ignore errors. */ 553 acl_delete_file_np(path, ACL_TYPE_DEFAULT); 554 acl_delete_file_np(path, ACL_TYPE_ACCESS); 555 acl_delete_file_np(path, ACL_TYPE_NFS4); 556 #elif defined(__APPLE__) 557 acl_set_file(path, unsn_path_acl_type(path), acl_from_text("!#acl 1")); 558 #endif 559 } 560 561 562 /************************************ 563 * Set ACL from text 564 ************************************/ 565 static _Bool unsn_acl_from_text_os(const char *acl_text, UNSN_ACL_T *aclp) 566 { 567 #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__) 568 *aclp = acl_from_text(acl_text); 569 570 return (*aclp != NULL); 571 #elif defined(__Solaris__) 572 int error = acl_fromtext(acl_text, aclp); 573 574 return (error == 0 && aclp != NULL); 575 #endif 576 } 577 578 CAMLprim value unison_acl_from_text(value path, value acl) 579 { 580 CAMLparam2(path, acl); 581 const char *acl_text = String_val(acl); 582 const char *name = String_val(path); 583 UNSN_ACL_T aclp = NULL; 584 int error; 585 586 #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__) 587 acl_type_t type = unsn_path_acl_type(name); 588 if (type == -1) { 589 caml_failwith("ACL not supported on this path"); 590 } 591 #endif 592 593 /* Check if ACL must be removed */ 594 if (*acl_text == '\0') { 595 unsn_remove_acl_os(name); 596 CAMLreturn(Val_unit); 597 } 598 599 if (!unsn_acl_from_text_os(acl_text, &aclp)) { 600 caml_failwith("Error converting ACL from text"); 601 } 602 603 #if defined(__Solaris__) 604 error = acl_set(name, aclp); 605 #elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__) 606 error = acl_set_file(name, type, aclp); 607 #endif 608 int real_err = errno; 609 acl_free(aclp); 610 errno = real_err; 611 612 if (error == -1) { 613 unsn_acl_fail("Error setting ACL: %s"); 614 } 615 616 CAMLreturn(Val_unit); 617 } 618 619 620 /************************************ 621 * Get ACL as text 622 ************************************/ 623 /* This function does not allocate new, 624 * it returns the pointer to its argument. */ 625 static char *postprocess_acl_os(char *s) 626 { 627 #if defined(__FreeBSD__) || defined(__NetBSD__) 628 char *p; 629 char *buf = s; /* Just an alias; modify input string in place */ 630 int perms = 0, comment = 0, offs = 0; 631 632 for (p = s; *p; p++) { 633 switch (*p) { 634 case ',' : 635 perms = 0; 636 break; 637 case '#' : 638 /* FreeBSD acl_to_text embeds the #effective permissions, 639 * which are actually not part of the ACL. */ 640 comment = 1; 641 break; 642 case '@' : 643 case ':' : 644 if (!comment) { 645 perms++; 646 } 647 break; 648 case 'D' : 649 /* Swap the position of d and D permissions. 650 * Synchronization works even without swapping, but the different 651 * ordering will show up as constant synchronization difference. */ 652 if (perms == 2) { 653 if (buf[offs - 1] != 'd' && *(p + 1) == 'd') { 654 *p = 'd'; 655 *(p + 1) = 'D'; 656 } else if (buf[offs - 1] != 'd' && *(p + 1) == '-') { 657 *p = '-'; 658 *(p + 1) = 'D'; 659 } 660 perms = 0; /* prevent further swapping */ 661 } 662 break; 663 case 'd' : 664 if (perms == 2) { 665 if (buf[offs - 1] == '-' && *(p + 1) != 'D' && *(p + 1) != '\0') { 666 buf[offs - 1] = 'd'; 667 *p = '-'; 668 } else if (buf[offs - 1] != 'd' && *(p + 1) == '-') { 669 *p = '-'; 670 *(p + 1) = 'D'; 671 } 672 perms = 0; /* prevent further swapping */ 673 } 674 break; 675 case '\n' : 676 /* Replace newlines with commas... 677 * ... except if it's the last one. */ 678 if (*(p + 1) != '\0') { 679 *p = ','; 680 } else { 681 *p = ' '; 682 } 683 perms = 0; 684 comment = 0; 685 break; 686 } 687 688 /* Remove all whitespace and comments. */ 689 if (*p != ' ' && *p != '\t' && !comment) { 690 buf[offs++] = *p; 691 } 692 } 693 buf[offs] = '\0'; 694 695 return buf; 696 #elif defined(__APPLE__) 697 /* Remove trailing newline */ 698 size_t last = strlen(s) - 1; 699 if (last >= 0 && s[last] == '\n') { 700 s[last] = '\0'; 701 } 702 703 return s; 704 #endif 705 } 706 707 static char *unsn_acl_to_text_os(UNSN_ACL_T aclp) 708 { 709 #if defined(__FreeBSD__) || defined(__NetBSD__) 710 return postprocess_acl_os(acl_to_text_np(aclp, NULL, ACL_TEXT_APPEND_ID)); 711 #elif defined(__APPLE__) 712 return postprocess_acl_os(acl_to_text(aclp, NULL)); 713 #elif defined(__Solaris__) 714 return acl_totext(aclp, ACL_APPEND_ID | ACL_COMPACT_FMT | ACL_SID_FMT); 715 #endif 716 } 717 718 static _Bool unsn_acl_is_empty_or_trivial_os(UNSN_ACL_T aclp) 719 { 720 #if defined(__FreeBSD__) || defined(__NetBSD__) 721 int is_trivial = 0; 722 723 acl_is_trivial_np(aclp, &is_trivial); /* Ignore any errors here */ 724 725 return (is_trivial || aclp == NULL); 726 #elif defined(__APPLE__) || defined(__Solaris__) 727 return (aclp == NULL); 728 #endif 729 } 730 731 static int unsn_get_acl_os(const char *path, UNSN_ACL_T *aclp) 732 { 733 #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__) 734 acl_type_t type = unsn_path_acl_type(path); 735 if (type == -1) { 736 errno = EOPNOTSUPP; 737 return -1; 738 } 739 740 errno = 0; 741 *aclp = acl_get_file(path, type); 742 743 #if defined(__APPLE__) 744 if (errno != 0) { 745 /* ACLs are always enabled on Darwin since version 10 (2009). 746 * Unfortunately, Darwin sets errno to ENOENT also when the file 747 * does not have an extended ACL. Since it is impossible to distinguish 748 * from the real ENOENT (due to the path), we must check with stat(). */ 749 if (errno == ENOENT) { 750 struct stat st; 751 errno = 0; 752 stat(path, &st); 753 754 if (errno != ENOENT) { 755 /* The path does not trigger ENOENT; 756 * this means that there is no extended ACL. This is allowed. */ 757 return 0; 758 } 759 760 errno = ENOENT; /* Ignore errno from stat(), restore original errno. */ 761 } 762 763 return -1; 764 } 765 #elif defined(__FreeBSD__) || defined(__NetBSD__) 766 if (*aclp == NULL) { 767 return -1; 768 } 769 #endif 770 771 return 0; 772 #endif /* FreeBSD or NetBSD or Darwin */ 773 774 #if defined(__Solaris__) 775 return acl_get(path, ACL_NO_TRIVIAL, aclp); 776 #endif 777 } 778 779 CAMLprim value unison_acl_to_text(value path) 780 { 781 CAMLparam1(path); 782 CAMLlocal1(result); 783 UNSN_ACL_T aclp = NULL; 784 char *acltxt; 785 786 int err = unsn_get_acl_os(String_val(path), &aclp); 787 if (err == -1) { 788 if (errno == ENOSYS || errno == EOPNOTSUPP) { 789 CAMLreturn(UNSN_ACL_NOT_SUPPORTED); 790 } else { 791 unsn_acl_fail("Error getting ACL: %s"); 792 } 793 } 794 795 /* If there was no error but aclp is NULL then it means an empty 796 * or trivial ACL (that is, just the mode), which is allowed. */ 797 if (aclp == NULL || unsn_acl_is_empty_or_trivial_os(aclp)) { 798 if (aclp != NULL) { 799 acl_free(aclp); 800 } 801 802 CAMLreturn(UNSN_ACL_EMPTY); 803 } 804 805 acltxt = unsn_acl_to_text_os(aclp); 806 acl_free(aclp); 807 808 if (acltxt == NULL) { 809 caml_failwith("Error converting ACL to text"); 810 } 811 812 result = caml_copy_string(acltxt); 813 free(acltxt); 814 815 CAMLreturn(result); 816 } 817 818 #endif /* !defined(_WIN32) */ 819 820 821 #endif /* UNSN_HAS_FS_ACL */