Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
| ... | ... |
@@ -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 |
| 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 |