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