| From: Miaohe Lin <linmiaohe@huawei.com> |
| Subject: mm/shmem: fix infinite loop when swap in shmem error at swapoff time |
| Date: Thu, 19 May 2022 20:50:29 +0800 |
| |
| When swap in shmem error at swapoff time, there would be a infinite loop |
| in the while loop in shmem_unuse_inode(). It's because swapin error is |
| deliberately ignored now and thus info->swapped will never reach 0. So we |
| can't escape the loop in shmem_unuse(). |
| |
| In order to fix the issue, swapin_error entry is stored in the mapping |
| when swapin error occurs. So the swapcache page can be freed and the user |
| won't end up with a permanently mounted swap because a sector is bad. If |
| the page is accessed later, the user process will be killed so that |
| corrupted data is never consumed. On the other hand, if the page is never |
| accessed, the user won't even notice it. |
| |
| Link: https://lkml.kernel.org/r/20220519125030.21486-5-linmiaohe@huawei.com |
| Signed-off-by: Miaohe Lin <linmiaohe@huawei.com> |
| Reported-by: Naoya Horiguchi <naoya.horiguchi@nec.com> |
| Reviewed-by: Naoya Horiguchi <naoya.horiguchi@nec.com> |
| Cc: Alistair Popple <apopple@nvidia.com> |
| Cc: David Hildenbrand <david@redhat.com> |
| Cc: David Howells <dhowells@redhat.com> |
| Cc: Hugh Dickins <hughd@google.com> |
| Cc: Matthew Wilcox (Oracle) <willy@infradead.org> |
| Cc: NeilBrown <neilb@suse.de> |
| Cc: Peter Xu <peterx@redhat.com> |
| Cc: Ralph Campbell <rcampbell@nvidia.com> |
| Cc: Suren Baghdasaryan <surenb@google.com> |
| Cc: Vlastimil Babka <vbabka@suse.cz> |
| Signed-off-by: Andrew Morton <akpm@linux-foundation.org> |
| --- |
| |
| mm/shmem.c | 39 +++++++++++++++++++++++++++++++++++++++ |
| 1 file changed, 39 insertions(+) |
| |
| --- a/mm/shmem.c~mm-shmem-fix-infinite-loop-when-swap-in-shmem-error-at-swapoff-time |
| +++ a/mm/shmem.c |
| @@ -1174,6 +1174,10 @@ static int shmem_find_swap_entries(struc |
| continue; |
| |
| entry = radix_to_swp_entry(folio); |
| + /* |
| + * swapin error entries can be found in the mapping. But they're |
| + * deliberately ignored here as we've done everything we can do. |
| + */ |
| if (swp_type(entry) != type) |
| continue; |
| |
| @@ -1671,6 +1675,36 @@ static int shmem_replace_page(struct pag |
| return error; |
| } |
| |
| +static void shmem_set_folio_swapin_error(struct inode *inode, pgoff_t index, |
| + struct folio *folio, swp_entry_t swap) |
| +{ |
| + struct address_space *mapping = inode->i_mapping; |
| + struct shmem_inode_info *info = SHMEM_I(inode); |
| + swp_entry_t swapin_error; |
| + void *old; |
| + |
| + swapin_error = make_swapin_error_entry(&folio->page); |
| + old = xa_cmpxchg_irq(&mapping->i_pages, index, |
| + swp_to_radix_entry(swap), |
| + swp_to_radix_entry(swapin_error), 0); |
| + if (old != swp_to_radix_entry(swap)) |
| + return; |
| + |
| + folio_wait_writeback(folio); |
| + delete_from_swap_cache(&folio->page); |
| + spin_lock_irq(&info->lock); |
| + /* |
| + * Don't treat swapin error folio as alloced. Otherwise inode->i_blocks won't |
| + * be 0 when inode is released and thus trigger WARN_ON(inode->i_blocks) in |
| + * shmem_evict_inode. |
| + */ |
| + info->alloced--; |
| + info->swapped--; |
| + shmem_recalc_inode(inode); |
| + spin_unlock_irq(&info->lock); |
| + swap_free(swap); |
| +} |
| + |
| /* |
| * Swap in the page pointed to by *pagep. |
| * Caller has to make sure that *pagep contains a valid swapped page. |
| @@ -1694,6 +1728,9 @@ static int shmem_swapin_folio(struct ino |
| swap = radix_to_swp_entry(*foliop); |
| *foliop = NULL; |
| |
| + if (is_swapin_error_entry(swap)) |
| + return -EIO; |
| + |
| /* Look it up and read it in.. */ |
| page = lookup_swap_cache(swap, NULL, 0); |
| if (!page) { |
| @@ -1761,6 +1798,8 @@ static int shmem_swapin_folio(struct ino |
| failed: |
| if (!shmem_confirm_swap(mapping, index, swap)) |
| error = -EEXIST; |
| + if (error == -EIO) |
| + shmem_set_folio_swapin_error(inode, index, folio, swap); |
| unlock: |
| if (folio) { |
| folio_unlock(folio); |
| _ |