blob d5451f64 (39845B) - Raw
1 const std = @import("../std.zig"); 2 const os = std.os; 3 const testing = std.testing; 4 const expect = testing.expect; 5 const expectEqual = testing.expectEqual; 6 const expectError = testing.expectError; 7 const io = std.io; 8 const fs = std.fs; 9 const mem = std.mem; 10 const elf = std.elf; 11 const File = std.fs.File; 12 const Thread = std.Thread; 13 14 const a = std.testing.allocator; 15 16 const builtin = @import("builtin"); 17 const AtomicRmwOp = std.builtin.AtomicRmwOp; 18 const AtomicOrder = std.builtin.AtomicOrder; 19 const native_os = builtin.target.os.tag; 20 const tmpDir = std.testing.tmpDir; 21 const Dir = std.fs.Dir; 22 const ArenaAllocator = std.heap.ArenaAllocator; 23 24 test "chdir smoke test" { 25 if (native_os == .wasi) return error.SkipZigTest; 26 27 if (true) { 28 // https://github.com/ziglang/zig/issues/14968 29 return error.SkipZigTest; 30 } 31 32 // Get current working directory path 33 var old_cwd_buf: [fs.MAX_PATH_BYTES]u8 = undefined; 34 const old_cwd = try os.getcwd(old_cwd_buf[0..]); 35 36 { 37 // Firstly, changing to itself should have no effect 38 try os.chdir(old_cwd); 39 var new_cwd_buf: [fs.MAX_PATH_BYTES]u8 = undefined; 40 const new_cwd = try os.getcwd(new_cwd_buf[0..]); 41 try expect(mem.eql(u8, old_cwd, new_cwd)); 42 } 43 44 // Next, change current working directory to one level above 45 if (native_os != .wasi) { // WASI does not support navigating outside of Preopens 46 const parent = fs.path.dirname(old_cwd) orelse unreachable; // old_cwd should be absolute 47 try os.chdir(parent); 48 49 // Restore cwd because process may have other tests that do not tolerate chdir. 50 defer os.chdir(old_cwd) catch unreachable; 51 52 var new_cwd_buf: [fs.MAX_PATH_BYTES]u8 = undefined; 53 const new_cwd = try os.getcwd(new_cwd_buf[0..]); 54 try expect(mem.eql(u8, parent, new_cwd)); 55 } 56 57 // Next, change current working directory to a temp directory one level below 58 { 59 // Create a tmp directory 60 var tmp_dir_buf: [fs.MAX_PATH_BYTES]u8 = undefined; 61 var tmp_dir_path = path: { 62 var allocator = std.heap.FixedBufferAllocator.init(&tmp_dir_buf); 63 break :path try fs.path.resolve(allocator.allocator(), &[_][]const u8{ old_cwd, "zig-test-tmp" }); 64 }; 65 var tmp_dir = try fs.cwd().makeOpenPath("zig-test-tmp", .{}); 66 67 // Change current working directory to tmp directory 68 try os.chdir("zig-test-tmp"); 69 70 var new_cwd_buf: [fs.MAX_PATH_BYTES]u8 = undefined; 71 const new_cwd = try os.getcwd(new_cwd_buf[0..]); 72 73 // On Windows, fs.path.resolve returns an uppercase drive letter, but the drive letter returned by getcwd may be lowercase 74 var resolved_cwd_buf: [fs.MAX_PATH_BYTES]u8 = undefined; 75 var resolved_cwd = path: { 76 var allocator = std.heap.FixedBufferAllocator.init(&resolved_cwd_buf); 77 break :path try fs.path.resolve(allocator.allocator(), &[_][]const u8{new_cwd}); 78 }; 79 try expect(mem.eql(u8, tmp_dir_path, resolved_cwd)); 80 81 // Restore cwd because process may have other tests that do not tolerate chdir. 82 tmp_dir.close(); 83 os.chdir(old_cwd) catch unreachable; 84 try fs.cwd().deleteDir("zig-test-tmp"); 85 } 86 } 87 88 test "open smoke test" { 89 if (native_os == .wasi) return error.SkipZigTest; 90 91 // TODO verify file attributes using `fstat` 92 93 var tmp = tmpDir(.{}); 94 defer tmp.cleanup(); 95 96 // Get base abs path 97 var arena = ArenaAllocator.init(testing.allocator); 98 defer arena.deinit(); 99 const allocator = arena.allocator(); 100 101 const base_path = blk: { 102 const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] }); 103 break :blk try fs.realpathAlloc(allocator, relative_path); 104 }; 105 106 var file_path: []u8 = undefined; 107 var fd: os.fd_t = undefined; 108 const mode: os.mode_t = if (native_os == .windows) 0 else 0o666; 109 110 // Create some file using `open`. 111 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); 112 fd = try os.open(file_path, os.O.RDWR | os.O.CREAT | os.O.EXCL, mode); 113 os.close(fd); 114 115 // Try this again with the same flags. This op should fail with error.PathAlreadyExists. 116 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); 117 try expectError(error.PathAlreadyExists, os.open(file_path, os.O.RDWR | os.O.CREAT | os.O.EXCL, mode)); 118 119 // Try opening without `O.EXCL` flag. 120 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); 121 fd = try os.open(file_path, os.O.RDWR | os.O.CREAT, mode); 122 os.close(fd); 123 124 // Try opening as a directory which should fail. 125 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); 126 try expectError(error.NotDir, os.open(file_path, os.O.RDWR | os.O.DIRECTORY, mode)); 127 128 // Create some directory 129 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" }); 130 try os.mkdir(file_path, mode); 131 132 // Open dir using `open` 133 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" }); 134 fd = try os.open(file_path, os.O.RDONLY | os.O.DIRECTORY, mode); 135 os.close(fd); 136 137 // Try opening as file which should fail. 138 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" }); 139 try expectError(error.IsDir, os.open(file_path, os.O.RDWR, mode)); 140 } 141 142 test "openat smoke test" { 143 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest; 144 145 // TODO verify file attributes using `fstatat` 146 147 var tmp = tmpDir(.{}); 148 defer tmp.cleanup(); 149 150 var fd: os.fd_t = undefined; 151 const mode: os.mode_t = if (native_os == .windows) 0 else 0o666; 152 153 // Create some file using `openat`. 154 fd = try os.openat(tmp.dir.fd, "some_file", os.O.RDWR | os.O.CREAT | os.O.EXCL, mode); 155 os.close(fd); 156 157 // Try this again with the same flags. This op should fail with error.PathAlreadyExists. 158 try expectError(error.PathAlreadyExists, os.openat(tmp.dir.fd, "some_file", os.O.RDWR | os.O.CREAT | os.O.EXCL, mode)); 159 160 // Try opening without `O.EXCL` flag. 161 fd = try os.openat(tmp.dir.fd, "some_file", os.O.RDWR | os.O.CREAT, mode); 162 os.close(fd); 163 164 // Try opening as a directory which should fail. 165 try expectError(error.NotDir, os.openat(tmp.dir.fd, "some_file", os.O.RDWR | os.O.DIRECTORY, mode)); 166 167 // Create some directory 168 try os.mkdirat(tmp.dir.fd, "some_dir", mode); 169 170 // Open dir using `open` 171 fd = try os.openat(tmp.dir.fd, "some_dir", os.O.RDONLY | os.O.DIRECTORY, mode); 172 os.close(fd); 173 174 // Try opening as file which should fail. 175 try expectError(error.IsDir, os.openat(tmp.dir.fd, "some_dir", os.O.RDWR, mode)); 176 } 177 178 test "symlink with relative paths" { 179 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest; 180 181 if (true) { 182 // https://github.com/ziglang/zig/issues/14968 183 return error.SkipZigTest; 184 } 185 const cwd = fs.cwd(); 186 cwd.deleteFile("file.txt") catch {}; 187 cwd.deleteFile("symlinked") catch {}; 188 189 // First, try relative paths in cwd 190 try cwd.writeFile("file.txt", "nonsense"); 191 192 if (native_os == .windows) { 193 os.windows.CreateSymbolicLink( 194 cwd.fd, 195 &[_]u16{ 's', 'y', 'm', 'l', 'i', 'n', 'k', 'e', 'd' }, 196 &[_]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' }, 197 false, 198 ) catch |err| switch (err) { 199 // Symlink requires admin privileges on windows, so this test can legitimately fail. 200 error.AccessDenied => { 201 try cwd.deleteFile("file.txt"); 202 try cwd.deleteFile("symlinked"); 203 return error.SkipZigTest; 204 }, 205 else => return err, 206 }; 207 } else { 208 try os.symlink("file.txt", "symlinked"); 209 } 210 211 var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; 212 const given = try os.readlink("symlinked", buffer[0..]); 213 try expect(mem.eql(u8, "file.txt", given)); 214 215 try cwd.deleteFile("file.txt"); 216 try cwd.deleteFile("symlinked"); 217 } 218 219 test "readlink on Windows" { 220 if (native_os != .windows) return error.SkipZigTest; 221 222 try testReadlink("C:\\ProgramData", "C:\\Users\\All Users"); 223 try testReadlink("C:\\Users\\Default", "C:\\Users\\Default User"); 224 try testReadlink("C:\\Users", "C:\\Documents and Settings"); 225 } 226 227 fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void { 228 var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; 229 const given = try os.readlink(symlink_path, buffer[0..]); 230 try expect(mem.eql(u8, target_path, given)); 231 } 232 233 test "link with relative paths" { 234 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest; 235 236 switch (native_os) { 237 .wasi, .linux, .solaris => {}, 238 else => return error.SkipZigTest, 239 } 240 if (true) { 241 // https://github.com/ziglang/zig/issues/14968 242 return error.SkipZigTest; 243 } 244 var cwd = fs.cwd(); 245 246 cwd.deleteFile("example.txt") catch {}; 247 cwd.deleteFile("new.txt") catch {}; 248 249 try cwd.writeFile("example.txt", "example"); 250 try os.link("example.txt", "new.txt", 0); 251 252 const efd = try cwd.openFile("example.txt", .{}); 253 defer efd.close(); 254 255 const nfd = try cwd.openFile("new.txt", .{}); 256 defer nfd.close(); 257 258 { 259 const estat = try os.fstat(efd.handle); 260 const nstat = try os.fstat(nfd.handle); 261 262 try testing.expectEqual(estat.ino, nstat.ino); 263 try testing.expectEqual(@as(@TypeOf(nstat.nlink), 2), nstat.nlink); 264 } 265 266 try os.unlink("new.txt"); 267 268 { 269 const estat = try os.fstat(efd.handle); 270 try testing.expectEqual(@as(@TypeOf(estat.nlink), 1), estat.nlink); 271 } 272 273 try cwd.deleteFile("example.txt"); 274 } 275 276 test "linkat with different directories" { 277 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest; 278 279 switch (native_os) { 280 .wasi, .linux, .solaris => {}, 281 else => return error.SkipZigTest, 282 } 283 if (true) { 284 // https://github.com/ziglang/zig/issues/14968 285 return error.SkipZigTest; 286 } 287 var cwd = fs.cwd(); 288 var tmp = tmpDir(.{}); 289 290 cwd.deleteFile("example.txt") catch {}; 291 tmp.dir.deleteFile("new.txt") catch {}; 292 293 try cwd.writeFile("example.txt", "example"); 294 try os.linkat(cwd.fd, "example.txt", tmp.dir.fd, "new.txt", 0); 295 296 const efd = try cwd.openFile("example.txt", .{}); 297 defer efd.close(); 298 299 const nfd = try tmp.dir.openFile("new.txt", .{}); 300 301 { 302 defer nfd.close(); 303 const estat = try os.fstat(efd.handle); 304 const nstat = try os.fstat(nfd.handle); 305 306 try testing.expectEqual(estat.ino, nstat.ino); 307 try testing.expectEqual(@as(@TypeOf(nstat.nlink), 2), nstat.nlink); 308 } 309 310 try os.unlinkat(tmp.dir.fd, "new.txt", 0); 311 312 { 313 const estat = try os.fstat(efd.handle); 314 try testing.expectEqual(@as(@TypeOf(estat.nlink), 1), estat.nlink); 315 } 316 317 try cwd.deleteFile("example.txt"); 318 } 319 320 test "fstatat" { 321 // enable when `fstat` and `fstatat` are implemented on Windows 322 if (native_os == .windows) return error.SkipZigTest; 323 324 var tmp = tmpDir(.{}); 325 defer tmp.cleanup(); 326 327 // create dummy file 328 const contents = "nonsense"; 329 try tmp.dir.writeFile("file.txt", contents); 330 331 // fetch file's info on the opened fd directly 332 const file = try tmp.dir.openFile("file.txt", .{}); 333 const stat = try os.fstat(file.handle); 334 defer file.close(); 335 336 // now repeat but using `fstatat` instead 337 const flags = if (native_os == .wasi) 0x0 else os.AT.SYMLINK_NOFOLLOW; 338 const statat = try os.fstatat(tmp.dir.fd, "file.txt", flags); 339 try expectEqual(stat, statat); 340 } 341 342 test "readlinkat" { 343 var tmp = tmpDir(.{}); 344 defer tmp.cleanup(); 345 346 // create file 347 try tmp.dir.writeFile("file.txt", "nonsense"); 348 349 // create a symbolic link 350 if (native_os == .windows) { 351 os.windows.CreateSymbolicLink( 352 tmp.dir.fd, 353 &[_]u16{ 'l', 'i', 'n', 'k' }, 354 &[_]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' }, 355 false, 356 ) catch |err| switch (err) { 357 // Symlink requires admin privileges on windows, so this test can legitimately fail. 358 error.AccessDenied => return error.SkipZigTest, 359 else => return err, 360 }; 361 } else { 362 try os.symlinkat("file.txt", tmp.dir.fd, "link"); 363 } 364 365 // read the link 366 var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; 367 const read_link = try os.readlinkat(tmp.dir.fd, "link", buffer[0..]); 368 try expect(mem.eql(u8, "file.txt", read_link)); 369 } 370 371 fn testThreadIdFn(thread_id: *Thread.Id) void { 372 thread_id.* = Thread.getCurrentId(); 373 } 374 375 test "std.Thread.getCurrentId" { 376 if (builtin.single_threaded) return error.SkipZigTest; 377 378 var thread_current_id: Thread.Id = undefined; 379 const thread = try Thread.spawn(.{}, testThreadIdFn, .{&thread_current_id}); 380 thread.join(); 381 try expect(Thread.getCurrentId() != thread_current_id); 382 } 383 384 test "spawn threads" { 385 if (builtin.single_threaded) return error.SkipZigTest; 386 387 var shared_ctx: i32 = 1; 388 389 const thread1 = try Thread.spawn(.{}, start1, .{}); 390 const thread2 = try Thread.spawn(.{}, start2, .{&shared_ctx}); 391 const thread3 = try Thread.spawn(.{}, start2, .{&shared_ctx}); 392 const thread4 = try Thread.spawn(.{}, start2, .{&shared_ctx}); 393 394 thread1.join(); 395 thread2.join(); 396 thread3.join(); 397 thread4.join(); 398 399 try expect(shared_ctx == 4); 400 } 401 402 fn start1() u8 { 403 return 0; 404 } 405 406 fn start2(ctx: *i32) u8 { 407 _ = @atomicRmw(i32, ctx, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); 408 return 0; 409 } 410 411 test "cpu count" { 412 if (native_os == .wasi) return error.SkipZigTest; 413 414 const cpu_count = try Thread.getCpuCount(); 415 try expect(cpu_count >= 1); 416 } 417 418 test "thread local storage" { 419 if (builtin.single_threaded) return error.SkipZigTest; 420 const thread1 = try Thread.spawn(.{}, testTls, .{}); 421 const thread2 = try Thread.spawn(.{}, testTls, .{}); 422 try testTls(); 423 thread1.join(); 424 thread2.join(); 425 } 426 427 threadlocal var x: i32 = 1234; 428 fn testTls() !void { 429 if (x != 1234) return error.TlsBadStartValue; 430 x += 1; 431 if (x != 1235) return error.TlsBadEndValue; 432 } 433 434 test "getrandom" { 435 var buf_a: [50]u8 = undefined; 436 var buf_b: [50]u8 = undefined; 437 try os.getrandom(&buf_a); 438 try os.getrandom(&buf_b); 439 // If this test fails the chance is significantly higher that there is a bug than 440 // that two sets of 50 bytes were equal. 441 try expect(!mem.eql(u8, &buf_a, &buf_b)); 442 } 443 444 test "getcwd" { 445 // at least call it so it gets compiled 446 var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; 447 _ = os.getcwd(&buf) catch undefined; 448 } 449 450 test "sigaltstack" { 451 if (native_os == .windows or native_os == .wasi) return error.SkipZigTest; 452 453 var st: os.stack_t = undefined; 454 try os.sigaltstack(null, &st); 455 // Setting a stack size less than MINSIGSTKSZ returns ENOMEM 456 st.flags = 0; 457 st.size = 1; 458 try testing.expectError(error.SizeTooSmall, os.sigaltstack(&st, null)); 459 } 460 461 // If the type is not available use void to avoid erroring out when `iter_fn` is 462 // analyzed 463 const dl_phdr_info = if (@hasDecl(os.system, "dl_phdr_info")) os.dl_phdr_info else anyopaque; 464 465 const IterFnError = error{ 466 MissingPtLoadSegment, 467 MissingLoad, 468 BadElfMagic, 469 FailedConsistencyCheck, 470 }; 471 472 fn iter_fn(info: *dl_phdr_info, size: usize, counter: *usize) IterFnError!void { 473 _ = size; 474 // Count how many libraries are loaded 475 counter.* += @as(usize, 1); 476 477 // The image should contain at least a PT_LOAD segment 478 if (info.dlpi_phnum < 1) return error.MissingPtLoadSegment; 479 480 // Quick & dirty validation of the phdr pointers, make sure we're not 481 // pointing to some random gibberish 482 var i: usize = 0; 483 var found_load = false; 484 while (i < info.dlpi_phnum) : (i += 1) { 485 const phdr = info.dlpi_phdr[i]; 486 487 if (phdr.p_type != elf.PT_LOAD) continue; 488 489 const reloc_addr = info.dlpi_addr + phdr.p_vaddr; 490 // Find the ELF header 491 const elf_header = @as(*elf.Ehdr, @ptrFromInt(reloc_addr - phdr.p_offset)); 492 // Validate the magic 493 if (!mem.eql(u8, elf_header.e_ident[0..4], elf.MAGIC)) return error.BadElfMagic; 494 // Consistency check 495 if (elf_header.e_phnum != info.dlpi_phnum) return error.FailedConsistencyCheck; 496 497 found_load = true; 498 break; 499 } 500 501 if (!found_load) return error.MissingLoad; 502 } 503 504 test "dl_iterate_phdr" { 505 if (builtin.object_format != .elf) return error.SkipZigTest; 506 507 var counter: usize = 0; 508 try os.dl_iterate_phdr(&counter, IterFnError, iter_fn); 509 try expect(counter != 0); 510 } 511 512 test "gethostname" { 513 if (native_os == .windows or native_os == .wasi) 514 return error.SkipZigTest; 515 516 var buf: [os.HOST_NAME_MAX]u8 = undefined; 517 const hostname = try os.gethostname(&buf); 518 try expect(hostname.len != 0); 519 } 520 521 test "pipe" { 522 if (native_os == .windows or native_os == .wasi) 523 return error.SkipZigTest; 524 525 var fds = try os.pipe(); 526 try expect((try os.write(fds[1], "hello")) == 5); 527 var buf: [16]u8 = undefined; 528 try expect((try os.read(fds[0], buf[0..])) == 5); 529 try testing.expectEqualSlices(u8, buf[0..5], "hello"); 530 os.close(fds[1]); 531 os.close(fds[0]); 532 } 533 534 test "argsAlloc" { 535 var args = try std.process.argsAlloc(std.testing.allocator); 536 std.process.argsFree(std.testing.allocator, args); 537 } 538 539 test "memfd_create" { 540 // memfd_create is only supported by linux and freebsd. 541 switch (native_os) { 542 .linux => {}, 543 .freebsd => { 544 if (comptime builtin.os.version_range.semver.max.order(.{ .major = 13, .minor = 0, .patch = 0 }) == .lt) 545 return error.SkipZigTest; 546 }, 547 else => return error.SkipZigTest, 548 } 549 550 const fd = os.memfd_create("test", 0) catch |err| switch (err) { 551 // Related: https://github.com/ziglang/zig/issues/4019 552 error.SystemOutdated => return error.SkipZigTest, 553 else => |e| return e, 554 }; 555 defer os.close(fd); 556 try expect((try os.write(fd, "test")) == 4); 557 try os.lseek_SET(fd, 0); 558 559 var buf: [10]u8 = undefined; 560 const bytes_read = try os.read(fd, &buf); 561 try expect(bytes_read == 4); 562 try expect(mem.eql(u8, buf[0..4], "test")); 563 } 564 565 test "mmap" { 566 if (native_os == .windows or native_os == .wasi) 567 return error.SkipZigTest; 568 569 var tmp = tmpDir(.{}); 570 defer tmp.cleanup(); 571 572 // Simple mmap() call with non page-aligned size 573 { 574 const data = try os.mmap( 575 null, 576 1234, 577 os.PROT.READ | os.PROT.WRITE, 578 os.MAP.ANONYMOUS | os.MAP.PRIVATE, 579 -1, 580 0, 581 ); 582 defer os.munmap(data); 583 584 try testing.expectEqual(@as(usize, 1234), data.len); 585 586 // By definition the data returned by mmap is zero-filled 587 try testing.expect(mem.eql(u8, data, &[_]u8{0x00} ** 1234)); 588 589 // Make sure the memory is writeable as requested 590 @memset(data, 0x55); 591 try testing.expect(mem.eql(u8, data, &[_]u8{0x55} ** 1234)); 592 } 593 594 const test_out_file = "os_tmp_test"; 595 // Must be a multiple of 4096 so that the test works with mmap2 596 const alloc_size = 8 * 4096; 597 598 // Create a file used for testing mmap() calls with a file descriptor 599 { 600 const file = try tmp.dir.createFile(test_out_file, .{}); 601 defer file.close(); 602 603 const stream = file.writer(); 604 605 var i: u32 = 0; 606 while (i < alloc_size / @sizeOf(u32)) : (i += 1) { 607 try stream.writeIntNative(u32, i); 608 } 609 } 610 611 // Map the whole file 612 { 613 const file = try tmp.dir.openFile(test_out_file, .{}); 614 defer file.close(); 615 616 const data = try os.mmap( 617 null, 618 alloc_size, 619 os.PROT.READ, 620 os.MAP.PRIVATE, 621 file.handle, 622 0, 623 ); 624 defer os.munmap(data); 625 626 var mem_stream = io.fixedBufferStream(data); 627 const stream = mem_stream.reader(); 628 629 var i: u32 = 0; 630 while (i < alloc_size / @sizeOf(u32)) : (i += 1) { 631 try testing.expectEqual(i, try stream.readIntNative(u32)); 632 } 633 } 634 635 // Map the upper half of the file 636 { 637 const file = try tmp.dir.openFile(test_out_file, .{}); 638 defer file.close(); 639 640 const data = try os.mmap( 641 null, 642 alloc_size / 2, 643 os.PROT.READ, 644 os.MAP.PRIVATE, 645 file.handle, 646 alloc_size / 2, 647 ); 648 defer os.munmap(data); 649 650 var mem_stream = io.fixedBufferStream(data); 651 const stream = mem_stream.reader(); 652 653 var i: u32 = alloc_size / 2 / @sizeOf(u32); 654 while (i < alloc_size / @sizeOf(u32)) : (i += 1) { 655 try testing.expectEqual(i, try stream.readIntNative(u32)); 656 } 657 } 658 659 try tmp.dir.deleteFile(test_out_file); 660 } 661 662 test "getenv" { 663 if (native_os == .windows) { 664 try expect(os.getenvW(&[_:0]u16{ 'B', 'O', 'G', 'U', 'S', 0x11, 0x22, 0x33, 0x44, 0x55 }) == null); 665 } else { 666 try expect(os.getenvZ("BOGUSDOESNOTEXISTENVVAR") == null); 667 } 668 } 669 670 test "fcntl" { 671 if (native_os == .windows or native_os == .wasi) 672 return error.SkipZigTest; 673 674 var tmp = tmpDir(.{}); 675 defer tmp.cleanup(); 676 677 const test_out_file = "os_tmp_test"; 678 679 const file = try tmp.dir.createFile(test_out_file, .{}); 680 defer { 681 file.close(); 682 tmp.dir.deleteFile(test_out_file) catch {}; 683 } 684 685 // Note: The test assumes createFile opens the file with O.CLOEXEC 686 { 687 const flags = try os.fcntl(file.handle, os.F.GETFD, 0); 688 try expect((flags & os.FD_CLOEXEC) != 0); 689 } 690 { 691 _ = try os.fcntl(file.handle, os.F.SETFD, 0); 692 const flags = try os.fcntl(file.handle, os.F.GETFD, 0); 693 try expect((flags & os.FD_CLOEXEC) == 0); 694 } 695 { 696 _ = try os.fcntl(file.handle, os.F.SETFD, os.FD_CLOEXEC); 697 const flags = try os.fcntl(file.handle, os.F.GETFD, 0); 698 try expect((flags & os.FD_CLOEXEC) != 0); 699 } 700 } 701 702 test "signalfd" { 703 switch (native_os) { 704 .linux, .solaris => {}, 705 else => return error.SkipZigTest, 706 } 707 _ = &os.signalfd; 708 } 709 710 test "sync" { 711 if (native_os != .linux) 712 return error.SkipZigTest; 713 714 var tmp = tmpDir(.{}); 715 defer tmp.cleanup(); 716 717 const test_out_file = "os_tmp_test"; 718 const file = try tmp.dir.createFile(test_out_file, .{}); 719 defer { 720 file.close(); 721 tmp.dir.deleteFile(test_out_file) catch {}; 722 } 723 724 os.sync(); 725 try os.syncfs(file.handle); 726 } 727 728 test "fsync" { 729 switch (native_os) { 730 .linux, .windows, .solaris => {}, 731 else => return error.SkipZigTest, 732 } 733 734 var tmp = tmpDir(.{}); 735 defer tmp.cleanup(); 736 737 const test_out_file = "os_tmp_test"; 738 const file = try tmp.dir.createFile(test_out_file, .{}); 739 defer { 740 file.close(); 741 tmp.dir.deleteFile(test_out_file) catch {}; 742 } 743 744 try os.fsync(file.handle); 745 try os.fdatasync(file.handle); 746 } 747 748 test "getrlimit and setrlimit" { 749 if (!@hasDecl(os.system, "rlimit")) { 750 return error.SkipZigTest; 751 } 752 753 inline for (std.meta.fields(os.rlimit_resource)) |field| { 754 const resource = @as(os.rlimit_resource, @enumFromInt(field.value)); 755 const limit = try os.getrlimit(resource); 756 757 // On 32 bit MIPS musl includes a fix which changes limits greater than -1UL/2 to RLIM_INFINITY. 758 // See http://git.musl-libc.org/cgit/musl/commit/src/misc/getrlimit.c?id=8258014fd1e34e942a549c88c7e022a00445c352 759 // 760 // This happens for example if RLIMIT_MEMLOCK is bigger than ~2GiB. 761 // In that case the following the limit would be RLIM_INFINITY and the following setrlimit fails with EPERM. 762 if (comptime builtin.cpu.arch.isMIPS() and builtin.link_libc) { 763 if (limit.cur != os.linux.RLIM.INFINITY) { 764 try os.setrlimit(resource, limit); 765 } 766 } else { 767 try os.setrlimit(resource, limit); 768 } 769 } 770 } 771 772 test "shutdown socket" { 773 if (native_os == .wasi) 774 return error.SkipZigTest; 775 if (native_os == .windows) { 776 _ = try os.windows.WSAStartup(2, 2); 777 } 778 defer { 779 if (native_os == .windows) { 780 os.windows.WSACleanup() catch unreachable; 781 } 782 } 783 const sock = try os.socket(os.AF.INET, os.SOCK.STREAM, 0); 784 os.shutdown(sock, .both) catch |err| switch (err) { 785 error.SocketNotConnected => {}, 786 else => |e| return e, 787 }; 788 os.closeSocket(sock); 789 } 790 791 test "sigaction" { 792 if (native_os == .wasi or native_os == .windows) 793 return error.SkipZigTest; 794 795 // https://github.com/ziglang/zig/issues/7427 796 if (native_os == .linux and builtin.target.cpu.arch == .x86) 797 return error.SkipZigTest; 798 799 // https://github.com/ziglang/zig/issues/15381 800 if (native_os == .macos and builtin.target.cpu.arch == .x86_64) { 801 return error.SkipZigTest; 802 } 803 804 const S = struct { 805 var handler_called_count: u32 = 0; 806 807 fn handler(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const anyopaque) callconv(.C) void { 808 _ = ctx_ptr; 809 // Check that we received the correct signal. 810 switch (native_os) { 811 .netbsd => { 812 if (sig == os.SIG.USR1 and sig == info.info.signo) 813 handler_called_count += 1; 814 }, 815 else => { 816 if (sig == os.SIG.USR1 and sig == info.signo) 817 handler_called_count += 1; 818 }, 819 } 820 } 821 }; 822 823 var sa = os.Sigaction{ 824 .handler = .{ .sigaction = &S.handler }, 825 .mask = os.empty_sigset, 826 .flags = os.SA.SIGINFO | os.SA.RESETHAND, 827 }; 828 var old_sa: os.Sigaction = undefined; 829 830 // Install the new signal handler. 831 try os.sigaction(os.SIG.USR1, &sa, null); 832 833 // Check that we can read it back correctly. 834 try os.sigaction(os.SIG.USR1, null, &old_sa); 835 try testing.expectEqual(&S.handler, old_sa.handler.sigaction.?); 836 try testing.expect((old_sa.flags & os.SA.SIGINFO) != 0); 837 838 // Invoke the handler. 839 try os.raise(os.SIG.USR1); 840 try testing.expect(S.handler_called_count == 1); 841 842 // Check if passing RESETHAND correctly reset the handler to SIG_DFL 843 try os.sigaction(os.SIG.USR1, null, &old_sa); 844 try testing.expectEqual(os.SIG.DFL, old_sa.handler.handler); 845 846 // Reinstall the signal w/o RESETHAND and re-raise 847 sa.flags = os.SA.SIGINFO; 848 try os.sigaction(os.SIG.USR1, &sa, null); 849 try os.raise(os.SIG.USR1); 850 try testing.expect(S.handler_called_count == 2); 851 852 // Now set the signal to ignored 853 sa.handler = .{ .handler = os.SIG.IGN }; 854 sa.flags = 0; 855 try os.sigaction(os.SIG.USR1, &sa, null); 856 857 // Re-raise to ensure handler is actually ignored 858 try os.raise(os.SIG.USR1); 859 try testing.expect(S.handler_called_count == 2); 860 861 // Ensure that ignored state is returned when querying 862 try os.sigaction(os.SIG.USR1, null, &old_sa); 863 try testing.expectEqual(os.SIG.IGN, old_sa.handler.handler.?); 864 } 865 866 test "dup & dup2" { 867 switch (native_os) { 868 .linux, .solaris => {}, 869 else => return error.SkipZigTest, 870 } 871 872 var tmp = tmpDir(.{}); 873 defer tmp.cleanup(); 874 875 { 876 var file = try tmp.dir.createFile("os_dup_test", .{}); 877 defer file.close(); 878 879 var duped = std.fs.File{ .handle = try os.dup(file.handle) }; 880 defer duped.close(); 881 try duped.writeAll("dup"); 882 883 // Tests aren't run in parallel so using the next fd shouldn't be an issue. 884 const new_fd = duped.handle + 1; 885 try os.dup2(file.handle, new_fd); 886 var dup2ed = std.fs.File{ .handle = new_fd }; 887 defer dup2ed.close(); 888 try dup2ed.writeAll("dup2"); 889 } 890 891 var file = try tmp.dir.openFile("os_dup_test", .{}); 892 defer file.close(); 893 894 var buf: [7]u8 = undefined; 895 try testing.expectEqualStrings("dupdup2", buf[0..try file.readAll(&buf)]); 896 } 897 898 test "writev longer than IOV_MAX" { 899 if (native_os == .windows or native_os == .wasi) return error.SkipZigTest; 900 901 var tmp = tmpDir(.{}); 902 defer tmp.cleanup(); 903 904 var file = try tmp.dir.createFile("pwritev", .{}); 905 defer file.close(); 906 907 const iovecs = [_]os.iovec_const{.{ .iov_base = "a", .iov_len = 1 }} ** (os.IOV_MAX + 1); 908 const amt = try file.writev(&iovecs); 909 try testing.expectEqual(@as(usize, os.IOV_MAX), amt); 910 } 911 912 test "POSIX file locking with fcntl" { 913 if (native_os == .windows or native_os == .wasi) { 914 // Not POSIX. 915 return error.SkipZigTest; 916 } 917 918 if (true) { 919 // https://github.com/ziglang/zig/issues/11074 920 return error.SkipZigTest; 921 } 922 923 var tmp = std.testing.tmpDir(.{}); 924 defer tmp.cleanup(); 925 926 // Create a temporary lock file 927 var file = try tmp.dir.createFile("lock", .{ .read = true }); 928 defer file.close(); 929 try file.setEndPos(2); 930 const fd = file.handle; 931 932 // Place an exclusive lock on the first byte, and a shared lock on the second byte: 933 var struct_flock = std.mem.zeroInit(os.Flock, .{ .type = os.F.WRLCK }); 934 _ = try os.fcntl(fd, os.F.SETLK, @intFromPtr(&struct_flock)); 935 struct_flock.start = 1; 936 struct_flock.type = os.F.RDLCK; 937 _ = try os.fcntl(fd, os.F.SETLK, @intFromPtr(&struct_flock)); 938 939 // Check the locks in a child process: 940 const pid = try os.fork(); 941 if (pid == 0) { 942 // child expects be denied the exclusive lock: 943 struct_flock.start = 0; 944 struct_flock.type = os.F.WRLCK; 945 try expectError(error.Locked, os.fcntl(fd, os.F.SETLK, @intFromPtr(&struct_flock))); 946 // child expects to get the shared lock: 947 struct_flock.start = 1; 948 struct_flock.type = os.F.RDLCK; 949 _ = try os.fcntl(fd, os.F.SETLK, @intFromPtr(&struct_flock)); 950 // child waits for the exclusive lock in order to test deadlock: 951 struct_flock.start = 0; 952 struct_flock.type = os.F.WRLCK; 953 _ = try os.fcntl(fd, os.F.SETLKW, @intFromPtr(&struct_flock)); 954 // child exits without continuing: 955 os.exit(0); 956 } else { 957 // parent waits for child to get shared lock: 958 std.time.sleep(1 * std.time.ns_per_ms); 959 // parent expects deadlock when attempting to upgrade the shared lock to exclusive: 960 struct_flock.start = 1; 961 struct_flock.type = os.F.WRLCK; 962 try expectError(error.DeadLock, os.fcntl(fd, os.F.SETLKW, @intFromPtr(&struct_flock))); 963 // parent releases exclusive lock: 964 struct_flock.start = 0; 965 struct_flock.type = os.F.UNLCK; 966 _ = try os.fcntl(fd, os.F.SETLK, @intFromPtr(&struct_flock)); 967 // parent releases shared lock: 968 struct_flock.start = 1; 969 struct_flock.type = os.F.UNLCK; 970 _ = try os.fcntl(fd, os.F.SETLK, @intFromPtr(&struct_flock)); 971 // parent waits for child: 972 const result = os.waitpid(pid, 0); 973 try expect(result.status == 0 * 256); 974 } 975 } 976 977 test "rename smoke test" { 978 if (native_os == .wasi) return error.SkipZigTest; 979 980 var tmp = tmpDir(.{}); 981 defer tmp.cleanup(); 982 983 // Get base abs path 984 var arena = ArenaAllocator.init(testing.allocator); 985 defer arena.deinit(); 986 const allocator = arena.allocator(); 987 988 const base_path = blk: { 989 const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] }); 990 break :blk try fs.realpathAlloc(allocator, relative_path); 991 }; 992 993 var file_path: []u8 = undefined; 994 var fd: os.fd_t = undefined; 995 const mode: os.mode_t = if (native_os == .windows) 0 else 0o666; 996 997 // Create some file using `open`. 998 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); 999 fd = try os.open(file_path, os.O.RDWR | os.O.CREAT | os.O.EXCL, mode); 1000 os.close(fd); 1001 1002 // Rename the file 1003 var new_file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_file" }); 1004 try os.rename(file_path, new_file_path); 1005 1006 // Try opening renamed file 1007 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_file" }); 1008 fd = try os.open(file_path, os.O.RDWR, mode); 1009 os.close(fd); 1010 1011 // Try opening original file - should fail with error.FileNotFound 1012 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); 1013 try expectError(error.FileNotFound, os.open(file_path, os.O.RDWR, mode)); 1014 1015 // Create some directory 1016 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" }); 1017 try os.mkdir(file_path, mode); 1018 1019 // Rename the directory 1020 new_file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_dir" }); 1021 try os.rename(file_path, new_file_path); 1022 1023 // Try opening renamed directory 1024 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_dir" }); 1025 fd = try os.open(file_path, os.O.RDONLY | os.O.DIRECTORY, mode); 1026 os.close(fd); 1027 1028 // Try opening original directory - should fail with error.FileNotFound 1029 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" }); 1030 try expectError(error.FileNotFound, os.open(file_path, os.O.RDONLY | os.O.DIRECTORY, mode)); 1031 } 1032 1033 test "access smoke test" { 1034 if (native_os == .wasi) return error.SkipZigTest; 1035 1036 var tmp = tmpDir(.{}); 1037 defer tmp.cleanup(); 1038 1039 // Get base abs path 1040 var arena = ArenaAllocator.init(testing.allocator); 1041 defer arena.deinit(); 1042 const allocator = arena.allocator(); 1043 1044 const base_path = blk: { 1045 const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] }); 1046 break :blk try fs.realpathAlloc(allocator, relative_path); 1047 }; 1048 1049 var file_path: []u8 = undefined; 1050 var fd: os.fd_t = undefined; 1051 const mode: os.mode_t = if (native_os == .windows) 0 else 0o666; 1052 1053 // Create some file using `open`. 1054 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); 1055 fd = try os.open(file_path, os.O.RDWR | os.O.CREAT | os.O.EXCL, mode); 1056 os.close(fd); 1057 1058 // Try to access() the file 1059 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); 1060 if (builtin.os.tag == .windows) { 1061 try os.access(file_path, os.F_OK); 1062 } else { 1063 try os.access(file_path, os.F_OK | os.W_OK | os.R_OK); 1064 } 1065 1066 // Try to access() a non-existent file - should fail with error.FileNotFound 1067 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_file" }); 1068 try expectError(error.FileNotFound, os.access(file_path, os.F_OK)); 1069 1070 // Create some directory 1071 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" }); 1072 try os.mkdir(file_path, mode); 1073 1074 // Try to access() the directory 1075 file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" }); 1076 try os.access(file_path, os.F_OK); 1077 } 1078 1079 test "timerfd" { 1080 if (native_os != .linux) 1081 return error.SkipZigTest; 1082 1083 const linux = os.linux; 1084 var tfd = try os.timerfd_create(linux.CLOCK.MONOTONIC, linux.TFD.CLOEXEC); 1085 defer os.close(tfd); 1086 1087 // Fire event 10_000_000ns = 10ms after the os.timerfd_settime call. 1088 var sit: linux.itimerspec = .{ .it_interval = .{ .tv_sec = 0, .tv_nsec = 0 }, .it_value = .{ .tv_sec = 0, .tv_nsec = 10 * (1000 * 1000) } }; 1089 try os.timerfd_settime(tfd, 0, &sit, null); 1090 1091 var fds: [1]os.pollfd = .{.{ .fd = tfd, .events = os.linux.POLL.IN, .revents = 0 }}; 1092 try expectEqual(@as(usize, 1), try os.poll(&fds, -1)); // -1 => infinite waiting 1093 1094 var git = try os.timerfd_gettime(tfd); 1095 var expect_disarmed_timer: linux.itimerspec = .{ .it_interval = .{ .tv_sec = 0, .tv_nsec = 0 }, .it_value = .{ .tv_sec = 0, .tv_nsec = 0 } }; 1096 try expectEqual(expect_disarmed_timer, git); 1097 } 1098 1099 test "isatty" { 1100 var tmp = tmpDir(.{}); 1101 defer tmp.cleanup(); 1102 1103 var file = try tmp.dir.createFile("foo", .{}); 1104 defer file.close(); 1105 1106 try expectEqual(os.isatty(file.handle), false); 1107 } 1108 1109 test "read with empty buffer" { 1110 if (native_os == .wasi) return error.SkipZigTest; 1111 1112 var tmp = tmpDir(.{}); 1113 defer tmp.cleanup(); 1114 1115 var arena = ArenaAllocator.init(testing.allocator); 1116 defer arena.deinit(); 1117 const allocator = arena.allocator(); 1118 1119 // Get base abs path 1120 const base_path = blk: { 1121 const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] }); 1122 break :blk try fs.realpathAlloc(allocator, relative_path); 1123 }; 1124 1125 var file_path: []u8 = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); 1126 var file = try fs.cwd().createFile(file_path, .{ .read = true }); 1127 defer file.close(); 1128 1129 var bytes = try allocator.alloc(u8, 0); 1130 1131 _ = try os.read(file.handle, bytes); 1132 } 1133 1134 test "pread with empty buffer" { 1135 if (native_os == .wasi) return error.SkipZigTest; 1136 1137 var tmp = tmpDir(.{}); 1138 defer tmp.cleanup(); 1139 1140 var arena = ArenaAllocator.init(testing.allocator); 1141 defer arena.deinit(); 1142 const allocator = arena.allocator(); 1143 1144 // Get base abs path 1145 const base_path = blk: { 1146 const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] }); 1147 break :blk try fs.realpathAlloc(allocator, relative_path); 1148 }; 1149 1150 var file_path: []u8 = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); 1151 var file = try fs.cwd().createFile(file_path, .{ .read = true }); 1152 defer file.close(); 1153 1154 var bytes = try allocator.alloc(u8, 0); 1155 1156 _ = try os.pread(file.handle, bytes, 0); 1157 } 1158 1159 test "write with empty buffer" { 1160 if (native_os == .wasi) return error.SkipZigTest; 1161 1162 var tmp = tmpDir(.{}); 1163 defer tmp.cleanup(); 1164 1165 var arena = ArenaAllocator.init(testing.allocator); 1166 defer arena.deinit(); 1167 const allocator = arena.allocator(); 1168 1169 // Get base abs path 1170 const base_path = blk: { 1171 const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] }); 1172 break :blk try fs.realpathAlloc(allocator, relative_path); 1173 }; 1174 1175 var file_path: []u8 = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); 1176 var file = try fs.cwd().createFile(file_path, .{}); 1177 defer file.close(); 1178 1179 var bytes = try allocator.alloc(u8, 0); 1180 1181 _ = try os.write(file.handle, bytes); 1182 } 1183 1184 test "pwrite with empty buffer" { 1185 if (native_os == .wasi) return error.SkipZigTest; 1186 1187 var tmp = tmpDir(.{}); 1188 defer tmp.cleanup(); 1189 1190 var arena = ArenaAllocator.init(testing.allocator); 1191 defer arena.deinit(); 1192 const allocator = arena.allocator(); 1193 1194 // Get base abs path 1195 const base_path = blk: { 1196 const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] }); 1197 break :blk try fs.realpathAlloc(allocator, relative_path); 1198 }; 1199 1200 var file_path: []u8 = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); 1201 var file = try fs.cwd().createFile(file_path, .{}); 1202 defer file.close(); 1203 1204 var bytes = try allocator.alloc(u8, 0); 1205 1206 _ = try os.pwrite(file.handle, bytes, 0); 1207 } 1208 1209 test "fchmodat smoke test" { 1210 if (!std.fs.has_executable_bit) return error.SkipZigTest; 1211 1212 var tmp = tmpDir(.{}); 1213 defer tmp.cleanup(); 1214 1215 try expectError(error.FileNotFound, os.fchmodat(tmp.dir.fd, "foo.txt", 0o666, 0)); 1216 const fd = try os.openat(tmp.dir.fd, "foo.txt", os.O.RDWR | os.O.CREAT | os.O.EXCL, 0o666); 1217 os.close(fd); 1218 try os.fchmodat(tmp.dir.fd, "foo.txt", 0o755, 0); 1219 const st = try os.fstatat(tmp.dir.fd, "foo.txt", 0); 1220 try expectEqual(@as(os.mode_t, 0o755), st.mode & 0b111_111_111); 1221 }