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