1
0
Fork 0
forked from koniifer/ableos

Rewrote RepBuild

This commit is contained in:
Erin 2023-01-07 01:57:20 +01:00 committed by ondra05
parent 8d640b6a9b
commit b802732acf
15 changed files with 529 additions and 1438 deletions

7
.gitignore vendored
View file

@ -1,8 +1 @@
target/ target/
.gdb_history
!*/.gitkeep
__pycache__/
debug.log
/disk/
/limine/
/disk.img

5
.gitmodules vendored Normal file
View file

@ -0,0 +1,5 @@
[submodule "limine"]
path = limine
url = https://github.com/limine-bootloader/limine.git
branch = v4.x-branch-binary
shallow = true

976
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,4 +0,0 @@
# Base Root Filesystem
This is the base root filesystem for ableOS. It's used by tepbuild while
building the disk image.

Binary file not shown.

View file

@ -1,7 +0,0 @@
[logging]
enabled = true
level = "Trace"
log_to_serial = true
log_to_vterm = false
filter = ["ableos::ps2_mouse", "ableos::vterm", "ableos::devices::pci"]

Binary file not shown.

View file

@ -1,252 +0,0 @@
# Able doesn't have a full keyboard
0-NONE
1-
2-
3-BACKSPACE
4-
5-
6-
7-
8-
9-TAB
10-
11-
12-
13-ENTER
14-
15-
16-SHIFT
17-CONTROL
18-ALT
19-PAUSE
20-CAPS_LOCK
21-
22-
23-
24-
25-
26-
27-
28-
29-
30-
31-
32-SPACE
33-PAGE_UP
34-PAGE_DOWN
35-END
36-HOME
37-ARROW_LEFT
38-ARROW_UP
39-ARROW_RIGHT
40-ARROW_DOWN
41-
42-
43-
44-
45-INSERT
46-DELETE
47-
48-0
49-1
50-2
51-3
52-4
53-5
54-6
55-7
56-8
57-9
58-
59-SEMICOLON
60-
61-EQUAL
62-
63-
64-
65-a
66-b
67-c
68-d
69-e
70-f
71-g
72-h
73-i
74-j
75-k
76-l
77-m
78-n
79-o
80-p
81-q
82-r
83-s
84-t
85-u
86-v
87-w
88-x
89-y
90-z
91-
92-
93-
94-
95-
96-
97-
98-
99-
100-
101-
102-
103-
106-
107-
108-
109-
110-
111-
112-FUNCTION_1
113-FUNCTION_2
114-FUNCTION_3
115-FUNCTION_4
116-FUNCTION_5
117-FUNCTION_6
118-FUNCTION_7
119-FUNCTION_8
120-FUNCTION_9
121-FUNCTION_10
122-FUNCTION_11
123-FUNCTION_12
124-
125-
126-
127-
128-
129-
130-
131-
132-
134-
135-
136-
137-
138-
139-
140-
141-
142-
143-
145-SCROLL_LOCK
146-
147-
148-
149-
150-
151-
152-
153-
154-
155-
156-
157-
158-
159-
160-
161-
162-
163-
164-
165-
166-
167-
168-
169-
170-
171-
172-
173-MINUS
174-
175-
176-
177-
178-
179-
180-
181-
182-
183-
184-
185-
186-
187-
188-COMMA
189-
190-PERIOD
191-FORWARD_SLASH
192-GRAVE
193-
194-
195-
196-
197-
198-
199-
200-
201-
202-
203-
204-
205-
206-
207-
208-
209-
210-
211-
212-
213-
214-
215-
216-
218-
219-BRACKET_LEFT
220-BACK_SLASH
221-BRACKET_RIGHT
222-QUOTE
223-
224-
225-
226-
227-
228-
229-
230-
231-
232-
233-
234-
235-
236-
237-
238-
239-
240-
241-
242-
243-
244-
245-
246-
247-
248-
249-
250-
251-
252-
253-
254-
255-

View file

@ -1,14 +0,0 @@
boot/
├─ kernel.img
home/
├─ able/
│ ├─ bins/
│ ├─ config/
│ │ ├─ able_edit/
│ │ │ ├─ config.toml
│ ├─ irl_pic.png
│ ├─ password.txt
system/
├─ bins/
├─ configs/
│ ├─ kernel.toml

