Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: allow setting custom file types beyond S_IFREG and S_IFDIR #1082

Merged
merged 1 commit into from
Jan 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/__tests__/node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ describe('node.ts', () => {
describe('Node', () => {
const node = new Node(1);
it('properly sets mode with permission respected', () => {
const node = new Node(1, 0o755);
const node = new Node(1, constants.S_IFREG | 0o755);
expect(node.perm).toBe(0o755);
expect(node.mode).toBe(constants.S_IFREG | 0o755);
expect(node.isFile()).toBe(true); // Make sure we still know it's a file
Expand Down Expand Up @@ -72,7 +72,7 @@ describe('node.ts', () => {
});
});
describe('.chmod(perm)', () => {
const node = new Node(1);
const node = new Node(1, constants.S_IFREG | 0o666);
expect(node.perm).toBe(0o666);
expect(node.isFile()).toBe(true);
node.chmod(0o600);
Expand Down
9 changes: 9 additions & 0 deletions src/__tests__/volume.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,15 @@ describe('volume', () => {
done();
});
});
it('Creates a character device at root (/null)', done => {
vol.open('/null', 'w', constants.S_IFCHR | 0o666, (err, fd) => {
expect(err).toBe(null);
expect(vol.root.getChild('null')?.getNode().isCharacterDevice()).toBe(true);
expect(typeof fd).toBe('number');
expect(fd).toBeGreaterThan(0);
done();
});
}, 100);
it('Error on file not found', done => {
vol.open('/non-existing-file.txt', 'r', (err, fd) => {
expect(err).toHaveProperty('code', 'ENOENT');
Expand Down
46 changes: 17 additions & 29 deletions src/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Volume } from './volume';
import { EventEmitter } from 'events';
import Stats from './Stats';

const { S_IFMT, S_IFDIR, S_IFREG, S_IFLNK, O_APPEND } = constants;
const { S_IFMT, S_IFDIR, S_IFREG, S_IFLNK, S_IFCHR, O_APPEND } = constants;
const getuid = (): number => process.getuid?.() ?? 0;
const getgid = (): number => process.getgid?.() ?? 0;

Expand All @@ -29,20 +29,17 @@ export class Node extends EventEmitter {
// data: string = '';
buf: Buffer;

private _perm = 0o666; // Permissions `chmod`, `fchmod`

mode = S_IFREG; // S_IFDIR, S_IFREG, etc.. (file by default?)
mode: number; // S_IFDIR, S_IFREG, etc..

// Number of hard links pointing at this Node.
private _nlink = 1;

// Path to another node, if this is a symlink.
symlink: string;

constructor(ino: number, perm: number = 0o666) {
constructor(ino: number, mode: number = 0o666) {
super();
this._perm = perm;
this.mode |= perm;
this.mode = mode;
this.ino = ino;
}

Expand Down Expand Up @@ -90,13 +87,13 @@ export class Node extends EventEmitter {
return this._mtime;
}

public set perm(perm: number) {
this._perm = perm;
this.ctime = new Date();
public get perm(): number {
return this.mode & ~S_IFMT;
}

public get perm(): number {
return this._perm;
public set perm(perm: number) {
this.mode = (this.mode & S_IFMT) | (perm & ~S_IFMT);
this.ctime = new Date();
}

public set nlink(nlink: number) {
Expand Down Expand Up @@ -135,19 +132,7 @@ export class Node extends EventEmitter {
}

setModeProperty(property: number) {
this.mode = (this.mode & ~S_IFMT) | property;
}

setIsFile() {
this.setModeProperty(S_IFREG);
}

setIsDirectory() {
this.setModeProperty(S_IFDIR);
}

setIsSymlink() {
this.setModeProperty(S_IFLNK);
this.mode = property;
}

isFile() {
Expand All @@ -163,8 +148,12 @@ export class Node extends EventEmitter {
return (this.mode & S_IFMT) === S_IFLNK;
}

isCharacterDevice() {
return (this.mode & S_IFMT) === S_IFCHR;
}

makeSymlink(symlink: string) {
this.mode = S_IFLNK;
this.mode = S_IFLNK | 0o666;
this.symlink = symlink;
}

Expand Down Expand Up @@ -223,8 +212,7 @@ export class Node extends EventEmitter {
}

chmod(perm: number) {
this.perm = perm;
this.mode = (this.mode & ~0o777) | perm;
this.mode = (this.mode & S_IFMT) | (perm & ~S_IFMT);
this.touch();
}

Expand Down Expand Up @@ -376,7 +364,7 @@ export class Link extends EventEmitter {
return this.node;
}

createChild(name: string, node: Node = this.vol.createNode()): Link {
createChild(name: string, node: Node = this.vol.createNode(S_IFREG | 0o666)): Link {
const link = new Link(this.vol, this, name);
link.setNode(node);

Expand Down
27 changes: 16 additions & 11 deletions src/volume.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ export class Volume implements FsCallbackApi, FsSynchronousApi {
this.props = Object.assign({ Node, Link, File }, props);

const root = this.createLink();
root.setNode(this.createNode(true));
root.setNode(this.createNode(constants.S_IFDIR | 0o777));

const self = this; // tslint:disable-line no-this-assignment

Expand Down Expand Up @@ -349,8 +349,8 @@ export class Volume implements FsCallbackApi, FsSynchronousApi {
}

createLink(): Link;
createLink(parent: Link, name: string, isDirectory?: boolean, perm?: number): Link;
createLink(parent?: Link, name?: string, isDirectory: boolean = false, perm?: number): Link {
createLink(parent: Link, name: string, isDirectory?: boolean, mode?: number): Link;
createLink(parent?: Link, name?: string, isDirectory: boolean = false, mode?: number): Link {
if (!parent) {
return new this.props.Link(this, null, '');
}
Expand All @@ -359,7 +359,14 @@ export class Volume implements FsCallbackApi, FsSynchronousApi {
throw new Error('createLink: name cannot be empty');
}

return parent.createChild(name, this.createNode(isDirectory, perm));
// If no explicit permission is provided, use defaults based on type
const finalPerm = mode ?? (isDirectory ? 0o777 : 0o666);
// To prevent making a breaking change, `mode` can also just be a permission number
// and the file type is set based on `isDirectory`
const hasFileType = mode && mode & constants.S_IFMT;
const modeType = hasFileType ? mode & constants.S_IFMT : isDirectory ? constants.S_IFDIR : constants.S_IFREG;
const finalMode = (finalPerm & ~constants.S_IFMT) | modeType;
return parent.createChild(name, this.createNode(finalMode));
}

deleteLink(link: Link): boolean {
Expand Down Expand Up @@ -387,10 +394,8 @@ export class Volume implements FsCallbackApi, FsSynchronousApi {
return typeof releasedFd === 'number' ? releasedFd : Volume.fd--;
}

createNode(isDirectory: boolean = false, perm?: number): Node {
perm ??= isDirectory ? 0o777 : 0o666;
const node = new this.props.Node(this.newInoNumber(), perm);
if (isDirectory) node.setIsDirectory();
createNode(mode: number): Node {
const node = new this.props.Node(this.newInoNumber(), mode);
this.inodes[node.ino] = node;
return node;
}
Expand Down Expand Up @@ -685,7 +690,7 @@ export class Volume implements FsCallbackApi, FsSynchronousApi {
this.openFiles = 0;

this.root = this.createLink();
this.root.setNode(this.createNode(true));
this.root.setNode(this.createNode(constants.S_IFDIR | 0o777));
}

// Legacy interface
Expand Down Expand Up @@ -1796,7 +1801,7 @@ export class Volume implements FsCallbackApi, FsSynchronousApi {
const node = dir.getNode();
if (!node.canWrite() || !node.canExecute()) throw createError(EACCES, 'mkdir', filename);

dir.createChild(name, this.createNode(true, modeNum));
dir.createChild(name, this.createNode(constants.S_IFDIR | modeNum));
}

/**
Expand Down Expand Up @@ -1834,7 +1839,7 @@ export class Volume implements FsCallbackApi, FsSynchronousApi {
}

created = true;
curr = curr.createChild(steps[i], this.createNode(true, modeNum));
curr = curr.createChild(steps[i], this.createNode(constants.S_IFDIR | modeNum));
}
return created ? filename : undefined;
}
Expand Down
Loading