gofat

package module
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 3, 2021 License: MIT Imports: 16 Imported by: 0

README

Actions Status CodeQL codecov

GoFAT

A FAT filesystem implementation in pure Go.
It implements the interface of afero.

Current Status

Readonly File access works great. Write support is missing, yet.

Usage

package main

import (
	"fmt"
	"io"
	"os"
	"github.com/aligator/gofat"
	"github.com/spf13/afero"
)

func main() {
	// Get any io.ReadSeeker implementation which provides a reader to a FAT32 filesystem.
	// You can use for example os.Open(...) to open an image file or even a `/dev/sdxy` device file from linux. 
	var reader io.ReadSeeker = ...

	// Then create a new GoFAT filesystem from the reader.
	fat, err := gofat.New(reader)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	// Now you can access any method from the afero.Fs filesystem like for example afero.Walk.
	_ := afero.Walk(fat, "/", func(path string, info os.FileInfo, err error) error {
		if err != nil {
			fmt.Println(err)
			return err
		}
		fmt.Println(path, info.IsDir())
		return nil
	})

	// You can also access some FAT specific fields.
	fmt.Printf("Opened volume '%v' with type %v\n\n", fat.Label(), fat.FSType())
}

That's it!

Compatibility with Go 1.16

As the Go 1.16 fs.FS interface is not fully compatible with the afero.Fs interface. But I added a simple wrapper around it.
You can either just wrap an existing fat fs:

gofs := GoFs{*fs}

Or directly create a new one using NewGoFS(...) or NewGoFSSkipChecks(...).
Note that this wrapper has a small overhead, especially ReadDir because the result has to be converted to []fs.DirEntry.

I also added testing.fstest to the unit tests.

Test images

To get access to some test-images which already contain a FAT filesystem just run

go generate

This will extract the test images into ./testdata.

Contribution

Contributions are welcome, just create issues or even better PRs. You may open a draft or issue first to discuss the changes you would like to implement.

You may use github.com/cweill/gotests to generate your test boilerplate code.

Some resources on how FAT works are:

ToDo

  • more tests
  • implement some more attributes (e.g. hidden)
  • implement write support
  • support FAT12
  • check if compatibility with TinyGo for microcontrollers is possible. That would be a good use case for this lib...
  • use for a fuse filesystem driver

Documentation

Overview

Package gofat is a generated GoMock package.

Index

Constants

View Source
const (
	AttrReadOnly  = 0x01
	AttrHidden    = 0x02
	AttrSystem    = 0x04
	AttrVolumeId  = 0x08
	AttrDirectory = 0x10
	AttrArchive   = 0x20
	AttrDevice    = 0x40
	AttrReserved  = 0x80
	AttrLongName  = AttrReadOnly | AttrHidden | AttrSystem | AttrVolumeId
)

Variables

View Source
var (
	ErrReadFile = errors.New("could not read file completely")
	ErrSeekFile = errors.New("could not seek inside of the file")
	ErrReadDir  = errors.New("could not read the directory")
)

These errors may occur while processing a file.

View Source
var (
	ErrInvalidPath          = errors.New("invalid path")
	ErrOpenFilesystem       = errors.New("could not open the filesystem")
	ErrReadFilesystemFile   = errors.New("could not read file completely from the filesystem")
	ErrReadFilesystemDir    = errors.New("could not a directory from the filesystem")
	ErrNotSupported         = errors.New("not supported")
	ErrInitializeFilesystem = errors.New("initialize the filesystem")
	ErrFetchingSector       = errors.New("could not fetch a new sector")
	ErrReadFat              = errors.New("could not read FAT sector")
)

These errors may occur while processing a FAT filesystem.

Functions

func ParseDate added in v0.2.0

func ParseDate(input uint16) time.Time

ParseDate reads the given input as a date like it is specified in the specification:

A FAT directory entry date stamp is a 16- bit field that is basically a
date relative to the MS- DOS epoch of 01/01 / 19 80. Here is the format (bit 0 is the
LSB of the 16- bit word, bit 15 is the MSB of the 16- bit word):
 Bits 0–4: Day of month, valid value range 1- 31 inclusive.
 Bits 5–8: Month of year, 1 = January, valid value range 1–12 inclusive.
 Bits 9–15: Count of years from 1980, valid value range 0–127 inclusive
 (1980–2107).

It returns a time.Time which has always a time of 00:00:00.000000000 UTC.

As value 0 for day and month is defined as invalid in the specification the value time.Time{} is used to be compatible with time.Time.IsZero() if any of that cases occurs.

Note that monthOfYear may be bigger than 12 which is unspecified. In this case the year gets incremented by one.

func ParseTime added in v0.2.0

func ParseTime(input uint16) time.Time

