Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed crash in posix.open when TMPFILE=true and DIRECTORY=false #20387

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

CPestka
Copy link
Contributor

@CPestka CPestka commented Jun 22, 2024

Fixes #20378.

As said in the issue, I think it would also be reasonable to solve this via returning an error instead.

@CPestka
Copy link
Contributor Author

CPestka commented Jun 23, 2024

I saw some disabled tests in this file, due to #14968.
I don't think these new tests would interfere with that, but i'm not entirely sure.


Also the tests are currently only run for linux, which is overly restrictive, but I'm not entirely sure how to only enable the tests for the platforms that have .O flags, smth like @hasDecl(posix, "O") does no work, as O is defined for these, but is instead "cough" via a compileError prong. The "last resort" i guess would be to just list the relevant posix platforms manually.

@alexrp
Copy link
Member

alexrp commented Oct 10, 2024

I'm not convinced this fix is correct. I can't find any code in musl or glibc that does something equivalent to this.

@CPestka
Copy link
Contributor Author

CPestka commented Oct 11, 2024

I just had a look at musl's and glibc's code and they indeed do not mess with the flags like this before the syscall. They dont do this because they do not have to. They simply define the o_tmpfile constant, as such that it has the value of o_directory already or-ed into it.
So from glibcs fcntl-linux.h:

#ifndef __O_DIRECTORY
# define __O_DIRECTORY	0200000
#endif
#ifndef __O_NOFOLLOW
# define __O_NOFOLLOW	0400000
#endif
#ifndef __O_CLOEXEC
# define __O_CLOEXEC   02000000
#endif
#ifndef __O_DIRECT
# define __O_DIRECT	 040000
#endif
#ifndef __O_NOATIME
# define __O_NOATIME   01000000
#endif
#ifndef __O_PATH
# define __O_PATH     010000000
#endif
#ifndef __O_DSYNC
# define __O_DSYNC	 010000
#endif
#ifndef __O_TMPFILE
# define __O_TMPFILE   (020000000 | __O_DIRECTORY)
#endif

in musl in the generic header it is similarly

#define O_TMPFILE 020200000 

The way zig defines these constants makes them "exclusive" so O.TMPFILE is "just" the tmp flag and does not normally implicitly include the O.DIRECTORY flag.

As i said in the bug report, not sure that this should be addressed this way. The kernel expects this flag to be present. glibc and musl do this by defining the constant to always include it. One could do it this way and fix the flags after the fact or "fix" the definiton of the flag.

@alexrp
Copy link
Member

alexrp commented Oct 12, 2024

Thanks for doing that research; I'd totally missed that.

At minimum, we should make sure that the O constants in std.c are correctly defined in the way that glibc and musl define them... is what I would have liked to say, but that's not possible with packed structs. 🙁

As for std.os.linux, we've recently made efforts to stop doing emulation and compatibility layers (see e.g. #6600), and this module is supposed to give you roughly the same interface to the kernel as the UAPI headers, so in any case, it wouldn't be appropriate to use the libc-ism of defining O_TMPFILE to include O_DIRECTORY in this module, even if we could.

#6600 is also a reason why we likely wouldn't want to do this in std.posix either.

Honestly, given these constraints, it seems like the best we can do is put a doc comment on std.os.linux.O.TMPFILE telling the user to remember to also set DIRECTORY. But before going that route, I think we should leave this open for more discussion.

@rootbeer
Copy link
Contributor

I agree this shouldn't be fixed in wrapper code, but I think we can define O to make it usable without adding emulation or compatiblity hacks. I think adding a __TMPFILE entry to the O enum (or ZIG_TMPFILE?) for the single bit, and then adding a wrapper function TMPFILE that sets the multiple required bits would work. This seems like the Zig equivalent of the C headers that do #define O_TMPFILE (__TMPFILE | O_DIRECTORY). So something like:

pub const O = packed struct(32) {
    ...
    __TMPFILE: bool = false, 
    ... 

    // TMPFILE is defined to set both DIRECTORY and __TMPFILE bits at the same time in C headers
    pub fn TMPFILE (oflags: O, enabled: bool) O {
      var nflags = oflags;
      nflags.DIRECTORY = enabled;
      nflags.__TMPFILE = enabled;
      return nflags;
    };
};

Use it like:

  var oflags : 0 = .{ .OTHERFLAG = true };
  oflags = oflags.TMPFILE(true);

Maybe its better to make oflags a pointer to an in/out parameter, and return void? Or call this setTmpfile? Or most minimally just define TMPFILE as an instance of the struct with the two bits set:

    // namespaced inside the `O` struct ...
    const TMPFILE: O = .{ .DIRECTORY = true, .__TMPFILE = true };

I thought there was some existing packed-struct definition in Zig that solved a multiple-bits problem like this, but I can't find any. Except for definitions where the bits are adjacent, in which case an embedded enum works (e.g., see MAP.TYPE in c.zig). But the bits aren't close enough in this case.

Also, it looks like O_RSYNC has a similar problem on many platforms (e.g., it is 0x04010000).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

std.posix.open runs into unreachable when used to open a tmp file
3 participants