Skip to content

Commit 1228bb4

Browse files
committed
add exported API to fat
1 parent 4cf6c03 commit 1228bb4

File tree

4 files changed

+276
-23
lines changed

4 files changed

+276
-23
lines changed

Diff for: example_test.go

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package fat_test
2+
3+
import (
4+
"fmt"
5+
"io"
6+
7+
"github.com/soypat/fat"
8+
)
9+
10+
func ExampleFS_basic_usage() {
11+
// device could be an SD card, RAM, or anything that implements the BlockDevice interface.
12+
device := fat.DefaultFATByteBlocks(32000)
13+
var fs fat.FS
14+
err := fs.Mount(device, device.BlockSize(), fat.ModeRW)
15+
if err != nil {
16+
panic(err)
17+
}
18+
var file fat.File
19+
err = fs.OpenFile(&file, "newfile.txt", fat.ModeCreateAlways|fat.ModeWrite)
20+
if err != nil {
21+
panic(err)
22+
}
23+
24+
_, err = file.Write([]byte("Hello, World!"))
25+
if err != nil {
26+
panic(err)
27+
}
28+
err = file.Close()
29+
if err != nil {
30+
panic(err)
31+
}
32+
33+
// Read back the file:
34+
err = fs.OpenFile(&file, "newfile.txt", fat.ModeRead)
35+
if err != nil {
36+
panic(err)
37+
}
38+
data, err := io.ReadAll(&file)
39+
if err != nil {
40+
panic(err)
41+
}
42+
fmt.Println(string(data))
43+
file.Close()
44+
// Output:
45+
// Hello, World!
46+
}

Diff for: exported.go

