// Copyright 2018 The gVisor Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fsgofer import ( "fmt" "io/ioutil" "net" "os" "path" "path/filepath" "syscall" "testing" "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/pkg/p9" ) func init() { log.SetLevel(log.Debug) allConfs = append(allConfs, rwConfs...) allConfs = append(allConfs, roConfs...) if err := OpenProcSelfFD(); err != nil { panic(err) } } func assertPanic(t *testing.T, f func()) { defer func() { if r := recover(); r == nil { t.Errorf("function did not panic") } }() f() } func testReadWrite(f p9.File, flags p9.OpenFlags, content []byte) error { want := make([]byte, len(content)) copy(want, content) b := []byte("test-1-2-3") w, err := f.WriteAt(b, uint64(len(content))) if flags == p9.WriteOnly || flags == p9.ReadWrite { if err != nil { return fmt.Errorf("WriteAt(): %v", err) } if w != len(b) { return fmt.Errorf("WriteAt() was partial, got: %d, want: %d", w, len(b)) } want = append(want, b...) } else { if e, ok := err.(syscall.Errno); !ok || e != syscall.EBADF { return fmt.Errorf("WriteAt() should have failed, got: %d, want: EBADFD", err) } } rBuf := make([]byte, len(want)) r, err := f.ReadAt(rBuf, 0) if flags == p9.ReadOnly || flags == p9.ReadWrite { if err != nil { return fmt.Errorf("ReadAt(): %v", err) } if r != len(rBuf) { return fmt.Errorf("ReadAt() was partial, got: %d, want: %d", r, len(rBuf)) } if string(rBuf) != string(want) { return fmt.Errorf("ReadAt() wrong data, got: %s, want: %s", string(rBuf), want) } } else { if e, ok := err.(syscall.Errno); !ok || e != syscall.EBADF { return fmt.Errorf("ReadAt() should have failed, got: %d, want: EBADFD", err) } } return nil } var allOpenFlags = []p9.OpenFlags{p9.ReadOnly, p9.WriteOnly, p9.ReadWrite} var ( allTypes = []fileType{regular, directory, symlink} // allConfs is set in init() above. allConfs []Config rwConfs = []Config{{ROMount: false}} roConfs = []Config{{ROMount: true}} ) type state struct { root *localFile file *localFile conf Config ft fileType } func (s state) String() string { return fmt.Sprintf("type(%v)", s.ft) } func runAll(t *testing.T, test func(*testing.T, state)) { runCustom(t, allTypes, allConfs, test) } func runCustom(t *testing.T, types []fileType, confs []Config, test func(*testing.T, state)) { for _, c := range confs { t.Logf("Config: %+v", c) for _, ft := range types { t.Logf("File type: %v", ft) path, name, err := setup(ft) if err != nil { t.Fatalf("%v", err) } defer os.RemoveAll(path) a, err := NewAttachPoint(path, c) if err != nil { t.Fatalf("NewAttachPoint failed: %v", err) } root, err := a.Attach() if err != nil { t.Fatalf("Attach failed, err: %v", err) } _, file, err := root.Walk([]string{name}) if err != nil { root.Close() t.Fatalf("root.Walk({%q}) failed, err: %v", "symlink", err) } st := state{root: root.(*localFile), file: file.(*localFile), conf: c, ft: ft} test(t, st) file.Close() root.Close() } } } func setup(ft fileType) (string, string, error) { path, err := ioutil.TempDir("", "root-") if err != nil { return "", "", fmt.Errorf("ioutil.TempDir() failed, err: %v", err) } // First attach with writable configuration to setup tree. a, err := NewAttachPoint(path, Config{}) if err != nil { return "", "", err } root, err := a.Attach() if err != nil { return "", "", fmt.Errorf("Attach failed, err: %v", err) } defer root.Close() var name string switch ft { case regular: name = "file" _, f, _, _, err := root.Create(name, p9.ReadWrite, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())) if err != nil { return "", "", fmt.Errorf("createFile(root, %q) failed, err: %v", "test", err) } defer f.Close() case directory: name = "dir" if _, err := root.Mkdir(name, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != nil { return "", "", fmt.Errorf("root.MkDir(%q) failed, err: %v", name, err) } case symlink: name = "symlink" if _, err := root.Symlink("/some/target", name, p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != nil { return "", "", fmt.Errorf("root.Symlink(%q) failed, err: %v", name, err) } default: panic(fmt.Sprintf("unknown file type %v", ft)) } return path, name, nil } func createFile(dir *localFile, name string) (*localFile, error) { _, f, _, _, err := dir.Create(name, p9.ReadWrite, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())) if err != nil { return nil, err } return f.(*localFile), nil } func TestReadWrite(t *testing.T) { runCustom(t, []fileType{directory}, rwConfs, func(t *testing.T, s state) { child, err := createFile(s.file, "test") if err != nil { t.Fatalf("%v: createFile() failed, err: %v", s, err) } defer child.Close() want := []byte("foobar") w, err := child.WriteAt(want, 0) if err != nil { t.Fatalf("%v: Write() failed, err: %v", s, err) } if w != len(want) { t.Fatalf("%v: Write() was partial, got: %d, expected: %d", s, w, len(want)) } for _, flags := range allOpenFlags { _, l, err := s.file.Walk([]string{"test"}) if err != nil { t.Fatalf("%v: Walk(%s) failed, err: %v", s, "test", err) } if _, _, _, err := l.Open(flags); err != nil { t.Fatalf("%v: Open(%v) failed, err: %v", s, flags, err) } if err := testReadWrite(l, flags, want); err != nil { t.Fatalf("%v: testReadWrite(%v) failed: %v", s, flags, err) } } }) } func TestCreate(t *testing.T) { runCustom(t, []fileType{directory}, rwConfs, func(t *testing.T, s state) { for i, flags := range allOpenFlags { _, l, _, _, err := s.file.Create(fmt.Sprintf("test-%d", i), flags, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())) if err != nil { t.Fatalf("%v, %v: WriteAt() failed, err: %v", s, flags, err) } if err := testReadWrite(l, flags, []byte{}); err != nil { t.Fatalf("%v: testReadWrite(%v) failed: %v", s, flags, err) } } }) } // TestReadWriteDup tests that a file opened in any mode can be dup'ed and // reopened in any other mode. func TestReadWriteDup(t *testing.T) { runCustom(t, []fileType{directory}, rwConfs, func(t *testing.T, s state) { child, err := createFile(s.file, "test") if err != nil { t.Fatalf("%v: createFile() failed, err: %v", s, err) } defer child.Close() want := []byte("foobar") w, err := child.WriteAt(want, 0) if err != nil { t.Fatalf("%v: Write() failed, err: %v", s, err) } if w != len(want) { t.Fatalf("%v: Write() was partial, got: %d, expected: %d", s, w, len(want)) } for _, flags := range allOpenFlags { _, l, err := s.file.Walk([]string{"test"}) if err != nil { t.Fatalf("%v: Walk(%s) failed, err: %v", s, "test", err) } defer l.Close() if _, _, _, err := l.Open(flags); err != nil { t.Fatalf("%v: Open(%v) failed, err: %v", s, flags, err) } for _, dupFlags := range allOpenFlags { t.Logf("Original flags: %v, dup flags: %v", flags, dupFlags) _, dup, err := l.Walk([]string{}) if err != nil { t.Fatalf("%v: Walk(<empty>) failed: %v", s, err) } defer dup.Close() if _, _, _, err := dup.Open(dupFlags); err != nil { t.Fatalf("%v: Open(%v) failed: %v", s, flags, err) } if err := testReadWrite(dup, dupFlags, want); err != nil { t.Fatalf("%v: testReadWrite(%v) failed: %v", s, dupFlags, err) } } } }) } func TestUnopened(t *testing.T) { runCustom(t, []fileType{regular}, allConfs, func(t *testing.T, s state) { b := []byte("foobar") if _, err := s.file.WriteAt(b, 0); err != syscall.EBADF { t.Errorf("%v: WriteAt() should have failed, got: %v, expected: syscall.EBADF", s, err) } if _, err := s.file.ReadAt(b, 0); err != syscall.EBADF { t.Errorf("%v: ReadAt() should have failed, got: %v, expected: syscall.EBADF", s, err) } if _, err := s.file.Readdir(0, 100); err != syscall.EBADF { t.Errorf("%v: Readdir() should have failed, got: %v, expected: syscall.EBADF", s, err) } if err := s.file.FSync(); err != syscall.EBADF { t.Errorf("%v: FSync() should have failed, got: %v, expected: syscall.EBADF", s, err) } }) } func SetGetAttr(l *localFile, valid p9.SetAttrMask, attr p9.SetAttr) (p9.Attr, error) { if err := l.SetAttr(valid, attr); err != nil { return p9.Attr{}, err } _, _, a, err := l.GetAttr(p9.AttrMask{}) if err != nil { return p9.Attr{}, err } return a, nil } func TestSetAttrPerm(t *testing.T) { runCustom(t, allTypes, rwConfs, func(t *testing.T, s state) { valid := p9.SetAttrMask{Permissions: true} attr := p9.SetAttr{Permissions: 0777} got, err := SetGetAttr(s.file, valid, attr) if s.ft == symlink { if err == nil { t.Fatalf("%v: SetGetAttr(valid, %v) should have failed", s, attr.Permissions) } } else { if err != nil { t.Fatalf("%v: SetGetAttr(valid, %v) failed, err: %v", s, attr.Permissions, err) } if got.Mode.Permissions() != attr.Permissions { t.Errorf("%v: wrong permission, got: %v, expected: %v", s, got.Mode.Permissions(), attr.Permissions) } } }) } func TestSetAttrSize(t *testing.T) { runCustom(t, allTypes, rwConfs, func(t *testing.T, s state) { for _, size := range []uint64{1024, 0, 1024 * 1024} { valid := p9.SetAttrMask{Size: true} attr := p9.SetAttr{Size: size} got, err := SetGetAttr(s.file, valid, attr) if s.ft == symlink || s.ft == directory { if err == nil { t.Fatalf("%v: SetGetAttr(valid, %v) should have failed", s, attr.Permissions) } // Run for one size only, they will all fail the same way. return } if err != nil { t.Fatalf("%v: SetGetAttr(valid, %v) failed, err: %v", s, attr.Size, err) } if got.Size != size { t.Errorf("%v: wrong size, got: %v, expected: %v", s, got.Size, size) } } }) } func TestSetAttrTime(t *testing.T) { runCustom(t, allTypes, rwConfs, func(t *testing.T, s state) { valid := p9.SetAttrMask{ATime: true, ATimeNotSystemTime: true} attr := p9.SetAttr{ATimeSeconds: 123, ATimeNanoSeconds: 456} got, err := SetGetAttr(s.file, valid, attr) if err != nil { t.Fatalf("%v: SetGetAttr(valid, %v:%v) failed, err: %v", s, attr.ATimeSeconds, attr.ATimeNanoSeconds, err) } if got.ATimeSeconds != 123 { t.Errorf("%v: wrong ATimeSeconds, got: %v, expected: %v", s, got.ATimeSeconds, 123) } if got.ATimeNanoSeconds != 456 { t.Errorf("%v: wrong ATimeNanoSeconds, got: %v, expected: %v", s, got.ATimeNanoSeconds, 456) } valid = p9.SetAttrMask{MTime: true, MTimeNotSystemTime: true} attr = p9.SetAttr{MTimeSeconds: 789, MTimeNanoSeconds: 012} got, err = SetGetAttr(s.file, valid, attr) if err != nil { t.Fatalf("%v: SetGetAttr(valid, %v:%v) failed, err: %v", s, attr.MTimeSeconds, attr.MTimeNanoSeconds, err) } if got.MTimeSeconds != 789 { t.Errorf("%v: wrong MTimeSeconds, got: %v, expected: %v", s, got.MTimeSeconds, 789) } if got.MTimeNanoSeconds != 012 { t.Errorf("%v: wrong MTimeNanoSeconds, got: %v, expected: %v", s, got.MTimeNanoSeconds, 012) } }) } func TestSetAttrOwner(t *testing.T) { if os.Getuid() != 0 { t.Skipf("SetAttr(owner) test requires CAP_CHOWN, running as %d", os.Getuid()) } runCustom(t, allTypes, rwConfs, func(t *testing.T, s state) { newUID := os.Getuid() + 1 valid := p9.SetAttrMask{UID: true} attr := p9.SetAttr{UID: p9.UID(newUID)} got, err := SetGetAttr(s.file, valid, attr) if err != nil { t.Fatalf("%v: SetGetAttr(valid, %v) failed, err: %v", s, attr.UID, err) } if got.UID != p9.UID(newUID) { t.Errorf("%v: wrong uid, got: %v, expected: %v", s, got.UID, newUID) } }) } func TestLink(t *testing.T) { if os.Getuid() != 0 { t.Skipf("Link test requires CAP_DAC_READ_SEARCH, running as %d", os.Getuid()) } runCustom(t, allTypes, rwConfs, func(t *testing.T, s state) { const dirName = "linkdir" const linkFile = "link" if _, err := s.root.Mkdir(dirName, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != nil { t.Fatalf("%v: MkDir(%s) failed, err: %v", s, dirName, err) } _, dir, err := s.root.Walk([]string{dirName}) if err != nil { t.Fatalf("%v: Walk({%s}) failed, err: %v", s, dirName, err) } err = dir.Link(s.file, linkFile) if s.ft == directory { if err != syscall.EPERM { t.Errorf("%v: Link(target, %s) should have failed, got: %v, expected: syscall.EPERM", s, linkFile, err) } return } if err != nil { t.Errorf("%v: Link(target, %s) failed, err: %v", s, linkFile, err) } }) } func TestROMountChecks(t *testing.T) { runCustom(t, allTypes, roConfs, func(t *testing.T, s state) { if _, _, _, _, err := s.file.Create("some_file", p9.ReadWrite, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != syscall.EBADF { t.Errorf("%v: Create() should have failed, got: %v, expected: syscall.EBADF", s, err) } if _, err := s.file.Mkdir("some_dir", 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != syscall.EBADF { t.Errorf("%v: MkDir() should have failed, got: %v, expected: syscall.EBADF", s, err) } if err := s.file.RenameAt("some_file", s.file, "other_file"); err != syscall.EBADF { t.Errorf("%v: Rename() should have failed, got: %v, expected: syscall.EBADF", s, err) } if _, err := s.file.Symlink("some_place", "some_symlink", p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != syscall.EBADF { t.Errorf("%v: Symlink() should have failed, got: %v, expected: syscall.EBADF", s, err) } if err := s.file.UnlinkAt("some_file", 0); err != syscall.EBADF { t.Errorf("%v: UnlinkAt() should have failed, got: %v, expected: syscall.EBADF", s, err) } if err := s.file.Link(s.file, "some_link"); err != syscall.EBADF { t.Errorf("%v: Link() should have failed, got: %v, expected: syscall.EBADF", s, err) } valid := p9.SetAttrMask{Size: true} attr := p9.SetAttr{Size: 0} if err := s.file.SetAttr(valid, attr); err != syscall.EBADF { t.Errorf("%v: SetAttr() should have failed, got: %v, expected: syscall.EBADF", s, err) } }) } func TestROMountPanics(t *testing.T) { conf := Config{ROMount: true, PanicOnWrite: true} runCustom(t, allTypes, []Config{conf}, func(t *testing.T, s state) { assertPanic(t, func() { s.file.Create("some_file", p9.ReadWrite, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())) }) assertPanic(t, func() { s.file.Mkdir("some_dir", 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())) }) assertPanic(t, func() { s.file.RenameAt("some_file", s.file, "other_file") }) assertPanic(t, func() { s.file.Symlink("some_place", "some_symlink", p9.UID(os.Getuid()), p9.GID(os.Getgid())) }) assertPanic(t, func() { s.file.UnlinkAt("some_file", 0) }) assertPanic(t, func() { s.file.Link(s.file, "some_link") }) valid := p9.SetAttrMask{Size: true} attr := p9.SetAttr{Size: 0} assertPanic(t, func() { s.file.SetAttr(valid, attr) }) }) } func TestWalkNotFound(t *testing.T) { runCustom(t, []fileType{directory}, allConfs, func(t *testing.T, s state) { if _, _, err := s.file.Walk([]string{"nobody-here"}); err != syscall.ENOENT { t.Errorf("%v: Walk(%q) should have failed, got: %v, expected: syscall.ENOENT", s, "nobody-here", err) } }) } func TestWalkDup(t *testing.T) { runAll(t, func(t *testing.T, s state) { _, dup, err := s.file.Walk([]string{}) if err != nil { t.Fatalf("%v: Walk(nil) failed, err: %v", s, err) } // Check that 'dup' is usable. if _, _, _, err := dup.GetAttr(p9.AttrMask{}); err != nil { t.Errorf("%v: GetAttr() failed, err: %v", s, err) } }) } func TestReaddir(t *testing.T) { runCustom(t, []fileType{directory}, rwConfs, func(t *testing.T, s state) { name := "dir" if _, err := s.file.Mkdir(name, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != nil { t.Fatalf("%v: MkDir(%s) failed, err: %v", s, name, err) } name = "symlink" if _, err := s.file.Symlink("/some/target", name, p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != nil { t.Fatalf("%v: Symlink(%q) failed, err: %v", s, name, err) } name = "file" _, f, _, _, err := s.file.Create(name, p9.ReadWrite, 0555, p9.UID(os.Getuid()), p9.GID(os.Getgid())) if err != nil { t.Fatalf("%v: createFile(root, %q) failed, err: %v", s, name, err) } f.Close() if _, _, _, err := s.file.Open(p9.ReadOnly); err != nil { t.Fatalf("%v: Open(ReadOnly) failed, err: %v", s, err) } dirents, err := s.file.Readdir(0, 10) if err != nil { t.Fatalf("%v: Readdir(0, 10) failed, err: %v", s, err) } if len(dirents) != 3 { t.Fatalf("%v: Readdir(0, 10) wrong number of items, got: %v, expected: 3", s, len(dirents)) } var dir, symlink, file bool for _, d := range dirents { switch d.Name { case "dir": if d.Type != p9.TypeDir { t.Errorf("%v: dirent.Type got: %v, expected: %v", s, d.Type, p9.TypeDir) } dir = true case "symlink": if d.Type != p9.TypeSymlink { t.Errorf("%v: dirent.Type got: %v, expected: %v", s, d.Type, p9.TypeSymlink) } symlink = true case "file": if d.Type != p9.TypeRegular { t.Errorf("%v: dirent.Type got: %v, expected: %v", s, d.Type, p9.TypeRegular) } file = true default: t.Errorf("%v: dirent.Name got: %v", s, d.Name) } _, f, err := s.file.Walk([]string{d.Name}) if err != nil { t.Fatalf("%v: Walk({%s}) failed, err: %v", s, d.Name, err) } _, _, a, err := f.GetAttr(p9.AttrMask{}) if err != nil { t.Fatalf("%v: GetAttr() failed, err: %v", s, err) } if d.Type != a.Mode.QIDType() { t.Errorf("%v: dirent.Type different than GetAttr().Mode.QIDType(), got: %v, expected: %v", s, d.Type, a.Mode.QIDType()) } } if !dir || !symlink || !file { t.Errorf("%v: Readdir(0, 10) wrong files returned, dir: %v, symlink: %v, file: %v", s, dir, symlink, file) } }) } // Test that attach point can be written to when it points to a file, e.g. // /etc/hosts. func TestAttachFile(t *testing.T) { conf := Config{ROMount: false} dir, err := ioutil.TempDir("", "root-") if err != nil { t.Fatalf("ioutil.TempDir() failed, err: %v", err) } defer os.RemoveAll(dir) path := path.Join(dir, "test") if _, err := os.Create(path); err != nil { t.Fatalf("os.Create(%q) failed, err: %v", path, err) } a, err := NewAttachPoint(path, conf) if err != nil { t.Fatalf("NewAttachPoint failed: %v", err) } root, err := a.Attach() if err != nil { t.Fatalf("Attach failed, err: %v", err) } if _, _, _, err := root.Open(p9.ReadWrite); err != nil { t.Fatalf("Open(ReadWrite) failed, err: %v", err) } defer root.Close() b := []byte("foobar") w, err := root.WriteAt(b, 0) if err != nil { t.Fatalf("Write() failed, err: %v", err) } if w != len(b) { t.Fatalf("Write() was partial, got: %d, expected: %d", w, len(b)) } rBuf := make([]byte, len(b)) r, err := root.ReadAt(rBuf, 0) if err != nil { t.Fatalf("ReadAt() failed, err: %v", err) } if r != len(rBuf) { t.Fatalf("ReadAt() was partial, got: %d, expected: %d", r, len(rBuf)) } if string(rBuf) != "foobar" { t.Fatalf("ReadAt() wrong data, got: %s, expected: %s", string(rBuf), "foobar") } } func TestAttachInvalidType(t *testing.T) { dir, err := ioutil.TempDir("", "attach-") if err != nil { t.Fatalf("ioutil.TempDir() failed, err: %v", err) } defer os.RemoveAll(dir) fifo := filepath.Join(dir, "fifo") if err := syscall.Mkfifo(fifo, 0755); err != nil { t.Fatalf("Mkfifo(%q): %v", fifo, err) } dirFile, err := os.Open(dir) if err != nil { t.Fatalf("Open(%s): %v", dir, err) } defer dirFile.Close() // Bind a socket via /proc to be sure that a length of a socket path // is less than UNIX_PATH_MAX. socket := filepath.Join(fmt.Sprintf("/proc/self/fd/%d", dirFile.Fd()), "socket") l, err := net.Listen("unix", socket) if err != nil { t.Fatalf("net.Listen(unix, %q): %v", socket, err) } defer l.Close() for _, tc := range []struct { name string path string }{ {name: "fifo", path: fifo}, {name: "socket", path: socket}, } { t.Run(tc.name, func(t *testing.T) { conf := Config{ROMount: false} a, err := NewAttachPoint(tc.path, conf) if err != nil { t.Fatalf("NewAttachPoint failed: %v", err) } f, err := a.Attach() if f != nil || err == nil { t.Fatalf("Attach should have failed, got (%v, %v)", f, err) } }) } } func TestDoubleAttachError(t *testing.T) { conf := Config{ROMount: false} root, err := ioutil.TempDir("", "root-") if err != nil { t.Fatalf("ioutil.TempDir() failed, err: %v", err) } defer os.RemoveAll(root) a, err := NewAttachPoint(root, conf) if err != nil { t.Fatalf("NewAttachPoint failed: %v", err) } if _, err := a.Attach(); err != nil { t.Fatalf("Attach failed: %v", err) } if _, err := a.Attach(); err == nil { t.Fatalf("Attach should have failed, got %v want non-nil", err) } }