From 7a4d638085d1ca6ad4a01f24b9f5a94710b53408 Mon Sep 17 00:00:00 2001 From: sago35 Date: Fri, 14 May 2021 09:30:25 +0900 Subject: [PATCH 01/41] go mod --- go.mod | 4 ++-- go.sum | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 12b4459..d23c6ac 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ -module github.com/bgould/tinyfs +module github.com/tinygo-org/tinyfs go 1.14 -require tinygo.org/x/drivers v0.13.0 +require tinygo.org/x/drivers v0.16.0 diff --git a/go.sum b/go.sum index 512a9ff..70b699c 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,11 @@ github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= +github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= tinygo.org/x/drivers v0.13.0 h1:ohzhFiPb/5dcRop3X+Gdvsr6uswmnpfMX9KsAMtgtTM= tinygo.org/x/drivers v0.13.0/go.mod h1:mShi1lpVtJFpApkZgwyrzDKHToeGfWIuB08utyHxZ7g= +tinygo.org/x/drivers v0.16.0 h1:U+cOLx21iT8clWtBqbHzXZE2yDkCrjk7AitTdwLQwJc= +tinygo.org/x/drivers v0.16.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= From d71e2449d2bfd426442406022b53088ac7308fb5 Mon Sep 17 00:00:00 2001 From: sago35 Date: Fri, 14 May 2021 09:41:36 +0900 Subject: [PATCH 02/41] replace import path --- examples/console/example.go | 2 +- examples/console/fatfs/qspi/main.go | 4 ++-- examples/console/fatfs/spi/main.go | 4 ++-- examples/console/littlefs/qspi/main.go | 4 ++-- examples/console/littlefs/spi/main.go | 4 ++-- fatfs/examples/flash/example.go | 4 ++-- fatfs/examples/flash/qspi/main.go | 2 +- fatfs/examples/flash/spi/main.go | 2 +- fatfs/examples/simple/main.go | 4 ++-- fatfs/go_fatfs.go | 6 +++--- fatfs/go_fatfs_callbacks.go | 4 ++-- fatfs/go_fatfs_test.go | 2 +- littlefs/go_lfs.go | 6 +++--- littlefs/go_lfs_callbacks.go | 4 ++-- littlefs/go_lfs_test.go | 2 +- 15 files changed, 27 insertions(+), 27 deletions(-) diff --git a/examples/console/example.go b/examples/console/example.go index 032fd18..6a78101 100644 --- a/examples/console/example.go +++ b/examples/console/example.go @@ -11,7 +11,7 @@ import ( "strings" "time" - "github.com/bgould/tinyfs" + "github.com/tinygo-org/tinyfs" "tinygo.org/x/drivers/flash" ) diff --git a/examples/console/fatfs/qspi/main.go b/examples/console/fatfs/qspi/main.go index 27da2c4..753331a 100644 --- a/examples/console/fatfs/qspi/main.go +++ b/examples/console/fatfs/qspi/main.go @@ -6,8 +6,8 @@ import ( "machine" "time" - "github.com/bgould/tinyfs/examples/console" - "github.com/bgould/tinyfs/fatfs" + "github.com/tinygo-org/tinyfs/examples/console" + "github.com/tinygo-org/tinyfs/fatfs" "tinygo.org/x/drivers/flash" ) diff --git a/examples/console/fatfs/spi/main.go b/examples/console/fatfs/spi/main.go index dc4e70b..f259b35 100644 --- a/examples/console/fatfs/spi/main.go +++ b/examples/console/fatfs/spi/main.go @@ -6,8 +6,8 @@ import ( "machine" "time" - "github.com/bgould/tinyfs/examples/console" - "github.com/bgould/tinyfs/fatfs" + "github.com/tinygo-org/tinyfs/examples/console" + "github.com/tinygo-org/tinyfs/fatfs" "tinygo.org/x/drivers/flash" ) diff --git a/examples/console/littlefs/qspi/main.go b/examples/console/littlefs/qspi/main.go index daac670..ea282a9 100644 --- a/examples/console/littlefs/qspi/main.go +++ b/examples/console/littlefs/qspi/main.go @@ -6,8 +6,8 @@ import ( "machine" "time" - "github.com/bgould/tinyfs/examples/console" - "github.com/bgould/tinyfs/littlefs" + "github.com/tinygo-org/tinyfs/examples/console" + "github.com/tinygo-org/tinyfs/littlefs" "tinygo.org/x/drivers/flash" ) diff --git a/examples/console/littlefs/spi/main.go b/examples/console/littlefs/spi/main.go index 0a95fec..35cf873 100644 --- a/examples/console/littlefs/spi/main.go +++ b/examples/console/littlefs/spi/main.go @@ -6,8 +6,8 @@ import ( "machine" "time" - "github.com/bgould/tinyfs/examples/console" - "github.com/bgould/tinyfs/littlefs" + "github.com/tinygo-org/tinyfs/examples/console" + "github.com/tinygo-org/tinyfs/littlefs" "tinygo.org/x/drivers/flash" ) diff --git a/fatfs/examples/flash/example.go b/fatfs/examples/flash/example.go index 865d9d1..b933efd 100644 --- a/fatfs/examples/flash/example.go +++ b/fatfs/examples/flash/example.go @@ -11,8 +11,8 @@ import ( "strings" "time" - "github.com/bgould/tinyfs" - "github.com/bgould/tinyfs/fatfs" + "github.com/tinygo-org/tinyfs" + "github.com/tinygo-org/tinyfs/fatfs" "tinygo.org/x/drivers/flash" ) diff --git a/fatfs/examples/flash/qspi/main.go b/fatfs/examples/flash/qspi/main.go index 3e1db0e..d1f3261 100644 --- a/fatfs/examples/flash/qspi/main.go +++ b/fatfs/examples/flash/qspi/main.go @@ -5,7 +5,7 @@ package main import ( "machine" - example "github.com/bgould/tinyfs/examples/flash" + example "github.com/tinygo-org/tinyfs/examples/flash" "tinygo.org/x/drivers/flash" ) diff --git a/fatfs/examples/flash/spi/main.go b/fatfs/examples/flash/spi/main.go index e0476aa..4a9b2cf 100644 --- a/fatfs/examples/flash/spi/main.go +++ b/fatfs/examples/flash/spi/main.go @@ -5,7 +5,7 @@ package main import ( "machine" - example "github.com/bgould/tinyfs/examples/flash" + example "github.com/tinygo-org/tinyfs/examples/flash" "tinygo.org/x/drivers/flash" ) diff --git a/fatfs/examples/simple/main.go b/fatfs/examples/simple/main.go index 368bfd7..b9cba7a 100644 --- a/fatfs/examples/simple/main.go +++ b/fatfs/examples/simple/main.go @@ -5,8 +5,8 @@ import ( "io" "os" - "github.com/bgould/tinyfs" - "github.com/bgould/tinyfs/fatfs" + "github.com/tinygo-org/tinyfs" + "github.com/tinygo-org/tinyfs/fatfs" ) func main() { diff --git a/fatfs/go_fatfs.go b/fatfs/go_fatfs.go index e73743c..eb9ef88 100644 --- a/fatfs/go_fatfs.go +++ b/fatfs/go_fatfs.go @@ -11,9 +11,9 @@ import ( "time" "unsafe" - "github.com/bgould/tinyfs" - "github.com/bgould/tinyfs/internal/gopointer" - "github.com/bgould/tinyfs/internal/util" + "github.com/tinygo-org/tinyfs" + "github.com/tinygo-org/tinyfs/internal/gopointer" + "github.com/tinygo-org/tinyfs/internal/util" ) const ( diff --git a/fatfs/go_fatfs_callbacks.go b/fatfs/go_fatfs_callbacks.go index f9c27d4..692150a 100644 --- a/fatfs/go_fatfs_callbacks.go +++ b/fatfs/go_fatfs_callbacks.go @@ -8,8 +8,8 @@ import ( "time" "unsafe" - "github.com/bgould/tinyfs" - "github.com/bgould/tinyfs/internal/gopointer" + "github.com/tinygo-org/tinyfs" + "github.com/tinygo-org/tinyfs/internal/gopointer" ) import "C" diff --git a/fatfs/go_fatfs_test.go b/fatfs/go_fatfs_test.go index 9f5cde5..070e048 100644 --- a/fatfs/go_fatfs_test.go +++ b/fatfs/go_fatfs_test.go @@ -3,7 +3,7 @@ package fatfs import ( "testing" - "github.com/bgould/tinyfs" + "github.com/tinygo-org/tinyfs" ) const ( diff --git a/littlefs/go_lfs.go b/littlefs/go_lfs.go index ebbbe67..a5b4aae 100644 --- a/littlefs/go_lfs.go +++ b/littlefs/go_lfs.go @@ -12,9 +12,9 @@ import ( "time" "unsafe" - "github.com/bgould/tinyfs" - "github.com/bgould/tinyfs/internal/gopointer" - "github.com/bgould/tinyfs/internal/util" + "github.com/tinygo-org/tinyfs" + "github.com/tinygo-org/tinyfs/internal/gopointer" + "github.com/tinygo-org/tinyfs/internal/util" ) const ( diff --git a/littlefs/go_lfs_callbacks.go b/littlefs/go_lfs_callbacks.go index c480451..d1bc660 100644 --- a/littlefs/go_lfs_callbacks.go +++ b/littlefs/go_lfs_callbacks.go @@ -4,8 +4,8 @@ import ( "fmt" "unsafe" - "github.com/bgould/tinyfs" - "github.com/bgould/tinyfs/internal/gopointer" + "github.com/tinygo-org/tinyfs" + "github.com/tinygo-org/tinyfs/internal/gopointer" ) import "C" diff --git a/littlefs/go_lfs_test.go b/littlefs/go_lfs_test.go index 2a087cd..face3d7 100644 --- a/littlefs/go_lfs_test.go +++ b/littlefs/go_lfs_test.go @@ -6,7 +6,7 @@ import ( "os" "testing" - "github.com/bgould/tinyfs" + "github.com/tinygo-org/tinyfs" ) const ( From 924e60a7bcf89bbdc56e5a8f4286560df0bc56f9 Mon Sep 17 00:00:00 2001 From: sago35 Date: Fri, 14 May 2021 09:08:29 +0900 Subject: [PATCH 03/41] fatfs: translate osFlags into fatfs flags --- fatfs/go_fatfs.go | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/fatfs/go_fatfs.go b/fatfs/go_fatfs.go index eb9ef88..d400c21 100644 --- a/fatfs/go_fatfs.go +++ b/fatfs/go_fatfs.go @@ -274,7 +274,7 @@ func (l *FATFS) OpenFile(path string, flags int) (tinyfs.File, error) { // file file.typ = 0 file.hndl = unsafe.Pointer(C.go_fatfs_new_fil()) - errno = C.f_open(l.fs, (*C.FIL)(file.hndl), cs, C.BYTE(flags)) + errno = C.f_open(l.fs, (*C.FIL)(file.hndl), cs, translateFlags(flags)) } // check to make sure f_open/f_opendir didn't produce an error @@ -290,6 +290,35 @@ func (l *FATFS) OpenFile(path string, flags int) (tinyfs.File, error) { return file, nil } +// translateFlags translates osFlags such as os.O_RDONLY into fatfs flags. +// http://elm-chan.org/fsw/ff/doc/open.html +func translateFlags(osFlags int) C.BYTE { + var result C.BYTE + result = C.FA_READ + switch osFlags { + case os.O_RDONLY: + // r + result = C.FA_READ + case os.O_WRONLY | os.O_CREATE | os.O_TRUNC: + // w + result = C.FA_CREATE_ALWAYS | C.FA_WRITE + case os.O_WRONLY | os.O_CREATE | os.O_APPEND: + // a + result = C.FA_OPEN_APPEND | C.FA_WRITE + case os.O_RDWR: + // r+ + result = C.FA_READ | C.FA_WRITE + case os.O_RDWR | os.O_CREATE | os.O_TRUNC: + // w+ + result = C.FA_CREATE_ALWAYS | C.FA_WRITE | C.FA_READ + case os.O_RDWR | os.O_CREATE | os.O_APPEND: + // a+ + result = C.FA_OPEN_APPEND | C.FA_WRITE | C.FA_READ + default: + } + return result +} + type File struct { fs *FATFS typ uint8 From 5b00ebcf68bbd536895766f9e18cb34719117761 Mon Sep 17 00:00:00 2001 From: sago35 Date: Mon, 21 Jun 2021 03:41:25 +0900 Subject: [PATCH 04/41] fatfs: enable writing functions (#5) --- fatfs/ffconf.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fatfs/ffconf.h b/fatfs/ffconf.h index 96a1351..5c31826 100644 --- a/fatfs/ffconf.h +++ b/fatfs/ffconf.h @@ -12,7 +12,7 @@ / Function Configurations /---------------------------------------------------------------------------*/ -#define FF_FS_READONLY 1 +#define FF_FS_READONLY 0 /* This option switches read-only configuration. (0:Read/Write or 1:Read-only) / Read-only configuration removes writing API functions, f_write(), f_sync(), / f_unlink(), f_mkdir(), f_chmod(), f_rename(), f_truncate(), f_getfree() From e5632c7ceff2a5080e2755918d3b371bb222d61a Mon Sep 17 00:00:00 2001 From: BCG Date: Fri, 25 Jun 2021 19:31:08 -0400 Subject: [PATCH 05/41] updated import path for initial release --- examples/console/example.go | 5 +- examples/console/fatfs/qspi/main.go | 4 +- examples/console/fatfs/spi/main.go | 4 +- examples/console/littlefs/qspi/main.go | 4 +- examples/console/littlefs/spi/main.go | 4 +- .../simple-fatfs}/block_device_file.go | 0 .../simple => examples/simple-fatfs}/main.go | 6 +- fatfs/examples/flash/example.go | 502 ------------------ fatfs/examples/flash/qspi/main.go | 23 - fatfs/examples/flash/spi/main.go | 23 - fatfs/go_fatfs.go | 6 +- fatfs/go_fatfs_callbacks.go | 6 +- fatfs/go_fatfs_test.go | 2 +- go.mod | 2 +- go.sum | 2 - littlefs/go_lfs.go | 6 +- littlefs/go_lfs_callbacks.go | 4 +- littlefs/go_lfs_test.go | 2 +- 18 files changed, 26 insertions(+), 579 deletions(-) rename {fatfs/examples/simple => examples/simple-fatfs}/block_device_file.go (100%) rename {fatfs/examples/simple => examples/simple-fatfs}/main.go (96%) delete mode 100644 fatfs/examples/flash/example.go delete mode 100644 fatfs/examples/flash/qspi/main.go delete mode 100644 fatfs/examples/flash/spi/main.go diff --git a/examples/console/example.go b/examples/console/example.go index 6a78101..e9ba926 100644 --- a/examples/console/example.go +++ b/examples/console/example.go @@ -11,9 +11,8 @@ import ( "strings" "time" - "github.com/tinygo-org/tinyfs" - "tinygo.org/x/drivers/flash" + "tinygo.org/x/tinyfs" ) const consoleBufLen = 64 @@ -26,7 +25,7 @@ var ( input [consoleBufLen]byte - console = machine.UART0 + console = machine.Serial readyLED = machine.LED flashdev *flash.Device diff --git a/examples/console/fatfs/qspi/main.go b/examples/console/fatfs/qspi/main.go index 753331a..e7f60d9 100644 --- a/examples/console/fatfs/qspi/main.go +++ b/examples/console/fatfs/qspi/main.go @@ -6,9 +6,9 @@ import ( "machine" "time" - "github.com/tinygo-org/tinyfs/examples/console" - "github.com/tinygo-org/tinyfs/fatfs" "tinygo.org/x/drivers/flash" + "tinygo.org/x/tinyfs/examples/console" + "tinygo.org/x/tinyfs/fatfs" ) var ( diff --git a/examples/console/fatfs/spi/main.go b/examples/console/fatfs/spi/main.go index f259b35..1d200c3 100644 --- a/examples/console/fatfs/spi/main.go +++ b/examples/console/fatfs/spi/main.go @@ -6,9 +6,9 @@ import ( "machine" "time" - "github.com/tinygo-org/tinyfs/examples/console" - "github.com/tinygo-org/tinyfs/fatfs" "tinygo.org/x/drivers/flash" + "tinygo.org/x/tinyfs/examples/console" + "tinygo.org/x/tinyfs/fatfs" ) var ( diff --git a/examples/console/littlefs/qspi/main.go b/examples/console/littlefs/qspi/main.go index ea282a9..ccd29e7 100644 --- a/examples/console/littlefs/qspi/main.go +++ b/examples/console/littlefs/qspi/main.go @@ -6,8 +6,8 @@ import ( "machine" "time" - "github.com/tinygo-org/tinyfs/examples/console" - "github.com/tinygo-org/tinyfs/littlefs" + "tinygo.org/x/tinyfs/examples/console" + "tinygo.org/x/tinyfs/littlefs" "tinygo.org/x/drivers/flash" ) diff --git a/examples/console/littlefs/spi/main.go b/examples/console/littlefs/spi/main.go index 35cf873..6cd49aa 100644 --- a/examples/console/littlefs/spi/main.go +++ b/examples/console/littlefs/spi/main.go @@ -6,8 +6,8 @@ import ( "machine" "time" - "github.com/tinygo-org/tinyfs/examples/console" - "github.com/tinygo-org/tinyfs/littlefs" + "tinygo.org/x/tinyfs/examples/console" + "tinygo.org/x/tinyfs/littlefs" "tinygo.org/x/drivers/flash" ) diff --git a/fatfs/examples/simple/block_device_file.go b/examples/simple-fatfs/block_device_file.go similarity index 100% rename from fatfs/examples/simple/block_device_file.go rename to examples/simple-fatfs/block_device_file.go diff --git a/fatfs/examples/simple/main.go b/examples/simple-fatfs/main.go similarity index 96% rename from fatfs/examples/simple/main.go rename to examples/simple-fatfs/main.go index b9cba7a..f89fe8d 100644 --- a/fatfs/examples/simple/main.go +++ b/examples/simple-fatfs/main.go @@ -5,8 +5,8 @@ import ( "io" "os" - "github.com/tinygo-org/tinyfs" - "github.com/tinygo-org/tinyfs/fatfs" + "tinygo.org/x/tinyfs" + "tinygo.org/x/tinyfs/fatfs" ) func main() { @@ -68,7 +68,7 @@ func main() { for i := 0; i < 20; i++ { if _, err := f.Write([]byte("01234567890abcdef")); err != nil { - fmt.Println("Could not write: %s", err.Error()) + fmt.Println("Could not write: ", err.Error()) os.Exit(1) } } diff --git a/fatfs/examples/flash/example.go b/fatfs/examples/flash/example.go deleted file mode 100644 index b933efd..0000000 --- a/fatfs/examples/flash/example.go +++ /dev/null @@ -1,502 +0,0 @@ -// +build tinygo - -package flash_example - -import ( - "fmt" - "io" - "machine" - "os" - "strconv" - "strings" - "time" - - "github.com/tinygo-org/tinyfs" - "github.com/tinygo-org/tinyfs/fatfs" - - "tinygo.org/x/drivers/flash" -) - -const consoleBufLen = 64 -const storageBufLen = 512 - -var ( - debug = false - - input [consoleBufLen]byte - store [storageBufLen]byte - - console = machine.UART0 - readyLED = machine.LED - - flashdev *flash.Device - blockdev tinyfs.BlockDevice - fs *fatfs.FATFS - - currdir = "/" - - commands = map[string]cmdfunc{ - "": noop, - "dbg": dbg, - "lsblk": lsblk, - "mount": mount, - "umount": umount, - "format": format, - "xxd": xxd, - "ls": ls, - "samples": samples, - "mkdir": mkdir, - "cat": cat, - "create": create, - "write": write, - "rm": rm, - } -) - -type blockDev struct { - *flash.Device -} - -func (d *blockDev) Sync() error { - return nil -} - -func (d *blockDev) SectorSize() int64 { - return 512 -} - -type cmdfunc func(argv []string) - -const ( - StateInput = iota - StateEscape - StateEscBrc - StateCSI -) - -func RunFor(dev *flash.Device) { - time.Sleep(3 * time.Second) - - flashdev = dev - - readyLED.Configure(machine.PinConfig{Mode: machine.PinOutput}) - readyLED.High() - - config := &flash.DeviceConfig{Identifier: flash.DefaultDeviceIdentifier} - if err := dev.Configure(config); err != nil { - for { - time.Sleep(5 * time.Second) - println("Config was not valid: "+err.Error(), "\r") - } - } - - readyLED.Low() - println("SPI Configured. Reading flash info") - - blockdev = &blockDev{flashdev} - fs = fatfs.New(blockdev) - - prompt() - - var state = StateInput - - for i := 0; ; { - if console.Buffered() > 0 { - data, _ := console.ReadByte() - if debug { - fmt.Printf("\rdata: %x\r\n\r", data) - prompt() - console.Write(input[:i]) - } - switch state { - case StateInput: - switch data { - case 0x8: - fallthrough - case 0x7f: // this is probably wrong... works on my machine tho :) - // backspace - if i > 0 { - i -= 1 - console.Write([]byte{0x8, 0x20, 0x8}) - } - case 13: - // return key - if console.Buffered() > 0 { - data, _ := console.ReadByte() - if data != 10 { - println("\r\nunexpected: \r", int(data)) - } - } - console.Write([]byte("\r\n")) - runCommand(string(input[:i])) - prompt() - - i = 0 - continue - case 27: - // escape - state = StateEscape - default: - // anything else, just echo the character if it is printable - if strconv.IsPrint(rune(data)) { - if i < (consoleBufLen - 1) { - console.WriteByte(data) - input[i] = data - i++ - } - } - } - case StateEscape: - switch data { - case 0x5b: - state = StateEscBrc - default: - state = StateInput - } - default: - // TODO: handle escape sequences - state = StateInput - } - } - } -} - -func runCommand(line string) { - argv := strings.SplitN(strings.TrimSpace(line), " ", -1) - cmd := argv[0] - cmdfn, ok := commands[cmd] - if !ok { - println("unknown command: " + line) - return - } - cmdfn(argv) -} - -func noop(argv []string) {} - -func dbg(argv []string) { - if debug { - debug = false - println("Console debugging off") - } else { - debug = true - println("Console debugging on") - } -} - -func lsblk(argv []string) { - attrs := flashdev.Attrs() - status1, _ := flashdev.ReadStatus() - status2, _ := flashdev.ReadStatus2() - serialNumber1, _ := flashdev.ReadSerialNumber() - fmt.Printf( - "\n-------------------------------------\r\n"+ - " Device Information: \r\n"+ - "-------------------------------------\r\n"+ - " JEDEC ID: %6X\r\n"+ - " Serial: %08X\r\n"+ - " Status 1: %02x\r\n"+ - " Status 2: %02x\r\n"+ - " \r\n"+ - " Max clock speed (MHz): %d\r\n"+ - " Has Sector Protection: %t\r\n"+ - " Supports Fast Reads: %t\r\n"+ - " Supports QSPI Reads: %t\r\n"+ - " Supports QSPI Write: %t\r\n"+ - " Write Status Split: %t\r\n"+ - " Single Status Byte: %t\r\n"+ - "-------------------------------------\r\n\r\n", - attrs.JedecID.Uint32(), - serialNumber1, - status1, - status2, - attrs.MaxClockSpeedMHz, - attrs.HasSectorProtection, - attrs.SupportsFastRead, - attrs.SupportsQSPI, - attrs.SupportsQSPIWrites, - attrs.WriteStatusSplit, - attrs.SingleStatusByte, - ) -} - -func mount(argv []string) { - if err := fs.Mount(); err != nil { - println("Could not mount filesystem: " + err.Error() + "\r\n") - } else { - println("Successfully mounted filesystem.\r\n") - } -} - -func format(argv []string) { - if err := fs.Format(); err != nil { - println("Could not format filesystem: " + err.Error() + "\r\n") - } else { - println("Successfully formatted filesystem.\r\n") - } -} - -func umount(argv []string) { - if err := fs.Unmount(); err != nil { - println("Could not unmount filesystem: " + err.Error() + "\r\n") - } else { - println("Successfully unmounted filesystem.\r\n") - } -} - -func ls(argv []string) { - path := "/" - if len(argv) > 1 { - path = strings.TrimSpace(argv[1]) - } - dir, err := fs.Open(path) - if err != nil { - fmt.Printf("Could not open directory %s: %v\n", path, err) - return - } - defer dir.Close() - infos, err := dir.Readdir(0) - _ = infos - if err != nil { - fmt.Printf("Could not read directory %s: %v\n", path, err) - return - } - for _, info := range infos { - s := "-rwxrwxrwx" - if info.IsDir() { - s = "drwxrwxrwx" - } - fmt.Printf("%s %5d %s\n", s, info.Size(), info.Name()) - } -} - -func mkdir(argv []string) { - tgt := "" - if len(argv) == 2 { - tgt = strings.TrimSpace(argv[1]) - } - if debug { - println("Trying mkdir to " + tgt) - } - if tgt == "" { - println("Usage: mkdir ") - return - } - err := fs.Mkdir(tgt, 0777) - if err != nil { - println("Could not mkdir " + tgt + ": " + err.Error()) - } -} - -func rm(argv []string) { - tgt := "" - if len(argv) == 2 { - tgt = strings.TrimSpace(argv[1]) - } - if debug { - println("Trying rm to " + tgt) - } - if tgt == "" { - println("Usage: rm ") - return - } - err := fs.Remove(tgt) - if err != nil { - println("Could not rm " + tgt + ": " + err.Error()) - } -} - -func samples(argv []string) { - buf := make([]byte, 90) - for i := 0; i < 5; i++ { - name := fmt.Sprintf("file%d.txt", i) - if bytes, err := createSampleFile(name, buf); err != nil { - fmt.Printf("%s\r\n", err) - return - } else { - fmt.Printf("wrote %d bytes to %s\r\n", bytes, name) - } - } -} - -func create(argv []string) { - tgt := "" - if len(argv) == 2 { - tgt = strings.TrimSpace(argv[1]) - } - if debug { - println("Trying create to " + tgt) - } - buf := make([]byte, 90) - if bytes, err := createSampleFile(tgt, buf); err != nil { - fmt.Printf("%s\r\n", err) - return - } else { - fmt.Printf("wrote %d bytes to %s\r\n", bytes, tgt) - } -} - -func write(argv []string) { - tgt := "" - if len(argv) == 2 { - tgt = strings.TrimSpace(argv[1]) - } - if debug { - println("Trying receive to " + tgt) - } - buf := make([]byte, 1) - f, err := fs.OpenFile(tgt, os.O_WRONLY|os.O_CREATE) - if err != nil { - fmt.Printf("error opening %s: %s\r\n", tgt, err.Error()) - return - } - defer f.Close() - var n int - for { - if console.Buffered() > 0 { - data, _ := console.ReadByte() - switch data { - case 0x04: - fmt.Printf("wrote %d bytes to %s\r\n", n, tgt) - return - default: - // anything else, just echo the character if it is printable - if strconv.IsPrint(rune(data)) { - console.WriteByte(data) - } - buf[0] = data - if _, err := f.Write(buf); err != nil { - fmt.Printf("\nerror writing: %s\r\n", err) - return - } - n++ - } - } - } -} - -func createSampleFile(name string, buf []byte) (int, error) { - for j := uint8(0); j < uint8(len(buf)); j++ { - buf[j] = 0x20 + j - } - f, err := fs.OpenFile(name, os.O_CREATE|os.O_WRONLY) - if err != nil { - return 0, fmt.Errorf("error opening %s: %s", name, err.Error()) - } - defer f.Close() - bytes, err := f.Write(buf) - if err != nil { - return 0, fmt.Errorf("error writing %s: %s", name, err.Error()) - } - return bytes, nil -} - -func cat(argv []string) { - tgt := "" - if len(argv) == 2 { - tgt = strings.TrimSpace(argv[1]) - } - if debug { - println("Trying to cat to " + tgt) - } - if tgt == "" { - println("Usage: cat ") - return - } - if debug { - println("Getting entry") - } - f, err := fs.Open(tgt) - if err != nil { - println("Could not open: " + err.Error()) - return - } - defer f.Close() - if f.IsDir() { - println("Not a file: " + tgt) - return - } - off := 0x0 - buf := make([]byte, 64) - for { - n, err := f.Read(buf) - if err != nil { - if err == io.EOF { - break - } - println("Error reading " + tgt + ": " + err.Error()) - } - xxdfprint(os.Stdout, uint32(off), buf[:n]) - off += n - } -} - -func xxd(argv []string) { - var err error - var addr uint64 = 0x0 - var size int = 64 - switch len(argv) { - case 3: - if size, err = strconv.Atoi(argv[2]); err != nil { - println("Invalid size argument: " + err.Error() + "\r\n") - return - } - if size > storageBufLen || size < 1 { - fmt.Printf("Size of hexdump must be greater than 0 and less than %d\r\n", storageBufLen) - return - } - fallthrough - case 2: - /* - if argv[1][:2] != "0x" { - println("Invalid hex address (should start with 0x)") - return - } - */ - if addr, err = strconv.ParseUint(argv[1], 16, 32); err != nil { - println("Invalid address: " + err.Error() + "\r\n") - return - } - fallthrough - case 1: - // no args supplied, so nothing to do here, just use the defaults - default: - println("usage: xxd \r\n") - return - } - buf := store[0:size] - //bsz := uint64(flash.SectorSize) - blockdev.ReadAt(buf, int64(addr)) - //ReadBlock(uint32(addr/bsz), uint32(addr%bsz), buf) - //fatdisk.ReadAt(buf, int64(addr)) - xxdfprint(os.Stdout, uint32(addr), buf) -} - -func xxdfprint(w io.Writer, offset uint32, b []byte) { - var l int - var buf16 = make([]byte, 16) - var padding = "" - for i, c := 0, len(b); i < c; i += 16 { - l = i + 16 - if l >= c { - padding = strings.Repeat(" ", (l-c)*3) - l = c - } - fmt.Fprintf(w, "%08x: % x "+padding, offset+uint32(i), b[i:l]) - for j, n := 0, l-i; j < 16; j++ { - if j >= n { - buf16[j] = ' ' - } else if !strconv.IsPrint(rune(b[i+j])) { - buf16[j] = '.' - } else { - buf16[j] = b[i+j] - } - } - console.Write(buf16) - println() - } -} - -func prompt() { - print("==> ") -} diff --git a/fatfs/examples/flash/qspi/main.go b/fatfs/examples/flash/qspi/main.go deleted file mode 100644 index d1f3261..0000000 --- a/fatfs/examples/flash/qspi/main.go +++ /dev/null @@ -1,23 +0,0 @@ -// +build tinygo - -package main - -import ( - "machine" - - example "github.com/tinygo-org/tinyfs/examples/flash" - "tinygo.org/x/drivers/flash" -) - -func main() { - example.RunFor( - flash.NewQSPI( - machine.QSPI_CS, - machine.QSPI_SCK, - machine.QSPI_DATA0, - machine.QSPI_DATA1, - machine.QSPI_DATA2, - machine.QSPI_DATA3, - ), - ) -} diff --git a/fatfs/examples/flash/spi/main.go b/fatfs/examples/flash/spi/main.go deleted file mode 100644 index 4a9b2cf..0000000 --- a/fatfs/examples/flash/spi/main.go +++ /dev/null @@ -1,23 +0,0 @@ -// +build tinygo - -package main - -import ( - "machine" - - example "github.com/tinygo-org/tinyfs/examples/flash" - - "tinygo.org/x/drivers/flash" -) - -func main() { - example.RunFor( - flash.NewSPI( - &machine.SPI1, - machine.SPI1_MOSI_PIN, - machine.SPI1_MISO_PIN, - machine.SPI1_SCK_PIN, - machine.SPI1_CS_PIN, - ), - ) -} diff --git a/fatfs/go_fatfs.go b/fatfs/go_fatfs.go index d400c21..65c023f 100644 --- a/fatfs/go_fatfs.go +++ b/fatfs/go_fatfs.go @@ -11,9 +11,9 @@ import ( "time" "unsafe" - "github.com/tinygo-org/tinyfs" - "github.com/tinygo-org/tinyfs/internal/gopointer" - "github.com/tinygo-org/tinyfs/internal/util" + "tinygo.org/x/tinyfs" + "tinygo.org/x/tinyfs/internal/gopointer" + "tinygo.org/x/tinyfs/internal/util" ) const ( diff --git a/fatfs/go_fatfs_callbacks.go b/fatfs/go_fatfs_callbacks.go index 692150a..bf0822c 100644 --- a/fatfs/go_fatfs_callbacks.go +++ b/fatfs/go_fatfs_callbacks.go @@ -8,12 +8,10 @@ import ( "time" "unsafe" - "github.com/tinygo-org/tinyfs" - "github.com/tinygo-org/tinyfs/internal/gopointer" + "tinygo.org/x/tinyfs" + "tinygo.org/x/tinyfs/internal/gopointer" ) -import "C" - const ( debug = false ) diff --git a/fatfs/go_fatfs_test.go b/fatfs/go_fatfs_test.go index 070e048..39d1669 100644 --- a/fatfs/go_fatfs_test.go +++ b/fatfs/go_fatfs_test.go @@ -3,7 +3,7 @@ package fatfs import ( "testing" - "github.com/tinygo-org/tinyfs" + "tinygo.org/x/tinyfs" ) const ( diff --git a/go.mod b/go.mod index d23c6ac..7860252 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/tinygo-org/tinyfs +module tinygo.org/x/tinyfs go 1.14 diff --git a/go.sum b/go.sum index 70b699c..03460ba 100644 --- a/go.sum +++ b/go.sum @@ -5,7 +5,5 @@ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -tinygo.org/x/drivers v0.13.0 h1:ohzhFiPb/5dcRop3X+Gdvsr6uswmnpfMX9KsAMtgtTM= -tinygo.org/x/drivers v0.13.0/go.mod h1:mShi1lpVtJFpApkZgwyrzDKHToeGfWIuB08utyHxZ7g= tinygo.org/x/drivers v0.16.0 h1:U+cOLx21iT8clWtBqbHzXZE2yDkCrjk7AitTdwLQwJc= tinygo.org/x/drivers v0.16.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= diff --git a/littlefs/go_lfs.go b/littlefs/go_lfs.go index a5b4aae..bdd285d 100644 --- a/littlefs/go_lfs.go +++ b/littlefs/go_lfs.go @@ -12,9 +12,9 @@ import ( "time" "unsafe" - "github.com/tinygo-org/tinyfs" - "github.com/tinygo-org/tinyfs/internal/gopointer" - "github.com/tinygo-org/tinyfs/internal/util" + "tinygo.org/x/tinyfs" + "tinygo.org/x/tinyfs/internal/gopointer" + "tinygo.org/x/tinyfs/internal/util" ) const ( diff --git a/littlefs/go_lfs_callbacks.go b/littlefs/go_lfs_callbacks.go index d1bc660..e79d08f 100644 --- a/littlefs/go_lfs_callbacks.go +++ b/littlefs/go_lfs_callbacks.go @@ -4,8 +4,8 @@ import ( "fmt" "unsafe" - "github.com/tinygo-org/tinyfs" - "github.com/tinygo-org/tinyfs/internal/gopointer" + "tinygo.org/x/tinyfs" + "tinygo.org/x/tinyfs/internal/gopointer" ) import "C" diff --git a/littlefs/go_lfs_test.go b/littlefs/go_lfs_test.go index face3d7..617e0e0 100644 --- a/littlefs/go_lfs_test.go +++ b/littlefs/go_lfs_test.go @@ -6,7 +6,7 @@ import ( "os" "testing" - "github.com/tinygo-org/tinyfs" + "tinygo.org/x/tinyfs" ) const ( From 31b3327eedd7e97edf8fb58de68e33b4e0123c68 Mon Sep 17 00:00:00 2001 From: BCG Date: Sat, 26 Jun 2021 08:08:19 -0400 Subject: [PATCH 06/41] added documentation for initial release --- CHANGELOG.md | 6 ++++++ CONTRIBUTING.md | 40 ++++++++++++++++++++++++++++++++++++++++ LICENSE | 27 +++++++++++++++++++++++++++ README.md | 4 ++++ 4 files changed, 77 insertions(+) create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 README.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..738ca0e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,6 @@ +0.1.0 +--- +- **first release** + - This is the first official release of TinyFS repo, coinciding with TinyGo 0.19.0. The following filesystems are supported: + - LittleFS (https://github.com/littlefs-project/littlefs) + - FAT (http://elm-chan.org/fsw/ff/00index_e.html) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..a035a15 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,40 @@ +# How to contribute + +Thank you for your interest in improving TinyFS. + +We would like your help to make this project better, so we appreciate any contributions. See if one of the following descriptions matches your situation: + +### New to TinyGo + +We'd love to get your feedback on getting started with TinyGo. Run into any difficulty, confusion, or anything else? You are not alone. We want to know about your experience, so we can help the next people. Please open a Github issue with your questions, or you can also get in touch directly with us on our Slack channel at [https://gophers.slack.com/messages/CDJD3SUP6](https://gophers.slack.com/messages/CDJD3SUP6). + +### Something is not working as you expect + +Please open a Github issue with your problem, and we will be happy to assist. + +### Some specific feature does not appear to be in TinyFS + +We probably have not implemented it yet. Your contribution would be greatly appreciated. + +Please first open a Github issue. We want to help, and also make sure that there is no duplications of efforts. Sometimes what you need is already being worked on by someone else. + +## How to use our Github repository + +The `release` branch of this repo will always have the latest released version of TinyFS. All of the active development work for the next release will take place in the `dev` branch. TinyFS will use semantic versioning and will create a tag/release for each release. + +Here is how to contribute back some code or documentation: + +- Fork repo +- Create a feature branch off of the `dev` branch +- Make some useful change +- Make sure the tests still pass +- Submit a pull request against the `dev` branch. +- Be kind + +## How to run tests + +To run the tests: + +``` +make test +``` diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7634817 --- /dev/null +++ b/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2018-2021 The TinyGo Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c792dff --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# TinyFS + +TinyFS contains Go implementations of embedded filesystems. The packages in +this module require CGo support and are [TinyGo](https://tinygo.org/) compatible. From e302f75c039cf293b3894d285c93ebf69f52a5a6 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Thu, 1 Jul 2021 08:25:13 +0200 Subject: [PATCH 07/41] build: ignore build directory Signed-off-by: deadprogram --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9ef9604 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build + From 6499c76744281b35c351bef00a7ba61065c419ef Mon Sep 17 00:00:00 2001 From: deadprogram Date: Thu, 1 Jul 2021 08:26:10 +0200 Subject: [PATCH 08/41] examples: correct SPI pin naming to match current boards Signed-off-by: deadprogram --- examples/console/fatfs/spi/main.go | 4 ++-- examples/console/littlefs/spi/main.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/console/fatfs/spi/main.go b/examples/console/fatfs/spi/main.go index 1d200c3..e4f562a 100644 --- a/examples/console/fatfs/spi/main.go +++ b/examples/console/fatfs/spi/main.go @@ -14,8 +14,8 @@ import ( var ( blockDevice = flash.NewSPI( &machine.SPI1, - machine.SPI1_MOSI_PIN, - machine.SPI1_MISO_PIN, + machine.SPI1_SDO_PIN, + machine.SPI1_SDI_PIN, machine.SPI1_SCK_PIN, machine.SPI1_CS_PIN, ) diff --git a/examples/console/littlefs/spi/main.go b/examples/console/littlefs/spi/main.go index 6cd49aa..c8e7308 100644 --- a/examples/console/littlefs/spi/main.go +++ b/examples/console/littlefs/spi/main.go @@ -6,16 +6,16 @@ import ( "machine" "time" + "tinygo.org/x/drivers/flash" "tinygo.org/x/tinyfs/examples/console" "tinygo.org/x/tinyfs/littlefs" - "tinygo.org/x/drivers/flash" ) var ( blockDevice = flash.NewSPI( &machine.SPI1, - machine.SPI1_MOSI_PIN, - machine.SPI1_MISO_PIN, + machine.SPI1_SDO_PIN, + machine.SPI1_SDI_PIN, machine.SPI1_SCK_PIN, machine.SPI1_CS_PIN, ) From cf18454221d20f81761390c965cc82018fa3ee45 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Thu, 1 Jul 2021 08:26:49 +0200 Subject: [PATCH 09/41] all: add version ID for support purposes Signed-off-by: deadprogram --- version.go | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 version.go diff --git a/version.go b/version.go new file mode 100644 index 0000000..7844219 --- /dev/null +++ b/version.go @@ -0,0 +1,5 @@ +package tinyfs + +// Version returns a user-readable string showing the version of the package for support purposes. +// Update this value before release of new version of software. +const Version = "0.1.0" From d795dab062fa4c18b1d2447d3691f2f5db21b7c9 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Thu, 1 Jul 2021 08:27:19 +0200 Subject: [PATCH 10/41] build: add CircleCI build Signed-off-by: deadprogram --- .circleci/config.yml | 17 +++++++++++++++++ Makefile | 23 +++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 .circleci/config.yml create mode 100644 Makefile diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..67f7497 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,17 @@ +# Golang CircleCI 2.0 configuration file +# +# Check https://circleci.com/docs/2.0/language-go/ for more details +version: 2 +jobs: + build: + docker: + - image: tinygo/tinygo-dev + steps: + - checkout + - run: tinygo version + - run: + name: "Enforce Go Formatted Code" + command: make fmt-check + - run: + name: "Run build and smoke tests" + command: make smoke-test diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7f883b6 --- /dev/null +++ b/Makefile @@ -0,0 +1,23 @@ + +clean: + @rm -rf build + +FMT_PATHS = ./*.go ./examples/**/*.go ./fatfs/*.go ./littlefs/*.go + +fmt-check: + @unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1 + +smoke-test: + @mkdir -p build + tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/simple-fatfs/ + @md5sum ./build/test.hex + tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/console/fatfs/spi/ + @md5sum ./build/test.hex + tinygo build -size short -o ./build/test.hex -target=itsybitsy-m4 ./examples/console/fatfs/qspi/ + @md5sum ./build/test.hex + tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/console/littlefs/spi/ + @md5sum ./build/test.hex + tinygo build -size short -o ./build/test.hex -target=itsybitsy-m4 ./examples/console/littlefs/qspi/ + @md5sum ./build/test.hex + +test: clean fmt-check smoke-test From 274cc9fbf9052af6782fa44721c51939f1cb87a2 Mon Sep 17 00:00:00 2001 From: Ron Evans Date: Thu, 1 Jul 2021 08:28:53 +0200 Subject: [PATCH 11/41] Add .circleci/config.yml From d418d905efe79b10f13681e51524c4cb7596d5be Mon Sep 17 00:00:00 2001 From: deadprogram Date: Thu, 1 Jul 2021 08:48:40 +0200 Subject: [PATCH 12/41] docs: add CircleCI badge Signed-off-by: deadprogram --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c792dff..9bd1778 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ # TinyFS +[![CircleCI](https://circleci.com/gh/tinygo-org/tinyfs/tree/dev.svg?style=svg)](https://circleci.com/gh/tinygo-org/tinyfs/tree/dev) + TinyFS contains Go implementations of embedded filesystems. The packages in this module require CGo support and are [TinyGo](https://tinygo.org/) compatible. From 23518e3d73cfa3c3a3bd8bc6b9621584531f718a Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sun, 13 Mar 2022 20:40:55 +0100 Subject: [PATCH 13/41] all: correct Go fmt for Go 1.17 and backwards Signed-off-by: deadprogram --- examples/console/example.go | 1 + examples/simple-fatfs/block_device_file.go | 1 + tinygo_os.go | 1 + 3 files changed, 3 insertions(+) diff --git a/examples/console/example.go b/examples/console/example.go index e9ba926..dfd1fed 100644 --- a/examples/console/example.go +++ b/examples/console/example.go @@ -1,3 +1,4 @@ +//go:build tinygo // +build tinygo package console diff --git a/examples/simple-fatfs/block_device_file.go b/examples/simple-fatfs/block_device_file.go index f29169a..cc89d70 100644 --- a/examples/simple-fatfs/block_device_file.go +++ b/examples/simple-fatfs/block_device_file.go @@ -1,3 +1,4 @@ +//go:build !tinygo // +build !tinygo package main diff --git a/tinygo_os.go b/tinygo_os.go index 0e6dbe4..c5badd5 100644 --- a/tinygo_os.go +++ b/tinygo_os.go @@ -1,3 +1,4 @@ +//go:build tinygo && osfilesystem // +build tinygo,osfilesystem package tinyfs From 78677ba3b7b8a3dadabaf75240d8e31160f78abf Mon Sep 17 00:00:00 2001 From: deadprogram Date: Fri, 29 Apr 2022 10:46:46 +0200 Subject: [PATCH 14/41] docs: update license year Signed-off-by: deadprogram --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 7634817..67b74a5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018-2021 The TinyGo Authors. All rights reserved. +Copyright (c) 2018-2022 The TinyGo Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are From d2d45bfb66cdaa0e684eb2402df3bf1d6b0f10e3 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Fri, 29 Apr 2022 10:48:18 +0200 Subject: [PATCH 15/41] all: update to drivers 0.19 Signed-off-by: deadprogram --- go.mod | 4 ++-- go.sum | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7860252..b5db456 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module tinygo.org/x/tinyfs -go 1.14 +go 1.16 -require tinygo.org/x/drivers v0.16.0 +require tinygo.org/x/drivers v0.19.0 diff --git a/go.sum b/go.sum index 03460ba..5237387 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,23 @@ +github.com/bgould/http v0.0.0-20190627042742-d268792bdee7/go.mod h1:BTqvVegvwifopl4KTEDth6Zezs9eR+lCWhvGKvkxJHE= github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -tinygo.org/x/drivers v0.16.0 h1:U+cOLx21iT8clWtBqbHzXZE2yDkCrjk7AitTdwLQwJc= +tinygo.org/x/drivers v0.14.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= +tinygo.org/x/drivers v0.15.1/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= tinygo.org/x/drivers v0.16.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= +tinygo.org/x/drivers v0.19.0 h1:x/TIC8SFWeGViJvcYO1ATjgwSNF7hHN2ouAyjMUXI2Q= +tinygo.org/x/drivers v0.19.0/go.mod h1:uJD/l1qWzxzLx+vcxaW0eY464N5RAgFi1zTVzASFdqI= +tinygo.org/x/tinyfont v0.2.1/go.mod h1:eLqnYSrFRjt5STxWaMeOWJTzrKhXqpWw7nU3bPfKOAM= +tinygo.org/x/tinyfs v0.1.0/go.mod h1:ysc8Y92iHfhTXeyEM9+c7zviUQ4fN9UCFgSOFfMWv20= +tinygo.org/x/tinyterm v0.1.0/go.mod h1:/DDhNnGwNF2/tNgHywvyZuCGnbH3ov49Z/6e8LPLRR4= From 3bd21c19a26bc5e98af501cfe77f00518d8115be Mon Sep 17 00:00:00 2001 From: deadprogram Date: Fri, 29 Apr 2022 10:49:41 +0200 Subject: [PATCH 16/41] all: update for TinyGo 0.23 Signed-off-by: deadprogram --- CHANGELOG.md | 6 ++++++ version.go | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 738ca0e..95802ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +0.2.0 +--- + +- Compatability with TinyGo 0.23.0 + + 0.1.0 --- - **first release** diff --git a/version.go b/version.go index 7844219..0b35b0b 100644 --- a/version.go +++ b/version.go @@ -2,4 +2,4 @@ package tinyfs // Version returns a user-readable string showing the version of the package for support purposes. // Update this value before release of new version of software. -const Version = "0.1.0" +const Version = "0.2.0" From 50efb4e00363c3475920cb5a6f880ca5e203c74d Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sat, 18 Feb 2023 11:03:12 +0100 Subject: [PATCH 17/41] ci: remove circleci --- .circleci/config.yml | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 67f7497..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,17 +0,0 @@ -# Golang CircleCI 2.0 configuration file -# -# Check https://circleci.com/docs/2.0/language-go/ for more details -version: 2 -jobs: - build: - docker: - - image: tinygo/tinygo-dev - steps: - - checkout - - run: tinygo version - - run: - name: "Enforce Go Formatted Code" - command: make fmt-check - - run: - name: "Run build and smoke tests" - command: make smoke-test From 60969e705cd3861cf4a8a900cc97e19ac21db1bd Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sat, 18 Feb 2023 11:03:30 +0100 Subject: [PATCH 18/41] modules: update to latest version of tinygo drivers Signed-off-by: deadprogram --- go.mod | 4 ++-- go.sum | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b5db456..fb0828a 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module tinygo.org/x/tinyfs -go 1.16 +go 1.18 -require tinygo.org/x/drivers v0.19.0 +require tinygo.org/x/drivers v0.24.0 diff --git a/go.sum b/go.sum index 5237387..704ce40 100644 --- a/go.sum +++ b/go.sum @@ -2,10 +2,13 @@ github.com/bgould/http v0.0.0-20190627042742-d268792bdee7/go.mod h1:BTqvVegvwifo github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/hajimehoshi/go-jisx0208 v1.0.0/go.mod h1:yYxEStHL7lt9uL+AbdWgW9gBumwieDoZCiB1f/0X0as= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/sago35/go-bdf v0.0.0-20200313142241-6c17821c91c4/go.mod h1:rOebXGuMLsXhZAC6mF/TjxONsm45498ZyzVhel++6KM= github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= +golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -16,8 +19,11 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T tinygo.org/x/drivers v0.14.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= tinygo.org/x/drivers v0.15.1/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= tinygo.org/x/drivers v0.16.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= -tinygo.org/x/drivers v0.19.0 h1:x/TIC8SFWeGViJvcYO1ATjgwSNF7hHN2ouAyjMUXI2Q= tinygo.org/x/drivers v0.19.0/go.mod h1:uJD/l1qWzxzLx+vcxaW0eY464N5RAgFi1zTVzASFdqI= +tinygo.org/x/drivers v0.24.0 h1:9yOV/GbdDXg+EKOWJl+FHLKMSfSI/skQ8Gmo8N/KzQo= +tinygo.org/x/drivers v0.24.0/go.mod h1:J4+51Li1kcfL5F93kmnDWEEzQF3bLGz0Am3Q7E2a8/E= tinygo.org/x/tinyfont v0.2.1/go.mod h1:eLqnYSrFRjt5STxWaMeOWJTzrKhXqpWw7nU3bPfKOAM= +tinygo.org/x/tinyfont v0.3.0/go.mod h1:+TV5q0KpwSGRWnN+ITijsIhrWYJkoUCp9MYELjKpAXk= tinygo.org/x/tinyfs v0.1.0/go.mod h1:ysc8Y92iHfhTXeyEM9+c7zviUQ4fN9UCFgSOFfMWv20= +tinygo.org/x/tinyfs v0.2.0/go.mod h1:6ZHYdvB3sFYeMB3ypmXZCNEnFwceKc61ADYTYHpep1E= tinygo.org/x/tinyterm v0.1.0/go.mod h1:/DDhNnGwNF2/tNgHywvyZuCGnbH3ov49Z/6e8LPLRR4= From 810473ec1d43db3ae6423cd870559f72779b32b4 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sat, 18 Feb 2023 11:03:50 +0100 Subject: [PATCH 19/41] ci: switch to GH actions Signed-off-by: deadprogram --- .github/workflows/build.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..cb99b98 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,26 @@ +name: Build + +on: + pull_request: + push: + branches: + - dev + - release + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + container: tinygo/tinygo-dev + steps: + - name: Work around CVE-2022-24765 + # We're not on a multi-user machine, so this is safe. + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + - name: Checkout + uses: actions/checkout@v3 + - name: TinyGo version check + run: tinygo version + - name: Enforce Go Formatted Code + run: make fmt-check + - name: Run build and smoke tests + run: make smoke-test From 558c8b3b142538f8141a7639caa988ac50602e24 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sat, 18 Feb 2023 11:05:17 +0100 Subject: [PATCH 20/41] docs: switch CI badge in README to GH action Signed-off-by: deadprogram --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9bd1778..884d493 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # TinyFS -[![CircleCI](https://circleci.com/gh/tinygo-org/tinyfs/tree/dev.svg?style=svg)](https://circleci.com/gh/tinygo-org/tinyfs/tree/dev) +[![Build](https://github.com/tinygo-org/tinyfs/actions/workflows/build.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinyfs/actions/workflows/build.yml) TinyFS contains Go implementations of embedded filesystems. The packages in this module require CGo support and are [TinyGo](https://tinygo.org/) compatible. From acb50e5e1b4f7346f92106794227eda63236453f Mon Sep 17 00:00:00 2001 From: BCG Date: Sun, 26 Feb 2023 22:39:03 -0500 Subject: [PATCH 21/41] Adding machine.Flash example --- examples/console/example.go | 34 ++++++++++++++++++++--- examples/console/littlefs/machine/main.go | 26 +++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 examples/console/littlefs/machine/main.go diff --git a/examples/console/example.go b/examples/console/example.go index dfd1fed..5cd7f78 100644 --- a/examples/console/example.go +++ b/examples/console/example.go @@ -29,7 +29,8 @@ var ( console = machine.Serial readyLED = machine.LED - flashdev *flash.Device + // flashdev *flash.Device + blockdev tinyfs.BlockDevice fs tinyfs.Filesystem currdir = "/" @@ -61,10 +62,10 @@ const ( StateCSI ) -func RunFor(dev *flash.Device, filesys tinyfs.Filesystem) { +func RunFor(dev tinyfs.BlockDevice, filesys tinyfs.Filesystem) { time.Sleep(3 * time.Second) + blockdev = dev - flashdev = dev fs = filesys readyLED.Configure(machine.PinConfig{Mode: machine.PinOutput}) @@ -175,6 +176,18 @@ func dbg(argv []string) { } func lsblk(argv []string) { + if flashdev, ok := blockdev.(*flash.Device); ok { + lsblk_flash(flashdev) + return + } + if blockdev == machine.Flash { + lsblk_machine() + return + } + println("Unknown device") +} + +func lsblk_flash(flashdev *flash.Device) { attrs := flashdev.Attrs() status1, _ := flashdev.ReadStatus() status2, _ := flashdev.ReadStatus2() @@ -210,6 +223,19 @@ func lsblk(argv []string) { ) } +func lsblk_machine() { + fmt.Printf( + "\n-------------------------------------\r\n"+ + " Device Information: \r\n"+ + "-------------------------------------\r\n"+ + " flash data start: %08X\r\n"+ + " flash data end: %08X\r\n"+ + "-------------------------------------\r\n\r\n", + machine.FlashDataStart(), + machine.FlashDataEnd(), + ) +} + func mount(argv []string) { if err := fs.Mount(); err != nil { println("Could not mount LittleFS filesystem: " + err.Error() + "\r\n") @@ -524,7 +550,7 @@ func xxd(argv []string) { buf := make([]byte, size) //bsz := uint64(flash.SectorSize) //blockdev.ReadBlock(uint32(addr/bsz), uint32(addr%bsz), buf) - flashdev.ReadAt(buf, int64(addr)) + blockdev.ReadAt(buf, int64(addr)) xxdfprint(os.Stdout, uint32(addr), buf) } diff --git a/examples/console/littlefs/machine/main.go b/examples/console/littlefs/machine/main.go new file mode 100644 index 0000000..080b9c5 --- /dev/null +++ b/examples/console/littlefs/machine/main.go @@ -0,0 +1,26 @@ +//go:build tinygo +// +build tinygo + +package main + +import ( + "machine" + + "tinygo.org/x/tinyfs/examples/console" + "tinygo.org/x/tinyfs/littlefs" +) + +var ( + blockDevice = machine.Flash + filesystem = littlefs.New(blockDevice) +) + +func main() { + // Configure littlefs with parameters for caches and wear levelling + filesystem.Configure(&littlefs.Config{ + CacheSize: 512, + LookaheadSize: 512, + BlockCycles: 100, + }) + console.RunFor(blockDevice, filesystem) +} From f6a96b4016aa25e24774ac2153523f7c61a9e152 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Fri, 28 Apr 2023 00:09:32 +0200 Subject: [PATCH 22/41] examples/fatfs: adding machine.Flash example Signed-off-by: deadprogram --- examples/console/fatfs/machine/main.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 examples/console/fatfs/machine/main.go diff --git a/examples/console/fatfs/machine/main.go b/examples/console/fatfs/machine/main.go new file mode 100644 index 0000000..771a883 --- /dev/null +++ b/examples/console/fatfs/machine/main.go @@ -0,0 +1,25 @@ +//go:build tinygo +// +build tinygo + +package main + +import ( + "machine" + + "tinygo.org/x/tinyfs/examples/console" + "tinygo.org/x/tinyfs/fatfs" +) + +var ( + blockDevice = machine.Flash + filesystem = fatfs.New(blockDevice) +) + +func main() { + // Configure FATFS with sector size (must match value in ff.h - use 512) + filesystem.Configure(&fatfs.Config{ + SectorSize: 512, + }) + + console.RunFor(blockDevice, filesystem) +} From bd682adf90664881a43a75acc7682f8cdb78975f Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sun, 19 Mar 2023 17:53:06 +0100 Subject: [PATCH 23/41] build: switch to ghcr.io for docker container Signed-off-by: deadprogram --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cb99b98..b1ae611 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ on: jobs: build: runs-on: ubuntu-latest - container: tinygo/tinygo-dev + container: ghcr.io/tinygo-org/tinygo-dev steps: - name: Work around CVE-2022-24765 # We're not on a multi-user machine, so this is safe. From d325a802a1f87e6f3779b4d69d62ea73766c5fd5 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Thu, 18 May 2023 17:12:54 +0200 Subject: [PATCH 24/41] docs: update LICENSE year to 2023 Signed-off-by: deadprogram --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 67b74a5..658115a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018-2022 The TinyGo Authors. All rights reserved. +Copyright (c) 2018-2023 The TinyGo Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are From 5374e45a641fbd2991142424e0722f2477b42303 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Thu, 18 May 2023 17:13:15 +0200 Subject: [PATCH 25/41] docs: update README with useful info Signed-off-by: deadprogram --- README.md | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 101 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 884d493..1b55230 100644 --- a/README.md +++ b/README.md @@ -2,5 +2,105 @@ [![Build](https://github.com/tinygo-org/tinyfs/actions/workflows/build.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinyfs/actions/workflows/build.yml) -TinyFS contains Go implementations of embedded filesystems. The packages in +TinyFS contains Go implementations of embedded filesystems. The packages in this module require CGo support and are [TinyGo](https://tinygo.org/) compatible. + +## Supported hardware + +You can use TinyFS with the following embedded hardware configurations: + +### Onboard Flash memory + +You can use TinyFS on processors that have onboard Flash memory in the area above where the compiled TinyGo program is running. + +### External Flash memory + +You can use TinyFS on an external Flash memory chip connected via SPI/QSPI hardware interface. + +See https://github.com/tinygo-org/drivers/tree/release/flash + +### SD card connected via SPI/QPI + +You can use TinyFS on an SD card connected via SPI/QSPI hardware interface. + +See https://github.com/tinygo-org/drivers/tree/release/sdcard + +## LittleFS + +The LittleFS file system is specifically designed for embedded applications. + +See https://github.com/littlefs-project/littlefs for more information. + +### Example + +This example runs on the RP2040 using the on-board flash in the available memory above where the program code itself is running: + +``` +$ tinygo flash -target pico -monitor ./examples/console/littlefs/machine/ +Connected to /dev/ttyACM0. Press Ctrl-C to exit. +SPI Configured. Reading flash info +==> lsblk + +------------------------------------- + Device Information: +------------------------------------- + flash data start: 10017000 + flash data end: 10200000 +------------------------------------- + +==> format +Successfully formatted LittleFS filesystem. + +==> mount +Successfully mounted LittleFS filesystem. + +==> ls +==> samples +wrote 90 bytes to file0.txt +wrote 90 bytes to file1.txt +wrote 90 bytes to file2.txt +wrote 90 bytes to file3.txt +wrote 90 bytes to file4.txt +==> ls +-rwxrwxrwx 90 file0.txt +-rwxrwxrwx 90 file1.txt +-rwxrwxrwx 90 file2.txt +-rwxrwxrwx 90 file3.txt +-rwxrwxrwx 90 file4.txt +==> cat file3.txt +00000000: 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f !"#$%&'()*+,-./ +00000010: 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 0123456789:;<=>? +00000020: 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f @ABCDEFGHIJKLMNO +00000030: 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f PQRSTUVWXYZ[\]^_ +00000040: 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f `abcdefghijklmno +00000050: 70 71 72 73 74 75 76 77 78 79 pqrstuvwxy +``` + +After unplugging and reconnecting the RP2040 device (a hard restart): + +``` +$ tinygo monitor +Connected to /dev/ttyACM0. Press Ctrl-C to exit. +SPI Configured. Reading flash info +==> mount +Successfully mounted LittleFS filesystem. + +==> ls +-rwxrwxrwx 90 file0.txt +-rwxrwxrwx 90 file1.txt +-rwxrwxrwx 90 file2.txt +-rwxrwxrwx 90 file3.txt +-rwxrwxrwx 90 file4.txt +==> cat file3.txt +00000000: 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f !"#$%&'()*+,-./ +00000010: 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 0123456789:;<=>? +00000020: 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f @ABCDEFGHIJKLMNO +00000030: 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f PQRSTUVWXYZ[\]^_ +00000040: 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f `abcdefghijklmno +00000050: 70 71 72 73 74 75 76 77 78 79 pqrstuvwxy +``` + +## FAT FS + +The FAT file system is not currently working, due to https://github.com/tinygo-org/tinygo/issues/3460. + From d950a4750e9eaeb709df58609c4154d792003e1d Mon Sep 17 00:00:00 2001 From: deadprogram Date: Mon, 22 May 2023 15:41:52 +0200 Subject: [PATCH 26/41] all: preparing for 0.3.0 release Signed-off-by: deadprogram --- CHANGELOG.md | 16 +++++++++++++++- version.go | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95802ba..c44d1ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,20 @@ -0.2.0 +0.3.0 --- +- **examples** + - adding machine.Flash example +- **build** + - switch to ghcr.io for docker container + - remove circleci + - switch to GH actions +- **docs** + - switch CI badge in README to GH action + - update LICENSE year to 2023 + - update README with useful info +- **modules** + - update to latest version of tinygo drivers +0.2.0 +--- - Compatability with TinyGo 0.23.0 diff --git a/version.go b/version.go index 0b35b0b..cace609 100644 --- a/version.go +++ b/version.go @@ -2,4 +2,4 @@ package tinyfs // Version returns a user-readable string showing the version of the package for support purposes. // Update this value before release of new version of software. -const Version = "0.2.0" +const Version = "0.3.0" From 5ec6806173612cb453b3eab5974264eeef3b011d Mon Sep 17 00:00:00 2001 From: deadprogram Date: Mon, 12 Jun 2023 01:13:59 +0200 Subject: [PATCH 27/41] modules: update to use TinyGo drivers 0.25.0 Signed-off-by: deadprogram --- go.mod | 2 +- go.sum | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index fb0828a..9892bbb 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,4 @@ module tinygo.org/x/tinyfs go 1.18 -require tinygo.org/x/drivers v0.24.0 +require tinygo.org/x/drivers v0.25.0 diff --git a/go.sum b/go.sum index 704ce40..2b9bc89 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,7 @@ github.com/bgould/http v0.0.0-20190627042742-d268792bdee7/go.mod h1:BTqvVegvwifo github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/hajimehoshi/go-jisx0208 v1.0.0/go.mod h1:yYxEStHL7lt9uL+AbdWgW9gBumwieDoZCiB1f/0X0as= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -20,10 +21,9 @@ tinygo.org/x/drivers v0.14.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2 tinygo.org/x/drivers v0.15.1/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= tinygo.org/x/drivers v0.16.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= tinygo.org/x/drivers v0.19.0/go.mod h1:uJD/l1qWzxzLx+vcxaW0eY464N5RAgFi1zTVzASFdqI= -tinygo.org/x/drivers v0.24.0 h1:9yOV/GbdDXg+EKOWJl+FHLKMSfSI/skQ8Gmo8N/KzQo= -tinygo.org/x/drivers v0.24.0/go.mod h1:J4+51Li1kcfL5F93kmnDWEEzQF3bLGz0Am3Q7E2a8/E= +tinygo.org/x/drivers v0.25.0 h1:MFnec5lY8Sxk1bIfqQWsflIbxcpAFbohWhg/qZ7psdM= +tinygo.org/x/drivers v0.25.0/go.mod h1:v+mXaA4cgpz/YZJ3ZPm/86bYQJAXTaYtMkHlVwbodbw= tinygo.org/x/tinyfont v0.2.1/go.mod h1:eLqnYSrFRjt5STxWaMeOWJTzrKhXqpWw7nU3bPfKOAM= tinygo.org/x/tinyfont v0.3.0/go.mod h1:+TV5q0KpwSGRWnN+ITijsIhrWYJkoUCp9MYELjKpAXk= tinygo.org/x/tinyfs v0.1.0/go.mod h1:ysc8Y92iHfhTXeyEM9+c7zviUQ4fN9UCFgSOFfMWv20= -tinygo.org/x/tinyfs v0.2.0/go.mod h1:6ZHYdvB3sFYeMB3ypmXZCNEnFwceKc61ADYTYHpep1E= tinygo.org/x/tinyterm v0.1.0/go.mod h1:/DDhNnGwNF2/tNgHywvyZuCGnbH3ov49Z/6e8LPLRR4= From baadfc556bba3fff3426a1cfb0465b904adfb2a8 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Thu, 7 Dec 2023 20:35:46 +0100 Subject: [PATCH 28/41] modules: update to latest TinyGo drivers Signed-off-by: deadprogram --- go.mod | 2 +- go.sum | 31 ++----------------------------- 2 files changed, 3 insertions(+), 30 deletions(-) diff --git a/go.mod b/go.mod index 9892bbb..358c853 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,4 @@ module tinygo.org/x/tinyfs go 1.18 -require tinygo.org/x/drivers v0.25.0 +require tinygo.org/x/drivers v0.26.1-0.20231206190939-3fabdc5c9680 diff --git a/go.sum b/go.sum index 2b9bc89..d377570 100644 --- a/go.sum +++ b/go.sum @@ -1,29 +1,2 @@ -github.com/bgould/http v0.0.0-20190627042742-d268792bdee7/go.mod h1:BTqvVegvwifopl4KTEDth6Zezs9eR+lCWhvGKvkxJHE= -github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= -github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/hajimehoshi/go-jisx0208 v1.0.0/go.mod h1:yYxEStHL7lt9uL+AbdWgW9gBumwieDoZCiB1f/0X0as= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/sago35/go-bdf v0.0.0-20200313142241-6c17821c91c4/go.mod h1:rOebXGuMLsXhZAC6mF/TjxONsm45498ZyzVhel++6KM= -github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= -golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -tinygo.org/x/drivers v0.14.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= -tinygo.org/x/drivers v0.15.1/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= -tinygo.org/x/drivers v0.16.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= -tinygo.org/x/drivers v0.19.0/go.mod h1:uJD/l1qWzxzLx+vcxaW0eY464N5RAgFi1zTVzASFdqI= -tinygo.org/x/drivers v0.25.0 h1:MFnec5lY8Sxk1bIfqQWsflIbxcpAFbohWhg/qZ7psdM= -tinygo.org/x/drivers v0.25.0/go.mod h1:v+mXaA4cgpz/YZJ3ZPm/86bYQJAXTaYtMkHlVwbodbw= -tinygo.org/x/tinyfont v0.2.1/go.mod h1:eLqnYSrFRjt5STxWaMeOWJTzrKhXqpWw7nU3bPfKOAM= -tinygo.org/x/tinyfont v0.3.0/go.mod h1:+TV5q0KpwSGRWnN+ITijsIhrWYJkoUCp9MYELjKpAXk= -tinygo.org/x/tinyfs v0.1.0/go.mod h1:ysc8Y92iHfhTXeyEM9+c7zviUQ4fN9UCFgSOFfMWv20= -tinygo.org/x/tinyterm v0.1.0/go.mod h1:/DDhNnGwNF2/tNgHywvyZuCGnbH3ov49Z/6e8LPLRR4= +tinygo.org/x/drivers v0.26.1-0.20231206190939-3fabdc5c9680 h1:S7FwtuTMSkyEjF1cgl3AFlnBR940GYgCMSADm0U8e7o= +tinygo.org/x/drivers v0.26.1-0.20231206190939-3fabdc5c9680/go.mod h1:q/mU8G/wz821p8xXqbkBACOlmZFDHXd//DnYnCW+dDQ= From 02b88ccd2b161ed35b520f1ecee84ce3d0e1f1da Mon Sep 17 00:00:00 2001 From: deadprogram Date: Thu, 7 Dec 2023 20:36:10 +0100 Subject: [PATCH 29/41] littlefs: update to littlefs 2.8.1 Signed-off-by: deadprogram --- littlefs/lfs.c | 3896 ++++++++++++++++++++++++++++++------------- littlefs/lfs.h | 180 +- littlefs/lfs_util.c | 3 +- littlefs/lfs_util.h | 75 +- 4 files changed, 2931 insertions(+), 1223 deletions(-) diff --git a/littlefs/lfs.c b/littlefs/lfs.c index 4553a6e..95c241e 100644 --- a/littlefs/lfs.c +++ b/littlefs/lfs.c @@ -1,16 +1,33 @@ /* * The little filesystem * + * Copyright (c) 2022, The littlefs authors. * Copyright (c) 2017, Arm Limited. All rights reserved. * SPDX-License-Identifier: BSD-3-Clause */ #include "lfs.h" #include "lfs_util.h" + +// some constants used throughout the code #define LFS_BLOCK_NULL ((lfs_block_t)-1) #define LFS_BLOCK_INLINE ((lfs_block_t)-2) +enum { + LFS_OK_RELOCATED = 1, + LFS_OK_DROPPED = 2, + LFS_OK_ORPHANED = 3, +}; + +enum { + LFS_CMP_EQ = 0, + LFS_CMP_LT = 1, + LFS_CMP_GT = 2, +}; + + /// Caching block device operations /// + static inline void lfs_cache_drop(lfs_t *lfs, lfs_cache_t *rcache) { // do not zero, cheaper if cache is readonly or only going to be // written with identical data (during relocates) @@ -29,8 +46,8 @@ static int lfs_bd_read(lfs_t *lfs, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size) { uint8_t *data = buffer; - LFS_ASSERT(block != LFS_BLOCK_NULL); - if (off+size > lfs->cfg->block_size) { + if (off+size > lfs->cfg->block_size + || (lfs->block_count && block >= lfs->block_count)) { return LFS_ERR_CORRUPT; } @@ -71,8 +88,23 @@ static int lfs_bd_read(lfs_t *lfs, diff = lfs_min(diff, rcache->off-off); } + if (size >= hint && off % lfs->cfg->read_size == 0 && + size >= lfs->cfg->read_size) { + // bypass cache? + diff = lfs_aligndown(diff, lfs->cfg->read_size); + int err = lfs->cfg->read(lfs->cfg, block, off, data, diff); + if (err) { + return err; + } + + data += diff; + off += diff; + size -= diff; + continue; + } + // load to cache, first condition can no longer fail - LFS_ASSERT(block < lfs->cfg->block_count); + LFS_ASSERT(!lfs->block_count || block < lfs->block_count); rcache->block = block; rcache->off = lfs_aligndown(off, lfs->cfg->read_size); rcache->size = lfs_min( @@ -92,39 +124,59 @@ static int lfs_bd_read(lfs_t *lfs, return 0; } -enum { - LFS_CMP_EQ = 0, - LFS_CMP_LT = 1, - LFS_CMP_GT = 2, -}; - static int lfs_bd_cmp(lfs_t *lfs, const lfs_cache_t *pcache, lfs_cache_t *rcache, lfs_size_t hint, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) { const uint8_t *data = buffer; + lfs_size_t diff = 0; - for (lfs_off_t i = 0; i < size; i++) { - uint8_t dat; + for (lfs_off_t i = 0; i < size; i += diff) { + uint8_t dat[8]; + + diff = lfs_min(size-i, sizeof(dat)); int err = lfs_bd_read(lfs, pcache, rcache, hint-i, - block, off+i, &dat, 1); + block, off+i, &dat, diff); if (err) { return err; } - if (dat != data[i]) { - return (dat < data[i]) ? LFS_CMP_LT : LFS_CMP_GT; + int res = memcmp(dat, data + i, diff); + if (res) { + return res < 0 ? LFS_CMP_LT : LFS_CMP_GT; } } return LFS_CMP_EQ; } +static int lfs_bd_crc(lfs_t *lfs, + const lfs_cache_t *pcache, lfs_cache_t *rcache, lfs_size_t hint, + lfs_block_t block, lfs_off_t off, lfs_size_t size, uint32_t *crc) { + lfs_size_t diff = 0; + + for (lfs_off_t i = 0; i < size; i += diff) { + uint8_t dat[8]; + diff = lfs_min(size-i, sizeof(dat)); + int err = lfs_bd_read(lfs, + pcache, rcache, hint-i, + block, off+i, &dat, diff); + if (err) { + return err; + } + + *crc = lfs_crc(*crc, &dat, diff); + } + + return 0; +} + +#ifndef LFS_READONLY static int lfs_bd_flush(lfs_t *lfs, lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate) { if (pcache->block != LFS_BLOCK_NULL && pcache->block != LFS_BLOCK_INLINE) { - LFS_ASSERT(pcache->block < lfs->cfg->block_count); + LFS_ASSERT(pcache->block < lfs->block_count); lfs_size_t diff = lfs_alignup(pcache->size, lfs->cfg->prog_size); int err = lfs->cfg->prog(lfs->cfg, pcache->block, pcache->off, pcache->buffer, diff); @@ -153,7 +205,9 @@ static int lfs_bd_flush(lfs_t *lfs, return 0; } +#endif +#ifndef LFS_READONLY static int lfs_bd_sync(lfs_t *lfs, lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate) { lfs_cache_drop(lfs, rcache); @@ -167,13 +221,15 @@ static int lfs_bd_sync(lfs_t *lfs, LFS_ASSERT(err <= 0); return err; } +#endif +#ifndef LFS_READONLY static int lfs_bd_prog(lfs_t *lfs, lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) { const uint8_t *data = buffer; - LFS_ASSERT(block != LFS_BLOCK_NULL); + LFS_ASSERT(block == LFS_BLOCK_INLINE || block < lfs->block_count); LFS_ASSERT(off + size <= lfs->cfg->block_size); while (size > 0) { @@ -213,13 +269,16 @@ static int lfs_bd_prog(lfs_t *lfs, return 0; } +#endif +#ifndef LFS_READONLY static int lfs_bd_erase(lfs_t *lfs, lfs_block_t block) { - LFS_ASSERT(block < lfs->cfg->block_count); + LFS_ASSERT(block < lfs->block_count); int err = lfs->cfg->erase(lfs->cfg, block); LFS_ASSERT(err <= 0); return err; } +#endif /// Small type-level utilities /// @@ -241,7 +300,7 @@ static inline int lfs_pair_cmp( paira[0] == pairb[1] || paira[1] == pairb[0]); } -static inline bool lfs_pair_sync( +static inline bool lfs_pair_issync( const lfs_block_t paira[2], const lfs_block_t pairb[2]) { return (paira[0] == pairb[0] && paira[1] == pairb[1]) || @@ -253,10 +312,12 @@ static inline void lfs_pair_fromle32(lfs_block_t pair[2]) { pair[1] = lfs_fromle32(pair[1]); } +#ifndef LFS_READONLY static inline void lfs_pair_tole32(lfs_block_t pair[2]) { pair[0] = lfs_tole32(pair[0]); pair[1] = lfs_tole32(pair[1]); } +#endif // operations on 32-bit entry tags typedef uint32_t lfs_tag_t; @@ -265,6 +326,12 @@ typedef int32_t lfs_stag_t; #define LFS_MKTAG(type, id, size) \ (((lfs_tag_t)(type) << 20) | ((lfs_tag_t)(id) << 10) | (lfs_tag_t)(size)) +#define LFS_MKTAG_IF(cond, type, id, size) \ + ((cond) ? LFS_MKTAG(type, id, size) : LFS_MKTAG(LFS_FROM_NOOP, 0, 0)) + +#define LFS_MKTAG_IF_ELSE(cond, type1, id1, size1, type2, id2, size2) \ + ((cond) ? LFS_MKTAG(type1, id1, size1) : LFS_MKTAG(type2, id2, size2)) + static inline bool lfs_tag_isvalid(lfs_tag_t tag) { return !(tag & 0x80000000); } @@ -277,6 +344,10 @@ static inline uint16_t lfs_tag_type1(lfs_tag_t tag) { return (tag & 0x70000000) >> 20; } +static inline uint16_t lfs_tag_type2(lfs_tag_t tag) { + return (tag & 0x78000000) >> 20; +} + static inline uint16_t lfs_tag_type3(lfs_tag_t tag) { return (tag & 0x7ff00000) >> 20; } @@ -317,14 +388,13 @@ struct lfs_diskoff { sizeof((struct lfs_mattr[]){__VA_ARGS__}) / sizeof(struct lfs_mattr) // operations on global state -static inline void lfs_gstate_xor(struct lfs_gstate *a, - const struct lfs_gstate *b) { +static inline void lfs_gstate_xor(lfs_gstate_t *a, const lfs_gstate_t *b) { for (int i = 0; i < 3; i++) { ((uint32_t*)a)[i] ^= ((const uint32_t*)b)[i]; } } -static inline bool lfs_gstate_iszero(const struct lfs_gstate *a) { +static inline bool lfs_gstate_iszero(const lfs_gstate_t *a) { for (int i = 0; i < 3; i++) { if (((uint32_t*)a)[i] != 0) { return false; @@ -333,47 +403,60 @@ static inline bool lfs_gstate_iszero(const struct lfs_gstate *a) { return true; } -static inline bool lfs_gstate_hasorphans(const struct lfs_gstate *a) { +#ifndef LFS_READONLY +static inline bool lfs_gstate_hasorphans(const lfs_gstate_t *a) { return lfs_tag_size(a->tag); } -static inline uint8_t lfs_gstate_getorphans(const struct lfs_gstate *a) { - return lfs_tag_size(a->tag); +static inline uint8_t lfs_gstate_getorphans(const lfs_gstate_t *a) { + return lfs_tag_size(a->tag) & 0x1ff; } -static inline bool lfs_gstate_hasmove(const struct lfs_gstate *a) { +static inline bool lfs_gstate_hasmove(const lfs_gstate_t *a) { return lfs_tag_type1(a->tag); } +#endif -static inline bool lfs_gstate_hasmovehere(const struct lfs_gstate *a, - const lfs_block_t *pair) { - return lfs_tag_type1(a->tag) && lfs_pair_cmp(a->pair, pair) == 0; -} - -static inline void lfs_gstate_xororphans(struct lfs_gstate *a, - const struct lfs_gstate *b, bool orphans) { - a->tag ^= LFS_MKTAG(0x800, 0, 0) & (b->tag ^ ((uint32_t)orphans << 31)); +static inline bool lfs_gstate_needssuperblock(const lfs_gstate_t *a) { + return lfs_tag_size(a->tag) >> 9; } -static inline void lfs_gstate_xormove(struct lfs_gstate *a, - const struct lfs_gstate *b, uint16_t id, const lfs_block_t pair[2]) { - a->tag ^= LFS_MKTAG(0x7ff, 0x3ff, 0) & (b->tag ^ ( - (id != 0x3ff) ? LFS_MKTAG(LFS_TYPE_DELETE, id, 0) : 0)); - a->pair[0] ^= b->pair[0] ^ ((id != 0x3ff) ? pair[0] : 0); - a->pair[1] ^= b->pair[1] ^ ((id != 0x3ff) ? pair[1] : 0); +static inline bool lfs_gstate_hasmovehere(const lfs_gstate_t *a, + const lfs_block_t *pair) { + return lfs_tag_type1(a->tag) && lfs_pair_cmp(a->pair, pair) == 0; } -static inline void lfs_gstate_fromle32(struct lfs_gstate *a) { +static inline void lfs_gstate_fromle32(lfs_gstate_t *a) { a->tag = lfs_fromle32(a->tag); a->pair[0] = lfs_fromle32(a->pair[0]); a->pair[1] = lfs_fromle32(a->pair[1]); } -static inline void lfs_gstate_tole32(struct lfs_gstate *a) { +#ifndef LFS_READONLY +static inline void lfs_gstate_tole32(lfs_gstate_t *a) { a->tag = lfs_tole32(a->tag); a->pair[0] = lfs_tole32(a->pair[0]); a->pair[1] = lfs_tole32(a->pair[1]); } +#endif + +// operations on forward-CRCs used to track erased state +struct lfs_fcrc { + lfs_size_t size; + uint32_t crc; +}; + +static void lfs_fcrc_fromle32(struct lfs_fcrc *fcrc) { + fcrc->size = lfs_fromle32(fcrc->size); + fcrc->crc = lfs_fromle32(fcrc->crc); +} + +#ifndef LFS_READONLY +static void lfs_fcrc_tole32(struct lfs_fcrc *fcrc) { + fcrc->size = lfs_tole32(fcrc->size); + fcrc->crc = lfs_tole32(fcrc->crc); +} +#endif // other endianness operations static void lfs_ctz_fromle32(struct lfs_ctz *ctz) { @@ -381,10 +464,12 @@ static void lfs_ctz_fromle32(struct lfs_ctz *ctz) { ctz->size = lfs_fromle32(ctz->size); } +#ifndef LFS_READONLY static void lfs_ctz_tole32(struct lfs_ctz *ctz) { ctz->head = lfs_tole32(ctz->head); ctz->size = lfs_tole32(ctz->size); } +#endif static inline void lfs_superblock_fromle32(lfs_superblock_t *superblock) { superblock->version = lfs_fromle32(superblock->version); @@ -395,6 +480,7 @@ static inline void lfs_superblock_fromle32(lfs_superblock_t *superblock) { superblock->attr_max = lfs_fromle32(superblock->attr_max); } +#ifndef LFS_READONLY static inline void lfs_superblock_tole32(lfs_superblock_t *superblock) { superblock->version = lfs_tole32(superblock->version); superblock->block_size = lfs_tole32(superblock->block_size); @@ -403,37 +489,115 @@ static inline void lfs_superblock_tole32(lfs_superblock_t *superblock) { superblock->file_max = lfs_tole32(superblock->file_max); superblock->attr_max = lfs_tole32(superblock->attr_max); } +#endif + +#ifndef LFS_NO_ASSERT +static bool lfs_mlist_isopen(struct lfs_mlist *head, + struct lfs_mlist *node) { + for (struct lfs_mlist **p = &head; *p; p = &(*p)->next) { + if (*p == (struct lfs_mlist*)node) { + return true; + } + } + + return false; +} +#endif + +static void lfs_mlist_remove(lfs_t *lfs, struct lfs_mlist *mlist) { + for (struct lfs_mlist **p = &lfs->mlist; *p; p = &(*p)->next) { + if (*p == mlist) { + *p = (*p)->next; + break; + } + } +} + +static void lfs_mlist_append(lfs_t *lfs, struct lfs_mlist *mlist) { + mlist->next = lfs->mlist; + lfs->mlist = mlist; +} + +// some other filesystem operations +static uint32_t lfs_fs_disk_version(lfs_t *lfs) { + (void)lfs; +#ifdef LFS_MULTIVERSION + if (lfs->cfg->disk_version) { + return lfs->cfg->disk_version; + } else +#endif + { + return LFS_DISK_VERSION; + } +} + +static uint16_t lfs_fs_disk_version_major(lfs_t *lfs) { + return 0xffff & (lfs_fs_disk_version(lfs) >> 16); + +} + +static uint16_t lfs_fs_disk_version_minor(lfs_t *lfs) { + return 0xffff & (lfs_fs_disk_version(lfs) >> 0); +} /// Internal operations predeclared here /// +#ifndef LFS_READONLY static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir, const struct lfs_mattr *attrs, int attrcount); static int lfs_dir_compact(lfs_t *lfs, lfs_mdir_t *dir, const struct lfs_mattr *attrs, int attrcount, lfs_mdir_t *source, uint16_t begin, uint16_t end); +static lfs_ssize_t lfs_file_flushedwrite(lfs_t *lfs, lfs_file_t *file, + const void *buffer, lfs_size_t size); +static lfs_ssize_t lfs_file_rawwrite(lfs_t *lfs, lfs_file_t *file, + const void *buffer, lfs_size_t size); +static int lfs_file_rawsync(lfs_t *lfs, lfs_file_t *file); static int lfs_file_outline(lfs_t *lfs, lfs_file_t *file); static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file); -static void lfs_fs_preporphans(lfs_t *lfs, int8_t orphans); + +static int lfs_fs_deorphan(lfs_t *lfs, bool powerloss); +static int lfs_fs_preporphans(lfs_t *lfs, int8_t orphans); static void lfs_fs_prepmove(lfs_t *lfs, uint16_t id, const lfs_block_t pair[2]); static int lfs_fs_pred(lfs_t *lfs, const lfs_block_t dir[2], lfs_mdir_t *pdir); static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t dir[2], lfs_mdir_t *parent); -static int lfs_fs_relocate(lfs_t *lfs, - const lfs_block_t oldpair[2], lfs_block_t newpair[2]); static int lfs_fs_forceconsistency(lfs_t *lfs); -static int lfs_deinit(lfs_t *lfs); +#endif + +static void lfs_fs_prepsuperblock(lfs_t *lfs, bool needssuperblock); + #ifdef LFS_MIGRATE static int lfs1_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data); #endif +static int lfs_dir_rawrewind(lfs_t *lfs, lfs_dir_t *dir); + +static lfs_ssize_t lfs_file_flushedread(lfs_t *lfs, lfs_file_t *file, + void *buffer, lfs_size_t size); +static lfs_ssize_t lfs_file_rawread(lfs_t *lfs, lfs_file_t *file, + void *buffer, lfs_size_t size); +static int lfs_file_rawclose(lfs_t *lfs, lfs_file_t *file); +static lfs_soff_t lfs_file_rawsize(lfs_t *lfs, lfs_file_t *file); + +static lfs_ssize_t lfs_fs_rawsize(lfs_t *lfs); +static int lfs_fs_rawtraverse(lfs_t *lfs, + int (*cb)(void *data, lfs_block_t block), void *data, + bool includeorphans); + +static int lfs_deinit(lfs_t *lfs); +static int lfs_rawunmount(lfs_t *lfs); + + /// Block allocator /// +#ifndef LFS_READONLY static int lfs_alloc_lookahead(void *p, lfs_block_t block) { lfs_t *lfs = (lfs_t*)p; lfs_block_t off = ((block - lfs->free.off) - + lfs->cfg->block_count) % lfs->cfg->block_count; + + lfs->block_count) % lfs->block_count; if (off < lfs->free.size) { lfs->free.buffer[off / 32] |= 1U << (off % 32); @@ -441,7 +605,44 @@ static int lfs_alloc_lookahead(void *p, lfs_block_t block) { return 0; } +#endif + +// indicate allocated blocks have been committed into the filesystem, this +// is to prevent blocks from being garbage collected in the middle of a +// commit operation +static void lfs_alloc_ack(lfs_t *lfs) { + lfs->free.ack = lfs->block_count; +} + +// drop the lookahead buffer, this is done during mounting and failed +// traversals in order to avoid invalid lookahead state +static void lfs_alloc_drop(lfs_t *lfs) { + lfs->free.size = 0; + lfs->free.i = 0; + lfs_alloc_ack(lfs); +} + +#ifndef LFS_READONLY +static int lfs_fs_rawgc(lfs_t *lfs) { + // Move free offset at the first unused block (lfs->free.i) + // lfs->free.i is equal lfs->free.size when all blocks are used + lfs->free.off = (lfs->free.off + lfs->free.i) % lfs->block_count; + lfs->free.size = lfs_min(8*lfs->cfg->lookahead_size, lfs->free.ack); + lfs->free.i = 0; + + // find mask of free blocks from tree + memset(lfs->free.buffer, 0, lfs->cfg->lookahead_size); + int err = lfs_fs_rawtraverse(lfs, lfs_alloc_lookahead, lfs, true); + if (err) { + lfs_alloc_drop(lfs); + return err; + } + + return 0; +} +#endif +#ifndef LFS_READONLY static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) { while (true) { while (lfs->free.i != lfs->free.size) { @@ -451,7 +652,7 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) { if (!(lfs->free.buffer[off / 32] & (1U << (off % 32)))) { // found a free block - *block = (lfs->free.off + off) % lfs->cfg->block_count; + *block = (lfs->free.off + off) % lfs->block_count; // eagerly find next off so an alloc ack can // discredit old lookahead blocks @@ -473,24 +674,13 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) { return LFS_ERR_NOSPC; } - lfs->free.off = (lfs->free.off + lfs->free.size) - % lfs->cfg->block_count; - lfs->free.size = lfs_min(8*lfs->cfg->lookahead_size, lfs->free.ack); - lfs->free.i = 0; - - // find mask of free blocks from tree - memset(lfs->free.buffer, 0, lfs->cfg->lookahead_size); - int err = lfs_fs_traverse(lfs, lfs_alloc_lookahead, lfs); - if (err) { + int err = lfs_fs_rawgc(lfs); + if(err) { return err; } } } - -static void lfs_alloc_ack(lfs_t *lfs) { - lfs->free.ack = lfs->cfg->block_count; -} - +#endif /// Metadata pair and directory operations /// static lfs_stag_t lfs_dir_getslice(lfs_t *lfs, const lfs_mdir_t *dir, @@ -500,8 +690,9 @@ static lfs_stag_t lfs_dir_getslice(lfs_t *lfs, const lfs_mdir_t *dir, lfs_tag_t ntag = dir->etag; lfs_stag_t gdiff = 0; - if (lfs_gstate_hasmovehere(&lfs->gstate, dir->pair) && - lfs_tag_id(gtag) <= lfs_tag_id(lfs->gstate.tag)) { + if (lfs_gstate_hasmovehere(&lfs->gdisk, dir->pair) && + lfs_tag_id(gmask) != 0 && + lfs_tag_id(lfs->gdisk.tag) <= lfs_tag_id(gtag)) { // synthetic moves gdiff -= LFS_MKTAG(0, 1, 0); } @@ -622,6 +813,7 @@ static int lfs_dir_getread(lfs_t *lfs, const lfs_mdir_t *dir, return 0; } +#ifndef LFS_READONLY static int lfs_dir_traverse_filter(void *p, lfs_tag_t tag, const void *buffer) { lfs_tag_t *filtertag = p; @@ -638,6 +830,7 @@ static int lfs_dir_traverse_filter(void *p, (LFS_MKTAG(0x7ff, 0x3ff, 0) & tag) == ( LFS_MKTAG(LFS_TYPE_DELETE, 0, 0) | (LFS_MKTAG(0, 0x3ff, 0) & *filtertag))) { + *filtertag = LFS_MKTAG(LFS_FROM_NOOP, 0, 0); return true; } @@ -649,107 +842,232 @@ static int lfs_dir_traverse_filter(void *p, return false; } +#endif + +#ifndef LFS_READONLY +// maximum recursive depth of lfs_dir_traverse, the deepest call: +// +// traverse with commit +// '-> traverse with move +// '-> traverse with filter +// +#define LFS_DIR_TRAVERSE_DEPTH 3 + +struct lfs_dir_traverse { + const lfs_mdir_t *dir; + lfs_off_t off; + lfs_tag_t ptag; + const struct lfs_mattr *attrs; + int attrcount; + + lfs_tag_t tmask; + lfs_tag_t ttag; + uint16_t begin; + uint16_t end; + int16_t diff; + + int (*cb)(void *data, lfs_tag_t tag, const void *buffer); + void *data; + + lfs_tag_t tag; + const void *buffer; + struct lfs_diskoff disk; +}; static int lfs_dir_traverse(lfs_t *lfs, const lfs_mdir_t *dir, lfs_off_t off, lfs_tag_t ptag, - const struct lfs_mattr *attrs, int attrcount, bool hasseenmove, + const struct lfs_mattr *attrs, int attrcount, lfs_tag_t tmask, lfs_tag_t ttag, uint16_t begin, uint16_t end, int16_t diff, int (*cb)(void *data, lfs_tag_t tag, const void *buffer), void *data) { + // This function in inherently recursive, but bounded. To allow tool-based + // analysis without unnecessary code-cost we use an explicit stack + struct lfs_dir_traverse stack[LFS_DIR_TRAVERSE_DEPTH-1]; + unsigned sp = 0; + int res; + // iterate over directory and attrs + lfs_tag_t tag; + const void *buffer; + struct lfs_diskoff disk = {0}; while (true) { - lfs_tag_t tag; - const void *buffer; - struct lfs_diskoff disk; - if (off+lfs_tag_dsize(ptag) < dir->off) { - off += lfs_tag_dsize(ptag); - int err = lfs_bd_read(lfs, - NULL, &lfs->rcache, sizeof(tag), - dir->pair[0], off, &tag, sizeof(tag)); - if (err) { - return err; - } - - tag = (lfs_frombe32(tag) ^ ptag) | 0x80000000; - disk.block = dir->pair[0]; - disk.off = off+sizeof(lfs_tag_t); - buffer = &disk; - ptag = tag; - } else if (attrcount > 0) { - tag = attrs[0].tag; - buffer = attrs[0].buffer; - attrs += 1; - attrcount -= 1; - } else if (!hasseenmove && - lfs_gstate_hasmovehere(&lfs->gpending, dir->pair)) { - // Wait, we have pending move? Handle this here (we need to - // or else we risk letting moves fall out of date) - tag = lfs->gpending.tag & LFS_MKTAG(0x7ff, 0x3ff, 0); - buffer = NULL; - hasseenmove = true; - } else { - return 0; - } - - lfs_tag_t mask = LFS_MKTAG(0x7ff, 0, 0); - if ((mask & tmask & tag) != (mask & tmask & ttag)) { - continue; - } + { + if (off+lfs_tag_dsize(ptag) < dir->off) { + off += lfs_tag_dsize(ptag); + int err = lfs_bd_read(lfs, + NULL, &lfs->rcache, sizeof(tag), + dir->pair[0], off, &tag, sizeof(tag)); + if (err) { + return err; + } - // do we need to filter? inlining the filtering logic here allows - // for some minor optimizations - if (lfs_tag_id(tmask) != 0) { - // scan for duplicates and update tag based on creates/deletes - int filter = lfs_dir_traverse(lfs, - dir, off, ptag, attrs, attrcount, hasseenmove, - 0, 0, 0, 0, 0, - lfs_dir_traverse_filter, &tag); - if (filter < 0) { - return filter; + tag = (lfs_frombe32(tag) ^ ptag) | 0x80000000; + disk.block = dir->pair[0]; + disk.off = off+sizeof(lfs_tag_t); + buffer = &disk; + ptag = tag; + } else if (attrcount > 0) { + tag = attrs[0].tag; + buffer = attrs[0].buffer; + attrs += 1; + attrcount -= 1; + } else { + // finished traversal, pop from stack? + res = 0; + break; } - if (filter) { + // do we need to filter? + lfs_tag_t mask = LFS_MKTAG(0x7ff, 0, 0); + if ((mask & tmask & tag) != (mask & tmask & ttag)) { continue; } - // in filter range? - if (!(lfs_tag_id(tag) >= begin && lfs_tag_id(tag) < end)) { + if (lfs_tag_id(tmask) != 0) { + LFS_ASSERT(sp < LFS_DIR_TRAVERSE_DEPTH); + // recurse, scan for duplicates, and update tag based on + // creates/deletes + stack[sp] = (struct lfs_dir_traverse){ + .dir = dir, + .off = off, + .ptag = ptag, + .attrs = attrs, + .attrcount = attrcount, + .tmask = tmask, + .ttag = ttag, + .begin = begin, + .end = end, + .diff = diff, + .cb = cb, + .data = data, + .tag = tag, + .buffer = buffer, + .disk = disk, + }; + sp += 1; + + tmask = 0; + ttag = 0; + begin = 0; + end = 0; + diff = 0; + cb = lfs_dir_traverse_filter; + data = &stack[sp-1].tag; continue; } } +popped: + // in filter range? + if (lfs_tag_id(tmask) != 0 && + !(lfs_tag_id(tag) >= begin && lfs_tag_id(tag) < end)) { + continue; + } + // handle special cases for mcu-side operations if (lfs_tag_type3(tag) == LFS_FROM_NOOP) { // do nothing } else if (lfs_tag_type3(tag) == LFS_FROM_MOVE) { + // Without this condition, lfs_dir_traverse can exhibit an + // extremely expensive O(n^3) of nested loops when renaming. + // This happens because lfs_dir_traverse tries to filter tags by + // the tags in the source directory, triggering a second + // lfs_dir_traverse with its own filter operation. + // + // traverse with commit + // '-> traverse with filter + // '-> traverse with move + // '-> traverse with filter + // + // However we don't actually care about filtering the second set of + // tags, since duplicate tags have no effect when filtering. + // + // This check skips this unnecessary recursive filtering explicitly, + // reducing this runtime from O(n^3) to O(n^2). + if (cb == lfs_dir_traverse_filter) { + continue; + } + + // recurse into move + stack[sp] = (struct lfs_dir_traverse){ + .dir = dir, + .off = off, + .ptag = ptag, + .attrs = attrs, + .attrcount = attrcount, + .tmask = tmask, + .ttag = ttag, + .begin = begin, + .end = end, + .diff = diff, + .cb = cb, + .data = data, + .tag = LFS_MKTAG(LFS_FROM_NOOP, 0, 0), + }; + sp += 1; + uint16_t fromid = lfs_tag_size(tag); uint16_t toid = lfs_tag_id(tag); - int err = lfs_dir_traverse(lfs, - buffer, 0, LFS_BLOCK_NULL, NULL, 0, true, - LFS_MKTAG(0x600, 0x3ff, 0), - LFS_MKTAG(LFS_TYPE_STRUCT, 0, 0), - fromid, fromid+1, toid-fromid+diff, - cb, data); - if (err) { - return err; - } + dir = buffer; + off = 0; + ptag = 0xffffffff; + attrs = NULL; + attrcount = 0; + tmask = LFS_MKTAG(0x600, 0x3ff, 0); + ttag = LFS_MKTAG(LFS_TYPE_STRUCT, 0, 0); + begin = fromid; + end = fromid+1; + diff = toid-fromid+diff; } else if (lfs_tag_type3(tag) == LFS_FROM_USERATTRS) { for (unsigned i = 0; i < lfs_tag_size(tag); i++) { const struct lfs_attr *a = buffer; - int err = cb(data, LFS_MKTAG(LFS_TYPE_USERATTR + a[i].type, + res = cb(data, LFS_MKTAG(LFS_TYPE_USERATTR + a[i].type, lfs_tag_id(tag) + diff, a[i].size), a[i].buffer); - if (err) { - return err; + if (res < 0) { + return res; + } + + if (res) { + break; } } } else { - int err = cb(data, tag + LFS_MKTAG(0, diff, 0), buffer); - if (err) { - return err; + res = cb(data, tag + LFS_MKTAG(0, diff, 0), buffer); + if (res < 0) { + return res; + } + + if (res) { + break; } } } + + if (sp > 0) { + // pop from the stack and return, fortunately all pops share + // a destination + dir = stack[sp-1].dir; + off = stack[sp-1].off; + ptag = stack[sp-1].ptag; + attrs = stack[sp-1].attrs; + attrcount = stack[sp-1].attrcount; + tmask = stack[sp-1].tmask; + ttag = stack[sp-1].ttag; + begin = stack[sp-1].begin; + end = stack[sp-1].end; + diff = stack[sp-1].diff; + cb = stack[sp-1].cb; + data = stack[sp-1].data; + tag = stack[sp-1].tag; + buffer = stack[sp-1].buffer; + disk = stack[sp-1].disk; + sp -= 1; + goto popped; + } else { + return res; + } } +#endif static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs, lfs_mdir_t *dir, const lfs_block_t pair[2], @@ -759,6 +1077,13 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs, // scanning the entire directory lfs_stag_t besttag = -1; + // if either block address is invalid we return LFS_ERR_CORRUPT here, + // otherwise later writes to the pair could fail + if (lfs->block_count + && (pair[0] >= lfs->block_count || pair[1] >= lfs->block_count)) { + return LFS_ERR_CORRUPT; + } + // find the block with the most recent revision uint32_t revs[2] = {0, 0}; int r = 0; @@ -785,15 +1110,20 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs, // now scan tags to fetch the actual dir and find possible match for (int i = 0; i < 2; i++) { lfs_off_t off = 0; - lfs_tag_t ptag = LFS_BLOCK_NULL; + lfs_tag_t ptag = 0xffffffff; uint16_t tempcount = 0; lfs_block_t temptail[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL}; bool tempsplit = false; lfs_stag_t tempbesttag = besttag; + // assume not erased until proven otherwise + bool maybeerased = false; + bool hasfcrc = false; + struct lfs_fcrc fcrc; + dir->rev = lfs_tole32(dir->rev); - uint32_t crc = lfs_crc(LFS_BLOCK_NULL, &dir->rev, sizeof(dir->rev)); + uint32_t crc = lfs_crc(0xffffffff, &dir->rev, sizeof(dir->rev)); dir->rev = lfs_fromle32(dir->rev); while (true) { @@ -806,7 +1136,6 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs, if (err) { if (err == LFS_ERR_CORRUPT) { // can't continue? - dir->erased = false; break; } return err; @@ -815,17 +1144,19 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs, crc = lfs_crc(crc, &tag, sizeof(tag)); tag = lfs_frombe32(tag) ^ ptag; - // next commit not yet programmed or we're not in valid range - if (!lfs_tag_isvalid(tag) || - off + lfs_tag_dsize(tag) > lfs->cfg->block_size) { - dir->erased = (lfs_tag_type1(ptag) == LFS_TYPE_CRC && - dir->off % lfs->cfg->prog_size == 0); + // next commit not yet programmed? + if (!lfs_tag_isvalid(tag)) { + // we only might be erased if the last tag was a crc + maybeerased = (lfs_tag_type2(ptag) == LFS_TYPE_CCRC); + break; + // out of range? + } else if (off + lfs_tag_dsize(tag) > lfs->cfg->block_size) { break; } ptag = tag; - if (lfs_tag_type1(tag) == LFS_TYPE_CRC) { + if (lfs_tag_type2(tag) == LFS_TYPE_CCRC) { // check the crc attr uint32_t dcrc; err = lfs_bd_read(lfs, @@ -833,7 +1164,6 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs, dir->pair[0], off+sizeof(tag), &dcrc, sizeof(dcrc)); if (err) { if (err == LFS_ERR_CORRUPT) { - dir->erased = false; break; } return err; @@ -841,7 +1171,6 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs, dcrc = lfs_fromle32(dcrc); if (crc != dcrc) { - dir->erased = false; break; } @@ -849,8 +1178,10 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs, ptag ^= (lfs_tag_t)(lfs_tag_chunk(tag) & 1U) << 31; // toss our crc into the filesystem seed for - // pseudorandom numbers - lfs->seed ^= crc; + // pseudorandom numbers, note we use another crc here + // as a collection function because it is sufficiently + // random and convenient + lfs->seed = lfs_crc(lfs->seed, &crc, sizeof(crc)); // update with what's found so far besttag = tempbesttag; @@ -861,26 +1192,21 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs, dir->tail[1] = temptail[1]; dir->split = tempsplit; - // reset crc - crc = LFS_BLOCK_NULL; + // reset crc, hasfcrc + crc = 0xffffffff; continue; } // crc the entry first, hopefully leaving it in the cache - for (lfs_off_t j = sizeof(tag); j < lfs_tag_dsize(tag); j++) { - uint8_t dat; - err = lfs_bd_read(lfs, - NULL, &lfs->rcache, lfs->cfg->block_size, - dir->pair[0], off+j, &dat, 1); - if (err) { - if (err == LFS_ERR_CORRUPT) { - dir->erased = false; - break; - } - return err; + err = lfs_bd_crc(lfs, + NULL, &lfs->rcache, lfs->cfg->block_size, + dir->pair[0], off+sizeof(tag), + lfs_tag_dsize(tag)-sizeof(tag), &crc); + if (err) { + if (err == LFS_ERR_CORRUPT) { + break; } - - crc = lfs_crc(crc, &dat, 1); + return err; } // directory modification tags? @@ -907,11 +1233,24 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs, dir->pair[0], off+sizeof(tag), &temptail, 8); if (err) { if (err == LFS_ERR_CORRUPT) { - dir->erased = false; break; } + return err; } lfs_pair_fromle32(temptail); + } else if (lfs_tag_type3(tag) == LFS_TYPE_FCRC) { + err = lfs_bd_read(lfs, + NULL, &lfs->rcache, lfs->cfg->block_size, + dir->pair[0], off+sizeof(tag), + &fcrc, sizeof(fcrc)); + if (err) { + if (err == LFS_ERR_CORRUPT) { + break; + } + } + + lfs_fcrc_fromle32(&fcrc); + hasfcrc = true; } // found a match for our fetcher? @@ -920,7 +1259,6 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs, dir->pair[0], off+sizeof(tag)}); if (res < 0) { if (res == LFS_ERR_CORRUPT) { - dir->erased = false; break; } return res; @@ -942,38 +1280,70 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs, } } - // consider what we have good enough - if (dir->off > 0) { - // synthetic move - if (lfs_gstate_hasmovehere(&lfs->gstate, dir->pair)) { - if (lfs_tag_id(lfs->gstate.tag) == lfs_tag_id(besttag)) { - besttag |= 0x80000000; - } else if (besttag != -1 && - lfs_tag_id(lfs->gstate.tag) < lfs_tag_id(besttag)) { - besttag -= LFS_MKTAG(0, 1, 0); + // found no valid commits? + if (dir->off == 0) { + // try the other block? + lfs_pair_swap(dir->pair); + dir->rev = revs[(r+1)%2]; + continue; + } + + // did we end on a valid commit? we may have an erased block + dir->erased = false; + if (maybeerased && dir->off % lfs->cfg->prog_size == 0) { + #ifdef LFS_MULTIVERSION + // note versions < lfs2.1 did not have fcrc tags, if + // we're < lfs2.1 treat missing fcrc as erased data + // + // we don't strictly need to do this, but otherwise writing + // to lfs2.0 disks becomes very inefficient + if (lfs_fs_disk_version(lfs) < 0x00020001) { + dir->erased = true; + + } else + #endif + if (hasfcrc) { + // check for an fcrc matching the next prog's erased state, if + // this failed most likely a previous prog was interrupted, we + // need a new erase + uint32_t fcrc_ = 0xffffffff; + int err = lfs_bd_crc(lfs, + NULL, &lfs->rcache, lfs->cfg->block_size, + dir->pair[0], dir->off, fcrc.size, &fcrc_); + if (err && err != LFS_ERR_CORRUPT) { + return err; } - } - // found tag? or found best id? - if (id) { - *id = lfs_min(lfs_tag_id(besttag), dir->count); + // found beginning of erased part? + dir->erased = (fcrc_ == fcrc.crc); } + } - if (lfs_tag_isvalid(besttag)) { - return besttag; - } else if (lfs_tag_id(besttag) < dir->count) { - return LFS_ERR_NOENT; - } else { - return 0; + // synthetic move + if (lfs_gstate_hasmovehere(&lfs->gdisk, dir->pair)) { + if (lfs_tag_id(lfs->gdisk.tag) == lfs_tag_id(besttag)) { + besttag |= 0x80000000; + } else if (besttag != -1 && + lfs_tag_id(lfs->gdisk.tag) < lfs_tag_id(besttag)) { + besttag -= LFS_MKTAG(0, 1, 0); } } - // failed, try the other block? - lfs_pair_swap(dir->pair); - dir->rev = revs[(r+1)%2]; + // found tag? or found best id? + if (id) { + *id = lfs_min(lfs_tag_id(besttag), dir->count); + } + + if (lfs_tag_isvalid(besttag)) { + return besttag; + } else if (lfs_tag_id(besttag) < dir->count) { + return LFS_ERR_NOENT; + } else { + return 0; + } } - LFS_ERROR("Corrupted dir pair at %"PRIx32" %"PRIx32, + LFS_ERROR("Corrupted dir pair at {0x%"PRIx32", 0x%"PRIx32"}", dir->pair[0], dir->pair[1]); return LFS_ERR_CORRUPT; } @@ -987,8 +1357,8 @@ static int lfs_dir_fetch(lfs_t *lfs, } static int lfs_dir_getgstate(lfs_t *lfs, const lfs_mdir_t *dir, - struct lfs_gstate *gstate) { - struct lfs_gstate temp; + lfs_gstate_t *gstate) { + lfs_gstate_t temp; lfs_stag_t res = lfs_dir_get(lfs, dir, LFS_MKTAG(0x7ff, 0, 0), LFS_MKTAG(LFS_TYPE_MOVESTATE, 0, sizeof(temp)), &temp); if (res < 0 && res != LFS_ERR_NOENT) { @@ -1179,6 +1549,7 @@ struct lfs_commit { lfs_off_t end; }; +#ifndef LFS_READONLY static int lfs_dir_commitprog(lfs_t *lfs, struct lfs_commit *commit, const void *buffer, lfs_size_t size) { int err = lfs_bd_prog(lfs, @@ -1193,7 +1564,9 @@ static int lfs_dir_commitprog(lfs_t *lfs, struct lfs_commit *commit, commit->off += size; return 0; } +#endif +#ifndef LFS_READONLY static int lfs_dir_commitattr(lfs_t *lfs, struct lfs_commit *commit, lfs_tag_t tag, const void *buffer) { // check if we fit @@ -1238,93 +1611,156 @@ static int lfs_dir_commitattr(lfs_t *lfs, struct lfs_commit *commit, commit->ptag = tag & 0x7fffffff; return 0; } +#endif + +#ifndef LFS_READONLY static int lfs_dir_commitcrc(lfs_t *lfs, struct lfs_commit *commit) { // align to program units - const lfs_off_t off1 = commit->off + sizeof(lfs_tag_t); - const lfs_off_t end = lfs_alignup(off1 + sizeof(uint32_t), + // + // this gets a bit complex as we have two types of crcs: + // - 5-word crc with fcrc to check following prog (middle of block) + // - 2-word crc with no following prog (end of block) + const lfs_off_t end = lfs_alignup( + lfs_min(commit->off + 5*sizeof(uint32_t), lfs->cfg->block_size), lfs->cfg->prog_size); + lfs_off_t off1 = 0; + uint32_t crc1 = 0; + // create crc tags to fill up remainder of commit, note that - // padding is not crcd, which lets fetches skip padding but + // padding is not crced, which lets fetches skip padding but // makes committing a bit more complicated while (commit->off < end) { - lfs_off_t off = commit->off + sizeof(lfs_tag_t); - lfs_off_t noff = lfs_min(end - off, 0x3fe) + off; + lfs_off_t noff = ( + lfs_min(end - (commit->off+sizeof(lfs_tag_t)), 0x3fe) + + (commit->off+sizeof(lfs_tag_t))); + // too large for crc tag? need padding commits if (noff < end) { - noff = lfs_min(noff, end - 2*sizeof(uint32_t)); + noff = lfs_min(noff, end - 5*sizeof(uint32_t)); } - // read erased state from next program unit - lfs_tag_t tag = LFS_BLOCK_NULL; - int err = lfs_bd_read(lfs, - NULL, &lfs->rcache, sizeof(tag), - commit->block, noff, &tag, sizeof(tag)); - if (err && err != LFS_ERR_CORRUPT) { - return err; - } + // space for fcrc? + uint8_t eperturb = (uint8_t)-1; + if (noff >= end && noff <= lfs->cfg->block_size - lfs->cfg->prog_size) { + // first read the leading byte, this always contains a bit + // we can perturb to avoid writes that don't change the fcrc + int err = lfs_bd_read(lfs, + NULL, &lfs->rcache, lfs->cfg->prog_size, + commit->block, noff, &eperturb, 1); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } + + #ifdef LFS_MULTIVERSION + // unfortunately fcrcs break mdir fetching < lfs2.1, so only write + // these if we're a >= lfs2.1 filesystem + if (lfs_fs_disk_version(lfs) <= 0x00020000) { + // don't write fcrc + } else + #endif + { + // find the expected fcrc, don't bother avoiding a reread + // of the eperturb, it should still be in our cache + struct lfs_fcrc fcrc = { + .size = lfs->cfg->prog_size, + .crc = 0xffffffff + }; + err = lfs_bd_crc(lfs, + NULL, &lfs->rcache, lfs->cfg->prog_size, + commit->block, noff, fcrc.size, &fcrc.crc); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } - // build crc tag - bool reset = ~lfs_frombe32(tag) >> 31; - tag = LFS_MKTAG(LFS_TYPE_CRC + reset, 0x3ff, noff - off); + lfs_fcrc_tole32(&fcrc); + err = lfs_dir_commitattr(lfs, commit, + LFS_MKTAG(LFS_TYPE_FCRC, 0x3ff, sizeof(struct lfs_fcrc)), + &fcrc); + if (err) { + return err; + } + } + } - // write out crc - uint32_t footer[2]; - footer[0] = lfs_tobe32(tag ^ commit->ptag); - commit->crc = lfs_crc(commit->crc, &footer[0], sizeof(footer[0])); - footer[1] = lfs_tole32(commit->crc); - err = lfs_bd_prog(lfs, + // build commit crc + struct { + lfs_tag_t tag; + uint32_t crc; + } ccrc; + lfs_tag_t ntag = LFS_MKTAG( + LFS_TYPE_CCRC + (((uint8_t)~eperturb) >> 7), 0x3ff, + noff - (commit->off+sizeof(lfs_tag_t))); + ccrc.tag = lfs_tobe32(ntag ^ commit->ptag); + commit->crc = lfs_crc(commit->crc, &ccrc.tag, sizeof(lfs_tag_t)); + ccrc.crc = lfs_tole32(commit->crc); + + int err = lfs_bd_prog(lfs, &lfs->pcache, &lfs->rcache, false, - commit->block, commit->off, &footer, sizeof(footer)); + commit->block, commit->off, &ccrc, sizeof(ccrc)); if (err) { return err; } - commit->off += sizeof(tag)+lfs_tag_size(tag); - commit->ptag = tag ^ ((lfs_tag_t)reset << 31); - commit->crc = LFS_BLOCK_NULL; // reset crc for next "commit" - } + // keep track of non-padding checksum to verify + if (off1 == 0) { + off1 = commit->off + sizeof(lfs_tag_t); + crc1 = commit->crc; + } - // flush buffers - int err = lfs_bd_sync(lfs, &lfs->pcache, &lfs->rcache, false); - if (err) { - return err; - } + commit->off = noff; + // perturb valid bit? + commit->ptag = ntag ^ ((0x80UL & ~eperturb) << 24); + // reset crc for next commit + commit->crc = 0xffffffff; - // successful commit, check checksums to make sure - lfs_off_t off = commit->begin; - lfs_off_t noff = off1; - while (off < end) { - uint32_t crc = LFS_BLOCK_NULL; - for (lfs_off_t i = off; i < noff+sizeof(uint32_t); i++) { - // leave it up to caching to make this efficient - uint8_t dat; - err = lfs_bd_read(lfs, - NULL, &lfs->rcache, noff+sizeof(uint32_t)-i, - commit->block, i, &dat, 1); + // manually flush here since we don't prog the padding, this confuses + // the caching layer + if (noff >= end || noff >= lfs->pcache.off + lfs->cfg->cache_size) { + // flush buffers + int err = lfs_bd_sync(lfs, &lfs->pcache, &lfs->rcache, false); if (err) { return err; } - - crc = lfs_crc(crc, &dat, 1); } + } - // detected write error? - if (crc != 0) { - return LFS_ERR_CORRUPT; - } + // successful commit, check checksums to make sure + // + // note that we don't need to check padding commits, worst + // case if they are corrupted we would have had to compact anyways + lfs_off_t off = commit->begin; + uint32_t crc = 0xffffffff; + int err = lfs_bd_crc(lfs, + NULL, &lfs->rcache, off1+sizeof(uint32_t), + commit->block, off, off1-off, &crc); + if (err) { + return err; + } - // skip padding - off = lfs_min(end - noff, 0x3fe) + noff; - if (off < end) { - off = lfs_min(off, end - 2*sizeof(uint32_t)); - } - noff = off + sizeof(uint32_t); + // check non-padding commits against known crc + if (crc != crc1) { + return LFS_ERR_CORRUPT; + } + + // make sure to check crc in case we happen to pick + // up an unrelated crc (frozen block?) + err = lfs_bd_crc(lfs, + NULL, &lfs->rcache, sizeof(uint32_t), + commit->block, off1, sizeof(uint32_t), &crc); + if (err) { + return err; + } + + if (crc != 0) { + return LFS_ERR_CORRUPT; } return 0; } +#endif +#ifndef LFS_READONLY static int lfs_dir_alloc(lfs_t *lfs, lfs_mdir_t *dir) { // allocate pair of dir blocks (backwards, so we write block 1 first) for (int i = 0; i < 2; i++) { @@ -1334,6 +1770,9 @@ static int lfs_dir_alloc(lfs_t *lfs, lfs_mdir_t *dir) { } } + // zero for reproducibility in case initial block is unreadable + dir->rev = 0; + // rather than clobbering one of the blocks we just pretend // the revision may be valid int err = lfs_bd_read(lfs, @@ -1344,12 +1783,16 @@ static int lfs_dir_alloc(lfs_t *lfs, lfs_mdir_t *dir) { return err; } - // make sure we don't immediately evict - dir->rev += dir->rev & 1; + // to make sure we don't immediately evict, align the new revision count + // to our block_cycles modulus, see lfs_dir_compact for why our modulus + // is tweaked this way + if (lfs->cfg->block_cycles > 0) { + dir->rev = lfs_alignup(dir->rev, ((lfs->cfg->block_cycles+1)|1)); + } // set defaults dir->off = sizeof(dir->rev); - dir->etag = LFS_BLOCK_NULL; + dir->etag = 0xffffffff; dir->count = 0; dir->tail[0] = LFS_BLOCK_NULL; dir->tail[1] = LFS_BLOCK_NULL; @@ -1359,7 +1802,9 @@ static int lfs_dir_alloc(lfs_t *lfs, lfs_mdir_t *dir) { // don't write out yet, let caller take care of that return 0; } +#endif +#ifndef LFS_READONLY static int lfs_dir_drop(lfs_t *lfs, lfs_mdir_t *dir, lfs_mdir_t *tail) { // steal state int err = lfs_dir_getgstate(lfs, tail, &lfs->gdelta); @@ -1378,12 +1823,13 @@ static int lfs_dir_drop(lfs_t *lfs, lfs_mdir_t *dir, lfs_mdir_t *tail) { return 0; } +#endif +#ifndef LFS_READONLY static int lfs_dir_split(lfs_t *lfs, lfs_mdir_t *dir, const struct lfs_mattr *attrs, int attrcount, lfs_mdir_t *source, uint16_t split, uint16_t end) { - // create tail directory - lfs_alloc_ack(lfs); + // create tail metadata pair lfs_mdir_t tail; int err = lfs_dir_alloc(lfs, &tail); if (err) { @@ -1394,9 +1840,10 @@ static int lfs_dir_split(lfs_t *lfs, tail.tail[0] = dir->tail[0]; tail.tail[1] = dir->tail[1]; - err = lfs_dir_compact(lfs, &tail, attrs, attrcount, source, split, end); - if (err) { - return err; + // note we don't care about LFS_OK_RELOCATED + int res = lfs_dir_compact(lfs, &tail, attrs, attrcount, source, split, end); + if (res < 0) { + return res; } dir->tail[0] = tail.pair[0]; @@ -1411,7 +1858,9 @@ static int lfs_dir_split(lfs_t *lfs, return 0; } +#endif +#ifndef LFS_READONLY static int lfs_dir_commit_size(void *p, lfs_tag_t tag, const void *buffer) { lfs_size_t *size = p; (void)buffer; @@ -1419,135 +1868,80 @@ static int lfs_dir_commit_size(void *p, lfs_tag_t tag, const void *buffer) { *size += lfs_tag_dsize(tag); return 0; } +#endif +#ifndef LFS_READONLY struct lfs_dir_commit_commit { lfs_t *lfs; struct lfs_commit *commit; }; +#endif +#ifndef LFS_READONLY static int lfs_dir_commit_commit(void *p, lfs_tag_t tag, const void *buffer) { struct lfs_dir_commit_commit *commit = p; return lfs_dir_commitattr(commit->lfs, commit->commit, tag, buffer); } +#endif + +#ifndef LFS_READONLY +static bool lfs_dir_needsrelocation(lfs_t *lfs, lfs_mdir_t *dir) { + // If our revision count == n * block_cycles, we should force a relocation, + // this is how littlefs wear-levels at the metadata-pair level. Note that we + // actually use (block_cycles+1)|1, this is to avoid two corner cases: + // 1. block_cycles = 1, which would prevent relocations from terminating + // 2. block_cycles = 2n, which, due to aliasing, would only ever relocate + // one metadata block in the pair, effectively making this useless + return (lfs->cfg->block_cycles > 0 + && ((dir->rev + 1) % ((lfs->cfg->block_cycles+1)|1) == 0)); +} +#endif +#ifndef LFS_READONLY static int lfs_dir_compact(lfs_t *lfs, lfs_mdir_t *dir, const struct lfs_mattr *attrs, int attrcount, lfs_mdir_t *source, uint16_t begin, uint16_t end) { // save some state in case block is bad - const lfs_block_t oldpair[2] = {dir->pair[1], dir->pair[0]}; bool relocated = false; - bool exhausted = false; - - // should we split? - while (end - begin > 1) { - // find size - lfs_size_t size = 0; - int err = lfs_dir_traverse(lfs, - source, 0, LFS_BLOCK_NULL, attrs, attrcount, false, - LFS_MKTAG(0x400, 0x3ff, 0), - LFS_MKTAG(LFS_TYPE_NAME, 0, 0), - begin, end, -begin, - lfs_dir_commit_size, &size); - if (err) { - return err; - } - - // space is complicated, we need room for tail, crc, gstate, - // cleanup delete, and we cap at half a block to give room - // for metadata updates. - if (end - begin < 0xff && - size <= lfs_min(lfs->cfg->block_size - 36, - lfs_alignup(lfs->cfg->block_size/2, - lfs->cfg->prog_size))) { - break; - } - - // can't fit, need to split, we should really be finding the - // largest size that fits with a small binary search, but right now - // it's not worth the code size - uint16_t split = (end - begin) / 2; - err = lfs_dir_split(lfs, dir, attrs, attrcount, - source, begin+split, end); - if (err) { - // if we fail to split, we may be able to overcompact, unless - // we're too big for even the full block, in which case our - // only option is to error - if (err == LFS_ERR_NOSPC && size <= lfs->cfg->block_size - 36) { - break; - } - return err; - } - - end = begin + split; - } + bool tired = lfs_dir_needsrelocation(lfs, dir); // increment revision count dir->rev += 1; - if (lfs->cfg->block_cycles > 0 && - (dir->rev % (lfs->cfg->block_cycles+1) == 0)) { - if (lfs_pair_cmp(dir->pair, (const lfs_block_t[2]){0, 1}) == 0) { - // oh no! we're writing too much to the superblock, - // should we expand? - lfs_ssize_t res = lfs_fs_size(lfs); - if (res < 0) { - return res; - } - // do we have extra space? littlefs can't reclaim this space - // by itself, so expand cautiously - if ((lfs_size_t)res < lfs->cfg->block_count/2) { - LFS_DEBUG("Expanding superblock at rev %"PRIu32, dir->rev); - int err = lfs_dir_split(lfs, dir, attrs, attrcount, - source, begin, end); - if (err && err != LFS_ERR_NOSPC) { - return err; - } - - // welp, we tried, if we ran out of space there's not much - // we can do, we'll error later if we've become frozen - if (!err) { - end = begin; - } - } + // do not proactively relocate blocks during migrations, this + // can cause a number of failure states such: clobbering the + // v1 superblock if we relocate root, and invalidating directory + // pointers if we relocate the head of a directory. On top of + // this, relocations increase the overall complexity of + // lfs_migration, which is already a delicate operation. #ifdef LFS_MIGRATE - } else if (lfs->lfs1) { - // do not proactively relocate blocks during migrations, this - // can cause a number of failure states such: clobbering the - // v1 superblock if we relocate root, and invalidating directory - // pointers if we relocate the head of a directory. On top of - // this, relocations increase the overall complexity of - // lfs_migration, which is already a delicate operation. + if (lfs->lfs1) { + tired = false; + } #endif - } else { - // we're writing too much, time to relocate - exhausted = true; - goto relocate; - } + + if (tired && lfs_pair_cmp(dir->pair, (const lfs_block_t[2]){0, 1}) != 0) { + // we're writing too much, time to relocate + goto relocate; } // begin loop to commit compaction to blocks until a compact sticks while (true) { { - // There's nothing special about our global delta, so feed it into - // our local global delta - int err = lfs_dir_getgstate(lfs, dir, &lfs->gdelta); - if (err) { - return err; - } - // setup commit state struct lfs_commit commit = { .block = dir->pair[1], .off = 0, - .ptag = LFS_BLOCK_NULL, - .crc = LFS_BLOCK_NULL, + .ptag = 0xffffffff, + .crc = 0xffffffff, .begin = 0, - .end = lfs->cfg->block_size - 8, + .end = (lfs->cfg->metadata_max ? + lfs->cfg->metadata_max : lfs->cfg->block_size) - 8, }; // erase block to write to - err = lfs_bd_erase(lfs, dir->pair[1]); + int err = lfs_bd_erase(lfs, dir->pair[1]); if (err) { if (err == LFS_ERR_CORRUPT) { goto relocate; @@ -1569,7 +1963,7 @@ static int lfs_dir_compact(lfs_t *lfs, // traverse the directory, this time writing out all unique tags err = lfs_dir_traverse(lfs, - source, 0, LFS_BLOCK_NULL, attrs, attrcount, false, + source, 0, 0xffffffff, attrs, attrcount, LFS_MKTAG(0x400, 0x3ff, 0), LFS_MKTAG(LFS_TYPE_NAME, 0, 0), begin, end, -begin, @@ -1597,14 +1991,25 @@ static int lfs_dir_compact(lfs_t *lfs, } } - if (!relocated && !lfs_gstate_iszero(&lfs->gdelta)) { - // commit any globals, unless we're relocating, - // in which case our parent will steal our globals - lfs_gstate_tole32(&lfs->gdelta); + // bring over gstate? + lfs_gstate_t delta = {0}; + if (!relocated) { + lfs_gstate_xor(&delta, &lfs->gdisk); + lfs_gstate_xor(&delta, &lfs->gstate); + } + lfs_gstate_xor(&delta, &lfs->gdelta); + delta.tag &= ~LFS_MKTAG(0, 0, 0x3ff); + + err = lfs_dir_getgstate(lfs, dir, &delta); + if (err) { + return err; + } + + if (!lfs_gstate_iszero(&delta)) { + lfs_gstate_tole32(&delta); err = lfs_dir_commitattr(lfs, &commit, LFS_MKTAG(LFS_TYPE_MOVESTATE, 0x3ff, - sizeof(lfs->gdelta)), &lfs->gdelta); - lfs_gstate_fromle32(&lfs->gdelta); + sizeof(delta)), &delta); if (err) { if (err == LFS_ERR_CORRUPT) { goto relocate; @@ -1613,6 +2018,7 @@ static int lfs_dir_compact(lfs_t *lfs, } } + // complete commit with crc err = lfs_dir_commitcrc(lfs, &commit); if (err) { if (err == LFS_ERR_CORRUPT) { @@ -1627,10 +2033,10 @@ static int lfs_dir_compact(lfs_t *lfs, dir->count = end - begin; dir->off = commit.off; dir->etag = commit.ptag; - // note we able to have already handled move here - if (lfs_gstate_hasmovehere(&lfs->gpending, dir->pair)) { - lfs_gstate_xormove(&lfs->gpending, - &lfs->gpending, 0x3ff, NULL); + // update gstate + lfs->gdelta = (lfs_gstate_t){0}; + if (!relocated) { + lfs->gdisk = lfs->gstate; } } break; @@ -1639,71 +2045,152 @@ static int lfs_dir_compact(lfs_t *lfs, // commit was corrupted, drop caches and prepare to relocate block relocated = true; lfs_cache_drop(lfs, &lfs->pcache); - if (!exhausted) { - LFS_DEBUG("Bad block at %"PRIx32, dir->pair[1]); + if (!tired) { + LFS_DEBUG("Bad block at 0x%"PRIx32, dir->pair[1]); } // can't relocate superblock, filesystem is now frozen - if (lfs_pair_cmp(oldpair, (const lfs_block_t[2]){0, 1}) == 0) { - LFS_WARN("Superblock %"PRIx32" has become unwritable", oldpair[1]); + if (lfs_pair_cmp(dir->pair, (const lfs_block_t[2]){0, 1}) == 0) { + LFS_WARN("Superblock 0x%"PRIx32" has become unwritable", + dir->pair[1]); return LFS_ERR_NOSPC; } // relocate half of pair int err = lfs_alloc(lfs, &dir->pair[1]); - if (err && (err != LFS_ERR_NOSPC && !exhausted)) { + if (err && (err != LFS_ERR_NOSPC || !tired)) { return err; } + + tired = false; continue; } - if (!relocated) { - lfs->gstate = lfs->gpending; - lfs->gdelta = (struct lfs_gstate){0}; - } else { - // update references if we relocated - LFS_DEBUG("Relocating %"PRIx32" %"PRIx32" -> %"PRIx32" %"PRIx32, - oldpair[0], oldpair[1], dir->pair[0], dir->pair[1]); - int err = lfs_fs_relocate(lfs, oldpair, dir->pair); - if (err) { + return relocated ? LFS_OK_RELOCATED : 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_dir_splittingcompact(lfs_t *lfs, lfs_mdir_t *dir, + const struct lfs_mattr *attrs, int attrcount, + lfs_mdir_t *source, uint16_t begin, uint16_t end) { + while (true) { + // find size of first split, we do this by halving the split until + // the metadata is guaranteed to fit + // + // Note that this isn't a true binary search, we never increase the + // split size. This may result in poorly distributed metadata but isn't + // worth the extra code size or performance hit to fix. + lfs_size_t split = begin; + while (end - split > 1) { + lfs_size_t size = 0; + int err = lfs_dir_traverse(lfs, + source, 0, 0xffffffff, attrs, attrcount, + LFS_MKTAG(0x400, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_NAME, 0, 0), + split, end, -split, + lfs_dir_commit_size, &size); + if (err) { + return err; + } + + // space is complicated, we need room for: + // + // - tail: 4+2*4 = 12 bytes + // - gstate: 4+3*4 = 16 bytes + // - move delete: 4 = 4 bytes + // - crc: 4+4 = 8 bytes + // total = 40 bytes + // + // And we cap at half a block to avoid degenerate cases with + // nearly-full metadata blocks. + // + if (end - split < 0xff + && size <= lfs_min( + lfs->cfg->block_size - 40, + lfs_alignup( + (lfs->cfg->metadata_max + ? lfs->cfg->metadata_max + : lfs->cfg->block_size)/2, + lfs->cfg->prog_size))) { + break; + } + + split = split + ((end - split) / 2); + } + + if (split == begin) { + // no split needed + break; + } + + // split into two metadata pairs and continue + int err = lfs_dir_split(lfs, dir, attrs, attrcount, + source, split, end); + if (err && err != LFS_ERR_NOSPC) { return err; } + + if (err) { + // we can't allocate a new block, try to compact with degraded + // performance + LFS_WARN("Unable to split {0x%"PRIx32", 0x%"PRIx32"}", + dir->pair[0], dir->pair[1]); + break; + } else { + end = split; + } } - return 0; -} + if (lfs_dir_needsrelocation(lfs, dir) + && lfs_pair_cmp(dir->pair, (const lfs_block_t[2]){0, 1}) == 0) { + // oh no! we're writing too much to the superblock, + // should we expand? + lfs_ssize_t size = lfs_fs_rawsize(lfs); + if (size < 0) { + return size; + } -static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir, - const struct lfs_mattr *attrs, int attrcount) { - // check for any inline files that aren't RAM backed and - // forcefully evict them, needed for filesystem consistency - for (lfs_file_t *f = (lfs_file_t*)lfs->mlist; f; f = f->next) { - if (dir != &f->m && lfs_pair_cmp(f->m.pair, dir->pair) == 0 && - f->type == LFS_TYPE_REG && (f->flags & LFS_F_INLINE) && - f->ctz.size > lfs->cfg->cache_size) { - int err = lfs_file_outline(lfs, f); - if (err) { + // do we have extra space? littlefs can't reclaim this space + // by itself, so expand cautiously + if ((lfs_size_t)size < lfs->block_count/2) { + LFS_DEBUG("Expanding superblock at rev %"PRIu32, dir->rev); + int err = lfs_dir_split(lfs, dir, attrs, attrcount, + source, begin, end); + if (err && err != LFS_ERR_NOSPC) { return err; } - err = lfs_file_flush(lfs, f); if (err) { - return err; + // welp, we tried, if we ran out of space there's not much + // we can do, we'll error later if we've become frozen + LFS_WARN("Unable to expand superblock"); + } else { + end = begin; } } } + return lfs_dir_compact(lfs, dir, attrs, attrcount, source, begin, end); +} +#endif + +#ifndef LFS_READONLY +static int lfs_dir_relocatingcommit(lfs_t *lfs, lfs_mdir_t *dir, + const lfs_block_t pair[2], + const struct lfs_mattr *attrs, int attrcount, + lfs_mdir_t *pdir) { + int state = 0; + // calculate changes to the directory - lfs_tag_t deletetag = LFS_BLOCK_NULL; - lfs_tag_t createtag = LFS_BLOCK_NULL; + bool hasdelete = false; for (int i = 0; i < attrcount; i++) { if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_CREATE) { - createtag = attrs[i].tag; dir->count += 1; } else if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_DELETE) { - deletetag = attrs[i].tag; LFS_ASSERT(dir->count > 0); dir->count -= 1; + hasdelete = true; } else if (lfs_tag_type1(attrs[i].tag) == LFS_TYPE_TAIL) { dir->tail[0] = ((lfs_block_t*)attrs[i].buffer)[0]; dir->tail[1] = ((lfs_block_t*)attrs[i].buffer)[1]; @@ -1712,45 +2199,37 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir, } } - // do we have a pending move? - if (lfs_gstate_hasmovehere(&lfs->gpending, dir->pair)) { - deletetag = lfs->gpending.tag & LFS_MKTAG(0x7ff, 0x3ff, 0); - LFS_ASSERT(dir->count > 0); - dir->count -= 1; - - // mark gdelta so we reflect the move we will fix - lfs_gstate_xormove(&lfs->gdelta, &lfs->gpending, 0x3ff, NULL); - } - // should we actually drop the directory block? - if (lfs_tag_isvalid(deletetag) && dir->count == 0) { - lfs_mdir_t pdir; - int err = lfs_fs_pred(lfs, dir->pair, &pdir); + if (hasdelete && dir->count == 0) { + LFS_ASSERT(pdir); + int err = lfs_fs_pred(lfs, dir->pair, pdir); if (err && err != LFS_ERR_NOENT) { return err; } - if (err != LFS_ERR_NOENT && pdir.split) { - return lfs_dir_drop(lfs, &pdir, dir); + if (err != LFS_ERR_NOENT && pdir->split) { + state = LFS_OK_DROPPED; + goto fixmlist; } } - if (dir->erased || dir->count >= 0xff) { + if (dir->erased) { // try to commit struct lfs_commit commit = { .block = dir->pair[0], .off = dir->off, .ptag = dir->etag, - .crc = LFS_BLOCK_NULL, + .crc = 0xffffffff, .begin = dir->off, - .end = lfs->cfg->block_size - 8, + .end = (lfs->cfg->metadata_max ? + lfs->cfg->metadata_max : lfs->cfg->block_size) - 8, }; // traverse attrs that need to be written out lfs_pair_tole32(dir->tail); int err = lfs_dir_traverse(lfs, - dir, dir->off, dir->etag, attrs, attrcount, false, + dir, dir->off, dir->etag, attrs, attrcount, 0, 0, 0, 0, 0, lfs_dir_commit_commit, &(struct lfs_dir_commit_commit){ lfs, &commit}); @@ -1763,17 +2242,21 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir, } // commit any global diffs if we have any - if (!lfs_gstate_iszero(&lfs->gdelta)) { - err = lfs_dir_getgstate(lfs, dir, &lfs->gdelta); + lfs_gstate_t delta = {0}; + lfs_gstate_xor(&delta, &lfs->gstate); + lfs_gstate_xor(&delta, &lfs->gdisk); + lfs_gstate_xor(&delta, &lfs->gdelta); + delta.tag &= ~LFS_MKTAG(0, 0, 0x3ff); + if (!lfs_gstate_iszero(&delta)) { + err = lfs_dir_getgstate(lfs, dir, &delta); if (err) { return err; } - lfs_gstate_tole32(&lfs->gdelta); + lfs_gstate_tole32(&delta); err = lfs_dir_commitattr(lfs, &commit, LFS_MKTAG(LFS_TYPE_MOVESTATE, 0x3ff, - sizeof(lfs->gdelta)), &lfs->gdelta); - lfs_gstate_fromle32(&lfs->gdelta); + sizeof(delta)), &delta); if (err) { if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) { goto compact; @@ -1795,47 +2278,56 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir, LFS_ASSERT(commit.off % lfs->cfg->prog_size == 0); dir->off = commit.off; dir->etag = commit.ptag; + // and update gstate + lfs->gdisk = lfs->gstate; + lfs->gdelta = (lfs_gstate_t){0}; - // note we able to have already handled move here - if (lfs_gstate_hasmovehere(&lfs->gpending, dir->pair)) { - lfs_gstate_xormove(&lfs->gpending, &lfs->gpending, 0x3ff, NULL); - } + goto fixmlist; + } - // update gstate - lfs->gstate = lfs->gpending; - lfs->gdelta = (struct lfs_gstate){0}; - } else { compact: - // fall back to compaction - lfs_cache_drop(lfs, &lfs->pcache); + // fall back to compaction + lfs_cache_drop(lfs, &lfs->pcache); - int err = lfs_dir_compact(lfs, dir, attrs, attrcount, - dir, 0, dir->count); - if (err) { - return err; - } + state = lfs_dir_splittingcompact(lfs, dir, attrs, attrcount, + dir, 0, dir->count); + if (state < 0) { + return state; } - // update any directories that are affected - lfs_mdir_t copy = *dir; + goto fixmlist; - // two passes, once for things that aren't us, and one - // for things that are +fixmlist:; + // this complicated bit of logic is for fixing up any active + // metadata-pairs that we may have affected + // + // note we have to make two passes since the mdir passed to + // lfs_dir_commit could also be in this list, and even then + // we need to copy the pair so they don't get clobbered if we refetch + // our mdir. + lfs_block_t oldpair[2] = {pair[0], pair[1]}; for (struct lfs_mlist *d = lfs->mlist; d; d = d->next) { - if (lfs_pair_cmp(d->m.pair, copy.pair) == 0) { + if (lfs_pair_cmp(d->m.pair, oldpair) == 0) { d->m = *dir; - if (d->id == lfs_tag_id(deletetag)) { - d->m.pair[0] = LFS_BLOCK_NULL; - d->m.pair[1] = LFS_BLOCK_NULL; - } else if (d->id > lfs_tag_id(deletetag)) { - d->id -= 1; - if (d->type == LFS_TYPE_DIR) { - ((lfs_dir_t*)d)->pos -= 1; - } - } else if (&d->m != dir && d->id >= lfs_tag_id(createtag)) { - d->id += 1; - if (d->type == LFS_TYPE_DIR) { - ((lfs_dir_t*)d)->pos += 1; + if (d->m.pair != pair) { + for (int i = 0; i < attrcount; i++) { + if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_DELETE && + d->id == lfs_tag_id(attrs[i].tag)) { + d->m.pair[0] = LFS_BLOCK_NULL; + d->m.pair[1] = LFS_BLOCK_NULL; + } else if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_DELETE && + d->id > lfs_tag_id(attrs[i].tag)) { + d->id -= 1; + if (d->type == LFS_TYPE_DIR) { + ((lfs_dir_t*)d)->pos -= 1; + } + } else if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_CREATE && + d->id >= lfs_tag_id(attrs[i].tag)) { + d->id += 1; + if (d->type == LFS_TYPE_DIR) { + ((lfs_dir_t*)d)->pos += 1; + } + } } } @@ -1850,114 +2342,337 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir, } } - return 0; + return state; } +#endif +#ifndef LFS_READONLY +static int lfs_dir_orphaningcommit(lfs_t *lfs, lfs_mdir_t *dir, + const struct lfs_mattr *attrs, int attrcount) { + // check for any inline files that aren't RAM backed and + // forcefully evict them, needed for filesystem consistency + for (lfs_file_t *f = (lfs_file_t*)lfs->mlist; f; f = f->next) { + if (dir != &f->m && lfs_pair_cmp(f->m.pair, dir->pair) == 0 && + f->type == LFS_TYPE_REG && (f->flags & LFS_F_INLINE) && + f->ctz.size > lfs->cfg->cache_size) { + int err = lfs_file_outline(lfs, f); + if (err) { + return err; + } -/// Top level directory operations /// -int lfs_mkdir(lfs_t *lfs, const char *path) { - LFS_TRACE("lfs_mkdir(%p, \"%s\")", (void*)lfs, path); - // deorphan if we haven't yet, needed at most once after poweron - int err = lfs_fs_forceconsistency(lfs); - if (err) { - LFS_TRACE("lfs_mkdir -> %d", err); - return err; + err = lfs_file_flush(lfs, f); + if (err) { + return err; + } + } } - lfs_mdir_t cwd; - uint16_t id; - err = lfs_dir_find(lfs, &cwd, &path, &id); - if (!(err == LFS_ERR_NOENT && id != 0x3ff)) { - LFS_TRACE("lfs_mkdir -> %d", (err < 0) ? err : LFS_ERR_EXIST); - return (err < 0) ? err : LFS_ERR_EXIST; + lfs_block_t lpair[2] = {dir->pair[0], dir->pair[1]}; + lfs_mdir_t ldir = *dir; + lfs_mdir_t pdir; + int state = lfs_dir_relocatingcommit(lfs, &ldir, dir->pair, + attrs, attrcount, &pdir); + if (state < 0) { + return state; } - // check that name fits - lfs_size_t nlen = strlen(path); - if (nlen > lfs->name_max) { - LFS_TRACE("lfs_mkdir -> %d", LFS_ERR_NAMETOOLONG); - return LFS_ERR_NAMETOOLONG; + // update if we're not in mlist, note we may have already been + // updated if we are in mlist + if (lfs_pair_cmp(dir->pair, lpair) == 0) { + *dir = ldir; } - // build up new directory - lfs_alloc_ack(lfs); - lfs_mdir_t dir; - err = lfs_dir_alloc(lfs, &dir); - if (err) { - LFS_TRACE("lfs_mkdir -> %d", err); - return err; - } + // commit was successful, but may require other changes in the + // filesystem, these would normally be tail recursive, but we have + // flattened them here avoid unbounded stack usage - // find end of list - lfs_mdir_t pred = cwd; - while (pred.split) { - err = lfs_dir_fetch(lfs, &pred, pred.tail); + // need to drop? + if (state == LFS_OK_DROPPED) { + // steal state + int err = lfs_dir_getgstate(lfs, dir, &lfs->gdelta); if (err) { - LFS_TRACE("lfs_mkdir -> %d", err); return err; } - } - - // setup dir - lfs_pair_tole32(pred.tail); - err = lfs_dir_commit(lfs, &dir, LFS_MKATTRS( - {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), pred.tail})); - lfs_pair_fromle32(pred.tail); - if (err) { - LFS_TRACE("lfs_mkdir -> %d", err); - return err; - } - // current block end of list? - if (cwd.split) { - // update tails, this creates a desync - lfs_fs_preporphans(lfs, +1); - lfs_pair_tole32(dir.pair); - err = lfs_dir_commit(lfs, &pred, LFS_MKATTRS( - {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), dir.pair})); - lfs_pair_fromle32(dir.pair); - if (err) { - LFS_TRACE("lfs_mkdir -> %d", err); - return err; + // steal tail, note that this can't create a recursive drop + lpair[0] = pdir.pair[0]; + lpair[1] = pdir.pair[1]; + lfs_pair_tole32(dir->tail); + state = lfs_dir_relocatingcommit(lfs, &pdir, lpair, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_TAIL + dir->split, 0x3ff, 8), + dir->tail}), + NULL); + lfs_pair_fromle32(dir->tail); + if (state < 0) { + return state; } - lfs_fs_preporphans(lfs, -1); - } - // now insert into our parent block - lfs_pair_tole32(dir.pair); - err = lfs_dir_commit(lfs, &cwd, LFS_MKATTRS( - {LFS_MKTAG(LFS_TYPE_CREATE, id, 0), NULL}, - {LFS_MKTAG(LFS_TYPE_DIR, id, nlen), path}, - {LFS_MKTAG(LFS_TYPE_DIRSTRUCT, id, 8), dir.pair}, - {!cwd.split - ? LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8) - : LFS_MKTAG(LFS_FROM_NOOP, 0, 0), dir.pair})); - lfs_pair_fromle32(dir.pair); - if (err) { - LFS_TRACE("lfs_mkdir -> %d", err); - return err; + ldir = pdir; } - LFS_TRACE("lfs_mkdir -> %d", 0); - return 0; -} + // need to relocate? + bool orphans = false; + while (state == LFS_OK_RELOCATED) { + LFS_DEBUG("Relocating {0x%"PRIx32", 0x%"PRIx32"} " + "-> {0x%"PRIx32", 0x%"PRIx32"}", + lpair[0], lpair[1], ldir.pair[0], ldir.pair[1]); + state = 0; -int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path) { - LFS_TRACE("lfs_dir_open(%p, %p, \"%s\")", (void*)lfs, (void*)dir, path); - lfs_stag_t tag = lfs_dir_find(lfs, &dir->m, &path, NULL); - if (tag < 0) { - LFS_TRACE("lfs_dir_open -> %"PRId32, tag); - return tag; - } + // update internal root + if (lfs_pair_cmp(lpair, lfs->root) == 0) { + lfs->root[0] = ldir.pair[0]; + lfs->root[1] = ldir.pair[1]; + } - if (lfs_tag_type3(tag) != LFS_TYPE_DIR) { - LFS_TRACE("lfs_dir_open -> %d", LFS_ERR_NOTDIR); - return LFS_ERR_NOTDIR; - } + // update internally tracked dirs + for (struct lfs_mlist *d = lfs->mlist; d; d = d->next) { + if (lfs_pair_cmp(lpair, d->m.pair) == 0) { + d->m.pair[0] = ldir.pair[0]; + d->m.pair[1] = ldir.pair[1]; + } - lfs_block_t pair[2]; - if (lfs_tag_id(tag) == 0x3ff) { - // handle root dir separately + if (d->type == LFS_TYPE_DIR && + lfs_pair_cmp(lpair, ((lfs_dir_t*)d)->head) == 0) { + ((lfs_dir_t*)d)->head[0] = ldir.pair[0]; + ((lfs_dir_t*)d)->head[1] = ldir.pair[1]; + } + } + + // find parent + lfs_stag_t tag = lfs_fs_parent(lfs, lpair, &pdir); + if (tag < 0 && tag != LFS_ERR_NOENT) { + return tag; + } + + bool hasparent = (tag != LFS_ERR_NOENT); + if (tag != LFS_ERR_NOENT) { + // note that if we have a parent, we must have a pred, so this will + // always create an orphan + int err = lfs_fs_preporphans(lfs, +1); + if (err) { + return err; + } + + // fix pending move in this pair? this looks like an optimization but + // is in fact _required_ since relocating may outdate the move. + uint16_t moveid = 0x3ff; + if (lfs_gstate_hasmovehere(&lfs->gstate, pdir.pair)) { + moveid = lfs_tag_id(lfs->gstate.tag); + LFS_DEBUG("Fixing move while relocating " + "{0x%"PRIx32", 0x%"PRIx32"} 0x%"PRIx16"\n", + pdir.pair[0], pdir.pair[1], moveid); + lfs_fs_prepmove(lfs, 0x3ff, NULL); + if (moveid < lfs_tag_id(tag)) { + tag -= LFS_MKTAG(0, 1, 0); + } + } + + lfs_block_t ppair[2] = {pdir.pair[0], pdir.pair[1]}; + lfs_pair_tole32(ldir.pair); + state = lfs_dir_relocatingcommit(lfs, &pdir, ppair, LFS_MKATTRS( + {LFS_MKTAG_IF(moveid != 0x3ff, + LFS_TYPE_DELETE, moveid, 0), NULL}, + {tag, ldir.pair}), + NULL); + lfs_pair_fromle32(ldir.pair); + if (state < 0) { + return state; + } + + if (state == LFS_OK_RELOCATED) { + lpair[0] = ppair[0]; + lpair[1] = ppair[1]; + ldir = pdir; + orphans = true; + continue; + } + } + + // find pred + int err = lfs_fs_pred(lfs, lpair, &pdir); + if (err && err != LFS_ERR_NOENT) { + return err; + } + LFS_ASSERT(!(hasparent && err == LFS_ERR_NOENT)); + + // if we can't find dir, it must be new + if (err != LFS_ERR_NOENT) { + if (lfs_gstate_hasorphans(&lfs->gstate)) { + // next step, clean up orphans + err = lfs_fs_preporphans(lfs, -hasparent); + if (err) { + return err; + } + } + + // fix pending move in this pair? this looks like an optimization + // but is in fact _required_ since relocating may outdate the move. + uint16_t moveid = 0x3ff; + if (lfs_gstate_hasmovehere(&lfs->gstate, pdir.pair)) { + moveid = lfs_tag_id(lfs->gstate.tag); + LFS_DEBUG("Fixing move while relocating " + "{0x%"PRIx32", 0x%"PRIx32"} 0x%"PRIx16"\n", + pdir.pair[0], pdir.pair[1], moveid); + lfs_fs_prepmove(lfs, 0x3ff, NULL); + } + + // replace bad pair, either we clean up desync, or no desync occured + lpair[0] = pdir.pair[0]; + lpair[1] = pdir.pair[1]; + lfs_pair_tole32(ldir.pair); + state = lfs_dir_relocatingcommit(lfs, &pdir, lpair, LFS_MKATTRS( + {LFS_MKTAG_IF(moveid != 0x3ff, + LFS_TYPE_DELETE, moveid, 0), NULL}, + {LFS_MKTAG(LFS_TYPE_TAIL + pdir.split, 0x3ff, 8), + ldir.pair}), + NULL); + lfs_pair_fromle32(ldir.pair); + if (state < 0) { + return state; + } + + ldir = pdir; + } + } + + return orphans ? LFS_OK_ORPHANED : 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir, + const struct lfs_mattr *attrs, int attrcount) { + int orphans = lfs_dir_orphaningcommit(lfs, dir, attrs, attrcount); + if (orphans < 0) { + return orphans; + } + + if (orphans) { + // make sure we've removed all orphans, this is a noop if there + // are none, but if we had nested blocks failures we may have + // created some + int err = lfs_fs_deorphan(lfs, false); + if (err) { + return err; + } + } + + return 0; +} +#endif + + +/// Top level directory operations /// +#ifndef LFS_READONLY +static int lfs_rawmkdir(lfs_t *lfs, const char *path) { + // deorphan if we haven't yet, needed at most once after poweron + int err = lfs_fs_forceconsistency(lfs); + if (err) { + return err; + } + + struct lfs_mlist cwd; + cwd.next = lfs->mlist; + uint16_t id; + err = lfs_dir_find(lfs, &cwd.m, &path, &id); + if (!(err == LFS_ERR_NOENT && id != 0x3ff)) { + return (err < 0) ? err : LFS_ERR_EXIST; + } + + // check that name fits + lfs_size_t nlen = strlen(path); + if (nlen > lfs->name_max) { + return LFS_ERR_NAMETOOLONG; + } + + // build up new directory + lfs_alloc_ack(lfs); + lfs_mdir_t dir; + err = lfs_dir_alloc(lfs, &dir); + if (err) { + return err; + } + + // find end of list + lfs_mdir_t pred = cwd.m; + while (pred.split) { + err = lfs_dir_fetch(lfs, &pred, pred.tail); + if (err) { + return err; + } + } + + // setup dir + lfs_pair_tole32(pred.tail); + err = lfs_dir_commit(lfs, &dir, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), pred.tail})); + lfs_pair_fromle32(pred.tail); + if (err) { + return err; + } + + // current block not end of list? + if (cwd.m.split) { + // update tails, this creates a desync + err = lfs_fs_preporphans(lfs, +1); + if (err) { + return err; + } + + // it's possible our predecessor has to be relocated, and if + // our parent is our predecessor's predecessor, this could have + // caused our parent to go out of date, fortunately we can hook + // ourselves into littlefs to catch this + cwd.type = 0; + cwd.id = 0; + lfs->mlist = &cwd; + + lfs_pair_tole32(dir.pair); + err = lfs_dir_commit(lfs, &pred, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), dir.pair})); + lfs_pair_fromle32(dir.pair); + if (err) { + lfs->mlist = cwd.next; + return err; + } + + lfs->mlist = cwd.next; + err = lfs_fs_preporphans(lfs, -1); + if (err) { + return err; + } + } + + // now insert into our parent block + lfs_pair_tole32(dir.pair); + err = lfs_dir_commit(lfs, &cwd.m, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_CREATE, id, 0), NULL}, + {LFS_MKTAG(LFS_TYPE_DIR, id, nlen), path}, + {LFS_MKTAG(LFS_TYPE_DIRSTRUCT, id, 8), dir.pair}, + {LFS_MKTAG_IF(!cwd.m.split, + LFS_TYPE_SOFTTAIL, 0x3ff, 8), dir.pair})); + lfs_pair_fromle32(dir.pair); + if (err) { + return err; + } + + return 0; +} +#endif + +static int lfs_dir_rawopen(lfs_t *lfs, lfs_dir_t *dir, const char *path) { + lfs_stag_t tag = lfs_dir_find(lfs, &dir->m, &path, NULL); + if (tag < 0) { + return tag; + } + + if (lfs_tag_type3(tag) != LFS_TYPE_DIR) { + return LFS_ERR_NOTDIR; + } + + lfs_block_t pair[2]; + if (lfs_tag_id(tag) == 0x3ff) { + // handle root dir separately pair[0] = lfs->root[0]; pair[1] = lfs->root[1]; } else { @@ -1965,7 +2680,6 @@ int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path) { lfs_stag_t res = lfs_dir_get(lfs, &dir->m, LFS_MKTAG(0x700, 0x3ff, 0), LFS_MKTAG(LFS_TYPE_STRUCT, lfs_tag_id(tag), 8), pair); if (res < 0) { - LFS_TRACE("lfs_dir_open -> %"PRId32, res); return res; } lfs_pair_fromle32(pair); @@ -1974,7 +2688,6 @@ int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path) { // fetch first pair int err = lfs_dir_fetch(lfs, &dir->m, pair); if (err) { - LFS_TRACE("lfs_dir_open -> %d", err); return err; } @@ -1986,30 +2699,19 @@ int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path) { // add to list of mdirs dir->type = LFS_TYPE_DIR; - dir->next = (lfs_dir_t*)lfs->mlist; - lfs->mlist = (struct lfs_mlist*)dir; + lfs_mlist_append(lfs, (struct lfs_mlist *)dir); - LFS_TRACE("lfs_dir_open -> %d", 0); return 0; } -int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir) { - LFS_TRACE("lfs_dir_close(%p, %p)", (void*)lfs, (void*)dir); +static int lfs_dir_rawclose(lfs_t *lfs, lfs_dir_t *dir) { // remove from list of mdirs - for (struct lfs_mlist **p = &lfs->mlist; *p; p = &(*p)->next) { - if (*p == (struct lfs_mlist*)dir) { - *p = (*p)->next; - break; - } - } + lfs_mlist_remove(lfs, (struct lfs_mlist *)dir); - LFS_TRACE("lfs_dir_close -> %d", 0); return 0; } -int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) { - LFS_TRACE("lfs_dir_read(%p, %p, %p)", - (void*)lfs, (void*)dir, (void*)info); +static int lfs_dir_rawread(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) { memset(info, 0, sizeof(*info)); // special offset for '.' and '..' @@ -2017,26 +2719,22 @@ int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) { info->type = LFS_TYPE_DIR; strcpy(info->name, "."); dir->pos += 1; - LFS_TRACE("lfs_dir_read -> %d", true); return true; } else if (dir->pos == 1) { info->type = LFS_TYPE_DIR; strcpy(info->name, ".."); dir->pos += 1; - LFS_TRACE("lfs_dir_read -> %d", true); return true; } while (true) { if (dir->id == dir->m.count) { if (!dir->m.split) { - LFS_TRACE("lfs_dir_read -> %d", false); return false; } int err = lfs_dir_fetch(lfs, &dir->m, dir->m.tail); if (err) { - LFS_TRACE("lfs_dir_read -> %d", err); return err; } @@ -2045,7 +2743,6 @@ int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) { int err = lfs_dir_getinfo(lfs, &dir->m, dir->id, info); if (err && err != LFS_ERR_NOENT) { - LFS_TRACE("lfs_dir_read -> %d", err); return err; } @@ -2056,17 +2753,13 @@ int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) { } dir->pos += 1; - LFS_TRACE("lfs_dir_read -> %d", true); return true; } -int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off) { - LFS_TRACE("lfs_dir_seek(%p, %p, %"PRIu32")", - (void*)lfs, (void*)dir, off); +static int lfs_dir_rawseek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off) { // simply walk from head dir - int err = lfs_dir_rewind(lfs, dir); + int err = lfs_dir_rawrewind(lfs, dir); if (err) { - LFS_TRACE("lfs_dir_seek -> %d", err); return err; } @@ -2078,50 +2771,42 @@ int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off) { dir->id = (off > 0 && lfs_pair_cmp(dir->head, lfs->root) == 0); while (off > 0) { - int diff = lfs_min(dir->m.count - dir->id, off); - dir->id += diff; - dir->pos += diff; - off -= diff; - if (dir->id == dir->m.count) { if (!dir->m.split) { - LFS_TRACE("lfs_dir_seek -> %d", LFS_ERR_INVAL); return LFS_ERR_INVAL; } err = lfs_dir_fetch(lfs, &dir->m, dir->m.tail); if (err) { - LFS_TRACE("lfs_dir_seek -> %d", err); return err; } dir->id = 0; } + + int diff = lfs_min(dir->m.count - dir->id, off); + dir->id += diff; + dir->pos += diff; + off -= diff; } - LFS_TRACE("lfs_dir_seek -> %d", 0); return 0; } -lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir) { - LFS_TRACE("lfs_dir_tell(%p, %p)", (void*)lfs, (void*)dir); +static lfs_soff_t lfs_dir_rawtell(lfs_t *lfs, lfs_dir_t *dir) { (void)lfs; - LFS_TRACE("lfs_dir_tell -> %"PRId32, dir->pos); return dir->pos; } -int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir) { - LFS_TRACE("lfs_dir_rewind(%p, %p)", (void*)lfs, (void*)dir); +static int lfs_dir_rawrewind(lfs_t *lfs, lfs_dir_t *dir) { // reload the head dir int err = lfs_dir_fetch(lfs, &dir->m, dir->head); if (err) { - LFS_TRACE("lfs_dir_rewind -> %d", err); return err; } dir->id = 0; dir->pos = 0; - LFS_TRACE("lfs_dir_rewind -> %d", 0); return 0; } @@ -2166,7 +2851,6 @@ static int lfs_ctz_find(lfs_t *lfs, return err; } - LFS_ASSERT(head >= 2 && head <= lfs->cfg->block_count); current -= 1 << skip; } @@ -2175,6 +2859,7 @@ static int lfs_ctz_find(lfs_t *lfs, return 0; } +#ifndef LFS_READONLY static int lfs_ctz_extend(lfs_t *lfs, lfs_cache_t *pcache, lfs_cache_t *rcache, lfs_block_t head, lfs_size_t size, @@ -2186,7 +2871,6 @@ static int lfs_ctz_extend(lfs_t *lfs, if (err) { return err; } - LFS_ASSERT(nblock >= 2 && nblock <= lfs->cfg->block_count); { err = lfs_bd_erase(lfs, nblock); @@ -2203,16 +2887,16 @@ static int lfs_ctz_extend(lfs_t *lfs, return 0; } - size -= 1; - lfs_off_t index = lfs_ctz_index(lfs, &size); - size += 1; + lfs_size_t noff = size - 1; + lfs_off_t index = lfs_ctz_index(lfs, &noff); + noff = noff + 1; // just copy out the last block if it is incomplete - if (size != lfs->cfg->block_size) { - for (lfs_off_t i = 0; i < size; i++) { + if (noff != lfs->cfg->block_size) { + for (lfs_off_t i = 0; i < noff; i++) { uint8_t data; err = lfs_bd_read(lfs, - NULL, rcache, size-i, + NULL, rcache, noff-i, head, i, &data, 1); if (err) { return err; @@ -2230,19 +2914,19 @@ static int lfs_ctz_extend(lfs_t *lfs, } *block = nblock; - *off = size; + *off = noff; return 0; } // append block index += 1; lfs_size_t skips = lfs_ctz(index) + 1; - + lfs_block_t nhead = head; for (lfs_off_t i = 0; i < skips; i++) { - head = lfs_tole32(head); + nhead = lfs_tole32(nhead); err = lfs_bd_prog(lfs, pcache, rcache, true, - nblock, 4*i, &head, 4); - head = lfs_fromle32(head); + nblock, 4*i, &nhead, 4); + nhead = lfs_fromle32(nhead); if (err) { if (err == LFS_ERR_CORRUPT) { goto relocate; @@ -2252,15 +2936,13 @@ static int lfs_ctz_extend(lfs_t *lfs, if (i != skips-1) { err = lfs_bd_read(lfs, - NULL, rcache, sizeof(head), - head, 4*i, &head, sizeof(head)); - head = lfs_fromle32(head); + NULL, rcache, sizeof(nhead), + nhead, 4*i, &nhead, sizeof(nhead)); + nhead = lfs_fromle32(nhead); if (err) { return err; } } - - LFS_ASSERT(head >= 2 && head <= lfs->cfg->block_count); } *block = nblock; @@ -2269,12 +2951,13 @@ static int lfs_ctz_extend(lfs_t *lfs, } relocate: - LFS_DEBUG("Bad block at %"PRIx32, nblock); + LFS_DEBUG("Bad block at 0x%"PRIx32, nblock); // just clear cache and try a new block lfs_cache_drop(lfs, pcache); } } +#endif static int lfs_ctz_traverse(lfs_t *lfs, const lfs_cache_t *pcache, lfs_cache_t *rcache, @@ -2321,27 +3004,25 @@ static int lfs_ctz_traverse(lfs_t *lfs, /// Top level file operations /// -int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, +static int lfs_file_rawopencfg(lfs_t *lfs, lfs_file_t *file, const char *path, int flags, const struct lfs_file_config *cfg) { - LFS_TRACE("lfs_file_opencfg(%p, %p, \"%s\", %x, %p {" - ".buffer=%p, .attrs=%p, .attr_count=%"PRIu32"})", - (void*)lfs, (void*)file, path, flags, - (void*)cfg, cfg->buffer, (void*)cfg->attrs, cfg->attr_count); - +#ifndef LFS_READONLY // deorphan if we haven't yet, needed at most once after poweron - if ((flags & 3) != LFS_O_RDONLY) { + if ((flags & LFS_O_WRONLY) == LFS_O_WRONLY) { int err = lfs_fs_forceconsistency(lfs); if (err) { - LFS_TRACE("lfs_file_opencfg -> %d", err); return err; } } +#else + LFS_ASSERT((flags & LFS_O_RDONLY) == LFS_O_RDONLY); +#endif // setup simple file details int err; file->cfg = cfg; - file->flags = flags | LFS_F_OPENED; + file->flags = flags; file->pos = 0; file->off = 0; file->cache.buffer = NULL; @@ -2355,9 +3036,13 @@ int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, // get id, add to list of mdirs to catch update changes file->type = LFS_TYPE_REG; - file->next = (lfs_file_t*)lfs->mlist; - lfs->mlist = (struct lfs_mlist*)file; + lfs_mlist_append(lfs, (struct lfs_mlist *)file); +#ifdef LFS_READONLY + if (tag == LFS_ERR_NOENT) { + err = LFS_ERR_NOENT; + goto cleanup; +#else if (tag == LFS_ERR_NOENT) { if (!(flags & LFS_O_CREAT)) { err = LFS_ERR_NOENT; @@ -2376,8 +3061,11 @@ int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, {LFS_MKTAG(LFS_TYPE_CREATE, file->id, 0), NULL}, {LFS_MKTAG(LFS_TYPE_REG, file->id, nlen), path}, {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0), NULL})); + + // it may happen that the file name doesn't fit in the metadata blocks, e.g., a 256 byte file name will + // not fit in a 128 byte block. + err = (err == LFS_ERR_NOSPC) ? LFS_ERR_NAMETOOLONG : err; if (err) { - err = LFS_ERR_NAMETOOLONG; goto cleanup; } @@ -2385,13 +3073,16 @@ int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, } else if (flags & LFS_O_EXCL) { err = LFS_ERR_EXIST; goto cleanup; +#endif } else if (lfs_tag_type3(tag) != LFS_TYPE_REG) { err = LFS_ERR_ISDIR; goto cleanup; +#ifndef LFS_READONLY } else if (flags & LFS_O_TRUNC) { // truncate if requested tag = LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0); file->flags |= LFS_F_DIRTY; +#endif } else { // try to load what's on disk, if it's inlined we'll fix it later tag = lfs_dir_get(lfs, &file->m, LFS_MKTAG(0x700, 0x3ff, 0), @@ -2405,7 +3096,8 @@ int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, // fetch attrs for (unsigned i = 0; i < file->cfg->attr_count; i++) { - if ((file->flags & 3) != LFS_O_WRONLY) { + // if opened for read / read-write operations + if ((file->flags & LFS_O_RDONLY) == LFS_O_RDONLY) { lfs_stag_t res = lfs_dir_get(lfs, &file->m, LFS_MKTAG(0x7ff, 0x3ff, 0), LFS_MKTAG(LFS_TYPE_USERATTR + file->cfg->attrs[i].type, @@ -2417,7 +3109,9 @@ int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, } } - if ((file->flags & 3) != LFS_O_RDONLY) { +#ifndef LFS_READONLY + // if opened for write / read-write operations + if ((file->flags & LFS_O_WRONLY) == LFS_O_WRONLY) { if (file->cfg->attrs[i].size > lfs->attr_max) { err = LFS_ERR_NOSPC; goto cleanup; @@ -2425,6 +3119,7 @@ int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, file->flags |= LFS_F_DIRTY; } +#endif } // allocate buffer if needed @@ -2464,54 +3159,47 @@ int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, } } - LFS_TRACE("lfs_file_opencfg -> %d", 0); return 0; cleanup: // clean up lingering resources +#ifndef LFS_READONLY file->flags |= LFS_F_ERRED; - lfs_file_close(lfs, file); - LFS_TRACE("lfs_file_opencfg -> %d", err); +#endif + lfs_file_rawclose(lfs, file); return err; } -int lfs_file_open(lfs_t *lfs, lfs_file_t *file, +#ifndef LFS_NO_MALLOC +static int lfs_file_rawopen(lfs_t *lfs, lfs_file_t *file, const char *path, int flags) { - LFS_TRACE("lfs_file_open(%p, %p, \"%s\", %x)", - (void*)lfs, (void*)file, path, flags); static const struct lfs_file_config defaults = {0}; - int err = lfs_file_opencfg(lfs, file, path, flags, &defaults); - LFS_TRACE("lfs_file_open -> %d", err); + int err = lfs_file_rawopencfg(lfs, file, path, flags, &defaults); return err; } +#endif -int lfs_file_close(lfs_t *lfs, lfs_file_t *file) { - LFS_TRACE("lfs_file_close(%p, %p)", (void*)lfs, (void*)file); - LFS_ASSERT(file->flags & LFS_F_OPENED); - - int err = lfs_file_sync(lfs, file); +static int lfs_file_rawclose(lfs_t *lfs, lfs_file_t *file) { +#ifndef LFS_READONLY + int err = lfs_file_rawsync(lfs, file); +#else + int err = 0; +#endif // remove from list of mdirs - for (struct lfs_mlist **p = &lfs->mlist; *p; p = &(*p)->next) { - if (*p == (struct lfs_mlist*)file) { - *p = (*p)->next; - break; - } - } + lfs_mlist_remove(lfs, (struct lfs_mlist*)file); // clean up memory if (!file->cfg->buffer) { lfs_free(file->cache.buffer); } - file->flags &= ~LFS_F_OPENED; - LFS_TRACE("lfs_file_close -> %d", err); return err; } -static int lfs_file_relocate(lfs_t *lfs, lfs_file_t *file) { - LFS_ASSERT(file->flags & LFS_F_OPENED); +#ifndef LFS_READONLY +static int lfs_file_relocate(lfs_t *lfs, lfs_file_t *file) { while (true) { // just relocate what exists into new block lfs_block_t nblock; @@ -2573,13 +3261,15 @@ static int lfs_file_relocate(lfs_t *lfs, lfs_file_t *file) { return 0; relocate: - LFS_DEBUG("Bad block at %"PRIx32, nblock); + LFS_DEBUG("Bad block at 0x%"PRIx32, nblock); // just clear cache and try a new block lfs_cache_drop(lfs, &lfs->pcache); } } +#endif +#ifndef LFS_READONLY static int lfs_file_outline(lfs_t *lfs, lfs_file_t *file) { file->off = file->pos; lfs_alloc_ack(lfs); @@ -2591,10 +3281,9 @@ static int lfs_file_outline(lfs_t *lfs, lfs_file_t *file) { file->flags &= ~LFS_F_INLINE; return 0; } +#endif static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file) { - LFS_ASSERT(file->flags & LFS_F_OPENED); - if (file->flags & LFS_F_READING) { if (!(file->flags & LFS_F_INLINE)) { lfs_cache_drop(lfs, &file->cache); @@ -2602,6 +3291,7 @@ static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file) { file->flags &= ~LFS_F_READING; } +#ifndef LFS_READONLY if (file->flags & LFS_F_WRITING) { lfs_off_t pos = file->pos; @@ -2610,7 +3300,7 @@ static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file) { lfs_file_t orig = { .ctz.head = file->ctz.head, .ctz.size = file->ctz.size, - .flags = LFS_O_RDONLY | LFS_F_OPENED, + .flags = LFS_O_RDONLY, .pos = file->pos, .cache = lfs->rcache, }; @@ -2620,12 +3310,12 @@ static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file) { // copy over a byte at a time, leave it up to caching // to make this efficient uint8_t data; - lfs_ssize_t res = lfs_file_read(lfs, &orig, &data, 1); + lfs_ssize_t res = lfs_file_flushedread(lfs, &orig, &data, 1); if (res < 0) { return res; } - res = lfs_file_write(lfs, file, &data, 1); + res = lfs_file_flushedwrite(lfs, file, &data, 1); if (res < 0) { return res; } @@ -2650,7 +3340,7 @@ static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file) { break; relocate: - LFS_DEBUG("Bad block at %"PRIx32, file->block); + LFS_DEBUG("Bad block at 0x%"PRIx32, file->block); err = lfs_file_relocate(lfs, file); if (err) { return err; @@ -2668,98 +3358,71 @@ static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file) { file->pos = pos; } +#endif return 0; } -int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) { - LFS_TRACE("lfs_file_sync(%p, %p)", (void*)lfs, (void*)file); - LFS_ASSERT(file->flags & LFS_F_OPENED); +#ifndef LFS_READONLY +static int lfs_file_rawsync(lfs_t *lfs, lfs_file_t *file) { + if (file->flags & LFS_F_ERRED) { + // it's not safe to do anything if our file errored + return 0; + } - while (true) { - int err = lfs_file_flush(lfs, file); - if (err) { - file->flags |= LFS_F_ERRED; - LFS_TRACE("lfs_file_sync -> %d", err); - return err; - } + int err = lfs_file_flush(lfs, file); + if (err) { + file->flags |= LFS_F_ERRED; + return err; + } - if ((file->flags & LFS_F_DIRTY) && - !(file->flags & LFS_F_ERRED) && - !lfs_pair_isnull(file->m.pair)) { - // update dir entry - uint16_t type; - const void *buffer; - lfs_size_t size; - struct lfs_ctz ctz; - if (file->flags & LFS_F_INLINE) { - // inline the whole file - type = LFS_TYPE_INLINESTRUCT; - buffer = file->cache.buffer; - size = file->ctz.size; - } else { - // update the ctz reference - type = LFS_TYPE_CTZSTRUCT; - // copy ctz so alloc will work during a relocate - ctz = file->ctz; - lfs_ctz_tole32(&ctz); - buffer = &ctz; - size = sizeof(ctz); - } - - // commit file data and attributes - err = lfs_dir_commit(lfs, &file->m, LFS_MKATTRS( - {LFS_MKTAG(type, file->id, size), buffer}, - {LFS_MKTAG(LFS_FROM_USERATTRS, file->id, - file->cfg->attr_count), file->cfg->attrs})); - if (err) { - if (err == LFS_ERR_NOSPC && (file->flags & LFS_F_INLINE)) { - goto relocate; - } - file->flags |= LFS_F_ERRED; - LFS_TRACE("lfs_file_sync -> %d", err); - return err; - } - file->flags &= ~LFS_F_DIRTY; + if ((file->flags & LFS_F_DIRTY) && + !lfs_pair_isnull(file->m.pair)) { + // update dir entry + uint16_t type; + const void *buffer; + lfs_size_t size; + struct lfs_ctz ctz; + if (file->flags & LFS_F_INLINE) { + // inline the whole file + type = LFS_TYPE_INLINESTRUCT; + buffer = file->cache.buffer; + size = file->ctz.size; + } else { + // update the ctz reference + type = LFS_TYPE_CTZSTRUCT; + // copy ctz so alloc will work during a relocate + ctz = file->ctz; + lfs_ctz_tole32(&ctz); + buffer = &ctz; + size = sizeof(ctz); } - LFS_TRACE("lfs_file_sync -> %d", 0); - return 0; - -relocate: - // inline file doesn't fit anymore - err = lfs_file_outline(lfs, file); + // commit file data and attributes + err = lfs_dir_commit(lfs, &file->m, LFS_MKATTRS( + {LFS_MKTAG(type, file->id, size), buffer}, + {LFS_MKTAG(LFS_FROM_USERATTRS, file->id, + file->cfg->attr_count), file->cfg->attrs})); if (err) { file->flags |= LFS_F_ERRED; - LFS_TRACE("lfs_file_sync -> %d", err); return err; } + + file->flags &= ~LFS_F_DIRTY; } + + return 0; } +#endif -lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, +static lfs_ssize_t lfs_file_flushedread(lfs_t *lfs, lfs_file_t *file, void *buffer, lfs_size_t size) { - LFS_TRACE("lfs_file_read(%p, %p, %p, %"PRIu32")", - (void*)lfs, (void*)file, buffer, size); - LFS_ASSERT(file->flags & LFS_F_OPENED); - LFS_ASSERT((file->flags & 3) != LFS_O_WRONLY); - uint8_t *data = buffer; lfs_size_t nsize = size; - if (file->flags & LFS_F_WRITING) { - // flush out any writes - int err = lfs_file_flush(lfs, file); - if (err) { - LFS_TRACE("lfs_file_read -> %d", err); - return err; - } - } - if (file->pos >= file->ctz.size) { // eof if past end - LFS_TRACE("lfs_file_read -> %d", 0); return 0; } @@ -2775,7 +3438,6 @@ lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, file->ctz.head, file->ctz.size, file->pos, &file->block, &file->off); if (err) { - LFS_TRACE("lfs_file_read -> %d", err); return err; } } else { @@ -2795,7 +3457,6 @@ lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0), file->off, data, diff); if (err) { - LFS_TRACE("lfs_file_read -> %d", err); return err; } } else { @@ -2803,7 +3464,6 @@ lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, NULL, &file->cache, lfs->cfg->block_size, file->block, file->off, data, diff); if (err) { - LFS_TRACE("lfs_file_read -> %d", err); return err; } } @@ -2814,62 +3474,43 @@ lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, nsize -= diff; } - LFS_TRACE("lfs_file_read -> %"PRId32, size); return size; } -lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, - const void *buffer, lfs_size_t size) { - LFS_TRACE("lfs_file_write(%p, %p, %p, %"PRIu32")", - (void*)lfs, (void*)file, buffer, size); - LFS_ASSERT(file->flags & LFS_F_OPENED); - LFS_ASSERT((file->flags & 3) != LFS_O_RDONLY); - - const uint8_t *data = buffer; - lfs_size_t nsize = size; +static lfs_ssize_t lfs_file_rawread(lfs_t *lfs, lfs_file_t *file, + void *buffer, lfs_size_t size) { + LFS_ASSERT((file->flags & LFS_O_RDONLY) == LFS_O_RDONLY); - if (file->flags & LFS_F_READING) { - // drop any reads +#ifndef LFS_READONLY + if (file->flags & LFS_F_WRITING) { + // flush out any writes int err = lfs_file_flush(lfs, file); if (err) { - LFS_TRACE("lfs_file_write -> %d", err); return err; } } +#endif - if ((file->flags & LFS_O_APPEND) && file->pos < file->ctz.size) { - file->pos = file->ctz.size; - } - - if (file->pos + size > lfs->file_max) { - // Larger than file limit? - LFS_TRACE("lfs_file_write -> %d", LFS_ERR_FBIG); - return LFS_ERR_FBIG; - } + return lfs_file_flushedread(lfs, file, buffer, size); +} - if (!(file->flags & LFS_F_WRITING) && file->pos > file->ctz.size) { - // fill with zeros - lfs_off_t pos = file->pos; - file->pos = file->ctz.size; - while (file->pos < pos) { - lfs_ssize_t res = lfs_file_write(lfs, file, &(uint8_t){0}, 1); - if (res < 0) { - LFS_TRACE("lfs_file_write -> %"PRId32, res); - return res; - } - } - } +#ifndef LFS_READONLY +static lfs_ssize_t lfs_file_flushedwrite(lfs_t *lfs, lfs_file_t *file, + const void *buffer, lfs_size_t size) { + const uint8_t *data = buffer; + lfs_size_t nsize = size; if ((file->flags & LFS_F_INLINE) && lfs_max(file->pos+nsize, file->ctz.size) > lfs_min(0x3fe, lfs_min( - lfs->cfg->cache_size, lfs->cfg->block_size/8))) { + lfs->cfg->cache_size, + (lfs->cfg->metadata_max ? + lfs->cfg->metadata_max : lfs->cfg->block_size) / 8))) { // inline file doesn't fit anymore int err = lfs_file_outline(lfs, file); if (err) { file->flags |= LFS_F_ERRED; - LFS_TRACE("lfs_file_write -> %d", err); return err; } } @@ -2883,10 +3524,9 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, // find out which block we're extending from int err = lfs_ctz_find(lfs, NULL, &file->cache, file->ctz.head, file->ctz.size, - file->pos-1, &file->block, &file->off); + file->pos-1, &file->block, &(lfs_off_t){0}); if (err) { file->flags |= LFS_F_ERRED; - LFS_TRACE("lfs_file_write -> %d", err); return err; } @@ -2901,7 +3541,6 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, &file->block, &file->off); if (err) { file->flags |= LFS_F_ERRED; - LFS_TRACE("lfs_file_write -> %d", err); return err; } } else { @@ -2922,7 +3561,6 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, goto relocate; } file->flags |= LFS_F_ERRED; - LFS_TRACE("lfs_file_write -> %d", err); return err; } @@ -2931,7 +3569,6 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, err = lfs_file_relocate(lfs, file); if (err) { file->flags |= LFS_F_ERRED; - LFS_TRACE("lfs_file_write -> %d", err); return err; } } @@ -2944,239 +3581,324 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, lfs_alloc_ack(lfs); } - file->flags &= ~LFS_F_ERRED; - LFS_TRACE("lfs_file_write -> %"PRId32, size); return size; } -lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, - lfs_soff_t off, int whence) { - LFS_TRACE("lfs_file_seek(%p, %p, %"PRId32", %d)", - (void*)lfs, (void*)file, off, whence); - LFS_ASSERT(file->flags & LFS_F_OPENED); +static lfs_ssize_t lfs_file_rawwrite(lfs_t *lfs, lfs_file_t *file, + const void *buffer, lfs_size_t size) { + LFS_ASSERT((file->flags & LFS_O_WRONLY) == LFS_O_WRONLY); - // write out everything beforehand, may be noop if rdonly - int err = lfs_file_flush(lfs, file); - if (err) { - LFS_TRACE("lfs_file_seek -> %d", err); - return err; + if (file->flags & LFS_F_READING) { + // drop any reads + int err = lfs_file_flush(lfs, file); + if (err) { + return err; + } + } + + if ((file->flags & LFS_O_APPEND) && file->pos < file->ctz.size) { + file->pos = file->ctz.size; + } + + if (file->pos + size > lfs->file_max) { + // Larger than file limit? + return LFS_ERR_FBIG; + } + + if (!(file->flags & LFS_F_WRITING) && file->pos > file->ctz.size) { + // fill with zeros + lfs_off_t pos = file->pos; + file->pos = file->ctz.size; + + while (file->pos < pos) { + lfs_ssize_t res = lfs_file_flushedwrite(lfs, file, &(uint8_t){0}, 1); + if (res < 0) { + return res; + } + } } + lfs_ssize_t nsize = lfs_file_flushedwrite(lfs, file, buffer, size); + if (nsize < 0) { + return nsize; + } + + file->flags &= ~LFS_F_ERRED; + return nsize; +} +#endif + +static lfs_soff_t lfs_file_rawseek(lfs_t *lfs, lfs_file_t *file, + lfs_soff_t off, int whence) { // find new pos lfs_off_t npos = file->pos; if (whence == LFS_SEEK_SET) { npos = off; } else if (whence == LFS_SEEK_CUR) { - npos = file->pos + off; + if ((lfs_soff_t)file->pos + off < 0) { + return LFS_ERR_INVAL; + } else { + npos = file->pos + off; + } } else if (whence == LFS_SEEK_END) { - npos = file->ctz.size + off; + lfs_soff_t res = lfs_file_rawsize(lfs, file) + off; + if (res < 0) { + return LFS_ERR_INVAL; + } else { + npos = res; + } } if (npos > lfs->file_max) { // file position out of range - LFS_TRACE("lfs_file_seek -> %d", LFS_ERR_INVAL); return LFS_ERR_INVAL; } + if (file->pos == npos) { + // noop - position has not changed + return npos; + } + + // if we're only reading and our new offset is still in the file's cache + // we can avoid flushing and needing to reread the data + if ( +#ifndef LFS_READONLY + !(file->flags & LFS_F_WRITING) +#else + true +#endif + ) { + int oindex = lfs_ctz_index(lfs, &(lfs_off_t){file->pos}); + lfs_off_t noff = npos; + int nindex = lfs_ctz_index(lfs, &noff); + if (oindex == nindex + && noff >= file->cache.off + && noff < file->cache.off + file->cache.size) { + file->pos = npos; + file->off = noff; + return npos; + } + } + + // write out everything beforehand, may be noop if rdonly + int err = lfs_file_flush(lfs, file); + if (err) { + return err; + } + // update pos file->pos = npos; - LFS_TRACE("lfs_file_seek -> %"PRId32, npos); return npos; } -int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size) { - LFS_TRACE("lfs_file_truncate(%p, %p, %"PRIu32")", - (void*)lfs, (void*)file, size); - LFS_ASSERT(file->flags & LFS_F_OPENED); - LFS_ASSERT((file->flags & 3) != LFS_O_RDONLY); +#ifndef LFS_READONLY +static int lfs_file_rawtruncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size) { + LFS_ASSERT((file->flags & LFS_O_WRONLY) == LFS_O_WRONLY); if (size > LFS_FILE_MAX) { - LFS_TRACE("lfs_file_truncate -> %d", LFS_ERR_INVAL); return LFS_ERR_INVAL; } lfs_off_t pos = file->pos; - lfs_off_t oldsize = lfs_file_size(lfs, file); + lfs_off_t oldsize = lfs_file_rawsize(lfs, file); if (size < oldsize) { - // need to flush since directly changing metadata - int err = lfs_file_flush(lfs, file); - if (err) { - LFS_TRACE("lfs_file_truncate -> %d", err); - return err; - } - - // lookup new head in ctz skip list - err = lfs_ctz_find(lfs, NULL, &file->cache, - file->ctz.head, file->ctz.size, - size, &file->block, &file->off); - if (err) { - LFS_TRACE("lfs_file_truncate -> %d", err); - return err; - } + // revert to inline file? + if (size <= lfs_min(0x3fe, lfs_min( + lfs->cfg->cache_size, + (lfs->cfg->metadata_max ? + lfs->cfg->metadata_max : lfs->cfg->block_size) / 8))) { + // flush+seek to head + lfs_soff_t res = lfs_file_rawseek(lfs, file, 0, LFS_SEEK_SET); + if (res < 0) { + return (int)res; + } - file->ctz.head = file->block; - file->ctz.size = size; - file->flags |= LFS_F_DIRTY | LFS_F_READING; - } else if (size > oldsize) { - // flush+seek if not already at end - if (file->pos != oldsize) { - lfs_soff_t res = lfs_file_seek(lfs, file, 0, LFS_SEEK_END); + // read our data into rcache temporarily + lfs_cache_drop(lfs, &lfs->rcache); + res = lfs_file_flushedread(lfs, file, + lfs->rcache.buffer, size); if (res < 0) { - LFS_TRACE("lfs_file_truncate -> %"PRId32, res); return (int)res; } + + file->ctz.head = LFS_BLOCK_INLINE; + file->ctz.size = size; + file->flags |= LFS_F_DIRTY | LFS_F_READING | LFS_F_INLINE; + file->cache.block = file->ctz.head; + file->cache.off = 0; + file->cache.size = lfs->cfg->cache_size; + memcpy(file->cache.buffer, lfs->rcache.buffer, size); + + } else { + // need to flush since directly changing metadata + int err = lfs_file_flush(lfs, file); + if (err) { + return err; + } + + // lookup new head in ctz skip list + err = lfs_ctz_find(lfs, NULL, &file->cache, + file->ctz.head, file->ctz.size, + size-1, &file->block, &(lfs_off_t){0}); + if (err) { + return err; + } + + // need to set pos/block/off consistently so seeking back to + // the old position does not get confused + file->pos = size; + file->ctz.head = file->block; + file->ctz.size = size; + file->flags |= LFS_F_DIRTY | LFS_F_READING; + } + } else if (size > oldsize) { + // flush+seek if not already at end + lfs_soff_t res = lfs_file_rawseek(lfs, file, 0, LFS_SEEK_END); + if (res < 0) { + return (int)res; } // fill with zeros while (file->pos < size) { - lfs_ssize_t res = lfs_file_write(lfs, file, &(uint8_t){0}, 1); + res = lfs_file_rawwrite(lfs, file, &(uint8_t){0}, 1); if (res < 0) { - LFS_TRACE("lfs_file_truncate -> %"PRId32, res); return (int)res; } } } // restore pos - lfs_soff_t res = lfs_file_seek(lfs, file, pos, LFS_SEEK_SET); + lfs_soff_t res = lfs_file_rawseek(lfs, file, pos, LFS_SEEK_SET); if (res < 0) { - LFS_TRACE("lfs_file_truncate -> %"PRId32, res); return (int)res; } - LFS_TRACE("lfs_file_truncate -> %d", 0); return 0; } +#endif -lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file) { - LFS_TRACE("lfs_file_tell(%p, %p)", (void*)lfs, (void*)file); - LFS_ASSERT(file->flags & LFS_F_OPENED); +static lfs_soff_t lfs_file_rawtell(lfs_t *lfs, lfs_file_t *file) { (void)lfs; - LFS_TRACE("lfs_file_tell -> %"PRId32, file->pos); return file->pos; } -int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file) { - LFS_TRACE("lfs_file_rewind(%p, %p)", (void*)lfs, (void*)file); - lfs_soff_t res = lfs_file_seek(lfs, file, 0, LFS_SEEK_SET); +static int lfs_file_rawrewind(lfs_t *lfs, lfs_file_t *file) { + lfs_soff_t res = lfs_file_rawseek(lfs, file, 0, LFS_SEEK_SET); if (res < 0) { - LFS_TRACE("lfs_file_rewind -> %"PRId32, res); return (int)res; } - LFS_TRACE("lfs_file_rewind -> %d", 0); return 0; } -lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file) { - LFS_TRACE("lfs_file_size(%p, %p)", (void*)lfs, (void*)file); - LFS_ASSERT(file->flags & LFS_F_OPENED); +static lfs_soff_t lfs_file_rawsize(lfs_t *lfs, lfs_file_t *file) { (void)lfs; + +#ifndef LFS_READONLY if (file->flags & LFS_F_WRITING) { - LFS_TRACE("lfs_file_size -> %"PRId32, - lfs_max(file->pos, file->ctz.size)); return lfs_max(file->pos, file->ctz.size); - } else { - LFS_TRACE("lfs_file_size -> %"PRId32, file->ctz.size); - return file->ctz.size; } +#endif + + return file->ctz.size; } /// General fs operations /// -int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info) { - LFS_TRACE("lfs_stat(%p, \"%s\", %p)", (void*)lfs, path, (void*)info); +static int lfs_rawstat(lfs_t *lfs, const char *path, struct lfs_info *info) { lfs_mdir_t cwd; lfs_stag_t tag = lfs_dir_find(lfs, &cwd, &path, NULL); if (tag < 0) { - LFS_TRACE("lfs_stat -> %"PRId32, tag); return (int)tag; } - int err = lfs_dir_getinfo(lfs, &cwd, lfs_tag_id(tag), info); - LFS_TRACE("lfs_stat -> %d", err); - return err; + return lfs_dir_getinfo(lfs, &cwd, lfs_tag_id(tag), info); } -int lfs_remove(lfs_t *lfs, const char *path) { - LFS_TRACE("lfs_remove(%p, \"%s\")", (void*)lfs, path); +#ifndef LFS_READONLY +static int lfs_rawremove(lfs_t *lfs, const char *path) { // deorphan if we haven't yet, needed at most once after poweron int err = lfs_fs_forceconsistency(lfs); if (err) { - LFS_TRACE("lfs_remove -> %d", err); return err; } lfs_mdir_t cwd; lfs_stag_t tag = lfs_dir_find(lfs, &cwd, &path, NULL); if (tag < 0 || lfs_tag_id(tag) == 0x3ff) { - LFS_TRACE("lfs_remove -> %"PRId32, (tag < 0) ? tag : LFS_ERR_INVAL); return (tag < 0) ? (int)tag : LFS_ERR_INVAL; } - lfs_mdir_t dir; + struct lfs_mlist dir; + dir.next = lfs->mlist; if (lfs_tag_type3(tag) == LFS_TYPE_DIR) { // must be empty before removal lfs_block_t pair[2]; lfs_stag_t res = lfs_dir_get(lfs, &cwd, LFS_MKTAG(0x700, 0x3ff, 0), LFS_MKTAG(LFS_TYPE_STRUCT, lfs_tag_id(tag), 8), pair); if (res < 0) { - LFS_TRACE("lfs_remove -> %"PRId32, res); return (int)res; } lfs_pair_fromle32(pair); - err = lfs_dir_fetch(lfs, &dir, pair); + err = lfs_dir_fetch(lfs, &dir.m, pair); if (err) { - LFS_TRACE("lfs_remove -> %d", err); return err; } - if (dir.count > 0 || dir.split) { - LFS_TRACE("lfs_remove -> %d", LFS_ERR_NOTEMPTY); + if (dir.m.count > 0 || dir.m.split) { return LFS_ERR_NOTEMPTY; } // mark fs as orphaned - lfs_fs_preporphans(lfs, +1); + err = lfs_fs_preporphans(lfs, +1); + if (err) { + return err; + } + + // I know it's crazy but yes, dir can be changed by our parent's + // commit (if predecessor is child) + dir.type = 0; + dir.id = 0; + lfs->mlist = &dir; } // delete the entry err = lfs_dir_commit(lfs, &cwd, LFS_MKATTRS( {LFS_MKTAG(LFS_TYPE_DELETE, lfs_tag_id(tag), 0), NULL})); if (err) { - LFS_TRACE("lfs_remove -> %d", err); + lfs->mlist = dir.next; return err; } + lfs->mlist = dir.next; if (lfs_tag_type3(tag) == LFS_TYPE_DIR) { // fix orphan - lfs_fs_preporphans(lfs, -1); + err = lfs_fs_preporphans(lfs, -1); + if (err) { + return err; + } - err = lfs_fs_pred(lfs, dir.pair, &cwd); + err = lfs_fs_pred(lfs, dir.m.pair, &cwd); if (err) { - LFS_TRACE("lfs_remove -> %d", err); return err; } - err = lfs_dir_drop(lfs, &cwd, &dir); + err = lfs_dir_drop(lfs, &cwd, &dir.m); if (err) { - LFS_TRACE("lfs_remove -> %d", err); return err; } } - LFS_TRACE("lfs_remove -> %d", 0); return 0; } +#endif -int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { - LFS_TRACE("lfs_rename(%p, \"%s\", \"%s\")", (void*)lfs, oldpath, newpath); - +#ifndef LFS_READONLY +static int lfs_rawrename(lfs_t *lfs, const char *oldpath, const char *newpath) { // deorphan if we haven't yet, needed at most once after poweron int err = lfs_fs_forceconsistency(lfs); if (err) { - LFS_TRACE("lfs_rename -> %d", err); return err; } @@ -3184,7 +3906,6 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { lfs_mdir_t oldcwd; lfs_stag_t oldtag = lfs_dir_find(lfs, &oldcwd, &oldpath, NULL); if (oldtag < 0 || lfs_tag_id(oldtag) == 0x3ff) { - LFS_TRACE("lfs_rename -> %"PRId32, (oldtag < 0) ? oldtag : LFS_ERR_INVAL); return (oldtag < 0) ? (int)oldtag : LFS_ERR_INVAL; } @@ -3194,113 +3915,126 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { lfs_stag_t prevtag = lfs_dir_find(lfs, &newcwd, &newpath, &newid); if ((prevtag < 0 || lfs_tag_id(prevtag) == 0x3ff) && !(prevtag == LFS_ERR_NOENT && newid != 0x3ff)) { - LFS_TRACE("lfs_rename -> %"PRId32, (prevtag < 0) ? prevtag : LFS_ERR_INVAL); return (prevtag < 0) ? (int)prevtag : LFS_ERR_INVAL; } - lfs_mdir_t prevdir; + // if we're in the same pair there's a few special cases... + bool samepair = (lfs_pair_cmp(oldcwd.pair, newcwd.pair) == 0); + uint16_t newoldid = lfs_tag_id(oldtag); + + struct lfs_mlist prevdir; + prevdir.next = lfs->mlist; if (prevtag == LFS_ERR_NOENT) { // check that name fits lfs_size_t nlen = strlen(newpath); if (nlen > lfs->name_max) { - LFS_TRACE("lfs_rename -> %d", LFS_ERR_NAMETOOLONG); return LFS_ERR_NAMETOOLONG; } + + // there is a small chance we are being renamed in the same + // directory/ to an id less than our old id, the global update + // to handle this is a bit messy + if (samepair && newid <= newoldid) { + newoldid += 1; + } } else if (lfs_tag_type3(prevtag) != lfs_tag_type3(oldtag)) { - LFS_TRACE("lfs_rename -> %d", LFS_ERR_ISDIR); return LFS_ERR_ISDIR; + } else if (samepair && newid == newoldid) { + // we're renaming to ourselves?? + return 0; } else if (lfs_tag_type3(prevtag) == LFS_TYPE_DIR) { // must be empty before removal lfs_block_t prevpair[2]; lfs_stag_t res = lfs_dir_get(lfs, &newcwd, LFS_MKTAG(0x700, 0x3ff, 0), LFS_MKTAG(LFS_TYPE_STRUCT, newid, 8), prevpair); if (res < 0) { - LFS_TRACE("lfs_rename -> %"PRId32, res); return (int)res; } lfs_pair_fromle32(prevpair); // must be empty before removal - err = lfs_dir_fetch(lfs, &prevdir, prevpair); + err = lfs_dir_fetch(lfs, &prevdir.m, prevpair); if (err) { - LFS_TRACE("lfs_rename -> %d", err); return err; } - if (prevdir.count > 0 || prevdir.split) { - LFS_TRACE("lfs_rename -> %d", LFS_ERR_NOTEMPTY); + if (prevdir.m.count > 0 || prevdir.m.split) { return LFS_ERR_NOTEMPTY; } // mark fs as orphaned - lfs_fs_preporphans(lfs, +1); - } + err = lfs_fs_preporphans(lfs, +1); + if (err) { + return err; + } - // create move to fix later - uint16_t newoldtagid = lfs_tag_id(oldtag); - if (lfs_pair_cmp(oldcwd.pair, newcwd.pair) == 0 && - prevtag == LFS_ERR_NOENT && newid <= newoldtagid) { - // there is a small chance we are being renamed in the same directory - // to an id less than our old id, the global update to handle this - // is a bit messy - newoldtagid += 1; + // I know it's crazy but yes, dir can be changed by our parent's + // commit (if predecessor is child) + prevdir.type = 0; + prevdir.id = 0; + lfs->mlist = &prevdir; } - lfs_fs_prepmove(lfs, newoldtagid, oldcwd.pair); + if (!samepair) { + lfs_fs_prepmove(lfs, newoldid, oldcwd.pair); + } // move over all attributes err = lfs_dir_commit(lfs, &newcwd, LFS_MKATTRS( - {prevtag != LFS_ERR_NOENT - ? LFS_MKTAG(LFS_TYPE_DELETE, newid, 0) - : LFS_MKTAG(LFS_FROM_NOOP, 0, 0), NULL}, + {LFS_MKTAG_IF(prevtag != LFS_ERR_NOENT, + LFS_TYPE_DELETE, newid, 0), NULL}, {LFS_MKTAG(LFS_TYPE_CREATE, newid, 0), NULL}, - {LFS_MKTAG(lfs_tag_type3(oldtag), newid, strlen(newpath)), - newpath}, - {LFS_MKTAG(LFS_FROM_MOVE, newid, lfs_tag_id(oldtag)), &oldcwd})); + {LFS_MKTAG(lfs_tag_type3(oldtag), newid, strlen(newpath)), newpath}, + {LFS_MKTAG(LFS_FROM_MOVE, newid, lfs_tag_id(oldtag)), &oldcwd}, + {LFS_MKTAG_IF(samepair, + LFS_TYPE_DELETE, newoldid, 0), NULL})); if (err) { - LFS_TRACE("lfs_rename -> %d", err); + lfs->mlist = prevdir.next; return err; } // let commit clean up after move (if we're different! otherwise move // logic already fixed it for us) - if (lfs_pair_cmp(oldcwd.pair, newcwd.pair) != 0) { - err = lfs_dir_commit(lfs, &oldcwd, NULL, 0); + if (!samepair && lfs_gstate_hasmove(&lfs->gstate)) { + // prep gstate and delete move id + lfs_fs_prepmove(lfs, 0x3ff, NULL); + err = lfs_dir_commit(lfs, &oldcwd, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_DELETE, lfs_tag_id(oldtag), 0), NULL})); if (err) { - LFS_TRACE("lfs_rename -> %d", err); + lfs->mlist = prevdir.next; return err; } } - if (prevtag != LFS_ERR_NOENT && lfs_tag_type3(prevtag) == LFS_TYPE_DIR) { + lfs->mlist = prevdir.next; + if (prevtag != LFS_ERR_NOENT + && lfs_tag_type3(prevtag) == LFS_TYPE_DIR) { // fix orphan - lfs_fs_preporphans(lfs, -1); + err = lfs_fs_preporphans(lfs, -1); + if (err) { + return err; + } - err = lfs_fs_pred(lfs, prevdir.pair, &newcwd); + err = lfs_fs_pred(lfs, prevdir.m.pair, &newcwd); if (err) { - LFS_TRACE("lfs_rename -> %d", err); return err; } - err = lfs_dir_drop(lfs, &newcwd, &prevdir); + err = lfs_dir_drop(lfs, &newcwd, &prevdir.m); if (err) { - LFS_TRACE("lfs_rename -> %d", err); return err; } } - LFS_TRACE("lfs_rename -> %d", 0); return 0; } +#endif -lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path, +static lfs_ssize_t lfs_rawgetattr(lfs_t *lfs, const char *path, uint8_t type, void *buffer, lfs_size_t size) { - LFS_TRACE("lfs_getattr(%p, \"%s\", %"PRIu8", %p, %"PRIu32")", - (void*)lfs, path, type, buffer, size); lfs_mdir_t cwd; lfs_stag_t tag = lfs_dir_find(lfs, &cwd, &path, NULL); if (tag < 0) { - LFS_TRACE("lfs_getattr -> %"PRId32, tag); return tag; } @@ -3310,7 +4044,6 @@ lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path, id = 0; int err = lfs_dir_fetch(lfs, &cwd, lfs->root); if (err) { - LFS_TRACE("lfs_getattr -> %d", err); return err; } } @@ -3321,19 +4054,16 @@ lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path, buffer); if (tag < 0) { if (tag == LFS_ERR_NOENT) { - LFS_TRACE("lfs_getattr -> %d", LFS_ERR_NOATTR); return LFS_ERR_NOATTR; } - LFS_TRACE("lfs_getattr -> %"PRId32, tag); return tag; } - size = lfs_tag_size(tag); - LFS_TRACE("lfs_getattr -> %"PRId32, size); - return size; + return lfs_tag_size(tag); } +#ifndef LFS_READONLY static int lfs_commitattr(lfs_t *lfs, const char *path, uint8_t type, const void *buffer, lfs_size_t size) { lfs_mdir_t cwd; @@ -3355,34 +4085,47 @@ static int lfs_commitattr(lfs_t *lfs, const char *path, return lfs_dir_commit(lfs, &cwd, LFS_MKATTRS( {LFS_MKTAG(LFS_TYPE_USERATTR + type, id, size), buffer})); } +#endif -int lfs_setattr(lfs_t *lfs, const char *path, +#ifndef LFS_READONLY +static int lfs_rawsetattr(lfs_t *lfs, const char *path, uint8_t type, const void *buffer, lfs_size_t size) { - LFS_TRACE("lfs_setattr(%p, \"%s\", %"PRIu8", %p, %"PRIu32")", - (void*)lfs, path, type, buffer, size); if (size > lfs->attr_max) { - LFS_TRACE("lfs_setattr -> %d", LFS_ERR_NOSPC); return LFS_ERR_NOSPC; } - int err = lfs_commitattr(lfs, path, type, buffer, size); - LFS_TRACE("lfs_setattr -> %d", err); - return err; + return lfs_commitattr(lfs, path, type, buffer, size); } +#endif -int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type) { - LFS_TRACE("lfs_removeattr(%p, \"%s\", %"PRIu8")", (void*)lfs, path, type); - int err = lfs_commitattr(lfs, path, type, NULL, 0x3ff); - LFS_TRACE("lfs_removeattr -> %d", err); - return err; +#ifndef LFS_READONLY +static int lfs_rawremoveattr(lfs_t *lfs, const char *path, uint8_t type) { + return lfs_commitattr(lfs, path, type, NULL, 0x3ff); } +#endif /// Filesystem operations /// static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { lfs->cfg = cfg; + lfs->block_count = cfg->block_count; // May be 0 int err = 0; +#ifdef LFS_MULTIVERSION + // this driver only supports minor version < current minor version + LFS_ASSERT(!lfs->cfg->disk_version || ( + (0xffff & (lfs->cfg->disk_version >> 16)) + == LFS_DISK_VERSION_MAJOR + && (0xffff & (lfs->cfg->disk_version >> 0)) + <= LFS_DISK_VERSION_MINOR)); +#endif + + // check that bool is a truthy-preserving type + // + // note the most common reason for this failure is a before-c99 compiler, + // which littlefs currently does not support + LFS_ASSERT((bool)0x80000000); + // validate that the lfs-cfg sizes were initiated properly before // performing any arithmetic logics with them LFS_ASSERT(lfs->cfg->read_size != 0); @@ -3395,8 +4138,11 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { LFS_ASSERT(lfs->cfg->cache_size % lfs->cfg->prog_size == 0); LFS_ASSERT(lfs->cfg->block_size % lfs->cfg->cache_size == 0); - // check that the block size is large enough to fit ctz pointers - LFS_ASSERT(4*lfs_npw2(LFS_BLOCK_NULL / (lfs->cfg->block_size-2*4)) + // check that the block size is large enough to fit all ctz pointers + LFS_ASSERT(lfs->cfg->block_size >= 128); + // this is the exact calculation for all ctz pointers, if this fails + // and the simpler assert above does not, math must be broken + LFS_ASSERT(4*lfs_npw2(0xffffffff / (lfs->cfg->block_size-2*4)) <= lfs->cfg->block_size); // block_cycles = 0 is no longer supported. @@ -3467,14 +4213,16 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { lfs->attr_max = LFS_ATTR_MAX; } + LFS_ASSERT(lfs->cfg->metadata_max <= lfs->cfg->block_size); + // setup default state lfs->root[0] = LFS_BLOCK_NULL; lfs->root[1] = LFS_BLOCK_NULL; lfs->mlist = NULL; lfs->seed = 0; - lfs->gstate = (struct lfs_gstate){0}; - lfs->gpending = (struct lfs_gstate){0}; - lfs->gdelta = (struct lfs_gstate){0}; + lfs->gdisk = (lfs_gstate_t){0}; + lfs->gstate = (lfs_gstate_t){0}; + lfs->gdelta = (lfs_gstate_t){0}; #ifdef LFS_MIGRATE lfs->lfs1 = NULL; #endif @@ -3503,36 +4251,24 @@ static int lfs_deinit(lfs_t *lfs) { return 0; } -int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) { - LFS_TRACE("lfs_format(%p, %p {.context=%p, " - ".read=%p, .prog=%p, .erase=%p, .sync=%p, " - ".read_size=%"PRIu32", .prog_size=%"PRIu32", " - ".block_size=%"PRIu32", .block_count=%"PRIu32", " - ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " - ".lookahead_size=%"PRIu32", .read_buffer=%p, " - ".prog_buffer=%p, .lookahead_buffer=%p, " - ".name_max=%"PRIu32", .file_max=%"PRIu32", " - ".attr_max=%"PRIu32"})", - (void*)lfs, (void*)cfg, cfg->context, - (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, - (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, - cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, - cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, - cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, - cfg->name_max, cfg->file_max, cfg->attr_max); + + +#ifndef LFS_READONLY +static int lfs_rawformat(lfs_t *lfs, const struct lfs_config *cfg) { int err = 0; { err = lfs_init(lfs, cfg); if (err) { - LFS_TRACE("lfs_format -> %d", err); return err; } + LFS_ASSERT(cfg->block_count != 0); + // create free lookahead memset(lfs->free.buffer, 0, lfs->cfg->lookahead_size); lfs->free.off = 0; lfs->free.size = lfs_min(8*lfs->cfg->lookahead_size, - lfs->cfg->block_count); + lfs->block_count); lfs->free.i = 0; lfs_alloc_ack(lfs); @@ -3545,9 +4281,9 @@ int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) { // write one superblock lfs_superblock_t superblock = { - .version = LFS_DISK_VERSION, + .version = lfs_fs_disk_version(lfs), .block_size = lfs->cfg->block_size, - .block_count = lfs->cfg->block_count, + .block_count = lfs->block_count, .name_max = lfs->name_max, .file_max = lfs->file_max, .attr_max = lfs->attr_max, @@ -3563,12 +4299,6 @@ int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) { goto cleanup; } - // sanity check that fetch works - err = lfs_dir_fetch(lfs, &root, (const lfs_block_t[2]){0, 1}); - if (err) { - goto cleanup; - } - // force compaction to prevent accidentally mounting any // older version of littlefs that may live on disk root.erased = false; @@ -3576,40 +4306,47 @@ int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) { if (err) { goto cleanup; } + + // sanity check that fetch works + err = lfs_dir_fetch(lfs, &root, (const lfs_block_t[2]){0, 1}); + if (err) { + goto cleanup; + } } cleanup: lfs_deinit(lfs); - LFS_TRACE("lfs_format -> %d", err); return err; + } +#endif -int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { - LFS_TRACE("lfs_mount(%p, %p {.context=%p, " - ".read=%p, .prog=%p, .erase=%p, .sync=%p, " - ".read_size=%"PRIu32", .prog_size=%"PRIu32", " - ".block_size=%"PRIu32", .block_count=%"PRIu32", " - ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " - ".lookahead_size=%"PRIu32", .read_buffer=%p, " - ".prog_buffer=%p, .lookahead_buffer=%p, " - ".name_max=%"PRIu32", .file_max=%"PRIu32", " - ".attr_max=%"PRIu32"})", - (void*)lfs, (void*)cfg, cfg->context, - (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, - (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, - cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, - cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, - cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, - cfg->name_max, cfg->file_max, cfg->attr_max); +static int lfs_rawmount(lfs_t *lfs, const struct lfs_config *cfg) { int err = lfs_init(lfs, cfg); if (err) { - LFS_TRACE("lfs_mount -> %d", err); return err; } // scan directory blocks for superblock and any global updates lfs_mdir_t dir = {.tail = {0, 1}}; + lfs_block_t tortoise[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL}; + lfs_size_t tortoise_i = 1; + lfs_size_t tortoise_period = 1; while (!lfs_pair_isnull(dir.tail)) { + // detect cycles with Brent's algorithm + if (lfs_pair_issync(dir.tail, tortoise)) { + LFS_WARN("Cycle detected in tail list"); + err = LFS_ERR_CORRUPT; + goto cleanup; + } + if (tortoise_i == tortoise_period) { + tortoise[0] = dir.tail[0]; + tortoise[1] = dir.tail[1]; + tortoise_i = 0; + tortoise_period *= 2; + } + tortoise_i += 1; + // fetch next block in tail list lfs_stag_t tag = lfs_dir_fetchmatch(lfs, &dir, dir.tail, LFS_MKTAG(0x7ff, 0x3ff, 0), @@ -3642,14 +4379,33 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { // check version uint16_t major_version = (0xffff & (superblock.version >> 16)); uint16_t minor_version = (0xffff & (superblock.version >> 0)); - if ((major_version != LFS_DISK_VERSION_MAJOR || - minor_version > LFS_DISK_VERSION_MINOR)) { - LFS_ERROR("Invalid version %"PRIu16".%"PRIu16, - major_version, minor_version); + if (major_version != lfs_fs_disk_version_major(lfs) + || minor_version > lfs_fs_disk_version_minor(lfs)) { + LFS_ERROR("Invalid version " + "v%"PRIu16".%"PRIu16" != v%"PRIu16".%"PRIu16, + major_version, + minor_version, + lfs_fs_disk_version_major(lfs), + lfs_fs_disk_version_minor(lfs)); err = LFS_ERR_INVAL; goto cleanup; } + // found older minor version? set an in-device only bit in the + // gstate so we know we need to rewrite the superblock before + // the first write + if (minor_version < lfs_fs_disk_version_minor(lfs)) { + LFS_DEBUG("Found older minor version " + "v%"PRIu16".%"PRIu16" < v%"PRIu16".%"PRIu16, + major_version, + minor_version, + lfs_fs_disk_version_major(lfs), + lfs_fs_disk_version_minor(lfs)); + // note this bit is reserved on disk, so fetching more gstate + // will not interfere here + lfs_fs_prepsuperblock(lfs, true); + } + // check superblock configuration if (superblock.name_max) { if (superblock.name_max > lfs->name_max) { @@ -3683,59 +4439,104 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { lfs->attr_max = superblock.attr_max; } - } - // has gstate? - err = lfs_dir_getgstate(lfs, &dir, &lfs->gpending); + // this is where we get the block_count from disk if block_count=0 + if (lfs->cfg->block_count + && superblock.block_count != lfs->cfg->block_count) { + LFS_ERROR("Invalid block count (%"PRIu32" != %"PRIu32")", + superblock.block_count, lfs->cfg->block_count); + err = LFS_ERR_INVAL; + goto cleanup; + } + + lfs->block_count = superblock.block_count; + + if (superblock.block_size != lfs->cfg->block_size) { + LFS_ERROR("Invalid block size (%"PRIu32" != %"PRIu32")", + superblock.block_size, lfs->cfg->block_size); + err = LFS_ERR_INVAL; + goto cleanup; + } + } + + // has gstate? + err = lfs_dir_getgstate(lfs, &dir, &lfs->gstate); if (err) { goto cleanup; } } - // found superblock? - if (lfs_pair_isnull(lfs->root)) { - err = LFS_ERR_INVAL; - goto cleanup; - } - // update littlefs with gstate - lfs->gpending.tag += !lfs_tag_isvalid(lfs->gpending.tag); - lfs->gstate = lfs->gpending; - if (lfs_gstate_hasmove(&lfs->gstate)) { - LFS_DEBUG("Found move %"PRIx32" %"PRIx32" %"PRIx16, + if (!lfs_gstate_iszero(&lfs->gstate)) { + LFS_DEBUG("Found pending gstate 0x%08"PRIx32"%08"PRIx32"%08"PRIx32, + lfs->gstate.tag, lfs->gstate.pair[0], - lfs->gstate.pair[1], - lfs_tag_id(lfs->gstate.tag)); + lfs->gstate.pair[1]); } + lfs->gstate.tag += !lfs_tag_isvalid(lfs->gstate.tag); + lfs->gdisk = lfs->gstate; - // setup free lookahead - lfs->free.off = lfs->seed % lfs->cfg->block_size; - lfs->free.size = 0; - lfs->free.i = 0; - lfs_alloc_ack(lfs); + // setup free lookahead, to distribute allocations uniformly across + // boots, we start the allocator at a random location + lfs->free.off = lfs->seed % lfs->block_count; + lfs_alloc_drop(lfs); - LFS_TRACE("lfs_mount -> %d", 0); return 0; cleanup: - lfs_unmount(lfs); - LFS_TRACE("lfs_mount -> %d", err); + lfs_rawunmount(lfs); return err; } -int lfs_unmount(lfs_t *lfs) { - LFS_TRACE("lfs_unmount(%p)", (void*)lfs); - int err = lfs_deinit(lfs); - LFS_TRACE("lfs_unmount -> %d", err); - return err; +static int lfs_rawunmount(lfs_t *lfs) { + return lfs_deinit(lfs); } /// Filesystem filesystem operations /// -int lfs_fs_traverse(lfs_t *lfs, - int (*cb)(void *data, lfs_block_t block), void *data) { - LFS_TRACE("lfs_fs_traverse(%p, %p, %p)", - (void*)lfs, (void*)(uintptr_t)cb, data); +static int lfs_fs_rawstat(lfs_t *lfs, struct lfs_fsinfo *fsinfo) { + // if the superblock is up-to-date, we must be on the most recent + // minor version of littlefs + if (!lfs_gstate_needssuperblock(&lfs->gstate)) { + fsinfo->disk_version = lfs_fs_disk_version(lfs); + + // otherwise we need to read the minor version on disk + } else { + // fetch the superblock + lfs_mdir_t dir; + int err = lfs_dir_fetch(lfs, &dir, lfs->root); + if (err) { + return err; + } + + lfs_superblock_t superblock; + lfs_stag_t tag = lfs_dir_get(lfs, &dir, LFS_MKTAG(0x7ff, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), + &superblock); + if (tag < 0) { + return tag; + } + lfs_superblock_fromle32(&superblock); + + // read the on-disk version + fsinfo->disk_version = superblock.version; + } + + // filesystem geometry + fsinfo->block_size = lfs->cfg->block_size; + fsinfo->block_count = lfs->block_count; + + // other on-disk configuration, we cache all of these for internal use + fsinfo->name_max = lfs->name_max; + fsinfo->file_max = lfs->file_max; + fsinfo->attr_max = lfs->attr_max; + + return 0; +} + +int lfs_fs_rawtraverse(lfs_t *lfs, + int (*cb)(void *data, lfs_block_t block), void *data, + bool includeorphans) { // iterate over metadata pairs lfs_mdir_t dir = {.tail = {0, 1}}; @@ -3744,7 +4545,6 @@ int lfs_fs_traverse(lfs_t *lfs, if (lfs->lfs1) { int err = lfs1_traverse(lfs, cb, data); if (err) { - LFS_TRACE("lfs_fs_traverse -> %d", err); return err; } @@ -3753,11 +4553,26 @@ int lfs_fs_traverse(lfs_t *lfs, } #endif + lfs_block_t tortoise[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL}; + lfs_size_t tortoise_i = 1; + lfs_size_t tortoise_period = 1; while (!lfs_pair_isnull(dir.tail)) { + // detect cycles with Brent's algorithm + if (lfs_pair_issync(dir.tail, tortoise)) { + LFS_WARN("Cycle detected in tail list"); + return LFS_ERR_CORRUPT; + } + if (tortoise_i == tortoise_period) { + tortoise[0] = dir.tail[0]; + tortoise[1] = dir.tail[1]; + tortoise_i = 0; + tortoise_period *= 2; + } + tortoise_i += 1; + for (int i = 0; i < 2; i++) { int err = cb(data, dir.tail[i]); if (err) { - LFS_TRACE("lfs_fs_traverse -> %d", err); return err; } } @@ -3765,7 +4580,6 @@ int lfs_fs_traverse(lfs_t *lfs, // iterate through ids in directory int err = lfs_dir_fetch(lfs, &dir, dir.tail); if (err) { - LFS_TRACE("lfs_fs_traverse -> %d", err); return err; } @@ -3777,7 +4591,6 @@ int lfs_fs_traverse(lfs_t *lfs, if (tag == LFS_ERR_NOENT) { continue; } - LFS_TRACE("lfs_fs_traverse -> %"PRId32, tag); return tag; } lfs_ctz_fromle32(&ctz); @@ -3786,13 +4599,21 @@ int lfs_fs_traverse(lfs_t *lfs, err = lfs_ctz_traverse(lfs, NULL, &lfs->rcache, ctz.head, ctz.size, cb, data); if (err) { - LFS_TRACE("lfs_fs_traverse -> %d", err); return err; } + } else if (includeorphans && + lfs_tag_type3(tag) == LFS_TYPE_DIRSTRUCT) { + for (int i = 0; i < 2; i++) { + err = cb(data, (&ctz.head)[i]); + if (err) { + return err; + } + } } } } +#ifndef LFS_READONLY // iterate over any open files for (lfs_file_t *f = (lfs_file_t*)lfs->mlist; f; f = f->next) { if (f->type != LFS_TYPE_REG) { @@ -3803,7 +4624,6 @@ int lfs_fs_traverse(lfs_t *lfs, int err = lfs_ctz_traverse(lfs, &f->cache, &lfs->rcache, f->ctz.head, f->ctz.size, cb, data); if (err) { - LFS_TRACE("lfs_fs_traverse -> %d", err); return err; } } @@ -3812,22 +4632,38 @@ int lfs_fs_traverse(lfs_t *lfs, int err = lfs_ctz_traverse(lfs, &f->cache, &lfs->rcache, f->block, f->pos, cb, data); if (err) { - LFS_TRACE("lfs_fs_traverse -> %d", err); return err; } } } +#endif - LFS_TRACE("lfs_fs_traverse -> %d", 0); return 0; } +#ifndef LFS_READONLY static int lfs_fs_pred(lfs_t *lfs, const lfs_block_t pair[2], lfs_mdir_t *pdir) { // iterate over all directory directory entries pdir->tail[0] = 0; pdir->tail[1] = 1; + lfs_block_t tortoise[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL}; + lfs_size_t tortoise_i = 1; + lfs_size_t tortoise_period = 1; while (!lfs_pair_isnull(pdir->tail)) { + // detect cycles with Brent's algorithm + if (lfs_pair_issync(pdir->tail, tortoise)) { + LFS_WARN("Cycle detected in tail list"); + return LFS_ERR_CORRUPT; + } + if (tortoise_i == tortoise_period) { + tortoise[0] = pdir->tail[0]; + tortoise[1] = pdir->tail[1]; + tortoise_i = 0; + tortoise_period *= 2; + } + tortoise_i += 1; + if (lfs_pair_cmp(pdir->tail, pair) == 0) { return 0; } @@ -3840,12 +4676,16 @@ static int lfs_fs_pred(lfs_t *lfs, return LFS_ERR_NOENT; } +#endif +#ifndef LFS_READONLY struct lfs_fs_parent_match { lfs_t *lfs; const lfs_block_t pair[2]; }; +#endif +#ifndef LFS_READONLY static int lfs_fs_parent_match(void *data, lfs_tag_t tag, const void *buffer) { struct lfs_fs_parent_match *find = data; @@ -3864,13 +4704,31 @@ static int lfs_fs_parent_match(void *data, lfs_pair_fromle32(child); return (lfs_pair_cmp(child, find->pair) == 0) ? LFS_CMP_EQ : LFS_CMP_LT; } +#endif +#ifndef LFS_READONLY static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t pair[2], lfs_mdir_t *parent) { // use fetchmatch with callback to find pairs parent->tail[0] = 0; parent->tail[1] = 1; + lfs_block_t tortoise[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL}; + lfs_size_t tortoise_i = 1; + lfs_size_t tortoise_period = 1; while (!lfs_pair_isnull(parent->tail)) { + // detect cycles with Brent's algorithm + if (lfs_pair_issync(parent->tail, tortoise)) { + LFS_WARN("Cycle detected in tail list"); + return LFS_ERR_CORRUPT; + } + if (tortoise_i == tortoise_period) { + tortoise[0] = parent->tail[0]; + tortoise[1] = parent->tail[1]; + tortoise_i = 0; + tortoise_period *= 2; + } + tortoise_i += 1; + lfs_stag_t tag = lfs_dir_fetchmatch(lfs, parent, parent->tail, LFS_MKTAG(0x7ff, 0, 0x3ff), LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 0, 8), @@ -3884,201 +4742,291 @@ static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t pair[2], return LFS_ERR_NOENT; } +#endif -static int lfs_fs_relocate(lfs_t *lfs, - const lfs_block_t oldpair[2], lfs_block_t newpair[2]) { - // update internal root - if (lfs_pair_cmp(oldpair, lfs->root) == 0) { - LFS_DEBUG("Relocating root %"PRIx32" %"PRIx32, - newpair[0], newpair[1]); - lfs->root[0] = newpair[0]; - lfs->root[1] = newpair[1]; - } - - // update internally tracked dirs - for (struct lfs_mlist *d = lfs->mlist; d; d = d->next) { - if (lfs_pair_cmp(oldpair, d->m.pair) == 0) { - d->m.pair[0] = newpair[0]; - d->m.pair[1] = newpair[1]; - } - - if (d->type == LFS_TYPE_DIR && - lfs_pair_cmp(oldpair, ((lfs_dir_t*)d)->head) == 0) { - ((lfs_dir_t*)d)->head[0] = newpair[0]; - ((lfs_dir_t*)d)->head[1] = newpair[1]; - } - } +static void lfs_fs_prepsuperblock(lfs_t *lfs, bool needssuperblock) { + lfs->gstate.tag = (lfs->gstate.tag & ~LFS_MKTAG(0, 0, 0x200)) + | (uint32_t)needssuperblock << 9; +} - // find parent - lfs_mdir_t parent; - lfs_stag_t tag = lfs_fs_parent(lfs, oldpair, &parent); - if (tag < 0 && tag != LFS_ERR_NOENT) { - return tag; - } +#ifndef LFS_READONLY +static int lfs_fs_preporphans(lfs_t *lfs, int8_t orphans) { + LFS_ASSERT(lfs_tag_size(lfs->gstate.tag) > 0x000 || orphans >= 0); + LFS_ASSERT(lfs_tag_size(lfs->gstate.tag) < 0x1ff || orphans <= 0); + lfs->gstate.tag += orphans; + lfs->gstate.tag = ((lfs->gstate.tag & ~LFS_MKTAG(0x800, 0, 0)) | + ((uint32_t)lfs_gstate_hasorphans(&lfs->gstate) << 31)); - if (tag != LFS_ERR_NOENT) { - // update disk, this creates a desync - lfs_fs_preporphans(lfs, +1); + return 0; +} +#endif - lfs_pair_tole32(newpair); - int err = lfs_dir_commit(lfs, &parent, LFS_MKATTRS({tag, newpair})); - lfs_pair_fromle32(newpair); - if (err) { - return err; - } +#ifndef LFS_READONLY +static void lfs_fs_prepmove(lfs_t *lfs, + uint16_t id, const lfs_block_t pair[2]) { + lfs->gstate.tag = ((lfs->gstate.tag & ~LFS_MKTAG(0x7ff, 0x3ff, 0)) | + ((id != 0x3ff) ? LFS_MKTAG(LFS_TYPE_DELETE, id, 0) : 0)); + lfs->gstate.pair[0] = (id != 0x3ff) ? pair[0] : 0; + lfs->gstate.pair[1] = (id != 0x3ff) ? pair[1] : 0; +} +#endif - // next step, clean up orphans - lfs_fs_preporphans(lfs, -1); +#ifndef LFS_READONLY +static int lfs_fs_desuperblock(lfs_t *lfs) { + if (!lfs_gstate_needssuperblock(&lfs->gstate)) { + return 0; } - // find pred - int err = lfs_fs_pred(lfs, oldpair, &parent); - if (err && err != LFS_ERR_NOENT) { + LFS_DEBUG("Rewriting superblock {0x%"PRIx32", 0x%"PRIx32"}", + lfs->root[0], + lfs->root[1]); + + lfs_mdir_t root; + int err = lfs_dir_fetch(lfs, &root, lfs->root); + if (err) { return err; } - // if we can't find dir, it must be new - if (err != LFS_ERR_NOENT) { - // replace bad pair, either we clean up desync, or no desync occured - lfs_pair_tole32(newpair); - err = lfs_dir_commit(lfs, &parent, LFS_MKATTRS( - {LFS_MKTAG(LFS_TYPE_TAIL + parent.split, 0x3ff, 8), newpair})); - lfs_pair_fromle32(newpair); - if (err) { - return err; - } + // write a new superblock + lfs_superblock_t superblock = { + .version = lfs_fs_disk_version(lfs), + .block_size = lfs->cfg->block_size, + .block_count = lfs->block_count, + .name_max = lfs->name_max, + .file_max = lfs->file_max, + .attr_max = lfs->attr_max, + }; + + lfs_superblock_tole32(&superblock); + err = lfs_dir_commit(lfs, &root, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), + &superblock})); + if (err) { + return err; } + lfs_fs_prepsuperblock(lfs, false); return 0; } +#endif -static void lfs_fs_preporphans(lfs_t *lfs, int8_t orphans) { - lfs->gpending.tag += orphans; - lfs_gstate_xororphans(&lfs->gdelta, &lfs->gpending, - lfs_gstate_hasorphans(&lfs->gpending)); - lfs_gstate_xororphans(&lfs->gpending, &lfs->gpending, - lfs_gstate_hasorphans(&lfs->gpending)); -} - -static void lfs_fs_prepmove(lfs_t *lfs, - uint16_t id, const lfs_block_t pair[2]) { - lfs_gstate_xormove(&lfs->gdelta, &lfs->gpending, id, pair); - lfs_gstate_xormove(&lfs->gpending, &lfs->gpending, id, pair); -} - - +#ifndef LFS_READONLY static int lfs_fs_demove(lfs_t *lfs) { - if (!lfs_gstate_hasmove(&lfs->gstate)) { + if (!lfs_gstate_hasmove(&lfs->gdisk)) { return 0; } // Fix bad moves - LFS_DEBUG("Fixing move %"PRIx32" %"PRIx32" %"PRIx16, - lfs->gstate.pair[0], - lfs->gstate.pair[1], - lfs_tag_id(lfs->gstate.tag)); + LFS_DEBUG("Fixing move {0x%"PRIx32", 0x%"PRIx32"} 0x%"PRIx16, + lfs->gdisk.pair[0], + lfs->gdisk.pair[1], + lfs_tag_id(lfs->gdisk.tag)); + + // no other gstate is supported at this time, so if we found something else + // something most likely went wrong in gstate calculation + LFS_ASSERT(lfs_tag_type3(lfs->gdisk.tag) == LFS_TYPE_DELETE); // fetch and delete the moved entry lfs_mdir_t movedir; - int err = lfs_dir_fetch(lfs, &movedir, lfs->gstate.pair); + int err = lfs_dir_fetch(lfs, &movedir, lfs->gdisk.pair); if (err) { return err; } - // rely on cancel logic inside commit - err = lfs_dir_commit(lfs, &movedir, NULL, 0); + // prep gstate and delete move id + uint16_t moveid = lfs_tag_id(lfs->gdisk.tag); + lfs_fs_prepmove(lfs, 0x3ff, NULL); + err = lfs_dir_commit(lfs, &movedir, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_DELETE, moveid, 0), NULL})); if (err) { return err; } return 0; } +#endif -static int lfs_fs_deorphan(lfs_t *lfs) { +#ifndef LFS_READONLY +static int lfs_fs_deorphan(lfs_t *lfs, bool powerloss) { if (!lfs_gstate_hasorphans(&lfs->gstate)) { return 0; } - // Fix any orphans - lfs_mdir_t pdir = {.split = true}; - lfs_mdir_t dir = {.tail = {0, 1}}; + // Check for orphans in two separate passes: + // - 1 for half-orphans (relocations) + // - 2 for full-orphans (removes/renames) + // + // Two separate passes are needed as half-orphans can contain outdated + // references to full-orphans, effectively hiding them from the deorphan + // search. + // + int pass = 0; + while (pass < 2) { + // Fix any orphans + lfs_mdir_t pdir = {.split = true, .tail = {0, 1}}; + lfs_mdir_t dir; + bool moreorphans = false; + + // iterate over all directory directory entries + while (!lfs_pair_isnull(pdir.tail)) { + int err = lfs_dir_fetch(lfs, &dir, pdir.tail); + if (err) { + return err; + } - // iterate over all directory directory entries - while (!lfs_pair_isnull(dir.tail)) { - int err = lfs_dir_fetch(lfs, &dir, dir.tail); - if (err) { - return err; - } + // check head blocks for orphans + if (!pdir.split) { + // check if we have a parent + lfs_mdir_t parent; + lfs_stag_t tag = lfs_fs_parent(lfs, pdir.tail, &parent); + if (tag < 0 && tag != LFS_ERR_NOENT) { + return tag; + } - // check head blocks for orphans - if (!pdir.split) { - // check if we have a parent - lfs_mdir_t parent; - lfs_stag_t tag = lfs_fs_parent(lfs, pdir.tail, &parent); - if (tag < 0 && tag != LFS_ERR_NOENT) { - return tag; - } + if (pass == 0 && tag != LFS_ERR_NOENT) { + lfs_block_t pair[2]; + lfs_stag_t state = lfs_dir_get(lfs, &parent, + LFS_MKTAG(0x7ff, 0x3ff, 0), tag, pair); + if (state < 0) { + return state; + } + lfs_pair_fromle32(pair); + + if (!lfs_pair_issync(pair, pdir.tail)) { + // we have desynced + LFS_DEBUG("Fixing half-orphan " + "{0x%"PRIx32", 0x%"PRIx32"} " + "-> {0x%"PRIx32", 0x%"PRIx32"}", + pdir.tail[0], pdir.tail[1], pair[0], pair[1]); + + // fix pending move in this pair? this looks like an + // optimization but is in fact _required_ since + // relocating may outdate the move. + uint16_t moveid = 0x3ff; + if (lfs_gstate_hasmovehere(&lfs->gstate, pdir.pair)) { + moveid = lfs_tag_id(lfs->gstate.tag); + LFS_DEBUG("Fixing move while fixing orphans " + "{0x%"PRIx32", 0x%"PRIx32"} 0x%"PRIx16"\n", + pdir.pair[0], pdir.pair[1], moveid); + lfs_fs_prepmove(lfs, 0x3ff, NULL); + } - if (tag == LFS_ERR_NOENT) { - // we are an orphan - LFS_DEBUG("Fixing orphan %"PRIx32" %"PRIx32, - pdir.tail[0], pdir.tail[1]); + lfs_pair_tole32(pair); + state = lfs_dir_orphaningcommit(lfs, &pdir, LFS_MKATTRS( + {LFS_MKTAG_IF(moveid != 0x3ff, + LFS_TYPE_DELETE, moveid, 0), NULL}, + {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), + pair})); + lfs_pair_fromle32(pair); + if (state < 0) { + return state; + } - err = lfs_dir_drop(lfs, &pdir, &dir); - if (err) { - return err; + // did our commit create more orphans? + if (state == LFS_OK_ORPHANED) { + moreorphans = true; + } + + // refetch tail + continue; + } } - break; - } + // note we only check for full orphans if we may have had a + // power-loss, otherwise orphans are created intentionally + // during operations such as lfs_mkdir + if (pass == 1 && tag == LFS_ERR_NOENT && powerloss) { + // we are an orphan + LFS_DEBUG("Fixing orphan {0x%"PRIx32", 0x%"PRIx32"}", + pdir.tail[0], pdir.tail[1]); - lfs_block_t pair[2]; - lfs_stag_t res = lfs_dir_get(lfs, &parent, - LFS_MKTAG(0x7ff, 0x3ff, 0), tag, pair); - if (res < 0) { - return res; - } - lfs_pair_fromle32(pair); + // steal state + err = lfs_dir_getgstate(lfs, &dir, &lfs->gdelta); + if (err) { + return err; + } - if (!lfs_pair_sync(pair, pdir.tail)) { - // we have desynced - LFS_DEBUG("Fixing half-orphan %"PRIx32" %"PRIx32, - pair[0], pair[1]); + // steal tail + lfs_pair_tole32(dir.tail); + int state = lfs_dir_orphaningcommit(lfs, &pdir, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_TAIL + dir.split, 0x3ff, 8), + dir.tail})); + lfs_pair_fromle32(dir.tail); + if (state < 0) { + return state; + } - lfs_pair_tole32(pair); - err = lfs_dir_commit(lfs, &pdir, LFS_MKATTRS( - {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), pair})); - lfs_pair_fromle32(pair); - if (err) { - return err; - } + // did our commit create more orphans? + if (state == LFS_OK_ORPHANED) { + moreorphans = true; + } - break; + // refetch tail + continue; + } } + + pdir = dir; } - memcpy(&pdir, &dir, sizeof(pdir)); + pass = moreorphans ? 0 : pass+1; } // mark orphans as fixed - lfs_fs_preporphans(lfs, -lfs_gstate_getorphans(&lfs->gstate)); - lfs->gstate = lfs->gpending; - return 0; + return lfs_fs_preporphans(lfs, -lfs_gstate_getorphans(&lfs->gstate)); } +#endif +#ifndef LFS_READONLY static int lfs_fs_forceconsistency(lfs_t *lfs) { - int err = lfs_fs_demove(lfs); + int err = lfs_fs_desuperblock(lfs); if (err) { return err; } - err = lfs_fs_deorphan(lfs); + err = lfs_fs_demove(lfs); if (err) { return err; } + err = lfs_fs_deorphan(lfs, true); + if (err) { + return err; + } + + return 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_fs_rawmkconsistent(lfs_t *lfs) { + // lfs_fs_forceconsistency does most of the work here + int err = lfs_fs_forceconsistency(lfs); + if (err) { + return err; + } + + // do we have any pending gstate? + lfs_gstate_t delta = {0}; + lfs_gstate_xor(&delta, &lfs->gdisk); + lfs_gstate_xor(&delta, &lfs->gstate); + if (!lfs_gstate_iszero(&delta)) { + // lfs_dir_commit will implicitly write out any pending gstate + lfs_mdir_t root; + err = lfs_dir_fetch(lfs, &root, lfs->root); + if (err) { + return err; + } + + err = lfs_dir_commit(lfs, &root, NULL, 0); + if (err) { + return err; + } + } + return 0; } +#endif static int lfs_fs_size_count(void *p, lfs_block_t block) { (void)block; @@ -4087,19 +5035,55 @@ static int lfs_fs_size_count(void *p, lfs_block_t block) { return 0; } -lfs_ssize_t lfs_fs_size(lfs_t *lfs) { - LFS_TRACE("lfs_fs_size(%p)", (void*)lfs); +static lfs_ssize_t lfs_fs_rawsize(lfs_t *lfs) { lfs_size_t size = 0; - int err = lfs_fs_traverse(lfs, lfs_fs_size_count, &size); + int err = lfs_fs_rawtraverse(lfs, lfs_fs_size_count, &size, false); if (err) { - LFS_TRACE("lfs_fs_size -> %d", err); return err; } - LFS_TRACE("lfs_fs_size -> %d", err); return size; } +#ifndef LFS_READONLY +static int lfs_fs_rawgrow(lfs_t *lfs, lfs_size_t block_count) { + // shrinking is not supported + LFS_ASSERT(block_count >= lfs->block_count); + + if (block_count > lfs->block_count) { + lfs->block_count = block_count; + + // fetch the root + lfs_mdir_t root; + int err = lfs_dir_fetch(lfs, &root, lfs->root); + if (err) { + return err; + } + + // update the superblock + lfs_superblock_t superblock; + lfs_stag_t tag = lfs_dir_get(lfs, &root, LFS_MKTAG(0x7ff, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), + &superblock); + if (tag < 0) { + return tag; + } + lfs_superblock_fromle32(&superblock); + + superblock.block_count = lfs->block_count; + + lfs_superblock_tole32(&superblock); + err = lfs_dir_commit(lfs, &root, LFS_MKATTRS( + {tag, &superblock})); + if (err) { + return err; + } + } + + return 0; +} +#endif + #ifdef LFS_MIGRATE ////// Migration from littelfs v1 below this ////// @@ -4278,7 +5262,7 @@ static int lfs1_dir_fetch(lfs_t *lfs, continue; } - uint32_t crc = LFS_BLOCK_NULL; + uint32_t crc = 0xffffffff; lfs1_dir_tole32(&test); lfs1_crc(&crc, &test, sizeof(test)); lfs1_dir_fromle32(&test); @@ -4305,7 +5289,7 @@ static int lfs1_dir_fetch(lfs_t *lfs, } if (!valid) { - LFS_ERROR("Corrupted dir pair at %" PRIx32 " %" PRIx32 , + LFS_ERROR("Corrupted dir pair at {0x%"PRIx32", 0x%"PRIx32"}", tpair[0], tpair[1]); return LFS_ERR_CORRUPT; } @@ -4493,7 +5477,8 @@ static int lfs1_mount(lfs_t *lfs, struct lfs1 *lfs1, } if (err || memcmp(superblock.d.magic, "littlefs", 8) != 0) { - LFS_ERROR("Invalid superblock at %d %d", 0, 1); + LFS_ERROR("Invalid superblock at {0x%"PRIx32", 0x%"PRIx32"}", + 0, 1); err = LFS_ERR_CORRUPT; goto cleanup; } @@ -4502,7 +5487,7 @@ static int lfs1_mount(lfs_t *lfs, struct lfs1 *lfs1, uint16_t minor_version = (0xffff & (superblock.d.version >> 0)); if ((major_version != LFS1_DISK_VERSION_MAJOR || minor_version > LFS1_DISK_VERSION_MINOR)) { - LFS_ERROR("Invalid version %d.%d", major_version, minor_version); + LFS_ERROR("Invalid version v%d.%d", major_version, minor_version); err = LFS_ERR_INVAL; goto cleanup; } @@ -4520,27 +5505,14 @@ static int lfs1_unmount(lfs_t *lfs) { } /// v1 migration /// -int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg) { - LFS_TRACE("lfs_migrate(%p, %p {.context=%p, " - ".read=%p, .prog=%p, .erase=%p, .sync=%p, " - ".read_size=%"PRIu32", .prog_size=%"PRIu32", " - ".block_size=%"PRIu32", .block_count=%"PRIu32", " - ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " - ".lookahead_size=%"PRIu32", .read_buffer=%p, " - ".prog_buffer=%p, .lookahead_buffer=%p, " - ".name_max=%"PRIu32", .file_max=%"PRIu32", " - ".attr_max=%"PRIu32"})", - (void*)lfs, (void*)cfg, cfg->context, - (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, - (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, - cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, - cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, - cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, - cfg->name_max, cfg->file_max, cfg->attr_max); +static int lfs_rawmigrate(lfs_t *lfs, const struct lfs_config *cfg) { struct lfs1 lfs1; + + // Indeterminate filesystem size not allowed for migration. + LFS_ASSERT(cfg->block_count != 0); + int err = lfs1_mount(lfs, &lfs1, cfg); if (err) { - LFS_TRACE("lfs_migrate -> %d", err); return err; } @@ -4629,12 +5601,14 @@ int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg) { lfs1_entry_tole32(&entry1.d); err = lfs_dir_commit(lfs, &dir2, LFS_MKATTRS( {LFS_MKTAG(LFS_TYPE_CREATE, id, 0), NULL}, - {LFS_MKTAG( - isdir ? LFS_TYPE_DIR : LFS_TYPE_REG, - id, entry1.d.nlen), name}, - {LFS_MKTAG( - isdir ? LFS_TYPE_DIRSTRUCT : LFS_TYPE_CTZSTRUCT, - id, sizeof(entry1.d.u)), &entry1.d.u})); + {LFS_MKTAG_IF_ELSE(isdir, + LFS_TYPE_DIR, id, entry1.d.nlen, + LFS_TYPE_REG, id, entry1.d.nlen), + name}, + {LFS_MKTAG_IF_ELSE(isdir, + LFS_TYPE_DIRSTRUCT, id, sizeof(entry1.d.u), + LFS_TYPE_CTZSTRUCT, id, sizeof(entry1.d.u)), + &entry1.d.u})); lfs1_entry_fromle32(&entry1.d); if (err) { goto cleanup; @@ -4657,8 +5631,7 @@ int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg) { lfs_pair_tole32(dir2.pair); err = lfs_dir_commit(lfs, &dir2, LFS_MKATTRS( - {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), - dir1.d.tail})); + {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), dir1.d.tail})); lfs_pair_fromle32(dir2.pair); if (err) { goto cleanup; @@ -4667,7 +5640,8 @@ int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg) { // Copy over first block to thread into fs. Unfortunately // if this fails there is not much we can do. - LFS_DEBUG("Migrating %"PRIx32" %"PRIx32" -> %"PRIx32" %"PRIx32, + LFS_DEBUG("Migrating {0x%"PRIx32", 0x%"PRIx32"} " + "-> {0x%"PRIx32", 0x%"PRIx32"}", lfs->root[0], lfs->root[1], dir1.head[0], dir1.head[1]); err = lfs_bd_erase(lfs, dir1.head[1]); @@ -4713,7 +5687,7 @@ int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg) { dir2.pair[1] = dir1.pair[1]; dir2.rev = dir1.d.rev; dir2.off = sizeof(dir2.rev); - dir2.etag = LFS_BLOCK_NULL; + dir2.etag = 0xffffffff; dir2.count = 0; dir2.tail[0] = lfs->lfs1->root[0]; dir2.tail[1] = lfs->lfs1->root[1]; @@ -4755,8 +5729,602 @@ int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg) { cleanup: lfs1_unmount(lfs); - LFS_TRACE("lfs_migrate -> %d", err); return err; } #endif + + +/// Public API wrappers /// + +// Here we can add tracing/thread safety easily + +// Thread-safe wrappers if enabled +#ifdef LFS_THREADSAFE +#define LFS_LOCK(cfg) cfg->lock(cfg) +#define LFS_UNLOCK(cfg) cfg->unlock(cfg) +#else +#define LFS_LOCK(cfg) ((void)cfg, 0) +#define LFS_UNLOCK(cfg) ((void)cfg) +#endif + +// Public API +#ifndef LFS_READONLY +int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) { + int err = LFS_LOCK(cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_format(%p, %p {.context=%p, " + ".read=%p, .prog=%p, .erase=%p, .sync=%p, " + ".read_size=%"PRIu32", .prog_size=%"PRIu32", " + ".block_size=%"PRIu32", .block_count=%"PRIu32", " + ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " + ".lookahead_size=%"PRIu32", .read_buffer=%p, " + ".prog_buffer=%p, .lookahead_buffer=%p, " + ".name_max=%"PRIu32", .file_max=%"PRIu32", " + ".attr_max=%"PRIu32"})", + (void*)lfs, (void*)cfg, cfg->context, + (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, + (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, + cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, + cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, + cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, + cfg->name_max, cfg->file_max, cfg->attr_max); + + err = lfs_rawformat(lfs, cfg); + + LFS_TRACE("lfs_format -> %d", err); + LFS_UNLOCK(cfg); + return err; +} +#endif + +int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { + int err = LFS_LOCK(cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_mount(%p, %p {.context=%p, " + ".read=%p, .prog=%p, .erase=%p, .sync=%p, " + ".read_size=%"PRIu32", .prog_size=%"PRIu32", " + ".block_size=%"PRIu32", .block_count=%"PRIu32", " + ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " + ".lookahead_size=%"PRIu32", .read_buffer=%p, " + ".prog_buffer=%p, .lookahead_buffer=%p, " + ".name_max=%"PRIu32", .file_max=%"PRIu32", " + ".attr_max=%"PRIu32"})", + (void*)lfs, (void*)cfg, cfg->context, + (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, + (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, + cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, + cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, + cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, + cfg->name_max, cfg->file_max, cfg->attr_max); + + err = lfs_rawmount(lfs, cfg); + + LFS_TRACE("lfs_mount -> %d", err); + LFS_UNLOCK(cfg); + return err; +} + +int lfs_unmount(lfs_t *lfs) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_unmount(%p)", (void*)lfs); + + err = lfs_rawunmount(lfs); + + LFS_TRACE("lfs_unmount -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +#ifndef LFS_READONLY +int lfs_remove(lfs_t *lfs, const char *path) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_remove(%p, \"%s\")", (void*)lfs, path); + + err = lfs_rawremove(lfs, path); + + LFS_TRACE("lfs_remove -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + +#ifndef LFS_READONLY +int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_rename(%p, \"%s\", \"%s\")", (void*)lfs, oldpath, newpath); + + err = lfs_rawrename(lfs, oldpath, newpath); + + LFS_TRACE("lfs_rename -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + +int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_stat(%p, \"%s\", %p)", (void*)lfs, path, (void*)info); + + err = lfs_rawstat(lfs, path, info); + + LFS_TRACE("lfs_stat -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path, + uint8_t type, void *buffer, lfs_size_t size) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_getattr(%p, \"%s\", %"PRIu8", %p, %"PRIu32")", + (void*)lfs, path, type, buffer, size); + + lfs_ssize_t res = lfs_rawgetattr(lfs, path, type, buffer, size); + + LFS_TRACE("lfs_getattr -> %"PRId32, res); + LFS_UNLOCK(lfs->cfg); + return res; +} + +#ifndef LFS_READONLY +int lfs_setattr(lfs_t *lfs, const char *path, + uint8_t type, const void *buffer, lfs_size_t size) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_setattr(%p, \"%s\", %"PRIu8", %p, %"PRIu32")", + (void*)lfs, path, type, buffer, size); + + err = lfs_rawsetattr(lfs, path, type, buffer, size); + + LFS_TRACE("lfs_setattr -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + +#ifndef LFS_READONLY +int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_removeattr(%p, \"%s\", %"PRIu8")", (void*)lfs, path, type); + + err = lfs_rawremoveattr(lfs, path, type); + + LFS_TRACE("lfs_removeattr -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + +#ifndef LFS_NO_MALLOC +int lfs_file_open(lfs_t *lfs, lfs_file_t *file, const char *path, int flags) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_file_open(%p, %p, \"%s\", %x)", + (void*)lfs, (void*)file, path, flags); + LFS_ASSERT(!lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + + err = lfs_file_rawopen(lfs, file, path, flags); + + LFS_TRACE("lfs_file_open -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + +int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, + const char *path, int flags, + const struct lfs_file_config *cfg) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_file_opencfg(%p, %p, \"%s\", %x, %p {" + ".buffer=%p, .attrs=%p, .attr_count=%"PRIu32"})", + (void*)lfs, (void*)file, path, flags, + (void*)cfg, cfg->buffer, (void*)cfg->attrs, cfg->attr_count); + LFS_ASSERT(!lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + + err = lfs_file_rawopencfg(lfs, file, path, flags, cfg); + + LFS_TRACE("lfs_file_opencfg -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +int lfs_file_close(lfs_t *lfs, lfs_file_t *file) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_file_close(%p, %p)", (void*)lfs, (void*)file); + LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + + err = lfs_file_rawclose(lfs, file); + + LFS_TRACE("lfs_file_close -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +#ifndef LFS_READONLY +int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_file_sync(%p, %p)", (void*)lfs, (void*)file); + LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + + err = lfs_file_rawsync(lfs, file); + + LFS_TRACE("lfs_file_sync -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + +lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, + void *buffer, lfs_size_t size) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_file_read(%p, %p, %p, %"PRIu32")", + (void*)lfs, (void*)file, buffer, size); + LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + + lfs_ssize_t res = lfs_file_rawread(lfs, file, buffer, size); + + LFS_TRACE("lfs_file_read -> %"PRId32, res); + LFS_UNLOCK(lfs->cfg); + return res; +} + +#ifndef LFS_READONLY +lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, + const void *buffer, lfs_size_t size) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_file_write(%p, %p, %p, %"PRIu32")", + (void*)lfs, (void*)file, buffer, size); + LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + + lfs_ssize_t res = lfs_file_rawwrite(lfs, file, buffer, size); + + LFS_TRACE("lfs_file_write -> %"PRId32, res); + LFS_UNLOCK(lfs->cfg); + return res; +} +#endif + +lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, + lfs_soff_t off, int whence) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_file_seek(%p, %p, %"PRId32", %d)", + (void*)lfs, (void*)file, off, whence); + LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + + lfs_soff_t res = lfs_file_rawseek(lfs, file, off, whence); + + LFS_TRACE("lfs_file_seek -> %"PRId32, res); + LFS_UNLOCK(lfs->cfg); + return res; +} + +#ifndef LFS_READONLY +int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_file_truncate(%p, %p, %"PRIu32")", + (void*)lfs, (void*)file, size); + LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + + err = lfs_file_rawtruncate(lfs, file, size); + + LFS_TRACE("lfs_file_truncate -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + +lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_file_tell(%p, %p)", (void*)lfs, (void*)file); + LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + + lfs_soff_t res = lfs_file_rawtell(lfs, file); + + LFS_TRACE("lfs_file_tell -> %"PRId32, res); + LFS_UNLOCK(lfs->cfg); + return res; +} + +int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_file_rewind(%p, %p)", (void*)lfs, (void*)file); + + err = lfs_file_rawrewind(lfs, file); + + LFS_TRACE("lfs_file_rewind -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_file_size(%p, %p)", (void*)lfs, (void*)file); + LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + + lfs_soff_t res = lfs_file_rawsize(lfs, file); + + LFS_TRACE("lfs_file_size -> %"PRId32, res); + LFS_UNLOCK(lfs->cfg); + return res; +} + +#ifndef LFS_READONLY +int lfs_mkdir(lfs_t *lfs, const char *path) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_mkdir(%p, \"%s\")", (void*)lfs, path); + + err = lfs_rawmkdir(lfs, path); + + LFS_TRACE("lfs_mkdir -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + +int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_dir_open(%p, %p, \"%s\")", (void*)lfs, (void*)dir, path); + LFS_ASSERT(!lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)dir)); + + err = lfs_dir_rawopen(lfs, dir, path); + + LFS_TRACE("lfs_dir_open -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_dir_close(%p, %p)", (void*)lfs, (void*)dir); + + err = lfs_dir_rawclose(lfs, dir); + + LFS_TRACE("lfs_dir_close -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_dir_read(%p, %p, %p)", + (void*)lfs, (void*)dir, (void*)info); + + err = lfs_dir_rawread(lfs, dir, info); + + LFS_TRACE("lfs_dir_read -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_dir_seek(%p, %p, %"PRIu32")", + (void*)lfs, (void*)dir, off); + + err = lfs_dir_rawseek(lfs, dir, off); + + LFS_TRACE("lfs_dir_seek -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_dir_tell(%p, %p)", (void*)lfs, (void*)dir); + + lfs_soff_t res = lfs_dir_rawtell(lfs, dir); + + LFS_TRACE("lfs_dir_tell -> %"PRId32, res); + LFS_UNLOCK(lfs->cfg); + return res; +} + +int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_dir_rewind(%p, %p)", (void*)lfs, (void*)dir); + + err = lfs_dir_rawrewind(lfs, dir); + + LFS_TRACE("lfs_dir_rewind -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +int lfs_fs_stat(lfs_t *lfs, struct lfs_fsinfo *fsinfo) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_fs_stat(%p, %p)", (void*)lfs, (void*)fsinfo); + + err = lfs_fs_rawstat(lfs, fsinfo); + + LFS_TRACE("lfs_fs_stat -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +lfs_ssize_t lfs_fs_size(lfs_t *lfs) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_fs_size(%p)", (void*)lfs); + + lfs_ssize_t res = lfs_fs_rawsize(lfs); + + LFS_TRACE("lfs_fs_size -> %"PRId32, res); + LFS_UNLOCK(lfs->cfg); + return res; +} + +int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void *, lfs_block_t), void *data) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_fs_traverse(%p, %p, %p)", + (void*)lfs, (void*)(uintptr_t)cb, data); + + err = lfs_fs_rawtraverse(lfs, cb, data, true); + + LFS_TRACE("lfs_fs_traverse -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +#ifndef LFS_READONLY +int lfs_fs_gc(lfs_t *lfs) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_fs_gc(%p)", (void*)lfs); + + err = lfs_fs_rawgc(lfs); + + LFS_TRACE("lfs_fs_gc -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + +#ifndef LFS_READONLY +int lfs_fs_mkconsistent(lfs_t *lfs) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_fs_mkconsistent(%p)", (void*)lfs); + + err = lfs_fs_rawmkconsistent(lfs); + + LFS_TRACE("lfs_fs_mkconsistent -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + +#ifndef LFS_READONLY +int lfs_fs_grow(lfs_t *lfs, lfs_size_t block_count) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_fs_grow(%p, %"PRIu32")", (void*)lfs, block_count); + + err = lfs_fs_rawgrow(lfs, block_count); + + LFS_TRACE("lfs_fs_grow -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + +#ifdef LFS_MIGRATE +int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg) { + int err = LFS_LOCK(cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_migrate(%p, %p {.context=%p, " + ".read=%p, .prog=%p, .erase=%p, .sync=%p, " + ".read_size=%"PRIu32", .prog_size=%"PRIu32", " + ".block_size=%"PRIu32", .block_count=%"PRIu32", " + ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " + ".lookahead_size=%"PRIu32", .read_buffer=%p, " + ".prog_buffer=%p, .lookahead_buffer=%p, " + ".name_max=%"PRIu32", .file_max=%"PRIu32", " + ".attr_max=%"PRIu32"})", + (void*)lfs, (void*)cfg, cfg->context, + (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, + (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, + cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, + cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, + cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, + cfg->name_max, cfg->file_max, cfg->attr_max); + + err = lfs_rawmigrate(lfs, cfg); + + LFS_TRACE("lfs_migrate -> %d", err); + LFS_UNLOCK(cfg); + return err; +} +#endif diff --git a/littlefs/lfs.h b/littlefs/lfs.h index 04054e9..36c3bcd 100644 --- a/littlefs/lfs.h +++ b/littlefs/lfs.h @@ -1,14 +1,14 @@ /* * The little filesystem * + * Copyright (c) 2022, The littlefs authors. * Copyright (c) 2017, Arm Limited. All rights reserved. * SPDX-License-Identifier: BSD-3-Clause */ #ifndef LFS_H #define LFS_H -#include -#include +#include "lfs_util.h" #ifdef __cplusplus extern "C" @@ -21,14 +21,14 @@ extern "C" // Software library version // Major (top-nibble), incremented on backwards incompatible changes // Minor (bottom-nibble), incremented on feature additions -#define LFS_VERSION 0x00020001 +#define LFS_VERSION 0x00020008 #define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16)) #define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >> 0)) // Version of On-disk data structures // Major (top-nibble), incremented on backwards incompatible changes // Minor (bottom-nibble), incremented on feature additions -#define LFS_DISK_VERSION 0x00020000 +#define LFS_DISK_VERSION 0x00020001 #define LFS_DISK_VERSION_MAJOR (0xffff & (LFS_DISK_VERSION >> 16)) #define LFS_DISK_VERSION_MINOR (0xffff & (LFS_DISK_VERSION >> 0)) @@ -112,6 +112,8 @@ enum lfs_type { LFS_TYPE_SOFTTAIL = 0x600, LFS_TYPE_HARDTAIL = 0x601, LFS_TYPE_MOVESTATE = 0x7ff, + LFS_TYPE_CCRC = 0x500, + LFS_TYPE_FCRC = 0x5ff, // internal chip sources LFS_FROM_NOOP = 0x000, @@ -123,20 +125,25 @@ enum lfs_type { enum lfs_open_flags { // open flags LFS_O_RDONLY = 1, // Open a file as read only +#ifndef LFS_READONLY LFS_O_WRONLY = 2, // Open a file as write only LFS_O_RDWR = 3, // Open a file as read and write LFS_O_CREAT = 0x0100, // Create a file if it does not exist LFS_O_EXCL = 0x0200, // Fail if a file already exists LFS_O_TRUNC = 0x0400, // Truncate the existing file to zero size LFS_O_APPEND = 0x0800, // Move to end of file on every write +#endif // internally used flags +#ifndef LFS_READONLY LFS_F_DIRTY = 0x010000, // File does not match storage LFS_F_WRITING = 0x020000, // File has been written since last flush +#endif LFS_F_READING = 0x040000, // File has been read since last flush - LFS_F_ERRED = 0x080000, // An error occured during write +#ifndef LFS_READONLY + LFS_F_ERRED = 0x080000, // An error occurred during write +#endif LFS_F_INLINE = 0x100000, // Currently inlined in directory entry - LFS_F_OPENED = 0x200000, // File has been opened }; // File seek flags @@ -153,45 +160,55 @@ struct lfs_config { // information to the block device operations void *context; - // Read a region in a block. Negative error codes are propogated + // Read a region in a block. Negative error codes are propagated // to the user. int (*read)(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size); // Program a region in a block. The block must have previously - // been erased. Negative error codes are propogated to the user. + // been erased. Negative error codes are propagated to the user. // May return LFS_ERR_CORRUPT if the block should be considered bad. int (*prog)(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size); // Erase a block. A block must be erased before being programmed. // The state of an erased block is undefined. Negative error codes - // are propogated to the user. + // are propagated to the user. // May return LFS_ERR_CORRUPT if the block should be considered bad. int (*erase)(const struct lfs_config *c, lfs_block_t block); // Sync the state of the underlying block device. Negative error codes - // are propogated to the user. + // are propagated to the user. int (*sync)(const struct lfs_config *c); - // Minimum size of a block read. All read operations will be a +#ifdef LFS_THREADSAFE + // Lock the underlying block device. Negative error codes + // are propagated to the user. + int (*lock)(const struct lfs_config *c); + + // Unlock the underlying block device. Negative error codes + // are propagated to the user. + int (*unlock)(const struct lfs_config *c); +#endif + + // Minimum size of a block read in bytes. All read operations will be a // multiple of this value. lfs_size_t read_size; - // Minimum size of a block program. All program operations will be a - // multiple of this value. + // Minimum size of a block program in bytes. All program operations will be + // a multiple of this value. lfs_size_t prog_size; - // Size of an erasable block. This does not impact ram consumption and - // may be larger than the physical erase size. However, non-inlined files - // take up at minimum one block. Must be a multiple of the read - // and program sizes. + // Size of an erasable block in bytes. This does not impact ram consumption + // and may be larger than the physical erase size. However, non-inlined + // files take up at minimum one block. Must be a multiple of the read and + // program sizes. lfs_size_t block_size; // Number of erasable blocks on the device. lfs_size_t block_count; - // Number of erase cycles before littlefs evicts metadata logs and moves + // Number of erase cycles before littlefs evicts metadata logs and moves // the metadata to another block. Suggested values are in the // range 100-1000, with large values having better performance at the cost // of less consistent wear distribution. @@ -199,11 +216,11 @@ struct lfs_config { // Set to -1 to disable block-level wear-leveling. int32_t block_cycles; - // Size of block caches. Each cache buffers a portion of a block in RAM. - // The littlefs needs a read cache, a program cache, and one additional + // Size of block caches in bytes. Each cache buffers a portion of a block in + // RAM. The littlefs needs a read cache, a program cache, and one additional // cache per file. Larger caches can improve performance by storing more - // data and reducing the number of disk accesses. Must be a multiple of - // the read and program sizes, and a factor of the block size. + // data and reducing the number of disk accesses. Must be a multiple of the + // read and program sizes, and a factor of the block size. lfs_size_t cache_size; // Size of the lookahead buffer in bytes. A larger lookahead buffer @@ -240,6 +257,20 @@ struct lfs_config { // larger attributes size but must be <= LFS_ATTR_MAX. Defaults to // LFS_ATTR_MAX when zero. lfs_size_t attr_max; + + // Optional upper limit on total space given to metadata pairs in bytes. On + // devices with large blocks (e.g. 128kB) setting this to a low size (2-8kB) + // can help bound the metadata compaction time. Must be <= block_size. + // Defaults to block_size when zero. + lfs_size_t metadata_max; + +#ifdef LFS_MULTIVERSION + // On-disk version to use when writing in the form of 16-bit major version + // + 16-bit minor version. This limiting metadata to what is supported by + // older minor versions. Note that some features will be lost. Defaults to + // to the most recent minor version when zero. + uint32_t disk_version; +#endif }; // File info structure @@ -257,6 +288,27 @@ struct lfs_info { char name[LFS_NAME_MAX+1]; }; +// Filesystem info structure +struct lfs_fsinfo { + // On-disk version. + uint32_t disk_version; + + // Size of a logical block in bytes. + lfs_size_t block_size; + + // Number of logical blocks in filesystem. + lfs_size_t block_count; + + // Upper limit on the length of file names in bytes. + lfs_size_t name_max; + + // Upper limit on the size of files in bytes. + lfs_size_t file_max; + + // Upper limit on the size of custom attributes in bytes. + lfs_size_t attr_max; +}; + // Custom attribute structure, used to describe custom attributes // committed atomically during file writes. struct lfs_attr { @@ -355,6 +407,11 @@ typedef struct lfs_superblock { lfs_size_t attr_max; } lfs_superblock_t; +typedef struct lfs_gstate { + uint32_t tag; + lfs_block_t pair[2]; +} lfs_gstate_t; + // The littlefs filesystem type typedef struct lfs { lfs_cache_t rcache; @@ -369,10 +426,9 @@ typedef struct lfs { } *mlist; uint32_t seed; - struct lfs_gstate { - uint32_t tag; - lfs_block_t pair[2]; - } gstate, gpending, gdelta; + lfs_gstate_t gstate; + lfs_gstate_t gdisk; + lfs_gstate_t gdelta; struct lfs_free { lfs_block_t off; @@ -383,6 +439,7 @@ typedef struct lfs { } free; const struct lfs_config *cfg; + lfs_size_t block_count; lfs_size_t name_max; lfs_size_t file_max; lfs_size_t attr_max; @@ -395,6 +452,7 @@ typedef struct lfs { /// Filesystem functions /// +#ifndef LFS_READONLY // Format a block device with the littlefs // // Requires a littlefs object and config struct. This clobbers the littlefs @@ -403,6 +461,7 @@ typedef struct lfs { // // Returns a negative error code on failure. int lfs_format(lfs_t *lfs, const struct lfs_config *config); +#endif // Mounts a littlefs // @@ -422,12 +481,15 @@ int lfs_unmount(lfs_t *lfs); /// General operations /// +#ifndef LFS_READONLY // Removes a file or directory // // If removing a directory, the directory must be empty. // Returns a negative error code on failure. int lfs_remove(lfs_t *lfs, const char *path); +#endif +#ifndef LFS_READONLY // Rename or move a file or directory // // If the destination exists, it must match the source in type. @@ -435,6 +497,7 @@ int lfs_remove(lfs_t *lfs, const char *path); // // Returns a negative error code on failure. int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath); +#endif // Find info about a file or directory // @@ -453,10 +516,11 @@ int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info); // Returns the size of the attribute, or a negative error code on failure. // Note, the returned size is the size of the attribute on disk, irrespective // of the size of the buffer. This can be used to dynamically allocate a buffer -// or check for existance. +// or check for existence. lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path, uint8_t type, void *buffer, lfs_size_t size); +#ifndef LFS_READONLY // Set custom attributes // // Custom attributes are uniquely identified by an 8-bit type and limited @@ -466,17 +530,21 @@ lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path, // Returns a negative error code on failure. int lfs_setattr(lfs_t *lfs, const char *path, uint8_t type, const void *buffer, lfs_size_t size); +#endif +#ifndef LFS_READONLY // Removes a custom attribute // // If an attribute is not found, nothing happens. // // Returns a negative error code on failure. int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type); +#endif /// File operations /// +#ifndef LFS_NO_MALLOC // Open a file // // The mode that the file is opened in is determined by the flags, which @@ -486,14 +554,18 @@ int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type); int lfs_file_open(lfs_t *lfs, lfs_file_t *file, const char *path, int flags); +// if LFS_NO_MALLOC is defined, lfs_file_open() will fail with LFS_ERR_NOMEM +// thus use lfs_file_opencfg() with config.buffer set. +#endif + // Open a file with extra configuration // // The mode that the file is opened in is determined by the flags, which // are values from the enum lfs_open_flags that are bitwise-ored together. // // The config struct provides additional config options per file as described -// above. The config struct must be allocated while the file is open, and the -// config struct must be zeroed for defaults and backwards compatibility. +// above. The config struct must remain allocated while the file is open, and +// the config struct must be zeroed for defaults and backwards compatibility. // // Returns a negative error code on failure. int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, @@ -521,6 +593,7 @@ int lfs_file_sync(lfs_t *lfs, lfs_file_t *file); lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, void *buffer, lfs_size_t size); +#ifndef LFS_READONLY // Write data to file // // Takes a buffer and size indicating the data to write. The file will not @@ -529,6 +602,7 @@ lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, // Returns the number of bytes written, or a negative error code on failure. lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, const void *buffer, lfs_size_t size); +#endif // Change the position of the file // @@ -537,10 +611,12 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, lfs_soff_t off, int whence); +#ifndef LFS_READONLY // Truncates the size of the file to the specified size // // Returns a negative error code on failure. int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size); +#endif // Return the position of the file // @@ -563,10 +639,12 @@ lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file); /// Directory operations /// +#ifndef LFS_READONLY // Create a directory // // Returns a negative error code on failure. int lfs_mkdir(lfs_t *lfs, const char *path); +#endif // Open a directory // @@ -611,6 +689,12 @@ int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir); /// Filesystem-level filesystem operations +// Find on-disk info about the filesystem +// +// Fills out the fsinfo structure based on the filesystem found on-disk. +// Returns a negative error code on failure. +int lfs_fs_stat(lfs_t *lfs, struct lfs_fsinfo *fsinfo); + // Finds the current size of the filesystem // // Note: Result is best effort. If files share COW structures, the returned @@ -628,6 +712,41 @@ lfs_ssize_t lfs_fs_size(lfs_t *lfs); // Returns a negative error code on failure. int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data); +// Attempt to proactively find free blocks +// +// Calling this function is not required, but may allowing the offloading of +// the expensive block allocation scan to a less time-critical code path. +// +// Note: littlefs currently does not persist any found free blocks to disk. +// This may change in the future. +// +// Returns a negative error code on failure. Finding no free blocks is +// not an error. +int lfs_fs_gc(lfs_t *lfs); + +#ifndef LFS_READONLY +// Attempt to make the filesystem consistent and ready for writing +// +// Calling this function is not required, consistency will be implicitly +// enforced on the first operation that writes to the filesystem, but this +// function allows the work to be performed earlier and without other +// filesystem changes. +// +// Returns a negative error code on failure. +int lfs_fs_mkconsistent(lfs_t *lfs); +#endif + +#ifndef LFS_READONLY +// Grows the filesystem to a new size, updating the superblock with the new +// block count. +// +// Note: This is irreversible. +// +// Returns a negative error code on failure. +int lfs_fs_grow(lfs_t *lfs, lfs_size_t block_count); +#endif + +#ifndef LFS_READONLY #ifdef LFS_MIGRATE // Attempts to migrate a previous version of littlefs // @@ -642,10 +761,11 @@ int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data); // Returns a negative error code on failure. int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg); #endif +#endif #ifdef __cplusplus } /* extern "C" */ #endif -#endif +#endif \ No newline at end of file diff --git a/littlefs/lfs_util.c b/littlefs/lfs_util.c index 0b60e3b..49c90ff 100644 --- a/littlefs/lfs_util.c +++ b/littlefs/lfs_util.c @@ -1,6 +1,7 @@ /* * lfs util functions * + * Copyright (c) 2022, The littlefs authors. * Copyright (c) 2017, Arm Limited. All rights reserved. * SPDX-License-Identifier: BSD-3-Clause */ @@ -30,4 +31,4 @@ uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size) { } -#endif +#endif \ No newline at end of file diff --git a/littlefs/lfs_util.h b/littlefs/lfs_util.h index 14da311..ee1aad9 100644 --- a/littlefs/lfs_util.h +++ b/littlefs/lfs_util.h @@ -1,12 +1,19 @@ /* * lfs utility functions * + * Copyright (c) 2022, The littlefs authors. * Copyright (c) 2017, Arm Limited. All rights reserved. * SPDX-License-Identifier: BSD-3-Clause */ #ifndef LFS_UTIL_H #define LFS_UTIL_H +// For some reason, the TinyGo compiler needs this to be defined here: +#define LFS_NO_DEBUG 1 +#define LFS_NO_ASSERT 1 +#define LFS_NO_ERROR 1 +#define LFS_NO_WARN 1 + // Users can override lfs_util.h with their own configuration by defining // LFS_CONFIG as a header file to include (-DLFS_CONFIG=lfs_config.h). // @@ -28,12 +35,12 @@ #ifndef LFS_NO_MALLOC #include #endif -#ifdef LFS_YES_ASSERT +#ifndef LFS_NO_ASSERT #include #endif -#if defined(LFS_YES_DEBUG) || \ - defined(LFS_YES_WARN) || \ - defined(LFS_YES_ERROR) || \ +#if !defined(LFS_NO_DEBUG) || \ + !defined(LFS_NO_WARN) || \ + !defined(LFS_NO_ERROR) || \ defined(LFS_YES_TRACE) #include #endif @@ -49,40 +56,54 @@ extern "C" // code footprint // Logging functions +#ifndef LFS_TRACE #ifdef LFS_YES_TRACE -#define LFS_TRACE(fmt, ...) \ - printf("lfs_trace:%d: " fmt "\n", __LINE__, __VA_ARGS__) +#define LFS_TRACE_(fmt, ...) \ + printf("%s:%d:trace: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) +#define LFS_TRACE(...) LFS_TRACE_(__VA_ARGS__, "") #else -#define LFS_TRACE(fmt, ...) +#define LFS_TRACE(...) +#endif #endif -#ifdef LFS_YES_DEBUG -#define LFS_DEBUG(fmt, ...) \ - printf("lfs_debug:%d: " fmt "\n", __LINE__, __VA_ARGS__) +#ifndef LFS_DEBUG +#ifndef LFS_NO_DEBUG +#define LFS_DEBUG_(fmt, ...) \ + printf("%s:%d:debug: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) +#define LFS_DEBUG(...) LFS_DEBUG_(__VA_ARGS__, "") #else -#define LFS_DEBUG(fmt, ...) +#define LFS_DEBUG(...) +#endif #endif -#ifdef LFS_YES_WARN -#define LFS_WARN(fmt, ...) \ - printf("lfs_warn:%d: " fmt "\n", __LINE__, __VA_ARGS__) +#ifndef LFS_WARN +#ifndef LFS_NO_WARN +#define LFS_WARN_(fmt, ...) \ + printf("%s:%d:warn: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) +#define LFS_WARN(...) LFS_WARN_(__VA_ARGS__, "") #else -#define LFS_WARN(fmt, ...) +#define LFS_WARN(...) +#endif #endif -#ifdef LFS_YES_ERROR -#define LFS_ERROR(fmt, ...) \ - printf("lfs_error:%d: " fmt "\n", __LINE__, __VA_ARGS__) +#ifndef LFS_ERROR +#ifndef LFS_NO_ERROR +#define LFS_ERROR_(fmt, ...) \ + printf("%s:%d:error: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) +#define LFS_ERROR(...) LFS_ERROR_(__VA_ARGS__, "") #else -#define LFS_ERROR(fmt, ...) +#define LFS_ERROR(...) +#endif #endif // Runtime assertions -#ifdef LFS_YES_ASSERT +#ifndef LFS_ASSERT +#ifndef LFS_NO_ASSERT #define LFS_ASSERT(test) assert(test) #else #define LFS_ASSERT(test) #endif +#endif // Builtin functions, these may be replaced by more efficient @@ -107,7 +128,7 @@ static inline uint32_t lfs_alignup(uint32_t a, uint32_t alignment) { return lfs_aligndown(a + alignment-1, alignment); } -// Find the next smallest power of 2 less than or equal to a +// Find the smallest power of 2 greater than or equal to a static inline uint32_t lfs_npw2(uint32_t a) { #if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) return 32 - __builtin_clz(a-1); @@ -152,10 +173,9 @@ static inline int lfs_scmp(uint32_t a, uint32_t b) { // Convert between 32-bit little-endian and native order static inline uint32_t lfs_fromle32(uint32_t a) { -#if !defined(LFS_NO_INTRINSICS) && ( \ - (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ +#if (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ (defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ - (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) + (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) return a; #elif !defined(LFS_NO_INTRINSICS) && ( \ (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ @@ -181,10 +201,9 @@ static inline uint32_t lfs_frombe32(uint32_t a) { (defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) return __builtin_bswap32(a); -#elif !defined(LFS_NO_INTRINSICS) && ( \ - (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ +#elif (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ (defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ - (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) + (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) return a; #else return (((uint8_t*)&a)[0] << 24) | @@ -227,4 +246,4 @@ static inline void lfs_free(void *p) { #endif #endif -#endif +#endif \ No newline at end of file From d80c44a1fba1056aee3b6ae95c5425733bc9d5c1 Mon Sep 17 00:00:00 2001 From: BCG Date: Sun, 10 Dec 2023 20:29:55 -0500 Subject: [PATCH 30/41] Adding sdcard example for fatfs --- examples/console/fatfs/sdcard/feather-m4.go | 17 +++++++ .../console/fatfs/sdcard/grandcentral-m4.go | 17 +++++++ examples/console/fatfs/sdcard/m0.go | 17 +++++++ examples/console/fatfs/sdcard/main.go | 50 +++++++++++++++++++ examples/console/fatfs/sdcard/p1am-100.go | 17 +++++++ examples/console/fatfs/sdcard/pygamer.go | 17 +++++++ examples/console/fatfs/sdcard/pyportal.go | 17 +++++++ .../console/fatfs/sdcard/thingplus-rp2040.go | 17 +++++++ examples/console/fatfs/sdcard/wioterminal.go | 17 +++++++ 9 files changed, 186 insertions(+) create mode 100644 examples/console/fatfs/sdcard/feather-m4.go create mode 100644 examples/console/fatfs/sdcard/grandcentral-m4.go create mode 100644 examples/console/fatfs/sdcard/m0.go create mode 100644 examples/console/fatfs/sdcard/main.go create mode 100644 examples/console/fatfs/sdcard/p1am-100.go create mode 100644 examples/console/fatfs/sdcard/pygamer.go create mode 100644 examples/console/fatfs/sdcard/pyportal.go create mode 100644 examples/console/fatfs/sdcard/thingplus-rp2040.go create mode 100644 examples/console/fatfs/sdcard/wioterminal.go diff --git a/examples/console/fatfs/sdcard/feather-m4.go b/examples/console/fatfs/sdcard/feather-m4.go new file mode 100644 index 0000000..258aac5 --- /dev/null +++ b/examples/console/fatfs/sdcard/feather-m4.go @@ -0,0 +1,17 @@ +//go:build feather_m4 || feather_m4_can || feather_nrf52840 + +package main + +import ( + "machine" +) + +func init() { + spi = &machine.SPI0 + sckPin = machine.SPI0_SCK_PIN + sdoPin = machine.SPI0_SDO_PIN + sdiPin = machine.SPI0_SDI_PIN + csPin = machine.D10 + + ledPin = machine.LED +} diff --git a/examples/console/fatfs/sdcard/grandcentral-m4.go b/examples/console/fatfs/sdcard/grandcentral-m4.go new file mode 100644 index 0000000..51eb5c2 --- /dev/null +++ b/examples/console/fatfs/sdcard/grandcentral-m4.go @@ -0,0 +1,17 @@ +//go:build grandcentral_m4 + +package main + +import ( + "machine" +) + +func init() { + spi = &machine.SPI1 + sckPin = machine.SDCARD_SCK_PIN + sdoPin = machine.SDCARD_SDO_PIN + sdiPin = machine.SDCARD_SDI_PIN + csPin = machine.SDCARD_CS_PIN + + ledPin = machine.LED +} diff --git a/examples/console/fatfs/sdcard/m0.go b/examples/console/fatfs/sdcard/m0.go new file mode 100644 index 0000000..5ba5391 --- /dev/null +++ b/examples/console/fatfs/sdcard/m0.go @@ -0,0 +1,17 @@ +//go:build atsamd21 && !p1am_100 + +package main + +import ( + "machine" +) + +func init() { + spi = &machine.SPI0 + sckPin = machine.SPI0_SCK_PIN + sdoPin = machine.SPI0_SDO_PIN + sdiPin = machine.SPI0_SDI_PIN + csPin = machine.D2 + + ledPin = machine.LED +} diff --git a/examples/console/fatfs/sdcard/main.go b/examples/console/fatfs/sdcard/main.go new file mode 100644 index 0000000..028fefa --- /dev/null +++ b/examples/console/fatfs/sdcard/main.go @@ -0,0 +1,50 @@ +package main + +import ( + "fmt" + "machine" + "time" + + "tinygo.org/x/drivers/sdcard" + "tinygo.org/x/tinyfs/examples/console" + "tinygo.org/x/tinyfs/fatfs" +) + +var ( + spi *machine.SPI + sckPin machine.Pin + sdoPin machine.Pin + sdiPin machine.Pin + csPin machine.Pin + ledPin machine.Pin +) + +func main() { + fmt.Printf("sdcard console\r\n") + + led := ledPin + led.Configure(machine.PinConfig{Mode: machine.PinOutput}) + + sd := sdcard.New(spi, sckPin, sdoPin, sdiPin, csPin) + err := sd.Configure() + if err != nil { + fmt.Printf("%s\r\n", err.Error()) + for { + time.Sleep(time.Hour) + } + } + + filesystem := fatfs.New(&sd) + filesystem.Configure(&fatfs.Config{ + SectorSize: 512, + }) + + go console.RunFor(&sd, filesystem) + + for { + led.High() + time.Sleep(200 * time.Millisecond) + led.Low() + time.Sleep(200 * time.Millisecond) + } +} diff --git a/examples/console/fatfs/sdcard/p1am-100.go b/examples/console/fatfs/sdcard/p1am-100.go new file mode 100644 index 0000000..b1acb62 --- /dev/null +++ b/examples/console/fatfs/sdcard/p1am-100.go @@ -0,0 +1,17 @@ +//go:build p1am_100 + +package main + +import ( + "machine" +) + +func init() { + spi = &machine.SDCARD_SPI + sckPin = machine.SDCARD_SCK_PIN + sdoPin = machine.SDCARD_SDO_PIN + sdiPin = machine.SDCARD_SDI_PIN + csPin = machine.SDCARD_SS_PIN + + ledPin = machine.LED +} diff --git a/examples/console/fatfs/sdcard/pygamer.go b/examples/console/fatfs/sdcard/pygamer.go new file mode 100644 index 0000000..3518244 --- /dev/null +++ b/examples/console/fatfs/sdcard/pygamer.go @@ -0,0 +1,17 @@ +//go:build pygamer + +package main + +import ( + "machine" +) + +func init() { + spi = &machine.SPI0 + sckPin = machine.SPI0_SCK_PIN + sdoPin = machine.SPI0_SDO_PIN + sdiPin = machine.SPI0_SDI_PIN + csPin = machine.D4 + + ledPin = machine.LED +} diff --git a/examples/console/fatfs/sdcard/pyportal.go b/examples/console/fatfs/sdcard/pyportal.go new file mode 100644 index 0000000..9a8accb --- /dev/null +++ b/examples/console/fatfs/sdcard/pyportal.go @@ -0,0 +1,17 @@ +//go:build pyportal + +package main + +import ( + "machine" +) + +func init() { + spi = &machine.SPI0 + sckPin = machine.SPI0_SCK_PIN + sdoPin = machine.SPI0_SDO_PIN + sdiPin = machine.SPI0_SDI_PIN + csPin = machine.D32 // SD_CS + + ledPin = machine.LED +} diff --git a/examples/console/fatfs/sdcard/thingplus-rp2040.go b/examples/console/fatfs/sdcard/thingplus-rp2040.go new file mode 100644 index 0000000..e132e0f --- /dev/null +++ b/examples/console/fatfs/sdcard/thingplus-rp2040.go @@ -0,0 +1,17 @@ +//go:build thingplus_rp2040 + +package main + +import ( + "machine" +) + +func init() { + spi = machine.SPI1 + sckPin = machine.SPI1_SCK_PIN + sdoPin = machine.SPI1_SDO_PIN + sdiPin = machine.SPI1_SDI_PIN + csPin = machine.GPIO9 + + ledPin = machine.LED +} diff --git a/examples/console/fatfs/sdcard/wioterminal.go b/examples/console/fatfs/sdcard/wioterminal.go new file mode 100644 index 0000000..96019e3 --- /dev/null +++ b/examples/console/fatfs/sdcard/wioterminal.go @@ -0,0 +1,17 @@ +//go:build wioterminal + +package main + +import ( + "machine" +) + +func init() { + spi = &machine.SPI2 + sckPin = machine.SCK2 + sdoPin = machine.SDO2 + sdiPin = machine.SDI2 + csPin = machine.SS2 + + ledPin = machine.LED +} From 32ae3f6bbad99dbba222a2182c085d653e8376dc Mon Sep 17 00:00:00 2001 From: BCG Date: Sun, 10 Dec 2023 20:39:23 -0500 Subject: [PATCH 31/41] adding smoke test for sdcard fatfs example --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 7f883b6..768e7f5 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,8 @@ smoke-test: @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=itsybitsy-m4 ./examples/console/fatfs/qspi/ @md5sum ./build/test.hex + tinygo build -size short -o ./build/test.hex -target=feather-m4 ./examples/console/fatfs/sdcard/ + @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/console/littlefs/spi/ @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=itsybitsy-m4 ./examples/console/littlefs/qspi/ From ec3dd03a07299c0a6a61496867004cd89e30851c Mon Sep 17 00:00:00 2001 From: deadprogram Date: Mon, 26 Feb 2024 20:14:32 +0100 Subject: [PATCH 32/41] modules: update to drivers 0.27.0 Signed-off-by: deadprogram --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 358c853..be9590f 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,4 @@ module tinygo.org/x/tinyfs go 1.18 -require tinygo.org/x/drivers v0.26.1-0.20231206190939-3fabdc5c9680 +require tinygo.org/x/drivers v0.27.0 diff --git a/go.sum b/go.sum index d377570..cae9e5e 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,2 @@ -tinygo.org/x/drivers v0.26.1-0.20231206190939-3fabdc5c9680 h1:S7FwtuTMSkyEjF1cgl3AFlnBR940GYgCMSADm0U8e7o= -tinygo.org/x/drivers v0.26.1-0.20231206190939-3fabdc5c9680/go.mod h1:q/mU8G/wz821p8xXqbkBACOlmZFDHXd//DnYnCW+dDQ= +tinygo.org/x/drivers v0.27.0 h1:TEGk1lQvEhXxfvpEhUu+pwmCnhtldPI+hpHlO9VYixI= +tinygo.org/x/drivers v0.27.0/go.mod h1:q/mU8G/wz821p8xXqbkBACOlmZFDHXd//DnYnCW+dDQ= From 167069107db99e3a7cc31770db6b7477a42e5531 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Mon, 26 Feb 2024 20:18:04 +0100 Subject: [PATCH 33/41] docs: update LICENSE year Signed-off-by: deadprogram --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 658115a..40bc82b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018-2023 The TinyGo Authors. All rights reserved. +Copyright (c) 2018-2024 The TinyGo Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are From 925879dc2ea18cf54550da4ae5f2f5544d7d4809 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Mon, 26 Feb 2024 20:18:40 +0100 Subject: [PATCH 34/41] Update for v0.4.0 Signed-off-by: deadprogram --- CHANGELOG.md | 9 +++++++++ version.go | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c44d1ca..80be4bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +0.4.0 +--- +- **littlefs** + - update to littlefs 2.8.1 +- **examples** + - Adding sdcard example for fatfs +- **modules** + - update to latest version of tinygo drivers 0.27.0 + 0.3.0 --- - **examples** diff --git a/version.go b/version.go index cace609..535c6d1 100644 --- a/version.go +++ b/version.go @@ -2,4 +2,4 @@ package tinyfs // Version returns a user-readable string showing the version of the package for support purposes. // Update this value before release of new version of software. -const Version = "0.3.0" +const Version = "0.4.0" From cee98a6a8726c0c7f10d41555af902fd4e964c4d Mon Sep 17 00:00:00 2001 From: BCG Date: Tue, 5 Mar 2024 23:35:26 -0500 Subject: [PATCH 35/41] Adding Stat() method to tinyfs.File interface and the corresponding littlefs and fatfs implementations. Also adding NewTinyFS() method to create instances of the fs.FS interface. --- Makefile | 1 + examples/simple-fatfs/block_device_file.go | 20 +++++-- examples/simple-fatfs/main.go | 61 +++++++++++++++++----- fatfs/go_fatfs.go | 26 +++++---- fatfs/go_fatfs_test.go | 32 ++++++++++++ fs.go | 20 +++++++ littlefs/go_lfs.go | 31 ++++++----- littlefs/go_lfs_test.go | 2 + tinyfs.go | 1 + 9 files changed, 153 insertions(+), 41 deletions(-) create mode 100644 fs.go diff --git a/Makefile b/Makefile index 768e7f5..825349a 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,7 @@ fmt-check: @unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1 smoke-test: + go run ./examples/simple-fatfs @mkdir -p build tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/simple-fatfs/ @md5sum ./build/test.hex diff --git a/examples/simple-fatfs/block_device_file.go b/examples/simple-fatfs/block_device_file.go index cc89d70..1f62b6c 100644 --- a/examples/simple-fatfs/block_device_file.go +++ b/examples/simple-fatfs/block_device_file.go @@ -5,21 +5,27 @@ package main import ( "os" + + "tinygo.org/x/tinyfs" + "tinygo.org/x/tinyfs/fatfs" ) // FileBlockDevice is a block device implementation backed by a byte slice type FileBlockDevice struct { file *os.File blankBlock []byte + pageSize uint32 blockSize uint32 blockCount uint32 } -var _ BlockDevice = (*FileBlockDevice)(nil) +var _ tinyfs.BlockDevice = (*FileBlockDevice)(nil) -func NewFileDevice(file *os.File, blockSize int, blockCount int) *FileBlockDevice { +func NewFileDevice(file *os.File, pageSize, blockSize int, blockCount int) *FileBlockDevice { dev := &FileBlockDevice{ + file: file, blankBlock: make([]byte, blockSize), + pageSize: uint32(pageSize), blockSize: uint32(blockSize), blockCount: uint32(blockCount), } @@ -40,11 +46,15 @@ func (bd *FileBlockDevice) Size() int64 { } func (bd *FileBlockDevice) SectorSize() int { - return SectorSize + return fatfs.SectorSize +} + +func (bd *FileBlockDevice) WriteBlockSize() int64 { + return int64(bd.pageSize) } -func (bd *FileBlockDevice) EraseBlockSize() int { - return int(bd.blockSize) +func (bd *FileBlockDevice) EraseBlockSize() int64 { + return int64(bd.blockSize) } func (bd *FileBlockDevice) EraseBlocks(start int64, len int64) error { diff --git a/examples/simple-fatfs/main.go b/examples/simple-fatfs/main.go index f89fe8d..b437ed6 100644 --- a/examples/simple-fatfs/main.go +++ b/examples/simple-fatfs/main.go @@ -3,6 +3,7 @@ package main import ( "fmt" "io" + "io/fs" "os" "tinygo.org/x/tinyfs" @@ -12,24 +13,26 @@ import ( func main() { // create/format/mount the filesystem - fs := fatfs.New(tinyfs.NewMemoryDevice(4096, 64, 64)) - if err := fs.Format(); err != nil { + fat := fatfs.New(tinyfs.NewMemoryDevice(64, 256, 4096)) + fat.Configure(&fatfs.Config{SectorSize: 512}) + + if err := fat.Format(); err != nil { fmt.Println("Could not format", err) os.Exit(1) } - if err := fs.Mount(); err != nil { + if err := fat.Mount(); err != nil { fmt.Println("Could not mount", err) os.Exit(1) } defer func() { - if err := fs.Unmount(); err != nil { + if err := fat.Unmount(); err != nil { fmt.Println("Could not ummount", err) os.Exit(1) } }() // test an invalid operation to make sure it returns an appropriate error - if err := fs.Rename("test.txt", "test2.txt"); err == nil { + if err := fat.Rename("test.txt", "test2.txt"); err == nil { fmt.Println("Could not rename file (as expected):", err) os.Exit(1) } @@ -38,20 +41,20 @@ func main() { path := "/tmp" fmt.Println("making directory", path) - if err := fs.Mkdir(path, 0777); err != nil { + if err := fat.Mkdir(path, 0777); err != nil { fmt.Println("Could not create "+path+" dir", err) os.Exit(1) } filepath := path + "/test.txt" fmt.Println("opening file", filepath) - f, err := fs.OpenFile(filepath, os.O_CREATE|os.O_WRONLY) + f, err := fat.OpenFile(filepath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY) if err != nil { fmt.Println("Could not open file", err) os.Exit(1) } - size, err := fs.Free() + size, err := fat.Free() if err != nil { fmt.Println("Could not get filesystem free:", err.Error()) } else { @@ -79,7 +82,7 @@ func main() { os.Exit(1) } - if stat, err := fs.Stat(path); err != nil { + if stat, err := fat.Stat(path); err != nil { fmt.Println("Could not stat dir", err) os.Exit(1) } else { @@ -88,7 +91,7 @@ func main() { stat.Name(), stat.Size(), stat.IsDir()) } - if stat, err := fs.Stat(filepath); err != nil { + if stat, err := fat.Stat(filepath); err != nil { fmt.Println("Could not stat file", err) os.Exit(1) } else { @@ -98,13 +101,23 @@ func main() { } fmt.Println("opening file read only") - f, err = fs.OpenFile(filepath, os.O_RDONLY) + f, err = fat.OpenFile(filepath, os.O_RDONLY) if err != nil { fmt.Println("Could not open file", err) os.Exit(1) } defer f.Close() + fmt.Println("calling File.Stat()") + info, err := f.Stat() + if err != nil { + fmt.Println("Could not stat file", err) + os.Exit(1) + } else { + fmt.Printf( + "file info: name=%s size=%d dir=%t\n", + info.Name(), info.Size(), info.IsDir()) + } /* if size, err := f.Size(); err != nil { fmt.Printf("Failed getting file size: %v\n", err) @@ -130,17 +143,17 @@ func main() { } break } - fmt.Printf("read %d bytes from file: `%s`", n, string(buf[:n])) + fmt.Printf("read %d bytes from file: `%s`\n", n, string(buf[:n])) } - size, err = fs.Free() + size, err = fat.Free() if err != nil { fmt.Println("Could not get filesystem free:", err.Error()) } else { fmt.Println("Filesystem free:", size) } - dir, err := fs.Open("tmp") + dir, err := fat.Open("tmp") if err != nil { fmt.Printf("Could not open directory %s: %v\n", path, err) os.Exit(1) @@ -155,6 +168,26 @@ func main() { for _, info := range infos { fmt.Printf(" directory entry: %s %d %t\n", info.Name(), info.Size(), info.IsDir()) } + + // exercise fs.FS interfaces: + { + var fsfs fs.FS = tinyfs.NewTinyFS(fat) + f, err := fsfs.Open(filepath) + if err != nil { + fmt.Printf("Could not open %s via io/fs package: %v\n", filepath, err) + os.Exit(1) + } + info, err := f.Stat() + if err != nil { + fmt.Println("Could not stat file", err) + os.Exit(1) + } else { + fmt.Printf( + "fsfs info: name=%s size=%d dir=%t\n", + info.Name(), info.Size(), info.IsDir()) + } + } + fmt.Println("done") return } diff --git a/fatfs/go_fatfs.go b/fatfs/go_fatfs.go index 65c023f..5ec867d 100644 --- a/fatfs/go_fatfs.go +++ b/fatfs/go_fatfs.go @@ -141,23 +141,23 @@ type Info struct { var _ os.FileInfo = (*Info)(nil) -func (info *Info) Name() string { +func (info Info) Name() string { return info.name } -func (info *Info) Size() int64 { +func (info Info) Size() int64 { return info.size } -func (info *Info) IsDir() bool { +func (info Info) IsDir() bool { return (info.attr & AttrDirectory) > 0 } -func (info *Info) Sys() interface{} { +func (info Info) Sys() interface{} { return nil } -func (info *Info) Mode() os.FileMode { +func (info Info) Mode() os.FileMode { v := os.FileMode(0777) if info.IsDir() { v |= os.ModeDir @@ -165,7 +165,7 @@ func (info *Info) Mode() os.FileMode { return v } -func (info *Info) ModTime() time.Time { +func (info Info) ModTime() time.Time { return time.Time{} } @@ -263,7 +263,11 @@ func (l *FATFS) OpenFile(path string, flags int) (tinyfs.File, error) { } // use f_open or f_opendir to obtain a handle to the object - var file = &File{fs: l, name: path} + var file = &File{fs: l, info: Info{ + name: path, + size: int64(info.fsize), + attr: FileAttr(info.fattrib), + }} var errno C.FRESULT if path == "/" || info.fattrib&C.AM_DIR > 0 { // directory @@ -323,7 +327,7 @@ type File struct { fs *FATFS typ uint8 hndl unsafe.Pointer - name string + info Info } func (f *File) dirptr() *C.FF_DIR { @@ -336,7 +340,7 @@ func (f *File) fileptr() *C.FIL { // Name returns the name of the file as presented to OpenFile func (f *File) Name() string { - return f.name + return f.info.name } func (f *File) Close() error { @@ -407,6 +411,10 @@ func (f *File) Size() (int64, error) { } } +func (f *File) Stat() (os.FileInfo, error) { + return f.info, nil +} + // Synchronize a file on storage // // Any pending writes are written out to storage. diff --git a/fatfs/go_fatfs_test.go b/fatfs/go_fatfs_test.go index 39d1669..5c923dd 100644 --- a/fatfs/go_fatfs_test.go +++ b/fatfs/go_fatfs_test.go @@ -1,6 +1,9 @@ +//go:build !tinygo + package fatfs import ( + "os" "testing" "tinygo.org/x/tinyfs" @@ -36,6 +39,7 @@ func createTestFS(t *testing.T) (*FATFS, tinyfs.BlockDevice, func()) { dev := tinyfs.NewMemoryDevice(testPageSize, testBlockSize, testBlockCount) fs := New(dev) println("formatting") + fs.Configure(&Config{SectorSize: 512}) //check(t, fs.Configure()) if err := fs.Format(); err != nil { t.Fatal(err) @@ -78,6 +82,34 @@ func TestDirectories(t *testing.T) { }) } +func TestFiles(t *testing.T) { + t.Run("BasicFile", func(t *testing.T) { + fs, _, unmount := createTestFS(t) + defer unmount() + f, err := fs.OpenFile("hello_world.txt", os.O_CREATE|os.O_WRONLY|os.O_TRUNC) + check(t, err) + n, err := f.Write([]byte("hello world")) + check(t, err) + check(t, f.Close()) + if n != 11 { + t.Fail() + } + f, err = fs.Open("hello_world.txt") + check(t, err) + info, err := f.Stat() + check(t, err) + if info.IsDir() { + t.Fail() + } + if info.Name() != "hello_world.txt" { + t.Fail() + } + if info.Size() != 11 { + t.Fail() + } + }) +} + func expectString(t *testing.T, expected string, actual string) { if expected != actual { t.Fatalf("expected \"%s\", was actually \"%s\"", expected, actual) diff --git a/fs.go b/fs.go new file mode 100644 index 0000000..dcd55a7 --- /dev/null +++ b/fs.go @@ -0,0 +1,20 @@ +package tinyfs + +import ( + "io/fs" +) + +func NewTinyFS(filesystem Filesystem) fs.FS { + return &fsWrapper{fs: filesystem} +} + +type fsWrapper struct { + fs Filesystem +} + +func (w *fsWrapper) Open(name string) (f fs.File, err error) { + return w.fs.Open(name) +} + +// type assertion to ensure tinyfs.File satisfies fs.File +var _ fs.File = (File)(nil) diff --git a/littlefs/go_lfs.go b/littlefs/go_lfs.go index bdd285d..1e94f5d 100644 --- a/littlefs/go_lfs.go +++ b/littlefs/go_lfs.go @@ -119,23 +119,23 @@ type Info struct { name string } -func (info *Info) Name() string { +func (info Info) Name() string { return info.name } -func (info *Info) Size() int64 { +func (info Info) Size() int64 { return int64(info.size) } -func (info *Info) IsDir() bool { +func (info Info) IsDir() bool { return info.ftyp == fileTypeDir } -func (info *Info) Sys() interface{} { +func (info Info) Sys() interface{} { return nil } -func (info *Info) Mode() os.FileMode { +func (info Info) Mode() os.FileMode { v := os.FileMode(0777) if info.IsDir() { v |= os.ModeDir @@ -143,7 +143,7 @@ func (info *Info) Mode() os.FileMode { return v } -func (info *Info) ModTime() time.Time { +func (info Info) ModTime() time.Time { return time.Time{} } @@ -234,16 +234,16 @@ func (l *LFS) OpenFile(path string, flags int) (tinyfs.File, error) { cs := (*C.char)(cstring(path)) defer C.free(unsafe.Pointer(cs)) - file := &File{lfs: l, name: path} + file := &File{lfs: l, info: Info{name: path}} - var ftype fileType info := C.struct_lfs_info{} if err := errval(C.lfs_stat(l.lfs, cs, &info)); err == nil { - ftype = fileType(info._type) + file.info.ftyp = fileType(info._type) + file.info.size = uint32(info.size) } var errno C.int - if ftype == fileTypeDir { + if file.info.ftyp == fileTypeDir { file.typ = fileTypeDir file.hndl = unsafe.Pointer(C.go_lfs_new_lfs_dir()) errno = C.lfs_dir_open(l.lfs, file.dirptr(), cs) @@ -282,7 +282,7 @@ type File struct { lfs *LFS typ fileType hndl unsafe.Pointer - name string + info Info } func (f *File) dirptr() *C.struct_lfs_dir { @@ -295,7 +295,7 @@ func (f *File) fileptr() *C.struct_lfs_file { // Name returns the name of the file as presented to OpenFile func (f *File) Name() string { - return f.name + return f.info.name } // Close the file; any pending writes are written out to storage @@ -366,6 +366,11 @@ func (f *File) Size() (int64, error) { return int64(errno), nil } +// Stat satisfies the `fs.File` interface +func (f *File) Stat() (os.FileInfo, error) { + return f.info, nil +} + // Sync synchronizes to storage so that any pending writes are written out. func (f *File) Sync() error { return errval(C.lfs_file_sync(f.lfs.lfs, f.fileptr())) @@ -412,7 +417,7 @@ func (f *File) Readdir(n int) (infos []os.FileInfo, err error) { if name == "." || name == ".." { continue // littlefs returns . and .., but Readdir() in Go does not } - infos = append(infos, &Info{ + infos = append(infos, Info{ ftyp: fileType(info._type), size: uint32(info.size), name: name, diff --git a/littlefs/go_lfs_test.go b/littlefs/go_lfs_test.go index 617e0e0..9d41735 100644 --- a/littlefs/go_lfs_test.go +++ b/littlefs/go_lfs_test.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package littlefs import ( diff --git a/tinyfs.go b/tinyfs.go index 9a9602c..ac0108d 100644 --- a/tinyfs.go +++ b/tinyfs.go @@ -25,6 +25,7 @@ type File interface { FileHandle IsDir() bool Readdir(n int) (infos []os.FileInfo, err error) + Stat() (info os.FileInfo, err error) } // FileHandle is a copy of the experimental os.FileHandle interface in TinyGo From 2b9db5013cc694e334100d53d71a680720682213 Mon Sep 17 00:00:00 2001 From: BCG Date: Sat, 9 Mar 2024 21:09:09 -0500 Subject: [PATCH 36/41] Adding io.Seeker interface to tinyfs.File --- examples/simple-fatfs/main.go | 14 ++++++++++++++ fatfs/go_fatfs.go | 28 ++++++++++++++++++++++------ tinyfs.go | 1 + 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/examples/simple-fatfs/main.go b/examples/simple-fatfs/main.go index b437ed6..a596240 100644 --- a/examples/simple-fatfs/main.go +++ b/examples/simple-fatfs/main.go @@ -146,6 +146,20 @@ func main() { fmt.Printf("read %d bytes from file: `%s`\n", n, string(buf[:n])) } + offset, err := f.Seek(8, io.SeekStart) + if err != nil { + fmt.Println("Could not Seek(): ", err) + } else { + fmt.Println("new offset:", offset) + n, err := f.Read(buf) + if err != nil { + if err != io.EOF { + fmt.Printf("f.Read() error: %v\n", err.Error()) + } + } + fmt.Printf("read %d bytes from file: `%s`\n", n, string(buf[:n])) + } + size, err = fat.Free() if err != nil { fmt.Println("Could not get filesystem free:", err.Error()) diff --git a/fatfs/go_fatfs.go b/fatfs/go_fatfs.go index 5ec867d..ec54b96 100644 --- a/fatfs/go_fatfs.go +++ b/fatfs/go_fatfs.go @@ -38,6 +38,7 @@ const ( FileResultTooManyOpenFiles FileResult = C.FR_TOO_MANY_OPEN_FILES FileResultInvalidParameter FileResult = C.FR_INVALID_PARAMETER FileResultReadOnly FileResult = 99 + FileResultNotImplemented FileResult = 0xe0 // tinyfs custom error TypeFAT12 Type = C.FS_FAT12 TypeFAT16 Type = C.FS_FAT16 @@ -125,6 +126,8 @@ func (r FileResult) Error() string { msg = "(19) Given parameter is invalid" case FileResultReadOnly: msg = "(99) Read-only filesystem" + case FileResultNotImplemented: + msg = "(e0) Feature Not Implemented" default: msg = "unknown file result error" } @@ -375,19 +378,32 @@ func (f *File) Read(buf []byte) (n int, err error) { return int(br), nil } -/* // Seek changes the position of the file func (f *File) Seek(offset int64, whence int) (ret int64, err error) { - errno := C.int(C.lfs_file_seek(f.lfs.lfs, &f.fptr, C.lfs_soff_t(offset), C.int(whence))) - if errno < 0 { - return -1, errval(errno) + // FRESULT f_lseek ( + // FIL* fp, /* Pointer to the file object */ + // FSIZE_t ofs /* File pointer from top of file */ + // ) + switch whence { + case io.SeekStart: + case io.SeekCurrent: + return -1, FileResultNotImplemented // FIXME: support these options + case io.SeekEnd: + return -1, FileResultNotImplemented // FIXME: support these options + default: + return -1, FileResultInvalidParameter } - return int64(errno), nil + errno := C.f_lseek(f.fileptr(), C.FSIZE_t(offset)) + if err := errval(errno); err != nil { + return -1, err + } + return offset, nil } +/* // Tell returns the position of the file func (f *File) Tell() (ret int64, err error) { - errno := C.int(C.lfs_file_tell(f.lfs.lfs, &f.fptr)) + errno := C.int(C.f_tell(f.fileptr(), &f.fptr)) if errno < 0 { return -1, errval(errno) } diff --git a/tinyfs.go b/tinyfs.go index ac0108d..8fef0cb 100644 --- a/tinyfs.go +++ b/tinyfs.go @@ -23,6 +23,7 @@ type Filesystem interface { // if/when that is merged and standardized. type File interface { FileHandle + io.Seeker IsDir() bool Readdir(n int) (infos []os.FileInfo, err error) Stat() (info os.FileInfo, err error) From 80318bcc488fe9ad0f38a83957aa84d8d9123001 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Wed, 12 Mar 2025 10:25:45 +0100 Subject: [PATCH 37/41] modules: update to latest tinygo drivers dev branch Signed-off-by: deadprogram --- go.mod | 6 ++++-- go.sum | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index be9590f..92d5696 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,7 @@ module tinygo.org/x/tinyfs -go 1.18 +go 1.22.1 -require tinygo.org/x/drivers v0.27.0 +toolchain go1.24.0 + +require tinygo.org/x/drivers v0.30.1-0.20250311194328-156d6e7c9ce4 diff --git a/go.sum b/go.sum index cae9e5e..1ddc091 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,2 @@ -tinygo.org/x/drivers v0.27.0 h1:TEGk1lQvEhXxfvpEhUu+pwmCnhtldPI+hpHlO9VYixI= -tinygo.org/x/drivers v0.27.0/go.mod h1:q/mU8G/wz821p8xXqbkBACOlmZFDHXd//DnYnCW+dDQ= +tinygo.org/x/drivers v0.30.1-0.20250311194328-156d6e7c9ce4 h1:cH7LCCxUrVltIloAWWsRLXPB8V+/doUFkM4Y8RLnpg0= +tinygo.org/x/drivers v0.30.1-0.20250311194328-156d6e7c9ce4/go.mod h1:ZdErNrApSABdVXjA1RejD67R8SNRI6RKVfYgQDZtKtk= From 6036a83c41d4c7672444d284c1d9bc23a4b2908e Mon Sep 17 00:00:00 2001 From: deadprogram Date: Wed, 12 Mar 2025 10:26:16 +0100 Subject: [PATCH 38/41] examples: updates needed for latest tinygo/drivers using SPI pointers Signed-off-by: deadprogram --- examples/console/fatfs/sdcard/feather-m4.go | 2 +- examples/console/fatfs/sdcard/grandcentral-m4.go | 2 +- examples/console/fatfs/sdcard/m0.go | 2 +- examples/console/fatfs/sdcard/p1am-100.go | 2 +- examples/console/fatfs/sdcard/pygamer.go | 2 +- examples/console/fatfs/sdcard/pyportal.go | 2 +- examples/console/fatfs/sdcard/wioterminal.go | 2 +- examples/console/fatfs/spi/main.go | 4 ++-- examples/console/littlefs/spi/main.go | 3 ++- 9 files changed, 11 insertions(+), 10 deletions(-) diff --git a/examples/console/fatfs/sdcard/feather-m4.go b/examples/console/fatfs/sdcard/feather-m4.go index 258aac5..dae1fb0 100644 --- a/examples/console/fatfs/sdcard/feather-m4.go +++ b/examples/console/fatfs/sdcard/feather-m4.go @@ -7,7 +7,7 @@ import ( ) func init() { - spi = &machine.SPI0 + spi = machine.SPI0 sckPin = machine.SPI0_SCK_PIN sdoPin = machine.SPI0_SDO_PIN sdiPin = machine.SPI0_SDI_PIN diff --git a/examples/console/fatfs/sdcard/grandcentral-m4.go b/examples/console/fatfs/sdcard/grandcentral-m4.go index 51eb5c2..ed13803 100644 --- a/examples/console/fatfs/sdcard/grandcentral-m4.go +++ b/examples/console/fatfs/sdcard/grandcentral-m4.go @@ -7,7 +7,7 @@ import ( ) func init() { - spi = &machine.SPI1 + spi = machine.SPI1 sckPin = machine.SDCARD_SCK_PIN sdoPin = machine.SDCARD_SDO_PIN sdiPin = machine.SDCARD_SDI_PIN diff --git a/examples/console/fatfs/sdcard/m0.go b/examples/console/fatfs/sdcard/m0.go index 5ba5391..fcb0b03 100644 --- a/examples/console/fatfs/sdcard/m0.go +++ b/examples/console/fatfs/sdcard/m0.go @@ -7,7 +7,7 @@ import ( ) func init() { - spi = &machine.SPI0 + spi = machine.SPI0 sckPin = machine.SPI0_SCK_PIN sdoPin = machine.SPI0_SDO_PIN sdiPin = machine.SPI0_SDI_PIN diff --git a/examples/console/fatfs/sdcard/p1am-100.go b/examples/console/fatfs/sdcard/p1am-100.go index b1acb62..b003042 100644 --- a/examples/console/fatfs/sdcard/p1am-100.go +++ b/examples/console/fatfs/sdcard/p1am-100.go @@ -7,7 +7,7 @@ import ( ) func init() { - spi = &machine.SDCARD_SPI + spi = machine.SDCARD_SPI sckPin = machine.SDCARD_SCK_PIN sdoPin = machine.SDCARD_SDO_PIN sdiPin = machine.SDCARD_SDI_PIN diff --git a/examples/console/fatfs/sdcard/pygamer.go b/examples/console/fatfs/sdcard/pygamer.go index 3518244..cc1187c 100644 --- a/examples/console/fatfs/sdcard/pygamer.go +++ b/examples/console/fatfs/sdcard/pygamer.go @@ -7,7 +7,7 @@ import ( ) func init() { - spi = &machine.SPI0 + spi = machine.SPI0 sckPin = machine.SPI0_SCK_PIN sdoPin = machine.SPI0_SDO_PIN sdiPin = machine.SPI0_SDI_PIN diff --git a/examples/console/fatfs/sdcard/pyportal.go b/examples/console/fatfs/sdcard/pyportal.go index 9a8accb..3599cdf 100644 --- a/examples/console/fatfs/sdcard/pyportal.go +++ b/examples/console/fatfs/sdcard/pyportal.go @@ -7,7 +7,7 @@ import ( ) func init() { - spi = &machine.SPI0 + spi = machine.SPI0 sckPin = machine.SPI0_SCK_PIN sdoPin = machine.SPI0_SDO_PIN sdiPin = machine.SPI0_SDI_PIN diff --git a/examples/console/fatfs/sdcard/wioterminal.go b/examples/console/fatfs/sdcard/wioterminal.go index 96019e3..934b87a 100644 --- a/examples/console/fatfs/sdcard/wioterminal.go +++ b/examples/console/fatfs/sdcard/wioterminal.go @@ -7,7 +7,7 @@ import ( ) func init() { - spi = &machine.SPI2 + spi = machine.SPI2 sckPin = machine.SCK2 sdoPin = machine.SDO2 sdiPin = machine.SDI2 diff --git a/examples/console/fatfs/spi/main.go b/examples/console/fatfs/spi/main.go index e4f562a..1453203 100644 --- a/examples/console/fatfs/spi/main.go +++ b/examples/console/fatfs/spi/main.go @@ -1,4 +1,4 @@ -// +build tinygo +//go:build tinygo package main @@ -13,7 +13,7 @@ import ( var ( blockDevice = flash.NewSPI( - &machine.SPI1, + machine.SPI1, machine.SPI1_SDO_PIN, machine.SPI1_SDI_PIN, machine.SPI1_SCK_PIN, diff --git a/examples/console/littlefs/spi/main.go b/examples/console/littlefs/spi/main.go index c8e7308..c0a786a 100644 --- a/examples/console/littlefs/spi/main.go +++ b/examples/console/littlefs/spi/main.go @@ -1,3 +1,4 @@ +//go:build tinygo // +build tinygo package main @@ -13,7 +14,7 @@ import ( var ( blockDevice = flash.NewSPI( - &machine.SPI1, + machine.SPI1, machine.SPI1_SDO_PIN, machine.SPI1_SDI_PIN, machine.SPI1_SCK_PIN, From 8b8b43cadd9b31ffa93fd80aa182576057fe6a83 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Wed, 19 Mar 2025 18:12:28 +0100 Subject: [PATCH 39/41] modules: update to drivers 0.31.0 Signed-off-by: deadprogram --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 92d5696..f70cdf9 100644 --- a/go.mod +++ b/go.mod @@ -4,4 +4,4 @@ go 1.22.1 toolchain go1.24.0 -require tinygo.org/x/drivers v0.30.1-0.20250311194328-156d6e7c9ce4 +require tinygo.org/x/drivers v0.31.0 diff --git a/go.sum b/go.sum index 1ddc091..adb414e 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,2 @@ -tinygo.org/x/drivers v0.30.1-0.20250311194328-156d6e7c9ce4 h1:cH7LCCxUrVltIloAWWsRLXPB8V+/doUFkM4Y8RLnpg0= -tinygo.org/x/drivers v0.30.1-0.20250311194328-156d6e7c9ce4/go.mod h1:ZdErNrApSABdVXjA1RejD67R8SNRI6RKVfYgQDZtKtk= +tinygo.org/x/drivers v0.31.0 h1:Q2RpvTRMtdmjHD2Xyn4e8WXsJZKpIny3Lg4hzG1dLu4= +tinygo.org/x/drivers v0.31.0/go.mod h1:ZdErNrApSABdVXjA1RejD67R8SNRI6RKVfYgQDZtKtk= From 03edc1e228d2d2d01a09f2819fb1c5a37f3d4558 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Wed, 19 Mar 2025 18:13:03 +0100 Subject: [PATCH 40/41] license: update year Signed-off-by: deadprogram --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 40bc82b..c4652c1 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018-2024 The TinyGo Authors. All rights reserved. +Copyright (c) 2018-2025 The TinyGo Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are From 4a9250042d75d95cf55e59ea2a49dd283c76885c Mon Sep 17 00:00:00 2001 From: deadprogram Date: Wed, 19 Mar 2025 18:13:33 +0100 Subject: [PATCH 41/41] Release 0.5.0 Signed-off-by: deadprogram --- CHANGELOG.md | 10 ++++++++++ version.go | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80be4bb..fbda171 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +0.5.0 +--- +- **all** + - Adding io.Seeker interface to tinyfs.File + - Adding Stat() method to tinyfs.File interface and the corresponding littlefs and fatfs implementations. Also adding NewTinyFS() method to create instances of the fs.FS interface. +- **examples** + - Update examples for latest driver changes +- **modules** + - update to latest version of tinygo drivers 0.31.0 + 0.4.0 --- - **littlefs** diff --git a/version.go b/version.go index 535c6d1..71444a6 100644 --- a/version.go +++ b/version.go @@ -2,4 +2,4 @@ package tinyfs // Version returns a user-readable string showing the version of the package for support purposes. // Update this value before release of new version of software. -const Version = "0.4.0" +const Version = "0.5.0"