1 #[cfg(not(target_os = "redox"))]
2 use nix::fcntl::{self, open, readlink};
3 use nix::fcntl::{fcntl, FcntlArg, FdFlag, OFlag};
4 use nix::unistd::*;
5 use nix::unistd::ForkResult::*;
6 #[cfg(not(target_os = "redox"))]
7 use nix::sys::signal::{SaFlags, SigAction, SigHandler, SigSet, Signal, sigaction};
8 use nix::sys::wait::*;
9 use nix::sys::stat::{self, Mode, SFlag};
10 #[cfg(not(target_os = "redox"))]
11 use nix::pty::{posix_openpt, grantpt, unlockpt, ptsname};
12 use nix::errno::Errno;
13 #[cfg(not(target_os = "redox"))]
14 use nix::Error;
15 use std::{env, iter};
16 #[cfg(not(target_os = "redox"))]
17 use std::ffi::CString;
18 #[cfg(not(target_os = "redox"))]
19 use std::fs::DirBuilder;
20 use std::fs::{self, File};
21 use std::io::Write;
22 use std::mem;
23 use std::os::unix::prelude::*;
24 #[cfg(not(target_os = "redox"))]
25 use std::path::Path;
26 use tempfile::{tempdir, tempfile};
27 use libc::{_exit, off_t};
28 
29 #[test]
30 #[cfg(not(any(target_os = "netbsd")))]
test_fork_and_waitpid()31 fn test_fork_and_waitpid() {
32     let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test");
33 
34     // Safe: Child only calls `_exit`, which is signal-safe
35     match fork().expect("Error: Fork Failed") {
36         Child => unsafe { _exit(0) },
37         Parent { child } => {
38             // assert that child was created and pid > 0
39             let child_raw: ::libc::pid_t = child.into();
40             assert!(child_raw > 0);
41             let wait_status = waitpid(child, None);
42             match wait_status {
43                 // assert that waitpid returned correct status and the pid is the one of the child
44                 Ok(WaitStatus::Exited(pid_t, _)) =>  assert_eq!(pid_t, child),
45 
46                 // panic, must never happen
47                 s @ Ok(_) => panic!("Child exited {:?}, should never happen", s),
48 
49                 // panic, waitpid should never fail
50                 Err(s) => panic!("Error: waitpid returned Err({:?}", s)
51             }
52 
53         },
54     }
55 }
56 
57 #[test]
test_wait()58 fn test_wait() {
59     // Grab FORK_MTX so wait doesn't reap a different test's child process
60     let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test");
61 
62     // Safe: Child only calls `_exit`, which is signal-safe
63     match fork().expect("Error: Fork Failed") {
64         Child => unsafe { _exit(0) },
65         Parent { child } => {
66             let wait_status = wait();
67 
68             // just assert that (any) one child returns with WaitStatus::Exited
69             assert_eq!(wait_status, Ok(WaitStatus::Exited(child, 0)));
70         },
71     }
72 }
73 
74 #[test]
test_mkstemp()75 fn test_mkstemp() {
76     let mut path = env::temp_dir();
77     path.push("nix_tempfile.XXXXXX");
78 
79     let result = mkstemp(&path);
80     match result {
81         Ok((fd, path)) => {
82             close(fd).unwrap();
83             unlink(path.as_path()).unwrap();
84         },
85         Err(e) => panic!("mkstemp failed: {}", e)
86     }
87 }
88 
89 #[test]
test_mkstemp_directory()90 fn test_mkstemp_directory() {
91     // mkstemp should fail if a directory is given
92     assert!(mkstemp(&env::temp_dir()).is_err());
93 }
94 
95 #[test]
96 #[cfg(not(target_os = "redox"))]
test_mkfifo()97 fn test_mkfifo() {
98     let tempdir = tempdir().unwrap();
99     let mkfifo_fifo = tempdir.path().join("mkfifo_fifo");
100 
101     mkfifo(&mkfifo_fifo, Mode::S_IRUSR).unwrap();
102 
103     let stats = stat::stat(&mkfifo_fifo).unwrap();
104     let typ = stat::SFlag::from_bits_truncate(stats.st_mode);
105     assert!(typ == SFlag::S_IFIFO);
106 }
107 
108 #[test]
109 #[cfg(not(target_os = "redox"))]
test_mkfifo_directory()110 fn test_mkfifo_directory() {
111     // mkfifo should fail if a directory is given
112     assert!(mkfifo(&env::temp_dir(), Mode::S_IRUSR).is_err());
113 }
114 
115 #[test]
116 #[cfg(not(any(
117     target_os = "macos", target_os = "ios",
118     target_os = "android", target_os = "redox")))]
test_mkfifoat_none()119 fn test_mkfifoat_none() {
120     let _m = crate::CWD_LOCK.read().expect("Mutex got poisoned by another test");
121 
122     let tempdir = tempdir().unwrap();
123     let mkfifoat_fifo = tempdir.path().join("mkfifoat_fifo");
124 
125     mkfifoat(None, &mkfifoat_fifo, Mode::S_IRUSR).unwrap();
126 
127     let stats = stat::stat(&mkfifoat_fifo).unwrap();
128     let typ = stat::SFlag::from_bits_truncate(stats.st_mode);
129     assert_eq!(typ, SFlag::S_IFIFO);
130 }
131 
132 #[test]
133 #[cfg(not(any(
134     target_os = "macos", target_os = "ios",
135     target_os = "android", target_os = "redox")))]
test_mkfifoat()136 fn test_mkfifoat() {
137     let tempdir = tempdir().unwrap();
138     let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap();
139     let mkfifoat_name = "mkfifoat_name";
140 
141     mkfifoat(Some(dirfd), mkfifoat_name, Mode::S_IRUSR).unwrap();
142 
143     let stats = stat::fstatat(dirfd, mkfifoat_name, fcntl::AtFlags::empty()).unwrap();
144     let typ = stat::SFlag::from_bits_truncate(stats.st_mode);
145     assert_eq!(typ, SFlag::S_IFIFO);
146 }
147 
148 #[test]
149 #[cfg(not(any(
150     target_os = "macos", target_os = "ios",
151     target_os = "android", target_os = "redox")))]
test_mkfifoat_directory_none()152 fn test_mkfifoat_directory_none() {
153     let _m = crate::CWD_LOCK.read().expect("Mutex got poisoned by another test");
154 
155     // mkfifoat should fail if a directory is given
156     assert!(!mkfifoat(None, &env::temp_dir(), Mode::S_IRUSR).is_ok());
157 }
158 
159 #[test]
160 #[cfg(not(any(
161     target_os = "macos", target_os = "ios",
162     target_os = "android", target_os = "redox")))]
test_mkfifoat_directory()163 fn test_mkfifoat_directory() {
164     // mkfifoat should fail if a directory is given
165     let tempdir = tempdir().unwrap();
166     let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap();
167     let mkfifoat_dir = "mkfifoat_dir";
168     stat::mkdirat(dirfd, mkfifoat_dir, Mode::S_IRUSR).unwrap();
169 
170     assert!(!mkfifoat(Some(dirfd), mkfifoat_dir, Mode::S_IRUSR).is_ok());
171 }
172 
173 #[test]
test_getpid()174 fn test_getpid() {
175     let pid: ::libc::pid_t = getpid().into();
176     let ppid: ::libc::pid_t = getppid().into();
177     assert!(pid > 0);
178     assert!(ppid > 0);
179 }
180 
181 #[test]
182 #[cfg(not(target_os = "redox"))]
test_getsid()183 fn test_getsid() {
184     let none_sid: ::libc::pid_t = getsid(None).unwrap().into();
185     let pid_sid: ::libc::pid_t = getsid(Some(getpid())).unwrap().into();
186     assert!(none_sid > 0);
187     assert_eq!(none_sid, pid_sid);
188 }
189 
190 #[cfg(any(target_os = "linux", target_os = "android"))]
191 mod linux_android {
192     use nix::unistd::gettid;
193 
194     #[test]
test_gettid()195     fn test_gettid() {
196         let tid: ::libc::pid_t = gettid().into();
197         assert!(tid > 0);
198     }
199 }
200 
201 #[test]
202 // `getgroups()` and `setgroups()` do not behave as expected on Apple platforms
203 #[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "redox")))]
test_setgroups()204 fn test_setgroups() {
205     // Skip this test when not run as root as `setgroups()` requires root.
206     skip_if_not_root!("test_setgroups");
207 
208     let _m = crate::GROUPS_MTX.lock().expect("Mutex got poisoned by another test");
209 
210     // Save the existing groups
211     let old_groups = getgroups().unwrap();
212 
213     // Set some new made up groups
214     let groups = [Gid::from_raw(123), Gid::from_raw(456)];
215     setgroups(&groups).unwrap();
216 
217     let new_groups = getgroups().unwrap();
218     assert_eq!(new_groups, groups);
219 
220     // Revert back to the old groups
221     setgroups(&old_groups).unwrap();
222 }
223 
224 #[test]
225 // `getgroups()` and `setgroups()` do not behave as expected on Apple platforms
226 #[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "redox")))]
test_initgroups()227 fn test_initgroups() {
228     // Skip this test when not run as root as `initgroups()` and `setgroups()`
229     // require root.
230     skip_if_not_root!("test_initgroups");
231 
232     let _m = crate::GROUPS_MTX.lock().expect("Mutex got poisoned by another test");
233 
234     // Save the existing groups
235     let old_groups = getgroups().unwrap();
236 
237     // It doesn't matter if the root user is not called "root" or if a user
238     // called "root" doesn't exist. We are just checking that the extra,
239     // made-up group, `123`, is set.
240     // FIXME: Test the other half of initgroups' functionality: whether the
241     // groups that the user belongs to are also set.
242     let user = CString::new("root").unwrap();
243     let group = Gid::from_raw(123);
244     let group_list = getgrouplist(&user, group).unwrap();
245     assert!(group_list.contains(&group));
246 
247     initgroups(&user, group).unwrap();
248 
249     let new_groups = getgroups().unwrap();
250     assert_eq!(new_groups, group_list);
251 
252     // Revert back to the old groups
253     setgroups(&old_groups).unwrap();
254 }
255 
256 #[cfg(not(target_os = "redox"))]
257 macro_rules! execve_test_factory(
258     ($test_name:ident, $syscall:ident, $exe: expr $(, $pathname:expr, $flags:expr)*) => (
259     #[test]
260     fn $test_name() {
261         if "execveat" == stringify!($syscall) {
262             // Though undocumented, Docker's default seccomp profile seems to
263             // block this syscall.  https://github.com/nix-rust/nix/issues/1122
264             skip_if_seccomp!($test_name);
265         }
266 
267         let m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test");
268         // The `exec`d process will write to `writer`, and we'll read that
269         // data from `reader`.
270         let (reader, writer) = pipe().unwrap();
271 
272         // Safe: Child calls `exit`, `dup`, `close` and the provided `exec*` family function.
273         // NOTE: Technically, this makes the macro unsafe to use because you could pass anything.
274         //       The tests make sure not to do that, though.
275         match fork().unwrap() {
276             Child => {
277                 // Make `writer` be the stdout of the new process.
278                 dup2(writer, 1).unwrap();
279                 let r = $syscall(
280                     $exe,
281                     $(CString::new($pathname).unwrap().as_c_str(), )*
282                     &[CString::new(b"".as_ref()).unwrap().as_c_str(),
283                       CString::new(b"-c".as_ref()).unwrap().as_c_str(),
284                       CString::new(b"echo nix!!! && echo foo=$foo && echo baz=$baz"
285                                    .as_ref()).unwrap().as_c_str()],
286                     &[CString::new(b"foo=bar".as_ref()).unwrap().as_c_str(),
287                       CString::new(b"baz=quux".as_ref()).unwrap().as_c_str()]
288                     $(, $flags)*);
289                 let _ = std::io::stderr()
290                     .write_all(format!("{:?}", r).as_bytes());
291                 // Should only get here in event of error
292                 unsafe{ _exit(1) };
293             },
294             Parent { child } => {
295                 // Wait for the child to exit.
296                 let ws = waitpid(child, None);
297                 drop(m);
298                 assert_eq!(ws, Ok(WaitStatus::Exited(child, 0)));
299                 // Read 1024 bytes.
300                 let mut buf = [0u8; 1024];
301                 read(reader, &mut buf).unwrap();
302                 // It should contain the things we printed using `/bin/sh`.
303                 let string = String::from_utf8_lossy(&buf);
304                 assert!(string.contains("nix!!!"));
305                 assert!(string.contains("foo=bar"));
306                 assert!(string.contains("baz=quux"));
307             }
308         }
309     }
310     )
311 );
312 
313 cfg_if!{
314     if #[cfg(target_os = "android")] {
315         execve_test_factory!(test_execve, execve, CString::new("/system/bin/sh").unwrap().as_c_str());
316         execve_test_factory!(test_fexecve, fexecve, File::open("/system/bin/sh").unwrap().into_raw_fd());
317     } else if #[cfg(any(target_os = "freebsd",
318                         target_os = "linux"))] {
319         execve_test_factory!(test_execve, execve, CString::new("/bin/sh").unwrap().as_c_str());
320         execve_test_factory!(test_fexecve, fexecve, File::open("/bin/sh").unwrap().into_raw_fd());
321     } else if #[cfg(any(target_os = "dragonfly",
322                         target_os = "ios",
323                         target_os = "macos",
324                         target_os = "netbsd",
325                         target_os = "openbsd"))] {
326         execve_test_factory!(test_execve, execve, CString::new("/bin/sh").unwrap().as_c_str());
327         // No fexecve() on DragonFly, ios, macos, NetBSD, OpenBSD.
328         //
329         // Note for NetBSD and OpenBSD: although rust-lang/libc includes it
330         // (under unix/bsd/netbsdlike/) fexecve is not currently implemented on
331         // NetBSD nor on OpenBSD.
332     }
333 }
334 
335 #[cfg(any(target_os = "haiku", target_os = "linux", target_os = "openbsd"))]
336 execve_test_factory!(test_execvpe, execvpe, &CString::new("sh").unwrap());
337 
338 cfg_if!{
339     if #[cfg(target_os = "android")] {
340         use nix::fcntl::AtFlags;
341         execve_test_factory!(test_execveat_empty, execveat, File::open("/system/bin/sh").unwrap().into_raw_fd(),
342                              "", AtFlags::AT_EMPTY_PATH);
343         execve_test_factory!(test_execveat_relative, execveat, File::open("/system/bin/").unwrap().into_raw_fd(),
344                              "./sh", AtFlags::empty());
345         execve_test_factory!(test_execveat_absolute, execveat, File::open("/").unwrap().into_raw_fd(),
346                              "/system/bin/sh", AtFlags::empty());
347     } else if #[cfg(all(target_os = "linux"), any(target_arch ="x86_64", target_arch ="x86"))] {
348         use nix::fcntl::AtFlags;
349         execve_test_factory!(test_execveat_empty, execveat, File::open("/bin/sh").unwrap().into_raw_fd(),
350                              "", AtFlags::AT_EMPTY_PATH);
351         execve_test_factory!(test_execveat_relative, execveat, File::open("/bin/").unwrap().into_raw_fd(),
352                              "./sh", AtFlags::empty());
353         execve_test_factory!(test_execveat_absolute, execveat, File::open("/").unwrap().into_raw_fd(),
354                              "/bin/sh", AtFlags::empty());
355     }
356 }
357 
358 #[test]
test_fchdir()359 fn test_fchdir() {
360     // fchdir changes the process's cwd
361     let _dr = crate::DirRestore::new();
362 
363     let tmpdir = tempdir().unwrap();
364     let tmpdir_path = tmpdir.path().canonicalize().unwrap();
365     let tmpdir_fd = File::open(&tmpdir_path).unwrap().into_raw_fd();
366 
367     assert!(fchdir(tmpdir_fd).is_ok());
368     assert_eq!(getcwd().unwrap(), tmpdir_path);
369 
370     assert!(close(tmpdir_fd).is_ok());
371 }
372 
373 #[test]
test_getcwd()374 fn test_getcwd() {
375     // chdir changes the process's cwd
376     let _dr = crate::DirRestore::new();
377 
378     let tmpdir = tempdir().unwrap();
379     let tmpdir_path = tmpdir.path().canonicalize().unwrap();
380     assert!(chdir(&tmpdir_path).is_ok());
381     assert_eq!(getcwd().unwrap(), tmpdir_path);
382 
383     // make path 500 chars longer so that buffer doubling in getcwd
384     // kicks in.  Note: One path cannot be longer than 255 bytes
385     // (NAME_MAX) whole path cannot be longer than PATH_MAX (usually
386     // 4096 on linux, 1024 on macos)
387     let mut inner_tmp_dir = tmpdir_path.to_path_buf();
388     for _ in 0..5 {
389         let newdir = iter::repeat("a").take(100).collect::<String>();
390         inner_tmp_dir.push(newdir);
391         assert!(mkdir(inner_tmp_dir.as_path(), Mode::S_IRWXU).is_ok());
392     }
393     assert!(chdir(inner_tmp_dir.as_path()).is_ok());
394     assert_eq!(getcwd().unwrap(), inner_tmp_dir.as_path());
395 }
396 
397 #[test]
test_chown()398 fn test_chown() {
399     // Testing for anything other than our own UID/GID is hard.
400     let uid = Some(getuid());
401     let gid = Some(getgid());
402 
403     let tempdir = tempdir().unwrap();
404     let path = tempdir.path().join("file");
405     {
406         File::create(&path).unwrap();
407     }
408 
409     chown(&path, uid, gid).unwrap();
410     chown(&path, uid, None).unwrap();
411     chown(&path, None, gid).unwrap();
412 
413     fs::remove_file(&path).unwrap();
414     chown(&path, uid, gid).unwrap_err();
415 }
416 
417 #[test]
test_fchown()418 fn test_fchown() {
419     // Testing for anything other than our own UID/GID is hard.
420     let uid = Some(getuid());
421     let gid = Some(getgid());
422 
423     let path = tempfile().unwrap();
424     let fd = path.as_raw_fd();
425 
426     fchown(fd, uid, gid).unwrap();
427     fchown(fd, uid, None).unwrap();
428     fchown(fd, None, gid).unwrap();
429 
430     mem::drop(path);
431     fchown(fd, uid, gid).unwrap_err();
432 }
433 
434 #[test]
435 #[cfg(not(target_os = "redox"))]
test_fchownat()436 fn test_fchownat() {
437     let _dr = crate::DirRestore::new();
438     // Testing for anything other than our own UID/GID is hard.
439     let uid = Some(getuid());
440     let gid = Some(getgid());
441 
442     let tempdir = tempdir().unwrap();
443     let path = tempdir.path().join("file");
444     {
445         File::create(&path).unwrap();
446     }
447 
448     let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap();
449 
450     fchownat(Some(dirfd), "file", uid, gid, FchownatFlags::FollowSymlink).unwrap();
451 
452     chdir(tempdir.path()).unwrap();
453     fchownat(None, "file", uid, gid, FchownatFlags::FollowSymlink).unwrap();
454 
455     fs::remove_file(&path).unwrap();
456     fchownat(None, "file", uid, gid, FchownatFlags::FollowSymlink).unwrap_err();
457 }
458 
459 #[test]
test_lseek()460 fn test_lseek() {
461     const CONTENTS: &[u8] = b"abcdef123456";
462     let mut tmp = tempfile().unwrap();
463     tmp.write_all(CONTENTS).unwrap();
464     let tmpfd = tmp.into_raw_fd();
465 
466     let offset: off_t = 5;
467     lseek(tmpfd, offset, Whence::SeekSet).unwrap();
468 
469     let mut buf = [0u8; 7];
470     crate::read_exact(tmpfd, &mut buf);
471     assert_eq!(b"f123456", &buf);
472 
473     close(tmpfd).unwrap();
474 }
475 
476 #[cfg(any(target_os = "linux", target_os = "android"))]
477 #[test]
test_lseek64()478 fn test_lseek64() {
479     const CONTENTS: &[u8] = b"abcdef123456";
480     let mut tmp = tempfile().unwrap();
481     tmp.write_all(CONTENTS).unwrap();
482     let tmpfd = tmp.into_raw_fd();
483 
484     lseek64(tmpfd, 5, Whence::SeekSet).unwrap();
485 
486     let mut buf = [0u8; 7];
487     crate::read_exact(tmpfd, &mut buf);
488     assert_eq!(b"f123456", &buf);
489 
490     close(tmpfd).unwrap();
491 }
492 
493 cfg_if!{
494     if #[cfg(any(target_os = "android", target_os = "linux"))] {
495         macro_rules! require_acct{
496             () => {
497                 require_capability!(CAP_SYS_PACCT);
498             }
499         }
500     } else if #[cfg(target_os = "freebsd")] {
501         macro_rules! require_acct{
502             () => {
503                 skip_if_not_root!("test_acct");
504                 skip_if_jailed!("test_acct");
505             }
506         }
507     } else if #[cfg(not(target_os = "redox"))] {
508         macro_rules! require_acct{
509             () => {
510                 skip_if_not_root!("test_acct");
511             }
512         }
513     }
514 }
515 
516 #[test]
517 #[cfg(not(target_os = "redox"))]
test_acct()518 fn test_acct() {
519     use tempfile::NamedTempFile;
520     use std::process::Command;
521     use std::{thread, time};
522 
523     let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test");
524     require_acct!();
525 
526     let file = NamedTempFile::new().unwrap();
527     let path = file.path().to_str().unwrap();
528 
529     acct::enable(path).unwrap();
530 
531     loop {
532         Command::new("echo").arg("Hello world");
533         let len = fs::metadata(path).unwrap().len();
534         if len > 0 { break; }
535         thread::sleep(time::Duration::from_millis(10));
536     }
537     acct::disable().unwrap();
538 }
539 
540 #[test]
test_fpathconf_limited()541 fn test_fpathconf_limited() {
542     let f = tempfile().unwrap();
543     // AFAIK, PATH_MAX is limited on all platforms, so it makes a good test
544     let path_max = fpathconf(f.as_raw_fd(), PathconfVar::PATH_MAX);
545     assert!(path_max.expect("fpathconf failed").expect("PATH_MAX is unlimited") > 0);
546 }
547 
548 #[test]
test_pathconf_limited()549 fn test_pathconf_limited() {
550     // AFAIK, PATH_MAX is limited on all platforms, so it makes a good test
551     let path_max = pathconf("/", PathconfVar::PATH_MAX);
552     assert!(path_max.expect("pathconf failed").expect("PATH_MAX is unlimited") > 0);
553 }
554 
555 #[test]
test_sysconf_limited()556 fn test_sysconf_limited() {
557     // AFAIK, OPEN_MAX is limited on all platforms, so it makes a good test
558     let open_max = sysconf(SysconfVar::OPEN_MAX);
559     assert!(open_max.expect("sysconf failed").expect("OPEN_MAX is unlimited") > 0);
560 }
561 
562 #[cfg(target_os = "freebsd")]
563 #[test]
test_sysconf_unsupported()564 fn test_sysconf_unsupported() {
565     // I know of no sysconf variables that are unsupported everywhere, but
566     // _XOPEN_CRYPT is unsupported on FreeBSD 11.0, which is one of the platforms
567     // we test.
568     let open_max = sysconf(SysconfVar::_XOPEN_CRYPT);
569     assert!(open_max.expect("sysconf failed").is_none())
570 }
571 
572 // Test that we can create a pair of pipes.  No need to verify that they pass
573 // data; that's the domain of the OS, not nix.
574 #[test]
test_pipe()575 fn test_pipe() {
576     let (fd0, fd1) = pipe().unwrap();
577     let m0 = stat::SFlag::from_bits_truncate(stat::fstat(fd0).unwrap().st_mode);
578     // S_IFIFO means it's a pipe
579     assert_eq!(m0, SFlag::S_IFIFO);
580     let m1 = stat::SFlag::from_bits_truncate(stat::fstat(fd1).unwrap().st_mode);
581     assert_eq!(m1, SFlag::S_IFIFO);
582 }
583 
584 // pipe2(2) is the same as pipe(2), except it allows setting some flags.  Check
585 // that we can set a flag.
586 #[cfg(any(target_os = "android",
587           target_os = "dragonfly",
588           target_os = "emscripten",
589           target_os = "freebsd",
590           target_os = "linux",
591           target_os = "netbsd",
592           target_os = "openbsd",
593           target_os = "redox"))]
594 #[test]
test_pipe2()595 fn test_pipe2() {
596     let (fd0, fd1) = pipe2(OFlag::O_CLOEXEC).unwrap();
597     let f0 = FdFlag::from_bits_truncate(fcntl(fd0, FcntlArg::F_GETFD).unwrap());
598     assert!(f0.contains(FdFlag::FD_CLOEXEC));
599     let f1 = FdFlag::from_bits_truncate(fcntl(fd1, FcntlArg::F_GETFD).unwrap());
600     assert!(f1.contains(FdFlag::FD_CLOEXEC));
601 }
602 
603 #[test]
604 #[cfg(not(target_os = "redox"))]
test_truncate()605 fn test_truncate() {
606     let tempdir = tempdir().unwrap();
607     let path = tempdir.path().join("file");
608 
609     {
610         let mut tmp = File::create(&path).unwrap();
611         const CONTENTS: &[u8] = b"12345678";
612         tmp.write_all(CONTENTS).unwrap();
613     }
614 
615     truncate(&path, 4).unwrap();
616 
617     let metadata = fs::metadata(&path).unwrap();
618     assert_eq!(4, metadata.len());
619 }
620 
621 #[test]
test_ftruncate()622 fn test_ftruncate() {
623     let tempdir = tempdir().unwrap();
624     let path = tempdir.path().join("file");
625 
626     let tmpfd = {
627         let mut tmp = File::create(&path).unwrap();
628         const CONTENTS: &[u8] = b"12345678";
629         tmp.write_all(CONTENTS).unwrap();
630         tmp.into_raw_fd()
631     };
632 
633     ftruncate(tmpfd, 2).unwrap();
634     close(tmpfd).unwrap();
635 
636     let metadata = fs::metadata(&path).unwrap();
637     assert_eq!(2, metadata.len());
638 }
639 
640 // Used in `test_alarm`.
641 #[cfg(not(target_os = "redox"))]
642 static mut ALARM_CALLED: bool = false;
643 
644 // Used in `test_alarm`.
645 #[cfg(not(target_os = "redox"))]
alarm_signal_handler(raw_signal: libc::c_int)646 pub extern fn alarm_signal_handler(raw_signal: libc::c_int) {
647     assert_eq!(raw_signal, libc::SIGALRM, "unexpected signal: {}", raw_signal);
648     unsafe { ALARM_CALLED = true };
649 }
650 
651 #[test]
652 #[cfg(not(target_os = "redox"))]
test_alarm()653 fn test_alarm() {
654     let _m = crate::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test");
655 
656     let handler = SigHandler::Handler(alarm_signal_handler);
657     let signal_action = SigAction::new(handler, SaFlags::SA_RESTART, SigSet::empty());
658     let old_handler = unsafe {
659         sigaction(Signal::SIGALRM, &signal_action)
660             .expect("unable to set signal handler for alarm")
661     };
662 
663     // Set an alarm.
664     assert_eq!(alarm::set(60), None);
665 
666     // Overwriting an alarm should return the old alarm.
667     assert_eq!(alarm::set(1), Some(60));
668 
669     // We should be woken up after 1 second by the alarm, so we'll sleep for 2
670     // seconds to be sure.
671     sleep(2);
672     assert_eq!(unsafe { ALARM_CALLED }, true, "expected our alarm signal handler to be called");
673 
674     // Reset the signal.
675     unsafe {
676         sigaction(Signal::SIGALRM, &old_handler)
677             .expect("unable to set signal handler for alarm");
678     }
679 }
680 
681 #[test]
682 #[cfg(not(target_os = "redox"))]
test_canceling_alarm()683 fn test_canceling_alarm() {
684     let _m = crate::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test");
685 
686     assert_eq!(alarm::cancel(), None);
687 
688     assert_eq!(alarm::set(60), None);
689     assert_eq!(alarm::cancel(), Some(60));
690 }
691 
692 #[test]
693 #[cfg(not(target_os = "redox"))]
test_symlinkat()694 fn test_symlinkat() {
695     let _m = crate::CWD_LOCK.read().expect("Mutex got poisoned by another test");
696 
697     let tempdir = tempdir().unwrap();
698 
699     let target = tempdir.path().join("a");
700     let linkpath = tempdir.path().join("b");
701     symlinkat(&target, None, &linkpath).unwrap();
702     assert_eq!(
703         readlink(&linkpath).unwrap().to_str().unwrap(),
704         target.to_str().unwrap()
705     );
706 
707     let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap();
708     let target = "c";
709     let linkpath = "d";
710     symlinkat(target, Some(dirfd), linkpath).unwrap();
711     assert_eq!(
712         readlink(&tempdir.path().join(linkpath))
713             .unwrap()
714             .to_str()
715             .unwrap(),
716         target
717     );
718 }
719 
720 #[test]
721 #[cfg(not(target_os = "redox"))]
test_linkat_file()722 fn test_linkat_file() {
723     let tempdir = tempdir().unwrap();
724     let oldfilename = "foo.txt";
725     let oldfilepath = tempdir.path().join(oldfilename);
726 
727     let newfilename = "bar.txt";
728     let newfilepath = tempdir.path().join(newfilename);
729 
730     // Create file
731     File::create(&oldfilepath).unwrap();
732 
733     // Get file descriptor for base directory
734     let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
735 
736     // Attempt hard link file at relative path
737     linkat(Some(dirfd), oldfilename, Some(dirfd), newfilename, LinkatFlags::SymlinkFollow).unwrap();
738     assert!(newfilepath.exists());
739 }
740 
741 #[test]
742 #[cfg(not(target_os = "redox"))]
test_linkat_olddirfd_none()743 fn test_linkat_olddirfd_none() {
744     let _dr = crate::DirRestore::new();
745 
746     let tempdir_oldfile = tempdir().unwrap();
747     let oldfilename = "foo.txt";
748     let oldfilepath = tempdir_oldfile.path().join(oldfilename);
749 
750     let tempdir_newfile = tempdir().unwrap();
751     let newfilename = "bar.txt";
752     let newfilepath = tempdir_newfile.path().join(newfilename);
753 
754     // Create file
755     File::create(&oldfilepath).unwrap();
756 
757     // Get file descriptor for base directory of new file
758     let dirfd = fcntl::open(tempdir_newfile.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
759 
760     // Attempt hard link file using curent working directory as relative path for old file path
761     chdir(tempdir_oldfile.path()).unwrap();
762     linkat(None, oldfilename, Some(dirfd), newfilename, LinkatFlags::SymlinkFollow).unwrap();
763     assert!(newfilepath.exists());
764 }
765 
766 #[test]
767 #[cfg(not(target_os = "redox"))]
test_linkat_newdirfd_none()768 fn test_linkat_newdirfd_none() {
769     let _dr = crate::DirRestore::new();
770 
771     let tempdir_oldfile = tempdir().unwrap();
772     let oldfilename = "foo.txt";
773     let oldfilepath = tempdir_oldfile.path().join(oldfilename);
774 
775     let tempdir_newfile = tempdir().unwrap();
776     let newfilename = "bar.txt";
777     let newfilepath = tempdir_newfile.path().join(newfilename);
778 
779     // Create file
780     File::create(&oldfilepath).unwrap();
781 
782     // Get file descriptor for base directory of old file
783     let dirfd = fcntl::open(tempdir_oldfile.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
784 
785     // Attempt hard link file using current working directory as relative path for new file path
786     chdir(tempdir_newfile.path()).unwrap();
787     linkat(Some(dirfd), oldfilename, None, newfilename, LinkatFlags::SymlinkFollow).unwrap();
788     assert!(newfilepath.exists());
789 }
790 
791 #[test]
792 #[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "redox")))]
test_linkat_no_follow_symlink()793 fn test_linkat_no_follow_symlink() {
794     let _m = crate::CWD_LOCK.read().expect("Mutex got poisoned by another test");
795 
796     let tempdir = tempdir().unwrap();
797     let oldfilename = "foo.txt";
798     let oldfilepath = tempdir.path().join(oldfilename);
799 
800     let symoldfilename = "symfoo.txt";
801     let symoldfilepath = tempdir.path().join(symoldfilename);
802 
803     let newfilename = "nofollowsymbar.txt";
804     let newfilepath = tempdir.path().join(newfilename);
805 
806     // Create file
807     File::create(&oldfilepath).unwrap();
808 
809     // Create symlink to file
810     symlinkat(&oldfilepath, None, &symoldfilepath).unwrap();
811 
812     // Get file descriptor for base directory
813     let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
814 
815     // Attempt link symlink of file at relative path
816     linkat(Some(dirfd), symoldfilename, Some(dirfd), newfilename, LinkatFlags::NoSymlinkFollow).unwrap();
817 
818     // Assert newfile is actually a symlink to oldfile.
819     assert_eq!(
820         readlink(&newfilepath)
821             .unwrap()
822             .to_str()
823             .unwrap(),
824         oldfilepath.to_str().unwrap()
825     );
826 }
827 
828 #[test]
829 #[cfg(not(target_os = "redox"))]
test_linkat_follow_symlink()830 fn test_linkat_follow_symlink() {
831     let _m = crate::CWD_LOCK.read().expect("Mutex got poisoned by another test");
832 
833     let tempdir = tempdir().unwrap();
834     let oldfilename = "foo.txt";
835     let oldfilepath = tempdir.path().join(oldfilename);
836 
837     let symoldfilename = "symfoo.txt";
838     let symoldfilepath = tempdir.path().join(symoldfilename);
839 
840     let newfilename = "nofollowsymbar.txt";
841     let newfilepath = tempdir.path().join(newfilename);
842 
843     // Create file
844     File::create(&oldfilepath).unwrap();
845 
846     // Create symlink to file
847     symlinkat(&oldfilepath, None, &symoldfilepath).unwrap();
848 
849     // Get file descriptor for base directory
850     let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
851 
852     // Attempt link target of symlink of file at relative path
853     linkat(Some(dirfd), symoldfilename, Some(dirfd), newfilename, LinkatFlags::SymlinkFollow).unwrap();
854 
855     let newfilestat = stat::stat(&newfilepath).unwrap();
856 
857     // Check the file type of the new link
858     assert!((stat::SFlag::from_bits_truncate(newfilestat.st_mode) & SFlag::S_IFMT) ==  SFlag::S_IFREG);
859 
860     // Check the number of hard links to the original file
861     assert_eq!(newfilestat.st_nlink, 2);
862 }
863 
864 #[test]
865 #[cfg(not(target_os = "redox"))]
test_unlinkat_dir_noremovedir()866 fn test_unlinkat_dir_noremovedir() {
867     let tempdir = tempdir().unwrap();
868     let dirname = "foo_dir";
869     let dirpath = tempdir.path().join(dirname);
870 
871     // Create dir
872     DirBuilder::new().recursive(true).create(&dirpath).unwrap();
873 
874     // Get file descriptor for base directory
875     let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
876 
877     // Attempt unlink dir at relative path without proper flag
878     let err_result = unlinkat(Some(dirfd), dirname, UnlinkatFlags::NoRemoveDir).unwrap_err();
879     assert!(err_result == Error::Sys(Errno::EISDIR) || err_result == Error::Sys(Errno::EPERM));
880  }
881 
882 #[test]
883 #[cfg(not(target_os = "redox"))]
test_unlinkat_dir_removedir()884 fn test_unlinkat_dir_removedir() {
885     let tempdir = tempdir().unwrap();
886     let dirname = "foo_dir";
887     let dirpath = tempdir.path().join(dirname);
888 
889     // Create dir
890     DirBuilder::new().recursive(true).create(&dirpath).unwrap();
891 
892     // Get file descriptor for base directory
893     let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
894 
895     // Attempt unlink dir at relative path with proper flag
896     unlinkat(Some(dirfd), dirname, UnlinkatFlags::RemoveDir).unwrap();
897     assert!(!dirpath.exists());
898  }
899 
900 #[test]
901 #[cfg(not(target_os = "redox"))]
test_unlinkat_file()902 fn test_unlinkat_file() {
903     let tempdir = tempdir().unwrap();
904     let filename = "foo.txt";
905     let filepath = tempdir.path().join(filename);
906 
907     // Create file
908     File::create(&filepath).unwrap();
909 
910     // Get file descriptor for base directory
911     let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
912 
913     // Attempt unlink file at relative path
914     unlinkat(Some(dirfd), filename, UnlinkatFlags::NoRemoveDir).unwrap();
915     assert!(!filepath.exists());
916  }
917 
918 #[test]
test_access_not_existing()919 fn test_access_not_existing() {
920     let tempdir = tempdir().unwrap();
921     let dir = tempdir.path().join("does_not_exist.txt");
922     assert_eq!(access(&dir, AccessFlags::F_OK).err().unwrap().as_errno().unwrap(),
923                Errno::ENOENT);
924 }
925 
926 #[test]
test_access_file_exists()927 fn test_access_file_exists() {
928     let tempdir = tempdir().unwrap();
929     let path  = tempdir.path().join("does_exist.txt");
930     let _file = File::create(path.clone()).unwrap();
931     assert!(access(&path, AccessFlags::R_OK | AccessFlags::W_OK).is_ok());
932 }
933 
934 /// Tests setting the filesystem UID with `setfsuid`.
935 #[cfg(any(target_os = "linux", target_os = "android"))]
936 #[test]
test_setfsuid()937 fn test_setfsuid() {
938     use std::os::unix::fs::PermissionsExt;
939     use std::{fs, io, thread};
940     require_capability!(CAP_SETUID);
941 
942     // get the UID of the "nobody" user
943     let nobody = User::from_name("nobody").unwrap().unwrap();
944 
945     // create a temporary file with permissions '-rw-r-----'
946     let file = tempfile::NamedTempFile::new().unwrap();
947     let temp_path = file.into_temp_path();
948     let temp_path_2 = (&temp_path).to_path_buf();
949     let mut permissions = fs::metadata(&temp_path).unwrap().permissions();
950     permissions.set_mode(640);
951 
952     // spawn a new thread where to test setfsuid
953     thread::spawn(move || {
954         // set filesystem UID
955         let fuid = setfsuid(nobody.uid);
956         // trying to open the temporary file should fail with EACCES
957         let res = fs::File::open(&temp_path);
958         assert!(res.is_err());
959         assert_eq!(res.err().unwrap().kind(), io::ErrorKind::PermissionDenied);
960 
961         // assert fuid actually changes
962         let prev_fuid = setfsuid(Uid::from_raw(-1i32 as u32));
963         assert_ne!(prev_fuid, fuid);
964     })
965     .join()
966     .unwrap();
967 
968     // open the temporary file with the current thread filesystem UID
969     fs::File::open(temp_path_2).unwrap();
970 }
971 
972 #[test]
973 #[cfg(not(target_os = "redox"))]
test_ttyname()974 fn test_ttyname() {
975     let fd = posix_openpt(OFlag::O_RDWR).expect("posix_openpt failed");
976     assert!(fd.as_raw_fd() > 0);
977 
978     // on linux, we can just call ttyname on the pty master directly, but
979     // apparently osx requires that ttyname is called on a slave pty (can't
980     // find this documented anywhere, but it seems to empirically be the case)
981     grantpt(&fd).expect("grantpt failed");
982     unlockpt(&fd).expect("unlockpt failed");
983     let sname = unsafe { ptsname(&fd) }.expect("ptsname failed");
984     let fds = open(
985         Path::new(&sname),
986         OFlag::O_RDWR,
987         stat::Mode::empty(),
988     ).expect("open failed");
989     assert!(fds > 0);
990 
991     let name = ttyname(fds).expect("ttyname failed");
992     assert!(name.starts_with("/dev"));
993 }
994 
995 #[test]
996 #[cfg(not(target_os = "redox"))]
test_ttyname_not_pty()997 fn test_ttyname_not_pty() {
998     let fd = File::open("/dev/zero").unwrap();
999     assert!(fd.as_raw_fd() > 0);
1000     assert_eq!(ttyname(fd.as_raw_fd()), Err(Error::Sys(Errno::ENOTTY)));
1001 }
1002 
1003 #[test]
1004 #[cfg(all(not(target_os = "redox"), not(target_env = "musl")))]
test_ttyname_invalid_fd()1005 fn test_ttyname_invalid_fd() {
1006     assert_eq!(ttyname(-1), Err(Error::Sys(Errno::EBADF)));
1007 }
1008 
1009 #[test]
1010 #[cfg(all(not(target_os = "redox"), target_env = "musl"))]
test_ttyname_invalid_fd()1011 fn test_ttyname_invalid_fd() {
1012     assert_eq!(ttyname(-1), Err(Error::Sys(Errno::ENOTTY)));
1013 }
1014