github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fsimpl/tmpfs/directory.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 tmpfs
    16  
    17  import (
    18  	"sync/atomic"
    19  
    20  	"github.com/SagerNet/gvisor/pkg/abi/linux"
    21  	"github.com/SagerNet/gvisor/pkg/context"
    22  	"github.com/SagerNet/gvisor/pkg/errors/linuxerr"
    23  	"github.com/SagerNet/gvisor/pkg/sentry/kernel/auth"
    24  	"github.com/SagerNet/gvisor/pkg/sentry/vfs"
    25  	"github.com/SagerNet/gvisor/pkg/sync"
    26  )
    27  
    28  // +stateify savable
    29  type directory struct {
    30  	// Since directories can't be hard-linked, each directory can only be
    31  	// associated with a single dentry, which we can store in the directory
    32  	// struct.
    33  	dentry dentry
    34  	inode  inode
    35  
    36  	// childMap maps the names of the directory's children to their dentries.
    37  	// childMap is protected by filesystem.mu.
    38  	childMap map[string]*dentry
    39  
    40  	// numChildren is len(childMap), but accessed using atomic memory
    41  	// operations to avoid locking in inode.statTo().
    42  	numChildren int64
    43  
    44  	// childList is a list containing (1) child dentries and (2) fake dentries
    45  	// (with inode == nil) that represent the iteration position of
    46  	// directoryFDs. childList is used to support directoryFD.IterDirents()
    47  	// efficiently. childList is protected by iterMu.
    48  	iterMu    sync.Mutex `state:"nosave"`
    49  	childList dentryList
    50  }
    51  
    52  func (fs *filesystem) newDirectory(kuid auth.KUID, kgid auth.KGID, mode linux.FileMode, parentDir *directory) *directory {
    53  	dir := &directory{}
    54  	dir.inode.init(dir, fs, kuid, kgid, linux.S_IFDIR|mode, parentDir)
    55  	dir.inode.nlink = 2 // from "." and parent directory or ".." for root
    56  	dir.dentry.inode = &dir.inode
    57  	dir.dentry.vfsd.Init(&dir.dentry)
    58  	return dir
    59  }
    60  
    61  // Preconditions:
    62  // * filesystem.mu must be locked for writing.
    63  // * dir must not already contain a child with the given name.
    64  func (dir *directory) insertChildLocked(child *dentry, name string) {
    65  	child.parent = &dir.dentry
    66  	child.name = name
    67  	if dir.childMap == nil {
    68  		dir.childMap = make(map[string]*dentry)
    69  	}
    70  	dir.childMap[name] = child
    71  	atomic.AddInt64(&dir.numChildren, 1)
    72  	dir.iterMu.Lock()
    73  	dir.childList.PushBack(child)
    74  	dir.iterMu.Unlock()
    75  }
    76  
    77  // Preconditions: filesystem.mu must be locked for writing.
    78  func (dir *directory) removeChildLocked(child *dentry) {
    79  	delete(dir.childMap, child.name)
    80  	atomic.AddInt64(&dir.numChildren, -1)
    81  	dir.iterMu.Lock()
    82  	dir.childList.Remove(child)
    83  	dir.iterMu.Unlock()
    84  }
    85  
    86  func (dir *directory) mayDelete(creds *auth.Credentials, child *dentry) error {
    87  	return vfs.CheckDeleteSticky(
    88  		creds,
    89  		linux.FileMode(atomic.LoadUint32(&dir.inode.mode)),
    90  		auth.KUID(atomic.LoadUint32(&dir.inode.uid)),
    91  		auth.KUID(atomic.LoadUint32(&child.inode.uid)),
    92  		auth.KGID(atomic.LoadUint32(&child.inode.gid)),
    93  	)
    94  }
    95  
    96  // +stateify savable
    97  type directoryFD struct {
    98  	fileDescription
    99  	vfs.DirectoryFileDescriptionDefaultImpl
   100  
   101  	// Protected by directory.iterMu.
   102  	iter *dentry
   103  	off  int64
   104  }
   105  
   106  // Release implements vfs.FileDescriptionImpl.Release.
   107  func (fd *directoryFD) Release(ctx context.Context) {
   108  	if fd.iter != nil {
   109  		dir := fd.inode().impl.(*directory)
   110  		dir.iterMu.Lock()
   111  		dir.childList.Remove(fd.iter)
   112  		dir.iterMu.Unlock()
   113  		fd.iter = nil
   114  	}
   115  }
   116  
   117  // IterDirents implements vfs.FileDescriptionImpl.IterDirents.
   118  func (fd *directoryFD) IterDirents(ctx context.Context, cb vfs.IterDirentsCallback) error {
   119  	fs := fd.filesystem()
   120  	dir := fd.inode().impl.(*directory)
   121  
   122  	defer fd.dentry().InotifyWithParent(ctx, linux.IN_ACCESS, 0, vfs.PathEvent)
   123  
   124  	// fs.mu is required to read d.parent and dentry.name.
   125  	fs.mu.RLock()
   126  	defer fs.mu.RUnlock()
   127  	dir.iterMu.Lock()
   128  	defer dir.iterMu.Unlock()
   129  
   130  	fd.inode().touchAtime(fd.vfsfd.Mount())
   131  
   132  	if fd.off == 0 {
   133  		if err := cb.Handle(vfs.Dirent{
   134  			Name:    ".",
   135  			Type:    linux.DT_DIR,
   136  			Ino:     dir.inode.ino,
   137  			NextOff: 1,
   138  		}); err != nil {
   139  			return err
   140  		}
   141  		fd.off++
   142  	}
   143  
   144  	if fd.off == 1 {
   145  		parentInode := genericParentOrSelf(&dir.dentry).inode
   146  		if err := cb.Handle(vfs.Dirent{
   147  			Name:    "..",
   148  			Type:    parentInode.direntType(),
   149  			Ino:     parentInode.ino,
   150  			NextOff: 2,
   151  		}); err != nil {
   152  			return err
   153  		}
   154  		fd.off++
   155  	}
   156  
   157  	var child *dentry
   158  	if fd.iter == nil {
   159  		// Start iteration at the beginning of dir.
   160  		child = dir.childList.Front()
   161  		fd.iter = &dentry{}
   162  	} else {
   163  		// Continue iteration from where we left off.
   164  		child = fd.iter.Next()
   165  		dir.childList.Remove(fd.iter)
   166  	}
   167  	for child != nil {
   168  		// Skip other directoryFD iterators.
   169  		if child.inode != nil {
   170  			if err := cb.Handle(vfs.Dirent{
   171  				Name:    child.name,
   172  				Type:    child.inode.direntType(),
   173  				Ino:     child.inode.ino,
   174  				NextOff: fd.off + 1,
   175  			}); err != nil {
   176  				dir.childList.InsertBefore(child, fd.iter)
   177  				return err
   178  			}
   179  			fd.off++
   180  		}
   181  		child = child.Next()
   182  	}
   183  	dir.childList.PushBack(fd.iter)
   184  	return nil
   185  }
   186  
   187  // Seek implements vfs.FileDescriptionImpl.Seek.
   188  func (fd *directoryFD) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
   189  	dir := fd.inode().impl.(*directory)
   190  	dir.iterMu.Lock()
   191  	defer dir.iterMu.Unlock()
   192  
   193  	switch whence {
   194  	case linux.SEEK_SET:
   195  		// Use offset as given.
   196  	case linux.SEEK_CUR:
   197  		offset += fd.off
   198  	default:
   199  		return 0, linuxerr.EINVAL
   200  	}
   201  	if offset < 0 {
   202  		return 0, linuxerr.EINVAL
   203  	}
   204  
   205  	// If the offset isn't changing (e.g. due to lseek(0, SEEK_CUR)), don't
   206  	// seek even if doing so might reposition the iterator due to concurrent
   207  	// mutation of the directory. Compare fs/libfs.c:dcache_dir_lseek().
   208  	if fd.off == offset {
   209  		return offset, nil
   210  	}
   211  
   212  	fd.off = offset
   213  	// Compensate for "." and "..".
   214  	remChildren := int64(0)
   215  	if offset >= 2 {
   216  		remChildren = offset - 2
   217  	}
   218  
   219  	// Ensure that fd.iter exists and is not linked into dir.childList.
   220  	if fd.iter == nil {
   221  		fd.iter = &dentry{}
   222  	} else {
   223  		dir.childList.Remove(fd.iter)
   224  	}
   225  	// Insert fd.iter before the remChildren'th child, or at the end of the
   226  	// list if remChildren >= number of children.
   227  	child := dir.childList.Front()
   228  	for child != nil {
   229  		// Skip other directoryFD iterators.
   230  		if child.inode != nil {
   231  			if remChildren == 0 {
   232  				dir.childList.InsertBefore(child, fd.iter)
   233  				return offset, nil
   234  			}
   235  			remChildren--
   236  		}
   237  		child = child.Next()
   238  	}
   239  	dir.childList.PushBack(fd.iter)
   240  	return offset, nil
   241  }