Skip to content

Commit

Permalink
feat: add support for O_SYMLINK (#944)
Browse files Browse the repository at this point in the history
  • Loading branch information
uhyo authored Sep 15, 2023
1 parent f6d988d commit 96cbce4
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 3 deletions.
45 changes: 44 additions & 1 deletion src/__tests__/volume.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import { Volume, filenameToSteps, StatWatcher } from '../volume';
import hasBigInt from './hasBigInt';
import { tryGetChild, tryGetChildNode } from './util';
import { genRndStr6 } from '../node/util';
import { constants } from '../constants';

const { O_RDWR, O_SYMLINK } = constants;

describe('volume', () => {
describe('filenameToSteps(filename): string[]', () => {
Expand Down Expand Up @@ -484,13 +487,26 @@ describe('volume', () => {
});
});
describe('.read(fd, buffer, offset, length, position, callback)', () => {
xit('...', () => {});
const vol = new Volume();
const data = 'trololo';
const fileNode = (vol as any).createLink(vol.root, 'text.txt').getNode();
fileNode.setString(data);
vol.symlinkSync('/text.txt', '/link.txt');

it('Attempt to read from a symlink should throw EPERM', () => {
const fd = vol.openSync('/link.txt', O_SYMLINK);
expect(vol.fstatSync(fd).isSymbolicLink()).toBe(true);
const buf = Buffer.alloc(10);
const fn = () => vol.readSync(fd, buf, 0, 10, 0);
expect(fn).toThrowError('EPERM');
});
});
describe('.readFileSync(path[, options])', () => {
const vol = new Volume();
const data = 'trololo';
const fileNode = (vol as any).createLink(vol.root, 'text.txt').getNode();
fileNode.setString(data);

it('Read file at root (/text.txt)', () => {
const buf = vol.readFileSync('/text.txt');
const str = buf.toString();
Expand Down Expand Up @@ -584,6 +600,16 @@ describe('volume', () => {
vol.closeSync(fd);
expect(vol.readFileSync('/overwrite.txt', 'utf8')).toBe('mArmagedon');
});
it('Attempt to write to a symlink should throw EBADF', () => {
const data = 'asdfasdf asdfasdf asdf';
vol.writeFileSync('/file.txt', data);
vol.symlinkSync('/file.txt', '/link.txt');

const fd = vol.openSync('/link.txt', O_SYMLINK | O_RDWR);
expect(vol.fstatSync(fd).isSymbolicLink()).toBe(true);
const fn = () => vol.writeSync(fd, 'hello');
expect(fn).toThrowError('EBADF');
});
});
describe('.write(fd, buffer, offset, length, position, callback)', () => {
it('Simple write to a file descriptor', done => {
Expand Down Expand Up @@ -834,6 +860,8 @@ describe('volume', () => {
const data = '(function(){})();';
dojo.getNode().setString(data);

vol.symlinkSync('/dojo.js', '/link.js');

it('Returns basic file stats', () => {
const fd = vol.openSync('/dojo.js', 'r');
const stats = vol.fstatSync(fd);
Expand All @@ -855,6 +883,21 @@ describe('volume', () => {
expect(() => vol.fstatSync(fd, { bigint: true })).toThrowError();
}
});
it('Returns stats about regular file for fd opened without O_SYMLINK', () => {
const fd = vol.openSync('/link.js', 0);
const stats = vol.fstatSync(fd);
expect(stats).toBeInstanceOf(Stats);
expect(stats.size).toBe(data.length);
expect(stats.isFile()).toBe(true);
expect(stats.isDirectory()).toBe(false);
});
it('Returns stats about symlink itself for fd opened with O_SYMLINK', () => {
const fd = vol.openSync('/link.js', O_SYMLINK);
const stats = vol.fstatSync(fd);
expect(stats.isSymbolicLink()).toBe(true);
expect(stats.isFile()).toBe(false);
expect(stats.size).toBe(0);
});
});
describe('.fstat(fd, callback)', () => {
xit('...', () => {});
Expand Down
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const constants = {
O_NOATIME: 262144,
O_NOFOLLOW: 131072,
O_SYNC: 1052672,
O_SYMLINK: 2097152,
O_DIRECT: 16384,
O_NONBLOCK: 2048,
S_IRWXU: 448,
Expand Down
12 changes: 10 additions & 2 deletions src/volume.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ const {
O_TRUNC,
O_APPEND,
O_DIRECTORY,
O_SYMLINK,
F_OK,
COPYFILE_EXCL,
COPYFILE_FICLONE_FORCE,
Expand Down Expand Up @@ -95,6 +96,7 @@ const kMinPoolSpace = 128;

// ---------------------------------------- Error messages

const EPERM = 'EPERM';
const ENOENT = 'ENOENT';
const EBADF = 'EBADF';
const EINVAL = 'EINVAL';
Expand Down Expand Up @@ -703,7 +705,7 @@ export class Volume implements FsCallbackApi {
const modeNum = modeToNumber(mode);
const fileName = pathToFilename(path);
const flagsNum = flagsToNumber(flags);
return this.openBase(fileName, flagsNum, modeNum);
return this.openBase(fileName, flagsNum, modeNum, !(flagsNum & O_SYMLINK));
}

open(path: PathLike, flags: TFlags, /* ... */ callback: TCallback<number>);
Expand All @@ -722,7 +724,7 @@ export class Volume implements FsCallbackApi {
const fileName = pathToFilename(path);
const flagsNum = flagsToNumber(flags);

this.wrapAsync(this.openBase, [fileName, flagsNum, modeNum], callback);
this.wrapAsync(this.openBase, [fileName, flagsNum, modeNum, !(flagsNum & O_SYMLINK)], callback);
}

private closeFile(file: File) {
Expand Down Expand Up @@ -762,6 +764,9 @@ export class Volume implements FsCallbackApi {
position: number,
): number {
const file = this.getFileByFdOrThrow(fd);
if (file.node.isSymlink()) {
throw createError(EPERM, 'read', file.link.getPath());
}
return file.read(buffer, Number(offset), Number(length), position);
}

Expand Down Expand Up @@ -851,6 +856,9 @@ export class Volume implements FsCallbackApi {

private writeBase(fd: number, buf: Buffer, offset?: number, length?: number, position?: number | null): number {
const file = this.getFileByFdOrThrow(fd, 'write');
if (file.node.isSymlink()) {
throw createError(EBADF, 'write', file.link.getPath());
}
return file.write(buf, offset, length, position);
}

Expand Down

0 comments on commit 96cbce4

Please sign in to comment.