Repository navigation
Conversation
|
@ojwb since this touches Xapian::Database(int fd) semantics directly (getDbFromAccessInfo() in src/tools.cpp) -- would value your input on whether dup()'ing a descriptor we already hold, instead of reopening a synthesized /dev/fd/ path, is the right way to hand Xapian an fd here, and whether that has any implications on Xapian's side (e.g. around the fd ownership Xapian::Database(int) takes). |
The only potential problem I can see is that Xapian will use The file status flags are also shared, but that seems unlikely to cause problems.
|
Hmm, though it looks like reading the version file uses However it looks like Once we know the offset we can use There are two potential issues:
(If the caller - libzim here - only uses I think for libzim you can likely still use |
|
@veloman-yunkan This is very touchy but worth a review IMHO as it would be very beneficial to get rid of the current jack. One additional thing I worry about is the handling of ZIM chunks... Really concerned how this piece of code coop with this scenario. |
There was a problem hiding this comment.
Pull request overview
This PR addresses failures when reopening fd-backed archives via synthetic /dev/fd/<n> paths by switching internal consumers to duplicate the already-open descriptor with dup(), improving compatibility with Android content-provider file descriptors and similar non-reopenable FDs.
Changes:
- Add a POSIX-only
dupFd()helper and use it to open fd-backedFilePartinstances without reopening by path. - Extend
ItemDataDirectAccessInfo(POSIX-only) with an optionalfdfield and plumb it throughFileImplandgetDbFromAccessInfo()for Xapian DB opening. - Add regression tests covering suggestion search on an archive opened from an FD and update direct-access test helpers to prefer
dup()when an FD is available.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
src/fs_unix.h |
Declares dupFd(int) helper for POSIX builds. |
src/fs_unix.cpp |
Implements dupFd() using dup(2) and throws on failure. |
src/file_part.h |
Switches fd-backed FilePart construction to dupFd(); exposes nativeFd() (POSIX-only). |
include/zim/zim.h |
Adds POSIX-only fd field and constructor to ItemDataDirectAccessInfo. |
src/fileimpl.cpp |
Populates ItemDataDirectAccessInfo with nativeFd() when available (POSIX-only). |
src/tools.cpp |
Updates getDbFromAccessInfo() to prefer dupFd(accessInfo.fd) over reopening by filename (POSIX-only). |
test/suggestion.cpp |
Adds regression test for suggestion search on FD-opened archives (POSIX-only). |
test/archive.cpp |
Updates direct-access test helper to dup() the provided fd when present (POSIX-only). |
Suppressed comments (1)
test/suggestion.cpp:112
zim::Archive(int fd)does not take ownership of the passed-in descriptor (see public API docs), so this test should closefdafter constructing the archive to avoid leaking descriptors when the loop iterates over multiple test files.
const int fd = open(testfile.path.c_str(), O_RDONLY);
ASSERT_NE(-1, fd);
const zim::Archive archive(fd);
ASSERT_TRUE(archive.hasTitleIndex());
const auto mainItem = archive.getMainEntry().getItem(true);
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Pushed 7daa73b addressing Copilot's 3 inline comments (replied inline on each). Summary of the real one: it correctly flagged that adding @ojwb re: @kelson42 re: chunked/multi-part archives -- unaffected by this change either way. if (++part_its.first != part_its.second) {
// The content is split on two parts. We cannot have direct access
return ItemDataDirectAccessInfo();
}The dup()-vs-reopen choice only ever applies to the single-part case, same as Verified: full local build (meson+ninja, |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1119 +/- ##
==========================================
+ Coverage 56.72% 56.92% +0.20%
==========================================
Files 100 100
Lines 5056 5094 +38
Branches 2239 2252 +13
==========================================
+ Hits 2868 2900 +32
+ Misses 714 713 -1
- Partials 1474 1481 +7 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…mDataDirectAccessInfo::fd field openzim/libzim#1119 no longer builds ItemDataDirectAccessInfo with an `fd` field (that was a real ABI break -- the struct is returned by value from a public API, adding a field changes its layout for any consumer built against an older header). It now exposes the same fd through a new, purely additive method instead: `Item::getDirectAccessFd()`, which already returns an owned, dup()'d descriptor (or -1) -- no manual dup() needed on this side anymore. Verified: standalone compile-check of the exact THIS->getDirectAccessInformation()/getDirectAccessFd() call pattern against the updated libzim's public headers (this repo's own CMake setup is Android-NDK-only, no desktop native build target available in this sandbox to compile the JNI target directly). Co-Authored-By: soloturn <soloturn@gmail.com>
Follow-up: the previous fix ( Pushed |
Follow-up to openzim/libzim#1119: `zim::Item::getDirectAccessFd()` returns an already-open, dup()'d descriptor for an item's data (populated for both fd-opened and path-opened archives), letting callers avoid reopening `getDirectAccessInformation().filename` by path - a reopen that fails with EACCES for some Android content-provider descriptors (openzim/libzim#852). Wires that fd through the JNI boundary into `DirectAccessInfo.fd`. The C++ side already hands back an owned descriptor, so the JNI glue just forwards it - the Java side adopts and is responsible for closing it. Depends on libzim#1119 landing and being released - this repo's own build downloads a released libzim tarball, so CI here won't recompile clean against it until then. Co-Authored-By: soloturn <soloturn@gmail.com>
codecov/patch was failing. Built libzim with coverage instrumentation (
Added two tests that pass each an invalid fd directly and check they fail gracefully. Both lines are green now. The other flagged files (fileimpl.cpp, item.cpp, suggestion.cpp) still show "partial" coverage, but every actually new/changed line in this diff is already exercised by the existing tests. What's left is either pre-existing code the diff happened to touch, or the same catch block reached through a deeper call path that would need a fixture with a deliberately corrupted embedded Xapian db to trigger - not worth the fragility for the remaining points. Pushed |
4908f66 to
4970765
Compare
Moved the general (unrelated to this PR's own diff) coverage tests out to #1126, back to this PR's original scope. Current state: |
4970765 to
8ef5646
Compare
@kelson42 re: ZIM chunk/multi-part handling -- the guard sits in one place, and everything funnels through it.
All three places this PR touches read the fd through that same function, so they inherit the guard automatically:
In every bail-out case the caller falls back to the normal buffered read path. That fallback logic is pre-existing and unchanged by this PR -- the PR only adds the |
veloman-yunkan
left a comment
There was a problem hiding this comment.
I started with a dup()-based implementation in #449 but had to resort to the /dev/fd/* hack because of the constraints imposed by the need to account for the presence of the zim::Item::getDirectAccessInformation() method in the public API. We need to get rid of that method (or convert its return type to an fd-based respresentation) - something that we could and should have done in the years that have passed since #449 was merged (three opportunities of major-version increases have been thoughtlessly missed).
This PR can be a starting point for deprecating the getDirectAccessInformation() method, however we must find a viable alternative first.
8ef5646 to
a86d04f
Compare
…cate getDirectAccessInformation() on POSIX
getDirectAccessFd() required a second call to getDirectAccessInformation() just for the offset.
Added ItemDirectAccessFd{fd, offset} (new type, no ABI impact) so one call gives both.
getDirectAccessInformation() is DEPRECATED on POSIX only, where the replacement exists; Windows has no fd-based archive opening, so it keeps the undecorated declaration there and stays the only option.
Archive::hasFulltextIndex()/hasTitleIndex() switched to the internal FileImpl lookup (no dup(), no leak) instead of the now-deprecated public method. Remaining POSIX call sites that deliberately exercise the deprecated method (regression tests) wrap it with ZIM_TEST_SUPPRESS_DEPRECATED_BEGIN/END (test/tools.h), new in this commit.
Addresses the API-redesign ask from openzim#1119's review thread (@veloman-yunkan, @kelson42).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
@veloman-yunkan I hardly see how this PR could reach completeness and maturity within a few iterations. What should we do with it? |
Some Android content providers hand libzim a file descriptor with no safely reopenable path. Reopening "/dev/fd/<fd>" by path can fail with a permission error there. Duplicate the fd instead wherever we need a second, independently-seekable handle to the same file: FilePart's fd-based constructors, and Xapian database opening for direct-access items and suggestion search. Adds Item::getDirectAccessFd(), a new public API for callers to get an owned, dup()'d fd for an item's direct-access data instead of a filename to reopen. Verified: full test suite passes locally, including new regression tests for the dup()'d-fd path and for dupFd()/getDbFromAccessInfo()'s error paths. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…cate getDirectAccessInformation() on POSIX
getDirectAccessFd() required a second call to getDirectAccessInformation() just for the offset.
Added ItemDirectAccessFd{fd, offset} (new type, no ABI impact) so one call gives both.
getDirectAccessInformation() is DEPRECATED on POSIX only, where the replacement exists; Windows has no fd-based archive opening, so it keeps the undecorated declaration there and stays the only option.
Archive::hasFulltextIndex()/hasTitleIndex() switched to the internal FileImpl lookup (no dup(), no leak) instead of the now-deprecated public method. Remaining POSIX call sites that deliberately exercise the deprecated method (regression tests) wrap it with ZIM_TEST_SUPPRESS_DEPRECATED_BEGIN/END (test/tools.h), new in this commit.
Addresses the API-redesign ask from openzim#1119's review thread (@veloman-yunkan, @kelson42).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
35c9ac0 to
42dd9a1
Compare
…ng its filename Large, uncompressed items (video/audio >1MB) were served through Item.directAccessInformation's filename, opened fresh via ParcelFileDescriptor.open(File, MODE_READ_ONLY). For a SAF-backed archive that filename resolves to the book's real path on shared storage, and Android denies re-opening it that way without a standing grant - even though libzim already handed us a perfectly good descriptor. Confirmed on-device: tapping a video inside a SAF-imported book crashed with EACCES, misattributed by R8 to an unrelated coil3 class after method deduplication. DirectAccessInfo.fd (openzim/libzim#1119, wired through java-libkiwix#151) is exactly this descriptor, already dup()'d and owned by the caller. Prefer ParcelFileDescriptor.adoptFd(fd) when it's set; only reopen by path as a fallback for callers without it (older libzim, Windows). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
DirectAccessInfo gains an `fd` field. On POSIX it's read via libzim's getDirectAccessFd() (openzim/libzim#1119), which now returns an owned dup()'d fd + offset in one call; getDirectAccessInformation() is deprecated there, so it's used only as the Windows fallback. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A SAF-imported book's direct-access filename can be a synthetic /dev/fd/N path or a real path the app has no standing grant to reopen - Android denies both, crashing on large embedded assets (e.g. video links) and on the initial import's descriptor sanity check. Use FileDescriptor.valid() instead of reopening for the sanity check, and prefer libzim's own dup()'d fd (openzim/libzim#1119, wired via a local java-libkiwix build) over ParcelFileDescriptor.open(filename) in the direct-access read path. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@veloman-yunkan comments fixed. especially, getDirectAccessInformation() is not used any more. i successfully tested this pr 1119 end to end by building kiwix-android, and publishing it as release for branch "main-solo". you can e.g. install the apk by installing "obtanium" from f-droid, then point it to https://github.com/soloturn/kiwix-android/releases, set the filter to "main-solo". there are a couple of fixes to kiwix-android necessary to avoid double opening the zim via direct file access, and one can choose the folder via settings (soloturn/kiwix-android@5e310b8). the kiwix-android build uses the following pr:
the kiwix-android built with these pr benefits from the fd and its dup. it stores the downloaded zim into default folder Documents/Kiwix using androids SAF, asking for permission to store outside the kiwix app folder. that means, uninstall kiwix-android, and reinstall does not remove the zim any more. |
|
@veloman-yunkan We need to get rid of thsi hack and move forward on fixing bug/limitation on Android. How looks the way forward? Do you develop the fix based on inputbfrom @soloturn? Or do we let him the opportunity to complete this PR in the way you expect it to be? |
@veloman-yunkan mind having a look, here, and #1126 ? |
Summary
/dev/fd/12and reopened it; reopening that path can fail with a permission error even though the original descriptor still works. Duplicate the descriptor instead.getDirectAccessInformation()is nowDEPRECATEDon POSIX, where the fd-based replacement exists. Windows has no fd-based archive opening, so it keeps the plain declaration there and stays the only option.Test plan
meson test(POSIX). Pre-existing, unrelated multi-part test-data failures excluded — same on this branch before these changes.Related