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

13

我希望有一个func FolderExists(path string) bool函数来告诉我一个文件夹是否存在且可写。目前进展如下:

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

如何判断这个文件夹是否可写?我不能简单地检查文件模式权限(例如用户写入权限的0200),因为那样我就必须检查文件的所有者。在Go中有没有直接的方法来做到这一点?

对于那些具有UNIX背景的人,寻找非常简单等效的方法:

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

1
这种编程问题可能会很棘手;最健壮的解决方案也是最愚蠢的:尝试向目录写入内容 - 如果失败,则假定它不可写。 - thwd
2
我认为需要修改文件系统来检查权限似乎是一个疏忽。 - user1002430
3个回答

17

如果你正在编写Unix-y操作系统的代码,可以使用unix.Access(path string, mode uint32) error和常量unix.W_OK作为mode

(感谢用户fxxn标记添加了unix包,如果您想知道如何为Windows编写替代代码,请参见Gautham的答案。)

虽然这回答了提出的问题,但在实际访问目录时仍需要检查访问是否成功,也许这就是你想要做的全部。正如kostix所指出的那样,权限可能会在访问检查之后更改,或者可能存在无关错误。Ricky Zhang在评论中表示,在某些特定情况下,unix.Access返回的结果可能完全错误。如果你只检查实际操作是否成功,你也避免了与unix包中的特定于平台的函数纠缠不清。

显式访问检查只有在退出未来操作预计会失败时特别有帮助时才有必要。

无论如何,以下是检查存在和访问的代码,它在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"))
}

1
请注意,现在不应使用syscall软件包。它已被x/sys替换,并且还具有Access函数(参见godoc)甚至W_OK常量。 - fxnn
感谢,已刷新答案。 - twotwotwo
顺便提一下,这也适用于目录,以检查您是否可以在其中创建/删除文件。 - fxnn
1
没错。问题的范围似乎是正确的,但标题和我的回答并没有明确表明您可以使用unix.Access来检查所有这些内容。我编辑了标题和回答文本以更清晰地表达它。 - twotwotwo
1
我发现这种方法对于文件夹是NFS挂载的情况不起作用,因为此时正在使用UID映射。 - Ricky Zhang
显示剩余2条评论

7

由于在Windows系统中无法检查用户是否创建了文件夹,因此没有平台独立的方法来实现此操作。所以,我们将为Windows和非Windows系统分别实现它,并使用Golang的构建约束根据操作系统有条件地编译文件。

按下面所示在file-perm目录下创建3个.go文件 -

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))
}

文件:is-writable.go

在除 Windows 外的所有平台上都可以构建,因为 Windows 上没有 syscall.Stat_t。请注意,在构建约束 // +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
}

文件名:is-writable_windows.go

这个文件只能在Windows系统上构建,因为文件名中包含了_windows.go后缀。更多信息请参考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
}

现在,您必须在文件权限目录中使用go build,然后运行可执行文件。请注意,它不能与go run一起使用。

首先,syscall包已被弃用:调用者应该使用golang.org/x/sys中的相应包。https://golang.org/pkg/syscall/#pkg-overview 其次,我认为uint32(os.Geteuid()) != stat.Uid检查是错误的,因为它检查当前用户是否是该文件夹的所有者,而不是它是否具有写入权限,这才是所需的。 - Cirelli94

2

虽然我的回答不能直接回答你的问题,但仍然有用。

简而言之:不要这样做(除非你正在编写嵌入式代码)。解释:文件系统是完全并发的,因此它们本质上是容易受到竞态条件的影响的。简单来说,一个序列

  1. 检查文件是否存在
  2. 如果存在,读取它

在现实世界中不起作用,因为文件完全可以在(1)和(2)之间消失,因为其他进程删除了它。

因此,编写上述序列的唯一真正方法是放弃(1)并准备在(2)中处理文件打开错误。

回到你的问题,唯一明智的方法是尝试在目标目录中创建一个文件,并准备处理由于该目录不可写而引发的错误。此外,Go的错误处理方式特别适合处理这种(现实世界中的)情况。


我同意你仍然需要为写入时的错误(竞争条件、其他种类的错误)做好准备,但我认为检查访问权限的合理用例存在。也许它可以帮助你的程序立即给出有用的错误信息,否则失败的写操作要到完成了一堆工作之后才会出现。(由于 POSIX 设计者显然认为访问检查和写操作分离有一些用途,因此他们指定了一个系统调用来处理这个问题。) - twotwotwo
当你无法写入重要目录时,拒绝启动服务是完全有道理的。这样可以避免等待并在第一个请求失败。 - Gellweiler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接