-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
rework the API layers between the standard library and the operating system #2527
Conversation
Recommendations:
Much of this can be thought of as "separating the (2) from the (3)" (this is a quote that stuck in my head from somewhere, but now I can't find a source for it), which is a reference to man page sections "2: System calls (functions provided by the kernel)" and "3: Library calls". Though in practice man pages sometimes end up documenting glibc things in section 2 🤦♂️ |
std/os/bits/wasi.zig
Outdated
buf: [*]const u8, | ||
buf_len: usize, | ||
}; | ||
pub const ciovec_t = iovec_const; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use @import("../bits.zig");
is needed for this to work
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@andrewrk patch to fix WASI failing:
diff --git a/std/os/bits/wasi.zig b/std/os/bits/wasi.zig
index a6f96de3..93d2a82f 100644
--- a/std/os/bits/wasi.zig
+++ b/std/os/bits/wasi.zig
@@ -1,2 +0,0 @@
-use @import("../bits.zig");
-
@@ -15,2 +12,0 @@ pub const ADVICE_NOREUSE: advice_t = 5;
-pub const ciovec_t = iovec_const;
-
@@ -185,2 +180,0 @@ pub const inode_t = u64;
-pub const iovec_t = iovec;
-
diff --git a/std/os/wasi.zig b/std/os/wasi.zig
index 1ce0fe0c..adfe9e82 100644
--- a/std/os/wasi.zig
+++ b/std/os/wasi.zig
@@ -8 +8 @@ pub const is_the_target = builtin.os == .wasi;
-pub use @import("bits/wasi.zig");
+pub use @import("bits.zig");
@@ -20,0 +21,3 @@ comptime {
+pub const iovec_t = iovec;
+pub const ciovec_t = iovec_const;
+
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh that's much better, incorporated, thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And here’s for when the docs start to fail:
diff --git a/doc/langref.html.in b/doc/langref.html.in
index 19839904..402e5365 100644
--- a/doc/langref.html.in
+++ b/doc/langref.html.in
@@ -9011,0 +9012 @@ export fn add(a: i32, b: i32) void {
+ {#code_release_fast#}
@@ -9015,2 +9016,2 @@ pub fn main() !void {
- const args = try std.os.argsAlloc(std.heap.wasm_allocator);
- defer std.os.argsFree(std.heap.wasm_allocator, args);
+ const args = try std.process.argsAlloc(std.heap.wasm_allocator);
+ defer std.process.argsFree(std.heap.wasm_allocator, args);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's the release fast for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Building WASI in debug mode fails with Unsupported OS in debug.zig. I think this is the correct behavior as we don’t have support for debugging symbols for WASM.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah. Well that's a separate issue but we can easily make it work, sans debug symbols. And then it can go from "debug mode isn't yet supported" to "debug symbols aren't yet supported", which I think is a preferable state to be in. There are lots of benefits to debug mode besides debug info parsing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. It works on master. I didn’t want CI to fail due to WAS{M,I}.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I only reviewed a little bit of the code; huge PR!
// See #397 | ||
const errno = posix.getErrno(posix.getrandom(buf.ptr, buf.len, 0)); | ||
switch (errno) { | ||
pub fn getrandom(buf: []u8) GetRandomError!void { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this function should be in std/crypto
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
std.crypto.randomBytes
calls (or is aliased to) this function. This exists in std.os
because it's a Linux API and so this is the code that bridges libc and non-libc. Since it's a useful API to make cross platform, this is also the layer where it is made so, just like the other posix functions. You can think of it somewhat like kevent
in that it's not POSIX but it is cross-platform.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since it's a useful API to make cross platform, this is also the layer where it is made so, just like the other posix functions
I don't follow why it belongs std/os
instead of std/crypto
though?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can think of it somewhat like kevent in that it's not POSIX but it is cross-platform.
kevent
is very not cross platform; there are many kevent
semantics that are impossible to implement on linux, windows or wasi.
You can abstract over the top of kevent
(bsds+osx)/epoll
(linux)/ports
(solaris), but that wouldn't be kevent, it would be something else (and I would put it in a new module, std/kpoll
or similar)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The OS provides some assurances about random numbers that a pure user-space implementation cannot (most importantly that the PRNG will be seeded).
} | ||
switch (errno(system.close(fd))) { | ||
EBADF => unreachable, // Always a race condition. | ||
EINTR => return, // This is still a success. See https://github.com/ziglang/zig/issues/2425 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment here isn't exactly correct. As mentioned in linked issue, some OSes you should retry, others your don't.
Also for OSX we should be calling close_nocancel
here instead (that can be in another PR, but I'd leave a TODO here)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I looked into close_nocancel via experimentation and looking for man pages on OSX and concluded that it's not a thing that exists anymore. Can you point to documentation for it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return inotify_add_watchC(inotify_fd, &pathname_c, mask); | ||
} | ||
|
||
/// Same as `inotify_add_watch` except pathname is null-terminated. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't inotify be under linux?
flags: u32, | ||
fd: fd_t, | ||
offset: isize, | ||
) MMapError![]align(mem.page_size) u8 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🍾
@@ -8,6 +8,11 @@ const meta = std.meta; | |||
const trait = meta.trait; | |||
const testing = std.testing; | |||
|
|||
pub const page_size = switch (builtin.arch) { | |||
.wasm32, .wasm64 => 64 * 1024, | |||
else => 4 * 1024, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no longer just one page size; huge pages are getting used more and more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that's going to have to be a separate and even-more-breaking change if page size stops being comptime known. out of scope for this PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created #2564 to track.
} | ||
} | ||
|
||
pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move to std.time
? Or possibly remove all-together? (on posixy operating systems we should be preferring clock_gettime
, on windows GetSystemTimeAsFileTime
or similar)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's called from std.time for the darwin target. I'm not sure why darwin is treated specially; it does seem that clock_gettime should work there too. maybe @tgschultz knows why
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure why darwin is treated specially; it does seem that clock_gettime should work there too. maybe @tgschultz knows why
From another project I work on https://github.com/wahern/cqueues/blob/36b4b8cf1d8d3e3224cab04357dda3a633c7f0ff/src/cqueues.c#L128 :
OS X didn't implement the
clock_gettime()
POSIX interface until macOS 10.12 (Sierra). But it did provide a monotonic clock throughmach_absolute_time()
. On i386 and x86_64 architectures this clock is in nanosecond units, but not so on other devices.mach_timebase_info()
provides the conversion parameters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
daurnimator's info is indeed why I didn't just call clock_gettime. I thought I left a comment about it in the code.
const prefix = []u16{ '\\', '\\', '?', '\\' }; | ||
const start_index = if (mem.startsWith(u16, wide_slice, prefix)) prefix.len else 0; | ||
|
||
// Trust that Windows gives us valid UTF-16LE. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this PR probably isn't the place for it; but windows allows unpaired surrogates in paths; which are not valid UTF-16LE.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has come up before. Yes this should not trust, even if we do not support using such files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, definitely want to change this, but it also affects master branch. Out of scope for this PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created #2565 to track
const wide_slice = wide_len[0..wide_len]; | ||
|
||
// Windows returns \\?\ prepended to the path. | ||
// We strip it to make this function consistent across platforms. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand this comment. Why do we strip \\?\
? that would be like stripping /
from the start of unix paths.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a like a magic way to pass a flag to the windows syscall. It makes it look at the path-name is a very different way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are many different types of windows paths; the \\?\
prefix is the way to say "I know what I'm doing", and allows you avoid things like windows blowing up on files named CON
.
See https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html?m=1 for more info
} | ||
|
||
/// Spurious wakeups are possible and no precision of timing is guaranteed. | ||
pub fn nanosleep(seconds: u64, nanoseconds: u64) void { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move to std.time
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
std.time
and std.crypto
should use these routines. these are the low-level versions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand your general preference @daurnimator to move stuff out of std/os.zig. I'd like to handle that proposal separately. I think there is something to it, but I also think that the premise of this PR as it stands is sound. The comment at the top says:
// This file contains thin wrappers around OS-specific APIs, with these
// specific goals in mind:
// * Convert "errno"-style error codes into Zig errors.
// * When null-terminated byte buffers are required, provide APIs which accept
// slices as well as APIs which accept null-terminated byte buffers. Same goes
// for UTF-16LE encoding.
// * Where operating systems share APIs, e.g. POSIX, these thin wrappers provide
// cross platform abstracting.
// * When there exists a corresponding libc function and linking libc, the libc
// implementation is used. Exceptions are made for known buggy areas of libc.
// On Linux libc can be side-stepped by using `std.os.linux` directly.
// * For Windows, this file represents the API that libc would provide for
// Windows. For thin wrappers around Windows-specific APIs, see `std.os.windows`.
// Note: The Zig standard library does not support POSIX thread cancellation, and
// in general EINTR is handled by trying again.
In particular 3 of these points apply to nanosleep
:
- nanosleep is a libc function, available on linux, freebsd, macos, netbsd, and probably more. os.nanosleep provides cross platform abstraction over these.
- on linux when not linking libc, os.nanosleep provides abstraction over calling the libc version or the linux syscall directly.
- the possible error of nanosleep is converted to
void
meaning that it cannot fail (by adding "spurious wakeups are possible" to the documented behavior)
On the other hand, the decision to make it work for Windows is questionable. nanosleep
is not a Windows API function, either directly or in msvcrt. os.nanosleep
could have Windows support removed and that logic can go directly in std.time.sleep, and become simpler. That's probably a good idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets continue in #2571
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me (only reviewed Linux changes).
/// in the same directory as dest_path. | ||
/// Destination file will have the same mode as the source file. | ||
pub fn copyFile(source_path: []const u8, dest_path: []const u8) !void { | ||
var in_file = try File.openRead(source_path); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could use copy_file_path
, but that is another bug (i just opened). #2563
/// Returned value is a slice of out_buffer. | ||
/// | ||
/// On Linux, depends on procfs being mounted. If the currently executing binary has | ||
/// been deleted, the file path looks something like `/a/b/c/exe (deleted)`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a funky API, because it is ambiguous.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. That's one reason to make these 3 functions different than each other:
openSelfExe
(always correct on linux even if the currently executing exe has been deleted) See https://stackoverflow.com/questions/28953307/how-to-handle-readlink-of-proc-self-exe-when-executable-is-replaced-during/39001204selfExeDirPath
(always correct on linux even if the currently executing exe has been deleted, because taking the dirname gets rid of(deleted)
)selfExePath
(ambiguous on linux)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
always correct on linux even if the currently executing exe has been deleted
I'm not so sure. e.g. what if you create an in-memory file memfd_create()
and fexecve()
it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@daurnimator check that stack overflow link 😉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
check that stack overflow link wink
I didn't see the answer in there.
FWIW I tried it with the following C program:
#define _GNU_SOURCE
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
char elf[] = {
#include "elf.xxd"
};
int main() {
int fd = memfd_create("foo", MFD_CLOEXEC);
if (fd==-1) return 1;
if (write(fd, elf, sizeof elf) < 0) return 2;
if (fd != 3) return 3;
fd = open("/proc/self/fd/3", O_RDONLY);
if (-1 == fd) return 4;
if (-1 == close(3)) return 5;
char *cmd[] = { "ls", "-l", "/proc/self/fd/", "/proc/self/exe", NULL };
char *env[] = { NULL };
if (-1 == fexecve(fd, cmd, env)) return 6;
return 7;
}
Where elf.xxd
is created with xxd -i < /usr/bin/ls > elf.xxd
And I see:
lrwxrwxrwx 1 root root 0 May 27 16:15 /proc/self/exe -> '/memfd:foo (deleted)'
/proc/self/fd:
total 0
lrwx------ 1 root root 64 May 27 16:15 0 -> /dev/pts/1
lrwx------ 1 root root 64 May 27 16:15 1 -> /dev/pts/1
lrwx------ 1 root root 64 May 27 16:15 2 -> /dev/pts/1
lr-x------ 1 root root 64 May 27 16:15 3 -> /proc/18364/fd
lr-x------ 1 root root 64 May 27 16:15 4 -> '/memfd:foo (deleted)'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm working on a Linux patch to make this unambiguous. It will be a hack, but it will not require another syscall.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh apologies. I didn't understand what you were getting at, but now I do. (The winky face was just because the stack overflow answer was authored by me a few years ago and I thought that was amusing.) This is a good find - something that also affects master branch. I believe @shawnl is working on a solution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created #2570 to track
@@ -0,0 +1,405 @@ | |||
// arm64-specific declarations that are intended to be imported into the POSIX namespace. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
kinda annoying that github doesn't recognize renames the way git does. (and yes i see you added new syscalls)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think I added new syscalls. I don't remember doing that, and I just did a diff with this file and the corresponding one in master branch. I only tried to fix rename/renameat & friends.
return sum; | ||
} | ||
|
||
// TODO port these over |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sigset support is similarly lacking.
@@ -9923,6 +9923,7 @@ keyword <- KEYWORD_align / KEYWORD_and / KEYWORD_allowzero / KEYWORD_anyerror | |||
<li>Avoid local maximums.</li> | |||
<li>Reduce the amount one must remember.</li> | |||
<li>Minimize energy spent on coding style.</li> | |||
<li>Resource deallocation must succeed.</li> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to update the zen in src-self-hosted/main.zig
?
OutOfMemory, | ||
/// Used to convert a slice to a null terminated slice on the stack. | ||
/// TODO https://github.com/ziglang/zig/issues/287 | ||
pub fn toPosixPath(file_path: []const u8) ![PATH_MAX]u8 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PATH_MAX
doesn't exist on windows and should be MAX_PATH
instead.
const path_w = try windows.cStrToPrefixedFileW(path); | ||
return openWriteNoClobberW(&path_w, file_mode); | ||
} | ||
const flags = os.O_LARGEFILE | os.O_WRONLY | os.O_CREAT | os.O_CLOEXEC | os.O_EXCL; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these are not defined on windows for some reason.
pub fn posixDup2(old_fd: i32, new_fd: i32) !void { | ||
const rc = system.open(file_path, flags, perm); | ||
switch (errno(rc)) { | ||
0 => return @intCast(fd_t, rc), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fd_t
is *c_void
on windows.
Unexpected, | ||
}; | ||
|
||
pub fn fstat(fd: fd_t) FStatError!Stat { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stat
doesn't exist on windows.
Thanks everybody for your comments. Tests are passing now and I'm going to merge this into master. If this breaks something for you and simple std lib changes are needed to fix the problem please submit PRs and I will merge them immediately. If your comments didn't get addressed in this PR please open separate issues for them so that they don't get lost. |
This is a very breaking change. I apologize in advance - everybody is going to have to update their code, and the untested regions of the standard library probably have compile errors lurking in them. The good news is the new APIs are more stable and future-proof than before.
The main driving issue behind this pull request is #2380. However it solves a few other issues, which I will note below.
std.os
has been moved to other places, as suggested by @jayschwa. These are the APIs that are intended to be used by applications.std.fs
.std.os.path
is nowstd.fs.path
.std.os.ChildProcess
is nowstd.ChildProcess
std.process
. this is also where the concept of the current working directory lives.std.os.Thread
is moved tostd.Thread
.std.os.spawnThread
is moved tostd.Thread.spawn
.std.os.time
is moved tostd.time
.std.os.getRandomBytes
is moved tostd.crypto.randomBytes
.std.cstr.toSlice
,std.cstr.toSliceConst
, andstd.cstr.len
are removed. Instead use, respectively,std.mem.toSlice
,std.mem.toSliceConst
, andstd.mem.len
. The idea here is that after proposal: type for null terminated pointer #265, null-terminated byte buffers does not necessarily mean it's a "C string". It's just a different way of arranging memory.std.os.system
when linking libc is aliased tostd.c
. When not linking libc, which applies to targets linux, windows, wasi,std.os.system
is aliased tostd.os.linux
,std.os.windows
,std.os.wasi
respectively.std.os
is thin wrappers around operating system API that provide zig-style parameters and error sets. These wrappers are cross platform where applicable, for example posix functions, andstd.os.exit
. These wrappers call into libc when linking libc.std.os.linux
andstd.os.windows
etc can be used directly to bypass libc, even when linking libc.std.os
also has all the OS constants and types, such asiovec
. These are declared instd/os/bits.zig
with target-specific additions instd/os/bits/*
. The linux ones are compatible when compiling with and without libc.@hasDecl
to check for the existence of syscalls to make the appropriate syscall. For example, some systems do not haverename
and some systems do not haverenameat
. Zig standard library now does the correct thing for both. This fixes zig fmt failure in Window Subsystem for Linux #2481.std.os.windows
functions have been moved to be namespaced under the DLL they are found in.std.os.windows
still has all the values and types, however.std.os.windows
has new functions which are thin wrappers around Windows API that provide Zig-style parameters and error sets.I changed my mind about "glue code to work around kernel API footguns". I think this belongs in the OS-specific API layer.
I also changed my mind about "Make the OS-specific files do exactly what they say. E.g. if you call std.os.linux.fork, that is going to do the fork syscall when you observe the program with strace, guaranteed." Upon close inspection I believe the way @shawnl did it makes sense - it just needed the
@hasDecl
improvement, which Shawn has been trying to get added into Zig. I apologize for my wavering on this issue; I believe the code in this PR gets it right.This pull request also fixes #2415.
It also fixes #2425.
What's left to do