1 #[cfg(not(target_os = "redox"))]
2 use nix::fcntl::{self, open, readlink};
3 use nix::fcntl::{fcntl, FcntlArg, FdFlag, 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 #[cfg(not(target_os = "redox"))]
14 use nix::Error;
15 use std::{env, iter};
16 #[cfg(not(target_os = "redox"))]
17 use std::ffi::CString;
18 #[cfg(not(target_os = "redox"))]
19 use std::fs::DirBuilder;
20 use std::fs::{self, File};
21 use std::io::Write;
22 use std::os::unix::prelude::*;
23 #[cfg(not(target_os = "redox"))]
24 use std::path::Path;
25 use tempfile::{tempdir, tempfile};
26 use libc::{_exit, off_t};
27 
28 use crate::*;
29 
30 #[test]
31 #[cfg(not(any(target_os = "netbsd")))]
test_fork_and_waitpid()32 fn test_fork_and_waitpid() {
33     let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test");
34 
35     // Safe: Child only calls `_exit`, which is signal-safe
36     match unsafe{fork()}.expect("Error: Fork Failed") {
37         Child => unsafe { _exit(0) },
38         Parent { child } => {
39             // assert that child was created and pid > 0
40             let child_raw: ::libc::pid_t = child.into();
41             assert!(child_raw > 0);
42             let wait_status = waitpid(child, None);
43             match wait_status {
44                 // assert that waitpid returned correct status and the pid is the one of the child
45                 Ok(WaitStatus::Exited(pid_t, _)) =>  assert_eq!(pid_t, child),
46 
47                 // panic, must never happen
48                 s @ Ok(_) => panic!("Child exited {:?}, should never happen", s),
49 
50                 // panic, waitpid should never fail
51                 Err(s) => panic!("Error: waitpid returned Err({:?}", s)
52             }
53 
54         },
55     }
56 }
57 
58 #[test]
test_wait()59 fn test_wait() {
60     // Grab FORK_MTX so wait doesn't reap a different test's child process
61     let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test");
62 
63     // Safe: Child only calls `_exit`, which is signal-safe
64     match unsafe{fork()}.expect("Error: Fork Failed") {
65         Child => unsafe { _exit(0) },
66         Parent { child } => {
67             let wait_status = wait();
68 
69             // just assert that (any) one child returns with WaitStatus::Exited
70             assert_eq!(wait_status, Ok(WaitStatus::Exited(child, 0)));
71         },
72     }
73 }
74 
75 #[test]
test_mkstemp()76 fn test_mkstemp() {
77     let mut path = env::temp_dir();
78     path.push("nix_tempfile.XXXXXX");
79 
80     let result = mkstemp(&path);
81     match result {
82         Ok((fd, path)) => {
83             close(fd).unwrap();
84             unlink(path.as_path()).unwrap();
85         },
86         Err(e) => panic!("mkstemp failed: {}", e)
87     }
88 }
89 
90 #[test]
test_mkstemp_directory()91 fn test_mkstemp_directory() {
92     // mkstemp should fail if a directory is given
93     assert!(mkstemp(&env::temp_dir()).is_err());
94 }
95 
96 #[test]
97 #[cfg(not(target_os = "redox"))]
test_mkfifo()98 fn test_mkfifo() {
99     let tempdir = tempdir().unwrap();
100     let mkfifo_fifo = tempdir.path().join("mkfifo_fifo");
101 
102     mkfifo(&mkfifo_fifo, Mode::S_IRUSR).unwrap();
103 
104     let stats = stat::stat(&mkfifo_fifo).unwrap();
105     let typ = stat::SFlag::from_bits_truncate(stats.st_mode);
106     assert!(typ == SFlag::S_IFIFO);
107 }
108 
109 #[test]
110 #[cfg(not(target_os = "redox"))]
test_mkfifo_directory()111 fn test_mkfifo_directory() {
112     // mkfifo should fail if a directory is given
113     assert!(mkfifo(&env::temp_dir(), Mode::S_IRUSR).is_err());
114 }
115 
116 #[test]
117 #[cfg(not(any(
118     target_os = "macos", target_os = "ios",
119     target_os = "android", target_os = "redox")))]
test_mkfifoat_none()120 fn test_mkfifoat_none() {
121     let _m = crate::CWD_LOCK.read().expect("Mutex got poisoned by another test");
122 
123     let tempdir = tempdir().unwrap();
124     let mkfifoat_fifo = tempdir.path().join("mkfifoat_fifo");
125 
126     mkfifoat(None, &mkfifoat_fifo, Mode::S_IRUSR).unwrap();
127 
128     let stats = stat::stat(&mkfifoat_fifo).unwrap();
129     let typ = stat::SFlag::from_bits_truncate(stats.st_mode);
130     assert_eq!(typ, SFlag::S_IFIFO);
131 }
132 
133 #[test]
134 #[cfg(not(any(
135     target_os = "macos", target_os = "ios",
136     target_os = "android", target_os = "redox")))]
test_mkfifoat()137 fn test_mkfifoat() {
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", target_os = "macos", target_os = "redox", target_os = "fuchsia")))]
test_initgroups()228 fn test_initgroups() {
229     // Skip this test when not run as root as `initgroups()` and `setgroups()`
230     // require root.
231     skip_if_not_root!("test_initgroups");
232 
233     let _m = crate::GROUPS_MTX.lock().expect("Mutex got poisoned by another test");
234 
235     // Save the existing groups
236     let old_groups = getgroups().unwrap();
237 
238     // It doesn't matter if the root user is not called "root" or if a user
239     // called "root" doesn't exist. We are just checking that the extra,
240     // made-up group, `123`, is set.
241     // FIXME: Test the other half of initgroups' functionality: whether the
242     // groups that the user belongs to are also set.
243     let user = CString::new("root").unwrap();
244     let group = Gid::from_raw(123);
245     let group_list = getgrouplist(&user, group).unwrap();
246     assert!(group_list.contains(&group));
247 
248     initgroups(&user, group).unwrap();
249 
250     let new_groups = getgroups().unwrap();
251     assert_eq!(new_groups, group_list);
252 
253     // Revert back to the old groups
254     setgroups(&old_groups).unwrap();
255 }
256 
257 #[cfg(not(target_os = "redox"))]
258 macro_rules! execve_test_factory(
259     ($test_name:ident, $syscall:ident, $exe: expr $(, $pathname:expr, $flags:expr)*) => (
260 
261     #[cfg(test)]
262     mod $test_name {
263     use std::ffi::CStr;
264     use super::*;
265 
266     const EMPTY: &'static [u8] = b"\0";
267     const DASH_C: &'static [u8] = b"-c\0";
268     const BIGARG: &'static [u8] = b"echo nix!!! && echo foo=$foo && echo baz=$baz\0";
269     const FOO: &'static [u8] = b"foo=bar\0";
270     const BAZ: &'static [u8] = b"baz=quux\0";
271 
272     fn syscall_cstr_ref() -> Result<std::convert::Infallible, nix::Error> {
273         $syscall(
274             $exe,
275             $(CString::new($pathname).unwrap().as_c_str(), )*
276             &[CStr::from_bytes_with_nul(EMPTY).unwrap(),
277               CStr::from_bytes_with_nul(DASH_C).unwrap(),
278               CStr::from_bytes_with_nul(BIGARG).unwrap()],
279             &[CStr::from_bytes_with_nul(FOO).unwrap(),
280               CStr::from_bytes_with_nul(BAZ).unwrap()]
281             $(, $flags)*)
282     }
283 
284     fn syscall_cstring() -> Result<std::convert::Infallible, nix::Error> {
285         $syscall(
286             $exe,
287             $(CString::new($pathname).unwrap().as_c_str(), )*
288             &[CString::from(CStr::from_bytes_with_nul(EMPTY).unwrap()),
289               CString::from(CStr::from_bytes_with_nul(DASH_C).unwrap()),
290               CString::from(CStr::from_bytes_with_nul(BIGARG).unwrap())],
291             &[CString::from(CStr::from_bytes_with_nul(FOO).unwrap()),
292               CString::from(CStr::from_bytes_with_nul(BAZ).unwrap())]
293             $(, $flags)*)
294     }
295 
296     fn common_test(syscall: fn() -> Result<std::convert::Infallible, nix::Error>) {
297         if "execveat" == stringify!($syscall) {
298             // Though undocumented, Docker's default seccomp profile seems to
299             // block this syscall.  https://github.com/nix-rust/nix/issues/1122
300             skip_if_seccomp!($test_name);
301         }
302 
303         let m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test");
304         // The `exec`d process will write to `writer`, and we'll read that
305         // data from `reader`.
306         let (reader, writer) = pipe().unwrap();
307 
308         // Safe: Child calls `exit`, `dup`, `close` and the provided `exec*` family function.
309         // NOTE: Technically, this makes the macro unsafe to use because you could pass anything.
310         //       The tests make sure not to do that, though.
311         match unsafe{fork()}.unwrap() {
312             Child => {
313                 // Make `writer` be the stdout of the new process.
314                 dup2(writer, 1).unwrap();
315                 let r = syscall();
316                 let _ = std::io::stderr()
317                     .write_all(format!("{:?}", r).as_bytes());
318                 // Should only get here in event of error
319                 unsafe{ _exit(1) };
320             },
321             Parent { child } => {
322                 // Wait for the child to exit.
323                 let ws = waitpid(child, None);
324                 drop(m);
325                 assert_eq!(ws, Ok(WaitStatus::Exited(child, 0)));
326                 // Read 1024 bytes.
327                 let mut buf = [0u8; 1024];
328                 read(reader, &mut buf).unwrap();
329                 // It should contain the things we printed using `/bin/sh`.
330                 let string = String::from_utf8_lossy(&buf);
331                 assert!(string.contains("nix!!!"));
332                 assert!(string.contains("foo=bar"));
333                 assert!(string.contains("baz=quux"));
334             }
335         }
336     }
337 
338     // These tests frequently fail on musl, probably due to
339         // https://github.com/nix-rust/nix/issues/555
340     #[cfg_attr(target_env = "musl", ignore)]
341     #[test]
342     fn test_cstr_ref() {
343         common_test(syscall_cstr_ref);
344     }
345 
346     // These tests frequently fail on musl, probably due to
347         // https://github.com/nix-rust/nix/issues/555
348     #[cfg_attr(target_env = "musl", ignore)]
349     #[test]
350     fn test_cstring() {
351         common_test(syscall_cstring);
352     }
353     }
354 
355     )
356 );
357 
358 cfg_if!{
359     if #[cfg(target_os = "android")] {
360         execve_test_factory!(test_execve, execve, CString::new("/system/bin/sh").unwrap().as_c_str());
361         execve_test_factory!(test_fexecve, fexecve, File::open("/system/bin/sh").unwrap().into_raw_fd());
362     } else if #[cfg(any(target_os = "freebsd",
363                         target_os = "linux"))] {
364         // These tests frequently fail on musl, probably due to
365         // https://github.com/nix-rust/nix/issues/555
366         execve_test_factory!(test_execve, execve, CString::new("/bin/sh").unwrap().as_c_str());
367         execve_test_factory!(test_fexecve, fexecve, File::open("/bin/sh").unwrap().into_raw_fd());
368     } else if #[cfg(any(target_os = "dragonfly",
369                         target_os = "ios",
370                         target_os = "macos",
371                         target_os = "netbsd",
372                         target_os = "openbsd"))] {
373         execve_test_factory!(test_execve, execve, CString::new("/bin/sh").unwrap().as_c_str());
374         // No fexecve() on DragonFly, ios, macos, NetBSD, OpenBSD.
375         //
376         // Note for NetBSD and OpenBSD: although rust-lang/libc includes it
377         // (under unix/bsd/netbsdlike/) fexecve is not currently implemented on
378         // NetBSD nor on OpenBSD.
379     }
380 }
381 
382 #[cfg(any(target_os = "haiku", target_os = "linux", target_os = "openbsd"))]
383 execve_test_factory!(test_execvpe, execvpe, &CString::new("sh").unwrap());
384 
385 cfg_if!{
386     if #[cfg(target_os = "android")] {
387         use nix::fcntl::AtFlags;
388         execve_test_factory!(test_execveat_empty, execveat,
389                              File::open("/system/bin/sh").unwrap().into_raw_fd(),
390                              "", AtFlags::AT_EMPTY_PATH);
391         execve_test_factory!(test_execveat_relative, execveat,
392                              File::open("/system/bin/").unwrap().into_raw_fd(),
393                              "./sh", AtFlags::empty());
394         execve_test_factory!(test_execveat_absolute, execveat,
395                              File::open("/").unwrap().into_raw_fd(),
396                              "/system/bin/sh", AtFlags::empty());
397     } else if #[cfg(all(target_os = "linux", any(target_arch ="x86_64", target_arch ="x86")))] {
398         use nix::fcntl::AtFlags;
399         execve_test_factory!(test_execveat_empty, execveat, File::open("/bin/sh").unwrap().into_raw_fd(),
400                              "", AtFlags::AT_EMPTY_PATH);
401         execve_test_factory!(test_execveat_relative, execveat, File::open("/bin/").unwrap().into_raw_fd(),
402                              "./sh", AtFlags::empty());
403         execve_test_factory!(test_execveat_absolute, execveat, File::open("/").unwrap().into_raw_fd(),
404                              "/bin/sh", AtFlags::empty());
405     }
406 }
407 
408 #[test]
409 #[cfg(not(target_os = "fuchsia"))]
test_fchdir()410 fn test_fchdir() {
411     // fchdir changes the process's cwd
412     let _dr = crate::DirRestore::new();
413 
414     let tmpdir = tempdir().unwrap();
415     let tmpdir_path = tmpdir.path().canonicalize().unwrap();
416     let tmpdir_fd = File::open(&tmpdir_path).unwrap().into_raw_fd();
417 
418     assert!(fchdir(tmpdir_fd).is_ok());
419     assert_eq!(getcwd().unwrap(), tmpdir_path);
420 
421     assert!(close(tmpdir_fd).is_ok());
422 }
423 
424 #[test]
test_getcwd()425 fn test_getcwd() {
426     // chdir changes the process's cwd
427     let _dr = crate::DirRestore::new();
428 
429     let tmpdir = tempdir().unwrap();
430     let tmpdir_path = tmpdir.path().canonicalize().unwrap();
431     assert!(chdir(&tmpdir_path).is_ok());
432     assert_eq!(getcwd().unwrap(), tmpdir_path);
433 
434     // make path 500 chars longer so that buffer doubling in getcwd
435     // kicks in.  Note: One path cannot be longer than 255 bytes
436     // (NAME_MAX) whole path cannot be longer than PATH_MAX (usually
437     // 4096 on linux, 1024 on macos)
438     let mut inner_tmp_dir = tmpdir_path.to_path_buf();
439     for _ in 0..5 {
440         let newdir = iter::repeat("a").take(100).collect::<String>();
441         inner_tmp_dir.push(newdir);
442         assert!(mkdir(inner_tmp_dir.as_path(), Mode::S_IRWXU).is_ok());
443     }
444     assert!(chdir(inner_tmp_dir.as_path()).is_ok());
445     assert_eq!(getcwd().unwrap(), inner_tmp_dir.as_path());
446 }
447 
448 #[test]
test_chown()449 fn test_chown() {
450     // Testing for anything other than our own UID/GID is hard.
451     let uid = Some(getuid());
452     let gid = Some(getgid());
453 
454     let tempdir = tempdir().unwrap();
455     let path = tempdir.path().join("file");
456     {
457         File::create(&path).unwrap();
458     }
459 
460     chown(&path, uid, gid).unwrap();
461     chown(&path, uid, None).unwrap();
462     chown(&path, None, gid).unwrap();
463 
464     fs::remove_file(&path).unwrap();
465     chown(&path, uid, gid).unwrap_err();
466 }
467 
468 #[test]
test_fchown()469 fn test_fchown() {
470     // Testing for anything other than our own UID/GID is hard.
471     let uid = Some(getuid());
472     let gid = Some(getgid());
473 
474     let path = tempfile().unwrap();
475     let fd = path.as_raw_fd();
476 
477     fchown(fd, uid, gid).unwrap();
478     fchown(fd, uid, None).unwrap();
479     fchown(fd, None, gid).unwrap();
480     fchown(999999999, uid, gid).unwrap_err();
481 }
482 
483 #[test]
484 #[cfg(not(target_os = "redox"))]
test_fchownat()485 fn test_fchownat() {
486     let _dr = crate::DirRestore::new();
487     // Testing for anything other than our own UID/GID is hard.
488     let uid = Some(getuid());
489     let gid = Some(getgid());
490 
491     let tempdir = tempdir().unwrap();
492     let path = tempdir.path().join("file");
493     {
494         File::create(&path).unwrap();
495     }
496 
497     let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap();
498 
499     fchownat(Some(dirfd), "file", uid, gid, FchownatFlags::FollowSymlink).unwrap();
500 
501     chdir(tempdir.path()).unwrap();
502     fchownat(None, "file", uid, gid, FchownatFlags::FollowSymlink).unwrap();
503 
504     fs::remove_file(&path).unwrap();
505     fchownat(None, "file", uid, gid, FchownatFlags::FollowSymlink).unwrap_err();
506 }
507 
508 #[test]
test_lseek()509 fn test_lseek() {
510     const CONTENTS: &[u8] = b"abcdef123456";
511     let mut tmp = tempfile().unwrap();
512     tmp.write_all(CONTENTS).unwrap();
513     let tmpfd = tmp.into_raw_fd();
514 
515     let offset: off_t = 5;
516     lseek(tmpfd, offset, Whence::SeekSet).unwrap();
517 
518     let mut buf = [0u8; 7];
519     crate::read_exact(tmpfd, &mut buf);
520     assert_eq!(b"f123456", &buf);
521 
522     close(tmpfd).unwrap();
523 }
524 
525 #[cfg(any(target_os = "linux", target_os = "android"))]
526 #[test]
test_lseek64()527 fn test_lseek64() {
528     const CONTENTS: &[u8] = b"abcdef123456";
529     let mut tmp = tempfile().unwrap();
530     tmp.write_all(CONTENTS).unwrap();
531     let tmpfd = tmp.into_raw_fd();
532 
533     lseek64(tmpfd, 5, Whence::SeekSet).unwrap();
534 
535     let mut buf = [0u8; 7];
536     crate::read_exact(tmpfd, &mut buf);
537     assert_eq!(b"f123456", &buf);
538 
539     close(tmpfd).unwrap();
540 }
541 
542 cfg_if!{
543     if #[cfg(any(target_os = "android", target_os = "linux"))] {
544         macro_rules! require_acct{
545             () => {
546                 require_capability!(CAP_SYS_PACCT);
547             }
548         }
549     } else if #[cfg(target_os = "freebsd")] {
550         macro_rules! require_acct{
551             () => {
552                 skip_if_not_root!("test_acct");
553                 skip_if_jailed!("test_acct");
554             }
555         }
556     } else if #[cfg(not(any(target_os = "redox", target_os = "fuchsia")))] {
557         macro_rules! require_acct{
558             () => {
559                 skip_if_not_root!("test_acct");
560             }
561         }
562     }
563 }
564 
565 #[test]
566 #[cfg(not(any(target_os = "redox", target_os = "fuchsia")))]
test_acct()567 fn test_acct() {
568     use tempfile::NamedTempFile;
569     use std::process::Command;
570     use std::{thread, time};
571 
572     let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test");
573     require_acct!();
574 
575     let file = NamedTempFile::new().unwrap();
576     let path = file.path().to_str().unwrap();
577 
578     acct::enable(path).unwrap();
579 
580     loop {
581         Command::new("echo").arg("Hello world");
582         let len = fs::metadata(path).unwrap().len();
583         if len > 0 { break; }
584         thread::sleep(time::Duration::from_millis(10));
585     }
586     acct::disable().unwrap();
587 }
588 
589 #[test]
test_fpathconf_limited()590 fn test_fpathconf_limited() {
591     let f = tempfile().unwrap();
592     // AFAIK, PATH_MAX is limited on all platforms, so it makes a good test
593     let path_max = fpathconf(f.as_raw_fd(), PathconfVar::PATH_MAX);
594     assert!(path_max.expect("fpathconf failed").expect("PATH_MAX is unlimited") > 0);
595 }
596 
597 #[test]
test_pathconf_limited()598 fn test_pathconf_limited() {
599     // AFAIK, PATH_MAX is limited on all platforms, so it makes a good test
600     let path_max = pathconf("/", PathconfVar::PATH_MAX);
601     assert!(path_max.expect("pathconf failed").expect("PATH_MAX is unlimited") > 0);
602 }
603 
604 #[test]
test_sysconf_limited()605 fn test_sysconf_limited() {
606     // AFAIK, OPEN_MAX is limited on all platforms, so it makes a good test
607     let open_max = sysconf(SysconfVar::OPEN_MAX);
608     assert!(open_max.expect("sysconf failed").expect("OPEN_MAX is unlimited") > 0);
609 }
610 
611 #[cfg(target_os = "freebsd")]
612 #[test]
test_sysconf_unsupported()613 fn test_sysconf_unsupported() {
614     // I know of no sysconf variables that are unsupported everywhere, but
615     // _XOPEN_CRYPT is unsupported on FreeBSD 11.0, which is one of the platforms
616     // we test.
617     let open_max = sysconf(SysconfVar::_XOPEN_CRYPT);
618     assert!(open_max.expect("sysconf failed").is_none())
619 }
620 
621 // Test that we can create a pair of pipes.  No need to verify that they pass
622 // data; that's the domain of the OS, not nix.
623 #[test]
test_pipe()624 fn test_pipe() {
625     let (fd0, fd1) = pipe().unwrap();
626     let m0 = stat::SFlag::from_bits_truncate(stat::fstat(fd0).unwrap().st_mode);
627     // S_IFIFO means it's a pipe
628     assert_eq!(m0, SFlag::S_IFIFO);
629     let m1 = stat::SFlag::from_bits_truncate(stat::fstat(fd1).unwrap().st_mode);
630     assert_eq!(m1, SFlag::S_IFIFO);
631 }
632 
633 // pipe2(2) is the same as pipe(2), except it allows setting some flags.  Check
634 // that we can set a flag.
635 #[cfg(any(target_os = "android",
636           target_os = "dragonfly",
637           target_os = "emscripten",
638           target_os = "freebsd",
639           target_os = "linux",
640           target_os = "netbsd",
641           target_os = "openbsd",
642           target_os = "redox"))]
643 #[test]
test_pipe2()644 fn test_pipe2() {
645     let (fd0, fd1) = pipe2(OFlag::O_CLOEXEC).unwrap();
646     let f0 = FdFlag::from_bits_truncate(fcntl(fd0, FcntlArg::F_GETFD).unwrap());
647     assert!(f0.contains(FdFlag::FD_CLOEXEC));
648     let f1 = FdFlag::from_bits_truncate(fcntl(fd1, FcntlArg::F_GETFD).unwrap());
649     assert!(f1.contains(FdFlag::FD_CLOEXEC));
650 }
651 
652 #[test]
653 #[cfg(not(any(target_os = "redox", target_os = "fuchsia")))]
test_truncate()654 fn test_truncate() {
655     let tempdir = tempdir().unwrap();
656     let path = tempdir.path().join("file");
657 
658     {
659         let mut tmp = File::create(&path).unwrap();
660         const CONTENTS: &[u8] = b"12345678";
661         tmp.write_all(CONTENTS).unwrap();
662     }
663 
664     truncate(&path, 4).unwrap();
665 
666     let metadata = fs::metadata(&path).unwrap();
667     assert_eq!(4, metadata.len());
668 }
669 
670 #[test]
test_ftruncate()671 fn test_ftruncate() {
672     let tempdir = tempdir().unwrap();
673     let path = tempdir.path().join("file");
674 
675     let tmpfd = {
676         let mut tmp = File::create(&path).unwrap();
677         const CONTENTS: &[u8] = b"12345678";
678         tmp.write_all(CONTENTS).unwrap();
679         tmp.into_raw_fd()
680     };
681 
682     ftruncate(tmpfd, 2).unwrap();
683     close(tmpfd).unwrap();
684 
685     let metadata = fs::metadata(&path).unwrap();
686     assert_eq!(2, metadata.len());
687 }
688 
689 // Used in `test_alarm`.
690 #[cfg(not(target_os = "redox"))]
691 static mut ALARM_CALLED: bool = false;
692 
693 // Used in `test_alarm`.
694 #[cfg(not(target_os = "redox"))]
alarm_signal_handler(raw_signal: libc::c_int)695 pub extern fn alarm_signal_handler(raw_signal: libc::c_int) {
696     assert_eq!(raw_signal, libc::SIGALRM, "unexpected signal: {}", raw_signal);
697     unsafe { ALARM_CALLED = true };
698 }
699 
700 #[test]
701 #[cfg(not(target_os = "redox"))]
test_alarm()702 fn test_alarm() {
703     use std::{
704         time::{Duration, Instant,},
705         thread
706     };
707 
708     // Maybe other tests that fork interfere with this one?
709     let _m = crate::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test");
710 
711     let handler = SigHandler::Handler(alarm_signal_handler);
712     let signal_action = SigAction::new(handler, SaFlags::SA_RESTART, SigSet::empty());
713     let old_handler = unsafe {
714         sigaction(Signal::SIGALRM, &signal_action)
715             .expect("unable to set signal handler for alarm")
716     };
717 
718     // Set an alarm.
719     assert_eq!(alarm::set(60), None);
720 
721     // Overwriting an alarm should return the old alarm.
722     assert_eq!(alarm::set(1), Some(60));
723 
724     // We should be woken up after 1 second by the alarm, so we'll sleep for 2
725     // seconds to be sure.
726     let starttime = Instant::now();
727     loop {
728         thread::sleep(Duration::from_millis(100));
729         if unsafe { ALARM_CALLED} {
730             break;
731         }
732         if starttime.elapsed() > Duration::from_secs(3) {
733             panic!("Timeout waiting for SIGALRM");
734         }
735     }
736 
737     // Reset the signal.
738     unsafe {
739         sigaction(Signal::SIGALRM, &old_handler)
740             .expect("unable to set signal handler for alarm");
741     }
742 }
743 
744 #[test]
745 #[cfg(not(target_os = "redox"))]
test_canceling_alarm()746 fn test_canceling_alarm() {
747     let _m = crate::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test");
748 
749     assert_eq!(alarm::cancel(), None);
750 
751     assert_eq!(alarm::set(60), None);
752     assert_eq!(alarm::cancel(), Some(60));
753 }
754 
755 #[test]
756 #[cfg(not(target_os = "redox"))]
test_symlinkat()757 fn test_symlinkat() {
758     let _m = crate::CWD_LOCK.read().expect("Mutex got poisoned by another test");
759 
760     let tempdir = tempdir().unwrap();
761 
762     let target = tempdir.path().join("a");
763     let linkpath = tempdir.path().join("b");
764     symlinkat(&target, None, &linkpath).unwrap();
765     assert_eq!(
766         readlink(&linkpath).unwrap().to_str().unwrap(),
767         target.to_str().unwrap()
768     );
769 
770     let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap();
771     let target = "c";
772     let linkpath = "d";
773     symlinkat(target, Some(dirfd), linkpath).unwrap();
774     assert_eq!(
775         readlink(&tempdir.path().join(linkpath))
776             .unwrap()
777             .to_str()
778             .unwrap(),
779         target
780     );
781 }
782 
783 #[test]
784 #[cfg(not(target_os = "redox"))]
test_linkat_file()785 fn test_linkat_file() {
786     let tempdir = tempdir().unwrap();
787     let oldfilename = "foo.txt";
788     let oldfilepath = tempdir.path().join(oldfilename);
789 
790     let newfilename = "bar.txt";
791     let newfilepath = tempdir.path().join(newfilename);
792 
793     // Create file
794     File::create(&oldfilepath).unwrap();
795 
796     // Get file descriptor for base directory
797     let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
798 
799     // Attempt hard link file at relative path
800     linkat(Some(dirfd), oldfilename, Some(dirfd), newfilename, LinkatFlags::SymlinkFollow).unwrap();
801     assert!(newfilepath.exists());
802 }
803 
804 #[test]
805 #[cfg(not(target_os = "redox"))]
test_linkat_olddirfd_none()806 fn test_linkat_olddirfd_none() {
807     let _dr = crate::DirRestore::new();
808 
809     let tempdir_oldfile = tempdir().unwrap();
810     let oldfilename = "foo.txt";
811     let oldfilepath = tempdir_oldfile.path().join(oldfilename);
812 
813     let tempdir_newfile = tempdir().unwrap();
814     let newfilename = "bar.txt";
815     let newfilepath = tempdir_newfile.path().join(newfilename);
816 
817     // Create file
818     File::create(&oldfilepath).unwrap();
819 
820     // Get file descriptor for base directory of new file
821     let dirfd = fcntl::open(tempdir_newfile.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
822 
823     // Attempt hard link file using curent working directory as relative path for old file path
824     chdir(tempdir_oldfile.path()).unwrap();
825     linkat(None, oldfilename, Some(dirfd), newfilename, LinkatFlags::SymlinkFollow).unwrap();
826     assert!(newfilepath.exists());
827 }
828 
829 #[test]
830 #[cfg(not(target_os = "redox"))]
test_linkat_newdirfd_none()831 fn test_linkat_newdirfd_none() {
832     let _dr = crate::DirRestore::new();
833 
834     let tempdir_oldfile = tempdir().unwrap();
835     let oldfilename = "foo.txt";
836     let oldfilepath = tempdir_oldfile.path().join(oldfilename);
837 
838     let tempdir_newfile = tempdir().unwrap();
839     let newfilename = "bar.txt";
840     let newfilepath = tempdir_newfile.path().join(newfilename);
841 
842     // Create file
843     File::create(&oldfilepath).unwrap();
844 
845     // Get file descriptor for base directory of old file
846     let dirfd = fcntl::open(tempdir_oldfile.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
847 
848     // Attempt hard link file using current working directory as relative path for new file path
849     chdir(tempdir_newfile.path()).unwrap();
850     linkat(Some(dirfd), oldfilename, None, newfilename, LinkatFlags::SymlinkFollow).unwrap();
851     assert!(newfilepath.exists());
852 }
853 
854 #[test]
855 #[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "redox")))]
test_linkat_no_follow_symlink()856 fn test_linkat_no_follow_symlink() {
857     let _m = crate::CWD_LOCK.read().expect("Mutex got poisoned by another test");
858 
859     let tempdir = tempdir().unwrap();
860     let oldfilename = "foo.txt";
861     let oldfilepath = tempdir.path().join(oldfilename);
862 
863     let symoldfilename = "symfoo.txt";
864     let symoldfilepath = tempdir.path().join(symoldfilename);
865 
866     let newfilename = "nofollowsymbar.txt";
867     let newfilepath = tempdir.path().join(newfilename);
868 
869     // Create file
870     File::create(&oldfilepath).unwrap();
871 
872     // Create symlink to file
873     symlinkat(&oldfilepath, None, &symoldfilepath).unwrap();
874 
875     // Get file descriptor for base directory
876     let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
877 
878     // Attempt link symlink of file at relative path
879     linkat(Some(dirfd), symoldfilename, Some(dirfd), newfilename, LinkatFlags::NoSymlinkFollow).unwrap();
880 
881     // Assert newfile is actually a symlink to oldfile.
882     assert_eq!(
883         readlink(&newfilepath)
884             .unwrap()
885             .to_str()
886             .unwrap(),
887         oldfilepath.to_str().unwrap()
888     );
889 }
890 
891 #[test]
892 #[cfg(not(target_os = "redox"))]
test_linkat_follow_symlink()893 fn test_linkat_follow_symlink() {
894     let _m = crate::CWD_LOCK.read().expect("Mutex got poisoned by another test");
895 
896     let tempdir = tempdir().unwrap();
897     let oldfilename = "foo.txt";
898     let oldfilepath = tempdir.path().join(oldfilename);
899 
900     let symoldfilename = "symfoo.txt";
901     let symoldfilepath = tempdir.path().join(symoldfilename);
902 
903     let newfilename = "nofollowsymbar.txt";
904     let newfilepath = tempdir.path().join(newfilename);
905 
906     // Create file
907     File::create(&oldfilepath).unwrap();
908 
909     // Create symlink to file
910     symlinkat(&oldfilepath, None, &symoldfilepath).unwrap();
911 
912     // Get file descriptor for base directory
913     let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
914 
915     // Attempt link target of symlink of file at relative path
916     linkat(Some(dirfd), symoldfilename, Some(dirfd), newfilename, LinkatFlags::SymlinkFollow).unwrap();
917 
918     let newfilestat = stat::stat(&newfilepath).unwrap();
919 
920     // Check the file type of the new link
921     assert!((stat::SFlag::from_bits_truncate(newfilestat.st_mode) & SFlag::S_IFMT) ==  SFlag::S_IFREG);
922 
923     // Check the number of hard links to the original file
924     assert_eq!(newfilestat.st_nlink, 2);
925 }
926 
927 #[test]
928 #[cfg(not(target_os = "redox"))]
test_unlinkat_dir_noremovedir()929 fn test_unlinkat_dir_noremovedir() {
930     let tempdir = tempdir().unwrap();
931     let dirname = "foo_dir";
932     let dirpath = tempdir.path().join(dirname);
933 
934     // Create dir
935     DirBuilder::new().recursive(true).create(&dirpath).unwrap();
936 
937     // Get file descriptor for base directory
938     let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
939 
940     // Attempt unlink dir at relative path without proper flag
941     let err_result = unlinkat(Some(dirfd), dirname, UnlinkatFlags::NoRemoveDir).unwrap_err();
942     assert!(err_result == Error::Sys(Errno::EISDIR) || err_result == Error::Sys(Errno::EPERM));
943  }
944 
945 #[test]
946 #[cfg(not(target_os = "redox"))]
test_unlinkat_dir_removedir()947 fn test_unlinkat_dir_removedir() {
948     let tempdir = tempdir().unwrap();
949     let dirname = "foo_dir";
950     let dirpath = tempdir.path().join(dirname);
951 
952     // Create dir
953     DirBuilder::new().recursive(true).create(&dirpath).unwrap();
954 
955     // Get file descriptor for base directory
956     let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
957 
958     // Attempt unlink dir at relative path with proper flag
959     unlinkat(Some(dirfd), dirname, UnlinkatFlags::RemoveDir).unwrap();
960     assert!(!dirpath.exists());
961  }
962 
963 #[test]
964 #[cfg(not(target_os = "redox"))]
test_unlinkat_file()965 fn test_unlinkat_file() {
966     let tempdir = tempdir().unwrap();
967     let filename = "foo.txt";
968     let filepath = tempdir.path().join(filename);
969 
970     // Create file
971     File::create(&filepath).unwrap();
972 
973     // Get file descriptor for base directory
974     let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
975 
976     // Attempt unlink file at relative path
977     unlinkat(Some(dirfd), filename, UnlinkatFlags::NoRemoveDir).unwrap();
978     assert!(!filepath.exists());
979  }
980 
981 #[test]
test_access_not_existing()982 fn test_access_not_existing() {
983     let tempdir = tempdir().unwrap();
984     let dir = tempdir.path().join("does_not_exist.txt");
985     assert_eq!(access(&dir, AccessFlags::F_OK).err().unwrap().as_errno().unwrap(),
986                Errno::ENOENT);
987 }
988 
989 #[test]
test_access_file_exists()990 fn test_access_file_exists() {
991     let tempdir = tempdir().unwrap();
992     let path  = tempdir.path().join("does_exist.txt");
993     let _file = File::create(path.clone()).unwrap();
994     assert!(access(&path, AccessFlags::R_OK | AccessFlags::W_OK).is_ok());
995 }
996 
997 /// Tests setting the filesystem UID with `setfsuid`.
998 #[cfg(any(target_os = "linux", target_os = "android"))]
999 #[test]
test_setfsuid()1000 fn test_setfsuid() {
1001     use std::os::unix::fs::PermissionsExt;
1002     use std::{fs, io, thread};
1003     require_capability!(CAP_SETUID);
1004 
1005     // get the UID of the "nobody" user
1006     let nobody = User::from_name("nobody").unwrap().unwrap();
1007 
1008     // create a temporary file with permissions '-rw-r-----'
1009     let file = tempfile::NamedTempFile::new_in("/var/tmp").unwrap();
1010     let temp_path = file.into_temp_path();
1011     dbg!(&temp_path);
1012     let temp_path_2 = (&temp_path).to_path_buf();
1013     let mut permissions = fs::metadata(&temp_path).unwrap().permissions();
1014     permissions.set_mode(640);
1015 
1016     // spawn a new thread where to test setfsuid
1017     thread::spawn(move || {
1018         // set filesystem UID
1019         let fuid = setfsuid(nobody.uid);
1020         // trying to open the temporary file should fail with EACCES
1021         let res = fs::File::open(&temp_path);
1022         assert!(res.is_err());
1023         assert_eq!(res.err().unwrap().kind(), io::ErrorKind::PermissionDenied);
1024 
1025         // assert fuid actually changes
1026         let prev_fuid = setfsuid(Uid::from_raw(-1i32 as u32));
1027         assert_ne!(prev_fuid, fuid);
1028     })
1029     .join()
1030     .unwrap();
1031 
1032     // open the temporary file with the current thread filesystem UID
1033     fs::File::open(temp_path_2).unwrap();
1034 }
1035 
1036 #[test]
1037 #[cfg(not(any(target_os = "redox", target_os = "fuchsia")))]
test_ttyname()1038 fn test_ttyname() {
1039     let fd = posix_openpt(OFlag::O_RDWR).expect("posix_openpt failed");
1040     assert!(fd.as_raw_fd() > 0);
1041 
1042     // on linux, we can just call ttyname on the pty master directly, but
1043     // apparently osx requires that ttyname is called on a slave pty (can't
1044     // find this documented anywhere, but it seems to empirically be the case)
1045     grantpt(&fd).expect("grantpt failed");
1046     unlockpt(&fd).expect("unlockpt failed");
1047     let sname = unsafe { ptsname(&fd) }.expect("ptsname failed");
1048     let fds = open(
1049         Path::new(&sname),
1050         OFlag::O_RDWR,
1051         stat::Mode::empty(),
1052     ).expect("open failed");
1053     assert!(fds > 0);
1054 
1055     let name = ttyname(fds).expect("ttyname failed");
1056     assert!(name.starts_with("/dev"));
1057 }
1058 
1059 #[test]
1060 #[cfg(not(any(target_os = "redox", target_os = "fuchsia")))]
test_ttyname_not_pty()1061 fn test_ttyname_not_pty() {
1062     let fd = File::open("/dev/zero").unwrap();
1063     assert!(fd.as_raw_fd() > 0);
1064     assert_eq!(ttyname(fd.as_raw_fd()), Err(Error::Sys(Errno::ENOTTY)));
1065 }
1066 
1067 #[test]
1068 #[cfg(not(any(target_os = "redox", target_os = "fuchsia")))]
test_ttyname_invalid_fd()1069 fn test_ttyname_invalid_fd() {
1070     assert_eq!(ttyname(-1), Err(Error::Sys(Errno::EBADF)));
1071 }
1072 
1073 #[test]
1074 #[cfg(any(
1075     target_os = "macos",
1076     target_os = "ios",
1077     target_os = "freebsd",
1078     target_os = "openbsd",
1079     target_os = "netbsd",
1080     target_os = "dragonfly",
1081 ))]
test_getpeereid()1082 fn test_getpeereid() {
1083     use std::os::unix::net::UnixStream;
1084     let (sock_a, sock_b) = UnixStream::pair().unwrap();
1085 
1086     let (uid_a, gid_a) = getpeereid(sock_a.as_raw_fd()).unwrap();
1087     let (uid_b, gid_b) = getpeereid(sock_b.as_raw_fd()).unwrap();
1088 
1089     let uid = geteuid();
1090     let gid = getegid();
1091 
1092     assert_eq!(uid, uid_a);
1093     assert_eq!(gid, gid_a);
1094     assert_eq!(uid_a, uid_b);
1095     assert_eq!(gid_a, gid_b);
1096 }
1097 
1098 #[test]
1099 #[cfg(any(
1100     target_os = "macos",
1101     target_os = "ios",
1102     target_os = "freebsd",
1103     target_os = "openbsd",
1104     target_os = "netbsd",
1105     target_os = "dragonfly",
1106 ))]
test_getpeereid_invalid_fd()1107 fn test_getpeereid_invalid_fd() {
1108     // getpeereid is not POSIX, so error codes are inconsistent between different Unices.
1109     assert!(getpeereid(-1).is_err());
1110 }
1111