Skip to content

Commit

Permalink
Merge pull request #2 from pocket7878/feature/darwin
Browse files Browse the repository at this point in the history
Implement file resource's POSIX inline backend.
  • Loading branch information
mizzy committed Sep 6, 2017
2 parents 9dadd66 + 962f8ca commit 2330ee5
Show file tree
Hide file tree
Showing 6 changed files with 258 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .gitignore
@@ -1,2 +1,5 @@
target
Cargo.lock
.idea/
specinfra.iml
*.bk
5 changes: 5 additions & 0 deletions Cargo.toml
Expand Up @@ -9,7 +9,12 @@ description = "This is the Rust implementation of Ruby specinfra gem"
[dependencies]
uname = "0.1.1"
libc = "0.2.24"
users = "0.5.0"
ssh2 = "0.3.1"
md5 = "0.3.5"
sha2 = "0.6.0"
nix = "0.9.0"


[lib]
name = "specinfra"
Expand Down
4 changes: 4 additions & 0 deletions src/lib.rs
@@ -1,5 +1,9 @@
extern crate uname;
extern crate libc;
extern crate users;
extern crate md5;
extern crate sha2;
extern crate nix;

use std::ffi::CStr;
use std::error::Error;
Expand Down
25 changes: 25 additions & 0 deletions src/provider/file/inline/mod.rs
Expand Up @@ -2,9 +2,34 @@ use std::error::Error;
use provider::Output;

// See https://users.rust-lang.org/t/solved-is-it-possible-to-clone-a-boxed-trait-object/1714/6
pub enum Whom {
Owner,
Group,
Others,
User(String),
}

pub trait InlineProvider {
fn is_file(&self, &str) -> Result<Output, Box<Error>>;
fn exist(&self, &str) -> Result<Output, Box<Error>>;
fn is_directory(&self, &str) -> Result<Output, Box<Error>>;
fn is_block_device(&self, &str) -> Result<Output, Box<Error>>;
fn is_character_device(&self, &str) -> Result<Output, Box<Error>>;
fn is_pipe(&self, &str) -> Result<Output, Box<Error>>;
fn is_socket(&self, &str) -> Result<Output, Box<Error>>;
fn is_symlink(&self, &str) -> Result<Output, Box<Error>>;
fn contents(&self, &str) -> Result<Output, Box<Error>>;
fn mode(&self, &str) -> Result<Output, Box<Error>>;
fn owner(&self, &str) -> Result<Output, Box<Error>>;
fn group(&self, &str) -> Result<Output, Box<Error>>;
fn linked_to(&self, &str) -> Result<Output, Box<Error>>;
fn is_readable(&self, &str, Option<&Whom>) -> Result<Output, Box<Error>>;
fn is_writable(&self, &str, Option<&Whom>) -> Result<Output, Box<Error>>;
fn md5sum(&self, &str) -> Result<Output, Box<Error>>;
fn sha256sum(&self, &str) -> Result<Output, Box<Error>>;
fn selinux_label(&self, &str) -> Result<Output, Box<Error>>;
fn size(&self, &str) -> Result<Output, Box<Error>>;

fn box_clone(&self) -> Box<InlineProvider>;
}

Expand Down
200 changes: 198 additions & 2 deletions src/provider/file/inline/posix.rs
@@ -1,20 +1,216 @@
use std::result::Result;
use std::fs;
use std::os::unix::fs::PermissionsExt;
use std::path;
use std::io::BufReader;
use std::io::prelude::*;
use std::os::unix::fs::{PermissionsExt, FileTypeExt, MetadataExt};
use std::os::unix::io::{AsRawFd, RawFd};
use std::error::Error;

use provider::Output;
use provider::file::inline::InlineProvider;
use provider::file::inline::{InlineProvider, Whom};

use users;
use users::os::unix::GroupExt;

use md5;
use sha2::{self, Digest};

use nix;


#[derive(Clone)]
pub struct Posix;

