| ... | @@ -348,17 +348,58 @@ pub fn fchmod(fd: fd_t, mode: mode_t) FChmodError!void { | ... | @@ -348,17 +348,58 @@ pub fn fchmod(fd: fd_t, mode: mode_t) FChmodError!void { |
| 348 | } | 348 | } |
| 349 | | 349 | |
| 350 | const FChmodAtError = FChmodError || error{ | 350 | const FChmodAtError = FChmodError || error{ |
| | 351 | /// A component of `path` exceeded `NAME_MAX`, or the entire path exceeded |
| | 352 | /// `PATH_MAX`. |
| 351 | NameTooLong, | 353 | NameTooLong, |
| | 354 | |
| | 355 | /// `path` resolves to a symbolic link, and `AT.SYMLINK_NOFOLLOW` was set |
| | 356 | /// in `flags`. This error only occurs on Linux, where changing the mode of |
| | 357 | /// a symbolic link has no meaning and can cause undefined behaviour on |
| | 358 | /// certain filesystems. |
| | 359 | /// |
| | 360 | /// The procfs fallback was used but procfs was not mounted. |
| | 361 | OperationNotSupported, |
| | 362 | |
| | 363 | /// The procfs fallback was used but the process exceeded its open file |
| | 364 | /// limit. |
| | 365 | ProcessFdQuotaExceeded, |
| | 366 | |
| | 367 | /// The procfs fallback was used but the system exceeded it open file limit. |
| | 368 | SystemFdQuotaExceeded, |
| 352 | }; | 369 | }; |
| 353 | | 370 | |
| 354 | pub fn fchmodat(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtError!void { | 371 | var has_fchmodat2_syscall = std.atomic.Value(bool).init(true); |
| | 372 | |
| | 373 | inline fn skipFchmodatFallback(flags: u32) bool { |
| | 374 | return builtin.os.tag != .linux or |
| | 375 | flags == 0 or |
| | 376 | std.c.versionCheck(std.SemanticVersion{ .major = 2, .minor = 32, .patch = 0 }).ok; |
| | 377 | } |
| | 378 | |
| | 379 | /// Changes the `mode` of `path` relative to the directory referred to by |
| | 380 | /// `dirfd`. The process must have the correct privileges in order to do this |
| | 381 | /// successfully, or must have the effective user ID matching the owner of the |
| | 382 | /// file. |
| | 383 | /// |
| | 384 | /// On Linux the `fchmodat2` syscall will be used if available, otherwise a |
| | 385 | /// workaround using procfs will be employed. Changing the mode of a symbolic |
| | 386 | /// link with `AT.SYMLINK_NOFOLLOW` set will also return |
| | 387 | /// `OperationNotSupported`, as: |
| | 388 | /// |
| | 389 | /// 1. Permissions on the link are ignored when resolving its target. |
| | 390 | /// 2. This operation has been known to invoke undefined behaviour across |
| | 391 | /// different filesystems[1]. |
| | 392 | /// |
| | 393 | /// [1]: https://sourceware.org/legacy-ml/libc-alpha/2020-02/msg00467.html. |
| | 394 | pub inline fn fchmodat(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtError!void { |
| 355 | if (!std.fs.has_executable_bit) @compileError("fchmodat unsupported by target OS"); | 395 | if (!std.fs.has_executable_bit) @compileError("fchmodat unsupported by target OS"); |
| 356 | | 396 | |
| 357 | const path_c = try toPosixPath(path); | 397 | const path_c = try toPosixPath(path); |
| 358 | | 398 | |
| 359 | while (true) { | 399 | // No special handling for linux is needed if we can use the libc fallback |
| | 400 | // or `flags` is empty. Glibc only added the fallback in 2.32. |
| | 401 | while (skipFchmodatFallback(flags)) { |
| 360 | const res = system.fchmodat(dirfd, &path_c, mode, flags); | 402 | const res = system.fchmodat(dirfd, &path_c, mode, flags); |
| 361 | | | |
| 362 | switch (system.getErrno(res)) { | 403 | switch (system.getErrno(res)) { |
| 363 | .SUCCESS => return, | 404 | .SUCCESS => return, |
| 364 | .INTR => continue, | 405 | .INTR => continue, |
| ... | @@ -368,8 +409,104 @@ pub fn fchmodat(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodA | ... | @@ -368,8 +409,104 @@ pub fn fchmodat(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodA |
| 368 | .ACCES => return error.AccessDenied, | 409 | .ACCES => return error.AccessDenied, |
| 369 | .IO => return error.InputOutput, | 410 | .IO => return error.InputOutput, |
| 370 | .LOOP => return error.SymLinkLoop, | 411 | .LOOP => return error.SymLinkLoop, |
| | 412 | .MFILE => return error.ProcessFdQuotaExceeded, |
| | 413 | .NAMETOOLONG => return error.NameTooLong, |
| | 414 | .NFILE => return error.SystemFdQuotaExceeded, |
| | 415 | .NOENT => return error.FileNotFound, |
| | 416 | .NOTDIR => return error.FileNotFound, |
| | 417 | .NOMEM => return error.SystemResources, |
| | 418 | .OPNOTSUPP => return error.OperationNotSupported, |
| | 419 | .PERM => return error.AccessDenied, |
| | 420 | .ROFS => return error.ReadOnlyFileSystem, |
| | 421 | else => |err| return unexpectedErrno(err), |
| | 422 | } |
| | 423 | } |
| | 424 | |
| | 425 | const use_fchmodat2 = (comptime builtin.os.isAtLeast(.linux, .{ .major = 6, .minor = 6, .patch = 0 }) orelse false) and |
| | 426 | has_fchmodat2_syscall.load(.Monotonic); |
| | 427 | while (use_fchmodat2) { |
| | 428 | // Later on this should be changed to `system.fchmodat2` |
| | 429 | // when the musl/glibc add a wrapper. |
| | 430 | const res = linux.fchmodat2(dirfd, &path_c, mode, flags); |
| | 431 | switch (linux.getErrno(res)) { |
| | 432 | .SUCCESS => return, |
| | 433 | .INTR => continue, |
| | 434 | .BADF => unreachable, |
| | 435 | .FAULT => unreachable, |
| | 436 | .INVAL => unreachable, |
| | 437 | .ACCES => return error.AccessDenied, |
| | 438 | .IO => return error.InputOutput, |
| | 439 | .LOOP => return error.SymLinkLoop, |
| | 440 | .NOENT => return error.FileNotFound, |
| | 441 | .NOMEM => return error.SystemResources, |
| | 442 | .NOTDIR => return error.FileNotFound, |
| | 443 | .OPNOTSUPP => return error.OperationNotSupported, |
| | 444 | .PERM => return error.AccessDenied, |
| | 445 | .ROFS => return error.ReadOnlyFileSystem, |
| | 446 | |
| | 447 | .NOSYS => { // Use fallback. |
| | 448 | has_fchmodat2_syscall.store(false, .Monotonic); |
| | 449 | break; |
| | 450 | }, |
| | 451 | else => |err| return unexpectedErrno(err), |
| | 452 | } |
| | 453 | } |
| | 454 | |
| | 455 | // Fallback to changing permissions using procfs: |
| | 456 | // |
| | 457 | // 1. Open `path` as an `O.PATH` descriptor. |
| | 458 | // 2. Stat the fd and check if it isn't a symbolic link. |
| | 459 | // 3. Generate the procfs reference to the fd via `/proc/self/fd/{fd}`. |
| | 460 | // 4. Pass the procfs path to `chmod` with the `mode`. |
| | 461 | var pathfd: fd_t = undefined; |
| | 462 | while (true) { |
| | 463 | const rc = system.openat(dirfd, &path_c, O.PATH | O.NOFOLLOW | O.CLOEXEC, @as(mode_t, 0)); |
| | 464 | switch (system.getErrno(rc)) { |
| | 465 | .SUCCESS => { |
| | 466 | pathfd = @as(fd_t, @intCast(rc)); |
| | 467 | break; |
| | 468 | }, |
| | 469 | .INTR => continue, |
| | 470 | .FAULT => unreachable, |
| | 471 | .INVAL => unreachable, |
| | 472 | .ACCES => return error.AccessDenied, |
| | 473 | .PERM => return error.AccessDenied, |
| | 474 | .LOOP => return error.SymLinkLoop, |
| | 475 | .MFILE => return error.ProcessFdQuotaExceeded, |
| | 476 | .NAMETOOLONG => return error.NameTooLong, |
| | 477 | .NFILE => return error.SystemFdQuotaExceeded, |
| 371 | .NOENT => return error.FileNotFound, | 478 | .NOENT => return error.FileNotFound, |
| 372 | .NOMEM => return error.SystemResources, | 479 | .NOMEM => return error.SystemResources, |
| | 480 | else => |err| return unexpectedErrno(err), |
| | 481 | } |
| | 482 | } |
| | 483 | defer close(pathfd); |
| | 484 | |
| | 485 | const stat = fstatatZ(pathfd, "", AT.EMPTY_PATH) catch |err| switch (err) { |
| | 486 | error.NameTooLong => unreachable, |
| | 487 | error.FileNotFound => unreachable, |
| | 488 | else => |e| return e, |
| | 489 | }; |
| | 490 | if ((stat.mode & S.IFMT) == S.IFLNK) |
| | 491 | return error.OperationNotSupported; |
| | 492 | |
| | 493 | var procfs_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined; |
| | 494 | const proc_path = std.fmt.bufPrintZ(procfs_buf[0..], "/proc/self/fd/{d}", .{pathfd}) catch unreachable; |
| | 495 | while (true) { |
| | 496 | const res = system.chmod(proc_path, mode); |
| | 497 | switch (system.getErrno(res)) { |
| | 498 | // Getting NOENT here means that procfs isn't mounted. |
| | 499 | .NOENT => return error.OperationNotSupported, |
| | 500 | |
| | 501 | .SUCCESS => return, |
| | 502 | .INTR => continue, |
| | 503 | .BADF => unreachable, |
| | 504 | .FAULT => unreachable, |
| | 505 | .INVAL => unreachable, |
| | 506 | .ACCES => return error.AccessDenied, |
| | 507 | .IO => return error.InputOutput, |
| | 508 | .LOOP => return error.SymLinkLoop, |
| | 509 | .NOMEM => return error.SystemResources, |
| 373 | .NOTDIR => return error.FileNotFound, | 510 | .NOTDIR => return error.FileNotFound, |
| 374 | .PERM => return error.AccessDenied, | 511 | .PERM => return error.AccessDenied, |
| 375 | .ROFS => return error.ReadOnlyFileSystem, | 512 | .ROFS => return error.ReadOnlyFileSystem, |