Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 79 additions & 9 deletions littlefs/go_lfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,24 +186,48 @@ func (l *LFS) Mount() error {
}

func (l *LFS) Format() error {
return errval(C.lfs_format(l.lfs, l.cfg))
if err := errval(C.lfs_format(l.lfs, l.cfg)); err != nil {
return err
}
if syncer, ok := l.dev.(tinyfs.Syncer); ok {
return syncer.Sync()
}
return nil
}

func (l *LFS) Unmount() error {
return errval(C.lfs_unmount(l.lfs))
if err := errval(C.lfs_unmount(l.lfs)); err != nil {
return err
}
if syncer, ok := l.dev.(tinyfs.Syncer); ok {
return syncer.Sync()
}
return nil
}

func (l *LFS) Remove(path string) error {
cs := cstring(path)
defer C.free(unsafe.Pointer(cs))
return errval(C.lfs_remove(l.lfs, cs))
if err := errval(C.lfs_remove(l.lfs, cs)); err != nil {
return err
}
if syncer, ok := l.dev.(tinyfs.Syncer); ok {
return syncer.Sync()
}
return nil
}

func (l *LFS) Rename(oldPath string, newPath string) error {
cs1, cs2 := cstring(oldPath), cstring(newPath)
defer C.free(unsafe.Pointer(cs1))
defer C.free(unsafe.Pointer(cs2))
return errval(C.lfs_rename(l.lfs, cs1, cs2))
if err := errval(C.lfs_rename(l.lfs, cs1, cs2)); err != nil {
return err
}
if syncer, ok := l.dev.(tinyfs.Syncer); ok {
return syncer.Sync()
}
return nil
}

func (l *LFS) Stat(path string) (os.FileInfo, error) {
Expand All @@ -223,7 +247,13 @@ func (l *LFS) Stat(path string) (os.FileInfo, error) {
func (l *LFS) Mkdir(path string, _ os.FileMode) error {
cs := (*C.char)(cstring(path))
defer C.free(unsafe.Pointer(cs))
return errval(C.lfs_mkdir(l.lfs, cs))
if err := errval(C.lfs_mkdir(l.lfs, cs)); err != nil {
return err
}
if syncer, ok := l.dev.(tinyfs.Syncer); ok {
return syncer.Sync()
}
return nil
}

func (l *LFS) Open(path string) (tinyfs.File, error) {
Expand Down Expand Up @@ -261,6 +291,24 @@ func (l *LFS) OpenFile(path string, flags int) (tinyfs.File, error) {
return nil, err
}

if flags&(os.O_CREATE|os.O_TRUNC) != 0 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think it makes sense to call Sync() on an Open().

if flags&os.O_TRUNC != 0 {
if err := errval(C.lfs_file_sync(l.lfs, file.fileptr())); err != nil {
file.Close()
return nil, err
}
}
if syncer, ok := l.dev.(tinyfs.Syncer); ok {
if err := syncer.Sync(); err != nil {
// Should we close and return error?
// Maybe just log? But we can't log.
// For now let's just return error and close.
file.Close()
return nil, err
}
}
}

return file, nil
}

Expand Down Expand Up @@ -305,14 +353,21 @@ func (f *File) Close() error {
C.free(f.hndl)
f.hndl = nil
}()
var err error
switch f.typ {
case fileTypeReg:
return errval(C.lfs_file_close(f.lfs.lfs, f.fileptr()))
err = errval(C.lfs_file_close(f.lfs.lfs, f.fileptr()))
case fileTypeDir:
return errval(C.lfs_dir_close(f.lfs.lfs, f.dirptr()))
err = errval(C.lfs_dir_close(f.lfs.lfs, f.dirptr()))
default:
panic("lfs: unknown typ for file handle")
}
if err != nil {
return err
}
if syncer, ok := f.lfs.dev.(tinyfs.Syncer); ok {
return syncer.Sync()
}
}
return nil
}
Expand Down Expand Up @@ -373,12 +428,27 @@ func (f *File) Stat() (os.FileInfo, error) {

// 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()))
if err := errval(C.lfs_file_sync(f.lfs.lfs, f.fileptr())); err != nil {
return err
}
if syncer, ok := f.lfs.dev.(tinyfs.Syncer); ok {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually Sync() on a Sync()? Seems like a yes! 👍

return syncer.Sync()
}
return nil
}

