2005-04-16 22:20:36 +00:00
|
|
|
/*
|
|
|
|
* JFFS2 -- Journalling Flash File System, Version 2.
|
|
|
|
*
|
2007-04-25 13:16:47 +00:00
|
|
|
* Copyright © 2001-2007 Red Hat, Inc.
|
2010-08-08 13:15:22 +00:00
|
|
|
* Copyright © 2004-2010 David Woodhouse <dwmw2@infradead.org>
|
2005-04-16 22:20:36 +00:00
|
|
|
*
|
|
|
|
* Created by David Woodhouse <dwmw2@infradead.org>
|
|
|
|
*
|
|
|
|
* For licensing information, see the file 'LICENCE' in this directory.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2012-02-15 23:56:45 +00:00
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/fs.h>
|
|
|
|
#include <linux/time.h>
|
|
|
|
#include <linux/pagemap.h>
|
|
|
|
#include <linux/highmem.h>
|
|
|
|
#include <linux/crc32.h>
|
|
|
|
#include <linux/jffs2.h>
|
|
|
|
#include "nodelist.h"
|
|
|
|
|
2007-10-16 08:25:18 +00:00
|
|
|
static int jffs2_write_end(struct file *filp, struct address_space *mapping,
|
|
|
|
loff_t pos, unsigned len, unsigned copied,
|
2024-07-10 19:45:32 +00:00
|
|
|
struct folio *folio, void *fsdata);
|
2007-10-16 08:25:18 +00:00
|
|
|
static int jffs2_write_begin(struct file *filp, struct address_space *mapping,
|
2022-02-22 19:31:43 +00:00
|
|
|
loff_t pos, unsigned len,
|
2007-10-16 08:25:18 +00:00
|
|
|
struct page **pagep, void **fsdata);
|
2022-04-29 15:12:16 +00:00
|
|
|
static int jffs2_read_folio(struct file *filp, struct folio *folio);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2011-07-17 00:44:56 +00:00
|
|
|
int jffs2_fsync(struct file *filp, loff_t start, loff_t end, int datasync)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2010-05-26 15:53:25 +00:00
|
|
|
struct inode *inode = filp->f_mapping->host;
|
2005-04-16 22:20:36 +00:00
|
|
|
struct jffs2_sb_info *c = JFFS2_SB_INFO(inode->i_sb);
|
2011-07-17 00:44:56 +00:00
|
|
|
int ret;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2017-07-07 19:20:52 +00:00
|
|
|
ret = file_write_and_wait_range(filp, start, end);
|
2011-07-17 00:44:56 +00:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2016-01-22 20:40:57 +00:00
|
|
|
inode_lock(inode);
|
2005-04-16 22:20:36 +00:00
|
|
|
/* Trigger GC to flush any pending writes for this inode */
|
|
|
|
jffs2_flush_wbuf_gc(c, inode->i_ino);
|
2016-01-22 20:40:57 +00:00
|
|
|
inode_unlock(inode);
|
2005-11-07 11:16:07 +00:00
|
|
|
|
|
|
|
return 0;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2006-03-28 09:56:42 +00:00
|
|
|
const struct file_operations jffs2_file_operations =
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
|
|
|
.llseek = generic_file_llseek,
|
|
|
|
.open = generic_file_open,
|
2014-04-02 18:33:16 +00:00
|
|
|
.read_iter = generic_file_read_iter,
|
2014-04-03 07:17:43 +00:00
|
|
|
.write_iter = generic_file_write_iter,
|
2008-07-07 12:45:59 +00:00
|
|
|
.unlocked_ioctl=jffs2_ioctl,
|
2005-04-16 22:20:36 +00:00
|
|
|
.mmap = generic_file_readonly_mmap,
|
|
|
|
.fsync = jffs2_fsync,
|
2023-05-22 13:50:15 +00:00
|
|
|
.splice_read = filemap_splice_read,
|
2021-03-30 13:45:37 +00:00
|
|
|
.splice_write = iter_file_splice_write,
|
2005-04-16 22:20:36 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/* jffs2_file_inode_operations */
|
|
|
|
|
2007-02-12 08:55:39 +00:00
|
|
|
const struct inode_operations jffs2_file_inode_operations =
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2022-09-22 15:17:00 +00:00
|
|
|
.get_inode_acl = jffs2_get_acl,
|
2013-12-20 13:16:47 +00:00
|
|
|
.set_acl = jffs2_set_acl,
|
2006-05-13 06:09:47 +00:00
|
|
|
.setattr = jffs2_setattr,
|
|
|
|
.listxattr = jffs2_listxattr,
|
2005-04-16 22:20:36 +00:00
|
|
|
};
|
|
|
|
|
2006-06-28 11:26:44 +00:00
|
|
|
const struct address_space_operations jffs2_file_address_operations =
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2022-04-29 15:12:16 +00:00
|
|
|
.read_folio = jffs2_read_folio,
|
2007-10-16 08:25:18 +00:00
|
|
|
.write_begin = jffs2_write_begin,
|
|
|
|
.write_end = jffs2_write_end,
|
2005-04-16 22:20:36 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static int jffs2_do_readpage_nolock (struct inode *inode, struct page *pg)
|
|
|
|
{
|
|
|
|
struct jffs2_inode_info *f = JFFS2_INODE_INFO(inode);
|
|
|
|
struct jffs2_sb_info *c = JFFS2_SB_INFO(inode->i_sb);
|
|
|
|
unsigned char *pg_buf;
|
|
|
|
int ret;
|
|
|
|
|
2012-02-15 23:56:43 +00:00
|
|
|
jffs2_dbg(2, "%s(): ino #%lu, page at offset 0x%lx\n",
|
mm, fs: get rid of PAGE_CACHE_* and page_cache_{get,release} macros
PAGE_CACHE_{SIZE,SHIFT,MASK,ALIGN} macros were introduced *long* time
ago with promise that one day it will be possible to implement page
cache with bigger chunks than PAGE_SIZE.
This promise never materialized. And unlikely will.
We have many places where PAGE_CACHE_SIZE assumed to be equal to
PAGE_SIZE. And it's constant source of confusion on whether
PAGE_CACHE_* or PAGE_* constant should be used in a particular case,
especially on the border between fs and mm.
Global switching to PAGE_CACHE_SIZE != PAGE_SIZE would cause to much
breakage to be doable.
Let's stop pretending that pages in page cache are special. They are
not.
The changes are pretty straight-forward:
- <foo> << (PAGE_CACHE_SHIFT - PAGE_SHIFT) -> <foo>;
- <foo> >> (PAGE_CACHE_SHIFT - PAGE_SHIFT) -> <foo>;
- PAGE_CACHE_{SIZE,SHIFT,MASK,ALIGN} -> PAGE_{SIZE,SHIFT,MASK,ALIGN};
- page_cache_get() -> get_page();
- page_cache_release() -> put_page();
This patch contains automated changes generated with coccinelle using
script below. For some reason, coccinelle doesn't patch header files.
I've called spatch for them manually.
The only adjustment after coccinelle is revert of changes to
PAGE_CAHCE_ALIGN definition: we are going to drop it later.
There are few places in the code where coccinelle didn't reach. I'll
fix them manually in a separate patch. Comments and documentation also
will be addressed with the separate patch.
virtual patch
@@
expression E;
@@
- E << (PAGE_CACHE_SHIFT - PAGE_SHIFT)
+ E
@@
expression E;
@@
- E >> (PAGE_CACHE_SHIFT - PAGE_SHIFT)
+ E
@@
@@
- PAGE_CACHE_SHIFT
+ PAGE_SHIFT
@@
@@
- PAGE_CACHE_SIZE
+ PAGE_SIZE
@@
@@
- PAGE_CACHE_MASK
+ PAGE_MASK
@@
expression E;
@@
- PAGE_CACHE_ALIGN(E)
+ PAGE_ALIGN(E)
@@
expression E;
@@
- page_cache_get(E)
+ get_page(E)
@@
expression E;
@@
- page_cache_release(E)
+ put_page(E)
Signed-off-by: Kirill A. Shutemov <kirill.shutemov@linux.intel.com>
Acked-by: Michal Hocko <mhocko@suse.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2016-04-01 12:29:47 +00:00
|
|
|
__func__, inode->i_ino, pg->index << PAGE_SHIFT);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2005-05-01 15:59:01 +00:00
|
|
|
BUG_ON(!PageLocked(pg));
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
pg_buf = kmap(pg);
|
|
|
|
/* FIXME: Can kmap fail? */
|
|
|
|
|
mm, fs: get rid of PAGE_CACHE_* and page_cache_{get,release} macros
PAGE_CACHE_{SIZE,SHIFT,MASK,ALIGN} macros were introduced *long* time
ago with promise that one day it will be possible to implement page
cache with bigger chunks than PAGE_SIZE.
This promise never materialized. And unlikely will.
We have many places where PAGE_CACHE_SIZE assumed to be equal to
PAGE_SIZE. And it's constant source of confusion on whether
PAGE_CACHE_* or PAGE_* constant should be used in a particular case,
especially on the border between fs and mm.
Global switching to PAGE_CACHE_SIZE != PAGE_SIZE would cause to much
breakage to be doable.
Let's stop pretending that pages in page cache are special. They are
not.
The changes are pretty straight-forward:
- <foo> << (PAGE_CACHE_SHIFT - PAGE_SHIFT) -> <foo>;
- <foo> >> (PAGE_CACHE_SHIFT - PAGE_SHIFT) -> <foo>;
- PAGE_CACHE_{SIZE,SHIFT,MASK,ALIGN} -> PAGE_{SIZE,SHIFT,MASK,ALIGN};
- page_cache_get() -> get_page();
- page_cache_release() -> put_page();
This patch contains automated changes generated with coccinelle using
script below. For some reason, coccinelle doesn't patch header files.
I've called spatch for them manually.
The only adjustment after coccinelle is revert of changes to
PAGE_CAHCE_ALIGN definition: we are going to drop it later.
There are few places in the code where coccinelle didn't reach. I'll
fix them manually in a separate patch. Comments and documentation also
will be addressed with the separate patch.
virtual patch
@@
expression E;
@@
- E << (PAGE_CACHE_SHIFT - PAGE_SHIFT)
+ E
@@
expression E;
@@
- E >> (PAGE_CACHE_SHIFT - PAGE_SHIFT)
+ E
@@
@@
- PAGE_CACHE_SHIFT
+ PAGE_SHIFT
@@
@@
- PAGE_CACHE_SIZE
+ PAGE_SIZE
@@
@@
- PAGE_CACHE_MASK
+ PAGE_MASK
@@
expression E;
@@
- PAGE_CACHE_ALIGN(E)
+ PAGE_ALIGN(E)
@@
expression E;
@@
- page_cache_get(E)
+ get_page(E)
@@
expression E;
@@
- page_cache_release(E)
+ put_page(E)
Signed-off-by: Kirill A. Shutemov <kirill.shutemov@linux.intel.com>
Acked-by: Michal Hocko <mhocko@suse.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2016-04-01 12:29:47 +00:00
|
|
|
ret = jffs2_read_inode_range(c, f, pg_buf, pg->index << PAGE_SHIFT,
|
|
|
|
PAGE_SIZE);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2024-05-30 20:21:00 +00:00
|
|
|
if (!ret)
|
2005-04-16 22:20:36 +00:00
|
|
|
SetPageUptodate(pg);
|
|
|
|
|
|
|
|
flush_dcache_page(pg);
|
|
|
|
kunmap(pg);
|
|
|
|
|
2012-02-15 23:56:43 +00:00
|
|
|
jffs2_dbg(2, "readpage finished\n");
|
2009-08-04 11:11:47 +00:00
|
|
|
return ret;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2022-05-02 01:39:29 +00:00
|
|
|
int __jffs2_read_folio(struct file *file, struct folio *folio)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2022-05-02 01:39:29 +00:00
|
|
|
int ret = jffs2_do_readpage_nolock(folio->mapping->host, &folio->page);
|
|
|
|
folio_unlock(folio);
|
2005-04-16 22:20:36 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2022-04-29 15:12:16 +00:00
|
|
|
static int jffs2_read_folio(struct file *file, struct folio *folio)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2022-04-29 15:12:16 +00:00
|
|
|
struct jffs2_inode_info *f = JFFS2_INODE_INFO(folio->mapping->host);
|
2005-04-16 22:20:36 +00:00
|
|
|
int ret;
|
2005-11-07 11:16:07 +00:00
|
|
|
|
2008-04-22 14:13:40 +00:00
|
|
|
mutex_lock(&f->sem);
|
2022-05-02 01:39:29 +00:00
|
|
|
ret = __jffs2_read_folio(file, folio);
|
2008-04-22 14:13:40 +00:00
|
|
|
mutex_unlock(&f->sem);
|
2005-04-16 22:20:36 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2007-10-16 08:25:18 +00:00
|
|
|
static int jffs2_write_begin(struct file *filp, struct address_space *mapping,
|
2022-02-22 19:31:43 +00:00
|
|
|
loff_t pos, unsigned len,
|
2007-10-16 08:25:18 +00:00
|
|
|
struct page **pagep, void **fsdata)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2024-07-11 20:58:06 +00:00
|
|
|
struct folio *folio;
|
2007-10-16 08:25:18 +00:00
|
|
|
struct inode *inode = mapping->host;
|
2005-04-16 22:20:36 +00:00
|
|
|
struct jffs2_inode_info *f = JFFS2_INODE_INFO(inode);
|
2017-07-04 04:22:38 +00:00
|
|
|
struct jffs2_sb_info *c = JFFS2_SB_INFO(inode->i_sb);
|
mm, fs: get rid of PAGE_CACHE_* and page_cache_{get,release} macros
PAGE_CACHE_{SIZE,SHIFT,MASK,ALIGN} macros were introduced *long* time
ago with promise that one day it will be possible to implement page
cache with bigger chunks than PAGE_SIZE.
This promise never materialized. And unlikely will.
We have many places where PAGE_CACHE_SIZE assumed to be equal to
PAGE_SIZE. And it's constant source of confusion on whether
PAGE_CACHE_* or PAGE_* constant should be used in a particular case,
especially on the border between fs and mm.
Global switching to PAGE_CACHE_SIZE != PAGE_SIZE would cause to much
breakage to be doable.
Let's stop pretending that pages in page cache are special. They are
not.
The changes are pretty straight-forward:
- <foo> << (PAGE_CACHE_SHIFT - PAGE_SHIFT) -> <foo>;
- <foo> >> (PAGE_CACHE_SHIFT - PAGE_SHIFT) -> <foo>;
- PAGE_CACHE_{SIZE,SHIFT,MASK,ALIGN} -> PAGE_{SIZE,SHIFT,MASK,ALIGN};
- page_cache_get() -> get_page();
- page_cache_release() -> put_page();
This patch contains automated changes generated with coccinelle using
script below. For some reason, coccinelle doesn't patch header files.
I've called spatch for them manually.
The only adjustment after coccinelle is revert of changes to
PAGE_CAHCE_ALIGN definition: we are going to drop it later.
There are few places in the code where coccinelle didn't reach. I'll
fix them manually in a separate patch. Comments and documentation also
will be addressed with the separate patch.
virtual patch
@@
expression E;
@@
- E << (PAGE_CACHE_SHIFT - PAGE_SHIFT)
+ E
@@
expression E;
@@
- E >> (PAGE_CACHE_SHIFT - PAGE_SHIFT)
+ E
@@
@@
- PAGE_CACHE_SHIFT
+ PAGE_SHIFT
@@
@@
- PAGE_CACHE_SIZE
+ PAGE_SIZE
@@
@@
- PAGE_CACHE_MASK
+ PAGE_MASK
@@
expression E;
@@
- PAGE_CACHE_ALIGN(E)
+ PAGE_ALIGN(E)
@@
expression E;
@@
- page_cache_get(E)
+ get_page(E)
@@
expression E;
@@
- page_cache_release(E)
+ put_page(E)
Signed-off-by: Kirill A. Shutemov <kirill.shutemov@linux.intel.com>
Acked-by: Michal Hocko <mhocko@suse.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2016-04-01 12:29:47 +00:00
|
|
|
pgoff_t index = pos >> PAGE_SHIFT;
|
2005-04-16 22:20:36 +00:00
|
|
|
int ret = 0;
|
|
|
|
|
2015-11-10 21:18:15 +00:00
|
|
|
jffs2_dbg(1, "%s()\n", __func__);
|
|
|
|
|
jffs2: correct logic when creating a hole in jffs2_write_begin
Bug description and fix:
1. Write data to a file, say all 1s from offset 0 to 16.
2. Truncate the file to a smaller size, say 8 bytes.
3. Write new bytes (say 2s) from an offset past the original size of the
file, say at offset 20, for 4 bytes. This is supposed to create a "hole"
in the file, meaning that the bytes from offset 8 (where it was truncated
above) up to the new write at offset 20, should all be 0s (zeros).
4. Flush all caches using "echo 3 > /proc/sys/vm/drop_caches" (or unmount
and remount) the f/s.
5. Check the content of the file. It is wrong. The 1s that used to be
between bytes 9 and 16, before the truncation, have REAPPEARED (they should
be 0s).
We wrote a script and helper C program to reproduce the bug
(reproduce_jffs2_write_begin_issue.sh, write_file.c, and Makefile). We can
make them available to anyone.
The above example is shown when writing a small file within the same first
page. But the bug happens for larger files, as long as steps 1, 2, and 3
above all happen within the same page.
The problem was traced to the jffs2_write_begin code, where it goes into an
'if' statement intended to handle writes past the current EOF (i.e., writes
that may create a hole). The code computes a 'pageofs' that is the floor
of the write position (pos), aligned to the page size boundary. In other
words, 'pageofs' will never be larger than 'pos'. The code then sets the
internal jffs2_raw_inode->isize to the size of max(current inode size,
pageofs) but that is wrong: the new file size should be the 'pos', which is
larger than both the current inode size and pageofs.
Similarly, the code incorrectly sets the internal jffs2_raw_inode->dsize to
the difference between the pageofs minus current inode size; instead it
should be the current pos minus the current inode size. Finally,
inode->i_size was also set incorrectly.
The patch below fixes this bug. The bug was discovered using a new tool
for finding f/s bugs using model checking, called MCFS (Model Checking File
Systems).
Signed-off-by: Yifei Liu <yifeliu@cs.stonybrook.edu>
Signed-off-by: Erez Zadok <ezk@cs.stonybrook.edu>
Signed-off-by: Manish Adkar <madkar@cs.stonybrook.edu>
Signed-off-by: Richard Weinberger <richard@nod.at>
2022-08-03 15:53:12 +00:00
|
|
|
if (pos > inode->i_size) {
|
|
|
|
/* Make new hole frag from old EOF to new position */
|
2015-11-10 21:18:15 +00:00
|
|
|
struct jffs2_raw_inode ri;
|
2005-04-16 22:20:36 +00:00
|
|
|
struct jffs2_full_dnode *fn;
|
2015-11-10 21:18:15 +00:00
|
|
|
uint32_t alloc_len;
|
2005-11-07 11:16:07 +00:00
|
|
|
|
jffs2: correct logic when creating a hole in jffs2_write_begin
Bug description and fix:
1. Write data to a file, say all 1s from offset 0 to 16.
2. Truncate the file to a smaller size, say 8 bytes.
3. Write new bytes (say 2s) from an offset past the original size of the
file, say at offset 20, for 4 bytes. This is supposed to create a "hole"
in the file, meaning that the bytes from offset 8 (where it was truncated
above) up to the new write at offset 20, should all be 0s (zeros).
4. Flush all caches using "echo 3 > /proc/sys/vm/drop_caches" (or unmount
and remount) the f/s.
5. Check the content of the file. It is wrong. The 1s that used to be
between bytes 9 and 16, before the truncation, have REAPPEARED (they should
be 0s).
We wrote a script and helper C program to reproduce the bug
(reproduce_jffs2_write_begin_issue.sh, write_file.c, and Makefile). We can
make them available to anyone.
The above example is shown when writing a small file within the same first
page. But the bug happens for larger files, as long as steps 1, 2, and 3
above all happen within the same page.
The problem was traced to the jffs2_write_begin code, where it goes into an
'if' statement intended to handle writes past the current EOF (i.e., writes
that may create a hole). The code computes a 'pageofs' that is the floor
of the write position (pos), aligned to the page size boundary. In other
words, 'pageofs' will never be larger than 'pos'. The code then sets the
internal jffs2_raw_inode->isize to the size of max(current inode size,
pageofs) but that is wrong: the new file size should be the 'pos', which is
larger than both the current inode size and pageofs.
Similarly, the code incorrectly sets the internal jffs2_raw_inode->dsize to
the difference between the pageofs minus current inode size; instead it
should be the current pos minus the current inode size. Finally,
inode->i_size was also set incorrectly.
The patch below fixes this bug. The bug was discovered using a new tool
for finding f/s bugs using model checking, called MCFS (Model Checking File
Systems).
Signed-off-by: Yifei Liu <yifeliu@cs.stonybrook.edu>
Signed-off-by: Erez Zadok <ezk@cs.stonybrook.edu>
Signed-off-by: Manish Adkar <madkar@cs.stonybrook.edu>
Signed-off-by: Richard Weinberger <richard@nod.at>
2022-08-03 15:53:12 +00:00
|
|
|
jffs2_dbg(1, "Writing new hole frag 0x%x-0x%x between current EOF and new position\n",
|
|
|
|
(unsigned int)inode->i_size, (uint32_t)pos);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2015-11-10 21:18:15 +00:00
|
|
|
ret = jffs2_reserve_space(c, sizeof(ri), &alloc_len,
|
|
|
|
ALLOC_NORMAL, JFFS2_SUMMARY_INODE_SIZE);
|
|
|
|
if (ret)
|
2017-07-04 04:22:38 +00:00
|
|
|
goto out_err;
|
2015-11-10 21:18:15 +00:00
|
|
|
|
|
|
|
mutex_lock(&f->sem);
|
2005-04-16 22:20:36 +00:00
|
|
|
memset(&ri, 0, sizeof(ri));
|
|
|
|
|
|
|
|
ri.magic = cpu_to_je16(JFFS2_MAGIC_BITMASK);
|
|
|
|
ri.nodetype = cpu_to_je16(JFFS2_NODETYPE_INODE);
|
|
|
|
ri.totlen = cpu_to_je32(sizeof(ri));
|
|
|
|
ri.hdr_crc = cpu_to_je32(crc32(0, &ri, sizeof(struct jffs2_unknown_node)-4));
|
|
|
|
|
|
|
|
ri.ino = cpu_to_je32(f->inocache->ino);
|
|
|
|
ri.version = cpu_to_je32(++f->highest_version);
|
|
|
|
ri.mode = cpu_to_jemode(inode->i_mode);
|
2012-02-08 00:28:39 +00:00
|
|
|
ri.uid = cpu_to_je16(i_uid_read(inode));
|
|
|
|
ri.gid = cpu_to_je16(i_gid_read(inode));
|
jffs2: correct logic when creating a hole in jffs2_write_begin
Bug description and fix:
1. Write data to a file, say all 1s from offset 0 to 16.
2. Truncate the file to a smaller size, say 8 bytes.
3. Write new bytes (say 2s) from an offset past the original size of the
file, say at offset 20, for 4 bytes. This is supposed to create a "hole"
in the file, meaning that the bytes from offset 8 (where it was truncated
above) up to the new write at offset 20, should all be 0s (zeros).
4. Flush all caches using "echo 3 > /proc/sys/vm/drop_caches" (or unmount
and remount) the f/s.
5. Check the content of the file. It is wrong. The 1s that used to be
between bytes 9 and 16, before the truncation, have REAPPEARED (they should
be 0s).
We wrote a script and helper C program to reproduce the bug
(reproduce_jffs2_write_begin_issue.sh, write_file.c, and Makefile). We can
make them available to anyone.
The above example is shown when writing a small file within the same first
page. But the bug happens for larger files, as long as steps 1, 2, and 3
above all happen within the same page.
The problem was traced to the jffs2_write_begin code, where it goes into an
'if' statement intended to handle writes past the current EOF (i.e., writes
that may create a hole). The code computes a 'pageofs' that is the floor
of the write position (pos), aligned to the page size boundary. In other
words, 'pageofs' will never be larger than 'pos'. The code then sets the
internal jffs2_raw_inode->isize to the size of max(current inode size,
pageofs) but that is wrong: the new file size should be the 'pos', which is
larger than both the current inode size and pageofs.
Similarly, the code incorrectly sets the internal jffs2_raw_inode->dsize to
the difference between the pageofs minus current inode size; instead it
should be the current pos minus the current inode size. Finally,
inode->i_size was also set incorrectly.
The patch below fixes this bug. The bug was discovered using a new tool
for finding f/s bugs using model checking, called MCFS (Model Checking File
Systems).
Signed-off-by: Yifei Liu <yifeliu@cs.stonybrook.edu>
Signed-off-by: Erez Zadok <ezk@cs.stonybrook.edu>
Signed-off-by: Manish Adkar <madkar@cs.stonybrook.edu>
Signed-off-by: Richard Weinberger <richard@nod.at>
2022-08-03 15:53:12 +00:00
|
|
|
ri.isize = cpu_to_je32((uint32_t)pos);
|
2018-07-13 14:47:16 +00:00
|
|
|
ri.atime = ri.ctime = ri.mtime = cpu_to_je32(JFFS2_NOW());
|
2005-04-16 22:20:36 +00:00
|
|
|
ri.offset = cpu_to_je32(inode->i_size);
|
jffs2: correct logic when creating a hole in jffs2_write_begin
Bug description and fix:
1. Write data to a file, say all 1s from offset 0 to 16.
2. Truncate the file to a smaller size, say 8 bytes.
3. Write new bytes (say 2s) from an offset past the original size of the
file, say at offset 20, for 4 bytes. This is supposed to create a "hole"
in the file, meaning that the bytes from offset 8 (where it was truncated
above) up to the new write at offset 20, should all be 0s (zeros).
4. Flush all caches using "echo 3 > /proc/sys/vm/drop_caches" (or unmount
and remount) the f/s.
5. Check the content of the file. It is wrong. The 1s that used to be
between bytes 9 and 16, before the truncation, have REAPPEARED (they should
be 0s).
We wrote a script and helper C program to reproduce the bug
(reproduce_jffs2_write_begin_issue.sh, write_file.c, and Makefile). We can
make them available to anyone.
The above example is shown when writing a small file within the same first
page. But the bug happens for larger files, as long as steps 1, 2, and 3
above all happen within the same page.
The problem was traced to the jffs2_write_begin code, where it goes into an
'if' statement intended to handle writes past the current EOF (i.e., writes
that may create a hole). The code computes a 'pageofs' that is the floor
of the write position (pos), aligned to the page size boundary. In other
words, 'pageofs' will never be larger than 'pos'. The code then sets the
internal jffs2_raw_inode->isize to the size of max(current inode size,
pageofs) but that is wrong: the new file size should be the 'pos', which is
larger than both the current inode size and pageofs.
Similarly, the code incorrectly sets the internal jffs2_raw_inode->dsize to
the difference between the pageofs minus current inode size; instead it
should be the current pos minus the current inode size. Finally,
inode->i_size was also set incorrectly.
The patch below fixes this bug. The bug was discovered using a new tool
for finding f/s bugs using model checking, called MCFS (Model Checking File
Systems).
Signed-off-by: Yifei Liu <yifeliu@cs.stonybrook.edu>
Signed-off-by: Erez Zadok <ezk@cs.stonybrook.edu>
Signed-off-by: Manish Adkar <madkar@cs.stonybrook.edu>
Signed-off-by: Richard Weinberger <richard@nod.at>
2022-08-03 15:53:12 +00:00
|
|
|
ri.dsize = cpu_to_je32((uint32_t)pos - inode->i_size);
|
2005-04-16 22:20:36 +00:00
|
|
|
ri.csize = cpu_to_je32(0);
|
|
|
|
ri.compr = JFFS2_COMPR_ZERO;
|
|
|
|
ri.node_crc = cpu_to_je32(crc32(0, &ri, sizeof(ri)-8));
|
|
|
|
ri.data_crc = cpu_to_je32(0);
|
2005-11-07 11:16:07 +00:00
|
|
|
|
2006-05-22 23:38:06 +00:00
|
|
|
fn = jffs2_write_dnode(c, f, &ri, NULL, 0, ALLOC_NORMAL);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
if (IS_ERR(fn)) {
|
|
|
|
ret = PTR_ERR(fn);
|
|
|
|
jffs2_complete_reservation(c);
|
2015-11-10 21:18:15 +00:00
|
|
|
mutex_unlock(&f->sem);
|
2017-07-04 04:22:38 +00:00
|
|
|
goto out_err;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
ret = jffs2_add_full_dnode_to_inode(c, f, fn);
|
|
|
|
if (f->metadata) {
|
|
|
|
jffs2_mark_node_obsolete(c, f->metadata->raw);
|
|
|
|
jffs2_free_full_dnode(f->metadata);
|
|
|
|
f->metadata = NULL;
|
|
|
|
}
|
|
|
|
if (ret) {
|
2012-02-15 23:56:43 +00:00
|
|
|
jffs2_dbg(1, "Eep. add_full_dnode_to_inode() failed in write_begin, returned %d\n",
|
|
|
|
ret);
|
2005-04-16 22:20:36 +00:00
|
|
|
jffs2_mark_node_obsolete(c, fn->raw);
|
|
|
|
jffs2_free_full_dnode(fn);
|
|
|
|
jffs2_complete_reservation(c);
|
2015-11-10 21:18:15 +00:00
|
|
|
mutex_unlock(&f->sem);
|
2017-07-04 04:22:38 +00:00
|
|
|
goto out_err;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
jffs2_complete_reservation(c);
|
jffs2: correct logic when creating a hole in jffs2_write_begin
Bug description and fix:
1. Write data to a file, say all 1s from offset 0 to 16.
2. Truncate the file to a smaller size, say 8 bytes.
3. Write new bytes (say 2s) from an offset past the original size of the
file, say at offset 20, for 4 bytes. This is supposed to create a "hole"
in the file, meaning that the bytes from offset 8 (where it was truncated
above) up to the new write at offset 20, should all be 0s (zeros).
4. Flush all caches using "echo 3 > /proc/sys/vm/drop_caches" (or unmount
and remount) the f/s.
5. Check the content of the file. It is wrong. The 1s that used to be
between bytes 9 and 16, before the truncation, have REAPPEARED (they should
be 0s).
We wrote a script and helper C program to reproduce the bug
(reproduce_jffs2_write_begin_issue.sh, write_file.c, and Makefile). We can
make them available to anyone.
The above example is shown when writing a small file within the same first
page. But the bug happens for larger files, as long as steps 1, 2, and 3
above all happen within the same page.
The problem was traced to the jffs2_write_begin code, where it goes into an
'if' statement intended to handle writes past the current EOF (i.e., writes
that may create a hole). The code computes a 'pageofs' that is the floor
of the write position (pos), aligned to the page size boundary. In other
words, 'pageofs' will never be larger than 'pos'. The code then sets the
internal jffs2_raw_inode->isize to the size of max(current inode size,
pageofs) but that is wrong: the new file size should be the 'pos', which is
larger than both the current inode size and pageofs.
Similarly, the code incorrectly sets the internal jffs2_raw_inode->dsize to
the difference between the pageofs minus current inode size; instead it
should be the current pos minus the current inode size. Finally,
inode->i_size was also set incorrectly.
The patch below fixes this bug. The bug was discovered using a new tool
for finding f/s bugs using model checking, called MCFS (Model Checking File
Systems).
Signed-off-by: Yifei Liu <yifeliu@cs.stonybrook.edu>
Signed-off-by: Erez Zadok <ezk@cs.stonybrook.edu>
Signed-off-by: Manish Adkar <madkar@cs.stonybrook.edu>
Signed-off-by: Richard Weinberger <richard@nod.at>
2022-08-03 15:53:12 +00:00
|
|
|
inode->i_size = pos;
|
2015-11-10 21:18:15 +00:00
|
|
|
mutex_unlock(&f->sem);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
2005-11-07 11:16:07 +00:00
|
|
|
|
2017-07-04 04:22:38 +00:00
|
|
|
/*
|
|
|
|
* While getting a page and reading data in, lock c->alloc_sem until
|
|
|
|
* the page is Uptodate. Otherwise GC task may attempt to read the same
|
|
|
|
* page in read_cache_page(), which causes a deadlock.
|
|
|
|
*/
|
|
|
|
mutex_lock(&c->alloc_sem);
|
2024-07-11 20:58:06 +00:00
|
|
|
folio = __filemap_get_folio(mapping, index, FGP_WRITEBEGIN,
|
|
|
|
mapping_gfp_mask(mapping));
|
|
|
|
if (IS_ERR(folio)) {
|
|
|
|
ret = PTR_ERR(folio);
|
2017-07-04 04:22:38 +00:00
|
|
|
goto release_sem;
|
|
|
|
}
|
2024-07-11 20:58:06 +00:00
|
|
|
*pagep = &folio->page;
|
2017-07-04 04:22:38 +00:00
|
|
|
|
2007-10-16 08:25:18 +00:00
|
|
|
/*
|
2024-07-11 20:58:06 +00:00
|
|
|
* Read in the folio if it wasn't already present. Cannot optimize away
|
|
|
|
* the whole folio write case until jffs2_write_end can handle the
|
2007-10-16 08:25:18 +00:00
|
|
|
* case of a short-copy.
|
|
|
|
*/
|
2024-07-11 20:58:06 +00:00
|
|
|
if (!folio_test_uptodate(folio)) {
|
2015-11-10 21:18:15 +00:00
|
|
|
mutex_lock(&f->sem);
|
2024-07-11 20:58:06 +00:00
|
|
|
ret = jffs2_do_readpage_nolock(inode, &folio->page);
|
2015-11-10 21:18:15 +00:00
|
|
|
mutex_unlock(&f->sem);
|
2017-07-04 04:22:38 +00:00
|
|
|
if (ret) {
|
2024-07-11 20:58:06 +00:00
|
|
|
folio_unlock(folio);
|
|
|
|
folio_put(folio);
|
2017-07-04 04:22:38 +00:00
|
|
|
goto release_sem;
|
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
2024-07-11 20:58:06 +00:00
|
|
|
jffs2_dbg(1, "end write_begin(). folio->flags %lx\n", folio->flags);
|
2007-10-16 08:25:18 +00:00
|
|
|
|
2017-07-04 04:22:38 +00:00
|
|
|
release_sem:
|
|
|
|
mutex_unlock(&c->alloc_sem);
|
|
|
|
out_err:
|
2005-04-16 22:20:36 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2007-10-16 08:25:18 +00:00
|
|
|
static int jffs2_write_end(struct file *filp, struct address_space *mapping,
|
|
|
|
loff_t pos, unsigned len, unsigned copied,
|
2024-07-10 19:45:32 +00:00
|
|
|
struct folio *folio, void *fsdata)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
|
|
|
/* Actually commit the write from the page cache page we're looking at.
|
|
|
|
* For now, we write the full page out each time. It sucks, but it's simple
|
|
|
|
*/
|
2007-10-16 08:25:18 +00:00
|
|
|
struct inode *inode = mapping->host;
|
2005-04-16 22:20:36 +00:00
|
|
|
struct jffs2_inode_info *f = JFFS2_INODE_INFO(inode);
|
|
|
|
struct jffs2_sb_info *c = JFFS2_SB_INFO(inode->i_sb);
|
|
|
|
struct jffs2_raw_inode *ri;
|
mm, fs: get rid of PAGE_CACHE_* and page_cache_{get,release} macros
PAGE_CACHE_{SIZE,SHIFT,MASK,ALIGN} macros were introduced *long* time
ago with promise that one day it will be possible to implement page
cache with bigger chunks than PAGE_SIZE.
This promise never materialized. And unlikely will.
We have many places where PAGE_CACHE_SIZE assumed to be equal to
PAGE_SIZE. And it's constant source of confusion on whether
PAGE_CACHE_* or PAGE_* constant should be used in a particular case,
especially on the border between fs and mm.
Global switching to PAGE_CACHE_SIZE != PAGE_SIZE would cause to much
breakage to be doable.
Let's stop pretending that pages in page cache are special. They are
not.
The changes are pretty straight-forward:
- <foo> << (PAGE_CACHE_SHIFT - PAGE_SHIFT) -> <foo>;
- <foo> >> (PAGE_CACHE_SHIFT - PAGE_SHIFT) -> <foo>;
- PAGE_CACHE_{SIZE,SHIFT,MASK,ALIGN} -> PAGE_{SIZE,SHIFT,MASK,ALIGN};
- page_cache_get() -> get_page();
- page_cache_release() -> put_page();
This patch contains automated changes generated with coccinelle using
script below. For some reason, coccinelle doesn't patch header files.
I've called spatch for them manually.
The only adjustment after coccinelle is revert of changes to
PAGE_CAHCE_ALIGN definition: we are going to drop it later.
There are few places in the code where coccinelle didn't reach. I'll
fix them manually in a separate patch. Comments and documentation also
will be addressed with the separate patch.
virtual patch
@@
expression E;
@@
- E << (PAGE_CACHE_SHIFT - PAGE_SHIFT)
+ E
@@
expression E;
@@
- E >> (PAGE_CACHE_SHIFT - PAGE_SHIFT)
+ E
@@
@@
- PAGE_CACHE_SHIFT
+ PAGE_SHIFT
@@
@@
- PAGE_CACHE_SIZE
+ PAGE_SIZE
@@
@@
- PAGE_CACHE_MASK
+ PAGE_MASK
@@
expression E;
@@
- PAGE_CACHE_ALIGN(E)
+ PAGE_ALIGN(E)
@@
expression E;
@@
- page_cache_get(E)
+ get_page(E)
@@
expression E;
@@
- page_cache_release(E)
+ put_page(E)
Signed-off-by: Kirill A. Shutemov <kirill.shutemov@linux.intel.com>
Acked-by: Michal Hocko <mhocko@suse.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2016-04-01 12:29:47 +00:00
|
|
|
unsigned start = pos & (PAGE_SIZE - 1);
|
2007-10-16 08:25:18 +00:00
|
|
|
unsigned end = start + copied;
|
2005-04-16 22:20:36 +00:00
|
|
|
unsigned aligned_start = start & ~3;
|
|
|
|
int ret = 0;
|
|
|
|
uint32_t writtenlen = 0;
|
2024-07-10 20:42:35 +00:00
|
|
|
void *buf;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2024-07-10 20:42:35 +00:00
|
|
|
jffs2_dbg(1, "%s(): ino #%lu, page at 0x%llx, range %d-%d, flags %lx\n",
|
|
|
|
__func__, inode->i_ino, folio_pos(folio),
|
|
|
|
start, end, folio->flags);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2007-10-16 08:25:18 +00:00
|
|
|
/* We need to avoid deadlock with page_cache_read() in
|
2024-07-10 20:42:35 +00:00
|
|
|
jffs2_garbage_collect_pass(). So the folio must be
|
2007-10-16 08:25:18 +00:00
|
|
|
up to date to prevent page_cache_read() from trying
|
|
|
|
to re-lock it. */
|
2024-07-10 20:42:35 +00:00
|
|
|
BUG_ON(!folio_test_uptodate(folio));
|
2007-10-16 08:25:18 +00:00
|
|
|
|
mm, fs: get rid of PAGE_CACHE_* and page_cache_{get,release} macros
PAGE_CACHE_{SIZE,SHIFT,MASK,ALIGN} macros were introduced *long* time
ago with promise that one day it will be possible to implement page
cache with bigger chunks than PAGE_SIZE.
This promise never materialized. And unlikely will.
We have many places where PAGE_CACHE_SIZE assumed to be equal to
PAGE_SIZE. And it's constant source of confusion on whether
PAGE_CACHE_* or PAGE_* constant should be used in a particular case,
especially on the border between fs and mm.
Global switching to PAGE_CACHE_SIZE != PAGE_SIZE would cause to much
breakage to be doable.
Let's stop pretending that pages in page cache are special. They are
not.
The changes are pretty straight-forward:
- <foo> << (PAGE_CACHE_SHIFT - PAGE_SHIFT) -> <foo>;
- <foo> >> (PAGE_CACHE_SHIFT - PAGE_SHIFT) -> <foo>;
- PAGE_CACHE_{SIZE,SHIFT,MASK,ALIGN} -> PAGE_{SIZE,SHIFT,MASK,ALIGN};
- page_cache_get() -> get_page();
- page_cache_release() -> put_page();
This patch contains automated changes generated with coccinelle using
script below. For some reason, coccinelle doesn't patch header files.
I've called spatch for them manually.
The only adjustment after coccinelle is revert of changes to
PAGE_CAHCE_ALIGN definition: we are going to drop it later.
There are few places in the code where coccinelle didn't reach. I'll
fix them manually in a separate patch. Comments and documentation also
will be addressed with the separate patch.
virtual patch
@@
expression E;
@@
- E << (PAGE_CACHE_SHIFT - PAGE_SHIFT)
+ E
@@
expression E;
@@
- E >> (PAGE_CACHE_SHIFT - PAGE_SHIFT)
+ E
@@
@@
- PAGE_CACHE_SHIFT
+ PAGE_SHIFT
@@
@@
- PAGE_CACHE_SIZE
+ PAGE_SIZE
@@
@@
- PAGE_CACHE_MASK
+ PAGE_MASK
@@
expression E;
@@
- PAGE_CACHE_ALIGN(E)
+ PAGE_ALIGN(E)
@@
expression E;
@@
- page_cache_get(E)
+ get_page(E)
@@
expression E;
@@
- page_cache_release(E)
+ put_page(E)
Signed-off-by: Kirill A. Shutemov <kirill.shutemov@linux.intel.com>
Acked-by: Michal Hocko <mhocko@suse.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2016-04-01 12:29:47 +00:00
|
|
|
if (end == PAGE_SIZE) {
|
2007-10-16 08:25:18 +00:00
|
|
|
/* When writing out the end of a page, write out the
|
|
|
|
_whole_ page. This helps to reduce the number of
|
|
|
|
nodes in files which have many short writes, like
|
|
|
|
syslog files. */
|
2007-10-19 07:16:53 +00:00
|
|
|
aligned_start = 0;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ri = jffs2_alloc_raw_inode();
|
|
|
|
|
|
|
|
if (!ri) {
|
2012-02-15 23:56:43 +00:00
|
|
|
jffs2_dbg(1, "%s(): Allocation of raw inode failed\n",
|
|
|
|
__func__);
|
2024-07-10 20:42:35 +00:00
|
|
|
folio_unlock(folio);
|
|
|
|
folio_put(folio);
|
2005-04-16 22:20:36 +00:00
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Set the fields that the generic jffs2_write_inode_range() code can't find */
|
|
|
|
ri->ino = cpu_to_je32(inode->i_ino);
|
|
|
|
ri->mode = cpu_to_jemode(inode->i_mode);
|
2012-02-08 00:28:39 +00:00
|
|
|
ri->uid = cpu_to_je16(i_uid_read(inode));
|
|
|
|
ri->gid = cpu_to_je16(i_gid_read(inode));
|
2005-04-16 22:20:36 +00:00
|
|
|
ri->isize = cpu_to_je32((uint32_t)inode->i_size);
|
2018-07-13 14:47:16 +00:00
|
|
|
ri->atime = ri->ctime = ri->mtime = cpu_to_je32(JFFS2_NOW());
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2024-07-10 20:42:35 +00:00
|
|
|
buf = kmap_local_folio(folio, aligned_start);
|
|
|
|
ret = jffs2_write_inode_range(c, f, ri, buf,
|
|
|
|
folio_pos(folio) + aligned_start,
|
2005-04-16 22:20:36 +00:00
|
|
|
end - aligned_start, &writtenlen);
|
2024-07-10 20:42:35 +00:00
|
|
|
kunmap_local(buf);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2024-05-30 20:21:00 +00:00
|
|
|
if (ret)
|
|
|
|
mapping_set_error(mapping, ret);
|
2005-11-07 11:16:07 +00:00
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
/* Adjust writtenlen for the padding we did, so we don't confuse our caller */
|
2007-10-19 07:16:53 +00:00
|
|
|
writtenlen -= min(writtenlen, (start - aligned_start));
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
if (writtenlen) {
|
2007-10-19 07:16:53 +00:00
|
|
|
if (inode->i_size < pos + writtenlen) {
|
|
|
|
inode->i_size = pos + writtenlen;
|
2005-04-16 22:20:36 +00:00
|
|
|
inode->i_blocks = (inode->i_size + 511) >> 9;
|
2005-11-07 11:16:07 +00:00
|
|
|
|
2023-10-04 18:52:32 +00:00
|
|
|
inode_set_mtime_to_ts(inode,
|
|
|
|
inode_set_ctime_to_ts(inode, ITIME(je32_to_cpu(ri->ctime))));
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
jffs2_free_raw_inode(ri);
|
|
|
|
|
|
|
|
if (start+writtenlen < end) {
|
|
|
|
/* generic_file_write has written more to the page cache than we've
|
2005-11-07 11:16:07 +00:00
|
|
|
actually written to the medium. Mark the page !Uptodate so that
|
2005-04-16 22:20:36 +00:00
|
|
|
it gets reread */
|
2012-02-15 23:56:43 +00:00
|
|
|
jffs2_dbg(1, "%s(): Not all bytes written. Marking page !uptodate\n",
|
|
|
|
__func__);
|
2024-07-10 20:42:35 +00:00
|
|
|
folio_clear_uptodate(folio);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2012-02-15 23:56:43 +00:00
|
|
|
jffs2_dbg(1, "%s() returning %d\n",
|
|
|
|
__func__, writtenlen > 0 ? writtenlen : ret);
|
2024-07-10 20:42:35 +00:00
|
|
|
folio_unlock(folio);
|
|
|
|
folio_put(folio);
|
2007-10-16 08:25:18 +00:00
|
|
|
return writtenlen > 0 ? writtenlen : ret;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|