| // Copyright 2016 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package msdosfs |
| |
| import ( |
| "bytes" |
| "math/rand" |
| "testing" |
| "time" |
| |
| "github.com/golang/glog" |
| |
| "thinfs/block" |
| "thinfs/fs" |
| "thinfs/fs/msdosfs/node" |
| "thinfs/fs/msdosfs/testutil" |
| ) |
| |
| // Without accessing any nodes, mount and unmount the filesystem |
| func TestMountUnmount(t *testing.T) { |
| fileBackedFAT, dev := setupFAT32(t) |
| defer cleanup(fileBackedFAT, dev) |
| |
| fatFS := checkNewFS(t, dev, fs.ReadWrite) |
| |
| blockcount := fatFS.Blockcount() |
| blocksize := fatFS.Blocksize() |
| size := fatFS.Size() |
| |
| if blockcount <= 0 { |
| t.Fatalf("Expected positive blockcount, but got %d", blockcount) |
| } else if blocksize != 2048 { |
| t.Fatalf("Expected blocksize of %d, but got %d", 2048, blocksize) |
| } else if size != blockcount*blocksize { |
| t.Fatalf("Expected size of %d, but got %d", blockcount*blocksize, size) |
| } |
| |
| root := fatFS.RootDirectory() |
| if root == nil { |
| t.Fatal("RootDirectory returned nil") |
| } |
| |
| checkClose(t, root) |
| checkCloseFS(t, fatFS) |
| } |
| |
| // Test some basic directory / file operations inside the root |
| func TestRootBasic(t *testing.T) { |
| doTest := func(dev block.Device) { |
| fatFS := checkNewFS(t, dev, fs.ReadWrite) |
| root := fatFS.RootDirectory() |
| |
| rootContents := checkReadDir(t, root, 1) |
| checkDirent(t, rootContents[0], ".", fs.FileTypeDirectory) |
| |
| // First, try to open the subdirectory as if it existed. |
| if _, _, err := root.Open("subdir", fs.OpenFlagRead); err != fs.ErrNotFound { |
| t.Fatalf("Expected ErrNotFound, saw err: %s", err) |
| } |
| // Okay, it doesn't exist. Try creating it. |
| subdir := checkOpenDirectory(t, root, "subdir", fs.OpenFlagWrite|fs.OpenFlagRead|fs.OpenFlagCreate) |
| |
| // Verify that the new directory is empty. |
| subDirContents := checkReadDir(t, subdir, 1) |
| checkDirent(t, subDirContents[0], ".", fs.FileTypeDirectory) |
| |
| // Verify the root has been updated. |
| rootContents = checkReadDir(t, root, 2) |
| checkDirent(t, rootContents[0], ".", fs.FileTypeDirectory) |
| checkDirent(t, rootContents[1], "subdir", fs.FileTypeDirectory) |
| |
| checkClose(t, subdir) |
| |
| // Try making a file in root. |
| if _, _, err := root.Open("foo", fs.OpenFlagRead); err != fs.ErrNotFound { |
| t.Fatalf("Expected ErrNotFound, saw err: %s", err) |
| } |
| foo := checkOpenFile(t, root, "foo", fs.OpenFlagRead|fs.OpenFlagWrite|fs.OpenFlagCreate) |
| |
| // Verify the root has been updated. |
| rootContents = checkReadDir(t, root, 3) |
| checkDirent(t, rootContents[0], ".", fs.FileTypeDirectory) |
| checkDirent(t, rootContents[1], "subdir", fs.FileTypeDirectory) |
| checkDirent(t, rootContents[2], "foo", fs.FileTypeRegularFile) |
| |
| // Write to the file, close it, reopen it, and read it. |
| writeBuf := []byte{'a', 'b', 'c'} |
| checkWrite(t, foo, writeBuf, 0, fs.WhenceFromStart) |
| checkClose(t, foo) |
| if _, _, err := root.Open("foo", fs.OpenFlagRead|fs.OpenFlagCreate|fs.OpenFlagExclusive); err != fs.ErrAlreadyExists { |
| t.Fatalf("Expected ErrAlreadyExists, saw err: %s", err) |
| } |
| foo = checkOpenFile(t, root, "foo", fs.OpenFlagRead) |
| readBuf := make([]byte, len(writeBuf)) |
| checkRead(t, foo, readBuf, 0, fs.WhenceFromStart) |
| if !bytes.Equal(readBuf, writeBuf) { |
| t.Fatal("Input buffer did not equal output buffer") |
| } |
| |
| // Close the root, unmount the filesystem. |
| checkClose(t, root) |
| checkCloseFS(t, fatFS) |
| |
| // Remount the filesystem, verify everything still exists. |
| fatFS = checkNewFS(t, dev, fs.ReadWrite) |
| root = fatFS.RootDirectory() |
| |
| // Verify root |
| rootContents = checkReadDir(t, root, 3) |
| checkDirent(t, rootContents[0], ".", fs.FileTypeDirectory) |
| checkDirent(t, rootContents[1], "subdir", fs.FileTypeDirectory) |
| checkDirent(t, rootContents[2], "foo", fs.FileTypeRegularFile) |
| |
| // Verify subdirectory |
| subdir = checkOpenDirectory(t, root, "subdir", fs.OpenFlagRead) |
| subDirContents = checkReadDir(t, subdir, 1) |
| checkDirent(t, subDirContents[0], ".", fs.FileTypeDirectory) |
| |
| // Verify foo |
| foo = checkOpenFile(t, root, "foo", fs.OpenFlagRead) |
| checkRead(t, foo, readBuf, 0, fs.WhenceFromStart) |
| if !bytes.Equal(readBuf, writeBuf) { |
| t.Fatal("Input buffer did not equal output buffer") |
| } |
| |
| checkClose(t, foo) |
| checkClose(t, subdir) |
| checkClose(t, root) |
| checkCloseFS(t, fatFS) |
| } |
| |
| glog.Info("Testing FAT32 Root") |
| fileBackedFAT, dev := setupFAT32(t) |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| |
| glog.Info("Testing FAT16 Root") |
| fileBackedFAT, dev = setupFAT16(t) |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| } |
| |
| // Test that the open flags behave as they should |
| func TestOpenFlags(t *testing.T) { |
| doTest := func(dev block.Device) { |
| fatFS := checkNewFS(t, dev, fs.ReadWrite) |
| root := fatFS.RootDirectory() |
| |
| testOpenFlags := func(d fs.Directory) { |
| // Start with an empty directory |
| contents := checkReadDir(t, d, 1) |
| checkDirent(t, contents[0], ".", fs.FileTypeDirectory) |
| |
| // Try creating a node without specifying if it is a file or directory |
| if _, _, err := d.Open("foo", fs.OpenFlagCreate|fs.OpenFlagWrite); err != fs.ErrInvalidArgs { |
| t.Fatalf("Expected ErrInvalidArgs, saw err: %s", err) |
| } |
| |
| // Try creating a file without permissions |
| if _, _, err := d.Open("foo", fs.OpenFlagCreate|fs.OpenFlagFile); err != fs.ErrPermission { |
| t.Fatalf("Expected ErrInvalidArgs, saw err: %s", err) |
| } |
| |
| // Try creating a file without write permissions |
| if _, _, err := d.Open("foo", fs.OpenFlagCreate|fs.OpenFlagRead|fs.OpenFlagFile); err != fs.ErrPermission { |
| t.Fatalf("Expected ErrInvalidArgs, saw err: %s", err) |
| } |
| |
| // Make a file that is writeable (without append) |
| f := checkOpenFile(t, d, "foo", fs.OpenFlagCreate|fs.OpenFlagWrite|fs.OpenFlagFile) |
| |
| // Test that the file is writable |
| bufRead := make([]byte, 100) |
| bufA := testutil.MakeRandomBuffer(100) |
| checkWrite(t, f, bufA, 0, fs.WhenceFromCurrent) |
| // Test that the file is seekable |
| if pos := checkSeek(t, f, 0, fs.WhenceFromStart); pos != 0 { |
| t.Fatal("Unexpected seek position: ", pos) |
| } |
| // Test that the file is not readable |
| if _, err := f.Read(bufRead, 0, fs.WhenceFromStart); err != fs.ErrPermission { |
| t.Fatal("Expected permission error, saw ", err) |
| } |
| checkClose(t, f) |
| |
| // Re-open the file as readable (without append, and without 'file' flag) |
| f = checkOpenFile(t, d, "foo", fs.OpenFlagRead) |
| |
| // Test that the file is not writeable |
| if _, err := f.Write(bufA, 0, fs.WhenceFromCurrent); err != fs.ErrPermission { |
| t.Fatal("Expected permission error, saw ", err) |
| } |
| // Test that the file is readable |
| checkRead(t, f, bufRead, 0, fs.WhenceFromStart) |
| if !bytes.Equal(bufRead, bufA) { |
| t.Fatal("Bytes written as write-only did not equal bytes read as read-only") |
| } |
| // Test that the file is seekable |
| if pos := checkSeek(t, f, 0, fs.WhenceFromStart); pos != 0 { |
| t.Fatal("Unexpected seek position: ", pos) |
| } |
| checkClose(t, f) |
| |
| // Re-open the file as append-only |
| f = checkOpenFile(t, d, "foo", fs.OpenFlagRead|fs.OpenFlagWrite|fs.OpenFlagAppend) |
| // Test that file is still seekable |
| checkSeek(t, f, 1, fs.WhenceFromStart) |
| sizeBefore, _, _ := checkStat(t, f) |
| // Test a command that normally would not append |
| if _, err := f.Write(bufA, 0, fs.WhenceFromStart); err != nil { |
| t.Fatal(err) |
| } |
| // Observe that we appended to the file anyway |
| sizeAfter, _, _ := checkStat(t, f) |
| if sizeBefore+int64(len(bufA)) != sizeAfter { |
| t.Fatalf("Expected append-only write to add %d bytes to file", len(bufA)) |
| } |
| checkClose(t, f) |
| |
| // Try opening the file as a directory |
| var err error |
| if _, _, err = d.Open("foo", fs.OpenFlagRead|fs.OpenFlagDirectory); err != fs.ErrNotADir { |
| t.Fatalf("Expected ErrNotADir, but saw: %s", err) |
| } |
| // Try creating the file as a directory (non-exclusive) |
| flags := fs.OpenFlagRead | fs.OpenFlagWrite | fs.OpenFlagDirectory | fs.OpenFlagCreate |
| if _, _, err = d.Open("foo", flags); err != fs.ErrNotADir { |
| t.Fatalf("Expected ErrAlreadyExists, but saw: %s", err) |
| } |
| // Try creating the file as a directory (exclusive) |
| flags |= fs.OpenFlagExclusive |
| if _, _, err = d.Open("foo", flags); err != fs.ErrAlreadyExists { |
| t.Fatalf("Expected ErrAlreadyExists, but saw: %s", err) |
| } |
| flags = fs.OpenFlagWrite | fs.OpenFlagDirectory | fs.OpenFlagCreate |
| // Try making a new directory without read permissions (note the new name) |
| if _, _, err = d.Open("newdir", flags); err != fs.ErrPermission { |
| t.Fatalf("Expected error due to lack of read perm, but saw: %s", err) |
| } |
| |
| // Try truncating a file without write permissions |
| if _, _, err = d.Open("foo", fs.OpenFlagRead|fs.OpenFlagTruncate); err != fs.ErrPermission { |
| t.Fatalf("Expected ErrPermission, but saw %s", err) |
| } |
| // Re-open the file, successfully truncating it |
| f = checkOpenFile(t, d, "foo", fs.OpenFlagWrite|fs.OpenFlagTruncate) |
| size, _, _ := checkStat(t, f) |
| if size != 0 { |
| t.Fatal("Truncate flag should have set the file size to zero") |
| } |
| |
| // Verify the size of the truncated file |
| size, _, _ = checkStat(t, f) |
| if size != int64(0) { |
| t.Fatalf("Unexpected size from stat: %d (expected %d)", size, 0) |
| } |
| |
| checkClose(t, f) |
| |
| contents = checkReadDir(t, d, 2) |
| checkDirent(t, contents[0], ".", fs.FileTypeDirectory) |
| checkDirent(t, contents[1], "foo", fs.FileTypeRegularFile) |
| } |
| |
| testOpenFlags(root) |
| subdir := checkOpenDirectory(t, root, "subdir", fs.OpenFlagCreate|fs.OpenFlagRead|fs.OpenFlagWrite) |
| testOpenFlags(subdir) |
| |
| checkClose(t, subdir) |
| checkClose(t, root) |
| checkCloseFS(t, fatFS) |
| } |
| |
| glog.Info("Testing FAT32") |
| fileBackedFAT, dev := setupFAT32(t) |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| |
| glog.Info("Testing FAT16") |
| fileBackedFAT, dev = setupFAT16(t) |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| } |
| |
| // Test that readdir still functions when the directory contains "free" direntries |
| func TestDirectoryHoles(t *testing.T) { |
| doTest := func(dev block.Device) { |
| fatFS := checkNewFS(t, dev, fs.ReadWrite) |
| root := fatFS.RootDirectory() |
| |
| // Confirm that the directory contains 'filenames' as files, in order. |
| confirmDirectoryContents := func(d fs.Directory, filenames []string) { |
| contents := checkReadDir(t, d, len(filenames)+1) |
| checkDirent(t, contents[0], ".", fs.FileTypeDirectory) |
| for i := range filenames { |
| checkDirent(t, contents[i+1], filenames[i], fs.FileTypeRegularFile) |
| } |
| } |
| |
| doTest := func(d fs.Directory) { |
| // Start with an empty directory |
| contents := checkReadDir(t, d, 1) |
| checkDirent(t, contents[0], ".", fs.FileTypeDirectory) |
| |
| filenames := []string{ |
| "foo", |
| "This is a long file name", |
| "This is also a long file name, which uses multiple FAT direntries", |
| "short", |
| "One more long filename, for good measure", |
| } |
| |
| // Create all files |
| for i := range filenames { |
| f := checkOpenFile(t, d, filenames[i], fs.OpenFlagCreate|fs.OpenFlagWrite|fs.OpenFlagFile) |
| checkClose(t, f) |
| } |
| |
| // Verify initial state |
| confirmDirectoryContents(d, filenames) |
| |
| for len(filenames) != 0 { |
| // Randomly pick one entry to remove until the directory is empty |
| i := rand.Intn(len(filenames)) |
| checkUnlink(t, d, filenames[i]) |
| filenames = append(filenames[:i], filenames[i+1:]...) |
| |
| // Confirm the directory still contains the rest of the filenames |
| confirmDirectoryContents(d, filenames) |
| } |
| |
| // When we're finished, the directory should be empty |
| checkDirectoryEmpty(t, d) |
| } |
| |
| doTest(root) |
| subdir := checkOpenDirectory(t, root, "subdir", fs.OpenFlagCreate|fs.OpenFlagRead|fs.OpenFlagWrite) |
| doTest(subdir) |
| |
| checkClose(t, subdir) |
| checkClose(t, root) |
| checkCloseFS(t, fatFS) |
| } |
| |
| glog.Info("Testing FAT32") |
| fileBackedFAT, dev := setupFAT32(t) |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| |
| glog.Info("Testing FAT16") |
| fileBackedFAT, dev = setupFAT16(t) |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| } |
| |
| // Test unmounting a filesystem with open files and directories. Verify that the file hierarchy is |
| // stored on disk when unmount is called while files are still open. |
| func TestUnmountWithOpenFiles(t *testing.T) { |
| doTest := func(dev block.Device) { |
| fatFS := checkNewFS(t, dev, fs.ReadWrite) |
| root := fatFS.RootDirectory() |
| |
| // /aaa/bbb/ccc/ddd.txt |
| // 'aaa' will have one ref, 'bbb' will have no refs, 'ccc' will have two refs, 'ddd' will |
| // have one ref. Additionally, there is an unlinked file which will not be saved. |
| flags := fs.OpenFlagCreate | fs.OpenFlagRead | fs.OpenFlagWrite |
| aaa := checkOpenDirectory(t, root, "aaa", flags) |
| bbb := checkOpenDirectory(t, aaa, "bbb", flags) |
| ccc := checkOpenDirectory(t, bbb, "ccc", flags) |
| checkOpenDirectory(t, bbb, "ccc", flags) |
| checkClose(t, bbb) |
| checkOpenFile(t, ccc, "ddd.txt", flags) |
| |
| // Close the filesystem before individually closing any file / directory |
| checkCloseFS(t, fatFS) |
| |
| // Re-open the filesystem, verify its structure |
| fatFS = checkNewFS(t, dev, fs.ReadOnly) |
| root = fatFS.RootDirectory() |
| |
| contents := checkReadDir(t, root, 2) |
| checkDirent(t, contents[0], ".", fs.FileTypeDirectory) |
| checkDirent(t, contents[1], "aaa", fs.FileTypeDirectory) |
| |
| aaa = checkOpenDirectory(t, root, "aaa", fs.OpenFlagRead) |
| contents = checkReadDir(t, aaa, 2) |
| checkDirent(t, contents[0], ".", fs.FileTypeDirectory) |
| checkDirent(t, contents[1], "bbb", fs.FileTypeDirectory) |
| |
| bbb = checkOpenDirectory(t, aaa, "bbb", fs.OpenFlagRead) |
| contents = checkReadDir(t, bbb, 2) |
| checkDirent(t, contents[0], ".", fs.FileTypeDirectory) |
| checkDirent(t, contents[1], "ccc", fs.FileTypeDirectory) |
| |
| ccc = checkOpenDirectory(t, bbb, "ccc", fs.OpenFlagRead) |
| contents = checkReadDir(t, ccc, 2) |
| checkDirent(t, contents[0], ".", fs.FileTypeDirectory) |
| checkDirent(t, contents[1], "ddd.txt", fs.FileTypeRegularFile) |
| |
| checkCloseFS(t, fatFS) |
| } |
| |
| glog.Info("Testing FAT32") |
| fileBackedFAT, dev := setupFAT32(t) |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| |
| glog.Info("Testing FAT16") |
| fileBackedFAT, dev = setupFAT16(t) |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| } |
| |
| // Test the file cursor when reading and writing to a file |
| func TestFilePositionValid(t *testing.T) { |
| doTest := func(dev block.Device) { |
| fatFS := checkNewFS(t, dev, fs.ReadWrite) |
| root := fatFS.RootDirectory() |
| |
| bufA := testutil.MakeRandomBuffer(1000) |
| bufB := testutil.MakeRandomBuffer(500) |
| readBuf := make([]byte, len(bufA)) |
| |
| // Start off writing from the start of the file |
| f := checkOpenFile(t, root, "foo", fs.OpenFlagCreate|fs.OpenFlagRead|fs.OpenFlagWrite) |
| checkWrite(t, f, bufA, 0, fs.WhenceFromCurrent) |
| if n := checkTell(t, f); n != int64(len(bufA)) { |
| t.Fatalf("Unexpected seek position: %d", n) |
| } |
| // Read the same buffer to verify the write |
| checkRead(t, f, readBuf, 0, fs.WhenceFromStart) |
| if !bytes.Equal(bufA, readBuf) { |
| t.Fatal("Read buffer did not equal write buffer") |
| } |
| |
| // The cursor already is at the end of the file. |
| // Try writing beyond the end of the file |
| extraOffset := int64(200) |
| checkWrite(t, f, bufB, extraOffset, fs.WhenceFromCurrent) |
| if n := checkTell(t, f); n != int64(len(bufA)+200+len(bufB)) { |
| t.Fatalf("Unexpected seek position: %d", n) |
| } |
| // Try reading the file using a negative offset |
| readBuf = make([]byte, len(bufA)+len(bufB)+int(extraOffset)) |
| checkRead(t, f, readBuf, int64(-len(readBuf)), fs.WhenceFromCurrent) |
| if !bytes.Equal(readBuf, append(append(bufA, make([]byte, extraOffset)...), bufB...)) { |
| t.Fatal("Read buffer did not equal write buffer") |
| } |
| |
| // Double check the seek position |
| if n := checkTell(t, f); n != int64(len(bufA)+200+len(bufB)) { |
| t.Fatalf("Unexpected seek position: %d", n) |
| } |
| |
| // Truncate the file |
| checkTruncate(t, f, uint64(len(bufA))) |
| // Observe that the size has changed |
| if size, _, _ := checkStat(t, f); size != int64(len(bufA)) { |
| t.Fatalf("Unexpected post-truncation size: %d", size) |
| } |
| // Observe that the seek position has not changed |
| if n := checkTell(t, f); n != int64(len(bufA)+200+len(bufB)) { |
| t.Fatalf("Unexpected seek position: %d", n) |
| } |
| |
| checkCloseFS(t, fatFS) |
| } |
| |
| glog.Info("Testing FAT32") |
| fileBackedFAT, dev := setupFAT32(t) |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| |
| glog.Info("Testing FAT16") |
| fileBackedFAT, dev = setupFAT16(t) |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| } |
| |
| // Test invalid operations on a file when the seek position is invalid |
| func TestFilePositionInvalid(t *testing.T) { |
| doTest := func(dev block.Device) { |
| fatFS := checkNewFS(t, dev, fs.ReadWrite) |
| root := fatFS.RootDirectory() |
| |
| buf := testutil.MakeRandomBuffer(100) |
| readBuf := make([]byte, 1) |
| |
| // Start off writing from the start of the file |
| f := checkOpenFile(t, root, "foo", fs.OpenFlagCreate|fs.OpenFlagRead|fs.OpenFlagWrite) |
| checkWrite(t, f, buf, 0, fs.WhenceFromCurrent) |
| if n := checkTell(t, f); n != int64(len(buf)) { |
| t.Fatalf("Unexpected seek position: %d", n) |
| } |
| |
| tryEmptyRead := func(off int64, whence int) { |
| n, err := f.Read(readBuf, off, whence) |
| if n != 0 || err != fs.ErrEOF { |
| t.Fatalf("Expected empty read, saw: %d bytes, err: %s", n, err) |
| } |
| } |
| tryEmptyRead(0, fs.WhenceFromCurrent) // Read from current position (end of file) |
| tryEmptyRead(0, fs.WhenceFromEnd) // Read from end of file |
| tryEmptyRead(5, fs.WhenceFromEnd) // Read from end of file + five bytes |
| tryEmptyRead(int64(len(buf)), fs.WhenceFromStart) // Read from start + size of file |
| |
| tryBadRead := func(off int64, whence int) { |
| n, err := f.Read(readBuf, off, whence) |
| if n != 0 || err != node.ErrBadArgument { |
| t.Fatalf("Expected bad read, saw: %d bytes, err: %s", n, err) |
| } |
| } |
| |
| tryBadRead(-1, fs.WhenceFromStart) // Read from start of file - 1 |
| tryBadRead(-int64(len(buf)+1), fs.WhenceFromEnd) // Read from end - (size of file + 1) |
| tryBadRead(-int64(len(buf)+1), fs.WhenceFromCurrent) // Read from current (end) - (size of file + 1) |
| |
| tryBadWrite := func(off int64, whence int) { |
| n, err := f.Write([]byte{'a'}, off, whence) |
| if n != 0 || err != node.ErrBadArgument { |
| t.Fatalf("Expected bad write, saw: %d bytes, err: %s", n, err) |
| } |
| } |
| |
| tryBadWrite(-1, fs.WhenceFromStart) // Write to start - 1 |
| tryBadWrite(-int64(len(buf)+1), fs.WhenceFromEnd) // Write to end - (size of file + 1) |
| tryBadWrite(-int64(len(buf)+1), fs.WhenceFromCurrent) // Write to current (end) - (size of file + 1) |
| |
| checkCloseFS(t, fatFS) |
| } |
| |
| glog.Info("Testing FAT32") |
| fileBackedFAT, dev := setupFAT32(t) |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| |
| glog.Info("Testing FAT16") |
| fileBackedFAT, dev = setupFAT16(t) |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| } |
| |
| // Test the effect of duplicating files and directories |
| func TestDup(t *testing.T) { |
| doTest := func(dev block.Device) { |
| fatFS := checkNewFS(t, dev, fs.ReadWrite) |
| root := fatFS.RootDirectory() |
| |
| buf := testutil.MakeRandomBuffer(100) |
| // Dup 'f' as 'f2'. Verify the seek position stays the same. |
| f := checkOpenFile(t, root, "foo", fs.OpenFlagCreate|fs.OpenFlagRead|fs.OpenFlagWrite) |
| f2 := checkDupFile(t, f) |
| checkWrite(t, f, buf, 0, fs.WhenceFromCurrent) |
| |
| if n := checkTell(t, f); n != int64(len(buf)) { |
| t.Fatalf("Unexpected seek position: %d", n) |
| } |
| if n := checkTell(t, f2); n != int64(len(buf)) { |
| t.Fatalf("Unexpected seek position: %d", n) |
| } |
| |
| // Move the seek position on 'f2', check that 'f' also moves. |
| checkSeek(t, f2, -50, fs.WhenceFromCurrent) |
| newPosition := int64(50) |
| if n := checkTell(t, f); n != newPosition { |
| t.Fatalf("Unexpected seek position: %d", n) |
| } |
| if n := checkTell(t, f2); n != newPosition { |
| t.Fatalf("Unexpected seek position: %d", n) |
| } |
| checkClose(t, f) |
| checkClose(t, f2) |
| |
| // Try Dup with a readonly file |
| f = checkOpenFile(t, root, "foo", fs.OpenFlagRead) |
| f2 = checkDupFile(t, f) |
| if _, err := f2.Write(buf, 0, fs.WhenceFromEnd); err != fs.ErrPermission { |
| t.Fatal("Expected permission error") |
| } |
| checkClose(t, f) |
| checkClose(t, f2) |
| |
| // Try Dup on a directory |
| d := checkOpenDirectory(t, root, "dir", fs.OpenFlagWrite|fs.OpenFlagRead|fs.OpenFlagCreate) |
| d2 := checkDupDir(t, d) |
| |
| // Verify that dup worked on a directory by writing to d... |
| f = checkOpenFile(t, d, "file", fs.OpenFlagWrite|fs.OpenFlagCreate) |
| checkClose(t, f) |
| |
| // ... but reading the result on d2. |
| rootContents := checkReadDir(t, d2, 2) |
| checkDirent(t, rootContents[0], ".", fs.FileTypeDirectory) |
| checkDirent(t, rootContents[1], "file", fs.FileTypeRegularFile) |
| |
| checkClose(t, d) |
| checkClose(t, d2) |
| checkCloseFS(t, fatFS) |
| } |
| |
| glog.Info("Testing FAT32") |
| fileBackedFAT, dev := setupFAT32(t) |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| |
| glog.Info("Testing FAT16") |
| fileBackedFAT, dev = setupFAT16(t) |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| } |
| |
| // Test access to a readonly filesystem |
| func TestReadonly(t *testing.T) { |
| doTest := func(dev block.Device) { |
| // First, create a filesystem as read-write, so we can create some files and directories. |
| fatFS := checkNewFS(t, dev, fs.ReadWrite) |
| root := fatFS.RootDirectory() |
| |
| // Create a file in root and write to it |
| foo := checkOpenFile(t, root, "foo", fs.OpenFlagWrite|fs.OpenFlagCreate) |
| fooContents := "This is the data inside file foo" |
| checkWrite(t, foo, []byte(fooContents), 0, fs.WhenceFromStart) |
| checkClose(t, foo) |
| |
| // Create a subdirectory |
| subdir := checkOpenDirectory(t, root, "subdir", fs.OpenFlagCreate|fs.OpenFlagRead|fs.OpenFlagWrite) |
| |
| // Create a file in the subdirectory and write to it |
| bar := checkOpenFile(t, subdir, "bar", fs.OpenFlagWrite|fs.OpenFlagCreate) |
| barContents := "This is different data inside file bar" |
| checkWrite(t, bar, []byte(barContents), 0, fs.WhenceFromStart) |
| checkClose(t, subdir) |
| checkClose(t, bar) |
| |
| // Close the filesystem |
| checkClose(t, root) |
| checkCloseFS(t, fatFS) |
| |
| // Reopen the filesystem as readonly |
| fatFS = checkNewFS(t, dev, fs.ReadOnly) |
| root = fatFS.RootDirectory() |
| |
| // Verify the structure of the filesystem |
| rootContents := checkReadDir(t, root, 3) |
| checkDirent(t, rootContents[0], ".", fs.FileTypeDirectory) |
| checkDirent(t, rootContents[1], "foo", fs.FileTypeRegularFile) |
| checkDirent(t, rootContents[2], "subdir", fs.FileTypeDirectory) |
| |
| subdir = checkOpenDirectory(t, root, "subdir", fs.OpenFlagRead) |
| subdirContents := checkReadDir(t, subdir, 2) |
| checkDirent(t, subdirContents[0], ".", fs.FileTypeDirectory) |
| checkDirent(t, subdirContents[1], "bar", fs.FileTypeRegularFile) |
| |
| // Verify the contents of the files. Verify that new writes fail |
| foo = checkOpenFile(t, root, "foo", fs.OpenFlagRead) |
| readBuf := make([]byte, len(fooContents)) |
| checkRead(t, foo, readBuf, 0, fs.WhenceFromStart) |
| if !bytes.Equal(readBuf, []byte(fooContents)) { |
| t.Fatal("File foo not the same when re-opened") |
| } else if _, err := foo.Write([]byte("new foo contents"), 0, fs.WhenceFromStart); err != fs.ErrPermission { |
| t.Fatal("Expected read only error, but saw: ", err) |
| } |
| checkClose(t, foo) |
| |
| bar = checkOpenFile(t, subdir, "bar", fs.OpenFlagRead) |
| readBuf = make([]byte, len(barContents)) |
| checkRead(t, bar, readBuf, 0, fs.WhenceFromStart) |
| if !bytes.Equal(readBuf, []byte(barContents)) { |
| t.Fatal("File bar not the same when re-opened") |
| } else if _, err := bar.Write([]byte("new bar contents"), 0, fs.WhenceFromStart); err != fs.ErrPermission { |
| t.Fatal("Expected read only errors, but saw: ", err) |
| } |
| checkClose(t, bar) |
| |
| // Confirm that "write" operations on root do not work |
| openFlags := fs.OpenFlagWrite | fs.OpenFlagCreate | fs.OpenFlagFile |
| if _, _, err := root.Open("newFile", openFlags); err != fs.ErrPermission { |
| t.Fatal("Expected read only error, but saw: ", err) |
| } |
| openFlags = fs.OpenFlagWrite | fs.OpenFlagCreate | fs.OpenFlagDirectory |
| if _, _, err := root.Open("newDirectory", openFlags); err != fs.ErrPermission { |
| t.Fatal("Expected read only error, but saw: ", err) |
| } |
| if err := root.Rename(root, "foo", "foo2"); err != fs.ErrPermission { |
| t.Fatal("Expected read only error, but saw: ", err) |
| } else if err := root.Unlink("foo"); err != fs.ErrPermission { |
| t.Fatal("Expected read only error, but saw: ", err) |
| } |
| |
| // Confirm that "write" operations on a subdirectory do not work |
| subdir = checkOpenDirectory(t, root, "subdir", fs.OpenFlagRead) |
| openFlags = fs.OpenFlagWrite | fs.OpenFlagCreate | fs.OpenFlagFile |
| if _, _, err := subdir.Open("newFile", openFlags); err != fs.ErrPermission { |
| t.Fatal("Expected read only error, but saw: ", err) |
| } |
| openFlags = fs.OpenFlagWrite | fs.OpenFlagCreate | fs.OpenFlagDirectory |
| if _, _, err := subdir.Open("newDirectory", openFlags); err != fs.ErrPermission { |
| t.Fatal("Expected read only error, but saw: ", err) |
| } |
| if err := subdir.Rename(subdir, "bar", "bar2"); err != fs.ErrPermission { |
| t.Fatal("Expected read only error, but saw: ", err) |
| } else if err := subdir.Unlink("bar"); err != fs.ErrPermission { |
| t.Fatal("Expected read only error, but saw: ", err) |
| } |
| |
| // Close the root, unmount the filesystem |
| checkClose(t, subdir) |
| checkClose(t, root) |
| checkCloseFS(t, fatFS) |
| } |
| |
| glog.Info("Testing FAT32 Readonly") |
| fileBackedFAT, dev := setupFAT32(t) |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| |
| glog.Info("Testing FAT16 Readonly") |
| fileBackedFAT, dev = setupFAT16(t) |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| } |
| |
| // Test creation of files and directories with invalid names |
| func TestBadNames(t *testing.T) { |
| doTest := func(dev block.Device) { |
| fatFS := checkNewFS(t, dev, fs.ReadWrite) |
| root := fatFS.RootDirectory() |
| |
| flags := fs.OpenFlagRead | fs.OpenFlagWrite | fs.OpenFlagCreate | fs.OpenFlagFile |
| if _, _, err := root.Open("this_filename\x00contains_null", flags); err == nil { |
| t.Fatal("Expected error") |
| } else if _, _, err := root.Open("this_filename\\contains_slash", flags); err == nil { |
| t.Fatal("Expected error") |
| } |
| |
| flags = fs.OpenFlagRead | fs.OpenFlagWrite | fs.OpenFlagCreate | fs.OpenFlagDirectory |
| if _, _, err := root.Open("dirname\x00with_null", flags); err == nil { |
| t.Fatal("Expected error") |
| } else if _, _, err := root.Open("dirname\\with_slash", flags); err == nil { |
| t.Fatal("Expected error") |
| } |
| |
| // Close the filesystem |
| checkClose(t, root) |
| checkCloseFS(t, fatFS) |
| } |
| |
| fileBackedFAT, dev := setupFAT32(t) |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| |
| fileBackedFAT, dev = setupFAT16(t) |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| } |
| |
| // Tests renaming to and from invalid targets |
| func TestRenameInvalid(t *testing.T) { |
| doTest := func(dev block.Device) { |
| fatFS := checkNewFS(t, dev, fs.ReadWrite) |
| root := fatFS.RootDirectory() |
| |
| renameTestsInDirectory := func(d fs.Directory) { |
| exclusiveCreateFlags := fs.OpenFlagRead | fs.OpenFlagWrite | fs.OpenFlagCreate | fs.OpenFlagExclusive |
| // Test invalid sources |
| if err := d.Rename(d, "source_that_doesn't_exist", "dst"); err != fs.ErrNotFound { |
| t.Fatal("Expected error; source doesn't exist") |
| } else if err := d.Rename(d, ".", "dst"); err != fs.ErrIsActive { |
| t.Fatal("Expected error; . does exist, but it should be busy") |
| } else if err := d.Rename(d, "..", "dst"); err != fs.ErrIsActive { |
| t.Fatal("Expected error; .. does exist, but it should be busy") |
| } |
| |
| // Test invalid destinations (with input file) |
| filename := "foo.txt" |
| foo := checkOpenFile(t, d, filename, exclusiveCreateFlags) |
| if err := d.Rename(d, filename, "."); err != fs.ErrIsActive { |
| t.Fatal("Expected error: . does exist, but it should be busy") |
| } else if err := d.Rename(d, filename, ".."); err != fs.ErrIsActive { |
| t.Fatal("Expected error: .. does exist, but it should be busy") |
| } else if err := d.Rename(d, filename, filename); err != fs.ErrIsActive { |
| t.Fatal("Expected error: file does exist, but it should be busy") |
| } else if err := d.Rename(d, filename, "target_parent_dir/does_not_exist"); err != fs.ErrNotFound { |
| t.Fatal("Expected error: source exists, but the target's parent directory does not") |
| } |
| checkClose(t, foo) |
| |
| // Test invalid destinations (with input directory) |
| dirname := "bar" |
| bar := checkOpenDirectory(t, d, dirname, exclusiveCreateFlags) |
| if err := d.Rename(d, dirname, "."); err != fs.ErrIsActive { |
| t.Fatal("Expected error; . does exist, but it should be busy") |
| } else if err := d.Rename(d, dirname, ".."); err != fs.ErrIsActive { |
| t.Fatal("Expected error; .. does exist, but it should be busy") |
| } else if err := d.Rename(d, dirname, dirname); err != fs.ErrIsActive { |
| t.Fatal("Expected error; directory does exist, but it should be busy") |
| } |
| |
| // Test renaming directory to target directory where target is not closed |
| overwriteName := "overwrite_me" |
| overwriteDir := checkOpenDirectory(t, d, overwriteName, exclusiveCreateFlags) |
| checkRename(t, d, dirname, overwriteName) |
| checkRename(t, d, overwriteName, dirname) |
| checkClose(t, overwriteDir) |
| |
| // Test renaming file to directory and vice-versa |
| if err := d.Rename(d, filename, dirname); err != fs.ErrNotADir { |
| t.Fatal("Expected error: Should not be able to rename a file to a directory") |
| } else if err := d.Rename(d, dirname, filename); err != fs.ErrNotADir { |
| t.Fatal("Expected error: Should not be able to rename a directory to a file") |
| } |
| |
| // Test cases of renaming a directory to a subdirectory of itself |
| subdirname := "baz" |
| baz := checkOpenDirectory(t, bar, subdirname, exclusiveCreateFlags) |
| checkClose(t, baz) |
| if err := bar.Rename(bar, subdirname, subdirname+"/blat"); err != fs.ErrInvalidArgs { |
| // bar/baz -> bar/baz/blat |
| t.Fatal("Expected error: Should not be able to make a directory a subdirectory of itself") |
| } else if err := bar.Rename(bar, subdirname, subdirname+"/blat/blah"); err != fs.ErrNotFound { |
| // bar/baz -> bar/baz/blat/blah |
| t.Fatal("Expected error: Subdirectory does not exist") |
| } else if err := bar.Rename(bar, subdirname, subdirname+"/blat"); err != fs.ErrInvalidArgs { |
| // bar/baz -> bar/baz/blat |
| t.Fatal("Expected error: Should not be able to make a directory a subdirectory of itself") |
| } else if err := bar.Rename(bar, subdirname, "./"+subdirname+"/./blat"); err != fs.ErrInvalidArgs { |
| // bar/baz -> bar/./baz/./blat |
| t.Fatal("Expected error: Should not be able to make a directory a subdirectory of itself") |
| } |
| checkClose(t, bar) |
| |
| // Test case where destination is non-empty directory |
| // bat -> bar, but bar contains baz |
| bat := checkOpenDirectory(t, d, "bat", exclusiveCreateFlags) |
| if err := d.Rename(d, "bat", dirname); err != fs.ErrNotEmpty { |
| t.Fatal("Expected error: Should not be able to (via rename) overwrite non-empty directory") |
| } |
| checkClose(t, bat) |
| checkUnlink(t, d, "bat") |
| |
| checkUnlink(t, d, filename) |
| checkUnlink(t, d, dirname+"/"+subdirname) |
| checkUnlink(t, d, dirname) |
| } |
| |
| // Test the failure cases inside the root directory |
| renameTestsInDirectory(root) |
| |
| // Test the failure cases when ".." refers to root |
| subDir := checkOpenDirectory(t, root, "subdir", fs.OpenFlagRead|fs.OpenFlagWrite|fs.OpenFlagCreate) |
| renameTestsInDirectory(subDir) |
| |
| // Test the failure cases when ".." refers to a non-root directory |
| subSubDir := checkOpenDirectory(t, subDir, "subsubdir", fs.OpenFlagRead|fs.OpenFlagWrite|fs.OpenFlagCreate) |
| renameTestsInDirectory(subSubDir) |
| |
| // Close the filesystem |
| checkClose(t, root) |
| checkCloseFS(t, fatFS) |
| } |
| |
| fileBackedFAT, dev := setupFAT32(t) |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| |
| fileBackedFAT, dev = setupFAT16(t) |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| } |
| |
| // Test simple cases of renaming: |
| // - Rename file to new location (non-overwrite) |
| // - Rename file to new location (overwrite) |
| // - Rename directory to new location (non-overwrite) |
| // - Rename directory to new location (overwrite) |
| func TestRenameSimple(t *testing.T) { |
| doTest := func(dev block.Device) { |
| fatFS := checkNewFS(t, dev, fs.ReadWrite) |
| root := fatFS.RootDirectory() |
| |
| exclusiveCreateFlags := fs.OpenFlagRead | fs.OpenFlagWrite | fs.OpenFlagCreate | fs.OpenFlagExclusive |
| renameTestsInDirectory := func(renameBaseDir fs.Directory, srcPfx, dstPfx string) { |
| checkRenameAndBack := func(src, target string) { |
| checkRename(t, renameBaseDir, src, target) |
| // Verify that this operation removed the original file |
| checkExists(t, renameBaseDir, target) |
| checkDoesNotExist(t, renameBaseDir, src) |
| // Rename back to the original file |
| checkRename(t, renameBaseDir, target, src) |
| checkExists(t, renameBaseDir, src) |
| checkDoesNotExist(t, renameBaseDir, target) |
| } |
| |
| // Test renaming a single file |
| srcName := srcPfx + "foo.txt" |
| targetName := dstPfx + "foo_renamed.txt" |
| foo := checkOpenFile(t, renameBaseDir, srcName, exclusiveCreateFlags) |
| |
| for i := 0; i < 2; i++ { |
| // Rename: File --> File that doesn't exist |
| checkExists(t, renameBaseDir, srcName) |
| checkDoesNotExist(t, renameBaseDir, targetName) |
| checkRenameAndBack(srcName, targetName) |
| |
| // Rename: File --> Files that DOES exist |
| targetName = dstPfx + "overwrite_me.txt" |
| overwriteMe := checkOpenFile(t, renameBaseDir, targetName, exclusiveCreateFlags) |
| checkExists(t, renameBaseDir, srcName) |
| checkExists(t, renameBaseDir, targetName) |
| checkRenameAndBack(srcName, targetName) |
| // ... Even with all this renaming, the overwritten file should still be closeable |
| checkClose(t, overwriteMe) |
| |
| if i == 0 { |
| // Try these operations, once with the file open, and once with the file closed. |
| checkClose(t, foo) |
| } |
| } |
| checkUnlink(t, renameBaseDir, srcName) |
| |
| // Test renaming a single directory |
| srcName = srcPfx + "bar" |
| targetName = dstPfx + "bar_renamed" |
| bar := checkOpenDirectory(t, renameBaseDir, srcName, exclusiveCreateFlags) |
| |
| for i := 0; i < 2; i++ { |
| // Rename: Dir --> Dir that doesn't exist |
| checkExists(t, renameBaseDir, srcName) |
| checkDoesNotExist(t, renameBaseDir, targetName) |
| checkRenameAndBack(srcName, targetName) |
| |
| // Rename: Dir --> Dir that DOES exist, is closed, and is empty. |
| targetName = dstPfx + "overwrite_me" |
| checkClose(t, checkOpenDirectory(t, renameBaseDir, targetName, exclusiveCreateFlags)) |
| checkExists(t, renameBaseDir, srcName) |
| checkExists(t, renameBaseDir, targetName) |
| checkRenameAndBack(srcName, targetName) |
| if i == 0 { |
| // Try these operations, once with the directory open, and once with the |
| // directory closed. |
| checkClose(t, bar) |
| } |
| } |
| |
| // Rename: Dir --> Dir that DOES exist, is NOT closed, and is empty. |
| targetDir := checkOpenDirectory(t, renameBaseDir, targetName, exclusiveCreateFlags) |
| checkExists(t, renameBaseDir, srcName) |
| checkExists(t, renameBaseDir, targetName) |
| checkRenameAndBack(srcName, targetName) |
| // Target should exist, but shouldn't be writable (it no longer has a name) |
| targetContents := checkReadDir(t, targetDir, 1) |
| checkDirent(t, targetContents[0], ".", fs.FileTypeDirectory) |
| if _, _, err := targetDir.Open("foo", exclusiveCreateFlags|fs.OpenFlagFile); err != fs.ErrFailedPrecondition { |
| t.Fatal("Expected error writing to deleted dir, but saw: ", err) |
| } |
| checkClose(t, targetDir) |
| |
| checkUnlink(t, renameBaseDir, srcName) |
| } |
| |
| renameTestsInDirectory(root, "", "") |
| |
| subDir := checkOpenDirectory(t, root, "subdir", exclusiveCreateFlags) |
| renameTestsInDirectory(subDir, "", "") |
| renameTestsInDirectory(root, "subdir/", "") |
| renameTestsInDirectory(root, "", "subdir/") |
| |
| subSubDir := checkOpenDirectory(t, subDir, "subsubdir", exclusiveCreateFlags) |
| renameTestsInDirectory(subDir, "", "") |
| renameTestsInDirectory(subDir, "subsubdir/", "") |
| renameTestsInDirectory(subDir, "", "subsubdir/") |
| renameTestsInDirectory(root, "subdir/subsubdir/", "subdir/") |
| renameTestsInDirectory(root, "subdir/", "subdir/subsubdir/") |
| |
| checkClose(t, subDir) |
| checkClose(t, subSubDir) |
| |
| // Close the filesystem |
| checkClose(t, root) |
| checkClose(t, fatFS) |
| } |
| |
| fileBackedFAT, dev := setupFAT32(t) |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| |
| fileBackedFAT, dev = setupFAT16(t) |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| } |
| |
| // Test renaming between directories. |
| // This may seem like a somewhat contrived case (double-open + rename) but |
| // it is intended to prevent a regression against a real refcounting bug that |
| // has occurred in the past. |
| func TestRenameInterDirectory(t *testing.T) { |
| doTest := func(dev block.Device) { |
| fatFS := checkNewFS(t, dev, fs.ReadWrite) |
| root := fatFS.RootDirectory() |
| |
| // Verify that moving a "twice-opened-file" into a new directory preserves |
| // the refcount upon close. |
| exclusiveCreateFlags := fs.OpenFlagRead | fs.OpenFlagWrite | fs.OpenFlagCreate | fs.OpenFlagExclusive |
| subDir := checkOpenDirectory(t, root, "subdir", exclusiveCreateFlags) |
| dstfile := checkOpenFile(t, subDir, "srcfile", exclusiveCreateFlags) |
| checkClose(t, dstfile) |
| checkClose(t, subDir) |
| |
| srcfile := checkOpenFile(t, root, "srcfile", exclusiveCreateFlags) |
| srcfile2 := checkOpenFile(t, root, "srcfile", fs.OpenFlagRead) |
| checkRename(t, root, "srcfile", "subdir/srcfile") |
| checkClose(t, srcfile) |
| checkClose(t, srcfile2) |
| |
| checkUnlink(t, root, "subdir/srcfile") |
| checkUnlink(t, root, "subdir") |
| |
| // Verify the same thing for a "twice-opened-directory". |
| |
| subDir = checkOpenDirectory(t, root, "subdir", exclusiveCreateFlags) |
| dstDir := checkOpenDirectory(t, subDir, "src", exclusiveCreateFlags) |
| checkClose(t, dstDir) |
| checkClose(t, subDir) |
| |
| srcdir := checkOpenDirectory(t, root, "src", exclusiveCreateFlags) |
| srcdir2 := checkOpenDirectory(t, root, "src", fs.OpenFlagRead) |
| checkRename(t, root, "src", "subdir/src") |
| checkClose(t, srcdir) |
| checkClose(t, srcdir2) |
| |
| checkUnlink(t, root, "subdir/src") |
| checkUnlink(t, root, "subdir") |
| |
| // Close the filesystem |
| checkClose(t, root) |
| checkClose(t, fatFS) |
| } |
| |
| fileBackedFAT, dev := setupFAT32(t) |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| |
| fileBackedFAT, dev = setupFAT16(t) |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| } |
| |
| // Test simple cases of unlinking: |
| // - Unlinking files and directories which are closed |
| // - Unlinking files and directories which are open |
| // - Accessing files after they have been unlinked |
| // - Failing to unlink non-empty directories |
| func TestUnlinkSimple(t *testing.T) { |
| testFileRemovalSimple := func(d fs.Directory, filename string) { |
| // Create a new file |
| foo := checkOpenFile(t, d, filename, fs.OpenFlagWrite|fs.OpenFlagCreate|fs.OpenFlagExclusive) |
| |
| // Write to the file |
| fooContents := "This is the data inside file foo" |
| checkWrite(t, foo, []byte(fooContents), 0, fs.WhenceFromStart) |
| |
| // Close the file |
| checkClose(t, foo) |
| checkDirectoryContains(t, d, filename, fs.FileTypeRegularFile, 2) |
| |
| // Unlink the file |
| checkUnlink(t, d, filename) |
| checkDirectoryEmpty(t, d) |
| checkDoesNotExist(t, d, filename) |
| } |
| |
| testFileUseAfterUnlink := func(d fs.Directory, filename string) { |
| // Create a new file |
| foo := checkOpenFile(t, d, filename, fs.OpenFlagWrite|fs.OpenFlagCreate|fs.OpenFlagExclusive) |
| |
| // Write to the file |
| fooContents := "This is the data inside file foo" |
| checkWrite(t, foo, []byte(fooContents), 0, fs.WhenceFromStart) |
| |
| // Unlink the file |
| checkUnlink(t, d, filename) |
| checkDirectoryEmpty(t, d) |
| checkDoesNotExist(t, d, filename) |
| |
| // Write to the file again, after being unlinked |
| fooContents = "Hang on, let me change the contents of file foo" |
| checkWrite(t, foo, []byte(fooContents), 0, fs.WhenceFromStart) |
| checkDirectoryEmpty(t, d) |
| |
| // Close the file |
| checkClose(t, foo) |
| checkDirectoryEmpty(t, d) |
| checkDoesNotExist(t, d, filename) |
| } |
| |
| testDirectoryRemoval := func(d fs.Directory, subdirname, subfilename string) { |
| // Create a new subdirectory, and create a file within that subdirectory |
| subdir := checkOpenDirectory(t, d, subdirname, fs.OpenFlagWrite|fs.OpenFlagRead|fs.OpenFlagCreate|fs.OpenFlagExclusive) |
| subfile := checkOpenFile(t, subdir, subfilename, fs.OpenFlagWrite|fs.OpenFlagCreate|fs.OpenFlagExclusive) |
| |
| // Verify the parent directory contains the subd, and the subdir contains the subfile |
| checkDirectoryContains(t, d, subdirname, fs.FileTypeDirectory, 2) |
| checkDirectoryContains(t, subdir, subfilename, fs.FileTypeRegularFile, 2) |
| |
| // Try (and fail) to unlink the subdirectory. Verify nothing was removed |
| checkClose(t, subdir) |
| if err := d.Unlink(subdirname); err != fs.ErrNotEmpty { |
| t.Fatal("Expected error ErrNotEmpty, saw: ", err) |
| } |
| subdir = checkOpenDirectory(t, d, subdirname, fs.OpenFlagWrite|fs.OpenFlagRead) |
| |
| checkDirectoryContains(t, d, subdirname, fs.FileTypeDirectory, 2) |
| checkDirectoryContains(t, subdir, subfilename, fs.FileTypeRegularFile, 2) |
| |
| // Try (and succeed) at removing the subfile |
| checkUnlink(t, subdir, subfilename) |
| checkDoesNotExist(t, subdir, subfilename) |
| checkDirectoryContains(t, d, subdirname, fs.FileTypeDirectory, 2) |
| checkDirectoryEmpty(t, subdir) |
| |
| // Try (and succeed) to unlink the subdirectory |
| checkDirectoryContains(t, d, subdirname, fs.FileTypeDirectory, 2) |
| checkUnlink(t, d, subdirname) |
| checkDirectoryEmpty(t, d) |
| checkDirectoryEmpty(t, subdir) |
| checkClose(t, subdir) |
| |
| // Clean up the subfile, which was unlinked a while ago |
| checkClose(t, subfile) |
| } |
| |
| doTest := func(dev block.Device) { |
| fatFS := checkNewFS(t, dev, fs.ReadWrite) |
| root := fatFS.RootDirectory() |
| |
| // Try running tests with different file/directory names, so we can test a variable number |
| // of direntries to be deleted. |
| testFileRemovalSimple(root, "foo.txt") |
| testFileRemovalSimple(root, "foooooooobar is a long name.txt") |
| testFileRemovalSimple(root, "FOO.TXT") |
| testFileUseAfterUnlink(root, "foo.txt") |
| testFileUseAfterUnlink(root, "foooooooobar is a long name.txt") |
| testFileUseAfterUnlink(root, "FOO.TXT") |
| testDirectoryRemoval(root, "subdir", "foo.txt") |
| testDirectoryRemoval(root, "long subdirectory name", "foooooooobar is a long name.txt") |
| testDirectoryRemoval(root, "SUBDIR", "FOO.TXT") |
| |
| subdir := checkOpenDirectory(t, root, "test_subdirectory", fs.OpenFlagRead|fs.OpenFlagWrite|fs.OpenFlagCreate) |
| |
| testFileRemovalSimple(subdir, "foo.txt") |
| testFileRemovalSimple(subdir, "foooooooobar is a long name.txt") |
| testFileRemovalSimple(subdir, "FOO.TXT") |
| testFileUseAfterUnlink(subdir, "foo.txt") |
| testFileUseAfterUnlink(subdir, "foooooooobar is a long name.txt") |
| testFileUseAfterUnlink(subdir, "FOO.TXT") |
| testDirectoryRemoval(subdir, "subdir", "foo.txt") |
| testDirectoryRemoval(subdir, "long subdirectory name", "foooooooobar is a long name.txt") |
| testDirectoryRemoval(subdir, "SUBDIR", "FOO.TXT") |
| |
| // Close the filesystem |
| checkClose(t, root) |
| checkCloseFS(t, fatFS) |
| } |
| |
| fileBackedFAT, dev := setupFAT32(t) |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| |
| fileBackedFAT, dev = setupFAT16(t) |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| } |
| |
| // Test cases of unlink where an error is expected |
| func TestUnlinkInvalid(t *testing.T) { |
| doTest := func(dev block.Device) { |
| fatFS := checkNewFS(t, dev, fs.ReadWrite) |
| root := fatFS.RootDirectory() |
| |
| if err := root.Unlink("/"); err != fs.ErrInvalidArgs { |
| t.Fatal(err) |
| t.Fatal("Expected error unlinking root") |
| } else if err := root.Unlink("."); err != fs.ErrIsActive { |
| t.Fatal("Expected error unlinking root") |
| } else if err := root.Unlink(".."); err != fs.ErrIsActive { |
| t.Fatal("Expected error unlinking root") |
| } |
| |
| subdir := checkOpenDirectory(t, root, "test_subdirectory", fs.OpenFlagRead|fs.OpenFlagWrite|fs.OpenFlagCreate) |
| |
| if err := subdir.Unlink("."); err != fs.ErrIsActive { |
| t.Fatal("Expected error unlinking subdirectory") |
| } else if err := subdir.Unlink(".."); err != fs.ErrIsActive { |
| t.Fatal("Expected error unlinking subdirectory") |
| } else if err := subdir.Unlink("foo"); err != fs.ErrNotFound { |
| t.Fatal("Expected error unlinking missing file") |
| } |
| |
| // Close the filesystem |
| checkClose(t, root) |
| checkCloseFS(t, fatFS) |
| } |
| |
| fileBackedFAT, dev := setupFAT32(t) |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| |
| fileBackedFAT, dev = setupFAT16(t) |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| } |
| |
| // Test observing and modifying the "last modified time" of files and directories |
| func TestTime(t *testing.T) { |
| doTest := func(dev block.Device) { |
| fatFS := checkNewFS(t, dev, fs.ReadWrite) |
| root := fatFS.RootDirectory() |
| |
| // Test creating / stat-ing / touching a node (file or directory) |
| testNode := func(flags fs.OpenFlags) { |
| name := "test_node" |
| n := checkOpen(t, root, name, flags|fs.OpenFlagRead|fs.OpenFlagWrite|fs.OpenFlagCreate) |
| _, _, timeCreate := checkStat(t, n) |
| if timeCreate.Unix() < time.Now().Unix()-2 || timeCreate.Unix() > time.Now().Unix() { |
| t.Fatal("File should have been made in the last couple seconds") |
| } |
| |
| // FAT timestamps have a granularity of 2 seconds. Wait for 3 second to make it likely that |
| // the timestamp will be updated. |
| time.Sleep(3 * time.Second) |
| touchTime := time.Now() |
| checkTouch(t, n, touchTime, touchTime) |
| _, _, timeUpdate := checkStat(t, n) |
| if timeUpdate.Unix() <= timeCreate.Unix() { |
| t.Fatal("Expected time to be updated after touch") |
| } |
| |
| // Try closing and re-opening the directory to verify that timestamp was saved to disk. |
| checkClose(t, n) |
| n = checkOpen(t, root, name, flags|fs.OpenFlagRead) |
| _, _, timeReopen := checkStat(t, n) |
| // When the "timeUpdate" gets written to disk, it only has two-second granularity. |
| timeUpdateDisk := timeUpdate.Unix() |
| if timeUpdateDisk%2 != 0 { |
| timeUpdateDisk-- |
| } |
| if timeUpdateDisk != timeReopen.Unix() { |
| t.Fatal("Expected time to be updated after re-opening file") |
| } |
| |
| checkClose(t, n) |
| checkUnlink(t, root, name) |
| } |
| |
| testNode(fs.OpenFlagFile) |
| // This test would fail for directories, but that is the expected behavior of common FAT |
| // filesystems. |
| // To quote "File System Forensic Analysis", Chapter 9: FAT Concepts and Analysis, page 235: |
| // "For directories... the dates were set when the directory was created and were not |
| // updated much after that. Even when new clusters were allocated for the directory or |
| // new files were created in the directory, the written times were not updated" |
| |
| // Close the filesystem |
| checkClose(t, root) |
| checkCloseFS(t, fatFS) |
| } |
| |
| fileBackedFAT, dev := setupFAT32(t) |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| |
| fileBackedFAT, dev = setupFAT16(t) |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| } |
| |
| // Test using files and directories after they are closed. |
| func TestUseAfterClose(t *testing.T) { |
| checkClosedFileOps := func(f fs.File, goldErr error) { |
| buf := []byte{'a'} |
| if err := f.Close(); err != goldErr { |
| t.Fatalf("Expected %s error, saw: %s", goldErr, err) |
| } else if _, _, _, err := f.Stat(); err != goldErr { |
| t.Fatalf("Expected %s error, saw: %s", goldErr, err) |
| } else if _, err := f.Dup(); err != goldErr { |
| t.Fatalf("Expected %s error, saw: %s", goldErr, err) |
| } else if _, err := f.Reopen(fs.OpenFlagRead); err != goldErr { |
| t.Fatalf("Expected %s error, saw: %s", goldErr, err) |
| } else if _, err := f.Read(buf, 0, fs.WhenceFromStart); err != goldErr { |
| t.Fatalf("Expected %s error, saw: %s", goldErr, err) |
| } else if _, err := f.Write(buf, 0, fs.WhenceFromStart); err != goldErr { |
| t.Fatalf("Expected %s error, saw: %s", goldErr, err) |
| } else if err := f.Truncate(0); err != goldErr { |
| t.Fatalf("Expected %s error, saw: %s", goldErr, err) |
| } else if _, err := f.Tell(); err != goldErr { |
| t.Fatalf("Expected %s error, saw: %s", goldErr, err) |
| } else if _, err := f.Seek(0, fs.WhenceFromStart); err != goldErr { |
| t.Fatalf("Expected %s error, saw: %s", goldErr, err) |
| } |
| } |
| checkClosedDirOps := func(d fs.Directory, goldErr error) { |
| if err := d.Close(); err != goldErr { |
| t.Fatalf("Expected %s error, saw: %s", goldErr, err) |
| } else if _, _, _, err := d.Stat(); err != goldErr { |
| t.Fatalf("Expected %s error, saw: %s", goldErr, err) |
| } else if err := d.Touch(time.Now(), time.Now()); err != goldErr { |
| t.Fatalf("Expected %s error, saw: %s", goldErr, err) |
| } else if _, err := d.Dup(); err != goldErr { |
| t.Fatalf("Expected %s error, saw: %s", goldErr, err) |
| } else if _, err := d.Reopen(fs.OpenFlagRead); err != goldErr { |
| t.Fatalf("Expected %s error, saw: %s", goldErr, err) |
| } else if _, err := d.Read(); err != goldErr { |
| t.Fatalf("Expected %s error, saw: %s", goldErr, err) |
| } else if _, _, err := d.Open("foo", fs.OpenFlagRead); err != goldErr { |
| t.Fatalf("Expected %s error, saw: %s", goldErr, err) |
| } else if err := d.Rename(d, "foo", "bar"); err != goldErr { |
| t.Fatalf("Expected %s error, saw: %s", goldErr, err) |
| } else if err := d.Sync(); err != goldErr { |
| t.Fatalf("Expected %s error, saw: %s", goldErr, err) |
| } else if err := d.Unlink("foo"); err != goldErr { |
| t.Fatalf("Expected %s error, saw: %s", goldErr, err) |
| } |
| } |
| |
| doTest := func(dev block.Device) { |
| fatFS := checkNewFS(t, dev, fs.ReadWrite) |
| root := fatFS.RootDirectory() |
| |
| foo := checkOpenFile(t, root, "foo", fs.OpenFlagWrite|fs.OpenFlagCreate) |
| fooContents := "This is the data inside file foo" |
| checkWrite(t, foo, []byte(fooContents), 0, fs.WhenceFromStart) |
| // Close / Verify foo's closed state |
| checkClose(t, foo) |
| checkClosedFileOps(foo, fs.ErrNotOpen) |
| |
| subdir := checkOpenDirectory(t, root, "subdir", fs.OpenFlagCreate|fs.OpenFlagRead|fs.OpenFlagWrite) |
| bar := checkOpenFile(t, subdir, "bar", fs.OpenFlagWrite|fs.OpenFlagCreate) |
| barContents := "This is different data inside file bar" |
| checkWrite(t, bar, []byte(barContents), 0, fs.WhenceFromStart) |
| |
| // Close / Verify subdir and bar's closed state |
| checkClose(t, subdir) |
| checkClosedDirOps(subdir, fs.ErrNotOpen) |
| checkClose(t, bar) |
| checkClosedFileOps(bar, fs.ErrNotOpen) |
| |
| // Re-open all files / directories |
| foo = checkOpenFile(t, root, "foo", fs.OpenFlagRead) |
| subdir = checkOpenDirectory(t, root, "subdir", fs.OpenFlagRead) |
| bar = checkOpenFile(t, subdir, "bar", fs.OpenFlagRead) |
| |
| // Close the filesystem, verify the root is closed |
| checkClose(t, root) |
| checkClosedDirOps(root, fs.ErrNotOpen) |
| checkCloseFS(t, fatFS) |
| |
| // Verify that all files / directories are unmounted and inaccessible |
| checkClosedFileOps(foo, fs.ErrUnmounted) |
| checkClosedFileOps(bar, fs.ErrUnmounted) |
| checkClosedDirOps(subdir, fs.ErrUnmounted) |
| checkClosedDirOps(root, fs.ErrUnmounted) |
| } |
| |
| glog.Info("Testing FAT32") |
| fileBackedFAT, dev := setupFAT32(t) |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| |
| glog.Info("Testing FAT16") |
| fileBackedFAT, dev = setupFAT16(t) |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| } |
| |
| // Test simple path traversal in a non-changing directory structure. |
| func TestPathTraversalStatic(t *testing.T) { |
| doTest := func(dev block.Device) { |
| fatFS := checkNewFS(t, dev, fs.ReadWrite) |
| root := fatFS.RootDirectory() |
| |
| rootContents := checkReadDir(t, root, 1) |
| checkDirent(t, rootContents[0], ".", fs.FileTypeDirectory) |
| |
| // Make a subdirectory, Verify that the new directory is empty. |
| flags := fs.OpenFlagCreate | fs.OpenFlagWrite | fs.OpenFlagRead | fs.OpenFlagExclusive |
| foo := checkOpenDirectory(t, root, "foo", flags) |
| fiz := checkOpenDirectory(t, root, "foo/fiz", flags) |
| fizFile := checkOpenFile(t, root, "foo/fiz/file.txt", flags) |
| checkClose(t, fizFile) |
| bar := checkOpenDirectory(t, root, "foo/bar", flags) |
| baz := checkOpenDirectory(t, root, "foo/bar/baz", flags) |
| |
| // Verify the directory structure has been created |
| checkValidRoot := func(d fs.Directory) { |
| contents := checkReadDir(t, d, 2) |
| checkDirent(t, contents[0], ".", fs.FileTypeDirectory) |
| checkDirent(t, contents[1], "foo", fs.FileTypeDirectory) |
| } |
| checkValidRoot(root) |
| |
| checkValidFoo := func(d fs.Directory) { |
| contents := checkReadDir(t, d, 3) |
| checkDirent(t, contents[0], ".", fs.FileTypeDirectory) |
| checkDirent(t, contents[1], "fiz", fs.FileTypeDirectory) |
| checkDirent(t, contents[2], "bar", fs.FileTypeDirectory) |
| } |
| checkValidFoo(foo) |
| |
| checkValidFiz := func(d fs.Directory) { |
| contents := checkReadDir(t, d, 2) |
| checkDirent(t, contents[0], ".", fs.FileTypeDirectory) |
| checkDirent(t, contents[1], "file.txt", fs.FileTypeRegularFile) |
| } |
| checkValidFiz(fiz) |
| |
| checkValidBar := func(d fs.Directory) { |
| contents := checkReadDir(t, d, 2) |
| checkDirent(t, contents[0], ".", fs.FileTypeDirectory) |
| checkDirent(t, contents[1], "baz", fs.FileTypeDirectory) |
| } |
| checkValidBar(bar) |
| |
| checkValidBaz := func(d fs.Directory) { |
| contents := checkReadDir(t, d, 1) |
| checkDirent(t, contents[0], ".", fs.FileTypeDirectory) |
| } |
| checkValidBaz(baz) |
| |
| dir := checkOpenDirectory(t, root, ".", fs.OpenFlagRead) // In root, open self |
| checkValidRoot(dir) |
| checkClose(t, dir) |
| |
| dir = checkOpenDirectory(t, foo, ".", fs.OpenFlagRead) // In "/foo", open foo |
| checkValidFoo(dir) |
| checkClose(t, dir) |
| |
| dir = checkOpenDirectory(t, foo, "bar/baz", fs.OpenFlagRead) // In "/foo", open baz |
| checkValidBaz(dir) |
| checkClose(t, dir) |
| |
| dir = checkOpenDirectory(t, foo, "fiz", fs.OpenFlagRead) // In "/foo", open fiz |
| checkValidFiz(dir) |
| checkClose(t, dir) |
| |
| dir = checkOpenDirectory(t, foo, ".///././", fs.OpenFlagRead) // In "/foo", open "foo" |
| checkValidFoo(dir) |
| checkClose(t, dir) |
| |
| // Close all the original copies of the directories we had open |
| checkClose(t, foo) |
| checkClose(t, fiz) |
| checkClose(t, bar) |
| checkClose(t, baz) |
| checkClose(t, root) |
| checkCloseFS(t, fatFS) |
| } |
| |
| fileBackedFAT, dev := setupFAT32(t) |
| glog.Info("FAT32") |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| |
| fileBackedFAT, dev = setupFAT16(t) |
| glog.Info("FAT16") |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| } |
| |
| func deleteAllInDirectory(d fs.Directory, path string, maxNumToDelete int) (numDeleted int) { |
| // Read the contents of a directory |
| entries, err := d.Read() |
| if err != nil { |
| panic(err) |
| } |
| // Try to unlink any non "." and non ".." entries |
| for i := range entries { |
| if maxNumToDelete == 0 { |
| return |
| } else if entries[i].GetName() != "." && entries[i].GetName() != ".." { |
| // Do NOT make an assumption about the type of the entry. It may be deleted / recreated |
| // by another thread. |
| err := d.Unlink(entries[i].GetName()) |
| if err == nil { |
| // We successfully deleted a file / directory |
| numDeleted++ |
| maxNumToDelete-- |
| continue |
| } else if err == fs.ErrNotFound { |
| // Someone else deleted the file / directory |
| continue |
| } else if err == fs.ErrNotEmpty { |
| // If the directory is not empty, try to delete its contents |
| if _, subdir, _ := d.Open(entries[i].GetName(), fs.OpenFlagRead|fs.OpenFlagDirectory); subdir != nil { |
| subDeleted := deleteAllInDirectory(subdir, path+entries[i].GetName()+"/", maxNumToDelete) |
| numDeleted += subDeleted |
| maxNumToDelete -= subDeleted |
| if err := subdir.Close(); err != nil { |
| panic(err) |
| } |
| } |
| } else if err != fs.ErrIsActive { |
| panic(err) |
| } |
| } |
| } |
| return |
| } |
| |
| // Used to generate random file / directory names |
| func randomName() string { |
| letters := []rune("abcdefABCDEF01234") |
| b := make([]rune, 1+rand.Intn(10)) |
| for i := range b { |
| b[i] = letters[rand.Intn(len(letters))] |
| } |
| return string(b) |
| } |
| |
| const ( |
| choiceCreateFile = iota // Create a file and immediately close it |
| choiceCreateDir // Create a directory and immediately close it |
| choiceCreateDirEnter // Create a directory and enter it |
| numChoices |
| ) |
| |
| func createInDirectory(d fs.Directory, path string, maxNumToCreate int) (numCreated int) { |
| if maxNumToCreate == 0 { |
| return |
| } |
| choice := rand.Intn(numChoices) |
| name := randomName() |
| switch choice { |
| case choiceCreateFile: |
| n, _, err := d.Open(name, fs.OpenFlagWrite|fs.OpenFlagFile|fs.OpenFlagCreate|fs.OpenFlagExclusive) |
| if err == fs.ErrNotAFile || err == fs.ErrAlreadyExists { |
| // 'name' already exists |
| return |
| } else if err == fs.ErrNoSpace { |
| // Disk (or directory) space is full |
| return |
| } else if err != nil { |
| panic(err) |
| } else if err := n.Close(); err != nil { |
| panic(err) |
| } |
| numCreated++ |
| case choiceCreateDir, choiceCreateDirEnter: |
| _, n, err := d.Open(name, fs.OpenFlagRead|fs.OpenFlagWrite|fs.OpenFlagDirectory|fs.OpenFlagCreate|fs.OpenFlagExclusive) |
| if err == fs.ErrNotADir || err == fs.ErrAlreadyExists { |
| // 'name' already exists |
| return |
| } else if err == fs.ErrNoSpace { |
| // Disk (or directory) space is full |
| return |
| } else if err != nil { |
| panic(err) |
| } |
| |
| if choice == choiceCreateDirEnter { |
| numCreated += createInDirectory(n, path+name+"/", maxNumToCreate-1) |
| } |
| |
| if err := n.Close(); err != nil { |
| panic(err) |
| } |
| numCreated++ |
| } |
| return |
| } |
| |
| func TestConcurrentOpenDelete(t *testing.T) { |
| doTest := func(dev block.Device) { |
| rand.Seed(time.Now().Unix()) |
| fatFS := checkNewFS(t, dev, fs.ReadWrite) |
| root := fatFS.RootDirectory() |
| |
| done := make(chan bool) |
| // A deleter thread reads a directory and attempts to unlink files |
| deleter := func(numToDelete int) { |
| for numToDelete > 0 { |
| numToDelete -= deleteAllInDirectory(root, "/", numToDelete) |
| } |
| done <- true |
| } |
| |
| // A creator thread randomly makes files and directories |
| creator := func(numToCreate int) { |
| for numToCreate > 0 { |
| delta := createInDirectory(root, "/", numToCreate) |
| numToCreate -= delta |
| if delta == 0 { |
| // Purely for performance: avoid churning when root / filesystem is full |
| time.Sleep(10 * time.Microsecond) |
| } |
| } |
| done <- true |
| } |
| |
| n := 1000 |
| numCreators := 5 |
| numDeleters := 5 |
| for i := 0; i < numCreators; i++ { |
| go creator(n) |
| } |
| for i := 0; i < numDeleters; i++ { |
| go deleter(n) |
| } |
| for i := 0; i < numCreators+numDeleters; i++ { |
| <-done |
| } |
| |
| checkCloseFS(t, fatFS) |
| } |
| |
| fileBackedFAT, dev := setupFAT32(t) |
| glog.Info("FAT32") |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| |
| fileBackedFAT, dev = setupFAT16(t) |
| glog.Info("FAT16") |
| doTest(dev) |
| cleanup(fileBackedFAT, dev) |
| } |