Skip to content
Closed
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
55 changes: 54 additions & 1 deletion src/CacheFS.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,28 @@ module.exports = class CacheFS {
}
return _root;
}
_lookup(filepath) {
_lookup(filepath, follow = true) {
let dir = this._root;
let partialPath = '/'
for (let part of path.split(filepath)) {
dir = dir.get(part);
if (!dir) throw new ENOENT(filepath);
// Follow symlinks
if (follow) {
const stat = dir.get(STAT)
if (stat.type === 'symlink') {
let target = stat.target
if (!target.startsWith('/')) {
target = path.normalize(path.join(partialPath, target))
}
dir = this._lookup(target)
}
if (!partialPath) {
partialPath = part
} else {
partialPath = path.join(partialPath, part)
}
}
}
return dir;
}
Expand Down Expand Up @@ -187,4 +204,40 @@ module.exports = class CacheFS {
stat(filepath) {
return this._lookup(filepath).get(STAT);
}
lstat(filepath) {
return this._lookup(filepath, false).get(STAT);
}
readlink(filepath) {
return this._lookup(filepath, false).get(STAT).target;
}
symlink(target, filepath) {
let ino, mode;
try {
let oldStat = this.stat(filepath);
if (mode === null) {
mode = oldStat.mode;
}
ino = oldStat.ino;
} catch (err) {}
if (mode == null) {
mode = 0o666;
}
if (ino == null) {
ino = this.autoinc();
}
let dir = this._lookup(path.dirname(filepath));
let basename = path.basename(filepath);
let stat = {
mode,
type: "symlink",
target,
size: 0,
mtimeMs: Date.now(),
ino,
};
let entry = new Map();
entry.set(STAT, stat);
dir.set(basename, entry);
return stat;
}
};
4 changes: 2 additions & 2 deletions src/IdbBackend.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ module.exports = class IdbBackend {
await idb.set("!root", superblock, this._store);
await idb.del("!locked", this._store);
this._saving = null
console.log(`${iam} released lock`)
// console.log(`${iam} released lock`)
done()
}, 500)
}
Expand All @@ -42,7 +42,7 @@ module.exports = class IdbBackend {
} else {
// auto-expire locks after 24 hours
if (value < (new Date().valueOf() - 24 * 60 * 60 * 1000)) {
console.log('lock is expired')
// console.log('lock is expired')
value = undefined
}
// console.log(`${iam} ${call} denied`)
Expand Down
17 changes: 14 additions & 3 deletions src/PromisifiedFS.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ module.exports = class PromisifiedFS {
this.rename = this._wrap(this._rename.bind(this))
this.stat = this._wrap(this._stat.bind(this))
this.lstat = this._wrap(this._lstat.bind(this))
this.readlink = this._wrap(this._readlink.bind(this))
this.symlink = this._wrap(this._symlink.bind(this))
// Needed so things don't break if you destructure fs and pass individual functions around
this.readFile = this.readFile.bind(this)
this.writeFile = this.writeFile.bind(this)
Expand Down Expand Up @@ -174,8 +176,17 @@ module.exports = class PromisifiedFS {
return new Stat(data);
}
async _lstat(filepath, opts) {
return this.stat(filepath, opts);
;[filepath, opts] = cleanParams(filepath, opts);
let data = this._cache.lstat(filepath);
return new Stat(data);
}
async _readlink(filepath, opts) {
;[filepath, opts] = cleanParams(filepath, opts);
return this._cache.readlink(filepath);
}
async _symlink(target, filepath) {
;[target, filepath] = cleanParams2(target, filepath);
this._cache.symlink(target, filepath);
return null;
}
readlink() {}
symlink() {}
}
140 changes: 139 additions & 1 deletion src/__tests__/fs.promises.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ describe("fs.promises module", () => {
it("root directory already exists", (done) => {
fs.mkdir("/").catch(err => {
expect(err).not.toBe(null);
console.log(err)
expect(err.code).toEqual("EEXIST");
done();
});
Expand Down Expand Up @@ -249,4 +248,143 @@ describe("fs.promises module", () => {
});
});
});

describe("symlink", () => {
it("symlink a file and read/write to it", done => {
fs.mkdir("/symlink").finally(() => {
fs.writeFile("/symlink/a.txt", "hello").then(() => {
fs.symlink("/symlink/a.txt", "/symlink/b.txt").then(() => {
fs.readFile("/symlink/b.txt", "utf8").then(data => {
expect(data).toBe("hello")
fs.writeFile("/symlink/b.txt", "world").then(() => {
fs.readFile("/symlink/a.txt", "utf8").then(data => {
expect(data).toBe("world");
done();
})
})
});
});
});
});
});
it("symlink a file and read/write to it (relative)", done => {
fs.mkdir("/symlink").finally(() => {
fs.writeFile("/symlink/a.txt", "hello").then(() => {
fs.symlink("a.txt", "/symlink/b.txt").then(() => {
fs.readFile("/symlink/b.txt", "utf8").then(data => {
expect(data).toBe("hello")
fs.writeFile("/symlink/b.txt", "world").then(() => {
fs.readFile("/symlink/a.txt", "utf8").then(data => {
expect(data).toBe("world");
done();
})
})
});
});
});
});
});
it("symlink a directory and read/write to it", done => {
fs.mkdir("/symlink").finally(() => {
fs.mkdir("/symlink/a").finally(() => {
fs.writeFile("/symlink/a/file.txt", "data").then(() => {
fs.symlink("/symlink/a", "/symlink/b").then(() => {
fs.readdir("/symlink/b").then(data => {
expect(data.includes("file.txt")).toBe(true);
fs.readFile("/symlink/b/file.txt", "utf8").then(data => {
expect(data).toBe("data")
fs.writeFile("/symlink/b/file2.txt", "world").then(() => {
fs.readFile("/symlink/a/file2.txt", "utf8").then(data => {
expect(data).toBe("world");
done();
})
})
});
});
});
});
});
});
});
it("symlink a directory and read/write to it (relative)", done => {
fs.mkdir("/symlink").finally(() => {
fs.mkdir("/symlink/a").finally(() => {
fs.mkdir("/symlink/b").finally(() => {
fs.writeFile("/symlink/a/file.txt", "data").then(() => {
fs.symlink("../a", "/symlink/b/c").then(() => {
fs.readdir("/symlink/b/c").then(data => {
expect(data.includes("file.txt")).toBe(true);
fs.readFile("/symlink/b/c/file.txt", "utf8").then(data => {
expect(data).toBe("data")
fs.writeFile("/symlink/b/c/file2.txt", "world").then(() => {
fs.readFile("/symlink/a/file2.txt", "utf8").then(data => {
expect(data).toBe("world");
done();
})
})
});
});
});
});
});
});
});
});
it("unlink doesn't follow symlinks", done => {
fs.mkdir("/symlink").finally(() => {
fs.mkdir("/symlink/del").finally(() => {
fs.writeFile("/symlink/del/file.txt", "data").then(() => {
fs.symlink("/symlink/del/file.txt", "/symlink/del/file2.txt").then(() => {
fs.readdir("/symlink/del").then(data => {
expect(data.includes("file.txt")).toBe(true)
expect(data.includes("file2.txt")).toBe(true)
fs.unlink("/symlink/del/file2.txt").then(data => {
fs.readdir("/symlink/del").then(data => {
expect(data.includes("file.txt")).toBe(true)
expect(data.includes("file2.txt")).toBe(false)
done();
});
});
});
});
});
});
});
});
it("lstat doesn't follow symlinks", done => {
fs.mkdir("/symlink").finally(() => {
fs.mkdir("/symlink/lstat").finally(() => {
fs.writeFile("/symlink/lstat/file.txt", "data").then(() => {
fs.symlink("/symlink/lstat/file.txt", "/symlink/lstat/file2.txt").then(() => {
fs.stat("/symlink/lstat/file2.txt").then(stat => {
expect(stat.isFile()).toBe(true)
expect(stat.isSymbolicLink()).toBe(false)
fs.lstat("/symlink/lstat/file2.txt").then(stat => {
expect(stat.isFile()).toBe(false)
expect(stat.isSymbolicLink()).toBe(true)
done();
});
});
});
});
});
});
});
});

describe("readlink", () => {
it("readlink returns the target path", done => {
fs.mkdir("/readlink").finally(() => {
fs.writeFile("/readlink/a.txt", "hello").then(() => {
fs.symlink("/readlink/a.txt", "/readlink/b.txt").then(() => {
fs.readlink("/readlink/b.txt", "utf8").then(data => {
expect(data).toBe("/readlink/a.txt")
done();
});
});
});
});
});
});

});
Loading