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