From 4f2a93ea9015441a2a4aaa1641f190d71aab7b66 Mon Sep 17 00:00:00 2001 From: jD91mZM2 Date: Mon, 12 Aug 2019 09:29:24 +0200 Subject: [PATCH] Vast refactor of pwd.h, add getpwent/setpwent/endpwent --- src/header/pwd/linux.rs | 20 ++- src/header/pwd/mod.rs | 385 ++++++++++++++++++++++------------------ src/header/pwd/redox.rs | 22 ++- src/lib.rs | 3 +- tests/pwd.c | 27 ++- 5 files changed, 263 insertions(+), 194 deletions(-) diff --git a/src/header/pwd/linux.rs b/src/header/pwd/linux.rs index 5080b5c852..0d74036685 100644 --- a/src/header/pwd/linux.rs +++ b/src/header/pwd/linux.rs @@ -1,7 +1,15 @@ -pub fn split(line: &[u8]) -> [&[u8]; 7] { - let mut parts: [&[u8]; 7] = [&[]; 7]; - for (i, part) in line.splitn(7, |b| *b == b':').enumerate() { - parts[i] = part; - } - parts +use super::{parsed, passwd}; +use crate::platform::types::*; + +pub fn split(line: &mut [u8]) -> Option { + let mut parts = line.split_mut(|&c| c == b'\0'); + Some(passwd { + pw_name: parts.next()?.as_mut_ptr() as *mut c_char, + pw_passwd: parts.next()?.as_mut_ptr() as *mut c_char, + pw_uid: parsed(parts.next())?, + pw_gid: parsed(parts.next())?, + pw_gecos: parts.next()?.as_mut_ptr() as *mut c_char, + pw_dir: parts.next()?.as_mut_ptr() as *mut c_char, + pw_shell: parts.next()?.as_mut_ptr() as *mut c_char, + }) } diff --git a/src/header/pwd/mod.rs b/src/header/pwd/mod.rs index a4564c9010..055b80cf8b 100644 --- a/src/header/pwd/mod.rs +++ b/src/header/pwd/mod.rs @@ -1,11 +1,23 @@ //! pwd implementation for relibc -use core::ptr; +use alloc::{ + boxed::Box, + vec::Vec, +}; +use core::{ + ops::{Deref, DerefMut}, + pin::Pin, + ptr, +}; use crate::{ fs::File, - header::{errno, fcntl}, - io::{BufRead, BufReader}, + header::{ + errno, + fcntl, + string::strcmp, + }, + io::{prelude::*, BufReader, SeekFrom}, platform::{self, types::*}, }; @@ -20,6 +32,7 @@ use self::linux as sys; use self::redox as sys; #[repr(C)] +#[derive(Debug)] pub struct passwd { pw_name: *mut c_char, pw_passwd: *mut c_char, @@ -30,7 +43,7 @@ pub struct passwd { pw_shell: *mut c_char, } -static mut PASSWD_BUF: *mut c_char = ptr::null_mut(); +static mut PASSWD_BUF: Option = None; static mut PASSWD: passwd = passwd { pw_name: ptr::null_mut(), pw_passwd: ptr::null_mut(), @@ -41,110 +54,155 @@ static mut PASSWD: passwd = passwd { pw_shell: ptr::null_mut(), }; -enum OptionPasswd { - Error, - NotFound, - Found(*mut c_char), +#[derive(Clone, Copy, Debug)] +struct DestBuffer { + ptr: *mut u8, + len: usize, } -fn pwd_lookup( - out: *mut passwd, - alloc: Option<(*mut c_char, size_t)>, - mut callback: F, -) -> OptionPasswd +#[derive(Debug)] +enum MaybeAllocated { + Owned(Pin>), + Borrowed(DestBuffer), +} +impl Deref for MaybeAllocated { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + match self { + MaybeAllocated::Owned(boxed) => boxed, + MaybeAllocated::Borrowed(dst) => unsafe { + core::slice::from_raw_parts(dst.ptr, dst.len) + }, + } + } +} +impl DerefMut for MaybeAllocated { + fn deref_mut(&mut self) -> &mut Self::Target { + match self { + MaybeAllocated::Owned(boxed) => boxed, + MaybeAllocated::Borrowed(dst) => unsafe { + core::slice::from_raw_parts_mut(dst.ptr, dst.len) + }, + } + } +} + +#[derive(Debug)] +struct OwnedPwd { + buffer: MaybeAllocated, + reference: passwd, +} + +impl OwnedPwd { + fn into_global(self) -> *mut passwd { + unsafe { + PASSWD_BUF = Some(self.buffer); + PASSWD = self.reference; + &mut PASSWD + } + } +} + +#[derive(Clone, Copy, Debug)] +enum Cause { + Eof, + Other, +} + +static mut READER: Option> = None; + +fn parsed(buf: Option) -> Option where - // TODO F: FnMut(impl Iterator) -> bool - F: FnMut(&[&[u8]]) -> bool, + I: core::borrow::Borrow<[u8]>, + O: core::str::FromStr, +{ + let buf = buf?; + let string = core::str::from_utf8(buf.borrow()).ok()?; + string.parse().ok() +} + +fn getpwent_r(reader: &mut BufReader, destination: Option) -> Result { + let mut buf = Vec::new(); + if reader.read_until(b'\n', &mut buf).map_err(|_| Cause::Other)? == 0 { + return Err(Cause::Eof); + } + + // Replace all occurences of ':' with terminating NUL byte + let mut start = 0; + while let Some(i) = memchr::memchr(b':', &buf[start..]) { + buf[start + i] = 0; + start += i + 1; + } + + // Place terminating NUL byte at the end, replace newline + let last = buf.last_mut(); + if last == Some(&mut b'\n') { + *last.unwrap() = 0; + } else { + buf.push(0); + } + + let mut buf = match destination { + None => MaybeAllocated::Owned(Box::into_pin(buf.into_boxed_slice())), + Some(dst) => { + let mut new = MaybeAllocated::Borrowed(dst); + if new.len() < buf.len() { + unsafe { + platform::errno = errno::ERANGE; + } + return Err(Cause::Other); + } + new[..buf.len()].copy_from_slice(&buf); + new + }, + }; + + // Chop up the result into a valid structure + let passwd = sys::split(&mut buf).ok_or(Cause::Other)?; + + Ok(OwnedPwd { + buffer: buf, + reference: passwd, + }) +} + +fn pwd_lookup(mut matches: F, destination: Option) -> Result +where + F: FnMut(&passwd) -> bool, { let file = match File::open(c_str!("/etc/passwd"), fcntl::O_RDONLY) { Ok(file) => file, - Err(_) => return OptionPasswd::Error, + Err(_) => return Err(Cause::Other), }; - let file = BufReader::new(file); + let mut reader = BufReader::new(file); - for line in file.split(b'\n') { - let line = match line { - Ok(line) => line, - Err(err) => unsafe { - platform::errno = errno::EIO; - return OptionPasswd::Error; - }, - }; + loop { + let entry = getpwent_r(&mut reader, destination)?; - // Parse into passwd - let parts: [&[u8]; 7] = sys::split(&line); - - if !callback(&parts) { - continue; + if matches(&entry.reference) { + return Ok(entry); } - - let len = parts - .iter() - .enumerate() - .filter(|(i, _)| *i != 2 && *i != 3) - .map(|(_, part)| part.len() + 1) - .sum(); - - if alloc.map(|(_, s)| len > s as usize).unwrap_or(false) { - unsafe { - platform::errno = errno::ERANGE; - } - return OptionPasswd::Error; - } - - let alloc = match alloc { - Some((alloc, _)) => alloc, - None => unsafe { platform::alloc(len) as *mut c_char }, - }; - // _ prefix so it won't complain about the trailing - // _off += - // in the macro that is never read - let mut _off = 0; - - let mut parts = parts.iter(); - - macro_rules! copy_into { - ($entry:expr) => { - debug_assert!(_off as usize <= len); - - let src = parts.next().unwrap_or(&(&[] as &[u8])); // this is madness - let dst = unsafe { alloc.offset(_off) }; - - for (i, c) in src.iter().enumerate() { - unsafe { - *dst.add(i) = *c as c_char; - } - } - unsafe { - *dst.add(src.len()) = 0; - - $entry = dst; - } - _off += src.len() as isize + 1; - }; - ($entry:expr,parse) => { - unsafe { - $entry = parts - .next() - .and_then(|part| core::str::from_utf8(part).ok()) - .and_then(|part| part.parse().ok()) - .unwrap_or(0); - } - }; - } - - copy_into!((*out).pw_name); - copy_into!((*out).pw_passwd); - copy_into!((*out).pw_uid, parse); - copy_into!((*out).pw_gid, parse); - copy_into!((*out).pw_gecos); - copy_into!((*out).pw_dir); - copy_into!((*out).pw_shell); - - return OptionPasswd::Found(alloc); } - OptionPasswd::NotFound +} + +unsafe fn mux(status: Result, out: *mut passwd, result: *mut *mut passwd) -> c_int { + match status { + Ok(owned) => { + *out = owned.reference; + *result = out; + 0 + }, + Err(Cause::Eof) => { + *result = ptr::null_mut(); + 0 + }, + Err(Cause::Other) => { + *result = ptr::null_mut(); + -1 + }, + } } #[no_mangle] @@ -155,31 +213,14 @@ pub unsafe extern "C" fn getpwnam_r( size: size_t, result: *mut *mut passwd, ) -> c_int { - match pwd_lookup(out, Some((buf, size)), |parts| { - let part = parts.get(0).unwrap_or(&(&[] as &[u8])); - for (i, c) in part.iter().enumerate() { - // /etc/passwd should not contain any NUL bytes in the middle - // of entries, but if this happens, it can't possibly match the - // search query since it's NUL terminated. - if *c == 0 || *name.add(i) != *c as c_char { - return false; - } - } - true - }) { - OptionPasswd::Error => { - *result = ptr::null_mut(); - -1 - } - OptionPasswd::NotFound => { - *result = ptr::null_mut(); - 0 - } - OptionPasswd::Found(_) => { - *result = out; - 0 - } - } + mux( + pwd_lookup(|parts| strcmp(parts.pw_name, name) == 0, Some(DestBuffer { + ptr: buf as *mut u8, + len: size, + })), + out, + result, + ) } #[no_mangle] @@ -190,68 +231,62 @@ pub unsafe extern "C" fn getpwuid_r( size: size_t, result: *mut *mut passwd, ) -> c_int { - match pwd_lookup(out, Some((buf, size)), |parts| { - let part = parts - .get(2) - .and_then(|part| core::str::from_utf8(part).ok()) - .and_then(|part| part.parse().ok()); - part == Some(uid) - }) { - OptionPasswd::Error => { - *result = ptr::null_mut(); - -1 - } - OptionPasswd::NotFound => { - *result = ptr::null_mut(); - 0 - } - OptionPasswd::Found(_) => { - *result = out; - 0 - } - } + let slice = core::slice::from_raw_parts_mut(buf as *mut u8, size); + mux( + pwd_lookup(|part| part.pw_uid == uid, Some(DestBuffer { + ptr: buf as *mut u8, + len: size, + })), + out, + result, + ) } #[no_mangle] pub extern "C" fn getpwnam(name: *const c_char) -> *mut passwd { - match pwd_lookup(unsafe { &mut PASSWD }, None, |parts| { - let part = parts.get(0).unwrap_or(&(&[] as &[u8])); - for (i, c) in part.iter().enumerate() { - // /etc/passwd should not contain any NUL bytes in the middle - // of entries, but if this happens, it can't possibly match the - // search query since it's NUL terminated. - if *c == 0 || unsafe { *name.add(i) } != *c as c_char { - return false; - } - } - true - }) { - OptionPasswd::Error => ptr::null_mut(), - OptionPasswd::NotFound => ptr::null_mut(), - OptionPasswd::Found(buf) => unsafe { - PASSWD_BUF = buf; - &mut PASSWD - }, - } + pwd_lookup(|parts| unsafe { strcmp(parts.pw_name, name) } == 0, None) + .map(|res| res.into_global()) + .unwrap_or(ptr::null_mut()) } #[no_mangle] pub extern "C" fn getpwuid(uid: uid_t) -> *mut passwd { - match pwd_lookup(unsafe { &mut PASSWD }, None, |parts| { - let part = parts - .get(2) - .and_then(|part| core::str::from_utf8(part).ok()) - .and_then(|part| part.parse().ok()); - part == Some(uid) - }) { - OptionPasswd::Error => ptr::null_mut(), - OptionPasswd::NotFound => ptr::null_mut(), - OptionPasswd::Found(buf) => unsafe { - if !PASSWD_BUF.is_null() { - platform::free(PASSWD_BUF as *mut c_void); + pwd_lookup(|parts| parts.pw_uid == uid, None) + .map(|res| res.into_global()) + .unwrap_or(ptr::null_mut()) +} + +#[no_mangle] +pub extern "C" fn getpwent() -> *mut passwd { + let reader = match unsafe { &mut READER } { + Some(reader) => reader, + None => { + let file = match File::open(c_str!("/etc/passwd"), fcntl::O_RDONLY) { + Ok(file) => file, + Err(_) => return ptr::null_mut(), + }; + let reader = BufReader::new(file); + unsafe { + READER = Some(reader); + READER.as_mut().unwrap() } - PASSWD_BUF = buf; - &mut PASSWD - }, + } + }; + getpwent_r(reader, None) + .map(|res| res.into_global()) + .unwrap_or(ptr::null_mut()) +} + +#[no_mangle] +pub extern "C" fn setpwent() { + if let Some(reader) = unsafe { &mut READER } { + let _ = reader.seek(SeekFrom::Start(0)); + } +} + +#[no_mangle] +pub extern "C" fn endpwent() { + unsafe { + READER = None; } } diff --git a/src/header/pwd/redox.rs b/src/header/pwd/redox.rs index 9bce4ec869..88b218ead9 100644 --- a/src/header/pwd/redox.rs +++ b/src/header/pwd/redox.rs @@ -1,11 +1,15 @@ -pub fn split(line: &[u8]) -> [&[u8]; 7] { - let mut parts: [&[u8]; 7] = [&[]; 7]; - let mut iter = line.split(|b| *b == b';'); +use super::{parsed, passwd}; +use crate::platform::types::*; - parts[0] = iter.next().unwrap_or(&[]); - // Skip passwd - for i in 2..7 { - parts[i] = iter.next().unwrap_or(&[]); - } - parts +pub fn split(line: &mut [u8]) -> Option { + let mut parts = line.split_mut(|&c| c == b'\0'); + Some(passwd { + pw_name: parts.next()?.as_mut_ptr() as *mut c_char, + pw_passwd: "x\0".as_ptr() as *const c_char as *mut c_char, + pw_uid: parsed(parts.next())?, + pw_gid: parsed(parts.next())?, + pw_gecos: parts.next()?.as_mut_ptr() as *mut c_char, + pw_dir: parts.next()?.as_mut_ptr() as *mut c_char, + pw_shell: parts.next()?.as_mut_ptr() as *mut c_char, + }) } diff --git a/src/lib.rs b/src/lib.rs index a5031040a3..d5eafcdcfb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ #![allow(unused_variables)] #![feature(allocator_api)] #![feature(asm)] +#![feature(box_into_pin)] #![feature(c_variadic)] #![feature(const_fn)] #![feature(const_raw_ptr_deref)] @@ -11,9 +12,9 @@ #![feature(const_vec_new)] #![feature(core_intrinsics)] #![feature(global_asm)] -#![feature(maybe_uninit_extra)] #![feature(lang_items)] #![feature(linkage)] +#![feature(maybe_uninit_extra)] #![feature(stmt_expr_attributes)] #![feature(str_internals)] #![feature(thread_local)] diff --git a/tests/pwd.c b/tests/pwd.c index 6e51c6c130..13761aae92 100644 --- a/tests/pwd.c +++ b/tests/pwd.c @@ -41,7 +41,7 @@ int main(void) { puts("--- Checking getpwuid_r ---"); struct passwd pwd2; struct passwd* result; - char* buf = malloc(100); + char* buf = malloc(300); if (getpwuid_r(0, &pwd2, buf, 100, &result) < 0) { perror("getpwuid_r"); free(buf); @@ -56,7 +56,7 @@ int main(void) { } puts("--- Checking getpwnam_r ---"); - if (getpwnam_r("nobody", &pwd2, buf, 100, &result) < 0) { + if (getpwnam_r("nobody", &pwd2, buf, 300, &result) < 0) { perror("getpwuid_r"); free(buf); exit(EXIT_FAILURE); @@ -73,7 +73,7 @@ int main(void) { puts("--- Checking getpwuid_r error handling ---"); char buf2[1]; if (getpwuid_r(0, &pwd2, buf2, 1, &result) == 0) { - puts("This shouldn't have succeeded, but did!"); + puts("This shouldn't have succeeded, but it did!"); exit(EXIT_FAILURE); } if (errno != ERANGE) { @@ -81,4 +81,25 @@ int main(void) { exit(EXIT_FAILURE); } puts("Returned ERANGE because the buffer was too small 👍"); + + errno = 0; + + struct passwd *entry = NULL; + for (int i = 1; entry = getpwent(); ++i) { + int backup = errno; + printf("--- getpwent #%d ---\n", i); + if (backup != 0) { + errno = backup; + perror("getpwent"); + exit(EXIT_FAILURE); + } + print(entry); + } + puts("--- getpwent #1 (rewind) ---"); + setpwent(); + entry = getpwent(); + perror("getpwent"); + print(entry); + + endpwent(); }