blob: 618869d7f842031ac991920d533f1723eaca0cd0 [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 file implements the block.Device interface backed by a traditional file.
package file
import (
"fmt"
"os"
"github.com/golang/glog"
)
// File represents a block device backed by a file on a traditional file system.
type File struct {
f *os.File
info os.FileInfo
size int64
blockSize int64
base int64
}
func getSize(f *os.File, info os.FileInfo) int64 {
if info.Mode()&os.ModeDevice != 0 {
if size, err := ioctlBlockGetSize(f.Fd()); err == nil {
return size
}
}
// If the file is a block device but the ioctl failed for some reason or if the file is a
// regular file, just fall back to using the size reported by Stat().
return info.Size()
}
// New creates and returns a new File, using f as the backing store. The size of the
// block device represented by the returned File will be the size of f. New will
// not close f if any errors occur. New will also attempt to detect the block size for
// f and will use defaultBlockSize if it is unable to do so. If there is an error, it will
// be of type *os.PathError.
func New(f *os.File, defaultBlockSize int64) (*File, error) {
info, err := f.Stat()
if err != nil {
return nil, &os.PathError{
Op: "New",
Path: f.Name(),
Err: err,
}
}
size := getSize(f, info)
blockSize := defaultBlockSize
if info.Mode()&os.ModeDevice != 0 {
if ssz, err := ioctlBlockGetSectorSize(f.Fd()); err == nil {
blockSize = ssz
}
}
if glog.V(2) {
glog.Info("File name: ", info.Name())
glog.Info(" size: ", size)
glog.Info(" mode: ", info.Mode())
glog.Info(" blocksize: ", blockSize)
}
return &File{
f: f,
info: info,
size: size,
blockSize: blockSize,
}, nil
}
// NewRange creates and returns a new File, using a subset of f as the backing
// store. The blockSize, offset and size must be provided.
func NewRange(f *os.File, blockSize, offset, size int64) (*File, error) {
// TODO(raggi): there might be some case where one day we'll want to support
// mis-alignment in the outer range, but for now, this keeps the check() code
// simple:
if offset%blockSize != 0 {
return nil, &os.PathError{
Op: "NewRange",
Path: f.Name(),
Err: fmt.Errorf("offset (%v) is not a multiple of blocksize (%v)", offset, blockSize),
}
}
info, err := f.Stat()
if err != nil {
return nil, &os.PathError{
Op: "New",
Path: f.Name(),
Err: err,
}
}
if glog.V(2) {
glog.Info("File name: ", info.Name())
glog.Info(" size: ", size)
glog.Info(" mode: ", info.Mode())
glog.Info(" blocksize: ", blockSize)
}
return &File{
f: f,
info: info,
size: size,
blockSize: blockSize,
base: offset,
}, nil
}
// BlockSize returns the size in bytes of the smallest block that can be written by the File.
// The return value is undefined after Close() is called.
func (f *File) BlockSize() int64 {
return f.blockSize
}
// Size returns the fixed size of the File in bytes. The return value is undefined after Close()
// is called.
func (f *File) Size() int64 {
return f.size
}
func (f *File) check(p []byte, off int64, op string) error {
if off%f.blockSize != 0 {
return &os.PathError{
Op: op,
Path: f.info.Name(),
Err: fmt.Errorf("off (%v) is not a multiple of blocksize", off),
}
}
if int64(len(p))%f.blockSize != 0 {
return &os.PathError{
Op: op,
Path: f.info.Name(),
Err: fmt.Errorf("len(p) (%v) is not a multiple of blocksize", len(p)),
}
}
if off+int64(len(p)) > f.Size() {
return &os.PathError{
Op: op,
Path: f.info.Name(),
Err: fmt.Errorf("the requested range [%v, %v) is out of bounds", off, off+int64(len(p))),
}
}
return nil
}
// ReadAt reads len(p) bytes from the device starting at offset off. Both off and len(p) must
// be multiples of BlockSize(). It returns the number of bytes read and the error, if any.
// ReadAt always returns a non-nil error when n < len(p).
func (f *File) ReadAt(p []byte, off int64) (n int, err error) {
if err := f.check(p, off, "ReadAt"); err != nil {
return 0, err
}
if glog.V(2) {
glog.Infof("ReadAt: reading %v bytes from offset %#x\n", len(p), off)
}
return f.f.ReadAt(p, f.base+off)
}
// WriteAt writes the contents of p to the devices starting at offset off. Both off and len(p) must
// be multiples of BlockSize(). It returns the number of bytes written and an error, if any.
// WriteAt always returns a non-nil error when n < len(p).
func (f *File) WriteAt(p []byte, off int64) (n int, err error) {
if err := f.check(p, off, "WriteAt"); err != nil {
return 0, err
}
if glog.V(2) {
glog.Infof("WriteAt: writing %v bytes to address %#x\n", len(p), off)
}
return f.f.WriteAt(p, f.base+off)
}
// Flush forces any writes that have been cached in memory to be committed to persistent storage.
// Returns an error, if any.
func (f *File) Flush() error {
if glog.V(2) {
glog.Infof("Syncing file %s\n", f.info.Name())
}
return f.f.Sync()
}
// Discard marks the address range [off, off+size) as being unused, allowing it to be reclaimed by
// the device for other purposes. Both off and size must be multiples of Blocksize(). Returns an
// error, if any.
func (f *File) Discard(off, len int64) error {
if glog.V(2) {
glog.Infof("Discarding data in range [%v, %v)\n", off, off+len)
}
if f.info.Mode()&os.ModeDevice != 0 {
return ioctlBlockDiscard(f.f.Fd(), uint64(f.base+off), uint64(len))
}
return fallocate(f.f.Fd(), f.base+off, len)
}
// Close calls Flush() and then closes the device, rendering it unusable for I/O. It returns an error,
// if any.
func (f *File) Close() error {
if err := f.Flush(); err != nil {
return err
}
if glog.V(2) {
glog.Infof("Closing file %s\n", f.info.Name())
}
return f.f.Close()
}
// Path obtains path to underlying block device via ioctl.
func (f *File) Path() string {
return ioctlDeviceGetTopoPath(f.f.Fd())
}