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