@@ -5,9 +5,14 @@ use std::process::Command;
5
5
use anyhow:: Result ;
6
6
use bootc_utils:: CommandRunExt ;
7
7
use fn_error_context:: context;
8
+ use openat_ext:: OpenatDirExt ;
8
9
use rustix:: fd:: BorrowedFd ;
9
10
use serde:: Deserialize ;
10
11
12
+ use crate :: blockdev;
13
+ use crate :: efi:: Efi ;
14
+ use std:: path:: Path ;
15
+
11
16
#[ derive( Deserialize , Debug ) ]
12
17
#[ serde( rename_all = "kebab-case" ) ]
13
18
#[ allow( dead_code) ]
@@ -38,3 +43,260 @@ pub(crate) fn inspect_filesystem(root: &openat::Dir, path: &str) -> Result<Files
38
43
. next ( )
39
44
. ok_or_else ( || anyhow:: anyhow!( "findmnt returned no data" ) )
40
45
}
46
+
47
+ #[ context( "Copying {file_path} from {src_root} to {dest_root}" ) ]
48
+ pub ( crate ) fn copy_files ( src_root : & str , dest_root : & str , file_path : & str ) -> Result < ( ) > {
49
+ let src_dir = openat:: Dir :: open ( src_root) ?;
50
+ let file_path = file_path. strip_prefix ( "/" ) . unwrap_or ( file_path) ;
51
+ let dest_dir = if file_path. starts_with ( "boot/efi" ) {
52
+ let efi = Efi :: default ( ) ;
53
+ match blockdev:: get_single_device ( "/" ) {
54
+ Ok ( device) => {
55
+ let esp_device = blockdev:: get_esp_partition ( & device) ?;
56
+ let esp_path = efi. ensure_mounted_esp (
57
+ Path :: new ( dest_root) ,
58
+ Path :: new ( & esp_device. unwrap_or_default ( ) ) ,
59
+ ) ?;
60
+ openat:: Dir :: open ( & esp_path) ?
61
+ }
62
+ Err ( e) => anyhow:: bail!( "Unable to find device: {}" , e) ,
63
+ }
64
+ } else {
65
+ openat:: Dir :: open ( dest_root) ?
66
+ } ;
67
+
68
+ let src_meta = src_dir. metadata ( file_path) ?;
69
+ match src_meta. simple_type ( ) {
70
+ openat:: SimpleType :: File => {
71
+ let parent = Path :: new ( file_path) . parent ( ) . unwrap_or ( Path :: new ( "." ) ) ;
72
+ if !parent. as_os_str ( ) . is_empty ( ) {
73
+ dest_dir. ensure_dir_all ( parent, 0o755 ) ?;
74
+ }
75
+ src_dir. copy_file_at ( file_path, & dest_dir, file_path) ?;
76
+ log:: info!( "Copied file: {} to destination" , file_path) ;
77
+ }
78
+ openat:: SimpleType :: Dir => {
79
+ anyhow:: bail!( "Unsupported copying of Directory {}" , file_path)
80
+ }
81
+ openat:: SimpleType :: Symlink => {
82
+ anyhow:: bail!( "Unsupported symbolic link {}" , file_path)
83
+ }
84
+ openat:: SimpleType :: Other => {
85
+ anyhow:: bail!( "Unsupported non-file/directory {}" , file_path)
86
+ }
87
+ }
88
+
89
+ Ok ( ( ) )
90
+ }
91
+
92
+ // ... existing code in filesystem.rs ...
93
+
94
+ #[ cfg( test) ]
95
+ mod test {
96
+ use super :: * ;
97
+ use anyhow:: Result ;
98
+ use openat_ext:: OpenatDirExt ;
99
+ use std:: fs as std_fs;
100
+ use std:: io:: Write ;
101
+ use tempfile:: tempdir;
102
+
103
+ #[ test]
104
+ fn test_copy_single_file_basic ( ) -> Result < ( ) > {
105
+ let tmp = tempdir ( ) ?;
106
+ let tmp_root_dir = openat:: Dir :: open ( tmp. path ( ) ) ?;
107
+
108
+ let src_root_name = "src_root" ;
109
+ let dest_root_name = "dest_root" ;
110
+
111
+ tmp_root_dir. create_dir ( src_root_name, 0o755 ) ?;
112
+ tmp_root_dir. create_dir ( dest_root_name, 0o755 ) ?;
113
+
114
+ let src_dir = tmp_root_dir. sub_dir ( src_root_name) ?;
115
+
116
+ let file_to_copy_rel = "file.txt" ;
117
+ let content = "This is a test file." ;
118
+
119
+ // Create source file using
120
+ src_dir. write_file_contents ( file_to_copy_rel, 0o644 , content. as_bytes ( ) ) ?;
121
+
122
+ let src_root_abs_path_str = tmp. path ( ) . join ( src_root_name) . to_str ( ) . unwrap ( ) . to_string ( ) ;
123
+ let dest_root_abs_path_str = tmp
124
+ . path ( )
125
+ . join ( dest_root_name)
126
+ . to_str ( )
127
+ . unwrap ( )
128
+ . to_string ( ) ;
129
+
130
+ copy_files (
131
+ & src_root_abs_path_str,
132
+ & dest_root_abs_path_str,
133
+ file_to_copy_rel,
134
+ ) ?;
135
+
136
+ let dest_file_abs_path = tmp. path ( ) . join ( dest_root_name) . join ( file_to_copy_rel) ;
137
+ assert ! ( dest_file_abs_path. exists( ) , "Destination file should exist" ) ;
138
+ assert_eq ! (
139
+ std_fs:: read_to_string( & dest_file_abs_path) ?,
140
+ content,
141
+ "File content should match"
142
+ ) ;
143
+
144
+ Ok ( ( ) )
145
+ }
146
+
147
+ #[ test]
148
+ fn test_copy_file_in_subdirectory ( ) -> Result < ( ) > {
149
+ let tmp = tempdir ( ) ?;
150
+ let tmp_root_dir = openat:: Dir :: open ( tmp. path ( ) ) ?;
151
+
152
+ let src_root_name = "src" ;
153
+ let dest_root_name = "dest" ;
154
+
155
+ tmp_root_dir. create_dir ( src_root_name, 0o755 ) ?;
156
+ tmp_root_dir. create_dir ( dest_root_name, 0o755 ) ?;
157
+
158
+ let src_dir_oat = tmp_root_dir. sub_dir ( src_root_name) ?;
159
+
160
+ let file_to_copy_rel = "subdir/another_file.txt" ;
161
+ let content = "Content in a subdirectory." ;
162
+
163
+ // Create subdirectory and file in source
164
+ src_dir_oat. ensure_dir_all ( "subdir" , 0o755 ) ?;
165
+ let mut f = src_dir_oat. write_file ( "subdir/another_file.txt" , 0o644 ) ?;
166
+ f. write_all ( content. as_bytes ( ) ) ?;
167
+ f. flush ( ) ?;
168
+
169
+ let src_root_abs_path_str = tmp. path ( ) . join ( src_root_name) . to_str ( ) . unwrap ( ) . to_string ( ) ;
170
+ let dest_root_abs_path_str = tmp
171
+ . path ( )
172
+ . join ( dest_root_name)
173
+ . to_str ( )
174
+ . unwrap ( )
175
+ . to_string ( ) ;
176
+
177
+ copy_files (
178
+ & src_root_abs_path_str,
179
+ & dest_root_abs_path_str,
180
+ file_to_copy_rel,
181
+ ) ?;
182
+
183
+ let dest_file_abs_path = tmp. path ( ) . join ( dest_root_name) . join ( file_to_copy_rel) ;
184
+ assert ! (
185
+ dest_file_abs_path. exists( ) ,
186
+ "Destination file in subdirectory should exist"
187
+ ) ;
188
+ assert_eq ! (
189
+ std_fs:: read_to_string( & dest_file_abs_path) ?,
190
+ content,
191
+ "File content should match"
192
+ ) ;
193
+ assert ! (
194
+ dest_file_abs_path. parent( ) . unwrap( ) . is_dir( ) ,
195
+ "Destination subdirectory should be a directory"
196
+ ) ;
197
+
198
+ Ok ( ( ) )
199
+ }
200
+
201
+ #[ test]
202
+ fn test_copy_file_with_leading_slash_in_filepath_arg ( ) -> Result < ( ) > {
203
+ let tmp = tempdir ( ) ?;
204
+ let tmp_root_dir = openat:: Dir :: open ( tmp. path ( ) ) ?;
205
+
206
+ let src_root_name = "src" ;
207
+ let dest_root_name = "dest" ;
208
+
209
+ tmp_root_dir. create_dir ( src_root_name, 0o755 ) ?;
210
+ tmp_root_dir. create_dir ( dest_root_name, 0o755 ) ?;
211
+
212
+ let src_dir_oat = tmp_root_dir. sub_dir ( src_root_name) ?;
213
+
214
+ let file_rel_actual = "root_file.txt" ;
215
+ let file_arg_with_slash = "/root_file.txt" ;
216
+ let content = "Leading slash test." ;
217
+
218
+ src_dir_oat. write_file_contents ( file_rel_actual, 0o644 , content. as_bytes ( ) ) ?;
219
+
220
+ let src_root_abs_path_str = tmp. path ( ) . join ( src_root_name) . to_str ( ) . unwrap ( ) . to_string ( ) ;
221
+ let dest_root_abs_path_str = tmp
222
+ . path ( )
223
+ . join ( dest_root_name)
224
+ . to_str ( )
225
+ . unwrap ( )
226
+ . to_string ( ) ;
227
+
228
+ copy_files (
229
+ & src_root_abs_path_str,
230
+ & dest_root_abs_path_str,
231
+ file_arg_with_slash,
232
+ ) ?;
233
+
234
+ // The destination path should be based on the path *after* stripping the leading slash
235
+ let dest_file_abs_path = tmp. path ( ) . join ( dest_root_name) . join ( file_rel_actual) ;
236
+ assert ! (
237
+ dest_file_abs_path. exists( ) ,
238
+ "Destination file should exist despite leading slash in arg"
239
+ ) ;
240
+ assert_eq ! (
241
+ std_fs:: read_to_string( & dest_file_abs_path) ?,
242
+ content,
243
+ "File content should match"
244
+ ) ;
245
+
246
+ Ok ( ( ) )
247
+ }
248
+
249
+ #[ test]
250
+ fn test_copy_fails_for_directory ( ) -> Result < ( ) > {
251
+ let tmp = tempdir ( ) ?;
252
+ let tmp_root_dir = openat:: Dir :: open ( tmp. path ( ) ) ?;
253
+
254
+ let src_root_name = "src" ;
255
+ let dest_root_name = "dest" ;
256
+
257
+ tmp_root_dir. create_dir ( src_root_name, 0o755 ) ?;
258
+ tmp_root_dir. create_dir ( dest_root_name, 0o755 ) ?;
259
+
260
+ let src_dir_oat = tmp_root_dir. sub_dir ( src_root_name) ?;
261
+
262
+ let dir_to_copy_rel = "a_directory" ;
263
+ src_dir_oat. create_dir ( dir_to_copy_rel, 0o755 ) ?; // Create the directory in the source
264
+
265
+ let src_root_abs_path_str = tmp. path ( ) . join ( src_root_name) . to_str ( ) . unwrap ( ) . to_string ( ) ;
266
+ let dest_root_abs_path_str = tmp
267
+ . path ( )
268
+ . join ( dest_root_name)
269
+ . to_str ( )
270
+ . unwrap ( )
271
+ . to_string ( ) ;
272
+
273
+ let result = copy_files (
274
+ & src_root_abs_path_str,
275
+ & dest_root_abs_path_str,
276
+ dir_to_copy_rel,
277
+ ) ;
278
+
279
+ assert ! ( result. is_err( ) , "Copying a directory should fail." ) ;
280
+ if let Err ( e) = result {
281
+ let mut found_unsupported_message = false ;
282
+ for cause in e. chain ( ) {
283
+ // Iterate through the error chain
284
+ if cause
285
+ . to_string ( )
286
+ . contains ( "Unsupported copying of Directory" )
287
+ {
288
+ found_unsupported_message = true ;
289
+ break ;
290
+ }
291
+ }
292
+ assert ! (
293
+ found_unsupported_message,
294
+ "The error chain should contain 'Unsupported copying of Directory'. Full error: {:#?}" ,
295
+ e
296
+ ) ;
297
+ } else {
298
+ panic ! ( "Expected an error when attempting to copy a directory, but got Ok." ) ;
299
+ }
300
+ Ok ( ( ) )
301
+ }
302
+ }
0 commit comments