How to check for file name too long error - go

While trying to create files, I am running into os.PathError because of "file name too long". I would like to handle this scenario to do something specific. How do I go about it, apart from inspecting error.Error which returns the string "file name too long"?

That error is system dependent, but on unix systems the error value is syscall.ENAMETOOLONG
if pe, ok := err.(*os.PathError); ok {
if pe.Err == syscall.ENAMETOOLONG {
log.Fatal("name really was too long")
}
}

Related

How to handle "no such file or directory" in os.Stat

I have a piece of code that is looking to stat a file and return some default values if the file does not exist. Namely, the piece of code looks something like the following:
fileInfo, err := os.Stat(absolutePath)
if err != nil {
if os.IsNotExist(err) {
return <default val>, nil
}
return nil, fmt.Errorf(...)
}
To "catch" the "file does not exist" error I have read that it's advised to use os.IsNotExist to check if the error indicates the file does not exist. However, os.IsNotExist does not catch this error since the platform returns open <blah>: no such file or directory. I can add my own error handling here but is there is an idiomatic way to handle "file does not exist" errors from os calls? It seems like the error handled by the os.IsNotExist check is special cased to just one type of potential "file does not exist" error.
If you read the documentation, you'll see this:
func IsNotExist
func IsNotExist(err error) bool
IsNotExist returns a boolean indicating whether the error is known to
report that a file or directory does not exist. It is satisfied by
ErrNotExist as well as some syscall errors.
This function predates errors.Is. It only supports errors returned by the os package.
New code should use errors.Is(err, fs.ErrNotExist). [emph. mine]

Proper way to get errno value out of PathError

