zig/lib/std/os/test.zig
Veikka Tuominen 41b7e40d75
Merge pull request #13418 from ryanschneider/signal-alignment-13216
std.os: fix alignment of Sigaction.handler_fn
2022-11-09 17:36:40 +02:00

1048 lines
34 KiB
Zig

const std = @import("../std.zig");
const os = std.os;
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 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;
test "chdir smoke test" {
if (native_os == .wasi) return error.SkipZigTest; // WASI doesn't allow navigating outside of a preopen
// Get current working directory path
var old_cwd_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
const old_cwd = try os.getcwd(old_cwd_buf[0..]);
{
// Firstly, changing to itself should have no effect
try os.chdir(old_cwd);
var new_cwd_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
const new_cwd = try os.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 os.chdir(parent);
// Restore cwd because process may have other tests that do not tolerate chdir.
defer os.chdir(old_cwd) catch unreachable;
var new_cwd_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
const new_cwd = try os.getcwd(new_cwd_buf[0..]);
try expect(mem.eql(u8, parent, new_cwd));
}
}
test "open smoke test" {
if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
// 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: os.fd_t = undefined;
const mode: os.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 os.open(file_path, os.O.RDWR | os.O.CREAT | os.O.EXCL, mode);
os.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, os.open(file_path, os.O.RDWR | os.O.CREAT | os.O.EXCL, mode));
// Try opening without `O.EXCL` flag.
file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
fd = try os.open(file_path, os.O.RDWR | os.O.CREAT, mode);
os.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, os.open(file_path, os.O.RDWR | os.O.DIRECTORY, mode));
// Create some directory
file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" });
try os.mkdir(file_path, mode);
// Open dir using `open`
file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" });
fd = try os.open(file_path, os.O.RDONLY | os.O.DIRECTORY, mode);
os.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, os.open(file_path, os.O.RDWR, mode));
}
test "openat smoke test" {
if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
// TODO verify file attributes using `fstatat`
var tmp = tmpDir(.{});
defer tmp.cleanup();
var fd: os.fd_t = undefined;
const mode: os.mode_t = if (native_os == .windows) 0 else 0o666;
// Create some file using `openat`.
fd = try os.openat(tmp.dir.fd, "some_file", os.O.RDWR | os.O.CREAT | os.O.EXCL, mode);
os.close(fd);
// Try this again with the same flags. This op should fail with error.PathAlreadyExists.
try expectError(error.PathAlreadyExists, os.openat(tmp.dir.fd, "some_file", os.O.RDWR | os.O.CREAT | os.O.EXCL, mode));
// Try opening without `O.EXCL` flag.
fd = try os.openat(tmp.dir.fd, "some_file", os.O.RDWR | os.O.CREAT, mode);
os.close(fd);
// Try opening as a directory which should fail.
try expectError(error.NotDir, os.openat(tmp.dir.fd, "some_file", os.O.RDWR | os.O.DIRECTORY, mode));
// Create some directory
try os.mkdirat(tmp.dir.fd, "some_dir", mode);
// Open dir using `open`
fd = try os.openat(tmp.dir.fd, "some_dir", os.O.RDONLY | os.O.DIRECTORY, mode);
os.close(fd);
// Try opening as file which should fail.
try expectError(error.IsDir, os.openat(tmp.dir.fd, "some_dir", os.O.RDWR, mode));
}
test "symlink with relative paths" {
if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
const cwd = fs.cwd();
cwd.deleteFile("file.txt") catch {};
cwd.deleteFile("symlinked") catch {};
// First, try relative paths in cwd
try cwd.writeFile("file.txt", "nonsense");
if (native_os == .windows) {
os.windows.CreateSymbolicLink(
cwd.fd,
&[_]u16{ 's', 'y', 'm', 'l', 'i', 'n', 'k', 'e', 'd' },
&[_]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 => {
try cwd.deleteFile("file.txt");
try cwd.deleteFile("symlinked");
return error.SkipZigTest;
},
else => return err,
};
} else {
try os.symlink("file.txt", "symlinked");
}
var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
const given = try os.readlink("symlinked", buffer[0..]);
try expect(mem.eql(u8, "file.txt", given));
try cwd.deleteFile("file.txt");
try cwd.deleteFile("symlinked");
}
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 os.readlink(symlink_path, buffer[0..]);
try expect(mem.eql(u8, target_path, given));
}
test "link with relative paths" {
switch (native_os) {
.wasi => {
if (builtin.link_libc) {
return error.SkipZigTest;
} else {
try os.initPreopensWasi(std.heap.page_allocator, "/");
}
},
.linux, .solaris => {},
else => return error.SkipZigTest,
}
var cwd = fs.cwd();
cwd.deleteFile("example.txt") catch {};
cwd.deleteFile("new.txt") catch {};
try cwd.writeFile("example.txt", "example");
try os.link("example.txt", "new.txt", 0);
const efd = try cwd.openFile("example.txt", .{});
defer efd.close();
const nfd = try cwd.openFile("new.txt", .{});
defer nfd.close();
{
const estat = try os.fstat(efd.handle);
const nstat = try os.fstat(nfd.handle);
try testing.expectEqual(estat.ino, nstat.ino);
try testing.expectEqual(@as(@TypeOf(nstat.nlink), 2), nstat.nlink);
}
try os.unlink("new.txt");
{
const estat = try os.fstat(efd.handle);
try testing.expectEqual(@as(@TypeOf(estat.nlink), 1), estat.nlink);
}
try cwd.deleteFile("example.txt");
}
test "linkat with different directories" {
switch (native_os) {
.wasi => if (!builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/"),
.linux, .solaris => {},
else => return error.SkipZigTest,
}
var cwd = fs.cwd();
var tmp = tmpDir(.{});
cwd.deleteFile("example.txt") catch {};
tmp.dir.deleteFile("new.txt") catch {};
try cwd.writeFile("example.txt", "example");
try os.linkat(cwd.fd, "example.txt", tmp.dir.fd, "new.txt", 0);
const efd = try cwd.openFile("example.txt", .{});
defer efd.close();
const nfd = try tmp.dir.openFile("new.txt", .{});
{
defer nfd.close();
const estat = try os.fstat(efd.handle);
const nstat = try os.fstat(nfd.handle);
try testing.expectEqual(estat.ino, nstat.ino);
try testing.expectEqual(@as(@TypeOf(nstat.nlink), 2), nstat.nlink);
}
try os.unlinkat(tmp.dir.fd, "new.txt", 0);
{
const estat = try os.fstat(efd.handle);
try testing.expectEqual(@as(@TypeOf(estat.nlink), 1), estat.nlink);
}
try cwd.deleteFile("example.txt");
}
test "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("file.txt", contents);
// fetch file's info on the opened fd directly
const file = try tmp.dir.openFile("file.txt", .{});
const stat = try os.fstat(file.handle);
defer file.close();
// now repeat but using `fstatat` instead
const flags = if (native_os == .wasi) 0x0 else os.AT.SYMLINK_NOFOLLOW;
const statat = try os.fstatat(tmp.dir.fd, "file.txt", flags);
try expectEqual(stat, statat);
}
test "readlinkat" {
var tmp = tmpDir(.{});
defer tmp.cleanup();
// create file
try tmp.dir.writeFile("file.txt", "nonsense");
// create a symbolic link
if (native_os == .windows) {
os.windows.CreateSymbolicLink(
tmp.dir.fd,
&[_]u16{ 'l', 'i', 'n', 'k' },
&[_]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 os.symlinkat("file.txt", tmp.dir.fd, "link");
}
// read the link
var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
const read_link = try os.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 "std.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.SeqCst);
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 os.getrandom(&buf_a);
try os.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;
_ = os.getcwd(&buf) catch undefined;
}
test "sigaltstack" {
if (native_os == .windows or native_os == .wasi) return error.SkipZigTest;
var st: os.stack_t = undefined;
try os.sigaltstack(null, &st);
// Setting a stack size less than MINSIGSTKSZ returns ENOMEM
st.flags = 0;
st.size = 1;
try testing.expectError(error.SizeTooSmall, os.sigaltstack(&st, null));
}
// If the type is not available use void to avoid erroring out when `iter_fn` is
// analyzed
const dl_phdr_info = if (@hasDecl(os.system, "dl_phdr_info")) os.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.dlpi_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.dlpi_phnum) : (i += 1) {
const phdr = info.dlpi_phdr[i];
if (phdr.p_type != elf.PT_LOAD) continue;
const reloc_addr = info.dlpi_addr + phdr.p_vaddr;
// Find the ELF header
const elf_header = @intToPtr(*elf.Ehdr, 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.dlpi_phnum) return error.FailedConsistencyCheck;
found_load = true;
break;
}
if (!found_load) return error.MissingLoad;
}
test "dl_iterate_phdr" {
if (native_os == .windows or native_os == .wasi or native_os == .macos)
return error.SkipZigTest;
var counter: usize = 0;
try os.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: [os.HOST_NAME_MAX]u8 = undefined;
const hostname = try os.gethostname(&buf);
try expect(hostname.len != 0);
}
test "pipe" {
if (native_os == .windows or native_os == .wasi)
return error.SkipZigTest;
var fds = try os.pipe();
try expect((try os.write(fds[1], "hello")) == 5);
var buf: [16]u8 = undefined;
try expect((try os.read(fds[0], buf[0..])) == 5);
try testing.expectEqualSlices(u8, buf[0..5], "hello");
os.close(fds[1]);
os.close(fds[0]);
}
test "argsAlloc" {
var 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.
if (native_os != .linux and native_os != .freebsd) return error.SkipZigTest;
const fd = std.os.memfd_create("test", 0) catch |err| switch (err) {
// Related: https://github.com/ziglang/zig/issues/4019
error.SystemOutdated => return error.SkipZigTest,
else => |e| return e,
};
defer std.os.close(fd);
try expect((try std.os.write(fd, "test")) == 4);
try std.os.lseek_SET(fd, 0);
var buf: [10]u8 = undefined;
const bytes_read = try std.os.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 os.mmap(
null,
1234,
os.PROT.READ | os.PROT.WRITE,
os.MAP.ANONYMOUS | os.MAP.PRIVATE,
-1,
0,
);
defer os.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
std.mem.set(u8, 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.writeIntNative(u32, i);
}
}
// Map the whole file
{
const file = try tmp.dir.openFile(test_out_file, .{});
defer file.close();
const data = try os.mmap(
null,
alloc_size,
os.PROT.READ,
os.MAP.PRIVATE,
file.handle,
0,
);
defer os.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.readIntNative(u32));
}
}
// Map the upper half of the file
{
const file = try tmp.dir.openFile(test_out_file, .{});
defer file.close();
const data = try os.mmap(
null,
alloc_size / 2,
os.PROT.READ,
os.MAP.PRIVATE,
file.handle,
alloc_size / 2,
);
defer os.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.readIntNative(u32));
}
}
try tmp.dir.deleteFile(test_out_file);
}
test "getenv" {
if (native_os == .windows) {
try expect(os.getenvW(&[_:0]u16{ 'B', 'O', 'G', 'U', 'S', 0x11, 0x22, 0x33, 0x44, 0x55 }) == null);
} else {
try expect(os.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 O.CLOEXEC
{
const flags = try os.fcntl(file.handle, os.F.GETFD, 0);
try expect((flags & os.FD_CLOEXEC) != 0);
}
{
_ = try os.fcntl(file.handle, os.F.SETFD, 0);
const flags = try os.fcntl(file.handle, os.F.GETFD, 0);
try expect((flags & os.FD_CLOEXEC) == 0);
}
{
_ = try os.fcntl(file.handle, os.F.SETFD, os.FD_CLOEXEC);
const flags = try os.fcntl(file.handle, os.F.GETFD, 0);
try expect((flags & os.FD_CLOEXEC) != 0);
}
}
test "signalfd" {
switch (native_os) {
.linux, .solaris => {},
else => return error.SkipZigTest,
}
_ = std.os.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 {};
}
os.sync();
try os.syncfs(file.handle);
}
test "fsync" {
switch (native_os) {
.linux, .windows, .solaris => {},
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 os.fsync(file.handle);
try os.fdatasync(file.handle);
}
test "getrlimit and setrlimit" {
if (!@hasDecl(os.system, "rlimit")) {
return error.SkipZigTest;
}
inline for (std.meta.fields(os.rlimit_resource)) |field| {
const resource = @intToEnum(os.rlimit_resource, field.value);
const limit = try os.getrlimit(resource);
// 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 (comptime builtin.cpu.arch.isMIPS() and builtin.link_libc) {
if (limit.cur != os.linux.RLIM.INFINITY) {
try os.setrlimit(resource, limit);
}
} else {
try os.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 os.socket(os.AF.INET, os.SOCK.STREAM, 0);
os.shutdown(sock, .both) catch |err| switch (err) {
error.SocketNotConnected => {},
else => |e| return e,
};
os.closeSocket(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;
const S = struct {
var handler_called_count: u32 = 0;
fn handler(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const anyopaque) callconv(.C) void {
_ = ctx_ptr;
// Check that we received the correct signal.
switch (native_os) {
.netbsd => {
if (sig == os.SIG.USR1 and sig == info.info.signo)
handler_called_count += 1;
},
else => {
if (sig == os.SIG.USR1 and sig == info.signo)
handler_called_count += 1;
},
}
}
};
const actual_handler = if (builtin.zig_backend == .stage1) S.handler else &S.handler;
var sa = os.Sigaction{
.handler = .{ .sigaction = actual_handler },
.mask = os.empty_sigset,
.flags = os.SA.SIGINFO | os.SA.RESETHAND,
};
var old_sa: os.Sigaction = undefined;
// Install the new signal handler.
try os.sigaction(os.SIG.USR1, &sa, null);
// Check that we can read it back correctly.
try os.sigaction(os.SIG.USR1, null, &old_sa);
try testing.expectEqual(actual_handler, old_sa.handler.sigaction.?);
try testing.expect((old_sa.flags & os.SA.SIGINFO) != 0);
// Invoke the handler.
try os.raise(os.SIG.USR1);
try testing.expect(S.handler_called_count == 1);
// Check if passing RESETHAND correctly reset the handler to SIG_DFL
try os.sigaction(os.SIG.USR1, null, &old_sa);
try testing.expectEqual(os.SIG.DFL, old_sa.handler.handler);
// Reinstall the signal w/o RESETHAND and re-raise
sa.flags = os.SA.SIGINFO;
try os.sigaction(os.SIG.USR1, &sa, null);
try os.raise(os.SIG.USR1);
try testing.expect(S.handler_called_count == 2);
// Now set the signal to ignored
sa.handler = .{ .handler = os.SIG.IGN };
sa.flags = 0;
try os.sigaction(os.SIG.USR1, &sa, null);
// Re-raise to ensure handler is actually ignored
try os.raise(os.SIG.USR1);
try testing.expect(S.handler_called_count == 2);
// Ensure that ignored state is returned when querying
try os.sigaction(os.SIG.USR1, null, &old_sa);
try testing.expectEqual(os.SIG.IGN, old_sa.handler.handler.?);
}
test "dup & dup2" {
switch (native_os) {
.linux, .solaris => {},
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 std.os.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 std.os.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 = [_]os.iovec_const{.{ .iov_base = "a", .iov_len = 1 }} ** (os.IOV_MAX + 1);
const amt = try file.writev(&iovecs);
try testing.expectEqual(@as(usize, os.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 = std.testing.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(std.os.Flock, .{ .type = std.os.F.WRLCK });
_ = try std.os.fcntl(fd, std.os.F.SETLK, @ptrToInt(&struct_flock));
struct_flock.start = 1;
struct_flock.type = std.os.F.RDLCK;
_ = try std.os.fcntl(fd, std.os.F.SETLK, @ptrToInt(&struct_flock));
// Check the locks in a child process:
const pid = try std.os.fork();
if (pid == 0) {
// child expects be denied the exclusive lock:
struct_flock.start = 0;
struct_flock.type = std.os.F.WRLCK;
try expectError(error.Locked, std.os.fcntl(fd, std.os.F.SETLK, @ptrToInt(&struct_flock)));
// child expects to get the shared lock:
struct_flock.start = 1;
struct_flock.type = std.os.F.RDLCK;
_ = try std.os.fcntl(fd, std.os.F.SETLK, @ptrToInt(&struct_flock));
// child waits for the exclusive lock in order to test deadlock:
struct_flock.start = 0;
struct_flock.type = std.os.F.WRLCK;
_ = try std.os.fcntl(fd, std.os.F.SETLKW, @ptrToInt(&struct_flock));
// child exits without continuing:
std.os.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 = std.os.F.WRLCK;
try expectError(error.DeadLock, std.os.fcntl(fd, std.os.F.SETLKW, @ptrToInt(&struct_flock)));
// parent releases exclusive lock:
struct_flock.start = 0;
struct_flock.type = std.os.F.UNLCK;
_ = try std.os.fcntl(fd, std.os.F.SETLK, @ptrToInt(&struct_flock));
// parent releases shared lock:
struct_flock.start = 1;
struct_flock.type = std.os.F.UNLCK;
_ = try std.os.fcntl(fd, std.os.F.SETLK, @ptrToInt(&struct_flock));
// parent waits for child:
const result = std.os.waitpid(pid, 0);
try expect(result.status == 0 * 256);
}
}
test "rename smoke test" {
if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
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: os.fd_t = undefined;
const mode: os.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 os.open(file_path, os.O.RDWR | os.O.CREAT | os.O.EXCL, mode);
os.close(fd);
// Rename the file
var new_file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_file" });
try os.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 os.open(file_path, os.O.RDWR, mode);
os.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, os.open(file_path, os.O.RDWR, mode));
// Create some directory
file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" });
try os.mkdir(file_path, mode);
// Rename the directory
new_file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_dir" });
try os.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 os.open(file_path, os.O.RDONLY | os.O.DIRECTORY, mode);
os.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, os.open(file_path, os.O.RDONLY | os.O.DIRECTORY, mode));
}
test "access smoke test" {
if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
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: os.fd_t = undefined;
const mode: os.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 os.open(file_path, os.O.RDWR | os.O.CREAT | os.O.EXCL, mode);
os.close(fd);
// Try to access() the file
file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
if (builtin.os.tag == .windows) {
try os.access(file_path, os.F_OK);
} else {
try os.access(file_path, os.F_OK | os.W_OK | os.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, os.access(file_path, os.F_OK));
// Create some directory
file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" });
try os.mkdir(file_path, mode);
// Try to access() the directory
file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" });
try os.access(file_path, os.F_OK);
}
test "timerfd" {
if (native_os != .linux)
return error.SkipZigTest;
const linux = os.linux;
var tfd = try os.timerfd_create(linux.CLOCK.MONOTONIC, linux.TFD.CLOEXEC);
defer os.close(tfd);
var sit: linux.itimerspec = .{ .it_interval = .{ .tv_sec = 0, .tv_nsec = 0 }, .it_value = .{ .tv_sec = 0, .tv_nsec = 10 * (1000 * 1000) } };
try os.timerfd_settime(tfd, 0, &sit, null);
var fds: [1]os.pollfd = .{.{ .fd = tfd, .events = os.linux.POLL.IN, .revents = 0 }};
try expectEqual(try os.poll(&fds, -1), 1);
var git = try os.timerfd_gettime(tfd);
try expectEqual(git, .{ .it_interval = .{ .tv_sec = 0, .tv_nsec = 0 }, .it_value = .{ .tv_sec = 0, .tv_nsec = 0 } });
try os.timerfd_settime(tfd, 0, &sit, null);
try expectEqual(try os.poll(&fds, 5), 0);
}