fs2/
unix.rs

1extern crate libc;
2
3use std::ffi::CString;
4use std::fs::File;
5use std::io::{Error, ErrorKind, Result};
6use std::mem;
7use std::os::unix::ffi::OsStrExt;
8use std::os::unix::fs::MetadataExt;
9use std::os::unix::io::{AsRawFd, FromRawFd};
10use std::path::Path;
11
12use FsStats;
13
14pub fn duplicate(file: &File) -> Result<File> {
15    unsafe {
16        let fd = libc::dup(file.as_raw_fd());
17
18        if fd < 0 {
19            Err(Error::last_os_error())
20        } else {
21            Ok(File::from_raw_fd(fd))
22        }
23    }
24}
25
26pub fn lock_shared(file: &File) -> Result<()> {
27    flock(file, libc::LOCK_SH)
28}
29
30pub fn lock_exclusive(file: &File) -> Result<()> {
31    flock(file, libc::LOCK_EX)
32}
33
34pub fn try_lock_shared(file: &File) -> Result<()> {
35    flock(file, libc::LOCK_SH | libc::LOCK_NB)
36}
37
38pub fn try_lock_exclusive(file: &File) -> Result<()> {
39    flock(file, libc::LOCK_EX | libc::LOCK_NB)
40}
41
42pub fn unlock(file: &File) -> Result<()> {
43    flock(file, libc::LOCK_UN)
44}
45
46pub fn lock_error() -> Error {
47    Error::from_raw_os_error(libc::EWOULDBLOCK)
48}
49
50#[cfg(not(target_os = "solaris"))]
51fn flock(file: &File, flag: libc::c_int) -> Result<()> {
52    let ret = unsafe { libc::flock(file.as_raw_fd(), flag) };
53    if ret < 0 { Err(Error::last_os_error()) } else { Ok(()) }
54}
55
56/// Simulate flock() using fcntl(); primarily for Oracle Solaris.
57#[cfg(target_os = "solaris")]
58fn flock(file: &File, flag: libc::c_int) -> Result<()> {
59    let mut fl = libc::flock {
60        l_whence: 0,
61        l_start: 0,
62        l_len: 0,
63        l_type: 0,
64        l_pad: [0; 4],
65        l_pid: 0,
66        l_sysid: 0,
67    };
68
69    // In non-blocking mode, use F_SETLK for cmd, F_SETLKW otherwise, and don't forget to clear
70    // LOCK_NB.
71    let (cmd, operation) = match flag & libc::LOCK_NB {
72        0 => (libc::F_SETLKW, flag),
73        _ => (libc::F_SETLK, flag & !libc::LOCK_NB),
74    };
75
76    match operation {
77        libc::LOCK_SH => fl.l_type |= libc::F_RDLCK,
78        libc::LOCK_EX => fl.l_type |= libc::F_WRLCK,
79        libc::LOCK_UN => fl.l_type |= libc::F_UNLCK,
80        _ => return Err(Error::from_raw_os_error(libc::EINVAL)),
81    }
82
83    let ret = unsafe { libc::fcntl(file.as_raw_fd(), cmd, &fl) };
84    match ret {
85        // Translate EACCES to EWOULDBLOCK
86        -1 => match Error::last_os_error().raw_os_error() {
87            Some(libc::EACCES) => return Err(lock_error()),
88            _ => return Err(Error::last_os_error())
89        },
90        _ => Ok(())
91    }
92}
93
94pub fn allocated_size(file: &File) -> Result<u64> {
95    file.metadata().map(|m| m.blocks() as u64 * 512)
96}
97
98#[cfg(any(target_os = "linux",
99          target_os = "freebsd",
100          target_os = "android",
101          target_os = "nacl"))]
102pub fn allocate(file: &File, len: u64) -> Result<()> {
103    let ret = unsafe { libc::posix_fallocate(file.as_raw_fd(), 0, len as libc::off_t) };
104    if ret == 0 { Ok(()) } else { Err(Error::last_os_error()) }
105}
106
107#[cfg(any(target_os = "macos", target_os = "ios"))]
108pub fn allocate(file: &File, len: u64) -> Result<()> {
109    let stat = try!(file.metadata());
110
111    if len > stat.blocks() as u64 * 512 {
112        let mut fstore = libc::fstore_t {
113            fst_flags: libc::F_ALLOCATECONTIG,
114            fst_posmode: libc::F_PEOFPOSMODE,
115            fst_offset: 0,
116            fst_length: len as libc::off_t,
117            fst_bytesalloc: 0,
118        };
119
120        let ret = unsafe { libc::fcntl(file.as_raw_fd(), libc::F_PREALLOCATE, &fstore) };
121        if ret == -1 {
122            // Unable to allocate contiguous disk space; attempt to allocate non-contiguously.
123            fstore.fst_flags = libc::F_ALLOCATEALL;
124            let ret = unsafe { libc::fcntl(file.as_raw_fd(), libc::F_PREALLOCATE, &fstore) };
125            if ret == -1 {
126                return Err(Error::last_os_error());
127            }
128        }
129    }
130
131    if len > stat.size() as u64 {
132        file.set_len(len)
133    } else {
134        Ok(())
135    }
136}
137
138#[cfg(any(target_os = "openbsd",
139          target_os = "netbsd",
140          target_os = "dragonfly",
141          target_os = "solaris",
142          target_os = "haiku"))]
143pub fn allocate(file: &File, len: u64) -> Result<()> {
144    // No file allocation API available, just set the length if necessary.
145    if len > try!(file.metadata()).len() as u64 {
146        file.set_len(len)
147    } else {
148        Ok(())
149    }
150}
151
152pub fn statvfs(path: &Path) -> Result<FsStats> {
153    let cstr = match CString::new(path.as_os_str().as_bytes()) {
154        Ok(cstr) => cstr,
155        Err(..) => return Err(Error::new(ErrorKind::InvalidInput, "path contained a null")),
156    };
157
158    unsafe {
159        let mut stat: libc::statvfs = mem::zeroed();
160        // danburkert/fs2-rs#1: cast is necessary for platforms where c_char != u8.
161        if libc::statvfs(cstr.as_ptr() as *const _, &mut stat) != 0 {
162            Err(Error::last_os_error())
163        } else {
164            Ok(FsStats {
165                free_space: stat.f_frsize as u64 * stat.f_bfree as u64,
166                available_space: stat.f_frsize as u64 * stat.f_bavail as u64,
167                total_space: stat.f_frsize as u64 * stat.f_blocks as u64,
168                allocation_granularity: stat.f_frsize as u64,
169            })
170        }
171    }
172}
173
174#[cfg(test)]
175mod test {
176    extern crate tempdir;
177    extern crate libc;
178
179    use std::fs::{self, File};
180    use std::os::unix::io::AsRawFd;
181
182    use {FileExt, lock_contended_error};
183
184    /// The duplicate method returns a file with a new file descriptor.
185    #[test]
186    fn duplicate_new_fd() {
187        let tempdir = tempdir::TempDir::new("fs2").unwrap();
188        let path = tempdir.path().join("fs2");
189        let file1 = fs::OpenOptions::new().write(true).create(true).open(&path).unwrap();
190        let file2 = file1.duplicate().unwrap();
191        assert!(file1.as_raw_fd() != file2.as_raw_fd());
192    }
193
194    /// The duplicate method should preservesthe close on exec flag.
195    #[test]
196    fn duplicate_cloexec() {
197
198        fn flags(file: &File) -> libc::c_int {
199            unsafe { libc::fcntl(file.as_raw_fd(), libc::F_GETFL, 0) }
200        }
201
202        let tempdir = tempdir::TempDir::new("fs2").unwrap();
203        let path = tempdir.path().join("fs2");
204        let file1 = fs::OpenOptions::new().write(true).create(true).open(&path).unwrap();
205        let file2 = file1.duplicate().unwrap();
206
207        assert_eq!(flags(&file1), flags(&file2));
208    }
209
210    /// Tests that locking a file descriptor will replace any existing locks
211    /// held on the file descriptor.
212    #[test]
213    fn lock_replace() {
214        let tempdir = tempdir::TempDir::new("fs2").unwrap();
215        let path = tempdir.path().join("fs2");
216        let file1 = fs::OpenOptions::new().write(true).create(true).open(&path).unwrap();
217        let file2 = fs::OpenOptions::new().write(true).create(true).open(&path).unwrap();
218
219        // Creating a shared lock will drop an exclusive lock.
220        file1.lock_exclusive().unwrap();
221        file1.lock_shared().unwrap();
222        file2.lock_shared().unwrap();
223
224        // Attempting to replace a shared lock with an exclusive lock will fail
225        // with multiple lock holders, and remove the original shared lock.
226        assert_eq!(file2.try_lock_exclusive().unwrap_err().raw_os_error(),
227                   lock_contended_error().raw_os_error());
228        file1.lock_shared().unwrap();
229    }
230
231    /// Tests that locks are shared among duplicated file descriptors.
232    #[test]
233    fn lock_duplicate() {
234        let tempdir = tempdir::TempDir::new("fs2").unwrap();
235        let path = tempdir.path().join("fs2");
236        let file1 = fs::OpenOptions::new().write(true).create(true).open(&path).unwrap();
237        let file2 = file1.duplicate().unwrap();
238        let file3 = fs::OpenOptions::new().write(true).create(true).open(&path).unwrap();
239
240        // Create a lock through fd1, then replace it through fd2.
241        file1.lock_shared().unwrap();
242        file2.lock_exclusive().unwrap();
243        assert_eq!(file3.try_lock_shared().unwrap_err().raw_os_error(),
244                   lock_contended_error().raw_os_error());
245
246        // Either of the file descriptors should be able to unlock.
247        file1.unlock().unwrap();
248        file3.lock_shared().unwrap();
249    }
250}