I'm trying to determine whether an os.PathError is due to an EINVAL or ENOENT. How do I correctly make that determination?
res, err := os.Readlink(fpath)
if err, ok := err.(*os.PathError); ok {
if err.Err == os.ErrInvalid {
// This path here. What's the correct check?
return fpath
}
log.Printf("ResolveLinks error: %s", err)
return ""
}
log.Printf("Resolved: %s to %s", fpath, res)
return res
If fpath points to a regular file instead of a symlink, readlink should produce EINVAL, but my err.Err == os.ErrInvalid check fails and the following is logged:
2019/03/28 12:04:42 ResolveLinks error: readlink foo: invalid argument
I'm supposed to unpack the PathError, but then what? Compare the error string?
I notice that the os module has certain functions to match error types, like os.IsNotExist, but I don't see one for all possible error codes.
The err.Err is of type syscall.Errno and this is integer type that you can convert to int. Running this code on Ubuntu produces error code 22:
if serr, ok := err.Err.(syscall.Errno); ok {
fmt.Println(int(serr))
}
Bear in mind I do not know if this kind of check is cross platform or not.
if you just want to check file is symlink you can use Lstat to get FileInfo struct and do the check:
fi.Mode() & os.ModeSymlink != 0
I've found that the error's string will allow to match on the error type, but I'm not confident (at all) this works across platforms or even locales.
if err, ok := err.(*os.PathError); ok {
//EINVAL
if err.Err.Error() == "invalid argument" {
…
// - OR -
//ENOENT
if err.Err.Error() == "no such file or directory" {
…
}

How to properly check type of error returned by plugin.Open

I would like to know how can I check the type of error returned by plugin.Open, e.g:
package main
import "plugin"
func main() {
_, err := plugin.Open("./module.so")
// here
}
I would like to do something different if the error is:
plugin.Open("./module.so"): realpath failed
Which basically means that the file doesn't exist.
Example of desired result:
package main
import "plugin"
func main() {
_, err := plugin.Open("./module.so")
if err.Error() == "plugin.Open(\"./module.so\"): realpath failed" {
// do something different here
} else {
log.Fatal(err)
}
}
The string that I pass to plugin.Open can have other values, so it needs to be something more smart than that.
Thanks in advance.
Inspection of the code for plugin.Open() reveals the package calls out to some C code to determine whether the path exists. If it doesn't, it returns a plain error value. In particular, the package does not define any sentinel errors which you can compare against, nor does it return its own concrete implementer of the error interface which carries custom metadata. This is the code which produces that error:
return nil, errors.New(`plugin.Open("` + name + `"): realpath failed`)
errors.New is a basic implementation of the error interface which doesn't allow any additional information to be passed. Unlike other locations in the standard library which return errors (such as path non-existent errors from the os package), you can't get such metadata in this instance.
Check whether the module file exists first
My preference would be to verify whether the module exists before attempting to load it, using the native capabilities provided by the os package:
modulePath := "./module.so"
if _, err := os.Stat(modulePath); os.IsNotExist(err) {
// Do whatever is required on module not existing
}
// Continue to load the module – can be another branch of the if block
// above if necessary, depending on your desired control flow.
Compare a subset of the error values
You could also use strings.Contains to search for the value realpath failed in the returned error value. This is not a good idea in the event that string changes in future, so if you adopt this pattern, at the very least you should ensure you have rigorous tests around it (and even then it's still not great).
_, err := plugin.Open("./module.so")
if err != nil {
if strings.Contains(err.Error(), "realpath failed") {
// Do your fallback behavior for module not existing
log.Fatalf("module doesn't exist")
} else {
// Some other type of error
log.Fatalf("%+v", err)
}
}

Difference between windows symbolic links and directories

I run into a problem with Go when trying to tell difference between windows symbolic links and directories.
I've googled and all I could find was this:
https://github.com/golang/go/issues/3498#issuecomment-142810957
Which is unfortunately closed and not being worked on.
So my question is, is there any workaround? I tried to listdir the path with the symlinks but it is returning the same that it would return on an empty directory.
With python I was able to do something like this:
def test(dir):
try:
os.chdir(dir)
except Exception as e:
if "[Error 2]" in str(e):
return False
else:
return True
Is there any bash command I could use to call from go to detect it?
I'm running out of ideas :(
The only test I see (and I just tested it with go 1.5.1 on Windows) is in os/os_test.go:
fromstat, err = Lstat(from)
if err != nil {
t.Fatalf("lstat %q failed: %v", from, err)
}
if fromstat.Mode()&ModeSymlink == 0 {
t.Fatalf("symlink %q, %q did not create symlink", to, from)
}
It uses os/#Lstat:
Lstat returns a FileInfo describing the named file.
If the file is a symbolic link, the returned FileInfo describes the symbolic link. Lstat makes no attempt to follow the link.
If there is an error, it will be of type *PathError.
You can also get os.Stat() of the same folder, and then call os.Samefile() (as in this test):
if !SameFile(tostat, fromstat) {
t.Errorf("symlink %q, %q did not create symlink", to, from)
}

What is the best way to group errors in go?

I was looking at net/http and crypto/x509
I wondering which approach is better and why.
net/http/http.go uses strings:
// HTTP request parsing errors.
type ProtocolError struct {
ErrorString string
}
func (err *ProtocolError) Error() string { return err.ErrorString }
var (
ErrHeaderTooLong = &ProtocolError{"header too long"}
ErrShortBody = &ProtocolError{"entity body too short"}
ErrNotSupported = &ProtocolError{"feature not supported"}
ErrUnexpectedTrailer = &ProtocolError{"trailer header without chunked transfer encoding"}
ErrMissingContentLength = &ProtocolError{"missing ContentLength in HEAD response"}
ErrNotMultipart = &ProtocolError{"request Content-Type isn't multipart/form-data"}
ErrMissingBoundary = &ProtocolError{"no multipart boundary param in Content-Type"}
)
crypto/x509/verify.go uses ints:
type InvalidReason int
const (
TooManyIntermediates
IncompatibleUsage
)
type CertificateInvalidError struct {
Cert *Certificate
Reason InvalidReason
}
func (e CertificateInvalidError) Error() string {
switch e.Reason {
case TooManyIntermediates:
return "x509: too many intermediates for path length constraint"
case IncompatibleUsage:
return "x509: certificate specifies an incompatible key usage"
}
return "x509: unknown error"
}
Both usage are good, but it depends on your needs.
If you find it useful to attach additional data to the error that doesn't show in the error message, then the approach in crypto/x509 is preferable.
But I think in most cases, the simple error string as found in the errors package is sufficient.
Edit
An error can have different "attributes":
Describing
The Error() method should return a short describing error message
Identifiable
By letting a package export the different errors it might return, you can identify them. This is either done like in the io package by exporting initialized error variables of same type:
if err == io.EOF { ... } // That's easy
Or like in the encoding/json package by exporting the different error types:
if mErr, ok := err.(*json.MarshalerError); ok { ... } // That's fairly easy
Or by doing like they do in the crypto/x509 package, by exporting the different Reasons (error codes):
if e, ok := err.(x509.CertificateInvalidError); ok && e.Reason == x509.Expired { ... } // Well, it works
Unique error code
If errors should have specific codes due to a protocol spec, these could be embedded in the error variable. The crypto/x509 package might be used for that, even though it is probably not the case.
But when it comes to how to solve it, I think there is no best approach, nor any clearly idiomatic one. The examples above shows you ways to do it and ways it is done in the standard libraries. Take your pick.
.. but maybe not using switch statements.

Resources