├── .gitattributes ├── .gitignore ├── go.mod ├── Makefile ├── .golangci.yml ├── errors.go ├── utils.go ├── go.sum ├── .github └── workflows │ └── ci.yml ├── handle_nolinux.go ├── readme.md ├── handle_linux.go ├── fifo_nolinux_test.go ├── raw.go ├── fifo_linux_test.go ├── fifo.go ├── raw_test.go ├── LICENSE └── fifo_test.go /.gitattributes: -------------------------------------------------------------------------------- 1 | *.go text eol=lf 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage.txt 2 | vendor/ 3 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/containerd/fifo 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/stretchr/testify v1.8.1 7 | golang.org/x/sys v0.5.0 8 | ) 9 | 10 | require ( 11 | github.com/davecgh/go-spew v1.1.1 // indirect 12 | github.com/pmezard/go-difflib v1.0.0 // indirect 13 | gopkg.in/yaml.v3 v3.0.1 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright The containerd Authors. 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | .PHONY: check test deps 16 | 17 | test: deps 18 | go test -v -race ./... 19 | 20 | deps: 21 | go mod vendor 22 | 23 | check: 24 | GOGC=75 golangci-lint run 25 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | enable: 3 | - exportloopref # Checks for pointers to enclosing loop variables 4 | - gofmt 5 | - goimports 6 | - gosec 7 | - ineffassign 8 | - misspell 9 | - nolintlint 10 | - revive 11 | - staticcheck 12 | - tenv # Detects using os.Setenv instead of t.Setenv since Go 1.17 13 | - unconvert 14 | - unused 15 | - vet 16 | - dupword # Checks for duplicate words in the source code 17 | disable: 18 | - errcheck 19 | 20 | linters-settings: 21 | gosec: 22 | # The following issues surfaced when `gosec` linter 23 | # was enabled. They are temporarily excluded to unblock 24 | # the existing workflow, but still to be addressed by 25 | # future works. 26 | excludes: 27 | - G204 28 | - G305 29 | - G306 30 | - G402 31 | - G404 32 | 33 | run: 34 | timeout: 3m 35 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The containerd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package fifo 18 | 19 | import "errors" 20 | 21 | var ( 22 | ErrClosed = errors.New("fifo closed") 23 | ErrCtrlClosed = errors.New("control of closed fifo") 24 | ErrRdFrmWRONLY = errors.New("reading from write-only fifo") 25 | ErrReadClosed = errors.New("reading from a closed fifo") 26 | ErrWrToRDONLY = errors.New("writing to read-only fifo") 27 | ErrWriteClosed = errors.New("writing to a closed fifo") 28 | ) 29 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The containerd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package fifo 18 | 19 | import "os" 20 | 21 | // IsFifo checks if a file is a (named pipe) fifo 22 | // if the file does not exist then it returns false 23 | func IsFifo(path string) (bool, error) { 24 | stat, err := os.Stat(path) 25 | if err != nil { 26 | if os.IsNotExist(err) { 27 | return false, nil 28 | } 29 | return false, err 30 | } 31 | if stat.Mode()&os.ModeNamedPipe == os.ModeNamedPipe { 32 | return true, nil 33 | } 34 | return false, nil 35 | } 36 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 5 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 6 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 7 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 8 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 9 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 10 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 11 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 12 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 13 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= 14 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 15 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 16 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 17 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 18 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 19 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 20 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | checks: 12 | name: Project Checks 13 | runs-on: ubuntu-22.04 14 | timeout-minutes: 5 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | path: src/github.com/containerd/fifo # project-checks depends on GOPATH mode 20 | fetch-depth: 25 21 | - uses: actions/setup-go@v5 22 | with: 23 | go-version: 1.22.x 24 | - uses: containerd/project-checks@v1.1.0 25 | with: 26 | working-directory: src/github.com/containerd/fifo # project-checks depends on GOPATH mode 27 | 28 | linters: 29 | name: Linters 30 | runs-on: ${{ matrix.os }} 31 | timeout-minutes: 10 32 | 33 | strategy: 34 | matrix: 35 | go-version: [1.22.x] 36 | os: [ubuntu-22.04, macos-14, windows-2022] 37 | 38 | steps: 39 | - uses: actions/checkout@v4 40 | - uses: actions/setup-go@v5 41 | with: 42 | go-version: ${{ matrix.go-version }} 43 | 44 | - name: Set env 45 | shell: bash 46 | run: | 47 | echo "${{ github.workspace }}/bin" >> $GITHUB_PATH 48 | 49 | - uses: golangci/golangci-lint-action@v6 50 | with: 51 | version: v1.59.1 52 | 53 | tests: 54 | name: Tests 55 | runs-on: ${{ matrix.os }} 56 | timeout-minutes: 5 57 | 58 | strategy: 59 | matrix: 60 | go-version: [1.20.x, 1.22.x] 61 | os: [ubuntu-22.04] 62 | 63 | steps: 64 | - uses: actions/checkout@v4 65 | - uses: actions/setup-go@v5 66 | with: 67 | go-version: ${{ matrix.go-version }} 68 | 69 | - run: make test 70 | -------------------------------------------------------------------------------- /handle_nolinux.go: -------------------------------------------------------------------------------- 1 | //go:build !linux && !windows 2 | 3 | /* 4 | Copyright The containerd Authors. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | package fifo 20 | 21 | import ( 22 | "fmt" 23 | "syscall" 24 | ) 25 | 26 | type handle struct { 27 | fn string 28 | dev uint64 29 | ino uint64 30 | } 31 | 32 | func getHandle(fn string) (*handle, error) { 33 | var stat syscall.Stat_t 34 | if err := syscall.Stat(fn, &stat); err != nil { 35 | return nil, fmt.Errorf("failed to stat %v: %w", fn, err) 36 | } 37 | 38 | h := &handle{ 39 | fn: fn, 40 | dev: uint64(stat.Dev), //nolint:unconvert,nolintlint 41 | ino: uint64(stat.Ino), //nolint:unconvert,nolintlint 42 | } 43 | 44 | return h, nil 45 | } 46 | 47 | func (h *handle) Path() (string, error) { 48 | var stat syscall.Stat_t 49 | if err := syscall.Stat(h.fn, &stat); err != nil { 50 | return "", fmt.Errorf("path %v could not be statted: %w", h.fn, err) 51 | } 52 | if uint64(stat.Dev) != h.dev || uint64(stat.Ino) != h.ino { //nolint:unconvert,nolintlint 53 | return "", fmt.Errorf("failed to verify handle %v/%v %v/%v for %v", stat.Dev, h.dev, stat.Ino, h.ino, h.fn) 54 | } 55 | return h.fn, nil 56 | } 57 | 58 | func (h *handle) Name() string { 59 | return h.fn 60 | } 61 | 62 | func (h *handle) Close() error { 63 | return nil 64 | } 65 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ### fifo 2 | 3 | [![PkgGoDev](https://pkg.go.dev/badge/github.com/containerd/fifo)](https://pkg.go.dev/github.com/containerd/fifo) 4 | [![Build Status](https://github.com/containerd/fifo/workflows/CI/badge.svg)](https://github.com/containerd/fifo/actions?query=workflow%3ACI) 5 | [![codecov](https://codecov.io/gh/containerd/fifo/branch/main/graph/badge.svg)](https://codecov.io/gh/containerd/fifo) 6 | [![Go Report Card](https://goreportcard.com/badge/github.com/containerd/fifo)](https://goreportcard.com/report/github.com/containerd/fifo) 7 | 8 | Go package for handling fifos in a sane way. 9 | 10 | ``` 11 | // OpenFifo opens a fifo. Returns io.ReadWriteCloser. 12 | // Context can be used to cancel this function until open(2) has not returned. 13 | // Accepted flags: 14 | // - syscall.O_CREAT - create new fifo if one doesn't exist 15 | // - syscall.O_RDONLY - open fifo only from reader side 16 | // - syscall.O_WRONLY - open fifo only from writer side 17 | // - syscall.O_RDWR - open fifo from both sides, never block on syscall level 18 | // - syscall.O_NONBLOCK - return io.ReadWriteCloser even if other side of the 19 | // fifo isn't open. read/write will be connected after the actual fifo is 20 | // open or after fifo is closed. 21 | func OpenFifo(ctx context.Context, fn string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) 22 | 23 | 24 | // Read from a fifo to a byte array. 25 | func (f *fifo) Read(b []byte) (int, error) 26 | 27 | 28 | // Write from byte array to a fifo. 29 | func (f *fifo) Write(b []byte) (int, error) 30 | 31 | 32 | // Close the fifo. Next reads/writes will error. This method can also be used 33 | // before open(2) has returned and fifo was never opened. 34 | func (f *fifo) Close() error 35 | ``` 36 | 37 | ## Project details 38 | 39 | The fifo is a containerd sub-project, licensed under the [Apache 2.0 license](./LICENSE). 40 | As a containerd sub-project, you will find the: 41 | 42 | * [Project governance](https://github.com/containerd/project/blob/main/GOVERNANCE.md), 43 | * [Maintainers](https://github.com/containerd/project/blob/main/MAINTAINERS), 44 | * and [Contributing guidelines](https://github.com/containerd/project/blob/main/CONTRIBUTING.md) 45 | 46 | information in our [`containerd/project`](https://github.com/containerd/project) repository. 47 | -------------------------------------------------------------------------------- /handle_linux.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | 3 | /* 4 | Copyright The containerd Authors. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | package fifo 20 | 21 | import ( 22 | "fmt" 23 | "os" 24 | "sync" 25 | "syscall" 26 | ) 27 | 28 | //nolint:revive 29 | const O_PATH = 0o10000000 30 | 31 | type handle struct { 32 | f *os.File 33 | fd uintptr 34 | dev uint64 35 | ino uint64 36 | closeOnce sync.Once 37 | name string 38 | } 39 | 40 | func getHandle(fn string) (*handle, error) { 41 | f, err := os.OpenFile(fn, O_PATH, 0) 42 | if err != nil { 43 | return nil, fmt.Errorf("failed to open %v with O_PATH: %w", fn, err) 44 | } 45 | 46 | var ( 47 | stat syscall.Stat_t 48 | fd = f.Fd() 49 | ) 50 | if err := syscall.Fstat(int(fd), &stat); err != nil { 51 | f.Close() 52 | return nil, fmt.Errorf("failed to stat handle %v: %w", fd, err) 53 | } 54 | 55 | h := &handle{ 56 | f: f, 57 | name: fn, 58 | //nolint:unconvert 59 | dev: uint64(stat.Dev), 60 | ino: stat.Ino, 61 | fd: fd, 62 | } 63 | 64 | // check /proc just in case 65 | if _, err := os.Stat(h.procPath()); err != nil { 66 | f.Close() 67 | return nil, fmt.Errorf("couldn't stat %v: %w", h.procPath(), err) 68 | } 69 | 70 | return h, nil 71 | } 72 | 73 | func (h *handle) procPath() string { 74 | return fmt.Sprintf("/proc/self/fd/%d", h.fd) 75 | } 76 | 77 | func (h *handle) Name() string { 78 | return h.name 79 | } 80 | 81 | func (h *handle) Path() (string, error) { 82 | var stat syscall.Stat_t 83 | if err := syscall.Stat(h.procPath(), &stat); err != nil { 84 | return "", fmt.Errorf("path %v could not be statted: %w", h.procPath(), err) 85 | } 86 | //nolint:unconvert 87 | if uint64(stat.Dev) != h.dev || stat.Ino != h.ino { 88 | return "", fmt.Errorf("failed to verify handle %v/%v %v/%v", stat.Dev, h.dev, stat.Ino, h.ino) 89 | } 90 | return h.procPath(), nil 91 | } 92 | 93 | func (h *handle) Close() error { 94 | h.closeOnce.Do(func() { 95 | h.f.Close() 96 | }) 97 | return nil 98 | } 99 | -------------------------------------------------------------------------------- /fifo_nolinux_test.go: -------------------------------------------------------------------------------- 1 | //go:build !linux && !windows 2 | 3 | /* 4 | Copyright The containerd Authors. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | package fifo 20 | 21 | import ( 22 | "context" 23 | "os" 24 | "path/filepath" 25 | "syscall" 26 | "testing" 27 | "time" 28 | 29 | "github.com/stretchr/testify/assert" 30 | ) 31 | 32 | func TestFifoCloseAfterRm(t *testing.T) { 33 | tmpdir, err := os.MkdirTemp("", "fifos") 34 | assert.NoError(t, err) 35 | defer os.RemoveAll(tmpdir) 36 | 37 | // non-linux version of this test leaks a goroutine 38 | 39 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 40 | defer cancel() 41 | f, err := OpenFifo(ctx, filepath.Join(tmpdir, "f0"), syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0o600) 42 | assert.NoError(t, err) 43 | 44 | time.Sleep(time.Second) // non-linux doesn't allow removing before syscall has been called. will cause an error. 45 | 46 | err = os.RemoveAll(filepath.Join(tmpdir, "f0")) 47 | assert.NoError(t, err) 48 | 49 | cerr := make(chan error) 50 | 51 | go func() { 52 | b := make([]byte, 32) 53 | _, err := f.Read(b) 54 | cerr <- err 55 | }() 56 | 57 | select { 58 | case err := <-cerr: 59 | t.Fatalf("read should have blocked, but got %v", err) 60 | case <-time.After(500 * time.Millisecond): 61 | } 62 | 63 | err = f.Close() 64 | assert.NoError(t, err) 65 | 66 | select { 67 | case err := <-cerr: 68 | assert.EqualError(t, err, "reading from a closed fifo") 69 | case <-time.After(500 * time.Millisecond): 70 | t.Fatal("read should have been unblocked") 71 | } 72 | 73 | cancel() 74 | ctx, cancel = context.WithCancel(context.Background()) 75 | cerr = make(chan error) 76 | go func() { 77 | _, err = OpenFifo(ctx, filepath.Join(tmpdir, "f1"), syscall.O_WRONLY|syscall.O_CREAT, 0o600) 78 | cerr <- err 79 | }() 80 | 81 | select { 82 | case err := <-cerr: 83 | t.Fatalf("open should have blocked, but got %v", err) 84 | case <-time.After(500 * time.Millisecond): 85 | } 86 | 87 | err = os.RemoveAll(filepath.Join(tmpdir, "f1")) 88 | cancel() 89 | 90 | select { 91 | case err := <-cerr: 92 | assert.Error(t, err) 93 | case <-time.After(500 * time.Millisecond): 94 | t.Fatal("open should have been unblocked") 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /raw.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | /* 4 | Copyright The containerd Authors. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | package fifo 20 | 21 | import ( 22 | "syscall" 23 | ) 24 | 25 | // SyscallConn provides raw access to the fifo's underlying filedescrptor. 26 | // See syscall.Conn for guarantees provided by this interface. 27 | func (f *fifo) SyscallConn() (syscall.RawConn, error) { 28 | // deterministic check for closed 29 | select { 30 | case <-f.closed: 31 | return nil, ErrClosed 32 | default: 33 | } 34 | 35 | select { 36 | case <-f.closed: 37 | return nil, ErrClosed 38 | case <-f.opened: 39 | return f.file.SyscallConn() 40 | default: 41 | } 42 | 43 | // Not opened and not closed, this means open is non-blocking AND it's not open yet 44 | // Use rawConn to deal with non-blocking open. 45 | rc := &rawConn{f: f, ready: make(chan struct{})} 46 | go func() { 47 | select { 48 | case <-f.closed: 49 | return 50 | case <-f.opened: 51 | rc.raw, rc.err = f.file.SyscallConn() 52 | close(rc.ready) 53 | } 54 | }() 55 | 56 | return rc, nil 57 | } 58 | 59 | type rawConn struct { 60 | f *fifo 61 | ready chan struct{} 62 | raw syscall.RawConn 63 | err error 64 | } 65 | 66 | func (r *rawConn) Control(f func(fd uintptr)) error { 67 | select { 68 | case <-r.f.closed: 69 | return ErrCtrlClosed 70 | case <-r.ready: 71 | } 72 | 73 | if r.err != nil { 74 | return r.err 75 | } 76 | 77 | return r.raw.Control(f) 78 | } 79 | 80 | func (r *rawConn) Read(f func(fd uintptr) (done bool)) error { 81 | if r.f.flag&syscall.O_WRONLY > 0 { 82 | return ErrRdFrmWRONLY 83 | } 84 | 85 | select { 86 | case <-r.f.closed: 87 | return ErrReadClosed 88 | case <-r.ready: 89 | } 90 | 91 | if r.err != nil { 92 | return r.err 93 | } 94 | 95 | return r.raw.Read(f) 96 | } 97 | 98 | func (r *rawConn) Write(f func(fd uintptr) (done bool)) error { 99 | if r.f.flag&(syscall.O_WRONLY|syscall.O_RDWR) == 0 { 100 | return ErrWrToRDONLY 101 | } 102 | 103 | select { 104 | case <-r.f.closed: 105 | return ErrWriteClosed 106 | case <-r.ready: 107 | } 108 | 109 | if r.err != nil { 110 | return r.err 111 | } 112 | 113 | return r.raw.Write(f) 114 | } 115 | -------------------------------------------------------------------------------- /fifo_linux_test.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | 3 | /* 4 | Copyright The containerd Authors. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | package fifo 20 | 21 | import ( 22 | "context" 23 | "os" 24 | "path/filepath" 25 | "sync" 26 | "syscall" 27 | "testing" 28 | "time" 29 | 30 | "github.com/stretchr/testify/assert" 31 | ) 32 | 33 | func TestFifoCloseAfterRm(t *testing.T) { 34 | tmpdir, err := os.MkdirTemp("", "fifos") 35 | assert.NoError(t, err) 36 | defer os.RemoveAll(tmpdir) 37 | 38 | leakCheckWg = &sync.WaitGroup{} 39 | defer func() { 40 | leakCheckWg = nil 41 | }() 42 | 43 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 44 | defer cancel() 45 | 46 | f, err := OpenFifo(ctx, filepath.Join(tmpdir, "f0"), syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0o600) 47 | assert.NoError(t, err) 48 | 49 | err = os.RemoveAll(filepath.Join(tmpdir, "f0")) 50 | assert.NoError(t, err) 51 | 52 | cerr := make(chan error) 53 | 54 | go func() { 55 | b := make([]byte, 32) 56 | _, err := f.Read(b) 57 | cerr <- err 58 | }() 59 | 60 | select { 61 | case err := <-cerr: 62 | t.Fatalf("read should have blocked, but got %v", err) 63 | case <-time.After(500 * time.Millisecond): 64 | } 65 | 66 | err = f.Close() 67 | assert.NoError(t, err) 68 | 69 | select { 70 | case err := <-cerr: 71 | assert.EqualError(t, err, "reading from a closed fifo") 72 | case <-time.After(500 * time.Millisecond): 73 | t.Fatal("read should have been unblocked") 74 | } 75 | 76 | assert.NoError(t, checkWgDone(leakCheckWg)) 77 | 78 | cancel() 79 | ctx, cancel = context.WithCancel(context.Background()) 80 | 81 | cerr = make(chan error) 82 | go func() { 83 | _, err = OpenFifo(ctx, filepath.Join(tmpdir, "f1"), syscall.O_WRONLY|syscall.O_CREAT, 0o600) 84 | cerr <- err 85 | }() 86 | 87 | select { 88 | case err := <-cerr: 89 | t.Fatalf("open should have blocked, but got %v", err) 90 | case <-time.After(500 * time.Millisecond): 91 | } 92 | 93 | err = os.RemoveAll(filepath.Join(tmpdir, "f1")) 94 | cancel() 95 | 96 | select { 97 | case err := <-cerr: 98 | assert.EqualError(t, err, "context canceled") 99 | case <-time.After(500 * time.Millisecond): 100 | t.Fatal("open should have been unblocked") 101 | } 102 | 103 | assert.NoError(t, checkWgDone(leakCheckWg)) 104 | } 105 | -------------------------------------------------------------------------------- /fifo.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | /* 4 | Copyright The containerd Authors. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | package fifo 20 | 21 | import ( 22 | "context" 23 | "fmt" 24 | "io" 25 | "os" 26 | "runtime" 27 | "sync" 28 | "syscall" 29 | 30 | "golang.org/x/sys/unix" 31 | ) 32 | 33 | type fifo struct { 34 | flag int 35 | opened chan struct{} 36 | closed chan struct{} 37 | closing chan struct{} 38 | err error 39 | file *os.File 40 | closingOnce sync.Once // close has been called 41 | closedOnce sync.Once // fifo is closed 42 | handle *handle 43 | } 44 | 45 | var leakCheckWg *sync.WaitGroup 46 | 47 | // OpenFifoDup2 is same as OpenFifo, but additionally creates a copy of the FIFO file descriptor with dup2 syscall. 48 | func OpenFifoDup2(ctx context.Context, fn string, flag int, perm os.FileMode, fd int) (io.ReadWriteCloser, error) { 49 | f, err := openFifo(ctx, fn, flag, perm) 50 | if err != nil { 51 | return nil, fmt.Errorf("fifo error: %w", err) 52 | } 53 | 54 | if err := unix.Dup2(int(f.file.Fd()), fd); err != nil { 55 | _ = f.Close() 56 | return nil, fmt.Errorf("dup2 error: %w", err) 57 | } 58 | 59 | return f, nil 60 | } 61 | 62 | // OpenFifo opens a fifo. Returns io.ReadWriteCloser. 63 | // Context can be used to cancel this function until open(2) has not returned. 64 | // Accepted flags: 65 | // - syscall.O_CREAT - create new fifo if one doesn't exist 66 | // - syscall.O_RDONLY - open fifo only from reader side 67 | // - syscall.O_WRONLY - open fifo only from writer side 68 | // - syscall.O_RDWR - open fifo from both sides, never block on syscall level 69 | // - syscall.O_NONBLOCK - return io.ReadWriteCloser even if other side of the 70 | // fifo isn't open. read/write will be connected after the actual fifo is 71 | // open or after fifo is closed. 72 | func OpenFifo(ctx context.Context, fn string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) { 73 | fifo, err := openFifo(ctx, fn, flag, perm) 74 | if fifo == nil { 75 | // Do not return a non-nil ReadWriteCloser((*fifo)(nil)) value 76 | // as that can confuse callers. 77 | return nil, err 78 | } 79 | return fifo, err 80 | } 81 | 82 | func openFifo(ctx context.Context, fn string, flag int, perm os.FileMode) (*fifo, error) { 83 | if file, err := os.Stat(fn); err != nil { 84 | if os.IsNotExist(err) && flag&syscall.O_CREAT != 0 { 85 | if err := syscall.Mkfifo(fn, uint32(perm&os.ModePerm)); err != nil && !os.IsExist(err) { 86 | return nil, fmt.Errorf("error creating fifo %v: %w", fn, err) 87 | } 88 | } else { 89 | return nil, err 90 | } 91 | } else { 92 | if file.Mode()&os.ModeNamedPipe == 0 { 93 | return nil, fmt.Errorf("file %s is not fifo", fn) 94 | } 95 | } 96 | 97 | block := flag&syscall.O_NONBLOCK == 0 || flag&syscall.O_RDWR != 0 98 | 99 | flag &= ^syscall.O_CREAT 100 | flag &= ^syscall.O_NONBLOCK 101 | 102 | h, err := getHandle(fn) 103 | if err != nil { 104 | return nil, err 105 | } 106 | 107 | f := &fifo{ 108 | handle: h, 109 | flag: flag, 110 | opened: make(chan struct{}), 111 | closed: make(chan struct{}), 112 | closing: make(chan struct{}), 113 | } 114 | 115 | wg := leakCheckWg 116 | if wg != nil { 117 | wg.Add(2) 118 | } 119 | 120 | go func() { 121 | if wg != nil { 122 | defer wg.Done() 123 | } 124 | select { 125 | case <-ctx.Done(): 126 | select { 127 | case <-f.opened: 128 | default: 129 | f.Close() 130 | } 131 | case <-f.opened: 132 | case <-f.closed: 133 | } 134 | }() 135 | go func() { 136 | if wg != nil { 137 | defer wg.Done() 138 | } 139 | var file *os.File 140 | fn, err := h.Path() 141 | if err == nil { 142 | file, err = os.OpenFile(fn, flag, 0) 143 | } 144 | select { 145 | case <-f.closing: 146 | if err == nil { 147 | select { 148 | case <-ctx.Done(): 149 | err = ctx.Err() 150 | default: 151 | err = fmt.Errorf("fifo %v was closed before opening", h.Name()) 152 | } 153 | if file != nil { 154 | file.Close() 155 | } 156 | } 157 | default: 158 | } 159 | if err != nil { 160 | f.closedOnce.Do(func() { 161 | f.err = err 162 | close(f.closed) 163 | }) 164 | return 165 | } 166 | f.file = file 167 | close(f.opened) 168 | }() 169 | if block { 170 | select { 171 | case <-f.opened: 172 | case <-f.closed: 173 | return nil, f.err 174 | } 175 | } 176 | return f, nil 177 | } 178 | 179 | // Read from a fifo to a byte array. 180 | func (f *fifo) Read(b []byte) (int, error) { 181 | if f.flag&syscall.O_WRONLY > 0 { 182 | return 0, ErrRdFrmWRONLY 183 | } 184 | select { 185 | case <-f.opened: 186 | return f.file.Read(b) 187 | default: 188 | } 189 | select { 190 | case <-f.opened: 191 | return f.file.Read(b) 192 | case <-f.closed: 193 | return 0, ErrReadClosed 194 | } 195 | } 196 | 197 | // Write from byte array to a fifo. 198 | func (f *fifo) Write(b []byte) (int, error) { 199 | if f.flag&(syscall.O_WRONLY|syscall.O_RDWR) == 0 { 200 | return 0, ErrWrToRDONLY 201 | } 202 | select { 203 | case <-f.opened: 204 | return f.file.Write(b) 205 | default: 206 | } 207 | select { 208 | case <-f.opened: 209 | return f.file.Write(b) 210 | case <-f.closed: 211 | return 0, ErrWriteClosed 212 | } 213 | } 214 | 215 | // Close the fifo. Next reads/writes will error. This method can also be used 216 | // before open(2) has returned and fifo was never opened. 217 | func (f *fifo) Close() (retErr error) { 218 | for { 219 | if f == nil { 220 | return 221 | } 222 | 223 | select { 224 | case <-f.closed: 225 | f.handle.Close() 226 | return 227 | default: 228 | select { 229 | case <-f.opened: 230 | f.closedOnce.Do(func() { 231 | retErr = f.file.Close() 232 | f.err = retErr 233 | close(f.closed) 234 | }) 235 | default: 236 | if f.flag&syscall.O_RDWR != 0 { 237 | runtime.Gosched() 238 | break 239 | } 240 | f.closingOnce.Do(func() { 241 | close(f.closing) 242 | }) 243 | reverseMode := syscall.O_WRONLY 244 | if f.flag&syscall.O_WRONLY > 0 { 245 | reverseMode = syscall.O_RDONLY 246 | } 247 | fn, err := f.handle.Path() 248 | // if Close() is called concurrently(shouldn't) it may cause error 249 | // because handle is closed 250 | select { 251 | case <-f.closed: 252 | default: 253 | if err != nil { 254 | // Path has become invalid. We will leak a goroutine. 255 | // This case should not happen in linux. 256 | f.closedOnce.Do(func() { 257 | f.err = err 258 | close(f.closed) 259 | }) 260 | <-f.closed 261 | break 262 | } 263 | f, err := os.OpenFile(fn, reverseMode|syscall.O_NONBLOCK, 0) 264 | if err == nil { 265 | f.Close() 266 | } 267 | runtime.Gosched() 268 | } 269 | } 270 | } 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /raw_test.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | /* 4 | Copyright The containerd Authors. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | package fifo 20 | 21 | import ( 22 | "bytes" 23 | "context" 24 | "io" 25 | "os" 26 | "path" 27 | "path/filepath" 28 | "syscall" 29 | "testing" 30 | "time" 31 | 32 | "github.com/stretchr/testify/assert" 33 | ) 34 | 35 | func TestRawReadWrite(t *testing.T) { 36 | tmpdir, err := os.MkdirTemp("", "fifos") 37 | assert.NoError(t, err) 38 | defer os.RemoveAll(tmpdir) 39 | 40 | ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) 41 | defer cancel() 42 | 43 | r, err := OpenFifo(ctx, filepath.Join(tmpdir, t.Name()), syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0o600) 44 | assert.NoError(t, err) 45 | defer r.Close() 46 | rawR := makeRawConn(t, r, false) 47 | assert.Error(t, rawR.Write(func(uintptr) bool { return true })) 48 | 49 | w, err := OpenFifo(ctx, filepath.Join(tmpdir, t.Name()), syscall.O_WRONLY|syscall.O_NONBLOCK, 0) 50 | assert.NoError(t, err) 51 | defer w.Close() 52 | rawW := makeRawConn(t, w, false) 53 | assert.Error(t, rawW.Read(func(uintptr) bool { return true })) 54 | 55 | data := []byte("hello world") 56 | rawWrite(t, rawW, data) 57 | 58 | dataR := make([]byte, len(data)) 59 | rawRead(t, rawR, dataR) 60 | assert.True(t, bytes.Equal(data, dataR)) 61 | } 62 | 63 | func TestRawWriteUserRead(t *testing.T) { 64 | tmpdir, err := os.MkdirTemp("", "fifos") 65 | assert.NoError(t, err) 66 | defer os.RemoveAll(tmpdir) 67 | 68 | ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) 69 | defer cancel() 70 | 71 | w, err := OpenFifo(ctx, filepath.Join(tmpdir, t.Name()), syscall.O_WRONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0o600) 72 | assert.NoError(t, err) 73 | defer w.Close() 74 | rawW := makeRawConn(t, w, false) 75 | 76 | r, err := OpenFifo(ctx, filepath.Join(tmpdir, t.Name()), syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0o600) 77 | assert.NoError(t, err) 78 | defer r.Close() 79 | 80 | data := []byte("hello world!") 81 | rawWrite(t, rawW, data) 82 | w.Close() 83 | 84 | buf := make([]byte, len(data)) 85 | n, err := io.ReadFull(r, buf) 86 | assert.NoError(t, err) 87 | assert.True(t, bytes.Equal(data, buf[:n])) 88 | } 89 | 90 | func TestUserWriteRawRead(t *testing.T) { 91 | tmpdir, err := os.MkdirTemp("", "fifos") 92 | assert.NoError(t, err) 93 | defer os.RemoveAll(tmpdir) 94 | 95 | ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) 96 | defer cancel() 97 | 98 | w, err := OpenFifo(ctx, filepath.Join(tmpdir, t.Name()), syscall.O_WRONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0o600) 99 | assert.NoError(t, err) 100 | defer w.Close() 101 | 102 | r, err := OpenFifo(ctx, filepath.Join(tmpdir, t.Name()), syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0o600) 103 | assert.NoError(t, err) 104 | defer r.Close() 105 | rawR := makeRawConn(t, r, false) 106 | 107 | data := []byte("hello world!") 108 | n, err := w.Write(data) 109 | assert.NoError(t, err) 110 | assert.Equal(t, n, len(data)) 111 | w.Close() 112 | 113 | buf := make([]byte, len(data)) 114 | rawRead(t, rawR, buf) 115 | assert.True(t, bytes.Equal(data, buf[:n])) 116 | } 117 | 118 | func TestRawCloseError(t *testing.T) { 119 | tmpdir, err := os.MkdirTemp("", "fifos") 120 | assert.NoError(t, err) 121 | defer os.RemoveAll(tmpdir) 122 | 123 | t.Run("SyscallConnAfterClose", func(t *testing.T) { 124 | ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) 125 | defer cancel() 126 | 127 | f, err := OpenFifo(ctx, filepath.Join(tmpdir, path.Base(t.Name())), syscall.O_RDWR|syscall.O_CREAT, 0o600) 128 | assert.NoError(t, err) 129 | 130 | f.Close() 131 | 132 | makeRawConn(t, f, true) 133 | }) 134 | 135 | t.Run("RawOpsAfterClose", func(t *testing.T) { 136 | ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) 137 | defer cancel() 138 | f, err := OpenFifo(ctx, filepath.Join(tmpdir, path.Base(t.Name())), syscall.O_RDWR|syscall.O_CREAT, 0o600) 139 | assert.NoError(t, err) 140 | defer f.Close() 141 | 142 | raw := makeRawConn(t, f, false) 143 | 144 | f.Close() 145 | 146 | assert.Error(t, raw.Control(func(uintptr) {})) 147 | dummy := func(uintptr) bool { return true } 148 | assert.Error(t, raw.Write(dummy)) 149 | assert.Error(t, raw.Read(dummy)) 150 | }) 151 | 152 | t.Run("NonBlockRawOpsAfterClose", func(t *testing.T) { 153 | ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) 154 | defer cancel() 155 | dummy := func(uintptr) bool { return true } 156 | r, err := OpenFifo(ctx, filepath.Join(tmpdir, path.Base(t.Name())), syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0o600) 157 | assert.NoError(t, err) 158 | defer r.Close() 159 | rawR := makeRawConn(t, r, false) 160 | r.Close() 161 | 162 | assert.Equal(t, ErrCtrlClosed, rawR.Control(func(uintptr) {})) 163 | assert.Equal(t, ErrReadClosed, rawR.Read(dummy)) 164 | 165 | w, err := OpenFifo(ctx, filepath.Join(tmpdir, path.Base(t.Name())), syscall.O_WRONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0o600) 166 | assert.NoError(t, err) 167 | defer w.Close() 168 | rawW := makeRawConn(t, w, false) 169 | w.Close() 170 | 171 | assert.Equal(t, ErrCtrlClosed, rawW.Control(func(uintptr) {})) 172 | assert.Equal(t, ErrWriteClosed, rawW.Write(dummy)) 173 | }) 174 | } 175 | 176 | func TestRawWrongRdWrError(t *testing.T) { 177 | tmpdir, err := os.MkdirTemp("", "fifos") 178 | assert.NoError(t, err) 179 | defer os.RemoveAll(tmpdir) 180 | 181 | ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) 182 | defer cancel() 183 | dummy := func(uintptr) bool { return true } 184 | r, err := OpenFifo(ctx, filepath.Join(tmpdir, path.Base(t.Name())), syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0o600) 185 | assert.NoError(t, err) 186 | defer r.Close() 187 | rawR := makeRawConn(t, r, false) 188 | 189 | assert.Equal(t, ErrWrToRDONLY, rawR.Write(dummy)) 190 | 191 | w, err := OpenFifo(ctx, filepath.Join(tmpdir, path.Base(t.Name())), syscall.O_WRONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0o600) 192 | assert.NoError(t, err) 193 | defer w.Close() 194 | rawW := makeRawConn(t, w, false) 195 | 196 | assert.Equal(t, ErrRdFrmWRONLY, rawW.Read(dummy)) 197 | } 198 | 199 | func makeRawConn(t *testing.T, fifo io.ReadWriteCloser, expectError bool) syscall.RawConn { 200 | sc, ok := fifo.(syscall.Conn) 201 | assert.True(t, ok, "not a syscall.Conn") 202 | 203 | raw, err := sc.SyscallConn() 204 | if !expectError { 205 | assert.NoError(t, err) 206 | } else { 207 | assert.Error(t, err) 208 | } 209 | 210 | return raw 211 | } 212 | 213 | func rawWrite(t *testing.T, rc syscall.RawConn, data []byte) { 214 | var written int 215 | var wErr error 216 | 217 | err := rc.Write(func(fd uintptr) bool { 218 | var n int 219 | n, wErr = syscall.Write(int(fd), data[written:]) 220 | written += n 221 | if wErr != nil || n == 0 || written == len(data) { 222 | return true 223 | } 224 | return false 225 | }) 226 | assert.NoError(t, err) 227 | assert.NoError(t, wErr) 228 | assert.Equal(t, written, len(data)) 229 | } 230 | 231 | func rawRead(t *testing.T, rc syscall.RawConn, data []byte) { 232 | var ( 233 | rErr error 234 | read int 235 | ) 236 | 237 | err := rc.Read(func(fd uintptr) bool { 238 | var n int 239 | n, rErr = syscall.Read(int(fd), data[read:]) 240 | read += n 241 | if rErr != nil || n == 0 || read == len(data) { 242 | return true 243 | } 244 | return false 245 | }) 246 | assert.NoError(t, err) 247 | assert.NoError(t, rErr) 248 | assert.Equal(t, read, len(data)) 249 | } 250 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /fifo_test.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | /* 4 | Copyright The containerd Authors. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | package fifo 20 | 21 | import ( 22 | "context" 23 | "fmt" 24 | "io" 25 | "os" 26 | "path/filepath" 27 | "sync" 28 | "syscall" 29 | "testing" 30 | "time" 31 | 32 | "github.com/stretchr/testify/assert" 33 | ) 34 | 35 | func TestOpenNonNamedPipe(t *testing.T) { 36 | tmpdir := t.TempDir() 37 | 38 | normalFile := filepath.Join(tmpdir, "empty") 39 | f, err := os.Create(normalFile) 40 | assert.NoError(t, err) 41 | f.Close() 42 | 43 | _, err = OpenFifo(context.TODO(), normalFile, syscall.O_RDONLY|syscall.O_NONBLOCK, 0o600) 44 | assert.ErrorContains(t, err, fmt.Sprintf("file %v is not fifo", normalFile)) 45 | } 46 | 47 | func TestFifoCancel(t *testing.T) { 48 | tmpdir, err := os.MkdirTemp("", "fifos") 49 | assert.NoError(t, err) 50 | defer os.RemoveAll(tmpdir) 51 | 52 | leakCheckWg = &sync.WaitGroup{} 53 | defer func() { 54 | leakCheckWg = nil 55 | }() 56 | 57 | f, err := OpenFifo(context.Background(), filepath.Join(tmpdir, "f0"), syscall.O_RDONLY|syscall.O_NONBLOCK, 0o600) 58 | assert.Exactly(t, nil, f) 59 | assert.NotNil(t, err) 60 | 61 | assert.NoError(t, checkWgDone(leakCheckWg)) 62 | 63 | ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) 64 | defer cancel() 65 | 66 | f, err = OpenFifo(ctx, filepath.Join(tmpdir, "f0"), syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0o600) 67 | assert.NoError(t, err) 68 | 69 | b := make([]byte, 32) 70 | n, err := f.Read(b) 71 | assert.Equal(t, n, 0) 72 | assert.Equal(t, err, ErrReadClosed) 73 | 74 | select { 75 | case <-ctx.Done(): 76 | default: 77 | t.Fatal("context should have been done") 78 | } 79 | assert.NoError(t, checkWgDone(leakCheckWg)) 80 | } 81 | 82 | func TestFifoReadWrite(t *testing.T) { 83 | tmpdir, err := os.MkdirTemp("", "fifos") 84 | assert.NoError(t, err) 85 | defer os.RemoveAll(tmpdir) 86 | 87 | leakCheckWg = &sync.WaitGroup{} 88 | defer func() { 89 | leakCheckWg = nil 90 | }() 91 | 92 | ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) 93 | defer cancel() 94 | 95 | r, err := OpenFifo(ctx, filepath.Join(tmpdir, "f0"), syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0o600) 96 | assert.NoError(t, err) 97 | 98 | w, err := OpenFifo(ctx, filepath.Join(tmpdir, "f0"), syscall.O_WRONLY|syscall.O_NONBLOCK, 0) 99 | assert.NoError(t, err) 100 | 101 | _, err = w.Write([]byte("foo")) 102 | assert.NoError(t, err) 103 | 104 | b := make([]byte, 32) 105 | n, err := r.Read(b) 106 | assert.NoError(t, err) 107 | assert.Equal(t, string(b[:n]), "foo") 108 | 109 | err = r.Close() 110 | assert.NoError(t, err) 111 | 112 | _, err = w.Write([]byte("bar")) 113 | assert.NotNil(t, err) 114 | 115 | assert.NoError(t, checkWgDone(leakCheckWg)) 116 | 117 | cancel() 118 | ctx, cancel = context.WithTimeout(context.Background(), 2*time.Second) 119 | defer cancel() 120 | 121 | w, err = OpenFifo(ctx, filepath.Join(tmpdir, "f1"), syscall.O_CREAT|syscall.O_WRONLY|syscall.O_NONBLOCK, 0o600) 122 | assert.NoError(t, err) 123 | 124 | written := make(chan struct{}) 125 | go func() { 126 | w.Write([]byte("baz")) 127 | close(written) 128 | }() 129 | 130 | time.Sleep(200 * time.Millisecond) 131 | 132 | r, err = OpenFifo(ctx, filepath.Join(tmpdir, "f1"), syscall.O_RDONLY|syscall.O_NONBLOCK, 0) 133 | assert.NoError(t, err) 134 | n, err = r.Read(b) 135 | assert.NoError(t, err) 136 | assert.Equal(t, string(b[:n]), "baz") 137 | select { 138 | case <-written: 139 | case <-time.After(500 * time.Millisecond): 140 | t.Fatal("content should have been written") 141 | } 142 | 143 | _, err = w.Write([]byte("barbar")) // kernel-buffer 144 | assert.NoError(t, err) 145 | err = w.Close() 146 | assert.NoError(t, err) 147 | n, err = r.Read(b) 148 | assert.NoError(t, err) 149 | assert.Equal(t, string(b[:n]), "barbar") 150 | n, err = r.Read(b) 151 | assert.Equal(t, n, 0) 152 | assert.Equal(t, err, io.EOF) 153 | n, err = r.Read(b) 154 | assert.Equal(t, n, 0) 155 | assert.Equal(t, err, io.EOF) 156 | 157 | assert.NoError(t, checkWgDone(leakCheckWg)) 158 | } 159 | 160 | func TestFifoCancelOneSide(t *testing.T) { 161 | tmpdir, err := os.MkdirTemp("", "fifos") 162 | assert.NoError(t, err) 163 | defer os.RemoveAll(tmpdir) 164 | 165 | leakCheckWg = &sync.WaitGroup{} 166 | defer func() { 167 | leakCheckWg = nil 168 | }() 169 | 170 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 171 | defer cancel() 172 | 173 | f, err := OpenFifo(ctx, filepath.Join(tmpdir, "f0"), syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0o600) 174 | assert.NoError(t, err) 175 | 176 | read := make(chan struct{}) 177 | b := make([]byte, 32) 178 | go func() { 179 | _, err = f.Read(b) 180 | close(read) 181 | }() 182 | 183 | select { 184 | case <-read: 185 | t.Fatal("read should have blocked") 186 | case <-time.After(time.Second): 187 | } 188 | 189 | cerr := f.Close() 190 | assert.NoError(t, cerr) 191 | 192 | select { 193 | case <-read: 194 | case <-time.After(time.Second): 195 | t.Fatal("read should have unblocked") 196 | } 197 | 198 | assert.Equal(t, err, ErrReadClosed) 199 | 200 | assert.NoError(t, checkWgDone(leakCheckWg)) 201 | } 202 | 203 | func TestFifoBlocking(t *testing.T) { 204 | tmpdir, err := os.MkdirTemp("", "fifos") 205 | assert.NoError(t, err) 206 | defer os.RemoveAll(tmpdir) 207 | 208 | leakCheckWg = &sync.WaitGroup{} 209 | defer func() { 210 | leakCheckWg = nil 211 | }() 212 | 213 | ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) 214 | defer cancel() 215 | 216 | f, err := OpenFifo(ctx, filepath.Join(tmpdir, "f0"), syscall.O_RDONLY|syscall.O_CREAT, 0o600) 217 | assert.Exactly(t, nil, f) 218 | assert.EqualError(t, err, "context deadline exceeded") 219 | 220 | select { 221 | case <-ctx.Done(): 222 | default: 223 | t.Fatal("context should have been completed") 224 | } 225 | 226 | assert.NoError(t, checkWgDone(leakCheckWg)) 227 | 228 | cancel() 229 | ctx, cancel = context.WithTimeout(context.Background(), 2*time.Second) 230 | defer cancel() 231 | 232 | var rerr error 233 | var r io.ReadCloser 234 | readerOpen := make(chan struct{}) 235 | go func() { 236 | r, rerr = OpenFifo(ctx, filepath.Join(tmpdir, "f1"), syscall.O_RDONLY|syscall.O_CREAT, 0o600) 237 | close(readerOpen) 238 | }() 239 | 240 | time.Sleep(500 * time.Millisecond) 241 | w, err := OpenFifo(ctx, filepath.Join(tmpdir, "f1"), syscall.O_WRONLY, 0) 242 | assert.NoError(t, err) 243 | 244 | select { 245 | case <-readerOpen: 246 | case <-time.After(time.Second): 247 | t.Fatal("writer should have unblocke reader") 248 | } 249 | 250 | assert.NoError(t, rerr) 251 | 252 | _, err = w.Write([]byte("foobar")) 253 | assert.NoError(t, err) 254 | 255 | b := make([]byte, 32) 256 | n, err := r.Read(b) 257 | assert.NoError(t, err) 258 | assert.Equal(t, string(b[:n]), "foobar") 259 | 260 | assert.NoError(t, checkWgDone(leakCheckWg)) 261 | 262 | err = w.Close() 263 | assert.NoError(t, err) 264 | n, err = r.Read(b) 265 | assert.Equal(t, n, 0) 266 | assert.Equal(t, err, io.EOF) 267 | 268 | assert.NoError(t, checkWgDone(leakCheckWg)) 269 | } 270 | 271 | func TestFifoORDWR(t *testing.T) { 272 | tmpdir, err := os.MkdirTemp("", "fifos") 273 | assert.NoError(t, err) 274 | defer os.RemoveAll(tmpdir) 275 | 276 | leakCheckWg = &sync.WaitGroup{} 277 | defer func() { 278 | leakCheckWg = nil 279 | }() 280 | 281 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 282 | defer cancel() 283 | 284 | f, err := OpenFifo(ctx, filepath.Join(tmpdir, "f0"), syscall.O_RDWR|syscall.O_CREAT, 0o600) 285 | assert.NoError(t, err) 286 | 287 | _, err = f.Write([]byte("foobar")) 288 | assert.NoError(t, err) 289 | 290 | b := make([]byte, 32) 291 | n, err := f.Read(b) 292 | assert.NoError(t, err) 293 | assert.Equal(t, string(b[:n]), "foobar") 294 | 295 | r1, err := OpenFifo(ctx, filepath.Join(tmpdir, "f0"), syscall.O_RDONLY|syscall.O_NONBLOCK, 0) 296 | assert.NoError(t, err) 297 | 298 | _, err = f.Write([]byte("barbar")) 299 | assert.NoError(t, err) 300 | 301 | n, err = r1.Read(b) 302 | assert.NoError(t, err) 303 | assert.Equal(t, string(b[:n]), "barbar") 304 | 305 | r2, err := OpenFifo(ctx, filepath.Join(tmpdir, "f0"), syscall.O_RDONLY, 0) 306 | assert.NoError(t, err) 307 | 308 | _, err = f.Write([]byte("barbaz")) 309 | assert.NoError(t, err) 310 | 311 | n, err = r2.Read(b) 312 | assert.NoError(t, err) 313 | assert.Equal(t, string(b[:n]), "barbaz") 314 | 315 | err = r2.Close() 316 | assert.NoError(t, err) 317 | 318 | _, err = f.Write([]byte("bar123")) 319 | assert.NoError(t, err) 320 | 321 | n, err = r1.Read(b) 322 | assert.NoError(t, err) 323 | assert.Equal(t, string(b[:n]), "bar123") 324 | 325 | err = r1.Close() 326 | assert.NoError(t, err) 327 | 328 | _, err = f.Write([]byte("bar456")) 329 | assert.NoError(t, err) 330 | 331 | r2, err = OpenFifo(ctx, filepath.Join(tmpdir, "f0"), syscall.O_RDONLY, 0) 332 | assert.NoError(t, err) 333 | 334 | n, err = r2.Read(b) 335 | assert.NoError(t, err) 336 | assert.Equal(t, string(b[:n]), "bar456") 337 | 338 | err = f.Close() 339 | assert.NoError(t, err) 340 | 341 | _, err = r2.Read(b) 342 | assert.EqualError(t, err, io.EOF.Error()) 343 | 344 | assert.NoError(t, checkWgDone(leakCheckWg)) 345 | } 346 | 347 | func TestFifoCloseError(t *testing.T) { 348 | tmpdir, err := os.MkdirTemp("", "fifos") 349 | assert.NoError(t, err) 350 | defer os.RemoveAll(tmpdir) 351 | 352 | ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) 353 | defer cancel() 354 | 355 | w, err := OpenFifo(ctx, filepath.Join(tmpdir, t.Name()), syscall.O_WRONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0o600) 356 | assert.NoError(t, err) 357 | w.Close() 358 | 359 | data := []byte("hello world!") 360 | _, err = w.Write(data) 361 | assert.Equal(t, ErrWriteClosed, err) 362 | 363 | r, err := OpenFifo(ctx, filepath.Join(tmpdir, t.Name()), syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0o600) 364 | assert.NoError(t, err) 365 | r.Close() 366 | 367 | buf := make([]byte, len(data)) 368 | _, err = r.Read(buf) 369 | assert.Equal(t, ErrReadClosed, err) 370 | } 371 | 372 | func TestFifoCloseWhileReading(t *testing.T) { 373 | tmpdir, err := os.MkdirTemp("", "fifos") 374 | assert.NoError(t, err) 375 | defer os.RemoveAll(tmpdir) 376 | 377 | ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) 378 | defer cancel() 379 | 380 | r, err := OpenFifo(ctx, filepath.Join(tmpdir, t.Name()), syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0o600) 381 | assert.NoError(t, err) 382 | 383 | read := make(chan struct{}) 384 | readErr := make(chan error) 385 | 386 | go func() { 387 | buf := make([]byte, 32) 388 | _, err := r.Read(buf) 389 | if err != nil { 390 | readErr <- err 391 | return 392 | } 393 | 394 | close(read) 395 | }() 396 | 397 | time.Sleep(500 * time.Millisecond) 398 | r.Close() 399 | 400 | select { 401 | case <-read: 402 | t.Fatal("Read should not succeed") 403 | case err := <-readErr: 404 | assert.Equal(t, ErrReadClosed, err) 405 | case <-time.After(500 * time.Millisecond): 406 | t.Fatal("Read should not be blocked") 407 | } 408 | } 409 | 410 | func TestFifoCloseWhileReadingAndWriting(t *testing.T) { 411 | tmpdir, err := os.MkdirTemp("", "fifos") 412 | assert.NoError(t, err) 413 | defer os.RemoveAll(tmpdir) 414 | 415 | ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) 416 | defer cancel() 417 | 418 | r, err := OpenFifo(ctx, filepath.Join(tmpdir, t.Name()), syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0o600) 419 | assert.NoError(t, err) 420 | 421 | w, err := OpenFifo(ctx, filepath.Join(tmpdir, t.Name()), syscall.O_WRONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0) 422 | assert.NoError(t, err) 423 | 424 | read := make(chan struct{}) 425 | readErr := make(chan error) 426 | wBuffer := []byte("foo") 427 | 428 | go func() { 429 | buf := make([]byte, 32) 430 | _, err := r.Read(buf) 431 | if err != nil { 432 | readErr <- err 433 | return 434 | } 435 | 436 | close(read) 437 | }() 438 | 439 | time.Sleep(500 * time.Millisecond) 440 | 441 | // Close the reader and then write in the writer. 442 | // The reader thread should return an error. 443 | r.Close() 444 | 445 | // The write should fail, the reader end of the pipe is closed. 446 | _, err = w.Write(wBuffer) 447 | assert.Error(t, err) 448 | 449 | select { 450 | case <-read: 451 | t.Fatal("Read should not succeed") 452 | case err := <-readErr: 453 | assert.Error(t, err) 454 | case <-time.After(500 * time.Millisecond): 455 | t.Fatal("Read should not be blocked") 456 | } 457 | } 458 | 459 | func TestFifoWrongRdWrError(t *testing.T) { 460 | tmpdir, err := os.MkdirTemp("", "fifos") 461 | assert.NoError(t, err) 462 | defer os.RemoveAll(tmpdir) 463 | 464 | ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) 465 | defer cancel() 466 | 467 | r, err := OpenFifo(ctx, filepath.Join(tmpdir, t.Name()), syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0o600) 468 | assert.NoError(t, err) 469 | 470 | data := []byte("hello world!") 471 | _, err = r.Write(data) 472 | assert.Equal(t, ErrWrToRDONLY, err) 473 | 474 | w, err := OpenFifo(ctx, filepath.Join(tmpdir, t.Name()), syscall.O_WRONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0o600) 475 | assert.NoError(t, err) 476 | 477 | buf := make([]byte, len(data)) 478 | _, err = w.Read(buf) 479 | assert.Equal(t, ErrRdFrmWRONLY, err) 480 | } 481 | 482 | func checkWgDone(wg *sync.WaitGroup) error { 483 | ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) 484 | defer cancel() 485 | done := make(chan struct{}) 486 | go func() { 487 | wg.Wait() // No way to cancel 488 | close(done) 489 | }() 490 | select { 491 | case <-done: 492 | return nil 493 | case <-ctx.Done(): 494 | return ctx.Err() 495 | } 496 | } 497 | --------------------------------------------------------------------------------