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