1
1
use std:: io:: BufRead ;
2
2
3
3
use anyhow:: { Context , Result } ;
4
+ use camino:: Utf8PathBuf ;
4
5
use cap_std:: fs:: Dir ;
5
6
use cap_std_ext:: { cap_std, dirext:: CapStdExtDirExt } ;
6
7
use fn_error_context:: context;
7
- use ostree_ext:: container_utils:: is_ostree_booted_in;
8
+ use ostree_ext:: container_utils:: { is_ostree_booted_in, OSTREE_BOOTED } ;
8
9
use rustix:: { fd:: AsFd , fs:: StatVfsMountFlags } ;
9
10
11
+ use crate :: install:: DESTRUCTIVE_CLEANUP ;
12
+
13
+ const STATUS_ONBOOT_UNIT : & str = "bootc-status-updated-onboot.target" ;
14
+ const STATUS_PATH_UNIT : & str = "bootc-status-updated.path" ;
15
+ const CLEANUP_UNIT : & str = "bootc-destructive-cleanup.service" ;
16
+ const MULTI_USER_TARGET : & str = "multi-user.target" ;
10
17
const EDIT_UNIT : & str = "bootc-fstab-edit.service" ;
11
18
const FSTAB_ANACONDA_STAMP : & str = "Created by anaconda" ;
12
19
pub ( crate ) const BOOTC_EDITED_STAMP : & str = "Updated by bootc-fstab-edit.service" ;
@@ -47,9 +54,50 @@ pub(crate) fn fstab_generator_impl(root: &Dir, unit_dir: &Dir) -> Result<bool> {
47
54
Ok ( false )
48
55
}
49
56
57
+ pub ( crate ) fn enable_unit ( unitdir : & Dir , name : & str , target : & str ) -> Result < ( ) > {
58
+ let wants = Utf8PathBuf :: from ( format ! ( "{target}.wants" ) ) ;
59
+ unitdir
60
+ . create_dir_all ( & wants)
61
+ . with_context ( || format ! ( "Creating {wants}" ) ) ?;
62
+ let source = format ! ( "/usr/lib/systemd/system/{name}" ) ;
63
+ let target = wants. join ( name) ;
64
+ unitdir. remove_file_optional ( & target) ?;
65
+ unitdir
66
+ . symlink_contents ( & source, & target)
67
+ . with_context ( || format ! ( "Writing {name}" ) ) ?;
68
+ Ok ( ( ) )
69
+ }
70
+
71
+ /// Enable our units
72
+ pub ( crate ) fn unit_enablement_impl ( sysroot : & Dir , unit_dir : & Dir ) -> Result < ( ) > {
73
+ for unit in [ STATUS_ONBOOT_UNIT , STATUS_PATH_UNIT ] {
74
+ enable_unit ( unit_dir, unit, MULTI_USER_TARGET ) ?;
75
+ }
76
+
77
+ if sysroot. try_exists ( DESTRUCTIVE_CLEANUP ) ? {
78
+ tracing:: debug!( "Found {DESTRUCTIVE_CLEANUP}" ) ;
79
+ enable_unit ( unit_dir, CLEANUP_UNIT , MULTI_USER_TARGET ) ?;
80
+ } else {
81
+ tracing:: debug!( "Didn't find {DESTRUCTIVE_CLEANUP}" ) ;
82
+ }
83
+
84
+ Ok ( ( ) )
85
+ }
86
+
50
87
/// Main entrypoint for the generator
51
88
pub ( crate ) fn generator ( root : & Dir , unit_dir : & Dir ) -> Result < ( ) > {
52
- // Right now we only do something if the root is a read-only overlayfs (a composefs really)
89
+ // Only run on ostree systems
90
+ if !root. try_exists ( OSTREE_BOOTED ) ? {
91
+ return Ok ( ( ) ) ;
92
+ }
93
+
94
+ let Some ( ref sysroot) = root. open_dir_optional ( "sysroot" ) ? else {
95
+ return Ok ( ( ) ) ;
96
+ } ;
97
+
98
+ unit_enablement_impl ( sysroot, unit_dir) ?;
99
+
100
+ // Also only run if the root is a read-only overlayfs (a composefs really)
53
101
let st = rustix:: fs:: fstatfs ( root. as_fd ( ) ) ?;
54
102
if st. f_type != libc:: OVERLAYFS_SUPER_MAGIC {
55
103
tracing:: trace!( "Root is not overlayfs" ) ;
@@ -62,6 +110,7 @@ pub(crate) fn generator(root: &Dir, unit_dir: &Dir) -> Result<()> {
62
110
}
63
111
let updated = fstab_generator_impl ( root, unit_dir) ?;
64
112
tracing:: trace!( "Generated fstab: {updated}" ) ;
113
+
65
114
Ok ( ( ) )
66
115
}
67
116
@@ -89,12 +138,16 @@ ExecStart=bootc internals fixup-etc-fstab\n\
89
138
90
139
#[ cfg( test) ]
91
140
mod tests {
141
+ use camino:: Utf8Path ;
142
+ use cap_std_ext:: cmdext:: CapStdExtCommandExt as _;
143
+
92
144
use super :: * ;
93
145
94
146
fn fixture ( ) -> Result < cap_std_ext:: cap_tempfile:: TempDir > {
95
147
let tempdir = cap_std_ext:: cap_tempfile:: tempdir ( cap_std:: ambient_authority ( ) ) ?;
96
148
tempdir. create_dir ( "etc" ) ?;
97
149
tempdir. create_dir ( "run" ) ?;
150
+ tempdir. create_dir ( "sysroot" ) ?;
98
151
tempdir. create_dir_all ( "run/systemd/system" ) ?;
99
152
Ok ( tempdir)
100
153
}
@@ -109,6 +162,53 @@ mod tests {
109
162
Ok ( ( ) )
110
163
}
111
164
165
+ #[ test]
166
+ fn test_units ( ) -> Result < ( ) > {
167
+ let tempdir = & fixture ( ) ?;
168
+ let sysroot = & tempdir. open_dir ( "sysroot" ) . unwrap ( ) ;
169
+ let unit_dir = & tempdir. open_dir ( "run/systemd/system" ) ?;
170
+
171
+ let verify = |wantsdir : & Dir , n : u32 | -> Result < ( ) > {
172
+ assert_eq ! ( unit_dir. entries( ) ?. count( ) , 1 ) ;
173
+ let r = wantsdir. read_link_contents ( STATUS_ONBOOT_UNIT ) ?;
174
+ let r: Utf8PathBuf = r. try_into ( ) . unwrap ( ) ;
175
+ assert_eq ! ( r, format!( "/usr/lib/systemd/system/{STATUS_ONBOOT_UNIT}" ) ) ;
176
+ assert_eq ! ( wantsdir. entries( ) ?. count( ) , n as usize ) ;
177
+ anyhow:: Ok ( ( ) )
178
+ } ;
179
+
180
+ // Explicitly run this twice to test idempotency
181
+
182
+ unit_enablement_impl ( sysroot, & unit_dir) . unwrap ( ) ;
183
+ unit_enablement_impl ( sysroot, & unit_dir) . unwrap ( ) ;
184
+ let wantsdir = & unit_dir. open_dir ( "multi-user.target.wants" ) ?;
185
+ verify ( wantsdir, 2 ) ?;
186
+ assert ! ( wantsdir
187
+ . symlink_metadata_optional( CLEANUP_UNIT )
188
+ . unwrap( )
189
+ . is_none( ) ) ;
190
+
191
+ // Now create sysroot and rerun the generator
192
+ unit_enablement_impl ( sysroot, & unit_dir) . unwrap ( ) ;
193
+ verify ( wantsdir, 2 ) ?;
194
+
195
+ // Create the destructive stamp
196
+ sysroot
197
+ . create_dir_all ( Utf8Path :: new ( DESTRUCTIVE_CLEANUP ) . parent ( ) . unwrap ( ) )
198
+ . unwrap ( ) ;
199
+ sysroot. atomic_write ( DESTRUCTIVE_CLEANUP , b"" ) . unwrap ( ) ;
200
+ unit_enablement_impl ( sysroot, unit_dir) . unwrap ( ) ;
201
+ verify ( wantsdir, 3 ) ?;
202
+
203
+ // And now the unit should be enabled
204
+ assert ! ( wantsdir
205
+ . symlink_metadata( CLEANUP_UNIT )
206
+ . unwrap( )
207
+ . is_symlink( ) ) ;
208
+
209
+ Ok ( ( ) )
210
+ }
211
+
112
212
#[ cfg( test) ]
113
213
mod test {
114
214
use super :: * ;
0 commit comments