-
Notifications
You must be signed in to change notification settings - Fork 315
[PHP-wasm] File mounting in NODEFS #2338
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
Conversation
Interesting! It gets more interesting: fs.writeFileSync(
import.meta.dirname + '/file.txt',
'Hello, world!'
);
php.mkdirTree('/tmp/file.txt');
await php.mount(
'/tmp/file.txt',
createNodeFsMountHandler(import.meta.dirname + '/file.txt'),
);
console.log(php.readFileAsText('/tmp/file.txt'));
php.unlink('/tmp/file.txt'); throws: Error: Could not unlink "/tmp/file.txt": There is a directory under that path. Apparently we can read that path as a file, but we can't delete it. I bet the reading is somehow routed to NODEFS while deleting is routed to MEMFS. Interesting! |
I went through the filesystem implementation and it seems like supporting file mounts may actually be easy. If we comment out the isDir check: if (!FS.isDir(node.mode)) {
- throw new FS.ErrnoError(54);
+ // throw new FS.ErrnoError(54);
} And create an empty file first, e.g.
if (
FS.isMountpoint(current) &&
(!islast || opts.follow_mount)
) {
current = current.mounted.root;
}
Let's try to comment out that check via |
I commented out that check via replace.sh in the Dockerfile and added support for creating file and directory nodes inside My next step is to remove the node after unmounting and add tests. Thanks for the research @adamziel! |
Should we automatically remove the node? I'm thinking about preserving it. It's a fairly low level api, being explicit is more useful than being implicit |
1a7a534
to
d18f5fa
Compare
Good timing, I just added node removal to unmount d18f5fa. |
I wonder – could we just borrow some of the Emscripten test suite for files to test this? |
Here are the Emscripten mount tests, they seem basic and I think that we could do a bit more with our tests. Here's a list of tests I had in mind.
|
That's a great list of tests @bgrgicak, let's do it! I'm kind of on the fence on |
I will compare all of my tests with POSIX to match the spec, or start a thread about differences. |
f456091
to
652563a
Compare
@@ -2143,6 +2143,9 @@ RUN set -euxo pipefail; \ | |||
# stream.stream_ops is sometimes undefined and the check fails. Let's adjust it to | |||
# tolerate a null stream.stream_ops value. | |||
/root/replace.sh "s/if\s*\(stream\.stream_ops\.poll\)/if (stream.stream_ops?.poll)/g" /root/output/php.js; \ | |||
# Emscriptend allows only directories to be mounted, but in Playground we support mounting files, directories, and symlinks. | |||
# For file mounting to work, we need to remove the directory check. | |||
/root/replace-across-lines.sh 's/(\s+)if\s*\(\s*!FS\.isDir\(node\.mode\)\s*\)\s*\{\s+throw\s+new\s+FS\.ErrnoError\(54\)\s*;\s+\}(\s+)/$1$2/gs' /root/output/php.js; \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we certain this only affects that one instance? Maybe let's add a "--exactly-one-match" or similar flag to replace-across-lines.sh to make sure we're not replacing too much.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we certain this only affects that one instance?
Today yes, but that might change in the future.
I tried a few things to add --exactly-one-match
to replace-across-lines.sh
but it seemed overly complicated, especially compared to removing the g
argument from the regex like I ended up doing in cbdc543.
} else if (FS.isLink(fromNode.mode)) { | ||
FS.symlink(FS.readlink(fromPath), toPath); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copy recursive didn't copy symlinks.
@adamziel this is now ready for review. I'm just waiting on PHP-wasm Node to recompile. |
👋 Hi @bgrgicak, I plan to review this yet today. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left some comments and questions, but this looks good to me. The biggest question I have is whether we should be mounting files at all, but it seems useful. And I am not aware of a downside.
packages/php-wasm/universal/src/lib/rethrow-file-system-error.ts
Outdated
Show resolved
Hide resolved
Good question! It is a natural enough interaction that people keep trying to mount plugin files or wp-config and get confused when it doesn't work. If it's easy for us to support it, let's support it. Feel free to proceed here once you feel it's ready :) |
Co-authored-by: Brandon Payton <[email protected]>
Thanks for the feedback! I think this PR is in a good place after addressing it. |
I'm not sure how I missed this while testing, but there was a condition in |
## Motivation for the change, related issues We accidentally added support for mounting files to the Playground CLI by creating a directory with the file path and mounting the file into that node. This PR removes the accidental mount implementation and adds file mounting support to `createNodeFsMountHandler` by detecting the mount type and creating the correct node type (symlink, file, directory). Because Emscripten allows only the mounting of directories, we had to remove the directory check from `FS.mount` during PHP-wasm compile time. To ensure mounting works as expected for files, directories, and symlinks, this PR also adds mount tests that include file system operations like reading files, modifying files, deleting, moving, and unmounting. While writing tests I found two bugs and addressed them in this PR: - When attempting to remove a mountpoint node `FSHelpers.rmdir` would first remove all files and directories from the mountpoint and fail to remove the mountpoint in the end. To address this, we now detect if a directory we are attempting to delete is a mount point and throw an error early. The same applies for moving a mount point. - `FSHelpers.copyRecursive` copied files and directories, but skipped symlinks. Now the function will also copy symlinks. ## Testing Instructions (or ideally a Blueprint) - CI --------- Co-authored-by: Brandon Payton <[email protected]>
Motivation for the change, related issues
We accidentally added support for mounting files to the Playground CLI by creating a directory with the file path and mounting the file into that node.
This PR removes the accidental mount implementation and adds file mounting support to
createNodeFsMountHandler
by detecting the mount type and creating the correct node type (symlink, file, directory).Because Emscripten allows only the mounting of directories, we had to remove the directory check from
FS.mount
during PHP-wasm compile time.To ensure mounting works as expected for files, directories, and symlinks, this PR also adds mount tests that include file system operations like reading files, modifying files, deleting, moving, and unmounting.
While writing tests I found two bugs and addressed them in this PR:
FSHelpers.rmdir
would first remove all files and directories from the mountpoint and fail to remove the mountpoint in the end. To address this, we now detect if a directory we are attempting to delete is a mount point and throw an error early. The same applies for moving a mount point.FSHelpers.copyRecursive
copied files and directories, but skipped symlinks. Now the function will also copy symlinks.Testing Instructions (or ideally a Blueprint)