mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-10-26 10:57:38 +08:00
Merge tag 'ntfs-for-7.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/ntfs
Pull ntfs updates from Namjae Jeon:
"New features:
- Implement ioctls to list, read, write, and remove named streams on
files and directories. Provide optional file:stream pathname access
through streams_interface=windows, exposing streams as regular file
descriptors with support for mmap, locking, truncation, and
allocation.
- Support file attribute queries and updates through FS_IOC_GETFLAGS
and FS_IOC_SETFLAGS. Store immutable, append-only, nodump, and
noatime settings in the private $LINUXFLAGS extended attribute and
restore them when loading inodes. Also report compression,
encryption, and the mount-wide case-folding policy as read-only
attributes.
Other changes:
- Expose the existing NTFS volume serial number through FS_IOC_GETFSUUID
by registering it as an eight-byte filesystem identifier.
- Validate MFT I/O geometry and write MFT records and mirror data
through mapped I/O units, keeping neighboring records intact when
multiple records share a native 4Kn block. Track folio writeback
until all BIOs complete, serialize raw record updates against
writeback, and use the caller's requested completion mode.
- Discard stale uninitialized cache data before extending initialized
size, ensure shared mmap writes update initialized size after
native 4Kn size extension, and synchronize attribute inode sizes
after internal resizes. Keep VFS and NTFS sizes consistent when
write-error rollback fails and record the volume error.
- Wait for existing folio writeback before updating attribute data,
require page alignment for full-page write optimizations, and
include the final partial cluster in synchronous writes. Skip
duplicate VFS page-cache truncation under the MFT record lock to
avoid a truncate and writeback deadlock.
- Restart allocation zone searches after failed hints and prevent
contiguous allocation requests from returning additional disjoint
runs that callers leave uninitialized. Allow fallocate after
MFT-zone exhaustion and interrupt allocation loops only for fatal
signals, avoiding spurious interruptions from io_uring task-work
notifications.
- Wait for the initial bitmap scan before extending the MFT bitmap,
bound direct allocation candidates by the volume size, update
free-space accounting, and undo only newly allocated clusters after
run coalescing. Update attribute-list inode size before reserving
additional space.
- Hold runlist locks while expanding non-resident attributes, balance
locks during MFT extension error handling, and avoid pointers
invalidated by runlist reallocation. Keep source runlists available
for rollback and allocate enough room for runlist markers and
terminators.
- Return errors for unmappable runlist fragments instead of treating
them as holes or delayed allocation. Check non-resident attribute
sizes against their allocation and fail mounts when $MFT needs
extent records outside the decoded runlist to describe itself,
preventing silent zero reads and mount hangs.
- Load and validate $BadClus:$Bad at mount time and reserve known bad
clusters before the free-space scan. Fall back to read-only
operation when bad-cluster metadata cannot be validated and prevent
subsequent read-write remounts.
- Serialize volume flag updates and retain the dirty bit after
metadata changes while the volume remains mounted read-write.
Persist recorded errors after final metadata commits, fail ordinary
read-only remounts on sync, commit, or flush errors, and retain the
dirty bit on forced remounts.
- Flush the device write cache when syncing directories and report
flush failures from both file and directory fsync. Drain in-flight
direct I/O before buffered write fallback and keep nonblocking
requests from entering the blocking fallback path.
- Mount hibernated volumes, or volumes whose hibernation state cannot
be determined, read-only regardless of the errors= policy. Prevent
mount-time and dirty-state updates from modifying hibernated
volumes.
- Validate complete EA chains, link zero-terminated tails when
appending entries, retain the EA presence flag, and distinguish
empty values from removal. Propagate lookup, write, and truncate
errors, restore both EA attributes after failed updates, and shut
down the volume if restoration fails.
- Restore the previous ACL xattr when a subsequent WSL mode update
fails, including replacement and removal cases. Record rollback
failures so an unsuccessful permission change cannot silently leave
inconsistent ACL and mode state.
- Apply MST fixups to individual index blocks on large-page systems
and use fixed 512-byte VCN units when an index block is smaller
than its cluster. Support lookup and removal of POSIX names
containing Windows-invalid characters and identify directory
symlinks and junctions during enumeration.
- Report the stored creation time for symbolic links through statx()
when querying the link itself with AT_SYMLINK_NOFOLLOW.
- Use wider decompression offsets to avoid overflow on 64 KB pages,
accept the standard sparse compression unit, and require the
compressed-size field only on the first attribute extent. Block
direct reads and skip readahead for encrypted files to prevent
returning raw ciphertext.
- Validate index entry offsets, logfile update sequence array counts,
attribute-list sizes, and index collation key lengths to prevent
out-of-bounds accesses, NULL pointer dereferences, and excessive
allocations from corrupt volume metadata.
- Derive FITRIM bitmap indexes from the page size and retain the full
64-bit minimum discard length. Balance highmem mappings in logfile
and MFT mirror checks and propagate specific errors from extent,
attribute, and reparse operations.
- Compare streams_interface with the mounted volume's setting during
remount, make named-stream UAPI padding explicit, document stream
access and Linux file attributes, and add the NTFS UAPI header to
MAINTAINERS"
* tag 'ntfs-for-7.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/ntfs: (93 commits)
ntfs: handle a failed size rollback in the write error path
ntfs: fix u16 truncation of the update sequence array count check
ntfs: reject index blocks with a too small entries offset
ntfs: support persistent noatime attribute
ntfs: roll back the ACL xattr commit when the WSL mode update fails
ntfs: report birth time for symbolic links
ntfs: support FS_IOC_GETFSUUID
ntfs: avoid unnecessary padding in uapi structure
ntfs: flush the block device when syncing a directory
ntfs: report block device flush failures from fsync
ntfs: fix NULL dereference on index allocation open failure
ntfs: compare streams_interface with mounted volume on remount
ntfs: widen the sub-block offsets in ntfs_decompress()
ntfs: reject direct I/O reads of encrypted files
ntfs: do not read ahead encrypted files
ntfs: persist nodump in the $LINUXFLAGS EA
ntfs: persist append-only in the $LINUXFLAGS EA
ntfs: persist immutable in the $LINUXFLAGS EA
ntfs: add fileattr query support
ntfs: restore EA attributes when an update fails
...
This commit is contained in:
commit
12392d40a3
39 files changed
+5170
-1110
No files matched your search
@@ -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.
|
||||
@@ -402,6 +402,7 @@ Code Seq# Include File Comments
|
||||
0xE5 00-3F linux/fuse.h
|
||||
0xEC 00-01 linux/platform_data/cros_ec_chardev.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
|
||||
|
||||
@@ -19827,6 +19827,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>
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+4
-3
@@ -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);
|
||||
|
||||
+220
-108
@@ -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:
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
+13
-9
@@ -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) ?
|
||||
|
||||
+63
-9
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
+4
-4
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -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. */
|
||||
|
||||
+22
-10
@@ -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
|
||||
|
||||
+326
-239
@@ -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;
|
||||
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 > 255)
|
||||
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;
|
||||
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;
|
||||
}
|
||||
if (ea_info_size != sizeof(struct ea_information)) {
|
||||
err = -EIO;
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
size = le32_to_cpu(old_info->ea_query_length);
|
||||
if (size > old_size) {
|
||||
err = -EUCLEAN;
|
||||
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)
|
||||
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;
|
||||
}
|
||||
|
||||
if (ntfs_attr_size_bounds_check(ni->vol, AT_EA,
|
||||
ea_info_qsize + new_ea_size)) {
|
||||
err = -EFBIG;
|
||||
goto out;
|
||||
}
|
||||
offset = size;
|
||||
entry_size = 0;
|
||||
}
|
||||
alloc_new_ea:
|
||||
new_ea_buf = kvzalloc(ea_info_qsize + new_ea_size, GFP_NOFS);
|
||||
if (!new_ea_buf) {
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
if (ea_info_qsize)
|
||||
memcpy(new_ea_buf, ea_buf, ea_info_qsize);
|
||||
kvfree(ea_buf);
|
||||
ea_buf = new_ea_buf;
|
||||
p_ea = (struct ea_attr *)(ea_buf + ea_info_qsize);
|
||||
|
||||
/*
|
||||
* EA and REPARSE_POINT compatibility not checked any more,
|
||||
* required by Windows 10, but having both may lead to
|
||||
* problems with earlier versions.
|
||||
*/
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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)
|
||||
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);
|
||||
}
|
||||
size -= entry_size;
|
||||
|
||||
err = ntfs_write_ea(ni, AT_EA_INFORMATION, (char *)p_ea_info, 0,
|
||||
sizeof(struct ea_information), false);
|
||||
if (err)
|
||||
/* 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 (!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;
|
||||
|
||||
if (packed_ea_size)
|
||||
*packed_ea_size = p_ea_info->ea_length;
|
||||
mark_mft_record_dirty(ni);
|
||||
out:
|
||||
if (!err) {
|
||||
if (ea_info_qsize > 0)
|
||||
NInoSetHasEA(ni);
|
||||
else
|
||||
NInoClearHasEA(ni);
|
||||
}
|
||||
info.ea_length = cpu_to_le16(packed);
|
||||
info.need_ea_count = cpu_to_le16(needed);
|
||||
info.ea_query_length = cpu_to_le32(new_size);
|
||||
|
||||
kvfree(ea_buf);
|
||||
kvfree(old_ea_buf);
|
||||
kvfree(p_ea_info);
|
||||
|
||||
/* 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;
|
||||
}
|
||||
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:
|
||||
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;
|
||||
|
||||
@@ -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
|
||||
|
||||
+430
-74
@@ -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)))
|
||||
@@ -136,6 +145,131 @@ static int ntfs_file_release(struct inode *vi, struct file *filp)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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(const 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
|
||||
@@ -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;
|
||||
@@ -657,15 +934,53 @@ static ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
|
||||
&ntfs_iomap_folio_ops, NULL);
|
||||
out:
|
||||
if (ret < 0 && ret != -EIOCBQUEUED) {
|
||||
if (ni->initialized_size != old_init_size) {
|
||||
mutex_lock(&ni->mrec_lock);
|
||||
ntfs_attr_set_initialized_size(ni, old_init_size);
|
||||
mutex_unlock(&ni->mrec_lock);
|
||||
int init_err = 0;
|
||||
int data_err = 0;
|
||||
|
||||
/*
|
||||
* Like ntfs_setattr_size(): __ntfs_attr_truncate_vfs()
|
||||
* expects the invalidate lock held across the size
|
||||
* updates.
|
||||
*/
|
||||
filemap_invalidate_lock(vi->i_mapping);
|
||||
|
||||
/* ntfs_attr_set_initialized_size() rejects a resident one. */
|
||||
if (ni->initialized_size != old_init_size &&
|
||||
NInoNonResident(ni)) {
|
||||
struct ntfs_inode *mrec_ni = ntfs_base_inode(ni);
|
||||
|
||||
mutex_lock(&mrec_ni->mrec_lock);
|
||||
init_err = ntfs_attr_set_initialized_size(ni,
|
||||
old_init_size);
|
||||
mutex_unlock(&mrec_ni->mrec_lock);
|
||||
}
|
||||
if (ni->data_size != old_data_size) {
|
||||
/*
|
||||
* The shrink clamps the initialized size to the data size,
|
||||
* which can sit above the old initialized one and expose
|
||||
* the uninitialized tail, so skip it unless the rollback
|
||||
* above restored that size.
|
||||
*/
|
||||
if (ni->data_size != old_data_size && !init_err) {
|
||||
struct ntfs_inode *mrec_ni = ntfs_base_inode(ni);
|
||||
|
||||
truncate_setsize(vi, old_data_size);
|
||||
ntfs_attr_truncate(ni, old_data_size);
|
||||
mutex_lock(&mrec_ni->mrec_lock);
|
||||
data_err = __ntfs_attr_truncate_vfs(ni, old_data_size,
|
||||
old_data_size);
|
||||
mutex_unlock(&mrec_ni->mrec_lock);
|
||||
/* Like ntfs_setattr_size(): stay on the durable size. */
|
||||
if (data_err)
|
||||
i_size_write(vi, ni->data_size);
|
||||
}
|
||||
if (init_err || data_err) {
|
||||
/* The failed sizes stay in the dirty MFT record. */
|
||||
NVolSetErrors(vol);
|
||||
ntfs_error(vi->i_sb,
|
||||
"Failed to roll back the size update: %d",
|
||||
init_err ?: data_err);
|
||||
}
|
||||
|
||||
filemap_invalidate_unlock(vi->i_mapping);
|
||||
}
|
||||
out_lock:
|
||||
inode_unlock(vi);
|
||||
@@ -678,13 +993,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 +1019,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 +1033,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 +1066,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 +1103,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 +1196,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 +1219,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 +1283,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 +1348,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 +1392,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 +1405,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 +1449,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 +1465,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 +1484,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 +1492,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 +1537,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 +1582,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 +1593,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,
|
||||
};
|
||||
|
||||
+25
-4
@@ -489,6 +489,14 @@ static struct index_entry *ntfs_ie_dup_novcn(struct index_entry *ie)
|
||||
return dup;
|
||||
}
|
||||
|
||||
/* Offset of the first entry in an index block created by ntfs_ib_alloc(). */
|
||||
static u32 ntfs_ib_canonical_entries_offset(u32 block_size)
|
||||
{
|
||||
u32 usa_count = block_size / NTFS_BLOCK_SIZE + 1;
|
||||
|
||||
return (sizeof(struct index_header) + usa_count * 2 + 7) & ~7;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check the consistency of an index block
|
||||
*
|
||||
@@ -548,6 +556,16 @@ int ntfs_index_block_inconsistent(struct ntfs_volume *vol,
|
||||
offsetof(struct index_block, index),
|
||||
inum))
|
||||
return -EIO;
|
||||
|
||||
if (le32_to_cpu(ib->index.entries_offset) <
|
||||
ntfs_ib_canonical_entries_offset(block_size)) {
|
||||
ntfs_error(sb,
|
||||
"Index block: entries offset %u is below the minimum for a %u-byte block: vcn %lld inode %llu\n",
|
||||
le32_to_cpu(ib->index.entries_offset), block_size,
|
||||
vcn, inum);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (ntfs_index_entries_inconsistent(vol, &ib->index, cr, inum))
|
||||
return -EIO;
|
||||
return 0;
|
||||
@@ -812,6 +830,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 +873,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 +898,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;
|
||||
}
|
||||
|
||||
@@ -957,8 +978,8 @@ static struct index_block *ntfs_ib_alloc(s64 ib_vcn, u32 ib_size,
|
||||
*(__le16 *)((char *)ib + le16_to_cpu(ib->usa_ofs)) = cpu_to_le16(1);
|
||||
ib->lsn = 0;
|
||||
ib->index_block_vcn = cpu_to_le64(ib_vcn);
|
||||
ib->index.entries_offset = cpu_to_le32((ih_size +
|
||||
le16_to_cpu(ib->usa_count) * 2 + 7) & ~7);
|
||||
ib->index.entries_offset =
|
||||
cpu_to_le32(ntfs_ib_canonical_entries_offset(ib_size));
|
||||
ib->index.index_length = 0;
|
||||
ib->index.allocated_size = cpu_to_le32(ib_size -
|
||||
(sizeof(struct index_block) - ih_size));
|
||||
|
||||
+1
-1
@@ -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.
|
||||
|
||||
+335
-96
@@ -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;
|
||||
}
|
||||
@@ -66,6 +77,33 @@ int ntfs_test_inode(struct inode *vi, void *data)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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
|
||||
@@ -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;
|
||||
@@ -2122,6 +2240,16 @@ int ntfs_read_inode_mount(struct inode *vi)
|
||||
vi->i_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);
|
||||
/*
|
||||
* 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 (!ctx) {
|
||||
ntfs_error(vi->i_sb, "Failed to get attr search ctx");
|
||||
return -ENOMEM;
|
||||
if (ntfs_inode_is_named_stream(ni)) {
|
||||
if (NInoEncrypted(ni))
|
||||
return -EACCES;
|
||||
if (NInoCompressed(ni))
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
ret = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, CASE_SENSITIVE,
|
||||
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");
|
||||
ret = -ENOMEM;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
+51
-27
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+11
-8
@@ -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
|
||||
|
||||
+32
-3
@@ -375,6 +375,16 @@ struct runlist_element *ntfs_cluster_alloc(struct ntfs_volume *vol, const s64 st
|
||||
has_guess = 1;
|
||||
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.
|
||||
@@ -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)
|
||||
|
||||
+7
-6
@@ -27,7 +27,7 @@ static bool ntfs_check_restart_page_header(struct inode *vi,
|
||||
struct restart_page_header *rp, s64 pos)
|
||||
{
|
||||
u32 logfile_system_page_size, logfile_log_page_size;
|
||||
u16 ra_ofs, usa_count, usa_ofs, usa_end = 0;
|
||||
u32 ra_ofs, usa_count, usa_ofs, usa_end = 0;
|
||||
bool have_usa = true;
|
||||
|
||||
ntfs_debug("Entering.");
|
||||
@@ -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) {
|
||||
|
||||
+653
-327
File diff suppressed because it is too large.
Load diff
+11
-8
@@ -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)
|
||||
{
|
||||
|
||||
+2
-2
@@ -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);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+161
-47
@@ -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,
|
||||
|
||||
+8
-3
@@ -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(const 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);
|
||||
|
||||
+1
-1
@@ -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 */
|
||||
|
||||
+81
-22
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -714,6 +735,44 @@ finished:
|
||||
return drl;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(const 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 */
|
||||
+525
-84
@@ -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;
|
||||
@@ -65,6 +68,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,
|
||||
@@ -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).
|
||||
*
|
||||
* 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().
|
||||
* 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.
|
||||
*
|
||||
* 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
|
||||
|
||||
+20
-9
@@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
+4
-2
@@ -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;
|
||||
|
||||
|
||||
@@ -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 */
|
||||
Reference in new issue
Block a user