mirror of
https://github.com/torvalds/linux.git
synced 2026-04-18 06:44:00 -04:00
Merge tag 'ntfs-for-7.1-rc1-v2' of git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/ntfs
Pull ntfs resurrection from Namjae Jeon:
"Ever since Kari Argillander’s 2022 report [1] regarding the state of
the ntfs3 driver, I have spent the last 4 years working to provide
full write support and current trends (iomap, no buffer head, folio),
enhanced performance, stable maintenance, utility support including
fsck for NTFS in Linux.
This new implementation is built upon the clean foundation of the
original read-only NTFS driver, adding:
- Write support:
Implemented full write support based on the classic read-only NTFS
driver. Added delayed allocation to improve write performance
through multi-cluster allocation and reduced fragmentation of the
cluster bitmap.
- iomap conversion:
Switched buffered IO (reads/writes), direct IO, file extent
mapping, readpages, and writepages to use iomap.
- Remove buffer_head:
Completely removed buffer_head usage by converting to folios. As a
result, the dependency on CONFIG_BUFFER_HEAD has been removed from
Kconfig.
- Stability improvements:
The new ntfs driver passes 326 xfstests, compared to 273 for ntfs3.
All tests passed by ntfs3 are a complete subset of the tests passed
by this implementation. Added support for fallocate, idmapped
mounts, permissions, and more.
xfstests Results report:
Total tests run: 787
Passed : 326
Failed : 38
Skipped : 423
Failed tests breakdown:
- 34 tests require metadata journaling
- 4 other tests:
094: No unwritten extent concept in NTFS on-disk format
563: cgroup v2 aware writeback accounting not supported
631: RENAME_WHITEOUT support required
787: NFS delegation test"
Link: https://lore.kernel.org/all/da20d32b-5185-f40b-48b8-2986922d8b25@stargateuniverse.net/ [1]
[ Let's see if this undead filesystem ends up being of the "Easter
miracle" kind, or the "Nosferatu of filesystems" kind... ]
* tag 'ntfs-for-7.1-rc1-v2' of git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/ntfs: (46 commits)
ntfs: remove redundant out-of-bound checks
ntfs: add bound checking to ntfs_external_attr_find
ntfs: add bound checking to ntfs_attr_find
ntfs: fix ignoring unreachable code warnings
ntfs: fix inconsistent indenting warnings
ntfs: fix variable dereferenced before check warnings
ntfs: prefer IS_ERR_OR_NULL() over manual NULL check
ntfs: harden ntfs_listxattr against EA entries
ntfs: harden ntfs_ea_lookup against malformed EA entries
ntfs: check $EA query-length in ntfs_ea_get
ntfs: validate WSL EA payload sizes
ntfs: fix WSL ea restore condition
ntfs: add missing newlines to pr_err() messages
ntfs: fix pointer/integer casting warnings
ntfs: use ->mft_no instead of ->i_ino in prints
ntfs: change mft_no type to u64
ntfs: select FS_IOMAP in Kconfig
ntfs: add MODULE_ALIAS_FS
ntfs: reduce stack usage in ntfs_write_mft_block()
ntfs: fix sysctl table registration and path
...
This commit is contained in:
9
CREDITS
9
CREDITS
@@ -71,11 +71,6 @@ D: dosfs, LILO, some fd features, ATM, various other hacks here and there
|
|||||||
S: Buenos Aires
|
S: Buenos Aires
|
||||||
S: Argentina
|
S: Argentina
|
||||||
|
|
||||||
NTFS FILESYSTEM
|
|
||||||
N: Anton Altaparmakov
|
|
||||||
E: anton@tuxera.com
|
|
||||||
D: NTFS filesystem
|
|
||||||
|
|
||||||
N: Tim Alpaerts
|
N: Tim Alpaerts
|
||||||
E: tim_alpaerts@toyota-motor-europe.com
|
E: tim_alpaerts@toyota-motor-europe.com
|
||||||
D: 802.2 class II logical link control layer,
|
D: 802.2 class II logical link control layer,
|
||||||
@@ -85,8 +80,8 @@ S: B-2610 Wilrijk-Antwerpen
|
|||||||
S: Belgium
|
S: Belgium
|
||||||
|
|
||||||
N: Anton Altaparmakov
|
N: Anton Altaparmakov
|
||||||
E: aia21@cantab.net
|
E: anton@tuxera.com
|
||||||
W: http://www-stu.christs.cam.ac.uk/~aia21/
|
W: http://www.tuxera.com/
|
||||||
D: Author of new NTFS driver, various other kernel hacks.
|
D: Author of new NTFS driver, various other kernel hacks.
|
||||||
S: Christ's College
|
S: Christ's College
|
||||||
S: Cambridge CB2 3BU
|
S: Cambridge CB2 3BU
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ Documentation for filesystem implementations.
|
|||||||
isofs
|
isofs
|
||||||
nilfs2
|
nilfs2
|
||||||
nfs/index
|
nfs/index
|
||||||
|
ntfs
|
||||||
ntfs3
|
ntfs3
|
||||||
ocfs2
|
ocfs2
|
||||||
ocfs2-online-filecheck
|
ocfs2-online-filecheck
|
||||||
|
|||||||
159
Documentation/filesystems/ntfs.rst
Normal file
159
Documentation/filesystems/ntfs.rst
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
.. SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
|
=================================
|
||||||
|
The Linux NTFS filesystem driver
|
||||||
|
=================================
|
||||||
|
|
||||||
|
|
||||||
|
.. Table of contents
|
||||||
|
|
||||||
|
- Overview
|
||||||
|
- Utilities support
|
||||||
|
- Supported mount options
|
||||||
|
|
||||||
|
|
||||||
|
Overview
|
||||||
|
========
|
||||||
|
|
||||||
|
NTFS is a Linux kernel filesystem driver that provides full read and write
|
||||||
|
support for NTFS volumes. It is designed for high performance, modern
|
||||||
|
kernel infrastructure (iomap, folio), and stable long-term maintenance.
|
||||||
|
|
||||||
|
|
||||||
|
Utilities support
|
||||||
|
=================
|
||||||
|
|
||||||
|
The NTFS utilities project, called ntfsprogs-plus, provides mkfs.ntfs,
|
||||||
|
fsck.ntfs, and other related tools (e.g., ntfsinfo, ntfsclone, etc.) for
|
||||||
|
creating, checking, and managing NTFS volumes. These utilities can be used
|
||||||
|
for filesystem testing with xfstests as well as for recovering corrupted
|
||||||
|
NTFS devices.
|
||||||
|
|
||||||
|
The project is available at:
|
||||||
|
|
||||||
|
https://github.com/ntfsprogs-plus/ntfsprogs-plus
|
||||||
|
|
||||||
|
|
||||||
|
Supported mount options
|
||||||
|
=======================
|
||||||
|
|
||||||
|
The NTFS driver supports the following mount options:
|
||||||
|
|
||||||
|
======================= ====================================================
|
||||||
|
iocharset=name Character set to use for converting between
|
||||||
|
the encoding is used for user visible filename and
|
||||||
|
16 bit Unicode characters.
|
||||||
|
|
||||||
|
nls=name Deprecated option. Still supported but please use
|
||||||
|
iocharset=name in the future.
|
||||||
|
|
||||||
|
uid=
|
||||||
|
gid=
|
||||||
|
umask= Provide default owner, group, and access mode mask.
|
||||||
|
These options work as documented in mount(8). By
|
||||||
|
default, the files/directories are owned by root
|
||||||
|
and he/she has read and write permissions, as well
|
||||||
|
as browse permission for directories. No one else
|
||||||
|
has any access permissions. I.e. the mode on all
|
||||||
|
files is by default rw------- and
|
||||||
|
for directories rwx------, a consequence of
|
||||||
|
the default fmask=0177 and dmask=0077.
|
||||||
|
Using a umask of zero will grant all permissions to
|
||||||
|
everyone, i.e. all files and directories will have
|
||||||
|
mode rwxrwxrwx.
|
||||||
|
|
||||||
|
fmask=
|
||||||
|
dmask= Instead of specifying umask which applies both to
|
||||||
|
files and directories, fmask applies only to files
|
||||||
|
and dmask only to directories.
|
||||||
|
|
||||||
|
showmeta=<BOOL>
|
||||||
|
show_sys_files=<BOOL> If show_sys_files is specified, show the system
|
||||||
|
files in directory listings. Otherwise the default
|
||||||
|
behaviour is to hide the system files.
|
||||||
|
Note that even when show_sys_files is specified,
|
||||||
|
"$MFT" will not be visible due to bugs/mis-features
|
||||||
|
in glibc. Further, note that irrespective of
|
||||||
|
show_sys_files, all files are accessible by name,
|
||||||
|
i.e. you can always do "ls -l \$UpCase" for example
|
||||||
|
to specifically show the system file containing
|
||||||
|
the Unicode upcase table.
|
||||||
|
|
||||||
|
case_sensitive=<BOOL> If case_sensitive is specified, treat all filenames
|
||||||
|
as case sensitive and create file names in
|
||||||
|
the POSIX namespace (default behavior). Note,
|
||||||
|
the Linux NTFS driver will never create short
|
||||||
|
filenames and will remove them on rename/delete of
|
||||||
|
the corresponding long file name. Note that files
|
||||||
|
remain accessible via their short file name, if it
|
||||||
|
exists.
|
||||||
|
|
||||||
|
nocase=<BOOL> If nocase is specified, treat filenames
|
||||||
|
case-insensitively.
|
||||||
|
|
||||||
|
disable_sparse=<BOOL> If disable_sparse is specified, creation of sparse
|
||||||
|
regions, i.e. holes, inside files is disabled for
|
||||||
|
the volume (for the duration of this mount only).
|
||||||
|
By default, creation of sparse regions is enabled,
|
||||||
|
which is consistent with the behaviour of
|
||||||
|
traditional Unix filesystems.
|
||||||
|
|
||||||
|
errors=opt Specify NTFS behavior on critical errors: panic,
|
||||||
|
remount the partition in read-only mode or
|
||||||
|
continue without doing anything (default behavior).
|
||||||
|
|
||||||
|
mft_zone_multiplier= Set the MFT zone multiplier for the volume (this
|
||||||
|
setting is not persistent across mounts and can be
|
||||||
|
changed from mount to mount but cannot be changed
|
||||||
|
on remount). Values of 1 to 4 are allowed, 1 being
|
||||||
|
the default. The MFT zone multiplier determines
|
||||||
|
how much space is reserved for the MFT on the
|
||||||
|
volume. If all other space is used up, then the
|
||||||
|
MFT zone will be shrunk dynamically, so this has no
|
||||||
|
impact on the amount of free space. However, it
|
||||||
|
can have an impact on performance by affecting
|
||||||
|
fragmentation of the MFT. In general use the
|
||||||
|
default. If you have a lot of small files then use
|
||||||
|
a higher value. The values have the following
|
||||||
|
meaning:
|
||||||
|
|
||||||
|
===== =================================
|
||||||
|
Value MFT zone size (% of volume size)
|
||||||
|
===== =================================
|
||||||
|
1 12.5%
|
||||||
|
2 25%
|
||||||
|
3 37.5%
|
||||||
|
4 50%
|
||||||
|
===== =================================
|
||||||
|
|
||||||
|
Note this option is irrelevant for read-only mount.
|
||||||
|
|
||||||
|
preallocated_size= Set preallocated size to optimize runlist merge
|
||||||
|
overhead with small chunck size.(64KB size by
|
||||||
|
default)
|
||||||
|
|
||||||
|
acl=<BOOL> Enable POSIX ACL support. When specified, POSIX
|
||||||
|
ACLs stored in extended attributes are enforced.
|
||||||
|
Default is off. Requires kernel config
|
||||||
|
NTFS_FS_POSIX_ACL enabled.
|
||||||
|
|
||||||
|
sys_immutable=<BOOL> Make NTFS system files (e.g. $MFT, $LogFile,
|
||||||
|
$Bitmap, $UpCase, etc.) immutable to user initiated
|
||||||
|
modifications for extra safety. Default is off.
|
||||||
|
|
||||||
|
nohidden=<BOOL> Hide files and directories marked with the Windows
|
||||||
|
"hidden" attribute. By default hidden items are
|
||||||
|
shown.
|
||||||
|
|
||||||
|
hide_dot_files=<BOOL> Hide names beginning with a dot ("."). By default
|
||||||
|
dot files are shown. When enabled, files and
|
||||||
|
directories created with a leading '.' will be
|
||||||
|
hidden from directory listings.
|
||||||
|
|
||||||
|
windows_names=<BOOL> Refuse creation/rename of files with characters or
|
||||||
|
reserved device names disallowed on Windows (e.g.
|
||||||
|
CON, NUL, AUX, COM1, LPT1, etc.). Default is off.
|
||||||
|
discard=<BOOL> Issue block device discard for clusters freed on
|
||||||
|
file deletion/truncation to inform underlying
|
||||||
|
storage.
|
||||||
|
======================= ====================================================
|
||||||
@@ -18997,6 +18997,15 @@ W: https://github.com/davejiang/linux/wiki
|
|||||||
T: git https://github.com/davejiang/linux.git
|
T: git https://github.com/davejiang/linux.git
|
||||||
F: drivers/ntb/hw/intel/
|
F: drivers/ntb/hw/intel/
|
||||||
|
|
||||||
|
NTFS FILESYSTEM
|
||||||
|
M: Namjae Jeon <linkinjeon@kernel.org>
|
||||||
|
M: Hyunchul Lee <hyc.lee@gmail.com>
|
||||||
|
L: linux-fsdevel@vger.kernel.org
|
||||||
|
S: Maintained
|
||||||
|
T: git git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/ntfs.git
|
||||||
|
F: Documentation/filesystems/ntfs.rst
|
||||||
|
F: fs/ntfs/
|
||||||
|
|
||||||
NTFS3 FILESYSTEM
|
NTFS3 FILESYSTEM
|
||||||
M: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
|
M: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
|
||||||
L: ntfs3@lists.linux.dev
|
L: ntfs3@lists.linux.dev
|
||||||
|
|||||||
@@ -152,6 +152,7 @@ menu "DOS/FAT/EXFAT/NT Filesystems"
|
|||||||
|
|
||||||
source "fs/fat/Kconfig"
|
source "fs/fat/Kconfig"
|
||||||
source "fs/exfat/Kconfig"
|
source "fs/exfat/Kconfig"
|
||||||
|
source "fs/ntfs/Kconfig"
|
||||||
source "fs/ntfs3/Kconfig"
|
source "fs/ntfs3/Kconfig"
|
||||||
|
|
||||||
endmenu
|
endmenu
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ obj-$(CONFIG_NLS) += nls/
|
|||||||
obj-y += unicode/
|
obj-y += unicode/
|
||||||
obj-$(CONFIG_SMBFS) += smb/
|
obj-$(CONFIG_SMBFS) += smb/
|
||||||
obj-$(CONFIG_HPFS_FS) += hpfs/
|
obj-$(CONFIG_HPFS_FS) += hpfs/
|
||||||
|
obj-$(CONFIG_NTFS_FS) += ntfs/
|
||||||
obj-$(CONFIG_NTFS3_FS) += ntfs3/
|
obj-$(CONFIG_NTFS3_FS) += ntfs3/
|
||||||
obj-$(CONFIG_UFS_FS) += ufs/
|
obj-$(CONFIG_UFS_FS) += ufs/
|
||||||
obj-$(CONFIG_EFS_FS) += efs/
|
obj-$(CONFIG_EFS_FS) += efs/
|
||||||
|
|||||||
48
fs/ntfs/Kconfig
Normal file
48
fs/ntfs/Kconfig
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
config NTFS_FS
|
||||||
|
tristate "NTFS file system support"
|
||||||
|
select NLS
|
||||||
|
select FS_IOMAP
|
||||||
|
help
|
||||||
|
NTFS is the file system of Microsoft Windows NT, 2000, XP and 2003.
|
||||||
|
This allows you to mount devices formatted with the ntfs file system.
|
||||||
|
|
||||||
|
To compile this as a module, choose M here: the module will be called
|
||||||
|
ntfs.
|
||||||
|
|
||||||
|
config NTFS_DEBUG
|
||||||
|
bool "NTFS debugging support"
|
||||||
|
depends on NTFS_FS
|
||||||
|
help
|
||||||
|
If you are experiencing any problems with the NTFS file system, say
|
||||||
|
Y here. This will result in additional consistency checks to be
|
||||||
|
performed by the driver as well as additional debugging messages to
|
||||||
|
be written to the system log. Note that debugging messages are
|
||||||
|
disabled by default. To enable them, supply the option debug_msgs=1
|
||||||
|
at the kernel command line when booting the kernel or as an option
|
||||||
|
to insmod when loading the ntfs module. Once the driver is active,
|
||||||
|
you can enable debugging messages by doing (as root):
|
||||||
|
echo 1 > /proc/sys/fs/ntfs-debug
|
||||||
|
Replacing the "1" with "0" would disable debug messages.
|
||||||
|
|
||||||
|
If you leave debugging messages disabled, this results in little
|
||||||
|
overhead, but enabling debug messages results in very significant
|
||||||
|
slowdown of the system.
|
||||||
|
|
||||||
|
When reporting bugs, please try to have available a full dump of
|
||||||
|
debugging messages while the misbehaviour was occurring.
|
||||||
|
|
||||||
|
config NTFS_FS_POSIX_ACL
|
||||||
|
bool "NTFS POSIX Access Control Lists"
|
||||||
|
depends on NTFS_FS
|
||||||
|
select FS_POSIX_ACL
|
||||||
|
help
|
||||||
|
POSIX Access Control Lists (ACLs) support additional access rights
|
||||||
|
for users and groups beyond the standard owner/group/world scheme.
|
||||||
|
|
||||||
|
This option enables ACL support for ntfs, providing functional parity
|
||||||
|
with ntfs3 drivier.
|
||||||
|
|
||||||
|
NOTE: this is linux only feature. Windows will ignore these ACLs.
|
||||||
|
|
||||||
|
If you don't know what Access Control Lists are, say N.
|
||||||
10
fs/ntfs/Makefile
Normal file
10
fs/ntfs/Makefile
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
|
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 quota.o object_id.o bdev-io.o
|
||||||
|
|
||||||
|
ccflags-$(CONFIG_NTFS_DEBUG) += -DDEBUG
|
||||||
263
fs/ntfs/aops.c
Normal file
263
fs/ntfs/aops.c
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* NTFS kernel address space operations and page cache handling.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2001-2014 Anton Altaparmakov and Tuxera Inc.
|
||||||
|
* Copyright (c) 2002 Richard Russon
|
||||||
|
* Copyright (c) 2025 LG Electronics Co., Ltd.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/writeback.h>
|
||||||
|
|
||||||
|
#include "attrib.h"
|
||||||
|
#include "mft.h"
|
||||||
|
#include "ntfs.h"
|
||||||
|
#include "debug.h"
|
||||||
|
#include "iomap.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_read_folio - Read data for a folio from the device
|
||||||
|
* @file: open file to which the folio @folio belongs or NULL
|
||||||
|
* @folio: page cache folio to fill with data
|
||||||
|
*
|
||||||
|
* This function handles reading data into the page cache. It first checks
|
||||||
|
* for specific ntfs attribute type like encryption and compression.
|
||||||
|
*
|
||||||
|
* - If the attribute is encrypted, access is denied (-EACCES) because
|
||||||
|
* decryption is not supported in this path.
|
||||||
|
* - If the attribute is non-resident and compressed, the read operation is
|
||||||
|
* delegated to ntfs_read_compressed_block().
|
||||||
|
* - For normal resident or non-resident attribute, it utilizes the generic
|
||||||
|
* iomap infrastructure via iomap_bio_read_folio() to perform the I/O.
|
||||||
|
*
|
||||||
|
* Return: 0 on success, or -errno on error.
|
||||||
|
*/
|
||||||
|
static int ntfs_read_folio(struct file *file, struct folio *folio)
|
||||||
|
{
|
||||||
|
struct ntfs_inode *ni = NTFS_I(folio->mapping->host);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Only $DATA attributes can be encrypted and only unnamed $DATA
|
||||||
|
* attributes can be compressed. Index root can have the flags set but
|
||||||
|
* this means to create compressed/encrypted files, not that the
|
||||||
|
* attribute is compressed/encrypted. Note we need to check for
|
||||||
|
* AT_INDEX_ALLOCATION since this is the type of both directory and
|
||||||
|
* index inodes.
|
||||||
|
*/
|
||||||
|
if (ni->type != AT_INDEX_ALLOCATION) {
|
||||||
|
/*
|
||||||
|
* EFS-encrypted files are not supported.
|
||||||
|
* (decryption/encryption is not implemented yet)
|
||||||
|
*/
|
||||||
|
if (NInoEncrypted(ni)) {
|
||||||
|
folio_unlock(folio);
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
/* Compressed data streams are handled in compress.c. */
|
||||||
|
if (NInoNonResident(ni) && NInoCompressed(ni))
|
||||||
|
return ntfs_read_compressed_block(folio);
|
||||||
|
}
|
||||||
|
|
||||||
|
iomap_bio_read_folio(folio, &ntfs_read_iomap_ops);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_bmap - map logical file block to physical device block
|
||||||
|
* @mapping: address space mapping to which the block to be mapped belongs
|
||||||
|
* @block: logical block to map to its physical device block
|
||||||
|
*
|
||||||
|
* For regular, non-resident files (i.e. not compressed and not encrypted), map
|
||||||
|
* the logical @block belonging to the file described by the address space
|
||||||
|
* mapping @mapping to its physical device block.
|
||||||
|
*
|
||||||
|
* The size of the block is equal to the @s_blocksize field of the super block
|
||||||
|
* of the mounted file system which is guaranteed to be smaller than or equal
|
||||||
|
* to the cluster size thus the block is guaranteed to fit entirely inside the
|
||||||
|
* cluster which means we do not need to care how many contiguous bytes are
|
||||||
|
* available after the beginning of the block.
|
||||||
|
*
|
||||||
|
* Return the physical device block if the mapping succeeded or 0 if the block
|
||||||
|
* is sparse or there was an error.
|
||||||
|
*
|
||||||
|
* Note: This is a problem if someone tries to run bmap() on $Boot system file
|
||||||
|
* as that really is in block zero but there is nothing we can do. bmap() is
|
||||||
|
* just broken in that respect (just like it cannot distinguish sparse from
|
||||||
|
* not available or error).
|
||||||
|
*/
|
||||||
|
static sector_t ntfs_bmap(struct address_space *mapping, sector_t block)
|
||||||
|
{
|
||||||
|
s64 ofs, size;
|
||||||
|
loff_t i_size;
|
||||||
|
s64 lcn;
|
||||||
|
unsigned long blocksize, flags;
|
||||||
|
struct ntfs_inode *ni = NTFS_I(mapping->host);
|
||||||
|
struct ntfs_volume *vol = ni->vol;
|
||||||
|
unsigned int delta;
|
||||||
|
unsigned char blocksize_bits;
|
||||||
|
|
||||||
|
ntfs_debug("Entering for mft_no 0x%llx, logical block 0x%llx.",
|
||||||
|
ni->mft_no, (unsigned long long)block);
|
||||||
|
if (ni->type != AT_DATA || !NInoNonResident(ni) || NInoEncrypted(ni) ||
|
||||||
|
NInoMstProtected(ni)) {
|
||||||
|
ntfs_error(vol->sb, "BMAP does not make sense for %s attributes, returning 0.",
|
||||||
|
(ni->type != AT_DATA) ? "non-data" :
|
||||||
|
(!NInoNonResident(ni) ? "resident" :
|
||||||
|
"encrypted"));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
/* None of these can happen. */
|
||||||
|
blocksize = vol->sb->s_blocksize;
|
||||||
|
blocksize_bits = vol->sb->s_blocksize_bits;
|
||||||
|
ofs = (s64)block << blocksize_bits;
|
||||||
|
read_lock_irqsave(&ni->size_lock, flags);
|
||||||
|
size = ni->initialized_size;
|
||||||
|
i_size = i_size_read(VFS_I(ni));
|
||||||
|
read_unlock_irqrestore(&ni->size_lock, flags);
|
||||||
|
/*
|
||||||
|
* If the offset is outside the initialized size or the block straddles
|
||||||
|
* the initialized size then pretend it is a hole unless the
|
||||||
|
* initialized size equals the file size.
|
||||||
|
*/
|
||||||
|
if (unlikely(ofs >= size || (ofs + blocksize > size && size < i_size)))
|
||||||
|
goto hole;
|
||||||
|
down_read(&ni->runlist.lock);
|
||||||
|
lcn = ntfs_attr_vcn_to_lcn_nolock(ni, ntfs_bytes_to_cluster(vol, ofs),
|
||||||
|
false);
|
||||||
|
up_read(&ni->runlist.lock);
|
||||||
|
if (unlikely(lcn < LCN_HOLE)) {
|
||||||
|
/*
|
||||||
|
* Step down to an integer to avoid gcc doing a long long
|
||||||
|
* comparision in the switch when we know @lcn is between
|
||||||
|
* LCN_HOLE and LCN_EIO (i.e. -1 to -5).
|
||||||
|
*
|
||||||
|
* Otherwise older gcc (at least on some architectures) will
|
||||||
|
* try to use __cmpdi2() which is of course not available in
|
||||||
|
* the kernel.
|
||||||
|
*/
|
||||||
|
switch ((int)lcn) {
|
||||||
|
case LCN_ENOENT:
|
||||||
|
/*
|
||||||
|
* If the offset is out of bounds then pretend it is a
|
||||||
|
* hole.
|
||||||
|
*/
|
||||||
|
goto hole;
|
||||||
|
case LCN_ENOMEM:
|
||||||
|
ntfs_error(vol->sb,
|
||||||
|
"Not enough memory to complete mapping for inode 0x%llx. Returning 0.",
|
||||||
|
ni->mft_no);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ntfs_error(vol->sb,
|
||||||
|
"Failed to complete mapping for inode 0x%llx. Run chkdsk. Returning 0.",
|
||||||
|
ni->mft_no);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (lcn < 0) {
|
||||||
|
/* It is a hole. */
|
||||||
|
hole:
|
||||||
|
ntfs_debug("Done (returning hole).");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* The block is really allocated and fullfils all our criteria.
|
||||||
|
* Convert the cluster to units of block size and return the result.
|
||||||
|
*/
|
||||||
|
delta = ofs & vol->cluster_size_mask;
|
||||||
|
if (unlikely(sizeof(block) < sizeof(lcn))) {
|
||||||
|
block = lcn = (ntfs_cluster_to_bytes(vol, lcn) + delta) >>
|
||||||
|
blocksize_bits;
|
||||||
|
/* If the block number was truncated return 0. */
|
||||||
|
if (unlikely(block != lcn)) {
|
||||||
|
ntfs_error(vol->sb,
|
||||||
|
"Physical block 0x%llx is too large to be returned, returning 0.",
|
||||||
|
(long long)lcn);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
block = (ntfs_cluster_to_bytes(vol, lcn) + delta) >>
|
||||||
|
blocksize_bits;
|
||||||
|
ntfs_debug("Done (returning block 0x%llx).", (unsigned long long)lcn);
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ntfs_readahead(struct readahead_control *rac)
|
||||||
|
{
|
||||||
|
struct address_space *mapping = rac->mapping;
|
||||||
|
struct inode *inode = mapping->host;
|
||||||
|
struct ntfs_inode *ni = NTFS_I(inode);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Resident files are not cached in the page cache,
|
||||||
|
* and readahead is not implemented for compressed files.
|
||||||
|
*/
|
||||||
|
if (!NInoNonResident(ni) || NInoCompressed(ni))
|
||||||
|
return;
|
||||||
|
iomap_bio_readahead(rac, &ntfs_read_iomap_ops);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ntfs_writepages(struct address_space *mapping,
|
||||||
|
struct writeback_control *wbc)
|
||||||
|
{
|
||||||
|
struct inode *inode = mapping->host;
|
||||||
|
struct ntfs_inode *ni = NTFS_I(inode);
|
||||||
|
struct iomap_writepage_ctx wpc = {
|
||||||
|
.inode = mapping->host,
|
||||||
|
.wbc = wbc,
|
||||||
|
.ops = &ntfs_writeback_ops,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (NVolShutdown(ni->vol))
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
if (!NInoNonResident(ni))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* EFS-encrypted files are not supported.
|
||||||
|
* (decryption/encryption is not implemented yet)
|
||||||
|
*/
|
||||||
|
if (NInoEncrypted(ni)) {
|
||||||
|
ntfs_debug("Encrypted I/O not supported");
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
|
return iomap_writepages(&wpc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ntfs_swap_activate(struct swap_info_struct *sis,
|
||||||
|
struct file *swap_file, sector_t *span)
|
||||||
|
{
|
||||||
|
return iomap_swapfile_activate(sis, swap_file, span,
|
||||||
|
&ntfs_read_iomap_ops);
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct address_space_operations ntfs_aops = {
|
||||||
|
.read_folio = ntfs_read_folio,
|
||||||
|
.readahead = ntfs_readahead,
|
||||||
|
.writepages = ntfs_writepages,
|
||||||
|
.direct_IO = noop_direct_IO,
|
||||||
|
.dirty_folio = iomap_dirty_folio,
|
||||||
|
.bmap = ntfs_bmap,
|
||||||
|
.migrate_folio = filemap_migrate_folio,
|
||||||
|
.is_partially_uptodate = iomap_is_partially_uptodate,
|
||||||
|
.error_remove_folio = generic_error_remove_folio,
|
||||||
|
.release_folio = iomap_release_folio,
|
||||||
|
.invalidate_folio = iomap_invalidate_folio,
|
||||||
|
.swap_activate = ntfs_swap_activate,
|
||||||
|
};
|
||||||
|
|
||||||
|
const struct address_space_operations ntfs_mft_aops = {
|
||||||
|
.read_folio = ntfs_read_folio,
|
||||||
|
.readahead = ntfs_readahead,
|
||||||
|
.writepages = ntfs_mft_writepages,
|
||||||
|
.dirty_folio = iomap_dirty_folio,
|
||||||
|
.bmap = ntfs_bmap,
|
||||||
|
.migrate_folio = filemap_migrate_folio,
|
||||||
|
.is_partially_uptodate = iomap_is_partially_uptodate,
|
||||||
|
.error_remove_folio = generic_error_remove_folio,
|
||||||
|
.release_folio = iomap_release_folio,
|
||||||
|
.invalidate_folio = iomap_invalidate_folio,
|
||||||
|
};
|
||||||
5539
fs/ntfs/attrib.c
Normal file
5539
fs/ntfs/attrib.c
Normal file
File diff suppressed because it is too large
Load Diff
164
fs/ntfs/attrib.h
Normal file
164
fs/ntfs/attrib.h
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
/*
|
||||||
|
* Defines for attribute handling in NTFS Linux kernel driver.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2001-2005 Anton Altaparmakov
|
||||||
|
* Copyright (c) 2002 Richard Russon
|
||||||
|
* Copyright (c) 2025 LG Electronics Co., Ltd.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _LINUX_NTFS_ATTRIB_H
|
||||||
|
#define _LINUX_NTFS_ATTRIB_H
|
||||||
|
|
||||||
|
#include "ntfs.h"
|
||||||
|
#include "dir.h"
|
||||||
|
|
||||||
|
extern __le16 AT_UNNAMED[];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_attr_search_ctx - used in attribute search functions
|
||||||
|
* @mrec: buffer containing mft record to search
|
||||||
|
* @mapped_mrec: true if @mrec was mapped by the search functions
|
||||||
|
* @attr: attribute record in @mrec where to begin/continue search
|
||||||
|
* @is_first: if true ntfs_attr_lookup() begins search with @attr, else after
|
||||||
|
* @ntfs_ino: Inode owning this attribute search
|
||||||
|
* @al_entry: Current attribute list entry
|
||||||
|
* @base_ntfs_ino: Base inode
|
||||||
|
* @mapped_base_mrec: true if @base_mrec was mapped by the search
|
||||||
|
* @base_attr: Base attribute record pointer
|
||||||
|
*
|
||||||
|
* Structure must be initialized to zero before the first call to one of the
|
||||||
|
* attribute search functions. Initialize @mrec to point to the mft record to
|
||||||
|
* search, and @attr to point to the first attribute within @mrec (not necessary
|
||||||
|
* if calling the _first() functions), and set @is_first to 'true' (not necessary
|
||||||
|
* if calling the _first() functions).
|
||||||
|
*
|
||||||
|
* If @is_first is 'true', the search begins with @attr. If @is_first is 'false',
|
||||||
|
* the search begins after @attr. This is so that, after the first call to one
|
||||||
|
* of the search attribute functions, we can call the function again, without
|
||||||
|
* any modification of the search context, to automagically get the next
|
||||||
|
* matching attribute.
|
||||||
|
*/
|
||||||
|
struct ntfs_attr_search_ctx {
|
||||||
|
struct mft_record *mrec;
|
||||||
|
bool mapped_mrec;
|
||||||
|
struct attr_record *attr;
|
||||||
|
bool is_first;
|
||||||
|
struct ntfs_inode *ntfs_ino;
|
||||||
|
struct attr_list_entry *al_entry;
|
||||||
|
struct ntfs_inode *base_ntfs_ino;
|
||||||
|
struct mft_record *base_mrec;
|
||||||
|
bool mapped_base_mrec;
|
||||||
|
struct attr_record *base_attr;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum { /* ways of processing holes when expanding */
|
||||||
|
HOLES_NO,
|
||||||
|
HOLES_OK,
|
||||||
|
};
|
||||||
|
|
||||||
|
int ntfs_map_runlist_nolock(struct ntfs_inode *ni, s64 vcn,
|
||||||
|
struct ntfs_attr_search_ctx *ctx);
|
||||||
|
int ntfs_map_runlist(struct ntfs_inode *ni, s64 vcn);
|
||||||
|
s64 ntfs_attr_vcn_to_lcn_nolock(struct ntfs_inode *ni, const s64 vcn,
|
||||||
|
const bool write_locked);
|
||||||
|
struct runlist_element *ntfs_attr_find_vcn_nolock(struct ntfs_inode *ni,
|
||||||
|
const s64 vcn, struct ntfs_attr_search_ctx *ctx);
|
||||||
|
struct runlist_element *__ntfs_attr_find_vcn_nolock(struct runlist *runlist,
|
||||||
|
const s64 vcn);
|
||||||
|
int ntfs_attr_map_whole_runlist(struct ntfs_inode *ni);
|
||||||
|
int ntfs_attr_lookup(const __le32 type, const __le16 *name,
|
||||||
|
const u32 name_len, const u32 ic,
|
||||||
|
const s64 lowest_vcn, const u8 *val, const u32 val_len,
|
||||||
|
struct ntfs_attr_search_ctx *ctx);
|
||||||
|
int load_attribute_list(struct ntfs_inode *base_ni,
|
||||||
|
u8 *al_start, const s64 size);
|
||||||
|
|
||||||
|
static inline s64 ntfs_attr_size(const struct attr_record *a)
|
||||||
|
{
|
||||||
|
if (!a->non_resident)
|
||||||
|
return (s64)le32_to_cpu(a->data.resident.value_length);
|
||||||
|
return le64_to_cpu(a->data.non_resident.data_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ntfs_attr_reinit_search_ctx(struct ntfs_attr_search_ctx *ctx);
|
||||||
|
struct ntfs_attr_search_ctx *ntfs_attr_get_search_ctx(struct ntfs_inode *ni,
|
||||||
|
struct mft_record *mrec);
|
||||||
|
void ntfs_attr_put_search_ctx(struct ntfs_attr_search_ctx *ctx);
|
||||||
|
int ntfs_attr_size_bounds_check(const struct ntfs_volume *vol,
|
||||||
|
const __le32 type, const s64 size);
|
||||||
|
int ntfs_attr_can_be_resident(const struct ntfs_volume *vol,
|
||||||
|
const __le32 type);
|
||||||
|
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);
|
||||||
|
int ntfs_attr_record_resize(struct mft_record *m, struct attr_record *a, u32 new_size);
|
||||||
|
int ntfs_resident_attr_value_resize(struct mft_record *m, struct attr_record *a,
|
||||||
|
const u32 new_size);
|
||||||
|
int ntfs_attr_make_non_resident(struct ntfs_inode *ni, const u32 data_size);
|
||||||
|
int ntfs_attr_set(struct ntfs_inode *ni, const s64 ofs, const s64 cnt,
|
||||||
|
const u8 val);
|
||||||
|
int ntfs_attr_set_initialized_size(struct ntfs_inode *ni, loff_t new_size);
|
||||||
|
int ntfs_attr_open(struct ntfs_inode *ni, const __le32 type,
|
||||||
|
__le16 *name, u32 name_len);
|
||||||
|
void ntfs_attr_close(struct ntfs_inode *n);
|
||||||
|
int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bool keep_size);
|
||||||
|
int ntfs_non_resident_attr_insert_range(struct ntfs_inode *ni, s64 start_vcn, s64 len);
|
||||||
|
int ntfs_non_resident_attr_collapse_range(struct ntfs_inode *ni, s64 start_vcn, s64 len);
|
||||||
|
int ntfs_non_resident_attr_punch_hole(struct ntfs_inode *ni, s64 start_vcn, s64 len);
|
||||||
|
int __ntfs_attr_truncate_vfs(struct ntfs_inode *ni, const s64 newsize,
|
||||||
|
const s64 i_size);
|
||||||
|
int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 prealloc_size);
|
||||||
|
int ntfs_attr_truncate_i(struct ntfs_inode *ni, const s64 newsize, unsigned int holes);
|
||||||
|
int ntfs_attr_truncate(struct ntfs_inode *ni, const s64 newsize);
|
||||||
|
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);
|
||||||
|
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,
|
||||||
|
__le16 *name, u8 name_len, u8 *val, s64 size);
|
||||||
|
int ntfs_attr_record_move_away(struct ntfs_attr_search_ctx *ctx, int extra);
|
||||||
|
char *ntfs_attr_name_get(const struct ntfs_volume *vol, const __le16 *uname,
|
||||||
|
const int uname_len);
|
||||||
|
void ntfs_attr_name_free(unsigned char **name);
|
||||||
|
void *ntfs_attr_readall(struct ntfs_inode *ni, const __le32 type,
|
||||||
|
__le16 *name, u32 name_len, s64 *data_size);
|
||||||
|
int ntfs_resident_attr_record_add(struct ntfs_inode *ni, __le32 type,
|
||||||
|
__le16 *name, u8 name_len, u8 *val, u32 size,
|
||||||
|
__le16 flags);
|
||||||
|
int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn);
|
||||||
|
struct runlist_element *ntfs_attr_vcn_to_rl(struct ntfs_inode *ni, s64 vcn, s64 *lcn);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_attrs_walk - syntactic sugar for walking all attributes in an inode
|
||||||
|
* @ctx: initialised attribute search context
|
||||||
|
*
|
||||||
|
* Syntactic sugar for walking attributes in an inode.
|
||||||
|
*
|
||||||
|
* Return 0 on success and -1 on error with errno set to the error code from
|
||||||
|
* ntfs_attr_lookup().
|
||||||
|
*
|
||||||
|
* Example: When you want to enumerate all attributes in an open ntfs inode
|
||||||
|
* @ni, you can simply do:
|
||||||
|
*
|
||||||
|
* int err;
|
||||||
|
* struct ntfs_attr_search_ctx *ctx = ntfs_attr_get_search_ctx(ni, NULL);
|
||||||
|
* if (!ctx)
|
||||||
|
* // Error code is in errno. Handle this case.
|
||||||
|
* while (!(err = ntfs_attrs_walk(ctx))) {
|
||||||
|
* struct attr_record *attr = ctx->attr;
|
||||||
|
* // attr now contains the next attribute. Do whatever you want
|
||||||
|
* // with it and then just continue with the while loop.
|
||||||
|
* }
|
||||||
|
* if (err && errno != ENOENT)
|
||||||
|
* // Ooops. An error occurred! You should handle this case.
|
||||||
|
* // Now finished with all attributes in the inode.
|
||||||
|
*/
|
||||||
|
static inline int ntfs_attrs_walk(struct ntfs_attr_search_ctx *ctx)
|
||||||
|
{
|
||||||
|
return ntfs_attr_lookup(AT_UNUSED, NULL, 0, CASE_SENSITIVE, 0,
|
||||||
|
NULL, 0, ctx);
|
||||||
|
}
|
||||||
|
#endif /* _LINUX_NTFS_ATTRIB_H */
|
||||||
289
fs/ntfs/attrlist.c
Normal file
289
fs/ntfs/attrlist.c
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* Attribute list attribute handling code.
|
||||||
|
* Part of this file is based on code from the NTFS-3G.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2004-2005 Anton Altaparmakov
|
||||||
|
* Copyright (c) 2004-2005 Yura Pakhuchiy
|
||||||
|
* Copyright (c) 2006 Szabolcs Szakacsits
|
||||||
|
* Copyright (c) 2025 LG Electronics Co., Ltd.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "mft.h"
|
||||||
|
#include "attrib.h"
|
||||||
|
#include "attrlist.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_attrlist_need - check whether inode need attribute list
|
||||||
|
* @ni: opened ntfs inode for which perform check
|
||||||
|
*
|
||||||
|
* Check whether all are attributes belong to one MFT record, in that case
|
||||||
|
* attribute list is not needed.
|
||||||
|
*
|
||||||
|
* Return 1 if inode need attribute list, 0 if not, or -errno on error.
|
||||||
|
*/
|
||||||
|
int ntfs_attrlist_need(struct ntfs_inode *ni)
|
||||||
|
{
|
||||||
|
struct attr_list_entry *ale;
|
||||||
|
|
||||||
|
if (!ni) {
|
||||||
|
ntfs_debug("Invalid arguments.\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
ntfs_debug("Entering for inode 0x%llx.\n", (long long) ni->mft_no);
|
||||||
|
|
||||||
|
if (!NInoAttrList(ni)) {
|
||||||
|
ntfs_debug("Inode haven't got attribute list.\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ni->attr_list) {
|
||||||
|
ntfs_debug("Corrupt in-memory struct.\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ale = (struct attr_list_entry *)ni->attr_list;
|
||||||
|
while ((u8 *)ale < ni->attr_list + ni->attr_list_size) {
|
||||||
|
if (MREF_LE(ale->mft_reference) != ni->mft_no)
|
||||||
|
return 1;
|
||||||
|
ale = (struct attr_list_entry *)((u8 *)ale + le16_to_cpu(ale->length));
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ntfs_attrlist_update(struct ntfs_inode *base_ni)
|
||||||
|
{
|
||||||
|
struct inode *attr_vi;
|
||||||
|
struct ntfs_inode *attr_ni;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
attr_vi = ntfs_attr_iget(VFS_I(base_ni), AT_ATTRIBUTE_LIST, AT_UNNAMED, 0);
|
||||||
|
if (IS_ERR(attr_vi)) {
|
||||||
|
err = PTR_ERR(attr_vi);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
attr_ni = NTFS_I(attr_vi);
|
||||||
|
|
||||||
|
err = ntfs_attr_truncate_i(attr_ni, base_ni->attr_list_size, HOLES_NO);
|
||||||
|
if (err == -ENOSPC && attr_ni->mft_no == FILE_MFT) {
|
||||||
|
err = ntfs_attr_truncate(attr_ni, 0);
|
||||||
|
if (err || ntfs_attr_truncate_i(attr_ni, base_ni->attr_list_size, HOLES_NO) != 0) {
|
||||||
|
iput(attr_vi);
|
||||||
|
ntfs_error(base_ni->vol->sb,
|
||||||
|
"Failed to truncate attribute list of inode %#llx",
|
||||||
|
(long long)base_ni->mft_no);
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
} else if (err) {
|
||||||
|
iput(attr_vi);
|
||||||
|
ntfs_error(base_ni->vol->sb,
|
||||||
|
"Failed to truncate attribute list of inode %#llx",
|
||||||
|
(long long)base_ni->mft_no);
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
i_size_write(attr_vi, base_ni->attr_list_size);
|
||||||
|
|
||||||
|
if (NInoNonResident(attr_ni) && !NInoAttrListNonResident(base_ni))
|
||||||
|
NInoSetAttrListNonResident(base_ni);
|
||||||
|
|
||||||
|
if (ntfs_inode_attr_pwrite(attr_vi, 0, base_ni->attr_list_size,
|
||||||
|
base_ni->attr_list, false) !=
|
||||||
|
base_ni->attr_list_size) {
|
||||||
|
iput(attr_vi);
|
||||||
|
ntfs_error(base_ni->vol->sb,
|
||||||
|
"Failed to write attribute list of inode %#llx",
|
||||||
|
(long long)base_ni->mft_no);
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
NInoSetAttrListDirty(base_ni);
|
||||||
|
iput(attr_vi);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_attrlist_entry_add - add an attribute list attribute entry
|
||||||
|
* @ni: opened ntfs inode, which contains that attribute
|
||||||
|
* @attr: attribute record to add to attribute list
|
||||||
|
*
|
||||||
|
* Return 0 on success and -errno on error.
|
||||||
|
*/
|
||||||
|
int ntfs_attrlist_entry_add(struct ntfs_inode *ni, struct attr_record *attr)
|
||||||
|
{
|
||||||
|
struct attr_list_entry *ale;
|
||||||
|
__le64 mref;
|
||||||
|
struct ntfs_attr_search_ctx *ctx;
|
||||||
|
u8 *new_al;
|
||||||
|
int entry_len, entry_offset, err;
|
||||||
|
struct mft_record *ni_mrec;
|
||||||
|
u8 *old_al;
|
||||||
|
|
||||||
|
ntfs_debug("Entering for inode 0x%llx, attr 0x%x.\n",
|
||||||
|
(long long) ni->mft_no,
|
||||||
|
(unsigned int) le32_to_cpu(attr->type));
|
||||||
|
|
||||||
|
if (!ni || !attr) {
|
||||||
|
ntfs_debug("Invalid arguments.\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ni_mrec = map_mft_record(ni);
|
||||||
|
if (IS_ERR(ni_mrec)) {
|
||||||
|
ntfs_debug("Invalid arguments.\n");
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
mref = MK_LE_MREF(ni->mft_no, le16_to_cpu(ni_mrec->sequence_number));
|
||||||
|
unmap_mft_record(ni);
|
||||||
|
|
||||||
|
if (ni->nr_extents == -1)
|
||||||
|
ni = ni->ext.base_ntfs_ino;
|
||||||
|
|
||||||
|
if (!NInoAttrList(ni)) {
|
||||||
|
ntfs_debug("Attribute list isn't present.\n");
|
||||||
|
return -ENOENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Determine size and allocate memory for new attribute list. */
|
||||||
|
entry_len = (sizeof(struct attr_list_entry) + sizeof(__le16) *
|
||||||
|
attr->name_length + 7) & ~7;
|
||||||
|
new_al = kvzalloc(ni->attr_list_size + entry_len, GFP_NOFS);
|
||||||
|
if (!new_al)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
/* Find place for the new entry. */
|
||||||
|
ctx = ntfs_attr_get_search_ctx(ni, NULL);
|
||||||
|
if (!ctx) {
|
||||||
|
err = -ENOMEM;
|
||||||
|
ntfs_error(ni->vol->sb, "Failed to get search context");
|
||||||
|
goto err_out;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
(attr->non_resident) ? le64_to_cpu(attr->data.non_resident.lowest_vcn) :
|
||||||
|
0, (attr->non_resident) ? NULL : ((u8 *)attr +
|
||||||
|
le16_to_cpu(attr->data.resident.value_offset)), (attr->non_resident) ?
|
||||||
|
0 : le32_to_cpu(attr->data.resident.value_length), ctx);
|
||||||
|
if (!err) {
|
||||||
|
/* Found some extent, check it to be before new extent. */
|
||||||
|
if (ctx->al_entry->lowest_vcn == attr->data.non_resident.lowest_vcn) {
|
||||||
|
err = -EEXIST;
|
||||||
|
ntfs_debug("Such attribute already present in the attribute list.\n");
|
||||||
|
ntfs_attr_put_search_ctx(ctx);
|
||||||
|
goto err_out;
|
||||||
|
}
|
||||||
|
/* Add new entry after this extent. */
|
||||||
|
ale = (struct attr_list_entry *)((u8 *)ctx->al_entry +
|
||||||
|
le16_to_cpu(ctx->al_entry->length));
|
||||||
|
} else {
|
||||||
|
/* Check for real errors. */
|
||||||
|
if (err != -ENOENT) {
|
||||||
|
ntfs_debug("Attribute lookup failed.\n");
|
||||||
|
ntfs_attr_put_search_ctx(ctx);
|
||||||
|
goto err_out;
|
||||||
|
}
|
||||||
|
/* No previous extents found. */
|
||||||
|
ale = ctx->al_entry;
|
||||||
|
}
|
||||||
|
/* Don't need it anymore, @ctx->al_entry points to @ni->attr_list. */
|
||||||
|
ntfs_attr_put_search_ctx(ctx);
|
||||||
|
|
||||||
|
/* Determine new entry offset. */
|
||||||
|
entry_offset = ((u8 *)ale - ni->attr_list);
|
||||||
|
/* Set pointer to new entry. */
|
||||||
|
ale = (struct attr_list_entry *)(new_al + entry_offset);
|
||||||
|
memset(ale, 0, entry_len);
|
||||||
|
/* Form new entry. */
|
||||||
|
ale->type = attr->type;
|
||||||
|
ale->length = cpu_to_le16(entry_len);
|
||||||
|
ale->name_length = attr->name_length;
|
||||||
|
ale->name_offset = offsetof(struct attr_list_entry, name);
|
||||||
|
if (attr->non_resident)
|
||||||
|
ale->lowest_vcn = attr->data.non_resident.lowest_vcn;
|
||||||
|
else
|
||||||
|
ale->lowest_vcn = 0;
|
||||||
|
ale->mft_reference = mref;
|
||||||
|
ale->instance = attr->instance;
|
||||||
|
memcpy(ale->name, (u8 *)attr + le16_to_cpu(attr->name_offset),
|
||||||
|
attr->name_length * sizeof(__le16));
|
||||||
|
|
||||||
|
/* Copy entries from old attribute list to new. */
|
||||||
|
memcpy(new_al, ni->attr_list, entry_offset);
|
||||||
|
memcpy(new_al + entry_offset + entry_len, ni->attr_list +
|
||||||
|
entry_offset, ni->attr_list_size - entry_offset);
|
||||||
|
|
||||||
|
/* Set new runlist. */
|
||||||
|
old_al = ni->attr_list;
|
||||||
|
ni->attr_list = new_al;
|
||||||
|
ni->attr_list_size = ni->attr_list_size + entry_len;
|
||||||
|
|
||||||
|
err = ntfs_attrlist_update(ni);
|
||||||
|
if (err) {
|
||||||
|
ni->attr_list = old_al;
|
||||||
|
ni->attr_list_size -= entry_len;
|
||||||
|
goto err_out;
|
||||||
|
}
|
||||||
|
kvfree(old_al);
|
||||||
|
return 0;
|
||||||
|
err_out:
|
||||||
|
kvfree(new_al);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_attrlist_entry_rm - remove an attribute list attribute entry
|
||||||
|
* @ctx: attribute search context describing the attribute list entry
|
||||||
|
*
|
||||||
|
* Remove the attribute list entry @ctx->al_entry from the attribute list.
|
||||||
|
*
|
||||||
|
* Return 0 on success and -errno on error.
|
||||||
|
*/
|
||||||
|
int ntfs_attrlist_entry_rm(struct ntfs_attr_search_ctx *ctx)
|
||||||
|
{
|
||||||
|
u8 *new_al;
|
||||||
|
int new_al_len;
|
||||||
|
struct ntfs_inode *base_ni;
|
||||||
|
struct attr_list_entry *ale;
|
||||||
|
|
||||||
|
if (!ctx || !ctx->ntfs_ino || !ctx->al_entry) {
|
||||||
|
ntfs_debug("Invalid arguments.\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->base_ntfs_ino)
|
||||||
|
base_ni = ctx->base_ntfs_ino;
|
||||||
|
else
|
||||||
|
base_ni = ctx->ntfs_ino;
|
||||||
|
ale = ctx->al_entry;
|
||||||
|
|
||||||
|
ntfs_debug("Entering for inode 0x%llx, attr 0x%x, lowest_vcn %lld.\n",
|
||||||
|
(long long)ctx->ntfs_ino->mft_no,
|
||||||
|
(unsigned int)le32_to_cpu(ctx->al_entry->type),
|
||||||
|
(long long)le64_to_cpu(ctx->al_entry->lowest_vcn));
|
||||||
|
|
||||||
|
if (!NInoAttrList(base_ni)) {
|
||||||
|
ntfs_debug("Attribute list isn't present.\n");
|
||||||
|
return -ENOENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate memory for new attribute list. */
|
||||||
|
new_al_len = base_ni->attr_list_size - le16_to_cpu(ale->length);
|
||||||
|
new_al = kvzalloc(new_al_len, GFP_NOFS);
|
||||||
|
if (!new_al)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
/* Copy entries from old attribute list to new. */
|
||||||
|
memcpy(new_al, base_ni->attr_list, (u8 *)ale - base_ni->attr_list);
|
||||||
|
memcpy(new_al + ((u8 *)ale - base_ni->attr_list), (u8 *)ale + le16_to_cpu(
|
||||||
|
ale->length), new_al_len - ((u8 *)ale - base_ni->attr_list));
|
||||||
|
|
||||||
|
/* Set new runlist. */
|
||||||
|
kvfree(base_ni->attr_list);
|
||||||
|
base_ni->attr_list = new_al;
|
||||||
|
base_ni->attr_list_size = new_al_len;
|
||||||
|
|
||||||
|
return ntfs_attrlist_update(base_ni);
|
||||||
|
}
|
||||||
20
fs/ntfs/attrlist.h
Normal file
20
fs/ntfs/attrlist.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
/*
|
||||||
|
* Exports for attribute list attribute handling.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2004 Anton Altaparmakov
|
||||||
|
* Copyright (c) 2004 Yura Pakhuchiy
|
||||||
|
* Copyright (c) 2025 LG Electronics Co., Ltd.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _NTFS_ATTRLIST_H
|
||||||
|
#define _NTFS_ATTRLIST_H
|
||||||
|
|
||||||
|
#include "attrib.h"
|
||||||
|
|
||||||
|
int ntfs_attrlist_need(struct ntfs_inode *ni);
|
||||||
|
int ntfs_attrlist_entry_add(struct ntfs_inode *ni, struct attr_record *attr);
|
||||||
|
int ntfs_attrlist_entry_rm(struct ntfs_attr_search_ctx *ctx);
|
||||||
|
int ntfs_attrlist_update(struct ntfs_inode *base_ni);
|
||||||
|
|
||||||
|
#endif /* defined _NTFS_ATTRLIST_H */
|
||||||
117
fs/ntfs/bdev-io.c
Normal file
117
fs/ntfs/bdev-io.c
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* NTFS block device I/O.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2026 LG Electronics Co., Ltd.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/blkdev.h>
|
||||||
|
|
||||||
|
#include "ntfs.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_bdev_read - Read data directly from block device using bio
|
||||||
|
* @bdev: block device to read from
|
||||||
|
* @data: destination buffer
|
||||||
|
* @start: starting byte offset on the block device
|
||||||
|
* @size: number of bytes to read
|
||||||
|
*
|
||||||
|
* Reads @size bytes starting from byte offset @start directly from the block
|
||||||
|
* device using one or more BIOs. This function bypasses the page cache
|
||||||
|
* completely and performs synchronous I/O with REQ_META | REQ_SYNC flags set.
|
||||||
|
*
|
||||||
|
* The @start offset must be sector-aligned (512 bytes). If it is not aligned,
|
||||||
|
* the function will return -EINVAL.
|
||||||
|
*
|
||||||
|
* If the destination buffer @data is not a vmalloc address, it falls back
|
||||||
|
* to the more efficient bdev_rw_virt() helper.
|
||||||
|
*
|
||||||
|
* Return: 0 on success, negative error code on failure.
|
||||||
|
*/
|
||||||
|
int ntfs_bdev_read(struct block_device *bdev, char *data, loff_t start, size_t size)
|
||||||
|
{
|
||||||
|
unsigned int done = 0, added;
|
||||||
|
int error;
|
||||||
|
struct bio *bio;
|
||||||
|
enum req_op op;
|
||||||
|
sector_t sector = start >> SECTOR_SHIFT;
|
||||||
|
|
||||||
|
if (start & (SECTOR_SIZE - 1))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
op = REQ_OP_READ | REQ_META | REQ_SYNC;
|
||||||
|
if (!is_vmalloc_addr(data))
|
||||||
|
return bdev_rw_virt(bdev, sector, data, size, op);
|
||||||
|
|
||||||
|
bio = bio_alloc(bdev,
|
||||||
|
bio_max_segs(DIV_ROUND_UP(size, PAGE_SIZE)),
|
||||||
|
op, GFP_KERNEL);
|
||||||
|
bio->bi_iter.bi_sector = sector;
|
||||||
|
|
||||||
|
do {
|
||||||
|
added = bio_add_vmalloc_chunk(bio, data + done, size - done);
|
||||||
|
if (!added) {
|
||||||
|
struct bio *prev = bio;
|
||||||
|
|
||||||
|
bio = bio_alloc(prev->bi_bdev,
|
||||||
|
bio_max_segs(DIV_ROUND_UP(size - done, PAGE_SIZE)),
|
||||||
|
prev->bi_opf, GFP_KERNEL);
|
||||||
|
bio->bi_iter.bi_sector = bio_end_sector(prev);
|
||||||
|
bio_chain(prev, bio);
|
||||||
|
submit_bio(prev);
|
||||||
|
}
|
||||||
|
done += added;
|
||||||
|
} while (done < size);
|
||||||
|
|
||||||
|
error = submit_bio_wait(bio);
|
||||||
|
bio_put(bio);
|
||||||
|
|
||||||
|
if (op == REQ_OP_READ)
|
||||||
|
invalidate_kernel_vmap_range(data, size);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_bdev_write - Update block device contents via page cache
|
||||||
|
* @sb: super block of the mounted NTFS filesystem
|
||||||
|
* @buf: source buffer containing data to write
|
||||||
|
* @start: starting byte offset on the block device
|
||||||
|
* @size: number of bytes to write
|
||||||
|
*
|
||||||
|
* Writes @size bytes from @buf to the block device (sb->s_bdev) starting
|
||||||
|
* at byte offset @start. The write is performed entirely through the page
|
||||||
|
* cache of the block device's address space.
|
||||||
|
*/
|
||||||
|
int ntfs_bdev_write(struct super_block *sb, void *buf, loff_t start, size_t size)
|
||||||
|
{
|
||||||
|
pgoff_t idx, idx_end;
|
||||||
|
loff_t offset, end = start + size;
|
||||||
|
u32 from, to, buf_off = 0;
|
||||||
|
struct folio *folio;
|
||||||
|
|
||||||
|
idx = start >> PAGE_SHIFT;
|
||||||
|
idx_end = end >> PAGE_SHIFT;
|
||||||
|
from = start & ~PAGE_MASK;
|
||||||
|
|
||||||
|
if (idx == idx_end)
|
||||||
|
idx_end++;
|
||||||
|
|
||||||
|
for (; idx < idx_end; idx++, from = 0) {
|
||||||
|
folio = read_mapping_folio(sb->s_bdev->bd_mapping, idx, NULL);
|
||||||
|
if (IS_ERR(folio)) {
|
||||||
|
ntfs_error(sb, "Unable to read %ld page", idx);
|
||||||
|
return PTR_ERR(folio);
|
||||||
|
}
|
||||||
|
|
||||||
|
offset = (loff_t)idx << PAGE_SHIFT;
|
||||||
|
to = min_t(u32, end - offset, PAGE_SIZE);
|
||||||
|
|
||||||
|
memcpy_to_folio(folio, from, buf + buf_off, to);
|
||||||
|
buf_off += to;
|
||||||
|
folio_mark_uptodate(folio);
|
||||||
|
folio_mark_dirty(folio);
|
||||||
|
folio_put(folio);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
287
fs/ntfs/bitmap.c
Normal file
287
fs/ntfs/bitmap.c
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* NTFS kernel bitmap handling.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2004-2005 Anton Altaparmakov
|
||||||
|
* Copyright (c) 2025 LG Electronics Co., Ltd.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/bitops.h>
|
||||||
|
#include <linux/blkdev.h>
|
||||||
|
|
||||||
|
#include "bitmap.h"
|
||||||
|
#include "ntfs.h"
|
||||||
|
|
||||||
|
int ntfs_trim_fs(struct ntfs_volume *vol, struct fstrim_range *range)
|
||||||
|
{
|
||||||
|
size_t buf_clusters;
|
||||||
|
pgoff_t index, start_index, end_index;
|
||||||
|
struct file_ra_state *ra;
|
||||||
|
struct folio *folio;
|
||||||
|
unsigned long *bitmap;
|
||||||
|
char *kaddr;
|
||||||
|
u64 end, trimmed = 0, start_buf, end_buf, end_cluster;
|
||||||
|
u64 start_cluster = ntfs_bytes_to_cluster(vol, range->start);
|
||||||
|
u32 dq = bdev_discard_granularity(vol->sb->s_bdev);
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
if (!dq)
|
||||||
|
dq = vol->cluster_size;
|
||||||
|
|
||||||
|
if (start_cluster >= vol->nr_clusters)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (range->len == (u64)-1)
|
||||||
|
end_cluster = vol->nr_clusters;
|
||||||
|
else {
|
||||||
|
end_cluster = ntfs_bytes_to_cluster(vol,
|
||||||
|
(range->start + range->len + vol->cluster_size - 1));
|
||||||
|
if (end_cluster > vol->nr_clusters)
|
||||||
|
end_cluster = vol->nr_clusters;
|
||||||
|
}
|
||||||
|
|
||||||
|
ra = kzalloc(sizeof(*ra), GFP_NOFS);
|
||||||
|
if (!ra)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
buf_clusters = PAGE_SIZE * 8;
|
||||||
|
start_index = start_cluster >> 15;
|
||||||
|
end_index = (end_cluster + buf_clusters - 1) >> 15;
|
||||||
|
|
||||||
|
for (index = start_index; index < end_index; index++) {
|
||||||
|
folio = ntfs_get_locked_folio(vol->lcnbmp_ino->i_mapping,
|
||||||
|
index, end_index, ra);
|
||||||
|
if (IS_ERR(folio)) {
|
||||||
|
ret = PTR_ERR(folio);
|
||||||
|
goto out_free;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
end = start_buf;
|
||||||
|
while (end < end_buf) {
|
||||||
|
u64 aligned_start, aligned_count;
|
||||||
|
u64 start = find_next_zero_bit(bitmap, end_buf - start_buf,
|
||||||
|
end - start_buf) + start_buf;
|
||||||
|
if (start >= end_buf)
|
||||||
|
break;
|
||||||
|
|
||||||
|
end = find_next_bit(bitmap, end_buf - start_buf,
|
||||||
|
start - start_buf) + start_buf;
|
||||||
|
|
||||||
|
aligned_start = ALIGN(ntfs_cluster_to_bytes(vol, start), dq);
|
||||||
|
aligned_count =
|
||||||
|
ALIGN_DOWN(ntfs_cluster_to_bytes(vol, end - start), dq);
|
||||||
|
if (aligned_count >= range->minlen) {
|
||||||
|
ret = blkdev_issue_discard(vol->sb->s_bdev, aligned_start >> 9,
|
||||||
|
aligned_count >> 9, GFP_NOFS);
|
||||||
|
if (ret)
|
||||||
|
goto out_unmap;
|
||||||
|
trimmed += aligned_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out_unmap:
|
||||||
|
kunmap_local(kaddr);
|
||||||
|
folio_unlock(folio);
|
||||||
|
folio_put(folio);
|
||||||
|
|
||||||
|
if (ret)
|
||||||
|
goto out_free;
|
||||||
|
}
|
||||||
|
|
||||||
|
range->len = trimmed;
|
||||||
|
|
||||||
|
out_free:
|
||||||
|
kfree(ra);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* __ntfs_bitmap_set_bits_in_run - set a run of bits in a bitmap to a value
|
||||||
|
* @vi: vfs inode describing the bitmap
|
||||||
|
* @start_bit: first bit to set
|
||||||
|
* @count: number of bits to set
|
||||||
|
* @value: value to set the bits to (i.e. 0 or 1)
|
||||||
|
* @is_rollback: if 'true' this is a rollback operation
|
||||||
|
*
|
||||||
|
* Set @count bits starting at bit @start_bit in the bitmap described by the
|
||||||
|
* vfs inode @vi to @value, where @value is either 0 or 1.
|
||||||
|
*
|
||||||
|
* @is_rollback should always be 'false', it is for internal use to rollback
|
||||||
|
* errors. You probably want to use ntfs_bitmap_set_bits_in_run() instead.
|
||||||
|
*
|
||||||
|
* Return 0 on success and -errno on error.
|
||||||
|
*/
|
||||||
|
int __ntfs_bitmap_set_bits_in_run(struct inode *vi, const s64 start_bit,
|
||||||
|
const s64 count, const u8 value, const bool is_rollback)
|
||||||
|
{
|
||||||
|
s64 cnt = count;
|
||||||
|
pgoff_t index, end_index;
|
||||||
|
struct address_space *mapping;
|
||||||
|
struct folio *folio;
|
||||||
|
u8 *kaddr;
|
||||||
|
int pos, len;
|
||||||
|
u8 bit;
|
||||||
|
struct ntfs_inode *ni = NTFS_I(vi);
|
||||||
|
struct ntfs_volume *vol = ni->vol;
|
||||||
|
|
||||||
|
ntfs_debug("Entering for i_ino 0x%llx, start_bit 0x%llx, count 0x%llx, value %u.%s",
|
||||||
|
ni->mft_no, (unsigned long long)start_bit,
|
||||||
|
(unsigned long long)cnt, (unsigned int)value,
|
||||||
|
is_rollback ? " (rollback)" : "");
|
||||||
|
|
||||||
|
if (start_bit < 0 || cnt < 0 || value > 1)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Calculate the indices for the pages containing the first and last
|
||||||
|
* bits, i.e. @start_bit and @start_bit + @cnt - 1, respectively.
|
||||||
|
*/
|
||||||
|
index = start_bit >> (3 + PAGE_SHIFT);
|
||||||
|
end_index = (start_bit + cnt - 1) >> (3 + PAGE_SHIFT);
|
||||||
|
|
||||||
|
/* Get the page containing the first bit (@start_bit). */
|
||||||
|
mapping = vi->i_mapping;
|
||||||
|
folio = read_mapping_folio(mapping, index, NULL);
|
||||||
|
if (IS_ERR(folio)) {
|
||||||
|
if (!is_rollback)
|
||||||
|
ntfs_error(vi->i_sb,
|
||||||
|
"Failed to map first page (error %li), aborting.",
|
||||||
|
PTR_ERR(folio));
|
||||||
|
return PTR_ERR(folio);
|
||||||
|
}
|
||||||
|
|
||||||
|
folio_lock(folio);
|
||||||
|
kaddr = kmap_local_folio(folio, 0);
|
||||||
|
|
||||||
|
/* Set @pos to the position of the byte containing @start_bit. */
|
||||||
|
pos = (start_bit >> 3) & ~PAGE_MASK;
|
||||||
|
|
||||||
|
/* Calculate the position of @start_bit in the first byte. */
|
||||||
|
bit = start_bit & 7;
|
||||||
|
|
||||||
|
/* If the first byte is partial, modify the appropriate bits in it. */
|
||||||
|
if (bit) {
|
||||||
|
u8 *byte = kaddr + pos;
|
||||||
|
|
||||||
|
if (ni->mft_no == FILE_Bitmap)
|
||||||
|
ntfs_set_lcn_empty_bits(vol, index, value, min_t(s64, 8 - bit, cnt));
|
||||||
|
while ((bit & 7) && cnt) {
|
||||||
|
cnt--;
|
||||||
|
if (value)
|
||||||
|
*byte |= 1 << bit++;
|
||||||
|
else
|
||||||
|
*byte &= ~(1 << bit++);
|
||||||
|
}
|
||||||
|
/* If we are done, unmap the page and return success. */
|
||||||
|
if (!cnt)
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
/* Update @pos to the new position. */
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Depending on @value, modify all remaining whole bytes in the page up
|
||||||
|
* to @cnt.
|
||||||
|
*/
|
||||||
|
len = min_t(s64, cnt >> 3, PAGE_SIZE - pos);
|
||||||
|
memset(kaddr + pos, value ? 0xff : 0, len);
|
||||||
|
cnt -= len << 3;
|
||||||
|
if (ni->mft_no == FILE_Bitmap)
|
||||||
|
ntfs_set_lcn_empty_bits(vol, index, value, len << 3);
|
||||||
|
|
||||||
|
/* Update @len to point to the first not-done byte in the page. */
|
||||||
|
if (cnt < 8)
|
||||||
|
len += pos;
|
||||||
|
|
||||||
|
/* If we are not in the last page, deal with all subsequent pages. */
|
||||||
|
while (index < end_index) {
|
||||||
|
if (cnt <= 0)
|
||||||
|
goto rollback;
|
||||||
|
|
||||||
|
/* Update @index and get the next folio. */
|
||||||
|
folio_mark_dirty(folio);
|
||||||
|
folio_unlock(folio);
|
||||||
|
kunmap_local(kaddr);
|
||||||
|
folio_put(folio);
|
||||||
|
folio = read_mapping_folio(mapping, ++index, NULL);
|
||||||
|
if (IS_ERR(folio)) {
|
||||||
|
ntfs_error(vi->i_sb,
|
||||||
|
"Failed to map subsequent page (error %li), aborting.",
|
||||||
|
PTR_ERR(folio));
|
||||||
|
goto rollback;
|
||||||
|
}
|
||||||
|
|
||||||
|
folio_lock(folio);
|
||||||
|
kaddr = kmap_local_folio(folio, 0);
|
||||||
|
/*
|
||||||
|
* Depending on @value, modify all remaining whole bytes in the
|
||||||
|
* page up to @cnt.
|
||||||
|
*/
|
||||||
|
len = min_t(s64, cnt >> 3, PAGE_SIZE);
|
||||||
|
memset(kaddr, value ? 0xff : 0, len);
|
||||||
|
cnt -= len << 3;
|
||||||
|
if (ni->mft_no == FILE_Bitmap)
|
||||||
|
ntfs_set_lcn_empty_bits(vol, index, value, len << 3);
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* The currently mapped page is the last one. If the last byte is
|
||||||
|
* partial, modify the appropriate bits in it. Note, @len is the
|
||||||
|
* position of the last byte inside the page.
|
||||||
|
*/
|
||||||
|
if (cnt) {
|
||||||
|
u8 *byte;
|
||||||
|
|
||||||
|
WARN_ON(cnt > 7);
|
||||||
|
|
||||||
|
bit = cnt;
|
||||||
|
byte = kaddr + len;
|
||||||
|
if (ni->mft_no == FILE_Bitmap)
|
||||||
|
ntfs_set_lcn_empty_bits(vol, index, value, bit);
|
||||||
|
while (bit--) {
|
||||||
|
if (value)
|
||||||
|
*byte |= 1 << bit;
|
||||||
|
else
|
||||||
|
*byte &= ~(1 << bit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
done:
|
||||||
|
/* We are done. Unmap the folio and return success. */
|
||||||
|
folio_mark_dirty(folio);
|
||||||
|
folio_unlock(folio);
|
||||||
|
kunmap_local(kaddr);
|
||||||
|
folio_put(folio);
|
||||||
|
ntfs_debug("Done.");
|
||||||
|
return 0;
|
||||||
|
rollback:
|
||||||
|
/*
|
||||||
|
* Current state:
|
||||||
|
* - no pages are mapped
|
||||||
|
* - @count - @cnt is the number of bits that have been modified
|
||||||
|
*/
|
||||||
|
if (is_rollback)
|
||||||
|
return PTR_ERR(folio);
|
||||||
|
if (count != cnt)
|
||||||
|
pos = __ntfs_bitmap_set_bits_in_run(vi, start_bit, count - cnt,
|
||||||
|
value ? 0 : 1, true);
|
||||||
|
else
|
||||||
|
pos = 0;
|
||||||
|
if (!pos) {
|
||||||
|
/* Rollback was successful. */
|
||||||
|
ntfs_error(vi->i_sb,
|
||||||
|
"Failed to map subsequent page (error %li), aborting.",
|
||||||
|
PTR_ERR(folio));
|
||||||
|
} else {
|
||||||
|
/* Rollback failed. */
|
||||||
|
ntfs_error(vi->i_sb,
|
||||||
|
"Failed to map subsequent page (error %li) and rollback failed (error %i). Aborting and leaving inconsistent metadata. Unmount and run chkdsk.",
|
||||||
|
PTR_ERR(folio), pos);
|
||||||
|
NVolSetErrors(NTFS_SB(vi->i_sb));
|
||||||
|
}
|
||||||
|
return PTR_ERR(folio);
|
||||||
|
}
|
||||||
100
fs/ntfs/bitmap.h
Normal file
100
fs/ntfs/bitmap.h
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
/*
|
||||||
|
* Defines for NTFS kernel bitmap handling.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2004 Anton Altaparmakov
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _LINUX_NTFS_BITMAP_H
|
||||||
|
#define _LINUX_NTFS_BITMAP_H
|
||||||
|
|
||||||
|
#include <linux/fs.h>
|
||||||
|
|
||||||
|
#include "volume.h"
|
||||||
|
|
||||||
|
int ntfs_trim_fs(struct ntfs_volume *vol, struct fstrim_range *range);
|
||||||
|
int __ntfs_bitmap_set_bits_in_run(struct inode *vi, const s64 start_bit,
|
||||||
|
const s64 count, const u8 value, const bool is_rollback);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_bitmap_set_bits_in_run - set a run of bits in a bitmap to a value
|
||||||
|
* @vi: vfs inode describing the bitmap
|
||||||
|
* @start_bit: first bit to set
|
||||||
|
* @count: number of bits to set
|
||||||
|
* @value: value to set the bits to (i.e. 0 or 1)
|
||||||
|
*
|
||||||
|
* Set @count bits starting at bit @start_bit in the bitmap described by the
|
||||||
|
* vfs inode @vi to @value, where @value is either 0 or 1.
|
||||||
|
*
|
||||||
|
* Return 0 on success and -errno on error.
|
||||||
|
*/
|
||||||
|
static inline int ntfs_bitmap_set_bits_in_run(struct inode *vi,
|
||||||
|
const s64 start_bit, const s64 count, const u8 value)
|
||||||
|
{
|
||||||
|
return __ntfs_bitmap_set_bits_in_run(vi, start_bit, count, value,
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_bitmap_set_run - set a run of bits in a bitmap
|
||||||
|
* @vi: vfs inode describing the bitmap
|
||||||
|
* @start_bit: first bit to set
|
||||||
|
* @count: number of bits to set
|
||||||
|
*
|
||||||
|
* Set @count bits starting at bit @start_bit in the bitmap described by the
|
||||||
|
* vfs inode @vi.
|
||||||
|
*
|
||||||
|
* Return 0 on success and -errno on error.
|
||||||
|
*/
|
||||||
|
static inline int ntfs_bitmap_set_run(struct inode *vi, const s64 start_bit,
|
||||||
|
const s64 count)
|
||||||
|
{
|
||||||
|
return ntfs_bitmap_set_bits_in_run(vi, start_bit, count, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_bitmap_clear_run - clear a run of bits in a bitmap
|
||||||
|
* @vi: vfs inode describing the bitmap
|
||||||
|
* @start_bit: first bit to clear
|
||||||
|
* @count: number of bits to clear
|
||||||
|
*
|
||||||
|
* Clear @count bits starting at bit @start_bit in the bitmap described by the
|
||||||
|
* vfs inode @vi.
|
||||||
|
*
|
||||||
|
* Return 0 on success and -errno on error.
|
||||||
|
*/
|
||||||
|
static inline int ntfs_bitmap_clear_run(struct inode *vi, const s64 start_bit,
|
||||||
|
const s64 count)
|
||||||
|
{
|
||||||
|
return ntfs_bitmap_set_bits_in_run(vi, start_bit, count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_bitmap_set_bit - set a bit in a bitmap
|
||||||
|
* @vi: vfs inode describing the bitmap
|
||||||
|
* @bit: bit to set
|
||||||
|
*
|
||||||
|
* Set bit @bit in the bitmap described by the vfs inode @vi.
|
||||||
|
*
|
||||||
|
* Return 0 on success and -errno on error.
|
||||||
|
*/
|
||||||
|
static inline int ntfs_bitmap_set_bit(struct inode *vi, const s64 bit)
|
||||||
|
{
|
||||||
|
return ntfs_bitmap_set_run(vi, bit, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_bitmap_clear_bit - clear a bit in a bitmap
|
||||||
|
* @vi: vfs inode describing the bitmap
|
||||||
|
* @bit: bit to clear
|
||||||
|
*
|
||||||
|
* Clear bit @bit in the bitmap described by the vfs inode @vi.
|
||||||
|
*
|
||||||
|
* Return 0 on success and -errno on error.
|
||||||
|
*/
|
||||||
|
static inline int ntfs_bitmap_clear_bit(struct inode *vi, const s64 bit)
|
||||||
|
{
|
||||||
|
return ntfs_bitmap_clear_run(vi, bit, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* defined _LINUX_NTFS_BITMAP_H */
|
||||||
146
fs/ntfs/collate.c
Normal file
146
fs/ntfs/collate.c
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* NTFS kernel collation handling.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2004 Anton Altaparmakov
|
||||||
|
*
|
||||||
|
* Part of this file is based on code from the NTFS-3G.
|
||||||
|
* and is copyrighted by the respective authors below:
|
||||||
|
* Copyright (c) 2004 Anton Altaparmakov
|
||||||
|
* Copyright (c) 2005 Yura Pakhuchiy
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "collate.h"
|
||||||
|
#include "debug.h"
|
||||||
|
#include "ntfs.h"
|
||||||
|
|
||||||
|
#include <linux/sort.h>
|
||||||
|
|
||||||
|
static int ntfs_collate_binary(struct ntfs_volume *vol,
|
||||||
|
const void *data1, const u32 data1_len,
|
||||||
|
const void *data2, const u32 data2_len)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
rc = memcmp(data1, data2, min(data1_len, data2_len));
|
||||||
|
if (!rc && (data1_len != data2_len)) {
|
||||||
|
if (data1_len < data2_len)
|
||||||
|
rc = -1;
|
||||||
|
else
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ntfs_collate_ntofs_ulong(struct ntfs_volume *vol,
|
||||||
|
const void *data1, const u32 data1_len,
|
||||||
|
const void *data2, const u32 data2_len)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
u32 d1 = le32_to_cpup(data1), d2 = le32_to_cpup(data2);
|
||||||
|
|
||||||
|
if (data1_len != data2_len || data1_len != 4)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (d1 < d2)
|
||||||
|
rc = -1;
|
||||||
|
else {
|
||||||
|
if (d1 == d2)
|
||||||
|
rc = 0;
|
||||||
|
else
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_collate_ntofs_ulongs - Which of two le32 arrays should be listed first
|
||||||
|
* @vol: ntfs volume
|
||||||
|
* @data1: first ulong array to collate
|
||||||
|
* @data1_len: length in bytes of @data1
|
||||||
|
* @data2: second ulong array to collate
|
||||||
|
* @data2_len: length in bytes of @data2
|
||||||
|
*
|
||||||
|
* Returns: -1, 0 or 1 depending of how the arrays compare
|
||||||
|
*/
|
||||||
|
static int ntfs_collate_ntofs_ulongs(struct ntfs_volume *vol,
|
||||||
|
const void *data1, const u32 data1_len,
|
||||||
|
const void *data2, const u32 data2_len)
|
||||||
|
{
|
||||||
|
int len;
|
||||||
|
const __le32 *p1 = data1, *p2 = data2;
|
||||||
|
u32 d1, d2;
|
||||||
|
|
||||||
|
if (data1_len != data2_len || data1_len & 3) {
|
||||||
|
ntfs_error(vol->sb, "data1_len or data2_len not valid\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
len = data1_len;
|
||||||
|
do {
|
||||||
|
d1 = le32_to_cpup(p1);
|
||||||
|
p1++;
|
||||||
|
d2 = le32_to_cpup(p2);
|
||||||
|
p2++;
|
||||||
|
} while (d1 == d2 && (len -= 4) > 0);
|
||||||
|
return cmp_int(d1, d2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_collate_file_name - Which of two filenames should be listed first
|
||||||
|
* @vol: ntfs volume
|
||||||
|
* @data1: first filename to collate
|
||||||
|
* @data1_len: length in bytes of @data1(unused)
|
||||||
|
* @data2: second filename to collate
|
||||||
|
* @data2_len: length in bytes of @data2(unused)
|
||||||
|
*/
|
||||||
|
static int ntfs_collate_file_name(struct ntfs_volume *vol,
|
||||||
|
const void *data1, const u32 data1_len,
|
||||||
|
const void *data2, const u32 data2_len)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
rc = ntfs_file_compare_values(data1, data2, -EINVAL,
|
||||||
|
IGNORE_CASE, vol->upcase, vol->upcase_len);
|
||||||
|
if (!rc)
|
||||||
|
rc = ntfs_file_compare_values(data1, data2,
|
||||||
|
-EINVAL, CASE_SENSITIVE, vol->upcase, vol->upcase_len);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_collate - collate two data items using a specified collation rule
|
||||||
|
* @vol: ntfs volume to which the data items belong
|
||||||
|
* @cr: collation rule to use when comparing the items
|
||||||
|
* @data1: first data item to collate
|
||||||
|
* @data1_len: length in bytes of @data1
|
||||||
|
* @data2: second data item to collate
|
||||||
|
* @data2_len: length in bytes of @data2
|
||||||
|
*
|
||||||
|
* Collate the two data items @data1 and @data2 using the collation rule @cr
|
||||||
|
* and return -1, 0, ir 1 if @data1 is found, respectively, to collate before,
|
||||||
|
* to match, or to collate after @data2. return -EINVAL if an error occurred.
|
||||||
|
*/
|
||||||
|
int ntfs_collate(struct ntfs_volume *vol, __le32 cr,
|
||||||
|
const void *data1, const u32 data1_len,
|
||||||
|
const void *data2, const u32 data2_len)
|
||||||
|
{
|
||||||
|
switch (le32_to_cpu(cr)) {
|
||||||
|
case le32_to_cpu(COLLATION_BINARY):
|
||||||
|
return ntfs_collate_binary(vol, data1, data1_len,
|
||||||
|
data2, data2_len);
|
||||||
|
case le32_to_cpu(COLLATION_FILE_NAME):
|
||||||
|
return ntfs_collate_file_name(vol, data1, data1_len,
|
||||||
|
data2, data2_len);
|
||||||
|
case le32_to_cpu(COLLATION_NTOFS_ULONG):
|
||||||
|
return ntfs_collate_ntofs_ulong(vol, data1, data1_len,
|
||||||
|
data2, data2_len);
|
||||||
|
case le32_to_cpu(COLLATION_NTOFS_ULONGS):
|
||||||
|
return ntfs_collate_ntofs_ulongs(vol, data1, data1_len,
|
||||||
|
data2, data2_len);
|
||||||
|
default:
|
||||||
|
ntfs_error(vol->sb, "Unknown collation rule 0x%x",
|
||||||
|
le32_to_cpu(cr));
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
36
fs/ntfs/collate.h
Normal file
36
fs/ntfs/collate.h
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
/*
|
||||||
|
* Defines for NTFS kernel collation handling.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2004 Anton Altaparmakov
|
||||||
|
*
|
||||||
|
* Part of this file is based on code from the NTFS-3G.
|
||||||
|
* and is copyrighted by the respective authors below:
|
||||||
|
* Copyright (c) 2004 Anton Altaparmakov
|
||||||
|
* Copyright (c) 2005 Yura Pakhuchiy
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _LINUX_NTFS_COLLATE_H
|
||||||
|
#define _LINUX_NTFS_COLLATE_H
|
||||||
|
|
||||||
|
#include "volume.h"
|
||||||
|
|
||||||
|
static inline bool ntfs_is_collation_rule_supported(__le32 cr)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (unlikely(cr != COLLATION_BINARY && cr != COLLATION_NTOFS_ULONG &&
|
||||||
|
cr != COLLATION_FILE_NAME) && cr != COLLATION_NTOFS_ULONGS)
|
||||||
|
return false;
|
||||||
|
i = le32_to_cpu(cr);
|
||||||
|
if (likely(((i >= 0) && (i <= 0x02)) ||
|
||||||
|
((i >= 0x10) && (i <= 0x13))))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ntfs_collate(struct ntfs_volume *vol, __le32 cr,
|
||||||
|
const void *data1, const u32 data1_len,
|
||||||
|
const void *data2, const u32 data2_len);
|
||||||
|
|
||||||
|
#endif /* _LINUX_NTFS_COLLATE_H */
|
||||||
1577
fs/ntfs/compress.c
Normal file
1577
fs/ntfs/compress.c
Normal file
File diff suppressed because it is too large
Load Diff
171
fs/ntfs/debug.c
Normal file
171
fs/ntfs/debug.c
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* NTFS kernel debug support.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2001-2004 Anton Altaparmakov
|
||||||
|
*/
|
||||||
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||||
|
#include "debug.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* __ntfs_warning - output a warning to the syslog
|
||||||
|
* @function: name of function outputting the warning
|
||||||
|
* @sb: super block of mounted ntfs filesystem
|
||||||
|
* @fmt: warning string containing format specifications
|
||||||
|
* @...: a variable number of arguments specified in @fmt
|
||||||
|
*
|
||||||
|
* Outputs a warning to the syslog for the mounted ntfs filesystem described
|
||||||
|
* by @sb.
|
||||||
|
*
|
||||||
|
* @fmt and the corresponding @... is printf style format string containing
|
||||||
|
* the warning string and the corresponding format arguments, respectively.
|
||||||
|
*
|
||||||
|
* @function is the name of the function from which __ntfs_warning is being
|
||||||
|
* called.
|
||||||
|
*
|
||||||
|
* Note, you should be using debug.h::ntfs_warning(@sb, @fmt, @...) instead
|
||||||
|
* as this provides the @function parameter automatically.
|
||||||
|
*/
|
||||||
|
void __ntfs_warning(const char *function, const struct super_block *sb,
|
||||||
|
const char *fmt, ...)
|
||||||
|
{
|
||||||
|
struct va_format vaf;
|
||||||
|
va_list args;
|
||||||
|
int flen = 0;
|
||||||
|
|
||||||
|
if (function)
|
||||||
|
flen = strlen(function);
|
||||||
|
va_start(args, fmt);
|
||||||
|
vaf.fmt = fmt;
|
||||||
|
vaf.va = &args;
|
||||||
|
#ifdef DEBUG
|
||||||
|
if (sb)
|
||||||
|
pr_warn("(device %s): %s(): %pV\n",
|
||||||
|
sb->s_id, flen ? function : "", &vaf);
|
||||||
|
else
|
||||||
|
pr_warn("%s(): %pV\n", flen ? function : "", &vaf);
|
||||||
|
#else
|
||||||
|
if (sb)
|
||||||
|
pr_warn_ratelimited("(device %s): %s(): %pV\n",
|
||||||
|
sb->s_id, flen ? function : "", &vaf);
|
||||||
|
else
|
||||||
|
pr_warn_ratelimited("%s(): %pV\n", flen ? function : "", &vaf);
|
||||||
|
#endif
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* __ntfs_error - output an error to the syslog
|
||||||
|
* @function: name of function outputting the error
|
||||||
|
* @sb: super block of mounted ntfs filesystem
|
||||||
|
* @fmt: error string containing format specifications
|
||||||
|
* @...: a variable number of arguments specified in @fmt
|
||||||
|
*
|
||||||
|
* Outputs an error to the syslog for the mounted ntfs filesystem described
|
||||||
|
* by @sb.
|
||||||
|
*
|
||||||
|
* @fmt and the corresponding @... is printf style format string containing
|
||||||
|
* the error string and the corresponding format arguments, respectively.
|
||||||
|
*
|
||||||
|
* @function is the name of the function from which __ntfs_error is being
|
||||||
|
* called.
|
||||||
|
*
|
||||||
|
* Note, you should be using debug.h::ntfs_error(@sb, @fmt, @...) instead
|
||||||
|
* as this provides the @function parameter automatically.
|
||||||
|
*/
|
||||||
|
void __ntfs_error(const char *function, struct super_block *sb,
|
||||||
|
const char *fmt, ...)
|
||||||
|
{
|
||||||
|
struct va_format vaf;
|
||||||
|
va_list args;
|
||||||
|
int flen = 0;
|
||||||
|
|
||||||
|
if (function)
|
||||||
|
flen = strlen(function);
|
||||||
|
va_start(args, fmt);
|
||||||
|
vaf.fmt = fmt;
|
||||||
|
vaf.va = &args;
|
||||||
|
#ifdef DEBUG
|
||||||
|
if (sb)
|
||||||
|
pr_err("(device %s): %s(): %pV\n",
|
||||||
|
sb->s_id, flen ? function : "", &vaf);
|
||||||
|
else
|
||||||
|
pr_err("%s(): %pV\n", flen ? function : "", &vaf);
|
||||||
|
#else
|
||||||
|
if (sb)
|
||||||
|
pr_err_ratelimited("(device %s): %s(): %pV\n",
|
||||||
|
sb->s_id, flen ? function : "", &vaf);
|
||||||
|
else
|
||||||
|
pr_err_ratelimited("%s(): %pV\n", flen ? function : "", &vaf);
|
||||||
|
#endif
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
if (sb)
|
||||||
|
ntfs_handle_error(sb);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
|
||||||
|
/* If 1, output debug messages, and if 0, don't. */
|
||||||
|
int debug_msgs;
|
||||||
|
|
||||||
|
void __ntfs_debug(const char *file, int line, const char *function,
|
||||||
|
const char *fmt, ...)
|
||||||
|
{
|
||||||
|
struct va_format vaf;
|
||||||
|
va_list args;
|
||||||
|
int flen = 0;
|
||||||
|
|
||||||
|
if (!debug_msgs)
|
||||||
|
return;
|
||||||
|
if (function)
|
||||||
|
flen = strlen(function);
|
||||||
|
va_start(args, fmt);
|
||||||
|
vaf.fmt = fmt;
|
||||||
|
vaf.va = &args;
|
||||||
|
pr_debug("(%s, %d): %s(): %pV", file, line, flen ? function : "", &vaf);
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dump a runlist. Caller has to provide synchronisation for @rl. */
|
||||||
|
void ntfs_debug_dump_runlist(const struct runlist_element *rl)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
const char *lcn_str[5] = { "LCN_DELALLOC ", "LCN_HOLE ",
|
||||||
|
"LCN_RL_NOT_MAPPED", "LCN_ENOENT ",
|
||||||
|
"LCN_unknown " };
|
||||||
|
|
||||||
|
if (!debug_msgs)
|
||||||
|
return;
|
||||||
|
pr_debug("Dumping runlist (values in hex):\n");
|
||||||
|
if (!rl) {
|
||||||
|
pr_debug("Run list not present.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pr_debug("VCN LCN Run length\n");
|
||||||
|
for (i = 0; ; i++) {
|
||||||
|
s64 lcn = (rl + i)->lcn;
|
||||||
|
|
||||||
|
if (lcn < 0) {
|
||||||
|
int index = -lcn - 1;
|
||||||
|
|
||||||
|
if (index > -LCN_ENOENT - 1)
|
||||||
|
index = 3;
|
||||||
|
pr_debug("%-16Lx %s %-16Lx%s\n",
|
||||||
|
(long long)(rl + i)->vcn, lcn_str[index],
|
||||||
|
(long long)(rl + i)->length,
|
||||||
|
(rl + i)->length ? "" :
|
||||||
|
" (runlist end)");
|
||||||
|
} else
|
||||||
|
pr_debug("%-16Lx %-16Lx %-16Lx%s\n",
|
||||||
|
(long long)(rl + i)->vcn,
|
||||||
|
(long long)(rl + i)->lcn,
|
||||||
|
(long long)(rl + i)->length,
|
||||||
|
(rl + i)->length ? "" :
|
||||||
|
" (runlist end)");
|
||||||
|
if (!(rl + i)->length)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
63
fs/ntfs/debug.h
Normal file
63
fs/ntfs/debug.h
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
/*
|
||||||
|
* NTFS kernel debug support.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2001-2004 Anton Altaparmakov
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _LINUX_NTFS_DEBUG_H
|
||||||
|
#define _LINUX_NTFS_DEBUG_H
|
||||||
|
|
||||||
|
#include <linux/fs.h>
|
||||||
|
|
||||||
|
#include "runlist.h"
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
|
||||||
|
extern int debug_msgs;
|
||||||
|
|
||||||
|
extern __printf(4, 5)
|
||||||
|
void __ntfs_debug(const char *file, int line, const char *function,
|
||||||
|
const char *format, ...);
|
||||||
|
/*
|
||||||
|
* ntfs_debug - write a debug level message to syslog
|
||||||
|
* @f: a printf format string containing the message
|
||||||
|
* @...: the variables to substitute into @f
|
||||||
|
*
|
||||||
|
* ntfs_debug() writes a DEBUG level message to the syslog but only if the
|
||||||
|
* driver was compiled with -DDEBUG. Otherwise, the call turns into a NOP.
|
||||||
|
*/
|
||||||
|
#define ntfs_debug(f, a...) \
|
||||||
|
__ntfs_debug(__FILE__, __LINE__, __func__, f, ##a)
|
||||||
|
|
||||||
|
void ntfs_debug_dump_runlist(const struct runlist_element *rl);
|
||||||
|
|
||||||
|
#else /* !DEBUG */
|
||||||
|
|
||||||
|
#define ntfs_debug(fmt, ...) \
|
||||||
|
do { \
|
||||||
|
if (0) \
|
||||||
|
no_printk(fmt, ##__VA_ARGS__); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define ntfs_debug_dump_runlist(rl) \
|
||||||
|
do { \
|
||||||
|
if (0) \
|
||||||
|
(void)rl; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#endif /* !DEBUG */
|
||||||
|
|
||||||
|
extern __printf(3, 4)
|
||||||
|
void __ntfs_warning(const char *function, const struct super_block *sb,
|
||||||
|
const char *fmt, ...);
|
||||||
|
#define ntfs_warning(sb, f, a...) __ntfs_warning(__func__, sb, f, ##a)
|
||||||
|
|
||||||
|
extern __printf(3, 4)
|
||||||
|
void __ntfs_error(const char *function, struct super_block *sb,
|
||||||
|
const char *fmt, ...);
|
||||||
|
#define ntfs_error(sb, f, a...) __ntfs_error(__func__, sb, f, ##a)
|
||||||
|
|
||||||
|
void ntfs_handle_error(struct super_block *sb);
|
||||||
|
|
||||||
|
#endif /* _LINUX_NTFS_DEBUG_H */
|
||||||
1238
fs/ntfs/dir.c
Normal file
1238
fs/ntfs/dir.c
Normal file
File diff suppressed because it is too large
Load Diff
32
fs/ntfs/dir.h
Normal file
32
fs/ntfs/dir.h
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
/*
|
||||||
|
* Defines for directory handling in NTFS Linux kernel driver.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2002-2004 Anton Altaparmakov
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _LINUX_NTFS_DIR_H
|
||||||
|
#define _LINUX_NTFS_DIR_H
|
||||||
|
|
||||||
|
#include "inode.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_name is used to return the file name to the caller of
|
||||||
|
* ntfs_lookup_inode_by_name() in order for the caller (namei.c::ntfs_lookup())
|
||||||
|
* to be able to deal with dcache aliasing issues.
|
||||||
|
*/
|
||||||
|
struct ntfs_name {
|
||||||
|
u64 mref;
|
||||||
|
u8 type;
|
||||||
|
u8 len;
|
||||||
|
__le16 name[];
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
/* The little endian Unicode string $I30 as a global constant. */
|
||||||
|
extern __le16 I30[5];
|
||||||
|
|
||||||
|
u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni,
|
||||||
|
const __le16 *uname, const int uname_len, struct ntfs_name **res);
|
||||||
|
int ntfs_check_empty_dir(struct ntfs_inode *ni, struct mft_record *ni_mrec);
|
||||||
|
|
||||||
|
#endif /* _LINUX_NTFS_FS_DIR_H */
|
||||||
954
fs/ntfs/ea.c
Normal file
954
fs/ntfs/ea.c
Normal file
@@ -0,0 +1,954 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* Pocessing of EA's
|
||||||
|
*
|
||||||
|
* Part of this file is based on code from the NTFS-3G.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014-2021 Jean-Pierre Andre
|
||||||
|
* Copyright (c) 2025 LG Electronics Co., Ltd.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/fs.h>
|
||||||
|
#include <linux/posix_acl.h>
|
||||||
|
#include <linux/posix_acl_xattr.h>
|
||||||
|
#include <linux/xattr.h>
|
||||||
|
|
||||||
|
#include "layout.h"
|
||||||
|
#include "attrib.h"
|
||||||
|
#include "index.h"
|
||||||
|
#include "dir.h"
|
||||||
|
#include "ea.h"
|
||||||
|
|
||||||
|
static int ntfs_write_ea(struct ntfs_inode *ni, __le32 type, char *value, s64 ea_off,
|
||||||
|
s64 ea_size, bool need_truncate)
|
||||||
|
{
|
||||||
|
struct inode *ea_vi;
|
||||||
|
int err = 0;
|
||||||
|
s64 written;
|
||||||
|
|
||||||
|
ea_vi = ntfs_attr_iget(VFS_I(ni), type, AT_UNNAMED, 0);
|
||||||
|
if (IS_ERR(ea_vi))
|
||||||
|
return PTR_ERR(ea_vi);
|
||||||
|
|
||||||
|
written = ntfs_inode_attr_pwrite(ea_vi, ea_off, ea_size, value, false);
|
||||||
|
if (written != ea_size)
|
||||||
|
err = -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);
|
||||||
|
mark_mft_record_dirty(ni);
|
||||||
|
}
|
||||||
|
|
||||||
|
iput(ea_vi);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ntfs_ea_lookup(char *ea_buf, s64 ea_buf_size, const char *name,
|
||||||
|
int name_len, s64 *ea_offset, s64 *ea_size)
|
||||||
|
{
|
||||||
|
const struct ea_attr *p_ea;
|
||||||
|
size_t actual_size;
|
||||||
|
loff_t offset, p_ea_size;
|
||||||
|
unsigned int next;
|
||||||
|
|
||||||
|
if (ea_buf_size < sizeof(struct ea_attr))
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
offset = 0;
|
||||||
|
do {
|
||||||
|
p_ea = (const struct ea_attr *)&ea_buf[offset];
|
||||||
|
next = le32_to_cpu(p_ea->next_entry_offset);
|
||||||
|
p_ea_size = next ? next : (ea_buf_size - offset);
|
||||||
|
|
||||||
|
if (p_ea_size < sizeof(struct ea_attr) ||
|
||||||
|
offset + p_ea_size > ea_buf_size)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if ((s64)p_ea->ea_name_length + 1 >
|
||||||
|
p_ea_size - offsetof(struct ea_attr, ea_name))
|
||||||
|
break;
|
||||||
|
|
||||||
|
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 (p_ea->ea_name_length == name_len &&
|
||||||
|
!memcmp(p_ea->ea_name, name, name_len)) {
|
||||||
|
*ea_offset = offset;
|
||||||
|
*ea_size = next ? next : actual_size;
|
||||||
|
|
||||||
|
if (ea_buf_size < *ea_offset + *ea_size)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
offset += next;
|
||||||
|
} while (next > 0 && offset < ea_buf_size);
|
||||||
|
|
||||||
|
out:
|
||||||
|
return -ENOENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the existing EA
|
||||||
|
*
|
||||||
|
* The EA_INFORMATION is not examined and the consistency of the
|
||||||
|
* existing EA is not checked.
|
||||||
|
*
|
||||||
|
* If successful, the full attribute is returned unchanged
|
||||||
|
* and its size is returned.
|
||||||
|
* If the designated buffer is too small, the needed size is
|
||||||
|
* returned, and the buffer is left unchanged.
|
||||||
|
* If there is an error, a negative value is returned and errno
|
||||||
|
* is set according to the error.
|
||||||
|
*/
|
||||||
|
static int ntfs_get_ea(struct inode *inode, const char *name, size_t name_len,
|
||||||
|
void *buffer, size_t size)
|
||||||
|
{
|
||||||
|
struct ntfs_inode *ni = NTFS_I(inode);
|
||||||
|
const struct ea_attr *p_ea;
|
||||||
|
char *ea_buf;
|
||||||
|
s64 ea_off, ea_size, all_ea_size, ea_info_size;
|
||||||
|
int err;
|
||||||
|
u32 ea_info_qlen;
|
||||||
|
u16 ea_value_len;
|
||||||
|
struct ea_information *p_ea_info;
|
||||||
|
|
||||||
|
if (!NInoHasEA(ni))
|
||||||
|
return -ENODATA;
|
||||||
|
|
||||||
|
p_ea_info = ntfs_attr_readall(ni, AT_EA_INFORMATION, NULL, 0,
|
||||||
|
&ea_info_size);
|
||||||
|
if (!p_ea_info || ea_info_size != sizeof(struct ea_information)) {
|
||||||
|
kvfree(p_ea_info);
|
||||||
|
return -ENODATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
ea_info_qlen = le32_to_cpu(p_ea_info->ea_query_length);
|
||||||
|
kvfree(p_ea_info);
|
||||||
|
|
||||||
|
ea_buf = ntfs_attr_readall(ni, AT_EA, NULL, 0, &all_ea_size);
|
||||||
|
if (!ea_buf)
|
||||||
|
return -ENODATA;
|
||||||
|
|
||||||
|
if (ea_info_qlen > all_ea_size) {
|
||||||
|
err = -EIO;
|
||||||
|
goto free_ea_buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ntfs_ea_lookup(ea_buf, ea_info_qlen, name, name_len, &ea_off,
|
||||||
|
&ea_size);
|
||||||
|
if (!err) {
|
||||||
|
p_ea = (struct ea_attr *)&ea_buf[ea_off];
|
||||||
|
ea_value_len = le16_to_cpu(p_ea->ea_value_length);
|
||||||
|
if (!buffer) {
|
||||||
|
kvfree(ea_buf);
|
||||||
|
return ea_value_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ea_value_len > size) {
|
||||||
|
err = -ERANGE;
|
||||||
|
goto free_ea_buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(buffer, &p_ea->ea_name[p_ea->ea_name_length + 1],
|
||||||
|
ea_value_len);
|
||||||
|
kvfree(ea_buf);
|
||||||
|
return ea_value_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = -ENODATA;
|
||||||
|
free_ea_buf:
|
||||||
|
kvfree(ea_buf);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int ea_packed_size(const struct ea_attr *p_ea)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* 4 bytes for header (flags and lengths) + name length + 1 +
|
||||||
|
* value length.
|
||||||
|
*/
|
||||||
|
return 5 + p_ea->ea_name_length + le16_to_cpu(p_ea->ea_value_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
size_t new_ea_size = ALIGN(struct_size(p_ea, ea_name, 1 + name_len + val_size), 4);
|
||||||
|
s64 ea_off, ea_info_size, all_ea_size, ea_size;
|
||||||
|
|
||||||
|
if (name_len > 255)
|
||||||
|
return -ENAMETOOLONG;
|
||||||
|
|
||||||
|
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 (!p_ea_info || ea_info_size != sizeof(struct ea_information))
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
ea_buf = ntfs_attr_readall(ni, AT_EA, NULL, 0, &all_ea_size);
|
||||||
|
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(sizeof(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;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ntfs_ea_lookup(ea_buf, ea_info_qsize, name, name_len, &ea_off,
|
||||||
|
&ea_size);
|
||||||
|
if (ea_info_qsize && !err) {
|
||||||
|
if (flags & XATTR_CREATE) {
|
||||||
|
err = -EEXIST;
|
||||||
|
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);
|
||||||
|
|
||||||
|
err = ntfs_write_ea(ni, AT_EA_INFORMATION, (char *)p_ea_info, 0,
|
||||||
|
sizeof(struct ea_information), false);
|
||||||
|
if (err)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
err = ntfs_write_ea(ni, AT_EA, ea_buf, 0, ea_info_qsize, true);
|
||||||
|
if (err)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
if ((flags & XATTR_REPLACE) && !val_size) {
|
||||||
|
/* Remove xattr. */
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (flags & XATTR_REPLACE) {
|
||||||
|
err = -ENODATA;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
kvfree(ea_buf);
|
||||||
|
|
||||||
|
alloc_new_ea:
|
||||||
|
ea_buf = kzalloc(new_ea_size, GFP_NOFS);
|
||||||
|
if (!ea_buf) {
|
||||||
|
err = -ENOMEM;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* EA and REPARSE_POINT compatibility not checked any more,
|
||||||
|
* required by Windows 10, but having both may lead to
|
||||||
|
* problems with earlier versions.
|
||||||
|
*/
|
||||||
|
p_ea = (struct ea_attr *)ea_buf;
|
||||||
|
memcpy(p_ea->ea_name, name, name_len);
|
||||||
|
p_ea->ea_name_length = name_len;
|
||||||
|
p_ea->ea_name[name_len] = 0;
|
||||||
|
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 ||
|
||||||
|
ntfs_attr_size_bounds_check(ni->vol, AT_EA, new_ea_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, (char *)p_ea,
|
||||||
|
new_ea_size);
|
||||||
|
if (err)
|
||||||
|
goto out;
|
||||||
|
} else {
|
||||||
|
err = ntfs_write_ea(ni, AT_EA, (char *)p_ea, ea_info_qsize,
|
||||||
|
new_ea_size, false);
|
||||||
|
if (err)
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ntfs_write_ea(ni, AT_EA_INFORMATION, (char *)p_ea_info, 0,
|
||||||
|
sizeof(struct ea_information), false);
|
||||||
|
if (err)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
if (packed_ea_size)
|
||||||
|
*packed_ea_size = p_ea_info->ea_length;
|
||||||
|
mark_mft_record_dirty(ni);
|
||||||
|
out:
|
||||||
|
if (ea_info_qsize > 0)
|
||||||
|
NInoSetHasEA(ni);
|
||||||
|
else
|
||||||
|
NInoClearHasEA(ni);
|
||||||
|
|
||||||
|
kvfree(ea_buf);
|
||||||
|
kvfree(p_ea_info);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check for the presence of an EA "$LXDEV" (used by WSL)
|
||||||
|
* and return its value as a device address
|
||||||
|
*/
|
||||||
|
int ntfs_ea_get_wsl_inode(struct inode *inode, dev_t *rdevp, unsigned int flags)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
__le32 v;
|
||||||
|
|
||||||
|
if (!(flags & NTFS_VOL_UID)) {
|
||||||
|
/* Load uid to lxuid EA */
|
||||||
|
err = ntfs_get_ea(inode, "$LXUID", sizeof("$LXUID") - 1, &v,
|
||||||
|
sizeof(v));
|
||||||
|
if (err < 0)
|
||||||
|
return err;
|
||||||
|
if (err != sizeof(v))
|
||||||
|
return -EIO;
|
||||||
|
i_uid_write(inode, le32_to_cpu(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(flags & NTFS_VOL_GID)) {
|
||||||
|
/* Load gid to lxgid EA */
|
||||||
|
err = ntfs_get_ea(inode, "$LXGID", sizeof("$LXGID") - 1, &v,
|
||||||
|
sizeof(v));
|
||||||
|
if (err < 0)
|
||||||
|
return err;
|
||||||
|
if (err != sizeof(v))
|
||||||
|
return -EIO;
|
||||||
|
i_gid_write(inode, le32_to_cpu(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Load mode to lxmod EA */
|
||||||
|
err = ntfs_get_ea(inode, "$LXMOD", sizeof("$LXMOD") - 1, &v, sizeof(v));
|
||||||
|
if (err == sizeof(v)) {
|
||||||
|
inode->i_mode = le32_to_cpu(v);
|
||||||
|
} else {
|
||||||
|
/* Everyone gets all permissions. */
|
||||||
|
inode->i_mode |= 0777;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Load mode to lxdev EA */
|
||||||
|
err = ntfs_get_ea(inode, "$LXDEV", sizeof("$LXDEV") - 1, &v, sizeof(v));
|
||||||
|
if (err == sizeof(v))
|
||||||
|
*rdevp = le32_to_cpu(v);
|
||||||
|
err = 0;
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ntfs_ea_set_wsl_inode(struct inode *inode, dev_t rdev, __le16 *ea_size,
|
||||||
|
unsigned int flags)
|
||||||
|
{
|
||||||
|
__le32 v;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (flags & NTFS_EA_UID) {
|
||||||
|
/* Store uid to lxuid EA */
|
||||||
|
v = cpu_to_le32(i_uid_read(inode));
|
||||||
|
err = ntfs_set_ea(inode, "$LXUID", sizeof("$LXUID") - 1, &v,
|
||||||
|
sizeof(v), 0, ea_size);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags & NTFS_EA_GID) {
|
||||||
|
/* Store gid to lxgid EA */
|
||||||
|
v = cpu_to_le32(i_gid_read(inode));
|
||||||
|
err = ntfs_set_ea(inode, "$LXGID", sizeof("$LXGID") - 1, &v,
|
||||||
|
sizeof(v), 0, ea_size);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags & NTFS_EA_MODE) {
|
||||||
|
/* Store mode to lxmod EA */
|
||||||
|
v = cpu_to_le32(inode->i_mode);
|
||||||
|
err = ntfs_set_ea(inode, "$LXMOD", sizeof("$LXMOD") - 1, &v,
|
||||||
|
sizeof(v), 0, ea_size);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rdev) {
|
||||||
|
v = cpu_to_le32(rdev);
|
||||||
|
err = ntfs_set_ea(inode, "$LXDEV", sizeof("$LXDEV") - 1, &v, sizeof(v),
|
||||||
|
0, ea_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t ntfs_listxattr(struct dentry *dentry, char *buffer, size_t size)
|
||||||
|
{
|
||||||
|
struct inode *inode = d_inode(dentry);
|
||||||
|
struct ntfs_inode *ni = NTFS_I(inode);
|
||||||
|
const struct ea_attr *p_ea;
|
||||||
|
s64 offset, ea_buf_size, ea_info_size;
|
||||||
|
s64 ea_size;
|
||||||
|
u32 next;
|
||||||
|
int err = 0;
|
||||||
|
u32 ea_info_qsize;
|
||||||
|
char *ea_buf = NULL;
|
||||||
|
ssize_t ret = 0;
|
||||||
|
struct ea_information *ea_info;
|
||||||
|
|
||||||
|
if (!NInoHasEA(ni))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
mutex_lock(&NTFS_I(inode)->mrec_lock);
|
||||||
|
ea_info = ntfs_attr_readall(ni, AT_EA_INFORMATION, NULL, 0,
|
||||||
|
&ea_info_size);
|
||||||
|
if (!ea_info || ea_info_size != sizeof(struct ea_information))
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
ea_info_qsize = le32_to_cpu(ea_info->ea_query_length);
|
||||||
|
|
||||||
|
ea_buf = ntfs_attr_readall(ni, AT_EA, NULL, 0, &ea_buf_size);
|
||||||
|
if (!ea_buf)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
if (ea_info_qsize > ea_buf_size || ea_info_qsize == 0)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
if (ea_info_qsize < sizeof(struct ea_attr)) {
|
||||||
|
err = -EIO;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset = 0;
|
||||||
|
do {
|
||||||
|
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;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(buffer + ret, p_ea->ea_name, p_ea->ea_name_length);
|
||||||
|
buffer[ret + p_ea->ea_name_length] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret += p_ea->ea_name_length + 1;
|
||||||
|
offset += ea_size;
|
||||||
|
} while (next > 0 && offset < ea_info_qsize);
|
||||||
|
|
||||||
|
out:
|
||||||
|
mutex_unlock(&NTFS_I(inode)->mrec_lock);
|
||||||
|
kvfree(ea_info);
|
||||||
|
kvfree(ea_buf);
|
||||||
|
|
||||||
|
return err ? err : ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
#define SYSTEM_DOS_ATTRIB "system.dos_attrib"
|
||||||
|
#define SYSTEM_NTFS_ATTRIB "system.ntfs_attrib"
|
||||||
|
#define SYSTEM_NTFS_ATTRIB_BE "system.ntfs_attrib_be"
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
static int ntfs_getxattr(const struct xattr_handler *handler,
|
||||||
|
struct dentry *unused, struct inode *inode, const char *name,
|
||||||
|
void *buffer, size_t size)
|
||||||
|
{
|
||||||
|
struct ntfs_inode *ni = NTFS_I(inode);
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (NVolShutdown(ni->vol))
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
if (!strcmp(name, SYSTEM_DOS_ATTRIB)) {
|
||||||
|
if (!buffer) {
|
||||||
|
err = sizeof(u8);
|
||||||
|
} else if (size < sizeof(u8)) {
|
||||||
|
err = -ENODATA;
|
||||||
|
} else {
|
||||||
|
err = sizeof(u8);
|
||||||
|
*(u8 *)buffer = (u8)(le32_to_cpu(ni->flags) & 0x3F);
|
||||||
|
}
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(name, SYSTEM_NTFS_ATTRIB) ||
|
||||||
|
!strcmp(name, SYSTEM_NTFS_ATTRIB_BE)) {
|
||||||
|
if (!buffer) {
|
||||||
|
err = sizeof(u32);
|
||||||
|
} else if (size < sizeof(u32)) {
|
||||||
|
err = -ENODATA;
|
||||||
|
} else {
|
||||||
|
err = sizeof(u32);
|
||||||
|
*(u32 *)buffer = le32_to_cpu(ni->flags);
|
||||||
|
if (!strcmp(name, SYSTEM_NTFS_ATTRIB_BE))
|
||||||
|
*(__be32 *)buffer = cpu_to_be32(*(u32 *)buffer);
|
||||||
|
}
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_lock(&ni->mrec_lock);
|
||||||
|
err = ntfs_get_ea(inode, name, strlen(name), buffer, size);
|
||||||
|
mutex_unlock(&ni->mrec_lock);
|
||||||
|
|
||||||
|
out:
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ntfs_new_attr_flags(struct ntfs_inode *ni, __le32 fattr)
|
||||||
|
{
|
||||||
|
struct ntfs_attr_search_ctx *ctx;
|
||||||
|
struct mft_record *m;
|
||||||
|
struct attr_record *a;
|
||||||
|
__le16 new_aflags;
|
||||||
|
int mp_size, mp_ofs, name_ofs, arec_size, err;
|
||||||
|
|
||||||
|
m = map_mft_record(ni);
|
||||||
|
if (IS_ERR(m))
|
||||||
|
return PTR_ERR(m);
|
||||||
|
|
||||||
|
ctx = ntfs_attr_get_search_ctx(ni, m);
|
||||||
|
if (!ctx) {
|
||||||
|
err = -ENOMEM;
|
||||||
|
goto err_out;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len,
|
||||||
|
CASE_SENSITIVE, 0, NULL, 0, ctx);
|
||||||
|
if (err) {
|
||||||
|
err = -EINVAL;
|
||||||
|
goto err_out;
|
||||||
|
}
|
||||||
|
|
||||||
|
a = ctx->attr;
|
||||||
|
new_aflags = ctx->attr->flags;
|
||||||
|
|
||||||
|
if (fattr & FILE_ATTR_SPARSE_FILE)
|
||||||
|
new_aflags |= ATTR_IS_SPARSE;
|
||||||
|
else
|
||||||
|
new_aflags &= ~ATTR_IS_SPARSE;
|
||||||
|
|
||||||
|
if (fattr & FILE_ATTR_COMPRESSED)
|
||||||
|
new_aflags |= ATTR_IS_COMPRESSED;
|
||||||
|
else
|
||||||
|
new_aflags &= ~ATTR_IS_COMPRESSED;
|
||||||
|
|
||||||
|
if (new_aflags == a->flags)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if ((new_aflags & (ATTR_IS_SPARSE | ATTR_IS_COMPRESSED)) ==
|
||||||
|
(ATTR_IS_SPARSE | ATTR_IS_COMPRESSED)) {
|
||||||
|
pr_err("file can't be sparsed and compressed\n");
|
||||||
|
err = -EOPNOTSUPP;
|
||||||
|
goto err_out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!a->non_resident)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
if (a->data.non_resident.data_size) {
|
||||||
|
pr_err("Can't change sparsed/compressed for non-empty file\n");
|
||||||
|
err = -EOPNOTSUPP;
|
||||||
|
goto err_out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_aflags & (ATTR_IS_SPARSE | ATTR_IS_COMPRESSED))
|
||||||
|
name_ofs = (offsetof(struct attr_record,
|
||||||
|
data.non_resident.compressed_size) +
|
||||||
|
sizeof(a->data.non_resident.compressed_size) + 7) & ~7;
|
||||||
|
else
|
||||||
|
name_ofs = (offsetof(struct attr_record,
|
||||||
|
data.non_resident.compressed_size) + 7) & ~7;
|
||||||
|
|
||||||
|
mp_size = ntfs_get_size_for_mapping_pairs(ni->vol, ni->runlist.rl, 0, -1, -1);
|
||||||
|
if (unlikely(mp_size < 0)) {
|
||||||
|
err = mp_size;
|
||||||
|
ntfs_debug("Failed to get size for mapping pairs array, error code %i.\n", err);
|
||||||
|
goto err_out;
|
||||||
|
}
|
||||||
|
|
||||||
|
mp_ofs = (name_ofs + a->name_length * sizeof(__le16) + 7) & ~7;
|
||||||
|
arec_size = (mp_ofs + mp_size + 7) & ~7;
|
||||||
|
|
||||||
|
err = ntfs_attr_record_resize(m, a, arec_size);
|
||||||
|
if (unlikely(err))
|
||||||
|
goto err_out;
|
||||||
|
|
||||||
|
if (new_aflags & (ATTR_IS_SPARSE | ATTR_IS_COMPRESSED)) {
|
||||||
|
a->data.non_resident.compression_unit = 0;
|
||||||
|
if (new_aflags & ATTR_IS_COMPRESSED || ni->vol->major_ver < 3)
|
||||||
|
a->data.non_resident.compression_unit = 4;
|
||||||
|
a->data.non_resident.compressed_size = 0;
|
||||||
|
ni->itype.compressed.size = 0;
|
||||||
|
if (a->data.non_resident.compression_unit) {
|
||||||
|
ni->itype.compressed.block_size = 1U <<
|
||||||
|
(a->data.non_resident.compression_unit +
|
||||||
|
ni->vol->cluster_size_bits);
|
||||||
|
ni->itype.compressed.block_size_bits =
|
||||||
|
ffs(ni->itype.compressed.block_size) -
|
||||||
|
1;
|
||||||
|
ni->itype.compressed.block_clusters = 1U <<
|
||||||
|
a->data.non_resident.compression_unit;
|
||||||
|
} else {
|
||||||
|
ni->itype.compressed.block_size = 0;
|
||||||
|
ni->itype.compressed.block_size_bits = 0;
|
||||||
|
ni->itype.compressed.block_clusters = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_aflags & ATTR_IS_SPARSE) {
|
||||||
|
NInoSetSparse(ni);
|
||||||
|
ni->flags |= FILE_ATTR_SPARSE_FILE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_aflags & ATTR_IS_COMPRESSED) {
|
||||||
|
NInoSetCompressed(ni);
|
||||||
|
ni->flags |= FILE_ATTR_COMPRESSED;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ni->flags &= ~(FILE_ATTR_SPARSE_FILE | FILE_ATTR_COMPRESSED);
|
||||||
|
a->data.non_resident.compression_unit = 0;
|
||||||
|
NInoClearSparse(ni);
|
||||||
|
NInoClearCompressed(ni);
|
||||||
|
}
|
||||||
|
|
||||||
|
a->name_offset = cpu_to_le16(name_ofs);
|
||||||
|
a->data.non_resident.mapping_pairs_offset = cpu_to_le16(mp_ofs);
|
||||||
|
|
||||||
|
out:
|
||||||
|
a->flags = new_aflags;
|
||||||
|
mark_mft_record_dirty(ctx->ntfs_ino);
|
||||||
|
err_out:
|
||||||
|
if (ctx)
|
||||||
|
ntfs_attr_put_search_ctx(ctx);
|
||||||
|
unmap_mft_record(ni);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ntfs_setxattr(const struct xattr_handler *handler,
|
||||||
|
struct mnt_idmap *idmap, struct dentry *unused,
|
||||||
|
struct inode *inode, const char *name, const void *value,
|
||||||
|
size_t size, int flags)
|
||||||
|
{
|
||||||
|
struct ntfs_inode *ni = NTFS_I(inode);
|
||||||
|
int err;
|
||||||
|
__le32 fattr;
|
||||||
|
|
||||||
|
if (NVolShutdown(ni->vol))
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
if (!strcmp(name, SYSTEM_DOS_ATTRIB)) {
|
||||||
|
if (sizeof(u8) != size) {
|
||||||
|
err = -EINVAL;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
fattr = cpu_to_le32(*(u8 *)value);
|
||||||
|
goto set_fattr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(name, SYSTEM_NTFS_ATTRIB) ||
|
||||||
|
!strcmp(name, SYSTEM_NTFS_ATTRIB_BE)) {
|
||||||
|
if (size != sizeof(u32)) {
|
||||||
|
err = -EINVAL;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if (!strcmp(name, SYSTEM_NTFS_ATTRIB_BE))
|
||||||
|
fattr = cpu_to_le32(be32_to_cpu(*(__be32 *)value));
|
||||||
|
else
|
||||||
|
fattr = cpu_to_le32(*(u32 *)value);
|
||||||
|
|
||||||
|
if (S_ISREG(inode->i_mode)) {
|
||||||
|
mutex_lock(&ni->mrec_lock);
|
||||||
|
err = ntfs_new_attr_flags(ni, fattr);
|
||||||
|
mutex_unlock(&ni->mrec_lock);
|
||||||
|
if (err)
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_fattr:
|
||||||
|
if (S_ISDIR(inode->i_mode))
|
||||||
|
fattr |= FILE_ATTR_DIRECTORY;
|
||||||
|
else
|
||||||
|
fattr &= ~FILE_ATTR_DIRECTORY;
|
||||||
|
|
||||||
|
if (ni->flags != fattr) {
|
||||||
|
ni->flags = fattr;
|
||||||
|
if (fattr & FILE_ATTR_READONLY)
|
||||||
|
inode->i_mode &= ~0222;
|
||||||
|
else
|
||||||
|
inode->i_mode |= 0222;
|
||||||
|
NInoSetFileNameDirty(ni);
|
||||||
|
mark_inode_dirty(inode);
|
||||||
|
}
|
||||||
|
err = 0;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_lock(&ni->mrec_lock);
|
||||||
|
err = ntfs_set_ea(inode, name, strlen(name), value, size, flags, NULL);
|
||||||
|
mutex_unlock(&ni->mrec_lock);
|
||||||
|
|
||||||
|
out:
|
||||||
|
inode_set_ctime_current(inode);
|
||||||
|
mark_inode_dirty(inode);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ntfs_xattr_user_list(struct dentry *dentry)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
static const struct xattr_handler ntfs_other_xattr_handler = {
|
||||||
|
.prefix = "",
|
||||||
|
.get = ntfs_getxattr,
|
||||||
|
.set = ntfs_setxattr,
|
||||||
|
.list = ntfs_xattr_user_list,
|
||||||
|
};
|
||||||
|
|
||||||
|
const struct xattr_handler * const ntfs_xattr_handlers[] = {
|
||||||
|
&ntfs_other_xattr_handler,
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
#ifdef CONFIG_NTFS_FS_POSIX_ACL
|
||||||
|
struct posix_acl *ntfs_get_acl(struct mnt_idmap *idmap, struct dentry *dentry,
|
||||||
|
int type)
|
||||||
|
{
|
||||||
|
struct inode *inode = d_inode(dentry);
|
||||||
|
struct ntfs_inode *ni = NTFS_I(inode);
|
||||||
|
const char *name;
|
||||||
|
size_t name_len;
|
||||||
|
struct posix_acl *acl;
|
||||||
|
int err;
|
||||||
|
void *buf;
|
||||||
|
|
||||||
|
buf = kmalloc(PATH_MAX, GFP_KERNEL);
|
||||||
|
if (!buf)
|
||||||
|
return ERR_PTR(-ENOMEM);
|
||||||
|
|
||||||
|
/* Possible values of 'type' was already checked above. */
|
||||||
|
if (type == ACL_TYPE_ACCESS) {
|
||||||
|
name = XATTR_NAME_POSIX_ACL_ACCESS;
|
||||||
|
name_len = sizeof(XATTR_NAME_POSIX_ACL_ACCESS) - 1;
|
||||||
|
} else {
|
||||||
|
name = XATTR_NAME_POSIX_ACL_DEFAULT;
|
||||||
|
name_len = sizeof(XATTR_NAME_POSIX_ACL_DEFAULT) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_lock(&ni->mrec_lock);
|
||||||
|
err = ntfs_get_ea(inode, name, name_len, buf, PATH_MAX);
|
||||||
|
mutex_unlock(&ni->mrec_lock);
|
||||||
|
|
||||||
|
/* Translate extended attribute to acl. */
|
||||||
|
if (err >= 0)
|
||||||
|
acl = posix_acl_from_xattr(&init_user_ns, buf, err);
|
||||||
|
else if (err == -ENODATA)
|
||||||
|
acl = NULL;
|
||||||
|
else
|
||||||
|
acl = ERR_PTR(err);
|
||||||
|
|
||||||
|
if (!IS_ERR(acl))
|
||||||
|
set_cached_acl(inode, type, acl);
|
||||||
|
|
||||||
|
kfree(buf);
|
||||||
|
|
||||||
|
return acl;
|
||||||
|
}
|
||||||
|
|
||||||
|
static noinline int ntfs_set_acl_ex(struct mnt_idmap *idmap,
|
||||||
|
struct inode *inode, struct posix_acl *acl,
|
||||||
|
int type, bool init_acl)
|
||||||
|
{
|
||||||
|
const char *name;
|
||||||
|
size_t size, name_len;
|
||||||
|
void *value;
|
||||||
|
int err;
|
||||||
|
int flags;
|
||||||
|
umode_t mode;
|
||||||
|
|
||||||
|
if (S_ISLNK(inode->i_mode))
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
|
||||||
|
mode = inode->i_mode;
|
||||||
|
switch (type) {
|
||||||
|
case ACL_TYPE_ACCESS:
|
||||||
|
/* Do not change i_mode if we are in init_acl */
|
||||||
|
if (acl && !init_acl) {
|
||||||
|
err = posix_acl_update_mode(idmap, inode, &mode, &acl);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
name = XATTR_NAME_POSIX_ACL_ACCESS;
|
||||||
|
name_len = sizeof(XATTR_NAME_POSIX_ACL_ACCESS) - 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ACL_TYPE_DEFAULT:
|
||||||
|
if (!S_ISDIR(inode->i_mode))
|
||||||
|
return acl ? -EACCES : 0;
|
||||||
|
name = XATTR_NAME_POSIX_ACL_DEFAULT;
|
||||||
|
name_len = sizeof(XATTR_NAME_POSIX_ACL_DEFAULT) - 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!acl) {
|
||||||
|
/* Remove xattr if it can be presented via mode. */
|
||||||
|
size = 0;
|
||||||
|
value = NULL;
|
||||||
|
flags = XATTR_REPLACE;
|
||||||
|
} else {
|
||||||
|
value = posix_acl_to_xattr(&init_user_ns, acl, &size, GFP_NOFS);
|
||||||
|
if (!value)
|
||||||
|
return -ENOMEM;
|
||||||
|
flags = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_lock(&NTFS_I(inode)->mrec_lock);
|
||||||
|
err = ntfs_set_ea(inode, name, name_len, value, size, flags, NULL);
|
||||||
|
mutex_unlock(&NTFS_I(inode)->mrec_lock);
|
||||||
|
if (err == -ENODATA && !size)
|
||||||
|
err = 0; /* Removing non existed xattr. */
|
||||||
|
if (!err) {
|
||||||
|
__le16 ea_size = 0;
|
||||||
|
umode_t old_mode = inode->i_mode;
|
||||||
|
|
||||||
|
inode->i_mode = mode;
|
||||||
|
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);
|
||||||
|
mutex_unlock(&NTFS_I(inode)->mrec_lock);
|
||||||
|
inode->i_mode = old_mode;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
mutex_unlock(&NTFS_I(inode)->mrec_lock);
|
||||||
|
|
||||||
|
set_cached_acl(inode, type, acl);
|
||||||
|
inode_set_ctime_current(inode);
|
||||||
|
mark_inode_dirty(inode);
|
||||||
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
kfree(value);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ntfs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,
|
||||||
|
struct posix_acl *acl, int type)
|
||||||
|
{
|
||||||
|
return ntfs_set_acl_ex(idmap, d_inode(dentry), acl, type, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ntfs_init_acl(struct mnt_idmap *idmap, struct inode *inode,
|
||||||
|
struct inode *dir)
|
||||||
|
{
|
||||||
|
struct posix_acl *default_acl, *acl;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = posix_acl_create(dir, &inode->i_mode, &default_acl, &acl);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
if (default_acl) {
|
||||||
|
err = ntfs_set_acl_ex(idmap, inode, default_acl,
|
||||||
|
ACL_TYPE_DEFAULT, true);
|
||||||
|
posix_acl_release(default_acl);
|
||||||
|
} else {
|
||||||
|
inode->i_default_acl = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (acl) {
|
||||||
|
if (!err)
|
||||||
|
err = ntfs_set_acl_ex(idmap, inode, acl,
|
||||||
|
ACL_TYPE_ACCESS, true);
|
||||||
|
posix_acl_release(acl);
|
||||||
|
} else {
|
||||||
|
inode->i_acl = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
30
fs/ntfs/ea.h
Normal file
30
fs/ntfs/ea.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
|
||||||
|
#ifndef _LINUX_NTFS_EA_H
|
||||||
|
#define _LINUX_NTFS_EA_H
|
||||||
|
|
||||||
|
#define NTFS_EA_UID BIT(1)
|
||||||
|
#define NTFS_EA_GID BIT(2)
|
||||||
|
#define NTFS_EA_MODE BIT(3)
|
||||||
|
|
||||||
|
extern const struct xattr_handler *const ntfs_xattr_handlers[];
|
||||||
|
|
||||||
|
int ntfs_ea_set_wsl_not_symlink(struct ntfs_inode *ni, mode_t mode, dev_t dev);
|
||||||
|
int ntfs_ea_get_wsl_inode(struct inode *inode, dev_t *rdevp, unsigned int flags);
|
||||||
|
int ntfs_ea_set_wsl_inode(struct inode *inode, dev_t rdev, __le16 *ea_size,
|
||||||
|
unsigned int flags);
|
||||||
|
ssize_t ntfs_listxattr(struct dentry *dentry, char *buffer, size_t size);
|
||||||
|
|
||||||
|
#ifdef CONFIG_NTFS_FS_POSIX_ACL
|
||||||
|
struct posix_acl *ntfs_get_acl(struct mnt_idmap *idmap, struct dentry *dentry,
|
||||||
|
int type);
|
||||||
|
int ntfs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,
|
||||||
|
struct posix_acl *acl, int type);
|
||||||
|
int ntfs_init_acl(struct mnt_idmap *idmap, struct inode *inode,
|
||||||
|
struct inode *dir);
|
||||||
|
#else
|
||||||
|
#define ntfs_get_acl NULL
|
||||||
|
#define ntfs_set_acl NULL
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* _LINUX_NTFS_EA_H */
|
||||||
1161
fs/ntfs/file.c
Normal file
1161
fs/ntfs/file.c
Normal file
File diff suppressed because it is too large
Load Diff
2117
fs/ntfs/index.c
Normal file
2117
fs/ntfs/index.c
Normal file
File diff suppressed because it is too large
Load Diff
111
fs/ntfs/index.h
Normal file
111
fs/ntfs/index.h
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
/*
|
||||||
|
* Defines for NTFS kernel index handling.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2004 Anton Altaparmakov
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _LINUX_NTFS_INDEX_H
|
||||||
|
#define _LINUX_NTFS_INDEX_H
|
||||||
|
|
||||||
|
#include <linux/fs.h>
|
||||||
|
|
||||||
|
#include "attrib.h"
|
||||||
|
#include "mft.h"
|
||||||
|
|
||||||
|
#define VCN_INDEX_ROOT_PARENT ((s64)-2)
|
||||||
|
|
||||||
|
#define MAX_PARENT_VCN 32
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @idx_ni: index inode containing the @entry described by this context
|
||||||
|
* @name: Unicode name of the indexed attribute
|
||||||
|
* (usually $I30 for directories)
|
||||||
|
* @name_len: length of @name in Unicode characters
|
||||||
|
* @entry: index entry (points into @ir or @ia)
|
||||||
|
* @cr: creation time of the entry (for sorting/validation)
|
||||||
|
* @data: index entry data (points into @entry)
|
||||||
|
* @data_len: length in bytes of @data
|
||||||
|
* @is_in_root: 'true' if @entry is in @ir and 'false' if it is in @ia
|
||||||
|
* @ir: index root if @is_in_root and NULL otherwise
|
||||||
|
* @actx: attribute search context if @is_in_root and NULL otherwise
|
||||||
|
* @ib: index block header (valid when @is_in_root is 'false')
|
||||||
|
* @ia_ni: index allocation inode (extent inode) for @ia
|
||||||
|
* @parent_pos: array of parent entry positions in the B-tree nodes
|
||||||
|
* @parent_vcn: VCNs of parent index blocks in the B-tree traversal
|
||||||
|
* @pindex: current depth (number of parent nodes) in the traversal
|
||||||
|
* (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)
|
||||||
|
* @sync_write: true if synchronous writeback is requested for this context
|
||||||
|
*
|
||||||
|
* @idx_ni is the index inode this context belongs to.
|
||||||
|
*
|
||||||
|
* @entry is the index entry described by this context. @data and @data_len
|
||||||
|
* are the index entry data and its length in bytes, respectively. @data
|
||||||
|
* simply points into @entry. This is probably what the user is interested in.
|
||||||
|
*
|
||||||
|
* If @is_in_root is 'true', @entry is in the index root attribute @ir described
|
||||||
|
* by the attribute search context @actx and the base inode @base_ni. @ia and
|
||||||
|
* @page are NULL in this case.
|
||||||
|
*
|
||||||
|
* If @is_in_root is 'false', @entry is in the index allocation attribute and @ia
|
||||||
|
* and @page point to the index allocation block and the mapped, locked page it
|
||||||
|
* is in, respectively. @ir, @actx and @base_ni are NULL in this case.
|
||||||
|
*
|
||||||
|
* To obtain a context call ntfs_index_ctx_get().
|
||||||
|
*
|
||||||
|
* We use this context to allow ntfs_index_lookup() to return the found index
|
||||||
|
* @entry and its @data without having to allocate a buffer and copy the @entry
|
||||||
|
* and/or its @data into it.
|
||||||
|
*
|
||||||
|
* When finished with the @entry and its @data, call ntfs_index_ctx_put() to
|
||||||
|
* free the context and other associated resources.
|
||||||
|
*
|
||||||
|
* If the index entry was modified, ntfs_index_entry_mark_dirty()
|
||||||
|
* or ntfs_index_entry_write() before the call to ntfs_index_ctx_put() to
|
||||||
|
* ensure that the changes are written to disk.
|
||||||
|
*/
|
||||||
|
struct ntfs_index_context {
|
||||||
|
struct ntfs_inode *idx_ni;
|
||||||
|
__le16 *name;
|
||||||
|
u32 name_len;
|
||||||
|
struct index_entry *entry;
|
||||||
|
__le32 cr;
|
||||||
|
void *data;
|
||||||
|
u16 data_len;
|
||||||
|
bool is_in_root;
|
||||||
|
struct index_root *ir;
|
||||||
|
struct ntfs_attr_search_ctx *actx;
|
||||||
|
struct index_block *ib;
|
||||||
|
struct ntfs_inode *ia_ni;
|
||||||
|
int parent_pos[MAX_PARENT_VCN];
|
||||||
|
s64 parent_vcn[MAX_PARENT_VCN];
|
||||||
|
int pindex;
|
||||||
|
bool ib_dirty;
|
||||||
|
u32 block_size;
|
||||||
|
u8 vcn_size_bits;
|
||||||
|
bool sync_write;
|
||||||
|
};
|
||||||
|
|
||||||
|
int ntfs_index_entry_inconsistent(struct ntfs_index_context *icx, struct ntfs_volume *vol,
|
||||||
|
const struct index_entry *ie, __le32 collation_rule, u64 inum);
|
||||||
|
struct ntfs_index_context *ntfs_index_ctx_get(struct ntfs_inode *ni, __le16 *name,
|
||||||
|
u32 name_len);
|
||||||
|
void ntfs_index_ctx_put(struct ntfs_index_context *ictx);
|
||||||
|
int ntfs_index_lookup(const void *key, const u32 key_len,
|
||||||
|
struct ntfs_index_context *ictx);
|
||||||
|
|
||||||
|
void ntfs_index_entry_mark_dirty(struct ntfs_index_context *ictx);
|
||||||
|
int ntfs_index_add_filename(struct ntfs_inode *ni, struct file_name_attr *fn, u64 mref);
|
||||||
|
int ntfs_index_remove(struct ntfs_inode *ni, const void *key, const u32 keylen);
|
||||||
|
struct ntfs_inode *ntfs_ia_open(struct ntfs_index_context *icx, struct ntfs_inode *ni);
|
||||||
|
struct index_entry *ntfs_index_walk_down(struct index_entry *ie, struct ntfs_index_context *ictx);
|
||||||
|
struct index_entry *ntfs_index_next(struct index_entry *ie, struct ntfs_index_context *ictx);
|
||||||
|
int ntfs_index_rm(struct ntfs_index_context *icx);
|
||||||
|
void ntfs_index_ctx_reinit(struct ntfs_index_context *icx);
|
||||||
|
int ntfs_ie_add(struct ntfs_index_context *icx, struct index_entry *ie);
|
||||||
|
int ntfs_icx_ib_sync_write(struct ntfs_index_context *icx);
|
||||||
|
|
||||||
|
#endif /* _LINUX_NTFS_INDEX_H */
|
||||||
3787
fs/ntfs/inode.c
Normal file
3787
fs/ntfs/inode.c
Normal file
File diff suppressed because it is too large
Load Diff
358
fs/ntfs/inode.h
Normal file
358
fs/ntfs/inode.h
Normal file
@@ -0,0 +1,358 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
/*
|
||||||
|
* Defines for inode structures NTFS Linux kernel driver.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2001-2007 Anton Altaparmakov
|
||||||
|
* Copyright (c) 2002 Richard Russon
|
||||||
|
* Copyright (c) 2025 LG Electronics Co., Ltd.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _LINUX_NTFS_INODE_H
|
||||||
|
#define _LINUX_NTFS_INODE_H
|
||||||
|
|
||||||
|
#include "debug.h"
|
||||||
|
|
||||||
|
enum ntfs_inode_mutex_lock_class {
|
||||||
|
NTFS_INODE_MUTEX_PARENT,
|
||||||
|
NTFS_INODE_MUTEX_NORMAL,
|
||||||
|
NTFS_INODE_MUTEX_NORMAL_CHILD,
|
||||||
|
NTFS_INODE_MUTEX_PARENT_2,
|
||||||
|
NTFS_INODE_MUTEX_NORMAL_2,
|
||||||
|
NTFS_EXTEND_MUTEX_PARENT,
|
||||||
|
NTFS_EA_MUTEX_NORMAL
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The NTFS in-memory inode structure. It is just used as an extension to the
|
||||||
|
* fields already provided in the VFS inode.
|
||||||
|
* @size_lock: Lock serializing access to inode sizes.
|
||||||
|
* @state: NTFS specific flags describing this inode.
|
||||||
|
* @flags: Flags describing the file. (Copy from STANDARD_INFORMATION).
|
||||||
|
* @mft_no: Number of the mft record / inode.
|
||||||
|
* @seq_no: Sequence number of the mft record.
|
||||||
|
* @count: Inode reference count for book keeping.
|
||||||
|
* @vol: Pointer to the ntfs volume of this inode.
|
||||||
|
*
|
||||||
|
* If NInoAttr() is true, the below fields describe the attribute which
|
||||||
|
* this fake inode belongs to. The actual inode of this attribute is
|
||||||
|
* pointed to by base_ntfs_ino and nr_extents is always set to -1 (see
|
||||||
|
* below). For real inodes, we also set the type (AT_DATA for files and
|
||||||
|
* AT_INDEX_ALLOCATION for directories), with the name = NULL and
|
||||||
|
* name_len = 0 for files and name = I30 (global constant) and
|
||||||
|
* name_len = 4 for directories.
|
||||||
|
* @type: Attribute type of this fake inode.
|
||||||
|
* @name: Attribute name of this fake inode.
|
||||||
|
* @name_len: Attribute name length of this fake inode.
|
||||||
|
* @runlist: If state has the NI_NonResident bit set, the runlist of
|
||||||
|
* the unnamed data attribute (if a file) or of the index allocation
|
||||||
|
* attribute (directory) or of the attribute described by the fake inode
|
||||||
|
* (if NInoAttr()). If runlist.rl is NULL, the runlist has not been read
|
||||||
|
* in yet or has been unmapped. If NI_NonResident is clear, the attribute
|
||||||
|
* is resident (file and fake inode) or there is no $I30 index allocation
|
||||||
|
* attribute (small directory). In the latter case runlist.rl is always
|
||||||
|
* NULL.
|
||||||
|
* @data_size: Copy from the attribute record.
|
||||||
|
* @initialized_size: Copy from the attribute record.
|
||||||
|
* @allocated_size: Copy from the attribute record.
|
||||||
|
* @i_crtime: File Creation time.
|
||||||
|
* @mrec: MFT record
|
||||||
|
* @mrec_lock: Lock for serializing access to the mft record belonging to
|
||||||
|
* this inode.
|
||||||
|
* @folio: The folio containing the mft record of the inode.
|
||||||
|
* @folio_ofs: Offset into the folio at which the mft record begins.
|
||||||
|
* @mft_lcn: Number containing the mft record.
|
||||||
|
* @mft_lcn_count: Number of clusters per mft record.
|
||||||
|
*
|
||||||
|
* Attribute list support (only for use by the attribute lookup
|
||||||
|
* functions). Setup during read_inode for all inodes with attribute
|
||||||
|
* lists. Only valid if NI_AttrList is set in state.
|
||||||
|
* @attr_list_size: Length of attribute list value in bytes.
|
||||||
|
* @attr_list: Attribute list value itself.
|
||||||
|
*
|
||||||
|
* It is a directory, $MFT, or an index inode.
|
||||||
|
* @block_size: Size of an index block.
|
||||||
|
* @vcn_size: Size of a vcn in this index.
|
||||||
|
* @collation_rule: The collation rule for the index.
|
||||||
|
* @block_size_bits: Log2 of the above.
|
||||||
|
* @vcn_size_bits: Log2 of the above.
|
||||||
|
*
|
||||||
|
* It is a compressed/sparse file/attribute inode.
|
||||||
|
* @size: Copy of compressed_size from $DATA.
|
||||||
|
* @block_size: Size of a compression block (cb).
|
||||||
|
* @block_size_bits: Log2 of the size of a cb.
|
||||||
|
* @block_clusters: Number of clusters per cb.
|
||||||
|
* @extent_lock: Lock for accessing/modifying the below.
|
||||||
|
* @nr_extents: For a base mft record, the number of attached extent inodes
|
||||||
|
* (0 if none), for extent records and for fake inodes describing an
|
||||||
|
* attribute this is -1.
|
||||||
|
*
|
||||||
|
* This union is only used if nr_extents != 0.
|
||||||
|
* @extent_ntfs_inos: For nr_extents > 0, array of the ntfs inodes of
|
||||||
|
* the extent mft records belonging to this base inode which have been
|
||||||
|
* loaded.
|
||||||
|
* @base_ntfs_ino: For nr_extents == -1, the ntfs inode of the base mft
|
||||||
|
* record. For fake inodes, the real (base) inode to which the attribute
|
||||||
|
* belongs.
|
||||||
|
* @i_dealloc_clusters: delayed allocated clusters.
|
||||||
|
* @target: symlink buffer.
|
||||||
|
*/
|
||||||
|
struct ntfs_inode {
|
||||||
|
rwlock_t size_lock;
|
||||||
|
unsigned long state;
|
||||||
|
__le32 flags;
|
||||||
|
u64 mft_no;
|
||||||
|
u16 seq_no;
|
||||||
|
atomic_t count;
|
||||||
|
struct ntfs_volume *vol;
|
||||||
|
__le32 type;
|
||||||
|
__le16 *name;
|
||||||
|
u32 name_len;
|
||||||
|
struct runlist runlist;
|
||||||
|
s64 data_size;
|
||||||
|
s64 initialized_size;
|
||||||
|
s64 allocated_size;
|
||||||
|
struct timespec64 i_crtime;
|
||||||
|
void *mrec;
|
||||||
|
struct mutex mrec_lock;
|
||||||
|
struct folio *folio;
|
||||||
|
int folio_ofs;
|
||||||
|
s64 mft_lcn[2];
|
||||||
|
unsigned int mft_lcn_count;
|
||||||
|
u32 attr_list_size;
|
||||||
|
u8 *attr_list;
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
u32 block_size;
|
||||||
|
u32 vcn_size;
|
||||||
|
__le32 collation_rule;
|
||||||
|
u8 block_size_bits;
|
||||||
|
u8 vcn_size_bits;
|
||||||
|
} index;
|
||||||
|
struct {
|
||||||
|
s64 size;
|
||||||
|
u32 block_size;
|
||||||
|
u8 block_size_bits;
|
||||||
|
u8 block_clusters;
|
||||||
|
} compressed;
|
||||||
|
} itype;
|
||||||
|
struct mutex extent_lock;
|
||||||
|
s32 nr_extents;
|
||||||
|
union {
|
||||||
|
struct ntfs_inode **extent_ntfs_inos;
|
||||||
|
struct ntfs_inode *base_ntfs_ino;
|
||||||
|
} ext;
|
||||||
|
unsigned int i_dealloc_clusters;
|
||||||
|
char *target;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Defined bits for the state field in the ntfs_inode structure.
|
||||||
|
* (f) = files only, (d) = directories only, (a) = attributes/fake inodes only
|
||||||
|
*
|
||||||
|
* NI_Dirty Mft record needs to be written to disk.
|
||||||
|
* NI_AttrListDirty Mft record contains an attribute list.
|
||||||
|
* NI_AttrList Mft record contains an attribute list.
|
||||||
|
* NI_AttrListNonResident Attribute list is non-resident. Implies
|
||||||
|
* NI_AttrList is set.
|
||||||
|
* NI_Attr 1: Fake inode for attribute i/o.
|
||||||
|
* 0: Real inode or extent inode.
|
||||||
|
* NI_MstProtected Attribute is protected by MST fixups.
|
||||||
|
* NI_NonResident Unnamed data attr is non-resident (f)
|
||||||
|
* Attribute is non-resident (a).
|
||||||
|
* NI_IndexAllocPresent $I30 index alloc attr is present (d).
|
||||||
|
* NI_Compressed Unnamed data attr is compressed (f).
|
||||||
|
* Create compressed files by default (d).
|
||||||
|
* Attribute is compressed (a).
|
||||||
|
* NI_Encrypted Unnamed data attr is encrypted (f).
|
||||||
|
* Create encrypted files by default (d).
|
||||||
|
* Attribute is encrypted (a).
|
||||||
|
* NI_Sparse Unnamed data attr is sparse (f).
|
||||||
|
* Create sparse files by default (d).
|
||||||
|
* Attribute is sparse (a).
|
||||||
|
* NI_SparseDisabled May not create sparse regions.
|
||||||
|
* NI_FullyMapped Runlist is fully mapped.
|
||||||
|
* NI_FileNameDirty FILE_NAME attributes need to be updated.
|
||||||
|
* NI_BeingDeleted ntfs inode is being delated.
|
||||||
|
* NI_BeingCreated ntfs inode is being created.
|
||||||
|
* NI_HasEA ntfs inode has EA attribute.
|
||||||
|
* NI_RunlistDirty runlist need to be updated.
|
||||||
|
*/
|
||||||
|
enum {
|
||||||
|
NI_Dirty,
|
||||||
|
NI_AttrListDirty,
|
||||||
|
NI_AttrList,
|
||||||
|
NI_AttrListNonResident,
|
||||||
|
NI_Attr,
|
||||||
|
NI_MstProtected,
|
||||||
|
NI_NonResident,
|
||||||
|
NI_IndexAllocPresent,
|
||||||
|
NI_Compressed,
|
||||||
|
NI_Encrypted,
|
||||||
|
NI_Sparse,
|
||||||
|
NI_SparseDisabled,
|
||||||
|
NI_FullyMapped,
|
||||||
|
NI_FileNameDirty,
|
||||||
|
NI_BeingDeleted,
|
||||||
|
NI_BeingCreated,
|
||||||
|
NI_HasEA,
|
||||||
|
NI_RunlistDirty,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NOTE: We should be adding dirty mft records to a list somewhere and they
|
||||||
|
* should be independent of the (ntfs/vfs) inode structure so that an inode can
|
||||||
|
* be removed but the record can be left dirty for syncing later.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Macro tricks to expand the NInoFoo(), NInoSetFoo(), and NInoClearFoo()
|
||||||
|
* functions.
|
||||||
|
*/
|
||||||
|
#define NINO_FNS(flag) \
|
||||||
|
static inline int NIno##flag(struct ntfs_inode *ni) \
|
||||||
|
{ \
|
||||||
|
return test_bit(NI_##flag, &(ni)->state); \
|
||||||
|
} \
|
||||||
|
static inline void NInoSet##flag(struct ntfs_inode *ni) \
|
||||||
|
{ \
|
||||||
|
set_bit(NI_##flag, &(ni)->state); \
|
||||||
|
} \
|
||||||
|
static inline void NInoClear##flag(struct ntfs_inode *ni) \
|
||||||
|
{ \
|
||||||
|
clear_bit(NI_##flag, &(ni)->state); \
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* As above for NInoTestSetFoo() and NInoTestClearFoo().
|
||||||
|
*/
|
||||||
|
#define TAS_NINO_FNS(flag) \
|
||||||
|
static inline int NInoTestSet##flag(struct ntfs_inode *ni) \
|
||||||
|
{ \
|
||||||
|
return test_and_set_bit(NI_##flag, &(ni)->state); \
|
||||||
|
} \
|
||||||
|
static inline int NInoTestClear##flag(struct ntfs_inode *ni) \
|
||||||
|
{ \
|
||||||
|
return test_and_clear_bit(NI_##flag, &(ni)->state); \
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Emit the ntfs inode bitops functions. */
|
||||||
|
NINO_FNS(Dirty)
|
||||||
|
TAS_NINO_FNS(Dirty)
|
||||||
|
NINO_FNS(AttrList)
|
||||||
|
NINO_FNS(AttrListDirty)
|
||||||
|
NINO_FNS(AttrListNonResident)
|
||||||
|
NINO_FNS(Attr)
|
||||||
|
NINO_FNS(MstProtected)
|
||||||
|
NINO_FNS(NonResident)
|
||||||
|
NINO_FNS(IndexAllocPresent)
|
||||||
|
NINO_FNS(Compressed)
|
||||||
|
NINO_FNS(Encrypted)
|
||||||
|
NINO_FNS(Sparse)
|
||||||
|
NINO_FNS(SparseDisabled)
|
||||||
|
NINO_FNS(FullyMapped)
|
||||||
|
NINO_FNS(FileNameDirty)
|
||||||
|
TAS_NINO_FNS(FileNameDirty)
|
||||||
|
NINO_FNS(BeingDeleted)
|
||||||
|
NINO_FNS(HasEA)
|
||||||
|
NINO_FNS(RunlistDirty)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The full structure containing a ntfs_inode and a vfs struct inode. Used for
|
||||||
|
* all real and fake inodes but not for extent inodes which lack the vfs struct
|
||||||
|
* inode.
|
||||||
|
*/
|
||||||
|
struct big_ntfs_inode {
|
||||||
|
struct ntfs_inode ntfs_inode;
|
||||||
|
struct inode vfs_inode; /* The vfs inode structure. */
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NTFS_I - return the ntfs inode given a vfs inode
|
||||||
|
* @inode: VFS inode
|
||||||
|
*
|
||||||
|
* NTFS_I() returns the ntfs inode associated with the VFS @inode.
|
||||||
|
*/
|
||||||
|
static inline struct ntfs_inode *NTFS_I(struct inode *inode)
|
||||||
|
{
|
||||||
|
return &container_of(inode, struct big_ntfs_inode, vfs_inode)->ntfs_inode;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct inode *VFS_I(struct ntfs_inode *ni)
|
||||||
|
{
|
||||||
|
return &container_of(ni, struct big_ntfs_inode, ntfs_inode)->vfs_inode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_attr - ntfs in memory attribute structure
|
||||||
|
*
|
||||||
|
* This structure exists only to provide a small structure for the
|
||||||
|
* ntfs_{attr_}iget()/ntfs_test_inode()/ntfs_init_locked_inode() mechanism.
|
||||||
|
*
|
||||||
|
* NOTE: Elements are ordered by size to make the structure as compact as
|
||||||
|
* possible on all architectures.
|
||||||
|
*/
|
||||||
|
struct ntfs_attr {
|
||||||
|
u64 mft_no;
|
||||||
|
__le16 *name;
|
||||||
|
u32 name_len;
|
||||||
|
__le32 type;
|
||||||
|
unsigned long state;
|
||||||
|
};
|
||||||
|
|
||||||
|
int ntfs_test_inode(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);
|
||||||
|
struct inode *ntfs_index_iget(struct inode *base_vi, __le16 *name,
|
||||||
|
u32 name_len);
|
||||||
|
struct inode *ntfs_alloc_big_inode(struct super_block *sb);
|
||||||
|
void ntfs_free_big_inode(struct inode *inode);
|
||||||
|
int ntfs_drop_big_inode(struct inode *inode);
|
||||||
|
void ntfs_evict_big_inode(struct inode *vi);
|
||||||
|
void __ntfs_init_inode(struct super_block *sb, struct ntfs_inode *ni);
|
||||||
|
|
||||||
|
static inline void ntfs_init_big_inode(struct inode *vi)
|
||||||
|
{
|
||||||
|
struct ntfs_inode *ni = NTFS_I(vi);
|
||||||
|
|
||||||
|
ntfs_debug("Entering.");
|
||||||
|
__ntfs_init_inode(vi->i_sb, ni);
|
||||||
|
ni->mft_no = vi->i_ino;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ntfs_inode *ntfs_new_extent_inode(struct super_block *sb, u64 mft_no);
|
||||||
|
void ntfs_clear_extent_inode(struct ntfs_inode *ni);
|
||||||
|
int ntfs_read_inode_mount(struct inode *vi);
|
||||||
|
int ntfs_show_options(struct seq_file *sf, struct dentry *root);
|
||||||
|
int ntfs_truncate_vfs(struct inode *vi, loff_t new_size, loff_t i_size);
|
||||||
|
|
||||||
|
int ntfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
|
||||||
|
struct iattr *attr);
|
||||||
|
int ntfs_getattr(struct mnt_idmap *idmap, const struct path *path,
|
||||||
|
struct kstat *stat, unsigned int request_mask,
|
||||||
|
unsigned int query_flags);
|
||||||
|
|
||||||
|
int ntfs_get_block_mft_record(struct ntfs_inode *mft_ni, struct ntfs_inode *ni);
|
||||||
|
int __ntfs_write_inode(struct inode *vi, int sync);
|
||||||
|
int ntfs_inode_attach_all_extents(struct ntfs_inode *ni);
|
||||||
|
int ntfs_inode_add_attrlist(struct ntfs_inode *ni);
|
||||||
|
void ntfs_destroy_ext_inode(struct ntfs_inode *ni);
|
||||||
|
int ntfs_inode_free_space(struct ntfs_inode *ni, int size);
|
||||||
|
s64 ntfs_inode_attr_pread(struct inode *vi, s64 pos, s64 count, u8 *buf);
|
||||||
|
s64 ntfs_inode_attr_pwrite(struct inode *vi, s64 pos, s64 count, u8 *buf,
|
||||||
|
bool sync);
|
||||||
|
int ntfs_inode_close(struct ntfs_inode *ni);
|
||||||
|
|
||||||
|
static inline void ntfs_commit_inode(struct inode *vi)
|
||||||
|
{
|
||||||
|
__ntfs_write_inode(vi, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ntfs_inode_sync_filename(struct ntfs_inode *ni);
|
||||||
|
int ntfs_extend_initialized_size(struct inode *vi, const loff_t offset,
|
||||||
|
const loff_t new_size, bool bsync);
|
||||||
|
void ntfs_set_vfs_operations(struct inode *inode, mode_t mode, dev_t dev);
|
||||||
|
struct folio *ntfs_get_locked_folio(struct address_space *mapping,
|
||||||
|
pgoff_t index, pgoff_t end_index, struct file_ra_state *ra);
|
||||||
|
|
||||||
|
#endif /* _LINUX_NTFS_INODE_H */
|
||||||
870
fs/ntfs/iomap.c
Normal file
870
fs/ntfs/iomap.c
Normal file
@@ -0,0 +1,870 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* iomap callack functions
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 LG Electronics Co., Ltd.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/writeback.h>
|
||||||
|
|
||||||
|
#include "attrib.h"
|
||||||
|
#include "mft.h"
|
||||||
|
#include "ntfs.h"
|
||||||
|
#include "iomap.h"
|
||||||
|
|
||||||
|
static void ntfs_iomap_put_folio_non_resident(struct inode *inode, loff_t pos,
|
||||||
|
unsigned int len, struct folio *folio)
|
||||||
|
{
|
||||||
|
struct ntfs_inode *ni = NTFS_I(inode);
|
||||||
|
unsigned long sector_size = 1UL << inode->i_blkbits;
|
||||||
|
loff_t start_down, end_up, init;
|
||||||
|
|
||||||
|
start_down = round_down(pos, sector_size);
|
||||||
|
end_up = (pos + len - 1) | (sector_size - 1);
|
||||||
|
init = ni->initialized_size;
|
||||||
|
|
||||||
|
if (init >= start_down && init <= end_up) {
|
||||||
|
if (init < pos) {
|
||||||
|
loff_t offset = offset_in_folio(folio, pos + len);
|
||||||
|
|
||||||
|
if (offset == 0)
|
||||||
|
offset = folio_size(folio);
|
||||||
|
folio_zero_segments(folio,
|
||||||
|
offset_in_folio(folio, init),
|
||||||
|
offset_in_folio(folio, pos),
|
||||||
|
offset,
|
||||||
|
folio_size(folio));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
loff_t offset = max_t(loff_t, pos + len, init);
|
||||||
|
|
||||||
|
offset = offset_in_folio(folio, offset);
|
||||||
|
if (offset == 0)
|
||||||
|
offset = folio_size(folio);
|
||||||
|
folio_zero_segment(folio,
|
||||||
|
offset,
|
||||||
|
folio_size(folio));
|
||||||
|
}
|
||||||
|
} else if (init <= pos) {
|
||||||
|
loff_t offset = 0, offset2 = offset_in_folio(folio, pos + len);
|
||||||
|
|
||||||
|
if ((init >> folio_shift(folio)) == (pos >> folio_shift(folio)))
|
||||||
|
offset = offset_in_folio(folio, init);
|
||||||
|
if (offset2 == 0)
|
||||||
|
offset2 = folio_size(folio);
|
||||||
|
folio_zero_segments(folio,
|
||||||
|
offset,
|
||||||
|
offset_in_folio(folio, pos),
|
||||||
|
offset2,
|
||||||
|
folio_size(folio));
|
||||||
|
}
|
||||||
|
folio_unlock(folio);
|
||||||
|
folio_put(folio);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iomap_zero_range is called for an area beyond the initialized size,
|
||||||
|
* garbage values can be read, so zeroing out is needed.
|
||||||
|
*/
|
||||||
|
static void ntfs_iomap_put_folio(struct inode *inode, loff_t pos,
|
||||||
|
unsigned int len, struct folio *folio)
|
||||||
|
{
|
||||||
|
if (NInoNonResident(NTFS_I(inode)))
|
||||||
|
return ntfs_iomap_put_folio_non_resident(inode, pos,
|
||||||
|
len, folio);
|
||||||
|
folio_unlock(folio);
|
||||||
|
folio_put(folio);
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct iomap_write_ops ntfs_iomap_folio_ops = {
|
||||||
|
.put_folio = ntfs_iomap_put_folio,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int ntfs_read_iomap_begin_resident(struct inode *inode, loff_t offset, loff_t length,
|
||||||
|
unsigned int flags, struct iomap *iomap)
|
||||||
|
{
|
||||||
|
struct ntfs_inode *base_ni, *ni = NTFS_I(inode);
|
||||||
|
struct ntfs_attr_search_ctx *ctx;
|
||||||
|
loff_t i_size;
|
||||||
|
u32 attr_len;
|
||||||
|
int err = 0;
|
||||||
|
char *kattr;
|
||||||
|
|
||||||
|
if (NInoAttr(ni))
|
||||||
|
base_ni = ni->ext.base_ntfs_ino;
|
||||||
|
else
|
||||||
|
base_ni = ni;
|
||||||
|
|
||||||
|
ctx = ntfs_attr_get_search_ctx(base_ni, NULL);
|
||||||
|
if (!ctx) {
|
||||||
|
err = -ENOMEM;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len,
|
||||||
|
CASE_SENSITIVE, 0, NULL, 0, ctx);
|
||||||
|
if (unlikely(err))
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
attr_len = le32_to_cpu(ctx->attr->data.resident.value_length);
|
||||||
|
if (unlikely(attr_len > ni->initialized_size))
|
||||||
|
attr_len = ni->initialized_size;
|
||||||
|
i_size = i_size_read(inode);
|
||||||
|
|
||||||
|
if (unlikely(attr_len > i_size)) {
|
||||||
|
/* Race with shrinking truncate. */
|
||||||
|
attr_len = i_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset >= attr_len) {
|
||||||
|
if (flags & IOMAP_REPORT)
|
||||||
|
err = -ENOENT;
|
||||||
|
else {
|
||||||
|
iomap->type = IOMAP_HOLE;
|
||||||
|
iomap->offset = offset;
|
||||||
|
iomap->length = length;
|
||||||
|
}
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
kattr = (u8 *)ctx->attr + le16_to_cpu(ctx->attr->data.resident.value_offset);
|
||||||
|
|
||||||
|
iomap->inline_data = kmemdup(kattr, attr_len, GFP_KERNEL);
|
||||||
|
if (!iomap->inline_data) {
|
||||||
|
err = -ENOMEM;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
iomap->type = IOMAP_INLINE;
|
||||||
|
iomap->offset = 0;
|
||||||
|
iomap->length = attr_len;
|
||||||
|
|
||||||
|
out:
|
||||||
|
if (ctx)
|
||||||
|
ntfs_attr_put_search_ctx(ctx);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_read_iomap_begin_non_resident - map non-resident NTFS file data
|
||||||
|
* @inode: inode to map
|
||||||
|
* @offset: file offset to map
|
||||||
|
* @length: length of mapping
|
||||||
|
* @flags: IOMAP flags
|
||||||
|
* @iomap: iomap structure to fill
|
||||||
|
* @need_unwritten: true if UNWRITTEN extent type is needed
|
||||||
|
*
|
||||||
|
* Map a range of a non-resident NTFS file to an iomap extent.
|
||||||
|
*
|
||||||
|
* NTFS UNWRITTEN extent handling:
|
||||||
|
* ================================
|
||||||
|
* The concept of an unwritten extent in NTFS is slightly different from
|
||||||
|
* that of other filesystems. NTFS conceptually manages only a single
|
||||||
|
* continuous unwritten region, which is strictly defined based on
|
||||||
|
* initialized_size.
|
||||||
|
*
|
||||||
|
* File offset layout:
|
||||||
|
* 0 initialized_size i_size(EOF)
|
||||||
|
* |----------#0----------|----------#1----------|----------#2----------|
|
||||||
|
* | Actual data | Pre-allocated | Pre-allocated |
|
||||||
|
* | (user written) | (within initialized) | (initialized ~ EOF) |
|
||||||
|
* |----------------------|----------------------|----------------------|
|
||||||
|
* MAPPED MAPPED UNWRITTEN (conditionally)
|
||||||
|
*
|
||||||
|
* Region #0: User-written data, initialized and valid.
|
||||||
|
* Region #1: Pre-allocated within initialized_size, must be zero-initialized
|
||||||
|
* by the filesystem before exposure to userspace.
|
||||||
|
* Region #2: Pre-allocated beyond initialized_size, does not need initialization.
|
||||||
|
*
|
||||||
|
* The @need_unwritten parameter controls whether region #2 is mapped as
|
||||||
|
* IOMAP_UNWRITTEN or IOMAP_MAPPED:
|
||||||
|
* - For seek operations (SEEK_DATA/SEEK_HOLE): IOMAP_MAPPED is needed to
|
||||||
|
* prevent iomap_seek_data from incorrectly interpreting pre-allocated
|
||||||
|
* space as a hole. Since NTFS does not support multiple unwritten extents,
|
||||||
|
* all pre-allocated regions should be treated as data, not holes.
|
||||||
|
* - For zero_range operations: IOMAP_MAPPED is needed to be zeroed out.
|
||||||
|
*
|
||||||
|
* Return: 0 on success, negative error code on failure.
|
||||||
|
*/
|
||||||
|
static int ntfs_read_iomap_begin_non_resident(struct inode *inode, loff_t offset,
|
||||||
|
loff_t length, unsigned int flags, struct iomap *iomap,
|
||||||
|
bool need_unwritten)
|
||||||
|
{
|
||||||
|
struct ntfs_inode *ni = NTFS_I(inode);
|
||||||
|
s64 vcn;
|
||||||
|
s64 lcn;
|
||||||
|
struct runlist_element *rl;
|
||||||
|
struct ntfs_volume *vol = ni->vol;
|
||||||
|
loff_t vcn_ofs;
|
||||||
|
loff_t rl_length;
|
||||||
|
|
||||||
|
vcn = ntfs_bytes_to_cluster(vol, offset);
|
||||||
|
vcn_ofs = ntfs_bytes_to_cluster_off(vol, offset);
|
||||||
|
|
||||||
|
down_write(&ni->runlist.lock);
|
||||||
|
rl = ntfs_attr_vcn_to_rl(ni, vcn, &lcn);
|
||||||
|
if (IS_ERR(rl)) {
|
||||||
|
up_write(&ni->runlist.lock);
|
||||||
|
return PTR_ERR(rl);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags & IOMAP_REPORT) {
|
||||||
|
if (lcn < LCN_HOLE) {
|
||||||
|
up_write(&ni->runlist.lock);
|
||||||
|
return -ENOENT;
|
||||||
|
}
|
||||||
|
} else if (lcn < LCN_ENOENT) {
|
||||||
|
up_write(&ni->runlist.lock);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
iomap->bdev = inode->i_sb->s_bdev;
|
||||||
|
iomap->offset = offset;
|
||||||
|
|
||||||
|
if (lcn <= LCN_DELALLOC) {
|
||||||
|
if (lcn == LCN_DELALLOC)
|
||||||
|
iomap->type = IOMAP_DELALLOC;
|
||||||
|
else
|
||||||
|
iomap->type = IOMAP_HOLE;
|
||||||
|
iomap->addr = IOMAP_NULL_ADDR;
|
||||||
|
} else {
|
||||||
|
if (need_unwritten && offset >= ni->initialized_size)
|
||||||
|
iomap->type = IOMAP_UNWRITTEN;
|
||||||
|
else
|
||||||
|
iomap->type = IOMAP_MAPPED;
|
||||||
|
iomap->addr = ntfs_cluster_to_bytes(vol, lcn) + vcn_ofs;
|
||||||
|
}
|
||||||
|
|
||||||
|
rl_length = ntfs_cluster_to_bytes(vol, rl->length - (vcn - rl->vcn));
|
||||||
|
|
||||||
|
if (rl_length == 0 && rl->lcn > LCN_DELALLOC) {
|
||||||
|
ntfs_error(inode->i_sb,
|
||||||
|
"runlist(vcn : %lld, length : %lld, lcn : %lld) is corrupted\n",
|
||||||
|
rl->vcn, rl->length, rl->lcn);
|
||||||
|
up_write(&ni->runlist.lock);
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rl_length && length > rl_length - vcn_ofs)
|
||||||
|
iomap->length = rl_length - vcn_ofs;
|
||||||
|
else
|
||||||
|
iomap->length = length;
|
||||||
|
up_write(&ni->runlist.lock);
|
||||||
|
|
||||||
|
if (!(flags & IOMAP_ZERO) &&
|
||||||
|
iomap->type == IOMAP_MAPPED &&
|
||||||
|
iomap->offset < ni->initialized_size &&
|
||||||
|
iomap->offset + iomap->length > ni->initialized_size) {
|
||||||
|
iomap->length = round_up(ni->initialized_size, 1 << inode->i_blkbits) -
|
||||||
|
iomap->offset;
|
||||||
|
}
|
||||||
|
iomap->flags |= IOMAP_F_MERGED;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __ntfs_read_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
|
||||||
|
unsigned int flags, struct iomap *iomap, struct iomap *srcmap,
|
||||||
|
bool need_unwritten)
|
||||||
|
{
|
||||||
|
if (NInoNonResident(NTFS_I(inode)))
|
||||||
|
return ntfs_read_iomap_begin_non_resident(inode, offset, length,
|
||||||
|
flags, iomap, need_unwritten);
|
||||||
|
return ntfs_read_iomap_begin_resident(inode, offset, length,
|
||||||
|
flags, iomap);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ntfs_read_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
|
||||||
|
unsigned int flags, struct iomap *iomap, struct iomap *srcmap)
|
||||||
|
{
|
||||||
|
return __ntfs_read_iomap_begin(inode, offset, length, flags, iomap,
|
||||||
|
srcmap, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ntfs_read_iomap_end(struct inode *inode, loff_t pos, loff_t length,
|
||||||
|
ssize_t written, unsigned int flags, struct iomap *iomap)
|
||||||
|
{
|
||||||
|
if (iomap->type == IOMAP_INLINE)
|
||||||
|
kfree(iomap->inline_data);
|
||||||
|
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct iomap_ops ntfs_read_iomap_ops = {
|
||||||
|
.iomap_begin = ntfs_read_iomap_begin,
|
||||||
|
.iomap_end = ntfs_read_iomap_end,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check that the cached iomap still matches the NTFS runlist before
|
||||||
|
* iomap_zero_range() is called. if the runlist changes while iomap is
|
||||||
|
* iterating a cached iomap, iomap_zero_range() may overwrite folios
|
||||||
|
* that have been already written with valid data.
|
||||||
|
*/
|
||||||
|
static bool ntfs_iomap_valid(struct inode *inode, const struct iomap *iomap)
|
||||||
|
{
|
||||||
|
struct ntfs_inode *ni = NTFS_I(inode);
|
||||||
|
struct runlist_element *rl;
|
||||||
|
s64 vcn, lcn;
|
||||||
|
|
||||||
|
if (!NInoNonResident(ni))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
vcn = iomap->offset >> ni->vol->cluster_size_bits;
|
||||||
|
|
||||||
|
down_read(&ni->runlist.lock);
|
||||||
|
rl = __ntfs_attr_find_vcn_nolock(&ni->runlist, vcn);
|
||||||
|
if (IS_ERR(rl)) {
|
||||||
|
up_read(&ni->runlist.lock);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
lcn = ntfs_rl_vcn_to_lcn(rl, vcn);
|
||||||
|
up_read(&ni->runlist.lock);
|
||||||
|
return lcn == LCN_DELALLOC;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct iomap_write_ops ntfs_zero_iomap_folio_ops = {
|
||||||
|
.put_folio = ntfs_iomap_put_folio,
|
||||||
|
.iomap_valid = ntfs_iomap_valid,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int ntfs_seek_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
|
||||||
|
unsigned int flags, struct iomap *iomap, struct iomap *srcmap)
|
||||||
|
{
|
||||||
|
return __ntfs_read_iomap_begin(inode, offset, length, flags, iomap,
|
||||||
|
srcmap, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ntfs_zero_read_iomap_end(struct inode *inode, loff_t pos, loff_t length,
|
||||||
|
ssize_t written, unsigned int flags, struct iomap *iomap)
|
||||||
|
{
|
||||||
|
if ((flags & IOMAP_ZERO) && (iomap->flags & IOMAP_F_STALE))
|
||||||
|
return -EPERM;
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct iomap_ops ntfs_zero_read_iomap_ops = {
|
||||||
|
.iomap_begin = ntfs_seek_iomap_begin,
|
||||||
|
.iomap_end = ntfs_zero_read_iomap_end,
|
||||||
|
};
|
||||||
|
|
||||||
|
const struct iomap_ops ntfs_seek_iomap_ops = {
|
||||||
|
.iomap_begin = ntfs_seek_iomap_begin,
|
||||||
|
.iomap_end = ntfs_read_iomap_end,
|
||||||
|
};
|
||||||
|
|
||||||
|
int ntfs_dio_zero_range(struct inode *inode, loff_t offset, loff_t length)
|
||||||
|
{
|
||||||
|
if ((offset | length) & (SECTOR_SIZE - 1))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
return blkdev_issue_zeroout(inode->i_sb->s_bdev,
|
||||||
|
offset >> SECTOR_SHIFT,
|
||||||
|
length >> SECTOR_SHIFT,
|
||||||
|
GFP_NOFS,
|
||||||
|
BLKDEV_ZERO_NOUNMAP);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ntfs_zero_range(struct inode *inode, loff_t offset, loff_t length)
|
||||||
|
{
|
||||||
|
return iomap_zero_range(inode,
|
||||||
|
offset, length,
|
||||||
|
NULL,
|
||||||
|
&ntfs_zero_read_iomap_ops,
|
||||||
|
&ntfs_zero_iomap_folio_ops,
|
||||||
|
NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ntfs_write_simple_iomap_begin_non_resident(struct inode *inode, loff_t offset,
|
||||||
|
loff_t length, struct iomap *iomap)
|
||||||
|
{
|
||||||
|
struct ntfs_inode *ni = NTFS_I(inode);
|
||||||
|
struct ntfs_volume *vol = ni->vol;
|
||||||
|
loff_t vcn_ofs, rl_length;
|
||||||
|
struct runlist_element *rl, *rlc;
|
||||||
|
bool is_retry = false;
|
||||||
|
int err;
|
||||||
|
s64 vcn, lcn;
|
||||||
|
s64 max_clu_count =
|
||||||
|
ntfs_bytes_to_cluster(vol, round_up(length, vol->cluster_size));
|
||||||
|
|
||||||
|
vcn = ntfs_bytes_to_cluster(vol, offset);
|
||||||
|
vcn_ofs = ntfs_bytes_to_cluster_off(vol, offset);
|
||||||
|
|
||||||
|
down_read(&ni->runlist.lock);
|
||||||
|
rl = ni->runlist.rl;
|
||||||
|
if (!rl) {
|
||||||
|
up_read(&ni->runlist.lock);
|
||||||
|
err = ntfs_map_runlist(ni, vcn);
|
||||||
|
if (err) {
|
||||||
|
mutex_unlock(&ni->mrec_lock);
|
||||||
|
return -ENOENT;
|
||||||
|
}
|
||||||
|
down_read(&ni->runlist.lock);
|
||||||
|
rl = ni->runlist.rl;
|
||||||
|
}
|
||||||
|
up_read(&ni->runlist.lock);
|
||||||
|
|
||||||
|
down_write(&ni->runlist.lock);
|
||||||
|
remap_rl:
|
||||||
|
/* Seek to element containing target vcn. */
|
||||||
|
rl = __ntfs_attr_find_vcn_nolock(&ni->runlist, vcn);
|
||||||
|
if (IS_ERR(rl)) {
|
||||||
|
up_write(&ni->runlist.lock);
|
||||||
|
mutex_unlock(&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)) {
|
||||||
|
rl = ni->runlist.rl;
|
||||||
|
goto remap_rl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
iomap->bdev = inode->i_sb->s_bdev;
|
||||||
|
iomap->offset = offset;
|
||||||
|
|
||||||
|
if (lcn <= LCN_DELALLOC) {
|
||||||
|
if (lcn < LCN_DELALLOC) {
|
||||||
|
max_clu_count =
|
||||||
|
ntfs_available_clusters_count(vol, max_clu_count);
|
||||||
|
if (max_clu_count < 0) {
|
||||||
|
err = max_clu_count;
|
||||||
|
up_write(&ni->runlist.lock);
|
||||||
|
mutex_unlock(&ni->mrec_lock);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iomap->type = IOMAP_DELALLOC;
|
||||||
|
iomap->addr = IOMAP_NULL_ADDR;
|
||||||
|
|
||||||
|
if (lcn <= LCN_HOLE) {
|
||||||
|
size_t new_rl_count;
|
||||||
|
|
||||||
|
rlc = kmalloc(sizeof(struct runlist_element) * 2,
|
||||||
|
GFP_NOFS);
|
||||||
|
if (!rlc) {
|
||||||
|
up_write(&ni->runlist.lock);
|
||||||
|
mutex_unlock(&ni->mrec_lock);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
rlc->vcn = vcn;
|
||||||
|
rlc->lcn = LCN_DELALLOC;
|
||||||
|
rlc->length = max_clu_count;
|
||||||
|
|
||||||
|
rlc[1].vcn = vcn + max_clu_count;
|
||||||
|
rlc[1].lcn = LCN_RL_NOT_MAPPED;
|
||||||
|
rlc[1].length = 0;
|
||||||
|
|
||||||
|
rl = ntfs_runlists_merge(&ni->runlist, rlc, 0,
|
||||||
|
&new_rl_count);
|
||||||
|
if (IS_ERR(rl)) {
|
||||||
|
ntfs_error(vol->sb, "Failed to merge runlists");
|
||||||
|
up_write(&ni->runlist.lock);
|
||||||
|
mutex_unlock(&ni->mrec_lock);
|
||||||
|
kvfree(rlc);
|
||||||
|
return PTR_ERR(rl);
|
||||||
|
}
|
||||||
|
|
||||||
|
ni->runlist.rl = rl;
|
||||||
|
ni->runlist.count = new_rl_count;
|
||||||
|
ni->i_dealloc_clusters += max_clu_count;
|
||||||
|
}
|
||||||
|
up_write(&ni->runlist.lock);
|
||||||
|
mutex_unlock(&ni->mrec_lock);
|
||||||
|
|
||||||
|
if (lcn < LCN_DELALLOC)
|
||||||
|
ntfs_hold_dirty_clusters(vol, max_clu_count);
|
||||||
|
|
||||||
|
rl_length = ntfs_cluster_to_bytes(vol, max_clu_count);
|
||||||
|
if (length > rl_length - vcn_ofs)
|
||||||
|
iomap->length = rl_length - vcn_ofs;
|
||||||
|
else
|
||||||
|
iomap->length = length;
|
||||||
|
|
||||||
|
iomap->flags = IOMAP_F_NEW;
|
||||||
|
if (lcn <= LCN_HOLE) {
|
||||||
|
loff_t end = offset + length;
|
||||||
|
|
||||||
|
if (vcn_ofs || ((vol->cluster_size > iomap->length) &&
|
||||||
|
end < ni->initialized_size)) {
|
||||||
|
loff_t z_start, z_end;
|
||||||
|
|
||||||
|
z_start = vcn << vol->cluster_size_bits;
|
||||||
|
z_end = min_t(loff_t, z_start + vol->cluster_size,
|
||||||
|
i_size_read(inode));
|
||||||
|
if (z_end > z_start)
|
||||||
|
err = ntfs_zero_range(inode,
|
||||||
|
z_start,
|
||||||
|
z_end - z_start);
|
||||||
|
}
|
||||||
|
if ((!err || err == -EPERM) &&
|
||||||
|
max_clu_count > 1 &&
|
||||||
|
(iomap->length & vol->cluster_size_mask &&
|
||||||
|
end < ni->initialized_size)) {
|
||||||
|
loff_t z_start, z_end;
|
||||||
|
|
||||||
|
z_start = (vcn + max_clu_count - 1) <<
|
||||||
|
vol->cluster_size_bits;
|
||||||
|
z_end = min_t(loff_t, z_start + vol->cluster_size,
|
||||||
|
i_size_read(inode));
|
||||||
|
if (z_end > z_start)
|
||||||
|
err = ntfs_zero_range(inode,
|
||||||
|
z_start,
|
||||||
|
z_end - z_start);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err == -EPERM)
|
||||||
|
err = 0;
|
||||||
|
if (err) {
|
||||||
|
ntfs_release_dirty_clusters(vol, max_clu_count);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
up_write(&ni->runlist.lock);
|
||||||
|
mutex_unlock(&ni->mrec_lock);
|
||||||
|
|
||||||
|
iomap->type = IOMAP_MAPPED;
|
||||||
|
iomap->addr = ntfs_cluster_to_bytes(vol, lcn) + vcn_ofs;
|
||||||
|
|
||||||
|
rl_length = ntfs_cluster_to_bytes(vol, max_clu_count);
|
||||||
|
if (length > rl_length - vcn_ofs)
|
||||||
|
iomap->length = rl_length - vcn_ofs;
|
||||||
|
else
|
||||||
|
iomap->length = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define NTFS_IOMAP_FLAGS_BEGIN BIT(1)
|
||||||
|
#define NTFS_IOMAP_FLAGS_DIO BIT(2)
|
||||||
|
#define NTFS_IOMAP_FLAGS_MKWRITE BIT(3)
|
||||||
|
#define NTFS_IOMAP_FLAGS_WRITEBACK BIT(4)
|
||||||
|
|
||||||
|
static int ntfs_write_da_iomap_begin_non_resident(struct inode *inode,
|
||||||
|
loff_t offset, loff_t length, unsigned int flags,
|
||||||
|
struct iomap *iomap, int ntfs_iomap_flags)
|
||||||
|
{
|
||||||
|
struct ntfs_inode *ni = NTFS_I(inode);
|
||||||
|
struct ntfs_volume *vol = ni->vol;
|
||||||
|
loff_t vcn_ofs, rl_length;
|
||||||
|
s64 vcn, start_lcn, lcn_count;
|
||||||
|
bool balloc = false, update_mp;
|
||||||
|
int err;
|
||||||
|
s64 max_clu_count =
|
||||||
|
ntfs_bytes_to_cluster(vol, round_up(length, vol->cluster_size));
|
||||||
|
|
||||||
|
vcn = ntfs_bytes_to_cluster(vol, offset);
|
||||||
|
vcn_ofs = ntfs_bytes_to_cluster_off(vol, offset);
|
||||||
|
|
||||||
|
update_mp = ntfs_iomap_flags & (NTFS_IOMAP_FLAGS_DIO | NTFS_IOMAP_FLAGS_MKWRITE) ||
|
||||||
|
NInoAttr(ni) || ni->mft_no < FILE_first_user;
|
||||||
|
down_write(&ni->runlist.lock);
|
||||||
|
err = ntfs_attr_map_cluster(ni, vcn, &start_lcn, &lcn_count,
|
||||||
|
max_clu_count, &balloc, update_mp,
|
||||||
|
ntfs_iomap_flags & NTFS_IOMAP_FLAGS_WRITEBACK);
|
||||||
|
up_write(&ni->runlist.lock);
|
||||||
|
mutex_unlock(&ni->mrec_lock);
|
||||||
|
if (err) {
|
||||||
|
ni->i_dealloc_clusters = 0;
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
iomap->bdev = inode->i_sb->s_bdev;
|
||||||
|
iomap->offset = offset;
|
||||||
|
|
||||||
|
rl_length = ntfs_cluster_to_bytes(vol, lcn_count);
|
||||||
|
if (length > rl_length - vcn_ofs)
|
||||||
|
iomap->length = rl_length - vcn_ofs;
|
||||||
|
else
|
||||||
|
iomap->length = length;
|
||||||
|
|
||||||
|
if (start_lcn == LCN_HOLE)
|
||||||
|
iomap->type = IOMAP_HOLE;
|
||||||
|
else
|
||||||
|
iomap->type = IOMAP_MAPPED;
|
||||||
|
if (balloc == true)
|
||||||
|
iomap->flags = IOMAP_F_NEW;
|
||||||
|
|
||||||
|
iomap->addr = ntfs_cluster_to_bytes(vol, start_lcn) + vcn_ofs;
|
||||||
|
|
||||||
|
if (balloc == true) {
|
||||||
|
if (flags & IOMAP_DIRECT ||
|
||||||
|
ntfs_iomap_flags & NTFS_IOMAP_FLAGS_MKWRITE) {
|
||||||
|
loff_t end = offset + length;
|
||||||
|
|
||||||
|
if (vcn_ofs || ((vol->cluster_size > iomap->length) &&
|
||||||
|
end < ni->initialized_size))
|
||||||
|
err = ntfs_dio_zero_range(inode,
|
||||||
|
start_lcn <<
|
||||||
|
vol->cluster_size_bits,
|
||||||
|
vol->cluster_size);
|
||||||
|
if (!err && lcn_count > 1 &&
|
||||||
|
(iomap->length & vol->cluster_size_mask &&
|
||||||
|
end < ni->initialized_size))
|
||||||
|
err = ntfs_dio_zero_range(inode,
|
||||||
|
(start_lcn + lcn_count - 1) <<
|
||||||
|
vol->cluster_size_bits,
|
||||||
|
vol->cluster_size);
|
||||||
|
} else {
|
||||||
|
if (lcn_count > ni->i_dealloc_clusters)
|
||||||
|
ni->i_dealloc_clusters = 0;
|
||||||
|
else
|
||||||
|
ni->i_dealloc_clusters -= lcn_count;
|
||||||
|
}
|
||||||
|
if (err < 0)
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ntfs_write_iomap_begin_resident(struct inode *inode, loff_t offset,
|
||||||
|
struct iomap *iomap)
|
||||||
|
{
|
||||||
|
struct ntfs_inode *ni = NTFS_I(inode);
|
||||||
|
struct attr_record *a;
|
||||||
|
struct ntfs_attr_search_ctx *ctx;
|
||||||
|
u32 attr_len;
|
||||||
|
int err = 0;
|
||||||
|
char *kattr;
|
||||||
|
|
||||||
|
ctx = ntfs_attr_get_search_ctx(ni, NULL);
|
||||||
|
if (!ctx) {
|
||||||
|
err = -ENOMEM;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len,
|
||||||
|
CASE_SENSITIVE, 0, NULL, 0, ctx);
|
||||||
|
if (err) {
|
||||||
|
if (err == -ENOENT)
|
||||||
|
err = -EIO;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
a = ctx->attr;
|
||||||
|
/* The total length of the attribute value. */
|
||||||
|
attr_len = le32_to_cpu(a->data.resident.value_length);
|
||||||
|
kattr = (u8 *)a + le16_to_cpu(a->data.resident.value_offset);
|
||||||
|
|
||||||
|
iomap->inline_data = kmemdup(kattr, attr_len, GFP_KERNEL);
|
||||||
|
if (!iomap->inline_data) {
|
||||||
|
err = -ENOMEM;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
iomap->type = IOMAP_INLINE;
|
||||||
|
iomap->offset = 0;
|
||||||
|
/* iomap requires there is only one INLINE_DATA extent */
|
||||||
|
iomap->length = attr_len;
|
||||||
|
|
||||||
|
out:
|
||||||
|
if (ctx)
|
||||||
|
ntfs_attr_put_search_ctx(ctx);
|
||||||
|
mutex_unlock(&ni->mrec_lock);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ntfs_write_iomap_begin_non_resident(struct inode *inode, loff_t offset,
|
||||||
|
loff_t length, unsigned int flags,
|
||||||
|
struct iomap *iomap, int ntfs_iomap_flags)
|
||||||
|
{
|
||||||
|
struct ntfs_inode *ni = NTFS_I(inode);
|
||||||
|
|
||||||
|
if (ntfs_iomap_flags & (NTFS_IOMAP_FLAGS_BEGIN | NTFS_IOMAP_FLAGS_DIO) &&
|
||||||
|
offset + length > ni->initialized_size) {
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = ntfs_extend_initialized_size(inode, offset,
|
||||||
|
offset + length,
|
||||||
|
ntfs_iomap_flags &
|
||||||
|
NTFS_IOMAP_FLAGS_DIO);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_lock(&ni->mrec_lock);
|
||||||
|
if (ntfs_iomap_flags & NTFS_IOMAP_FLAGS_BEGIN)
|
||||||
|
return ntfs_write_simple_iomap_begin_non_resident(inode, offset,
|
||||||
|
length, iomap);
|
||||||
|
else
|
||||||
|
return ntfs_write_da_iomap_begin_non_resident(inode,
|
||||||
|
offset, length,
|
||||||
|
flags, iomap,
|
||||||
|
ntfs_iomap_flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __ntfs_write_iomap_begin(struct inode *inode, loff_t offset,
|
||||||
|
loff_t length, unsigned int flags,
|
||||||
|
struct iomap *iomap, int ntfs_iomap_flags)
|
||||||
|
{
|
||||||
|
struct ntfs_inode *ni = NTFS_I(inode);
|
||||||
|
loff_t end = offset + length;
|
||||||
|
|
||||||
|
if (NVolShutdown(ni->vol))
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
if (ntfs_iomap_flags & (NTFS_IOMAP_FLAGS_BEGIN | NTFS_IOMAP_FLAGS_DIO) &&
|
||||||
|
end > ni->data_size) {
|
||||||
|
struct ntfs_volume *vol = ni->vol;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
mutex_lock(&ni->mrec_lock);
|
||||||
|
if (end > ni->allocated_size &&
|
||||||
|
end < ni->allocated_size + vol->preallocated_size)
|
||||||
|
ret = ntfs_attr_expand(ni, end,
|
||||||
|
ni->allocated_size + vol->preallocated_size);
|
||||||
|
else
|
||||||
|
ret = ntfs_attr_expand(ni, end, 0);
|
||||||
|
mutex_unlock(&ni->mrec_lock);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!NInoNonResident(ni)) {
|
||||||
|
mutex_lock(&ni->mrec_lock);
|
||||||
|
return ntfs_write_iomap_begin_resident(inode, offset, iomap);
|
||||||
|
}
|
||||||
|
return ntfs_write_iomap_begin_non_resident(inode, offset, length, flags,
|
||||||
|
iomap, ntfs_iomap_flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ntfs_write_iomap_begin(struct inode *inode, loff_t offset,
|
||||||
|
loff_t length, unsigned int flags,
|
||||||
|
struct iomap *iomap, struct iomap *srcmap)
|
||||||
|
{
|
||||||
|
return __ntfs_write_iomap_begin(inode, offset, length, flags, iomap,
|
||||||
|
NTFS_IOMAP_FLAGS_BEGIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
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_attr_search_ctx *ctx;
|
||||||
|
u32 attr_len;
|
||||||
|
int err;
|
||||||
|
char *kattr;
|
||||||
|
|
||||||
|
mutex_lock(&ni->mrec_lock);
|
||||||
|
ctx = ntfs_attr_get_search_ctx(ni, NULL);
|
||||||
|
if (!ctx) {
|
||||||
|
written = -ENOMEM;
|
||||||
|
mutex_unlock(&ni->mrec_lock);
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len,
|
||||||
|
CASE_SENSITIVE, 0, NULL, 0, ctx);
|
||||||
|
if (err) {
|
||||||
|
if (err == -ENOENT)
|
||||||
|
err = -EIO;
|
||||||
|
written = err;
|
||||||
|
goto err_out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The total length of the attribute value. */
|
||||||
|
attr_len = le32_to_cpu(ctx->attr->data.resident.value_length);
|
||||||
|
if (pos >= attr_len || pos + written > attr_len)
|
||||||
|
goto err_out;
|
||||||
|
|
||||||
|
kattr = (u8 *)ctx->attr + le16_to_cpu(ctx->attr->data.resident.value_offset);
|
||||||
|
memcpy(kattr + pos, iomap_inline_data(iomap, pos), written);
|
||||||
|
mark_mft_record_dirty(ctx->ntfs_ino);
|
||||||
|
err_out:
|
||||||
|
ntfs_attr_put_search_ctx(ctx);
|
||||||
|
kfree(iomap->inline_data);
|
||||||
|
mutex_unlock(&ni->mrec_lock);
|
||||||
|
return written;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ntfs_write_iomap_end(struct inode *inode, loff_t pos, loff_t length,
|
||||||
|
ssize_t written, unsigned int flags,
|
||||||
|
struct iomap *iomap)
|
||||||
|
{
|
||||||
|
if (iomap->type == IOMAP_INLINE)
|
||||||
|
return ntfs_write_iomap_end_resident(inode, pos, length,
|
||||||
|
written, flags, iomap);
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct iomap_ops ntfs_write_iomap_ops = {
|
||||||
|
.iomap_begin = ntfs_write_iomap_begin,
|
||||||
|
.iomap_end = ntfs_write_iomap_end,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int ntfs_page_mkwrite_iomap_begin(struct inode *inode, loff_t offset,
|
||||||
|
loff_t length, unsigned int flags,
|
||||||
|
struct iomap *iomap, struct iomap *srcmap)
|
||||||
|
{
|
||||||
|
return __ntfs_write_iomap_begin(inode, offset, length, flags, iomap,
|
||||||
|
NTFS_IOMAP_FLAGS_MKWRITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct iomap_ops ntfs_page_mkwrite_iomap_ops = {
|
||||||
|
.iomap_begin = ntfs_page_mkwrite_iomap_begin,
|
||||||
|
.iomap_end = ntfs_write_iomap_end,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int ntfs_dio_iomap_begin(struct inode *inode, loff_t offset,
|
||||||
|
loff_t length, unsigned int flags,
|
||||||
|
struct iomap *iomap, struct iomap *srcmap)
|
||||||
|
{
|
||||||
|
return __ntfs_write_iomap_begin(inode, offset, length, flags, iomap,
|
||||||
|
NTFS_IOMAP_FLAGS_DIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct iomap_ops ntfs_dio_iomap_ops = {
|
||||||
|
.iomap_begin = ntfs_dio_iomap_begin,
|
||||||
|
.iomap_end = ntfs_write_iomap_end,
|
||||||
|
};
|
||||||
|
|
||||||
|
static ssize_t ntfs_writeback_range(struct iomap_writepage_ctx *wpc,
|
||||||
|
struct folio *folio, u64 offset, unsigned int len, u64 end_pos)
|
||||||
|
{
|
||||||
|
if (offset < wpc->iomap.offset ||
|
||||||
|
offset >= wpc->iomap.offset + wpc->iomap.length) {
|
||||||
|
int error;
|
||||||
|
|
||||||
|
error = __ntfs_write_iomap_begin(wpc->inode, offset,
|
||||||
|
NTFS_I(wpc->inode)->allocated_size - offset,
|
||||||
|
IOMAP_WRITE, &wpc->iomap,
|
||||||
|
NTFS_IOMAP_FLAGS_WRITEBACK);
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return iomap_add_to_ioend(wpc, folio, offset, end_pos, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct iomap_writeback_ops ntfs_writeback_ops = {
|
||||||
|
.writeback_range = ntfs_writeback_range,
|
||||||
|
.writeback_submit = iomap_ioend_writeback_submit,
|
||||||
|
};
|
||||||
23
fs/ntfs/iomap.h
Normal file
23
fs/ntfs/iomap.h
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2025 LG Electronics Co., Ltd.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _LINUX_NTFS_IOMAP_H
|
||||||
|
#define _LINUX_NTFS_IOMAP_H
|
||||||
|
|
||||||
|
#include <linux/pagemap.h>
|
||||||
|
#include <linux/iomap.h>
|
||||||
|
|
||||||
|
#include "volume.h"
|
||||||
|
#include "inode.h"
|
||||||
|
|
||||||
|
extern const struct iomap_ops ntfs_write_iomap_ops;
|
||||||
|
extern const struct iomap_ops ntfs_read_iomap_ops;
|
||||||
|
extern const struct iomap_ops ntfs_seek_iomap_ops;
|
||||||
|
extern const struct iomap_ops ntfs_page_mkwrite_iomap_ops;
|
||||||
|
extern const struct iomap_ops ntfs_dio_iomap_ops;
|
||||||
|
extern const struct iomap_writeback_ops ntfs_writeback_ops;
|
||||||
|
extern const struct iomap_write_ops ntfs_iomap_folio_ops;
|
||||||
|
extern int ntfs_dio_zero_range(struct inode *inode, loff_t offset, loff_t length);
|
||||||
|
#endif /* _LINUX_NTFS_IOMAP_H */
|
||||||
2346
fs/ntfs/layout.h
Normal file
2346
fs/ntfs/layout.h
Normal file
File diff suppressed because it is too large
Load Diff
1049
fs/ntfs/lcnalloc.c
Normal file
1049
fs/ntfs/lcnalloc.c
Normal file
File diff suppressed because it is too large
Load Diff
134
fs/ntfs/lcnalloc.h
Normal file
134
fs/ntfs/lcnalloc.h
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
/*
|
||||||
|
* Exports for NTFS kernel cluster (de)allocation.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2004-2005 Anton Altaparmakov
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _LINUX_NTFS_LCNALLOC_H
|
||||||
|
#define _LINUX_NTFS_LCNALLOC_H
|
||||||
|
|
||||||
|
#include <linux/sched/mm.h>
|
||||||
|
|
||||||
|
#include "attrib.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* enum zone_type - Zone identifiers for cluster allocation policy
|
||||||
|
*
|
||||||
|
* FIRST_ZONE For sanity checking.
|
||||||
|
* MFT_ZONE Allocate from $MFT zone.
|
||||||
|
* DATA_ZONE Allocate from $DATA zone.
|
||||||
|
* LAST_ZONE For sanity checking.
|
||||||
|
*/
|
||||||
|
enum {
|
||||||
|
FIRST_ZONE = 0,
|
||||||
|
MFT_ZONE = 0,
|
||||||
|
DATA_ZONE = 1,
|
||||||
|
LAST_ZONE = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct runlist_element *ntfs_cluster_alloc(struct ntfs_volume *vol,
|
||||||
|
const s64 start_vcn, const s64 count, const s64 start_lcn,
|
||||||
|
const int zone,
|
||||||
|
const bool is_extension,
|
||||||
|
const bool is_contig,
|
||||||
|
const bool is_dealloc);
|
||||||
|
s64 __ntfs_cluster_free(struct ntfs_inode *ni, const s64 start_vcn,
|
||||||
|
s64 count, struct ntfs_attr_search_ctx *ctx, const bool is_rollback);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_cluster_free - free clusters on an ntfs volume
|
||||||
|
* @ni: ntfs inode whose runlist describes the clusters to free
|
||||||
|
* @start_vcn: vcn in the runlist of @ni at which to start freeing clusters
|
||||||
|
* @count: number of clusters to free or -1 for all clusters
|
||||||
|
* @ctx: active attribute search context if present or NULL if not
|
||||||
|
*
|
||||||
|
* Free @count clusters starting at the cluster @start_vcn in the runlist
|
||||||
|
* described by the ntfs inode @ni.
|
||||||
|
*
|
||||||
|
* If @count is -1, all clusters from @start_vcn to the end of the runlist are
|
||||||
|
* deallocated. Thus, to completely free all clusters in a runlist, use
|
||||||
|
* @start_vcn = 0 and @count = -1.
|
||||||
|
*
|
||||||
|
* If @ctx is specified, it is an active search context of @ni and its base mft
|
||||||
|
* record. This is needed when ntfs_cluster_free() encounters unmapped runlist
|
||||||
|
* fragments and allows their mapping. If you do not have the mft record
|
||||||
|
* mapped, you can specify @ctx as NULL and ntfs_cluster_free() will perform
|
||||||
|
* the necessary mapping and unmapping.
|
||||||
|
*
|
||||||
|
* Note, ntfs_cluster_free() saves the state of @ctx on entry and restores it
|
||||||
|
* before returning. Thus, @ctx will be left pointing to the same attribute on
|
||||||
|
* return as on entry. However, the actual pointers in @ctx may point to
|
||||||
|
* different memory locations on return, so you must remember to reset any
|
||||||
|
* cached pointers from the @ctx, i.e. after the call to ntfs_cluster_free(),
|
||||||
|
* you will probably want to do:
|
||||||
|
* m = ctx->mrec;
|
||||||
|
* a = ctx->attr;
|
||||||
|
* Assuming you cache ctx->attr in a variable @a of type ATTR_RECORD * and that
|
||||||
|
* you cache ctx->mrec in a variable @m of type MFT_RECORD *.
|
||||||
|
*
|
||||||
|
* Note, ntfs_cluster_free() does not modify the runlist, so you have to remove
|
||||||
|
* from the runlist or mark sparse the freed runs later.
|
||||||
|
*
|
||||||
|
* Return the number of deallocated clusters (not counting sparse ones) on
|
||||||
|
* success and -errno on error.
|
||||||
|
*
|
||||||
|
* WARNING: If @ctx is supplied, regardless of whether success or failure is
|
||||||
|
* returned, you need to check IS_ERR(@ctx->mrec) and if 'true' the @ctx
|
||||||
|
* is no longer valid, i.e. you need to either call
|
||||||
|
* ntfs_attr_reinit_search_ctx() or ntfs_attr_put_search_ctx() on it.
|
||||||
|
* In that case PTR_ERR(@ctx->mrec) will give you the error code for
|
||||||
|
* why the mapping of the old inode failed.
|
||||||
|
*
|
||||||
|
* Locking: - The runlist described by @ni must be locked for writing on entry
|
||||||
|
* and is locked on return. Note the runlist may be modified when
|
||||||
|
* needed runlist fragments need to be mapped.
|
||||||
|
* - The volume lcn bitmap must be unlocked on entry and is unlocked
|
||||||
|
* on return.
|
||||||
|
* - This function takes the volume lcn bitmap lock for writing and
|
||||||
|
* modifies the bitmap contents.
|
||||||
|
* - If @ctx is NULL, the base mft record of @ni must not be mapped on
|
||||||
|
* entry and it will be left unmapped on return.
|
||||||
|
* - If @ctx is not NULL, the base mft record must be mapped on entry
|
||||||
|
* and it will be left mapped on return.
|
||||||
|
*/
|
||||||
|
static inline s64 ntfs_cluster_free(struct ntfs_inode *ni, const s64 start_vcn,
|
||||||
|
s64 count, struct ntfs_attr_search_ctx *ctx)
|
||||||
|
{
|
||||||
|
return __ntfs_cluster_free(ni, start_vcn, count, ctx, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ntfs_cluster_free_from_rl_nolock(struct ntfs_volume *vol,
|
||||||
|
const struct runlist_element *rl);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_cluster_free_from_rl - free clusters from runlist
|
||||||
|
* @vol: mounted ntfs volume on which to free the clusters
|
||||||
|
* @rl: runlist describing the clusters to free
|
||||||
|
*
|
||||||
|
* Free all the clusters described by the runlist @rl on the volume @vol. In
|
||||||
|
* the case of an error being returned, at least some of the clusters were not
|
||||||
|
* freed.
|
||||||
|
*
|
||||||
|
* Return 0 on success and -errno on error.
|
||||||
|
*
|
||||||
|
* Locking: - This function takes the volume lcn bitmap lock for writing and
|
||||||
|
* modifies the bitmap contents.
|
||||||
|
* - The caller must have locked the runlist @rl for reading or
|
||||||
|
* writing.
|
||||||
|
*/
|
||||||
|
static inline int ntfs_cluster_free_from_rl(struct ntfs_volume *vol,
|
||||||
|
const struct runlist_element *rl)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
unsigned int memalloc_flags;
|
||||||
|
|
||||||
|
memalloc_flags = memalloc_nofs_save();
|
||||||
|
down_write(&vol->lcnbmp_lock);
|
||||||
|
ret = ntfs_cluster_free_from_rl_nolock(vol, rl);
|
||||||
|
up_write(&vol->lcnbmp_lock);
|
||||||
|
memalloc_nofs_restore(memalloc_flags);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* defined _LINUX_NTFS_LCNALLOC_H */
|
||||||
778
fs/ntfs/logfile.c
Normal file
778
fs/ntfs/logfile.c
Normal file
@@ -0,0 +1,778 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* NTFS kernel journal handling.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2002-2007 Anton Altaparmakov
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/blkdev.h>
|
||||||
|
|
||||||
|
#include "attrib.h"
|
||||||
|
#include "logfile.h"
|
||||||
|
#include "ntfs.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_check_restart_page_header - check the page header for consistency
|
||||||
|
* @vi: LogFile inode to which the restart page header belongs
|
||||||
|
* @rp: restart page header to check
|
||||||
|
* @pos: position in @vi at which the restart page header resides
|
||||||
|
*
|
||||||
|
* Check the restart page header @rp for consistency and return 'true' if it is
|
||||||
|
* consistent and 'false' otherwise.
|
||||||
|
*
|
||||||
|
* This function only needs NTFS_BLOCK_SIZE bytes in @rp, i.e. it does not
|
||||||
|
* require the full restart page.
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
bool have_usa = true;
|
||||||
|
|
||||||
|
ntfs_debug("Entering.");
|
||||||
|
/*
|
||||||
|
* If the system or log page sizes are smaller than the ntfs block size
|
||||||
|
* or either is not a power of 2 we cannot handle this log file.
|
||||||
|
*/
|
||||||
|
logfile_system_page_size = le32_to_cpu(rp->system_page_size);
|
||||||
|
logfile_log_page_size = le32_to_cpu(rp->log_page_size);
|
||||||
|
if (logfile_system_page_size < NTFS_BLOCK_SIZE ||
|
||||||
|
logfile_log_page_size < NTFS_BLOCK_SIZE ||
|
||||||
|
logfile_system_page_size &
|
||||||
|
(logfile_system_page_size - 1) ||
|
||||||
|
!is_power_of_2(logfile_log_page_size)) {
|
||||||
|
ntfs_error(vi->i_sb, "LogFile uses unsupported page size.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* We must be either at !pos (1st restart page) or at pos = system page
|
||||||
|
* size (2nd restart page).
|
||||||
|
*/
|
||||||
|
if (pos && pos != logfile_system_page_size) {
|
||||||
|
ntfs_error(vi->i_sb, "Found restart area in incorrect position in LogFile.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/* We only know how to handle version 1.1. */
|
||||||
|
if (le16_to_cpu(rp->major_ver) != 1 ||
|
||||||
|
le16_to_cpu(rp->minor_ver) != 1) {
|
||||||
|
ntfs_error(vi->i_sb,
|
||||||
|
"LogFile version %i.%i is not supported. (This driver supports version 1.1 only.)",
|
||||||
|
(int)le16_to_cpu(rp->major_ver),
|
||||||
|
(int)le16_to_cpu(rp->minor_ver));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* If chkdsk has been run the restart page may not be protected by an
|
||||||
|
* update sequence array.
|
||||||
|
*/
|
||||||
|
if (ntfs_is_chkd_record(rp->magic) && !le16_to_cpu(rp->usa_count)) {
|
||||||
|
have_usa = false;
|
||||||
|
goto skip_usa_checks;
|
||||||
|
}
|
||||||
|
/* Verify the size of the update sequence array. */
|
||||||
|
usa_count = 1 + (logfile_system_page_size >> NTFS_BLOCK_SIZE_BITS);
|
||||||
|
if (usa_count != le16_to_cpu(rp->usa_count)) {
|
||||||
|
ntfs_error(vi->i_sb,
|
||||||
|
"LogFile restart page specifies inconsistent update sequence array count.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/* Verify the position of the update sequence array. */
|
||||||
|
usa_ofs = le16_to_cpu(rp->usa_ofs);
|
||||||
|
usa_end = usa_ofs + usa_count * sizeof(u16);
|
||||||
|
if (usa_ofs < sizeof(struct restart_page_header) ||
|
||||||
|
usa_end > NTFS_BLOCK_SIZE - sizeof(u16)) {
|
||||||
|
ntfs_error(vi->i_sb,
|
||||||
|
"LogFile restart page specifies inconsistent update sequence array offset.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
skip_usa_checks:
|
||||||
|
/*
|
||||||
|
* Verify the position of the restart area. It must be:
|
||||||
|
* - aligned to 8-byte boundary,
|
||||||
|
* - after the update sequence array, and
|
||||||
|
* - within the system page size.
|
||||||
|
*/
|
||||||
|
ra_ofs = le16_to_cpu(rp->restart_area_offset);
|
||||||
|
if (ra_ofs & 7 || (have_usa ? ra_ofs < usa_end :
|
||||||
|
ra_ofs < sizeof(struct restart_page_header)) ||
|
||||||
|
ra_ofs > logfile_system_page_size) {
|
||||||
|
ntfs_error(vi->i_sb,
|
||||||
|
"LogFile restart page specifies inconsistent restart area offset.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Only restart pages modified by chkdsk are allowed to have chkdsk_lsn
|
||||||
|
* set.
|
||||||
|
*/
|
||||||
|
if (!ntfs_is_chkd_record(rp->magic) && le64_to_cpu(rp->chkdsk_lsn)) {
|
||||||
|
ntfs_error(vi->i_sb,
|
||||||
|
"LogFile restart page is not modified by chkdsk but a chkdsk LSN is specified.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ntfs_debug("Done.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_check_restart_area - check the restart area for consistency
|
||||||
|
* @vi: LogFile inode to which the restart page belongs
|
||||||
|
* @rp: restart page whose restart area to check
|
||||||
|
*
|
||||||
|
* Check the restart area of the restart page @rp for consistency and return
|
||||||
|
* 'true' if it is consistent and 'false' otherwise.
|
||||||
|
*
|
||||||
|
* This function assumes that the restart page header has already been
|
||||||
|
* consistency checked.
|
||||||
|
*
|
||||||
|
* This function only needs NTFS_BLOCK_SIZE bytes in @rp, i.e. it does not
|
||||||
|
* require the full restart page.
|
||||||
|
*/
|
||||||
|
static bool ntfs_check_restart_area(struct inode *vi, struct restart_page_header *rp)
|
||||||
|
{
|
||||||
|
u64 file_size;
|
||||||
|
struct restart_area *ra;
|
||||||
|
u16 ra_ofs, ra_len, ca_ofs;
|
||||||
|
u8 fs_bits;
|
||||||
|
|
||||||
|
ntfs_debug("Entering.");
|
||||||
|
ra_ofs = le16_to_cpu(rp->restart_area_offset);
|
||||||
|
ra = (struct restart_area *)((u8 *)rp + ra_ofs);
|
||||||
|
/*
|
||||||
|
* Everything before ra->file_size must be before the first word
|
||||||
|
* protected by an update sequence number. This ensures that it is
|
||||||
|
* safe to access ra->client_array_offset.
|
||||||
|
*/
|
||||||
|
if (ra_ofs + offsetof(struct restart_area, file_size) >
|
||||||
|
NTFS_BLOCK_SIZE - sizeof(u16)) {
|
||||||
|
ntfs_error(vi->i_sb,
|
||||||
|
"LogFile restart area specifies inconsistent file offset.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Now that we can access ra->client_array_offset, make sure everything
|
||||||
|
* up to the log client array is before the first word protected by an
|
||||||
|
* update sequence number. This ensures we can access all of the
|
||||||
|
* restart area elements safely. Also, the client array offset must be
|
||||||
|
* aligned to an 8-byte boundary.
|
||||||
|
*/
|
||||||
|
ca_ofs = le16_to_cpu(ra->client_array_offset);
|
||||||
|
if (((ca_ofs + 7) & ~7) != ca_ofs ||
|
||||||
|
ra_ofs + ca_ofs > NTFS_BLOCK_SIZE - sizeof(u16)) {
|
||||||
|
ntfs_error(vi->i_sb,
|
||||||
|
"LogFile restart area specifies inconsistent client array offset.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* The restart area must end within the system page size both when
|
||||||
|
* calculated manually and as specified by ra->restart_area_length.
|
||||||
|
* Also, the calculated length must not exceed the specified length.
|
||||||
|
*/
|
||||||
|
ra_len = ca_ofs + le16_to_cpu(ra->log_clients) *
|
||||||
|
sizeof(struct log_client_record);
|
||||||
|
if (ra_ofs + ra_len > le32_to_cpu(rp->system_page_size) ||
|
||||||
|
ra_ofs + le16_to_cpu(ra->restart_area_length) >
|
||||||
|
le32_to_cpu(rp->system_page_size) ||
|
||||||
|
ra_len > le16_to_cpu(ra->restart_area_length)) {
|
||||||
|
ntfs_error(vi->i_sb,
|
||||||
|
"LogFile restart area is out of bounds of the system page size specified by the restart page header and/or the specified restart area length is inconsistent.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* The ra->client_free_list and ra->client_in_use_list must be either
|
||||||
|
* LOGFILE_NO_CLIENT or less than ra->log_clients or they are
|
||||||
|
* overflowing the client array.
|
||||||
|
*/
|
||||||
|
if ((ra->client_free_list != LOGFILE_NO_CLIENT &&
|
||||||
|
le16_to_cpu(ra->client_free_list) >=
|
||||||
|
le16_to_cpu(ra->log_clients)) ||
|
||||||
|
(ra->client_in_use_list != LOGFILE_NO_CLIENT &&
|
||||||
|
le16_to_cpu(ra->client_in_use_list) >=
|
||||||
|
le16_to_cpu(ra->log_clients))) {
|
||||||
|
ntfs_error(vi->i_sb,
|
||||||
|
"LogFile restart area specifies overflowing client free and/or in use lists.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Check ra->seq_number_bits against ra->file_size for consistency.
|
||||||
|
* We cannot just use ffs() because the file size is not a power of 2.
|
||||||
|
*/
|
||||||
|
file_size = le64_to_cpu(ra->file_size);
|
||||||
|
fs_bits = 0;
|
||||||
|
while (file_size) {
|
||||||
|
file_size >>= 1;
|
||||||
|
fs_bits++;
|
||||||
|
}
|
||||||
|
if (le32_to_cpu(ra->seq_number_bits) != 67 - fs_bits) {
|
||||||
|
ntfs_error(vi->i_sb,
|
||||||
|
"LogFile restart area specifies inconsistent sequence number bits.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/* The log record header length must be a multiple of 8. */
|
||||||
|
if (((le16_to_cpu(ra->log_record_header_length) + 7) & ~7) !=
|
||||||
|
le16_to_cpu(ra->log_record_header_length)) {
|
||||||
|
ntfs_error(vi->i_sb,
|
||||||
|
"LogFile restart area specifies inconsistent log record header length.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/* Dito for the log page data offset. */
|
||||||
|
if (((le16_to_cpu(ra->log_page_data_offset) + 7) & ~7) !=
|
||||||
|
le16_to_cpu(ra->log_page_data_offset)) {
|
||||||
|
ntfs_error(vi->i_sb,
|
||||||
|
"LogFile restart area specifies inconsistent log page data offset.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ntfs_debug("Done.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_check_log_client_array - check the log client array for consistency
|
||||||
|
* @vi: LogFile inode to which the restart page belongs
|
||||||
|
* @rp: restart page whose log client array to check
|
||||||
|
*
|
||||||
|
* Check the log client array of the restart page @rp for consistency and
|
||||||
|
* return 'true' if it is consistent and 'false' otherwise.
|
||||||
|
*
|
||||||
|
* This function assumes that the restart page header and the restart area have
|
||||||
|
* already been consistency checked.
|
||||||
|
*
|
||||||
|
* Unlike ntfs_check_restart_page_header() and ntfs_check_restart_area(), this
|
||||||
|
* function needs @rp->system_page_size bytes in @rp, i.e. it requires the full
|
||||||
|
* restart page and the page must be multi sector transfer deprotected.
|
||||||
|
*/
|
||||||
|
static bool ntfs_check_log_client_array(struct inode *vi,
|
||||||
|
struct restart_page_header *rp)
|
||||||
|
{
|
||||||
|
struct restart_area *ra;
|
||||||
|
struct log_client_record *ca, *cr;
|
||||||
|
u16 nr_clients, idx;
|
||||||
|
bool in_free_list, idx_is_first;
|
||||||
|
|
||||||
|
ntfs_debug("Entering.");
|
||||||
|
ra = (struct restart_area *)((u8 *)rp + le16_to_cpu(rp->restart_area_offset));
|
||||||
|
ca = (struct log_client_record *)((u8 *)ra +
|
||||||
|
le16_to_cpu(ra->client_array_offset));
|
||||||
|
/*
|
||||||
|
* Check the ra->client_free_list first and then check the
|
||||||
|
* ra->client_in_use_list. Check each of the log client records in
|
||||||
|
* each of the lists and check that the array does not overflow the
|
||||||
|
* ra->log_clients value. Also keep track of the number of records
|
||||||
|
* visited as there cannot be more than ra->log_clients records and
|
||||||
|
* that way we detect eventual loops in within a list.
|
||||||
|
*/
|
||||||
|
nr_clients = le16_to_cpu(ra->log_clients);
|
||||||
|
idx = le16_to_cpu(ra->client_free_list);
|
||||||
|
in_free_list = true;
|
||||||
|
check_list:
|
||||||
|
for (idx_is_first = true; idx != LOGFILE_NO_CLIENT_CPU; nr_clients--,
|
||||||
|
idx = le16_to_cpu(cr->next_client)) {
|
||||||
|
if (!nr_clients || idx >= le16_to_cpu(ra->log_clients))
|
||||||
|
goto err_out;
|
||||||
|
/* Set @cr to the current log client record. */
|
||||||
|
cr = ca + idx;
|
||||||
|
/* The first log client record must not have a prev_client. */
|
||||||
|
if (idx_is_first) {
|
||||||
|
if (cr->prev_client != LOGFILE_NO_CLIENT)
|
||||||
|
goto err_out;
|
||||||
|
idx_is_first = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Switch to and check the in use list if we just did the free list. */
|
||||||
|
if (in_free_list) {
|
||||||
|
in_free_list = false;
|
||||||
|
idx = le16_to_cpu(ra->client_in_use_list);
|
||||||
|
goto check_list;
|
||||||
|
}
|
||||||
|
ntfs_debug("Done.");
|
||||||
|
return true;
|
||||||
|
err_out:
|
||||||
|
ntfs_error(vi->i_sb, "LogFile log client array is corrupt.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_check_and_load_restart_page - check the restart page for consistency
|
||||||
|
* @vi: LogFile inode to which the restart page belongs
|
||||||
|
* @rp: restart page to check
|
||||||
|
* @pos: position in @vi at which the restart page resides
|
||||||
|
* @wrp: [OUT] copy of the multi sector transfer deprotected restart page
|
||||||
|
* @lsn: [OUT] set to the current logfile lsn on success
|
||||||
|
*
|
||||||
|
* Check the restart page @rp for consistency and return 0 if it is consistent
|
||||||
|
* and -errno otherwise. The restart page may have been modified by chkdsk in
|
||||||
|
* which case its magic is CHKD instead of RSTR.
|
||||||
|
*
|
||||||
|
* This function only needs NTFS_BLOCK_SIZE bytes in @rp, i.e. it does not
|
||||||
|
* require the full restart page.
|
||||||
|
*
|
||||||
|
* If @wrp is not NULL, on success, *@wrp will point to a buffer containing a
|
||||||
|
* copy of the complete multi sector transfer deprotected page. On failure,
|
||||||
|
* *@wrp is undefined.
|
||||||
|
*
|
||||||
|
* Simillarly, if @lsn is not NULL, on success *@lsn will be set to the current
|
||||||
|
* logfile lsn according to this restart page. On failure, *@lsn is undefined.
|
||||||
|
*
|
||||||
|
* The following error codes are defined:
|
||||||
|
* -EINVAL - The restart page is inconsistent.
|
||||||
|
* -ENOMEM - Not enough memory to load the restart page.
|
||||||
|
* -EIO - Failed to reading from LogFile.
|
||||||
|
*/
|
||||||
|
static int ntfs_check_and_load_restart_page(struct inode *vi,
|
||||||
|
struct restart_page_header *rp, s64 pos, struct restart_page_header **wrp,
|
||||||
|
s64 *lsn)
|
||||||
|
{
|
||||||
|
struct restart_area *ra;
|
||||||
|
struct restart_page_header *trp;
|
||||||
|
int size, err;
|
||||||
|
|
||||||
|
ntfs_debug("Entering.");
|
||||||
|
/* Check the restart page header for consistency. */
|
||||||
|
if (!ntfs_check_restart_page_header(vi, rp, pos)) {
|
||||||
|
/* Error output already done inside the function. */
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
/* Check the restart area for consistency. */
|
||||||
|
if (!ntfs_check_restart_area(vi, rp)) {
|
||||||
|
/* Error output already done inside the function. */
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
ra = (struct restart_area *)((u8 *)rp + le16_to_cpu(rp->restart_area_offset));
|
||||||
|
/*
|
||||||
|
* Allocate a buffer to store the whole restart page so we can multi
|
||||||
|
* sector transfer deprotect it.
|
||||||
|
*/
|
||||||
|
trp = kvzalloc(le32_to_cpu(rp->system_page_size), GFP_NOFS);
|
||||||
|
if (!trp) {
|
||||||
|
ntfs_error(vi->i_sb, "Failed to allocate memory for LogFile restart page buffer.");
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Read the whole of the restart page into the buffer. If it fits
|
||||||
|
* completely inside @rp, just copy it from there. Otherwise map all
|
||||||
|
* the required pages and copy the data from them.
|
||||||
|
*/
|
||||||
|
size = PAGE_SIZE - (pos & ~PAGE_MASK);
|
||||||
|
if (size >= le32_to_cpu(rp->system_page_size)) {
|
||||||
|
memcpy(trp, rp, le32_to_cpu(rp->system_page_size));
|
||||||
|
} else {
|
||||||
|
pgoff_t idx;
|
||||||
|
struct folio *folio;
|
||||||
|
int have_read, to_read;
|
||||||
|
|
||||||
|
/* First copy what we already have in @rp. */
|
||||||
|
memcpy(trp, rp, size);
|
||||||
|
/* Copy the remaining data one page at a time. */
|
||||||
|
have_read = size;
|
||||||
|
to_read = le32_to_cpu(rp->system_page_size) - size;
|
||||||
|
idx = (pos + size) >> PAGE_SHIFT;
|
||||||
|
do {
|
||||||
|
folio = read_mapping_folio(vi->i_mapping, idx, NULL);
|
||||||
|
if (IS_ERR(folio)) {
|
||||||
|
ntfs_error(vi->i_sb, "Error mapping LogFile page (index %lu).",
|
||||||
|
idx);
|
||||||
|
err = PTR_ERR(folio);
|
||||||
|
if (err != -EIO && err != -ENOMEM)
|
||||||
|
err = -EIO;
|
||||||
|
goto err_out;
|
||||||
|
}
|
||||||
|
size = min_t(int, to_read, PAGE_SIZE);
|
||||||
|
memcpy((u8 *)trp + have_read, folio_address(folio), size);
|
||||||
|
folio_put(folio);
|
||||||
|
have_read += size;
|
||||||
|
to_read -= size;
|
||||||
|
idx++;
|
||||||
|
} while (to_read > 0);
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Perform the multi sector transfer deprotection on the buffer if the
|
||||||
|
* restart page is protected.
|
||||||
|
*/
|
||||||
|
if ((!ntfs_is_chkd_record(trp->magic) || le16_to_cpu(trp->usa_count)) &&
|
||||||
|
post_read_mst_fixup((struct ntfs_record *)trp, le32_to_cpu(rp->system_page_size))) {
|
||||||
|
/*
|
||||||
|
* A multi sector transfer error was detected. We only need to
|
||||||
|
* abort if the restart page contents exceed the multi sector
|
||||||
|
* transfer fixup of the first sector.
|
||||||
|
*/
|
||||||
|
if (le16_to_cpu(rp->restart_area_offset) +
|
||||||
|
le16_to_cpu(ra->restart_area_length) >
|
||||||
|
NTFS_BLOCK_SIZE - sizeof(u16)) {
|
||||||
|
ntfs_error(vi->i_sb,
|
||||||
|
"Multi sector transfer error detected in LogFile restart page.");
|
||||||
|
err = -EINVAL;
|
||||||
|
goto err_out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* If the restart page is modified by chkdsk or there are no active
|
||||||
|
* logfile clients, the logfile is consistent. Otherwise, need to
|
||||||
|
* check the log client records for consistency, too.
|
||||||
|
*/
|
||||||
|
err = 0;
|
||||||
|
if (ntfs_is_rstr_record(rp->magic) &&
|
||||||
|
ra->client_in_use_list != LOGFILE_NO_CLIENT) {
|
||||||
|
if (!ntfs_check_log_client_array(vi, trp)) {
|
||||||
|
err = -EINVAL;
|
||||||
|
goto err_out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lsn) {
|
||||||
|
if (ntfs_is_rstr_record(rp->magic))
|
||||||
|
*lsn = le64_to_cpu(ra->current_lsn);
|
||||||
|
else /* if (ntfs_is_chkd_record(rp->magic)) */
|
||||||
|
*lsn = le64_to_cpu(rp->chkdsk_lsn);
|
||||||
|
}
|
||||||
|
ntfs_debug("Done.");
|
||||||
|
if (wrp)
|
||||||
|
*wrp = trp;
|
||||||
|
else {
|
||||||
|
err_out:
|
||||||
|
kvfree(trp);
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_check_logfile - check the journal for consistency
|
||||||
|
* @log_vi: struct inode of loaded journal LogFile to check
|
||||||
|
* @rp: [OUT] on success this is a copy of the current restart page
|
||||||
|
*
|
||||||
|
* Check the LogFile journal for consistency and return 'true' if it is
|
||||||
|
* consistent and 'false' if not. On success, the current restart page is
|
||||||
|
* returned in *@rp. Caller must call kvfree(*@rp) when finished with it.
|
||||||
|
*
|
||||||
|
* At present we only check the two restart pages and ignore the log record
|
||||||
|
* pages.
|
||||||
|
*
|
||||||
|
* Note that the MstProtected flag is not set on the LogFile inode and hence
|
||||||
|
* when reading pages they are not deprotected. This is because we do not know
|
||||||
|
* if the LogFile was created on a system with a different page size to ours
|
||||||
|
* yet and mst deprotection would fail if our page size is smaller.
|
||||||
|
*/
|
||||||
|
bool ntfs_check_logfile(struct inode *log_vi, struct restart_page_header **rp)
|
||||||
|
{
|
||||||
|
s64 size, pos;
|
||||||
|
s64 rstr1_lsn, rstr2_lsn;
|
||||||
|
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;
|
||||||
|
struct restart_page_header *rstr1_ph = NULL;
|
||||||
|
struct restart_page_header *rstr2_ph = NULL;
|
||||||
|
int log_page_size, err;
|
||||||
|
bool logfile_is_empty = true;
|
||||||
|
u8 log_page_bits;
|
||||||
|
|
||||||
|
ntfs_debug("Entering.");
|
||||||
|
/* An empty LogFile must have been clean before it got emptied. */
|
||||||
|
if (NVolLogFileEmpty(vol))
|
||||||
|
goto is_empty;
|
||||||
|
size = i_size_read(log_vi);
|
||||||
|
/* Make sure the file doesn't exceed the maximum allowed size. */
|
||||||
|
if (size > MaxLogFileSize)
|
||||||
|
size = MaxLogFileSize;
|
||||||
|
/*
|
||||||
|
* Truncate size to a multiple of the page cache size or the default
|
||||||
|
* log page size if the page cache size is between the default log page
|
||||||
|
* log page size if the page cache size is between the default log page
|
||||||
|
* size and twice that.
|
||||||
|
*/
|
||||||
|
if (DefaultLogPageSize <= PAGE_SIZE &&
|
||||||
|
DefaultLogPageSize * 2 <= PAGE_SIZE)
|
||||||
|
log_page_size = DefaultLogPageSize;
|
||||||
|
else
|
||||||
|
log_page_size = PAGE_SIZE;
|
||||||
|
/*
|
||||||
|
* Use ntfs_ffs() instead of ffs() to enable the compiler to
|
||||||
|
* optimize log_page_size and log_page_bits into constants.
|
||||||
|
*/
|
||||||
|
log_page_bits = ntfs_ffs(log_page_size) - 1;
|
||||||
|
size &= ~(s64)(log_page_size - 1);
|
||||||
|
/*
|
||||||
|
* Ensure the log file is big enough to store at least the two restart
|
||||||
|
* pages and the minimum number of log record pages.
|
||||||
|
*/
|
||||||
|
if (size < log_page_size * 2 || (size - log_page_size * 2) >>
|
||||||
|
log_page_bits < MinLogRecordPages) {
|
||||||
|
ntfs_error(vol->sb, "LogFile is too small.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Read through the file looking for a restart page. Since the restart
|
||||||
|
* page header is at the beginning of a page we only need to search at
|
||||||
|
* what could be the beginning of a page (for each page size) rather
|
||||||
|
* than scanning the whole file byte by byte. If all potential places
|
||||||
|
* contain empty and uninitialzed records, the log file can be assumed
|
||||||
|
* to be empty.
|
||||||
|
*/
|
||||||
|
for (pos = 0; pos < size; pos <<= 1) {
|
||||||
|
pgoff_t idx = pos >> PAGE_SHIFT;
|
||||||
|
|
||||||
|
if (!folio || folio->index != idx) {
|
||||||
|
if (folio) {
|
||||||
|
kunmap_local(kaddr);
|
||||||
|
folio_put(folio);
|
||||||
|
}
|
||||||
|
folio = read_mapping_folio(mapping, idx, NULL);
|
||||||
|
if (IS_ERR(folio)) {
|
||||||
|
ntfs_error(vol->sb, "Error mapping LogFile page (index %lu).",
|
||||||
|
idx);
|
||||||
|
goto err_out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
kaddr = (u8 *)kmap_local_folio(folio, 0) + (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
|
||||||
|
* means we are done.
|
||||||
|
*/
|
||||||
|
if (!ntfs_is_empty_recordp((__le32 *)kaddr))
|
||||||
|
logfile_is_empty = false;
|
||||||
|
else if (!logfile_is_empty)
|
||||||
|
break;
|
||||||
|
/*
|
||||||
|
* A log record page means there cannot be a restart page after
|
||||||
|
* this so no need to continue searching.
|
||||||
|
*/
|
||||||
|
if (ntfs_is_rcrd_recordp((__le32 *)kaddr))
|
||||||
|
break;
|
||||||
|
/* If not a (modified by chkdsk) restart page, continue. */
|
||||||
|
if (!ntfs_is_rstr_recordp((__le32 *)kaddr) &&
|
||||||
|
!ntfs_is_chkd_recordp((__le32 *)kaddr)) {
|
||||||
|
if (!pos)
|
||||||
|
pos = NTFS_BLOCK_SIZE >> 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Check the (modified by chkdsk) restart page for consistency
|
||||||
|
* and get a copy of the complete multi sector transfer
|
||||||
|
* deprotected restart page.
|
||||||
|
*/
|
||||||
|
err = ntfs_check_and_load_restart_page(log_vi,
|
||||||
|
(struct restart_page_header *)kaddr, pos,
|
||||||
|
!rstr1_ph ? &rstr1_ph : &rstr2_ph,
|
||||||
|
!rstr1_ph ? &rstr1_lsn : &rstr2_lsn);
|
||||||
|
if (!err) {
|
||||||
|
/*
|
||||||
|
* If we have now found the first (modified by chkdsk)
|
||||||
|
* restart page, continue looking for the second one.
|
||||||
|
*/
|
||||||
|
if (!pos) {
|
||||||
|
pos = NTFS_BLOCK_SIZE >> 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* We have now found the second (modified by chkdsk)
|
||||||
|
* restart page, so we can stop looking.
|
||||||
|
*/
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Error output already done inside the function. Note, we do
|
||||||
|
* not abort if the restart page was invalid as we might still
|
||||||
|
* find a valid one further in the file.
|
||||||
|
*/
|
||||||
|
if (err != -EINVAL) {
|
||||||
|
kunmap_local(kaddr);
|
||||||
|
folio_put(folio);
|
||||||
|
goto err_out;
|
||||||
|
}
|
||||||
|
/* Continue looking. */
|
||||||
|
if (!pos)
|
||||||
|
pos = NTFS_BLOCK_SIZE >> 1;
|
||||||
|
}
|
||||||
|
if (folio) {
|
||||||
|
kunmap_local(kaddr);
|
||||||
|
folio_put(folio);
|
||||||
|
}
|
||||||
|
if (logfile_is_empty) {
|
||||||
|
NVolSetLogFileEmpty(vol);
|
||||||
|
is_empty:
|
||||||
|
ntfs_debug("Done. (LogFile is empty.)");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!rstr1_ph) {
|
||||||
|
ntfs_error(vol->sb,
|
||||||
|
"Did not find any restart pages in LogFile and it was not empty.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/* If both restart pages were found, use the more recent one. */
|
||||||
|
if (rstr2_ph) {
|
||||||
|
/*
|
||||||
|
* If the second restart area is more recent, switch to it.
|
||||||
|
* Otherwise just throw it away.
|
||||||
|
*/
|
||||||
|
if (rstr2_lsn > rstr1_lsn) {
|
||||||
|
ntfs_debug("Using second restart page as it is more recent.");
|
||||||
|
kvfree(rstr1_ph);
|
||||||
|
rstr1_ph = rstr2_ph;
|
||||||
|
/* rstr1_lsn = rstr2_lsn; */
|
||||||
|
} else {
|
||||||
|
ntfs_debug("Using first restart page as it is more recent.");
|
||||||
|
kvfree(rstr2_ph);
|
||||||
|
}
|
||||||
|
rstr2_ph = NULL;
|
||||||
|
}
|
||||||
|
/* All consistency checks passed. */
|
||||||
|
if (rp)
|
||||||
|
*rp = rstr1_ph;
|
||||||
|
else
|
||||||
|
kvfree(rstr1_ph);
|
||||||
|
ntfs_debug("Done.");
|
||||||
|
return true;
|
||||||
|
err_out:
|
||||||
|
if (rstr1_ph)
|
||||||
|
kvfree(rstr1_ph);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_empty_logfile - empty the contents of the LogFile journal
|
||||||
|
* @log_vi: struct inode of loaded journal LogFile to empty
|
||||||
|
*
|
||||||
|
* Empty the contents of the LogFile journal @log_vi and return 'true' on
|
||||||
|
* success and 'false' on error.
|
||||||
|
*
|
||||||
|
* This function assumes that the LogFile journal has already been consistency
|
||||||
|
* checked by a call to ntfs_check_logfile() and that ntfs_is_logfile_clean()
|
||||||
|
* has been used to ensure that the LogFile is clean.
|
||||||
|
*/
|
||||||
|
bool ntfs_empty_logfile(struct inode *log_vi)
|
||||||
|
{
|
||||||
|
s64 vcn, end_vcn;
|
||||||
|
struct ntfs_inode *log_ni = NTFS_I(log_vi);
|
||||||
|
struct ntfs_volume *vol = log_ni->vol;
|
||||||
|
struct super_block *sb = vol->sb;
|
||||||
|
struct runlist_element *rl;
|
||||||
|
unsigned long flags;
|
||||||
|
int err;
|
||||||
|
bool should_wait = true;
|
||||||
|
char *empty_buf = NULL;
|
||||||
|
struct file_ra_state *ra = NULL;
|
||||||
|
|
||||||
|
ntfs_debug("Entering.");
|
||||||
|
if (NVolLogFileEmpty(vol)) {
|
||||||
|
ntfs_debug("Done.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We cannot use ntfs_attr_set() because we may be still in the middle
|
||||||
|
* of a mount operation. Thus we do the emptying by hand by first
|
||||||
|
* zapping the page cache pages for the LogFile/DATA attribute and
|
||||||
|
* then emptying each of the buffers in each of the clusters specified
|
||||||
|
* by the runlist by hand.
|
||||||
|
*/
|
||||||
|
vcn = 0;
|
||||||
|
read_lock_irqsave(&log_ni->size_lock, flags);
|
||||||
|
end_vcn = (log_ni->initialized_size + vol->cluster_size_mask) >>
|
||||||
|
vol->cluster_size_bits;
|
||||||
|
read_unlock_irqrestore(&log_ni->size_lock, flags);
|
||||||
|
truncate_inode_pages(log_vi->i_mapping, 0);
|
||||||
|
down_write(&log_ni->runlist.lock);
|
||||||
|
rl = log_ni->runlist.rl;
|
||||||
|
if (unlikely(!rl || vcn < rl->vcn || !rl->length)) {
|
||||||
|
map_vcn:
|
||||||
|
err = ntfs_map_runlist_nolock(log_ni, vcn, NULL);
|
||||||
|
if (err) {
|
||||||
|
ntfs_error(sb, "Failed to map runlist fragment (error %d).", -err);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
rl = log_ni->runlist.rl;
|
||||||
|
}
|
||||||
|
/* Seek to the runlist element containing @vcn. */
|
||||||
|
while (rl->length && vcn >= rl[1].vcn)
|
||||||
|
rl++;
|
||||||
|
|
||||||
|
err = -ENOMEM;
|
||||||
|
empty_buf = kvzalloc(vol->cluster_size, GFP_NOFS);
|
||||||
|
if (!empty_buf)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
memset(empty_buf, 0xff, vol->cluster_size);
|
||||||
|
|
||||||
|
ra = kzalloc(sizeof(*ra), GFP_NOFS);
|
||||||
|
if (!ra)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
file_ra_state_init(ra, sb->s_bdev->bd_mapping);
|
||||||
|
do {
|
||||||
|
s64 lcn;
|
||||||
|
loff_t start, end;
|
||||||
|
s64 len;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If this run is not mapped map it now and start again as the
|
||||||
|
* runlist will have been updated.
|
||||||
|
*/
|
||||||
|
lcn = rl->lcn;
|
||||||
|
if (unlikely(lcn == LCN_RL_NOT_MAPPED)) {
|
||||||
|
vcn = rl->vcn;
|
||||||
|
kvfree(empty_buf);
|
||||||
|
goto map_vcn;
|
||||||
|
}
|
||||||
|
/* If this run is not valid abort with an error. */
|
||||||
|
if (unlikely(!rl->length || lcn < LCN_HOLE))
|
||||||
|
goto rl_err;
|
||||||
|
/* Skip holes. */
|
||||||
|
if (lcn == LCN_HOLE)
|
||||||
|
continue;
|
||||||
|
start = NTFS_CLU_TO_B(vol, lcn);
|
||||||
|
len = rl->length;
|
||||||
|
if (rl[1].vcn > end_vcn)
|
||||||
|
len = end_vcn - rl->vcn;
|
||||||
|
end = NTFS_CLU_TO_B(vol, lcn + len);
|
||||||
|
|
||||||
|
page_cache_sync_readahead(sb->s_bdev->bd_mapping, ra, NULL,
|
||||||
|
start >> PAGE_SHIFT, (end - start) >> PAGE_SHIFT);
|
||||||
|
|
||||||
|
do {
|
||||||
|
err = ntfs_bdev_write(sb, empty_buf, start,
|
||||||
|
vol->cluster_size);
|
||||||
|
if (err) {
|
||||||
|
ntfs_error(sb, "ntfs_dev_write failed, err : %d\n", err);
|
||||||
|
goto io_err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Submit the buffer and wait for i/o to complete but
|
||||||
|
* only for the first buffer so we do not miss really
|
||||||
|
* serious i/o errors. Once the first buffer has
|
||||||
|
* completed ignore errors afterwards as we can assume
|
||||||
|
* that if one buffer worked all of them will work.
|
||||||
|
*/
|
||||||
|
if (should_wait) {
|
||||||
|
should_wait = false;
|
||||||
|
err = filemap_write_and_wait_range(sb->s_bdev->bd_mapping,
|
||||||
|
start, start + vol->cluster_size);
|
||||||
|
if (err)
|
||||||
|
goto io_err;
|
||||||
|
}
|
||||||
|
start += vol->cluster_size;
|
||||||
|
} while (start < end);
|
||||||
|
} while ((++rl)->vcn < end_vcn);
|
||||||
|
up_write(&log_ni->runlist.lock);
|
||||||
|
kfree(empty_buf);
|
||||||
|
kfree(ra);
|
||||||
|
truncate_inode_pages(log_vi->i_mapping, 0);
|
||||||
|
/* Set the flag so we do not have to do it again on remount. */
|
||||||
|
NVolSetLogFileEmpty(vol);
|
||||||
|
ntfs_debug("Done.");
|
||||||
|
return true;
|
||||||
|
io_err:
|
||||||
|
ntfs_error(sb, "Failed to write buffer. Unmount and run chkdsk.");
|
||||||
|
goto dirty_err;
|
||||||
|
rl_err:
|
||||||
|
ntfs_error(sb, "Runlist is corrupt. Unmount and run chkdsk.");
|
||||||
|
dirty_err:
|
||||||
|
NVolSetErrors(vol);
|
||||||
|
err = -EIO;
|
||||||
|
err:
|
||||||
|
kvfree(empty_buf);
|
||||||
|
kfree(ra);
|
||||||
|
up_write(&log_ni->runlist.lock);
|
||||||
|
ntfs_error(sb, "Failed to fill LogFile with 0xff bytes (error %d).",
|
||||||
|
-err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
245
fs/ntfs/logfile.h
Normal file
245
fs/ntfs/logfile.h
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
/*
|
||||||
|
* Defines for NTFS kernel journal (LogFile) handling.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2000-2005 Anton Altaparmakov
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _LINUX_NTFS_LOGFILE_H
|
||||||
|
#define _LINUX_NTFS_LOGFILE_H
|
||||||
|
|
||||||
|
#include "layout.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Journal (LogFile) organization:
|
||||||
|
*
|
||||||
|
* Two restart areas present in the first two pages (restart pages, one restart
|
||||||
|
* area in each page). When the volume is dismounted they should be identical,
|
||||||
|
* except for the update sequence array which usually has a different update
|
||||||
|
* sequence number.
|
||||||
|
*
|
||||||
|
* These are followed by log records organized in pages headed by a log record
|
||||||
|
* header going up to log file size. Not all pages contain log records when a
|
||||||
|
* volume is first formatted, but as the volume ages, all records will be used.
|
||||||
|
* When the log file fills up, the records at the beginning are purged (by
|
||||||
|
* modifying the oldest_lsn to a higher value presumably) and writing begins
|
||||||
|
* at the beginning of the file. Effectively, the log file is viewed as a
|
||||||
|
* circular entity.
|
||||||
|
*
|
||||||
|
* NOTE: Windows NT, 2000, and XP all use log file version 1.1 but they accept
|
||||||
|
* versions <= 1.x, including 0.-1. (Yes, that is a minus one in there!) We
|
||||||
|
* probably only want to support 1.1 as this seems to be the current version
|
||||||
|
* and we don't know how that differs from the older versions. The only
|
||||||
|
* exception is if the journal is clean as marked by the two restart pages
|
||||||
|
* then it doesn't matter whether we are on an earlier version. We can just
|
||||||
|
* reinitialize the logfile and start again with version 1.1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Some LogFile related constants. */
|
||||||
|
#define MaxLogFileSize 0x100000000ULL
|
||||||
|
#define DefaultLogPageSize 4096
|
||||||
|
#define MinLogRecordPages 48
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Log file restart page header (begins the restart area).
|
||||||
|
*
|
||||||
|
* @magic: The magic is "RSTR".
|
||||||
|
* @usa_ofs: See ntfs_record struct definition in layout.h. When creating,
|
||||||
|
* set this to be immediately after this header structure (without any
|
||||||
|
* alignment).
|
||||||
|
* @usa_count: See ntfs_record struct definition in layout.h.
|
||||||
|
* @chkdsk_lsn: The last log file sequence number found by chkdsk. Only
|
||||||
|
* used when the magic is changed to "CHKD". Otherwise this is zero.
|
||||||
|
* @system_page_size: Byte size of system pages when the log file was
|
||||||
|
* created, has to be >= 512 and a power of 2. Use this to calculate
|
||||||
|
* the required size of the usa (usa_count) and add it to usa_ofs. Then
|
||||||
|
* verify that the result is less than the value of
|
||||||
|
* the restart_area_offset.
|
||||||
|
* @log_page_size: Byte size of log file pages, has to be >= 512 and
|
||||||
|
* a power of 2. The default is 4096 and is used when the system page
|
||||||
|
* size is between 4096 and 8192. Otherwise this is set to the system
|
||||||
|
* page size instead.
|
||||||
|
* @restart_area_offset: Byte offset from the start of this header to
|
||||||
|
* the RESTART_AREA. Value has to be aligned to 8-byte boundary. When
|
||||||
|
* creating, set this to be after the usa.
|
||||||
|
* @minor_ver: Log file minor version. Only check if major version is 1.
|
||||||
|
* @major_ver: Log file major version. We only support version 1.1.
|
||||||
|
*/
|
||||||
|
struct restart_page_header {
|
||||||
|
__le32 magic;
|
||||||
|
__le16 usa_ofs;
|
||||||
|
__le16 usa_count;
|
||||||
|
__le64 chkdsk_lsn;
|
||||||
|
__le32 system_page_size;
|
||||||
|
__le32 log_page_size;
|
||||||
|
__le16 restart_area_offset;
|
||||||
|
__le16 minor_ver;
|
||||||
|
__le16 major_ver;
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Constant for the log client indices meaning that there are no client records
|
||||||
|
* in this particular client array. Also inside the client records themselves,
|
||||||
|
* this means that there are no client records preceding or following this one.
|
||||||
|
*/
|
||||||
|
#define LOGFILE_NO_CLIENT cpu_to_le16(0xffff)
|
||||||
|
#define LOGFILE_NO_CLIENT_CPU 0xffff
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These are the so far known RESTART_AREA_* flags (16-bit) which contain
|
||||||
|
* information about the log file in which they are present.
|
||||||
|
* gcc: Force enum bit width to 16.
|
||||||
|
*/
|
||||||
|
enum {
|
||||||
|
RESTART_VOLUME_IS_CLEAN = cpu_to_le16(0x0002),
|
||||||
|
RESTART_SPACE_FILLER = cpu_to_le16(0xffff),
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Log file restart area record. The offset of this record is found by adding
|
||||||
|
* the offset of the RESTART_PAGE_HEADER to the restart_area_offset value found
|
||||||
|
* in it. See notes at restart_area_offset above.
|
||||||
|
*
|
||||||
|
* @current_lsn: The current, i.e. last LSN inside the log when
|
||||||
|
* the restart area was last written. This happens often but what is
|
||||||
|
* the interval? Is it just fixed time or is it every time a check point
|
||||||
|
* is written or somethine else? On create set to 0.
|
||||||
|
* @log_clients: Number of log client records in the array of log client
|
||||||
|
* records which follows this restart area. Must be 1.
|
||||||
|
* @client_free_list: The index of the first free log client record in
|
||||||
|
* the array of log client records. LOGFILE_NO_CLIENT means that there
|
||||||
|
* are no free log client records in the array. If != LOGFILE_NO_CLIENT,
|
||||||
|
* check that log_clients > client_free_list. On Win2k and presumably
|
||||||
|
* earlier, on a clean volume this is != LOGFILE_NO_CLIENT, and it should
|
||||||
|
* be 0, i.e. the first (and only) client record is free and thus
|
||||||
|
* the logfile is closed and hence clean. A dirty volume would have left
|
||||||
|
* the logfile open and hence this would be LOGFILE_NO_CLIENT. On WinXP
|
||||||
|
* and presumably later, the logfile is always open, even on clean
|
||||||
|
* shutdown so this should always be LOGFILE_NO_CLIENT.
|
||||||
|
* @client_in_use_list: The index of the first in-use log client record in
|
||||||
|
* the array of log client records. LOGFILE_NO_CLIENT means that there
|
||||||
|
* are no in-use log client records in the array.
|
||||||
|
* If != LOGFILE_NO_CLIENT check that log_clients > client_in_use_list.
|
||||||
|
* On Win2k and presumably earlier, on a clean volume this is
|
||||||
|
* LOGFILE_NO_CLIENT, i.e. there are no client records in use and thus
|
||||||
|
* the logfile is closed and hence clean. A dirty volume would have left
|
||||||
|
* the logfile open and hence this would be != LOGFILE_NO_CLIENT, and it
|
||||||
|
* should be 0, i.e. the first (and only) client record is in use. On
|
||||||
|
* WinXP and presumably later, the logfile is always open, even on clean
|
||||||
|
* shutdown so this should always be 0.
|
||||||
|
* @flags: Flags modifying LFS behaviour. On Win2k and presumably earlier
|
||||||
|
* this is always 0. On WinXP and presumably later, if the logfile was
|
||||||
|
* shutdown cleanly, the second bit, RESTART_VOLUME_IS_CLEAN, is set.
|
||||||
|
* This bit is cleared when the volume is mounted by WinXP and set when
|
||||||
|
* the volume is dismounted, thus if the logfile is dirty, this bit is
|
||||||
|
* clear. Thus we don't need to check the Windows version to determine
|
||||||
|
* if the logfile is clean. Instead if the logfile is closed, we know
|
||||||
|
* it must be clean. If it is open and this bit is set, we also know
|
||||||
|
* it must be clean. If on the other hand the logfile is open and this
|
||||||
|
* bit is clear, we can be almost certain that the logfile is dirty.
|
||||||
|
* @seq_number_bits: How many bits to use for the sequence number. This
|
||||||
|
* is calculated as 67 - the number of bits required to store the logfile
|
||||||
|
* size in bytes and this can be used in with the specified file_size as
|
||||||
|
* a consistency check.
|
||||||
|
* @restart_area_length: Length of the restart area including the client
|
||||||
|
* array. Following checks required if version matches. Otherwise,
|
||||||
|
* skip them. restart_area_offset + restart_area_length has to be
|
||||||
|
* <= system_page_size. Also, restart_area_length has to be >=
|
||||||
|
* client_array_offset + (log_clients * sizeof(log client record)).
|
||||||
|
* @client_array_offset: Offset from the start of this record to the first
|
||||||
|
* log client record if versions are matched. When creating, set this
|
||||||
|
* to be after this restart area structure, aligned to 8-bytes boundary.
|
||||||
|
* If the versions do not match, this is ignored and the offset is
|
||||||
|
* assumed to be (sizeof(RESTART_AREA) + 7) & ~7, i.e. rounded up to
|
||||||
|
* first 8-byte boundary. Either way, client_array_offset has to be
|
||||||
|
* aligned to an 8-byte boundary. Also, restart_area_offset +
|
||||||
|
* client_array_offset has to be <= 510. Finally, client_array_offset +
|
||||||
|
* (log_clients * sizeof(log client record)) has to be <= system_page_size.
|
||||||
|
* On Win2k and presumably earlier, this is 0x30, i.e. immediately
|
||||||
|
* following this record. On WinXP and presumably later, this is 0x40,
|
||||||
|
* i.e. there are 16 extra bytes between this record and the client
|
||||||
|
* array. This probably means that the RESTART_AREA record is actually
|
||||||
|
* bigger in WinXP and later.
|
||||||
|
* @file_size: Usable byte size of the log file.
|
||||||
|
* If the restart_area_offset + the offset of the file_size are > 510
|
||||||
|
* then corruption has occurred. This is the very first check when
|
||||||
|
* starting with the restart_area as if it fails it means that some of
|
||||||
|
* the above values will be corrupted by the multi sector transfer
|
||||||
|
* protection. The file_size has to be rounded down to be a multiple
|
||||||
|
* of the log_page_size in the RESTART_PAGE_HEADER and then it has to be
|
||||||
|
* at least big enough to store the two restart pages and 48 (0x30) log
|
||||||
|
* record pages.
|
||||||
|
* @last_lsn_data_length: Length of data of last LSN, not including the log
|
||||||
|
* record header. On create set to 0.
|
||||||
|
* @log_record_header_length: Byte size of the log record header. If the
|
||||||
|
* version matches then check that the value of log_record_header_length
|
||||||
|
* is a multiple of 8, i.e. (log_record_header_length + 7) & ~7 ==
|
||||||
|
* log_record_header_length. When creating set it to
|
||||||
|
* sizeof(LOG_RECORD_HEADER), aligned to 8 bytes.
|
||||||
|
* @log_page_data_offset: Offset to the start of data in a log record page.
|
||||||
|
* Must be a multiple of 8. On create set it to immediately after
|
||||||
|
* the update sequence array of the log record page.
|
||||||
|
* @restart_log_open_count: A counter that gets incremented every time
|
||||||
|
* the logfile is restarted which happens at mount time when the logfile
|
||||||
|
* is opened. When creating set to a random value. Win2k sets it to
|
||||||
|
* the low 32 bits of the current system time in NTFS format (see time.h).
|
||||||
|
* @reserved: Reserved/alignment to 8-byte boundary.
|
||||||
|
*/
|
||||||
|
struct restart_area {
|
||||||
|
__le64 current_lsn;
|
||||||
|
__le16 log_clients;
|
||||||
|
__le16 client_free_list;
|
||||||
|
__le16 client_in_use_list;
|
||||||
|
__le16 flags;
|
||||||
|
__le32 seq_number_bits;
|
||||||
|
__le16 restart_area_length;
|
||||||
|
__le16 client_array_offset;
|
||||||
|
__le64 file_size;
|
||||||
|
__le32 last_lsn_data_length;
|
||||||
|
__le16 log_record_header_length;
|
||||||
|
__le16 log_page_data_offset;
|
||||||
|
__le32 restart_log_open_count;
|
||||||
|
__le32 reserved;
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Log client record. The offset of this record is found by adding the offset
|
||||||
|
* of the RESTART_AREA to the client_array_offset value found in it.
|
||||||
|
*
|
||||||
|
* @oldest_lsn: Oldest LSN needed by this client. On create set to 0.
|
||||||
|
* @client_restart_lsn: LSN at which this client needs to restart
|
||||||
|
* the volume, i.e. the current position within the log file.
|
||||||
|
* At present, if clean this should = current_lsn in restart area but it
|
||||||
|
* probably also = current_lsn when dirty most of the time.
|
||||||
|
* At create set to 0.
|
||||||
|
* @prev_client: The offset to the previous log client record in the array
|
||||||
|
* of log client records. LOGFILE_NO_CLIENT means there is no previous
|
||||||
|
* client record, i.e. this is the first one. This is always
|
||||||
|
* LOGFILE_NO_CLIENT.
|
||||||
|
* @next_client: The offset to the next log client record in the array of
|
||||||
|
* log client records. LOGFILE_NO_CLIENT means there are no next client
|
||||||
|
* records, i.e. this is the last one. This is always LOGFILE_NO_CLIENT.
|
||||||
|
* @seq_number: On Win2k and presumably earlier, this is set to zero every
|
||||||
|
* time the logfile is restarted and it is incremented when the logfile
|
||||||
|
* is closed at dismount time. Thus it is 0 when dirty and 1 when clean.
|
||||||
|
* On WinXP and presumably later, this is always 0.
|
||||||
|
* @reserved[6]: Reserved/alignment.
|
||||||
|
* @client_name_length: Length of client name in bytes. Should always be 8.
|
||||||
|
* @client_name[64]: Name of the client in Unicode. Should always be "NTFS"
|
||||||
|
* with the remaining bytes set to 0.
|
||||||
|
*/
|
||||||
|
struct log_client_record {
|
||||||
|
__le64 oldest_lsn;
|
||||||
|
__le64 client_restart_lsn;
|
||||||
|
__le16 prev_client;
|
||||||
|
__le16 next_client;
|
||||||
|
__le16 seq_number;
|
||||||
|
u8 reserved[6];
|
||||||
|
__le32 client_name_length;
|
||||||
|
__le16 client_name[64];
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
bool ntfs_check_logfile(struct inode *log_vi,
|
||||||
|
struct restart_page_header **rp);
|
||||||
|
bool ntfs_empty_logfile(struct inode *log_vi);
|
||||||
|
#endif /* _LINUX_NTFS_LOGFILE_H */
|
||||||
77
fs/ntfs/malloc.h
Normal file
77
fs/ntfs/malloc.h
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
/*
|
||||||
|
* malloc.h - NTFS kernel memory handling. Part of the Linux-NTFS project.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2001-2005 Anton Altaparmakov
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _LINUX_NTFS_MALLOC_H
|
||||||
|
#define _LINUX_NTFS_MALLOC_H
|
||||||
|
|
||||||
|
#include <linux/vmalloc.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/highmem.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __ntfs_malloc - allocate memory in multiples of pages
|
||||||
|
* @size: number of bytes to allocate
|
||||||
|
* @gfp_mask: extra flags for the allocator
|
||||||
|
*
|
||||||
|
* Internal function. You probably want ntfs_malloc_nofs()...
|
||||||
|
*
|
||||||
|
* Allocates @size bytes of memory, rounded up to multiples of PAGE_SIZE and
|
||||||
|
* returns a pointer to the allocated memory.
|
||||||
|
*
|
||||||
|
* If there was insufficient memory to complete the request, return NULL.
|
||||||
|
* Depending on @gfp_mask the allocation may be guaranteed to succeed.
|
||||||
|
*/
|
||||||
|
static inline void *__ntfs_malloc(unsigned long size, gfp_t gfp_mask)
|
||||||
|
{
|
||||||
|
if (likely(size <= PAGE_SIZE)) {
|
||||||
|
BUG_ON(!size);
|
||||||
|
/* kmalloc() has per-CPU caches so is faster for now. */
|
||||||
|
return kmalloc(PAGE_SIZE, gfp_mask & ~__GFP_HIGHMEM);
|
||||||
|
/* return (void *)__get_free_page(gfp_mask); */
|
||||||
|
}
|
||||||
|
if (likely((size >> PAGE_SHIFT) < totalram_pages()))
|
||||||
|
return __vmalloc(size, gfp_mask);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ntfs_malloc_nofs - allocate memory in multiples of pages
|
||||||
|
* @size: number of bytes to allocate
|
||||||
|
*
|
||||||
|
* Allocates @size bytes of memory, rounded up to multiples of PAGE_SIZE and
|
||||||
|
* returns a pointer to the allocated memory.
|
||||||
|
*
|
||||||
|
* If there was insufficient memory to complete the request, return NULL.
|
||||||
|
*/
|
||||||
|
static inline void *ntfs_malloc_nofs(unsigned long size)
|
||||||
|
{
|
||||||
|
return __ntfs_malloc(size, GFP_NOFS | __GFP_HIGHMEM);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ntfs_malloc_nofs_nofail - allocate memory in multiples of pages
|
||||||
|
* @size: number of bytes to allocate
|
||||||
|
*
|
||||||
|
* Allocates @size bytes of memory, rounded up to multiples of PAGE_SIZE and
|
||||||
|
* returns a pointer to the allocated memory.
|
||||||
|
*
|
||||||
|
* This function guarantees that the allocation will succeed. It will sleep
|
||||||
|
* for as long as it takes to complete the allocation.
|
||||||
|
*
|
||||||
|
* If there was insufficient memory to complete the request, return NULL.
|
||||||
|
*/
|
||||||
|
static inline void *ntfs_malloc_nofs_nofail(unsigned long size)
|
||||||
|
{
|
||||||
|
return __ntfs_malloc(size, GFP_NOFS | __GFP_HIGHMEM | __GFP_NOFAIL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void ntfs_free(void *addr)
|
||||||
|
{
|
||||||
|
kvfree(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* _LINUX_NTFS_MALLOC_H */
|
||||||
2924
fs/ntfs/mft.c
Normal file
2924
fs/ntfs/mft.c
Normal file
File diff suppressed because it is too large
Load Diff
91
fs/ntfs/mft.h
Normal file
91
fs/ntfs/mft.h
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
/*
|
||||||
|
* Defines for mft record handling in NTFS Linux kernel driver.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2001-2004 Anton Altaparmakov
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _LINUX_NTFS_MFT_H
|
||||||
|
#define _LINUX_NTFS_MFT_H
|
||||||
|
|
||||||
|
#include <linux/highmem.h>
|
||||||
|
#include <linux/pagemap.h>
|
||||||
|
|
||||||
|
#include "inode.h"
|
||||||
|
|
||||||
|
struct mft_record *map_mft_record(struct ntfs_inode *ni);
|
||||||
|
void unmap_mft_record(struct ntfs_inode *ni);
|
||||||
|
struct mft_record *map_extent_mft_record(struct ntfs_inode *base_ni, u64 mref,
|
||||||
|
struct ntfs_inode **ntfs_ino);
|
||||||
|
|
||||||
|
static inline void unmap_extent_mft_record(struct ntfs_inode *ni)
|
||||||
|
{
|
||||||
|
unmap_mft_record(ni);
|
||||||
|
}
|
||||||
|
|
||||||
|
void __mark_mft_record_dirty(struct ntfs_inode *ni);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* mark_mft_record_dirty - set the mft record and the page containing it dirty
|
||||||
|
* @ni: ntfs inode describing the mapped mft record
|
||||||
|
*
|
||||||
|
* Set the mapped (extent) mft record of the (base or extent) ntfs inode @ni,
|
||||||
|
* as well as the page containing the mft record, dirty. Also, mark the base
|
||||||
|
* vfs inode dirty. This ensures that any changes to the mft record are
|
||||||
|
* written out to disk.
|
||||||
|
*
|
||||||
|
* NOTE: Do not do anything if the mft record is already marked dirty.
|
||||||
|
*/
|
||||||
|
static inline void mark_mft_record_dirty(struct ntfs_inode *ni)
|
||||||
|
{
|
||||||
|
if (!NInoTestSetDirty(ni))
|
||||||
|
__mark_mft_record_dirty(ni);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ntfs_sync_mft_mirror(struct ntfs_volume *vol, const u64 mft_no,
|
||||||
|
struct mft_record *m);
|
||||||
|
int write_mft_record_nolock(struct ntfs_inode *ni, struct mft_record *m, int sync);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* write_mft_record - write out a mapped (extent) mft record
|
||||||
|
* @ni: ntfs inode describing the mapped (extent) mft record
|
||||||
|
* @m: mapped (extent) mft record to write
|
||||||
|
* @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.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
static inline int write_mft_record(struct ntfs_inode *ni, struct mft_record *m, int sync)
|
||||||
|
{
|
||||||
|
struct folio *folio = ni->folio;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
folio_lock(folio);
|
||||||
|
err = write_mft_record_nolock(ni, m, sync);
|
||||||
|
folio_unlock(folio);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
|
||||||
|
struct ntfs_inode **ni, struct ntfs_inode *base_ni,
|
||||||
|
struct mft_record **ni_mrec);
|
||||||
|
int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni);
|
||||||
|
int ntfs_mft_records_write(const struct ntfs_volume *vol, const u64 mref,
|
||||||
|
const s64 count, struct mft_record *b);
|
||||||
|
int ntfs_mft_record_check(const struct ntfs_volume *vol, struct mft_record *m,
|
||||||
|
u64 mft_no);
|
||||||
|
int ntfs_mft_writepages(struct address_space *mapping,
|
||||||
|
struct writeback_control *wbc);
|
||||||
|
void ntfs_mft_mark_dirty(struct folio *folio);
|
||||||
|
|
||||||
|
#endif /* _LINUX_NTFS_MFT_H */
|
||||||
194
fs/ntfs/mst.c
Normal file
194
fs/ntfs/mst.c
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* NTFS multi sector transfer protection handling code.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2001-2004 Anton Altaparmakov
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/ratelimit.h>
|
||||||
|
|
||||||
|
#include "ntfs.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* post_read_mst_fixup - deprotect multi sector transfer protected data
|
||||||
|
* @b: pointer to the data to deprotect
|
||||||
|
* @size: size in bytes of @b
|
||||||
|
*
|
||||||
|
* Perform the necessary post read multi sector transfer fixup and detect the
|
||||||
|
* presence of incomplete multi sector transfers. - In that case, overwrite the
|
||||||
|
* 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).
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* be fixed up. Thus, we return success and not failure in this case. This is
|
||||||
|
* in contrast to pre_write_mst_fixup(), see below.
|
||||||
|
*/
|
||||||
|
int post_read_mst_fixup(struct ntfs_record *b, const u32 size)
|
||||||
|
{
|
||||||
|
u16 usa_ofs, usa_count, usn;
|
||||||
|
u16 *usa_pos, *data_pos;
|
||||||
|
|
||||||
|
/* Setup the variables. */
|
||||||
|
usa_ofs = le16_to_cpu(b->usa_ofs);
|
||||||
|
/* Decrement usa_count to get number of fixups. */
|
||||||
|
usa_count = le16_to_cpu(b->usa_count) - 1;
|
||||||
|
/* Size and alignment checks. */
|
||||||
|
if (size & (NTFS_BLOCK_SIZE - 1) || usa_ofs & 1 ||
|
||||||
|
usa_ofs + (usa_count * 2) > size ||
|
||||||
|
(size >> NTFS_BLOCK_SIZE_BITS) != usa_count)
|
||||||
|
return 0;
|
||||||
|
/* Position of usn in update sequence array. */
|
||||||
|
usa_pos = (u16 *)b + usa_ofs/sizeof(u16);
|
||||||
|
/*
|
||||||
|
* The update sequence number which has to be equal to each of the
|
||||||
|
* u16 values before they are fixed up. Note no need to care for
|
||||||
|
* endianness since we are comparing and moving data for on disk
|
||||||
|
* structures which means the data is consistent. - If it is
|
||||||
|
* consistenty the wrong endianness it doesn't make any difference.
|
||||||
|
*/
|
||||||
|
usn = *usa_pos;
|
||||||
|
/*
|
||||||
|
* Position in protected data of first u16 that needs fixing up.
|
||||||
|
*/
|
||||||
|
data_pos = (u16 *)b + NTFS_BLOCK_SIZE / sizeof(u16) - 1;
|
||||||
|
/*
|
||||||
|
* Check for incomplete multi sector transfer(s).
|
||||||
|
*/
|
||||||
|
while (usa_count--) {
|
||||||
|
if (*data_pos != usn) {
|
||||||
|
struct mft_record *m = (struct mft_record *)b;
|
||||||
|
|
||||||
|
pr_err_ratelimited("ntfs: Incomplete multi sector transfer detected! (Record magic : 0x%x, mft number : 0x%x, base mft number : 0x%lx, mft in use : %d, data : 0x%x, usn 0x%x)\n",
|
||||||
|
le32_to_cpu(m->magic), le32_to_cpu(m->mft_record_number),
|
||||||
|
MREF_LE(m->base_mft_record), m->flags & MFT_RECORD_IN_USE,
|
||||||
|
*data_pos, usn);
|
||||||
|
/*
|
||||||
|
* Incomplete multi sector transfer detected! )-:
|
||||||
|
* Set the magic to "BAAD" and return failure.
|
||||||
|
* Note that magic_BAAD is already converted to le32.
|
||||||
|
*/
|
||||||
|
b->magic = magic_BAAD;
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
data_pos += NTFS_BLOCK_SIZE / sizeof(u16);
|
||||||
|
}
|
||||||
|
/* Re-setup the variables. */
|
||||||
|
usa_count = le16_to_cpu(b->usa_count) - 1;
|
||||||
|
data_pos = (u16 *)b + NTFS_BLOCK_SIZE / sizeof(u16) - 1;
|
||||||
|
/* Fixup all sectors. */
|
||||||
|
while (usa_count--) {
|
||||||
|
/*
|
||||||
|
* Increment position in usa and restore original data from
|
||||||
|
* the usa into the data buffer.
|
||||||
|
*/
|
||||||
|
*data_pos = *(++usa_pos);
|
||||||
|
/* Increment position in data as well. */
|
||||||
|
data_pos += NTFS_BLOCK_SIZE/sizeof(u16);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* pre_write_mst_fixup - apply multi sector transfer protection
|
||||||
|
* @b: pointer to the data to protect
|
||||||
|
* @size: size in bytes of @b
|
||||||
|
*
|
||||||
|
* Perform the necessary pre write multi sector transfer fixup on the data
|
||||||
|
* pointer to by @b of @size.
|
||||||
|
*
|
||||||
|
* Return 0 if fixup applied (success) or -EINVAL if no fixup was performed
|
||||||
|
* (assumed not needed). This is in contrast to post_read_mst_fixup() above.
|
||||||
|
*
|
||||||
|
* NOTE: We consider the absence / invalidity of an update sequence array to
|
||||||
|
* mean that the structure is not subject to protection and hence doesn't need
|
||||||
|
* to be fixed up. This means that you have to create a valid update sequence
|
||||||
|
* array header in the ntfs record before calling this function, otherwise it
|
||||||
|
* will fail (the header needs to contain the position of the update sequence
|
||||||
|
* array together with the number of elements in the array). You also need to
|
||||||
|
* initialise the update sequence number before calling this function
|
||||||
|
* otherwise a random word will be used (whatever was in the record at that
|
||||||
|
* position at that time).
|
||||||
|
*/
|
||||||
|
int pre_write_mst_fixup(struct ntfs_record *b, const u32 size)
|
||||||
|
{
|
||||||
|
__le16 *usa_pos, *data_pos;
|
||||||
|
u16 usa_ofs, usa_count, usn;
|
||||||
|
__le16 le_usn;
|
||||||
|
|
||||||
|
/* Sanity check + only fixup if it makes sense. */
|
||||||
|
if (!b || ntfs_is_baad_record(b->magic) ||
|
||||||
|
ntfs_is_hole_record(b->magic))
|
||||||
|
return -EINVAL;
|
||||||
|
/* Setup the variables. */
|
||||||
|
usa_ofs = le16_to_cpu(b->usa_ofs);
|
||||||
|
/* Decrement usa_count to get number of fixups. */
|
||||||
|
usa_count = le16_to_cpu(b->usa_count) - 1;
|
||||||
|
/* Size and alignment checks. */
|
||||||
|
if (size & (NTFS_BLOCK_SIZE - 1) || usa_ofs & 1 ||
|
||||||
|
usa_ofs + (usa_count * 2) > size ||
|
||||||
|
(size >> NTFS_BLOCK_SIZE_BITS) != usa_count)
|
||||||
|
return -EINVAL;
|
||||||
|
/* Position of usn in update sequence array. */
|
||||||
|
usa_pos = (__le16 *)((u8 *)b + usa_ofs);
|
||||||
|
/*
|
||||||
|
* Cyclically increment the update sequence number
|
||||||
|
* (skipping 0 and -1, i.e. 0xffff).
|
||||||
|
*/
|
||||||
|
usn = le16_to_cpup(usa_pos) + 1;
|
||||||
|
if (usn == 0xffff || !usn)
|
||||||
|
usn = 1;
|
||||||
|
le_usn = cpu_to_le16(usn);
|
||||||
|
*usa_pos = le_usn;
|
||||||
|
/* Position in data of first u16 that needs fixing up. */
|
||||||
|
data_pos = (__le16 *)b + NTFS_BLOCK_SIZE/sizeof(__le16) - 1;
|
||||||
|
/* Fixup all sectors. */
|
||||||
|
while (usa_count--) {
|
||||||
|
/*
|
||||||
|
* Increment the position in the usa and save the
|
||||||
|
* original data from the data buffer into the usa.
|
||||||
|
*/
|
||||||
|
*(++usa_pos) = *data_pos;
|
||||||
|
/* Apply fixup to data. */
|
||||||
|
*data_pos = le_usn;
|
||||||
|
/* Increment position in data as well. */
|
||||||
|
data_pos += NTFS_BLOCK_SIZE / sizeof(__le16);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* post_write_mst_fixup - fast deprotect multi sector transfer protected data
|
||||||
|
* @b: pointer to the data to deprotect
|
||||||
|
*
|
||||||
|
* Perform the necessary post write multi sector transfer fixup, not checking
|
||||||
|
* for any errors, because we assume we have just used pre_write_mst_fixup(),
|
||||||
|
* thus the data will be fine or we would never have gotten here.
|
||||||
|
*/
|
||||||
|
void post_write_mst_fixup(struct ntfs_record *b)
|
||||||
|
{
|
||||||
|
__le16 *usa_pos, *data_pos;
|
||||||
|
|
||||||
|
u16 usa_ofs = le16_to_cpu(b->usa_ofs);
|
||||||
|
u16 usa_count = le16_to_cpu(b->usa_count) - 1;
|
||||||
|
|
||||||
|
/* Position of usn in update sequence array. */
|
||||||
|
usa_pos = (__le16 *)b + usa_ofs/sizeof(__le16);
|
||||||
|
|
||||||
|
/* Position in protected data of first u16 that needs fixing up. */
|
||||||
|
data_pos = (__le16 *)b + NTFS_BLOCK_SIZE/sizeof(__le16) - 1;
|
||||||
|
|
||||||
|
/* Fixup all sectors. */
|
||||||
|
while (usa_count--) {
|
||||||
|
/*
|
||||||
|
* Increment position in usa and restore original data from
|
||||||
|
* the usa into the data buffer.
|
||||||
|
*/
|
||||||
|
*data_pos = *(++usa_pos);
|
||||||
|
|
||||||
|
/* Increment position in data as well. */
|
||||||
|
data_pos += NTFS_BLOCK_SIZE/sizeof(__le16);
|
||||||
|
}
|
||||||
|
}
|
||||||
1684
fs/ntfs/namei.c
Normal file
1684
fs/ntfs/namei.c
Normal file
File diff suppressed because it is too large
Load Diff
294
fs/ntfs/ntfs.h
Normal file
294
fs/ntfs/ntfs.h
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
/*
|
||||||
|
* Defines for NTFS Linux kernel driver.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2001-2014 Anton Altaparmakov and Tuxera Inc.
|
||||||
|
* Copyright (C) 2002 Richard Russon
|
||||||
|
* Copyright (c) 2025 LG Electronics Co., Ltd.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _LINUX_NTFS_H
|
||||||
|
#define _LINUX_NTFS_H
|
||||||
|
|
||||||
|
#include <linux/stddef.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/hex.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/compiler.h>
|
||||||
|
#include <linux/fs.h>
|
||||||
|
#include <linux/nls.h>
|
||||||
|
#include <linux/smp.h>
|
||||||
|
#include <linux/pagemap.h>
|
||||||
|
#include <linux/uidgid.h>
|
||||||
|
|
||||||
|
#include "volume.h"
|
||||||
|
#include "layout.h"
|
||||||
|
#include "inode.h"
|
||||||
|
|
||||||
|
#ifdef pr_fmt
|
||||||
|
#undef pr_fmt
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Default pre-allocation size is optimize runlist merge overhead
|
||||||
|
* with small chunk size.
|
||||||
|
*/
|
||||||
|
#define NTFS_DEF_PREALLOC_SIZE 65536
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The log2 of the standard number of clusters per compression block.
|
||||||
|
* A value of 4 corresponds to 16 clusters (1 << 4), which is the
|
||||||
|
* default chunk size used by NTFS LZNT1 compression.
|
||||||
|
*/
|
||||||
|
#define STANDARD_COMPRESSION_UNIT 4
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The maximum cluster size (4KB) allowed for compression to be enabled.
|
||||||
|
* By design, NTFS does not support compression on volumes where the
|
||||||
|
* cluster size exceeds 4096 bytes.
|
||||||
|
*/
|
||||||
|
#define MAX_COMPRESSION_CLUSTER_SIZE 4096
|
||||||
|
|
||||||
|
#define NTFS_B_TO_CLU(vol, b) ((b) >> (vol)->cluster_size_bits)
|
||||||
|
#define NTFS_CLU_TO_B(vol, clu) ((u64)(clu) << (vol)->cluster_size_bits)
|
||||||
|
#define NTFS_B_TO_CLU_OFS(vol, clu) ((u64)(clu) & (vol)->cluster_size_mask)
|
||||||
|
|
||||||
|
#define NTFS_MFT_NR_TO_CLU(vol, mft_no) (((u64)mft_no << (vol)->mft_record_size_bits) >> \
|
||||||
|
(vol)->cluster_size_bits)
|
||||||
|
#define NTFS_MFT_NR_TO_PIDX(vol, mft_no) (mft_no >> (PAGE_SHIFT - \
|
||||||
|
(vol)->mft_record_size_bits))
|
||||||
|
#define NTFS_MFT_NR_TO_POFS(vol, mft_no) (((u64)mft_no << (vol)->mft_record_size_bits) & \
|
||||||
|
~PAGE_MASK)
|
||||||
|
|
||||||
|
#define NTFS_PIDX_TO_BLK(vol, idx) (((u64)idx << PAGE_SHIFT) >> \
|
||||||
|
((vol)->sb)->s_blocksize_bits)
|
||||||
|
#define NTFS_PIDX_TO_CLU(vol, idx) (((u64)idx << PAGE_SHIFT) >> \
|
||||||
|
(vol)->cluster_size_bits)
|
||||||
|
#define NTFS_CLU_TO_PIDX(vol, clu) (((u64)(clu) << (vol)->cluster_size_bits) >> \
|
||||||
|
PAGE_SHIFT)
|
||||||
|
#define NTFS_CLU_TO_POFS(vol, clu) (((u64)(clu) << (vol)->cluster_size_bits) & \
|
||||||
|
~PAGE_MASK)
|
||||||
|
|
||||||
|
#define NTFS_B_TO_SECTOR(vol, b) ((b) >> ((vol)->sb)->s_blocksize_bits)
|
||||||
|
|
||||||
|
enum {
|
||||||
|
NTFS_BLOCK_SIZE = 512,
|
||||||
|
NTFS_BLOCK_SIZE_BITS = 9,
|
||||||
|
NTFS_SB_MAGIC = 0x5346544e, /* 'NTFS' */
|
||||||
|
NTFS_MAX_NAME_LEN = 255,
|
||||||
|
NTFS_MAX_LABEL_LEN = 128,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
CASE_SENSITIVE = 0,
|
||||||
|
IGNORE_CASE = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Conversion helpers for NTFS units.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Convert bytes to cluster count */
|
||||||
|
static inline u64 ntfs_bytes_to_cluster(const struct ntfs_volume *vol,
|
||||||
|
s64 bytes)
|
||||||
|
{
|
||||||
|
return bytes >> vol->cluster_size_bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Convert cluster count to bytes */
|
||||||
|
static inline u64 ntfs_cluster_to_bytes(const struct ntfs_volume *vol,
|
||||||
|
u64 clusters)
|
||||||
|
{
|
||||||
|
return clusters << vol->cluster_size_bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get the byte offset within a cluster from a linear byte address */
|
||||||
|
static inline u64 ntfs_bytes_to_cluster_off(const struct ntfs_volume *vol,
|
||||||
|
u64 bytes)
|
||||||
|
{
|
||||||
|
return bytes & vol->cluster_size_mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Calculate the physical cluster number containing a specific MFT record. */
|
||||||
|
static inline u64 ntfs_mft_no_to_cluster(const struct ntfs_volume *vol,
|
||||||
|
unsigned long mft_no)
|
||||||
|
{
|
||||||
|
return ((u64)mft_no << vol->mft_record_size_bits) >>
|
||||||
|
vol->cluster_size_bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Calculate the folio index where the MFT record resides. */
|
||||||
|
static inline pgoff_t ntfs_mft_no_to_pidx(const struct ntfs_volume *vol,
|
||||||
|
unsigned long mft_no)
|
||||||
|
{
|
||||||
|
return mft_no >> (PAGE_SHIFT - vol->mft_record_size_bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Calculate the byte offset within a folio for an MFT record. */
|
||||||
|
static inline u64 ntfs_mft_no_to_poff(const struct ntfs_volume *vol,
|
||||||
|
unsigned long mft_no)
|
||||||
|
{
|
||||||
|
return ((u64)mft_no << vol->mft_record_size_bits) & ~PAGE_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Convert folio index to cluster number. */
|
||||||
|
static inline u64 ntfs_pidx_to_cluster(const struct ntfs_volume *vol,
|
||||||
|
pgoff_t idx)
|
||||||
|
{
|
||||||
|
return ((u64)idx << PAGE_SHIFT) >> vol->cluster_size_bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Convert cluster number to folio index. */
|
||||||
|
static inline pgoff_t ntfs_cluster_to_pidx(const struct ntfs_volume *vol,
|
||||||
|
u64 clu)
|
||||||
|
{
|
||||||
|
return (clu << vol->cluster_size_bits) >> PAGE_SHIFT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get the byte offset within a folio from a cluster number */
|
||||||
|
static inline u64 ntfs_cluster_to_poff(const struct ntfs_volume *vol,
|
||||||
|
u64 clu)
|
||||||
|
{
|
||||||
|
return (clu << vol->cluster_size_bits) & ~PAGE_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Convert byte offset to sector (block) number. */
|
||||||
|
static inline sector_t ntfs_bytes_to_sector(const struct ntfs_volume *vol,
|
||||||
|
u64 bytes)
|
||||||
|
{
|
||||||
|
return bytes >> vol->sb->s_blocksize_bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Global variables. */
|
||||||
|
|
||||||
|
/* Slab caches (from super.c). */
|
||||||
|
extern struct kmem_cache *ntfs_name_cache;
|
||||||
|
extern struct kmem_cache *ntfs_inode_cache;
|
||||||
|
extern struct kmem_cache *ntfs_big_inode_cache;
|
||||||
|
extern struct kmem_cache *ntfs_attr_ctx_cache;
|
||||||
|
extern struct kmem_cache *ntfs_index_ctx_cache;
|
||||||
|
|
||||||
|
/* The various operations structs defined throughout the driver files. */
|
||||||
|
extern const struct address_space_operations ntfs_aops;
|
||||||
|
extern const struct address_space_operations ntfs_mft_aops;
|
||||||
|
|
||||||
|
extern const struct file_operations ntfs_file_ops;
|
||||||
|
extern const struct inode_operations ntfs_file_inode_ops;
|
||||||
|
extern const struct inode_operations ntfs_symlink_inode_operations;
|
||||||
|
extern const struct inode_operations ntfs_special_inode_operations;
|
||||||
|
|
||||||
|
extern const struct file_operations ntfs_dir_ops;
|
||||||
|
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;
|
||||||
|
|
||||||
|
extern const struct export_operations ntfs_export_ops;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NTFS_SB - return the ntfs volume given a vfs super block
|
||||||
|
* @sb: VFS super block
|
||||||
|
*
|
||||||
|
* NTFS_SB() returns the ntfs volume associated with the VFS super block @sb.
|
||||||
|
*/
|
||||||
|
static inline struct ntfs_volume *NTFS_SB(struct super_block *sb)
|
||||||
|
{
|
||||||
|
return sb->s_fs_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Declarations of functions and global variables. */
|
||||||
|
|
||||||
|
/* From fs/ntfs/compress.c */
|
||||||
|
int ntfs_read_compressed_block(struct folio *folio);
|
||||||
|
int allocate_compression_buffers(void);
|
||||||
|
void free_compression_buffers(void);
|
||||||
|
int ntfs_compress_write(struct ntfs_inode *ni, loff_t pos, size_t count,
|
||||||
|
struct iov_iter *from);
|
||||||
|
|
||||||
|
/* From fs/ntfs/super.c */
|
||||||
|
#define default_upcase_len 0x10000
|
||||||
|
extern struct mutex ntfs_lock;
|
||||||
|
|
||||||
|
struct option_t {
|
||||||
|
int val;
|
||||||
|
char *str;
|
||||||
|
};
|
||||||
|
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 */
|
||||||
|
int post_read_mst_fixup(struct ntfs_record *b, const u32 size);
|
||||||
|
int pre_write_mst_fixup(struct ntfs_record *b, const u32 size);
|
||||||
|
void post_write_mst_fixup(struct ntfs_record *b);
|
||||||
|
|
||||||
|
/* From fs/ntfs/unistr.c */
|
||||||
|
bool ntfs_are_names_equal(const __le16 *s1, size_t s1_len,
|
||||||
|
const __le16 *s2, size_t s2_len,
|
||||||
|
const u32 ic,
|
||||||
|
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 __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 __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);
|
||||||
|
int ntfs_ucstonls(const struct ntfs_volume *vol, const __le16 *ins,
|
||||||
|
const int ins_len, unsigned char **outs, int outs_len);
|
||||||
|
__le16 *ntfs_ucsndup(const __le16 *s, u32 maxlen);
|
||||||
|
bool ntfs_names_are_equal(const __le16 *s1, size_t s1_len,
|
||||||
|
const __le16 *s2, size_t s2_len,
|
||||||
|
const u32 ic,
|
||||||
|
const __le16 *upcase, const u32 upcase_size);
|
||||||
|
int ntfs_force_shutdown(struct super_block *sb, u32 flags);
|
||||||
|
long ntfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
|
||||||
|
#ifdef CONFIG_COMPAT
|
||||||
|
long ntfs_compat_ioctl(struct file *filp, unsigned int cmd,
|
||||||
|
unsigned long arg);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* From fs/ntfs/upcase.c */
|
||||||
|
__le16 *generate_default_upcase(void);
|
||||||
|
|
||||||
|
static inline int ntfs_ffs(int x)
|
||||||
|
{
|
||||||
|
int r = 1;
|
||||||
|
|
||||||
|
if (!x)
|
||||||
|
return 0;
|
||||||
|
if (!(x & 0xffff)) {
|
||||||
|
x >>= 16;
|
||||||
|
r += 16;
|
||||||
|
}
|
||||||
|
if (!(x & 0xff)) {
|
||||||
|
x >>= 8;
|
||||||
|
r += 8;
|
||||||
|
}
|
||||||
|
if (!(x & 0xf)) {
|
||||||
|
x >>= 4;
|
||||||
|
r += 4;
|
||||||
|
}
|
||||||
|
if (!(x & 3)) {
|
||||||
|
x >>= 2;
|
||||||
|
r += 2;
|
||||||
|
}
|
||||||
|
if (!(x & 1))
|
||||||
|
r += 1;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* From fs/ntfs/bdev-io.c */
|
||||||
|
int ntfs_bdev_read(struct block_device *bdev, char *data, loff_t start, size_t size);
|
||||||
|
int ntfs_bdev_write(struct super_block *sb, void *buf, loff_t start, size_t size);
|
||||||
|
|
||||||
|
#endif /* _LINUX_NTFS_H */
|
||||||
158
fs/ntfs/object_id.c
Normal file
158
fs/ntfs/object_id.c
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* Pocessing of object ids
|
||||||
|
*
|
||||||
|
* Part of this file is based on code from the NTFS-3G.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2009-2019 Jean-Pierre Andre
|
||||||
|
* Copyright (c) 2026 LG Electronics Co., Ltd.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ntfs.h"
|
||||||
|
#include "index.h"
|
||||||
|
#include "object_id.h"
|
||||||
|
|
||||||
|
struct object_id_index_key {
|
||||||
|
union {
|
||||||
|
u32 alignment;
|
||||||
|
struct guid guid;
|
||||||
|
} object_id;
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
struct object_id_index_data {
|
||||||
|
__le64 file_id;
|
||||||
|
struct guid birth_volume_id;
|
||||||
|
struct guid birth_object_id;
|
||||||
|
struct guid domain_id;
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
/* Index entry in $Extend/$ObjId */
|
||||||
|
struct object_id_index {
|
||||||
|
struct index_entry_header header;
|
||||||
|
struct object_id_index_key key;
|
||||||
|
struct object_id_index_data data;
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
__le16 objid_index_name[] = {cpu_to_le16('$'), cpu_to_le16('O'), 0};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* open_object_id_index - Open the $Extend/$ObjId file and its index
|
||||||
|
* @vol: NTFS volume structure
|
||||||
|
*
|
||||||
|
* Opens the $ObjId system file and retrieves its index context.
|
||||||
|
*
|
||||||
|
* Return: The index context if opened successfully, or NULL if an error
|
||||||
|
* occurred.
|
||||||
|
*/
|
||||||
|
static struct ntfs_index_context *open_object_id_index(struct ntfs_volume *vol)
|
||||||
|
{
|
||||||
|
struct inode *dir_vi, *vi;
|
||||||
|
struct ntfs_inode *dir_ni;
|
||||||
|
struct ntfs_index_context *xo = NULL;
|
||||||
|
struct ntfs_name *name = NULL;
|
||||||
|
u64 mref;
|
||||||
|
int uname_len;
|
||||||
|
__le16 *uname;
|
||||||
|
|
||||||
|
uname_len = ntfs_nlstoucs(vol, "$ObjId", 6, &uname,
|
||||||
|
NTFS_MAX_NAME_LEN);
|
||||||
|
if (uname_len < 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* do not use path_name_to inode - could reopen root */
|
||||||
|
dir_vi = ntfs_iget(vol->sb, FILE_Extend);
|
||||||
|
if (IS_ERR(dir_vi)) {
|
||||||
|
kmem_cache_free(ntfs_name_cache, uname);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
dir_ni = NTFS_I(dir_vi);
|
||||||
|
|
||||||
|
mutex_lock_nested(&dir_ni->mrec_lock, NTFS_EXTEND_MUTEX_PARENT);
|
||||||
|
mref = ntfs_lookup_inode_by_name(dir_ni, uname, uname_len, &name);
|
||||||
|
mutex_unlock(&dir_ni->mrec_lock);
|
||||||
|
kfree(name);
|
||||||
|
kmem_cache_free(ntfs_name_cache, uname);
|
||||||
|
if (IS_ERR_MREF(mref))
|
||||||
|
goto put_dir_vi;
|
||||||
|
|
||||||
|
vi = ntfs_iget(vol->sb, MREF(mref));
|
||||||
|
if (IS_ERR(vi))
|
||||||
|
goto put_dir_vi;
|
||||||
|
|
||||||
|
xo = ntfs_index_ctx_get(NTFS_I(vi), objid_index_name, 2);
|
||||||
|
if (!xo)
|
||||||
|
iput(vi);
|
||||||
|
put_dir_vi:
|
||||||
|
iput(dir_vi);
|
||||||
|
return xo;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* remove_object_id_index - Remove an object id index entry if attribute present
|
||||||
|
* @ni: NTFS inode structure containing the attribute
|
||||||
|
* @xo: Index context for the object id index
|
||||||
|
*
|
||||||
|
* Reads the existing object ID attribute and removes it from the index.
|
||||||
|
*
|
||||||
|
* Return: 0 on success, or a negative error code on failure.
|
||||||
|
*/
|
||||||
|
static int remove_object_id_index(struct ntfs_inode *ni, struct ntfs_index_context *xo)
|
||||||
|
{
|
||||||
|
struct object_id_index_key key = {0};
|
||||||
|
s64 size;
|
||||||
|
|
||||||
|
if (ni->data_size == 0)
|
||||||
|
return -ENODATA;
|
||||||
|
|
||||||
|
/* read the existing object id attribute */
|
||||||
|
size = ntfs_inode_attr_pread(VFS_I(ni), 0, sizeof(struct guid),
|
||||||
|
(char *)&key);
|
||||||
|
if (size != sizeof(struct guid))
|
||||||
|
return -ENODATA;
|
||||||
|
|
||||||
|
if (!ntfs_index_lookup(&key, sizeof(struct object_id_index_key), xo))
|
||||||
|
return ntfs_index_rm(xo);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_delete_object_id_index - Delete an object_id index entry
|
||||||
|
* @ni: NTFS inode structure
|
||||||
|
*
|
||||||
|
* Opens the object ID index and removes the entry corresponding to the inode.
|
||||||
|
*
|
||||||
|
* Return: 0 on success, or a negative error code on failure.
|
||||||
|
*/
|
||||||
|
int ntfs_delete_object_id_index(struct ntfs_inode *ni)
|
||||||
|
{
|
||||||
|
struct ntfs_index_context *xo;
|
||||||
|
struct ntfs_inode *xoni;
|
||||||
|
struct inode *attr_vi;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
attr_vi = ntfs_attr_iget(VFS_I(ni), AT_OBJECT_ID, AT_UNNAMED, 0);
|
||||||
|
if (IS_ERR(attr_vi))
|
||||||
|
return PTR_ERR(attr_vi);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* read the existing object id and un-index it
|
||||||
|
*/
|
||||||
|
xo = open_object_id_index(ni->vol);
|
||||||
|
if (xo) {
|
||||||
|
xoni = xo->idx_ni;
|
||||||
|
mutex_lock_nested(&xoni->mrec_lock, NTFS_EXTEND_MUTEX_PARENT);
|
||||||
|
ret = remove_object_id_index(NTFS_I(attr_vi), xo);
|
||||||
|
if (!ret) {
|
||||||
|
ntfs_index_entry_mark_dirty(xo);
|
||||||
|
mark_mft_record_dirty(xoni);
|
||||||
|
}
|
||||||
|
ntfs_index_ctx_put(xo);
|
||||||
|
mutex_unlock(&xoni->mrec_lock);
|
||||||
|
iput(VFS_I(xoni));
|
||||||
|
}
|
||||||
|
|
||||||
|
iput(attr_vi);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
14
fs/ntfs/object_id.h
Normal file
14
fs/ntfs/object_id.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2008-2021 Jean-Pierre Andre
|
||||||
|
* Copyright (c) 2025 LG Electronics Co., Ltd.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _LINUX_NTFS_OBJECT_ID_H
|
||||||
|
#define _LINUX_NTFS_OBJECT_ID_H
|
||||||
|
|
||||||
|
extern __le16 objid_index_name[];
|
||||||
|
|
||||||
|
int ntfs_delete_object_id_index(struct ntfs_inode *ni);
|
||||||
|
|
||||||
|
#endif /* _LINUX_NTFS_OBJECT_ID_H */
|
||||||
95
fs/ntfs/quota.c
Normal file
95
fs/ntfs/quota.c
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* NTFS kernel quota ($Quota) handling.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2004 Anton Altaparmakov
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "index.h"
|
||||||
|
#include "quota.h"
|
||||||
|
#include "debug.h"
|
||||||
|
#include "ntfs.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_mark_quotas_out_of_date - mark the quotas out of date on an ntfs volume
|
||||||
|
* @vol: ntfs volume on which to mark the quotas out of date
|
||||||
|
*
|
||||||
|
* Mark the quotas out of date on the ntfs volume @vol and return 'true' on
|
||||||
|
* success and 'false' on error.
|
||||||
|
*/
|
||||||
|
bool ntfs_mark_quotas_out_of_date(struct ntfs_volume *vol)
|
||||||
|
{
|
||||||
|
struct ntfs_index_context *ictx;
|
||||||
|
struct quota_control_entry *qce;
|
||||||
|
const __le32 qid = QUOTA_DEFAULTS_ID;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
ntfs_debug("Entering.");
|
||||||
|
if (NVolQuotaOutOfDate(vol))
|
||||||
|
goto done;
|
||||||
|
if (!vol->quota_ino || !vol->quota_q_ino) {
|
||||||
|
ntfs_error(vol->sb, "Quota inodes are not open.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
inode_lock(vol->quota_q_ino);
|
||||||
|
ictx = ntfs_index_ctx_get(NTFS_I(vol->quota_q_ino), I30, 4);
|
||||||
|
if (!ictx) {
|
||||||
|
ntfs_error(vol->sb, "Failed to get index context.");
|
||||||
|
goto err_out;
|
||||||
|
}
|
||||||
|
err = ntfs_index_lookup(&qid, sizeof(qid), ictx);
|
||||||
|
if (err) {
|
||||||
|
if (err == -ENOENT)
|
||||||
|
ntfs_error(vol->sb, "Quota defaults entry is not present.");
|
||||||
|
else
|
||||||
|
ntfs_error(vol->sb, "Lookup of quota defaults entry failed.");
|
||||||
|
goto err_out;
|
||||||
|
}
|
||||||
|
if (ictx->data_len < offsetof(struct quota_control_entry, sid)) {
|
||||||
|
ntfs_error(vol->sb, "Quota defaults entry size is invalid. Run chkdsk.");
|
||||||
|
goto err_out;
|
||||||
|
}
|
||||||
|
qce = (struct quota_control_entry *)ictx->data;
|
||||||
|
if (le32_to_cpu(qce->version) != QUOTA_VERSION) {
|
||||||
|
ntfs_error(vol->sb,
|
||||||
|
"Quota defaults entry version 0x%x is not supported.",
|
||||||
|
le32_to_cpu(qce->version));
|
||||||
|
goto err_out;
|
||||||
|
}
|
||||||
|
ntfs_debug("Quota defaults flags = 0x%x.", le32_to_cpu(qce->flags));
|
||||||
|
/* If quotas are already marked out of date, no need to do anything. */
|
||||||
|
if (qce->flags & QUOTA_FLAG_OUT_OF_DATE)
|
||||||
|
goto set_done;
|
||||||
|
/*
|
||||||
|
* If quota tracking is neither requested, nor enabled and there are no
|
||||||
|
* pending deletes, no need to mark the quotas out of date.
|
||||||
|
*/
|
||||||
|
if (!(qce->flags & (QUOTA_FLAG_TRACKING_ENABLED |
|
||||||
|
QUOTA_FLAG_TRACKING_REQUESTED |
|
||||||
|
QUOTA_FLAG_PENDING_DELETES)))
|
||||||
|
goto set_done;
|
||||||
|
/*
|
||||||
|
* Set the QUOTA_FLAG_OUT_OF_DATE bit thus marking quotas out of date.
|
||||||
|
* This is verified on WinXP to be sufficient to cause windows to
|
||||||
|
* rescan the volume on boot and update all quota entries.
|
||||||
|
*/
|
||||||
|
qce->flags |= QUOTA_FLAG_OUT_OF_DATE;
|
||||||
|
/* Ensure the modified flags are written to disk. */
|
||||||
|
ntfs_index_entry_mark_dirty(ictx);
|
||||||
|
set_done:
|
||||||
|
ntfs_index_ctx_put(ictx);
|
||||||
|
inode_unlock(vol->quota_q_ino);
|
||||||
|
/*
|
||||||
|
* We set the flag so we do not try to mark the quotas out of date
|
||||||
|
* again on remount.
|
||||||
|
*/
|
||||||
|
NVolSetQuotaOutOfDate(vol);
|
||||||
|
done:
|
||||||
|
ntfs_debug("Done.");
|
||||||
|
return true;
|
||||||
|
err_out:
|
||||||
|
if (ictx)
|
||||||
|
ntfs_index_ctx_put(ictx);
|
||||||
|
inode_unlock(vol->quota_q_ino);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
15
fs/ntfs/quota.h
Normal file
15
fs/ntfs/quota.h
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
/*
|
||||||
|
* Defines for NTFS kernel quota ($Quota) handling.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2004 Anton Altaparmakov
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _LINUX_NTFS_QUOTA_H
|
||||||
|
#define _LINUX_NTFS_QUOTA_H
|
||||||
|
|
||||||
|
#include "volume.h"
|
||||||
|
|
||||||
|
bool ntfs_mark_quotas_out_of_date(struct ntfs_volume *vol);
|
||||||
|
|
||||||
|
#endif /* _LINUX_NTFS_QUOTA_H */
|
||||||
573
fs/ntfs/reparse.c
Normal file
573
fs/ntfs/reparse.c
Normal file
@@ -0,0 +1,573 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* Processing of reparse points
|
||||||
|
*
|
||||||
|
* Part of this file is based on code from the NTFS-3G.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2008-2021 Jean-Pierre Andre
|
||||||
|
* Copyright (c) 2025 LG Electronics Co., Ltd.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ntfs.h"
|
||||||
|
#include "layout.h"
|
||||||
|
#include "attrib.h"
|
||||||
|
#include "inode.h"
|
||||||
|
#include "dir.h"
|
||||||
|
#include "volume.h"
|
||||||
|
#include "mft.h"
|
||||||
|
#include "index.h"
|
||||||
|
#include "lcnalloc.h"
|
||||||
|
#include "reparse.h"
|
||||||
|
|
||||||
|
struct wsl_link_reparse_data {
|
||||||
|
__le32 type;
|
||||||
|
char link[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Index entry in $Extend/$Reparse */
|
||||||
|
struct reparse_index {
|
||||||
|
struct index_entry_header header;
|
||||||
|
struct reparse_index_key key;
|
||||||
|
__le32 filling;
|
||||||
|
};
|
||||||
|
|
||||||
|
__le16 reparse_index_name[] = {cpu_to_le16('$'), cpu_to_le16('R'), 0};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check if the reparse point attribute buffer is valid.
|
||||||
|
* Returns true if valid, false otherwise.
|
||||||
|
*/
|
||||||
|
static bool ntfs_is_valid_reparse_buffer(struct ntfs_inode *ni,
|
||||||
|
const struct reparse_point *reparse_attr, size_t size)
|
||||||
|
{
|
||||||
|
size_t expected;
|
||||||
|
|
||||||
|
if (!ni || !reparse_attr)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* Minimum size must cover reparse_point header */
|
||||||
|
if (size < sizeof(struct reparse_point))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* Reserved zero tag is invalid */
|
||||||
|
if (reparse_attr->reparse_tag == IO_REPARSE_TAG_RESERVED_ZERO)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* Calculate expected total size */
|
||||||
|
expected = sizeof(struct reparse_point) +
|
||||||
|
le16_to_cpu(reparse_attr->reparse_data_length);
|
||||||
|
|
||||||
|
/* Add GUID size for non-Microsoft tags */
|
||||||
|
if (!(reparse_attr->reparse_tag & IO_REPARSE_TAG_IS_MICROSOFT))
|
||||||
|
expected += sizeof(struct guid);
|
||||||
|
|
||||||
|
/* Buffer must exactly match the expected size */
|
||||||
|
return expected == size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Do some sanity checks on reparse data
|
||||||
|
*
|
||||||
|
* Microsoft reparse points have an 8-byte header whereas
|
||||||
|
* non-Microsoft reparse points have a 24-byte header. In each case,
|
||||||
|
* 'reparse_data_length' must equal the number of non-header bytes.
|
||||||
|
*
|
||||||
|
* If the reparse data looks like a junction point or symbolic
|
||||||
|
* link, more checks can be done.
|
||||||
|
*/
|
||||||
|
static bool valid_reparse_data(struct ntfs_inode *ni,
|
||||||
|
const struct reparse_point *reparse_attr, size_t size)
|
||||||
|
{
|
||||||
|
const struct wsl_link_reparse_data *wsl_reparse_data =
|
||||||
|
(const struct wsl_link_reparse_data *)reparse_attr->reparse_data;
|
||||||
|
unsigned int data_len = le16_to_cpu(reparse_attr->reparse_data_length);
|
||||||
|
|
||||||
|
if (ntfs_is_valid_reparse_buffer(ni, reparse_attr, size) == false)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
switch (reparse_attr->reparse_tag) {
|
||||||
|
case IO_REPARSE_TAG_LX_SYMLINK:
|
||||||
|
if (data_len <= sizeof(wsl_reparse_data->type) ||
|
||||||
|
wsl_reparse_data->type != cpu_to_le32(2))
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
case IO_REPARSE_TAG_AF_UNIX:
|
||||||
|
case IO_REPARSE_TAG_LX_FIFO:
|
||||||
|
case IO_REPARSE_TAG_LX_CHR:
|
||||||
|
case IO_REPARSE_TAG_LX_BLK:
|
||||||
|
if (data_len || !(ni->flags & FILE_ATTRIBUTE_RECALL_ON_OPEN))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned int ntfs_reparse_tag_mode(struct reparse_point *reparse_attr)
|
||||||
|
{
|
||||||
|
unsigned int mode = 0;
|
||||||
|
|
||||||
|
switch (reparse_attr->reparse_tag) {
|
||||||
|
case IO_REPARSE_TAG_SYMLINK:
|
||||||
|
case IO_REPARSE_TAG_LX_SYMLINK:
|
||||||
|
mode = S_IFLNK;
|
||||||
|
break;
|
||||||
|
case IO_REPARSE_TAG_AF_UNIX:
|
||||||
|
mode = S_IFSOCK;
|
||||||
|
break;
|
||||||
|
case IO_REPARSE_TAG_LX_FIFO:
|
||||||
|
mode = S_IFIFO;
|
||||||
|
break;
|
||||||
|
case IO_REPARSE_TAG_LX_CHR:
|
||||||
|
mode = S_IFCHR;
|
||||||
|
break;
|
||||||
|
case IO_REPARSE_TAG_LX_BLK:
|
||||||
|
mode = S_IFBLK;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the target for symbolic link
|
||||||
|
*/
|
||||||
|
unsigned int ntfs_make_symlink(struct ntfs_inode *ni)
|
||||||
|
{
|
||||||
|
s64 attr_size = 0;
|
||||||
|
unsigned int lth;
|
||||||
|
struct reparse_point *reparse_attr;
|
||||||
|
struct wsl_link_reparse_data *wsl_link_data;
|
||||||
|
unsigned int mode = 0;
|
||||||
|
|
||||||
|
reparse_attr = ntfs_attr_readall(ni, AT_REPARSE_POINT, NULL, 0,
|
||||||
|
&attr_size);
|
||||||
|
if (reparse_attr && attr_size &&
|
||||||
|
valid_reparse_data(ni, reparse_attr, attr_size)) {
|
||||||
|
switch (reparse_attr->reparse_tag) {
|
||||||
|
case IO_REPARSE_TAG_LX_SYMLINK:
|
||||||
|
wsl_link_data =
|
||||||
|
(struct wsl_link_reparse_data *)reparse_attr->reparse_data;
|
||||||
|
if (wsl_link_data->type == cpu_to_le32(2)) {
|
||||||
|
lth = le16_to_cpu(reparse_attr->reparse_data_length) -
|
||||||
|
sizeof(wsl_link_data->type);
|
||||||
|
ni->target = kvzalloc(lth + 1, GFP_NOFS);
|
||||||
|
if (ni->target) {
|
||||||
|
memcpy(ni->target, wsl_link_data->link, lth);
|
||||||
|
ni->target[lth] = 0;
|
||||||
|
mode = ntfs_reparse_tag_mode(reparse_attr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
mode = ntfs_reparse_tag_mode(reparse_attr);
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
ni->flags &= ~FILE_ATTR_REPARSE_POINT;
|
||||||
|
|
||||||
|
if (reparse_attr)
|
||||||
|
kvfree(reparse_attr);
|
||||||
|
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int ntfs_reparse_tag_dt_types(struct ntfs_volume *vol, unsigned long mref)
|
||||||
|
{
|
||||||
|
s64 attr_size = 0;
|
||||||
|
struct reparse_point *reparse_attr;
|
||||||
|
unsigned int dt_type = DT_UNKNOWN;
|
||||||
|
struct inode *vi;
|
||||||
|
|
||||||
|
vi = ntfs_iget(vol->sb, mref);
|
||||||
|
if (IS_ERR(vi))
|
||||||
|
return PTR_ERR(vi);
|
||||||
|
|
||||||
|
reparse_attr = (struct reparse_point *)ntfs_attr_readall(NTFS_I(vi),
|
||||||
|
AT_REPARSE_POINT, NULL, 0, &attr_size);
|
||||||
|
|
||||||
|
if (reparse_attr && attr_size) {
|
||||||
|
switch (reparse_attr->reparse_tag) {
|
||||||
|
case IO_REPARSE_TAG_SYMLINK:
|
||||||
|
case IO_REPARSE_TAG_LX_SYMLINK:
|
||||||
|
dt_type = DT_LNK;
|
||||||
|
break;
|
||||||
|
case IO_REPARSE_TAG_AF_UNIX:
|
||||||
|
dt_type = DT_SOCK;
|
||||||
|
break;
|
||||||
|
case IO_REPARSE_TAG_LX_FIFO:
|
||||||
|
dt_type = DT_FIFO;
|
||||||
|
break;
|
||||||
|
case IO_REPARSE_TAG_LX_CHR:
|
||||||
|
dt_type = DT_CHR;
|
||||||
|
break;
|
||||||
|
case IO_REPARSE_TAG_LX_BLK:
|
||||||
|
dt_type = DT_BLK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reparse_attr)
|
||||||
|
kvfree(reparse_attr);
|
||||||
|
|
||||||
|
iput(vi);
|
||||||
|
return dt_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the index for new reparse data
|
||||||
|
*/
|
||||||
|
static int set_reparse_index(struct ntfs_inode *ni, struct ntfs_index_context *xr,
|
||||||
|
__le32 reparse_tag)
|
||||||
|
{
|
||||||
|
struct reparse_index indx;
|
||||||
|
u64 file_id_cpu;
|
||||||
|
__le64 file_id;
|
||||||
|
|
||||||
|
file_id_cpu = MK_MREF(ni->mft_no, ni->seq_no);
|
||||||
|
file_id = cpu_to_le64(file_id_cpu);
|
||||||
|
indx.header.data.vi.data_offset =
|
||||||
|
cpu_to_le16(sizeof(struct index_entry_header) + sizeof(struct reparse_index_key));
|
||||||
|
indx.header.data.vi.data_length = 0;
|
||||||
|
indx.header.data.vi.reservedV = 0;
|
||||||
|
indx.header.length = cpu_to_le16(sizeof(struct reparse_index));
|
||||||
|
indx.header.key_length = cpu_to_le16(sizeof(struct reparse_index_key));
|
||||||
|
indx.header.flags = 0;
|
||||||
|
indx.header.reserved = 0;
|
||||||
|
indx.key.reparse_tag = reparse_tag;
|
||||||
|
/* danger on processors which require proper alignment! */
|
||||||
|
memcpy(&indx.key.file_id, &file_id, 8);
|
||||||
|
indx.filling = 0;
|
||||||
|
ntfs_index_ctx_reinit(xr);
|
||||||
|
|
||||||
|
return ntfs_ie_add(xr, (struct index_entry *)&indx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove a reparse data index entry if attribute present
|
||||||
|
*/
|
||||||
|
static int remove_reparse_index(struct inode *rp, struct ntfs_index_context *xr,
|
||||||
|
__le32 *preparse_tag)
|
||||||
|
{
|
||||||
|
struct reparse_index_key key;
|
||||||
|
u64 file_id_cpu;
|
||||||
|
__le64 file_id;
|
||||||
|
s64 size;
|
||||||
|
struct ntfs_inode *ni = NTFS_I(rp);
|
||||||
|
int err = 0, ret = ni->data_size;
|
||||||
|
|
||||||
|
if (ni->data_size == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* read the existing reparse_tag */
|
||||||
|
size = ntfs_inode_attr_pread(rp, 0, 4, (char *)preparse_tag);
|
||||||
|
if (size != 4)
|
||||||
|
return -ENODATA;
|
||||||
|
|
||||||
|
file_id_cpu = MK_MREF(ni->mft_no, ni->seq_no);
|
||||||
|
file_id = cpu_to_le64(file_id_cpu);
|
||||||
|
key.reparse_tag = *preparse_tag;
|
||||||
|
/* danger on processors which require proper alignment! */
|
||||||
|
memcpy(&key.file_id, &file_id, 8);
|
||||||
|
if (!ntfs_index_lookup(&key, sizeof(struct reparse_index_key), xr)) {
|
||||||
|
err = ntfs_index_rm(xr);
|
||||||
|
if (err)
|
||||||
|
ret = err;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Open the $Extend/$Reparse file and its index
|
||||||
|
*/
|
||||||
|
static struct ntfs_index_context *open_reparse_index(struct ntfs_volume *vol)
|
||||||
|
{
|
||||||
|
struct ntfs_index_context *xr = NULL;
|
||||||
|
u64 mref;
|
||||||
|
__le16 *uname;
|
||||||
|
struct ntfs_name *name = NULL;
|
||||||
|
int uname_len;
|
||||||
|
struct inode *vi, *dir_vi;
|
||||||
|
|
||||||
|
/* do not use path_name_to inode - could reopen root */
|
||||||
|
dir_vi = ntfs_iget(vol->sb, FILE_Extend);
|
||||||
|
if (IS_ERR(dir_vi))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
uname_len = ntfs_nlstoucs(vol, "$Reparse", 8, &uname,
|
||||||
|
NTFS_MAX_NAME_LEN);
|
||||||
|
if (uname_len < 0) {
|
||||||
|
iput(dir_vi);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_lock_nested(&NTFS_I(dir_vi)->mrec_lock, NTFS_EXTEND_MUTEX_PARENT);
|
||||||
|
mref = ntfs_lookup_inode_by_name(NTFS_I(dir_vi), uname, uname_len,
|
||||||
|
&name);
|
||||||
|
mutex_unlock(&NTFS_I(dir_vi)->mrec_lock);
|
||||||
|
kfree(name);
|
||||||
|
kmem_cache_free(ntfs_name_cache, uname);
|
||||||
|
if (IS_ERR_MREF(mref))
|
||||||
|
goto put_dir_vi;
|
||||||
|
|
||||||
|
vi = ntfs_iget(vol->sb, MREF(mref));
|
||||||
|
if (IS_ERR(vi))
|
||||||
|
goto put_dir_vi;
|
||||||
|
|
||||||
|
xr = ntfs_index_ctx_get(NTFS_I(vi), reparse_index_name, 2);
|
||||||
|
if (!xr)
|
||||||
|
iput(vi);
|
||||||
|
put_dir_vi:
|
||||||
|
iput(dir_vi);
|
||||||
|
return xr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Update the reparse data and index
|
||||||
|
*
|
||||||
|
* The reparse data attribute should have been created, and
|
||||||
|
* an existing index is expected if there is an existing value.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static int update_reparse_data(struct ntfs_inode *ni, struct ntfs_index_context *xr,
|
||||||
|
char *value, size_t size)
|
||||||
|
{
|
||||||
|
struct inode *rp_inode;
|
||||||
|
int err = 0;
|
||||||
|
s64 written;
|
||||||
|
int oldsize;
|
||||||
|
__le32 reparse_tag;
|
||||||
|
struct ntfs_inode *rp_ni;
|
||||||
|
|
||||||
|
rp_inode = ntfs_attr_iget(VFS_I(ni), AT_REPARSE_POINT, AT_UNNAMED, 0);
|
||||||
|
if (IS_ERR(rp_inode))
|
||||||
|
return -EINVAL;
|
||||||
|
rp_ni = NTFS_I(rp_inode);
|
||||||
|
|
||||||
|
/* remove the existing reparse data */
|
||||||
|
oldsize = remove_reparse_index(rp_inode, xr, &reparse_tag);
|
||||||
|
if (oldsize < 0) {
|
||||||
|
err = oldsize;
|
||||||
|
goto put_rp_inode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* overwrite value if any */
|
||||||
|
written = ntfs_inode_attr_pwrite(rp_inode, 0, size, value, false);
|
||||||
|
if (written != size) {
|
||||||
|
ntfs_error(ni->vol->sb, "Failed to update reparse data\n");
|
||||||
|
err = -EIO;
|
||||||
|
goto put_rp_inode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (set_reparse_index(ni, xr, ((const struct reparse_point *)value)->reparse_tag) &&
|
||||||
|
oldsize > 0) {
|
||||||
|
/*
|
||||||
|
* If cannot index, try to remove the reparse
|
||||||
|
* data and log the error. There will be an
|
||||||
|
* inconsistency if removal fails.
|
||||||
|
*/
|
||||||
|
ntfs_attr_rm(rp_ni);
|
||||||
|
ntfs_error(ni->vol->sb,
|
||||||
|
"Failed to index reparse data. Possible corruption.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
mark_mft_record_dirty(ni);
|
||||||
|
put_rp_inode:
|
||||||
|
iput(rp_inode);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Delete a reparse index entry
|
||||||
|
*/
|
||||||
|
int ntfs_delete_reparse_index(struct ntfs_inode *ni)
|
||||||
|
{
|
||||||
|
struct inode *vi;
|
||||||
|
struct ntfs_index_context *xr;
|
||||||
|
struct ntfs_inode *xrni;
|
||||||
|
__le32 reparse_tag;
|
||||||
|
int err = 0;
|
||||||
|
|
||||||
|
if (!(ni->flags & FILE_ATTR_REPARSE_POINT))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
vi = ntfs_attr_iget(VFS_I(ni), AT_REPARSE_POINT, AT_UNNAMED, 0);
|
||||||
|
if (IS_ERR(vi))
|
||||||
|
return PTR_ERR(vi);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* read the existing reparse data (the tag is enough)
|
||||||
|
* and un-index it
|
||||||
|
*/
|
||||||
|
xr = open_reparse_index(ni->vol);
|
||||||
|
if (xr) {
|
||||||
|
xrni = xr->idx_ni;
|
||||||
|
mutex_lock_nested(&xrni->mrec_lock, NTFS_EXTEND_MUTEX_PARENT);
|
||||||
|
err = remove_reparse_index(vi, xr, &reparse_tag);
|
||||||
|
if (err < 0) {
|
||||||
|
ntfs_index_ctx_put(xr);
|
||||||
|
mutex_unlock(&xrni->mrec_lock);
|
||||||
|
iput(VFS_I(xrni));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
mark_mft_record_dirty(xrni);
|
||||||
|
ntfs_index_ctx_put(xr);
|
||||||
|
mutex_unlock(&xrni->mrec_lock);
|
||||||
|
iput(VFS_I(xrni));
|
||||||
|
}
|
||||||
|
|
||||||
|
ni->flags &= ~FILE_ATTR_REPARSE_POINT;
|
||||||
|
NInoSetFileNameDirty(ni);
|
||||||
|
mark_mft_record_dirty(ni);
|
||||||
|
|
||||||
|
out:
|
||||||
|
iput(vi);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the reparse data from an extended attribute
|
||||||
|
*/
|
||||||
|
static int ntfs_set_ntfs_reparse_data(struct ntfs_inode *ni, char *value, size_t size)
|
||||||
|
{
|
||||||
|
int err = 0;
|
||||||
|
struct ntfs_inode *xrni;
|
||||||
|
struct ntfs_index_context *xr;
|
||||||
|
|
||||||
|
if (!ni)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* reparse data compatibily with EA is not checked
|
||||||
|
* any more, it is required by Windows 10, but may
|
||||||
|
* lead to problems with earlier versions.
|
||||||
|
*/
|
||||||
|
if (valid_reparse_data(ni, (const struct reparse_point *)value, size) == false)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
xr = open_reparse_index(ni->vol);
|
||||||
|
if (!xr)
|
||||||
|
return -EINVAL;
|
||||||
|
xrni = xr->idx_ni;
|
||||||
|
|
||||||
|
if (!ntfs_attr_exist(ni, AT_REPARSE_POINT, AT_UNNAMED, 0)) {
|
||||||
|
struct reparse_point rp = {0, };
|
||||||
|
|
||||||
|
/*
|
||||||
|
* no reparse data attribute : add one,
|
||||||
|
* apparently, this does not feed the new value in
|
||||||
|
* Note : NTFS version must be >= 3
|
||||||
|
*/
|
||||||
|
if (ni->vol->major_ver < 3) {
|
||||||
|
err = -EOPNOTSUPP;
|
||||||
|
ntfs_index_ctx_put(xr);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ntfs_attr_add(ni, AT_REPARSE_POINT, AT_UNNAMED, 0, (u8 *)&rp, sizeof(rp));
|
||||||
|
if (err) {
|
||||||
|
ntfs_index_ctx_put(xr);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
ni->flags |= FILE_ATTR_REPARSE_POINT;
|
||||||
|
NInoSetFileNameDirty(ni);
|
||||||
|
mark_mft_record_dirty(ni);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* update value and index */
|
||||||
|
mutex_lock_nested(&xrni->mrec_lock, NTFS_EXTEND_MUTEX_PARENT);
|
||||||
|
err = update_reparse_data(ni, xr, value, size);
|
||||||
|
if (err) {
|
||||||
|
ni->flags &= ~FILE_ATTR_REPARSE_POINT;
|
||||||
|
NInoSetFileNameDirty(ni);
|
||||||
|
mark_mft_record_dirty(ni);
|
||||||
|
}
|
||||||
|
ntfs_index_ctx_put(xr);
|
||||||
|
mutex_unlock(&xrni->mrec_lock);
|
||||||
|
|
||||||
|
out:
|
||||||
|
if (!err)
|
||||||
|
mark_mft_record_dirty(xrni);
|
||||||
|
iput(VFS_I(xrni));
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set reparse data for a WSL type symlink
|
||||||
|
*/
|
||||||
|
int ntfs_reparse_set_wsl_symlink(struct ntfs_inode *ni,
|
||||||
|
const __le16 *target, int target_len)
|
||||||
|
{
|
||||||
|
int err = 0;
|
||||||
|
int len;
|
||||||
|
int reparse_len;
|
||||||
|
unsigned char *utarget = NULL;
|
||||||
|
struct reparse_point *reparse;
|
||||||
|
struct wsl_link_reparse_data *data;
|
||||||
|
|
||||||
|
utarget = (char *)NULL;
|
||||||
|
len = ntfs_ucstonls(ni->vol, target, target_len, &utarget, 0);
|
||||||
|
if (len <= 0)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
reparse_len = sizeof(struct reparse_point) + sizeof(data->type) + len;
|
||||||
|
reparse = kvzalloc(reparse_len, GFP_NOFS);
|
||||||
|
if (!reparse) {
|
||||||
|
err = -ENOMEM;
|
||||||
|
kvfree(utarget);
|
||||||
|
} else {
|
||||||
|
data = (struct wsl_link_reparse_data *)reparse->reparse_data;
|
||||||
|
reparse->reparse_tag = IO_REPARSE_TAG_LX_SYMLINK;
|
||||||
|
reparse->reparse_data_length =
|
||||||
|
cpu_to_le16(sizeof(data->type) + len);
|
||||||
|
reparse->reserved = 0;
|
||||||
|
data->type = cpu_to_le32(2);
|
||||||
|
memcpy(data->link, utarget, len);
|
||||||
|
err = ntfs_set_ntfs_reparse_data(ni,
|
||||||
|
(char *)reparse, reparse_len);
|
||||||
|
kvfree(reparse);
|
||||||
|
if (!err)
|
||||||
|
ni->target = utarget;
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set reparse data for a WSL special file other than a symlink
|
||||||
|
* (socket, fifo, character or block device)
|
||||||
|
*/
|
||||||
|
int ntfs_reparse_set_wsl_not_symlink(struct ntfs_inode *ni, mode_t mode)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
int len;
|
||||||
|
int reparse_len;
|
||||||
|
__le32 reparse_tag;
|
||||||
|
struct reparse_point *reparse;
|
||||||
|
|
||||||
|
len = 0;
|
||||||
|
if (S_ISSOCK(mode))
|
||||||
|
reparse_tag = IO_REPARSE_TAG_AF_UNIX;
|
||||||
|
else if (S_ISFIFO(mode))
|
||||||
|
reparse_tag = IO_REPARSE_TAG_LX_FIFO;
|
||||||
|
else if (S_ISCHR(mode))
|
||||||
|
reparse_tag = IO_REPARSE_TAG_LX_CHR;
|
||||||
|
else if (S_ISBLK(mode))
|
||||||
|
reparse_tag = IO_REPARSE_TAG_LX_BLK;
|
||||||
|
else
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
|
||||||
|
reparse_len = sizeof(struct reparse_point) + len;
|
||||||
|
reparse = kvzalloc(reparse_len, GFP_NOFS);
|
||||||
|
if (!reparse)
|
||||||
|
err = -ENOMEM;
|
||||||
|
else {
|
||||||
|
reparse->reparse_tag = reparse_tag;
|
||||||
|
reparse->reparse_data_length = cpu_to_le16(len);
|
||||||
|
reparse->reserved = cpu_to_le16(0);
|
||||||
|
err = ntfs_set_ntfs_reparse_data(ni, (char *)reparse,
|
||||||
|
reparse_len);
|
||||||
|
kvfree(reparse);
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
20
fs/ntfs/reparse.h
Normal file
20
fs/ntfs/reparse.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2008-2021 Jean-Pierre Andre
|
||||||
|
* Copyright (c) 2025 LG Electronics Co., Ltd.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _LINUX_NTFS_REPARSE_H
|
||||||
|
#define _LINUX_NTFS_REPARSE_H
|
||||||
|
|
||||||
|
extern __le16 reparse_index_name[];
|
||||||
|
|
||||||
|
unsigned int ntfs_make_symlink(struct ntfs_inode *ni);
|
||||||
|
unsigned int ntfs_reparse_tag_dt_types(struct ntfs_volume *vol, unsigned long mref);
|
||||||
|
int ntfs_reparse_set_wsl_symlink(struct ntfs_inode *ni,
|
||||||
|
const __le16 *target, int target_len);
|
||||||
|
int ntfs_reparse_set_wsl_not_symlink(struct ntfs_inode *ni, mode_t mode);
|
||||||
|
int ntfs_delete_reparse_index(struct ntfs_inode *ni);
|
||||||
|
int ntfs_remove_ntfs_reparse_data(struct ntfs_inode *ni);
|
||||||
|
|
||||||
|
#endif /* _LINUX_NTFS_REPARSE_H */
|
||||||
2066
fs/ntfs/runlist.c
Normal file
2066
fs/ntfs/runlist.c
Normal file
File diff suppressed because it is too large
Load Diff
97
fs/ntfs/runlist.h
Normal file
97
fs/ntfs/runlist.h
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
/*
|
||||||
|
* Defines for runlist handling in NTFS Linux kernel driver.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2001-2005 Anton Altaparmakov
|
||||||
|
* Copyright (c) 2002 Richard Russon
|
||||||
|
* Copyright (c) 2025 LG Electronics Co., Ltd.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _LINUX_NTFS_RUNLIST_H
|
||||||
|
#define _LINUX_NTFS_RUNLIST_H
|
||||||
|
|
||||||
|
#include "volume.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* runlist_element - in memory vcn to lcn mapping array element
|
||||||
|
* @vcn: starting vcn of the current array element
|
||||||
|
* @lcn: starting lcn of the current array element
|
||||||
|
* @length: length in clusters of the current array element
|
||||||
|
*
|
||||||
|
* The last vcn (in fact the last vcn + 1) is reached when length == 0.
|
||||||
|
*
|
||||||
|
* When lcn == -1 this means that the count vcns starting at vcn are not
|
||||||
|
* physically allocated (i.e. this is a hole / data is sparse).
|
||||||
|
*
|
||||||
|
* In memory vcn to lcn mapping structure element.
|
||||||
|
* @vcn: vcn = Starting virtual cluster number.
|
||||||
|
* @lcn: lcn = Starting logical cluster number.
|
||||||
|
* @length: Run length in clusters.
|
||||||
|
*/
|
||||||
|
struct runlist_element {
|
||||||
|
s64 vcn;
|
||||||
|
s64 lcn;
|
||||||
|
s64 length;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* runlist - in memory vcn to lcn mapping array including a read/write lock
|
||||||
|
* @rl: pointer to an array of runlist elements
|
||||||
|
* @lock: read/write spinlock for serializing access to @rl
|
||||||
|
* @rl_hint: hint/cache pointing to the last accessed runlist element
|
||||||
|
*/
|
||||||
|
struct runlist {
|
||||||
|
struct runlist_element *rl;
|
||||||
|
struct rw_semaphore lock;
|
||||||
|
size_t count;
|
||||||
|
int rl_hint;
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline void ntfs_init_runlist(struct runlist *rl)
|
||||||
|
{
|
||||||
|
rl->rl = NULL;
|
||||||
|
init_rwsem(&rl->lock);
|
||||||
|
rl->count = 0;
|
||||||
|
rl->rl_hint = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum {
|
||||||
|
LCN_DELALLOC = -1,
|
||||||
|
LCN_HOLE = -2,
|
||||||
|
LCN_RL_NOT_MAPPED = -3,
|
||||||
|
LCN_ENOENT = -4,
|
||||||
|
LCN_ENOMEM = -5,
|
||||||
|
LCN_EIO = -6,
|
||||||
|
LCN_EINVAL = -7,
|
||||||
|
};
|
||||||
|
|
||||||
|
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_mapping_pairs_decompress(const struct ntfs_volume *vol,
|
||||||
|
const struct attr_record *attr, struct runlist *old_runlist,
|
||||||
|
size_t *new_rl_count);
|
||||||
|
s64 ntfs_rl_vcn_to_lcn(const struct runlist_element *rl, const s64 vcn);
|
||||||
|
struct runlist_element *ntfs_rl_find_vcn_nolock(struct runlist_element *rl, const s64 vcn);
|
||||||
|
int ntfs_get_size_for_mapping_pairs(const struct ntfs_volume *vol,
|
||||||
|
const struct runlist_element *rl, const s64 first_vcn,
|
||||||
|
const s64 last_vcn, int max_mp_size);
|
||||||
|
int ntfs_mapping_pairs_build(const struct ntfs_volume *vol, s8 *dst,
|
||||||
|
const int dst_len, const struct runlist_element *rl,
|
||||||
|
const s64 first_vcn, const s64 last_vcn, s64 *const stop_vcn,
|
||||||
|
struct runlist_element **stop_rl, unsigned int *de_cluster_count);
|
||||||
|
int ntfs_rl_truncate_nolock(const struct ntfs_volume *vol,
|
||||||
|
struct runlist *const runlist, const s64 new_length);
|
||||||
|
int ntfs_rl_sparse(struct runlist_element *rl);
|
||||||
|
s64 ntfs_rl_get_compressed_size(struct ntfs_volume *vol, struct runlist_element *rl);
|
||||||
|
struct runlist_element *ntfs_rl_insert_range(struct runlist_element *dst_rl, int dst_cnt,
|
||||||
|
struct runlist_element *src_rl, int src_cnt, size_t *new_cnt);
|
||||||
|
struct runlist_element *ntfs_rl_punch_hole(struct runlist_element *dst_rl, int dst_cnt,
|
||||||
|
s64 start_vcn, s64 len, struct runlist_element **punch_rl,
|
||||||
|
size_t *new_rl_cnt);
|
||||||
|
struct runlist_element *ntfs_rl_collapse_range(struct runlist_element *dst_rl, int dst_cnt,
|
||||||
|
s64 start_vcn, s64 len, struct runlist_element **punch_rl,
|
||||||
|
size_t *new_rl_cnt);
|
||||||
|
struct runlist_element *ntfs_rl_realloc(struct runlist_element *rl, int old_size,
|
||||||
|
int new_size);
|
||||||
|
#endif /* _LINUX_NTFS_RUNLIST_H */
|
||||||
2765
fs/ntfs/super.c
Normal file
2765
fs/ntfs/super.c
Normal file
File diff suppressed because it is too large
Load Diff
54
fs/ntfs/sysctl.c
Normal file
54
fs/ntfs/sysctl.c
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* Code for sysctl handling in NTFS Linux kernel driver.
|
||||||
|
*
|
||||||
|
* Copyright (C) 1997 Martin von Löwis, Régis Duchesne
|
||||||
|
* Copyright (c) 2002-2005 Anton Altaparmakov
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
|
||||||
|
#ifdef CONFIG_SYSCTL
|
||||||
|
|
||||||
|
#include <linux/proc_fs.h>
|
||||||
|
#include <linux/sysctl.h>
|
||||||
|
|
||||||
|
#include "sysctl.h"
|
||||||
|
#include "debug.h"
|
||||||
|
|
||||||
|
/* Definition of the ntfs sysctl. */
|
||||||
|
static const struct ctl_table ntfs_sysctls[] = {
|
||||||
|
{
|
||||||
|
.procname = "ntfs-debug",
|
||||||
|
.data = &debug_msgs, /* Data pointer and size. */
|
||||||
|
.maxlen = sizeof(debug_msgs),
|
||||||
|
.mode = 0644, /* Mode, proc handler. */
|
||||||
|
.proc_handler = proc_dointvec
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Storage for the sysctls header. */
|
||||||
|
static struct ctl_table_header *sysctls_root_table;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_sysctl - add or remove the debug sysctl
|
||||||
|
* @add: add (1) or remove (0) the sysctl
|
||||||
|
*
|
||||||
|
* Add or remove the debug sysctl. Return 0 on success or -errno on error.
|
||||||
|
*/
|
||||||
|
int ntfs_sysctl(int add)
|
||||||
|
{
|
||||||
|
if (add) {
|
||||||
|
sysctls_root_table = register_sysctl("fs/ntfs", ntfs_sysctls);
|
||||||
|
if (!sysctls_root_table)
|
||||||
|
return -ENOMEM;
|
||||||
|
} else {
|
||||||
|
unregister_sysctl_table(sysctls_root_table);
|
||||||
|
sysctls_root_table = NULL;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_SYSCTL */
|
||||||
|
#endif /* DEBUG */
|
||||||
26
fs/ntfs/sysctl.h
Normal file
26
fs/ntfs/sysctl.h
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
/*
|
||||||
|
* Defines for sysctl handling in NTFS Linux kernel driver.
|
||||||
|
*
|
||||||
|
* Copyright (C) 1997 Martin von Löwis, Régis Duchesne
|
||||||
|
* Copyright (c) 2002-2004 Anton Altaparmakov
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _LINUX_NTFS_SYSCTL_H
|
||||||
|
#define _LINUX_NTFS_SYSCTL_H
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(DEBUG) && defined(CONFIG_SYSCTL)
|
||||||
|
|
||||||
|
int ntfs_sysctl(int add);
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
/* Just return success. */
|
||||||
|
static inline int ntfs_sysctl(int add)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* DEBUG && CONFIG_SYSCTL */
|
||||||
|
#endif /* _LINUX_NTFS_SYSCTL_H */
|
||||||
87
fs/ntfs/time.h
Normal file
87
fs/ntfs/time.h
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
/*
|
||||||
|
* NTFS time conversion functions.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2001-2005 Anton Altaparmakov
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _LINUX_NTFS_TIME_H
|
||||||
|
#define _LINUX_NTFS_TIME_H
|
||||||
|
|
||||||
|
#include <linux/time.h>
|
||||||
|
#include <asm/div64.h> /* For do_div(). */
|
||||||
|
|
||||||
|
#define NTFS_TIME_OFFSET ((s64)(369 * 365 + 89) * 24 * 3600)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* utc2ntfs - convert Linux UTC time to NTFS time
|
||||||
|
* @ts: Linux UTC time to convert to NTFS time
|
||||||
|
*
|
||||||
|
* Convert the Linux UTC time @ts to its corresponding NTFS time and return
|
||||||
|
* that in little endian format.
|
||||||
|
*
|
||||||
|
* Linux stores time in a struct timespec64 consisting of a time64_t tv_sec
|
||||||
|
* and a long tv_nsec where tv_sec is the number of 1-second intervals since
|
||||||
|
* 1st January 1970, 00:00:00 UTC and tv_nsec is the number of 1-nano-second
|
||||||
|
* intervals since the value of tv_sec.
|
||||||
|
*
|
||||||
|
* NTFS uses Microsoft's standard time format which is stored in a s64 and is
|
||||||
|
* measured as the number of 100-nano-second intervals since 1st January 1601,
|
||||||
|
* 00:00:00 UTC.
|
||||||
|
*/
|
||||||
|
static inline __le64 utc2ntfs(const struct timespec64 ts)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Convert the seconds to 100ns intervals, add the nano-seconds
|
||||||
|
* converted to 100ns intervals, and then add the NTFS time offset.
|
||||||
|
*/
|
||||||
|
return cpu_to_le64((u64)(ts.tv_sec + NTFS_TIME_OFFSET) * 10000000 +
|
||||||
|
ts.tv_nsec / 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* get_current_ntfs_time - get the current time in little endian NTFS format
|
||||||
|
*
|
||||||
|
* Get the current time from the Linux kernel, convert it to its corresponding
|
||||||
|
* NTFS time and return that in little endian format.
|
||||||
|
*/
|
||||||
|
static inline __le64 get_current_ntfs_time(void)
|
||||||
|
{
|
||||||
|
struct timespec64 ts;
|
||||||
|
|
||||||
|
ktime_get_coarse_real_ts64(&ts);
|
||||||
|
return utc2ntfs(ts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs2utc - convert NTFS time to Linux time
|
||||||
|
* @time: NTFS time (little endian) to convert to Linux UTC
|
||||||
|
*
|
||||||
|
* Convert the little endian NTFS time @time to its corresponding Linux UTC
|
||||||
|
* time and return that in cpu format.
|
||||||
|
*
|
||||||
|
* Linux stores time in a struct timespec64 consisting of a time64_t tv_sec
|
||||||
|
* and a long tv_nsec where tv_sec is the number of 1-second intervals since
|
||||||
|
* 1st January 1970, 00:00:00 UTC and tv_nsec is the number of 1-nano-second
|
||||||
|
* intervals since the value of tv_sec.
|
||||||
|
*
|
||||||
|
* NTFS uses Microsoft's standard time format which is stored in a s64 and is
|
||||||
|
* measured as the number of 100 nano-second intervals since 1st January 1601,
|
||||||
|
* 00:00:00 UTC.
|
||||||
|
*/
|
||||||
|
static inline struct timespec64 ntfs2utc(const __le64 time)
|
||||||
|
{
|
||||||
|
struct timespec64 ts;
|
||||||
|
s32 t32;
|
||||||
|
|
||||||
|
/* Subtract the NTFS time offset. */
|
||||||
|
s64 t = le64_to_cpu(time) - NTFS_TIME_OFFSET * 10000000;
|
||||||
|
/*
|
||||||
|
* Convert the time to 1-second intervals and the remainder to
|
||||||
|
* 1-nano-second intervals.
|
||||||
|
*/
|
||||||
|
ts.tv_sec = div_s64_rem(t, 10000000, &t32);
|
||||||
|
ts.tv_nsec = t32 * 100;
|
||||||
|
return ts;
|
||||||
|
}
|
||||||
|
#endif /* _LINUX_NTFS_TIME_H */
|
||||||
477
fs/ntfs/unistr.c
Normal file
477
fs/ntfs/unistr.c
Normal file
@@ -0,0 +1,477 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* NTFS Unicode string handling.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2001-2006 Anton Altaparmakov
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ntfs.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* IMPORTANT
|
||||||
|
* =========
|
||||||
|
*
|
||||||
|
* All these routines assume that the Unicode characters are in little endian
|
||||||
|
* encoding inside the strings!!!
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is used by the name collation functions to quickly determine what
|
||||||
|
* characters are (in)valid.
|
||||||
|
*/
|
||||||
|
static const u8 legal_ansi_char_array[0x40] = {
|
||||||
|
0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
|
||||||
|
0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
|
||||||
|
|
||||||
|
0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
|
||||||
|
0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
|
||||||
|
|
||||||
|
0x17, 0x07, 0x18, 0x17, 0x17, 0x17, 0x17, 0x17,
|
||||||
|
0x17, 0x17, 0x18, 0x16, 0x16, 0x17, 0x07, 0x00,
|
||||||
|
|
||||||
|
0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17,
|
||||||
|
0x17, 0x17, 0x04, 0x16, 0x18, 0x16, 0x18, 0x18,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_are_names_equal - compare two Unicode names for equality
|
||||||
|
* @s1: name to compare to @s2
|
||||||
|
* @s1_len: length in Unicode characters of @s1
|
||||||
|
* @s2: name to compare to @s1
|
||||||
|
* @s2_len: length in Unicode characters of @s2
|
||||||
|
* @ic: ignore case bool
|
||||||
|
* @upcase: upcase table (only if @ic == IGNORE_CASE)
|
||||||
|
* @upcase_size: length in Unicode characters of @upcase (if present)
|
||||||
|
*
|
||||||
|
* Compare the names @s1 and @s2 and return 'true' (1) if the names are
|
||||||
|
* identical, or 'false' (0) if they are not identical. If @ic is IGNORE_CASE,
|
||||||
|
* the @upcase table is used to performa a case insensitive comparison.
|
||||||
|
*/
|
||||||
|
bool ntfs_are_names_equal(const __le16 *s1, size_t s1_len,
|
||||||
|
const __le16 *s2, size_t s2_len, const u32 ic,
|
||||||
|
const __le16 *upcase, const u32 upcase_size)
|
||||||
|
{
|
||||||
|
if (s1_len != s2_len)
|
||||||
|
return false;
|
||||||
|
if (ic == CASE_SENSITIVE)
|
||||||
|
return !ntfs_ucsncmp(s1, s2, s1_len);
|
||||||
|
return !ntfs_ucsncasecmp(s1, s2, s1_len, upcase, upcase_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_collate_names - collate two Unicode names
|
||||||
|
* @name1: first Unicode name to compare
|
||||||
|
* @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
|
||||||
|
* @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)
|
||||||
|
*
|
||||||
|
* ntfs_collate_names collates two Unicode names and returns:
|
||||||
|
*
|
||||||
|
* -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.
|
||||||
|
*
|
||||||
|
* 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 __le16 *upcase, const u32 upcase_len)
|
||||||
|
{
|
||||||
|
u32 cnt, min_len;
|
||||||
|
u16 c1, c2;
|
||||||
|
|
||||||
|
min_len = name1_len;
|
||||||
|
if (name1_len > name2_len)
|
||||||
|
min_len = name2_len;
|
||||||
|
for (cnt = 0; cnt < min_len; ++cnt) {
|
||||||
|
c1 = le16_to_cpu(*name1++);
|
||||||
|
c2 = le16_to_cpu(*name2++);
|
||||||
|
if (ic) {
|
||||||
|
if (c1 < upcase_len)
|
||||||
|
c1 = le16_to_cpu(upcase[c1]);
|
||||||
|
if (c2 < upcase_len)
|
||||||
|
c2 = le16_to_cpu(upcase[c2]);
|
||||||
|
}
|
||||||
|
if (c1 < 64 && legal_ansi_char_array[c1] & 8)
|
||||||
|
return err_val;
|
||||||
|
if (c1 < c2)
|
||||||
|
return -1;
|
||||||
|
if (c1 > c2)
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (name1_len < name2_len)
|
||||||
|
return -1;
|
||||||
|
if (name1_len == name2_len)
|
||||||
|
return 0;
|
||||||
|
/* name1_len > name2_len */
|
||||||
|
c1 = le16_to_cpu(*name1);
|
||||||
|
if (c1 < 64 && legal_ansi_char_array[c1] & 8)
|
||||||
|
return err_val;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_ucsncmp - compare two little endian Unicode strings
|
||||||
|
* @s1: first string
|
||||||
|
* @s2: second string
|
||||||
|
* @n: maximum unicode characters to compare
|
||||||
|
*
|
||||||
|
* Compare the first @n characters of the Unicode strings @s1 and @s2,
|
||||||
|
* The strings in little endian format and appropriate le16_to_cpu()
|
||||||
|
* conversion is performed on non-little endian machines.
|
||||||
|
*
|
||||||
|
* The function returns an integer less than, equal to, or greater than zero
|
||||||
|
* if @s1 (or the first @n Unicode characters thereof) is found, respectively,
|
||||||
|
* to be less than, to match, or be greater than @s2.
|
||||||
|
*/
|
||||||
|
int ntfs_ucsncmp(const __le16 *s1, const __le16 *s2, size_t n)
|
||||||
|
{
|
||||||
|
u16 c1, c2;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
for (i = 0; i < n; ++i) {
|
||||||
|
c1 = le16_to_cpu(s1[i]);
|
||||||
|
c2 = le16_to_cpu(s2[i]);
|
||||||
|
if (c1 < c2)
|
||||||
|
return -1;
|
||||||
|
if (c1 > c2)
|
||||||
|
return 1;
|
||||||
|
if (!c1)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_ucsncasecmp - compare two little endian Unicode strings, ignoring case
|
||||||
|
* @s1: first string
|
||||||
|
* @s2: second string
|
||||||
|
* @n: maximum unicode characters to compare
|
||||||
|
* @upcase: upcase table
|
||||||
|
* @upcase_size: upcase table size in Unicode characters
|
||||||
|
*
|
||||||
|
* Compare the first @n characters of the Unicode strings @s1 and @s2,
|
||||||
|
* ignoring case. The strings in little endian format and appropriate
|
||||||
|
* le16_to_cpu() conversion is performed on non-little endian machines.
|
||||||
|
*
|
||||||
|
* Each character is uppercased using the @upcase table before the comparison.
|
||||||
|
*
|
||||||
|
* The function returns an integer less than, equal to, or greater than zero
|
||||||
|
* if @s1 (or the first @n Unicode characters thereof) is found, respectively,
|
||||||
|
* to be less than, to match, or be greater than @s2.
|
||||||
|
*/
|
||||||
|
int ntfs_ucsncasecmp(const __le16 *s1, const __le16 *s2, size_t n,
|
||||||
|
const __le16 *upcase, const u32 upcase_size)
|
||||||
|
{
|
||||||
|
size_t i;
|
||||||
|
u16 c1, c2;
|
||||||
|
|
||||||
|
for (i = 0; i < n; ++i) {
|
||||||
|
c1 = le16_to_cpu(s1[i]);
|
||||||
|
if (c1 < upcase_size)
|
||||||
|
c1 = le16_to_cpu(upcase[c1]);
|
||||||
|
c2 = le16_to_cpu(s2[i]);
|
||||||
|
if (c2 < upcase_size)
|
||||||
|
c2 = le16_to_cpu(upcase[c2]);
|
||||||
|
if (c1 < c2)
|
||||||
|
return -1;
|
||||||
|
if (c1 > c2)
|
||||||
|
return 1;
|
||||||
|
if (!c1)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 __le16 *upcase, const u32 upcase_len)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_nlstoucs - convert NLS string to little endian Unicode string
|
||||||
|
* @vol: ntfs volume which we are working with
|
||||||
|
* @ins: input NLS string buffer
|
||||||
|
* @ins_len: length of input string in bytes
|
||||||
|
* @outs: on return contains the allocated output Unicode string buffer
|
||||||
|
* @max_name_len: maximum number of Unicode characters allowed for the output name
|
||||||
|
*
|
||||||
|
* Convert the input string @ins, which is in whatever format the loaded NLS
|
||||||
|
* map dictates, into a little endian, 2-byte Unicode string.
|
||||||
|
*
|
||||||
|
* This function allocates the string and the caller is responsible for
|
||||||
|
* calling kmem_cache_free(ntfs_name_cache, *@outs); when finished with it.
|
||||||
|
*
|
||||||
|
* On success the function returns the number of Unicode characters written to
|
||||||
|
* the output string *@outs (>= 0), not counting the terminating Unicode NULL
|
||||||
|
* character. *@outs is set to the allocated output string buffer.
|
||||||
|
*
|
||||||
|
* On error, a negative number corresponding to the error code is returned. In
|
||||||
|
* that case the output string is not allocated. Both *@outs and *@outs_len
|
||||||
|
* are then undefined.
|
||||||
|
*
|
||||||
|
* This might look a bit odd due to fast path optimization...
|
||||||
|
*/
|
||||||
|
int ntfs_nlstoucs(const struct ntfs_volume *vol, const char *ins,
|
||||||
|
const int ins_len, __le16 **outs, int max_name_len)
|
||||||
|
{
|
||||||
|
struct nls_table *nls = vol->nls_map;
|
||||||
|
__le16 *ucs;
|
||||||
|
wchar_t wc;
|
||||||
|
int i, o, wc_len;
|
||||||
|
|
||||||
|
/* We do not trust outside sources. */
|
||||||
|
if (likely(ins)) {
|
||||||
|
if (max_name_len > NTFS_MAX_NAME_LEN)
|
||||||
|
ucs = kvmalloc((max_name_len + 2) * sizeof(__le16),
|
||||||
|
GFP_NOFS | __GFP_ZERO);
|
||||||
|
else
|
||||||
|
ucs = kmem_cache_alloc(ntfs_name_cache, GFP_NOFS);
|
||||||
|
if (likely(ucs)) {
|
||||||
|
if (vol->nls_utf8) {
|
||||||
|
o = utf8s_to_utf16s(ins, ins_len,
|
||||||
|
UTF16_LITTLE_ENDIAN,
|
||||||
|
(wchar_t *)ucs,
|
||||||
|
max_name_len + 2);
|
||||||
|
if (o < 0 || o > max_name_len) {
|
||||||
|
wc_len = o;
|
||||||
|
goto name_err;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (i = o = 0; i < ins_len; i += wc_len) {
|
||||||
|
wc_len = nls->char2uni(ins + i, ins_len - i,
|
||||||
|
&wc);
|
||||||
|
if (likely(wc_len >= 0 &&
|
||||||
|
o < max_name_len)) {
|
||||||
|
if (likely(wc)) {
|
||||||
|
ucs[o++] = cpu_to_le16(wc);
|
||||||
|
continue;
|
||||||
|
} /* else if (!wc) */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
goto name_err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ucs[o] = 0;
|
||||||
|
*outs = ucs;
|
||||||
|
return o;
|
||||||
|
} /* else if (!ucs) */
|
||||||
|
ntfs_debug("Failed to allocate buffer for converted name from ntfs_name_cache.");
|
||||||
|
return -ENOMEM;
|
||||||
|
} /* else if (!ins) */
|
||||||
|
ntfs_error(vol->sb, "Received NULL pointer.");
|
||||||
|
return -EINVAL;
|
||||||
|
name_err:
|
||||||
|
if (max_name_len > NTFS_MAX_NAME_LEN)
|
||||||
|
kvfree(ucs);
|
||||||
|
else
|
||||||
|
kmem_cache_free(ntfs_name_cache, ucs);
|
||||||
|
if (wc_len < 0) {
|
||||||
|
ntfs_debug("Name using character set %s contains characters that cannot be converted to Unicode.",
|
||||||
|
nls->charset);
|
||||||
|
i = -EILSEQ;
|
||||||
|
} else {
|
||||||
|
ntfs_debug("Name is too long (maximum length for a name on NTFS is %d Unicode characters.",
|
||||||
|
max_name_len);
|
||||||
|
i = -ENAMETOOLONG;
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_ucstonls - convert little endian Unicode string to NLS string
|
||||||
|
* @vol: ntfs volume which we are working with
|
||||||
|
* @ins: input Unicode string buffer
|
||||||
|
* @ins_len: length of input string in Unicode characters
|
||||||
|
* @outs: on return contains the (allocated) output NLS string buffer
|
||||||
|
* @outs_len: length of output string buffer in bytes
|
||||||
|
*
|
||||||
|
* Convert the input little endian, 2-byte Unicode string @ins, of length
|
||||||
|
* @ins_len into the string format dictated by the loaded NLS.
|
||||||
|
*
|
||||||
|
* If *@outs is NULL, this function allocates the string and the caller is
|
||||||
|
* responsible for calling kfree(*@outs); when finished with it. In this case
|
||||||
|
* @outs_len is ignored and can be 0.
|
||||||
|
*
|
||||||
|
* On success the function returns the number of bytes written to the output
|
||||||
|
* string *@outs (>= 0), not counting the terminating NULL byte. If the output
|
||||||
|
* string buffer was allocated, *@outs is set to it.
|
||||||
|
*
|
||||||
|
* On error, a negative number corresponding to the error code is returned. In
|
||||||
|
* that case the output string is not allocated. The contents of *@outs are
|
||||||
|
* then undefined.
|
||||||
|
*
|
||||||
|
* This might look a bit odd due to fast path optimization...
|
||||||
|
*/
|
||||||
|
int ntfs_ucstonls(const struct ntfs_volume *vol, const __le16 *ins,
|
||||||
|
const int ins_len, unsigned char **outs, int outs_len)
|
||||||
|
{
|
||||||
|
struct nls_table *nls = vol->nls_map;
|
||||||
|
unsigned char *ns;
|
||||||
|
int i, o, ns_len, wc;
|
||||||
|
|
||||||
|
/* We don't trust outside sources. */
|
||||||
|
if (ins) {
|
||||||
|
ns = *outs;
|
||||||
|
ns_len = outs_len;
|
||||||
|
if (ns && !ns_len) {
|
||||||
|
wc = -ENAMETOOLONG;
|
||||||
|
goto conversion_err;
|
||||||
|
}
|
||||||
|
if (!ns) {
|
||||||
|
ns_len = ins_len * NLS_MAX_CHARSET_SIZE;
|
||||||
|
ns = kmalloc(ns_len + 1, GFP_NOFS);
|
||||||
|
if (!ns)
|
||||||
|
goto mem_err_out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vol->nls_utf8) {
|
||||||
|
o = utf16s_to_utf8s((const wchar_t *)ins, ins_len,
|
||||||
|
UTF16_LITTLE_ENDIAN, ns, ns_len);
|
||||||
|
if (o >= ns_len) {
|
||||||
|
wc = -ENAMETOOLONG;
|
||||||
|
goto conversion_err;
|
||||||
|
}
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = o = 0; i < ins_len; i++) {
|
||||||
|
retry:
|
||||||
|
wc = nls->uni2char(le16_to_cpu(ins[i]), ns + o,
|
||||||
|
ns_len - o);
|
||||||
|
if (wc > 0) {
|
||||||
|
o += wc;
|
||||||
|
continue;
|
||||||
|
} else if (!wc)
|
||||||
|
break;
|
||||||
|
else if (wc == -ENAMETOOLONG && ns != *outs) {
|
||||||
|
unsigned char *tc;
|
||||||
|
/* Grow in multiples of 64 bytes. */
|
||||||
|
tc = kmalloc((ns_len + 64) &
|
||||||
|
~63, GFP_NOFS);
|
||||||
|
if (tc) {
|
||||||
|
memcpy(tc, ns, ns_len);
|
||||||
|
ns_len = ((ns_len + 64) & ~63) - 1;
|
||||||
|
kfree(ns);
|
||||||
|
ns = tc;
|
||||||
|
goto retry;
|
||||||
|
} /* No memory so goto conversion_error; */
|
||||||
|
} /* wc < 0, real error. */
|
||||||
|
goto conversion_err;
|
||||||
|
}
|
||||||
|
done:
|
||||||
|
ns[o] = 0;
|
||||||
|
*outs = ns;
|
||||||
|
return o;
|
||||||
|
} /* else (!ins) */
|
||||||
|
ntfs_error(vol->sb, "Received NULL pointer.");
|
||||||
|
return -EINVAL;
|
||||||
|
conversion_err:
|
||||||
|
ntfs_error(vol->sb,
|
||||||
|
"Unicode name contains characters that cannot be converted to character set %s. You might want to try to use the mount option nls=utf8.",
|
||||||
|
nls->charset);
|
||||||
|
if (ns != *outs)
|
||||||
|
kfree(ns);
|
||||||
|
if (wc != -ENAMETOOLONG)
|
||||||
|
wc = -EILSEQ;
|
||||||
|
return wc;
|
||||||
|
mem_err_out:
|
||||||
|
ntfs_error(vol->sb, "Failed to allocate name!");
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_ucsnlen - determine the length of a little endian Unicode string
|
||||||
|
* @s: pointer to Unicode string
|
||||||
|
* @maxlen: maximum length of string @s
|
||||||
|
*
|
||||||
|
* Return the number of Unicode characters in the little endian Unicode
|
||||||
|
* string @s up to a maximum of maxlen Unicode characters, not including
|
||||||
|
* the terminating (__le16)'\0'. If there is no (__le16)'\0' between @s
|
||||||
|
* and @s + @maxlen, @maxlen is returned.
|
||||||
|
*
|
||||||
|
* This function never looks beyond @s + @maxlen.
|
||||||
|
*/
|
||||||
|
static u32 ntfs_ucsnlen(const __le16 *s, u32 maxlen)
|
||||||
|
{
|
||||||
|
u32 i;
|
||||||
|
|
||||||
|
for (i = 0; i < maxlen; i++) {
|
||||||
|
if (!le16_to_cpu(s[i]))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_ucsndup - duplicate little endian Unicode string
|
||||||
|
* @s: pointer to Unicode string
|
||||||
|
* @maxlen: maximum length of string @s
|
||||||
|
*
|
||||||
|
* Return a pointer to a new little endian Unicode string which is a duplicate
|
||||||
|
* of the string s. Memory for the new string is obtained with kmalloc,
|
||||||
|
* and can be freed with kfree.
|
||||||
|
*
|
||||||
|
* A maximum of @maxlen Unicode characters are copied and a terminating
|
||||||
|
* (__le16)'\0' little endian Unicode character is added.
|
||||||
|
*
|
||||||
|
* This function never looks beyond @s + @maxlen.
|
||||||
|
*
|
||||||
|
* Return a pointer to the new little endian Unicode string on success and NULL
|
||||||
|
* on failure with errno set to the error code.
|
||||||
|
*/
|
||||||
|
__le16 *ntfs_ucsndup(const __le16 *s, u32 maxlen)
|
||||||
|
{
|
||||||
|
__le16 *dst;
|
||||||
|
u32 len;
|
||||||
|
|
||||||
|
len = ntfs_ucsnlen(s, maxlen);
|
||||||
|
dst = kmalloc((len + 1) * sizeof(__le16), GFP_NOFS);
|
||||||
|
if (dst) {
|
||||||
|
memcpy(dst, s, len * sizeof(__le16));
|
||||||
|
dst[len] = cpu_to_le16(L'\0');
|
||||||
|
}
|
||||||
|
return dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_names_are_equal - compare two Unicode names for equality
|
||||||
|
* @s1: name to compare to @s2
|
||||||
|
* @s1_len: length in Unicode characters of @s1
|
||||||
|
* @s2: name to compare to @s1
|
||||||
|
* @s2_len: length in Unicode characters of @s2
|
||||||
|
* @ic: ignore case bool
|
||||||
|
* @upcase: upcase table (only if @ic == IGNORE_CASE)
|
||||||
|
* @upcase_size: length in Unicode characters of @upcase (if present)
|
||||||
|
*
|
||||||
|
* Compare the names @s1 and @s2 and return TRUE (1) if the names are
|
||||||
|
* identical, or FALSE (0) if they are not identical. If @ic is IGNORE_CASE,
|
||||||
|
* the @upcase table is used to perform a case insensitive comparison.
|
||||||
|
*/
|
||||||
|
bool ntfs_names_are_equal(const __le16 *s1, size_t s1_len,
|
||||||
|
const __le16 *s2, size_t s2_len,
|
||||||
|
const u32 ic,
|
||||||
|
const __le16 *upcase, const u32 upcase_size)
|
||||||
|
{
|
||||||
|
if (s1_len != s2_len)
|
||||||
|
return false;
|
||||||
|
if (!s1_len)
|
||||||
|
return true;
|
||||||
|
if (ic == CASE_SENSITIVE)
|
||||||
|
return ntfs_ucsncmp(s1, s2, s1_len) ? false : true;
|
||||||
|
return ntfs_ucsncasecmp(s1, s2, s1_len, upcase, upcase_size) ? false : true;
|
||||||
|
}
|
||||||
70
fs/ntfs/upcase.c
Normal file
70
fs/ntfs/upcase.c
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* Generate the full NTFS Unicode upcase table in little endian.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2001 Richard Russon <ntfs@flatcap.org>
|
||||||
|
* Copyright (c) 2001-2006 Anton Altaparmakov
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ntfs.h"
|
||||||
|
|
||||||
|
__le16 *generate_default_upcase(void)
|
||||||
|
{
|
||||||
|
static const int uc_run_table[][3] = { /* Start, End, Add */
|
||||||
|
{0x0061, 0x007B, -32}, {0x0451, 0x045D, -80}, {0x1F70, 0x1F72, 74},
|
||||||
|
{0x00E0, 0x00F7, -32}, {0x045E, 0x0460, -80}, {0x1F72, 0x1F76, 86},
|
||||||
|
{0x00F8, 0x00FF, -32}, {0x0561, 0x0587, -48}, {0x1F76, 0x1F78, 100},
|
||||||
|
{0x0256, 0x0258, -205}, {0x1F00, 0x1F08, 8}, {0x1F78, 0x1F7A, 128},
|
||||||
|
{0x028A, 0x028C, -217}, {0x1F10, 0x1F16, 8}, {0x1F7A, 0x1F7C, 112},
|
||||||
|
{0x03AC, 0x03AD, -38}, {0x1F20, 0x1F28, 8}, {0x1F7C, 0x1F7E, 126},
|
||||||
|
{0x03AD, 0x03B0, -37}, {0x1F30, 0x1F38, 8}, {0x1FB0, 0x1FB2, 8},
|
||||||
|
{0x03B1, 0x03C2, -32}, {0x1F40, 0x1F46, 8}, {0x1FD0, 0x1FD2, 8},
|
||||||
|
{0x03C2, 0x03C3, -31}, {0x1F51, 0x1F52, 8}, {0x1FE0, 0x1FE2, 8},
|
||||||
|
{0x03C3, 0x03CC, -32}, {0x1F53, 0x1F54, 8}, {0x1FE5, 0x1FE6, 7},
|
||||||
|
{0x03CC, 0x03CD, -64}, {0x1F55, 0x1F56, 8}, {0x2170, 0x2180, -16},
|
||||||
|
{0x03CD, 0x03CF, -63}, {0x1F57, 0x1F58, 8}, {0x24D0, 0x24EA, -26},
|
||||||
|
{0x0430, 0x0450, -32}, {0x1F60, 0x1F68, 8}, {0xFF41, 0xFF5B, -32},
|
||||||
|
{0}
|
||||||
|
};
|
||||||
|
|
||||||
|
static const int uc_dup_table[][2] = { /* Start, End */
|
||||||
|
{0x0100, 0x012F}, {0x01A0, 0x01A6}, {0x03E2, 0x03EF}, {0x04CB, 0x04CC},
|
||||||
|
{0x0132, 0x0137}, {0x01B3, 0x01B7}, {0x0460, 0x0481}, {0x04D0, 0x04EB},
|
||||||
|
{0x0139, 0x0149}, {0x01CD, 0x01DD}, {0x0490, 0x04BF}, {0x04EE, 0x04F5},
|
||||||
|
{0x014A, 0x0178}, {0x01DE, 0x01EF}, {0x04BF, 0x04BF}, {0x04F8, 0x04F9},
|
||||||
|
{0x0179, 0x017E}, {0x01F4, 0x01F5}, {0x04C1, 0x04C4}, {0x1E00, 0x1E95},
|
||||||
|
{0x018B, 0x018B}, {0x01FA, 0x0218}, {0x04C7, 0x04C8}, {0x1EA0, 0x1EF9},
|
||||||
|
{0}
|
||||||
|
};
|
||||||
|
|
||||||
|
static const int uc_word_table[][2] = { /* Offset, Value */
|
||||||
|
{0x00FF, 0x0178}, {0x01AD, 0x01AC}, {0x01F3, 0x01F1}, {0x0269, 0x0196},
|
||||||
|
{0x0183, 0x0182}, {0x01B0, 0x01AF}, {0x0253, 0x0181}, {0x026F, 0x019C},
|
||||||
|
{0x0185, 0x0184}, {0x01B9, 0x01B8}, {0x0254, 0x0186}, {0x0272, 0x019D},
|
||||||
|
{0x0188, 0x0187}, {0x01BD, 0x01BC}, {0x0259, 0x018F}, {0x0275, 0x019F},
|
||||||
|
{0x018C, 0x018B}, {0x01C6, 0x01C4}, {0x025B, 0x0190}, {0x0283, 0x01A9},
|
||||||
|
{0x0192, 0x0191}, {0x01C9, 0x01C7}, {0x0260, 0x0193}, {0x0288, 0x01AE},
|
||||||
|
{0x0199, 0x0198}, {0x01CC, 0x01CA}, {0x0263, 0x0194}, {0x0292, 0x01B7},
|
||||||
|
{0x01A8, 0x01A7}, {0x01DD, 0x018E}, {0x0268, 0x0197},
|
||||||
|
{0}
|
||||||
|
};
|
||||||
|
|
||||||
|
int i, r;
|
||||||
|
__le16 *uc;
|
||||||
|
|
||||||
|
uc = kvcalloc(default_upcase_len, sizeof(__le16), GFP_NOFS);
|
||||||
|
if (!uc)
|
||||||
|
return uc;
|
||||||
|
/* Generate the little endian Unicode upcase table used by ntfs. */
|
||||||
|
for (i = 0; i < default_upcase_len; i++)
|
||||||
|
uc[i] = cpu_to_le16(i);
|
||||||
|
for (r = 0; uc_run_table[r][0]; r++)
|
||||||
|
for (i = uc_run_table[r][0]; i < uc_run_table[r][1]; i++)
|
||||||
|
le16_add_cpu(&uc[i], uc_run_table[r][2]);
|
||||||
|
for (r = 0; uc_dup_table[r][0]; r++)
|
||||||
|
for (i = uc_dup_table[r][0]; i < uc_dup_table[r][1]; i += 2)
|
||||||
|
le16_add_cpu(&uc[i + 1], -1);
|
||||||
|
for (r = 0; uc_word_table[r][0]; r++)
|
||||||
|
uc[uc_word_table[r][0]] = cpu_to_le16(uc_word_table[r][1]);
|
||||||
|
return uc;
|
||||||
|
}
|
||||||
296
fs/ntfs/volume.h
Normal file
296
fs/ntfs/volume.h
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
/*
|
||||||
|
* Defines for volume structures in NTFS Linux kernel driver.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2001-2006 Anton Altaparmakov
|
||||||
|
* Copyright (c) 2002 Richard Russon
|
||||||
|
* Copyright (c) 2025 LG Electronics Co., Ltd.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _LINUX_NTFS_VOLUME_H
|
||||||
|
#define _LINUX_NTFS_VOLUME_H
|
||||||
|
|
||||||
|
#include <linux/rwsem.h>
|
||||||
|
#include <linux/sched.h>
|
||||||
|
#include <linux/wait.h>
|
||||||
|
#include <linux/uidgid.h>
|
||||||
|
#include <linux/workqueue.h>
|
||||||
|
#include <linux/errseq.h>
|
||||||
|
|
||||||
|
#include "layout.h"
|
||||||
|
|
||||||
|
#define NTFS_VOL_UID BIT(1)
|
||||||
|
#define NTFS_VOL_GID BIT(2)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The NTFS in memory super block structure.
|
||||||
|
*
|
||||||
|
* @sb: Pointer back to the super_block.
|
||||||
|
* @nr_blocks: Number of sb->s_blocksize bytes sized blocks on the device.
|
||||||
|
* @flags: Miscellaneous flags, see below.
|
||||||
|
* @uid: uid that files will be mounted as.
|
||||||
|
* @gid: gid that files will be mounted as.
|
||||||
|
* @fmask: The mask for file permissions.
|
||||||
|
* @dmask: The mask for directory permissions.
|
||||||
|
* @mft_zone_multiplier: Initial mft zone multiplier.
|
||||||
|
* @on_errors: What to do on filesystem errors.
|
||||||
|
* @wb_err: Writeback error tracking.
|
||||||
|
* @sector_size: in bytes
|
||||||
|
* @sector_size_bits: log2(sector_size)
|
||||||
|
* @cluster_size: in bytes
|
||||||
|
* @cluster_size_mask: cluster_size - 1
|
||||||
|
* @cluster_size_bits: log2(cluster_size)
|
||||||
|
* @mft_record_size: in bytes
|
||||||
|
* @mft_record_size_mask: mft_record_size - 1
|
||||||
|
* @mft_record_size_bits: log2(mft_record_size)
|
||||||
|
* @index_record_size: in bytes
|
||||||
|
* @index_record_size_mask: index_record_size - 1
|
||||||
|
* @index_record_size_bits: log2(index_record_size)
|
||||||
|
* @nr_clusters: Volume size in clusters == number of bits in lcn bitmap.
|
||||||
|
* @mft_lcn: Cluster location of mft data.
|
||||||
|
* @mftmirr_lcn: Cluster location of copy of mft.
|
||||||
|
* @serial_no: The volume serial number.
|
||||||
|
* @upcase_len: Number of entries in upcase[].
|
||||||
|
* @upcase: The upcase table.
|
||||||
|
* @attrdef_size: Size of the attribute definition table in bytes.
|
||||||
|
* @attrdef: Table of attribute definitions. Obtained from FILE_AttrDef.
|
||||||
|
* @mft_data_pos: Mft record number at which to allocate the next mft record.
|
||||||
|
* @mft_zone_start: First cluster of the mft zone.
|
||||||
|
* @mft_zone_end: First cluster beyond the mft zone.
|
||||||
|
* @mft_zone_pos: Current position in the mft zone.
|
||||||
|
* @data1_zone_pos: Current position in the first data zone.
|
||||||
|
* @data2_zone_pos: Current position in the second data zone.
|
||||||
|
* @mft_ino: The VFS inode of $MFT.
|
||||||
|
* @mftbmp_ino: Attribute inode for $MFT/$BITMAP.
|
||||||
|
* @mftbmp_lock: Lock for serializing accesses to the mft record bitmap.
|
||||||
|
* @mftmirr_ino: The VFS inode of $MFTMirr.
|
||||||
|
* @mftmirr_size: Size of mft mirror in mft records.
|
||||||
|
* @logfile_ino: The VFS inode of LogFile.
|
||||||
|
* @lcnbmp_ino: The VFS inode of $Bitmap.
|
||||||
|
* @lcnbmp_lock: Lock for serializing accesses to the cluster bitmap
|
||||||
|
* @vol_ino: The VFS inode of $Volume.
|
||||||
|
* @vol_flags: Volume flags.
|
||||||
|
* @major_ver: Ntfs major version of volume.
|
||||||
|
* @minor_ver: Ntfs minor version of volume.
|
||||||
|
* @volume_label: volume label.
|
||||||
|
* @root_ino: The VFS inode of the root directory.
|
||||||
|
* @secure_ino: The VFS inode of $Secure (NTFS3.0+ only, otherwise NULL).
|
||||||
|
* @extend_ino: The VFS inode of $Extend (NTFS3.0+ only, otherwise NULL).
|
||||||
|
* @quota_ino: The VFS inode of $Quota.
|
||||||
|
* @quota_q_ino: Attribute inode for $Quota/$Q.
|
||||||
|
* @nls_map: NLS (National Language Support) table.
|
||||||
|
* @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.
|
||||||
|
* @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.
|
||||||
|
* @lcn_empty_bits_per_page: Number of empty bits per page in the LCN bitmap.
|
||||||
|
* @precalc_work: Work structure for background pre-calculation tasks.
|
||||||
|
* @preallocated_size: reallocation size (in bytes).
|
||||||
|
*/
|
||||||
|
struct ntfs_volume {
|
||||||
|
struct super_block *sb;
|
||||||
|
s64 nr_blocks;
|
||||||
|
unsigned long flags;
|
||||||
|
kuid_t uid;
|
||||||
|
kgid_t gid;
|
||||||
|
umode_t fmask;
|
||||||
|
umode_t dmask;
|
||||||
|
u8 mft_zone_multiplier;
|
||||||
|
u8 on_errors;
|
||||||
|
errseq_t wb_err;
|
||||||
|
u16 sector_size;
|
||||||
|
u8 sector_size_bits;
|
||||||
|
u32 cluster_size;
|
||||||
|
u32 cluster_size_mask;
|
||||||
|
u8 cluster_size_bits;
|
||||||
|
u32 mft_record_size;
|
||||||
|
u32 mft_record_size_mask;
|
||||||
|
u8 mft_record_size_bits;
|
||||||
|
u32 index_record_size;
|
||||||
|
u32 index_record_size_mask;
|
||||||
|
u8 index_record_size_bits;
|
||||||
|
s64 nr_clusters;
|
||||||
|
s64 mft_lcn;
|
||||||
|
s64 mftmirr_lcn;
|
||||||
|
u64 serial_no;
|
||||||
|
u32 upcase_len;
|
||||||
|
__le16 *upcase;
|
||||||
|
s32 attrdef_size;
|
||||||
|
struct attr_def *attrdef;
|
||||||
|
s64 mft_data_pos;
|
||||||
|
s64 mft_zone_start;
|
||||||
|
s64 mft_zone_end;
|
||||||
|
s64 mft_zone_pos;
|
||||||
|
s64 data1_zone_pos;
|
||||||
|
s64 data2_zone_pos;
|
||||||
|
struct inode *mft_ino;
|
||||||
|
struct inode *mftbmp_ino;
|
||||||
|
struct rw_semaphore mftbmp_lock;
|
||||||
|
struct inode *mftmirr_ino;
|
||||||
|
int mftmirr_size;
|
||||||
|
struct inode *logfile_ino;
|
||||||
|
struct inode *lcnbmp_ino;
|
||||||
|
struct rw_semaphore lcnbmp_lock;
|
||||||
|
struct inode *vol_ino;
|
||||||
|
__le16 vol_flags;
|
||||||
|
u8 major_ver;
|
||||||
|
u8 minor_ver;
|
||||||
|
unsigned char *volume_label;
|
||||||
|
struct inode *root_ino;
|
||||||
|
struct inode *secure_ino;
|
||||||
|
struct inode *extend_ino;
|
||||||
|
struct inode *quota_ino;
|
||||||
|
struct inode *quota_q_ino;
|
||||||
|
struct nls_table *nls_map;
|
||||||
|
bool nls_utf8;
|
||||||
|
wait_queue_head_t free_waitq;
|
||||||
|
atomic64_t free_clusters;
|
||||||
|
atomic64_t free_mft_records;
|
||||||
|
atomic64_t dirty_clusters;
|
||||||
|
u8 sparse_compression_unit;
|
||||||
|
unsigned int *lcn_empty_bits_per_page;
|
||||||
|
struct work_struct precalc_work;
|
||||||
|
loff_t preallocated_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Defined bits for the flags field in the ntfs_volume structure.
|
||||||
|
*
|
||||||
|
* NV_Errors Volume has errors, prevent remount rw.
|
||||||
|
* NV_ShowSystemFiles Return system files in ntfs_readdir().
|
||||||
|
* NV_CaseSensitive Treat file names as case sensitive and
|
||||||
|
* create filenames in the POSIX namespace.
|
||||||
|
* Otherwise be case insensitive but still
|
||||||
|
* create file names in POSIX namespace.
|
||||||
|
* NV_LogFileEmpty LogFile journal is empty.
|
||||||
|
* NV_QuotaOutOfDate Quota is out of date.
|
||||||
|
* NV_UsnJrnlStamped UsnJrnl has been stamped.
|
||||||
|
* NV_ReadOnly Volume is mounted read-only.
|
||||||
|
* NV_Compression Volume supports compression.
|
||||||
|
* NV_FreeClusterKnown Free cluster count is known and up-to-date.
|
||||||
|
* NV_Shutdown Volume is in shutdown state
|
||||||
|
* NV_SysImmutable Protect system files from deletion.
|
||||||
|
* NV_ShowHiddenFiles Return hidden files in ntfs_readdir().
|
||||||
|
* NV_HideDotFiles Hide names beginning with a dot (".").
|
||||||
|
* NV_CheckWindowsNames Refuse creation/rename of files with
|
||||||
|
* Windows-reserved names (CON, AUX, NUL, COM1,
|
||||||
|
* LPT1, etc.) or invalid characters.
|
||||||
|
*
|
||||||
|
* NV_Discard Issue discard/TRIM commands for freed clusters.
|
||||||
|
* NV_DisableSparse Disable creation of sparse regions.
|
||||||
|
*/
|
||||||
|
enum {
|
||||||
|
NV_Errors,
|
||||||
|
NV_ShowSystemFiles,
|
||||||
|
NV_CaseSensitive,
|
||||||
|
NV_LogFileEmpty,
|
||||||
|
NV_QuotaOutOfDate,
|
||||||
|
NV_UsnJrnlStamped,
|
||||||
|
NV_ReadOnly,
|
||||||
|
NV_Compression,
|
||||||
|
NV_FreeClusterKnown,
|
||||||
|
NV_Shutdown,
|
||||||
|
NV_SysImmutable,
|
||||||
|
NV_ShowHiddenFiles,
|
||||||
|
NV_HideDotFiles,
|
||||||
|
NV_CheckWindowsNames,
|
||||||
|
NV_Discard,
|
||||||
|
NV_DisableSparse,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Macro tricks to expand the NVolFoo(), NVolSetFoo(), and NVolClearFoo()
|
||||||
|
* functions.
|
||||||
|
*/
|
||||||
|
#define DEFINE_NVOL_BIT_OPS(flag) \
|
||||||
|
static inline int NVol##flag(struct ntfs_volume *vol) \
|
||||||
|
{ \
|
||||||
|
return test_bit(NV_##flag, &(vol)->flags); \
|
||||||
|
} \
|
||||||
|
static inline void NVolSet##flag(struct ntfs_volume *vol) \
|
||||||
|
{ \
|
||||||
|
set_bit(NV_##flag, &(vol)->flags); \
|
||||||
|
} \
|
||||||
|
static inline void NVolClear##flag(struct ntfs_volume *vol) \
|
||||||
|
{ \
|
||||||
|
clear_bit(NV_##flag, &(vol)->flags); \
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Emit the ntfs volume bitops functions. */
|
||||||
|
DEFINE_NVOL_BIT_OPS(Errors)
|
||||||
|
DEFINE_NVOL_BIT_OPS(ShowSystemFiles)
|
||||||
|
DEFINE_NVOL_BIT_OPS(CaseSensitive)
|
||||||
|
DEFINE_NVOL_BIT_OPS(LogFileEmpty)
|
||||||
|
DEFINE_NVOL_BIT_OPS(QuotaOutOfDate)
|
||||||
|
DEFINE_NVOL_BIT_OPS(UsnJrnlStamped)
|
||||||
|
DEFINE_NVOL_BIT_OPS(ReadOnly)
|
||||||
|
DEFINE_NVOL_BIT_OPS(Compression)
|
||||||
|
DEFINE_NVOL_BIT_OPS(FreeClusterKnown)
|
||||||
|
DEFINE_NVOL_BIT_OPS(Shutdown)
|
||||||
|
DEFINE_NVOL_BIT_OPS(SysImmutable)
|
||||||
|
DEFINE_NVOL_BIT_OPS(ShowHiddenFiles)
|
||||||
|
DEFINE_NVOL_BIT_OPS(HideDotFiles)
|
||||||
|
DEFINE_NVOL_BIT_OPS(CheckWindowsNames)
|
||||||
|
DEFINE_NVOL_BIT_OPS(Discard)
|
||||||
|
DEFINE_NVOL_BIT_OPS(DisableSparse)
|
||||||
|
|
||||||
|
static inline void ntfs_inc_free_clusters(struct ntfs_volume *vol, s64 nr)
|
||||||
|
{
|
||||||
|
if (!NVolFreeClusterKnown(vol))
|
||||||
|
wait_event(vol->free_waitq, NVolFreeClusterKnown(vol));
|
||||||
|
atomic64_add(nr, &vol->free_clusters);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void ntfs_dec_free_clusters(struct ntfs_volume *vol, s64 nr)
|
||||||
|
{
|
||||||
|
if (!NVolFreeClusterKnown(vol))
|
||||||
|
wait_event(vol->free_waitq, NVolFreeClusterKnown(vol));
|
||||||
|
atomic64_sub(nr, &vol->free_clusters);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void ntfs_inc_free_mft_records(struct ntfs_volume *vol, s64 nr)
|
||||||
|
{
|
||||||
|
if (!NVolFreeClusterKnown(vol))
|
||||||
|
return;
|
||||||
|
|
||||||
|
atomic64_add(nr, &vol->free_mft_records);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void ntfs_dec_free_mft_records(struct ntfs_volume *vol, s64 nr)
|
||||||
|
{
|
||||||
|
if (!NVolFreeClusterKnown(vol))
|
||||||
|
return;
|
||||||
|
|
||||||
|
atomic64_sub(nr, &vol->free_mft_records);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void ntfs_set_lcn_empty_bits(struct ntfs_volume *vol, unsigned long index,
|
||||||
|
u8 val, unsigned int count)
|
||||||
|
{
|
||||||
|
if (!NVolFreeClusterKnown(vol))
|
||||||
|
wait_event(vol->free_waitq, NVolFreeClusterKnown(vol));
|
||||||
|
|
||||||
|
if (val)
|
||||||
|
vol->lcn_empty_bits_per_page[index] -= count;
|
||||||
|
else
|
||||||
|
vol->lcn_empty_bits_per_page[index] += count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static __always_inline void ntfs_hold_dirty_clusters(struct ntfs_volume *vol, s64 nr_clusters)
|
||||||
|
{
|
||||||
|
atomic64_add(nr_clusters, &vol->dirty_clusters);
|
||||||
|
}
|
||||||
|
|
||||||
|
static __always_inline void ntfs_release_dirty_clusters(struct ntfs_volume *vol, s64 nr_clusters)
|
||||||
|
{
|
||||||
|
if (atomic64_read(&vol->dirty_clusters) < nr_clusters)
|
||||||
|
atomic64_set(&vol->dirty_clusters, 0);
|
||||||
|
else
|
||||||
|
atomic64_sub(nr_clusters, &vol->dirty_clusters);
|
||||||
|
}
|
||||||
|
|
||||||
|
s64 ntfs_available_clusters_count(struct ntfs_volume *vol, s64 nr_clusters);
|
||||||
|
s64 get_nr_free_clusters(struct ntfs_volume *vol);
|
||||||
|
#endif /* _LINUX_NTFS_VOLUME_H */
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
# SPDX-License-Identifier: GPL-2.0-only
|
# SPDX-License-Identifier: GPL-2.0-only
|
||||||
config NTFS3_FS
|
config NTFS3_FS
|
||||||
tristate "NTFS Read-Write file system support"
|
tristate "NTFS Read-Write file system support"
|
||||||
|
depends on !NTFS_FS || m
|
||||||
select BUFFER_HEAD
|
select BUFFER_HEAD
|
||||||
select NLS
|
select NLS
|
||||||
select LEGACY_DIRECT_IO
|
select LEGACY_DIRECT_IO
|
||||||
@@ -46,12 +47,3 @@ config NTFS3_FS_POSIX_ACL
|
|||||||
NOTE: this is linux only feature. Windows will ignore these ACLs.
|
NOTE: this is linux only feature. Windows will ignore these ACLs.
|
||||||
|
|
||||||
If you don't know what Access Control Lists are, say N.
|
If you don't know what Access Control Lists are, say N.
|
||||||
|
|
||||||
config NTFS_FS
|
|
||||||
tristate "NTFS file system support"
|
|
||||||
select NTFS3_FS
|
|
||||||
select BUFFER_HEAD
|
|
||||||
select NLS
|
|
||||||
help
|
|
||||||
This config option is here only for backward compatibility. NTFS
|
|
||||||
filesystem is now handled by the NTFS3 driver.
|
|
||||||
|
|||||||
@@ -676,14 +676,4 @@ const struct file_operations ntfs_dir_operations = {
|
|||||||
#endif
|
#endif
|
||||||
.setlease = generic_setlease,
|
.setlease = generic_setlease,
|
||||||
};
|
};
|
||||||
|
|
||||||
#if IS_ENABLED(CONFIG_NTFS_FS)
|
|
||||||
const struct file_operations ntfs_legacy_dir_operations = {
|
|
||||||
.llseek = generic_file_llseek,
|
|
||||||
.read = generic_read_dir,
|
|
||||||
.iterate_shared = ntfs_readdir,
|
|
||||||
.open = ntfs_file_open,
|
|
||||||
.setlease = generic_setlease,
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|||||||
@@ -1566,15 +1566,4 @@ const struct file_operations ntfs_file_operations = {
|
|||||||
.release = ntfs_file_release,
|
.release = ntfs_file_release,
|
||||||
.setlease = generic_setlease,
|
.setlease = generic_setlease,
|
||||||
};
|
};
|
||||||
|
|
||||||
#if IS_ENABLED(CONFIG_NTFS_FS)
|
|
||||||
const struct file_operations ntfs_legacy_file_operations = {
|
|
||||||
.llseek = generic_file_llseek,
|
|
||||||
.read_iter = ntfs_file_read_iter,
|
|
||||||
.splice_read = ntfs_file_splice_read,
|
|
||||||
.open = ntfs_file_open,
|
|
||||||
.release = ntfs_file_release,
|
|
||||||
.setlease = generic_setlease,
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|||||||
@@ -443,9 +443,7 @@ end_enum:
|
|||||||
* Usually a hard links to directories are disabled.
|
* Usually a hard links to directories are disabled.
|
||||||
*/
|
*/
|
||||||
inode->i_op = &ntfs_dir_inode_operations;
|
inode->i_op = &ntfs_dir_inode_operations;
|
||||||
inode->i_fop = unlikely(is_legacy_ntfs(sb)) ?
|
inode->i_fop = &ntfs_dir_operations;
|
||||||
&ntfs_legacy_dir_operations :
|
|
||||||
&ntfs_dir_operations;
|
|
||||||
ni->i_valid = 0;
|
ni->i_valid = 0;
|
||||||
} else if (S_ISLNK(mode)) {
|
} else if (S_ISLNK(mode)) {
|
||||||
ni->std_fa &= ~FILE_ATTRIBUTE_DIRECTORY;
|
ni->std_fa &= ~FILE_ATTRIBUTE_DIRECTORY;
|
||||||
@@ -455,9 +453,7 @@ end_enum:
|
|||||||
} else if (S_ISREG(mode)) {
|
} else if (S_ISREG(mode)) {
|
||||||
ni->std_fa &= ~FILE_ATTRIBUTE_DIRECTORY;
|
ni->std_fa &= ~FILE_ATTRIBUTE_DIRECTORY;
|
||||||
inode->i_op = &ntfs_file_inode_operations;
|
inode->i_op = &ntfs_file_inode_operations;
|
||||||
inode->i_fop = unlikely(is_legacy_ntfs(sb)) ?
|
inode->i_fop = &ntfs_file_operations;
|
||||||
&ntfs_legacy_file_operations :
|
|
||||||
&ntfs_file_operations;
|
|
||||||
inode->i_mapping->a_ops = is_compressed(ni) ? &ntfs_aops_cmpr :
|
inode->i_mapping->a_ops = is_compressed(ni) ? &ntfs_aops_cmpr :
|
||||||
&ntfs_aops;
|
&ntfs_aops;
|
||||||
if (ino != MFT_REC_MFT)
|
if (ino != MFT_REC_MFT)
|
||||||
@@ -1601,9 +1597,7 @@ int ntfs_create_inode(struct mnt_idmap *idmap, struct inode *dir,
|
|||||||
|
|
||||||
if (S_ISDIR(mode)) {
|
if (S_ISDIR(mode)) {
|
||||||
inode->i_op = &ntfs_dir_inode_operations;
|
inode->i_op = &ntfs_dir_inode_operations;
|
||||||
inode->i_fop = unlikely(is_legacy_ntfs(sb)) ?
|
inode->i_fop = &ntfs_dir_operations;
|
||||||
&ntfs_legacy_dir_operations :
|
|
||||||
&ntfs_dir_operations;
|
|
||||||
} else if (S_ISLNK(mode)) {
|
} else if (S_ISLNK(mode)) {
|
||||||
inode->i_op = &ntfs_link_inode_operations;
|
inode->i_op = &ntfs_link_inode_operations;
|
||||||
inode->i_fop = NULL;
|
inode->i_fop = NULL;
|
||||||
@@ -1612,9 +1606,7 @@ int ntfs_create_inode(struct mnt_idmap *idmap, struct inode *dir,
|
|||||||
inode_nohighmem(inode);
|
inode_nohighmem(inode);
|
||||||
} else if (S_ISREG(mode)) {
|
} else if (S_ISREG(mode)) {
|
||||||
inode->i_op = &ntfs_file_inode_operations;
|
inode->i_op = &ntfs_file_inode_operations;
|
||||||
inode->i_fop = unlikely(is_legacy_ntfs(sb)) ?
|
inode->i_fop = &ntfs_file_operations;
|
||||||
&ntfs_legacy_file_operations :
|
|
||||||
&ntfs_file_operations;
|
|
||||||
inode->i_mapping->a_ops = is_compressed(ni) ? &ntfs_aops_cmpr :
|
inode->i_mapping->a_ops = is_compressed(ni) ? &ntfs_aops_cmpr :
|
||||||
&ntfs_aops;
|
&ntfs_aops;
|
||||||
init_rwsem(&ni->file.run_lock);
|
init_rwsem(&ni->file.run_lock);
|
||||||
|
|||||||
@@ -530,7 +530,6 @@ struct inode *dir_search_u(struct inode *dir, const struct cpu_str *uni,
|
|||||||
struct ntfs_fnd *fnd);
|
struct ntfs_fnd *fnd);
|
||||||
bool dir_is_empty(struct inode *dir);
|
bool dir_is_empty(struct inode *dir);
|
||||||
extern const struct file_operations ntfs_dir_operations;
|
extern const struct file_operations ntfs_dir_operations;
|
||||||
extern const struct file_operations ntfs_legacy_dir_operations;
|
|
||||||
|
|
||||||
/* Globals from file.c */
|
/* Globals from file.c */
|
||||||
int ntfs_getattr(struct mnt_idmap *idmap, const struct path *path,
|
int ntfs_getattr(struct mnt_idmap *idmap, const struct path *path,
|
||||||
@@ -546,7 +545,6 @@ long ntfs_compat_ioctl(struct file *filp, u32 cmd, unsigned long arg);
|
|||||||
extern const struct inode_operations ntfs_special_inode_operations;
|
extern const struct inode_operations ntfs_special_inode_operations;
|
||||||
extern const struct inode_operations ntfs_file_inode_operations;
|
extern const struct inode_operations ntfs_file_inode_operations;
|
||||||
extern const struct file_operations ntfs_file_operations;
|
extern const struct file_operations ntfs_file_operations;
|
||||||
extern const struct file_operations ntfs_legacy_file_operations;
|
|
||||||
|
|
||||||
/* Globals from frecord.c */
|
/* Globals from frecord.c */
|
||||||
void ni_remove_mi(struct ntfs_inode *ni, struct mft_inode *mi);
|
void ni_remove_mi(struct ntfs_inode *ni, struct mft_inode *mi);
|
||||||
@@ -1250,13 +1248,4 @@ static inline void le64_sub_cpu(__le64 *var, u64 val)
|
|||||||
*var = cpu_to_le64(le64_to_cpu(*var) - val);
|
*var = cpu_to_le64(le64_to_cpu(*var) - val);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if IS_ENABLED(CONFIG_NTFS_FS)
|
|
||||||
bool is_legacy_ntfs(struct super_block *sb);
|
|
||||||
#else
|
|
||||||
static inline bool is_legacy_ntfs(struct super_block *sb)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* _LINUX_NTFS3_NTFS_FS_H */
|
#endif /* _LINUX_NTFS3_NTFS_FS_H */
|
||||||
|
|||||||
@@ -434,12 +434,6 @@ static int ntfs_fs_reconfigure(struct fs_context *fc)
|
|||||||
struct ntfs_mount_options *new_opts = fc->fs_private;
|
struct ntfs_mount_options *new_opts = fc->fs_private;
|
||||||
int ro_rw;
|
int ro_rw;
|
||||||
|
|
||||||
/* If ntfs3 is used as legacy ntfs enforce read-only mode. */
|
|
||||||
if (is_legacy_ntfs(sb)) {
|
|
||||||
fc->sb_flags |= SB_RDONLY;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
ro_rw = sb_rdonly(sb) && !(fc->sb_flags & SB_RDONLY);
|
ro_rw = sb_rdonly(sb) && !(fc->sb_flags & SB_RDONLY);
|
||||||
if (ro_rw && (sbi->flags & NTFS_FLAGS_NEED_REPLAY)) {
|
if (ro_rw && (sbi->flags & NTFS_FLAGS_NEED_REPLAY)) {
|
||||||
errorf(fc,
|
errorf(fc,
|
||||||
@@ -466,7 +460,6 @@ static int ntfs_fs_reconfigure(struct fs_context *fc)
|
|||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
out:
|
|
||||||
sync_filesystem(sb);
|
sync_filesystem(sb);
|
||||||
swap(sbi->options, fc->fs_private);
|
swap(sbi->options, fc->fs_private);
|
||||||
|
|
||||||
@@ -1699,8 +1692,6 @@ load_root:
|
|||||||
|
|
||||||
ntfs_create_procdir(sb);
|
ntfs_create_procdir(sb);
|
||||||
|
|
||||||
if (is_legacy_ntfs(sb))
|
|
||||||
sb->s_flags |= SB_RDONLY;
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
put_inode_out:
|
put_inode_out:
|
||||||
@@ -1823,7 +1814,7 @@ static const struct fs_context_operations ntfs_context_ops = {
|
|||||||
* This will called when mount/remount. We will first initialize
|
* This will called when mount/remount. We will first initialize
|
||||||
* options so that if remount we can use just that.
|
* options so that if remount we can use just that.
|
||||||
*/
|
*/
|
||||||
static int __ntfs_init_fs_context(struct fs_context *fc)
|
static int ntfs_init_fs_context(struct fs_context *fc)
|
||||||
{
|
{
|
||||||
struct ntfs_mount_options *opts;
|
struct ntfs_mount_options *opts;
|
||||||
struct ntfs_sb_info *sbi;
|
struct ntfs_sb_info *sbi;
|
||||||
@@ -1877,11 +1868,6 @@ free_opts:
|
|||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ntfs_init_fs_context(struct fs_context *fc)
|
|
||||||
{
|
|
||||||
return __ntfs_init_fs_context(fc);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ntfs3_kill_sb(struct super_block *sb)
|
static void ntfs3_kill_sb(struct super_block *sb)
|
||||||
{
|
{
|
||||||
struct ntfs_sb_info *sbi = sb->s_fs_info;
|
struct ntfs_sb_info *sbi = sb->s_fs_info;
|
||||||
@@ -1903,47 +1889,6 @@ static struct file_system_type ntfs_fs_type = {
|
|||||||
.fs_flags = FS_REQUIRES_DEV | FS_ALLOW_IDMAP,
|
.fs_flags = FS_REQUIRES_DEV | FS_ALLOW_IDMAP,
|
||||||
};
|
};
|
||||||
|
|
||||||
#if IS_ENABLED(CONFIG_NTFS_FS)
|
|
||||||
static int ntfs_legacy_init_fs_context(struct fs_context *fc)
|
|
||||||
{
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
ret = __ntfs_init_fs_context(fc);
|
|
||||||
/* If ntfs3 is used as legacy ntfs enforce read-only mode. */
|
|
||||||
fc->sb_flags |= SB_RDONLY;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct file_system_type ntfs_legacy_fs_type = {
|
|
||||||
.owner = THIS_MODULE,
|
|
||||||
.name = "ntfs",
|
|
||||||
.init_fs_context = ntfs_legacy_init_fs_context,
|
|
||||||
.parameters = ntfs_fs_parameters,
|
|
||||||
.kill_sb = ntfs3_kill_sb,
|
|
||||||
.fs_flags = FS_REQUIRES_DEV | FS_ALLOW_IDMAP,
|
|
||||||
};
|
|
||||||
MODULE_ALIAS_FS("ntfs");
|
|
||||||
|
|
||||||
static inline void register_as_ntfs_legacy(void)
|
|
||||||
{
|
|
||||||
int err = register_filesystem(&ntfs_legacy_fs_type);
|
|
||||||
if (err)
|
|
||||||
pr_warn("ntfs3: Failed to register legacy ntfs filesystem driver: %d\n", err);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void unregister_as_ntfs_legacy(void)
|
|
||||||
{
|
|
||||||
unregister_filesystem(&ntfs_legacy_fs_type);
|
|
||||||
}
|
|
||||||
bool is_legacy_ntfs(struct super_block *sb)
|
|
||||||
{
|
|
||||||
return sb->s_type == &ntfs_legacy_fs_type;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
static inline void register_as_ntfs_legacy(void) {}
|
|
||||||
static inline void unregister_as_ntfs_legacy(void) {}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
static int __init init_ntfs_fs(void)
|
static int __init init_ntfs_fs(void)
|
||||||
@@ -1972,7 +1917,6 @@ static int __init init_ntfs_fs(void)
|
|||||||
goto out1;
|
goto out1;
|
||||||
}
|
}
|
||||||
|
|
||||||
register_as_ntfs_legacy();
|
|
||||||
err = register_filesystem(&ntfs_fs_type);
|
err = register_filesystem(&ntfs_fs_type);
|
||||||
if (err)
|
if (err)
|
||||||
goto out;
|
goto out;
|
||||||
@@ -1992,7 +1936,6 @@ static void __exit exit_ntfs_fs(void)
|
|||||||
rcu_barrier();
|
rcu_barrier();
|
||||||
kmem_cache_destroy(ntfs_inode_cachep);
|
kmem_cache_destroy(ntfs_inode_cachep);
|
||||||
unregister_filesystem(&ntfs_fs_type);
|
unregister_filesystem(&ntfs_fs_type);
|
||||||
unregister_as_ntfs_legacy();
|
|
||||||
ntfs3_exit_bitmap();
|
ntfs3_exit_bitmap();
|
||||||
ntfs_remove_proc_root();
|
ntfs_remove_proc_root();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -657,4 +657,16 @@ struct procmap_query {
|
|||||||
__u64 build_id_addr; /* in */
|
__u64 build_id_addr; /* in */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Shutdown the filesystem.
|
||||||
|
*/
|
||||||
|
#define FS_IOC_SHUTDOWN _IOR('X', 125, __u32)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Flags for FS_IOC_SHUTDOWN
|
||||||
|
*/
|
||||||
|
#define FS_SHUTDOWN_FLAGS_DEFAULT 0x0
|
||||||
|
#define FS_SHUTDOWN_FLAGS_LOGFLUSH 0x1 /* flush log but not data*/
|
||||||
|
#define FS_SHUTDOWN_FLAGS_NOLOGFLUSH 0x2 /* don't flush log nor data */
|
||||||
|
|
||||||
#endif /* _UAPI_LINUX_FS_H */
|
#endif /* _UAPI_LINUX_FS_H */
|
||||||
|
|||||||
Reference in New Issue
Block a user