Skip to content

Commit d331965

Browse files
committed
Narrowed rename test error code
Fixed `rename` not throwing `EISDIR`/`ENOTDIR` correctly Change default log level for tests to ERR
1 parent c024950 commit d331965

File tree

5 files changed

+40
-24
lines changed

5 files changed

+40
-24
lines changed

src/backends/store/fs.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -242,9 +242,8 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
242242
: decodeDirListing((await tx.get(newDirNode.data)) ?? _throw(withErrno('ENODATA')));
243243

244244
if (newDirList[_new.base]) {
245-
// If it's a file, delete it, if it's a directory, throw a permissions error.
246245
const existing = new Inode((await tx.get(newDirList[_new.base])) ?? _throw(withErrno('ENOENT')));
247-
if (!existing.toStats().isFile()) throw withErrno('EPERM');
246+
if (!existing.toStats().isFile()) throw withErrno('EISDIR');
248247

249248
await tx.remove(existing.data);
250249
await tx.remove(newDirList[_new.base]);
@@ -288,9 +287,8 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
288287
const newDirList: typeof oldDirList = sameParent ? oldDirList : decodeDirListing(tx.getSync(newDirNode.data) ?? _throw(withErrno('ENODATA')));
289288

290289
if (newDirList[_new.base]) {
291-
// If it's a file, delete it, if it's a directory, throw a permissions error.
292290
const existing = new Inode(tx.getSync(newDirList[_new.base]) ?? _throw(withErrno('ENOENT')));
293-
if (!existing.toStats().isFile()) throw withErrno('EPERM');
291+
if (!existing.toStats().isFile()) throw withErrno('EISDIR');
294292

295293
tx.removeSync(existing.data);
296294
tx.removeSync(newDirList[_new.base]);

src/vfs/promises.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,6 @@ export class FileHandle implements promises.FileHandle {
209209
position: number = this.position
210210
): Promise<{ bytesRead: number; buffer: TBuffer }> {
211211
if (this.closed) throw UV('EBADF', 'read', this.path);
212-
213212
if (this.flag & constants.O_WRONLY) throw UV('EBADF', 'read', this.path);
214213

215214
if (!(this.inode.flags! & InodeFlags.NoAtime)) {
@@ -286,7 +285,8 @@ export class FileHandle implements promises.FileHandle {
286285
if (flag & constants.O_WRONLY) throw UV('EBADF', 'read', this.path);
287286

288287
const { size } = await this.stat();
289-
const { buffer: data } = await this._read(new Uint8Array(size), 0, size, 0);
288+
const data = new Uint8Array(size);
289+
await this._read(data, 0, size, 0);
290290
const buffer = Buffer.from(data);
291291
return options.encoding ? buffer.toString(options.encoding) : buffer;
292292
}
@@ -518,9 +518,18 @@ export async function rename(this: V_Context, oldPath: fs.PathLike, newPath: fs.
518518

519519
if (src.fs !== dst.fs) throw UV('EXDEV', $ex);
520520

521-
const stats = await stat.call(this, dirname(oldPath)).catch(rethrow($ex));
521+
const parent = (await stat.call(this, dirname(oldPath)).catch(rethrow($ex))) as Stats;
522+
const stats = (await stat.call(this, oldPath).catch(rethrow($ex))) as Stats;
523+
const newParent = (await stat.call(this, dirname(newPath)).catch(rethrow($ex))) as Stats;
524+
const newStats = (await stat.call(this, newPath).catch((e: Exception) => {
525+
if (e.code == 'ENOENT') return null;
526+
throw setUVMessage(Object.assign(e, $ex));
527+
})) as Stats;
528+
529+
if (checkAccess && (!parent.hasAccess(constants.R_OK, this) || !newParent.hasAccess(constants.W_OK, this))) throw UV('EACCES', $ex);
522530

523-
if (checkAccess && !stats.hasAccess(constants.W_OK, this)) throw UV('EACCES', $ex);
531+
if (newStats && !isDirectory(stats) && isDirectory(newStats)) throw UV('EISDIR', $ex);
532+
if (newStats && isDirectory(stats) && !isDirectory(newStats)) throw UV('ENOTDIR', $ex);
524533

525534
await src.fs.rename(src.path, dst.path).catch(rethrow($ex));
526535

@@ -618,6 +627,8 @@ async function _open($: V_Context, path: fs.PathLike, opt: OpenOptions): Promise
618627

619628
if (!isDirectory(parentStats)) throw UV('ENOTDIR', 'open', dirname(path));
620629

630+
if (!opt.allowDirectory && mode & constants.S_IFDIR) throw UV('EISDIR', 'open', path);
631+
621632
const { euid: uid, egid: gid } = $?.credentials ?? defaultContext.credentials;
622633

623634
const inode = await fs.createFile(resolved, {
@@ -630,17 +641,12 @@ async function _open($: V_Context, path: fs.PathLike, opt: OpenOptions): Promise
630641
}
631642

632643
if (checkAccess && !hasAccess($, stats, flags.toMode(flag))) throw UV('EACCES', $ex);
633-
634644
if (flag & constants.O_EXCL) throw UV('EEXIST', $ex);
635645

636646
const handle = new FileHandle($, toFD(new SyncHandle($, path, fs, resolved, flag, stats)));
637647

638-
/*
639-
In a previous implementation, we deleted the file and
640-
re-created it. However, this created a race condition if another
641-
asynchronous request was trying to read the file, as the file
642-
would not exist for a small period of time.
643-
*/
648+
if (!opt.allowDirectory && mode & constants.S_IFDIR) throw UV('EISDIR', 'open', path);
649+
644650
if (flag & constants.O_TRUNC) await handle.truncate(0);
645651

646652
return handle;

src/vfs/sync.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,26 @@ export function renameSync(this: V_Context, oldPath: fs.PathLike, newPath: fs.Pa
2929
const oldMount = resolveMount(oldPath, this);
3030
const newMount = resolveMount(newPath, this);
3131

32-
const oldStats = statSync.call<V_Context, Parameters<fs.StatSyncFn>, Stats>(this, dirname(oldPath));
33-
3432
const $ex = { syscall: 'rename', path: oldPath, dest: newPath };
3533

36-
if (checkAccess && !oldStats.hasAccess(constants.W_OK, this)) throw UV('EACCES', $ex);
37-
3834
if (oldMount.fs !== newMount.fs) throw UV('EXDEV', $ex);
3935

36+
const oldStats = statSync.call<V_Context, Parameters<fs.StatSyncFn>, Stats>(this, oldPath);
37+
const oldParent = statSync.call<V_Context, Parameters<fs.StatSyncFn>, Stats>(this, dirname(oldPath));
38+
const newParent = statSync.call<V_Context, Parameters<fs.StatSyncFn>, Stats>(this, dirname(newPath));
39+
let newStats: Stats | undefined;
40+
try {
41+
newStats = statSync.call<V_Context, Parameters<fs.StatSyncFn>, Stats>(this, newPath);
42+
} catch (e: any) {
43+
setUVMessage(Object.assign(e, $ex));
44+
if (e.code != 'ENOENT') throw e;
45+
}
46+
47+
if (checkAccess && (!oldParent.hasAccess(constants.R_OK, this) || !newParent.hasAccess(constants.W_OK, this))) throw UV('EACCES', $ex);
48+
49+
if (newStats && !isDirectory(oldStats) && isDirectory(newStats)) throw UV('EISDIR', $ex);
50+
if (newStats && isDirectory(oldStats) && !isDirectory(newStats)) throw UV('ENOTDIR', $ex);
51+
4052
try {
4153
oldMount.fs.renameSync(oldMount.path, newMount.path);
4254
} catch (e: any) {
@@ -175,10 +187,10 @@ function _openSync(this: V_Context, path: fs.PathLike, opt: OpenOptions): SyncHa
175187

176188
const file = new SyncHandle(this, path, fs, resolved, flag, stats);
177189

178-
if (flag & constants.O_TRUNC) file.truncate(0);
179-
180190
if (!opt.allowDirectory && stats.mode & constants.S_IFDIR) throw UV('EISDIR', 'open', path);
181191

192+
if (flag & constants.O_TRUNC) file.truncate(0);
193+
182194
return file;
183195
}
184196

tests/fs/rename.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ suite('Rename', () => {
7676
await fs.promises.mkdir(dir);
7777
await fs.promises.writeFile(file, 'file contents go here');
7878

79-
assert.rejects(fs.promises.rename(file, dir), { code: /EISDIR|EPERM/ });
80-
assert.throws(() => fs.renameSync(file, dir), { code: /EISDIR|EPERM/ });
79+
assert.rejects(fs.promises.rename(file, dir), { code: 'EISDIR' });
80+
assert.throws(() => fs.renameSync(file, dir), { code: 'EISDIR' });
8181
});
8282

8383
test('rename directory inside itself', async () => {

tests/logs.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { log } from 'kerium';
33
export function setupLogs(prefix) {
44
const { ZENFS_LOG_LEVEL } = process.env;
55

6-
let level = log.Level.CRIT;
6+
let level = log.Level.ERR;
77

88
if (ZENFS_LOG_LEVEL) {
99
const tmp = parseInt(ZENFS_LOG_LEVEL);

0 commit comments

Comments
 (0)