| 92 | return output; |
| 93 | } |
| 94 | Result<Ref<MmapBuffer>> ZStdUtils::decompressToMmap(const Byte* input, |
| 95 | size_t len, |
| 96 | const Path& filePath, |
| 97 | bool* outPublishFailed) { |
| 98 | auto firstFrameSize = ZSTD_findFrameCompressedSize(input, len); |
| 99 | bool isSingleFrame = !ZSTD_isError(firstFrameSize) && firstFrameSize == len; |
| 100 | |
| 101 | if (!isSingleFrame) { |
| 102 | return Error("decompressToMmap requires a single ZStd frame"); |
| 103 | } |
| 104 | |
| 105 | auto contentSize = ZSTD_getFrameContentSize(input, len); |
| 106 | if (contentSize == ZSTD_CONTENTSIZE_UNKNOWN || contentSize == ZSTD_CONTENTSIZE_ERROR) { |
| 107 | return Error("ZStd frame does not encode the decompressed content size"); |
| 108 | } |
| 109 | |
| 110 | if (contentSize > kMaxSinglePassDecompressSize) { |
| 111 | return Error( |
| 112 | STRING_FORMAT("Decompressed size {} exceeds maximum {}", contentSize, kMaxSinglePassDecompressSize)); |
| 113 | } |
| 114 | |
| 115 | // mkstemp atomically creates a uniquely-named file and returns an open fd. |
| 116 | // We deliberately place the tmp file in the SAME directory as the final |
| 117 | // target rather than under std::filesystem::temp_directory_path(): |
| 118 | // 1. On Android, temp_directory_path() throws filesystem_error when |
| 119 | // TMPDIR is unset and /tmp doesn't exist. |
| 120 | // 2. Even when it returns a valid path, it's typically on a different |
| 121 | // mount point than the app's cache dir, which makes std::rename |
| 122 | // fail with EXDEV and silently drop the file. |
| 123 | // Placing the tmp next to the target guarantees same-filesystem atomic |
| 124 | // rename. The parent directory is already ensured to exist by |
| 125 | // ValdiModuleArchive::decompress before we get here. |
| 126 | std::string tmpPathStr = filePath.toString() + ".XXXXXX"; |
| 127 | int tmpFd = mkstemp(tmpPathStr.data()); |
| 128 | if (tmpFd < 0) { |
| 129 | return Error(STRING_FORMAT("mkstemp failed for template '{}': {}", tmpPathStr, strerror(errno))); |
| 130 | } |
| 131 | |
| 132 | struct TmpFileGuard { |
| 133 | std::string path; |
| 134 | bool dismissed = false; |
| 135 | ~TmpFileGuard() { |
| 136 | if (!dismissed) { |
| 137 | unlink(path.c_str()); |
| 138 | } |
| 139 | } |
| 140 | } tmpGuard{tmpPathStr}; |
| 141 | |
| 142 | // createWritable(fd, ...) takes ownership of tmpFd and closes it on every path. |
| 143 | auto bufferResult = MmapBuffer::createWritable(tmpFd, static_cast<size_t>(contentSize), tmpPathStr); |
| 144 | if (!bufferResult) { |
| 145 | return bufferResult.error().rethrow("Failed to create mmap buffer"); |
| 146 | } |
| 147 | |
| 148 | auto buffer = bufferResult.moveValue(); |
| 149 | |
| 150 | auto result = ZSTD_decompress(const_cast<Byte*>(buffer->data()), buffer->size(), input, len); |
| 151 | if (ZSTD_isError(result) != 0) { |
nothing calls this directly
no test coverage detected