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

lock: wrap the returned error #54

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open

Conversation

djdv
Copy link
Contributor

@djdv djdv commented Jan 25, 2021

Context: ipfs/go-fs-lock#19
One of the libraries using the lock package is currently doing string compares on the error values to distinguish the cause of failure.
It seems like it would be better to expose the system specific errors to the caller, so that they may be used in conjuncture with Is and As.

Thoughts on this?

@@ -54,12 +54,12 @@ func (u *winUnlocker) close() {
func lockWindows(name string) (io.Closer, error) {
fi, err := os.Stat(name)
if err == nil && fi.Size() > 0 {
return nil, fmt.Errorf("can't lock file %q: %s", name, "has non-zero size")
return nil, fmt.Errorf("can't lock file %s: has non-zero size", name)

Choose a reason for hiding this comment

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

The use of %q here was likely intentional.

Using a simple %s for filenames can often lead to confusing error output: (https://play.golang.org/p/fiuJEvghF7y)

open : no such file or directory

While we can clearly see exactly why the output here is what it is in this simple case, in the real world, it can often be much more difficult to realize that there is in fact a filename in that error message.

Copy link
Contributor Author

@djdv djdv Jan 25, 2021

Choose a reason for hiding this comment

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

It's true! I put that %q there a while ago for that reason. However, when looking at it today I noticed the Unix files use %s.
The other issue though, is that while %q avoids the empty string issue, it formats to "string safely escaped with Go syntax".
In this case, it causes "fencing"(or whatever you'd call this escaping) in Windows paths. ("C:\tmp\lock" becomes "C:\\tmp\\lock")

We can avoid both though by manually providing quotations here:
https://play.golang.org/p/RO0HBOIrdz7

Suggested change
return nil, fmt.Errorf("can't lock file %s: has non-zero size", name)
return nil, fmt.Errorf(`can't lock file "%s": has non-zero size`, name)

The same could be added in the lock_unix file, or their %s changed to a %q.

Which do you think would make more sense to use in the _unix file? I'm thinking manual quotes just for consistency.

Choose a reason for hiding this comment

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

Ah yeah. Hm. An interesting dilemma there. I‘ve simply accepted that backslashes get doubled in the Windows code. I suppose I was already deadened to it after years of dealing with backslash doubling, and quadrupling, and worse while at Microsoft. 😆

I’m not sure we want to use manual quotes either though, because then it doesn’t escape quotes in the string as well. Which, sure they cannot appear in Windows filenames, but this is an error message about “cannot make such a file”, so it could still happen.

I mean, also https://play.golang.org/p/pZmjyiqj5M3 this wonderful piece of bad, but I think that one is playing way way more into the weeds of what to protect against. 🤔 Maybe we could just if name == "" { name = "" } 😆

Not sure, I’m down with going to %s, the rest of the errors do this, and so do the internal Go libraries. At least we won’t end up doing anything worse than them.

return 0, err
}
return handle, nil
return windows.CreateFile(windows.StringToUTF16Ptr(name), 0, 0, nil, windows.OPEN_ALWAYS, FILE_ATTRIBUTE_TEMPORARY|FILE_FLAG_DELETE_ON_CLOSE, 0)

Choose a reason for hiding this comment

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

On error, CreateFileW (the underlying call here) returns INVALID_HANDLE_VALUE (windows.InvalidHandle) (https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew) which is defined to be -1 not 0: (https://referencesource.microsoft.com/#System.Runtime.Remoting/channels/ipc/win32namedpipes.cs,89)

I imagine this is why the if err != nil { return 0, err } is there, i.e. to normalize the return value to the zero value in the case of an error.

Though, this might not be a big deal, as the handle can be considered an undefined value in the event err != nil and so, should actually be rarely/never used. But this is a significant change of behavior that needs to be checked, and double-checked.

Copy link
Contributor Author

@djdv djdv Jan 25, 2021

Choose a reason for hiding this comment

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

i.e. to normalize the return value to the zero value in the case of an error.

I wrote this originally and think that was the rationale.

Currently, the only caller lockWindows, immediately discards the handle if err != nil.
But I figure, if we're hoisting the error values, we may want to do the same with the system error.
And let the caller decide if any special action should be taken, or just bail out if they encounter any error.

A nice thing, is that this value is explicitly defined by the WINAPI to be an invalid handle, so even if it mistakenly propagates, passing that handle to windows/WINAPI methods should detect this, and return an error themselves saying the handle passed in was invalid.
*I think this is mostly true ^

That said, I don't think CreateFile's r1 will ever return a value besides -1, and we're more interested in r2 the GetLastError value anyhow.
So it seems we can pass back both to the caller, or normalize r1.

I'm fine with either since r2 seems most important here.

Choose a reason for hiding this comment

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

👍 sounds good. It’s also an unexported function, so we can actually fairly definitively make sure this won’t break any code.

@djdv
Copy link
Contributor Author

djdv commented Jan 26, 2021

I think this is is order now. Thanks for the help with the details :^)

Names are getting escaped and quoted by the fmt verb %q, and the errors themselves are being wrapped by %w.
So the caller should have everything they need to both print or compare the error value received.
(There is a small typo fix, and a missing : was added to one of the error messages)

@puellanivis
Copy link

Indeed, I also don’t see any concerns.

- use fmt verb %w to wrap system native error values
- use fmt verb %q to quote and escape file names in error strings
@djdv
Copy link
Contributor Author

djdv commented Jan 28, 2021

Squashed the commits and added a description.
Unless something new comes up, I think this could be merged. :^)

@djdv
Copy link
Contributor Author

djdv commented Feb 9, 2021

Pinging @mpl or @bradfitz
Can I get a final look at this? Potentially ready to merge.
(I have no auth)

@puellanivis
Copy link

I unfortunately also do not have any auth to make the merge.

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