2020-08-20 02:40:15 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
// Copyright (c) 2015-2020 Zig Contributors
|
|
|
|
// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
|
|
|
|
// The MIT license requires this copyright notice to be included in all copies
|
|
|
|
// and substantial portions of the software.
|
2019-05-26 17:17:34 +00:00
|
|
|
const builtin = @import("builtin");
|
2019-05-24 22:27:18 +00:00
|
|
|
const std = @import("std.zig");
|
|
|
|
const os = std.os;
|
|
|
|
const mem = std.mem;
|
2019-05-26 17:37:34 +00:00
|
|
|
const base64 = std.base64;
|
|
|
|
const crypto = std.crypto;
|
2019-05-26 17:17:34 +00:00
|
|
|
const Allocator = std.mem.Allocator;
|
2019-05-26 17:37:34 +00:00
|
|
|
const assert = std.debug.assert;
|
2019-11-21 22:07:29 +00:00
|
|
|
const math = std.math;
|
2019-05-24 22:27:18 +00:00
|
|
|
|
2020-05-02 03:17:15 +00:00
|
|
|
const is_darwin = std.Target.current.os.tag.isDarwin();
|
|
|
|
|
2019-05-24 22:27:18 +00:00
|
|
|
pub const path = @import("fs/path.zig");
|
|
|
|
pub const File = @import("fs/file.zig").File;
|
2020-05-05 13:07:21 +00:00
|
|
|
pub const wasi = @import("fs/wasi.zig");
|
2019-05-24 22:27:18 +00:00
|
|
|
|
2020-03-30 18:23:22 +00:00
|
|
|
// TODO audit these APIs with respect to Dir and absolute paths
|
|
|
|
|
2019-05-24 22:27:18 +00:00
|
|
|
pub const realpath = os.realpath;
|
2020-03-30 18:23:22 +00:00
|
|
|
pub const realpathZ = os.realpathZ;
|
|
|
|
pub const realpathC = @compileError("deprecated: renamed to realpathZ");
|
2019-05-24 22:27:18 +00:00
|
|
|
pub const realpathW = os.realpathW;
|
|
|
|
|
|
|
|
pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir;
|
|
|
|
pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError;
|
|
|
|
|
2020-02-06 22:56:40 +00:00
|
|
|
pub const Watch = @import("fs/watch.zig").Watch;
|
|
|
|
|
2020-03-27 19:28:48 +00:00
|
|
|
/// This represents the maximum size of a UTF-8 encoded file path that the
|
|
|
|
/// operating system will accept. Paths, including those returned from file
|
|
|
|
/// system operations, may be longer than this length, but such paths cannot
|
|
|
|
/// be successfully passed back in other file system operations. However,
|
|
|
|
/// all path components returned by file system operations are assumed to
|
2019-05-24 22:27:18 +00:00
|
|
|
/// fit into a UTF-8 encoded array of this length.
|
2019-11-25 05:30:28 +00:00
|
|
|
/// The byte count includes room for a null sentinel byte.
|
2020-02-25 06:52:27 +00:00
|
|
|
pub const MAX_PATH_BYTES = switch (builtin.os.tag) {
|
2020-10-17 15:38:23 +00:00
|
|
|
.linux, .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd => os.PATH_MAX,
|
2019-05-24 22:27:18 +00:00
|
|
|
// Each UTF-16LE character may be expanded to 3 UTF-8 bytes.
|
|
|
|
// If it would require 4 UTF-8 bytes, then there would be a surrogate
|
|
|
|
// pair in the UTF-16LE, and we (over)account 3 bytes for it that way.
|
|
|
|
// +1 for the null byte at the end, which can be encoded in 1 byte.
|
2019-05-25 02:52:07 +00:00
|
|
|
.windows => os.windows.PATH_MAX_WIDE * 3 + 1,
|
Add/fix missing WASI functionality to pass libstd tests
This rather large commit adds/fixes missing WASI functionality
in `libstd` needed to pass the `libstd` tests. As such, now by
default tests targeting `wasm32-wasi` target are enabled in
`test/tests.zig` module. However, they can be disabled by passing
the `-Dskip-wasi=true` flag when invoking the `zig build test`
command. When the flag is set to `false`, i.e., when WASI tests are
included, `wasmtime` with `--dir=.` is used as the default testing
command.
Since the majority of `libstd` tests were relying on `fs.cwd()`
call to get current working directory handle wrapped in `Dir`
struct, in order to make the tests WASI-friendly, `fs.cwd()`
call was replaced with `testing.getTestDir()` function which
resolved to either `fs.cwd()` for non-WASI targets, or tries to
fetch the preopen list from the WASI runtime and extract a
preopen for '.' path.
The summary of changes introduced by this commit:
* implement `Dir.makeDir` and `Dir.openDir` targeting WASI
* implement `Dir.deleteFile` and `Dir.deleteDir` targeting WASI
* fix `os.close` and map errors in `unlinkat`
* move WASI-specific `mkdirat` and `unlinkat` from `std.fs.wasi`
to `std.os` module
* implement `lseek_{SET, CUR, END}` targeting WASI
* implement `futimens` targeting WASI
* implement `ftruncate` targeting WASI
* implement `readv`, `writev`, `pread{v}`, `pwrite{v}` targeting WASI
* make sure ANSI escape codes are _not_ used in stderr or stdout
in WASI, as WASI always sanitizes stderr, and sanitizes stdout if
fd is a TTY
* fix specifying WASI rights when opening/creating files/dirs
* tweak `AtomicFile` to be WASI-compatible
* implement `os.renameatWasi` for WASI-compliant `os.renameat` function
* implement sleep() targeting WASI
* fix `process.getEnvMap` targeting WASI
2020-05-05 15:23:49 +00:00
|
|
|
// TODO work out what a reasonable value we should use here
|
|
|
|
.wasi => 4096,
|
2019-05-24 22:27:18 +00:00
|
|
|
else => @compileError("Unsupported OS"),
|
|
|
|
};
|
|
|
|
|
2020-04-15 01:07:27 +00:00
|
|
|
pub const base64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
2019-05-24 22:27:18 +00:00
|
|
|
|
2020-04-15 01:07:27 +00:00
|
|
|
/// Base64 encoder, replacing the standard `+/` with `-_` so that it can be used in a file name on any filesystem.
|
|
|
|
pub const base64_encoder = base64.Base64Encoder.init(base64_alphabet, base64.standard_pad_char);
|
|
|
|
|
|
|
|
/// Base64 decoder, replacing the standard `+/` with `-_` so that it can be used in a file name on any filesystem.
|
|
|
|
pub const base64_decoder = base64.Base64Decoder.init(base64_alphabet, base64.standard_pad_char);
|
2020-03-07 03:17:23 +00:00
|
|
|
|
2020-02-06 22:56:40 +00:00
|
|
|
/// Whether or not async file system syscalls need a dedicated thread because the operating
|
|
|
|
/// system does not support non-blocking I/O on the file system.
|
2020-02-25 06:52:27 +00:00
|
|
|
pub const need_async_thread = std.io.is_async and switch (builtin.os.tag) {
|
2020-02-06 22:56:40 +00:00
|
|
|
.windows, .other => false,
|
|
|
|
else => true,
|
|
|
|
};
|
|
|
|
|
2019-05-24 22:27:18 +00:00
|
|
|
/// TODO remove the allocator requirement from this API
|
|
|
|
pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) !void {
|
2020-07-21 07:26:01 +00:00
|
|
|
if (cwd().symLink(existing_path, new_path, .{})) {
|
2019-05-24 22:27:18 +00:00
|
|
|
return;
|
|
|
|
} else |err| switch (err) {
|
|
|
|
error.PathAlreadyExists => {},
|
|
|
|
else => return err, // TODO zig should know this set does not include PathAlreadyExists
|
|
|
|
}
|
|
|
|
|
2019-05-26 17:17:34 +00:00
|
|
|
const dirname = path.dirname(new_path) orelse ".";
|
2019-05-24 22:27:18 +00:00
|
|
|
|
2020-03-26 20:07:40 +00:00
|
|
|
var rand_buf: [AtomicFile.RANDOM_BYTES]u8 = undefined;
|
2019-05-24 22:27:18 +00:00
|
|
|
const tmp_path = try allocator.alloc(u8, dirname.len + 1 + base64.Base64Encoder.calcSize(rand_buf.len));
|
|
|
|
defer allocator.free(tmp_path);
|
|
|
|
mem.copy(u8, tmp_path[0..], dirname);
|
2019-05-26 17:17:34 +00:00
|
|
|
tmp_path[dirname.len] = path.sep;
|
2019-05-24 22:27:18 +00:00
|
|
|
while (true) {
|
2019-05-26 17:37:34 +00:00
|
|
|
try crypto.randomBytes(rand_buf[0..]);
|
2020-01-05 07:01:28 +00:00
|
|
|
base64_encoder.encode(tmp_path[dirname.len + 1 ..], &rand_buf);
|
2019-05-24 22:27:18 +00:00
|
|
|
|
2020-07-28 17:00:45 +00:00
|
|
|
if (cwd().symLink(existing_path, tmp_path, .{})) {
|
2020-09-17 03:59:45 +00:00
|
|
|
return cwd().rename(tmp_path, new_path);
|
2019-05-24 22:27:18 +00:00
|
|
|
} else |err| switch (err) {
|
|
|
|
error.PathAlreadyExists => continue,
|
|
|
|
else => return err, // TODO zig should know this set does not include PathAlreadyExists
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
zig build system: correctly handle multiple output artifacts
Previously the zig build system incorrectly assumed that the only build
artifact was a binary. Now, when you enable the cache, only the output
dir is printed to stdout, and the zig build system iterates over the
files in that directory, copying them to the output directory.
To support this change:
* Add `std.os.renameat`, `std.os.renameatZ`, and `std.os.renameatW`.
* Fix `std.os.linux.renameat` not compiling due to typos.
* Deprecate `std.fs.updateFile` and `std.fs.updateFileMode`.
* Add `std.fs.Dir.updateFile`, which supports using open directory
handles for both the source and destination paths, as well as an
options parameter which allows overriding the mode.
* Update `std.fs.AtomicFile` to support operating based on an open
directory handle. Instead of `std.fs.AtomicFile.init`, use
`std.fs.Dir.atomicFile`.
* `std.fs.AtomicFile` deinit() better handles the situation when the
rename fails but the temporary file still exists, by still
attempting to remove the temporary file.
* `std.fs.Dir.openFileWindows` is moved to `std.os.windows.OpenFileW`.
* `std.os.RenameError` gains the error codes `NoDevice`,
`SharingViolation`, and `PipeBusy` which have been observed from
Windows.
Closes #4733
2020-03-14 01:06:07 +00:00
|
|
|
pub const PrevStatus = enum {
|
2019-07-14 07:06:20 +00:00
|
|
|
stale,
|
|
|
|
fresh,
|
|
|
|
};
|
|
|
|
|
2020-03-19 18:38:21 +00:00
|
|
|
pub const CopyFileOptions = struct {
|
|
|
|
/// When this is `null` the mode is copied from the source file.
|
|
|
|
override_mode: ?File.Mode = null,
|
|
|
|
};
|
2019-07-14 07:06:20 +00:00
|
|
|
|
2020-03-19 18:38:21 +00:00
|
|
|
/// Same as `Dir.updateFile`, except asserts that both `source_path` and `dest_path`
|
|
|
|
/// are absolute. See `Dir.updateFile` for a function that operates on both
|
|
|
|
/// absolute and relative paths.
|
|
|
|
pub fn updateFileAbsolute(
|
|
|
|
source_path: []const u8,
|
|
|
|
dest_path: []const u8,
|
|
|
|
args: CopyFileOptions,
|
|
|
|
) !PrevStatus {
|
|
|
|
assert(path.isAbsolute(source_path));
|
|
|
|
assert(path.isAbsolute(dest_path));
|
2019-11-30 20:14:04 +00:00
|
|
|
const my_cwd = cwd();
|
2020-03-19 18:38:21 +00:00
|
|
|
return Dir.updateFile(my_cwd, source_path, my_cwd, dest_path, args);
|
2019-05-24 22:27:18 +00:00
|
|
|
}
|
|
|
|
|
2020-03-19 18:38:21 +00:00
|
|
|
/// Same as `Dir.copyFile`, except asserts that both `source_path` and `dest_path`
|
|
|
|
/// are absolute. See `Dir.copyFile` for a function that operates on both
|
|
|
|
/// absolute and relative paths.
|
|
|
|
pub fn copyFileAbsolute(source_path: []const u8, dest_path: []const u8, args: CopyFileOptions) !void {
|
|
|
|
assert(path.isAbsolute(source_path));
|
|
|
|
assert(path.isAbsolute(dest_path));
|
|
|
|
const my_cwd = cwd();
|
|
|
|
return Dir.copyFile(my_cwd, source_path, my_cwd, dest_path, args);
|
2019-05-24 22:27:18 +00:00
|
|
|
}
|
|
|
|
|
zig build system: correctly handle multiple output artifacts
Previously the zig build system incorrectly assumed that the only build
artifact was a binary. Now, when you enable the cache, only the output
dir is printed to stdout, and the zig build system iterates over the
files in that directory, copying them to the output directory.
To support this change:
* Add `std.os.renameat`, `std.os.renameatZ`, and `std.os.renameatW`.
* Fix `std.os.linux.renameat` not compiling due to typos.
* Deprecate `std.fs.updateFile` and `std.fs.updateFileMode`.
* Add `std.fs.Dir.updateFile`, which supports using open directory
handles for both the source and destination paths, as well as an
options parameter which allows overriding the mode.
* Update `std.fs.AtomicFile` to support operating based on an open
directory handle. Instead of `std.fs.AtomicFile.init`, use
`std.fs.Dir.atomicFile`.
* `std.fs.AtomicFile` deinit() better handles the situation when the
rename fails but the temporary file still exists, by still
attempting to remove the temporary file.
* `std.fs.Dir.openFileWindows` is moved to `std.os.windows.OpenFileW`.
* `std.os.RenameError` gains the error codes `NoDevice`,
`SharingViolation`, and `PipeBusy` which have been observed from
Windows.
Closes #4733
2020-03-14 01:06:07 +00:00
|
|
|
/// TODO update this API to avoid a getrandom syscall for every operation.
|
2019-05-24 22:27:18 +00:00
|
|
|
pub const AtomicFile = struct {
|
2019-05-25 02:52:07 +00:00
|
|
|
file: File,
|
2020-03-26 03:17:41 +00:00
|
|
|
// TODO either replace this with rand_buf or use []u16 on Windows
|
|
|
|
tmp_path_buf: [TMP_PATH_LEN:0]u8,
|
2020-03-30 18:23:22 +00:00
|
|
|
dest_basename: []const u8,
|
zig build system: correctly handle multiple output artifacts
Previously the zig build system incorrectly assumed that the only build
artifact was a binary. Now, when you enable the cache, only the output
dir is printed to stdout, and the zig build system iterates over the
files in that directory, copying them to the output directory.
To support this change:
* Add `std.os.renameat`, `std.os.renameatZ`, and `std.os.renameatW`.
* Fix `std.os.linux.renameat` not compiling due to typos.
* Deprecate `std.fs.updateFile` and `std.fs.updateFileMode`.
* Add `std.fs.Dir.updateFile`, which supports using open directory
handles for both the source and destination paths, as well as an
options parameter which allows overriding the mode.
* Update `std.fs.AtomicFile` to support operating based on an open
directory handle. Instead of `std.fs.AtomicFile.init`, use
`std.fs.Dir.atomicFile`.
* `std.fs.AtomicFile` deinit() better handles the situation when the
rename fails but the temporary file still exists, by still
attempting to remove the temporary file.
* `std.fs.Dir.openFileWindows` is moved to `std.os.windows.OpenFileW`.
* `std.os.RenameError` gains the error codes `NoDevice`,
`SharingViolation`, and `PipeBusy` which have been observed from
Windows.
Closes #4733
2020-03-14 01:06:07 +00:00
|
|
|
file_open: bool,
|
|
|
|
file_exists: bool,
|
2020-03-26 03:17:41 +00:00
|
|
|
close_dir_on_deinit: bool,
|
zig build system: correctly handle multiple output artifacts
Previously the zig build system incorrectly assumed that the only build
artifact was a binary. Now, when you enable the cache, only the output
dir is printed to stdout, and the zig build system iterates over the
files in that directory, copying them to the output directory.
To support this change:
* Add `std.os.renameat`, `std.os.renameatZ`, and `std.os.renameatW`.
* Fix `std.os.linux.renameat` not compiling due to typos.
* Deprecate `std.fs.updateFile` and `std.fs.updateFileMode`.
* Add `std.fs.Dir.updateFile`, which supports using open directory
handles for both the source and destination paths, as well as an
options parameter which allows overriding the mode.
* Update `std.fs.AtomicFile` to support operating based on an open
directory handle. Instead of `std.fs.AtomicFile.init`, use
`std.fs.Dir.atomicFile`.
* `std.fs.AtomicFile` deinit() better handles the situation when the
rename fails but the temporary file still exists, by still
attempting to remove the temporary file.
* `std.fs.Dir.openFileWindows` is moved to `std.os.windows.OpenFileW`.
* `std.os.RenameError` gains the error codes `NoDevice`,
`SharingViolation`, and `PipeBusy` which have been observed from
Windows.
Closes #4733
2020-03-14 01:06:07 +00:00
|
|
|
dir: Dir,
|
2019-05-24 22:27:18 +00:00
|
|
|
|
2019-05-25 02:52:07 +00:00
|
|
|
const InitError = File.OpenError;
|
2019-05-24 22:27:18 +00:00
|
|
|
|
2020-03-26 20:07:40 +00:00
|
|
|
const RANDOM_BYTES = 12;
|
|
|
|
const TMP_PATH_LEN = base64.Base64Encoder.calcSize(RANDOM_BYTES);
|
2020-03-26 03:17:41 +00:00
|
|
|
|
2020-03-30 18:23:22 +00:00
|
|
|
/// Note that the `Dir.atomicFile` API may be more handy than this lower-level function.
|
|
|
|
pub fn init(
|
|
|
|
dest_basename: []const u8,
|
|
|
|
mode: File.Mode,
|
|
|
|
dir: Dir,
|
|
|
|
close_dir_on_deinit: bool,
|
|
|
|
) InitError!AtomicFile {
|
2020-03-26 20:07:40 +00:00
|
|
|
var rand_buf: [RANDOM_BYTES]u8 = undefined;
|
2020-03-26 03:17:41 +00:00
|
|
|
var tmp_path_buf: [TMP_PATH_LEN:0]u8 = undefined;
|
2020-03-30 18:23:22 +00:00
|
|
|
// TODO: should be able to use TMP_PATH_LEN here.
|
2020-03-26 20:07:40 +00:00
|
|
|
tmp_path_buf[base64.Base64Encoder.calcSize(RANDOM_BYTES)] = 0;
|
2019-05-24 22:27:18 +00:00
|
|
|
|
|
|
|
while (true) {
|
2019-05-26 17:37:34 +00:00
|
|
|
try crypto.randomBytes(rand_buf[0..]);
|
2020-03-26 03:17:41 +00:00
|
|
|
base64_encoder.encode(&tmp_path_buf, &rand_buf);
|
2019-05-24 22:27:18 +00:00
|
|
|
|
Add/fix missing WASI functionality to pass libstd tests
This rather large commit adds/fixes missing WASI functionality
in `libstd` needed to pass the `libstd` tests. As such, now by
default tests targeting `wasm32-wasi` target are enabled in
`test/tests.zig` module. However, they can be disabled by passing
the `-Dskip-wasi=true` flag when invoking the `zig build test`
command. When the flag is set to `false`, i.e., when WASI tests are
included, `wasmtime` with `--dir=.` is used as the default testing
command.
Since the majority of `libstd` tests were relying on `fs.cwd()`
call to get current working directory handle wrapped in `Dir`
struct, in order to make the tests WASI-friendly, `fs.cwd()`
call was replaced with `testing.getTestDir()` function which
resolved to either `fs.cwd()` for non-WASI targets, or tries to
fetch the preopen list from the WASI runtime and extract a
preopen for '.' path.
The summary of changes introduced by this commit:
* implement `Dir.makeDir` and `Dir.openDir` targeting WASI
* implement `Dir.deleteFile` and `Dir.deleteDir` targeting WASI
* fix `os.close` and map errors in `unlinkat`
* move WASI-specific `mkdirat` and `unlinkat` from `std.fs.wasi`
to `std.os` module
* implement `lseek_{SET, CUR, END}` targeting WASI
* implement `futimens` targeting WASI
* implement `ftruncate` targeting WASI
* implement `readv`, `writev`, `pread{v}`, `pwrite{v}` targeting WASI
* make sure ANSI escape codes are _not_ used in stderr or stdout
in WASI, as WASI always sanitizes stderr, and sanitizes stdout if
fd is a TTY
* fix specifying WASI rights when opening/creating files/dirs
* tweak `AtomicFile` to be WASI-compatible
* implement `os.renameatWasi` for WASI-compliant `os.renameat` function
* implement sleep() targeting WASI
* fix `process.getEnvMap` targeting WASI
2020-05-05 15:23:49 +00:00
|
|
|
const file = dir.createFile(
|
2020-03-26 03:17:41 +00:00
|
|
|
&tmp_path_buf,
|
2019-11-30 20:14:04 +00:00
|
|
|
.{ .mode = mode, .exclusive = true },
|
|
|
|
) catch |err| switch (err) {
|
2019-05-24 22:27:18 +00:00
|
|
|
error.PathAlreadyExists => continue,
|
2019-11-30 20:14:04 +00:00
|
|
|
else => |e| return e,
|
2019-05-24 22:27:18 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
return AtomicFile{
|
|
|
|
.file = file,
|
|
|
|
.tmp_path_buf = tmp_path_buf,
|
2020-03-30 18:23:22 +00:00
|
|
|
.dest_basename = dest_basename,
|
zig build system: correctly handle multiple output artifacts
Previously the zig build system incorrectly assumed that the only build
artifact was a binary. Now, when you enable the cache, only the output
dir is printed to stdout, and the zig build system iterates over the
files in that directory, copying them to the output directory.
To support this change:
* Add `std.os.renameat`, `std.os.renameatZ`, and `std.os.renameatW`.
* Fix `std.os.linux.renameat` not compiling due to typos.
* Deprecate `std.fs.updateFile` and `std.fs.updateFileMode`.
* Add `std.fs.Dir.updateFile`, which supports using open directory
handles for both the source and destination paths, as well as an
options parameter which allows overriding the mode.
* Update `std.fs.AtomicFile` to support operating based on an open
directory handle. Instead of `std.fs.AtomicFile.init`, use
`std.fs.Dir.atomicFile`.
* `std.fs.AtomicFile` deinit() better handles the situation when the
rename fails but the temporary file still exists, by still
attempting to remove the temporary file.
* `std.fs.Dir.openFileWindows` is moved to `std.os.windows.OpenFileW`.
* `std.os.RenameError` gains the error codes `NoDevice`,
`SharingViolation`, and `PipeBusy` which have been observed from
Windows.
Closes #4733
2020-03-14 01:06:07 +00:00
|
|
|
.file_open = true,
|
|
|
|
.file_exists = true,
|
2020-03-26 03:17:41 +00:00
|
|
|
.close_dir_on_deinit = close_dir_on_deinit,
|
zig build system: correctly handle multiple output artifacts
Previously the zig build system incorrectly assumed that the only build
artifact was a binary. Now, when you enable the cache, only the output
dir is printed to stdout, and the zig build system iterates over the
files in that directory, copying them to the output directory.
To support this change:
* Add `std.os.renameat`, `std.os.renameatZ`, and `std.os.renameatW`.
* Fix `std.os.linux.renameat` not compiling due to typos.
* Deprecate `std.fs.updateFile` and `std.fs.updateFileMode`.
* Add `std.fs.Dir.updateFile`, which supports using open directory
handles for both the source and destination paths, as well as an
options parameter which allows overriding the mode.
* Update `std.fs.AtomicFile` to support operating based on an open
directory handle. Instead of `std.fs.AtomicFile.init`, use
`std.fs.Dir.atomicFile`.
* `std.fs.AtomicFile` deinit() better handles the situation when the
rename fails but the temporary file still exists, by still
attempting to remove the temporary file.
* `std.fs.Dir.openFileWindows` is moved to `std.os.windows.OpenFileW`.
* `std.os.RenameError` gains the error codes `NoDevice`,
`SharingViolation`, and `PipeBusy` which have been observed from
Windows.
Closes #4733
2020-03-14 01:06:07 +00:00
|
|
|
.dir = dir,
|
2019-05-24 22:27:18 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// always call deinit, even after successful finish()
|
|
|
|
pub fn deinit(self: *AtomicFile) void {
|
zig build system: correctly handle multiple output artifacts
Previously the zig build system incorrectly assumed that the only build
artifact was a binary. Now, when you enable the cache, only the output
dir is printed to stdout, and the zig build system iterates over the
files in that directory, copying them to the output directory.
To support this change:
* Add `std.os.renameat`, `std.os.renameatZ`, and `std.os.renameatW`.
* Fix `std.os.linux.renameat` not compiling due to typos.
* Deprecate `std.fs.updateFile` and `std.fs.updateFileMode`.
* Add `std.fs.Dir.updateFile`, which supports using open directory
handles for both the source and destination paths, as well as an
options parameter which allows overriding the mode.
* Update `std.fs.AtomicFile` to support operating based on an open
directory handle. Instead of `std.fs.AtomicFile.init`, use
`std.fs.Dir.atomicFile`.
* `std.fs.AtomicFile` deinit() better handles the situation when the
rename fails but the temporary file still exists, by still
attempting to remove the temporary file.
* `std.fs.Dir.openFileWindows` is moved to `std.os.windows.OpenFileW`.
* `std.os.RenameError` gains the error codes `NoDevice`,
`SharingViolation`, and `PipeBusy` which have been observed from
Windows.
Closes #4733
2020-03-14 01:06:07 +00:00
|
|
|
if (self.file_open) {
|
2019-05-24 22:27:18 +00:00
|
|
|
self.file.close();
|
zig build system: correctly handle multiple output artifacts
Previously the zig build system incorrectly assumed that the only build
artifact was a binary. Now, when you enable the cache, only the output
dir is printed to stdout, and the zig build system iterates over the
files in that directory, copying them to the output directory.
To support this change:
* Add `std.os.renameat`, `std.os.renameatZ`, and `std.os.renameatW`.
* Fix `std.os.linux.renameat` not compiling due to typos.
* Deprecate `std.fs.updateFile` and `std.fs.updateFileMode`.
* Add `std.fs.Dir.updateFile`, which supports using open directory
handles for both the source and destination paths, as well as an
options parameter which allows overriding the mode.
* Update `std.fs.AtomicFile` to support operating based on an open
directory handle. Instead of `std.fs.AtomicFile.init`, use
`std.fs.Dir.atomicFile`.
* `std.fs.AtomicFile` deinit() better handles the situation when the
rename fails but the temporary file still exists, by still
attempting to remove the temporary file.
* `std.fs.Dir.openFileWindows` is moved to `std.os.windows.OpenFileW`.
* `std.os.RenameError` gains the error codes `NoDevice`,
`SharingViolation`, and `PipeBusy` which have been observed from
Windows.
Closes #4733
2020-03-14 01:06:07 +00:00
|
|
|
self.file_open = false;
|
|
|
|
}
|
|
|
|
if (self.file_exists) {
|
Add/fix missing WASI functionality to pass libstd tests
This rather large commit adds/fixes missing WASI functionality
in `libstd` needed to pass the `libstd` tests. As such, now by
default tests targeting `wasm32-wasi` target are enabled in
`test/tests.zig` module. However, they can be disabled by passing
the `-Dskip-wasi=true` flag when invoking the `zig build test`
command. When the flag is set to `false`, i.e., when WASI tests are
included, `wasmtime` with `--dir=.` is used as the default testing
command.
Since the majority of `libstd` tests were relying on `fs.cwd()`
call to get current working directory handle wrapped in `Dir`
struct, in order to make the tests WASI-friendly, `fs.cwd()`
call was replaced with `testing.getTestDir()` function which
resolved to either `fs.cwd()` for non-WASI targets, or tries to
fetch the preopen list from the WASI runtime and extract a
preopen for '.' path.
The summary of changes introduced by this commit:
* implement `Dir.makeDir` and `Dir.openDir` targeting WASI
* implement `Dir.deleteFile` and `Dir.deleteDir` targeting WASI
* fix `os.close` and map errors in `unlinkat`
* move WASI-specific `mkdirat` and `unlinkat` from `std.fs.wasi`
to `std.os` module
* implement `lseek_{SET, CUR, END}` targeting WASI
* implement `futimens` targeting WASI
* implement `ftruncate` targeting WASI
* implement `readv`, `writev`, `pread{v}`, `pwrite{v}` targeting WASI
* make sure ANSI escape codes are _not_ used in stderr or stdout
in WASI, as WASI always sanitizes stderr, and sanitizes stdout if
fd is a TTY
* fix specifying WASI rights when opening/creating files/dirs
* tweak `AtomicFile` to be WASI-compatible
* implement `os.renameatWasi` for WASI-compliant `os.renameat` function
* implement sleep() targeting WASI
* fix `process.getEnvMap` targeting WASI
2020-05-05 15:23:49 +00:00
|
|
|
self.dir.deleteFile(&self.tmp_path_buf) catch {};
|
zig build system: correctly handle multiple output artifacts
Previously the zig build system incorrectly assumed that the only build
artifact was a binary. Now, when you enable the cache, only the output
dir is printed to stdout, and the zig build system iterates over the
files in that directory, copying them to the output directory.
To support this change:
* Add `std.os.renameat`, `std.os.renameatZ`, and `std.os.renameatW`.
* Fix `std.os.linux.renameat` not compiling due to typos.
* Deprecate `std.fs.updateFile` and `std.fs.updateFileMode`.
* Add `std.fs.Dir.updateFile`, which supports using open directory
handles for both the source and destination paths, as well as an
options parameter which allows overriding the mode.
* Update `std.fs.AtomicFile` to support operating based on an open
directory handle. Instead of `std.fs.AtomicFile.init`, use
`std.fs.Dir.atomicFile`.
* `std.fs.AtomicFile` deinit() better handles the situation when the
rename fails but the temporary file still exists, by still
attempting to remove the temporary file.
* `std.fs.Dir.openFileWindows` is moved to `std.os.windows.OpenFileW`.
* `std.os.RenameError` gains the error codes `NoDevice`,
`SharingViolation`, and `PipeBusy` which have been observed from
Windows.
Closes #4733
2020-03-14 01:06:07 +00:00
|
|
|
self.file_exists = false;
|
2019-05-24 22:27:18 +00:00
|
|
|
}
|
2020-03-26 03:17:41 +00:00
|
|
|
if (self.close_dir_on_deinit) {
|
|
|
|
self.dir.close();
|
|
|
|
}
|
zig build system: correctly handle multiple output artifacts
Previously the zig build system incorrectly assumed that the only build
artifact was a binary. Now, when you enable the cache, only the output
dir is printed to stdout, and the zig build system iterates over the
files in that directory, copying them to the output directory.
To support this change:
* Add `std.os.renameat`, `std.os.renameatZ`, and `std.os.renameatW`.
* Fix `std.os.linux.renameat` not compiling due to typos.
* Deprecate `std.fs.updateFile` and `std.fs.updateFileMode`.
* Add `std.fs.Dir.updateFile`, which supports using open directory
handles for both the source and destination paths, as well as an
options parameter which allows overriding the mode.
* Update `std.fs.AtomicFile` to support operating based on an open
directory handle. Instead of `std.fs.AtomicFile.init`, use
`std.fs.Dir.atomicFile`.
* `std.fs.AtomicFile` deinit() better handles the situation when the
rename fails but the temporary file still exists, by still
attempting to remove the temporary file.
* `std.fs.Dir.openFileWindows` is moved to `std.os.windows.OpenFileW`.
* `std.os.RenameError` gains the error codes `NoDevice`,
`SharingViolation`, and `PipeBusy` which have been observed from
Windows.
Closes #4733
2020-03-14 01:06:07 +00:00
|
|
|
self.* = undefined;
|
2019-05-24 22:27:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn finish(self: *AtomicFile) !void {
|
zig build system: correctly handle multiple output artifacts
Previously the zig build system incorrectly assumed that the only build
artifact was a binary. Now, when you enable the cache, only the output
dir is printed to stdout, and the zig build system iterates over the
files in that directory, copying them to the output directory.
To support this change:
* Add `std.os.renameat`, `std.os.renameatZ`, and `std.os.renameatW`.
* Fix `std.os.linux.renameat` not compiling due to typos.
* Deprecate `std.fs.updateFile` and `std.fs.updateFileMode`.
* Add `std.fs.Dir.updateFile`, which supports using open directory
handles for both the source and destination paths, as well as an
options parameter which allows overriding the mode.
* Update `std.fs.AtomicFile` to support operating based on an open
directory handle. Instead of `std.fs.AtomicFile.init`, use
`std.fs.Dir.atomicFile`.
* `std.fs.AtomicFile` deinit() better handles the situation when the
rename fails but the temporary file still exists, by still
attempting to remove the temporary file.
* `std.fs.Dir.openFileWindows` is moved to `std.os.windows.OpenFileW`.
* `std.os.RenameError` gains the error codes `NoDevice`,
`SharingViolation`, and `PipeBusy` which have been observed from
Windows.
Closes #4733
2020-03-14 01:06:07 +00:00
|
|
|
assert(self.file_exists);
|
|
|
|
if (self.file_open) {
|
|
|
|
self.file.close();
|
|
|
|
self.file_open = false;
|
|
|
|
}
|
Add/fix missing WASI functionality to pass libstd tests
This rather large commit adds/fixes missing WASI functionality
in `libstd` needed to pass the `libstd` tests. As such, now by
default tests targeting `wasm32-wasi` target are enabled in
`test/tests.zig` module. However, they can be disabled by passing
the `-Dskip-wasi=true` flag when invoking the `zig build test`
command. When the flag is set to `false`, i.e., when WASI tests are
included, `wasmtime` with `--dir=.` is used as the default testing
command.
Since the majority of `libstd` tests were relying on `fs.cwd()`
call to get current working directory handle wrapped in `Dir`
struct, in order to make the tests WASI-friendly, `fs.cwd()`
call was replaced with `testing.getTestDir()` function which
resolved to either `fs.cwd()` for non-WASI targets, or tries to
fetch the preopen list from the WASI runtime and extract a
preopen for '.' path.
The summary of changes introduced by this commit:
* implement `Dir.makeDir` and `Dir.openDir` targeting WASI
* implement `Dir.deleteFile` and `Dir.deleteDir` targeting WASI
* fix `os.close` and map errors in `unlinkat`
* move WASI-specific `mkdirat` and `unlinkat` from `std.fs.wasi`
to `std.os` module
* implement `lseek_{SET, CUR, END}` targeting WASI
* implement `futimens` targeting WASI
* implement `ftruncate` targeting WASI
* implement `readv`, `writev`, `pread{v}`, `pwrite{v}` targeting WASI
* make sure ANSI escape codes are _not_ used in stderr or stdout
in WASI, as WASI always sanitizes stderr, and sanitizes stdout if
fd is a TTY
* fix specifying WASI rights when opening/creating files/dirs
* tweak `AtomicFile` to be WASI-compatible
* implement `os.renameatWasi` for WASI-compliant `os.renameat` function
* implement sleep() targeting WASI
* fix `process.getEnvMap` targeting WASI
2020-05-05 15:23:49 +00:00
|
|
|
try os.renameat(self.dir.fd, self.tmp_path_buf[0..], self.dir.fd, self.dest_basename);
|
|
|
|
self.file_exists = false;
|
2019-05-24 22:27:18 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const default_new_dir_mode = 0o755;
|
|
|
|
|
2020-03-03 20:01:08 +00:00
|
|
|
/// Create a new directory, based on an absolute path.
|
|
|
|
/// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates
|
|
|
|
/// on both absolute and relative paths.
|
|
|
|
pub fn makeDirAbsolute(absolute_path: []const u8) !void {
|
2020-03-07 20:14:47 +00:00
|
|
|
assert(path.isAbsolute(absolute_path));
|
2020-03-03 20:01:08 +00:00
|
|
|
return os.mkdir(absolute_path, default_new_dir_mode);
|
2019-05-24 22:27:18 +00:00
|
|
|
}
|
|
|
|
|
2020-03-03 20:01:08 +00:00
|
|
|
/// Same as `makeDirAbsolute` except the parameter is a null-terminated UTF8-encoded string.
|
|
|
|
pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {
|
2020-03-30 18:23:22 +00:00
|
|
|
assert(path.isAbsoluteZ(absolute_path_z));
|
2020-03-03 20:01:08 +00:00
|
|
|
return os.mkdirZ(absolute_path_z, default_new_dir_mode);
|
2019-05-24 22:27:18 +00:00
|
|
|
}
|
|
|
|
|
2020-03-03 20:01:08 +00:00
|
|
|
/// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16 encoded string.
|
|
|
|
pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void {
|
|
|
|
assert(path.isAbsoluteWindowsW(absolute_path_w));
|
2020-07-30 15:00:50 +00:00
|
|
|
return os.mkdirW(absolute_path_w, default_new_dir_mode);
|
2019-05-24 22:27:18 +00:00
|
|
|
}
|
|
|
|
|
2020-03-30 18:23:22 +00:00
|
|
|
pub const deleteDir = @compileError("deprecated; use dir.deleteDir or deleteDirAbsolute");
|
|
|
|
pub const deleteDirC = @compileError("deprecated; use dir.deleteDirZ or deleteDirAbsoluteZ");
|
|
|
|
pub const deleteDirW = @compileError("deprecated; use dir.deleteDirW or deleteDirAbsoluteW");
|
|
|
|
|
|
|
|
/// Same as `Dir.deleteDir` except the path is absolute.
|
|
|
|
pub fn deleteDirAbsolute(dir_path: []const u8) !void {
|
|
|
|
assert(path.isAbsolute(dir_path));
|
2019-05-25 02:52:07 +00:00
|
|
|
return os.rmdir(dir_path);
|
2019-05-24 22:27:18 +00:00
|
|
|
}
|
|
|
|
|
2020-03-30 18:23:22 +00:00
|
|
|
/// Same as `deleteDirAbsolute` except the path parameter is null-terminated.
|
|
|
|
pub fn deleteDirAbsoluteZ(dir_path: [*:0]const u8) !void {
|
|
|
|
assert(path.isAbsoluteZ(dir_path));
|
|
|
|
return os.rmdirZ(dir_path);
|
2019-05-24 22:27:18 +00:00
|
|
|
}
|
|
|
|
|
2020-03-30 18:23:22 +00:00
|
|
|
/// Same as `deleteDirAbsolute` except the path parameter is WTF-16 and target OS is assumed Windows.
|
|
|
|
pub fn deleteDirAbsoluteW(dir_path: [*:0]const u16) !void {
|
|
|
|
assert(path.isAbsoluteWindowsW(dir_path));
|
2019-05-25 02:52:07 +00:00
|
|
|
return os.rmdirW(dir_path);
|
2019-05-24 22:27:18 +00:00
|
|
|
}
|
|
|
|
|
2020-09-17 03:59:45 +00:00
|
|
|
pub const renameC = @compileError("deprecated: use renameZ, dir.renameZ, or renameAbsoluteZ");
|
|
|
|
|
|
|
|
/// Same as `Dir.rename` except the paths are absolute.
|
|
|
|
pub fn renameAbsolute(old_path: []const u8, new_path: []const u8) !void {
|
|
|
|
assert(path.isAbsolute(old_path));
|
|
|
|
assert(path.isAbsolute(new_path));
|
|
|
|
return os.rename(old_path, new_path);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Same as `renameAbsolute` except the path parameters are null-terminated.
|
|
|
|
pub fn renameAbsoluteZ(old_path: [*:0]const u8, new_path: [*:0]const u8) !void {
|
|
|
|
assert(path.isAbsoluteZ(old_path));
|
|
|
|
assert(path.isAbsoluteZ(new_path));
|
|
|
|
return os.renameZ(old_path, new_path);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Same as `renameAbsolute` except the path parameters are WTF-16 and target OS is assumed Windows.
|
|
|
|
pub fn renameAbsoluteW(old_path: [*:0]const u16, new_path: [*:0]const u16) !void {
|
|
|
|
assert(path.isAbsoluteWindowsW(old_path));
|
|
|
|
assert(path.isAbsoluteWindowsW(new_path));
|
|
|
|
return os.renameW(old_path, new_path);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Same as `Dir.rename`, except `new_sub_path` is relative to `new_dir`
|
|
|
|
pub fn rename(old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) !void {
|
|
|
|
return os.renameat(old_dir.fd, old_sub_path, new_dir.fd, new_sub_path);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Same as `rename` except the parameters are null-terminated.
|
|
|
|
pub fn renameZ(old_dir: Dir, old_sub_path_z: [*:0]const u8, new_dir: Dir, new_sub_path_z: [*:0]const u8) !void {
|
|
|
|
return os.renameatZ(old_dir.fd, old_sub_path_z, new_dir.fd, new_sub_path_z);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Same as `rename` except the parameters are UTF16LE, NT prefixed.
|
|
|
|
/// This function is Windows-only.
|
|
|
|
pub fn renameW(old_dir: Dir, old_sub_path_w: []const u16, new_dir: Dir, new_sub_path_w: []const u16) !void {
|
|
|
|
return os.renameatW(old_dir.fd, old_sub_path_w, new_dir.fd, new_sub_path_w);
|
|
|
|
}
|
|
|
|
|
2019-05-24 22:27:18 +00:00
|
|
|
pub const Dir = struct {
|
2019-10-21 01:48:23 +00:00
|
|
|
fd: os.fd_t,
|
2019-05-24 22:27:18 +00:00
|
|
|
|
|
|
|
pub const Entry = struct {
|
|
|
|
name: []const u8,
|
|
|
|
kind: Kind,
|
|
|
|
|
2020-06-20 22:27:37 +00:00
|
|
|
pub const Kind = File.Kind;
|
2019-05-24 22:27:18 +00:00
|
|
|
};
|
|
|
|
|
2020-06-30 16:18:25 +00:00
|
|
|
const IteratorError = error{AccessDenied} || os.UnexpectedError;
|
2019-10-22 00:16:43 +00:00
|
|
|
|
2020-02-25 06:52:27 +00:00
|
|
|
pub const Iterator = switch (builtin.os.tag) {
|
2020-10-17 15:38:23 +00:00
|
|
|
.macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd => struct {
|
2019-10-21 01:48:23 +00:00
|
|
|
dir: Dir,
|
|
|
|
seek: i64,
|
2019-10-21 18:18:01 +00:00
|
|
|
buf: [8192]u8, // TODO align(@alignOf(os.dirent)),
|
2019-10-21 01:48:23 +00:00
|
|
|
index: usize,
|
|
|
|
end_index: usize,
|
|
|
|
|
|
|
|
const Self = @This();
|
|
|
|
|
2019-10-22 00:16:43 +00:00
|
|
|
pub const Error = IteratorError;
|
|
|
|
|
2019-10-21 01:48:23 +00:00
|
|
|
/// Memory such as file names referenced in this returned entry becomes invalid
|
|
|
|
/// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
|
2019-10-22 00:16:43 +00:00
|
|
|
pub fn next(self: *Self) Error!?Entry {
|
2020-02-25 06:52:27 +00:00
|
|
|
switch (builtin.os.tag) {
|
2020-10-12 08:59:43 +00:00
|
|
|
.macos, .ios => return self.nextDarwin(),
|
2020-10-11 08:23:36 +00:00
|
|
|
.freebsd, .netbsd, .dragonfly, .openbsd => return self.nextBsd(),
|
2019-10-21 01:48:23 +00:00
|
|
|
else => @compileError("unimplemented"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn nextDarwin(self: *Self) !?Entry {
|
|
|
|
start_over: while (true) {
|
|
|
|
if (self.index >= self.end_index) {
|
2019-10-21 18:18:01 +00:00
|
|
|
const rc = os.system.__getdirentries64(
|
|
|
|
self.dir.fd,
|
|
|
|
&self.buf,
|
|
|
|
self.buf.len,
|
|
|
|
&self.seek,
|
|
|
|
);
|
|
|
|
if (rc == 0) return null;
|
|
|
|
if (rc < 0) {
|
|
|
|
switch (os.errno(rc)) {
|
2020-03-18 18:45:01 +00:00
|
|
|
os.EBADF => unreachable, // Dir is invalid or was opened without iteration ability
|
2019-10-21 18:18:01 +00:00
|
|
|
os.EFAULT => unreachable,
|
|
|
|
os.ENOTDIR => unreachable,
|
|
|
|
os.EINVAL => unreachable,
|
|
|
|
else => |err| return os.unexpectedErrno(err),
|
2019-10-21 01:48:23 +00:00
|
|
|
}
|
|
|
|
}
|
2019-10-21 18:18:01 +00:00
|
|
|
self.index = 0;
|
|
|
|
self.end_index = @intCast(usize, rc);
|
2019-10-21 01:48:23 +00:00
|
|
|
}
|
|
|
|
const darwin_entry = @ptrCast(*align(1) os.dirent, &self.buf[self.index]);
|
2019-10-31 16:24:26 +00:00
|
|
|
const next_index = self.index + darwin_entry.reclen();
|
2019-10-21 01:48:23 +00:00
|
|
|
self.index = next_index;
|
|
|
|
|
|
|
|
const name = @ptrCast([*]u8, &darwin_entry.d_name)[0..darwin_entry.d_namlen];
|
|
|
|
|
|
|
|
if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
|
|
|
|
continue :start_over;
|
|
|
|
}
|
|
|
|
|
|
|
|
const entry_kind = switch (darwin_entry.d_type) {
|
|
|
|
os.DT_BLK => Entry.Kind.BlockDevice,
|
|
|
|
os.DT_CHR => Entry.Kind.CharacterDevice,
|
|
|
|
os.DT_DIR => Entry.Kind.Directory,
|
|
|
|
os.DT_FIFO => Entry.Kind.NamedPipe,
|
|
|
|
os.DT_LNK => Entry.Kind.SymLink,
|
|
|
|
os.DT_REG => Entry.Kind.File,
|
|
|
|
os.DT_SOCK => Entry.Kind.UnixDomainSocket,
|
|
|
|
os.DT_WHT => Entry.Kind.Whiteout,
|
|
|
|
else => Entry.Kind.Unknown,
|
|
|
|
};
|
|
|
|
return Entry{
|
|
|
|
.name = name,
|
|
|
|
.kind = entry_kind,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn nextBsd(self: *Self) !?Entry {
|
|
|
|
start_over: while (true) {
|
|
|
|
if (self.index >= self.end_index) {
|
2020-03-23 17:54:14 +00:00
|
|
|
const rc = if (builtin.os.tag == .netbsd)
|
|
|
|
os.system.__getdents30(self.dir.fd, &self.buf, self.buf.len)
|
|
|
|
else
|
|
|
|
os.system.getdents(self.dir.fd, &self.buf, self.buf.len);
|
2019-10-21 18:18:01 +00:00
|
|
|
switch (os.errno(rc)) {
|
|
|
|
0 => {},
|
2020-03-18 18:45:01 +00:00
|
|
|
os.EBADF => unreachable, // Dir is invalid or was opened without iteration ability
|
2019-10-21 18:18:01 +00:00
|
|
|
os.EFAULT => unreachable,
|
|
|
|
os.ENOTDIR => unreachable,
|
|
|
|
os.EINVAL => unreachable,
|
|
|
|
else => |err| return os.unexpectedErrno(err),
|
2019-10-21 01:48:23 +00:00
|
|
|
}
|
2019-10-21 18:18:01 +00:00
|
|
|
if (rc == 0) return null;
|
|
|
|
self.index = 0;
|
|
|
|
self.end_index = @intCast(usize, rc);
|
2019-10-21 01:48:23 +00:00
|
|
|
}
|
|
|
|
const freebsd_entry = @ptrCast(*align(1) os.dirent, &self.buf[self.index]);
|
2019-10-31 16:24:26 +00:00
|
|
|
const next_index = self.index + freebsd_entry.reclen();
|
2019-10-21 01:48:23 +00:00
|
|
|
self.index = next_index;
|
|
|
|
|
|
|
|
const name = @ptrCast([*]u8, &freebsd_entry.d_name)[0..freebsd_entry.d_namlen];
|
|
|
|
|
|
|
|
if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
|
|
|
|
continue :start_over;
|
|
|
|
}
|
|
|
|
|
|
|
|
const entry_kind = switch (freebsd_entry.d_type) {
|
|
|
|
os.DT_BLK => Entry.Kind.BlockDevice,
|
|
|
|
os.DT_CHR => Entry.Kind.CharacterDevice,
|
|
|
|
os.DT_DIR => Entry.Kind.Directory,
|
|
|
|
os.DT_FIFO => Entry.Kind.NamedPipe,
|
|
|
|
os.DT_LNK => Entry.Kind.SymLink,
|
|
|
|
os.DT_REG => Entry.Kind.File,
|
|
|
|
os.DT_SOCK => Entry.Kind.UnixDomainSocket,
|
|
|
|
os.DT_WHT => Entry.Kind.Whiteout,
|
|
|
|
else => Entry.Kind.Unknown,
|
|
|
|
};
|
|
|
|
return Entry{
|
|
|
|
.name = name,
|
|
|
|
.kind = entry_kind,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
.linux => struct {
|
|
|
|
dir: Dir,
|
2019-10-21 18:18:01 +00:00
|
|
|
buf: [8192]u8, // TODO align(@alignOf(os.dirent64)),
|
2019-10-21 01:48:23 +00:00
|
|
|
index: usize,
|
|
|
|
end_index: usize,
|
|
|
|
|
|
|
|
const Self = @This();
|
|
|
|
|
2019-10-22 00:16:43 +00:00
|
|
|
pub const Error = IteratorError;
|
|
|
|
|
2019-10-21 01:48:23 +00:00
|
|
|
/// Memory such as file names referenced in this returned entry becomes invalid
|
|
|
|
/// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
|
2019-10-22 00:16:43 +00:00
|
|
|
pub fn next(self: *Self) Error!?Entry {
|
2019-10-21 01:48:23 +00:00
|
|
|
start_over: while (true) {
|
|
|
|
if (self.index >= self.end_index) {
|
2019-10-21 18:18:01 +00:00
|
|
|
const rc = os.linux.getdents64(self.dir.fd, &self.buf, self.buf.len);
|
|
|
|
switch (os.linux.getErrno(rc)) {
|
|
|
|
0 => {},
|
2020-03-18 18:45:01 +00:00
|
|
|
os.EBADF => unreachable, // Dir is invalid or was opened without iteration ability
|
2019-10-21 18:18:01 +00:00
|
|
|
os.EFAULT => unreachable,
|
|
|
|
os.ENOTDIR => unreachable,
|
|
|
|
os.EINVAL => unreachable,
|
|
|
|
else => |err| return os.unexpectedErrno(err),
|
2019-10-21 01:48:23 +00:00
|
|
|
}
|
2019-10-21 18:18:01 +00:00
|
|
|
if (rc == 0) return null;
|
|
|
|
self.index = 0;
|
|
|
|
self.end_index = rc;
|
2019-10-21 01:48:23 +00:00
|
|
|
}
|
|
|
|
const linux_entry = @ptrCast(*align(1) os.dirent64, &self.buf[self.index]);
|
2019-10-31 16:24:26 +00:00
|
|
|
const next_index = self.index + linux_entry.reclen();
|
2019-10-21 01:48:23 +00:00
|
|
|
self.index = next_index;
|
|
|
|
|
2020-03-30 18:23:22 +00:00
|
|
|
const name = mem.spanZ(@ptrCast([*:0]u8, &linux_entry.d_name));
|
2019-10-21 01:48:23 +00:00
|
|
|
|
|
|
|
// skip . and .. entries
|
|
|
|
if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
|
|
|
|
continue :start_over;
|
|
|
|
}
|
|
|
|
|
|
|
|
const entry_kind = switch (linux_entry.d_type) {
|
|
|
|
os.DT_BLK => Entry.Kind.BlockDevice,
|
|
|
|
os.DT_CHR => Entry.Kind.CharacterDevice,
|
|
|
|
os.DT_DIR => Entry.Kind.Directory,
|
|
|
|
os.DT_FIFO => Entry.Kind.NamedPipe,
|
|
|
|
os.DT_LNK => Entry.Kind.SymLink,
|
|
|
|
os.DT_REG => Entry.Kind.File,
|
|
|
|
os.DT_SOCK => Entry.Kind.UnixDomainSocket,
|
|
|
|
else => Entry.Kind.Unknown,
|
|
|
|
};
|
|
|
|
return Entry{
|
|
|
|
.name = name,
|
|
|
|
.kind = entry_kind,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
.windows => struct {
|
|
|
|
dir: Dir,
|
2019-10-21 18:18:01 +00:00
|
|
|
buf: [8192]u8 align(@alignOf(os.windows.FILE_BOTH_DIR_INFORMATION)),
|
|
|
|
index: usize,
|
|
|
|
end_index: usize,
|
2019-10-21 01:48:23 +00:00
|
|
|
first: bool,
|
|
|
|
name_data: [256]u8,
|
2019-10-21 18:18:01 +00:00
|
|
|
|
|
|
|
const Self = @This();
|
|
|
|
|
2019-10-22 00:16:43 +00:00
|
|
|
pub const Error = IteratorError;
|
|
|
|
|
2020-07-07 22:03:45 +00:00
|
|
|
/// Memory such as file names referenced in this returned entry becomes invalid
|
|
|
|
/// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
|
2019-10-22 00:16:43 +00:00
|
|
|
pub fn next(self: *Self) Error!?Entry {
|
2019-10-21 18:18:01 +00:00
|
|
|
start_over: while (true) {
|
|
|
|
const w = os.windows;
|
|
|
|
if (self.index >= self.end_index) {
|
|
|
|
var io: w.IO_STATUS_BLOCK = undefined;
|
|
|
|
const rc = w.ntdll.NtQueryDirectoryFile(
|
|
|
|
self.dir.fd,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
&io,
|
|
|
|
&self.buf,
|
|
|
|
self.buf.len,
|
|
|
|
.FileBothDirectoryInformation,
|
|
|
|
w.FALSE,
|
|
|
|
null,
|
2019-11-08 20:56:21 +00:00
|
|
|
if (self.first) @as(w.BOOLEAN, w.TRUE) else @as(w.BOOLEAN, w.FALSE),
|
2019-10-21 18:18:01 +00:00
|
|
|
);
|
|
|
|
self.first = false;
|
|
|
|
if (io.Information == 0) return null;
|
|
|
|
self.index = 0;
|
|
|
|
self.end_index = io.Information;
|
|
|
|
switch (rc) {
|
2020-01-31 08:47:00 +00:00
|
|
|
.SUCCESS => {},
|
2020-03-18 20:09:06 +00:00
|
|
|
.ACCESS_DENIED => return error.AccessDenied, // Double-check that the Dir was opened with iteration ability
|
|
|
|
|
2019-10-21 18:18:01 +00:00
|
|
|
else => return w.unexpectedStatus(rc),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const aligned_ptr = @alignCast(@alignOf(w.FILE_BOTH_DIR_INFORMATION), &self.buf[self.index]);
|
|
|
|
const dir_info = @ptrCast(*w.FILE_BOTH_DIR_INFORMATION, aligned_ptr);
|
|
|
|
if (dir_info.NextEntryOffset != 0) {
|
|
|
|
self.index += dir_info.NextEntryOffset;
|
|
|
|
} else {
|
|
|
|
self.index = self.buf.len;
|
|
|
|
}
|
|
|
|
|
|
|
|
const name_utf16le = @ptrCast([*]u16, &dir_info.FileName)[0 .. dir_info.FileNameLength / 2];
|
|
|
|
|
2019-11-30 04:04:19 +00:00
|
|
|
if (mem.eql(u16, name_utf16le, &[_]u16{'.'}) or mem.eql(u16, name_utf16le, &[_]u16{ '.', '.' }))
|
2019-10-21 18:18:01 +00:00
|
|
|
continue;
|
|
|
|
// Trust that Windows gives us valid UTF-16LE
|
|
|
|
const name_utf8_len = std.unicode.utf16leToUtf8(self.name_data[0..], name_utf16le) catch unreachable;
|
|
|
|
const name_utf8 = self.name_data[0..name_utf8_len];
|
|
|
|
const kind = blk: {
|
|
|
|
const attrs = dir_info.FileAttributes;
|
|
|
|
if (attrs & w.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk Entry.Kind.Directory;
|
|
|
|
if (attrs & w.FILE_ATTRIBUTE_REPARSE_POINT != 0) break :blk Entry.Kind.SymLink;
|
|
|
|
break :blk Entry.Kind.File;
|
|
|
|
};
|
|
|
|
return Entry{
|
|
|
|
.name = name_utf8,
|
|
|
|
.kind = kind,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
2019-10-21 01:48:23 +00:00
|
|
|
},
|
Add/fix missing WASI functionality to pass libstd tests
This rather large commit adds/fixes missing WASI functionality
in `libstd` needed to pass the `libstd` tests. As such, now by
default tests targeting `wasm32-wasi` target are enabled in
`test/tests.zig` module. However, they can be disabled by passing
the `-Dskip-wasi=true` flag when invoking the `zig build test`
command. When the flag is set to `false`, i.e., when WASI tests are
included, `wasmtime` with `--dir=.` is used as the default testing
command.
Since the majority of `libstd` tests were relying on `fs.cwd()`
call to get current working directory handle wrapped in `Dir`
struct, in order to make the tests WASI-friendly, `fs.cwd()`
call was replaced with `testing.getTestDir()` function which
resolved to either `fs.cwd()` for non-WASI targets, or tries to
fetch the preopen list from the WASI runtime and extract a
preopen for '.' path.
The summary of changes introduced by this commit:
* implement `Dir.makeDir` and `Dir.openDir` targeting WASI
* implement `Dir.deleteFile` and `Dir.deleteDir` targeting WASI
* fix `os.close` and map errors in `unlinkat`
* move WASI-specific `mkdirat` and `unlinkat` from `std.fs.wasi`
to `std.os` module
* implement `lseek_{SET, CUR, END}` targeting WASI
* implement `futimens` targeting WASI
* implement `ftruncate` targeting WASI
* implement `readv`, `writev`, `pread{v}`, `pwrite{v}` targeting WASI
* make sure ANSI escape codes are _not_ used in stderr or stdout
in WASI, as WASI always sanitizes stderr, and sanitizes stdout if
fd is a TTY
* fix specifying WASI rights when opening/creating files/dirs
* tweak `AtomicFile` to be WASI-compatible
* implement `os.renameatWasi` for WASI-compliant `os.renameat` function
* implement sleep() targeting WASI
* fix `process.getEnvMap` targeting WASI
2020-05-05 15:23:49 +00:00
|
|
|
.wasi => struct {
|
|
|
|
dir: Dir,
|
|
|
|
buf: [8192]u8, // TODO align(@alignOf(os.wasi.dirent_t)),
|
|
|
|
cookie: u64,
|
|
|
|
index: usize,
|
|
|
|
end_index: usize,
|
|
|
|
|
|
|
|
const Self = @This();
|
|
|
|
|
|
|
|
pub const Error = IteratorError;
|
|
|
|
|
|
|
|
/// Memory such as file names referenced in this returned entry becomes invalid
|
|
|
|
/// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
|
|
|
|
pub fn next(self: *Self) Error!?Entry {
|
|
|
|
const w = os.wasi;
|
|
|
|
start_over: while (true) {
|
|
|
|
if (self.index >= self.end_index) {
|
|
|
|
var bufused: usize = undefined;
|
|
|
|
switch (w.fd_readdir(self.dir.fd, &self.buf, self.buf.len, self.cookie, &bufused)) {
|
|
|
|
w.ESUCCESS => {},
|
|
|
|
w.EBADF => unreachable, // Dir is invalid or was opened without iteration ability
|
|
|
|
w.EFAULT => unreachable,
|
|
|
|
w.ENOTDIR => unreachable,
|
|
|
|
w.EINVAL => unreachable,
|
2020-06-30 16:18:25 +00:00
|
|
|
w.ENOTCAPABLE => return error.AccessDenied,
|
Add/fix missing WASI functionality to pass libstd tests
This rather large commit adds/fixes missing WASI functionality
in `libstd` needed to pass the `libstd` tests. As such, now by
default tests targeting `wasm32-wasi` target are enabled in
`test/tests.zig` module. However, they can be disabled by passing
the `-Dskip-wasi=true` flag when invoking the `zig build test`
command. When the flag is set to `false`, i.e., when WASI tests are
included, `wasmtime` with `--dir=.` is used as the default testing
command.
Since the majority of `libstd` tests were relying on `fs.cwd()`
call to get current working directory handle wrapped in `Dir`
struct, in order to make the tests WASI-friendly, `fs.cwd()`
call was replaced with `testing.getTestDir()` function which
resolved to either `fs.cwd()` for non-WASI targets, or tries to
fetch the preopen list from the WASI runtime and extract a
preopen for '.' path.
The summary of changes introduced by this commit:
* implement `Dir.makeDir` and `Dir.openDir` targeting WASI
* implement `Dir.deleteFile` and `Dir.deleteDir` targeting WASI
* fix `os.close` and map errors in `unlinkat`
* move WASI-specific `mkdirat` and `unlinkat` from `std.fs.wasi`
to `std.os` module
* implement `lseek_{SET, CUR, END}` targeting WASI
* implement `futimens` targeting WASI
* implement `ftruncate` targeting WASI
* implement `readv`, `writev`, `pread{v}`, `pwrite{v}` targeting WASI
* make sure ANSI escape codes are _not_ used in stderr or stdout
in WASI, as WASI always sanitizes stderr, and sanitizes stdout if
fd is a TTY
* fix specifying WASI rights when opening/creating files/dirs
* tweak `AtomicFile` to be WASI-compatible
* implement `os.renameatWasi` for WASI-compliant `os.renameat` function
* implement sleep() targeting WASI
* fix `process.getEnvMap` targeting WASI
2020-05-05 15:23:49 +00:00
|
|
|
else => |err| return os.unexpectedErrno(err),
|
|
|
|
}
|
|
|
|
if (bufused == 0) return null;
|
|
|
|
self.index = 0;
|
|
|
|
self.end_index = bufused;
|
|
|
|
}
|
2020-06-29 15:10:01 +00:00
|
|
|
const entry = @ptrCast(*align(1) w.dirent_t, &self.buf[self.index]);
|
|
|
|
const entry_size = @sizeOf(w.dirent_t);
|
Add/fix missing WASI functionality to pass libstd tests
This rather large commit adds/fixes missing WASI functionality
in `libstd` needed to pass the `libstd` tests. As such, now by
default tests targeting `wasm32-wasi` target are enabled in
`test/tests.zig` module. However, they can be disabled by passing
the `-Dskip-wasi=true` flag when invoking the `zig build test`
command. When the flag is set to `false`, i.e., when WASI tests are
included, `wasmtime` with `--dir=.` is used as the default testing
command.
Since the majority of `libstd` tests were relying on `fs.cwd()`
call to get current working directory handle wrapped in `Dir`
struct, in order to make the tests WASI-friendly, `fs.cwd()`
call was replaced with `testing.getTestDir()` function which
resolved to either `fs.cwd()` for non-WASI targets, or tries to
fetch the preopen list from the WASI runtime and extract a
preopen for '.' path.
The summary of changes introduced by this commit:
* implement `Dir.makeDir` and `Dir.openDir` targeting WASI
* implement `Dir.deleteFile` and `Dir.deleteDir` targeting WASI
* fix `os.close` and map errors in `unlinkat`
* move WASI-specific `mkdirat` and `unlinkat` from `std.fs.wasi`
to `std.os` module
* implement `lseek_{SET, CUR, END}` targeting WASI
* implement `futimens` targeting WASI
* implement `ftruncate` targeting WASI
* implement `readv`, `writev`, `pread{v}`, `pwrite{v}` targeting WASI
* make sure ANSI escape codes are _not_ used in stderr or stdout
in WASI, as WASI always sanitizes stderr, and sanitizes stdout if
fd is a TTY
* fix specifying WASI rights when opening/creating files/dirs
* tweak `AtomicFile` to be WASI-compatible
* implement `os.renameatWasi` for WASI-compliant `os.renameat` function
* implement sleep() targeting WASI
* fix `process.getEnvMap` targeting WASI
2020-05-05 15:23:49 +00:00
|
|
|
const name_index = self.index + entry_size;
|
|
|
|
const name = mem.span(self.buf[name_index .. name_index + entry.d_namlen]);
|
|
|
|
|
|
|
|
const next_index = name_index + entry.d_namlen;
|
|
|
|
self.index = next_index;
|
|
|
|
self.cookie = entry.d_next;
|
|
|
|
|
|
|
|
// skip . and .. entries
|
|
|
|
if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
|
|
|
|
continue :start_over;
|
|
|
|
}
|
|
|
|
|
|
|
|
const entry_kind = switch (entry.d_type) {
|
2020-06-29 15:10:01 +00:00
|
|
|
w.FILETYPE_BLOCK_DEVICE => Entry.Kind.BlockDevice,
|
|
|
|
w.FILETYPE_CHARACTER_DEVICE => Entry.Kind.CharacterDevice,
|
|
|
|
w.FILETYPE_DIRECTORY => Entry.Kind.Directory,
|
|
|
|
w.FILETYPE_SYMBOLIC_LINK => Entry.Kind.SymLink,
|
|
|
|
w.FILETYPE_REGULAR_FILE => Entry.Kind.File,
|
|
|
|
w.FILETYPE_SOCKET_STREAM, wasi.FILETYPE_SOCKET_DGRAM => Entry.Kind.UnixDomainSocket,
|
Add/fix missing WASI functionality to pass libstd tests
This rather large commit adds/fixes missing WASI functionality
in `libstd` needed to pass the `libstd` tests. As such, now by
default tests targeting `wasm32-wasi` target are enabled in
`test/tests.zig` module. However, they can be disabled by passing
the `-Dskip-wasi=true` flag when invoking the `zig build test`
command. When the flag is set to `false`, i.e., when WASI tests are
included, `wasmtime` with `--dir=.` is used as the default testing
command.
Since the majority of `libstd` tests were relying on `fs.cwd()`
call to get current working directory handle wrapped in `Dir`
struct, in order to make the tests WASI-friendly, `fs.cwd()`
call was replaced with `testing.getTestDir()` function which
resolved to either `fs.cwd()` for non-WASI targets, or tries to
fetch the preopen list from the WASI runtime and extract a
preopen for '.' path.
The summary of changes introduced by this commit:
* implement `Dir.makeDir` and `Dir.openDir` targeting WASI
* implement `Dir.deleteFile` and `Dir.deleteDir` targeting WASI
* fix `os.close` and map errors in `unlinkat`
* move WASI-specific `mkdirat` and `unlinkat` from `std.fs.wasi`
to `std.os` module
* implement `lseek_{SET, CUR, END}` targeting WASI
* implement `futimens` targeting WASI
* implement `ftruncate` targeting WASI
* implement `readv`, `writev`, `pread{v}`, `pwrite{v}` targeting WASI
* make sure ANSI escape codes are _not_ used in stderr or stdout
in WASI, as WASI always sanitizes stderr, and sanitizes stdout if
fd is a TTY
* fix specifying WASI rights when opening/creating files/dirs
* tweak `AtomicFile` to be WASI-compatible
* implement `os.renameatWasi` for WASI-compliant `os.renameat` function
* implement sleep() targeting WASI
* fix `process.getEnvMap` targeting WASI
2020-05-05 15:23:49 +00:00
|
|
|
else => Entry.Kind.Unknown,
|
|
|
|
};
|
|
|
|
return Entry{
|
|
|
|
.name = name,
|
|
|
|
.kind = entry_kind,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2019-10-21 01:48:23 +00:00
|
|
|
else => @compileError("unimplemented"),
|
|
|
|
};
|
|
|
|
|
2019-10-21 18:18:01 +00:00
|
|
|
pub fn iterate(self: Dir) Iterator {
|
2020-02-25 06:52:27 +00:00
|
|
|
switch (builtin.os.tag) {
|
2020-10-17 15:38:23 +00:00
|
|
|
.macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd => return Iterator{
|
2019-10-21 18:18:01 +00:00
|
|
|
.dir = self,
|
|
|
|
.seek = 0,
|
|
|
|
.index = 0,
|
|
|
|
.end_index = 0,
|
|
|
|
.buf = undefined,
|
|
|
|
},
|
|
|
|
.linux => return Iterator{
|
|
|
|
.dir = self,
|
|
|
|
.index = 0,
|
|
|
|
.end_index = 0,
|
|
|
|
.buf = undefined,
|
|
|
|
},
|
|
|
|
.windows => return Iterator{
|
|
|
|
.dir = self,
|
|
|
|
.index = 0,
|
|
|
|
.end_index = 0,
|
|
|
|
.first = true,
|
|
|
|
.buf = undefined,
|
|
|
|
.name_data = undefined,
|
|
|
|
},
|
Add/fix missing WASI functionality to pass libstd tests
This rather large commit adds/fixes missing WASI functionality
in `libstd` needed to pass the `libstd` tests. As such, now by
default tests targeting `wasm32-wasi` target are enabled in
`test/tests.zig` module. However, they can be disabled by passing
the `-Dskip-wasi=true` flag when invoking the `zig build test`
command. When the flag is set to `false`, i.e., when WASI tests are
included, `wasmtime` with `--dir=.` is used as the default testing
command.
Since the majority of `libstd` tests were relying on `fs.cwd()`
call to get current working directory handle wrapped in `Dir`
struct, in order to make the tests WASI-friendly, `fs.cwd()`
call was replaced with `testing.getTestDir()` function which
resolved to either `fs.cwd()` for non-WASI targets, or tries to
fetch the preopen list from the WASI runtime and extract a
preopen for '.' path.
The summary of changes introduced by this commit:
* implement `Dir.makeDir` and `Dir.openDir` targeting WASI
* implement `Dir.deleteFile` and `Dir.deleteDir` targeting WASI
* fix `os.close` and map errors in `unlinkat`
* move WASI-specific `mkdirat` and `unlinkat` from `std.fs.wasi`
to `std.os` module
* implement `lseek_{SET, CUR, END}` targeting WASI
* implement `futimens` targeting WASI
* implement `ftruncate` targeting WASI
* implement `readv`, `writev`, `pread{v}`, `pwrite{v}` targeting WASI
* make sure ANSI escape codes are _not_ used in stderr or stdout
in WASI, as WASI always sanitizes stderr, and sanitizes stdout if
fd is a TTY
* fix specifying WASI rights when opening/creating files/dirs
* tweak `AtomicFile` to be WASI-compatible
* implement `os.renameatWasi` for WASI-compliant `os.renameat` function
* implement sleep() targeting WASI
* fix `process.getEnvMap` targeting WASI
2020-05-05 15:23:49 +00:00
|
|
|
.wasi => return Iterator{
|
|
|
|
.dir = self,
|
|
|
|
.cookie = os.wasi.DIRCOOKIE_START,
|
|
|
|
.index = 0,
|
|
|
|
.end_index = 0,
|
|
|
|
.buf = undefined,
|
|
|
|
},
|
2019-10-21 18:18:01 +00:00
|
|
|
else => @compileError("unimplemented"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-24 22:27:18 +00:00
|
|
|
pub const OpenError = error{
|
|
|
|
FileNotFound,
|
|
|
|
NotDir,
|
|
|
|
AccessDenied,
|
|
|
|
SymLinkLoop,
|
|
|
|
ProcessFdQuotaExceeded,
|
|
|
|
NameTooLong,
|
|
|
|
SystemFdQuotaExceeded,
|
|
|
|
NoDevice,
|
|
|
|
SystemResources,
|
|
|
|
InvalidUtf8,
|
|
|
|
BadPathName,
|
|
|
|
DeviceBusy,
|
2019-10-21 01:48:23 +00:00
|
|
|
} || os.UnexpectedError;
|
2019-05-24 22:27:18 +00:00
|
|
|
|
2019-10-21 01:48:23 +00:00
|
|
|
pub fn close(self: *Dir) void {
|
2020-02-06 22:56:40 +00:00
|
|
|
if (need_async_thread) {
|
|
|
|
std.event.Loop.instance.?.close(self.fd);
|
|
|
|
} else {
|
|
|
|
os.close(self.fd);
|
|
|
|
}
|
2019-10-21 01:48:23 +00:00
|
|
|
self.* = undefined;
|
2019-05-24 22:27:18 +00:00
|
|
|
}
|
|
|
|
|
2019-11-30 18:32:11 +00:00
|
|
|
/// Opens a file for reading or writing, without attempting to create a new file.
|
2020-02-08 06:38:01 +00:00
|
|
|
/// To create a new file, see `createFile`.
|
2019-11-30 18:32:11 +00:00
|
|
|
/// Call `File.close` to release the resource.
|
2019-11-30 20:14:04 +00:00
|
|
|
/// Asserts that the path parameter has no null bytes.
|
2019-11-30 18:32:11 +00:00
|
|
|
pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
|
2020-02-25 06:52:27 +00:00
|
|
|
if (builtin.os.tag == .windows) {
|
2019-11-21 22:07:29 +00:00
|
|
|
const path_w = try os.windows.sliceToPrefixedFileW(sub_path);
|
2020-05-02 05:25:22 +00:00
|
|
|
return self.openFileW(path_w.span(), flags);
|
2019-11-21 22:07:29 +00:00
|
|
|
}
|
2020-05-01 10:33:11 +00:00
|
|
|
if (builtin.os.tag == .wasi) {
|
|
|
|
return self.openFileWasi(sub_path, flags);
|
|
|
|
}
|
2019-10-21 01:48:23 +00:00
|
|
|
const path_c = try os.toPosixPath(sub_path);
|
2020-03-04 06:05:42 +00:00
|
|
|
return self.openFileZ(&path_c, flags);
|
2019-08-17 01:29:29 +00:00
|
|
|
}
|
|
|
|
|
2020-05-05 15:05:30 +00:00
|
|
|
/// Save as `openFile` but WASI only.
|
|
|
|
pub fn openFileWasi(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
|
|
|
|
const w = os.wasi;
|
Add/fix missing WASI functionality to pass libstd tests
This rather large commit adds/fixes missing WASI functionality
in `libstd` needed to pass the `libstd` tests. As such, now by
default tests targeting `wasm32-wasi` target are enabled in
`test/tests.zig` module. However, they can be disabled by passing
the `-Dskip-wasi=true` flag when invoking the `zig build test`
command. When the flag is set to `false`, i.e., when WASI tests are
included, `wasmtime` with `--dir=.` is used as the default testing
command.
Since the majority of `libstd` tests were relying on `fs.cwd()`
call to get current working directory handle wrapped in `Dir`
struct, in order to make the tests WASI-friendly, `fs.cwd()`
call was replaced with `testing.getTestDir()` function which
resolved to either `fs.cwd()` for non-WASI targets, or tries to
fetch the preopen list from the WASI runtime and extract a
preopen for '.' path.
The summary of changes introduced by this commit:
* implement `Dir.makeDir` and `Dir.openDir` targeting WASI
* implement `Dir.deleteFile` and `Dir.deleteDir` targeting WASI
* fix `os.close` and map errors in `unlinkat`
* move WASI-specific `mkdirat` and `unlinkat` from `std.fs.wasi`
to `std.os` module
* implement `lseek_{SET, CUR, END}` targeting WASI
* implement `futimens` targeting WASI
* implement `ftruncate` targeting WASI
* implement `readv`, `writev`, `pread{v}`, `pwrite{v}` targeting WASI
* make sure ANSI escape codes are _not_ used in stderr or stdout
in WASI, as WASI always sanitizes stderr, and sanitizes stdout if
fd is a TTY
* fix specifying WASI rights when opening/creating files/dirs
* tweak `AtomicFile` to be WASI-compatible
* implement `os.renameatWasi` for WASI-compliant `os.renameat` function
* implement sleep() targeting WASI
* fix `process.getEnvMap` targeting WASI
2020-05-05 15:23:49 +00:00
|
|
|
var fdflags: w.fdflags_t = 0x0;
|
|
|
|
var base: w.rights_t = 0x0;
|
2020-05-01 10:33:11 +00:00
|
|
|
if (flags.read) {
|
Add/fix missing WASI functionality to pass libstd tests
This rather large commit adds/fixes missing WASI functionality
in `libstd` needed to pass the `libstd` tests. As such, now by
default tests targeting `wasm32-wasi` target are enabled in
`test/tests.zig` module. However, they can be disabled by passing
the `-Dskip-wasi=true` flag when invoking the `zig build test`
command. When the flag is set to `false`, i.e., when WASI tests are
included, `wasmtime` with `--dir=.` is used as the default testing
command.
Since the majority of `libstd` tests were relying on `fs.cwd()`
call to get current working directory handle wrapped in `Dir`
struct, in order to make the tests WASI-friendly, `fs.cwd()`
call was replaced with `testing.getTestDir()` function which
resolved to either `fs.cwd()` for non-WASI targets, or tries to
fetch the preopen list from the WASI runtime and extract a
preopen for '.' path.
The summary of changes introduced by this commit:
* implement `Dir.makeDir` and `Dir.openDir` targeting WASI
* implement `Dir.deleteFile` and `Dir.deleteDir` targeting WASI
* fix `os.close` and map errors in `unlinkat`
* move WASI-specific `mkdirat` and `unlinkat` from `std.fs.wasi`
to `std.os` module
* implement `lseek_{SET, CUR, END}` targeting WASI
* implement `futimens` targeting WASI
* implement `ftruncate` targeting WASI
* implement `readv`, `writev`, `pread{v}`, `pwrite{v}` targeting WASI
* make sure ANSI escape codes are _not_ used in stderr or stdout
in WASI, as WASI always sanitizes stderr, and sanitizes stdout if
fd is a TTY
* fix specifying WASI rights when opening/creating files/dirs
* tweak `AtomicFile` to be WASI-compatible
* implement `os.renameatWasi` for WASI-compliant `os.renameat` function
* implement sleep() targeting WASI
* fix `process.getEnvMap` targeting WASI
2020-05-05 15:23:49 +00:00
|
|
|
base |= w.RIGHT_FD_READ | w.RIGHT_FD_TELL | w.RIGHT_FD_SEEK | w.RIGHT_FD_FILESTAT_GET;
|
2020-05-01 10:33:11 +00:00
|
|
|
}
|
|
|
|
if (flags.write) {
|
2020-05-05 15:05:30 +00:00
|
|
|
fdflags |= w.FDFLAG_APPEND;
|
Add/fix missing WASI functionality to pass libstd tests
This rather large commit adds/fixes missing WASI functionality
in `libstd` needed to pass the `libstd` tests. As such, now by
default tests targeting `wasm32-wasi` target are enabled in
`test/tests.zig` module. However, they can be disabled by passing
the `-Dskip-wasi=true` flag when invoking the `zig build test`
command. When the flag is set to `false`, i.e., when WASI tests are
included, `wasmtime` with `--dir=.` is used as the default testing
command.
Since the majority of `libstd` tests were relying on `fs.cwd()`
call to get current working directory handle wrapped in `Dir`
struct, in order to make the tests WASI-friendly, `fs.cwd()`
call was replaced with `testing.getTestDir()` function which
resolved to either `fs.cwd()` for non-WASI targets, or tries to
fetch the preopen list from the WASI runtime and extract a
preopen for '.' path.
The summary of changes introduced by this commit:
* implement `Dir.makeDir` and `Dir.openDir` targeting WASI
* implement `Dir.deleteFile` and `Dir.deleteDir` targeting WASI
* fix `os.close` and map errors in `unlinkat`
* move WASI-specific `mkdirat` and `unlinkat` from `std.fs.wasi`
to `std.os` module
* implement `lseek_{SET, CUR, END}` targeting WASI
* implement `futimens` targeting WASI
* implement `ftruncate` targeting WASI
* implement `readv`, `writev`, `pread{v}`, `pwrite{v}` targeting WASI
* make sure ANSI escape codes are _not_ used in stderr or stdout
in WASI, as WASI always sanitizes stderr, and sanitizes stdout if
fd is a TTY
* fix specifying WASI rights when opening/creating files/dirs
* tweak `AtomicFile` to be WASI-compatible
* implement `os.renameatWasi` for WASI-compliant `os.renameat` function
* implement sleep() targeting WASI
* fix `process.getEnvMap` targeting WASI
2020-05-05 15:23:49 +00:00
|
|
|
base |= w.RIGHT_FD_WRITE |
|
|
|
|
w.RIGHT_FD_TELL |
|
|
|
|
w.RIGHT_FD_SEEK |
|
|
|
|
w.RIGHT_FD_DATASYNC |
|
|
|
|
w.RIGHT_FD_FDSTAT_SET_FLAGS |
|
|
|
|
w.RIGHT_FD_SYNC |
|
|
|
|
w.RIGHT_FD_ALLOCATE |
|
|
|
|
w.RIGHT_FD_ADVISE |
|
|
|
|
w.RIGHT_FD_FILESTAT_SET_TIMES |
|
|
|
|
w.RIGHT_FD_FILESTAT_SET_SIZE;
|
2020-05-01 10:33:11 +00:00
|
|
|
}
|
2020-07-19 21:18:20 +00:00
|
|
|
const fd = try os.openatWasi(self.fd, sub_path, 0x0, 0x0, fdflags, base, 0x0);
|
2020-05-04 11:35:02 +00:00
|
|
|
return File{ .handle = fd };
|
2020-05-01 10:33:11 +00:00
|
|
|
}
|
|
|
|
|
2020-03-30 18:23:22 +00:00
|
|
|
pub const openFileC = @compileError("deprecated: renamed to openFileZ");
|
2020-03-04 06:05:42 +00:00
|
|
|
|
2019-11-30 18:32:11 +00:00
|
|
|
/// Same as `openFile` but the path parameter is null-terminated.
|
2020-03-04 06:05:42 +00:00
|
|
|
pub fn openFileZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) File.OpenError!File {
|
2020-02-25 06:52:27 +00:00
|
|
|
if (builtin.os.tag == .windows) {
|
2019-11-21 22:07:29 +00:00
|
|
|
const path_w = try os.windows.cStrToPrefixedFileW(sub_path);
|
2020-05-02 05:25:22 +00:00
|
|
|
return self.openFileW(path_w.span(), flags);
|
2019-11-21 22:07:29 +00:00
|
|
|
}
|
2020-03-24 02:59:09 +00:00
|
|
|
|
2020-08-23 16:27:26 +00:00
|
|
|
var os_flags: u32 = os.O_CLOEXEC;
|
2020-03-24 02:59:09 +00:00
|
|
|
// Use the O_ locking flags if the os supports them
|
2020-04-08 22:29:44 +00:00
|
|
|
// (Or if it's darwin, as darwin's `open` doesn't support the O_SYNC flag)
|
2020-05-02 03:17:15 +00:00
|
|
|
const has_flock_open_flags = @hasDecl(os, "O_EXLOCK") and !is_darwin;
|
2020-08-23 16:27:26 +00:00
|
|
|
if (has_flock_open_flags) {
|
|
|
|
const nonblocking_lock_flag = if (flags.lock_nonblocking)
|
|
|
|
os.O_NONBLOCK | os.O_SYNC
|
|
|
|
else
|
|
|
|
@as(u32, 0);
|
|
|
|
os_flags |= switch (flags.lock) {
|
|
|
|
.None => @as(u32, 0),
|
|
|
|
.Shared => os.O_SHLOCK | nonblocking_lock_flag,
|
|
|
|
.Exclusive => os.O_EXLOCK | nonblocking_lock_flag,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
if (@hasDecl(os, "O_LARGEFILE")) {
|
|
|
|
os_flags |= os.O_LARGEFILE;
|
|
|
|
}
|
2020-08-23 16:28:31 +00:00
|
|
|
if (!flags.allow_ctty) {
|
|
|
|
os_flags |= os.O_NOCTTY;
|
|
|
|
}
|
2020-08-23 16:27:26 +00:00
|
|
|
os_flags |= if (flags.write and flags.read)
|
2019-11-30 18:32:11 +00:00
|
|
|
@as(u32, os.O_RDWR)
|
|
|
|
else if (flags.write)
|
|
|
|
@as(u32, os.O_WRONLY)
|
|
|
|
else
|
|
|
|
@as(u32, os.O_RDONLY);
|
2020-05-02 03:17:15 +00:00
|
|
|
const fd = if (flags.intended_io_mode != .blocking)
|
2020-02-06 22:56:40 +00:00
|
|
|
try std.event.Loop.instance.?.openatZ(self.fd, sub_path, os_flags, 0)
|
|
|
|
else
|
2020-03-30 18:23:22 +00:00
|
|
|
try os.openatZ(self.fd, sub_path, os_flags, 0);
|
2020-11-02 11:51:30 +00:00
|
|
|
errdefer os.close(fd);
|
2020-03-09 02:57:47 +00:00
|
|
|
|
2020-04-07 22:23:12 +00:00
|
|
|
if (!has_flock_open_flags and flags.lock != .None) {
|
2020-03-09 02:57:47 +00:00
|
|
|
// TODO: integrate async I/O
|
2020-04-08 00:00:12 +00:00
|
|
|
const lock_nonblocking = if (flags.lock_nonblocking) os.LOCK_NB else @as(i32, 0);
|
2020-04-07 22:23:12 +00:00
|
|
|
try os.flock(fd, switch (flags.lock) {
|
|
|
|
.None => unreachable,
|
2020-04-08 00:00:12 +00:00
|
|
|
.Shared => os.LOCK_SH | lock_nonblocking,
|
|
|
|
.Exclusive => os.LOCK_EX | lock_nonblocking,
|
2020-04-07 22:23:12 +00:00
|
|
|
});
|
2020-03-09 02:57:47 +00:00
|
|
|
}
|
|
|
|
|
2020-02-26 18:17:38 +00:00
|
|
|
return File{
|
|
|
|
.handle = fd,
|
2020-05-02 03:17:15 +00:00
|
|
|
.capable_io_mode = .blocking,
|
|
|
|
.intended_io_mode = flags.intended_io_mode,
|
2020-02-26 18:17:38 +00:00
|
|
|
};
|
2019-11-30 18:32:11 +00:00
|
|
|
}
|
|
|
|
|
2019-11-30 23:36:17 +00:00
|
|
|
/// Same as `openFile` but Windows-only and the path parameter is
|
|
|
|
/// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded.
|
2020-05-01 23:02:16 +00:00
|
|
|
pub fn openFileW(self: Dir, sub_path_w: []const u16, flags: File.OpenFlags) File.OpenError!File {
|
2019-11-30 18:32:11 +00:00
|
|
|
const w = os.windows;
|
zig build system: correctly handle multiple output artifacts
Previously the zig build system incorrectly assumed that the only build
artifact was a binary. Now, when you enable the cache, only the output
dir is printed to stdout, and the zig build system iterates over the
files in that directory, copying them to the output directory.
To support this change:
* Add `std.os.renameat`, `std.os.renameatZ`, and `std.os.renameatW`.
* Fix `std.os.linux.renameat` not compiling due to typos.
* Deprecate `std.fs.updateFile` and `std.fs.updateFileMode`.
* Add `std.fs.Dir.updateFile`, which supports using open directory
handles for both the source and destination paths, as well as an
options parameter which allows overriding the mode.
* Update `std.fs.AtomicFile` to support operating based on an open
directory handle. Instead of `std.fs.AtomicFile.init`, use
`std.fs.Dir.atomicFile`.
* `std.fs.AtomicFile` deinit() better handles the situation when the
rename fails but the temporary file still exists, by still
attempting to remove the temporary file.
* `std.fs.Dir.openFileWindows` is moved to `std.os.windows.OpenFileW`.
* `std.os.RenameError` gains the error codes `NoDevice`,
`SharingViolation`, and `PipeBusy` which have been observed from
Windows.
Closes #4733
2020-03-14 01:06:07 +00:00
|
|
|
return @as(File, .{
|
2020-05-01 23:02:16 +00:00
|
|
|
.handle = try os.windows.OpenFile(sub_path_w, .{
|
|
|
|
.dir = self.fd,
|
|
|
|
.access_mask = w.SYNCHRONIZE |
|
|
|
|
(if (flags.read) @as(u32, w.GENERIC_READ) else 0) |
|
|
|
|
(if (flags.write) @as(u32, w.GENERIC_WRITE) else 0),
|
|
|
|
.share_access = switch (flags.lock) {
|
2020-05-02 03:17:15 +00:00
|
|
|
.None => w.FILE_SHARE_WRITE | w.FILE_SHARE_READ | w.FILE_SHARE_DELETE,
|
2020-05-01 23:02:16 +00:00
|
|
|
.Shared => w.FILE_SHARE_READ | w.FILE_SHARE_DELETE,
|
|
|
|
.Exclusive => w.FILE_SHARE_DELETE,
|
|
|
|
},
|
|
|
|
.share_access_nonblocking = flags.lock_nonblocking,
|
|
|
|
.creation = w.FILE_OPEN,
|
2020-05-02 03:17:15 +00:00
|
|
|
.io_mode = flags.intended_io_mode,
|
2020-05-01 23:02:16 +00:00
|
|
|
}),
|
2020-05-02 03:17:15 +00:00
|
|
|
.capable_io_mode = std.io.default_mode,
|
|
|
|
.intended_io_mode = flags.intended_io_mode,
|
zig build system: correctly handle multiple output artifacts
Previously the zig build system incorrectly assumed that the only build
artifact was a binary. Now, when you enable the cache, only the output
dir is printed to stdout, and the zig build system iterates over the
files in that directory, copying them to the output directory.
To support this change:
* Add `std.os.renameat`, `std.os.renameatZ`, and `std.os.renameatW`.
* Fix `std.os.linux.renameat` not compiling due to typos.
* Deprecate `std.fs.updateFile` and `std.fs.updateFileMode`.
* Add `std.fs.Dir.updateFile`, which supports using open directory
handles for both the source and destination paths, as well as an
options parameter which allows overriding the mode.
* Update `std.fs.AtomicFile` to support operating based on an open
directory handle. Instead of `std.fs.AtomicFile.init`, use
`std.fs.Dir.atomicFile`.
* `std.fs.AtomicFile` deinit() better handles the situation when the
rename fails but the temporary file still exists, by still
attempting to remove the temporary file.
* `std.fs.Dir.openFileWindows` is moved to `std.os.windows.OpenFileW`.
* `std.os.RenameError` gains the error codes `NoDevice`,
`SharingViolation`, and `PipeBusy` which have been observed from
Windows.
Closes #4733
2020-03-14 01:06:07 +00:00
|
|
|
});
|
2019-08-17 01:29:29 +00:00
|
|
|
}
|
|
|
|
|
2019-11-30 18:32:11 +00:00
|
|
|
/// Creates, opens, or overwrites a file with write access.
|
|
|
|
/// Call `File.close` on the result when done.
|
2019-11-30 20:14:04 +00:00
|
|
|
/// Asserts that the path parameter has no null bytes.
|
2019-11-30 18:32:11 +00:00
|
|
|
pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
|
2020-02-25 06:52:27 +00:00
|
|
|
if (builtin.os.tag == .windows) {
|
2019-11-30 18:32:11 +00:00
|
|
|
const path_w = try os.windows.sliceToPrefixedFileW(sub_path);
|
2020-05-02 05:25:22 +00:00
|
|
|
return self.createFileW(path_w.span(), flags);
|
2019-11-30 18:32:11 +00:00
|
|
|
}
|
2020-05-01 10:33:11 +00:00
|
|
|
if (builtin.os.tag == .wasi) {
|
|
|
|
return self.createFileWasi(sub_path, flags);
|
|
|
|
}
|
2019-11-30 18:32:11 +00:00
|
|
|
const path_c = try os.toPosixPath(sub_path);
|
2020-03-30 18:23:22 +00:00
|
|
|
return self.createFileZ(&path_c, flags);
|
2019-11-30 18:32:11 +00:00
|
|
|
}
|
|
|
|
|
2020-03-30 18:23:22 +00:00
|
|
|
pub const createFileC = @compileError("deprecated: renamed to createFileZ");
|
|
|
|
|
2020-05-05 15:05:30 +00:00
|
|
|
/// Same as `createFile` but WASI only.
|
|
|
|
pub fn createFileWasi(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
|
|
|
|
const w = os.wasi;
|
|
|
|
var oflags = w.O_CREAT;
|
Add/fix missing WASI functionality to pass libstd tests
This rather large commit adds/fixes missing WASI functionality
in `libstd` needed to pass the `libstd` tests. As such, now by
default tests targeting `wasm32-wasi` target are enabled in
`test/tests.zig` module. However, they can be disabled by passing
the `-Dskip-wasi=true` flag when invoking the `zig build test`
command. When the flag is set to `false`, i.e., when WASI tests are
included, `wasmtime` with `--dir=.` is used as the default testing
command.
Since the majority of `libstd` tests were relying on `fs.cwd()`
call to get current working directory handle wrapped in `Dir`
struct, in order to make the tests WASI-friendly, `fs.cwd()`
call was replaced with `testing.getTestDir()` function which
resolved to either `fs.cwd()` for non-WASI targets, or tries to
fetch the preopen list from the WASI runtime and extract a
preopen for '.' path.
The summary of changes introduced by this commit:
* implement `Dir.makeDir` and `Dir.openDir` targeting WASI
* implement `Dir.deleteFile` and `Dir.deleteDir` targeting WASI
* fix `os.close` and map errors in `unlinkat`
* move WASI-specific `mkdirat` and `unlinkat` from `std.fs.wasi`
to `std.os` module
* implement `lseek_{SET, CUR, END}` targeting WASI
* implement `futimens` targeting WASI
* implement `ftruncate` targeting WASI
* implement `readv`, `writev`, `pread{v}`, `pwrite{v}` targeting WASI
* make sure ANSI escape codes are _not_ used in stderr or stdout
in WASI, as WASI always sanitizes stderr, and sanitizes stdout if
fd is a TTY
* fix specifying WASI rights when opening/creating files/dirs
* tweak `AtomicFile` to be WASI-compatible
* implement `os.renameatWasi` for WASI-compliant `os.renameat` function
* implement sleep() targeting WASI
* fix `process.getEnvMap` targeting WASI
2020-05-05 15:23:49 +00:00
|
|
|
var base: w.rights_t = w.RIGHT_FD_WRITE |
|
2020-05-05 15:05:30 +00:00
|
|
|
w.RIGHT_FD_DATASYNC |
|
|
|
|
w.RIGHT_FD_SEEK |
|
Add/fix missing WASI functionality to pass libstd tests
This rather large commit adds/fixes missing WASI functionality
in `libstd` needed to pass the `libstd` tests. As such, now by
default tests targeting `wasm32-wasi` target are enabled in
`test/tests.zig` module. However, they can be disabled by passing
the `-Dskip-wasi=true` flag when invoking the `zig build test`
command. When the flag is set to `false`, i.e., when WASI tests are
included, `wasmtime` with `--dir=.` is used as the default testing
command.
Since the majority of `libstd` tests were relying on `fs.cwd()`
call to get current working directory handle wrapped in `Dir`
struct, in order to make the tests WASI-friendly, `fs.cwd()`
call was replaced with `testing.getTestDir()` function which
resolved to either `fs.cwd()` for non-WASI targets, or tries to
fetch the preopen list from the WASI runtime and extract a
preopen for '.' path.
The summary of changes introduced by this commit:
* implement `Dir.makeDir` and `Dir.openDir` targeting WASI
* implement `Dir.deleteFile` and `Dir.deleteDir` targeting WASI
* fix `os.close` and map errors in `unlinkat`
* move WASI-specific `mkdirat` and `unlinkat` from `std.fs.wasi`
to `std.os` module
* implement `lseek_{SET, CUR, END}` targeting WASI
* implement `futimens` targeting WASI
* implement `ftruncate` targeting WASI
* implement `readv`, `writev`, `pread{v}`, `pwrite{v}` targeting WASI
* make sure ANSI escape codes are _not_ used in stderr or stdout
in WASI, as WASI always sanitizes stderr, and sanitizes stdout if
fd is a TTY
* fix specifying WASI rights when opening/creating files/dirs
* tweak `AtomicFile` to be WASI-compatible
* implement `os.renameatWasi` for WASI-compliant `os.renameat` function
* implement sleep() targeting WASI
* fix `process.getEnvMap` targeting WASI
2020-05-05 15:23:49 +00:00
|
|
|
w.RIGHT_FD_TELL |
|
2020-05-05 15:05:30 +00:00
|
|
|
w.RIGHT_FD_FDSTAT_SET_FLAGS |
|
|
|
|
w.RIGHT_FD_SYNC |
|
|
|
|
w.RIGHT_FD_ALLOCATE |
|
|
|
|
w.RIGHT_FD_ADVISE |
|
|
|
|
w.RIGHT_FD_FILESTAT_SET_TIMES |
|
Add/fix missing WASI functionality to pass libstd tests
This rather large commit adds/fixes missing WASI functionality
in `libstd` needed to pass the `libstd` tests. As such, now by
default tests targeting `wasm32-wasi` target are enabled in
`test/tests.zig` module. However, they can be disabled by passing
the `-Dskip-wasi=true` flag when invoking the `zig build test`
command. When the flag is set to `false`, i.e., when WASI tests are
included, `wasmtime` with `--dir=.` is used as the default testing
command.
Since the majority of `libstd` tests were relying on `fs.cwd()`
call to get current working directory handle wrapped in `Dir`
struct, in order to make the tests WASI-friendly, `fs.cwd()`
call was replaced with `testing.getTestDir()` function which
resolved to either `fs.cwd()` for non-WASI targets, or tries to
fetch the preopen list from the WASI runtime and extract a
preopen for '.' path.
The summary of changes introduced by this commit:
* implement `Dir.makeDir` and `Dir.openDir` targeting WASI
* implement `Dir.deleteFile` and `Dir.deleteDir` targeting WASI
* fix `os.close` and map errors in `unlinkat`
* move WASI-specific `mkdirat` and `unlinkat` from `std.fs.wasi`
to `std.os` module
* implement `lseek_{SET, CUR, END}` targeting WASI
* implement `futimens` targeting WASI
* implement `ftruncate` targeting WASI
* implement `readv`, `writev`, `pread{v}`, `pwrite{v}` targeting WASI
* make sure ANSI escape codes are _not_ used in stderr or stdout
in WASI, as WASI always sanitizes stderr, and sanitizes stdout if
fd is a TTY
* fix specifying WASI rights when opening/creating files/dirs
* tweak `AtomicFile` to be WASI-compatible
* implement `os.renameatWasi` for WASI-compliant `os.renameat` function
* implement sleep() targeting WASI
* fix `process.getEnvMap` targeting WASI
2020-05-05 15:23:49 +00:00
|
|
|
w.RIGHT_FD_FILESTAT_SET_SIZE |
|
|
|
|
w.RIGHT_FD_FILESTAT_GET;
|
2020-05-01 10:33:11 +00:00
|
|
|
if (flags.read) {
|
Add/fix missing WASI functionality to pass libstd tests
This rather large commit adds/fixes missing WASI functionality
in `libstd` needed to pass the `libstd` tests. As such, now by
default tests targeting `wasm32-wasi` target are enabled in
`test/tests.zig` module. However, they can be disabled by passing
the `-Dskip-wasi=true` flag when invoking the `zig build test`
command. When the flag is set to `false`, i.e., when WASI tests are
included, `wasmtime` with `--dir=.` is used as the default testing
command.
Since the majority of `libstd` tests were relying on `fs.cwd()`
call to get current working directory handle wrapped in `Dir`
struct, in order to make the tests WASI-friendly, `fs.cwd()`
call was replaced with `testing.getTestDir()` function which
resolved to either `fs.cwd()` for non-WASI targets, or tries to
fetch the preopen list from the WASI runtime and extract a
preopen for '.' path.
The summary of changes introduced by this commit:
* implement `Dir.makeDir` and `Dir.openDir` targeting WASI
* implement `Dir.deleteFile` and `Dir.deleteDir` targeting WASI
* fix `os.close` and map errors in `unlinkat`
* move WASI-specific `mkdirat` and `unlinkat` from `std.fs.wasi`
to `std.os` module
* implement `lseek_{SET, CUR, END}` targeting WASI
* implement `futimens` targeting WASI
* implement `ftruncate` targeting WASI
* implement `readv`, `writev`, `pread{v}`, `pwrite{v}` targeting WASI
* make sure ANSI escape codes are _not_ used in stderr or stdout
in WASI, as WASI always sanitizes stderr, and sanitizes stdout if
fd is a TTY
* fix specifying WASI rights when opening/creating files/dirs
* tweak `AtomicFile` to be WASI-compatible
* implement `os.renameatWasi` for WASI-compliant `os.renameat` function
* implement sleep() targeting WASI
* fix `process.getEnvMap` targeting WASI
2020-05-05 15:23:49 +00:00
|
|
|
base |= w.RIGHT_FD_READ;
|
2020-05-01 10:33:11 +00:00
|
|
|
}
|
|
|
|
if (flags.truncate) {
|
2020-05-05 15:05:30 +00:00
|
|
|
oflags |= w.O_TRUNC;
|
2020-05-01 10:33:11 +00:00
|
|
|
}
|
|
|
|
if (flags.exclusive) {
|
2020-05-05 15:05:30 +00:00
|
|
|
oflags |= w.O_EXCL;
|
2020-05-01 10:33:11 +00:00
|
|
|
}
|
2020-07-19 21:18:20 +00:00
|
|
|
const fd = try os.openatWasi(self.fd, sub_path, 0x0, oflags, 0x0, base, 0x0);
|
2020-05-04 11:35:02 +00:00
|
|
|
return File{ .handle = fd };
|
2020-05-01 10:33:11 +00:00
|
|
|
}
|
|
|
|
|
2019-11-30 18:32:11 +00:00
|
|
|
/// Same as `createFile` but the path parameter is null-terminated.
|
2020-03-30 18:23:22 +00:00
|
|
|
pub fn createFileZ(self: Dir, sub_path_c: [*:0]const u8, flags: File.CreateFlags) File.OpenError!File {
|
2020-02-25 06:52:27 +00:00
|
|
|
if (builtin.os.tag == .windows) {
|
2019-11-30 18:32:11 +00:00
|
|
|
const path_w = try os.windows.cStrToPrefixedFileW(sub_path_c);
|
2020-05-02 05:25:22 +00:00
|
|
|
return self.createFileW(path_w.span(), flags);
|
2019-11-21 22:07:29 +00:00
|
|
|
}
|
2020-03-24 02:59:09 +00:00
|
|
|
|
|
|
|
// Use the O_ locking flags if the os supports them
|
2020-04-08 22:29:44 +00:00
|
|
|
// (Or if it's darwin, as darwin's `open` doesn't support the O_SYNC flag)
|
2020-05-02 03:17:15 +00:00
|
|
|
const has_flock_open_flags = @hasDecl(os, "O_EXLOCK") and !is_darwin;
|
|
|
|
const nonblocking_lock_flag: u32 = if (has_flock_open_flags and flags.lock_nonblocking)
|
|
|
|
os.O_NONBLOCK | os.O_SYNC
|
|
|
|
else
|
|
|
|
0;
|
2020-04-07 22:23:12 +00:00
|
|
|
const lock_flag: u32 = if (has_flock_open_flags) switch (flags.lock) {
|
|
|
|
.None => @as(u32, 0),
|
|
|
|
.Shared => os.O_SHLOCK,
|
|
|
|
.Exclusive => os.O_EXLOCK,
|
|
|
|
} else 0;
|
2020-03-24 02:59:09 +00:00
|
|
|
|
2019-11-21 22:07:29 +00:00
|
|
|
const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0;
|
2020-03-24 02:59:09 +00:00
|
|
|
const os_flags = lock_flag | O_LARGEFILE | os.O_CREAT | os.O_CLOEXEC |
|
2019-11-30 18:32:11 +00:00
|
|
|
(if (flags.truncate) @as(u32, os.O_TRUNC) else 0) |
|
|
|
|
(if (flags.read) @as(u32, os.O_RDWR) else os.O_WRONLY) |
|
|
|
|
(if (flags.exclusive) @as(u32, os.O_EXCL) else 0);
|
2020-05-02 03:17:15 +00:00
|
|
|
const fd = if (flags.intended_io_mode != .blocking)
|
2020-02-06 22:56:40 +00:00
|
|
|
try std.event.Loop.instance.?.openatZ(self.fd, sub_path_c, os_flags, flags.mode)
|
|
|
|
else
|
2020-03-30 18:23:22 +00:00
|
|
|
try os.openatZ(self.fd, sub_path_c, os_flags, flags.mode);
|
2020-03-11 00:54:05 +00:00
|
|
|
|
2020-04-07 22:23:12 +00:00
|
|
|
if (!has_flock_open_flags and flags.lock != .None) {
|
2020-03-11 00:54:05 +00:00
|
|
|
// TODO: integrate async I/O
|
2020-04-08 00:00:12 +00:00
|
|
|
const lock_nonblocking = if (flags.lock_nonblocking) os.LOCK_NB else @as(i32, 0);
|
2020-04-07 22:23:12 +00:00
|
|
|
try os.flock(fd, switch (flags.lock) {
|
|
|
|
.None => unreachable,
|
2020-04-08 00:00:12 +00:00
|
|
|
.Shared => os.LOCK_SH | lock_nonblocking,
|
|
|
|
.Exclusive => os.LOCK_EX | lock_nonblocking,
|
2020-04-07 22:23:12 +00:00
|
|
|
});
|
2020-03-11 00:54:05 +00:00
|
|
|
}
|
|
|
|
|
2020-05-02 03:17:15 +00:00
|
|
|
return File{
|
|
|
|
.handle = fd,
|
|
|
|
.capable_io_mode = .blocking,
|
|
|
|
.intended_io_mode = flags.intended_io_mode,
|
|
|
|
};
|
2019-11-30 18:32:11 +00:00
|
|
|
}
|
|
|
|
|
2019-11-30 23:36:17 +00:00
|
|
|
/// Same as `createFile` but Windows-only and the path parameter is
|
|
|
|
/// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded.
|
2020-05-01 23:02:16 +00:00
|
|
|
pub fn createFileW(self: Dir, sub_path_w: []const u16, flags: File.CreateFlags) File.OpenError!File {
|
2019-11-30 18:32:11 +00:00
|
|
|
const w = os.windows;
|
2020-05-01 23:02:16 +00:00
|
|
|
const read_flag = if (flags.read) @as(u32, w.GENERIC_READ) else 0;
|
zig build system: correctly handle multiple output artifacts
Previously the zig build system incorrectly assumed that the only build
artifact was a binary. Now, when you enable the cache, only the output
dir is printed to stdout, and the zig build system iterates over the
files in that directory, copying them to the output directory.
To support this change:
* Add `std.os.renameat`, `std.os.renameatZ`, and `std.os.renameatW`.
* Fix `std.os.linux.renameat` not compiling due to typos.
* Deprecate `std.fs.updateFile` and `std.fs.updateFileMode`.
* Add `std.fs.Dir.updateFile`, which supports using open directory
handles for both the source and destination paths, as well as an
options parameter which allows overriding the mode.
* Update `std.fs.AtomicFile` to support operating based on an open
directory handle. Instead of `std.fs.AtomicFile.init`, use
`std.fs.Dir.atomicFile`.
* `std.fs.AtomicFile` deinit() better handles the situation when the
rename fails but the temporary file still exists, by still
attempting to remove the temporary file.
* `std.fs.Dir.openFileWindows` is moved to `std.os.windows.OpenFileW`.
* `std.os.RenameError` gains the error codes `NoDevice`,
`SharingViolation`, and `PipeBusy` which have been observed from
Windows.
Closes #4733
2020-03-14 01:06:07 +00:00
|
|
|
return @as(File, .{
|
2020-05-01 23:02:16 +00:00
|
|
|
.handle = try os.windows.OpenFile(sub_path_w, .{
|
|
|
|
.dir = self.fd,
|
|
|
|
.access_mask = w.SYNCHRONIZE | w.GENERIC_WRITE | read_flag,
|
|
|
|
.share_access = switch (flags.lock) {
|
2020-05-02 04:41:19 +00:00
|
|
|
.None => w.FILE_SHARE_WRITE | w.FILE_SHARE_READ | w.FILE_SHARE_DELETE,
|
2020-05-01 23:02:16 +00:00
|
|
|
.Shared => w.FILE_SHARE_READ | w.FILE_SHARE_DELETE,
|
|
|
|
.Exclusive => w.FILE_SHARE_DELETE,
|
|
|
|
},
|
|
|
|
.share_access_nonblocking = flags.lock_nonblocking,
|
|
|
|
.creation = if (flags.exclusive)
|
|
|
|
@as(u32, w.FILE_CREATE)
|
|
|
|
else if (flags.truncate)
|
|
|
|
@as(u32, w.FILE_OVERWRITE_IF)
|
|
|
|
else
|
|
|
|
@as(u32, w.FILE_OPEN_IF),
|
2020-05-02 03:17:15 +00:00
|
|
|
.io_mode = flags.intended_io_mode,
|
2020-05-01 23:02:16 +00:00
|
|
|
}),
|
2020-05-02 03:17:15 +00:00
|
|
|
.capable_io_mode = std.io.default_mode,
|
|
|
|
.intended_io_mode = flags.intended_io_mode,
|
zig build system: correctly handle multiple output artifacts
Previously the zig build system incorrectly assumed that the only build
artifact was a binary. Now, when you enable the cache, only the output
dir is printed to stdout, and the zig build system iterates over the
files in that directory, copying them to the output directory.
To support this change:
* Add `std.os.renameat`, `std.os.renameatZ`, and `std.os.renameatW`.
* Fix `std.os.linux.renameat` not compiling due to typos.
* Deprecate `std.fs.updateFile` and `std.fs.updateFileMode`.
* Add `std.fs.Dir.updateFile`, which supports using open directory
handles for both the source and destination paths, as well as an
options parameter which allows overriding the mode.
* Update `std.fs.AtomicFile` to support operating based on an open
directory handle. Instead of `std.fs.AtomicFile.init`, use
`std.fs.Dir.atomicFile`.
* `std.fs.AtomicFile` deinit() better handles the situation when the
rename fails but the temporary file still exists, by still
attempting to remove the temporary file.
* `std.fs.Dir.openFileWindows` is moved to `std.os.windows.OpenFileW`.
* `std.os.RenameError` gains the error codes `NoDevice`,
`SharingViolation`, and `PipeBusy` which have been observed from
Windows.
Closes #4733
2020-03-14 01:06:07 +00:00
|
|
|
});
|
2019-11-30 18:32:11 +00:00
|
|
|
}
|
|
|
|
|
2020-03-30 18:23:22 +00:00
|
|
|
pub const openRead = @compileError("deprecated in favor of openFile");
|
|
|
|
pub const openReadC = @compileError("deprecated in favor of openFileZ");
|
|
|
|
pub const openReadW = @compileError("deprecated in favor of openFileW");
|
2019-08-17 01:29:29 +00:00
|
|
|
|
2020-01-15 08:09:10 +00:00
|
|
|
pub fn makeDir(self: Dir, sub_path: []const u8) !void {
|
|
|
|
try os.mkdirat(self.fd, sub_path, default_new_dir_mode);
|
|
|
|
}
|
|
|
|
|
2020-03-03 20:01:08 +00:00
|
|
|
pub fn makeDirZ(self: Dir, sub_path: [*:0]const u8) !void {
|
2020-03-30 18:23:22 +00:00
|
|
|
try os.mkdiratZ(self.fd, sub_path, default_new_dir_mode);
|
2020-01-15 08:09:10 +00:00
|
|
|
}
|
|
|
|
|
2020-03-03 20:01:08 +00:00
|
|
|
pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void {
|
2020-07-30 15:00:50 +00:00
|
|
|
try os.mkdiratW(self.fd, sub_path, default_new_dir_mode);
|
2020-03-03 20:01:08 +00:00
|
|
|
}
|
|
|
|
|
2020-01-16 00:07:32 +00:00
|
|
|
/// Calls makeDir recursively to make an entire path. Returns success if the path
|
|
|
|
/// already exists and is a directory.
|
|
|
|
/// This function is not atomic, and if it returns an error, the file system may
|
|
|
|
/// have been modified regardless.
|
|
|
|
pub fn makePath(self: Dir, sub_path: []const u8) !void {
|
|
|
|
var end_index: usize = sub_path.len;
|
|
|
|
while (true) {
|
|
|
|
self.makeDir(sub_path[0..end_index]) catch |err| switch (err) {
|
|
|
|
error.PathAlreadyExists => {
|
|
|
|
// TODO stat the file and return an error if it's not a directory
|
|
|
|
// this is important because otherwise a dangling symlink
|
|
|
|
// could cause an infinite loop
|
|
|
|
if (end_index == sub_path.len) return;
|
|
|
|
},
|
|
|
|
error.FileNotFound => {
|
|
|
|
if (end_index == 0) return err;
|
|
|
|
// march end_index backward until next path component
|
|
|
|
while (true) {
|
|
|
|
end_index -= 1;
|
|
|
|
if (path.isSep(sub_path[end_index])) break;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
},
|
|
|
|
else => return err,
|
|
|
|
};
|
|
|
|
if (end_index == sub_path.len) return;
|
|
|
|
// march end_index forward until next path component
|
|
|
|
while (true) {
|
|
|
|
end_index += 1;
|
|
|
|
if (end_index == sub_path.len or path.isSep(sub_path[end_index])) break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-27 22:26:59 +00:00
|
|
|
/// This function performs `makePath`, followed by `openDir`.
|
|
|
|
/// If supported by the OS, this operation is atomic. It is not atomic on
|
|
|
|
/// all operating systems.
|
|
|
|
pub fn makeOpenPath(self: Dir, sub_path: []const u8, open_dir_options: OpenDirOptions) !Dir {
|
|
|
|
// TODO improve this implementation on Windows; we can avoid 1 call to NtClose
|
|
|
|
try self.makePath(sub_path);
|
|
|
|
return self.openDir(sub_path, open_dir_options);
|
|
|
|
}
|
|
|
|
|
2020-08-12 21:50:00 +00:00
|
|
|
/// This function returns the canonicalized absolute pathname of
|
|
|
|
/// `pathname` relative to this `Dir`. If `pathname` is absolute, ignores this
|
|
|
|
/// `Dir` handle and returns the canonicalized absolute pathname of `pathname`
|
|
|
|
/// argument.
|
|
|
|
/// This function is not universally supported by all platforms.
|
|
|
|
/// Currently supported hosts are: Linux, macOS, and Windows.
|
|
|
|
/// See also `Dir.realpathZ`, `Dir.realpathW`, and `Dir.realpathAlloc`.
|
|
|
|
pub fn realpath(self: Dir, pathname: []const u8, out_buffer: []u8) ![]u8 {
|
|
|
|
if (builtin.os.tag == .wasi) {
|
|
|
|
@compileError("realpath is unsupported in WASI");
|
|
|
|
}
|
|
|
|
if (builtin.os.tag == .windows) {
|
|
|
|
const pathname_w = try os.windows.sliceToPrefixedFileW(pathname);
|
|
|
|
return self.realpathW(pathname_w.span(), out_buffer);
|
|
|
|
}
|
|
|
|
const pathname_c = try os.toPosixPath(pathname);
|
|
|
|
return self.realpathZ(&pathname_c, out_buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Same as `Dir.realpath` except `pathname` is null-terminated.
|
|
|
|
/// See also `Dir.realpath`, `realpathZ`.
|
|
|
|
pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) ![]u8 {
|
|
|
|
if (builtin.os.tag == .windows) {
|
|
|
|
const pathname_w = try os.windows.cStrToPrefixedFileW(pathname);
|
|
|
|
return self.realpathW(pathname_w.span(), out_buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
const flags = if (builtin.os.tag == .linux) os.O_PATH | os.O_NONBLOCK | os.O_CLOEXEC else os.O_NONBLOCK | os.O_CLOEXEC;
|
|
|
|
const fd = os.openatZ(self.fd, pathname, flags, 0) catch |err| switch (err) {
|
|
|
|
error.FileLocksNotSupported => unreachable,
|
|
|
|
else => |e| return e,
|
|
|
|
};
|
|
|
|
defer os.close(fd);
|
|
|
|
|
|
|
|
// Use of MAX_PATH_BYTES here is valid as the realpath function does not
|
|
|
|
// have a variant that takes an arbitrary-size buffer.
|
|
|
|
// TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008
|
|
|
|
// NULL out parameter (GNU's canonicalize_file_name) to handle overelong
|
|
|
|
// paths. musl supports passing NULL but restricts the output to PATH_MAX
|
|
|
|
// anyway.
|
|
|
|
var buffer: [MAX_PATH_BYTES]u8 = undefined;
|
|
|
|
const out_path = try os.getFdPath(fd, &buffer);
|
|
|
|
|
|
|
|
if (out_path.len > out_buffer.len) {
|
|
|
|
return error.NameTooLong;
|
|
|
|
}
|
|
|
|
|
|
|
|
mem.copy(u8, out_buffer, out_path);
|
|
|
|
|
|
|
|
return out_buffer[0..out_path.len];
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Windows-only. Same as `Dir.realpath` except `pathname` is WTF16 encoded.
|
|
|
|
/// See also `Dir.realpath`, `realpathW`.
|
|
|
|
pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) ![]u8 {
|
|
|
|
const w = os.windows;
|
|
|
|
|
|
|
|
const access_mask = w.GENERIC_READ | w.SYNCHRONIZE;
|
|
|
|
const share_access = w.FILE_SHARE_READ;
|
|
|
|
const creation = w.FILE_OPEN;
|
|
|
|
const h_file = blk: {
|
|
|
|
const res = w.OpenFile(pathname, .{
|
|
|
|
.dir = self.fd,
|
|
|
|
.access_mask = access_mask,
|
|
|
|
.share_access = share_access,
|
|
|
|
.creation = creation,
|
|
|
|
.io_mode = .blocking,
|
|
|
|
}) catch |err| switch (err) {
|
|
|
|
error.IsDir => break :blk w.OpenFile(pathname, .{
|
|
|
|
.dir = self.fd,
|
|
|
|
.access_mask = access_mask,
|
|
|
|
.share_access = share_access,
|
|
|
|
.creation = creation,
|
|
|
|
.io_mode = .blocking,
|
|
|
|
.open_dir = true,
|
|
|
|
}) catch |er| switch (er) {
|
|
|
|
error.WouldBlock => unreachable,
|
|
|
|
else => |e2| return e2,
|
|
|
|
},
|
|
|
|
error.WouldBlock => unreachable,
|
|
|
|
else => |e| return e,
|
|
|
|
};
|
|
|
|
break :blk res;
|
|
|
|
};
|
|
|
|
defer w.CloseHandle(h_file);
|
|
|
|
|
|
|
|
// Use of MAX_PATH_BYTES here is valid as the realpath function does not
|
|
|
|
// have a variant that takes an arbitrary-size buffer.
|
|
|
|
// TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008
|
|
|
|
// NULL out parameter (GNU's canonicalize_file_name) to handle overelong
|
|
|
|
// paths. musl supports passing NULL but restricts the output to PATH_MAX
|
|
|
|
// anyway.
|
|
|
|
var buffer: [MAX_PATH_BYTES]u8 = undefined;
|
|
|
|
const out_path = try os.getFdPath(h_file, &buffer);
|
|
|
|
|
|
|
|
if (out_path.len > out_buffer.len) {
|
|
|
|
return error.NameTooLong;
|
|
|
|
}
|
|
|
|
|
|
|
|
mem.copy(u8, out_buffer, out_path);
|
|
|
|
|
|
|
|
return out_buffer[0..out_path.len];
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Same as `Dir.realpath` except caller must free the returned memory.
|
|
|
|
/// See also `Dir.realpath`.
|
|
|
|
pub fn realpathAlloc(self: Dir, allocator: *Allocator, pathname: []const u8) ![]u8 {
|
|
|
|
// Use of MAX_PATH_BYTES here is valid as the realpath function does not
|
|
|
|
// have a variant that takes an arbitrary-size buffer.
|
|
|
|
// TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008
|
|
|
|
// NULL out parameter (GNU's canonicalize_file_name) to handle overelong
|
|
|
|
// paths. musl supports passing NULL but restricts the output to PATH_MAX
|
|
|
|
// anyway.
|
|
|
|
var buf: [MAX_PATH_BYTES]u8 = undefined;
|
|
|
|
return allocator.dupe(u8, try self.realpath(pathname, buf[0..]));
|
|
|
|
}
|
|
|
|
|
2020-03-03 20:01:08 +00:00
|
|
|
/// Changes the current working directory to the open directory handle.
|
|
|
|
/// This modifies global state and can have surprising effects in multi-
|
|
|
|
/// threaded applications. Most applications and especially libraries should
|
|
|
|
/// not call this function as a general rule, however it can have use cases
|
|
|
|
/// in, for example, implementing a shell, or child process execution.
|
|
|
|
/// Not all targets support this. For example, WASI does not have the concept
|
|
|
|
/// of a current working directory.
|
|
|
|
pub fn setAsCwd(self: Dir) !void {
|
Add/fix missing WASI functionality to pass libstd tests
This rather large commit adds/fixes missing WASI functionality
in `libstd` needed to pass the `libstd` tests. As such, now by
default tests targeting `wasm32-wasi` target are enabled in
`test/tests.zig` module. However, they can be disabled by passing
the `-Dskip-wasi=true` flag when invoking the `zig build test`
command. When the flag is set to `false`, i.e., when WASI tests are
included, `wasmtime` with `--dir=.` is used as the default testing
command.
Since the majority of `libstd` tests were relying on `fs.cwd()`
call to get current working directory handle wrapped in `Dir`
struct, in order to make the tests WASI-friendly, `fs.cwd()`
call was replaced with `testing.getTestDir()` function which
resolved to either `fs.cwd()` for non-WASI targets, or tries to
fetch the preopen list from the WASI runtime and extract a
preopen for '.' path.
The summary of changes introduced by this commit:
* implement `Dir.makeDir` and `Dir.openDir` targeting WASI
* implement `Dir.deleteFile` and `Dir.deleteDir` targeting WASI
* fix `os.close` and map errors in `unlinkat`
* move WASI-specific `mkdirat` and `unlinkat` from `std.fs.wasi`
to `std.os` module
* implement `lseek_{SET, CUR, END}` targeting WASI
* implement `futimens` targeting WASI
* implement `ftruncate` targeting WASI
* implement `readv`, `writev`, `pread{v}`, `pwrite{v}` targeting WASI
* make sure ANSI escape codes are _not_ used in stderr or stdout
in WASI, as WASI always sanitizes stderr, and sanitizes stdout if
fd is a TTY
* fix specifying WASI rights when opening/creating files/dirs
* tweak `AtomicFile` to be WASI-compatible
* implement `os.renameatWasi` for WASI-compliant `os.renameat` function
* implement sleep() targeting WASI
* fix `process.getEnvMap` targeting WASI
2020-05-05 15:23:49 +00:00
|
|
|
if (builtin.os.tag == .wasi) {
|
|
|
|
@compileError("changing cwd is not currently possible in WASI");
|
|
|
|
}
|
2020-01-15 08:11:54 +00:00
|
|
|
try os.fchdir(self.fd);
|
|
|
|
}
|
|
|
|
|
2020-03-18 18:45:01 +00:00
|
|
|
pub const OpenDirOptions = struct {
|
|
|
|
/// `true` means the opened directory can be used as the `Dir` parameter
|
|
|
|
/// for functions which operate based on an open directory handle. When `false`,
|
|
|
|
/// such operations are Illegal Behavior.
|
|
|
|
access_sub_paths: bool = true,
|
2019-11-22 21:40:46 +00:00
|
|
|
|
2020-03-18 18:45:01 +00:00
|
|
|
/// `true` means the opened directory can be scanned for the files and sub-directories
|
|
|
|
/// of the result. It means the `iterate` function can be called.
|
|
|
|
iterate: bool = false,
|
2020-07-19 20:25:00 +00:00
|
|
|
|
2020-07-19 21:18:20 +00:00
|
|
|
/// `true` means it won't dereference the symlinks.
|
2020-07-19 20:25:00 +00:00
|
|
|
no_follow: bool = false,
|
2020-03-18 18:45:01 +00:00
|
|
|
};
|
2019-11-22 21:40:46 +00:00
|
|
|
|
2020-03-18 18:45:01 +00:00
|
|
|
/// Opens a directory at the given path. The directory is a system resource that remains
|
|
|
|
/// open until `close` is called on the result.
|
2019-11-30 20:14:04 +00:00
|
|
|
///
|
|
|
|
/// Asserts that the path parameter has no null bytes.
|
2020-03-18 18:45:01 +00:00
|
|
|
pub fn openDir(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError!Dir {
|
2020-02-25 06:52:27 +00:00
|
|
|
if (builtin.os.tag == .windows) {
|
2019-10-21 18:18:01 +00:00
|
|
|
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
|
2020-05-02 05:25:22 +00:00
|
|
|
return self.openDirW(sub_path_w.span().ptr, args);
|
Add/fix missing WASI functionality to pass libstd tests
This rather large commit adds/fixes missing WASI functionality
in `libstd` needed to pass the `libstd` tests. As such, now by
default tests targeting `wasm32-wasi` target are enabled in
`test/tests.zig` module. However, they can be disabled by passing
the `-Dskip-wasi=true` flag when invoking the `zig build test`
command. When the flag is set to `false`, i.e., when WASI tests are
included, `wasmtime` with `--dir=.` is used as the default testing
command.
Since the majority of `libstd` tests were relying on `fs.cwd()`
call to get current working directory handle wrapped in `Dir`
struct, in order to make the tests WASI-friendly, `fs.cwd()`
call was replaced with `testing.getTestDir()` function which
resolved to either `fs.cwd()` for non-WASI targets, or tries to
fetch the preopen list from the WASI runtime and extract a
preopen for '.' path.
The summary of changes introduced by this commit:
* implement `Dir.makeDir` and `Dir.openDir` targeting WASI
* implement `Dir.deleteFile` and `Dir.deleteDir` targeting WASI
* fix `os.close` and map errors in `unlinkat`
* move WASI-specific `mkdirat` and `unlinkat` from `std.fs.wasi`
to `std.os` module
* implement `lseek_{SET, CUR, END}` targeting WASI
* implement `futimens` targeting WASI
* implement `ftruncate` targeting WASI
* implement `readv`, `writev`, `pread{v}`, `pwrite{v}` targeting WASI
* make sure ANSI escape codes are _not_ used in stderr or stdout
in WASI, as WASI always sanitizes stderr, and sanitizes stdout if
fd is a TTY
* fix specifying WASI rights when opening/creating files/dirs
* tweak `AtomicFile` to be WASI-compatible
* implement `os.renameatWasi` for WASI-compliant `os.renameat` function
* implement sleep() targeting WASI
* fix `process.getEnvMap` targeting WASI
2020-05-05 15:23:49 +00:00
|
|
|
} else if (builtin.os.tag == .wasi) {
|
|
|
|
return self.openDirWasi(sub_path, args);
|
2020-03-18 18:45:01 +00:00
|
|
|
} else {
|
|
|
|
const sub_path_c = try os.toPosixPath(sub_path);
|
2020-03-30 18:23:22 +00:00
|
|
|
return self.openDirZ(&sub_path_c, args);
|
2019-11-22 21:40:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-30 18:23:22 +00:00
|
|
|
pub const openDirC = @compileError("deprecated: renamed to openDirZ");
|
|
|
|
|
Add/fix missing WASI functionality to pass libstd tests
This rather large commit adds/fixes missing WASI functionality
in `libstd` needed to pass the `libstd` tests. As such, now by
default tests targeting `wasm32-wasi` target are enabled in
`test/tests.zig` module. However, they can be disabled by passing
the `-Dskip-wasi=true` flag when invoking the `zig build test`
command. When the flag is set to `false`, i.e., when WASI tests are
included, `wasmtime` with `--dir=.` is used as the default testing
command.
Since the majority of `libstd` tests were relying on `fs.cwd()`
call to get current working directory handle wrapped in `Dir`
struct, in order to make the tests WASI-friendly, `fs.cwd()`
call was replaced with `testing.getTestDir()` function which
resolved to either `fs.cwd()` for non-WASI targets, or tries to
fetch the preopen list from the WASI runtime and extract a
preopen for '.' path.
The summary of changes introduced by this commit:
* implement `Dir.makeDir` and `Dir.openDir` targeting WASI
* implement `Dir.deleteFile` and `Dir.deleteDir` targeting WASI
* fix `os.close` and map errors in `unlinkat`
* move WASI-specific `mkdirat` and `unlinkat` from `std.fs.wasi`
to `std.os` module
* implement `lseek_{SET, CUR, END}` targeting WASI
* implement `futimens` targeting WASI
* implement `ftruncate` targeting WASI
* implement `readv`, `writev`, `pread{v}`, `pwrite{v}` targeting WASI
* make sure ANSI escape codes are _not_ used in stderr or stdout
in WASI, as WASI always sanitizes stderr, and sanitizes stdout if
fd is a TTY
* fix specifying WASI rights when opening/creating files/dirs
* tweak `AtomicFile` to be WASI-compatible
* implement `os.renameatWasi` for WASI-compliant `os.renameat` function
* implement sleep() targeting WASI
* fix `process.getEnvMap` targeting WASI
2020-05-05 15:23:49 +00:00
|
|
|
/// Same as `openDir` except only WASI.
|
|
|
|
pub fn openDirWasi(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError!Dir {
|
|
|
|
const w = os.wasi;
|
|
|
|
var base: w.rights_t = w.RIGHT_FD_FILESTAT_GET | w.RIGHT_FD_FDSTAT_SET_FLAGS | w.RIGHT_FD_FILESTAT_SET_TIMES;
|
|
|
|
if (args.iterate) {
|
|
|
|
base |= w.RIGHT_FD_READDIR;
|
|
|
|
}
|
|
|
|
if (args.access_sub_paths) {
|
|
|
|
base |= w.RIGHT_PATH_CREATE_DIRECTORY |
|
|
|
|
w.RIGHT_PATH_CREATE_FILE |
|
|
|
|
w.RIGHT_PATH_LINK_SOURCE |
|
|
|
|
w.RIGHT_PATH_LINK_TARGET |
|
|
|
|
w.RIGHT_PATH_OPEN |
|
|
|
|
w.RIGHT_PATH_READLINK |
|
|
|
|
w.RIGHT_PATH_RENAME_SOURCE |
|
|
|
|
w.RIGHT_PATH_RENAME_TARGET |
|
|
|
|
w.RIGHT_PATH_FILESTAT_GET |
|
|
|
|
w.RIGHT_PATH_FILESTAT_SET_SIZE |
|
|
|
|
w.RIGHT_PATH_FILESTAT_SET_TIMES |
|
|
|
|
w.RIGHT_PATH_SYMLINK |
|
|
|
|
w.RIGHT_PATH_REMOVE_DIRECTORY |
|
|
|
|
w.RIGHT_PATH_UNLINK_FILE;
|
|
|
|
}
|
2020-07-19 20:25:00 +00:00
|
|
|
const symlink_flags: w.lookupflags_t = if (args.no_follow) 0x0 else w.LOOKUP_SYMLINK_FOLLOW;
|
Add/fix missing WASI functionality to pass libstd tests
This rather large commit adds/fixes missing WASI functionality
in `libstd` needed to pass the `libstd` tests. As such, now by
default tests targeting `wasm32-wasi` target are enabled in
`test/tests.zig` module. However, they can be disabled by passing
the `-Dskip-wasi=true` flag when invoking the `zig build test`
command. When the flag is set to `false`, i.e., when WASI tests are
included, `wasmtime` with `--dir=.` is used as the default testing
command.
Since the majority of `libstd` tests were relying on `fs.cwd()`
call to get current working directory handle wrapped in `Dir`
struct, in order to make the tests WASI-friendly, `fs.cwd()`
call was replaced with `testing.getTestDir()` function which
resolved to either `fs.cwd()` for non-WASI targets, or tries to
fetch the preopen list from the WASI runtime and extract a
preopen for '.' path.
The summary of changes introduced by this commit:
* implement `Dir.makeDir` and `Dir.openDir` targeting WASI
* implement `Dir.deleteFile` and `Dir.deleteDir` targeting WASI
* fix `os.close` and map errors in `unlinkat`
* move WASI-specific `mkdirat` and `unlinkat` from `std.fs.wasi`
to `std.os` module
* implement `lseek_{SET, CUR, END}` targeting WASI
* implement `futimens` targeting WASI
* implement `ftruncate` targeting WASI
* implement `readv`, `writev`, `pread{v}`, `pwrite{v}` targeting WASI
* make sure ANSI escape codes are _not_ used in stderr or stdout
in WASI, as WASI always sanitizes stderr, and sanitizes stdout if
fd is a TTY
* fix specifying WASI rights when opening/creating files/dirs
* tweak `AtomicFile` to be WASI-compatible
* implement `os.renameatWasi` for WASI-compliant `os.renameat` function
* implement sleep() targeting WASI
* fix `process.getEnvMap` targeting WASI
2020-05-05 15:23:49 +00:00
|
|
|
// TODO do we really need all the rights here?
|
|
|
|
const inheriting: w.rights_t = w.RIGHT_ALL ^ w.RIGHT_SOCK_SHUTDOWN;
|
|
|
|
|
2020-07-19 21:18:20 +00:00
|
|
|
const result = os.openatWasi(self.fd, sub_path, symlink_flags, w.O_DIRECTORY, 0x0, base, inheriting);
|
Add/fix missing WASI functionality to pass libstd tests
This rather large commit adds/fixes missing WASI functionality
in `libstd` needed to pass the `libstd` tests. As such, now by
default tests targeting `wasm32-wasi` target are enabled in
`test/tests.zig` module. However, they can be disabled by passing
the `-Dskip-wasi=true` flag when invoking the `zig build test`
command. When the flag is set to `false`, i.e., when WASI tests are
included, `wasmtime` with `--dir=.` is used as the default testing
command.
Since the majority of `libstd` tests were relying on `fs.cwd()`
call to get current working directory handle wrapped in `Dir`
struct, in order to make the tests WASI-friendly, `fs.cwd()`
call was replaced with `testing.getTestDir()` function which
resolved to either `fs.cwd()` for non-WASI targets, or tries to
fetch the preopen list from the WASI runtime and extract a
preopen for '.' path.
The summary of changes introduced by this commit:
* implement `Dir.makeDir` and `Dir.openDir` targeting WASI
* implement `Dir.deleteFile` and `Dir.deleteDir` targeting WASI
* fix `os.close` and map errors in `unlinkat`
* move WASI-specific `mkdirat` and `unlinkat` from `std.fs.wasi`
to `std.os` module
* implement `lseek_{SET, CUR, END}` targeting WASI
* implement `futimens` targeting WASI
* implement `ftruncate` targeting WASI
* implement `readv`, `writev`, `pread{v}`, `pwrite{v}` targeting WASI
* make sure ANSI escape codes are _not_ used in stderr or stdout
in WASI, as WASI always sanitizes stderr, and sanitizes stdout if
fd is a TTY
* fix specifying WASI rights when opening/creating files/dirs
* tweak `AtomicFile` to be WASI-compatible
* implement `os.renameatWasi` for WASI-compliant `os.renameat` function
* implement sleep() targeting WASI
* fix `process.getEnvMap` targeting WASI
2020-05-05 15:23:49 +00:00
|
|
|
const fd = result catch |err| switch (err) {
|
|
|
|
error.FileTooBig => unreachable, // can't happen for directories
|
|
|
|
error.IsDir => unreachable, // we're providing O_DIRECTORY
|
|
|
|
error.NoSpaceLeft => unreachable, // not providing O_CREAT
|
|
|
|
error.PathAlreadyExists => unreachable, // not providing O_CREAT
|
|
|
|
error.FileLocksNotSupported => unreachable, // locking folders is not supported
|
|
|
|
else => |e| return e,
|
|
|
|
};
|
|
|
|
return Dir{ .fd = fd };
|
|
|
|
}
|
|
|
|
|
2020-03-18 18:45:01 +00:00
|
|
|
/// Same as `openDir` except the parameter is null-terminated.
|
2020-03-30 18:23:22 +00:00
|
|
|
pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenDirOptions) OpenError!Dir {
|
2020-02-25 06:52:27 +00:00
|
|
|
if (builtin.os.tag == .windows) {
|
2019-11-22 21:56:46 +00:00
|
|
|
const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path_c);
|
2020-05-02 05:25:22 +00:00
|
|
|
return self.openDirW(sub_path_w.span().ptr, args);
|
2020-07-19 20:25:00 +00:00
|
|
|
}
|
|
|
|
const symlink_flags: u32 = if (args.no_follow) os.O_NOFOLLOW else 0x0;
|
|
|
|
if (!args.iterate) {
|
2019-11-22 21:56:46 +00:00
|
|
|
const O_PATH = if (@hasDecl(os, "O_PATH")) os.O_PATH else 0;
|
2020-07-19 20:25:00 +00:00
|
|
|
return self.openDirFlagsZ(sub_path_c, os.O_DIRECTORY | os.O_RDONLY | os.O_CLOEXEC | O_PATH | symlink_flags);
|
2020-03-18 18:45:01 +00:00
|
|
|
} else {
|
2020-07-19 20:25:00 +00:00
|
|
|
return self.openDirFlagsZ(sub_path_c, os.O_DIRECTORY | os.O_RDONLY | os.O_CLOEXEC | symlink_flags);
|
2019-11-22 21:56:46 +00:00
|
|
|
}
|
2019-11-22 21:40:46 +00:00
|
|
|
}
|
|
|
|
|
2020-03-18 18:45:01 +00:00
|
|
|
/// Same as `openDir` except the path parameter is WTF-16 encoded, NT-prefixed.
|
|
|
|
/// This function asserts the target OS is Windows.
|
|
|
|
pub fn openDirW(self: Dir, sub_path_w: [*:0]const u16, args: OpenDirOptions) OpenError!Dir {
|
|
|
|
const w = os.windows;
|
|
|
|
// TODO remove some of these flags if args.access_sub_paths is false
|
|
|
|
const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
|
|
|
|
w.SYNCHRONIZE | w.FILE_TRAVERSE;
|
2020-03-18 20:09:06 +00:00
|
|
|
const flags: u32 = if (args.iterate) base_flags | w.FILE_LIST_DIRECTORY else base_flags;
|
2020-07-19 20:25:00 +00:00
|
|
|
return self.openDirAccessMaskW(sub_path_w, flags, args.no_follow);
|
2019-11-22 21:56:46 +00:00
|
|
|
}
|
2019-10-21 18:18:01 +00:00
|
|
|
|
2020-03-18 18:45:01 +00:00
|
|
|
/// `flags` must contain `os.O_DIRECTORY`.
|
2020-03-30 18:23:22 +00:00
|
|
|
fn openDirFlagsZ(self: Dir, sub_path_c: [*:0]const u8, flags: u32) OpenError!Dir {
|
2020-02-06 22:56:40 +00:00
|
|
|
const result = if (need_async_thread)
|
2020-03-18 18:45:01 +00:00
|
|
|
std.event.Loop.instance.?.openatZ(self.fd, sub_path_c, flags, 0)
|
2020-02-06 22:56:40 +00:00
|
|
|
else
|
2020-03-30 18:23:22 +00:00
|
|
|
os.openatZ(self.fd, sub_path_c, flags, 0);
|
2020-02-06 22:56:40 +00:00
|
|
|
const fd = result catch |err| switch (err) {
|
2019-10-21 01:48:23 +00:00
|
|
|
error.FileTooBig => unreachable, // can't happen for directories
|
|
|
|
error.IsDir => unreachable, // we're providing O_DIRECTORY
|
|
|
|
error.NoSpaceLeft => unreachable, // not providing O_CREAT
|
|
|
|
error.PathAlreadyExists => unreachable, // not providing O_CREAT
|
2020-04-03 05:39:25 +00:00
|
|
|
error.FileLocksNotSupported => unreachable, // locking folders is not supported
|
2019-10-21 01:48:23 +00:00
|
|
|
else => |e| return e,
|
|
|
|
};
|
|
|
|
return Dir{ .fd = fd };
|
|
|
|
}
|
2019-05-24 22:27:18 +00:00
|
|
|
|
2020-07-19 20:25:00 +00:00
|
|
|
fn openDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u32, no_follow: bool) OpenError!Dir {
|
2019-10-21 18:18:01 +00:00
|
|
|
const w = os.windows;
|
2019-10-21 23:17:33 +00:00
|
|
|
|
2019-10-21 18:18:01 +00:00
|
|
|
var result = Dir{
|
|
|
|
.fd = undefined,
|
|
|
|
};
|
2019-10-21 23:17:33 +00:00
|
|
|
|
2020-03-30 18:23:22 +00:00
|
|
|
const path_len_bytes = @intCast(u16, mem.lenZ(sub_path_w) * 2);
|
2019-10-21 18:18:01 +00:00
|
|
|
var nt_name = w.UNICODE_STRING{
|
|
|
|
.Length = path_len_bytes,
|
|
|
|
.MaximumLength = path_len_bytes,
|
|
|
|
.Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)),
|
|
|
|
};
|
|
|
|
var attr = w.OBJECT_ATTRIBUTES{
|
|
|
|
.Length = @sizeOf(w.OBJECT_ATTRIBUTES),
|
2020-02-05 19:39:14 +00:00
|
|
|
.RootDirectory = if (path.isAbsoluteWindowsW(sub_path_w)) null else self.fd,
|
2019-10-21 18:18:01 +00:00
|
|
|
.Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
|
|
|
|
.ObjectName = &nt_name,
|
|
|
|
.SecurityDescriptor = null,
|
|
|
|
.SecurityQualityOfService = null,
|
|
|
|
};
|
2019-10-21 23:17:33 +00:00
|
|
|
if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
|
|
|
|
// Windows does not recognize this, but it does work with empty string.
|
|
|
|
nt_name.Length = 0;
|
|
|
|
}
|
2019-10-22 00:16:43 +00:00
|
|
|
if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
|
|
|
|
// If you're looking to contribute to zig and fix this, see here for an example of how to
|
|
|
|
// implement this: https://git.midipix.org/ntapi/tree/src/fs/ntapi_tt_open_physical_parent_directory.c
|
|
|
|
@panic("TODO opening '..' with a relative directory handle is not yet implemented on Windows");
|
|
|
|
}
|
2020-07-19 20:25:00 +00:00
|
|
|
const open_reparse_point: w.DWORD = if (no_follow) w.FILE_OPEN_REPARSE_POINT else 0x0;
|
2019-10-21 18:18:01 +00:00
|
|
|
var io: w.IO_STATUS_BLOCK = undefined;
|
|
|
|
const rc = w.ntdll.NtCreateFile(
|
|
|
|
&result.fd,
|
2019-11-22 22:25:43 +00:00
|
|
|
access_mask,
|
2019-10-21 18:18:01 +00:00
|
|
|
&attr,
|
|
|
|
&io,
|
|
|
|
null,
|
|
|
|
0,
|
|
|
|
w.FILE_SHARE_READ | w.FILE_SHARE_WRITE,
|
|
|
|
w.FILE_OPEN,
|
2020-07-19 20:25:00 +00:00
|
|
|
w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT | open_reparse_point,
|
2019-10-21 18:18:01 +00:00
|
|
|
null,
|
|
|
|
0,
|
|
|
|
);
|
|
|
|
switch (rc) {
|
2020-01-31 08:47:00 +00:00
|
|
|
.SUCCESS => return result,
|
|
|
|
.OBJECT_NAME_INVALID => unreachable,
|
|
|
|
.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
|
|
|
|
.OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
|
2020-06-24 00:36:28 +00:00
|
|
|
.NOT_A_DIRECTORY => return error.NotDir,
|
2020-01-31 08:47:00 +00:00
|
|
|
.INVALID_PARAMETER => unreachable,
|
2019-10-21 18:18:01 +00:00
|
|
|
else => return w.unexpectedStatus(rc),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-30 16:18:25 +00:00
|
|
|
pub const DeleteFileError = os.UnlinkError;
|
2019-05-24 22:27:18 +00:00
|
|
|
|
2019-10-21 01:48:23 +00:00
|
|
|
/// Delete a file name and possibly the file it refers to, based on an open directory handle.
|
2019-11-30 20:14:04 +00:00
|
|
|
/// Asserts that the path parameter has no null bytes.
|
2019-10-21 01:48:23 +00:00
|
|
|
pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void {
|
2020-06-26 23:08:26 +00:00
|
|
|
if (builtin.os.tag == .windows) {
|
|
|
|
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
|
2020-07-30 20:47:30 +00:00
|
|
|
return self.deleteFileW(sub_path_w.span());
|
2020-06-26 23:08:26 +00:00
|
|
|
} else if (builtin.os.tag == .wasi) {
|
|
|
|
os.unlinkatWasi(self.fd, sub_path, 0) catch |err| switch (err) {
|
|
|
|
error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR
|
|
|
|
else => |e| return e,
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
const sub_path_c = try os.toPosixPath(sub_path);
|
|
|
|
return self.deleteFileZ(&sub_path_c);
|
|
|
|
}
|
2019-05-24 22:27:18 +00:00
|
|
|
}
|
|
|
|
|
2020-03-30 18:23:22 +00:00
|
|
|
pub const deleteFileC = @compileError("deprecated: renamed to deleteFileZ");
|
|
|
|
|
2019-10-21 01:48:23 +00:00
|
|
|
/// Same as `deleteFile` except the parameter is null-terminated.
|
2020-03-30 18:23:22 +00:00
|
|
|
pub fn deleteFileZ(self: Dir, sub_path_c: [*:0]const u8) DeleteFileError!void {
|
|
|
|
os.unlinkatZ(self.fd, sub_path_c, 0) catch |err| switch (err) {
|
2019-10-21 01:48:23 +00:00
|
|
|
error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR
|
2020-06-26 22:49:31 +00:00
|
|
|
error.AccessDenied => |e| switch (builtin.os.tag) {
|
|
|
|
// non-Linux POSIX systems return EPERM when trying to delete a directory, so
|
|
|
|
// we need to handle that case specifically and translate the error
|
2020-10-17 15:38:23 +00:00
|
|
|
.macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd => {
|
2020-06-27 00:12:02 +00:00
|
|
|
// Don't follow symlinks to match unlinkat (which acts on symlinks rather than follows them)
|
|
|
|
const fstat = os.fstatatZ(self.fd, sub_path_c, os.AT_SYMLINK_NOFOLLOW) catch return e;
|
2020-06-26 22:49:31 +00:00
|
|
|
const is_dir = fstat.mode & os.S_IFMT == os.S_IFDIR;
|
|
|
|
return if (is_dir) error.IsDir else e;
|
|
|
|
},
|
|
|
|
else => return e,
|
|
|
|
},
|
2019-10-21 01:48:23 +00:00
|
|
|
else => |e| return e,
|
|
|
|
};
|
2019-05-24 22:27:18 +00:00
|
|
|
}
|
|
|
|
|
2019-11-30 20:14:04 +00:00
|
|
|
/// Same as `deleteFile` except the parameter is WTF-16 encoded.
|
2020-07-30 20:47:30 +00:00
|
|
|
pub fn deleteFileW(self: Dir, sub_path_w: []const u16) DeleteFileError!void {
|
2019-11-30 20:14:04 +00:00
|
|
|
os.unlinkatW(self.fd, sub_path_w, 0) catch |err| switch (err) {
|
|
|
|
error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR
|
|
|
|
else => |e| return e,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-10-21 01:48:23 +00:00
|
|
|
pub const DeleteDirError = error{
|
|
|
|
DirNotEmpty,
|
|
|
|
FileNotFound,
|
|
|
|
AccessDenied,
|
|
|
|
FileBusy,
|
|
|
|
FileSystem,
|
|
|
|
SymLinkLoop,
|
|
|
|
NameTooLong,
|
|
|
|
NotDir,
|
|
|
|
SystemResources,
|
|
|
|
ReadOnlyFileSystem,
|
|
|
|
InvalidUtf8,
|
|
|
|
BadPathName,
|
|
|
|
Unexpected,
|
|
|
|
};
|
2019-05-24 22:27:18 +00:00
|
|
|
|
2019-10-21 01:48:23 +00:00
|
|
|
/// Returns `error.DirNotEmpty` if the directory is not empty.
|
|
|
|
/// To delete a directory recursively, see `deleteTree`.
|
2019-11-30 20:14:04 +00:00
|
|
|
/// Asserts that the path parameter has no null bytes.
|
2019-10-21 01:48:23 +00:00
|
|
|
pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void {
|
2020-02-25 06:52:27 +00:00
|
|
|
if (builtin.os.tag == .windows) {
|
2019-10-21 18:18:01 +00:00
|
|
|
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
|
2020-07-30 20:47:30 +00:00
|
|
|
return self.deleteDirW(sub_path_w.span());
|
Add/fix missing WASI functionality to pass libstd tests
This rather large commit adds/fixes missing WASI functionality
in `libstd` needed to pass the `libstd` tests. As such, now by
default tests targeting `wasm32-wasi` target are enabled in
`test/tests.zig` module. However, they can be disabled by passing
the `-Dskip-wasi=true` flag when invoking the `zig build test`
command. When the flag is set to `false`, i.e., when WASI tests are
included, `wasmtime` with `--dir=.` is used as the default testing
command.
Since the majority of `libstd` tests were relying on `fs.cwd()`
call to get current working directory handle wrapped in `Dir`
struct, in order to make the tests WASI-friendly, `fs.cwd()`
call was replaced with `testing.getTestDir()` function which
resolved to either `fs.cwd()` for non-WASI targets, or tries to
fetch the preopen list from the WASI runtime and extract a
preopen for '.' path.
The summary of changes introduced by this commit:
* implement `Dir.makeDir` and `Dir.openDir` targeting WASI
* implement `Dir.deleteFile` and `Dir.deleteDir` targeting WASI
* fix `os.close` and map errors in `unlinkat`
* move WASI-specific `mkdirat` and `unlinkat` from `std.fs.wasi`
to `std.os` module
* implement `lseek_{SET, CUR, END}` targeting WASI
* implement `futimens` targeting WASI
* implement `ftruncate` targeting WASI
* implement `readv`, `writev`, `pread{v}`, `pwrite{v}` targeting WASI
* make sure ANSI escape codes are _not_ used in stderr or stdout
in WASI, as WASI always sanitizes stderr, and sanitizes stdout if
fd is a TTY
* fix specifying WASI rights when opening/creating files/dirs
* tweak `AtomicFile` to be WASI-compatible
* implement `os.renameatWasi` for WASI-compliant `os.renameat` function
* implement sleep() targeting WASI
* fix `process.getEnvMap` targeting WASI
2020-05-05 15:23:49 +00:00
|
|
|
} else if (builtin.os.tag == .wasi) {
|
|
|
|
os.unlinkat(self.fd, sub_path, os.AT_REMOVEDIR) catch |err| switch (err) {
|
|
|
|
error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR
|
|
|
|
else => |e| return e,
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
const sub_path_c = try os.toPosixPath(sub_path);
|
|
|
|
return self.deleteDirZ(&sub_path_c);
|
2019-10-21 18:18:01 +00:00
|
|
|
}
|
2019-10-21 01:48:23 +00:00
|
|
|
}
|
2019-05-24 22:27:18 +00:00
|
|
|
|
2019-10-21 01:48:23 +00:00
|
|
|
/// Same as `deleteDir` except the parameter is null-terminated.
|
2020-03-30 18:23:22 +00:00
|
|
|
pub fn deleteDirZ(self: Dir, sub_path_c: [*:0]const u8) DeleteDirError!void {
|
|
|
|
os.unlinkatZ(self.fd, sub_path_c, os.AT_REMOVEDIR) catch |err| switch (err) {
|
2019-10-21 01:48:23 +00:00
|
|
|
error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR
|
|
|
|
else => |e| return e,
|
|
|
|
};
|
|
|
|
}
|
2019-05-24 22:27:18 +00:00
|
|
|
|
2019-10-21 18:18:01 +00:00
|
|
|
/// Same as `deleteDir` except the parameter is UTF16LE, NT prefixed.
|
|
|
|
/// This function is Windows-only.
|
2020-07-30 20:47:30 +00:00
|
|
|
pub fn deleteDirW(self: Dir, sub_path_w: []const u16) DeleteDirError!void {
|
2019-10-21 18:18:01 +00:00
|
|
|
os.unlinkatW(self.fd, sub_path_w, os.AT_REMOVEDIR) catch |err| switch (err) {
|
|
|
|
error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR
|
|
|
|
else => |e| return e,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-09-17 03:59:45 +00:00
|
|
|
pub const RenameError = os.RenameError;
|
|
|
|
|
|
|
|
/// Change the name or location of a file or directory.
|
|
|
|
/// If new_sub_path already exists, it will be replaced.
|
|
|
|
/// Renaming a file over an existing directory or a directory
|
|
|
|
/// over an existing file will fail with `error.IsDir` or `error.NotDir`
|
|
|
|
pub fn rename(self: Dir, old_sub_path: []const u8, new_sub_path: []const u8) RenameError!void {
|
|
|
|
return os.renameat(self.fd, old_sub_path, self.fd, new_sub_path);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Same as `rename` except the parameters are null-terminated.
|
|
|
|
pub fn renameZ(self: Dir, old_sub_path_z: [*:0]const u8, new_sub_path_z: [*:0]const u8) RenameError!void {
|
|
|
|
return os.renameatZ(self.fd, old_sub_path_z, self.fd, new_sub_path_z);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Same as `rename` except the parameters are UTF16LE, NT prefixed.
|
|
|
|
/// This function is Windows-only.
|
|
|
|
pub fn renameW(self: Dir, old_sub_path_w: []const u16, new_sub_path_w: []const u16) RenameError!void {
|
|
|
|
return os.renameatW(self.fd, old_sub_path_w, self.fd, new_sub_path_w);
|
|
|
|
}
|
|
|
|
|
2020-07-19 21:18:20 +00:00
|
|
|
/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
|
|
|
|
/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
|
|
|
|
/// one; the latter case is known as a dangling link.
|
|
|
|
/// If `sym_link_path` exists, it will not be overwritten.
|
|
|
|
pub fn symLink(
|
|
|
|
self: Dir,
|
|
|
|
target_path: []const u8,
|
|
|
|
sym_link_path: []const u8,
|
|
|
|
flags: SymLinkFlags,
|
|
|
|
) !void {
|
|
|
|
if (builtin.os.tag == .wasi) {
|
2020-07-21 07:26:01 +00:00
|
|
|
return self.symLinkWasi(target_path, sym_link_path, flags);
|
2020-07-19 21:18:20 +00:00
|
|
|
}
|
|
|
|
if (builtin.os.tag == .windows) {
|
2020-07-21 07:26:01 +00:00
|
|
|
const target_path_w = try os.windows.sliceToPrefixedFileW(target_path);
|
|
|
|
const sym_link_path_w = try os.windows.sliceToPrefixedFileW(sym_link_path);
|
|
|
|
return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags);
|
2020-07-19 21:18:20 +00:00
|
|
|
}
|
|
|
|
const target_path_c = try os.toPosixPath(target_path);
|
|
|
|
const sym_link_path_c = try os.toPosixPath(sym_link_path);
|
|
|
|
return self.symLinkZ(&target_path_c, &sym_link_path_c, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// WASI-only. Same as `symLink` except targeting WASI.
|
2020-07-21 07:26:01 +00:00
|
|
|
pub fn symLinkWasi(
|
|
|
|
self: Dir,
|
|
|
|
target_path: []const u8,
|
|
|
|
sym_link_path: []const u8,
|
|
|
|
flags: SymLinkFlags,
|
|
|
|
) !void {
|
2020-07-19 21:18:20 +00:00
|
|
|
return os.symlinkatWasi(target_path, self.fd, sym_link_path);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Same as `symLink`, except the pathname parameters are null-terminated.
|
2020-07-21 07:26:01 +00:00
|
|
|
pub fn symLinkZ(
|
|
|
|
self: Dir,
|
|
|
|
target_path_c: [*:0]const u8,
|
|
|
|
sym_link_path_c: [*:0]const u8,
|
|
|
|
flags: SymLinkFlags,
|
|
|
|
) !void {
|
|
|
|
if (builtin.os.tag == .windows) {
|
|
|
|
const target_path_w = try os.windows.cStrToPrefixedFileW(target_path_c);
|
|
|
|
const sym_link_path_w = try os.windows.cStrToPrefixedFileW(sym_link_path_c);
|
|
|
|
return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags);
|
|
|
|
}
|
2020-07-19 21:18:20 +00:00
|
|
|
return os.symlinkatZ(target_path_c, self.fd, sym_link_path_c);
|
|
|
|
}
|
|
|
|
|
2020-07-21 07:26:01 +00:00
|
|
|
/// Windows-only. Same as `symLink` except the pathname parameters
|
|
|
|
/// are null-terminated, WTF16 encoded.
|
|
|
|
pub fn symLinkW(
|
|
|
|
self: Dir,
|
2020-07-30 15:50:49 +00:00
|
|
|
target_path_w: []const u16,
|
|
|
|
sym_link_path_w: []const u16,
|
2020-07-21 07:26:01 +00:00
|
|
|
flags: SymLinkFlags,
|
|
|
|
) !void {
|
2020-07-30 15:50:49 +00:00
|
|
|
return os.windows.CreateSymbolicLink(self.fd, sym_link_path_w, target_path_w, flags.is_directory);
|
2020-07-21 07:26:01 +00:00
|
|
|
}
|
|
|
|
|
2019-10-21 18:18:01 +00:00
|
|
|
/// Read value of a symbolic link.
|
|
|
|
/// The return value is a slice of `buffer`, from index `0`.
|
2019-11-30 20:14:04 +00:00
|
|
|
/// Asserts that the path parameter has no null bytes.
|
2020-03-27 19:28:48 +00:00
|
|
|
pub fn readLink(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 {
|
2020-07-21 18:40:34 +00:00
|
|
|
if (builtin.os.tag == .wasi) {
|
|
|
|
return self.readLinkWasi(sub_path, buffer);
|
|
|
|
}
|
|
|
|
if (builtin.os.tag == .windows) {
|
2020-07-30 15:50:49 +00:00
|
|
|
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
|
|
|
|
return self.readLinkW(sub_path_w.span(), buffer);
|
2020-07-21 18:40:34 +00:00
|
|
|
}
|
2019-10-21 18:18:01 +00:00
|
|
|
const sub_path_c = try os.toPosixPath(sub_path);
|
2020-03-30 18:23:22 +00:00
|
|
|
return self.readLinkZ(&sub_path_c, buffer);
|
2019-10-21 18:18:01 +00:00
|
|
|
}
|
|
|
|
|
2020-03-30 18:23:22 +00:00
|
|
|
pub const readLinkC = @compileError("deprecated: renamed to readLinkZ");
|
|
|
|
|
2020-07-21 18:40:34 +00:00
|
|
|
/// WASI-only. Same as `readLink` except targeting WASI.
|
|
|
|
pub fn readLinkWasi(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 {
|
|
|
|
return os.readlinkatWasi(self.fd, sub_path, buffer);
|
|
|
|
}
|
|
|
|
|
2019-10-21 18:18:01 +00:00
|
|
|
/// Same as `readLink`, except the `pathname` parameter is null-terminated.
|
2020-03-27 19:28:48 +00:00
|
|
|
pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 {
|
2020-07-21 18:40:34 +00:00
|
|
|
if (builtin.os.tag == .windows) {
|
|
|
|
const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path_c);
|
2020-07-30 15:50:49 +00:00
|
|
|
return self.readLinkW(sub_path_w.span(), buffer);
|
2020-07-21 18:40:34 +00:00
|
|
|
}
|
2020-03-30 18:23:22 +00:00
|
|
|
return os.readlinkatZ(self.fd, sub_path_c, buffer);
|
2019-05-24 22:27:18 +00:00
|
|
|
}
|
|
|
|
|
2020-07-21 18:40:34 +00:00
|
|
|
/// Windows-only. Same as `readLink` except the pathname parameter
|
|
|
|
/// is null-terminated, WTF16 encoded.
|
2020-07-30 15:50:49 +00:00
|
|
|
pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 {
|
|
|
|
return os.windows.ReadLink(self.fd, sub_path_w, buffer);
|
2020-07-21 18:40:34 +00:00
|
|
|
}
|
|
|
|
|
2020-10-09 23:45:39 +00:00
|
|
|
/// Read all of file contents using a preallocated buffer.
|
|
|
|
/// The returned slice has the same pointer as `buffer`. If the length matches `buffer.len`
|
|
|
|
/// the situation is ambiguous. It could either mean that the entire file was read, and
|
|
|
|
/// it exactly fits the buffer, or it could mean the buffer was not big enough for the
|
|
|
|
/// entire file.
|
2020-10-09 20:50:43 +00:00
|
|
|
pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 {
|
|
|
|
var file = try self.openFile(file_path, .{});
|
|
|
|
defer file.close();
|
|
|
|
|
|
|
|
const end_index = try file.readAll(buffer);
|
|
|
|
return buffer[0..end_index];
|
|
|
|
}
|
|
|
|
|
2019-11-21 22:07:29 +00:00
|
|
|
/// On success, caller owns returned buffer.
|
|
|
|
/// If the file is larger than `max_bytes`, returns `error.FileTooBig`.
|
|
|
|
pub fn readFileAlloc(self: Dir, allocator: *mem.Allocator, file_path: []const u8, max_bytes: usize) ![]u8 {
|
2020-09-04 07:28:43 +00:00
|
|
|
return self.readFileAllocOptions(allocator, file_path, max_bytes, null, @alignOf(u8), null);
|
2019-11-21 22:07:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// On success, caller owns returned buffer.
|
|
|
|
/// If the file is larger than `max_bytes`, returns `error.FileTooBig`.
|
2020-09-04 07:28:43 +00:00
|
|
|
/// If `size_hint` is specified the initial buffer size is calculated using
|
|
|
|
/// that value, otherwise the effective file size is used instead.
|
2020-04-20 23:19:35 +00:00
|
|
|
/// Allows specifying alignment and a sentinel value.
|
|
|
|
pub fn readFileAllocOptions(
|
2019-11-21 22:07:29 +00:00
|
|
|
self: Dir,
|
|
|
|
allocator: *mem.Allocator,
|
|
|
|
file_path: []const u8,
|
|
|
|
max_bytes: usize,
|
2020-09-04 07:28:43 +00:00
|
|
|
size_hint: ?usize,
|
2020-04-20 23:19:35 +00:00
|
|
|
comptime alignment: u29,
|
|
|
|
comptime optional_sentinel: ?u8,
|
|
|
|
) !(if (optional_sentinel) |s| [:s]align(alignment) u8 else []align(alignment) u8) {
|
2020-03-30 18:23:22 +00:00
|
|
|
var file = try self.openFile(file_path, .{});
|
2019-11-21 22:07:29 +00:00
|
|
|
defer file.close();
|
|
|
|
|
2020-09-04 10:48:36 +00:00
|
|
|
// If the file size doesn't fit a usize it'll be certainly greater than
|
|
|
|
// `max_bytes`
|
|
|
|
const stat_size = size_hint orelse math.cast(usize, try file.getEndPos()) catch
|
|
|
|
return error.FileTooBig;
|
2020-09-04 07:28:43 +00:00
|
|
|
|
|
|
|
return file.readToEndAllocOptions(allocator, max_bytes, stat_size, alignment, optional_sentinel);
|
2019-11-21 22:07:29 +00:00
|
|
|
}
|
|
|
|
|
2019-10-21 01:48:23 +00:00
|
|
|
pub const DeleteTreeError = error{
|
|
|
|
AccessDenied,
|
|
|
|
FileTooBig,
|
|
|
|
SymLinkLoop,
|
|
|
|
ProcessFdQuotaExceeded,
|
|
|
|
NameTooLong,
|
|
|
|
SystemFdQuotaExceeded,
|
|
|
|
NoDevice,
|
|
|
|
SystemResources,
|
|
|
|
ReadOnlyFileSystem,
|
|
|
|
FileSystem,
|
|
|
|
FileBusy,
|
|
|
|
DeviceBusy,
|
2019-05-24 22:27:18 +00:00
|
|
|
|
2019-10-21 01:48:23 +00:00
|
|
|
/// One of the path components was not a directory.
|
|
|
|
/// This error is unreachable if `sub_path` does not contain a path separator.
|
|
|
|
NotDir,
|
2019-05-24 22:27:18 +00:00
|
|
|
|
2019-10-21 01:48:23 +00:00
|
|
|
/// On Windows, file paths must be valid Unicode.
|
|
|
|
InvalidUtf8,
|
|
|
|
|
|
|
|
/// On Windows, file paths cannot contain these characters:
|
|
|
|
/// '/', '*', '?', '"', '<', '>', '|'
|
|
|
|
BadPathName,
|
|
|
|
} || os.UnexpectedError;
|
|
|
|
|
|
|
|
/// Whether `full_path` describes a symlink, file, or directory, this function
|
|
|
|
/// removes it. If it cannot be removed because it is a non-empty directory,
|
|
|
|
/// this function recursively removes its entries and then tries again.
|
|
|
|
/// This operation is not atomic on most file systems.
|
|
|
|
pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
|
|
|
|
start_over: while (true) {
|
|
|
|
var got_access_denied = false;
|
2020-07-19 20:25:00 +00:00
|
|
|
|
2019-10-21 01:48:23 +00:00
|
|
|
// First, try deleting the item as a file. This way we don't follow sym links.
|
|
|
|
if (self.deleteFile(sub_path)) {
|
|
|
|
return;
|
|
|
|
} else |err| switch (err) {
|
|
|
|
error.FileNotFound => return,
|
|
|
|
error.IsDir => {},
|
|
|
|
error.AccessDenied => got_access_denied = true,
|
|
|
|
|
|
|
|
error.InvalidUtf8,
|
|
|
|
error.SymLinkLoop,
|
|
|
|
error.NameTooLong,
|
|
|
|
error.SystemResources,
|
|
|
|
error.ReadOnlyFileSystem,
|
|
|
|
error.NotDir,
|
|
|
|
error.FileSystem,
|
|
|
|
error.FileBusy,
|
|
|
|
error.BadPathName,
|
|
|
|
error.Unexpected,
|
|
|
|
=> |e| return e,
|
2019-05-24 22:27:18 +00:00
|
|
|
}
|
2020-07-19 20:25:00 +00:00
|
|
|
var dir = self.openDir(sub_path, .{ .iterate = true, .no_follow = true }) catch |err| switch (err) {
|
2019-10-21 01:48:23 +00:00
|
|
|
error.NotDir => {
|
|
|
|
if (got_access_denied) {
|
|
|
|
return error.AccessDenied;
|
|
|
|
}
|
|
|
|
continue :start_over;
|
|
|
|
},
|
|
|
|
error.FileNotFound => {
|
|
|
|
// That's fine, we were trying to remove this directory anyway.
|
|
|
|
continue :start_over;
|
|
|
|
},
|
2019-05-24 22:27:18 +00:00
|
|
|
|
2019-10-21 01:48:23 +00:00
|
|
|
error.AccessDenied,
|
|
|
|
error.SymLinkLoop,
|
|
|
|
error.ProcessFdQuotaExceeded,
|
|
|
|
error.NameTooLong,
|
|
|
|
error.SystemFdQuotaExceeded,
|
|
|
|
error.NoDevice,
|
|
|
|
error.SystemResources,
|
|
|
|
error.Unexpected,
|
|
|
|
error.InvalidUtf8,
|
|
|
|
error.BadPathName,
|
|
|
|
error.DeviceBusy,
|
|
|
|
=> |e| return e,
|
2019-05-24 22:27:18 +00:00
|
|
|
};
|
2019-10-21 01:48:23 +00:00
|
|
|
var cleanup_dir_parent: ?Dir = null;
|
|
|
|
defer if (cleanup_dir_parent) |*d| d.close();
|
|
|
|
|
|
|
|
var cleanup_dir = true;
|
|
|
|
defer if (cleanup_dir) dir.close();
|
|
|
|
|
2020-05-29 20:39:47 +00:00
|
|
|
// Valid use of MAX_PATH_BYTES because dir_name_buf will only
|
2020-03-27 19:28:48 +00:00
|
|
|
// ever store a single path component that was returned from the
|
|
|
|
// filesystem.
|
2019-10-21 01:48:23 +00:00
|
|
|
var dir_name_buf: [MAX_PATH_BYTES]u8 = undefined;
|
|
|
|
var dir_name: []const u8 = sub_path;
|
|
|
|
|
|
|
|
// Here we must avoid recursion, in order to provide O(1) memory guarantee of this function.
|
|
|
|
// Go through each entry and if it is not a directory, delete it. If it is a directory,
|
|
|
|
// open it, and close the original directory. Repeat. Then start the entire operation over.
|
|
|
|
|
|
|
|
scan_dir: while (true) {
|
|
|
|
var dir_it = dir.iterate();
|
|
|
|
while (try dir_it.next()) |entry| {
|
|
|
|
if (dir.deleteFile(entry.name)) {
|
|
|
|
continue;
|
|
|
|
} else |err| switch (err) {
|
|
|
|
error.FileNotFound => continue,
|
|
|
|
|
|
|
|
// Impossible because we do not pass any path separators.
|
|
|
|
error.NotDir => unreachable,
|
|
|
|
|
|
|
|
error.IsDir => {},
|
|
|
|
error.AccessDenied => got_access_denied = true,
|
|
|
|
|
|
|
|
error.InvalidUtf8,
|
|
|
|
error.SymLinkLoop,
|
|
|
|
error.NameTooLong,
|
|
|
|
error.SystemResources,
|
|
|
|
error.ReadOnlyFileSystem,
|
|
|
|
error.FileSystem,
|
|
|
|
error.FileBusy,
|
|
|
|
error.BadPathName,
|
|
|
|
error.Unexpected,
|
|
|
|
=> |e| return e,
|
|
|
|
}
|
|
|
|
|
2020-07-19 20:25:00 +00:00
|
|
|
const new_dir = dir.openDir(entry.name, .{ .iterate = true, .no_follow = true }) catch |err| switch (err) {
|
2019-10-21 01:48:23 +00:00
|
|
|
error.NotDir => {
|
|
|
|
if (got_access_denied) {
|
|
|
|
return error.AccessDenied;
|
|
|
|
}
|
|
|
|
continue :scan_dir;
|
|
|
|
},
|
|
|
|
error.FileNotFound => {
|
|
|
|
// That's fine, we were trying to remove this directory anyway.
|
|
|
|
continue :scan_dir;
|
|
|
|
},
|
|
|
|
|
|
|
|
error.AccessDenied,
|
|
|
|
error.SymLinkLoop,
|
|
|
|
error.ProcessFdQuotaExceeded,
|
|
|
|
error.NameTooLong,
|
|
|
|
error.SystemFdQuotaExceeded,
|
|
|
|
error.NoDevice,
|
|
|
|
error.SystemResources,
|
|
|
|
error.Unexpected,
|
|
|
|
error.InvalidUtf8,
|
|
|
|
error.BadPathName,
|
|
|
|
error.DeviceBusy,
|
|
|
|
=> |e| return e,
|
|
|
|
};
|
|
|
|
if (cleanup_dir_parent) |*d| d.close();
|
|
|
|
cleanup_dir_parent = dir;
|
|
|
|
dir = new_dir;
|
|
|
|
mem.copy(u8, &dir_name_buf, entry.name);
|
|
|
|
dir_name = dir_name_buf[0..entry.name.len];
|
|
|
|
continue :scan_dir;
|
|
|
|
}
|
|
|
|
// Reached the end of the directory entries, which means we successfully deleted all of them.
|
|
|
|
// Now to remove the directory itself.
|
|
|
|
dir.close();
|
|
|
|
cleanup_dir = false;
|
|
|
|
|
|
|
|
if (cleanup_dir_parent) |d| {
|
|
|
|
d.deleteDir(dir_name) catch |err| switch (err) {
|
|
|
|
// These two things can happen due to file system race conditions.
|
|
|
|
error.FileNotFound, error.DirNotEmpty => continue :start_over,
|
|
|
|
else => |e| return e,
|
|
|
|
};
|
|
|
|
continue :start_over;
|
|
|
|
} else {
|
|
|
|
self.deleteDir(sub_path) catch |err| switch (err) {
|
|
|
|
error.FileNotFound => return,
|
|
|
|
error.DirNotEmpty => continue :start_over,
|
|
|
|
else => |e| return e,
|
|
|
|
};
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2019-05-24 22:27:18 +00:00
|
|
|
}
|
|
|
|
}
|
2019-11-30 20:14:04 +00:00
|
|
|
|
|
|
|
/// Writes content to the file system, creating a new file if it does not exist, truncating
|
|
|
|
/// if it already exists.
|
|
|
|
pub fn writeFile(self: Dir, sub_path: []const u8, data: []const u8) !void {
|
|
|
|
var file = try self.createFile(sub_path, .{});
|
|
|
|
defer file.close();
|
2020-03-03 07:03:22 +00:00
|
|
|
try file.writeAll(data);
|
2019-11-30 20:14:04 +00:00
|
|
|
}
|
2020-02-16 18:25:30 +00:00
|
|
|
|
|
|
|
pub const AccessError = os.AccessError;
|
|
|
|
|
|
|
|
/// Test accessing `path`.
|
|
|
|
/// `path` is UTF8-encoded.
|
|
|
|
/// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function.
|
|
|
|
/// For example, instead of testing if a file exists and then opening it, just
|
|
|
|
/// open it and handle the error for file not found.
|
|
|
|
pub fn access(self: Dir, sub_path: []const u8, flags: File.OpenFlags) AccessError!void {
|
2020-02-25 06:52:27 +00:00
|
|
|
if (builtin.os.tag == .windows) {
|
2020-02-16 22:10:43 +00:00
|
|
|
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
|
2020-05-02 05:25:22 +00:00
|
|
|
return self.accessW(sub_path_w.span().ptr, flags);
|
2020-02-16 22:10:43 +00:00
|
|
|
}
|
2020-02-16 18:25:30 +00:00
|
|
|
const path_c = try os.toPosixPath(sub_path);
|
|
|
|
return self.accessZ(&path_c, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Same as `access` except the path parameter is null-terminated.
|
|
|
|
pub fn accessZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) AccessError!void {
|
2020-02-25 06:52:27 +00:00
|
|
|
if (builtin.os.tag == .windows) {
|
2020-02-16 22:10:43 +00:00
|
|
|
const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path);
|
2020-05-02 05:25:22 +00:00
|
|
|
return self.accessW(sub_path_w.span().ptr, flags);
|
2020-02-16 22:10:43 +00:00
|
|
|
}
|
2020-02-16 18:25:30 +00:00
|
|
|
const os_mode = if (flags.write and flags.read)
|
|
|
|
@as(u32, os.R_OK | os.W_OK)
|
|
|
|
else if (flags.write)
|
|
|
|
@as(u32, os.W_OK)
|
|
|
|
else
|
|
|
|
@as(u32, os.F_OK);
|
2020-05-02 03:17:15 +00:00
|
|
|
const result = if (need_async_thread and flags.intended_io_mode != .blocking)
|
2020-03-08 00:11:03 +00:00
|
|
|
std.event.Loop.instance.?.faccessatZ(self.fd, sub_path, os_mode, 0)
|
2020-02-16 18:25:30 +00:00
|
|
|
else
|
|
|
|
os.faccessatZ(self.fd, sub_path, os_mode, 0);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2020-02-16 22:10:43 +00:00
|
|
|
/// Same as `access` except asserts the target OS is Windows and the path parameter is
|
|
|
|
/// * WTF-16 encoded
|
|
|
|
/// * null-terminated
|
|
|
|
/// * NtDll prefixed
|
|
|
|
/// TODO currently this ignores `flags`.
|
|
|
|
pub fn accessW(self: Dir, sub_path_w: [*:0]const u16, flags: File.OpenFlags) AccessError!void {
|
|
|
|
return os.faccessatW(self.fd, sub_path_w, 0, 0);
|
2020-02-16 18:25:30 +00:00
|
|
|
}
|
zig build system: correctly handle multiple output artifacts
Previously the zig build system incorrectly assumed that the only build
artifact was a binary. Now, when you enable the cache, only the output
dir is printed to stdout, and the zig build system iterates over the
files in that directory, copying them to the output directory.
To support this change:
* Add `std.os.renameat`, `std.os.renameatZ`, and `std.os.renameatW`.
* Fix `std.os.linux.renameat` not compiling due to typos.
* Deprecate `std.fs.updateFile` and `std.fs.updateFileMode`.
* Add `std.fs.Dir.updateFile`, which supports using open directory
handles for both the source and destination paths, as well as an
options parameter which allows overriding the mode.
* Update `std.fs.AtomicFile` to support operating based on an open
directory handle. Instead of `std.fs.AtomicFile.init`, use
`std.fs.Dir.atomicFile`.
* `std.fs.AtomicFile` deinit() better handles the situation when the
rename fails but the temporary file still exists, by still
attempting to remove the temporary file.
* `std.fs.Dir.openFileWindows` is moved to `std.os.windows.OpenFileW`.
* `std.os.RenameError` gains the error codes `NoDevice`,
`SharingViolation`, and `PipeBusy` which have been observed from
Windows.
Closes #4733
2020-03-14 01:06:07 +00:00
|
|
|
|
|
|
|
/// Check the file size, mtime, and mode of `source_path` and `dest_path`. If they are equal, does nothing.
|
|
|
|
/// Otherwise, atomically copies `source_path` to `dest_path`. The destination file gains the mtime,
|
|
|
|
/// atime, and mode of the source file so that the next call to `updateFile` will not need a copy.
|
|
|
|
/// Returns the previous status of the file before updating.
|
|
|
|
/// If any of the directories do not exist for dest_path, they are created.
|
|
|
|
pub fn updateFile(
|
|
|
|
source_dir: Dir,
|
|
|
|
source_path: []const u8,
|
|
|
|
dest_dir: Dir,
|
|
|
|
dest_path: []const u8,
|
2020-03-19 18:38:21 +00:00
|
|
|
options: CopyFileOptions,
|
zig build system: correctly handle multiple output artifacts
Previously the zig build system incorrectly assumed that the only build
artifact was a binary. Now, when you enable the cache, only the output
dir is printed to stdout, and the zig build system iterates over the
files in that directory, copying them to the output directory.
To support this change:
* Add `std.os.renameat`, `std.os.renameatZ`, and `std.os.renameatW`.
* Fix `std.os.linux.renameat` not compiling due to typos.
* Deprecate `std.fs.updateFile` and `std.fs.updateFileMode`.
* Add `std.fs.Dir.updateFile`, which supports using open directory
handles for both the source and destination paths, as well as an
options parameter which allows overriding the mode.
* Update `std.fs.AtomicFile` to support operating based on an open
directory handle. Instead of `std.fs.AtomicFile.init`, use
`std.fs.Dir.atomicFile`.
* `std.fs.AtomicFile` deinit() better handles the situation when the
rename fails but the temporary file still exists, by still
attempting to remove the temporary file.
* `std.fs.Dir.openFileWindows` is moved to `std.os.windows.OpenFileW`.
* `std.os.RenameError` gains the error codes `NoDevice`,
`SharingViolation`, and `PipeBusy` which have been observed from
Windows.
Closes #4733
2020-03-14 01:06:07 +00:00
|
|
|
) !PrevStatus {
|
|
|
|
var src_file = try source_dir.openFile(source_path, .{});
|
|
|
|
defer src_file.close();
|
|
|
|
|
|
|
|
const src_stat = try src_file.stat();
|
|
|
|
const actual_mode = options.override_mode orelse src_stat.mode;
|
|
|
|
check_dest_stat: {
|
|
|
|
const dest_stat = blk: {
|
|
|
|
var dest_file = dest_dir.openFile(dest_path, .{}) catch |err| switch (err) {
|
|
|
|
error.FileNotFound => break :check_dest_stat,
|
|
|
|
else => |e| return e,
|
|
|
|
};
|
|
|
|
defer dest_file.close();
|
|
|
|
|
|
|
|
break :blk try dest_file.stat();
|
|
|
|
};
|
|
|
|
|
|
|
|
if (src_stat.size == dest_stat.size and
|
|
|
|
src_stat.mtime == dest_stat.mtime and
|
|
|
|
actual_mode == dest_stat.mode)
|
|
|
|
{
|
|
|
|
return PrevStatus.fresh;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (path.dirname(dest_path)) |dirname| {
|
|
|
|
try dest_dir.makePath(dirname);
|
|
|
|
}
|
|
|
|
|
|
|
|
var atomic_file = try dest_dir.atomicFile(dest_path, .{ .mode = actual_mode });
|
|
|
|
defer atomic_file.deinit();
|
|
|
|
|
|
|
|
try atomic_file.file.writeFileAll(src_file, .{ .in_len = src_stat.size });
|
|
|
|
try atomic_file.file.updateTimes(src_stat.atime, src_stat.mtime);
|
|
|
|
try atomic_file.finish();
|
|
|
|
return PrevStatus.stale;
|
|
|
|
}
|
|
|
|
|
2020-03-19 18:38:21 +00:00
|
|
|
/// Guaranteed to be atomic.
|
|
|
|
/// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and readily available,
|
|
|
|
/// there is a possibility of power loss or application termination leaving temporary files present
|
|
|
|
/// in the same directory as dest_path.
|
|
|
|
pub fn copyFile(
|
|
|
|
source_dir: Dir,
|
|
|
|
source_path: []const u8,
|
|
|
|
dest_dir: Dir,
|
|
|
|
dest_path: []const u8,
|
|
|
|
options: CopyFileOptions,
|
|
|
|
) !void {
|
|
|
|
var in_file = try source_dir.openFile(source_path, .{});
|
|
|
|
defer in_file.close();
|
|
|
|
|
|
|
|
var size: ?u64 = null;
|
|
|
|
const mode = options.override_mode orelse blk: {
|
2020-06-21 00:43:56 +00:00
|
|
|
const st = try in_file.stat();
|
|
|
|
size = st.size;
|
|
|
|
break :blk st.mode;
|
2020-03-19 18:38:21 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
var atomic_file = try dest_dir.atomicFile(dest_path, .{ .mode = mode });
|
|
|
|
defer atomic_file.deinit();
|
|
|
|
|
2020-10-06 07:38:59 +00:00
|
|
|
try copy_file(in_file.handle, atomic_file.file.handle);
|
2020-03-19 18:38:21 +00:00
|
|
|
return atomic_file.finish();
|
|
|
|
}
|
|
|
|
|
zig build system: correctly handle multiple output artifacts
Previously the zig build system incorrectly assumed that the only build
artifact was a binary. Now, when you enable the cache, only the output
dir is printed to stdout, and the zig build system iterates over the
files in that directory, copying them to the output directory.
To support this change:
* Add `std.os.renameat`, `std.os.renameatZ`, and `std.os.renameatW`.
* Fix `std.os.linux.renameat` not compiling due to typos.
* Deprecate `std.fs.updateFile` and `std.fs.updateFileMode`.
* Add `std.fs.Dir.updateFile`, which supports using open directory
handles for both the source and destination paths, as well as an
options parameter which allows overriding the mode.
* Update `std.fs.AtomicFile` to support operating based on an open
directory handle. Instead of `std.fs.AtomicFile.init`, use
`std.fs.Dir.atomicFile`.
* `std.fs.AtomicFile` deinit() better handles the situation when the
rename fails but the temporary file still exists, by still
attempting to remove the temporary file.
* `std.fs.Dir.openFileWindows` is moved to `std.os.windows.OpenFileW`.
* `std.os.RenameError` gains the error codes `NoDevice`,
`SharingViolation`, and `PipeBusy` which have been observed from
Windows.
Closes #4733
2020-03-14 01:06:07 +00:00
|
|
|
pub const AtomicFileOptions = struct {
|
|
|
|
mode: File.Mode = File.default_mode,
|
|
|
|
};
|
|
|
|
|
2020-04-23 03:42:58 +00:00
|
|
|
/// Directly access the `.file` field, and then call `AtomicFile.finish`
|
|
|
|
/// to atomically replace `dest_path` with contents.
|
|
|
|
/// Always call `AtomicFile.deinit` to clean up, regardless of whether `AtomicFile.finish` succeeded.
|
|
|
|
/// `dest_path` must remain valid until `AtomicFile.deinit` is called.
|
zig build system: correctly handle multiple output artifacts
Previously the zig build system incorrectly assumed that the only build
artifact was a binary. Now, when you enable the cache, only the output
dir is printed to stdout, and the zig build system iterates over the
files in that directory, copying them to the output directory.
To support this change:
* Add `std.os.renameat`, `std.os.renameatZ`, and `std.os.renameatW`.
* Fix `std.os.linux.renameat` not compiling due to typos.
* Deprecate `std.fs.updateFile` and `std.fs.updateFileMode`.
* Add `std.fs.Dir.updateFile`, which supports using open directory
handles for both the source and destination paths, as well as an
options parameter which allows overriding the mode.
* Update `std.fs.AtomicFile` to support operating based on an open
directory handle. Instead of `std.fs.AtomicFile.init`, use
`std.fs.Dir.atomicFile`.
* `std.fs.AtomicFile` deinit() better handles the situation when the
rename fails but the temporary file still exists, by still
attempting to remove the temporary file.
* `std.fs.Dir.openFileWindows` is moved to `std.os.windows.OpenFileW`.
* `std.os.RenameError` gains the error codes `NoDevice`,
`SharingViolation`, and `PipeBusy` which have been observed from
Windows.
Closes #4733
2020-03-14 01:06:07 +00:00
|
|
|
pub fn atomicFile(self: Dir, dest_path: []const u8, options: AtomicFileOptions) !AtomicFile {
|
2020-03-26 03:17:41 +00:00
|
|
|
if (path.dirname(dest_path)) |dirname| {
|
|
|
|
const dir = try self.openDir(dirname, .{});
|
2020-03-30 18:23:22 +00:00
|
|
|
return AtomicFile.init(path.basename(dest_path), options.mode, dir, true);
|
2020-03-26 03:17:41 +00:00
|
|
|
} else {
|
2020-03-30 18:23:22 +00:00
|
|
|
return AtomicFile.init(dest_path, options.mode, self, false);
|
2020-03-26 03:17:41 +00:00
|
|
|
}
|
zig build system: correctly handle multiple output artifacts
Previously the zig build system incorrectly assumed that the only build
artifact was a binary. Now, when you enable the cache, only the output
dir is printed to stdout, and the zig build system iterates over the
files in that directory, copying them to the output directory.
To support this change:
* Add `std.os.renameat`, `std.os.renameatZ`, and `std.os.renameatW`.
* Fix `std.os.linux.renameat` not compiling due to typos.
* Deprecate `std.fs.updateFile` and `std.fs.updateFileMode`.
* Add `std.fs.Dir.updateFile`, which supports using open directory
handles for both the source and destination paths, as well as an
options parameter which allows overriding the mode.
* Update `std.fs.AtomicFile` to support operating based on an open
directory handle. Instead of `std.fs.AtomicFile.init`, use
`std.fs.Dir.atomicFile`.
* `std.fs.AtomicFile` deinit() better handles the situation when the
rename fails but the temporary file still exists, by still
attempting to remove the temporary file.
* `std.fs.Dir.openFileWindows` is moved to `std.os.windows.OpenFileW`.
* `std.os.RenameError` gains the error codes `NoDevice`,
`SharingViolation`, and `PipeBusy` which have been observed from
Windows.
Closes #4733
2020-03-14 01:06:07 +00:00
|
|
|
}
|
2020-06-20 23:46:14 +00:00
|
|
|
|
|
|
|
pub const Stat = File.Stat;
|
|
|
|
pub const StatError = File.StatError;
|
|
|
|
|
|
|
|
pub fn stat(self: Dir) StatError!Stat {
|
|
|
|
const file: File = .{
|
|
|
|
.handle = self.fd,
|
|
|
|
.capable_io_mode = .blocking,
|
|
|
|
};
|
|
|
|
return file.stat();
|
|
|
|
}
|
2019-05-24 22:27:18 +00:00
|
|
|
};
|
|
|
|
|
2020-10-05 13:59:43 +00:00
|
|
|
/// Returns a handle to the current working directory. It is not opened with iteration capability.
|
2019-11-30 20:14:04 +00:00
|
|
|
/// Closing the returned `Dir` is checked illegal behavior. Iterating over the result is illegal behavior.
|
|
|
|
/// On POSIX targets, this function is comptime-callable.
|
|
|
|
pub fn cwd() Dir {
|
2020-02-25 06:52:27 +00:00
|
|
|
if (builtin.os.tag == .windows) {
|
2019-11-30 20:14:04 +00:00
|
|
|
return Dir{ .fd = os.windows.peb().ProcessParameters.CurrentDirectory.Handle };
|
2020-05-01 20:27:11 +00:00
|
|
|
} else if (builtin.os.tag == .wasi) {
|
Add/fix missing WASI functionality to pass libstd tests
This rather large commit adds/fixes missing WASI functionality
in `libstd` needed to pass the `libstd` tests. As such, now by
default tests targeting `wasm32-wasi` target are enabled in
`test/tests.zig` module. However, they can be disabled by passing
the `-Dskip-wasi=true` flag when invoking the `zig build test`
command. When the flag is set to `false`, i.e., when WASI tests are
included, `wasmtime` with `--dir=.` is used as the default testing
command.
Since the majority of `libstd` tests were relying on `fs.cwd()`
call to get current working directory handle wrapped in `Dir`
struct, in order to make the tests WASI-friendly, `fs.cwd()`
call was replaced with `testing.getTestDir()` function which
resolved to either `fs.cwd()` for non-WASI targets, or tries to
fetch the preopen list from the WASI runtime and extract a
preopen for '.' path.
The summary of changes introduced by this commit:
* implement `Dir.makeDir` and `Dir.openDir` targeting WASI
* implement `Dir.deleteFile` and `Dir.deleteDir` targeting WASI
* fix `os.close` and map errors in `unlinkat`
* move WASI-specific `mkdirat` and `unlinkat` from `std.fs.wasi`
to `std.os` module
* implement `lseek_{SET, CUR, END}` targeting WASI
* implement `futimens` targeting WASI
* implement `ftruncate` targeting WASI
* implement `readv`, `writev`, `pread{v}`, `pwrite{v}` targeting WASI
* make sure ANSI escape codes are _not_ used in stderr or stdout
in WASI, as WASI always sanitizes stderr, and sanitizes stdout if
fd is a TTY
* fix specifying WASI rights when opening/creating files/dirs
* tweak `AtomicFile` to be WASI-compatible
* implement `os.renameatWasi` for WASI-compliant `os.renameat` function
* implement sleep() targeting WASI
* fix `process.getEnvMap` targeting WASI
2020-05-05 15:23:49 +00:00
|
|
|
@compileError("WASI doesn't have a concept of cwd(); use std.fs.wasi.PreopenList to get available Dir handles instead");
|
2019-11-30 20:14:04 +00:00
|
|
|
} else {
|
|
|
|
return Dir{ .fd = os.AT_FDCWD };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-18 07:42:35 +00:00
|
|
|
/// Opens a directory at the given path. The directory is a system resource that remains
|
|
|
|
/// open until `close` is called on the result.
|
|
|
|
/// See `openDirAbsoluteZ` for a function that accepts a null-terminated path.
|
|
|
|
///
|
|
|
|
/// Asserts that the path parameter has no null bytes.
|
|
|
|
pub fn openDirAbsolute(absolute_path: []const u8, flags: Dir.OpenDirOptions) File.OpenError!Dir {
|
|
|
|
if (builtin.os.tag == .wasi) {
|
|
|
|
@compileError("WASI doesn't have the concept of an absolute directory; use openDir instead for WASI.");
|
|
|
|
}
|
|
|
|
assert(path.isAbsolute(absolute_path));
|
|
|
|
return cwd().openDir(absolute_path, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Same as `openDirAbsolute` but the path parameter is null-terminated.
|
|
|
|
pub fn openDirAbsoluteZ(absolute_path_c: [*:0]const u8, flags: Dir.OpenDirOptions) File.OpenError!Dir {
|
|
|
|
if (builtin.os.tag == .wasi) {
|
|
|
|
@compileError("WASI doesn't have the concept of an absolute directory; use openDir instead for WASI.");
|
|
|
|
}
|
|
|
|
assert(path.isAbsoluteZ(absolute_path_c));
|
|
|
|
return cwd().openDirZ(absolute_path_c, flags);
|
|
|
|
}
|
|
|
|
/// Same as `openDirAbsolute` but the path parameter is null-terminated.
|
|
|
|
pub fn openDirAbsoluteW(absolute_path_c: [*:0]const u16, flags: Dir.OpenDirOptions) File.OpenError!Dir {
|
|
|
|
if (builtin.os.tag == .wasi) {
|
|
|
|
@compileError("WASI doesn't have the concept of an absolute directory; use openDir instead for WASI.");
|
|
|
|
}
|
|
|
|
assert(path.isAbsoluteWindowsW(absolute_path_c));
|
|
|
|
return cwd().openDirW(absolute_path_c, flags);
|
|
|
|
}
|
|
|
|
|
2019-11-30 20:14:04 +00:00
|
|
|
/// Opens a file for reading or writing, without attempting to create a new file, based on an absolute path.
|
|
|
|
/// Call `File.close` to release the resource.
|
|
|
|
/// Asserts that the path is absolute. See `Dir.openFile` for a function that
|
|
|
|
/// operates on both absolute and relative paths.
|
2020-11-18 07:42:35 +00:00
|
|
|
/// Asserts that the path parameter has no null bytes. See `openFileAbsoluteZ` for a function
|
2019-11-30 20:14:04 +00:00
|
|
|
/// that accepts a null-terminated path.
|
|
|
|
pub fn openFileAbsolute(absolute_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
|
|
|
|
assert(path.isAbsolute(absolute_path));
|
|
|
|
return cwd().openFile(absolute_path, flags);
|
|
|
|
}
|
|
|
|
|
2020-03-30 18:23:22 +00:00
|
|
|
pub const openFileAbsoluteC = @compileError("deprecated: renamed to openFileAbsoluteZ");
|
|
|
|
|
2019-11-30 20:14:04 +00:00
|
|
|
/// Same as `openFileAbsolute` but the path parameter is null-terminated.
|
2020-03-30 18:23:22 +00:00
|
|
|
pub fn openFileAbsoluteZ(absolute_path_c: [*:0]const u8, flags: File.OpenFlags) File.OpenError!File {
|
|
|
|
assert(path.isAbsoluteZ(absolute_path_c));
|
2020-03-04 06:05:42 +00:00
|
|
|
return cwd().openFileZ(absolute_path_c, flags);
|
2019-11-30 20:14:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Same as `openFileAbsolute` but the path parameter is WTF-16 encoded.
|
2020-05-02 03:17:15 +00:00
|
|
|
pub fn openFileAbsoluteW(absolute_path_w: []const u16, flags: File.OpenFlags) File.OpenError!File {
|
|
|
|
assert(path.isAbsoluteWindowsWTF16(absolute_path_w));
|
2019-11-30 20:14:04 +00:00
|
|
|
return cwd().openFileW(absolute_path_w, flags);
|
|
|
|
}
|
|
|
|
|
2020-11-18 07:42:35 +00:00
|
|
|
/// Test accessing `path`.
|
|
|
|
/// `path` is UTF8-encoded.
|
|
|
|
/// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function.
|
|
|
|
/// For example, instead of testing if a file exists and then opening it, just
|
|
|
|
/// open it and handle the error for file not found.
|
|
|
|
/// See `accessAbsoluteZ` for a function that accepts a null-terminated path.
|
|
|
|
pub fn accessAbsolute(absolute_path: []const u8, flags: File.OpenFlags) Dir.AccessError!void {
|
|
|
|
if (builtin.os.tag == .wasi) {
|
|
|
|
@compileError("WASI doesn't have the concept of an absolute path; use access instead for WASI.");
|
|
|
|
}
|
|
|
|
assert(path.isAbsolute(absolute_path));
|
|
|
|
try cwd().access(absolute_path, flags);
|
|
|
|
}
|
|
|
|
/// Same as `accessAbsolute` but the path parameter is null-terminated.
|
|
|
|
pub fn accessAbsoluteZ(absolute_path: [*:0]const u8, flags: File.OpenFlags) Dir.AccessError!void {
|
|
|
|
if (builtin.os.tag == .wasi) {
|
|
|
|
@compileError("WASI doesn't have the concept of an absolute path; use access instead for WASI.");
|
|
|
|
}
|
|
|
|
assert(path.isAbsoluteZ(absolute_path));
|
|
|
|
try cwd().accessZ(absolute_path, flags);
|
|
|
|
}
|
|
|
|
/// Same as `accessAbsolute` but the path parameter is WTF-16 encoded.
|
|
|
|
pub fn accessAbsoluteW(absolute_path: [*:0]const 16, flags: File.OpenFlags) Dir.AccessError!void {
|
|
|
|
if (builtin.os.tag == .wasi) {
|
|
|
|
@compileError("WASI doesn't have the concept of an absolute path; use access instead for WASI.");
|
|
|
|
}
|
|
|
|
assert(path.isAbsoluteWindowsW(absolute_path));
|
|
|
|
try cwd().accessW(absolute_path, flags);
|
|
|
|
}
|
|
|
|
|
2019-11-30 20:14:04 +00:00
|
|
|
/// Creates, opens, or overwrites a file with write access, based on an absolute path.
|
|
|
|
/// Call `File.close` to release the resource.
|
|
|
|
/// Asserts that the path is absolute. See `Dir.createFile` for a function that
|
|
|
|
/// operates on both absolute and relative paths.
|
|
|
|
/// Asserts that the path parameter has no null bytes. See `createFileAbsoluteC` for a function
|
|
|
|
/// that accepts a null-terminated path.
|
|
|
|
pub fn createFileAbsolute(absolute_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
|
|
|
|
assert(path.isAbsolute(absolute_path));
|
|
|
|
return cwd().createFile(absolute_path, flags);
|
|
|
|
}
|
|
|
|
|
2020-03-30 18:23:22 +00:00
|
|
|
pub const createFileAbsoluteC = @compileError("deprecated: renamed to createFileAbsoluteZ");
|
|
|
|
|
2019-11-30 20:14:04 +00:00
|
|
|
/// Same as `createFileAbsolute` but the path parameter is null-terminated.
|
2020-03-30 18:23:22 +00:00
|
|
|
pub fn createFileAbsoluteZ(absolute_path_c: [*:0]const u8, flags: File.CreateFlags) File.OpenError!File {
|
|
|
|
assert(path.isAbsoluteZ(absolute_path_c));
|
|
|
|
return cwd().createFileZ(absolute_path_c, flags);
|
2019-11-30 20:14:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Same as `createFileAbsolute` but the path parameter is WTF-16 encoded.
|
|
|
|
pub fn createFileAbsoluteW(absolute_path_w: [*:0]const u16, flags: File.CreateFlags) File.OpenError!File {
|
2020-02-05 19:39:14 +00:00
|
|
|
assert(path.isAbsoluteWindowsW(absolute_path_w));
|
2019-11-30 20:14:04 +00:00
|
|
|
return cwd().createFileW(absolute_path_w, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Delete a file name and possibly the file it refers to, based on an absolute path.
|
|
|
|
/// Asserts that the path is absolute. See `Dir.deleteFile` for a function that
|
|
|
|
/// operates on both absolute and relative paths.
|
|
|
|
/// Asserts that the path parameter has no null bytes.
|
2020-05-02 08:19:07 +00:00
|
|
|
pub fn deleteFileAbsolute(absolute_path: []const u8) Dir.DeleteFileError!void {
|
2019-11-30 20:14:04 +00:00
|
|
|
assert(path.isAbsolute(absolute_path));
|
|
|
|
return cwd().deleteFile(absolute_path);
|
|
|
|
}
|
|
|
|
|
2020-03-30 18:23:22 +00:00
|
|
|
pub const deleteFileAbsoluteC = @compileError("deprecated: renamed to deleteFileAbsoluteZ");
|
|
|
|
|
2019-11-30 20:14:04 +00:00
|
|
|
/// Same as `deleteFileAbsolute` except the parameter is null-terminated.
|
2020-05-02 08:19:07 +00:00
|
|
|
pub fn deleteFileAbsoluteZ(absolute_path_c: [*:0]const u8) Dir.DeleteFileError!void {
|
2020-03-30 18:23:22 +00:00
|
|
|
assert(path.isAbsoluteZ(absolute_path_c));
|
|
|
|
return cwd().deleteFileZ(absolute_path_c);
|
2019-11-30 20:14:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Same as `deleteFileAbsolute` except the parameter is WTF-16 encoded.
|
2020-05-02 08:19:07 +00:00
|
|
|
pub fn deleteFileAbsoluteW(absolute_path_w: [*:0]const u16) Dir.DeleteFileError!void {
|
2020-02-05 19:39:14 +00:00
|
|
|
assert(path.isAbsoluteWindowsW(absolute_path_w));
|
2019-11-30 20:14:04 +00:00
|
|
|
return cwd().deleteFileW(absolute_path_w);
|
|
|
|
}
|
|
|
|
|
2020-03-18 20:09:06 +00:00
|
|
|
/// Removes a symlink, file, or directory.
|
|
|
|
/// This is equivalent to `Dir.deleteTree` with the base directory.
|
|
|
|
/// Asserts that the path is absolute. See `Dir.deleteTree` for a function that
|
|
|
|
/// operates on both absolute and relative paths.
|
|
|
|
/// Asserts that the path parameter has no null bytes.
|
|
|
|
pub fn deleteTreeAbsolute(absolute_path: []const u8) !void {
|
|
|
|
assert(path.isAbsolute(absolute_path));
|
|
|
|
const dirname = path.dirname(absolute_path) orelse return error{
|
|
|
|
/// Attempt to remove the root file system path.
|
|
|
|
/// This error is unreachable if `absolute_path` is relative.
|
|
|
|
CannotDeleteRootDirectory,
|
|
|
|
}.CannotDeleteRootDirectory;
|
|
|
|
|
|
|
|
var dir = try cwd().openDir(dirname, .{});
|
|
|
|
defer dir.close();
|
|
|
|
|
|
|
|
return dir.deleteTree(path.basename(absolute_path));
|
|
|
|
}
|
|
|
|
|
2020-03-30 18:23:22 +00:00
|
|
|
/// Same as `Dir.readLink`, except it asserts the path is absolute.
|
|
|
|
pub fn readLinkAbsolute(pathname: []const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
|
|
|
|
assert(path.isAbsolute(pathname));
|
|
|
|
return os.readlink(pathname, buffer);
|
|
|
|
}
|
|
|
|
|
2020-07-19 09:47:00 +00:00
|
|
|
/// Windows-only. Same as `readlinkW`, except the path parameter is null-terminated, WTF16
|
|
|
|
/// encoded.
|
|
|
|
pub fn readlinkAbsoluteW(pathname_w: [*:0]const u16, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
|
|
|
|
assert(path.isAbsoluteWindowsW(pathname_w));
|
|
|
|
return os.readlinkW(pathname_w, buffer);
|
|
|
|
}
|
|
|
|
|
2020-03-30 18:23:22 +00:00
|
|
|
/// Same as `readLink`, except the path parameter is null-terminated.
|
2020-07-19 09:47:00 +00:00
|
|
|
pub fn readLinkAbsoluteZ(pathname_c: [*:0]const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
|
2020-03-30 18:23:22 +00:00
|
|
|
assert(path.isAbsoluteZ(pathname_c));
|
|
|
|
return os.readlinkZ(pathname_c, buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub const readLink = @compileError("deprecated; use Dir.readLink or readLinkAbsolute");
|
|
|
|
pub const readLinkC = @compileError("deprecated; use Dir.readLinkZ or readLinkAbsoluteZ");
|
|
|
|
|
2020-07-21 07:26:01 +00:00
|
|
|
/// Use with `Dir.symLink` and `symLinkAbsolute` to specify whether the symlink
|
|
|
|
/// will point to a file or a directory. This value is ignored on all hosts
|
|
|
|
/// except Windows where creating symlinks to different resource types, requires
|
|
|
|
/// different flags. By default, `symLinkAbsolute` is assumed to point to a file.
|
2020-07-19 21:18:20 +00:00
|
|
|
pub const SymLinkFlags = struct {
|
2020-07-19 09:47:00 +00:00
|
|
|
is_directory: bool = false,
|
|
|
|
};
|
|
|
|
|
|
|
|
/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
|
|
|
|
/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
|
|
|
|
/// one; the latter case is known as a dangling link.
|
|
|
|
/// If `sym_link_path` exists, it will not be overwritten.
|
|
|
|
/// See also `symLinkAbsoluteZ` and `symLinkAbsoluteW`.
|
2020-07-19 21:18:20 +00:00
|
|
|
pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags: SymLinkFlags) !void {
|
2020-07-19 09:47:00 +00:00
|
|
|
if (builtin.os.tag == .wasi) {
|
2020-07-19 21:18:20 +00:00
|
|
|
@compileError("symLinkAbsolute is not supported in WASI; use Dir.symLinkWasi instead");
|
2020-07-19 09:47:00 +00:00
|
|
|
}
|
|
|
|
assert(path.isAbsolute(target_path));
|
|
|
|
assert(path.isAbsolute(sym_link_path));
|
|
|
|
if (builtin.os.tag == .windows) {
|
2020-07-30 15:50:49 +00:00
|
|
|
const target_path_w = try os.windows.sliceToPrefixedFileW(target_path);
|
|
|
|
const sym_link_path_w = try os.windows.sliceToPrefixedFileW(sym_link_path);
|
|
|
|
return os.windows.CreateSymbolicLink(null, sym_link_path_w.span(), target_path_w.span(), flags.is_directory);
|
2020-07-19 09:47:00 +00:00
|
|
|
}
|
|
|
|
return os.symlink(target_path, sym_link_path);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Windows-only. Same as `symLinkAbsolute` except the parameters are null-terminated, WTF16 encoded.
|
|
|
|
/// Note that this function will by default try creating a symbolic link to a file. If you would
|
2020-07-19 21:18:20 +00:00
|
|
|
/// like to create a symbolic link to a directory, specify this with `SymLinkFlags{ .is_directory = true }`.
|
2020-07-19 09:47:00 +00:00
|
|
|
/// See also `symLinkAbsolute`, `symLinkAbsoluteZ`.
|
2020-07-30 15:50:49 +00:00
|
|
|
pub fn symLinkAbsoluteW(target_path_w: []const u16, sym_link_path_w: []const u16, flags: SymLinkFlags) !void {
|
|
|
|
assert(path.isAbsoluteWindowsWTF16(target_path_w));
|
|
|
|
assert(path.isAbsoluteWindowsWTF16(sym_link_path_w));
|
|
|
|
return os.windows.CreateSymbolicLink(null, sym_link_path_w, target_path_w, flags.is_directory);
|
2020-07-19 09:47:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Same as `symLinkAbsolute` except the parameters are null-terminated pointers.
|
|
|
|
/// See also `symLinkAbsolute`.
|
2020-07-19 21:18:20 +00:00
|
|
|
pub fn symLinkAbsoluteZ(target_path_c: [*:0]const u8, sym_link_path_c: [*:0]const u8, flags: SymLinkFlags) !void {
|
2020-07-19 09:47:00 +00:00
|
|
|
assert(path.isAbsoluteZ(target_path_c));
|
|
|
|
assert(path.isAbsoluteZ(sym_link_path_c));
|
|
|
|
if (builtin.os.tag == .windows) {
|
|
|
|
const target_path_w = try os.windows.cStrToWin32PrefixedFileW(target_path_c);
|
|
|
|
const sym_link_path_w = try os.windows.cStrToWin32PrefixedFileW(sym_link_path_c);
|
2020-07-30 15:50:49 +00:00
|
|
|
return os.windows.CreateSymbolicLink(sym_link_path_w.span(), target_path_w.span(), flags.is_directory);
|
2020-07-19 09:47:00 +00:00
|
|
|
}
|
|
|
|
return os.symlinkZ(target_path_c, sym_link_path_c);
|
|
|
|
}
|
|
|
|
|
2020-07-19 21:18:20 +00:00
|
|
|
pub const symLink = @compileError("deprecated: use Dir.symLink or symLinkAbsolute");
|
|
|
|
pub const symLinkC = @compileError("deprecated: use Dir.symLinkZ or symLinkAbsoluteZ");
|
2020-07-19 09:47:00 +00:00
|
|
|
|
2019-07-14 07:06:20 +00:00
|
|
|
pub const Walker = struct {
|
|
|
|
stack: std.ArrayList(StackItem),
|
2020-04-01 16:44:45 +00:00
|
|
|
name_buffer: std.ArrayList(u8),
|
2019-07-14 07:06:20 +00:00
|
|
|
|
|
|
|
pub const Entry = struct {
|
2019-10-21 01:48:23 +00:00
|
|
|
/// The containing directory. This can be used to operate directly on `basename`
|
|
|
|
/// rather than `path`, avoiding `error.NameTooLong` for deeply nested paths.
|
|
|
|
/// The directory remains open until `next` or `deinit` is called.
|
|
|
|
dir: Dir,
|
2020-04-01 16:44:45 +00:00
|
|
|
/// TODO make this null terminated for API convenience
|
2019-07-14 07:06:20 +00:00
|
|
|
basename: []const u8,
|
2019-10-21 01:48:23 +00:00
|
|
|
|
|
|
|
path: []const u8,
|
2019-07-14 07:06:20 +00:00
|
|
|
kind: Dir.Entry.Kind,
|
|
|
|
};
|
|
|
|
|
|
|
|
const StackItem = struct {
|
2019-10-21 01:48:23 +00:00
|
|
|
dir_it: Dir.Iterator,
|
2019-07-14 07:06:20 +00:00
|
|
|
dirname_len: usize,
|
|
|
|
};
|
|
|
|
|
|
|
|
/// After each call to this function, and on deinit(), the memory returned
|
|
|
|
/// from this function becomes invalid. A copy must be made in order to keep
|
|
|
|
/// a reference to the path.
|
|
|
|
pub fn next(self: *Walker) !?Entry {
|
|
|
|
while (true) {
|
2020-04-01 22:00:42 +00:00
|
|
|
if (self.stack.items.len == 0) return null;
|
2019-07-14 07:06:20 +00:00
|
|
|
// `top` becomes invalid after appending to `self.stack`.
|
2020-04-01 22:00:42 +00:00
|
|
|
const top = &self.stack.span()[self.stack.items.len - 1];
|
2019-07-14 07:06:20 +00:00
|
|
|
const dirname_len = top.dirname_len;
|
|
|
|
if (try top.dir_it.next()) |base| {
|
|
|
|
self.name_buffer.shrink(dirname_len);
|
2020-04-01 16:44:45 +00:00
|
|
|
try self.name_buffer.append(path.sep);
|
|
|
|
try self.name_buffer.appendSlice(base.name);
|
2019-07-14 07:06:20 +00:00
|
|
|
if (base.kind == .Directory) {
|
2020-03-18 18:45:01 +00:00
|
|
|
var new_dir = top.dir_it.dir.openDir(base.name, .{ .iterate = true }) catch |err| switch (err) {
|
2019-10-21 01:48:23 +00:00
|
|
|
error.NameTooLong => unreachable, // no path sep in base.name
|
|
|
|
else => |e| return e,
|
|
|
|
};
|
2019-07-14 07:06:20 +00:00
|
|
|
{
|
|
|
|
errdefer new_dir.close();
|
|
|
|
try self.stack.append(StackItem{
|
2019-10-21 01:48:23 +00:00
|
|
|
.dir_it = new_dir.iterate(),
|
2020-04-01 22:00:42 +00:00
|
|
|
.dirname_len = self.name_buffer.items.len,
|
2019-07-14 07:06:20 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return Entry{
|
2019-10-21 01:48:23 +00:00
|
|
|
.dir = top.dir_it.dir,
|
2020-03-30 18:23:22 +00:00
|
|
|
.basename = self.name_buffer.span()[dirname_len + 1 ..],
|
|
|
|
.path = self.name_buffer.span(),
|
2019-07-14 07:06:20 +00:00
|
|
|
.kind = base.kind,
|
|
|
|
};
|
|
|
|
} else {
|
2019-10-21 01:48:23 +00:00
|
|
|
self.stack.pop().dir_it.dir.close();
|
2019-07-14 07:06:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn deinit(self: *Walker) void {
|
2019-11-09 21:08:53 +00:00
|
|
|
while (self.stack.popOrNull()) |*item| item.dir_it.dir.close();
|
2019-07-14 07:06:20 +00:00
|
|
|
self.stack.deinit();
|
|
|
|
self.name_buffer.deinit();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/// Recursively iterates over a directory.
|
|
|
|
/// Must call `Walker.deinit` when done.
|
|
|
|
/// `dir_path` must not end in a path separator.
|
2019-10-21 01:48:23 +00:00
|
|
|
/// The order of returned file system entries is undefined.
|
2019-07-14 07:06:20 +00:00
|
|
|
pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker {
|
|
|
|
assert(!mem.endsWith(u8, dir_path, path.sep_str));
|
|
|
|
|
2020-03-18 18:45:01 +00:00
|
|
|
var dir = try cwd().openDir(dir_path, .{ .iterate = true });
|
2019-10-21 01:48:23 +00:00
|
|
|
errdefer dir.close();
|
2019-07-14 07:06:20 +00:00
|
|
|
|
2020-04-01 16:44:45 +00:00
|
|
|
var name_buffer = std.ArrayList(u8).init(allocator);
|
2019-07-14 07:06:20 +00:00
|
|
|
errdefer name_buffer.deinit();
|
|
|
|
|
2020-04-01 16:44:45 +00:00
|
|
|
try name_buffer.appendSlice(dir_path);
|
|
|
|
|
2019-07-14 07:06:20 +00:00
|
|
|
var walker = Walker{
|
|
|
|
.stack = std.ArrayList(Walker.StackItem).init(allocator),
|
|
|
|
.name_buffer = name_buffer,
|
|
|
|
};
|
|
|
|
|
|
|
|
try walker.stack.append(Walker.StackItem{
|
2019-10-21 01:48:23 +00:00
|
|
|
.dir_it = dir.iterate(),
|
2019-07-14 07:06:20 +00:00
|
|
|
.dirname_len = dir_path.len,
|
|
|
|
});
|
|
|
|
|
|
|
|
return walker;
|
|
|
|
}
|
|
|
|
|
2020-07-30 20:47:30 +00:00
|
|
|
pub const OpenSelfExeError = error{
|
|
|
|
SharingViolation,
|
|
|
|
PathAlreadyExists,
|
|
|
|
FileNotFound,
|
|
|
|
AccessDenied,
|
|
|
|
PipeBusy,
|
|
|
|
NameTooLong,
|
|
|
|
/// On Windows, file paths must be valid Unicode.
|
|
|
|
InvalidUtf8,
|
|
|
|
/// On Windows, file paths cannot contain these characters:
|
|
|
|
/// '/', '*', '?', '"', '<', '>', '|'
|
|
|
|
BadPathName,
|
|
|
|
Unexpected,
|
|
|
|
} || os.OpenError || SelfExePathError || os.FlockError;
|
2019-05-25 02:52:07 +00:00
|
|
|
|
2020-03-27 20:13:26 +00:00
|
|
|
pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {
|
2020-02-25 06:52:27 +00:00
|
|
|
if (builtin.os.tag == .linux) {
|
2020-03-27 20:13:26 +00:00
|
|
|
return openFileAbsoluteZ("/proc/self/exe", flags);
|
2019-05-25 02:52:07 +00:00
|
|
|
}
|
2020-02-25 06:52:27 +00:00
|
|
|
if (builtin.os.tag == .windows) {
|
2019-11-21 23:51:12 +00:00
|
|
|
const wide_slice = selfExePathW();
|
|
|
|
const prefixed_path_w = try os.windows.wToPrefixedFileW(wide_slice);
|
2020-05-29 20:39:47 +00:00
|
|
|
return cwd().openFileW(prefixed_path_w.span(), flags);
|
2019-05-24 22:27:18 +00:00
|
|
|
}
|
2020-03-27 19:28:48 +00:00
|
|
|
// Use of MAX_PATH_BYTES here is valid as the resulting path is immediately
|
|
|
|
// opened with no modification.
|
2019-05-25 02:52:07 +00:00
|
|
|
var buf: [MAX_PATH_BYTES]u8 = undefined;
|
|
|
|
const self_exe_path = try selfExePath(&buf);
|
|
|
|
buf[self_exe_path.len] = 0;
|
2020-05-29 20:39:47 +00:00
|
|
|
return openFileAbsoluteZ(buf[0..self_exe_path.len :0].ptr, flags);
|
2019-05-24 22:27:18 +00:00
|
|
|
}
|
|
|
|
|
2020-10-02 15:06:29 +00:00
|
|
|
pub const SelfExePathError = os.ReadLinkError || os.SysCtlError || os.RealPathError;
|
2019-05-24 22:27:18 +00:00
|
|
|
|
2020-03-25 22:40:28 +00:00
|
|
|
/// `selfExePath` except allocates the result on the heap.
|
|
|
|
/// Caller owns returned memory.
|
|
|
|
pub fn selfExePathAlloc(allocator: *Allocator) ![]u8 {
|
2020-03-28 05:33:12 +00:00
|
|
|
// Use of MAX_PATH_BYTES here is justified as, at least on one tested Linux
|
|
|
|
// system, readlink will completely fail to return a result larger than
|
|
|
|
// PATH_MAX even if given a sufficiently large buffer. This makes it
|
|
|
|
// fundamentally impossible to get the selfExePath of a program running in
|
|
|
|
// a very deeply nested directory chain in this way.
|
|
|
|
// TODO(#4812): Investigate other systems and whether it is possible to get
|
|
|
|
// this path by trying larger and larger buffers until one succeeds.
|
2020-03-25 22:40:28 +00:00
|
|
|
var buf: [MAX_PATH_BYTES]u8 = undefined;
|
2020-07-04 11:44:28 +00:00
|
|
|
return allocator.dupe(u8, try selfExePath(&buf));
|
2020-03-25 22:40:28 +00:00
|
|
|
}
|
|
|
|
|
2019-05-24 22:27:18 +00:00
|
|
|
/// Get the path to the current executable.
|
|
|
|
/// If you only need the directory, use selfExeDirPath.
|
|
|
|
/// If you only want an open file handle, use openSelfExe.
|
|
|
|
/// This function may return an error if the current executable
|
|
|
|
/// was deleted after spawning.
|
|
|
|
/// Returned value is a slice of out_buffer.
|
|
|
|
///
|
|
|
|
/// On Linux, depends on procfs being mounted. If the currently executing binary has
|
|
|
|
/// been deleted, the file path looks something like `/a/b/c/exe (deleted)`.
|
|
|
|
/// TODO make the return type of this a null terminated pointer
|
2020-03-27 19:28:48 +00:00
|
|
|
pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {
|
2020-05-02 03:17:15 +00:00
|
|
|
if (is_darwin) {
|
2020-10-02 15:06:29 +00:00
|
|
|
// Note that _NSGetExecutablePath() will return "a path" to
|
|
|
|
// the executable not a "real path" to the executable.
|
2020-10-02 17:33:14 +00:00
|
|
|
var symlink_path_buf: [MAX_PATH_BYTES:0]u8 = undefined;
|
2020-10-03 10:03:22 +00:00
|
|
|
var u32_len: u32 = MAX_PATH_BYTES + 1; // include the sentinel
|
2020-10-02 15:06:29 +00:00
|
|
|
const rc = std.c._NSGetExecutablePath(&symlink_path_buf, &u32_len);
|
2019-05-25 02:52:07 +00:00
|
|
|
if (rc != 0) return error.NameTooLong;
|
2020-10-02 15:06:29 +00:00
|
|
|
|
|
|
|
var real_path_buf: [MAX_PATH_BYTES]u8 = undefined;
|
2020-10-02 17:33:14 +00:00
|
|
|
const real_path = try std.os.realpathZ(&symlink_path_buf, &real_path_buf);
|
2020-10-02 15:06:29 +00:00
|
|
|
if (real_path.len > out_buffer.len) return error.NameTooLong;
|
|
|
|
std.mem.copy(u8, out_buffer, real_path);
|
|
|
|
return out_buffer[0..real_path.len];
|
2019-05-25 02:52:07 +00:00
|
|
|
}
|
2020-02-25 06:52:27 +00:00
|
|
|
switch (builtin.os.tag) {
|
2020-03-30 18:23:22 +00:00
|
|
|
.linux => return os.readlinkZ("/proc/self/exe", out_buffer),
|
2019-10-22 22:06:35 +00:00
|
|
|
.freebsd, .dragonfly => {
|
2019-05-25 02:52:07 +00:00
|
|
|
var mib = [4]c_int{ os.CTL_KERN, os.KERN_PROC, os.KERN_PROC_PATHNAME, -1 };
|
2019-05-24 22:27:18 +00:00
|
|
|
var out_len: usize = out_buffer.len;
|
2020-03-27 19:28:48 +00:00
|
|
|
try os.sysctl(&mib, out_buffer.ptr, &out_len, null, 0);
|
2019-05-24 22:27:18 +00:00
|
|
|
// TODO could this slice from 0 to out_len instead?
|
2020-03-30 18:23:22 +00:00
|
|
|
return mem.spanZ(@ptrCast([*:0]u8, out_buffer));
|
2019-05-24 22:27:18 +00:00
|
|
|
},
|
2019-05-25 02:52:07 +00:00
|
|
|
.netbsd => {
|
|
|
|
var mib = [4]c_int{ os.CTL_KERN, os.KERN_PROC_ARGS, -1, os.KERN_PROC_PATHNAME };
|
2019-05-24 22:27:18 +00:00
|
|
|
var out_len: usize = out_buffer.len;
|
2020-03-27 19:28:48 +00:00
|
|
|
try os.sysctl(&mib, out_buffer.ptr, &out_len, null, 0);
|
2019-05-24 22:27:18 +00:00
|
|
|
// TODO could this slice from 0 to out_len instead?
|
2020-03-30 18:23:22 +00:00
|
|
|
return mem.spanZ(@ptrCast([*:0]u8, out_buffer));
|
2019-05-24 22:27:18 +00:00
|
|
|
},
|
2020-10-11 08:23:36 +00:00
|
|
|
.openbsd => {
|
|
|
|
// OpenBSD doesn't support getting the path of a running process, so try to guess it
|
2020-10-18 00:52:09 +00:00
|
|
|
if (os.argv.len == 0)
|
|
|
|
return error.FileNotFound;
|
|
|
|
|
|
|
|
const argv0 = mem.span(os.argv[0]);
|
|
|
|
if (mem.indexOf(u8, argv0, "/") != null) {
|
|
|
|
// argv[0] is a path (relative or absolute): use realpath(3) directly
|
|
|
|
var real_path_buf: [MAX_PATH_BYTES]u8 = undefined;
|
|
|
|
const real_path = try os.realpathZ(os.argv[0], &real_path_buf);
|
|
|
|
if (real_path.len > out_buffer.len)
|
|
|
|
return error.NameTooLong;
|
|
|
|
mem.copy(u8, out_buffer, real_path);
|
|
|
|
return out_buffer[0..real_path.len];
|
|
|
|
} else if (argv0.len != 0) {
|
|
|
|
// argv[0] is not empty (and not a path): search it inside PATH
|
|
|
|
const PATH = std.os.getenvZ("PATH") orelse return error.FileNotFound;
|
|
|
|
var path_it = mem.tokenize(PATH, &[_]u8{path.delimiter});
|
|
|
|
while (path_it.next()) |a_path| {
|
2020-11-18 07:42:35 +00:00
|
|
|
var resolved_path_buf: [MAX_PATH_BYTES - 1:0]u8 = undefined;
|
2020-10-18 00:52:09 +00:00
|
|
|
const resolved_path = std.fmt.bufPrintZ(&resolved_path_buf, "{s}/{s}", .{
|
|
|
|
a_path,
|
|
|
|
os.argv[0],
|
|
|
|
}) catch continue;
|
|
|
|
|
2020-10-11 08:23:36 +00:00
|
|
|
var real_path_buf: [MAX_PATH_BYTES]u8 = undefined;
|
2020-10-18 00:52:09 +00:00
|
|
|
if (os.realpathZ(&resolved_path_buf, &real_path_buf)) |real_path| {
|
|
|
|
// found a file, and hope it is the right file
|
|
|
|
if (real_path.len > out_buffer.len)
|
|
|
|
return error.NameTooLong;
|
|
|
|
mem.copy(u8, out_buffer, real_path);
|
|
|
|
return out_buffer[0..real_path.len];
|
|
|
|
} else |_| continue;
|
2020-10-11 08:23:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return error.FileNotFound;
|
|
|
|
},
|
2019-05-25 02:52:07 +00:00
|
|
|
.windows => {
|
2019-11-21 23:51:12 +00:00
|
|
|
const utf16le_slice = selfExePathW();
|
2019-05-24 22:27:18 +00:00
|
|
|
// Trust that Windows gives us valid UTF-16LE.
|
|
|
|
const end_index = std.unicode.utf16leToUtf8(out_buffer, utf16le_slice) catch unreachable;
|
|
|
|
return out_buffer[0..end_index];
|
|
|
|
},
|
2019-05-25 02:52:07 +00:00
|
|
|
else => @compileError("std.fs.selfExePath not supported for this target"),
|
2019-05-24 22:27:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-21 23:51:12 +00:00
|
|
|
/// The result is UTF16LE-encoded.
|
2019-11-25 05:30:28 +00:00
|
|
|
pub fn selfExePathW() [:0]const u16 {
|
2019-11-21 23:51:12 +00:00
|
|
|
const image_path_name = &os.windows.peb().ProcessParameters.ImagePathName;
|
2020-03-30 18:23:22 +00:00
|
|
|
return mem.spanZ(@ptrCast([*:0]const u16, image_path_name.Buffer));
|
2019-05-25 02:52:07 +00:00
|
|
|
}
|
|
|
|
|
2019-05-24 22:27:18 +00:00
|
|
|
/// `selfExeDirPath` except allocates the result on the heap.
|
|
|
|
/// Caller owns returned memory.
|
2019-05-27 18:32:03 +00:00
|
|
|
pub fn selfExeDirPathAlloc(allocator: *Allocator) ![]u8 {
|
2020-03-28 05:33:12 +00:00
|
|
|
// Use of MAX_PATH_BYTES here is justified as, at least on one tested Linux
|
|
|
|
// system, readlink will completely fail to return a result larger than
|
|
|
|
// PATH_MAX even if given a sufficiently large buffer. This makes it
|
|
|
|
// fundamentally impossible to get the selfExeDirPath of a program running
|
|
|
|
// in a very deeply nested directory chain in this way.
|
|
|
|
// TODO(#4812): Investigate other systems and whether it is possible to get
|
|
|
|
// this path by trying larger and larger buffers until one succeeds.
|
2019-05-24 22:27:18 +00:00
|
|
|
var buf: [MAX_PATH_BYTES]u8 = undefined;
|
2020-07-04 11:44:28 +00:00
|
|
|
return allocator.dupe(u8, try selfExeDirPath(&buf));
|
2019-05-24 22:27:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the directory path that contains the current executable.
|
|
|
|
/// Returned value is a slice of out_buffer.
|
2020-03-27 19:28:48 +00:00
|
|
|
pub fn selfExeDirPath(out_buffer: []u8) SelfExePathError![]const u8 {
|
2019-05-25 02:52:07 +00:00
|
|
|
const self_exe_path = try selfExePath(out_buffer);
|
|
|
|
// Assume that the OS APIs return absolute paths, and therefore dirname
|
|
|
|
// will not return null.
|
|
|
|
return path.dirname(self_exe_path).?;
|
2019-05-24 22:27:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// `realpath`, except caller must free the returned memory.
|
2020-08-12 21:50:00 +00:00
|
|
|
/// See also `Dir.realpath`.
|
2019-05-25 02:52:07 +00:00
|
|
|
pub fn realpathAlloc(allocator: *Allocator, pathname: []const u8) ![]u8 {
|
2020-03-27 19:28:48 +00:00
|
|
|
// Use of MAX_PATH_BYTES here is valid as the realpath function does not
|
|
|
|
// have a variant that takes an arbitrary-size buffer.
|
|
|
|
// TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008
|
|
|
|
// NULL out parameter (GNU's canonicalize_file_name) to handle overelong
|
|
|
|
// paths. musl supports passing NULL but restricts the output to PATH_MAX
|
|
|
|
// anyway.
|
2019-05-24 22:27:18 +00:00
|
|
|
var buf: [MAX_PATH_BYTES]u8 = undefined;
|
2020-07-04 11:44:28 +00:00
|
|
|
return allocator.dupe(u8, try os.realpath(pathname, &buf));
|
2019-05-24 22:27:18 +00:00
|
|
|
}
|
|
|
|
|
2020-10-06 07:38:59 +00:00
|
|
|
const CopyFileError = error{SystemResources} || os.CopyFileRangeError || os.SendFileError;
|
|
|
|
|
2020-10-07 09:13:26 +00:00
|
|
|
// Transfer all the data between two file descriptors in the most efficient way.
|
|
|
|
// The copy starts at offset 0, the initial offsets are preserved.
|
|
|
|
// No metadata is transferred over.
|
2020-10-06 07:38:59 +00:00
|
|
|
fn copy_file(fd_in: os.fd_t, fd_out: os.fd_t) CopyFileError!void {
|
|
|
|
if (comptime std.Target.current.isDarwin()) {
|
|
|
|
const rc = os.system.fcopyfile(fd_in, fd_out, null, os.system.COPYFILE_DATA);
|
2020-10-06 09:57:23 +00:00
|
|
|
switch (os.errno(rc)) {
|
2020-10-06 07:38:59 +00:00
|
|
|
0 => return,
|
2020-10-06 09:57:23 +00:00
|
|
|
os.EINVAL => unreachable,
|
|
|
|
os.ENOMEM => return error.SystemResources,
|
|
|
|
// The source file is not a directory, symbolic link, or regular file.
|
2020-10-06 07:38:59 +00:00
|
|
|
// Try with the fallback path before giving up.
|
2020-10-06 09:57:23 +00:00
|
|
|
os.ENOTSUP => {},
|
|
|
|
else => |err| return os.unexpectedErrno(err),
|
2020-10-06 07:38:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (std.Target.current.os.tag == .linux) {
|
|
|
|
// Try copy_file_range first as that works at the FS level and is the
|
|
|
|
// most efficient method (if available).
|
|
|
|
var offset: u64 = 0;
|
|
|
|
cfr_loop: while (true) {
|
2020-10-06 09:57:23 +00:00
|
|
|
// The kernel checks the u64 value `offset+count` for overflow, use
|
|
|
|
// a 32 bit value so that the syscall won't return EINVAL except for
|
|
|
|
// impossibly large files (> 2^64-1 - 2^32-1).
|
2020-10-06 07:38:59 +00:00
|
|
|
const amt = try os.copy_file_range(fd_in, offset, fd_out, offset, math.maxInt(u32), 0);
|
|
|
|
// Terminate when no data was copied
|
|
|
|
if (amt == 0) break :cfr_loop;
|
|
|
|
offset += amt;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sendfile is a zero-copy mechanism iff the OS supports it, otherwise the
|
|
|
|
// fallback code will copy the contents chunk by chunk.
|
|
|
|
const empty_iovec = [0]os.iovec_const{};
|
|
|
|
var offset: u64 = 0;
|
|
|
|
sendfile_loop: while (true) {
|
|
|
|
const amt = try os.sendfile(fd_out, fd_in, offset, 0, &empty_iovec, &empty_iovec, 0);
|
|
|
|
// Terminate when no data was copied
|
|
|
|
if (amt == 0) break :sendfile_loop;
|
|
|
|
offset += amt;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-24 22:27:18 +00:00
|
|
|
test "" {
|
Add/fix missing WASI functionality to pass libstd tests
This rather large commit adds/fixes missing WASI functionality
in `libstd` needed to pass the `libstd` tests. As such, now by
default tests targeting `wasm32-wasi` target are enabled in
`test/tests.zig` module. However, they can be disabled by passing
the `-Dskip-wasi=true` flag when invoking the `zig build test`
command. When the flag is set to `false`, i.e., when WASI tests are
included, `wasmtime` with `--dir=.` is used as the default testing
command.
Since the majority of `libstd` tests were relying on `fs.cwd()`
call to get current working directory handle wrapped in `Dir`
struct, in order to make the tests WASI-friendly, `fs.cwd()`
call was replaced with `testing.getTestDir()` function which
resolved to either `fs.cwd()` for non-WASI targets, or tries to
fetch the preopen list from the WASI runtime and extract a
preopen for '.' path.
The summary of changes introduced by this commit:
* implement `Dir.makeDir` and `Dir.openDir` targeting WASI
* implement `Dir.deleteFile` and `Dir.deleteDir` targeting WASI
* fix `os.close` and map errors in `unlinkat`
* move WASI-specific `mkdirat` and `unlinkat` from `std.fs.wasi`
to `std.os` module
* implement `lseek_{SET, CUR, END}` targeting WASI
* implement `futimens` targeting WASI
* implement `ftruncate` targeting WASI
* implement `readv`, `writev`, `pread{v}`, `pwrite{v}` targeting WASI
* make sure ANSI escape codes are _not_ used in stderr or stdout
in WASI, as WASI always sanitizes stderr, and sanitizes stdout if
fd is a TTY
* fix specifying WASI rights when opening/creating files/dirs
* tweak `AtomicFile` to be WASI-compatible
* implement `os.renameatWasi` for WASI-compliant `os.renameat` function
* implement sleep() targeting WASI
* fix `process.getEnvMap` targeting WASI
2020-05-05 15:23:49 +00:00
|
|
|
if (builtin.os.tag != .wasi) {
|
|
|
|
_ = makeDirAbsolute;
|
|
|
|
_ = makeDirAbsoluteZ;
|
|
|
|
_ = copyFileAbsolute;
|
|
|
|
_ = updateFileAbsolute;
|
|
|
|
}
|
2020-03-19 18:38:21 +00:00
|
|
|
_ = Dir.copyFile;
|
2020-04-11 21:50:38 +00:00
|
|
|
_ = @import("fs/test.zig");
|
2019-05-24 22:27:18 +00:00
|
|
|
_ = @import("fs/path.zig");
|
|
|
|
_ = @import("fs/file.zig");
|
|
|
|
_ = @import("fs/get_app_data_dir.zig");
|
2020-02-06 22:56:40 +00:00
|
|
|
_ = @import("fs/watch.zig");
|
2019-05-24 22:27:18 +00:00
|
|
|
}
|