summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorManos Pitsidianakis <el13635@mail.ntua.gr>2019-07-11 11:44:27 +0300
committerManos Pitsidianakis <el13635@mail.ntua.gr>2019-07-28 20:20:14 +0300
commit38da6c83b9e8e9b0b2e27020e61e6cb086bacc22 (patch)
tree873fc0ac5d006448812e70388fe987cdf91ef6ec
parent8a0e702127bc6229cad363918ae7542b26c11317 (diff)
melib: add read-only support for mbox
-rw-r--r--melib/Cargo.toml1
-rw-r--r--melib/src/backends.rs10
-rw-r--r--melib/src/backends/mbox.rs573
-rw-r--r--melib/src/email.rs3
-rw-r--r--sample-config9
-rw-r--r--ui/Cargo.toml1
-rw-r--r--ui/src/lib.rs50
7 files changed, 621 insertions, 26 deletions
diff --git a/melib/Cargo.toml b/melib/Cargo.toml
index a5497248..cd6ca801 100644
--- a/melib/Cargo.toml
+++ b/melib/Cargo.toml
@@ -24,3 +24,4 @@ serde_derive = "1.0.71"
bincode = "1.0.1"
uuid = { version = "0.6", features = ["serde", "v4"] }
text_processing = { path = "../text_processing", version = "*" }
+libc = {version = "0.2.59", features = ["extra_traits",]}
diff --git a/melib/src/backends.rs b/melib/src/backends.rs
index 0ff75b52..c1fb6f14 100644
--- a/melib/src/backends.rs
+++ b/melib/src/backends.rs
@@ -26,8 +26,8 @@ use crate::async_workers::*;
use crate::conf::AccountSettings;
use crate::error::{MeliError, Result};
//use mailbox::backends::imap::ImapType;
-//use mailbox::backends::mbox::MboxType;
use self::maildir::MaildirType;
+use self::mbox::MboxType;
use super::email::{Envelope, EnvelopeHash, Flag};
use std::fmt;
use std::fmt::Debug;
@@ -59,7 +59,10 @@ impl Backends {
"maildir".to_string(),
Box::new(|| Box::new(|f| Box::new(MaildirType::new(f)))),
);
- //b.register("mbox".to_string(), Box::new(|| Box::new(MboxType::new(""))));
+ b.register(
+ "mbox".to_string(),
+ Box::new(|| Box::new(|f| Box::new(MboxType::new(f)))),
+ );
//b.register("imap".to_string(), Box::new(|| Box::new(ImapType::new(""))));
b
}
@@ -249,6 +252,9 @@ impl BackendOp for ReadOnlyOp {
pub trait BackendFolder: Debug {
fn hash(&self) -> FolderHash;
fn name(&self) -> &str;
+ fn path(&self) -> &str {
+ self.name()
+ }
fn change_name(&mut self, new_name: &str);
fn clone(&self) -> Folder;
fn children(&self) -> &Vec<FolderHash>;
diff --git a/melib/src/backends/mbox.rs b/melib/src/backends/mbox.rs
index db38a8b1..11e72f81 100644
--- a/melib/src/backends/mbox.rs
+++ b/melib/src/backends/mbox.rs
@@ -23,59 +23,584 @@
* https://wiki2.dovecot.org/MailboxFormat/mbox
*/
-/*
-use async::*;
-use error::Result;
-use mailbox::backends::{MailBackend, RefreshEventConsumer, Folder};
-use mailbox::email::Envelope;
+use crate::async_workers::{Async, AsyncBuilder, AsyncStatus};
+use crate::backends::BackendOp;
+use crate::backends::FolderHash;
+use crate::backends::{
+ BackendFolder, Folder, MailBackend, RefreshEvent, RefreshEventConsumer, RefreshEventKind,
+};
+use crate::conf::AccountSettings;
+use crate::email::parser::BytesExt;
+use crate::email::*;
+use crate::error::{MeliError, Result};
+use fnv::FnvHashMap;
+use libc;
+use memmap::{Mmap, Protection};
+use nom::{IResult, Needed};
+extern crate notify;
+use self::notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
+use std::collections::hash_map::DefaultHasher;
+use std::fs::File;
+use std::hash::{Hash, Hasher};
+use std::io::BufReader;
+use std::io::Read;
+use std::os::unix::io::AsRawFd;
+use std::path::{Path, PathBuf};
+use std::sync::mpsc::channel;
+use std::sync::{Arc, Mutex};
+
+const F_OFD_SETLKW: libc::c_int = 38;
+
+// Open file description locking
+// # man fcntl
+fn get_rw_lock_blocking(f: &File) {
+ let fd: libc::c_int = f.as_raw_fd();
+ let mut flock: libc::flock = libc::flock {
+ l_type: libc::F_WRLCK as libc::c_short,
+ l_whence: libc::SEEK_SET as libc::c_short,
+ l_start: 0,
+ l_len: 0, /* "Specifying 0 for l_len has the special meaning: lock all bytes starting at the location
+ specified by l_whence and l_start through to the end of file, no matter how large the file grows." */
+ l_pid: 0, /* "By contrast with traditional record locks, the l_pid field of that structure must be set to zero when using the commands described below." */
+ };
+ let ptr: *mut libc::flock = &mut flock;
+ let ret_val = unsafe { libc::fcntl(fd, F_OFD_SETLKW, ptr as *mut libc::c_void) };
+ debug!(&ret_val);
+ assert!(-1 != ret_val);
+}
+
+macro_rules! get_path_hash {
+ ($path:expr) => {{
+ let mut hasher = DefaultHasher::new();
+ $path.hash(&mut hasher);
+ hasher.finish()
+ }};
+}
+
+#[derive(Debug)]
+struct MboxFolder {
+ hash: FolderHash,
+ name: String,
+ path: PathBuf,
+ content: Vec<u8>,
+ children: Vec<FolderHash>,
+ parent: Option<FolderHash>,
+}
+
+impl BackendFolder for MboxFolder {
+ fn hash(&self) -> FolderHash {
+ self.hash
+ }
+
+ fn name(&self) -> &str {
+ self.name.as_str()
+ }
+
+ fn path(&self) -> &str {
+ /* We know it's valid UTF-8 because we supplied it */
+ self.path.to_str().unwrap()
+ }
+
+ fn change_name(&mut self, s: &str) {
+ self.name = s.to_string();
+ }
+
+ fn clone(&self) -> Folder {
+ Box::new(MboxFolder {
+ hash: self.hash,
+ name: self.name.clone(),
+ path: self.path.clone(),
+ content: self.content.clone(),
+ children: self.children.clone(),
+ parent: self.parent,
+ })
+ }
+
+ fn children(&self) -> &Vec<FolderHash> {
+ &self.children
+ }
+
+ fn parent(&self) -> Option<FolderHash> {
+ self.parent
+ }
+}
/// `BackendOp` implementor for Mbox
-#[derive(Debug, Default, Clone)]
-pub struct MboxOp {}
+#[derive(Debug, Default)]
+pub struct MboxOp {
+ hash: EnvelopeHash,
+ path: PathBuf,
+ offset: Offset,
+ length: Length,
+ slice: Option<Mmap>,
+}
impl MboxOp {
- pub fn new(_path: String) -> Self {
- MboxOp {}
+ pub fn new(hash: EnvelopeHash, path: &Path, offset: Offset, length: Length) -> Self {
+ MboxOp {
+ hash,
+ path: path.to_path_buf(),
+ slice: None,
+ offset,
+ length,
+ }
}
}
impl BackendOp for MboxOp {
fn description(&self) -> String {
- unimplemented!();
+ String::new()
}
+
fn as_bytes(&mut self) -> Result<&[u8]> {
- unimplemented!();
+ if self.slice.is_none() {
+ self.slice = Some(Mmap::open_path(&self.path, Protection::Read)?);
+ }
+ /* Unwrap is safe since we use ? above. */
+ Ok(unsafe {
+ &self.slice.as_ref().unwrap().as_slice()[self.offset..self.offset + self.length]
+ })
}
+
fn fetch_headers(&mut self) -> Result<&[u8]> {
- unimplemented!();
+ let raw = self.as_bytes()?;
+ let result = parser::headers_raw(raw).to_full_result()?;
+ Ok(result)
}
+
fn fetch_body(&mut self) -> Result<&[u8]> {
- unimplemented!();
+ let raw = self.as_bytes()?;
+ let result = parser::body_raw(raw).to_full_result()?;
+ Ok(result)
}
+
fn fetch_flags(&self) -> Flag {
- unimplemented!();
+ let mut flags = Flag::empty();
+ let file = match std::fs::OpenOptions::new()
+ .read(true)
+ .write(true)
+ .open(&self.path)
+ {
+ Ok(f) => f,
+ Err(e) => {
+ debug!(e);
+ return flags;
+ }
+ };
+ get_rw_lock_blocking(&file);
+ let mut buf_reader = BufReader::new(file);
+ let mut contents = Vec::new();
+ if let Err(e) = buf_reader.read_to_end(&mut contents) {
+ debug!(e);
+ return flags;
+ };
+
+ if let Ok(headers) = parser::headers_raw(contents.as_slice()).to_full_result() {
+ if let Some(start) = headers.find(b"Status:") {
+ if let Some(end) = headers[start..].find(b"\n") {
+ let start = start + b"Status:".len();
+ let status = headers[start..start + end].trim();
+ if status.contains(&b'F') {
+ flags.set(Flag::FLAGGED, true);
+ }
+ if status.contains(&b'A') {
+ flags.set(Flag::REPLIED, true);
+ }
+ if status.contains(&b'R') {
+ flags.set(Flag::SEEN, true);
+ }
+ if status.contains(&b'D') {
+ flags.set(Flag::TRASHED, true);
+ }
+ if status.contains(&b'T') {
+ flags.set(Flag::DRAFT, true);
+ }
+ }
+ }
+ if let Some(start) = headers.find(b"X-Status:") {
+ let start = start + b"X-Status:".len();
+ if let Some(end) = headers[start..].find(b"\n") {
+ let status = headers[start..start + end].trim();
+ if status.contains(&b'F') {
+ flags.set(Flag::FLAGGED, true);
+ }
+ if status.contains(&b'A') {
+ flags.set(Flag::REPLIED, true);
+ }
+ if status.contains(&b'R') {
+ flags.set(Flag::SEEN, true);
+ }
+ if status.contains(&b'D') {
+ flags.set(Flag::TRASHED, true);
+ }
+ if status.contains(&b'T') {
+ flags.set(Flag::DRAFT, true);
+ }
+ }
+ }
+ }
+ flags
}
- fn set_flags(&self, f: Flag) -> Result<()> {
- unimplemented!()
+
+ fn set_flag(&mut self, envelope: &mut Envelope, flag: Flag) -> Result<()> {
+ Ok(())
}
}
+pub fn mbox_parse(
+ index: Arc<Mutex<FnvHashMap<EnvelopeHash, (Offset, Length)>>>,
+ input: &[u8],
+ file_offset: usize,
+) -> IResult<&[u8], Vec<Envelope>> {
+ if input.is_empty() {
+ return IResult::Incomplete(Needed::Unknown);
+ }
+ let mut input = input;
+ let mut offset = 0;
+ let mut index = index.lock().unwrap();
+ let mut envelopes = Vec::with_capacity(32);
+ while !input.is_empty() {
+ let next_offset: Option<usize> = input.find(b"\n\nFrom ");
+ if let Some(len) = next_offset {
+ match Envelope::from_bytes(&input[..len]) {
+ Ok(mut env) => {
+ let mut flags = Flag::empty();
+ if env.other_headers().contains_key("Status") {
+ if env.other_headers()["Status"].contains("F") {
+ flags.set(Flag::FLAGGED, true);
+ }
+ if env.other_headers()["Status"].contains("A") {
+ flags.set(Flag::REPLIED, true);
+ }
+ if env.other_headers()["Status"].contains("R") {
+ flags.set(Flag::SEEN, true);
+ }
+ if env.other_headers()["Status"].contains("D") {
+ flags.set(Flag::TRASHED, true);
+ }
+ }
+ if env.other_headers().contains_key("X-Status") {
+ if env.other_headers()["X-Status"].contains("F") {
+ flags.set(Flag::FLAGGED, true);
+ }
+ if env.other_headers()["X-Status"].contains("A") {
+ flags.set(Flag::REPLIED, true);
+ }
+ if env.other_headers()["X-Status"].contains("R") {
+ flags.set(Flag::SEEN, true);
+ }
+ if env.other_headers()["X-Status"].contains("D") {
+ flags.set(Flag::TRASHED, true);
+ }
+ if env.other_headers()["X-Status"].contains("T") {
+ flags.set(Flag::DRAFT, true);
+ }
+ }
+ env.set_flags(flags);
+ index.insert(env.hash(), (offset + file_offset, len));
+ envelopes.push(env);
+ }
+ Err(_) => {
+ debug!("Could not parse mail at byte offset {}", offset);
+ }
+ }
+ offset += len + 2;
+ input = &input[len + 2..];
+ } else {
+ match Envelope::from_bytes(input) {
+ Ok(mut env) => {
+ let mut flags = Flag::empty();
+ if env.other_headers().contains_key("Status") {
+ if env.other_headers()["Status"].contains("F") {
+ flags.set(Flag::FLAGGED, true);
+ }
+ if env.other_headers()["Status"].contains("A") {
+ flags.set(Flag::REPLIED, true);
+ }
+ if env.other_headers()["Status"].contains("R") {
+ flags.set(Flag::SEEN, true);
+ }
+ if env.other_headers()["Status"].contains("D") {
+ flags.set(Flag::TRASHED, true);
+ }
+ }
+ if env.other_headers().contains_key("X-Status") {
+ if env.other_headers()["X-Status"].contains("F") {
+ flags.set(Flag::FLAGGED, true);
+ }
+ if env.other_headers()["X-Status"].contains("A") {
+ flags.set(Flag::REPLIED, true);
+ }
+ if env.other_headers()["X-Status"].contains("R") {
+ flags.set(Flag::SEEN, true);
+ }
+ if env.other_headers()["X-Status"].contains("D") {
+ flags.set(Flag::TRASHED, true);
+ }
+ if env.other_headers()["X-Status"].contains("T") {
+ flags.set(Flag::DRAFT, true);
+ }
+ }
+ env.set_flags(flags);
+ index.insert(env.hash(), (offset + file_offset, input.len()));
+ envelopes.push(env);
+ }
+ Err(_) => {
+ debug!("Could not parse mail at byte offset {}", offset);
+ }
+ }
+ break;
+ }
+ }
+ return IResult::Done(&[], envelopes);
+}
+
+type Offset = usize;
+type Length = usize;
/// Mbox backend
-#[derive(Debug)]
-pub struct MboxType {}
+#[derive(Debug, Default)]
+pub struct MboxType {
+ path: PathBuf,
+ index: Arc<Mutex<FnvHashMap<EnvelopeHash, (Offset, Length)>>>,
+ folders: Arc<Mutex<FnvHashMap<FolderHash, MboxFolder>>>,
+}
impl MailBackend for MboxType {
- fn get(&self, _folder: &Folder) -> Async<Result<Vec<Envelope>>> {
- unimplemented!();
+ fn get(&mut self, folder: &Folder) -> Async<Result<Vec<Envelope>>> {
+ let mut w = AsyncBuilder::new();
+ let handle = {
+ let tx = w.tx();
+ let index = self.index.clone();
+ let folder_path = folder.path().to_string();
+ let folder_hash = folder.hash();
+ let folders = self.folders.clone();
+ let closure = move || {
+ let tx = tx.clone();
+ let index = index.clone();
+ let file = match std::fs::OpenOptions::new()
+ .read(true)
+ .write(true)
+ .open(&folder_path)
+ {
+ Ok(f) => f,
+ Err(e) => {
+ tx.send(AsyncStatus::Payload(Err(MeliError::from(e))));
+ return;
+ }
+ };
+ get_rw_lock_blocking(&file);
+ let mut buf_reader = BufReader::new(file);
+ let mut contents = Vec::new();
+ if let Err(e) = buf_reader.read_to_end(&mut contents) {
+ tx.send(AsyncStatus::Payload(Err(MeliError::from(e))));
+ return;
+ };
+
+ let payload = mbox_parse(index, contents.as_slice(), 0)
+ .to_full_result()
+ .map_err(|e| MeliError::from(e));
+ {
+ let mut folder_lock = folders.lock().unwrap();
+ folder_lock
+ .entry(folder_hash)
+ .and_modify(|f| f.content = contents);
+ }
+
+ tx.send(AsyncStatus::Payload(payload));
+ };
+ Box::new(closure)
+ };
+ w.build(handle)
+ }
+
+ fn watch(&self, sender: RefreshEventConsumer) -> Result<()> {
+ let (tx, rx) = channel();
+ let mut watcher = watcher(tx, std::time::Duration::from_secs(10)).unwrap();
+ for f in self.folders.lock().unwrap().values() {
+ watcher.watch(&f.path, RecursiveMode::Recursive).unwrap();
+ debug!("watching {:?}", f.path.as_path());
+ }
+ let index = self.index.clone();
+ let folders = self.folders.clone();
+ std::thread::Builder::new()
+ .name(format!(
+ "watching {}",
+ self.path.file_name().unwrap().to_str().unwrap()
+ ))
+ .spawn(move || {
+ // Move `watcher` in the closure's scope so that it doesn't get dropped.
+ let _watcher = watcher;
+ let index = index;
+ let folders = folders;
+ loop {
+ match rx.recv() {
+ /*
+ * Event types:
+ *
+ * pub enum RefreshEventKind {
+ * Update(EnvelopeHash, Envelope), // Old hash, new envelope
+ * Create(Envelope),
+ * Remove(EnvelopeHash),
+ * Rescan,
+ * }
+ */
+ Ok(event) => match event {
+ /* Update */
+ DebouncedEvent::NoticeWrite(pathbuf)
+ | DebouncedEvent::Write(pathbuf) => {
+ let folder_hash = get_path_hash!(&pathbuf);
+ let file = match std::fs::OpenOptions::new()
+ .read(true)
+ .write(true)
+ .open(&pathbuf)
+ {
+ Ok(f) => f,
+ Err(_) => {
+ continue;
+ }
+ };
+ get_rw_lock_blocking(&file);
+ let mut folder_lock = folders.lock().unwrap();
+ let mut buf_reader = BufReader::new(file);
+ let mut contents = Vec::new();
+ if let Err(e) = buf_reader.read_to_end(&mut contents) {
+ debug!(e);
+ continue;
+ };
+ if contents
+ .starts_with(folder_lock[&folder_hash].content.as_slice())
+ {
+ if let Ok(envelopes) = mbox_parse(
+ index.clone(),
+ &contents[folder_lock[&folder_hash].content.len()..],
+ folder_lock[&folder_hash].content.len(),
+ )
+ .to_full_result()
+ {
+ for env in envelopes {
+ sender.send(RefreshEvent {
+ hash: folder_hash,
+ kind: RefreshEventKind::Create(Box::new(env)),
+ });
+ }
+ }
+ } else {
+ sender.send(RefreshEvent {
+ hash: folder_hash,
+ kind: RefreshEventKind::Rescan,
+ });
+ }
+ folder_lock
+ .entry(folder_hash)
+ .and_modify(|f| f.content = contents);
+ }
+ /* Remove */
+ DebouncedEvent::NoticeRemove(pathbuf)
+ | DebouncedEvent::Remove(pathbuf) => {
+ panic!(format!("mbox folder {} was removed.", pathbuf.display()))
+ }
+ /* Envelope hasn't changed */
+ DebouncedEvent::Rename(src, dest) => panic!(format!(
+ "mbox folder {} was renamed to {}.",
+ src.display(),
+ dest.display()
+ )),
+ /* Trigger rescan of folder */
+ DebouncedEvent::Rescan => {
+ /* Actually should rescan all folders */
+ unreachable!("Unimplemented: rescan of all folders in MboxType")
+ }
+ _ => {}
+ },
+ Err(e) => debug!("watch error: {:?}", e),
+ }
+ }
+ })?;
+ Ok(())
}
- fn watch(&self, _sender: RefreshEventConsumer, _folders: &[Folder]) -> () {
+ fn folders(&self) -> FnvHashMap<FolderHash, Folder> {
+ self.folders
+ .lock()
+ .unwrap()
+ .iter()
+ .map(|(h, f)| (*h, f.clone() as Folder))
+ .collect()
+ }
+ fn operation(&self, hash: EnvelopeHash, _folder_hash: FolderHash) -> Box<BackendOp> {
+ let (offset, length) = {
+ let index = self.index.lock().unwrap();
+ index[&hash]
+ };
+ Box::new(MboxOp::new(hash, self.path.as_path(), offset, length))
+ }
+
+ fn save(&self, bytes: &[u8], folder: &str) -> Result<()> {
unimplemented!();
}
}
impl MboxType {
- pub fn new(_path: &str) -> Self {
- MboxType {}
+ pub fn new(s: &AccountSettings) -> Self {
+ let path = Path::new(s.root_folder.as_str());
+ if !path.exists() {
+ panic!(
+ "\"root_folder\" {} for account {} is not a valid path.",
+ s.root_folder.as_str(),
+ s.name()
+ );
+ }
+ let ret = MboxType {
+ path: PathBuf::from(path),
+ ..Default::default()
+ };
+ let name: String = ret
+ .path
+ .file_name()
+ .map(|f| f.to_string_lossy().into())
+ .unwrap_or(String::new());
+ let hash = get_path_hash!(path);
+ ret.folders.lock().unwrap().insert(
+ hash,
+ MboxFolder {
+ hash,
+ path: PathBuf::from(path),
+ name,
+ content: Vec::new(),
+ children: Vec::new(),
+ parent: None,
+ },
+ );
+ /*
+ /* Look for other mailboxes */
+ let parent_folder = Path::new(path).parent().unwrap();
+ let read_dir = std::fs::read_dir(parent_folder);
+ if read_dir.is_ok() {
+ for f in read_dir.unwrap() {
+ if f.is_err() {
+ continue;
+ }
+ let f = f.unwrap().path();
+ if f.is_file() && f != path {
+ let name: String = f
+ .file_name()
+ .map(|f| f.to_string_lossy().into())
+ .unwrap_or(String::new());
+ let hash = get_path_hash!(f);
+ ret.folders.lock().unwrap().insert(
+ hash,
+ MboxFolder {
+ hash,
+ path: f,
+ name,
+ content: Vec::new(),
+ children: Vec::new(),
+ parent: None,
+ },
+ );
+ }
+ }
+ }
+ */
+ ret
}
}
-*/
diff --git a/melib/src/email.rs b/melib/src/email.rs
index 06630b97..b7b8690d 100644
--- a/melib/src/email.rs
+++ b/melib/src/email.rs
@@ -775,6 +775,9 @@ impl Envelope {
operation.set_flag(self, f)?;
Ok(())
}
+ pub fn set_flags(&mut self, f: Flag) {
+ self.flags = f;
+ }
pub fn flags(&self) -> Flag {
self.flags
}
diff --git a/sample-config b/sample-config
index 8e130858..90f0303f 100644
--- a/sample-config
+++ b/sample-config
@@ -14,6 +14,15 @@
# "drafts" = { rename="Drafts" }
# "foobar-devel" = { ignore = true } # don't show notifications for this folder
+# Setting up an mbox account
+#[accounts.mbox]
+#root_folder = "/var/mail/username"
+#draft_folder = ""
+#sent_folder = ""
+#format = "mbox"
+#index = "Compact"
+#identity="username@hostname.local"
+
#[pager]
#pager_ratio = 80
#filter = "/usr/bin/pygmentize"
diff --git a/ui/Cargo.toml b/ui/Cargo.toml
index fdc30a88..8c732efb 100644
--- a/ui/Cargo.toml
+++ b/ui/Cargo.toml
@@ -25,3 +25,4 @@ bincode = "1.0.1"
uuid = { version = "0.6", features = ["serde", "v4"] }
unicode-segmentation = "1.2.1" # >:c
text_processing = { path = "../text_processing", version = "*" }
+libc = {version = "0.2.59", features = ["extra_traits",]}
diff --git a/ui/src/lib.rs b/ui/src/lib.rs
index fed7fc92..6650112c 100644
--- a/ui/src/lib.rs
+++ b/ui/src/lib.rs
@@ -71,3 +71,53 @@ pub use crate::conf::*;
pub mod workers;
pub use crate::workers::*;
+
+pub use crate::username::*;
+pub mod username {
+ use libc;
+ use std::ptr::null_mut;
+ /* taken from whoami-0.1.1 */
+ fn getpwuid() -> libc::passwd {
+ let mut pwent = libc::passwd {
+ pw_name: null_mut(),
+ pw_passwd: null_mut(),
+ pw_uid: 0,
+ pw_gid: 0,
+ pw_gecos: null_mut(),
+ pw_dir: null_mut(),
+ pw_shell: null_mut(),
+ };
+ let mut pwentp = null_mut();
+ let mut buffer = [0i8; 16384]; // from the man page
+
+ unsafe {
+ libc::getpwuid_r(
+ libc::geteuid(),
+ &mut pwent,
+ &mut buffer[0],
+ 16384,
+ &mut pwentp,
+ );
+ }
+
+ pwent
+ }
+ fn ptr_to_string(name: *mut i8) -> String {
+ let uname = name as *mut _ as *mut u8;
+
+ let s;
+ let string;
+
+ unsafe {
+ s = ::std::slice::from_raw_parts(uname, libc::strlen(name));
+ string = String::from_utf8_lossy(s).to_string();
+ }
+
+ string
+ }
+ pub fn username() -> String {
+ let pwent = getpwuid();
+
+ ptr_to_string(pwent.pw_name)
+ }
+}