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

     1  // Copyright 2020 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 eventfd implements event fds.
    16  package eventfd
    17  
    18  import (
    19  	"math"
    20  	"sync"
    21  
    22  	"golang.org/x/sys/unix"
    23  	"github.com/SagerNet/gvisor/pkg/abi/linux"
    24  	"github.com/SagerNet/gvisor/pkg/context"
    25  	"github.com/SagerNet/gvisor/pkg/fdnotifier"
    26  	"github.com/SagerNet/gvisor/pkg/hostarch"
    27  	"github.com/SagerNet/gvisor/pkg/log"
    28  	"github.com/SagerNet/gvisor/pkg/sentry/vfs"
    29  	"github.com/SagerNet/gvisor/pkg/syserror"
    30  	"github.com/SagerNet/gvisor/pkg/usermem"
    31  	"github.com/SagerNet/gvisor/pkg/waiter"
    32  )
    33  
    34  // EventFileDescription implements vfs.FileDescriptionImpl for file-based event
    35  // notification (eventfd). Eventfds are usually internal to the Sentry but in
    36  // certain situations they may be converted into a host-backed eventfd.
    37  //
    38  // +stateify savable
    39  type EventFileDescription struct {
    40  	vfsfd vfs.FileDescription
    41  	vfs.FileDescriptionDefaultImpl
    42  	vfs.DentryMetadataFileDescriptionImpl
    43  	vfs.NoLockFD
    44  
    45  	// queue is used to notify interested parties when the event object
    46  	// becomes readable or writable.
    47  	queue waiter.Queue
    48  
    49  	// mu protects the fields below.
    50  	mu sync.Mutex `state:"nosave"`
    51  
    52  	// val is the current value of the event counter.
    53  	val uint64
    54  
    55  	// semMode specifies whether the event is in "semaphore" mode.
    56  	semMode bool
    57  
    58  	// hostfd indicates whether this eventfd is passed through to the host.
    59  	hostfd int
    60  }
    61  
    62  var _ vfs.FileDescriptionImpl = (*EventFileDescription)(nil)
    63  
    64  // New creates a new event fd.
    65  func New(ctx context.Context, vfsObj *vfs.VirtualFilesystem, initVal uint64, semMode bool, flags uint32) (*vfs.FileDescription, error) {
    66  	vd := vfsObj.NewAnonVirtualDentry("[eventfd]")
    67  	defer vd.DecRef(ctx)
    68  	efd := &EventFileDescription{
    69  		val:     initVal,
    70  		semMode: semMode,
    71  		hostfd:  -1,
    72  	}
    73  	if err := efd.vfsfd.Init(efd, flags, vd.Mount(), vd.Dentry(), &vfs.FileDescriptionOptions{
    74  		UseDentryMetadata: true,
    75  		DenyPRead:         true,
    76  		DenyPWrite:        true,
    77  	}); err != nil {
    78  		return nil, err
    79  	}
    80  	return &efd.vfsfd, nil
    81  }
    82  
    83  // HostFD returns the host eventfd associated with this event.
    84  func (efd *EventFileDescription) HostFD() (int, error) {
    85  	efd.mu.Lock()
    86  	defer efd.mu.Unlock()
    87  	if efd.hostfd >= 0 {
    88  		return efd.hostfd, nil
    89  	}
    90  
    91  	flags := linux.EFD_NONBLOCK
    92  	if efd.semMode {
    93  		flags |= linux.EFD_SEMAPHORE
    94  	}
    95  
    96  	fd, _, errno := unix.Syscall(unix.SYS_EVENTFD2, uintptr(efd.val), uintptr(flags), 0)
    97  	if errno != 0 {
    98  		return -1, errno
    99  	}
   100  
   101  	if err := fdnotifier.AddFD(int32(fd), &efd.queue); err != nil {
   102  		if closeErr := unix.Close(int(fd)); closeErr != nil {
   103  			log.Warningf("close(%d) eventfd failed: %v", fd, closeErr)
   104  		}
   105  		return -1, err
   106  	}
   107  
   108  	efd.hostfd = int(fd)
   109  	return efd.hostfd, nil
   110  }
   111  
   112  // Release implements vfs.FileDescriptionImpl.Release.
   113  func (efd *EventFileDescription) Release(context.Context) {
   114  	efd.mu.Lock()
   115  	defer efd.mu.Unlock()
   116  	if efd.hostfd >= 0 {
   117  		fdnotifier.RemoveFD(int32(efd.hostfd))
   118  		if closeErr := unix.Close(int(efd.hostfd)); closeErr != nil {
   119  			log.Warningf("close(%d) eventfd failed: %v", efd.hostfd, closeErr)
   120  		}
   121  		efd.hostfd = -1
   122  	}
   123  }
   124  
   125  // Read implements vfs.FileDescriptionImpl.Read.
   126  func (efd *EventFileDescription) Read(ctx context.Context, dst usermem.IOSequence, _ vfs.ReadOptions) (int64, error) {
   127  	if dst.NumBytes() < 8 {
   128  		return 0, unix.EINVAL
   129  	}
   130  	if err := efd.read(ctx, dst); err != nil {
   131  		return 0, err
   132  	}
   133  	return 8, nil
   134  }
   135  
   136  // Write implements vfs.FileDescriptionImpl.Write.
   137  func (efd *EventFileDescription) Write(ctx context.Context, src usermem.IOSequence, _ vfs.WriteOptions) (int64, error) {
   138  	if src.NumBytes() < 8 {
   139  		return 0, unix.EINVAL
   140  	}
   141  	if err := efd.write(ctx, src); err != nil {
   142  		return 0, err
   143  	}
   144  	return 8, nil
   145  }
   146  
   147  // Preconditions: Must be called with efd.mu locked.
   148  func (efd *EventFileDescription) hostReadLocked(ctx context.Context, dst usermem.IOSequence) error {
   149  	var buf [8]byte
   150  	if _, err := unix.Read(efd.hostfd, buf[:]); err != nil {
   151  		if err == unix.EWOULDBLOCK {
   152  			return syserror.ErrWouldBlock
   153  		}
   154  		return err
   155  	}
   156  	_, err := dst.CopyOut(ctx, buf[:])
   157  	return err
   158  }
   159  
   160  func (efd *EventFileDescription) read(ctx context.Context, dst usermem.IOSequence) error {
   161  	efd.mu.Lock()
   162  	if efd.hostfd >= 0 {
   163  		defer efd.mu.Unlock()
   164  		return efd.hostReadLocked(ctx, dst)
   165  	}
   166  
   167  	// We can't complete the read if the value is currently zero.
   168  	if efd.val == 0 {
   169  		efd.mu.Unlock()
   170  		return syserror.ErrWouldBlock
   171  	}
   172  
   173  	// Update the value based on the mode the event is operating in.
   174  	var val uint64
   175  	if efd.semMode {
   176  		val = 1
   177  		// Consistent with Linux, this is done even if writing to memory fails.
   178  		efd.val--
   179  	} else {
   180  		val = efd.val
   181  		efd.val = 0
   182  	}
   183  
   184  	efd.mu.Unlock()
   185  
   186  	// Notify writers. We do this even if we were already writable because
   187  	// it is possible that a writer is waiting to write the maximum value
   188  	// to the event.
   189  	efd.queue.Notify(waiter.WritableEvents)
   190  
   191  	var buf [8]byte
   192  	hostarch.ByteOrder.PutUint64(buf[:], val)
   193  	_, err := dst.CopyOut(ctx, buf[:])
   194  	return err
   195  }
   196  
   197  // Preconditions: Must be called with efd.mu locked.
   198  func (efd *EventFileDescription) hostWriteLocked(val uint64) error {
   199  	var buf [8]byte
   200  	hostarch.ByteOrder.PutUint64(buf[:], val)
   201  	_, err := unix.Write(efd.hostfd, buf[:])
   202  	if err == unix.EWOULDBLOCK {
   203  		return syserror.ErrWouldBlock
   204  	}
   205  	return err
   206  }
   207  
   208  func (efd *EventFileDescription) write(ctx context.Context, src usermem.IOSequence) error {
   209  	var buf [8]byte
   210  	if _, err := src.CopyIn(ctx, buf[:]); err != nil {
   211  		return err
   212  	}
   213  	val := hostarch.ByteOrder.Uint64(buf[:])
   214  
   215  	return efd.Signal(val)
   216  }
   217  
   218  // Signal is an internal function to signal the event fd.
   219  func (efd *EventFileDescription) Signal(val uint64) error {
   220  	if val == math.MaxUint64 {
   221  		return unix.EINVAL
   222  	}
   223  
   224  	efd.mu.Lock()
   225  
   226  	if efd.hostfd >= 0 {
   227  		defer efd.mu.Unlock()
   228  		return efd.hostWriteLocked(val)
   229  	}
   230  
   231  	// We only allow writes that won't cause the value to go over the max
   232  	// uint64 minus 1.
   233  	if val > math.MaxUint64-1-efd.val {
   234  		efd.mu.Unlock()
   235  		return syserror.ErrWouldBlock
   236  	}
   237  
   238  	efd.val += val
   239  	efd.mu.Unlock()
   240  
   241  	// Always trigger a notification.
   242  	efd.queue.Notify(waiter.ReadableEvents)
   243  
   244  	return nil
   245  }
   246  
   247  // Readiness implements waiter.Waitable.Readiness.
   248  func (efd *EventFileDescription) Readiness(mask waiter.EventMask) waiter.EventMask {
   249  	efd.mu.Lock()
   250  	defer efd.mu.Unlock()
   251  
   252  	if efd.hostfd >= 0 {
   253  		return fdnotifier.NonBlockingPoll(int32(efd.hostfd), mask)
   254  	}
   255  
   256  	ready := waiter.EventMask(0)
   257  	if efd.val > 0 {
   258  		ready |= waiter.ReadableEvents
   259  	}
   260  
   261  	if efd.val < math.MaxUint64-1 {
   262  		ready |= waiter.WritableEvents
   263  	}
   264  
   265  	return mask & ready
   266  }
   267  
   268  // EventRegister implements waiter.Waitable.EventRegister.
   269  func (efd *EventFileDescription) EventRegister(entry *waiter.Entry, mask waiter.EventMask) {
   270  	efd.queue.EventRegister(entry, mask)
   271  
   272  	efd.mu.Lock()
   273  	defer efd.mu.Unlock()
   274  	if efd.hostfd >= 0 {
   275  		fdnotifier.UpdateFD(int32(efd.hostfd))
   276  	}
   277  }
   278  
   279  // EventUnregister implements waiter.Waitable.EventUnregister.
   280  func (efd *EventFileDescription) EventUnregister(entry *waiter.Entry) {
   281  	efd.queue.EventUnregister(entry)
   282  
   283  	efd.mu.Lock()
   284  	defer efd.mu.Unlock()
   285  	if efd.hostfd >= 0 {
   286  		fdnotifier.UpdateFD(int32(efd.hostfd))
   287  	}
   288  }