idiomatic way to get os err after call - go

If I do
s, err := os.Stat(path)
and err != nil I need to know if the file doesn't exist vs I don't have permission to access it, etc. How do I get the underlying error code? Reading the os package docs it seems to suggest that I read the text of the error string - surely not?

What FUZxxl says.
From the os.Stat documentation:
Stat returns a FileInfo describing the named file. If there is an error, it will be of type *PathError.
PathError is documented on the same page, stating that it holds the operation that caused the error, the path to the file that caused it and the underlying system's error. In case the file was not found when calling os.Stat, the returned error would be something like this:
&PathError{"stat", "/your/file", syscall.Errno(2)}
Since the underlying error is inherently depending on the OS you use, the only thing that you can do is to
understand PathError.Err. For UNIX systems the syscall package has the Errno error type returned by syscalls like syscall.Stat. You can compare this value with the constants in the syscall package and handle the error (Click to play):
stat, err := os.Stat(file)
if perr, ok := err.(*os.PathError); ok {
switch perr.Err.(syscall.Errno) {
case syscall.ENOENT: fmt.Println("No such file or directory.")
default: panic("Unknown error")
}
}
The shorter way of doing this is to use os.IsNotExist which does pretty much the above
and is, most importantly, platform independent:
stat, err := os.Stat(file)
if err != nil && os.IsNotExist(err) {
// ...
}

The other answer is great, but I wanted add a note about this suggestion:
stat, err := os.Stat(file)
if err != nil && os.IsNotExist(err) {
// ...
}
I found that in many cases, I was needing to take different actions depending on
each test, so in reality you have three branches here. Here is code I use for
that:
stat, err := os.Stat(file)
if os.IsNotExist(err) {
// branch one
} else if err != nil {
// branch two
}
// branch three

Related

Is it a problem to pass a single error to errors.Join?

Go 1.20 introduces the errors.Join function that can wrap multiple errors. Are there any issues with calling this function and only passing in a single error?
For example, this article recommends against using the defer f.Close() idiom for writable files, because that would silently ignore any error returned by Close. Instead, it suggests using a named return value err to allow the return value of Close to be be propagated - unless doing so would overwrite an earlier error:
defer func() {
cerr := f.Close()
if err == nil {
err = cerr
}
}()
It seems more correct to use errors.Join in this scenario:
defer func() {
cerr := f.Close()
err = errors.Join(err, cerr)
}()
If both err and cerr are non-nil, this will now return both errors. If both are nil, it will return nil.
However, if one is nil and the other non-nil, errors.Join will not just return the non-nil error but an errors.joinError wrapper around it. Could wrapping an error like this cause any problems? Especially if several functions in the call stack use this approach, so a single error could end up within multiple layers of wrapper?
If errors.JoinError has only one non-nil error, that is still a join-error, and errors.As and errors.Is functions work as expected. This is correct no matter the level of nesting of joined errors.
The only potential problem would be if there is code like:
err:=someFunc()
if err==io.EOF {
...
}
then this will fail. This code has to be rewritten to use errors.Is.

Are these line codes the same in golang?

Is A same as B?
A
if err := json.NewDecoder(r.Body).Decode(&t); err != nil {
rnd.JSON(w, http.StatusProcessing, err)
return
}
B
err := json.NewDecoder(r.Body).Decode(&t);
if err != nil {
rnd.JSON(w, http.StatusProcessing, err)
return
}
They are equivalent except one difference: the scope of the err variable. In the A version the scope of the err variable is the if statement: it's not accessible after the if.
In the B version the err variable will be in scope after the if statement too, and if err is already defined earlier, it could result in a compile-time error too.
It's good practice to always minimize the scope of variables (which gives you less chance to misuse them). If you do not wish to further examine the returned error after the if, it's better to use the A version. If you do need it after the if, then obviously the B version is the way to go.

How to overwrite a symlink in Go?

I would like to overwrite a symlink using Go but I couldn't find how to do it.
If I try to create the symlink and it already exists an error is returned.
My code:
err := os.Symlink(filePath, symlinkPath)
if err != nil {
fmt.Println(err)
}
I guess the symlink must be removed and then created again. Is that right? If so, how can I unlink the symlink?
Just check that symlink exists and delete it before creating new one
if _, err := os.Lstat(symlinkPath); err == nil {
os.Remove(symlinkPath)
}
Note that #Vadyus's answer hides actual filesystem errors while running lstat. For example, if your disk is broken and Lstat fails, you will still run os.Remove and ignore its error (DANGEROUS, unless you like to debug things for hours).
The following snippets checks for file existence and other errors correctly:
if _, err := os.Lstat(symlinkPath); err == nil {
if err := os.Remove(symlinkPath); err != nil {
return fmt.Errorf("failed to unlink: %+v", err)
}
} else if os.IsNotExist(err) {
return fmt.Errorf("failed to check symlink: %+v", err)
}
The other answers here are correct...but there are two small issues:
There is a tiny data race where the new symlink will be created elsewhere before here but after the delete, leaving it in a potentially inconsistent state.
If the program were to die / crash before the symlink is created but after the previous one is deleted, it may again leave things in an inconsistent state.
The more atomic way to handle this is to create a temporary symlink and then rename it over the original:
symlinkPathTmp := symlinkPath + ".tmp"
if err := os.Remove(symlinkPathTmp); err != nil && !os.IsNotExist(err) {
return err
}
if err := os.Symlink(filePath, symlinkPathTmp); err != nil {
return err
}
if err := os.Rename(symlinkPathTmp, symlinkPath); err != nil {
return err
}
There is still a small race between deleting the temporary link and re-creating it, but it won't risk leaving the primarily link in an inconsistent state. Ideally, we would be able to work around that by using a randomized name for the temporary link, but Go's TempFile always creates a new file, so it isn't quite as useful. (You could maybe call TempFile, then delete the file name and re-use the name, which would be riskier but still safer then just appending a constant .tmp suffix.)
Even with that race however, you still gain the atomic-ness where any interruptions won't result in a missing link.
Note that this is dependent on Posix behavior and may not work on Windows (why would you be using symlinks on Windows anyway?), but it's a technique shared by many macOS/Linux tools that need atomic symlink replacements.

