1 #[cfg(not(target_os = "redox"))]
2 use nix::Error;
3 #[cfg(not(target_os = "redox"))]
4 use nix::errno::*;
5 #[cfg(not(target_os = "redox"))]
6 use nix::fcntl::{open, OFlag, readlink};
7 #[cfg(not(target_os = "redox"))]
8 use nix::fcntl::{openat, readlinkat, renameat};
9 #[cfg(not(target_os = "redox"))]
10 use nix::sys::stat::Mode;
11 #[cfg(not(target_os = "redox"))]
12 use nix::unistd::{close, read};
13 #[cfg(not(target_os = "redox"))]
14 use tempfile::{self, NamedTempFile};
15 #[cfg(not(target_os = "redox"))]
16 use std::fs::File;
17 #[cfg(not(target_os = "redox"))]
18 use std::io::prelude::*;
19 #[cfg(not(target_os = "redox"))]
20 use std::os::unix::fs;
21
22 #[test]
23 #[cfg(not(target_os = "redox"))]
test_openat()24 fn test_openat() {
25 const CONTENTS: &[u8] = b"abcd";
26 let mut tmp = NamedTempFile::new().unwrap();
27 tmp.write_all(CONTENTS).unwrap();
28
29 let dirfd = open(tmp.path().parent().unwrap(),
30 OFlag::empty(),
31 Mode::empty()).unwrap();
32 let fd = openat(dirfd,
33 tmp.path().file_name().unwrap(),
34 OFlag::O_RDONLY,
35 Mode::empty()).unwrap();
36
37 let mut buf = [0u8; 1024];
38 assert_eq!(4, read(fd, &mut buf).unwrap());
39 assert_eq!(CONTENTS, &buf[0..4]);
40
41 close(fd).unwrap();
42 close(dirfd).unwrap();
43 }
44
45 #[test]
46 #[cfg(not(target_os = "redox"))]
test_renameat()47 fn test_renameat() {
48 let old_dir = tempfile::tempdir().unwrap();
49 let old_dirfd = open(old_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
50 let old_path = old_dir.path().join("old");
51 File::create(&old_path).unwrap();
52 let new_dir = tempfile::tempdir().unwrap();
53 let new_dirfd = open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
54 renameat(Some(old_dirfd), "old", Some(new_dirfd), "new").unwrap();
55 assert_eq!(renameat(Some(old_dirfd), "old", Some(new_dirfd), "new").unwrap_err(),
56 Error::Sys(Errno::ENOENT));
57 close(old_dirfd).unwrap();
58 close(new_dirfd).unwrap();
59 assert!(new_dir.path().join("new").exists());
60 }
61
62 #[test]
63 #[cfg(not(target_os = "redox"))]
test_readlink()64 fn test_readlink() {
65 let tempdir = tempfile::tempdir().unwrap();
66 let src = tempdir.path().join("a");
67 let dst = tempdir.path().join("b");
68 println!("a: {:?}, b: {:?}", &src, &dst);
69 fs::symlink(&src.as_path(), &dst.as_path()).unwrap();
70 let dirfd = open(tempdir.path(),
71 OFlag::empty(),
72 Mode::empty()).unwrap();
73 let expected_dir = src.to_str().unwrap();
74
75 assert_eq!(readlink(&dst).unwrap().to_str().unwrap(), expected_dir);
76 assert_eq!(readlinkat(dirfd, "b").unwrap().to_str().unwrap(), expected_dir);
77
78 }
79
80 #[cfg(any(target_os = "linux", target_os = "android"))]
81 mod linux_android {
82 use std::fs::File;
83 use std::io::prelude::*;
84 use std::io::{BufRead, BufReader, SeekFrom};
85 use std::os::unix::prelude::*;
86
87 use libc::loff_t;
88
89 use nix::fcntl::*;
90 use nix::sys::stat::fstat;
91 use nix::sys::uio::IoVec;
92 use nix::unistd::{close, pipe, read, write};
93
94 use tempfile::{tempfile, NamedTempFile};
95
96 /// This test creates a temporary file containing the contents
97 /// 'foobarbaz' and uses the `copy_file_range` call to transfer
98 /// 3 bytes at offset 3 (`bar`) to another empty file at offset 0. The
99 /// resulting file is read and should contain the contents `bar`.
100 /// The from_offset should be updated by the call to reflect
101 /// the 3 bytes read (6).
102 ///
103 /// FIXME: This test is disabled for linux based builds, because Travis
104 /// Linux version is too old for `copy_file_range`.
105 #[test]
106 #[ignore]
test_copy_file_range()107 fn test_copy_file_range() {
108 const CONTENTS: &[u8] = b"foobarbaz";
109
110 let mut tmp1 = tempfile().unwrap();
111 let mut tmp2 = tempfile().unwrap();
112
113 tmp1.write_all(CONTENTS).unwrap();
114 tmp1.flush().unwrap();
115
116 let mut from_offset: i64 = 3;
117 copy_file_range(
118 tmp1.as_raw_fd(),
119 Some(&mut from_offset),
120 tmp2.as_raw_fd(),
121 None,
122 3,
123 )
124 .unwrap();
125
126 let mut res: String = String::new();
127 tmp2.seek(SeekFrom::Start(0)).unwrap();
128 tmp2.read_to_string(&mut res).unwrap();
129
130 assert_eq!(res, String::from("bar"));
131 assert_eq!(from_offset, 6);
132 }
133
134 #[test]
test_splice()135 fn test_splice() {
136 const CONTENTS: &[u8] = b"abcdef123456";
137 let mut tmp = tempfile().unwrap();
138 tmp.write_all(CONTENTS).unwrap();
139
140 let (rd, wr) = pipe().unwrap();
141 let mut offset: loff_t = 5;
142 let res = splice(tmp.as_raw_fd(), Some(&mut offset),
143 wr, None, 2, SpliceFFlags::empty()).unwrap();
144
145 assert_eq!(2, res);
146
147 let mut buf = [0u8; 1024];
148 assert_eq!(2, read(rd, &mut buf).unwrap());
149 assert_eq!(b"f1", &buf[0..2]);
150 assert_eq!(7, offset);
151
152 close(rd).unwrap();
153 close(wr).unwrap();
154 }
155
156 #[test]
test_tee()157 fn test_tee() {
158 let (rd1, wr1) = pipe().unwrap();
159 let (rd2, wr2) = pipe().unwrap();
160
161 write(wr1, b"abc").unwrap();
162 let res = tee(rd1, wr2, 2, SpliceFFlags::empty()).unwrap();
163
164 assert_eq!(2, res);
165
166 let mut buf = [0u8; 1024];
167
168 // Check the tee'd bytes are at rd2.
169 assert_eq!(2, read(rd2, &mut buf).unwrap());
170 assert_eq!(b"ab", &buf[0..2]);
171
172 // Check all the bytes are still at rd1.
173 assert_eq!(3, read(rd1, &mut buf).unwrap());
174 assert_eq!(b"abc", &buf[0..3]);
175
176 close(rd1).unwrap();
177 close(wr1).unwrap();
178 close(rd2).unwrap();
179 close(wr2).unwrap();
180 }
181
182 #[test]
test_vmsplice()183 fn test_vmsplice() {
184 let (rd, wr) = pipe().unwrap();
185
186 let buf1 = b"abcdef";
187 let buf2 = b"defghi";
188 let mut iovecs = Vec::with_capacity(2);
189 iovecs.push(IoVec::from_slice(&buf1[0..3]));
190 iovecs.push(IoVec::from_slice(&buf2[0..3]));
191
192 let res = vmsplice(wr, &iovecs[..], SpliceFFlags::empty()).unwrap();
193
194 assert_eq!(6, res);
195
196 // Check the bytes can be read at rd.
197 let mut buf = [0u8; 32];
198 assert_eq!(6, read(rd, &mut buf).unwrap());
199 assert_eq!(b"abcdef", &buf[0..6]);
200
201 close(rd).unwrap();
202 close(wr).unwrap();
203 }
204
205 #[test]
test_fallocate()206 fn test_fallocate() {
207 let tmp = NamedTempFile::new().unwrap();
208
209 let fd = tmp.as_raw_fd();
210 fallocate(fd, FallocateFlags::empty(), 0, 100).unwrap();
211
212 // Check if we read exactly 100 bytes
213 let mut buf = [0u8; 200];
214 assert_eq!(100, read(fd, &mut buf).unwrap());
215 }
216
217 // The tests below are disabled for the listed targets
218 // due to OFD locks not being available in the kernel/libc
219 // versions used in the CI environment, probably because
220 // they run under QEMU.
221
222 #[test]
223 #[cfg(not(any(target_arch = "aarch64",
224 target_arch = "arm",
225 target_arch = "armv7",
226 target_arch = "x86",
227 target_arch = "mips",
228 target_arch = "mips64",
229 target_arch = "mips64el",
230 target_arch = "powerpc64",
231 target_arch = "powerpc64le",
232 target_env = "musl")))]
test_ofd_write_lock()233 fn test_ofd_write_lock() {
234 let tmp = NamedTempFile::new().unwrap();
235
236 let fd = tmp.as_raw_fd();
237 let statfs = nix::sys::statfs::fstatfs(&tmp).unwrap();
238 if statfs.filesystem_type() == nix::sys::statfs::OVERLAYFS_SUPER_MAGIC {
239 // OverlayFS is a union file system. It returns one inode value in
240 // stat(2), but a different one shows up in /proc/locks. So we must
241 // skip the test.
242 skip!("/proc/locks does not work on overlayfs");
243 }
244 let inode = fstat(fd).expect("fstat failed").st_ino as usize;
245
246 let mut flock = libc::flock {
247 l_type: libc::F_WRLCK as libc::c_short,
248 l_whence: libc::SEEK_SET as libc::c_short,
249 l_start: 0,
250 l_len: 0,
251 l_pid: 0,
252 };
253 fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("write lock failed");
254 assert_eq!(
255 Some(("OFDLCK".to_string(), "WRITE".to_string())),
256 lock_info(inode)
257 );
258
259 flock.l_type = libc::F_UNLCK as libc::c_short;
260 fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("write unlock failed");
261 assert_eq!(None, lock_info(inode));
262 }
263
264 #[test]
265 #[cfg(not(any(target_arch = "aarch64",
266 target_arch = "arm",
267 target_arch = "armv7",
268 target_arch = "x86",
269 target_arch = "mips",
270 target_arch = "mips64",
271 target_arch = "mips64el",
272 target_arch = "powerpc64",
273 target_arch = "powerpc64le",
274 target_env = "musl")))]
test_ofd_read_lock()275 fn test_ofd_read_lock() {
276 let tmp = NamedTempFile::new().unwrap();
277
278 let fd = tmp.as_raw_fd();
279 let statfs = nix::sys::statfs::fstatfs(&tmp).unwrap();
280 if statfs.filesystem_type() == nix::sys::statfs::OVERLAYFS_SUPER_MAGIC {
281 // OverlayFS is a union file system. It returns one inode value in
282 // stat(2), but a different one shows up in /proc/locks. So we must
283 // skip the test.
284 skip!("/proc/locks does not work on overlayfs");
285 }
286 let inode = fstat(fd).expect("fstat failed").st_ino as usize;
287
288 let mut flock = libc::flock {
289 l_type: libc::F_RDLCK as libc::c_short,
290 l_whence: libc::SEEK_SET as libc::c_short,
291 l_start: 0,
292 l_len: 0,
293 l_pid: 0,
294 };
295 fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("read lock failed");
296 assert_eq!(
297 Some(("OFDLCK".to_string(), "READ".to_string())),
298 lock_info(inode)
299 );
300
301 flock.l_type = libc::F_UNLCK as libc::c_short;
302 fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("read unlock failed");
303 assert_eq!(None, lock_info(inode));
304 }
305
lock_info(inode: usize) -> Option<(String, String)>306 fn lock_info(inode: usize) -> Option<(String, String)> {
307 let file = File::open("/proc/locks").expect("open /proc/locks failed");
308 let buf = BufReader::new(file);
309
310 for line in buf.lines() {
311 let line = line.unwrap();
312 let parts: Vec<_> = line.split_whitespace().collect();
313 let lock_type = parts[1];
314 let lock_access = parts[3];
315 let ino_parts: Vec<_> = parts[5].split(':').collect();
316 let ino: usize = ino_parts[2].parse().unwrap();
317 if ino == inode {
318 return Some((lock_type.to_string(), lock_access.to_string()));
319 }
320 }
321 None
322 }
323 }
324
325 #[cfg(any(target_os = "linux",
326 target_os = "android",
327 target_os = "emscripten",
328 target_os = "fuchsia",
329 any(target_os = "wasi", target_env = "wasi"),
330 target_env = "uclibc",
331 target_env = "freebsd"))]
332 mod test_posix_fadvise {
333
334 use tempfile::NamedTempFile;
335 use std::os::unix::io::{RawFd, AsRawFd};
336 use nix::errno::Errno;
337 use nix::fcntl::*;
338 use nix::unistd::pipe;
339
340 #[test]
test_success()341 fn test_success() {
342 let tmp = NamedTempFile::new().unwrap();
343 let fd = tmp.as_raw_fd();
344 let res = posix_fadvise(fd, 0, 100, PosixFadviseAdvice::POSIX_FADV_WILLNEED).unwrap();
345
346 assert_eq!(res, 0);
347 }
348
349 #[test]
test_errno()350 fn test_errno() {
351 let (rd, _wr) = pipe().unwrap();
352 let errno = posix_fadvise(rd as RawFd, 0, 100, PosixFadviseAdvice::POSIX_FADV_WILLNEED)
353 .unwrap();
354 assert_eq!(errno, Errno::ESPIPE as i32);
355 }
356 }
357
358 #[cfg(any(target_os = "linux",
359 target_os = "android",
360 target_os = "emscripten",
361 target_os = "fuchsia",
362 any(target_os = "wasi", target_env = "wasi"),
363 target_os = "freebsd"))]
364 mod test_posix_fallocate {
365
366 use tempfile::NamedTempFile;
367 use std::{io::Read, os::unix::io::{RawFd, AsRawFd}};
368 use nix::errno::Errno;
369 use nix::fcntl::*;
370 use nix::unistd::pipe;
371
372 #[test]
success()373 fn success() {
374 const LEN: usize = 100;
375 let mut tmp = NamedTempFile::new().unwrap();
376 let fd = tmp.as_raw_fd();
377 let res = posix_fallocate(fd, 0, LEN as libc::off_t);
378 match res {
379 Ok(_) => {
380 let mut data = [1u8; LEN];
381 assert_eq!(tmp.read(&mut data).expect("read failure"), LEN);
382 assert_eq!(&data[..], &[0u8; LEN][..]);
383 }
384 Err(nix::Error::Sys(Errno::EINVAL)) => {
385 // POSIX requires posix_fallocate to return EINVAL both for
386 // invalid arguments (i.e. len < 0) and if the operation is not
387 // supported by the file system.
388 // There's no way to tell for sure whether the file system
389 // supports posix_fallocate, so we must pass the test if it
390 // returns EINVAL.
391 }
392 _ => res.unwrap(),
393 }
394 }
395
396 #[test]
errno()397 fn errno() {
398 let (rd, _wr) = pipe().unwrap();
399 let err = posix_fallocate(rd as RawFd, 0, 100).unwrap_err();
400 use nix::Error::Sys;
401 match err {
402 Sys(Errno::EINVAL)
403 | Sys(Errno::ENODEV)
404 | Sys(Errno::ESPIPE)
405 | Sys(Errno::EBADF) => (),
406 errno =>
407 panic!(
408 "unexpected errno {}",
409 errno,
410 ),
411 }
412 }
413 }
414