1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 use crate::AconfigdError;
18 use openssl::hash::{Hasher, MessageDigest};
19 use std::fs::File;
20 use std::io::Read;
21 use std::os::unix::fs::PermissionsExt;
22 use std::path::Path;
23 
24 /// Set file permission
set_file_permission(file: &Path, mode: u32) -> Result<(), AconfigdError>25 pub(crate) fn set_file_permission(file: &Path, mode: u32) -> Result<(), AconfigdError> {
26     let perms = std::fs::Permissions::from_mode(mode);
27     std::fs::set_permissions(file, perms).map_err(|errmsg| {
28         AconfigdError::FailToUpdateFilePerm { file: file.display().to_string(), mode, errmsg }
29     })?;
30     Ok(())
31 }
32 
33 /// Copy file
copy_file(src: &Path, dst: &Path, mode: u32) -> Result<(), AconfigdError>34 pub(crate) fn copy_file(src: &Path, dst: &Path, mode: u32) -> Result<(), AconfigdError> {
35     std::fs::copy(src, dst).map_err(|errmsg| AconfigdError::FailToCopyFile {
36         src: src.display().to_string(),
37         dst: dst.display().to_string(),
38         errmsg,
39     })?;
40     set_file_permission(dst, mode)
41 }
42 
43 /// Remove file
remove_file(src: &Path) -> Result<(), AconfigdError>44 pub(crate) fn remove_file(src: &Path) -> Result<(), AconfigdError> {
45     std::fs::remove_file(src).map_err(|errmsg| AconfigdError::FailToRemoveFile {
46         file: src.display().to_string(),
47         errmsg,
48     })
49 }
50 
51 /// Read pb from file
read_pb_from_file<T: protobuf::Message>(file: &Path) -> Result<T, AconfigdError>52 pub(crate) fn read_pb_from_file<T: protobuf::Message>(file: &Path) -> Result<T, AconfigdError> {
53     if !Path::new(file).exists() {
54         return Ok(T::new());
55     }
56 
57     let data = std::fs::read(file).map_err(|errmsg| AconfigdError::FailToReadFile {
58         file: file.display().to_string(),
59         errmsg,
60     })?;
61     protobuf::Message::parse_from_bytes(data.as_ref()).map_err(|errmsg| {
62         AconfigdError::FailToParsePbFromBytes { file: file.display().to_string(), errmsg }
63     })
64 }
65 
66 /// Write pb to file
write_pb_to_file<T: protobuf::Message>( pb: &T, file: &Path, ) -> Result<(), AconfigdError>67 pub(crate) fn write_pb_to_file<T: protobuf::Message>(
68     pb: &T,
69     file: &Path,
70 ) -> Result<(), AconfigdError> {
71     let bytes = protobuf::Message::write_to_bytes(pb).map_err(|errmsg| {
72         AconfigdError::FailToSerializePb { file: file.display().to_string(), errmsg }
73     })?;
74     std::fs::write(file, bytes).map_err(|errmsg| AconfigdError::FailToWriteFile {
75         file: file.display().to_string(),
76         errmsg,
77     })?;
78     Ok(())
79 }
80 
81 /// The digest is returned as a hexadecimal string.
get_files_digest(paths: &[&Path]) -> Result<String, AconfigdError>82 pub(crate) fn get_files_digest(paths: &[&Path]) -> Result<String, AconfigdError> {
83     let mut hasher = Hasher::new(MessageDigest::sha256())
84         .map_err(|errmsg| AconfigdError::FailToGetHasherForDigest { errmsg })?;
85     let mut buffer = [0; 1024];
86     for path in paths {
87         let mut f = File::open(path).map_err(|errmsg| AconfigdError::FailToOpenFile {
88             file: path.display().to_string(),
89             errmsg,
90         })?;
91         loop {
92             let n = f.read(&mut buffer[..]).map_err(|errmsg| AconfigdError::FailToReadFile {
93                 file: path.display().to_string(),
94                 errmsg,
95             })?;
96             if n == 0 {
97                 break;
98             }
99             hasher.update(&buffer).map_err(|errmsg| AconfigdError::FailToHashFile {
100                 file: path.display().to_string(),
101                 errmsg,
102             })?;
103         }
104     }
105     let digest: &[u8] =
106         &hasher.finish().map_err(|errmsg| AconfigdError::FailToGetDigest { errmsg })?;
107     let mut xdigest = String::new();
108     for x in digest {
109         xdigest.push_str(format!("{:02x}", x).as_str());
110     }
111     Ok(xdigest)
112 }
113 
114 #[cfg(test)]
115 mod tests {
116     use super::*;
117     use aconfigd_protos::ProtoLocalFlagOverrides;
118     use std::io::Write;
119     use tempfile::tempdir;
120 
get_file_perm_mode(file: &Path) -> u32121     fn get_file_perm_mode(file: &Path) -> u32 {
122         let f = std::fs::File::open(&file).unwrap();
123         let metadata = f.metadata().unwrap();
124         metadata.permissions().mode() & 0o777
125     }
126 
127     #[test]
test_copy_file()128     fn test_copy_file() {
129         let tmp_dir = tempdir().unwrap();
130 
131         let package_map = tmp_dir.path().join("package.map");
132         copy_file(&Path::new("./tests/data/package.map"), &package_map, 0o444).unwrap();
133         assert_eq!(get_file_perm_mode(&package_map), 0o444);
134 
135         let flag_map = tmp_dir.path().join("flag.map");
136         copy_file(&Path::new("./tests/data/flag.map"), &flag_map, 0o644).unwrap();
137         assert_eq!(get_file_perm_mode(&flag_map), 0o644);
138     }
139 
140     #[test]
test_remove_file()141     fn test_remove_file() {
142         let tmp_dir = tempdir().unwrap();
143         let package_map = tmp_dir.path().join("package.map");
144         copy_file(&Path::new("./tests/data/package.map"), &package_map, 0o444).unwrap();
145         assert!(remove_file(&package_map).is_ok());
146         assert!(!package_map.exists());
147     }
148 
149     #[test]
test_set_file_permission()150     fn test_set_file_permission() {
151         let tmp_dir = tempdir().unwrap();
152         let package_map = tmp_dir.path().join("package.map");
153         copy_file(&Path::new("./tests/data/package.map"), &package_map, 0o644).unwrap();
154         set_file_permission(&package_map, 0o444).unwrap();
155         assert_eq!(get_file_perm_mode(&package_map), 0o444);
156     }
157 
158     #[test]
test_write_pb_to_file()159     fn test_write_pb_to_file() {
160         let tmp_dir = tempdir().unwrap();
161         let test_pb = tmp_dir.path().join("test.pb");
162         let pb = ProtoLocalFlagOverrides::new();
163         write_pb_to_file(&pb, &test_pb).unwrap();
164         assert!(test_pb.exists());
165     }
166 
167     #[test]
test_read_pb_from_file()168     fn test_read_pb_from_file() {
169         let tmp_dir = tempdir().unwrap();
170         let test_pb = tmp_dir.path().join("test.pb");
171         let pb = ProtoLocalFlagOverrides::new();
172         write_pb_to_file(&pb, &test_pb).unwrap();
173         let new_pb: ProtoLocalFlagOverrides = read_pb_from_file(&test_pb).unwrap();
174         assert_eq!(new_pb.overrides.len(), 0);
175     }
176 
177     #[test]
test_get_files_digest()178     fn test_get_files_digest() {
179         let path1 = Path::new("/tmp/hi.txt");
180         let path2 = Path::new("/tmp/bye.txt");
181         let mut file1 = File::create(path1).unwrap();
182         let mut file2 = File::create(path2).unwrap();
183         file1.write_all(b"Hello, world!").expect("Writing to file");
184         file2.write_all(b"Goodbye, world!").expect("Writing to file");
185         let digest = get_files_digest(&[path1, path2]);
186         assert_eq!(
187             digest.expect("Calculating digest"),
188             "8352c31d9ff5f446b838139b7f4eb5fed821a1f80d6648ffa6ed7391ecf431f4"
189         );
190     }
191 }
192