Serve the initfs scheme here
This commit is contained in:
Generated
+9
@@ -29,6 +29,7 @@ dependencies = [
|
||||
"libc",
|
||||
"linked_list_allocator",
|
||||
"memoffset",
|
||||
"redox-initfs",
|
||||
"redox_syscall",
|
||||
"scroll",
|
||||
]
|
||||
@@ -83,6 +84,14 @@ version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
|
||||
|
||||
[[package]]
|
||||
name = "redox-initfs"
|
||||
version = "0.1.0"
|
||||
source = "git+https://gitlab.redox-os.org/redox-os/redox-initfs.git#89b8fb8984cf96c418880b7dcd9ce3d6afc3f71c"
|
||||
dependencies = [
|
||||
"plain",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.13"
|
||||
|
||||
@@ -16,6 +16,7 @@ libc = "0.2"
|
||||
memoffset = "0.6"
|
||||
scroll = { version = "0.10", default-features = false }
|
||||
linked_list_allocator = "0.9"
|
||||
redox-initfs = { git = "https://gitlab.redox-os.org/redox-os/redox-initfs.git", features = ["kernel"], default-features = false }
|
||||
redox_syscall = { git = "https://gitlab.redox-os.org/4lDO2/syscall.git", branch = "userspace_fexec" }
|
||||
|
||||
[profile.release]
|
||||
|
||||
+59
-1
@@ -1,5 +1,7 @@
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn main(_: libc::c_int, _: *const *const libc::c_char) -> libc::c_int {
|
||||
let (initfs_offset, initfs_length);
|
||||
|
||||
let envp = {
|
||||
let len = 4096;
|
||||
let buf = libc::calloc(len, 1);
|
||||
@@ -20,7 +22,27 @@ pub unsafe extern "C" fn main(_: libc::c_int, _: *const *const libc::c_char) ->
|
||||
*c = b'\0';
|
||||
}
|
||||
}
|
||||
let iter = || env.split(|c| *c == b'\0').filter(|var| !var.is_empty());
|
||||
let raw_iter = || env.split(|c| *c == b'\0').filter(|var| !var.is_empty());
|
||||
|
||||
let mut initfs_offset_opt = None;
|
||||
let mut initfs_length_opt = None;
|
||||
|
||||
for var in raw_iter() {
|
||||
let equal_sign = var.iter().position(|c| *c == b'=').expect("malformed environment variable");
|
||||
let name = &var[..equal_sign];
|
||||
let value = &var[equal_sign + 1..];
|
||||
|
||||
match name {
|
||||
b"INITFS_OFFSET" => initfs_offset_opt = core::str::from_utf8(value).ok().and_then(|s| usize::from_str_radix(s, 16).ok()),
|
||||
b"INITFS_LENGTH" => initfs_length_opt = core::str::from_utf8(value).ok().and_then(|s| usize::from_str_radix(s, 16).ok()),
|
||||
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
initfs_offset = initfs_offset_opt.expect("missing INITFS_OFFSET");
|
||||
initfs_length = initfs_length_opt.expect("missing INITFS_LENGTH");
|
||||
|
||||
let iter = || raw_iter().filter(|var| !var.starts_with(b"INITFS_"));
|
||||
let env_count = iter().count();
|
||||
|
||||
let envp = libc::calloc(env_count + 1, core::mem::size_of::<*const u8>()) as *mut *const libc::c_char;
|
||||
@@ -32,12 +54,48 @@ pub unsafe extern "C" fn main(_: libc::c_int, _: *const *const libc::c_char) ->
|
||||
|
||||
envp
|
||||
};
|
||||
{
|
||||
use syscall::flag::MapFlags;
|
||||
// XXX: It may be a little unsafe to mprotect this after relibc has started, but since only
|
||||
// the bootloader can influence the data we use, it should be fine security-wise.
|
||||
let _ = syscall::mprotect(initfs_offset, initfs_length, MapFlags::PROT_READ | MapFlags::MAP_PRIVATE).expect("mprotect failed for initfs");
|
||||
|
||||
spawn_initfs(initfs_offset, initfs_length);
|
||||
}
|
||||
|
||||
let name_ptr = b"initfs:bin/init\0".as_ptr() as *const libc::c_char;
|
||||
let argv = [name_ptr, core::ptr::null()];
|
||||
|
||||
if libc::execve(name_ptr, argv.as_ptr(), envp) == -1 {
|
||||
libc::perror(b"Failed to execute init\0".as_ptr() as *const libc::c_char);
|
||||
panic!();
|
||||
}
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
unsafe fn spawn_initfs(initfs_start: usize, initfs_length: usize) {
|
||||
let mut buf = [0; 2];
|
||||
syscall::pipe2(&mut buf, syscall::O_CLOEXEC).expect("failed to open sync pipe");
|
||||
let [read, write] = buf;
|
||||
|
||||
match libc::fork() {
|
||||
-1 => {
|
||||
libc::perror(b"Failed to fork in order to start initfs daemon\0".as_ptr() as *const libc::c_char);
|
||||
panic!();
|
||||
}
|
||||
// Continue serving the scheme as the child.
|
||||
0 => {
|
||||
let _ = syscall::close(read);
|
||||
}
|
||||
// Return in order to execute init, as the parent.
|
||||
_ => {
|
||||
let _ = syscall::close(write);
|
||||
let _ = syscall::read(read, &mut [0]);
|
||||
|
||||
let _ = syscall::chdir("initfs:");
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
crate::initfs::run(core::slice::from_raw_parts(initfs_start as *const u8, initfs_length), write);
|
||||
}
|
||||
|
||||
+279
@@ -0,0 +1,279 @@
|
||||
use core::convert::TryFrom;
|
||||
use core::str;
|
||||
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::string::String;
|
||||
|
||||
use redox_initfs::{InitFs, InodeStruct, Inode, InodeDir, InodeKind, types::Timespec};
|
||||
|
||||
use syscall::data::{Packet, Stat};
|
||||
use syscall::error::*;
|
||||
use syscall::flag::*;
|
||||
use syscall::scheme::{calc_seek_offset_usize, SchemeMut};
|
||||
|
||||
struct Handle {
|
||||
inode: Inode,
|
||||
seek: usize,
|
||||
// TODO: Any better way to implement fpath? Or maybe work around it, e.g. by giving paths such
|
||||
// as `initfs:__inodes__/<inode>`?
|
||||
filename: String,
|
||||
}
|
||||
pub struct InitFsScheme {
|
||||
handles: BTreeMap<usize, Handle>,
|
||||
next_id: usize,
|
||||
fs: InitFs<'static>,
|
||||
}
|
||||
impl InitFsScheme {
|
||||
pub fn new(bytes: &'static [u8]) -> Self {
|
||||
Self {
|
||||
handles: BTreeMap::new(),
|
||||
next_id: 0,
|
||||
fs: InitFs::new(bytes).expect("failed to parse initfs"),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_inode(fs: &InitFs<'static>, inode: Inode) -> Result<InodeStruct<'static>> {
|
||||
fs.get_inode(inode).ok_or_else(|| Error::new(EIO))
|
||||
}
|
||||
fn next_id(&mut self) -> usize {
|
||||
assert_ne!(self.next_id, usize::MAX, "usize overflow in initfs scheme");
|
||||
self.next_id += 1;
|
||||
self.next_id
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct Iter {
|
||||
dir: InodeDir<'static>,
|
||||
idx: u32,
|
||||
}
|
||||
impl Iterator for Iter {
|
||||
type Item = Result<redox_initfs::Entry<'static>>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let entry = self.dir.get_entry(self.idx).map_err(|_| Error::new(EIO));
|
||||
self.idx += 1;
|
||||
entry.transpose()
|
||||
}
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
match self.dir.entry_count().ok() {
|
||||
Some(size) => {
|
||||
let size = usize::try_from(size).expect("expected u32 to be convertible into usize");
|
||||
(size, Some(size))
|
||||
}
|
||||
None => (0, None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn inode_len(inode: InodeStruct<'static>) -> Result<usize> {
|
||||
Ok(match inode.kind() {
|
||||
InodeKind::File(file) => file.data().map_err(|_| Error::new(EIO))?.len(),
|
||||
InodeKind::Dir(dir) => (Iter { dir, idx: 0 })
|
||||
.fold(0, |len, entry| len + entry.and_then(|entry| entry.name().map_err(|_| Error::new(EIO))).map_or(0, |name| name.len() + 1)),
|
||||
InodeKind::Unknown => return Err(Error::new(EIO)),
|
||||
})
|
||||
}
|
||||
|
||||
impl SchemeMut for InitFsScheme {
|
||||
fn open(&mut self, path: &str, _flags: usize, _uid: u32, _gid: u32) -> Result<usize> {
|
||||
let mut components = path
|
||||
// trim leading and trailing slash
|
||||
.trim_matches('/')
|
||||
// divide into components
|
||||
.split('/')
|
||||
// filter out double slashes (e.g. /usr//bin/...)
|
||||
.filter(|c| !c.is_empty());
|
||||
|
||||
let mut current_inode = InitFs::ROOT_INODE;
|
||||
|
||||
while let Some(component) = components.next() {
|
||||
match component {
|
||||
"." => continue,
|
||||
".." => {
|
||||
let _ = components.next_back();
|
||||
continue
|
||||
}
|
||||
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let current_inode_struct = Self::get_inode(&self.fs, current_inode)?;
|
||||
|
||||
let dir = match current_inode_struct.kind() {
|
||||
InodeKind::Dir(dir) => dir,
|
||||
|
||||
// If we still have more components in the path, and the file tree for that
|
||||
// particular branch is not all directories except the last, then that file cannot
|
||||
// exist.
|
||||
InodeKind::File(_) | InodeKind::Unknown => return Err(Error::new(ENOENT)),
|
||||
};
|
||||
|
||||
let mut entries = Iter {
|
||||
dir,
|
||||
idx: 0,
|
||||
};
|
||||
|
||||
current_inode = loop {
|
||||
let entry_res = match entries.next() {
|
||||
Some(e) => e,
|
||||
None => return Err(Error::new(ENOENT)),
|
||||
};
|
||||
let entry = entry_res?;
|
||||
let name = entry.name().map_err(|_| Error::new(EIO))?;
|
||||
if name == component.as_bytes() {
|
||||
break entry.inode();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let id = self.next_id();
|
||||
let old = self.handles.insert(id, Handle {
|
||||
inode: current_inode,
|
||||
seek: 0_usize,
|
||||
filename: path.into(),
|
||||
});
|
||||
assert!(old.is_none());
|
||||
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
fn read(&mut self, id: usize, buffer: &mut [u8]) -> Result<usize> {
|
||||
let handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?;
|
||||
|
||||
match Self::get_inode(&self.fs, handle.inode)?.kind() {
|
||||
InodeKind::Dir(dir) => {
|
||||
let mut bytes_read = 0;
|
||||
let mut bytes_skipped = 0;
|
||||
|
||||
for entry_res in (Iter { dir, idx: 0 }) {
|
||||
let entry = entry_res?;
|
||||
let name = entry.name().map_err(|_| Error::new(EIO))?;
|
||||
let entry_len = name.len() + 1;
|
||||
|
||||
let to_skip = core::cmp::min(handle.seek - bytes_skipped, entry_len);
|
||||
|
||||
let to_copy = entry_len.saturating_sub(to_skip).saturating_sub(1);
|
||||
buffer[bytes_read..bytes_read + to_copy].copy_from_slice(&name[..to_copy]);
|
||||
|
||||
if to_copy.saturating_sub(to_skip) == 1 {
|
||||
buffer[bytes_read + to_copy] = b'\n';
|
||||
bytes_read += 1;
|
||||
}
|
||||
|
||||
bytes_read += to_copy;
|
||||
bytes_skipped += to_skip;
|
||||
}
|
||||
|
||||
handle.seek = handle.seek.checked_add(bytes_read).ok_or(Error::new(EOVERFLOW))?;
|
||||
|
||||
Ok(bytes_read)
|
||||
}
|
||||
InodeKind::File(file) => {
|
||||
let data = file.data().map_err(|_| Error::new(EIO))?;
|
||||
let src_buf = &data[core::cmp::min(handle.seek, data.len())..];
|
||||
|
||||
let to_copy = core::cmp::min(src_buf.len(), buffer.len());
|
||||
buffer[..to_copy].copy_from_slice(&src_buf[..to_copy]);
|
||||
|
||||
handle.seek = handle.seek.checked_add(to_copy).ok_or(Error::new(EOVERFLOW))?;
|
||||
|
||||
Ok(to_copy)
|
||||
}
|
||||
InodeKind::Unknown => return Err(Error::new(EIO)),
|
||||
}
|
||||
}
|
||||
|
||||
fn seek(&mut self, id: usize, pos: isize, whence: usize) -> Result<isize> {
|
||||
let handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?;
|
||||
|
||||
let new_offset = calc_seek_offset_usize(handle.seek, pos, whence, inode_len(Self::get_inode(&self.fs, handle.inode)?)?)?;
|
||||
handle.seek = new_offset as usize;
|
||||
Ok(new_offset)
|
||||
}
|
||||
|
||||
fn fcntl(&mut self, id: usize, _cmd: usize, _arg: usize) -> Result<usize> {
|
||||
let _handle = self.handles.get(&id).ok_or(Error::new(EBADF))?;
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn fpath(&mut self, id: usize, buf: &mut [u8]) -> Result<usize> {
|
||||
let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?;
|
||||
|
||||
// TODO: Copy scheme part in kernel
|
||||
let scheme_path = b"initfs:";
|
||||
let scheme_bytes = core::cmp::min(scheme_path.len(), buf.len());
|
||||
buf[..scheme_bytes].copy_from_slice(&scheme_path[..scheme_bytes]);
|
||||
|
||||
let source = handle.filename.as_bytes();
|
||||
let path_bytes = core::cmp::min(buf.len() - scheme_bytes, source.len());
|
||||
buf[scheme_bytes..scheme_bytes + path_bytes].copy_from_slice(&source[..path_bytes]);
|
||||
|
||||
Ok(scheme_bytes + path_bytes)
|
||||
}
|
||||
|
||||
fn fstat(&mut self, id: usize, stat: &mut Stat) -> Result<usize> {
|
||||
let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?;
|
||||
|
||||
let Timespec { sec, nsec } = self.fs.image_creation_time();
|
||||
|
||||
let inode = Self::get_inode(&self.fs, handle.inode)?;
|
||||
|
||||
stat.st_mode = inode.mode() | match inode.kind() { InodeKind::Dir(_) => MODE_DIR, InodeKind::File(_) => MODE_FILE, _ => 0 };
|
||||
stat.st_uid = inode.uid();
|
||||
stat.st_gid = inode.gid();
|
||||
stat.st_size = u64::try_from(inode_len(inode)?).unwrap_or(u64::MAX);
|
||||
|
||||
stat.st_ctime = sec.get();
|
||||
stat.st_ctime_nsec = nsec.get();
|
||||
stat.st_mtime = sec.get();
|
||||
stat.st_mtime_nsec = nsec.get();
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn fsync(&mut self, id: usize) -> Result<usize> {
|
||||
if !self.handles.contains_key(&id) {
|
||||
return Err(Error::new(EBADF));
|
||||
}
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn close(&mut self, id: usize) -> Result<usize> {
|
||||
let _ = self.handles.remove(&id).ok_or(Error::new(EBADF))?;
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(bytes: &'static [u8], sync_pipe: usize) {
|
||||
let mut scheme = InitFsScheme::new(bytes);
|
||||
|
||||
let socket = syscall::open(":initfs", O_RDWR | O_CLOEXEC | O_CREAT)
|
||||
.expect("failed to open initfs scheme socket");
|
||||
|
||||
let _ = syscall::write(sync_pipe, &[0]);
|
||||
|
||||
let mut packet = Packet::default();
|
||||
|
||||
'packets: loop {
|
||||
loop {
|
||||
match syscall::read(socket, &mut packet) {
|
||||
Ok(0) => break 'packets,
|
||||
Ok(_) => break,
|
||||
Err(error) if error == Error::new(EINTR) => continue,
|
||||
Err(error) => panic!("failed to read from scheme socket: {}", error),
|
||||
}
|
||||
}
|
||||
scheme.handle(&mut packet);
|
||||
loop {
|
||||
match syscall::write(socket, &packet) {
|
||||
Ok(0) => break 'packets,
|
||||
Ok(_) => break,
|
||||
Err(error) if error == Error::new(EINTR) => continue,
|
||||
Err(error) => panic!("failed to write to scheme socket: {}", error),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
-2
@@ -1,13 +1,32 @@
|
||||
#![no_std]
|
||||
#![feature(alloc_error_handler, core_intrinsics, lang_items)]
|
||||
#![feature(alloc_error_handler, core_intrinsics, lang_items, panic_info_message)]
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub mod x86_64;
|
||||
|
||||
pub mod exec;
|
||||
pub mod initfs;
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
#[panic_handler]
|
||||
fn panic_handler(_: &core::panic::PanicInfo) -> ! {
|
||||
fn panic_handler(info: &core::panic::PanicInfo) -> ! {
|
||||
use core::fmt::Write;
|
||||
|
||||
struct Writer;
|
||||
|
||||
impl Write for Writer {
|
||||
fn write_str(&mut self, s: &str) -> core::fmt::Result {
|
||||
syscall::write(1, s.as_bytes()).map_err(|_| core::fmt::Error).map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
let _ = syscall::write(1, b"panic: ");
|
||||
if let Some(message) = info.message() {
|
||||
writeln!(&mut Writer, "{}", message).unwrap();
|
||||
} else {
|
||||
let _ = syscall::write(1, b"(explicit panic)\n");
|
||||
}
|
||||
core::intrinsics::abort();
|
||||
}
|
||||
|
||||
@@ -17,3 +36,28 @@ fn alloc_error_handler(_: core::alloc::Layout) -> ! {
|
||||
}
|
||||
#[lang = "eh_personality"]
|
||||
extern "C" fn rust_eh_personality() {}
|
||||
|
||||
mod allocator {
|
||||
struct RelibcAllocator;
|
||||
|
||||
#[global_allocator]
|
||||
static GLOBAL: RelibcAllocator = RelibcAllocator;
|
||||
|
||||
use alloc::alloc::*;
|
||||
|
||||
unsafe impl GlobalAlloc for RelibcAllocator {
|
||||
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||
let mut ptr = core::ptr::null_mut();
|
||||
let align = core::cmp::max(layout.align(), core::mem::align_of::<*mut libc::c_void>());
|
||||
|
||||
if libc::posix_memalign(&mut ptr, align, layout.size()) == 0 {
|
||||
ptr.cast()
|
||||
} else {
|
||||
core::ptr::null_mut()
|
||||
}
|
||||
}
|
||||
unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
|
||||
libc::free(ptr.cast());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,10 +23,6 @@ mod offsets {
|
||||
pub fn data_and_bss() -> (usize, usize) {
|
||||
unsafe { (&__data_start as *const u8 as usize, &__bss_end as *const u8 as usize) }
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub fn end() -> usize {
|
||||
unsafe { &__end as *const u8 as usize }
|
||||
}
|
||||
}
|
||||
|
||||
// relibc linkage stuff
|
||||
|
||||
Reference in New Issue
Block a user