blob: a61bb2f395d5c92bafef7fe9ac347dee3d49874d [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 (
"sync"
"time"
"thinfs/fs"
"thinfs/fs/msdosfs/node"
)
// Structure representing position within file. May be shared between multiple dup-ed files.
type filePosition struct {
sync.Mutex
offset int64 // Seek position in the file
}
type file struct {
fs *fsFAT
node node.FileNode
flags fs.OpenFlags // Original flags which opened this file
position *filePosition // Position within file. Used for next read / write, depending on whence.
sync.RWMutex
closed bool
}
// Ensure the file implements the fs.File interface
var _ fs.File = (*file)(nil)
func (f *file) Close() error {
f.fs.RLock()
defer f.fs.RUnlock()
if f.fs.unmounted {
return fs.ErrUnmounted
}
f.Lock() // Changing 'closed'
defer f.Unlock()
if f.closed {
return fs.ErrNotOpen
}
f.closed = true
return closeFile(f.node)
}
func (f *file) Stat() (int64, time.Time, time.Time, error) {
f.fs.RLock()
defer f.fs.RUnlock()
if f.fs.unmounted {
return 0, time.Time{}, time.Time{}, fs.ErrUnmounted
}
f.RLock()
defer f.RUnlock()
if f.closed {
return 0, time.Time{}, time.Time{}, fs.ErrNotOpen
}
return stat(f.node)
}
func (f *file) Touch(lastAccess, lastModified time.Time) error {
f.fs.RLock()
defer f.fs.RUnlock()
if f.fs.unmounted {
return fs.ErrUnmounted
}
f.RLock()
defer f.RUnlock()
if f.closed {
return fs.ErrNotOpen
}
touch(f.node, lastAccess, lastModified)
return nil
}
func (f *file) Dup() (fs.File, error) {
f.fs.RLock()
defer f.fs.RUnlock()
if f.fs.unmounted {
return nil, fs.ErrUnmounted
}
f.RLock()
defer f.RUnlock()
if f.closed {
return nil, fs.ErrNotOpen
}
dup(f.node)
return &file{
fs: f.fs,
node: f.node,
flags: f.flags,
position: f.position,
}, nil
}
func (f *file) GetOpenFlags() fs.OpenFlags {
f.RLock()
defer f.RUnlock()
return f.flags
}
func (f *file) SetOpenFlags(flags fs.OpenFlags) error {
f.Lock()
defer f.Unlock()
f.flags = flags
return nil
}
// TODO(smklein): Test reopen before plugging it into Remote IO
func (f *file) Reopen(flags fs.OpenFlags) (fs.File, error) {
f.fs.RLock()
defer f.fs.RUnlock()
if f.fs.unmounted {
return nil, fs.ErrUnmounted
}
f.RLock()
defer f.RUnlock()
if f.closed {
return nil, fs.ErrNotOpen
}
if (flags.Read() && !f.flags.Read()) || (flags.Write() && !f.flags.Write()) {
return nil, fs.ErrPermission
} else if flags.Directory() {
return nil, fs.ErrNotADir
}
dup(f.node)
return &file{
fs: f.fs,
node: f.node,
flags: flags,
position: &filePosition{}, // Create a new file position (unlike dup)
}, nil
}
func (f *file) Read(p []byte, off int64, whence int) (int, error) {
f.fs.RLock()
defer f.fs.RUnlock()
if f.fs.unmounted {
return 0, fs.ErrUnmounted
}
f.RLock() // Reading 'closed'
defer f.RUnlock()
if f.closed {
return 0, fs.ErrNotOpen
}
if !f.flags.Read() {
return 0, fs.ErrPermission
}
f.node.RLock()
defer f.node.RUnlock()
var updateCursor bool
switch whence {
case fs.WhenceFromCurrent:
f.position.Lock()
defer f.position.Unlock()
off += f.position.offset
updateCursor = true
case fs.WhenceFromStart:
case fs.WhenceFromEnd:
off += f.node.Size()
default:
return 0, fs.ErrInvalidArgs
}
bytesRead, err := f.node.ReadAt(p, off)
if updateCursor {
f.position.offset = off + int64(bytesRead)
}
return bytesRead, err
}
func (f *file) Write(p []byte, off int64, whence int) (int, error) {
f.fs.RLock()
defer f.fs.RUnlock()
if f.fs.unmounted {
return 0, fs.ErrUnmounted
}
f.RLock() // Reading 'closed'
defer f.RUnlock()
if f.closed {
return 0, fs.ErrNotOpen
}
if f.fs.info.Readonly {
return 0, fs.ErrPermission
} else if !f.flags.Write() {
return 0, fs.ErrPermission
}
f.node.Lock()
defer f.node.Unlock()
var updateCursor bool
if f.flags.Append() {
// The 'append' flag is the same as forcing 'WhenceFromEnd' on all writes
whence = fs.WhenceFromEnd
f.position.Lock()
defer f.position.Unlock()
updateCursor = true
}
switch whence {
case fs.WhenceFromCurrent:
f.position.Lock()
defer f.position.Unlock()
updateCursor = true
off += f.position.offset
case fs.WhenceFromStart:
case fs.WhenceFromEnd:
off += f.node.Size()
default:
return 0, fs.ErrInvalidArgs
}
bytesWritten, err := f.node.WriteAt(p, off)
if updateCursor {
f.position.offset = off + int64(bytesWritten)
}
return bytesWritten, err
}
func (f *file) Truncate(size uint64) error {
f.fs.RLock()
defer f.fs.RUnlock()
if f.fs.unmounted {
return fs.ErrUnmounted
}
f.RLock()
defer f.RUnlock()
if f.closed {
return fs.ErrNotOpen
}
if f.fs.info.Readonly || !f.flags.Write() {
return fs.ErrPermission
}
if size > uint64(node.MaxSizeFile) {
return fs.ErrInvalidArgs
}
f.node.Lock()
defer f.node.Unlock()
if size > uint64(f.node.Size()) {
empty := make([]byte, 1<<14)
for size > uint64(f.node.Size()) {
// Make the file larger (filled with zeroes). Since FAT (unfortunately) does not support
// sparse files, this requires manually writing 'zero' to the end of the file.
bytesToWrite := uint64(len(empty))
bytesLeft := size - uint64(f.node.Size())
if bytesToWrite > bytesLeft {
bytesToWrite = bytesLeft
}
f.node.WriteAt(empty[:bytesToWrite], f.node.Size())
}
}
if size < uint64(f.node.Size()) {
// Make the file smaller. This can be done by simply reducing the size.
f.node.SetSize(int64(size))
}
return nil
}
func (f *file) Tell() (int64, error) {
f.fs.RLock()
defer f.fs.RUnlock()
if f.fs.unmounted {
return 0, fs.ErrUnmounted
}
f.RLock()
defer f.RUnlock()
if f.closed {
return 0, fs.ErrNotOpen
}
f.position.Lock()
defer f.position.Unlock()
return f.position.offset, nil
}
func (f *file) Seek(offset int64, whence int) (int64, error) {
f.fs.RLock()
defer f.fs.RUnlock()
if f.fs.unmounted {
return 0, fs.ErrUnmounted
} else if f.flags.Path() {
return 0, fs.ErrPermission
}
f.RLock() // Reading 'closed'
defer f.RUnlock()
if f.closed {
return 0, fs.ErrNotOpen
}
f.node.RLock() // Possibly reading 'Size'
defer f.node.RUnlock()
f.position.Lock() // Setting position
defer f.position.Unlock()
switch whence {
case fs.WhenceFromCurrent:
f.position.offset += offset
case fs.WhenceFromStart:
f.position.offset = offset
case fs.WhenceFromEnd:
f.position.offset = f.node.Size() + offset
default:
return 0, fs.ErrInvalidArgs
}
return f.position.offset, nil
}
func (f *file) Sync() error {
f.fs.RLock()
defer f.fs.RUnlock()
if f.fs.unmounted {
return fs.ErrUnmounted
}
f.RLock()
defer f.RUnlock()
if f.closed {
return fs.ErrNotOpen
}
return syncFile(f.node)
}