Skip to content

Commit

Permalink
Properly get size of Unix devices
Browse files Browse the repository at this point in the history
Closes #66
  • Loading branch information
nabijaczleweli committed May 17, 2018
1 parent c2ccea4 commit fd843a8
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 32 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ default-features = false
version = "0.5"
features = ["hyper-native-tls"]

[target.'cfg(not(target_os = "windows"))'.dependencies.libc]
version = "0.2"

[build-dependencies]
embed-resource = "1.1"

Expand Down
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ extern crate serde;
#[macro_use]
extern crate clap;
extern crate iron;
#[cfg(not(target_os = "windows"))]
extern crate libc;
extern crate time;
extern crate url;
extern crate md6;
Expand Down
58 changes: 32 additions & 26 deletions src/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ use trivial_colours::{Reset as CReset, Colour as C};
use std::process::{ExitStatus, Command, Child, Stdio};
use rfsapi::{RawFsApiHeader, FilesetData, RawFileData};
use iron::{headers, status, method, mime, IronResult, Listening, Response, TypeMap, Request, Handler, Iron};
use self::super::util::{url_path, file_hash, is_symlink, encode_str, encode_file, hash_string, html_response, file_binary, client_mobile, percent_decode,
file_icon_suffix, is_actually_file, response_encoding, detect_file_as_dir, encoding_extension, file_time_modified, get_raw_fs_metadata,
human_readable_size, USER_AGENT, ERROR_HTML, INDEX_EXTENSIONS, MIN_ENCODING_GAIN, MAX_ENCODING_SIZE, MIN_ENCODING_SIZE,
DIRECTORY_LISTING_HTML, MOBILE_DIRECTORY_LISTING_HTML, BLACKLISTED_ENCODING_EXTENSIONS};
use self::super::util::{url_path, file_hash, is_symlink, encode_str, encode_file, file_length, hash_string, html_response, file_binary, client_mobile,
percent_decode, file_icon_suffix, is_actually_file, response_encoding, detect_file_as_dir, encoding_extension, file_time_modified,
get_raw_fs_metadata, human_readable_size, USER_AGENT, ERROR_HTML, INDEX_EXTENSIONS, MIN_ENCODING_GAIN, MAX_ENCODING_SIZE,
MIN_ENCODING_SIZE, DIRECTORY_LISTING_HTML, MOBILE_DIRECTORY_LISTING_HTML, BLACKLISTED_ENCODING_EXTENSIONS};


