golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
121.24k stars 17.37k forks source link

os: could not iterate over named pipes on Windows #32423

Open Codehardt opened 5 years ago

Codehardt commented 5 years ago

What version of Go are you using (go version)?

$ go version
go version go1.12.5 windows/amd64

Does this issue reproduce with the latest release?

yes

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
set GOARCH=amd64
set GOBIN=
set GOCACHE=C:\Users\mars9\AppData\Local\go-build
set GOEXE=.exe
set GOFLAGS=
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOOS=windows
set GOPATH=C:\Users\mars9\go
set GOPROXY=
set GORACE=
set GOROOT=C:\Go
set GOTMPDIR=
set GOTOOLDIR=C:\Go\pkg\tool\windows_amd64
set GCCGO=gccgo
set CC=gcc
set CXX=g++
set CGO_ENABLED=1
set GOMOD=
set CGO_CFLAGS=-g -O2
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-g -O2
set CGO_FFLAGS=-g -O2
set CGO_LDFLAGS=-g -O2
set PKG_CONFIG=pkg-config
set GOGCCFLAGS=-m64 -mthreads -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=C:\Users\mars9\AppData\Local\Temp\go-build413446826=/tmp/go-build -gno-record-gcc-switches

What did you do?

I wanted to iterate over Windows' named pipes using os package:

    f, err := os.Open(`\\.\pipe`)
    if err != nil {
        log.Fatalf("Could not open pipe list: %s", err)
    }
    defer f.Close()
    stat, err := f.Stat()
    if err != nil {
        log.Fatalf("Could not get file stats: %s", err)
    }
    log.Printf("Is Directory: %t", stat.IsDir()) // output: false
    pipelist, err := f.Readdir(-1)
    if os.IsNotExist(err) {
        log.Print("Pipe list does not exist")
    } else if err != nil {
        log.Printf("Could not read pipe list: %s", err)
    }
    for _, pipe := range pipelist {
        log.Printf("Pipe found: %s", pipe.Name())
    }

What did you expect to see?

The same result as in Python:

for pipeName in os.listdir(r'\\.\pipe'):
    print("Pipe found: %s" % pipeName)
Pipe found: InitShutdown
Pipe found: lsass
Pipe found: ntsvcs
Pipe found: scerpc
...

What did you see instead?

Is Directory: false
Pipe list does not exist
mdlayher commented 5 years ago

It isn't clear to me if this is something that the standard library itself should handle, but I recently wrote some Go code to do just this if it helps: https://github.com/WireGuard/wgctrl-go/blob/master/internal/wguser/conn_windows.go#L196.

mdlayher commented 5 years ago

/cc @alexbrainman

alexbrainman commented 5 years ago

Or sure, maybe we could fix that code.

I don't have time to debug this, but, if someone is interested, maybe they could make code above work. Maybe FindFirstFile and FindNextFile could be used to read that directory.

Alex

gopherbot commented 5 years ago

Change https://golang.org/cl/181057 mentions this issue: os: treat the Windows named pipe root as a directory

Codehardt commented 5 years ago

Maybe FindFirstFile and FindNextFile could be used to read that directory.

Thanks, this solved my issue.

mdlayher commented 5 years ago

I've submitted a patch that makes this work with package os, but it needs a bit more work and it probably won't land until Go 1.14 at this point.

iwdgo commented 4 years ago

Documentation states explicitly that it is not possible to list pipes using Win 32 API. For the same reason, >dir \\.\\pipe\\ does not return anything but an error.

The same documentation suggests to use NtQueryDirectoryFile of library NtosKrnlwhich is unavailable in the syscallpackage. For completeness, the Powershell instruction to list pipes on Windows is [System.IO.Directory]::GetFiles("\\.\\pipe\\"). The directory value is currently not supported by the files method which uses FindFirstFileW.

Adding the relevant call require seems more like a proposal.

alexbrainman commented 4 years ago

It isn't clear to me if this is something that the standard library itself should handle, but I recently wrote some Go code to do just this if it helps: https://github.com/WireGuard/wgctrl-go/blob/master/internal/wguser/conn_windows.go#L196.

@mdlayher the code you are referring to, implements functionality to connect to Windows named pipe. What we want in this issue is to list all files under \\.\pipe. Not the same thing.

The same documentation suggests to use NtQueryDirectoryFile of library NtosKrnlwhich is unavailable in the syscallpackage. ... Adding the relevant call require seems more like a proposal.

@iWdGo using NtQueryDirectoryFile sounds like it might work. And you don't need to add NtQueryDirectoryFile to syscall package. We can put it in internal/syscall/windows if it works.

Alex

iwdgo commented 4 years ago

Following code can help until fixed.

// List pipes on Windows
package main

import (
    "bytes"
    "fmt"
    "log"
    "os/exec"
    "strings"
    "syscall"
)

// https://github.com/golang/go/issues/32423

const (
    windows10Pipes = "\\\\.\\pipe\\\\"
)

func cmdListPipes() (pipes []string, err error ) {
    cmd := exec.Command("cmd")
    out := new(bytes.Buffer)
    cmd.Stdout = out
    cmd.Stderr = out
    cmd.SysProcAttr = &syscall.SysProcAttr{
        HideWindow: false,
        CmdLine:    "/C \"dir " + windows10Pipes + " /B \" ",
        CreationFlags:     0,
        Token:             0,
        ProcessAttributes: nil,
        ThreadAttributes:  nil,
    }
    err = cmd.Run()
    if err != nil {
        fmt.Errorf("%v", err)
        if cmd.Stderr != nil {
            out, _ := cmd.CombinedOutput()
            log.Fatalf("%s", out)
        }
    }
    return strings.Split(out.String(), "\n"), nil
}

func main() {
    pipes, err := cmdListPipes()
    if err != nil {
        fmt.Println(err)
    }
    for _, p := range pipes {
        fmt.Println(p)
    }
    if len(pipes) == 0 {
        fmt.Println("no output")
    }
}
qmuntal commented 9 months ago

The issue here is that Windows is not treating \\.\pipe in the same way as \\.\pipe\ (notice the trailing slash). You can iterate over the later, but not over the former. In fact, using go1.21.1 I can iterate over all the named pipes if opening the root with os.Open(\\.\pipe\).

This behavior is weird, can't see it documented anywhere, but it is what it is. Python's os.listdir(r'\\.\pipe') work because they call FindFirstFileW(r'\\.\pipe\*') under the hood.

We could special-case \\.\pipe in os.Open so it opens \\.\pipe\ instead, but I don't know the implications, documentation lacking.