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