golang zlib reader output not being copied over to stdout

I've modified the official documentation example for the zlib package to use an opened file rather than a set of hardcoded bytes (code below).
The code reads in the contents of a source text file and compresses it with the zlib package. I then try to read back the compressed file and print its decompressed contents into stdout.
The code doesn't error, but it also doesn't do what I expect it to do; which is to display the decompressed file contents into stdout.
Also: is there another way of displaying this information, rather than using io.Copy?
package main
import (
"compress/zlib"
"io"
"log"
"os"
)
func main() {
var err error
// This defends against an error preventing `defer` from being called
// As log.Fatal otherwise calls `os.Exit`
defer func() {
if err != nil {
log.Fatalln("\nDeferred log: \n", err)
}
}()
src, err := os.Open("source.txt")
if err != nil {
return
}
defer src.Close()
dest, err := os.Create("new.txt")
if err != nil {
return
}
defer dest.Close()
zdest := zlib.NewWriter(dest)
defer zdest.Close()
if _, err := io.Copy(zdest, src); err != nil {
return
}
n, err := os.Open("new.txt")
if err != nil {
return
}
r, err := zlib.NewReader(n)
if err != nil {
return
}
defer r.Close()
io.Copy(os.Stdout, r)
err = os.Remove("new.txt")
if err != nil {
return
}
}
Your defer func doesn't do anything, because you're shadowing the err variable on every new assignment. If you want a defer to run, return from a separate function, and call log.Fatal after the return statement.
As for why you're not seeing any output, it's because you're deferring all the Close calls. The zlib.Writer isn't flushed until after the function exits, and neither is the destination file. Call Close() explicitly where you need it.
zdest := zlib.NewWriter(dest)
if _, err := io.Copy(zdest, src); err != nil {
log.Fatal(err)
}
zdest.Close()
dest.Close()
I think you messed up the code logic with all this defer stuff and your "trick" err checking.
Files are definitively written when flushed or closed. You just copy into new.txt without closing it before opening it to read it.
Defering the closing of the file is neat inside a function which has multiple exits: It makes sure the file is closed once the function is left. But your main requires the new.txt to be closed after the copy, before re-opening it. So don't defer the close here.
BTW: Your defense against log.Fatal terminating the code without calling your defers is, well, at least strange. The files are all put into some proper state by the OS, there is absolutely no need to complicate the stuff like this.
Check the error from the second Copy:
2015/12/22 19:00:33
Deferred log:
unexpected EOF
exit status 1
The thing is, you need to close zdest immediately after you've done writing. Close it after the first Copy and it works.
I would have suggested to use io.MultiWriter.
In this way you read only once from src. Not much gain for small files but is faster for bigger files.
w := io.MultiWriter(dest, os.Stdout)

how do I check for errors when calling os.Open(<filename>) in Go?

I'm new to Go (spent 30mins so far!) and am trying to do File I/O.
file, ok := os.Open("../../sample.txt")
if ok != nil {
// error handling code here
os.Exit(1)
}
...
When the call fails, shouldn't it return an error number? This call returns os.Error and it has no methods other than 'String()'.
Is this the recommended way to check for errors in Go?
Typical Go code (which uses the os package) is not analyzing the returned error object. It just prints the error message to the user (who then knows what went wrong based on the printed message) or returns the error as-is to the caller.
If you want to prevent your program from opening a non-existent file, or want to check whether the file is readable/writable, I wound suggest to use the os.Stat function prior to opening the file.
Your can analyze the Go type of the returned error, but this seems inconvenient:
package main
import "fmt"
import "os"
func main() {
_, err := os.Open("non-existent")
if err != nil {
fmt.Printf("err has type %T\n", err)
if err2, ok := err.(*os.PathError); ok {
fmt.Printf("err2 has type %T\n", err2.Error)
if errno, ok := err2.Error.(os.Errno); ok {
fmt.Fprintf(os.Stderr, "errno=%d\n", int64(errno))
}
}
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}
}
which prints:
err has type *os.PathError
err2 has type os.Errno
errno=2
open non-existent: no such file or directory
Yes this is the normal way in Go (multi-value return), the makers of Go have a different view on Exceptions and handle it in this way.
Read this:
http://www.softmachinecubed.com/tech/2009/12/6/googles-go-language-multi-value-return-vs-exceptions-c.html

Resources