xref: /aosp_15_r20/tools/asuite/adevice/src/fingerprint.rs (revision c2e18aaa1096c836b086f94603d04f4eb9cf37f5)
1*c2e18aaaSAndroid Build Coastguard Worker //! Recursively hash the contents of a directory
2*c2e18aaaSAndroid Build Coastguard Worker use anyhow::Result;
3*c2e18aaaSAndroid Build Coastguard Worker use hex::encode;
4*c2e18aaaSAndroid Build Coastguard Worker use rayon::iter::IntoParallelIterator;
5*c2e18aaaSAndroid Build Coastguard Worker use rayon::iter::ParallelIterator;
6*c2e18aaaSAndroid Build Coastguard Worker use ring::digest::{self, SHA256};
7*c2e18aaaSAndroid Build Coastguard Worker use serde::{Deserialize, Serialize};
8*c2e18aaaSAndroid Build Coastguard Worker use std::collections::HashMap;
9*c2e18aaaSAndroid Build Coastguard Worker use std::fs;
10*c2e18aaaSAndroid Build Coastguard Worker use std::io::{self, Read, Write};
11*c2e18aaaSAndroid Build Coastguard Worker use std::os::unix::fs::FileTypeExt;
12*c2e18aaaSAndroid Build Coastguard Worker use std::os::unix::fs::MetadataExt;
13*c2e18aaaSAndroid Build Coastguard Worker use std::os::unix::fs::PermissionsExt;
14*c2e18aaaSAndroid Build Coastguard Worker use std::path::{Path, PathBuf};
15*c2e18aaaSAndroid Build Coastguard Worker use walkdir::WalkDir;
16*c2e18aaaSAndroid Build Coastguard Worker 
17*c2e18aaaSAndroid Build Coastguard Worker #[allow(missing_docs)]
18*c2e18aaaSAndroid Build Coastguard Worker #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
19*c2e18aaaSAndroid Build Coastguard Worker pub enum FileType {
20*c2e18aaaSAndroid Build Coastguard Worker     #[default]
21*c2e18aaaSAndroid Build Coastguard Worker     File,
22*c2e18aaaSAndroid Build Coastguard Worker     Symlink,
23*c2e18aaaSAndroid Build Coastguard Worker     Directory,
24*c2e18aaaSAndroid Build Coastguard Worker }
25*c2e18aaaSAndroid Build Coastguard Worker 
26*c2e18aaaSAndroid Build Coastguard Worker #[derive(Clone, Copy, PartialEq)]
27*c2e18aaaSAndroid Build Coastguard Worker #[allow(dead_code)]
28*c2e18aaaSAndroid Build Coastguard Worker pub enum DiffMode {
29*c2e18aaaSAndroid Build Coastguard Worker     IgnorePermissions,
30*c2e18aaaSAndroid Build Coastguard Worker     UsePermissions,
31*c2e18aaaSAndroid Build Coastguard Worker }
32*c2e18aaaSAndroid Build Coastguard Worker 
33*c2e18aaaSAndroid Build Coastguard Worker /// Represents a file, directory, or symlink.
34*c2e18aaaSAndroid Build Coastguard Worker /// We need enough information to be able to tell if:
35*c2e18aaaSAndroid Build Coastguard Worker ///   1) A regular file changes to a directory or symlink.
36*c2e18aaaSAndroid Build Coastguard Worker ///   2) A symlink's target file path changes.
37*c2e18aaaSAndroid Build Coastguard Worker #[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
38*c2e18aaaSAndroid Build Coastguard Worker pub struct FileMetadata {
39*c2e18aaaSAndroid Build Coastguard Worker     /// Is this a file, dir or symlink?
40*c2e18aaaSAndroid Build Coastguard Worker     pub file_type: FileType,
41*c2e18aaaSAndroid Build Coastguard Worker 
42*c2e18aaaSAndroid Build Coastguard Worker     /// Path that this symlinks to or ""
43*c2e18aaaSAndroid Build Coastguard Worker     #[serde(skip_serializing_if = "String::is_empty", default)]
44*c2e18aaaSAndroid Build Coastguard Worker     pub symlink: String,
45*c2e18aaaSAndroid Build Coastguard Worker 
46*c2e18aaaSAndroid Build Coastguard Worker     /// Sha256 of contents for regular files.
47*c2e18aaaSAndroid Build Coastguard Worker     #[serde(skip_serializing_if = "String::is_empty", default)]
48*c2e18aaaSAndroid Build Coastguard Worker     pub digest: String,
49*c2e18aaaSAndroid Build Coastguard Worker 
50*c2e18aaaSAndroid Build Coastguard Worker     /// Permission bits.
51*c2e18aaaSAndroid Build Coastguard Worker     #[serde(default, skip_serializing_if = "is_default")]
52*c2e18aaaSAndroid Build Coastguard Worker     pub permission_bits: u32,
53*c2e18aaaSAndroid Build Coastguard Worker 
54*c2e18aaaSAndroid Build Coastguard Worker     // A unique string used only to determine if digest should be recomputed
55*c2e18aaaSAndroid Build Coastguard Worker     // or can be used from cache.
56*c2e18aaaSAndroid Build Coastguard Worker     // The key includes: path, size, mtime, and ctime
57*c2e18aaaSAndroid Build Coastguard Worker     //
58*c2e18aaaSAndroid Build Coastguard Worker     // The cache_key will be different for the  host/device and is not used to compare
59*c2e18aaaSAndroid Build Coastguard Worker     // if the files are different
60*c2e18aaaSAndroid Build Coastguard Worker     #[serde(skip)]
61*c2e18aaaSAndroid Build Coastguard Worker     pub cache_key: String,
62*c2e18aaaSAndroid Build Coastguard Worker }
63*c2e18aaaSAndroid Build Coastguard Worker 
is_default<T: Default + PartialEq>(t: &T) -> bool64*c2e18aaaSAndroid Build Coastguard Worker fn is_default<T: Default + PartialEq>(t: &T) -> bool {
65*c2e18aaaSAndroid Build Coastguard Worker     t == &T::default()
66*c2e18aaaSAndroid Build Coastguard Worker }
67*c2e18aaaSAndroid Build Coastguard Worker 
68*c2e18aaaSAndroid Build Coastguard Worker impl FileMetadata {
from_path(file_path: &Path, cache: &Cache) -> Result<Self>69*c2e18aaaSAndroid Build Coastguard Worker     pub fn from_path(file_path: &Path, cache: &Cache) -> Result<Self> {
70*c2e18aaaSAndroid Build Coastguard Worker         let metadata = fs::symlink_metadata(file_path)?;
71*c2e18aaaSAndroid Build Coastguard Worker 
72*c2e18aaaSAndroid Build Coastguard Worker         if metadata.is_dir() {
73*c2e18aaaSAndroid Build Coastguard Worker             Ok(FileMetadata::from_dir())
74*c2e18aaaSAndroid Build Coastguard Worker         } else if metadata.is_symlink() {
75*c2e18aaaSAndroid Build Coastguard Worker             FileMetadata::from_symlink(file_path, &metadata)
76*c2e18aaaSAndroid Build Coastguard Worker         } else {
77*c2e18aaaSAndroid Build Coastguard Worker             Ok(FileMetadata::from_file(file_path, &metadata, cache)?)
78*c2e18aaaSAndroid Build Coastguard Worker         }
79*c2e18aaaSAndroid Build Coastguard Worker     }
80*c2e18aaaSAndroid Build Coastguard Worker 
from_dir() -> Self81*c2e18aaaSAndroid Build Coastguard Worker     pub fn from_dir() -> Self {
82*c2e18aaaSAndroid Build Coastguard Worker         FileMetadata { file_type: FileType::Directory, ..Default::default() }
83*c2e18aaaSAndroid Build Coastguard Worker     }
from_symlink(file_path: &Path, metadata: &fs::Metadata) -> Result<Self>84*c2e18aaaSAndroid Build Coastguard Worker     pub fn from_symlink(file_path: &Path, metadata: &fs::Metadata) -> Result<Self> {
85*c2e18aaaSAndroid Build Coastguard Worker         let link = fs::read_link(file_path)?;
86*c2e18aaaSAndroid Build Coastguard Worker         let target_path_string =
87*c2e18aaaSAndroid Build Coastguard Worker             link.into_os_string().into_string().expect("Expected valid file name");
88*c2e18aaaSAndroid Build Coastguard Worker         let mut perms = 0;
89*c2e18aaaSAndroid Build Coastguard Worker 
90*c2e18aaaSAndroid Build Coastguard Worker         // Getting the permissions doesn't work on windows, so don't try and don't compare them.
91*c2e18aaaSAndroid Build Coastguard Worker         if !cfg!(windows) {
92*c2e18aaaSAndroid Build Coastguard Worker             perms = metadata.permissions().mode();
93*c2e18aaaSAndroid Build Coastguard Worker         }
94*c2e18aaaSAndroid Build Coastguard Worker         Ok(FileMetadata {
95*c2e18aaaSAndroid Build Coastguard Worker             file_type: FileType::Symlink,
96*c2e18aaaSAndroid Build Coastguard Worker             symlink: target_path_string,
97*c2e18aaaSAndroid Build Coastguard Worker             permission_bits: perms,
98*c2e18aaaSAndroid Build Coastguard Worker             ..Default::default()
99*c2e18aaaSAndroid Build Coastguard Worker         })
100*c2e18aaaSAndroid Build Coastguard Worker     }
from_file(file_path: &Path, metadata: &fs::Metadata, cache: &Cache) -> Result<Self>101*c2e18aaaSAndroid Build Coastguard Worker     pub fn from_file(file_path: &Path, metadata: &fs::Metadata, cache: &Cache) -> Result<Self> {
102*c2e18aaaSAndroid Build Coastguard Worker         // Getting the permissions doesn't work on windows, so don't try and don't compare them.
103*c2e18aaaSAndroid Build Coastguard Worker         let mut perms = 0;
104*c2e18aaaSAndroid Build Coastguard Worker         if !cfg!(windows) {
105*c2e18aaaSAndroid Build Coastguard Worker             perms = metadata.permissions().mode();
106*c2e18aaaSAndroid Build Coastguard Worker         }
107*c2e18aaaSAndroid Build Coastguard Worker 
108*c2e18aaaSAndroid Build Coastguard Worker         let (digest, cache_key) = get_or_compute_digest(file_path, metadata, cache)?;
109*c2e18aaaSAndroid Build Coastguard Worker         Ok(FileMetadata {
110*c2e18aaaSAndroid Build Coastguard Worker             file_type: FileType::File,
111*c2e18aaaSAndroid Build Coastguard Worker             digest,
112*c2e18aaaSAndroid Build Coastguard Worker             cache_key,
113*c2e18aaaSAndroid Build Coastguard Worker             permission_bits: perms,
114*c2e18aaaSAndroid Build Coastguard Worker             ..Default::default()
115*c2e18aaaSAndroid Build Coastguard Worker         })
116*c2e18aaaSAndroid Build Coastguard Worker     }
117*c2e18aaaSAndroid Build Coastguard Worker }
118*c2e18aaaSAndroid Build Coastguard Worker 
119*c2e18aaaSAndroid Build Coastguard Worker /// A description of the differences on the filesystems between the host
120*c2e18aaaSAndroid Build Coastguard Worker /// and device. Each file that is different will be a key in one of
121*c2e18aaaSAndroid Build Coastguard Worker /// three maps with the value indicating the difference.
122*c2e18aaaSAndroid Build Coastguard Worker #[derive(Debug, Default, PartialEq)]
123*c2e18aaaSAndroid Build Coastguard Worker pub struct Diffs {
124*c2e18aaaSAndroid Build Coastguard Worker     /// Files on host, but not on device
125*c2e18aaaSAndroid Build Coastguard Worker     pub device_needs: HashMap<PathBuf, FileMetadata>,
126*c2e18aaaSAndroid Build Coastguard Worker     /// Files on device, but not host.
127*c2e18aaaSAndroid Build Coastguard Worker     pub device_extra: HashMap<PathBuf, FileMetadata>,
128*c2e18aaaSAndroid Build Coastguard Worker     /// Files that are different between host and device.
129*c2e18aaaSAndroid Build Coastguard Worker     pub device_diffs: HashMap<PathBuf, FileMetadata>,
130*c2e18aaaSAndroid Build Coastguard Worker }
131*c2e18aaaSAndroid Build Coastguard Worker 
132*c2e18aaaSAndroid Build Coastguard Worker /// Compute the files that need to be added, removed or updated on the device.
133*c2e18aaaSAndroid Build Coastguard Worker /// Each file should land in of the three categories (i.e. updated, not
134*c2e18aaaSAndroid Build Coastguard Worker /// removed and added);
135*c2e18aaaSAndroid Build Coastguard Worker /// TODO(rbraunstein): Fix allow(unused) by breaking out methods not
136*c2e18aaaSAndroid Build Coastguard Worker /// needed by adevice_fingerprint.
137*c2e18aaaSAndroid Build Coastguard Worker #[allow(unused)]
diff( host_files: &HashMap<PathBuf, FileMetadata>, device_files: &HashMap<PathBuf, FileMetadata>, diff_mode: DiffMode, ) -> Diffs138*c2e18aaaSAndroid Build Coastguard Worker pub fn diff(
139*c2e18aaaSAndroid Build Coastguard Worker     host_files: &HashMap<PathBuf, FileMetadata>,
140*c2e18aaaSAndroid Build Coastguard Worker     device_files: &HashMap<PathBuf, FileMetadata>,
141*c2e18aaaSAndroid Build Coastguard Worker     diff_mode: DiffMode,
142*c2e18aaaSAndroid Build Coastguard Worker ) -> Diffs {
143*c2e18aaaSAndroid Build Coastguard Worker     let mut diffs = Diffs {
144*c2e18aaaSAndroid Build Coastguard Worker         device_needs: HashMap::new(),
145*c2e18aaaSAndroid Build Coastguard Worker         device_extra: HashMap::new(),
146*c2e18aaaSAndroid Build Coastguard Worker         device_diffs: HashMap::new(),
147*c2e18aaaSAndroid Build Coastguard Worker     };
148*c2e18aaaSAndroid Build Coastguard Worker 
149*c2e18aaaSAndroid Build Coastguard Worker     // Insert diffs files that are on the host, but not on the device or
150*c2e18aaaSAndroid Build Coastguard Worker     // file on the host that are different on the device.
151*c2e18aaaSAndroid Build Coastguard Worker     for (file_name, host_metadata) in host_files {
152*c2e18aaaSAndroid Build Coastguard Worker         match device_files.get(file_name) {
153*c2e18aaaSAndroid Build Coastguard Worker             // File on host and device, but the metadata is different.
154*c2e18aaaSAndroid Build Coastguard Worker             Some(device_metadata)
155*c2e18aaaSAndroid Build Coastguard Worker                 if is_metadata_diff(device_metadata, host_metadata, diff_mode) =>
156*c2e18aaaSAndroid Build Coastguard Worker             {
157*c2e18aaaSAndroid Build Coastguard Worker                 diffs.device_diffs.insert(file_name.clone(), host_metadata.clone())
158*c2e18aaaSAndroid Build Coastguard Worker             }
159*c2e18aaaSAndroid Build Coastguard Worker             // If the device metadata == host metadata there is nothing to do.
160*c2e18aaaSAndroid Build Coastguard Worker             Some(_) => None,
161*c2e18aaaSAndroid Build Coastguard Worker             // Not on the device yet, insert it
162*c2e18aaaSAndroid Build Coastguard Worker             None => diffs.device_needs.insert(file_name.clone(), host_metadata.clone()),
163*c2e18aaaSAndroid Build Coastguard Worker         };
164*c2e18aaaSAndroid Build Coastguard Worker     }
165*c2e18aaaSAndroid Build Coastguard Worker 
166*c2e18aaaSAndroid Build Coastguard Worker     // Files on the device, but not one the host.
167*c2e18aaaSAndroid Build Coastguard Worker     for (file_name, metadata) in device_files {
168*c2e18aaaSAndroid Build Coastguard Worker         if host_files.get(file_name).is_none() {
169*c2e18aaaSAndroid Build Coastguard Worker             diffs.device_extra.insert(file_name.clone(), metadata.clone());
170*c2e18aaaSAndroid Build Coastguard Worker         }
171*c2e18aaaSAndroid Build Coastguard Worker     }
172*c2e18aaaSAndroid Build Coastguard Worker     diffs
173*c2e18aaaSAndroid Build Coastguard Worker }
174*c2e18aaaSAndroid Build Coastguard Worker 
175*c2e18aaaSAndroid Build Coastguard Worker /// Return true if left != right ignoring cachekey since that include last_modifed
176*c2e18aaaSAndroid Build Coastguard Worker /// When useing DiffMode::IgnorePermissions, clear the permission bits before doing the comparison
is_metadata_diff(left: &FileMetadata, right: &FileMetadata, diff_mode: DiffMode) -> bool177*c2e18aaaSAndroid Build Coastguard Worker pub fn is_metadata_diff(left: &FileMetadata, right: &FileMetadata, diff_mode: DiffMode) -> bool {
178*c2e18aaaSAndroid Build Coastguard Worker     let mut cleared_left = left.clone();
179*c2e18aaaSAndroid Build Coastguard Worker     let mut cleared_right = right.clone();
180*c2e18aaaSAndroid Build Coastguard Worker     cleared_left.cache_key = "".to_string();
181*c2e18aaaSAndroid Build Coastguard Worker     cleared_right.cache_key = "".to_string();
182*c2e18aaaSAndroid Build Coastguard Worker 
183*c2e18aaaSAndroid Build Coastguard Worker     if diff_mode == DiffMode::UsePermissions {
184*c2e18aaaSAndroid Build Coastguard Worker         return cleared_left != cleared_right;
185*c2e18aaaSAndroid Build Coastguard Worker     }
186*c2e18aaaSAndroid Build Coastguard Worker     cleared_left.permission_bits = 0;
187*c2e18aaaSAndroid Build Coastguard Worker     cleared_right.permission_bits = 0;
188*c2e18aaaSAndroid Build Coastguard Worker     cleared_left != cleared_right
189*c2e18aaaSAndroid Build Coastguard Worker }
190*c2e18aaaSAndroid Build Coastguard Worker 
191*c2e18aaaSAndroid Build Coastguard Worker /// Given a `partition_root`, traverse all files under the named |partitions|
192*c2e18aaaSAndroid Build Coastguard Worker /// at the root.  Typically, ["system", "apex"] are partition_names.
193*c2e18aaaSAndroid Build Coastguard Worker /// The keys will be rooted at the `partition root`, ie. if system contains
194*c2e18aaaSAndroid Build Coastguard Worker /// a file named FILE and system is the `partition_root`, the key wil be
195*c2e18aaaSAndroid Build Coastguard Worker /// system/FILE.
196*c2e18aaaSAndroid Build Coastguard Worker /// Cache is used only to speed up computing digests; if the cache_key is the same
197*c2e18aaaSAndroid Build Coastguard Worker /// as an earlier fingerprint; then we reuse it rather than recomputing.
fingerprint_partitions( partition_root: &Path, partition_names: &[PathBuf], ) -> Result<HashMap<PathBuf, FileMetadata>>198*c2e18aaaSAndroid Build Coastguard Worker pub fn fingerprint_partitions(
199*c2e18aaaSAndroid Build Coastguard Worker     partition_root: &Path,
200*c2e18aaaSAndroid Build Coastguard Worker     partition_names: &[PathBuf],
201*c2e18aaaSAndroid Build Coastguard Worker ) -> Result<HashMap<PathBuf, FileMetadata>> {
202*c2e18aaaSAndroid Build Coastguard Worker     let cache = Cache::read().unwrap_or_default();
203*c2e18aaaSAndroid Build Coastguard Worker     let filenames: Vec<PathBuf> = partition_names
204*c2e18aaaSAndroid Build Coastguard Worker         .iter()
205*c2e18aaaSAndroid Build Coastguard Worker         .flat_map(|p| WalkDir::new(partition_root.join(p)).follow_links(false))
206*c2e18aaaSAndroid Build Coastguard Worker         .map(|result| result.expect("Walking directory").path().to_path_buf())
207*c2e18aaaSAndroid Build Coastguard Worker         .collect();
208*c2e18aaaSAndroid Build Coastguard Worker 
209*c2e18aaaSAndroid Build Coastguard Worker     // Compute digest for each file.
210*c2e18aaaSAndroid Build Coastguard Worker     let results = filenames
211*c2e18aaaSAndroid Build Coastguard Worker         .into_par_iter()
212*c2e18aaaSAndroid Build Coastguard Worker         // Walking the /data partition quickly leads to sockets, filter those out.
213*c2e18aaaSAndroid Build Coastguard Worker         .filter(|file_path| !is_special_file(file_path))
214*c2e18aaaSAndroid Build Coastguard Worker         .map(|file_path| {
215*c2e18aaaSAndroid Build Coastguard Worker             (
216*c2e18aaaSAndroid Build Coastguard Worker                 file_path.strip_prefix(partition_root).unwrap().to_owned(),
217*c2e18aaaSAndroid Build Coastguard Worker                 FileMetadata::from_path(&file_path, &cache).unwrap(),
218*c2e18aaaSAndroid Build Coastguard Worker             )
219*c2e18aaaSAndroid Build Coastguard Worker         })
220*c2e18aaaSAndroid Build Coastguard Worker         .collect();
221*c2e18aaaSAndroid Build Coastguard Worker     cache.write(&results)?;
222*c2e18aaaSAndroid Build Coastguard Worker     Ok(results)
223*c2e18aaaSAndroid Build Coastguard Worker }
224*c2e18aaaSAndroid Build Coastguard Worker 
225*c2e18aaaSAndroid Build Coastguard Worker /// Return true for special files like sockets that would be incorrect
226*c2e18aaaSAndroid Build Coastguard Worker /// to digest and we that we can skip when comparing the device
227*c2e18aaaSAndroid Build Coastguard Worker /// to the build tree.
is_special_file(file_path: &Path) -> bool228*c2e18aaaSAndroid Build Coastguard Worker fn is_special_file(file_path: &Path) -> bool {
229*c2e18aaaSAndroid Build Coastguard Worker     // `symlink_metadata` doesn't follow links. We don't want to follow symlinks here.
230*c2e18aaaSAndroid Build Coastguard Worker     // The stat costs much less than the digest operations we are about to perform.
231*c2e18aaaSAndroid Build Coastguard Worker     let file_metadata = fs::symlink_metadata(file_path).expect("no metadata");
232*c2e18aaaSAndroid Build Coastguard Worker     file_metadata.file_type().is_block_device()
233*c2e18aaaSAndroid Build Coastguard Worker         || file_metadata.file_type().is_char_device()
234*c2e18aaaSAndroid Build Coastguard Worker         || file_metadata.file_type().is_fifo()
235*c2e18aaaSAndroid Build Coastguard Worker         || file_metadata.file_type().is_socket()
236*c2e18aaaSAndroid Build Coastguard Worker }
237*c2e18aaaSAndroid Build Coastguard Worker 
238*c2e18aaaSAndroid Build Coastguard Worker /// Compute the sha256 and return it as a lowercase hex string.
compute_digest(file_path: &Path) -> Result<String>239*c2e18aaaSAndroid Build Coastguard Worker fn compute_digest(file_path: &Path) -> Result<String> {
240*c2e18aaaSAndroid Build Coastguard Worker     let input = fs::File::open(file_path)?;
241*c2e18aaaSAndroid Build Coastguard Worker     let mut reader = io::BufReader::new(input);
242*c2e18aaaSAndroid Build Coastguard Worker     let mut context = digest::Context::new(&SHA256);
243*c2e18aaaSAndroid Build Coastguard Worker     let mut buffer = [0; 4096];
244*c2e18aaaSAndroid Build Coastguard Worker 
245*c2e18aaaSAndroid Build Coastguard Worker     loop {
246*c2e18aaaSAndroid Build Coastguard Worker         let num_bytes_read = reader.read(&mut buffer)?;
247*c2e18aaaSAndroid Build Coastguard Worker         if num_bytes_read == 0 {
248*c2e18aaaSAndroid Build Coastguard Worker             break;
249*c2e18aaaSAndroid Build Coastguard Worker         }
250*c2e18aaaSAndroid Build Coastguard Worker         context.update(&buffer[..num_bytes_read]);
251*c2e18aaaSAndroid Build Coastguard Worker     }
252*c2e18aaaSAndroid Build Coastguard Worker 
253*c2e18aaaSAndroid Build Coastguard Worker     Ok(encode(context.finish().as_ref()))
254*c2e18aaaSAndroid Build Coastguard Worker }
255*c2e18aaaSAndroid Build Coastguard Worker 
256*c2e18aaaSAndroid Build Coastguard Worker /// Get digest from cache or compute digest and return a cache_key
get_or_compute_digest( file_path: &Path, metadata: &fs::Metadata, cache: &Cache, ) -> Result<(String, String)>257*c2e18aaaSAndroid Build Coastguard Worker fn get_or_compute_digest(
258*c2e18aaaSAndroid Build Coastguard Worker     file_path: &Path,
259*c2e18aaaSAndroid Build Coastguard Worker     metadata: &fs::Metadata,
260*c2e18aaaSAndroid Build Coastguard Worker     cache: &Cache,
261*c2e18aaaSAndroid Build Coastguard Worker ) -> Result<(String, String)> {
262*c2e18aaaSAndroid Build Coastguard Worker     let cache_key = cache.cache_key(file_path, metadata)?;
263*c2e18aaaSAndroid Build Coastguard Worker     let digest;
264*c2e18aaaSAndroid Build Coastguard Worker     if let Some(cached_digest) = cache.get(&cache_key) {
265*c2e18aaaSAndroid Build Coastguard Worker         digest = cached_digest.to_string();
266*c2e18aaaSAndroid Build Coastguard Worker     } else {
267*c2e18aaaSAndroid Build Coastguard Worker         digest = compute_digest(file_path)?;
268*c2e18aaaSAndroid Build Coastguard Worker     }
269*c2e18aaaSAndroid Build Coastguard Worker     Ok((digest, cache_key))
270*c2e18aaaSAndroid Build Coastguard Worker }
271*c2e18aaaSAndroid Build Coastguard Worker 
272*c2e18aaaSAndroid Build Coastguard Worker // The cache is intended to be used to skip computing digests - it relies on the assumption that unchanged
273*c2e18aaaSAndroid Build Coastguard Worker // file stats imply unchanged content (and therefore, unchanged digest).
274*c2e18aaaSAndroid Build Coastguard Worker //
275*c2e18aaaSAndroid Build Coastguard Worker // The host stores the cache file in $OUT/ by default.  Acloud/physical devices don't have $OUT so will attempt to store
276*c2e18aaaSAndroid Build Coastguard Worker // the cache in /cache/. Any file modification (add/delete/change) triggers a cache key recomputation for that specific file
277*c2e18aaaSAndroid Build Coastguard Worker // and the cache file will be updated.  The cache should persist across reboots but may be deleted between flashes
278*c2e18aaaSAndroid Build Coastguard Worker #[derive(Default)]
279*c2e18aaaSAndroid Build Coastguard Worker pub struct Cache {
280*c2e18aaaSAndroid Build Coastguard Worker     pub data: HashMap<String, String>,
281*c2e18aaaSAndroid Build Coastguard Worker }
282*c2e18aaaSAndroid Build Coastguard Worker impl Cache {
get(&self, key: &str) -> Option<&String>283*c2e18aaaSAndroid Build Coastguard Worker     pub fn get(&self, key: &str) -> Option<&String> {
284*c2e18aaaSAndroid Build Coastguard Worker         self.data.get(key)
285*c2e18aaaSAndroid Build Coastguard Worker     }
286*c2e18aaaSAndroid Build Coastguard Worker 
287*c2e18aaaSAndroid Build Coastguard Worker     // Generate cache key from file metadata
cache_key(&self, file_path: &Path, metadata: &fs::Metadata) -> Result<String>288*c2e18aaaSAndroid Build Coastguard Worker     pub fn cache_key(&self, file_path: &Path, metadata: &fs::Metadata) -> Result<String> {
289*c2e18aaaSAndroid Build Coastguard Worker         Ok(format!(
290*c2e18aaaSAndroid Build Coastguard Worker             "{}#{}#{}.{}#{}.{}",
291*c2e18aaaSAndroid Build Coastguard Worker             file_path.display(),
292*c2e18aaaSAndroid Build Coastguard Worker             metadata.len(),
293*c2e18aaaSAndroid Build Coastguard Worker             metadata.mtime(),
294*c2e18aaaSAndroid Build Coastguard Worker             metadata.mtime_nsec(),
295*c2e18aaaSAndroid Build Coastguard Worker             metadata.ctime(),
296*c2e18aaaSAndroid Build Coastguard Worker             metadata.ctime_nsec()
297*c2e18aaaSAndroid Build Coastguard Worker         ))
298*c2e18aaaSAndroid Build Coastguard Worker     }
299*c2e18aaaSAndroid Build Coastguard Worker 
300*c2e18aaaSAndroid Build Coastguard Worker     /// Reads cache from a file
read_from_file(file_path: &Path) -> Result<Self>301*c2e18aaaSAndroid Build Coastguard Worker     pub fn read_from_file(file_path: &Path) -> Result<Self> {
302*c2e18aaaSAndroid Build Coastguard Worker         let mut file = fs::File::open(file_path)?;
303*c2e18aaaSAndroid Build Coastguard Worker         let mut contents = String::new();
304*c2e18aaaSAndroid Build Coastguard Worker         file.read_to_string(&mut contents)?;
305*c2e18aaaSAndroid Build Coastguard Worker         match serde_json::from_str(&contents) {
306*c2e18aaaSAndroid Build Coastguard Worker             Ok(data) => Ok(Cache { data }),
307*c2e18aaaSAndroid Build Coastguard Worker             Err(_error) => Err(_error.into()),
308*c2e18aaaSAndroid Build Coastguard Worker         }
309*c2e18aaaSAndroid Build Coastguard Worker     }
310*c2e18aaaSAndroid Build Coastguard Worker 
read() -> Result<Self>311*c2e18aaaSAndroid Build Coastguard Worker     pub fn read() -> Result<Self> {
312*c2e18aaaSAndroid Build Coastguard Worker         let cache_file_path = Cache::default_cache_path();
313*c2e18aaaSAndroid Build Coastguard Worker         Cache::read_from_file(&cache_file_path)
314*c2e18aaaSAndroid Build Coastguard Worker     }
315*c2e18aaaSAndroid Build Coastguard Worker 
316*c2e18aaaSAndroid Build Coastguard Worker     /// Writes cache to a file
write_to_file( self, results: &HashMap<PathBuf, FileMetadata>, file_path: &Path, ) -> Result<()>317*c2e18aaaSAndroid Build Coastguard Worker     pub fn write_to_file(
318*c2e18aaaSAndroid Build Coastguard Worker         self,
319*c2e18aaaSAndroid Build Coastguard Worker         results: &HashMap<PathBuf, FileMetadata>,
320*c2e18aaaSAndroid Build Coastguard Worker         file_path: &Path,
321*c2e18aaaSAndroid Build Coastguard Worker     ) -> Result<()> {
322*c2e18aaaSAndroid Build Coastguard Worker         let mut new_cache: HashMap<String, String> = HashMap::new();
323*c2e18aaaSAndroid Build Coastguard Worker         for meta in results.values() {
324*c2e18aaaSAndroid Build Coastguard Worker             if !meta.cache_key.is_empty() {
325*c2e18aaaSAndroid Build Coastguard Worker                 new_cache.insert(meta.cache_key.clone(), meta.digest.clone());
326*c2e18aaaSAndroid Build Coastguard Worker             }
327*c2e18aaaSAndroid Build Coastguard Worker         }
328*c2e18aaaSAndroid Build Coastguard Worker         if new_cache == self.data {
329*c2e18aaaSAndroid Build Coastguard Worker             // cache did not change - skip write.
330*c2e18aaaSAndroid Build Coastguard Worker             return Ok(());
331*c2e18aaaSAndroid Build Coastguard Worker         }
332*c2e18aaaSAndroid Build Coastguard Worker         let cache_str = serde_json::to_string(&new_cache)?;
333*c2e18aaaSAndroid Build Coastguard Worker         let mut file = fs::File::create(file_path)?;
334*c2e18aaaSAndroid Build Coastguard Worker         file.write_all(cache_str.as_bytes())?;
335*c2e18aaaSAndroid Build Coastguard Worker         Ok(())
336*c2e18aaaSAndroid Build Coastguard Worker     }
337*c2e18aaaSAndroid Build Coastguard Worker 
write(self, results: &HashMap<PathBuf, FileMetadata>) -> Result<()>338*c2e18aaaSAndroid Build Coastguard Worker     pub fn write(self, results: &HashMap<PathBuf, FileMetadata>) -> Result<()> {
339*c2e18aaaSAndroid Build Coastguard Worker         let cache_file_path = Cache::default_cache_path();
340*c2e18aaaSAndroid Build Coastguard Worker         self.write_to_file(results, &cache_file_path)
341*c2e18aaaSAndroid Build Coastguard Worker     }
342*c2e18aaaSAndroid Build Coastguard Worker 
default_cache_path() -> PathBuf343*c2e18aaaSAndroid Build Coastguard Worker     pub fn default_cache_path() -> PathBuf {
344*c2e18aaaSAndroid Build Coastguard Worker         // Attempt to use $OUT, then /cache and finally fall back to /tmp
345*c2e18aaaSAndroid Build Coastguard Worker         // /tmp is deleted on reboot on acloud devices
346*c2e18aaaSAndroid Build Coastguard Worker         let mut cache_dir = std::env::var("OUT").unwrap_or_else(|_| "/cache".to_string());
347*c2e18aaaSAndroid Build Coastguard Worker         if !Path::new(&cache_dir).is_dir() {
348*c2e18aaaSAndroid Build Coastguard Worker             cache_dir = "/tmp".to_string();
349*c2e18aaaSAndroid Build Coastguard Worker         }
350*c2e18aaaSAndroid Build Coastguard Worker 
351*c2e18aaaSAndroid Build Coastguard Worker         PathBuf::from(cache_dir).join("adevice_digest_cache.json")
352*c2e18aaaSAndroid Build Coastguard Worker     }
353*c2e18aaaSAndroid Build Coastguard Worker }
354*c2e18aaaSAndroid Build Coastguard Worker 
355*c2e18aaaSAndroid Build Coastguard Worker #[cfg(test)]
356*c2e18aaaSAndroid Build Coastguard Worker mod tests {
357*c2e18aaaSAndroid Build Coastguard Worker     use super::*;
358*c2e18aaaSAndroid Build Coastguard Worker     use crate::fingerprint::DiffMode::UsePermissions;
359*c2e18aaaSAndroid Build Coastguard Worker     use std::collections::BTreeSet;
360*c2e18aaaSAndroid Build Coastguard Worker     use std::path::PathBuf;
361*c2e18aaaSAndroid Build Coastguard Worker     use tempfile::TempDir;
362*c2e18aaaSAndroid Build Coastguard Worker 
363*c2e18aaaSAndroid Build Coastguard Worker     #[test]
empty_inputs()364*c2e18aaaSAndroid Build Coastguard Worker     fn empty_inputs() {
365*c2e18aaaSAndroid Build Coastguard Worker         assert_eq!(diff(&HashMap::new(), &HashMap::new(), UsePermissions), Diffs::default());
366*c2e18aaaSAndroid Build Coastguard Worker     }
367*c2e18aaaSAndroid Build Coastguard Worker 
368*c2e18aaaSAndroid Build Coastguard Worker     #[test]
same_inputs()369*c2e18aaaSAndroid Build Coastguard Worker     fn same_inputs() {
370*c2e18aaaSAndroid Build Coastguard Worker         let file_entry = HashMap::from([(
371*c2e18aaaSAndroid Build Coastguard Worker             PathBuf::from("a/b/foo.so"),
372*c2e18aaaSAndroid Build Coastguard Worker             FileMetadata {
373*c2e18aaaSAndroid Build Coastguard Worker                 file_type: FileType::File,
374*c2e18aaaSAndroid Build Coastguard Worker                 digest: "deadbeef".to_string(),
375*c2e18aaaSAndroid Build Coastguard Worker                 ..Default::default()
376*c2e18aaaSAndroid Build Coastguard Worker             },
377*c2e18aaaSAndroid Build Coastguard Worker         )]);
378*c2e18aaaSAndroid Build Coastguard Worker         assert_eq!(diff(&file_entry, &file_entry.clone(), UsePermissions), Diffs::default());
379*c2e18aaaSAndroid Build Coastguard Worker     }
380*c2e18aaaSAndroid Build Coastguard Worker 
381*c2e18aaaSAndroid Build Coastguard Worker     #[test]
same_inputs_with_permissions()382*c2e18aaaSAndroid Build Coastguard Worker     fn same_inputs_with_permissions() {
383*c2e18aaaSAndroid Build Coastguard Worker         let file_entry = HashMap::from([(
384*c2e18aaaSAndroid Build Coastguard Worker             PathBuf::from("a/b/foo.so"),
385*c2e18aaaSAndroid Build Coastguard Worker             FileMetadata {
386*c2e18aaaSAndroid Build Coastguard Worker                 file_type: FileType::File,
387*c2e18aaaSAndroid Build Coastguard Worker                 digest: "deadbeef".to_string(),
388*c2e18aaaSAndroid Build Coastguard Worker                 permission_bits: 0o644,
389*c2e18aaaSAndroid Build Coastguard Worker                 ..Default::default()
390*c2e18aaaSAndroid Build Coastguard Worker             },
391*c2e18aaaSAndroid Build Coastguard Worker         )]);
392*c2e18aaaSAndroid Build Coastguard Worker         assert_eq!(diff(&file_entry, &file_entry.clone(), UsePermissions), Diffs::default());
393*c2e18aaaSAndroid Build Coastguard Worker     }
394*c2e18aaaSAndroid Build Coastguard Worker 
395*c2e18aaaSAndroid Build Coastguard Worker     #[test]
same_inputs_with_different_permissions_are_not_equal()396*c2e18aaaSAndroid Build Coastguard Worker     fn same_inputs_with_different_permissions_are_not_equal() {
397*c2e18aaaSAndroid Build Coastguard Worker         let orig = HashMap::from([(
398*c2e18aaaSAndroid Build Coastguard Worker             PathBuf::from("a/b/foo.so"),
399*c2e18aaaSAndroid Build Coastguard Worker             FileMetadata {
400*c2e18aaaSAndroid Build Coastguard Worker                 file_type: FileType::File,
401*c2e18aaaSAndroid Build Coastguard Worker                 digest: "deadbeef".to_string(),
402*c2e18aaaSAndroid Build Coastguard Worker                 permission_bits: 0o644,
403*c2e18aaaSAndroid Build Coastguard Worker                 ..Default::default()
404*c2e18aaaSAndroid Build Coastguard Worker             },
405*c2e18aaaSAndroid Build Coastguard Worker         )]);
406*c2e18aaaSAndroid Build Coastguard Worker         let mut copy = orig.clone();
407*c2e18aaaSAndroid Build Coastguard Worker         copy.entry(PathBuf::from("a/b/foo.so")).and_modify(|v| v.permission_bits = 0);
408*c2e18aaaSAndroid Build Coastguard Worker 
409*c2e18aaaSAndroid Build Coastguard Worker         // Not equal
410*c2e18aaaSAndroid Build Coastguard Worker         assert_ne!(diff(&orig, &copy, UsePermissions), Diffs::default());
411*c2e18aaaSAndroid Build Coastguard Worker     }
412*c2e18aaaSAndroid Build Coastguard Worker 
413*c2e18aaaSAndroid Build Coastguard Worker     #[test]
same_inputs_ignoring_permissions()414*c2e18aaaSAndroid Build Coastguard Worker     fn same_inputs_ignoring_permissions() {
415*c2e18aaaSAndroid Build Coastguard Worker         let orig = HashMap::from([(
416*c2e18aaaSAndroid Build Coastguard Worker             PathBuf::from("a/b/foo.so"),
417*c2e18aaaSAndroid Build Coastguard Worker             FileMetadata {
418*c2e18aaaSAndroid Build Coastguard Worker                 file_type: FileType::File,
419*c2e18aaaSAndroid Build Coastguard Worker                 digest: "deadbeef".to_string(),
420*c2e18aaaSAndroid Build Coastguard Worker                 permission_bits: 0o644,
421*c2e18aaaSAndroid Build Coastguard Worker                 ..Default::default()
422*c2e18aaaSAndroid Build Coastguard Worker             },
423*c2e18aaaSAndroid Build Coastguard Worker         )]);
424*c2e18aaaSAndroid Build Coastguard Worker         let mut copy = orig.clone();
425*c2e18aaaSAndroid Build Coastguard Worker         copy.entry(PathBuf::from("a/b/foo.so")).and_modify(|v| v.permission_bits = 0);
426*c2e18aaaSAndroid Build Coastguard Worker 
427*c2e18aaaSAndroid Build Coastguard Worker         // Equal when we ignore the different permission bits.
428*c2e18aaaSAndroid Build Coastguard Worker         assert_eq!(diff(&orig, &copy, DiffMode::IgnorePermissions), Diffs::default());
429*c2e18aaaSAndroid Build Coastguard Worker     }
430*c2e18aaaSAndroid Build Coastguard Worker 
431*c2e18aaaSAndroid Build Coastguard Worker     #[test]
different_file_type()432*c2e18aaaSAndroid Build Coastguard Worker     fn different_file_type() {
433*c2e18aaaSAndroid Build Coastguard Worker         let host_map_with_filename_as_file = HashMap::from([(
434*c2e18aaaSAndroid Build Coastguard Worker             PathBuf::from("a/b/foo.so"),
435*c2e18aaaSAndroid Build Coastguard Worker             FileMetadata {
436*c2e18aaaSAndroid Build Coastguard Worker                 file_type: FileType::File,
437*c2e18aaaSAndroid Build Coastguard Worker                 digest: "deadbeef".to_string(),
438*c2e18aaaSAndroid Build Coastguard Worker                 ..Default::default()
439*c2e18aaaSAndroid Build Coastguard Worker             },
440*c2e18aaaSAndroid Build Coastguard Worker         )]);
441*c2e18aaaSAndroid Build Coastguard Worker 
442*c2e18aaaSAndroid Build Coastguard Worker         let device_map_with_filename_as_dir = HashMap::from([(
443*c2e18aaaSAndroid Build Coastguard Worker             PathBuf::from("a/b/foo.so"),
444*c2e18aaaSAndroid Build Coastguard Worker             FileMetadata { file_type: FileType::Directory, ..Default::default() },
445*c2e18aaaSAndroid Build Coastguard Worker         )]);
446*c2e18aaaSAndroid Build Coastguard Worker 
447*c2e18aaaSAndroid Build Coastguard Worker         let diffs =
448*c2e18aaaSAndroid Build Coastguard Worker             diff(&host_map_with_filename_as_file, &device_map_with_filename_as_dir, UsePermissions);
449*c2e18aaaSAndroid Build Coastguard Worker         assert_eq!(
450*c2e18aaaSAndroid Build Coastguard Worker             diffs.device_diffs.get(&PathBuf::from("a/b/foo.so")).expect("Missing file"),
451*c2e18aaaSAndroid Build Coastguard Worker             // `diff` returns FileMetadata for host, but we really only care that the
452*c2e18aaaSAndroid Build Coastguard Worker             // file name was found.
453*c2e18aaaSAndroid Build Coastguard Worker             &FileMetadata {
454*c2e18aaaSAndroid Build Coastguard Worker                 file_type: FileType::File,
455*c2e18aaaSAndroid Build Coastguard Worker                 digest: "deadbeef".to_string(),
456*c2e18aaaSAndroid Build Coastguard Worker                 ..Default::default()
457*c2e18aaaSAndroid Build Coastguard Worker             },
458*c2e18aaaSAndroid Build Coastguard Worker         );
459*c2e18aaaSAndroid Build Coastguard Worker     }
460*c2e18aaaSAndroid Build Coastguard Worker 
461*c2e18aaaSAndroid Build Coastguard Worker     #[test]
diff_simple_trees()462*c2e18aaaSAndroid Build Coastguard Worker     fn diff_simple_trees() {
463*c2e18aaaSAndroid Build Coastguard Worker         let host_map = HashMap::from([
464*c2e18aaaSAndroid Build Coastguard Worker             (PathBuf::from("matching_file"), file_metadata("digest_matching_file")),
465*c2e18aaaSAndroid Build Coastguard Worker             (PathBuf::from("path/to/diff_file"), file_metadata("digest_file2")),
466*c2e18aaaSAndroid Build Coastguard Worker             (PathBuf::from("path/to/new_file"), file_metadata("digest_new_file")),
467*c2e18aaaSAndroid Build Coastguard Worker             (PathBuf::from("same_link"), link_metadata("matching_file")),
468*c2e18aaaSAndroid Build Coastguard Worker             (PathBuf::from("diff_link"), link_metadata("targetxx")),
469*c2e18aaaSAndroid Build Coastguard Worker             (PathBuf::from("new_link"), link_metadata("new_target")),
470*c2e18aaaSAndroid Build Coastguard Worker             (PathBuf::from("matching dir"), dir_metadata()),
471*c2e18aaaSAndroid Build Coastguard Worker             (PathBuf::from("new_dir"), dir_metadata()),
472*c2e18aaaSAndroid Build Coastguard Worker         ]);
473*c2e18aaaSAndroid Build Coastguard Worker 
474*c2e18aaaSAndroid Build Coastguard Worker         let device_map = HashMap::from([
475*c2e18aaaSAndroid Build Coastguard Worker             (PathBuf::from("matching_file"), file_metadata("digest_matching_file")),
476*c2e18aaaSAndroid Build Coastguard Worker             (PathBuf::from("path/to/diff_file"), file_metadata("digest_file2_DIFF")),
477*c2e18aaaSAndroid Build Coastguard Worker             (PathBuf::from("path/to/deleted_file"), file_metadata("digest_deleted_file")),
478*c2e18aaaSAndroid Build Coastguard Worker             (PathBuf::from("same_link"), link_metadata("matching_file")),
479*c2e18aaaSAndroid Build Coastguard Worker             (PathBuf::from("diff_link"), link_metadata("targetxx_DIFF")),
480*c2e18aaaSAndroid Build Coastguard Worker             (PathBuf::from("deleted_link"), link_metadata("new_target")),
481*c2e18aaaSAndroid Build Coastguard Worker             (PathBuf::from("matching dir"), dir_metadata()),
482*c2e18aaaSAndroid Build Coastguard Worker             (PathBuf::from("deleted_dir"), dir_metadata()),
483*c2e18aaaSAndroid Build Coastguard Worker         ]);
484*c2e18aaaSAndroid Build Coastguard Worker 
485*c2e18aaaSAndroid Build Coastguard Worker         let diffs = diff(&host_map, &device_map, UsePermissions);
486*c2e18aaaSAndroid Build Coastguard Worker         assert_eq!(
487*c2e18aaaSAndroid Build Coastguard Worker             BTreeSet::from_iter(diffs.device_diffs.keys()),
488*c2e18aaaSAndroid Build Coastguard Worker             BTreeSet::from([&PathBuf::from("diff_link"), &PathBuf::from("path/to/diff_file")])
489*c2e18aaaSAndroid Build Coastguard Worker         );
490*c2e18aaaSAndroid Build Coastguard Worker         assert_eq!(
491*c2e18aaaSAndroid Build Coastguard Worker             BTreeSet::from_iter(diffs.device_needs.keys()),
492*c2e18aaaSAndroid Build Coastguard Worker             BTreeSet::from([
493*c2e18aaaSAndroid Build Coastguard Worker                 &PathBuf::from("path/to/new_file"),
494*c2e18aaaSAndroid Build Coastguard Worker                 &PathBuf::from("new_link"),
495*c2e18aaaSAndroid Build Coastguard Worker                 &PathBuf::from("new_dir")
496*c2e18aaaSAndroid Build Coastguard Worker             ])
497*c2e18aaaSAndroid Build Coastguard Worker         );
498*c2e18aaaSAndroid Build Coastguard Worker         assert_eq!(
499*c2e18aaaSAndroid Build Coastguard Worker             BTreeSet::from_iter(diffs.device_extra.keys()),
500*c2e18aaaSAndroid Build Coastguard Worker             BTreeSet::from([
501*c2e18aaaSAndroid Build Coastguard Worker                 &PathBuf::from("path/to/deleted_file"),
502*c2e18aaaSAndroid Build Coastguard Worker                 &PathBuf::from("deleted_link"),
503*c2e18aaaSAndroid Build Coastguard Worker                 &PathBuf::from("deleted_dir")
504*c2e18aaaSAndroid Build Coastguard Worker             ])
505*c2e18aaaSAndroid Build Coastguard Worker         );
506*c2e18aaaSAndroid Build Coastguard Worker     }
507*c2e18aaaSAndroid Build Coastguard Worker 
508*c2e18aaaSAndroid Build Coastguard Worker     #[test]
compute_digest_empty_file()509*c2e18aaaSAndroid Build Coastguard Worker     fn compute_digest_empty_file() {
510*c2e18aaaSAndroid Build Coastguard Worker         let tmpdir = TempDir::new().unwrap();
511*c2e18aaaSAndroid Build Coastguard Worker         let file_path = tmpdir.path().join("empty_file");
512*c2e18aaaSAndroid Build Coastguard Worker         fs::write(&file_path, "").unwrap();
513*c2e18aaaSAndroid Build Coastguard Worker         assert_eq!(
514*c2e18aaaSAndroid Build Coastguard Worker             "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855".to_string(),
515*c2e18aaaSAndroid Build Coastguard Worker             compute_digest(&file_path).unwrap()
516*c2e18aaaSAndroid Build Coastguard Worker         );
517*c2e18aaaSAndroid Build Coastguard Worker     }
518*c2e18aaaSAndroid Build Coastguard Worker 
519*c2e18aaaSAndroid Build Coastguard Worker     #[test]
compute_digest_small_file()520*c2e18aaaSAndroid Build Coastguard Worker     fn compute_digest_small_file() {
521*c2e18aaaSAndroid Build Coastguard Worker         let tmpdir = TempDir::new().unwrap();
522*c2e18aaaSAndroid Build Coastguard Worker         let file_path = tmpdir.path().join("small_file");
523*c2e18aaaSAndroid Build Coastguard Worker         fs::write(&file_path, "This is a test\nof a small file.\n").unwrap();
524*c2e18aaaSAndroid Build Coastguard Worker         assert_eq!(
525*c2e18aaaSAndroid Build Coastguard Worker             "a519d054afdf2abfbdd90a738d248f606685d6c187e96390bde22e958240449e".to_string(),
526*c2e18aaaSAndroid Build Coastguard Worker             compute_digest(&file_path).unwrap()
527*c2e18aaaSAndroid Build Coastguard Worker         );
528*c2e18aaaSAndroid Build Coastguard Worker     }
529*c2e18aaaSAndroid Build Coastguard Worker 
530*c2e18aaaSAndroid Build Coastguard Worker     #[test]
get_or_compute_digest_small_file()531*c2e18aaaSAndroid Build Coastguard Worker     fn get_or_compute_digest_small_file() {
532*c2e18aaaSAndroid Build Coastguard Worker         let tmpdir = TempDir::new().unwrap();
533*c2e18aaaSAndroid Build Coastguard Worker         let file_path = tmpdir.path().join("small_file");
534*c2e18aaaSAndroid Build Coastguard Worker         fs::write(&file_path, "This is a test\nof a small file.\n").unwrap();
535*c2e18aaaSAndroid Build Coastguard Worker 
536*c2e18aaaSAndroid Build Coastguard Worker         let metadata = fs::metadata(&file_path).expect("file metadata");
537*c2e18aaaSAndroid Build Coastguard Worker         let mut cache = Cache::default();
538*c2e18aaaSAndroid Build Coastguard Worker         let (digest, cache_key) = get_or_compute_digest(&file_path, &metadata, &cache).unwrap();
539*c2e18aaaSAndroid Build Coastguard Worker         assert_eq!(
540*c2e18aaaSAndroid Build Coastguard Worker             "a519d054afdf2abfbdd90a738d248f606685d6c187e96390bde22e958240449e".to_string(),
541*c2e18aaaSAndroid Build Coastguard Worker             digest
542*c2e18aaaSAndroid Build Coastguard Worker         );
543*c2e18aaaSAndroid Build Coastguard Worker         assert_eq!(
544*c2e18aaaSAndroid Build Coastguard Worker             cache_key,
545*c2e18aaaSAndroid Build Coastguard Worker             format!(
546*c2e18aaaSAndroid Build Coastguard Worker                 "{}#{}#{}.{}#{}.{}",
547*c2e18aaaSAndroid Build Coastguard Worker                 file_path.display(),
548*c2e18aaaSAndroid Build Coastguard Worker                 metadata.len(),
549*c2e18aaaSAndroid Build Coastguard Worker                 metadata.mtime(),
550*c2e18aaaSAndroid Build Coastguard Worker                 metadata.mtime_nsec(),
551*c2e18aaaSAndroid Build Coastguard Worker                 metadata.ctime(),
552*c2e18aaaSAndroid Build Coastguard Worker                 metadata.ctime_nsec()
553*c2e18aaaSAndroid Build Coastguard Worker             )
554*c2e18aaaSAndroid Build Coastguard Worker         );
555*c2e18aaaSAndroid Build Coastguard Worker 
556*c2e18aaaSAndroid Build Coastguard Worker         // if cache entry exists; than that is used instead of recomputing
557*c2e18aaaSAndroid Build Coastguard Worker         cache.data.insert(cache_key, "test-saved-cache-digest".to_string());
558*c2e18aaaSAndroid Build Coastguard Worker         let (digest, _) = get_or_compute_digest(&file_path, &metadata, &cache).unwrap();
559*c2e18aaaSAndroid Build Coastguard Worker         assert_eq!("test-saved-cache-digest".to_string(), digest);
560*c2e18aaaSAndroid Build Coastguard Worker     }
561*c2e18aaaSAndroid Build Coastguard Worker 
562*c2e18aaaSAndroid Build Coastguard Worker     // Generate some files near the buffer size to check for off-by-one errors
563*c2e18aaaSAndroid Build Coastguard Worker     // and compute the digest and store here.
564*c2e18aaaSAndroid Build Coastguard Worker     // We can't check these into testdata and read from testdata unless we serialize
565*c2e18aaaSAndroid Build Coastguard Worker     // all the tests. Some tests to `cd` to create relative symlinks and that affects
566*c2e18aaaSAndroid Build Coastguard Worker     // any tests that want to read from testdata.
567*c2e18aaaSAndroid Build Coastguard Worker     #[test]
verify_edge_case_digests()568*c2e18aaaSAndroid Build Coastguard Worker     fn verify_edge_case_digests() {
569*c2e18aaaSAndroid Build Coastguard Worker         let tmpdir = TempDir::new().unwrap();
570*c2e18aaaSAndroid Build Coastguard Worker         // We could use a RNG with a seed, but lets just create simple files of bytes.
571*c2e18aaaSAndroid Build Coastguard Worker         let raw_bytes: &[u8; 10] = &[0, 1, 17, 200, 11, 8, 0, 32, 9, 10];
572*c2e18aaaSAndroid Build Coastguard Worker         let mut boring_buff = Vec::new();
573*c2e18aaaSAndroid Build Coastguard Worker         for _ in 1..1000 {
574*c2e18aaaSAndroid Build Coastguard Worker             boring_buff.extend_from_slice(raw_bytes);
575*c2e18aaaSAndroid Build Coastguard Worker         }
576*c2e18aaaSAndroid Build Coastguard Worker 
577*c2e18aaaSAndroid Build Coastguard Worker         for (num_bytes, digest) in
578*c2e18aaaSAndroid Build Coastguard Worker             &[(4095, "a0e88b2743"), (4096, "b2e324aac3"), (4097, "70fcbe6a8d")]
579*c2e18aaaSAndroid Build Coastguard Worker         {
580*c2e18aaaSAndroid Build Coastguard Worker             let file_path = tmpdir.path().join(num_bytes.to_string());
581*c2e18aaaSAndroid Build Coastguard Worker             fs::write(&file_path, &boring_buff[0..*num_bytes]).unwrap();
582*c2e18aaaSAndroid Build Coastguard Worker             assert!(
583*c2e18aaaSAndroid Build Coastguard Worker                 compute_digest(&file_path).unwrap().starts_with(digest),
584*c2e18aaaSAndroid Build Coastguard Worker                 "Expected file {:?} to have a digest starting with {:?}",
585*c2e18aaaSAndroid Build Coastguard Worker                 file_path,
586*c2e18aaaSAndroid Build Coastguard Worker                 digest
587*c2e18aaaSAndroid Build Coastguard Worker             );
588*c2e18aaaSAndroid Build Coastguard Worker         }
589*c2e18aaaSAndroid Build Coastguard Worker     }
590*c2e18aaaSAndroid Build Coastguard Worker 
591*c2e18aaaSAndroid Build Coastguard Worker     #[test]
fingerprint_file_for_file()592*c2e18aaaSAndroid Build Coastguard Worker     fn fingerprint_file_for_file() {
593*c2e18aaaSAndroid Build Coastguard Worker         let partition_root = TempDir::new().unwrap();
594*c2e18aaaSAndroid Build Coastguard Worker         let file_path = partition_root.path().join("small_file");
595*c2e18aaaSAndroid Build Coastguard Worker         fs::write(&file_path, "This is a test\nof a small file.\n").unwrap();
596*c2e18aaaSAndroid Build Coastguard Worker 
597*c2e18aaaSAndroid Build Coastguard Worker         // NOTE: files are 0x644 on the host tests and 0x655 on device tests
598*c2e18aaaSAndroid Build Coastguard Worker         // for me and CI so changing the file to always be 655 during the test.
599*c2e18aaaSAndroid Build Coastguard Worker         let mut perms = fs::metadata(&file_path).expect("Getting permissions").permissions();
600*c2e18aaaSAndroid Build Coastguard Worker         perms.set_mode(0o100655);
601*c2e18aaaSAndroid Build Coastguard Worker         assert!(fs::set_permissions(&file_path, perms).is_ok());
602*c2e18aaaSAndroid Build Coastguard Worker         let cache = Cache::default();
603*c2e18aaaSAndroid Build Coastguard Worker         let entry = FileMetadata::from_path(&file_path, &cache).unwrap();
604*c2e18aaaSAndroid Build Coastguard Worker         assert_eq!(
605*c2e18aaaSAndroid Build Coastguard Worker             FileMetadata {
606*c2e18aaaSAndroid Build Coastguard Worker                 file_type: FileType::File,
607*c2e18aaaSAndroid Build Coastguard Worker                 digest: "a519d054afdf2abfbdd90a738d248f606685d6c187e96390bde22e958240449e"
608*c2e18aaaSAndroid Build Coastguard Worker                     .to_string(),
609*c2e18aaaSAndroid Build Coastguard Worker                 permission_bits: 0o100655,
610*c2e18aaaSAndroid Build Coastguard Worker                 cache_key: entry.cache_key.clone(),
611*c2e18aaaSAndroid Build Coastguard Worker                 ..Default::default()
612*c2e18aaaSAndroid Build Coastguard Worker             },
613*c2e18aaaSAndroid Build Coastguard Worker             entry
614*c2e18aaaSAndroid Build Coastguard Worker         )
615*c2e18aaaSAndroid Build Coastguard Worker     }
616*c2e18aaaSAndroid Build Coastguard Worker 
617*c2e18aaaSAndroid Build Coastguard Worker     #[test]
fingerprint_file_for_relative_symlink()618*c2e18aaaSAndroid Build Coastguard Worker     fn fingerprint_file_for_relative_symlink() {
619*c2e18aaaSAndroid Build Coastguard Worker         let partition_root = TempDir::new().unwrap();
620*c2e18aaaSAndroid Build Coastguard Worker         let file_path = partition_root.path().join("small_file");
621*c2e18aaaSAndroid Build Coastguard Worker         fs::write(file_path, "This is a test\nof a small file.\n").unwrap();
622*c2e18aaaSAndroid Build Coastguard Worker 
623*c2e18aaaSAndroid Build Coastguard Worker         let link = create_symlink(
624*c2e18aaaSAndroid Build Coastguard Worker             &PathBuf::from("small_file"),
625*c2e18aaaSAndroid Build Coastguard Worker             "link_to_small_file",
626*c2e18aaaSAndroid Build Coastguard Worker             partition_root.path(),
627*c2e18aaaSAndroid Build Coastguard Worker         );
628*c2e18aaaSAndroid Build Coastguard Worker         let cache = Cache::default();
629*c2e18aaaSAndroid Build Coastguard Worker         let entry = FileMetadata::from_path(&link, &cache).unwrap();
630*c2e18aaaSAndroid Build Coastguard Worker         assert_eq!(
631*c2e18aaaSAndroid Build Coastguard Worker             FileMetadata {
632*c2e18aaaSAndroid Build Coastguard Worker                 file_type: FileType::Symlink,
633*c2e18aaaSAndroid Build Coastguard Worker                 symlink: "small_file".to_string(),
634*c2e18aaaSAndroid Build Coastguard Worker                 permission_bits: 0o120777,
635*c2e18aaaSAndroid Build Coastguard Worker                 ..Default::default()
636*c2e18aaaSAndroid Build Coastguard Worker             },
637*c2e18aaaSAndroid Build Coastguard Worker             entry
638*c2e18aaaSAndroid Build Coastguard Worker         )
639*c2e18aaaSAndroid Build Coastguard Worker     }
640*c2e18aaaSAndroid Build Coastguard Worker 
641*c2e18aaaSAndroid Build Coastguard Worker     #[test]
fingerprint_file_for_absolute_symlink()642*c2e18aaaSAndroid Build Coastguard Worker     fn fingerprint_file_for_absolute_symlink() {
643*c2e18aaaSAndroid Build Coastguard Worker         let partition_root = TempDir::new().unwrap();
644*c2e18aaaSAndroid Build Coastguard Worker         let link = create_symlink(&PathBuf::from("/tmp"), "link_to_tmp", partition_root.path());
645*c2e18aaaSAndroid Build Coastguard Worker         let cache = Cache::default();
646*c2e18aaaSAndroid Build Coastguard Worker         let entry = FileMetadata::from_path(&link, &cache).unwrap();
647*c2e18aaaSAndroid Build Coastguard Worker         assert_eq!(
648*c2e18aaaSAndroid Build Coastguard Worker             FileMetadata {
649*c2e18aaaSAndroid Build Coastguard Worker                 file_type: FileType::Symlink,
650*c2e18aaaSAndroid Build Coastguard Worker                 symlink: "/tmp".to_string(),
651*c2e18aaaSAndroid Build Coastguard Worker                 permission_bits: 0o120777,
652*c2e18aaaSAndroid Build Coastguard Worker                 ..Default::default()
653*c2e18aaaSAndroid Build Coastguard Worker             },
654*c2e18aaaSAndroid Build Coastguard Worker             entry
655*c2e18aaaSAndroid Build Coastguard Worker         )
656*c2e18aaaSAndroid Build Coastguard Worker     }
657*c2e18aaaSAndroid Build Coastguard Worker 
658*c2e18aaaSAndroid Build Coastguard Worker     #[test]
fingerprint_file_for_directory()659*c2e18aaaSAndroid Build Coastguard Worker     fn fingerprint_file_for_directory() {
660*c2e18aaaSAndroid Build Coastguard Worker         let partition_root = TempDir::new().unwrap();
661*c2e18aaaSAndroid Build Coastguard Worker         let newdir_path = partition_root.path().join("some_dir");
662*c2e18aaaSAndroid Build Coastguard Worker         fs::create_dir(&newdir_path).expect("Should have create 'some_dir' in temp dir");
663*c2e18aaaSAndroid Build Coastguard Worker         let cache = Cache::default();
664*c2e18aaaSAndroid Build Coastguard Worker         let entry = FileMetadata::from_path(&newdir_path, &cache).unwrap();
665*c2e18aaaSAndroid Build Coastguard Worker         assert_eq!(FileMetadata { file_type: FileType::Directory, ..Default::default() }, entry)
666*c2e18aaaSAndroid Build Coastguard Worker     }
667*c2e18aaaSAndroid Build Coastguard Worker 
668*c2e18aaaSAndroid Build Coastguard Worker     #[test]
fingerprint_file_on_bad_path_reports_err()669*c2e18aaaSAndroid Build Coastguard Worker     fn fingerprint_file_on_bad_path_reports_err() {
670*c2e18aaaSAndroid Build Coastguard Worker         let cache = Cache::default();
671*c2e18aaaSAndroid Build Coastguard Worker         if FileMetadata::from_path(Path::new("testdata/not_exist"), &cache).is_ok() {
672*c2e18aaaSAndroid Build Coastguard Worker             panic!("Should have failed on invalid path")
673*c2e18aaaSAndroid Build Coastguard Worker         }
674*c2e18aaaSAndroid Build Coastguard Worker     }
675*c2e18aaaSAndroid Build Coastguard Worker 
676*c2e18aaaSAndroid Build Coastguard Worker     /// /tmp/.tmpxO0pRC/system
677*c2e18aaaSAndroid Build Coastguard Worker     /// % tree
678*c2e18aaaSAndroid Build Coastguard Worker     /// .
679*c2e18aaaSAndroid Build Coastguard Worker     /// ├── cycle1 -> cycle2
680*c2e18aaaSAndroid Build Coastguard Worker     /// ├── cycle2 -> cycle1
681*c2e18aaaSAndroid Build Coastguard Worker     /// ├── danglers
682*c2e18aaaSAndroid Build Coastguard Worker     /// │   ├── d1 -> nowhere
683*c2e18aaaSAndroid Build Coastguard Worker     /// │   └── d2 -> /not/existing
684*c2e18aaaSAndroid Build Coastguard Worker     /// ├── dir1
685*c2e18aaaSAndroid Build Coastguard Worker     /// │   ├── dir2
686*c2e18aaaSAndroid Build Coastguard Worker     /// │   │   ├── nested
687*c2e18aaaSAndroid Build Coastguard Worker     /// │   │   └── nested2
688*c2e18aaaSAndroid Build Coastguard Worker     /// │   ├── dir4
689*c2e18aaaSAndroid Build Coastguard Worker     /// │   └── f1.txt
690*c2e18aaaSAndroid Build Coastguard Worker     /// ├── dir3
691*c2e18aaaSAndroid Build Coastguard Worker     /// │   ├── to_tmp -> /tmp
692*c2e18aaaSAndroid Build Coastguard Worker     /// │   └── to_tmp2 -> /system/cycle1
693*c2e18aaaSAndroid Build Coastguard Worker     /// ├── file1.so
694*c2e18aaaSAndroid Build Coastguard Worker     /// ├── file2.so
695*c2e18aaaSAndroid Build Coastguard Worker     /// ├── link1 -> file1.so
696*c2e18aaaSAndroid Build Coastguard Worker     /// └── link2 -> link1
697*c2e18aaaSAndroid Build Coastguard Worker     #[test]
698*c2e18aaaSAndroid Build Coastguard Worker 
fingerprint_simple_partition()699*c2e18aaaSAndroid Build Coastguard Worker     fn fingerprint_simple_partition() {
700*c2e18aaaSAndroid Build Coastguard Worker         let tmp_root = TempDir::new().unwrap();
701*c2e18aaaSAndroid Build Coastguard Worker         // TODO(rbraunstein): Change make_partition to look more like `expected` variable below.
702*c2e18aaaSAndroid Build Coastguard Worker         // i.e. use file_type rather than pass files, dirs, and symlinks in different arrays.
703*c2e18aaaSAndroid Build Coastguard Worker         // Or use a struct with named fields as the args.
704*c2e18aaaSAndroid Build Coastguard Worker         make_partition(
705*c2e18aaaSAndroid Build Coastguard Worker             tmp_root.path(),
706*c2e18aaaSAndroid Build Coastguard Worker             "system",
707*c2e18aaaSAndroid Build Coastguard Worker             &[
708*c2e18aaaSAndroid Build Coastguard Worker                 ("file1.so", "some text"),
709*c2e18aaaSAndroid Build Coastguard Worker                 ("file2.so", "more text"),
710*c2e18aaaSAndroid Build Coastguard Worker                 ("dir1/f1.txt", ""),
711*c2e18aaaSAndroid Build Coastguard Worker                 ("dir1/dir2/nested", "some more text"),
712*c2e18aaaSAndroid Build Coastguard Worker                 ("dir1/dir2/nested2", "some more text"),
713*c2e18aaaSAndroid Build Coastguard Worker             ],
714*c2e18aaaSAndroid Build Coastguard Worker             // Empty directories/
715*c2e18aaaSAndroid Build Coastguard Worker             &["dir3", "dir1/dir4", "danglers"],
716*c2e18aaaSAndroid Build Coastguard Worker             // Symlinks:
717*c2e18aaaSAndroid Build Coastguard Worker             //   Linkname, target.
718*c2e18aaaSAndroid Build Coastguard Worker             &[
719*c2e18aaaSAndroid Build Coastguard Worker                 ("link1", "file1.so"),
720*c2e18aaaSAndroid Build Coastguard Worker                 ("link2", "link1"),
721*c2e18aaaSAndroid Build Coastguard Worker                 ("cycle1", "cycle2"),
722*c2e18aaaSAndroid Build Coastguard Worker                 ("cycle2", "cycle1"),
723*c2e18aaaSAndroid Build Coastguard Worker                 ("dir3/to_tmp", "/tmp"),
724*c2e18aaaSAndroid Build Coastguard Worker                 ("dir3/to_tmp2", "/system/cycle1"),
725*c2e18aaaSAndroid Build Coastguard Worker                 ("danglers/d1", "nowhere"),
726*c2e18aaaSAndroid Build Coastguard Worker                 ("danglers/d2", "/not/existing"),
727*c2e18aaaSAndroid Build Coastguard Worker             ],
728*c2e18aaaSAndroid Build Coastguard Worker         );
729*c2e18aaaSAndroid Build Coastguard Worker         let result = fingerprint_partitions(tmp_root.path(), &[PathBuf::from("system")]).unwrap();
730*c2e18aaaSAndroid Build Coastguard Worker         println!("RESULTS\n");
731*c2e18aaaSAndroid Build Coastguard Worker         for x in &result {
732*c2e18aaaSAndroid Build Coastguard Worker             println!("{:?}", x);
733*c2e18aaaSAndroid Build Coastguard Worker         }
734*c2e18aaaSAndroid Build Coastguard Worker         let expected = &[
735*c2e18aaaSAndroid Build Coastguard Worker             ("system/file1.so", FileType::File, "b94f"),
736*c2e18aaaSAndroid Build Coastguard Worker             ("system/file2.so", FileType::File, "c0dc"),
737*c2e18aaaSAndroid Build Coastguard Worker             ("system/dir1/f1.txt", FileType::File, "e3b0c"),
738*c2e18aaaSAndroid Build Coastguard Worker             ("system/dir1/dir2/nested", FileType::File, "bde27b"),
739*c2e18aaaSAndroid Build Coastguard Worker             ("system/dir1/dir2/nested2", FileType::File, "bde27b"),
740*c2e18aaaSAndroid Build Coastguard Worker             ("system/dir3", FileType::Directory, ""),
741*c2e18aaaSAndroid Build Coastguard Worker             ("system/danglers", FileType::Directory, ""),
742*c2e18aaaSAndroid Build Coastguard Worker             ("system/dir1", FileType::Directory, ""),
743*c2e18aaaSAndroid Build Coastguard Worker             ("system/dir1/dir2", FileType::Directory, ""),
744*c2e18aaaSAndroid Build Coastguard Worker             ("system/dir1/dir4", FileType::Directory, ""),
745*c2e18aaaSAndroid Build Coastguard Worker             ("system/link1", FileType::Symlink, "file1.so"),
746*c2e18aaaSAndroid Build Coastguard Worker             ("system/link2", FileType::Symlink, "link1"),
747*c2e18aaaSAndroid Build Coastguard Worker             ("system/cycle1", FileType::Symlink, "cycle2"),
748*c2e18aaaSAndroid Build Coastguard Worker             ("system/cycle2", FileType::Symlink, "cycle1"),
749*c2e18aaaSAndroid Build Coastguard Worker             ("system/dir3/to_tmp", FileType::Symlink, "/tmp"),
750*c2e18aaaSAndroid Build Coastguard Worker             ("system/dir3/to_tmp2", FileType::Symlink, "/system/cycle1"),
751*c2e18aaaSAndroid Build Coastguard Worker             ("system/danglers/d1", FileType::Symlink, "nowhere"),
752*c2e18aaaSAndroid Build Coastguard Worker             ("system/danglers/d2", FileType::Symlink, "/not/existing"),
753*c2e18aaaSAndroid Build Coastguard Worker             ("system", FileType::Directory, ""),
754*c2e18aaaSAndroid Build Coastguard Worker         ];
755*c2e18aaaSAndroid Build Coastguard Worker 
756*c2e18aaaSAndroid Build Coastguard Worker         assert_eq!(
757*c2e18aaaSAndroid Build Coastguard Worker             expected.len(),
758*c2e18aaaSAndroid Build Coastguard Worker             result.len(),
759*c2e18aaaSAndroid Build Coastguard Worker             "expected: {}, result {}",
760*c2e18aaaSAndroid Build Coastguard Worker             expected.len(),
761*c2e18aaaSAndroid Build Coastguard Worker             result.len()
762*c2e18aaaSAndroid Build Coastguard Worker         );
763*c2e18aaaSAndroid Build Coastguard Worker 
764*c2e18aaaSAndroid Build Coastguard Worker         for (file_name, file_type, data) in expected {
765*c2e18aaaSAndroid Build Coastguard Worker             match file_type {
766*c2e18aaaSAndroid Build Coastguard Worker                 FileType::File => assert!(
767*c2e18aaaSAndroid Build Coastguard Worker                     matching_file_fingerprint(file_name, data, &result),
768*c2e18aaaSAndroid Build Coastguard Worker                     "mismatch on {:?} {:?}",
769*c2e18aaaSAndroid Build Coastguard Worker                     file_name,
770*c2e18aaaSAndroid Build Coastguard Worker                     data
771*c2e18aaaSAndroid Build Coastguard Worker                 ),
772*c2e18aaaSAndroid Build Coastguard Worker                 FileType::Directory => assert!(result
773*c2e18aaaSAndroid Build Coastguard Worker                     .get(&PathBuf::from(file_name))
774*c2e18aaaSAndroid Build Coastguard Worker                     .is_some_and(|d| d.file_type == FileType::Directory)),
775*c2e18aaaSAndroid Build Coastguard Worker                 FileType::Symlink => assert!(result
776*c2e18aaaSAndroid Build Coastguard Worker                     .get(&PathBuf::from(file_name))
777*c2e18aaaSAndroid Build Coastguard Worker                     .is_some_and(|s| s.file_type == FileType::Symlink && &s.symlink == data)),
778*c2e18aaaSAndroid Build Coastguard Worker             };
779*c2e18aaaSAndroid Build Coastguard Worker         }
780*c2e18aaaSAndroid Build Coastguard Worker     }
781*c2e18aaaSAndroid Build Coastguard Worker 
782*c2e18aaaSAndroid Build Coastguard Worker     #[test]
fingerprint_multiple_partitions()783*c2e18aaaSAndroid Build Coastguard Worker     fn fingerprint_multiple_partitions() {
784*c2e18aaaSAndroid Build Coastguard Worker         let tmp_root = TempDir::new().unwrap();
785*c2e18aaaSAndroid Build Coastguard Worker         // Use same file name, with and without same contents in two different partitions.
786*c2e18aaaSAndroid Build Coastguard Worker         make_partition(
787*c2e18aaaSAndroid Build Coastguard Worker             tmp_root.path(),
788*c2e18aaaSAndroid Build Coastguard Worker             "system",
789*c2e18aaaSAndroid Build Coastguard Worker             &[("file1.so", "some text"), ("file2", "system part")],
790*c2e18aaaSAndroid Build Coastguard Worker             // Empty directories/
791*c2e18aaaSAndroid Build Coastguard Worker             &[],
792*c2e18aaaSAndroid Build Coastguard Worker             // Symlinks
793*c2e18aaaSAndroid Build Coastguard Worker             &[],
794*c2e18aaaSAndroid Build Coastguard Worker         );
795*c2e18aaaSAndroid Build Coastguard Worker         make_partition(
796*c2e18aaaSAndroid Build Coastguard Worker             tmp_root.path(),
797*c2e18aaaSAndroid Build Coastguard Worker             "data",
798*c2e18aaaSAndroid Build Coastguard Worker             &[("file1.so", "some text"), ("file2", "data part")],
799*c2e18aaaSAndroid Build Coastguard Worker             // Empty directories/
800*c2e18aaaSAndroid Build Coastguard Worker             &[],
801*c2e18aaaSAndroid Build Coastguard Worker             // Symlinks
802*c2e18aaaSAndroid Build Coastguard Worker             &[],
803*c2e18aaaSAndroid Build Coastguard Worker         );
804*c2e18aaaSAndroid Build Coastguard Worker 
805*c2e18aaaSAndroid Build Coastguard Worker         let result = fingerprint_partitions(
806*c2e18aaaSAndroid Build Coastguard Worker             tmp_root.path(),
807*c2e18aaaSAndroid Build Coastguard Worker             &[PathBuf::from("system"), PathBuf::from("data")],
808*c2e18aaaSAndroid Build Coastguard Worker         )
809*c2e18aaaSAndroid Build Coastguard Worker         .unwrap();
810*c2e18aaaSAndroid Build Coastguard Worker         println!("RESULTS\n");
811*c2e18aaaSAndroid Build Coastguard Worker         for x in &result {
812*c2e18aaaSAndroid Build Coastguard Worker             println!("{:?}", x);
813*c2e18aaaSAndroid Build Coastguard Worker         }
814*c2e18aaaSAndroid Build Coastguard Worker         let expected = &[
815*c2e18aaaSAndroid Build Coastguard Worker             ("system/file1.so", FileType::File, "b94f"),
816*c2e18aaaSAndroid Build Coastguard Worker             ("data/file1.so", FileType::File, "b94f"),
817*c2e18aaaSAndroid Build Coastguard Worker             ("system/file2", FileType::File, "ae7c6c"),
818*c2e18aaaSAndroid Build Coastguard Worker             ("data/file2", FileType::File, "4ae46d"),
819*c2e18aaaSAndroid Build Coastguard Worker             ("data", FileType::Directory, ""),
820*c2e18aaaSAndroid Build Coastguard Worker             ("system", FileType::Directory, ""),
821*c2e18aaaSAndroid Build Coastguard Worker         ];
822*c2e18aaaSAndroid Build Coastguard Worker 
823*c2e18aaaSAndroid Build Coastguard Worker         assert_eq!(
824*c2e18aaaSAndroid Build Coastguard Worker             expected.len(),
825*c2e18aaaSAndroid Build Coastguard Worker             result.len(),
826*c2e18aaaSAndroid Build Coastguard Worker             "expected: {}, result {}",
827*c2e18aaaSAndroid Build Coastguard Worker             expected.len(),
828*c2e18aaaSAndroid Build Coastguard Worker             result.len()
829*c2e18aaaSAndroid Build Coastguard Worker         );
830*c2e18aaaSAndroid Build Coastguard Worker 
831*c2e18aaaSAndroid Build Coastguard Worker         for (file_name, file_type, data) in expected {
832*c2e18aaaSAndroid Build Coastguard Worker             match file_type {
833*c2e18aaaSAndroid Build Coastguard Worker                 FileType::File => assert!(
834*c2e18aaaSAndroid Build Coastguard Worker                     matching_file_fingerprint(file_name, data, &result),
835*c2e18aaaSAndroid Build Coastguard Worker                     "mismatch on {:?} {:?}",
836*c2e18aaaSAndroid Build Coastguard Worker                     file_name,
837*c2e18aaaSAndroid Build Coastguard Worker                     data
838*c2e18aaaSAndroid Build Coastguard Worker                 ),
839*c2e18aaaSAndroid Build Coastguard Worker                 FileType::Directory => assert!(result
840*c2e18aaaSAndroid Build Coastguard Worker                     .get(&PathBuf::from(file_name))
841*c2e18aaaSAndroid Build Coastguard Worker                     .is_some_and(|d| d.file_type == FileType::Directory)),
842*c2e18aaaSAndroid Build Coastguard Worker                 _ => (),
843*c2e18aaaSAndroid Build Coastguard Worker             };
844*c2e18aaaSAndroid Build Coastguard Worker         }
845*c2e18aaaSAndroid Build Coastguard Worker     }
846*c2e18aaaSAndroid Build Coastguard Worker 
847*c2e18aaaSAndroid Build Coastguard Worker     #[test]
fingerprint_partition_with_interesting_file_names()848*c2e18aaaSAndroid Build Coastguard Worker     fn fingerprint_partition_with_interesting_file_names() {
849*c2e18aaaSAndroid Build Coastguard Worker         let tmp_dir = TempDir::new().unwrap();
850*c2e18aaaSAndroid Build Coastguard Worker         let tmp_root = tmp_dir.path().to_owned();
851*c2e18aaaSAndroid Build Coastguard Worker         println!("DEBUG: {tmp_root:?}");
852*c2e18aaaSAndroid Build Coastguard Worker         make_partition(
853*c2e18aaaSAndroid Build Coastguard Worker             &tmp_root,
854*c2e18aaaSAndroid Build Coastguard Worker             "funky",
855*c2e18aaaSAndroid Build Coastguard Worker             &[("안녕하세요", "hello\n")],
856*c2e18aaaSAndroid Build Coastguard Worker             // Empty directories/
857*c2e18aaaSAndroid Build Coastguard Worker             &[
858*c2e18aaaSAndroid Build Coastguard Worker                 // TODO(rbraunstein): This invalid file name (embedded newlind and Nil) breaks tests.
859*c2e18aaaSAndroid Build Coastguard Worker                 // Need to fix the code to remove `unwraps` and propagate errors.
860*c2e18aaaSAndroid Build Coastguard Worker                 // "d\ni\0r3"
861*c2e18aaaSAndroid Build Coastguard Worker                 ],
862*c2e18aaaSAndroid Build Coastguard Worker             // symlinks
863*c2e18aaaSAndroid Build Coastguard Worker             // linkname, target
864*c2e18aaaSAndroid Build Coastguard Worker             &[("שלום", "안녕하세요")],
865*c2e18aaaSAndroid Build Coastguard Worker         );
866*c2e18aaaSAndroid Build Coastguard Worker         let result = fingerprint_partitions(&tmp_root, &[PathBuf::from("funky")]).unwrap();
867*c2e18aaaSAndroid Build Coastguard Worker         println!("RESULTS\n");
868*c2e18aaaSAndroid Build Coastguard Worker         for x in &result {
869*c2e18aaaSAndroid Build Coastguard Worker             println!("{:?}", x);
870*c2e18aaaSAndroid Build Coastguard Worker         }
871*c2e18aaaSAndroid Build Coastguard Worker         let expected = &[
872*c2e18aaaSAndroid Build Coastguard Worker             ("funky/안녕하세요", FileType::File, "5891b"),
873*c2e18aaaSAndroid Build Coastguard Worker             // ("funky/d\ni\0r3", FileType::Directory, ""),
874*c2e18aaaSAndroid Build Coastguard Worker             ("funky/שלום", FileType::Symlink, "안녕하세요"),
875*c2e18aaaSAndroid Build Coastguard Worker             ("funky", FileType::Directory, ""),
876*c2e18aaaSAndroid Build Coastguard Worker         ];
877*c2e18aaaSAndroid Build Coastguard Worker 
878*c2e18aaaSAndroid Build Coastguard Worker         assert_eq!(
879*c2e18aaaSAndroid Build Coastguard Worker             expected.len(),
880*c2e18aaaSAndroid Build Coastguard Worker             result.len(),
881*c2e18aaaSAndroid Build Coastguard Worker             "expected: {}, result {}",
882*c2e18aaaSAndroid Build Coastguard Worker             expected.len(),
883*c2e18aaaSAndroid Build Coastguard Worker             result.len()
884*c2e18aaaSAndroid Build Coastguard Worker         );
885*c2e18aaaSAndroid Build Coastguard Worker 
886*c2e18aaaSAndroid Build Coastguard Worker         for (file_name, file_type, data) in expected {
887*c2e18aaaSAndroid Build Coastguard Worker             match file_type {
888*c2e18aaaSAndroid Build Coastguard Worker                 FileType::File => assert!(
889*c2e18aaaSAndroid Build Coastguard Worker                     matching_file_fingerprint(file_name, data, &result),
890*c2e18aaaSAndroid Build Coastguard Worker                     "mismatch on {:?} {:?}",
891*c2e18aaaSAndroid Build Coastguard Worker                     file_name,
892*c2e18aaaSAndroid Build Coastguard Worker                     data
893*c2e18aaaSAndroid Build Coastguard Worker                 ),
894*c2e18aaaSAndroid Build Coastguard Worker                 FileType::Directory => assert!(result
895*c2e18aaaSAndroid Build Coastguard Worker                     .get(&PathBuf::from(file_name))
896*c2e18aaaSAndroid Build Coastguard Worker                     .is_some_and(|d| d.file_type == FileType::Directory)),
897*c2e18aaaSAndroid Build Coastguard Worker                 FileType::Symlink => assert!(result
898*c2e18aaaSAndroid Build Coastguard Worker                     .get(&PathBuf::from(file_name))
899*c2e18aaaSAndroid Build Coastguard Worker                     .is_some_and(|s| s.file_type == FileType::Symlink && &s.symlink == data)),
900*c2e18aaaSAndroid Build Coastguard Worker             };
901*c2e18aaaSAndroid Build Coastguard Worker         }
902*c2e18aaaSAndroid Build Coastguard Worker     }
903*c2e18aaaSAndroid Build Coastguard Worker 
904*c2e18aaaSAndroid Build Coastguard Worker     #[test]
fingerprint_partition_cache_mismatch_test()905*c2e18aaaSAndroid Build Coastguard Worker     fn fingerprint_partition_cache_mismatch_test() {
906*c2e18aaaSAndroid Build Coastguard Worker         // test to assure that when a file is modified; the cache doesn't return the same digest
907*c2e18aaaSAndroid Build Coastguard Worker         let tmp_root = TempDir::new().unwrap();
908*c2e18aaaSAndroid Build Coastguard Worker         make_partition(
909*c2e18aaaSAndroid Build Coastguard Worker             tmp_root.path(),
910*c2e18aaaSAndroid Build Coastguard Worker             "system",
911*c2e18aaaSAndroid Build Coastguard Worker             &[("file1.so", "some text"), ("file2.so", "more text")],
912*c2e18aaaSAndroid Build Coastguard Worker             // Empty directories/
913*c2e18aaaSAndroid Build Coastguard Worker             &[],
914*c2e18aaaSAndroid Build Coastguard Worker             // Symlinks
915*c2e18aaaSAndroid Build Coastguard Worker             &[("link1.so", "file1.so")],
916*c2e18aaaSAndroid Build Coastguard Worker         );
917*c2e18aaaSAndroid Build Coastguard Worker         let result = fingerprint_partitions(tmp_root.path(), &[PathBuf::from("system")]).unwrap();
918*c2e18aaaSAndroid Build Coastguard Worker         let expected = &[
919*c2e18aaaSAndroid Build Coastguard Worker             ("system/file1.so", FileType::File, "b94f"),
920*c2e18aaaSAndroid Build Coastguard Worker             ("system/file2.so", FileType::File, "c0dc"),
921*c2e18aaaSAndroid Build Coastguard Worker             ("system/link1.so", FileType::Symlink, "file1.so"),
922*c2e18aaaSAndroid Build Coastguard Worker         ];
923*c2e18aaaSAndroid Build Coastguard Worker         for (file_name, file_type, data) in expected {
924*c2e18aaaSAndroid Build Coastguard Worker             match file_type {
925*c2e18aaaSAndroid Build Coastguard Worker                 FileType::File => assert!(
926*c2e18aaaSAndroid Build Coastguard Worker                     matching_file_fingerprint(file_name, data, &result),
927*c2e18aaaSAndroid Build Coastguard Worker                     "mismatch on {:?} {:?}",
928*c2e18aaaSAndroid Build Coastguard Worker                     file_name,
929*c2e18aaaSAndroid Build Coastguard Worker                     data
930*c2e18aaaSAndroid Build Coastguard Worker                 ),
931*c2e18aaaSAndroid Build Coastguard Worker                 FileType::Directory => assert!(result
932*c2e18aaaSAndroid Build Coastguard Worker                     .get(&PathBuf::from(file_name))
933*c2e18aaaSAndroid Build Coastguard Worker                     .is_some_and(|d| d.file_type == FileType::Directory)),
934*c2e18aaaSAndroid Build Coastguard Worker                 FileType::Symlink => assert!(result
935*c2e18aaaSAndroid Build Coastguard Worker                     .get(&PathBuf::from(file_name))
936*c2e18aaaSAndroid Build Coastguard Worker                     .is_some_and(|s| s.file_type == FileType::Symlink && &s.symlink == data)),
937*c2e18aaaSAndroid Build Coastguard Worker             }
938*c2e18aaaSAndroid Build Coastguard Worker         }
939*c2e18aaaSAndroid Build Coastguard Worker 
940*c2e18aaaSAndroid Build Coastguard Worker         // modify file
941*c2e18aaaSAndroid Build Coastguard Worker         let file_path = tmp_root.path().join("system/file1.so");
942*c2e18aaaSAndroid Build Coastguard Worker         fs::write(file_path, "modified file.").unwrap();
943*c2e18aaaSAndroid Build Coastguard Worker         let result2 = fingerprint_partitions(tmp_root.path(), &[PathBuf::from("system")]).unwrap();
944*c2e18aaaSAndroid Build Coastguard Worker         let expected2 = &[
945*c2e18aaaSAndroid Build Coastguard Worker             ("system/file1.so", FileType::File, "047c5"),
946*c2e18aaaSAndroid Build Coastguard Worker             ("system/file2.so", FileType::File, "c0dc"),
947*c2e18aaaSAndroid Build Coastguard Worker         ];
948*c2e18aaaSAndroid Build Coastguard Worker         for (file_name, file_type, data) in expected2 {
949*c2e18aaaSAndroid Build Coastguard Worker             match file_type {
950*c2e18aaaSAndroid Build Coastguard Worker                 FileType::File => assert!(
951*c2e18aaaSAndroid Build Coastguard Worker                     matching_file_fingerprint(file_name, data, &result2),
952*c2e18aaaSAndroid Build Coastguard Worker                     "mismatch on {:?} {:?}",
953*c2e18aaaSAndroid Build Coastguard Worker                     file_name,
954*c2e18aaaSAndroid Build Coastguard Worker                     data
955*c2e18aaaSAndroid Build Coastguard Worker                 ),
956*c2e18aaaSAndroid Build Coastguard Worker                 FileType::Directory => assert!(result2
957*c2e18aaaSAndroid Build Coastguard Worker                     .get(&PathBuf::from(file_name))
958*c2e18aaaSAndroid Build Coastguard Worker                     .is_some_and(|d| d.file_type == FileType::Directory)),
959*c2e18aaaSAndroid Build Coastguard Worker                 FileType::Symlink => assert!(result
960*c2e18aaaSAndroid Build Coastguard Worker                     .get(&PathBuf::from(file_name))
961*c2e18aaaSAndroid Build Coastguard Worker                     .is_some_and(|s| s.file_type == FileType::Symlink && &s.symlink == data)),
962*c2e18aaaSAndroid Build Coastguard Worker             }
963*c2e18aaaSAndroid Build Coastguard Worker         }
964*c2e18aaaSAndroid Build Coastguard Worker     }
965*c2e18aaaSAndroid Build Coastguard Worker 
966*c2e18aaaSAndroid Build Coastguard Worker     #[test]
test_write_and_read_cache_file()967*c2e18aaaSAndroid Build Coastguard Worker     fn test_write_and_read_cache_file() {
968*c2e18aaaSAndroid Build Coastguard Worker         let root = TempDir::new().unwrap();
969*c2e18aaaSAndroid Build Coastguard Worker         let file_path = root.path().join("cache.json");
970*c2e18aaaSAndroid Build Coastguard Worker         let results = HashMap::from([
971*c2e18aaaSAndroid Build Coastguard Worker             (
972*c2e18aaaSAndroid Build Coastguard Worker                 PathBuf::from("path1"),
973*c2e18aaaSAndroid Build Coastguard Worker                 FileMetadata {
974*c2e18aaaSAndroid Build Coastguard Worker                     cache_key: "key1".to_string(),
975*c2e18aaaSAndroid Build Coastguard Worker                     digest: "value1".to_string(),
976*c2e18aaaSAndroid Build Coastguard Worker                     ..Default::default()
977*c2e18aaaSAndroid Build Coastguard Worker                 },
978*c2e18aaaSAndroid Build Coastguard Worker             ),
979*c2e18aaaSAndroid Build Coastguard Worker             (
980*c2e18aaaSAndroid Build Coastguard Worker                 PathBuf::from("path2"),
981*c2e18aaaSAndroid Build Coastguard Worker                 FileMetadata {
982*c2e18aaaSAndroid Build Coastguard Worker                     cache_key: "key2".to_string(),
983*c2e18aaaSAndroid Build Coastguard Worker                     digest: "value2".to_string(),
984*c2e18aaaSAndroid Build Coastguard Worker                     ..Default::default()
985*c2e18aaaSAndroid Build Coastguard Worker                 },
986*c2e18aaaSAndroid Build Coastguard Worker             ),
987*c2e18aaaSAndroid Build Coastguard Worker         ]);
988*c2e18aaaSAndroid Build Coastguard Worker 
989*c2e18aaaSAndroid Build Coastguard Worker         let cache = Cache::default();
990*c2e18aaaSAndroid Build Coastguard Worker         let write_result = cache.write_to_file(&results, &file_path);
991*c2e18aaaSAndroid Build Coastguard Worker         assert!(write_result.is_ok());
992*c2e18aaaSAndroid Build Coastguard Worker 
993*c2e18aaaSAndroid Build Coastguard Worker         let cache = Cache::read_from_file(&file_path).unwrap();
994*c2e18aaaSAndroid Build Coastguard Worker         assert_eq!(cache.get("key1"), Some(&"value1".to_string()));
995*c2e18aaaSAndroid Build Coastguard Worker         assert_eq!(cache.get("key2"), Some(&"value2".to_string()));
996*c2e18aaaSAndroid Build Coastguard Worker     }
997*c2e18aaaSAndroid Build Coastguard Worker 
998*c2e18aaaSAndroid Build Coastguard Worker     #[test]
test_read_cache_file_no_file()999*c2e18aaaSAndroid Build Coastguard Worker     fn test_read_cache_file_no_file() {
1000*c2e18aaaSAndroid Build Coastguard Worker         let bad_path = Path::new("/tmp/fake/non/existing/path");
1001*c2e18aaaSAndroid Build Coastguard Worker         let cache = Cache::read_from_file(bad_path).unwrap_or_default();
1002*c2e18aaaSAndroid Build Coastguard Worker         assert!(cache.data.is_empty());
1003*c2e18aaaSAndroid Build Coastguard Worker     }
1004*c2e18aaaSAndroid Build Coastguard Worker 
1005*c2e18aaaSAndroid Build Coastguard Worker     #[test]
test_read_cache_file_invalid_file()1006*c2e18aaaSAndroid Build Coastguard Worker     fn test_read_cache_file_invalid_file() {
1007*c2e18aaaSAndroid Build Coastguard Worker         let root = TempDir::new().unwrap();
1008*c2e18aaaSAndroid Build Coastguard Worker         let file_path = root.path().join("cache.json");
1009*c2e18aaaSAndroid Build Coastguard Worker         fs::write(file_path.clone(), "invalid cache data").unwrap();
1010*c2e18aaaSAndroid Build Coastguard Worker 
1011*c2e18aaaSAndroid Build Coastguard Worker         let cache = Cache::read_from_file(&file_path).unwrap_or_default();
1012*c2e18aaaSAndroid Build Coastguard Worker         assert!(cache.data.is_empty());
1013*c2e18aaaSAndroid Build Coastguard Worker     }
1014*c2e18aaaSAndroid Build Coastguard Worker 
1015*c2e18aaaSAndroid Build Coastguard Worker     // Ensure the FileMetadata for the given file matches the prefix of the digest.
1016*c2e18aaaSAndroid Build Coastguard Worker     // We don't require whole digests as that just muddys up the test code and
1017*c2e18aaaSAndroid Build Coastguard Worker     // other methods tests full digests.
matching_file_fingerprint( file_name: &str, digest_prefix: &str, fingerprints: &HashMap<PathBuf, FileMetadata>, ) -> bool1018*c2e18aaaSAndroid Build Coastguard Worker     fn matching_file_fingerprint(
1019*c2e18aaaSAndroid Build Coastguard Worker         file_name: &str,
1020*c2e18aaaSAndroid Build Coastguard Worker         digest_prefix: &str,
1021*c2e18aaaSAndroid Build Coastguard Worker         fingerprints: &HashMap<PathBuf, FileMetadata>,
1022*c2e18aaaSAndroid Build Coastguard Worker     ) -> bool {
1023*c2e18aaaSAndroid Build Coastguard Worker         match fingerprints.get(&PathBuf::from(file_name)) {
1024*c2e18aaaSAndroid Build Coastguard Worker             None => false,
1025*c2e18aaaSAndroid Build Coastguard Worker             Some(metadata) => {
1026*c2e18aaaSAndroid Build Coastguard Worker                 metadata.file_type == FileType::File
1027*c2e18aaaSAndroid Build Coastguard Worker                     && metadata.symlink.is_empty()
1028*c2e18aaaSAndroid Build Coastguard Worker                     && metadata.digest.starts_with(digest_prefix)
1029*c2e18aaaSAndroid Build Coastguard Worker             }
1030*c2e18aaaSAndroid Build Coastguard Worker         }
1031*c2e18aaaSAndroid Build Coastguard Worker     }
1032*c2e18aaaSAndroid Build Coastguard Worker 
1033*c2e18aaaSAndroid Build Coastguard Worker     // Create a temporary folder and create files, directories and symlinks under it.
make_partition( tmp_root: &Path, partition_name: &str, files: &[(&str, &str)], directories: &[&str], symlinks: &[(&str, &str)], )1034*c2e18aaaSAndroid Build Coastguard Worker     fn make_partition(
1035*c2e18aaaSAndroid Build Coastguard Worker         tmp_root: &Path,
1036*c2e18aaaSAndroid Build Coastguard Worker         partition_name: &str,
1037*c2e18aaaSAndroid Build Coastguard Worker         files: &[(&str, &str)],
1038*c2e18aaaSAndroid Build Coastguard Worker         directories: &[&str],
1039*c2e18aaaSAndroid Build Coastguard Worker         symlinks: &[(&str, &str)],
1040*c2e18aaaSAndroid Build Coastguard Worker     ) {
1041*c2e18aaaSAndroid Build Coastguard Worker         let partition_dir = tmp_root.join(partition_name);
1042*c2e18aaaSAndroid Build Coastguard Worker         fs::create_dir(&partition_dir).expect("should have created directory partition_dir");
1043*c2e18aaaSAndroid Build Coastguard Worker         // First create all empty directories.
1044*c2e18aaaSAndroid Build Coastguard Worker         for dir in directories {
1045*c2e18aaaSAndroid Build Coastguard Worker             fs::create_dir_all(partition_dir.join(dir))
1046*c2e18aaaSAndroid Build Coastguard Worker                 .unwrap_or_else(|_| panic!("Should have created {dir} in {tmp_root:?}"));
1047*c2e18aaaSAndroid Build Coastguard Worker         }
1048*c2e18aaaSAndroid Build Coastguard Worker         for (file_name, file_content) in files {
1049*c2e18aaaSAndroid Build Coastguard Worker             // Create parent dirs, in case they are needed.
1050*c2e18aaaSAndroid Build Coastguard Worker             fs::create_dir_all(partition_dir.join(file_name).parent().unwrap()).unwrap();
1051*c2e18aaaSAndroid Build Coastguard Worker             fs::write(partition_dir.join(file_name), file_content).expect("Trouble writing file");
1052*c2e18aaaSAndroid Build Coastguard Worker         }
1053*c2e18aaaSAndroid Build Coastguard Worker         for (symlink_name, target) in symlinks {
1054*c2e18aaaSAndroid Build Coastguard Worker             fs::create_dir_all(partition_dir.join(symlink_name).parent().unwrap()).unwrap();
1055*c2e18aaaSAndroid Build Coastguard Worker             create_symlink(&PathBuf::from(target), symlink_name, &partition_dir);
1056*c2e18aaaSAndroid Build Coastguard Worker         }
1057*c2e18aaaSAndroid Build Coastguard Worker     }
1058*c2e18aaaSAndroid Build Coastguard Worker 
1059*c2e18aaaSAndroid Build Coastguard Worker     // Create a symlink in `directory` named `link_name` that points to `target`.
1060*c2e18aaaSAndroid Build Coastguard Worker     // Returns the absolute path to the created symlink.
create_symlink(target: &Path, link_name: &str, directory: &Path) -> PathBuf1061*c2e18aaaSAndroid Build Coastguard Worker     fn create_symlink(target: &Path, link_name: &str, directory: &Path) -> PathBuf {
1062*c2e18aaaSAndroid Build Coastguard Worker         fs::soft_link(target, directory.join(link_name))
1063*c2e18aaaSAndroid Build Coastguard Worker             .unwrap_or_else(|e| println!("Could not symlink to {:?} {:?}", directory, e));
1064*c2e18aaaSAndroid Build Coastguard Worker 
1065*c2e18aaaSAndroid Build Coastguard Worker         directory.join(Path::new(link_name))
1066*c2e18aaaSAndroid Build Coastguard Worker     }
1067*c2e18aaaSAndroid Build Coastguard Worker 
file_metadata(digest: &str) -> FileMetadata1068*c2e18aaaSAndroid Build Coastguard Worker     fn file_metadata(digest: &str) -> FileMetadata {
1069*c2e18aaaSAndroid Build Coastguard Worker         FileMetadata { file_type: FileType::File, digest: digest.to_string(), ..Default::default() }
1070*c2e18aaaSAndroid Build Coastguard Worker     }
1071*c2e18aaaSAndroid Build Coastguard Worker 
link_metadata(target: &str) -> FileMetadata1072*c2e18aaaSAndroid Build Coastguard Worker     fn link_metadata(target: &str) -> FileMetadata {
1073*c2e18aaaSAndroid Build Coastguard Worker         FileMetadata {
1074*c2e18aaaSAndroid Build Coastguard Worker             file_type: FileType::Symlink,
1075*c2e18aaaSAndroid Build Coastguard Worker             digest: target.to_string(),
1076*c2e18aaaSAndroid Build Coastguard Worker             ..Default::default()
1077*c2e18aaaSAndroid Build Coastguard Worker         }
1078*c2e18aaaSAndroid Build Coastguard Worker     }
1079*c2e18aaaSAndroid Build Coastguard Worker 
dir_metadata() -> FileMetadata1080*c2e18aaaSAndroid Build Coastguard Worker     fn dir_metadata() -> FileMetadata {
1081*c2e18aaaSAndroid Build Coastguard Worker         FileMetadata { file_type: FileType::Directory, ..Default::default() }
1082*c2e18aaaSAndroid Build Coastguard Worker     }
1083*c2e18aaaSAndroid Build Coastguard Worker 
1084*c2e18aaaSAndroid Build Coastguard Worker     // TODO(rbraunstein): a bunch more tests:
1085*c2e18aaaSAndroid Build Coastguard Worker }
1086