1 extern crate libc;
2 
3 use std::ffi::CString;
4 use std::fs::File;
5 use std::io::{Error, ErrorKind, Result};
6 use std::mem;
7 use std::os::unix::ffi::OsStrExt;
8 use std::os::unix::fs::MetadataExt;
9 use std::os::unix::io::{AsRawFd, FromRawFd};
10 use std::path::Path;
11 
12 use FsStats;
13 
duplicate(file: &File) -> Result<File>14 pub 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 
lock_shared(file: &File) -> Result<()>26 pub fn lock_shared(file: &File) -> Result<()> {
27     flock(file, libc::LOCK_SH)
28 }
29 
lock_exclusive(file: &File) -> Result<()>30 pub fn lock_exclusive(file: &File) -> Result<()> {
31     flock(file, libc::LOCK_EX)
32 }
33 
try_lock_shared(file: &File) -> Result<()>34 pub fn try_lock_shared(file: &File) -> Result<()> {
35     flock(file, libc::LOCK_SH | libc::LOCK_NB)
36 }
37 
try_lock_exclusive(file: &File) -> Result<()>38 pub fn try_lock_exclusive(file: &File) -> Result<()> {
39     flock(file, libc::LOCK_EX | libc::LOCK_NB)
40 }
41 
unlock(file: &File) -> Result<()>42 pub fn unlock(file: &File) -> Result<()> {
43     flock(file, libc::LOCK_UN)
44 }
45 
lock_error() -> Error46 pub fn lock_error() -> Error {
47     Error::from_raw_os_error(libc::EWOULDBLOCK)
48 }
49 
50 #[cfg(not(target_os = "solaris"))]
flock(file: &File, flag: libc::c_int) -> Result<()>51 fn 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")]
flock(file: &File, flag: libc::c_int) -> Result<()>58 fn 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 
allocated_size(file: &File) -> Result<u64>94 pub 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"))]
allocate(file: &File, len: u64) -> Result<()>102 pub 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"))]
allocate(file: &File, len: u64) -> Result<()>108 pub 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"))]
allocate(file: &File, len: u64) -> Result<()>143 pub 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 
statvfs(path: &Path) -> Result<FsStats>152 pub 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)]
175 mod 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]
duplicate_new_fd()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]
duplicate_cloexec()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]
lock_replace()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]
lock_duplicate()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 }
251