mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-04-27 19:09:47 +03:00
39a6d5d1c5
On Windows, we need to know ahead of time whether a file was opened in synchronous mode or asynchronous mode. There may be advantages to tracking this state for POSIX operating systems as well.
543 lines
17 KiB
Zig
543 lines
17 KiB
Zig
const builtin = @import("builtin");
|
|
const native_os = builtin.target.os.tag;
|
|
const AtomicRmwOp = std.builtin.AtomicRmwOp;
|
|
const AtomicOrder = std.builtin.AtomicOrder;
|
|
|
|
const std = @import("../std.zig");
|
|
const Io = std.Io;
|
|
const Dir = std.Io.Dir;
|
|
const posix = std.posix;
|
|
const mem = std.mem;
|
|
const elf = std.elf;
|
|
const linux = std.os.linux;
|
|
const AT = std.posix.AT;
|
|
|
|
const testing = std.testing;
|
|
const expect = std.testing.expect;
|
|
const expectEqual = std.testing.expectEqual;
|
|
const expectEqualSlices = std.testing.expectEqualSlices;
|
|
const expectEqualStrings = std.testing.expectEqualStrings;
|
|
const expectError = std.testing.expectError;
|
|
const tmpDir = std.testing.tmpDir;
|
|
|
|
const fstest = @import("../fs/test.zig");
|
|
|
|
test "check WASI CWD" {
|
|
if (native_os == .wasi) {
|
|
const cwd: Dir = .cwd();
|
|
if (cwd.handle != 3) {
|
|
@panic("WASI code that uses cwd (like this test) needs a preopen for cwd (add '--dir=.' to wasmtime)");
|
|
}
|
|
if (!builtin.link_libc) {
|
|
// WASI without-libc hardcodes fd 3 as the FDCWD token so it can be passed directly to WASI calls
|
|
try expectEqual(3, posix.AT.FDCWD);
|
|
}
|
|
}
|
|
}
|
|
|
|
test "getuid" {
|
|
if (native_os == .windows or native_os == .wasi) return error.SkipZigTest;
|
|
_ = posix.system.getuid();
|
|
_ = posix.system.geteuid();
|
|
}
|
|
|
|
test "getgid" {
|
|
if (native_os == .windows or native_os == .wasi) return error.SkipZigTest;
|
|
_ = posix.system.getgid();
|
|
_ = posix.system.getegid();
|
|
}
|
|
|
|
test "sigaltstack" {
|
|
if (native_os == .windows or native_os == .wasi) return error.SkipZigTest;
|
|
|
|
var st: posix.stack_t = undefined;
|
|
try posix.sigaltstack(null, &st);
|
|
// Setting a stack size less than MINSIGSTKSZ returns ENOMEM
|
|
st.flags = 0;
|
|
st.size = 1;
|
|
try expectError(error.SizeTooSmall, posix.sigaltstack(&st, null));
|
|
}
|
|
|
|
// If the type is not available use void to avoid erroring out when `iter_fn` is
|
|
// analyzed
|
|
const have_dl_phdr_info = posix.system.dl_phdr_info != void;
|
|
const dl_phdr_info = if (have_dl_phdr_info) posix.dl_phdr_info else anyopaque;
|
|
|
|
const IterFnError = error{
|
|
MissingPtLoadSegment,
|
|
MissingLoad,
|
|
BadElfMagic,
|
|
FailedConsistencyCheck,
|
|
};
|
|
|
|
fn iter_fn(info: *dl_phdr_info, size: usize, counter: *usize) IterFnError!void {
|
|
_ = size;
|
|
// Count how many libraries are loaded
|
|
counter.* += @as(usize, 1);
|
|
|
|
// The image should contain at least a PT_LOAD segment
|
|
if (info.phnum < 1) return error.MissingPtLoadSegment;
|
|
|
|
// Quick & dirty validation of the phdr pointers, make sure we're not
|
|
// pointing to some random gibberish
|
|
var i: usize = 0;
|
|
var found_load = false;
|
|
while (i < info.phnum) : (i += 1) {
|
|
const phdr = info.phdr[i];
|
|
|
|
if (phdr.type != .LOAD) continue;
|
|
|
|
const reloc_addr = info.addr + phdr.vaddr;
|
|
// Find the ELF header
|
|
const elf_header = @as(*elf.Ehdr, @ptrFromInt(reloc_addr - phdr.offset));
|
|
// Validate the magic
|
|
if (!mem.eql(u8, elf_header.e_ident[0..4], elf.MAGIC)) return error.BadElfMagic;
|
|
// Consistency check
|
|
if (elf_header.e_phnum != info.phnum) return error.FailedConsistencyCheck;
|
|
|
|
found_load = true;
|
|
break;
|
|
}
|
|
|
|
if (!found_load) return error.MissingLoad;
|
|
}
|
|
|
|
test "dl_iterate_phdr" {
|
|
if (builtin.object_format != .elf) return error.SkipZigTest;
|
|
|
|
var counter: usize = 0;
|
|
try posix.dl_iterate_phdr(&counter, IterFnError, iter_fn);
|
|
try expect(counter != 0);
|
|
}
|
|
|
|
test "gethostname" {
|
|
if (native_os == .windows or native_os == .wasi)
|
|
return error.SkipZigTest;
|
|
|
|
var buf: [posix.HOST_NAME_MAX]u8 = undefined;
|
|
const hostname = try posix.gethostname(&buf);
|
|
try expect(hostname.len != 0);
|
|
}
|
|
|
|
test "pipe" {
|
|
if (native_os == .windows or native_os == .wasi)
|
|
return error.SkipZigTest;
|
|
|
|
const io = testing.io;
|
|
|
|
const fds = try std.Io.Threaded.pipe2(.{});
|
|
const out: Io.File = .{ .handle = fds[0], .flags = .{ .nonblocking = false } };
|
|
const in: Io.File = .{ .handle = fds[1], .flags = .{ .nonblocking = false } };
|
|
try in.writeStreamingAll(io, "hello");
|
|
var buf: [16]u8 = undefined;
|
|
try expect((try out.readStreaming(io, &.{&buf})) == 5);
|
|
|
|
try expectEqualSlices(u8, buf[0..5], "hello");
|
|
out.close(io);
|
|
in.close(io);
|
|
}
|
|
|
|
test "memfd_create" {
|
|
const io = testing.io;
|
|
|
|
// memfd_create is only supported by linux and freebsd.
|
|
switch (native_os) {
|
|
.linux => {},
|
|
.freebsd => {
|
|
if (comptime builtin.os.version_range.semver.max.order(.{ .major = 13, .minor = 0, .patch = 0 }) == .lt)
|
|
return error.SkipZigTest;
|
|
},
|
|
else => return error.SkipZigTest,
|
|
}
|
|
|
|
const file: Io.File = .{
|
|
.handle = try posix.memfd_create("test", 0),
|
|
.flags = .{ .nonblocking = false },
|
|
};
|
|
defer file.close(io);
|
|
try file.writePositionalAll(io, "test", 0);
|
|
|
|
var buf: [10]u8 = undefined;
|
|
const bytes_read = try file.readPositionalAll(io, &buf, 0);
|
|
try expect(bytes_read == 4);
|
|
try expectEqualStrings("test", buf[0..4]);
|
|
}
|
|
|
|
test "mmap" {
|
|
if (native_os == .windows or native_os == .wasi)
|
|
return error.SkipZigTest;
|
|
|
|
const io = testing.io;
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
// Simple mmap() call with non page-aligned size
|
|
{
|
|
const data = try posix.mmap(
|
|
null,
|
|
1234,
|
|
.{ .READ = true, .WRITE = true },
|
|
.{ .TYPE = .PRIVATE, .ANONYMOUS = true },
|
|
-1,
|
|
0,
|
|
);
|
|
defer posix.munmap(data);
|
|
|
|
try expectEqual(@as(usize, 1234), data.len);
|
|
|
|
// By definition the data returned by mmap is zero-filled
|
|
try expect(mem.eql(u8, data, &[_]u8{0x00} ** 1234));
|
|
|
|
// Make sure the memory is writeable as requested
|
|
@memset(data, 0x55);
|
|
try expect(mem.eql(u8, data, &[_]u8{0x55} ** 1234));
|
|
}
|
|
|
|
const test_out_file = "os_tmp_test";
|
|
// Must be a multiple of the page size so that the test works with mmap2
|
|
const alloc_size = 8 * std.heap.pageSize();
|
|
|
|
// Create a file used for testing mmap() calls with a file descriptor
|
|
{
|
|
const file = try tmp.dir.createFile(io, test_out_file, .{});
|
|
defer file.close(io);
|
|
|
|
var stream = file.writer(io, &.{});
|
|
|
|
var i: usize = 0;
|
|
while (i < alloc_size / @sizeOf(u32)) : (i += 1) {
|
|
try stream.interface.writeInt(u32, @intCast(i), .little);
|
|
}
|
|
}
|
|
|
|
// Map the whole file
|
|
{
|
|
const file = try tmp.dir.openFile(io, test_out_file, .{});
|
|
defer file.close(io);
|
|
|
|
const data = try posix.mmap(
|
|
null,
|
|
alloc_size,
|
|
.{ .READ = true },
|
|
.{ .TYPE = .PRIVATE },
|
|
file.handle,
|
|
0,
|
|
);
|
|
defer posix.munmap(data);
|
|
|
|
var stream: std.Io.Reader = .fixed(data);
|
|
|
|
var i: usize = 0;
|
|
while (i < alloc_size / @sizeOf(u32)) : (i += 1) {
|
|
try expectEqual(i, try stream.takeInt(u32, .little));
|
|
}
|
|
}
|
|
|
|
if (builtin.cpu.arch == .hexagon) return error.SkipZigTest;
|
|
|
|
// Map the upper half of the file
|
|
{
|
|
const file = try tmp.dir.openFile(io, test_out_file, .{});
|
|
defer file.close(io);
|
|
|
|
const data = try posix.mmap(
|
|
null,
|
|
alloc_size / 2,
|
|
.{ .READ = true },
|
|
.{ .TYPE = .PRIVATE },
|
|
file.handle,
|
|
alloc_size / 2,
|
|
);
|
|
defer posix.munmap(data);
|
|
|
|
var stream: std.Io.Reader = .fixed(data);
|
|
|
|
var i: usize = alloc_size / 2 / @sizeOf(u32);
|
|
while (i < alloc_size / @sizeOf(u32)) : (i += 1) {
|
|
try expectEqual(i, try stream.takeInt(u32, .little));
|
|
}
|
|
}
|
|
}
|
|
|
|
test "fcntl" {
|
|
if (native_os == .windows or native_os == .wasi)
|
|
return error.SkipZigTest;
|
|
|
|
const io = testing.io;
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
const test_out_file = "os_tmp_test";
|
|
|
|
const file = try tmp.dir.createFile(io, test_out_file, .{});
|
|
defer file.close(io);
|
|
|
|
// Note: The test assumes createFile opens the file with CLOEXEC
|
|
{
|
|
const flags = posix.system.fcntl(file.handle, posix.F.GETFD, @as(usize, 0));
|
|
try expect((flags & posix.FD_CLOEXEC) != 0);
|
|
}
|
|
{
|
|
_ = posix.system.fcntl(file.handle, posix.F.SETFD, @as(usize, 0));
|
|
const flags = posix.system.fcntl(file.handle, posix.F.GETFD, @as(usize, 0));
|
|
try expect((flags & posix.FD_CLOEXEC) == 0);
|
|
}
|
|
{
|
|
_ = posix.system.fcntl(file.handle, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC));
|
|
const flags = posix.system.fcntl(file.handle, posix.F.GETFD, @as(usize, 0));
|
|
try expect((flags & posix.FD_CLOEXEC) != 0);
|
|
}
|
|
}
|
|
|
|
test "signalfd" {
|
|
switch (native_os) {
|
|
.linux, .illumos => {},
|
|
else => return error.SkipZigTest,
|
|
}
|
|
_ = &posix.signalfd;
|
|
}
|
|
|
|
test "sync" {
|
|
if (native_os != .linux)
|
|
return error.SkipZigTest;
|
|
|
|
// Unfortunately, we cannot safely call `sync` or `syncfs`, because if file IO is happening
|
|
// than the system can commit the results to disk, such calls could block indefinitely.
|
|
|
|
_ = &posix.sync;
|
|
_ = &posix.syncfs;
|
|
}
|
|
|
|
test "fsync" {
|
|
switch (native_os) {
|
|
.linux, .illumos => {},
|
|
else => return error.SkipZigTest,
|
|
}
|
|
|
|
const io = testing.io;
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
const test_out_file = "os_tmp_test";
|
|
const file = try tmp.dir.createFile(io, test_out_file, .{});
|
|
defer file.close(io);
|
|
|
|
try file.sync(io);
|
|
try posix.fdatasync(file.handle);
|
|
}
|
|
|
|
test "getrlimit and setrlimit" {
|
|
if (posix.system.rlimit_resource == void) return error.SkipZigTest;
|
|
|
|
inline for (@typeInfo(posix.rlimit_resource).@"enum".fields) |field| {
|
|
const resource: posix.rlimit_resource = @enumFromInt(field.value);
|
|
const limit = try posix.getrlimit(resource);
|
|
|
|
// XNU kernel does not support RLIMIT_STACK if a custom stack is active,
|
|
// which looks to always be the case. EINVAL is returned.
|
|
// See https://github.com/apple-oss-distributions/xnu/blob/5e3eaea39dcf651e66cb99ba7d70e32cc4a99587/bsd/kern/kern_resource.c#L1173
|
|
if (native_os.isDarwin() and resource == .STACK) {
|
|
continue;
|
|
}
|
|
|
|
// On 32 bit MIPS musl includes a fix which changes limits greater than -1UL/2 to RLIM_INFINITY.
|
|
// See http://git.musl-libc.org/cgit/musl/commit/src/misc/getrlimit.c?id=8258014fd1e34e942a549c88c7e022a00445c352
|
|
//
|
|
// This happens for example if RLIMIT_MEMLOCK is bigger than ~2GiB.
|
|
// In that case the following the limit would be RLIM_INFINITY and the following setrlimit fails with EPERM.
|
|
if (builtin.cpu.arch.isMIPS() and builtin.link_libc) {
|
|
if (limit.cur != linux.RLIM.INFINITY) {
|
|
try posix.setrlimit(resource, limit);
|
|
}
|
|
} else {
|
|
try posix.setrlimit(resource, limit);
|
|
}
|
|
}
|
|
}
|
|
|
|
test "sigrtmin/max" {
|
|
if (native_os.isDarwin() or switch (native_os) {
|
|
.wasi, .windows, .openbsd, .dragonfly => true,
|
|
else => false,
|
|
}) return error.SkipZigTest;
|
|
|
|
try expect(posix.sigrtmin() >= 32);
|
|
try expect(posix.sigrtmin() >= posix.system.sigrtmin());
|
|
try expect(posix.sigrtmin() < posix.system.sigrtmax());
|
|
}
|
|
|
|
test "sigset empty/full" {
|
|
if (native_os == .wasi or native_os == .windows)
|
|
return error.SkipZigTest;
|
|
|
|
var set: posix.sigset_t = posix.sigemptyset();
|
|
for (1..posix.NSIG) |i| {
|
|
const sig = std.enums.fromInt(posix.SIG, i) orelse continue;
|
|
try expectEqual(false, posix.sigismember(&set, sig));
|
|
}
|
|
|
|
// The C library can reserve some (unnamed) signals, so can't check the full
|
|
// NSIG set is defined, but just test a couple:
|
|
set = posix.sigfillset();
|
|
try expectEqual(true, posix.sigismember(&set, .CHLD));
|
|
try expectEqual(true, posix.sigismember(&set, .INT));
|
|
}
|
|
|
|
// Some signals (i.e., 32 - 34 on glibc/musl) are not allowed to be added to a
|
|
// sigset by the C library, so avoid testing them.
|
|
fn reserved_signo(i: usize) bool {
|
|
if (native_os.isDarwin()) return false;
|
|
if (!builtin.link_libc) return false;
|
|
const max = if (native_os == .netbsd) 32 else 31;
|
|
if (i > max) return true;
|
|
if (native_os == .openbsd or native_os == .dragonfly) return false; // no RT signals
|
|
return i < posix.sigrtmin();
|
|
}
|
|
|
|
test "sigset add/del" {
|
|
if (native_os == .wasi or native_os == .windows)
|
|
return error.SkipZigTest;
|
|
|
|
var sigset: posix.sigset_t = posix.sigemptyset();
|
|
|
|
// See that none are set, then set each one, see that they're all set, then
|
|
// remove them all, and then see that none are set.
|
|
for (1..posix.NSIG) |i| {
|
|
const sig = std.enums.fromInt(posix.SIG, i) orelse continue;
|
|
try expectEqual(false, posix.sigismember(&sigset, sig));
|
|
}
|
|
for (1..posix.NSIG) |i| {
|
|
if (!reserved_signo(i)) {
|
|
const sig = std.enums.fromInt(posix.SIG, i) orelse continue;
|
|
posix.sigaddset(&sigset, sig);
|
|
}
|
|
}
|
|
for (1..posix.NSIG) |i| {
|
|
if (!reserved_signo(i)) {
|
|
const sig = std.enums.fromInt(posix.SIG, i) orelse continue;
|
|
try expectEqual(true, posix.sigismember(&sigset, sig));
|
|
}
|
|
}
|
|
for (1..posix.NSIG) |i| {
|
|
if (!reserved_signo(i)) {
|
|
const sig = std.enums.fromInt(posix.SIG, i) orelse continue;
|
|
posix.sigdelset(&sigset, sig);
|
|
}
|
|
}
|
|
for (1..posix.NSIG) |i| {
|
|
const sig = std.enums.fromInt(posix.SIG, i) orelse continue;
|
|
try expectEqual(false, posix.sigismember(&sigset, sig));
|
|
}
|
|
}
|
|
|
|
test "getpid" {
|
|
if (native_os == .wasi) return error.SkipZigTest;
|
|
if (native_os == .windows) return error.SkipZigTest;
|
|
|
|
try expect(posix.system.getpid() != 0);
|
|
}
|
|
|
|
test "getppid" {
|
|
if (native_os == .wasi) return error.SkipZigTest;
|
|
if (native_os == .windows) return error.SkipZigTest;
|
|
if (native_os == .plan9 and !builtin.link_libc) return error.SkipZigTest;
|
|
|
|
try expect(posix.getppid() >= 0);
|
|
}
|
|
|
|
test "rename smoke test" {
|
|
if (native_os == .windows) return error.SkipZigTest;
|
|
if (!fstest.isRealPathSupported()) return error.SkipZigTest;
|
|
|
|
const io = testing.io;
|
|
const gpa = testing.allocator;
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
const base_path = try tmp.dir.realPathFileAlloc(io, ".", gpa);
|
|
defer gpa.free(base_path);
|
|
|
|
const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666;
|
|
|
|
{
|
|
// Create some file using `open`.
|
|
const file_path = try Dir.path.join(gpa, &.{ base_path, "some_file" });
|
|
defer gpa.free(file_path);
|
|
const file = try Io.Dir.cwd().createFile(io, file_path, .{
|
|
.read = true,
|
|
.exclusive = true,
|
|
.permissions = .fromMode(mode),
|
|
});
|
|
file.close(io);
|
|
|
|
// Rename the file
|
|
const new_file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_file" });
|
|
defer gpa.free(new_file_path);
|
|
try Io.Dir.renameAbsolute(file_path, new_file_path, io);
|
|
}
|
|
|
|
{
|
|
// Try opening renamed file
|
|
const file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_file" });
|
|
defer gpa.free(file_path);
|
|
const file = try Io.Dir.cwd().openFile(io, file_path, .{ .mode = .read_write });
|
|
file.close(io);
|
|
}
|
|
|
|
{
|
|
// Try opening original file - should fail with error.FileNotFound
|
|
const file_path = try Dir.path.join(gpa, &.{ base_path, "some_file" });
|
|
defer gpa.free(file_path);
|
|
try expectError(error.FileNotFound, Io.Dir.cwd().openFile(io, file_path, .{ .mode = .read_write }));
|
|
}
|
|
|
|
{
|
|
// Create some directory
|
|
const file_path = try Dir.path.join(gpa, &.{ base_path, "some_dir" });
|
|
defer gpa.free(file_path);
|
|
try Io.Dir.createDirAbsolute(io, file_path, .fromMode(mode));
|
|
|
|
// Rename the directory
|
|
const new_file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_dir" });
|
|
defer gpa.free(new_file_path);
|
|
try Io.Dir.renameAbsolute(file_path, new_file_path, io);
|
|
}
|
|
|
|
{
|
|
// Try opening renamed directory
|
|
const file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_dir" });
|
|
defer gpa.free(file_path);
|
|
const dir = try Io.Dir.cwd().openDir(io, file_path, .{});
|
|
dir.close(io);
|
|
}
|
|
|
|
{
|
|
// Try opening original directory - should fail with error.FileNotFound
|
|
const file_path = try Dir.path.join(gpa, &.{ base_path, "some_dir" });
|
|
defer gpa.free(file_path);
|
|
try expectError(error.FileNotFound, Io.Dir.cwd().openDir(io, file_path, .{}));
|
|
}
|
|
}
|
|
|
|
test "timerfd" {
|
|
if (native_os != .linux) return error.SkipZigTest;
|
|
|
|
const tfd = try posix.timerfd_create(.MONOTONIC, .{ .CLOEXEC = true });
|
|
defer posix.close(tfd);
|
|
|
|
// Fire event 10_000_000ns = 10ms after the posix.timerfd_settime call.
|
|
var sit: linux.itimerspec = .{ .it_interval = .{ .sec = 0, .nsec = 0 }, .it_value = .{ .sec = 0, .nsec = 10 * (1000 * 1000) } };
|
|
try posix.timerfd_settime(tfd, .{}, &sit, null);
|
|
|
|
var fds: [1]posix.pollfd = .{.{ .fd = tfd, .events = linux.POLL.IN, .revents = 0 }};
|
|
try expectEqual(@as(usize, 1), try posix.poll(&fds, -1)); // -1 => infinite waiting
|
|
|
|
const git = try posix.timerfd_gettime(tfd);
|
|
const expect_disarmed_timer: linux.itimerspec = .{ .it_interval = .{ .sec = 0, .nsec = 0 }, .it_value = .{ .sec = 0, .nsec = 0 } };
|
|
try expectEqual(expect_disarmed_timer, git);
|
|
}
|