+187
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
package fat
2+
3+
import (
4+
"errors"
5+
"io"
6+
"math"
7+
"time"
8+
)
9+
10+
// Mode represents the file access mode used in Open.
11+
type Mode uint8
12+
13+
// File access modes for calling Open.
14+
const (
15+
ModeRead Mode = Mode(faRead)
16+
ModeWrite Mode = Mode(faWrite)
17+
ModeRW Mode = ModeRead | ModeWrite
18+
19+
ModeCreateNew Mode = Mode(faCreateNew)
20+
ModeCreateAlways Mode = Mode(faCreateAlways)
21+
ModeOpenExisting Mode = Mode(faOpenExisting)
22+
ModeOpenAppend Mode = Mode(faOpenAppend)
23+
24+
allowedModes = ModeRead | ModeWrite | ModeCreateNew | ModeCreateAlways | ModeOpenExisting | ModeOpenAppend
25+
)
26+
27+
var (
28+
errInvalidMode = errors.New("invalid fat access mode")
29+
errForbiddenMode = errors.New("forbidden fat access mode")
30+
)
31+
32+
// Dir represents an open FAT directory.
33+
type Dir struct {
34+
dir
35+
inlineInfo FileInfo
36+
}
37+
38+
// Mount mounts the FAT file system on the given block device and sector size.
39+
// It immediately invalidates previously open files and directories pointing to the same FS.
40+
// Mode should be ModeRead, ModeWrite, or both.
41+
func (fsys *FS) Mount(bd BlockDevice, blockSize int, mode Mode) error {
42+
if mode&^(ModeRead|ModeWrite) != 0 {
43+
return errInvalidMode
44+
} else if blockSize > math.MaxUint16 {
45+
return errors.New("sector size too large")
46+
}
47+
fr := fsys.mount_volume(bd, uint16(blockSize), uint8(mode))
48+
if fr != frOK {
49+
return fr
50+
}
51+
return nil
52+
}
53+
54+
// OpenFile opens the named file for reading or writing, depending on the mode.
55+
// The path must be absolute (starting with a slash) and must not contain
56+
// any elements that are "." or "..".
57+
func (fsys *FS) OpenFile(fp *File, path string, mode Mode) error {
58+
prohibited := (mode & ModeRW) &^ fsys.perm
59+
if mode&^allowedModes != 0 {
60+
return errInvalidMode
61+
} else if prohibited != 0 {
62+
return errForbiddenMode
63+
}
64+
fr := fsys.f_open(fp, path, uint8(mode))
65+
if fr != frOK {
66+
return fr
67+
}
68+
return nil
69+
}
70+
71+
// Read reads up to len(buf) bytes from the File. It implements the [io.Reader] interface.
72+
func (fp *File) Read(buf []byte) (int, error) {
73+
fr := fp.obj.validate()
74+
if fr != frOK {
75+
return 0, fr
76+
}
77+
br, fr := fp.f_read(buf)
78+
if fr != frOK {
79+
return br, fr
80+
} else if br == 0 && fr == frOK {
81+
return br, io.EOF
82+
}
83+
return br, nil
84+
}
85+
86+
// Write writes len(buf) bytes to the File. It implements the [io.Writer] interface.
87+
func (fp *File) Write(buf []byte) (int, error) {
88+
fr := fp.obj.validate()
89+
if fr != frOK {
90+
return 0, fr
91+
}
92+
bw, fr := fp.f_write(buf)
93+
if fr != frOK {
94+
return bw, fr
95+
}
96+
return bw, nil
97+
}
98+
99+
// Close closes the file and syncs any unwritten data to the underlying device.
100+
func (fp *File) Close() error {
101+
fr := fp.f_close()
102+
if fr != frOK {
103+
return fr
104+
}
105+
return nil
106+
}
107+
108+
// Sync syncs any unwritten data to the underlying device.
109+
func (fp *File) Sync() error {
110+
fr := fp.obj.validate()
111+
if fr != frOK {
112+
return fr
113+
}
114+
fr = fp.obj.fs.sync()
115+
if fr != frOK {
116+
return fr
117+
}
118+
return nil
119+
}
120+
121+
// OpenDir opens the named directory for reading.
122+
func (fsys *FS) OpenDir(dp *Dir, path string) error {
123+
fr := fsys.f_opendir(&dp.dir, path)
124+
if fr != frOK {
125+
return fr
126+
}
127+
return nil
128+
}
129+
130+
// ForEachFile calls the callback function for each file in the directory.
131+
func (dp *Dir) ForEachFile(callback func(*FileInfo) error) error {
132+
fr := dp.obj.validate()
133+
if fr != frOK {
134+
return fr
135+
} else if dp.obj.fs.perm&ModeRead == 0 {
136+
return errForbiddenMode
137+
}
138+
139+
fr = dp.sdi(0) // Rewind directory.
140+
if fr != frOK {
141+
return fr
142+
}
143+
for {
144+
fr := dp.f_readdir(&dp.inlineInfo)
145+
if fr != frOK {
146+
return fr
147+
} else if dp.inlineInfo.fname[0] == 0 {
148+
return nil // End of directory.
149+
}
150+
err := callback(&dp.inlineInfo)
151+
if err != nil {
152+
return err
153+
}
154+
}
155+
}
156+
157+
// AlternateName returns the alternate name of the file.
158+
func (finfo *FileInfo) AlternateName() string {
159+
return str(finfo.altname[:])
160+
}
161+
162+
// Name returns the name of the file.
163+
func (finfo *FileInfo) Name() string {
164+
return str(finfo.fname[:])
165+
}
166+
167+
// Size returns the size of the file in bytes.
168+
func (finfo *FileInfo) Size() int64 {
169+
return finfo.fsize
170+
}
171+
172+
// ModTime returns the modification time of the file.
173+
func (finfo *FileInfo) ModTime() time.Time {
174+
// https://www.win.tue.nl/~aeb/linux/fs/fat/fat-1.html
175+
hour := int(finfo.ftime >> 11)
176+
min := int((finfo.ftime >> 5) & 0x3f)
177+
doubleSeconds := int(finfo.ftime & 0x1f)
178+
yearSince1980 := int(finfo.fdate >> 9)
179+
month := int((finfo.fdate >> 5) & 0xf)
180+
day := int(finfo.fdate & 0x1f)
181+
return time.Date(yearSince1980+1980, time.Month(month), day, hour, min, 2*doubleSeconds, 0, time.UTC)
182+
}
183+
184+
// IsDir returns true if the file is a directory.
185+
func (finfo *FileInfo) IsDir() bool {
186+
return finfo.fattrib&amDIR != 0
187+
}

Diff for: fat.go

