dir.h sizes its directory entry buffer from it: char d_name[MAX_PATH].
One thing I had wrong: 260 bounds the path as the device sees it, so the
directory the mirror is copied into spends part of the same budget. The checker
was measuring the mirror-relative path against the device-absolute limit and
under-reporting by the length of the destination.
Shortening
Over-budget paths are cut from the deepest component outward. The track
name carries the least navigational value and the artist directory the most, so
the filename goes first and the artist only if nothing else will serve:
A Very Long Artist Name... <- untouched
An Extremely Long Album Title... <- untouched
A Track Whose Name Simply...~bc43.mp3
A shortened component keeps its extension and gains four hex digits of the
original name. Two long titles sharing a prefix cut to the same string
otherwise, and a silent collision between two tracks is a worse outcome than an
ugly filename.
Stable by construction. The same source always yields the same shortened
name, so one pass does not rename what the last one wrote — an unstable scheme
would churn the whole mirror every six hours. There is a test for it.
A path too deeply nested to fit without reducing every component to nonsense is
left alone and reported, not mangled.
--max-path and --device-prefix
--max-path 260 # Rockbox's MAX_PATH
--device-prefix /Music # comes out of the budget
Both also on tools/check_fat32.py, so the checker and the mirror agree on
what fits.
Migration
There is now more than one previous naming to match. A mirror already running
with --fat32-safe holds sanitised-but-unshortened paths, and matching only
the original unsanitised name would have re-encoded every one of them
rather than moving it. Both candidates are tried.
Testing
75 tests, green locally and in docker build --target test. Six new: deepest-
first shortening, stability across passes, two long names not colliding,
rename-not-re-encode from a sanitised mirror, no shortening when under budget,
and the unfittable path being reported.
## The limit is real
Verified in `firmware/include/fs_defines.h`:
```c
#ifndef MAX_PATH
#define MAX_PATH 260
#endif
#define MAX_COMPNAME 260
```
`dir.h` sizes its directory entry buffer from it: `char d_name[MAX_PATH]`.
One thing I had wrong: 260 bounds the path **as the device sees it**, so the
directory the mirror is copied into spends part of the same budget. The checker
was measuring the mirror-relative path against the device-absolute limit and
under-reporting by the length of the destination.
## Shortening
Over-budget paths are cut **from the deepest component outward**. The track
name carries the least navigational value and the artist directory the most, so
the filename goes first and the artist only if nothing else will serve:
A Very Long Artist Name... <- untouched
An Extremely Long Album Title... <- untouched
A Track Whose Name Simply...~bc43.mp3
A shortened component keeps its extension and gains four hex digits of the
original name. Two long titles sharing a prefix cut to the same string
otherwise, and a silent collision between two tracks is a worse outcome than an
ugly filename.
**Stable by construction.** The same source always yields the same shortened
name, so one pass does not rename what the last one wrote — an unstable scheme
would churn the whole mirror every six hours. There is a test for it.
A path too deeply nested to fit without reducing every component to nonsense is
left alone and reported, not mangled.
## `--max-path` and `--device-prefix`
--max-path 260 # Rockbox's MAX_PATH
--device-prefix /Music # comes out of the budget
Both also on `tools/check_fat32.py`, so the checker and the mirror agree on
what fits.
## Migration
There is now more than one previous naming to match. A mirror already running
with `--fat32-safe` holds sanitised-but-unshortened paths, and matching only
the original unsanitised name would have **re-encoded every one of them**
rather than moving it. Both candidates are tried.
## Testing
75 tests, green locally and in `docker build --target test`. Six new: deepest-
first shortening, stability across passes, two long names not colliding,
rename-not-re-encode from a sanitised mirror, no shortening when under budget,
and the unfittable path being reported.
Rockbox's MAX_PATH is 260, defined in firmware/include/fs_defines.h and used to
size the directory entry buffer in dir.h. It bounds the path as the device sees
it, so the directory the mirror is copied into spends part of the same budget;
--device-prefix accounts for that and defaults to /Music.
Over-budget paths are shortened from the deepest component outward. The track
name carries the least navigational value and the artist directory the most, so
the filename is cut first and the artist only if nothing else will serve. A
shortened component keeps its extension and gains four hex digits of the
original name: two long titles sharing a prefix cut to the same string
otherwise, and a silent collision between two tracks is a worse outcome than an
ugly filename.
The result is stable. The same source always yields the same shortened name, so
one pass does not rename what the last one wrote -- an unstable scheme would
churn the whole mirror every six hours. A path too deeply nested to fit without
reducing every component to nonsense is left alone and reported rather than
mangled.
Migration now tries more than one previous naming, because there is more than
one. A mirror already running with --fat32-safe holds sanitised but unshortened
paths, and matching only the original unsanitised name would have re-encoded
every one of them instead of moving it.
The checker gains the same two options, since it was measuring the
mirror-relative path against a limit that applies to the device-absolute one,
and so under-reported by the length of the destination directory.
The shortening fitted the path and destroyed its meaning. Lidarr writes
"Artist - Album - 07 - Flamethrower.mp3" inside a directory already named for
that artist and album, so a long album title occurs three times in one path and
everything that distinguishes one track from another sits at the very end.
Cutting from the end removed precisely that:
King Gizzard & the Lizard Wizard - PetroDragonic Apocalypse; or, Dawn of Eter~c526.mp3
All seven tracks on that record reduced to the same string bar the hash. The
path fitted; the result was seven files nobody could tell apart on the device,
which is a worse outcome than the failure it replaced.
Cut from the middle instead, giving two thirds of the remaining room to the
tail because the head is generally a restatement of the directory the file
already sits in:
King Gizzard & the Lizard~c526~ginning of Merciless Damnation - 07 - Flamethrower.mp3
The eight real paths that prompted this are now regression tests: every track
on that album keeps its number and title, all seven names stay distinct, and
The Beatles' "The Long One" -- whose length is the title itself rather than a
repeated album name -- keeps both ends.
The budget subtracted the prefix length plus two, on the assumption of a
leading and a trailing slash. That is right for /Music and wrong for an empty
prefix, where there is only one slash -- losing a character at the card root,
which is exactly where the longest paths sit.
Computed from the prefix as it will actually appear instead: /Music/ costs
seven characters and gives a mirror-relative budget of 253, the root costs one
and gives 259.
Worth being exact about because the reverse error is worse. A checker
comparing mirror-relative paths against the flat 260 passes everything between
253 and 260, and those are precisely the paths closest to the edge.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
The limit is real
Verified in
firmware/include/fs_defines.h:dir.hsizes its directory entry buffer from it:char d_name[MAX_PATH].One thing I had wrong: 260 bounds the path as the device sees it, so the
directory the mirror is copied into spends part of the same budget. The checker
was measuring the mirror-relative path against the device-absolute limit and
under-reporting by the length of the destination.
Shortening
Over-budget paths are cut from the deepest component outward. The track
name carries the least navigational value and the artist directory the most, so
the filename goes first and the artist only if nothing else will serve:
A shortened component keeps its extension and gains four hex digits of the
original name. Two long titles sharing a prefix cut to the same string
otherwise, and a silent collision between two tracks is a worse outcome than an
ugly filename.
Stable by construction. The same source always yields the same shortened
name, so one pass does not rename what the last one wrote — an unstable scheme
would churn the whole mirror every six hours. There is a test for it.
A path too deeply nested to fit without reducing every component to nonsense is
left alone and reported, not mangled.
--max-pathand--device-prefixBoth also on
tools/check_fat32.py, so the checker and the mirror agree onwhat fits.
Migration
There is now more than one previous naming to match. A mirror already running
with
--fat32-safeholds sanitised-but-unshortened paths, and matching onlythe original unsanitised name would have re-encoded every one of them
rather than moving it. Both candidates are tried.
Testing
75 tests, green locally and in
docker build --target test. Six new: deepest-first shortening, stability across passes, two long names not colliding,
rename-not-re-encode from a sanitised mirror, no shortening when under budget,
and the unfittable path being reported.