diff --git a/Cargo.lock b/Cargo.lock index a909a63bac..83e5b5499e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 94cdc739ee..f336f0de67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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] diff --git a/src/exec.rs b/src/exec.rs index 64741de024..daf587b710 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -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); +} diff --git a/src/initfs.rs b/src/initfs.rs new file mode 100644 index 0000000000..06b5380cc2 --- /dev/null +++ b/src/initfs.rs @@ -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__/`? + filename: String, +} +pub struct InitFsScheme { + handles: BTreeMap, + 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> { + 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>; + + fn next(&mut self) -> Option { + let entry = self.dir.get_entry(self.idx).map_err(|_| Error::new(EIO)); + self.idx += 1; + entry.transpose() + } + fn size_hint(&self) -> (usize, Option) { + 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 { + 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 { + 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 { + 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 { + 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 { + let _handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; + + Ok(0) + } + + fn fpath(&mut self, id: usize, buf: &mut [u8]) -> Result { + 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 { + 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 { + if !self.handles.contains_key(&id) { + return Err(Error::new(EBADF)); + } + + Ok(0) + } + + fn close(&mut self, id: usize) -> Result { + 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), + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index c47e6c82cb..0df48f6548 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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()); + } + } +} diff --git a/src/x86_64.rs b/src/x86_64.rs index fe137247fb..2303239102 100644 --- a/src/x86_64.rs +++ b/src/x86_64.rs @@ -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