1use anyhow::{bail, Context, Result};
16use lazy_static::lazy_static;
17use serde::Serialize;
18use std::collections::HashMap;
19use std::fs::{create_dir_all, read, File, OpenOptions};
20use std::io::{self, copy, BufReader, BufWriter, Read, Seek, Write};
21use std::path::{Component, Path, PathBuf};
22
23use crate::cmdline::*;
24use crate::io::*;
25use crate::iso9660::{self, IsoFs};
26use crate::miniso;
27use crate::util::set_die_on_sigpipe;
28
29mod customize;
30mod embed;
31mod util;
32
33use self::customize::*;
34use self::embed::*;
35use self::util::*;
36
37const INITRD_LIVE_STAMP_PATH: &str = "etc/coreos-live-initramfs";
38const COREOS_ISO_PXEBOOT_DIR: &str = "IMAGES/PXEBOOT";
39const COREOS_ISO_ROOTFS_IMG: &str = "IMAGES/PXEBOOT/ROOTFS.IMG";
40const COREOS_ISO_MINISO_FILE: &str = "COREOS/MINISO.DAT";
41
42lazy_static! {
43 static ref ALL_GLOB: GlobMatcher = GlobMatcher::new(&["*"]).unwrap();
44}
45
46pub fn iso_embed(config: IsoEmbedConfig) -> Result<()> {
47 eprintln!("`iso embed` is deprecated; use `iso ignition embed`. Continuing.");
48 iso_ignition_embed(IsoIgnitionEmbedConfig {
49 force: config.force,
50 ignition_file: config.config,
51 output: config.output,
52 input: config.input,
53 })
54}
55
56pub fn iso_show(config: IsoShowConfig) -> Result<()> {
57 eprintln!("`iso show` is deprecated; use `iso ignition show`. Continuing.");
58 iso_ignition_show(IsoIgnitionShowConfig {
59 input: config.input,
60 })
61}
62
63pub fn iso_remove(config: IsoRemoveConfig) -> Result<()> {
64 eprintln!("`iso remove` is deprecated; use `iso ignition remove`. Continuing.");
65 iso_ignition_remove(IsoIgnitionRemoveConfig {
66 output: config.output,
67 input: config.input,
68 })
69}
70
71pub fn iso_ignition_embed(config: IsoIgnitionEmbedConfig) -> Result<()> {
72 let ignition = match &config.ignition_file {
73 Some(ignition_path) => {
74 read(ignition_path).with_context(|| format!("reading {ignition_path}"))?
75 }
76 None => {
77 let mut data = Vec::new();
78 io::stdin()
79 .lock()
80 .read_to_end(&mut data)
81 .context("reading stdin")?;
82 data
83 }
84 };
85
86 let mut iso_file = open_live_iso(&config.input, Some(config.output.as_ref()))?;
87 let mut iso = IsoConfig::for_file(&mut iso_file)?;
88
89 if !config.force && iso.have_ignition() {
90 bail!("This ISO image already has an embedded Ignition config; use -f to force.");
91 }
92
93 iso.initrd_mut().add(INITRD_IGNITION_PATH, ignition);
94
95 write_live_iso(&iso, &mut iso_file, config.output.as_ref())
96}
97
98pub fn iso_ignition_show(config: IsoIgnitionShowConfig) -> Result<()> {
99 set_die_on_sigpipe()?;
100 let mut iso_file = open_live_iso(&config.input, None)?;
101 let iso = IsoConfig::for_file(&mut iso_file)?;
102 if !iso.have_ignition() {
103 bail!("No embedded Ignition config.");
104 }
105 let stdout = io::stdout();
106 let mut out = stdout.lock();
107 out.write_all(
108 iso.initrd()
109 .get(INITRD_IGNITION_PATH)
110 .context("couldn't find Ignition config in archive")?,
111 )
112 .context("writing output")?;
113 out.flush().context("flushing output")?;
114 Ok(())
115}
116
117pub fn iso_ignition_remove(config: IsoIgnitionRemoveConfig) -> Result<()> {
118 let mut iso_file = open_live_iso(&config.input, Some(config.output.as_ref()))?;
119 let mut iso = IsoConfig::for_file(&mut iso_file)?;
120
121 iso.initrd_mut().remove(INITRD_IGNITION_PATH);
122
123 write_live_iso(&iso, &mut iso_file, config.output.as_ref())
124}
125
126pub fn iso_network_embed(config: IsoNetworkEmbedConfig) -> Result<()> {
127 let mut iso_file = open_live_iso(&config.input, Some(config.output.as_ref()))?;
128 let mut iso_fs = IsoFs::from_file(iso_file.try_clone().context("cloning file")?)
129 .context("parsing ISO9660 image")?;
130 let mut iso = IsoConfig::for_iso(&mut iso_fs)?;
131
132 if !OsFeatures::for_iso(&mut iso_fs)?.live_initrd_network {
133 bail!("This OS image does not support customizing network settings.");
134 }
135 if !config.force && iso.have_network() {
136 bail!("This ISO image already has embedded network settings; use -f to force.");
137 }
138
139 iso.remove_network();
140 initrd_network_embed(iso.initrd_mut(), &config.keyfile)?;
141
142 write_live_iso(&iso, &mut iso_file, config.output.as_ref())
143}
144
145pub fn iso_network_extract(config: IsoNetworkExtractConfig) -> Result<()> {
146 let mut iso_file = open_live_iso(&config.input, None)?;
147 let iso = IsoConfig::for_file(&mut iso_file)?;
148 initrd_network_extract(iso.initrd(), config.directory.as_ref())
149}
150
151pub fn iso_network_remove(config: IsoNetworkRemoveConfig) -> Result<()> {
152 let mut iso_file = open_live_iso(&config.input, Some(config.output.as_ref()))?;
153 let mut iso = IsoConfig::for_file(&mut iso_file)?;
154
155 iso.remove_network();
156
157 write_live_iso(&iso, &mut iso_file, config.output.as_ref())
158}
159
160pub fn pxe_ignition_wrap(config: PxeIgnitionWrapConfig) -> Result<()> {
161 if config.output.is_none() {
162 verify_stdout_not_tty()?;
163 }
164
165 let ignition = match &config.ignition_file {
166 Some(ignition_path) => {
167 read(ignition_path).with_context(|| format!("reading {ignition_path}"))?
168 }
169 None => {
170 let mut data = Vec::new();
171 io::stdin()
172 .lock()
173 .read_to_end(&mut data)
174 .context("reading stdin")?;
175 data
176 }
177 };
178
179 let mut initrd = Initrd::default();
180 initrd.add(INITRD_IGNITION_PATH, ignition);
181
182 write_live_pxe(&initrd, config.output.as_ref())
183}
184
185pub fn pxe_ignition_unwrap(config: PxeIgnitionUnwrapConfig) -> Result<()> {
186 set_die_on_sigpipe()?;
187 let stdin = io::stdin();
188 let mut f: Box<dyn Read> = if let Some(path) = &config.input {
189 Box::new(
190 OpenOptions::new()
191 .read(true)
192 .open(path)
193 .with_context(|| format!("opening {path}"))?,
194 )
195 } else {
196 Box::new(stdin.lock())
197 };
198 let stdout = io::stdout();
199 let mut out = stdout.lock();
200 out.write_all(
201 Initrd::from_reader_filtered(&mut f, &INITRD_IGNITION_GLOB)?
202 .get(INITRD_IGNITION_PATH)
203 .context("couldn't find Ignition config in archive")?,
204 )
205 .context("writing output")?;
206 out.flush().context("flushing output")?;
207 Ok(())
208}
209
210pub fn pxe_network_wrap(config: PxeNetworkWrapConfig) -> Result<()> {
211 if config.output.is_none() {
212 verify_stdout_not_tty()?;
213 }
214
215 let mut initrd = Initrd::default();
216 initrd_network_embed(&mut initrd, &config.keyfile)?;
217
218 write_live_pxe(&initrd, config.output.as_ref())
219}
220
221fn initrd_network_embed(initrd: &mut Initrd, keyfiles: &[String]) -> Result<()> {
222 for path in keyfiles {
223 let data = read(path).with_context(|| format!("reading {path}"))?;
224 let name = filename(path)?;
225 let path = format!("{INITRD_NETWORK_DIR}/{name}");
226 if initrd.get(&path).is_some() {
227 bail!("multiple input files named '{}'", name);
228 }
229 initrd.add(&path, data);
230 }
231 Ok(())
232}
233
234pub fn pxe_network_unwrap(config: PxeNetworkUnwrapConfig) -> Result<()> {
235 let stdin = io::stdin();
236 let f: Box<dyn Read> = if let Some(path) = &config.input {
237 Box::new(
238 OpenOptions::new()
239 .read(true)
240 .open(path)
241 .with_context(|| format!("opening {path}"))?,
242 )
243 } else {
244 Box::new(stdin.lock())
245 };
246 initrd_network_extract(
247 &Initrd::from_reader_filtered(f, &INITRD_NETWORK_GLOB)?,
248 config.directory.as_ref(),
249 )
250}
251
252fn initrd_network_extract(initrd: &Initrd, directory: Option<&String>) -> Result<()> {
253 let files = initrd.find(&INITRD_NETWORK_GLOB);
254 if files.is_empty() {
255 bail!("No embedded network settings.");
256 }
257 if let Some(dir) = directory {
258 create_dir_all(dir)?;
259 for (path, contents) in files {
260 let path = Path::new(dir).join(filename(path)?);
261 OpenOptions::new()
262 .create_new(true)
263 .write(true)
264 .open(&path)
265 .with_context(|| format!("opening {}", path.display()))?
266 .write_all(contents)
267 .with_context(|| format!("writing {}", path.display()))?;
268 println!("{}", path.display());
269 }
270 } else {
271 set_die_on_sigpipe()?;
272 for (i, (path, contents)) in files.iter().enumerate() {
273 if i > 0 {
274 println!();
275 }
276 println!("########## {} ##########", filename(path)?);
277 io::stdout()
278 .lock()
279 .write_all(contents)
280 .context("writing network settings to stdout")?;
281 }
282 }
283 Ok(())
284}
285
286pub fn iso_kargs_modify(config: IsoKargsModifyConfig) -> Result<()> {
287 let mut iso_file = open_live_iso(&config.input, Some(config.output.as_ref()))?;
288 let mut iso = IsoConfig::for_file(&mut iso_file)?;
289
290 let kargs = KargsEditor::new()
291 .append(&config.append)
292 .replace(&config.replace)
293 .delete(&config.delete)
294 .apply_to(iso.kargs()?)?;
295 iso.set_kargs(&kargs)?;
296
297 write_live_iso(&iso, &mut iso_file, config.output.as_ref())
298}
299
300pub fn iso_kargs_reset(config: IsoKargsResetConfig) -> Result<()> {
301 let mut iso_file = open_live_iso(&config.input, Some(config.output.as_ref()))?;
302 let mut iso = IsoConfig::for_file(&mut iso_file)?;
303
304 #[allow(clippy::unnecessary_to_owned)]
305 iso.set_kargs(&iso.kargs_default()?.to_string())?;
306
307 write_live_iso(&iso, &mut iso_file, config.output.as_ref())
308}
309
310pub fn iso_kargs_show(config: IsoKargsShowConfig) -> Result<()> {
311 set_die_on_sigpipe()?;
312 let mut iso_file = open_live_iso(&config.input, None)?;
313 let iso = IsoConfig::for_file(&mut iso_file)?;
314 let kargs = if config.default {
315 iso.kargs_default()?
316 } else {
317 iso.kargs()?
318 };
319 println!("{kargs}");
320 Ok(())
321}
322
323pub fn iso_customize(config: IsoCustomizeConfig) -> Result<()> {
324 let mut iso_file = open_live_iso(&config.input, Some(config.output.as_ref()))?;
325 let mut iso_fs = IsoFs::from_file(iso_file.try_clone().context("cloning file")?)
326 .context("parsing ISO9660 image")?;
327 let mut iso = IsoConfig::for_iso(&mut iso_fs)?;
328
329 if !config.force
330 && (iso.have_ignition()
331 || iso.have_network()
332 || (iso.kargs_supported() && iso.kargs()? != iso.kargs_default()?))
333 {
334 bail!("This ISO image is already customized; use -f to force.");
335 }
336
337 let live = LiveInitrd::from_common(&config.common, OsFeatures::for_iso(&mut iso_fs)?)?;
338 *iso.initrd_mut() = live.into_initrd()?;
339
340 if [
341 &config.live_karg_append,
342 &config.live_karg_replace,
343 &config.live_karg_delete,
344 ]
345 .iter()
346 .any(|v| !v.is_empty())
347 {
348 if !iso.kargs_supported() {
349 bail!("This OS image does not support customizing live kernel arguments.");
350 }
351 let kargs = KargsEditor::new()
352 .append(&config.live_karg_append)
353 .replace(&config.live_karg_replace)
354 .delete(&config.live_karg_delete)
355 .apply_to(iso.kargs_default()?)?;
356 iso.set_kargs(&kargs)?;
357 }
358
359 write_live_iso(&iso, &mut iso_file, config.output.as_ref())
360}
361
362pub fn iso_reset(config: IsoResetConfig) -> Result<()> {
363 let mut iso_file = open_live_iso(&config.input, Some(config.output.as_ref()))?;
364 let mut iso = IsoConfig::for_file(&mut iso_file)?;
365
366 *iso.initrd_mut() = Initrd::default();
367 if iso.kargs_supported() {
368 #[allow(clippy::unnecessary_to_owned)]
369 iso.set_kargs(&iso.kargs_default()?.to_string())?;
370 };
371
372 write_live_iso(&iso, &mut iso_file, config.output.as_ref())
373}
374
375pub fn pxe_customize(config: PxeCustomizeConfig) -> Result<()> {
376 let mut input = BufReader::with_capacity(
378 BUFFER_SIZE,
379 OpenOptions::new()
380 .read(true)
381 .open(&config.input)
382 .with_context(|| format!("opening {}", &config.input))?,
383 );
384 let mut tempfile = match &*config.output {
385 "-" => {
386 verify_stdout_not_tty()?;
387 None
388 }
389 path => {
390 let dir = Path::new(path)
391 .parent()
392 .with_context(|| format!("no parent directory of {path}"))?;
393 let tempfile = tempfile::Builder::new()
394 .prefix(".coreos-installer-temp-")
395 .tempfile_in(dir)
396 .context("creating temporary file")?;
397 Some(tempfile)
398 }
399 };
400
401 let filter = GlobMatcher::new(&[
403 INITRD_LIVE_STAMP_PATH,
404 INITRD_FEATURES_PATH,
405 INITRD_IGNITION_PATH,
406 &format!("{INITRD_NETWORK_DIR}/*"),
407 ])
408 .unwrap();
409 let base_initrd = match &*config.output {
410 "-" => {
411 Initrd::from_reader_filtered(TeeReader::new(&mut input, io::stdout().lock()), &filter)
412 .context("reading/copying input initrd")?
413 }
414 _ => Initrd::from_reader_filtered(
415 TeeReader::new(&mut input, tempfile.as_mut().unwrap()),
416 &filter,
417 )
418 .context("reading/copying input initrd")?,
419 };
420 if base_initrd.get(INITRD_LIVE_STAMP_PATH).is_none() {
421 bail!("not a CoreOS live initramfs image");
422 }
423 if base_initrd.get(INITRD_IGNITION_PATH).is_some()
424 || !base_initrd.find(&INITRD_NETWORK_GLOB).is_empty()
425 {
426 bail!("input is already customized");
427 }
428 let features = match base_initrd.get(INITRD_FEATURES_PATH) {
429 Some(json) => serde_json::from_slice::<OsFeatures>(json).context("parsing OS features")?,
430 None => OsFeatures::default(),
431 };
432
433 let live = LiveInitrd::from_common(&config.common, features)?;
434 let initrd = live.into_initrd()?;
435 if initrd.get(INITRD_IGNITION_PATH).is_some() {
436 eprintln!(
437 "PXE configuration must include kernel arguments:\n\tignition.firstboot ignition.platform.id=metal"
438 );
439 }
440
441 let do_write = |writer: &mut dyn Write| -> Result<()> {
443 let mut buf = BufWriter::with_capacity(BUFFER_SIZE, writer);
444 buf.write_all(&initrd.to_bytes()?)
445 .context("writing initrd")?;
446 buf.flush().context("flushing initrd")
447 };
448 match &*config.output {
449 "-" => do_write(&mut io::stdout().lock()),
450 path => {
451 let mut tempfile = tempfile.unwrap();
452 do_write(tempfile.as_file_mut())?;
453 tempfile
454 .persist_noclobber(path)
455 .map_err(|e| e.error)
456 .with_context(|| format!("persisting output file to {path}"))?;
457 Ok(())
458 }
459 }
460}
461
462#[derive(Serialize)]
463struct DevShowIsoOutput {
464 header: IsoFs,
465 records: Vec<String>,
466}
467
468pub fn dev_show_iso(config: DevShowIsoConfig) -> Result<()> {
469 set_die_on_sigpipe()?;
470 let mut iso_file = open_live_iso(&config.input, None)?;
471 let stdout = io::stdout();
472 let mut out = stdout.lock();
473 if config.ignition || config.kargs {
474 let iso = IsoConfig::for_file(&mut iso_file)?;
475 let data = if config.ignition {
476 iso.initrd_header_json()?
477 } else {
478 iso.kargs_header_json()?
479 };
480 out.write_all(&data).context("failed to write header")?;
481 } else {
482 let mut iso = IsoFs::from_file(iso_file)?;
483 let records = iso
484 .walk()?
485 .map(|r| r.map(|(path, _)| path))
486 .collect::<Result<Vec<String>>>()
487 .context("while walking ISO filesystem")?;
488 let info = DevShowIsoOutput {
489 header: iso,
490 records,
491 };
492
493 serde_json::to_writer_pretty(&mut out, &info)
494 .context("failed to serialize ISO metadata")?;
495 out.write_all(b"\n").context("failed to write newline")?;
496 }
497 Ok(())
498}
499
500pub fn dev_show_initrd(config: DevShowInitrdConfig) -> Result<()> {
501 set_die_on_sigpipe()?;
502 let initrd = read_initrd(&config.input, &config.filter)?;
503 for path in initrd.find(&ALL_GLOB).keys() {
504 println!("{path}");
505 }
506 Ok(())
507}
508
509pub fn dev_extract_initrd(config: DevExtractInitrdConfig) -> Result<()> {
510 let initrd = read_initrd(&config.input, &config.filter)?;
511 let base_path = Path::new(&config.directory);
512 for (path, contents) in initrd.find(&ALL_GLOB) {
513 if Path::new(path)
514 .components()
515 .any(|c| matches!(c, Component::RootDir | Component::ParentDir))
516 {
517 bail!("path {} contains path traversal", path);
518 }
519 let out_path = base_path.join(path);
520 if config.verbose {
521 println!("{}", out_path.display());
522 }
523 let out_parent = out_path
524 .parent()
525 .with_context(|| format!("finding parent of {}", out_path.display()))?;
526 create_dir_all(out_parent).with_context(|| format!("creating {}", out_parent.display()))?;
527 OpenOptions::new()
528 .create_new(true)
529 .write(true)
530 .open(&out_path)
531 .with_context(|| format!("opening {}", out_path.display()))?
532 .write_all(contents)
533 .with_context(|| format!("writing {}", out_path.display()))?;
534 }
535 Ok(())
536}
537
538fn read_initrd(path: &str, filter: &[String]) -> Result<Initrd> {
539 let filter = if filter.is_empty() {
540 vec!["*"]
541 } else {
542 filter.iter().map(String::as_str).collect()
543 };
544 let filter = GlobMatcher::new(&filter).context("parsing glob patterns")?;
545 match path {
546 "-" => Initrd::from_reader_filtered(io::stdin().lock(), &filter),
547 path => Initrd::from_reader_filtered(
548 OpenOptions::new()
549 .read(true)
550 .open(path)
551 .with_context(|| format!("opening {path}"))?,
552 &filter,
553 ),
554 }
555 .context("decoding initrd")
556}
557
558pub fn iso_extract_pxe(config: IsoExtractPxeConfig) -> Result<()> {
559 let mut iso = IsoFs::from_file(open_live_iso(&config.input, None)?)?;
560 let pxeboot = iso
561 .get_path(COREOS_ISO_PXEBOOT_DIR)
562 .context("Unrecognized CoreOS ISO image.")?
563 .try_into_dir()?;
564 create_dir_all(&config.output_dir)?;
565
566 let base = {
567 let mut s = Path::new(&config.input).file_stem().unwrap().to_os_string();
569 s.push("-");
570 s
571 };
572
573 for record in iso.list_dir(&pxeboot)? {
574 match record? {
575 iso9660::DirectoryRecord::Directory(_) => continue,
576 iso9660::DirectoryRecord::File(file) => {
577 let filename = {
578 let mut s = base.clone();
579 s.push(file.name.to_lowercase());
580 s
581 };
582 let path = Path::new(&config.output_dir).join(filename);
583 println!("{}", path.display());
584 copy_file_from_iso(&mut iso, &file, &path)?;
585 }
586 }
587 }
588 Ok(())
589}
590
591pub fn iso_extract_minimal_iso(config: IsoExtractMinimalIsoConfig) -> Result<()> {
592 let mut full_iso = IsoFs::from_file(open_live_iso(&config.input, None)?)?;
595
596 let iso = IsoConfig::for_iso(&mut full_iso)?;
599 if !iso.initrd().is_empty() || iso.kargs()? != iso.kargs_default()? {
600 bail!("Cannot operate on ISO with embedded customizations.\nReset it with `coreos-installer iso reset` and try again.");
601 }
602
603 let output_dir: PathBuf = if &config.output == "-" {
605 verify_stdout_not_tty()?;
606 std::env::temp_dir()
607 } else {
608 Path::new(&config.output)
609 .parent()
610 .with_context(|| format!("no parent directory of {}", &config.output))?
611 .into()
612 };
613
614 if let Some(path) = &config.output_rootfs {
615 let rootfs = full_iso
616 .get_path(COREOS_ISO_ROOTFS_IMG)
617 .with_context(|| format!("looking up '{COREOS_ISO_ROOTFS_IMG}'"))?
618 .try_into_file()?;
619 copy_file_from_iso(&mut full_iso, &rootfs, Path::new(path))?;
620 }
621
622 let miniso_data_file = match full_iso.get_path(COREOS_ISO_MINISO_FILE) {
623 Ok(record) => record.try_into_file()?,
624 Err(e) if e.is::<iso9660::NotFound>() => {
625 bail!("This ISO image does not support extracting a minimal ISO.")
626 }
627 Err(e) => return Err(e).with_context(|| format!("looking up '{COREOS_ISO_MINISO_FILE}'")),
628 };
629
630 let data = {
631 let mut f = full_iso.read_file(&miniso_data_file)?;
632 miniso::Data::deserialize(&mut f).context("reading miniso data file")?
633 };
634 let mut outf = tempfile::Builder::new()
635 .prefix(".coreos-installer-temp-")
636 .tempfile_in(output_dir)
637 .context("creating temporary file")?;
638 data.unxzpack(full_iso.as_file()?, &mut outf)
639 .context("unpacking miniso")?;
640
641 modify_miniso_kargs(outf.as_file_mut(), config.rootfs_url.as_ref())
642 .context("modifying miniso kernel args")?;
643
644 if &config.output == "-" {
645 outf.rewind()
646 .context("seeking back to start of miniso tempfile")?;
647 copy(&mut outf, &mut io::stdout().lock()).context("writing output")?;
648 } else {
649 outf.persist_noclobber(&config.output)
650 .map_err(|e| e.error)?;
651 }
652
653 Ok(())
654}
655
656pub fn pack_minimal_iso(config: PackMinimalIsoConfig) -> Result<()> {
657 let mut full_iso = IsoFs::from_file(open_live_iso(&config.full, Some(None))?)?;
658 let mut minimal_iso = IsoFs::from_file(open_live_iso(&config.minimal, None)?)?;
659
660 let full_files = collect_iso_files(&mut full_iso)
661 .with_context(|| format!("collecting files from {}", &config.full))?;
662 let minimal_files = collect_iso_files(&mut minimal_iso)
663 .with_context(|| format!("collecting files from {}", &config.minimal))?;
664 if full_files.is_empty() {
665 bail!("No files found in {}", &config.full);
666 } else if minimal_files.is_empty() {
667 bail!("No files found in {}", &config.minimal);
668 }
669
670 eprintln!("Packing minimal ISO");
671 let (data, matches, skipped, written, written_compressed) =
672 miniso::Data::xzpack(minimal_iso.as_file()?, &full_files, &minimal_files)
673 .context("packing miniso")?;
674 eprintln!("Matched {} files of {}", matches, minimal_files.len());
675
676 eprintln!("Total bytes skipped: {skipped}");
677 eprintln!("Total bytes written: {written}");
678 eprintln!("Total bytes written (compressed): {written_compressed}");
679
680 eprintln!("Verifying that packed image matches digest");
681 data.unxzpack(full_iso.as_file()?, std::io::sink())
682 .context("unpacking miniso for verification")?;
683
684 let miniso_entry = full_iso
685 .get_path(COREOS_ISO_MINISO_FILE)
686 .with_context(|| format!("looking up '{COREOS_ISO_MINISO_FILE}'"))?
687 .try_into_file()?;
688 let mut w = full_iso.overwrite_file(&miniso_entry)?;
689 data.serialize(&mut w).context("writing miniso data file")?;
690 w.flush().context("flushing full ISO")?;
691
692 if config.consume {
693 std::fs::remove_file(&config.minimal)
694 .with_context(|| format!("consuming {}", &config.minimal))?;
695 }
696
697 eprintln!("Packing successful!");
698 Ok(())
699}
700
701fn collect_iso_files(iso: &mut IsoFs) -> Result<HashMap<String, iso9660::File>> {
702 iso.walk()?
703 .filter_map(|r| match r {
704 Err(e) => Some(Err(e)),
705 Ok((s, iso9660::DirectoryRecord::File(f))) => Some(Ok((s, f))),
706 Ok(_) => None,
707 })
708 .collect::<Result<HashMap<String, iso9660::File>>>()
709 .context("while walking ISO filesystem")
710}
711
712fn modify_miniso_kargs(f: &mut File, rootfs_url: Option<&String>) -> Result<()> {
713 let mut iso = IsoFs::from_file(f.try_clone().context("cloning a file")?)?;
714 let mut cfg = IsoConfig::for_file(f)?;
715
716 let kargs = cfg.kargs()?;
717
718 let liveiso_karg = kargs
720 .split_ascii_whitespace()
721 .find(|&karg| karg.starts_with("coreos.liveiso="))
722 .context("minimal ISO does not have coreos.liveiso= karg")?
723 .to_string();
724
725 let new_default_kargs = KargsEditor::new().delete(&[liveiso_karg]).apply_to(kargs)?;
726 cfg.set_kargs(&new_default_kargs)?;
727
728 if let Some(url) = rootfs_url {
729 if url.split_ascii_whitespace().count() > 1 {
730 bail!("forbidden whitespace found in '{}'", url);
731 }
732 let final_kargs = KargsEditor::new()
733 .append(&[format!("coreos.live.rootfs_url={url}")])
734 .apply_to(&new_default_kargs)?;
735
736 cfg.set_kargs(&final_kargs)?;
737 }
738
739 write_live_iso(&cfg, f, None)?;
741
742 set_default_kargs(&mut iso, new_default_kargs)
745}