github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fs/inode_inotify.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 fs
    16  
    17  import (
    18  	"fmt"
    19  
    20  	"github.com/SagerNet/gvisor/pkg/context"
    21  	"github.com/SagerNet/gvisor/pkg/sync"
    22  )
    23  
    24  // Watches is the collection of inotify watches on an inode.
    25  //
    26  // +stateify savable
    27  type Watches struct {
    28  	// mu protects the fields below.
    29  	mu sync.RWMutex `state:"nosave"`
    30  
    31  	// ws is the map of active watches in this collection, keyed by the inotify
    32  	// instance id of the owner.
    33  	ws map[uint64]*Watch
    34  
    35  	// unlinked indicates whether the target inode was ever unlinked. This is a
    36  	// hack to figure out if we should queue a IN_DELETE_SELF event when this
    37  	// watches collection is being destroyed, since otherwise we have no way of
    38  	// knowing if the target inode is going down due to a deletion or
    39  	// revalidation.
    40  	unlinked bool
    41  }
    42  
    43  func newWatches() *Watches {
    44  	return &Watches{}
    45  }
    46  
    47  // MarkUnlinked indicates the target for this set of watches to be unlinked.
    48  // This has implications for the IN_EXCL_UNLINK flag.
    49  func (w *Watches) MarkUnlinked() {
    50  	w.mu.Lock()
    51  	defer w.mu.Unlock()
    52  	w.unlinked = true
    53  }
    54  
    55  // Lookup returns a matching watch with the given id. Returns nil if no such
    56  // watch exists. Note that the result returned by this method only remains valid
    57  // if the inotify instance owning the watch is locked, preventing modification
    58  // of the returned watch and preventing the replacement of the watch by another
    59  // one from the same instance (since there may be at most one watch per
    60  // instance, per target).
    61  func (w *Watches) Lookup(id uint64) *Watch {
    62  	w.mu.Lock()
    63  	defer w.mu.Unlock()
    64  	return w.ws[id]
    65  }
    66  
    67  // Add adds watch into this set of watches. The watch being added must be unique
    68  // - its ID() should not collide with any existing watches.
    69  func (w *Watches) Add(watch *Watch) {
    70  	w.mu.Lock()
    71  	defer w.mu.Unlock()
    72  
    73  	// Sanity check, the new watch shouldn't collide with an existing
    74  	// watch. Silently replacing an existing watch would result in a ref leak on
    75  	// this inode. We could handle this collision by calling Unpin() on the
    76  	// existing watch, but then we end up leaking watch descriptor ids at the
    77  	// inotify level.
    78  	if _, exists := w.ws[watch.ID()]; exists {
    79  		panic(fmt.Sprintf("Watch collision with ID %+v", watch.ID()))
    80  	}
    81  	if w.ws == nil {
    82  		w.ws = make(map[uint64]*Watch)
    83  	}
    84  	w.ws[watch.ID()] = watch
    85  }
    86  
    87  // Remove removes a watch with the given id from this set of watches. The caller
    88  // is responsible for generating any watch removal event, as appropriate. The
    89  // provided id must match an existing watch in this collection.
    90  func (w *Watches) Remove(id uint64) {
    91  	w.mu.Lock()
    92  	defer w.mu.Unlock()
    93  
    94  	if w.ws == nil {
    95  		// This watch set is being destroyed. The thread executing the
    96  		// destructor is already in the process of deleting all our watches. We
    97  		// got here with no refs on the inode because we raced with the
    98  		// destructor notifying all the watch owners of the inode's destruction.
    99  		// See the comment in Watches.TargetDestroyed for why this race exists.
   100  		return
   101  	}
   102  
   103  	watch, ok := w.ws[id]
   104  	if !ok {
   105  		// While there's technically no problem with silently ignoring a missing
   106  		// watch, this is almost certainly a bug.
   107  		panic(fmt.Sprintf("Attempt to remove a watch, but no watch found with provided id %+v.", id))
   108  	}
   109  	delete(w.ws, watch.ID())
   110  }
   111  
   112  // Notify queues a new event with all watches in this set.
   113  func (w *Watches) Notify(name string, events, cookie uint32) {
   114  	// N.B. We don't defer the unlocks because Notify is in the hot path of
   115  	// all IO operations, and the defer costs too much for small IO
   116  	// operations.
   117  	w.mu.RLock()
   118  	for _, watch := range w.ws {
   119  		if name != "" && w.unlinked && !watch.NotifyParentAfterUnlink() {
   120  			// IN_EXCL_UNLINK - By default, when watching events on the children
   121  			// of a directory, events are generated for children even after they
   122  			// have been unlinked from the directory. This can result in large
   123  			// numbers of uninteresting events for some applications (e.g., if
   124  			// watching /tmp, in which many applications create temporary files
   125  			// whose names are immediately unlinked). Specifying IN_EXCL_UNLINK
   126  			// changes the default behavior, so that events are not generated
   127  			// for children after they have been unlinked from the watched
   128  			// directory.  -- inotify(7)
   129  			//
   130  			// We know we're dealing with events for a parent when the name
   131  			// isn't empty.
   132  			continue
   133  		}
   134  		watch.Notify(name, events, cookie)
   135  	}
   136  	w.mu.RUnlock()
   137  }
   138  
   139  // Unpin unpins dirent from all watches in this set.
   140  func (w *Watches) Unpin(ctx context.Context, d *Dirent) {
   141  	w.mu.RLock()
   142  	defer w.mu.RUnlock()
   143  	for _, watch := range w.ws {
   144  		watch.Unpin(ctx, d)
   145  	}
   146  }
   147  
   148  // targetDestroyed is called by the inode destructor to notify the watch owners
   149  // of the impending destruction of the watch target.
   150  func (w *Watches) targetDestroyed() {
   151  	var ws map[uint64]*Watch
   152  
   153  	// We can't hold w.mu while calling watch.TargetDestroyed to preserve lock
   154  	// ordering w.r.t to the owner inotify instances. Instead, atomically move
   155  	// the watches map into a local variable so we can iterate over it safely.
   156  	//
   157  	// Because of this however, it is possible for the watches' owners to reach
   158  	// this inode while the inode has no refs. This is still safe because the
   159  	// owners can only reach the inode until this function finishes calling
   160  	// watch.TargetDestroyed() below and the inode is guaranteed to exist in the
   161  	// meanwhile. But we still have to be very careful not to rely on inode
   162  	// state that may have been already destroyed.
   163  	w.mu.Lock()
   164  	ws = w.ws
   165  	w.ws = nil
   166  	w.mu.Unlock()
   167  
   168  	for _, watch := range ws {
   169  		watch.TargetDestroyed()
   170  	}
   171  }