Skip to content
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

Add skeleton reimplementation of sandboxfs in Rust #23

Merged
merged 5 commits into from
Aug 3, 2018

Conversation

jmmv
Copy link
Contributor

@jmmv jmmv commented Jun 4, 2018

This change adds a barebones reimplementation of sandboxfs in Rust.
As things are now, the new version does nothing: it just provides
trivial command-line processing and mounts an empty FUSE file
system.

However, this change puts all pieces in place to continue development
of this new version. In particular, this adjusts the Travis CI
configuration to support running the exact same integration tests as
we run for the Go implementation against the new code, and currently
blacklists all of them because the new code passes none.

# For testing the Rust implementation, we only need to build the tests using
# one Go version.
- env: DO=rust
go: 1.8
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that stable is 1.10, are you sure you want to pin back to 1.8?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this is an exclusion and it's actually saying "for the Rust build, don't bother using Go 1.8". Thanks Travis for making this so obvious!

I guess I should update the configuration to use 1.10 now, but let's not conflate that change with this review ;-)

# pass all integration tests. This blacklist keeps track of which those are.
# Ideally, by the time this alternative implementation is ready, this list
# will be empty and we can then refactor this file to just test one version.
local blacklist=(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like there should be an issue tracking the progress on this blacklist that is linked from this comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe. But this project (including the previous implementation) is all experimental and under active development. I don't see much value in tracking this in a bug at this point.

TestSignal_QueuedWhileInUse
)

# TODO(jmmv): Replace by a Bazel-based build once the Rust rules are
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Link to an open issue in a tracker somewhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


# Compute the list of tests to run by comparing the full list of tests that
# we got from the test program and taking out all blacklisted tests. This is
# O(n^2), sure, but it doesn't matter: the list is small and this will go away
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could use associative arrays if you know it's bash.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, macOS's ancient /bin/bash (3.2.57) doesn't support this feature and we do have a Travis build on macOS. (I haven't double-checked if Travis installs a newer bash; maybe it does, but I'm lazy and don't think it's super-important to improve this.)

done
set -x

[ "${#valid[@]}" -gt 0 ] || return 0 # Only run tests if any are valid.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is unnecessary. The for loop won't do anything on an empty array.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah... I'm not even sure why I wrote this. I must have had some extra code in the past that evaluated to non-zero and caused set -e to fail the build.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I remember now. Removed this line and Travis started failing again:

./admin/travis-build.sh: line 179: valid[@]: unbound variable

I've seen bugs in macOS's ancient bash that cause this, but I'm a bit surprised to see that Linux is failing as well.

src/main.rs Outdated

impl From<io::Error> for MainError {
fn from(err: io::Error) -> Self {
MainError::Runtime(format!("{}", err))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

io::Error should probably be embedded instead of flattened. Consider using withoutboats's failure crate to automate this logic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still trying to figure this one out... but because GitHub didn't let me batch replies... writing this reply to avoid confusion.

src/main.rs Outdated
/// Obtains the program name from the execution's first argument, or returns a
/// default if the program name cannot be determined for whatever reason.
fn program_name(args: &[String], default: &'static str) -> String {
args.get(0)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Long chains like this are hard to read. Instead, break out of the monad early and use regular code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good. Somehow I thought this was common style in Rust as I've seen it in many places... but glad to see I'm not the only one thinking it's hard to read.

I've replaced this with a match chain... which maybe is also hard?

src/main.rs Outdated
.unwrap_or(default.to_string())
}

/// Prints program usage information to stdout.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usage is generally printed to stderr, not stdout.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. That gets in the way of sandboxfs --help | grep foo, which is very frustrating. Note that I'm only printing usage when explicitly asked to, and when that's the case, the output belongs in stdout because that's what the user asked; it's not an error.

Self-citation: http://julio.meroh.net/2013/08/cli-design-requesting-and-offering-help.html

src/main.rs Outdated
process::exit(1);
},
Err(MainError::Usage(message)) => {
eprintln!("Usage error: {}", message);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this message needs to be prefixed.

src/main.rs Outdated
},
Err(MainError::Usage(message)) => {
eprintln!("Usage error: {}", message);
eprintln!("Type {} --help for more information", program);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just print the usage at this point?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because I find it incredibly annoying to have a long usage message printed when I didn't ask for one. Such a long message clobbers the actual problem that triggered this because that first line vanishes among the noise.

@jmmv
Copy link
Contributor Author

jmmv commented Jun 15, 2018

Alright. I think I have addressed everything. But oh what a mess GitHub is. I can't tell why I couldn't bundle all replies to your commits in one "review step" and instead had to reply to them individually. I'm sure you have received too many notifications...

@jmmv jmmv requested a review from hlopko June 15, 2018 21:22
.collect::<Vec<&OsStr>>();
let fs = SandboxFS::new();
info!("Mounting file system onto {:?}", mount_point);
fuse::mount(fs, &mount_point, &options)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh, this is a bug in their API. They're requesting &P instead of P, but forgot to specify ?Sized. Honestly, they should probably not be requesting &P, since they're indirecting via AsRef.

src/main.rs Outdated
Err(MainError::Usage(message)) => {
eprintln!("Usage error: {}", message);
if let Err(err) = safe_main(&program, &args[1..]) {
if let Some(err) = err.cause().downcast_ref::<UsageError>() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can downcast_ref the error directly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't figure out what you mean by this... (and especially not after a month since I dealt with this code).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove .cause()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Done.

@jmmv
Copy link
Contributor Author

jmmv commented Jul 25, 2018

Thanks again for reviewing this and apologies for the long delay in getting back to this change. Have been mostly on vacation since then.

.collect::<Vec<&OsStr>>();
let fs = SandboxFS::new();
info!("Mounting file system onto {:?}", mount_point);
fuse::mount(fs, &mount_point, &options)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively you could just add ?Sized to their constraint: P: AsRef<Path>+?Sized

jmmv and others added 4 commits July 26, 2018 11:43
This change adds a barebones reimplementation of sandboxfs in Rust.
As things are now, the new version does nothing: it just provides
trivial command-line processing and mounts an empty FUSE file
system.

However, this change puts all pieces in place to continue development
of this new version.  In particular, this adjusts the Travis CI
configuration to support running the exact same integration tests as
we run for the Go implementation against the new code, and currently
blacklists all of them because the new code passes none.
@jmmv
Copy link
Contributor Author

jmmv commented Jul 31, 2018

Ping @mhlopko for Bazel project review.

Copy link
Member

@hlopko hlopko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thought I've lgtmed this already.

@jmmv jmmv merged commit 0b78fae into bazelbuild:master Aug 3, 2018
@jmmv jmmv deleted the rust branch August 3, 2018 15:48
},
None => default,
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I find using the methods on Option much easier to understand: https://rust.godbolt.org/z/sGTXht

You can also return a &str to save an allocation but it probably isn't worth it in this case: https://rust.godbolt.org/z/7wGCfT

&matches.free[0]
} else {
return Err(Error::from(UsageError { message: "invalid number of arguments".to_string() }));
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I think it would be easier to read with the failure first.

if matches.free.len() != 1 {
 return Err(...)
}
let mount_point = &matches.free[0];

use std::env;
use std::path::Path;
use std::process;
use std::result::Result;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is in the prelude and shouldn't have to be imported explicitly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants