github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fsimpl/gofer/host_named_pipe.go (about)

     1  // Copyright 2019 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 gofer
    16  
    17  import (
    18  	"fmt"
    19  	"sync"
    20  	"time"
    21  
    22  	"golang.org/x/sys/unix"
    23  	"github.com/SagerNet/gvisor/pkg/context"
    24  	"github.com/SagerNet/gvisor/pkg/errors/linuxerr"
    25  	"github.com/SagerNet/gvisor/pkg/syserror"
    26  )
    27  
    28  // Global pipe used by blockUntilNonblockingPipeHasWriter since we can't create
    29  // pipes after sentry initialization due to syscall filters.
    30  var (
    31  	tempPipeMu      sync.Mutex
    32  	tempPipeReadFD  int
    33  	tempPipeWriteFD int
    34  	tempPipeBuf     [1]byte
    35  )
    36  
    37  func init() {
    38  	var pipeFDs [2]int
    39  	if err := unix.Pipe(pipeFDs[:]); err != nil {
    40  		panic(fmt.Sprintf("failed to create pipe for gofer.blockUntilNonblockingPipeHasWriter: %v", err))
    41  	}
    42  	tempPipeReadFD = pipeFDs[0]
    43  	tempPipeWriteFD = pipeFDs[1]
    44  }
    45  
    46  func blockUntilNonblockingPipeHasWriter(ctx context.Context, fd int32) error {
    47  	for {
    48  		ok, err := nonblockingPipeHasWriter(fd)
    49  		if err != nil {
    50  			return err
    51  		}
    52  		if ok {
    53  			return nil
    54  		}
    55  		if sleepErr := sleepBetweenNamedPipeOpenChecks(ctx); sleepErr != nil {
    56  			// Another application thread may have opened this pipe for
    57  			// writing, succeeded because we previously opened the pipe for
    58  			// reading, and subsequently interrupted us for checkpointing (e.g.
    59  			// this occurs in mknod tests under cooperative save/restore). In
    60  			// this case, our open has to succeed for the checkpoint to include
    61  			// a readable FD for the pipe, which is in turn necessary to
    62  			// restore the other thread's writable FD for the same pipe
    63  			// (otherwise it will get ENXIO). So we have to check
    64  			// nonblockingPipeHasWriter() once last time.
    65  			ok, err := nonblockingPipeHasWriter(fd)
    66  			if err != nil {
    67  				return err
    68  			}
    69  			if ok {
    70  				return nil
    71  			}
    72  			return sleepErr
    73  		}
    74  	}
    75  }
    76  
    77  func nonblockingPipeHasWriter(fd int32) (bool, error) {
    78  	tempPipeMu.Lock()
    79  	defer tempPipeMu.Unlock()
    80  	// Copy 1 byte from fd into the temporary pipe.
    81  	n, err := unix.Tee(int(fd), tempPipeWriteFD, 1, unix.SPLICE_F_NONBLOCK)
    82  	if linuxerr.Equals(linuxerr.EAGAIN, err) {
    83  		// The pipe represented by fd is empty, but has a writer.
    84  		return true, nil
    85  	}
    86  	if err != nil {
    87  		return false, err
    88  	}
    89  	if n == 0 {
    90  		// The pipe represented by fd is empty and has no writer.
    91  		return false, nil
    92  	}
    93  	// The pipe represented by fd is non-empty, so it either has, or has
    94  	// previously had, a writer. Remove the byte copied to the temporary pipe
    95  	// before returning.
    96  	if n, err := unix.Read(tempPipeReadFD, tempPipeBuf[:]); err != nil || n != 1 {
    97  		panic(fmt.Sprintf("failed to drain pipe for gofer.blockUntilNonblockingPipeHasWriter: got (%d, %v), wanted (1, nil)", n, err))
    98  	}
    99  	return true, nil
   100  }
   101  
   102  func sleepBetweenNamedPipeOpenChecks(ctx context.Context) error {
   103  	t := time.NewTimer(100 * time.Millisecond)
   104  	defer t.Stop()
   105  	cancel := ctx.SleepStart()
   106  	select {
   107  	case <-t.C:
   108  		ctx.SleepFinish(true)
   109  		return nil
   110  	case <-cancel:
   111  		ctx.SleepFinish(false)
   112  		return syserror.ErrInterrupted
   113  	}
   114  }