Prepare, configure, and manage Firecracker microVMs in seconds!
virtualization linux microvm firecracker

Merge pull request #8 from tsirysndr/feat/extract-vmlinuz

feat: add vmlinuz extraction functionality and update prepare function to accept kernel file

authored by tsiry-sandratraina.com and committed by

GitHub 7c3b1c17 12d4749c

+312 -57
+184 -56
crates/firecracker-prepare/src/lib.rs
··· 4 4 use owo_colors::OwoColorize; 5 5 use serde::{Deserialize, Serialize}; 6 6 7 - use crate::command::{run_command, run_command_with_stdout_inherit}; 7 + use crate::{ 8 + command::{run_command, run_command_with_stdout_inherit}, 9 + vmlinuz::extract_vmlinuz, 10 + }; 8 11 9 12 pub mod command; 10 13 pub mod config; 11 14 pub mod downloader; 12 15 pub mod rootfs; 13 16 pub mod ssh; 17 + pub mod vmlinuz; 14 18 15 19 const BRIDGE_IP: &str = "172.16.0.1"; 16 20 ··· 49 53 } 50 54 } 51 55 56 + pub fn prepare(distro: Distro, kernel_file: Option<String>) -> Result<()> { 57 + let arch = run_command("uname", &["-m"], false)?.stdout; 58 + let arch = String::from_utf8_lossy(&arch).trim().to_string(); 59 + println!("[+] Detected architecture: {}", arch.bright_green()); 60 + 61 + if let Some(ref vmlinuz_file) = kernel_file { 62 + if !std::path::Path::new(vmlinuz_file).exists() { 63 + println!( 64 + "{} {}", 65 + "[!]".red(), 66 + format!("vmlinuz file {} does not exist", vmlinuz_file).red() 67 + ); 68 + std::process::exit(1); 69 + } 70 + } 71 + 72 + let app_dir = config::get_config_dir()?; 73 + let preparer: Box<dyn RootfsPreparer> = match distro { 74 + Distro::Debian => Box::new(DebianPreparer), 75 + Distro::Alpine => Box::new(AlpinePreparer), 76 + Distro::Ubuntu => Box::new(UbuntuPreparer), 77 + Distro::NixOS => Box::new(NixOSPreparer), 78 + Distro::Fedora => Box::new(FedoraPreparer), 79 + Distro::Gentoo => Box::new(GentooPreparer), 80 + Distro::Slackware => Box::new(SlackwarePreparer), 81 + Distro::Opensuse => Box::new(OpensusePreparer), 82 + Distro::OpensuseTumbleweed => Box::new(OpensuseTumbleweedPreparer), 83 + Distro::Almalinux => Box::new(AlmalinuxPreparer), 84 + Distro::RockyLinux => Box::new(RockyLinuxPreparer), 85 + Distro::Archlinux => Box::new(ArchlinuxPreparer), 86 + }; 87 + 88 + let (kernel_file, img_file, ssh_key_file) = preparer.prepare(&arch, &app_dir, kernel_file)?; 89 + 90 + extract_vmlinuz(&kernel_file)?; 91 + 92 + println!("[✓] Kernel: {}", kernel_file.bright_green()); 93 + println!("[✓] Rootfs: {}", img_file.bright_green()); 94 + println!("[✓] SSH Key: {}", ssh_key_file.bright_green()); 95 + 96 + Ok(()) 97 + } 98 + 52 99 pub trait RootfsPreparer { 53 - fn prepare(&self, arch: &str, app_dir: &str) -> Result<(String, String, String)>; 100 + fn prepare( 101 + &self, 102 + arch: &str, 103 + app_dir: &str, 104 + kernel_file: Option<String>, 105 + ) -> Result<(String, String, String)>; 54 106 fn name(&self) -> &'static str; 55 107 } 56 108 ··· 72 124 "Debian" 73 125 } 74 126 75 - fn prepare(&self, arch: &str, app_dir: &str) -> Result<(String, String, String)> { 127 + fn prepare( 128 + &self, 129 + arch: &str, 130 + app_dir: &str, 131 + kernel_file: Option<String>, 132 + ) -> Result<(String, String, String)> { 76 133 println!( 77 134 "[+] Preparing {} rootfs for {}...", 78 135 self.name(), 79 136 arch.bright_green() 80 137 ); 81 - let kernel_file = downloader::download_kernel(arch)?; 138 + 139 + let kernel_file = match kernel_file { 140 + Some(k) => fs::canonicalize(k)?.to_str().unwrap().to_string(), 141 + None => downloader::download_kernel(arch)?, 142 + }; 82 143 let debootstrap_dir = format!("{}/debian-rootfs", app_dir); 83 144 84 145 let arch = match arch { ··· 186 247 "Alpine" 187 248 } 188 249 189 - fn prepare(&self, arch: &str, app_dir: &str) -> Result<(String, String, String)> { 250 + fn prepare( 251 + &self, 252 + arch: &str, 253 + app_dir: &str, 254 + kernel_file: Option<String>, 255 + ) -> Result<(String, String, String)> { 190 256 println!( 191 257 "[+] Preparing {} rootfs for {}...", 192 258 self.name(), 193 259 arch.bright_green() 194 260 ); 195 - let kernel_file = downloader::download_kernel(arch)?; 261 + 262 + let kernel_file = match kernel_file { 263 + Some(k) => fs::canonicalize(k)?.to_str().unwrap().to_string(), 264 + None => downloader::download_kernel(arch)?, 265 + }; 196 266 let minirootfs = format!("{}/minirootfs", app_dir); 197 267 downloader::download_alpine_rootfs(&minirootfs, arch)?; 198 268 ··· 310 380 "Ubuntu" 311 381 } 312 382 313 - fn prepare(&self, arch: &str, app_dir: &str) -> Result<(String, String, String)> { 383 + fn prepare( 384 + &self, 385 + arch: &str, 386 + app_dir: &str, 387 + kernel_file: Option<String>, 388 + ) -> Result<(String, String, String)> { 314 389 println!( 315 390 "[+] Preparing {} rootfs for {}...", 316 391 self.name(), 317 392 arch.bright_green() 318 393 ); 319 - let (kernel_file, ubuntu_file, _ubuntu_version) = downloader::download_files(arch)?; 394 + let (vmlinuz_file, ubuntu_file, _ubuntu_version) = downloader::download_files(arch)?; 395 + 396 + let kernel_file = match kernel_file { 397 + Some(k) => fs::canonicalize(k)?.to_str().unwrap().to_string(), 398 + None => vmlinuz_file, 399 + }; 320 400 321 401 let squashfs_root_dir = format!("{}/squashfs_root", app_dir); 322 402 rootfs::extract_squashfs(&ubuntu_file, &squashfs_root_dir)?; ··· 363 443 "NixOS" 364 444 } 365 445 366 - fn prepare(&self, arch: &str, app_dir: &str) -> Result<(String, String, String)> { 446 + fn prepare( 447 + &self, 448 + arch: &str, 449 + app_dir: &str, 450 + kernel_file: Option<String>, 451 + ) -> Result<(String, String, String)> { 367 452 println!( 368 453 "[+] Preparing {} rootfs for {}...", 369 454 self.name(), 370 455 arch.bright_green() 371 456 ); 372 - let kernel_file = downloader::download_kernel(arch)?; 457 + let kernel_file = match kernel_file { 458 + Some(k) => fs::canonicalize(k)?.to_str().unwrap().to_string(), 459 + None => downloader::download_kernel(arch)?, 460 + }; 373 461 let nixos_rootfs = format!("{}/nixos-rootfs", app_dir); 374 462 let squashfs_file = format!("{}/nixos-rootfs.squashfs", app_dir); 375 463 ··· 399 487 "Fedora" 400 488 } 401 489 402 - fn prepare(&self, arch: &str, app_dir: &str) -> Result<(String, String, String)> { 490 + fn prepare( 491 + &self, 492 + arch: &str, 493 + app_dir: &str, 494 + kernel_file: Option<String>, 495 + ) -> Result<(String, String, String)> { 403 496 println!( 404 497 "[+] Preparing {} rootfs for {}...", 405 498 self.name(), 406 499 arch.bright_green() 407 500 ); 408 501 409 - let kernel_file = downloader::download_kernel(arch)?; 502 + let kernel_file = match kernel_file { 503 + Some(k) => fs::canonicalize(k)?.to_str().unwrap().to_string(), 504 + None => downloader::download_kernel(arch)?, 505 + }; 410 506 let fedora_rootfs = format!("{}/fedora-rootfs", app_dir); 411 507 let squashfs_file = format!("{}/fedora-rootfs.squashfs", app_dir); 412 508 ··· 442 538 "Gentoo" 443 539 } 444 540 445 - fn prepare(&self, arch: &str, app_dir: &str) -> Result<(String, String, String)> { 541 + fn prepare( 542 + &self, 543 + arch: &str, 544 + app_dir: &str, 545 + kernel_file: Option<String>, 546 + ) -> Result<(String, String, String)> { 446 547 println!( 447 548 "[+] Preparing {} rootfs for {}...", 448 549 self.name(), 449 550 arch.bright_green() 450 551 ); 451 552 452 - let kernel_file = downloader::download_kernel(arch)?; 553 + let kernel_file = match kernel_file { 554 + Some(k) => fs::canonicalize(k)?.to_str().unwrap().to_string(), 555 + None => downloader::download_kernel(arch)?, 556 + }; 557 + 453 558 let gentoo_rootfs = format!("{}/gentoo-rootfs", app_dir); 454 559 let squashfs_file = format!("{}/gentoo-rootfs.squashfs", app_dir); 455 560 ··· 480 585 "Slackware" 481 586 } 482 587 483 - fn prepare(&self, arch: &str, app_dir: &str) -> Result<(String, String, String)> { 588 + fn prepare( 589 + &self, 590 + arch: &str, 591 + app_dir: &str, 592 + kernel_file: Option<String>, 593 + ) -> Result<(String, String, String)> { 484 594 println!( 485 595 "[+] Preparing {} rootfs for {}...", 486 596 self.name(), 487 597 arch.bright_green() 488 598 ); 489 599 490 - let kernel_file = downloader::download_kernel(arch)?; 600 + let kernel_file = match kernel_file { 601 + Some(k) => fs::canonicalize(k)?.to_str().unwrap().to_string(), 602 + None => downloader::download_kernel(arch)?, 603 + }; 604 + 491 605 let slackware_rootfs = format!("{}/slackware-rootfs", app_dir); 492 606 let squashfs_file = format!("{}/slackware-rootfs.squashfs", app_dir); 493 607 ··· 523 637 "OpenSUSE (Leap)" 524 638 } 525 639 526 - fn prepare(&self, arch: &str, app_dir: &str) -> Result<(String, String, String)> { 640 + fn prepare( 641 + &self, 642 + arch: &str, 643 + app_dir: &str, 644 + kernel_file: Option<String>, 645 + ) -> Result<(String, String, String)> { 527 646 println!( 528 647 "[+] Preparing {} rootfs for {}...", 529 648 self.name(), 530 649 arch.bright_green() 531 650 ); 532 651 533 - let kernel_file = downloader::download_kernel(arch)?; 652 + let kernel_file = match kernel_file { 653 + Some(k) => fs::canonicalize(k)?.to_str().unwrap().to_string(), 654 + None => downloader::download_kernel(arch)?, 655 + }; 656 + 534 657 let opensuse_rootfs = format!("{}/opensuse-rootfs", app_dir); 535 658 let squashfs_file = format!("{}/opensuse-rootfs.squashfs", app_dir); 536 659 ··· 559 682 "AlmaLinux" 560 683 } 561 684 562 - fn prepare(&self, arch: &str, app_dir: &str) -> Result<(String, String, String)> { 685 + fn prepare( 686 + &self, 687 + arch: &str, 688 + app_dir: &str, 689 + kernel_file: Option<String>, 690 + ) -> Result<(String, String, String)> { 563 691 println!( 564 692 "[+] Preparing {} rootfs for {}...", 565 693 self.name(), 566 694 arch.bright_green() 567 695 ); 568 696 569 - let kernel_file = downloader::download_kernel(arch)?; 697 + let kernel_file = match kernel_file { 698 + Some(k) => fs::canonicalize(k)?.to_str().unwrap().to_string(), 699 + None => downloader::download_kernel(arch)?, 700 + }; 701 + 570 702 let almalinux_rootfs = format!("{}/almalinux-rootfs", app_dir); 571 703 let squashfs_file = format!("{}/almalinux-rootfs.squashfs", app_dir); 572 704 ··· 590 722 "RockyLinux" 591 723 } 592 724 593 - fn prepare(&self, arch: &str, app_dir: &str) -> Result<(String, String, String)> { 725 + fn prepare( 726 + &self, 727 + arch: &str, 728 + app_dir: &str, 729 + kernel_file: Option<String>, 730 + ) -> Result<(String, String, String)> { 594 731 println!( 595 732 "[+] Preparing {} rootfs for {}...", 596 733 self.name(), 597 734 arch.bright_green() 598 735 ); 599 736 600 - let kernel_file = downloader::download_kernel(arch)?; 737 + let kernel_file = match kernel_file { 738 + Some(k) => fs::canonicalize(k)?.to_str().unwrap().to_string(), 739 + None => downloader::download_kernel(arch)?, 740 + }; 741 + 601 742 let rockylinux_rootfs = format!("{}/rockylinux-rootfs", app_dir); 602 743 let squashfs_file = format!("{}/rockylinux-rootfs.squashfs", app_dir); 603 744 ··· 621 762 "ArchLinux" 622 763 } 623 764 624 - fn prepare(&self, arch: &str, app_dir: &str) -> Result<(String, String, String)> { 765 + fn prepare( 766 + &self, 767 + arch: &str, 768 + app_dir: &str, 769 + kernel_file: Option<String>, 770 + ) -> Result<(String, String, String)> { 625 771 println!( 626 772 "[+] Preparing {} rootfs for {}...", 627 773 self.name(), 628 774 arch.bright_green() 629 775 ); 630 776 631 - let kernel_file = downloader::download_kernel(arch)?; 777 + let kernel_file = match kernel_file { 778 + Some(k) => fs::canonicalize(k)?.to_str().unwrap().to_string(), 779 + None => downloader::download_kernel(arch)?, 780 + }; 632 781 let archlinux_rootfs = format!("{}/archlinux-rootfs", app_dir); 633 782 let squashfs_file = format!("{}/archlinux-rootfs.squashfs", app_dir); 634 783 ··· 663 812 "OpenSUSE (Tumbleweed)" 664 813 } 665 814 666 - fn prepare(&self, arch: &str, app_dir: &str) -> Result<(String, String, String)> { 815 + fn prepare( 816 + &self, 817 + arch: &str, 818 + app_dir: &str, 819 + kernel_file: Option<String>, 820 + ) -> Result<(String, String, String)> { 667 821 println!( 668 822 "[+] Preparing {} rootfs for {}...", 669 823 self.name(), 670 824 arch.bright_green() 671 825 ); 672 826 673 - let kernel_file = downloader::download_kernel(arch)?; 827 + let kernel_file = match kernel_file { 828 + Some(k) => fs::canonicalize(k)?.to_str().unwrap().to_string(), 829 + None => downloader::download_kernel(arch)?, 830 + }; 831 + 674 832 let opensuse_rootfs = format!("{}/opensuse-tumbleweed-rootfs", app_dir); 675 833 let squashfs_file = format!("{}/opensuse-tumbleweed-rootfs.squashfs", app_dir); 676 834 ··· 693 851 Ok((kernel_file, img_file, ssh_key_file)) 694 852 } 695 853 } 696 - 697 - pub fn prepare(distro: Distro) -> Result<()> { 698 - let arch = run_command("uname", &["-m"], false)?.stdout; 699 - let arch = String::from_utf8_lossy(&arch).trim().to_string(); 700 - println!("[+] Detected architecture: {}", arch.bright_green()); 701 - 702 - let app_dir = config::get_config_dir()?; 703 - let preparer: Box<dyn RootfsPreparer> = match distro { 704 - Distro::Debian => Box::new(DebianPreparer), 705 - Distro::Alpine => Box::new(AlpinePreparer), 706 - Distro::Ubuntu => Box::new(UbuntuPreparer), 707 - Distro::NixOS => Box::new(NixOSPreparer), 708 - Distro::Fedora => Box::new(FedoraPreparer), 709 - Distro::Gentoo => Box::new(GentooPreparer), 710 - Distro::Slackware => Box::new(SlackwarePreparer), 711 - Distro::Opensuse => Box::new(OpensusePreparer), 712 - Distro::OpensuseTumbleweed => Box::new(OpensuseTumbleweedPreparer), 713 - Distro::Almalinux => Box::new(AlmalinuxPreparer), 714 - Distro::RockyLinux => Box::new(RockyLinuxPreparer), 715 - Distro::Archlinux => Box::new(ArchlinuxPreparer), 716 - }; 717 - 718 - let (kernel_file, img_file, ssh_key_file) = preparer.prepare(&arch, &app_dir)?; 719 - 720 - println!("[✓] Kernel: {}", kernel_file.bright_green()); 721 - println!("[✓] Rootfs: {}", img_file.bright_green()); 722 - println!("[✓] SSH Key: {}", ssh_key_file.bright_green()); 723 - 724 - Ok(()) 725 - }
+63
crates/firecracker-prepare/src/scripts/extract-vmlinux.sh
··· 1 + #!/bin/sh 2 + # SPDX-License-Identifier: GPL-2.0-only 3 + # ---------------------------------------------------------------------- 4 + # extract-vmlinux - Extract uncompressed vmlinux from a kernel image 5 + # 6 + # Inspired from extract-ikconfig 7 + # (c) 2009,2010 Dick Streefland <dick@streefland.net> 8 + # 9 + # (c) 2011 Corentin Chary <corentin.chary@gmail.com> 10 + # 11 + # ---------------------------------------------------------------------- 12 + 13 + check_vmlinux() 14 + { 15 + if file "$1" | grep -q 'Linux kernel.*boot executable' || 16 + readelf -h "$1" > /dev/null 2>&1 17 + then 18 + cat "$1" 19 + exit 0 20 + fi 21 + } 22 + 23 + try_decompress() 24 + { 25 + # The obscure use of the "tr" filter is to work around older versions of 26 + # "grep" that report the byte offset of the line instead of the pattern. 27 + 28 + # Try to find the header ($1) and decompress from here 29 + for pos in `tr "$1\n$2" "\n$2=" < "$img" | grep -abo "^$2"` 30 + do 31 + pos=${pos%%:*} 32 + tail -c+$pos "$img" | $3 > $tmp 2> /dev/null 33 + check_vmlinux $tmp 34 + done 35 + } 36 + 37 + # Check invocation: 38 + me=${0##*/} 39 + img=$1 40 + if [ $# -ne 1 -o ! -s "$img" ] 41 + then 42 + echo "Usage: $me <kernel-image>" >&2 43 + exit 2 44 + fi 45 + 46 + # Prepare temp files: 47 + tmp=$(mktemp /tmp/vmlinux-XXX) 48 + trap "rm -f $tmp" 0 49 + 50 + # That didn't work, so retry after decompression. 51 + try_decompress '\037\213\010' xy gunzip 52 + try_decompress '\3757zXZ\000' abcde unxz 53 + try_decompress 'BZh' xy bunzip2 54 + try_decompress '\135\0\0\0' xxx unlzma 55 + try_decompress '\211\114\132' xy 'lzop -d' 56 + try_decompress '\002!L\030' xxx 'lz4 -d' 57 + try_decompress '(\265/\375' xxx unzstd 58 + 59 + # Finally check for uncompressed images or objects: 60 + check_vmlinux $img 61 + 62 + # Bail out: 63 + echo "$me: Cannot find vmlinux." >&2
+64
crates/firecracker-prepare/src/vmlinuz.rs
··· 1 + use anyhow::Error; 2 + use owo_colors::OwoColorize; 3 + use std::process::Command; 4 + 5 + use crate::command::run_command; 6 + 7 + pub fn extract_vmlinuz(vmlinuz_file: &str) -> Result<(), Error> { 8 + if !is_compressed(vmlinuz_file)? { 9 + return Ok(()); 10 + } 11 + 12 + println!("[*] Extracting vmlinux from compressed vmlinuz..."); 13 + 14 + const EXTRACT_VMLINUX: &str = include_str!("./scripts/extract-vmlinux.sh"); 15 + 16 + let home_dir = dirs::home_dir().unwrap(); 17 + let bin_dir = home_dir.join(".fireup").join("bin"); 18 + std::fs::create_dir_all(&bin_dir)?; 19 + let extract_vmlinux_path = bin_dir.join("extract-vmlinux"); 20 + std::fs::write(&extract_vmlinux_path, EXTRACT_VMLINUX)?; 21 + 22 + run_command( 23 + "chmod", 24 + &["+x", extract_vmlinux_path.to_str().unwrap()], 25 + false, 26 + )?; 27 + 28 + let output = std::path::Path::new(vmlinuz_file) 29 + .file_name() 30 + .unwrap() 31 + .to_str() 32 + .unwrap(); 33 + 34 + run_command( 35 + "bash", 36 + &[ 37 + "-c", 38 + &format!( 39 + "{} {} > /tmp/{}", 40 + extract_vmlinux_path.to_str().unwrap(), 41 + vmlinuz_file, 42 + output 43 + ), 44 + ], 45 + false, 46 + )?; 47 + 48 + std::fs::copy(format!("/tmp/{}", output), vmlinuz_file)?; 49 + std::fs::remove_file(format!("/tmp/{}", output))?; 50 + 51 + println!("[*] vmlinux extracted to {}", vmlinuz_file.cyan()); 52 + 53 + Ok(()) 54 + } 55 + 56 + fn is_compressed(vmlinuz_file: &str) -> Result<bool, Error> { 57 + // run command file on vmlinuz_file and check if output contains "bzImage" 58 + let output = Command::new("file").arg(vmlinuz_file).output()?; 59 + let output_str = String::from_utf8_lossy(&output.stdout); 60 + if output_str.contains("bzImage") { 61 + return Ok(true); 62 + } 63 + Ok(false) 64 + }
+1 -1
crates/firecracker-up/src/cmd/up.rs
··· 78 78 } 79 79 } 80 80 81 - firecracker_prepare::prepare(options.clone().into())?; 81 + firecracker_prepare::prepare(options.clone().into(), options.vmlinux.clone())?; 82 82 firecracker_vm::setup(&options, pid, vm_id).await?; 83 83 Ok(()) 84 84 }