1
limine Submodule

@ -0,0 +1 @@
Subproject commit 83e6e5b4da7791ade3b367ceec8ce82c469d06db

View file

@ -1,11 +1,15 @@
[package] [package]
name = "repbuild" name = "repbuild"
version = "0.1.0" version = "0.2.0"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
colored = "2.0" env_logger = "0.10"
udisks = "0.1" error-stack = "0.2"
zbus = "2.3" fatfs = "0.3"
log = "0.4"
[dependencies.nix]
version = "0.26"
default-features = false
features = ["fs"]

View file

@ -1,4 +1,4 @@
${ABLEOS_KERNEL}=boot:///boot/kernel ${ABLEOS_KERNEL}=boot:///kernel
# TODO: Make a boot background image for ableOS # TODO: Make a boot background image for ableOS
# ${WALLPAPER_PATH}=boot:///boot/bg.bmp # ${WALLPAPER_PATH}=boot:///boot/bg.bmp
@ -18,5 +18,5 @@ TERM_BACKDROP=008080
# Setting a default resolution for the framebuffer # Setting a default resolution for the framebuffer
RESOLUTION=800x600x24 RESOLUTION=800x600x24
MODULE_PATH=boot:///boot/initrd.tar # MODULE_PATH=boot:///boot/initrd.tar
MODULE_CMDLINE=This is the first module. # MODULE_CMDLINE=This is the first module.

View file

