github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fs/fdpipe/pipe.go (about)

     1  // Copyright 2018 The gVisor 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  // Package fdpipe implements common namedpipe opening and accessing logic.
    16  package fdpipe
    17  
    18  import (
    19  	"os"
    20  
    21  	"golang.org/x/sys/unix"
    22  	"github.com/SagerNet/gvisor/pkg/context"
    23  	"github.com/SagerNet/gvisor/pkg/errors/linuxerr"
    24  	"github.com/SagerNet/gvisor/pkg/fd"
    25  	"github.com/SagerNet/gvisor/pkg/fdnotifier"
    26  	"github.com/SagerNet/gvisor/pkg/log"
    27  	"github.com/SagerNet/gvisor/pkg/safemem"
    28  	"github.com/SagerNet/gvisor/pkg/secio"
    29  	"github.com/SagerNet/gvisor/pkg/sentry/fs"
    30  	"github.com/SagerNet/gvisor/pkg/sentry/fs/fsutil"
    31  	"github.com/SagerNet/gvisor/pkg/sync"
    32  	"github.com/SagerNet/gvisor/pkg/syserror"
    33  	"github.com/SagerNet/gvisor/pkg/usermem"
    34  	"github.com/SagerNet/gvisor/pkg/waiter"
    35  )
    36  
    37  // pipeOperations are the fs.FileOperations of a host pipe.
    38  //
    39  // +stateify savable
    40  type pipeOperations struct {
    41  	fsutil.FilePipeSeek             `state:"nosave"`
    42  	fsutil.FileNotDirReaddir        `state:"nosave"`
    43  	fsutil.FileNoFsync              `state:"nosave"`
    44  	fsutil.FileNoopFlush            `state:"nosave"`
    45  	fsutil.FileNoMMap               `state:"nosave"`
    46  	fsutil.FileNoIoctl              `state:"nosave"`
    47  	fsutil.FileNoSplice             `state:"nosave"`
    48  	fsutil.FileUseInodeUnstableAttr `state:"nosave"`
    49  	waiter.Queue                    `state:"nosave"`
    50  
    51  	// flags are the flags used to open the pipe.
    52  	flags fs.FileFlags `state:".(fs.FileFlags)"`
    53  
    54  	// opener is how the pipe was opened.
    55  	opener NonBlockingOpener `state:"wait"`
    56  
    57  	// file represents the host pipe.
    58  	file *fd.FD `state:"nosave"`
    59  
    60  	// mu protects readAheadBuffer access below.
    61  	mu sync.Mutex `state:"nosave"`
    62  
    63  	// readAheadBuffer contains read bytes that have not yet been read
    64  	// by the application but need to be buffered for save-restore for correct
    65  	// opening semantics.  The readAheadBuffer will only be non-empty when the
    66  	// is first opened and will be drained by subsequent reads on the pipe.
    67  	readAheadBuffer []byte
    68  }
    69  
    70  // newPipeOperations returns an implementation of fs.FileOperations for a pipe.
    71  func newPipeOperations(ctx context.Context, opener NonBlockingOpener, flags fs.FileFlags, file *fd.FD, readAheadBuffer []byte) (*pipeOperations, error) {
    72  	pipeOps := &pipeOperations{
    73  		flags:           flags,
    74  		opener:          opener,
    75  		file:            file,
    76  		readAheadBuffer: readAheadBuffer,
    77  	}
    78  	if err := pipeOps.init(); err != nil {
    79  		return nil, err
    80  	}
    81  	return pipeOps, nil
    82  }
    83  
    84  // init initializes p.file.
    85  func (p *pipeOperations) init() error {
    86  	var s unix.Stat_t
    87  	if err := unix.Fstat(p.file.FD(), &s); err != nil {
    88  		log.Warningf("pipe: cannot stat fd %d: %v", p.file.FD(), err)
    89  		return unix.EINVAL
    90  	}
    91  	if (s.Mode & unix.S_IFMT) != unix.S_IFIFO {
    92  		log.Warningf("pipe: cannot load fd %d as pipe, file type: %o", p.file.FD(), s.Mode)
    93  		return unix.EINVAL
    94  	}
    95  	if err := unix.SetNonblock(p.file.FD(), true); err != nil {
    96  		return err
    97  	}
    98  	return fdnotifier.AddFD(int32(p.file.FD()), &p.Queue)
    99  }
   100  
   101  // EventRegister implements waiter.Waitable.EventRegister.
   102  func (p *pipeOperations) EventRegister(e *waiter.Entry, mask waiter.EventMask) {
   103  	p.Queue.EventRegister(e, mask)
   104  	fdnotifier.UpdateFD(int32(p.file.FD()))
   105  }
   106  
   107  // EventUnregister implements waiter.Waitable.EventUnregister.
   108  func (p *pipeOperations) EventUnregister(e *waiter.Entry) {
   109  	p.Queue.EventUnregister(e)
   110  	fdnotifier.UpdateFD(int32(p.file.FD()))
   111  }
   112  
   113  // Readiness returns a mask of ready events for stream.
   114  func (p *pipeOperations) Readiness(mask waiter.EventMask) (eventMask waiter.EventMask) {
   115  	return fdnotifier.NonBlockingPoll(int32(p.file.FD()), mask)
   116  }
   117  
   118  // Release implements fs.FileOperations.Release.
   119  func (p *pipeOperations) Release(context.Context) {
   120  	fdnotifier.RemoveFD(int32(p.file.FD()))
   121  	p.file.Close()
   122  	p.file = nil
   123  }
   124  
   125  // Read implements fs.FileOperations.Read.
   126  func (p *pipeOperations) Read(ctx context.Context, file *fs.File, dst usermem.IOSequence, offset int64) (int64, error) {
   127  	// Drain the read ahead buffer, if it contains anything first.
   128  	var bufN int
   129  	var bufErr error
   130  	p.mu.Lock()
   131  	if len(p.readAheadBuffer) > 0 {
   132  		bufN, bufErr = dst.CopyOut(ctx, p.readAheadBuffer)
   133  		p.readAheadBuffer = p.readAheadBuffer[bufN:]
   134  		dst = dst.DropFirst(bufN)
   135  	}
   136  	p.mu.Unlock()
   137  	if dst.NumBytes() == 0 || bufErr != nil {
   138  		return int64(bufN), bufErr
   139  	}
   140  
   141  	// Pipes expect full reads.
   142  	n, err := dst.CopyOutFrom(ctx, safemem.FromIOReader{secio.FullReader{p.file}})
   143  	total := int64(bufN) + n
   144  	if err != nil && isBlockError(err) {
   145  		return total, syserror.ErrWouldBlock
   146  	}
   147  	return total, err
   148  }
   149  
   150  // Write implements fs.FileOperations.Write.
   151  func (p *pipeOperations) Write(ctx context.Context, file *fs.File, src usermem.IOSequence, offset int64) (int64, error) {
   152  	n, err := src.CopyInTo(ctx, safemem.FromIOWriter{p.file})
   153  	if err != nil && isBlockError(err) {
   154  		return n, syserror.ErrWouldBlock
   155  	}
   156  	return n, err
   157  }
   158  
   159  // isBlockError unwraps os errors and checks if they are caused by EAGAIN or
   160  // EWOULDBLOCK. This is so they can be transformed into syserror.ErrWouldBlock.
   161  func isBlockError(err error) bool {
   162  	if linuxerr.Equals(linuxerr.EAGAIN, err) || linuxerr.Equals(linuxerr.EWOULDBLOCK, err) {
   163  		return true
   164  	}
   165  	if pe, ok := err.(*os.PathError); ok {
   166  		return isBlockError(pe.Err)
   167  	}
   168  	return false
   169  }