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