MuntashirAkon/AppManager · error · IOException

Could not read the whole resource (total = %d, read = %d)

Error message

Could not read the whole resource (total = %d, read = %d)

What it means

Thrown inside the read-side ProxyFileDescriptorCallback pipe in StorageManagerCompat.openProxyFileDescriptor when the total bytes read from the callback does not match the size reported by callback.onGetSize() (and totalSize > 0). It signals a truncated or inconsistent remote resource being proxied through a storage file descriptor.

Source

Thrown at app/src/main/java/io/github/muntashirakon/AppManager/compat/StorageManagerCompat.java:99

    @NonNull
    public static ParcelFileDescriptor openProxyFileDescriptor(int mode, @NonNull ProxyFileDescriptorCallbackCompat callback)
            throws IOException, UnsupportedOperationException {
        // We cannot use StorageManager#openProxyFileDescriptor directly due to its limitation on how callbacks are handled
        ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createReliablePipe();
        if ((mode & ParcelFileDescriptor.MODE_READ_ONLY) != 0) {
            // Reading requested i.e. we have to read from our side and write it to the target
            callback.mHandler.post(() -> {
                try (ParcelFileDescriptor.AutoCloseOutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(pipe[1])) {
                    long totalSize = callback.onGetSize();
                    long currOffset = 0;
                    byte[] buf = new byte[DEFAULT_BUFFER_SIZE];
                    int size;
                    while ((size = callback.onRead(currOffset, DEFAULT_BUFFER_SIZE, buf)) > 0) {
                        os.write(buf, 0, size);
                        currOffset += size;
                    }
                    if (totalSize > 0 && currOffset != totalSize) {
                        throw new IOException(String.format(Locale.ROOT, "Could not read the whole resource (total = %d, read = %d)", totalSize, currOffset));
                    }
                } catch (IOException | ErrnoException e) {
                    Log.e(TAG, "Failed to read file.", e);
                    try {
                        pipe[1].closeWithError(e.getMessage());
                    } catch (IOException exc) {
                        Log.e(TAG, "Can't even close PFD with error.", exc);
                    }
                } finally {
                    callback.onRelease();
                }
            });
            return pipe[0];
        } else if ((mode & ParcelFileDescriptor.MODE_WRITE_ONLY) != 0) {
            // Writing requested i.e. we have to read from the target and write it to our side
            callback.mHandler.post(() -> {
                try (ParcelFileDescriptor.AutoCloseInputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pipe[0])) {
                    long currOffset = 0;

View on GitHub (pinned to 0152f468fc)

Solutions

  1. Make onGetSize() return the exact byte count of the content actually served (decompressed size)
  2. Fix the source stream in onRead so it supplies the full totalSize bytes or return <= 0 only at true EOF
  3. Handle the IOException thrown to the reader via closeWithError and check LocalInterop/pipe error messages
  4. Verify server sends correct Content-Length and no transfer-encoding mismatch

Example fix

// before
long totalSize = urlConnection.getContentLength(); // may mismatch gzip body
// after
try (InputStream in = urlConnection.getInputStream()) {
    long totalSize = countStream(in); // actual decoded byte count
}
Defensive patterns

Strategy: validation

Validate before calling

// verify reported size matches actual content before proxying
long actual = countBytes(sourceStream);
if (reportedSize != actual) {
    throw new IllegalStateException("Size metadata mismatch: " + reportedSize + " vs " + actual);
}

Try / catch

try (ParcelFileDescriptor pfd = StorageManagerCompat.openProxyFileDescriptor(
        ParcelFileDescriptor.MODE_READ_ONLY, callback, handler)) {
    // consume pfd
} catch (IOException e) {
    Log.e(TAG, "Proxy read failed: " + e.getMessage());
    fallbackToLocalCopy();
}

Prevention

When it happens

Trigger: The callback's onRead returns fewer total bytes than onGetSize() reports — e.g. the underlying stream ends early, network truncation, or onGetSize() returns a stale/incorrect size for a growing or compressed resource.

Common situations: Proxying a cloud file whose size metadata differs from actual content; reading over an unstable network connection; size computed from HTTP Content-Length while body was gzip-encoded or truncated.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


AI-assisted analysis of MuntashirAkon/AppManager@0152f468fc (2026-09-12). Data as JSON: /api/errors/07754e8935de7521. Report an issue: GitHub.