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