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