libc: properly set up main thread
This commit is contained in:
parent
dea1b3ecf2
commit
03f6362756
1
test.c
1
test.c
@ -1,6 +1,7 @@
|
|||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <sys/yggdrasil.h>
|
||||||
|
|
||||||
_Thread_local int x = 123;
|
_Thread_local int x = 123;
|
||||||
|
|
||||||
|
@ -193,9 +193,6 @@ fn main() {
|
|||||||
.map(|d| d.path().as_path().join("cbindgen.toml"))
|
.map(|d| d.path().as_path().join("cbindgen.toml"))
|
||||||
.filter(|p| p.exists())
|
.filter(|p| p.exists())
|
||||||
.for_each(|p| {
|
.for_each(|p| {
|
||||||
println!("cargo:rerun-if-changed={:?}", p.parent().unwrap());
|
|
||||||
println!("cargo:rerun-if-changed={:?}", p);
|
|
||||||
println!("cargo:rerun-if-changed={:?}", p.with_file_name("mod.rs"));
|
|
||||||
generate_header(&p, &header_output);
|
generate_header(&p, &header_output);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -164,7 +164,6 @@ unsafe extern "C" fn creat(pathname: *const c_char, mode: mode_t) -> CFdResult {
|
|||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
unsafe extern "C" fn open(pathname: *const c_char, opts: c_int, mut args: ...) -> CFdResult {
|
unsafe extern "C" fn open(pathname: *const c_char, opts: c_int, mut args: ...) -> CFdResult {
|
||||||
yggdrasil_rt::debug_trace!("&errno = {:p}", &crate::error::errno);
|
|
||||||
vopenat(AT_FDCWD, pathname, opts, args.as_va_list())
|
vopenat(AT_FDCWD, pathname, opts, args.as_va_list())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,6 +122,8 @@ pub mod utime;
|
|||||||
pub mod wchar;
|
pub mod wchar;
|
||||||
pub mod wctype;
|
pub mod wctype;
|
||||||
|
|
||||||
|
pub mod pthread;
|
||||||
|
|
||||||
pub mod sys_mman;
|
pub mod sys_mman;
|
||||||
pub mod sys_resource;
|
pub mod sys_resource;
|
||||||
pub mod sys_select;
|
pub mod sys_select;
|
||||||
@ -135,4 +137,5 @@ pub mod sys_wait;
|
|||||||
|
|
||||||
// TODO Generate those as part of dyn-loader (and make dyn-loader a shared library)
|
// TODO Generate those as part of dyn-loader (and make dyn-loader a shared library)
|
||||||
pub mod link;
|
pub mod link;
|
||||||
pub mod pthread;
|
|
||||||
|
pub mod sys_yggdrasil;
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
language = "C"
|
||||||
|
style = "Type"
|
||||||
|
|
||||||
|
sys_includes = [
|
||||||
|
"stddef.h",
|
||||||
|
]
|
||||||
|
no_includes = true
|
||||||
|
|
||||||
|
include_guard = "_SYS_YGGDRASIL_H"
|
||||||
|
|
||||||
|
usize_type = "size_t"
|
||||||
|
isize_type = "ssize_t"
|
14
userspace/lib/ygglibc/src/headers/sys_yggdrasil/mod.rs
Normal file
14
userspace/lib/ygglibc/src/headers/sys_yggdrasil/mod.rs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
use core::ffi::{c_char, CStr};
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
unsafe extern "C" fn ygg_panic(message: *const c_char) -> ! {
|
||||||
|
let message = if message.is_null() {
|
||||||
|
"<null>"
|
||||||
|
} else if let Ok(str) = CStr::from_ptr(message).to_str() {
|
||||||
|
str
|
||||||
|
} else {
|
||||||
|
"<invalid str>"
|
||||||
|
};
|
||||||
|
|
||||||
|
panic!("Explicit panic: {message}")
|
||||||
|
}
|
@ -11,7 +11,8 @@
|
|||||||
rustc_private,
|
rustc_private,
|
||||||
naked_functions,
|
naked_functions,
|
||||||
non_null_from_ref,
|
non_null_from_ref,
|
||||||
thread_local
|
thread_local,
|
||||||
|
let_chains
|
||||||
)]
|
)]
|
||||||
#![allow(internal_features)]
|
#![allow(internal_features)]
|
||||||
#![cfg_attr(not(test), no_std)]
|
#![cfg_attr(not(test), no_std)]
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
use core::{fmt, sync::atomic::AtomicUsize};
|
use core::{fmt, sync::atomic::AtomicUsize};
|
||||||
|
|
||||||
use crate::io::{managed::{stderr, FILE}, Write};
|
use crate::io::{
|
||||||
|
managed::{stderr, FILE},
|
||||||
|
Write,
|
||||||
|
};
|
||||||
|
|
||||||
struct PanicPrinter {
|
struct PanicPrinter {
|
||||||
buffer: [u8; 1024],
|
buffer: [u8; 1024],
|
||||||
pos: usize,
|
pos: usize,
|
||||||
|
|
||||||
print: Option<&'static mut FILE>
|
print: Option<&'static mut FILE>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Write for PanicPrinter {
|
impl fmt::Write for PanicPrinter {
|
||||||
@ -59,6 +62,7 @@ impl PanicPrinter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[thread_local]
|
||||||
static PANIC_COUNT: AtomicUsize = AtomicUsize::new(0);
|
static PANIC_COUNT: AtomicUsize = AtomicUsize::new(0);
|
||||||
|
|
||||||
#[cfg(any(not(test), rust_analyzer))]
|
#[cfg(any(not(test), rust_analyzer))]
|
||||||
@ -66,11 +70,21 @@ static PANIC_COUNT: AtomicUsize = AtomicUsize::new(0);
|
|||||||
fn panic_handler(pi: &core::panic::PanicInfo) -> ! {
|
fn panic_handler(pi: &core::panic::PanicInfo) -> ! {
|
||||||
use core::{fmt::Write, sync::atomic::Ordering};
|
use core::{fmt::Write, sync::atomic::Ordering};
|
||||||
|
|
||||||
|
use crate::{error::EResult, thread::Thread};
|
||||||
|
|
||||||
match PANIC_COUNT.fetch_add(1, Ordering::Relaxed) {
|
match PANIC_COUNT.fetch_add(1, Ordering::Relaxed) {
|
||||||
0 => {
|
0 => {
|
||||||
|
let pthread_self = Thread::this();
|
||||||
let mut printer = PanicPrinter::new();
|
let mut printer = PanicPrinter::new();
|
||||||
|
|
||||||
writeln!(printer, "!!! ygglibc panic !!!").ok();
|
writeln!(printer, "!!! ygglibc panic !!!").ok();
|
||||||
|
if let EResult::Ok(this) = pthread_self {
|
||||||
|
if this.is_main() {
|
||||||
|
writeln!(printer, "* Main thread panicked *").ok();
|
||||||
|
} else {
|
||||||
|
writeln!(printer, "* Thread {} panicked*", this.id()).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Some(location) = pi.location() {
|
if let Some(location) = pi.location() {
|
||||||
writeln!(printer, "{}:{}:", location.file(), location.line()).ok();
|
writeln!(printer, "{}:{}:", location.file(), location.line()).ok();
|
||||||
}
|
}
|
||||||
@ -84,6 +98,5 @@ fn panic_handler(pi: &core::panic::PanicInfo) -> ! {
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kill myself
|
Thread::abort_current()
|
||||||
super::process::abort();
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use core::{
|
use core::{
|
||||||
|
cell::OnceCell,
|
||||||
ffi::c_void,
|
ffi::c_void,
|
||||||
mem,
|
mem,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
@ -13,9 +14,10 @@ use yggdrasil_rt::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::EResult,
|
error::{EResult, OptionExt}, headers::{
|
||||||
headers::sys_types::{pthread_attr_t, pthread_t},
|
errno::{self, Errno},
|
||||||
sync::Mutex,
|
sys_types::{pthread_attr_t, pthread_t},
|
||||||
|
}, process, sync::Mutex
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod tls;
|
pub mod tls;
|
||||||
@ -23,7 +25,7 @@ pub mod tls;
|
|||||||
static THREADS: Mutex<BTreeMap<pthread_t, Arc<Thread>>> = Mutex::new(BTreeMap::new());
|
static THREADS: Mutex<BTreeMap<pthread_t, Arc<Thread>>> = Mutex::new(BTreeMap::new());
|
||||||
|
|
||||||
#[thread_local]
|
#[thread_local]
|
||||||
static mut SELF: Option<Arc<Thread>> = None;
|
static SELF: OnceCell<Arc<Thread>> = OnceCell::new();
|
||||||
|
|
||||||
enum ThreadStack {
|
enum ThreadStack {
|
||||||
Owned(usize, usize),
|
Owned(usize, usize),
|
||||||
@ -44,6 +46,18 @@ pub struct Thread {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Thread {
|
impl Thread {
|
||||||
|
fn main() -> Self {
|
||||||
|
Self {
|
||||||
|
id: AtomicU32::new(0),
|
||||||
|
stack: ThreadStack::Borrowed,
|
||||||
|
result: AtomicPtr::new(null_mut())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_main(&self) -> bool {
|
||||||
|
self.id.load(Ordering::Acquire) == 0
|
||||||
|
}
|
||||||
|
|
||||||
pub fn spawn(
|
pub fn spawn(
|
||||||
attr: Option<&pthread_attr_t>,
|
attr: Option<&pthread_attr_t>,
|
||||||
entry: extern "C" fn(*mut c_void) -> *mut c_void,
|
entry: extern "C" fn(*mut c_void) -> *mut c_void,
|
||||||
@ -117,28 +131,38 @@ impl Thread {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn this() -> EResult<Arc<Thread>> {
|
pub fn this() -> EResult<Arc<Thread>> {
|
||||||
let ptr = unsafe { SELF.clone() }.expect("pthread_self() is NULL");
|
SELF.get().cloned().e_ok_or(errno::EINVAL)
|
||||||
EResult::Ok(ptr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn set_this(thread: Arc<Self>) {
|
unsafe fn set_this(thread: Arc<Self>) {
|
||||||
unsafe { SELF = Some(thread) };
|
if SELF.set(thread).is_err() {
|
||||||
|
unreachable!("pthread_self already initialized?")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn clear_this() {
|
unsafe fn clear_this() {
|
||||||
unsafe { SELF = None };
|
let this = SELF.get().expect("pthread_self == NULL");
|
||||||
|
Arc::decrement_strong_count(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn result(&self) -> *mut c_void {
|
pub fn result(&self) -> *mut c_void {
|
||||||
self.result.load(Ordering::Acquire)
|
self.result.load(Ordering::Acquire)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn abort_current() -> ! {
|
||||||
|
let this = Self::this();
|
||||||
|
if let EResult::Ok(this) = this && !this.is_main() {
|
||||||
|
this.result.store(null_mut(), Ordering::Release);
|
||||||
|
unsafe { yggdrasil_rt::sys::exit_thread() };
|
||||||
|
} else {
|
||||||
|
process::abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extern "C" fn thread_entry(argument: usize) -> ! {
|
extern "C" fn thread_entry(argument: usize) -> ! {
|
||||||
// Set up TLS as soon as possible. Note the `force = true` parameter, because the image
|
// Set up TLS as soon as possible. Note the `force = true` parameter, because the image
|
||||||
// contains "already initialized" tag, which only matters for the main thread.
|
// contains "already initialized" tag, which only matters for the main thread.
|
||||||
if let Err(err) =
|
if let Err(err) = unsafe { thread_local::init_tls(tls::TLS_IMAGE.as_ref(), true) } {
|
||||||
unsafe { thread_local::init_tls(tls::TLS_IMAGE.as_ref(), true) }
|
|
||||||
{
|
|
||||||
yggdrasil_rt::debug_trace!("thread_entry failed: TLS init error: {err:?}");
|
yggdrasil_rt::debug_trace!("thread_entry failed: TLS init error: {err:?}");
|
||||||
unsafe { yggdrasil_rt::sys::exit_thread() };
|
unsafe { yggdrasil_rt::sys::exit_thread() };
|
||||||
}
|
}
|
||||||
@ -187,11 +211,17 @@ pub fn init_main_thread(arg: &ProgramArgumentInner) {
|
|||||||
// Usually, a dynamic loader will do this for us, but this still needs to be
|
// Usually, a dynamic loader will do this for us, but this still needs to be
|
||||||
// done for statically-linked programs if the kernel transfers control directly to
|
// done for statically-linked programs if the kernel transfers control directly to
|
||||||
// the program.
|
// the program.
|
||||||
let tls_image = thread_local::init_tls_from_auxv(arg.auxv(), false)
|
let tls_image =
|
||||||
.expect("Could not initialize TLS");
|
thread_local::init_tls_from_auxv(arg.auxv(), false).expect("Could not initialize TLS");
|
||||||
|
|
||||||
// Store the TLS image, it'll be needed when creating new threads
|
// Store the TLS image, it'll be needed when creating new threads
|
||||||
unsafe {
|
unsafe {
|
||||||
tls::TLS_IMAGE = tls_image;
|
tls::TLS_IMAGE = tls_image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO set thread ID for main
|
||||||
|
let main = Arc::new(Thread::main());
|
||||||
|
unsafe {
|
||||||
|
Thread::set_this(main);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,6 @@ fn build_test_cpp_program(
|
|||||||
let mut command = llvm.c_clangpp(env);
|
let mut command = llvm.c_clangpp(env);
|
||||||
command
|
command
|
||||||
.args([
|
.args([
|
||||||
"-v",
|
|
||||||
"-fpie",
|
"-fpie",
|
||||||
"-Bdynamic",
|
"-Bdynamic",
|
||||||
"-O0",
|
"-O0",
|
||||||
@ -93,14 +92,7 @@ fn build_test_c_program(
|
|||||||
log::info!("Building a test C program [static]");
|
log::info!("Building a test C program [static]");
|
||||||
let mut command = llvm.c_clang(env);
|
let mut command = llvm.c_clang(env);
|
||||||
command
|
command
|
||||||
.args([
|
.args(["-static", "-O0", "-ggdb", "-fstack-protector-strong", "-lm"])
|
||||||
"-v",
|
|
||||||
"-static",
|
|
||||||
"-O0",
|
|
||||||
"-ggdb",
|
|
||||||
"-fstack-protector-strong",
|
|
||||||
"-lm",
|
|
||||||
])
|
|
||||||
.arg("-o")
|
.arg("-o")
|
||||||
.arg(target_dir.join("c-test-static"))
|
.arg(target_dir.join("c-test-static"))
|
||||||
.arg(env.workspace_root.join("test.c"));
|
.arg(env.workspace_root.join("test.c"));
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
use std::{fs, path::PathBuf, process::Command};
|
use std::{
|
||||||
|
fs,
|
||||||
|
path::PathBuf,
|
||||||
|
process::{Command, Stdio},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{env::BuildEnv, error::Error};
|
use crate::{
|
||||||
|
env::BuildEnv,
|
||||||
|
error::{CommandExt, Error},
|
||||||
|
};
|
||||||
|
|
||||||
pub struct Llvm {
|
pub struct Llvm {
|
||||||
root: PathBuf,
|
root: PathBuf,
|
||||||
@ -76,23 +83,22 @@ pub fn install_compiler_rt(env: &BuildEnv, llvm: &Llvm) -> Result<(), Error> {
|
|||||||
.arg("-GNinja")
|
.arg("-GNinja")
|
||||||
.arg("../../compiler-rt");
|
.arg("../../compiler-rt");
|
||||||
|
|
||||||
if !command.status()?.success() {
|
command.success().map_err(Error::CompilerRtConfigFailed)?;
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build and install compiler-rt
|
// Build and install compiler-rt
|
||||||
log::info!("Build compiler-rt for {}", env.arch.name());
|
log::info!("Build compiler-rt for {}", env.arch.name());
|
||||||
let mut command = Command::new("cmake");
|
let mut command = Command::new("cmake");
|
||||||
|
if !env.verbose {
|
||||||
|
command.stdout(Stdio::null());
|
||||||
|
}
|
||||||
command.current_dir(&build_dir);
|
command.current_dir(&build_dir);
|
||||||
command
|
command
|
||||||
.args(["--build", "."])
|
.args(["--build", "."])
|
||||||
.args(["-t install"])
|
.args(["-t install"])
|
||||||
.arg("-j");
|
.arg("-j");
|
||||||
|
|
||||||
if !command.status()?.success() {
|
command.success().map_err(Error::CompilerRtBuildFailed)?;
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -149,9 +155,7 @@ pub fn install_llvm_cxx_runtime(env: &BuildEnv, llvm: &Llvm) -> Result<(), Error
|
|||||||
))
|
))
|
||||||
.arg("../../runtimes");
|
.arg("../../runtimes");
|
||||||
|
|
||||||
if !command.status()?.success() {
|
command.success().map_err(Error::CRuntimeConfigFailed)?;
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build and install libc++/abi
|
// Build and install libc++/abi
|
||||||
@ -160,15 +164,16 @@ pub fn install_llvm_cxx_runtime(env: &BuildEnv, llvm: &Llvm) -> Result<(), Error
|
|||||||
// present in the sysroot
|
// present in the sysroot
|
||||||
log::info!("Build libc++/libc++abi for {}", env.arch.name());
|
log::info!("Build libc++/libc++abi for {}", env.arch.name());
|
||||||
let mut command = Command::new("cmake");
|
let mut command = Command::new("cmake");
|
||||||
|
if !env.verbose {
|
||||||
|
command.stdout(Stdio::null());
|
||||||
|
}
|
||||||
command.current_dir(&build_dir);
|
command.current_dir(&build_dir);
|
||||||
command
|
command
|
||||||
.args(["--build", "."])
|
.args(["--build", "."])
|
||||||
.args(["-t install"])
|
.args(["-t install"])
|
||||||
.arg("-j");
|
.arg("-j");
|
||||||
|
|
||||||
if !command.status()?.success() {
|
command.success().map_err(Error::CRuntimeBuildFailed)?;
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -203,23 +208,22 @@ pub fn install_llvm_compiler(env: &BuildEnv) -> Result<Llvm, Error> {
|
|||||||
.arg("-GNinja")
|
.arg("-GNinja")
|
||||||
.arg("../llvm");
|
.arg("../llvm");
|
||||||
|
|
||||||
if !command.status()?.success() {
|
command.success().map_err(Error::LlvmConfigFailed)?;
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build and install LLVM
|
// Build and install LLVM
|
||||||
log::info!("Build LLVM");
|
log::info!("Build LLVM");
|
||||||
let mut command = Command::new("cmake");
|
let mut command = Command::new("cmake");
|
||||||
|
if !env.verbose {
|
||||||
|
command.stdout(Stdio::null());
|
||||||
|
}
|
||||||
command.current_dir(&build_dir);
|
command.current_dir(&build_dir);
|
||||||
command
|
command
|
||||||
.args(["--build", "."])
|
.args(["--build", "."])
|
||||||
.args(["-t install"])
|
.args(["-t install"])
|
||||||
.arg("-j");
|
.arg("-j");
|
||||||
|
|
||||||
if !command.status()?.success() {
|
command.success().map_err(Error::LlvmBuildFailed)?;
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Llvm { root })
|
Ok(Llvm { root })
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
use std::{io, path::PathBuf};
|
use std::{
|
||||||
|
io,
|
||||||
|
path::PathBuf,
|
||||||
|
process::{Command, ExitStatusError},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
@ -30,4 +34,36 @@ pub enum Error {
|
|||||||
InvalidPath(PathBuf),
|
InvalidPath(PathBuf),
|
||||||
#[error("Could not resolve modules")]
|
#[error("Could not resolve modules")]
|
||||||
UnresolvedModuleDependencies,
|
UnresolvedModuleDependencies,
|
||||||
|
|
||||||
|
#[error("LLVM config failed: {0}")]
|
||||||
|
LlvmConfigFailed(CommandFailed),
|
||||||
|
#[error("LLVM build failed: {0}")]
|
||||||
|
LlvmBuildFailed(CommandFailed),
|
||||||
|
#[error("C/C++ runtime config failed: {0}")]
|
||||||
|
CRuntimeConfigFailed(CommandFailed),
|
||||||
|
#[error("C/C++ runtime build failed: {0}")]
|
||||||
|
CRuntimeBuildFailed(CommandFailed),
|
||||||
|
#[error("compiler-rt config failed: {0}")]
|
||||||
|
CompilerRtConfigFailed(CommandFailed),
|
||||||
|
#[error("compiler-rt build failed: {0}")]
|
||||||
|
CompilerRtBuildFailed(CommandFailed),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum CommandFailed {
|
||||||
|
#[error("{0}")]
|
||||||
|
Io(#[from] io::Error),
|
||||||
|
#[error("exit status {0:?}")]
|
||||||
|
ExitCode(#[from] ExitStatusError),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait CommandExt {
|
||||||
|
fn success(self) -> Result<(), CommandFailed>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommandExt for Command {
|
||||||
|
fn success(mut self) -> Result<(), CommandFailed> {
|
||||||
|
self.status()?.exit_ok()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#![deny(warnings)]
|
#![deny(warnings)]
|
||||||
|
#![feature(exit_status_error)]
|
||||||
|
|
||||||
use std::{fs, path::PathBuf, process::ExitCode};
|
use std::{fs, path::PathBuf, process::ExitCode};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user