mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 06:13:07 +00:00
Four tests in lib/std/posix/test.zig were disabled because they created fixed-name files in the current working directory, and this caused problems if tests were running in parallel with other build's tests. This PR fixes those tests to all use `std.testing.tmpDir` to create unique temporary names and directories. Also clean the tests up to more consistently use `defer` to clean up, or to just rely on tmpDir cleanup to remove individual files. Working on these tests revealed a bunch of stale WASI code paths in posix.zig, fixed by replacing stale `wast.AT.FDCWD` references with just `AT.FDCWD`. Fixes #14968.
1341 lines
44 KiB
Zig
1341 lines
44 KiB
Zig
const std = @import("../std.zig");
|
|
const posix = std.posix;
|
|
const testing = std.testing;
|
|
const expect = testing.expect;
|
|
const expectEqual = testing.expectEqual;
|
|
const expectError = testing.expectError;
|
|
const io = std.io;
|
|
const fs = std.fs;
|
|
const mem = std.mem;
|
|
const elf = std.elf;
|
|
const File = std.fs.File;
|
|
const Thread = std.Thread;
|
|
const linux = std.os.linux;
|
|
|
|
const a = std.testing.allocator;
|
|
|
|
const builtin = @import("builtin");
|
|
const AtomicRmwOp = std.builtin.AtomicRmwOp;
|
|
const AtomicOrder = std.builtin.AtomicOrder;
|
|
const native_os = builtin.target.os.tag;
|
|
const tmpDir = std.testing.tmpDir;
|
|
const Dir = std.fs.Dir;
|
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
|
|
|
// https://github.com/ziglang/zig/issues/20288
|
|
test "WTF-8 to WTF-16 conversion buffer overflows" {
|
|
if (native_os != .windows) return error.SkipZigTest;
|
|
|
|
const input_wtf8 = "\u{10FFFF}" ** 16385;
|
|
try expectError(error.NameTooLong, posix.chdir(input_wtf8));
|
|
try expectError(error.NameTooLong, posix.chdirZ(input_wtf8));
|
|
}
|
|
|
|
test "check WASI CWD" {
|
|
if (native_os == .wasi) {
|
|
if (std.options.wasiCwd() != 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 "chdir absolute parent" {
|
|
if (native_os == .wasi) return error.SkipZigTest;
|
|
|
|
// Restore default CWD at end of test.
|
|
const orig_cwd = try fs.cwd().openDir(".", .{});
|
|
defer orig_cwd.setAsCwd() catch unreachable;
|
|
|
|
// Get current working directory path
|
|
var old_cwd_buf: [fs.max_path_bytes]u8 = undefined;
|
|
const old_cwd = try posix.getcwd(old_cwd_buf[0..]);
|
|
|
|
{
|
|
// Firstly, changing to itself should have no effect
|
|
try posix.chdir(old_cwd);
|
|
var new_cwd_buf: [fs.max_path_bytes]u8 = undefined;
|
|
const new_cwd = try posix.getcwd(new_cwd_buf[0..]);
|
|
try expect(mem.eql(u8, old_cwd, new_cwd));
|
|
}
|
|
|
|
// Next, change current working directory to one level above
|
|
const parent = fs.path.dirname(old_cwd) orelse unreachable; // old_cwd should be absolute
|
|
try posix.chdir(parent);
|
|
|
|
var new_cwd_buf: [fs.max_path_bytes]u8 = undefined;
|
|
const new_cwd = try posix.getcwd(new_cwd_buf[0..]);
|
|
try expect(mem.eql(u8, parent, new_cwd));
|
|
}
|
|
|
|
test "chdir relative" {
|
|
if (native_os == .wasi) return error.SkipZigTest;
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
// Restore default CWD at end of test.
|
|
const orig_cwd = try fs.cwd().openDir(".", .{});
|
|
defer orig_cwd.setAsCwd() catch unreachable;
|
|
|
|
// Use the tmpDir parent_dir as the "base" for the test. Then cd into the child
|
|
try tmp.parent_dir.setAsCwd();
|
|
|
|
// Capture base working directory path, to build expected full path
|
|
var base_cwd_buf: [fs.max_path_bytes]u8 = undefined;
|
|
const base_cwd = try posix.getcwd(base_cwd_buf[0..]);
|
|
|
|
const dir_name = &tmp.sub_path;
|
|
const expected_path = try fs.path.resolve(a, &.{ base_cwd, dir_name });
|
|
defer a.free(expected_path);
|
|
|
|
// change current working directory to new directory
|
|
try posix.chdir(dir_name);
|
|
|
|
var new_cwd_buf: [fs.max_path_bytes]u8 = undefined;
|
|
const new_cwd = try posix.getcwd(new_cwd_buf[0..]);
|
|
|
|
// On Windows, fs.path.resolve returns an uppercase drive letter, but the drive letter returned by getcwd may be lowercase
|
|
const resolved_cwd = try fs.path.resolve(a, &.{new_cwd});
|
|
defer a.free(resolved_cwd);
|
|
|
|
try expect(mem.eql(u8, expected_path, resolved_cwd));
|
|
}
|
|
|
|
test "open smoke test" {
|
|
if (native_os == .wasi) return error.SkipZigTest;
|
|
if (native_os == .windows) return error.SkipZigTest;
|
|
|
|
// TODO verify file attributes using `fstat`
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
// Get base abs path
|
|
var arena = ArenaAllocator.init(testing.allocator);
|
|
defer arena.deinit();
|
|
const allocator = arena.allocator();
|
|
|
|
const base_path = blk: {
|
|
const relative_path = try fs.path.join(allocator, &[_][]const u8{ ".zig-cache", "tmp", tmp.sub_path[0..] });
|
|
break :blk try fs.realpathAlloc(allocator, relative_path);
|
|
};
|
|
|
|
var file_path: []u8 = undefined;
|
|
var fd: posix.fd_t = undefined;
|
|
const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666;
|
|
|
|
// Create some file using `open`.
|
|
file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
|
|
fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode);
|
|
posix.close(fd);
|
|
|
|
// Try this again with the same flags. This op should fail with error.PathAlreadyExists.
|
|
file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
|
|
try expectError(error.PathAlreadyExists, posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode));
|
|
|
|
// Try opening without `EXCL` flag.
|
|
file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
|
|
fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true }, mode);
|
|
posix.close(fd);
|
|
|
|
// Try opening as a directory which should fail.
|
|
file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
|
|
try expectError(error.NotDir, posix.open(file_path, .{ .ACCMODE = .RDWR, .DIRECTORY = true }, mode));
|
|
|
|
// Create some directory
|
|
file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" });
|
|
try posix.mkdir(file_path, mode);
|
|
|
|
// Open dir using `open`
|
|
file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" });
|
|
fd = try posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode);
|
|
posix.close(fd);
|
|
|
|
// Try opening as file which should fail.
|
|
file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" });
|
|
try expectError(error.IsDir, posix.open(file_path, .{ .ACCMODE = .RDWR }, mode));
|
|
}
|
|
|
|
test "openat smoke test" {
|
|
if (native_os == .windows) return error.SkipZigTest;
|
|
|
|
// TODO verify file attributes using `fstatat`
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
var fd: posix.fd_t = undefined;
|
|
const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666;
|
|
|
|
// Create some file using `openat`.
|
|
fd = try posix.openat(tmp.dir.fd, "some_file", CommonOpenFlags.lower(.{
|
|
.ACCMODE = .RDWR,
|
|
.CREAT = true,
|
|
.EXCL = true,
|
|
}), mode);
|
|
posix.close(fd);
|
|
|
|
// Try this again with the same flags. This op should fail with error.PathAlreadyExists.
|
|
try expectError(error.PathAlreadyExists, posix.openat(tmp.dir.fd, "some_file", CommonOpenFlags.lower(.{
|
|
.ACCMODE = .RDWR,
|
|
.CREAT = true,
|
|
.EXCL = true,
|
|
}), mode));
|
|
|
|
// Try opening without `EXCL` flag.
|
|
fd = try posix.openat(tmp.dir.fd, "some_file", CommonOpenFlags.lower(.{
|
|
.ACCMODE = .RDWR,
|
|
.CREAT = true,
|
|
}), mode);
|
|
posix.close(fd);
|
|
|
|
// Try opening as a directory which should fail.
|
|
try expectError(error.NotDir, posix.openat(tmp.dir.fd, "some_file", CommonOpenFlags.lower(.{
|
|
.ACCMODE = .RDWR,
|
|
.DIRECTORY = true,
|
|
}), mode));
|
|
|
|
// Create some directory
|
|
try posix.mkdirat(tmp.dir.fd, "some_dir", mode);
|
|
|
|
// Open dir using `open`
|
|
fd = try posix.openat(tmp.dir.fd, "some_dir", CommonOpenFlags.lower(.{
|
|
.ACCMODE = .RDONLY,
|
|
.DIRECTORY = true,
|
|
}), mode);
|
|
posix.close(fd);
|
|
|
|
// Try opening as file which should fail (skip on wasi+libc due to
|
|
// https://github.com/bytecodealliance/wasmtime/issues/9054)
|
|
if (native_os != .wasi or !builtin.link_libc) {
|
|
try expectError(error.IsDir, posix.openat(tmp.dir.fd, "some_dir", CommonOpenFlags.lower(.{
|
|
.ACCMODE = .RDWR,
|
|
}), mode));
|
|
}
|
|
}
|
|
|
|
test "symlink with relative paths" {
|
|
if (native_os == .wasi) return error.SkipZigTest; // Can symlink, but can't change into tmpDir
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
const target_name = "symlink-target";
|
|
const symlink_name = "symlinker";
|
|
|
|
// Restore default CWD at end of test.
|
|
const orig_cwd = try fs.cwd().openDir(".", .{});
|
|
defer orig_cwd.setAsCwd() catch unreachable;
|
|
|
|
// Create the target file
|
|
try tmp.dir.writeFile(.{ .sub_path = target_name, .data = "nonsense" });
|
|
|
|
// Want to test relative paths, so cd into the tmpdir for this test
|
|
try tmp.dir.setAsCwd();
|
|
|
|
if (native_os == .windows) {
|
|
const wtarget_name = try std.unicode.wtf8ToWtf16LeAllocZ(a, target_name);
|
|
const wsymlink_name = try std.unicode.wtf8ToWtf16LeAllocZ(a, symlink_name);
|
|
defer a.free(wtarget_name);
|
|
defer a.free(wsymlink_name);
|
|
|
|
std.os.windows.CreateSymbolicLink(tmp.dir.fd, wsymlink_name, wtarget_name, false) catch |err| switch (err) {
|
|
// Symlink requires admin privileges on windows, so this test can legitimately fail.
|
|
error.AccessDenied => return error.SkipZigTest,
|
|
else => return err,
|
|
};
|
|
} else {
|
|
try posix.symlink(target_name, symlink_name);
|
|
}
|
|
|
|
var buffer: [fs.max_path_bytes]u8 = undefined;
|
|
const given = try posix.readlink(symlink_name, buffer[0..]);
|
|
try expect(mem.eql(u8, target_name, given));
|
|
}
|
|
|
|
test "readlink on Windows" {
|
|
if (native_os != .windows) return error.SkipZigTest;
|
|
|
|
try testReadlink("C:\\ProgramData", "C:\\Users\\All Users");
|
|
try testReadlink("C:\\Users\\Default", "C:\\Users\\Default User");
|
|
try testReadlink("C:\\Users", "C:\\Documents and Settings");
|
|
}
|
|
|
|
fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void {
|
|
var buffer: [fs.max_path_bytes]u8 = undefined;
|
|
const given = try posix.readlink(symlink_path, buffer[0..]);
|
|
try expect(mem.eql(u8, target_path, given));
|
|
}
|
|
|
|
test "link with relative paths" {
|
|
if (native_os == .wasi) return error.SkipZigTest; // Can link, but can't change into tmpDir
|
|
if (builtin.cpu.arch == .riscv32 and builtin.os.tag == .linux and !builtin.link_libc) return error.SkipZigTest; // No `fstat()`.
|
|
|
|
switch (native_os) {
|
|
.wasi, .linux, .solaris, .illumos => {},
|
|
else => return error.SkipZigTest,
|
|
}
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
// Restore default CWD at end of test.
|
|
const orig_cwd = try fs.cwd().openDir(".", .{});
|
|
defer orig_cwd.setAsCwd() catch unreachable;
|
|
|
|
const target_name = "link-target";
|
|
const link_name = "newlink";
|
|
|
|
try tmp.dir.writeFile(.{ .sub_path = target_name, .data = "example" });
|
|
|
|
// Test 1: create the relative link from inside tmp
|
|
try tmp.dir.setAsCwd();
|
|
try posix.link(target_name, link_name);
|
|
|
|
// Verify
|
|
const efd = try tmp.dir.openFile(target_name, .{});
|
|
defer efd.close();
|
|
|
|
const nfd = try tmp.dir.openFile(link_name, .{});
|
|
defer nfd.close();
|
|
|
|
{
|
|
const estat = try posix.fstat(efd.handle);
|
|
const nstat = try posix.fstat(nfd.handle);
|
|
try testing.expectEqual(estat.ino, nstat.ino);
|
|
try testing.expectEqual(@as(@TypeOf(nstat.nlink), 2), nstat.nlink);
|
|
}
|
|
|
|
// Test 2: Remove the link and see the stats update
|
|
try posix.unlink(link_name);
|
|
|
|
{
|
|
const estat = try posix.fstat(efd.handle);
|
|
try testing.expectEqual(@as(@TypeOf(estat.nlink), 1), estat.nlink);
|
|
}
|
|
}
|
|
|
|
test "linkat with different directories" {
|
|
if (builtin.cpu.arch == .riscv32 and builtin.os.tag == .linux and !builtin.link_libc) return error.SkipZigTest; // No `fstatat()`.
|
|
|
|
switch (native_os) {
|
|
.wasi, .linux, .solaris, .illumos => {},
|
|
else => return error.SkipZigTest,
|
|
}
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
const target_name = "link-target";
|
|
const link_name = "newlink";
|
|
|
|
const subdir = try tmp.dir.makeOpenPath("subdir", .{});
|
|
|
|
defer tmp.dir.deleteFile(target_name) catch {};
|
|
try tmp.dir.writeFile(.{ .sub_path = target_name, .data = "example" });
|
|
|
|
// Test 1: link from file in subdir back up to target in parent directory
|
|
try posix.linkat(tmp.dir.fd, target_name, subdir.fd, link_name, 0);
|
|
|
|
const efd = try tmp.dir.openFile(target_name, .{});
|
|
defer efd.close();
|
|
|
|
const nfd = try subdir.openFile(link_name, .{});
|
|
defer nfd.close();
|
|
|
|
{
|
|
const estat = try posix.fstat(efd.handle);
|
|
const nstat = try posix.fstat(nfd.handle);
|
|
try testing.expectEqual(estat.ino, nstat.ino);
|
|
try testing.expectEqual(@as(@TypeOf(nstat.nlink), 2), nstat.nlink);
|
|
}
|
|
|
|
// Test 2: remove link
|
|
try posix.unlinkat(subdir.fd, link_name, 0);
|
|
|
|
{
|
|
const estat = try posix.fstat(efd.handle);
|
|
try testing.expectEqual(@as(@TypeOf(estat.nlink), 1), estat.nlink);
|
|
}
|
|
}
|
|
|
|
test "fstatat" {
|
|
if (builtin.cpu.arch == .riscv32 and builtin.os.tag == .linux and !builtin.link_libc) return error.SkipZigTest; // No `fstatat()`.
|
|
// enable when `fstat` and `fstatat` are implemented on Windows
|
|
if (native_os == .windows) return error.SkipZigTest;
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
// create dummy file
|
|
const contents = "nonsense";
|
|
try tmp.dir.writeFile(.{ .sub_path = "file.txt", .data = contents });
|
|
|
|
// fetch file's info on the opened fd directly
|
|
const file = try tmp.dir.openFile("file.txt", .{});
|
|
const stat = try posix.fstat(file.handle);
|
|
defer file.close();
|
|
|
|
// now repeat but using `fstatat` instead
|
|
const statat = try posix.fstatat(tmp.dir.fd, "file.txt", posix.AT.SYMLINK_NOFOLLOW);
|
|
|
|
// s390x-linux does not have nanosecond precision for fstat(), but it does for fstatat(). As a
|
|
// result, comparing the two structures is doomed to fail.
|
|
if (builtin.cpu.arch == .s390x and builtin.os.tag == .linux) return error.SkipZigTest;
|
|
|
|
try expectEqual(stat, statat);
|
|
}
|
|
|
|
test "readlinkat" {
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
// create file
|
|
try tmp.dir.writeFile(.{ .sub_path = "file.txt", .data = "nonsense" });
|
|
|
|
// create a symbolic link
|
|
if (native_os == .windows) {
|
|
std.os.windows.CreateSymbolicLink(
|
|
tmp.dir.fd,
|
|
&[_]u16{ 'l', 'i', 'n', 'k' },
|
|
&[_:0]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' },
|
|
false,
|
|
) catch |err| switch (err) {
|
|
// Symlink requires admin privileges on windows, so this test can legitimately fail.
|
|
error.AccessDenied => return error.SkipZigTest,
|
|
else => return err,
|
|
};
|
|
} else {
|
|
try posix.symlinkat("file.txt", tmp.dir.fd, "link");
|
|
}
|
|
|
|
// read the link
|
|
var buffer: [fs.max_path_bytes]u8 = undefined;
|
|
const read_link = try posix.readlinkat(tmp.dir.fd, "link", buffer[0..]);
|
|
try expect(mem.eql(u8, "file.txt", read_link));
|
|
}
|
|
|
|
fn testThreadIdFn(thread_id: *Thread.Id) void {
|
|
thread_id.* = Thread.getCurrentId();
|
|
}
|
|
|
|
test "Thread.getCurrentId" {
|
|
if (builtin.single_threaded) return error.SkipZigTest;
|
|
|
|
var thread_current_id: Thread.Id = undefined;
|
|
const thread = try Thread.spawn(.{}, testThreadIdFn, .{&thread_current_id});
|
|
thread.join();
|
|
try expect(Thread.getCurrentId() != thread_current_id);
|
|
}
|
|
|
|
test "spawn threads" {
|
|
if (builtin.single_threaded) return error.SkipZigTest;
|
|
|
|
var shared_ctx: i32 = 1;
|
|
|
|
const thread1 = try Thread.spawn(.{}, start1, .{});
|
|
const thread2 = try Thread.spawn(.{}, start2, .{&shared_ctx});
|
|
const thread3 = try Thread.spawn(.{}, start2, .{&shared_ctx});
|
|
const thread4 = try Thread.spawn(.{}, start2, .{&shared_ctx});
|
|
|
|
thread1.join();
|
|
thread2.join();
|
|
thread3.join();
|
|
thread4.join();
|
|
|
|
try expect(shared_ctx == 4);
|
|
}
|
|
|
|
fn start1() u8 {
|
|
return 0;
|
|
}
|
|
|
|
fn start2(ctx: *i32) u8 {
|
|
_ = @atomicRmw(i32, ctx, AtomicRmwOp.Add, 1, AtomicOrder.seq_cst);
|
|
return 0;
|
|
}
|
|
|
|
test "cpu count" {
|
|
if (native_os == .wasi) return error.SkipZigTest;
|
|
|
|
const cpu_count = try Thread.getCpuCount();
|
|
try expect(cpu_count >= 1);
|
|
}
|
|
|
|
test "thread local storage" {
|
|
if (builtin.single_threaded) return error.SkipZigTest;
|
|
|
|
const thread1 = try Thread.spawn(.{}, testTls, .{});
|
|
const thread2 = try Thread.spawn(.{}, testTls, .{});
|
|
try testTls();
|
|
thread1.join();
|
|
thread2.join();
|
|
}
|
|
|
|
threadlocal var x: i32 = 1234;
|
|
fn testTls() !void {
|
|
if (x != 1234) return error.TlsBadStartValue;
|
|
x += 1;
|
|
if (x != 1235) return error.TlsBadEndValue;
|
|
}
|
|
|
|
test "getrandom" {
|
|
var buf_a: [50]u8 = undefined;
|
|
var buf_b: [50]u8 = undefined;
|
|
try posix.getrandom(&buf_a);
|
|
try posix.getrandom(&buf_b);
|
|
// If this test fails the chance is significantly higher that there is a bug than
|
|
// that two sets of 50 bytes were equal.
|
|
try expect(!mem.eql(u8, &buf_a, &buf_b));
|
|
}
|
|
|
|
test "getcwd" {
|
|
// at least call it so it gets compiled
|
|
var buf: [std.fs.max_path_bytes]u8 = undefined;
|
|
_ = posix.getcwd(&buf) catch undefined;
|
|
}
|
|
|
|
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 testing.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.p_type != elf.PT_LOAD) continue;
|
|
|
|
const reloc_addr = info.addr + phdr.p_vaddr;
|
|
// Find the ELF header
|
|
const elf_header = @as(*elf.Ehdr, @ptrFromInt(reloc_addr - phdr.p_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 fds = try posix.pipe();
|
|
try expect((try posix.write(fds[1], "hello")) == 5);
|
|
var buf: [16]u8 = undefined;
|
|
try expect((try posix.read(fds[0], buf[0..])) == 5);
|
|
try testing.expectEqualSlices(u8, buf[0..5], "hello");
|
|
posix.close(fds[1]);
|
|
posix.close(fds[0]);
|
|
}
|
|
|
|
test "argsAlloc" {
|
|
const args = try std.process.argsAlloc(std.testing.allocator);
|
|
std.process.argsFree(std.testing.allocator, args);
|
|
}
|
|
|
|
test "memfd_create" {
|
|
// 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 fd = try posix.memfd_create("test", 0);
|
|
defer posix.close(fd);
|
|
try expect((try posix.write(fd, "test")) == 4);
|
|
try posix.lseek_SET(fd, 0);
|
|
|
|
var buf: [10]u8 = undefined;
|
|
const bytes_read = try posix.read(fd, &buf);
|
|
try expect(bytes_read == 4);
|
|
try expect(mem.eql(u8, buf[0..4], "test"));
|
|
}
|
|
|
|
test "mmap" {
|
|
if (native_os == .windows or native_os == .wasi)
|
|
return error.SkipZigTest;
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
// Simple mmap() call with non page-aligned size
|
|
{
|
|
const data = try posix.mmap(
|
|
null,
|
|
1234,
|
|
posix.PROT.READ | posix.PROT.WRITE,
|
|
.{ .TYPE = .PRIVATE, .ANONYMOUS = true },
|
|
-1,
|
|
0,
|
|
);
|
|
defer posix.munmap(data);
|
|
|
|
try testing.expectEqual(@as(usize, 1234), data.len);
|
|
|
|
// By definition the data returned by mmap is zero-filled
|
|
try testing.expect(mem.eql(u8, data, &[_]u8{0x00} ** 1234));
|
|
|
|
// Make sure the memory is writeable as requested
|
|
@memset(data, 0x55);
|
|
try testing.expect(mem.eql(u8, data, &[_]u8{0x55} ** 1234));
|
|
}
|
|
|
|
const test_out_file = "os_tmp_test";
|
|
// Must be a multiple of 4096 so that the test works with mmap2
|
|
const alloc_size = 8 * 4096;
|
|
|
|
// Create a file used for testing mmap() calls with a file descriptor
|
|
{
|
|
const file = try tmp.dir.createFile(test_out_file, .{});
|
|
defer file.close();
|
|
|
|
const stream = file.writer();
|
|
|
|
var i: u32 = 0;
|
|
while (i < alloc_size / @sizeOf(u32)) : (i += 1) {
|
|
try stream.writeInt(u32, i, .little);
|
|
}
|
|
}
|
|
|
|
// Map the whole file
|
|
{
|
|
const file = try tmp.dir.openFile(test_out_file, .{});
|
|
defer file.close();
|
|
|
|
const data = try posix.mmap(
|
|
null,
|
|
alloc_size,
|
|
posix.PROT.READ,
|
|
.{ .TYPE = .PRIVATE },
|
|
file.handle,
|
|
0,
|
|
);
|
|
defer posix.munmap(data);
|
|
|
|
var mem_stream = io.fixedBufferStream(data);
|
|
const stream = mem_stream.reader();
|
|
|
|
var i: u32 = 0;
|
|
while (i < alloc_size / @sizeOf(u32)) : (i += 1) {
|
|
try testing.expectEqual(i, try stream.readInt(u32, .little));
|
|
}
|
|
}
|
|
|
|
// Map the upper half of the file
|
|
{
|
|
const file = try tmp.dir.openFile(test_out_file, .{});
|
|
defer file.close();
|
|
|
|
const data = try posix.mmap(
|
|
null,
|
|
alloc_size / 2,
|
|
posix.PROT.READ,
|
|
.{ .TYPE = .PRIVATE },
|
|
file.handle,
|
|
alloc_size / 2,
|
|
);
|
|
defer posix.munmap(data);
|
|
|
|
var mem_stream = io.fixedBufferStream(data);
|
|
const stream = mem_stream.reader();
|
|
|
|
var i: u32 = alloc_size / 2 / @sizeOf(u32);
|
|
while (i < alloc_size / @sizeOf(u32)) : (i += 1) {
|
|
try testing.expectEqual(i, try stream.readInt(u32, .little));
|
|
}
|
|
}
|
|
|
|
try tmp.dir.deleteFile(test_out_file);
|
|
}
|
|
|
|
test "getenv" {
|
|
if (native_os == .wasi and !builtin.link_libc) {
|
|
// std.posix.getenv is not supported on WASI due to the need of allocation
|
|
return error.SkipZigTest;
|
|
}
|
|
|
|
if (native_os == .windows) {
|
|
try expect(std.process.getenvW(&[_:0]u16{ 'B', 'O', 'G', 'U', 'S', 0x11, 0x22, 0x33, 0x44, 0x55 }) == null);
|
|
} else {
|
|
try expect(posix.getenvZ("BOGUSDOESNOTEXISTENVVAR") == null);
|
|
}
|
|
}
|
|
|
|
test "fcntl" {
|
|
if (native_os == .windows or native_os == .wasi)
|
|
return error.SkipZigTest;
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
const test_out_file = "os_tmp_test";
|
|
|
|
const file = try tmp.dir.createFile(test_out_file, .{});
|
|
defer {
|
|
file.close();
|
|
tmp.dir.deleteFile(test_out_file) catch {};
|
|
}
|
|
|
|
// Note: The test assumes createFile opens the file with CLOEXEC
|
|
{
|
|
const flags = try posix.fcntl(file.handle, posix.F.GETFD, 0);
|
|
try expect((flags & posix.FD_CLOEXEC) != 0);
|
|
}
|
|
{
|
|
_ = try posix.fcntl(file.handle, posix.F.SETFD, 0);
|
|
const flags = try posix.fcntl(file.handle, posix.F.GETFD, 0);
|
|
try expect((flags & posix.FD_CLOEXEC) == 0);
|
|
}
|
|
{
|
|
_ = try posix.fcntl(file.handle, posix.F.SETFD, posix.FD_CLOEXEC);
|
|
const flags = try posix.fcntl(file.handle, posix.F.GETFD, 0);
|
|
try expect((flags & posix.FD_CLOEXEC) != 0);
|
|
}
|
|
}
|
|
|
|
test "signalfd" {
|
|
switch (native_os) {
|
|
.linux, .solaris, .illumos => {},
|
|
else => return error.SkipZigTest,
|
|
}
|
|
_ = &posix.signalfd;
|
|
}
|
|
|
|
test "sync" {
|
|
if (native_os != .linux)
|
|
return error.SkipZigTest;
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
const test_out_file = "os_tmp_test";
|
|
const file = try tmp.dir.createFile(test_out_file, .{});
|
|
defer {
|
|
file.close();
|
|
tmp.dir.deleteFile(test_out_file) catch {};
|
|
}
|
|
|
|
posix.sync();
|
|
try posix.syncfs(file.handle);
|
|
}
|
|
|
|
test "fsync" {
|
|
switch (native_os) {
|
|
.linux, .windows, .solaris, .illumos => {},
|
|
else => return error.SkipZigTest,
|
|
}
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
const test_out_file = "os_tmp_test";
|
|
const file = try tmp.dir.createFile(test_out_file, .{});
|
|
defer {
|
|
file.close();
|
|
tmp.dir.deleteFile(test_out_file) catch {};
|
|
}
|
|
|
|
try posix.fsync(file.handle);
|
|
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 "shutdown socket" {
|
|
if (native_os == .wasi)
|
|
return error.SkipZigTest;
|
|
if (native_os == .windows) {
|
|
_ = try std.os.windows.WSAStartup(2, 2);
|
|
}
|
|
defer {
|
|
if (native_os == .windows) {
|
|
std.os.windows.WSACleanup() catch unreachable;
|
|
}
|
|
}
|
|
const sock = try posix.socket(posix.AF.INET, posix.SOCK.STREAM, 0);
|
|
posix.shutdown(sock, .both) catch |err| switch (err) {
|
|
error.SocketNotConnected => {},
|
|
else => |e| return e,
|
|
};
|
|
std.net.Stream.close(.{ .handle = sock });
|
|
}
|
|
|
|
test "sigaction" {
|
|
if (native_os == .wasi or native_os == .windows)
|
|
return error.SkipZigTest;
|
|
|
|
// https://github.com/ziglang/zig/issues/7427
|
|
if (native_os == .linux and builtin.target.cpu.arch == .x86)
|
|
return error.SkipZigTest;
|
|
|
|
// https://github.com/ziglang/zig/issues/15381
|
|
if (native_os == .macos and builtin.target.cpu.arch == .x86_64) {
|
|
return error.SkipZigTest;
|
|
}
|
|
|
|
const S = struct {
|
|
var handler_called_count: u32 = 0;
|
|
|
|
fn handler(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) void {
|
|
_ = ctx_ptr;
|
|
// Check that we received the correct signal.
|
|
switch (native_os) {
|
|
.netbsd => {
|
|
if (sig == posix.SIG.USR1 and sig == info.info.signo)
|
|
handler_called_count += 1;
|
|
},
|
|
else => {
|
|
if (sig == posix.SIG.USR1 and sig == info.signo)
|
|
handler_called_count += 1;
|
|
},
|
|
}
|
|
}
|
|
};
|
|
|
|
var sa: posix.Sigaction = .{
|
|
.handler = .{ .sigaction = &S.handler },
|
|
.mask = posix.empty_sigset,
|
|
.flags = posix.SA.SIGINFO | posix.SA.RESETHAND,
|
|
};
|
|
var old_sa: posix.Sigaction = undefined;
|
|
|
|
// Install the new signal handler.
|
|
posix.sigaction(posix.SIG.USR1, &sa, null);
|
|
|
|
// Check that we can read it back correctly.
|
|
posix.sigaction(posix.SIG.USR1, null, &old_sa);
|
|
try testing.expectEqual(&S.handler, old_sa.handler.sigaction.?);
|
|
try testing.expect((old_sa.flags & posix.SA.SIGINFO) != 0);
|
|
|
|
// Invoke the handler.
|
|
try posix.raise(posix.SIG.USR1);
|
|
try testing.expect(S.handler_called_count == 1);
|
|
|
|
// Check if passing RESETHAND correctly reset the handler to SIG_DFL
|
|
posix.sigaction(posix.SIG.USR1, null, &old_sa);
|
|
try testing.expectEqual(posix.SIG.DFL, old_sa.handler.handler);
|
|
|
|
// Reinstall the signal w/o RESETHAND and re-raise
|
|
sa.flags = posix.SA.SIGINFO;
|
|
posix.sigaction(posix.SIG.USR1, &sa, null);
|
|
try posix.raise(posix.SIG.USR1);
|
|
try testing.expect(S.handler_called_count == 2);
|
|
|
|
// Now set the signal to ignored
|
|
sa.handler = .{ .handler = posix.SIG.IGN };
|
|
sa.flags = 0;
|
|
posix.sigaction(posix.SIG.USR1, &sa, null);
|
|
|
|
// Re-raise to ensure handler is actually ignored
|
|
try posix.raise(posix.SIG.USR1);
|
|
try testing.expect(S.handler_called_count == 2);
|
|
|
|
// Ensure that ignored state is returned when querying
|
|
posix.sigaction(posix.SIG.USR1, null, &old_sa);
|
|
try testing.expectEqual(posix.SIG.IGN, old_sa.handler.handler.?);
|
|
}
|
|
|
|
test "dup & dup2" {
|
|
switch (native_os) {
|
|
.linux, .solaris, .illumos => {},
|
|
else => return error.SkipZigTest,
|
|
}
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
{
|
|
var file = try tmp.dir.createFile("os_dup_test", .{});
|
|
defer file.close();
|
|
|
|
var duped = std.fs.File{ .handle = try posix.dup(file.handle) };
|
|
defer duped.close();
|
|
try duped.writeAll("dup");
|
|
|
|
// Tests aren't run in parallel so using the next fd shouldn't be an issue.
|
|
const new_fd = duped.handle + 1;
|
|
try posix.dup2(file.handle, new_fd);
|
|
var dup2ed = std.fs.File{ .handle = new_fd };
|
|
defer dup2ed.close();
|
|
try dup2ed.writeAll("dup2");
|
|
}
|
|
|
|
var file = try tmp.dir.openFile("os_dup_test", .{});
|
|
defer file.close();
|
|
|
|
var buf: [7]u8 = undefined;
|
|
try testing.expectEqualStrings("dupdup2", buf[0..try file.readAll(&buf)]);
|
|
}
|
|
|
|
test "writev longer than IOV_MAX" {
|
|
if (native_os == .windows or native_os == .wasi) return error.SkipZigTest;
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
var file = try tmp.dir.createFile("pwritev", .{});
|
|
defer file.close();
|
|
|
|
const iovecs = [_]posix.iovec_const{.{ .base = "a", .len = 1 }} ** (posix.IOV_MAX + 1);
|
|
const amt = try file.writev(&iovecs);
|
|
try testing.expectEqual(@as(usize, posix.IOV_MAX), amt);
|
|
}
|
|
|
|
test "POSIX file locking with fcntl" {
|
|
if (native_os == .windows or native_os == .wasi) {
|
|
// Not POSIX.
|
|
return error.SkipZigTest;
|
|
}
|
|
|
|
if (true) {
|
|
// https://github.com/ziglang/zig/issues/11074
|
|
return error.SkipZigTest;
|
|
}
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
// Create a temporary lock file
|
|
var file = try tmp.dir.createFile("lock", .{ .read = true });
|
|
defer file.close();
|
|
try file.setEndPos(2);
|
|
const fd = file.handle;
|
|
|
|
// Place an exclusive lock on the first byte, and a shared lock on the second byte:
|
|
var struct_flock = std.mem.zeroInit(posix.Flock, .{ .type = posix.F.WRLCK });
|
|
_ = try posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock));
|
|
struct_flock.start = 1;
|
|
struct_flock.type = posix.F.RDLCK;
|
|
_ = try posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock));
|
|
|
|
// Check the locks in a child process:
|
|
const pid = try posix.fork();
|
|
if (pid == 0) {
|
|
// child expects be denied the exclusive lock:
|
|
struct_flock.start = 0;
|
|
struct_flock.type = posix.F.WRLCK;
|
|
try expectError(error.Locked, posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock)));
|
|
// child expects to get the shared lock:
|
|
struct_flock.start = 1;
|
|
struct_flock.type = posix.F.RDLCK;
|
|
_ = try posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock));
|
|
// child waits for the exclusive lock in order to test deadlock:
|
|
struct_flock.start = 0;
|
|
struct_flock.type = posix.F.WRLCK;
|
|
_ = try posix.fcntl(fd, posix.F.SETLKW, @intFromPtr(&struct_flock));
|
|
// child exits without continuing:
|
|
posix.exit(0);
|
|
} else {
|
|
// parent waits for child to get shared lock:
|
|
std.time.sleep(1 * std.time.ns_per_ms);
|
|
// parent expects deadlock when attempting to upgrade the shared lock to exclusive:
|
|
struct_flock.start = 1;
|
|
struct_flock.type = posix.F.WRLCK;
|
|
try expectError(error.DeadLock, posix.fcntl(fd, posix.F.SETLKW, @intFromPtr(&struct_flock)));
|
|
// parent releases exclusive lock:
|
|
struct_flock.start = 0;
|
|
struct_flock.type = posix.F.UNLCK;
|
|
_ = try posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock));
|
|
// parent releases shared lock:
|
|
struct_flock.start = 1;
|
|
struct_flock.type = posix.F.UNLCK;
|
|
_ = try posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock));
|
|
// parent waits for child:
|
|
const result = posix.waitpid(pid, 0);
|
|
try expect(result.status == 0 * 256);
|
|
}
|
|
}
|
|
|
|
test "rename smoke test" {
|
|
if (native_os == .wasi) return error.SkipZigTest;
|
|
if (native_os == .windows) return error.SkipZigTest;
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
// Get base abs path
|
|
var arena = ArenaAllocator.init(testing.allocator);
|
|
defer arena.deinit();
|
|
const allocator = arena.allocator();
|
|
|
|
const base_path = blk: {
|
|
const relative_path = try fs.path.join(allocator, &[_][]const u8{ ".zig-cache", "tmp", tmp.sub_path[0..] });
|
|
break :blk try fs.realpathAlloc(allocator, relative_path);
|
|
};
|
|
|
|
var file_path: []u8 = undefined;
|
|
var fd: posix.fd_t = undefined;
|
|
const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666;
|
|
|
|
// Create some file using `open`.
|
|
file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
|
|
fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode);
|
|
posix.close(fd);
|
|
|
|
// Rename the file
|
|
var new_file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_file" });
|
|
try posix.rename(file_path, new_file_path);
|
|
|
|
// Try opening renamed file
|
|
file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_file" });
|
|
fd = try posix.open(file_path, .{ .ACCMODE = .RDWR }, mode);
|
|
posix.close(fd);
|
|
|
|
// Try opening original file - should fail with error.FileNotFound
|
|
file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
|
|
try expectError(error.FileNotFound, posix.open(file_path, .{ .ACCMODE = .RDWR }, mode));
|
|
|
|
// Create some directory
|
|
file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" });
|
|
try posix.mkdir(file_path, mode);
|
|
|
|
// Rename the directory
|
|
new_file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_dir" });
|
|
try posix.rename(file_path, new_file_path);
|
|
|
|
// Try opening renamed directory
|
|
file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_dir" });
|
|
fd = try posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode);
|
|
posix.close(fd);
|
|
|
|
// Try opening original directory - should fail with error.FileNotFound
|
|
file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" });
|
|
try expectError(error.FileNotFound, posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode));
|
|
}
|
|
|
|
test "access smoke test" {
|
|
if (native_os == .wasi) return error.SkipZigTest;
|
|
if (native_os == .windows) return error.SkipZigTest;
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
// Get base abs path
|
|
var arena = ArenaAllocator.init(testing.allocator);
|
|
defer arena.deinit();
|
|
const allocator = arena.allocator();
|
|
|
|
const base_path = blk: {
|
|
const relative_path = try fs.path.join(allocator, &[_][]const u8{ ".zig-cache", "tmp", tmp.sub_path[0..] });
|
|
break :blk try fs.realpathAlloc(allocator, relative_path);
|
|
};
|
|
|
|
var file_path: []u8 = undefined;
|
|
var fd: posix.fd_t = undefined;
|
|
const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666;
|
|
|
|
// Create some file using `open`.
|
|
file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
|
|
fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode);
|
|
posix.close(fd);
|
|
|
|
// Try to access() the file
|
|
file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
|
|
if (native_os == .windows) {
|
|
try posix.access(file_path, posix.F_OK);
|
|
} else {
|
|
try posix.access(file_path, posix.F_OK | posix.W_OK | posix.R_OK);
|
|
}
|
|
|
|
// Try to access() a non-existent file - should fail with error.FileNotFound
|
|
file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_file" });
|
|
try expectError(error.FileNotFound, posix.access(file_path, posix.F_OK));
|
|
|
|
// Create some directory
|
|
file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" });
|
|
try posix.mkdir(file_path, mode);
|
|
|
|
// Try to access() the directory
|
|
file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" });
|
|
try posix.access(file_path, posix.F_OK);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
test "isatty" {
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
var file = try tmp.dir.createFile("foo", .{});
|
|
defer file.close();
|
|
|
|
try expectEqual(posix.isatty(file.handle), false);
|
|
}
|
|
|
|
test "read with empty buffer" {
|
|
if (native_os == .wasi) return error.SkipZigTest;
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
var arena = ArenaAllocator.init(testing.allocator);
|
|
defer arena.deinit();
|
|
const allocator = arena.allocator();
|
|
|
|
// Get base abs path
|
|
const base_path = blk: {
|
|
const relative_path = try fs.path.join(allocator, &[_][]const u8{ ".zig-cache", "tmp", tmp.sub_path[0..] });
|
|
break :blk try fs.realpathAlloc(allocator, relative_path);
|
|
};
|
|
|
|
const file_path: []u8 = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
|
|
var file = try fs.cwd().createFile(file_path, .{ .read = true });
|
|
defer file.close();
|
|
|
|
const bytes = try allocator.alloc(u8, 0);
|
|
|
|
_ = try posix.read(file.handle, bytes);
|
|
}
|
|
|
|
test "pread with empty buffer" {
|
|
if (native_os == .wasi) return error.SkipZigTest;
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
var arena = ArenaAllocator.init(testing.allocator);
|
|
defer arena.deinit();
|
|
const allocator = arena.allocator();
|
|
|
|
// Get base abs path
|
|
const base_path = blk: {
|
|
const relative_path = try fs.path.join(allocator, &[_][]const u8{ ".zig-cache", "tmp", tmp.sub_path[0..] });
|
|
break :blk try fs.realpathAlloc(allocator, relative_path);
|
|
};
|
|
|
|
const file_path: []u8 = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
|
|
var file = try fs.cwd().createFile(file_path, .{ .read = true });
|
|
defer file.close();
|
|
|
|
const bytes = try allocator.alloc(u8, 0);
|
|
|
|
_ = try posix.pread(file.handle, bytes, 0);
|
|
}
|
|
|
|
test "write with empty buffer" {
|
|
if (native_os == .wasi) return error.SkipZigTest;
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
var arena = ArenaAllocator.init(testing.allocator);
|
|
defer arena.deinit();
|
|
const allocator = arena.allocator();
|
|
|
|
// Get base abs path
|
|
const base_path = blk: {
|
|
const relative_path = try fs.path.join(allocator, &[_][]const u8{ ".zig-cache", "tmp", tmp.sub_path[0..] });
|
|
break :blk try fs.realpathAlloc(allocator, relative_path);
|
|
};
|
|
|
|
const file_path: []u8 = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
|
|
var file = try fs.cwd().createFile(file_path, .{});
|
|
defer file.close();
|
|
|
|
const bytes = try allocator.alloc(u8, 0);
|
|
|
|
_ = try posix.write(file.handle, bytes);
|
|
}
|
|
|
|
test "pwrite with empty buffer" {
|
|
if (native_os == .wasi) return error.SkipZigTest;
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
var arena = ArenaAllocator.init(testing.allocator);
|
|
defer arena.deinit();
|
|
const allocator = arena.allocator();
|
|
|
|
// Get base abs path
|
|
const base_path = blk: {
|
|
const relative_path = try fs.path.join(allocator, &[_][]const u8{ ".zig-cache", "tmp", tmp.sub_path[0..] });
|
|
break :blk try fs.realpathAlloc(allocator, relative_path);
|
|
};
|
|
|
|
const file_path: []u8 = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
|
|
var file = try fs.cwd().createFile(file_path, .{});
|
|
defer file.close();
|
|
|
|
const bytes = try allocator.alloc(u8, 0);
|
|
|
|
_ = try posix.pwrite(file.handle, bytes, 0);
|
|
}
|
|
|
|
fn expectMode(dir: posix.fd_t, file: []const u8, mode: posix.mode_t) !void {
|
|
const st = try posix.fstatat(dir, file, posix.AT.SYMLINK_NOFOLLOW);
|
|
try expectEqual(mode, st.mode & 0b111_111_111);
|
|
}
|
|
|
|
test "fchmodat smoke test" {
|
|
if (!std.fs.has_executable_bit) return error.SkipZigTest;
|
|
|
|
var tmp = tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
try expectError(error.FileNotFound, posix.fchmodat(tmp.dir.fd, "regfile", 0o666, 0));
|
|
const fd = try posix.openat(
|
|
tmp.dir.fd,
|
|
"regfile",
|
|
.{ .ACCMODE = .WRONLY, .CREAT = true, .EXCL = true, .TRUNC = true },
|
|
0o644,
|
|
);
|
|
posix.close(fd);
|
|
|
|
if (builtin.cpu.arch == .riscv32 and builtin.os.tag == .linux and !builtin.link_libc) return error.SkipZigTest; // No `fstatat()`.
|
|
|
|
try posix.symlinkat("regfile", tmp.dir.fd, "symlink");
|
|
const sym_mode = blk: {
|
|
const st = try posix.fstatat(tmp.dir.fd, "symlink", posix.AT.SYMLINK_NOFOLLOW);
|
|
break :blk st.mode & 0b111_111_111;
|
|
};
|
|
|
|
try posix.fchmodat(tmp.dir.fd, "regfile", 0o640, 0);
|
|
try expectMode(tmp.dir.fd, "regfile", 0o640);
|
|
try posix.fchmodat(tmp.dir.fd, "regfile", 0o600, posix.AT.SYMLINK_NOFOLLOW);
|
|
try expectMode(tmp.dir.fd, "regfile", 0o600);
|
|
|
|
try posix.fchmodat(tmp.dir.fd, "symlink", 0o640, 0);
|
|
try expectMode(tmp.dir.fd, "regfile", 0o640);
|
|
try expectMode(tmp.dir.fd, "symlink", sym_mode);
|
|
|
|
var test_link = true;
|
|
posix.fchmodat(tmp.dir.fd, "symlink", 0o600, posix.AT.SYMLINK_NOFOLLOW) catch |err| switch (err) {
|
|
error.OperationNotSupported => test_link = false,
|
|
else => |e| return e,
|
|
};
|
|
if (test_link)
|
|
try expectMode(tmp.dir.fd, "symlink", 0o600);
|
|
try expectMode(tmp.dir.fd, "regfile", 0o640);
|
|
}
|
|
|
|
const CommonOpenFlags = packed struct {
|
|
ACCMODE: posix.ACCMODE = .RDONLY,
|
|
CREAT: bool = false,
|
|
EXCL: bool = false,
|
|
LARGEFILE: bool = false,
|
|
DIRECTORY: bool = false,
|
|
CLOEXEC: bool = false,
|
|
NONBLOCK: bool = false,
|
|
|
|
pub fn lower(cof: CommonOpenFlags) posix.O {
|
|
var result: posix.O = if (native_os == .wasi) .{
|
|
.read = cof.ACCMODE != .WRONLY,
|
|
.write = cof.ACCMODE != .RDONLY,
|
|
} else .{
|
|
.ACCMODE = cof.ACCMODE,
|
|
};
|
|
result.CREAT = cof.CREAT;
|
|
result.EXCL = cof.EXCL;
|
|
result.DIRECTORY = cof.DIRECTORY;
|
|
result.NONBLOCK = cof.NONBLOCK;
|
|
if (@hasField(posix.O, "CLOEXEC")) result.CLOEXEC = cof.CLOEXEC;
|
|
if (@hasField(posix.O, "LARGEFILE")) result.LARGEFILE = cof.LARGEFILE;
|
|
return result;
|
|
}
|
|
};
|