art_apply: statvfs pre-flight is POSIX-only — Windows has no os.statvfs

Boulder's lock-in question caught it: the read-only pre-flight called
os.statvfs unconditionally, which doesn't exist on Windows, and the
AttributeError wasn't covered by the except OSError — the whole cover-art
apply would have crashed for every Windows install (docker images are
Linux, so the reporter was fine; the maintainer wasn't). getattr-guarded
now: no statvfs -> skip the pre-flight, per-file EROFS detection (errno is
cross-platform) still active. Test pins the no-statvfs path.
This commit is contained in:
BoulderBadgeDad 2026-06-07 10:46:09 -07:00
parent 3c758635d5
commit ece74250fb
2 changed files with 24 additions and 2 deletions

View file

@ -126,10 +126,13 @@ def apply_art_to_album_files(
# Pre-flight: if the mount is read-only, every save below would fail with
# EROFS one by one (Tim's report: a wall of per-file warnings and a 777
# chmod that couldn't help). statvfs asks the kernel without writing.
# POSIX-only — Windows has no os.statvfs (and no ':ro' bind mounts);
# there we skip straight to the per-file path, same as before this fix.
probe_dir = folder or (os.path.dirname(paths[0]) if paths else None)
if probe_dir:
_statvfs = getattr(os, "statvfs", None)
if probe_dir and _statvfs is not None:
try:
if os.statvfs(probe_dir).f_flag & os.ST_RDONLY:
if _statvfs(probe_dir).f_flag & getattr(os, "ST_RDONLY", 1):
logger.warning(
"Art apply skipped: %s is on a READ-ONLY filesystem "
"(docker ':ro' volume mount — chmod cannot fix this)",

View file

@ -203,3 +203,22 @@ def test_apply_normal_failure_not_flagged_read_only(tmp_path, monkeypatch):
assert res['read_only_fs'] is False # EACCES is chmod-able, EROFS is not
assert res['failed'] == 1
def test_apply_works_without_statvfs_windows(tmp_path, monkeypatch):
"""Windows has no os.statvfs — the pre-flight must vanish gracefully,
not crash the apply (Boulder runs on Windows)."""
f = tmp_path / 'a.mp3'
f.write_bytes(b'')
saved = []
audio = SimpleNamespace(pictures=[], tags=None, add_tags=lambda: None,
save=lambda: saved.append(True))
monkeypatch.setattr(aa, 'get_mutagen_symbols', lambda: _fake_symbols(audio))
monkeypatch.setattr(aa, 'embed_album_art_metadata', lambda *a, **k: True)
monkeypatch.setattr(aa, 'download_cover_art', lambda *a, **k: None)
monkeypatch.delattr(aa.os, 'statvfs', raising=False)
res = aa.apply_art_to_album_files([str(f)], {}, {}, folder=str(tmp_path))
assert res['embedded'] == 1 and saved == [True]
assert res['read_only_fs'] is False