ParseTime reads the given input as a date like it is specified in the specification:

A FAT directory entry time stamp is a 16- bit field that has a
granularity of 2 seconds. Here is the format (bit 0 is the LSB of the 16- bit word, bit
15 is the MSB of the 16- bit word).
 Bits 0–4: 2- second count, valid value range 0–29 inclusive (0 – 58 seconds).
 Bits 5–10: Minutes, valid value range 0–59 inclusive.
 Bits 11–15: Hours, valid value range 0–23 inclusive.
The valid time range is from Midnight 00:00:00 to 23:59:58.

It returns a time.Time which has always a date of of January 1, year 1. That way in case of seconds == 0, minutes == 0 and hours == 0 time.Time.IsZero() can be used.

Note that bigger values than the specified ones are just added to the time. But this is limited to 23:59:59. This edge case should happen rarely and only if the time filed is invalid.

Types

type BPB

type BPB struct {
	BSJumpBoot          [3]byte
	BSOEMName           [8]byte
	BytesPerSector      uint16
	SectorsPerCluster   byte
	ReservedSectorCount uint16
	NumFATs             byte
	RootEntryCount      uint16
	TotalSectors16      uint16
	Media               byte
	FATSize16           uint16
	SectorsPerTrack     uint16
	NumberOfHeads       uint16
	HiddenSectors       uint32
	TotalSectors32      uint32
	FATSpecificData     [54]byte
}

type EntryHeader

type EntryHeader struct {
	Name            [11]byte
	Attribute       byte
	NTReserved      byte
	CreateTimeTenth byte
	CreateTime      uint16
	CreateDate      uint16
	LastAccessDate  uint16
	FirstClusterHI  uint16
	WriteTime       uint16
	WriteDate       uint16
	FirstClusterLO  uint16
	FileSize        uint32
}

type ExtendedEntryHeader

type ExtendedEntryHeader struct {
	EntryHeader
	ExtendedName string
}

func (*ExtendedEntryHeader) FileInfo

func (h *ExtendedEntryHeader) FileInfo() os.FileInfo

type FAT16SpecificData

type FAT16SpecificData struct {
	BSDriveNumber    byte
	BSReserved1      byte
	BSBootSignature  byte
	BSVolumeId       uint32
	BSVolumeLabel    [11]byte
	BSFileSystemType [8]byte
}

type FAT32SpecificData

type FAT32SpecificData struct {
	FatSize          uint32
	ExtFlags         uint16
	FSVersion        uint16
	RootCluster      fatEntry
	FSInfo           uint16
	BkBootSector     uint16
	Reserved         [12]byte
	BSDriveNumber    byte
	BSReserved1      byte
	BSBootSignature  byte
	BSVolumeID       uint32
	BSVolumeLabel    [11]byte
	BSFileSystemType [8]byte
}

type FATType

type FATType string
const (
	FAT12 FATType = "FAT12"
	FAT16 FATType = "FAT16"
	FAT32 FATType = "FAT32"
)

type File

type File struct {
	// contains filtered or unexported fields
}

func (*File) Close

func (f *File) Close() error

func (*File) Name

func (f *File) Name() string

func (*File) Read

func (f *File) Read(p []byte) (n int, err error)

func (*File) ReadAt

func (f *File) ReadAt(p []byte, off int64) (n int, err error)

func (*File) Readdir

func (f *File) Readdir(count int) ([]os.FileInfo, error)

Readdir reads the contents of a directory. May return syscall.ENOTDIR if the current File is no directory.

func (*File) Readdirnames

func (f *File) Readdirnames(count int) ([]string, error)

func (*File) Seek

func (f *File) Seek(offset int64, whence int) (int64, error)

Seek jumps to a specific offset in the file. This affects all Read operation except ReadAt. May return a syscall.EINVAL error if the whence value is invalid. May return an afero.ErrOutOfRange error if the offset is out of range.

func (*File) Stat

func (f *File) Stat() (os.FileInfo, error)

func (*File) Sync

func (f *File) Sync() error

func (*File) Truncate

func (f *File) Truncate(size int64) error

func (*File) Write

func (f *File) Write(p []byte) (n int, err error)

func (*File) WriteAt

func (f *File) WriteAt(p []byte, off int64) (n int, err error)

func (*File) WriteString

func (f *File) WriteString(s string) (ret int, err error)

type Fs

type Fs struct {
	// contains filtered or unexported fields
}

func New

func New(reader io.ReadSeeker) (*Fs, error)

New opens a FAT filesystem from the given reader.

func NewSkipChecks

func NewSkipChecks(reader io.ReadSeeker) (*Fs, error)

NewSkipChecks opens a FAT filesystem from the given reader just like New but it skips some filesystem validations which may allow you to open not perfectly standard FAT filesystems. Use with caution!

