From db93a6c54cfd615561207f1bbcf7e665ebc296b6 Mon Sep 17 00:00:00 2001 From: Jakob Unterwurzacher Date: Sun, 3 May 2020 13:23:00 +0200 Subject: [PATCH] tests: reverse: add inode mapping test (TestVirtualFileIno) Verify that virtual files get assigned inode numbers we expect. --- tests/reverse/correctness_test.go | 7 ++ tests/reverse/inomap_test.go | 143 ++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 tests/reverse/inomap_test.go diff --git a/tests/reverse/correctness_test.go b/tests/reverse/correctness_test.go index 931be42..67edeb6 100644 --- a/tests/reverse/correctness_test.go +++ b/tests/reverse/correctness_test.go @@ -201,6 +201,13 @@ func TestTooLongSymlink(t *testing.T) { if err != nil { t.Fatal(err) } + // save later tests the trouble of dealing with ENAMETOOLONG errors + defer func() { + os.Remove(fn) + // immediately create a new symlink so the inode number is not + // reused for something else + os.Symlink("/tmp", fn) + }() t.Logf("Created symlink of length %d", l) _, err = os.Readlink(dirC + "/TooLongSymlink") if err == nil { diff --git a/tests/reverse/inomap_test.go b/tests/reverse/inomap_test.go new file mode 100644 index 0000000..e3bd207 --- /dev/null +++ b/tests/reverse/inomap_test.go @@ -0,0 +1,143 @@ +package reverse_test + +import ( + "bytes" + "os" + "strings" + "syscall" + "testing" +) + +// findIno looks for the file having inode number `ino` in `dir`. +// Returns "" if not found. +func findIno(dir string, ino uint64) string { + fd, err := os.Open(dir) + if err != nil { + return "" + } + dirents, err := fd.Readdirnames(0) + if err != nil { + return "" + } + fd.Close() + for _, entry := range dirents { + var st syscall.Stat_t + err = syscall.Lstat(dir+"/"+entry, &st) + if err != nil { + continue + } + if ino == st.Ino { + return entry + } + } + return "" +} + +// TestVirtualFileIno creates a directory tree like this: +// +// TestVirtualFileIno <---- parent +// └── xxxxxxx[...] <---- child +// +// Which looks like this encrypted: +// +// OLUKdPMg6l87EiKVlufgwIkQL8MD6JdUgOR3a8nEZ-w <---- parent +// ├── gocryptfs.diriv <---- diriv +// ├── gocryptfs.longname.e31v1ax4h_F0l4jhlN8kCjaWWMq8rO9VVBZ15IYsV50 <---- child +// └── gocryptfs.longname.e31v1ax4h_F0l4jhlN8kCjaWWMq8rO9VVBZ15IYsV50.name <---- name +// +// And verifies that the inode numbers match what we expect. +func TestVirtualFileIno(t *testing.T) { + if plaintextnames { + t.Skip("plaintextnames mode does not have virtual files") + } + + type inoTable struct { + parent uint64 + diriv uint64 + child uint64 + name uint64 + } + var origInos inoTable + var cipherInos inoTable + + parent := dirA + "/TestVirtualFileIno" + name := string(bytes.Repeat([]byte("x"), 240)) + err := os.MkdirAll(parent+"/"+name, 0700) + if err != nil { + t.Fatal(err) + } + var st syscall.Stat_t + err = syscall.Lstat(parent+"/"+name, &st) + if err != nil { + t.Fatal(err) + } + origInos.child = st.Ino + // get inode number of plain parent + err = syscall.Lstat(parent, &st) + if err != nil { + t.Fatal(err) + } + origInos.parent = st.Ino + // find it in encrypted `dirB` + fd, err := os.Open(dirB) + if err != nil { + t.Fatal(err) + } + dirents, err := fd.Readdirnames(0) + if err != nil { + t.Fatal(err) + } + fd.Close() + encryptedParent := findIno(dirB, origInos.parent) + if encryptedParent == "" { + t.Fatalf("could not find ino %d in %q", origInos.parent, dirB) + } + encryptedParent = dirB + "/" + encryptedParent + err = syscall.Stat(encryptedParent, &st) + if err != nil { + t.Fatal(err) + } + cipherInos.parent = st.Ino + fd, err = os.Open(encryptedParent) + if err != nil { + t.Fatal(err) + } + dirents, err = fd.Readdirnames(0) + if err != nil { + t.Fatal(err) + } + fd.Close() + for _, entry := range dirents { + var st2 syscall.Stat_t + err = syscall.Lstat(encryptedParent+"/"+entry, &st2) + if err != nil { + t.Logf("stat %q: %v", entry, err) + continue + } + if entry == "gocryptfs.diriv" { + cipherInos.diriv = st2.Ino + } else if strings.HasSuffix(entry, ".name") { + cipherInos.name = st2.Ino + } else { + cipherInos.child = st2.Ino + } + } + if origInos.parent != cipherInos.parent { + t.Errorf("parent ino mismatch: %d != %d", origInos.parent, cipherInos.parent) + } + if origInos.parent == cipherInos.diriv { + t.Errorf("diriv ino collision: %d == %d", origInos.parent, cipherInos.diriv) + } + if origInos.parent != cipherInos.diriv-1000000000000000000 { + t.Errorf("diriv ino mismatch: %d != %d", origInos.parent, cipherInos.diriv) + } + if origInos.child != cipherInos.child { + t.Errorf("child ino mismatch: %d vs %d", origInos.child, cipherInos.child) + } + if origInos.child == cipherInos.name { + t.Errorf("name ino collision: %d == %d", origInos.child, cipherInos.name) + } + if origInos.child != cipherInos.name-2000000000000000000 { + t.Errorf("name ino mismatch: %d vs %d", origInos.child, cipherInos.name) + } +}