mirror of
https://github.com/Momoko-Ayase/Senbei.git
synced 2026-11-04 10:31:29 -05:00
fix(android): deduplicate only successfully restored inputs
This commit is contained in:
1 parent
f5896b3428
commit
d16f1c9394
3 files changed
+149
-5
No files matched your search
@@ -15,6 +15,9 @@
|
|||||||
//! All functions in this module are native filesystem orchestration; the web
|
//! All functions in this module are native filesystem orchestration; the web
|
||||||
//! app (wasm) never touches them.
|
//! app (wasm) never touches them.
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{BufWriter, Read, Seek, Write};
|
use std::io::{BufWriter, Read, Seek, Write};
|
||||||
@@ -480,7 +483,8 @@ fn restore_package_entry<R: Read + Seek>(
|
|||||||
if !is_so && !is_meta {
|
if !is_so && !is_meta {
|
||||||
return Ok(vec![outcome(EntryKind::So, EntryStatus::NotTarget)]);
|
return Ok(vec![outcome(EntryKind::So, EntryStatus::NotTarget)]);
|
||||||
}
|
}
|
||||||
if !seen.insert(content_identity(&entry_data)) {
|
let identity = content_identity(&entry_data);
|
||||||
|
if seen.contains(&identity) {
|
||||||
let kind = if is_so {
|
let kind = if is_so {
|
||||||
EntryKind::So
|
EntryKind::So
|
||||||
} else {
|
} else {
|
||||||
@@ -512,6 +516,12 @@ fn restore_package_entry<R: Read + Seek>(
|
|||||||
status,
|
status,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if outcomes
|
||||||
|
.iter()
|
||||||
|
.all(|outcome| !matches!(outcome.status, EntryStatus::Failed(_)))
|
||||||
|
{
|
||||||
|
seen.insert(identity);
|
||||||
|
}
|
||||||
outcomes
|
outcomes
|
||||||
}
|
}
|
||||||
Err(error) => vec![outcome(EntryKind::So, EntryStatus::Failed(error))],
|
Err(error) => vec![outcome(EntryKind::So, EntryStatus::Failed(error))],
|
||||||
@@ -540,6 +550,12 @@ fn restore_package_entry<R: Read + Seek>(
|
|||||||
EntryStatus::Failed(error),
|
EntryStatus::Failed(error),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
if matches!(
|
||||||
|
kind_and_status.1,
|
||||||
|
EntryStatus::Restored | EntryStatus::Unchanged
|
||||||
|
) {
|
||||||
|
seen.insert(identity);
|
||||||
|
}
|
||||||
Ok(vec![outcome(kind_and_status.0, kind_and_status.1)])
|
Ok(vec![outcome(kind_and_status.0, kind_and_status.1)])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
use super::*;
|
||||||
|
use zip::write::SimpleFileOptions;
|
||||||
|
|
||||||
|
fn metadata_fixture() -> Vec<u8> {
|
||||||
|
let mut data = vec![0; 0x1a4];
|
||||||
|
let mut put = |offset: usize, value: u32| {
|
||||||
|
data[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
|
||||||
|
};
|
||||||
|
put(0, 0xfab1_1baf);
|
||||||
|
put(4, 31);
|
||||||
|
put(0x30, 0x180);
|
||||||
|
put(0x34, 0x24);
|
||||||
|
put(0xa0, 0x128);
|
||||||
|
put(0xa4, 0x58);
|
||||||
|
put(0xa8, 0x100);
|
||||||
|
put(0xac, 0x28);
|
||||||
|
put(0x10c, 1);
|
||||||
|
put(0x14c, 0);
|
||||||
|
put(0x168, 1);
|
||||||
|
put(0x198, 0x0600_1234);
|
||||||
|
data
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_package(path: &Path, entries: &[(&str, &[u8])]) {
|
||||||
|
let mut archive = zip::ZipWriter::new(File::create(path).unwrap());
|
||||||
|
for (name, data) in entries {
|
||||||
|
archive
|
||||||
|
.start_file(*name, SimpleFileOptions::default())
|
||||||
|
.unwrap();
|
||||||
|
archive.write_all(data).unwrap();
|
||||||
|
}
|
||||||
|
archive.finish().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn failed_metadata_output_does_not_poison_deduplication() {
|
||||||
|
let temp = tempfile::tempdir().unwrap();
|
||||||
|
let package = temp.path().join("app.apk");
|
||||||
|
let out = temp.path().join("out");
|
||||||
|
let data = metadata_fixture();
|
||||||
|
write_package(
|
||||||
|
&package,
|
||||||
|
&[
|
||||||
|
("a/global-metadata.dat", &data),
|
||||||
|
("b/global-metadata.dat", &data),
|
||||||
|
("c/global-metadata.dat", &data),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
std::fs::create_dir_all(out.join("app.apk")).unwrap();
|
||||||
|
std::fs::write(out.join("app.apk/a"), b"blocks the output directory").unwrap();
|
||||||
|
let mut seen = HashSet::new();
|
||||||
|
let outcomes = restore_package(&package, Path::new("app.apk"), &out, &mut seen, false).unwrap();
|
||||||
|
assert_eq!(outcomes.len(), 3);
|
||||||
|
assert!(matches!(outcomes[0].status, EntryStatus::Failed(_)));
|
||||||
|
assert!(matches!(outcomes[1].status, EntryStatus::Restored));
|
||||||
|
assert!(matches!(outcomes[2].status, EntryStatus::Duplicate));
|
||||||
|
assert_eq!(seen.len(), 1);
|
||||||
|
let expected = restore_metadata_bytes(&data).unwrap().0;
|
||||||
|
assert_eq!(std::fs::read(&outcomes[1].dest).unwrap(), expected);
|
||||||
|
assert!(!outcomes[2].dest.exists());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn repeated_failed_metadata_is_retried() {
|
||||||
|
let temp = tempfile::tempdir().unwrap();
|
||||||
|
let package = temp.path().join("app.apk");
|
||||||
|
let malformed = [0xaf, 0x1b, 0xb1, 0xfa, 31, 0, 0, 0];
|
||||||
|
write_package(
|
||||||
|
&package,
|
||||||
|
&[
|
||||||
|
("a/global-metadata.dat", &malformed),
|
||||||
|
("b/global-metadata.dat", &malformed),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
let mut seen = HashSet::new();
|
||||||
|
let outcomes = restore_package(
|
||||||
|
&package,
|
||||||
|
Path::new("app.apk"),
|
||||||
|
&temp.path().join("out"),
|
||||||
|
&mut seen,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(outcomes.len(), 2);
|
||||||
|
assert!(
|
||||||
|
outcomes
|
||||||
|
.iter()
|
||||||
|
.all(|outcome| matches!(outcome.status, EntryStatus::Failed(_)))
|
||||||
|
);
|
||||||
|
assert!(seen.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unchanged_metadata_is_a_successful_deduplication_source() {
|
||||||
|
let temp = tempfile::tempdir().unwrap();
|
||||||
|
let package = temp.path().join("app.apk");
|
||||||
|
let data = restore_metadata_bytes(&metadata_fixture()).unwrap().0;
|
||||||
|
write_package(
|
||||||
|
&package,
|
||||||
|
&[
|
||||||
|
("a/global-metadata.dat", &data),
|
||||||
|
("b/global-metadata.dat", &data),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
let mut seen = HashSet::new();
|
||||||
|
let outcomes = restore_package(
|
||||||
|
&package,
|
||||||
|
Path::new("app.apk"),
|
||||||
|
&temp.path().join("out"),
|
||||||
|
&mut seen,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert!(matches!(outcomes[0].status, EntryStatus::Unchanged));
|
||||||
|
assert!(matches!(outcomes[1].status, EntryStatus::Duplicate));
|
||||||
|
assert_eq!(seen.len(), 1);
|
||||||
|
assert!(outcomes.iter().all(|outcome| !outcome.dest.exists()));
|
||||||
|
}
|
||||||
+14
-4
@@ -228,10 +228,14 @@ pub fn run_folder_opts(
|
|||||||
for input in &scan.android_so {
|
for input in &scan.android_so {
|
||||||
let rel = rel_in_tree(root, input);
|
let rel = rel_in_tree(root, input);
|
||||||
let dest = out_root.join(out_name(&rel));
|
let dest = out_root.join(out_name(&rel));
|
||||||
|
// Failed restores must not hide an identical input encountered later.
|
||||||
// Unreadable here is fine: the restore reports the same error.
|
// Unreadable here is fine: the restore reports the same error.
|
||||||
if android_dedup
|
let identity = android_dedup
|
||||||
&& let Ok(identity) = crate::android::file_content_identity(input)
|
.then(|| crate::android::file_content_identity(input).ok())
|
||||||
&& !android_seen.insert(identity)
|
.flatten();
|
||||||
|
if identity
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|id| android_seen.contains(id))
|
||||||
{
|
{
|
||||||
s.skipped += 1;
|
s.skipped += 1;
|
||||||
if let Some(log) = &log {
|
if let Some(log) = &log {
|
||||||
@@ -263,7 +267,13 @@ pub fn run_folder_opts(
|
|||||||
if let Some(log) = &log {
|
if let Some(log) = &log {
|
||||||
log.step(&format!("OK {rel:?} -> {dest:?} (Android SO)"));
|
log.step(&format!("OK {rel:?} -> {dest:?} (Android SO)"));
|
||||||
}
|
}
|
||||||
match write_embedded_metadata(embedded, &dest) {
|
let embedded_result = write_embedded_metadata(embedded, &dest);
|
||||||
|
if embedded_result.is_ok()
|
||||||
|
&& let Some(identity) = identity
|
||||||
|
{
|
||||||
|
android_seen.insert(identity);
|
||||||
|
}
|
||||||
|
match embedded_result {
|
||||||
Ok(Some(meta_dest)) => {
|
Ok(Some(meta_dest)) => {
|
||||||
s.metadata += 1;
|
s.metadata += 1;
|
||||||
crate::ui::ok_label(
|
crate::ui::ok_label(
|
||||||
|
|||||||
Reference in new issue
Block a user