diff options
-rw-r--r-- | src/bundler.zig | 17 | ||||
-rw-r--r-- | src/c.zig | 79 | ||||
-rw-r--r-- | src/fs.zig | 47 |
3 files changed, 131 insertions, 12 deletions
diff --git a/src/bundler.zig b/src/bundler.zig index d1b530c82..2ca075760 100644 --- a/src/bundler.zig +++ b/src/bundler.zig @@ -700,15 +700,13 @@ pub const Bundler = struct { const tmpname = try bundler.fs.tmpname( ".bun", std.mem.span(&tmpname_buf), - std.hash.Wyhash.hash(0, std.mem.span(destination)), + std.hash.Wyhash.hash(@intCast(usize, std.time.milliTimestamp()) % std.math.maxInt(u32), std.mem.span(destination)), ); - var tmpfile = try tmpdir.createFileZ(tmpname, .{ .read = isDebug, .exclusive = true }); + var tmpfile = Fs.FileSystem.RealFS.Tmpfile{}; + try tmpfile.create(&bundler.fs.fs, tmpname); - errdefer { - tmpfile.close(); - tmpdir.deleteFile(std.mem.span(tmpname)) catch {}; - } + errdefer tmpfile.closeAndDelete(tmpname); var generator = try allocator.create(GenerateNodeModuleBundle); var queue = try BunQueue.init(allocator); @@ -722,7 +720,7 @@ pub const Bundler = struct { .estimated_input_lines_of_code = 0, // .resolve_queue = queue, .bundler = bundler, - .tmpfile = tmpfile, + .tmpfile = tmpfile.file(), .dynamic_import_file_size_store = U32Map.init(allocator), .dynamic_import_file_size_store_lock = Lock.init(), @@ -930,8 +928,7 @@ pub const Bundler = struct { // } if (this.log.errors > 0) { - tmpfile.close(); - tmpdir.deleteFile(std.mem.span(tmpname)) catch {}; + tmpfile.closeAndDelete(std.mem.span(tmpname)); // We stop here because if there are errors we don't know if the bundle is valid // This manifests as a crash when sorting through the module list because we may have added files to the bundle which were never actually finished being added. return null; @@ -1112,7 +1109,7 @@ pub const Bundler = struct { // chmod 777 0000010 | 0000100 | 0000001 | 0001000 | 0000040 | 0000004 | 0000002 | 0000400 | 0000200 | 0000020, ); - try std.os.renameatZ(tmpdir.fd, tmpname, top_dir.fd, destination); + try tmpfile.promote(tmpname, top_dir.fd, destination); // Print any errors at the end // try this.log.print(Output.errorWriter()); return javascript_bundle_container; @@ -1,4 +1,5 @@ const std = @import("std"); +const Enviroment = @import("./env.zig"); pub usingnamespace switch (std.Target.current.os.tag) { .macos => @import("./darwin_c.zig"), @@ -16,10 +17,10 @@ const errno = os.errno; const zeroes = mem.zeroes; pub extern "c" fn chmod([*c]const u8, mode_t) c_int; -pub extern "c" fn fchmod(c_int, mode_t) c_int; +pub extern "c" fn fchmod(std.c.fd_t, mode_t) c_int; pub extern "c" fn umask(mode_t) mode_t; pub extern "c" fn fchmodat(c_int, [*c]const u8, mode_t, c_int) c_int; - +pub extern "c" fn fchown(std.c.fd_t, std.c.uid_t, std.c.gid_t) c_int; pub extern "c" fn lstat([*c]const u8, [*c]libc_stat) c_int; pub extern "c" fn lstat64([*c]const u8, [*c]libc_stat) c_int; @@ -89,3 +90,77 @@ pub fn lstat_absolute(path: [:0]const u8) StatError!Stat { .ctime = @as(i128, ctime.tv_sec) * std.time.ns_per_s + ctime.tv_nsec, }; } + +// renameatZ fails when renaming across mount points +// we assume that this is relatively uncommon +pub fn moveFileZ(from_dir: std.os.fd_t, filename: [*:0]const u8, to_dir: std.os.fd_t, destination: [*:0]const u8) !void { + std.os.renameatZ(from_dir, filename, to_dir, destination) catch |err| { + switch (err) { + error.RenameAcrossMountPoints => { + try moveFileZSlow(from_dir, filename, to_dir, destination); + }, + else => { + return err; + }, + } + }; +} + +pub fn moveFileZWithHandle(from_handle: std.os.fd_t, from_dir: std.os.fd_t, filename: [*:0]const u8, to_dir: std.os.fd_t, destination: [*:0]const u8) !void { + std.os.renameatZ(from_dir, filename, to_dir, destination) catch |err| { + switch (err) { + error.RenameAcrossMountPoints => { + try moveFileZSlowWithHandle(from_handle, to_dir, destination); + }, + else => { + return err; + }, + } + }; +} + +// On Linux, this will be fast because sendfile() supports copying between two file descriptors on disk +// macOS & BSDs will be slow because +pub fn moveFileZSlow(from_dir: std.os.fd_t, filename: [*:0]const u8, to_dir: std.os.fd_t, destination: [*:0]const u8) !void { + const in_handle = try std.os.openatZ(from_dir, filename, std.os.O_RDONLY | std.os.O_CLOEXEC, 0600); + try moveFileZSlowWithHandle(in_handle, to_dir, destination); +} + +pub fn moveFileZSlowWithHandle(in_handle: std.os.fd_t, to_dir: std.os.fd_t, destination: [*:0]const u8) !void { + const stat = try std.os.fstat(in_handle); + // delete if exists, don't care if it fails. it may fail due to the file not existing + // delete here because we run into weird truncation issues if we do not + // ftruncate() instead didn't work. + // this is technically racy because it could end up deleting the file without saving + std.os.unlinkatZ(to_dir, destination, 0) catch {}; + const out_handle = try std.os.openatZ(to_dir, destination, std.os.O_WRONLY | std.os.O_CREAT | std.os.O_CLOEXEC, 022); + defer std.os.close(out_handle); + if (comptime Enviroment.isLinux) { + _ = std.os.system.fallocate(out_handle, 0, 0, @intCast(i64, stat.size)); + _ = try std.os.sendfile(out_handle, in_handle, 0, @intCast(usize, stat.size), &[_]std.c.iovec_const{}, &[_]std.c.iovec_const{}, 0); + } else { + if (comptime Enviroment.isMac) { + // if this fails, it doesn't matter + // we only really care about read & write succeeding + preallocate_file( + out_handle, + @intCast(std.os.off_t, 0), + @intCast(std.os.off_t, stat.size), + ) catch {}; + } + + var buf: [8092 * 2]u8 = undefined; + var total_read: usize = 0; + while (true) { + const read = try std.os.pread(in_handle, &buf, total_read); + total_read += read; + if (read == 0) break; + const bytes = buf[0..read]; + const written = try std.os.write(out_handle, bytes); + if (written == 0) break; + } + } + + _ = fchmod(out_handle, stat.mode); + _ = fchown(out_handle, stat.uid, stat.gid); +} diff --git a/src/fs.zig b/src/fs.zig index 8a6d4e5f7..0b28fa9b7 100644 --- a/src/fs.zig +++ b/src/fs.zig @@ -513,6 +513,53 @@ pub const FileSystem = struct { return file; } + pub const Tmpfile = struct { + fd: std.os.fd_t = 0, + dir_fd: std.os.fd_t = 0, + + pub inline fn dir(this: *Tmpfile) std.fs.Dir { + return std.fs.Dir{ + .fd = this.dir_fd, + }; + } + + pub inline fn file(this: *Tmpfile) std.fs.File { + return std.fs.File{ + .handle = this.fd, + }; + } + + pub fn close(this: *Tmpfile) void { + if (this.fd != 0) std.os.close(this.fd); + } + + pub fn create(this: *Tmpfile, rfs: *RealFS, name: [*:0]const u8) !void { + var tmpdir_ = try rfs.openTmpDir(); + + const flags = std.os.O_CREAT | std.os.O_RDWR | std.os.O_CLOEXEC; + this.dir_fd = tmpdir_.fd; + this.fd = try std.os.openatZ(tmpdir_.fd, name, flags, std.os.S_IRWXO); + } + + pub fn promote(this: *Tmpfile, from_name: [*:0]const u8, destination_fd: std.os.fd_t, name: [*:0]const u8) !void { + std.debug.assert(this.fd != 0); + std.debug.assert(this.dir_fd != 0); + + try C.moveFileZWithHandle(this.fd, this.dir_fd, from_name, destination_fd, name); + this.close(); + } + + pub fn closeAndDelete(this: *Tmpfile, name: [*:0]const u8) void { + this.close(); + + if (comptime !Environment.isLinux) { + if (this.dir_fd == 0) return; + + this.dir().deleteFileZ(name) catch {}; + } + } + }; + inline fn _fetchCacheFile(fs: *RealFS, basename: string) !std.fs.File { var parts = [_]string{ "node_modules", ".cache", basename }; var path = fs.parent_fs.join(&parts); |