1 use nix::fcntl::{fcntl, FcntlArg, FdFlag, open, OFlag, readlink};
2 use nix::unistd::*;
3 use nix::unistd::ForkResult::*;
4 use nix::sys::signal::{SaFlags, SigAction, SigHandler, SigSet, Signal, sigaction};
5 use nix::sys::wait::*;
6 use nix::sys::stat::{self, Mode, SFlag};
7 use std::{env, iter};
8 use std::ffi::CString;
9 use std::fs::{self, File};
10 use std::io::Write;
11 use std::os::unix::prelude::*;
12 use tempfile::{self, tempfile};
13 use libc::{self, _exit, off_t};
14 
15 #[test]
16 #[cfg(not(any(target_os = "netbsd")))]
test_fork_and_waitpid()17 fn test_fork_and_waitpid() {
18     let _m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
19 
20     // Safe: Child only calls `_exit`, which is signal-safe
21     match fork().expect("Error: Fork Failed") {
22         Child => unsafe { _exit(0) },
23         Parent { child } => {
24             // assert that child was created and pid > 0
25             let child_raw: ::libc::pid_t = child.into();
26             assert!(child_raw > 0);
27             let wait_status = waitpid(child, None);
28             match wait_status {
29                 // assert that waitpid returned correct status and the pid is the one of the child
30                 Ok(WaitStatus::Exited(pid_t, _)) =>  assert!(pid_t == child),
31 
32                 // panic, must never happen
33                 s @ Ok(_) => panic!("Child exited {:?}, should never happen", s),
34 
35                 // panic, waitpid should never fail
36                 Err(s) => panic!("Error: waitpid returned Err({:?}", s)
37             }
38 
39         },
40     }
41 }
42 
43 #[test]
test_wait()44 fn test_wait() {
45     // Grab FORK_MTX so wait doesn't reap a different test's child process
46     let _m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
47 
48     // Safe: Child only calls `_exit`, which is signal-safe
49     match fork().expect("Error: Fork Failed") {
50         Child => unsafe { _exit(0) },
51         Parent { child } => {
52             let wait_status = wait();
53 
54             // just assert that (any) one child returns with WaitStatus::Exited
55             assert_eq!(wait_status, Ok(WaitStatus::Exited(child, 0)));
56         },
57     }
58 }
59 
60 #[test]
test_mkstemp()61 fn test_mkstemp() {
62     let mut path = env::temp_dir();
63     path.push("nix_tempfile.XXXXXX");
64 
65     let result = mkstemp(&path);
66     match result {
67         Ok((fd, path)) => {
68             close(fd).unwrap();
69             unlink(path.as_path()).unwrap();
70         },
71         Err(e) => panic!("mkstemp failed: {}", e)
72     }
73 }
74 
75 #[test]
test_mkstemp_directory()76 fn test_mkstemp_directory() {
77     // mkstemp should fail if a directory is given
78     assert!(mkstemp(&env::temp_dir()).is_err());
79 }
80 
81 #[test]
test_mkfifo()82 fn test_mkfifo() {
83     let tempdir = tempfile::tempdir().unwrap();
84     let mkfifo_fifo = tempdir.path().join("mkfifo_fifo");
85 
86     mkfifo(&mkfifo_fifo, Mode::S_IRUSR).unwrap();
87 
88     let stats = stat::stat(&mkfifo_fifo).unwrap();
89     let typ = stat::SFlag::from_bits_truncate(stats.st_mode);
90     assert!(typ == SFlag::S_IFIFO);
91 }
92 
93 #[test]
test_mkfifo_directory()94 fn test_mkfifo_directory() {
95     // mkfifo should fail if a directory is given
96     assert!(mkfifo(&env::temp_dir(), Mode::S_IRUSR).is_err());
97 }
98 
99 #[test]
test_getpid()100 fn test_getpid() {
101     let pid: ::libc::pid_t = getpid().into();
102     let ppid: ::libc::pid_t = getppid().into();
103     assert!(pid > 0);
104     assert!(ppid > 0);
105 }
106 
107 #[test]
test_getsid()108 fn test_getsid() {
109     let none_sid: ::libc::pid_t = getsid(None).unwrap().into();
110     let pid_sid: ::libc::pid_t = getsid(Some(getpid())).unwrap().into();
111     assert!(none_sid > 0);
112     assert!(none_sid == pid_sid);
113 }
114 
115 #[cfg(any(target_os = "linux", target_os = "android"))]
116 mod linux_android {
117     use nix::unistd::gettid;
118 
119     #[test]
test_gettid()120     fn test_gettid() {
121         let tid: ::libc::pid_t = gettid().into();
122         assert!(tid > 0);
123     }
124 }
125 
126 #[test]
127 // `getgroups()` and `setgroups()` do not behave as expected on Apple platforms
128 #[cfg(not(any(target_os = "ios", target_os = "macos")))]
test_setgroups()129 fn test_setgroups() {
130     // Skip this test when not run as root as `setgroups()` requires root.
131     skip_if_not_root!("test_setgroups");
132 
133     let _m = ::GROUPS_MTX.lock().expect("Mutex got poisoned by another test");
134 
135     // Save the existing groups
136     let old_groups = getgroups().unwrap();
137 
138     // Set some new made up groups
139     let groups = [Gid::from_raw(123), Gid::from_raw(456)];
140     setgroups(&groups).unwrap();
141 
142     let new_groups = getgroups().unwrap();
143     assert_eq!(new_groups, groups);
144 
145     // Revert back to the old groups
146     setgroups(&old_groups).unwrap();
147 }
148 
149 #[test]
150 // `getgroups()` and `setgroups()` do not behave as expected on Apple platforms
151 #[cfg(not(any(target_os = "ios", target_os = "macos")))]
test_initgroups()152 fn test_initgroups() {
153     // Skip this test when not run as root as `initgroups()` and `setgroups()`
154     // require root.
155     skip_if_not_root!("test_initgroups");
156 
157     let _m = ::GROUPS_MTX.lock().expect("Mutex got poisoned by another test");
158 
159     // Save the existing groups
160     let old_groups = getgroups().unwrap();
161 
162     // It doesn't matter if the root user is not called "root" or if a user
163     // called "root" doesn't exist. We are just checking that the extra,
164     // made-up group, `123`, is set.
165     // FIXME: Test the other half of initgroups' functionality: whether the
166     // groups that the user belongs to are also set.
167     let user = CString::new("root").unwrap();
168     let group = Gid::from_raw(123);
169     let group_list = getgrouplist(&user, group).unwrap();
170     assert!(group_list.contains(&group));
171 
172     initgroups(&user, group).unwrap();
173 
174     let new_groups = getgroups().unwrap();
175     assert_eq!(new_groups, group_list);
176 
177     // Revert back to the old groups
178     setgroups(&old_groups).unwrap();
179 }
180 
181 macro_rules! execve_test_factory(
182     ($test_name:ident, $syscall:ident, $exe: expr $(, $pathname:expr, $flags:expr)*) => (
183     #[test]
184     fn $test_name() {
185         let _m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
186         // The `exec`d process will write to `writer`, and we'll read that
187         // data from `reader`.
188         let (reader, writer) = pipe().unwrap();
189 
190         // Safe: Child calls `exit`, `dup`, `close` and the provided `exec*` family function.
191         // NOTE: Technically, this makes the macro unsafe to use because you could pass anything.
192         //       The tests make sure not to do that, though.
193         match fork().unwrap() {
194             Child => {
195                 // Close stdout.
196                 close(1).unwrap();
197                 // Make `writer` be the stdout of the new process.
198                 dup(writer).unwrap();
199                 // exec!
200                 $syscall(
201                     $exe,
202                     $(&CString::new($pathname).unwrap(), )*
203                     &[CString::new(b"".as_ref()).unwrap(),
204                       CString::new(b"-c".as_ref()).unwrap(),
205                       CString::new(b"echo nix!!! && echo foo=$foo && echo baz=$baz"
206                                    .as_ref()).unwrap()],
207                     &[CString::new(b"foo=bar".as_ref()).unwrap(),
208                       CString::new(b"baz=quux".as_ref()).unwrap()]
209                     $(, $flags)*).unwrap();
210             },
211             Parent { child } => {
212                 // Wait for the child to exit.
213                 waitpid(child, None).unwrap();
214                 // Read 1024 bytes.
215                 let mut buf = [0u8; 1024];
216                 read(reader, &mut buf).unwrap();
217                 // It should contain the things we printed using `/bin/sh`.
218                 let string = String::from_utf8_lossy(&buf);
219                 assert!(string.contains("nix!!!"));
220                 assert!(string.contains("foo=bar"));
221                 assert!(string.contains("baz=quux"));
222             }
223         }
224     }
225     )
226 );
227 
228 cfg_if!{
229     if #[cfg(target_os = "android")] {
230         execve_test_factory!(test_execve, execve, &CString::new("/system/bin/sh").unwrap());
231         execve_test_factory!(test_fexecve, fexecve, File::open("/system/bin/sh").unwrap().into_raw_fd());
232     } else if #[cfg(any(target_os = "freebsd",
233                         target_os = "linux"))] {
234         execve_test_factory!(test_execve, execve, &CString::new("/bin/sh").unwrap());
235         execve_test_factory!(test_fexecve, fexecve, File::open("/bin/sh").unwrap().into_raw_fd());
236     } else if #[cfg(any(target_os = "dragonfly",
237                         target_os = "ios",
238                         target_os = "macos",
239                         target_os = "netbsd",
240                         target_os = "openbsd"))] {
241         execve_test_factory!(test_execve, execve, &CString::new("/bin/sh").unwrap());
242         // No fexecve() on DragonFly, ios, macos, NetBSD, OpenBSD.
243         //
244         // Note for NetBSD and OpenBSD: although rust-lang/libc includes it
245         // (under unix/bsd/netbsdlike/) fexecve is not currently implemented on
246         // NetBSD nor on OpenBSD.
247     }
248 }
249 
250 #[cfg(any(target_os = "haiku", target_os = "linux", target_os = "openbsd"))]
251 execve_test_factory!(test_execvpe, execvpe, &CString::new("sh").unwrap());
252 
253 cfg_if!{
254     if #[cfg(target_os = "android")] {
255         use nix::fcntl::AtFlags;
256         execve_test_factory!(test_execveat_empty, execveat, File::open("/system/bin/sh").unwrap().into_raw_fd(),
257                              "", AtFlags::AT_EMPTY_PATH);
258         execve_test_factory!(test_execveat_relative, execveat, File::open("/system/bin/").unwrap().into_raw_fd(),
259                              "./sh", AtFlags::empty());
260         execve_test_factory!(test_execveat_absolute, execveat, File::open("/").unwrap().into_raw_fd(),
261                              "/system/bin/sh", AtFlags::empty());
262     } else if #[cfg(all(target_os = "linux"), any(target_arch ="x86_64", target_arch ="x86"))] {
263         use nix::fcntl::AtFlags;
264         execve_test_factory!(test_execveat_empty, execveat, File::open("/bin/sh").unwrap().into_raw_fd(),
265                              "", AtFlags::AT_EMPTY_PATH);
266         execve_test_factory!(test_execveat_relative, execveat, File::open("/bin/").unwrap().into_raw_fd(),
267                              "./sh", AtFlags::empty());
268         execve_test_factory!(test_execveat_absolute, execveat, File::open("/").unwrap().into_raw_fd(),
269                              "/bin/sh", AtFlags::empty());
270     }
271 }
272 
273 #[test]
test_fchdir()274 fn test_fchdir() {
275     // fchdir changes the process's cwd
276     let _m = ::CWD_MTX.lock().expect("Mutex got poisoned by another test");
277 
278     let tmpdir = tempfile::tempdir().unwrap();
279     let tmpdir_path = tmpdir.path().canonicalize().unwrap();
280     let tmpdir_fd = File::open(&tmpdir_path).unwrap().into_raw_fd();
281 
282     assert!(fchdir(tmpdir_fd).is_ok());
283     assert_eq!(getcwd().unwrap(), tmpdir_path);
284 
285     assert!(close(tmpdir_fd).is_ok());
286 }
287 
288 #[test]
test_getcwd()289 fn test_getcwd() {
290     // chdir changes the process's cwd
291     let _m = ::CWD_MTX.lock().expect("Mutex got poisoned by another test");
292 
293     let tmpdir = tempfile::tempdir().unwrap();
294     let tmpdir_path = tmpdir.path().canonicalize().unwrap();
295     assert!(chdir(&tmpdir_path).is_ok());
296     assert_eq!(getcwd().unwrap(), tmpdir_path);
297 
298     // make path 500 chars longer so that buffer doubling in getcwd
299     // kicks in.  Note: One path cannot be longer than 255 bytes
300     // (NAME_MAX) whole path cannot be longer than PATH_MAX (usually
301     // 4096 on linux, 1024 on macos)
302     let mut inner_tmp_dir = tmpdir_path.to_path_buf();
303     for _ in 0..5 {
304         let newdir = iter::repeat("a").take(100).collect::<String>();
305         inner_tmp_dir.push(newdir);
306         assert!(mkdir(inner_tmp_dir.as_path(), Mode::S_IRWXU).is_ok());
307     }
308     assert!(chdir(inner_tmp_dir.as_path()).is_ok());
309     assert_eq!(getcwd().unwrap(), inner_tmp_dir.as_path());
310 }
311 
312 #[test]
test_chown()313 fn test_chown() {
314     // Testing for anything other than our own UID/GID is hard.
315     let uid = Some(getuid());
316     let gid = Some(getgid());
317 
318     let tempdir = tempfile::tempdir().unwrap();
319     let path = tempdir.path().join("file");
320     {
321         File::create(&path).unwrap();
322     }
323 
324     chown(&path, uid, gid).unwrap();
325     chown(&path, uid, None).unwrap();
326     chown(&path, None, gid).unwrap();
327 
328     fs::remove_file(&path).unwrap();
329     chown(&path, uid, gid).unwrap_err();
330 }
331 
332 #[test]
test_fchownat()333 fn test_fchownat() {
334     // Testing for anything other than our own UID/GID is hard.
335     let uid = Some(getuid());
336     let gid = Some(getgid());
337 
338     let tempdir = tempfile::tempdir().unwrap();
339     let path = tempdir.path().join("file");
340     {
341         File::create(&path).unwrap();
342     }
343 
344     let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap();
345 
346     fchownat(Some(dirfd), "file", uid, gid, FchownatFlags::FollowSymlink).unwrap();
347 
348     chdir(tempdir.path()).unwrap();
349     fchownat(None, "file", uid, gid, FchownatFlags::FollowSymlink).unwrap();
350 
351     fs::remove_file(&path).unwrap();
352     fchownat(None, "file", uid, gid, FchownatFlags::FollowSymlink).unwrap_err();
353 }
354 
355 #[test]
test_lseek()356 fn test_lseek() {
357     const CONTENTS: &[u8] = b"abcdef123456";
358     let mut tmp = tempfile().unwrap();
359     tmp.write_all(CONTENTS).unwrap();
360     let tmpfd = tmp.into_raw_fd();
361 
362     let offset: off_t = 5;
363     lseek(tmpfd, offset, Whence::SeekSet).unwrap();
364 
365     let mut buf = [0u8; 7];
366     ::read_exact(tmpfd, &mut buf);
367     assert_eq!(b"f123456", &buf);
368 
369     close(tmpfd).unwrap();
370 }
371 
372 #[cfg(any(target_os = "linux", target_os = "android"))]
373 #[test]
test_lseek64()374 fn test_lseek64() {
375     const CONTENTS: &[u8] = b"abcdef123456";
376     let mut tmp = tempfile().unwrap();
377     tmp.write_all(CONTENTS).unwrap();
378     let tmpfd = tmp.into_raw_fd();
379 
380     lseek64(tmpfd, 5, Whence::SeekSet).unwrap();
381 
382     let mut buf = [0u8; 7];
383     ::read_exact(tmpfd, &mut buf);
384     assert_eq!(b"f123456", &buf);
385 
386     close(tmpfd).unwrap();
387 }
388 
389 // Skip on FreeBSD because FreeBSD's CI environment is jailed, and jails
390 // aren't allowed to use acct(2)
391 #[cfg(not(target_os = "freebsd"))]
392 #[test]
test_acct()393 fn test_acct() {
394     use tempfile::NamedTempFile;
395     use std::process::Command;
396     use std::{thread, time};
397 
398     skip_if_not_root!("test_acct");
399     let file = NamedTempFile::new().unwrap();
400     let path = file.path().to_str().unwrap();
401 
402     acct::enable(path).unwrap();
403     Command::new("echo").arg("Hello world");
404     acct::disable().unwrap();
405 
406     loop {
407         let len = fs::metadata(path).unwrap().len();
408         if len > 0 { break; }
409         thread::sleep(time::Duration::from_millis(10));
410     }
411 }
412 
413 #[test]
test_fpathconf_limited()414 fn test_fpathconf_limited() {
415     let f = tempfile().unwrap();
416     // AFAIK, PATH_MAX is limited on all platforms, so it makes a good test
417     let path_max = fpathconf(f.as_raw_fd(), PathconfVar::PATH_MAX);
418     assert!(path_max.expect("fpathconf failed").expect("PATH_MAX is unlimited") > 0);
419 }
420 
421 #[test]
test_pathconf_limited()422 fn test_pathconf_limited() {
423     // AFAIK, PATH_MAX is limited on all platforms, so it makes a good test
424     let path_max = pathconf("/", PathconfVar::PATH_MAX);
425     assert!(path_max.expect("pathconf failed").expect("PATH_MAX is unlimited") > 0);
426 }
427 
428 #[test]
test_sysconf_limited()429 fn test_sysconf_limited() {
430     // AFAIK, OPEN_MAX is limited on all platforms, so it makes a good test
431     let open_max = sysconf(SysconfVar::OPEN_MAX);
432     assert!(open_max.expect("sysconf failed").expect("OPEN_MAX is unlimited") > 0);
433 }
434 
435 #[cfg(target_os = "freebsd")]
436 #[test]
test_sysconf_unsupported()437 fn test_sysconf_unsupported() {
438     // I know of no sysconf variables that are unsupported everywhere, but
439     // _XOPEN_CRYPT is unsupported on FreeBSD 11.0, which is one of the platforms
440     // we test.
441     let open_max = sysconf(SysconfVar::_XOPEN_CRYPT);
442     assert!(open_max.expect("sysconf failed").is_none())
443 }
444 
445 // Test that we can create a pair of pipes.  No need to verify that they pass
446 // data; that's the domain of the OS, not nix.
447 #[test]
test_pipe()448 fn test_pipe() {
449     let (fd0, fd1) = pipe().unwrap();
450     let m0 = stat::SFlag::from_bits_truncate(stat::fstat(fd0).unwrap().st_mode);
451     // S_IFIFO means it's a pipe
452     assert_eq!(m0, SFlag::S_IFIFO);
453     let m1 = stat::SFlag::from_bits_truncate(stat::fstat(fd1).unwrap().st_mode);
454     assert_eq!(m1, SFlag::S_IFIFO);
455 }
456 
457 // pipe2(2) is the same as pipe(2), except it allows setting some flags.  Check
458 // that we can set a flag.
459 #[test]
test_pipe2()460 fn test_pipe2() {
461     let (fd0, fd1) = pipe2(OFlag::O_CLOEXEC).unwrap();
462     let f0 = FdFlag::from_bits_truncate(fcntl(fd0, FcntlArg::F_GETFD).unwrap());
463     assert!(f0.contains(FdFlag::FD_CLOEXEC));
464     let f1 = FdFlag::from_bits_truncate(fcntl(fd1, FcntlArg::F_GETFD).unwrap());
465     assert!(f1.contains(FdFlag::FD_CLOEXEC));
466 }
467 
468 #[test]
test_truncate()469 fn test_truncate() {
470     let tempdir = tempfile::tempdir().unwrap();
471     let path = tempdir.path().join("file");
472 
473     {
474         let mut tmp = File::create(&path).unwrap();
475         const CONTENTS: &[u8] = b"12345678";
476         tmp.write_all(CONTENTS).unwrap();
477     }
478 
479     truncate(&path, 4).unwrap();
480 
481     let metadata = fs::metadata(&path).unwrap();
482     assert_eq!(4, metadata.len());
483 }
484 
485 #[test]
test_ftruncate()486 fn test_ftruncate() {
487     let tempdir = tempfile::tempdir().unwrap();
488     let path = tempdir.path().join("file");
489 
490     let tmpfd = {
491         let mut tmp = File::create(&path).unwrap();
492         const CONTENTS: &[u8] = b"12345678";
493         tmp.write_all(CONTENTS).unwrap();
494         tmp.into_raw_fd()
495     };
496 
497     ftruncate(tmpfd, 2).unwrap();
498     close(tmpfd).unwrap();
499 
500     let metadata = fs::metadata(&path).unwrap();
501     assert_eq!(2, metadata.len());
502 }
503 
504 // Used in `test_alarm`.
505 static mut ALARM_CALLED: bool = false;
506 
507 // Used in `test_alarm`.
alarm_signal_handler(raw_signal: libc::c_int)508 pub extern fn alarm_signal_handler(raw_signal: libc::c_int) {
509     assert_eq!(raw_signal, libc::SIGALRM, "unexpected signal: {}", raw_signal);
510     unsafe { ALARM_CALLED = true };
511 }
512 
513 #[test]
test_alarm()514 fn test_alarm() {
515     let _m = ::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test");
516 
517     let handler = SigHandler::Handler(alarm_signal_handler);
518     let signal_action = SigAction::new(handler, SaFlags::SA_RESTART, SigSet::empty());
519     let old_handler = unsafe {
520         sigaction(Signal::SIGALRM, &signal_action)
521             .expect("unable to set signal handler for alarm")
522     };
523 
524     // Set an alarm.
525     assert_eq!(alarm::set(60), None);
526 
527     // Overwriting an alarm should return the old alarm.
528     assert_eq!(alarm::set(1), Some(60));
529 
530     // We should be woken up after 1 second by the alarm, so we'll sleep for 2
531     // seconds to be sure.
532     sleep(2);
533     assert_eq!(unsafe { ALARM_CALLED }, true, "expected our alarm signal handler to be called");
534 
535     // Reset the signal.
536     unsafe {
537         sigaction(Signal::SIGALRM, &old_handler)
538             .expect("unable to set signal handler for alarm");
539     }
540 }
541 
542 #[test]
test_canceling_alarm()543 fn test_canceling_alarm() {
544     let _m = ::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test");
545 
546     assert_eq!(alarm::cancel(), None);
547 
548     assert_eq!(alarm::set(60), None);
549     assert_eq!(alarm::cancel(), Some(60));
550 }
551 
552 #[test]
test_symlinkat()553 fn test_symlinkat() {
554     let mut buf = [0; 1024];
555     let tempdir = tempfile::tempdir().unwrap();
556 
557     let target = tempdir.path().join("a");
558     let linkpath = tempdir.path().join("b");
559     symlinkat(&target, None, &linkpath).unwrap();
560     assert_eq!(
561         readlink(&linkpath, &mut buf).unwrap().to_str().unwrap(),
562         target.to_str().unwrap()
563     );
564 
565     let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap();
566     let target = "c";
567     let linkpath = "d";
568     symlinkat(target, Some(dirfd), linkpath).unwrap();
569     assert_eq!(
570         readlink(&tempdir.path().join(linkpath), &mut buf)
571             .unwrap()
572             .to_str()
573             .unwrap(),
574         target
575     );
576 }
577