Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detect cause of failing rename

Tags:

go

I use some code similar to the lines below. The rename can fail if the directory is blocked because some application has a file open in it.

err := os.Rename("C:/temp/inUse", "c:/temp/Renamed")

if err != nil {
    fmt.Println(err)
    ...
}

I am able to detect this fact when I check err for this content:

rename C:/temp/inUse c:/temp/Renamed: Der Prozess kann nicht auf die Datei zugreifen, da sie von einem anderen Prozess verwendet wird.

(which translates to "The process is unable to access the file because it is used by a different process") But this message varies depending on the OS language.

Is it possible to detect the problem by a unique "error code" ? I wouldn't mind if the solution is specific to Windows and doesn't work on other operating systems supported by Go.

Ideally I could do something like if err.Err == 32 { fmt.Println( "Your directory is in use please ...

like image 991
Marged Avatar asked Apr 27 '16 20:04

Marged


Video Answer


2 Answers

The error returned by os.Rename is of type *os.LinkError which gives you access to the underlying error from the operating system. You should be able to use that to distinguish the particular error you are encountering.

You'll need to convert the error to *os.LinkError first.

For instance, trying to rename a folder (/Users/t/pprof) to another name that is write-protected (/Users/t/pprof2):

func TestRename(t *testing.T) {
    err := os.Rename("/Users/t/pprof", "/Users/t/pprof2")
    if err != nil {
        e := err.(*os.LinkError)
        t.Logf("Op: ", e.Op)
        t.Logf("Old: ", e.Old)
        t.Logf("New: ", e.New)
        t.Logf("Err: ", e.Err)
    }
}

Provides the following output:

Op: %!(EXTRA string=rename)
Old: %!(EXTRA string=/Users/t/pprof)
New: %!(EXTRA string=/Users/t/pprof2)
Err: %!(EXTRA syscall.Errno=operation not permitted)

The OS error code is accessible as the Err member, but will be different depending on the OS you are running.

The Err member is of type syscall.Errno. To further inspect the actual error, you need to convert it to that type first:

oserr := e.Err.(syscall.Errno)

Now oserr can be compared with the values for Errno declared in the syscall package. You'll find them if you search for ENOENT on that page.

For instance, you can check for your particular error by doing:

switch oserr {
    case syscall.ENOENT:
        // Handle this error
    default:
        // Handle other errors
    }
}

In general, it's very convenient to use fmt.Printf when debugging these sort of issues. In the example above:

fmt.Println(err)

prints

rename /Users/t/pprof /Users/t/pprof2: operation not permitted

where-as

fmt.Printf("%#v\n", err)

prints

&os.LinkError{Op:"rename", Old:"/Users/t/pprof", New:"/Users/t/pprof2", Err:0x1}

giving details on the actual error, and not just its string representation.

like image 169
Andreas Schyman Avatar answered Oct 02 '22 20:10

Andreas Schyman


Starting with Go 1.13, if you just want to check if the underlying error is of a specific type you can do:

err := os.Rename("C:/temp/inUse", "c:/temp/Renamed")

if err != nil && errors.Is(err, syscall.EEXIST) {
    fmt.Println("File already exists")
    ...
}

You can read more about error handling in Go 1.13 on the official Golang blog.

like image 28
rocku Avatar answered Oct 02 '22 20:10

rocku