react-native-update 10.55.0 → 10.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README-CN.md +35 -0
- package/README.md +37 -0
- package/android/build.gradle +53 -27
- package/android/jni/Application.mk +6 -1
- package/android/jni/hpatch.c +59 -4
- package/android/lib/arm64-v8a/librnupdate.so +0 -0
- package/android/lib/armeabi-v7a/librnupdate.so +0 -0
- package/android/lib/x86/librnupdate.so +0 -0
- package/android/lib/x86_64/librnupdate.so +0 -0
- package/android/proguard.pro +70 -14
- package/android/src/main/java/cn/reactnative/modules/update/ApkInstaller.java +15 -8
- package/android/src/main/java/cn/reactnative/modules/update/ArchiveLimits.java +1 -0
- package/android/src/main/java/cn/reactnative/modules/update/BundledResourceCopier.java +1 -2
- package/android/src/main/java/cn/reactnative/modules/update/CleanupKeepNames.java +25 -0
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +98 -34
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java +29 -0
- package/android/src/main/java/cn/reactnative/modules/update/FileOperationException.java +15 -0
- package/android/src/main/java/cn/reactnative/modules/update/HttpUtils.java +59 -0
- package/android/src/main/java/cn/reactnative/modules/update/InstallRecord.java +27 -1
- package/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java +57 -16
- package/android/src/main/java/cn/reactnative/modules/update/ReactReloadManager.java +38 -46
- package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +279 -183
- package/android/src/main/java/cn/reactnative/modules/update/UpdateFileUtils.java +14 -0
- package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +222 -215
- package/android/src/main/java/cn/reactnative/modules/update/UpdatePackage.java +5 -0
- package/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java +23 -25
- package/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java +29 -29
- package/cpp/patch_core/archive_limits.h +6 -0
- package/cpp/patch_core/jni_util.h +6 -2
- package/cpp/patch_core/patch_core.cpp +237 -84
- package/cpp/patch_core/patch_core.h +11 -0
- package/cpp/patch_core/patch_core_android.cpp +49 -33
- package/cpp/patch_core/update_core_android.cpp +134 -103
- package/cpp/update_flow_core/flow_json.cpp +150 -17
- package/cpp/update_flow_core/flow_json.h +13 -1
- package/cpp/update_flow_core/update_flow_core.cpp +78 -8
- package/cpp/update_flow_core/update_flow_core.h +9 -0
- package/cpp/update_flow_core/update_flow_jni.cpp +44 -25
- package/harmony/hvigor-plugin.ts +168 -21
- package/harmony/pushy/src/README.md +74 -0
- package/harmony/pushy/src/main/cpp/CMakeLists.txt +13 -3
- package/harmony/pushy/src/main/cpp/PushyTurboModule.cpp +11 -5
- package/harmony/pushy/src/main/cpp/pushy.cpp +103 -18
- package/harmony/pushy/src/main/ets/ArchiveLimits.ts +61 -5
- package/harmony/pushy/src/main/ets/DownloadTask.ts +255 -166
- package/harmony/pushy/src/main/ets/ErrorCodes.ts +75 -0
- package/harmony/pushy/src/main/ets/EventHub.ts +22 -20
- package/harmony/pushy/src/main/ets/InstallRecord.ts +79 -21
- package/harmony/pushy/src/main/ets/Logger.ts +42 -16
- package/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts +98 -17
- package/harmony/pushy/src/main/ets/NativePatchCore.ts +10 -3
- package/harmony/pushy/src/main/ets/PathUtils.ts +9 -3
- package/harmony/pushy/src/main/ets/PushyTurboModule.ts +139 -53
- package/harmony/pushy/src/main/ets/UpdateContext.ts +269 -159
- package/harmony/pushy/src/main/module.json5 +5 -1
- package/harmony/pushy.har +0 -0
- package/ios/PrivacyInfo.xcprivacy +79 -0
- package/ios/RCTPushy/RCTPushy.mm +406 -170
- package/ios/RCTPushy/RCTPushyDownloader.h +14 -0
- package/ios/RCTPushy/RCTPushyDownloader.mm +136 -11
- package/package.json +14 -24
- package/react-native-update.podspec +36 -3
- package/src/client.ts +244 -86
- package/src/context.ts +40 -27
- package/src/core.ts +41 -16
- package/src/endpoint.ts +2 -1
- package/src/error.ts +49 -11
- package/src/index.ts +14 -0
- package/src/locales/en.ts +1 -23
- package/src/locales/zh.ts +1 -22
- package/src/provider.tsx +159 -111
- package/src/type.ts +0 -2
- package/src/updateFlowCore.ts +10 -2
- package/src/utils.ts +61 -4
- package/android/src/main/java/cn/reactnative/modules/update/ReactNativeHostHandler.java +0 -13
- package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleSupport.java +0 -45
|
@@ -3,13 +3,18 @@
|
|
|
3
3
|
#include "digest.h"
|
|
4
4
|
|
|
5
5
|
#include <cerrno>
|
|
6
|
+
#include <cstdint>
|
|
6
7
|
#include <cstdio>
|
|
7
8
|
#include <cstring>
|
|
8
9
|
#include <dirent.h>
|
|
10
|
+
#include <fcntl.h>
|
|
11
|
+
#include <sys/mman.h>
|
|
9
12
|
#include <sys/stat.h>
|
|
10
13
|
#include <sys/types.h>
|
|
11
14
|
#include <unistd.h>
|
|
12
15
|
|
|
16
|
+
#include <algorithm>
|
|
17
|
+
#include <limits>
|
|
13
18
|
#include <vector>
|
|
14
19
|
|
|
15
20
|
#include "hbc_transform.h"
|
|
@@ -30,6 +35,12 @@ namespace {
|
|
|
30
35
|
|
|
31
36
|
constexpr size_t kCopyBufferSize = 16 * 1024;
|
|
32
37
|
|
|
38
|
+
// Directory walkers recurse once per nesting level. A hostile archive can nest
|
|
39
|
+
// thousands of directories while staying well inside the entry-count cap, so
|
|
40
|
+
// bound the depth explicitly instead of letting the stack overflow (mirrors
|
|
41
|
+
// flowjson::kMaxDepth). Real bundles nest a handful of levels.
|
|
42
|
+
constexpr int kMaxDirectoryDepth = 64;
|
|
43
|
+
|
|
33
44
|
class HdiffBundlePatcher final : public BundlePatcher {
|
|
34
45
|
public:
|
|
35
46
|
Status Apply(
|
|
@@ -124,28 +135,31 @@ Status EnsureDirectory(const std::string& path) {
|
|
|
124
135
|
if (path.empty()) {
|
|
125
136
|
return Status::Ok();
|
|
126
137
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (!
|
|
138
|
-
|
|
138
|
+
// Walk the components left to right instead of recursing on the parent:
|
|
139
|
+
// manifest paths are attacker-controlled and may carry thousands of
|
|
140
|
+
// segments.
|
|
141
|
+
size_t pos = 0;
|
|
142
|
+
while (pos <= path.size()) {
|
|
143
|
+
size_t slash = path.find('/', pos);
|
|
144
|
+
if (slash == std::string::npos) {
|
|
145
|
+
slash = path.size();
|
|
146
|
+
}
|
|
147
|
+
const std::string prefix = path.substr(0, slash);
|
|
148
|
+
if (!prefix.empty()) {
|
|
149
|
+
if (PathExists(prefix)) {
|
|
150
|
+
if (!IsDirectory(prefix)) {
|
|
151
|
+
return Status::Error("Expected directory path: " + prefix);
|
|
152
|
+
}
|
|
153
|
+
} else if (mkdir(prefix.c_str(), 0755) != 0 && errno != EEXIST) {
|
|
154
|
+
return MakeErrnoStatus("Failed to create directory " + prefix);
|
|
155
|
+
}
|
|
139
156
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (mkdir(path.c_str(), 0755) != 0 && errno != EEXIST) {
|
|
143
|
-
return MakeErrnoStatus("Failed to create directory " + path);
|
|
157
|
+
pos = slash + 1;
|
|
144
158
|
}
|
|
145
159
|
return Status::Ok();
|
|
146
160
|
}
|
|
147
161
|
|
|
148
|
-
Status RemovePathRecursively(const std::string& path) {
|
|
162
|
+
Status RemovePathRecursively(const std::string& path, int depth = 0) {
|
|
149
163
|
struct stat st;
|
|
150
164
|
if (lstat(path.c_str(), &st) != 0) {
|
|
151
165
|
if (errno == ENOENT) {
|
|
@@ -155,6 +169,9 @@ Status RemovePathRecursively(const std::string& path) {
|
|
|
155
169
|
}
|
|
156
170
|
|
|
157
171
|
if (S_ISDIR(st.st_mode)) {
|
|
172
|
+
if (depth >= kMaxDirectoryDepth) {
|
|
173
|
+
return Status::Error("Directory nesting too deep: " + path);
|
|
174
|
+
}
|
|
158
175
|
DIR* dir = opendir(path.c_str());
|
|
159
176
|
if (!dir) {
|
|
160
177
|
return MakeErrnoStatus("Failed to open directory " + path);
|
|
@@ -166,7 +183,8 @@ Status RemovePathRecursively(const std::string& path) {
|
|
|
166
183
|
if (name == "." || name == "..") {
|
|
167
184
|
continue;
|
|
168
185
|
}
|
|
169
|
-
Status remove_status =
|
|
186
|
+
Status remove_status =
|
|
187
|
+
RemovePathRecursively(JoinPath(path, name), depth + 1);
|
|
170
188
|
if (!remove_status) {
|
|
171
189
|
closedir(dir);
|
|
172
190
|
return remove_status;
|
|
@@ -191,20 +209,26 @@ Status ReadFileBytes(const std::string& path, std::vector<uint8_t>* out) {
|
|
|
191
209
|
if (file == nullptr) {
|
|
192
210
|
return MakeErrnoStatus("Failed to open file for reading " + path);
|
|
193
211
|
}
|
|
194
|
-
|
|
212
|
+
// fstat rather than fseek/ftell: ftell returns a 32-bit long on the 32-bit
|
|
213
|
+
// ABIs (armeabi-v7a, x86), and the regular-file check keeps directories and
|
|
214
|
+
// devices out.
|
|
215
|
+
struct stat st;
|
|
216
|
+
if (fstat(fileno(file), &st) != 0) {
|
|
195
217
|
fclose(file);
|
|
196
|
-
return MakeErrnoStatus("Failed to
|
|
218
|
+
return MakeErrnoStatus("Failed to size file " + path);
|
|
197
219
|
}
|
|
198
|
-
|
|
199
|
-
if (size < 0) {
|
|
220
|
+
if (!S_ISREG(st.st_mode)) {
|
|
200
221
|
fclose(file);
|
|
201
|
-
return
|
|
222
|
+
return Status::Error("Not a regular file: " + path);
|
|
202
223
|
}
|
|
203
|
-
if (
|
|
224
|
+
if (st.st_size < 0 ||
|
|
225
|
+
static_cast<uint64_t>(st.st_size) >
|
|
226
|
+
static_cast<uint64_t>(std::numeric_limits<size_t>::max() / 2)) {
|
|
204
227
|
fclose(file);
|
|
205
|
-
return
|
|
228
|
+
return Status::Error("File too large to load: " + path);
|
|
206
229
|
}
|
|
207
|
-
|
|
230
|
+
const size_t size = static_cast<size_t>(st.st_size);
|
|
231
|
+
out->resize(size);
|
|
208
232
|
if (size > 0 &&
|
|
209
233
|
fread(out->data(), 1, out->size(), file) != out->size()) {
|
|
210
234
|
fclose(file);
|
|
@@ -214,6 +238,20 @@ Status ReadFileBytes(const std::string& path, std::vector<uint8_t>* out) {
|
|
|
214
238
|
return Status::Ok();
|
|
215
239
|
}
|
|
216
240
|
|
|
241
|
+
// fflush + fsync so payload bytes are durable before the caller's two-phase
|
|
242
|
+
// rename: a power cut must not leave a complete-looking version directory
|
|
243
|
+
// with a truncated bundle. EINVAL means the descriptor cannot be synced (some
|
|
244
|
+
// pseudo filesystems) and is not an error.
|
|
245
|
+
Status SyncFile(FILE* file, const std::string& path) {
|
|
246
|
+
if (fflush(file) != 0) {
|
|
247
|
+
return MakeErrnoStatus("Failed to flush file " + path);
|
|
248
|
+
}
|
|
249
|
+
if (fsync(fileno(file)) != 0 && errno != EINVAL) {
|
|
250
|
+
return MakeErrnoStatus("Failed to sync file " + path);
|
|
251
|
+
}
|
|
252
|
+
return Status::Ok();
|
|
253
|
+
}
|
|
254
|
+
|
|
217
255
|
Status WriteFileBytes(const std::string& path, const std::vector<uint8_t>& data) {
|
|
218
256
|
FILE* file = fopen(path.c_str(), "wb");
|
|
219
257
|
if (file == nullptr) {
|
|
@@ -224,6 +262,12 @@ Status WriteFileBytes(const std::string& path, const std::vector<uint8_t>& data)
|
|
|
224
262
|
remove(path.c_str());
|
|
225
263
|
return Status::Error("Failed to write file " + path);
|
|
226
264
|
}
|
|
265
|
+
Status sync_status = SyncFile(file, path);
|
|
266
|
+
if (!sync_status) {
|
|
267
|
+
fclose(file);
|
|
268
|
+
remove(path.c_str());
|
|
269
|
+
return sync_status;
|
|
270
|
+
}
|
|
227
271
|
if (fclose(file) != 0) {
|
|
228
272
|
remove(path.c_str());
|
|
229
273
|
return MakeErrnoStatus("Failed to flush file " + path);
|
|
@@ -231,42 +275,10 @@ Status WriteFileBytes(const std::string& path, const std::vector<uint8_t>& data)
|
|
|
231
275
|
return Status::Ok();
|
|
232
276
|
}
|
|
233
277
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
if (!S_ISREG(st.st_mode)) {
|
|
240
|
-
return Status::Error("Source is not a regular file: " + from);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
const std::string parent = Dirname(to);
|
|
244
|
-
Status dir_status = EnsureDirectory(parent);
|
|
245
|
-
if (!dir_status) {
|
|
246
|
-
return dir_status;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (PathExists(to)) {
|
|
250
|
-
if (!overwrite) {
|
|
251
|
-
return Status::Ok();
|
|
252
|
-
}
|
|
253
|
-
Status remove_status = RemovePathRecursively(to);
|
|
254
|
-
if (!remove_status) {
|
|
255
|
-
return remove_status;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// Prefer a hard link over copying bytes: unchanged files between versions
|
|
260
|
-
// are identical, so linking is O(1) per file, writes nothing to flash, and
|
|
261
|
-
// shares disk blocks. Version directories are immutable once created (patch
|
|
262
|
-
// outputs are always written as new files), so sharing the inode with the
|
|
263
|
-
// source is safe. Fall back to a byte copy whenever linking is not possible
|
|
264
|
-
// (cross-device source such as the installed app bundle, EPERM, EMLINK, or
|
|
265
|
-
// filesystems without hard-link support).
|
|
266
|
-
if (!internal::g_disable_hard_links && link(from.c_str(), to.c_str()) == 0) {
|
|
267
|
-
return Status::Ok();
|
|
268
|
-
}
|
|
269
|
-
|
|
278
|
+
// Streams `from` into a fresh `to` through a small buffer: never a hard link
|
|
279
|
+
// (callers that go on to mutate `to` in place rely on that) and never the
|
|
280
|
+
// whole file in memory.
|
|
281
|
+
Status CopyFileBytes(const std::string& from, const std::string& to) {
|
|
270
282
|
FILE* source = std::fopen(from.c_str(), "rb");
|
|
271
283
|
if (!source) {
|
|
272
284
|
return MakeErrnoStatus("Failed to open source file " + from);
|
|
@@ -301,12 +313,56 @@ Status CopyFile(const std::string& from, const std::string& to, bool overwrite)
|
|
|
301
313
|
}
|
|
302
314
|
|
|
303
315
|
std::fclose(source);
|
|
316
|
+
Status sync_status = SyncFile(destination, to);
|
|
317
|
+
if (!sync_status) {
|
|
318
|
+
std::fclose(destination);
|
|
319
|
+
return sync_status;
|
|
320
|
+
}
|
|
304
321
|
if (std::fclose(destination) != 0) {
|
|
305
322
|
return MakeErrnoStatus("Failed to close destination file " + to);
|
|
306
323
|
}
|
|
307
324
|
return Status::Ok();
|
|
308
325
|
}
|
|
309
326
|
|
|
327
|
+
Status CopyFile(const std::string& from, const std::string& to, bool overwrite) {
|
|
328
|
+
struct stat st;
|
|
329
|
+
if (stat(from.c_str(), &st) != 0) {
|
|
330
|
+
return MakeErrnoStatus("Failed to stat source file " + from);
|
|
331
|
+
}
|
|
332
|
+
if (!S_ISREG(st.st_mode)) {
|
|
333
|
+
return Status::Error("Source is not a regular file: " + from);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const std::string parent = Dirname(to);
|
|
337
|
+
Status dir_status = EnsureDirectory(parent);
|
|
338
|
+
if (!dir_status) {
|
|
339
|
+
return dir_status;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (PathExists(to)) {
|
|
343
|
+
if (!overwrite) {
|
|
344
|
+
return Status::Ok();
|
|
345
|
+
}
|
|
346
|
+
Status remove_status = RemovePathRecursively(to);
|
|
347
|
+
if (!remove_status) {
|
|
348
|
+
return remove_status;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Prefer a hard link over copying bytes: unchanged files between versions
|
|
353
|
+
// are identical, so linking is O(1) per file, writes nothing to flash, and
|
|
354
|
+
// shares disk blocks. Version directories are immutable once created (patch
|
|
355
|
+
// outputs are always written as new files), so sharing the inode with the
|
|
356
|
+
// source is safe. Fall back to a byte copy whenever linking is not possible
|
|
357
|
+
// (cross-device source such as the installed app bundle, EPERM, EMLINK, or
|
|
358
|
+
// filesystems without hard-link support).
|
|
359
|
+
if (!internal::g_disable_hard_links && link(from.c_str(), to.c_str()) == 0) {
|
|
360
|
+
return Status::Ok();
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return CopyFileBytes(from, to);
|
|
364
|
+
}
|
|
365
|
+
|
|
310
366
|
struct DeleteRule {
|
|
311
367
|
std::string path;
|
|
312
368
|
bool directory_hint;
|
|
@@ -367,7 +423,8 @@ Status MergeDirectoryRecursively(
|
|
|
367
423
|
const std::string& source_root,
|
|
368
424
|
const std::string& target_root,
|
|
369
425
|
const std::string& relative_root,
|
|
370
|
-
const DeleteMatcher& deletes
|
|
426
|
+
const DeleteMatcher& deletes,
|
|
427
|
+
int depth = 0) {
|
|
371
428
|
DIR* dir = opendir(source_root.c_str());
|
|
372
429
|
if (!dir) {
|
|
373
430
|
if (errno == ENOENT) {
|
|
@@ -399,14 +456,18 @@ Status MergeDirectoryRecursively(
|
|
|
399
456
|
|
|
400
457
|
const std::string target_path = JoinPath(target_root, name);
|
|
401
458
|
if (S_ISDIR(st.st_mode)) {
|
|
459
|
+
if (depth >= kMaxDirectoryDepth) {
|
|
460
|
+
closedir(dir);
|
|
461
|
+
return Status::Error("Directory nesting too deep: " + source_path);
|
|
462
|
+
}
|
|
402
463
|
Status dir_status = EnsureDirectory(target_path);
|
|
403
464
|
if (!dir_status) {
|
|
404
465
|
closedir(dir);
|
|
405
466
|
return dir_status;
|
|
406
467
|
}
|
|
407
468
|
|
|
408
|
-
Status merge_status =
|
|
409
|
-
|
|
469
|
+
Status merge_status = MergeDirectoryRecursively(
|
|
470
|
+
source_path, target_path, relative_path, deletes, depth + 1);
|
|
410
471
|
if (!merge_status) {
|
|
411
472
|
closedir(dir);
|
|
412
473
|
return merge_status;
|
|
@@ -445,10 +506,76 @@ const BundlePatcher& DefaultBundlePatcher() {
|
|
|
445
506
|
|
|
446
507
|
namespace {
|
|
447
508
|
|
|
509
|
+
// Applies T (or T⁻¹) to a file in place through a shared mapping, so a
|
|
510
|
+
// 10–50 MB bundle never has to be read into an anonymous heap buffer: the
|
|
511
|
+
// pages are backed by the file itself and can be written back and evicted
|
|
512
|
+
// under memory pressure. Falls back to read → transform → write when the
|
|
513
|
+
// mapping is unavailable. On a rejected transform the bytes are left as they
|
|
514
|
+
// were (TransformHbcInPlace never partially mutates).
|
|
515
|
+
Status TransformFileInPlace(
|
|
516
|
+
const std::string& path,
|
|
517
|
+
const hbc::HbcLayoutDesc& layout,
|
|
518
|
+
bool inverse) {
|
|
519
|
+
const char* failure = inverse
|
|
520
|
+
? "hbcTransform inverse failed on patched bundle"
|
|
521
|
+
: "hbcTransform failed on origin bundle";
|
|
522
|
+
|
|
523
|
+
const int fd = open(path.c_str(), O_RDWR);
|
|
524
|
+
if (fd < 0) {
|
|
525
|
+
return MakeErrnoStatus("Failed to open bundle for transform " + path);
|
|
526
|
+
}
|
|
527
|
+
struct stat st;
|
|
528
|
+
if (fstat(fd, &st) != 0) {
|
|
529
|
+
close(fd);
|
|
530
|
+
return MakeErrnoStatus("Failed to size bundle " + path);
|
|
531
|
+
}
|
|
532
|
+
void* mapped = MAP_FAILED;
|
|
533
|
+
size_t size = 0;
|
|
534
|
+
if (S_ISREG(st.st_mode) && st.st_size > 0 &&
|
|
535
|
+
static_cast<uint64_t>(st.st_size) <=
|
|
536
|
+
static_cast<uint64_t>(std::numeric_limits<size_t>::max() / 2)) {
|
|
537
|
+
size = static_cast<size_t>(st.st_size);
|
|
538
|
+
mapped = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
|
539
|
+
}
|
|
540
|
+
if (mapped == MAP_FAILED) {
|
|
541
|
+
close(fd);
|
|
542
|
+
std::vector<uint8_t> bytes;
|
|
543
|
+
Status status = ReadFileBytes(path, &bytes);
|
|
544
|
+
if (!status) {
|
|
545
|
+
return status;
|
|
546
|
+
}
|
|
547
|
+
if (!hbc::TransformHbcInPlace(bytes.data(), bytes.size(), layout, inverse)) {
|
|
548
|
+
return Status::Error(failure);
|
|
549
|
+
}
|
|
550
|
+
return WriteFileBytes(path, bytes);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const bool transformed = hbc::TransformHbcInPlace(
|
|
554
|
+
static_cast<uint8_t*>(mapped), size, layout, inverse);
|
|
555
|
+
const bool synced = msync(mapped, size, MS_SYNC) == 0;
|
|
556
|
+
const int sync_errno = errno;
|
|
557
|
+
munmap(mapped, size);
|
|
558
|
+
const bool fsynced = fsync(fd) == 0 || errno == EINVAL;
|
|
559
|
+
const int fsync_errno = errno;
|
|
560
|
+
close(fd);
|
|
561
|
+
if (!transformed) {
|
|
562
|
+
return Status::Error(failure);
|
|
563
|
+
}
|
|
564
|
+
if (!synced) {
|
|
565
|
+
return MakeErrnoStatus("Failed to sync transformed bundle " + path, sync_errno);
|
|
566
|
+
}
|
|
567
|
+
if (!fsynced) {
|
|
568
|
+
return MakeErrnoStatus("Failed to sync transformed bundle " + path, fsync_errno);
|
|
569
|
+
}
|
|
570
|
+
return Status::Ok();
|
|
571
|
+
}
|
|
572
|
+
|
|
448
573
|
// 变换域 bundle patch:T(origin) → hpatch → T⁻¹。
|
|
449
574
|
// 元数据/变换的任何失败都返回错误——调用方沿既有失败路径回退整包;
|
|
450
575
|
// 绝不能忽略元数据直接 hpatch(会产出损坏 bundle,虽然最终 hash 校验
|
|
451
576
|
// 也会拦住,但应在此处快速失败)。
|
|
577
|
+
// 两次变换都在临时文件上原地完成(见 TransformFileInPlace),整个流程
|
|
578
|
+
// 端到端不把 bundle 整体读进内存。
|
|
452
579
|
Status ApplyBundlePatchWithHbcTransform(
|
|
453
580
|
const FileSourcePatchOptions& options,
|
|
454
581
|
const BundlePatcher& bundle_patcher) {
|
|
@@ -464,26 +591,27 @@ Status ApplyBundlePatchWithHbcTransform(
|
|
|
464
591
|
std::vector<hbc::HbcSectionDesc> sections_scratch;
|
|
465
592
|
const hbc::HbcLayoutDesc layout = hbc::BuildLayout(meta, §ions_scratch);
|
|
466
593
|
|
|
467
|
-
std::vector<uint8_t> origin;
|
|
468
|
-
Status status = ReadFileBytes(options.origin_bundle_path, &origin);
|
|
469
|
-
if (!status) {
|
|
470
|
-
return status;
|
|
471
|
-
}
|
|
472
|
-
if (!hbc::TransformHbcInPlace(origin.data(), origin.size(), layout, false)) {
|
|
473
|
-
return Status::Error("hbcTransform failed on origin bundle");
|
|
474
|
-
}
|
|
475
|
-
|
|
476
594
|
Status dir_status = EnsureDirectory(Dirname(options.bundle_output_path));
|
|
477
595
|
if (!dir_status) {
|
|
478
596
|
return dir_status;
|
|
479
597
|
}
|
|
480
598
|
const std::string temp_origin = options.bundle_output_path + ".hbct-origin";
|
|
481
599
|
const std::string temp_patched = options.bundle_output_path + ".hbct-patched";
|
|
482
|
-
|
|
600
|
+
remove(temp_origin.c_str());
|
|
601
|
+
remove(temp_patched.c_str());
|
|
602
|
+
|
|
603
|
+
// A private byte copy — never a hard link — because the transform mutates
|
|
604
|
+
// it in place and the origin bundle must stay untouched.
|
|
605
|
+
Status status = CopyFileBytes(options.origin_bundle_path, temp_origin);
|
|
606
|
+
if (!status) {
|
|
607
|
+
remove(temp_origin.c_str());
|
|
608
|
+
return status;
|
|
609
|
+
}
|
|
610
|
+
status = TransformFileInPlace(temp_origin, layout, false);
|
|
483
611
|
if (!status) {
|
|
612
|
+
remove(temp_origin.c_str());
|
|
484
613
|
return status;
|
|
485
614
|
}
|
|
486
|
-
origin = std::vector<uint8_t>(); // 及早释放,patch 期间只保留文件副本
|
|
487
615
|
|
|
488
616
|
Status patch_status =
|
|
489
617
|
bundle_patcher.Apply(temp_origin, options.bundle_patch_path, temp_patched);
|
|
@@ -493,23 +621,26 @@ Status ApplyBundlePatchWithHbcTransform(
|
|
|
493
621
|
return patch_status;
|
|
494
622
|
}
|
|
495
623
|
|
|
496
|
-
|
|
497
|
-
status = ReadFileBytes(temp_patched, &patched);
|
|
498
|
-
remove(temp_patched.c_str());
|
|
624
|
+
status = TransformFileInPlace(temp_patched, layout, true);
|
|
499
625
|
if (!status) {
|
|
626
|
+
remove(temp_patched.c_str());
|
|
500
627
|
return status;
|
|
501
628
|
}
|
|
502
|
-
if (!hbc::TransformHbcInPlace(patched.data(), patched.size(), layout, true)) {
|
|
503
|
-
return Status::Error("hbcTransform inverse failed on patched bundle");
|
|
504
|
-
}
|
|
505
629
|
|
|
506
630
|
if (PathExists(options.bundle_output_path)) {
|
|
507
631
|
Status remove_status = RemovePathRecursively(options.bundle_output_path);
|
|
508
632
|
if (!remove_status) {
|
|
633
|
+
remove(temp_patched.c_str());
|
|
509
634
|
return remove_status;
|
|
510
635
|
}
|
|
511
636
|
}
|
|
512
|
-
|
|
637
|
+
if (rename(temp_patched.c_str(), options.bundle_output_path.c_str()) != 0) {
|
|
638
|
+
Status rename_status = MakeErrnoStatus(
|
|
639
|
+
"Failed to move patched bundle into place " + options.bundle_output_path);
|
|
640
|
+
remove(temp_patched.c_str());
|
|
641
|
+
return rename_status;
|
|
642
|
+
}
|
|
643
|
+
return Status::Ok();
|
|
513
644
|
}
|
|
514
645
|
|
|
515
646
|
} // namespace
|
|
@@ -591,6 +722,16 @@ Status CleanupOldEntries(
|
|
|
591
722
|
const std::string& keep_previous,
|
|
592
723
|
int max_age_days,
|
|
593
724
|
std::time_t now) {
|
|
725
|
+
return CleanupOldEntries(
|
|
726
|
+
root_dir, std::vector<std::string>{keep_current, keep_previous},
|
|
727
|
+
max_age_days, now);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
Status CleanupOldEntries(
|
|
731
|
+
const std::string& root_dir,
|
|
732
|
+
const std::vector<std::string>& keep_names,
|
|
733
|
+
int max_age_days,
|
|
734
|
+
std::time_t now) {
|
|
594
735
|
DIR* dir = opendir(root_dir.c_str());
|
|
595
736
|
if (!dir) {
|
|
596
737
|
if (errno == ENOENT) {
|
|
@@ -609,7 +750,8 @@ Status CleanupOldEntries(
|
|
|
609
750
|
if (name == "." || name == ".." || (!name.empty() && name[0] == '.')) {
|
|
610
751
|
continue;
|
|
611
752
|
}
|
|
612
|
-
if (
|
|
753
|
+
if (std::find(keep_names.begin(), keep_names.end(), name) !=
|
|
754
|
+
keep_names.end()) {
|
|
613
755
|
continue;
|
|
614
756
|
}
|
|
615
757
|
|
|
@@ -648,6 +790,17 @@ bool IsSafeRelativePath(const std::string& path) {
|
|
|
648
790
|
if (segment.empty() || segment == "." || segment == "..") {
|
|
649
791
|
return false;
|
|
650
792
|
}
|
|
793
|
+
for (const char ch : segment) {
|
|
794
|
+
// Control bytes (NUL above all) are never legitimate in a manifest
|
|
795
|
+
// path: everything downstream goes through c_str(), so an embedded NUL
|
|
796
|
+
// would silently truncate "..\0x" to ".." and escape the staging root
|
|
797
|
+
// (reachable through the HarmonyOS NAPI string bridge, which preserves
|
|
798
|
+
// NULs). Non-ASCII UTF-8 (>= 0x80) stays allowed.
|
|
799
|
+
const unsigned char byte = static_cast<unsigned char>(ch);
|
|
800
|
+
if (byte < 0x20 || byte == 0x7f) {
|
|
801
|
+
return false;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
651
804
|
}
|
|
652
805
|
return true;
|
|
653
806
|
}
|
|
@@ -68,6 +68,17 @@ Status ApplyPatchFromFileSource(
|
|
|
68
68
|
const FileSourcePatchOptions& options,
|
|
69
69
|
const BundlePatcher& bundle_patcher = DefaultBundlePatcher());
|
|
70
70
|
|
|
71
|
+
// Removes every non-dot entry under root_dir older than max_age_days whose
|
|
72
|
+
// name is not in keep_names. Callers must include every version that may
|
|
73
|
+
// still be in use: the persisted current/last versions AND the version this
|
|
74
|
+
// process booted from (two switches without a restart otherwise evict the
|
|
75
|
+
// running bundle while its on-demand assets are still being served).
|
|
76
|
+
Status CleanupOldEntries(
|
|
77
|
+
const std::string& root_dir,
|
|
78
|
+
const std::vector<std::string>& keep_names,
|
|
79
|
+
int max_age_days,
|
|
80
|
+
std::time_t now = 0);
|
|
81
|
+
|
|
71
82
|
Status CleanupOldEntries(
|
|
72
83
|
const std::string& root_dir,
|
|
73
84
|
const std::string& keep_current,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#include <jni.h>
|
|
2
2
|
|
|
3
|
+
#include <exception>
|
|
3
4
|
#include <string>
|
|
4
5
|
#include <vector>
|
|
5
6
|
|
|
@@ -29,36 +30,45 @@ Java_cn_reactnative_modules_update_DownloadTask_applyPatchFromFileSource(
|
|
|
29
30
|
jobjectArray copy_tos,
|
|
30
31
|
jobjectArray deletes,
|
|
31
32
|
jstring hbc_transform_meta) {
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
// C++ exceptions (bad_alloc/length_error from input-proportional STL
|
|
34
|
+
// allocations) must not unwind through the JNI boundary: convert them to a
|
|
35
|
+
// Java RuntimeException like every other failure of this entry point.
|
|
36
|
+
try {
|
|
37
|
+
const std::vector<std::string> from_values = JArrayToVector(env, copy_froms);
|
|
38
|
+
const std::vector<std::string> to_values = JArrayToVector(env, copy_tos);
|
|
34
39
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
if (from_values.size() != to_values.size()) {
|
|
41
|
+
ThrowRuntimeException(env, "copy_froms and copy_tos length mismatch");
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
39
44
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
pushy::patch::FileSourcePatchOptions options;
|
|
46
|
+
options.source_root = JStringToString(env, source_root);
|
|
47
|
+
options.target_root = JStringToString(env, target_root);
|
|
48
|
+
options.origin_bundle_path = JStringToString(env, origin_bundle_path);
|
|
49
|
+
options.bundle_patch_path = JStringToString(env, bundle_patch_path);
|
|
50
|
+
options.bundle_output_path = JStringToString(env, bundle_output_path);
|
|
51
|
+
options.merge_source_subdir = JStringToString(env, merge_source_subdir);
|
|
52
|
+
options.enable_merge = enable_merge == JNI_TRUE;
|
|
53
|
+
options.bundle_hbc_transform_meta = JStringToString(env, hbc_transform_meta);
|
|
49
54
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
for (size_t index = 0; index < from_values.size(); ++index) {
|
|
56
|
+
options.manifest.copies.push_back(pushy::patch::CopyOperation{
|
|
57
|
+
from_values[index],
|
|
58
|
+
to_values[index],
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
options.manifest.deletes = JArrayToVector(env, deletes);
|
|
57
62
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
63
|
+
const pushy::patch::Status status =
|
|
64
|
+
pushy::patch::ApplyPatchFromFileSource(options);
|
|
65
|
+
if (!status.ok) {
|
|
66
|
+
ThrowRuntimeException(env, status.message);
|
|
67
|
+
}
|
|
68
|
+
} catch (const std::exception& error) {
|
|
69
|
+
ThrowRuntimeException(env, error.what());
|
|
70
|
+
} catch (...) {
|
|
71
|
+
ThrowRuntimeException(env, "Unexpected native exception in applyPatchFromFileSource");
|
|
62
72
|
}
|
|
63
73
|
}
|
|
64
74
|
|
|
@@ -70,12 +80,18 @@ Java_cn_reactnative_modules_update_DownloadTask_cleanupOldEntries(
|
|
|
70
80
|
jstring keep_current,
|
|
71
81
|
jstring keep_previous,
|
|
72
82
|
jint max_age_days) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
83
|
+
try {
|
|
84
|
+
const pushy::patch::Status status = pushy::patch::CleanupOldEntries(
|
|
85
|
+
JStringToString(env, root_dir),
|
|
86
|
+
JStringToString(env, keep_current),
|
|
87
|
+
JStringToString(env, keep_previous),
|
|
88
|
+
static_cast<int>(max_age_days));
|
|
89
|
+
if (!status.ok) {
|
|
90
|
+
ThrowRuntimeException(env, status.message);
|
|
91
|
+
}
|
|
92
|
+
} catch (const std::exception& error) {
|
|
93
|
+
ThrowRuntimeException(env, error.what());
|
|
94
|
+
} catch (...) {
|
|
95
|
+
ThrowRuntimeException(env, "Unexpected native exception in cleanupOldEntries");
|
|
80
96
|
}
|
|
81
97
|
}
|