Can';不要在MacOS上的`/dev/fd`上调用'ioutil.ReadDir'

Can';不要在MacOS上的`/dev/fd`上调用'ioutil.ReadDir',macos,go,Macos,Go,我尝试运行以下Go代码: package main import ( "fmt" "io/ioutil" ) func main() { items, err := ioutil.ReadDir("/dev/fd") if err != nil { panic(err) } fmt.Println(items) } 我刚刚得到这个错误: panic: lstat /dev/fd/4: bad file descriptor

我尝试运行以下Go代码:

package main

import (
    "fmt"
    "io/ioutil"
)

func main() {
    items, err := ioutil.ReadDir("/dev/fd")
    if err != nil {
        panic(err)
    }
    fmt.Println(items)
}
我刚刚得到这个错误:

panic: lstat /dev/fd/4: bad file descriptor

goroutine 1 [running]:
main.main()
    /Users/andy/Desktop/demo.go:11 +0xe8
exit status 2
/dev/fd
文件夹肯定存在,当我
ls
它时,里面有一个
/dev/fd/4

$ ls -Al /dev/fd
total 9
crw--w----  1 andy  tty     16,   4 Jan 25 00:16 0
crw--w----  1 andy  tty     16,   4 Jan 25 00:16 1
crw--w----  1 andy  tty     16,   4 Jan 25 00:16 2
dr--r--r--  3 root  wheel      4419 Jan 23 20:42 3/
dr--r--r--  1 root  wheel         0 Jan 23 20:42 4/
发生什么事了?为什么我不能读这个目录?我正在尝试将
ls
命令移植到Go,因此我希望能够读取此目录,以便生成与
ls
类似的输出

编辑:我以非root用户的身份运行所有内容。设置了
/dev/fd
上的可执行位


假设您以非root用户身份运行代码,我猜问题在于目录没有设置execute位,这会阻止
chdir
-ing到该目录(在尝试读取内容之前,
ReadDir
可能会这样做)。

首先,让我们记住/dev/fd是特殊的。它的内容对于每个进程都是不同的(因为它们反映了该进程的文件描述符),因此它实际上并不意味着
ls
可以列出它,因为它的内容对于
ls
和您的程序是不同的

无论如何,这里有一个稍微更新的程序版本,我们不让ioutil在背后做事情,而是自己做,看看发生了什么:

package main

import (
        "fmt"
        "time"
        "os"
        "log"
)

func main() {
        d, err := os.Open("/dev/fd")
        if err != nil {
                log.Fatal(err)
        }
        names, err := d.Readdirnames(0)
        if err != nil {
                log.Fatal(err)
        }
        for _, n := range names {
                n = "/dev/fd/" + n
                fmt.Printf("file: %s\n", n)
                _, err := os.Lstat(n)
                if err != nil {
                        fmt.Printf("lstat error: %s - %v\n", n, err)
                }
        }

        time.Sleep(time.Second * 200)
}
然后当运行时,它会给我:

file: /dev/fd/0
file: /dev/fd/1
file: /dev/fd/2
file: /dev/fd/3
file: /dev/fd/4
lstat error: /dev/fd/4 - lstat /dev/fd/4: bad file descriptor
所以这确实是同一个问题。我在最后添加了睡眠,这样进程就不会死,这样我们就可以调试它有哪些文件描述符。在另一个终端:

$ lsof -p 7861
COMMAND  PID USER   FD     TYPE     DEVICE SIZE/OFF       NODE NAME
foo     7861  art  cwd      DIR        1,4     2272     731702 /Users/art/src/go/src
foo     7861  art  txt      REG        1,4  1450576 8591078117 /private/var/folders/m7/d614cd9x61s0l3thb7cf3rkh0000gn/T/go-build268777304/command-line-arguments/_obj/exe/foo
foo     7861  art  txt      REG        1,4   837248 8590944844 /usr/lib/dyld
foo     7861  art    0u     CHR       16,4   0t8129        645 /dev/ttys004
foo     7861  art    1u     CHR       16,4   0t8129        645 /dev/ttys004
foo     7861  art    2u     CHR       16,4   0t8129        645 /dev/ttys004
foo     7861  art    3r     DIR 37,7153808        0        316 /dev/fd
foo     7861  art    4u  KQUEUE                                count=0, state=0x8
我们可以看到fd4是一个KQUEUE。Kqueue文件描述符用于管理OSX上的事件,如果您知道该机制,类似于Linux上的
epoll

OSX似乎不允许对
/dev/fd
中的kqueue文件描述符使用
stat
,我们可以用以下方法进行验证:

$ cat > foo.c
#include <sys/types.h>
#include <sys/event.h>
#include <sys/stat.h>
#include <stdio.h>
#include <err.h>

int
main(int argc, char **argv)
{
    int fd = kqueue();
    char path[16];
    struct stat st;

    snprintf(path, sizeof(path), "/dev/fd/%d", fd);
    if (lstat(path, &st) == -1)
        err(1, "lstat");
    return 0;
}
$ cc -o foo foo.c && ./foo
foo: lstat: Bad file descriptor
$
$cat>foo.c
#包括
#包括
#包括
#包括
#包括
int
主(内部argc,字符**argv)
{
int fd=kqueue();
字符路径[16];
结构统计;
snprintf(路径,sizeof(路径),“/dev/fd/%d”,fd);
if(lstat(path,&st)=-1)
错误(1,“lstat”);
返回0;
}
$cc-o foo foo.c&./foo
foo:lstat:错误的文件描述符
$
OSX上的Go程序需要一个kqueue来处理各种事件(不确定是哪个,但可能是信号、计时器和各种文件事件)


这里有四个选项:不要统计,忽略错误,不要触摸/dev/fd,因为它很奇怪,让苹果相信这是一个bug。

命令的输出是什么
stat/dev/fd/4
stat-L/dev/fd/4
/dev/fd
很少直接交互,当OSX作为一个常规文件系统处理时,它似乎有很多优点。不过,如果您想确切了解它在做什么,请尝试
strace ls-li/dev/fd
。通常,建议从常规文件处理中排除
/dev/fd
(甚至
/dev
)。
stat/dev/fd/4
会产生相同的错误:
stat:Bad file descriptor
。我的机器上似乎没有
strace
命令。是的,我以非root用户的身份运行所有操作。不过,执行位已设置。请参阅编辑。@AndyCarlson为了清楚起见,我刚才说的是
/dev/fd/4
-上的执行位,因为错误引用的是执行位,而不是其父级。我的理解是,打开目录需要执行位。但是我甚至不能统计目录。我应该仍然能够在没有
-x
位的情况下
stat
a dir。
$ cat > foo.c
#include <sys/types.h>
#include <sys/event.h>
#include <sys/stat.h>
#include <stdio.h>
#include <err.h>

int
main(int argc, char **argv)
{
    int fd = kqueue();
    char path[16];
    struct stat st;

    snprintf(path, sizeof(path), "/dev/fd/%d", fd);
    if (lstat(path, &st) == -1)
        err(1, "lstat");
    return 0;
}
$ cc -o foo foo.c && ./foo
foo: lstat: Bad file descriptor
$