func (*Fs) Chmod

func (f *Fs) Chmod(name string, mode os.FileMode) error

func (*Fs) Chown

func (f *Fs) Chown(name string, uid, gid int) error

func (*Fs) Chtimes

func (f *Fs) Chtimes(name string, atime time.Time, mtime time.Time) error

func (*Fs) Create

func (f *Fs) Create(name string) (afero.File, error)

func (*Fs) FSType

func (f *Fs) FSType() FATType

func (*Fs) Label

func (f *Fs) Label() string

func (*Fs) Mkdir

func (f *Fs) Mkdir(name string, perm os.FileMode) error

func (*Fs) MkdirAll

func (f *Fs) MkdirAll(path string, perm os.FileMode) error

func (*Fs) Name

func (f *Fs) Name() string

func (*Fs) Open

func (f *Fs) Open(path string) (afero.File, error)

func (*Fs) OpenFile

func (f *Fs) OpenFile(name string, flag int, perm os.FileMode) (afero.File, error)

func (*Fs) Remove

func (f *Fs) Remove(name string) error

func (*Fs) RemoveAll

func (f *Fs) RemoveAll(path string) error

func (*Fs) Rename

func (f *Fs) Rename(oldname, newname string) error

func (*Fs) Stat

func (f *Fs) Stat(path string) (os.FileInfo, error)

type GoDirEntry added in v0.4.0

type GoDirEntry struct {
	fs.FileInfo
}

func (GoDirEntry) Info added in v0.4.0

func (g GoDirEntry) Info() (fs.FileInfo, error)

func (GoDirEntry) Type added in v0.4.0

func (g GoDirEntry) Type() fs.FileMode

type GoFile added in v0.4.0

type GoFile struct {
	*File
}

func (GoFile) Close added in v0.4.0

func (g GoFile) Close() error

func (GoFile) Read added in v0.4.0

func (g GoFile) Read(bytes []byte) (int, error)

func (GoFile) ReadDir added in v0.4.0

func (g GoFile) ReadDir(n int) ([]fs.DirEntry, error)

func (GoFile) Stat added in v0.4.0

func (g GoFile) Stat() (fs.FileInfo, error)

type GoFs added in v0.4.0

type GoFs struct {
	Fs
}

GoFs just wraps the afero FAT implementation to be compatible with fs.FS.

func NewGoFS added in v0.4.0

func NewGoFS(reader io.ReadSeeker) (*GoFs, error)

NewGoFS opens a FAT filesystem from the given reader as fs.FS compatible filesystem.

func NewGoFSSkipChecks added in v0.4.0

func NewGoFSSkipChecks(reader io.ReadSeeker) (*GoFs, error)

NewGoFSSkipChecks opens a FAT filesystem from the given reader as fs.FS compatible filesystem just like NewGoFs but it skips some filesystem validations which may allow you to open not perfectly standard FAT filesystems. Use with caution!

func (GoFs) Open added in v0.4.0

func (g GoFs) Open(name string) (fs.File, error)

type Info

type Info struct {
	FSType              FATType
	FatCount            uint8
	FatSize             uint32
	SectorsPerCluster   uint8
	FirstDataSector     uint32
	TotalSectorCount    uint32
	ReservedSectorCount uint16
	BytesPerSector      uint16
	Label               string

	RootEntryCount uint16 // RootEntryCount is only needed for < FAT32.
	// contains filtered or unexported fields
}

Info contains all information about the whole filesystem.

type LongFilenameEntry

type LongFilenameEntry struct {
	Sequence  byte
	First     [5]uint16
	Attribute byte
	EntryType byte
	Checksum  byte
	Second    [6]uint16
	Zero      [2]byte
	Third     [2]uint16
}

type MockfatFileFs added in v0.3.0

type MockfatFileFs struct {
	// contains filtered or unexported fields
}

MockfatFileFs is a mock of fatFileFs interface.

func NewMockfatFileFs added in v0.3.0

func NewMockfatFileFs(ctrl *gomock.Controller) *MockfatFileFs

NewMockfatFileFs creates a new mock instance.

func (*MockfatFileFs) EXPECT added in v0.3.0

EXPECT returns an object that allows the caller to indicate expected use.

type MockfatFileFsMockRecorder added in v0.3.0

type MockfatFileFsMockRecorder struct {
	// contains filtered or unexported fields
}

MockfatFileFsMockRecorder is the mock recorder for MockfatFileFs.

type Sector

type Sector struct {
	// contains filtered or unexported fields
}

Directories

Path Synopsis
Package checkpoint provides a way to decorate errors by some additional caller information which results in something similar to a stacktrace.
Package checkpoint provides a way to decorate errors by some additional caller information which results in something similar to a stacktrace.
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL