elfpak_core/plan/
model.rs1use crate::{
4 elf::Architecture,
5 graph::{DependencyGraph, Digest},
6 policy::{DependencyPolicy, Preset, RuntimeFeature, RuntimePolicy},
7};
8use std::path::{Path, PathBuf};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
11pub enum PlannedFileKind {
12 Directory,
13 Symlink,
14 Executable,
15 Interpreter,
16 SharedObject,
17 CertificateBundle,
18 RuntimeConfig,
19 ApplicationData,
20}
21
22impl PlannedFileKind {
23 pub fn as_str(&self) -> &'static str {
24 match self {
25 PlannedFileKind::Directory => "directory",
26 PlannedFileKind::Symlink => "symlink",
27 PlannedFileKind::Executable => "executable",
28 PlannedFileKind::Interpreter => "interpreter",
29 PlannedFileKind::SharedObject => "shared-object",
30 PlannedFileKind::CertificateBundle => "certificate-bundle",
31 PlannedFileKind::RuntimeConfig => "runtime-config",
32 PlannedFileKind::ApplicationData => "application-data",
33 }
34 }
35}
36
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub enum InclusionReason {
39 Application,
40 Interpreter,
41 NeededBy { binary: PathBuf, soname: String },
42 RuntimePolicy { feature: RuntimeFeature },
43 ExplicitInclude,
44}
45
46#[derive(Debug, Clone)]
48pub struct PlannedFile {
49 pub(crate) source: Option<PathBuf>,
51 pub(crate) destination: PathBuf,
53 pub(crate) kind: PlannedFileKind,
54 pub(crate) reason: InclusionReason,
55 pub(crate) mode: u32,
56 pub(crate) size: u64,
57 pub(crate) sha256: Option<Digest>,
58 pub(crate) link_target: Option<PathBuf>,
60 pub(crate) content: Option<Vec<u8>>,
62}
63
64impl PlannedFile {
65 pub fn source(&self) -> Option<&Path> {
66 self.source.as_deref()
67 }
68
69 pub fn destination(&self) -> &Path {
70 &self.destination
71 }
72
73 pub fn kind(&self) -> PlannedFileKind {
74 self.kind
75 }
76
77 pub fn reason(&self) -> &InclusionReason {
78 &self.reason
79 }
80
81 pub fn mode(&self) -> u32 {
82 self.mode
83 }
84
85 pub fn size(&self) -> u64 {
86 self.size
87 }
88
89 pub fn sha256(&self) -> Option<&Digest> {
90 self.sha256.as_ref()
91 }
92
93 pub fn link_target(&self) -> Option<&Path> {
94 self.link_target.as_deref()
95 }
96
97 pub fn content(&self) -> Option<&[u8]> {
98 self.content.as_deref()
99 }
100
101 pub(crate) fn assert_well_formed(&self) {
104 assert!(self.destination.is_absolute());
105 assert!(self.mode <= 0o7777);
106
107 match self.kind {
108 PlannedFileKind::Directory => {
109 assert!(self.source.is_none());
110 assert!(self.link_target.is_none());
111 assert!(self.content.is_none());
112 assert!(self.sha256.is_none());
113 assert_eq!(self.size, 0);
114 }
115 PlannedFileKind::Symlink => {
116 assert!(self.source.is_none());
117 assert!(self.link_target.is_some());
118 assert!(self.content.is_none());
119 assert!(self.sha256.is_none());
120 assert_eq!(self.size, 0);
121 }
122 _ => {
123 assert!(self.link_target.is_none());
124 assert_ne!(self.source.is_some(), self.content.is_some());
125 assert!(self.sha256.as_ref().is_some_and(Digest::is_well_formed));
126 if let Some(content) = &self.content {
127 assert_eq!(self.size, content.len() as u64);
128 }
129 }
130 }
131 }
132}
133
134#[derive(Debug, Clone)]
135pub struct ApplicationPlan {
136 pub(crate) executable: PlannedFile,
137 pub(crate) graph: DependencyGraph,
138 pub(crate) interpreter: Option<PathBuf>,
140 pub(crate) interpreter_resolved: Option<PathBuf>,
142}
143
144impl ApplicationPlan {
145 pub fn executable(&self) -> &PlannedFile {
146 &self.executable
147 }
148
149 pub fn graph(&self) -> &DependencyGraph {
150 &self.graph
151 }
152
153 pub fn interpreter(&self) -> Option<&Path> {
154 self.interpreter.as_deref()
155 }
156
157 pub fn interpreter_resolved(&self) -> Option<&Path> {
158 self.interpreter_resolved.as_deref()
159 }
160}
161
162#[derive(Debug, Clone)]
163pub struct BundlePlan {
164 pub(crate) applications: Vec<ApplicationPlan>,
165 pub(crate) files: Vec<PlannedFile>,
167 pub(crate) architecture: Architecture,
168 pub(crate) preset: Option<Preset>,
170 pub(crate) runtime_policy: RuntimePolicy,
172 pub(crate) dependency_policy: DependencyPolicy,
173 pub(crate) warnings: Vec<Warning>,
174}
175
176impl BundlePlan {
177 pub fn executable(&self) -> &PlannedFile {
179 self.applications
180 .first()
181 .expect("a bundle plan always contains an application")
182 .executable()
183 }
184
185 pub fn applications(&self) -> &[ApplicationPlan] {
186 &self.applications
187 }
188
189 pub fn executables(&self) -> impl Iterator<Item = &PlannedFile> {
190 self.applications.iter().map(ApplicationPlan::executable)
191 }
192
193 pub fn files(&self) -> &[PlannedFile] {
194 &self.files
195 }
196
197 pub fn graph(&self) -> &DependencyGraph {
199 self.applications
200 .first()
201 .expect("a bundle plan always contains an application")
202 .graph()
203 }
204
205 pub fn architecture(&self) -> Architecture {
206 self.architecture
207 }
208
209 pub fn preset(&self) -> Option<Preset> {
210 self.preset
211 }
212
213 pub fn runtime_policy(&self) -> &RuntimePolicy {
214 &self.runtime_policy
215 }
216
217 pub fn dependency_policy(&self) -> &DependencyPolicy {
218 &self.dependency_policy
219 }
220
221 pub fn interpreter(&self) -> Option<&Path> {
223 self.applications
224 .first()
225 .and_then(ApplicationPlan::interpreter)
226 }
227
228 pub fn interpreter_resolved(&self) -> Option<&Path> {
230 self.applications
231 .first()
232 .and_then(ApplicationPlan::interpreter_resolved)
233 }
234
235 pub fn warnings(&self) -> &[Warning] {
236 &self.warnings
237 }
238
239 pub fn total_size(&self) -> u64 {
240 self.files.iter().map(|file| file.size).sum()
241 }
242
243 pub fn files_of_kind(&self, kind: PlannedFileKind) -> impl Iterator<Item = &PlannedFile> {
244 self.files.iter().filter(move |file| file.kind == kind)
245 }
246}
247
248#[derive(Debug, Clone)]
249pub struct Warning {
250 pub code: &'static str,
251 pub message: String,
252 pub details: Vec<String>,
253}