gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/sentry/fsimpl/kernfs/kernfs_test.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 kernfs_test 16 17 import ( 18 "bytes" 19 "fmt" 20 "testing" 21 22 "github.com/google/go-cmp/cmp" 23 "gvisor.dev/gvisor/pkg/abi/linux" 24 "gvisor.dev/gvisor/pkg/context" 25 "gvisor.dev/gvisor/pkg/errors/linuxerr" 26 "gvisor.dev/gvisor/pkg/fspath" 27 "gvisor.dev/gvisor/pkg/sentry/contexttest" 28 "gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs" 29 "gvisor.dev/gvisor/pkg/sentry/fsimpl/testutil" 30 "gvisor.dev/gvisor/pkg/sentry/kernel/auth" 31 "gvisor.dev/gvisor/pkg/sentry/vfs" 32 "gvisor.dev/gvisor/pkg/usermem" 33 ) 34 35 const defaultMode linux.FileMode = 01777 36 const staticFileContent = "This is sample content for a static test file." 37 38 // RootDentryFn is a generator function for creating the root dentry of a test 39 // filesystem. See newTestSystem. 40 type RootDentryFn func(context.Context, *auth.Credentials, *filesystem) kernfs.Inode 41 42 // newTestSystem sets up a minimal environment for running a test, including an 43 // instance of a test filesystem. Tests can control the contents of the 44 // filesystem by providing an appropriate rootFn, which should return a 45 // pre-populated root dentry. 46 func newTestSystem(t *testing.T, rootFn RootDentryFn) *testutil.System { 47 ctx := contexttest.Context(t) 48 creds := auth.CredentialsFromContext(ctx) 49 v := &vfs.VirtualFilesystem{} 50 if err := v.Init(ctx); err != nil { 51 t.Fatalf("VFS init: %v", err) 52 } 53 v.MustRegisterFilesystemType("testfs", &fsType{rootFn: rootFn}, &vfs.RegisterFilesystemTypeOptions{ 54 AllowUserMount: true, 55 }) 56 mns, err := v.NewMountNamespace(ctx, creds, "", "testfs", &vfs.MountOptions{}, nil) 57 if err != nil { 58 t.Fatalf("Failed to create testfs root mount: %v", err) 59 } 60 return testutil.NewSystem(ctx, t, v, mns) 61 } 62 63 type fsType struct { 64 rootFn RootDentryFn 65 } 66 67 type filesystem struct { 68 kernfs.Filesystem 69 } 70 71 // MountOptions implements vfs.FilesystemImpl.MountOptions. 72 func (fs *filesystem) MountOptions() string { 73 return "" 74 } 75 76 type file struct { 77 kernfs.DynamicBytesFile 78 content string 79 } 80 81 func (fs *filesystem) newFile(ctx context.Context, creds *auth.Credentials, content string) kernfs.Inode { 82 f := &file{} 83 f.content = content 84 f.DynamicBytesFile.Init(ctx, creds, 0 /* devMajor */, 0 /* devMinor */, fs.NextIno(), f, 0777) 85 return f 86 } 87 88 func (f *file) Generate(ctx context.Context, buf *bytes.Buffer) error { 89 fmt.Fprintf(buf, "%s", f.content) 90 return nil 91 } 92 93 type attrs struct { 94 kernfs.InodeAttrs 95 } 96 97 func (*attrs) SetStat(context.Context, *vfs.Filesystem, *auth.Credentials, vfs.SetStatOptions) error { 98 return linuxerr.EPERM 99 } 100 101 type readonlyDir struct { 102 readonlyDirRefs 103 attrs 104 kernfs.InodeAlwaysValid 105 kernfs.InodeDirectoryNoNewChildren 106 kernfs.InodeNoStatFS 107 kernfs.InodeNotAnonymous 108 kernfs.InodeNotSymlink 109 kernfs.InodeTemporary 110 kernfs.InodeWatches 111 kernfs.OrderedChildren 112 113 locks vfs.FileLocks 114 } 115 116 func (fs *filesystem) newReadonlyDir(ctx context.Context, creds *auth.Credentials, mode linux.FileMode, contents map[string]kernfs.Inode) kernfs.Inode { 117 dir := &readonlyDir{} 118 dir.attrs.Init(ctx, creds, 0 /* devMajor */, 0 /* devMinor */, fs.NextIno(), linux.ModeDirectory|mode) 119 dir.OrderedChildren.Init(kernfs.OrderedChildrenOptions{}) 120 dir.InitRefs() 121 dir.IncLinks(dir.OrderedChildren.Populate(contents)) 122 return dir 123 } 124 125 func (d *readonlyDir) Open(ctx context.Context, rp *vfs.ResolvingPath, kd *kernfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) { 126 fd, err := kernfs.NewGenericDirectoryFD(rp.Mount(), kd, &d.OrderedChildren, &d.locks, &opts, kernfs.GenericDirectoryFDOptions{ 127 SeekEnd: kernfs.SeekEndStaticEntries, 128 }) 129 if err != nil { 130 return nil, err 131 } 132 return fd.VFSFileDescription(), nil 133 } 134 135 func (d *readonlyDir) DecRef(ctx context.Context) { 136 d.readonlyDirRefs.DecRef(func() { d.Destroy(ctx) }) 137 } 138 139 type dir struct { 140 dirRefs 141 attrs 142 kernfs.InodeAlwaysValid 143 kernfs.InodeNoStatFS 144 kernfs.InodeNotAnonymous 145 kernfs.InodeNotSymlink 146 kernfs.InodeTemporary 147 kernfs.InodeWatches 148 kernfs.OrderedChildren 149 150 locks vfs.FileLocks 151 152 fs *filesystem 153 } 154 155 func (fs *filesystem) newDir(ctx context.Context, creds *auth.Credentials, mode linux.FileMode, contents map[string]kernfs.Inode) kernfs.Inode { 156 dir := &dir{} 157 dir.fs = fs 158 dir.attrs.Init(ctx, creds, 0 /* devMajor */, 0 /* devMinor */, fs.NextIno(), linux.ModeDirectory|mode) 159 dir.OrderedChildren.Init(kernfs.OrderedChildrenOptions{Writable: true}) 160 dir.InitRefs() 161 162 dir.IncLinks(dir.OrderedChildren.Populate(contents)) 163 return dir 164 } 165 166 func (d *dir) Open(ctx context.Context, rp *vfs.ResolvingPath, kd *kernfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) { 167 fd, err := kernfs.NewGenericDirectoryFD(rp.Mount(), kd, &d.OrderedChildren, &d.locks, &opts, kernfs.GenericDirectoryFDOptions{ 168 SeekEnd: kernfs.SeekEndStaticEntries, 169 }) 170 if err != nil { 171 return nil, err 172 } 173 return fd.VFSFileDescription(), nil 174 } 175 176 func (d *dir) DecRef(ctx context.Context) { 177 d.dirRefs.DecRef(func() { d.Destroy(ctx) }) 178 } 179 180 func (d *dir) NewDir(ctx context.Context, name string, opts vfs.MkdirOptions) (kernfs.Inode, error) { 181 creds := auth.CredentialsFromContext(ctx) 182 dir := d.fs.newDir(ctx, creds, opts.Mode, nil) 183 if err := d.OrderedChildren.Insert(name, dir); err != nil { 184 dir.DecRef(ctx) 185 return nil, err 186 } 187 d.TouchCMtime(ctx) 188 d.IncLinks(1) 189 return dir, nil 190 } 191 192 func (d *dir) NewFile(ctx context.Context, name string, opts vfs.OpenOptions) (kernfs.Inode, error) { 193 creds := auth.CredentialsFromContext(ctx) 194 f := d.fs.newFile(ctx, creds, "") 195 if err := d.OrderedChildren.Insert(name, f); err != nil { 196 f.DecRef(ctx) 197 return nil, err 198 } 199 d.TouchCMtime(ctx) 200 return f, nil 201 } 202 203 func (*dir) NewLink(context.Context, string, kernfs.Inode) (kernfs.Inode, error) { 204 return nil, linuxerr.EPERM 205 } 206 207 func (*dir) NewSymlink(context.Context, string, string) (kernfs.Inode, error) { 208 return nil, linuxerr.EPERM 209 } 210 211 func (*dir) NewNode(context.Context, string, vfs.MknodOptions) (kernfs.Inode, error) { 212 return nil, linuxerr.EPERM 213 } 214 215 func (fsType) Name() string { 216 return "kernfs" 217 } 218 219 func (fsType) Release(ctx context.Context) {} 220 221 func (fst fsType) GetFilesystem(ctx context.Context, vfsObj *vfs.VirtualFilesystem, creds *auth.Credentials, source string, opt vfs.GetFilesystemOptions) (*vfs.Filesystem, *vfs.Dentry, error) { 222 fs := &filesystem{} 223 fs.VFSFilesystem().Init(vfsObj, &fst, fs) 224 root := fst.rootFn(ctx, creds, fs) 225 var d kernfs.Dentry 226 d.Init(&fs.Filesystem, root) 227 return fs.VFSFilesystem(), d.VFSDentry(), nil 228 } 229 230 // -------------------- Remainder of the file are test cases -------------------- 231 232 func TestBasic(t *testing.T) { 233 sys := newTestSystem(t, func(ctx context.Context, creds *auth.Credentials, fs *filesystem) kernfs.Inode { 234 return fs.newReadonlyDir(ctx, creds, 0755, map[string]kernfs.Inode{ 235 "file1": fs.newFile(ctx, creds, staticFileContent), 236 }) 237 }) 238 defer sys.Destroy() 239 sys.GetDentryOrDie(sys.PathOpAtRoot("file1")).DecRef(sys.Ctx) 240 } 241 242 func TestMkdirGetDentry(t *testing.T) { 243 sys := newTestSystem(t, func(ctx context.Context, creds *auth.Credentials, fs *filesystem) kernfs.Inode { 244 return fs.newReadonlyDir(ctx, creds, 0755, map[string]kernfs.Inode{ 245 "dir1": fs.newDir(ctx, creds, 0755, nil), 246 }) 247 }) 248 defer sys.Destroy() 249 250 pop := sys.PathOpAtRoot("dir1/a new directory") 251 if err := sys.VFS.MkdirAt(sys.Ctx, sys.Creds, pop, &vfs.MkdirOptions{Mode: 0755}); err != nil { 252 t.Fatalf("MkdirAt for PathOperation %+v failed: %v", pop, err) 253 } 254 sys.GetDentryOrDie(pop).DecRef(sys.Ctx) 255 } 256 257 func TestReadStaticFile(t *testing.T) { 258 sys := newTestSystem(t, func(ctx context.Context, creds *auth.Credentials, fs *filesystem) kernfs.Inode { 259 return fs.newReadonlyDir(ctx, creds, 0755, map[string]kernfs.Inode{ 260 "file1": fs.newFile(ctx, creds, staticFileContent), 261 }) 262 }) 263 defer sys.Destroy() 264 265 pop := sys.PathOpAtRoot("file1") 266 fd, err := sys.VFS.OpenAt(sys.Ctx, sys.Creds, pop, &vfs.OpenOptions{ 267 Flags: linux.O_RDONLY, 268 }) 269 if err != nil { 270 t.Fatalf("OpenAt for PathOperation %+v failed: %v", pop, err) 271 } 272 defer fd.DecRef(sys.Ctx) 273 274 content, err := sys.ReadToEnd(fd) 275 if err != nil { 276 t.Fatalf("Read failed: %v", err) 277 } 278 if diff := cmp.Diff(staticFileContent, content); diff != "" { 279 t.Fatalf("Read returned unexpected data:\n--- want\n+++ got\n%v", diff) 280 } 281 } 282 283 func TestCreateNewFileInStaticDir(t *testing.T) { 284 sys := newTestSystem(t, func(ctx context.Context, creds *auth.Credentials, fs *filesystem) kernfs.Inode { 285 return fs.newReadonlyDir(ctx, creds, 0755, map[string]kernfs.Inode{ 286 "dir1": fs.newDir(ctx, creds, 0755, nil), 287 }) 288 }) 289 defer sys.Destroy() 290 291 pop := sys.PathOpAtRoot("dir1/newfile") 292 opts := &vfs.OpenOptions{Flags: linux.O_CREAT | linux.O_EXCL, Mode: defaultMode} 293 fd, err := sys.VFS.OpenAt(sys.Ctx, sys.Creds, pop, opts) 294 if err != nil { 295 t.Fatalf("OpenAt(pop:%+v, opts:%+v) failed: %v", pop, opts, err) 296 } 297 298 // Close the file. The file should persist. 299 fd.DecRef(sys.Ctx) 300 301 fd, err = sys.VFS.OpenAt(sys.Ctx, sys.Creds, pop, &vfs.OpenOptions{ 302 Flags: linux.O_RDONLY, 303 }) 304 if err != nil { 305 t.Fatalf("OpenAt(pop:%+v) = %+v failed: %v", pop, fd, err) 306 } 307 fd.DecRef(sys.Ctx) 308 } 309 310 func TestDirFDReadWrite(t *testing.T) { 311 sys := newTestSystem(t, func(ctx context.Context, creds *auth.Credentials, fs *filesystem) kernfs.Inode { 312 return fs.newReadonlyDir(ctx, creds, 0755, nil) 313 }) 314 defer sys.Destroy() 315 316 pop := sys.PathOpAtRoot("/") 317 fd, err := sys.VFS.OpenAt(sys.Ctx, sys.Creds, pop, &vfs.OpenOptions{ 318 Flags: linux.O_RDONLY, 319 }) 320 if err != nil { 321 t.Fatalf("OpenAt for PathOperation %+v failed: %v", pop, err) 322 } 323 defer fd.DecRef(sys.Ctx) 324 325 // Read/Write should fail for directory FDs. 326 if _, err := fd.Read(sys.Ctx, usermem.BytesIOSequence([]byte{}), vfs.ReadOptions{}); !linuxerr.Equals(linuxerr.EISDIR, err) { 327 t.Fatalf("Read for directory FD failed with unexpected error: %v", err) 328 } 329 if _, err := fd.Write(sys.Ctx, usermem.BytesIOSequence([]byte{}), vfs.WriteOptions{}); !linuxerr.Equals(linuxerr.EBADF, err) { 330 t.Fatalf("Write for directory FD failed with unexpected error: %v", err) 331 } 332 } 333 334 func TestDirFDIterDirents(t *testing.T) { 335 sys := newTestSystem(t, func(ctx context.Context, creds *auth.Credentials, fs *filesystem) kernfs.Inode { 336 return fs.newReadonlyDir(ctx, creds, 0755, map[string]kernfs.Inode{ 337 // Fill root with nodes backed by various inode implementations. 338 "dir1": fs.newReadonlyDir(ctx, creds, 0755, nil), 339 "dir2": fs.newDir(ctx, creds, 0755, map[string]kernfs.Inode{ 340 "dir3": fs.newDir(ctx, creds, 0755, nil), 341 }), 342 "file1": fs.newFile(ctx, creds, staticFileContent), 343 }) 344 }) 345 defer sys.Destroy() 346 347 pop := sys.PathOpAtRoot("/") 348 sys.AssertAllDirentTypes(sys.ListDirents(pop), map[string]testutil.DirentType{ 349 "dir1": linux.DT_DIR, 350 "dir2": linux.DT_DIR, 351 "file1": linux.DT_REG, 352 }) 353 } 354 355 func TestDirWalkDentryTree(t *testing.T) { 356 sys := newTestSystem(t, func(ctx context.Context, creds *auth.Credentials, fs *filesystem) kernfs.Inode { 357 return fs.newDir(ctx, creds, 0755, map[string]kernfs.Inode{ 358 "dir1": fs.newDir(ctx, creds, 0755, nil), 359 "dir2": fs.newDir(ctx, creds, 0755, map[string]kernfs.Inode{ 360 "file1": fs.newFile(ctx, creds, staticFileContent), 361 "dir3": fs.newDir(ctx, creds, 0755, nil), 362 }), 363 }) 364 }) 365 defer sys.Destroy() 366 367 testWalk := func(from *kernfs.Dentry, getDentryPath, walkPath string, expectedErr error) { 368 var d *kernfs.Dentry 369 if getDentryPath != "" { 370 pop := sys.PathOpAtRoot(getDentryPath) 371 vd := sys.GetDentryOrDie(pop) 372 defer vd.DecRef(sys.Ctx) 373 d = vd.Dentry().Impl().(*kernfs.Dentry) 374 } 375 376 match, err := from.WalkDentryTree(sys.Ctx, sys.VFS, fspath.Parse(walkPath)) 377 if err == nil { 378 defer match.DecRef(sys.Ctx) 379 } 380 381 if err != expectedErr { 382 t.Fatalf("WalkDentryTree from %q to %q (with expected error: %v) unexpected error, want: %v, got: %v", from.FSLocalPath(), walkPath, expectedErr, expectedErr, err) 383 } 384 if expectedErr != nil { 385 return 386 } 387 388 if d != match { 389 t.Fatalf("WalkDentryTree from %q to %q (with expected error: %v) found unexpected dentry; want: %v, got: %v", from.FSLocalPath(), walkPath, expectedErr, d, match) 390 } 391 } 392 393 rootD := sys.Root.Dentry().Impl().(*kernfs.Dentry) 394 395 testWalk(rootD, "dir1", "/dir1", nil) 396 testWalk(rootD, "", "/dir-non-existent", linuxerr.ENOENT) 397 testWalk(rootD, "", "/dir1/child-non-existent", linuxerr.ENOENT) 398 testWalk(rootD, "", "/dir2/inner-non-existent/dir3", linuxerr.ENOENT) 399 400 testWalk(rootD, "dir2/dir3", "/dir2/../dir2/dir3", nil) 401 testWalk(rootD, "dir2/dir3", "/dir2/././dir3", nil) 402 testWalk(rootD, "dir2/dir3", "/dir2/././dir3/.././dir3", nil) 403 404 pop := sys.PathOpAtRoot("dir2") 405 dir2VD := sys.GetDentryOrDie(pop) 406 defer dir2VD.DecRef(sys.Ctx) 407 dir2D := dir2VD.Dentry().Impl().(*kernfs.Dentry) 408 409 testWalk(dir2D, "dir2/dir3", "/dir3", nil) 410 testWalk(dir2D, "dir2/dir3", "/../../../dir3", nil) 411 testWalk(dir2D, "dir2/file1", "/file1", nil) 412 testWalk(dir2D, "dir2/file1", "file1", nil) 413 }