github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/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/nicocha30/gvisor-ligolo/pkg/context"
    24  	"github.com/nicocha30/gvisor-ligolo/pkg/errors/linuxerr"
    25  	"github.com/nicocha30/gvisor-ligolo/pkg/waiter"
    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  		// Delay before trying again.
    56  		if sleepErr := sleepBetweenNamedPipeOpenChecks(ctx); sleepErr != nil {
    57  			// Another application thread may have opened this pipe for
    58  			// writing, succeeded because we previously opened the pipe for
    59  			// reading, and subsequently interrupted us for checkpointing (e.g.
    60  			// this occurs in mknod tests under cooperative save/restore). In
    61  			// this case, our open has to succeed for the checkpoint to include
    62  			// a readable FD for the pipe, which is in turn necessary to
    63  			// restore the other thread's writable FD for the same pipe
    64  			// (otherwise it will get ENXIO). So we have to check
    65  			// nonblockingPipeHasWriter() once last time.
    66  			ok, err := nonblockingPipeHasWriter(fd)
    67  			if err != nil {
    68  				return err
    69  			}
    70  			if ok {
    71  				return nil
    72  			}
    73  			return sleepErr
    74  		}
    75  	}
    76  }
    77  
    78  func nonblockingPipeHasWriter(fd int32) (bool, error) {
    79  	tempPipeMu.Lock()
    80  	defer tempPipeMu.Unlock()
    81  	// Copy 1 byte from fd into the temporary pipe.
    82  	n, err := unix.Tee(int(fd), tempPipeWriteFD, 1, unix.SPLICE_F_NONBLOCK)
    83  	if linuxerr.Equals(linuxerr.EAGAIN, err) {
    84  		// The pipe represented by fd is empty, but has a writer.
    85  		return true, nil
    86  	}
    87  	if err != nil {
    88  		return false, err
    89  	}
    90  	if n == 0 {
    91  		// The pipe represented by fd is empty and has no writer.
    92  		return false, nil
    93  	}
    94  	// The pipe represented by fd is non-empty, so it either has, or has
    95  	// previously had, a writer. Remove the byte copied to the temporary pipe
    96  	// before returning.
    97  	if n, err := unix.Read(tempPipeReadFD, tempPipeBuf[:]); err != nil || n != 1 {
    98  		panic(fmt.Sprintf("failed to drain pipe for gofer.blockUntilNonblockingPipeHasWriter: got (%d, %v), wanted (1, nil)", n, err))
    99  	}
   100  	return true, nil
   101  }
   102  
   103  func sleepBetweenNamedPipeOpenChecks(ctx context.Context) error {
   104  	var q waiter.NeverReady
   105  	left, ok := ctx.BlockWithTimeoutOn(&q, waiter.EventIn, 100*time.Millisecond)
   106  	if !ok && left != 0 {
   107  		return linuxerr.ErrInterrupted
   108  	}
   109  	return nil
   110  }