1 use nix::fcntl::{self, 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 nix::Error;
9 use std::{env, iter};
10 use std::ffi::CString;
11 use std::fs::{self, DirBuilder, File};
12 use std::io::Write;
13 use std::os::unix::prelude::*;
14 use tempfile::{self, tempfile};
15 use libc::{self, _exit, off_t};
16 
17 #[test]
18 #[cfg(not(any(target_os = "netbsd")))]
test_fork_and_waitpid()19 fn test_fork_and_waitpid() {
20     let _m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
21 
22     // Safe: Child only calls `_exit`, which is signal-safe
23     match fork().expect("Error: Fork Failed") {
24         Child => unsafe { _exit(0) },
25         Parent { child } => {
26             // assert that child was created and pid > 0
27             let child_raw: ::libc::pid_t = child.into();
28             assert!(child_raw > 0);
29             let wait_status = waitpid(child, None);
30             match wait_status {
31                 // assert that waitpid returned correct status and the pid is the one of the child
32                 Ok(WaitStatus::Exited(pid_t, _)) =>  assert_eq!(pid_t, child),
33 
34                 // panic, must never happen
35                 s @ Ok(_) => panic!("Child exited {:?}, should never happen", s),
36 
37                 // panic, waitpid should never fail
38                 Err(s) => panic!("Error: waitpid returned Err({:?}", s)
39             }
40 
41         },
42     }
43 }
44 
45 #[test]
test_wait()46 fn test_wait() {
47     // Grab FORK_MTX so wait doesn't reap a different test's child process
48     let _m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
49 
50     // Safe: Child only calls `_exit`, which is signal-safe
51     match fork().expect("Error: Fork Failed") {
52         Child => unsafe { _exit(0) },
53         Parent { child } => {
54             let wait_status = wait();
55 
56             // just assert that (any) one child returns with WaitStatus::Exited
57             assert_eq!(wait_status, Ok(WaitStatus::Exited(child, 0)));
58         },
59     }
60 }
61 
62 #[test]
test_mkstemp()63 fn test_mkstemp() {
64     let mut path = env::temp_dir();
65     path.push("nix_tempfile.XXXXXX");
66 
67     let result = mkstemp(&path);
68     match result {
69         Ok((fd, path)) => {
70             close(fd).unwrap();
71             unlink(path.as_path()).unwrap();
72         },
73         Err(e) => panic!("mkstemp failed: {}", e)
74     }
75 }
76 
77 #[test]
test_mkstemp_directory()78 fn test_mkstemp_directory() {
79     // mkstemp should fail if a directory is given
80     assert!(mkstemp(&env::temp_dir()).is_err());
81 }
82 
83 #[test]
test_mkfifo()84 fn test_mkfifo() {
85     let tempdir = tempfile::tempdir().unwrap();
86     let mkfifo_fifo = tempdir.path().join("mkfifo_fifo");
87 
88     mkfifo(&mkfifo_fifo, Mode::S_IRUSR).unwrap();
89 
90     let stats = stat::stat(&mkfifo_fifo).unwrap();
91     let typ = stat::SFlag::from_bits_truncate(stats.st_mode);
92     assert!(typ == SFlag::S_IFIFO);
93 }
94 
95 #[test]
test_mkfifo_directory()96 fn test_mkfifo_directory() {
97     // mkfifo should fail if a directory is given
98     assert!(mkfifo(&env::temp_dir(), Mode::S_IRUSR).is_err());
99 }
100 
101 #[test]
102 #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "android")))]
test_mkfifoat_none()103 fn test_mkfifoat_none() {
104     let _m = ::CWD_LOCK.read().expect("Mutex got poisoned by another test");
105 
106     let tempdir = tempfile::tempdir().unwrap();
107     let mkfifoat_fifo = tempdir.path().join("mkfifoat_fifo");
108 
109     mkfifoat(None, &mkfifoat_fifo, Mode::S_IRUSR).unwrap();
110 
111     let stats = stat::stat(&mkfifoat_fifo).unwrap();
112     let typ = stat::SFlag::from_bits_truncate(stats.st_mode);
113     assert_eq!(typ, SFlag::S_IFIFO);
114 }
115 
116 #[test]
117 #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "android")))]
test_mkfifoat()118 fn test_mkfifoat() {
119     let tempdir = tempfile::tempdir().unwrap();
120     let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap();
121     let mkfifoat_name = "mkfifoat_name";
122 
123     mkfifoat(Some(dirfd), mkfifoat_name, Mode::S_IRUSR).unwrap();
124 
125     let stats = stat::fstatat(dirfd, mkfifoat_name, fcntl::AtFlags::empty()).unwrap();
126     let typ = stat::SFlag::from_bits_truncate(stats.st_mode);
127     assert_eq!(typ, SFlag::S_IFIFO);
128 }
129 
130 #[test]
131 #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "android")))]
test_mkfifoat_directory_none()132 fn test_mkfifoat_directory_none() {
133     let _m = ::CWD_LOCK.read().expect("Mutex got poisoned by another test");
134 
135     // mkfifoat should fail if a directory is given
136     assert!(!mkfifoat(None, &env::temp_dir(), Mode::S_IRUSR).is_ok());
137 }
138 
139 #[test]
140 #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "android")))]
test_mkfifoat_directory()141 fn test_mkfifoat_directory() {
142     // mkfifoat should fail if a directory is given
143     let tempdir = tempfile::tempdir().unwrap();
144     let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap();
145     let mkfifoat_dir = "mkfifoat_dir";
146     stat::mkdirat(dirfd, mkfifoat_dir, Mode::S_IRUSR).unwrap();
147 
148     assert!(!mkfifoat(Some(dirfd), mkfifoat_dir, Mode::S_IRUSR).is_ok());
149 }
150 
151 #[test]
test_getpid()152 fn test_getpid() {
153     let pid: ::libc::pid_t = getpid().into();
154     let ppid: ::libc::pid_t = getppid().into();
155     assert!(pid > 0);
156     assert!(ppid > 0);
157 }
158 
159 #[test]
test_getsid()160 fn test_getsid() {
161     let none_sid: ::libc::pid_t = getsid(None).unwrap().into();
162     let pid_sid: ::libc::pid_t = getsid(Some(getpid())).unwrap().into();
163     assert!(none_sid > 0);
164     assert_eq!(none_sid, pid_sid);
165 }
166 
167 #[cfg(any(target_os = "linux", target_os = "android"))]
168 mod linux_android {
169     use nix::unistd::gettid;
170 
171     #[test]
test_gettid()172     fn test_gettid() {
173         let tid: ::libc::pid_t = gettid().into();
174         assert!(tid > 0);
175     }
176 }
177 
178 #[test]
179 // `getgroups()` and `setgroups()` do not behave as expected on Apple platforms
180 #[cfg(not(any(target_os = "ios", target_os = "macos")))]
test_setgroups()181 fn test_setgroups() {
182     // Skip this test when not run as root as `setgroups()` requires root.
183     skip_if_not_root!("test_setgroups");
184 
185     let _m = ::GROUPS_MTX.lock().expect("Mutex got poisoned by another test");
186 
187     // Save the existing groups
188     let old_groups = getgroups().unwrap();
189 
190     // Set some new made up groups
191     let groups = [Gid::from_raw(123), Gid::from_raw(456)];
192     setgroups(&groups).unwrap();
193 
194     let new_groups = getgroups().unwrap();
195     assert_eq!(new_groups, groups);
196 
197     // Revert back to the old groups
198     setgroups(&old_groups).unwrap();
199 }
200 
201 #[test]
202 // `getgroups()` and `setgroups()` do not behave as expected on Apple platforms
203 #[cfg(not(any(target_os = "ios", target_os = "macos")))]
test_initgroups()204 fn test_initgroups() {
205     // Skip this test when not run as root as `initgroups()` and `setgroups()`
206     // require root.
207     skip_if_not_root!("test_initgroups");
208 
209     let _m = ::GROUPS_MTX.lock().expect("Mutex got poisoned by another test");
210 
211     // Save the existing groups
212     let old_groups = getgroups().unwrap();
213 
214     // It doesn't matter if the root user is not called "root" or if a user
215     // called "root" doesn't exist. We are just checking that the extra,
216     // made-up group, `123`, is set.
217     // FIXME: Test the other half of initgroups' functionality: whether the
218     // groups that the user belongs to are also set.
219     let user = CString::new("root").unwrap();
220     let group = Gid::from_raw(123);
221     let group_list = getgrouplist(&user, group).unwrap();
222     assert!(group_list.contains(&group));
223 
224     initgroups(&user, group).unwrap();
225 
226     let new_groups = getgroups().unwrap();
227     assert_eq!(new_groups, group_list);
228 
229     // Revert back to the old groups
230     setgroups(&old_groups).unwrap();
231 }
232 
233 macro_rules! execve_test_factory(
234     ($test_name:ident, $syscall:ident, $exe: expr $(, $pathname:expr, $flags:expr)*) => (
235     #[test]
236     fn $test_name() {
237         if "execveat" == stringify!($syscall) {
238             // Though undocumented, Docker's default seccomp profile seems to
239             // block this syscall.  https://github.com/nix-rust/nix/issues/1122
240             skip_if_seccomp!($test_name);
241         }
242 
243         let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
244         // The `exec`d process will write to `writer`, and we'll read that
245         // data from `reader`.
246         let (reader, writer) = pipe().unwrap();
247 
248         // Safe: Child calls `exit`, `dup`, `close` and the provided `exec*` family function.
249         // NOTE: Technically, this makes the macro unsafe to use because you could pass anything.
250         //       The tests make sure not to do that, though.
251         match fork().unwrap() {
252             Child => {
253                 // Make `writer` be the stdout of the new process.
254                 dup2(writer, 1).unwrap();
255                 let r = $syscall(
256                     $exe,
257                     $(CString::new($pathname).unwrap().as_c_str(), )*
258                     &[CString::new(b"".as_ref()).unwrap().as_c_str(),
259                       CString::new(b"-c".as_ref()).unwrap().as_c_str(),
260                       CString::new(b"echo nix!!! && echo foo=$foo && echo baz=$baz"
261                                    .as_ref()).unwrap().as_c_str()],
262                     &[CString::new(b"foo=bar".as_ref()).unwrap().as_c_str(),
263                       CString::new(b"baz=quux".as_ref()).unwrap().as_c_str()]
264                     $(, $flags)*);
265                 let _ = std::io::stderr()
266                     .write_all(format!("{:?}", r).as_bytes());
267                 // Should only get here in event of error
268                 unsafe{ _exit(1) };
269             },
270             Parent { child } => {
271                 // Wait for the child to exit.
272                 let ws = waitpid(child, None);
273                 drop(m);
274                 assert_eq!(ws, Ok(WaitStatus::Exited(child, 0)));
275                 // Read 1024 bytes.
276                 let mut buf = [0u8; 1024];
277                 read(reader, &mut buf).unwrap();
278                 // It should contain the things we printed using `/bin/sh`.
279                 let string = String::from_utf8_lossy(&buf);
280                 assert!(string.contains("nix!!!"));
281                 assert!(string.contains("foo=bar"));
282                 assert!(string.contains("baz=quux"));
283             }
284         }
285     }
286     )
287 );
288 
289 cfg_if!{
290     if #[cfg(target_os = "android")] {
291         execve_test_factory!(test_execve, execve, CString::new("/system/bin/sh").unwrap().as_c_str());
292         execve_test_factory!(test_fexecve, fexecve, File::open("/system/bin/sh").unwrap().into_raw_fd());
293     } else if #[cfg(any(target_os = "freebsd",
294                         target_os = "linux"))] {
295         execve_test_factory!(test_execve, execve, CString::new("/bin/sh").unwrap().as_c_str());
296         execve_test_factory!(test_fexecve, fexecve, File::open("/bin/sh").unwrap().into_raw_fd());
297     } else if #[cfg(any(target_os = "dragonfly",
298                         target_os = "ios",
299                         target_os = "macos",
300                         target_os = "netbsd",
301                         target_os = "openbsd"))] {
302         execve_test_factory!(test_execve, execve, CString::new("/bin/sh").unwrap().as_c_str());
303         // No fexecve() on DragonFly, ios, macos, NetBSD, OpenBSD.
304         //
305         // Note for NetBSD and OpenBSD: although rust-lang/libc includes it
306         // (under unix/bsd/netbsdlike/) fexecve is not currently implemented on
307         // NetBSD nor on OpenBSD.
308     }
309 }
310 
311 #[cfg(any(target_os = "haiku", target_os = "linux", target_os = "openbsd"))]
312 execve_test_factory!(test_execvpe, execvpe, &CString::new("sh").unwrap());
313 
314 cfg_if!{
315     if #[cfg(target_os = "android")] {
316         use nix::fcntl::AtFlags;
317         execve_test_factory!(test_execveat_empty, execveat, File::open("/system/bin/sh").unwrap().into_raw_fd(),
318                              "", AtFlags::AT_EMPTY_PATH);
319         execve_test_factory!(test_execveat_relative, execveat, File::open("/system/bin/").unwrap().into_raw_fd(),
320                              "./sh", AtFlags::empty());
321         execve_test_factory!(test_execveat_absolute, execveat, File::open("/").unwrap().into_raw_fd(),
322                              "/system/bin/sh", AtFlags::empty());
323     } else if #[cfg(all(target_os = "linux"), any(target_arch ="x86_64", target_arch ="x86"))] {
324         use nix::fcntl::AtFlags;
325         execve_test_factory!(test_execveat_empty, execveat, File::open("/bin/sh").unwrap().into_raw_fd(),
326                              "", AtFlags::AT_EMPTY_PATH);
327         execve_test_factory!(test_execveat_relative, execveat, File::open("/bin/").unwrap().into_raw_fd(),
328                              "./sh", AtFlags::empty());
329         execve_test_factory!(test_execveat_absolute, execveat, File::open("/").unwrap().into_raw_fd(),
330                              "/bin/sh", AtFlags::empty());
331     }
332 }
333 
334 #[test]
test_fchdir()335 fn test_fchdir() {
336     // fchdir changes the process's cwd
337     let _dr = ::DirRestore::new();
338 
339     let tmpdir = tempfile::tempdir().unwrap();
340     let tmpdir_path = tmpdir.path().canonicalize().unwrap();
341     let tmpdir_fd = File::open(&tmpdir_path).unwrap().into_raw_fd();
342 
343     assert!(fchdir(tmpdir_fd).is_ok());
344     assert_eq!(getcwd().unwrap(), tmpdir_path);
345 
346     assert!(close(tmpdir_fd).is_ok());
347 }
348 
349 #[test]
test_getcwd()350 fn test_getcwd() {
351     // chdir changes the process's cwd
352     let _dr = ::DirRestore::new();
353 
354     let tmpdir = tempfile::tempdir().unwrap();
355     let tmpdir_path = tmpdir.path().canonicalize().unwrap();
356     assert!(chdir(&tmpdir_path).is_ok());
357     assert_eq!(getcwd().unwrap(), tmpdir_path);
358 
359     // make path 500 chars longer so that buffer doubling in getcwd
360     // kicks in.  Note: One path cannot be longer than 255 bytes
361     // (NAME_MAX) whole path cannot be longer than PATH_MAX (usually
362     // 4096 on linux, 1024 on macos)
363     let mut inner_tmp_dir = tmpdir_path.to_path_buf();
364     for _ in 0..5 {
365         let newdir = iter::repeat("a").take(100).collect::<String>();
366         inner_tmp_dir.push(newdir);
367         assert!(mkdir(inner_tmp_dir.as_path(), Mode::S_IRWXU).is_ok());
368     }
369     assert!(chdir(inner_tmp_dir.as_path()).is_ok());
370     assert_eq!(getcwd().unwrap(), inner_tmp_dir.as_path());
371 }
372 
373 #[test]
test_chown()374 fn test_chown() {
375     // Testing for anything other than our own UID/GID is hard.
376     let uid = Some(getuid());
377     let gid = Some(getgid());
378 
379     let tempdir = tempfile::tempdir().unwrap();
380     let path = tempdir.path().join("file");
381     {
382         File::create(&path).unwrap();
383     }
384 
385     chown(&path, uid, gid).unwrap();
386     chown(&path, uid, None).unwrap();
387     chown(&path, None, gid).unwrap();
388 
389     fs::remove_file(&path).unwrap();
390     chown(&path, uid, gid).unwrap_err();
391 }
392 
393 #[test]
test_fchownat()394 fn test_fchownat() {
395     let _dr = ::DirRestore::new();
396     // Testing for anything other than our own UID/GID is hard.
397     let uid = Some(getuid());
398     let gid = Some(getgid());
399 
400     let tempdir = tempfile::tempdir().unwrap();
401     let path = tempdir.path().join("file");
402     {
403         File::create(&path).unwrap();
404     }
405 
406     let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap();
407 
408     fchownat(Some(dirfd), "file", uid, gid, FchownatFlags::FollowSymlink).unwrap();
409 
410     chdir(tempdir.path()).unwrap();
411     fchownat(None, "file", uid, gid, FchownatFlags::FollowSymlink).unwrap();
412 
413     fs::remove_file(&path).unwrap();
414     fchownat(None, "file", uid, gid, FchownatFlags::FollowSymlink).unwrap_err();
415 }
416 
417 #[test]
test_lseek()418 fn test_lseek() {
419     const CONTENTS: &[u8] = b"abcdef123456";
420     let mut tmp = tempfile().unwrap();
421     tmp.write_all(CONTENTS).unwrap();
422     let tmpfd = tmp.into_raw_fd();
423 
424     let offset: off_t = 5;
425     lseek(tmpfd, offset, Whence::SeekSet).unwrap();
426 
427     let mut buf = [0u8; 7];
428     ::read_exact(tmpfd, &mut buf);
429     assert_eq!(b"f123456", &buf);
430 
431     close(tmpfd).unwrap();
432 }
433 
434 #[cfg(any(target_os = "linux", target_os = "android"))]
435 #[test]
test_lseek64()436 fn test_lseek64() {
437     const CONTENTS: &[u8] = b"abcdef123456";
438     let mut tmp = tempfile().unwrap();
439     tmp.write_all(CONTENTS).unwrap();
440     let tmpfd = tmp.into_raw_fd();
441 
442     lseek64(tmpfd, 5, Whence::SeekSet).unwrap();
443 
444     let mut buf = [0u8; 7];
445     ::read_exact(tmpfd, &mut buf);
446     assert_eq!(b"f123456", &buf);
447 
448     close(tmpfd).unwrap();
449 }
450 
451 cfg_if!{
452     if #[cfg(any(target_os = "android", target_os = "linux"))] {
453         macro_rules! require_acct{
454             () => {
455                 require_capability!(CAP_SYS_PACCT);
456             }
457         }
458     } else if #[cfg(target_os = "freebsd")] {
459         macro_rules! require_acct{
460             () => {
461                 skip_if_not_root!("test_acct");
462                 skip_if_jailed!("test_acct");
463             }
464         }
465     } else {
466         macro_rules! require_acct{
467             () => {
468                 skip_if_not_root!("test_acct");
469             }
470         }
471     }
472 }
473 
474 #[test]
test_acct()475 fn test_acct() {
476     use tempfile::NamedTempFile;
477     use std::process::Command;
478     use std::{thread, time};
479 
480     let _m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
481     require_acct!();
482 
483     let file = NamedTempFile::new().unwrap();
484     let path = file.path().to_str().unwrap();
485 
486     acct::enable(path).unwrap();
487 
488     loop {
489         Command::new("echo").arg("Hello world");
490         let len = fs::metadata(path).unwrap().len();
491         if len > 0 { break; }
492         thread::sleep(time::Duration::from_millis(10));
493     }
494     acct::disable().unwrap();
495 }
496 
497 #[test]
test_fpathconf_limited()498 fn test_fpathconf_limited() {
499     let f = tempfile().unwrap();
500     // AFAIK, PATH_MAX is limited on all platforms, so it makes a good test
501     let path_max = fpathconf(f.as_raw_fd(), PathconfVar::PATH_MAX);
502     assert!(path_max.expect("fpathconf failed").expect("PATH_MAX is unlimited") > 0);
503 }
504 
505 #[test]
test_pathconf_limited()506 fn test_pathconf_limited() {
507     // AFAIK, PATH_MAX is limited on all platforms, so it makes a good test
508     let path_max = pathconf("/", PathconfVar::PATH_MAX);
509     assert!(path_max.expect("pathconf failed").expect("PATH_MAX is unlimited") > 0);
510 }
511 
512 #[test]
test_sysconf_limited()513 fn test_sysconf_limited() {
514     // AFAIK, OPEN_MAX is limited on all platforms, so it makes a good test
515     let open_max = sysconf(SysconfVar::OPEN_MAX);
516     assert!(open_max.expect("sysconf failed").expect("OPEN_MAX is unlimited") > 0);
517 }
518 
519 #[cfg(target_os = "freebsd")]
520 #[test]
test_sysconf_unsupported()521 fn test_sysconf_unsupported() {
522     // I know of no sysconf variables that are unsupported everywhere, but
523     // _XOPEN_CRYPT is unsupported on FreeBSD 11.0, which is one of the platforms
524     // we test.
525     let open_max = sysconf(SysconfVar::_XOPEN_CRYPT);
526     assert!(open_max.expect("sysconf failed").is_none())
527 }
528 
529 // Test that we can create a pair of pipes.  No need to verify that they pass
530 // data; that's the domain of the OS, not nix.
531 #[test]
test_pipe()532 fn test_pipe() {
533     let (fd0, fd1) = pipe().unwrap();
534     let m0 = stat::SFlag::from_bits_truncate(stat::fstat(fd0).unwrap().st_mode);
535     // S_IFIFO means it's a pipe
536     assert_eq!(m0, SFlag::S_IFIFO);
537     let m1 = stat::SFlag::from_bits_truncate(stat::fstat(fd1).unwrap().st_mode);
538     assert_eq!(m1, SFlag::S_IFIFO);
539 }
540 
541 // pipe2(2) is the same as pipe(2), except it allows setting some flags.  Check
542 // that we can set a flag.
543 #[test]
test_pipe2()544 fn test_pipe2() {
545     let (fd0, fd1) = pipe2(OFlag::O_CLOEXEC).unwrap();
546     let f0 = FdFlag::from_bits_truncate(fcntl(fd0, FcntlArg::F_GETFD).unwrap());
547     assert!(f0.contains(FdFlag::FD_CLOEXEC));
548     let f1 = FdFlag::from_bits_truncate(fcntl(fd1, FcntlArg::F_GETFD).unwrap());
549     assert!(f1.contains(FdFlag::FD_CLOEXEC));
550 }
551 
552 #[test]
test_truncate()553 fn test_truncate() {
554     let tempdir = tempfile::tempdir().unwrap();
555     let path = tempdir.path().join("file");
556 
557     {
558         let mut tmp = File::create(&path).unwrap();
559         const CONTENTS: &[u8] = b"12345678";
560         tmp.write_all(CONTENTS).unwrap();
561     }
562 
563     truncate(&path, 4).unwrap();
564 
565     let metadata = fs::metadata(&path).unwrap();
566     assert_eq!(4, metadata.len());
567 }
568 
569 #[test]
test_ftruncate()570 fn test_ftruncate() {
571     let tempdir = tempfile::tempdir().unwrap();
572     let path = tempdir.path().join("file");
573 
574     let tmpfd = {
575         let mut tmp = File::create(&path).unwrap();
576         const CONTENTS: &[u8] = b"12345678";
577         tmp.write_all(CONTENTS).unwrap();
578         tmp.into_raw_fd()
579     };
580 
581     ftruncate(tmpfd, 2).unwrap();
582     close(tmpfd).unwrap();
583 
584     let metadata = fs::metadata(&path).unwrap();
585     assert_eq!(2, metadata.len());
586 }
587 
588 // Used in `test_alarm`.
589 static mut ALARM_CALLED: bool = false;
590 
591 // Used in `test_alarm`.
alarm_signal_handler(raw_signal: libc::c_int)592 pub extern fn alarm_signal_handler(raw_signal: libc::c_int) {
593     assert_eq!(raw_signal, libc::SIGALRM, "unexpected signal: {}", raw_signal);
594     unsafe { ALARM_CALLED = true };
595 }
596 
597 #[test]
test_alarm()598 fn test_alarm() {
599     let _m = ::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test");
600 
601     let handler = SigHandler::Handler(alarm_signal_handler);
602     let signal_action = SigAction::new(handler, SaFlags::SA_RESTART, SigSet::empty());
603     let old_handler = unsafe {
604         sigaction(Signal::SIGALRM, &signal_action)
605             .expect("unable to set signal handler for alarm")
606     };
607 
608     // Set an alarm.
609     assert_eq!(alarm::set(60), None);
610 
611     // Overwriting an alarm should return the old alarm.
612     assert_eq!(alarm::set(1), Some(60));
613 
614     // We should be woken up after 1 second by the alarm, so we'll sleep for 2
615     // seconds to be sure.
616     sleep(2);
617     assert_eq!(unsafe { ALARM_CALLED }, true, "expected our alarm signal handler to be called");
618 
619     // Reset the signal.
620     unsafe {
621         sigaction(Signal::SIGALRM, &old_handler)
622             .expect("unable to set signal handler for alarm");
623     }
624 }
625 
626 #[test]
test_canceling_alarm()627 fn test_canceling_alarm() {
628     let _m = ::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test");
629 
630     assert_eq!(alarm::cancel(), None);
631 
632     assert_eq!(alarm::set(60), None);
633     assert_eq!(alarm::cancel(), Some(60));
634 }
635 
636 #[test]
test_symlinkat()637 fn test_symlinkat() {
638     let _m = ::CWD_LOCK.read().expect("Mutex got poisoned by another test");
639 
640     let tempdir = tempfile::tempdir().unwrap();
641 
642     let target = tempdir.path().join("a");
643     let linkpath = tempdir.path().join("b");
644     symlinkat(&target, None, &linkpath).unwrap();
645     assert_eq!(
646         readlink(&linkpath).unwrap().to_str().unwrap(),
647         target.to_str().unwrap()
648     );
649 
650     let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap();
651     let target = "c";
652     let linkpath = "d";
653     symlinkat(target, Some(dirfd), linkpath).unwrap();
654     assert_eq!(
655         readlink(&tempdir.path().join(linkpath))
656             .unwrap()
657             .to_str()
658             .unwrap(),
659         target
660     );
661 }
662 
663 #[test]
test_linkat_file()664 fn test_linkat_file() {
665     let tempdir = tempfile::tempdir().unwrap();
666     let oldfilename = "foo.txt";
667     let oldfilepath = tempdir.path().join(oldfilename);
668 
669     let newfilename = "bar.txt";
670     let newfilepath = tempdir.path().join(newfilename);
671 
672     // Create file
673     File::create(&oldfilepath).unwrap();
674 
675     // Get file descriptor for base directory
676     let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
677 
678     // Attempt hard link file at relative path
679     linkat(Some(dirfd), oldfilename, Some(dirfd), newfilename, LinkatFlags::SymlinkFollow).unwrap();
680     assert!(newfilepath.exists());
681 }
682 
683 #[test]
test_linkat_olddirfd_none()684 fn test_linkat_olddirfd_none() {
685     let _dr = ::DirRestore::new();
686 
687     let tempdir_oldfile = tempfile::tempdir().unwrap();
688     let oldfilename = "foo.txt";
689     let oldfilepath = tempdir_oldfile.path().join(oldfilename);
690 
691     let tempdir_newfile = tempfile::tempdir().unwrap();
692     let newfilename = "bar.txt";
693     let newfilepath = tempdir_newfile.path().join(newfilename);
694 
695     // Create file
696     File::create(&oldfilepath).unwrap();
697 
698     // Get file descriptor for base directory of new file
699     let dirfd = fcntl::open(tempdir_newfile.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
700 
701     // Attempt hard link file using curent working directory as relative path for old file path
702     chdir(tempdir_oldfile.path()).unwrap();
703     linkat(None, oldfilename, Some(dirfd), newfilename, LinkatFlags::SymlinkFollow).unwrap();
704     assert!(newfilepath.exists());
705 }
706 
707 #[test]
test_linkat_newdirfd_none()708 fn test_linkat_newdirfd_none() {
709     let _dr = ::DirRestore::new();
710 
711     let tempdir_oldfile = tempfile::tempdir().unwrap();
712     let oldfilename = "foo.txt";
713     let oldfilepath = tempdir_oldfile.path().join(oldfilename);
714 
715     let tempdir_newfile = tempfile::tempdir().unwrap();
716     let newfilename = "bar.txt";
717     let newfilepath = tempdir_newfile.path().join(newfilename);
718 
719     // Create file
720     File::create(&oldfilepath).unwrap();
721 
722     // Get file descriptor for base directory of old file
723     let dirfd = fcntl::open(tempdir_oldfile.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
724 
725     // Attempt hard link file using current working directory as relative path for new file path
726     chdir(tempdir_newfile.path()).unwrap();
727     linkat(Some(dirfd), oldfilename, None, newfilename, LinkatFlags::SymlinkFollow).unwrap();
728     assert!(newfilepath.exists());
729 }
730 
731 #[test]
732 #[cfg(not(any(target_os = "ios", target_os = "macos")))]
test_linkat_no_follow_symlink()733 fn test_linkat_no_follow_symlink() {
734     let _m = ::CWD_LOCK.read().expect("Mutex got poisoned by another test");
735 
736     let tempdir = tempfile::tempdir().unwrap();
737     let oldfilename = "foo.txt";
738     let oldfilepath = tempdir.path().join(oldfilename);
739 
740     let symoldfilename = "symfoo.txt";
741     let symoldfilepath = tempdir.path().join(symoldfilename);
742 
743     let newfilename = "nofollowsymbar.txt";
744     let newfilepath = tempdir.path().join(newfilename);
745 
746     // Create file
747     File::create(&oldfilepath).unwrap();
748 
749     // Create symlink to file
750     symlinkat(&oldfilepath, None, &symoldfilepath).unwrap();
751 
752     // Get file descriptor for base directory
753     let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
754 
755     // Attempt link symlink of file at relative path
756     linkat(Some(dirfd), symoldfilename, Some(dirfd), newfilename, LinkatFlags::NoSymlinkFollow).unwrap();
757 
758     // Assert newfile is actually a symlink to oldfile.
759     assert_eq!(
760         readlink(&newfilepath)
761             .unwrap()
762             .to_str()
763             .unwrap(),
764         oldfilepath.to_str().unwrap()
765     );
766 }
767 
768 #[test]
test_linkat_follow_symlink()769 fn test_linkat_follow_symlink() {
770     let _m = ::CWD_LOCK.read().expect("Mutex got poisoned by another test");
771 
772     let tempdir = tempfile::tempdir().unwrap();
773     let oldfilename = "foo.txt";
774     let oldfilepath = tempdir.path().join(oldfilename);
775 
776     let symoldfilename = "symfoo.txt";
777     let symoldfilepath = tempdir.path().join(symoldfilename);
778 
779     let newfilename = "nofollowsymbar.txt";
780     let newfilepath = tempdir.path().join(newfilename);
781 
782     // Create file
783     File::create(&oldfilepath).unwrap();
784 
785     // Create symlink to file
786     symlinkat(&oldfilepath, None, &symoldfilepath).unwrap();
787 
788     // Get file descriptor for base directory
789     let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
790 
791     // Attempt link target of symlink of file at relative path
792     linkat(Some(dirfd), symoldfilename, Some(dirfd), newfilename, LinkatFlags::SymlinkFollow).unwrap();
793 
794     let newfilestat = stat::stat(&newfilepath).unwrap();
795 
796     // Check the file type of the new link
797     assert!((stat::SFlag::from_bits_truncate(newfilestat.st_mode) & SFlag::S_IFMT) ==  SFlag::S_IFREG);
798 
799     // Check the number of hard links to the original file
800     assert_eq!(newfilestat.st_nlink, 2);
801 }
802 
803 #[test]
test_unlinkat_dir_noremovedir()804 fn test_unlinkat_dir_noremovedir() {
805     let tempdir = tempfile::tempdir().unwrap();
806     let dirname = "foo_dir";
807     let dirpath = tempdir.path().join(dirname);
808 
809     // Create dir
810     DirBuilder::new().recursive(true).create(&dirpath).unwrap();
811 
812     // Get file descriptor for base directory
813     let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
814 
815     // Attempt unlink dir at relative path without proper flag
816     let err_result = unlinkat(Some(dirfd), dirname, UnlinkatFlags::NoRemoveDir).unwrap_err();
817     assert!(err_result == Error::Sys(Errno::EISDIR) || err_result == Error::Sys(Errno::EPERM));
818  }
819 
820 #[test]
test_unlinkat_dir_removedir()821 fn test_unlinkat_dir_removedir() {
822     let tempdir = tempfile::tempdir().unwrap();
823     let dirname = "foo_dir";
824     let dirpath = tempdir.path().join(dirname);
825 
826     // Create dir
827     DirBuilder::new().recursive(true).create(&dirpath).unwrap();
828 
829     // Get file descriptor for base directory
830     let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
831 
832     // Attempt unlink dir at relative path with proper flag
833     unlinkat(Some(dirfd), dirname, UnlinkatFlags::RemoveDir).unwrap();
834     assert!(!dirpath.exists());
835  }
836 
837 #[test]
test_unlinkat_file()838 fn test_unlinkat_file() {
839     let tempdir = tempfile::tempdir().unwrap();
840     let filename = "foo.txt";
841     let filepath = tempdir.path().join(filename);
842 
843     // Create file
844     File::create(&filepath).unwrap();
845 
846     // Get file descriptor for base directory
847     let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
848 
849     // Attempt unlink file at relative path
850     unlinkat(Some(dirfd), filename, UnlinkatFlags::NoRemoveDir).unwrap();
851     assert!(!filepath.exists());
852  }
853 
854 #[test]
test_access_not_existing()855 fn test_access_not_existing() {
856     let tempdir = tempfile::tempdir().unwrap();
857     let dir = tempdir.path().join("does_not_exist.txt");
858     assert_eq!(access(&dir, AccessFlags::F_OK).err().unwrap().as_errno().unwrap(),
859                Errno::ENOENT);
860 }
861 
862 #[test]
test_access_file_exists()863 fn test_access_file_exists() {
864     let tempdir = tempfile::tempdir().unwrap();
865     let path  = tempdir.path().join("does_exist.txt");
866     let _file = File::create(path.clone()).unwrap();
867     assert!(access(&path, AccessFlags::R_OK | AccessFlags::W_OK).is_ok());
868 }
869