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