Skip to content

Commit

Permalink
fix: ensure metadata is updated correctly so that watchFile is corr…
Browse files Browse the repository at this point in the history
…ectly triggered (#891)

* fix: some metadata changes not follow common unix filesystem lead to node.watchFile not trigger

1. nlink default value is 2
2. add subfolder nlink++
3. atime updated
4. ctime updated after uid/gid/atime/mtime/perm/nlink updated

#889

* fix: ignore . and .. from readdir
refactor: add the appropriate modifiers to properties & getters & setters.

Co-authored-by: Gareth Jones <Jones258@Gmail.com>

---------

Co-authored-by: Gareth Jones <Jones258@Gmail.com>
  • Loading branch information
vangie and G-Rath committed Apr 20, 2023
1 parent 8d9e533 commit 8af880e
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 24 deletions.
46 changes: 45 additions & 1 deletion src/__tests__/node.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Node } from '../node';
import { Node, Link } from '../node';
import { constants } from '../constants';

describe('node.ts', () => {
Expand Down Expand Up @@ -60,6 +60,16 @@ describe('node.ts', () => {
node.read(buf, 0, 1, 1);
expect(buf.equals(Buffer.from([2]))).toBe(true);
});
it('updates the atime and ctime', () => {
const node = new Node(1);
const oldAtime = node.atime;
const oldCtime = node.ctime;
node.read(Buffer.alloc(0));
const newAtime = node.atime;
const newCtime = node.ctime;
expect(newAtime).not.toBe(oldAtime);
expect(newCtime).not.toBe(oldCtime);
});
});
describe('.chmod(perm)', () => {
const node = new Node(1);
Expand All @@ -69,5 +79,39 @@ describe('node.ts', () => {
expect(node.perm).toBe(0o600);
expect(node.isFile()).toBe(true);
});
describe.each(['uid', 'gid', 'atime', 'mtime', 'perm', 'nlink'])('when %s changes', field => {
it('updates the property and the ctime', () => {
const node = new Node(1);
const oldCtime = node.ctime;
node[field] = 1;
const newCtime = node.ctime;
expect(newCtime).not.toBe(oldCtime);
expect(node[field]).toBe(1);
});
});
describe('.getString(encoding?)', () => {
it('updates the atime and ctime', () => {
const node = new Node(1);
const oldAtime = node.atime;
const oldCtime = node.ctime;
node.getString();
const newAtime = node.atime;
const newCtime = node.ctime;
expect(newAtime).not.toBe(oldAtime);
expect(newCtime).not.toBe(oldCtime);
});
});
describe('.getBuffer()', () => {
it('updates the atime and ctime', () => {
const node = new Node(1);
const oldAtime = node.atime;
const oldCtime = node.ctime;
node.getBuffer();
const newAtime = node.atime;
const newCtime = node.ctime;
expect(newAtime).not.toBe(oldAtime);
expect(newCtime).not.toBe(oldCtime);
});
});
});
});
9 changes: 9 additions & 0 deletions src/__tests__/volume.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,10 +332,13 @@ describe('volume', () => {
describe('.openSync(path, flags[, mode])', () => {
const vol = new Volume();
it('Create new file at root (/test.txt)', () => {
const oldMtime = vol.root.getNode().mtime;
const fd = vol.openSync('/test.txt', 'w');
const newMtime = vol.root.getNode().mtime;
expect(vol.root.getChild('test.txt')).toBeInstanceOf(Link);
expect(typeof fd).toBe('number');
expect(fd).toBeGreaterThan(0);
expect(oldMtime).not.toBe(newMtime);
});
it('Error on file not found', () => {
try {
Expand Down Expand Up @@ -989,10 +992,16 @@ describe('volume', () => {
describe('.mkdirSync(path[, options])', () => {
it('Create dir at root', () => {
const vol = new Volume();
const oldMtime = vol.root.getNode().mtime;
const oldNlink = vol.root.getNode().nlink;
vol.mkdirSync('/test');
const newMtime = vol.root.getNode().mtime;
const newNlink = vol.root.getNode().nlink;
const child = tryGetChild(vol.root, 'test');
expect(child).toBeInstanceOf(Link);
expect(child.getNode().isDirectory()).toBe(true);
expect(oldMtime).not.toBe(newMtime);
expect(newNlink).toBe(oldNlink + 1);
});
it('Create 2 levels deep folders', () => {
const vol = new Volume();
Expand Down
108 changes: 94 additions & 14 deletions src/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,34 +19,97 @@ export class Node extends EventEmitter {
ino: number;

// User ID and group ID.
uid: number = getuid();
gid: number = getgid();
private _uid: number = getuid();
private _gid: number = getgid();

atime = new Date();
mtime = new Date();
ctime = new Date();
private _atime = new Date();
private _mtime = new Date();
private _ctime = new Date();

// data: string = '';
buf: Buffer;

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

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

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

// Steps to another node, if this node is a symlink.
symlink: string[];

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

public set ctime(ctime: Date) {
this._ctime = ctime;
}

public get ctime(): Date {
return this._ctime;
}

public set uid(uid: number) {
this._uid = uid;
this.ctime = new Date();
}

public get uid(): number {
return this._uid;
}

public set gid(gid: number) {
this._gid = gid;
this.ctime = new Date();
}

public get gid(): number {
return this._gid;
}

public set atime(atime: Date) {
this._atime = atime;
this.ctime = new Date();
}

public get atime(): Date {
return this._atime;
}

public set mtime(mtime: Date) {
this._mtime = mtime;
this.ctime = new Date();
}

public get mtime(): Date {
return this._mtime;
}

public set perm(perm: number) {
this._perm = perm;
this.ctime = new Date();
}

public get perm(): number {
return this._perm;
}

public set nlink(nlink: number) {
this._nlink = nlink;
this.ctime = new Date();
}

public get nlink(): number {
return this._nlink;
}

getString(encoding = 'utf8'): string {
this.atime = new Date();
return this.getBuffer().toString(encoding);
}

Expand All @@ -57,6 +120,7 @@ export class Node extends EventEmitter {
}

getBuffer(): Buffer {
this.atime = new Date();
if (!this.buf) this.setBuffer(bufferAllocUnsafe(0));
return bufferFrom(this.buf); // Return a copy.
}
Expand Down Expand Up @@ -122,6 +186,7 @@ export class Node extends EventEmitter {

// Returns the number of bytes read.
read(buf: Buffer | Uint8Array, off: number = 0, len: number = buf.byteLength, pos: number = 0): number {
this.atime = new Date();
if (!this.buf) this.buf = bufferAllocUnsafe(0);

let actualLen = len;
Expand Down Expand Up @@ -262,8 +327,11 @@ export class Link extends EventEmitter {
// Recursively sync children steps, e.g. in case of dir rename
set steps(val) {
this._steps = val;
for (const child of Object.values(this.children)) {
child?.syncSteps();
for (const [child, link] of Object.entries(this.children)) {
if (child === '.' || child === '..') {
continue;
}
link?.syncSteps();
}
}

Expand All @@ -289,10 +357,8 @@ export class Link extends EventEmitter {
link.setNode(node);

if (node.isDirectory()) {
// link.setChild('.', link);
// link.getNode().nlink++;
// link.setChild('..', this);
// this.getNode().nlink++;
link.children['.'] = link;
link.getNode().nlink++;
}

this.setChild(name, link);
Expand All @@ -305,19 +371,33 @@ export class Link extends EventEmitter {
link.parent = this;
this.length++;

const node = link.getNode();
if (node.isDirectory()) {
link.children['..'] = this;
this.getNode().nlink++;
}

this.getNode().mtime = new Date();
this.emit('child:add', link, this);

return link;
}

deleteChild(link: Link) {
const node = link.getNode();
if (node.isDirectory()) {
delete link.children['..'];
this.getNode().nlink--;
}
delete this.children[link.getName()];
this.length--;

this.getNode().mtime = new Date();
this.emit('child:delete', link, this);
}

getChild(name: string): Link | undefined {
this.getNode().mtime = new Date();
if (Object.hasOwnProperty.call(this.children, name)) {
return this.children[name];
}
Expand Down
24 changes: 15 additions & 9 deletions src/volume.ts
Original file line number Diff line number Diff line change
Expand Up @@ -666,11 +666,11 @@ export class Volume {
}
};

// root.setChild('.', root);
// root.getNode().nlink++;
root.setChild('.', root);
root.getNode().nlink++;

// root.setChild('..', root);
// root.getNode().nlink++;
root.setChild('..', root);
root.getNode().nlink++;

this.root = root;
}
Expand Down Expand Up @@ -879,6 +879,9 @@ export class Volume {
}

for (const name in children) {
if (name === '.' || name === '..') {
continue;
}
isEmpty = false;

const child = link.getChild(name);
Expand Down Expand Up @@ -1741,7 +1744,7 @@ export class Volume {
for (const name in link.children) {
const child = link.getChild(name);

if (!child) {
if (!child || name === '.' || name === '..') {
continue;
}

Expand All @@ -1758,6 +1761,9 @@ export class Volume {

const list: TDataOut[] = [];
for (const name in link.children) {
if (name === '.' || name === '..') {
continue;
}
list.push(strToEncoding(name, options.encoding));
}

Expand Down Expand Up @@ -2798,8 +2804,8 @@ export class FSWatcher extends EventEmitter {
};

// children nodes changed
Object.values(link.children).forEach(childLink => {
if (childLink) {
Object.entries(link.children).forEach(([name, childLink]) => {
if (childLink && name !== '.' && name !== '..') {
watchLinkNodeChanged(childLink);
}
});
Expand All @@ -2814,8 +2820,8 @@ export class FSWatcher extends EventEmitter {
});

if (recursive) {
Object.values(link.children).forEach(childLink => {
if (childLink) {
Object.entries(link.children).forEach(([name, childLink]) => {
if (childLink && name !== '.' && name !== '..') {
watchLinkChildrenChanged(childLink);
}
});
Expand Down

0 comments on commit 8af880e

Please sign in to comment.