If pd->file->write() is called with a payload bigger than about 1KB and the write fails, every subsequent file API call that needs to resolve a path (mkdir, listfiles, stat, new open) returns -1 with unknown uC-FS error: 1414 (which is FS_ERR_VOL_NOT_MOUNTED upstream) until the app exits. File handles that were already open before the failure keep working fine, so it looks like the path-resolution / mount-state layer is where the failure lives, not the I/O layer underneath.
Reproduces on hardware, SDK 3.0.6, Rev A. Sim doesn't show it.
Repro:
Don't pre-mkdir any data folder subdir.
open("saves/foo.bin", kFileWrite) returns a non-NULL handle.
write(fp, buf, N) with N > 1024 returns -1.
All path-resolving file API calls now return 1414, including for known-good paths.
The reason this took me a while to find: the error string is "vol not mounted" which sounds like a hardware or system issue, but the actual cause is an unrelated failed write somewhere earlier in the same run. No way to recover in-app. So a perfectly normal save flow that happens to fail once will silently break the entire file API for the rest of the session, with the symptom appearing in code that has nothing to do with the actual bug.
If anyone else lands on this looking up "uC-FS error 1414": maybe try scrolling back through your boot log for the earliest failed pd->file->write() with a payload over a few KB.
A few things I didn't nail down. What's the exact byte threshold (mine was 524288, didn't bisect down)? Does going back to the Launcher and re-entering the app reset the mount state, or do you need a full reboot? And does the Lua side have the same behavior or is this only on the C API?
Hello Scott! I'm trying to reproduce this and not having much luck. Bummer, because we're seeing a ton of 1414 errors logged to memfault in 3.0.6, so it's something we need to fix asap. Some questions for you!
If there's no saves subdir in your data folder then trying to open saves/foo.bin should definitely return NULL. Am I missing something here?
Where is the buffer that you're writing? When I open just foo.bin (in the root of the data folder) and write to it from a statically allocated (i.e. declared in the global scope) buffer, it works for me. Or up to the 1 MB that I tested, anyway. If I declare it in the function scope, making it a stack allocation, I can only go up to around 7 KB. Any more than that and the system hangs and reboots after the 10 second watchdog timeout. The stack on the game task is only 8 KB, doesn't take much to overflow it.
Thanks for reporting this, it's a great lead to sorting out what's going wrong. If you've got code demonstrating this--even if it's just compiled code, I should be able to figure out what's going wrong in the debugger--it'd be a great help!
You know what, I am struggling to reproduce this with a simple check now too! I am a bit out of the same headspace I was in but I’m going to keep trying to find a simple C file I can send over. I’ll keep poking at isolating it, but this PDX here reproduces those failures! I’ll respond again if I find anything new out.
Unfortunately we can only detect a stack overflow if the RTOS happens to switch tasks while the stack pointer is out of bounds. But I did find a compiler warning -Wstack-usage that will warn if it see an obvious overflow so I've added that to the SDK.
saves/foo.bin question
I cannot reproduce the open-returns-non-NULL behavior in a fresh minimal pdx — on a clean device with no saves/ dir, my minimal pdx’s open(saves/foo.bin, kFileWrite) correctly returns NULL with err 0710 ("Not found"). But on the full app, with mkdir("saves") having been called (and returning 0), the open returns non-NULL, the write fails, and the FS gets poisoned. So either mkdir reports 0 without actually persisting the directory, or something else in the boot path is corrupting volume state before the write.
Where is the buffer that you’re writing?
Heap, malloc() of 524288 bytes during app init. Not stack, not global, not DTCM.
In the file I attached in the previous message, watch the console — the smoking-gun lines appear during the boot-time selftests.
A 524288-byte malloc + free + realloc to mimic the production app's allocation pattern
Two-step write (32 bytes then 524288 bytes on the same handle)
Open + write inside kEventInit
4 MB of static BSS to match the production app's memory footprint
A controlled DTCM write at 0x20004020 matching the production app's ITCM relocation
None of those, alone or combined, triggered 1414. Maybe this might be useful when you're hunting Memfault traces — it suggests the failure isn't reachable from a fresh boot via a simple file-API sequence. Something has to "warm up" the state first.
I hate pointing out bugs without having more concrete evidence, let me know if you can think of anything I can do to assist troubleshooting on my end!
No luck here. On first run console showed it running selftest then it said that I needed a ngp file. I put on the first one I could find (SNK vs Capcom), still no error. Does it happen for you immediately, or after you've selected a game?
Oh oops, didn't see your reply before posting that. Let me go back and read that!
rom_picker: saw '._README.md'
rom_picker: saw '._SNK Vs Capcom - Match Of The Millennium (JUE) [!].ngp'
rom_picker: saw 'README.md'
rom_picker: saw 'SNK Vs Capcom - Match Of The Millennium (JUE) [!].ngp'
rom_picker: scanned 'roms/' — found 2 ROM(s)
[0] roms/._SNK Vs Capcom - Match Of The Millennium (JUE) [!].ngp
[1] roms/SNK Vs Capcom - Match Of The Millennium (JUE) [!].ngp
integration: 2 ROMs found — showing startup picker
(the ._ file is garbage that macos puts on there..)
If you have a chance, could you run a disk checker on the data disk? Disk First Aid on Mac, fsck on Linux, no idea what the equivalent is on Windows. When we get weird stuff like this we'll shrug and say "disk corruption?" but we've never been able to pin it down. It'd be good to at least rule that out here.
I can definitely do that when I’m done with my work day, but just to confirm, are you running this PDX on hardware? Because the errors do not show up in the Simulator.
I ran macOS Disk Utility "First Aid" on my Playdate's storage and it found FAT corruption across 19 game directories: every .. entry had an incorrect start cluster, and the FSInfo block's free-cluster count was off by ~124K clusters. So the 1414 may not be a write bug? — it's the firmware correctly refusing to operate on a corrupted filesystem. The question is what's corrupting the .. entries in the first place. The corruption pattern is too uniform to be random: 19 directories all hit, and they span multiple sources (sideloaded /Games/User/*, purchased catalog, season games). Something is consistently mis-writing .. entries. I wasn’t seeing this in 3.0.5 I don’t think? I need to dig some more.
I just did a full factory reset, updated the firmware again and had all the games download from Catalog and from the Sideloaded games on my account on play.date. Hmm…
Interesting! I have a couple of those too. Probably not related but I'll file it and keep an eye on it. If you're still getting the write error after that then I think we can rule out filesystem corruption. I've only got a few games on this one right now so I'll try installing more, see if that makes any difference.
Say, did you ever check pd->file->geterr() right after the write() failure?
Oh also, after installing more games I ran First Aid again and all of the pdxes had the wrong .. link. So I suspect it's been that way for a long time and we've never noticed because it's not causing any problems. But I'll go back and check previous versions to be sure