diff --git a/Cargo.lock b/Cargo.lock index cb03ecb..6983054 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -851,6 +851,11 @@ name = "libc" version = "0.2.57" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "libc" +version = "0.2.59" +source = "git+https://github.com/rust-lang/libc#54b03c53fb8e70bcce87dc26d8795c995e41061f" + [[package]] name = "libpasta" version = "0.1.0" @@ -1064,6 +1069,7 @@ dependencies = [ "lru-cache 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "moonfire-base 0.0.1", "mylog 0.1.0 (git+https://github.com/scottlamb/mylog)", + "nix 0.14.1 (git+https://github.com/scottlamb/nix?branch=pr-renameat)", "odds 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "openssl 0.10.23 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1114,6 +1120,7 @@ dependencies = [ "moonfire-db 0.0.1", "moonfire-ffmpeg 0.0.1", "mylog 0.1.0 (git+https://github.com/scottlamb/mylog)", + "nix 0.14.1 (git+https://github.com/scottlamb/nix?branch=pr-renameat)", "openssl 0.10.23 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "protobuf 3.0.0-pre (git+https://github.com/stepancheg/rust-protobuf)", @@ -1181,6 +1188,18 @@ dependencies = [ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "nix" +version = "0.14.1" +source = "git+https://github.com/scottlamb/nix?branch=pr-renameat#c34f87cf133a27d75476b9f4e5289bf065f20c9b" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.59 (git+https://github.com/rust-lang/libc)", + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "nodrop" version = "0.1.13" @@ -2511,6 +2530,11 @@ name = "version_check" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "want" version = "0.0.6" @@ -2673,6 +2697,7 @@ dependencies = [ "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" "checksum libc 0.2.57 (registry+https://github.com/rust-lang/crates.io-index)" = "a844cabbd5a77e60403a58af576f0a1baa83c3dd2670be63e615bd24fc58b82d" +"checksum libc 0.2.59 (git+https://github.com/rust-lang/libc)" = "" "checksum libpasta 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "31a3bd8a4714af6f88d2cd850b016ad4567770f88fa3cf0571ec197591362f99" "checksum libsqlite3-sys 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e310445ab028c374b9efaaed4b7a52a14e3b8ad5a1351b4bbd46dec03ffce717" "checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83" @@ -2697,6 +2722,7 @@ dependencies = [ "checksum native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e" "checksum ncurses 5.99.0 (registry+https://github.com/rust-lang/crates.io-index)" = "15699bee2f37e9f8828c7b35b2bc70d13846db453f2d507713b758fabe536b82" "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" +"checksum nix 0.14.1 (git+https://github.com/scottlamb/nix?branch=pr-renameat)" = "" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" "checksum num 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cf4825417e1e1406b3782a8ce92f4d53f26ec055e3622e1881ca8e9f5f9e08db" "checksum num-complex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "107b9be86cd2481930688277b675b0114578227f034674726605b8a482d8baf8" @@ -2846,6 +2872,7 @@ dependencies = [ "checksum vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "def296d3eb3b12371b2c7d0e83bfe1403e4db2d7a0bba324a12b21c4ee13143d" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" +"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum want 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "797464475f30ddb8830cc529aaaae648d581f99e2036a928877dfde027ddf6b3" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" diff --git a/Cargo.toml b/Cargo.toml index 9a327d8..8be52d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ log = { version = "0.4", features = ["release_max_level_info"] } memchr = "2.0.2" memmap = "0.7" mylog = { git = "https://github.com/scottlamb/mylog" } +nix = { git = "https://github.com/scottlamb/nix", branch = "pr-renameat" } openssl = "0.10" parking_lot = { version = "0.8", features = [] } protobuf = { git = "https://github.com/stepancheg/rust-protobuf" } diff --git a/db/Cargo.toml b/db/Cargo.toml index ac602e8..64aa00b 100644 --- a/db/Cargo.toml +++ b/db/Cargo.toml @@ -24,6 +24,7 @@ libpasta = "0.1.0-rc2" log = "0.4" lru-cache = "0.1" mylog = { git = "https://github.com/scottlamb/mylog" } +nix = { git = "https://github.com/scottlamb/nix", branch = "pr-renameat" } odds = { version = "0.3.1", features = ["std-vec"] } openssl = "0.10" parking_lot = { version = "0.8", features = [] } diff --git a/db/db.rs b/db/db.rs index 0fffb9b..129660b 100644 --- a/db/db.rs +++ b/db/db.rs @@ -116,12 +116,7 @@ struct VideoIndex(Box<[u8]>); impl rusqlite::types::FromSql for VideoIndex { fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult { - let blob = value.as_blob()?; - let len = blob.len(); - let mut v = Vec::with_capacity(len); - unsafe { v.set_len(len) }; - v.copy_from_slice(blob); - Ok(VideoIndex(v.into_boxed_slice())) + Ok(VideoIndex(value.as_blob()?.to_vec().into_boxed_slice())) } } diff --git a/db/dir.rs b/db/dir.rs index fb8fd96..7dd2452 100644 --- a/db/dir.rs +++ b/db/dir.rs @@ -34,16 +34,16 @@ use crate::coding; use crate::db::CompositeId; +use crate::schema; use cstr::*; use failure::{Error, Fail, bail, format_err}; -use libc::c_char; use log::warn; use protobuf::Message; -use crate::schema; -use std::ffi; +use nix::{NixPath, fcntl::{AtFlags, FlockArg, OFlag}, sys::stat::Mode}; +use nix::sys::statvfs::Statvfs; +use std::ffi::{CStr, CString}; use std::fs; -use std::io::{self, Read, Write}; -use std::mem; +use std::io::{Read, Write}; use std::os::unix::ffi::OsStrExt; use std::os::unix::io::FromRawFd; use std::sync::Arc; @@ -66,14 +66,37 @@ pub struct SampleFileDir { pub(crate) fd: Fd, } +pub(crate) struct CompositeIdPath([u8; 17]); + +impl CompositeIdPath { + pub(crate) fn from(id: CompositeId) -> Self { + let mut buf = [0u8; 17]; + write!(&mut buf[..16], "{:016x}", id.0).expect("can't format id to pathname buf"); + CompositeIdPath(buf) + } +} + +impl NixPath for CompositeIdPath { + fn len(&self) -> usize { 16 } + + fn with_nix_path(&self, f: F) -> Result + where F: FnOnce(&CStr) -> T { + let p = CStr::from_bytes_with_nul(&self.0[..]).expect("no interior nuls"); + Ok(f(p)) + } +} + /// A file descriptor associated with a directory (not necessarily the sample file dir). #[derive(Debug)] -pub struct Fd(libc::c_int); +pub struct Fd(std::os::unix::io::RawFd); + +impl std::os::unix::io::AsRawFd for Fd { + fn as_raw_fd(&self) -> std::os::unix::io::RawFd { self.0 } +} impl Drop for Fd { fn drop(&mut self) { - if unsafe { libc::close(self.0) } < 0 { - let e = io::Error::last_os_error(); + if let Err(e) = nix::unistd::close(self.0) { warn!("Unable to close sample file dir: {}", e); } } @@ -81,76 +104,46 @@ impl Drop for Fd { impl Fd { /// Opens the given path as a directory. - pub fn open(path: &str, mkdir: bool) -> Result { - let cstring = ffi::CString::new(path) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?; - if mkdir && unsafe { libc::mkdir(cstring.as_ptr(), 0o700) } != 0 { - let e = io::Error::last_os_error(); - if e.kind() != io::ErrorKind::AlreadyExists { - return Err(e.into()); + pub fn open(path: &str, mkdir: bool) -> Result { + let cstring = CString::new(path).map_err(|_| nix::Error::InvalidPath)?; + if mkdir { + match nix::unistd::mkdir(cstring.as_c_str(), nix::sys::stat::Mode::S_IRWXU) { + Ok(()) | Err(nix::Error::Sys(nix::errno::Errno::EEXIST)) => {}, + Err(e) => return Err(e), } } - let fd = unsafe { libc::open(cstring.as_ptr(), libc::O_DIRECTORY | libc::O_RDONLY, 0) }; - if fd < 0 { - return Err(io::Error::last_os_error().into()); - } + let fd = nix::fcntl::open(cstring.as_c_str(), OFlag::O_DIRECTORY | OFlag::O_RDONLY, + Mode::empty())?; Ok(Fd(fd)) } - pub(crate) fn sync(&self) -> Result<(), io::Error> { - let res = unsafe { libc::fsync(self.0) }; - if res < 0 { - return Err(io::Error::last_os_error()) - } - Ok(()) + pub(crate) fn sync(&self) -> Result<(), nix::Error> { + nix::unistd::fsync(self.0) } /// Opens a sample file within this directory with the given flags and (if creating) mode. - pub(crate) unsafe fn openat(&self, p: *const c_char, flags: libc::c_int, mode: libc::c_int) - -> Result { - let fd = libc::openat(self.0, p, flags, mode); - if fd < 0 { - return Err(io::Error::last_os_error()) - } - Ok(fs::File::from_raw_fd(fd)) + pub(crate) fn openat(&self, p: &P, oflag: OFlag, mode: Mode) + -> Result { + let fd = nix::fcntl::openat(self.0, p, oflag, mode)?; + Ok(unsafe { fs::File::from_raw_fd(fd) }) } /// Locks the directory with the specified `flock` operation. - pub fn lock(&self, operation: libc::c_int) -> Result<(), io::Error> { - let ret = unsafe { libc::flock(self.0, operation) }; - if ret < 0 { - return Err(io::Error::last_os_error().into()); - } - Ok(()) + pub fn lock(&self, arg: FlockArg) -> Result<(), nix::Error> { + nix::fcntl::flock(self.0, arg) } - pub fn statfs(&self) -> Result { - unsafe { - let mut stat: libc::statvfs = mem::zeroed(); - if libc::fstatvfs(self.0, &mut stat) < 0 { - return Err(io::Error::last_os_error()) - } - Ok(stat) - } + pub fn statfs(&self) -> Result { + nix::sys::statvfs::fstatvfs(self) } } -pub(crate) unsafe fn renameat(from_fd: &Fd, from_path: *const c_char, - to_fd: &Fd, to_path: *const c_char) -> Result<(), io::Error> { - let result = libc::renameat(from_fd.0, from_path, to_fd.0, to_path); - if result < 0 { - return Err(io::Error::last_os_error()) - } - Ok(()) -} - /// Reads `dir`'s metadata. If none is found, returns an empty proto. pub(crate) fn read_meta(dir: &Fd) -> Result { let mut meta = schema::DirMeta::default(); - let p = cstr!("meta"); - let mut f = match unsafe { dir.openat(p.as_ptr(), libc::O_RDONLY, 0) } { + let mut f = match dir.openat(cstr!("meta"), OFlag::O_RDONLY, Mode::empty()) { Err(e) => { - if e.kind() == ::std::io::ErrorKind::NotFound { + if e == nix::Error::Sys(nix::errno::Errno::ENOENT) { return Ok(meta); } return Err(e.into()); @@ -179,9 +172,8 @@ pub(crate) fn write_meta(dir: &Fd, meta: &schema::DirMeta) -> Result<(), Error> data.len(), FIXED_DIR_META_LEN); } data.resize(FIXED_DIR_META_LEN, 0); // pad to required length. - let path = cstr!("meta"); - let mut f = unsafe { dir.openat(path.as_ptr(), - libc::O_CREAT | libc::O_WRONLY, 0o600)? }; + let mut f = dir.openat(cstr!("meta"), OFlag::O_CREAT | OFlag::O_WRONLY, + Mode::S_IRUSR | Mode::S_IWUSR)?; let stat = f.metadata()?; if stat.len() == 0 { // Need to sync not only the data but also the file metadata and dirent. @@ -207,7 +199,11 @@ impl SampleFileDir { -> Result, Error> { let read_write = db_meta.in_progress_open.is_some(); let s = SampleFileDir::open_self(path, false)?; - s.fd.lock(if read_write { libc::LOCK_EX } else { libc::LOCK_SH } | libc::LOCK_NB)?; + s.fd.lock(if read_write { + FlockArg::LockExclusiveNonblock + } else { + FlockArg::LockSharedNonblock + })?; let dir_meta = read_meta(&s.fd)?; if !SampleFileDir::consistent(db_meta, &dir_meta) { let serialized = @@ -243,7 +239,7 @@ impl SampleFileDir { pub(crate) fn create(path: &str, db_meta: &schema::DirMeta) -> Result, Error> { let s = SampleFileDir::open_self(path, true)?; - s.fd.lock(libc::LOCK_EX | libc::LOCK_NB)?; + s.fd.lock(FlockArg::LockExclusiveNonblock)?; let old_meta = read_meta(&s.fd)?; // Verify metadata. We only care that it hasn't been completely opened. @@ -280,43 +276,31 @@ impl SampleFileDir { } /// Opens the given sample file for reading. - pub fn open_file(&self, composite_id: CompositeId) -> Result { - let p = SampleFileDir::get_rel_pathname(composite_id); - unsafe { self.fd.openat(p.as_ptr(), libc::O_RDONLY, 0) } + pub fn open_file(&self, composite_id: CompositeId) -> Result { + let p = CompositeIdPath::from(composite_id); + self.fd.openat(&p, OFlag::O_RDONLY, Mode::empty()) } - pub fn create_file(&self, composite_id: CompositeId) -> Result { - let p = SampleFileDir::get_rel_pathname(composite_id); - unsafe { self.fd.openat(p.as_ptr(), libc::O_WRONLY | libc::O_EXCL | libc::O_CREAT, 0o600) } + pub fn create_file(&self, composite_id: CompositeId) -> Result { + let p = CompositeIdPath::from(composite_id); + self.fd.openat(&p, OFlag::O_WRONLY | OFlag::O_EXCL | OFlag::O_CREAT, + Mode::S_IRUSR | Mode::S_IWUSR) } pub(crate) fn write_meta(&self, meta: &schema::DirMeta) -> Result<(), Error> { write_meta(&self.fd, meta) } - pub fn statfs(&self) -> Result { self.fd.statfs() } - - /// Gets a pathname for a sample file suitable for passing to open or unlink. - fn get_rel_pathname(id: CompositeId) -> [libc::c_char; 17] { - let mut buf = [0u8; 17]; - write!(&mut buf[..16], "{:016x}", id.0).expect("can't format id to pathname buf"); - - // libc::c_char seems to be i8 on some platforms (Linux/arm) and u8 on others (Linux/amd64). - unsafe { mem::transmute::<[u8; 17], [libc::c_char; 17]>(buf) } - } + pub fn statfs(&self) -> Result { self.fd.statfs() } /// Unlinks the given sample file within this directory. - pub(crate) fn unlink_file(&self, id: CompositeId) -> Result<(), io::Error> { - let p = SampleFileDir::get_rel_pathname(id); - let res = unsafe { libc::unlinkat(self.fd.0, p.as_ptr(), 0) }; - if res < 0 { - return Err(io::Error::last_os_error()) - } - Ok(()) + pub(crate) fn unlink_file(&self, id: CompositeId) -> Result<(), nix::Error> { + let p = CompositeIdPath::from(id); + nix::unistd::unlinkat(self.fd.0, &p, AtFlags::empty()) } /// Syncs the directory itself. - pub(crate) fn sync(&self) -> Result<(), io::Error> { + pub(crate) fn sync(&self) -> Result<(), nix::Error> { self.fd.sync() } } diff --git a/db/upgrade/v1_to_v2.rs b/db/upgrade/v1_to_v2.rs index b509026..838c7f7 100644 --- a/db/upgrade/v1_to_v2.rs +++ b/db/upgrade/v1_to_v2.rs @@ -32,7 +32,7 @@ use crate::dir; use failure::{Error, bail, format_err}; -use libc; +use nix::fcntl::FlockArg; use protobuf::prelude::MessageField; use rusqlite::types::ToSql; use crate::schema::DirMeta; @@ -47,7 +47,7 @@ pub fn run(args: &super::Args, tx: &rusqlite::Transaction) -> Result<(), Error> schema version 1 to 2."))?; let d = dir::Fd::open(sample_file_path, false)?; - d.lock(libc::LOCK_EX | libc::LOCK_NB)?; + d.lock(FlockArg::LockExclusiveNonblock)?; verify_dir_contents(sample_file_path, tx)?; // These create statements match the schema.sql when version 2 was the latest. diff --git a/db/upgrade/v2_to_v3.rs b/db/upgrade/v2_to_v3.rs index 353a1ec..da108e6 100644 --- a/db/upgrade/v2_to_v3.rs +++ b/db/upgrade/v2_to_v3.rs @@ -35,12 +35,11 @@ use crate::db::{self, FromSqlUuid}; use crate::dir; use failure::Error; -use libc; use crate::schema; use protobuf::prelude::MessageField; use rusqlite::types::ToSql; -use std::io::{self, Write}; -use std::mem; +use std::io::Write; +use std::os::unix::io::AsRawFd; use std::sync::Arc; use uuid::Uuid; @@ -90,10 +89,10 @@ pub fn run(_args: &super::Args, tx: &rusqlite::Transaction) -> Result<(), Error> let id = db::CompositeId(row.get(0)?); let sample_file_uuid: FromSqlUuid = row.get(1)?; let from_path = get_uuid_pathname(sample_file_uuid.0); - let to_path = get_id_pathname(id); - let r = unsafe { dir::renameat(&d.fd, from_path.as_ptr(), &d.fd, to_path.as_ptr()) }; - if let Err(e) = r { - if e.kind() == io::ErrorKind::NotFound { + let to_path = crate::dir::CompositeIdPath::from(id); + if let Err(e) = nix::fcntl::renameat(d.fd.as_raw_fd(), &from_path[..], + d.fd.as_raw_fd(), &to_path) { + if e == nix::Error::Sys(nix::errno::Errno::ENOENT) { continue; // assume it was already moved. } Err(e)?; @@ -122,17 +121,9 @@ pub fn run(_args: &super::Args, tx: &rusqlite::Transaction) -> Result<(), Error> } /// Gets a pathname for a sample file suitable for passing to open or unlink. -fn get_uuid_pathname(uuid: Uuid) -> [libc::c_char; 37] { +fn get_uuid_pathname(uuid: Uuid) -> [u8; 37] { let mut buf = [0u8; 37]; write!(&mut buf[..36], "{}", uuid.to_hyphenated_ref()) .expect("can't format uuid to pathname buf"); - - // libc::c_char seems to be i8 on some platforms (Linux/arm) and u8 on others (Linux/amd64). - unsafe { mem::transmute::<[u8; 37], [libc::c_char; 37]>(buf) } -} - -fn get_id_pathname(id: db::CompositeId) -> [libc::c_char; 17] { - let mut buf = [0u8; 17]; - write!(&mut buf[..16], "{:016x}", id.0).expect("can't format id to pathname buf"); - unsafe { mem::transmute::<[u8; 17], [libc::c_char; 17]>(buf) } + buf } diff --git a/db/upgrade/v4_to_v5.rs b/db/upgrade/v4_to_v5.rs index 3dca674..2166304 100644 --- a/db/upgrade/v4_to_v5.rs +++ b/db/upgrade/v4_to_v5.rs @@ -37,9 +37,12 @@ use crate::db::FromSqlUuid; use crate::{dir, schema}; use cstr::*; use failure::{Error, Fail, bail}; +use nix::fcntl::{FlockArg, OFlag}; +use nix::sys::stat::Mode; use protobuf::{Message, prelude::MessageField}; use rusqlite::params; use std::io::{Read, Write}; +use std::os::unix::io::AsRawFd; const FIXED_DIR_META_LEN: usize = 512; @@ -76,10 +79,10 @@ pub fn run(_args: &super::Args, tx: &rusqlite::Transaction) -> Result<(), Error> } let dir = dir::Fd::open(path, false)?; - dir.lock(libc::LOCK_EX)?; + dir.lock(FlockArg::LockExclusiveNonblock)?; let tmp_path = cstr!("meta.tmp"); let path = cstr!("meta"); - let mut f = unsafe { dir.openat(path.as_ptr(), libc::O_RDONLY, 0) }?; + let mut f = dir.openat(path, OFlag::O_RDONLY, Mode::empty())?; let mut data = Vec::new(); f.read_to_end(&mut data)?; if data.len() == FIXED_DIR_META_LEN { @@ -92,8 +95,8 @@ pub fn run(_args: &super::Args, tx: &rusqlite::Transaction) -> Result<(), Error> if !dir::SampleFileDir::consistent(&db_meta, &dir_meta) { bail!("Inconsistent db_meta={:?} dir_meta={:?}", &db_meta, &dir_meta); } - let mut f = unsafe { dir.openat(tmp_path.as_ptr(), - libc::O_CREAT | libc::O_TRUNC | libc::O_WRONLY, 0o600)? }; + let mut f = dir.openat(tmp_path, OFlag::O_CREAT | OFlag::O_TRUNC | OFlag::O_WRONLY, + Mode::S_IRUSR | Mode::S_IWUSR)?; let mut data = dir_meta.write_length_delimited_to_bytes().expect("proto3->vec is infallible"); if data.len() > FIXED_DIR_META_LEN { @@ -103,7 +106,7 @@ pub fn run(_args: &super::Args, tx: &rusqlite::Transaction) -> Result<(), Error> data.resize(FIXED_DIR_META_LEN, 0); // pad to required length. f.write_all(&data)?; f.sync_all()?; - unsafe { dir::renameat(&dir, tmp_path.as_ptr(), &dir, path.as_ptr())? }; + nix::fcntl::renameat(dir.as_raw_fd(), tmp_path, dir.as_raw_fd(), path)?; dir.sync()?; } Ok(()) diff --git a/db/writer.rs b/db/writer.rs index 336f53a..4cd518a 100644 --- a/db/writer.rs +++ b/db/writer.rs @@ -55,9 +55,9 @@ use time::{Duration, Timespec}; pub trait DirWriter : 'static + Send { type File : FileWriter; - fn create_file(&self, id: CompositeId) -> Result; - fn sync(&self) -> Result<(), io::Error>; - fn unlink_file(&self, id: CompositeId) -> Result<(), io::Error>; + fn create_file(&self, id: CompositeId) -> Result; + fn sync(&self) -> Result<(), nix::Error>; + fn unlink_file(&self, id: CompositeId) -> Result<(), nix::Error>; } pub trait FileWriter : 'static { @@ -71,11 +71,11 @@ pub trait FileWriter : 'static { impl DirWriter for Arc { type File = ::std::fs::File; - fn create_file(&self, id: CompositeId) -> Result { + fn create_file(&self, id: CompositeId) -> Result { dir::SampleFileDir::create_file(self, id) } - fn sync(&self) -> Result<(), io::Error> { dir::SampleFileDir::sync(self) } - fn unlink_file(&self, id: CompositeId) -> Result<(), io::Error> { + fn sync(&self) -> Result<(), nix::Error> { dir::SampleFileDir::sync(self) } + fn unlink_file(&self, id: CompositeId) -> Result<(), nix::Error> { dir::SampleFileDir::unlink_file(self, id) } } @@ -308,7 +308,7 @@ impl Syncer> { let mut undeletable = 0; for &id in &to_abandon { if let Err(e) = dir.unlink_file(id) { - if e.kind() == io::ErrorKind::NotFound { + if e == nix::Error::Sys(nix::errno::Errno::ENOENT) { warn!("dir: abandoned recording {} already deleted!", id); } else { warn!("dir: Unable to unlink abandoned recording {}: {}", id, e); @@ -358,7 +358,7 @@ impl Syncer> { let mut errors = 0; for &id in &garbage { if let Err(e) = self.dir.unlink_file(id) { - if e.kind() != io::ErrorKind::NotFound { + if e != nix::Error::Sys(nix::errno::Errno::ENOENT) { warn!("dir: Unable to unlink {}: {}", id, e); errors += 1; } @@ -435,7 +435,7 @@ impl Syncer { for &id in &garbage { clock::retry_forever(c, &mut || { if let Err(e) = self.dir.unlink_file(id) { - if e.kind() == io::ErrorKind::NotFound { + if e == nix::Error::Sys(nix::errno::Errno::ENOENT) { warn!("dir: recording {} already deleted!", id); return Ok(()); } @@ -867,9 +867,9 @@ mod tests { struct MockDir(Arc>>); enum MockDirAction { - Create(CompositeId, Box Result + Send>), - Sync(Box Result<(), io::Error> + Send>), - Unlink(CompositeId, Box Result<(), io::Error> + Send>), + Create(CompositeId, Box Result + Send>), + Sync(Box Result<(), nix::Error> + Send>), + Unlink(CompositeId, Box Result<(), nix::Error> + Send>), } impl MockDir { @@ -881,7 +881,7 @@ mod tests { impl super::DirWriter for MockDir { type File = MockFile; - fn create_file(&self, id: CompositeId) -> Result { + fn create_file(&self, id: CompositeId) -> Result { match self.0.lock().pop_front().expect("got create_file with no expectation") { MockDirAction::Create(expected_id, ref f) => { assert_eq!(id, expected_id); @@ -890,13 +890,13 @@ mod tests { _ => panic!("got create_file({}), expected something else", id), } } - fn sync(&self) -> Result<(), io::Error> { + fn sync(&self) -> Result<(), nix::Error> { match self.0.lock().pop_front().expect("got sync with no expectation") { MockDirAction::Sync(f) => f(), _ => panic!("got sync, expected something else"), } } - fn unlink_file(&self, id: CompositeId) -> Result<(), io::Error> { + fn unlink_file(&self, id: CompositeId) -> Result<(), nix::Error> { match self.0.lock().pop_front().expect("got unlink_file with no expectation") { MockDirAction::Unlink(expected_id, f) => { assert_eq!(id, expected_id); @@ -991,6 +991,7 @@ mod tests { } fn eio() -> io::Error { io::Error::new(io::ErrorKind::Other, "got EIO") } + fn nix_eio() -> nix::Error { nix::Error::Sys(nix::errno::Errno::EIO) } /// Tests the database flushing while a syncer is still processing a previous flush event. #[test] @@ -1091,7 +1092,7 @@ mod tests { 1920, 1080, [0u8; 100].to_vec(), "avc1.000000".to_owned()).unwrap(); let mut w = Writer::new(&h.dir, &h.db, &h.channel, testutil::TEST_STREAM_ID, video_sample_entry_id); - h.dir.expect(MockDirAction::Create(CompositeId::new(1, 1), Box::new(|_id| Err(eio())))); + h.dir.expect(MockDirAction::Create(CompositeId::new(1, 1), Box::new(|_id| Err(nix_eio())))); let f = MockFile::new(); h.dir.expect(MockDirAction::Create(CompositeId::new(1, 1), Box::new({ let f = f.clone(); move |_id| Ok(f.clone()) }))); @@ -1114,7 +1115,7 @@ mod tests { f.expect(MockFileAction::SyncAll(Box::new(|| Err(eio())))); f.expect(MockFileAction::SyncAll(Box::new(|| Ok(())))); w.write(b"1234", recording::Time(1), 0, true).unwrap(); - h.dir.expect(MockDirAction::Sync(Box::new(|| Err(eio())))); + h.dir.expect(MockDirAction::Sync(Box::new(|| Err(nix_eio())))); h.dir.expect(MockDirAction::Sync(Box::new(|| Ok(())))); drop(w); assert!(h.syncer.iter(&h.syncer_rcv)); // AsyncSave @@ -1194,11 +1195,11 @@ mod tests { assert_eq!(s.bytes_to_delete, 0); assert_eq!(s.bytes_to_add, 0); assert_eq!(s.sample_file_bytes, 1); - Err(eio()) // force a retry. + Err(nix_eio()) // force a retry. } }))); h.dir.expect(MockDirAction::Unlink(CompositeId::new(1, 1), Box::new(|_| Ok(())))); - h.dir.expect(MockDirAction::Sync(Box::new(|| Err(eio())))); + h.dir.expect(MockDirAction::Sync(Box::new(|| Err(nix_eio())))); h.dir.expect(MockDirAction::Sync(Box::new(|| Ok(())))); drop(w); diff --git a/src/cmds/config/dirs.rs b/src/cmds/config/dirs.rs index 4379616..31a6e09 100644 --- a/src/cmds/config/dirs.rs +++ b/src/cmds/config/dirs.rs @@ -303,7 +303,7 @@ fn edit_dir_dialog(db: &Arc, siv: &mut Cursive, dir_id: i32) { l.open_sample_file_dirs(&[dir_id]).unwrap(); // TODO: don't unwrap. let dir = l.sample_file_dirs_by_id().get(&dir_id).unwrap(); let stat = dir.get().unwrap().statfs().unwrap(); - fs_capacity = stat.f_bsize as i64 * stat.f_bavail as i64 + total_used; + fs_capacity = stat.block_size() as i64 * stat.blocks_available() as i64 + total_used; path = dir.path.clone(); } Rc::new(RefCell::new(Model { diff --git a/src/cmds/mod.rs b/src/cmds/mod.rs index 87b9510..2077457 100644 --- a/src/cmds/mod.rs +++ b/src/cmds/mod.rs @@ -31,7 +31,7 @@ use db::dir; use docopt; use failure::{Error, Fail}; -use libc; +use nix::fcntl::FlockArg; use rusqlite; use serde::Deserialize; use std::path::Path; @@ -84,7 +84,7 @@ enum OpenMode { fn open_dir(db_dir: &str, mode: OpenMode) -> Result { let dir = dir::Fd::open(db_dir, mode == OpenMode::Create)?; let ro = mode == OpenMode::ReadOnly; - dir.lock(if ro { libc::LOCK_SH } else { libc::LOCK_EX } | libc::LOCK_NB) + dir.lock(if ro { FlockArg::LockExclusiveNonblock } else { FlockArg::LockSharedNonblock }) .map_err(|e| e.context(format!("db dir {:?} already in use; can't get {} lock", db_dir, if ro { "shared" } else { "exclusive" })))?; Ok(dir)