From b88de62f66c7c04003f4187bdcef273acb38f63f Mon Sep 17 00:00:00 2001 From: Divyendu Singh Date: Wed, 1 Nov 2023 09:18:45 +0000 Subject: [PATCH 1/5] chore: console.log to console.info --- packages/fuse-client/index.ts | 6 +++--- packages/fuse-client/syscalls/access.ts | 2 +- packages/fuse-client/syscalls/chmod.ts | 2 +- packages/fuse-client/syscalls/chown.ts | 2 +- packages/fuse-client/syscalls/create.ts | 2 +- packages/fuse-client/syscalls/destroy.ts | 2 +- packages/fuse-client/syscalls/fgetattr.ts | 2 +- packages/fuse-client/syscalls/flush.ts | 2 +- packages/fuse-client/syscalls/fsync.ts | 2 +- packages/fuse-client/syscalls/fsyncdir.ts | 2 +- packages/fuse-client/syscalls/ftruncate.ts | 2 +- packages/fuse-client/syscalls/getxattr.ts | 2 +- packages/fuse-client/syscalls/listxattr.ts | 2 +- packages/fuse-client/syscalls/mkdir.ts | 2 +- packages/fuse-client/syscalls/mknod.ts | 2 +- packages/fuse-client/syscalls/read.ts | 2 +- packages/fuse-client/syscalls/readdir.ts | 2 +- packages/fuse-client/syscalls/release.ts | 2 +- packages/fuse-client/syscalls/releasedir.ts | 2 +- packages/fuse-client/syscalls/removexattr.ts | 2 +- packages/fuse-client/syscalls/rename.ts | 3 +-- packages/fuse-client/syscalls/rmdir.ts | 2 +- packages/fuse-client/syscalls/setxattr.ts | 2 +- packages/fuse-client/syscalls/statfs.ts | 2 +- packages/fuse-client/syscalls/truncate.ts | 2 +- packages/fuse-client/syscalls/unlink.ts | 2 +- packages/fuse-client/syscalls/utimens.ts | 2 +- packages/fuse-client/syscalls/write.ts | 2 +- packages/sqlite-backend/WriteBuffer.ts | 3 +++ packages/sqlite-backend/{ => prisma}/baseline.sql | 13 ++++++++++++- packages/zoid-fs-client/index.ts | 2 +- 31 files changed, 46 insertions(+), 33 deletions(-) rename packages/sqlite-backend/{ => prisma}/baseline.sql (59%) diff --git a/packages/fuse-client/index.ts b/packages/fuse-client/index.ts index c610a2b..40777d1 100644 --- a/packages/fuse-client/index.ts +++ b/packages/fuse-client/index.ts @@ -97,7 +97,7 @@ export class FuseClient { this.unmountFS(mountPath); throw err; } - console.log("filesystem mounted on " + mountPath); + console.info("filesystem mounted on " + mountPath); } ); } @@ -105,12 +105,12 @@ export class FuseClient { unmountFS(mountPath: string) { fuse.unmount(mountPath, (err) => { if (err) { - console.log( + console.info( "filesystem at " + mountPath + " not unmounted", err.toString() ); } else { - console.log("filesystem at " + mountPath + " unmounted"); + console.info("filesystem at " + mountPath + " unmounted"); } }); } diff --git a/packages/fuse-client/syscalls/access.ts b/packages/fuse-client/syscalls/access.ts index 3ebe634..5664b51 100644 --- a/packages/fuse-client/syscalls/access.ts +++ b/packages/fuse-client/syscalls/access.ts @@ -5,7 +5,7 @@ export const access: (backend: SQLiteBackend) => MountOptions["access"] = ( backend ) => { return async (path, mode, cb) => { - console.log("access(%s, %d)", path, mode); + console.info("access(%s, %d)", path, mode); cb(0); }; }; diff --git a/packages/fuse-client/syscalls/chmod.ts b/packages/fuse-client/syscalls/chmod.ts index d950d26..05817cb 100644 --- a/packages/fuse-client/syscalls/chmod.ts +++ b/packages/fuse-client/syscalls/chmod.ts @@ -7,7 +7,7 @@ export const chmod: (backend: SQLiteBackend) => MountOptions["chmod"] = ( backend ) => { return async (path, mode, cb) => { - console.log("chmod(%s, %d)", path, mode); + console.info("chmod(%s, %d)", path, mode); const r = await backend.updateMode(path, mode); match(r) .with({ status: "ok" }, () => { diff --git a/packages/fuse-client/syscalls/chown.ts b/packages/fuse-client/syscalls/chown.ts index 6498b22..8be1ae3 100644 --- a/packages/fuse-client/syscalls/chown.ts +++ b/packages/fuse-client/syscalls/chown.ts @@ -5,7 +5,7 @@ export const chown: (backend: SQLiteBackend) => MountOptions["chown"] = ( backend ) => { return async (path, uid, gid, cb) => { - console.log("chown(%s, %d, %d)", path, uid, gid); + console.info("chown(%s, %d, %d)", path, uid, gid); cb(0); }; }; diff --git a/packages/fuse-client/syscalls/create.ts b/packages/fuse-client/syscalls/create.ts index eeec4ff..438c6aa 100644 --- a/packages/fuse-client/syscalls/create.ts +++ b/packages/fuse-client/syscalls/create.ts @@ -5,7 +5,7 @@ export const create: (backend: SQLiteBackend) => MountOptions["create"] = ( backend ) => { return async (path, mode, cb) => { - console.log("create(%s, %d)", path, mode); + console.info("create(%s, %d)", path, mode); //@ts-expect-error fix types const context = fuse.context(); diff --git a/packages/fuse-client/syscalls/destroy.ts b/packages/fuse-client/syscalls/destroy.ts index ea4e321..f7ef79c 100644 --- a/packages/fuse-client/syscalls/destroy.ts +++ b/packages/fuse-client/syscalls/destroy.ts @@ -5,7 +5,7 @@ export const destroy: (backend: SQLiteBackend) => MountOptions["destroy"] = ( backend ) => { return async (cb) => { - console.log("destroy"); + console.info("destroy"); cb(0); }; }; diff --git a/packages/fuse-client/syscalls/fgetattr.ts b/packages/fuse-client/syscalls/fgetattr.ts index d6cf732..4fd474b 100644 --- a/packages/fuse-client/syscalls/fgetattr.ts +++ b/packages/fuse-client/syscalls/fgetattr.ts @@ -6,7 +6,7 @@ export const fgetattr: (backend: SQLiteBackend) => MountOptions["fgetattr"] = ( backend ) => { return async (path, fd, cb) => { - console.log("fgetattr(%s, %d)", path, fd); + console.info("fgetattr -> getattr(%s, %d)", path, fd); //@ts-expect-error fix types // TODO: implement fgetattr properly getattr(backend)?.(path, cb); diff --git a/packages/fuse-client/syscalls/flush.ts b/packages/fuse-client/syscalls/flush.ts index a802015..6ac273f 100644 --- a/packages/fuse-client/syscalls/flush.ts +++ b/packages/fuse-client/syscalls/flush.ts @@ -5,7 +5,7 @@ export const flush: (backend: SQLiteBackend) => MountOptions["flush"] = ( backend ) => { return async (path, fd, cb) => { - console.log("flush(%s, %d)", path, fd); + console.info("flush(%s, %d)", path, fd); await backend.flush(path); cb(0); }; diff --git a/packages/fuse-client/syscalls/fsync.ts b/packages/fuse-client/syscalls/fsync.ts index e79d2e0..d6ccbbc 100644 --- a/packages/fuse-client/syscalls/fsync.ts +++ b/packages/fuse-client/syscalls/fsync.ts @@ -6,7 +6,7 @@ export const fsync: (backend: SQLiteBackend) => MountOptions["fsync"] = ( backend ) => { return async (path, fd, datasync, cb) => { - console.log("fsync(%s, %d, %d)", path, fd, datasync); + console.info("fsync -> flush(%s, %d, %d)", path, fd, datasync); // @ts-expect-error TODO: implement fsync properly // We do buffered writes and flush flushes the buffer! // A program may not call flush but fsync without relenquishing fd (like SQLite) diff --git a/packages/fuse-client/syscalls/fsyncdir.ts b/packages/fuse-client/syscalls/fsyncdir.ts index a5c7810..0fa442e 100644 --- a/packages/fuse-client/syscalls/fsyncdir.ts +++ b/packages/fuse-client/syscalls/fsyncdir.ts @@ -5,7 +5,7 @@ export const fsyncdir: (backend: SQLiteBackend) => MountOptions["fsyncdir"] = ( backend ) => { return async (path, fd, datasync, cb) => { - console.log("fsyncdir(%s, %d, %d)", path, fd, datasync); + console.info("fsyncdir(%s, %d, %d)", path, fd, datasync); cb(0); }; }; diff --git a/packages/fuse-client/syscalls/ftruncate.ts b/packages/fuse-client/syscalls/ftruncate.ts index 1696524..cb50abb 100644 --- a/packages/fuse-client/syscalls/ftruncate.ts +++ b/packages/fuse-client/syscalls/ftruncate.ts @@ -6,7 +6,7 @@ export const ftruncate: ( backend: SQLiteBackend ) => MountOptions["ftruncate"] = (backend) => { return async (path, fd, size, cb) => { - console.log("ftruncate(%s, %d, %d)", path, fd, size); + console.info("ftruncate -> truncate(%s, %d, %d)", path, fd, size); //@ts-expect-error fix types // TODO: implement ftruncate properly truncate(backend)(path, size, cb); diff --git a/packages/fuse-client/syscalls/getxattr.ts b/packages/fuse-client/syscalls/getxattr.ts index 9141ee7..7eb6694 100644 --- a/packages/fuse-client/syscalls/getxattr.ts +++ b/packages/fuse-client/syscalls/getxattr.ts @@ -5,7 +5,7 @@ export const getxattr: (backend: SQLiteBackend) => MountOptions["getxattr"] = ( backend ) => { return async (path, name, buffer, length, offset, cb) => { - console.log( + console.info( "getxattr(%s, %s, %o, %d, %d)", path, name, diff --git a/packages/fuse-client/syscalls/listxattr.ts b/packages/fuse-client/syscalls/listxattr.ts index 57cf88c..01e7097 100644 --- a/packages/fuse-client/syscalls/listxattr.ts +++ b/packages/fuse-client/syscalls/listxattr.ts @@ -5,7 +5,7 @@ export const listxattr: ( backend: SQLiteBackend ) => MountOptions["listxattr"] = (backend) => { return async (path, buffer, length, cb) => { - console.log("listxattr(%s, %d, %d)", path, buffer, length); + console.info("listxattr(%s, %d, %d)", path, buffer, length); cb(0, 0); }; }; diff --git a/packages/fuse-client/syscalls/mkdir.ts b/packages/fuse-client/syscalls/mkdir.ts index d8f6a75..43440d7 100644 --- a/packages/fuse-client/syscalls/mkdir.ts +++ b/packages/fuse-client/syscalls/mkdir.ts @@ -5,7 +5,7 @@ export const mkdir: (backend: SQLiteBackend) => MountOptions["mkdir"] = ( backend ) => { return async (filepath, mode, cb) => { - console.log("mkdir(%s, %s)", filepath, mode); + console.info("mkdir(%s, %s)", filepath, mode); const filename = path.parse(filepath).base; if (filename.length > 255) { diff --git a/packages/fuse-client/syscalls/mknod.ts b/packages/fuse-client/syscalls/mknod.ts index fa3048a..b03285a 100644 --- a/packages/fuse-client/syscalls/mknod.ts +++ b/packages/fuse-client/syscalls/mknod.ts @@ -6,7 +6,7 @@ export const mknod: (backend: SQLiteBackend) => MountOptions["mknod"] = ( backend ) => { return async (path, mode, dev, cb) => { - console.log("mknod(%s, %d, %d)", path, mode, dev); + console.info("mknod -> create(%s, %d, %d)", path, mode, dev); //@ts-expect-error fix types // TODO: implement mknod properly create(backend)(path, mode, cb); diff --git a/packages/fuse-client/syscalls/read.ts b/packages/fuse-client/syscalls/read.ts index c5ea0d5..8995c23 100644 --- a/packages/fuse-client/syscalls/read.ts +++ b/packages/fuse-client/syscalls/read.ts @@ -6,7 +6,7 @@ export const read: (backend: SQLiteBackend) => MountOptions["read"] = ( backend ) => { return async (path, fd, buf, len, pos, cb) => { - console.log("read(%s, %d, %d, %d)", path, fd, len, pos); + console.info("read(%s, %d, %d, %d)", path, fd, len, pos); const r = await backend.getFileChunks(fd, pos, len); await match(r) .with({ status: "ok" }, async (r) => { diff --git a/packages/fuse-client/syscalls/readdir.ts b/packages/fuse-client/syscalls/readdir.ts index 9d41689..f840c4c 100644 --- a/packages/fuse-client/syscalls/readdir.ts +++ b/packages/fuse-client/syscalls/readdir.ts @@ -5,7 +5,7 @@ export const readdir: (backend: SQLiteBackend) => MountOptions["readdir"] = ( backend ) => { return async (path, cb) => { - console.log("readdir(%s)", path); + console.info("readdir(%s)", path); // TODO: figure out how are these directories in output of ls -la const dotDirs = [".", ".."]; diff --git a/packages/fuse-client/syscalls/release.ts b/packages/fuse-client/syscalls/release.ts index 5516938..883a447 100644 --- a/packages/fuse-client/syscalls/release.ts +++ b/packages/fuse-client/syscalls/release.ts @@ -5,7 +5,7 @@ export const release: (backend: SQLiteBackend) => MountOptions["release"] = ( backend ) => { return async (path, fd, cb) => { - console.log("release(%s, %d)", path, fd); + console.info("release(%s, %d)", path, fd); cb(0); }; }; diff --git a/packages/fuse-client/syscalls/releasedir.ts b/packages/fuse-client/syscalls/releasedir.ts index 98aa815..4e3d9fe 100644 --- a/packages/fuse-client/syscalls/releasedir.ts +++ b/packages/fuse-client/syscalls/releasedir.ts @@ -5,7 +5,7 @@ export const releasedir: ( backend: SQLiteBackend ) => MountOptions["releasedir"] = (backend) => { return async (path, fd, cb) => { - console.log("releasedir(%s, %d)", path, fd); + console.info("releasedir(%s, %d)", path, fd); cb(0); }; }; diff --git a/packages/fuse-client/syscalls/removexattr.ts b/packages/fuse-client/syscalls/removexattr.ts index 69060de..ae4daeb 100644 --- a/packages/fuse-client/syscalls/removexattr.ts +++ b/packages/fuse-client/syscalls/removexattr.ts @@ -5,7 +5,7 @@ export const removexattr: ( backend: SQLiteBackend ) => MountOptions["removexattr"] = (backend) => { return async (path, name, cb) => { - console.log("removexattr(%s, %s)", path, name); + console.info("removexattr(%s, %s)", path, name); cb(0); }; }; diff --git a/packages/fuse-client/syscalls/rename.ts b/packages/fuse-client/syscalls/rename.ts index 31314ac..2b4a2e9 100644 --- a/packages/fuse-client/syscalls/rename.ts +++ b/packages/fuse-client/syscalls/rename.ts @@ -5,10 +5,9 @@ export const rename: (backend: SQLiteBackend) => MountOptions["rename"] = ( backend ) => { return async (srcPath, destPath, cb) => { - console.log("rename(%s, %s)", srcPath, destPath); + console.info("rename(%s, %s)", srcPath, destPath); const r = await backend.renameFile(srcPath, destPath); if (r.status === "ok") { - console.log("rename(%s, %s)", srcPath, destPath); cb(0); } else { // TODO: can move fail, if yes, when? diff --git a/packages/fuse-client/syscalls/rmdir.ts b/packages/fuse-client/syscalls/rmdir.ts index dea31ef..f9e3d3e 100644 --- a/packages/fuse-client/syscalls/rmdir.ts +++ b/packages/fuse-client/syscalls/rmdir.ts @@ -5,7 +5,7 @@ export const rmdir: (backend: SQLiteBackend) => MountOptions["rmdir"] = ( backend ) => { return async (path, cb) => { - console.log("rmdir(%s)", path); + console.info("rmdir(%s)", path); const r = await backend.deleteFile(path); match(r) .with({ status: "ok" }, (r) => { diff --git a/packages/fuse-client/syscalls/setxattr.ts b/packages/fuse-client/syscalls/setxattr.ts index a76334f..3b07903 100644 --- a/packages/fuse-client/syscalls/setxattr.ts +++ b/packages/fuse-client/syscalls/setxattr.ts @@ -5,7 +5,7 @@ export const setxattr: (backend: SQLiteBackend) => MountOptions["setxattr"] = ( backend ) => { return async (path, name, buffer, length, offset, flags, cb) => { - console.log( + console.info( "setxattr(%s, %s, %s, %d, %d, %d)", path, name, diff --git a/packages/fuse-client/syscalls/statfs.ts b/packages/fuse-client/syscalls/statfs.ts index ad308b9..6191198 100644 --- a/packages/fuse-client/syscalls/statfs.ts +++ b/packages/fuse-client/syscalls/statfs.ts @@ -5,7 +5,7 @@ export const statfs: (backend: SQLiteBackend) => MountOptions["statfs"] = ( backend ) => { return (path, cb) => { - console.log("statfs(%s)", path); + console.info("statfs(%s)", path); // TODO: fill actual values, these are just placeholders cb(0, { bsize: 1000000, // Block size diff --git a/packages/fuse-client/syscalls/truncate.ts b/packages/fuse-client/syscalls/truncate.ts index fff3f10..5f8f9f8 100644 --- a/packages/fuse-client/syscalls/truncate.ts +++ b/packages/fuse-client/syscalls/truncate.ts @@ -6,7 +6,7 @@ export const truncate: (backend: SQLiteBackend) => MountOptions["truncate"] = ( backend ) => { return async (path, size, cb) => { - console.log("truncate(%s, %d)", path, size); + console.info("truncate(%s, %d)", path, size); const r = await backend.truncateFile(path, size); await match(r) .with({ status: "ok" }, async (r) => { diff --git a/packages/fuse-client/syscalls/unlink.ts b/packages/fuse-client/syscalls/unlink.ts index b8b6f65..07985ef 100644 --- a/packages/fuse-client/syscalls/unlink.ts +++ b/packages/fuse-client/syscalls/unlink.ts @@ -5,7 +5,7 @@ export const unlink: (backend: SQLiteBackend) => MountOptions["unlink"] = ( backend ) => { return async (path, cb) => { - console.log("unlink(%s)", path); + console.info("unlink(%s)", path); const r = await backend.deleteFile(path); if (r.status === "ok") { cb(0); diff --git a/packages/fuse-client/syscalls/utimens.ts b/packages/fuse-client/syscalls/utimens.ts index 14109ef..1a8664e 100644 --- a/packages/fuse-client/syscalls/utimens.ts +++ b/packages/fuse-client/syscalls/utimens.ts @@ -5,7 +5,7 @@ export const utimens: (backend: SQLiteBackend) => MountOptions["utimens"] = ( backend ) => { return async (path, atime, mtime, cb) => { - console.log("utimens(%s, %s, %s)", path, atime, mtime); + console.info("utimens(%s, %s, %s)", path, atime, mtime); try { await backend.updateTimes(path, atime, mtime); } catch (e) { diff --git a/packages/fuse-client/syscalls/write.ts b/packages/fuse-client/syscalls/write.ts index 6da4f67..f6ed0a2 100644 --- a/packages/fuse-client/syscalls/write.ts +++ b/packages/fuse-client/syscalls/write.ts @@ -5,7 +5,7 @@ export const write: (backend: SQLiteBackend) => MountOptions["write"] = ( backend ) => { return async (path, fd, buf, len, pos, cb) => { - console.log("write(%s, %d, %d, %d)", path, fd, len, pos); + console.info("write(%s, %d, %d, %d)", path, fd, len, pos); const chunk = Buffer.from(buf, pos, len); // TODO: This may throw (because of flush!, what should happen then?) await backend.write(path, { content: chunk, offset: pos, size: len }); diff --git a/packages/sqlite-backend/WriteBuffer.ts b/packages/sqlite-backend/WriteBuffer.ts index ba9e858..d62eb17 100644 --- a/packages/sqlite-backend/WriteBuffer.ts +++ b/packages/sqlite-backend/WriteBuffer.ts @@ -8,6 +8,9 @@ export class WriteBuffer { async write(item: T): Promise { this.buffer.push(item); if (this.buffer.length >= this.size) { + console.info( + `WriteBuffer.write: Flushing because buffer size exceeded the set limit of ${this.size}` + ); await this.flush(); } // TODO: implement a time based flush, like, if there are no writes for 100ms diff --git a/packages/sqlite-backend/baseline.sql b/packages/sqlite-backend/prisma/baseline.sql similarity index 59% rename from packages/sqlite-backend/baseline.sql rename to packages/sqlite-backend/prisma/baseline.sql index 4817ae6..26e3f35 100644 --- a/packages/sqlite-backend/baseline.sql +++ b/packages/sqlite-backend/prisma/baseline.sql @@ -1,4 +1,5 @@ -- CreateTable +DROP TABLE IF EXISTS "File"; CREATE TABLE "File" ( "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "type" TEXT NOT NULL, @@ -7,7 +8,6 @@ CREATE TABLE "File" ( "name" TEXT NOT NULL, "dir" TEXT NOT NULL, "path" TEXT NOT NULL, - "content" BLOB NOT NULL, "uid" INTEGER NOT NULL, "gid" INTEGER NOT NULL, "atime" DATETIME NOT NULL, @@ -15,6 +15,17 @@ CREATE TABLE "File" ( "ctime" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); +DROP TABLE IF EXISTS "Content"; +-- CreateTable +CREATE TABLE "Content" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "content" BLOB NOT NULL, + "offset" INTEGER NOT NULL, + "size" INTEGER NOT NULL, + "fileId" INTEGER NOT NULL, + CONSTRAINT "Content_fileId_fkey" FOREIGN KEY ("fileId") REFERENCES "File" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + -- CreateIndex CREATE UNIQUE INDEX "File_path_key" ON "File"("path"); diff --git a/packages/zoid-fs-client/index.ts b/packages/zoid-fs-client/index.ts index 994c392..6e984e5 100644 --- a/packages/zoid-fs-client/index.ts +++ b/packages/zoid-fs-client/index.ts @@ -36,7 +36,7 @@ const backend = await match(backendArg) const fuseClient = new FuseClient(backend); setTimeout(async () => { - console.log("mounting: fuse mount points"); + console.info("mounting: fuse mount points"); fuseClient.mountFS(mountPathArg); }, 1000); From cff929afcf22ed86fc59363142cf37ebb621802b Mon Sep 17 00:00:00 2001 From: Divyendu Singh Date: Wed, 1 Nov 2023 09:25:44 +0000 Subject: [PATCH 2/5] fix: link, symlinks with current database schema --- packages/common/index.ts | 7 +- packages/fuse-client/syscalls/getattr.ts | 11 +- packages/fuse-client/syscalls/init.ts | 4 +- packages/fuse-client/syscalls/link.ts | 35 +++++- packages/fuse-client/syscalls/open.ts | 4 +- packages/fuse-client/syscalls/opendir.ts | 4 +- packages/fuse-client/syscalls/readlink.ts | 6 +- packages/fuse-client/syscalls/symlink.ts | 11 +- packages/sqlite-backend/SQLiteBackend.ts | 124 ++++++++++++++++--- packages/sqlite-backend/prisma/schema.prisma | 26 ++-- packages/zoid-fs-client/package.json | 2 +- 11 files changed, 168 insertions(+), 66 deletions(-) diff --git a/packages/common/index.ts b/packages/common/index.ts index a0ffbd6..a5ca5b5 100644 --- a/packages/common/index.ts +++ b/packages/common/index.ts @@ -15,7 +15,8 @@ type Result = export interface Backend { getFiles: (dir: string) => Promise; - getFile: (filepath: string) => Promise>; + getFileRaw: (filepath: string) => Promise>; + getFileResolved: (filepath: string) => Promise>; createFile: ( filepath: string, @@ -23,7 +24,7 @@ export interface Backend { mode: number, uid: number, gid: number, - targetId: number + targetPath: string ) => Promise>; writeFile: ( @@ -32,7 +33,7 @@ export interface Backend { gid: number ) => Promise>; - deleteFile: (filepath: string) => Promise>; + deleteFile: (filepath: string) => Promise>; renameFile: (srcPath: string, destPath: string) => Promise>; updateMode: (filepath: string, mode: number) => Promise>; } diff --git a/packages/fuse-client/syscalls/getattr.ts b/packages/fuse-client/syscalls/getattr.ts index 269d1f3..aabac67 100644 --- a/packages/fuse-client/syscalls/getattr.ts +++ b/packages/fuse-client/syscalls/getattr.ts @@ -6,9 +6,8 @@ export const getattr: (backend: SQLiteBackend) => MountOptions["getattr"] = ( backend ) => { return async (path, cb) => { - console.log("getattr(%s)", path); - const r = await backend.getFile(path); - + console.info("getattr(%s)", path); + const r = await backend.getFileResolved(path); await match(r) .with({ status: "ok" }, async (r) => { const rSize = await backend.getFileSize(path); @@ -16,13 +15,15 @@ export const getattr: (backend: SQLiteBackend) => MountOptions["getattr"] = ( cb(fuse.ENOENT); return; } - + const rNlinks = await backend.getFileNLinks(path); const { mtime, atime, ctime, mode } = r.file; cb(0, { mtime, atime, ctime, - nlink: 1, + blocks: 1, + ino: r.file.id, + nlink: rNlinks.nLinks?.length || 1, size: rSize.size, mode: mode, // TODO: enable posix mode where real uid/gid are returned diff --git a/packages/fuse-client/syscalls/init.ts b/packages/fuse-client/syscalls/init.ts index 94fd556..b941752 100644 --- a/packages/fuse-client/syscalls/init.ts +++ b/packages/fuse-client/syscalls/init.ts @@ -6,13 +6,13 @@ export const init: (backend: SQLiteBackend) => MountOptions["init"] = ( backend ) => { return async (cb) => { - console.log("init"); + console.info("init"); //@ts-expect-error fix types const context = fuse.context(); const { uid, gid } = context; - const rootFolder = await backend.getFile("/"); + const rootFolder = await backend.getFileResolved("/"); match(rootFolder) .with({ status: "ok" }, () => {}) .with({ status: "not_found" }, async () => { diff --git a/packages/fuse-client/syscalls/link.ts b/packages/fuse-client/syscalls/link.ts index 73294d2..4861ee7 100644 --- a/packages/fuse-client/syscalls/link.ts +++ b/packages/fuse-client/syscalls/link.ts @@ -1,14 +1,37 @@ import { SQLiteBackend } from "@zoid-fs/sqlite-backend"; -import { MountOptions } from "@zoid-fs/node-fuse-bindings"; -import { symlink } from "./symlink"; +import fuse, { MountOptions } from "@zoid-fs/node-fuse-bindings"; +import { match } from "ts-pattern"; export const link: (backend: SQLiteBackend) => MountOptions["link"] = ( backend ) => { - return async (src, dest, cb) => { - console.log("link(%s, %s)", src, dest); + return async (srcPath, destPath, cb) => { + console.info("link(%s, %s)", srcPath, destPath); + + // TODO: throw if destination doesn't exist + //@ts-expect-error fix types - // TODO: implement link properly - symlink(backend)(src, dest, cb); + const context = fuse.context(); + const { uid, gid } = context; + + // TODO: double check if mode for link is correct + // https://unix.stackexchange.com/questions/193465/what-file-mode-is-a-link + const r = await backend.createFile( + destPath, + "link", + 41453, // Link's mode??? from node-fuse-binding source, why though? + uid, + gid, + srcPath + ); + console.log({ r }); + match(r) + .with({ status: "ok" }, () => { + cb(0); + }) + .with({ status: "not_found" }, () => { + cb(fuse.ENOENT); + }) + .exhaustive(); }; }; diff --git a/packages/fuse-client/syscalls/open.ts b/packages/fuse-client/syscalls/open.ts index a921e86..a5eff57 100644 --- a/packages/fuse-client/syscalls/open.ts +++ b/packages/fuse-client/syscalls/open.ts @@ -6,8 +6,8 @@ export const open: (backend: SQLiteBackend) => MountOptions["open"] = ( backend ) => { return async (path, flags, cb) => { - console.log("open(%s, %d)", path, flags); - const r = await backend.getFile(path); + console.info("open(%s, %d)", path, flags); + const r = await backend.getFileResolved(path); match(r) .with({ status: "ok" }, (r) => { diff --git a/packages/fuse-client/syscalls/opendir.ts b/packages/fuse-client/syscalls/opendir.ts index 78de909..536bba3 100644 --- a/packages/fuse-client/syscalls/opendir.ts +++ b/packages/fuse-client/syscalls/opendir.ts @@ -6,14 +6,14 @@ export const opendir: (backend: SQLiteBackend) => MountOptions["opendir"] = ( backend ) => { return async (path, flags, cb) => { - console.log("opendir(%s, %d)", path, flags); + console.info("opendir(%s, %d)", path, flags); if (path === "/") { cb(0, 42); // TODO: Universal FD for root dir, it should probably be in the database as bootstrap return; } - const r = await backend.getFile(path); + const r = await backend.getFileResolved(path); match(r) .with({ status: "ok" }, (r) => { cb(0, r.file.id); diff --git a/packages/fuse-client/syscalls/readlink.ts b/packages/fuse-client/syscalls/readlink.ts index fe32b72..706bcfb 100644 --- a/packages/fuse-client/syscalls/readlink.ts +++ b/packages/fuse-client/syscalls/readlink.ts @@ -6,11 +6,11 @@ export const readlink: (backend: SQLiteBackend) => MountOptions["readlink"] = ( backend ) => { return async (path, cb) => { - console.log("readlink(%s)", path); - const r = await backend.getFile(path); + console.info("readlink(%s)", path); + const r = await backend.getFileRaw(path); match(r) .with({ status: "ok" }, (r) => { - cb(0, r.file.name); + cb(0, r.file.targetPath); }) .with({ status: "not_found" }, () => { //@ts-expect-error fix types, what to do if readlink fails? diff --git a/packages/fuse-client/syscalls/symlink.ts b/packages/fuse-client/syscalls/symlink.ts index 7400cda..ba2ac55 100644 --- a/packages/fuse-client/syscalls/symlink.ts +++ b/packages/fuse-client/syscalls/symlink.ts @@ -6,14 +6,7 @@ export const symlink: (backend: SQLiteBackend) => MountOptions["symlink"] = ( backend ) => { return async (srcPath, destPath, cb) => { - console.log("symlink(%s, %s)", srcPath, destPath); - - const targetFile = await backend.getFile(srcPath); - console.log({ targetFile }); - if (targetFile.status === "not_found") { - cb(fuse.ENOENT); - return; - } + console.info("symlink(%s, %s)", srcPath, destPath); //@ts-expect-error fix types const context = fuse.context(); @@ -27,7 +20,7 @@ export const symlink: (backend: SQLiteBackend) => MountOptions["symlink"] = ( 33188, uid, gid, - targetFile.file.id + srcPath ); match(r) .with({ status: "ok" }, () => { diff --git a/packages/sqlite-backend/SQLiteBackend.ts b/packages/sqlite-backend/SQLiteBackend.ts index 02a1407..d01d388 100644 --- a/packages/sqlite-backend/SQLiteBackend.ts +++ b/packages/sqlite-backend/SQLiteBackend.ts @@ -12,7 +12,7 @@ export type ContentChunk = { size: number; }; -const WRITE_BUFFER_SIZE = 10; +const WRITE_BUFFER_SIZE = 1000; export class SQLiteBackend implements Backend { private readonly writeBuffers: Map> = @@ -60,39 +60,78 @@ export class SQLiteBackend implements Backend { async getFiles(dir: string) { const files = await this.prisma.file.findMany({ where: { - dir, + AND: [ + { + dir, + }, + { + path: { + not: "/", + }, + }, + ], }, }); return files; } - async getFile(filepath: string) { + async getFileRaw(filepath: string) { try { - const fileOrSymlink = await this.prisma.file.findFirstOrThrow({ + const file = await this.prisma.file.findFirstOrThrow({ where: { path: filepath, }, }); - const file = await match(fileOrSymlink.type === "symlink") - .with(true, async () => { - const targetFile = await this.prisma.file.findFirstOrThrow({ - where: { - id: fileOrSymlink.targetId, - }, - }); + return { + status: "ok" as const, + file: file, + }; + } catch (e) { + console.error(e); + return { + status: "not_found" as const, + }; + } + } + + async getFileResolved(filepath: string) { + try { + const fileOrSymlink = await this.getFileRaw(filepath); + if (fileOrSymlink.status === "not_found") { + return { + status: "not_found" as const, + }; + } + + const file = await match(fileOrSymlink.file.type) + .with("symlink", async () => { + const targetFile = await this.getFileRaw( + fileOrSymlink.file.targetPath + ); return { - ...targetFile, + // TODO: error handling + ...targetFile.file!, mode: constants.S_IFLNK, }; }) - .otherwise(() => fileOrSymlink); + .with("link", async () => { + const targetFile = await this.getFileRaw( + fileOrSymlink.file.targetPath + ); + return { + // TODO: error handling + ...targetFile.file!, + }; + }) + .otherwise(() => fileOrSymlink.file); return { status: "ok" as const, file: file, }; } catch (e) { + console.error(e); return { status: "not_found" as const, }; @@ -127,6 +166,7 @@ export class SQLiteBackend implements Backend { chunks, }; } catch (e) { + console.error(e); return { status: "not_found" as const, }; @@ -135,10 +175,12 @@ export class SQLiteBackend implements Backend { async getFileSize(filepath: string) { try { + const file = await this.getFileResolved(filepath); + // TODO: error handling const chunks = await this.prisma.content.findMany({ where: { file: { - path: filepath, + path: file.file?.path, }, }, }); @@ -148,6 +190,36 @@ export class SQLiteBackend implements Backend { size: Buffer.byteLength(bufChunk), }; } catch (e) { + console.error(e); + return { + status: "not_found" as const, + }; + } + } + + async getFileNLinks(filepath: string) { + try { + const file = await this.getFileResolved(filepath); + + // TODO: error handling + const nLinks = await this.prisma.file.findMany({ + where: { + OR: [ + { + path: file.file?.path, + }, + { + targetPath: file.file?.path, + }, + ], + }, + }); + return { + status: "ok" as const, + nLinks, + }; + } catch (e) { + console.error(e); return { status: "not_found" as const, }; @@ -160,7 +232,7 @@ export class SQLiteBackend implements Backend { mode = 16877, // dir (for default to be file, use 33188) uid: number, gid: number, - targetId: number = 0 + targetPath: string = "" ) { try { const parsedPath = path.parse(filepath); @@ -176,7 +248,7 @@ export class SQLiteBackend implements Backend { ctime: new Date(), uid, gid, - targetId, + targetPath, }, }); return { @@ -184,6 +256,7 @@ export class SQLiteBackend implements Backend { file: file, }; } catch (e) { + console.error(e); return { status: "not_found" as const, }; @@ -216,6 +289,7 @@ export class SQLiteBackend implements Backend { file: file, }; } catch (e) { + console.error(e); return { status: "not_found" as const, }; @@ -231,7 +305,7 @@ export class SQLiteBackend implements Backend { } try { - const rFile = await this.getFile(filepath); + const rFile = await this.getFileResolved(filepath); const file = rFile.file; /** @@ -270,6 +344,7 @@ export class SQLiteBackend implements Backend { chunks, }; } catch (e) { + console.error(e); return { status: "not_found" as const, }; @@ -293,6 +368,7 @@ export class SQLiteBackend implements Backend { file: file, }; } catch (e) { + console.error(e); return { status: "not_found" as const, }; @@ -301,17 +377,23 @@ export class SQLiteBackend implements Backend { async deleteFile(filepath: string) { try { - const file = await this.prisma.file.delete({ + const file = await this.prisma.file.deleteMany({ where: { - path: filepath, + OR: [ + { + path: filepath, + }, + { AND: [{ type: "link", targetPath: filepath }] }, + ], }, }); return { status: "ok" as const, - file: file, + file: file.count, }; } catch (e) { + console.error(e); return { status: "not_found" as const, }; @@ -370,6 +452,7 @@ export class SQLiteBackend implements Backend { file: file, }; } catch (e) { + console.error(e); return { status: "not_found" as const, }; @@ -392,6 +475,7 @@ export class SQLiteBackend implements Backend { file: file, }; } catch (e) { + console.error(e); return { status: "not_found" as const, }; diff --git a/packages/sqlite-backend/prisma/schema.prisma b/packages/sqlite-backend/prisma/schema.prisma index d6c03b2..2eda7c0 100644 --- a/packages/sqlite-backend/prisma/schema.prisma +++ b/packages/sqlite-backend/prisma/schema.prisma @@ -13,20 +13,20 @@ datasource db { } model File { - id Int @id @default(autoincrement()) - type String // dir or file or symlink - targetId Int @default(0) // only relevant for symlink otherwise 0 - mode Int - name String - dir String - path String @unique + id Int @id @default(autoincrement()) + type String // dir or file or symlink or link + targetPath String @default("") // only relevant for symlink otherwise empty + mode Int + name String + dir String + path String @unique // fileType String // text or binary - uid Int - gid Int - atime DateTime - mtime DateTime @updatedAt - ctime DateTime @default(now()) - Content Content[] + uid Int + gid Int + atime DateTime + mtime DateTime @updatedAt + ctime DateTime @default(now()) + Content Content[] } model Content { diff --git a/packages/zoid-fs-client/package.json b/packages/zoid-fs-client/package.json index 4d65706..6525036 100644 --- a/packages/zoid-fs-client/package.json +++ b/packages/zoid-fs-client/package.json @@ -4,7 +4,7 @@ "license": "MIT", "type": "module", "scripts": { - "start": "vite-node --watch index.ts /home/divyendusingh/zoid/vfs/1", + "start": "vite-node --watch index.ts /home/divyenduz/Documents/zoid/vfs/1", "test:prepare": "vite-node --watch index.ts /home/div/code/vfs/test-fs --tenant test", "ci:setup-fuse": "vite-node --watch index.ts", "test": "vitest", From 9a3a42cb51c1e68bee0bb23ecec48cf27d82cc4b Mon Sep 17 00:00:00 2001 From: Divyendu Singh Date: Wed, 1 Nov 2023 09:26:13 +0000 Subject: [PATCH 3/5] chore: ignore .log files in git --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 829b328..3fefd17 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ tsconfig.tsbuildinfo img.png +*.log + # See http://help.github.com/ignore-files/ for more about ignoring files. # compiled output From 300258084c952cb964f1ad9f1f2e9ffe543708b9 Mon Sep 17 00:00:00 2001 From: Divyendu Singh Date: Wed, 1 Nov 2023 16:58:06 +0000 Subject: [PATCH 4/5] refactor: new link table for file name --- packages/common/index.ts | 29 +- packages/fuse-client/syscalls/getattr.ts | 2 +- packages/fuse-client/syscalls/init.ts | 2 +- packages/fuse-client/syscalls/link.ts | 14 +- packages/fuse-client/syscalls/open.ts | 2 +- packages/fuse-client/syscalls/opendir.ts | 2 +- packages/fuse-client/syscalls/readdir.ts | 4 +- packages/fuse-client/syscalls/readlink.ts | 27 +- packages/fuse-client/syscalls/symlink.ts | 17 +- packages/sqlite-backend/SQLiteBackend.ts | 279 ++++++++++--------- packages/sqlite-backend/prisma/schema.prisma | 31 ++- 11 files changed, 219 insertions(+), 190 deletions(-) diff --git a/packages/common/index.ts b/packages/common/index.ts index a5ca5b5..b8c15e2 100644 --- a/packages/common/index.ts +++ b/packages/common/index.ts @@ -1,12 +1,11 @@ -import { File } from "@prisma/client"; +import { File, Link } from "@prisma/client"; type FileType = "file" | "dir" | "symlink"; -type Result = - | { +type Result = + | ({ status: "ok"; - file: T; - } + } & { [P in K]: T }) | { status: "not_found"; }; @@ -14,9 +13,10 @@ type Result = // TODO: bump this based on the latest state of the actual backend! export interface Backend { - getFiles: (dir: string) => Promise; - getFileRaw: (filepath: string) => Promise>; - getFileResolved: (filepath: string) => Promise>; + // TODO: use resule + getLinks: (dir: string) => Promise; + getLink: (dir: string) => Promise>; + getFile: (filepath: string) => Promise>; createFile: ( filepath: string, @@ -27,13 +27,10 @@ export interface Backend { targetPath: string ) => Promise>; - writeFile: ( - filepath: string, - uid: number, - gid: number - ) => Promise>; - - deleteFile: (filepath: string) => Promise>; - renameFile: (srcPath: string, destPath: string) => Promise>; + deleteFile: (filepath: string) => Promise>; + renameFile: ( + srcPath: string, + destPath: string + ) => Promise>; updateMode: (filepath: string, mode: number) => Promise>; } diff --git a/packages/fuse-client/syscalls/getattr.ts b/packages/fuse-client/syscalls/getattr.ts index aabac67..aa760fc 100644 --- a/packages/fuse-client/syscalls/getattr.ts +++ b/packages/fuse-client/syscalls/getattr.ts @@ -7,7 +7,7 @@ export const getattr: (backend: SQLiteBackend) => MountOptions["getattr"] = ( ) => { return async (path, cb) => { console.info("getattr(%s)", path); - const r = await backend.getFileResolved(path); + const r = await backend.getFile(path); await match(r) .with({ status: "ok" }, async (r) => { const rSize = await backend.getFileSize(path); diff --git a/packages/fuse-client/syscalls/init.ts b/packages/fuse-client/syscalls/init.ts index b941752..2c6d8e4 100644 --- a/packages/fuse-client/syscalls/init.ts +++ b/packages/fuse-client/syscalls/init.ts @@ -12,7 +12,7 @@ export const init: (backend: SQLiteBackend) => MountOptions["init"] = ( const context = fuse.context(); const { uid, gid } = context; - const rootFolder = await backend.getFileResolved("/"); + const rootFolder = await backend.getFile("/"); match(rootFolder) .with({ status: "ok" }, () => {}) .with({ status: "not_found" }, async () => { diff --git a/packages/fuse-client/syscalls/link.ts b/packages/fuse-client/syscalls/link.ts index 4861ee7..6b75985 100644 --- a/packages/fuse-client/syscalls/link.ts +++ b/packages/fuse-client/syscalls/link.ts @@ -10,21 +10,9 @@ export const link: (backend: SQLiteBackend) => MountOptions["link"] = ( // TODO: throw if destination doesn't exist - //@ts-expect-error fix types - const context = fuse.context(); - const { uid, gid } = context; - // TODO: double check if mode for link is correct // https://unix.stackexchange.com/questions/193465/what-file-mode-is-a-link - const r = await backend.createFile( - destPath, - "link", - 41453, // Link's mode??? from node-fuse-binding source, why though? - uid, - gid, - srcPath - ); - console.log({ r }); + const r = await backend.createLink(srcPath, destPath); match(r) .with({ status: "ok" }, () => { cb(0); diff --git a/packages/fuse-client/syscalls/open.ts b/packages/fuse-client/syscalls/open.ts index a5eff57..d8965a1 100644 --- a/packages/fuse-client/syscalls/open.ts +++ b/packages/fuse-client/syscalls/open.ts @@ -7,7 +7,7 @@ export const open: (backend: SQLiteBackend) => MountOptions["open"] = ( ) => { return async (path, flags, cb) => { console.info("open(%s, %d)", path, flags); - const r = await backend.getFileResolved(path); + const r = await backend.getFile(path); match(r) .with({ status: "ok" }, (r) => { diff --git a/packages/fuse-client/syscalls/opendir.ts b/packages/fuse-client/syscalls/opendir.ts index 536bba3..367ad57 100644 --- a/packages/fuse-client/syscalls/opendir.ts +++ b/packages/fuse-client/syscalls/opendir.ts @@ -13,7 +13,7 @@ export const opendir: (backend: SQLiteBackend) => MountOptions["opendir"] = ( return; } - const r = await backend.getFileResolved(path); + const r = await backend.getFile(path); match(r) .with({ status: "ok" }, (r) => { cb(0, r.file.id); diff --git a/packages/fuse-client/syscalls/readdir.ts b/packages/fuse-client/syscalls/readdir.ts index f840c4c..42770e2 100644 --- a/packages/fuse-client/syscalls/readdir.ts +++ b/packages/fuse-client/syscalls/readdir.ts @@ -10,8 +10,8 @@ export const readdir: (backend: SQLiteBackend) => MountOptions["readdir"] = ( // TODO: figure out how are these directories in output of ls -la const dotDirs = [".", ".."]; - const files = await backend.getFiles(path); - const fileNames = dotDirs.concat(files.map((file) => file.name)); + const links = await backend.getLinks(path); + const fileNames = dotDirs.concat(links.map((link) => link.name)); return cb(0, fileNames); }; diff --git a/packages/fuse-client/syscalls/readlink.ts b/packages/fuse-client/syscalls/readlink.ts index 706bcfb..823ecc2 100644 --- a/packages/fuse-client/syscalls/readlink.ts +++ b/packages/fuse-client/syscalls/readlink.ts @@ -7,15 +7,22 @@ export const readlink: (backend: SQLiteBackend) => MountOptions["readlink"] = ( ) => { return async (path, cb) => { console.info("readlink(%s)", path); - const r = await backend.getFileRaw(path); - match(r) - .with({ status: "ok" }, (r) => { - cb(0, r.file.targetPath); - }) - .with({ status: "not_found" }, () => { - //@ts-expect-error fix types, what to do if readlink fails? - cb(fuse.ENOENT); - }) - .exhaustive(); + try { + const r = await backend.getLink(path); + match(r) + .with({ status: "ok" }, (r) => { + cb(0, r.link.targetPath); + }) + .with({ status: "not_found" }, () => { + //@ts-expect-error fix types, what to do if readlink fails? + cb(fuse.ENOENT); + }) + .exhaustive(); + } catch (e) { + console.error(e); + return { + status: "not_found" as const, + }; + } }; }; diff --git a/packages/fuse-client/syscalls/symlink.ts b/packages/fuse-client/syscalls/symlink.ts index ba2ac55..ab80bc1 100644 --- a/packages/fuse-client/syscalls/symlink.ts +++ b/packages/fuse-client/syscalls/symlink.ts @@ -1,6 +1,8 @@ import { SQLiteBackend } from "@zoid-fs/sqlite-backend"; import fuse, { MountOptions } from "@zoid-fs/node-fuse-bindings"; import { match } from "ts-pattern"; +import { constants } from "fs"; +import path from "path"; export const symlink: (backend: SQLiteBackend) => MountOptions["symlink"] = ( backend @@ -8,6 +10,19 @@ export const symlink: (backend: SQLiteBackend) => MountOptions["symlink"] = ( return async (srcPath, destPath, cb) => { console.info("symlink(%s, %s)", srcPath, destPath); + const parsedDestPath = path.parse(destPath); + // Note: actually 255 as per the spec but we get an extra / in the dest path + if (parsedDestPath.base.length > 255) { + cb(fuse.ENAMETOOLONG); + return; + } + + // Note: actually 1023 as per the spec but we get an extra / in the dest path + if (srcPath.length > 1023 || destPath.length > 1023) { + cb(fuse.ENAMETOOLONG); + return; + } + //@ts-expect-error fix types const context = fuse.context(); const { uid, gid } = context; @@ -17,7 +32,7 @@ export const symlink: (backend: SQLiteBackend) => MountOptions["symlink"] = ( const r = await backend.createFile( destPath, "symlink", - 33188, + constants.S_IFLNK, uid, gid, srcPath diff --git a/packages/sqlite-backend/SQLiteBackend.ts b/packages/sqlite-backend/SQLiteBackend.ts index d01d388..b434dba 100644 --- a/packages/sqlite-backend/SQLiteBackend.ts +++ b/packages/sqlite-backend/SQLiteBackend.ts @@ -57,8 +57,8 @@ export class SQLiteBackend implements Backend { await writeBuffer?.flush(); } - async getFiles(dir: string) { - const files = await this.prisma.file.findMany({ + async getLinks(dir: string) { + const files = await this.prisma.link.findMany({ where: { AND: [ { @@ -75,9 +75,9 @@ export class SQLiteBackend implements Backend { return files; } - async getFileRaw(filepath: string) { + async getLink(filepath: string) { try { - const file = await this.prisma.file.findFirstOrThrow({ + const link = await this.prisma.link.findFirstOrThrow({ where: { path: filepath, }, @@ -85,7 +85,7 @@ export class SQLiteBackend implements Backend { return { status: "ok" as const, - file: file, + link, }; } catch (e) { console.error(e); @@ -95,40 +95,22 @@ export class SQLiteBackend implements Backend { } } - async getFileResolved(filepath: string) { + async getFile(filepath: string) { try { - const fileOrSymlink = await this.getFileRaw(filepath); - if (fileOrSymlink.status === "not_found") { - return { - status: "not_found" as const, - }; - } - - const file = await match(fileOrSymlink.file.type) - .with("symlink", async () => { - const targetFile = await this.getFileRaw( - fileOrSymlink.file.targetPath - ); - return { - // TODO: error handling - ...targetFile.file!, - mode: constants.S_IFLNK, - }; - }) - .with("link", async () => { - const targetFile = await this.getFileRaw( - fileOrSymlink.file.targetPath - ); - return { - // TODO: error handling - ...targetFile.file!, - }; - }) - .otherwise(() => fileOrSymlink.file); + const link = await this.prisma.link.findFirstOrThrow({ + where: { + path: filepath, + }, + }); + const file = await this.prisma.file.findFirstOrThrow({ + where: { + id: link.fileId, + }, + }); return { status: "ok" as const, - file: file, + file, }; } catch (e) { console.error(e); @@ -175,13 +157,11 @@ export class SQLiteBackend implements Backend { async getFileSize(filepath: string) { try { - const file = await this.getFileResolved(filepath); + const file = await this.getFile(filepath); // TODO: error handling const chunks = await this.prisma.content.findMany({ where: { - file: { - path: file.file?.path, - }, + fileId: file.file?.id, }, }); const bufChunk = Buffer.concat(chunks.map((chunk) => chunk.content)); @@ -199,19 +179,12 @@ export class SQLiteBackend implements Backend { async getFileNLinks(filepath: string) { try { - const file = await this.getFileResolved(filepath); + const file = await this.getFile(filepath); // TODO: error handling - const nLinks = await this.prisma.file.findMany({ + const nLinks = await this.prisma.link.findMany({ where: { - OR: [ - { - path: file.file?.path, - }, - { - targetPath: file.file?.path, - }, - ], + fileId: file.file?.id, }, }); return { @@ -226,34 +199,25 @@ export class SQLiteBackend implements Backend { } } - async createFile( - filepath: string, - type = "file", - mode = 16877, // dir (for default to be file, use 33188) - uid: number, - gid: number, - targetPath: string = "" - ) { + async createLink(filepath: string, destinationPath: string) { try { - const parsedPath = path.parse(filepath); - const file = await this.prisma.file.create({ + // Note: destination is new link, filepath is the existing file + const file = await this.getFile(filepath); + const parsedPath = path.parse(destinationPath); + + const link = await this.prisma.link.create({ data: { name: parsedPath.base, dir: parsedPath.dir, - path: filepath, - type, - mode: type === "dir" ? 16877 : mode, - atime: new Date(), - mtime: new Date(), - ctime: new Date(), - uid, - gid, - targetPath, + path: destinationPath, + type: "file", // Note: hard link is a regular file + fileId: file.file!.id, }, }); + return { status: "ok" as const, - file: file, + file: link, }; } catch (e) { console.error(e); @@ -263,27 +227,43 @@ export class SQLiteBackend implements Backend { } } - async writeFile(filepath: string, uid: number, gid: number) { + async createFile( + filepath: string, + type = "file", + mode = 16877, // dir (for default to be file, use 33188) + uid: number, + gid: number, + targetPath: string = "" + ) { try { const parsedPath = path.parse(filepath); - const file = await this.prisma.file.upsert({ - where: { - path: filepath, - }, - update: {}, - create: { - type: "file", - name: parsedPath.base, - dir: parsedPath.dir, - path: filepath, - mode: 755, - atime: new Date(), - mtime: new Date(), - ctime: new Date(), - uid, - gid, - }, + + const { file, link } = await this.prisma.$transaction(async (tx) => { + const file = await tx.file.create({ + data: { + mode: type === "dir" ? 16877 : mode, + atime: new Date(), + mtime: new Date(), + ctime: new Date(), + uid, + gid, + }, + }); + + const link = await tx.link.create({ + data: { + name: parsedPath.base, + type, + dir: parsedPath.dir, + path: filepath, + targetPath, + fileId: file.id, + }, + }); + + return { file, link }; }); + return { status: "ok" as const, file: file, @@ -305,7 +285,7 @@ export class SQLiteBackend implements Backend { } try { - const rFile = await this.getFileResolved(filepath); + const rFile = await this.getFile(filepath); const file = rFile.file; /** @@ -353,11 +333,14 @@ export class SQLiteBackend implements Backend { async truncateFile(filepath: string, size: number) { try { + const link = await this.prisma.link.findFirstOrThrow({ + where: { + path: filepath, + }, + }); const file = await this.prisma.content.deleteMany({ where: { - file: { - path: filepath, - }, + fileId: link.fileId, offset: { gte: size, }, @@ -377,20 +360,20 @@ export class SQLiteBackend implements Backend { async deleteFile(filepath: string) { try { + const link = await this.prisma.link.findFirstOrThrow({ + where: { + path: filepath, + }, + }); const file = await this.prisma.file.deleteMany({ where: { - OR: [ - { - path: filepath, - }, - { AND: [{ type: "link", targetPath: filepath }] }, - ], + id: link.fileId, }, }); return { status: "ok" as const, - file: file.count, + count: file.count, }; } catch (e) { console.error(e); @@ -405,28 +388,42 @@ export class SQLiteBackend implements Backend { const parsedSrcPath = path.parse(srcPath); const parsedDestPath = path.parse(destPath); - // Note: Delete if the destiantion path already exists - await this.prisma.file.deleteMany({ - where: { - path: destPath, - }, - }); + const { updatedLink: link } = await this.prisma.$transaction( + async (tx) => { + // Note: Delete if the destiantion path already exists + const link = await tx.link.findFirstOrThrow({ + where: { + path: destPath, + }, + }); + + // Note: deleting file should delete link and content as cascade delete is enabled + const fileDeleteMany = await tx.file.deleteMany({ + where: { + id: link.id, + }, + }); + + const updatedLink = await tx.link.update({ + where: { + name: parsedSrcPath.base, + dir: parsedSrcPath.dir, + path: srcPath, + }, + data: { + name: parsedDestPath.base, + dir: parsedDestPath.dir, + path: destPath, + }, + }); + + return { updatedLink }; + } + ); - const file = await this.prisma.file.update({ - where: { - name: parsedSrcPath.base, - dir: parsedSrcPath.dir, - path: srcPath, - }, - data: { - name: parsedDestPath.base, - dir: parsedDestPath.dir, - path: destPath, - }, - }); return { status: "ok" as const, - file: file, + link, }; } catch (e) { console.error(e); @@ -439,14 +436,23 @@ export class SQLiteBackend implements Backend { async updateMode(filepath: string, mode: number) { try { - const file = await this.prisma.file.update({ - where: { - path: filepath, - }, - data: { - mode, - }, + const { file, link } = await this.prisma.$transaction(async (tx) => { + const link = await tx.link.findFirstOrThrow({ + where: { + path: filepath, + }, + }); + const file = await tx.file.update({ + where: { + id: link.id, + }, + data: { + mode, + }, + }); + return { file, link }; }); + return { status: "ok" as const, file: file, @@ -461,15 +467,24 @@ export class SQLiteBackend implements Backend { async updateTimes(filepath: string, atime: number, mtime: number) { try { - const file = await this.prisma.file.update({ - where: { - path: filepath, - }, - data: { - atime: new Date(atime), - mtime: new Date(mtime), - }, + const { file, link } = await this.prisma.$transaction(async (tx) => { + const link = await tx.link.findFirstOrThrow({ + where: { + path: filepath, + }, + }); + const file = await tx.file.update({ + where: { + id: link.fileId, + }, + data: { + atime: new Date(atime), + mtime: new Date(mtime), + }, + }); + return { file, link }; }); + return { status: "ok" as const, file: file, diff --git a/packages/sqlite-backend/prisma/schema.prisma b/packages/sqlite-backend/prisma/schema.prisma index 2eda7c0..b064968 100644 --- a/packages/sqlite-backend/prisma/schema.prisma +++ b/packages/sqlite-backend/prisma/schema.prisma @@ -13,20 +13,27 @@ datasource db { } model File { - id Int @id @default(autoincrement()) - type String // dir or file or symlink or link - targetPath String @default("") // only relevant for symlink otherwise empty - mode Int + id Int @id @default(autoincrement()) + mode Int + // fileType String // text or binary + uid Int + gid Int + atime DateTime + mtime DateTime @updatedAt + ctime DateTime @default(now()) + Content Content[] + Link Link[] +} + +model Link { + id Int @id @default(autoincrement()) name String + type String // dir or file or symlink dir String - path String @unique - // fileType String // text or binary - uid Int - gid Int - atime DateTime - mtime DateTime @updatedAt - ctime DateTime @default(now()) - Content Content[] + path String @unique + targetPath String @default("") // Only relevant for symlink, target path + file File @relation(fields: [fileId], references: [id], onDelete: Cascade) + fileId Int } model Content { From a5185d74b92467a47e269f7253832e9c232b3914 Mon Sep 17 00:00:00 2001 From: Divyendu Singh Date: Wed, 1 Nov 2023 17:10:00 +0000 Subject: [PATCH 5/5] fix: correctly reference link to file --- packages/sqlite-backend/SQLiteBackend.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sqlite-backend/SQLiteBackend.ts b/packages/sqlite-backend/SQLiteBackend.ts index b434dba..3338d43 100644 --- a/packages/sqlite-backend/SQLiteBackend.ts +++ b/packages/sqlite-backend/SQLiteBackend.ts @@ -400,7 +400,7 @@ export class SQLiteBackend implements Backend { // Note: deleting file should delete link and content as cascade delete is enabled const fileDeleteMany = await tx.file.deleteMany({ where: { - id: link.id, + id: link.fileId, }, }); @@ -444,7 +444,7 @@ export class SQLiteBackend implements Backend { }); const file = await tx.file.update({ where: { - id: link.id, + id: link.fileId, }, data: { mode,