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 if dst.exists() {
36 set_file_permission(dst, 0o644)?;
37 }
38
39 let mut src_file = File::open(src).map_err(|errmsg| AconfigdError::FailToCopyFile {
40 src: src.display().to_string(),
41 dst: dst.display().to_string(),
42 errmsg,
43 })?;
44
45 let mut dst_file = File::create(dst).map_err(|errmsg| AconfigdError::FailToCopyFile {
46 src: src.display().to_string(),
47 dst: dst.display().to_string(),
48 errmsg,
49 })?;
50
51 std::io::copy(&mut src_file, &mut dst_file).map_err(|errmsg| {
52 AconfigdError::FailToCopyFile {
53 src: src.display().to_string(),
54 dst: dst.display().to_string(),
55 errmsg,
56 }
57 })?;
58
59 // force kernel to flush file data in kernel buffer to filesystem
60 dst_file.sync_all().map_err(|errmsg| AconfigdError::FailToCopyFile {
61 src: src.display().to_string(),
62 dst: dst.display().to_string(),
63 errmsg,
64 })?;
65
66 set_file_permission(dst, mode)
67 }
68
69 /// Copy file without fsync
copy_file_without_fsync( src: &Path, dst: &Path, mode: u32, ) -> Result<(), AconfigdError>70 pub(crate) fn copy_file_without_fsync(
71 src: &Path,
72 dst: &Path,
73 mode: u32,
74 ) -> Result<(), AconfigdError> {
75 if dst.exists() {
76 set_file_permission(dst, 0o644)?;
77 }
78
79 let mut src_file = File::open(src).map_err(|errmsg| AconfigdError::FailToCopyFile {
80 src: src.display().to_string(),
81 dst: dst.display().to_string(),
82 errmsg,
83 })?;
84
85 let mut dst_file = File::create(dst).map_err(|errmsg| AconfigdError::FailToCopyFile {
86 src: src.display().to_string(),
87 dst: dst.display().to_string(),
88 errmsg,
89 })?;
90
91 std::io::copy(&mut src_file, &mut dst_file).map_err(|errmsg| {
92 AconfigdError::FailToCopyFile {
93 src: src.display().to_string(),
94 dst: dst.display().to_string(),
95 errmsg,
96 }
97 })?;
98
99 set_file_permission(dst, mode)
100 }
101
102 /// Remove file
remove_file(src: &Path) -> Result<(), AconfigdError>103 pub(crate) fn remove_file(src: &Path) -> Result<(), AconfigdError> {
104 if let Ok(true) = src.try_exists() {
105 std::fs::remove_file(src).map_err(|errmsg| AconfigdError::FailToRemoveFile {
106 file: src.display().to_string(),
107 errmsg,
108 })?;
109 }
110 Ok(())
111 }
112
113 /// Read pb from file
read_pb_from_file<T: protobuf::Message>(file: &Path) -> Result<T, AconfigdError>114 pub(crate) fn read_pb_from_file<T: protobuf::Message>(file: &Path) -> Result<T, AconfigdError> {
115 if !Path::new(file).exists() {
116 return Ok(T::new());
117 }
118
119 let data = std::fs::read(file).map_err(|errmsg| AconfigdError::FailToReadFile {
120 file: file.display().to_string(),
121 errmsg,
122 })?;
123 protobuf::Message::parse_from_bytes(data.as_ref()).map_err(|errmsg| {
124 AconfigdError::FailToParsePbFromBytes { file: file.display().to_string(), errmsg }
125 })
126 }
127
128 /// Write pb to file
write_pb_to_file<T: protobuf::Message>( pb: &T, file: &Path, ) -> Result<(), AconfigdError>129 pub(crate) fn write_pb_to_file<T: protobuf::Message>(
130 pb: &T,
131 file: &Path,
132 ) -> Result<(), AconfigdError> {
133 let bytes = protobuf::Message::write_to_bytes(pb).map_err(|errmsg| {
134 AconfigdError::FailToSerializePb { file: file.display().to_string(), errmsg }
135 })?;
136 std::fs::write(file, bytes).map_err(|errmsg| AconfigdError::FailToWriteFile {
137 file: file.display().to_string(),
138 errmsg,
139 })?;
140 Ok(())
141 }
142
143 /// The digest is returned as a hexadecimal string.
get_files_digest(paths: &[&Path]) -> Result<String, AconfigdError>144 pub(crate) fn get_files_digest(paths: &[&Path]) -> Result<String, AconfigdError> {
145 let mut hasher = Hasher::new(MessageDigest::sha256())
146 .map_err(|errmsg| AconfigdError::FailToGetHasherForDigest { errmsg })?;
147 let mut buffer = [0; 1024];
148 for path in paths {
149 let mut f = File::open(path).map_err(|errmsg| AconfigdError::FailToOpenFile {
150 file: path.display().to_string(),
151 errmsg,
152 })?;
153 loop {
154 let n = f.read(&mut buffer[..]).map_err(|errmsg| AconfigdError::FailToReadFile {
155 file: path.display().to_string(),
156 errmsg,
157 })?;
158 if n == 0 {
159 break;
160 }
161 hasher.update(&buffer).map_err(|errmsg| AconfigdError::FailToHashFile {
162 file: path.display().to_string(),
163 errmsg,
164 })?;
165 }
166 }
167 let digest: &[u8] =
168 &hasher.finish().map_err(|errmsg| AconfigdError::FailToGetDigest { errmsg })?;
169 let mut xdigest = String::new();
170 for x in digest {
171 xdigest.push_str(format!("{:02x}", x).as_str());
172 }
173 Ok(xdigest)
174 }
175
176 #[cfg(test)]
177 mod tests {
178 use super::*;
179 use aconfigd_protos::ProtoLocalFlagOverrides;
180 use std::io::Write;
181 use tempfile::tempdir;
182
get_file_perm_mode(file: &Path) -> u32183 fn get_file_perm_mode(file: &Path) -> u32 {
184 let f = std::fs::File::open(&file).unwrap();
185 let metadata = f.metadata().unwrap();
186 metadata.permissions().mode() & 0o777
187 }
188
189 #[test]
test_copy_file()190 fn test_copy_file() {
191 let tmp_dir = tempdir().unwrap();
192
193 let package_map = tmp_dir.path().join("package.map");
194 copy_file(&Path::new("./tests/data/package.map"), &package_map, 0o444).unwrap();
195 assert_eq!(get_file_perm_mode(&package_map), 0o444);
196
197 let flag_map = tmp_dir.path().join("flag.map");
198 copy_file(&Path::new("./tests/data/flag.map"), &flag_map, 0o644).unwrap();
199 assert_eq!(get_file_perm_mode(&flag_map), 0o644);
200 }
201
202 #[test]
test_remove_file()203 fn test_remove_file() {
204 let tmp_dir = tempdir().unwrap();
205 let package_map = tmp_dir.path().join("package.map");
206 copy_file(&Path::new("./tests/data/package.map"), &package_map, 0o444).unwrap();
207 assert!(remove_file(&package_map).is_ok());
208 assert!(!package_map.exists());
209 }
210
211 #[test]
test_set_file_permission()212 fn test_set_file_permission() {
213 let tmp_dir = tempdir().unwrap();
214 let package_map = tmp_dir.path().join("package.map");
215 copy_file(&Path::new("./tests/data/package.map"), &package_map, 0o644).unwrap();
216 set_file_permission(&package_map, 0o444).unwrap();
217 assert_eq!(get_file_perm_mode(&package_map), 0o444);
218 }
219
220 #[test]
test_write_pb_to_file()221 fn test_write_pb_to_file() {
222 let tmp_dir = tempdir().unwrap();
223 let test_pb = tmp_dir.path().join("test.pb");
224 let pb = ProtoLocalFlagOverrides::new();
225 write_pb_to_file(&pb, &test_pb).unwrap();
226 assert!(test_pb.exists());
227 }
228
229 #[test]
test_read_pb_from_file()230 fn test_read_pb_from_file() {
231 let tmp_dir = tempdir().unwrap();
232 let test_pb = tmp_dir.path().join("test.pb");
233 let pb = ProtoLocalFlagOverrides::new();
234 write_pb_to_file(&pb, &test_pb).unwrap();
235 let new_pb: ProtoLocalFlagOverrides = read_pb_from_file(&test_pb).unwrap();
236 assert_eq!(new_pb.overrides.len(), 0);
237 }
238
239 #[test]
test_get_files_digest()240 fn test_get_files_digest() {
241 let path1 = Path::new("/tmp/hi.txt");
242 let path2 = Path::new("/tmp/bye.txt");
243 let mut file1 = File::create(path1).unwrap();
244 let mut file2 = File::create(path2).unwrap();
245 file1.write_all(b"Hello, world!").expect("Writing to file");
246 file2.write_all(b"Goodbye, world!").expect("Writing to file");
247 let digest = get_files_digest(&[path1, path2]);
248 assert_eq!(
249 digest.expect("Calculating digest"),
250 "8352c31d9ff5f446b838139b7f4eb5fed821a1f80d6648ffa6ed7391ecf431f4"
251 );
252 }
253 }
254