// Truncate the size of the file to the specified size
func (f *File) Truncate(size uint32) error {
return errval(C.lfs_file_truncate(f.lfs.lfs, f.fileptr(), C.lfs_off_t(size)))
if err := errval(C.lfs_file_truncate(f.lfs.lfs, f.fileptr(), C.lfs_off_t(size))); err != nil {
return err
}
if err := errval(C.lfs_file_sync(f.lfs.lfs, f.fileptr())); err != nil {
return err
}
if syncer, ok := f.lfs.dev.(tinyfs.Syncer); ok {
return syncer.Sync()
}
return nil
}

func (f *File) Write(buf []byte) (n int, err error) {
Expand Down
62 changes: 61 additions & 1 deletion littlefs/go_lfs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,15 @@ func TestDirectories(t *testing.T) {
})

t.Run("NestedDirectories", func(t *testing.T) {

check(t, fs.Mkdir("parent", 0777))
check(t, fs.Mkdir("parent/child", 0777))
check(t, fs.Mkdir("parent/child/grandchild", 0777))
// Verify they exist
info, err := fs.Stat("parent/child/grandchild")
check(t, err)
if !info.IsDir() {
t.Error("expected directory")
}
})

t.Run("MultiBlockDirectory", func(t *testing.T) {
Expand Down Expand Up @@ -337,3 +345,55 @@ func check(t *testing.T, err error) {
t.Fatal(err)
}
}

// TestDirectoryPersistence verifies that directories persist after unmount/remount.
// This is a regression test for the Sync() fix that ensures filesystem changes
// are flushed to the underlying block device.
func TestDirectoryPersistence(t *testing.T) {
bd := tinyfs.NewMemoryDevice(testPageSize, testBlockSize, testBlockCount)
fs := New(bd).Configure(defaultConfig)

// Format and mount
if err := fs.Format(); err != nil {
t.Fatalf("Format failed: %v", err)
}
if err := fs.Mount(); err != nil {
t.Fatalf("Mount failed: %v", err)
}

// Create directory
dirName := "persist_test"
if err := fs.Mkdir(dirName, 0755); err != nil {
t.Fatalf("Mkdir failed: %v", err)
}

// Verify immediate existence
info, err := fs.Stat(dirName)
if err != nil {
t.Fatalf("Stat after mkdir failed: %v", err)
}
if !info.IsDir() {
t.Fatalf("Expected directory, got file")
}

// Unmount
if err := fs.Unmount(); err != nil {
t.Fatalf("Unmount failed: %v", err)
}

// Create new filesystem instance and remount (simulates reboot)
fs2 := New(bd).Configure(defaultConfig)
if err := fs2.Mount(); err != nil {
t.Fatalf("Remount failed: %v", err)
}
defer fs2.Unmount()

// Verify directory persisted
info2, err := fs2.Stat(dirName)
if err != nil {
t.Fatalf("Directory '%s' lost after remount: %v", dirName, err)
}
if !info2.IsDir() {
t.Fatalf("'%s' exists but is not a directory", dirName)
}
}
4 changes: 4 additions & 0 deletions littlefs/lfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,10 @@ static int lfs_bd_flush(lfs_t *lfs,
return err;
}

if (pcache->block != LFS_BLOCK_NULL && pcache->block == rcache->block) {
lfs_cache_drop(lfs, rcache);
}

if (validate) {
// check data on disk
lfs_cache_drop(lfs, rcache);
Expand Down