Skip to content

Commit

Permalink
Async file copying on windows (#8649)
Browse files Browse the repository at this point in the history
* rework and simplify file opening in windows

* fix tests

* symlink tests pass

* update error handling

* remove outdated normalization check

* fix mac build

* apply suggested fixes

* fix path tests

* remove debug print

* fix windows compile
  • Loading branch information
gvilums authored Feb 4, 2024
1 parent c75e768 commit 1a695f1
Show file tree
Hide file tree
Showing 9 changed files with 424 additions and 230 deletions.
4 changes: 2 additions & 2 deletions src/StandaloneModuleGraph.zig
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ pub const StandaloneModuleGraph = struct {
Global.exit(1);
};

const file = bun.sys.ntCreateFile(
const file = bun.sys.openFileAtWindows(
bun.invalid_fd,
out,
// access_mask
Expand Down Expand Up @@ -716,7 +716,7 @@ pub const StandaloneModuleGraph = struct {
var nt_path_buf: bun.WPathBuffer = undefined;
const nt_path = bun.strings.addNTPathPrefix(&nt_path_buf, image_path);

return bun.sys.ntCreateFile(
return bun.sys.openFileAtWindows(
bun.invalid_fd,
nt_path,
// access_mask
Expand Down
8 changes: 6 additions & 2 deletions src/bun.js/node/dir_iterator.zig
Original file line number Diff line number Diff line change
Expand Up @@ -284,8 +284,12 @@ pub fn NewIterator(comptime use_windows_ospath: bool) type {

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.sym_link;
const isdir = attrs & w.FILE_ATTRIBUTE_DIRECTORY != 0;
const islink = attrs & w.FILE_ATTRIBUTE_REPARSE_POINT != 0;
// on windows symlinks can be directories, too. We prioritize the
// "sym_link" kind over the "directory" kind
if (islink) break :blk Entry.Kind.sym_link;
if (isdir) break :blk Entry.Kind.directory;
break :blk Entry.Kind.file;
};

Expand Down
227 changes: 147 additions & 80 deletions src/bun.js/node/node_fs.zig

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/cli/run_command.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1377,7 +1377,7 @@ pub const RunCommand = struct {
if (Environment.allow_assert) {
std.debug.assert(std.fs.path.isAbsoluteWindowsWTF16(path_to_use));
}
const handle = (bun.sys.ntCreateFile(
const handle = (bun.sys.openFileAtWindows(
bun.invalid_fd, // absolute path is given
path_to_use,
w.STANDARD_RIGHTS_READ | w.FILE_READ_DATA | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA | w.SYNCHRONIZE,
Expand Down
138 changes: 102 additions & 36 deletions src/resolver/resolve_path.zig
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,15 @@ pub fn normalizeStringGeneric(
) []u8 {
return normalizeStringGenericT(u8, path_, buf, allow_above_root, separator, isSeparator, preserve_trailing_slash);
}

fn separatorAdapter(comptime T: type, func: anytype) fn (T) bool {
return struct {
fn call(char: T) bool {
return func(T, char);
}
}.call;
}

pub fn normalizeStringGenericT(
comptime T: type,
path_: []const T,
Expand All @@ -644,7 +653,41 @@ pub fn normalizeStringGenericT(
comptime isSeparatorT: anytype,
comptime preserve_trailing_slash: bool,
) []T {
const isWindows, const sep_str = comptime .{ separator == std.fs.path.sep_windows, &[_]u8{separator} };
return normalizeStringGenericTZ(T, path_, buf, .{
.allow_above_root = allow_above_root,
.separator = separator,
.isSeparator = separatorAdapter(T, isSeparatorT),
.preserve_trailing_slash = preserve_trailing_slash,
.zero_terminate = false,
.add_nt_prefix = false,
});
}

pub fn NormalizeOptions(comptime T: type) type {
return struct {
allow_above_root: bool = false,
separator: T = std.fs.path.sep,
isSeparator: fn (T) bool = struct {
fn call(char: T) bool {
return if (comptime std.fs.path.sep == std.fs.path.sep_windows)
char == '\\' or char == '/'
else
char == '/';
}
}.call,
preserve_trailing_slash: bool = false,
zero_terminate: bool = false,
add_nt_prefix: bool = false,
};
}

pub fn normalizeStringGenericTZ(
comptime T: type,
path_: []const T,
buf: []T,
comptime options: NormalizeOptions(T),
) if (options.zero_terminate) [:0]T else []T {
const isWindows, const sep_str = comptime .{ options.separator == std.fs.path.sep_windows, &[_]u8{options.separator} };

if (isWindows and bun.Environment.isDebug) {
// this is here to catch a potential mistake by the caller
Expand All @@ -656,64 +699,83 @@ pub fn normalizeStringGenericT(

var buf_i: usize = 0;
var dotdot: usize = 0;
var path_begin: usize = 0;

const volLen, const indexOfThirdUNCSlash = if (isWindows and !allow_above_root)
const volLen, const indexOfThirdUNCSlash = if (isWindows and !options.allow_above_root)
windowsVolumeNameLenT(T, path_)
else
.{ 0, 0 };

if (isWindows and !allow_above_root) {
if (isWindows and !options.allow_above_root) {
if (volLen > 0) {
if (options.add_nt_prefix) {
@memcpy(buf[buf_i .. buf_i + 4], &comptime strings.literalBuf(T, "\\??\\"));
buf_i += 4;
}
if (path_[1] != ':') {
// UNC paths
buf[0..2].* = comptime strings.literalBuf(T, sep_str ++ sep_str);
@memcpy(buf[2 .. indexOfThirdUNCSlash + 1], path_[2 .. indexOfThirdUNCSlash + 1]);
buf[indexOfThirdUNCSlash] = separator;
@memcpy(buf[buf_i .. buf_i + 2], &comptime strings.literalBuf(T, sep_str ++ sep_str));
@memcpy(buf[buf_i + 2 .. buf_i + indexOfThirdUNCSlash + 1], path_[2 .. indexOfThirdUNCSlash + 1]);
buf[buf_i + indexOfThirdUNCSlash] = options.separator;
@memcpy(
buf[indexOfThirdUNCSlash + 1 .. volLen],
buf[buf_i + indexOfThirdUNCSlash + 1 .. buf_i + volLen],
path_[indexOfThirdUNCSlash + 1 .. volLen],
);
buf[volLen] = separator;
buf_i = volLen + 1;
buf[buf_i + volLen] = options.separator;
buf_i += volLen + 1;
path_begin = volLen + 1;

// it is just a volume name
if (buf_i >= path_.len)
return buf[0..buf_i];
if (path_begin >= path_.len) {
if (options.zero_terminate) {
buf[buf_i] = 0;
return buf[0..buf_i :0];
} else {
return buf[0..buf_i];
}
}
} else {
// drive letter
buf[0] = path_[0];
buf[1] = ':';
buf_i = 2;
buf[buf_i] = path_[0];
buf[buf_i + 1] = ':';
buf_i += 2;
dotdot = buf_i;
path_begin = 2;
}
} else if (path_.len > 0 and isSeparatorT(T, path_[0])) {
buf[buf_i] = separator;
} else if (path_.len > 0 and options.isSeparator(path_[0])) {
buf[buf_i] = options.separator;
buf_i += 1;
dotdot = 1;
path_begin = 1;
}
}
if (isWindows and allow_above_root) {
if (isWindows and options.allow_above_root) {
if (path_.len >= 2 and path_[1] == ':') {
buf[0] = path_[0];
buf[1] = ':';
buf_i = 2;
if (options.add_nt_prefix) {
@memcpy(buf[buf_i .. buf_i + 4], &comptime strings.literalBuf(T, "\\??\\"));
buf_i += 4;
}
buf[buf_i] = path_[0];
buf[buf_i + 1] = ':';
buf_i += 2;
dotdot = buf_i;
path_begin = 2;
}
}

var r: usize = 0;
var path, const buf_start = if (isWindows)
.{ path_[buf_i..], buf_i }
.{ path_[path_begin..], buf_i }
else
.{ path_, 0 };

const n = path.len;

if (isWindows and (allow_above_root or volLen > 0)) {
if (isWindows and (options.allow_above_root or volLen > 0)) {
// consume leading slashes on windows
if (r < n and isSeparatorT(T, path[r])) {
if (r < n and options.isSeparator(path[r])) {
r += 1;
buf[buf_i] = separator;
buf[buf_i] = options.separator;
buf_i += 1;
}
}
Expand All @@ -722,26 +784,26 @@ pub fn normalizeStringGenericT(
// empty path element
// or
// . element
if (isSeparatorT(T, path[r])) {
if (options.isSeparator(path[r])) {
r += 1;
continue;
}

if (path[r] == '.' and (r + 1 == n or isSeparatorT(T, path[r + 1]))) {
if (path[r] == '.' and (r + 1 == n or options.isSeparator(path[r + 1]))) {
// skipping two is a windows-specific bugfix
r += 1;
continue;
}

if (@"is .. with type"(T, path[r..]) and (r + 2 == n or isSeparatorT(T, path[r + 2]))) {
if (@"is .. with type"(T, path[r..]) and (r + 2 == n or options.isSeparator(path[r + 2]))) {
r += 2;
// .. element: remove to last separator
if (buf_i > dotdot) {
buf_i -= 1;
while (buf_i > dotdot and !isSeparatorT(T, buf[buf_i])) {
while (buf_i > dotdot and !options.isSeparator(buf[buf_i])) {
buf_i -= 1;
}
} else if (allow_above_root) {
} else if (options.allow_above_root) {
if (buf_i > buf_start) {
buf[buf_i..][0..3].* = comptime strings.literalBuf(T, sep_str ++ "..");
buf_i += 3;
Expand All @@ -757,22 +819,22 @@ pub fn normalizeStringGenericT(

// real path element.
// add slash if needed
if (buf_i != buf_start and !isSeparatorT(T, buf[buf_i - 1])) {
buf[buf_i] = separator;
if (buf_i != buf_start and !options.isSeparator(buf[buf_i - 1])) {
buf[buf_i] = options.separator;
buf_i += 1;
}

const from = r;
while (r < n and !isSeparatorT(T, path[r])) : (r += 1) {}
while (r < n and !options.isSeparator(path[r])) : (r += 1) {}
const count = r - from;
@memcpy(buf[buf_i..][0..count], path[from..][0..count]);
buf_i += count;
}

if (preserve_trailing_slash) {
if (options.preserve_trailing_slash) {
// Was there a trailing slash? Let's keep it.
if (buf_i > 0 and path_[path_.len - 1] == separator and buf[buf_i - 1] != separator) {
buf[buf_i] = separator;
if (buf_i > 0 and path_[path_.len - 1] == options.separator and buf[buf_i - 1] != options.separator) {
buf[buf_i] = options.separator;
buf_i += 1;
}
}
Expand All @@ -784,7 +846,11 @@ pub fn normalizeStringGenericT(
buf_i += 1;
}

const result = buf[0..buf_i];
if (options.zero_terminate) {
buf[buf_i] = 0;
}

const result = if (options.zero_terminate) buf[0..buf_i :0] else buf[0..buf_i];

if (bun.Environment.allow_assert and isWindows) {
std.debug.assert(!strings.hasPrefixComptimeType(T, result, comptime strings.literal(T, "\\:\\")));
Expand Down
Loading

0 comments on commit 1a695f1

Please sign in to comment.