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#[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 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 -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 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 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 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 #[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 #[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 #[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 file1.lock_exclusive().unwrap();
221 file1.lock_shared().unwrap();
222 file2.lock_shared().unwrap();
223
224 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 #[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 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 file1.unlock().unwrap();
248 file3.lock_shared().unwrap();
249 }
250}