2019-06-21 16:15:01 +00:00
|
|
|
use std::io::Read;
|
|
|
|
use std::ffi::{CStr, CString};
|
|
|
|
use std::fs::File;
|
|
|
|
use std::os::unix::io::{FromRawFd, RawFd};
|
|
|
|
|
|
|
|
use failure::*;
|
|
|
|
use libc::{c_char, c_int};
|
2019-08-02 15:02:24 +00:00
|
|
|
use nix::fcntl;
|
|
|
|
use nix::fcntl::{AtFlags, OFlag};
|
2019-06-21 16:15:01 +00:00
|
|
|
use nix::errno::Errno;
|
|
|
|
use nix::NixPath;
|
2019-08-02 15:02:24 +00:00
|
|
|
use nix::sys::stat;
|
2019-06-21 16:15:01 +00:00
|
|
|
use nix::sys::stat::{FileStat, Mode};
|
|
|
|
|
2019-08-02 15:02:24 +00:00
|
|
|
pub const FNM_NOMATCH: c_int = 1;
|
2019-06-21 16:15:01 +00:00
|
|
|
|
|
|
|
extern "C" {
|
|
|
|
fn fnmatch(pattern: *const c_char, string: *const c_char, flags: c_int) -> c_int;
|
|
|
|
}
|
|
|
|
|
2019-08-01 14:23:47 +00:00
|
|
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
2019-06-21 16:15:01 +00:00
|
|
|
pub enum MatchType {
|
|
|
|
None,
|
2019-08-02 15:02:24 +00:00
|
|
|
Positive,
|
|
|
|
Negative,
|
|
|
|
PartialPositive,
|
|
|
|
PartialNegative,
|
2019-06-21 16:15:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
2019-08-02 15:02:24 +00:00
|
|
|
pub struct MatchPattern {
|
2019-06-21 16:15:01 +00:00
|
|
|
pattern: CString,
|
2019-08-02 15:02:24 +00:00
|
|
|
match_positive: bool,
|
2019-06-21 16:15:01 +00:00
|
|
|
match_dir_only: bool,
|
|
|
|
split_pattern: (CString, CString),
|
|
|
|
}
|
|
|
|
|
2019-08-02 15:02:24 +00:00
|
|
|
impl MatchPattern {
|
|
|
|
pub fn from_file<P: ?Sized + NixPath>(
|
|
|
|
parent_fd: RawFd,
|
|
|
|
filename: &P,
|
|
|
|
) -> Result<Option<(Vec<MatchPattern>, Vec<u8>, FileStat)>, Error> {
|
|
|
|
|
|
|
|
let stat = match stat::fstatat(parent_fd, filename, AtFlags::AT_SYMLINK_NOFOLLOW) {
|
2019-06-21 16:15:01 +00:00
|
|
|
Ok(stat) => stat,
|
|
|
|
Err(nix::Error::Sys(Errno::ENOENT)) => return Ok(None),
|
|
|
|
Err(err) => bail!("stat failed - {}", err),
|
|
|
|
};
|
|
|
|
|
2019-08-02 15:02:24 +00:00
|
|
|
let filefd = fcntl::openat(parent_fd, filename, OFlag::O_NOFOLLOW, Mode::empty())?;
|
2019-06-21 16:15:01 +00:00
|
|
|
let mut file = unsafe {
|
|
|
|
File::from_raw_fd(filefd)
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut content_buffer = Vec::new();
|
|
|
|
let _bytes = file.read_to_end(&mut content_buffer)?;
|
|
|
|
|
2019-08-02 15:02:24 +00:00
|
|
|
let mut match_pattern = Vec::new();
|
2019-06-21 16:15:01 +00:00
|
|
|
for line in content_buffer.split(|&c| c == b'\n') {
|
|
|
|
if line.is_empty() {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if let Some(pattern) = Self::from_line(line)? {
|
2019-08-02 15:02:24 +00:00
|
|
|
match_pattern.push(pattern);
|
2019-06-21 16:15:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-02 15:02:24 +00:00
|
|
|
Ok(Some((match_pattern, content_buffer, stat)))
|
2019-06-21 16:15:01 +00:00
|
|
|
}
|
|
|
|
|
2019-08-02 15:02:24 +00:00
|
|
|
pub fn from_line(line: &[u8]) -> Result<Option<MatchPattern>, Error> {
|
2019-06-21 16:15:01 +00:00
|
|
|
let mut input = line;
|
|
|
|
|
|
|
|
if input.starts_with(b"#") {
|
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
|
2019-08-02 15:02:24 +00:00
|
|
|
let match_positive = if input.starts_with(b"!") {
|
2019-06-21 16:15:01 +00:00
|
|
|
// Reduce slice view to exclude "!"
|
|
|
|
input = &input[1..];
|
|
|
|
false
|
|
|
|
} else {
|
|
|
|
true
|
|
|
|
};
|
|
|
|
|
|
|
|
// Paths ending in / match only directory names (no filenames)
|
|
|
|
let match_dir_only = if input.ends_with(b"/") {
|
|
|
|
let len = input.len();
|
|
|
|
input = &input[..len - 1];
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
};
|
|
|
|
|
|
|
|
// Ignore initial slash
|
|
|
|
if input.starts_with(b"/") {
|
|
|
|
input = &input[1..];
|
|
|
|
}
|
|
|
|
|
|
|
|
if input.is_empty() || input == b"." ||
|
|
|
|
input == b".." || input.contains(&b'\0') {
|
|
|
|
bail!("invalid path component encountered");
|
|
|
|
}
|
|
|
|
|
|
|
|
// This will fail if the line contains b"\0"
|
|
|
|
let pattern = CString::new(input)?;
|
|
|
|
let split_pattern = split_at_slash(&pattern);
|
|
|
|
|
2019-08-02 15:02:24 +00:00
|
|
|
Ok(Some(MatchPattern {
|
2019-06-21 16:15:01 +00:00
|
|
|
pattern,
|
2019-08-02 15:02:24 +00:00
|
|
|
match_positive,
|
2019-06-21 16:15:01 +00:00
|
|
|
match_dir_only,
|
|
|
|
split_pattern,
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
2019-08-02 15:02:24 +00:00
|
|
|
pub fn get_front_pattern(&self) -> MatchPattern {
|
2019-06-21 16:15:01 +00:00
|
|
|
let pattern = split_at_slash(&self.split_pattern.0);
|
2019-08-02 15:02:24 +00:00
|
|
|
MatchPattern {
|
2019-06-21 16:15:01 +00:00
|
|
|
pattern: self.split_pattern.0.clone(),
|
2019-08-02 15:02:24 +00:00
|
|
|
match_positive: self.match_positive,
|
2019-06-21 16:15:01 +00:00
|
|
|
match_dir_only: self.match_dir_only,
|
|
|
|
split_pattern: pattern,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-02 15:02:24 +00:00
|
|
|
pub fn get_rest_pattern(&self) -> MatchPattern {
|
2019-06-21 16:15:01 +00:00
|
|
|
let pattern = split_at_slash(&self.split_pattern.1);
|
2019-08-02 15:02:24 +00:00
|
|
|
MatchPattern {
|
2019-06-21 16:15:01 +00:00
|
|
|
pattern: self.split_pattern.1.clone(),
|
2019-08-02 15:02:24 +00:00
|
|
|
match_positive: self.match_positive,
|
2019-06-21 16:15:01 +00:00
|
|
|
match_dir_only: self.match_dir_only,
|
|
|
|
split_pattern: pattern,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn dump(&self) {
|
2019-08-02 15:02:24 +00:00
|
|
|
match (self.match_positive, self.match_dir_only) {
|
2019-06-21 16:15:01 +00:00
|
|
|
(true, true) => println!("{:#?}/", self.pattern),
|
|
|
|
(true, false) => println!("{:#?}", self.pattern),
|
|
|
|
(false, true) => println!("!{:#?}/", self.pattern),
|
|
|
|
(false, false) => println!("!{:#?}", self.pattern),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-05 12:28:03 +00:00
|
|
|
pub fn matches_filename(&self, filename: &CStr, is_dir: bool) -> Result<MatchType, Error> {
|
2019-06-21 16:15:01 +00:00
|
|
|
let mut res = MatchType::None;
|
|
|
|
let (front, _) = &self.split_pattern;
|
|
|
|
|
|
|
|
let fnmatch_res = unsafe {
|
2019-08-02 15:02:24 +00:00
|
|
|
let front_ptr = front.as_ptr() as *const libc::c_char;
|
|
|
|
let filename_ptr = filename.as_ptr() as *const libc::c_char;
|
|
|
|
fnmatch(front_ptr, filename_ptr , 0)
|
2019-06-21 16:15:01 +00:00
|
|
|
};
|
2019-08-05 12:28:03 +00:00
|
|
|
if fnmatch_res < 0 {
|
|
|
|
bail!("error in fnmatch inside of MatchPattern");
|
|
|
|
}
|
2019-06-21 16:15:01 +00:00
|
|
|
if fnmatch_res == 0 {
|
2019-08-02 15:02:24 +00:00
|
|
|
res = if self.match_positive {
|
|
|
|
MatchType::PartialPositive
|
2019-06-21 16:15:01 +00:00
|
|
|
} else {
|
2019-08-02 15:02:24 +00:00
|
|
|
MatchType::PartialNegative
|
2019-06-21 16:15:01 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
let full = if self.pattern.to_bytes().starts_with(b"**/") {
|
|
|
|
CString::new(&self.pattern.to_bytes()[3..]).unwrap()
|
|
|
|
} else {
|
|
|
|
CString::new(&self.pattern.to_bytes()[..]).unwrap()
|
|
|
|
};
|
|
|
|
let fnmatch_res = unsafe {
|
2019-08-02 15:02:24 +00:00
|
|
|
let full_ptr = full.as_ptr() as *const libc::c_char;
|
|
|
|
let filename_ptr = filename.as_ptr() as *const libc::c_char;
|
|
|
|
fnmatch(full_ptr, filename_ptr, 0)
|
2019-06-21 16:15:01 +00:00
|
|
|
};
|
2019-08-05 12:28:03 +00:00
|
|
|
if fnmatch_res < 0 {
|
|
|
|
bail!("error in fnmatch inside of MatchPattern");
|
|
|
|
}
|
2019-06-21 16:15:01 +00:00
|
|
|
if fnmatch_res == 0 {
|
2019-08-02 15:02:24 +00:00
|
|
|
res = if self.match_positive {
|
|
|
|
MatchType::Positive
|
2019-06-21 16:15:01 +00:00
|
|
|
} else {
|
2019-08-02 15:02:24 +00:00
|
|
|
MatchType::Negative
|
2019-06-21 16:15:01 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if !is_dir && self.match_dir_only {
|
|
|
|
res = MatchType::None;
|
|
|
|
}
|
|
|
|
|
2019-08-02 15:02:24 +00:00
|
|
|
if !is_dir && (res == MatchType::PartialPositive || res == MatchType::PartialNegative) {
|
2019-07-16 11:19:47 +00:00
|
|
|
res = MatchType::None;
|
|
|
|
}
|
|
|
|
|
2019-08-05 12:28:03 +00:00
|
|
|
Ok(res)
|
2019-06-21 16:15:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn split_at_slash(match_pattern: &CStr) -> (CString, CString) {
|
|
|
|
let match_pattern = match_pattern.to_bytes();
|
|
|
|
|
|
|
|
let pattern = if match_pattern.starts_with(b"./") {
|
|
|
|
&match_pattern[2..]
|
|
|
|
} else {
|
|
|
|
match_pattern
|
|
|
|
};
|
|
|
|
|
|
|
|
let (mut front, mut rest) = match pattern.iter().position(|&c| c == b'/') {
|
|
|
|
Some(ind) => {
|
|
|
|
let (front, rest) = pattern.split_at(ind);
|
|
|
|
(front, &rest[1..])
|
|
|
|
},
|
|
|
|
None => (pattern, &pattern[0..0]),
|
|
|
|
};
|
|
|
|
// '**' is treated such that it maches any directory
|
|
|
|
if front == b"**" {
|
|
|
|
front = b"*";
|
|
|
|
rest = pattern;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pattern where valid CStrings before, so it is safe to unwrap the Result
|
|
|
|
let front_pattern = CString::new(front).unwrap();
|
|
|
|
let rest_pattern = CString::new(rest).unwrap();
|
|
|
|
(front_pattern, rest_pattern)
|
|
|
|
}
|