Merge branch 'impl-posix_fallocate' into 'master'
Implement posix_fallocate See merge request redox-os/relibc!751
This commit is contained in:
@@ -4,6 +4,8 @@
|
||||
|
||||
#![deny(unsafe_op_in_unsafe_fn)]
|
||||
|
||||
use core::num::NonZeroU64;
|
||||
|
||||
use crate::{
|
||||
c_str::CStr,
|
||||
error::ResultExt,
|
||||
@@ -15,6 +17,8 @@ use crate::{
|
||||
|
||||
pub use self::sys::*;
|
||||
|
||||
use super::errno::EINVAL;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[path = "linux.rs"]
|
||||
pub mod sys;
|
||||
@@ -90,3 +94,20 @@ pub unsafe extern "C" fn open(path: *const c_char, oflag: c_int, mut __valist: .
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn cbindgen_stupid_struct_user_for_fcntl(a: flock) {}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn posix_fallocate(fd: c_int, offset: off_t, length: off_t) -> c_int {
|
||||
// Length can't be zero and offset must be positive.
|
||||
let Ok(offset) = offset.try_into() else {
|
||||
return EINVAL;
|
||||
};
|
||||
let Some(length) = length.try_into().ok().and_then(NonZeroU64::new) else {
|
||||
return EINVAL;
|
||||
};
|
||||
|
||||
// posix_fallocate does not set errno but instead returns it.
|
||||
Sys::posix_fallocate(fd, offset, length)
|
||||
.err()
|
||||
.map(|e| e.0)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
@@ -7,14 +7,14 @@ use crate::{
|
||||
platform::{Pal, Sys, types::*},
|
||||
};
|
||||
|
||||
pub const LOCK_SH: usize = 1;
|
||||
pub const LOCK_EX: usize = 2;
|
||||
pub const LOCK_NB: usize = 4;
|
||||
pub const LOCK_UN: usize = 8;
|
||||
pub const LOCK_SH: c_int = 1;
|
||||
pub const LOCK_EX: c_int = 2;
|
||||
pub const LOCK_NB: c_int = 4;
|
||||
pub const LOCK_UN: c_int = 8;
|
||||
|
||||
pub const L_SET: usize = 0;
|
||||
pub const L_INCR: usize = 1;
|
||||
pub const L_XTND: usize = 2;
|
||||
pub const L_SET: c_int = 0;
|
||||
pub const L_INCR: c_int = 1;
|
||||
pub const L_XTND: c_int = 2;
|
||||
|
||||
/// See <https://man7.org/linux/man-pages/man2/flock.2.html>.
|
||||
#[unsafe(no_mangle)]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use core::{arch::asm, ptr};
|
||||
use core::{arch::asm, num::NonZeroU64, ptr};
|
||||
|
||||
use super::{ERRNO, Pal, types::*};
|
||||
use crate::{
|
||||
@@ -560,6 +560,11 @@ impl Pal for Sys {
|
||||
e_raw(unsafe { syscall!(PIPE2, fildes.as_mut_ptr(), flags) }).map(|_| ())
|
||||
}
|
||||
|
||||
fn posix_fallocate(fd: c_int, offset: u64, length: NonZeroU64) -> Result<()> {
|
||||
let length = length.get();
|
||||
e_raw(unsafe { syscall!(FALLOCATE, fd, 0, offset, length) }).map(|_| ())
|
||||
}
|
||||
|
||||
fn posix_getdents(fildes: c_int, buf: &mut [u8]) -> Result<usize> {
|
||||
let current_offset = Self::lseek(fildes, 0, SEEK_CUR)? as u64;
|
||||
let bytes_read = Self::getdents(fildes, buf, current_offset)?;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use core::num::NonZeroU64;
|
||||
|
||||
use super::types::*;
|
||||
use crate::{
|
||||
c_str::CStr,
|
||||
@@ -217,6 +219,8 @@ pub trait Pal {
|
||||
|
||||
fn pipe2(fildes: Out<[c_int; 2]>, flags: c_int) -> Result<()>;
|
||||
|
||||
fn posix_fallocate(fd: c_int, offset: u64, length: NonZeroU64) -> Result<()>;
|
||||
|
||||
fn posix_getdents(fildes: c_int, buf: &mut [u8]) -> Result<usize>;
|
||||
|
||||
unsafe fn rlct_clone(stack: *mut usize) -> Result<pthread::OsTid, Errno>;
|
||||
|
||||
+48
-16
@@ -1,6 +1,7 @@
|
||||
use core::{
|
||||
convert::TryFrom,
|
||||
mem::{self, MaybeUninit, size_of},
|
||||
num::NonZeroU64,
|
||||
ptr, slice, str,
|
||||
};
|
||||
use redox_rt::{
|
||||
@@ -16,7 +17,7 @@ use syscall::{
|
||||
|
||||
use self::{
|
||||
exec::Executable,
|
||||
path::{canonicalize, openat2, openat2_path},
|
||||
path::{FileLock, canonicalize, openat2, openat2_path},
|
||||
};
|
||||
use super::{ERRNO, Pal, Read, types::*};
|
||||
use crate::{
|
||||
@@ -26,14 +27,15 @@ use crate::{
|
||||
header::{
|
||||
dirent::dirent,
|
||||
errno::{
|
||||
EBADF, EBADFD, EBADR, EEXIST, EFAULT, EINTR, EINVAL, EIO, ENAMETOOLONG, ENOENT, ENOMEM,
|
||||
ENOSYS, EOPNOTSUPP, EPERM, ERANGE,
|
||||
EBADF, EBADFD, EBADR, EEXIST, EFAULT, EFBIG, EINTR, EINVAL, EIO, ENAMETOOLONG, ENOENT,
|
||||
ENOMEM, ENOSYS, EOPNOTSUPP, EPERM, ERANGE,
|
||||
},
|
||||
fcntl::{self, AT_FDCWD, AT_SYMLINK_NOFOLLOW, O_CREAT, O_RDONLY, O_RDWR},
|
||||
limits,
|
||||
pthread::{pthread_cancel, pthread_create},
|
||||
signal::{NSIG, SIGEV_NONE, SIGEV_SIGNAL, SIGEV_THREAD, SIGRTMIN, sigevent},
|
||||
stdio::RENAME_NOREPLACE,
|
||||
sys_file,
|
||||
sys_mman::{MAP_ANONYMOUS, MAP_FAILED, PROT_READ, PROT_WRITE},
|
||||
sys_random,
|
||||
sys_resource::{RLIM_INFINITY, rlimit, rusage},
|
||||
@@ -56,6 +58,19 @@ use crate::{
|
||||
|
||||
pub use redox_rt::proc::FdGuard;
|
||||
|
||||
mod clone;
|
||||
mod epoll;
|
||||
mod event;
|
||||
mod exec;
|
||||
mod extra;
|
||||
mod libcscheme;
|
||||
mod libredox;
|
||||
pub(crate) mod path;
|
||||
mod ptrace;
|
||||
pub(crate) mod signal;
|
||||
mod socket;
|
||||
mod timer;
|
||||
|
||||
static mut BRK_CUR: *mut c_void = ptr::null_mut();
|
||||
static mut BRK_END: *mut c_void = ptr::null_mut();
|
||||
|
||||
@@ -72,19 +87,6 @@ fn cvt_uid(id: c_int) -> Result<Option<u32>> {
|
||||
Ok(Some(id.try_into().map_err(|_| Errno(EINVAL))?))
|
||||
}
|
||||
|
||||
mod clone;
|
||||
mod epoll;
|
||||
mod event;
|
||||
mod exec;
|
||||
mod extra;
|
||||
mod libcscheme;
|
||||
mod libredox;
|
||||
pub(crate) mod path;
|
||||
mod ptrace;
|
||||
pub(crate) mod signal;
|
||||
mod socket;
|
||||
mod timer;
|
||||
|
||||
macro_rules! path_from_c_str {
|
||||
($c_str:expr) => {{
|
||||
match $c_str.to_str() {
|
||||
@@ -820,6 +822,36 @@ impl Pal for Sys {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn posix_fallocate(fd: c_int, offset: u64, length: NonZeroU64) -> Result<()> {
|
||||
// Redox doesn't actually have flock yet but presumably the file will need to be locked to
|
||||
// avoid accidentally truncating it if the length changes.
|
||||
let _guard = FileLock::lock(fd, sys_file::LOCK_EX)?;
|
||||
|
||||
// posix_fallocate is less nuanced than the Linux syscall fallocate.
|
||||
// If the byte range is already allocated, posix_fallocate doesn't do any extra work.
|
||||
// If the byte range is unallocated; free, uninitialized bytes are reserved.
|
||||
// posix_fallocate does not shrink files.
|
||||
//
|
||||
// The main purpose of it is to ensure subsequent writes to a byte range don't fail.
|
||||
let length = length.get();
|
||||
let total_offset = offset.checked_add(length).ok_or(Errno(EFBIG))?;
|
||||
|
||||
let mut stat = syscall::Stat::default();
|
||||
syscall::fstat(fd as usize, &mut stat)?;
|
||||
// The difference between total_offset and the file size is the number of bytes to
|
||||
// allocate. So, if it's negative then the file is already large enough and we don't
|
||||
// need to do any extra work.
|
||||
if let Some(total_len) = total_offset
|
||||
.checked_sub(stat.st_size)
|
||||
.and_then(|diff| stat.st_size.checked_add(diff))
|
||||
{
|
||||
let total_len: usize = total_len.try_into().map_err(|_| Errno(EFBIG))?;
|
||||
syscall::ftruncate(fd as usize, total_len)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn posix_getdents(fildes: c_int, buf: &mut [u8]) -> Result<usize> {
|
||||
let current_offset = Self::lseek(fildes, 0, SEEK_CUR)? as u64;
|
||||
let bytes_read = Self::getdents(fildes, buf, current_offset)?;
|
||||
|
||||
@@ -13,7 +13,7 @@ use super::{FdGuard, Pal, Sys, libcscheme};
|
||||
use crate::{
|
||||
error::Errno,
|
||||
fs::File,
|
||||
header::{fcntl, limits},
|
||||
header::{fcntl, limits, sys_file},
|
||||
out::Out,
|
||||
sync::Mutex,
|
||||
};
|
||||
@@ -210,6 +210,30 @@ fn get_parent_path(path: &str) -> Option<&str> {
|
||||
})
|
||||
}
|
||||
|
||||
pub struct FileLock(c_int);
|
||||
|
||||
impl FileLock {
|
||||
pub fn lock(fd: c_int, op: c_int) -> Result<Self> {
|
||||
if op & sys_file::LOCK_SH | sys_file::LOCK_EX == 0 {
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
|
||||
Sys::flock(fd, op)?;
|
||||
Ok(Self(fd))
|
||||
}
|
||||
|
||||
pub fn unlock(self) -> Result<()> {
|
||||
Sys::flock(self.0, sys_file::LOCK_UN).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FileLock {
|
||||
fn drop(&mut self) {
|
||||
let fd = self.0;
|
||||
self.0 = -1;
|
||||
let _ = Sys::flock(self.0, sys_file::LOCK_UN);
|
||||
}
|
||||
}
|
||||
/// Resolve `path` under `dirfd`.
|
||||
///
|
||||
/// See [`openat2`] for more information.
|
||||
|
||||
@@ -35,6 +35,7 @@ EXPECT_NAMES=\
|
||||
error \
|
||||
fcntl/create \
|
||||
fcntl/fcntl \
|
||||
fcntl/posix_fallocate \
|
||||
features \
|
||||
fnmatch \
|
||||
glob \
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
__attribute__((nonnull))
|
||||
int run_test(
|
||||
int filefd,
|
||||
const char error_msg[],
|
||||
off_t offset,
|
||||
off_t length,
|
||||
int dirfd,
|
||||
const char file_name[],
|
||||
off_t expected_size
|
||||
) {
|
||||
// posix_fallocate does not set errno.
|
||||
int result = posix_fallocate(filefd, offset, length);
|
||||
if (result != 0) {
|
||||
const char* error = strerror(result);
|
||||
fprintf(stderr, "%s: %s\n", error_msg, error);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct stat stat = {0};
|
||||
if (fstatat(dirfd, file_name, &stat, 0) == -1) {
|
||||
perror("fstatat");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (stat.st_size != expected_size) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"Expected: %zd\nActual: %zd\n",
|
||||
expected_size,
|
||||
stat.st_size
|
||||
);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
int result = EXIT_FAILURE;
|
||||
|
||||
char dir_template[] = "/tmp/pfalloctest.XXXXXX";
|
||||
size_t dlen = sizeof(dir_template) - 1;
|
||||
if (!mkdtemp(dir_template)) {
|
||||
perror("mkdtemp");
|
||||
goto bye;
|
||||
}
|
||||
int dirfd = open(dir_template, O_DIRECTORY);
|
||||
if (dirfd == -1) {
|
||||
perror("open");
|
||||
goto rmtemp;
|
||||
}
|
||||
|
||||
// Failure conditions.
|
||||
// These are mostly a gut check for whatever backing syscall is used
|
||||
// in relibc. If it ever changes, we have to make sure the correct
|
||||
// errors are returned. If they're not, we have to add checks to our
|
||||
// relibc implementation. Linux's backing syscall (SYS_fallocate)
|
||||
// already does these checks.
|
||||
//
|
||||
// Directory:
|
||||
if (posix_fallocate(dirfd, 0, 1000) == 0) {
|
||||
fputs("posix_fallocate succeeded on a directory\n", stderr);
|
||||
goto rmtemp;
|
||||
}
|
||||
|
||||
// Socket:
|
||||
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (sock == -1) {
|
||||
perror("socket");
|
||||
goto rmtemp;
|
||||
}
|
||||
if (posix_fallocate(sock, 0, 1000) == 0) {
|
||||
fputs("posix_fallocate succeeded on a socket\n", stderr);
|
||||
close(sock);
|
||||
goto rmtemp;
|
||||
}
|
||||
close(sock);
|
||||
|
||||
// Pipe:
|
||||
int pipefd[2] = {0};
|
||||
if (pipe(pipefd) == -1) {
|
||||
perror("pipe");
|
||||
goto rmtemp;
|
||||
}
|
||||
if (posix_fallocate(pipefd[0], 0, 1000) == 0) {
|
||||
fputs("posix_fallocate succeeded on a pipe\n", stderr);
|
||||
close(pipefd[0]);
|
||||
close(pipefd[1]);
|
||||
goto rmtemp;
|
||||
}
|
||||
close(pipefd[0]);
|
||||
close(pipefd[1]);
|
||||
|
||||
// Success conditions.
|
||||
const char name[] = "commander_keen";
|
||||
char path[PATH_MAX] = {0};
|
||||
memcpy(path, dir_template, dlen);
|
||||
path[dlen] = '/';
|
||||
memcpy(&path[dlen + 1], name, sizeof(name));
|
||||
|
||||
int file = open(path, O_CREAT | O_RDWR);
|
||||
if (file == -1) {
|
||||
perror("open");
|
||||
goto rmtemp;
|
||||
}
|
||||
|
||||
// Expand an empty file.
|
||||
if (
|
||||
run_test(
|
||||
file,
|
||||
"posix_fallocate failed expanding an empty file",
|
||||
0,
|
||||
1000,
|
||||
dirfd,
|
||||
name,
|
||||
1000
|
||||
) == -1
|
||||
) {
|
||||
fputs("FAILED: Expanding empty file test\n", stderr);
|
||||
goto rmtempfile;
|
||||
}
|
||||
|
||||
// Don't shrink a file.
|
||||
if (
|
||||
run_test(
|
||||
file,
|
||||
"posix_fallocate failed on already allocated file",
|
||||
0,
|
||||
1,
|
||||
dirfd,
|
||||
name,
|
||||
1000
|
||||
) == -1
|
||||
) {
|
||||
fputs("FAILED: posix_fallocate should not shrink files\n", stderr);
|
||||
goto rmtempfile;
|
||||
}
|
||||
|
||||
// Don't overwrite allocated byte ranges.
|
||||
if (ftruncate(file, 0) == -1) {
|
||||
perror("ftruncate");
|
||||
goto rmtempfile;
|
||||
}
|
||||
const char msg[] = "If you're reading this you must play Commander Keen";
|
||||
if (write(file, msg, sizeof(msg)) != sizeof(msg)) {
|
||||
perror("write");
|
||||
goto rmtempfile;
|
||||
}
|
||||
|
||||
if (
|
||||
run_test(
|
||||
file,
|
||||
"posix_fallocate failed on an allocated range",
|
||||
0,
|
||||
sizeof(msg),
|
||||
dirfd,
|
||||
name,
|
||||
sizeof(msg)
|
||||
) == -1
|
||||
) {
|
||||
fputs(
|
||||
"FAILED: posix_fallocate don't overwrite allocated ranges test\n",
|
||||
stderr
|
||||
);
|
||||
goto rmtempfile;
|
||||
}
|
||||
// And now check that the range wasn't overwritten.
|
||||
char buf[sizeof(msg)] = {0};
|
||||
if (lseek(file, 0, SEEK_SET) == -1) {
|
||||
perror("lseek");
|
||||
goto rmtempfile;
|
||||
}
|
||||
if (read(file, buf, sizeof(msg)) != sizeof(msg)) {
|
||||
perror("read");
|
||||
goto rmtempfile;
|
||||
}
|
||||
if (strncmp(msg, buf, sizeof(msg)) != 0) {
|
||||
fputs("FAILED: posix_fallocate overwrote/destroyed a file\n", stderr);
|
||||
goto rmtempfile;
|
||||
}
|
||||
|
||||
// Offset + length that goes beyond file end expands file.
|
||||
if (
|
||||
run_test(
|
||||
file,
|
||||
"posix_fallocate failed on file expansion test",
|
||||
sizeof(msg),
|
||||
1000,
|
||||
dirfd,
|
||||
name,
|
||||
sizeof(msg) + 1000
|
||||
) == -1
|
||||
) {
|
||||
fputs("FAILED: posix_fallocate file expansion test\n", stderr);
|
||||
goto rmtempfile;
|
||||
}
|
||||
|
||||
result = EXIT_SUCCESS;
|
||||
rmtempfile:
|
||||
close(file);
|
||||
unlink(path);
|
||||
rmtemp:
|
||||
rmdir(dir_template);
|
||||
bye:
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user