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 Contains #23

Merged
merged 6 commits into from
Mar 24, 2019
Merged

Add Contains #23

merged 6 commits into from
Mar 24, 2019

Conversation

twpayne
Copy link
Owner

@twpayne twpayne commented Mar 21, 2019

This PR adds a expensive but (hopefully) accurate implementation of the deprecated filepath.HasPrefix, following discussions with @Merovius and @stapelberg at the Zurich Golang meetup organized by @SchumacherFM and independently proposed by @hirochachacha two years ago. Compared to @hirochachacha's implementation, it should correctly handle non-existent paths.

Refs golang/go#18358.

Reviews welcome.

Copy link

@Merovius Merovius left a comment

Choose a reason for hiding this comment

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

Some nits, some correctness-comments :)

hasprefix.go Outdated
// HasPrefix returns true if p or any parent of p is the same file as prefix.
// prefix must exist, but p may not. It is an expensive but accurate alternative
// to the deprecated filepath.HasPrefix.
func HasPrefix(fs Stater, p, prefix string) (bool, error) {

Choose a reason for hiding this comment

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

IMO "HasPrefix" is a bit misleading. To me, it suggests that you're operating on paths, but you're actually operating on actual FS datastructures. "IsIn" or "Contains" or something like that would be more accurate.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Good point. Renamed to Contains.

hasprefix.go Outdated
case os.IsNotExist(err):
// Do nothing and skip ahead to trying p's parent directory.
default:
return false, err

Choose a reason for hiding this comment

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

IMO this is not correct. If the error is another error (say, a permission denied error), p can still be in a subdirectory of prefix.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Good point again. I've update the code to handle more error cases.

hasprefix.go Outdated
default:
return false, err
}
parentDir := filepath.Dir(p)

Choose a reason for hiding this comment

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

There needs to be some thought about how you want to deal with symlinks etc. For example, if p denotes a symlink into prefix (say prefix == "/home/mero" and p is /tmp/foo, pointing at /home/mero/foo), should this function return true, or false? And on the other hand, if p is a symlink inside prefix, pointing out of it (say prefix == /home/mero and p is /home/mero/foo pointing at /tmp/foo)? It really depends on the semantic of what you want to achieve.

Just as food for thought: It would be possible to use Open in combination with Openat to walk the actual inodes. That should make this totally, 100% referring to the on-disk structures, respecting all permissions and symlinks and the like. Would obviously only work on posix operating systems. I think that mainly excludes Plan 9 currently? Dunno.

Again, depends on the semantics that you want to achieve.

Copy link
Owner Author

Choose a reason for hiding this comment

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

That's really insightful. I've updated the comment:

// Contains returns true if p is reachable by traversing through prefix. prefix
// must exist, but p may not. It is an expensive but accurate alternative to the
// deprecated filepath.HasPrefix.

I think this is enough for now, but you're right that there's a lot of subtlety here.

hasprefix.go Outdated
fi, err := fs.Stat(p)
switch {
case err == nil:
if os.SameFile(fi, prefixFI) {

Choose a reason for hiding this comment

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

Note that os.SameFile only works if the os.FileInfo was returned by the os-package. Meaning, your Stater interface doesn't fully capture the requirements of this function - if I implement it myself, with a custom os.FileInfo, this will probably fail.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Good point, but I suggest leaving this unchanged. Reasoning:

  • This package does not abstract *os.File (by design) and all operations fall back to the underlying os package. Therefore the os.FileInfo returned by fs.Stat should always be compatible with os.SameFile.

  • Note that we have similar problems with methods like os.IsNotExist and os.IsPermission: these make assumptions about the error argument being in some way compatible with the underlying os package.

  • At this point, I don't want to make os.SameFile, os.IsNotExist, and os.IsPermission methods of the FS interface to avoid cluttering it too much. But you're right, technically they should be.

Thanks again for the deep insight :)

Copy link
Owner Author

Choose a reason for hiding this comment

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

I added a comment about assumptions about fs.Stat.

@twpayne
Copy link
Owner Author

twpayne commented Mar 23, 2019

Thank you @Merovius for one of the most through and insightful code reviews that I've ever had!

@twpayne twpayne changed the title Add HasPrefix Add Contains Mar 23, 2019
@twpayne twpayne merged commit 61a44b1 into master Mar 24, 2019
@twpayne twpayne deleted the has-prefix branch March 24, 2019 10:50
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.

2 participants