Skip to content

faccessatZ on linux ignores flags, wrong syscall #16606

@jordanisaacs

Description

@jordanisaacs

Zig Version

0.11.0-dev.4296+7e25fb4a4

Steps to Reproduce and Observed Behavior

faccessatZ on linux ignores flags because it is using the wrong syscall, faccessat instead of faccessat2 (see here). In the man page

  Because  the  Linux kernel's faccessat() system call does not support a
  flags argument, the glibc  faccessat()  wrapper  function  provided  in
  glibc 2.32 and earlier emulates the required functionality using a com‐
  bination of the faccessat() system call and fstatat(2).  However,  this
  emulation  does  not take ACLs into account.  Starting with glibc 2.33,
  the wrapper function avoids this bug by making use of the  faccessat2()
  system call where it is provided by the underlying kernel.

This can be seen in the code below. Created a simple ln --symbolic main.zing src/main2.zig

const std = @import("std");

pub fn main() !void {
    // stdout is for the actual output of your application, for example if you
    // are implementing gzip, then only the compressed bytes should be sent to
    // stdout, not any debugging messages.
    const stdout_file = std.io.getStdOut().writer();
    var bw = std.io.bufferedWriter(stdout_file);
    const stdout = bw.writer();

    const x = faccessat2Z(std.os.AT.FDCWD, "./src/main2.zig", std.os.X_OK, 0);
    try stdout.print("faccessat2 (follow symlink): {!}\n", .{x});

    const y = faccessat2Z(std.os.AT.FDCWD, "./src/main2.zig", std.os.X_OK, std.os.AT.SYMLINK_NOFOLLOW);
    try stdout.print("faccessat2 (SYMLINK_NOFOLLOW): {!}\n", .{y});

    const a = std.os.faccessatZ(std.os.AT.FDCWD, "./src/main2.zig", std.os.X_OK, 0);
    try stdout.print("std.os.faccessat (follow symlink): {!}\n", .{a});

    const b = std.os.faccessatZ(std.os.AT.FDCWD, "./src/main2.zig", std.os.X_OK, std.os.AT.SYMLINK_NOFOLLOW);
    try stdout.print("std.os.faccessat (SYMLINK_NOFOLLOW): {!}\n", .{b});

    try bw.flush(); // don't forget to flush!
}

pub fn faccessat2(dirfd: i32, path: [*:0]const u8, mode: u32, flags: u32) usize {
    return std.os.linux.syscall4(.faccessat2, @as(usize, @bitCast(@as(isize, dirfd))), @intFromPtr(path), mode, flags);
}

pub fn faccessat2Z(dirfd: std.os.fd_t, path: [*:0]const u8, mode: u32, flags: u32) std.os.AccessError!void {
    switch (std.os.errno(faccessat2(dirfd, path, mode, flags))) {
        .SUCCESS => return,
        .ACCES => return error.PermissionDenied,
        .ROFS => return error.ReadOnlyFileSystem,
        .LOOP => return error.SymLinkLoop,
        .TXTBSY => return error.FileBusy,
        .NOTDIR => return error.FileNotFound,
        .NOENT => return error.FileNotFound,
        .NAMETOOLONG => return error.NameTooLong,
        .INVAL => unreachable,
        .FAULT => unreachable,
        .IO => return error.InputOutput,
        .NOMEM => return error.SystemResources,
        else => |err| return std.os.unexpectedErrno(err),
    }
}

Output: (notice only faccessat2 did not follow the symlink and returned success)

faccessat2 (follow symlink): error.PermissionDenied
faccessat2 (SYMLINK_NOFOLLOW): void
std.os.faccessat (follow symlink): error.PermissionDenied
std.os.faccessat (SYMLINK_NOFOLLOW): error.PermissionDenied

Expected Behavior

The std library should use the faccessat2. If it does not exist can choose to either fallback to faccessat and ignore flags, or also use the emulated fstatat behavior found in glibc.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugObserved behavior contradicts documented or intended behavioros-linuxLinuxstandard libraryThis issue involves writing Zig code for the standard library.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions