blob: 86349c4174ff0e9f0b48bb054390b771810c3335 [file] [log] [blame]
// 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 (
"strings"
"thinfs/fs"
"thinfs/fs/msdosfs/direntry"
"thinfs/fs/msdosfs/node"
)
// Given a path "foo/bar/baz.txt", this function with return a node pointing to "bar" and a string
// containing "baz.txt".
//
// Precondition:
// - no node locks are held by the caller
// Postcondition:
// - no node locks are held by the caller
// - If successful, the opened parent node is ACQUIRED in the dcache
func traversePath(n node.DirectoryNode, path string) (parent node.DirectoryNode, name string, isDir bool, err error) {
if len(path) == 0 || path[0] == '/' {
// Empty and absolute paths are both disallowed
return nil, "", false, fs.ErrInvalidArgs
}
// Break the path into components. Remove extraneous "/" characters.
pathComponents := strings.Split(path, "/")
var temp []string
for i := range pathComponents {
if pathComponents[i] != "" {
temp = append(temp, pathComponents[i])
}
}
pathComponents = temp
oldDirectoryID := int64(-1)
for i := 0; i < len(pathComponents)-1; i++ {
component := pathComponents[i]
if component == "." {
// In this case, "n" stays the same.
} else if component == ".." {
// Note that this is Fuchsia-specific behavior for '..', where server-side '..'
// is disallowed
return nil, "", false, fs.ErrNotSupported
} else {
n.RLock()
newDir, err := traverseDirectory(n, component, fs.OpenFlagRead|fs.OpenFlagDirectory)
n.RUnlock()
if oldDirectoryID != -1 {
n.Metadata().Dcache.Release(uint32(oldDirectoryID))
}
if err != nil {
return nil, "", false, err
}
n = newDir
oldDirectoryID = int64(n.ID())
}
}
if oldDirectoryID == -1 {
// We haven't iterated over any components. Therefore, our "parent" directory is "n". We
// need to ACQUIRE this node in the dcache before returning.
n.Metadata().Dcache.Acquire(n.ID())
}
// The final component of the path may be ".", "..", a file name, or a directory name.
extra := ""
isDir = path[len(path)-1] == '/'
return n, pathComponents[len(pathComponents)-1] + extra, isDir, nil
}
// Validates that the open flags can open a file of a certain type.
func validateFlags(flags fs.OpenFlags, fileType fs.FileType) error {
if flags.Create() && flags.Exclusive() {
return fs.ErrAlreadyExists
} else if fileType != fs.FileTypeDirectory && flags.Directory() {
return fs.ErrNotADir
} else if fileType != fs.FileTypeRegularFile && flags.File() {
return fs.ErrNotAFile
} else if fileType == fs.FileTypeDirectory {
if flags.Append() || flags.Truncate() {
return fs.ErrNotAFile
} else if flags.Write() {
return fs.ErrNotAFile
} else if !flags.Read() && !flags.Path() { // Directories require read permission
return fs.ErrPermission
}
} else if flags.Truncate() && !flags.Write() {
return fs.ErrPermission
}
return nil
}
// Loads a dirent from a directory and verifies that it can be opened with the requested flags.
//
// Precondition:
// - parent is rlocked or locked
// Postcondition:
// - parent is rlocked or locked
func lookupAndCheck(parent node.DirectoryNode, name string, flags fs.OpenFlags) (*direntry.Dirent, int, error) {
// Check that the path already exists with the requested name
entry, direntryIndex, err := node.Lookup(parent, name)
if err != nil {
return nil, 0, err
} else if entry == nil {
return nil, 0, fs.ErrNotFound
} else if err := validateFlags(flags, entry.GetType()); err != nil {
return nil, 0, err
}
return entry, direntryIndex, nil
}
// Opens a SINGLE directory WITHOUT path resolution.
//
// Precondition:
// - parent is rlocked or locked
// Postcondition:
// - parent is rlocked or locked
// - If successful, the opened node is ACQUIRED in the dcache
func traverseDirectory(parent node.DirectoryNode, name string, flags fs.OpenFlags) (node.DirectoryNode, error) {
metadata := parent.Metadata()
if name == "." {
panic("Cannot traverse '.'")
} else if name == ".." && parent.IsRoot() {
// Edge case: ".." in root does not exist. It should just open the root.
if err := validateFlags(flags, fs.FileTypeDirectory); err != nil {
return nil, err
}
// ACQUIRE a reference to root (it should already be in the dcache)
metadata.Dcache.Acquire(parent.ID())
return parent, nil
}
entry, _, err := lookupAndCheck(parent, name, flags)
if err != nil {
return nil, err
} else if entry.GetType() != fs.FileTypeDirectory {
return nil, fs.ErrNotADir
}
return metadata.Dcache.CreateOrAcquire(metadata, entry.Cluster, entry.WriteTime)
}