buffer: Store potential resolved symlinks as AbsPath#3997
buffer: Store potential resolved symlinks as AbsPath#3997JoeKar wants to merge 3 commits intomicro-editor:masterfrom
AbsPath#3997Conversation
|
I've noticed an "issue" with this PR. If we have a regular file For comparison, vim shows the resolved file name in the statusline, so it behaves predictably: it always shows the original file name. Well, maybe we can treat this as a feature ("first wins" rule)... |
Hm, indeed...this behavior is somehow strange. EDIT: EDIT2: |
|
Hmmmm... what is this? https://pkg.go.dev/path/filepath#EvalSymlinks :) Worth checking if we can just use this? Also, just realized: we should resolve each component of the path (i.e. each directory), not just the last one (the file itself), since any of the directories may be a symlink too? And skimming through the code in https://github.com/golang/go/blob/master/src/path/filepath/symlink.go it looks like this |
Whaaat...feels like this came out of nowhere. Don't know, why I didn't see this myself, especially since I had [filepath](the https://pkg.go.dev/path/filepath) documentation the last days open again. Thank you for pointing me to this link! |
5e77c5a to
9191df8
Compare
Otherwise we can't identify if we have the same file open multiple times via different symlinks. The test must be adapted to resolve symlinks in `findBuffer()`.
Otherwise it will be removed async, which shouldn't happen in case there is still one buffer open with the same modified file.
Otherwise we unnecessarily serialize the shared buffer every time when closing a bufpane with this buffer, so every such serialize overwrites the previous one, thus only the last serialize (when closing the last instance of the buffer, i.e. when actually closing the file, i.e. when the buffer is not shared anymore) will be used anyway.
| absPath := path | ||
| if btype == BTDefault && path != "" { | ||
| // Ignore the returned error, since the checks are already performed in | ||
| // NewBufferFromFileWithCommand() |
There was a problem hiding this comment.
Strictly speaking, it may fail here even if the checks in NewBufferFromFileWithCommand() didn't fail, since the state of the file may have changed in the meantime? https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use
And, checking and returning this error here isn't particularly hard?
There was a problem hiding this comment.
And, checking and returning this error here isn't particularly hard?
Here I agree, but...
Strictly speaking, it may fail here even if the checks in
NewBufferFromFileWithCommand()didn't fail, since the state of the file may have changed in the meantime?
...and here I only partially agree.
How often can it still change afterwards over the lifetime of the open buffer? The file, related to the stored path, is opened again at save and till then TOC/TOU is still a thing.
I though about this while writing, but didn't consider it for this PR.
The problem is, that we only track the path and no concrete file handle or any other sort of file abstraction, because we close it at exit of NewBufferFromFileWithCommand().
There was a problem hiding this comment.
I don't think we should be responsible for the file throughout the lifetime of a buffer. I doubt any editor does that, and I don't think that would be a good idea.
What we are responsible for is preventing micro from crashing or behaving unpredictably.
So, here I'm only talking about a simple issue: filepath.EvalSymlinks() may still return an error here (even if that's unlikely), so, what will be the value of path then? The documentation in https://pkg.go.dev/path/filepath#EvalSymlinks doesn't say what is returned as the 1st return value in case of an error. We might optimistically assume that it will be just the original path (without symlinks resolved), or e.g. with symlinks just partially resolved, but still a correct path. And we will be wrong: from the source code in https://github.com/golang/go/blob/master/src/path/filepath/symlink.go we can see that it will be just an empty string, not a file path.
So, it will silently cause incorrect behavior: we will open an empty unnamed buffer, instead of opening the file the user asked us to open?
There was a problem hiding this comment.
BTW I've noticed another issue: we should resolve the absolute path as well. The current working directory path may contain symlinks too, so if we only resolve the relative path, and prepend the current working directory path to it after that, the resulting absolute path will not be fully resolved.
For example:
mkdir dir
ln -s dir dir1
echo test >dir/file
cd dir1
micro -multiopen vsplit file ../dir/file
So we should do something like this?
resolvedPath, err := filepath.EvalSymlinks(path)
if err == nil {
path = resolvedPath
}
absPath, err = filepath.Abs(path)
if err != nil {
absPath = path
}
resolvedPath, err = filepath.EvalSymlinks(absPath)
if err == nil {
absPath = resolvedPath
}BTW since we are doing this in 2 or even 3 places, we should add a helper?
This will help us to keep track of the same file opened via different symlinks.
Fixes #3995