diff --git a/fatfs/go_fatfs_callbacks.go b/fatfs/go_fatfs_callbacks.go index bf0822c..fa4f258 100644 --- a/fatfs/go_fatfs_callbacks.go +++ b/fatfs/go_fatfs_callbacks.go @@ -83,15 +83,30 @@ func go_fatfs_disk_ioctl(drv unsafe.Pointer, cmd uint8, param unsafe.Pointer) in //export go_fatfs_get_fattime func go_fatfs_get_fattime() (t uint32) { + const ( + epochYear = 1980 // years are stored as an offset from 1980 + yearShift = 25 // year offset, bits 25-31 + monthShift = 21 // month, bits 21-24 + dayShift = 16 // day of month, bits 16-20 + hourShift = 11 // hour, bits 11-15 + minuteShift = 5 // minute, bits 5-10 + monthMask = 0x0f // month, 4 bits + dayMask = 0x1f // day of month, 5 bits + hourMask = 0x1f // hour, 5 bits + minuteMask = 0x3f // minute, 6 bits + secondMask = 0x1f // two-second units, 5 bits + secondsPerUnit = 2 // FAT timestamps have two-second resolution + ) + now := time.Now() year, month, day := now.Date() hour, minute, second := now.Hour(), now.Minute(), now.Second() - t |= uint32(year-1980) << 24 - t |= (uint32(month) & 0xF) << 20 - t |= (uint32(day) & 0x1F) << 15 - t |= (uint32(hour) & 0x1F) << 10 - t |= (uint32(minute) & 0x3F) << 4 - t |= (uint32(second) / 2) & 0xF + t |= uint32(year-epochYear) << yearShift + t |= (uint32(month) & monthMask) << monthShift + t |= (uint32(day) & dayMask) << dayShift + t |= (uint32(hour) & hourMask) << hourShift + t |= (uint32(minute) & minuteMask) << minuteShift + t |= (uint32(second) / secondsPerUnit) & secondMask return t } diff --git a/fatfs/go_fatfs_test.go b/fatfs/go_fatfs_test.go index 5c923dd..4454d69 100644 --- a/fatfs/go_fatfs_test.go +++ b/fatfs/go_fatfs_test.go @@ -3,8 +3,10 @@ package fatfs import ( + "encoding/binary" "os" "testing" + "time" "tinygo.org/x/tinyfs" ) @@ -15,6 +17,41 @@ const ( testBlockCount = 4096 ) +func TestStoredModificationTime(t *testing.T) { + fs, dev, unmount := createTestFS(t) + defer unmount() + if typ := Type(fs.fs.fs_type); typ != TypeFAT12 && typ != TypeFAT16 { + t.Fatalf("expected FAT12 or FAT16, got %s", typ) + } + before := time.Now().Truncate(2 * time.Second) + f, err := fs.OpenFile("TIME.TXT", os.O_CREATE|os.O_WRONLY|os.O_TRUNC) + check(t, err) + _, err = f.Write([]byte("timestamp")) + check(t, err) + check(t, f.Close()) + after := time.Now() + + root := make([]byte, int(fs.fs.n_rootdir)*32) + _, err = dev.ReadAt(root, int64(fs.fs.dirbase)*SectorSize) + check(t, err) + for offset := 0; offset < len(root); offset += 32 { + entry := root[offset : offset+32] + if string(entry[:11]) != "TIME TXT" { + continue + } + // FAT directory entries store modification time at bytes 22-25. + clock := binary.LittleEndian.Uint16(entry[22:24]) + date := binary.LittleEndian.Uint16(entry[24:26]) + got := time.Date(1980+int(date>>9), time.Month(date>>5&15), int(date&31), + int(clock>>11), int(clock>>5&63), int(clock&31)*2, 0, time.Local) + if got.Before(before) || got.After(after) { + t.Fatalf("stored modification time = %s, want between %s and %s", got, before, after) + } + return + } + t.Fatal("TIME.TXT not found in root directory") +} + func TestType_String(t *testing.T) { expectString(t, "fatfs: (1) A hard error occurred in the low level disk I/O layer", FileResultErr.Error()) }