Skip to content

fix: keep file permission bits when rewriting ComicInfo - #160

Open
bbutlerau wants to merge 2 commits into
pullboxapp:developfrom
bbutlerau:fix/comicinfo-rewrite-preserves-file-mode
Open

bbutlerau wants to merge 2 commits into
pullboxapp:developfrom
bbutlerau:fix/comicinfo-rewrite-preserves-file-mode

Conversation

@bbutlerau

Copy link
Copy Markdown

Summary

Embedding ComicInfo.xml rewrites the archive through a temp file created with tempfile.mkstemp, which is always 0600. The temp file then replaces the original, so the permission bits the file had before are lost:

  • Mass Convert / in-place embed (embed_comicinfo_in_cbz): a library file that was 0644 or 0666 comes out 0600.
  • Import with ComicInfo (materialize_cbz_with_comicinfo): the library copy is 0600 whatever the source file allowed.

For anyone running a reader as a different user (Komga, Kavita and so on), those files become unreadable. With Library Permission Management enabled the import path corrects it afterwards, but it is off by default and the utility path does not apply it. On my install a Mass Convert over 24 files left 7 of them owner-only; the ones that went through a caller-supplied temp path kept sane modes, the mkstemp ones did not.

Related Issues

None filed. Loosely related to #129 and #132 in that it adds a chmod, so it is written to be best effort (see below).

Changes

  • Add _copy_permission_bits() in utilities/comicinfo.py and call it just before the temp file replaces the target:
    • in-place embed copies the bits from the archive being replaced;
    • materialize copies the bits from the source file, which is what a plain move or copy would have preserved.
  • The chmod is wrapped in contextlib.suppress(OSError), so mounts that reject it (some SMB/CIFS shares) do not fail the rewrite.
  • contextlib and os are now module-level imports rather than imported inside the two finally blocks.

Not changed: direct-download artifacts are deliberately 0600 in quarantine, and this PR preserves the source mode, so those still arrive 0600 unless Library Permission Management is on. That seems worth its own discussion.

Checklist

  • Tests pass locally for the touched areas (tests/unit/test_comicinfo_embed.py, tests/utilities/test_comicinfo.py, test_archive_subprocess.py, test_mass_convert_pipeline.py, tests/unit/test_library_comicinfo.py, test_issue_import_service.py, test_import_file_registration_adapters.py). The new tests fail on develop without the change.
  • Lint clean (ruff check src/ tests/)
  • Type check clean for the touched file (mypy --strict src/pullbox/utilities/comicinfo.py). A full-tree run in my environment reports errors on untouched develop too, so I am relying on CI for the full check.
  • Format clean (ruff format --check src/ tests/)
  • New code has test coverage
  • Documentation updated (not applicable)
  • Commit messages use documented conventional prefixes
  • CHANGELOG.md updated: left for release prep, per the contributing guide

Screenshots

No UI changes.

🤖 Generated with Claude Code

@DeusExTaco

Copy link
Copy Markdown
Contributor

@bbutlerau
This looks good to me. Preserving the existing permission bits addresses a real problem with archives becoming owner-only after a metadata rewrite.
One small improvement I’d suggest is logging a warning if restoring the permissions fails. Keeping that best-effort behavior makes sense for filesystems that don’t support chmod; having something in the logs would make troubleshooting easier.
Could you bring the branch up to date with develop for final validation? The workflows currently show action_required, so I’ll handle the approval on my side. Assuming the required checks pass, I’m comfortable taking this one.

bbutlerau and others added 2 commits October 3, 2026 12:02
Embedding ComicInfo.xml rewrites the archive through a temp file created
with tempfile.mkstemp, which is always 0600. The rewritten archive then
replaced the original, so a file that a media server could read before a
Mass Convert or metadata embed was owner-only afterwards. Imports that
write ComicInfo while moving a file into the library had the same result:
the library copy came out 0600 whatever the source file allowed.

Copy the permission bits from the file being replaced (in-place embed) or
from the source file (materialize), which is what a plain move or copy
would have preserved. The chmod is best effort so mounts that reject it
do not fail the rewrite.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E9gUFQEryh4gDJRKvjNUZM
Restoring the bits stays best effort, so filesystems that reject chmod
still complete the rewrite, but the failure is now logged as a warning
naming the archive, so an owner-only file can be traced back to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E9gUFQEryh4gDJRKvjNUZM
@bbutlerau
bbutlerau force-pushed the fix/comicinfo-rewrite-preserves-file-mode branch from 9f00830 to 90683b7 Compare October 3, 2026 02:06
@bbutlerau

Copy link
Copy Markdown
Author

Thanks for the review. Both points are done:

  • Rebased on current develop: no conflicts.
  • Warning on failure: restoring the permission bits is still best-effort, but if chmod fails Pullbox now logs a comicinfo_permission_bits_not_restored warning with the archive path, the temp file and the error, and the rewrite still goes ahead. test_embed_comicinfo_survives_a_filesystem_that_rejects_chmod now also checks that exactly one warning is logged, for the right archive.

The change is in a separate commit on top (90683b7) to make it easier to review.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants