-
-
Notifications
You must be signed in to change notification settings - Fork 4k
Expand file tree
/
Copy pathmetamodule.rs
More file actions
289 lines (247 loc) · 9.52 KB
/
Copy pathmetamodule.rs
File metadata and controls
289 lines (247 loc) · 9.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
//! Metamodule management
//!
//! This module handles all metamodule-related functionality.
//! Metamodules are special modules that manage how regular modules are mounted
//! and provide hooks for module installation/uninstallation.
use anyhow::{Context, Result, ensure};
use log::{info, warn};
use std::{
collections::HashMap,
path::{Path, PathBuf},
process::Command,
};
use crate::module::ModuleType::All;
use crate::{assets, defs};
/// Determine whether the provided module properties mark it as a metamodule
pub fn is_metamodule(props: &HashMap<String, String>) -> bool {
props.get("metamodule").is_some_and(|s| {
let trimmed = s.trim();
trimmed == "1" || trimmed.eq_ignore_ascii_case("true")
})
}
/// Get metamodule path if it exists
/// The metamodule is stored in /data/adb/modules/{id} with a symlink at /data/adb/metamodule
pub fn get_metamodule_path() -> Option<PathBuf> {
let path = Path::new(defs::METAMODULE_DIR);
// Check if symlink exists and resolve it
if path.is_symlink()
&& let Ok(target) = std::fs::read_link(path)
{
// If target is relative, resolve it
let resolved = if target.is_absolute() {
target
} else {
path.parent()?.join(target)
};
if resolved.exists() && resolved.is_dir() {
return Some(resolved);
}
warn!(
"Metamodule symlink points to non-existent path: {}",
resolved.display()
);
}
// Fallback: search for metamodule=1 in modules directory
let mut result = None;
let _ = crate::module::foreach_module(All, |module_path| {
if let Ok(props) = crate::module::read_module_prop(module_path)
&& is_metamodule(&props)
{
info!(
"Found metamodule in modules directory: {}",
module_path.display()
);
result = Some(module_path.to_path_buf());
}
Ok(())
});
result
}
/// Get Metamodule Id
pub fn get_metamodule_id() -> Option<String> {
get_metamodule_path().and_then(|path| {
path.file_name()
.and_then(|os_str| os_str.to_str())
.map(ToString::to_string)
})
}
/// Check if metamodule exists
pub fn has_metamodule() -> bool {
get_metamodule_path().is_some()
}
/// Check if it's safe to install a regular module
/// Returns Ok(()) if safe, Err(is_disabled) if blocked
/// - Err(true) means metamodule is disabled
/// - Err(false) means metamodule is in other unstable state
pub fn check_install_safety() -> Result<(), bool> {
// No metamodule → safe
let Some(metamodule_path) = get_metamodule_path() else {
return Ok(());
};
// No metainstall.sh → safe (uses default installer)
// The staged update directory may contain the latest scripts, so check both locations
let has_metainstall = metamodule_path
.join(defs::METAMODULE_METAINSTALL_SCRIPT)
.exists()
|| metamodule_path.file_name().is_some_and(|module_id| {
Path::new(defs::MODULE_UPDATE_DIR)
.join(module_id)
.join(defs::METAMODULE_METAINSTALL_SCRIPT)
.exists()
});
if !has_metainstall {
return Ok(());
}
// Check for marker files
let has_update = metamodule_path.join(defs::UPDATE_FILE_NAME).exists();
let has_remove = metamodule_path.join(defs::REMOVE_FILE_NAME).exists();
let has_disable = metamodule_path.join(defs::DISABLE_FILE_NAME).exists();
// Stable state (no markers) → safe
if !has_update && !has_remove && !has_disable {
return Ok(());
}
// Return true if disabled, false for other unstable states
Err(has_disable && !has_update && !has_remove)
}
/// Create or update the metamodule symlink
/// Points /data/adb/metamodule -> /data/adb/modules/{module_id}
pub fn ensure_symlink(module_path: &Path) -> Result<()> {
// METAMODULE_DIR might have trailing slash, so we need to trim it
let symlink_path = Path::new(defs::METAMODULE_DIR.trim_end_matches('/'));
info!(
"Creating metamodule symlink: {} -> {}",
symlink_path.display(),
module_path.display()
);
// Remove existing symlink if it exists
if symlink_path.exists() || symlink_path.is_symlink() {
info!("Removing old metamodule symlink/path");
if symlink_path.is_symlink() {
std::fs::remove_file(symlink_path).with_context(|| "Failed to remove old symlink")?;
} else {
// Could be a directory, remove it
std::fs::remove_dir_all(symlink_path)
.with_context(|| "Failed to remove old directory")?;
}
}
// Create symlink
#[cfg(unix)]
std::os::unix::fs::symlink(module_path, symlink_path)
.with_context(|| format!("Failed to create symlink to {}", module_path.display()))?;
info!("Metamodule symlink created successfully");
Ok(())
}
/// Remove the metamodule symlink
pub fn remove_symlink() -> Result<()> {
let symlink_path = Path::new(defs::METAMODULE_DIR.trim_end_matches('/'));
if symlink_path.is_symlink() {
std::fs::remove_file(symlink_path)
.with_context(|| "Failed to remove metamodule symlink")?;
info!("Metamodule symlink removed");
}
Ok(())
}
/// Get the install script content, using metainstall.sh from metamodule if available
/// Returns the script content to be executed
pub fn get_install_script(
is_metamodule: bool,
installer_content: &str,
install_module_script: &str,
) -> Result<String> {
// Check if there's a metamodule with metainstall.sh
// Only apply this logic for regular modules (not when installing metamodule itself)
let install_script = if is_metamodule {
info!("Installing metamodule, using default installer");
install_module_script.to_string()
} else if let Some(metamodule_path) = get_metamodule_path() {
if metamodule_path.join(defs::DISABLE_FILE_NAME).exists() {
info!("Metamodule is disabled, using default installer");
install_module_script.to_string()
} else {
let metainstall_path = metamodule_path.join(defs::METAMODULE_METAINSTALL_SCRIPT);
if metainstall_path.exists() {
info!("Using metainstall.sh from metamodule");
let metamodule_content = std::fs::read_to_string(&metainstall_path)
.with_context(|| "Failed to read metamodule metainstall.sh")?;
format!("{installer_content}\n{metamodule_content}\nexit 0\n")
} else {
info!("Metamodule exists but has no metainstall.sh, using default installer");
install_module_script.to_string()
}
}
} else {
info!("No metamodule found, using default installer");
install_module_script.to_string()
};
Ok(install_script)
}
/// Check if metamodule script exists and is ready to execute
/// Returns None if metamodule doesn't exist, is disabled, or script is missing
/// Returns Some(script_path) if script is ready to execute
fn check_metamodule_script(script_name: &str) -> Option<PathBuf> {
// Check if metamodule exists
let metamodule_path = get_metamodule_path()?;
// Check if metamodule is disabled
if metamodule_path.join(defs::DISABLE_FILE_NAME).exists() {
info!("Metamodule is disabled, skipping {script_name}");
return None;
}
// Check if script exists
let script_path = metamodule_path.join(script_name);
if !script_path.exists() {
return None;
}
Some(script_path)
}
/// Execute metamodule's metauninstall.sh for a specific module
pub fn exec_metauninstall_script(module_id: &str) -> Result<()> {
let Some(metauninstall_path) = check_metamodule_script(defs::METAMODULE_METAUNINSTALL_SCRIPT)
else {
return Ok(());
};
info!("Executing metamodule metauninstall.sh for module: {module_id}");
let result = Command::new(assets::BUSYBOX_PATH)
.args(["sh", metauninstall_path.to_str().unwrap()])
.current_dir(metauninstall_path.parent().unwrap())
.envs(crate::module::get_common_script_envs(
get_metamodule_id().as_deref(),
))
.env("MODULE_ID", module_id)
.status()?;
ensure!(
result.success(),
"Metamodule metauninstall.sh failed for module {module_id}: {result:?}"
);
info!("Metamodule metauninstall.sh executed successfully for {module_id}");
Ok(())
}
/// Execute metamodule mount script
pub fn exec_mount_script(module_dir: &str) -> Result<()> {
let Some(mount_script) = check_metamodule_script(defs::METAMODULE_MOUNT_SCRIPT) else {
return Ok(());
};
info!("Executing mount script for metamodule");
let result = Command::new(assets::BUSYBOX_PATH)
.args(["sh", mount_script.to_str().unwrap()])
.envs(crate::module::get_common_script_envs(
get_metamodule_id().as_deref(),
))
.env("MODULE_DIR", module_dir)
.status()?;
ensure!(
result.success(),
"Metamodule mount script failed with status: {result:?}"
);
info!("Metamodule mount script executed successfully");
Ok(())
}
/// Execute metamodule script for a specific stage
pub fn exec_stage_script(stage: &str, block: bool) -> Result<()> {
let Some(script_path) = check_metamodule_script(&format!("{stage}.sh")) else {
return Ok(());
};
info!("Executing metamodule {stage}.sh");
crate::module::exec_script(&script_path, block)?;
info!("Metamodule {stage}.sh executed successfully");
Ok(())
}