{"record":{"id":"07754e8935de7521","repo":"MuntashirAkon/AppManager","slug":"could-not-read-the-whole-resource-total-d-read-d","errorCode":null,"errorMessage":"Could not read the whole resource (total = %d, read = %d)","messagePattern":"Could not read the whole resource \\(total = (.+?), read = (.+?)\\)","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"app/src/main/java/io/github/muntashirakon/AppManager/compat/StorageManagerCompat.java","lineNumber":99,"sourceCode":"    @NonNull\n    public static ParcelFileDescriptor openProxyFileDescriptor(int mode, @NonNull ProxyFileDescriptorCallbackCompat callback)\n            throws IOException, UnsupportedOperationException {\n        // We cannot use StorageManager#openProxyFileDescriptor directly due to its limitation on how callbacks are handled\n        ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createReliablePipe();\n        if ((mode & ParcelFileDescriptor.MODE_READ_ONLY) != 0) {\n            // Reading requested i.e. we have to read from our side and write it to the target\n            callback.mHandler.post(() -> {\n                try (ParcelFileDescriptor.AutoCloseOutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(pipe[1])) {\n                    long totalSize = callback.onGetSize();\n                    long currOffset = 0;\n                    byte[] buf = new byte[DEFAULT_BUFFER_SIZE];\n                    int size;\n                    while ((size = callback.onRead(currOffset, DEFAULT_BUFFER_SIZE, buf)) > 0) {\n                        os.write(buf, 0, size);\n                        currOffset += size;\n                    }\n                    if (totalSize > 0 && currOffset != totalSize) {\n                        throw new IOException(String.format(Locale.ROOT, \"Could not read the whole resource (total = %d, read = %d)\", totalSize, currOffset));\n                    }\n                } catch (IOException | ErrnoException e) {\n                    Log.e(TAG, \"Failed to read file.\", e);\n                    try {\n                        pipe[1].closeWithError(e.getMessage());\n                    } catch (IOException exc) {\n                        Log.e(TAG, \"Can't even close PFD with error.\", exc);\n                    }\n                } finally {\n                    callback.onRelease();\n                }\n            });\n            return pipe[0];\n        } else if ((mode & ParcelFileDescriptor.MODE_WRITE_ONLY) != 0) {\n            // Writing requested i.e. we have to read from the target and write it to our side\n            callback.mHandler.post(() -> {\n                try (ParcelFileDescriptor.AutoCloseInputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pipe[0])) {\n                    long currOffset = 0;","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/MuntashirAkon/AppManager/blob/0152f468fc9463ee02dc2ca83f6fe4989a2c4ca5/app/src/main/java/io/github/muntashirakon/AppManager/compat/StorageManagerCompat.java#L81-L117","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Make onGetSize() return the exact byte count of the content actually served (decompressed size)","Fix the source stream in onRead so it supplies the full totalSize bytes or return <= 0 only at true EOF","Handle the IOException thrown to the reader via closeWithError and check LocalInterop/pipe error messages","Verify server sends correct Content-Length and no transfer-encoding mismatch"],"exampleFix":"// before\nlong totalSize = urlConnection.getContentLength(); // may mismatch gzip body\n// after\ntry (InputStream in = urlConnection.getInputStream()) {\n    long totalSize = countStream(in); // actual decoded byte count\n}","handlingStrategy":"validation","validationCode":"// verify reported size matches actual content before proxying\nlong actual = countBytes(sourceStream);\nif (reportedSize != actual) {\n    throw new IllegalStateException(\"Size metadata mismatch: \" + reportedSize + \" vs \" + actual);\n}","typeGuard":null,"tryCatchPattern":"try (ParcelFileDescriptor pfd = StorageManagerCompat.openProxyFileDescriptor(\n        ParcelFileDescriptor.MODE_READ_ONLY, callback, handler)) {\n    // consume pfd\n} catch (IOException e) {\n    Log.e(TAG, \"Proxy read failed: \" + e.getMessage());\n    fallbackToLocalCopy();\n}","preventionTips":["Keep onGetSize() consistent with bytes actually served","Use decompressed sizes","Detect EOF vs error in onRead","Test with slow/unstable sources"],"tags":["android","storage","file-descriptor","truncated-read"],"backgroundTag":"file-read-failed","analyzedSha":"0152f468fc9463ee02dc2ca83f6fe4989a2c4ca5","analyzedAt":"2026-09-12T14:03:37.243Z","contentChangedAt":"2026-09-12T14:03:37.243Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}