- diff --git a/fs/fcntl.c b/fs/fcntl.c
- index 5598e4d57422..43150c2a293e 100644
- --- a/fs/fcntl.c
- +++ b/fs/fcntl.c
- @@ -14,6 +14,7 @@
- #include <linux/file.h>
- #include <linux/capability.h>
- #include <linux/dnotify.h>
- +#include <linux/falloc.h>
- #include <linux/slab.h>
- #include <linux/module.h>
- #include <linux/pipe_fs_i.h>
- @@ -36,6 +37,217 @@
- #define SETFL_MASK (O_APPEND | O_NONBLOCK | O_NDELAY | O_DIRECT | O_NOATIME)
- +struct fcntl_space_range {
- + loff_t start;
- + loff_t len;
- + bool to_eof;
- +};
- +
- +static int fcntl_space_resolve_range(struct file *file, int whence,
- + loff_t offset, loff_t flen,
- + struct fcntl_space_range *range)
- +{
- + struct inode *inode = file_inode(file);
- + loff_t base, start, len;
- +
- + switch (whence) {
- + case SEEK_SET:
- + base = 0;
- + break;
- + case SEEK_CUR:
- + base = file->f_pos;
- + break;
- + case SEEK_END:
- + base = i_size_read(inode);
- + break;
- + default:
- + return -EINVAL;
- + }
- +
- + if (check_add_overflow(base, offset, &start))
- + return -EOVERFLOW;
- +
- + /*
- + * Match POSIX byte-range convention:
- + *
- + * flen > 0: [start, start + flen)
- + * flen == 0: [start, EOF)
- + * flen < 0: [start + flen, start)
- + */
- + if (flen < 0) {
- + if (check_add_overflow(start, flen, &start))
- + return -EOVERFLOW;
- + if (flen == LLONG_MIN)
- + return -EOVERFLOW;
- + len = -flen;
- + range->to_eof = false;
- + } else if (flen == 0) {
- + len = 0;
- + range->to_eof = true;
- + } else {
- + len = flen;
- + range->to_eof = false;
- + }
- +
- + if (start < 0)
- + return -EINVAL;
- +
- + range->start = start;
- + range->len = len;
- + return 0;
- +}
- +
- +static int fcntl_space_alloc(struct file *file,
- + const struct fcntl_space_range *range)
- +{
- + loff_t len = range->len;
- + loff_t isize;
- +
- + /*
- + * For |len == 0|, allocate from start to current EOF.
- + * If start is beyond EOF, this is an empty range and therefore a
- + * no-op.
- + */
- + if (range->to_eof) {
- + isize = i_size_read(file_inode(file));
- + if (range->start >= isize)
- + return 0;
- + len = isize - range->start;
- + }
- +
- + if (len == 0)
- + return 0;
- +
- + return vfs_fallocate(file, 0, range->start, len);
- +}
- +
- +static int fcntl_space_free(struct file *file,
- + const struct fcntl_space_range *range)
- +{
- + struct inode *inode = file_inode(file);
- + loff_t isize = i_size_read(inode);
- + loff_t end;
- +
- + if (range->start >= isize)
- + return 0;
- +
- + if (range->to_eof)
- + return vfs_truncate(&file->f_path, range->start);
- +
- + if (check_add_overflow(range->start, range->len, &end))
- + return -EOVERFLOW;
- +
- + /*
- + * Freeing through EOF must shrink the file. Hole punching keeps
- + * |i_size| unchanged, which is not the desired |F_FREESP| tail
- + * behavior.
- + */
- + if (end >= isize)
- + return vfs_truncate(&file->f_path, range->start);
- +
- + if (range->len == 0)
- + return 0;
- +
- + return vfs_fallocate(file,
- + FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
- + range->start, range->len);
- +}
- +
- +static int fcntl_space_common(struct file *file, unsigned int cmd,
- + int whence, loff_t start, loff_t len)
- +{
- + struct fcntl_space_range range;
- + int error;
- +
- + error = fcntl_space_resolve_range(file, whence, start, len, &range);
- + if (error)
- + return error;
- +
- + switch (cmd) {
- + case F_ALLOCSP:
- + case F_ALLOCSP64:
- + return fcntl_space_alloc(file, &range);
- + case F_FREESP:
- + case F_FREESP64:
- + return fcntl_space_free(file, &range);
- + default:
- + return -EINVAL;
- + }
- +}
- +
- +static int fcntl_space_native(struct file *file, unsigned int cmd,
- + unsigned long arg)
- +{
- + struct flock fl;
- +
- + if (copy_from_user(&fl, (void __user *)arg, sizeof(fl)))
- + return -EFAULT;
- +
- + return fcntl_space_common(file, cmd, fl.l_whence,
- + fl.l_start, fl.l_len);
- +}
- +
- +static int fcntl_space_native64(struct file *file, unsigned int cmd,
- + unsigned long arg)
- +{
- + struct flock64 fl;
- +
- + if (copy_from_user(&fl, (void __user *)arg, sizeof(fl)))
- + return -EFAULT;
- +
- + return fcntl_space_common(file, cmd, fl.l_whence,
- + fl.l_start, fl.l_len);
- +}
- +
- +static int fcntl_space(struct file *file, unsigned int cmd, unsigned long arg)
- +{
- + if (!(file->f_mode & FMODE_WRITE))
- + return -EBADF;
- +
- + switch (cmd) {
- + case F_ALLOCSP:
- + case F_FREESP:
- + return fcntl_space_native(file, cmd, arg);
- + case F_ALLOCSP64:
- + case F_FREESP64:
- + return fcntl_space_native64(file, cmd, arg);
- + default:
- + return -EINVAL;
- + }
- +}
- +
- +#ifdef CONFIG_COMPAT
- +static int fcntl_space_compat(struct file *file, unsigned int cmd,
- + unsigned long arg)
- +{
- + struct compat_flock fl;
- +
- + if (!(file->f_mode & FMODE_WRITE))
- + return -EBADF;
- +
- + if (copy_from_user(&fl, compat_ptr(arg), sizeof(fl)))
- + return -EFAULT;
- +
- + return fcntl_space_common(file, cmd, fl.l_whence,
- + fl.l_start, fl.l_len);
- +}
- +
- +static int fcntl_space_compat64(struct file *file, unsigned int cmd,
- + unsigned long arg)
- +{
- + struct compat_flock64 fl;
- +
- + if (!(file->f_mode & FMODE_WRITE))
- + return -EBADF;
- +
- + if (copy_from_user(&fl, compat_ptr(arg), sizeof(fl)))
- + return -EFAULT;
- +
- + return fcntl_space_common(file, cmd, fl.l_whence,
- + fl.l_start, fl.l_len);
- +}
- +#endif /* CONFIG_COMPAT */
- +
- static int setfl(int fd, struct file * filp, unsigned int arg)
- {
- struct inode * inode = file_inode(filp);
- @@ -552,6 +764,12 @@ static long do_fcntl(int fd, unsigned int cmd, unsigned long arg,
- case F_SET_RW_HINT:
- err = fcntl_set_rw_hint(filp, cmd, arg);
- break;
- + case F_ALLOCSP:
- + case F_FREESP:
- + case F_ALLOCSP64:
- + case F_FREESP64:
- + err = fcntl_space(filp, cmd, arg);
- + break;
- default:
- break;
- }
- @@ -785,6 +1003,14 @@ static long do_compat_fcntl64(unsigned int fd, unsigned int cmd,
- break;
- err = fcntl_setlk(fd, fd_file(f), convert_fcntl_cmd(cmd), &flock);
- break;
- + case F_ALLOCSP:
- + case F_FREESP:
- + err = fcntl_space_compat(f.file, cmd, arg);
- + break;
- + case F_ALLOCSP64:
- + case F_FREESP64:
- + err = fcntl_space_compat64(f.file, cmd, arg);
- + break;
- default:
- err = do_fcntl(fd, cmd, arg, fd_file(f));
- break;
- diff --git a/include/uapi/linux/fcntl.h b/include/uapi/linux/fcntl.h
- index f291ab4f94eb..ece9cf1e4594 100644
- --- a/include/uapi/linux/fcntl.h
- +++ b/include/uapi/linux/fcntl.h
- @@ -79,6 +79,15 @@
- */
- #define RWF_WRITE_LIFE_NOT_SET RWH_WRITE_LIFE_NOT_SET
- +/*
- + * { Solaris, FreeBSD, SUPER/UX, ... }-style file space allocation/freeing.
- + * These commands take |struct flock| or |struct flock64|.
- + */
- +#define F_ALLOCSP (F_LINUX_SPECIFIC_BASE + 15)
- +#define F_FREESP (F_LINUX_SPECIFIC_BASE + 16)
- +#define F_ALLOCSP64 (F_LINUX_SPECIFIC_BASE + 17)
- +#define F_FREESP64 (F_LINUX_SPECIFIC_BASE + 18)
- +
- /*
- * Types of directory notifications that may be requested.
- */
- diff --git a/tools/testing/selftests/fcntl/Makefile b/tools/testing/selftests/fcntl/Makefile
- new file mode 100644
- index 000000000000..1bbaff237104
- --- /dev/null
- +++ b/tools/testing/selftests/fcntl/Makefile
- @@ -0,0 +1,7 @@
- +# SPDX-License-Identifier: GPL-2.0
- +
- +TEST_GEN_PROGS := fallocsp
- +
- +include ../lib.mk
- +
- +CFLAGS += -Wall -O2
- diff --git a/tools/testing/selftests/fcntl/fallocsp b/tools/testing/selftests/fcntl/fallocsp
- new file mode 100755
- index 000000000000..922c3d8f227a
- Binary files /dev/null and b/tools/testing/selftests/fcntl/fallocsp differ
- diff --git a/tools/testing/selftests/fcntl/fallocsp.c b/tools/testing/selftests/fcntl/fallocsp.c
- new file mode 100644
- index 000000000000..9e741955f239
- --- /dev/null
- +++ b/tools/testing/selftests/fcntl/fallocsp.c
- @@ -0,0 +1,239 @@
- +// SPDX-License-Identifier: GPL-2.0
- +
- +/*
- + * Test for { Solaris, FreeBSD, SUPER/UX, ... }-style |F_ALLOCSP|/|F_FREESP|
- + * Written by Roland Mainz <roland.mainz@nrubsig.org>
- + */
- +
- +#define _GNU_SOURCE
- +#define _FILE_OFFSET_BITS 64
- +
- +#include <errno.h>
- +#include <fcntl.h>
- +#include <stdio.h>
- +#include <stdlib.h>
- +#include <string.h>
- +#include <sys/stat.h>
- +#include <sys/types.h>
- +#include <unistd.h>
- +
- +#include "../kselftest.h"
- +
- +#ifndef F_LINUX_SPECIFIC_BASE
- +#define F_LINUX_SPECIFIC_BASE 1024
- +#endif
- +#ifndef F_ALLOCSP
- +#define F_ALLOCSP (F_LINUX_SPECIFIC_BASE + 15)
- +#endif
- +#ifndef F_FREESP
- +#define F_FREESP (F_LINUX_SPECIFIC_BASE + 16)
- +#endif
- +#ifndef F_ALLOCSP64
- +#define F_ALLOCSP64 (F_LINUX_SPECIFIC_BASE + 17)
- +#endif
- +#ifndef F_FREESP64
- +#define F_FREESP64 (F_LINUX_SPECIFIC_BASE + 18)
- +#endif
- +
- +static int write_pattern(int fd, off_t len)
- +{
- + char buf[4096];
- + off_t done = 0;
- +
- + (void)memset(buf, 0x5a, sizeof(buf));
- +
- + while (done < len) {
- + size_t todo = sizeof(buf);
- + ssize_t ret;
- +
- + if (len - done < (off_t)todo)
- + todo = len - done;
- +
- + ret = write(fd, buf, todo);
- + if (ret < 0)
- + return -1;
- + if (ret == 0) {
- + errno = EIO;
- + return -1;
- + }
- +
- + done += ret;
- + }
- +
- + return 0;
- +}
- +
- +static int is_zero_range(int fd, off_t off, size_t len)
- +{
- + unsigned char buf[4096];
- + size_t done = 0;
- +
- + if (lseek(fd, off, SEEK_SET) < 0)
- + return -1;
- +
- + while (done < len) {
- + size_t todo = sizeof(buf);
- + ssize_t ret;
- + size_t i;
- +
- + if (len - done < todo)
- + todo = len - done;
- +
- + ret = read(fd, buf, todo);
- + if (ret < 0)
- + return -1;
- + if (ret == 0)
- + return 0;
- +
- + for (i = 0; i < (size_t)ret; i++) {
- + if (buf[i] != 0)
- + return 0;
- + }
- +
- + done += ret;
- + }
- +
- + return 1;
- +}
- +
- +static int test_allocsp_extends(int fd)
- +{
- + struct flock fl = {
- + .l_whence = SEEK_SET,
- + .l_start = 0,
- + .l_len = 1024 * 1024,
- + };
- + struct stat st;
- +
- + if (fcntl(fd, F_ALLOCSP, &fl) < 0) {
- + if (errno == EINVAL) {
- + ksft_test_result_skip("F_ALLOCSP unsupported\n");
- + return 0;
- + }
- + perror("F_ALLOCSP");
- + return -1;
- + }
- +
- + if (fstat(fd, &st) < 0) {
- + perror("fstat");
- + return -1;
- + }
- +
- + if (st.st_size != fl.l_len) {
- + (void)fprintf(stderr, "F_ALLOCSP size %lld != %lld\n",
- + (long long)st.st_size, (long long)fl.l_len);
- + return -1;
- + }
- +
- + ksft_test_result_pass("F_ALLOCSP extends file\n");
- + return 0;
- +}
- +
- +static int test_freesp_truncates(int fd)
- +{
- + struct flock fl = {
- + .l_whence = SEEK_SET,
- + .l_start = 4096,
- + .l_len = 0,
- + };
- + struct stat st;
- +
- + if (ftruncate(fd, 0) < 0)
- + return -1;
- + if (write_pattern(fd, 16384) < 0)
- + return -1;
- +
- + if (fcntl(fd, F_FREESP, &fl) < 0) {
- + if (errno == EINVAL) {
- + ksft_test_result_skip("F_FREESP unsupported\n");
- + return 0;
- + }
- + perror("F_FREESP truncate");
- + return -1;
- + }
- +
- + if (fstat(fd, &st) < 0)
- + return -1;
- +
- + if (st.st_size != 4096) {
- + (void)fprintf(stderr, "F_FREESP size %lld != 4096\n",
- + (long long)st.st_size);
- + return -1;
- + }
- +
- + ksft_test_result_pass("F_FREESP truncates tail\n");
- + return 0;
- +}
- +
- +static int test_freesp_punches_hole(int fd)
- +{
- + struct flock fl = {
- + .l_whence = SEEK_SET,
- + .l_start = 4096,
- + .l_len = 4096,
- + };
- + struct stat st;
- + int zero;
- +
- + if (ftruncate(fd, 0) < 0)
- + return -1;
- + if (write_pattern(fd, 16384) < 0)
- + return -1;
- +
- + if (fcntl(fd, F_FREESP, &fl) < 0) {
- + if (errno == EINVAL || errno == EOPNOTSUPP) {
- + ksft_test_result_skip("F_FREESP hole punch unsupported\n");
- + return 0;
- + }
- + perror("F_FREESP punch");
- + return -1;
- + }
- +
- + if (fstat(fd, &st) < 0)
- + return -1;
- +
- + if (st.st_size != 16384) {
- + (void)fprintf(stderr, "hole punch changed size to %lld\n",
- + (long long)st.st_size);
- + return -1;
- + }
- +
- + zero = is_zero_range(fd, 4096, 4096);
- + if (zero < 0)
- + return -1;
- + if (!zero) {
- + (void)fprintf(stderr, "punched range did not read as zeroes\n");
- + return -1;
- + }
- +
- + ksft_test_result_pass("F_FREESP punches hole\n");
- + return 0;
- +}
- +
- +int main(void)
- +{
- + char tmpl[] = "/tmp/fallocsp-selftest-XXXXXX";
- + int fd;
- + int ret = 0;
- +
- + ksft_print_header();
- + ksft_set_plan(3);
- +
- + fd = mkstemp(tmpl);
- + if (fd < 0) {
- + perror("mkstemp");
- + return KSFT_FAIL;
- + }
- +
- + (void)unlink(tmpl);
- +
- + if (test_allocsp_extends(fd) < 0)
- + ret = KSFT_FAIL;
- + if (test_freesp_truncates(fd) < 0)
- + ret = KSFT_FAIL;
- + if (test_freesp_punches_hole(fd) < 0)
- + ret = KSFT_FAIL;
- +
- + (void)close(fd);
- + return ret;
- +}
Linux kernel linux6_17_rc4_rt3_fnctl_allocfree20260630_003 fnctl() F_ALLOCSP/F_FREESP patch
Posted by Anonymous on Tue 30th Jun 2026 12:23
raw | new post
modification of post by Anonymous (view diff)
Submit a correction or amendment below (click here to make a fresh posting)
After submitting an amendment, you'll be able to view the differences between the old and new posts easily.
nrubsig.kpaste.net RSS