-
Notifications
You must be signed in to change notification settings - Fork 15
path_get refactor and implementation of missing path_ hostcalls on Windows #41
Conversation
OK, so I guess the PR is ready for review and I'd really welcome your feedback here guys. I'm actually kinda stumped with some of this stuff, and especially handling of symlinks on Windows for the reasons mentioned in the PR's description. I've actually gone ahead and created a thin wrapper type, Symlink, around Windows symlinks that essentially tries to imitate *nix symlinks so that self loops and dangling symlinks are possible (otherwise the tests are broken). I'm not sure this is the way we want to go though. In fact, I'd welcome any and all discussion here on the best way to proceed. Finally, if we decide to scrap this PR as-is, I'm OK with that as well. We should still be able to salvage the implementation of at least some of the |
@sunfishcode here's a thought on the WASI spec: do you think it would make sense to disallow creation of symlink loops and dangling symlinks in WASI? Additionally, would it also make sense to split This still doesn't address the problem of privileges on Windows: creating symlinks requires that the user has either the necessary privileges or the OS is in developer mode, but I guess it'd clean up the implementation a lot. Anyhow, I'd love to hear your thoughts on all of this! |
Without looking too much into the implementation, this overall looks pretty reasonable. I agree that |
@sunfishcode did you perhaps have a look at the PR? I'd like to merge it into master before #46, #47 and #54 so as to avoid rebase conflicts and to give @marmistrz ability to enable+add more tests. :-) |
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.
Looks good to me! path_get
is some of the most complex code, and it's nice to have more of it factored out into common code.
src/hostcalls_impl/fs_helpers.rs
Outdated
) -> Result<(File, String)> { | ||
const MAX_SYMLINK_EXPANSIONS: usize = 128; | ||
|
||
if path.contains("\0") { |
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.
Can this be a character literal instead of a string literal?
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.
It should be indeed, thanks!
src/hostcalls_impl/fs.rs
Outdated
@@ -515,6 +520,8 @@ pub(crate) fn path_link( | |||
let new_dirfd = wasi_ctx | |||
.get_fd_entry(new_dirfd, host::__WASI_RIGHT_PATH_LINK_TARGET, 0) | |||
.and_then(|fe| fe.fd_object.descriptor.as_file())?; | |||
let (old_dirfd, old_path) = path_get(old_dirfd, 0, old_path, false)?; | |||
let (new_dirfd, new_path) = path_get(new_dirfd, 0, new_path, false)?; | |||
|
|||
hostcalls_impl::path_link(old_dirfd, new_dirfd, old_path, new_path) |
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.
This looks like it might be a little error prone, where one could forget to call path_get
and in some cases it would still compile and seem to work. Would it make sense to create a struct to replace path_get
's tuple return value? Then you could make the hostcalls_impl
functions use that struct for their arguments, which would help ensure that path_get
has been called. Does that make sense?
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.
Hmm, that sounds like a good idea. Let me have a play with it and I'll report back to this thread :-)
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.
OK, I've encapsulated the return value of path_get
inside a PathGet
struct. Have a look and let me know if this is what you've had in mind :-) Also, I can see several places we could refactor a lot of impl by extending this struct but I don't want to pile onto this PR too much. Instead, we can for sure refactor in smaller steps in subsequent PRs.
@sunfishcode if you're happy with the rest of PR as is, I guess I'll go ahead and squash and merge so that we can move forward with other PRs? |
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'm not yet comfortable with the path_symlink
implementation. I made a few comments below, but looking at the big picture, if symlink creation requires special privileges or a special OS mode, it suggests to me we should either:
- remove
path_symlink
from WASI, or - document that support for
path_symlink
may be subject to limitations of the underyling filesystem, and maybe even add a way to query a directory to determine what form of symlinks may be created inside it
My suggestion would be to remove the SYMLINKS
singleton here, since it's kind of misleading -- it helps emulate the desired functionality for the program that creates the symlinks, but they're not real and other processes wouldn't see them. I propose it's better to just have path_symlink
fail in the cases that Windows doesn't support, and we'll take up the remaining questions at the WASI standards level. How does that sound?
let target_path: &Path = target.as_ref(); | ||
|
||
if source_path.exists() { | ||
Err(host::__WASI_EEXIST) |
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.
Is it necessary to check if the source_path
exists up front, or can we defer this and only check it in the non-file non-dir case?
SymlinkKind::File | ||
} else if target_path.is_dir() { | ||
symlink_dir(target_path, source_path).map_err(errno_from_ioerror)?; | ||
SymlinkKind::Dir |
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.
How crazy would it be to do this: instead of checking is_file
and is_dir
, just start by calling symlink_file
and if that fails with the right error code, then try symlink_dir
, and if that fails, check for the loop/dangling cases?
Yep, sounds good to me! As I said, it's been a roller-coaster ride for me trying to get it to work on Windows. The emulation also made me somewhat uneasy since emulating a dangling symlink would not get committed to the OS until it became solid, which, if the app died prematurely, etc., would potentially never happen, leaving other processes none-the-wiser. Anyhow, is the following plan of action aligned with what you've had in mind:
Those are just some loose thoughts but, in principle, I reckon I should be able to refactor the code to make it work. Also, do you want me to address the comments relating |
@sunfishcode OK, so here are the changes as per your suggestion:
Have a look and let me know what you reckon! There is an additional caveat here that I've already mentioned in this PR: Windows does make a distinction between file and directory symlinks. I've adapted the |
If |
Cargo.toml
Outdated
@@ -16,6 +16,7 @@ libc = "0.2" | |||
rand = "0.6" | |||
cfg-if = "0.1.9" | |||
log = "0.4" | |||
lazy_static = "1.3.0" |
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.
This dependency is no longer needed.
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.
Ah, good catch, thanks!
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.
Other than the comment above, this LGTM, so feel free to merge when ready.
I don't yet have an opinion about splitting |
Unfortunately, that's very true on Windows. Perhaps this will require some more thought. :-) |
This way, we can re-enable nofollow_errors testcase on Windows also.
This PR moves
path_get
helper out of thesys
submodule and puts it in platform-independenthostcalls_impl::fs_helpers
module.It requiresreadlinkat
to be implemented which currently is missing on Windows, hence, this PR breaks WASI tutorial on Windows.It also strives at adding missing
path_
hostcalls so that we can enable more tests on Windows.TODO:
readlinkat
path_create_directory
path_remove_directory
path_readlink
path_symlink
path_unlink_file
EDIT: some basics of the above syscalls are now drafted out. I'm still uneasy about symlink handling on Windows for a couple of reasons:
path_unlink_file
is rather messy as we need to check whether we are dealing with a file or dir symlink and invoke appropriately eitherstd::fs::remove_file
orstd::fs::remove_dir
path_open
needs more love ;-)My current idea for symlinks on Windows is to create a thin, virtual wrapper
Symlink
(or similar) which will imitate *nix's functionality thus allowing for symlink loops and dangling symlinks until the symlink is actually made solid (by pointing to an actual resource). Then, theSymlink
should know whether it points to a file or a dir thus making the cleanup simpler.