| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame^] | 1 | #[cfg(not(target_os = "redox"))] | 
 | 2 | use nix::Error; | 
 | 3 | #[cfg(not(target_os = "redox"))] | 
 | 4 | use nix::errno::*; | 
 | 5 | #[cfg(not(target_os = "redox"))] | 
 | 6 | use nix::fcntl::{open, OFlag, readlink}; | 
 | 7 | #[cfg(not(target_os = "redox"))] | 
 | 8 | use nix::fcntl::{openat, readlinkat, renameat}; | 
 | 9 | #[cfg(not(target_os = "redox"))] | 
 | 10 | use nix::sys::stat::Mode; | 
 | 11 | #[cfg(not(target_os = "redox"))] | 
 | 12 | use nix::unistd::{close, read}; | 
 | 13 | #[cfg(not(target_os = "redox"))] | 
 | 14 | use tempfile::{self, NamedTempFile}; | 
 | 15 | #[cfg(not(target_os = "redox"))] | 
 | 16 | use std::fs::File; | 
 | 17 | #[cfg(not(target_os = "redox"))] | 
 | 18 | use std::io::prelude::*; | 
 | 19 | #[cfg(not(target_os = "redox"))] | 
 | 20 | use std::os::unix::fs; | 
 | 21 |  | 
 | 22 | #[test] | 
 | 23 | #[cfg(not(target_os = "redox"))] | 
 | 24 | fn test_openat() { | 
 | 25 |     const CONTENTS: &[u8] = b"abcd"; | 
 | 26 |     let mut tmp = NamedTempFile::new().unwrap(); | 
 | 27 |     tmp.write_all(CONTENTS).unwrap(); | 
 | 28 |  | 
 | 29 |     let dirfd = open(tmp.path().parent().unwrap(), | 
 | 30 |                      OFlag::empty(), | 
 | 31 |                      Mode::empty()).unwrap(); | 
 | 32 |     let fd = openat(dirfd, | 
 | 33 |                     tmp.path().file_name().unwrap(), | 
 | 34 |                     OFlag::O_RDONLY, | 
 | 35 |                     Mode::empty()).unwrap(); | 
 | 36 |  | 
 | 37 |     let mut buf = [0u8; 1024]; | 
 | 38 |     assert_eq!(4, read(fd, &mut buf).unwrap()); | 
 | 39 |     assert_eq!(CONTENTS, &buf[0..4]); | 
 | 40 |  | 
 | 41 |     close(fd).unwrap(); | 
 | 42 |     close(dirfd).unwrap(); | 
 | 43 | } | 
 | 44 |  | 
 | 45 | #[test] | 
 | 46 | #[cfg(not(target_os = "redox"))] | 
 | 47 | fn test_renameat() { | 
 | 48 |     let old_dir = tempfile::tempdir().unwrap(); | 
 | 49 |     let old_dirfd = open(old_dir.path(), OFlag::empty(), Mode::empty()).unwrap(); | 
 | 50 |     let old_path = old_dir.path().join("old"); | 
 | 51 |     File::create(&old_path).unwrap(); | 
 | 52 |     let new_dir = tempfile::tempdir().unwrap(); | 
 | 53 |     let new_dirfd = open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap(); | 
 | 54 |     renameat(Some(old_dirfd), "old", Some(new_dirfd), "new").unwrap(); | 
 | 55 |     assert_eq!(renameat(Some(old_dirfd), "old", Some(new_dirfd), "new").unwrap_err(), | 
 | 56 |                Error::Sys(Errno::ENOENT)); | 
 | 57 |     close(old_dirfd).unwrap(); | 
 | 58 |     close(new_dirfd).unwrap(); | 
 | 59 |     assert!(new_dir.path().join("new").exists()); | 
 | 60 | } | 
 | 61 |  | 
 | 62 | #[test] | 
 | 63 | #[cfg(not(target_os = "redox"))] | 
 | 64 | fn test_readlink() { | 
 | 65 |     let tempdir = tempfile::tempdir().unwrap(); | 
 | 66 |     let src = tempdir.path().join("a"); | 
 | 67 |     let dst = tempdir.path().join("b"); | 
 | 68 |     println!("a: {:?}, b: {:?}", &src, &dst); | 
 | 69 |     fs::symlink(&src.as_path(), &dst.as_path()).unwrap(); | 
 | 70 |     let dirfd = open(tempdir.path(), | 
 | 71 |                      OFlag::empty(), | 
 | 72 |                      Mode::empty()).unwrap(); | 
 | 73 |     let expected_dir = src.to_str().unwrap(); | 
 | 74 |  | 
 | 75 |     assert_eq!(readlink(&dst).unwrap().to_str().unwrap(), expected_dir); | 
 | 76 |     assert_eq!(readlinkat(dirfd, "b").unwrap().to_str().unwrap(), expected_dir); | 
 | 77 |  | 
 | 78 | } | 
 | 79 |  | 
 | 80 | #[cfg(any(target_os = "linux", target_os = "android"))] | 
 | 81 | mod linux_android { | 
 | 82 |     use std::fs::File; | 
 | 83 |     use std::io::prelude::*; | 
 | 84 |     use std::io::{BufRead, BufReader, SeekFrom}; | 
 | 85 |     use std::os::unix::prelude::*; | 
 | 86 |  | 
 | 87 |     use libc::loff_t; | 
 | 88 |  | 
 | 89 |     use nix::fcntl::*; | 
 | 90 |     use nix::sys::stat::fstat; | 
 | 91 |     use nix::sys::uio::IoVec; | 
 | 92 |     use nix::unistd::{close, pipe, read, write}; | 
 | 93 |  | 
 | 94 |     use tempfile::{tempfile, NamedTempFile}; | 
 | 95 |  | 
 | 96 |     /// This test creates a temporary file containing the contents | 
 | 97 |     /// 'foobarbaz' and uses the `copy_file_range` call to transfer | 
 | 98 |     /// 3 bytes at offset 3 (`bar`) to another empty file at offset 0. The | 
 | 99 |     /// resulting file is read and should contain the contents `bar`. | 
 | 100 |     /// The from_offset should be updated by the call to reflect | 
 | 101 |     /// the 3 bytes read (6). | 
 | 102 |     /// | 
 | 103 |     /// FIXME: This test is disabled for linux based builds, because Travis | 
 | 104 |     /// Linux version is too old for `copy_file_range`. | 
 | 105 |     #[test] | 
 | 106 |     #[ignore] | 
 | 107 |     fn test_copy_file_range() { | 
 | 108 |         const CONTENTS: &[u8] = b"foobarbaz"; | 
 | 109 |  | 
 | 110 |         let mut tmp1 = tempfile().unwrap(); | 
 | 111 |         let mut tmp2 = tempfile().unwrap(); | 
 | 112 |  | 
 | 113 |         tmp1.write_all(CONTENTS).unwrap(); | 
 | 114 |         tmp1.flush().unwrap(); | 
 | 115 |  | 
 | 116 |         let mut from_offset: i64 = 3; | 
 | 117 |         copy_file_range( | 
 | 118 |             tmp1.as_raw_fd(), | 
 | 119 |             Some(&mut from_offset), | 
 | 120 |             tmp2.as_raw_fd(), | 
 | 121 |             None, | 
 | 122 |             3, | 
 | 123 |         ) | 
 | 124 |         .unwrap(); | 
 | 125 |  | 
 | 126 |         let mut res: String = String::new(); | 
 | 127 |         tmp2.seek(SeekFrom::Start(0)).unwrap(); | 
 | 128 |         tmp2.read_to_string(&mut res).unwrap(); | 
 | 129 |  | 
 | 130 |         assert_eq!(res, String::from("bar")); | 
 | 131 |         assert_eq!(from_offset, 6); | 
 | 132 |     } | 
 | 133 |  | 
 | 134 |     #[test] | 
 | 135 |     fn test_splice() { | 
 | 136 |         const CONTENTS: &[u8] = b"abcdef123456"; | 
 | 137 |         let mut tmp = tempfile().unwrap(); | 
 | 138 |         tmp.write_all(CONTENTS).unwrap(); | 
 | 139 |  | 
 | 140 |         let (rd, wr) = pipe().unwrap(); | 
 | 141 |         let mut offset: loff_t = 5; | 
 | 142 |         let res = splice(tmp.as_raw_fd(), Some(&mut offset), | 
 | 143 |             wr, None, 2, SpliceFFlags::empty()).unwrap(); | 
 | 144 |  | 
 | 145 |         assert_eq!(2, res); | 
 | 146 |  | 
 | 147 |         let mut buf = [0u8; 1024]; | 
 | 148 |         assert_eq!(2, read(rd, &mut buf).unwrap()); | 
 | 149 |         assert_eq!(b"f1", &buf[0..2]); | 
 | 150 |         assert_eq!(7, offset); | 
 | 151 |  | 
 | 152 |         close(rd).unwrap(); | 
 | 153 |         close(wr).unwrap(); | 
 | 154 |     } | 
 | 155 |  | 
 | 156 |     #[test] | 
 | 157 |     fn test_tee() { | 
 | 158 |         let (rd1, wr1) = pipe().unwrap(); | 
 | 159 |         let (rd2, wr2) = pipe().unwrap(); | 
 | 160 |  | 
 | 161 |         write(wr1, b"abc").unwrap(); | 
 | 162 |         let res = tee(rd1, wr2, 2, SpliceFFlags::empty()).unwrap(); | 
 | 163 |  | 
 | 164 |         assert_eq!(2, res); | 
 | 165 |  | 
 | 166 |         let mut buf = [0u8; 1024]; | 
 | 167 |  | 
 | 168 |         // Check the tee'd bytes are at rd2. | 
 | 169 |         assert_eq!(2, read(rd2, &mut buf).unwrap()); | 
 | 170 |         assert_eq!(b"ab", &buf[0..2]); | 
 | 171 |  | 
 | 172 |         // Check all the bytes are still at rd1. | 
 | 173 |         assert_eq!(3, read(rd1, &mut buf).unwrap()); | 
 | 174 |         assert_eq!(b"abc", &buf[0..3]); | 
 | 175 |  | 
 | 176 |         close(rd1).unwrap(); | 
 | 177 |         close(wr1).unwrap(); | 
 | 178 |         close(rd2).unwrap(); | 
 | 179 |         close(wr2).unwrap(); | 
 | 180 |     } | 
 | 181 |  | 
 | 182 |     #[test] | 
 | 183 |     fn test_vmsplice() { | 
 | 184 |         let (rd, wr) = pipe().unwrap(); | 
 | 185 |  | 
 | 186 |         let buf1 = b"abcdef"; | 
 | 187 |         let buf2 = b"defghi"; | 
 | 188 |         let mut iovecs = Vec::with_capacity(2); | 
 | 189 |         iovecs.push(IoVec::from_slice(&buf1[0..3])); | 
 | 190 |         iovecs.push(IoVec::from_slice(&buf2[0..3])); | 
 | 191 |  | 
 | 192 |         let res = vmsplice(wr, &iovecs[..], SpliceFFlags::empty()).unwrap(); | 
 | 193 |  | 
 | 194 |         assert_eq!(6, res); | 
 | 195 |  | 
 | 196 |         // Check the bytes can be read at rd. | 
 | 197 |         let mut buf = [0u8; 32]; | 
 | 198 |         assert_eq!(6, read(rd, &mut buf).unwrap()); | 
 | 199 |         assert_eq!(b"abcdef", &buf[0..6]); | 
 | 200 |  | 
 | 201 |         close(rd).unwrap(); | 
 | 202 |         close(wr).unwrap(); | 
 | 203 |     } | 
 | 204 |  | 
 | 205 |     #[test] | 
 | 206 |     fn test_fallocate() { | 
 | 207 |         let tmp = NamedTempFile::new().unwrap(); | 
 | 208 |  | 
 | 209 |         let fd = tmp.as_raw_fd(); | 
 | 210 |         fallocate(fd, FallocateFlags::empty(), 0, 100).unwrap(); | 
 | 211 |  | 
 | 212 |         // Check if we read exactly 100 bytes | 
 | 213 |         let mut buf = [0u8; 200]; | 
 | 214 |         assert_eq!(100, read(fd, &mut buf).unwrap()); | 
 | 215 |     } | 
 | 216 |  | 
 | 217 |     // The tests below are disabled for the listed targets | 
 | 218 |     // due to OFD locks not being available in the kernel/libc | 
 | 219 |     // versions used in the CI environment, probably because | 
 | 220 |     // they run under QEMU. | 
 | 221 |  | 
 | 222 |     #[test] | 
 | 223 |     #[cfg(not(any(target_arch = "aarch64", | 
 | 224 |                   target_arch = "arm", | 
 | 225 |                   target_arch = "armv7", | 
 | 226 |                   target_arch = "x86", | 
 | 227 |                   target_arch = "mips", | 
 | 228 |                   target_arch = "mips64", | 
 | 229 |                   target_arch = "mips64el", | 
 | 230 |                   target_arch = "powerpc64", | 
 | 231 |                   target_arch = "powerpc64le", | 
 | 232 |                   target_env = "musl")))] | 
 | 233 |     fn test_ofd_write_lock() { | 
 | 234 |         let tmp = NamedTempFile::new().unwrap(); | 
 | 235 |  | 
 | 236 |         let fd = tmp.as_raw_fd(); | 
 | 237 |         let statfs = nix::sys::statfs::fstatfs(&tmp).unwrap(); | 
 | 238 |         if statfs.filesystem_type() == nix::sys::statfs::OVERLAYFS_SUPER_MAGIC { | 
 | 239 |             // OverlayFS is a union file system.  It returns one inode value in | 
 | 240 |             // stat(2), but a different one shows up in /proc/locks.  So we must | 
 | 241 |             // skip the test. | 
 | 242 |             skip!("/proc/locks does not work on overlayfs"); | 
 | 243 |         } | 
 | 244 |         let inode = fstat(fd).expect("fstat failed").st_ino as usize; | 
 | 245 |  | 
 | 246 |         let mut flock = libc::flock { | 
 | 247 |             l_type: libc::F_WRLCK as libc::c_short, | 
 | 248 |             l_whence: libc::SEEK_SET as libc::c_short, | 
 | 249 |             l_start: 0, | 
 | 250 |             l_len: 0, | 
 | 251 |             l_pid: 0, | 
 | 252 |         }; | 
 | 253 |         fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("write lock failed"); | 
 | 254 |         assert_eq!( | 
 | 255 |             Some(("OFDLCK".to_string(), "WRITE".to_string())), | 
 | 256 |             lock_info(inode) | 
 | 257 |         ); | 
 | 258 |  | 
 | 259 |         flock.l_type = libc::F_UNLCK as libc::c_short; | 
 | 260 |         fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("write unlock failed"); | 
 | 261 |         assert_eq!(None, lock_info(inode)); | 
 | 262 |     } | 
 | 263 |  | 
 | 264 |     #[test] | 
 | 265 |     #[cfg(not(any(target_arch = "aarch64", | 
 | 266 |                   target_arch = "arm", | 
 | 267 |                   target_arch = "armv7", | 
 | 268 |                   target_arch = "x86", | 
 | 269 |                   target_arch = "mips", | 
 | 270 |                   target_arch = "mips64", | 
 | 271 |                   target_arch = "mips64el", | 
 | 272 |                   target_arch = "powerpc64", | 
 | 273 |                   target_arch = "powerpc64le", | 
 | 274 |                   target_env = "musl")))] | 
 | 275 |     fn test_ofd_read_lock() { | 
 | 276 |         let tmp = NamedTempFile::new().unwrap(); | 
 | 277 |  | 
 | 278 |         let fd = tmp.as_raw_fd(); | 
 | 279 |         let statfs = nix::sys::statfs::fstatfs(&tmp).unwrap(); | 
 | 280 |         if statfs.filesystem_type() == nix::sys::statfs::OVERLAYFS_SUPER_MAGIC { | 
 | 281 |             // OverlayFS is a union file system.  It returns one inode value in | 
 | 282 |             // stat(2), but a different one shows up in /proc/locks.  So we must | 
 | 283 |             // skip the test. | 
 | 284 |             skip!("/proc/locks does not work on overlayfs"); | 
 | 285 |         } | 
 | 286 |         let inode = fstat(fd).expect("fstat failed").st_ino as usize; | 
 | 287 |  | 
 | 288 |         let mut flock = libc::flock { | 
 | 289 |             l_type: libc::F_RDLCK as libc::c_short, | 
 | 290 |             l_whence: libc::SEEK_SET as libc::c_short, | 
 | 291 |             l_start: 0, | 
 | 292 |             l_len: 0, | 
 | 293 |             l_pid: 0, | 
 | 294 |         }; | 
 | 295 |         fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("read lock failed"); | 
 | 296 |         assert_eq!( | 
 | 297 |             Some(("OFDLCK".to_string(), "READ".to_string())), | 
 | 298 |             lock_info(inode) | 
 | 299 |         ); | 
 | 300 |  | 
 | 301 |         flock.l_type = libc::F_UNLCK as libc::c_short; | 
 | 302 |         fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("read unlock failed"); | 
 | 303 |         assert_eq!(None, lock_info(inode)); | 
 | 304 |     } | 
 | 305 |  | 
 | 306 |     fn lock_info(inode: usize) -> Option<(String, String)> { | 
 | 307 |         let file = File::open("/proc/locks").expect("open /proc/locks failed"); | 
 | 308 |         let buf = BufReader::new(file); | 
 | 309 |  | 
 | 310 |         for line in buf.lines() { | 
 | 311 |             let line = line.unwrap(); | 
 | 312 |             let parts: Vec<_> = line.split_whitespace().collect(); | 
 | 313 |             let lock_type = parts[1]; | 
 | 314 |             let lock_access = parts[3]; | 
 | 315 |             let ino_parts: Vec<_> = parts[5].split(':').collect(); | 
 | 316 |             let ino: usize = ino_parts[2].parse().unwrap(); | 
 | 317 |             if ino == inode { | 
 | 318 |                 return Some((lock_type.to_string(), lock_access.to_string())); | 
 | 319 |             } | 
 | 320 |         } | 
 | 321 |         None | 
 | 322 |     } | 
 | 323 | } | 
 | 324 |  | 
 | 325 | #[cfg(any(target_os = "linux", | 
 | 326 |           target_os = "android", | 
 | 327 |           target_os = "emscripten", | 
 | 328 |           target_os = "fuchsia", | 
 | 329 |           any(target_os = "wasi", target_env = "wasi"), | 
 | 330 |           target_env = "uclibc", | 
 | 331 |           target_env = "freebsd"))] | 
 | 332 | mod test_posix_fadvise { | 
 | 333 |  | 
 | 334 |     use tempfile::NamedTempFile; | 
 | 335 |     use std::os::unix::io::{RawFd, AsRawFd}; | 
 | 336 |     use nix::errno::Errno; | 
 | 337 |     use nix::fcntl::*; | 
 | 338 |     use nix::unistd::pipe; | 
 | 339 |  | 
 | 340 |     #[test] | 
 | 341 |     fn test_success() { | 
 | 342 |         let tmp = NamedTempFile::new().unwrap(); | 
 | 343 |         let fd = tmp.as_raw_fd(); | 
 | 344 |         let res = posix_fadvise(fd, 0, 100, PosixFadviseAdvice::POSIX_FADV_WILLNEED).unwrap(); | 
 | 345 |  | 
 | 346 |         assert_eq!(res, 0); | 
 | 347 |     } | 
 | 348 |  | 
 | 349 |     #[test] | 
 | 350 |     fn test_errno() { | 
 | 351 |         let (rd, _wr) = pipe().unwrap(); | 
 | 352 |         let errno = posix_fadvise(rd as RawFd, 0, 100, PosixFadviseAdvice::POSIX_FADV_WILLNEED) | 
 | 353 |                                  .unwrap(); | 
 | 354 |         assert_eq!(errno, Errno::ESPIPE as i32); | 
 | 355 |     } | 
 | 356 | } | 
 | 357 |  | 
 | 358 | #[cfg(any(target_os = "linux", | 
 | 359 |           target_os = "android", | 
 | 360 |           target_os = "emscripten", | 
 | 361 |           target_os = "fuchsia", | 
 | 362 |           any(target_os = "wasi", target_env = "wasi"), | 
 | 363 |           target_os = "freebsd"))] | 
 | 364 | mod test_posix_fallocate { | 
 | 365 |  | 
 | 366 |     use tempfile::NamedTempFile; | 
 | 367 |     use std::{io::Read, os::unix::io::{RawFd, AsRawFd}}; | 
 | 368 |     use nix::errno::Errno; | 
 | 369 |     use nix::fcntl::*; | 
 | 370 |     use nix::unistd::pipe; | 
 | 371 |  | 
 | 372 |     #[test] | 
 | 373 |     fn success() { | 
 | 374 |         const LEN: usize = 100; | 
 | 375 |         let mut tmp = NamedTempFile::new().unwrap(); | 
 | 376 |         let fd = tmp.as_raw_fd(); | 
 | 377 |         let res = posix_fallocate(fd, 0, LEN as libc::off_t); | 
 | 378 |         match res { | 
 | 379 |             Ok(_) => { | 
 | 380 |                 let mut data = [1u8; LEN]; | 
 | 381 |                 assert_eq!(tmp.read(&mut data).expect("read failure"), LEN); | 
 | 382 |                 assert_eq!(&data[..], &[0u8; LEN][..]); | 
 | 383 |             } | 
 | 384 |             Err(nix::Error::Sys(Errno::EINVAL)) => { | 
 | 385 |                 // POSIX requires posix_fallocate to return EINVAL both for | 
 | 386 |                 // invalid arguments (i.e. len < 0) and if the operation is not | 
 | 387 |                 // supported by the file system. | 
 | 388 |                 // There's no way to tell for sure whether the file system | 
 | 389 |                 // supports posix_fallocate, so we must pass the test if it | 
 | 390 |                 // returns EINVAL. | 
 | 391 |             } | 
 | 392 |             _ => res.unwrap(), | 
 | 393 |         } | 
 | 394 |     } | 
 | 395 |  | 
 | 396 |     #[test] | 
 | 397 |     fn errno() { | 
 | 398 |         let (rd, _wr) = pipe().unwrap(); | 
 | 399 |         let err = posix_fallocate(rd as RawFd, 0, 100).unwrap_err(); | 
 | 400 |         use nix::Error::Sys; | 
 | 401 |         match err { | 
 | 402 |             Sys(Errno::EINVAL) | 
 | 403 |                 | Sys(Errno::ENODEV) | 
 | 404 |                 | Sys(Errno::ESPIPE) | 
 | 405 |                 | Sys(Errno::EBADF) => (), | 
 | 406 |             errno => | 
 | 407 |                 panic!( | 
 | 408 |                     "unexpected errno {}", | 
 | 409 |                     errno, | 
 | 410 |                 ), | 
 | 411 |         } | 
 | 412 |     } | 
 | 413 | } |