macro_rules! log {
Expand Down Expand Up @@ -201,7 +201,7 @@ impl HttpHandler {
match range {
headers::Range::Bytes(ref brs) => {
if brs.len() == 1 {
let flen = req_p.metadata().expect("Failed to get requested file metadata").len();
let flen = file_length(&req_p.metadata().expect("Failed to get requested file metadata"), &req_p);
match brs[0] {
// Cases where from is bigger than to are filtered out by iron so can never happen
headers::ByteRangeSpec::FromTo(from, to) => self.handle_get_file_closed_range(req, req_p, from, to),
Expand Down Expand Up @@ -251,7 +251,7 @@ impl HttpHandler {
Header(headers::LastModified(headers::HttpDate(file_time_modified(&req_p)))),
Header(headers::ContentRange(headers::ContentRangeSpec::Bytes {
range: Some((from, to)),
instance_length: Some(f.metadata().expect("Failed to get requested file metadata").len()),
instance_length: Some(file_length(&f.metadata().expect("Failed to get requested file metadata"), &req_p)),
})),
Header(headers::AcceptRanges(vec![headers::RangeUnit::Bytes]))),
buf,
Expand All @@ -270,7 +270,7 @@ impl HttpHandler {
from,
mime_type);

let flen = req_p.metadata().expect("Failed to get requested file metadata").len();
let flen = file_length(&req_p.metadata().expect("Failed to get requested file metadata"), &req_p);
self.handle_get_file_opened_range(req_p, SeekFrom::Start(from), from, flen - from, mime_type)
}

Expand All @@ -286,13 +286,13 @@ impl HttpHandler {
req_p.display(),
mime_type);

let flen = req_p.metadata().expect("Failed to get requested file metadata").len();
let flen = file_length(&req_p.metadata().expect("Failed to get requested file metadata"), &req_p);
self.handle_get_file_opened_range(req_p, SeekFrom::End(-(from as i64)), flen - from, from, mime_type)
}

fn handle_get_file_opened_range(&self, req_p: PathBuf, s: SeekFrom, b_from: u64, clen: u64, mt: Mime) -> IronResult<Response> {
let mut f = File::open(&req_p).expect("Failed to open requested file");
let flen = f.metadata().expect("Failed to get requested file metadata").len();
let flen = file_length(&f.metadata().expect("Failed to get requested file metadata"), &req_p);
f.seek(s).expect("Failed to seek requested file");

Ok(Response::with((status::PartialContent,
Expand Down Expand Up @@ -335,7 +335,7 @@ impl HttpHandler {
Header(headers::LastModified(headers::HttpDate(file_time_modified(&req_p)))),
Header(headers::ContentRange(headers::ContentRangeSpec::Bytes {
range: Some((from, to)),
instance_length: Some(req_p.metadata().expect("Failed to get requested file metadata").len()),
instance_length: Some(file_length(&req_p.metadata().expect("Failed to get requested file metadata"), &req_p)),
})),
Header(headers::AcceptRanges(vec![headers::RangeUnit::Bytes])),
mime_type)))
Expand All @@ -352,16 +352,18 @@ impl HttpHandler {
req_p.display(),
mime_type);

let flen = req_p.metadata().expect("Failed to get requested file metadata").len();
let metadata = req_p.metadata().expect("Failed to get requested file metadata");
let flen = file_length(&metadata, &req_p);
if self.encoded_temp_dir.is_some() && flen > MIN_ENCODING_SIZE && flen < MAX_ENCODING_SIZE &&
req_p.extension().and_then(|s| s.to_str()).map(|s| !BLACKLISTED_ENCODING_EXTENSIONS.contains(&UniCase::new(s))).unwrap_or(true) {
self.handle_get_file_encoded(req, req_p, mime_type)
} else {
Ok(Response::with((status::Ok,
Header(headers::Server(USER_AGENT.to_string())),
Header(headers::LastModified(headers::HttpDate(file_time_modified(&req_p)))),
Header(headers::AcceptRanges(vec![headers::RangeUnit::Bytes])),
req_p,
(Header(headers::Server(USER_AGENT.to_string())),
Header(headers::LastModified(headers::HttpDate(file_time_modified(&req_p)))),
Header(headers::AcceptRanges(vec![headers::RangeUnit::Bytes]))),
req_p.as_path(),
Header(headers::ContentLength(file_length(&metadata, &req_p))),
mime_type)))
}
}
Expand All @@ -377,8 +379,9 @@ impl HttpHandler {
log!("{} encoded as {} for {:.1}% ratio (cached)",
iter::repeat(' ').take(req.remote_addr.to_string().len()).collect::<String>(),
encoding,
((req_p.metadata().expect("Failed to get requested file metadata").len() as f64) /
(resp_p.metadata().expect("Failed to get encoded file metadata").len() as f64)) * 100f64);
((file_length(&req_p.metadata().expect("Failed to get requested file metadata"), &req_p) as f64) /
(file_length(&resp_p.metadata().expect("Failed to get encoded file metadata"), &resp_p) as f64)) *
100f64);

return Ok(Response::with((status::Ok,
Header(headers::Server(USER_AGENT.to_string())),
Expand Down Expand Up @@ -408,8 +411,8 @@ impl HttpHandler {
};

if encode_file(&req_p, &resp_p, &encoding) {
let gain = (req_p.metadata().expect("Failed to get requested file metadata").len() as f64) /
(resp_p.metadata().expect("Failed to get encoded file metadata").len() as f64);
let gain = (file_length(&req_p.metadata().expect("Failed to get requested file metadata"), &req_p) as f64) /
(file_length(&resp_p.metadata().expect("Failed to get encoded file metadata"), &resp_p) as f64);
if gain < MIN_ENCODING_GAIN {
let mut cache = self.cache_fs.write().expect("Filesystem cache write lock poisoned");
cache.insert(cache_key, (req_p.clone(), false));
Expand Down Expand Up @@ -438,10 +441,11 @@ impl HttpHandler {
}

Ok(Response::with((status::Ok,
Header(headers::Server(USER_AGENT.to_string())),
Header(headers::LastModified(headers::HttpDate(file_time_modified(&req_p)))),
Header(headers::AcceptRanges(vec![headers::RangeUnit::Bytes])),
(Header(headers::Server(USER_AGENT.to_string())),
Header(headers::LastModified(headers::HttpDate(file_time_modified(&req_p)))),
Header(headers::AcceptRanges(vec![headers::RangeUnit::Bytes]))),
req_p,
Header(headers::ContentLength(file_length(&metadata, &req_p))),
mt)))
}

Expand Down Expand Up @@ -546,7 +550,8 @@ impl HttpHandler {
.map(|p| p.expect("Failed to iterate over requested directory"))
.filter(|f| self.follow_symlinks || !is_symlink(f.path()))
.sorted_by(|lhs, rhs| {
(is_actually_file(&lhs.file_type().expect("Failed to get file type")), lhs.file_name().to_str().expect("Failed to get file name").to_lowercase())
(is_actually_file(&lhs.file_type().expect("Failed to get file type")),
lhs.file_name().to_str().expect("Failed to get file name").to_lowercase())
.cmp(&(is_actually_file(&rhs.file_type().expect("Failed to get file type")),
rhs.file_name().to_str().expect("Failed to get file name").to_lowercase()))
})
Expand All @@ -567,7 +572,7 @@ impl HttpHandler {
file_time_modified(&path).strftime("%F %T").unwrap(),
if is_file { "<span class=\"size\">" } else { "" },
if is_file {
human_readable_size(f.metadata().expect("Failed to get file metadata").len())
human_readable_size(file_length(&f.metadata().expect("Failed to get requested file metadata"), &path))
} else {
String::new()
},
Expand Down Expand Up @@ -622,15 +627,16 @@ impl HttpHandler {
.map(|p| p.expect("Failed to iterate over requested directory"))
.filter(|f| self.follow_symlinks || !is_symlink(f.path()))
.sorted_by(|lhs, rhs| {
(is_actually_file(&lhs.file_type().expect("Failed to get file type")), lhs.file_name().to_str().expect("Failed to get file name").to_lowercase())
(is_actually_file(&lhs.file_type().expect("Failed to get file type")),
lhs.file_name().to_str().expect("Failed to get file name").to_lowercase())
.cmp(&(is_actually_file(&rhs.file_type().expect("Failed to get file type")),
rhs.file_name().to_str().expect("Failed to get file name").to_lowercase()))
})
.fold("".to_string(), |cur, f| {
let is_file = is_actually_file(&f.file_type().expect("Failed to get file type"));
let fname = f.file_name().into_string().expect("Failed to get file name");
let path = f.path();
let len = f.metadata().expect("Failed to get file metadata").len();
let len = file_length(&f.metadata().expect("Failed to get requested file metadata"), &path);

format!("{}<tr><td><a href=\"{path}{fname}\" id=\"{}\" class=\"{}{}_icon\"></a></td> \
<td><a href=\"{path}{fname}\">{}{}</a></td> <td><a href=\"{path}{fname}\" class=\"datetime\">{}</a></td> \
Expand Down
7 changes: 5 additions & 2 deletions src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use std::io::{BufReader, BufRead};
use base64::display::Base64Display;
use mime_guess::{guess_mime_type_opt, get_mime_type_str};

pub use self::os::*;
pub use self::content_encoding::*;


Expand Down Expand Up @@ -117,7 +118,9 @@ pub fn uppercase_first(s: &str) -> String {
/// ```
pub fn file_binary<P: AsRef<Path>>(path: P) -> bool {
let path = path.as_ref();
path.metadata().map(|m| os::is_device(&m.file_type()) || File::open(path).and_then(|f| BufReader::new(f).read_line(&mut String::new())).is_err()).unwrap_or(true)
path.metadata()
.map(|m| is_device(&m.file_type()) || File::open(path).and_then(|f| BufReader::new(f).read_line(&mut String::new())).is_err())
.unwrap_or(true)
}

/// Fill out an HTML template.
Expand Down Expand Up @@ -213,7 +216,7 @@ pub fn is_symlink<P: AsRef<Path>>(p: P) -> bool {

/// Check if a path refers to a file in a way that includes Unix devices and Windows symlinks.
pub fn is_actually_file(tp: &FileType) -> bool {
tp.is_file() || os::is_device(tp)
tp.is_file() || is_device(tp)
}

/// Construct string representing a human-readable size.
Expand Down
4 changes: 2 additions & 2 deletions src/util/os/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ mod windows;
mod non_windows;

#[cfg(target_os = "windows")]
pub use self::windows::is_device;
pub use self::windows::{file_length, is_device};
#[cfg(not(target_os = "windows"))]
pub use self::non_windows::is_device;
pub use self::non_windows::{file_length, is_device};
34 changes: 33 additions & 1 deletion src/util/os/non_windows.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,40 @@
use std::fs::FileType;
use libc::{O_RDONLY, c_ulong, close, ioctl, open};
use std::os::unix::fs::FileTypeExt;
use std::os::unix::ffi::OsStrExt;
use std::fs::{FileType, Metadata};
use std::ffi::CString;
use std::path::Path;


/// Return `device size / 512` (`long *` arg)
///
/// Extracted from my armv6l machine
///
/// Would be probably a good idea to get this at build time from a build script
const BLKGETSIZE: c_ulong = (0x12 << 8) | 96;


/// OS-specific check for fileness
pub fn is_device(tp: &FileType) -> bool {
tp.is_block_device() || tp.is_char_device() || tp.is_fifo() || tp.is_socket()
}

/// Check file length responsibly
pub fn file_length<P: AsRef<Path>>(meta: &Metadata, path: &P) -> u64 {
if is_device(&meta.file_type()) {
let mut block_count: c_ulong = 0;

let path_c = CString::new(path.as_ref().as_os_str().as_bytes()).unwrap();
let dev_file = unsafe { open(path_c.as_ptr(), O_RDONLY) };
if dev_file >= 0 {
let ok = unsafe { ioctl(dev_file, BLKGETSIZE, &mut block_count as *mut c_ulong) } == 0;
unsafe { close(dev_file) };

if ok {
return block_count as u64 * 512;
}
}
}

meta.len()
}
8 changes: 7 additions & 1 deletion src/util/os/windows.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
use std::fs::FileType;
use std::fs::{FileType, Metadata};
use std::path::Path;


/// OS-specific check for fileness
pub fn is_device(_: &FileType) -> bool {
false
}

/// Check file length responsibly
pub fn file_length<P: AsRef<Path>>(meta: &Metadata, _: &P) -> u64 {
meta.len()
}

0 comments on commit fd843a8

Please sign in to comment.