如何判断文件夹是否存在并且可写?

I'd like func FolderExists(path string) bool that will tell whether a folder exists and is writable. I've gotten here:

func FolderExists(path string) bool {
    info, err := os.Stat(path)
    return os.IsExist(err) && info.Mode().IsDir() && info.Mode().???
}

How to tell if this folder is writable? I cannot simply check for filemode permissions (e.g. 0200 for user write permission) because then I'd have to check the owner of the file. Is there a straightforward way to do this in Go?

For those with UNIX backgrounds, looking for the equivalent of the very simple:

if [ -d "$n" && -w "$n" ] ; then ... fi

If you're writing for a Unix-y OS, you can use unix.Access(path string, mode uint32) error with the constant unix.W_OK as the mode.

As kostix says, you still have to check for an error when you access the directory (when you create or delete a file, say). The directory could have been removed since your check, or permissions could have changed, or you could get an entirely unrelated error.

(Thanks to user fxxn for pointing out that the situation has improved since the original answer in 2013, which was to use syscall.Access and extract the W_OK constant from unistd.h.)

Here's code that checks for existence and access that gets me the expected result on Linux:

package main

import (
    "fmt"
    "golang.org/x/sys/unix"
)

func writable(path string) bool {
    return unix.Access(path, unix.W_OK) == nil
}

func main() {
    fmt.Println("/etc writable?", writable("/etc"))
    fmt.Println("/tmp writable?", writable("/tmp"))
}

My answer won't answer your question directly but still…

In short: don't do that (unless maybe you're writing something embedded). Explanation: filesystems are fully concurrent and hence inherently they're a subject for race conditions. To put it simple, an sequence

  1. Check a file exists.
  2. If yes, read it.

Is not gonna work in real world as the file is perfectly able to disappear between (1) and (2) due to some other process deleting it.

So the only real way to write the above sequence is to drop (1) and be prepared to deal with a file open error in (2).

Returning to your problem, the only sensible way to go is to just try to create a file in the destination directory and be prepared to handle an error due to the directory being not writable. Moreover, Go's approach to error handling is specifically tailored for such (real world) situations.

There's not platform independent way to achieve this because checking whether the user created a directory is not possible on Windows. So, we will implement it differently for Windows and non-Windows system and then use Golang's build constraints to conditionally compile the files depending upon the OS.

Create 3 .go files under directory file-perm as shown -

file-perm /
    file-perm.go
    is-writable.go
    is-writable_windows.go

File : file-perm.go

package main

import (
    "fmt"
)

func main() {
    path := "/tmp/xyz"

    fmt.Println(IsWritable(path))
}

File : is-writable.go

This builds on all platforms except Windows because syscall.Stat_t is not available on Windows. Please note that there needs to be a blank line after the build constraint // +build !windows

// +build !windows

package main

import (
    "fmt"
    "os"
    "syscall"
)

func IsWritable(path string) (isWritable bool, err error) {
    isWritable = false
    info, err := os.Stat(path)
    if err != nil {
        fmt.Println("Path doesn't exist")
        return
    }

    err = nil
    if !info.IsDir() {
        fmt.Println("Path isn't a directory")
        return
    }

    // Check if the user bit is enabled in file permission
    if info.Mode().Perm() & (1 << (uint(7))) == 0 {
        fmt.Println("Write permission bit is not set on this file for user")
        return
    }

    var stat syscall.Stat_t
    if err = syscall.Stat(path, &stat); err != nil {
        fmt.Println("Unable to get stat")
        return
    }

    err = nil
    if uint32(os.Geteuid()) != stat.Uid {
        isWritable = false
        fmt.Println("User doesn't have permission to write to this directory")
        return
    }

    isWritable = true
    return
}

File : is-writable_windows.go

This file will build only on Windows because of the _windows.go suffix in the filename. For more info please refer to https://golang.org/pkg/go/build/

package main

import (
    "fmt"
    "os"
)

func IsWritable(path string) (isWritable bool, err error) {
    isWritable = false
    info, err := os.Stat(path)
    if err != nil {
        fmt.Println("Path doesn't exist")
        return
    }

    err = nil
    if !info.IsDir() {
        fmt.Println("Path isn't a directory")
        return
    }

    // Check if the user bit is enabled in file permission
    if info.Mode().Perm()&(1<<(uint(7))) == 0 {
        fmt.Println("Write permission bit is not set on this file for user")
        return
    }

    isWritable = true
    return
}

Now, you must use go build in file-perm directory and then run the executable. Please note that it won't work with go run.