+18-21
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"encoding/binary"
66
"errors"
7-
"io/fs"
87
"log/slog"
98
"math/bits"
109
"runtime"
@@ -18,9 +17,9 @@ import (
1817
type BlockDevice interface {
1918
ReadBlocks(dst []byte, startBlock int64) (int, error)
2019
WriteBlocks(data []byte, startBlock int64) (int, error)
21-
EraseSectors(startBlock, numBlocks int64) error
20+
EraseBlocks(startBlock, numBlocks int64) error
2221
// Mode returns 0 for no connection/prohibited access, 1 for read-only, 3 for read-write.
23-
Mode() accessmode
22+
// Mode() accessmode
2423
}
2524

2625
// sector index type.
@@ -60,7 +59,7 @@ type FS struct {
6059
ffCodePage int
6160
dbcTbl [10]byte
6261
id uint16 // Filesystem mount ID. Serves to invalidate open files after mount.
63-
perm fs.FileMode
62+
perm Mode
6463
codepage []byte // unicode conversion table.
6564
exCvt []byte // points to _tblCT* corresponding to codepage table.
6665
log *slog.Logger
@@ -108,7 +107,7 @@ const (
108107
sfnBufSize = 12
109108
)
110109

111-
type fileinfo struct {
110+
type FileInfo struct {
112111
fsize int64 // File Size.
113112
fdate uint16
114113
ftime uint16
@@ -208,6 +207,8 @@ func (fp *File) f_read(buff []byte) (br int, res fileResult) {
208207
rbuff := buff
209208
if fp.flag&faRead == 0 {
210209
return 0, frDenied
210+
} else if fsys.perm&ModeRead == 0 {
211+
return 0, frDenied
211212
}
212213
remain := fp.obj.objsize - fp.fptr
213214
btr := len(buff)
@@ -356,6 +357,8 @@ func (fp *File) f_write(buf []byte) (bw int, fr fileResult) {
356357
return 0, frWriteProtected
357358
} else if fp.obj.fs.fstype == fstypeExFAT {
358359
return 0, frUnsupported
360+
} else if fp.obj.fs.perm&ModeWrite == 0 {
361+
return 0, frWriteProtected
359362
}
360363
fs := fp.obj.fs
361364
btw := len(buf)
@@ -500,6 +503,8 @@ func (fsys *FS) f_open(fp *File, name string, mode accessmode) fileResult {
500503
return frInvalidObject
501504
} else if fsys.fstype == fstypeExFAT {
502505
return frUnsupported
506+
} else if fsys.perm == 0 {
507+
return frDenied
503508
}
504509

505510
var dj dir
@@ -619,7 +624,7 @@ func (fsys *FS) f_open(fp *File, name string, mode accessmode) fileResult {
619624
return res
620625
}
621626

622-
func (dp *dir) f_readdir(fno *fileinfo) fileResult {
627+
func (dp *dir) f_readdir(fno *FileInfo) fileResult {
623628
fsys := dp.obj.fs
624629
fsys.trace("dir:f_readdir")
625630
if fsys.fstype == fstypeExFAT {
@@ -764,12 +769,7 @@ func (fsys *FS) mount_volume(bd BlockDevice, ssize uint16, mode uint8) (fr fileR
764769
// mutexes or file path handling. File path handling is left to
765770
// the Go standard library which does a much better job than us.
766771
// See `filepath` and `fs` standard library packages.
767-
devMode := bd.Mode()
768-
if devMode == 0 {
769-
return frNotReady
770-
} else if mode&devMode != mode {
771-
return frDenied
772-
}
772+
773773
blk, err := makeBlockIndexer(int(ssize))
774774
if err != nil {
775775
return frInvalidParameter
@@ -778,6 +778,7 @@ func (fsys *FS) mount_volume(bd BlockDevice, ssize uint16, mode uint8) (fr fileR
778778
fsys.id++ // Invalidate open files.
779779
fsys.blk = blk
780780
fsys.ssize = ssize
781+
fsys.perm = Mode(mode)
781782
fmt := fsys.find_volume(0)
782783

783784
if fmt == bootsectorstatusDiskError {
@@ -1167,6 +1168,9 @@ func (fsys *FS) st_clust(dir []byte, cl uint32) {
11671168
}
11681169

11691170
func (fsys *FS) disk_write(buf []byte, sector lba, numsectors int) diskresult {
1171+
if fsys.perm&ModeWrite == 0 {
1172+
return drWriteProtected
1173+
}
11701174
fsys.trace("fs:disk_write", slog.Uint64("start", uint64(sector)), slog.Int("numsectors", numsectors))
11711175
if fsys.blk.off(int64(len(buf))) != 0 || fsys.blk._divideBlockSize(int64(len(buf))) != int64(numsectors) {
11721176
fsys.logerror("disk_write:unaligned")
@@ -1196,7 +1200,7 @@ func (fsys *FS) disk_read(dst []byte, sector lba, numsectors int) diskresult {
11961200
}
11971201
func (fsys *FS) disk_erase(startSector lba, numSectors int) diskresult {
11981202
fsys.trace("fs:disk_erase", slog.Uint64("start", uint64(startSector)), slog.Int("numsectors", numSectors))
1199-
err := fsys.device.EraseSectors(int64(startSector), int64(numSectors))
1203+
err := fsys.device.EraseBlocks(int64(startSector), int64(numSectors))
12001204
if err != nil {
12011205
fsys.logerror("disk_erase", slog.String("err", err.Error()))
12021206
return drError
@@ -1871,7 +1875,7 @@ func (dp *dir) sdi(ofs uint32) fileResult {
18711875
return frOK
18721876
}
18731877

1874-
func (dp *dir) get_fileinfo(fno *fileinfo) {
1878+
func (dp *dir) get_fileinfo(fno *FileInfo) {
18751879
fsys := dp.obj.fs
18761880

18771881
fno.fname[0] = 0 // Invalidate.
@@ -2348,13 +2352,6 @@ func (fsys *FS) gen_numname(dst, src []byte, lfn []uint16, seq uint32) {
23482352
}
23492353
}
23502354

2351-
func (finfo *fileinfo) AlternateName() string {
2352-
return str(finfo.altname[:])
2353-
}
2354-
func (finfo *fileinfo) Name() string {
2355-
return str(finfo.fname[:])
2356-
}
2357-
23582355
func str(s []byte) string {
23592356
if len(s) == 0 {
23602357
return ""

0 commit comments

Comments
 (0)