impl Posix {
fn file_owner(&self, name: &str) -> Result<users::User, Box<Error>> {
let uid = try!(fs::metadata(name).map(|m| MetadataExt::uid(&m)));
let owner = try!(users::get_user_by_uid(uid)
.ok_or(format!("Failed to get user from uid: {}", uid)));
Ok(owner)
}

fn file_group(&self, name: &str) -> Result<users::Group, Box<Error>> {
let gid = try!(fs::metadata(name).map(|m| MetadataExt::gid(&m)));
let group = try!(users::get_group_by_gid(gid)
.ok_or(format!("Failed to get group from gid: {}", gid)));
Ok(group)
}

fn file_content(&self, name: &str) -> Result<String, Box<Error>> {
let file = try!(fs::File::open(name));
let mut buf_reader = BufReader::new(file);
let mut contents = String::new();
try!(buf_reader.read_to_string(&mut contents));

Ok(contents)
}

// FIXME: is_readableとis_writableをまとめる
fn is_readable_by_user(&self, name: &str, user: &str) -> Result<Output, Box<Error>> {
let file_owner = try!(self.file_owner(name));

if file_owner.name() == user {
self.is_readable(name, Some(&Whom::Owner))
} else {
let file_group = try!(self.file_group(name));
let file_group_members = GroupExt::members(&file_group);
if file_group_members.contains(&user.to_owned()) {
self.is_readable(name, Some(&Whom::Group))
} else {
self.is_readable(name, Some(&Whom::Others))
}
}
}

fn is_writable_by_user(&self, name: &str, user: &str) -> Result<Output, Box<Error>> {
let file_owner = try!(self.file_owner(name));

if file_owner.name() == user {
self.is_writable(name, Some(&Whom::Owner))
} else {
let file_group = try!(self.file_group(name));
let file_group_members = GroupExt::members(&file_group);
if file_group_members.contains(&user.to_owned()) {
self.is_writable(name, Some(&Whom::Group))
} else {
self.is_writable(name, Some(&Whom::Others))
}
}
}
}

