diff options
39 files changed, 5112 insertions, 1100 deletions
diff --git a/Documentation/filesystems/ntfs.rst b/Documentation/filesystems/ntfs.rst index 4bfa392daec6..d5819da6ab53 100644 --- a/Documentation/filesystems/ntfs.rst +++ b/Documentation/filesystems/ntfs.rst @@ -9,6 +9,7 @@ The Linux NTFS filesystem driver - Overview - Utilities support + - Linux file attributes - Supported mount options @@ -34,6 +35,34 @@ The project is available at: https://github.com/ntfsprogs-plus/ntfsprogs-plus +Linux file attributes +===================== + +The driver supports lsattr(1) and chattr(1). Immutable, append-only, nodump +and noatime are stored in a private ``$LINUXFLAGS`` extended attribute within the +standard NTFS ``$EA``/``$EA_INFORMATION`` attributes. Its value is a +32-bit little-endian bitmask: bit 0 is immutable, bit 1 is append-only, +bit 2 is nodump, and bit 3 is noatime. Unknown bits are preserved when updating +the flags; the entry is removed when the entire value becomes zero. These settings +survive inode eviction, unmount and reboot. Direct writes to this EA +through setxattr(2) or removexattr(2) are rejected; use chattr(1) instead. + +The noatime attribute (``chattr +A``) suppresses automatic access-time +updates for the inode, including when mounted with ``strictatime``. It +applies to directories as well as files, but is not inherited by newly +created children. Use ``chattr -A`` to restore the mount's access-time policy. + +System metadata files and files protected by the ``sys_immutable`` mount +option cannot have their immutable protection cleared with chattr(1). +Protection derived solely from the mount option is not stored in the EA. +Case folding is reported according to the mount options and cannot be +configured per file. Compression and encryption are reported but cannot +be changed through chattr(1). + +These Linux flags are enforced by Linux. Storing them in an NTFS EA does +not make Windows enforce immutable, append-only, nodump or noatime semantics. + + Supported mount options ======================= @@ -169,4 +198,40 @@ symlink=wsl|native Configure how symbolic links are created. Under Linux) compatible symlinks are created. Under "native", Windows native symbolic links are created. + +streams_interface= Select the named-stream pathname interface: + "none" (default) treats ':' as an ordinary filename + character, while "windows" interprets the final + component of a path such as "file:stream" as a named + $DATA stream. In "windows" mode, filenames containing + ':' are hidden from directory listings and cannot be + addressed through the pathname interface. Only the + file:stream form is recognized. Typed forms such + as file::$DATA and file:stream:$DATA are not + supported. The ioctl interface is available in both + modes. The base file or directory must already exist + before a pathname stream can be created. Creating + file:stream does not create file. Removing a stream + while it is open fails with -EBUSY. Removing the base + file while a stream is open detaches the stream from + the namespace; NTFS has no orphan-stream list, so a + crash can leave that stream's data unreachable on + disk. ======================= ==================================================== + +Named-stream ioctls +=================== + +The named-stream ioctl interface is available regardless of +streams_interface. Open the base regular file or directory and use +NTFS_IOC_STREAM_READ and NTFS_IOC_STREAM_WRITE with a +struct ntfs_stream request. The request contains the stream name, +byte offset, and transfer length. Write requests create the stream when it +does not exist. Writes are not atomic. Error can leave partial data +changes, an extended stream size, or a newly created stream. bytes_returned +is valid only on success. Failed write does not report a completed byte +count. Use NTFS_IOC_STREAM_REMOVE to remove a stream and NTFS_IOC_LIST_STREAMS +to enumerate streams. Stream names are encoded with +the mounted NLS by default. Set NTFS_STREAM_FL_UTF16 to pass or receive +raw UTF-16LE names. These ioctls transfer stream data directly and do not +return a separate stream file descriptor. diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst index 2fc53093752d..94a421970b85 100644 --- a/Documentation/userspace-api/ioctl/ioctl-number.rst +++ b/Documentation/userspace-api/ioctl/ioctl-number.rst @@ -401,6 +401,7 @@ Code Seq# Include File Comments 0xE5 00-3F linux/fuse.h 0xEC 00-01 drivers/platform/chrome/cros_ec_dev.h ChromeOS EC driver 0xEE 00-09 uapi/linux/pfrut.h Platform Firmware Runtime Update and Telemetry +0xEF 00-0F uapi/linux/ntfs.h NTFS 0xF3 00-3F drivers/usb/misc/sisusbvga/sisusb.h sisfb (in development) <mailto:thomas@winischhofer.net> 0xF6 all LTTng Linux Trace Toolkit Next Generation diff --git a/MAINTAINERS b/MAINTAINERS index 079109a3e1d4..5ca4d91a3876 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19504,6 +19504,7 @@ S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/ntfs.git F: Documentation/filesystems/ntfs.rst F: fs/ntfs/ +F: include/uapi/linux/ntfs.h NTFS3 FILESYSTEM M: Konstantin Komarov <almaz.alexandrovich@paragon-software.com> diff --git a/fs/ntfs/Makefile b/fs/ntfs/Makefile index ee8987e496a8..3e8549577653 100644 --- a/fs/ntfs/Makefile +++ b/fs/ntfs/Makefile @@ -5,7 +5,7 @@ obj-$(CONFIG_NTFS_FS) += ntfs.o ntfs-y := aops.o attrib.o collate.o dir.o file.o index.o inode.o \ mft.o mst.o namei.o runlist.o super.o unistr.o attrlist.o ea.o \ upcase.o bitmap.o lcnalloc.o logfile.o reparse.o compress.o \ - iomap.o debug.o sysctl.o object_id.o bdev-io.o + iomap.o debug.o sysctl.o object_id.o bdev-io.o named_stream.o ntfs-$(CONFIG_NTFS_FS_WOF_COMPRESSION) += wof.o \ lib/decompress_common.o lib/lzx_decompress.o lib/xpress_decompress.o diff --git a/fs/ntfs/aops.c b/fs/ntfs/aops.c index 517d9a1563e7..ad781674fef1 100644 --- a/fs/ntfs/aops.c +++ b/fs/ntfs/aops.c @@ -240,10 +240,11 @@ static void ntfs_readahead(struct readahead_control *rac) }; /* - * Resident files are not cached in the page cache, - * and readahead is not implemented for compressed files. + * Resident files are not cached in the page cache, readahead is not + * implemented for compressed files, and ntfs_read_folio() refuses + * encrypted files because decryption is not implemented. */ - if (!NInoNonResident(ni) || NInoCompressed(ni) || + if (!NInoNonResident(ni) || NInoEncrypted(ni) || NInoCompressed(ni) || NInoWofCompressed(ni)) return; iomap_readahead(&ntfs_read_iomap_ops, &ctx, NULL); diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index c949ff765075..fce7144d7bef 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -103,6 +103,17 @@ int ntfs_map_runlist_nolock(struct ntfs_inode *ni, s64 vcn, struct ntfs_attr_sea base_ni = ni; else base_ni = ni->ext.base_ntfs_ino; + /* + * ntfs_read_inode_mount() builds $MFT's runlist itself, so nothing + * should reach here for $MFT. A crafted image can: the read that + * gets here already holds the $MFT folio lock it would wait on. + */ + if (unlikely(NVolMftBootstrap(ni->vol) && + base_ni == NTFS_I(ni->vol->mft_ino))) { + ntfs_error(ni->vol->sb, + "$MFT needs its own extent records to describe itself; cannot mount."); + return -EIO; + } if (!ctx) { ctx_is_temporary = ctx_needs_reset = true; m = map_mft_record(base_ni); @@ -317,7 +328,7 @@ int ntfs_map_runlist(struct ntfs_inode *ni, s64 vcn) struct runlist_element *ntfs_attr_vcn_to_rl(struct ntfs_inode *ni, s64 vcn, s64 *lcn) { struct runlist_element *rl = ni->runlist.rl; - int err; + int err = 0; bool is_retry = false; if (!rl) { @@ -335,12 +346,34 @@ remap_rl: if (*lcn <= LCN_RL_NOT_MAPPED && is_retry == false) { is_retry = true; - if (!ntfs_map_runlist_nolock(ni, vcn, NULL)) { + err = ntfs_map_runlist_nolock(ni, vcn, NULL); + if (!err) { rl = ni->runlist.rl; goto remap_rl; } } + /* + * Neither the runlist nor the retry mapped @vcn, e.g. because the + * extent mft record holding it is corrupt or because the mapping + * pairs end too soon. ntfs_map_runlist_nolock() reports the latter + * as -ENOENT, as @vcn lies past the extent it found. Below the + * allocated size, callers would treat LCN_RL_NOT_MAPPED or LCN_ENOENT + * as a hole, so fail instead. At or beyond it nothing is mapped: the + * runlist ends there with LCN_ENOENT, or with LCN_RL_NOT_MAPPED if + * only a later extent has been mapped, so return that end as it is. + */ + if (*lcn <= LCN_RL_NOT_MAPPED) { + unsigned long flags; + s64 allocated_size; + + read_lock_irqsave(&ni->size_lock, flags); + allocated_size = ni->allocated_size; + read_unlock_irqrestore(&ni->size_lock, flags); + if ((s64)ntfs_cluster_to_bytes(ni->vol, vcn) < allocated_size) + return ERR_PTR(err == -ENOMEM ? -ENOMEM : -EIO); + } + return rl; } @@ -700,8 +733,12 @@ static bool ntfs_non_resident_attr_value_is_valid(const struct attr_record *a) 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)) + /* + * Only the first extent of sparse and compressed attributes has the + * extra compressed_size field. + */ + if ((a->flags & (ATTR_IS_SPARSE | ATTR_COMPRESSION_MASK)) && + !a->data.non_resident.lowest_vcn) min_len += sizeof(a->data.non_resident.compressed_size); if (attr_len < min_len) @@ -909,7 +946,7 @@ static int ntfs_attr_find(const __le32 type, const __le16 *name, rc = ntfs_collate_names(name, name_len, (__le16 *)((u8 *)a + le16_to_cpu(a->name_offset)), - a->name_length, 1, IGNORE_CASE, + a->name_length, true, IGNORE_CASE, upcase, upcase_len); /* * If @name collates before a->name, there is no @@ -922,7 +959,7 @@ static int ntfs_attr_find(const __le32 type, const __le16 *name, continue; rc = ntfs_collate_names(name, name_len, (__le16 *)((u8 *)a + le16_to_cpu(a->name_offset)), - a->name_length, 1, CASE_SENSITIVE, + a->name_length, true, CASE_SENSITIVE, upcase, upcase_len); if (rc == -1) return -ENOENT; @@ -1313,7 +1350,7 @@ find_attr_list_attr: register int rc; rc = ntfs_collate_names(name, name_len, al_name, - al_name_len, 1, IGNORE_CASE, + al_name_len, true, IGNORE_CASE, vol->upcase, vol->upcase_len); /* * If @name collates before al_name, there is no @@ -1326,7 +1363,7 @@ find_attr_list_attr: continue; rc = ntfs_collate_names(name, name_len, al_name, - al_name_len, 1, CASE_SENSITIVE, + al_name_len, true, CASE_SENSITIVE, vol->upcase, vol->upcase_len); if (rc == -1) goto not_found; @@ -1775,7 +1812,7 @@ int ntfs_attr_size_bounds_check(const struct ntfs_volume *vol, const __le32 type * $ATTRIBUTE_LIST has a maximum size of 256kiB, but this is not * listed in $AttrDef. */ - if (unlikely(type == AT_ATTRIBUTE_LIST && size > 256 * 1024)) + if (unlikely(type == AT_ATTRIBUTE_LIST && size > NTFS_MAX_ATTR_LIST_SIZE)) return -ERANGE; /* Get the $AttrDef entry for the attribute @type. */ ad = ntfs_attr_find_in_attrdef(vol, type); @@ -2315,12 +2352,13 @@ int ntfs_attr_set(struct ntfs_inode *ni, s64 ofs, s64 cnt, const u8 val) int ntfs_attr_set_initialized_size(struct ntfs_inode *ni, loff_t new_size) { struct ntfs_attr_search_ctx *ctx; + struct ntfs_inode *base_ni = ntfs_base_inode(ni); int err = 0; if (!NInoNonResident(ni)) return -EINVAL; - ctx = ntfs_attr_get_search_ctx(ni, NULL); + ctx = ntfs_attr_get_search_ctx(base_ni, NULL); if (!ctx) return -ENOMEM; @@ -2402,6 +2440,7 @@ int ntfs_resident_attr_record_add(struct ntfs_inode *ni, __le32 type, struct mft_record *m; int err, offset; struct ntfs_inode *base_ni; + u32 ic; if (!ni || (!name && name_len)) return -EINVAL; @@ -2431,7 +2470,8 @@ int ntfs_resident_attr_record_add(struct ntfs_inode *ni, __le32 type, * attribute in @ni->mrec, not any extent inode in case if @ni is base * file record. */ - err = ntfs_attr_find(type, name, name_len, CASE_SENSITIVE, val, size, ctx); + ic = type == AT_DATA && name_len ? IGNORE_CASE : CASE_SENSITIVE; + err = ntfs_attr_find(type, name, name_len, ic, val, size, ctx); if (!err) { err = -EEXIST; ntfs_debug("Attribute already present.\n"); @@ -2523,6 +2563,7 @@ static int ntfs_non_resident_attr_record_add(struct ntfs_inode *ni, __le32 type, struct mft_record *m; struct ntfs_inode *base_ni; int err, offset; + u32 ic; if (!ni || dataruns_size <= 0 || (!name && name_len)) return -EINVAL; @@ -2552,7 +2593,8 @@ static int ntfs_non_resident_attr_record_add(struct ntfs_inode *ni, __le32 type, * attribute in @ni->mrec, not any extent inode in case if @ni is base * file record. */ - err = ntfs_attr_find(type, name, name_len, CASE_SENSITIVE, NULL, 0, ctx); + ic = type == AT_DATA && name_len ? IGNORE_CASE : CASE_SENSITIVE; + err = ntfs_attr_find(type, name, name_len, ic, NULL, 0, ctx); if (!err) { err = -EEXIST; pr_err("Attribute 0x%x already present\n", type); @@ -2626,7 +2668,7 @@ static int ntfs_non_resident_attr_record_add(struct ntfs_inode *ni, __le32 type, * update of attribute list. */ ntfs_attr_reinit_search_ctx(ctx); - err = ntfs_attr_lookup(type, name, name_len, CASE_SENSITIVE, + err = ntfs_attr_lookup(type, name, name_len, ic, lowest_vcn, NULL, 0, ctx); if (err) { pr_err("%s: attribute lookup failed\n", __func__); @@ -3095,6 +3137,7 @@ int ntfs_attr_open(struct ntfs_inode *ni, const __le32 type, struct attr_record *a; bool cs; struct ntfs_inode *base_ni; + u32 ic; int err; if (!ni || !ni->vol) @@ -3103,10 +3146,7 @@ int ntfs_attr_open(struct ntfs_inode *ni, const __le32 type, ntfs_debug("Entering for inode %lld, attr 0x%x.\n", ni->mft_no, type); - if (NInoAttr(ni)) - base_ni = ni->ext.base_ntfs_ino; - else - base_ni = ni; + base_ni = ntfs_base_inode(ni); if (name && name != AT_UNNAMED && name != I30) { name = ntfs_ucsndup(name, name_len); @@ -3124,7 +3164,8 @@ int ntfs_attr_open(struct ntfs_inode *ni, const __le32 type, goto err_out; } - err = ntfs_attr_lookup(type, name, name_len, 0, 0, NULL, 0, ctx); + ic = type == AT_DATA && name_len ? IGNORE_CASE : CASE_SENSITIVE; + err = ntfs_attr_lookup(type, name, name_len, ic, 0, NULL, 0, ctx); if (err) goto put_err_out; @@ -3309,7 +3350,8 @@ int ntfs_attr_map_whole_runlist(struct ntfs_inode *ni) not_mapped = 1; err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, - CASE_SENSITIVE, next_vcn, NULL, 0, ctx); + CASE_SENSITIVE, next_vcn, + NULL, 0, ctx); if (err) break; @@ -3394,6 +3436,7 @@ int ntfs_attr_record_move_to(struct ntfs_attr_search_ctx *ctx, struct ntfs_inode struct ntfs_attr_search_ctx *nctx; struct attr_record *a; int err; + u32 ic; struct mft_record *ni_mrec; struct super_block *sb; @@ -3429,9 +3472,11 @@ int ntfs_attr_record_move_to(struct ntfs_attr_search_ctx *ctx, struct ntfs_inode * attribute in @ni->mrec, not any extent inode in case if @ni is base * file record. */ - err = ntfs_attr_find(a->type, (__le16 *)((u8 *)a + le16_to_cpu(a->name_offset)), - a->name_length, CASE_SENSITIVE, NULL, - 0, nctx); + ic = a->type == AT_DATA && a->name_length ? + IGNORE_CASE : CASE_SENSITIVE; + err = ntfs_attr_find(a->type, + (__le16 *)((u8 *)a + le16_to_cpu(a->name_offset)), + a->name_length, ic, NULL, 0, nctx); if (!err) { ntfs_debug("Attribute of such type, with same name already present in this MFT record.\n"); err = -EEXIST; @@ -3763,7 +3808,8 @@ retry: finished_build = false; start_rl = ni->runlist.rl; while (!(err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, - CASE_SENSITIVE, from_vcn, NULL, 0, ctx))) { + CASE_SENSITIVE, from_vcn, NULL, + 0, ctx))) { unsigned int de_cnt = 0; a = ctx->attr; @@ -3974,7 +4020,8 @@ retry: ntfs_attr_reinit_search_ctx(ctx); ntfs_debug("Deallocate marked extents.\n"); while (!(err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, - CASE_SENSITIVE, 0, NULL, 0, ctx))) { + CASE_SENSITIVE, 0, NULL, 0, + ctx))) { if (le64_to_cpu(ctx->attr->data.non_resident.highest_vcn) != NTFS_VCN_DELETE_MARK) continue; @@ -4234,12 +4281,14 @@ static int ntfs_attr_make_resident(struct ntfs_inode *ni, struct ntfs_attr_searc * ntfs_non_resident_attr_shrink - shrink a non-resident, open ntfs attribute * @ni: non-resident ntfs attribute to shrink * @newsize: new size (in bytes) to which to shrink the attribute + * @pagecache_truncated: page cache was already truncated to @newsize * * Reduce the size of a non-resident, open ntfs attribute @na to @newsize bytes. */ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsize, - struct ntfs_inode *locked_ni) + struct ntfs_inode *locked_ni, + bool pagecache_truncated) { struct ntfs_volume *vol; struct ntfs_attr_search_ctx *ctx; @@ -4306,7 +4355,7 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, goto unlock_runlist; } - ctx = ntfs_attr_get_search_ctx(ni, NULL); + ctx = ntfs_attr_get_search_ctx(base_ni, NULL); if (!ctx) { ntfs_error(vol->sb, "%s: Failed to get search context", __func__); err = -ENOMEM; @@ -4365,8 +4414,8 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, return -ENOMEM; } - err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, CASE_SENSITIVE, - 0, NULL, 0, ctx); + err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, + CASE_SENSITIVE, 0, NULL, 0, ctx); if (err) { if (err == -ENOENT) err = -EIO; @@ -4389,7 +4438,8 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, * 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); + if (!pagecache_truncated) + truncate_inode_pages(VFS_I(ni)->i_mapping, newsize); /* Update data size in the index. */ if (ni->type == AT_DATA && ni->name == AT_UNNAMED) @@ -4450,6 +4500,8 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz struct ntfs_inode *base_ni; struct super_block *sb = ni->vol->sb; size_t new_rl_count; + unsigned long flags; + bool runlist_locked = locked_ni == ni; ntfs_debug("Inode 0x%llx, attr 0x%x, new size %lld old size %lld\n", (unsigned long long)ni->mft_no, ni->type, @@ -4491,10 +4543,19 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz * clusters if there is a change. */ if (ntfs_bytes_to_cluster(vol, ni->allocated_size) < first_free_vcn) { + /* + * The runlist array is replaced below. Readers such as the + * iomap read path hold only the runlist lock, not mrec_lock. + */ + if (runlist_locked) + lockdep_assert_held_write(&ni->runlist.lock); + else + down_write(&ni->runlist.lock); + err = ntfs_attr_map_whole_runlist(ni); if (err) { ntfs_error(sb, "ntfs_attr_map_whole_runlist failed"); - return err; + goto unlock_runlist; } /* @@ -4519,7 +4580,7 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz last + more_entries + 1); if (IS_ERR(rl)) { err = -ENOMEM; - goto put_err_out; + goto unlock_runlist; } alloc_size = ni->allocated_size; @@ -4544,7 +4605,7 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz rl = kmalloc(sizeof(struct runlist_element) * 2, GFP_NOFS); if (!rl) { err = -ENOMEM; - goto put_err_out; + goto unlock_runlist; } rl[0].vcn = ntfs_bytes_to_cluster(vol, ni->allocated_size); @@ -4591,7 +4652,8 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz ntfs_debug("Cluster allocation failed (%lld)", (long long)first_free_vcn - ntfs_bytes_to_cluster(vol, ni->allocated_size)); - return PTR_ERR(rl); + err = PTR_ERR(rl); + goto unlock_runlist; } /* * A contiguous ATTRIBUTE_LIST allocation keeps its mapping @@ -4615,8 +4677,10 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz ni->allocated_size), lcn_seek_from, DATA_ZONE, false, false, false); - if (IS_ERR(rl)) - return PTR_ERR(rl); + if (IS_ERR(rl)) { + err = PTR_ERR(rl); + goto unlock_runlist; + } } } @@ -4628,7 +4692,8 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz ntfs_error(sb, "Run list merge failed"); ntfs_cluster_free_from_rl(vol, rl); kvfree(rl); - return -EIO; + err = -EIO; + goto unlock_runlist; } ni->runlist.rl = rln; ni->runlist.count = new_rl_count; @@ -4636,11 +4701,28 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz /* Prepare to mapping pairs update. */ ni->allocated_size = ntfs_cluster_to_bytes(vol, first_free_vcn); - err = ntfs_attr_update_mapping_pairs_locked( - ni, 0, locked_ni); - if (err) { - ntfs_debug("Mapping pairs update failed"); - goto rollback; + if (ni->type == AT_ATTRIBUTE_LIST && !runlist_locked) { + /* + * Making room for the list's mapping pairs can resize + * this attribute list again through + * ntfs_attrlist_update_locked(), which takes its + * runlist lock. + */ + up_write(&ni->runlist.lock); + err = ntfs_attr_update_mapping_pairs_locked(ni, 0, + locked_ni); + if (err) { + ntfs_debug("Mapping pairs update failed"); + goto rollback; + } + } else { + err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni); + if (err) { + ntfs_debug("Mapping pairs update failed"); + goto rollback_locked; + } + if (!runlist_locked) + up_write(&ni->runlist.lock); } } @@ -4652,8 +4734,8 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz goto rollback; } - err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, CASE_SENSITIVE, - 0, NULL, 0, ctx); + err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, + CASE_SENSITIVE, 0, NULL, 0, ctx); if (err) { if (err == -ENOENT) err = -EIO; @@ -4674,19 +4756,27 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz ntfs_attr_put_search_ctx(ctx); return 0; rollback: + if (!runlist_locked) + down_write(&ni->runlist.lock); +rollback_locked: /* Free allocated clusters. */ err2 = ntfs_cluster_free(ni, ntfs_bytes_to_cluster(vol, org_alloc_size), -1, ctx); if (err2) ntfs_debug("Leaking clusters"); - /* Now, truncate the runlist itself. */ - if (ni != locked_ni) - down_write(&ni->runlist.lock); + /* + * Now, truncate the runlist itself. Restore allocated_size before + * dropping the lock: ntfs_attr_vcn_to_rl() fails a lookup below the + * allocated size that falls past the end of the runlist. + */ err2 = ntfs_rl_truncate_nolock(vol, &ni->runlist, ntfs_bytes_to_cluster(vol, org_alloc_size)); - if (ni != locked_ni) - up_write(&ni->runlist.lock); + if (!err2) { + write_lock_irqsave(&ni->size_lock, flags); + ni->allocated_size = org_alloc_size; + write_unlock_irqrestore(&ni->size_lock, flags); + } if (err2) { /* * Failed to truncate the runlist, so just throw it away, it @@ -4696,15 +4786,9 @@ rollback: ni->runlist.rl = NULL; ntfs_error(sb, "Couldn't truncate runlist. Rollback failed"); } else { - /* Prepare to mapping pairs update. */ - ni->allocated_size = org_alloc_size; /* Restore mapping pairs. */ - if (ni != locked_ni) - down_read(&ni->runlist.lock); - if (__ntfs_attr_update_mapping_pairs(ni, 0, locked_ni, true)) + if (__ntfs_attr_update_mapping_pairs(ni, 0, ni, true)) ntfs_error(sb, "Failed to restore old mapping pairs"); - if (ni != locked_ni) - up_read(&ni->runlist.lock); if (NInoSparse(ni) || NInoCompressed(ni)) { ni->itype.compressed.size = org_compressed_size; @@ -4712,6 +4796,8 @@ rollback: } else VFS_I(base_ni)->i_blocks = ni->allocated_size >> 9; } + if (!runlist_locked) + up_write(&ni->runlist.lock); if (ctx) ntfs_attr_put_search_ctx(ctx); return err; @@ -4719,6 +4805,10 @@ put_err_out: if (ctx) ntfs_attr_put_search_ctx(ctx); return err; +unlock_runlist: + if (!runlist_locked) + up_write(&ni->runlist.lock); + return err; } /* @@ -4757,7 +4847,7 @@ attr_resize_again: } err = ntfs_attr_lookup(attr_ni->type, attr_ni->name, attr_ni->name_len, - 0, 0, NULL, 0, ctx); + CASE_SENSITIVE, 0, NULL, 0, ctx); if (err) { ntfs_error(sb, "ntfs_attr_lookup failed"); goto put_err_out; @@ -4991,7 +5081,7 @@ int __ntfs_attr_truncate_vfs(struct ntfs_inode *ni, const s64 newsize, up_write(&ni->runlist.lock); } else err = ntfs_non_resident_attr_shrink( - ni, newsize, NULL); + ni, newsize, NULL, true); } else err = ntfs_resident_attr_resize(ni, newsize, 0, NVolDisableSparse(ni->vol) ? @@ -5104,9 +5194,11 @@ int ntfs_attr_truncate_i_locked(struct ntfs_inode *ni, const s64 newsize, ni, newsize, 0, holes, locked_ni); else err = ntfs_non_resident_attr_shrink( - ni, newsize, locked_ni); + ni, newsize, locked_ni, false); } else err = ntfs_resident_attr_resize(ni, newsize, 0, holes); + if (!err && NInoAttr(ni)) + i_size_write(VFS_I(ni), newsize); ntfs_debug("Return status %d\n", err); return err; } @@ -5131,6 +5223,7 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start, s64 *lcn_count, s64 max_clu_count, bool *balloc, bool update_mp, bool skip_holes) { + struct ntfs_inode *base_ni = ntfs_base_inode(ni); struct ntfs_volume *vol = ni->vol; struct ntfs_attr_search_ctx *ctx; struct runlist_element *rl, *rlc; @@ -5144,10 +5237,7 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start, if (err) return err; - if (NInoAttr(ni)) - ctx = ntfs_attr_get_search_ctx(ni->ext.base_ntfs_ino, NULL); - else - ctx = ntfs_attr_get_search_ctx(ni, NULL); + ctx = ntfs_attr_get_search_ctx(base_ni, NULL); if (!ctx) { ntfs_error(vol->sb, "%s: Failed to get search context", __func__); return -ENOMEM; @@ -5297,7 +5387,7 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start, } else { VFS_I(ni)->i_blocks += clu_count << (vol->cluster_size_bits - 9); NInoSetRunlistDirty(ni); - mark_mft_record_dirty(ni); + mark_mft_record_dirty(base_ni); } *lcn_start = lcn; @@ -5323,10 +5413,7 @@ int ntfs_attr_rm(struct ntfs_inode *ni) struct ntfs_inode *base_ni; struct super_block *sb = ni->vol->sb; - if (NInoAttr(ni)) - base_ni = ni->ext.base_ntfs_ino; - else - base_ni = ni; + base_ni = ntfs_base_inode(ni); ntfs_debug("Entering for inode 0x%llx, attr 0x%x.\n", (long long) ni->mft_no, ni->type); @@ -5338,7 +5425,7 @@ int ntfs_attr_rm(struct ntfs_inode *ni) err = ntfs_attr_map_whole_runlist(ni); if (err) return err; - ctx = ntfs_attr_get_search_ctx(ni, NULL); + ctx = ntfs_attr_get_search_ctx(base_ni, NULL); if (!ctx) { ntfs_error(sb, "%s: Failed to get search context", __func__); return -ENOMEM; @@ -5380,6 +5467,7 @@ int ntfs_attr_exist(struct ntfs_inode *ni, const __le32 type, __le16 *name, u32 name_len) { struct ntfs_attr_search_ctx *ctx; + u32 ic; int ret; ntfs_debug("Entering\n"); @@ -5391,39 +5479,54 @@ int ntfs_attr_exist(struct ntfs_inode *ni, const __le32 type, __le16 *name, return 0; } - ret = ntfs_attr_lookup(type, name, name_len, CASE_SENSITIVE, + ic = type == AT_DATA && name_len ? IGNORE_CASE : CASE_SENSITIVE; + ret = ntfs_attr_lookup(type, name, name_len, ic, 0, NULL, 0, ctx); ntfs_attr_put_search_ctx(ctx); return !ret; } +/* + * If @attr_vi is non-NULL, the attribute inode reference is returned to the + * caller, which must release it after dropping ni->mrec_lock. + */ int ntfs_attr_remove(struct ntfs_inode *ni, const __le32 type, __le16 *name, - u32 name_len) + u32 name_len, struct inode **attr_vi) { int err; - struct inode *attr_vi; + struct inode *vi; struct ntfs_inode *attr_ni; ntfs_debug("Entering\n"); + if (attr_vi) + *attr_vi = NULL; if (!ni) return -EINVAL; - attr_vi = ntfs_attr_iget(VFS_I(ni), type, name, name_len); - if (IS_ERR(attr_vi)) { - err = PTR_ERR(attr_vi); + vi = ntfs_attr_iget(VFS_I(ni), type, name, name_len); + if (IS_ERR(vi)) { + err = PTR_ERR(vi); ntfs_error(ni->vol->sb, "Failed to open attribute 0x%02x of inode 0x%llx", type, (unsigned long long)ni->mft_no); return err; } - attr_ni = NTFS_I(attr_vi); + attr_ni = NTFS_I(vi); err = ntfs_attr_rm(attr_ni); - if (err) + if (err) { ntfs_error(ni->vol->sb, "Failed to remove attribute 0x%02x of inode 0x%llx", type, (unsigned long long)ni->mft_no); - iput(attr_vi); + } else { + NInoClearDirty(attr_ni); + clear_nlink(vi); + remove_inode_hash(vi); + } + if (attr_vi) + *attr_vi = vi; + else + iput(vi); return err; } @@ -5498,13 +5601,14 @@ err_exit: int ntfs_non_resident_attr_insert_range(struct ntfs_inode *ni, s64 start_vcn, s64 len) { + struct ntfs_inode *base_ni = ntfs_base_inode(ni); struct ntfs_volume *vol = ni->vol; struct runlist_element *hole_rl, *rl; struct ntfs_attr_search_ctx *ctx; int ret; size_t new_rl_count; - if (NInoAttr(ni) || ni->type != AT_DATA) + if (ni->type != AT_DATA) return -EOPNOTSUPP; if (start_vcn > ntfs_bytes_to_cluster(vol, ni->allocated_size)) return -EINVAL; @@ -5553,14 +5657,14 @@ int ntfs_non_resident_attr_insert_range(struct ntfs_inode *ni, s64 start_vcn, s6 if (ret) return ret; - ctx = ntfs_attr_get_search_ctx(ni, NULL); + ctx = ntfs_attr_get_search_ctx(base_ni, NULL); if (!ctx) { ret = -ENOMEM; return ret; } - ret = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, CASE_SENSITIVE, - 0, NULL, 0, ctx); + ret = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, + CASE_SENSITIVE, 0, NULL, 0, ctx); if (ret) { ntfs_attr_put_search_ctx(ctx); return ret; @@ -5577,6 +5681,7 @@ int ntfs_non_resident_attr_insert_range(struct ntfs_inode *ni, s64 start_vcn, s6 int ntfs_non_resident_attr_collapse_range(struct ntfs_inode *ni, s64 start_vcn, s64 len) { + struct ntfs_inode *base_ni = ntfs_base_inode(ni); struct ntfs_volume *vol = ni->vol; struct runlist_element *punch_rl, *rl; struct ntfs_attr_search_ctx *ctx = NULL; @@ -5585,7 +5690,7 @@ int ntfs_non_resident_attr_collapse_range(struct ntfs_inode *ni, s64 start_vcn, int ret; size_t new_rl_cnt; - if (NInoAttr(ni) || ni->type != AT_DATA) + if (ni->type != AT_DATA) return -EOPNOTSUPP; end_vcn = ntfs_bytes_to_cluster(vol, ni->allocated_size); @@ -5641,14 +5746,14 @@ int ntfs_non_resident_attr_collapse_range(struct ntfs_inode *ni, s64 start_vcn, } up_write(&ni->runlist.lock); - ctx = ntfs_attr_get_search_ctx(ni, NULL); + ctx = ntfs_attr_get_search_ctx(base_ni, NULL); if (!ctx) { ret = -ENOMEM; goto out_rl; } - ret = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, CASE_SENSITIVE, - 0, NULL, 0, ctx); + ret = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, + CASE_SENSITIVE, 0, NULL, 0, ctx); if (ret) goto out_ctx; @@ -5666,12 +5771,13 @@ out_ctx: ntfs_attr_put_search_ctx(ctx); out_rl: kvfree(punch_rl); - mark_mft_record_dirty(ni); + mark_mft_record_dirty(base_ni); return ret; } int ntfs_non_resident_attr_punch_hole(struct ntfs_inode *ni, s64 start_vcn, s64 len) { + struct ntfs_inode *base_ni = ntfs_base_inode(ni); struct ntfs_volume *vol = ni->vol; struct runlist_element *punch_rl, *rl; s64 end_vcn; @@ -5679,7 +5785,7 @@ int ntfs_non_resident_attr_punch_hole(struct ntfs_inode *ni, s64 start_vcn, s64 int ret; size_t new_rl_count; - if (NInoAttr(ni) || ni->type != AT_DATA) + if (ni->type != AT_DATA) return -EOPNOTSUPP; end_vcn = ntfs_bytes_to_cluster(vol, ni->allocated_size); @@ -5723,12 +5829,13 @@ int ntfs_non_resident_attr_punch_hole(struct ntfs_inode *ni, s64 start_vcn, s64 ntfs_error(vol->sb, "Freeing of clusters failed"); kvfree(punch_rl); - mark_mft_record_dirty(ni); + mark_mft_record_dirty(base_ni); return ret; } int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bool keep_size) { + struct ntfs_inode *base_ni = ntfs_base_inode(ni); struct ntfs_volume *vol = ni->vol; struct mft_record *mrec; struct ntfs_attr_search_ctx *ctx; @@ -5740,7 +5847,7 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo struct runlist_element *rl; bool balloc; - if (NInoAttr(ni) || ni->type != AT_DATA) + if (ni->type != AT_DATA) return -EINVAL; if (NInoNonResident(ni) && !NInoFullyMapped(ni)) { @@ -5751,20 +5858,21 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo return err; } - mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); - mrec = map_mft_record(ni); + mutex_lock_nested(&base_ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); + mrec = map_mft_record(base_ni); if (IS_ERR(mrec)) { - mutex_unlock(&ni->mrec_lock); + mutex_unlock(&base_ni->mrec_lock); return PTR_ERR(mrec); } - ctx = ntfs_attr_get_search_ctx(ni, mrec); + ctx = ntfs_attr_get_search_ctx(base_ni, mrec); if (!ctx) { err = -ENOMEM; goto out_unmap; } - err = ntfs_attr_lookup(AT_DATA, AT_UNNAMED, 0, 0, 0, NULL, 0, ctx); + err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, + CASE_SENSITIVE, 0, NULL, 0, ctx); if (err) { err = -EIO; goto out_unmap; @@ -5777,25 +5885,28 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo goto out_unmap; if (keep_size) { ntfs_attr_reinit_search_ctx(ctx); - err = ntfs_attr_lookup(AT_DATA, AT_UNNAMED, 0, 0, 0, NULL, 0, ctx); + err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, + CASE_SENSITIVE, 0, NULL, 0, + ctx); if (err) { err = -EIO; goto out_unmap; } ni->data_size = old_data_size; + i_size_write(VFS_I(ni), old_data_size); if (NInoNonResident(ni)) ctx->attr->data.non_resident.data_size = cpu_to_le64(old_data_size); else ctx->attr->data.resident.value_length = cpu_to_le32((u32)old_data_size); - mark_mft_record_dirty(ni); + mark_mft_record_dirty(base_ni); } } ntfs_attr_put_search_ctx(ctx); - unmap_mft_record(ni); - mutex_unlock(&ni->mrec_lock); + unmap_mft_record(base_ni); + mutex_unlock(&base_ni->mrec_lock); if (!NInoNonResident(ni)) goto out; @@ -5837,12 +5948,13 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo } while (try_alloc_cnt > 0) { - mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); + mutex_lock_nested(&base_ni->mrec_lock, + NTFS_INODE_MUTEX_NORMAL); down_write(&ni->runlist.lock); err = ntfs_attr_map_cluster(ni, vcn, &lcn, &alloc_cnt, try_alloc_cnt, &balloc, false, false); up_write(&ni->runlist.lock); - mutex_unlock(&ni->mrec_lock); + mutex_unlock(&base_ni->mrec_lock); if (err) goto out; @@ -5855,7 +5967,7 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo goto out; } - if (signal_pending(current)) + if (fatal_signal_pending(current)) goto signal_out; vcn += alloc_cnt; @@ -5870,13 +5982,13 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo /* allocate clusters outside of initialized_size */ try_alloc_cnt = vcn_end - vcn; while (try_alloc_cnt > 0) { - mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); + mutex_lock_nested(&base_ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); down_write(&ni->runlist.lock); err = ntfs_attr_map_cluster(ni, vcn, &lcn, &alloc_cnt, try_alloc_cnt, &balloc, false, false); up_write(&ni->runlist.lock); - mutex_unlock(&ni->mrec_lock); - if (err || signal_pending(current)) + mutex_unlock(&base_ni->mrec_lock); + if (err || fatal_signal_pending(current)) goto signal_out; vcn += alloc_cnt; @@ -5885,7 +5997,7 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo } if (NInoRunlistDirty(ni)) { - mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); + mutex_lock_nested(&base_ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); down_write(&ni->runlist.lock); err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni); if (err) @@ -5893,14 +6005,14 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo else NInoClearRunlistDirty(ni); up_write(&ni->runlist.lock); - mutex_unlock(&ni->mrec_lock); + mutex_unlock(&base_ni->mrec_lock); } return err; out_unmap: if (ctx) ntfs_attr_put_search_ctx(ctx); - unmap_mft_record(ni); - mutex_unlock(&ni->mrec_lock); + unmap_mft_record(base_ni); + mutex_unlock(&base_ni->mrec_lock); out: return err >= 0 ? 0 : err; signal_out: diff --git a/fs/ntfs/attrib.h b/fs/ntfs/attrib.h index 6b4fa9f57640..bee91c9f6f81 100644 --- a/fs/ntfs/attrib.h +++ b/fs/ntfs/attrib.h @@ -124,7 +124,7 @@ int ntfs_attr_rm(struct ntfs_inode *ni); int ntfs_attr_exist(struct ntfs_inode *ni, const __le32 type, __le16 *name, u32 name_len); int ntfs_attr_remove(struct ntfs_inode *ni, const __le32 type, __le16 *name, - u32 name_len); + u32 name_len, struct inode **attr_vi); int ntfs_attr_record_rm(struct ntfs_attr_search_ctx *ctx); int ntfs_attr_record_move_to(struct ntfs_attr_search_ctx *ctx, struct ntfs_inode *ni); int ntfs_attr_add(struct ntfs_inode *ni, __le32 type, diff --git a/fs/ntfs/attrlist.c b/fs/ntfs/attrlist.c index bb191953dcb1..8291c4c95b37 100644 --- a/fs/ntfs/attrlist.c +++ b/fs/ntfs/attrlist.c @@ -14,8 +14,6 @@ #include "attrlist.h" #include "lcnalloc.h" -#define NTFS_MAX_ATTR_LIST_SIZE (256 * 1024) - /* * ntfs_attrlist_need - check whether inode need attribute list * @ni: opened ntfs inode for which perform check @@ -170,14 +168,18 @@ static int ntfs_attrlist_repack(struct inode *attr_vi, return 0; restore_old_runlist: + /* + * Restore allocated_size before dropping the runlist lock: + * ntfs_attr_vcn_to_rl() fails a lookup below the allocated size that + * falls past the end of the runlist. + */ down_write(&attr_ni->runlist.lock); attr_ni->runlist.rl = old_rl; attr_ni->runlist.count = old_rl_count; - up_write(&attr_ni->runlist.lock); - write_lock_irqsave(&attr_ni->size_lock, flags); attr_ni->allocated_size = old_alloc_size; write_unlock_irqrestore(&attr_ni->size_lock, flags); + up_write(&attr_ni->runlist.lock); restore_err = ntfs_attr_update_mapping_pairs_locked( attr_ni, 0, locked_ni); @@ -254,6 +256,9 @@ int ntfs_attrlist_update_locked(struct ntfs_inode *base_ni, return err; } + /* ntfs_attrlist_repack() below reads the list up to i_size. */ + i_size_write(attr_vi, base_ni->attr_list_size); + /* * Reserve the maximum legal list size while the MFT metadata area is * still easy to allocate contiguously. This prevents a later list entry @@ -281,8 +286,6 @@ int ntfs_attrlist_update_locked(struct ntfs_inode *base_ni, } } - i_size_write(attr_vi, base_ni->attr_list_size); - if (NInoNonResident(attr_ni) && !NInoAttrListNonResident(base_ni)) NInoSetAttrListNonResident(base_ni); @@ -335,8 +338,8 @@ int ntfs_attrlist_entry_add(struct ntfs_inode *ni, struct attr_record *attr) ni_mrec = map_mft_record(ni); if (IS_ERR(ni_mrec)) { - ntfs_debug("Invalid arguments.\n"); - return -EIO; + ntfs_debug("Failed to map mft record.\n"); + return PTR_ERR(ni_mrec); } mref = MK_LE_MREF(ni->mft_no, le16_to_cpu(ni_mrec->sequence_number)); @@ -371,7 +374,8 @@ int ntfs_attrlist_entry_add(struct ntfs_inode *ni, struct attr_record *attr) err = ntfs_attr_lookup(attr->type, (attr->name_length) ? (__le16 *) ((u8 *)attr + le16_to_cpu(attr->name_offset)) : - AT_UNNAMED, attr->name_length, CASE_SENSITIVE, + AT_UNNAMED, attr->name_length, + CASE_SENSITIVE, le64_to_cpu(lowest_vcn), (attr->non_resident) ? NULL : ((u8 *)attr + le16_to_cpu(attr->data.resident.value_offset)), (attr->non_resident) ? diff --git a/fs/ntfs/bitmap.c b/fs/ntfs/bitmap.c index 5a4457551306..af86fb1c199b 100644 --- a/fs/ntfs/bitmap.c +++ b/fs/ntfs/bitmap.c @@ -12,6 +12,59 @@ #include "bitmap.h" #include "ntfs.h" +/* + * Check a $BadClus run against $Bitmap and optionally reserve its free bits. + * This is only used during mount, before the free-space scan starts. The + * scan will account for the repaired bits, so do not use the regular bitmap + * setters, which wait for that scan and assume every bit changes state. + * @free_bits counts only bits that were free, including on read-only mounts. + */ +int ntfs_bitmap_check_used(struct ntfs_volume *vol, s64 start, s64 count, + bool repair, s64 *free_bits) +{ + struct address_space *mapping = vol->lcnbmp_ino->i_mapping; + const unsigned int bits_per_page = PAGE_SIZE * BITS_PER_BYTE; + struct folio *folio; + u8 *bitmap; + unsigned int bit, end, bits; + s64 nr_free; + + if (start < 0 || count <= 0 || start >= vol->nr_clusters || + count > vol->nr_clusters - start || NVolFreeClusterKnown(vol)) + return -EINVAL; + if (repair && sb_rdonly(vol->sb)) + return -EROFS; + + while (count) { + folio = read_mapping_folio(mapping, + start >> (PAGE_SHIFT + 3), NULL); + if (IS_ERR(folio)) + return PTR_ERR(folio); + + folio_lock(folio); + bitmap = kmap_local_folio(folio, 0); + bit = start & (bits_per_page - 1); + bits = min_t(s64, count, bits_per_page - bit); + end = bit + bits; + nr_free = 0; + for (bit = find_next_zero_bit_le(bitmap, end, bit); bit < end; + bit = find_next_zero_bit_le(bitmap, end, bit + 1)) { + nr_free++; + if (repair) + __set_bit_le(bit, bitmap); + } + kunmap_local(bitmap); + if (repair && nr_free) + folio_mark_dirty(folio); + folio_unlock(folio); + folio_put(folio); + *free_bits += nr_free; + start += bits; + count -= bits; + } + return 0; +} + int ntfs_trim_fs(struct ntfs_volume *vol, struct fstrim_range *range) { size_t buf_clusters; @@ -20,7 +73,7 @@ int ntfs_trim_fs(struct ntfs_volume *vol, struct fstrim_range *range) struct folio *folio; unsigned long *bitmap; char *kaddr; - u64 end, trimmed = 0, start_buf, end_buf, end_cluster; + u64 end, trimmed = 0, start_buf, end_buf, end_cluster, page_cluster; u64 start_cluster = ntfs_bytes_to_cluster(vol, range->start); u32 dq = bdev_discard_granularity(vol->sb->s_bdev); int ret = 0; @@ -45,8 +98,8 @@ int ntfs_trim_fs(struct ntfs_volume *vol, struct fstrim_range *range) return -ENOMEM; buf_clusters = PAGE_SIZE * 8; - start_index = start_cluster >> 15; - end_index = (end_cluster + buf_clusters - 1) >> 15; + start_index = start_cluster / buf_clusters; + end_index = DIV_ROUND_UP_ULL(end_cluster, buf_clusters); for (index = start_index; index < end_index; index++) { folio = ntfs_get_locked_folio(vol->lcnbmp_ino->i_mapping, @@ -59,19 +112,20 @@ int ntfs_trim_fs(struct ntfs_volume *vol, struct fstrim_range *range) kaddr = kmap_local_folio(folio, 0); bitmap = (unsigned long *)kaddr; - start_buf = max_t(u64, index * buf_clusters, start_cluster); - end_buf = min_t(u64, (index + 1) * buf_clusters, end_cluster); + page_cluster = (u64)index * buf_clusters; + start_buf = max_t(u64, page_cluster, start_cluster); + end_buf = min_t(u64, page_cluster + buf_clusters, end_cluster); end = start_buf; while (end < end_buf) { u64 aligned_start, aligned_end, aligned_count; - u64 start = find_next_zero_bit(bitmap, end_buf - start_buf, - end - start_buf) + start_buf; + u64 start = find_next_zero_bit(bitmap, end_buf - page_cluster, + end - page_cluster) + page_cluster; if (start >= end_buf) break; - end = find_next_bit(bitmap, end_buf - start_buf, - start - start_buf) + start_buf; + end = find_next_bit(bitmap, end_buf - page_cluster, + start - page_cluster) + page_cluster; aligned_start = ALIGN(ntfs_cluster_to_bytes(vol, start), dq); aligned_end = ALIGN_DOWN(ntfs_cluster_to_bytes(vol, end), dq); diff --git a/fs/ntfs/bitmap.h b/fs/ntfs/bitmap.h index 31177fe61da6..eccd0cf0f852 100644 --- a/fs/ntfs/bitmap.h +++ b/fs/ntfs/bitmap.h @@ -13,6 +13,8 @@ #include "volume.h" int ntfs_trim_fs(struct ntfs_volume *vol, struct fstrim_range *range); +int ntfs_bitmap_check_used(struct ntfs_volume *vol, s64 start, s64 count, + bool repair, s64 *free_bits); int __ntfs_bitmap_set_bits_in_run(struct inode *vi, const s64 start_bit, const s64 count, const u8 value, const bool is_rollback); diff --git a/fs/ntfs/collate.c b/fs/ntfs/collate.c index 77e34038902d..5417288c5825 100644 --- a/fs/ntfs/collate.c +++ b/fs/ntfs/collate.c @@ -73,7 +73,7 @@ static int ntfs_collate_ntofs_ulongs(struct ntfs_volume *vol, if (data1_len != data2_len || data1_len & 3) { ntfs_error(vol->sb, "data1_len or data2_len not valid\n"); - return -1; + return -EINVAL; } len = data1_len; @@ -100,11 +100,11 @@ static int ntfs_collate_file_name(struct ntfs_volume *vol, { int rc; - rc = ntfs_file_compare_values(data1, data2, -EINVAL, - IGNORE_CASE, vol->upcase, vol->upcase_len); + rc = ntfs_file_compare_values(data1, data2, + true, IGNORE_CASE, vol->upcase, vol->upcase_len); if (!rc) rc = ntfs_file_compare_values(data1, data2, - -EINVAL, CASE_SENSITIVE, vol->upcase, vol->upcase_len); + true, CASE_SENSITIVE, vol->upcase, vol->upcase_len); return rc; } diff --git a/fs/ntfs/compress.c b/fs/ntfs/compress.c index 075b57fc1de6..82c7c7d29073 100644 --- a/fs/ntfs/compress.c +++ b/fs/ntfs/compress.c @@ -182,8 +182,8 @@ static int ntfs_decompress(struct page *dest_pages[], int completed_pages[], 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). */ - u16 do_sb_start; /* @dest_ofs when starting this sub-block. */ - u16 do_sb_end; /* @dest_ofs of end of this sb (do_sb_start + NTFS_SB_SIZE). */ + int do_sb_start; /* @dest_ofs when starting this sub-block. */ + int do_sb_end; /* @dest_ofs of end of this sb (do_sb_start + NTFS_SB_SIZE). */ /* Variables for tag and token parsing. */ u8 tag; /* Current tag. */ diff --git a/fs/ntfs/dir.c b/fs/ntfs/dir.c index df60138f9b2d..fce10baa2b93 100644 --- a/fs/ntfs/dir.c +++ b/fs/ntfs/dir.c @@ -8,6 +8,7 @@ */ #include <linux/blkdev.h> +#include <linux/string.h> #include "dir.h" #include "mft.h" @@ -238,7 +239,7 @@ found_it: */ rc = ntfs_collate_names(uname, uname_len, (__le16 *)&ie->key.file_name.file_name, - ie->key.file_name.file_name_length, 1, + ie->key.file_name.file_name_length, false, IGNORE_CASE, vol->upcase, vol->upcase_len); /* * If uname collates before the name of the current entry, there @@ -257,7 +258,7 @@ found_it: */ rc = ntfs_collate_names(uname, uname_len, (__le16 *)&ie->key.file_name.file_name, - ie->key.file_name.file_name_length, 1, + ie->key.file_name.file_name_length, false, CASE_SENSITIVE, vol->upcase, vol->upcase_len); if (rc == -1) break; @@ -331,7 +332,6 @@ descend_into_child_node: } memcpy_from_folio(kaddr, folio, 0, PAGE_SIZE); - post_read_mst_fixup((struct ntfs_record *)kaddr, PAGE_SIZE); folio_unlock(folio); folio_put(folio); fast_descend_into_child_node: @@ -352,6 +352,14 @@ fast_descend_into_child_node: vcn, dir_ni->mft_no); goto unm_err_out; } + err = post_read_mst_fixup((struct ntfs_record *)ia, + dir_ni->itype.index.block_size); + if (err) { + ntfs_error(sb, + "MST fixup failed for index block vcn %lld in directory inode 0x%llx.", + vcn, dir_ni->mft_no); + goto unm_err_out; + } err = ntfs_index_block_inconsistent(vol, ia, dir_ni->itype.index.block_size, vcn, COLLATION_FILE_NAME, @@ -474,7 +482,7 @@ found_it2: */ rc = ntfs_collate_names(uname, uname_len, (__le16 *)&ie->key.file_name.file_name, - ie->key.file_name.file_name_length, 1, + ie->key.file_name.file_name_length, false, IGNORE_CASE, vol->upcase, vol->upcase_len); /* * If uname collates before the name of the current entry, there @@ -493,7 +501,7 @@ found_it2: */ rc = ntfs_collate_names(uname, uname_len, (__le16 *)&ie->key.file_name.file_name, - ie->key.file_name.file_name_length, 1, + ie->key.file_name.file_name_length, false, CASE_SENSITIVE, vol->upcase, vol->upcase_len); if (rc == -1) break; @@ -617,7 +625,6 @@ static inline int ntfs_filldir(struct ntfs_volume *vol, ntfs_debug("Skipping hidden file."); return 0; } - name_len = ntfs_ucstonls(vol, (__le16 *)&ie->key.file_name.file_name, ie->key.file_name.file_name_length, &name, NTFS_MAX_NAME_LEN * NLS_MAX_CHARSET_SIZE + 1); @@ -626,13 +633,16 @@ static inline int ntfs_filldir(struct ntfs_volume *vol, (long long)MREF_LE(ie->data.dir.indexed_file)); return 0; } - + if (NVolStreamsWindows(vol) && strchr((char *)name, ':')) { + ntfs_debug("Skipping file name containing a stream separator."); + return 0; + } mref = MREF_LE(ie->data.dir.indexed_file); - if (ie->key.file_name.file_attributes & + if (ie->key.file_name.file_attributes & FILE_ATTR_REPARSE_POINT) + dt_type = ntfs_reparse_tag_dt_types(vol, mref); + else if (ie->key.file_name.file_attributes & FILE_ATTR_DUP_FILE_NAME_INDEX_PRESENT) dt_type = DT_DIR; - else if (ie->key.file_name.file_attributes & FILE_ATTR_REPARSE_POINT) - dt_type = ntfs_reparse_tag_dt_types(vol, mref); else dt_type = DT_REG; @@ -1182,6 +1192,8 @@ static int ntfs_dir_fsync(struct file *filp, loff_t start, loff_t end, err = sync_blockdev(vi->i_sb->s_bdev); if (unlikely(err && !ret)) ret = err; + if (!ret) + ret = blkdev_issue_flush(vi->i_sb->s_bdev); if (likely(!ret)) ntfs_debug("Done."); else diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c index ddc201e3e2aa..fa5c5c625751 100644 --- a/fs/ntfs/ea.c +++ b/fs/ntfs/ea.c @@ -32,12 +32,12 @@ static int ntfs_write_ea(struct ntfs_inode *ni, __le32 type, char *value, s64 ea written = ntfs_inode_attr_pwrite(ea_vi, ea_off, ea_size, value, false); if (written != ea_size) - err = -EIO; + err = written < 0 ? written : -EIO; else { struct ntfs_inode *ea_ni = NTFS_I(ea_vi); if (need_truncate && ea_ni->data_size > ea_off + ea_size) - ntfs_attr_truncate(ea_ni, ea_off + ea_size); + err = ntfs_attr_truncate(ea_ni, ea_off + ea_size); mark_mft_record_dirty(ni); } @@ -53,10 +53,13 @@ static int ntfs_ea_lookup(char *ea_buf, s64 ea_buf_size, const char *name, loff_t offset, p_ea_size; unsigned int next; + if (!ea_buf_size) + return -ENOENT; + offset = 0; do { if (ea_buf_size - offset < sizeof(struct ea_attr)) - break; + return -EUCLEAN; p_ea = (const struct ea_attr *)&ea_buf[offset]; next = le32_to_cpu(p_ea->next_entry_offset); @@ -64,31 +67,31 @@ static int ntfs_ea_lookup(char *ea_buf, s64 ea_buf_size, const char *name, if (p_ea_size < sizeof(struct ea_attr) || offset + p_ea_size > ea_buf_size) - break; + return -EUCLEAN; if ((s64)p_ea->ea_name_length + 1 > p_ea_size - offsetof(struct ea_attr, ea_name)) - break; + return -EUCLEAN; actual_size = ALIGN(struct_size(p_ea, ea_name, 1 + p_ea->ea_name_length + le16_to_cpu(p_ea->ea_value_length)), 4); - if (actual_size > p_ea_size) - break; + if (actual_size > p_ea_size || + p_ea->ea_name[p_ea->ea_name_length]) + return -EUCLEAN; - if (p_ea->ea_name_length == name_len && + if (name && p_ea->ea_name_length == name_len && !memcmp(p_ea->ea_name, name, name_len)) { *ea_offset = offset; - *ea_size = next ? next : actual_size; + *ea_size = p_ea_size; if (ea_buf_size < *ea_offset + *ea_size) - goto out; + return -EUCLEAN; return 0; } offset += next; } while (next > 0 && offset < ea_buf_size); -out: return -ENOENT; } @@ -126,7 +129,7 @@ static int ntfs_get_ea(struct inode *inode, const char *name, size_t name_len, return PTR_ERR(p_ea_info); if (ea_info_size != sizeof(struct ea_information)) { kvfree(p_ea_info); - return -EIO; + return -EUCLEAN; } ea_info_qlen = le32_to_cpu(p_ea_info->ea_query_length); @@ -137,7 +140,7 @@ static int ntfs_get_ea(struct inode *inode, const char *name, size_t name_len, return PTR_ERR(ea_buf); if (ea_info_qlen > all_ea_size) { - err = -EIO; + err = -EUCLEAN; goto free_ea_buf; } @@ -162,7 +165,8 @@ static int ntfs_get_ea(struct inode *inode, const char *name, size_t name_len, return ea_value_len; } - err = -ENODATA; + if (err == -ENOENT) + err = -ENODATA; free_ea_buf: kvfree(ea_buf); return err; @@ -177,247 +181,214 @@ static inline int ea_packed_size(const struct ea_attr *p_ea) return 5 + p_ea->ea_name_length + le16_to_cpu(p_ea->ea_value_length); } +/* Unlike ntfs_attr_exist(), preserve lookup errors. */ +static int ntfs_ea_attr_lookup(struct ntfs_inode *ni, __le32 type) +{ + struct ntfs_attr_search_ctx *ctx; + struct mft_record *mrec; + int err; + + mrec = map_mft_record(ni); + if (IS_ERR(mrec)) + return PTR_ERR(mrec); + ctx = ntfs_attr_get_search_ctx(ni, mrec); + if (!ctx) { + err = -ENOMEM; + goto out; + } + err = ntfs_attr_lookup(type, AT_UNNAMED, 0, CASE_SENSITIVE, + 0, NULL, 0, ctx); + ntfs_attr_put_search_ctx(ctx); +out: + unmap_mft_record(ni); + return err; +} + +/* A NULL value means that the attribute should not exist. */ +static int ntfs_ea_update_attr(struct ntfs_inode *ni, __le32 type, + char *value, s64 size) +{ + int err = ntfs_ea_attr_lookup(ni, type); + + if (err && err != -ENOENT) + return err; + if (!value) + return err == -ENOENT ? 0 : + ntfs_attr_remove(ni, type, AT_UNNAMED, 0, NULL); + if (err == -ENOENT) + return ntfs_attr_add(ni, type, AT_UNNAMED, 0, value, size); + return ntfs_write_ea(ni, type, value, 0, size, true); +} + /* - * Set a new EA, and set EA_INFORMATION accordingly - * - * This is roughly the same as ZwSetEaFile() on Windows, however - * the "offset to next" of the last EA should not be cleared. - * - * Consistency of the new EA is first checked. - * - * EA_INFORMATION is set first, and it is restored to its former - * state if setting EA fails. + * Prepare the new EA list before changing either NTFS attribute. Keep the + * original values until both updates succeed, so a failed update can be + * rolled back. This does not provide atomicity across a crash or power loss. */ static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len, const void *value, size_t val_size, int flags, __le16 *packed_ea_size) { struct ntfs_inode *ni = NTFS_I(inode); - struct ea_information *p_ea_info = NULL; - int ea_packed, err = 0; - 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; - - if (name_len > 255) + struct ea_information *old_info = NULL, info = {}; + char *old_ea = NULL, *ea = NULL; + struct ea_attr *entry; + s64 old_size = 0, info_size, offset = 0, entry_size = 0; + u32 size = 0, new_size, next; + size_t add_size; + int err, info_err, ea_err, packed = 0, needed = 0; + bool exists, remove = !value; + + if (name_len > U8_MAX) return -ENAMETOOLONG; + if (val_size > U16_MAX) + return -E2BIG; - if (ntfs_attr_exist(ni, AT_EA_INFORMATION, AT_UNNAMED, 0)) { - p_ea_info = ntfs_attr_readall(ni, AT_EA_INFORMATION, NULL, 0, - &ea_info_size); - if (IS_ERR(p_ea_info)) { - err = PTR_ERR(p_ea_info); - p_ea_info = NULL; - goto out; - } - if (ea_info_size != sizeof(struct ea_information)) { - err = -EIO; + err = ntfs_ea_attr_lookup(ni, AT_EA_INFORMATION); + if (err && err != -ENOENT) + return err; + exists = !err; + err = ntfs_ea_attr_lookup(ni, AT_EA); + if (err && err != -ENOENT) + return err; + /* Do not silently discard an orphaned attribute. */ + if (exists != !err) + return -EUCLEAN; + + if (exists) { + old_info = ntfs_attr_readall(ni, AT_EA_INFORMATION, NULL, 0, + &info_size); + if (IS_ERR(old_info)) + return PTR_ERR(old_info); + if (info_size != sizeof(*old_info)) { + err = -EUCLEAN; goto out; } - - ea_buf = ntfs_attr_readall(ni, AT_EA, NULL, 0, &all_ea_size); - if (IS_ERR(ea_buf)) { - err = PTR_ERR(ea_buf); - ea_buf = NULL; + old_ea = ntfs_attr_readall(ni, AT_EA, NULL, 0, &old_size); + if (IS_ERR(old_ea)) { + err = PTR_ERR(old_ea); + old_ea = NULL; goto out; } - if (!ea_buf) { - ea_info_qsize = 0; - kvfree(p_ea_info); - goto create_ea_info; - } - - ea_info_qsize = le32_to_cpu(p_ea_info->ea_query_length); - } else { -create_ea_info: - p_ea_info = kzalloc_obj(struct ea_information, GFP_NOFS); - if (!p_ea_info) - return -ENOMEM; - - ea_info_qsize = 0; - err = ntfs_attr_add(ni, AT_EA_INFORMATION, AT_UNNAMED, 0, - (char *)p_ea_info, sizeof(struct ea_information)); - if (err) + size = le32_to_cpu(old_info->ea_query_length); + if (size > old_size) { + err = -EUCLEAN; goto out; - - if (ntfs_attr_exist(ni, AT_EA, AT_UNNAMED, 0)) { - err = ntfs_attr_remove(ni, AT_EA, AT_UNNAMED, 0); - if (err) - goto out; } - - goto alloc_new_ea; } - if (ea_info_qsize > all_ea_size) { - err = -EIO; + /* Validate even the entries following the one being replaced. */ + err = ntfs_ea_lookup(old_ea, size, NULL, 0, &offset, &entry_size); + if (err != -ENOENT) goto out; - } - - err = ntfs_ea_lookup(ea_buf, ea_info_qsize, name, name_len, &ea_off, - &ea_size); - if (ea_info_qsize && !err) { + err = ntfs_ea_lookup(old_ea, size, name, name_len, &offset, &entry_size); + if (err && err != -ENOENT) + goto out; + if (!err) { if (flags & XATTR_CREATE) { 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); - - if (val_size && - le16_to_cpu(p_ea->ea_value_length) == val_size && - !memcmp(p_ea->ea_name + p_ea->ea_name_length + 1, value, - val_size)) - goto out; - - le16_add_cpu(&p_ea_info->ea_length, 0 - ea_packed_size(p_ea)); - - if (p_ea->flags & NEED_EA) - le16_add_cpu(&p_ea_info->need_ea_count, -1); - - memmove((char *)p_ea, (char *)p_ea + ea_size, ea_info_qsize - (ea_off + ea_size)); - ea_info_qsize -= ea_size; - p_ea_info->ea_query_length = cpu_to_le32(ea_info_qsize); - - 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_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) { - 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; + entry = (struct ea_attr *)(old_ea + offset); + if (!remove && le16_to_cpu(entry->ea_value_length) == val_size && + !memcmp(entry->ea_name + name_len + 1, value, val_size)) { + info = *old_info; + goto done; } } else { - if (flags & XATTR_REPLACE) { + if (remove || (flags & XATTR_REPLACE)) { err = -ENODATA; goto out; } + offset = size; + entry_size = 0; + } - if (ntfs_attr_size_bounds_check(ni->vol, AT_EA, - ea_info_qsize + new_ea_size)) { - err = -EFBIG; + add_size = remove ? 0 : ALIGN(sizeof(*entry) + name_len + 1 + val_size, 4); + new_size = size - entry_size + add_size; + if (new_size && ntfs_attr_size_bounds_check(ni->vol, AT_EA, new_size)) { + err = -EFBIG; + goto out; + } + if (new_size) { + ea = kvzalloc(new_size, GFP_NOFS); + if (!ea) { + err = -ENOMEM; goto out; } + if (offset) + memcpy(ea, old_ea, offset); + if (size > offset + entry_size) + memcpy(ea + offset, old_ea + offset + entry_size, + size - offset - entry_size); } -alloc_new_ea: - new_ea_buf = kvzalloc(ea_info_qsize + new_ea_size, GFP_NOFS); - if (!new_ea_buf) { - err = -ENOMEM; - goto out; + size -= entry_size; + + /* Link a zero-terminated old tail before appending the new entry. */ + for (offset = 0; offset < size; offset += next) { + entry = (struct ea_attr *)(ea + offset); + next = le32_to_cpu(entry->next_entry_offset); + if (!next) { + next = size - offset; + entry->next_entry_offset = cpu_to_le32(next); + } + packed += ea_packed_size(entry); + if (entry->flags & NEED_EA) + needed++; } - 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. - */ - memcpy(p_ea->ea_name, name, name_len); - p_ea->ea_name_length = name_len; - p_ea->ea_name[name_len] = 0; - memcpy(p_ea->ea_name + name_len + 1, value, val_size); - p_ea->ea_value_length = cpu_to_le16(val_size); - p_ea->next_entry_offset = cpu_to_le32(new_ea_size); - - ea_packed = le16_to_cpu(p_ea_info->ea_length) + ea_packed_size(p_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) { + if (!remove) { + entry = (struct ea_attr *)(ea + size); + entry->next_entry_offset = cpu_to_le32(add_size); + entry->ea_name_length = name_len; + entry->ea_value_length = cpu_to_le16(val_size); + memcpy(entry->ea_name, name, name_len); + memcpy(entry->ea_name + name_len + 1, value, val_size); + packed += ea_packed_size(entry); + } + if (packed > U16_MAX) { err = -EFBIG; goto out; } - - /* - * 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, ea_buf, - ea_info_qsize + new_ea_size); - if (err) - goto out; - } else { - err = ntfs_write_ea(ni, AT_EA, ea_buf, 0, - ea_info_qsize + new_ea_size, true); - if (err) - goto out; - } - - err = ntfs_write_ea(ni, AT_EA_INFORMATION, (char *)p_ea_info, 0, - sizeof(struct ea_information), false); - if (err) + info.ea_length = cpu_to_le16(packed); + info.need_ea_count = cpu_to_le16(needed); + info.ea_query_length = cpu_to_le32(new_size); + + /* All preparation is complete; errors from here require rollback. */ + err = ntfs_ea_update_attr(ni, AT_EA, ea, new_size); + if (!err) + err = ntfs_ea_update_attr(ni, AT_EA_INFORMATION, + new_size ? (char *)&info : NULL, + sizeof(info)); + if (err) { + /* Try both restorations even if the first one fails. */ + ea_err = ntfs_ea_update_attr(ni, AT_EA, old_ea, old_size); + info_err = ntfs_ea_update_attr(ni, AT_EA_INFORMATION, + (char *)old_info, sizeof(info)); + if (ea_err || info_err) { + NVolSetErrors(ni->vol); + NVolSetShutdown(ni->vol); + ntfs_error(inode->i_sb, + "Failed to restore EAs of inode 0x%llx (%d, %d); shutting down.", + ni->mft_no, ea_err, info_err); + err = -EIO; + } goto out; - - if (packed_ea_size) - *packed_ea_size = p_ea_info->ea_length; + } + size = new_size; mark_mft_record_dirty(ni); +done: + if (packed_ea_size) + *packed_ea_size = info.ea_length; + if (size) + NInoSetHasEA(ni); + else + NInoClearHasEA(ni); out: - if (!err) { - if (ea_info_qsize > 0) - NInoSetHasEA(ni); - else - NInoClearHasEA(ni); - } - - kvfree(ea_buf); - kvfree(old_ea_buf); - kvfree(p_ea_info); - + kvfree(ea); + kvfree(old_ea); + kvfree(old_info); return err; } - /* * Check for the presence of an EA "$LXDEV" (used by WSL) * and return its value as a device address @@ -510,6 +481,55 @@ int ntfs_ea_set_wsl_inode(struct inode *inode, dev_t rdev, __le16 *ea_size, return err; } +int ntfs_ea_get_linuxflags(struct inode *inode) +{ + struct ntfs_inode *ni = NTFS_I(inode); + __le32 value; + int err; + + err = ntfs_get_ea(inode, "$LINUXFLAGS", sizeof("$LINUXFLAGS") - 1, + &value, sizeof(value)); + if (err == -ENODATA) + return 0; + if (err < 0) + return err == -ERANGE ? -EUCLEAN : err; + if (err != sizeof(value)) + return -EUCLEAN; + + ni->linuxflags = le32_to_cpu(value); + if (ni->linuxflags & NTFS_LINUXFLAGS_IMMUTABLE) + inode->i_flags |= S_IMMUTABLE; + if (ni->linuxflags & NTFS_LINUXFLAGS_APPEND) + inode->i_flags |= S_APPEND; + if (ni->linuxflags & NTFS_LINUXFLAGS_NOATIME) + inode->i_flags |= S_NOATIME; + return 0; +} + +int ntfs_ea_set_linuxflags(struct inode *inode, u32 linuxflags) +{ + struct ntfs_inode *ni = NTFS_I(inode); + __le32 value; + int err; + + if (linuxflags) { + value = cpu_to_le32(linuxflags); + err = ntfs_set_ea(inode, "$LINUXFLAGS", sizeof("$LINUXFLAGS") - 1, + &value, sizeof(value), 0, NULL); + } else if (NInoHasEA(ni)) { + err = ntfs_set_ea(inode, "$LINUXFLAGS", sizeof("$LINUXFLAGS") - 1, + NULL, 0, XATTR_REPLACE, NULL); + if (err == -ENODATA) + err = 0; + } else { + err = 0; + } + + if (!err) + ni->linuxflags = linuxflags; + return err; +} + ssize_t ntfs_listxattr(struct dentry *dentry, char *buffer, size_t size) { struct inode *inode = d_inode(dentry); @@ -536,7 +556,7 @@ ssize_t ntfs_listxattr(struct dentry *dentry, char *buffer, size_t size) goto out; } if (ea_info_size != sizeof(struct ea_information)) { - err = -EIO; + err = -EUCLEAN; goto out; } @@ -549,32 +569,26 @@ ssize_t ntfs_listxattr(struct dentry *dentry, char *buffer, size_t size) goto out; } - if (ea_info_qsize > ea_buf_size || ea_info_qsize == 0) + if (ea_info_qsize > ea_buf_size) { + err = -EUCLEAN; + goto out; + } + + /* Validate the entire chain, including size-only queries. */ + err = ntfs_ea_lookup(ea_buf, ea_info_qsize, NULL, 0, &offset, + &ea_size); + if (err != -ENOENT) + goto out; + err = 0; + if (!ea_info_qsize) goto out; offset = 0; do { - if (ea_info_qsize - offset < sizeof(struct ea_attr)) { - err = -EIO; - goto out; - } - p_ea = (const struct ea_attr *)&ea_buf[offset]; next = le32_to_cpu(p_ea->next_entry_offset); ea_size = next ? next : (ea_info_qsize - offset); - if (ea_size < sizeof(struct ea_attr) || - offset + ea_size > ea_info_qsize) { - err = -EIO; - goto out; - } - - if ((int)p_ea->ea_name_length + 1 > - ea_size - offsetof(struct ea_attr, ea_name)) { - err = -EIO; - goto out; - } - if (buffer) { if (ret + p_ea->ea_name_length + 1 > size) { err = -ERANGE; @@ -610,6 +624,14 @@ static int ntfs_getxattr(const struct xattr_handler *handler, struct ntfs_inode *ni = NTFS_I(inode); int err; + /* + * Stream permissions and privileges belong to the base inode. This + * also lets VFS killpriv helpers find the base security attributes. + */ + if (ntfs_inode_is_named_stream(ni)) { + ni = ni->ext.base_ntfs_ino; + inode = VFS_I(ni); + } if (NVolShutdown(ni->vol)) return -EIO; @@ -716,8 +738,8 @@ static int ntfs_new_attr_flags(struct ntfs_inode *ni, __le32 fattr) goto err_out; ntfs_attr_reinit_search_ctx(ctx); - err = ntfs_attr_lookup(ni->type, ni->name, - ni->name_len, CASE_SENSITIVE, + err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, + CASE_SENSITIVE, 0, NULL, 0, ctx); if (err) { err = -EINVAL; @@ -883,9 +905,15 @@ static int ntfs_setxattr(const struct xattr_handler *handler, int err; __le32 fattr; + if (ntfs_inode_is_named_stream(ni)) + return -EOPNOTSUPP; if (NVolShutdown(ni->vol)) return -EIO; + /* Only fileattr_set may change the flags and their cached state. */ + if (!strcmp(name, "$LINUXFLAGS")) + return -EPERM; + if (ntfs_is_reserved_lxattr(name) && !capable(CAP_SYS_ADMIN)) return -EPERM; @@ -1027,8 +1055,9 @@ static noinline int ntfs_set_acl_ex(const struct mnt_idmap *idmap, { const char *name; size_t size, name_len; - void *value; - int err; + void *value, *old_value = NULL; + int err, old_len = -ENODATA; + bool acl_committed = false; int flags; umode_t mode; @@ -1072,7 +1101,34 @@ static noinline int ntfs_set_acl_ex(const struct mnt_idmap *idmap, } mutex_lock(&NTFS_I(inode)->mrec_lock); + if (!init_acl) { + /* Save the old xattr for rollback. */ + old_len = ntfs_get_ea(inode, name, name_len, NULL, 0); + if (old_len >= 0) { + /* kmalloc(0) returns the non-NULL ZERO_SIZE_PTR. */ + old_value = kmalloc(old_len, GFP_NOFS); + if (!old_value) { + err = -ENOMEM; + goto out_unlock; + } + err = ntfs_get_ea(inode, name, name_len, + old_value, old_len); + if (err != old_len) { + kfree(old_value); + old_value = NULL; + if (err >= 0) + err = -EIO; + goto out_unlock; + } + } else if (old_len != -ENODATA) { + err = old_len; + goto out_unlock; + } + } err = ntfs_set_ea(inode, name, name_len, value, size, flags, NULL); + if (!err) + acl_committed = true; +out_unlock: mutex_unlock(&NTFS_I(inode)->mrec_lock); if (err == -ENODATA && !size) err = 0; /* Removing non existed xattr. */ @@ -1084,8 +1140,38 @@ static noinline int ntfs_set_acl_ex(const struct mnt_idmap *idmap, mutex_lock(&NTFS_I(inode)->mrec_lock); err = ntfs_ea_set_wsl_inode(inode, 0, &ea_size, NTFS_EA_MODE); if (err) { - ntfs_set_ea(inode, name, name_len, NULL, 0, - XATTR_REPLACE, NULL); + if (acl_committed) { + int undo_err; + + if (old_len >= 0) { + /* + * A removed or zero-length xattr must + * be recreated without XATTR_REPLACE. + */ + int undo_flags = acl && old_len ? + XATTR_REPLACE : 0; + + undo_err = ntfs_set_ea(inode, name, + name_len, old_value, + old_len, undo_flags, + NULL); + } else { + undo_err = ntfs_set_ea(inode, name, + name_len, NULL, 0, + XATTR_REPLACE, NULL); + } + if (undo_err) { + /* + * Record a failed rollback so the next + * persistence point marks the volume + * dirty. Run chkdsk. + */ + NVolSetErrors(NTFS_I(inode)->vol); + ntfs_error(inode->i_sb, + "Failed to undo the ACL xattr: %d", + undo_err); + } + } mutex_unlock(&NTFS_I(inode)->mrec_lock); inode->i_mode = old_mode; goto out; @@ -1098,6 +1184,7 @@ static noinline int ntfs_set_acl_ex(const struct mnt_idmap *idmap, } out: + kfree(old_value); kfree(value); return err; diff --git a/fs/ntfs/ea.h b/fs/ntfs/ea.h index 690fafe181fb..f200d49063e0 100644 --- a/fs/ntfs/ea.h +++ b/fs/ntfs/ea.h @@ -7,6 +7,15 @@ #define NTFS_EA_GID BIT(2) #define NTFS_EA_MODE BIT(3) +/* $LINUXFLAGS stores these bits in a single little-endian 32-bit value. */ +#define NTFS_LINUXFLAGS_IMMUTABLE BIT(0) +#define NTFS_LINUXFLAGS_APPEND BIT(1) +#define NTFS_LINUXFLAGS_NODUMP BIT(2) +#define NTFS_LINUXFLAGS_NOATIME BIT(3) +#define NTFS_LINUXFLAGS_MASK (NTFS_LINUXFLAGS_IMMUTABLE | \ + NTFS_LINUXFLAGS_APPEND | NTFS_LINUXFLAGS_NODUMP | \ + NTFS_LINUXFLAGS_NOATIME) + 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); @@ -14,6 +23,8 @@ 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); +int ntfs_ea_get_linuxflags(struct inode *inode); +int ntfs_ea_set_linuxflags(struct inode *inode, u32 linuxflags); ssize_t ntfs_listxattr(struct dentry *dentry, char *buffer, size_t size); #ifdef CONFIG_NTFS_FS_POSIX_ACL diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c index 0bde18081b3a..05963127a30e 100644 --- a/fs/ntfs/file.c +++ b/fs/ntfs/file.c @@ -15,6 +15,14 @@ #include <linux/posix_acl_xattr.h> #include <linux/compat.h> #include <linux/falloc.h> +#include <linux/fileattr.h> +#include <linux/file.h> +#include <linux/filelock.h> +#include <linux/list.h> +#include <linux/overflow.h> +#include <linux/security.h> +#include <linux/slab.h> +#include <uapi/linux/ntfs.h> #include "lcnalloc.h" #include "ntfs.h" @@ -23,8 +31,8 @@ #include "iomap.h" #include "bitmap.h" #include "volume.h" - -#include <linux/filelock.h> +#include "mft.h" +#include "stream.h" /* * ntfs_file_open - called when an inode is about to be opened @@ -44,7 +52,7 @@ * * After the check passes, just call generic_file_open() to do its work. */ -static int ntfs_file_open(struct inode *vi, struct file *filp) +int ntfs_file_open(struct inode *vi, struct file *filp) { struct ntfs_inode *ni = NTFS_I(vi); @@ -78,6 +86,7 @@ static int ntfs_file_open(struct inode *vi, struct file *filp) static int ntfs_trim_prealloc(struct inode *vi) { struct ntfs_inode *ni = NTFS_I(vi); + struct ntfs_inode *mrec_ni = ntfs_base_inode(ni); struct ntfs_volume *vol = ni->vol; struct runlist_element *rl; s64 aligned_data_size; @@ -86,7 +95,7 @@ static int ntfs_trim_prealloc(struct inode *vi) int err = 0; inode_lock(vi); - mutex_lock(&ni->mrec_lock); + mutex_lock(&mrec_ni->mrec_lock); down_write(&ni->runlist.lock); aligned_data_size = round_up(ni->data_size, vol->cluster_size); @@ -121,13 +130,13 @@ static int ntfs_trim_prealloc(struct inode *vi) out_unlock: up_write(&ni->runlist.lock); - mutex_unlock(&ni->mrec_lock); + mutex_unlock(&mrec_ni->mrec_lock); inode_unlock(vi); return err; } -static int ntfs_file_release(struct inode *vi, struct file *filp) +int ntfs_file_release(struct inode *vi, struct file *filp) { if (!NInoCompressed(NTFS_I(vi)) && !NInoWofCompressed(NTFS_I(vi))) @@ -137,6 +146,131 @@ static int ntfs_file_release(struct inode *vi, struct file *filp) } /* + * ntfs_fileattr_get - inode_operations::fileattr_get + * @dentry: dentry to report the flags of + * @fa: filled in with the flags of @dentry + */ +int ntfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa) +{ + struct inode *vi = d_inode(dentry); + struct ntfs_inode *ni = NTFS_I(vi); + u32 flags = 0; + + if (NVolShutdown(ni->vol)) + return -EIO; + + if (NInoCompressed(ni) || NInoWofCompressed(ni)) + flags |= FS_COMPR_FL; + if (NInoEncrypted(ni)) + flags |= FS_ENCRYPT_FL; + if (vi->i_flags & S_IMMUTABLE) + flags |= FS_IMMUTABLE_FL; + if (vi->i_flags & S_APPEND) + flags |= FS_APPEND_FL; + if (vi->i_flags & S_NOATIME) + flags |= FS_NOATIME_FL; + if (ni->linuxflags & NTFS_LINUXFLAGS_NODUMP) + flags |= FS_NODUMP_FL; + if (!NVolCaseSensitive(ni->vol)) + flags |= FS_CASEFOLD_FL; + + fileattr_fill_flags(fa, flags); + return 0; +} + +static bool ntfs_is_forced_immutable(struct inode *vi) +{ + struct ntfs_inode *ni = NTFS_I(vi); + + if (ni->mft_no < FILE_first_user && S_ISREG(vi->i_mode)) + return true; + + return NVolSysImmutable(ni->vol) && + (ni->flags & FILE_ATTR_SYSTEM) && + !S_ISFIFO(vi->i_mode) && !S_ISSOCK(vi->i_mode) && + !S_ISLNK(vi->i_mode); +} + +/* + * ntfs_fileattr_set - inode_operations::fileattr_set + * @idmap: idmap of the mount @dentry was found from + * @dentry: dentry to set the flags of + * @fa: flags to set + */ +int ntfs_fileattr_set(struct mnt_idmap *idmap, struct dentry *dentry, + struct file_kattr *fa) +{ + struct inode *vi = d_inode(dentry); + struct ntfs_inode *ni = NTFS_I(vi); + u32 allowed = FS_IMMUTABLE_FL | FS_APPEND_FL | FS_NODUMP_FL | + FS_NOATIME_FL; + u32 readonly = 0; + u32 linuxflags = ni->linuxflags & ~NTFS_LINUXFLAGS_MASK; + unsigned int new_fl = 0; + bool forced_immutable; + int err; + + if (NVolShutdown(ni->vol)) + return -EIO; + + if (fileattr_has_fsx(fa)) + return -EOPNOTSUPP; + + /* chattr passes the unchanged read-only flags back to us too. */ + if (NInoCompressed(ni) || NInoWofCompressed(ni)) + readonly |= FS_COMPR_FL; + if (NInoEncrypted(ni)) + readonly |= FS_ENCRYPT_FL; + if ((fa->flags ^ readonly) & (FS_COMPR_FL | FS_ENCRYPT_FL)) + return -EOPNOTSUPP; + allowed |= readonly; + + /* Case folding is a read-only, mount-wide property. */ + if (!NVolCaseSensitive(ni->vol)) + allowed |= FS_CASEFOLD_FL; + if (fa->flags & ~allowed) + return -EOPNOTSUPP; + + forced_immutable = ntfs_is_forced_immutable(vi); + if (!(fa->flags & FS_IMMUTABLE_FL) && forced_immutable) + return -EPERM; + + if (fa->flags & FS_IMMUTABLE_FL) { + new_fl |= S_IMMUTABLE; + /* Do not persist an immutable bit derived from the mount. */ + if (!forced_immutable || + (ni->linuxflags & NTFS_LINUXFLAGS_IMMUTABLE)) + linuxflags |= NTFS_LINUXFLAGS_IMMUTABLE; + } + if (fa->flags & FS_APPEND_FL) { + new_fl |= S_APPEND; + linuxflags |= NTFS_LINUXFLAGS_APPEND; + } + if (fa->flags & FS_NODUMP_FL) + linuxflags |= NTFS_LINUXFLAGS_NODUMP; + if (fa->flags & FS_NOATIME_FL) { + new_fl |= S_NOATIME; + linuxflags |= NTFS_LINUXFLAGS_NOATIME; + } + + mutex_lock(&ni->mrec_lock); + err = ntfs_ea_set_linuxflags(vi, linuxflags); + mutex_unlock(&ni->mrec_lock); + if (err) + return err; + + inode_set_flags(vi, new_fl, S_IMMUTABLE | S_APPEND | S_NOATIME); + inode_set_ctime_current(vi); + mark_inode_dirty(vi); + return 0; +} + +struct ntfs_fsync_attr { + struct list_head list; + struct inode *inode; +}; + +/* * ntfs_file_fsync - sync a file to disk * @filp: file to be synced * @start: start offset to be synced @@ -156,7 +290,7 @@ static int ntfs_file_release(struct inode *vi, struct file *filp) * Also, if @datasync is true, we do not wait on the inode to be written out * but we always wait on the page cache pages to be written out. */ -static int ntfs_file_fsync(struct file *filp, loff_t start, loff_t end, +int ntfs_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync) { struct inode *vi = filp->f_mapping->host; @@ -165,6 +299,9 @@ static int ntfs_file_fsync(struct file *filp, loff_t start, loff_t end, int err, ret = 0; struct inode *parent_vi, *ia_vi; struct ntfs_attr_search_ctx *ctx; + struct ntfs_fsync_attr *attr, *next; + LIST_HEAD(attrs); + bool non_resident, stream; ntfs_debug("Entering for inode 0x%llx.", ni->mft_no); @@ -175,10 +312,24 @@ static int ntfs_file_fsync(struct file *filp, loff_t start, loff_t end, if (err) return err; - if (!datasync || !NInoNonResident(NTFS_I(vi))) + stream = ntfs_inode_is_named_stream(ni); + non_resident = NInoNonResident(ni); + if (stream) { + ni = ni->ext.base_ntfs_ino; + vi = VFS_I(ni); + } + + if (!datasync || !non_resident) ret = __ntfs_write_inode(vi, 1); write_inode_now(vi, !datasync); + /* + * file_write_and_wait_range() already flushed this stream mapping. + * A stream fsync does not need to flush sibling attribute mappings. + */ + if (stream) + goto sync_volume; + ctx = ntfs_attr_get_search_ctx(ni, NULL); if (!ctx) return -ENOMEM; @@ -210,23 +361,55 @@ static int ntfs_file_fsync(struct file *filp, loff_t start, loff_t end, name = (__le16 *)((u8 *)ctx->attr + le16_to_cpu(ctx->attr->name_offset)); if (ctx->attr->type == AT_DATA && ctx->attr->name_length == 0) continue; + if (ctx->attr->type == AT_DATA && + ntfs_stream_unlinked(ni, name, ctx->attr->name_length)) + continue; + attr = kmalloc_obj(*attr, GFP_NOFS); + if (!attr) { + err = -ENOMEM; + break; + } attr_vi = ntfs_attr_iget(vi, ctx->attr->type, name, ctx->attr->name_length); - if (IS_ERR(attr_vi)) + if (IS_ERR(attr_vi)) { + kfree(attr); continue; - spin_lock(&attr_vi->i_lock); - if (inode_state_read_once(attr_vi) & I_DIRTY_PAGES) { - spin_unlock(&attr_vi->i_lock); - filemap_write_and_wait(attr_vi->i_mapping); - } else - spin_unlock(&attr_vi->i_lock); - iput(attr_vi); + } + if (ntfs_inode_is_named_stream(NTFS_I(attr_vi))) + atomic_inc(&NTFS_I(attr_vi)->stream_open_count); + attr->inode = attr_vi; + list_add_tail(&attr->list, &attrs); } } mutex_unlock(&ni->mrec_lock); ntfs_attr_put_search_ctx(ctx); + if (err != -ENOENT && !ret) + ret = err; + /* Attribute writeback takes the base inode's MFT record lock. */ + list_for_each_entry_safe(attr, next, &attrs, list) { + struct inode *attr_vi = attr->inode; + + err = filemap_write_and_wait(attr_vi->i_mapping); + if (err && !ret) + ret = err; + if (ntfs_inode_is_named_stream(NTFS_I(attr_vi))) { + err = ntfs_stream_put(attr_vi); + if (err && !ret) + ret = err; + } + iput(attr_vi); + list_del(&attr->list); + kfree(attr); + } + + /* Attribute writeback may have updated the base mapping pairs. */ + err = write_inode_now(vi, 1); + if (err && !ret) + ret = err; + +sync_volume: write_inode_now(vol->mftbmp_ino, 1); down_write(&vol->lcnbmp_lock); write_inode_now(vol->lcnbmp_ino, 1); @@ -241,23 +424,44 @@ static int ntfs_file_fsync(struct file *filp, loff_t start, loff_t end, err = sync_blockdev(vi->i_sb->s_bdev); if (unlikely(err && !ret)) ret = err; + if (!ret) + ret = blkdev_issue_flush(vi->i_sb->s_bdev); if (likely(!ret)) ntfs_debug("Done."); else ntfs_warning(vi->i_sb, "Failed to f%ssync inode 0x%llx. Error %u.", datasync ? "data" : "", ni->mft_no, -ret); - if (!ret) - blkdev_issue_flush(vi->i_sb->s_bdev); return ret; } -static int ntfs_setattr_size(struct inode *vi, struct iattr *attr) +static void ntfs_pagecache_extend(struct inode *vi, loff_t from, loff_t to) +{ + pagecache_isize_extended(vi, from, to); + + /* + * NTFS initialized_size is byte-granular, so a partial old-EOF page + * must stay write-protected until page_mkwrite() updates it. The + * generic helper skips this when the block size is at least a page, + * the extension ends before the rounded block boundary, or that + * boundary is page-aligned. + */ + if (from < to && from & (PAGE_SIZE - 1)) + unmap_mapping_range(vi->i_mapping, round_down(from, PAGE_SIZE), + PAGE_SIZE, 0); +} + +int ntfs_setattr_size(struct inode *vi, struct iattr *attr) { struct ntfs_inode *ni = NTFS_I(vi); + struct ntfs_inode *base_ni = ntfs_base_inode(ni); + bool stream = ntfs_inode_is_named_stream(ni); int err; loff_t old_size = vi->i_size; + if (stream && NVolShutdown(ni->vol)) + return -EIO; + if (NInoCompressed(ni) || NInoEncrypted(ni) || NInoWofCompressed(ni)) { ntfs_warning( vi->i_sb, @@ -277,19 +481,48 @@ static int ntfs_setattr_size(struct inode *vi, struct iattr *attr) * readers cannot observe the size change until the attribute * updates below have completed. */ - filemap_invalidate_lock(vi->i_mapping); + if (stream) { + filemap_invalidate_lock(vi->i_mapping); + err = filemap_write_and_wait(vi->i_mapping); + if (err) + goto out_unlock_mapping; + + mutex_lock(&base_ni->mrec_lock); + err = ntfs_stream_inode_validate(vi); + mutex_unlock(&base_ni->mrec_lock); + if (err) + goto out_unlock_mapping; + } else { + filemap_invalidate_lock(vi->i_mapping); + } + 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); + ntfs_pagecache_extend(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) + if (stream) { + mutex_lock(&base_ni->mrec_lock); + err = ntfs_stream_inode_validate(vi); + if (!err) + err = __ntfs_attr_truncate_vfs(ni, attr->ia_size, + old_size); + mutex_unlock(&base_ni->mrec_lock); + } else { + err = ntfs_truncate_vfs(vi, attr->ia_size, old_size); + } + if (err) { i_size_write(vi, old_size); + goto out_unlock_mapping; + } + +out_unlock_mapping: filemap_invalidate_unlock(vi->i_mapping); + if (!err && stream) + ntfs_stream_update_base_time(base_ni); return err; } @@ -325,8 +558,7 @@ int ntfs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, goto out; } - if (!(vol->vol_flags & VOLUME_IS_DIRTY)) - ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); if (ia_valid & ATTR_SIZE) { err = ntfs_setattr_size(vi, attr); @@ -398,9 +630,12 @@ int ntfs_getattr(const struct mnt_idmap *idmap, const struct path *path, if (inode->i_flags & S_APPEND) stat->attributes |= STATX_ATTR_APPEND; + if (ni->linuxflags & NTFS_LINUXFLAGS_NODUMP) + stat->attributes |= STATX_ATTR_NODUMP; stat->attributes_mask |= STATX_ATTR_COMPRESSED | STATX_ATTR_ENCRYPTED | - STATX_ATTR_IMMUTABLE | STATX_ATTR_APPEND; + STATX_ATTR_IMMUTABLE | STATX_ATTR_APPEND | + STATX_ATTR_NODUMP; /* * If it's a compressed or encrypted file, NTFS currently @@ -422,7 +657,25 @@ int ntfs_getattr(const struct mnt_idmap *idmap, const struct path *path, return 0; } -static loff_t ntfs_file_llseek(struct file *file, loff_t offset, int whence) +/* + * Validate a stream before VFS write handling removes privileges or updates + * timestamps on its base inode. + */ +static int ntfs_file_modified(struct kiocb *iocb) +{ + struct inode *inode = file_inode(iocb->ki_filp); + int err; + + if (ntfs_inode_is_named_stream(NTFS_I(inode))) { + err = ntfs_stream_validate_for_mutation(inode, + iocb->ki_flags & IOCB_NOWAIT); + if (err) + return err; + } + return kiocb_modified(iocb); +} + +loff_t ntfs_file_llseek(struct file *file, loff_t offset, int whence) { struct inode *inode = file->f_mapping->host; @@ -451,7 +704,7 @@ static loff_t ntfs_file_llseek(struct file *file, loff_t offset, int whence) return vfs_setpos(file, offset, inode->i_sb->s_maxbytes); } -static ssize_t ntfs_file_read_iter(struct kiocb *iocb, struct iov_iter *to) +ssize_t ntfs_file_read_iter(struct kiocb *iocb, struct iov_iter *to) { struct inode *vi = file_inode(iocb->ki_filp); struct super_block *sb = vi->i_sb; @@ -460,8 +713,8 @@ static ssize_t ntfs_file_read_iter(struct kiocb *iocb, struct iov_iter *to) if (NVolShutdown(NTFS_SB(sb))) return -EIO; - if ((NInoCompressed(NTFS_I(vi)) || NInoWofCompressed(NTFS_I(vi))) && - iocb->ki_flags & IOCB_DIRECT) + if ((NInoCompressed(NTFS_I(vi)) || NInoWofCompressed(NTFS_I(vi)) || + NInoEncrypted(NTFS_I(vi))) && iocb->ki_flags & IOCB_DIRECT) return -EOPNOTSUPP; inode_lock_shared(vi); @@ -498,7 +751,11 @@ static int ntfs_file_write_dio_end_io(struct kiocb *iocb, ssize_t size, if (size) { if (i_size_read(inode) < iocb->ki_pos + size) { i_size_write(inode, iocb->ki_pos + size); - mark_inode_dirty(inode); + if (ntfs_inode_is_named_stream(NTFS_I(inode))) + mark_inode_dirty(VFS_I( + NTFS_I(inode)->ext.base_ntfs_ino)); + else + mark_inode_dirty(inode); } } @@ -525,8 +782,21 @@ static ssize_t ntfs_dio_write_iter(struct kiocb *iocb, struct iov_iter *from) ssize_t written; int ret2; + if (iocb->ki_flags & IOCB_NOWAIT) { + if (!ret) + ret = -EAGAIN; + goto out; + } + offset = iocb->ki_pos; iocb->ki_flags &= ~IOCB_DIRECT; + + /* + * Prevent concurrent direct I/O and buffered I/O to the same file + * range. Wait for in-flight DIO to finish before dirtying pages. + */ + inode_dio_wait(file_inode(iocb->ki_filp)); + written = iomap_file_buffered_write(iocb, from, &ntfs_write_iomap_ops, &ntfs_iomap_folio_ops, NULL); @@ -555,6 +825,7 @@ out: static int ntfs_expand_for_write(struct ntfs_inode *ni, loff_t end) { struct ntfs_volume *vol = ni->vol; + struct ntfs_inode *mrec_ni = ntfs_base_inode(ni); loff_t prealloc_size = 0; int err; @@ -570,14 +841,14 @@ static int ntfs_expand_for_write(struct ntfs_inode *ni, loff_t end) prealloc_size = ni->allocated_size + vol->preallocated_size; } - mutex_lock(&ni->mrec_lock); + mutex_lock(&mrec_ni->mrec_lock); err = ntfs_attr_expand(ni, end, prealloc_size); - mutex_unlock(&ni->mrec_lock); + mutex_unlock(&mrec_ni->mrec_lock); return err; } -static ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from) +ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from) { struct file *file = iocb->ki_filp; struct inode *vi = file->f_mapping->host; @@ -614,14 +885,20 @@ static ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from) if (ret <= 0) goto out_lock; - err = file_modified(iocb->ki_filp); + err = ntfs_file_modified(iocb); if (err) { ret = err; goto out_lock; } - if (!(vol->vol_flags & VOLUME_IS_DIRTY)) - ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + /* + * The volume must be marked dirty before the modification is made, + * without an unlocked check of the in-memory flag: the dirty bit + * is only cleared at the quiescent transitions, under the same + * $Volume mrec_lock this call takes, so an unlocked skip could + * lose the set to one of them. + */ + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); pos = iocb->ki_pos; count = ret; @@ -658,13 +935,21 @@ static ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from) out: if (ret < 0 && ret != -EIOCBQUEUED) { if (ni->initialized_size != old_init_size) { - mutex_lock(&ni->mrec_lock); + struct ntfs_inode *mrec_ni = + ntfs_base_inode(ni); + + mutex_lock(&mrec_ni->mrec_lock); ntfs_attr_set_initialized_size(ni, old_init_size); - mutex_unlock(&ni->mrec_lock); + mutex_unlock(&mrec_ni->mrec_lock); } if (ni->data_size != old_data_size) { + struct ntfs_inode *mrec_ni = + ntfs_base_inode(ni); + truncate_setsize(vi, old_data_size); + mutex_lock(&mrec_ni->mrec_lock); ntfs_attr_truncate(ni, old_data_size); + mutex_unlock(&mrec_ni->mrec_lock); } } out_lock: @@ -678,13 +963,24 @@ static vm_fault_t ntfs_filemap_page_mkwrite(struct vm_fault *vmf) { struct inode *inode = file_inode(vmf->vma->vm_file); struct address_space *mapping = inode->i_mapping; + bool stream = ntfs_inode_is_named_stream(NTFS_I(inode)); + int err; vm_fault_t ret; if (NInoWofCompressed(NTFS_I(inode))) return VM_FAULT_SIGBUS; sb_start_pagefault(inode->i_sb); - file_update_time(vmf->vma->vm_file); + if (stream) { + err = ntfs_stream_validate_for_mutation(inode, false); + if (err) + goto out_error; + err = file_update_time(vmf->vma->vm_file); + if (err) + goto out_error; + } else { + file_update_time(vmf->vma->vm_file); + } /* * Serialize against truncate/fallocate which hold the lock @@ -693,9 +989,12 @@ static vm_fault_t ntfs_filemap_page_mkwrite(struct vm_fault *vmf) filemap_invalidate_lock_shared(mapping); ret = iomap_page_mkwrite(vmf, &ntfs_page_mkwrite_iomap_ops, NULL); filemap_invalidate_unlock_shared(mapping); - sb_end_pagefault(inode->i_sb); return ret; + +out_error: + sb_end_pagefault(inode->i_sb); + return vmf_error(err); } static const struct vm_operations_struct ntfs_file_vm_ops = { @@ -704,7 +1003,7 @@ static const struct vm_operations_struct ntfs_file_vm_ops = { .page_mkwrite = ntfs_filemap_page_mkwrite, }; -static int ntfs_file_mmap_prepare(struct vm_area_desc *desc) +int ntfs_file_mmap_prepare(struct vm_area_desc *desc) { struct file *file = desc->file; struct inode *inode = file_inode(file); @@ -737,7 +1036,7 @@ static int ntfs_file_mmap_prepare(struct vm_area_desc *desc) return 0; } -static int ntfs_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo, +int ntfs_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo, u64 start, u64 len) { if (NInoWofCompressed(NTFS_I(inode))) @@ -774,7 +1073,7 @@ static const char *ntfs_get_link(struct dentry *dentry, struct inode *inode, return ni->target; } -static ssize_t ntfs_file_splice_read(struct file *in, loff_t *ppos, +ssize_t ntfs_file_splice_read(struct file *in, loff_t *ppos, struct pipe_inode_info *pipe, size_t len, unsigned int flags) { if (NVolShutdown(NTFS_SB(in->f_mapping->host->i_sb))) @@ -867,7 +1166,7 @@ static int ntfs_ioctl_fitrim(struct ntfs_volume *vol, unsigned long arg) if (range.len < vol->cluster_size) return -EINVAL; - range.minlen = max_t(u32, range.minlen, bdev_discard_granularity(dev)); + range.minlen = max_t(u64, range.minlen, bdev_discard_granularity(dev)); err = ntfs_trim_fs(vol, &range); if (err < 0) @@ -890,6 +1189,14 @@ long ntfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) return ntfs_ioctl_set_volume_label(filp, arg); case FITRIM: return ntfs_ioctl_fitrim(NTFS_SB(file_inode(filp)->i_sb), arg); + case NTFS_IOC_STREAM_READ: + return ntfs_ioctl_stream_read(filp, arg); + case NTFS_IOC_STREAM_WRITE: + return ntfs_ioctl_stream_write(filp, arg); + case NTFS_IOC_STREAM_REMOVE: + return ntfs_ioctl_stream_remove(filp, arg); + case NTFS_IOC_LIST_STREAMS: + return ntfs_ioctl_list_streams(filp, arg); default: return -ENOTTY; } @@ -946,6 +1253,7 @@ out: static int ntfs_punch_hole(struct ntfs_inode *ni, int mode, loff_t offset, loff_t len) { + struct ntfs_inode *mrec_ni = ntfs_base_inode(ni); struct ntfs_volume *vol = ni->vol; struct inode *vi = VFS_I(ni); loff_t end_offset; @@ -1010,16 +1318,17 @@ static int ntfs_punch_hole(struct ntfs_inode *ni, int mode, loff_t offset, end_vcn--; } - mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); + mutex_lock_nested(&mrec_ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); err = ntfs_non_resident_attr_punch_hole(ni, start_vcn, end_vcn - start_vcn); - mutex_unlock(&ni->mrec_lock); + mutex_unlock(&mrec_ni->mrec_lock); out: return err; } static int ntfs_collapse_range(struct ntfs_inode *ni, loff_t offset, loff_t len) { + struct ntfs_inode *mrec_ni = ntfs_base_inode(ni); struct ntfs_volume *vol = ni->vol; struct inode *vi = VFS_I(ni); loff_t old_size, new_size; @@ -1053,10 +1362,10 @@ static int ntfs_collapse_range(struct ntfs_inode *ni, loff_t offset, loff_t len) truncate_pagecache(vi, offset_down); - mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); + mutex_lock_nested(&mrec_ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); err = ntfs_non_resident_attr_collapse_range(ni, start_vcn, end_vcn - start_vcn); - mutex_unlock(&ni->mrec_lock); + mutex_unlock(&mrec_ni->mrec_lock); if (new_size != old_size) i_size_write(vi, ni->data_size); @@ -1066,6 +1375,7 @@ out: static int ntfs_insert_range(struct ntfs_inode *ni, loff_t offset, loff_t len) { + struct ntfs_inode *mrec_ni = ntfs_base_inode(ni); struct ntfs_volume *vol = ni->vol; struct inode *vi = VFS_I(ni); loff_t offset_down = round_down(offset, @@ -1109,10 +1419,10 @@ static int ntfs_insert_range(struct ntfs_inode *ni, loff_t offset, loff_t len) truncate_pagecache(vi, offset_down); - mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); + mutex_lock_nested(&mrec_ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); err = ntfs_non_resident_attr_insert_range(ni, start_vcn, end_vcn - start_vcn); - mutex_unlock(&ni->mrec_lock); + mutex_unlock(&mrec_ni->mrec_lock); if (new_size != old_size) i_size_write(vi, ni->data_size); @@ -1125,13 +1435,15 @@ out: FALLOC_FL_INSERT_RANGE | FALLOC_FL_PUNCH_HOLE | \ FALLOC_FL_COLLAPSE_RANGE) -static long ntfs_fallocate(struct file *file, int mode, loff_t offset, loff_t len) +long ntfs_fallocate(struct file *file, int mode, loff_t offset, loff_t len) { struct inode *vi = file_inode(file); struct ntfs_inode *ni = NTFS_I(vi); + struct ntfs_inode *base_ni = ntfs_base_inode(ni); struct ntfs_volume *vol = ni->vol; + bool stream = ntfs_inode_is_named_stream(ni); int err = 0; - loff_t old_size; + loff_t old_size, new_size; if (mode & ~(NTFS_FALLOC_FL_SUPPORTED)) return -EOPNOTSUPP; @@ -1142,9 +1454,6 @@ static long ntfs_fallocate(struct file *file, int mode, loff_t offset, loff_t le if (!NVolFreeClusterKnown(vol)) wait_event(vol->free_waitq, NVolFreeClusterKnown(vol)); - if ((ni->vol->mft_zone_end - ni->vol->mft_zone_start) == 0) - return -ENOSPC; - if (NInoNonResident(ni) && !NInoFullyMapped(ni)) { down_write(&ni->runlist.lock); err = ntfs_attr_map_whole_runlist(ni); @@ -1153,19 +1462,23 @@ static long ntfs_fallocate(struct file *file, int mode, loff_t offset, loff_t le return err; } - if (!(vol->vol_flags & VOLUME_IS_DIRTY)) { - err = ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); - if (err) - return err; - } - - old_size = i_size_read(vi); + err = ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + if (err) + return err; inode_lock(vi); if (NInoCompressed(ni) || NInoEncrypted(ni) || NInoWofCompressed(ni)) { inode_unlock(vi); return -EOPNOTSUPP; } + if (stream) { + err = ntfs_stream_validate_for_mutation(vi, false); + if (err) { + inode_unlock(vi); + return err; + } + } + old_size = i_size_read(vi); inode_dio_wait(vi); /* Take invalidate_lock for all fallocate operations to prevent races */ @@ -1194,18 +1507,24 @@ static long ntfs_fallocate(struct file *file, int mode, loff_t offset, loff_t le err = file_modified(file); out: - if (!err && mode == 0 && NInoNonResident(ni) && - offset > old_size) { - truncate_pagecache(vi, old_size); - pagecache_isize_extended(vi, old_size, offset); + if (!err && mode == 0 && NInoNonResident(ni)) { + new_size = i_size_read(vi); + if (new_size > old_size) { + truncate_pagecache(vi, old_size); + ntfs_pagecache_extend(vi, old_size, new_size); + } } filemap_invalidate_unlock(vi->i_mapping); if (!err) { - NInoSetFileNameDirty(ni); - inode_set_mtime_to_ts(vi, inode_set_ctime_current(vi)); - mark_inode_dirty(vi); + if (stream) { + ntfs_stream_update_base_time(base_ni); + } else { + NInoSetFileNameDirty(ni); + inode_set_mtime_to_ts(vi, inode_set_ctime_current(vi)); + mark_inode_dirty(vi); + } } inode_unlock(vi); @@ -1233,6 +1552,8 @@ const struct file_operations ntfs_file_ops = { const struct inode_operations ntfs_file_inode_ops = { .setattr = ntfs_setattr, .getattr = ntfs_getattr, + .fileattr_get = ntfs_fileattr_get, + .fileattr_set = ntfs_fileattr_set, .listxattr = ntfs_listxattr, .get_acl = ntfs_get_acl, .set_acl = ntfs_set_acl, @@ -1242,13 +1563,18 @@ const struct inode_operations ntfs_file_inode_ops = { const struct inode_operations ntfs_symlink_inode_operations = { .get_link = ntfs_get_link, .setattr = ntfs_setattr, + .getattr = ntfs_getattr, .listxattr = ntfs_listxattr, + .fileattr_get = ntfs_fileattr_get, + .fileattr_set = ntfs_fileattr_set, }; const struct inode_operations ntfs_special_inode_operations = { .setattr = ntfs_setattr, .getattr = ntfs_getattr, .listxattr = ntfs_listxattr, + .fileattr_get = ntfs_fileattr_get, + .fileattr_set = ntfs_fileattr_set, .get_acl = ntfs_get_acl, .set_acl = ntfs_set_acl, }; diff --git a/fs/ntfs/index.c b/fs/ntfs/index.c index 580998990bc9..ac22d8407a77 100644 --- a/fs/ntfs/index.c +++ b/fs/ntfs/index.c @@ -812,6 +812,9 @@ static int ntfs_icx_parent_dec(struct ntfs_index_context *icx) * after the search @key, i.e. this is the position in the index at which * an index entry with a key of @key would need to be inserted. * + * If the index cannot be searched, a negative error code other than -ENOENT + * is returned and @icx does not describe a valid index entry. + * * When finished with the entry and its data, call ntfs_index_ctx_put() to free * the context and other associated resources. * @@ -852,7 +855,7 @@ int ntfs_index_lookup(const void *key, const u32 key_len, struct ntfs_index_cont if (ni->vol->cluster_size <= icx->block_size) icx->vcn_size_bits = ni->vol->cluster_size_bits; else - icx->vcn_size_bits = ni->vol->sector_size_bits; + icx->vcn_size_bits = NTFS_BLOCK_SIZE_BITS; icx->cr = ir->collation_rule; if (!ntfs_is_collation_rule_supported(icx->cr)) { @@ -877,7 +880,7 @@ int ntfs_index_lookup(const void *key, const u32 key_len, struct ntfs_index_cont /* Child node present, descend into it. */ icx->ia_ni = ntfs_ia_open(icx, ni); if (!icx->ia_ni) { - err = -ENOENT; + err = -EIO; goto err_out; } diff --git a/fs/ntfs/index.h b/fs/ntfs/index.h index 9a03f53bba47..a898e348f2c9 100644 --- a/fs/ntfs/index.h +++ b/fs/ntfs/index.h @@ -37,7 +37,7 @@ * (maximum is MAX_PARENT_VCN) * @ib_dirty: true if the current index block (@ia/@ib) was modified * @block_size: size of index blocks in bytes (from $INDEX_ROOT or $Boot) - * @vcn_size_bits: log2(cluster size) + * @vcn_size_bits: log2 of the VCN unit size * @sync_write: true if synchronous writeback is requested for this context * * @idx_ni is the index inode this context belongs to. diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index a777de8a80c7..206db4e99ef1 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -8,6 +8,7 @@ #include <linux/writeback.h> #include <linux/seq_file.h> +#include <linux/mm.h> #include "lcnalloc.h" #include "time.h" @@ -19,6 +20,7 @@ #include "attrib.h" #include "iomap.h" #include "object_id.h" +#include "stream.h" /* * ntfs_test_inode - compare two (possibly fake) inodes for equality @@ -55,9 +57,18 @@ int ntfs_test_inode(struct inode *vi, void *data) return 0; if (ni->name_len != na->name_len) return 0; - if (na->name_len && memcmp(ni->name, na->name, - na->name_len * sizeof(__le16))) - return 0; + if (na->name_len) { + if (ntfs_inode_is_named_stream(ni)) { + if (!ntfs_names_are_equal(ni->name, ni->name_len, + na->name, na->name_len, + IGNORE_CASE, ni->vol->upcase, + ni->vol->upcase_len)) + return 0; + } else if (memcmp(ni->name, na->name, + na->name_len * sizeof(__le16))) { + return 0; + } + } if (!ni->ext.base_ntfs_ino) return 0; } @@ -67,6 +78,33 @@ int ntfs_test_inode(struct inode *vi, void *data) } /* + * ntfs_test_inode_rcu() - Test an inode during RCU hash lookup + * @vi: inode being tested + * @data: NTFS attribute key to match + * + * Reject inodes being initialized or destroyed, then run the normal NTFS + * inode match while holding @vi's inode lock. + * + * Return: 1 if the inode matches, or 0 otherwise. + */ +int ntfs_test_inode_rcu(struct inode *vi, void *data) +{ + unsigned long state; + int ret; + + spin_lock(&vi->i_lock); + state = inode_state_read_once(vi); + if (state & (I_FREEING | I_WILL_FREE | I_NEW)) { + ret = 0; + goto out; + } + ret = ntfs_test_inode(vi, data); +out: + spin_unlock(&vi->i_lock); + return ret; +} + +/* * ntfs_init_locked_inode - initialize an inode * @vi: vfs inode to initialize * @data: data which to initialize @vi to @@ -467,6 +505,7 @@ void __ntfs_init_inode(struct super_block *sb, struct ntfs_inode *ni) ni->seq_no = 0; atomic_set(&ni->count, 1); ni->vol = NTFS_SB(sb); + atomic_set(&ni->stream_open_count, 0); ntfs_init_runlist(&ni->runlist); mutex_init(&ni->mrec_lock); if (ni->type == AT_ATTRIBUTE_LIST) { @@ -498,6 +537,7 @@ void __ntfs_init_inode(struct super_block *sb, struct ntfs_inode *ni) ni->reparse_tag = 0; ni->reparse_flags = 0; ni->target = NULL; + ni->linuxflags = 0; ni->i_dealloc_clusters = 0; } @@ -651,6 +691,25 @@ void ntfs_set_vfs_operations(struct inode *inode, mode_t mode, dev_t dev) } } +static bool ntfs_non_resident_sizes_inconsistent(struct inode *vi, + const struct attr_record *a) +{ + s64 allocated_size = le64_to_cpu(a->data.non_resident.allocated_size); + s64 data_size = le64_to_cpu(a->data.non_resident.data_size); + s64 initialized_size = le64_to_cpu(a->data.non_resident.initialized_size); + + if (initialized_size >= 0 && initialized_size <= data_size && + data_size <= allocated_size && + !ntfs_bytes_to_cluster_off(NTFS_I(vi)->vol, allocated_size)) + return false; + + ntfs_error(vi->i_sb, + "Attribute 0x%x of inode 0x%llx is corrupt (initialized size %lld, data size %lld, allocated size %lld).", + le32_to_cpu(a->type), NTFS_I(vi)->mft_no, initialized_size, + data_size, allocated_size); + return true; +} + /* * ntfs_read_locked_inode - read an inode from its device * @vi: inode to read @@ -669,10 +728,8 @@ void ntfs_set_vfs_operations(struct inode *inode, mode_t mode, dev_t dev) * Q: What locks are held when the function is called? * A: i_state has I_NEW set, hence the inode is locked, also * i_count is set to 1, so it is not going to go away - * i_flags is set to 0 and we have no business touching it. Only an ioctl() - * is allowed to write to them. We should of course be honouring them but - * we need to do that using the IS_* macros defined in include/linux/fs.h. - * In any case ntfs_read_locked_inode() has nothing to do with i_flags. + * i_flags is initialized from the persisted Linux file attributes and + * the driver's system-file protection policy before publishing the inode. * * Return 0 on success and -errno on error. */ @@ -805,6 +862,8 @@ static int ntfs_read_locked_inode(struct inode *vi) goto unm_err_out; } } else { + s64 attr_list_size; + if (vi->i_ino == FILE_MFT) goto skip_attr_list_load; ntfs_debug("Attribute list found in inode 0x%llx.", ni->mft_no); @@ -827,11 +886,14 @@ static int ntfs_read_locked_inode(struct inode *vi) ni->mft_no); } /* Now allocate memory for the attribute list. */ - ni->attr_list_size = (u32)ntfs_attr_size(a); - if (!ni->attr_list_size) { - ntfs_error(vi->i_sb, "Attr_list_size is zero"); + attr_list_size = ntfs_attr_size(a); + if (attr_list_size <= 0 || + attr_list_size > NTFS_MAX_ATTR_LIST_SIZE) { + ntfs_error(vi->i_sb, "Invalid attribute list size %lld (mft_no 0x%llx).", + (long long)attr_list_size, ni->mft_no); goto unm_err_out; } + ni->attr_list_size = (u32)attr_list_size; ni->attr_list = kvzalloc(ni->attr_list_size, GFP_NOFS); if (!ni->attr_list) { ntfs_error(vi->i_sb, @@ -871,6 +933,11 @@ skip_attr_list_load: if (!err) { NInoSetHasEA(ni); ntfs_ea_get_wsl_inode(vi, &dev, flags, &has_lxmod); + err = ntfs_ea_get_linuxflags(vi); + if (err) + goto unm_err_out; + } else if (err != -ENOENT) { + goto unm_err_out; } if (ni->flags & FILE_ATTR_REPARSE_POINT) { @@ -1021,8 +1088,8 @@ view_index_meta: ni->itype.index.vcn_size = vol->cluster_size; ni->itype.index.vcn_size_bits = vol->cluster_size_bits; } else { - ni->itype.index.vcn_size = vol->sector_size; - ni->itype.index.vcn_size_bits = vol->sector_size_bits; + ni->itype.index.vcn_size = NTFS_BLOCK_SIZE; + ni->itype.index.vcn_size_bits = NTFS_BLOCK_SIZE_BITS; } /* Setup the index allocation attribute, even if not present. */ @@ -1133,6 +1200,11 @@ view_index_meta: } if (a->non_resident) { NInoSetNonResident(ni); + if (a->data.non_resident.lowest_vcn) { + ntfs_error(vi->i_sb, + "First extent of $DATA attribute has non zero lowest_vcn."); + goto unm_err_out; + } if (NInoCompressed(ni) || (NInoSparse(ni) && !NInoWofCompressed(ni))) { if (NInoCompressed(ni) && a->data.non_resident.compression_unit != 4) { @@ -1143,14 +1215,21 @@ view_index_meta: goto unm_err_out; } + /* + * Windows stores the standard compression unit in + * sparse attributes even when the data is not compressed. + */ if (NInoSparse(ni) && a->data.non_resident.compression_unit && a->data.non_resident.compression_unit != - vol->sparse_compression_unit) { + vol->sparse_compression_unit && + a->data.non_resident.compression_unit != + STANDARD_COMPRESSION_UNIT) { ntfs_error(vi->i_sb, - "Found non-standard compression unit (%u instead of 0 or %d). Cannot handle this.", + "Found non-standard compression unit (%u instead of 0, %d, or %d). Cannot handle this.", a->data.non_resident.compression_unit, - vol->sparse_compression_unit); + vol->sparse_compression_unit, + STANDARD_COMPRESSION_UNIT); err = -EOPNOTSUPP; goto unm_err_out; } @@ -1174,11 +1253,8 @@ view_index_meta: ni->itype.compressed.size = le64_to_cpu( a->data.non_resident.compressed_size); } - if (a->data.non_resident.lowest_vcn) { - ntfs_error(vi->i_sb, - "First extent of $DATA attribute has non zero lowest_vcn."); + if (ntfs_non_resident_sizes_inconsistent(vi, a)) goto unm_err_out; - } vi->i_size = ni->data_size = le64_to_cpu(a->data.non_resident.data_size); ni->initialized_size = le64_to_cpu(a->data.non_resident.initialized_size); ni->allocated_size = le64_to_cpu(a->data.non_resident.allocated_size); @@ -1286,6 +1362,7 @@ static int ntfs_read_locked_attr_inode(struct inode *base_vi, struct inode *vi) struct attr_record *a; struct ntfs_attr_search_ctx *ctx; int err = 0; + u32 ic; ntfs_debug("Entering for i_ino 0x%llx.", ni->mft_no); @@ -1300,8 +1377,12 @@ static int ntfs_read_locked_attr_inode(struct inode *base_vi, struct inode *vi) inode_set_atime_to_ts(vi, inode_get_atime(base_vi)); vi->i_generation = ni->seq_no = base_ni->seq_no; - /* Set inode type to zero but preserve permissions. */ - vi->i_mode = base_vi->i_mode & ~S_IFMT; + /* Named data streams are regular files throughout initialization. */ + vi->i_mode = base_vi->i_mode & ~S_IFMT; + if (ni->type == AT_DATA && ni->name_len) { + vi->i_mode |= S_IFREG; + vi->i_flags = base_vi->i_flags; + } m = map_mft_record(base_ni); if (IS_ERR(m)) { @@ -1314,11 +1395,29 @@ static int ntfs_read_locked_attr_inode(struct inode *base_vi, struct inode *vi) goto unm_err_out; } /* Find the attribute. */ + ic = ntfs_inode_is_named_stream(ni) ? IGNORE_CASE : CASE_SENSITIVE; err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, - CASE_SENSITIVE, 0, NULL, 0, ctx); + ic, 0, NULL, 0, ctx); if (unlikely(err)) goto unm_err_out; a = ctx->attr; + if (ntfs_inode_is_named_stream(ni)) { + __le16 *disk_name = (__le16 *)((u8 *)a + + le16_to_cpu(a->name_offset)); + + /* Subsequent extent and mutation lookups must select this name. */ + if (ni->name == I30) { + ni->name = kmalloc_array(ni->name_len + 1, + sizeof(__le16), GFP_NOFS); + if (!ni->name) { + err = -ENOMEM; + goto unm_err_out; + } + } + memcpy(ni->name, disk_name, + ni->name_len * sizeof(__le16)); + ni->name[ni->name_len] = 0; + } if (a->flags & (ATTR_COMPRESSION_MASK | ATTR_IS_SPARSE)) { if (a->flags & ATTR_COMPRESSION_MASK) { NInoSetCompressed(ni); @@ -1402,6 +1501,10 @@ static int ntfs_read_locked_attr_inode(struct inode *base_vi, struct inode *vi) } } else { NInoSetNonResident(ni); + if (a->data.non_resident.lowest_vcn) { + ntfs_error(vi->i_sb, "First extent of attribute has non-zero lowest_vcn."); + goto unm_err_out; + } /* * Ensure the attribute name is placed before the mapping pairs * array. @@ -1437,10 +1540,8 @@ static int ntfs_read_locked_attr_inode(struct inode *base_vi, struct inode *vi) ni->itype.compressed.size = le64_to_cpu( a->data.non_resident.compressed_size); } - if (a->data.non_resident.lowest_vcn) { - ntfs_error(vi->i_sb, "First extent of attribute has non-zero lowest_vcn."); + if (ntfs_non_resident_sizes_inconsistent(vi, a)) goto unm_err_out; - } vi->i_size = ni->data_size = le64_to_cpu(a->data.non_resident.data_size); ni->initialized_size = le64_to_cpu(a->data.non_resident.initialized_size); ni->allocated_size = le64_to_cpu(a->data.non_resident.allocated_size); @@ -1450,6 +1551,10 @@ static int ntfs_read_locked_attr_inode(struct inode *base_vi, struct inode *vi) vi->i_blocks = ni->itype.compressed.size >> 9; else vi->i_blocks = ni->allocated_size >> 9; + if (ni->type == AT_DATA && ni->name_len) { + vi->i_op = &ntfs_stream_inode_ops; + vi->i_fop = &ntfs_stream_file_ops; + } /* * Make sure the base inode does not go away and attach it to the * attribute inode. @@ -1612,8 +1717,8 @@ static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi) ni->itype.index.vcn_size = vol->cluster_size; ni->itype.index.vcn_size_bits = vol->cluster_size_bits; } else { - ni->itype.index.vcn_size = vol->sector_size; - ni->itype.index.vcn_size_bits = vol->sector_size_bits; + ni->itype.index.vcn_size = NTFS_BLOCK_SIZE; + ni->itype.index.vcn_size_bits = NTFS_BLOCK_SIZE_BITS; } /* Find index allocation attribute. */ @@ -1670,6 +1775,8 @@ static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi) "First extent of $INDEX_ALLOCATION attribute has non zero lowest_vcn."); goto unm_err_out; } + if (ntfs_non_resident_sizes_inconsistent(vi, a)) + goto unm_err_out; vi->i_size = ni->data_size = le64_to_cpu(a->data.non_resident.data_size); ni->initialized_size = le64_to_cpu(a->data.non_resident.initialized_size); ni->allocated_size = le64_to_cpu(a->data.non_resident.allocated_size); @@ -1955,6 +2062,7 @@ int ntfs_read_inode_mount(struct inode *vi) } else /* if (!err) */ { struct attr_list_entry *al_entry, *next_al_entry; u8 *al_end; + s64 attr_list_size; static const char *es = " Not allowed. $MFT is corrupt. You should run chkdsk."; ntfs_debug("Attribute list attribute found in $MFT."); @@ -1978,11 +2086,14 @@ int ntfs_read_inode_mount(struct inode *vi) "Resident attribute list attribute in $MFT system file is marked encrypted/sparse which is not true. However, Windows allows this and chkdsk does not detect or correct it so we will just ignore the invalid flags and pretend they are not set."); } /* Now allocate memory for the attribute list. */ - ni->attr_list_size = (u32)ntfs_attr_size(a); - if (!ni->attr_list_size) { - ntfs_error(sb, "Attr_list_size is zero"); + attr_list_size = ntfs_attr_size(a); + if (attr_list_size <= 0 || + attr_list_size > NTFS_MAX_ATTR_LIST_SIZE) { + ntfs_error(sb, "Invalid attribute list size %lld (mft_no 0x%llx).%s", + (long long)attr_list_size, ni->mft_no, es); goto put_err_out; } + ni->attr_list_size = (u32)attr_list_size; ni->attr_list = kvzalloc(round_up(ni->attr_list_size, SECTOR_SIZE), GFP_NOFS); if (!ni->attr_list) { @@ -1999,6 +2110,8 @@ int ntfs_read_inode_mount(struct inode *vi) "Attribute list has non zero lowest_vcn. $MFT is corrupt. You should run chkdsk."); goto put_err_out; } + if (ntfs_non_resident_sizes_inconsistent(vi, a)) + goto put_err_out; rl = ntfs_mapping_pairs_decompress(vol, a, NULL, &new_rl_count); if (IS_ERR(rl)) { @@ -2071,6 +2184,11 @@ int ntfs_read_inode_mount(struct inode *vi) /* Now load all attribute extents. */ a = NULL; next_vcn = last_vcn = highest_vcn = 0; + /* + * Reading one of $MFT's own extent records in this loop can re-enter + * ntfs_map_runlist_nolock() for $MFT; see the check there. + */ + NVolSetMftBootstrap(vol); while (!(err = ntfs_attr_lookup(AT_DATA, NULL, 0, 0, next_vcn, NULL, 0, ctx))) { struct runlist_element *nrl; @@ -2123,6 +2241,16 @@ int ntfs_read_inode_mount(struct inode *vi) ni->initialized_size = le64_to_cpu(a->data.non_resident.initialized_size); ni->allocated_size = le64_to_cpu(a->data.non_resident.allocated_size); /* + * Records between allocated_size and data_size are not + * on disk, and would be read as zeros. + */ + if (vi->i_size > ni->allocated_size) { + ntfs_error(sb, + "$MFT data size %lld exceeds its allocated size %lld. $MFT is corrupt. Run chkdsk.", + vi->i_size, ni->allocated_size); + goto put_err_out; + } + /* * Verify the number of mft records does not exceed * 2^32 - 1. */ @@ -2153,6 +2281,7 @@ int ntfs_read_inode_mount(struct inode *vi) err = ntfs_read_locked_inode(vi); if (err) { ntfs_error(sb, "ntfs_read_inode() of $MFT failed.\n"); + NVolClearMftBootstrap(vol); ntfs_attr_put_search_ctx(ctx); /* Revert to the safe super operations. */ kfree(m); @@ -2186,6 +2315,7 @@ int ntfs_read_inode_mount(struct inode *vi) goto put_err_out; } } + NVolClearMftBootstrap(vol); if (err != -ENOENT) { ntfs_error(sb, "Failed to lookup $MFT/$DATA attribute extent. Run chkdsk.\n"); goto put_err_out; @@ -2220,6 +2350,8 @@ em_put_err_out: put_err_out: ntfs_attr_put_search_ctx(ctx); err_out: + /* Also reached from inside the $DATA loop. */ + NVolClearMftBootstrap(vol); ntfs_error(sb, "Failed. Marking inode as bad."); kfree(m); return -1; @@ -2413,6 +2545,10 @@ int ntfs_show_options(struct seq_file *sf, struct dentry *root) seq_puts(sf, ",symlink=native"); else seq_puts(sf, ",symlink=wsl"); + if (NVolStreamsWindows(vol)) + seq_puts(sf, ",streams_interface=windows"); + else + seq_puts(sf, ",streams_interface=none"); if (vol->sb->s_flags & SB_POSIXACL) seq_puts(sf, ",acl"); return 0; @@ -2422,53 +2558,70 @@ int ntfs_extend_initialized_size(struct inode *vi, const loff_t offset, const loff_t new_size) { struct ntfs_inode *ni = NTFS_I(vi); - loff_t old_init_size; + struct ntfs_inode *mrec_ni = ntfs_base_inode(ni); + loff_t old_init_size, clear_size; unsigned long flags; - int err; + int err = 0; + if (!NInoNonResident(ni)) + return -EINVAL; + + filemap_invalidate_lock(vi->i_mapping); read_lock_irqsave(&ni->size_lock, flags); old_init_size = ni->initialized_size; read_unlock_irqrestore(&ni->size_lock, flags); - if (!NInoNonResident(ni)) - return -EINVAL; if (old_init_size >= new_size) - return 0; + goto out; err = ntfs_attr_map_whole_runlist(ni); if (err) - return err; + goto out; - if (!NInoCompressed(ni) && old_init_size < offset) { - err = iomap_zero_range(vi, old_init_size, - offset - old_init_size, - NULL, &ntfs_seek_iomap_ops, - &ntfs_iomap_folio_ops, NULL); - if (err) - return err; + if (!NInoCompressed(ni)) { + /* + * Zeroing skips holes, so discard cached uninitialized bytes + * before making them visible to writes or shared mappings. + */ + clear_size = max_t(loff_t, i_size_read(vi), new_size); + truncate_pagecache_range(vi, old_init_size, clear_size - 1); + + if (old_init_size < offset) { + err = iomap_zero_range(vi, old_init_size, + offset - old_init_size, + NULL, &ntfs_seek_iomap_ops, + &ntfs_iomap_folio_ops, NULL); + if (err) + goto out; + } } - - mutex_lock(&ni->mrec_lock); + mutex_lock(&mrec_ni->mrec_lock); err = ntfs_attr_set_initialized_size(ni, new_size); - mutex_unlock(&ni->mrec_lock); + mutex_unlock(&mrec_ni->mrec_lock); if (err) truncate_setsize(vi, old_init_size); +out: + filemap_invalidate_unlock(vi->i_mapping); return err; } int ntfs_truncate_vfs(struct inode *vi, loff_t new_size, loff_t i_size) { struct ntfs_inode *ni = NTFS_I(vi); + struct ntfs_inode *mrec_ni = ntfs_base_inode(ni); int err; - mutex_lock(&ni->mrec_lock); + mutex_lock(&mrec_ni->mrec_lock); err = __ntfs_attr_truncate_vfs(ni, new_size, i_size); - mutex_unlock(&ni->mrec_lock); + mutex_unlock(&mrec_ni->mrec_lock); if (err < 0) return err; - inode_set_mtime_to_ts(vi, inode_set_ctime_current(vi)); + if (ntfs_inode_is_named_stream(ni)) + ntfs_stream_update_base_time(mrec_ni); + else + inode_set_mtime_to_ts(vi, inode_set_ctime_current(vi)); return 0; } @@ -2915,8 +3068,7 @@ err_out: * abort if deprotection or checks fail. * * Finally attach the ntfs inode to its base inode @base_ni and return a - * pointer to the ntfs_inode structure on success or NULL on error, with errno - * set to the error code. + * pointer to the ntfs_inode structure on success or ERR_PTR() on error. * * Note, extent inodes are never closed directly. They are automatically * disposed off by the closing of the base inode. @@ -2932,7 +3084,7 @@ static struct ntfs_inode *ntfs_extent_inode_open(struct ntfs_inode *base_ni, struct super_block *sb; if (!base_ni) - return NULL; + return ERR_PTR(-EINVAL); sb = base_ni->vol->sb; ntfs_debug("Opening extent inode %llu (base mft record %llu).\n", @@ -2951,7 +3103,7 @@ static struct ntfs_inode *ntfs_extent_inode_open(struct ntfs_inode *base_ni, if (IS_ERR(ni_mrec)) { ntfs_error(sb, "failed to map mft record for %llu", ni->mft_no); - goto out; + return ERR_CAST(ni_mrec); } /* Verify the sequence number if given. */ seq_no = MSEQNO_LE(mref); @@ -2960,7 +3112,7 @@ static struct ntfs_inode *ntfs_extent_inode_open(struct ntfs_inode *base_ni, ntfs_error(sb, "Found stale extent mft reference mft=%llu", ni->mft_no); unmap_mft_record(ni); - goto out; + return ERR_PTR(-EIO); } unmap_mft_record(ni); goto out; @@ -2969,7 +3121,7 @@ static struct ntfs_inode *ntfs_extent_inode_open(struct ntfs_inode *base_ni, /* Wasn't there, we need to load the extent inode. */ ni = ntfs_new_extent_inode(base_ni->vol->sb, mft_no); if (!ni) - goto out; + return ERR_PTR(-ENOMEM); ni->seq_no = (u16)MSEQNO_LE(mref); ni->nr_extents = -1; @@ -2979,8 +3131,10 @@ static struct ntfs_inode *ntfs_extent_inode_open(struct ntfs_inode *base_ni, i = (base_ni->nr_extents + 4) * sizeof(struct ntfs_inode *); extent_nis = kvzalloc(i, GFP_NOFS); - if (!extent_nis) - goto err_out; + if (!extent_nis) { + ntfs_destroy_ext_inode(ni); + return ERR_PTR(-ENOMEM); + } if (base_ni->nr_extents) { memcpy(extent_nis, base_ni->ext.extent_ntfs_inos, i - 4 * sizeof(struct ntfs_inode *)); @@ -2993,10 +3147,6 @@ static struct ntfs_inode *ntfs_extent_inode_open(struct ntfs_inode *base_ni, out: ntfs_debug("\n"); return ni; -err_out: - ntfs_destroy_ext_inode(ni); - ni = NULL; - goto out; } /* @@ -3034,9 +3184,12 @@ int ntfs_inode_attach_all_extents(struct ntfs_inode *ni) while ((u8 *)ale < ni->attr_list + ni->attr_list_size) { if (ni->mft_no != MREF_LE(ale->mft_reference) && prev_attached != MREF_LE(ale->mft_reference)) { - if (!ntfs_extent_inode_open(ni, ale->mft_reference)) { + struct ntfs_inode *ext_ni; + + ext_ni = ntfs_extent_inode_open(ni, ale->mft_reference); + if (IS_ERR(ext_ni)) { ntfs_debug("Couldn't attach extent inode.\n"); - return -1; + return PTR_ERR(ext_ni); } prev_attached = MREF_LE(ale->mft_reference); } @@ -3540,19 +3693,30 @@ s64 ntfs_inode_attr_pread(struct inode *vi, s64 pos, s64 count, u8 *buf) struct address_space *mapping = vi->i_mapping; struct folio *folio; struct ntfs_inode *ni = NTFS_I(vi); + struct ntfs_inode *base_ni = ni->ext.base_ntfs_ino; + struct ntfs_inode *mrec_ni = ntfs_inode_is_named_stream(ni) ? + base_ni : ni; s64 isize; u32 attr_len, total = 0, offset; pgoff_t index; int err = 0; + bool claimed = false; WARN_ON(!NInoAttr(ni)); if (!count) return 0; - mutex_lock(&ni->mrec_lock); + mutex_lock(&mrec_ni->mrec_lock); + if (ntfs_inode_is_named_stream(ni)) { + err = ntfs_stream_inode_validate(vi); + if (err) { + mutex_unlock(&mrec_ni->mrec_lock); + return err; + } + } isize = i_size_read(vi); if (pos > isize) { - mutex_unlock(&ni->mrec_lock); + mutex_unlock(&mrec_ni->mrec_lock); return -EINVAL; } if (pos + count > isize) @@ -3562,37 +3726,44 @@ s64 ntfs_inode_attr_pread(struct inode *vi, s64 pos, s64 count, u8 *buf) struct ntfs_attr_search_ctx *ctx; u8 *attr; - ctx = ntfs_attr_get_search_ctx(ni->ext.base_ntfs_ino, NULL); + ctx = ntfs_attr_get_search_ctx(base_ni, NULL); if (!ctx) { ntfs_error(vi->i_sb, "Failed to get attr search ctx"); err = -ENOMEM; - mutex_unlock(&ni->mrec_lock); + mutex_unlock(&mrec_ni->mrec_lock); goto out; } - err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, CASE_SENSITIVE, + err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, + CASE_SENSITIVE, 0, NULL, 0, ctx); if (err) { ntfs_error(vi->i_sb, "Failed to look up attr %#x", ni->type); ntfs_attr_put_search_ctx(ctx); - mutex_unlock(&ni->mrec_lock); + mutex_unlock(&mrec_ni->mrec_lock); goto out; } attr = (u8 *)ctx->attr + le16_to_cpu(ctx->attr->data.resident.value_offset); memcpy(buf, (u8 *)attr + pos, count); ntfs_attr_put_search_ctx(ctx); - mutex_unlock(&ni->mrec_lock); + mutex_unlock(&mrec_ni->mrec_lock); return count; } - mutex_unlock(&ni->mrec_lock); + if (ntfs_inode_is_named_stream(ni)) { + atomic_inc(&ni->stream_open_count); + claimed = true; + } + mutex_unlock(&mrec_ni->mrec_lock); index = pos >> PAGE_SHIFT; do { /* Update @index and get the next folio. */ folio = read_mapping_folio(mapping, index, NULL); - if (IS_ERR(folio)) + if (IS_ERR(folio)) { + err = PTR_ERR(folio); break; + } offset = offset_in_folio(folio, pos); attr_len = min_t(size_t, (size_t)count, folio_size(folio) - offset); @@ -3609,6 +3780,8 @@ s64 ntfs_inode_attr_pread(struct inode *vi, s64 pos, s64 count, u8 *buf) index++; } while (count); out: + if (claimed) + ntfs_stream_put(vi); return err ? (s64)err : total; } @@ -3629,14 +3802,15 @@ static inline int ntfs_enlarge_attribute(struct inode *vi, s64 pos, s64 count, return -EOPNOTSUPP; if (pos + count > ni->data_size) { - if (ntfs_attr_truncate(ni, pos + count)) { + ret = ntfs_attr_truncate(ni, pos + count); + if (ret) { ntfs_debug("Failed to truncate attribute"); - return -1; + return ret; } ntfs_attr_reinit_search_ctx(ctx); - ret = ntfs_attr_lookup(ni->type, - ni->name, ni->name_len, CASE_SENSITIVE, + ret = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, + CASE_SENSITIVE, 0, NULL, 0, ctx); if (ret) { ntfs_error(sb, "Failed to look up attr %#x", ni->type); @@ -3650,6 +3824,13 @@ static inline int ntfs_enlarge_attribute(struct inode *vi, s64 pos, s64 count, return 0; } + /* Named streams initialize gaps after dropping the MFT record lock. */ + if (ntfs_inode_is_named_stream(ni)) { + if (i_size_read(vi) < ni->data_size) + i_size_write(vi, ni->data_size); + return 0; + } + if (pos + count > ni->initialized_size) { ctx->attr->data.non_resident.initialized_size = cpu_to_le64(pos + count); mark_mft_record_dirty(ctx->ntfs_ino); @@ -3662,7 +3843,8 @@ static inline int ntfs_enlarge_attribute(struct inode *vi, s64 pos, s64 count, static s64 __ntfs_inode_resident_attr_pwrite(struct inode *vi, s64 pos, s64 count, u8 *buf, - struct ntfs_attr_search_ctx *ctx) + struct ntfs_attr_search_ctx *ctx, + bool *modified) { struct ntfs_inode *ni = NTFS_I(vi); struct folio *folio; @@ -3680,6 +3862,8 @@ static s64 __ntfs_inode_resident_attr_pwrite(struct inode *vi, addr = (u8 *)ctx->attr + le16_to_cpu(ctx->attr->data.resident.value_offset); memcpy(addr + pos, buf, count); mark_mft_record_dirty(ctx->ntfs_ino); + if (count) + *modified = true; /* Keep the first page clean and uptodate */ folio = __filemap_get_folio(mapping, 0, FGP_WRITEBEGIN | FGP_NOFS, @@ -3704,8 +3888,7 @@ out: static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi, s64 pos, s64 count, u8 *buf, - struct ntfs_attr_search_ctx *ctx, - bool sync) + bool sync, bool *modified) { struct ntfs_inode *ni = NTFS_I(vi); struct address_space *mapping = vi->i_mapping; @@ -3719,7 +3902,7 @@ static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi, index = pos >> PAGE_SHIFT; while (count) { - if (count == PAGE_SIZE) { + if (count == PAGE_SIZE && !offset_in_page(pos)) { folio = __filemap_get_folio(vi->i_mapping, index, FGP_CREAT | FGP_LOCK, mapping_gfp_mask(mapping)); @@ -3739,7 +3922,9 @@ static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi, folio_lock(folio); } - if (count == PAGE_SIZE) { + folio_wait_writeback(folio); + + if (count == PAGE_SIZE && !offset_in_page(pos)) { offset = 0; attr_len = count; } else { @@ -3747,6 +3932,7 @@ static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi, attr_len = min_t(size_t, (size_t)count, folio_size(folio) - offset); } memcpy_to_folio(folio, offset, buf, attr_len); + *modified = true; if (sync) { struct ntfs_volume *vol = ni->vol; @@ -3758,7 +3944,9 @@ static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi, struct runlist_element *rl; int bio_err; - lcn_count = max_t(s64, 1, ntfs_bytes_to_cluster(vol, attr_len)); + lcn_count = max_t(s64, 1, + ntfs_bytes_to_cluster(vol, attr_len + + vol->cluster_size - 1)); vcn = ntfs_pidx_to_cluster(vol, folio->index); do { @@ -3839,37 +4027,88 @@ err_unlock_folio: s64 ntfs_inode_attr_pwrite(struct inode *vi, s64 pos, s64 count, u8 *buf, bool sync) { struct ntfs_inode *ni = NTFS_I(vi); + struct ntfs_inode *base_ni = ni->ext.base_ntfs_ino; + struct ntfs_inode *mrec_ni = ntfs_inode_is_named_stream(ni) ? + base_ni : ni; struct ntfs_attr_search_ctx *ctx; + loff_t old_data_size, old_init_size; s64 ret; + bool modified = false; WARN_ON(!NInoAttr(ni)); - ctx = ntfs_attr_get_search_ctx(ni->ext.base_ntfs_ino, NULL); + if (ntfs_inode_is_named_stream(ni)) { + if (NInoEncrypted(ni)) + return -EACCES; + if (NInoCompressed(ni)) + return -EOPNOTSUPP; + } + + mutex_lock(&mrec_ni->mrec_lock); + if (ntfs_inode_is_named_stream(ni)) { + ret = ntfs_stream_inode_validate(vi); + if (ret) + goto out_unlock; + } + + ctx = ntfs_attr_get_search_ctx(base_ni, NULL); if (!ctx) { ntfs_error(vi->i_sb, "Failed to get attr search ctx"); - return -ENOMEM; + ret = -ENOMEM; + goto out_unlock; } - ret = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, CASE_SENSITIVE, + ret = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, + CASE_SENSITIVE, 0, NULL, 0, ctx); if (ret) { ntfs_attr_put_search_ctx(ctx); ntfs_error(vi->i_sb, "Failed to look up attr %#x", ni->type); - return ret; + goto out_unlock; } - mutex_lock(&ni->mrec_lock); + old_data_size = ni->data_size; + old_init_size = ni->initialized_size; ret = ntfs_enlarge_attribute(vi, pos, count, ctx); - mutex_unlock(&ni->mrec_lock); + modified = ni->data_size != old_data_size || + ni->initialized_size != old_init_size; if (ret) - goto out; + goto out_ctx; - if (NInoNonResident(ni)) - ret = __ntfs_inode_non_resident_attr_pwrite(vi, pos, count, buf, ctx, sync); - else - ret = __ntfs_inode_resident_attr_pwrite(vi, pos, count, buf, ctx); -out: + if (!NInoNonResident(ni)) { + ret = __ntfs_inode_resident_attr_pwrite(vi, pos, count, buf, ctx, + &modified); + goto out_ctx; + } + + if (ntfs_inode_is_named_stream(ni)) + atomic_inc(&ni->stream_open_count); + ntfs_attr_put_search_ctx(ctx); + mutex_unlock(&mrec_ni->mrec_lock); + /* Attribute writes bypass iomap's delayed-allocation preparation. */ + if (ntfs_inode_is_named_stream(ni)) { + /* A failed write must not expose any newly initialized data. */ + ret = ntfs_extend_initialized_size(vi, pos + count, pos + count); + modified |= ni->initialized_size != old_init_size; + if (!ret) + ret = ntfs_attr_fallocate(ni, pos, count, true); + } + if (!ret) + ret = __ntfs_inode_non_resident_attr_pwrite(vi, pos, count, + buf, sync, + &modified); + if (ntfs_inode_is_named_stream(ni)) + ntfs_stream_put(vi); + if (modified && ntfs_inode_is_named_stream(ni)) + ntfs_stream_update_base_time(base_ni); + return ret; + +out_ctx: ntfs_attr_put_search_ctx(ctx); +out_unlock: + mutex_unlock(&mrec_ni->mrec_lock); + if (modified && ntfs_inode_is_named_stream(ni)) + ntfs_stream_update_base_time(base_ni); return ret; } diff --git a/fs/ntfs/inode.h b/fs/ntfs/inode.h index 45a396c97846..a18cbcf1c631 100644 --- a/fs/ntfs/inode.h +++ b/fs/ntfs/inode.h @@ -95,6 +95,7 @@ enum ntfs_inode_mutex_lock_class { * belongs. * @i_dealloc_clusters: delayed allocated clusters. * @target: symlink buffer. + * @linuxflags: Linux-specific file attributes loaded from the $LINUXFLAGS EA. */ struct ntfs_inode { rwlock_t size_lock; @@ -107,6 +108,7 @@ struct ntfs_inode { __le32 type; __le16 *name; u32 name_len; + atomic_t stream_open_count; struct runlist runlist; s64 data_size; s64 initialized_size; @@ -145,6 +147,7 @@ struct ntfs_inode { __le32 reparse_tag; __le32 reparse_flags; char *target; + u32 linuxflags; }; /* @@ -178,6 +181,7 @@ struct ntfs_inode { * NI_BeingCreated ntfs inode is being created. * NI_HasEA ntfs inode has EA attribute. * NI_RunlistDirty runlist need to be updated. + * NI_StreamUnlinked Named stream is unlinked but still open. */ enum { NI_Dirty, @@ -199,6 +203,7 @@ enum { NI_BeingCreated, NI_HasEA, NI_RunlistDirty, + NI_StreamUnlinked, }; /* @@ -259,6 +264,7 @@ TAS_NINO_FNS(FileNameDirty) NINO_FNS(BeingDeleted) NINO_FNS(HasEA) NINO_FNS(RunlistDirty) +NINO_FNS(StreamUnlinked) /* * The full structure containing a ntfs_inode and a vfs struct inode. Used for @@ -286,6 +292,20 @@ static inline struct inode *VFS_I(struct ntfs_inode *ni) return &container_of(ni, struct big_ntfs_inode, ntfs_inode)->vfs_inode; } +/* Return true when @ni represents a named $DATA attribute inode. */ +static inline bool ntfs_inode_is_named_stream(struct ntfs_inode *ni) +{ + return NInoAttr(ni) && ni->type == AT_DATA && ni->name_len; +} + +/* Return the base MFT inode for an attribute inode, or @ni itself. */ +static inline struct ntfs_inode *ntfs_base_inode(struct ntfs_inode *ni) +{ + if (NInoAttr(ni) && ni->nr_extents == -1 && ni->ext.base_ntfs_ino) + return ni->ext.base_ntfs_ino; + return ni; +} + /* * ntfs_attr - ntfs in memory attribute structure * @@ -304,6 +324,7 @@ struct ntfs_attr { }; int ntfs_test_inode(struct inode *vi, void *data); +int ntfs_test_inode_rcu(struct inode *vi, void *data); struct inode *ntfs_iget(struct super_block *sb, u64 mft_no); struct inode *ntfs_attr_iget(struct inode *base_vi, __le32 type, __le16 *name, u32 name_len); diff --git a/fs/ntfs/iomap.c b/fs/ntfs/iomap.c index c812d7f19b36..6e51cce3aeba 100644 --- a/fs/ntfs/iomap.c +++ b/fs/ntfs/iomap.c @@ -90,11 +90,7 @@ static int ntfs_read_iomap_begin_resident(struct inode *inode, loff_t offset, lo int err = 0; char *kattr; - if (NInoAttr(ni)) - base_ni = ni->ext.base_ntfs_ino; - else - base_ni = ni; - + base_ni = ntfs_base_inode(ni); mutex_lock(&base_ni->mrec_lock); ctx = ntfs_attr_get_search_ctx(base_ni, NULL); @@ -140,7 +136,8 @@ out: if (ctx) ntfs_attr_put_search_ctx(ctx); - if (!err && keep_mrec_lock && iomap->type == IOMAP_INLINE) { + if (!err && keep_mrec_lock && + iomap->type == IOMAP_INLINE) { iomap->private = base_ni; return 0; } @@ -390,11 +387,13 @@ static int ntfs_write_simple_iomap_begin_non_resident(struct inode *inode, loff_ loff_t length, struct iomap *iomap) { struct ntfs_inode *ni = NTFS_I(inode); + struct ntfs_inode *mrec_ni = ntfs_inode_is_named_stream(ni) ? + ntfs_base_inode(ni) : ni; struct ntfs_volume *vol = ni->vol; loff_t vcn_ofs, rl_length; struct runlist_element *rl, *rlc; bool is_retry = false; - int err = 0; + int err = 0, map_err = 0; s64 vcn, lcn; s64 max_clu_count = ntfs_bytes_to_cluster(vol, round_up(length, vol->cluster_size)); @@ -408,7 +407,7 @@ static int ntfs_write_simple_iomap_begin_non_resident(struct inode *inode, loff_ up_read(&ni->runlist.lock); err = ntfs_map_runlist(ni, vcn); if (err) { - mutex_unlock(&ni->mrec_lock); + mutex_unlock(&mrec_ni->mrec_lock); return -ENOENT; } down_read(&ni->runlist.lock); @@ -422,26 +421,38 @@ remap_rl: rl = __ntfs_attr_find_vcn_nolock(&ni->runlist, vcn); if (IS_ERR(rl)) { up_write(&ni->runlist.lock); - mutex_unlock(&ni->mrec_lock); + mutex_unlock(&mrec_ni->mrec_lock); return -EIO; } lcn = ntfs_rl_vcn_to_lcn(rl, vcn); if (lcn <= LCN_RL_NOT_MAPPED && is_retry == false) { is_retry = true; - if (!ntfs_map_runlist_nolock(ni, vcn, NULL)) { + map_err = ntfs_map_runlist_nolock(ni, vcn, NULL); + if (!map_err) { rl = ni->runlist.rl; goto remap_rl; } } + /* + * As in ntfs_attr_vcn_to_rl(): a runlist fragment that could not be + * mapped is not a hole. Treating it as one would put a delalloc + * extent over clusters that are allocated on disk but unknown to us. + */ + if (lcn == LCN_RL_NOT_MAPPED) { + up_write(&ni->runlist.lock); + mutex_unlock(&mrec_ni->mrec_lock); + return map_err == -ENOMEM ? -ENOMEM : -EIO; + } + max_clu_count = min(max_clu_count, rl->length - (vcn - rl->vcn)); if (max_clu_count == 0) { ntfs_error(inode->i_sb, "runlist(vcn : %lld, length : %lld) is corrupted\n", rl->vcn, rl->length); up_write(&ni->runlist.lock); - mutex_unlock(&ni->mrec_lock); + mutex_unlock(&mrec_ni->mrec_lock); return -EIO; } @@ -455,7 +466,7 @@ remap_rl: if (max_clu_count < 0) { err = max_clu_count; up_write(&ni->runlist.lock); - mutex_unlock(&ni->mrec_lock); + mutex_unlock(&mrec_ni->mrec_lock); return err; } } @@ -470,7 +481,7 @@ remap_rl: GFP_NOFS); if (!rlc) { up_write(&ni->runlist.lock); - mutex_unlock(&ni->mrec_lock); + mutex_unlock(&mrec_ni->mrec_lock); return -ENOMEM; } @@ -487,7 +498,7 @@ remap_rl: if (IS_ERR(rl)) { ntfs_error(vol->sb, "Failed to merge runlists"); up_write(&ni->runlist.lock); - mutex_unlock(&ni->mrec_lock); + mutex_unlock(&mrec_ni->mrec_lock); kvfree(rlc); return PTR_ERR(rl); } @@ -497,7 +508,7 @@ remap_rl: ni->i_dealloc_clusters += max_clu_count; } up_write(&ni->runlist.lock); - mutex_unlock(&ni->mrec_lock); + mutex_unlock(&mrec_ni->mrec_lock); if (lcn < LCN_DELALLOC) ntfs_hold_dirty_clusters(vol, max_clu_count); @@ -549,7 +560,7 @@ remap_rl: } } else { up_write(&ni->runlist.lock); - mutex_unlock(&ni->mrec_lock); + mutex_unlock(&mrec_ni->mrec_lock); iomap->type = IOMAP_MAPPED; iomap->addr = ntfs_cluster_to_bytes(vol, lcn) + vcn_ofs; @@ -574,6 +585,8 @@ static int ntfs_write_da_iomap_begin_non_resident(struct inode *inode, struct iomap *iomap, int ntfs_iomap_flags) { struct ntfs_inode *ni = NTFS_I(inode); + struct ntfs_inode *mrec_ni = ntfs_inode_is_named_stream(ni) ? + ntfs_base_inode(ni) : ni; struct ntfs_volume *vol = ni->vol; loff_t vcn_ofs, rl_length; s64 vcn, start_lcn, lcn_count; @@ -592,7 +605,7 @@ static int ntfs_write_da_iomap_begin_non_resident(struct inode *inode, max_clu_count, &balloc, update_mp, ntfs_iomap_flags & NTFS_IOMAP_FLAGS_WRITEBACK); up_write(&ni->runlist.lock); - mutex_unlock(&ni->mrec_lock); + mutex_unlock(&mrec_ni->mrec_lock); if (err) { ni->i_dealloc_clusters = 0; return err; @@ -646,8 +659,8 @@ static int ntfs_write_da_iomap_begin_non_resident(struct inode *inode, if (ntfs_iomap_flags & NTFS_IOMAP_FLAGS_MKWRITE && iomap->offset + iomap->length > ni->initialized_size) { - err = ntfs_attr_set_initialized_size(ni, iomap->offset + - iomap->length); + err = ntfs_attr_set_initialized_size(ni, + iomap->offset + iomap->length); } return err; @@ -657,13 +670,15 @@ static int ntfs_write_iomap_begin_resident(struct inode *inode, loff_t offset, struct iomap *iomap) { struct ntfs_inode *ni = NTFS_I(inode); + struct ntfs_inode *mrec_ni = ntfs_inode_is_named_stream(ni) ? + ntfs_base_inode(ni) : ni; struct attr_record *a; - struct ntfs_attr_search_ctx *ctx; + struct ntfs_attr_search_ctx *ctx = NULL; u32 attr_len; int err = 0; char *kattr; - ctx = ntfs_attr_get_search_ctx(ni, NULL); + ctx = ntfs_attr_get_search_ctx(mrec_ni, NULL); if (!ctx) { err = -ENOMEM; goto out; @@ -686,13 +701,14 @@ static int ntfs_write_iomap_begin_resident(struct inode *inode, loff_t offset, iomap->inline_data = kattr; iomap->offset = 0; iomap->length = attr_len; + iomap->private = mrec_ni; out: if (ctx) ntfs_attr_put_search_ctx(ctx); if (err) - mutex_unlock(&ni->mrec_lock); + mutex_unlock(&mrec_ni->mrec_lock); return err; } @@ -701,7 +717,12 @@ 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) { - mutex_lock(&NTFS_I(inode)->mrec_lock); + struct ntfs_inode *ni = NTFS_I(inode); + struct ntfs_inode *mrec_ni = ntfs_inode_is_named_stream(ni) ? + ntfs_base_inode(ni) : ni; + + mutex_lock(&mrec_ni->mrec_lock); + if (ntfs_iomap_flags & NTFS_IOMAP_FLAGS_BEGIN) return ntfs_write_simple_iomap_begin_non_resident(inode, offset, length, iomap); @@ -717,12 +738,15 @@ 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); + struct ntfs_inode *mrec_ni; if (NVolShutdown(ni->vol)) return -EIO; if (!NInoNonResident(ni)) { - mutex_lock(&ni->mrec_lock); + mrec_ni = ntfs_inode_is_named_stream(ni) ? + ntfs_base_inode(ni) : ni; + mutex_lock(&mrec_ni->mrec_lock); return ntfs_write_iomap_begin_resident(inode, offset, iomap); } return ntfs_write_iomap_begin_non_resident(inode, offset, length, flags, @@ -741,10 +765,10 @@ static int ntfs_write_iomap_end_resident(struct inode *inode, loff_t pos, loff_t length, ssize_t written, unsigned int flags, struct iomap *iomap) { - struct ntfs_inode *ni = NTFS_I(inode); + struct ntfs_inode *base_ni = iomap->private; - mark_mft_record_dirty(ni); - mutex_unlock(&ni->mrec_lock); + mark_mft_record_dirty(base_ni); + mutex_unlock(&base_ni->mrec_lock); return written; } diff --git a/fs/ntfs/layout.h b/fs/ntfs/layout.h index 9438fd9b668e..2de83d4ca40b 100644 --- a/fs/ntfs/layout.h +++ b/fs/ntfs/layout.h @@ -811,14 +811,17 @@ enum { * on XP SP2+. * @data.non_resident.reserved: 5 bytes for 8-byte alignment. * @data.non_resident.allocated_size: - * Allocated disk space in bytes. - * For compressed: logical allocated size. + * Allocated size in bytes, a multiple of + * the cluster size. For compressed and + * sparse attributes holes count as + * allocated; the clusters actually in use + * are in compressed_size. * @data.non_resident.data_size: Logical attribute value size in bytes. - * Can be larger than allocated_size if - * compressed/sparse. + * Never larger than allocated_size, also + * when compressed/sparse. * @data.non_resident.initialized_size: * Initialized portion size in bytes. - * Usually equals data_size. + * Usually equals data_size, never larger. * @data.non_resident.compressed_size: * Compressed on-disk size in bytes. * Only present when compressed or sparse. @@ -1880,8 +1883,8 @@ struct index_header { * @index_block_size: Size of each index block in bytes * (in $INDEX_ALLOCATION). * @clusters_per_index_block: - * Clusters per index block (or log2(bytes) - * if < cluster). + * Clusters per index block, or 512-byte units when + * the index block is smaller than a cluster. * Power of 2; used for encoding block size. * @reserved: 3 bytes reserved/alignment (zero). * @index: Index header for root entries (entries follow @@ -1921,7 +1924,7 @@ struct index_root { * @lsn: Log sequence number of last modification. * @index_block_vcn: VCN of this index block. * Units: clusters if cluster_size <= index_block_size; - * sectors otherwise. + * 512-byte blocks otherwise. * @index: Index header describing entries in this block. * * When creating the index block, we place the update sequence array at this diff --git a/fs/ntfs/lcnalloc.c b/fs/ntfs/lcnalloc.c index 0d6cd08ee2e7..3ebcac4c2864 100644 --- a/fs/ntfs/lcnalloc.c +++ b/fs/ntfs/lcnalloc.c @@ -376,6 +376,16 @@ struct runlist_element *ntfs_cluster_alloc(struct ntfs_volume *vol, const s64 st continue; } /* + * A contiguous request gets a single run. The scan can + * move on without testing the clusters after the run + * (past a bitmap page that is full, to pass 2 or to + * another zone), so return the run when this cluster + * does not follow it. + */ + if (is_contig && rlpos && + lcn + bmp_pos != prev_lcn + prev_run_len) + goto out; + /* * Allocate more memory if needed, including space for * the terminator element. * kvzalloc() operates on whole pages only. @@ -506,13 +516,32 @@ done: } if (!used_zone_pos) { + /* + * The run at @start_lcn reached the end of the buffer + * or the zone. A contiguous request gets that run + * alone, not a second one from the zone position. + */ + if (is_contig && rlpos) + goto out; + /* + * Leaving @start_lcn for the zone position starts the + * zone over as if no hint had been given, even if the + * search had already reached pass 2, whose range ends + * at @start_lcn. + */ used_zone_pos = 1; - if (search_zone == 1) + has_guess = 0; + pass = 1; + if (search_zone == 1) { zone_start = vol->mft_zone_pos; - else if (search_zone == 2) + zone_end = vol->mft_zone_end; + } else if (search_zone == 2) { zone_start = vol->data1_zone_pos; - else + zone_end = vol->nr_clusters; + } else { zone_start = vol->data2_zone_pos; + zone_end = vol->mft_zone_start; + } if (!zone_start || zone_start == vol->mft_zone_start || zone_start == vol->mft_zone_end) diff --git a/fs/ntfs/logfile.c b/fs/ntfs/logfile.c index 1404664dacc0..ce38ce87f8fc 100644 --- a/fs/ntfs/logfile.c +++ b/fs/ntfs/logfile.c @@ -457,7 +457,7 @@ bool ntfs_check_logfile(struct inode *log_vi, struct restart_page_header **rp) struct ntfs_volume *vol = NTFS_SB(log_vi->i_sb); struct address_space *mapping = log_vi->i_mapping; struct folio *folio = NULL; - u8 *kaddr = NULL; + u8 *kaddr = NULL, *kbase = NULL; struct restart_page_header *rstr1_ph = NULL; struct restart_page_header *rstr2_ph = NULL; int log_page_size, err; @@ -511,7 +511,7 @@ bool ntfs_check_logfile(struct inode *log_vi, struct restart_page_header **rp) if (!folio || folio->index != idx) { if (folio) { - kunmap_local(kaddr); + kunmap_local(kbase); folio_put(folio); } folio = read_mapping_folio(mapping, idx, NULL); @@ -520,8 +520,9 @@ bool ntfs_check_logfile(struct inode *log_vi, struct restart_page_header **rp) idx); goto err_out; } + kbase = kmap_local_folio(folio, 0); } - kaddr = (u8 *)kmap_local_folio(folio, 0) + (pos & ~PAGE_MASK); + kaddr = kbase + (pos & ~PAGE_MASK); /* * A non-empty block means the logfile is not empty while an * empty block after a non-empty block has been encountered @@ -574,7 +575,7 @@ bool ntfs_check_logfile(struct inode *log_vi, struct restart_page_header **rp) * find a valid one further in the file. */ if (err != -EINVAL) { - kunmap_local(kaddr); + kunmap_local(kbase); folio_put(folio); goto err_out; } @@ -583,7 +584,7 @@ bool ntfs_check_logfile(struct inode *log_vi, struct restart_page_header **rp) pos = NTFS_BLOCK_SIZE >> 1; } if (folio) { - kunmap_local(kaddr); + kunmap_local(kbase); folio_put(folio); } if (logfile_is_empty) { diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index 4b7449495375..89288b794348 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -10,7 +10,10 @@ #include <linux/writeback.h> #include <linux/bio.h> +#include <linux/blkdev.h> +#include <linux/completion.h> #include <linux/iomap.h> +#include <linux/math64.h> #include "bitmap.h" #include "lcnalloc.h" @@ -429,81 +432,152 @@ void __mark_mft_record_dirty(struct ntfs_inode *ni) __mark_inode_dirty(VFS_I(base_ni), I_DIRTY_DATASYNC); } -/* - * ntfs_bio_end_io - bio completion callback for MFT record writes - * - * Decrements the folio reference count that was incremented before - * submit_bio(). This prevents a race condition where umount could - * evict the inode and release the folio while I/O is still in flight, - * potentially causing data corruption or use-after-free. - */ -static void ntfs_bio_end_io(struct bio *bio) +struct ntfs_mft_io_unit { + sector_t sector; + unsigned int folio_ofs; + unsigned int len; +}; + +struct ntfs_mft_write_ctx { + struct folio *folio; + struct address_space *mapping; + struct ntfs_volume *vol; + struct completion *done; + int error; + bool writeback_started; + struct bio bio; +}; + +static struct bio_set ntfs_mft_bioset; + +int ntfs_mft_bioset_init(void) +{ + return bioset_init(&ntfs_mft_bioset, BIO_POOL_SIZE, + offsetof(struct ntfs_mft_write_ctx, bio), + BIOSET_NEED_BVECS); +} + +void ntfs_mft_bioset_exit(void) +{ + bioset_exit(&ntfs_mft_bioset); +} + +static void ntfs_mft_end_io(struct bio *bio) { - if (bio->bi_private) - folio_put((struct folio *)bio->bi_private); - bio_put(bio); + struct ntfs_mft_write_ctx *ctx = + container_of(bio, struct ntfs_mft_write_ctx, bio); + struct folio *folio = ctx->folio; + struct completion *done = ctx->done; + int err; + + if (bio->bi_status) + err = blk_status_to_errno(bio->bi_status); + else + err = ctx->error; + if (err) { + mapping_set_error(ctx->mapping, err); + NVolSetErrors(ctx->vol); + ntfs_error(ctx->vol->sb, "I/O error while writing MFT: %d", + err); + } + + if (!done) + bio_put(bio); + folio_end_writeback(folio); + folio_put(folio); + if (done) + complete(done); +} + +static void ntfs_start_mft_writeback(struct ntfs_mft_write_ctx *ctx) +{ + if (ctx->writeback_started) + return; + + inode_attach_wb(ctx->mapping->host, NULL); + folio_get(ctx->folio); + folio_start_writeback(ctx->folio); + ctx->writeback_started = true; +} + +static struct bio *ntfs_alloc_mft_parent_bio(struct ntfs_volume *vol, + struct folio *folio, + struct completion *done) +{ + struct ntfs_mft_write_ctx *ctx; + struct bio *parent; + + parent = bio_alloc_bioset(vol->sb->s_bdev, 1, REQ_OP_WRITE, GFP_NOIO, + &ntfs_mft_bioset); + if (!parent) + return NULL; + + ctx = container_of(parent, struct ntfs_mft_write_ctx, bio); + ctx->folio = folio; + ctx->mapping = folio->mapping; + ctx->vol = vol; + ctx->done = done; + ctx->error = 0; + ctx->writeback_started = false; + parent->bi_end_io = ntfs_mft_end_io; + return parent; } /* - * ntfs_sync_mft_mirror - synchronize an mft record to the mft mirror - * @vol: ntfs volume on which the mft record to synchronize resides - * @mft_no: mft record number of mft record to synchronize - * @m: mapped, mst protected (extent) mft record to synchronize - * - * Write the mapped, mst protected (extent) mft record @m with mft record - * number @mft_no to the mft mirror ($MFTMirr) of the ntfs volume @vol. - * - * On success return 0. On error return -errno and set the volume errors flag - * in the ntfs volume @vol. - * - * NOTE: We always perform synchronous i/o. + * Write one MFT I/O unit to $MFTMirr. The source folio contains the MST + * protected image that will be written to $MFT. */ -int ntfs_sync_mft_mirror(struct ntfs_volume *vol, const u64 mft_no, - struct mft_record *m) +static int ntfs_sync_mft_mirror_unit(struct ntfs_volume *vol, + struct folio *source, u64 mirror_file_ofs, + const struct ntfs_mft_io_unit *unit) { - u8 *kmirr; - struct folio *folio; - unsigned int folio_ofs; - int err = 0; - struct bio *bio; + struct folio *mirror; + struct bio_vec bvec; + struct bio bio; + u64 mirror_size; + unsigned int mirror_ofs; + u8 *src, *dst; + int err; - ntfs_debug("Entering for inode 0x%llx.", mft_no); + if (unlikely(!vol->mftmirr_ino)) + return -EIO; + + mirror_size = (u64)vol->mftmirr_size * vol->mft_record_size; + if (mirror_file_ofs >= mirror_size || + unit->len > mirror_size - mirror_file_ofs) + return -EIO; + if (unit->folio_ofs + unit->len > folio_size(source)) + return -EIO; + + mirror = read_mapping_folio(vol->mftmirr_ino->i_mapping, + mirror_file_ofs >> PAGE_SHIFT, NULL); + if (IS_ERR(mirror)) + return PTR_ERR(mirror); - if (unlikely(!vol->mftmirr_ino)) { - /* This could happen during umount... */ + folio_lock(mirror); + if (folio_test_writeback(mirror)) + folio_wait_writeback(mirror); + mirror_ofs = mirror_file_ofs - folio_pos(mirror); + if (mirror_ofs + unit->len > folio_size(mirror)) { err = -EIO; - goto err_out; - } - /* Get the page containing the mirror copy of the mft record @m. */ - folio = read_mapping_folio(vol->mftmirr_ino->i_mapping, - NTFS_MFT_NR_TO_PIDX(vol, mft_no), NULL); - if (IS_ERR(folio)) { - ntfs_error(vol->sb, "Failed to map mft mirror page."); - err = PTR_ERR(folio); - goto err_out; + goto out_unlock; } - folio_lock(folio); - folio_clear_uptodate(folio); - /* Offset of the mft mirror record inside the page. */ - folio_ofs = NTFS_MFT_NR_TO_POFS(vol, mft_no); - /* The address in the page of the mirror copy of the mft record @m. */ - kmirr = kmap_local_folio(folio, 0) + folio_ofs; - /* Copy the mst protected mft record to the mirror. */ - memcpy(kmirr, m, vol->mft_record_size); - kunmap_local(kmirr); - - bio = bio_alloc(vol->sb->s_bdev, 1, REQ_OP_WRITE, GFP_NOIO); - bio->bi_iter.bi_sector = - ntfs_bytes_to_bio_sector(NTFS_CLU_TO_B(vol, vol->mftmirr_lcn) + - ((u64)folio->index << PAGE_SHIFT) + - folio_ofs); - - if (bio_add_folio(bio, folio, vol->mft_record_size, folio_ofs)) - err = submit_bio_wait(bio); - else + folio_clear_uptodate(mirror); + src = kmap_local_folio(source, unit->folio_ofs); + dst = kmap_local_folio(mirror, mirror_ofs); + memcpy(dst, src, unit->len); + kunmap_local(dst); + kunmap_local(src); + + bio_init(&bio, vol->sb->s_bdev, &bvec, 1, REQ_OP_WRITE); + bio.bi_iter.bi_sector = ntfs_bytes_to_bio_sector( + NTFS_CLU_TO_B(vol, vol->mftmirr_lcn) + mirror_file_ofs); + if (!bio_add_folio(&bio, mirror, unit->len, mirror_ofs)) err = -EIO; - bio_put(bio); + else + err = submit_bio_wait(&bio); + bio_uninit(&bio); /* * The in-memory mirror is now valid because we just memcpy()'d the @@ -512,23 +586,79 @@ int ntfs_sync_mft_mirror(struct ntfs_volume *vol, const u64 mft_no, * the stale on-disk mirror and overwrite this copy. The error is * propagated to the caller via @err. */ - folio_mark_uptodate(folio); + folio_mark_uptodate(mirror); - folio_unlock(folio); - folio_put(folio); - if (likely(!err)) { - ntfs_debug("Done."); - } else { - ntfs_error(vol->sb, "I/O error while writing mft mirror record 0x%llx!", mft_no); -err_out: - ntfs_error(vol->sb, - "Failed to synchronize $MFTMirr (error code %i). Volume will be left marked dirty on umount. Run chkdsk on the partition after umounting to correct this.", - err); - NVolSetErrors(vol); - } +out_unlock: + folio_unlock(mirror); + folio_put(mirror); return err; } +static int ntfs_sync_mft_mirror_record(struct ntfs_volume *vol, + struct folio *source, const u64 mft_no) +{ + u64 mirror_file_ofs = (u64)mft_no * vol->mft_record_size; + struct ntfs_mft_io_unit unit = { + .folio_ofs = NTFS_MFT_NR_TO_POFS(vol, mft_no), + .len = vol->mft_record_size, + }; + + mirror_file_ofs = + round_down(mirror_file_ofs, (u64)vol->mft_io_unit_size); + unit.folio_ofs = round_down(unit.folio_ofs, vol->mft_io_unit_size); + unit.len = vol->mft_io_unit_size; + + return ntfs_sync_mft_mirror_unit(vol, source, mirror_file_ofs, &unit); +} + +static int ntfs_prepare_mft_record_io_units(struct ntfs_inode *ni, + struct ntfs_mft_io_unit units[2]) +{ + struct ntfs_volume *vol = ni->vol; + u64 record_byte = (u64)ni->mft_no * vol->mft_record_size; + u64 disk_byte; + unsigned int nr_units = ni->mft_lcn_count; + unsigned int cluster_ofs; + unsigned int i; + + if (!nr_units || nr_units > ARRAY_SIZE(ni->mft_lcn)) + return -EIO; + for (i = 0; i < nr_units; i++) + if (ni->mft_lcn[i] < 0) + return -EIO; + + cluster_ofs = ntfs_bytes_to_cluster_off(vol, record_byte); + if (vol->mft_io_unit_size > vol->mft_record_size) { + cluster_ofs = round_down(cluster_ofs, vol->mft_io_unit_size); + disk_byte = NTFS_CLU_TO_B(vol, ni->mft_lcn[0]) + cluster_ofs; + units[0] = (struct ntfs_mft_io_unit){ + .sector = ntfs_bytes_to_bio_sector(disk_byte), + .folio_ofs = round_down(ni->folio_ofs, + vol->mft_io_unit_size), + .len = vol->mft_io_unit_size, + }; + return 1; + } + + disk_byte = NTFS_CLU_TO_B(vol, ni->mft_lcn[0]) + cluster_ofs; + units[0] = (struct ntfs_mft_io_unit){ + .sector = ntfs_bytes_to_bio_sector(disk_byte), + .folio_ofs = ni->folio_ofs, + .len = vol->mft_record_size, + }; + if (nr_units == 1) + return 1; + + units[0].len = vol->cluster_size; + disk_byte = NTFS_CLU_TO_B(vol, ni->mft_lcn[1]); + units[1] = (struct ntfs_mft_io_unit){ + .sector = ntfs_bytes_to_bio_sector(disk_byte), + .folio_ofs = ni->folio_ofs + vol->cluster_size, + .len = vol->mft_record_size - vol->cluster_size, + }; + return 2; +} + /* * write_mft_record_nolock - write out a mapped (extent) mft record * @ni: ntfs inode describing the mapped (extent) mft record @@ -541,25 +671,35 @@ err_out: * * We only write the mft record if the ntfs inode @ni is dirty. * - * On success, clean the mft record and return 0. - * On error (specifically ENOMEM), we redirty the record so it can be retried. - * For other errors, we mark the volume with errors. + * On success, clean the mft record and return 0. On ENOMEM, redirty the + * record so it can be retried. Asynchronous callers return success after + * redirtying while synchronous callers receive the error. For other errors, + * mark the volume with errors. + * + * If @sync is false, PG_writeback keeps the folio stable and serializes later + * writers until the I/O completes. */ int write_mft_record_nolock(struct ntfs_inode *ni, struct mft_record *m, int sync) { struct ntfs_volume *vol = ni->vol; struct folio *folio = ni->folio; - int err = 0, i = 0; + DECLARE_COMPLETION_ONSTACK(done); + struct ntfs_mft_io_unit units[2]; + struct ntfs_mft_write_ctx *ctx; + struct bio *parent, *child = NULL; + unsigned int nr_units; + int err = 0; u8 *kaddr; struct mft_record *fixup_m; - struct bio *bio; - unsigned int offset = 0, folio_size; ntfs_debug("Entering for inode 0x%llx.", ni->mft_no); WARN_ON(NInoAttr(ni)); WARN_ON(!folio_test_locked(folio)); + if (folio_test_writeback(folio)) + folio_wait_writeback(folio); + /* * If the struct ntfs_inode is clean no need to do anything. If it is dirty, * mark it as clean now so that it can be redirtied later on if needed. @@ -569,6 +709,11 @@ int write_mft_record_nolock(struct ntfs_inode *ni, struct mft_record *m, int syn if (!NInoTestClearDirty(ni)) goto done; + err = ntfs_prepare_mft_record_io_units(ni, units); + if (err < 0) + goto err_out; + nr_units = err; + kaddr = kmap_local_folio(folio, 0); fixup_m = (struct mft_record *)(kaddr + ni->folio_ofs); memcpy(fixup_m, m, vol->mft_record_size); @@ -580,68 +725,74 @@ int write_mft_record_nolock(struct ntfs_inode *ni, struct mft_record *m, int syn goto unmap_err_out; } - folio_size = vol->mft_record_size / ni->mft_lcn_count; - while (i < ni->mft_lcn_count) { - unsigned int clu_off; - - clu_off = (unsigned int)((s64)ni->mft_no * vol->mft_record_size + offset) & - vol->cluster_size_mask; - - bio = bio_alloc(vol->sb->s_bdev, 1, REQ_OP_WRITE, GFP_NOIO); - bio->bi_iter.bi_sector = - ntfs_bytes_to_bio_sector(NTFS_CLU_TO_B(vol, ni->mft_lcn[i]) + - clu_off); + parent = ntfs_alloc_mft_parent_bio(vol, folio, sync ? &done : NULL); + if (!parent) { + err = -ENOMEM; + goto unmap_err_out; + } + ctx = container_of(parent, struct ntfs_mft_write_ctx, bio); - if (!bio_add_folio(bio, folio, folio_size, - ni->folio_ofs + offset)) { - err = -EIO; - goto put_bio_out; + parent->bi_iter.bi_sector = units[0].sector; + if (!bio_add_folio(parent, folio, units[0].len, units[0].folio_ofs)) { + err = -EIO; + goto put_bios_out; + } + + if (nr_units == 2) { + if (bio_end_sector(parent) != units[1].sector || + !bio_add_folio(parent, folio, units[1].len, + units[1].folio_ofs)) { + child = bio_alloc(vol->sb->s_bdev, 1, REQ_OP_WRITE, + GFP_NOIO); + if (!child) { + err = -ENOMEM; + goto put_bios_out; + } + child->bi_iter.bi_sector = units[1].sector; + if (!bio_add_folio(child, folio, units[1].len, + units[1].folio_ofs)) { + err = -EIO; + goto put_bios_out; + } } + } - /* Synchronize the mft mirror now if not @sync. */ - if (!sync && ni->mft_no < vol->mftmirr_size) { - int sub_err = ntfs_sync_mft_mirror(vol, ni->mft_no, - fixup_m); - if (unlikely(sub_err) && !err) - err = sub_err; - } + if (ni->mft_no < vol->mftmirr_size) { + err = ntfs_sync_mft_mirror_record(vol, folio, ni->mft_no); + if (err) + ctx->error = err; + } - if (sync) { - int sub_err = submit_bio_wait(bio); + kunmap_local(kaddr); - bio_put(bio); - if (unlikely(sub_err) && !err) - err = sub_err; - } else { - folio_get(folio); - bio->bi_private = folio; - bio->bi_end_io = ntfs_bio_end_io; - submit_bio(bio); - } - offset += vol->cluster_size; - i++; + ntfs_start_mft_writeback(ctx); + if (child) { + bio_chain(child, parent); + submit_bio(child); } + submit_bio(parent); - /* If @sync, now synchronize the mft mirror. */ - if (sync && ni->mft_no < vol->mftmirr_size) { - int sub_err = ntfs_sync_mft_mirror(vol, ni->mft_no, fixup_m); - - if (unlikely(sub_err) && !err) - err = sub_err; + if (sync) { + wait_for_completion(&done); + if (!err && parent->bi_status) + err = blk_status_to_errno(parent->bi_status); + bio_put(parent); } - kunmap_local(kaddr); + if (unlikely(err)) { /* I/O error during writing. This is really bad! */ ntfs_error(vol->sb, "I/O error while writing mft record 0x%llx! Marking base inode as bad. You should unmount the volume and run chkdsk.", ni->mft_no); - goto err_out; + return err; } done: ntfs_debug("Done."); return 0; -put_bio_out: - bio_put(bio); +put_bios_out: + if (child) + bio_put(child); + bio_put(parent); unmap_err_out: kunmap_local(kaddr); err_out: @@ -655,7 +806,8 @@ err_out: ntfs_error(vol->sb, "Not enough memory to write mft record. Redirtying so the write is retried later."); mark_mft_record_dirty(ni); - err = 0; + if (!sync) + err = 0; } else NVolSetErrors(vol); return err; @@ -714,7 +866,8 @@ static int ntfs_test_inode_wb(struct inode *vi, u64 ino, void *data) * it in @ref_vi instead of calling iput() directly. The caller must call * iput() on @ref_vi after releasing the folio lock. * - * Return 'true' if the mft record may be written out and 'false' if not. + * Return 'true' if the mft record may be written out by this path and 'false' + * if direct inode writeback owns it. * * The caller has locked the page and cleared the uptodate flag on it which * means that we can safely write out any dirty mft records that do not have @@ -811,9 +964,10 @@ static bool ntfs_may_write_mft_record(struct ntfs_volume *vol, const u64 mft_no, mft_no); /* * The write has to occur while we hold the mft record lock so - * return the locked ntfs inode. + * return the locked ntfs inode and its vfs inode reference. */ *locked_ni = ni; + *ref_vi = vi; return true; } ntfs_debug("Inode 0x%llx is not in icache.", mft_no); @@ -1198,6 +1352,10 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol) size_t new_rl_count; ntfs_debug("Extending mft bitmap allocation."); + /* The initial bitmap scan must finish before we lock or change a folio. */ + if (!NVolFreeClusterKnown(vol)) + wait_event(vol->free_waitq, NVolFreeClusterKnown(vol)); + mft_ni = NTFS_I(vol->mft_ino); mftbmp_ni = NTFS_I(vol->mftbmp_ino); /* @@ -1223,6 +1381,11 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol) lcn = rl->lcn + rl->length; ntfs_debug("Last lcn of mft bitmap attribute is 0x%llx.", (long long)lcn); + /* There is no adjacent cluster if the last run ends at the volume end. */ + if (lcn >= vol->nr_clusters) { + lcn = -1; + goto alloc_cluster; + } /* * Attempt to get the cluster following the last allocated cluster by * hand as it may be in the MFT zone so the allocator would not give it @@ -1241,10 +1404,18 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol) folio_lock(folio); b = (u8 *)kmap_local_folio(folio, 0) + (ll & ~PAGE_MASK); tb = 1 << (lcn & 7ull); - if (*b != 0xff && !(*b & tb)) { + /* + * A page skipped by the initial scan has no free bits recorded. + * Honor that and the space reserved for delayed allocation. + */ + if (*b != 0xff && !(*b & tb) && + vol->lcn_empty_bits_per_page[ll >> PAGE_SHIFT] && + ntfs_available_clusters_count(vol, 1) > 0) { /* Next cluster is free, allocate it. */ *b |= tb; folio_mark_dirty(folio); + ntfs_dec_free_clusters(vol, 1); + ntfs_set_lcn_empty_bits(vol, ll >> PAGE_SHIFT, 1, 1); folio_unlock(folio); kunmap_local(b); folio_put(folio); @@ -1259,6 +1430,7 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol) kunmap_local(b); folio_put(folio); up_write(&vol->lcnbmp_lock); +alloc_cluster: /* Allocate a cluster from the DATA_ZONE. */ rl2 = ntfs_cluster_alloc(vol, rl[1].vcn, 1, lcn, DATA_ZONE, true, false, false); @@ -1268,6 +1440,8 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol) "Failed to allocate a cluster for the mft bitmap."); return PTR_ERR(rl2); } + /* The adjacent cluster may have become available while unlocked. */ + status.added_cluster = rl2->lcn == lcn; rl = ntfs_runlists_merge(&mftbmp_ni->runlist, rl2, 0, &new_rl_count); if (IS_ERR(rl)) { up_write(&mftbmp_ni->runlist.lock); @@ -1282,8 +1456,8 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol) } mftbmp_ni->runlist.rl = rl; mftbmp_ni->runlist.count = new_rl_count; - status.added_run = 1; - ntfs_debug("Adding one run to mft bitmap."); + status.added_run = !status.added_cluster; + ntfs_debug("Allocated one cluster for mft bitmap."); /* Find the last run in the new runlist. */ for (; rl[1].length; rl++) ; @@ -1617,7 +1791,7 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol) s64 min_nr, nr, ll; unsigned long flags; struct ntfs_inode *mft_ni; - struct runlist_element *rl, *rl2; + struct runlist_element *rl, *rl2, *alloc_rl; struct ntfs_attr_search_ctx *ctx = NULL; struct mft_record *mrec; struct attr_record *a = NULL; @@ -1685,15 +1859,15 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol) up_write(&mft_ni->runlist.lock); do { - rl2 = ntfs_cluster_alloc(vol, old_last_vcn, nr, lcn, MFT_ZONE, - true, false, false); - if (!IS_ERR(rl2)) + alloc_rl = ntfs_cluster_alloc(vol, old_last_vcn, nr, lcn, + MFT_ZONE, true, false, false); + if (!IS_ERR(alloc_rl)) break; - if (PTR_ERR(rl2) != -ENOSPC || nr == min_nr) { + if (PTR_ERR(alloc_rl) != -ENOSPC || nr == min_nr) { ntfs_error(vol->sb, "Failed to allocate the minimal number of clusters (%lli) for the mft data attribute.", nr); - return PTR_ERR(rl2); + return PTR_ERR(alloc_rl); } /* * There is not enough space to do the allocation, but there @@ -1704,17 +1878,22 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol) ntfs_debug("Retrying mft data allocation with minimal cluster count %lli.", nr); } while (1); + /* + * Keep @alloc_rl until the extension is complete: it describes exactly + * the new clusters, which undo_alloc frees if a later step fails. + */ down_write(&mft_ni->runlist.lock); - rl = ntfs_runlists_merge(&mft_ni->runlist, rl2, 0, &new_rl_count); + rl = ntfs_runlists_merge_keep_src(&mft_ni->runlist, alloc_rl, 0, + &new_rl_count); if (IS_ERR(rl)) { up_write(&mft_ni->runlist.lock); ntfs_error(vol->sb, "Failed to merge runlists for mft data attribute."); - if (ntfs_cluster_free_from_rl(vol, rl2)) { + if (ntfs_cluster_free_from_rl(vol, alloc_rl)) { ntfs_error(vol->sb, "Failed to deallocate clusters from the mft data attribute.%s", es); NVolSetErrors(vol); } - kvfree(rl2); + kvfree(alloc_rl); return PTR_ERR(rl); } mft_ni->runlist.rl = rl; @@ -1730,7 +1909,6 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol) if (IS_ERR(mrec)) { ntfs_error(vol->sb, "Failed to map mft record."); ret = PTR_ERR(mrec); - down_write(&mft_ni->runlist.lock); goto undo_alloc; } ctx = ntfs_attr_get_search_ctx(mft_ni, mrec); @@ -1828,6 +2006,7 @@ extended_ok: mark_mft_record_dirty(ctx->ntfs_ino); ntfs_attr_put_search_ctx(ctx); unmap_mft_record(mft_ni); + kvfree(alloc_rl); ntfs_debug("Done."); return 0; restore_undo_alloc: @@ -1841,7 +2020,7 @@ restore_undo_alloc: write_unlock_irqrestore(&mft_ni->size_lock, flags); ntfs_attr_put_search_ctx(ctx); unmap_mft_record(mft_ni); - up_write(&mft_ni->runlist.lock); + kvfree(alloc_rl); /* * The only thing that is now wrong is ->allocated_size of the * base attribute extent which chkdsk should be able to fix. @@ -1852,15 +2031,28 @@ restore_undo_alloc: ctx->attr->data.non_resident.highest_vcn = cpu_to_le64(old_last_vcn - 1); undo_alloc: - if (ntfs_cluster_free(mft_ni, old_last_vcn, -1, ctx) < 0) { - ntfs_error(vol->sb, "Failed to free clusters from mft data attribute.%s", es); - NVolSetErrors(vol); - } - + /* + * Entered without the runlist lock. Take the new runs off the + * runlist under it, and free their clusters from @alloc_rl once it + * is dropped, as lcnbmp_lock nests outside it (see above). + */ + down_write(&mft_ni->runlist.lock); if (ntfs_rl_truncate_nolock(vol, &mft_ni->runlist, old_last_vcn)) { ntfs_error(vol->sb, "Failed to truncate mft data attribute runlist.%s", es); NVolSetErrors(vol); + /* + * The runlist may still reference the clusters, so leave them + * allocated until chkdsk. + */ + kvfree(alloc_rl); + alloc_rl = NULL; + } + up_write(&mft_ni->runlist.lock); + if (ntfs_cluster_free_from_rl(vol, alloc_rl)) { + ntfs_error(vol->sb, "Failed to free clusters from mft data attribute.%s", es); + NVolSetErrors(vol); } + kvfree(alloc_rl); if (mp_extended && ntfs_attr_update_mapping_pairs(mft_ni, 0)) { ntfs_error(vol->sb, "Failed to restore mapping pairs.%s", es); @@ -1869,11 +2061,16 @@ undo_alloc: if (ctx) { a = ctx->attr; if (mp_rebuilt && !IS_ERR(ctx->mrec)) { - if (ntfs_mapping_pairs_build(vol, (u8 *)a + le16_to_cpu( + int err; + + down_read(&mft_ni->runlist.lock); + err = ntfs_mapping_pairs_build(vol, (u8 *)a + le16_to_cpu( a->data.non_resident.mapping_pairs_offset), old_alen - le16_to_cpu( a->data.non_resident.mapping_pairs_offset), - rl2, ll, -1, NULL, NULL, NULL)) { + mft_ni->runlist.rl, ll, -1, NULL, NULL, NULL); + up_read(&mft_ni->runlist.lock); + if (err) { ntfs_error(vol->sb, "Failed to restore mapping pairs array.%s", es); NVolSetErrors(vol); } @@ -2017,6 +2214,8 @@ static int ntfs_mft_record_format(const struct ntfs_volume *vol, const s64 mft_n return PTR_ERR(folio); } folio_lock(folio); + if (folio_test_writeback(folio)) + folio_wait_writeback(folio); folio_clear_uptodate(folio); m = (struct mft_record *)((u8 *)kmap_local_folio(folio, 0) + ofs); err = ntfs_mft_record_layout(vol, mft_no, m); @@ -2534,6 +2733,8 @@ mft_rec_already_initialized: goto undo_mftbmp_alloc; } folio_lock(folio); + if (folio_test_writeback(folio)) + folio_wait_writeback(folio); folio_clear_uptodate(folio); m = (struct mft_record *)((u8 *)kmap_local_folio(folio, 0) + ofs); /* If we just formatted the mft record no need to do it again. */ @@ -2804,7 +3005,7 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni) * record to be freed is guaranteed to do it already. */ NInoSetDirty(ni); - err = write_mft_record(ni, ni_mrec, 0); + err = write_mft_record(ni, ni_mrec, 1); if (err) goto sync_rollback; @@ -2854,19 +3055,150 @@ sync_rollback: return err; } -static s64 lcn_from_index(struct ntfs_volume *vol, struct ntfs_inode *ni, - unsigned long index) +static bool ntfs_add_mft_io_unit(struct bio *bio, struct folio *folio, + const struct ntfs_mft_io_unit *unit) { - s64 vcn; - s64 lcn; + if (!bio->bi_iter.bi_size) + bio->bi_iter.bi_sector = unit->sector; + else if (bio_end_sector(bio) != unit->sector) + return false; + + return bio_add_folio(bio, folio, unit->len, unit->folio_ofs); +} + +static void ntfs_release_mft_write_refs(struct ntfs_inode **locked_nis, + unsigned int nr_locked_nis, + struct inode **ref_inos, + unsigned int nr_ref_inos) +{ + while (nr_locked_nis-- > 0) { + struct ntfs_inode *tni = locked_nis[nr_locked_nis]; + + mutex_unlock(&tni->mrec_lock); + atomic_dec(&tni->count); + } - vcn = ntfs_pidx_to_cluster(vol, index); + while (nr_ref_inos-- > 0) + iput(ref_inos[nr_ref_inos]); +} + +static int ntfs_map_mft_io_locked(struct ntfs_inode *ni, u64 folio_byte, + struct ntfs_mft_io_unit *unit) +{ + struct ntfs_volume *vol = ni->vol; + u64 file_ofs = folio_byte + unit->folio_ofs; + s64 vcn, lcn; - down_read(&ni->runlist.lock); - lcn = ntfs_attr_vcn_to_lcn_nolock(ni, vcn, false); + lockdep_assert_held(&ni->runlist.lock); + if (!ni->runlist.rl) + return -EIO; + + vcn = ntfs_bytes_to_cluster(vol, file_ofs); + /* + * $MFT is fully mapped at mount time and extensions merge already + * mapped runs under the write lock. Do not remap here: the generic + * helper may drop this read lock while upgrading it. + */ + lcn = ntfs_rl_vcn_to_lcn(ni->runlist.rl, vcn); + if (lcn == LCN_RL_NOT_MAPPED) + return -EAGAIN; + if (lcn < 0) + return -EIO; + if (ntfs_bytes_to_cluster_off(vol, file_ofs) + unit->len > + vol->cluster_size) + return -EIO; + + unit->sector = ntfs_bytes_to_bio_sector( + NTFS_CLU_TO_B(vol, lcn) + + ntfs_bytes_to_cluster_off(vol, file_ofs)); + return 0; +} + +static int ntfs_map_mft_io_for_folio(struct ntfs_inode *ni, u64 folio_byte, + struct ntfs_mft_io_unit *unit) +{ + int err; + + if (!down_read_trylock(&ni->runlist.lock)) + return -EAGAIN; + err = ntfs_map_mft_io_locked(ni, folio_byte, unit); up_read(&ni->runlist.lock); + return err; +} - return lcn; +static int ntfs_prepare_mft_folio_units(struct ntfs_inode *ni, u64 folio_byte, + u64 file_limit, + const unsigned long *record_selected, + struct ntfs_mft_io_unit *units, + unsigned int *nr_units, + unsigned int max_units, bool *defer) +{ + struct ntfs_volume *vol = ni->vol; + u64 folio_end = folio_byte + PAGE_SIZE; + u64 unit_byte = folio_byte; + + while (unit_byte < folio_end && unit_byte < file_limit) { + struct ntfs_mft_io_unit unit; + u64 record_byte; + u64 unit_end; + u64 io_unit_end; + u64 cluster_end; + bool selected = false; + int err; + + io_unit_end = + round_down(unit_byte, (u64)vol->mft_io_unit_size) + + vol->mft_io_unit_size; + cluster_end = ntfs_cluster_to_bytes( + vol, ntfs_bytes_to_cluster(vol, unit_byte) + 1); + unit_end = min3(io_unit_end, cluster_end, folio_end); + + record_byte = round_down(unit_byte, (u64)vol->mft_record_size); + while (record_byte < min(unit_end, file_limit)) { + unsigned int record; + + record = div_u64(record_byte - folio_byte, + vol->mft_record_size); + if (test_bit(record, record_selected)) { + selected = true; + break; + } + record_byte += vol->mft_record_size; + } + /* + * Direct inode writeback owns unselected records. Their folio + * images are stable while this folio is locked, so a containing + * unit can preserve them when writing a selected record. + */ + if (!selected) + goto next; + + unit = (struct ntfs_mft_io_unit){ + .folio_ofs = unit_byte - folio_byte, + .len = unit_end - unit_byte, + }; + err = ntfs_map_mft_io_for_folio(ni, folio_byte, &unit); + if (err == -EAGAIN) { + *defer = true; + goto next; + } + if (err) + return err; + if (*nr_units >= max_units) + return -EOVERFLOW; + units[(*nr_units)++] = unit; +next: + unit_byte = unit_end; + } + return 0; +} + +static void ntfs_mft_write_error(struct ntfs_volume *vol, + struct address_space *mapping, int err) +{ + mapping_set_error(mapping, err); + NVolSetErrors(vol); + ntfs_error(vol->sb, "Error while writing MFT folio: %d", err); } /* @@ -2875,9 +3207,8 @@ static s64 lcn_from_index(struct ntfs_volume *vol, struct ntfs_inode *ni, * @wbc: Writeback control structure * * This function is called as part of the address_space_operations - * .writepages implementation for the $MFT inode (or $MFTMirr). - * It handles writing one folio (normally 4KiB page) worth of MFT records - * to the underlying block device. + * .writepages implementation for the $MFT inode. Despite its historical + * name, it handles one folio containing MFT records. * * Return: 0 on success, or -errno on error. */ @@ -2887,195 +3218,190 @@ static int ntfs_write_mft_block(struct folio *folio, struct writeback_control *w struct inode *vi = mapping->host; struct ntfs_inode *ni = NTFS_I(vi); struct ntfs_volume *vol = ni->vol; - u8 *kaddr; - struct ntfs_inode **locked_nis __free(kfree) = kmalloc_objs(struct ntfs_inode *, - PAGE_SIZE / NTFS_BLOCK_SIZE, - GFP_NOFS); - int nr_locked_nis = 0, err = 0, mft_ofs, prev_mft_ofs; - struct inode **ref_inos __free(kfree) = kmalloc_objs(struct inode *, - PAGE_SIZE / NTFS_BLOCK_SIZE, - GFP_NOFS); - int nr_ref_inos = 0; - struct bio *bio = NULL; - u64 mft_no; - struct ntfs_inode *tni; - s64 lcn; - s64 vcn = ntfs_pidx_to_cluster(vol, folio->index); - s64 end_vcn = ntfs_bytes_to_cluster(vol, ni->allocated_size); - unsigned int folio_sz; - loff_t i_size = i_size_read(vi); + struct ntfs_inode **locked_nis __free(kfree) = + kmalloc_objs(struct ntfs_inode *, PAGE_SIZE / NTFS_BLOCK_SIZE, + GFP_NOFS); + struct inode **ref_inos __free(kfree) = + kmalloc_objs(struct inode *, PAGE_SIZE / NTFS_BLOCK_SIZE, GFP_NOFS); + struct ntfs_mft_io_unit *units __free(kfree) = NULL; + struct bio *parent = NULL, *child = NULL; + struct ntfs_mft_write_ctx *ctx = NULL; + u8 *kaddr = NULL; + DECLARE_BITMAP(record_selected, PAGE_SIZE / NTFS_BLOCK_SIZE) = {}; + u64 folio_byte, file_limit, folio_end, mirror_size; + unsigned int nr_records, nr_units = 0, max_units; + unsigned int nr_locked_nis = 0, nr_ref_inos = 0; + unsigned int record, unit_idx; + unsigned long flags; + loff_t i_size; + s64 allocated_size; + bool defer = false, redirty = false; + int err = 0; - ntfs_debug("Entering for inode 0x%llx, attribute type 0x%x, folio index 0x%lx.", - ni->mft_no, ni->type, folio->index); + read_lock_irqsave(&ni->size_lock, flags); + i_size = i_size_read(vi); + allocated_size = ni->allocated_size; + read_unlock_irqrestore(&ni->size_lock, flags); - if (!locked_nis || !ref_inos) { - folio_redirty_for_writepage(wbc, folio); - folio_unlock(folio); - return -ENOMEM; + ntfs_debug("Entering for inode 0x%llx, folio index 0x%lx.", ni->mft_no, + folio->index); + WARN_ON(!folio_test_locked(folio)); + + folio_byte = folio_pos(folio); + folio_end = folio_byte + PAGE_SIZE; + mirror_size = (u64)vol->mftmirr_size * vol->mft_record_size; + + if (i_size <= folio_byte) + folio_zero_segment(folio, 0, PAGE_SIZE); + else if ((u64)i_size < folio_end) + folio_zero_segment(folio, i_size - folio_byte, PAGE_SIZE); + + file_limit = min_t(u64, i_size, allocated_size); + nr_records = PAGE_SIZE / vol->mft_record_size; + max_units = PAGE_SIZE / min(vol->cluster_size, vol->mft_record_size); + units = kmalloc_array(max_units, sizeof(*units), GFP_NOFS); + if (!locked_nis || !ref_inos || !units) { + err = -ENOMEM; + goto out_noio; } - /* We have to zero every time due to mmap-at-end-of-file. */ - if (folio->index >= (i_size >> folio_shift(folio))) - /* The page straddles i_size. */ - folio_zero_segment(folio, - offset_in_folio(folio, i_size), - folio_size(folio)); + kaddr = kmap_local_folio(folio, 0); + folio_clear_uptodate(folio); - lcn = lcn_from_index(vol, ni, folio->index); - if (lcn <= LCN_HOLE) { + for (record = 0; record < nr_records; record++) { + struct ntfs_inode *tni = NULL; + struct inode *ref_vi = NULL; + u64 record_byte = + folio_byte + (u64)record * vol->mft_record_size; + u64 mft_no; + + if (record_byte >= file_limit) + continue; + mft_no = record_byte >> vol->mft_record_size_bits; + if (!ntfs_may_write_mft_record( + vol, mft_no, + (struct mft_record *)(kaddr + + record * vol->mft_record_size), + &tni, &ref_vi)) { + if (ref_vi) + ref_inos[nr_ref_inos++] = ref_vi; + continue; + } + if (ref_vi) + ref_inos[nr_ref_inos++] = ref_vi; + if (tni) { + locked_nis[nr_locked_nis++] = tni; + if (tni->nr_extents < 0 && + tni->ext.base_ntfs_ino == NTFS_I(vol->mft_ino)) + continue; + } + __set_bit(record, record_selected); + } + + err = ntfs_prepare_mft_folio_units(ni, folio_byte, file_limit, + record_selected, units, &nr_units, + max_units, &defer); + if (err) + goto out_noio; + if (!nr_units) { + if (defer) + goto out_noio; + folio_mark_uptodate(folio); + kunmap_local(kaddr); folio_start_writeback(folio); folio_unlock(folio); folio_end_writeback(folio); - return -EIO; + ntfs_release_mft_write_refs(locked_nis, nr_locked_nis, ref_inos, + nr_ref_inos); + return 0; } - /* Map folio so we can access its contents. */ - kaddr = kmap_local_folio(folio, 0); - /* Clear the page uptodate flag whilst the mst fixups are applied. */ - folio_clear_uptodate(folio); + parent = ntfs_alloc_mft_parent_bio(vol, folio, NULL); + if (!parent) { + err = -ENOMEM; + goto out_noio; + } + ctx = container_of(parent, struct ntfs_mft_write_ctx, bio); - for (mft_ofs = 0; mft_ofs < PAGE_SIZE && vcn < end_vcn; - mft_ofs += vol->mft_record_size) { - /* Get the mft record number. */ - mft_no = (((s64)folio->index << PAGE_SHIFT) + mft_ofs) >> - vol->mft_record_size_bits; - vcn = ntfs_mft_no_to_cluster(vol, mft_no); - /* Check whether to write this mft record. */ - tni = NULL; - if (ntfs_may_write_mft_record(vol, mft_no, - (struct mft_record *)(kaddr + mft_ofs), - &tni, &ref_inos[nr_ref_inos])) { - unsigned int mft_record_off = 0; - s64 vcn_off = vcn; - s64 rl_len = 0; + if (!ntfs_add_mft_io_unit(parent, folio, &units[0])) { + err = -EIO; + goto out_noio; + } - /* - * The record should be written. If a locked ntfs - * inode was returned, add it to the array of locked - * ntfs inodes. - */ - if (tni) - locked_nis[nr_locked_nis++] = tni; - else if (ref_inos[nr_ref_inos]) - nr_ref_inos++; - - if (bio && (mft_ofs != prev_mft_ofs + vol->mft_record_size)) { -flush_bio: - bio->bi_end_io = ntfs_bio_end_io; - submit_bio(bio); - bio = NULL; - } + for (unit_idx = 0; unit_idx < nr_units; unit_idx++) { + struct ntfs_mft_io_unit *unit = &units[unit_idx]; + u64 file_ofs = folio_byte + unit->folio_ofs; - if (vol->cluster_size < folio_size(folio)) { - struct runlist_element *rl; + if (unit_idx) { + struct bio *target = child ? child : parent; - down_write(&ni->runlist.lock); - rl = ntfs_attr_vcn_to_rl(ni, vcn_off, &lcn); - if (!IS_ERR(rl)) - rl_len = rl->length - (vcn_off - rl->vcn); - up_write(&ni->runlist.lock); - if (IS_ERR(rl) || lcn < 0) { - err = -EIO; - goto unm_done; + if (!ntfs_add_mft_io_unit(target, folio, unit)) { + if (child) { + ntfs_start_mft_writeback(ctx); + bio_chain(child, parent); + submit_bio(child); + child = NULL; } - - if (bio && - (bio_end_sector(bio) >> (vol->cluster_size_bits - 9)) != - lcn) { - bio->bi_end_io = ntfs_bio_end_io; - submit_bio(bio); - bio = NULL; + child = bio_alloc(vol->sb->s_bdev, 1, + REQ_OP_WRITE, GFP_NOIO); + if (!child) { + defer = true; + break; + } + if (!ntfs_add_mft_io_unit(child, folio, unit)) { + bio_put(child); + child = NULL; + if (!ctx->error) + ctx->error = -EIO; + redirty = true; + err = -EIO; + break; } } + } + if (file_ofs < mirror_size) { + int mirror_err = ntfs_sync_mft_mirror_unit( + vol, folio, file_ofs, unit); - if (!bio) { - unsigned int off; - - off = ((mft_no << vol->mft_record_size_bits) + - mft_record_off) & vol->cluster_size_mask; - - bio = bio_alloc(vol->sb->s_bdev, 1, REQ_OP_WRITE, - GFP_NOIO); - bio->bi_iter.bi_sector = - ntfs_bytes_to_bio_sector( - ntfs_cluster_to_bytes(vol, lcn) + off); - } - - if (vol->cluster_size == NTFS_BLOCK_SIZE && - (mft_record_off || - rl_len == 1 || - mft_ofs + NTFS_BLOCK_SIZE >= PAGE_SIZE)) - folio_sz = NTFS_BLOCK_SIZE; - else - folio_sz = vol->mft_record_size; - if (!bio_add_folio(bio, folio, folio_sz, - mft_ofs + mft_record_off)) { - err = -EIO; - bio_put(bio); - goto unm_done; - } - mft_record_off += folio_sz; - - if (mft_record_off != vol->mft_record_size) { - vcn_off++; - goto flush_bio; - } - prev_mft_ofs = mft_ofs; - - if (mft_no < vol->mftmirr_size) { - int sub_err = ntfs_sync_mft_mirror(vol, mft_no, - (struct mft_record *)(kaddr + mft_ofs)); - - if (unlikely(sub_err) && !err) - err = sub_err; - } - } else if (ref_inos[nr_ref_inos]) - nr_ref_inos++; + if (mirror_err && !ctx->error) + ctx->error = mirror_err; + } } - if (bio) { - bio->bi_end_io = ntfs_bio_end_io; - submit_bio(bio); + if (child) { + ntfs_start_mft_writeback(ctx); + bio_chain(child, parent); + submit_bio(child); } -unm_done: + ntfs_start_mft_writeback(ctx); folio_mark_uptodate(folio); + if (defer || redirty) + folio_redirty_for_writepage(wbc, folio); kunmap_local(kaddr); - - folio_start_writeback(folio); + kaddr = NULL; folio_unlock(folio); - folio_end_writeback(folio); + submit_bio(parent); + ntfs_release_mft_write_refs(locked_nis, nr_locked_nis, ref_inos, + nr_ref_inos); - /* Unlock any locked inodes. */ - while (nr_locked_nis-- > 0) { - struct ntfs_inode *base_tni; - - tni = locked_nis[nr_locked_nis]; - mutex_unlock(&tni->mrec_lock); - - /* Get the base inode. */ - mutex_lock(&tni->extent_lock); - if (tni->nr_extents >= 0) - base_tni = tni; - else - base_tni = tni->ext.base_ntfs_ino; - mutex_unlock(&tni->extent_lock); - ntfs_debug("Unlocking %s inode 0x%llx.", - tni == base_tni ? "base" : "extent", - tni->mft_no); - atomic_dec(&tni->count); - iput(VFS_I(base_tni)); - } + return 0; - /* Dropping deferred references */ - while (nr_ref_inos-- > 0) { - if (ref_inos[nr_ref_inos]) - iput(ref_inos[nr_ref_inos]); +out_noio: + if (kaddr) { + folio_mark_uptodate(folio); + kunmap_local(kaddr); } - - if (unlikely(err && err != -ENOMEM)) - NVolSetErrors(vol); - if (likely(!err)) - ntfs_debug("Done."); + if (err && err != -ENOMEM) + ntfs_mft_write_error(vol, mapping, err); + if (parent) + bio_put(parent); + if (redirty || defer || err == -ENOMEM) + folio_redirty_for_writepage(wbc, folio); + folio_unlock(folio); + ntfs_release_mft_write_refs(locked_nis, nr_locked_nis, ref_inos, + nr_ref_inos); + if (err == -ENOMEM || (defer && !err)) + return 0; return err; } diff --git a/fs/ntfs/mft.h b/fs/ntfs/mft.h index ed5c1d595c0d..d2a31205e08c 100644 --- a/fs/ntfs/mft.h +++ b/fs/ntfs/mft.h @@ -42,8 +42,8 @@ static inline void mark_mft_record_dirty(struct ntfs_inode *ni) __mark_mft_record_dirty(ni); } -int ntfs_sync_mft_mirror(struct ntfs_volume *vol, const u64 mft_no, - struct mft_record *m); +int ntfs_mft_bioset_init(void); +void ntfs_mft_bioset_exit(void); int write_mft_record_nolock(struct ntfs_inode *ni, struct mft_record *m, int sync); /* @@ -53,16 +53,19 @@ int write_mft_record_nolock(struct ntfs_inode *ni, struct mft_record *m, int syn * @sync: if true, wait for i/o completion * * This is just a wrapper for write_mft_record_nolock() (see mft.c), which - * locks the page for the duration of the write. This ensures that there are - * no race conditions between writing the mft record via the dirty inode code - * paths and via the page cache write back code paths or between writing - * neighbouring mft records residing in the same page. + * locks the folio while preparing the write. write_mft_record_nolock() waits + * for prior folio writeback before modifying the folio and keeps PG_writeback + * set until the submitted I/O completes. Together these serialize dirty + * inode writes, page cache writeback, and neighbouring mft record writes in + * the same folio. * * Locking the page also serializes us against ->read_folio() if the page is not * uptodate. * - * On success, clean the mft record and return 0. On error, leave the mft - * record dirty and return -errno. + * On success, clean the mft record and return 0. On allocation failure, + * redirty the record for retry. Asynchronous callers return 0 after + * redirtying, while synchronous callers receive -ENOMEM. On other errors, + * return -errno and mark the volume with errors. */ static inline int write_mft_record(struct ntfs_inode *ni, struct mft_record *m, int sync) { diff --git a/fs/ntfs/mst.c b/fs/ntfs/mst.c index 7f9faad924ad..cc6363413449 100644 --- a/fs/ntfs/mst.c +++ b/fs/ntfs/mst.c @@ -19,7 +19,7 @@ * magic of the ntfs record header being processed with "BAAD" (in memory only!) * and abort processing. * - * Return 0 on success and -EINVAL on error ("BAAD" magic will be present). + * Return 0 on success and -EIO on error ("BAAD" magic will be present). * * NOTE: We consider the absence / invalidity of an update sequence array to * mean that the structure is not protected at all and hence doesn't need to @@ -71,7 +71,7 @@ int post_read_mst_fixup(struct ntfs_record *b, const u32 size) * Note that magic_BAAD is already converted to le32. */ b->magic = magic_BAAD; - return -EINVAL; + return -EIO; } data_pos += NTFS_BLOCK_SIZE / sizeof(u16); } diff --git a/fs/ntfs/named_stream.c b/fs/ntfs/named_stream.c new file mode 100644 index 000000000000..e44dbf09057b --- /dev/null +++ b/fs/ntfs/named_stream.c @@ -0,0 +1,1821 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NTFS named stream handling. + * + * Copyright (c) 2026 LG Electronics Co., Ltd. + */ + +#include <linux/file.h> +#include <linux/namei.h> +#include <linux/overflow.h> + +#include <uapi/linux/ntfs.h> + +#include "attrib.h" +#include "dir.h" +#include "iomap.h" +#include "mft.h" +#include "ntfs.h" +#include "stream.h" +#include "time.h" + +#define NTFS_STREAM_MAX_IO (16 * 1024 * 1024) + +/* + * ntfs_check_stream_name() - Validate a named-stream name + * @name: UTF-16LE stream name + * @name_len: Length of @name in UTF-16 code units + * + * Reject empty, overlong, separator-containing, or malformed UTF-16 names. + * + * Return: 0 if valid, or -EINVAL otherwise. + */ +int ntfs_check_stream_name(const __le16 *name, unsigned int name_len) +{ + unsigned int i; + + if (!name_len || name_len > NTFS_MAX_NAME_LEN) + return -EINVAL; + + for (i = 0; i < name_len; i++) { + u16 c = le16_to_cpu(name[i]); + + if (c == 0 || c == '/' || c == '\\' || c == ':') + return -EINVAL; + if (c >= 0xd800 && c <= 0xdbff) { + if (++i >= name_len) + return -EINVAL; + c = le16_to_cpu(name[i]); + if (c < 0xdc00 || c > 0xdfff) + return -EINVAL; + } else if (c >= 0xdc00 && c <= 0xdfff) { + return -EINVAL; + } + } + + return 0; +} + +/* + * ntfs_stream_inode_refresh() - Refresh base-derived stream inode metadata + * @vi: inode representing a named stream + * + * Synchronize ownership, mode, flags, timestamps, and generation with the + * base inode. Stream size and allocation remain specific to the stream. + */ +void ntfs_stream_inode_refresh(struct inode *vi) +{ + struct ntfs_inode *ni = NTFS_I(vi); + struct inode *base_vi; + + if (!ntfs_inode_is_named_stream(ni) || ni->nr_extents != -1) + return; + + base_vi = VFS_I(ni->ext.base_ntfs_ino); + vi->i_uid = base_vi->i_uid; + vi->i_gid = base_vi->i_gid; + vi->i_mode = (base_vi->i_mode & ~S_IFMT) | S_IFREG; + vi->i_flags = base_vi->i_flags; + inode_set_mtime_to_ts(vi, inode_get_mtime(base_vi)); + inode_set_ctime_to_ts(vi, inode_get_ctime(base_vi)); + inode_set_atime_to_ts(vi, inode_get_atime(base_vi)); + vi->i_generation = base_vi->i_generation; +} + +/* + * ntfs_stream_update_base_time() - Update base timestamps after a stream change + * @base_ni: base inode containing the stream attribute + * + * Update the base inode's mtime and ctime and mark it dirty. The helper takes + * the base inode lock. + */ +void ntfs_stream_update_base_time(struct ntfs_inode *base_ni) +{ + struct inode *base_vi = VFS_I(base_ni); + + inode_lock_nested(base_vi, I_MUTEX_PARENT); + inode_set_mtime_to_ts(base_vi, inode_set_ctime_current(base_vi)); + mark_inode_dirty(base_vi); + inode_unlock(base_vi); +} + +/* + * ntfs_stream_inode_validate() - Validate a named-stream inode + * @vi: stream inode to validate + * + * Verify that @vi still maps to its named DATA attribute and refresh its + * base-derived metadata. The caller must hold the base inode's MFT-record + * lock. + * + * Return: 0 if valid, or a negative errno. + */ +int ntfs_stream_inode_validate(struct inode *vi) +{ + struct ntfs_inode *ni = NTFS_I(vi); + struct ntfs_inode *base_ni; + struct ntfs_attr_search_ctx *ctx; + int err; + + if (!ntfs_inode_is_named_stream(ni) || ni->nr_extents != -1) + return -EINVAL; + + base_ni = ni->ext.base_ntfs_ino; + lockdep_assert_held(&base_ni->mrec_lock); + if (NVolShutdown(base_ni->vol)) + return -EIO; + if (!NInoStreamUnlinked(ni) && + (!vi->i_nlink || !VFS_I(base_ni)->i_nlink || + NInoBeingDeleted(base_ni))) + return -ESTALE; + + ctx = ntfs_attr_get_search_ctx(base_ni, NULL); + if (!ctx) + return -ENOMEM; + err = ntfs_attr_lookup(AT_DATA, ni->name, ni->name_len, + CASE_SENSITIVE, 0, NULL, 0, ctx); + ntfs_attr_put_search_ctx(ctx); + if (err) + return err; + + ntfs_stream_inode_refresh(vi); + return 0; +} + +/* + * ntfs_stream_unlinked() - Check a cached stream's deferred-unlink state + * @base_ni: base inode containing the stream + * @name: UTF-16LE stream name + * @name_len: Length of @name in UTF-16 code units + * + * The caller must hold the base inode's MFT-record lock. + * + * Return: true if the cached stream inode is unlinked, or false if it is not + * cached or is still linked. + */ +bool ntfs_stream_unlinked(struct ntfs_inode *base_ni, + const __le16 *name, u32 name_len) +{ + struct ntfs_attr na = { + .mft_no = base_ni->mft_no, + .type = AT_DATA, + .name = (__le16 *)name, + .name_len = name_len, + }; + struct inode *vi; + bool unlinked; + + lockdep_assert_held(&base_ni->mrec_lock); + rcu_read_lock(); + vi = find_inode_rcu(base_ni->vol->sb, na.mft_no, + ntfs_test_inode_rcu, &na); + if (!vi) { + rcu_read_unlock(); + return false; + } + unlinked = NInoStreamUnlinked(NTFS_I(vi)); + rcu_read_unlock(); + return unlinked; +} + +/* + * ntfs_stream_path_parse() - Split a pathname stream component + * @vol: NTFS volume + * @qname: final pathname component + * @path: receives slices of @qname on success + * + * Parse the ``file:stream`` form when the Windows pathname interface is + * enabled. This does not convert or validate the component names. + * + * Return: 1 if stream syntax was parsed, 0 if streams are disabled or no + * stream separator is present, or -EINVAL for malformed syntax. + */ +int ntfs_stream_path_parse(struct ntfs_volume *vol, + const struct qstr *qname, struct ntfs_stream_path *path) +{ + const unsigned char *colon; + + if (!NVolStreamsWindows(vol)) + return 0; + + colon = memchr(qname->name, ':', qname->len); + if (!colon) + return 0; + if (colon == qname->name || colon + 1 == qname->name + qname->len) + return -EINVAL; + if (memchr(colon + 1, ':', qname->name + qname->len - colon - 1)) + return -EINVAL; + + path->base_name = (const char *)qname->name; + path->base_len = colon - qname->name; + path->stream_name = (const char *)colon + 1; + path->stream_len = qname->name + qname->len - colon - 1; + return 1; +} + +/* + * ntfs_stream_path_has_colon() - Check for a pathname stream separator + * @vol: NTFS volume + * @qname: final pathname component + * + * Return: true if the Windows pathname interface is enabled and @qname + * contains a colon, or false otherwise. + */ +bool ntfs_stream_path_has_colon(struct ntfs_volume *vol, + const struct qstr *qname) +{ + return NVolStreamsWindows(vol) && + memchr(qname->name, ':', qname->len); +} + +/* + * ntfs_stream_path_check() - Reject stream syntax for unsupported operations + * @vol: NTFS volume + * @qname: final pathname component + * + * Return: 0 for an ordinary name or when streams are disabled, + * -EOPNOTSUPP for parsed stream syntax, or -EINVAL for malformed syntax. + */ +int ntfs_stream_path_check(struct ntfs_volume *vol, + const struct qstr *qname) +{ + struct ntfs_stream_path path; + int ret; + + ret = ntfs_stream_path_parse(vol, qname, &path); + if (ret < 0) + return ret; + return ret ? -EOPNOTSUPP : 0; +} + +/* + * ntfs_stream_lookup_inode_by_name() - Look up a base inode by name + * @dir_ino: directory containing the base file or directory + * @uname: UTF-16LE base name + * @uname_len: Length of @uname in UTF-16 code units + * + * Resolve the directory entry and verify its MFT reference before returning + * the inode. + * + * Return: Referenced inode on success, or ERR_PTR() on failure. + */ +struct inode *ntfs_stream_lookup_inode_by_name(struct inode *dir_ino, + __le16 *uname, int uname_len) +{ + struct ntfs_volume *vol = NTFS_SB(dir_ino->i_sb); + struct ntfs_name *name = NULL; + struct inode *inode; + u64 mref; + + mutex_lock(&NTFS_I(dir_ino)->mrec_lock); + mref = ntfs_lookup_inode_by_name(NTFS_I(dir_ino), uname, uname_len, + &name); + mutex_unlock(&NTFS_I(dir_ino)->mrec_lock); + kfree(name); + + if (IS_ERR_MREF(mref)) + return ERR_PTR(MREF_ERR(mref)); + + inode = ntfs_iget(vol->sb, MREF(mref)); + if (IS_ERR(inode)) + return inode; + if (MSEQNO(mref) != NTFS_I(inode)->seq_no && + MREF(mref) != FILE_MFT) { + iput(inode); + return ERR_PTR(-EIO); + } + return inode; +} + +/* + * Recheck that a stream dentry still names the same base inode and DATA + * attribute. + */ +static int ntfs_stream_d_revalidate(struct inode *dir, + const struct qstr *qname, struct dentry *dentry, + unsigned int flags) +{ + struct inode *inode = d_inode(dentry); + struct ntfs_inode *ni; + struct ntfs_stream_path path; + struct inode *base_vi; + __le16 *base_name = NULL, *stream_name = NULL; + int base_len, stream_len, err, ret = 0; + + if (flags & LOOKUP_RCU) + return -ECHILD; + if (!inode) + return 0; + if (!inode->i_nlink) + return 0; + if (NVolShutdown(NTFS_SB(dir->i_sb))) + return 0; + + ni = NTFS_I(inode); + if (!ntfs_inode_is_named_stream(ni)) + return 0; + + err = ntfs_stream_path_parse(NTFS_SB(dir->i_sb), qname, &path); + if (err <= 0) + return 0; + base_len = ntfs_nlstoucs(NTFS_SB(dir->i_sb), path.base_name, + path.base_len, &base_name, NTFS_MAX_NAME_LEN); + if (base_len < 0) + goto out; + stream_len = ntfs_nlstoucs(NTFS_SB(dir->i_sb), path.stream_name, + path.stream_len, &stream_name, NTFS_MAX_NAME_LEN); + if (stream_len < 0) + goto out; + if (ntfs_check_stream_name(stream_name, stream_len)) + goto out; + if (stream_len != ni->name_len || + !ntfs_names_are_equal(stream_name, stream_len, ni->name, + ni->name_len, IGNORE_CASE, ni->vol->upcase, + ni->vol->upcase_len)) + goto out; + + base_vi = ntfs_stream_lookup_inode_by_name(dir, base_name, base_len); + if (IS_ERR(base_vi)) + goto out; + if (NTFS_I(base_vi) != ni->ext.base_ntfs_ino) { + iput(base_vi); + goto out; + } + + mutex_lock(&NTFS_I(base_vi)->mrec_lock); + err = ntfs_stream_inode_validate(inode); + mutex_unlock(&NTFS_I(base_vi)->mrec_lock); + iput(base_vi); + if (!err) + ret = 1; +out: + if (stream_name) + kmem_cache_free(ntfs_name_cache, stream_name); + if (base_name) + kmem_cache_free(ntfs_name_cache, base_name); + return ret; +} + +/* Revalidate stream paths while leaving ordinary NTFS dentries cacheable. */ +static int ntfs_dentry_revalidate(struct inode *dir, + const struct qstr *qname, struct dentry *dentry, + unsigned int flags) +{ + struct inode *inode = d_inode(dentry); + struct ntfs_volume *vol = NTFS_SB(dir->i_sb); + + if (ntfs_stream_path_has_colon(vol, qname)) + return ntfs_stream_d_revalidate(dir, qname, dentry, flags); + if (inode && ntfs_inode_is_named_stream(NTFS_I(inode))) + return 0; + return 1; +} + +/* Drop stream-path aliases so future lookups recheck their backing attribute. */ +static int ntfs_dentry_delete(const struct dentry *dentry) +{ + struct inode *inode = d_inode(dentry); + struct ntfs_volume *vol = NTFS_SB(dentry->d_sb); + + if (ntfs_stream_path_has_colon(vol, &dentry->d_name) || + (inode && ntfs_inode_is_named_stream(NTFS_I(inode)))) + return always_delete_dentry(dentry); + return 0; +} + +static const struct dentry_operations ntfs_dentry_ops = { + .d_revalidate = ntfs_dentry_revalidate, + .d_delete = ntfs_dentry_delete, +}; + +/* + * ntfs_set_default_dentry_ops() - Install stream-aware dentry operations + * @sb: superblock on which to install the operations + * + * Ensure pathname stream dentries are revalidated and not kept as stale + * aliases. + */ +void ntfs_set_default_dentry_ops(struct super_block *sb) +{ + set_default_d_op(sb, &ntfs_dentry_ops); +} + +/* + * ntfs_lookup_stream() - Look up a named stream by pathname + * @dir_ino: directory containing the base file or directory + * @dent: dentry for the pathname component + * @path: parsed base and stream name slices + * + * Find the existing base inode and its named DATA attribute, then splice the + * stream inode into @dent. + * + * Return: NULL if @dent was instantiated, an alternate dentry if a + * disconnected alias was spliced, or ERR_PTR() on failure. + */ +struct dentry *ntfs_lookup_stream(struct inode *dir_ino, + struct dentry *dent, const struct ntfs_stream_path *path) +{ + struct ntfs_volume *vol = NTFS_SB(dir_ino->i_sb); + struct inode *base_vi, *stream_vi; + __le16 *base_uname = NULL, *stream_uname = NULL; + int base_len, stream_len, err; + + if (NVolShutdown(vol)) + return ERR_PTR(-EIO); + + base_len = ntfs_nlstoucs(vol, path->base_name, path->base_len, + &base_uname, NTFS_MAX_NAME_LEN); + if (base_len < 0) + return ERR_PTR(base_len); + + stream_len = ntfs_nlstoucs(vol, path->stream_name, path->stream_len, + &stream_uname, NTFS_MAX_NAME_LEN); + if (stream_len < 0) { + kmem_cache_free(ntfs_name_cache, base_uname); + return ERR_PTR(stream_len); + } + err = ntfs_check_stream_name(stream_uname, stream_len); + if (err) { + kmem_cache_free(ntfs_name_cache, base_uname); + kmem_cache_free(ntfs_name_cache, stream_uname); + return ERR_PTR(err); + } + + base_vi = ntfs_stream_lookup_inode_by_name(dir_ino, base_uname, + base_len); + kmem_cache_free(ntfs_name_cache, base_uname); + if (IS_ERR(base_vi)) { + err = PTR_ERR(base_vi); + kmem_cache_free(ntfs_name_cache, stream_uname); + if (err == -ENOENT) + return d_splice_alias(NULL, dent); + return ERR_PTR(err); + } + + if (!S_ISREG(base_vi->i_mode) && !S_ISDIR(base_vi->i_mode)) { + iput(base_vi); + kmem_cache_free(ntfs_name_cache, stream_uname); + return ERR_PTR(-ENOTDIR); + } + + mutex_lock(&NTFS_I(base_vi)->mrec_lock); + stream_vi = ntfs_attr_iget(base_vi, AT_DATA, stream_uname, stream_len); + if (!IS_ERR(stream_vi)) { + if (NInoStreamUnlinked(NTFS_I(stream_vi))) + err = -ENOENT; + else + err = ntfs_stream_inode_validate(stream_vi); + } + mutex_unlock(&NTFS_I(base_vi)->mrec_lock); + if (!IS_ERR(stream_vi) && err) { + iput(stream_vi); + stream_vi = ERR_PTR(err); + } + iput(base_vi); + kmem_cache_free(ntfs_name_cache, stream_uname); + + if (IS_ERR(stream_vi)) { + err = PTR_ERR(stream_vi); + if (err == -ENOENT || err == -ESTALE) + return d_splice_alias(NULL, dent); + return ERR_PTR(err); + } + + return d_splice_alias(stream_vi, dent); +} + +/* + * ntfs_stream_path_names() - Convert and validate pathname stream names + * @vol: NTFS volume + * @qname: final pathname component + * @base_name: receives the allocated UTF-16LE base name + * @base_len: receives the base name length in UTF-16 code units + * @stream_name: receives the allocated UTF-16LE stream name + * @stream_len: receives the stream name length in UTF-16 code units + * + * Return: 1 if stream names were produced, 0 if @qname has no stream syntax, + * or a negative errno. The caller owns both name buffers on success. + */ +int ntfs_stream_path_names(struct ntfs_volume *vol, + const struct qstr *qname, __le16 **base_name, int *base_len, + __le16 **stream_name, int *stream_len) +{ + struct ntfs_stream_path path; + int err; + + *base_name = NULL; + *stream_name = NULL; + err = ntfs_stream_path_parse(vol, qname, &path); + if (err <= 0) + return err; + + *base_len = ntfs_nlstoucs(vol, path.base_name, path.base_len, + base_name, NTFS_MAX_NAME_LEN); + if (*base_len < 0) + return *base_len; + + *stream_len = ntfs_nlstoucs(vol, path.stream_name, path.stream_len, + stream_name, NTFS_MAX_NAME_LEN); + if (*stream_len < 0) { + kmem_cache_free(ntfs_name_cache, *base_name); + *base_name = NULL; + return *stream_len; + } + + err = ntfs_check_bad_windows_name(vol, *base_name, *base_len); + if (err) + goto out_free; + err = ntfs_check_stream_name(*stream_name, *stream_len); + if (err) + goto out_free; + err = ntfs_check_bad_windows_name(vol, *stream_name, *stream_len); + if (err) + goto out_free; + return 1; + +out_free: + kmem_cache_free(ntfs_name_cache, *stream_name); + kmem_cache_free(ntfs_name_cache, *base_name); + *stream_name = NULL; + *base_name = NULL; + return err; +} + +/* + * ntfs_create_named_stream() - Create a named DATA stream + * @idmap: mount idmap used for permission checks + * @dir: directory containing the base object + * @base_name: UTF-16LE base name + * @base_len: length of @base_name in UTF-16 code units + * @stream_name: UTF-16LE stream name + * @stream_len: length of @stream_name in UTF-16 code units + * + * Create the stream on an existing regular file or directory. + * + * Return: Referenced stream inode on success, or ERR_PTR() on failure. + */ +struct inode *ntfs_create_named_stream(struct mnt_idmap *idmap, + struct inode *dir, __le16 *base_name, int base_len, + __le16 *stream_name, int stream_len) +{ + struct ntfs_inode *base_ni; + struct inode *base_vi, *stream_vi; + struct inode *rollback_vi = NULL; + struct ntfs_attr_search_ctx *ctx; + bool stream_created = false; + int err, rollback_err; + + base_vi = ntfs_stream_lookup_inode_by_name(dir, base_name, base_len); + if (IS_ERR(base_vi)) + return base_vi; + if (!S_ISREG(base_vi->i_mode) && !S_ISDIR(base_vi->i_mode)) { + iput(base_vi); + return ERR_PTR(-ENOTDIR); + } + err = inode_permission(idmap, base_vi, MAY_OPEN | MAY_WRITE); + if (err) + goto out_iput; + if (IS_APPEND(base_vi) || IS_IMMUTABLE(base_vi)) { + err = -EPERM; + goto out_iput; + } + if (!(NTFS_SB(base_vi->i_sb)->vol_flags & VOLUME_IS_DIRTY)) { + err = ntfs_set_volume_flags(NTFS_SB(base_vi->i_sb), + VOLUME_IS_DIRTY); + if (err) + goto out_iput; + } + + base_ni = NTFS_I(base_vi); + mutex_lock(&base_ni->mrec_lock); + if (NVolShutdown(base_ni->vol)) { + err = -EIO; + goto out_unlock; + } + if (NInoBeingDeleted(base_ni) || !base_vi->i_nlink) { + err = -ENOENT; + goto out_unlock; + } + if (IS_APPEND(base_vi) || IS_IMMUTABLE(base_vi)) { + err = -EPERM; + goto out_unlock; + } + ctx = ntfs_attr_get_search_ctx(base_ni, NULL); + if (!ctx) { + err = -ENOMEM; + goto out_unlock; + } + + err = ntfs_attr_lookup(AT_DATA, stream_name, stream_len, + IGNORE_CASE, 0, NULL, 0, ctx); + if (!err) { + if (ntfs_stream_unlinked(base_ni, stream_name, stream_len)) + err = -EBUSY; + else + err = -EEXIST; + } else if (err == -ENOENT) { + if (NVolShutdown(base_ni->vol)) { + err = -EIO; + goto out_put_ctx; + } + err = ntfs_attr_add(base_ni, AT_DATA, stream_name, stream_len, + NULL, 0); + if (!err) { + stream_created = true; + mark_mft_record_dirty(base_ni); + } + } + ntfs_attr_put_search_ctx(ctx); + if (err) + goto out_unlock; + + stream_vi = ntfs_attr_iget(base_vi, AT_DATA, stream_name, stream_len); + if (!IS_ERR(stream_vi)) + err = ntfs_stream_inode_validate(stream_vi); + else + err = PTR_ERR(stream_vi); + if (err) { + if (stream_created) { + rollback_err = ntfs_attr_remove(base_ni, AT_DATA, + stream_name, stream_len, &rollback_vi); + if (rollback_err) { + ntfs_error(base_ni->vol->sb, + "Failed to roll back named stream creation.\n"); + if (rollback_err == -ENOMEM || + rollback_err == -EINTR || + rollback_err == -ERESTARTSYS) { + err = rollback_err; + } else { + NVolSetErrors(base_ni->vol); + NVolSetShutdown(base_ni->vol); + err = -EIO; + } + } + } + mutex_unlock(&base_ni->mrec_lock); + if (stream_created) + ntfs_stream_update_base_time(base_ni); + if (!IS_ERR(stream_vi)) + iput(stream_vi); + if (rollback_vi) + iput(rollback_vi); + iput(base_vi); + return ERR_PTR(err); + } + mutex_unlock(&base_ni->mrec_lock); + if (stream_created) + ntfs_stream_update_base_time(base_ni); + iput(base_vi); + return stream_vi; + +out_put_ctx: + ntfs_attr_put_search_ctx(ctx); +out_unlock: + mutex_unlock(&base_ni->mrec_lock); +out_iput: + iput(base_vi); + return ERR_PTR(err); +} + +/* + * ntfs_unlink_named_stream() - Remove a pathname named stream + * @dir: directory containing the base object + * @dentry: dentry for the named stream + * + * Return: 0 on success, or a negative errno. + */ +int ntfs_unlink_named_stream(struct inode *dir, struct dentry *dentry) +{ + struct ntfs_volume *vol = NTFS_SB(dir->i_sb); + struct inode *base_vi; + __le16 *base_name = NULL, *stream_name = NULL; + int base_len, stream_len, path_result, err; + + path_result = ntfs_stream_path_names(vol, &dentry->d_name, &base_name, + &base_len, &stream_name, &stream_len); + if (path_result < 0) + return path_result; + if (!path_result) + return -EINVAL; + + base_vi = ntfs_stream_lookup_inode_by_name(dir, base_name, base_len); + if (IS_ERR(base_vi)) { + err = PTR_ERR(base_vi); + goto out_free; + } + if (!S_ISREG(base_vi->i_mode) && !S_ISDIR(base_vi->i_mode)) { + err = -ENOTDIR; + goto out_iput; + } + if (!ntfs_inode_is_named_stream(NTFS_I(dentry->d_inode))) { + err = -ESTALE; + goto out_iput; + } + if (NTFS_I(dentry->d_inode)->ext.base_ntfs_ino != NTFS_I(base_vi)) { + err = -ESTALE; + goto out_iput; + } + + /* + * VFS authorizes pathname removal via the parent directory, not base + * file write access. The shared helper enforces NTFS delete restrictions. + */ + err = ntfs_remove_named_stream(NTFS_I(base_vi), stream_name, + stream_len, dentry->d_inode); + +out_iput: + iput(base_vi); +out_free: + kmem_cache_free(ntfs_name_cache, stream_name); + kmem_cache_free(ntfs_name_cache, base_name); + return err; +} + +/* Obtain a dentry for the base inode behind a stream inode. */ +static struct dentry *ntfs_stream_base_dentry(struct inode *inode) +{ + struct inode *base_vi = + VFS_I(NTFS_I(inode)->ext.base_ntfs_ino); + struct dentry *dentry; + + dentry = d_find_alias(base_vi); + if (dentry) + return dentry; + if (!igrab(base_vi)) + return ERR_PTR(-ESTALE); + return d_obtain_alias(base_vi); +} + +/* + * ntfs_stream_validate_for_mutation() - Validate a stream before mutation + * @inode: stream inode to validate + * @nowait: do not wait for the base MFT-record lock + * + * Return: 0 if the backing DATA attribute is valid, -EAGAIN if a nonblocking + * lock attempt fails, or another negative errno. + */ +int ntfs_stream_validate_for_mutation(struct inode *inode, bool nowait) +{ + struct ntfs_inode *base_ni = NTFS_I(inode)->ext.base_ntfs_ino; + int err; + + if (nowait) { + if (!mutex_trylock(&base_ni->mrec_lock)) + return -EAGAIN; + } else { + mutex_lock(&base_ni->mrec_lock); + } + err = ntfs_stream_inode_validate(inode); + mutex_unlock(&base_ni->mrec_lock); + return err; +} + +/* + * Hold a temporary stream reference so unlink cannot remove its attribute + * while an operation is using it. + */ +static int ntfs_stream_claim(struct inode *inode) +{ + struct ntfs_inode *ni = NTFS_I(inode); + struct ntfs_inode *base_ni = ni->ext.base_ntfs_ino; + int err; + + mutex_lock(&base_ni->mrec_lock); + if (NInoStreamUnlinked(ni)) + err = -ESTALE; + else + err = ntfs_stream_inode_validate(inode); + if (!err) { + if (atomic_read(&ni->stream_open_count) < 0) + err = -ESTALE; + else + atomic_inc(&ni->stream_open_count); + } + mutex_unlock(&base_ni->mrec_lock); + return err; +} + +/* Stream permission checks use the base inode's permissions. */ +static int ntfs_stream_permission(struct mnt_idmap *idmap, + struct inode *inode, int mask) +{ + return inode_permission(idmap, + VFS_I(NTFS_I(inode)->ext.base_ntfs_ino), mask); +} + +/* Report base metadata with the stream's own size and allocation. */ +static int ntfs_stream_getattr(struct mnt_idmap *idmap, + const struct path *path, struct kstat *stat, + unsigned int request_mask, unsigned int query_flags) +{ + struct inode *inode = d_backing_inode(path->dentry); + struct ntfs_inode *ni = NTFS_I(inode); + struct inode *base_vi = VFS_I(ni->ext.base_ntfs_ino); + + generic_fillattr(idmap, request_mask, base_vi, stat); + stat->size = i_size_read(inode); + stat->blocks = (((u64)ni->i_dealloc_clusters << + NTFS_SB(inode->i_sb)->cluster_size_bits) >> 9) + + inode->i_blocks; + stat->blksize = NTFS_SB(inode->i_sb)->cluster_size; + stat->result_mask |= STATX_BTIME; + stat->btime = NTFS_I(base_vi)->i_crtime; + + if (NInoCompressed(ni)) + stat->attributes |= STATX_ATTR_COMPRESSED; + if (NInoEncrypted(ni)) + stat->attributes |= STATX_ATTR_ENCRYPTED; + if (base_vi->i_flags & S_IMMUTABLE) + stat->attributes |= STATX_ATTR_IMMUTABLE; + if (base_vi->i_flags & S_APPEND) + stat->attributes |= STATX_ATTR_APPEND; + stat->attributes_mask |= STATX_ATTR_COMPRESSED | STATX_ATTR_ENCRYPTED | + STATX_ATTR_IMMUTABLE | STATX_ATTR_APPEND; + stat->mode = (stat->mode & ~S_IFMT) | S_IFREG; + + if (request_mask & STATX_DIOALIGN) { + unsigned int align = + bdev_logical_block_size(inode->i_sb->s_bdev); + + stat->result_mask |= STATX_DIOALIGN; + if (!NInoCompressed(ni) && !NInoEncrypted(ni)) { + stat->dio_mem_align = align; + stat->dio_offset_align = align; + } + } + + return 0; +} + +/* + * Apply size changes to the stream and route shared inode metadata changes to + * its base inode. + */ +static int ntfs_stream_setattr_common(struct mnt_idmap *idmap, + struct inode *inode, struct iattr *attr) +{ + struct ntfs_inode *ni = NTFS_I(inode); + struct inode *base_vi = VFS_I(ni->ext.base_ntfs_ino); + struct dentry *base_dentry = NULL; + struct iattr base_attr = *attr; + int err; + + base_attr.ia_valid &= ~ATTR_FILE; + base_attr.ia_file = NULL; + if (base_attr.ia_valid & (ATTR_KILL_SUID | ATTR_KILL_SGID)) + base_attr.ia_valid &= ~ATTR_MODE; + else if (base_attr.ia_valid & ATTR_MODE) + base_attr.ia_mode = (base_attr.ia_mode & ~S_IFMT) | + (base_vi->i_mode & S_IFMT); + + if (attr->ia_valid & ATTR_SIZE) { + err = inode_permission(idmap, base_vi, MAY_WRITE); + if (err) + goto out; + if (IS_APPEND(base_vi) || IS_IMMUTABLE(base_vi)) { + err = -EPERM; + goto out; + } + if (!(ni->vol->vol_flags & VOLUME_IS_DIRTY)) { + err = ntfs_set_volume_flags(ni->vol, VOLUME_IS_DIRTY); + if (err) + goto out; + } + base_attr.ia_valid &= ~ATTR_SIZE; + } + + if ((attr->ia_valid & ATTR_SIZE) || base_attr.ia_valid) { + base_dentry = ntfs_stream_base_dentry(inode); + if (IS_ERR(base_dentry)) { + err = PTR_ERR(base_dentry); + base_dentry = NULL; + goto out; + } + inode_lock_nested(base_vi, I_MUTEX_PARENT); + err = ntfs_stream_validate_for_mutation(inode, false); + if (err) { + inode_unlock(base_vi); + goto out; + } + if (base_attr.ia_valid) + err = notify_change(idmap, base_dentry, &base_attr, + NULL); + else + err = 0; + if (!err) + ntfs_stream_inode_refresh(inode); + inode_unlock(base_vi); + if (err) + goto out; + } + + if (attr->ia_valid & ATTR_SIZE) + err = ntfs_setattr_size(inode, attr); + else + err = 0; +out: + dput(base_dentry); + return err; +} + +/* Hold the stream against unlink while applying VFS setattr operations. */ +static int ntfs_stream_setattr(struct mnt_idmap *idmap, + struct dentry *dentry, struct iattr *attr) +{ + struct inode *inode = d_inode(dentry); + struct ntfs_inode *ni = NTFS_I(inode); + bool claimed = false; + int err; + + if (NVolShutdown(ni->vol)) + return -EIO; + + if (!(attr->ia_valid & ATTR_FILE)) { + err = ntfs_stream_claim(inode); + if (err) + return err; + claimed = true; + } + + err = ntfs_stream_setattr_common(idmap, inode, attr); + if (claimed) + ntfs_stream_put(inode); + return err; +} + +/* + * Apply stream timestamp updates to the base inode after validating the + * backing attribute under its MFT-record lock. Nonblocking callers get + * -EAGAIN. + */ +static int ntfs_stream_update_time_common(struct inode *inode, + enum fs_update_time type, unsigned int flags) +{ + struct ntfs_inode *base_ni = NTFS_I(inode)->ext.base_ntfs_ino; + struct inode *base_vi = VFS_I(base_ni); + int err; + + if (NVolShutdown(base_ni->vol)) + return -EIO; + if (flags & IOCB_NOWAIT) + return -EAGAIN; + + mutex_lock(&base_ni->mrec_lock); + if (NVolShutdown(base_ni->vol)) { + err = -EIO; + goto out_mrec; + } + err = ntfs_stream_inode_validate(inode); + if (!err) { + err = generic_update_time(base_vi, type, flags); + if (!err) + ntfs_stream_inode_refresh(inode); + } +out_mrec: + mutex_unlock(&base_ni->mrec_lock); + return err; +} + +static int ntfs_stream_update_time(struct inode *inode, + enum fs_update_time type, unsigned int flags) +{ + return ntfs_stream_update_time_common(inode, type, flags); +} + +static ssize_t ntfs_stream_listxattr(struct dentry *dentry, char *buffer, + size_t size) +{ + return -EOPNOTSUPP; +} + +#ifdef CONFIG_NTFS_FS_POSIX_ACL +static struct posix_acl *ntfs_stream_get_acl(struct mnt_idmap *idmap, + struct dentry *dentry, int type) +{ + return ERR_PTR(-EOPNOTSUPP); +} + +static int ntfs_stream_set_acl(struct mnt_idmap *idmap, + struct dentry *dentry, struct posix_acl *acl, int type) +{ + return -EOPNOTSUPP; +} +#endif + +/* + * Remove an unlinked stream's DATA attribute after its final active reference + * is released. Base-inode deletion handles the attribute when the base is + * already being removed. + */ +static int ntfs_stream_finish_remove(struct inode *attr_vi) +{ + struct ntfs_inode *attr_ni = NTFS_I(attr_vi); + struct ntfs_inode *ni = attr_ni->ext.base_ntfs_ino; + int err; + + mutex_lock(&ni->mrec_lock); + if (!NInoStreamUnlinked(attr_ni) || + atomic_cmpxchg(&attr_ni->stream_open_count, 0, -1)) { + err = 0; + goto out_unlock; + } + if (NInoBeingDeleted(ni) || !VFS_I(ni)->i_nlink) { + remove_inode_hash(attr_vi); + err = 0; + goto out_unlock; + } + mutex_unlock(&ni->mrec_lock); + + truncate_inode_pages(attr_vi->i_mapping, 0); + + mutex_lock(&ni->mrec_lock); + if (NVolShutdown(ni->vol)) { + err = -EIO; + goto out_unlock; + } + if (NInoBeingDeleted(ni) || !VFS_I(ni)->i_nlink) { + remove_inode_hash(attr_vi); + err = 0; + goto out_unlock; + } + err = ntfs_attr_rm(attr_ni); + if (!err) { + NInoClearDirty(attr_ni); + remove_inode_hash(attr_vi); + } else { + NInoSetBeingDeleted(attr_ni); + remove_inode_hash(attr_vi); + } + +out_unlock: + mutex_unlock(&ni->mrec_lock); + return err; +} + +/* + * ntfs_stream_put() - Release a stream operation reference + * @vi: stream inode whose reference is being released + * + * Finish a deferred unlink when this is the last reference to an unlinked + * stream. + * + * Return: 0 on success, or a negative errno if deferred removal fails. + */ +int ntfs_stream_put(struct inode *vi) +{ + struct ntfs_inode *ni = NTFS_I(vi); + + if (atomic_dec_and_test(&ni->stream_open_count) && + NInoStreamUnlinked(ni)) + return ntfs_stream_finish_remove(vi); + return 0; +} + +/* + * ntfs_remove_named_stream() - Remove a named DATA stream + * @ni: base inode containing the stream + * @uname: UTF-16LE stream name + * @uname_len: length of @uname in UTF-16 code units + * @expected_vi: expected cached stream inode, or NULL + * + * Refuse removal while the stream is open. If @expected_vi is non-NULL, it + * must be the inode currently associated with the named attribute. + * + * Return: 0 on success, -ENOENT if the stream is absent, or another negative + * errno. + */ +int ntfs_remove_named_stream(struct ntfs_inode *ni, __le16 *uname, + u32 uname_len, struct inode *expected_vi) +{ + struct inode *attr_vi; + struct ntfs_inode *attr_ni; + int err; + + if (!ni || !uname || uname_len == 0) + return -EINVAL; + if (NVolShutdown(ni->vol)) + return -EIO; + if ((ni->flags & FILE_ATTR_READONLY) || + IS_APPEND(VFS_I(ni)) || IS_IMMUTABLE(VFS_I(ni))) + return -EPERM; + if (!(ni->vol->vol_flags & VOLUME_IS_DIRTY)) { + err = ntfs_set_volume_flags(ni->vol, VOLUME_IS_DIRTY); + if (err) + return err; + } + + mutex_lock(&ni->mrec_lock); + if (NInoBeingDeleted(ni) || !VFS_I(ni)->i_nlink) { + err = -ENOENT; + goto out_unlock; + } + attr_vi = ntfs_attr_iget(VFS_I(ni), AT_DATA, uname, uname_len); + if (IS_ERR(attr_vi)) { + err = PTR_ERR(attr_vi); + goto out_unlock; + } + if (expected_vi && attr_vi != expected_vi) { + err = -ESTALE; + goto out_iput; + } + + attr_ni = NTFS_I(attr_vi); + if (NInoStreamUnlinked(attr_ni)) { + err = -ENOENT; + goto out_iput; + } + if (atomic_read(&attr_ni->stream_open_count) > 0) { + err = -EBUSY; + goto out_iput; + } + err = ntfs_stream_inode_validate(attr_vi); + if (err) + goto out_iput; + + NInoSetStreamUnlinked(attr_ni); + clear_nlink(attr_vi); + mutex_unlock(&ni->mrec_lock); + + err = ntfs_stream_finish_remove(attr_vi); + if (!err) + ntfs_stream_update_base_time(ni); + iput(attr_vi); + return err; + +out_iput: + mutex_unlock(&ni->mrec_lock); + iput(attr_vi); + return err; +out_unlock: + mutex_unlock(&ni->mrec_lock); + return err; +} + +/* + * Validate access against the base inode and hold the stream against unlink + * until the file is released. + */ +static int ntfs_stream_file_open(struct inode *inode, struct file *file) +{ + struct ntfs_inode *ni = NTFS_I(inode); + struct ntfs_inode *base_ni = ni->ext.base_ntfs_ino; + struct inode *base_vi = VFS_I(base_ni); + int mask = MAY_OPEN; + int err; + + if (file->f_mode & FMODE_READ) + mask |= MAY_READ; + if (file->f_mode & FMODE_WRITE) + mask |= MAY_WRITE; + err = inode_permission(file_mnt_idmap(file), base_vi, mask); + if (err) + return err; + + mutex_lock(&base_ni->mrec_lock); + if (NInoStreamUnlinked(ni)) + err = -ENOENT; + else + err = ntfs_stream_inode_validate(inode); + if (err) + goto out_unlock; + if ((file->f_mode & FMODE_WRITE) && + (IS_IMMUTABLE(base_vi) || + (IS_APPEND(base_vi) && !(file->f_flags & O_APPEND)))) { + err = -EPERM; + goto out_unlock; + } + if ((file->f_flags & O_TRUNC) && IS_APPEND(base_vi)) { + err = -EPERM; + goto out_unlock; + } + if ((file->f_flags & O_NOATIME) && + !inode_owner_or_capable(file_mnt_idmap(file), base_vi)) { + err = -EPERM; + goto out_unlock; + } + err = ntfs_file_open(inode, file); + if (err) + goto out_unlock; + if (file->f_mode & FMODE_WRITE) { + err = get_write_access(base_vi); + if (err) + goto out_unlock; + file->private_data = base_vi; + } + + atomic_inc(&ni->stream_open_count); +out_unlock: + mutex_unlock(&base_ni->mrec_lock); + return err; +} + +/* Drop open-time references and complete any deferred stream unlink. */ +static int ntfs_stream_file_release(struct inode *inode, struct file *file) +{ + int err, remove_err; + + err = ntfs_file_release(inode, file); + if (file->private_data) { + put_write_access(file->private_data); + file->private_data = NULL; + } + remove_err = ntfs_stream_put(inode); + if (!err) + err = remove_err; + return err; +} + +enum ntfs_stream_ioctl_op { + NTFS_STREAM_IOCTL_READ, + NTFS_STREAM_IOCTL_WRITE, + NTFS_STREAM_IOCTL_REMOVE, +}; + +/* + * Look up or create an ioctl target stream and return its referenced inode. + */ +static int ntfs_stream_ioctl_get_inode(struct inode *base_vi, __le16 *sname, + int sname_len, bool create, struct inode **stream_vi) +{ + struct ntfs_inode *base_ni = NTFS_I(base_vi); + struct ntfs_attr_search_ctx *ctx; + struct inode *attr_vi = NULL; + bool created = false; + int err; + + *stream_vi = NULL; + + mutex_lock(&base_ni->mrec_lock); + if (NVolShutdown(base_ni->vol)) { + err = -EIO; + goto out_unlock; + } + if (NInoBeingDeleted(base_ni) || !base_vi->i_nlink) { + err = -ENOENT; + goto out_unlock; + } + + ctx = ntfs_attr_get_search_ctx(base_ni, NULL); + if (!ctx) { + err = -ENOMEM; + goto out_unlock; + } + + err = ntfs_attr_lookup(AT_DATA, sname, sname_len, IGNORE_CASE, + 0, NULL, 0, ctx); + if (err == -ENOENT && create) { + err = ntfs_attr_add(base_ni, AT_DATA, sname, sname_len, + NULL, 0); + if (!err) { + created = true; + mark_mft_record_dirty(base_ni); + } + } + ntfs_attr_put_search_ctx(ctx); + if (err) + goto out_unlock; + + attr_vi = ntfs_attr_iget(base_vi, AT_DATA, sname, sname_len); + if (IS_ERR(attr_vi)) { + err = PTR_ERR(attr_vi); + attr_vi = NULL; + goto out_unlock; + } + if (NInoStreamUnlinked(NTFS_I(attr_vi))) + err = create ? -EBUSY : -ENOENT; + else + err = ntfs_stream_inode_validate(attr_vi); +out_unlock: + mutex_unlock(&base_ni->mrec_lock); + if (created) + ntfs_stream_update_base_time(base_ni); + if (err) { + iput(attr_vi); + return err; + } + + *stream_vi = attr_vi; + return 0; +} + +/* Check file mode, base-inode flags, and the requested transfer range. */ +static int ntfs_stream_ioctl_access(struct file *filp, + enum ntfs_stream_ioctl_op op, loff_t pos, size_t len) +{ + struct inode *inode = file_inode(filp); + bool is_dir = S_ISDIR(inode->i_mode); + int err; + + if (op == NTFS_STREAM_IOCTL_READ) { + if (!(filp->f_mode & FMODE_READ)) + return -EBADF; + return rw_verify_area(READ, filp, &pos, len); + } + + if (!(filp->f_mode & FMODE_WRITE) && !is_dir) + return -EBADF; + if (is_dir) { + err = inode_permission(file_mnt_idmap(filp), inode, + MAY_WRITE | MAY_EXEC); + if (err) + return err; + } + if (IS_APPEND(inode) || IS_IMMUTABLE(inode)) + return -EPERM; + if (op == NTFS_STREAM_IOCTL_REMOVE) + return 0; + return rw_verify_area(WRITE, filp, &pos, len); +} + +/* + * Validate and execute a named-stream read, write, or remove ioctl request. + */ +static long ntfs_ioctl_stream(struct file *filp, unsigned long arg, + enum ntfs_stream_ioctl_op op) +{ + struct inode *base_vi = file_inode(filp); + struct ntfs_inode *base_ni = NTFS_I(base_vi); + struct ntfs_stream hdr; + struct ntfs_stream *req; + struct inode *stream_vi = NULL; + __le16 *uname = NULL, *sname; + size_t data_size, total_size; + loff_t pos; + s64 ret; + int sname_len, err; + bool claimed = false, got_write = false, utf16; + + if (NVolShutdown(base_ni->vol)) + return -EIO; + if (NInoAttr(base_ni) || + (!S_ISREG(base_vi->i_mode) && !S_ISDIR(base_vi->i_mode))) + return -EOPNOTSUPP; + if (copy_from_user(&hdr, (void __user *)arg, sizeof(hdr))) + return -EFAULT; + + if (hdr.io_len > NTFS_STREAM_MAX_IO || + (hdr.flags & ~NTFS_STREAM_FL_UTF16) || hdr.reserved) + return -EINVAL; + if (!hdr.name_len) + return -EINVAL; + + utf16 = hdr.flags & NTFS_STREAM_FL_UTF16; + if (utf16) { + if ((hdr.name_len & 1) || + hdr.name_len > NTFS_MAX_NAME_LEN * sizeof(__le16)) + return -EINVAL; + } else if (hdr.name_len > + NTFS_MAX_NAME_LEN * NLS_MAX_CHARSET_SIZE) { + return -EINVAL; + } + + switch (op) { + case NTFS_STREAM_IOCTL_READ: + case NTFS_STREAM_IOCTL_WRITE: + if (!hdr.io_len) + return -EINVAL; + if (hdr.stream_offset > S64_MAX || + hdr.stream_offset > S64_MAX - hdr.io_len) + return -EOVERFLOW; + data_size = hdr.io_len; + break; + case NTFS_STREAM_IOCTL_REMOVE: + if (hdr.io_len || hdr.stream_offset) + return -EINVAL; + data_size = 0; + break; + default: + return -ENOTTY; + } + + if (check_add_overflow(sizeof(hdr), (size_t)hdr.name_len, + &total_size) || + check_add_overflow(total_size, data_size, &total_size)) + return -EOVERFLOW; + + if (op == NTFS_STREAM_IOCTL_READ) { + req = kvmalloc(total_size, GFP_KERNEL); + if (!req) + return -ENOMEM; + if (copy_from_user(req, (void __user *)arg, + sizeof(hdr) + hdr.name_len)) { + kvfree(req); + return -EFAULT; + } + } else { + req = vmemdup_user((void __user *)arg, total_size); + if (IS_ERR(req)) + return PTR_ERR(req); + } + + req->bytes_returned = 0; + if (req->stream_offset != hdr.stream_offset || + req->io_len != hdr.io_len || req->name_len != hdr.name_len || + req->flags != hdr.flags || req->reserved) { + err = -EINVAL; + goto out_free; + } + + if (utf16) { + sname = (__le16 *)req->buffer; + sname_len = req->name_len / sizeof(__le16); + } else { + if (memchr(req->buffer, '\0', req->name_len)) { + err = -EINVAL; + goto out_free; + } + sname_len = ntfs_nlstoucs(base_ni->vol, req->buffer, + req->name_len, &uname, NTFS_MAX_NAME_LEN); + if (sname_len < 0) { + err = sname_len; + goto out_free; + } + sname = uname; + } + err = ntfs_check_stream_name(sname, sname_len); + if (err) + goto out_free; + + pos = hdr.stream_offset; + err = ntfs_stream_ioctl_access(filp, op, pos, data_size); + if (err) + goto out_free; + + if (op == NTFS_STREAM_IOCTL_REMOVE) { + err = mnt_want_write_file(filp); + if (err) + goto out_free; + err = ntfs_remove_named_stream(base_ni, sname, sname_len, NULL); + mnt_drop_write_file(filp); + goto out_free; + } + + if (op == NTFS_STREAM_IOCTL_WRITE) { + err = mnt_want_write_file(filp); + if (err) + goto out_free; + got_write = true; + inode_lock(base_vi); + err = file_remove_privs(filp); + inode_unlock(base_vi); + if (err) + goto out_drop_write; + if (!(base_ni->vol->vol_flags & VOLUME_IS_DIRTY)) { + err = ntfs_set_volume_flags(base_ni->vol, + VOLUME_IS_DIRTY); + if (err) + goto out_drop_write; + } + } + + err = ntfs_stream_ioctl_get_inode(base_vi, sname, sname_len, + op == NTFS_STREAM_IOCTL_WRITE, &stream_vi); + if (err) + goto out_drop_write; + + err = ntfs_stream_claim(stream_vi); + if (err) + goto out_drop_write; + claimed = true; + + if (op == NTFS_STREAM_IOCTL_READ) { + inode_lock_shared(stream_vi); + if (pos < i_size_read(stream_vi)) + ret = ntfs_inode_attr_pread(stream_vi, pos, data_size, + req->buffer + req->name_len); + else + ret = 0; + inode_unlock_shared(stream_vi); + if (ret < 0) + err = ret; + else + req->bytes_returned = ret; + if (!err) + file_accessed(filp); + } else { + inode_lock(stream_vi); + inode_dio_wait(stream_vi); + /* Exclude faults before taking MFT record and folio locks. */ + filemap_invalidate_lock(stream_vi->i_mapping); + err = inode_newsize_ok(stream_vi, pos + data_size); + if (!err) { + ret = ntfs_inode_attr_pwrite(stream_vi, pos, data_size, + req->buffer + req->name_len, false); + if (ret < 0) + err = ret; + else + req->bytes_returned = ret; + } + filemap_invalidate_unlock(stream_vi->i_mapping); + inode_unlock(stream_vi); + } + if (claimed) { + int put_err; + + put_err = ntfs_stream_put(stream_vi); + claimed = false; + if (!err && put_err) + err = put_err; + } + iput(stream_vi); + stream_vi = NULL; + if (err) + goto out_drop_write; + + if (op == NTFS_STREAM_IOCTL_READ) { + if (copy_to_user((void __user *)arg, req, + sizeof(*req) + req->name_len + + req->bytes_returned)) + err = -EFAULT; + } else if (copy_to_user((void __user *)arg + + offsetof(struct ntfs_stream, bytes_returned), + &req->bytes_returned, sizeof(req->bytes_returned))) { + err = -EFAULT; + } + goto out_drop_write; + +out_drop_write: + if (claimed) + ntfs_stream_put(stream_vi); + if (stream_vi) + iput(stream_vi); + if (got_write) + mnt_drop_write_file(filp); +out_free: + if (uname) + kmem_cache_free(ntfs_name_cache, uname); + kvfree(req); + return err; +} + +/* + * ntfs_ioctl_stream_read() - Handle a named-stream read ioctl + * @filp: file opened on the base file or directory + * @arg: userspace pointer to the request and data buffer + * + * Return: 0 on success, or a negative errno. + */ +long ntfs_ioctl_stream_read(struct file *filp, unsigned long arg) +{ + return ntfs_ioctl_stream(filp, arg, NTFS_STREAM_IOCTL_READ); +} + +/* + * ntfs_ioctl_stream_write() - Handle a named-stream write ioctl + * @filp: file opened on the base file or directory + * @arg: userspace pointer to the request and data buffer + * + * Create the stream if needed. + * + * Return: 0 on success, or a negative errno. + */ +long ntfs_ioctl_stream_write(struct file *filp, unsigned long arg) +{ + return ntfs_ioctl_stream(filp, arg, NTFS_STREAM_IOCTL_WRITE); +} + +/* + * ntfs_ioctl_stream_remove() - Handle a named-stream remove ioctl + * @filp: file opened on the base file or directory + * @arg: userspace pointer to the request + * + * Return: 0 on success, or a negative errno. + */ +long ntfs_ioctl_stream_remove(struct file *filp, unsigned long arg) +{ + return ntfs_ioctl_stream(filp, arg, NTFS_STREAM_IOCTL_REMOVE); +} + +/* + * ntfs_ioctl_list_streams() - List named DATA streams + * @filp: file opened on the base file or directory + * @arg: userspace pointer to the request and output buffer + * + * On -ENOSPC, return the required buffer size in the request header. + * + * Return: 0 on success, or a negative errno. + */ +int ntfs_ioctl_list_streams(struct file *filp, unsigned long arg) +{ + struct inode *inode = file_inode(filp); + struct ntfs_inode *ni = NTFS_I(inode); + struct ntfs_list_streams hdr; + struct ntfs_stream_entry *entry, *last_entry = NULL; + size_t required = 0; + void *kbuf = NULL; + void __user *ubuf; + struct attr_record *a; + struct ntfs_attr_search_ctx *actx; + int ret = 0, err, count = 0; + size_t entry_size, offset = 0; + const size_t name_offset = offsetof(struct ntfs_stream_entry, name); + int name_len; + unsigned char *sn = NULL; + bool utf16; + + if (NVolShutdown(ni->vol)) + return -EIO; + if (NInoAttr(ni) || + (!S_ISREG(inode->i_mode) && !S_ISDIR(inode->i_mode))) + return -EOPNOTSUPP; + err = inode_permission(file_mnt_idmap(filp), inode, MAY_READ); + if (err) + return err; + + if (copy_from_user(&hdr, (void __user *)arg, sizeof(hdr))) + return -EFAULT; + + if ((hdr.flags & ~NTFS_STREAM_FL_UTF16) || hdr.reserved) + return -EINVAL; + + utf16 = hdr.flags & NTFS_STREAM_FL_UTF16; + + ubuf = (void __user *)arg + sizeof(hdr); + + mutex_lock(&ni->mrec_lock); + actx = ntfs_attr_get_search_ctx(ni, NULL); + if (!actx) { + mutex_unlock(&ni->mrec_lock); + return -ENOMEM; + } + + while ((err = ntfs_attrs_walk(actx)) == 0) { + a = actx->attr; + if (a->type != AT_DATA || !a->name_length) + continue; + if (a->non_resident && + a->data.non_resident.lowest_vcn) + continue; + if (ntfs_stream_unlinked(ni, + (__le16 *)((u8 *)a + + le16_to_cpu(a->name_offset)), + a->name_length)) + continue; + + if (utf16) { + name_len = a->name_length * sizeof(__le16); + } else { + name_len = ntfs_ucstonls(ni->vol, + (__le16 *)((u8 *)a + + le16_to_cpu(a->name_offset)), + a->name_length, &sn, 0); + if (name_len < 0) { + if (name_len == -EILSEQ || + name_len == -ENAMETOOLONG) + continue; + ret = name_len; + goto out; + } + kfree(sn); + sn = NULL; + } + + entry_size = ALIGN(name_offset + name_len, 8); + + if (required > SIZE_MAX - entry_size) { + ret = -EOVERFLOW; + goto out; + } + required += entry_size; + count++; + } + if (err != -ENOENT) { + ret = err; + goto out; + } + + hdr.stream_count = count; + hdr.bytes_returned = required; + + if (!count) + goto out; + + if (hdr.buffer_size < required) { + ret = -ENOSPC; + goto out; + } + + kbuf = kvzalloc(required, GFP_NOFS); + if (!kbuf) { + ret = -ENOMEM; + goto out; + } + + ntfs_attr_reinit_search_ctx(actx); + while ((err = ntfs_attrs_walk(actx)) == 0) { + a = actx->attr; + if (a->type != AT_DATA || !a->name_length) + continue; + if (a->non_resident && + a->data.non_resident.lowest_vcn) + continue; + if (ntfs_stream_unlinked(ni, + (__le16 *)((u8 *)a + + le16_to_cpu(a->name_offset)), + a->name_length)) + continue; + if (utf16) { + sn = NULL; + name_len = a->name_length * sizeof(__le16); + } else { + name_len = ntfs_ucstonls(ni->vol, + (__le16 *)((u8 *)a + + le16_to_cpu(a->name_offset)), + a->name_length, &sn, 0); + if (name_len < 0) { + if (name_len == -EILSEQ || + name_len == -ENAMETOOLONG) + continue; + ret = name_len; + goto out; + } + } + + entry_size = ALIGN(name_offset + name_len, 8); + if (offset > required - entry_size) { + ret = -EOVERFLOW; + kfree(sn); + sn = NULL; + goto out; + } + + entry = (struct ntfs_stream_entry *)(kbuf + offset); + + if (a->non_resident) { + entry->size = le64_to_cpu(a->data.non_resident.data_size); + entry->alloc_size = + le64_to_cpu(a->data.non_resident.allocated_size); + } else { + entry->size = le32_to_cpu(a->data.resident.value_length); + entry->alloc_size = le32_to_cpu(a->length) - + le16_to_cpu(a->data.resident.value_offset); + } + + entry->name_len = name_len; + entry->name_offset = name_offset; + if (utf16) + memcpy(entry->name, + (u8 *)a + le16_to_cpu(a->name_offset), name_len); + else + memcpy(entry->name, sn, name_len); + kfree(sn); + sn = NULL; + + entry->next_entry_off = entry_size; + last_entry = entry; + offset += entry_size; + } + if (err != -ENOENT) { + ret = err; + } else { + /* The chain terminates at the last entry. */ + if (last_entry) + last_entry->next_entry_off = 0; + hdr.bytes_returned = offset; + } + +out: + kfree(sn); + ntfs_attr_put_search_ctx(actx); + mutex_unlock(&ni->mrec_lock); + + if (ret && ret != -ENOSPC) + goto out_free; + + if (!ret && kbuf && copy_to_user(ubuf, kbuf, hdr.bytes_returned)) { + ret = -EFAULT; + goto out_free; + } + + if (copy_to_user((void __user *)arg, &hdr, sizeof(hdr))) + ret = -EFAULT; + +out_free: + kvfree(kbuf); + return ret; +} + +const struct file_operations ntfs_stream_file_ops = { + .llseek = ntfs_file_llseek, + .read_iter = ntfs_file_read_iter, + .write_iter = ntfs_file_write_iter, + .fsync = ntfs_file_fsync, + .fallocate = ntfs_fallocate, + .mmap_prepare = ntfs_file_mmap_prepare, + .open = ntfs_stream_file_open, + .release = ntfs_stream_file_release, + .splice_read = ntfs_file_splice_read, + .splice_write = iter_file_splice_write, +}; + +const struct inode_operations ntfs_stream_inode_ops = { + .permission = ntfs_stream_permission, + .setattr = ntfs_stream_setattr, + .getattr = ntfs_stream_getattr, + .listxattr = ntfs_stream_listxattr, +#ifdef CONFIG_NTFS_FS_POSIX_ACL + .get_acl = ntfs_stream_get_acl, + .set_acl = ntfs_stream_set_acl, +#endif + .update_time = ntfs_stream_update_time, + .fiemap = ntfs_fiemap, +}; diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c index 3e572c00e102..62b01646c262 100644 --- a/fs/ntfs/namei.c +++ b/fs/ntfs/namei.c @@ -8,6 +8,7 @@ #include <linux/exportfs.h> #include <linux/iversion.h> +#include <linux/namei.h> #include "ntfs.h" #include "time.h" @@ -15,6 +16,7 @@ #include "reparse.h" #include "object_id.h" #include "ea.h" +#include "stream.h" static const __le16 aux_name_le[3] = { cpu_to_le16('A'), cpu_to_le16('U'), cpu_to_le16('X') @@ -57,7 +59,18 @@ static inline int ntfs_check_bad_char(const __le16 *wc, unsigned int wc_len) return 0; } -static int ntfs_check_bad_windows_name(struct ntfs_volume *vol, +/* + * ntfs_check_bad_windows_name() - Validate a name against Windows rules + * @vol: NTFS volume + * @wc: UTF-16LE name + * @wc_len: length of @wc in UTF-16 code units + * + * When Windows-name checks are enabled, reject disallowed characters, + * trailing spaces or dots, and reserved DOS device names. + * + * Return: 0 if valid, or -EINVAL otherwise. + */ +int ntfs_check_bad_windows_name(struct ntfs_volume *vol, const __le16 *wc, unsigned int wc_len) { @@ -167,19 +180,29 @@ static int ntfs_check_bad_windows_name(struct ntfs_volume *vol, * * Locking: Caller must hold i_mutex on the directory. */ + static struct dentry *ntfs_lookup(struct inode *dir_ino, struct dentry *dent, unsigned int flags) { struct ntfs_volume *vol = NTFS_SB(dir_ino->i_sb); + struct ntfs_stream_path stream_path; struct inode *dent_inode; __le16 *uname; struct ntfs_name *name = NULL; u64 mref; unsigned long dent_ino; int uname_len; + int stream_path_len; ntfs_debug("Looking up %pd in directory inode 0x%llx.", dent, NTFS_I(dir_ino)->mft_no); + stream_path_len = ntfs_stream_path_parse(vol, &dent->d_name, + &stream_path); + if (stream_path_len < 0) + return ERR_PTR(stream_path_len); + if (stream_path_len) + return ntfs_lookup_stream(dir_ino, dent, &stream_path); + /* Convert the name of the dentry to Unicode. */ uname_len = ntfs_nlstoucs(vol, dent->d_name.name, dent->d_name.len, &uname, NTFS_MAX_NAME_LEN); @@ -405,6 +428,7 @@ static struct ntfs_inode *__ntfs_create(const struct mnt_idmap *idmap, struct in struct inode *vi; struct mft_record *ni_mrec, *dni_mrec; struct super_block *sb = dir_ni->vol->sb; + struct inode *rollback_data_vi = NULL, *rollback_sd_vi = NULL; __le64 parent_mft_ref; u64 child_mft_ref; __le16 ea_size; @@ -433,9 +457,8 @@ static struct ntfs_inode *__ntfs_create(const struct mnt_idmap *idmap, struct in ni->itype.index.vcn_size_bits = vol->cluster_size_bits; } else { - ni->itype.index.vcn_size = vol->sector_size; - ni->itype.index.vcn_size_bits = - vol->sector_size_bits; + ni->itype.index.vcn_size = NTFS_BLOCK_SIZE; + ni->itype.index.vcn_size_bits = NTFS_BLOCK_SIZE_BITS; } } @@ -569,7 +592,7 @@ static struct ntfs_inode *__ntfs_create(const struct mnt_idmap *idmap, struct in NTFS_B_TO_CLU(vol, ni->vol->index_record_size); else ir->clusters_per_index_block = - ni->vol->index_record_size >> ni->vol->sector_size_bits; + ni->vol->index_record_size >> NTFS_BLOCK_SIZE_BITS; ir->index.entries_offset = cpu_to_le32(sizeof(struct index_header)); ir->index.index_length = cpu_to_le32(index_len); ir->index.allocated_size = cpu_to_le32(index_len); @@ -695,11 +718,25 @@ static struct ntfs_inode *__ntfs_create(const struct mnt_idmap *idmap, struct in return ni; err_out: - if (rollback_sd) - ntfs_attr_remove(ni, AT_SECURITY_DESCRIPTOR, AT_UNNAMED, 0); + if (rollback_sd) { + int rollback_err; - if (rollback_data) - ntfs_attr_remove(ni, AT_DATA, AT_UNNAMED, 0); + rollback_err = ntfs_attr_remove(ni, + AT_SECURITY_DESCRIPTOR, AT_UNNAMED, 0, + &rollback_sd_vi); + if (rollback_err) + ntfs_error(sb, + "Failed to roll back security descriptor.\n"); + } + + if (rollback_data) { + int rollback_err; + + rollback_err = ntfs_attr_remove(ni, AT_DATA, AT_UNNAMED, + 0, &rollback_data_vi); + if (rollback_err) + ntfs_error(sb, "Failed to roll back DATA attribute.\n"); + } if (rollback_reparse) ntfs_delete_reparse_index(ni); @@ -727,6 +764,10 @@ err_out: mutex_unlock(&dir_ni->mrec_lock); mutex_unlock(&ni->mrec_lock); + if (rollback_data_vi) + iput(rollback_data_vi); + if (rollback_sd_vi) + iput(rollback_sd_vi); remove_inode_hash(vi); discard_new_inode(vi); return ERR_PTR(err); @@ -737,12 +778,35 @@ static int ntfs_create(const struct mnt_idmap *idmap, struct inode *dir, { struct ntfs_volume *vol = NTFS_SB(dir->i_sb); struct ntfs_inode *ni; - __le16 *uname; - int uname_len, err; + struct inode *stream_vi = NULL; + __le16 *uname, *base_name = NULL, *stream_name = NULL; + int uname_len, stream_len, path_result, err; if (NVolShutdown(vol)) return -EIO; + path_result = ntfs_stream_path_names(vol, &dentry->d_name, &base_name, + &uname_len, &stream_name, &stream_len); + if (path_result) { + if (path_result < 0) + return path_result; + stream_vi = ntfs_create_named_stream(idmap, dir, base_name, + uname_len, stream_name, stream_len); + if (IS_ERR(stream_vi)) { + err = PTR_ERR(stream_vi); + goto out_free_stream_names; + } + kmem_cache_free(ntfs_name_cache, stream_name); + kmem_cache_free(ntfs_name_cache, base_name); + d_instantiate(dentry, stream_vi); + return 0; + +out_free_stream_names: + kmem_cache_free(ntfs_name_cache, stream_name); + kmem_cache_free(ntfs_name_cache, base_name); + return err; + } + uname_len = ntfs_nlstoucs(vol, dentry->d_name.name, dentry->d_name.len, &uname, NTFS_MAX_NAME_LEN); if (uname_len < 0) { @@ -757,10 +821,10 @@ static int ntfs_create(const struct mnt_idmap *idmap, struct inode *dir, return err; } - if (!(vol->vol_flags & VOLUME_IS_DIRTY)) - ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); - ni = __ntfs_create(idmap, dir, uname, uname_len, S_IFREG | mode, 0, NULL, 0); + ni = __ntfs_create(idmap, dir, uname, uname_len, S_IFREG | mode, 0, + NULL, 0); kmem_cache_free(ntfs_name_cache, uname); if (IS_ERR(ni)) return PTR_ERR(ni); @@ -807,8 +871,29 @@ static int ntfs_test_inode_attr(struct inode *vi, void *data) return 0; if (NInoAttr(ni) || ni->nr_extents == -1) return 1; - else - return 0; + return 0; +} + +static void ntfs_cleanup_deleted_inode(struct ntfs_inode *ni) +{ + struct inode *attr_vi; + struct super_block *sb = VFS_I(ni)->i_sb; + + if (!NInoBeingDeleted(ni)) + return; + + while ((attr_vi = ilookup5(sb, ni->mft_no, ntfs_test_inode_attr, + (void *)(uintptr_t)ni->mft_no))) { + struct ntfs_inode *attr_ni = NTFS_I(attr_vi); + + if (ntfs_inode_is_named_stream(attr_ni)) { + if (atomic_read(&attr_ni->stream_open_count) > 0) + NInoSetStreamUnlinked(attr_ni); + remove_inode_hash(attr_vi); + } + clear_nlink(attr_vi); + iput(attr_vi); + } } /* @@ -831,10 +916,10 @@ static int ntfs_delete(struct ntfs_inode *ni, struct ntfs_inode *dir_ni, struct file_name_attr *fn = NULL; bool looking_for_dos_name = false, looking_for_win32_name = false; bool case_sensitive_match = true; + bool link_count_zero = false; int err = 0; struct mft_record *ni_mrec; struct super_block *sb; - bool link_count_zero = false; ntfs_debug("Entering.\n"); @@ -978,22 +1063,10 @@ search: if (need_lock == true) { mutex_unlock(&dir_ni->mrec_lock); mutex_unlock(&ni->mrec_lock); + if (link_count_zero) + ntfs_cleanup_deleted_inode(ni); } - /* - * If hard link count is not equal to zero then we are done. In other - * case there are no reference to this inode left, so we should free all - * non-resident attributes and mark all MFT record as not in use. - */ - if (link_count_zero == true) { - struct inode *attr_vi; - - while ((attr_vi = ilookup5(sb, ni->mft_no, ntfs_test_inode_attr, - (void *)(uintptr_t)ni->mft_no)) != NULL) { - clear_nlink(attr_vi); - iput(attr_vi); - } - } ntfs_debug("Done.\n"); return 0; err_out: @@ -1018,6 +1091,11 @@ static int ntfs_unlink(struct inode *dir, struct dentry *dentry) if (NVolShutdown(vol)) return -EIO; + if (ntfs_stream_path_has_colon(vol, &dentry->d_name)) + return ntfs_unlink_named_stream(dir, dentry); + if (NInoAttr(ni)) + return -EOPNOTSUPP; + uname_len = ntfs_nlstoucs(vol, dentry->d_name.name, dentry->d_name.len, &uname, NTFS_MAX_NAME_LEN); if (uname_len < 0) { @@ -1032,8 +1110,7 @@ static int ntfs_unlink(struct inode *dir, struct dentry *dentry) return err; } - if (!(vol->vol_flags & VOLUME_IS_DIRTY)) - ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); err = ntfs_delete(ni, NTFS_I(dir), uname, uname_len, true); if (err) @@ -1062,6 +1139,10 @@ static struct dentry *ntfs_mkdir(const struct mnt_idmap *idmap, struct inode *di if (NVolShutdown(vol)) return ERR_PTR(-EIO); + err = ntfs_stream_path_check(vol, &dentry->d_name); + if (err) + return ERR_PTR(err); + uname_len = ntfs_nlstoucs(vol, dentry->d_name.name, dentry->d_name.len, &uname, NTFS_MAX_NAME_LEN); if (uname_len < 0) { @@ -1076,10 +1157,10 @@ static struct dentry *ntfs_mkdir(const struct mnt_idmap *idmap, struct inode *di return ERR_PTR(err); } - if (!(vol->vol_flags & VOLUME_IS_DIRTY)) - ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); - ni = __ntfs_create(idmap, dir, uname, uname_len, mode, 0, NULL, 0); + ni = __ntfs_create(idmap, dir, uname, uname_len, mode, 0, + NULL, 0); kmem_cache_free(ntfs_name_cache, uname); if (IS_ERR(ni)) { err = PTR_ERR(ni); @@ -1103,6 +1184,10 @@ static int ntfs_rmdir(struct inode *dir, struct dentry *dentry) if (NVolShutdown(vol)) return -EIO; + err = ntfs_stream_path_check(vol, &dentry->d_name); + if (err) + return err; + ni = NTFS_I(vi); uname_len = ntfs_nlstoucs(vol, dentry->d_name.name, dentry->d_name.len, &uname, NTFS_MAX_NAME_LEN); @@ -1118,8 +1203,7 @@ static int ntfs_rmdir(struct inode *dir, struct dentry *dentry) return err; } - if (!(vol->vol_flags & VOLUME_IS_DIRTY)) - ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); err = ntfs_delete(ni, NTFS_I(dir), uname, uname_len, true); if (err) @@ -1268,6 +1352,13 @@ static int ntfs_rename(const struct mnt_idmap *idmap, struct inode *old_dir, if (NVolShutdown(old_dir_ni->vol)) return -EIO; + err = ntfs_stream_path_check(vol, &old_dentry->d_name); + if (err) + return err; + err = ntfs_stream_path_check(vol, &new_dentry->d_name); + if (err) + return err; + if (flags & (RENAME_EXCHANGE | RENAME_WHITEOUT)) return -EINVAL; @@ -1305,8 +1396,7 @@ static int ntfs_rename(const struct mnt_idmap *idmap, struct inode *old_dir, new_dir_first = is_subdir(new_dentry->d_parent, old_dentry->d_parent); - if (!(vol->vol_flags & VOLUME_IS_DIRTY)) - ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); mutex_lock_nested(&old_ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); if (new_ni) @@ -1391,6 +1481,8 @@ err_out: if (new_ni) mutex_unlock(&new_ni->mrec_lock); mutex_unlock(&old_ni->mrec_lock); + if (new_ni) + ntfs_cleanup_deleted_inode(new_ni); if (uname_new) kmem_cache_free(ntfs_name_cache, uname_new); if (uname_old) @@ -1414,6 +1506,10 @@ static int ntfs_symlink(const struct mnt_idmap *idmap, struct inode *dir, if (NVolShutdown(vol)) return -EIO; + err = ntfs_stream_path_check(vol, &dentry->d_name); + if (err) + return err; + usrc_len = ntfs_nlstoucs(vol, dentry->d_name.name, dentry->d_name.len, &usrc, NTFS_MAX_NAME_LEN); if (usrc_len < 0) { @@ -1429,8 +1525,7 @@ static int ntfs_symlink(const struct mnt_idmap *idmap, struct inode *dir, goto out; } - if (!(vol->vol_flags & VOLUME_IS_DIRTY)) - ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); ni = __ntfs_create(idmap, dir, usrc, usrc_len, S_IFLNK | 0777, 0, symname, symlen); @@ -1460,6 +1555,10 @@ static int ntfs_mknod(const struct mnt_idmap *idmap, struct inode *dir, if (NVolShutdown(vol)) return -EIO; + err = ntfs_stream_path_check(vol, &dentry->d_name); + if (err) + return err; + uname_len = ntfs_nlstoucs(vol, dentry->d_name.name, dentry->d_name.len, &uname, NTFS_MAX_NAME_LEN); if (uname_len < 0) { @@ -1474,8 +1573,7 @@ static int ntfs_mknod(const struct mnt_idmap *idmap, struct inode *dir, return err; } - if (!(vol->vol_flags & VOLUME_IS_DIRTY)) - ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); switch (mode & S_IFMT) { case S_IFCHR: @@ -1513,6 +1611,13 @@ static int ntfs_link(struct dentry *old_dentry, struct inode *dir, if (NVolShutdown(vol)) return -EIO; + if (NInoAttr(ni)) + return -EOPNOTSUPP; + + err = ntfs_stream_path_check(vol, &dentry->d_name); + if (err) + return err; + uname_len = ntfs_nlstoucs(vol, dentry->d_name.name, dentry->d_name.len, &uname, NTFS_MAX_NAME_LEN); if (uname_len < 0) { @@ -1521,8 +1626,7 @@ static int ntfs_link(struct dentry *old_dentry, struct inode *dir, return -ENOMEM; } - if (!(vol->vol_flags & VOLUME_IS_DIRTY)) - ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); ihold(vi); mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); @@ -1567,6 +1671,8 @@ const struct inode_operations ntfs_dir_inode_ops = { .listxattr = ntfs_listxattr, .setattr = ntfs_setattr, .getattr = ntfs_getattr, + .fileattr_get = ntfs_fileattr_get, + .fileattr_set = ntfs_fileattr_set, .symlink = ntfs_symlink, .mknod = ntfs_mknod, .link = ntfs_link, @@ -1667,11 +1773,19 @@ static struct dentry *ntfs_fh_to_parent(struct super_block *sb, struct fid *fid, ntfs_nfs_get_inode); } +static int ntfs_encode_fh(struct inode *inode, u32 *fh, int *max_len, + struct inode *parent) +{ + if (ntfs_inode_is_named_stream(NTFS_I(inode))) + return -EOPNOTSUPP; + return generic_encode_ino32_fh(inode, fh, max_len, parent); +} + /* * Export operations allowing NFS exporting of mounted NTFS partitions. */ const struct export_operations ntfs_export_ops = { - .encode_fh = generic_encode_ino32_fh, + .encode_fh = ntfs_encode_fh, .get_parent = ntfs_get_parent, /* Find the parent of a given directory. */ .fh_to_dentry = ntfs_fh_to_dentry, .fh_to_parent = ntfs_fh_to_parent, diff --git a/fs/ntfs/ntfs.h b/fs/ntfs/ntfs.h index 45f77848a9cf..71e53d16b5c1 100644 --- a/fs/ntfs/ntfs.h +++ b/fs/ntfs/ntfs.h @@ -80,6 +80,8 @@ enum { NTFS_MAX_LABEL_LEN = 128, }; +#define NTFS_MAX_ATTR_LIST_SIZE (256 * 1024) + enum { CASE_SENSITIVE = 0, IGNORE_CASE = 1, @@ -183,6 +185,10 @@ extern const struct inode_operations ntfs_dir_inode_ops; extern const struct file_operations ntfs_empty_file_ops; extern const struct inode_operations ntfs_empty_inode_ops; +int ntfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa); +int ntfs_fileattr_set(struct mnt_idmap *idmap, struct dentry *dentry, + struct file_kattr *fa); + extern const struct export_operations ntfs_export_ops; /* @@ -219,7 +225,6 @@ struct option_t { }; extern const struct option_t on_errors_arr[]; int ntfs_set_volume_flags(struct ntfs_volume *vol, __le16 flags); -int ntfs_clear_volume_flags(struct ntfs_volume *vol, __le16 flags); int ntfs_write_volume_label(struct ntfs_volume *vol, char *label); /* From fs/ntfs/mst.c */ @@ -234,14 +239,14 @@ bool ntfs_are_names_equal(const __le16 *s1, size_t s1_len, const __le16 *upcase, const u32 upcase_size); int ntfs_collate_names(const __le16 *name1, const u32 name1_len, const __le16 *name2, const u32 name2_len, - const int err_val, const u32 ic, + const bool check_invalid, const u32 ic, const __le16 *upcase, const u32 upcase_len); int ntfs_ucsncmp(const __le16 *s1, const __le16 *s2, size_t n); int ntfs_ucsncasecmp(const __le16 *s1, const __le16 *s2, size_t n, const __le16 *upcase, const u32 upcase_size); int ntfs_file_compare_values(const struct file_name_attr *file_name_attr1, const struct file_name_attr *file_name_attr2, - const int err_val, const u32 ic, + const bool check_invalid, const u32 ic, const __le16 *upcase, const u32 upcase_len); int ntfs_nlstoucs(const struct ntfs_volume *vol, const char *ins, const int ins_len, __le16 **outs, int max_name_len); diff --git a/fs/ntfs/reparse.c b/fs/ntfs/reparse.c index 1a6073e22677..2a6480211fe3 100644 --- a/fs/ntfs/reparse.c +++ b/fs/ntfs/reparse.c @@ -676,7 +676,7 @@ static int update_reparse_data(struct ntfs_inode *ni, struct ntfs_index_context rp_inode = ntfs_attr_iget(VFS_I(ni), AT_REPARSE_POINT, AT_UNNAMED, 0); if (IS_ERR(rp_inode)) - return -EINVAL; + return PTR_ERR(rp_inode); rp_ni = NTFS_I(rp_inode); /* remove the existing reparse data */ diff --git a/fs/ntfs/runlist.c b/fs/ntfs/runlist.c index 3a61f19bcbee..76ba6ab72b8a 100644 --- a/fs/ntfs/runlist.c +++ b/fs/ntfs/runlist.c @@ -47,7 +47,7 @@ static inline void ntfs_rl_mm(struct runlist_element *base, int dst, int src, in * @srcbase. */ static inline void ntfs_rl_mc(struct runlist_element *dstbase, int dst, - struct runlist_element *srcbase, int src, int size) + const struct runlist_element *srcbase, int src, int size) { if (likely(size > 0)) memcpy(dstbase + dst, srcbase + src, size * sizeof(*dstbase)); @@ -152,8 +152,8 @@ static inline struct runlist_element *ntfs_rl_realloc_nofail(struct runlist_elem * Return: true Success, the runlists can be merged. * false Failure, the runlists cannot be merged. */ -static inline bool ntfs_are_rl_mergeable(struct runlist_element *dst, - struct runlist_element *src) +static inline bool ntfs_are_rl_mergeable(const struct runlist_element *dst, + const struct runlist_element *src) { /* We can merge unmapped regions even if they are misaligned. */ if ((dst->lcn == LCN_RL_NOT_MAPPED) && (src->lcn == LCN_RL_NOT_MAPPED)) @@ -186,7 +186,8 @@ static inline bool ntfs_are_rl_mergeable(struct runlist_element *dst, * * It is up to the caller to serialize access to the runlists @dst and @src. */ -static inline void __ntfs_rl_merge(struct runlist_element *dst, struct runlist_element *src) +static inline void __ntfs_rl_merge(struct runlist_element *dst, + const struct runlist_element *src) { dst->length += src->length; } @@ -214,10 +215,11 @@ static inline void __ntfs_rl_merge(struct runlist_element *dst, struct runlist_e * On error, return -errno. Both runlists are left unmodified. */ static inline struct runlist_element *ntfs_rl_append(struct runlist_element *dst, - int dsize, struct runlist_element *src, int ssize, int loc, + int dsize, const struct runlist_element *src, int ssize, int loc, size_t *new_size) { bool right = false; /* Right end of @src needs merging. */ + s64 right_length = 0; /* Length of the run merged at the right. */ int marker; /* End of the inserted runs. */ /* First, check if the right hand end needs merging. */ @@ -235,9 +237,9 @@ static inline struct runlist_element *ntfs_rl_append(struct runlist_element *dst * original runlists. */ - /* First, merge the right hand end, if necessary. */ + /* Remember the run that the right hand end absorbs, if any. */ if (right) - __ntfs_rl_merge(src + ssize - 1, dst + loc + 1); + right_length = dst[loc + 1].length; /* First run after the @src runs that have been inserted. */ marker = loc + ssize + 1; @@ -246,6 +248,9 @@ static inline struct runlist_element *ntfs_rl_append(struct runlist_element *dst ntfs_rl_mm(dst, marker, loc + 1 + right, dsize - (loc + 1 + right)); ntfs_rl_mc(dst, loc + 1, src, 0, ssize); + /* Merge the right hand end into the copy, leaving @src intact. */ + dst[marker - 1].length += right_length; + /* Adjust the size of the preceding hole. */ dst[loc].length = dst[loc + 1].vcn - dst[loc].vcn; @@ -279,7 +284,7 @@ static inline struct runlist_element *ntfs_rl_append(struct runlist_element *dst * On error, return -errno. Both runlists are left unmodified. */ static inline struct runlist_element *ntfs_rl_insert(struct runlist_element *dst, - int dsize, struct runlist_element *src, int ssize, int loc, + int dsize, const struct runlist_element *src, int ssize, int loc, size_t *new_size) { bool left = false; /* Left end of @src needs merging. */ @@ -374,12 +379,13 @@ static inline struct runlist_element *ntfs_rl_insert(struct runlist_element *dst * On error, return -errno. Both runlists are left unmodified. */ static inline struct runlist_element *ntfs_rl_replace(struct runlist_element *dst, - int dsize, struct runlist_element *src, int ssize, int loc, + int dsize, const struct runlist_element *src, int ssize, int loc, size_t *new_size) { int delta; bool left = false; /* Left end of @src needs merging. */ bool right = false; /* Right end of @src needs merging. */ + s64 right_length = 0; /* Length of the run merged at the right. */ int tail; /* Start of tail of @dst. */ int marker; /* End of the inserted runs. */ @@ -405,9 +411,12 @@ static inline struct runlist_element *ntfs_rl_replace(struct runlist_element *ds * original runlists. */ - /* First, merge the left and right ends, if necessary. */ + /* + * Remember the run that the right hand end absorbs, if any, then + * merge the left hand end. + */ if (right) - __ntfs_rl_merge(src + ssize - 1, dst + loc + 1); + right_length = dst[loc + 1].length; if (left) __ntfs_rl_merge(dst + loc - 1, src); /* @@ -432,6 +441,13 @@ static inline struct runlist_element *ntfs_rl_replace(struct runlist_element *ds ntfs_rl_mm(dst, marker, tail, dsize - tail); ntfs_rl_mc(dst, loc, src, left, ssize - left); + /* + * Merge the right hand end into the last run of @src, now in @dst at + * @marker - 1 (or into @dst[@loc - 1] if that absorbed all of @src), + * leaving @src intact. + */ + dst[marker - 1].length += right_length; + /* We may have changed the length of the file, so fix the end marker. */ if (dsize - tail > 0 && dst[marker].lcn == LCN_ENOENT) dst[marker].vcn = dst[marker - 1].vcn + dst[marker - 1].length; @@ -461,7 +477,7 @@ static inline struct runlist_element *ntfs_rl_replace(struct runlist_element *ds * On error, return -errno. Both runlists are left unmodified. */ static inline struct runlist_element *ntfs_rl_split(struct runlist_element *dst, int dsize, - struct runlist_element *src, int ssize, int loc, + const struct runlist_element *src, int ssize, int loc, size_t *new_size) { /* Space required: @dst size + @src size + one new hole. */ @@ -488,11 +504,12 @@ static inline struct runlist_element *ntfs_rl_split(struct runlist_element *dst, } /* - * ntfs_runlists_merge - merge two runlists into one + * __ntfs_runlists_merge - merge two runlists into one * @d_runlist: destination runlist structure to merge into * @srl: source runlist to merge from * @s_rl_count: number of elements in @srl (0 to auto-detect) * @new_rl_count: on success, set to the new combined runlist size + * @keep_srl: leave @srl to the caller instead of deallocating it * * First we sanity check the two runlists @srl and @drl to make sure that they * are sensible and can be merged. The runlist @srl must be either after the @@ -515,13 +532,14 @@ static inline struct runlist_element *ntfs_rl_split(struct runlist_element *dst, * On success, return a pointer to the new, combined, runlist. Note, both * runlists @drl and @srl are deallocated before returning so you cannot use * the pointers for anything any more. (Strictly speaking the returned runlist - * may be the same as @dst but this is irrelevant.) + * may be the same as @dst but this is irrelevant.) If @keep_srl is true, + * @srl is neither modified nor deallocated, and @drl must not be NULL. * * On error, return -errno. Both runlists are left unmodified. */ -struct runlist_element *ntfs_runlists_merge(struct runlist *d_runlist, - struct runlist_element *srl, size_t s_rl_count, - size_t *new_rl_count) +static struct runlist_element *__ntfs_runlists_merge(struct runlist *d_runlist, + struct runlist_element *srl, size_t s_rl_count, + size_t *new_rl_count, bool keep_srl) { int di, si; /* Current index into @[ds]rl. */ int sstart; /* First index with lcn > LCN_RL_NOT_MAPPED. */ @@ -553,6 +571,8 @@ struct runlist_element *ntfs_runlists_merge(struct runlist *d_runlist, /* Check for the case where the first mapping is being done now. */ if (unlikely(!drl)) { + if (WARN_ON(keep_srl)) + return ERR_PTR(-EINVAL); drl = srl; /* Complete the source runlist if necessary. */ if (unlikely(drl[0].vcn)) { @@ -582,7 +602,8 @@ struct runlist_element *ntfs_runlists_merge(struct runlist *d_runlist, si++; /* Can't have an entirely unmapped source runlist. */ - WARN_ON(!srl[si].length); + if (WARN_ON(!srl[si].length)) + return ERR_PTR(-EINVAL); /* Record the starting points. */ sstart = si; @@ -652,7 +673,8 @@ struct runlist_element *ntfs_runlists_merge(struct runlist *d_runlist, ntfs_error(NULL, "Merge failed."); return drl; } - kvfree(srl); + if (!keep_srl) + kvfree(srl); if (marker) { ntfs_debug("Triggering marker code."); for (ds = dend; drl[ds].length; ds++) @@ -680,9 +702,8 @@ struct runlist_element *ntfs_runlists_merge(struct runlist *d_runlist, /* Add an unmapped runlist element. */ if (!slots) { drl = ntfs_rl_realloc_nofail(drl, ds, - ds + 2); + ds + 3); slots = 2; - *new_rl_count += 2; } ds++; /* Need to set vcn if it isn't set already. */ @@ -698,11 +719,11 @@ struct runlist_element *ntfs_runlists_merge(struct runlist *d_runlist, ds++; if (!slots) { drl = ntfs_rl_realloc_nofail(drl, ds, ds + 1); - *new_rl_count += 1; } drl[ds].vcn = marker_vcn; drl[ds].lcn = LCN_ENOENT; drl[ds].length = (s64)0; + *new_rl_count = ds + 1; } } } @@ -715,6 +736,44 @@ finished: } /* + * ntfs_runlists_merge - merge two runlists into one + * @d_runlist: destination runlist structure to merge into + * @srl: source runlist to merge from, deallocated on success + * @s_rl_count: number of elements in @srl (0 to auto-detect) + * @new_rl_count: on success, set to the new combined runlist size + * + * See __ntfs_runlists_merge(). + */ +struct runlist_element *ntfs_runlists_merge(struct runlist *d_runlist, + struct runlist_element *srl, size_t s_rl_count, + size_t *new_rl_count) +{ + return __ntfs_runlists_merge(d_runlist, srl, s_rl_count, new_rl_count, + false); +} + +/* + * ntfs_runlists_merge_keep_src - merge two runlists, keeping the source + * @d_runlist: destination runlist structure to merge into, not empty + * @srl: source runlist to merge from + * @s_rl_count: number of elements in @srl (0 to auto-detect) + * @new_rl_count: on success, set to the new combined runlist size + * + * Like ntfs_runlists_merge(), but @srl is neither modified nor deallocated, + * on success or on error, and remains the caller's to kvfree(). Even when + * runs of @srl are coalesced with runs of @d_runlist, @srl keeps describing + * exactly its own runs, e.g. the clusters a caller has to free to undo the + * merge. + */ +struct runlist_element *ntfs_runlists_merge_keep_src(struct runlist *d_runlist, + struct runlist_element *srl, size_t s_rl_count, + size_t *new_rl_count) +{ + return __ntfs_runlists_merge(d_runlist, srl, s_rl_count, new_rl_count, + true); +} + +/* * ntfs_mapping_pairs_decompress - convert mapping pairs array to runlist * @vol: ntfs volume * @attr: attribute record whose mapping pairs to decompress diff --git a/fs/ntfs/runlist.h b/fs/ntfs/runlist.h index cc92e09fbf68..0d4c32b1a338 100644 --- a/fs/ntfs/runlist.h +++ b/fs/ntfs/runlist.h @@ -68,6 +68,9 @@ enum { struct runlist_element *ntfs_runlists_merge(struct runlist *d_runlist, struct runlist_element *srl, size_t s_rl_count, size_t *new_rl_count); +struct runlist_element *ntfs_runlists_merge_keep_src(struct runlist *d_runlist, + struct runlist_element *srl, size_t s_rl_count, + size_t *new_rl_count); struct runlist_element *ntfs_mapping_pairs_decompress(const struct ntfs_volume *vol, const struct attr_record *attr, struct runlist *old_runlist, size_t *new_rl_count); diff --git a/fs/ntfs/stream.h b/fs/ntfs/stream.h new file mode 100644 index 000000000000..5cbf6e8f6b68 --- /dev/null +++ b/fs/ntfs/stream.h @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef _NTFS_STREAM_H +#define _NTFS_STREAM_H + +#include <linux/fs.h> + +struct ntfs_inode; +struct ntfs_volume; + +struct ntfs_stream_path { + const char *base_name; + unsigned int base_len; + const char *stream_name; + unsigned int stream_len; +}; + +int ntfs_check_bad_windows_name(struct ntfs_volume *vol, + const __le16 *name, unsigned int name_len); +int ntfs_check_stream_name(const __le16 *name, unsigned int name_len); + +int ntfs_stream_path_parse(struct ntfs_volume *vol, + const struct qstr *qname, struct ntfs_stream_path *path); +bool ntfs_stream_path_has_colon(struct ntfs_volume *vol, + const struct qstr *qname); +int ntfs_stream_path_check(struct ntfs_volume *vol, + const struct qstr *qname); +int ntfs_stream_path_names(struct ntfs_volume *vol, + const struct qstr *qname, __le16 **base_name, int *base_len, + __le16 **stream_name, int *stream_len); + +struct inode *ntfs_stream_lookup_inode_by_name(struct inode *dir, + __le16 *name, int name_len); +struct dentry *ntfs_lookup_stream(struct inode *dir, struct dentry *dentry, + const struct ntfs_stream_path *path); +struct inode *ntfs_create_named_stream(struct mnt_idmap *idmap, + struct inode *dir, __le16 *base_name, int base_len, + __le16 *stream_name, int stream_len); +int ntfs_unlink_named_stream(struct inode *dir, struct dentry *dentry); + +void ntfs_set_default_dentry_ops(struct super_block *sb); + +void ntfs_stream_inode_refresh(struct inode *inode); +int ntfs_stream_inode_validate(struct inode *inode); +void ntfs_stream_update_base_time(struct ntfs_inode *base_ni); +bool ntfs_stream_unlinked(struct ntfs_inode *base_ni, + const __le16 *name, u32 name_len); +int ntfs_stream_put(struct inode *inode); +int ntfs_remove_named_stream(struct ntfs_inode *ni, __le16 *name, + u32 name_len, struct inode *expected_inode); + +int ntfs_stream_validate_for_mutation(struct inode *inode, bool nowait); +long ntfs_ioctl_stream_read(struct file *file, unsigned long arg); +long ntfs_ioctl_stream_write(struct file *file, unsigned long arg); +long ntfs_ioctl_stream_remove(struct file *file, unsigned long arg); +int ntfs_ioctl_list_streams(struct file *file, unsigned long arg); + +extern const struct file_operations ntfs_stream_file_ops; +extern const struct inode_operations ntfs_stream_inode_ops; + +/* + * Common file operations used by both ordinary files and named streams. + * Their implementations remain in file.c. + */ +int ntfs_file_open(struct inode *inode, struct file *file); +int ntfs_file_release(struct inode *inode, struct file *file); +int ntfs_file_fsync(struct file *file, loff_t start, loff_t end, + int datasync); +int ntfs_setattr_size(struct inode *inode, struct iattr *attr); +loff_t ntfs_file_llseek(struct file *file, loff_t offset, int whence); +ssize_t ntfs_file_read_iter(struct kiocb *iocb, struct iov_iter *to); +ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from); +int ntfs_file_mmap_prepare(struct vm_area_desc *desc); +ssize_t ntfs_file_splice_read(struct file *in, loff_t *ppos, + struct pipe_inode_info *pipe, size_t len, unsigned int flags); +int ntfs_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo, + u64 start, u64 len); +long ntfs_fallocate(struct file *file, int mode, loff_t offset, loff_t len); + +int ntfs_test_inode_rcu(struct inode *inode, void *data); + +#endif /* _NTFS_STREAM_H */ diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c index 4066bacabe37..71f0aea46bf8 100644 --- a/fs/ntfs/super.c +++ b/fs/ntfs/super.c @@ -18,9 +18,12 @@ #include "sysctl.h" #include "logfile.h" #include "index.h" +#include "mft.h" #include "ntfs.h" #include "ea.h" +#include "bitmap.h" #include "volume.h" +#include "stream.h" /* A global default upcase table and a corresponding reference count. */ static __le16 *default_upcase; @@ -66,6 +69,17 @@ static const struct constant_table ntfs_symlink_enums[] = { }; enum { + STREAMS_INTERFACE_NONE, + STREAMS_INTERFACE_WINDOWS, +}; + +static const struct constant_table ntfs_streams_interface_enums[] = { + { "none", STREAMS_INTERFACE_NONE }, + { "windows", STREAMS_INTERFACE_WINDOWS }, + {} +}; + +enum { Opt_uid, Opt_gid, Opt_umask, @@ -90,6 +104,7 @@ enum { Opt_nocase, Opt_native_symlink, Opt_symlink, + Opt_streams_interface, }; static const struct fs_parameter_spec ntfs_parameters[] = { @@ -117,6 +132,8 @@ static const struct fs_parameter_spec ntfs_parameters[] = { fsparam_flag("nocase", Opt_nocase), fsparam_enum("native_symlink", Opt_native_symlink, ntfs_native_symlink_enums), fsparam_enum("symlink", Opt_symlink, ntfs_symlink_enums), + fsparam_enum("streams_interface", Opt_streams_interface, + ntfs_streams_interface_enums), {} }; @@ -253,6 +270,22 @@ static int ntfs_parse_param(struct fs_context *fc, struct fs_parameter *param) else NVolClearSymlinkNative(vol); break; + case Opt_streams_interface: { + bool streams_windows = + result.uint_32 == STREAMS_INTERFACE_WINDOWS; + + if (fc->purpose == FS_CONTEXT_FOR_RECONFIGURE && + NVolStreamsWindows(NTFS_SB(fc->root->d_sb)) != streams_windows) { + ntfs_warning(fc->root->d_sb, + "Cannot change streams_interface on remount."); + return -EINVAL; + } + if (streams_windows) + NVolSetStreamsWindows(vol); + else + NVolClearStreamsWindows(vol); + break; + } case Opt_sparse: break; default: @@ -262,14 +295,23 @@ static int ntfs_parse_param(struct fs_context *fc, struct fs_parameter *param) return 0; } +static int ntfs_sync_volume_dirty_state(struct ntfs_volume *vol); + static int ntfs_reconfigure(struct fs_context *fc) { struct super_block *sb = fc->root->d_sb; struct ntfs_volume *vol = NTFS_SB(sb); + int err; ntfs_debug("Entering with remount"); - sync_filesystem(sb); + err = sync_filesystem(sb); + if (err) { + ntfs_warning(sb, "Failed to sync the filesystem."); + /* A forced remount must still turn the superblock read-only. */ + if (!(fc->sb_flags & SB_FORCE)) + return err; + } /* * For the read-write compiled driver, if we are remounting read-write, @@ -286,6 +328,12 @@ static int ntfs_reconfigure(struct fs_context *fc) static const char *es = ". Cannot remount read-write."; /* Remounting read-write. */ + if (!vol->mft_write_supported) { + ntfs_error(sb, + "MFT writeback is unsupported for this geometry%s", + es); + return -EROFS; + } if (NVolErrors(vol)) { ntfs_error(sb, "Volume has errors and is read-only%s", es); @@ -312,10 +360,58 @@ static int ntfs_reconfigure(struct fs_context *fc) } } else if (!sb_rdonly(sb) && (fc->sb_flags & SB_RDONLY)) { /* Remounting read-only. */ - if (!NVolErrors(vol)) { - if (ntfs_clear_volume_flags(vol, VOLUME_IS_DIRTY)) + /* + * With errors recorded the dirty bit is set rather than + * cleared, and it is committed right away: the VFS does + * not sync the filesystem during a remount, and once the + * remount succeeds no further persistence point exists - + * ntfs_sync_fs() is only ever invoked for read-write + * superblocks (all its VFS callers skip read-only ones) + * and ntfs_put_super() skips them, so the only remaining + * write would be the evict-time commit at unmount, which + * a crash never reaches. An error recorded only after + * the remount is still never persisted; a failed commit + * or flush fails the remount, leaving the superblock + * read-write so ntfs_put_super() retries at unmount. + */ + /* + * A forced remount does not drain writers in progress, + * so one may still be modifying metadata when the flags + * are committed: never clear the dirty bit then; if + * errors have been recorded, the update preserves or + * sets it; otherwise, skip the update entirely. + */ + if (!(fc->sb_flags & SB_FORCE) || NVolErrors(vol)) { + err = ntfs_sync_volume_dirty_state(vol); + if (err) { ntfs_warning(sb, - "Failed to clear dirty bit in volume information flags. Run chkdsk."); + "Failed to update dirty bit in volume information flags. Run chkdsk."); + return err; + } + } + if (NInoDirty(NTFS_I(vol->vol_ino))) { + /* ntfs_commit_inode() would discard the error. */ + err = __ntfs_write_inode(vol->vol_ino, 1); + if (err) { + ntfs_warning(sb, + "Failed to commit volume information flags. Run chkdsk."); + return err; + } + /* + * write_mft_record() redirties the record on + * -ENOMEM and still reports success. + */ + if (NInoDirty(NTFS_I(vol->vol_ino))) { + ntfs_warning(sb, + "Volume information flags remain dirty after commit. Run chkdsk."); + return -EIO; + } + err = blkdev_issue_flush(sb->s_bdev); + if (err) { + ntfs_warning(sb, + "Failed to flush volume information flags. Run chkdsk."); + return err; + } } } @@ -353,31 +449,52 @@ void ntfs_handle_error(struct super_block *sb) } /* - * ntfs_write_volume_flags - write new flags to the volume information flags + * ntfs_write_volume_flags - apply flag changes to the volume information flags * @vol: ntfs volume on which to modify the flags - * @flags: new flags value for the volume information flags + * @set_bits: bits to set in the volume information flags + * @clear_bits: bits to clear in the volume information flags + * @dirty_if_errors: force VOLUME_IS_DIRTY on when NVolErrors() is set * * Internal function. You probably want to use ntfs_{set,clear}_volume_flags() - * instead (see below). + * or ntfs_sync_volume_dirty_state() instead (see below). + * + * Combine @set_bits and @clear_bits with the current in-memory flag state and + * write the result back. The set/clear helpers pass only the bits to modify, + * not the complete flag state. The read-modify-write happens under + * ni->mrec_lock so that concurrent set/clear operations cannot lose updates. + * All bit manipulation is done on CPU-endian values, and the result is + * converted back to little-endian before storing it. * - * Replace the volume information flags on the volume @vol with the value - * supplied in @flags. Note, this overwrites the volume information flags, so - * make sure to combine the flags you want to modify with the old flags and use - * the result when calling ntfs_write_volume_flags(). + * When @dirty_if_errors is true and errors have been recorded on @vol, + * VOLUME_IS_DIRTY is forced on after the requested changes. NVolErrors() is + * evaluated under the same mrec_lock, which orders this against other + * locked flag updates; the runtime error paths themselves record the flag + * lock-free, so see ntfs_sync_volume_dirty_state() for the guarantee this + * provides against them. * * Return 0 on success and -errno on error. */ -static int ntfs_write_volume_flags(struct ntfs_volume *vol, const __le16 flags) +static int ntfs_write_volume_flags(struct ntfs_volume *vol, + const __le16 set_bits, const __le16 clear_bits, + const bool dirty_if_errors) { struct ntfs_inode *ni = NTFS_I(vol->vol_ino); struct volume_information *vi; struct ntfs_attr_search_ctx *ctx; + u16 flags; int err; - ntfs_debug("Entering, old flags = 0x%x, new flags = 0x%x.", - le16_to_cpu(vol->vol_flags), le16_to_cpu(flags)); mutex_lock(&ni->mrec_lock); - if (vol->vol_flags == flags) + + flags = le16_to_cpu(vol->vol_flags); + flags |= le16_to_cpu(set_bits) & le16_to_cpu(VOLUME_FLAGS_MASK); + flags &= ~(le16_to_cpu(clear_bits) & le16_to_cpu(VOLUME_FLAGS_MASK)); + if (dirty_if_errors && NVolErrors(vol)) + flags |= le16_to_cpu(VOLUME_IS_DIRTY); + ntfs_debug("Entering, old flags = 0x%x, new flags = 0x%x.", + le16_to_cpu(vol->vol_flags), flags); + + if (le16_to_cpu(vol->vol_flags) == flags) goto done; ctx = ntfs_attr_get_search_ctx(ni, NULL); @@ -393,7 +510,7 @@ static int ntfs_write_volume_flags(struct ntfs_volume *vol, const __le16 flags) vi = (struct volume_information *)((u8 *)ctx->attr + le16_to_cpu(ctx->attr->data.resident.value_offset)); - vol->vol_flags = vi->flags = flags; + vol->vol_flags = vi->flags = cpu_to_le16(flags); mark_mft_record_dirty(ctx->ntfs_ino); ntfs_attr_put_search_ctx(ctx); done: @@ -414,29 +531,56 @@ put_unm_err_out: * @flags: flags to set on the volume * * Set the bits in @flags in the volume information flags on the volume @vol. + * The bits are combined with the current flag state under the lock in + * ntfs_write_volume_flags(), so concurrent updates are not lost. * * Return 0 on success and -errno on error. */ int ntfs_set_volume_flags(struct ntfs_volume *vol, __le16 flags) { - flags &= VOLUME_FLAGS_MASK; - return ntfs_write_volume_flags(vol, vol->vol_flags | flags); + return ntfs_write_volume_flags(vol, flags, 0, false); } /* - * ntfs_clear_volume_flags - clear bits in the volume information flags - * @vol: ntfs volume on which to modify the flags - * @flags: flags to clear on the volume + * ntfs_sync_volume_dirty_state - persist the dirty bit per the error state + * @vol: ntfs volume whose dirty bit to persist * - * Clear the bits in @flags in the volume information flags on the volume @vol. + * Set VOLUME_IS_DIRTY if errors have been recorded on @vol and clear it + * otherwise, under the $Volume mrec_lock. + * + * The guarantee this provides is eventual, not instantaneous: the runtime + * error paths record NVolErrors() with a lock-free set_bit(), so a + * persistence point that evaluates the flag just before an error is + * recorded can still leave the on-disk bit clean. This is sound because + * NVolErrors() is sticky (nothing clears it for the lifetime of the mount) + * and every persistence point re-derives the on-disk bit from it; the + * last one, ntfs_put_super(), runs after evict_inodes() on a quiesced + * filesystem, so a volume that is read-write at unmount time cannot + * unmount clean. A volume that is already read-only when the error is + * recorded (errors=remount-ro flips the superblock on the first error, + * as does an earlier remount-ro) has no persistence point left and + * keeps whatever on-disk bit it had; that behaviour is unchanged. The + * residual window is a crash between the error and the next + * persistence point. + * + * This is the single point that persists the in-memory error state to disk. + * The runtime error paths only record NVolErrors() because they run under a + * variety of ntfs locks the dirty-bit write cannot be taken under (runlist + * locks, vol->lcnbmp_lock, vol->mftbmp_lock, mrec_locks); a sync of a + * volume with recorded errors, a remount to read-only, or the unmount, + * then persists the flag here. + * + * A hibernated volume is not written from these persistence paths: + * resuming Windows from a modified image corrupts it, so the dirty bit + * is left as it is on disk and only the in-memory error state is kept. * * Return 0 on success and -errno on error. */ -int ntfs_clear_volume_flags(struct ntfs_volume *vol, __le16 flags) +static int ntfs_sync_volume_dirty_state(struct ntfs_volume *vol) { - flags &= VOLUME_FLAGS_MASK; - flags = vol->vol_flags & cpu_to_le16(~le16_to_cpu(flags)); - return ntfs_write_volume_flags(vol, flags); + if (NVolHibernated(vol)) + return 0; + return ntfs_write_volume_flags(vol, 0, VOLUME_IS_DIRTY, true); } int ntfs_write_volume_label(struct ntfs_volume *vol, char *label) @@ -530,6 +674,8 @@ out: static bool is_boot_sector_ntfs(const struct super_block *sb, const struct ntfs_boot_sector *b, const bool silent) { + u16 sector_size = le16_to_cpu(b->bpb.bytes_per_sector); + /* * Check that checksum == sum of u32 values from b to the checksum * field. If checksum is zero, no checking is done. We will work when @@ -550,8 +696,8 @@ static bool is_boot_sector_ntfs(const struct super_block *sb, if (b->oem_id != magicNTFS) goto not_ntfs; /* Check bytes per sector value is between 256 and 4096. */ - if (le16_to_cpu(b->bpb.bytes_per_sector) < 0x100 || - le16_to_cpu(b->bpb.bytes_per_sector) > 0x1000) + if (sector_size < 0x100 || sector_size > 0x1000 || + !is_power_of_2(sector_size)) goto not_ntfs; /* * Check sectors per cluster value is valid and the cluster size @@ -632,6 +778,60 @@ static char *read_ntfs_boot_sector(struct super_block *sb, return boot_sector; } +static bool ntfs_validate_mft_io_geometry(struct ntfs_volume *vol, + const unsigned int logical_block_size) +{ + struct super_block *sb = vol->sb; + u32 mft_io_unit_size = 0; + bool mft_write_supported = true; + + if (!is_power_of_2(logical_block_size) || + logical_block_size > PAGE_SIZE || + !is_power_of_2(vol->sector_size) || + vol->sector_size < logical_block_size || + vol->sector_size % logical_block_size || + !is_power_of_2(vol->cluster_size) || + !is_power_of_2(vol->mft_record_size) || + vol->mft_record_size > PAGE_SIZE) + goto err; + + mft_io_unit_size = max(logical_block_size, vol->mft_record_size); + if (mft_io_unit_size > PAGE_SIZE || + mft_io_unit_size % logical_block_size || + mft_io_unit_size % vol->mft_record_size) + goto err; + + /* + * The current direct-write path stores at most two MFT runlist + * segments. Keep valid but larger records read-only until that path + * can map an arbitrary number of segments. + */ + if (vol->mft_record_size > 2 * (u64)vol->cluster_size) + mft_write_supported = false; + + /* + * A containing device block is currently mapped through one MFT + * runlist element. Keep valid geometries that require crossing a + * cluster read-only until the mapping is generalized. + */ + if (mft_io_unit_size > vol->mft_record_size && + (vol->cluster_size < mft_io_unit_size || + vol->cluster_size % mft_io_unit_size)) + mft_write_supported = false; + + vol->mft_io_unit_size = mft_io_unit_size; + vol->mft_write_supported = mft_write_supported; + return true; + +err: + ntfs_error(sb, + "Unsupported MFT I/O geometry (logical %u, block %lu, sector %u, cluster %u, MFT record %u, I/O unit %u).", + logical_block_size, sb->s_blocksize, + (unsigned int)vol->sector_size, vol->cluster_size, + vol->mft_record_size, mft_io_unit_size); + return false; +} + /* * parse_ntfs_boot_sector - parse the boot sector and store the data in @vol * @vol: volume structure to initialise with data from boot sector @@ -644,9 +844,11 @@ static bool parse_ntfs_boot_sector(struct ntfs_volume *vol, const struct ntfs_boot_sector *b) { unsigned int sectors_per_cluster, sectors_per_cluster_bits, nr_hidden_sects; + unsigned int logical_block_size; int clusters_per_mft_record, clusters_per_index_record; u64 ll; + logical_block_size = bdev_logical_block_size(vol->sb->s_bdev); vol->sector_size = le16_to_cpu(b->bpb.bytes_per_sector); vol->sector_size_bits = ffs(vol->sector_size) - 1; ntfs_debug("vol->sector_size = %i (0x%x)", vol->sector_size, @@ -719,6 +921,9 @@ static bool parse_ntfs_boot_sector(struct ntfs_volume *vol, ntfs_warning(vol->sb, "Mft record size (%i) is smaller than the sector size (%i).", vol->mft_record_size, vol->sector_size); } + if (!ntfs_validate_mft_io_geometry(vol, logical_block_size)) + return false; + clusters_per_index_record = b->clusters_per_index_record; ntfs_debug("clusters_per_index_record = %i (0x%x)", clusters_per_index_record, clusters_per_index_record); @@ -792,6 +997,8 @@ static bool parse_ntfs_boot_sector(struct ntfs_volume *vol, vol->mft_record_size_bits; ntfs_debug("vol->mftmirr_size = %i", vol->mftmirr_size); vol->serial_no = le64_to_cpu(b->volume_serial_number); + super_set_uuid(vol->sb, (const u8 *)&b->volume_serial_number, + sizeof(b->volume_serial_number)); ntfs_debug("vol->serial_no = 0x%llx", vol->serial_no); vol->sparse_compression_unit = 4; @@ -959,7 +1166,7 @@ static bool check_mft_mirror(struct ntfs_volume *vol) struct super_block *sb = vol->sb; struct ntfs_inode *mirr_ni; struct folio *mft_folio = NULL, *mirr_folio = NULL; - u8 *kmft = NULL, *kmirr = NULL; + u8 *kmft = NULL, *kmirr = NULL, *kmft_base = NULL, *kmirr_base = NULL; struct runlist_element *rl, rl2[2]; pgoff_t index; int mrecs_per_page, i; @@ -974,9 +1181,9 @@ static bool check_mft_mirror(struct ntfs_volume *vol) /* Switch pages if necessary. */ if (!(i % mrecs_per_page)) { if (index) { - kunmap_local(kmirr); + kunmap_local(kmirr_base); folio_put(mirr_folio); - kunmap_local(kmft); + kunmap_local(kmft_base); folio_put(mft_folio); } /* Get the $MFT page. */ @@ -986,7 +1193,8 @@ static bool check_mft_mirror(struct ntfs_volume *vol) ntfs_error(sb, "Failed to read $MFT."); return false; } - kmft = kmap_local_folio(mft_folio, 0); + kmft_base = kmap_local_folio(mft_folio, 0); + kmft = kmft_base; /* Get the $MFTMirr page. */ mirr_folio = read_mapping_folio(vol->mftmirr_ino->i_mapping, index, NULL); @@ -994,7 +1202,8 @@ static bool check_mft_mirror(struct ntfs_volume *vol) ntfs_error(sb, "Failed to read $MFTMirr."); goto mft_unmap_out; } - kmirr = kmap_local_folio(mirr_folio, 0); + kmirr_base = kmap_local_folio(mirr_folio, 0); + kmirr = kmirr_base; ++index; } @@ -1006,10 +1215,10 @@ static bool check_mft_mirror(struct ntfs_volume *vol) "Incomplete multi sector transfer detected in mft record %i.", i); mm_unmap_out: - kunmap_local(kmirr); + kunmap_local(kmirr_base); folio_put(mirr_folio); mft_unmap_out: - kunmap_local(kmft); + kunmap_local(kmft_base); folio_put(mft_folio); return false; } @@ -1045,9 +1254,9 @@ mft_unmap_out: kmirr += vol->mft_record_size; } while (++i < vol->mftmirr_size); /* Release the last folios. */ - kunmap_local(kmirr); + kunmap_local(kmirr_base); folio_put(mirr_folio); - kunmap_local(kmft); + kunmap_local(kmft_base); folio_put(mft_folio); /* Construct the mft mirror runlist by hand. */ @@ -1235,11 +1444,8 @@ static bool load_and_init_attrdef(struct ntfs_volume *vol) ntfs_debug("Entering."); /* Read attrdef table and setup vol->attrdef and vol->attrdef_size. */ ino = ntfs_iget(sb, FILE_AttrDef); - if (IS_ERR(ino)) { - if (!IS_ERR(ino)) - iput(ino); + if (IS_ERR(ino)) goto failed; - } NInoSetSparseDisabled(NTFS_I(ino)); /* FILE_AttrDef must hold at least one entry and fit inside 31 bits. */ i_size = i_size_read(ino); @@ -1301,11 +1507,8 @@ static bool load_and_init_upcase(struct ntfs_volume *vol) ntfs_debug("Entering."); /* Read upcase table and setup vol->upcase and vol->upcase_len. */ ino = ntfs_iget(sb, FILE_UpCase); - if (IS_ERR(ino)) { - if (!IS_ERR(ino)) - iput(ino); + if (IS_ERR(ino)) goto upcase_failed; - } /* * The upcase size must not be above 64k Unicode characters, must not * be zero and must be a multiple of sizeof(__le16). @@ -1379,6 +1582,125 @@ upcase_failed: return false; } +static int ntfs_validate_badclus(struct ntfs_volume *vol, + const struct runlist *runlist, s64 *bad_clusters) +{ + const struct runlist_element *rl = runlist->rl; + s64 next_vcn = 0, bad = 0; + size_t i; + + if (!rl) + return -EIO; + + for (i = 0; i < runlist->count; i++) { + if (!rl[i].length) { + if (i != runlist->count - 1 || + rl[i].vcn != vol->nr_clusters || + rl[i].lcn != LCN_ENOENT || + next_vcn != vol->nr_clusters) + return -EIO; + *bad_clusters = bad; + return 0; + } + if (rl[i].vcn != next_vcn || rl[i].length < 0 || + rl[i].length > vol->nr_clusters - next_vcn) + return -EIO; + if (rl[i].lcn != LCN_HOLE) { + /* Each bad cluster is mapped to its own volume offset. */ + if (rl[i].lcn != rl[i].vcn) + return -EIO; + bad += rl[i].length; + } + next_vcn += rl[i].length; + } + return -EIO; +} + +/* + * Read the allocation of $BadClus:$Bad, not its data. initialized_size may + * be zero even when the runlist contains bad clusters. Validate every run + * before repairing $Bitmap, and let the later free-space scan account for + * the reservations without subtracting the bad-cluster count again. + */ +static int load_and_check_badclus(struct ntfs_volume *vol) +{ + static __le16 bad_name[] = { + cpu_to_le16('$'), cpu_to_le16('B'), + cpu_to_le16('a'), cpu_to_le16('d'), + }; + struct super_block *sb = vol->sb; + struct inode *base_ino, *bad_ino; + struct ntfs_inode *ni; + s64 free_bits = 0; + u64 size = ntfs_cluster_to_bytes(vol, vol->nr_clusters); + size_t i; + int err; + + base_ino = ntfs_iget(sb, FILE_BadClus); + if (IS_ERR(base_ino)) + return PTR_ERR(base_ino); + if (!S_ISREG(base_ino->i_mode)) { + err = -EIO; + goto iput_base; + } + bad_ino = ntfs_attr_iget(base_ino, AT_DATA, bad_name, + ARRAY_SIZE(bad_name)); + if (IS_ERR(bad_ino)) { + err = PTR_ERR(bad_ino); + goto iput_base; + } + ni = NTFS_I(bad_ino); + if (!NInoNonResident(ni) || NInoCompressed(ni) || NInoEncrypted(ni) || + ni->data_size != size || ni->allocated_size != size) { + err = -EIO; + goto iput_bad; + } + + down_write(&ni->runlist.lock); + err = ntfs_attr_map_whole_runlist(ni); + if (err) + goto unlock; + err = ntfs_validate_badclus(vol, &ni->runlist, &vol->bad_clusters); + if (err) + goto unlock; + for (i = 0; i < ni->runlist.count - 1; i++) { + struct runlist_element *rl = &ni->runlist.rl[i]; + + if (rl->lcn == LCN_HOLE) + continue; + err = ntfs_bitmap_check_used(vol, rl->lcn, rl->length, + !sb_rdonly(sb), &free_bits); + if (err) + break; + } +unlock: + up_write(&ni->runlist.lock); +iput_bad: + iput(bad_ino); +iput_base: + iput(base_ino); + if (err) + return err; + if (free_bits) { + /* A read-only mount must not later remount RW without repair. */ + if (sb_rdonly(sb)) + return -EIO; + err = filemap_write_and_wait(vol->lcnbmp_ino->i_mapping); + if (err) + return err; + err = blkdev_issue_flush(sb->s_bdev); + if (err) + return err; + ntfs_warning(sb, + "Marked %lld bad clusters as used in $Bitmap.", + free_bits); + } + if (vol->bad_clusters) + pr_info("volume contains %lld bad clusters, dev %s\n", + vol->bad_clusters, sb->s_id); + return 0; +} + /* * The lcn and mft bitmap inodes are NTFS-internal inodes with * their own special locking rules: @@ -1394,16 +1716,17 @@ static struct lock_class_key * Open the system files with normal access functions and complete setting up * the ntfs super block @vol. * - * Return 'true' on success or 'false' on error. + * Return 0 on success and -errno on error. */ -static bool load_system_files(struct ntfs_volume *vol) +static int load_system_files(struct ntfs_volume *vol) { struct super_block *sb = vol->sb; struct mft_record *m; struct volume_information *vi; struct ntfs_attr_search_ctx *ctx; struct restart_page_header *rp; - int err; + int err, ret = -EINVAL; + u8 saved_on_errors; ntfs_debug("Entering."); /* Get mft mirror inode compare the contents of $MFT and $MFTMirr. */ @@ -1468,8 +1791,7 @@ bitmap_failed: */ vol->vol_ino = ntfs_iget(sb, FILE_Volume); if (IS_ERR(vol->vol_ino)) { - if (!IS_ERR(vol->vol_ino)) - iput(vol->vol_ino); + vol->vol_ino = NULL; volume_failed: ntfs_error(sb, "Failed to load $Volume."); goto iput_lcnbmp_err_out; @@ -1478,6 +1800,7 @@ volume_failed: if (IS_ERR(m)) { iput_volume_failed: iput(vol->vol_ino); + vol->vol_ino = NULL; goto volume_failed; } @@ -1578,8 +1901,19 @@ get_ctx_vol_failed: * NVolErrors() without setting the dirty volume flag and mount * read-only. This will prevent read-write remounting and it will also * prevent all writes. + * + * Nested lookup and inode-loading errors must not panic before the + * read-only fallback has run. Temporarily use errors=remount-ro + * instead of errors=panic, preserving any read-only transition even + * if an error is not propagated to the check's return value or + * recorded in NVolErrors(). This is safe during initial mount, + * before the super block is published. */ + saved_on_errors = vol->on_errors; + if (saved_on_errors == ON_ERRORS_PANIC) + vol->on_errors = ON_ERRORS_REMOUNT_RO; err = check_windows_hibernation_status(vol); + vol->on_errors = saved_on_errors; if (unlikely(err)) { static const char *es1a = "Failed to determine if Windows is hibernated"; static const char *es1b = "Windows is hibernated"; @@ -1587,12 +1921,40 @@ get_ctx_vol_failed: const char *es1; es1 = err < 0 ? es1a : es1b; - /* If a read-write mount, convert it to a read-only mount. */ - if (!sb_rdonly(sb) && vol->on_errors == ON_ERRORS_REMOUNT_RO) { - sb->s_flags |= SB_RDONLY; - ntfs_error(sb, "%s. Mounting read-only%s", es1, es2); - } + /* Hibernation safety takes precedence over the errors= policy. */ + sb->s_flags |= SB_RDONLY; NVolSetErrors(vol); + ntfs_error(sb, "%s. Mounting read-only%s", es1, es2); + + /* + * Remember it for the lifetime of the mount: see + * ntfs_sync_volume_dirty_state(). + */ + NVolSetHibernated(vol); + } else if (!sb_rdonly(sb) && NVolErrors(vol)) { + /* Match the read-write remount restriction for recorded errors. */ + sb->s_flags |= SB_RDONLY; + ntfs_error(sb, + "Errors were recorded during mount. Mounting read-only. Run chkdsk."); + } + + /* Check bad clusters after hibernation checks and before any writes. */ + saved_on_errors = vol->on_errors; + if (saved_on_errors == ON_ERRORS_PANIC) + vol->on_errors = ON_ERRORS_REMOUNT_RO; + err = load_and_check_badclus(vol); + vol->on_errors = saved_on_errors; + if (err == -ENOMEM) { + ret = err; + ntfs_warning(sb, "Not enough memory to load $BadClus."); + goto iput_root_err_out; + } + if (err) { + sb->s_flags |= SB_RDONLY; + NVolSetErrors(vol); + ntfs_error(sb, + "Failed to initialize $BadClus (%d). Mounting read-only. Run fsck.ntfs or chkdsk.", + err); } /* If (still) a read-write mount, empty the logfile. */ @@ -1609,7 +1971,7 @@ get_ctx_vol_failed: } /* If on NTFS versions before 3.0, we are done. */ if (unlikely(vol->major_ver < 3)) - return true; + return 0; /* NTFS 3.0+ specific initialization. */ /* Get the security descriptors inode. */ vol->secure_ino = ntfs_iget(sb, FILE_Secure); @@ -1628,7 +1990,7 @@ get_ctx_vol_failed: ntfs_error(sb, "Failed to load $Extend."); goto iput_sec_err_out; } - return true; + return 0; iput_sec_err_out: iput(vol->secure_ino); @@ -1638,6 +2000,8 @@ iput_logfile_err_out: if (vol->logfile_ino) iput(vol->logfile_ino); iput(vol->vol_ino); + /* Do not leave a stale pointer behind for the rest of the teardown. */ + vol->vol_ino = NULL; iput_lcnbmp_err_out: iput(vol->lcnbmp_ino); iput_attrdef_err_out: @@ -1662,7 +2026,7 @@ iput_mftbmp_err_out: iput(vol->mftbmp_ino); iput_mirr_err_out: iput(vol->mftmirr_ino); - return false; + return ret; } static void ntfs_volume_free(struct ntfs_volume *vol) @@ -1748,28 +2112,21 @@ static void ntfs_put_super(struct super_block *sb) ntfs_commit_inode(vol->mft_ino); /* - * If a read-write mount and no volume errors have occurred, mark the - * volume clean. Also, re-commit all affected inodes. + * If a read-write mount, re-commit all affected inodes once more. + * The dirty state itself is persisted at the end of ntfs_put_super(), + * after the last commits and the final write_inode_now(): those can + * still record errors via __ntfs_write_inode(), and the sync must + * evaluate NVolErrors() with the last setter already run. */ if (!sb_rdonly(sb)) { if (!NVolErrors(vol)) { - if (ntfs_clear_volume_flags(vol, VOLUME_IS_DIRTY)) - ntfs_warning(sb, - "Failed to clear dirty bit in volume information flags. Run chkdsk."); - ntfs_commit_inode(vol->vol_ino); ntfs_commit_inode(vol->root_ino); if (vol->mftmirr_ino) ntfs_commit_inode(vol->mftmirr_ino); ntfs_commit_inode(vol->mft_ino); - } else { - ntfs_warning(sb, - "Volume has errors. Leaving volume marked dirty. Run chkdsk."); } } - iput(vol->vol_ino); - vol->vol_ino = NULL; - /* NTFS 3.0+ specific clean up. */ if (vol->major_ver >= 3) { if (vol->extend_ino) { @@ -1799,8 +2156,6 @@ static void ntfs_put_super(struct super_block *sb) /* Re-commit the mft mirror and mft just in case. */ ntfs_commit_inode(vol->mftmirr_ino); ntfs_commit_inode(vol->mft_ino); - iput(vol->mftmirr_ino); - vol->mftmirr_ino = NULL; } /* * We should have no dirty inodes left, due to @@ -1810,6 +2165,59 @@ static void ntfs_put_super(struct super_block *sb) ntfs_commit_inode(vol->mft_ino); write_inode_now(vol->mft_ino, 1); + /* + * If a read-write mount, persist the error state in the volume flags: + * mark the volume clean if no volume errors have occurred, and make + * sure VOLUME_IS_DIRTY is on disk if any have, so chkdsk runs on the + * next mount. + */ + if (!sb_rdonly(sb)) { + if (ntfs_sync_volume_dirty_state(vol)) { + ntfs_warning(sb, + "Failed to sync dirty bit in volume information flags. Run chkdsk."); + } else { + /* + * __ntfs_write_inode(), not the void + * ntfs_commit_inode() wrapper: the error can only + * be warned about here. The mirror inode is only + * released below: writing the $Volume record (mft + * record number 3, below vol->mftmirr_size) mirrors + * it through ntfs_sync_mft_mirror(), which fails + * with -EIO once vol->mftmirr_ino is gone. + */ + if (__ntfs_write_inode(vol->vol_ino, 1)) { + ntfs_warning(sb, + "Failed to commit volume information flags. Run chkdsk."); + } else if (NInoDirty(NTFS_I(vol->vol_ino))) { + ntfs_warning(sb, + "Volume information flags remain dirty after commit. Run chkdsk."); + } else if (NVolErrors(vol)) { + /* + * Only warn once the commit has succeeded, + * or this could contradict a failure + * reported above. + */ + ntfs_warning(sb, + "Volume has errors. Leaving volume marked dirty. Run chkdsk."); + } + } + } + + /* + * Release $Volume while the mft inode is still available: if the + * commit above failed before it could clear the dirty flag, + * ntfs_evict_big_inode() commits the inode again on its way out, + * and __ntfs_write_inode() needs vol->mft_ino to look up the + * runlist of the record to write. + */ + iput(vol->vol_ino); + vol->vol_ino = NULL; + + if (vol->mftmirr_ino) { + iput(vol->mftmirr_ino); + vol->mftmirr_ino = NULL; + } + iput(vol->mft_ino); vol->mft_ino = NULL; blkdev_issue_flush(sb->s_bdev); @@ -1853,7 +2261,7 @@ static void ntfs_shutdown(struct super_block *sb) static int ntfs_sync_fs(struct super_block *sb, int wait) { struct ntfs_volume *vol = NTFS_SB(sb); - int err = 0; + int ret, err = 0; if (NVolShutdown(vol)) return -EIO; @@ -1861,15 +2269,30 @@ static int ntfs_sync_fs(struct super_block *sb, int wait) if (!wait) return 0; - /* If there are some dirty buffers in the bdev inode */ - if (!NVolErrors(vol) && - ntfs_clear_volume_flags(vol, VOLUME_IS_DIRTY)) { - ntfs_warning(sb, "Failed to clear dirty bit in volume information flags. Run chkdsk."); + /* + * The volume dirty bit is deliberately not cleared here: a sync + * running concurrently with an in-flight modification could clear + * and persist a bit that was just set, leaving the modification + * on a volume that is clean on disk. The bit is only cleared at + * the quiescent state transitions, remounting read-only and clean + * unmount. A recorded error state, however, is persisted right + * away so that it is not lost to a crash on a volume that has + * seen no modification; with NVolErrors() set this can only set + * the bit, never clear it. + */ + if (NVolErrors(vol) && + ntfs_sync_volume_dirty_state(vol)) { + ntfs_warning(sb, "Failed to sync dirty bit in volume information flags. Run chkdsk."); err = -EIO; } sync_inodes_sb(sb); - sync_blockdev(sb->s_bdev); - blkdev_issue_flush(sb->s_bdev); + ret = sync_blockdev(sb->s_bdev); + if (ret && !err) + err = ret; + + ret = blkdev_issue_flush(sb->s_bdev); + if (ret && !err) + err = ret; return err; } @@ -2209,7 +2632,7 @@ static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc) { char *boot; struct inode *tmp_ino; - int blocksize, result; + int blocksize, result, err = -EINVAL; pgoff_t lcn_bit_pages; struct ntfs_volume *vol = NTFS_SB(sb); int silent = fc->sb_flags & SB_SILENT; @@ -2290,6 +2713,11 @@ static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc) ntfs_error(sb, "Unsupported NTFS filesystem."); goto err_out_now; } + if (!vol->mft_write_supported && !sb_rdonly(sb)) { + sb->s_flags |= SB_RDONLY; + ntfs_warning(sb, + "MFT writeback is unsupported for this geometry. Mounting read-only."); + } if (vol->sector_size > blocksize) { blocksize = sb_set_blocksize(sb, vol->sector_size); @@ -2330,6 +2758,8 @@ static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc) * operations and associated address space operations to function. */ sb->s_op = &ntfs_sops; + if (NVolStreamsWindows(vol)) + ntfs_set_default_dentry_ops(sb); tmp_ino = new_inode(sb); if (!tmp_ino) { if (!silent) @@ -2376,8 +2806,10 @@ static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc) * Open the system files with normal access functions and complete * setting up the ntfs super block. */ - if (!load_system_files(vol)) { - ntfs_error(sb, "Failed to load system files."); + result = load_system_files(vol); + if (result) { + err = result; + ntfs_warning(sb, "Failed to load system files (%d).", err); goto unl_upcase_iput_tmp_ino_err_out_now; } @@ -2486,9 +2918,9 @@ err_out_now: kfree(vol->volume_label); unload_nls(vol->nls_map); kfree(vol); - ntfs_debug("Failed, returning -EINVAL."); + ntfs_debug("Failed, returning %d.", err); lockdep_on(); - return -EINVAL; + return err; } /* @@ -2613,6 +3045,13 @@ static int __init init_ntfs_fs(void) return err; } + err = ntfs_mft_bioset_init(); + if (err) { + pr_crit("Failed to initialize NTFS MFT bioset!\n"); + ntfs_workqueue_destroy(); + return err; + } + ntfs_index_ctx_cache = kmem_cache_create(ntfs_index_ctx_cache_name, sizeof(struct ntfs_index_context), 0 /* offset */, SLAB_HWCACHE_ALIGN, NULL /* ctor */); @@ -2680,6 +3119,7 @@ name_err_out: actx_err_out: kmem_cache_destroy(ntfs_index_ctx_cache); ictx_err_out: + ntfs_mft_bioset_exit(); if (!err) { pr_crit("Aborting NTFS filesystem driver registration...\n"); err = -ENOMEM; @@ -2698,6 +3138,7 @@ static void __exit exit_ntfs_fs(void) * destroy cache. */ rcu_barrier(); + ntfs_mft_bioset_exit(); #ifdef CONFIG_NTFS_FS_WOF_COMPRESSION ntfs_wof_free_workspaces(); #endif diff --git a/fs/ntfs/unistr.c b/fs/ntfs/unistr.c index 7f11a2825527..733bd6fe8599 100644 --- a/fs/ntfs/unistr.c +++ b/fs/ntfs/unistr.c @@ -64,7 +64,8 @@ bool ntfs_are_names_equal(const __le16 *s1, size_t s1_len, * @name1_len: first Unicode name length * @name2: second Unicode name to compare * @name2_len: second Unicode name length - * @err_val: if @name1 contains an invalid character return this value + * @check_invalid: if true and @name1 contains an invalid character, + * return -EINVAL * @ic: either CASE_SENSITIVE or IGNORE_CASE * @upcase: upcase table (ignored if @ic is CASE_SENSITIVE) * @upcase_len: upcase table size (ignored if @ic is CASE_SENSITIVE) @@ -74,13 +75,14 @@ bool ntfs_are_names_equal(const __le16 *s1, size_t s1_len, * -1 if the first name collates before the second one, * 0 if the names match, * 1 if the second name collates before the first one, or - * @err_val if an invalid character is found in @name1 during the comparison. + * -EINVAL if @check_invalid is true and an invalid character is found in + * @name1 during the comparison. * * The following characters are considered invalid: '"', '*', '<', '>' and '?'. */ int ntfs_collate_names(const __le16 *name1, const u32 name1_len, const __le16 *name2, const u32 name2_len, - const int err_val, const u32 ic, + const bool check_invalid, const u32 ic, const __le16 *upcase, const u32 upcase_len) { u32 cnt, min_len; @@ -98,8 +100,8 @@ int ntfs_collate_names(const __le16 *name1, const u32 name1_len, if (c2 < upcase_len) c2 = le16_to_cpu(upcase[c2]); } - if (c1 < 64 && legal_ansi_char_array[c1] & 8) - return err_val; + if (check_invalid && c1 < 64 && legal_ansi_char_array[c1] & 8) + return -EINVAL; if (c1 < c2) return -1; if (c1 > c2) @@ -111,8 +113,8 @@ int ntfs_collate_names(const __le16 *name1, const u32 name1_len, return 0; /* name1_len > name2_len */ c1 = le16_to_cpu(*name1); - if (c1 < 64 && legal_ansi_char_array[c1] & 8) - return err_val; + if (check_invalid && c1 < 64 && legal_ansi_char_array[c1] & 8) + return -EINVAL; return 1; } @@ -191,14 +193,23 @@ int ntfs_ucsncasecmp(const __le16 *s1, const __le16 *s2, size_t n, int ntfs_file_compare_values(const struct file_name_attr *file_name_attr1, const struct file_name_attr *file_name_attr2, - const int err_val, const u32 ic, + const bool check_invalid, const u32 ic, const __le16 *upcase, const u32 upcase_len) { + bool compare_check = check_invalid; + + /* + * POSIX file names may contain characters that are invalid in the + * Windows namespace, so compare them without treating them as errors. + */ + if (file_name_attr1->file_name_type == FILE_NAME_POSIX) + compare_check = false; + return ntfs_collate_names((__le16 *)&file_name_attr1->file_name, file_name_attr1->file_name_length, (__le16 *)&file_name_attr2->file_name, file_name_attr2->file_name_length, - err_val, ic, upcase, upcase_len); + compare_check, ic, upcase, upcase_len); } /* diff --git a/fs/ntfs/volume.h b/fs/ntfs/volume.h index bc85a9592245..ed5c0ccb9cd9 100644 --- a/fs/ntfs/volume.h +++ b/fs/ntfs/volume.h @@ -43,6 +43,7 @@ * @mft_record_size: in bytes * @mft_record_size_mask: mft_record_size - 1 * @mft_record_size_bits: log2(mft_record_size) + * @mft_write_supported: Whether the MFT write paths support the geometry. * @index_record_size: in bytes * @index_record_size_mask: index_record_size - 1 * @index_record_size_bits: log2(index_record_size) @@ -85,6 +86,7 @@ * @nls_utf8: NLS table for UTF-8. * @free_waitq: Wait queue for threads waiting for free clusters or MFT records. * @free_clusters: Track the number of free clusters. + * @bad_clusters: Number of clusters recorded in $BadClus:$Bad. * @free_mft_records: Track the free mft records. * @dirty_clusters: Number of clusters that are dirty. * @sparse_compression_unit: Size of compression/sparse unit in clusters. @@ -111,10 +113,18 @@ struct ntfs_volume { u32 mft_record_size; u32 mft_record_size_mask; u8 mft_record_size_bits; + bool mft_write_supported; + /* + * Unit size used for MFT I/O. This is the MFT record size when + * it is at least as large as the device logical block, or the + * containing device logical block when the record is smaller. + */ + u32 mft_io_unit_size; u32 index_record_size; u32 index_record_size_mask; u8 index_record_size_bits; s64 nr_clusters; + s64 bad_clusters; s64 mft_lcn; s64 mftmirr_lcn; u64 serial_no; @@ -181,9 +191,13 @@ struct ntfs_volume { * Windows-reserved names (CON, AUX, NUL, COM1, * LPT1, etc.) or invalid characters. * + * NV_Hibernated Windows is hibernated on the volume; the sync + * paths must not write the volume flags. * NV_Discard Issue discard/TRIM commands for freed clusters. * NV_DisableSparse Disable creation of sparse regions. * NV_NativeSymlinkRel Translate absolute Windows reparse targets (native_symlink=rel). + * NV_MftBootstrap Mount is still assembling $MFT's own runlist. + * NV_StreamsWindows Interpret the final path component as file:stream. */ enum { NV_Errors, @@ -199,10 +213,13 @@ enum { NV_ShowHiddenFiles, NV_HideDotFiles, NV_CheckWindowsNames, + NV_Hibernated, NV_Discard, NV_DisableSparse, NV_NativeSymlinkRel, NV_SymlinkNative, + NV_MftBootstrap, + NV_StreamsWindows, }; /* @@ -237,10 +254,13 @@ DEFINE_NVOL_BIT_OPS(SysImmutable) DEFINE_NVOL_BIT_OPS(ShowHiddenFiles) DEFINE_NVOL_BIT_OPS(HideDotFiles) DEFINE_NVOL_BIT_OPS(CheckWindowsNames) +DEFINE_NVOL_BIT_OPS(Hibernated) DEFINE_NVOL_BIT_OPS(Discard) DEFINE_NVOL_BIT_OPS(DisableSparse) DEFINE_NVOL_BIT_OPS(NativeSymlinkRel) DEFINE_NVOL_BIT_OPS(SymlinkNative) +DEFINE_NVOL_BIT_OPS(MftBootstrap) +DEFINE_NVOL_BIT_OPS(StreamsWindows) static inline void ntfs_inc_free_clusters(struct ntfs_volume *vol, s64 nr) { diff --git a/fs/ntfs/wof.c b/fs/ntfs/wof.c index 9847259e5b1a..294dbe5d711f 100644 --- a/fs/ntfs/wof.c +++ b/fs/ntfs/wof.c @@ -311,7 +311,8 @@ static int parse_wof_chunk_table(struct ntfs_inode *base_ni, goto out_unlock_mrec; } ret = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, - CASE_SENSITIVE, 0, NULL, 0, ctx); + CASE_SENSITIVE, + 0, NULL, 0, ctx); if (ret) goto out_put_ctx; @@ -399,7 +400,8 @@ static int ntfs_read_wof_chunk(struct ntfs_volume *vol, } err = ntfs_attr_lookup(wof_ni->type, wof_ni->name, wof_ni->name_len, - CASE_SENSITIVE, 0, NULL, 0, ctx); + CASE_SENSITIVE, + 0, NULL, 0, ctx); if (err) goto out_put_ctx; diff --git a/include/uapi/linux/ntfs.h b/include/uapi/linux/ntfs.h new file mode 100644 index 000000000000..89467ba07262 --- /dev/null +++ b/include/uapi/linux/ntfs.h @@ -0,0 +1,130 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Copyright (c) 2026 LG Electronics Co., Ltd. + */ + +#ifndef _UAPI_LINUX_NTFS_H +#define _UAPI_LINUX_NTFS_H +#include <linux/types.h> +#include <linux/ioctl.h> + +#define NTFS_IOC_MAGIC 0xEF + +/* + * Flags for ntfs_stream.flags and ntfs_list_streams.flags. + * + * NTFS_STREAM_FL_UTF16 makes stream names raw UTF-16LE, exactly as stored on + * disk, instead of encoding them with the mounted filesystem NLS. This + * allows lossless round-tripping of stream names whose characters are not + * representable by the mount NLS (e.g. for Wine, which works in UTF-16 + * natively). When set, the name length fields count bytes of UTF-16LE and + * must therefore be even. + */ +#define NTFS_STREAM_FL_UTF16 0x1 + +/* + * ntfs named stream read, write, and remove ioctl structure. + * + * @stream_offset: Offset within the named stream for read/write. + * @io_len: Number of bytes to read/write. Must be zero for remove. + * @bytes_returned: Actual bytes transferred on success (out). + * @name_len: Stream name length in bytes, not including any + * terminating NUL. When NTFS_STREAM_FL_UTF16 is set, + * this counts UTF-16LE bytes and must be even. + * @flags: Bit mask of NTFS_STREAM_FL_* flags. Other bits must + * be zero. + * @reserved: Must be zero. + * @buffer: Bare stream name followed by stream data for read/write. + * + * The stream name is encoded with the mounted filesystem NLS, or as raw + * UTF-16LE when NTFS_STREAM_FL_UTF16 is set. A write creates the stream if + * it does not already exist. A write failure after creation may leave the + * new stream behind; it can be removed separately. + * + * For NTFS_IOC_STREAM_READ, a successful short read indicates EOF. If a read + * fails, the ioctl returns an error and copies no stream data to userspace. + * + * NTFS_IOC_STREAM_WRITE is not atomic. Write errors may leave partial data + * changes and an extended stream size. On error, bytes_returned is not valid. + * The ioctl does not report the completed byte count. + */ +struct ntfs_stream { + __aligned_u64 stream_offset; + __aligned_u64 io_len; + __aligned_u64 bytes_returned; + __u32 name_len; + __u32 flags; + __aligned_u64 reserved; + __u8 buffer[]; +}; + +/* + * Single stream entry returned by NTFS_IOC_LIST_STREAMS. + * + * @next_entry_off: Byte offset from the start of this entry to the next + * entry, or zero for the last entry. Always a multiple + * of 8. Consumers must use this to advance instead of + * computing the stride themselves. + * @size: Stream data size in bytes. + * @alloc_size: Bytes allocated for the stream (cluster aligned for + * non-resident streams). + * @name_len: Stream name length in bytes, not including a NUL + * terminator (none is stored). Counts UTF-16LE bytes + * when NTFS_STREAM_FL_UTF16 was requested. + * @name_offset: Byte offset from the start of this entry to @name. + * @reserved: Must be zero. + * @name: Bare stream name; NLS encoded, or raw UTF-16LE when + * NTFS_STREAM_FL_UTF16 was requested. + * + * New fixed fields may be added after @reserved and before @name. Consumers + * must use @name_offset to locate the name and @next_entry_off to advance to + * the next entry. + */ +struct ntfs_stream_entry { + __aligned_u64 next_entry_off; + __aligned_u64 size; + __aligned_u64 alloc_size; + __u32 name_len; + __u32 name_offset; + __aligned_u64 reserved; + __u8 name[]; +}; + +/* + * ntfs list streams ioctl structure. + * + * @buffer_size: user buffer size(in). + * @bytes_returned: actual bytes written or required(out). + * @stream_count: number of streams(out). + * @flags: Bit mask of NTFS_STREAM_FL_* flags controlling the + * encoding of the returned names; other bits must be zero. + * @reserved: Must be zero. + * @buffer: ntfs_stream_entry array. + * + * The variable-length entries follow the header in @buffer, each aligned on + * an 8-byte boundary and chained via ntfs_stream_entry.next_entry_off. If + * @buffer_size is too small, no data is copied, @stream_count and + * @bytes_returned report the required values and the ioctl fails with + * -ENOSPC. In NLS mode, names that cannot be represented by the mounted + * character set are omitted; use NTFS_STREAM_FL_UTF16 to list all + * names without conversion loss. + */ +struct ntfs_list_streams { + __aligned_u64 buffer_size; + __aligned_u64 bytes_returned; + __aligned_u64 stream_count; + __u32 flags; + __u32 reserved; + __u8 buffer[]; +}; + +#define NTFS_IOC_STREAM_READ \ + _IOWR(NTFS_IOC_MAGIC, 1, struct ntfs_stream) +#define NTFS_IOC_STREAM_WRITE \ + _IOWR(NTFS_IOC_MAGIC, 2, struct ntfs_stream) +#define NTFS_IOC_STREAM_REMOVE \ + _IOWR(NTFS_IOC_MAGIC, 3, struct ntfs_stream) +#define NTFS_IOC_LIST_STREAMS \ + _IOWR(NTFS_IOC_MAGIC, 4, struct ntfs_list_streams) + +#endif /* _UAPI_LINUX_NTFS_H */ |
