github.com/metacubex/gvisor@v0.0.0-20240320004321-933faba989ec/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 "github.com/metacubex/gvisor/pkg/abi/linux" 19 "github.com/metacubex/gvisor/pkg/context" 20 "github.com/metacubex/gvisor/pkg/errors/linuxerr" 21 "github.com/metacubex/gvisor/pkg/fspath" 22 "github.com/metacubex/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 }