impl InlineProvider for Posix {
fn mode(&self, name: &str) -> Result<Output, Box<Error>> {
let res = try!(fs::metadata(name).map(|m| Output::U32(m.permissions().mode())));
Ok(res)
}

fn is_file(&self, name: &str) -> Result<Output, Box<Error>> {
let res = try!(fs::metadata(name).map(|m| Output::Bool(m.is_file())));
Ok(res)
}

fn exist(&self, name: &str) -> Result<Output, Box<Error>> {
let res = Output::Bool(path::Path::new(name).exists());
Ok(res)
}

fn is_directory(&self, name: &str) -> Result<Output, Box<Error>> {
let res = try!(fs::metadata(name).map(|m| Output::Bool(m.is_dir())));
Ok(res)
}

fn is_block_device(&self, name: &str) -> Result<Output, Box<Error>> {
let res = try!(fs::metadata(name)
.map(|m| Output::Bool(FileTypeExt::is_block_device(&m.file_type()))));
Ok(res)
}

fn is_character_device(&self, name: &str) -> Result<Output, Box<Error>> {
let res = try!(fs::metadata(name)
.map(|m| Output::Bool(FileTypeExt::is_char_device(&m.file_type()))));
Ok(res)
}

fn is_pipe(&self, name: &str) -> Result<Output, Box<Error>> {
let res = try!(fs::metadata(name)
.map(|m| Output::Bool(FileTypeExt::is_fifo(&m.file_type()))));
Ok(res)
}

fn is_socket(&self, name: &str) -> Result<Output, Box<Error>> {
let res = try!(fs::metadata(name)
.map(|m| Output::Bool(FileTypeExt::is_socket(&m.file_type()))));
Ok(res)
}

fn is_symlink(&self, name: &str) -> Result<Output, Box<Error>> {
let res = try!(fs::symlink_metadata(name)
.map(|m| Output::Bool(m.file_type().is_symlink())));
Ok(res)
}

fn contents(&self, name: &str) -> Result<Output, Box<Error>> {
let string_content = try!(self.file_content(name));
let res = Output::Text(string_content);
Ok(res)
}

fn owner(&self, name: &str) -> Result<Output, Box<Error>> {
let owner = try!(self.file_owner(name));
Ok(Output::Text(owner.name().to_owned()))
}

fn group(&self, name: &str) -> Result<Output, Box<Error>> {
let group = try!(self.file_group(name));
Ok(Output::Text(group.name().to_owned()))
}

fn linked_to(&self, name: &str) -> Result<Output, Box<Error>> {
let followed_path = try!(fs::read_link(name));
let followed_path_name = try!(followed_path.to_str().ok_or("Path is invalid utf-8"));
Ok(Output::Text(followed_path_name.to_owned()))
}

fn is_readable(&self, name: &str, whom: Option<&Whom>) -> Result<Output, Box<Error>> {
let mode = try!(self.mode(name));
let mode_octal = try!(Output::to_u32(mode));
let res = match whom {
Some(w) => {
match *w {
Whom::Owner => Output::Bool(mode_octal & 0o400 != 0),
Whom::Group => Output::Bool(mode_octal & 0o040 != 0),
Whom::Others => Output::Bool(mode_octal & 0o004 != 0),
Whom::User(ref u) => try!(self.is_readable_by_user(name, &u)),
}
}
None => Output::Bool(mode_octal & 0o444 != 0),
};
Ok(res)
}

fn is_writable(&self, name: &str, whom: Option<&Whom>) -> Result<Output, Box<Error>> {
let mode = try!(self.mode(name));
let mode_octal = try!(Output::to_u32(mode));
let res = match whom {
Some(w) => {
match *w {
Whom::Owner => Output::Bool(mode_octal & 0o200 != 0),
Whom::Group => Output::Bool(mode_octal & 0o020 != 0),
Whom::Others => Output::Bool(mode_octal & 0o002 != 0),
Whom::User(ref u) => try!(self.is_writable_by_user(name, &u)),
}
}
None => Output::Bool(mode_octal & 0o222 != 0),
};
Ok(res)
}

fn md5sum(&self, name: &str) -> Result<Output, Box<Error>> {
let content = try!(self.file_content(name));
let md5sum = format!("{:x}", md5::compute(content));
Ok(Output::Text(md5sum))
}

fn sha256sum(&self, name: &str) -> Result<Output, Box<Error>> {
let content = try!(self.file_content(name));
let output = sha2::Sha256::digest_str(&content);
Ok(Output::Text(format!("{:x}", output)))
}

fn selinux_label(&self, _: &str) -> Result<Output, Box<Error>> {
unimplemented!()
}

fn size(&self, name: &str) -> Result<Output, Box<Error>> {
let file = try!(fs::File::open(name));
let raw_fd: RawFd = file.as_raw_fd();
let file_stat = try!(nix::sys::stat::fstat(raw_fd));
let size = file_stat.st_size;
Ok(Output::I64(size))
}

fn box_clone(&self) -> Box<InlineProvider> {
Box::new((*self).clone())
}
Expand Down
23 changes: 23 additions & 0 deletions src/provider/mod.rs
Expand Up @@ -17,6 +17,8 @@ pub struct HandleFunc {

pub enum Output {
U32(u32),
I64(i64),
Bool(bool),
Text(String),
}

Expand All @@ -42,4 +44,25 @@ impl Output {
_ => Err(From::from(OutputError)),
}
}

pub fn to_i64(o: Output) -> Result<i64, Box<Error>> {
match o {
Output::I64(i) => Ok(i),
_ => Err(From::from(OutputError)),
}
}

pub fn to_bool(o: Output) -> Result<bool, Box<Error>> {
match o {
Output::Bool(b) => Ok(b),
_ => Err(From::from(OutputError)),
}
}

pub fn to_string(o: Output) -> Result<String, Box<Error>> {
match o {
Output::Text(s) => Ok(s),
_ => Err(From::from(OutputError)),
}
}
}

0 comments on commit 2330ee5

Please sign in to comment.