From f6b364845ecd7233bfe67c183bb732211f1d7b29 Mon Sep 17 00:00:00 2001 From: jD91mZM2 Date: Fri, 27 Jul 2018 15:13:00 +0200 Subject: [PATCH] Implement pwd.h --- Cargo.lock | 11 ++ Cargo.toml | 1 + include/sys/types.h | 24 +-- src/lib.rs | 1 + src/platform/src/lib.rs | 5 + src/platform/src/linux/mod.rs | 14 +- src/platform/src/rawfile.rs | 6 +- src/platform/src/redox/mod.rs | 12 +- src/platform/src/types.rs | 30 ++-- src/pwd/Cargo.toml | 12 ++ src/pwd/build.rs | 11 ++ src/pwd/cbindgen.toml | 7 + src/pwd/src/lib.rs | 269 ++++++++++++++++++++++++++++++++++ tests/.gitignore | 1 + tests/Makefile | 1 + tests/pwd.c | 82 +++++++++++ 16 files changed, 439 insertions(+), 48 deletions(-) create mode 100644 src/pwd/Cargo.toml create mode 100644 src/pwd/build.rs create mode 100644 src/pwd/cbindgen.toml create mode 100644 src/pwd/src/lib.rs create mode 100644 tests/pwd.c diff --git a/Cargo.lock b/Cargo.lock index c6b1a934b0..7f79565567 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -249,6 +249,16 @@ dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "pwd" +version = "0.1.0" +dependencies = [ + "cbindgen 0.5.2", + "errno 0.1.0", + "fcntl 0.1.0", + "platform 0.1.0", +] + [[package]] name = "quote" version = "0.3.15" @@ -324,6 +334,7 @@ dependencies = [ "locale 0.1.0", "netinet 0.1.0", "platform 0.1.0", + "pwd 0.1.0", "semaphore 0.1.0", "setjmp 0.1.0", "signal 0.1.0", diff --git a/Cargo.toml b/Cargo.toml index 4af47a1993..61741db75e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ inttypes = { path = "src/inttypes" } locale = { path = "src/locale" } netinet = { path = "src/netinet" } platform = { path = "src/platform" } +pwd = { path = "src/pwd" } semaphore = { path = "src/semaphore" } setjmp = { path = "src/setjmp" } signal = { path = "src/signal" } diff --git a/include/sys/types.h b/include/sys/types.h index d901d6e99d..c4390334b4 100644 --- a/include/sys/types.h +++ b/include/sys/types.h @@ -2,41 +2,31 @@ #define _SYS_TYPES_H typedef long blksize_t; - typedef long dev_t; - typedef unsigned long ino_t; - typedef int gid_t; - typedef int uid_t; - typedef int mode_t; - typedef unsigned long nlink_t; - typedef long off_t; - typedef int pid_t; - typedef unsigned id_t; - typedef long ssize_t; - typedef long time_t; - typedef unsigned int useconds_t; - typedef int suseconds_t; - typedef long clock_t; - typedef int clockid_t; - typedef void* timer_t; - typedef unsigned long int blkcnt_t; +typedef unsigned char u_char, uchar; +typedef unsigned short u_short, ushort; +typedef unsigned int u_int, uint; +typedef unsigned long u_long, ulong; +typedef long long quad_t; +typedef unsigned long long u_quad_t; + #ifdef __linux__ #define _SC_PAGE_SIZE 30 #endif diff --git a/src/lib.rs b/src/lib.rs index d2825fe8d8..915133e9b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,7 @@ pub extern crate float; pub extern crate grp; pub extern crate locale; pub extern crate netinet; +pub extern crate pwd; pub extern crate semaphore; pub extern crate setjmp; pub extern crate signal; diff --git a/src/platform/src/lib.rs b/src/platform/src/lib.rs index 21088f099c..3e3a25d63b 100644 --- a/src/platform/src/lib.rs +++ b/src/platform/src/lib.rs @@ -33,8 +33,11 @@ mod sys; #[path = "redox/mod.rs"] mod sys; +pub mod rawfile; pub mod types; +pub use rawfile::RawFile; + use alloc::Vec; use core::{fmt, ptr}; @@ -61,6 +64,7 @@ pub unsafe fn c_str_mut<'a>(s: *mut c_char) -> &'a mut [u8] { } pub unsafe fn c_str_n_mut<'a>(s: *mut c_char, n: usize) -> &'a mut [u8] { + assert!(s != ptr::null_mut()); use core::slice; let mut size = 0; @@ -81,6 +85,7 @@ pub unsafe fn c_str<'a>(s: *const c_char) -> &'a [u8] { } pub unsafe fn c_str_n<'a>(s: *const c_char, n: usize) -> &'a [u8] { + assert!(s != ptr::null()); use core::slice; let mut size = 0; diff --git a/src/platform/src/linux/mod.rs b/src/platform/src/linux/mod.rs index d95baa2047..c26157eda9 100644 --- a/src/platform/src/linux/mod.rs +++ b/src/platform/src/linux/mod.rs @@ -122,15 +122,15 @@ pub fn getdents(fd: c_int, dirents: *mut dirent, bytes: usize) -> c_int { } pub fn getegid() -> gid_t { - e(unsafe { syscall!(GETEGID) }) + e(unsafe { syscall!(GETEGID) }) as gid_t } pub fn geteuid() -> uid_t { - e(unsafe { syscall!(GETEUID) }) + e(unsafe { syscall!(GETEUID) }) as uid_t } pub fn getgid() -> gid_t { - e(unsafe { syscall!(GETGID) }) + e(unsafe { syscall!(GETGID) }) as gid_t } pub unsafe fn gethostname(mut name: *mut c_char, len: size_t) -> c_int { @@ -174,15 +174,15 @@ pub unsafe fn getpeername( } pub fn getpgid(pid: pid_t) -> pid_t { - e(unsafe { syscall!(GETPGID, pid) }) + e(unsafe { syscall!(GETPGID, pid) }) as pid_t } pub fn getpid() -> pid_t { - e(unsafe { syscall!(GETPID) }) + e(unsafe { syscall!(GETPID) }) as pid_t } pub fn getppid() -> pid_t { - e(unsafe { syscall!(GETPPID) }) + e(unsafe { syscall!(GETPPID) }) as pid_t } pub unsafe fn getsockname( @@ -217,7 +217,7 @@ pub fn gettimeofday(tp: *mut timeval, tzp: *mut timezone) -> c_int { } pub fn getuid() -> uid_t { - e(unsafe { syscall!(GETUID) }) + e(unsafe { syscall!(GETUID) }) as uid_t } pub fn ioctl(fd: c_int, request: c_ulong, out: *mut c_void) -> c_int { diff --git a/src/platform/src/rawfile.rs b/src/platform/src/rawfile.rs index e19be0b829..76aff649ce 100644 --- a/src/platform/src/rawfile.rs +++ b/src/platform/src/rawfile.rs @@ -11,7 +11,7 @@ impl RawFile { } } - pub fn dup(&self, _buf: &[u8]) -> Result { + pub fn dup(&self) -> Result { match dup(self.0) { -1 => Err(()), n => Ok(RawFile(n)) @@ -38,9 +38,9 @@ impl Drop for RawFile { } impl Deref for RawFile { - type Target = usize; + type Target = c_int; - fn deref(&self) -> &usize { + fn deref(&self) -> &c_int { &self.0 } } diff --git a/src/platform/src/redox/mod.rs b/src/platform/src/redox/mod.rs index 5a52bd7d49..71e18d7197 100644 --- a/src/platform/src/redox/mod.rs +++ b/src/platform/src/redox/mod.rs @@ -100,7 +100,7 @@ pub fn chmod(path: *const c_char, mode: mode_t) -> c_int { match syscall::open(path, O_WRONLY) { Err(err) => e(Err(err)) as c_int, Ok(fd) => { - let res = syscall::fchmod(fd as usize, mode); + let res = syscall::fchmod(fd as usize, mode as u16); let _ = syscall::close(fd); e(res) as c_int } @@ -215,7 +215,7 @@ pub fn fchdir(fd: c_int) -> c_int { } pub fn fchmod(fd: c_int, mode: mode_t) -> c_int { - e(syscall::fchmod(fd as usize, mode)) as c_int + e(syscall::fchmod(fd as usize, mode as u16)) as c_int } pub fn fchown(fd: c_int, owner: uid_t, group: gid_t) -> c_int { @@ -239,7 +239,7 @@ pub fn fstat(fildes: c_int, buf: *mut stat) -> c_int { (*buf).st_dev = redox_buf.st_dev as dev_t; (*buf).st_ino = redox_buf.st_ino as ino_t; (*buf).st_nlink = redox_buf.st_nlink as nlink_t; - (*buf).st_mode = redox_buf.st_mode; + (*buf).st_mode = redox_buf.st_mode as mode_t; (*buf).st_uid = redox_buf.st_uid as uid_t; (*buf).st_gid = redox_buf.st_gid as gid_t; // TODO st_rdev @@ -494,11 +494,11 @@ pub fn isatty(fd: c_int) -> c_int { } pub fn kill(pid: pid_t, sig: c_int) -> c_int { - e(syscall::kill(pid, sig as usize)) as c_int + e(syscall::kill(pid as usize, sig as usize)) as c_int } pub fn killpg(pgrp: pid_t, sig: c_int) -> c_int { - e(syscall::kill(-(pgrp as isize) as pid_t, sig as usize)) as c_int + e(syscall::kill(-(pgrp as isize) as usize, sig as usize)) as c_int } pub fn link(path1: *const c_char, path2: *const c_char) -> c_int { @@ -823,7 +823,7 @@ pub fn waitpid(pid: pid_t, stat_loc: *mut c_int, options: c_int) -> pid_t { if !stat_loc.is_null() { *stat_loc = temp as c_int; } - res + res as pid_t } } diff --git a/src/platform/src/types.rs b/src/platform/src/types.rs index 5843fd3ad6..012749cd3f 100644 --- a/src/platform/src/types.rs +++ b/src/platform/src/types.rs @@ -50,25 +50,25 @@ pub type wchar_t = i32; pub type wint_t = u32; pub type wctype_t = i64; -pub type off_t = i64; -pub type mode_t = u16; -pub type time_t = i64; -pub type pid_t = usize; -pub type id_t = usize; -pub type gid_t = usize; -pub type uid_t = usize; -pub type dev_t = usize; -pub type ino_t = usize; -pub type nlink_t = usize; -pub type blksize_t = isize; -pub type blkcnt_t = u64; +pub type off_t = c_long; +pub type mode_t = c_int; +pub type time_t = c_long; +pub type pid_t = c_int; +pub type id_t = c_uint; +pub type gid_t = c_int; +pub type uid_t = c_int; +pub type dev_t = c_long; +pub type ino_t = c_ulong; +pub type nlink_t = c_ulong; +pub type blksize_t = c_long; +pub type blkcnt_t = c_ulong; pub type useconds_t = c_uint; pub type suseconds_t = c_int; -pub type clock_t = i64; -pub type clockid_t = i32; -pub type timer_t = c_void; +pub type clock_t = c_long; +pub type clockid_t = c_int; +pub type timer_t = *mut c_void; #[repr(C)] #[derive(Default)] diff --git a/src/pwd/Cargo.toml b/src/pwd/Cargo.toml new file mode 100644 index 0000000000..6273ec82ed --- /dev/null +++ b/src/pwd/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "pwd" +version = "0.1.0" +authors = ["jD91mZM2 "] + +[build-dependencies] +cbindgen = { path = "../../cbindgen" } + +[dependencies] +errno = { path = "../errno" } +fcntl = { path = "../fcntl" } +platform = { path = "../platform" } diff --git a/src/pwd/build.rs b/src/pwd/build.rs new file mode 100644 index 0000000000..52d329c6ee --- /dev/null +++ b/src/pwd/build.rs @@ -0,0 +1,11 @@ +extern crate cbindgen; + +use std::{env, fs}; + +fn main() { + let crate_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"); + fs::create_dir_all("../../target/include").expect("failed to create include directory"); + cbindgen::generate(crate_dir) + .expect("failed to generate bindings") + .write_to_file("../../target/include/pwd.h"); +} diff --git a/src/pwd/cbindgen.toml b/src/pwd/cbindgen.toml new file mode 100644 index 0000000000..dfe0c433b3 --- /dev/null +++ b/src/pwd/cbindgen.toml @@ -0,0 +1,7 @@ +sys_includes = ["stddef.h", "sys/types.h"] +include_guard = "_PWD_H" +language = "C" +style = "Tag" + +[enum] +prefix_with_name = true diff --git a/src/pwd/src/lib.rs b/src/pwd/src/lib.rs new file mode 100644 index 0000000000..e2eb510cb8 --- /dev/null +++ b/src/pwd/src/lib.rs @@ -0,0 +1,269 @@ +//! pwd implementation for relibc + +#![no_std] +#![feature(alloc)] + +extern crate alloc; +extern crate errno; +extern crate fcntl; +extern crate platform; + +use alloc::vec::Vec; +use core::ptr; +use platform::RawFile; +use platform::types::*; + +#[repr(C)] +pub struct passwd { + pw_name: *mut c_char, + pw_passwd: *mut c_char, + pw_uid: uid_t, + pw_gid: gid_t, + pw_gecos: *mut c_char, + pw_dir: *mut c_char, + pw_shell: *mut c_char +} + +static mut PASSWD_BUF: *mut c_char = ptr::null_mut(); +static mut PASSWD: passwd = passwd { + pw_name: ptr::null_mut(), + pw_passwd: ptr::null_mut(), + pw_uid: 0, + pw_gid: 0, + pw_gecos: ptr::null_mut(), + pw_dir: ptr::null_mut(), + pw_shell: ptr::null_mut() +}; + +enum OptionPasswd { + Error, + NotFound, + Found(*mut c_char) +} + +fn pwd_lookup(out: *mut passwd, alloc: Option<(*mut c_char, size_t)>, mut callback: F) -> OptionPasswd + where + // TODO F: FnMut(impl Iterator) -> bool + F: FnMut(&[&[u8]]) -> bool +{ + let file = match RawFile::open("/etc/passwd\0".as_ptr() as *const c_char, fcntl::O_RDONLY, 0o644) { + Ok(file) => file, + Err(_) => return OptionPasswd::Error + }; + + let mut buf = Vec::new(); + let mut newline = None; + + loop { + // TODO when nll becomes a thing: + // let mut newline; + + // WORKAROUND: + if let Some(newline) = newline { + buf.drain(..newline + 1); + } + + // Read until newline + loop { + newline = buf.iter().position(|b| *b == b'\n'); + + if newline.is_some() { + break; + } + + let len = buf.len(); + + if len >= buf.capacity() { + buf.reserve(1024); + } + + unsafe { + let capacity = buf.capacity(); + buf.set_len(capacity); + } + + let read = platform::read(*file, &mut buf[len..]); + if read == 0 { + return OptionPasswd::NotFound; + } + if read < 0 { + return OptionPasswd::Error; + } + + unsafe { + buf.set_len(len + read as usize); + } + } + + // Parse into passwd + let newline = newline.unwrap(); // safe because it doesn't break the loop otherwise + let line = &buf[..newline]; + let mut parts: [&[u8]; 7] = [&[]; 7]; + for (i, part) in line.splitn(7, |b| *b == b':').enumerate() { + parts[i] = part; + } + + if !callback(&parts) { + // TODO when nll becomes a thing: + // buf.drain(..newline + 1); + continue; + } + + 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.into_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.offset(i as isize) = *c as c_char; + } + } + unsafe { + *dst.offset(src.len() as isize) = 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); + } +} + +#[no_mangle] +pub extern "C" fn getpwnam_r(name: *const c_char, out: *mut passwd, buf: *mut c_char, + 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 || unsafe { *name.offset(i as isize) } != *c as c_char { + return false; + } + } + true + }) { + OptionPasswd::Error => unsafe { + *result = ptr::null_mut(); + -1 + }, + OptionPasswd::NotFound => unsafe { + *result = ptr::null_mut(); + 0 + }, + OptionPasswd::Found(_) => unsafe { + *result = out; + 0 + } + } +} + +#[no_mangle] +pub extern "C" fn getpwuid_r(uid: uid_t, out: *mut passwd, buf: *mut c_char, + 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 => unsafe { + *result = ptr::null_mut(); + -1 + }, + OptionPasswd::NotFound => unsafe { + *result = ptr::null_mut(); + 0 + }, + OptionPasswd::Found(_) => unsafe { + *result = out; + 0 + } + } +} + +#[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.offset(i as isize) } != *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 + } + } +} + +#[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 { + PASSWD_BUF = buf; + &mut PASSWD + } + } +} diff --git a/tests/.gitignore b/tests/.gitignore index 253dece984..3454792e34 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -64,6 +64,7 @@ wchar/mbsrtowcs wchar/putwchar wchar/wcrtomb dirent +pwd stdlib/alloc stdlib/bsearch stdlib/mktemp diff --git a/tests/Makefile b/tests/Makefile index 432466dc58..e54d1de7df 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -67,6 +67,7 @@ EXPECT_BINS=\ BINS=\ $(EXPECT_BINS) \ dirent \ + pwd \ stdlib/alloc \ stdlib/bsearch \ stdlib/mktemp \ diff --git a/tests/pwd.c b/tests/pwd.c new file mode 100644 index 0000000000..52b3511675 --- /dev/null +++ b/tests/pwd.c @@ -0,0 +1,82 @@ +#include +#include +#include +#include + +void print(struct passwd *pwd) { + printf("pw_name: %s\n", pwd->pw_name); + printf("pw_password: %s\n", pwd->pw_passwd); + printf("pw_uid: %u\n", pwd->pw_uid); + printf("pw_gid: %u\n", pwd->pw_gid); + printf("pw_gecos: %s\n", pwd->pw_gecos); + printf("pw_dir: %s\n", pwd->pw_dir); + printf("pw_shell: %s\n", pwd->pw_shell); +} + +int main() { + puts("--- Checking getpwuid ---"); + errno = 0; + struct passwd *pwd = getpwuid(0); + if (errno != 0) { + perror("getpwuid"); + return 1; + } + if (pwd != NULL) { + print(pwd); + } + + puts("--- Checking getpwnam ---"); + errno = 0; + pwd = getpwnam("nobody"); + if (errno != 0) { + perror("getpwnam"); + return 1; + } + if (pwd != NULL) { + print(pwd); + } + + puts("--- Checking getpwuid_r ---"); + struct passwd pwd2; + struct passwd* result; + char* buf = malloc(100); + if (getpwuid_r(0, &pwd2, buf, 100, &result) < 0) { + perror("getpwuid_r"); + free(buf); + return 1; + } + if (result != NULL) { + if (result != &pwd2) { + free(buf); + return 1; + } + print(&pwd2); + } + + puts("--- Checking getpwnam_r ---"); + if (getpwnam_r("nobody", &pwd2, buf, 100, &result) < 0) { + perror("getpwuid_r"); + free(buf); + return 1; + } + if (result != NULL) { + if (result != &pwd2) { + free(buf); + return 1; + } + print(&pwd2); + } + free(buf); + + 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!"); + return 1; + } + if (errno != ERANGE) { + perror("getpwuid_r"); + return 1; + } + puts("Returned ERANGE because the buffer was too small 👍"); +}