From: Ahmad Fatoum <a.fatoum@pengutronix.de>
To: Sascha Hauer <s.hauer@pengutronix.de>,
BAREBOX <barebox@lists.infradead.org>
Subject: Re: [PATCH 1/2] fs: track streaming and unknown-size files in dedicated inode fields
Date: Tue, 8 Sep 2026 10:50:56 +0200 [thread overview]
Message-ID: <a501d3a8-583a-4c36-acd0-839225a46672@pengutronix.de> (raw)
In-Reply-To: <20260908-fs-filesize-fixes-v1-1-8cf3e781a4d0@pengutronix.de>
On 9/8/26 10:32 AM, Sascha Hauer wrote:
> Streaming files and files of not-yet-known size carried that fact as
> i_size == FILE_SIZE_STREAM, i.e. (loff_t)-1. Overloading i_size this way
> is fragile: a value read from media is indistinguishable from the
> sentinel, so nothing can tell a genuine special file from a corrupted
> inode that happens to hold -1.
>
> Two unrelated situations were hidden behind that one value:
>
> - a character device is a genuine sizeless stream, and
> - a tftp transfer without a negotiated size has a real, definite size
> that is simply not known until the file has been read to its end.
>
> The latter is not a stream: it has an end of file and it is seekable --
> tftp_lseek() moves forward by reading and discarding and backward by
> reopening the transfer. Conflating the two would invite wrong
> conclusions such as forbidding seeks on a tftp file.
>
> Give the inode an i_stream flag for the former and an i_size_unknown flag
> for the latter, and leave i_size at a real value in both cases. The two
> agree on the only thing that matters to the I/O layer: i_size is not a
> valid bound, so reads, writes and seeks must not be clamped to it and the
> driver reports the end of the file itself. That shared question is folded
> into the i_size_is_bound() helper. FILE_SIZE_STREAM stays only as the
> stat sentinel that stat_inode() synthesises from these flags for its
> callers.
>
> No functional change intended; this only moves the "no i_size bound" bit
> out of i_size so it can be trusted independently of the on-media value.
>
> Assisted-by: Claude:claude-fable-5
With S-o-b added:
Reviewed-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
> ---
> fs/devfs.c | 7 ++++++-
> fs/fs.c | 22 ++++++++++++++++++----
> fs/tftp.c | 2 +-
> include/linux/fs.h | 2 ++
> 4 files changed, 27 insertions(+), 6 deletions(-)
>
> diff --git a/fs/devfs.c b/fs/devfs.c
> index 75df330bcb..cb2980b114 100644
> --- a/fs/devfs.c
> +++ b/fs/devfs.c
> @@ -110,7 +110,12 @@ static int devfs_open(struct inode *inode, struct file *f)
> return -ENOENT;
> }
>
> - f->f_size = cdev_size(cdev);
> + if (cdev_size(cdev) == FILE_SIZE_STREAM) {
> + inode->i_stream = true;
> + f->f_size = 0;
> + } else {
> + f->f_size = cdev->size;
> + }
> f->private_data = cdev;
>
> return cdev_open(cdev, f->f_flags);
> diff --git a/fs/fs.c b/fs/fs.c
> index ce41f23f88..85881f6cf1 100644
> --- a/fs/fs.c
> +++ b/fs/fs.c
> @@ -373,6 +373,18 @@ static int fsdev_truncate(struct file *f, loff_t length)
> f->f_inode->i_fop->truncate(f, length) : -EROFS;
> }
>
> +/*
> + * Reads, writes and seeks are clamped to i_size, but only when i_size is a
> + * meaningful bound. A character device is a sizeless stream, and a file whose
> + * size is only discovered while reading (e.g. a tftp transfer without a
> + * negotiated size) starts out with i_size zero; for both, i_size must not clamp
> + * I/O and the driver reports the end of the file itself.
> + */
> +static bool i_size_is_bound(const struct inode *inode)
> +{
> + return !inode->i_stream && !inode->i_size_unknown;
> +}
> +
> int ftruncate(int fd, loff_t length)
> {
> struct file *f = fd_to_file(fd, false);
> @@ -381,7 +393,7 @@ int ftruncate(int fd, loff_t length)
> if (IS_ERR(f))
> return -errno;
>
> - if (f->f_size == FILE_SIZE_STREAM)
> + if (!i_size_is_bound(f->f_inode))
> return 0;
>
> ret = fsdev_truncate(f, length);
> @@ -427,7 +439,7 @@ static ssize_t __read(struct file *f, void *buf, size_t count)
> if (fsdrv != ramfs_driver)
> assert_command_context();
>
> - if (f->f_size != FILE_SIZE_STREAM && f->f_pos + count > f->f_size)
> + if (i_size_is_bound(f->f_inode) && f->f_pos + count > f->f_size)
> count = f->f_size - f->f_pos;
>
> if (!count)
> @@ -487,7 +499,7 @@ static ssize_t __write(struct file *f, const void *buf, size_t count)
> if (fsdrv != ramfs_driver)
> assert_command_context();
>
> - if (f->f_size != FILE_SIZE_STREAM && f->f_pos + count > f->f_size) {
> + if (i_size_is_bound(f->f_inode) && f->f_pos + count > f->f_size) {
> ret = fsdev_truncate(f, f->f_pos + count);
> if (ret) {
> if (ret == -EPERM)
> @@ -592,7 +604,7 @@ loff_t lseek(int fd, loff_t offset, int whence)
>
> pos += offset;
>
> - if (f->f_size != FILE_SIZE_STREAM && (pos < 0 || pos > f->f_size))
> + if (i_size_is_bound(f->f_inode) && (pos < 0 || pos > f->f_size))
> goto out;
>
> if (f->f_inode->i_fop->lseek) {
> @@ -1105,6 +1117,8 @@ static void stat_inode(struct inode *inode, struct stat *s)
> cdev = cdev_by_name(inode->cdevname);
>
> s->st_size = cdev ? cdev_size(cdev) : 0;
> + } else if (!i_size_is_bound(inode)) {
> + s->st_size = FILE_SIZE_STREAM;
> } else {
> s->st_size = inode->i_size;
> }
> diff --git a/fs/tftp.c b/fs/tftp.c
> index e8a6b62870..dc68f2c77c 100644
> --- a/fs/tftp.c
> +++ b/fs/tftp.c
> @@ -1088,7 +1088,7 @@ static struct dentry *tftp_lookup(struct inode *dir, struct dentry *dentry,
> if (filesize)
> inode->i_size = filesize;
> else
> - inode->i_size = FILE_SIZE_STREAM;
> + inode->i_size_unknown = true;
>
> d_add(dentry, inode);
>
> diff --git a/include/linux/fs.h b/include/linux/fs.h
> index d3813ad9c5..fc50d207a6 100644
> --- a/include/linux/fs.h
> +++ b/include/linux/fs.h
> @@ -129,6 +129,8 @@ struct inode {
> gid_t i_gid;
> u64 i_version;
> loff_t i_size;
> + bool i_stream; /* sizeless stream, e.g. a character device */
> + bool i_size_unknown; /* real size, not known until read, e.g. tftp */
> struct timespec i_atime;
> struct timespec i_mtime;
> struct timespec i_ctime;
>
--
Pengutronix e.K. | |
Steuerwalder Str. 21 | http://www.pengutronix.de/ |
31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
next prev parent reply other threads:[~2026-09-08 8:52 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-08 8:32 [PATCH 0/2] fs: reject inodes whose size the superblock cannot address Sascha Hauer
2026-09-08 8:32 ` [PATCH 1/2] fs: track streaming and unknown-size files in dedicated inode fields Sascha Hauer
2026-09-08 8:50 ` Ahmad Fatoum [this message]
2026-09-08 8:32 ` [PATCH 2/2] fs: reject inodes whose size the superblock cannot address Sascha Hauer
2026-09-08 8:55 ` Ahmad Fatoum
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=a501d3a8-583a-4c36-acd0-839225a46672@pengutronix.de \
--to=a.fatoum@pengutronix.de \
--cc=barebox@lists.infradead.org \
--cc=s.hauer@pengutronix.de \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox