mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-07 03:15:31 +09:00
Merge a760542666 ("mm: shmem: don't truncate page if memory failure happens") into android-mainline
Steps on the way to 5.17-rc1 Signed-off-by: Greg Kroah-Hartman <gregkh@google.com> Change-Id: Ia5757863cdca404809ab212a169d02d7bcb1b4be
This commit is contained in:
@@ -247,12 +247,12 @@ SWAP Page Table Helpers
|
||||
| __swp_to_pmd_entry | Creates a mapped PMD from a swapped entry (arch) |
|
||||
+---------------------------+--------------------------------------------------+
|
||||
| is_migration_entry | Tests a migration (read or write) swapped entry |
|
||||
+---------------------------+--------------------------------------------------+
|
||||
| is_write_migration_entry | Tests a write migration swapped entry |
|
||||
+---------------------------+--------------------------------------------------+
|
||||
| make_migration_entry_read | Converts into read migration swapped entry |
|
||||
+---------------------------+--------------------------------------------------+
|
||||
| make_migration_entry | Creates a migration swapped entry (read or write)|
|
||||
+---------------------------+--------------------------------------------------+
|
||||
+-------------------------------+----------------------------------------------+
|
||||
| is_writable_migration_entry | Tests a write migration swapped entry |
|
||||
+-------------------------------+----------------------------------------------+
|
||||
| make_readable_migration_entry | Creates a read migration swapped entry |
|
||||
+-------------------------------+----------------------------------------------+
|
||||
| make_writable_migration_entry | Creates a write migration swapped entry |
|
||||
+-------------------------------+----------------------------------------------+
|
||||
|
||||
[1] https://lore.kernel.org/linux-mm/20181017020930.GN30832@redhat.com/
|
||||
|
||||
49
fs/inode.c
49
fs/inode.c
@@ -526,6 +526,55 @@ void __remove_inode_hash(struct inode *inode)
|
||||
}
|
||||
EXPORT_SYMBOL(__remove_inode_hash);
|
||||
|
||||
void dump_mapping(const struct address_space *mapping)
|
||||
{
|
||||
struct inode *host;
|
||||
const struct address_space_operations *a_ops;
|
||||
struct hlist_node *dentry_first;
|
||||
struct dentry *dentry_ptr;
|
||||
struct dentry dentry;
|
||||
unsigned long ino;
|
||||
|
||||
/*
|
||||
* If mapping is an invalid pointer, we don't want to crash
|
||||
* accessing it, so probe everything depending on it carefully.
|
||||
*/
|
||||
if (get_kernel_nofault(host, &mapping->host) ||
|
||||
get_kernel_nofault(a_ops, &mapping->a_ops)) {
|
||||
pr_warn("invalid mapping:%px\n", mapping);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!host) {
|
||||
pr_warn("aops:%ps\n", a_ops);
|
||||
return;
|
||||
}
|
||||
|
||||
if (get_kernel_nofault(dentry_first, &host->i_dentry.first) ||
|
||||
get_kernel_nofault(ino, &host->i_ino)) {
|
||||
pr_warn("aops:%ps invalid inode:%px\n", a_ops, host);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dentry_first) {
|
||||
pr_warn("aops:%ps ino:%lx\n", a_ops, ino);
|
||||
return;
|
||||
}
|
||||
|
||||
dentry_ptr = container_of(dentry_first, struct dentry, d_u.d_alias);
|
||||
if (get_kernel_nofault(dentry, dentry_ptr)) {
|
||||
pr_warn("aops:%ps ino:%lx invalid dentry:%px\n",
|
||||
a_ops, ino, dentry_ptr);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* if dentry is corrupted, the %pd handler may still crash,
|
||||
* but it's unlikely that we reach here with a corrupt mapping
|
||||
*/
|
||||
pr_warn("aops:%ps ino:%lx dentry name:\"%pd\"\n", a_ops, ino, &dentry);
|
||||
}
|
||||
|
||||
void clear_inode(struct inode *inode)
|
||||
{
|
||||
/*
|
||||
|
||||
@@ -3093,6 +3093,7 @@ extern void unlock_new_inode(struct inode *);
|
||||
extern void discard_new_inode(struct inode *);
|
||||
extern unsigned int get_next_ino(void);
|
||||
extern void evict_inodes(struct super_block *sb);
|
||||
void dump_mapping(const struct address_space *);
|
||||
|
||||
/*
|
||||
* Userspace may rely on the the inode number being non-zero. For example, glibc
|
||||
|
||||
52
mm/debug.c
52
mm/debug.c
@@ -112,56 +112,8 @@ static void __dump_page(struct page *page)
|
||||
type = "ksm ";
|
||||
else if (PageAnon(page))
|
||||
type = "anon ";
|
||||
else if (mapping) {
|
||||
struct inode *host;
|
||||
const struct address_space_operations *a_ops;
|
||||
struct hlist_node *dentry_first;
|
||||
struct dentry *dentry_ptr;
|
||||
struct dentry dentry;
|
||||
unsigned long ino;
|
||||
|
||||
/*
|
||||
* mapping can be invalid pointer and we don't want to crash
|
||||
* accessing it, so probe everything depending on it carefully
|
||||
*/
|
||||
if (get_kernel_nofault(host, &mapping->host) ||
|
||||
get_kernel_nofault(a_ops, &mapping->a_ops)) {
|
||||
pr_warn("failed to read mapping contents, not a valid kernel address?\n");
|
||||
goto out_mapping;
|
||||
}
|
||||
|
||||
if (!host) {
|
||||
pr_warn("aops:%ps\n", a_ops);
|
||||
goto out_mapping;
|
||||
}
|
||||
|
||||
if (get_kernel_nofault(dentry_first, &host->i_dentry.first) ||
|
||||
get_kernel_nofault(ino, &host->i_ino)) {
|
||||
pr_warn("aops:%ps with invalid host inode %px\n",
|
||||
a_ops, host);
|
||||
goto out_mapping;
|
||||
}
|
||||
|
||||
if (!dentry_first) {
|
||||
pr_warn("aops:%ps ino:%lx\n", a_ops, ino);
|
||||
goto out_mapping;
|
||||
}
|
||||
|
||||
dentry_ptr = container_of(dentry_first, struct dentry, d_u.d_alias);
|
||||
if (get_kernel_nofault(dentry, dentry_ptr)) {
|
||||
pr_warn("aops:%ps ino:%lx with invalid dentry %px\n",
|
||||
a_ops, ino, dentry_ptr);
|
||||
} else {
|
||||
/*
|
||||
* if dentry is corrupted, the %pd handler may still
|
||||
* crash, but it's unlikely that we reach here with a
|
||||
* corrupted struct page
|
||||
*/
|
||||
pr_warn("aops:%ps ino:%lx dentry name:\"%pd\"\n",
|
||||
a_ops, ino, &dentry);
|
||||
}
|
||||
}
|
||||
out_mapping:
|
||||
else if (mapping)
|
||||
dump_mapping(mapping);
|
||||
BUILD_BUG_ON(ARRAY_SIZE(pageflag_names) != __NR_PAGEFLAGS + 1);
|
||||
|
||||
pr_warn("%sflags: %pGp%s\n", type, &head->flags,
|
||||
|
||||
@@ -888,8 +888,8 @@ static void __init swap_migration_tests(struct pgtable_debug_args *args)
|
||||
pr_debug("Validating swap migration\n");
|
||||
|
||||
/*
|
||||
* make_migration_entry() expects given page to be
|
||||
* locked, otherwise it stumbles upon a BUG_ON().
|
||||
* make_[readable|writable]_migration_entry() expects given page to
|
||||
* be locked, otherwise it stumbles upon a BUG_ON().
|
||||
*/
|
||||
__SetPageLocked(page);
|
||||
swp = make_writable_migration_entry(page_to_pfn(page));
|
||||
|
||||
31
mm/gup.c
31
mm/gup.c
@@ -642,12 +642,17 @@ static struct page *follow_pmd_mask(struct vm_area_struct *vma,
|
||||
}
|
||||
retry:
|
||||
if (!pmd_present(pmdval)) {
|
||||
/*
|
||||
* Should never reach here, if thp migration is not supported;
|
||||
* Otherwise, it must be a thp migration entry.
|
||||
*/
|
||||
VM_BUG_ON(!thp_migration_supported() ||
|
||||
!is_pmd_migration_entry(pmdval));
|
||||
|
||||
if (likely(!(flags & FOLL_MIGRATION)))
|
||||
return no_page_table(vma, flags);
|
||||
VM_BUG_ON(thp_migration_supported() &&
|
||||
!is_pmd_migration_entry(pmdval));
|
||||
if (is_pmd_migration_entry(pmdval))
|
||||
pmd_migration_entry_wait(mm, pmd);
|
||||
|
||||
pmd_migration_entry_wait(mm, pmd);
|
||||
pmdval = READ_ONCE(*pmd);
|
||||
/*
|
||||
* MADV_DONTNEED may convert the pmd to null because
|
||||
@@ -1672,21 +1677,22 @@ size_t fault_in_writeable(char __user *uaddr, size_t size)
|
||||
|
||||
if (unlikely(size == 0))
|
||||
return 0;
|
||||
if (!user_write_access_begin(uaddr, size))
|
||||
return size;
|
||||
if (!PAGE_ALIGNED(uaddr)) {
|
||||
if (unlikely(__put_user(0, uaddr) != 0))
|
||||
return size;
|
||||
unsafe_put_user(0, uaddr, out);
|
||||
uaddr = (char __user *)PAGE_ALIGN((unsigned long)uaddr);
|
||||
}
|
||||
end = (char __user *)PAGE_ALIGN((unsigned long)start + size);
|
||||
if (unlikely(end < start))
|
||||
end = NULL;
|
||||
while (uaddr != end) {
|
||||
if (unlikely(__put_user(0, uaddr) != 0))
|
||||
goto out;
|
||||
unsafe_put_user(0, uaddr, out);
|
||||
uaddr += PAGE_SIZE;
|
||||
}
|
||||
|
||||
out:
|
||||
user_write_access_end();
|
||||
if (size > uaddr - start)
|
||||
return size - (uaddr - start);
|
||||
return 0;
|
||||
@@ -1771,21 +1777,22 @@ size_t fault_in_readable(const char __user *uaddr, size_t size)
|
||||
|
||||
if (unlikely(size == 0))
|
||||
return 0;
|
||||
if (!user_read_access_begin(uaddr, size))
|
||||
return size;
|
||||
if (!PAGE_ALIGNED(uaddr)) {
|
||||
if (unlikely(__get_user(c, uaddr) != 0))
|
||||
return size;
|
||||
unsafe_get_user(c, uaddr, out);
|
||||
uaddr = (const char __user *)PAGE_ALIGN((unsigned long)uaddr);
|
||||
}
|
||||
end = (const char __user *)PAGE_ALIGN((unsigned long)start + size);
|
||||
if (unlikely(end < start))
|
||||
end = NULL;
|
||||
while (uaddr != end) {
|
||||
if (unlikely(__get_user(c, uaddr) != 0))
|
||||
goto out;
|
||||
unsafe_get_user(c, uaddr, out);
|
||||
uaddr += PAGE_SIZE;
|
||||
}
|
||||
|
||||
out:
|
||||
user_read_access_end();
|
||||
(void)c;
|
||||
if (size > uaddr - start)
|
||||
return size - (uaddr - start);
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
#include <linux/ratelimit.h>
|
||||
#include <linux/page-isolation.h>
|
||||
#include <linux/pagewalk.h>
|
||||
#include <linux/shmem_fs.h>
|
||||
#include "internal.h"
|
||||
#include "ras/ras_event.h"
|
||||
|
||||
@@ -867,6 +868,7 @@ static int me_pagecache_clean(struct page_state *ps, struct page *p)
|
||||
{
|
||||
int ret;
|
||||
struct address_space *mapping;
|
||||
bool extra_pins;
|
||||
|
||||
delete_from_lru_cache(p);
|
||||
|
||||
@@ -895,18 +897,24 @@ static int me_pagecache_clean(struct page_state *ps, struct page *p)
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* The shmem page is kept in page cache instead of truncating
|
||||
* so is expected to have an extra refcount after error-handling.
|
||||
*/
|
||||
extra_pins = shmem_mapping(mapping);
|
||||
|
||||
/*
|
||||
* Truncation is a bit tricky. Enable it per file system for now.
|
||||
*
|
||||
* Open: to take i_rwsem or not for this? Right now we don't.
|
||||
*/
|
||||
ret = truncate_error_page(p, page_to_pfn(p), mapping);
|
||||
if (has_extra_refcount(ps, p, extra_pins))
|
||||
ret = MF_FAILED;
|
||||
|
||||
out:
|
||||
unlock_page(p);
|
||||
|
||||
if (has_extra_refcount(ps, p, false))
|
||||
ret = MF_FAILED;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
51
mm/shmem.c
51
mm/shmem.c
@@ -2439,6 +2439,7 @@ shmem_write_begin(struct file *file, struct address_space *mapping,
|
||||
struct inode *inode = mapping->host;
|
||||
struct shmem_inode_info *info = SHMEM_I(inode);
|
||||
pgoff_t index = pos >> PAGE_SHIFT;
|
||||
int ret = 0;
|
||||
|
||||
/* i_rwsem is held by caller */
|
||||
if (unlikely(info->seals & (F_SEAL_GROW |
|
||||
@@ -2449,7 +2450,19 @@ shmem_write_begin(struct file *file, struct address_space *mapping,
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
return shmem_getpage(inode, index, pagep, SGP_WRITE);
|
||||
ret = shmem_getpage(inode, index, pagep, SGP_WRITE);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (PageHWPoison(*pagep)) {
|
||||
unlock_page(*pagep);
|
||||
put_page(*pagep);
|
||||
*pagep = NULL;
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
@@ -2536,6 +2549,12 @@ static ssize_t shmem_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
|
||||
if (sgp == SGP_CACHE)
|
||||
set_page_dirty(page);
|
||||
unlock_page(page);
|
||||
|
||||
if (PageHWPoison(page)) {
|
||||
put_page(page);
|
||||
error = -EIO;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -3075,7 +3094,8 @@ static const char *shmem_get_link(struct dentry *dentry,
|
||||
page = find_get_page(inode->i_mapping, 0);
|
||||
if (!page)
|
||||
return ERR_PTR(-ECHILD);
|
||||
if (!PageUptodate(page)) {
|
||||
if (PageHWPoison(page) ||
|
||||
!PageUptodate(page)) {
|
||||
put_page(page);
|
||||
return ERR_PTR(-ECHILD);
|
||||
}
|
||||
@@ -3083,6 +3103,13 @@ static const char *shmem_get_link(struct dentry *dentry,
|
||||
error = shmem_getpage(inode, 0, &page, SGP_READ);
|
||||
if (error)
|
||||
return ERR_PTR(error);
|
||||
if (!page)
|
||||
return ERR_PTR(-ECHILD);
|
||||
if (PageHWPoison(page)) {
|
||||
unlock_page(page);
|
||||
put_page(page);
|
||||
return ERR_PTR(-ECHILD);
|
||||
}
|
||||
unlock_page(page);
|
||||
}
|
||||
set_delayed_call(done, shmem_put_link, page);
|
||||
@@ -3733,6 +3760,13 @@ static void shmem_destroy_inodecache(void)
|
||||
kmem_cache_destroy(shmem_inode_cachep);
|
||||
}
|
||||
|
||||
/* Keep the page in page cache instead of truncating it */
|
||||
static int shmem_error_remove_page(struct address_space *mapping,
|
||||
struct page *page)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct address_space_operations shmem_aops = {
|
||||
.writepage = shmem_writepage,
|
||||
.set_page_dirty = __set_page_dirty_no_writeback,
|
||||
@@ -3743,7 +3777,7 @@ const struct address_space_operations shmem_aops = {
|
||||
#ifdef CONFIG_MIGRATION
|
||||
.migratepage = migrate_page,
|
||||
#endif
|
||||
.error_remove_page = generic_error_remove_page,
|
||||
.error_remove_page = shmem_error_remove_page,
|
||||
};
|
||||
EXPORT_SYMBOL(shmem_aops);
|
||||
|
||||
@@ -4151,9 +4185,14 @@ struct page *shmem_read_mapping_page_gfp(struct address_space *mapping,
|
||||
error = shmem_getpage_gfp(inode, index, &page, SGP_CACHE,
|
||||
gfp, NULL, NULL, NULL);
|
||||
if (error)
|
||||
page = ERR_PTR(error);
|
||||
else
|
||||
unlock_page(page);
|
||||
return ERR_PTR(error);
|
||||
|
||||
unlock_page(page);
|
||||
if (PageHWPoison(page)) {
|
||||
put_page(page);
|
||||
return ERR_PTR(-EIO);
|
||||
}
|
||||
|
||||
return page;
|
||||
#else
|
||||
/*
|
||||
|
||||
@@ -205,7 +205,6 @@ static void truncate_cleanup_folio(struct folio *folio)
|
||||
static int
|
||||
invalidate_complete_page(struct address_space *mapping, struct page *page)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (page->mapping != mapping)
|
||||
return 0;
|
||||
@@ -213,9 +212,7 @@ invalidate_complete_page(struct address_space *mapping, struct page *page)
|
||||
if (page_has_private(page) && !try_to_release_page(page, 0))
|
||||
return 0;
|
||||
|
||||
ret = remove_mapping(mapping, page);
|
||||
|
||||
return ret;
|
||||
return remove_mapping(mapping, page);
|
||||
}
|
||||
|
||||
int truncate_inode_folio(struct address_space *mapping, struct folio *folio)
|
||||
|
||||
@@ -232,6 +232,11 @@ static int mcontinue_atomic_pte(struct mm_struct *dst_mm,
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (PageHWPoison(page)) {
|
||||
ret = -EIO;
|
||||
goto out_release;
|
||||
}
|
||||
|
||||
ret = mfill_atomic_install_pte(dst_mm, dst_pmd, dst_vma, dst_addr,
|
||||
page, false, wp_copy);
|
||||
if (ret)
|
||||
|
||||
Reference in New Issue
Block a user