1 | use libc::{_exit, mode_t, off_t}; |
2 | use nix::errno::Errno; |
3 | #[cfg (not(any(target_os = "redox" , target_os = "haiku" )))] |
4 | use nix::fcntl::readlink; |
5 | use nix::fcntl::OFlag; |
6 | #[cfg (not(target_os = "redox" ))] |
7 | use nix::fcntl::{self, open}; |
8 | #[cfg (not(any( |
9 | target_os = "redox" , |
10 | target_os = "fuchsia" , |
11 | target_os = "haiku" |
12 | )))] |
13 | use nix::pty::{grantpt, posix_openpt, ptsname, unlockpt}; |
14 | #[cfg (not(target_os = "redox" ))] |
15 | use nix::sys::signal::{ |
16 | sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal, |
17 | }; |
18 | use nix::sys::stat::{self, Mode, SFlag}; |
19 | use nix::sys::wait::*; |
20 | use nix::unistd::ForkResult::*; |
21 | use nix::unistd::*; |
22 | use std::env; |
23 | #[cfg (not(any(target_os = "fuchsia" , target_os = "redox" )))] |
24 | use std::ffi::CString; |
25 | #[cfg (not(target_os = "redox" ))] |
26 | use std::fs::DirBuilder; |
27 | use std::fs::{self, File}; |
28 | use std::io::Write; |
29 | use std::os::unix::prelude::*; |
30 | #[cfg (not(any( |
31 | target_os = "fuchsia" , |
32 | target_os = "redox" , |
33 | target_os = "haiku" |
34 | )))] |
35 | use std::path::Path; |
36 | use tempfile::{tempdir, tempfile}; |
37 | |
38 | use crate::*; |
39 | |
40 | #[test] |
41 | #[cfg (not(any(target_os = "netbsd" )))] |
42 | fn test_fork_and_waitpid() { |
43 | let _m = crate::FORK_MTX.lock(); |
44 | |
45 | // Safe: Child only calls `_exit`, which is signal-safe |
46 | match unsafe { fork() }.expect("Error: Fork Failed" ) { |
47 | Child => unsafe { _exit(0) }, |
48 | Parent { child } => { |
49 | // assert that child was created and pid > 0 |
50 | let child_raw: ::libc::pid_t = child.into(); |
51 | assert!(child_raw > 0); |
52 | let wait_status = waitpid(child, None); |
53 | match wait_status { |
54 | // assert that waitpid returned correct status and the pid is the one of the child |
55 | Ok(WaitStatus::Exited(pid_t, _)) => assert_eq!(pid_t, child), |
56 | |
57 | // panic, must never happen |
58 | s @ Ok(_) => { |
59 | panic!("Child exited {s:?}, should never happen" ) |
60 | } |
61 | |
62 | // panic, waitpid should never fail |
63 | Err(s) => panic!("Error: waitpid returned Err({s:?}" ), |
64 | } |
65 | } |
66 | } |
67 | } |
68 | |
69 | #[test] |
70 | fn test_wait() { |
71 | // Grab FORK_MTX so wait doesn't reap a different test's child process |
72 | let _m = crate::FORK_MTX.lock(); |
73 | |
74 | // Safe: Child only calls `_exit`, which is signal-safe |
75 | match unsafe { fork() }.expect("Error: Fork Failed" ) { |
76 | Child => unsafe { _exit(0) }, |
77 | Parent { child } => { |
78 | let wait_status = wait(); |
79 | |
80 | // just assert that (any) one child returns with WaitStatus::Exited |
81 | assert_eq!(wait_status, Ok(WaitStatus::Exited(child, 0))); |
82 | } |
83 | } |
84 | } |
85 | |
86 | #[test] |
87 | fn test_mkstemp() { |
88 | let mut path = env::temp_dir(); |
89 | path.push("nix_tempfile.XXXXXX" ); |
90 | |
91 | let result = mkstemp(&path); |
92 | match result { |
93 | Ok((fd, path)) => { |
94 | close(fd).unwrap(); |
95 | unlink(path.as_path()).unwrap(); |
96 | } |
97 | Err(e) => panic!("mkstemp failed: {e}" ), |
98 | } |
99 | } |
100 | |
101 | #[test] |
102 | fn test_mkstemp_directory() { |
103 | // mkstemp should fail if a directory is given |
104 | mkstemp(&env::temp_dir()).expect_err("assertion failed" ); |
105 | } |
106 | |
107 | #[test] |
108 | #[cfg (not(target_os = "redox" ))] |
109 | fn test_mkfifo() { |
110 | let tempdir = tempdir().unwrap(); |
111 | let mkfifo_fifo = tempdir.path().join("mkfifo_fifo" ); |
112 | |
113 | mkfifo(&mkfifo_fifo, Mode::S_IRUSR).unwrap(); |
114 | |
115 | let stats = stat::stat(&mkfifo_fifo).unwrap(); |
116 | let typ = stat::SFlag::from_bits_truncate(stats.st_mode as mode_t); |
117 | assert_eq!(typ, SFlag::S_IFIFO); |
118 | } |
119 | |
120 | #[test] |
121 | #[cfg (not(target_os = "redox" ))] |
122 | fn test_mkfifo_directory() { |
123 | // mkfifo should fail if a directory is given |
124 | mkfifo(&env::temp_dir(), Mode::S_IRUSR).expect_err("assertion failed" ); |
125 | } |
126 | |
127 | #[test] |
128 | #[cfg (not(any( |
129 | target_os = "macos" , |
130 | target_os = "ios" , |
131 | target_os = "android" , |
132 | target_os = "redox" , |
133 | target_os = "haiku" |
134 | )))] |
135 | fn test_mkfifoat_none() { |
136 | let _m = crate::CWD_LOCK.read(); |
137 | |
138 | let tempdir = tempdir().unwrap(); |
139 | let mkfifoat_fifo = tempdir.path().join("mkfifoat_fifo" ); |
140 | |
141 | mkfifoat(None, &mkfifoat_fifo, Mode::S_IRUSR).unwrap(); |
142 | |
143 | let stats = stat::stat(&mkfifoat_fifo).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" , |
151 | target_os = "ios" , |
152 | target_os = "android" , |
153 | target_os = "redox" , |
154 | target_os = "haiku" |
155 | )))] |
156 | fn test_mkfifoat() { |
157 | use nix::fcntl; |
158 | |
159 | let tempdir = tempdir().unwrap(); |
160 | let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap(); |
161 | let mkfifoat_name = "mkfifoat_name" ; |
162 | |
163 | mkfifoat(Some(dirfd), mkfifoat_name, Mode::S_IRUSR).unwrap(); |
164 | |
165 | let stats = |
166 | stat::fstatat(dirfd, mkfifoat_name, fcntl::AtFlags::empty()).unwrap(); |
167 | let typ = stat::SFlag::from_bits_truncate(stats.st_mode); |
168 | assert_eq!(typ, SFlag::S_IFIFO); |
169 | } |
170 | |
171 | #[test] |
172 | #[cfg (not(any( |
173 | target_os = "macos" , |
174 | target_os = "ios" , |
175 | target_os = "android" , |
176 | target_os = "redox" , |
177 | target_os = "haiku" |
178 | )))] |
179 | fn test_mkfifoat_directory_none() { |
180 | let _m = crate::CWD_LOCK.read(); |
181 | |
182 | // mkfifoat should fail if a directory is given |
183 | mkfifoat(None, &env::temp_dir(), Mode::S_IRUSR) |
184 | .expect_err("assertion failed" ); |
185 | } |
186 | |
187 | #[test] |
188 | #[cfg (not(any( |
189 | target_os = "macos" , |
190 | target_os = "ios" , |
191 | target_os = "android" , |
192 | target_os = "redox" , |
193 | target_os = "haiku" |
194 | )))] |
195 | fn test_mkfifoat_directory() { |
196 | // mkfifoat should fail if a directory is given |
197 | let tempdir = tempdir().unwrap(); |
198 | let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap(); |
199 | let mkfifoat_dir = "mkfifoat_dir" ; |
200 | stat::mkdirat(dirfd, mkfifoat_dir, Mode::S_IRUSR).unwrap(); |
201 | |
202 | mkfifoat(Some(dirfd), mkfifoat_dir, Mode::S_IRUSR) |
203 | .expect_err("assertion failed" ); |
204 | } |
205 | |
206 | #[test] |
207 | fn test_getpid() { |
208 | let pid: ::libc::pid_t = getpid().into(); |
209 | let ppid: ::libc::pid_t = getppid().into(); |
210 | assert!(pid > 0); |
211 | assert!(ppid > 0); |
212 | } |
213 | |
214 | #[test] |
215 | #[cfg (not(target_os = "redox" ))] |
216 | fn test_getsid() { |
217 | let none_sid: ::libc::pid_t = getsid(None).unwrap().into(); |
218 | let pid_sid: ::libc::pid_t = getsid(Some(getpid())).unwrap().into(); |
219 | assert!(none_sid > 0); |
220 | assert_eq!(none_sid, pid_sid); |
221 | } |
222 | |
223 | #[cfg (any(target_os = "linux" , target_os = "android" ))] |
224 | mod linux_android { |
225 | use nix::unistd::gettid; |
226 | |
227 | #[test] |
228 | fn test_gettid() { |
229 | let tid: ::libc::pid_t = gettid().into(); |
230 | assert!(tid > 0); |
231 | } |
232 | } |
233 | |
234 | #[test] |
235 | // `getgroups()` and `setgroups()` do not behave as expected on Apple platforms |
236 | #[cfg (not(any( |
237 | target_os = "ios" , |
238 | target_os = "macos" , |
239 | target_os = "redox" , |
240 | target_os = "fuchsia" , |
241 | target_os = "haiku" |
242 | )))] |
243 | fn test_setgroups() { |
244 | // Skip this test when not run as root as `setgroups()` requires root. |
245 | skip_if_not_root!("test_setgroups" ); |
246 | |
247 | let _m = crate::GROUPS_MTX.lock(); |
248 | |
249 | // Save the existing groups |
250 | let old_groups = getgroups().unwrap(); |
251 | |
252 | // Set some new made up groups |
253 | let groups = [Gid::from_raw(123), Gid::from_raw(456)]; |
254 | setgroups(&groups).unwrap(); |
255 | |
256 | let new_groups = getgroups().unwrap(); |
257 | assert_eq!(new_groups, groups); |
258 | |
259 | // Revert back to the old groups |
260 | setgroups(&old_groups).unwrap(); |
261 | } |
262 | |
263 | #[test] |
264 | // `getgroups()` and `setgroups()` do not behave as expected on Apple platforms |
265 | #[cfg (not(any( |
266 | target_os = "ios" , |
267 | target_os = "macos" , |
268 | target_os = "redox" , |
269 | target_os = "fuchsia" , |
270 | target_os = "haiku" , |
271 | target_os = "illumos" |
272 | )))] |
273 | fn test_initgroups() { |
274 | // Skip this test when not run as root as `initgroups()` and `setgroups()` |
275 | // require root. |
276 | skip_if_not_root!("test_initgroups" ); |
277 | |
278 | let _m = crate::GROUPS_MTX.lock(); |
279 | |
280 | // Save the existing groups |
281 | let old_groups = getgroups().unwrap(); |
282 | |
283 | // It doesn't matter if the root user is not called "root" or if a user |
284 | // called "root" doesn't exist. We are just checking that the extra, |
285 | // made-up group, `123`, is set. |
286 | // FIXME: Test the other half of initgroups' functionality: whether the |
287 | // groups that the user belongs to are also set. |
288 | let user = CString::new("root" ).unwrap(); |
289 | let group = Gid::from_raw(123); |
290 | let group_list = getgrouplist(&user, group).unwrap(); |
291 | assert!(group_list.contains(&group)); |
292 | |
293 | initgroups(&user, group).unwrap(); |
294 | |
295 | let new_groups = getgroups().unwrap(); |
296 | assert_eq!(new_groups, group_list); |
297 | |
298 | // Revert back to the old groups |
299 | setgroups(&old_groups).unwrap(); |
300 | } |
301 | |
302 | #[cfg (not(any(target_os = "fuchsia" , target_os = "redox" )))] |
303 | macro_rules! execve_test_factory ( |
304 | ($test_name:ident, $syscall:ident, $exe: expr $(, $pathname:expr, $flags:expr)*) => ( |
305 | |
306 | #[cfg(test)] |
307 | mod $test_name { |
308 | use std::ffi::CStr; |
309 | use super::*; |
310 | |
311 | const EMPTY: &'static [u8] = b" \0" ; |
312 | const DASH_C: &'static [u8] = b"-c \0" ; |
313 | const BIGARG: &'static [u8] = b"echo nix!!! && echo foo=$foo && echo baz=$baz \0" ; |
314 | const FOO: &'static [u8] = b"foo=bar \0" ; |
315 | const BAZ: &'static [u8] = b"baz=quux \0" ; |
316 | |
317 | fn syscall_cstr_ref() -> Result<std::convert::Infallible, nix::Error> { |
318 | $syscall( |
319 | $exe, |
320 | $(CString::new($pathname).unwrap().as_c_str(), )* |
321 | &[CStr::from_bytes_with_nul(EMPTY).unwrap(), |
322 | CStr::from_bytes_with_nul(DASH_C).unwrap(), |
323 | CStr::from_bytes_with_nul(BIGARG).unwrap()], |
324 | &[CStr::from_bytes_with_nul(FOO).unwrap(), |
325 | CStr::from_bytes_with_nul(BAZ).unwrap()] |
326 | $(, $flags)*) |
327 | } |
328 | |
329 | fn syscall_cstring() -> Result<std::convert::Infallible, nix::Error> { |
330 | $syscall( |
331 | $exe, |
332 | $(CString::new($pathname).unwrap().as_c_str(), )* |
333 | &[CString::from(CStr::from_bytes_with_nul(EMPTY).unwrap()), |
334 | CString::from(CStr::from_bytes_with_nul(DASH_C).unwrap()), |
335 | CString::from(CStr::from_bytes_with_nul(BIGARG).unwrap())], |
336 | &[CString::from(CStr::from_bytes_with_nul(FOO).unwrap()), |
337 | CString::from(CStr::from_bytes_with_nul(BAZ).unwrap())] |
338 | $(, $flags)*) |
339 | } |
340 | |
341 | fn common_test(syscall: fn() -> Result<std::convert::Infallible, nix::Error>) { |
342 | if "execveat" == stringify!($syscall) { |
343 | // Though undocumented, Docker's default seccomp profile seems to |
344 | // block this syscall. https://github.com/nix-rust/nix/issues/1122 |
345 | skip_if_seccomp!($test_name); |
346 | } |
347 | |
348 | let m = crate::FORK_MTX.lock(); |
349 | // The `exec`d process will write to `writer`, and we'll read that |
350 | // data from `reader`. |
351 | let (reader, writer) = pipe().unwrap(); |
352 | |
353 | // Safe: Child calls `exit`, `dup`, `close` and the provided `exec*` family function. |
354 | // NOTE: Technically, this makes the macro unsafe to use because you could pass anything. |
355 | // The tests make sure not to do that, though. |
356 | match unsafe{fork()}.unwrap() { |
357 | Child => { |
358 | // Make `writer` be the stdout of the new process. |
359 | dup2(writer, 1).unwrap(); |
360 | let r = syscall(); |
361 | let _ = std::io::stderr() |
362 | .write_all(format!("{:?}" , r).as_bytes()); |
363 | // Should only get here in event of error |
364 | unsafe{ _exit(1) }; |
365 | }, |
366 | Parent { child } => { |
367 | // Wait for the child to exit. |
368 | let ws = waitpid(child, None); |
369 | drop(m); |
370 | assert_eq!(ws, Ok(WaitStatus::Exited(child, 0))); |
371 | // Read 1024 bytes. |
372 | let mut buf = [0u8; 1024]; |
373 | read(reader, &mut buf).unwrap(); |
374 | // It should contain the things we printed using `/bin/sh`. |
375 | let string = String::from_utf8_lossy(&buf); |
376 | assert!(string.contains("nix!!!" )); |
377 | assert!(string.contains("foo=bar" )); |
378 | assert!(string.contains("baz=quux" )); |
379 | } |
380 | } |
381 | } |
382 | |
383 | // These tests frequently fail on musl, probably due to |
384 | // https://github.com/nix-rust/nix/issues/555 |
385 | #[cfg_attr(target_env = "musl" , ignore)] |
386 | #[test] |
387 | fn test_cstr_ref() { |
388 | common_test(syscall_cstr_ref); |
389 | } |
390 | |
391 | // These tests frequently fail on musl, probably due to |
392 | // https://github.com/nix-rust/nix/issues/555 |
393 | #[cfg_attr(target_env = "musl" , ignore)] |
394 | #[test] |
395 | fn test_cstring() { |
396 | common_test(syscall_cstring); |
397 | } |
398 | } |
399 | |
400 | ) |
401 | ); |
402 | |
403 | cfg_if! { |
404 | if #[cfg(target_os = "android" )] { |
405 | execve_test_factory!(test_execve, execve, CString::new("/system/bin/sh" ).unwrap().as_c_str()); |
406 | execve_test_factory!(test_fexecve, fexecve, File::open("/system/bin/sh" ).unwrap().into_raw_fd()); |
407 | } else if #[cfg(any(target_os = "dragonfly" , |
408 | target_os = "freebsd" , |
409 | target_os = "linux" ))] { |
410 | // These tests frequently fail on musl, probably due to |
411 | // https://github.com/nix-rust/nix/issues/555 |
412 | execve_test_factory!(test_execve, execve, CString::new("/bin/sh" ).unwrap().as_c_str()); |
413 | execve_test_factory!(test_fexecve, fexecve, File::open("/bin/sh" ).unwrap().into_raw_fd()); |
414 | } else if #[cfg(any(target_os = "illumos" , |
415 | target_os = "ios" , |
416 | target_os = "macos" , |
417 | target_os = "netbsd" , |
418 | target_os = "openbsd" , |
419 | target_os = "solaris" ))] { |
420 | execve_test_factory!(test_execve, execve, CString::new("/bin/sh" ).unwrap().as_c_str()); |
421 | // No fexecve() on ios, macos, NetBSD, OpenBSD. |
422 | } |
423 | } |
424 | |
425 | #[cfg (any(target_os = "haiku" , target_os = "linux" , target_os = "openbsd" ))] |
426 | execve_test_factory!(test_execvpe, execvpe, &CString::new("sh" ).unwrap()); |
427 | |
428 | cfg_if! { |
429 | if #[cfg(target_os = "android" )] { |
430 | use nix::fcntl::AtFlags; |
431 | execve_test_factory!(test_execveat_empty, execveat, |
432 | File::open("/system/bin/sh" ).unwrap().into_raw_fd(), |
433 | "" , AtFlags::AT_EMPTY_PATH); |
434 | execve_test_factory!(test_execveat_relative, execveat, |
435 | File::open("/system/bin/" ).unwrap().into_raw_fd(), |
436 | "./sh" , AtFlags::empty()); |
437 | execve_test_factory!(test_execveat_absolute, execveat, |
438 | File::open("/" ).unwrap().into_raw_fd(), |
439 | "/system/bin/sh" , AtFlags::empty()); |
440 | } else if #[cfg(all(target_os = "linux" , any(target_arch ="x86_64" , target_arch ="x86" )))] { |
441 | use nix::fcntl::AtFlags; |
442 | execve_test_factory!(test_execveat_empty, execveat, File::open("/bin/sh" ).unwrap().into_raw_fd(), |
443 | "" , AtFlags::AT_EMPTY_PATH); |
444 | execve_test_factory!(test_execveat_relative, execveat, File::open("/bin/" ).unwrap().into_raw_fd(), |
445 | "./sh" , AtFlags::empty()); |
446 | execve_test_factory!(test_execveat_absolute, execveat, File::open("/" ).unwrap().into_raw_fd(), |
447 | "/bin/sh" , AtFlags::empty()); |
448 | } |
449 | } |
450 | |
451 | #[test] |
452 | #[cfg (not(target_os = "fuchsia" ))] |
453 | fn test_fchdir() { |
454 | // fchdir changes the process's cwd |
455 | let _dr = crate::DirRestore::new(); |
456 | |
457 | let tmpdir = tempdir().unwrap(); |
458 | let tmpdir_path = tmpdir.path().canonicalize().unwrap(); |
459 | let tmpdir_fd = File::open(&tmpdir_path).unwrap().into_raw_fd(); |
460 | |
461 | fchdir(tmpdir_fd).expect("assertion failed" ); |
462 | assert_eq!(getcwd().unwrap(), tmpdir_path); |
463 | |
464 | close(tmpdir_fd).expect("assertion failed" ); |
465 | } |
466 | |
467 | #[test] |
468 | fn test_getcwd() { |
469 | // chdir changes the process's cwd |
470 | let _dr = crate::DirRestore::new(); |
471 | |
472 | let tmpdir = tempdir().unwrap(); |
473 | let tmpdir_path = tmpdir.path().canonicalize().unwrap(); |
474 | chdir(&tmpdir_path).expect("assertion failed" ); |
475 | assert_eq!(getcwd().unwrap(), tmpdir_path); |
476 | |
477 | // make path 500 chars longer so that buffer doubling in getcwd |
478 | // kicks in. Note: One path cannot be longer than 255 bytes |
479 | // (NAME_MAX) whole path cannot be longer than PATH_MAX (usually |
480 | // 4096 on linux, 1024 on macos) |
481 | let mut inner_tmp_dir = tmpdir_path; |
482 | for _ in 0..5 { |
483 | let newdir = "a" .repeat(100); |
484 | inner_tmp_dir.push(newdir); |
485 | mkdir(inner_tmp_dir.as_path(), Mode::S_IRWXU) |
486 | .expect("assertion failed" ); |
487 | } |
488 | chdir(inner_tmp_dir.as_path()).expect("assertion failed" ); |
489 | assert_eq!(getcwd().unwrap(), inner_tmp_dir.as_path()); |
490 | } |
491 | |
492 | #[test] |
493 | fn test_chown() { |
494 | // Testing for anything other than our own UID/GID is hard. |
495 | let uid = Some(getuid()); |
496 | let gid = Some(getgid()); |
497 | |
498 | let tempdir = tempdir().unwrap(); |
499 | let path = tempdir.path().join("file" ); |
500 | { |
501 | File::create(&path).unwrap(); |
502 | } |
503 | |
504 | chown(&path, uid, gid).unwrap(); |
505 | chown(&path, uid, None).unwrap(); |
506 | chown(&path, None, gid).unwrap(); |
507 | |
508 | fs::remove_file(&path).unwrap(); |
509 | chown(&path, uid, gid).unwrap_err(); |
510 | } |
511 | |
512 | #[test] |
513 | fn test_fchown() { |
514 | // Testing for anything other than our own UID/GID is hard. |
515 | let uid = Some(getuid()); |
516 | let gid = Some(getgid()); |
517 | |
518 | let path = tempfile().unwrap(); |
519 | let fd = path.as_raw_fd(); |
520 | |
521 | fchown(fd, uid, gid).unwrap(); |
522 | fchown(fd, uid, None).unwrap(); |
523 | fchown(fd, None, gid).unwrap(); |
524 | fchown(999999999, uid, gid).unwrap_err(); |
525 | } |
526 | |
527 | #[test] |
528 | #[cfg (not(target_os = "redox" ))] |
529 | fn test_fchownat() { |
530 | let _dr = crate::DirRestore::new(); |
531 | // Testing for anything other than our own UID/GID is hard. |
532 | let uid = Some(getuid()); |
533 | let gid = Some(getgid()); |
534 | |
535 | let tempdir = tempdir().unwrap(); |
536 | let path = tempdir.path().join("file" ); |
537 | { |
538 | File::create(&path).unwrap(); |
539 | } |
540 | |
541 | let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap(); |
542 | |
543 | fchownat(Some(dirfd), "file" , uid, gid, FchownatFlags::FollowSymlink) |
544 | .unwrap(); |
545 | |
546 | chdir(tempdir.path()).unwrap(); |
547 | fchownat(None, "file" , uid, gid, FchownatFlags::FollowSymlink).unwrap(); |
548 | |
549 | fs::remove_file(&path).unwrap(); |
550 | fchownat(None, "file" , uid, gid, FchownatFlags::FollowSymlink).unwrap_err(); |
551 | } |
552 | |
553 | #[test] |
554 | fn test_lseek() { |
555 | const CONTENTS: &[u8] = b"abcdef123456" ; |
556 | let mut tmp = tempfile().unwrap(); |
557 | tmp.write_all(CONTENTS).unwrap(); |
558 | |
559 | let offset: off_t = 5; |
560 | lseek(tmp.as_raw_fd(), offset, Whence::SeekSet).unwrap(); |
561 | |
562 | let mut buf = [0u8; 7]; |
563 | crate::read_exact(&tmp, &mut buf); |
564 | assert_eq!(b"f123456" , &buf); |
565 | } |
566 | |
567 | #[cfg (any(target_os = "linux" , target_os = "android" ))] |
568 | #[test] |
569 | fn test_lseek64() { |
570 | const CONTENTS: &[u8] = b"abcdef123456" ; |
571 | let mut tmp = tempfile().unwrap(); |
572 | tmp.write_all(CONTENTS).unwrap(); |
573 | |
574 | lseek64(tmp.as_raw_fd(), 5, Whence::SeekSet).unwrap(); |
575 | |
576 | let mut buf = [0u8; 7]; |
577 | crate::read_exact(&tmp, &mut buf); |
578 | assert_eq!(b"f123456" , &buf); |
579 | } |
580 | |
581 | cfg_if! { |
582 | if #[cfg(any(target_os = "android" , target_os = "linux" ))] { |
583 | macro_rules! require_acct{ |
584 | () => { |
585 | require_capability!("test_acct" , CAP_SYS_PACCT); |
586 | } |
587 | } |
588 | } else if #[cfg(target_os = "freebsd" )] { |
589 | macro_rules! require_acct{ |
590 | () => { |
591 | skip_if_not_root!("test_acct" ); |
592 | skip_if_jailed!("test_acct" ); |
593 | } |
594 | } |
595 | } else if #[cfg(not(any(target_os = "redox" , target_os = "fuchsia" , target_os = "haiku" )))] { |
596 | macro_rules! require_acct{ |
597 | () => { |
598 | skip_if_not_root!("test_acct" ); |
599 | } |
600 | } |
601 | } |
602 | } |
603 | |
604 | #[test] |
605 | #[cfg (not(any( |
606 | target_os = "redox" , |
607 | target_os = "fuchsia" , |
608 | target_os = "haiku" |
609 | )))] |
610 | fn test_acct() { |
611 | use std::process::Command; |
612 | use std::{thread, time}; |
613 | use tempfile::NamedTempFile; |
614 | |
615 | let _m = crate::FORK_MTX.lock(); |
616 | require_acct!(); |
617 | |
618 | let file = NamedTempFile::new().unwrap(); |
619 | let path = file.path().to_str().unwrap(); |
620 | |
621 | acct::enable(path).unwrap(); |
622 | |
623 | loop { |
624 | Command::new("echo" ).arg("Hello world" ).output().unwrap(); |
625 | let len = fs::metadata(path).unwrap().len(); |
626 | if len > 0 { |
627 | break; |
628 | } |
629 | thread::sleep(time::Duration::from_millis(10)); |
630 | } |
631 | acct::disable().unwrap(); |
632 | } |
633 | |
634 | #[test] |
635 | fn test_fpathconf_limited() { |
636 | let f = tempfile().unwrap(); |
637 | // AFAIK, PATH_MAX is limited on all platforms, so it makes a good test |
638 | let path_max = fpathconf(f.as_raw_fd(), PathconfVar::PATH_MAX); |
639 | assert!( |
640 | path_max |
641 | .expect("fpathconf failed" ) |
642 | .expect("PATH_MAX is unlimited" ) |
643 | > 0 |
644 | ); |
645 | } |
646 | |
647 | #[test] |
648 | fn test_pathconf_limited() { |
649 | // AFAIK, PATH_MAX is limited on all platforms, so it makes a good test |
650 | let path_max = pathconf("/" , PathconfVar::PATH_MAX); |
651 | assert!( |
652 | path_max |
653 | .expect("pathconf failed" ) |
654 | .expect("PATH_MAX is unlimited" ) |
655 | > 0 |
656 | ); |
657 | } |
658 | |
659 | #[test] |
660 | fn test_sysconf_limited() { |
661 | // AFAIK, OPEN_MAX is limited on all platforms, so it makes a good test |
662 | let open_max = sysconf(SysconfVar::OPEN_MAX); |
663 | assert!( |
664 | open_max |
665 | .expect("sysconf failed" ) |
666 | .expect("OPEN_MAX is unlimited" ) |
667 | > 0 |
668 | ); |
669 | } |
670 | |
671 | #[cfg (target_os = "freebsd" )] |
672 | #[test] |
673 | fn test_sysconf_unsupported() { |
674 | // I know of no sysconf variables that are unsupported everywhere, but |
675 | // _XOPEN_CRYPT is unsupported on FreeBSD 11.0, which is one of the platforms |
676 | // we test. |
677 | let open_max = sysconf(SysconfVar::_XOPEN_CRYPT); |
678 | assert!(open_max.expect("sysconf failed" ).is_none()) |
679 | } |
680 | |
681 | #[cfg (any( |
682 | target_os = "android" , |
683 | target_os = "dragonfly" , |
684 | target_os = "freebsd" , |
685 | target_os = "linux" , |
686 | target_os = "openbsd" |
687 | ))] |
688 | #[test] |
689 | fn test_getresuid() { |
690 | let resuids = getresuid().unwrap(); |
691 | assert_ne!(resuids.real.as_raw(), libc::uid_t::MAX); |
692 | assert_ne!(resuids.effective.as_raw(), libc::uid_t::MAX); |
693 | assert_ne!(resuids.saved.as_raw(), libc::uid_t::MAX); |
694 | } |
695 | |
696 | #[cfg (any( |
697 | target_os = "android" , |
698 | target_os = "dragonfly" , |
699 | target_os = "freebsd" , |
700 | target_os = "linux" , |
701 | target_os = "openbsd" |
702 | ))] |
703 | #[test] |
704 | fn test_getresgid() { |
705 | let resgids = getresgid().unwrap(); |
706 | assert_ne!(resgids.real.as_raw(), libc::gid_t::MAX); |
707 | assert_ne!(resgids.effective.as_raw(), libc::gid_t::MAX); |
708 | assert_ne!(resgids.saved.as_raw(), libc::gid_t::MAX); |
709 | } |
710 | |
711 | // Test that we can create a pair of pipes. No need to verify that they pass |
712 | // data; that's the domain of the OS, not nix. |
713 | #[test] |
714 | fn test_pipe() { |
715 | let (fd0, fd1) = pipe().unwrap(); |
716 | let m0 = stat::SFlag::from_bits_truncate( |
717 | stat::fstat(fd0).unwrap().st_mode as mode_t, |
718 | ); |
719 | // S_IFIFO means it's a pipe |
720 | assert_eq!(m0, SFlag::S_IFIFO); |
721 | let m1 = stat::SFlag::from_bits_truncate( |
722 | stat::fstat(fd1).unwrap().st_mode as mode_t, |
723 | ); |
724 | assert_eq!(m1, SFlag::S_IFIFO); |
725 | } |
726 | |
727 | // pipe2(2) is the same as pipe(2), except it allows setting some flags. Check |
728 | // that we can set a flag. |
729 | #[cfg (any( |
730 | target_os = "android" , |
731 | target_os = "dragonfly" , |
732 | target_os = "emscripten" , |
733 | target_os = "freebsd" , |
734 | target_os = "illumos" , |
735 | target_os = "linux" , |
736 | target_os = "netbsd" , |
737 | target_os = "openbsd" , |
738 | target_os = "redox" , |
739 | target_os = "solaris" |
740 | ))] |
741 | #[test] |
742 | fn test_pipe2() { |
743 | use nix::fcntl::{fcntl, FcntlArg, FdFlag}; |
744 | |
745 | let (fd0, fd1) = pipe2(OFlag::O_CLOEXEC).unwrap(); |
746 | let f0 = FdFlag::from_bits_truncate(fcntl(fd0, FcntlArg::F_GETFD).unwrap()); |
747 | assert!(f0.contains(FdFlag::FD_CLOEXEC)); |
748 | let f1 = FdFlag::from_bits_truncate(fcntl(fd1, FcntlArg::F_GETFD).unwrap()); |
749 | assert!(f1.contains(FdFlag::FD_CLOEXEC)); |
750 | } |
751 | |
752 | #[test] |
753 | #[cfg (not(any(target_os = "redox" , target_os = "fuchsia" )))] |
754 | fn test_truncate() { |
755 | let tempdir = tempdir().unwrap(); |
756 | let path = tempdir.path().join("file" ); |
757 | |
758 | { |
759 | let mut tmp = File::create(&path).unwrap(); |
760 | const CONTENTS: &[u8] = b"12345678" ; |
761 | tmp.write_all(CONTENTS).unwrap(); |
762 | } |
763 | |
764 | truncate(&path, 4).unwrap(); |
765 | |
766 | let metadata = fs::metadata(&path).unwrap(); |
767 | assert_eq!(4, metadata.len()); |
768 | } |
769 | |
770 | #[test] |
771 | fn test_ftruncate() { |
772 | let tempdir = tempdir().unwrap(); |
773 | let path = tempdir.path().join("file" ); |
774 | |
775 | let mut file = File::create(&path).unwrap(); |
776 | const CONTENTS: &[u8] = b"12345678" ; |
777 | file.write_all(CONTENTS).unwrap(); |
778 | |
779 | ftruncate(&file, 2).unwrap(); |
780 | drop(file); |
781 | |
782 | let metadata = fs::metadata(&path).unwrap(); |
783 | assert_eq!(2, metadata.len()); |
784 | } |
785 | |
786 | // Used in `test_alarm`. |
787 | #[cfg (not(target_os = "redox" ))] |
788 | static mut ALARM_CALLED: bool = false; |
789 | |
790 | // Used in `test_alarm`. |
791 | #[cfg (not(target_os = "redox" ))] |
792 | pub extern "C" fn alarm_signal_handler(raw_signal: libc::c_int) { |
793 | assert_eq!(raw_signal, libc::SIGALRM, "unexpected signal: {raw_signal}" ); |
794 | unsafe { ALARM_CALLED = true }; |
795 | } |
796 | |
797 | #[test] |
798 | #[cfg (not(target_os = "redox" ))] |
799 | fn test_alarm() { |
800 | use std::{ |
801 | thread, |
802 | time::{Duration, Instant}, |
803 | }; |
804 | |
805 | // Maybe other tests that fork interfere with this one? |
806 | let _m = crate::SIGNAL_MTX.lock(); |
807 | |
808 | let handler = SigHandler::Handler(alarm_signal_handler); |
809 | let signal_action = |
810 | SigAction::new(handler, SaFlags::SA_RESTART, SigSet::empty()); |
811 | let old_handler = unsafe { |
812 | sigaction(Signal::SIGALRM, &signal_action) |
813 | .expect("unable to set signal handler for alarm" ) |
814 | }; |
815 | |
816 | // Set an alarm. |
817 | assert_eq!(alarm::set(60), None); |
818 | |
819 | // Overwriting an alarm should return the old alarm. |
820 | assert_eq!(alarm::set(1), Some(60)); |
821 | |
822 | // We should be woken up after 1 second by the alarm, so we'll sleep for 3 |
823 | // seconds to be sure. |
824 | let starttime = Instant::now(); |
825 | loop { |
826 | thread::sleep(Duration::from_millis(100)); |
827 | if unsafe { ALARM_CALLED } { |
828 | break; |
829 | } |
830 | if starttime.elapsed() > Duration::from_secs(3) { |
831 | panic!("Timeout waiting for SIGALRM" ); |
832 | } |
833 | } |
834 | |
835 | // Reset the signal. |
836 | unsafe { |
837 | sigaction(Signal::SIGALRM, &old_handler) |
838 | .expect("unable to set signal handler for alarm" ); |
839 | } |
840 | } |
841 | |
842 | #[test] |
843 | #[cfg (not(target_os = "redox" ))] |
844 | fn test_canceling_alarm() { |
845 | let _m = crate::SIGNAL_MTX.lock(); |
846 | |
847 | assert_eq!(alarm::cancel(), None); |
848 | |
849 | assert_eq!(alarm::set(60), None); |
850 | assert_eq!(alarm::cancel(), Some(60)); |
851 | } |
852 | |
853 | #[test] |
854 | #[cfg (not(any(target_os = "redox" , target_os = "haiku" )))] |
855 | fn test_symlinkat() { |
856 | let _m = crate::CWD_LOCK.read(); |
857 | |
858 | let tempdir = tempdir().unwrap(); |
859 | |
860 | let target = tempdir.path().join("a" ); |
861 | let linkpath = tempdir.path().join("b" ); |
862 | symlinkat(&target, None, &linkpath).unwrap(); |
863 | assert_eq!( |
864 | readlink(&linkpath).unwrap().to_str().unwrap(), |
865 | target.to_str().unwrap() |
866 | ); |
867 | |
868 | let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap(); |
869 | let target = "c" ; |
870 | let linkpath = "d" ; |
871 | symlinkat(target, Some(dirfd), linkpath).unwrap(); |
872 | assert_eq!( |
873 | readlink(&tempdir.path().join(linkpath)) |
874 | .unwrap() |
875 | .to_str() |
876 | .unwrap(), |
877 | target |
878 | ); |
879 | } |
880 | |
881 | #[test] |
882 | #[cfg (not(any(target_os = "redox" , target_os = "haiku" )))] |
883 | fn test_linkat_file() { |
884 | let tempdir = tempdir().unwrap(); |
885 | let oldfilename = "foo.txt" ; |
886 | let oldfilepath = tempdir.path().join(oldfilename); |
887 | |
888 | let newfilename = "bar.txt" ; |
889 | let newfilepath = tempdir.path().join(newfilename); |
890 | |
891 | // Create file |
892 | File::create(oldfilepath).unwrap(); |
893 | |
894 | // Get file descriptor for base directory |
895 | let dirfd = |
896 | fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()) |
897 | .unwrap(); |
898 | |
899 | // Attempt hard link file at relative path |
900 | linkat( |
901 | Some(dirfd), |
902 | oldfilename, |
903 | Some(dirfd), |
904 | newfilename, |
905 | LinkatFlags::SymlinkFollow, |
906 | ) |
907 | .unwrap(); |
908 | assert!(newfilepath.exists()); |
909 | } |
910 | |
911 | #[test] |
912 | #[cfg (not(any(target_os = "redox" , target_os = "haiku" )))] |
913 | fn test_linkat_olddirfd_none() { |
914 | let _dr = crate::DirRestore::new(); |
915 | |
916 | let tempdir_oldfile = tempdir().unwrap(); |
917 | let oldfilename = "foo.txt" ; |
918 | let oldfilepath = tempdir_oldfile.path().join(oldfilename); |
919 | |
920 | let tempdir_newfile = tempdir().unwrap(); |
921 | let newfilename = "bar.txt" ; |
922 | let newfilepath = tempdir_newfile.path().join(newfilename); |
923 | |
924 | // Create file |
925 | File::create(oldfilepath).unwrap(); |
926 | |
927 | // Get file descriptor for base directory of new file |
928 | let dirfd = fcntl::open( |
929 | tempdir_newfile.path(), |
930 | fcntl::OFlag::empty(), |
931 | stat::Mode::empty(), |
932 | ) |
933 | .unwrap(); |
934 | |
935 | // Attempt hard link file using curent working directory as relative path for old file path |
936 | chdir(tempdir_oldfile.path()).unwrap(); |
937 | linkat( |
938 | None, |
939 | oldfilename, |
940 | Some(dirfd), |
941 | newfilename, |
942 | LinkatFlags::SymlinkFollow, |
943 | ) |
944 | .unwrap(); |
945 | assert!(newfilepath.exists()); |
946 | } |
947 | |
948 | #[test] |
949 | #[cfg (not(any(target_os = "redox" , target_os = "haiku" )))] |
950 | fn test_linkat_newdirfd_none() { |
951 | let _dr = crate::DirRestore::new(); |
952 | |
953 | let tempdir_oldfile = tempdir().unwrap(); |
954 | let oldfilename = "foo.txt" ; |
955 | let oldfilepath = tempdir_oldfile.path().join(oldfilename); |
956 | |
957 | let tempdir_newfile = tempdir().unwrap(); |
958 | let newfilename = "bar.txt" ; |
959 | let newfilepath = tempdir_newfile.path().join(newfilename); |
960 | |
961 | // Create file |
962 | File::create(oldfilepath).unwrap(); |
963 | |
964 | // Get file descriptor for base directory of old file |
965 | let dirfd = fcntl::open( |
966 | tempdir_oldfile.path(), |
967 | fcntl::OFlag::empty(), |
968 | stat::Mode::empty(), |
969 | ) |
970 | .unwrap(); |
971 | |
972 | // Attempt hard link file using current working directory as relative path for new file path |
973 | chdir(tempdir_newfile.path()).unwrap(); |
974 | linkat( |
975 | Some(dirfd), |
976 | oldfilename, |
977 | None, |
978 | newfilename, |
979 | LinkatFlags::SymlinkFollow, |
980 | ) |
981 | .unwrap(); |
982 | assert!(newfilepath.exists()); |
983 | } |
984 | |
985 | #[test] |
986 | #[cfg (not(any( |
987 | target_os = "ios" , |
988 | target_os = "macos" , |
989 | target_os = "redox" , |
990 | target_os = "haiku" |
991 | )))] |
992 | fn test_linkat_no_follow_symlink() { |
993 | let _m = crate::CWD_LOCK.read(); |
994 | |
995 | let tempdir = tempdir().unwrap(); |
996 | let oldfilename = "foo.txt" ; |
997 | let oldfilepath = tempdir.path().join(oldfilename); |
998 | |
999 | let symoldfilename = "symfoo.txt" ; |
1000 | let symoldfilepath = tempdir.path().join(symoldfilename); |
1001 | |
1002 | let newfilename = "nofollowsymbar.txt" ; |
1003 | let newfilepath = tempdir.path().join(newfilename); |
1004 | |
1005 | // Create file |
1006 | File::create(&oldfilepath).unwrap(); |
1007 | |
1008 | // Create symlink to file |
1009 | symlinkat(&oldfilepath, None, &symoldfilepath).unwrap(); |
1010 | |
1011 | // Get file descriptor for base directory |
1012 | let dirfd = |
1013 | fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()) |
1014 | .unwrap(); |
1015 | |
1016 | // Attempt link symlink of file at relative path |
1017 | linkat( |
1018 | Some(dirfd), |
1019 | symoldfilename, |
1020 | Some(dirfd), |
1021 | newfilename, |
1022 | LinkatFlags::NoSymlinkFollow, |
1023 | ) |
1024 | .unwrap(); |
1025 | |
1026 | // Assert newfile is actually a symlink to oldfile. |
1027 | assert_eq!( |
1028 | readlink(&newfilepath).unwrap().to_str().unwrap(), |
1029 | oldfilepath.to_str().unwrap() |
1030 | ); |
1031 | } |
1032 | |
1033 | #[test] |
1034 | #[cfg (not(any(target_os = "redox" , target_os = "haiku" )))] |
1035 | fn test_linkat_follow_symlink() { |
1036 | let _m = crate::CWD_LOCK.read(); |
1037 | |
1038 | let tempdir = tempdir().unwrap(); |
1039 | let oldfilename = "foo.txt" ; |
1040 | let oldfilepath = tempdir.path().join(oldfilename); |
1041 | |
1042 | let symoldfilename = "symfoo.txt" ; |
1043 | let symoldfilepath = tempdir.path().join(symoldfilename); |
1044 | |
1045 | let newfilename = "nofollowsymbar.txt" ; |
1046 | let newfilepath = tempdir.path().join(newfilename); |
1047 | |
1048 | // Create file |
1049 | File::create(&oldfilepath).unwrap(); |
1050 | |
1051 | // Create symlink to file |
1052 | symlinkat(&oldfilepath, None, &symoldfilepath).unwrap(); |
1053 | |
1054 | // Get file descriptor for base directory |
1055 | let dirfd = |
1056 | fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()) |
1057 | .unwrap(); |
1058 | |
1059 | // Attempt link target of symlink of file at relative path |
1060 | linkat( |
1061 | Some(dirfd), |
1062 | symoldfilename, |
1063 | Some(dirfd), |
1064 | newfilename, |
1065 | LinkatFlags::SymlinkFollow, |
1066 | ) |
1067 | .unwrap(); |
1068 | |
1069 | let newfilestat = stat::stat(&newfilepath).unwrap(); |
1070 | |
1071 | // Check the file type of the new link |
1072 | assert_eq!( |
1073 | (stat::SFlag::from_bits_truncate(newfilestat.st_mode as mode_t) |
1074 | & SFlag::S_IFMT), |
1075 | SFlag::S_IFREG |
1076 | ); |
1077 | |
1078 | // Check the number of hard links to the original file |
1079 | assert_eq!(newfilestat.st_nlink, 2); |
1080 | } |
1081 | |
1082 | #[test] |
1083 | #[cfg (not(target_os = "redox" ))] |
1084 | fn test_unlinkat_dir_noremovedir() { |
1085 | let tempdir = tempdir().unwrap(); |
1086 | let dirname = "foo_dir" ; |
1087 | let dirpath = tempdir.path().join(dirname); |
1088 | |
1089 | // Create dir |
1090 | DirBuilder::new().recursive(true).create(dirpath).unwrap(); |
1091 | |
1092 | // Get file descriptor for base directory |
1093 | let dirfd = |
1094 | fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()) |
1095 | .unwrap(); |
1096 | |
1097 | // Attempt unlink dir at relative path without proper flag |
1098 | let err_result = |
1099 | unlinkat(Some(dirfd), dirname, UnlinkatFlags::NoRemoveDir).unwrap_err(); |
1100 | assert!(err_result == Errno::EISDIR || err_result == Errno::EPERM); |
1101 | } |
1102 | |
1103 | #[test] |
1104 | #[cfg (not(target_os = "redox" ))] |
1105 | fn test_unlinkat_dir_removedir() { |
1106 | let tempdir = tempdir().unwrap(); |
1107 | let dirname = "foo_dir" ; |
1108 | let dirpath = tempdir.path().join(dirname); |
1109 | |
1110 | // Create dir |
1111 | DirBuilder::new().recursive(true).create(&dirpath).unwrap(); |
1112 | |
1113 | // Get file descriptor for base directory |
1114 | let dirfd = |
1115 | fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()) |
1116 | .unwrap(); |
1117 | |
1118 | // Attempt unlink dir at relative path with proper flag |
1119 | unlinkat(Some(dirfd), dirname, UnlinkatFlags::RemoveDir).unwrap(); |
1120 | assert!(!dirpath.exists()); |
1121 | } |
1122 | |
1123 | #[test] |
1124 | #[cfg (not(target_os = "redox" ))] |
1125 | fn test_unlinkat_file() { |
1126 | let tempdir = tempdir().unwrap(); |
1127 | let filename = "foo.txt" ; |
1128 | let filepath = tempdir.path().join(filename); |
1129 | |
1130 | // Create file |
1131 | File::create(&filepath).unwrap(); |
1132 | |
1133 | // Get file descriptor for base directory |
1134 | let dirfd = |
1135 | fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()) |
1136 | .unwrap(); |
1137 | |
1138 | // Attempt unlink file at relative path |
1139 | unlinkat(Some(dirfd), filename, UnlinkatFlags::NoRemoveDir).unwrap(); |
1140 | assert!(!filepath.exists()); |
1141 | } |
1142 | |
1143 | #[test] |
1144 | fn test_access_not_existing() { |
1145 | let tempdir = tempdir().unwrap(); |
1146 | let dir = tempdir.path().join("does_not_exist.txt" ); |
1147 | assert_eq!( |
1148 | access(&dir, AccessFlags::F_OK).err().unwrap(), |
1149 | Errno::ENOENT |
1150 | ); |
1151 | } |
1152 | |
1153 | #[test] |
1154 | fn test_access_file_exists() { |
1155 | let tempdir = tempdir().unwrap(); |
1156 | let path = tempdir.path().join("does_exist.txt" ); |
1157 | let _file = File::create(path.clone()).unwrap(); |
1158 | access(&path, AccessFlags::R_OK | AccessFlags::W_OK) |
1159 | .expect("assertion failed" ); |
1160 | } |
1161 | |
1162 | //Clippy false positive https://github.com/rust-lang/rust-clippy/issues/9111 |
1163 | #[allow (clippy::needless_borrow)] |
1164 | #[cfg (not(target_os = "redox" ))] |
1165 | #[test] |
1166 | fn test_user_into_passwd() { |
1167 | // get the UID of the "nobody" user |
1168 | #[cfg (not(target_os = "haiku" ))] |
1169 | let test_username = "nobody" ; |
1170 | // "nobody" unavailable on haiku |
1171 | #[cfg (target_os = "haiku" )] |
1172 | let test_username = "user" ; |
1173 | |
1174 | let nobody = User::from_name(test_username).unwrap().unwrap(); |
1175 | let pwd: libc::passwd = nobody.into(); |
1176 | let _: User = (&pwd).into(); |
1177 | } |
1178 | |
1179 | /// Tests setting the filesystem UID with `setfsuid`. |
1180 | #[cfg (any(target_os = "linux" , target_os = "android" ))] |
1181 | #[test] |
1182 | fn test_setfsuid() { |
1183 | use std::os::unix::fs::PermissionsExt; |
1184 | use std::{fs, io, thread}; |
1185 | require_capability!("test_setfsuid" , CAP_SETUID); |
1186 | |
1187 | // get the UID of the "nobody" user |
1188 | let nobody = User::from_name("nobody" ).unwrap().unwrap(); |
1189 | |
1190 | // create a temporary file with permissions '-rw-r-----' |
1191 | let file = tempfile::NamedTempFile::new_in("/var/tmp" ).unwrap(); |
1192 | let temp_path = file.into_temp_path(); |
1193 | let temp_path_2 = temp_path.to_path_buf(); |
1194 | let mut permissions = fs::metadata(&temp_path).unwrap().permissions(); |
1195 | permissions.set_mode(0o640); |
1196 | |
1197 | // spawn a new thread where to test setfsuid |
1198 | thread::spawn(move || { |
1199 | // set filesystem UID |
1200 | let fuid = setfsuid(nobody.uid); |
1201 | // trying to open the temporary file should fail with EACCES |
1202 | let res = fs::File::open(&temp_path); |
1203 | let err = res.expect_err("assertion failed" ); |
1204 | assert_eq!(err.kind(), io::ErrorKind::PermissionDenied); |
1205 | |
1206 | // assert fuid actually changes |
1207 | let prev_fuid = setfsuid(Uid::from_raw(-1i32 as u32)); |
1208 | assert_ne!(prev_fuid, fuid); |
1209 | }) |
1210 | .join() |
1211 | .unwrap(); |
1212 | |
1213 | // open the temporary file with the current thread filesystem UID |
1214 | fs::File::open(temp_path_2).unwrap(); |
1215 | } |
1216 | |
1217 | #[test] |
1218 | #[cfg (not(any( |
1219 | target_os = "redox" , |
1220 | target_os = "fuchsia" , |
1221 | target_os = "haiku" |
1222 | )))] |
1223 | fn test_ttyname() { |
1224 | let fd = posix_openpt(OFlag::O_RDWR).expect("posix_openpt failed" ); |
1225 | assert!(fd.as_raw_fd() > 0); |
1226 | |
1227 | // on linux, we can just call ttyname on the pty master directly, but |
1228 | // apparently osx requires that ttyname is called on a slave pty (can't |
1229 | // find this documented anywhere, but it seems to empirically be the case) |
1230 | grantpt(&fd).expect("grantpt failed" ); |
1231 | unlockpt(&fd).expect("unlockpt failed" ); |
1232 | let sname = unsafe { ptsname(&fd) }.expect("ptsname failed" ); |
1233 | let fds = open(Path::new(&sname), OFlag::O_RDWR, stat::Mode::empty()) |
1234 | .expect("open failed" ); |
1235 | assert!(fds > 0); |
1236 | |
1237 | let name = ttyname(fds).expect("ttyname failed" ); |
1238 | assert!(name.starts_with("/dev" )); |
1239 | } |
1240 | |
1241 | #[test] |
1242 | #[cfg (not(any(target_os = "redox" , target_os = "fuchsia" )))] |
1243 | fn test_ttyname_not_pty() { |
1244 | let fd = File::open("/dev/zero" ).unwrap(); |
1245 | assert!(fd.as_raw_fd() > 0); |
1246 | assert_eq!(ttyname(fd.as_raw_fd()), Err(Errno::ENOTTY)); |
1247 | } |
1248 | |
1249 | #[test] |
1250 | #[cfg (not(any( |
1251 | target_os = "redox" , |
1252 | target_os = "fuchsia" , |
1253 | target_os = "haiku" |
1254 | )))] |
1255 | fn test_ttyname_invalid_fd() { |
1256 | assert_eq!(ttyname(-1), Err(Errno::EBADF)); |
1257 | } |
1258 | |
1259 | #[test] |
1260 | #[cfg (any( |
1261 | target_os = "macos" , |
1262 | target_os = "ios" , |
1263 | target_os = "freebsd" , |
1264 | target_os = "openbsd" , |
1265 | target_os = "netbsd" , |
1266 | target_os = "dragonfly" , |
1267 | ))] |
1268 | fn test_getpeereid() { |
1269 | use std::os::unix::net::UnixStream; |
1270 | let (sock_a, sock_b) = UnixStream::pair().unwrap(); |
1271 | |
1272 | let (uid_a, gid_a) = getpeereid(sock_a.as_raw_fd()).unwrap(); |
1273 | let (uid_b, gid_b) = getpeereid(sock_b.as_raw_fd()).unwrap(); |
1274 | |
1275 | let uid = geteuid(); |
1276 | let gid = getegid(); |
1277 | |
1278 | assert_eq!(uid, uid_a); |
1279 | assert_eq!(gid, gid_a); |
1280 | assert_eq!(uid_a, uid_b); |
1281 | assert_eq!(gid_a, gid_b); |
1282 | } |
1283 | |
1284 | #[test] |
1285 | #[cfg (any( |
1286 | target_os = "macos" , |
1287 | target_os = "ios" , |
1288 | target_os = "freebsd" , |
1289 | target_os = "openbsd" , |
1290 | target_os = "netbsd" , |
1291 | target_os = "dragonfly" , |
1292 | ))] |
1293 | fn test_getpeereid_invalid_fd() { |
1294 | // getpeereid is not POSIX, so error codes are inconsistent between different Unices. |
1295 | getpeereid(-1).expect_err("assertion failed" ); |
1296 | } |
1297 | |
1298 | #[test] |
1299 | #[cfg (not(target_os = "redox" ))] |
1300 | fn test_faccessat_none_not_existing() { |
1301 | use nix::fcntl::AtFlags; |
1302 | let tempdir = tempfile::tempdir().unwrap(); |
1303 | let dir = tempdir.path().join("does_not_exist.txt" ); |
1304 | assert_eq!( |
1305 | faccessat(None, &dir, AccessFlags::F_OK, AtFlags::empty()) |
1306 | .err() |
1307 | .unwrap(), |
1308 | Errno::ENOENT |
1309 | ); |
1310 | } |
1311 | |
1312 | #[test] |
1313 | #[cfg (not(target_os = "redox" ))] |
1314 | fn test_faccessat_not_existing() { |
1315 | use nix::fcntl::AtFlags; |
1316 | let tempdir = tempfile::tempdir().unwrap(); |
1317 | let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap(); |
1318 | let not_exist_file = "does_not_exist.txt" ; |
1319 | assert_eq!( |
1320 | faccessat( |
1321 | Some(dirfd), |
1322 | not_exist_file, |
1323 | AccessFlags::F_OK, |
1324 | AtFlags::empty(), |
1325 | ) |
1326 | .err() |
1327 | .unwrap(), |
1328 | Errno::ENOENT |
1329 | ); |
1330 | } |
1331 | |
1332 | #[test] |
1333 | #[cfg (not(target_os = "redox" ))] |
1334 | fn test_faccessat_none_file_exists() { |
1335 | use nix::fcntl::AtFlags; |
1336 | let tempdir = tempfile::tempdir().unwrap(); |
1337 | let path = tempdir.path().join("does_exist.txt" ); |
1338 | let _file = File::create(path.clone()).unwrap(); |
1339 | assert!(faccessat( |
1340 | None, |
1341 | &path, |
1342 | AccessFlags::R_OK | AccessFlags::W_OK, |
1343 | AtFlags::empty(), |
1344 | ) |
1345 | .is_ok()); |
1346 | } |
1347 | |
1348 | #[test] |
1349 | #[cfg (not(target_os = "redox" ))] |
1350 | fn test_faccessat_file_exists() { |
1351 | use nix::fcntl::AtFlags; |
1352 | let tempdir = tempfile::tempdir().unwrap(); |
1353 | let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap(); |
1354 | let exist_file = "does_exist.txt" ; |
1355 | let path = tempdir.path().join(exist_file); |
1356 | let _file = File::create(path.clone()).unwrap(); |
1357 | assert!(faccessat( |
1358 | Some(dirfd), |
1359 | &path, |
1360 | AccessFlags::R_OK | AccessFlags::W_OK, |
1361 | AtFlags::empty(), |
1362 | ) |
1363 | .is_ok()); |
1364 | } |
1365 | |
1366 | #[test] |
1367 | #[cfg (any( |
1368 | all(target_os = "linux" , not(target_env = "uclibc" )), |
1369 | target_os = "freebsd" , |
1370 | target_os = "dragonfly" |
1371 | ))] |
1372 | fn test_eaccess_not_existing() { |
1373 | let tempdir = tempdir().unwrap(); |
1374 | let dir = tempdir.path().join("does_not_exist.txt" ); |
1375 | assert_eq!( |
1376 | eaccess(&dir, AccessFlags::F_OK).err().unwrap(), |
1377 | Errno::ENOENT |
1378 | ); |
1379 | } |
1380 | |
1381 | #[test] |
1382 | #[cfg (any( |
1383 | all(target_os = "linux" , not(target_env = "uclibc" )), |
1384 | target_os = "freebsd" , |
1385 | target_os = "dragonfly" |
1386 | ))] |
1387 | fn test_eaccess_file_exists() { |
1388 | let tempdir = tempdir().unwrap(); |
1389 | let path = tempdir.path().join("does_exist.txt" ); |
1390 | let _file = File::create(path.clone()).unwrap(); |
1391 | eaccess(&path, AccessFlags::R_OK | AccessFlags::W_OK) |
1392 | .expect("assertion failed" ); |
1393 | } |
1394 | |