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