gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/sentry/fsimpl/overlay/directory.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 overlay
    16  
    17  import (
    18  	"gvisor.dev/gvisor/pkg/abi/linux"
    19  	"gvisor.dev/gvisor/pkg/context"
    20  	"gvisor.dev/gvisor/pkg/errors/linuxerr"
    21  	"gvisor.dev/gvisor/pkg/fspath"
    22  	"gvisor.dev/gvisor/pkg/sentry/vfs"
    23  )
    24  
    25  func (d *dentry) isDir() bool {
    26  	return d.mode.Load()&linux.S_IFMT == linux.S_IFDIR
    27  }
    28  
    29  // Preconditions:
    30  //   - d.dirMu must be locked.
    31  //   - d.isDir().
    32  func (d *dentry) collectWhiteoutsForRmdirLocked(ctx context.Context) (map[string]bool, error) {
    33  	vfsObj := d.fs.vfsfs.VirtualFilesystem()
    34  	var readdirErr error
    35  	whiteouts := make(map[string]bool)
    36  	var maybeWhiteouts []string
    37  	d.iterLayers(func(layerVD vfs.VirtualDentry, isUpper bool) bool {
    38  		layerFD, err := vfsObj.OpenAt(ctx, d.fs.creds, &vfs.PathOperation{
    39  			Root:  layerVD,
    40  			Start: layerVD,
    41  		}, &vfs.OpenOptions{
    42  			Flags: linux.O_RDONLY | linux.O_DIRECTORY,
    43  		})
    44  		if err != nil {
    45  			readdirErr = err
    46  			return false
    47  		}
    48  		defer layerFD.DecRef(ctx)
    49  
    50  		// Reuse slice allocated for maybeWhiteouts from a previous layer to
    51  		// reduce allocations.
    52  		maybeWhiteouts = maybeWhiteouts[:0]
    53  		err = layerFD.IterDirents(ctx, vfs.IterDirentsCallbackFunc(func(dirent vfs.Dirent) error {
    54  			if dirent.Name == "." || dirent.Name == ".." {
    55  				return nil
    56  			}
    57  			if _, ok := whiteouts[dirent.Name]; ok {
    58  				// This file has been whited-out in a previous layer.
    59  				return nil
    60  			}
    61  			if dirent.Type == linux.DT_CHR {
    62  				// We have to determine if this is a whiteout, which doesn't
    63  				// count against the directory's emptiness. However, we can't
    64  				// do so while holding locks held by layerFD.IterDirents().
    65  				maybeWhiteouts = append(maybeWhiteouts, dirent.Name)
    66  				return nil
    67  			}
    68  			// Non-whiteout file in the directory prevents rmdir.
    69  			return linuxerr.ENOTEMPTY
    70  		}))
    71  		if err != nil {
    72  			readdirErr = err
    73  			return false
    74  		}
    75  
    76  		for _, maybeWhiteoutName := range maybeWhiteouts {
    77  			stat, err := vfsObj.StatAt(ctx, d.fs.creds, &vfs.PathOperation{
    78  				Root:  layerVD,
    79  				Start: layerVD,
    80  				Path:  fspath.Parse(maybeWhiteoutName),
    81  			}, &vfs.StatOptions{})
    82  			if err != nil {
    83  				readdirErr = err
    84  				return false
    85  			}
    86  			if stat.RdevMajor != 0 || stat.RdevMinor != 0 {
    87  				// This file is a real character device, not a whiteout.
    88  				readdirErr = linuxerr.ENOTEMPTY
    89  				return false
    90  			}
    91  			whiteouts[maybeWhiteoutName] = isUpper
    92  		}
    93  		// Continue iteration since we haven't found any non-whiteout files in
    94  		// this directory yet.
    95  		return true
    96  	})
    97  	return whiteouts, readdirErr
    98  }
    99  
   100  // +stateify savable
   101  type directoryFD struct {
   102  	fileDescription
   103  	vfs.DirectoryFileDescriptionDefaultImpl
   104  	vfs.DentryMetadataFileDescriptionImpl
   105  
   106  	mu      directoryFDMutex `state:"nosave"`
   107  	off     int64
   108  	dirents []vfs.Dirent
   109  }
   110  
   111  // Release implements vfs.FileDescriptionImpl.Release.
   112  func (fd *directoryFD) Release(ctx context.Context) {
   113  }
   114  
   115  // IterDirents implements vfs.FileDescriptionImpl.IterDirents.
   116  func (fd *directoryFD) IterDirents(ctx context.Context, cb vfs.IterDirentsCallback) error {
   117  	d := fd.dentry()
   118  	fd.mu.Lock()
   119  	defer fd.mu.Unlock()
   120  
   121  	if fd.dirents == nil {
   122  		ds, err := d.getDirents(ctx)
   123  		if err != nil {
   124  			return err
   125  		}
   126  		fd.dirents = ds
   127  	}
   128  
   129  	for fd.off < int64(len(fd.dirents)) {
   130  		if err := cb.Handle(fd.dirents[fd.off]); err != nil {
   131  			return err
   132  		}
   133  		fd.off++
   134  	}
   135  	return nil
   136  }
   137  
   138  // Preconditions: d.isDir().
   139  func (d *dentry) getDirents(ctx context.Context) ([]vfs.Dirent, error) {
   140  	d.fs.renameMu.RLock()
   141  	defer d.fs.renameMu.RUnlock()
   142  	d.dirMu.Lock()
   143  	defer d.dirMu.Unlock()
   144  	return d.getDirentsLocked(ctx)
   145  }
   146  
   147  // Preconditions:
   148  //   - filesystem.renameMu must be locked.
   149  //   - d.dirMu must be locked.
   150  //   - d.isDir().
   151  func (d *dentry) getDirentsLocked(ctx context.Context) ([]vfs.Dirent, error) {
   152  	if d.dirents != nil {
   153  		return d.dirents, nil
   154  	}
   155  
   156  	parent := genericParentOrSelf(d)
   157  	dirents := []vfs.Dirent{
   158  		{
   159  			Name:    ".",
   160  			Type:    linux.DT_DIR,
   161  			Ino:     d.ino.Load(),
   162  			NextOff: 1,
   163  		},
   164  		{
   165  			Name:    "..",
   166  			Type:    uint8(parent.mode.Load() >> 12),
   167  			Ino:     parent.ino.Load(),
   168  			NextOff: 2,
   169  		},
   170  	}
   171  
   172  	// Merge dirents from all layers comprising this directory.
   173  	vfsObj := d.fs.vfsfs.VirtualFilesystem()
   174  	var readdirErr error
   175  	prevDirents := make(map[string]struct{})
   176  	var maybeWhiteouts []vfs.Dirent
   177  	d.iterLayers(func(layerVD vfs.VirtualDentry, isUpper bool) bool {
   178  		layerFD, err := vfsObj.OpenAt(ctx, d.fs.creds, &vfs.PathOperation{
   179  			Root:  layerVD,
   180  			Start: layerVD,
   181  		}, &vfs.OpenOptions{
   182  			Flags: linux.O_RDONLY | linux.O_DIRECTORY,
   183  		})
   184  		if err != nil {
   185  			readdirErr = err
   186  			return false
   187  		}
   188  		defer layerFD.DecRef(ctx)
   189  
   190  		// Reuse slice allocated for maybeWhiteouts from a previous layer to
   191  		// reduce allocations.
   192  		maybeWhiteouts = maybeWhiteouts[:0]
   193  		err = layerFD.IterDirents(ctx, vfs.IterDirentsCallbackFunc(func(dirent vfs.Dirent) error {
   194  			if dirent.Name == "." || dirent.Name == ".." {
   195  				return nil
   196  			}
   197  			if _, ok := prevDirents[dirent.Name]; ok {
   198  				// This file is hidden by, or merged with, another file with
   199  				// the same name in a previous layer.
   200  				return nil
   201  			}
   202  			prevDirents[dirent.Name] = struct{}{}
   203  			if dirent.Type == linux.DT_CHR {
   204  				// We can't determine if this file is a whiteout while holding
   205  				// locks held by layerFD.IterDirents().
   206  				maybeWhiteouts = append(maybeWhiteouts, dirent)
   207  				return nil
   208  			}
   209  			dirent.NextOff = int64(len(dirents) + 1)
   210  			dirents = append(dirents, dirent)
   211  			return nil
   212  		}))
   213  		if err != nil {
   214  			readdirErr = err
   215  			return false
   216  		}
   217  
   218  		for _, dirent := range maybeWhiteouts {
   219  			stat, err := vfsObj.StatAt(ctx, d.fs.creds, &vfs.PathOperation{
   220  				Root:  layerVD,
   221  				Start: layerVD,
   222  				Path:  fspath.Parse(dirent.Name),
   223  			}, &vfs.StatOptions{})
   224  			if err != nil {
   225  				readdirErr = err
   226  				return false
   227  			}
   228  			if stat.RdevMajor == 0 && stat.RdevMinor == 0 {
   229  				// This file is a whiteout; don't emit a dirent for it.
   230  				continue
   231  			}
   232  			dirent.NextOff = int64(len(dirents) + 1)
   233  			dirents = append(dirents, dirent)
   234  		}
   235  		return true
   236  	})
   237  	if readdirErr != nil {
   238  		return nil, readdirErr
   239  	}
   240  
   241  	// Cache dirents for future directoryFDs.
   242  	d.dirents = dirents
   243  	return dirents, nil
   244  }
   245  
   246  // Seek implements vfs.FileDescriptionImpl.Seek.
   247  func (fd *directoryFD) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
   248  	fd.mu.Lock()
   249  	defer fd.mu.Unlock()
   250  
   251  	switch whence {
   252  	case linux.SEEK_SET:
   253  		if offset < 0 {
   254  			return 0, linuxerr.EINVAL
   255  		}
   256  		if offset == 0 {
   257  			// Ensure that the next call to fd.IterDirents() calls
   258  			// fd.dentry().getDirents().
   259  			fd.dirents = nil
   260  		}
   261  		fd.off = offset
   262  		return fd.off, nil
   263  	case linux.SEEK_CUR:
   264  		offset += fd.off
   265  		if offset < 0 {
   266  			return 0, linuxerr.EINVAL
   267  		}
   268  		// Don't clear fd.dirents in this case, even if offset == 0.
   269  		fd.off = offset
   270  		return fd.off, nil
   271  	default:
   272  		return 0, linuxerr.EINVAL
   273  	}
   274  }
   275  
   276  // Sync implements vfs.FileDescriptionImpl.Sync. Forwards sync to the upper
   277  // layer, if there is one. The lower layer doesn't need to sync because it
   278  // never changes.
   279  func (fd *directoryFD) Sync(ctx context.Context) error {
   280  	d := fd.dentry()
   281  	if !d.isCopiedUp() {
   282  		return nil
   283  	}
   284  	vfsObj := d.fs.vfsfs.VirtualFilesystem()
   285  	pop := vfs.PathOperation{
   286  		Root:  d.upperVD,
   287  		Start: d.upperVD,
   288  	}
   289  	upperFD, err := vfsObj.OpenAt(ctx, d.fs.creds, &pop, &vfs.OpenOptions{Flags: linux.O_RDONLY | linux.O_DIRECTORY})
   290  	if err != nil {
   291  		return err
   292  	}
   293  	err = upperFD.Sync(ctx)
   294  	upperFD.DecRef(ctx)
   295  	return err
   296  }