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 }