diff options
author | 2022-10-11 00:03:32 -0700 | |
---|---|---|
committer | 2022-10-11 00:03:32 -0700 | |
commit | 40623cf967b6e776281b1f79cc4cd02e5d87f7d9 (patch) | |
tree | dda4d53456121a8f673bd191503df048b987448f | |
parent | d07e4f8bd178b4e7450a6990c51ca3bfa0201127 (diff) | |
download | bun-40623cf967b6e776281b1f79cc4cd02e5d87f7d9.tar.gz bun-40623cf967b6e776281b1f79cc4cd02e5d87f7d9.tar.zst bun-40623cf967b6e776281b1f79cc4cd02e5d87f7d9.zip |
Implement `fs.rm` cross-platformly
-rw-r--r-- | src/bun.js/node/node_fs.zig | 110 | ||||
-rw-r--r-- | test/bun.js/fs.test.js | 32 |
2 files changed, 139 insertions, 3 deletions
diff --git a/src/bun.js/node/node_fs.zig b/src/bun.js/node/node_fs.zig index df79ab72a..ef0f2385b 100644 --- a/src/bun.js/node/node_fs.zig +++ b/src/bun.js/node/node_fs.zig @@ -3658,9 +3658,9 @@ pub const NodeFS = struct { _ = flavor; switch (comptime flavor) { .sync => { - var dest = args.path.sliceZ(&this.sync_error_buf); - if (comptime Environment.isMac) { + var dest = args.path.sliceZ(&this.sync_error_buf); + while (true) { var flags: u32 = 0; if (args.recursive) { @@ -3706,7 +3706,111 @@ pub const NodeFS = struct { return Maybe(Return.Rm).success; } } else if (comptime Environment.isLinux) { - // TODO: + if (args.recursive) { + std.fs.cwd().deleteTree(args.path.slice()) catch |err| { + const errno: std.os.E = switch (err) { + error.InvalidHandle => .BADF, + error.AccessDenied => .PERM, + error.FileTooBig => .FBIG, + error.SymLinkLoop => .LOOP, + error.ProcessFdQuotaExceeded => .NFILE, + error.NameTooLong => .NAMETOOLONG, + error.SystemFdQuotaExceeded => .MFILE, + error.SystemResources => .NOMEM, + error.ReadOnlyFileSystem => .ROFS, + error.FileSystem => .IO, + error.FileBusy => .BUSY, + error.DeviceBusy => .BUSY, + + // One of the path components was not a directory. + // This error is unreachable if `sub_path` does not contain a path separator. + error.NotDir => .NOTDIR, + // On Windows, file paths must be valid Unicode. + error.InvalidUtf8 => .INVAL, + + // On Windows, file paths cannot contain these characters: + // '/', '*', '?', '"', '<', '>', '|' + error.BadPathName => .INVAL, + + else => .FAULT, + }; + if (args.force) { + return Maybe(Return.Rm).success; + } + return Maybe(Return.Rm){ + .err = JSC.Node.Syscall.Error.fromCode(errno, .unlink), + }; + }; + return Maybe(Return.Rm).success; + } + } + + { + var dest = args.path.sliceZ(&this.sync_error_buf); + std.os.unlinkZ(dest) catch |er| { + // empircally, it seems to return AccessDenied when the + // file is actually a directory on macOS. + if (er == error.IsDir or + er == error.NotDir or + er == error.AccessDenied) + { + std.os.rmdirZ(dest) catch |err| { + if (args.force) { + return Maybe(Return.Rm).success; + } + + const code: std.os.E = switch (err) { + error.AccessDenied => .PERM, + error.SymLinkLoop => .LOOP, + error.NameTooLong => .NAMETOOLONG, + error.SystemResources => .NOMEM, + error.ReadOnlyFileSystem => .ROFS, + error.FileBusy => .BUSY, + error.FileNotFound => .NOENT, + error.InvalidUtf8 => .INVAL, + error.BadPathName => .INVAL, + else => .FAULT, + }; + + return .{ + .err = JSC.Node.Syscall.Error.fromCode( + code, + .rmdir, + ), + }; + }; + + return Maybe(Return.Rm).success; + } + + if (args.force) { + return Maybe(Return.Rm).success; + } + + { + const code: std.os.E = switch (er) { + error.AccessDenied => .PERM, + error.SymLinkLoop => .LOOP, + error.NameTooLong => .NAMETOOLONG, + error.SystemResources => .NOMEM, + error.ReadOnlyFileSystem => .ROFS, + error.FileBusy => .BUSY, + error.InvalidUtf8 => .INVAL, + error.BadPathName => .INVAL, + error.FileNotFound => .NOENT, + else => .FAULT, + }; + + return .{ + .err = JSC.Node.Syscall.Error.fromCode( + code, + .unlink, + ), + }; + } + }; + + return Maybe(Return.Rm).success; } }, else => {}, diff --git a/test/bun.js/fs.test.js b/test/bun.js/fs.test.js index 94e6843f5..f2f3e6519 100644 --- a/test/bun.js/fs.test.js +++ b/test/bun.js/fs.test.js @@ -14,7 +14,9 @@ import { statSync, lstatSync, copyFileSync, + rmSync, } from "node:fs"; +import { join } from "node:path"; const Buffer = globalThis.Buffer || Uint8Array; @@ -387,3 +389,33 @@ describe("stat", () => { expect(fileStats.isDirectory()).toBe(true); }); }); + +describe("rm", () => { + it("removes a file", () => { + const path = `/tmp/${Date.now()}.rm.txt`; + writeFileSync(path, "File written successfully", "utf8"); + expect(existsSync(path)).toBe(true); + rmSync(path); + expect(existsSync(path)).toBe(false); + }); + + it("removes a dir", () => { + const path = `/tmp/${Date.now()}.rm.dir`; + try { + mkdirSync(path); + } catch (e) {} + expect(existsSync(path)).toBe(true); + rmSync(path); + expect(existsSync(path)).toBe(false); + }); + + it("removes a dir recursively", () => { + const path = `/tmp/${Date.now()}.rm.dir/foo/bar`; + try { + mkdirSync(path, { recursive: true }); + } catch (e) {} + expect(existsSync(path)).toBe(true); + rmSync(join(path, "../../"), { recursive: true }); + expect(existsSync(path)).toBe(false); + }); +}); |