fs,extension: Make truncate work on cached file content.

We now remember the path of each cached file, so when someone
truncates that path, we can truncate all open instances of it in
cache. Important when someone pipes to stomp a file / opens with
O_TRUNC (which FUSE disaggregates into an open() and then a truncate()
call). Gets rid of the need for FUSE_CAP_ATOMIC_O_TRUNC, which never
worked on macOS anyway.

Fixes #40.
This commit is contained in:
Omar Rizwan 2021-04-06 13:48:48 -07:00
parent 8fbfa9a7c9
commit b948c235b8
3 changed files with 19 additions and 33 deletions

View file

@ -17,9 +17,6 @@ const unix = {
S_IFREG: 0100000, // regular S_IFREG: 0100000, // regular
S_IFLNK: 0120000, // symbolic link S_IFLNK: 0120000, // symbolic link
S_IFSOCK: 0140000, // socket S_IFSOCK: 0140000, // socket
// Open flags
O_TRUNC: 01000,
} }
class UnixError extends Error { class UnixError extends Error {
constructor(error) { super(); this.name = "UnixError"; this.error = error; } constructor(error) { super(); this.name = "UnixError"; this.error = error; }
@ -74,14 +71,21 @@ const defineFile = (function() {
// whole new conversation with the browser and regenerating the // whole new conversation with the browser and regenerating the
// content -- important for taking a screenshot, for instance) // content -- important for taking a screenshot, for instance)
store: {}, nextHandle: 0, store: {}, nextHandle: 0,
storeObject(object) { storeObject(path, object) {
const handle = ++this.nextHandle; const handle = ++this.nextHandle;
this.store[handle] = object; this.store[handle] = {path, object};
return handle; return handle;
}, },
getObjectForHandle(handle) { return this.store[handle]; }, getObjectForHandle(handle) { return this.store[handle].object; },
setObjectForHandle(handle, object) { this.store[handle] = object; }, setObjectForHandle(handle, object) { this.store[handle].object = object; },
removeObjectForHandle(handle) { delete this.store[handle]; } removeObjectForHandle(handle) { delete this.store[handle]; },
setObjectForPath(path, object) {
for (let storedObject of Object.values(this.store)) {
if (storedObject.path === path) {
storedObject.object = object;
}
}
}
}; };
function toUtf8Array(stringOrArray) { function toUtf8Array(stringOrArray) {
@ -110,11 +114,11 @@ const defineFile = (function() {
// We call getData() once when the file is opened, then cache that // We call getData() once when the file is opened, then cache that
// data for all subsequent reads from that application. // data for all subsequent reads from that application.
async open(req) { async open(req) {
const data = !(req.flags & unix.O_TRUNC) ? await getData(req) : ""; const data = await getData(req);
return { fh: Cache.storeObject(toUtf8Array(data)) }; return { fh: Cache.storeObject(req.path, toUtf8Array(data)) };
}, },
async read({fh, size, offset}) { async read({fh, size, offset}) {
return { buf: String.fromCharCode(...Cache.getObjectForHandle(fh).slice(offset, offset + size)) } return { buf: String.fromCharCode(...Cache.getObjectForHandle(fh).slice(offset, offset + size)) };
}, },
async write(req) { async write(req) {
const {fh, offset, buf} = req; const {fh, offset, buf} = req;
@ -135,15 +139,13 @@ const defineFile = (function() {
async release({fh}) { Cache.removeObjectForHandle(fh); return {}; }, async release({fh}) { Cache.removeObjectForHandle(fh); return {}; },
async truncate(req) { async truncate(req) {
// TODO: weird case if they truncate while the file is open
// (but `echo hi > foo.txt`, the main thing I care about, uses
// O_TRUNC which thankfully doesn't do that)
let arr = toUtf8Array(await getData(req)); let arr = toUtf8Array(await getData(req));
if (req.size !== arr.length) { if (req.size !== arr.length) {
const newArr = new Uint8Array(req.size); const newArr = new Uint8Array(req.size);
newArr.set(arr.slice(0, Math.min(req.size, arr.length))); newArr.set(arr.slice(0, Math.min(req.size, arr.length)));
arr = newArr; arr = newArr;
} }
Cache.setObjectForPath(req.path, arr);
await setData(req, utf8ArrayToString(arr)); return {}; await setData(req, utf8ArrayToString(arr)); return {};
} }
}); });

View file

@ -423,20 +423,6 @@ static int tabfs_mknod(const char *path, mode_t mode, dev_t rdev) {
return 0; return 0;
} }
#define want_capability(conn, flag) \
do { \
if (conn->capable & flag) { \
conn->want |= flag; \
} else { \
eprintln("warning: " #flag " not supported"); \
} \
} while (0)
static void *tabfs_init(struct fuse_conn_info *conn) {
want_capability(conn, FUSE_CAP_ATOMIC_O_TRUNC);
return NULL;
}
static const struct fuse_operations tabfs_oper = { static const struct fuse_operations tabfs_oper = {
.getattr = tabfs_getattr, .getattr = tabfs_getattr,
.readlink = tabfs_readlink, .readlink = tabfs_readlink,
@ -455,8 +441,6 @@ static const struct fuse_operations tabfs_oper = {
.mkdir = tabfs_mkdir, .mkdir = tabfs_mkdir,
.mknod = tabfs_mknod, .mknod = tabfs_mknod,
.init = tabfs_init,
}; };
int main(int argc, char **argv) { int main(int argc, char **argv) {

View file

@ -79,9 +79,9 @@ int main() {
fclose(result); fclose(result);
} }
// try to shorten the URL (#40) // try to truncate & stomp the URL (#40)
/* assert(system("echo about:blank > ../fs/mnt/tabs/last-focused/url.txt") == 0); */ assert(system("echo about:blank > ../fs/mnt/tabs/last-focused/url.txt") == 0);
/* assert(file_contents_equal("../fs/mnt/tabs/last-focused/url.txt", "about:blank")); */ assert(file_contents_equal("../fs/mnt/tabs/last-focused/url.txt", "about:blank"));
assert(system("echo remove > ../fs/mnt/tabs/last-focused/control") == 0); assert(system("echo remove > ../fs/mnt/tabs/last-focused/control") == 0);
} }