Browse code

Fix issues with fifos blocking on open

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>

Tonis Tiigi authored on 2016/10/15 08:31:27
Showing 13 changed files
... ...
@@ -144,6 +144,7 @@ clone git github.com/docker/docker-credential-helpers v0.3.0
144 144
 
145 145
 # containerd
146 146
 clone git github.com/docker/containerd 52ef1ceb4b660c42cf4ea9013180a5663968d4c7
147
+clone git github.com/tonistiigi/fifo 8c56881ce5e63e19e2dfc495c8af0fb90916467d
147 148
 
148 149
 # cluster
149 150
 clone git github.com/docker/swarmkit 3b221eb0391d34ae0b9dac65df02b5b64de6dff2
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"errors"
5 5
 	"fmt"
6 6
 	"io"
7
+	"io/ioutil"
7 8
 	"os"
8 9
 	"path/filepath"
9 10
 	"strings"
... ...
@@ -322,10 +323,10 @@ func (clnt *client) AddProcess(ctx context.Context, containerID, processFriendly
322 322
 
323 323
 	// Convert io.ReadClosers to io.Readers
324 324
 	if stdout != nil {
325
-		iopipe.Stdout = openReaderFromPipe(stdout)
325
+		iopipe.Stdout = ioutil.NopCloser(&autoClosingReader{ReadCloser: stdout})
326 326
 	}
327 327
 	if stderr != nil {
328
-		iopipe.Stderr = openReaderFromPipe(stderr)
328
+		iopipe.Stderr = ioutil.NopCloser(&autoClosingReader{ReadCloser: stderr})
329 329
 	}
330 330
 
331 331
 	proc := &process{
... ...
@@ -7,11 +7,13 @@ import (
7 7
 	"os"
8 8
 	"path/filepath"
9 9
 	"syscall"
10
+	"time"
10 11
 
11 12
 	"github.com/Sirupsen/logrus"
12 13
 	containerd "github.com/docker/containerd/api/grpc/types"
13 14
 	"github.com/docker/docker/pkg/ioutils"
14 15
 	"github.com/opencontainers/runtime-spec/specs-go"
16
+	"github.com/tonistiigi/fifo"
15 17
 	"golang.org/x/net/context"
16 18
 )
17 19
 
... ...
@@ -207,15 +209,15 @@ func (ctr *container) handleEvent(e *containerd.Event) error {
207 207
 // discardFifos attempts to fully read the container fifos to unblock processes
208 208
 // that may be blocked on the writer side.
209 209
 func (ctr *container) discardFifos() {
210
+	ctx, _ := context.WithTimeout(context.Background(), 3*time.Second)
210 211
 	for _, i := range []int{syscall.Stdout, syscall.Stderr} {
211
-		f := ctr.fifo(i)
212
-		c := make(chan struct{})
212
+		f, err := fifo.OpenFifo(ctx, ctr.fifo(i), syscall.O_RDONLY|syscall.O_NONBLOCK, 0)
213
+		if err != nil {
214
+			logrus.Warnf("error opening fifo %v for discarding: %+v", f, err)
215
+			continue
216
+		}
213 217
 		go func() {
214
-			r := openReaderFromFifo(f)
215
-			close(c) // this channel is used to not close the writer too early, before readonly open has been called.
216
-			io.Copy(ioutil.Discard, r)
218
+			io.Copy(ioutil.Discard, f)
217 219
 		}()
218
-		<-c
219
-		closeReaderFifo(f) // avoid blocking permanently on open if there is no writer side
220 220
 	}
221 221
 }
... ...
@@ -3,6 +3,7 @@ package libcontainerd
3 3
 import (
4 4
 	"fmt"
5 5
 	"io"
6
+	"io/ioutil"
6 7
 	"strings"
7 8
 	"syscall"
8 9
 	"time"
... ...
@@ -131,10 +132,10 @@ func (ctr *container) start() error {
131 131
 
132 132
 	// Convert io.ReadClosers to io.Readers
133 133
 	if stdout != nil {
134
-		iopipe.Stdout = openReaderFromPipe(stdout)
134
+		iopipe.Stdout = ioutil.NopCloser(&autoClosingReader{ReadCloser: stdout})
135 135
 	}
136 136
 	if stderr != nil {
137
-		iopipe.Stderr = openReaderFromPipe(stderr)
137
+		iopipe.Stderr = ioutil.NopCloser(&autoClosingReader{ReadCloser: stderr})
138 138
 	}
139 139
 
140 140
 	// Save the PID
... ...
@@ -1,14 +1,16 @@
1 1
 package libcontainerd
2 2
 
3 3
 import (
4
-	"fmt"
5 4
 	"io"
5
+	"io/ioutil"
6 6
 	"os"
7 7
 	"path/filepath"
8 8
 	"syscall"
9
+	"time"
9 10
 
10 11
 	containerd "github.com/docker/containerd/api/grpc/types"
11 12
 	"github.com/docker/docker/pkg/ioutils"
13
+	"github.com/tonistiigi/fifo"
12 14
 	"golang.org/x/net/context"
13 15
 )
14 16
 
... ...
@@ -26,34 +28,53 @@ type process struct {
26 26
 	dir string
27 27
 }
28 28
 
29
-func (p *process) openFifos(terminal bool) (*IOPipe, error) {
30
-	bundleDir := p.dir
31
-	if err := os.MkdirAll(bundleDir, 0700); err != nil {
29
+func (p *process) openFifos(terminal bool) (pipe *IOPipe, err error) {
30
+	if err := os.MkdirAll(p.dir, 0700); err != nil {
32 31
 		return nil, err
33 32
 	}
34 33
 
35
-	for i := 0; i < 3; i++ {
36
-		f := p.fifo(i)
37
-		if err := syscall.Mkfifo(f, 0700); err != nil && !os.IsExist(err) {
38
-			return nil, fmt.Errorf("mkfifo: %s %v", f, err)
39
-		}
40
-	}
34
+	ctx, _ := context.WithTimeout(context.Background(), 15*time.Second)
41 35
 
42 36
 	io := &IOPipe{}
43
-	stdinf, err := os.OpenFile(p.fifo(syscall.Stdin), syscall.O_RDWR, 0)
37
+
38
+	stdin, err := fifo.OpenFifo(ctx, p.fifo(syscall.Stdin), syscall.O_WRONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700)
39
+	if err != nil {
40
+		return nil, err
41
+	}
42
+
43
+	defer func() {
44
+		if err != nil {
45
+			stdin.Close()
46
+		}
47
+	}()
48
+
49
+	io.Stdout, err = fifo.OpenFifo(ctx, p.fifo(syscall.Stdout), syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700)
44 50
 	if err != nil {
45 51
 		return nil, err
46 52
 	}
47 53
 
48
-	io.Stdout = openReaderFromFifo(p.fifo(syscall.Stdout))
54
+	defer func() {
55
+		if err != nil {
56
+			io.Stdout.Close()
57
+		}
58
+	}()
59
+
49 60
 	if !terminal {
50
-		io.Stderr = openReaderFromFifo(p.fifo(syscall.Stderr))
61
+		io.Stderr, err = fifo.OpenFifo(ctx, p.fifo(syscall.Stderr), syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700)
62
+		if err != nil {
63
+			return nil, err
64
+		}
65
+		defer func() {
66
+			if err != nil {
67
+				io.Stderr.Close()
68
+			}
69
+		}()
51 70
 	} else {
52
-		io.Stderr = emptyReader{}
71
+		io.Stderr = ioutil.NopCloser(emptyReader{})
53 72
 	}
54 73
 
55
-	io.Stdin = ioutils.NewWriteCloserWrapper(stdinf, func() error {
56
-		stdinf.Close()
74
+	io.Stdin = ioutils.NewWriteCloserWrapper(stdin, func() error {
75
+		stdin.Close()
57 76
 		_, err := p.client.remote.apiClient.UpdateProcess(context.Background(), &containerd.UpdateProcessRequest{
58 77
 			Id:         p.containerID,
59 78
 			Pid:        p.friendlyName,
... ...
@@ -67,8 +88,8 @@ func (p *process) openFifos(terminal bool) (*IOPipe, error) {
67 67
 
68 68
 func (p *process) closeFifos(io *IOPipe) {
69 69
 	io.Stdin.Close()
70
-	closeReaderFifo(p.fifo(syscall.Stdout))
71
-	closeReaderFifo(p.fifo(syscall.Stderr))
70
+	io.Stdout.Close()
71
+	io.Stderr.Close()
72 72
 }
73 73
 
74 74
 type emptyReader struct{}
... ...
@@ -77,34 +98,6 @@ func (r emptyReader) Read(b []byte) (int, error) {
77 77
 	return 0, io.EOF
78 78
 }
79 79
 
80
-func openReaderFromFifo(fn string) io.Reader {
81
-	r, w := io.Pipe()
82
-	c := make(chan struct{})
83
-	go func() {
84
-		close(c)
85
-		stdoutf, err := os.OpenFile(fn, syscall.O_RDONLY, 0)
86
-		if err != nil {
87
-			r.CloseWithError(err)
88
-		}
89
-		if _, err := io.Copy(w, stdoutf); err != nil {
90
-			r.CloseWithError(err)
91
-		}
92
-		w.Close()
93
-		stdoutf.Close()
94
-	}()
95
-	<-c // wait for the goroutine to get scheduled and syscall to block
96
-	return r
97
-}
98
-
99
-// closeReaderFifo closes fifo that may be blocked on open by opening the write side.
100
-func closeReaderFifo(fn string) {
101
-	f, err := os.OpenFile(fn, syscall.O_WRONLY|syscall.O_NONBLOCK, 0)
102
-	if err != nil {
103
-		return
104
-	}
105
-	f.Close()
106
-}
107
-
108 80
 func (p *process) fifo(index int) string {
109 81
 	return filepath.Join(p.dir, p.friendlyName+"-"+fdNames[index])
110 82
 }
... ...
@@ -2,6 +2,7 @@ package libcontainerd
2 2
 
3 3
 import (
4 4
 	"io"
5
+	"sync"
5 6
 
6 7
 	"github.com/Microsoft/hcsshim"
7 8
 	"github.com/docker/docker/pkg/ioutils"
... ...
@@ -18,16 +19,17 @@ type process struct {
18 18
 	hcsProcess  hcsshim.Process
19 19
 }
20 20
 
21
-func openReaderFromPipe(p io.ReadCloser) io.Reader {
22
-	r, w := io.Pipe()
23
-	go func() {
24
-		if _, err := io.Copy(w, p); err != nil {
25
-			r.CloseWithError(err)
26
-		}
27
-		w.Close()
28
-		p.Close()
29
-	}()
30
-	return r
21
+type autoClosingReader struct {
22
+	io.ReadCloser
23
+	sync.Once
24
+}
25
+
26
+func (r *autoClosingReader) Read(b []byte) (n int, err error) {
27
+	n, err = r.ReadCloser.Read(b)
28
+	if err == io.EOF {
29
+		r.Once.Do(func() { r.ReadCloser.Close() })
30
+	}
31
+	return
31 32
 }
32 33
 
33 34
 func createStdInCloser(pipe io.WriteCloser, process hcsshim.Process) io.WriteCloser {
... ...
@@ -61,7 +61,7 @@ type CreateOption interface {
61 61
 // IOPipe contains the stdio streams.
62 62
 type IOPipe struct {
63 63
 	Stdin    io.WriteCloser
64
-	Stdout   io.Reader
65
-	Stderr   io.Reader
64
+	Stdout   io.ReadCloser
65
+	Stderr   io.ReadCloser
66 66
 	Terminal bool // Whether stderr is connected on Windows
67 67
 }
68 68
new file mode 100644
... ...
@@ -0,0 +1,21 @@
0
+MIT
1
+
2
+Copyright (C) 2016 Tõnis Tiigi <tonistiigi@gmail.com>
3
+
4
+Permission is hereby granted, free of charge, to any person obtaining a copy
5
+of this software and associated documentation files (the "Software"), to deal
6
+in the Software without restriction, including without limitation the rights
7
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+copies of the Software, and to permit persons to whom the Software is
9
+furnished to do so, subject to the following conditions:
10
+
11
+The above copyright notice and this permission notice shall be included in
12
+all copies or substantial portions of the Software.
13
+
14
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+THE SOFTWARE.
0 21
\ No newline at end of file
1 22
new file mode 100644
... ...
@@ -0,0 +1,13 @@
0
+.PHONY: fmt vet test deps
1
+
2
+test: deps
3
+	go test -v ./...
4
+
5
+deps:
6
+	go get -d -t ./...
7
+
8
+fmt:
9
+	gofmt -s -l .
10
+
11
+vet:
12
+	go vet ./...
0 13
new file mode 100644
... ...
@@ -0,0 +1,215 @@
0
+package fifo
1
+
2
+import (
3
+	"context"
4
+	"io"
5
+	"os"
6
+	"runtime"
7
+	"sync"
8
+	"syscall"
9
+
10
+	"github.com/pkg/errors"
11
+)
12
+
13
+type fifo struct {
14
+	flag        int
15
+	opened      chan struct{}
16
+	closed      chan struct{}
17
+	closing     chan struct{}
18
+	err         error
19
+	file        *os.File
20
+	closingOnce sync.Once // close has been called
21
+	closedOnce  sync.Once // fifo is closed
22
+	handle      *handle
23
+}
24
+
25
+var leakCheckWg *sync.WaitGroup
26
+
27
+// OpenFifo opens a fifo. Returns io.ReadWriteCloser.
28
+// Context can be used to cancel this function until open(2) has not returned.
29
+// Accepted flags:
30
+// - syscall.O_CREAT - create new fifo if one doesn't exist
31
+// - syscall.O_RDONLY - open fifo only from reader side
32
+// - syscall.O_WRONLY - open fifo only from writer side
33
+// - syscall.O_RDWR - open fifo from both sides, never block on syscall level
34
+// - syscall.O_NONBLOCK - return io.ReadWriteCloser even if other side of the
35
+//     fifo isn't open. read/write will be connected after the actual fifo is
36
+//     open or after fifo is closed.
37
+func OpenFifo(ctx context.Context, fn string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
38
+	if _, err := os.Stat(fn); err != nil {
39
+		if os.IsNotExist(err) && flag&syscall.O_CREAT != 0 {
40
+			if err := syscall.Mkfifo(fn, uint32(perm&os.ModePerm)); err != nil && !os.IsExist(err) {
41
+				return nil, errors.Wrapf(err, "error creating fifo %v", fn)
42
+			}
43
+		} else {
44
+			return nil, err
45
+		}
46
+	}
47
+
48
+	block := flag&syscall.O_NONBLOCK == 0 || flag&syscall.O_RDWR != 0
49
+
50
+	flag &= ^syscall.O_CREAT
51
+	flag &= ^syscall.O_NONBLOCK
52
+
53
+	h, err := getHandle(fn)
54
+	if err != nil {
55
+		return nil, err
56
+	}
57
+
58
+	f := &fifo{
59
+		handle:  h,
60
+		flag:    flag,
61
+		opened:  make(chan struct{}),
62
+		closed:  make(chan struct{}),
63
+		closing: make(chan struct{}),
64
+	}
65
+
66
+	wg := leakCheckWg
67
+	if wg != nil {
68
+		wg.Add(2)
69
+	}
70
+
71
+	go func() {
72
+		if wg != nil {
73
+			defer wg.Done()
74
+		}
75
+		select {
76
+		case <-ctx.Done():
77
+			f.Close()
78
+		case <-f.opened:
79
+		case <-f.closed:
80
+		}
81
+	}()
82
+	go func() {
83
+		if wg != nil {
84
+			defer wg.Done()
85
+		}
86
+		var file *os.File
87
+		fn, err := h.Path()
88
+		if err == nil {
89
+			file, err = os.OpenFile(fn, flag, 0)
90
+		}
91
+		select {
92
+		case <-f.closing:
93
+			if err == nil {
94
+				select {
95
+				case <-ctx.Done():
96
+					err = ctx.Err()
97
+				default:
98
+					err = errors.Errorf("fifo %v was closed before opening", fn)
99
+				}
100
+				if file != nil {
101
+					file.Close()
102
+				}
103
+			}
104
+		default:
105
+		}
106
+		if err != nil {
107
+			f.closedOnce.Do(func() {
108
+				f.err = err
109
+				close(f.closed)
110
+			})
111
+			return
112
+		}
113
+		f.file = file
114
+		close(f.opened)
115
+	}()
116
+	if block {
117
+		select {
118
+		case <-f.opened:
119
+		case <-f.closed:
120
+			return nil, f.err
121
+		}
122
+	}
123
+	return f, nil
124
+}
125
+
126
+// Read from a fifo to a byte array.
127
+func (f *fifo) Read(b []byte) (int, error) {
128
+	if f.flag&syscall.O_WRONLY > 0 {
129
+		return 0, errors.New("reading from write-only fifo")
130
+	}
131
+	select {
132
+	case <-f.opened:
133
+		return f.file.Read(b)
134
+	default:
135
+	}
136
+	select {
137
+	case <-f.opened:
138
+		return f.file.Read(b)
139
+	case <-f.closed:
140
+		return 0, errors.New("reading from a closed fifo")
141
+	}
142
+}
143
+
144
+// Write from byte array to a fifo.
145
+func (f *fifo) Write(b []byte) (int, error) {
146
+	if f.flag&(syscall.O_WRONLY|syscall.O_RDWR) == 0 {
147
+		return 0, errors.New("writing to read-only fifo")
148
+	}
149
+	select {
150
+	case <-f.opened:
151
+		return f.file.Write(b)
152
+	default:
153
+	}
154
+	select {
155
+	case <-f.opened:
156
+		return f.file.Write(b)
157
+	case <-f.closed:
158
+		return 0, errors.New("writing to a closed fifo")
159
+	}
160
+}
161
+
162
+// Close the fifo. Next reads/writes will error. This method can also be used
163
+// before open(2) has returned and fifo was never opened.
164
+func (f *fifo) Close() error {
165
+	for {
166
+		select {
167
+		case <-f.closed:
168
+			f.handle.Close()
169
+			return f.err
170
+		default:
171
+			select {
172
+			case <-f.opened:
173
+				f.closedOnce.Do(func() {
174
+					f.err = f.file.Close()
175
+					close(f.closed)
176
+				})
177
+			default:
178
+				if f.flag&syscall.O_RDWR != 0 {
179
+					runtime.Gosched()
180
+					break
181
+				}
182
+				f.closingOnce.Do(func() {
183
+					close(f.closing)
184
+				})
185
+				reverseMode := syscall.O_WRONLY
186
+				if f.flag&syscall.O_WRONLY > 0 {
187
+					reverseMode = syscall.O_RDONLY
188
+				}
189
+				fn, err := f.handle.Path()
190
+				// if Close() is called concurrently(shouldn't) it may cause error
191
+				// because handle is closed
192
+				select {
193
+				case <-f.closed:
194
+				default:
195
+					if err != nil {
196
+						// Path has become invalid. We will leak a goroutine.
197
+						// This case should not happen in linux.
198
+						f.closedOnce.Do(func() {
199
+							f.err = err
200
+							close(f.closed)
201
+						})
202
+						<-f.closed
203
+						break
204
+					}
205
+					f, err := os.OpenFile(fn, reverseMode|syscall.O_NONBLOCK, 0)
206
+					if err == nil {
207
+						f.Close()
208
+					}
209
+					runtime.Gosched()
210
+				}
211
+			}
212
+		}
213
+	}
214
+}
0 215
new file mode 100644
... ...
@@ -0,0 +1,70 @@
0
+// +build linux
1
+
2
+package fifo
3
+
4
+import (
5
+	"fmt"
6
+	"os"
7
+	"sync"
8
+	"syscall"
9
+
10
+	"github.com/pkg/errors"
11
+)
12
+
13
+const O_PATH = 010000000
14
+
15
+type handle struct {
16
+	f         *os.File
17
+	dev       uint64
18
+	ino       uint64
19
+	closeOnce sync.Once
20
+}
21
+
22
+func getHandle(fn string) (*handle, error) {
23
+	f, err := os.OpenFile(fn, O_PATH, 0)
24
+	if err != nil {
25
+		return nil, errors.Wrapf(err, "failed to open %v with O_PATH", fn)
26
+	}
27
+
28
+	var stat syscall.Stat_t
29
+	if err := syscall.Fstat(int(f.Fd()), &stat); err != nil {
30
+		f.Close()
31
+		return nil, errors.Wrapf(err, "failed to stat handle %v", f.Fd())
32
+	}
33
+
34
+	h := &handle{
35
+		f:   f,
36
+		dev: stat.Dev,
37
+		ino: stat.Ino,
38
+	}
39
+
40
+	// check /proc just in case
41
+	if _, err := os.Stat(h.procPath()); err != nil {
42
+		f.Close()
43
+		return nil, errors.Wrapf(err, "couldn't stat %v", h.procPath())
44
+	}
45
+
46
+	return h, nil
47
+}
48
+
49
+func (h *handle) procPath() string {
50
+	return fmt.Sprintf("/proc/self/fd/%d", h.f.Fd())
51
+}
52
+
53
+func (h *handle) Path() (string, error) {
54
+	var stat syscall.Stat_t
55
+	if err := syscall.Stat(h.procPath(), &stat); err != nil {
56
+		return "", errors.Wrapf(err, "path %v could not be statted", h.procPath())
57
+	}
58
+	if stat.Dev != h.dev || stat.Ino != h.ino {
59
+		return "", errors.Errorf("failed to verify handle %v/%v %v/%v", stat.Dev, h.dev, stat.Ino, h.ino)
60
+	}
61
+	return h.procPath(), nil
62
+}
63
+
64
+func (h *handle) Close() error {
65
+	h.closeOnce.Do(func() {
66
+		h.f.Close()
67
+	})
68
+	return nil
69
+}
0 70
new file mode 100644
... ...
@@ -0,0 +1,45 @@
0
+// +build !linux
1
+
2
+package fifo
3
+
4
+import (
5
+	"syscall"
6
+
7
+	"github.com/pkg/errors"
8
+)
9
+
10
+type handle struct {
11
+	fn  string
12
+	dev uint64
13
+	ino uint64
14
+}
15
+
16
+func getHandle(fn string) (*handle, error) {
17
+	var stat syscall.Stat_t
18
+	if err := syscall.Stat(fn, &stat); err != nil {
19
+		return nil, errors.Wrapf(err, "failed to stat %v", fn)
20
+	}
21
+
22
+	h := &handle{
23
+		fn:  fn,
24
+		dev: uint64(stat.Dev),
25
+		ino: stat.Ino,
26
+	}
27
+
28
+	return h, nil
29
+}
30
+
31
+func (h *handle) Path() (string, error) {
32
+	var stat syscall.Stat_t
33
+	if err := syscall.Stat(h.fn, &stat); err != nil {
34
+		return "", errors.Wrapf(err, "path %v could not be statted", h.fn)
35
+	}
36
+	if uint64(stat.Dev) != h.dev || stat.Ino != h.ino {
37
+		return "", errors.Errorf("failed to verify handle %v/%v %v/%v", stat.Dev, h.dev, stat.Ino, h.ino)
38
+	}
39
+	return h.fn, nil
40
+}
41
+
42
+func (h *handle) Close() error {
43
+	return nil
44
+}
0 45
new file mode 100644
... ...
@@ -0,0 +1,30 @@
0
+### fifo
1
+
2
+Go package for handling fifos in a sane way.
3
+
4
+```
5
+// OpenFifo opens a fifo. Returns io.ReadWriteCloser.
6
+// Context can be used to cancel this function until open(2) has not returned.
7
+// Accepted flags:
8
+// - syscall.O_CREAT - create new fifo if one doesn't exist
9
+// - syscall.O_RDONLY - open fifo only from reader side
10
+// - syscall.O_WRONLY - open fifo only from writer side
11
+// - syscall.O_RDWR - open fifo from both sides, never block on syscall level
12
+// - syscall.O_NONBLOCK - return io.ReadWriteCloser even if other side of the
13
+//     fifo isn't open. read/write will be connected after the actual fifo is
14
+//     open or after fifo is closed.
15
+func OpenFifo(ctx context.Context, fn string, flag int, perm os.FileMode) (io.ReadWriteCloser, error)
16
+
17
+
18
+// Read from a fifo to a byte array.
19
+func (f *fifo) Read(b []byte) (int, error)
20
+
21
+
22
+// Write from byte array to a fifo.
23
+func (f *fifo) Write(b []byte) (int, error)
24
+
25
+
26
+// Close the fifo. Next reads/writes will error. This method can also be used
27
+// before open(2) has returned and fifo was never opened.
28
+func (f *fifo) Close() error 
29
+```
0 30
\ No newline at end of file