diff --git a/MAINTAINERS b/MAINTAINERS index 59f1efb4b296..be2affcd65bb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19284,7 +19284,7 @@ F: drivers/ntb/hw/intel/ NTFS FILESYSTEM M: Namjae Jeon M: Hyunchul Lee -L: linux-fsdevel@vger.kernel.org +L: ntfs@lists.linux.dev S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/ntfs.git F: Documentation/filesystems/ntfs.rst diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index 239b7bcbaedf..d354c3b0fae1 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -697,6 +697,11 @@ static bool ntfs_non_resident_attr_value_is_valid(const struct attr_record *a) attr_len = le32_to_cpu(a->length); min_len = offsetof(struct attr_record, data.non_resident.initialized_size) + sizeof(a->data.non_resident.initialized_size); + + /* Sparse and compressed attributes have the extra compressed_size field */ + if (a->flags & (ATTR_IS_SPARSE | ATTR_COMPRESSION_MASK)) + min_len += sizeof(a->data.non_resident.compressed_size); + if (attr_len < min_len) return false; @@ -4293,6 +4298,16 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz ni->initialized_size = newsize; ctx->attr->data.non_resident.initialized_size = cpu_to_le64(newsize); } + + /* + * Drop any page-cache folios that now lie beyond the shrunk + * attribute. The clusters backing them have just been freed and the + * runlist truncated, so leaving stale dirty folios around makes a + * later writeback map a vcn past the new allocation, which fails with + * -ENOENT and loses the write. + */ + truncate_inode_pages(VFS_I(ni)->i_mapping, newsize); + /* Update data size in the index. */ if (ni->type == AT_DATA && ni->name == AT_UNNAMED) NInoSetFileNameDirty(ni); diff --git a/fs/ntfs/compress.c b/fs/ntfs/compress.c index 76bd806b41ed..ea29fade9b9b 100644 --- a/fs/ntfs/compress.c +++ b/fs/ntfs/compress.c @@ -96,26 +96,6 @@ void free_compression_buffers(void) mutex_unlock(&ntfs_cb_lock); } -/* - * zero_partial_compressed_page - zero out of bounds compressed page region - * @page: page to zero - * @initialized_size: initialized size of the attribute - */ -static void zero_partial_compressed_page(struct page *page, - const s64 initialized_size) -{ - u8 *kp = page_address(page); - unsigned int kp_ofs; - - ntfs_debug("Zeroing page region outside initialized size."); - if (((s64)page->__folio_index << PAGE_SHIFT) >= initialized_size) { - clear_page(kp); - return; - } - kp_ofs = initialized_size & ~PAGE_MASK; - memset(kp + kp_ofs, 0, PAGE_SIZE - kp_ofs); -} - /* * handle_bounds_compressed_page - test for&handle out of bounds compressed page * @page: page to check and handle @@ -125,9 +105,21 @@ static void zero_partial_compressed_page(struct page *page, static inline void handle_bounds_compressed_page(struct page *page, const loff_t i_size, const s64 initialized_size) { - if ((page->__folio_index >= (initialized_size >> PAGE_SHIFT)) && - (initialized_size < i_size)) - zero_partial_compressed_page(page, initialized_size); + loff_t pos = page_offset(page); + + if ((pos + PAGE_SIZE > initialized_size) && + (initialized_size < i_size)) { + size_t offset; + + ntfs_debug("Zeroing page region outside initialized size."); + if (pos >= initialized_size) + offset = 0; + else + offset = offset_in_page(initialized_size); + zero_user_segment(page, offset, PAGE_SIZE); + } else { + flush_dcache_page(page); + } } /* @@ -185,6 +177,7 @@ static int ntfs_decompress(struct page *dest_pages[], int completed_pages[], /* Variables for uncompressed data / destination. */ struct page *dp; /* Current destination page being worked on. */ + u8 *dp_kaddr; /* Local kmap for the current destination page. */ u8 *dp_addr; /* Current pointer into dp. */ u8 *dp_sb_start; /* Start of current sub-block in dp. */ u8 *dp_sb_end; /* End of current sb in dp (dp_sb_start + NTFS_SB_SIZE). */ @@ -199,6 +192,7 @@ static int ntfs_decompress(struct page *dest_pages[], int completed_pages[], /* Default error code. */ int err = -EOVERFLOW; + dp_kaddr = NULL; ntfs_debug("Entering, cb_size = 0x%x.", cb_size); do_next_sb: ntfs_debug("Beginning sub-block at offset = 0x%zx in the cb.", @@ -231,8 +225,6 @@ return_error: */ handle_bounds_compressed_page(dp, i_size, initialized_size); - flush_dcache_page(dp); - kunmap_local(page_address(dp)); SetPageUptodate(dp); unlock_page(dp); if (di == xpage) @@ -278,7 +270,8 @@ return_error: } /* We have a valid destination page. Setup the destination pointers. */ - dp_addr = (u8 *)page_address(dp) + do_sb_start; + dp_kaddr = kmap_local_page(dp); + dp_addr = dp_kaddr + do_sb_start; /* Now, we are ready to process the current sub-block (sb). */ if (!(le16_to_cpup((__le16 *)cb) & NTFS_SB_IS_COMPRESSED)) { @@ -299,6 +292,8 @@ return_error: /* Advance destination position to next sub-block. */ *dest_ofs += NTFS_SB_SIZE; *dest_ofs &= ~PAGE_MASK; + kunmap_local(dp_kaddr); + dp_kaddr = NULL; if (!(*dest_ofs)) { finalize_page: /* @@ -333,6 +328,8 @@ do_next_tag: } /* We have finished the current sub-block. */ *dest_ofs &= ~PAGE_MASK; + kunmap_local(dp_kaddr); + dp_kaddr = NULL; if (!(*dest_ofs)) goto finalize_page; goto do_next_sb; @@ -438,6 +435,8 @@ do_next_tag: goto do_next_tag; return_overflow: + if (dp_kaddr) + kunmap_local(dp_kaddr); ntfs_error(NULL, "Failed. Returning -EOVERFLOW."); goto return_error; } @@ -465,14 +464,14 @@ int ntfs_read_compressed_block(struct folio *folio) struct page *page = &folio->page; loff_t i_size; s64 initialized_size; - struct address_space *mapping = page->mapping; + struct address_space *mapping = folio->mapping; struct ntfs_inode *ni = NTFS_I(mapping->host); struct ntfs_volume *vol = ni->vol; struct super_block *sb = vol->sb; struct runlist_element *rl; unsigned long flags; u8 *cb, *cb_pos, *cb_end; - unsigned long offset, index = page->__folio_index; + unsigned long offset, index = folio->index; u32 cb_size = ni->itype.compressed.block_size; u64 cb_size_mask = cb_size - 1UL; s64 vcn; @@ -566,7 +565,6 @@ int ntfs_read_compressed_block(struct folio *folio) * least wasting our time. */ if (!PageDirty(page) && (!PageUptodate(page))) { - kmap_local_page(page); continue; } unlock_page(page); @@ -652,8 +650,7 @@ lock_retry_remap: } lock_page(lpage); - memcpy(cb_pos, page_address(lpage) + page_ofs, - vol->cluster_size); + memcpy_from_page(cb_pos, lpage, page_ofs, vol->cluster_size); unlock_page(lpage); put_page(lpage); cb_pos += vol->cluster_size; @@ -692,14 +689,7 @@ lock_retry_remap: for (; cur_page < cb_max_page; cur_page++) { page = pages[cur_page]; if (page) { - if (likely(!cur_ofs)) - clear_page(page_address(page)); - else - memset(page_address(page) + cur_ofs, 0, - PAGE_SIZE - - cur_ofs); - flush_dcache_page(page); - kunmap_local(page_address(page)); + memzero_page(page, cur_ofs, PAGE_SIZE - cur_ofs); SetPageUptodate(page); unlock_page(page); if (cur_page == xpage) @@ -717,8 +707,7 @@ lock_retry_remap: if (cb_max_ofs && cb_pos < cb_end) { page = pages[cur_page]; if (page) - memset(page_address(page) + cur_ofs, 0, - cb_max_ofs - cur_ofs); + memzero_page(page, cur_ofs, cb_max_ofs - cur_ofs); /* * No need to update cb_pos at this stage: * cb_pos += cb_max_ofs - cur_ofs; @@ -739,7 +728,7 @@ lock_retry_remap: for (; cur_page < cb_max_page; cur_page++) { page = pages[cur_page]; if (page) - memcpy(page_address(page) + cur_ofs, cb_pos, + memcpy_to_page(page, cur_ofs, cb_pos, PAGE_SIZE - cur_ofs); cb_pos += PAGE_SIZE - cur_ofs; cur_ofs = 0; @@ -750,7 +739,7 @@ lock_retry_remap: if (cb_max_ofs && cb_pos < cb_end) { page = pages[cur_page]; if (page) - memcpy(page_address(page) + cur_ofs, cb_pos, + memcpy_to_page(page, cur_ofs, cb_pos, cb_max_ofs - cur_ofs); cb_pos += cb_max_ofs - cur_ofs; cur_ofs = cb_max_ofs; @@ -767,8 +756,6 @@ lock_retry_remap: */ handle_bounds_compressed_page(page, i_size, initialized_size); - flush_dcache_page(page); - kunmap_local(page_address(page)); SetPageUptodate(page); unlock_page(page); if (cur2_page == xpage) @@ -804,7 +791,6 @@ lock_retry_remap: page = pages[prev_cur_page]; if (page) { flush_dcache_page(page); - kunmap_local(page_address(page)); unlock_page(page); if (prev_cur_page != xpage) put_page(page); @@ -822,14 +808,15 @@ lock_retry_remap: for (cur_page = 0; cur_page < max_page; cur_page++) { page = pages[cur_page]; if (page) { + folio = page_folio(page); + ntfs_error(vol->sb, "Still have pages left! Terminating them with extreme prejudice. Inode 0x%llx, page index 0x%lx.", - ni->mft_no, page->__folio_index); - flush_dcache_page(page); - kunmap_local(page_address(page)); - unlock_page(page); + ni->mft_no, folio->index); + flush_dcache_folio(folio); + folio_unlock(folio); if (cur_page != xpage) - put_page(page); + folio_put(folio); pages[cur_page] = NULL; } } @@ -864,7 +851,6 @@ err_out: page = pages[i]; if (page) { flush_dcache_page(page); - kunmap_local(page_address(page)); unlock_page(page); if (i != xpage) put_page(page); @@ -908,6 +894,12 @@ struct compress_context { s16 prev[NTFS_SB_SIZE]; }; +struct ntfs_compress_workspace { + struct page **pages; + char *outbuf; + unsigned int nr_pages; +}; + /* * Hash the next 3-byte sequence in the input buffer */ @@ -1084,12 +1076,11 @@ static void ntfs_skip_position(struct compress_context *pctx, const int i) * * Returns the size of the compressed block, including the * header (minimal size is 2, maximum size is 4098) - * 0 if an error has been met. + * A negative error code if an error has been met. */ -static unsigned int ntfs_compress_block(const char *inbuf, const int bufsize, - char *outbuf) +static int ntfs_compress_block(struct compress_context *pctx, + const char *inbuf, const int bufsize, char *outbuf) { - struct compress_context *pctx; int i; /* current position */ int j; /* end of best match from current position */ int k; /* end of best match from next position */ @@ -1104,10 +1095,6 @@ static unsigned int ntfs_compress_block(const char *inbuf, const int bufsize, int tag; /* current value of tag */ int ntag; /* count of bits still undefined in tag */ - pctx = kvzalloc(sizeof(struct compress_context), GFP_NOFS); - if (!pctx) - return -ENOMEM; - /* * All hash chains start as empty. The special value '-1' indicates the * end of each hash chain. @@ -1263,22 +1250,76 @@ static unsigned int ntfs_compress_block(const char *inbuf, const int bufsize, xout = NTFS_SB_SIZE + 2; } - /* - * Free the compression context and return the total number of bytes - * written to 'outbuf'. - */ - kvfree(pctx); return xout; } +static int ntfs_compress_workspace_init(struct ntfs_inode *ni, + struct ntfs_compress_workspace *ws) +{ + unsigned int size, i; + + size = ni->itype.compressed.block_size + 2 * + (ni->itype.compressed.block_size / NTFS_SB_SIZE) + 2; + ws->nr_pages = DIV_ROUND_UP(size, PAGE_SIZE); + ws->pages = kcalloc(ws->nr_pages, sizeof(*ws->pages), GFP_NOFS); + if (!ws->pages) + return -ENOMEM; + + for (i = 0; i < ws->nr_pages; i++) { + ws->pages[i] = alloc_page(GFP_NOFS); + if (!ws->pages[i]) + goto free_pages; + } + + ws->outbuf = vmap(ws->pages, ws->nr_pages, VM_MAP, PAGE_KERNEL); + if (!ws->outbuf) + goto free_pages; + return 0; + +free_pages: + while (i) + put_page(ws->pages[--i]); + kfree(ws->pages); + return -ENOMEM; +} + +static void ntfs_compress_workspace_free(struct ntfs_compress_workspace *ws) +{ + unsigned int i; + + vunmap(ws->outbuf); + for (i = 0; i < ws->nr_pages; i++) + put_page(ws->pages[i]); + kfree(ws->pages); +} + +static void ntfs_copy_cb(struct page **pages, int pages_per_cb, + unsigned int page_offset, + struct ntfs_compress_workspace *ws, unsigned int bytes) +{ + unsigned int copied = 0, i; + + for (i = 0; i < pages_per_cb && copied < bytes; i++) { + unsigned int offset = i ? 0 : page_offset; + unsigned int len = min(bytes - copied, PAGE_SIZE - offset); + void *addr = kmap_local_page(pages[i]); + + memcpy(ws->outbuf + copied, addr + offset, len); + kunmap_local(addr); + copied += len; + } +} + static int ntfs_write_cb(struct ntfs_inode *ni, loff_t pos, struct page **pages, - int pages_per_cb) + int pages_per_cb, unsigned int page_offset, + struct compress_context *ctx, struct ntfs_compress_workspace *ws) { struct ntfs_volume *vol = ni->vol; - char *outbuf = NULL, *pbuf, *inbuf; - u32 compsz, p, insz = pages_per_cb << PAGE_SHIFT; + char *outbuf = ws->outbuf, *pbuf; + u32 compsz, p, insz = ni->itype.compressed.block_size; s32 rounded, bio_size; - unsigned int sz, bsz; + int sz; + unsigned int bsz; bool fail = false, allzeroes; /* a single compressed zero */ static char onezero[] = {0x01, 0xb0, 0x00, 0x00}; @@ -1286,54 +1327,36 @@ static int ntfs_write_cb(struct ntfs_inode *ni, loff_t pos, struct page **pages, static char twozeroes[] = {0x02, 0xb0, 0x00, 0x00, 0x00}; /* more compressed zeroes, to be followed by some count */ static char morezeroes[] = {0x03, 0xb0, 0x02, 0x00}; - struct page **pages_disk = NULL, *pg; - s64 bio_lcn; + s64 bio_lcn, bio_pos; struct runlist_element *rlc, *rl; int i, err; - int pages_count = (round_up(ni->itype.compressed.block_size + 2 * - (ni->itype.compressed.block_size / NTFS_SB_SIZE) + 2, PAGE_SIZE)) / PAGE_SIZE; + u32 cb_clusters = ni->itype.compressed.block_clusters; size_t new_rl_count; struct bio *bio = NULL; - loff_t new_length; + loff_t cb_pos, new_length; s64 new_vcn; - inbuf = vmap(pages, pages_per_cb, VM_MAP, PAGE_KERNEL_RO); - if (!inbuf) - return -ENOMEM; - - /* may need 2 extra bytes per block and 2 more bytes */ - pages_disk = kcalloc(pages_count, sizeof(struct page *), GFP_NOFS); - if (!pages_disk) { - vunmap(inbuf); - return -ENOMEM; - } - - for (i = 0; i < pages_count; i++) { - pg = alloc_page(GFP_KERNEL); - if (!pg) { - err = -ENOMEM; - goto out; - } - pages_disk[i] = pg; - lock_page(pg); - kmap_local_page(pg); - } - - outbuf = vmap(pages_disk, pages_count, VM_MAP, PAGE_KERNEL); - if (!outbuf) { - err = -ENOMEM; - goto out; - } - compsz = 0; allzeroes = true; for (p = 0; (p < insz) && !fail; p += NTFS_SB_SIZE) { + unsigned int input_offset = page_offset + p; + unsigned int page_idx = input_offset >> PAGE_SHIFT; + const char *input; + void *addr; + if ((p + NTFS_SB_SIZE) < insz) bsz = NTFS_SB_SIZE; else bsz = insz - p; pbuf = &outbuf[compsz]; - sz = ntfs_compress_block(&inbuf[p], bsz, pbuf); + addr = kmap_local_page(pages[page_idx]); + input = addr + offset_in_page(input_offset); + sz = ntfs_compress_block(ctx, input, bsz, pbuf); + kunmap_local(addr); + if (sz < 0) { + err = sz; + goto out; + } /* fail if all the clusters (or more) are needed */ if (!sz || ((compsz + sz + vol->cluster_size + 2) > ni->itype.compressed.block_size)) @@ -1360,28 +1383,25 @@ static int ntfs_write_cb(struct ntfs_inode *ni, loff_t pos, struct page **pages, } } + cb_pos = pos & ~((loff_t)ni->itype.compressed.block_size - 1); + new_vcn = ntfs_bytes_to_cluster(vol, cb_pos); + if (!fail && !allzeroes) { outbuf[compsz++] = 0; outbuf[compsz++] = 0; rounded = ((compsz - 1) | (vol->cluster_size - 1)) + 1; memset(&outbuf[compsz], 0, rounded - compsz); bio_size = rounded; - pages = pages_disk; } else if (allzeroes) { - err = 0; + err = ntfs_non_resident_attr_punch_hole(ni, new_vcn, cb_clusters); goto out; } else { + ntfs_copy_cb(pages, pages_per_cb, page_offset, ws, insz); bio_size = insz; } - new_vcn = ntfs_bytes_to_cluster(vol, - pos & ~((loff_t)ni->itype.compressed.block_size - 1)); new_length = ntfs_bytes_to_cluster(vol, round_up(bio_size, vol->cluster_size)); - err = ntfs_non_resident_attr_punch_hole(ni, new_vcn, ni->itype.compressed.block_clusters); - if (err < 0) - goto out; - rlc = ntfs_cluster_alloc(vol, new_vcn, new_length, -1, DATA_ZONE, false, true, true); if (IS_ERR(rlc)) { @@ -1390,74 +1410,56 @@ static int ntfs_write_cb(struct ntfs_inode *ni, loff_t pos, struct page **pages, } bio_lcn = rlc->lcn; + bio_pos = ntfs_cluster_to_bytes(vol, bio_lcn); + bio = bio_alloc(vol->sb->s_bdev, DIV_ROUND_UP(bio_size, PAGE_SIZE), + REQ_OP_WRITE, GFP_NOIO); + bio->bi_iter.bi_sector = ntfs_bytes_to_sector(vol, bio_pos); + + for (i = 0; bio_size; i++) { + unsigned int len = min_t(unsigned int, bio_size, PAGE_SIZE); + + if (bio_add_page(bio, ws->pages[i], len, 0) != len) { + err = -EIO; + bio_put(bio); + goto free_rlc; + } + bio_size -= len; + } + + err = submit_bio_wait(bio); + bio_put(bio); + if (err) + goto free_rlc; + + /* Do not discard the old compression block until the new one is safe. */ + err = ntfs_non_resident_attr_punch_hole(ni, new_vcn, cb_clusters); + if (err) + goto free_rlc; + down_write(&ni->runlist.lock); rl = ntfs_runlists_merge(&ni->runlist, rlc, 0, &new_rl_count); if (IS_ERR(rl)) { up_write(&ni->runlist.lock); ntfs_error(vol->sb, "Failed to merge runlists"); err = PTR_ERR(rl); - if (ntfs_cluster_free_from_rl(vol, rlc)) - ntfs_error(vol->sb, "Failed to free hot clusters."); - kvfree(rlc); - goto out; + goto free_rlc; } ni->runlist.count = new_rl_count; ni->runlist.rl = rl; + rlc = NULL; err = ntfs_attr_update_mapping_pairs(ni, 0); up_write(&ni->runlist.lock); - if (err) { + if (err) err = -EIO; - goto out; - } + goto out; - i = 0; - while (bio_size > 0) { - int page_size; - - if (bio_size >= PAGE_SIZE) { - page_size = PAGE_SIZE; - bio_size -= PAGE_SIZE; - } else { - page_size = bio_size; - bio_size = 0; - } - -setup_bio: - if (!bio) { - bio = bio_alloc(vol->sb->s_bdev, 1, REQ_OP_WRITE, - GFP_NOIO); - bio->bi_iter.bi_sector = - ntfs_bytes_to_sector(vol, - ntfs_cluster_to_bytes(vol, bio_lcn + i)); - } - - if (!bio_add_page(bio, pages[i], page_size, 0)) { - err = submit_bio_wait(bio); - bio_put(bio); - if (err) - goto out; - bio = NULL; - goto setup_bio; - } - i++; - } - - err = submit_bio_wait(bio); - bio_put(bio); +free_rlc: + if (ntfs_cluster_free_from_rl(vol, rlc)) + ntfs_error(vol->sb, "Failed to free hot clusters."); + kvfree(rlc); out: - vunmap(outbuf); - for (i = 0; i < pages_count; i++) { - pg = pages_disk[i]; - if (pg) { - kunmap_local(page_address(pg)); - unlock_page(pg); - put_page(pg); - } - } - kfree(pages_disk); - vunmap(inbuf); NInoSetFileNameDirty(ni); mark_mft_record_dirty(ni); @@ -1467,31 +1469,39 @@ out: int ntfs_compress_write(struct ntfs_inode *ni, loff_t pos, size_t count, struct iov_iter *from) { + struct ntfs_compress_workspace ws = {}; + struct compress_context *ctx; struct folio *folio; struct page **pages = NULL, *page; - int pages_per_cb = ni->itype.compressed.block_size >> PAGE_SHIFT; + int pages_per_cb; int cb_size = ni->itype.compressed.block_size, cb_off, err = 0; int i, ip; size_t written = 0; struct address_space *mapping = VFS_I(ni)->i_mapping; - if (NInoCompressed(ni) && pos + count > ni->allocated_size) { - int err; - loff_t end = pos + count; - - err = ntfs_attr_expand(ni, end, - round_up(end, ni->itype.compressed.block_size)); - if (err) - return err; - } + pages_per_cb = DIV_ROUND_UP(offset_in_page(pos & ~(cb_size - 1)) + + cb_size, PAGE_SIZE); pages = kmalloc_array(pages_per_cb, sizeof(struct page *), GFP_NOFS); if (!pages) return -ENOMEM; + ctx = kvzalloc_obj(*ctx, GFP_NOFS); + if (!ctx) { + kfree(pages); + return -ENOMEM; + } + err = ntfs_compress_workspace_init(ni, &ws); + if (err) { + kvfree(ctx); + kfree(pages); + return err; + } while (count) { pgoff_t index; size_t copied, bytes; + unsigned int page_offset; + bool full_cb; int off; off = pos & (cb_size - 1); @@ -1500,7 +1510,11 @@ int ntfs_compress_write(struct ntfs_inode *ni, loff_t pos, size_t count, bytes = count; cb_off = pos & ~(cb_size - 1); + page_offset = offset_in_page(cb_off); + pages_per_cb = DIV_ROUND_UP(page_offset + cb_size, PAGE_SIZE); index = cb_off >> PAGE_SHIFT; + full_cb = !off && bytes == cb_size && !page_offset && + !(cb_size & (PAGE_SIZE - 1)); if (unlikely(fault_in_iov_iter_readable(from, bytes))) { err = -EFAULT; @@ -1508,7 +1522,10 @@ int ntfs_compress_write(struct ntfs_inode *ni, loff_t pos, size_t count, } for (i = 0; i < pages_per_cb; i++) { - folio = read_mapping_folio(mapping, index + i, NULL); + if (full_cb) + folio = filemap_grab_folio(mapping, index + i); + else + folio = read_mapping_folio(mapping, index + i, NULL); if (IS_ERR(folio)) { for (ip = 0; ip < i; ip++) { folio_unlock(page_folio(pages[ip])); @@ -1518,7 +1535,8 @@ int ntfs_compress_write(struct ntfs_inode *ni, loff_t pos, size_t count, goto out; } - folio_lock(folio); + if (!full_cb) + folio_lock(folio); pages[i] = folio_page(folio, 0); } @@ -1548,13 +1566,26 @@ int ntfs_compress_write(struct ntfs_inode *ni, loff_t pos, size_t count, } } - err = ntfs_write_cb(ni, pos, pages, pages_per_cb); + if (!copied) { + err = -EFAULT; + goto release_pages; + } + err = ntfs_write_cb(ni, pos, pages, pages_per_cb, page_offset, ctx, &ws); + if (!err && pos + copied > ni->initialized_size) { + mutex_lock(&ni->mrec_lock); + err = ntfs_attr_set_initialized_size(ni, pos + copied); + mutex_unlock(&ni->mrec_lock); + } + +release_pages: for (i = 0; i < pages_per_cb; i++) { folio = page_folio(pages[i]); - if (i < ip) { + if (!err) { folio_clear_dirty(folio); folio_mark_uptodate(folio); + } else { + folio_clear_uptodate(folio); } folio_unlock(folio); folio_put(folio); @@ -1570,6 +1601,8 @@ int ntfs_compress_write(struct ntfs_inode *ni, loff_t pos, size_t count, } out: + ntfs_compress_workspace_free(&ws); + kvfree(ctx); kfree(pages); if (err < 0) written = err; diff --git a/fs/ntfs/dir.c b/fs/ntfs/dir.c index 6fa9ae3377cb..2d594cbb4ebe 100644 --- a/fs/ntfs/dir.c +++ b/fs/ntfs/dir.c @@ -966,13 +966,14 @@ filldir: */ private = file->private_data; kfree(private->key); - private->key = kmalloc(le16_to_cpu(next->key_length), GFP_KERNEL); + private->key = kmemdup(&next->key.file_name, + le16_to_cpu(next->key_length), + GFP_KERNEL); if (!private->key) { err = -ENOMEM; goto out; } - memcpy(private->key, &next->key.file_name, le16_to_cpu(next->key_length)); private->key_length = next->key_length; break; } diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c index 0cd192752b7c..fc6cec7ce130 100644 --- a/fs/ntfs/ea.c +++ b/fs/ntfs/ea.c @@ -196,6 +196,9 @@ static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len, struct ea_attr *p_ea; u32 ea_info_qsize = 0; char *ea_buf = NULL; + char *new_ea_buf; + char *old_ea_buf = NULL; + struct ea_information old_ea_info; size_t new_ea_size = ALIGN(struct_size(p_ea, ea_name, 1 + name_len + val_size), 4); s64 ea_off, ea_info_size, all_ea_size, ea_size; @@ -249,6 +252,22 @@ create_ea_info: err = -EEXIST; goto out; } + if ((flags & XATTR_REPLACE) && !val_size) { + old_ea_info = *p_ea_info; + old_ea_buf = kvmemdup(ea_buf, all_ea_size, GFP_NOFS); + if (!old_ea_buf) { + err = -ENOMEM; + goto out; + } + } + + /* Check the final $EA size before removing the old entry. */ + if (val_size && + ntfs_attr_size_bounds_check(ni->vol, AT_EA, + ea_info_qsize - ea_size + new_ea_size)) { + err = -EFBIG; + goto out; + } p_ea = (struct ea_attr *)(ea_buf + ea_off); @@ -267,17 +286,39 @@ create_ea_info: ea_info_qsize -= ea_size; p_ea_info->ea_query_length = cpu_to_le32(ea_info_qsize); - err = ntfs_write_ea(ni, AT_EA_INFORMATION, (char *)p_ea_info, 0, - sizeof(struct ea_information), false); - if (err) - goto out; + if ((flags & XATTR_REPLACE) && !val_size && !ea_info_qsize) { + err = ntfs_attr_remove(ni, AT_EA, AT_UNNAMED, 0); + if (err) + goto out; - err = ntfs_write_ea(ni, AT_EA, ea_buf, 0, ea_info_qsize, true); - if (err) + err = ntfs_attr_remove(ni, AT_EA_INFORMATION, AT_UNNAMED, 0); + if (err) { + /* Restore the original $EA if $EA_INFORMATION removal failed. */ + ntfs_attr_add(ni, AT_EA, AT_UNNAMED, 0, old_ea_buf, + all_ea_size); + ea_info_qsize = le32_to_cpu(old_ea_info.ea_query_length); + } goto out; + } if ((flags & XATTR_REPLACE) && !val_size) { - /* Remove xattr. */ + err = ntfs_write_ea(ni, AT_EA, ea_buf, 0, ea_info_qsize, + true); + if (err) { + ntfs_write_ea(ni, AT_EA, old_ea_buf, 0, + all_ea_size, false); + goto out; + } + + err = ntfs_write_ea(ni, AT_EA_INFORMATION, (char *)p_ea_info, + 0, sizeof(struct ea_information), false); + if (err) { + ntfs_write_ea(ni, AT_EA, old_ea_buf, 0, + all_ea_size, false); + ntfs_write_ea(ni, AT_EA_INFORMATION, + (char *)&old_ea_info, 0, + sizeof(old_ea_info), false); + } goto out; } } else { @@ -285,22 +326,30 @@ create_ea_info: err = -ENODATA; goto out; } - } - kvfree(ea_buf); + if (ntfs_attr_size_bounds_check(ni->vol, AT_EA, + ea_info_qsize + new_ea_size)) { + err = -EFBIG; + goto out; + } + } alloc_new_ea: - ea_buf = kzalloc(new_ea_size, GFP_NOFS); - if (!ea_buf) { + new_ea_buf = kvzalloc(ea_info_qsize + new_ea_size, GFP_NOFS); + if (!new_ea_buf) { err = -ENOMEM; goto out; } + if (ea_info_qsize) + memcpy(new_ea_buf, ea_buf, ea_info_qsize); + kvfree(ea_buf); + ea_buf = new_ea_buf; + p_ea = (struct ea_attr *)(ea_buf + ea_info_qsize); /* * EA and REPARSE_POINT compatibility not checked any more, * required by Windows 10, but having both may lead to * problems with earlier versions. */ - p_ea = (struct ea_attr *)ea_buf; memcpy(p_ea->ea_name, name, name_len); p_ea->ea_name_length = name_len; p_ea->ea_name[name_len] = 0; @@ -312,8 +361,7 @@ alloc_new_ea: p_ea_info->ea_length = cpu_to_le16(ea_packed); p_ea_info->ea_query_length = cpu_to_le32(ea_info_qsize + new_ea_size); - if (ea_packed > 0xffff || - ntfs_attr_size_bounds_check(ni->vol, AT_EA, new_ea_size)) { + if (ea_packed > 0xffff) { err = -EFBIG; goto out; } @@ -322,13 +370,13 @@ alloc_new_ea: * no EA or EA_INFORMATION : add them */ if (!ntfs_attr_exist(ni, AT_EA, AT_UNNAMED, 0)) { - err = ntfs_attr_add(ni, AT_EA, AT_UNNAMED, 0, (char *)p_ea, - new_ea_size); + err = ntfs_attr_add(ni, AT_EA, AT_UNNAMED, 0, ea_buf, + ea_info_qsize + new_ea_size); if (err) goto out; } else { - err = ntfs_write_ea(ni, AT_EA, (char *)p_ea, ea_info_qsize, - new_ea_size, false); + err = ntfs_write_ea(ni, AT_EA, ea_buf, 0, + ea_info_qsize + new_ea_size, true); if (err) goto out; } @@ -348,6 +396,7 @@ out: NInoClearHasEA(ni); kvfree(ea_buf); + kvfree(old_ea_buf); kvfree(p_ea_info); return err; @@ -357,37 +406,35 @@ out: * Check for the presence of an EA "$LXDEV" (used by WSL) * and return its value as a device address */ -int ntfs_ea_get_wsl_inode(struct inode *inode, dev_t *rdevp, unsigned int flags) +int ntfs_ea_get_wsl_inode(struct inode *inode, dev_t *rdevp, unsigned int flags, + bool *has_lxmod) { int err; __le32 v; + *has_lxmod = false; + if (!(flags & NTFS_VOL_UID)) { /* Load uid to lxuid EA */ err = ntfs_get_ea(inode, "$LXUID", sizeof("$LXUID") - 1, &v, sizeof(v)); - if (err < 0) - return err; - if (err != sizeof(v)) - return -EIO; - i_uid_write(inode, le32_to_cpu(v)); + if (err == sizeof(v)) + i_uid_write(inode, le32_to_cpu(v)); } if (!(flags & NTFS_VOL_GID)) { /* Load gid to lxgid EA */ err = ntfs_get_ea(inode, "$LXGID", sizeof("$LXGID") - 1, &v, sizeof(v)); - if (err < 0) - return err; - if (err != sizeof(v)) - return -EIO; - i_gid_write(inode, le32_to_cpu(v)); + if (err == sizeof(v)) + i_gid_write(inode, le32_to_cpu(v)); } /* Load mode to lxmod EA */ err = ntfs_get_ea(inode, "$LXMOD", sizeof("$LXMOD") - 1, &v, sizeof(v)); if (err == sizeof(v)) { inode->i_mode = le32_to_cpu(v); + *has_lxmod = true; } else { /* Everyone gets all permissions. */ inode->i_mode |= 0777; @@ -581,7 +628,8 @@ static int ntfs_new_attr_flags(struct ntfs_inode *ni, __le32 fattr) struct mft_record *m; struct attr_record *a; __le16 new_aflags; - int mp_size, mp_ofs, name_ofs, arec_size, err; + u16 old_name_ofs, old_mp_ofs; + int mp_size, mp_ofs, name_ofs, old_arec_size, arec_size, err; m = map_mft_record(ni); if (IS_ERR(m)) @@ -613,8 +661,10 @@ static int ntfs_new_attr_flags(struct ntfs_inode *ni, __le32 fattr) else new_aflags &= ~ATTR_IS_COMPRESSED; - if (new_aflags == a->flags) - return 0; + if (new_aflags == a->flags) { + err = 0; + goto err_out; + } if ((new_aflags & (ATTR_IS_SPARSE | ATTR_IS_COMPRESSED)) == (ATTR_IS_SPARSE | ATTR_IS_COMPRESSED)) { @@ -623,15 +673,40 @@ static int ntfs_new_attr_flags(struct ntfs_inode *ni, __le32 fattr) goto err_out; } - if (!a->non_resident) - goto out; + if (!a->non_resident) { + if (!(new_aflags & (ATTR_IS_SPARSE | ATTR_IS_COMPRESSED))) + return 0; - if (a->data.non_resident.data_size) { - pr_err("Can't change sparsed/compressed for non-empty file\n"); - err = -EOPNOTSUPP; - goto err_out; + if (le32_to_cpu(a->data.resident.value_length)) { + pr_err("Can't change sparse/compressed for non-empty file"); + err = -EOPNOTSUPP; + goto err_out; + } + + err = ntfs_attr_make_non_resident(ni, 0); + if (err) + goto err_out; + + ntfs_attr_reinit_search_ctx(ctx); + err = ntfs_attr_lookup(ni->type, ni->name, + ni->name_len, CASE_SENSITIVE, + 0, NULL, 0, ctx); + if (err) { + err = -EINVAL; + goto err_out; + } + a = ctx->attr; + } else { + if (a->data.non_resident.data_size) { + pr_err("Can't change sparsed/compressed for non-empty file"); + err = -EOPNOTSUPP; + goto err_out; + } } + old_name_ofs = le16_to_cpu(a->name_offset); + old_mp_ofs = le16_to_cpu(a->data.non_resident.mapping_pairs_offset); + if (new_aflags & (ATTR_IS_SPARSE | ATTR_IS_COMPRESSED)) name_ofs = (offsetof(struct attr_record, data.non_resident.compressed_size) + @@ -649,11 +724,37 @@ static int ntfs_new_attr_flags(struct ntfs_inode *ni, __le32 fattr) mp_ofs = (name_ofs + a->name_length * sizeof(__le16) + 7) & ~7; arec_size = (mp_ofs + mp_size + 7) & ~7; + old_arec_size = le32_to_cpu(a->length); + + /* + * Move payloads before shrinking the record. Otherwise resizing moves + * the following attribute over the old payload before it can be copied. + */ + if (arec_size < old_arec_size) { + if (a->name_length && name_ofs != old_name_ofs) + memmove((u8 *)a + name_ofs, (u8 *)a + old_name_ofs, + a->name_length * sizeof(__le16)); + if (mp_ofs != old_mp_ofs) + memmove((u8 *)a + mp_ofs, (u8 *)a + old_mp_ofs, mp_size); + } err = ntfs_attr_record_resize(m, a, arec_size); if (unlikely(err)) goto err_out; + /* + * When compressed/sparse state changes, the non-resident header grows or + * shrinks by the compressed_size field. Update the in-record payload layout + * to match the new offsets before exposing the new mapping_pairs_offset. + */ + if (arec_size > old_arec_size) { + if (mp_ofs != old_mp_ofs) + memmove((u8 *)a + mp_ofs, (u8 *)a + old_mp_ofs, mp_size); + if (a->name_length) + memmove((u8 *)a + name_ofs, (u8 *)a + old_name_ofs, + a->name_length * sizeof(__le16)); + } + if (new_aflags & (ATTR_IS_SPARSE | ATTR_IS_COMPRESSED)) { a->data.non_resident.compression_unit = 0; if (new_aflags & ATTR_IS_COMPRESSED || ni->vol->major_ver < 3) @@ -674,28 +775,31 @@ static int ntfs_new_attr_flags(struct ntfs_inode *ni, __le32 fattr) ni->itype.compressed.block_size_bits = 0; ni->itype.compressed.block_clusters = 0; } - - if (new_aflags & ATTR_IS_SPARSE) { - NInoSetSparse(ni); - ni->flags |= FILE_ATTR_SPARSE_FILE; - } - - if (new_aflags & ATTR_IS_COMPRESSED) { - NInoSetCompressed(ni); - ni->flags |= FILE_ATTR_COMPRESSED; - } } else { - ni->flags &= ~(FILE_ATTR_SPARSE_FILE | FILE_ATTR_COMPRESSED); a->data.non_resident.compression_unit = 0; - NInoClearSparse(ni); - NInoClearCompressed(ni); } a->name_offset = cpu_to_le16(name_ofs); a->data.non_resident.mapping_pairs_offset = cpu_to_le16(mp_ofs); -out: a->flags = new_aflags; + + if (new_aflags & ATTR_IS_SPARSE) { + NInoSetSparse(ni); + ni->flags |= FILE_ATTR_SPARSE_FILE; + } else { + NInoClearSparse(ni); + ni->flags &= ~FILE_ATTR_SPARSE_FILE; + } + + if (new_aflags & ATTR_IS_COMPRESSED) { + NInoSetCompressed(ni); + ni->flags |= FILE_ATTR_COMPRESSED; + } else { + NInoClearCompressed(ni); + ni->flags &= ~FILE_ATTR_COMPRESSED; + } + mark_mft_record_dirty(ctx->ntfs_ino); err_out: if (ctx) @@ -704,6 +808,12 @@ err_out: return err; } +static bool ntfs_is_reserved_lxattr(const char *name) +{ + return !strcmp(name, "$LXUID") || !strcmp(name, "$LXGID") || + !strcmp(name, "$LXMOD") || !strcmp(name, "$LXDEV"); +} + static int ntfs_setxattr(const struct xattr_handler *handler, struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, const void *value, @@ -716,6 +826,9 @@ static int ntfs_setxattr(const struct xattr_handler *handler, if (NVolShutdown(ni->vol)) return -EIO; + if (ntfs_is_reserved_lxattr(name) && !capable(CAP_SYS_ADMIN)) + return -EPERM; + if (!strcmp(name, SYSTEM_DOS_ATTRIB)) { if (sizeof(u8) != size) { err = -EINVAL; diff --git a/fs/ntfs/ea.h b/fs/ntfs/ea.h index 1f63bd55e057..acb39c2a6fbc 100644 --- a/fs/ntfs/ea.h +++ b/fs/ntfs/ea.h @@ -10,7 +10,8 @@ extern const struct xattr_handler *const ntfs_xattr_handlers[]; int ntfs_ea_set_wsl_not_symlink(struct ntfs_inode *ni, mode_t mode, dev_t dev); -int ntfs_ea_get_wsl_inode(struct inode *inode, dev_t *rdevp, unsigned int flags); +int ntfs_ea_get_wsl_inode(struct inode *inode, dev_t *rdevp, unsigned int flags, + bool *has_lxmod); int ntfs_ea_set_wsl_inode(struct inode *inode, dev_t rdev, __le16 *ea_size, unsigned int flags); ssize_t ntfs_listxattr(struct dentry *dentry, char *buffer, size_t size); diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c index 6a7b638e523d..d4282822b3ce 100644 --- a/fs/ntfs/file.c +++ b/fs/ntfs/file.c @@ -268,23 +268,19 @@ static int ntfs_setattr_size(struct inode *vi, struct iattr *attr) return err; inode_dio_wait(vi); - truncate_setsize(vi, attr->ia_size); + if (attr->ia_size > old_size) { + truncate_pagecache(vi, old_size); + i_size_write(vi, attr->ia_size); + pagecache_isize_extended(vi, old_size, attr->ia_size); + } else + truncate_setsize(vi, attr->ia_size); + err = ntfs_truncate_vfs(vi, attr->ia_size, old_size); if (err) { i_size_write(vi, old_size); return err; } - if (NInoNonResident(ni) && attr->ia_size > old_size && - old_size % PAGE_SIZE != 0) { - loff_t len = min_t(loff_t, - round_up(old_size, PAGE_SIZE) - old_size, - attr->ia_size - old_size); - err = iomap_zero_range(vi, old_size, len, - NULL, &ntfs_seek_iomap_ops, - &ntfs_iomap_folio_ops, NULL); - } - return err; } @@ -346,14 +342,12 @@ int ntfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, if (ia_valid & ATTR_MODE) flags |= NTFS_EA_MODE; - if (S_ISDIR(vi->i_mode)) - vi->i_mode &= ~vol->dmask; - else - vi->i_mode &= ~vol->fmask; - mutex_lock(&ni->mrec_lock); - ntfs_ea_set_wsl_inode(vi, 0, NULL, flags); + err = ntfs_ea_set_wsl_inode(vi, 0, NULL, flags); mutex_unlock(&ni->mrec_lock); + if (err) + goto out; + } mark_inode_dirty(vi); @@ -535,6 +529,31 @@ out: return ret; } +static int ntfs_expand_for_write(struct ntfs_inode *ni, loff_t end) +{ + struct ntfs_volume *vol = ni->vol; + loff_t prealloc_size = 0; + int err; + + if (end <= ni->data_size) + return 0; + + if (NInoCompressed(ni)) { + if (end > ni->allocated_size) + prealloc_size = round_up(end, + ni->itype.compressed.block_size); + } else if (end > ni->allocated_size && + end < ni->allocated_size + vol->preallocated_size) { + prealloc_size = ni->allocated_size + vol->preallocated_size; + } + + mutex_lock(&ni->mrec_lock); + err = ntfs_attr_expand(ni, end, prealloc_size); + mutex_unlock(&ni->mrec_lock); + + return err; +} + static ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from) { struct file *file = iocb->ki_filp; @@ -543,7 +562,7 @@ static ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from) struct ntfs_volume *vol = ni->vol; ssize_t ret; ssize_t count; - loff_t pos; + loff_t pos, end; int err; loff_t old_data_size, old_init_size; @@ -580,10 +599,24 @@ static ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from) pos = iocb->ki_pos; count = ret; + end = pos + count; old_data_size = ni->data_size; old_init_size = ni->initialized_size; + if (end > old_data_size) { + ret = ntfs_expand_for_write(ni, end); + if (ret < 0) + goto out; + } + + if (NInoNonResident(ni) && !NInoCompressed(ni) && + end > old_init_size) { + ret = ntfs_extend_initialized_size(vi, pos, end); + if (ret < 0) + goto out; + } + if (NInoNonResident(ni) && NInoCompressed(ni)) { ret = ntfs_compress_write(ni, pos, count, from); if (ret > 0) @@ -655,7 +688,7 @@ static int ntfs_file_mmap_prepare(struct vm_area_desc *desc) from + desc->end - desc->start); if (NTFS_I(inode)->initialized_size < to) { - err = ntfs_extend_initialized_size(inode, to, to, false); + err = ntfs_extend_initialized_size(inode, to, to); if (err) return err; } @@ -1126,13 +1159,9 @@ out: filemap_invalidate_unlock(vi->i_mapping); if (!err) { if (mode == 0 && NInoNonResident(ni) && - offset > old_size && old_size % PAGE_SIZE != 0) { - loff_t len = min_t(loff_t, - round_up(old_size, PAGE_SIZE) - old_size, - offset - old_size); - err = iomap_zero_range(vi, old_size, len, NULL, - &ntfs_seek_iomap_ops, - &ntfs_iomap_folio_ops, NULL); + offset > old_size) { + truncate_pagecache(vi, old_size); + pagecache_isize_extended(vi, old_size, offset); } NInoSetFileNameDirty(ni); inode_set_mtime_to_ts(vi, inode_set_ctime_current(vi)); diff --git a/fs/ntfs/index.c b/fs/ntfs/index.c index faa7ee920a3a..409759eab55d 100644 --- a/fs/ntfs/index.c +++ b/fs/ntfs/index.c @@ -1112,6 +1112,7 @@ static struct index_block *ntfs_ir_to_ib(struct index_root *ir, s64 ib_vcn) struct index_entry *ie_last; char *ies_start, *ies_end; int i; + u32 ib_cap; ntfs_debug("Entering\n"); @@ -1127,6 +1128,16 @@ static struct index_block *ntfs_ir_to_ib(struct index_root *ir, s64 ib_vcn) * as well, which can never have any data. */ i = (char *)ie_last - ies_start + le16_to_cpu(ie_last->length); + + /* Entries must fit in the allocated index block */ + ib_cap = le32_to_cpu(ib->index.allocated_size) - + le32_to_cpu(ib->index.entries_offset); + if ((u32)i > ib_cap) { + ntfs_error(NULL, "Entries (%d B) exceed IB capacity", i); + kvfree(ib); + return NULL; + } + memcpy(ntfs_ie_get_first(&ib->index), ies_start, i); ib->index.flags = ir->index.flags; diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index 7381a18cfadd..39c7fd8c1149 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -682,6 +682,7 @@ static int ntfs_read_locked_inode(struct inode *vi) unsigned int name_len = 4, flags = 0; int extend_sys = 0; dev_t dev = 0; + bool has_lxmod = false; bool vol_err = true; ntfs_debug("Entering for i_ino 0x%llx.", ni->mft_no); @@ -862,7 +863,7 @@ skip_attr_list_load: err = ntfs_attr_lookup(AT_EA_INFORMATION, NULL, 0, 0, 0, NULL, 0, ctx); if (!err) { NInoSetHasEA(ni); - ntfs_ea_get_wsl_inode(vi, &dev, flags); + ntfs_ea_get_wsl_inode(vi, &dev, flags, &has_lxmod); } if (ni->flags & FILE_ATTR_REPARSE_POINT) { @@ -886,16 +887,18 @@ skip_attr_list_load: if (S_ISDIR(vi->i_mode)) { /* - * Apply the directory permissions mask set in the mount - * options. + * Apply the directory permissions mask set in the mount options + * when no per-file WSL mode is present. */ - vi->i_mode &= ~vol->dmask; + if (!has_lxmod) + vi->i_mode &= ~vol->dmask; /* Things break without this kludge! */ if (vi->i_nlink > 1) set_nlink(vi, 1); } else { - /* Apply the file permissions mask set in the mount options. */ - vi->i_mode &= ~vol->fmask; + /* Apply the file permissions mask when no WSL mode is present. */ + if (!has_lxmod) + vi->i_mode &= ~vol->fmask; } /* @@ -2401,7 +2404,7 @@ int ntfs_show_options(struct seq_file *sf, struct dentry *root) } int ntfs_extend_initialized_size(struct inode *vi, const loff_t offset, - const loff_t new_size, bool bsync) + const loff_t new_size) { struct ntfs_inode *ni = NTFS_I(vi); loff_t old_init_size; @@ -2428,10 +2431,6 @@ int ntfs_extend_initialized_size(struct inode *vi, const loff_t offset, &ntfs_iomap_folio_ops, NULL); if (err) return err; - if (bsync) - err = filemap_write_and_wait_range(vi->i_mapping, - old_init_size, - offset - 1); } diff --git a/fs/ntfs/inode.h b/fs/ntfs/inode.h index 9aacd5787ffe..c6d065aaecd5 100644 --- a/fs/ntfs/inode.h +++ b/fs/ntfs/inode.h @@ -352,7 +352,7 @@ static inline void ntfs_commit_inode(struct inode *vi) int ntfs_inode_sync_filename(struct ntfs_inode *ni); int ntfs_extend_initialized_size(struct inode *vi, const loff_t offset, - const loff_t new_size, bool bsync); + const loff_t new_size); void ntfs_set_vfs_operations(struct inode *inode, mode_t mode, dev_t dev); struct folio *ntfs_get_locked_folio(struct address_space *mapping, pgoff_t index, pgoff_t end_index, struct file_ra_state *ra); diff --git a/fs/ntfs/iomap.c b/fs/ntfs/iomap.c index 52eecf5cb256..26a1831a2c18 100644 --- a/fs/ntfs/iomap.c +++ b/fs/ntfs/iomap.c @@ -675,21 +675,7 @@ static int ntfs_write_iomap_begin_non_resident(struct inode *inode, loff_t offse loff_t length, unsigned int flags, struct iomap *iomap, int ntfs_iomap_flags) { - struct ntfs_inode *ni = NTFS_I(inode); - - if (ntfs_iomap_flags & (NTFS_IOMAP_FLAGS_BEGIN | NTFS_IOMAP_FLAGS_DIO) && - offset + length > ni->initialized_size) { - int ret; - - ret = ntfs_extend_initialized_size(inode, offset, - offset + length, - ntfs_iomap_flags & - NTFS_IOMAP_FLAGS_DIO); - if (ret < 0) - return ret; - } - - mutex_lock(&ni->mrec_lock); + mutex_lock(&NTFS_I(inode)->mrec_lock); if (ntfs_iomap_flags & NTFS_IOMAP_FLAGS_BEGIN) return ntfs_write_simple_iomap_begin_non_resident(inode, offset, length, iomap); @@ -705,28 +691,10 @@ static int __ntfs_write_iomap_begin(struct inode *inode, loff_t offset, struct iomap *iomap, int ntfs_iomap_flags) { struct ntfs_inode *ni = NTFS_I(inode); - loff_t end = offset + length; if (NVolShutdown(ni->vol)) return -EIO; - if (ntfs_iomap_flags & (NTFS_IOMAP_FLAGS_BEGIN | NTFS_IOMAP_FLAGS_DIO) && - end > ni->data_size) { - struct ntfs_volume *vol = ni->vol; - int ret; - - mutex_lock(&ni->mrec_lock); - if (end > ni->allocated_size && - end < ni->allocated_size + vol->preallocated_size) - ret = ntfs_attr_expand(ni, end, - ni->allocated_size + vol->preallocated_size); - else - ret = ntfs_attr_expand(ni, end, 0); - mutex_unlock(&ni->mrec_lock); - if (ret) - return ret; - } - if (!NInoNonResident(ni)) { mutex_lock(&ni->mrec_lock); return ntfs_write_iomap_begin_resident(inode, offset, iomap); diff --git a/fs/ntfs/lcnalloc.c b/fs/ntfs/lcnalloc.c index 835a041023a2..aa2e017a4384 100644 --- a/fs/ntfs/lcnalloc.c +++ b/fs/ntfs/lcnalloc.c @@ -298,7 +298,12 @@ struct runlist_element *ntfs_cluster_alloc(struct ntfs_volume *vol, const s64 st clusters = count; rlpos = rlsize = 0; mapping = lcnbmp_vi->i_mapping; - i_size = i_size_read(lcnbmp_vi); + /* + * lcn_empty_bits_per_page is sized from nr_clusters, but $Bitmap can + * cover more clusters than that; bound the scan by the array. + */ + i_size = min_t(s64, i_size_read(lcnbmp_vi), + ((s64)vol->nr_clusters + 7) >> 3); while (1) { ntfs_debug("Start of outer while loop: done_zones 0x%x, search_zone %i, pass %i, zone_start 0x%llx, zone_end 0x%llx, bmp_initial_pos 0x%llx, bmp_pos 0x%llx, rlpos %i, rlsize %i.", done_zones, search_zone, pass, diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index fd20d7abd6f5..f95e433885a0 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -2420,7 +2420,7 @@ mft_rec_already_initialized: * record. */ - (*ni)->mrec = kmalloc(vol->mft_record_size, GFP_NOFS); + (*ni)->mrec = kmemdup(m, vol->mft_record_size, GFP_NOFS); if (!(*ni)->mrec) { folio_unlock(folio); kunmap_local(m); @@ -2429,7 +2429,6 @@ mft_rec_already_initialized: goto undo_mftbmp_alloc; } - memcpy((*ni)->mrec, m, vol->mft_record_size); post_read_mst_fixup((struct ntfs_record *)(*ni)->mrec, vol->mft_record_size); ntfs_mft_mark_dirty(folio); folio_unlock(folio); diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c index 5ff25e9aaa32..96045face63f 100644 --- a/fs/ntfs/namei.c +++ b/fs/ntfs/namei.c @@ -61,12 +61,12 @@ static int ntfs_check_bad_windows_name(struct ntfs_volume *vol, const __le16 *wc, unsigned int wc_len) { - if (ntfs_check_bad_char(wc, wc_len)) - return -EINVAL; - if (!NVolCheckWindowsNames(vol)) return 0; + if (ntfs_check_bad_char(wc, wc_len)) + return -EINVAL; + /* Check for trailing space or dot. */ if (wc_len > 0 && (wc[wc_len - 1] == cpu_to_le16(' ') || @@ -424,8 +424,6 @@ static struct ntfs_inode *__ntfs_create(struct mnt_idmap *idmap, struct inode *d * directories, also setup the index values to the defaults. */ if (S_ISDIR(mode)) { - mode &= ~vol->dmask; - NInoSetMstProtected(ni); ni->itype.index.block_size = 4096; ni->itype.index.block_size_bits = ntfs_ffs(4096) - 1; @@ -439,8 +437,6 @@ static struct ntfs_inode *__ntfs_create(struct mnt_idmap *idmap, struct inode *d ni->itype.index.vcn_size_bits = vol->sector_size_bits; } - } else { - mode &= ~vol->fmask; } if (IS_RDONLY(vi)) @@ -685,7 +681,8 @@ static struct ntfs_inode *__ntfs_create(struct mnt_idmap *idmap, struct inode *d mutex_unlock(&dir_ni->mrec_lock); mutex_unlock(&ni->mrec_lock); - ni->flags = fn->file_attributes; + ni->flags = fn->file_attributes | + (ni->flags & FILE_ATTRIBUTE_RECALL_ON_OPEN); /* Set the sequence number. */ vi->i_generation = ni->seq_no; set_nlink(vi, 1); diff --git a/fs/ntfs/reparse.c b/fs/ntfs/reparse.c index fa523dc3691e..0d3988992119 100644 --- a/fs/ntfs/reparse.c +++ b/fs/ntfs/reparse.c @@ -317,8 +317,7 @@ unsigned int ntfs_make_symlink(struct ntfs_inode *ni) } else ni->flags &= ~FILE_ATTR_REPARSE_POINT; - if (reparse_attr) - kvfree(reparse_attr); + kvfree(reparse_attr); return mode; } @@ -358,8 +357,7 @@ unsigned int ntfs_reparse_tag_dt_types(struct ntfs_volume *vol, unsigned long mr } } - if (reparse_attr) - kvfree(reparse_attr); + kvfree(reparse_attr); iput(vi); return dt_type; @@ -894,12 +892,7 @@ int ntfs_reparse_set_native_symlink(struct ntfs_inode *ni, err = ntfs_set_ntfs_reparse_data(ni, (char *)reparse, total_reparse_len); if (!err) { - int len = strlen(sub_name); - - for (i = 0; i < len; i++) { - if (sub_name[i] == '\\') - sub_name[i] = '/'; - } + strreplace(sub_name, '\\', '/'); ni->target = sub_name; sub_name = NULL; if (prt_sub_shared) diff --git a/fs/ntfs/runlist.c b/fs/ntfs/runlist.c index cbb6576cf725..17eb275a21ff 100644 --- a/fs/ntfs/runlist.c +++ b/fs/ntfs/runlist.c @@ -71,29 +71,46 @@ static inline void ntfs_rl_mc(struct runlist_element *dstbase, int dst, * On success, return a pointer to the newly allocated, or recycled, memory. * On error, return -errno. */ -struct runlist_element *ntfs_rl_realloc(struct runlist_element *rl, - int old_size, int new_size) +static inline struct runlist_element *ntfs_rl_realloc_gfp(struct runlist_element *rl, + int old_size, int new_size, gfp_t gfp) { struct runlist_element *new_rl; + size_t new_bytes; + + if (old_size < 0 || new_size < 0) + return ERR_PTR(-EINVAL); - old_size = old_size * sizeof(*rl); - new_size = new_size * sizeof(*rl); if (old_size == new_size) return rl; - new_rl = kvzalloc(new_size, GFP_NOFS); + if (check_mul_overflow(new_size, sizeof(*rl), &new_bytes)) + return ERR_PTR(-EINVAL); + + new_rl = kvzalloc(new_bytes, gfp); if (unlikely(!new_rl)) return ERR_PTR(-ENOMEM); if (likely(rl != NULL)) { - if (unlikely(old_size > new_size)) - old_size = new_size; - memcpy(new_rl, rl, old_size); + size_t old_bytes; + + if (check_mul_overflow(old_size, sizeof(*rl), &old_bytes)) { + kvfree(new_rl); + return ERR_PTR(-EINVAL); + } + if (unlikely(old_bytes > new_bytes)) + old_bytes = new_bytes; + memcpy(new_rl, rl, old_bytes); kvfree(rl); } return new_rl; } +struct runlist_element *ntfs_rl_realloc(struct runlist_element *rl, + int old_size, int new_size) +{ + return ntfs_rl_realloc_gfp(rl, old_size, new_size, GFP_NOFS); +} + /* * ntfs_rl_realloc_nofail - Reallocate memory for runlists * @rl: original runlist @@ -118,21 +135,8 @@ struct runlist_element *ntfs_rl_realloc(struct runlist_element *rl, static inline struct runlist_element *ntfs_rl_realloc_nofail(struct runlist_element *rl, int old_size, int new_size) { - struct runlist_element *new_rl; - - old_size = old_size * sizeof(*rl); - new_size = new_size * sizeof(*rl); - if (old_size == new_size) - return rl; - - new_rl = kvmalloc(new_size, GFP_NOFS | __GFP_NOFAIL); - if (likely(rl != NULL)) { - if (unlikely(old_size > new_size)) - old_size = new_size; - memcpy(new_rl, rl, old_size); - kvfree(rl); - } - return new_rl; + return ntfs_rl_realloc_gfp(rl, old_size, new_size, + GFP_NOFS | __GFP_NOFAIL); } /* @@ -880,6 +884,13 @@ struct runlist_element *ntfs_mapping_pairs_decompress(const struct ntfs_volume * ntfs_error(vol->sb, "lcn == -1"); } #endif + /* Check lcn is within the volume. */ + if (unlikely(lcn >= (s64)vol->nr_clusters)) { + ntfs_error(vol->sb, + "LCN >= nr_clusters in mapping pairs array."); + goto err_out; + } + /* Check lcn is not below -1. */ if (unlikely(lcn < -1)) { ntfs_error(vol->sb, "Invalid s64 < -1 in mapping pairs array.");