1#[cfg(not(target_os = "redox"))] 2use nix::errno::*; 3#[cfg(not(target_os = "redox"))] 4use nix::fcntl::{open, readlink, OFlag}; 5#[cfg(not(target_os = "redox"))] 6use nix::fcntl::{openat, readlinkat, renameat}; 7#[cfg(all( 8 target_os = "linux", 9 target_env = "gnu", 10 any( 11 target_arch = "x86_64", 12 target_arch = "x32", 13 target_arch = "powerpc", 14 target_arch = "s390x" 15 ) 16))] 17use nix::fcntl::{renameat2, RenameFlags}; 18#[cfg(not(target_os = "redox"))] 19use nix::sys::stat::Mode; 20#[cfg(not(target_os = "redox"))] 21use nix::unistd::{close, read}; 22#[cfg(not(target_os = "redox"))] 23use std::fs::File; 24#[cfg(not(target_os = "redox"))] 25use std::io::prelude::*; 26#[cfg(not(target_os = "redox"))] 27use std::os::unix::fs; 28#[cfg(not(target_os = "redox"))] 29use tempfile::{self, NamedTempFile}; 30 31#[test] 32#[cfg(not(target_os = "redox"))] 33// QEMU does not handle openat well enough to satisfy this test 34// https://gitlab.com/qemu-project/qemu/-/issues/829 35#[cfg_attr(qemu, ignore)] 36fn test_openat() { 37 const CONTENTS: &[u8] = b"abcd"; 38 let mut tmp = NamedTempFile::new().unwrap(); 39 tmp.write_all(CONTENTS).unwrap(); 40 41 let dirfd = 42 open(tmp.path().parent().unwrap(), OFlag::empty(), Mode::empty()) 43 .unwrap(); 44 let fd = openat( 45 dirfd, 46 tmp.path().file_name().unwrap(), 47 OFlag::O_RDONLY, 48 Mode::empty(), 49 ) 50 .unwrap(); 51 52 let mut buf = [0u8; 1024]; 53 assert_eq!(4, read(fd, &mut buf).unwrap()); 54 assert_eq!(CONTENTS, &buf[0..4]); 55 56 close(fd).unwrap(); 57 close(dirfd).unwrap(); 58} 59 60#[test] 61#[cfg(not(target_os = "redox"))] 62fn test_renameat() { 63 let old_dir = tempfile::tempdir().unwrap(); 64 let old_dirfd = 65 open(old_dir.path(), OFlag::empty(), Mode::empty()).unwrap(); 66 let old_path = old_dir.path().join("old"); 67 File::create(old_path).unwrap(); 68 let new_dir = tempfile::tempdir().unwrap(); 69 let new_dirfd = 70 open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap(); 71 renameat(Some(old_dirfd), "old", Some(new_dirfd), "new").unwrap(); 72 assert_eq!( 73 renameat(Some(old_dirfd), "old", Some(new_dirfd), "new").unwrap_err(), 74 Errno::ENOENT 75 ); 76 close(old_dirfd).unwrap(); 77 close(new_dirfd).unwrap(); 78 assert!(new_dir.path().join("new").exists()); 79} 80 81#[test] 82#[cfg(all( 83 target_os = "linux", 84 target_env = "gnu", 85 any( 86 target_arch = "x86_64", 87 target_arch = "x32", 88 target_arch = "powerpc", 89 target_arch = "s390x" 90 ) 91))] 92fn test_renameat2_behaves_like_renameat_with_no_flags() { 93 let old_dir = tempfile::tempdir().unwrap(); 94 let old_dirfd = 95 open(old_dir.path(), OFlag::empty(), Mode::empty()).unwrap(); 96 let old_path = old_dir.path().join("old"); 97 File::create(old_path).unwrap(); 98 let new_dir = tempfile::tempdir().unwrap(); 99 let new_dirfd = 100 open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap(); 101 renameat2( 102 Some(old_dirfd), 103 "old", 104 Some(new_dirfd), 105 "new", 106 RenameFlags::empty(), 107 ) 108 .unwrap(); 109 assert_eq!( 110 renameat2( 111 Some(old_dirfd), 112 "old", 113 Some(new_dirfd), 114 "new", 115 RenameFlags::empty() 116 ) 117 .unwrap_err(), 118 Errno::ENOENT 119 ); 120 close(old_dirfd).unwrap(); 121 close(new_dirfd).unwrap(); 122 assert!(new_dir.path().join("new").exists()); 123} 124 125#[test] 126#[cfg(all( 127 target_os = "linux", 128 target_env = "gnu", 129 any( 130 target_arch = "x86_64", 131 target_arch = "x32", 132 target_arch = "powerpc", 133 target_arch = "s390x" 134 ) 135))] 136fn test_renameat2_exchange() { 137 let old_dir = tempfile::tempdir().unwrap(); 138 let old_dirfd = 139 open(old_dir.path(), OFlag::empty(), Mode::empty()).unwrap(); 140 let old_path = old_dir.path().join("old"); 141 { 142 let mut old_f = File::create(&old_path).unwrap(); 143 old_f.write_all(b"old").unwrap(); 144 } 145 let new_dir = tempfile::tempdir().unwrap(); 146 let new_dirfd = 147 open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap(); 148 let new_path = new_dir.path().join("new"); 149 { 150 let mut new_f = File::create(&new_path).unwrap(); 151 new_f.write_all(b"new").unwrap(); 152 } 153 renameat2( 154 Some(old_dirfd), 155 "old", 156 Some(new_dirfd), 157 "new", 158 RenameFlags::RENAME_EXCHANGE, 159 ) 160 .unwrap(); 161 let mut buf = String::new(); 162 let mut new_f = File::open(&new_path).unwrap(); 163 new_f.read_to_string(&mut buf).unwrap(); 164 assert_eq!(buf, "old"); 165 buf = "".to_string(); 166 let mut old_f = File::open(&old_path).unwrap(); 167 old_f.read_to_string(&mut buf).unwrap(); 168 assert_eq!(buf, "new"); 169 close(old_dirfd).unwrap(); 170 close(new_dirfd).unwrap(); 171} 172 173#[test] 174#[cfg(all( 175 target_os = "linux", 176 target_env = "gnu", 177 any( 178 target_arch = "x86_64", 179 target_arch = "x32", 180 target_arch = "powerpc", 181 target_arch = "s390x" 182 ) 183))] 184fn test_renameat2_noreplace() { 185 let old_dir = tempfile::tempdir().unwrap(); 186 let old_dirfd = 187 open(old_dir.path(), OFlag::empty(), Mode::empty()).unwrap(); 188 let old_path = old_dir.path().join("old"); 189 File::create(old_path).unwrap(); 190 let new_dir = tempfile::tempdir().unwrap(); 191 let new_dirfd = 192 open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap(); 193 let new_path = new_dir.path().join("new"); 194 File::create(new_path).unwrap(); 195 assert_eq!( 196 renameat2( 197 Some(old_dirfd), 198 "old", 199 Some(new_dirfd), 200 "new", 201 RenameFlags::RENAME_NOREPLACE 202 ) 203 .unwrap_err(), 204 Errno::EEXIST 205 ); 206 close(old_dirfd).unwrap(); 207 close(new_dirfd).unwrap(); 208 assert!(new_dir.path().join("new").exists()); 209 assert!(old_dir.path().join("old").exists()); 210} 211 212#[test] 213#[cfg(not(target_os = "redox"))] 214fn test_readlink() { 215 let tempdir = tempfile::tempdir().unwrap(); 216 let src = tempdir.path().join("a"); 217 let dst = tempdir.path().join("b"); 218 println!("a: {:?}, b: {:?}", &src, &dst); 219 fs::symlink(src.as_path(), dst.as_path()).unwrap(); 220 let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap(); 221 let expected_dir = src.to_str().unwrap(); 222 223 assert_eq!(readlink(&dst).unwrap().to_str().unwrap(), expected_dir); 224 assert_eq!( 225 readlinkat(dirfd, "b").unwrap().to_str().unwrap(), 226 expected_dir 227 ); 228} 229 230#[cfg(any(target_os = "linux", target_os = "android"))] 231mod linux_android { 232 use libc::loff_t; 233 use std::io::prelude::*; 234 use std::io::IoSlice; 235 use std::os::unix::prelude::*; 236 237 use nix::fcntl::*; 238 use nix::unistd::{close, pipe, read, write}; 239 240 use tempfile::tempfile; 241 #[cfg(any(target_os = "linux"))] 242 use tempfile::NamedTempFile; 243 244 use crate::*; 245 246 /// This test creates a temporary file containing the contents 247 /// 'foobarbaz' and uses the `copy_file_range` call to transfer 248 /// 3 bytes at offset 3 (`bar`) to another empty file at offset 0. The 249 /// resulting file is read and should contain the contents `bar`. 250 /// The from_offset should be updated by the call to reflect 251 /// the 3 bytes read (6). 252 #[test] 253 // QEMU does not support copy_file_range. Skip under qemu 254 #[cfg_attr(qemu, ignore)] 255 fn test_copy_file_range() { 256 const CONTENTS: &[u8] = b"foobarbaz"; 257 258 let mut tmp1 = tempfile().unwrap(); 259 let mut tmp2 = tempfile().unwrap(); 260 261 tmp1.write_all(CONTENTS).unwrap(); 262 tmp1.flush().unwrap(); 263 264 let mut from_offset: i64 = 3; 265 copy_file_range( 266 tmp1.as_raw_fd(), 267 Some(&mut from_offset), 268 tmp2.as_raw_fd(), 269 None, 270 3, 271 ) 272 .unwrap(); 273 274 let mut res: String = String::new(); 275 tmp2.rewind().unwrap(); 276 tmp2.read_to_string(&mut res).unwrap(); 277 278 assert_eq!(res, String::from("bar")); 279 assert_eq!(from_offset, 6); 280 } 281 282 #[test] 283 fn test_splice() { 284 const CONTENTS: &[u8] = b"abcdef123456"; 285 let mut tmp = tempfile().unwrap(); 286 tmp.write_all(CONTENTS).unwrap(); 287 288 let (rd, wr) = pipe().unwrap(); 289 let mut offset: loff_t = 5; 290 let res = splice( 291 tmp.as_raw_fd(), 292 Some(&mut offset), 293 wr, 294 None, 295 2, 296 SpliceFFlags::empty(), 297 ) 298 .unwrap(); 299 300 assert_eq!(2, res); 301 302 let mut buf = [0u8; 1024]; 303 assert_eq!(2, read(rd, &mut buf).unwrap()); 304 assert_eq!(b"f1", &buf[0..2]); 305 assert_eq!(7, offset); 306 307 close(rd).unwrap(); 308 close(wr).unwrap(); 309 } 310 311 #[test] 312 fn test_tee() { 313 let (rd1, wr1) = pipe().unwrap(); 314 let (rd2, wr2) = pipe().unwrap(); 315 316 write(wr1, b"abc").unwrap(); 317 let res = tee(rd1, wr2, 2, SpliceFFlags::empty()).unwrap(); 318 319 assert_eq!(2, res); 320 321 let mut buf = [0u8; 1024]; 322 323 // Check the tee'd bytes are at rd2. 324 assert_eq!(2, read(rd2, &mut buf).unwrap()); 325 assert_eq!(b"ab", &buf[0..2]); 326 327 // Check all the bytes are still at rd1. 328 assert_eq!(3, read(rd1, &mut buf).unwrap()); 329 assert_eq!(b"abc", &buf[0..3]); 330 331 close(rd1).unwrap(); 332 close(wr1).unwrap(); 333 close(rd2).unwrap(); 334 close(wr2).unwrap(); 335 } 336 337 #[test] 338 fn test_vmsplice() { 339 let (rd, wr) = pipe().unwrap(); 340 341 let buf1 = b"abcdef"; 342 let buf2 = b"defghi"; 343 let iovecs = vec![IoSlice::new(&buf1[0..3]), IoSlice::new(&buf2[0..3])]; 344 345 let res = vmsplice(wr, &iovecs[..], SpliceFFlags::empty()).unwrap(); 346 347 assert_eq!(6, res); 348 349 // Check the bytes can be read at rd. 350 let mut buf = [0u8; 32]; 351 assert_eq!(6, read(rd, &mut buf).unwrap()); 352 assert_eq!(b"abcdef", &buf[0..6]); 353 354 close(rd).unwrap(); 355 close(wr).unwrap(); 356 } 357 358 #[cfg(any(target_os = "linux"))] 359 #[test] 360 fn test_fallocate() { 361 let tmp = NamedTempFile::new().unwrap(); 362 363 let fd = tmp.as_raw_fd(); 364 fallocate(fd, FallocateFlags::empty(), 0, 100).unwrap(); 365 366 // Check if we read exactly 100 bytes 367 let mut buf = [0u8; 200]; 368 assert_eq!(100, read(fd, &mut buf).unwrap()); 369 } 370 371 // The tests below are disabled for the listed targets 372 // due to OFD locks not being available in the kernel/libc 373 // versions used in the CI environment, probably because 374 // they run under QEMU. 375 376 #[test] 377 #[cfg(all(target_os = "linux", not(target_env = "musl")))] 378 #[cfg_attr(target_env = "uclibc", ignore)] // uclibc doesn't support OFD locks, but the test should still compile 379 fn test_ofd_write_lock() { 380 use nix::sys::stat::fstat; 381 use std::mem; 382 383 let tmp = NamedTempFile::new().unwrap(); 384 385 let fd = tmp.as_raw_fd(); 386 let statfs = nix::sys::statfs::fstatfs(&tmp).unwrap(); 387 if statfs.filesystem_type() == nix::sys::statfs::OVERLAYFS_SUPER_MAGIC { 388 // OverlayFS is a union file system. It returns one inode value in 389 // stat(2), but a different one shows up in /proc/locks. So we must 390 // skip the test. 391 skip!("/proc/locks does not work on overlayfs"); 392 } 393 let inode = fstat(fd).expect("fstat failed").st_ino as usize; 394 395 let mut flock: libc::flock = unsafe { 396 mem::zeroed() // required for Linux/mips 397 }; 398 flock.l_type = libc::F_WRLCK as libc::c_short; 399 flock.l_whence = libc::SEEK_SET as libc::c_short; 400 flock.l_start = 0; 401 flock.l_len = 0; 402 flock.l_pid = 0; 403 fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("write lock failed"); 404 assert_eq!( 405 Some(("OFDLCK".to_string(), "WRITE".to_string())), 406 lock_info(inode) 407 ); 408 409 flock.l_type = libc::F_UNLCK as libc::c_short; 410 fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("write unlock failed"); 411 assert_eq!(None, lock_info(inode)); 412 } 413 414 #[test] 415 #[cfg(all(target_os = "linux", not(target_env = "musl")))] 416 #[cfg_attr(target_env = "uclibc", ignore)] // uclibc doesn't support OFD locks, but the test should still compile 417 fn test_ofd_read_lock() { 418 use nix::sys::stat::fstat; 419 use std::mem; 420 421 let tmp = NamedTempFile::new().unwrap(); 422 423 let fd = tmp.as_raw_fd(); 424 let statfs = nix::sys::statfs::fstatfs(&tmp).unwrap(); 425 if statfs.filesystem_type() == nix::sys::statfs::OVERLAYFS_SUPER_MAGIC { 426 // OverlayFS is a union file system. It returns one inode value in 427 // stat(2), but a different one shows up in /proc/locks. So we must 428 // skip the test. 429 skip!("/proc/locks does not work on overlayfs"); 430 } 431 let inode = fstat(fd).expect("fstat failed").st_ino as usize; 432 433 let mut flock: libc::flock = unsafe { 434 mem::zeroed() // required for Linux/mips 435 }; 436 flock.l_type = libc::F_RDLCK as libc::c_short; 437 flock.l_whence = libc::SEEK_SET as libc::c_short; 438 flock.l_start = 0; 439 flock.l_len = 0; 440 flock.l_pid = 0; 441 fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("read lock failed"); 442 assert_eq!( 443 Some(("OFDLCK".to_string(), "READ".to_string())), 444 lock_info(inode) 445 ); 446 447 flock.l_type = libc::F_UNLCK as libc::c_short; 448 fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("read unlock failed"); 449 assert_eq!(None, lock_info(inode)); 450 } 451 452 #[cfg(all(target_os = "linux", not(target_env = "musl")))] 453 fn lock_info(inode: usize) -> Option<(String, String)> { 454 use std::{fs::File, io::BufReader}; 455 456 let file = File::open("/proc/locks").expect("open /proc/locks failed"); 457 let buf = BufReader::new(file); 458 459 for line in buf.lines() { 460 let line = line.unwrap(); 461 let parts: Vec<_> = line.split_whitespace().collect(); 462 let lock_type = parts[1]; 463 let lock_access = parts[3]; 464 let ino_parts: Vec<_> = parts[5].split(':').collect(); 465 let ino: usize = ino_parts[2].parse().unwrap(); 466 if ino == inode { 467 return Some((lock_type.to_string(), lock_access.to_string())); 468 } 469 } 470 None 471 } 472} 473 474#[cfg(any( 475 target_os = "linux", 476 target_os = "android", 477 target_os = "emscripten", 478 target_os = "fuchsia", 479 target_os = "wasi", 480 target_env = "uclibc", 481 target_os = "freebsd" 482))] 483mod test_posix_fadvise { 484 485 use nix::errno::Errno; 486 use nix::fcntl::*; 487 use nix::unistd::pipe; 488 use std::os::unix::io::{AsRawFd, RawFd}; 489 use tempfile::NamedTempFile; 490 491 #[test] 492 fn test_success() { 493 let tmp = NamedTempFile::new().unwrap(); 494 let fd = tmp.as_raw_fd(); 495 posix_fadvise(fd, 0, 100, PosixFadviseAdvice::POSIX_FADV_WILLNEED) 496 .expect("posix_fadvise failed"); 497 } 498 499 #[test] 500 fn test_errno() { 501 let (rd, _wr) = pipe().unwrap(); 502 let res = posix_fadvise( 503 rd as RawFd, 504 0, 505 100, 506 PosixFadviseAdvice::POSIX_FADV_WILLNEED, 507 ); 508 assert_eq!(res, Err(Errno::ESPIPE)); 509 } 510} 511 512#[cfg(any( 513 target_os = "linux", 514 target_os = "android", 515 target_os = "dragonfly", 516 target_os = "emscripten", 517 target_os = "fuchsia", 518 target_os = "wasi", 519 target_os = "freebsd" 520))] 521mod test_posix_fallocate { 522 523 use nix::errno::Errno; 524 use nix::fcntl::*; 525 use nix::unistd::pipe; 526 use std::{ 527 io::Read, 528 os::unix::io::{AsRawFd, RawFd}, 529 }; 530 use tempfile::NamedTempFile; 531 532 #[test] 533 fn success() { 534 const LEN: usize = 100; 535 let mut tmp = NamedTempFile::new().unwrap(); 536 let fd = tmp.as_raw_fd(); 537 let res = posix_fallocate(fd, 0, LEN as libc::off_t); 538 match res { 539 Ok(_) => { 540 let mut data = [1u8; LEN]; 541 assert_eq!(tmp.read(&mut data).expect("read failure"), LEN); 542 assert_eq!(&data[..], &[0u8; LEN][..]); 543 } 544 Err(Errno::EINVAL) => { 545 // POSIX requires posix_fallocate to return EINVAL both for 546 // invalid arguments (i.e. len < 0) and if the operation is not 547 // supported by the file system. 548 // There's no way to tell for sure whether the file system 549 // supports posix_fallocate, so we must pass the test if it 550 // returns EINVAL. 551 } 552 _ => res.unwrap(), 553 } 554 } 555 556 #[test] 557 fn errno() { 558 let (rd, _wr) = pipe().unwrap(); 559 let err = posix_fallocate(rd as RawFd, 0, 100).unwrap_err(); 560 match err { 561 Errno::EINVAL | Errno::ENODEV | Errno::ESPIPE | Errno::EBADF => (), 562 errno => panic!("unexpected errno {}", errno,), 563 } 564 } 565} 566