Skip to content

Commit

Permalink
Added implementation of purge_all for Linux.
Browse files Browse the repository at this point in the history
Also ran rustfmt and created a rustfmt config.
  • Loading branch information
ArturKovacs committed Nov 10, 2019
1 parent 02ffe0b commit a90f9bf
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 85 deletions.
3 changes: 3 additions & 0 deletions rustfmt.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
edition = "2018"
use_try_shorthand = true
use_small_heuristics = "Max"
35 changes: 15 additions & 20 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ mod platform;
/// Error that might happen during a trash operation.
#[derive(Debug)]
pub struct Error {
source: Option<Box<dyn std::error::Error + 'static>>,
source: Option<Box<dyn std::error::Error + 'static>>,
kind: ErrorKind,
}
impl fmt::Display for Error {
Expand All @@ -45,7 +45,7 @@ impl Error {
Error { source: Some(source), kind }
}
pub fn kind_only(kind: ErrorKind) -> Error {
Error { source: None, kind, }
Error { source: None, kind }
}
pub fn kind(&self) -> &ErrorKind {
&self.kind
Expand All @@ -54,7 +54,7 @@ impl Error {
self.source
}
/// Returns `Some` if the source is an `std::io::Error` error. Returns `None` otherwise.
///
///
/// In other words this is a shorthand for
/// `self.source().map(|x| x.downcast_ref::<std::io::Error>())`
pub fn io_error_source(&self) -> Option<&std::io::Error> {
Expand All @@ -65,16 +65,16 @@ impl Error {
///
/// A type that is contained within [`Error`]. It provides information about why the error was
/// produced. Some `ErrorKind` variants may promise that calling `source()`
/// (from `std::error::Error`) on [`Error`] will return a reference to a certain type of
/// (from `std::error::Error`) on [`Error`] will return a reference to a certain type of
/// `std::error::Error`.
///
///
/// For example further information can be extracted from a `CanonicalizePath` error
///
///
/// ```rust
/// use std::error::Error;
/// let result = trash::remove_all(&["non-existing"]);
/// if let Err(err) = result {
/// match err.kind() {
/// match err.kind() {
/// trash::ErrorKind::CanonicalizePath{..} => (), // This is what we expect
/// _ => panic!()
/// };
Expand All @@ -86,7 +86,7 @@ impl Error {
/// assert_eq!(io_kind, std::io::ErrorKind::NotFound);
/// }
/// ```
///
///
/// [`Error`]: struct.Error.html
#[derive(Debug)]
pub enum ErrorKind {
Expand All @@ -97,13 +97,10 @@ pub enum ErrorKind {
///
/// On Windows the `code` will contain the HRESULT that the function returned or that was
/// obtained with `HRESULT_FROM_WIN32(GetLastError())`
PlatformApi {
function_name: &'static str,
code: Option<i32>,
},
PlatformApi { function_name: &'static str, code: Option<i32> },

/// Error while canonicalizing path.
///
///
/// The `source()` function of the `Error` will return a reference to an `std::io::Error`.
CanonicalizePath {
/// Path that triggered the error.
Expand All @@ -115,15 +112,15 @@ pub enum ErrorKind {
/// The reason for this is that it provides vauge information of the circumstances
/// that caused the error. The user would've had to look into the source of the library
/// to understand when this error is produced.
///
///
/// Error while performing the remove operation.
/// `code` contains a raw os error code if accessible.
// Remove {
// code: Option<i32>,
// },

/// Error while converting an OsString to a String.
///
///
/// This error kind will not provide a `source()` but it directly corresponds to the error
/// returned by https://doc.rust-lang.org/std/ffi/struct.OsString.html#method.into_string
ConvertOsString {
Expand All @@ -132,14 +129,12 @@ pub enum ErrorKind {
},

/// Signals an error that occured during some operation on a file or folder.
///
///
/// In some cases the `source()` function of the `Error` will return a reference to an
/// `std::io::Error` but this is not guaranteed.
///
///
/// `path`: The path to the file or folder on which this error occured.
Filesystem {
path: PathBuf,
},
Filesystem { path: PathBuf },
}

/// This struct holds information about a single item within the trash.
Expand Down
107 changes: 74 additions & 33 deletions src/linux.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
//! This implementation will manage the trash according to the Freedesktop Trash specification,
//! version 1.0 found at https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html
//!
//! If the target system uses a different method for handling trashed items and you would be
//! intrested to use this crate on said system, please open an issue on the github page of `trash`.
//! https://github.com/ArturKovacs/trash
//!

use std::collections::HashSet;
use std::env;
use std::ffi::OsString;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::ffi::{CStr, CString};
use std::collections::HashSet;
use std::fs::{File, Permissions, Metadata};
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
use std::process::Command;

use chrono;
use libc;
Expand All @@ -30,7 +38,7 @@ where
let full_paths = paths
.map(|x| {
x.as_ref().canonicalize().map_err(|e| {
Error::new(ErrorKind::CanonicalizePath {original: x.as_ref().into()}, Box::new(e))
Error::new(ErrorKind::CanonicalizePath { original: x.as_ref().into() }, Box::new(e))
})
})
.collect::<Result<Vec<_>, _>>()?;
Expand Down Expand Up @@ -89,12 +97,12 @@ pub fn list() -> Result<Vec<TrashItem>, Error> {
Ok(home_trash) => {
trash_folders.insert(home_trash);
home_error = None;
},
}
Err(e) => {
home_error = Some(e);
}
}

// Get all mountpoints and attemt to find a trash folder in each adding them to the SET of
// trash folders when found one.
let uid = unsafe { libc::getuid() };
Expand Down Expand Up @@ -141,7 +149,9 @@ pub fn list() -> Result<Vec<TrashItem>, Error> {
let file_type = info_entry.file_type().map_err(|e| {
Error::new(ErrorKind::Filesystem { path: info_entry.path() }, Box::new(e))
})?;
if !file_type.is_file() { continue; }
if !file_type.is_file() {
continue;
}
let info_path = info_entry.path();
let info_file = File::open(&info_path).map_err(|e| {
Error::new(ErrorKind::Filesystem { path: info_path.clone() }, Box::new(e))
Expand All @@ -151,7 +161,7 @@ pub fn list() -> Result<Vec<TrashItem>, Error> {
let mut name = None;
let mut original_parent: Option<PathBuf> = None;
let mut time_deleted = None;

let info_reader = BufReader::new(info_file);
// Skip 1 because the first line must be "[Trash Info]"
for line in info_reader.lines().skip(1) {
Expand Down Expand Up @@ -180,10 +190,13 @@ pub fn list() -> Result<Vec<TrashItem>, Error> {
// because the described format does not conform RFC 3339. But oh well, I'll
// just append a 'Z' indicating that the time has no UTC offset and then it will
// be conforming.

let mut rfc3339_format = value.to_owned();
rfc3339_format.push('Z');
let date_time = chrono::DateTime::<chrono::FixedOffset>::parse_from_rfc3339(rfc3339_format.as_str()).unwrap();
let date_time = chrono::DateTime::<chrono::FixedOffset>::parse_from_rfc3339(
rfc3339_format.as_str(),
)
.unwrap();
time_deleted = Some(date_time.timestamp());
}
}
Expand All @@ -202,13 +215,38 @@ pub fn purge_all<I>(items: I) -> Result<(), Error>
where
I: IntoIterator<Item = TrashItem>,
{
Err(Error::kind_only(ErrorKind::PlatformApi {
function_name: "I lied. This is not a platform api error, but a NOT IMPLEMENTED error.",
code: None,
}))
for item in items.into_iter() {
// When purging an item the "in-trash" filename must be parsed from the trashinfo filename
// which is the filename in the `id` field.
let info_file = &item.id;

// A bunch of unwraps here. This is fine because if any of these fail that means
// that either there's a bug in this code or the target system didn't follow
// the specification.
let trash_folder = Path::new(info_file).parent().unwrap().parent().unwrap();
let name_in_trash = Path::new(info_file).file_stem().unwrap();

let file = trash_folder.join("files").join(&name_in_trash);
assert!(file.exists());
if file.is_dir() {
std::fs::remove_dir_all(&file).map_err(|e| {
Error::new(ErrorKind::Filesystem { path: file.into() }, Box::new(e))
})?;
// TODO Update directory size cache if there's one.
} else {
std::fs::remove_file(&file).map_err(|e| {
Error::new(ErrorKind::Filesystem { path: file.into() }, Box::new(e))
})?;
}
std::fs::remove_file(info_file).map_err(|e| {
Error::new(ErrorKind::Filesystem { path: info_file.into() }, Box::new(e))
})?;
}

Ok(())
}

pub fn restore_all<I>(items: I) -> Result<(), Error>
pub fn restore_all<I>(_items: I) -> Result<(), Error>
where
I: IntoIterator<Item = TrashItem>,
{
Expand All @@ -222,15 +260,19 @@ fn parse_uri_path(absolute_file_path: impl AsRef<Path>) -> String {
}

#[derive(Eq, PartialEq)]
enum TrashValidity { Valid, InvalidSymlink, InvalidNotSticky }
enum TrashValidity {
Valid,
InvalidSymlink,
InvalidNotSticky,
}

fn folder_validity(path: impl AsRef<Path>) -> Result<TrashValidity, Error> {
/// Mask for the sticky bit
/// Taken from: http://man7.org/linux/man-pages/man7/inode.7.html
const S_ISVTX: u32 = 0x1000;

let metadata = path.as_ref().symlink_metadata().map_err(|e| {
Error::new(ErrorKind::Filesystem {path: path.as_ref().into()}, Box::new(e))
Error::new(ErrorKind::Filesystem { path: path.as_ref().into() }, Box::new(e))
})?;
if metadata.file_type().is_symlink() {
return Ok(TrashValidity::InvalidSymlink);
Expand All @@ -243,7 +285,7 @@ fn folder_validity(path: impl AsRef<Path>) -> Result<TrashValidity, Error> {
Ok(TrashValidity::Valid)
}

/// Corresponds to the definition of "home_trash" from
/// Corresponds to the definition of "home_trash" from
/// https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html
fn home_trash() -> Result<PathBuf, Error> {
if let Some(data_home) = std::env::var_os("XDG_DATA_HOME") {
Expand All @@ -263,39 +305,38 @@ fn home_trash() -> Result<PathBuf, Error> {

struct MountPoint {
mnt_dir: PathBuf,
mnt_type: String,
mnt_fsname: String,
_mnt_type: String,
_mnt_fsname: String,
}

fn get_mount_points() -> Result<Vec<MountPoint>, Error> {
//let file;
let read_arg = CString::new("r").unwrap();
let mounts_path = CString::new("/proc/mounts").unwrap();
let mut file = unsafe {
libc::fopen(mounts_path.as_c_str().as_ptr(), read_arg.as_c_str().as_ptr())
};
let mut file =
unsafe { libc::fopen(mounts_path.as_c_str().as_ptr(), read_arg.as_c_str().as_ptr()) };
if file == std::ptr::null_mut() {
let mtab_path = CString::new("/etc/mtab").unwrap();
file = unsafe {
libc::fopen(mtab_path.as_c_str().as_ptr(), read_arg.as_c_str().as_ptr())
};
file = unsafe { libc::fopen(mtab_path.as_c_str().as_ptr(), read_arg.as_c_str().as_ptr()) };
}
if file == std::ptr::null_mut() {
// TODO ADD ERROR FOR WHEN NO MONTPOINTS FILE WAS FOUND
panic!();
}
defer!{{ unsafe { libc::fclose(file); } }}
defer! {{ unsafe { libc::fclose(file); } }}
let mut result = Vec::new();
loop {
let mntent = unsafe { libc::getmntent(file) };
if mntent == std::ptr::null_mut() {
break;
}
let mount_point = unsafe{ MountPoint {
mnt_dir: CStr::from_ptr((*mntent).mnt_dir).to_str().unwrap().into(),
mnt_fsname: CStr::from_ptr((*mntent).mnt_fsname).to_str().unwrap().into(),
mnt_type: CStr::from_ptr((*mntent).mnt_type).to_str().unwrap().into(),
}};
let mount_point = unsafe {
MountPoint {
mnt_dir: CStr::from_ptr((*mntent).mnt_dir).to_str().unwrap().into(),
_mnt_fsname: CStr::from_ptr((*mntent).mnt_fsname).to_str().unwrap().into(),
_mnt_type: CStr::from_ptr((*mntent).mnt_type).to_str().unwrap().into(),
}
};
result.push(mount_point);
}
if result.len() == 0 {
Expand Down
18 changes: 6 additions & 12 deletions src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,7 @@ fn create_remove_folder_with_file() {
fn create_multiple_remove_all() {
let file_name_prefix = get_unique_name();
let count: usize = 3;
let paths: Vec<_> = (0..count)
.map(|i| format!("{}#{}", file_name_prefix, i))
.collect();
let paths: Vec<_> = (0..count).map(|i| format!("{}#{}", file_name_prefix, i)).collect();
for path in paths.iter() {
File::create(path).unwrap();
}
Expand All @@ -71,9 +69,8 @@ fn list() {
let file_name_prefix = get_unique_name();
let batches: usize = 2;
let files_per_batch: usize = 3;
let names: Vec<_> = (0..files_per_batch)
.map(|i| format!("{}#{}", file_name_prefix, i))
.collect();
let names: Vec<_> =
(0..files_per_batch).map(|i| format!("{}#{}", file_name_prefix, i)).collect();
for _ in 0..batches {
for path in names.iter() {
File::create(path).unwrap();
Expand Down Expand Up @@ -112,9 +109,8 @@ fn purge() {
let file_name_prefix = get_unique_name();
let batches: usize = 2;
let files_per_batch: usize = 3;
let names: Vec<_> = (0..files_per_batch)
.map(|i| format!("{}#{}", file_name_prefix, i))
.collect();
let names: Vec<_> =
(0..files_per_batch).map(|i| format!("{}#{}", file_name_prefix, i)).collect();
for _ in 0..batches {
for path in names.iter() {
File::create(path).unwrap();
Expand Down Expand Up @@ -142,9 +138,7 @@ fn purge() {
fn restore() {
let file_name_prefix = get_unique_name();
let file_count: usize = 3;
let names: Vec<_> = (0..file_count)
.map(|i| format!("{}#{}", file_name_prefix, i))
.collect();
let names: Vec<_> = (0..file_count).map(|i| format!("{}#{}", file_name_prefix, i)).collect();
for path in names.iter() {
File::create(path).unwrap();
}
Expand Down

0 comments on commit a90f9bf

Please sign in to comment.