@ -1,539 +1,170 @@
/* use error_stack::{bail, report, Context, IntoReport, Result, ResultExt};
* Copyright (c) 2022, Umut İnan Erdoğan <umutinanerdogan@pm.me> use fatfs::{FileSystem, FormatVolumeOptions, FsOptions, ReadWriteSeek};
* Copyright (c) 2022, able <abl3theabove@gmail.com> use nix::fcntl::FallocateFlags;
* use std::{fmt::Display, fs::File, io, os::fd::AsRawFd, path::Path, process::Command};
* SPDX-License-Identifier: MPL-2.0
*/
use colored::*; fn main() -> Result<(), Error> {
use std::{ env_logger::init();
fs::{self, File}, let mut args = std::env::args();
os::fd::AsRawFd, args.next();
process::Command,
};
use udisks::{
filesystem::{MountOptions, UnMountOptions},
manager::LoopSetupOptions,
};
struct Options { match args.next().as_deref() {
pub subcommand: Subcommand, Some("build" | "b") => build(
pub arguments: Vec<String>, args.next()
} .map(|x| x == "-r" || x == "--release")
.unwrap_or_default(),
enum Subcommand { )
BuildImage, .change_context(Error::Build),
Doc, Some("run" | "r") => {
Help, build(
Run, args.next()
Empty, .map(|x| x == "-r" || x == "--release")
/// Run all tests for all architectures .unwrap_or_default(),
Test, )?;
Unknown(String), run()
}
impl Subcommand {
fn from_str<S: AsRef<str>>(str: S) -> Subcommand {
match str.as_ref() {
"build-image" => Subcommand::BuildImage,
"doc" => Subcommand::Doc,
"help" => Subcommand::Help,
"run" | "r" => Subcommand::Run,
"test" | "t" => Subcommand::Test,
"" => Subcommand::Empty,
unknown => Subcommand::Unknown(unknown.to_string()),
} }
} Some("help" | "h") => {
} println!(
concat!(
enum MachineType { "AbleOS RepBuild\n",
X86_64, "Subcommands:\n",
RiscV64, " build (b): Build a bootable disk image\n",
AArch64, " help (h): Print this message\n",
Unknown(String), " run (r): Build and run AbleOS in QEMU\n\n",
} "Options for build and run:\n",
" -r: build in release mode",
fn main() -> Result<(), Box<dyn std::error::Error>> { ),
let options = options();
match options.subcommand {
Subcommand::BuildImage => {
let machine_text = options.arguments.get(0).cloned().unwrap_or_default();
match machine(machine_text) {
MachineType::X86_64 => {
// Cleanup
// NOTE: we are not unwrapping these, as we don't want this to fail if they
// don't exist yet, probably not the best idea tho.
// FIXME: figure out a better way to ignore errors about these not existing
#[allow(unused_must_use)]
{
fs::remove_dir_all("./limine");
fs::remove_dir_all("./disk");
fs::remove_file("./target/disk.img");
}
// Build ableOS in release mode
Command::new("cargo")
.args(["build", "--release"])
.current_dir(fs::canonicalize("./kernel").unwrap())
.status()
.unwrap();
// Create disk directory
fs::create_dir("./disk").unwrap();
// Clone limine 4.x binaries
Command::new("git")
.arg("clone")
.arg("https://github.com/limine-bootloader/limine.git")
.arg("--branch=v4.x-branch-binary")
.arg("--depth=1")
.status()
.unwrap();
println!("{}", "Building limine".bold());
Command::new("make")
.args(["-C", "limine"])
.status()
.unwrap();
println!("{}", "Allocating new disk image".bold());
Command::new("fallocate")
.args(["-l", "256M", "./target/disk.img"])
.status()
.unwrap();
println!("{}", "Partitioning disk image".bold());
let dbus_conn = zbus::blocking::Connection::system()?;
// Setup loop device
let disk_img = File::options()
.read(true)
.write(true)
.open("./target/disk.img")?;
let loopdev = udisks::manager::UDisks2ManagerProxyBlocking::new(&dbus_conn)?
.loop_setup(
disk_img.as_raw_fd().into(),
LoopSetupOptions {
no_user_interaction: true,
offset: 0,
size: 0,
readonly: false,
no_part_scan: false,
},
)?;
// Create MBR
udisks::block::BlockProxyBlocking::builder(&dbus_conn)
.path(&loopdev)?
.build()?
.format("dos", Default::default())?;
// Create and format partition
let filesystem =
udisks::partition::PartitionTableProxyBlocking::builder(&dbus_conn)
.destination("org.freedesktop.UDisks2")?
.path(&loopdev)?
.build()?
.create_partition_and_format(
0,
0,
"",
"",
Default::default(),
"ext2",
[("take-ownership", true.into())].into_iter().collect(),
)?;
let fsproxy = udisks::filesystem::FilesystemProxyBlocking::builder(&dbus_conn)
.path(&filesystem)?
.build()?;
// Mount the filesystem
let mountpoint = fsproxy
.mount(MountOptions {
no_user_interaction: true,
fs_type: String::new(),
mount_options: String::new(),
})
.or_else(|_| {
Ok::<String, zbus::Error>(loop {
if let Some(m) = fsproxy.mount_points()?.get(0) {
break m.to_string();
}
})
})?;
// copy ./base/* over to ./disk
Command::new("sh")
.arg("-c")
.arg(format!("cp -r ./base/* {mountpoint}"))
.status()?;
// copy ./limine/limine.sys over to ./disk/boot
Command::new("cp")
.args(["./limine/limine.sys", &format!("{mountpoint}/boot")])
.status()?;
// copy the kernel over to ./disk/boot/kernel
Command::new("cp")
.arg("./target/x86_64-ableos/release/kernel")
.arg(&format!("{mountpoint}/boot/kernel"))
.status()?;
// Unmount the filesystem (and the rest of things will follow)
fsproxy.unmount(UnMountOptions {
no_user_interaction: true,
force: false,
})?;
println!("{}", "Deploying limine".bold());
Command::new("./limine/limine-deploy")
.arg("./target/disk.img")
.status()
.unwrap();
}
MachineType::Unknown(unknown) => {
eprintln!(
"{}: unknown machine type `{}`",
"error".red().bold(),
unknown.bold(),
);
eprintln!("expected one of x86_64, riscv64 or aarch64");
}
_ => {
eprintln!(
"{}: build-image not implemented for this machine type",
"error".red().bold(),
);
}
}
}
Subcommand::Test => {
Command::new("cargo")
.args(["test", "--target=json_targets/x86_64-ableos.json"])
.current_dir(fs::canonicalize("./ableos").unwrap())
.status()
.unwrap();
// panic!("Test Infrastructure missing");
}
Subcommand::Doc => {
let machine_text = options.arguments.get(0).cloned().unwrap_or_default();
match machine(machine_text) {
MachineType::X86_64 => {
Command::new("cargo")
.args(["doc", "--open"])
.current_dir(fs::canonicalize("./ableos").unwrap())
.status()
.unwrap();
}
MachineType::RiscV64 => {
Command::new("cargo")
.args(["doc", "--open", "--target=riscv64gc-unknown-none-elf"])
.current_dir(fs::canonicalize("./ableos").unwrap())
.status()
.unwrap();
}
MachineType::AArch64 => {
Command::new("cargo")
.args(["doc", "--open", "--target=json_targets/aarch64-ableos.json"])
.current_dir(fs::canonicalize("./ableos").unwrap())
.status()
.unwrap();
}
MachineType::Unknown(unknown) => {
eprintln!(
"{}: unknown machine type `{}`",
"error".red().bold(),
unknown.bold(),
);
eprintln!("expected one of x86_64, riscv64 or aarch64");
}
}
}
Subcommand::Help => help(),
Subcommand::Run => {
let machine_text = options.arguments.get(0).cloned().unwrap_or_default();
let debug = options.arguments.get(1).cloned().unwrap_or_default();
let debug = matches!(debug.as_str(), "--debug" | "--dbg" | "-d");
match machine(machine_text) {
MachineType::X86_64 if debug => {
// Build ableOS
Command::new("cargo")
.arg("build")
.current_dir(fs::canonicalize("./kernel").unwrap())
.status()
.unwrap();
// Setup loopback device for disk.img, with partitions
// FIXME: don't do ths if running without changes
// Setup loop device
let disk_img = File::options()
.read(true)
.write(true)
.open("./target/disk.img")?;
let dbus_conn = zbus::blocking::Connection::system()?;
let loopdev = udisks::manager::UDisks2ManagerProxyBlocking::new(&dbus_conn)?
.loop_setup(
disk_img.as_raw_fd().into(),
LoopSetupOptions {
no_user_interaction: true,
offset: 0,
size: 0,
readonly: false,
no_part_scan: false,
},
)?;
let parts = udisks::partition::PartitionTableProxyBlocking::builder(&dbus_conn)
.destination("org.freedesktop.UDisks2")?
.path(loopdev)?
.build()?
.partitions()?;
let fsobjpath = parts.get(0).ok_or("missing boot partition")?;
let mountpoint =
udisks::filesystem::FilesystemProxyBlocking::builder(&dbus_conn)
.path(fsobjpath)?
.build()?
.mount(MountOptions {
no_user_interaction: true,
fs_type: String::new(),
mount_options: String::new(),
})?;
// copy the kernel over to ./disk/boot/kernel
Command::new("cp")
.arg("./target/x86_64-ableos/debug/kernel")
.arg(format!("{mountpoint}/boot/kernel"))
.status()
.unwrap();
udisks::filesystem::FilesystemProxyBlocking::builder(&dbus_conn)
.path(fsobjpath)?
.build()?
.unmount(UnMountOptions {
no_user_interaction: true,
force: false,
})?;
// run qemu with "-S", "-gdb", "tcp:9000"
Command::new("qemu-system-x86_64")
.args(["-device", "piix4-ide,id=ide"])
.arg("-drive")
.arg("file=./target/disk.img,format=raw,if=none,id=disk")
.args(["-device", "ide-hd,drive=disk,bus=ide.0"])
// .arg("--nodefaults")
.args(["-cpu", "Broadwell-v3"])
.args(["-m", "4G"])
.args(["-serial", "stdio"])
.args(["-smp", "cores=2"])
// .args(["-soundhw", "pcspk"])
// .args(["-device", "VGA"])
// .args(["-device", "virtio-gpu-pci"])
.args(["-device", "vmware-svga"])
.args(["-device", "sb16"])
// .args(["-machine", "pcspk-audiodev=0"])
// .args(["-qmp", "unix:../qmp-sock,server,nowait"])
.args(["-S", "-gdb", "tcp:9000"])
.status()
.unwrap();
}
MachineType::X86_64 => {
// Build ableOS
Command::new("cargo")
.args(["build", "--release"])
.current_dir(fs::canonicalize("./kernel").unwrap())
.status()
.unwrap();
// Setup loopback device for disk.img, with partitions
// FIXME: don't do ths if running without changes
let disk_img = File::options()
.read(true)
.write(true)
.open("./target/disk.img")?;
let dbus_conn = zbus::blocking::Connection::system()?;
let loopdev = udisks::manager::UDisks2ManagerProxyBlocking::new(&dbus_conn)?
.loop_setup(
disk_img.as_raw_fd().into(),
LoopSetupOptions {
no_user_interaction: true,
offset: 0,
size: 0,
readonly: false,
no_part_scan: false,
},
)?;
let parts = udisks::partition::PartitionTableProxyBlocking::builder(&dbus_conn)
.destination("org.freedesktop.UDisks2")?
.path(loopdev)?
.build()?
.partitions()?;
let fsproxy = udisks::filesystem::FilesystemProxyBlocking::builder(&dbus_conn)
.path(&parts[0])?
.build()?;
// Mount the filesystem
let mountpoint = fsproxy
.mount(MountOptions {
no_user_interaction: true,
fs_type: String::new(),
mount_options: String::new(),
})
.or_else(|_| {
Ok::<String, zbus::Error>(loop {
if let Some(m) = fsproxy.mount_points()?.get(0) {
break m.to_string();
}
})
})?;
// copy the kernel over to ./disk/boot/kernel
Command::new("cp")
.arg("./target/x86_64-ableos/release/kernel")
.arg(format!("{mountpoint}/boot/kernel"))
.status()
.unwrap();
fsproxy.unmount(UnMountOptions {
no_user_interaction: true,
force: false,
})?;
// run qemu
Command::new("qemu-system-x86_64")
.args(["-device", "piix4-ide,id=ide"])
.arg("-drive")
.arg("file=./target/disk.img,format=raw,if=none,id=disk")
.args(["-device", "ide-hd,drive=disk,bus=ide.0"])
// .arg("--nodefaults")
.args(["-cpu", "Broadwell-v3"])
.args(["-m", "4G"])
.args(["-serial", "stdio"])
.args(["-smp", "cores=2"])
// .args(["-soundhw", "pcspk"])
// .args(["-device", "VGA"])
// .args(["-device", "virtio-gpu-pci"])
.args(["-device", "vmware-svga"])
.args(["-device", "sb16"])
// .args(["-machine", "pcspk-audiodev=0"])
// .args(["-qmp", "unix:../qmp-sock,server,nowait"])
.status()
.unwrap();
}
MachineType::RiscV64 if debug => {
eprintln!(
"{}: debug is not implemented for riscv64",
"error".red().bold()
);
}
MachineType::RiscV64 => {
Command::new("cargo")
.args(["build", "--release", "--target=riscv64gc-unknown-none-elf"])
.current_dir(fs::canonicalize("./ableos").unwrap())
.status()
.unwrap();
Command::new("qemu-system-riscv64")
.args(["-machine", "virt"])
.args(["-cpu", "rv64"])
.args(["-smp", "8"])
.args(["-m", "128M"])
.arg("-bios")
.arg("src/arch/riscv/firmwear/opensbi-riscv64-generic-fw_jump.bin")
.arg("-kernel")
.arg("target/riscv64gc-unknown-none-elf/release/ableos")
.current_dir(fs::canonicalize("./ableos").unwrap())
.status()
.unwrap();
}
MachineType::AArch64 if debug => {
eprintln!(
"{}: debug is not implemented for aarch64",
"error".red().bold()
);
}
MachineType::AArch64 => {
Command::new("cargo")
.args([
"build",
"--release",
"--target=json_targets/aarch64-ableos.json",
])
.current_dir(fs::canonicalize("./ableos").unwrap())
.status()
.unwrap();
Command::new("qemu-system-aarch64")
.args(["-machine", "virt"])
.args(["-m", "1024M"])
.args(["-cpu", "cortex-a53"])
.args(["-kernel", "target/aarch64-ableos/release/ableos"])
.args(["-device", "virtio-keyboard"])
.current_dir(fs::canonicalize("./ableos").unwrap())
.status()
.unwrap();
}
MachineType::Unknown(unknown) => {
eprintln!(
"{}: unknown machine type `{}`",
"error".red().bold(),
unknown.bold(),
);
eprintln!("expected one of x86_64, riscv64 or aarch64");
}
}
}
Subcommand::Empty => {
eprintln!("{}: no subcommand passed", "error".red().bold());
help();
}
Subcommand::Unknown(unknown) => {
eprintln!(
"{}: unknown subcommand `{}`",
"error".red().bold(),
unknown.bold()
); );
help(); Ok(())
}
_ => Err(report!(Error::InvalidSubCom)),
}
}
fn get_fs() -> Result<FileSystem<impl ReadWriteSeek>, io::Error> {
let path = Path::new("target/disk.img");
match std::fs::metadata(path) {
Err(e) if e.kind() == io::ErrorKind::NotFound => (),
Err(e) => bail!(e),
Ok(_) => {
return FileSystem::new(
File::options().read(true).write(true).open(path)?,
FsOptions::new(),
)
.into_report()
} }
} }
Ok(()) let mut img = File::options()
.read(true)
.write(true)
.create(true)
.open(path)?;
{
#[cfg(unix)]
nix::fcntl::fallocate(
img.as_raw_fd(),
FallocateFlags::from_bits(0).unwrap(),
0,
1024 * 1024 * 64,
)
.map_err(|e| std::io::Error::from_raw_os_error(e as i32))?;
#[cfg(not(any(unix)))]
compile_error!("unsupported platform");
}
fatfs::format_volume(&mut img, FormatVolumeOptions::new())?;
let fs = FileSystem::new(img, FsOptions::new())?;
let bootdir = fs.root_dir().create_dir("efi")?.create_dir("boot")?;
io::copy(
&mut File::open("limine/BOOTX64.EFI")?,
&mut bootdir.create_file("bootx64.efi")?,
)?;
io::copy(
&mut File::open(Path::new("repbuild/limine.cfg"))?,
&mut fs.root_dir().create_file("limine.cfg")?,
)?;
drop(bootdir);
Ok(fs)
} }
fn options() -> Options { fn build(release: bool) -> Result<(), Error> {
let subcommand = std::env::args().nth(1).unwrap_or_default(); let fs = get_fs().change_context(Error::Io)?;
let arguments = std::env::args().skip(2).collect(); let mut com = Command::new("cargo");
com.current_dir("kernel");
com.args(["b"]);
if release {
com.arg("-r");
}
Options { com.status().into_report().change_context(Error::Build)?;
subcommand: Subcommand::from_str(subcommand),
arguments, (|| -> std::io::Result<_> {
io::copy(
&mut File::open(
Path::new("target/x86_64-ableos")
.join(if release { "release" } else { "debug" })
.join("kernel"),
)?,
&mut fs.root_dir().create_file("kernel")?,
)
.map(|_| ())
})()
.into_report()
.change_context(Error::Io)
}
fn run() -> Result<(), Error> {
#[rustfmt::skip]
let args = [
"-bios", "/usr/share/OVMF/OVMF_CODE.fd",
"-enable-kvm",
"-cpu", "host",
"-drive", "file=target/disk.img,format=raw",
"-m", "4G",
"-serial", "stdio",
"-smp", "cores=2",
];
match Command::new("qemu-system-x86_64")
.args(args)
.status()
.into_report()
.change_context(Error::ProcessSpawn)?
{
s if s.success() => Ok(()),
s => Err(report!(Error::Qemu(s.code()))),
} }
} }
fn machine<S: AsRef<str>>(text: S) -> MachineType { #[derive(Debug)]
match text.as_ref() { enum Error {
"x86" | "x86_64" => MachineType::X86_64, Build,
"riscv" | "riscv64" => MachineType::RiscV64, InvalidSubCom,
"arm" | "arm64" | "aarch64" => MachineType::AArch64, Io,
"" => { ProcessSpawn,
eprintln!( Qemu(Option<i32>),
"{}: no machine type passed, defaulting to x86_64", }
"warning".yellow().bold()
); impl Context for Error {}
MachineType::X86_64 impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Build => f.write_str("failed to build the kernel"),
Self::InvalidSubCom => {
f.write_str("missing or invalid subcommand (available: build, run)")
}
Self::Io => f.write_str("IO error"),
Self::ProcessSpawn => f.write_str("failed to spawn a process"),
Self::Qemu(Some(c)) => write!(f, "QEMU Error: {c}"),
Self::Qemu(None) => write!(f, "QEMU Error: interrupted by signal"),
} }
unknown => MachineType::Unknown(unknown.to_string()),
} }
} }
fn help() {
todo!("`help`")
}