mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-07-22 02:17:36 -04:00
Merge tag 'ntfs-for-7.2-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/ntfs
Pull ntfs updates from Namjae Jeon: - Harden handling of malformed on-disk metadata. This adds stricter validation for attributes, attribute lists, index roots and entries, EA entries, mapping pairs, and $LogFile restart areas. These changes fix several out-of-bounds access, integer overflow, and inconsistent metadata handling issues. - Prevent a writeback deadlock involving extent MFT records - Fix resource leaks in fill_super() failure paths and the name cache - Serialize volume label access and improving its error handling - Fix mapping-pairs decoding bounds and LCN overflow checks - Keep resident index root metadata consistent during resize - Fix the reported size of symbolic links - Avoid an unnecessary allocation for resident inline data - Add support for following and creating Windows native symbolic links. Relative links, absolute links, and junctions are handled, with new mount options controlling native symlink creation and absolute target translation. The existing WSL symlink behavior remains the default. - The unsupported quota code is removed, along with several smaller cleanups * tag 'ntfs-for-7.2-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/ntfs: (39 commits) docs/fs/ntfs: add mount options to support Windows native symbolic links ntfs: support creating Windows native symlinks ntfs: clean up target name conversion for WSL symlinks ntfs: add native_symlink mount option ntfs: support following Windows native symlink with absolute paths ntfs: support following Windows native symlink with relative paths ntfs: fix incorrect size of symbolic link ntfs: use direct pointer for inline data to avoid redundant allocation ntfs: validate resident index root values on lookup ntfs: update index root allocated size before shrink ntfs: grow index root value before reparent header update ntfs: reject non-resident records for resident-only attributes ntfs: fix u16 truncation of restart-area length check ntfs: bound the attribute-list entry in ntfs_read_inode_mount() ntfs: bound the look-ahead attribute-list entry in ntfs_external_attr_find() ntfs: validate resident attribute lists and harden the validator ntfs: validate resident volume name values on lookup ntfs: reinit search context before volume information lookup ntfs: do not replace volume name after lookup errors ntfs: validate attribute values on lookup ...
This commit is contained in:
@@ -156,4 +156,17 @@ windows_names=<BOOL> Refuse creation/rename of files with characters or
|
||||
discard=<BOOL> Issue block device discard for clusters freed on
|
||||
file deletion/truncation to inform underlying
|
||||
storage.
|
||||
|
||||
native_symlink=raw|rel Configure how absolute symbolic links and mount
|
||||
points (junctions) are handled. Under "raw"
|
||||
(default), the absolute target path is returned
|
||||
as-is without translation. Under "rel", it is
|
||||
rewritten as a relative path anchored at
|
||||
the volume root.
|
||||
|
||||
symlink=wsl|native Configure how symbolic links are created. Under
|
||||
"wsl" (default), WSL (Windows Subsystem for
|
||||
Linux) compatible symlinks are created. Under
|
||||
"native", Windows native symbolic links are
|
||||
created.
|
||||
======================= ====================================================
|
||||
|
||||
@@ -5,6 +5,6 @@ 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
|
||||
iomap.o debug.o sysctl.o object_id.o bdev-io.o
|
||||
|
||||
ccflags-$(CONFIG_NTFS_DEBUG) += -DDEBUG
|
||||
|
||||
312
fs/ntfs/attrib.c
312
fs/ntfs/attrib.c
@@ -16,6 +16,7 @@
|
||||
* Copyright (c) 2010 Erik Larsson
|
||||
*/
|
||||
|
||||
#include <linux/string_choices.h>
|
||||
#include <linux/writeback.h>
|
||||
#include <linux/iomap.h>
|
||||
|
||||
@@ -588,6 +589,8 @@ static u32 ntfs_resident_attr_min_value_length(const __le32 type)
|
||||
sizeof(__le16) * 1;
|
||||
case AT_VOLUME_INFORMATION:
|
||||
return sizeof(struct volume_information);
|
||||
case AT_INDEX_ROOT:
|
||||
return sizeof(struct index_root);
|
||||
case AT_EA_INFORMATION:
|
||||
return sizeof(struct ea_information);
|
||||
default:
|
||||
@@ -595,6 +598,154 @@ static u32 ntfs_resident_attr_min_value_length(const __le32 type)
|
||||
}
|
||||
}
|
||||
|
||||
static bool ntfs_attr_type_is_resident_only(const __le32 type)
|
||||
{
|
||||
switch (type) {
|
||||
case AT_STANDARD_INFORMATION:
|
||||
case AT_FILE_NAME:
|
||||
case AT_OBJECT_ID:
|
||||
case AT_VOLUME_NAME:
|
||||
case AT_VOLUME_INFORMATION:
|
||||
case AT_INDEX_ROOT:
|
||||
case AT_EA_INFORMATION:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool ntfs_file_name_attr_value_is_valid(const u8 *value, const u32 value_length)
|
||||
{
|
||||
const struct file_name_attr *fn;
|
||||
u32 file_name_size;
|
||||
|
||||
fn = (const struct file_name_attr *)value;
|
||||
file_name_size = fn->file_name_length * sizeof(__le16);
|
||||
|
||||
return file_name_size <=
|
||||
value_length - offsetof(struct file_name_attr, file_name);
|
||||
}
|
||||
|
||||
static bool ntfs_volume_name_attr_value_is_valid(const u32 value_length)
|
||||
{
|
||||
if (value_length & 1)
|
||||
return false;
|
||||
|
||||
return value_length <= NTFS_MAX_LABEL_LEN * sizeof(__le16);
|
||||
}
|
||||
|
||||
static bool ntfs_index_root_attr_value_is_valid(const u8 *value, const u32 value_length)
|
||||
{
|
||||
const struct index_root *ir;
|
||||
u32 index_size;
|
||||
u32 entries_offset;
|
||||
u32 index_length;
|
||||
u32 allocated_size;
|
||||
|
||||
ir = (const struct index_root *)value;
|
||||
index_size = value_length - offsetof(struct index_root, index);
|
||||
entries_offset = le32_to_cpu(ir->index.entries_offset);
|
||||
index_length = le32_to_cpu(ir->index.index_length);
|
||||
allocated_size = le32_to_cpu(ir->index.allocated_size);
|
||||
|
||||
if ((entries_offset | index_length | allocated_size) & 7 ||
|
||||
entries_offset < sizeof(struct index_header) ||
|
||||
entries_offset > index_length ||
|
||||
index_length > allocated_size ||
|
||||
allocated_size > index_size ||
|
||||
index_length - entries_offset < sizeof(struct index_entry_header))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
struct ntfs_resident_attr_value {
|
||||
const u8 *data;
|
||||
u32 len;
|
||||
};
|
||||
|
||||
static bool ntfs_resident_attr_value_get(const struct attr_record *a,
|
||||
struct ntfs_resident_attr_value *value)
|
||||
{
|
||||
u32 attr_len;
|
||||
u16 value_offset;
|
||||
|
||||
attr_len = le32_to_cpu(a->length);
|
||||
if (attr_len < offsetof(struct attr_record, data.resident.reserved) +
|
||||
sizeof(a->data.resident.reserved))
|
||||
return false;
|
||||
|
||||
value->len = le32_to_cpu(a->data.resident.value_length);
|
||||
value_offset = le16_to_cpu(a->data.resident.value_offset);
|
||||
|
||||
if (value->len > attr_len || value_offset > attr_len - value->len)
|
||||
return false;
|
||||
|
||||
value->data = (const u8 *)a + value_offset;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ntfs_non_resident_attr_value_is_valid(const struct attr_record *a)
|
||||
{
|
||||
u32 attr_len;
|
||||
u32 min_len;
|
||||
u16 mp_offset;
|
||||
|
||||
attr_len = le32_to_cpu(a->length);
|
||||
min_len = offsetof(struct attr_record, data.non_resident.initialized_size) +
|
||||
sizeof(a->data.non_resident.initialized_size);
|
||||
if (attr_len < min_len)
|
||||
return false;
|
||||
|
||||
mp_offset = le16_to_cpu(a->data.non_resident.mapping_pairs_offset);
|
||||
return mp_offset >= min_len && mp_offset <= attr_len;
|
||||
}
|
||||
|
||||
static bool ntfs_attr_value_is_valid(struct ntfs_volume *vol,
|
||||
const struct attr_record *a,
|
||||
const u64 mft_no)
|
||||
{
|
||||
struct ntfs_resident_attr_value value;
|
||||
u32 min_len;
|
||||
|
||||
if (a->non_resident) {
|
||||
if (ntfs_attr_type_is_resident_only(a->type))
|
||||
goto corrupt;
|
||||
if (!ntfs_non_resident_attr_value_is_valid(a))
|
||||
goto corrupt;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!ntfs_resident_attr_value_get(a, &value))
|
||||
goto corrupt;
|
||||
|
||||
min_len = ntfs_resident_attr_min_value_length(a->type);
|
||||
if (min_len && value.len < min_len)
|
||||
goto corrupt;
|
||||
|
||||
switch (a->type) {
|
||||
case AT_FILE_NAME:
|
||||
if (!ntfs_file_name_attr_value_is_valid(value.data, value.len))
|
||||
goto corrupt;
|
||||
break;
|
||||
case AT_VOLUME_NAME:
|
||||
if (!ntfs_volume_name_attr_value_is_valid(value.len))
|
||||
goto corrupt;
|
||||
break;
|
||||
case AT_INDEX_ROOT:
|
||||
if (!ntfs_index_root_attr_value_is_valid(value.data, value.len))
|
||||
goto corrupt;
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
|
||||
corrupt:
|
||||
ntfs_error(vol->sb,
|
||||
"Corrupt %#x attribute in MFT record %llu\n",
|
||||
le32_to_cpu(a->type), mft_no);
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* ntfs_attr_find - find (next) attribute in mft record
|
||||
* @type: attribute type to find
|
||||
@@ -705,8 +856,11 @@ static int ntfs_attr_find(const __le32 type, const __le16 *name,
|
||||
}
|
||||
}
|
||||
|
||||
if (type == AT_UNUSED)
|
||||
if (type == AT_UNUSED) {
|
||||
if (!ntfs_attr_value_is_valid(vol, a, ctx->ntfs_ino->mft_no))
|
||||
break;
|
||||
return 0;
|
||||
}
|
||||
if (a->type != type)
|
||||
continue;
|
||||
/*
|
||||
@@ -747,37 +901,8 @@ static int ntfs_attr_find(const __le32 type, const __le16 *name,
|
||||
}
|
||||
}
|
||||
|
||||
/* Validate attribute's value offset/length */
|
||||
if (!a->non_resident) {
|
||||
u32 min_len;
|
||||
u32 value_length = le32_to_cpu(a->data.resident.value_length);
|
||||
u16 value_offset = le16_to_cpu(a->data.resident.value_offset);
|
||||
|
||||
if (value_length > le32_to_cpu(a->length) ||
|
||||
value_offset > le32_to_cpu(a->length) - value_length)
|
||||
break;
|
||||
|
||||
min_len = ntfs_resident_attr_min_value_length(a->type);
|
||||
if (min_len && value_length < min_len) {
|
||||
ntfs_error(vol->sb,
|
||||
"Too small %#x resident attribute value in MFT record %lld\n",
|
||||
le32_to_cpu(a->type), (long long)ctx->ntfs_ino->mft_no);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
u32 min_len;
|
||||
u16 mp_offset;
|
||||
|
||||
min_len = offsetof(struct attr_record, data.non_resident.initialized_size) +
|
||||
sizeof(a->data.non_resident.initialized_size);
|
||||
if (le32_to_cpu(a->length) < min_len)
|
||||
break;
|
||||
|
||||
mp_offset = le16_to_cpu(a->data.non_resident.mapping_pairs_offset);
|
||||
if (mp_offset < min_len ||
|
||||
mp_offset > le32_to_cpu(a->length))
|
||||
break;
|
||||
}
|
||||
if (!ntfs_attr_value_is_valid(vol, a, ctx->ntfs_ino->mft_no))
|
||||
break;
|
||||
|
||||
/*
|
||||
* The names match or @name not present and attribute is
|
||||
@@ -843,11 +968,71 @@ char *ntfs_attr_name_get(const struct ntfs_volume *vol, const __le16 *uname,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* ntfs_attr_list_entry_is_valid - sanity check one $ATTRIBUTE_LIST entry
|
||||
* @ale: the attribute-list entry to check
|
||||
* @al_end: end of the attribute-list buffer @ale lives in
|
||||
*
|
||||
* Verify that @ale is a well-formed attr_list_entry wholly contained in
|
||||
* [.., @al_end): its fixed header must lie in range before any field is
|
||||
* dereferenced, its length must be a multiple of 8 that covers the fixed
|
||||
* header plus the name, the name must lie within the buffer, the entry must
|
||||
* be in use and carry a live MFT reference. Return true if valid.
|
||||
*/
|
||||
bool ntfs_attr_list_entry_is_valid(const struct attr_list_entry *ale,
|
||||
const u8 *al_end)
|
||||
{
|
||||
const u8 *al = (const u8 *)ale;
|
||||
u16 ale_len;
|
||||
|
||||
/* The fixed header must be in bounds before it is parsed. */
|
||||
if (al + offsetof(struct attr_list_entry, name) > al_end)
|
||||
return false;
|
||||
ale_len = le16_to_cpu(ale->length);
|
||||
/* On-disk entries are 8-byte aligned (see struct attr_list_entry). */
|
||||
if (ale_len & 7)
|
||||
return false;
|
||||
if (ale->name_offset != sizeof(struct attr_list_entry))
|
||||
return false;
|
||||
if ((u32)ale->name_offset +
|
||||
(u32)ale->name_length * sizeof(__le16) > ale_len ||
|
||||
al + ale_len > al_end)
|
||||
return false;
|
||||
if (ale->type == AT_UNUSED)
|
||||
return false;
|
||||
if (MSEQNO_LE(ale->mft_reference) == 0)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* ntfs_attr_list_is_valid - sanity check an in-memory $ATTRIBUTE_LIST
|
||||
* @al_start: start of the attribute list buffer
|
||||
* @size: length of the attribute list in bytes
|
||||
*
|
||||
* Verify that [@al_start, @al_start + @size) is a sequence of valid
|
||||
* attr_list_entry records (see ntfs_attr_list_entry_is_valid()) that tile the
|
||||
* buffer exactly. Return true if valid, false otherwise.
|
||||
*/
|
||||
bool ntfs_attr_list_is_valid(const u8 *al_start, s64 size)
|
||||
{
|
||||
const u8 *al = al_start;
|
||||
const u8 *al_end = al_start + size;
|
||||
|
||||
while (al < al_end) {
|
||||
const struct attr_list_entry *ale =
|
||||
(const struct attr_list_entry *)al;
|
||||
|
||||
if (!ntfs_attr_list_entry_is_valid(ale, al_end))
|
||||
return false;
|
||||
al += le16_to_cpu(ale->length);
|
||||
}
|
||||
return al == al_end;
|
||||
}
|
||||
|
||||
int load_attribute_list(struct ntfs_inode *base_ni, u8 *al_start, const s64 size)
|
||||
{
|
||||
struct inode *attr_vi = NULL;
|
||||
u8 *al;
|
||||
struct attr_list_entry *ale;
|
||||
|
||||
if (!al_start || size <= 0)
|
||||
return -EINVAL;
|
||||
@@ -869,19 +1054,7 @@ int load_attribute_list(struct ntfs_inode *base_ni, u8 *al_start, const s64 size
|
||||
}
|
||||
iput(attr_vi);
|
||||
|
||||
for (al = al_start; al < al_start + size; al += le16_to_cpu(ale->length)) {
|
||||
ale = (struct attr_list_entry *)al;
|
||||
if (ale->name_offset != sizeof(struct attr_list_entry))
|
||||
break;
|
||||
if (le16_to_cpu(ale->length) <= ale->name_offset + ale->name_length ||
|
||||
al + le16_to_cpu(ale->length) > al_start + size)
|
||||
break;
|
||||
if (ale->type == AT_UNUSED)
|
||||
break;
|
||||
if (MSEQNO_LE(ale->mft_reference) == 0)
|
||||
break;
|
||||
}
|
||||
if (al != al_start + size) {
|
||||
if (!ntfs_attr_list_is_valid(al_start, size)) {
|
||||
ntfs_error(base_ni->vol->sb, "Corrupt attribute list, mft = %llu",
|
||||
base_ni->mft_no);
|
||||
return -EIO;
|
||||
@@ -1137,9 +1310,8 @@ static int ntfs_external_attr_find(const __le32 type,
|
||||
* we have reached the right one or the search has failed.
|
||||
*/
|
||||
if (lowest_vcn && (u8 *)next_al_entry >= al_start &&
|
||||
(u8 *)next_al_entry + 6 < al_end &&
|
||||
(u8 *)next_al_entry + le16_to_cpu(
|
||||
next_al_entry->length) <= al_end &&
|
||||
ntfs_attr_list_entry_is_valid(next_al_entry,
|
||||
al_end) &&
|
||||
le64_to_cpu(next_al_entry->lowest_vcn) <=
|
||||
lowest_vcn &&
|
||||
next_al_entry->type == al_entry->type &&
|
||||
@@ -1252,22 +1424,8 @@ static int ntfs_external_attr_find(const __le32 type,
|
||||
|
||||
ctx->attr = a;
|
||||
|
||||
if (a->non_resident) {
|
||||
u32 min_len;
|
||||
u16 mp_offset;
|
||||
|
||||
min_len = offsetof(struct attr_record,
|
||||
data.non_resident.initialized_size) +
|
||||
sizeof(a->data.non_resident.initialized_size);
|
||||
|
||||
if (le32_to_cpu(a->length) < min_len)
|
||||
break;
|
||||
|
||||
mp_offset =
|
||||
le16_to_cpu(a->data.non_resident.mapping_pairs_offset);
|
||||
if (mp_offset < min_len || mp_offset > attr_len)
|
||||
break;
|
||||
}
|
||||
if (!ntfs_attr_value_is_valid(vol, a, ctx->ntfs_ino->mft_no))
|
||||
break;
|
||||
|
||||
/*
|
||||
* If no @val specified or @val specified and it matches, we
|
||||
@@ -1279,19 +1437,6 @@ static int ntfs_external_attr_find(const __le32 type,
|
||||
u32 value_length = le32_to_cpu(a->data.resident.value_length);
|
||||
u16 value_offset = le16_to_cpu(a->data.resident.value_offset);
|
||||
|
||||
if (attr_len < offsetof(struct attr_record, data.resident.reserved) +
|
||||
sizeof(a->data.resident.reserved))
|
||||
break;
|
||||
if (value_length > attr_len || value_offset > attr_len - value_length)
|
||||
break;
|
||||
|
||||
value_length = ntfs_resident_attr_min_value_length(a->type);
|
||||
if (value_length && le32_to_cpu(a->data.resident.value_length) <
|
||||
value_length) {
|
||||
pr_err("Too small resident attribute value in MFT record %lld, type %#x\n",
|
||||
(long long)ctx->ntfs_ino->mft_no, a->type);
|
||||
break;
|
||||
}
|
||||
if (value_length == val_len &&
|
||||
!memcmp((u8 *)a + value_offset, val, val_len)) {
|
||||
attr_found:
|
||||
@@ -1855,7 +2000,7 @@ int ntfs_attr_make_non_resident(struct ntfs_inode *ni, const u32 data_size)
|
||||
if (IS_ERR(rl)) {
|
||||
err = PTR_ERR(rl);
|
||||
ntfs_debug("Failed to allocate cluster%s, error code %i.",
|
||||
ntfs_bytes_to_cluster(vol, new_size) > 1 ? "s" : "",
|
||||
str_plural(ntfs_bytes_to_cluster(vol, new_size)),
|
||||
err);
|
||||
goto folio_err_out;
|
||||
}
|
||||
@@ -4536,10 +4681,12 @@ static int ntfs_resident_attr_resize(struct ntfs_inode *attr_ni, const s64 newsi
|
||||
while (!(err = ntfs_attr_lookup(AT_UNUSED, NULL, 0, 0, 0, NULL, 0, ctx))) {
|
||||
struct inode *tvi;
|
||||
struct attr_record *a;
|
||||
u32 value_len;
|
||||
|
||||
a = ctx->attr;
|
||||
if (a->non_resident || a->type == AT_ATTRIBUTE_LIST)
|
||||
continue;
|
||||
value_len = le32_to_cpu(a->data.resident.value_length);
|
||||
|
||||
if (ntfs_attr_can_be_non_resident(vol, a->type))
|
||||
continue;
|
||||
@@ -4551,6 +4698,8 @@ static int ntfs_resident_attr_resize(struct ntfs_inode *attr_ni, const s64 newsi
|
||||
if (le32_to_cpu(a->length) <= (sizeof(struct attr_record) - sizeof(s64)) +
|
||||
((a->name_length * sizeof(__le16) + 7) & ~7) + 8)
|
||||
continue;
|
||||
if (a->type == AT_DATA && !value_len)
|
||||
continue;
|
||||
|
||||
if (a->type == AT_DATA)
|
||||
tvi = ntfs_iget(sb, base_ni->mft_no);
|
||||
@@ -4563,8 +4712,7 @@ static int ntfs_resident_attr_resize(struct ntfs_inode *attr_ni, const s64 newsi
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ntfs_attr_make_non_resident(NTFS_I(tvi),
|
||||
le32_to_cpu(ctx->attr->data.resident.value_length))) {
|
||||
if (ntfs_attr_make_non_resident(NTFS_I(tvi), value_len)) {
|
||||
iput(tvi);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -71,6 +71,10 @@ 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);
|
||||
bool ntfs_attr_list_entry_is_valid(const struct attr_list_entry *ale,
|
||||
const u8 *al_end);
|
||||
bool ntfs_attr_list_is_valid(const u8 *al_start, s64 size);
|
||||
|
||||
int load_attribute_list(struct ntfs_inode *base_ni,
|
||||
u8 *al_start, const s64 size);
|
||||
|
||||
|
||||
@@ -118,6 +118,7 @@ int ntfs_attrlist_entry_add(struct ntfs_inode *ni, struct attr_record *attr)
|
||||
int entry_len, entry_offset, err;
|
||||
struct mft_record *ni_mrec;
|
||||
u8 *old_al;
|
||||
__le64 lowest_vcn;
|
||||
|
||||
if (!ni || !attr) {
|
||||
ntfs_debug("Invalid arguments.\n");
|
||||
@@ -158,17 +159,21 @@ int ntfs_attrlist_entry_add(struct ntfs_inode *ni, struct attr_record *attr)
|
||||
ntfs_error(ni->vol->sb, "Failed to get search context");
|
||||
goto err_out;
|
||||
}
|
||||
if (attr->non_resident)
|
||||
lowest_vcn = attr->data.non_resident.lowest_vcn;
|
||||
else
|
||||
lowest_vcn = 0;
|
||||
|
||||
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 +
|
||||
le64_to_cpu(lowest_vcn),
|
||||
(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) {
|
||||
if (ctx->al_entry->lowest_vcn == lowest_vcn) {
|
||||
err = -EEXIST;
|
||||
ntfs_debug("Such attribute already present in the attribute list.\n");
|
||||
ntfs_attr_put_search_ctx(ctx);
|
||||
|
||||
@@ -135,10 +135,6 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname,
|
||||
/* Key length should not be zero if it is not last entry. */
|
||||
if (!ie->key_length)
|
||||
goto dir_err_out;
|
||||
/* Check the consistency of an index entry */
|
||||
if (ntfs_index_entry_inconsistent(NULL, vol, ie, COLLATION_FILE_NAME,
|
||||
dir_ni->mft_no))
|
||||
goto dir_err_out;
|
||||
/*
|
||||
* We perform a case sensitive comparison and if that matches
|
||||
* we are done and return the mft reference of the inode (i.e.
|
||||
@@ -342,43 +338,20 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname,
|
||||
dir_ni->mft_no);
|
||||
goto unm_err_out;
|
||||
}
|
||||
/* Catch multi sector transfer fixup errors. */
|
||||
if (unlikely(!ntfs_is_indx_record(ia->magic))) {
|
||||
ntfs_error(sb,
|
||||
"Directory index record with vcn 0x%llx is corrupt. Corrupt inode 0x%llx. Run chkdsk.",
|
||||
vcn, dir_ni->mft_no);
|
||||
goto unm_err_out;
|
||||
}
|
||||
if (le64_to_cpu(ia->index_block_vcn) != vcn) {
|
||||
ntfs_error(sb,
|
||||
"Actual VCN (0x%llx) of index buffer is different from expected VCN (0x%llx). Directory inode 0x%llx is corrupt or driver bug.",
|
||||
le64_to_cpu(ia->index_block_vcn),
|
||||
vcn, dir_ni->mft_no);
|
||||
goto unm_err_out;
|
||||
}
|
||||
if (le32_to_cpu(ia->index.allocated_size) + 0x18 !=
|
||||
dir_ni->itype.index.block_size) {
|
||||
ntfs_error(sb,
|
||||
"Index buffer (VCN 0x%llx) of directory inode 0x%llx has a size (%u) differing from the directory specified size (%u). Directory inode is corrupt or driver bug.",
|
||||
vcn, dir_ni->mft_no,
|
||||
le32_to_cpu(ia->index.allocated_size) + 0x18,
|
||||
dir_ni->itype.index.block_size);
|
||||
goto unm_err_out;
|
||||
}
|
||||
index_end = (u8 *)ia + dir_ni->itype.index.block_size;
|
||||
if (index_end > kaddr + PAGE_SIZE) {
|
||||
ntfs_error(sb,
|
||||
"Index buffer (VCN 0x%llx) of directory inode 0x%llx crosses page boundary. Impossible! Cannot access! This is probably a bug in the driver.",
|
||||
vcn, dir_ni->mft_no);
|
||||
"Index buffer (VCN 0x%llx) of directory inode 0x%llx crosses page boundary. Impossible! Cannot access! This is probably a bug in the driver.",
|
||||
vcn, dir_ni->mft_no);
|
||||
goto unm_err_out;
|
||||
}
|
||||
err = ntfs_index_block_inconsistent(vol, ia,
|
||||
dir_ni->itype.index.block_size,
|
||||
vcn, COLLATION_FILE_NAME,
|
||||
dir_ni->mft_no);
|
||||
if (err)
|
||||
goto unm_err_out;
|
||||
index_end = (u8 *)&ia->index + le32_to_cpu(ia->index.index_length);
|
||||
if (index_end > (u8 *)ia + dir_ni->itype.index.block_size) {
|
||||
ntfs_error(sb,
|
||||
"Size of index buffer (VCN 0x%llx) of directory inode 0x%llx exceeds maximum size.",
|
||||
vcn, dir_ni->mft_no);
|
||||
goto unm_err_out;
|
||||
}
|
||||
/* The first index entry. */
|
||||
ie = (struct index_entry *)((u8 *)&ia->index +
|
||||
le32_to_cpu(ia->index.entries_offset));
|
||||
@@ -388,15 +361,6 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname,
|
||||
* reach the last entry.
|
||||
*/
|
||||
for (;; ie = (struct index_entry *)((u8 *)ie + le16_to_cpu(ie->length))) {
|
||||
/* Bounds checks. */
|
||||
if ((u8 *)ie < (u8 *)ia ||
|
||||
(u8 *)ie + sizeof(struct index_entry_header) > index_end ||
|
||||
(u8 *)ie + sizeof(struct index_entry_header) + le16_to_cpu(ie->key_length) >
|
||||
index_end || (u8 *)ie + le16_to_cpu(ie->length) > index_end) {
|
||||
ntfs_error(sb, "Index entry out of bounds in directory inode 0x%llx.",
|
||||
dir_ni->mft_no);
|
||||
goto unm_err_out;
|
||||
}
|
||||
/*
|
||||
* The last entry cannot contain a name. It can however contain
|
||||
* a pointer to a child node in the B+tree so we just break out.
|
||||
@@ -406,10 +370,6 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname,
|
||||
/* Key length should not be zero if it is not last entry. */
|
||||
if (!ie->key_length)
|
||||
goto unm_err_out;
|
||||
/* Check the consistency of an index entry */
|
||||
if (ntfs_index_entry_inconsistent(NULL, vol, ie, COLLATION_FILE_NAME,
|
||||
dir_ni->mft_no))
|
||||
goto unm_err_out;
|
||||
/*
|
||||
* We perform a case sensitive comparison and if that matches
|
||||
* we are done and return the mft reference of the inode (i.e.
|
||||
@@ -892,6 +852,7 @@ static int ntfs_readdir(struct file *file, struct dir_context *actor)
|
||||
ictx->vcn_size_bits = vol->cluster_size_bits;
|
||||
else
|
||||
ictx->vcn_size_bits = NTFS_BLOCK_SIZE_BITS;
|
||||
ictx->cr = ir->collation_rule;
|
||||
|
||||
/* The first index entry. */
|
||||
next = (struct index_entry *)((u8 *)&ir->index +
|
||||
@@ -929,13 +890,6 @@ static int ntfs_readdir(struct file *file, struct dir_context *actor)
|
||||
if (!next)
|
||||
break;
|
||||
nextdir:
|
||||
/* Check the consistency of an index entry */
|
||||
if (ntfs_index_entry_inconsistent(ictx, vol, next, COLLATION_FILE_NAME,
|
||||
ndir->mft_no)) {
|
||||
err = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (ie_pos < actor->pos) {
|
||||
ie_pos += le16_to_cpu(next->length);
|
||||
continue;
|
||||
|
||||
16
fs/ntfs/ea.c
16
fs/ntfs/ea.c
@@ -53,11 +53,11 @@ static int ntfs_ea_lookup(char *ea_buf, s64 ea_buf_size, const char *name,
|
||||
loff_t offset, p_ea_size;
|
||||
unsigned int next;
|
||||
|
||||
if (ea_buf_size < sizeof(struct ea_attr))
|
||||
goto out;
|
||||
|
||||
offset = 0;
|
||||
do {
|
||||
if (ea_buf_size - offset < sizeof(struct ea_attr))
|
||||
break;
|
||||
|
||||
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);
|
||||
@@ -479,13 +479,13 @@ ssize_t ntfs_listxattr(struct dentry *dentry, char *buffer, size_t size)
|
||||
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 {
|
||||
if (ea_info_qsize - offset < sizeof(struct ea_attr)) {
|
||||
err = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
p_ea = (const struct ea_attr *)&ea_buf[offset];
|
||||
next = le32_to_cpu(p_ea->next_entry_offset);
|
||||
ea_size = next ? next : (ea_info_qsize - offset);
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "ea.h"
|
||||
#include "iomap.h"
|
||||
#include "bitmap.h"
|
||||
#include "volume.h"
|
||||
|
||||
#include <linux/filelock.h>
|
||||
|
||||
@@ -675,10 +676,29 @@ static int ntfs_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
|
||||
static const char *ntfs_get_link(struct dentry *dentry, struct inode *inode,
|
||||
struct delayed_call *done)
|
||||
{
|
||||
if (!NTFS_I(inode)->target)
|
||||
struct ntfs_inode *ni = NTFS_I(inode);
|
||||
char *target;
|
||||
int err;
|
||||
|
||||
if (!dentry)
|
||||
return ERR_PTR(-ECHILD);
|
||||
|
||||
if (!ni->target)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
return NTFS_I(inode)->target;
|
||||
if (ni->reparse_tag == IO_REPARSE_TAG_MOUNT_POINT ||
|
||||
(ni->reparse_tag == IO_REPARSE_TAG_SYMLINK &&
|
||||
!(ni->reparse_flags & cpu_to_le32(SYMLINK_FLAG_RELATIVE)))) {
|
||||
if (NVolNativeSymlinkRel(ni->vol)) {
|
||||
err = ntfs_translate_symlink_path(dentry, ni->target, &target);
|
||||
if (err < 0)
|
||||
return ERR_PTR(err);
|
||||
set_delayed_call(done, kfree_link, target);
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
return ni->target;
|
||||
}
|
||||
|
||||
static ssize_t ntfs_file_splice_read(struct file *in, loff_t *ppos,
|
||||
@@ -707,12 +727,21 @@ static int ntfs_ioctl_get_volume_label(struct file *filp, unsigned long arg)
|
||||
{
|
||||
struct ntfs_volume *vol = NTFS_SB(file_inode(filp)->i_sb);
|
||||
char __user *buf = (char __user *)arg;
|
||||
char label[FSLABEL_MAX];
|
||||
ssize_t len;
|
||||
|
||||
mutex_lock(&vol->volume_label_lock);
|
||||
if (!vol->volume_label) {
|
||||
if (copy_to_user(buf, "", 1))
|
||||
return -EFAULT;
|
||||
} else if (copy_to_user(buf, vol->volume_label,
|
||||
MIN(FSLABEL_MAX, strlen(vol->volume_label) + 1)))
|
||||
label[0] = '\0';
|
||||
len = 0;
|
||||
} else {
|
||||
len = strscpy(label, vol->volume_label, sizeof(label));
|
||||
if (len == -E2BIG)
|
||||
len = FSLABEL_MAX - 1;
|
||||
}
|
||||
mutex_unlock(&vol->volume_label_lock);
|
||||
|
||||
if (copy_to_user(buf, label, len + 1))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
297
fs/ntfs/index.c
297
fs/ntfs/index.c
@@ -28,41 +28,10 @@
|
||||
* length must have been checked beforehand to not overflow from the
|
||||
* index record.
|
||||
*/
|
||||
int ntfs_index_entry_inconsistent(struct ntfs_index_context *icx,
|
||||
struct ntfs_volume *vol, const struct index_entry *ie,
|
||||
__le32 collation_rule, u64 inum)
|
||||
static int ntfs_index_entry_inconsistent(const struct ntfs_volume *vol,
|
||||
const struct index_entry *ie,
|
||||
__le32 collation_rule, u64 inum)
|
||||
{
|
||||
if (icx) {
|
||||
struct index_header *ih;
|
||||
u8 *ie_start, *ie_end;
|
||||
|
||||
if (icx->is_in_root)
|
||||
ih = &icx->ir->index;
|
||||
else
|
||||
ih = &icx->ib->index;
|
||||
|
||||
if ((le32_to_cpu(ih->index_length) > le32_to_cpu(ih->allocated_size)) ||
|
||||
(le32_to_cpu(ih->index_length) > icx->block_size)) {
|
||||
ntfs_error(vol->sb, "%s Index entry(0x%p)'s length is too big.",
|
||||
icx->is_in_root ? "Index root" : "Index block",
|
||||
(u8 *)icx->entry);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ie_start = (u8 *)ih + le32_to_cpu(ih->entries_offset);
|
||||
ie_end = (u8 *)ih + le32_to_cpu(ih->index_length);
|
||||
|
||||
if (ie_start > (u8 *)ie ||
|
||||
ie_end <= (u8 *)ie + le16_to_cpu(ie->length) ||
|
||||
le16_to_cpu(ie->length) > le32_to_cpu(ih->allocated_size) ||
|
||||
le16_to_cpu(ie->length) > icx->block_size) {
|
||||
ntfs_error(vol->sb, "Index entry(0x%p) is out of range from %s",
|
||||
(u8 *)icx->entry,
|
||||
icx->is_in_root ? "index root" : "index block");
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
if (ie->key_length &&
|
||||
((le16_to_cpu(ie->key_length) + offsetof(struct index_entry, key)) >
|
||||
le16_to_cpu(ie->length))) {
|
||||
@@ -303,6 +272,93 @@ static int ntfs_ie_end(struct index_entry *ie)
|
||||
return ie->flags & INDEX_ENTRY_END || !ie->length;
|
||||
}
|
||||
|
||||
static int ntfs_index_header_inconsistent(struct ntfs_volume *vol,
|
||||
const struct index_header *ih,
|
||||
u32 bytes_available, u64 inum)
|
||||
{
|
||||
u32 entries_offset, index_length, allocated_size;
|
||||
|
||||
if (bytes_available < sizeof(struct index_header)) {
|
||||
ntfs_error(vol->sb,
|
||||
"index block in inode %llu is smaller than an index header.",
|
||||
(unsigned long long)inum);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
entries_offset = le32_to_cpu(ih->entries_offset);
|
||||
index_length = le32_to_cpu(ih->index_length);
|
||||
allocated_size = le32_to_cpu(ih->allocated_size);
|
||||
|
||||
if (entries_offset < sizeof(struct index_header) ||
|
||||
entries_offset > bytes_available) {
|
||||
ntfs_error(vol->sb,
|
||||
"Invalid index entry offset in inode %llu.",
|
||||
(unsigned long long)inum);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (index_length <= entries_offset) {
|
||||
ntfs_error(vol->sb,
|
||||
"No space for index entries in inode %llu.",
|
||||
(unsigned long long)inum);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (allocated_size < index_length) {
|
||||
ntfs_error(vol->sb,
|
||||
"Index entries overflow in inode %llu.",
|
||||
(unsigned long long)inum);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (allocated_size > bytes_available || index_length > bytes_available) {
|
||||
ntfs_error(vol->sb,
|
||||
"Index entries in inode %llu exceed the available buffer.",
|
||||
(unsigned long long)inum);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ntfs_index_entries_inconsistent(const struct ntfs_volume *vol,
|
||||
const struct index_header *ih,
|
||||
__le32 collation_rule, u64 inum)
|
||||
{
|
||||
struct index_entry *ie;
|
||||
u8 *index_end = (u8 *)ih + le32_to_cpu(ih->index_length);
|
||||
|
||||
for (ie = ntfs_ie_get_first((struct index_header *)ih);
|
||||
; ie = ntfs_ie_get_next(ie)) {
|
||||
if ((u8 *)ie + sizeof(struct index_entry_header) > index_end ||
|
||||
(u8 *)ie + le16_to_cpu(ie->length) > index_end) {
|
||||
ntfs_error(vol->sb,
|
||||
"Index entry out of bounds in inode %llu.",
|
||||
(unsigned long long)inum);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (le16_to_cpu(ie->length) < sizeof(struct index_entry_header)) {
|
||||
ntfs_error(vol->sb,
|
||||
"Index entry too small in inode %llu.",
|
||||
inum);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (ntfs_ie_end(ie))
|
||||
break;
|
||||
|
||||
if (!ie->key_length)
|
||||
return -EIO;
|
||||
|
||||
if (ntfs_index_entry_inconsistent(vol, ie,
|
||||
collation_rule, inum))
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find the last entry in the index block
|
||||
*/
|
||||
@@ -437,7 +493,7 @@ static struct index_entry *ntfs_ie_dup_novcn(struct index_entry *ie)
|
||||
* The size of block is assumed to have been checked to be what is
|
||||
* defined in the index root.
|
||||
*
|
||||
* Returns 0 if no error was found -1 otherwise (with errno unchanged)
|
||||
* Returns 0 if no error was found, -EIO otherwise
|
||||
*
|
||||
* |<--->| offsetof(struct index_block, index)
|
||||
* | |<--->| sizeof(struct index_header)
|
||||
@@ -452,21 +508,21 @@ static struct index_entry *ntfs_ie_dup_novcn(struct index_entry *ie)
|
||||
*
|
||||
* size(struct index_header) <= ent_offset < ind_length <= alloc_size < bk_size
|
||||
*/
|
||||
static int ntfs_index_block_inconsistent(struct ntfs_index_context *icx,
|
||||
struct index_block *ib, s64 vcn)
|
||||
int ntfs_index_block_inconsistent(struct ntfs_volume *vol,
|
||||
const struct index_block *ib,
|
||||
u32 block_size, s64 vcn, __le32 cr,
|
||||
u64 inum)
|
||||
{
|
||||
u32 ib_size = (unsigned int)le32_to_cpu(ib->index.allocated_size) +
|
||||
offsetof(struct index_block, index);
|
||||
struct super_block *sb = icx->idx_ni->vol->sb;
|
||||
unsigned long long inum = icx->idx_ni->mft_no;
|
||||
struct super_block *sb = vol->sb;
|
||||
|
||||
ntfs_debug("Entering\n");
|
||||
|
||||
if (!ntfs_is_indx_record(ib->magic)) {
|
||||
|
||||
ntfs_error(sb, "Corrupt index block signature: vcn %lld inode %llu\n",
|
||||
vcn, (unsigned long long)icx->idx_ni->mft_no);
|
||||
return -1;
|
||||
vcn, (unsigned long long)inum);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (le64_to_cpu(ib->index_block_vcn) != vcn) {
|
||||
@@ -474,34 +530,44 @@ static int ntfs_index_block_inconsistent(struct ntfs_index_context *icx,
|
||||
"Corrupt index block: s64 (%lld) is different from expected s64 (%lld) in inode %llu\n",
|
||||
(long long)le64_to_cpu(ib->index_block_vcn),
|
||||
vcn, inum);
|
||||
return -1;
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (ib_size != icx->block_size) {
|
||||
if (ib_size != block_size) {
|
||||
ntfs_error(sb,
|
||||
"Corrupt index block : s64 (%lld) of inode %llu has a size (%u) differing from the index specified size (%u)\n",
|
||||
vcn, inum, ib_size, icx->block_size);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (le32_to_cpu(ib->index.entries_offset) < sizeof(struct index_header)) {
|
||||
ntfs_error(sb, "Invalid index entry offset in inode %lld\n", inum);
|
||||
return -1;
|
||||
}
|
||||
if (le32_to_cpu(ib->index.index_length) <=
|
||||
le32_to_cpu(ib->index.entries_offset)) {
|
||||
ntfs_error(sb, "No space for index entries in inode %lld\n", inum);
|
||||
return -1;
|
||||
}
|
||||
if (le32_to_cpu(ib->index.allocated_size) <
|
||||
le32_to_cpu(ib->index.index_length)) {
|
||||
ntfs_error(sb, "Index entries overflow in inode %lld\n", inum);
|
||||
return -1;
|
||||
"Corrupt index block : s64 (%lld) of inode %llu has a size (%u) differing from the index specified size (%u)\n",
|
||||
vcn, inum, ib_size, block_size);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (ntfs_index_header_inconsistent(vol, &ib->index,
|
||||
block_size -
|
||||
offsetof(struct index_block, index),
|
||||
inum))
|
||||
return -EIO;
|
||||
if (ntfs_index_entries_inconsistent(vol, &ib->index, cr, inum))
|
||||
return -EIO;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ntfs_index_root_inconsistent(struct ntfs_volume *vol,
|
||||
const struct attr_record *a,
|
||||
const struct index_root *ir, u64 inum)
|
||||
{
|
||||
u32 value_length = le32_to_cpu(a->data.resident.value_length);
|
||||
|
||||
if (value_length < offsetof(struct index_root, index)) {
|
||||
ntfs_error(vol->sb, "$INDEX_ROOT in inode %llu is too small.",
|
||||
(unsigned long long)inum);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return ntfs_index_header_inconsistent(vol, &ir->index,
|
||||
value_length -
|
||||
offsetof(struct index_root, index),
|
||||
inum);
|
||||
}
|
||||
|
||||
static struct index_root *ntfs_ir_lookup(struct ntfs_inode *ni, __le16 *name,
|
||||
u32 name_len, struct ntfs_attr_search_ctx **ctx)
|
||||
{
|
||||
@@ -665,13 +731,14 @@ static int ntfs_ib_read(struct ntfs_index_context *icx, s64 vcn, struct index_bl
|
||||
else
|
||||
ntfs_error(icx->idx_ni->vol->sb,
|
||||
"Failed to read full index block at %lld\n", pos);
|
||||
return -1;
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
post_read_mst_fixup((struct ntfs_record *)((u8 *)dst), icx->block_size);
|
||||
if (ntfs_index_block_inconsistent(icx, dst, vcn))
|
||||
return -1;
|
||||
|
||||
if (ntfs_index_block_inconsistent(icx->idx_ni->vol, dst,
|
||||
icx->block_size, vcn, icx->cr,
|
||||
icx->idx_ni->mft_no))
|
||||
return -EIO;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1173,6 +1240,8 @@ static int ntfs_ir_reparent(struct ntfs_index_context *icx)
|
||||
struct index_entry *ie;
|
||||
struct index_block *ib = NULL;
|
||||
s64 new_ib_vcn;
|
||||
u32 index_length;
|
||||
u32 old_value_length;
|
||||
int ix_root_size;
|
||||
int ret = 0;
|
||||
|
||||
@@ -1220,6 +1289,21 @@ static int ntfs_ir_reparent(struct ntfs_index_context *icx)
|
||||
goto clear_bmp;
|
||||
}
|
||||
|
||||
old_value_length = le32_to_cpu(ctx->attr->data.resident.value_length);
|
||||
index_length = le32_to_cpu(ir->index.entries_offset) +
|
||||
sizeof(struct index_entry_header) + sizeof(s64);
|
||||
ix_root_size = offsetof(struct index_root, index) + index_length;
|
||||
/* Grow the resident value before publishing the larger root header. */
|
||||
if (ix_root_size > old_value_length) {
|
||||
ret = ntfs_resident_attr_value_resize(ctx->mrec, ctx->attr, ix_root_size);
|
||||
if (ret)
|
||||
goto resize_failed;
|
||||
|
||||
icx->idx_ni->data_size = ix_root_size;
|
||||
icx->idx_ni->initialized_size = ix_root_size;
|
||||
icx->idx_ni->allocated_size = (ix_root_size + 7) & ~7;
|
||||
}
|
||||
|
||||
ntfs_ir_nill(ir);
|
||||
|
||||
ie = ntfs_ie_get_first(&ir->index);
|
||||
@@ -1228,48 +1312,49 @@ static int ntfs_ir_reparent(struct ntfs_index_context *icx)
|
||||
|
||||
ir->index.flags = LARGE_INDEX;
|
||||
NInoSetIndexAllocPresent(icx->idx_ni);
|
||||
ir->index.index_length = cpu_to_le32(le32_to_cpu(ir->index.entries_offset) +
|
||||
le16_to_cpu(ie->length));
|
||||
ir->index.index_length = cpu_to_le32(index_length);
|
||||
ir->index.allocated_size = ir->index.index_length;
|
||||
|
||||
ix_root_size = sizeof(struct index_root) - sizeof(struct index_header) +
|
||||
le32_to_cpu(ir->index.allocated_size);
|
||||
ret = ntfs_resident_attr_value_resize(ctx->mrec, ctx->attr, ix_root_size);
|
||||
if (ret) {
|
||||
/*
|
||||
* When there is no space to build a non-resident
|
||||
* index, we may have to move the root to an extent
|
||||
*/
|
||||
if ((ret == -ENOSPC) && (ctx->al_entry || !ntfs_inode_add_attrlist(icx->idx_ni))) {
|
||||
ntfs_attr_put_search_ctx(ctx);
|
||||
ctx = NULL;
|
||||
ir = ntfs_ir_lookup(icx->idx_ni, icx->name, icx->name_len, &ctx);
|
||||
if (ir && !ntfs_attr_record_move_away(ctx, ix_root_size -
|
||||
le32_to_cpu(ctx->attr->data.resident.value_length))) {
|
||||
if (ntfs_attrlist_update(ctx->base_ntfs_ino ?
|
||||
ctx->base_ntfs_ino : ctx->ntfs_ino))
|
||||
goto clear_bmp;
|
||||
ntfs_attr_put_search_ctx(ctx);
|
||||
ctx = NULL;
|
||||
goto retry;
|
||||
}
|
||||
}
|
||||
goto clear_bmp;
|
||||
} else {
|
||||
icx->idx_ni->data_size = icx->idx_ni->initialized_size = ix_root_size;
|
||||
icx->idx_ni->allocated_size = (ix_root_size + 7) & ~7;
|
||||
if (ix_root_size <= old_value_length) {
|
||||
ret = ntfs_resident_attr_value_resize(ctx->mrec, ctx->attr, ix_root_size);
|
||||
if (ret)
|
||||
goto resize_failed;
|
||||
|
||||
icx->idx_ni->data_size = ix_root_size;
|
||||
icx->idx_ni->initialized_size = ix_root_size;
|
||||
icx->idx_ni->allocated_size = (ix_root_size + 7) & ~7;
|
||||
}
|
||||
ntfs_ie_set_vcn(ie, new_ib_vcn);
|
||||
goto err_out;
|
||||
|
||||
resize_failed:
|
||||
/*
|
||||
* When there is no space to build a non-resident
|
||||
* index, we may have to move the root to an extent
|
||||
*/
|
||||
if ((ret == -ENOSPC) && (ctx->al_entry || !ntfs_inode_add_attrlist(icx->idx_ni))) {
|
||||
ntfs_attr_put_search_ctx(ctx);
|
||||
ctx = NULL;
|
||||
ir = ntfs_ir_lookup(icx->idx_ni, icx->name, icx->name_len, &ctx);
|
||||
if (ir && !ntfs_attr_record_move_away(ctx, ix_root_size -
|
||||
le32_to_cpu(ctx->attr->data.resident.value_length))) {
|
||||
if (ntfs_attrlist_update(ctx->base_ntfs_ino ?
|
||||
ctx->base_ntfs_ino : ctx->ntfs_ino))
|
||||
goto clear_bmp;
|
||||
ntfs_attr_put_search_ctx(ctx);
|
||||
ctx = NULL;
|
||||
goto retry;
|
||||
}
|
||||
}
|
||||
clear_bmp:
|
||||
ntfs_ibm_clear(icx, new_ib_vcn);
|
||||
goto err_out;
|
||||
err_out:
|
||||
kvfree(ib);
|
||||
if (ctx)
|
||||
ntfs_attr_put_search_ctx(ctx);
|
||||
out:
|
||||
return ret;
|
||||
clear_bmp:
|
||||
ntfs_ibm_clear(icx, new_ib_vcn);
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1280,9 +1365,16 @@ static int ntfs_ir_reparent(struct ntfs_index_context *icx)
|
||||
static int ntfs_ir_truncate(struct ntfs_index_context *icx, int data_size)
|
||||
{
|
||||
int ret;
|
||||
u32 old_allocated_size;
|
||||
bool shrink;
|
||||
|
||||
ntfs_debug("Entering\n");
|
||||
|
||||
old_allocated_size = le32_to_cpu(icx->ir->index.allocated_size);
|
||||
shrink = data_size < old_allocated_size;
|
||||
if (shrink)
|
||||
icx->ir->index.allocated_size = cpu_to_le32(data_size);
|
||||
|
||||
/*
|
||||
* INDEX_ROOT must be resident and its entries can be moved to
|
||||
* struct index_block, so ENOSPC isn't a real error.
|
||||
@@ -1294,9 +1386,14 @@ static int ntfs_ir_truncate(struct ntfs_index_context *icx, int data_size)
|
||||
if (!icx->ir)
|
||||
return -ENOENT;
|
||||
|
||||
icx->ir->index.allocated_size = cpu_to_le32(data_size);
|
||||
} else if (ret != -ENOSPC)
|
||||
ntfs_error(icx->idx_ni->vol->sb, "Failed to truncate INDEX_ROOT");
|
||||
if (!shrink)
|
||||
icx->ir->index.allocated_size = cpu_to_le32(data_size);
|
||||
} else {
|
||||
if (shrink)
|
||||
icx->ir->index.allocated_size = cpu_to_le32(old_allocated_size);
|
||||
if (ret != -ENOSPC)
|
||||
ntfs_error(icx->idx_ni->vol->sb, "Failed to truncate INDEX_ROOT");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -89,8 +89,16 @@ struct ntfs_index_context {
|
||||
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);
|
||||
int ntfs_index_root_inconsistent(struct ntfs_volume *vol,
|
||||
const struct attr_record *a,
|
||||
const struct index_root *ir, u64 inum);
|
||||
int ntfs_index_block_inconsistent(struct ntfs_volume *vol,
|
||||
const struct index_block *ib,
|
||||
u32 block_size, s64 vcn,
|
||||
__le32 cr, u64 inum);
|
||||
int ntfs_index_entries_inconsistent(const struct ntfs_volume *vol,
|
||||
const struct index_header *ih,
|
||||
__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);
|
||||
|
||||
@@ -488,6 +488,8 @@ void __ntfs_init_inode(struct super_block *sb, struct ntfs_inode *ni)
|
||||
ni->flags = 0;
|
||||
ni->mft_lcn[0] = LCN_RL_NOT_MAPPED;
|
||||
ni->mft_lcn_count = 0;
|
||||
ni->reparse_tag = 0;
|
||||
ni->reparse_flags = 0;
|
||||
ni->target = NULL;
|
||||
ni->i_dealloc_clusters = 0;
|
||||
}
|
||||
@@ -848,6 +850,12 @@ static int ntfs_read_locked_inode(struct inode *vi)
|
||||
a->data.resident.value_offset),
|
||||
le32_to_cpu(
|
||||
a->data.resident.value_length));
|
||||
/* A resident list is not validated on load; check it now. */
|
||||
if (!ntfs_attr_list_is_valid(ni->attr_list,
|
||||
ni->attr_list_size)) {
|
||||
ntfs_error(vi->i_sb, "Corrupt attribute list.");
|
||||
goto unm_err_out;
|
||||
}
|
||||
}
|
||||
}
|
||||
skip_attr_list_load:
|
||||
@@ -857,8 +865,26 @@ static int ntfs_read_locked_inode(struct inode *vi)
|
||||
ntfs_ea_get_wsl_inode(vi, &dev, flags);
|
||||
}
|
||||
|
||||
if (m->flags & MFT_RECORD_IS_DIRECTORY) {
|
||||
if (ni->flags & FILE_ATTR_REPARSE_POINT) {
|
||||
unsigned int mode;
|
||||
|
||||
mode = ntfs_make_symlink(ni);
|
||||
if (mode)
|
||||
vi->i_mode |= mode;
|
||||
else {
|
||||
vi->i_mode &= ~S_IFLNK;
|
||||
if (m->flags & MFT_RECORD_IS_DIRECTORY)
|
||||
vi->i_mode |= S_IFDIR;
|
||||
else
|
||||
vi->i_mode |= S_IFREG;
|
||||
}
|
||||
} else if (m->flags & MFT_RECORD_IS_DIRECTORY) {
|
||||
vi->i_mode |= S_IFDIR;
|
||||
} else {
|
||||
vi->i_mode |= S_IFREG;
|
||||
}
|
||||
|
||||
if (S_ISDIR(vi->i_mode)) {
|
||||
/*
|
||||
* Apply the directory permissions mask set in the mount
|
||||
* options.
|
||||
@@ -868,18 +894,6 @@ static int ntfs_read_locked_inode(struct inode *vi)
|
||||
if (vi->i_nlink > 1)
|
||||
set_nlink(vi, 1);
|
||||
} else {
|
||||
if (ni->flags & FILE_ATTR_REPARSE_POINT) {
|
||||
unsigned int mode;
|
||||
|
||||
mode = ntfs_make_symlink(ni);
|
||||
if (mode)
|
||||
vi->i_mode |= mode;
|
||||
else {
|
||||
vi->i_mode &= ~S_IFLNK;
|
||||
vi->i_mode |= S_IFREG;
|
||||
}
|
||||
} else
|
||||
vi->i_mode |= S_IFREG;
|
||||
/* Apply the file permissions mask set in the mount options. */
|
||||
vi->i_mode &= ~vol->fmask;
|
||||
}
|
||||
@@ -888,9 +902,8 @@ static int ntfs_read_locked_inode(struct inode *vi)
|
||||
* If an attribute list is present we now have the attribute list value
|
||||
* in ntfs_ino->attr_list and it is ntfs_ino->attr_list_size bytes.
|
||||
*/
|
||||
if (S_ISDIR(vi->i_mode)) {
|
||||
if (m->flags & MFT_RECORD_IS_DIRECTORY) {
|
||||
struct index_root *ir;
|
||||
u8 *ir_end, *index_end;
|
||||
|
||||
view_index_meta:
|
||||
/* It is a directory, find index root attribute. */
|
||||
@@ -940,10 +953,9 @@ static int ntfs_read_locked_inode(struct inode *vi)
|
||||
}
|
||||
ir = (struct index_root *)((u8 *)a +
|
||||
le16_to_cpu(a->data.resident.value_offset));
|
||||
ir_end = (u8 *)ir + le32_to_cpu(a->data.resident.value_length);
|
||||
index_end = (u8 *)&ir->index +
|
||||
le32_to_cpu(ir->index.index_length);
|
||||
if (index_end > ir_end) {
|
||||
if (ntfs_index_root_inconsistent(ni->vol, a, ir, ni->mft_no) ||
|
||||
ntfs_index_entries_inconsistent(ni->vol, &ir->index,
|
||||
ir->collation_rule, ni->mft_no)) {
|
||||
ntfs_error(vi->i_sb, "Directory index is corrupt.");
|
||||
goto unm_err_out;
|
||||
}
|
||||
@@ -1014,7 +1026,7 @@ static int ntfs_read_locked_inode(struct inode *vi)
|
||||
m = NULL;
|
||||
ctx = NULL;
|
||||
/* Setup the operations for this inode. */
|
||||
ntfs_set_vfs_operations(vi, S_IFDIR, 0);
|
||||
ntfs_set_vfs_operations(vi, vi->i_mode, 0);
|
||||
if (ir->index.flags & LARGE_INDEX)
|
||||
NInoSetIndexAllocPresent(ni);
|
||||
} else {
|
||||
@@ -1195,6 +1207,9 @@ static int ntfs_read_locked_inode(struct inode *vi)
|
||||
else
|
||||
vi->i_blocks = ni->allocated_size >> 9;
|
||||
|
||||
if (S_ISLNK(vi->i_mode) && ni->target)
|
||||
vi->i_size = strlen(ni->target);
|
||||
|
||||
ntfs_debug("Done.");
|
||||
return 0;
|
||||
unm_err_out:
|
||||
@@ -1483,7 +1498,6 @@ static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi)
|
||||
struct attr_record *a;
|
||||
struct ntfs_attr_search_ctx *ctx;
|
||||
struct index_root *ir;
|
||||
u8 *ir_end, *index_end;
|
||||
int err = 0;
|
||||
|
||||
ntfs_debug("Entering for i_ino 0x%llx.", ni->mft_no);
|
||||
@@ -1534,9 +1548,9 @@ static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi)
|
||||
}
|
||||
|
||||
ir = (struct index_root *)((u8 *)a + le16_to_cpu(a->data.resident.value_offset));
|
||||
ir_end = (u8 *)ir + le32_to_cpu(a->data.resident.value_length);
|
||||
index_end = (u8 *)&ir->index + le32_to_cpu(ir->index.index_length);
|
||||
if (index_end > ir_end) {
|
||||
if (ntfs_index_root_inconsistent(vol, a, ir, ni->mft_no) ||
|
||||
ntfs_index_entries_inconsistent(vol, &ir->index,
|
||||
ir->collation_rule, ni->mft_no)) {
|
||||
ntfs_error(vi->i_sb, "Index is corrupt.");
|
||||
goto unm_err_out;
|
||||
}
|
||||
@@ -1994,10 +2008,7 @@ int ntfs_read_inode_mount(struct inode *vi)
|
||||
/* Catch the end of the attribute list. */
|
||||
if ((u8 *)al_entry == al_end)
|
||||
goto em_put_err_out;
|
||||
if (!al_entry->length)
|
||||
goto em_put_err_out;
|
||||
if ((u8 *)al_entry + 6 > al_end ||
|
||||
(u8 *)al_entry + le16_to_cpu(al_entry->length) > al_end)
|
||||
if (!ntfs_attr_list_entry_is_valid(al_entry, al_end))
|
||||
goto em_put_err_out;
|
||||
next_al_entry = (struct attr_list_entry *)((u8 *)al_entry +
|
||||
le16_to_cpu(al_entry->length));
|
||||
@@ -2367,6 +2378,14 @@ int ntfs_show_options(struct seq_file *sf, struct dentry *root)
|
||||
seq_puts(sf, ",discard");
|
||||
if (NVolDisableSparse(vol))
|
||||
seq_puts(sf, ",disable_sparse");
|
||||
if (NVolNativeSymlinkRel(vol))
|
||||
seq_puts(sf, ",native_symlink=rel");
|
||||
else
|
||||
seq_puts(sf, ",native_symlink=raw");
|
||||
if (NVolSymlinkNative(vol))
|
||||
seq_puts(sf, ",symlink=native");
|
||||
else
|
||||
seq_puts(sf, ",symlink=wsl");
|
||||
if (vol->sb->s_flags & SB_POSIXACL)
|
||||
seq_puts(sf, ",acl");
|
||||
return 0;
|
||||
|
||||
@@ -142,6 +142,8 @@ struct ntfs_inode {
|
||||
struct ntfs_inode *base_ntfs_ino;
|
||||
} ext;
|
||||
unsigned int i_dealloc_clusters;
|
||||
__le32 reparse_tag;
|
||||
__le32 reparse_flags;
|
||||
char *target;
|
||||
};
|
||||
|
||||
|
||||
@@ -89,7 +89,6 @@ static int ntfs_read_iomap_begin_resident(struct inode *inode, loff_t offset, lo
|
||||
u32 attr_len;
|
||||
int err = 0;
|
||||
char *kattr;
|
||||
struct page *ipage;
|
||||
|
||||
if (NInoAttr(ni))
|
||||
base_ni = ni->ext.base_ntfs_ino;
|
||||
@@ -130,18 +129,10 @@ static int ntfs_read_iomap_begin_resident(struct inode *inode, loff_t offset, lo
|
||||
|
||||
kattr = (u8 *)ctx->attr + le16_to_cpu(ctx->attr->data.resident.value_offset);
|
||||
|
||||
ipage = alloc_page(GFP_NOFS | __GFP_ZERO);
|
||||
if (!ipage) {
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
memcpy(page_address(ipage), kattr, attr_len);
|
||||
iomap->type = IOMAP_INLINE;
|
||||
iomap->inline_data = page_address(ipage);
|
||||
iomap->inline_data = kattr;
|
||||
iomap->offset = 0;
|
||||
iomap->length = attr_len;
|
||||
iomap->private = ipage;
|
||||
|
||||
out:
|
||||
if (ctx)
|
||||
@@ -286,21 +277,8 @@ static int ntfs_read_iomap_begin(struct inode *inode, loff_t offset, loff_t leng
|
||||
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) {
|
||||
struct page *ipage = iomap->private;
|
||||
|
||||
put_page(ipage);
|
||||
}
|
||||
|
||||
return written;
|
||||
}
|
||||
|
||||
const struct iomap_ops ntfs_read_iomap_ops = {
|
||||
.iomap_begin = ntfs_read_iomap_begin,
|
||||
.iomap_end = ntfs_read_iomap_end,
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -358,7 +336,6 @@ static const struct iomap_ops ntfs_zero_read_iomap_ops = {
|
||||
|
||||
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)
|
||||
@@ -659,7 +636,6 @@ static int ntfs_write_iomap_begin_resident(struct inode *inode, loff_t offset,
|
||||
u32 attr_len;
|
||||
int err = 0;
|
||||
char *kattr;
|
||||
struct page *ipage;
|
||||
|
||||
ctx = ntfs_attr_get_search_ctx(ni, NULL);
|
||||
if (!ctx) {
|
||||
@@ -680,24 +656,18 @@ static int ntfs_write_iomap_begin_resident(struct inode *inode, loff_t offset,
|
||||
attr_len = le32_to_cpu(a->data.resident.value_length);
|
||||
kattr = (u8 *)a + le16_to_cpu(a->data.resident.value_offset);
|
||||
|
||||
ipage = alloc_page(GFP_NOFS | __GFP_ZERO);
|
||||
if (!ipage) {
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
memcpy(page_address(ipage), kattr, attr_len);
|
||||
iomap->type = IOMAP_INLINE;
|
||||
iomap->inline_data = page_address(ipage);
|
||||
iomap->inline_data = kattr;
|
||||
iomap->offset = 0;
|
||||
/* iomap requires there is only one INLINE_DATA extent */
|
||||
iomap->length = attr_len;
|
||||
iomap->private = ipage;
|
||||
|
||||
out:
|
||||
if (ctx)
|
||||
ntfs_attr_put_search_ctx(ctx);
|
||||
mutex_unlock(&ni->mrec_lock);
|
||||
|
||||
if (err)
|
||||
mutex_unlock(&ni->mrec_lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -778,43 +748,10 @@ static int ntfs_write_iomap_end_resident(struct inode *inode, loff_t pos,
|
||||
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;
|
||||
struct page *ipage = iomap->private;
|
||||
|
||||
mutex_lock(&ni->mrec_lock);
|
||||
ctx = ntfs_attr_get_search_ctx(ni, NULL);
|
||||
if (!ctx) {
|
||||
written = -ENOMEM;
|
||||
goto err_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;
|
||||
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:
|
||||
if (ctx)
|
||||
ntfs_attr_put_search_ctx(ctx);
|
||||
put_page(ipage);
|
||||
mark_mft_record_dirty(ni);
|
||||
mutex_unlock(&ni->mrec_lock);
|
||||
return written;
|
||||
|
||||
}
|
||||
|
||||
static int ntfs_write_iomap_end(struct inode *inode, loff_t pos, loff_t length,
|
||||
|
||||
@@ -2267,6 +2267,8 @@ enum {
|
||||
IO_REPARSE_PLUGIN_SELECT = cpu_to_le32(0xffff0fff),
|
||||
};
|
||||
|
||||
#define SYMLINK_FLAG_RELATIVE 1
|
||||
|
||||
/*
|
||||
* struct reparse_point - $REPARSE_POINT attribute content (0xc0)\
|
||||
*
|
||||
@@ -2287,6 +2289,23 @@ struct reparse_point {
|
||||
u8 reparse_data[];
|
||||
} __packed;
|
||||
|
||||
struct mount_point_reparse_data {
|
||||
__le16 substitute_name_offset;
|
||||
__le16 substitute_name_length;
|
||||
__le16 print_name_offset;
|
||||
__le16 print_name_length;
|
||||
__le16 path_buffer[];
|
||||
} __packed;
|
||||
|
||||
struct symlink_reparse_data {
|
||||
__le16 substitute_name_offset;
|
||||
__le16 substitute_name_length;
|
||||
__le16 print_name_offset;
|
||||
__le16 print_name_length;
|
||||
__le32 flags;
|
||||
__le16 path_buffer[];
|
||||
} __packed;
|
||||
|
||||
/*
|
||||
* struct ea_information - $EA_INFORMATION attribute content (0xd0)
|
||||
*
|
||||
|
||||
@@ -132,7 +132,7 @@ static bool ntfs_check_restart_area(struct inode *vi, struct restart_page_header
|
||||
{
|
||||
u64 file_size;
|
||||
struct restart_area *ra;
|
||||
u16 ra_ofs, ra_len, ca_ofs;
|
||||
u32 ra_ofs, ra_len, ca_ofs;
|
||||
u8 fs_bits;
|
||||
|
||||
ntfs_debug("Entering.");
|
||||
@@ -622,8 +622,7 @@ bool ntfs_check_logfile(struct inode *log_vi, struct restart_page_header **rp)
|
||||
ntfs_debug("Done.");
|
||||
return true;
|
||||
err_out:
|
||||
if (rstr1_ph)
|
||||
kvfree(rstr1_ph);
|
||||
kvfree(rstr1_ph);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
129
fs/ntfs/mft.c
129
fs/ntfs/mft.c
@@ -743,23 +743,6 @@ static int ntfs_test_inode_wb(struct inode *vi, u64 ino, void *data)
|
||||
*
|
||||
* If the mft record is not a FILE record or it is a base mft record, we can
|
||||
* safely write it and return 'true'.
|
||||
*
|
||||
* We now know the mft record is an extent mft record. We check if the inode
|
||||
* corresponding to its base mft record is in icache. If it is not, we cannot
|
||||
* safely determine the state of the extent inode, so we return 'false'.
|
||||
*
|
||||
* We now have the base inode for the extent mft record. We check if it has an
|
||||
* ntfs inode for the extent mft record attached. If not, it is safe to write
|
||||
* the extent mft record and we return 'true'.
|
||||
*
|
||||
* If the extent inode is attached, we check if it is dirty. If so, we return
|
||||
* 'false' (letting the standard write_inode path handle it).
|
||||
*
|
||||
* If it is not dirty, we attempt to lock the extent mft record. If the lock
|
||||
* was already taken, it is not safe to write and we return 'false'.
|
||||
*
|
||||
* If we manage to obtain the lock we have exclusive access to the extent mft
|
||||
* record. We set @locked_ni to the now locked ntfs inode and return 'true'.
|
||||
*/
|
||||
static bool ntfs_may_write_mft_record(struct ntfs_volume *vol, const u64 mft_no,
|
||||
const struct mft_record *m, struct ntfs_inode **locked_ni,
|
||||
@@ -768,8 +751,7 @@ static bool ntfs_may_write_mft_record(struct ntfs_volume *vol, const u64 mft_no,
|
||||
struct super_block *sb = vol->sb;
|
||||
struct inode *mft_vi = vol->mft_ino;
|
||||
struct inode *vi;
|
||||
struct ntfs_inode *ni, *eni, **extent_nis;
|
||||
int i;
|
||||
struct ntfs_inode *ni;
|
||||
struct ntfs_attr na = {0};
|
||||
|
||||
ntfs_debug("Entering for inode 0x%llx.", mft_no);
|
||||
@@ -849,100 +831,10 @@ static bool ntfs_may_write_mft_record(struct ntfs_volume *vol, const u64 mft_no,
|
||||
mft_no);
|
||||
return true;
|
||||
}
|
||||
/*
|
||||
* This is an extent mft record. Check if the inode corresponding to
|
||||
* its base mft record is in icache and obtain a reference to it if it
|
||||
* is.
|
||||
*/
|
||||
na.mft_no = MREF_LE(m->base_mft_record);
|
||||
na.state = 0;
|
||||
ntfs_debug("Mft record 0x%llx is an extent record. Looking for base inode 0x%llx in icache.",
|
||||
mft_no, na.mft_no);
|
||||
if (!na.mft_no) {
|
||||
/* Balance the below iput(). */
|
||||
vi = igrab(mft_vi);
|
||||
WARN_ON(vi != mft_vi);
|
||||
} else {
|
||||
vi = find_inode_nowait(sb, na.mft_no, ntfs_test_inode_wb, &na);
|
||||
if (na.state == NI_BeingDeleted || na.state == NI_BeingCreated)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!vi)
|
||||
return false;
|
||||
ntfs_debug("Base inode 0x%llx is in icache.", na.mft_no);
|
||||
/*
|
||||
* The base inode is in icache. Check if it has the extent inode
|
||||
* corresponding to this extent mft record attached.
|
||||
*/
|
||||
ni = NTFS_I(vi);
|
||||
mutex_lock(&ni->extent_lock);
|
||||
if (ni->nr_extents <= 0) {
|
||||
/*
|
||||
* The base inode has no attached extent inodes, write this
|
||||
* extent mft record.
|
||||
*/
|
||||
mutex_unlock(&ni->extent_lock);
|
||||
*ref_vi = vi;
|
||||
ntfs_debug("Base inode 0x%llx has no attached extent inodes, write the extent record.",
|
||||
na.mft_no);
|
||||
return true;
|
||||
}
|
||||
/* Iterate over the attached extent inodes. */
|
||||
extent_nis = ni->ext.extent_ntfs_inos;
|
||||
for (eni = NULL, i = 0; i < ni->nr_extents; ++i) {
|
||||
if (mft_no == extent_nis[i]->mft_no) {
|
||||
/*
|
||||
* Found the extent inode corresponding to this extent
|
||||
* mft record.
|
||||
*/
|
||||
eni = extent_nis[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* If the extent inode was not attached to the base inode, write this
|
||||
* extent mft record.
|
||||
*/
|
||||
if (!eni) {
|
||||
mutex_unlock(&ni->extent_lock);
|
||||
*ref_vi = vi;
|
||||
ntfs_debug("Extent inode 0x%llx is not attached to its base inode 0x%llx, write the extent record.",
|
||||
mft_no, na.mft_no);
|
||||
return true;
|
||||
}
|
||||
ntfs_debug("Extent inode 0x%llx is attached to its base inode 0x%llx.",
|
||||
mft_no, na.mft_no);
|
||||
/* Take a reference to the extent ntfs inode. */
|
||||
atomic_inc(&eni->count);
|
||||
mutex_unlock(&ni->extent_lock);
|
||||
|
||||
/* if extent inode is dirty, write_inode will write it */
|
||||
if (NInoDirty(eni)) {
|
||||
atomic_dec(&eni->count);
|
||||
*ref_vi = vi;
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Found the extent inode coresponding to this extent mft record.
|
||||
* Try to take the mft record lock.
|
||||
*/
|
||||
if (unlikely(!mutex_trylock(&eni->mrec_lock))) {
|
||||
atomic_dec(&eni->count);
|
||||
*ref_vi = vi;
|
||||
ntfs_debug("Extent mft record 0x%llx is already locked, do not write it.",
|
||||
mft_no);
|
||||
return false;
|
||||
}
|
||||
ntfs_debug("Managed to lock extent mft record 0x%llx, write it.",
|
||||
mft_no);
|
||||
/*
|
||||
* The write has to occur while we hold the mft record lock so return
|
||||
* the locked extent ntfs inode.
|
||||
*/
|
||||
*locked_ni = eni;
|
||||
return true;
|
||||
ntfs_debug("Mft record 0x%llx is an extent record, skip it.",
|
||||
mft_no);
|
||||
return false;
|
||||
}
|
||||
|
||||
static const char *es = " Leaving inconsistent metadata. Unmount and run chkdsk.";
|
||||
@@ -2791,19 +2683,6 @@ static int ntfs_write_mft_block(struct folio *folio, struct writeback_control *w
|
||||
unsigned int mft_record_off = 0;
|
||||
s64 vcn_off = vcn;
|
||||
|
||||
/*
|
||||
* Skip $MFT extent mft records and let them being written
|
||||
* by writeback to avioid deadlocks. the $MFT runlist
|
||||
* lock must be taken before $MFT extent mrec_lock is taken.
|
||||
*/
|
||||
if (tni && tni->nr_extents < 0 &&
|
||||
tni->ext.base_ntfs_ino == NTFS_I(vol->mft_ino)) {
|
||||
mutex_unlock(&tni->mrec_lock);
|
||||
atomic_dec(&tni->count);
|
||||
iput(vol->mft_ino);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* The record should be written. If a locked ntfs
|
||||
* inode was returned, add it to the array of locked
|
||||
|
||||
@@ -393,7 +393,7 @@ static int ntfs_sd_add_everyone(struct ntfs_inode *ni)
|
||||
|
||||
static struct ntfs_inode *__ntfs_create(struct mnt_idmap *idmap, struct inode *dir,
|
||||
__le16 *name, u8 name_len, mode_t mode, dev_t dev,
|
||||
__le16 *target, int target_len)
|
||||
const char *target, int target_len)
|
||||
{
|
||||
struct ntfs_inode *dir_ni = NTFS_I(dir);
|
||||
struct ntfs_volume *vol = dir_ni->vol;
|
||||
@@ -607,7 +607,10 @@ static struct ntfs_inode *__ntfs_create(struct mnt_idmap *idmap, struct inode *d
|
||||
goto err_out;
|
||||
|
||||
if (S_ISLNK(mode)) {
|
||||
err = ntfs_reparse_set_wsl_symlink(ni, target, target_len);
|
||||
if (NVolSymlinkNative(vol))
|
||||
err = ntfs_reparse_set_native_symlink(ni, target, target_len);
|
||||
else
|
||||
err = ntfs_reparse_set_wsl_symlink(ni, target, target_len);
|
||||
if (!err)
|
||||
rollback_reparse = true;
|
||||
} else if (S_ISBLK(mode) || S_ISCHR(mode) || S_ISSOCK(mode) ||
|
||||
@@ -1408,9 +1411,7 @@ static int ntfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
|
||||
int err = 0;
|
||||
struct ntfs_inode *ni;
|
||||
__le16 *usrc;
|
||||
__le16 *utarget;
|
||||
int usrc_len;
|
||||
int utarget_len;
|
||||
int symlen = strlen(symname);
|
||||
|
||||
if (NVolShutdown(vol))
|
||||
@@ -1431,23 +1432,12 @@ static int ntfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
|
||||
goto out;
|
||||
}
|
||||
|
||||
utarget_len = ntfs_nlstoucs(vol, symname, symlen, &utarget,
|
||||
PATH_MAX);
|
||||
if (utarget_len < 0) {
|
||||
if (utarget_len != -ENAMETOOLONG)
|
||||
ntfs_error(sb, "Failed to convert target name to Unicode.");
|
||||
err = -ENOMEM;
|
||||
kmem_cache_free(ntfs_name_cache, usrc);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!(vol->vol_flags & VOLUME_IS_DIRTY))
|
||||
ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY);
|
||||
|
||||
ni = __ntfs_create(idmap, dir, usrc, usrc_len, S_IFLNK | 0777, 0,
|
||||
utarget, utarget_len);
|
||||
symname, symlen);
|
||||
kmem_cache_free(ntfs_name_cache, usrc);
|
||||
kvfree(utarget);
|
||||
if (IS_ERR(ni)) {
|
||||
err = PTR_ERR(ni);
|
||||
goto out;
|
||||
@@ -1531,8 +1521,7 @@ static int ntfs_link(struct dentry *old_dentry, struct inode *dir,
|
||||
if (uname_len < 0) {
|
||||
if (uname_len != -ENAMETOOLONG)
|
||||
ntfs_error(sb, "Failed to convert name to unicode.");
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
if (!(vol->vol_flags & VOLUME_IS_DIRTY))
|
||||
@@ -1562,7 +1551,7 @@ static int ntfs_link(struct dentry *old_dentry, struct inode *dir,
|
||||
mutex_unlock(&ni->mrec_lock);
|
||||
|
||||
out:
|
||||
kfree(uname);
|
||||
kmem_cache_free(ntfs_name_cache, uname);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
/* 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 */
|
||||
@@ -24,6 +24,47 @@ struct wsl_link_reparse_data {
|
||||
char link[];
|
||||
};
|
||||
|
||||
static bool reparse_name_is_valid(size_t size, size_t name_off, u16 len)
|
||||
{
|
||||
if ((name_off | len) & 1)
|
||||
return false;
|
||||
|
||||
return name_off + len <= size;
|
||||
}
|
||||
|
||||
/*
|
||||
* Windows-native reparse payloads store pathnames as UTF-16 strings with '\\'
|
||||
* separators. Convert the on-disk UTF-16 target into the mount's NLS and
|
||||
* normalize path separators.
|
||||
*/
|
||||
static int ntfs_reparse_target_to_nls(struct ntfs_volume *vol,
|
||||
const __le16 *uname, u16 ulen,
|
||||
char **target)
|
||||
{
|
||||
int err, i;
|
||||
|
||||
*target = NULL;
|
||||
ulen >>= 1;
|
||||
if (!ulen)
|
||||
return -EINVAL;
|
||||
|
||||
if (!uname[ulen - 1])
|
||||
ulen--;
|
||||
|
||||
err = ntfs_ucstonls(vol, uname, ulen, (unsigned char **)target, 0);
|
||||
if (err < 0) {
|
||||
ntfs_attr_name_free((unsigned char **)target);
|
||||
return err;
|
||||
}
|
||||
|
||||
for (i = 0; i < err; i++) {
|
||||
if ((*target)[i] == '\\')
|
||||
(*target)[i] = '/';
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Index entry in $Extend/$Reparse */
|
||||
struct reparse_index {
|
||||
struct index_entry_header header;
|
||||
@@ -38,8 +79,10 @@ __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)
|
||||
static bool valid_reparse_buffer(struct ntfs_inode *ni,
|
||||
const struct reparse_point *reparse_attr,
|
||||
size_t size,
|
||||
size_t payload_min_len)
|
||||
{
|
||||
size_t expected;
|
||||
|
||||
@@ -50,6 +93,11 @@ static bool ntfs_is_valid_reparse_buffer(struct ntfs_inode *ni,
|
||||
if (size < sizeof(struct reparse_point))
|
||||
return false;
|
||||
|
||||
/* The payload must contain the fixed fields for the current tag. */
|
||||
if (payload_min_len &&
|
||||
le16_to_cpu(reparse_attr->reparse_data_length) < payload_min_len)
|
||||
return false;
|
||||
|
||||
/* Reserved zero tag is invalid */
|
||||
if (reparse_attr->reparse_tag == IO_REPARSE_TAG_RESERVED_ZERO)
|
||||
return false;
|
||||
@@ -79,35 +127,97 @@ static bool ntfs_is_valid_reparse_buffer(struct ntfs_inode *ni,
|
||||
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)
|
||||
if (size < sizeof(*reparse_attr))
|
||||
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))
|
||||
case IO_REPARSE_TAG_MOUNT_POINT:
|
||||
{
|
||||
struct mount_point_reparse_data *data;
|
||||
size_t data_offs;
|
||||
|
||||
if (!valid_reparse_buffer(ni, reparse_attr, size, sizeof(*data)))
|
||||
return false;
|
||||
|
||||
data = (struct mount_point_reparse_data *)reparse_attr->reparse_data;
|
||||
data_offs = offsetof(struct reparse_point, reparse_data) +
|
||||
offsetof(struct mount_point_reparse_data, path_buffer);
|
||||
|
||||
if (!reparse_name_is_valid(size,
|
||||
data_offs +
|
||||
le16_to_cpu(data->substitute_name_offset),
|
||||
le16_to_cpu(data->substitute_name_length)) ||
|
||||
!reparse_name_is_valid(size,
|
||||
data_offs +
|
||||
le16_to_cpu(data->print_name_offset),
|
||||
le16_to_cpu(data->print_name_length)))
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
case IO_REPARSE_TAG_SYMLINK:
|
||||
{
|
||||
struct symlink_reparse_data *data;
|
||||
size_t data_offs;
|
||||
|
||||
if (!valid_reparse_buffer(ni, reparse_attr, size,
|
||||
sizeof(*data)))
|
||||
return false;
|
||||
|
||||
data = (struct symlink_reparse_data *)reparse_attr->reparse_data;
|
||||
data_offs = offsetof(struct reparse_point, reparse_data) +
|
||||
offsetof(struct symlink_reparse_data, path_buffer);
|
||||
|
||||
if (!reparse_name_is_valid(size,
|
||||
data_offs +
|
||||
le16_to_cpu(data->substitute_name_offset),
|
||||
le16_to_cpu(data->substitute_name_length)) ||
|
||||
!reparse_name_is_valid(size,
|
||||
data_offs +
|
||||
le16_to_cpu(data->print_name_offset),
|
||||
le16_to_cpu(data->print_name_length)))
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
case IO_REPARSE_TAG_LX_SYMLINK:
|
||||
{
|
||||
struct wsl_link_reparse_data *data;
|
||||
|
||||
if (!valid_reparse_buffer(ni, reparse_attr, size,
|
||||
sizeof(*data)))
|
||||
return false;
|
||||
|
||||
data = (struct wsl_link_reparse_data *)reparse_attr->reparse_data;
|
||||
|
||||
if (le16_to_cpu(reparse_attr->reparse_data_length) <= sizeof(data->type) ||
|
||||
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))
|
||||
if (!valid_reparse_buffer(ni, reparse_attr, size, 0))
|
||||
return false;
|
||||
if (le16_to_cpu(reparse_attr->reparse_data_length) ||
|
||||
!(ni->flags & FILE_ATTRIBUTE_RECALL_ON_OPEN))
|
||||
return false;
|
||||
break;
|
||||
default:
|
||||
if (!valid_reparse_buffer(ni, reparse_attr, size, 0))
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static unsigned int ntfs_reparse_tag_mode(struct reparse_point *reparse_attr)
|
||||
static unsigned int ntfs_reparse_tag_mode(__le32 reparse_tag)
|
||||
{
|
||||
unsigned int mode = 0;
|
||||
|
||||
switch (reparse_attr->reparse_tag) {
|
||||
switch (reparse_tag) {
|
||||
case IO_REPARSE_TAG_MOUNT_POINT:
|
||||
case IO_REPARSE_TAG_SYMLINK:
|
||||
case IO_REPARSE_TAG_LX_SYMLINK:
|
||||
mode = S_IFLNK;
|
||||
@@ -134,32 +244,75 @@ static unsigned int ntfs_reparse_tag_mode(struct reparse_point *reparse_attr)
|
||||
unsigned int ntfs_make_symlink(struct ntfs_inode *ni)
|
||||
{
|
||||
s64 attr_size = 0;
|
||||
int err;
|
||||
unsigned int lth;
|
||||
struct reparse_point *reparse_attr;
|
||||
struct wsl_link_reparse_data *wsl_link_data;
|
||||
unsigned int mode = 0;
|
||||
|
||||
kvfree(ni->target);
|
||||
ni->target = NULL;
|
||||
ni->reparse_tag = 0;
|
||||
ni->reparse_flags = 0;
|
||||
|
||||
reparse_attr = ntfs_attr_readall(ni, AT_REPARSE_POINT, NULL, 0,
|
||||
&attr_size);
|
||||
if (reparse_attr && attr_size &&
|
||||
if (reparse_attr &&
|
||||
valid_reparse_data(ni, reparse_attr, attr_size)) {
|
||||
err = -EINVAL;
|
||||
|
||||
switch (reparse_attr->reparse_tag) {
|
||||
case IO_REPARSE_TAG_MOUNT_POINT:
|
||||
{
|
||||
struct mount_point_reparse_data *data =
|
||||
(struct mount_point_reparse_data *)reparse_attr->reparse_data;
|
||||
const __le16 *name = (const __le16 *)((u8 *)data->path_buffer +
|
||||
le16_to_cpu(data->substitute_name_offset));
|
||||
|
||||
err = ntfs_reparse_target_to_nls(ni->vol,
|
||||
name,
|
||||
le16_to_cpu(data->substitute_name_length),
|
||||
&ni->target);
|
||||
break;
|
||||
}
|
||||
case IO_REPARSE_TAG_SYMLINK:
|
||||
{
|
||||
struct symlink_reparse_data *data =
|
||||
(struct symlink_reparse_data *)reparse_attr->reparse_data;
|
||||
const __le16 *name = (const __le16 *)((u8 *)data->path_buffer +
|
||||
le16_to_cpu(data->substitute_name_offset));
|
||||
|
||||
err = ntfs_reparse_target_to_nls(ni->vol,
|
||||
name,
|
||||
le16_to_cpu(data->substitute_name_length),
|
||||
&ni->target);
|
||||
if (!err)
|
||||
ni->reparse_flags = data->flags;
|
||||
break;
|
||||
}
|
||||
case IO_REPARSE_TAG_LX_SYMLINK:
|
||||
wsl_link_data =
|
||||
{
|
||||
struct wsl_link_reparse_data *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);
|
||||
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);
|
||||
err = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
mode = ntfs_reparse_tag_mode(reparse_attr);
|
||||
err = 0;
|
||||
}
|
||||
|
||||
if (!err) {
|
||||
mode = ntfs_reparse_tag_mode(reparse_attr->reparse_tag);
|
||||
ni->reparse_tag = reparse_attr->reparse_tag;
|
||||
}
|
||||
} else
|
||||
ni->flags &= ~FILE_ATTR_REPARSE_POINT;
|
||||
@@ -184,8 +337,9 @@ unsigned int ntfs_reparse_tag_dt_types(struct ntfs_volume *vol, unsigned long mr
|
||||
reparse_attr = (struct reparse_point *)ntfs_attr_readall(NTFS_I(vi),
|
||||
AT_REPARSE_POINT, NULL, 0, &attr_size);
|
||||
|
||||
if (reparse_attr && attr_size) {
|
||||
if (reparse_attr && attr_size >= sizeof(*reparse_attr)) {
|
||||
switch (reparse_attr->reparse_tag) {
|
||||
case IO_REPARSE_TAG_MOUNT_POINT:
|
||||
case IO_REPARSE_TAG_SYMLINK:
|
||||
case IO_REPARSE_TAG_LX_SYMLINK:
|
||||
dt_type = DT_LNK;
|
||||
@@ -211,6 +365,115 @@ unsigned int ntfs_reparse_tag_dt_types(struct ntfs_volume *vol, unsigned long mr
|
||||
return dt_type;
|
||||
}
|
||||
|
||||
static bool ntfs_is_drive_letter(const char *target)
|
||||
{
|
||||
return ((target[0] >= 'A' && target[0] <= 'Z') ||
|
||||
(target[0] >= 'a' && target[0] <= 'z')) &&
|
||||
target[1] == ':';
|
||||
}
|
||||
|
||||
/*
|
||||
* ntfs_translate_symlink_path
|
||||
*
|
||||
* @dentry: dentry of the symlink/junction being resolved
|
||||
* @target: NUL-terminated NLS target string with '\\' already normalized to '/'
|
||||
* @translated: out parameter, set to a newly kmalloc'd relative path on success
|
||||
*
|
||||
* Windows junctions (IO_REPARSE_TAG_MOUNT_POINT) and non-relative symlinks
|
||||
* (IO_REPARSE_TAG_SYMLINK without SYMLINK_FLAG_RELATIVE) store substitute
|
||||
* names such as "/??/C:/foo", "//?/C:/foo", "/foo", or "C:/foo". Linux
|
||||
* cannot continue pathname lookup from those syntaxes, so rewrite them as a
|
||||
* path relative to the symlink's containing directory on this NTFS volume,
|
||||
* anchored at the volume root via "../".
|
||||
*
|
||||
* Note: bind-mounted subtrees of the volume may resolve to unexpected
|
||||
* locations because the computed "../" depth is relative to the NTFS volume
|
||||
* root, not the bind-mounted subtree root.
|
||||
*
|
||||
* Return: 0 on success with *translated set to a newly allocated string the
|
||||
* caller must kfree(); negative errno on failure.
|
||||
*/
|
||||
int ntfs_translate_symlink_path(struct dentry *dentry, const char *target,
|
||||
char **translated)
|
||||
{
|
||||
char *buf, *link_path, *out, *p;
|
||||
const char *path, *tail;
|
||||
unsigned int up_levels = 0;
|
||||
size_t tail_len, out_len;
|
||||
int err;
|
||||
|
||||
if (!dentry || !target || !translated)
|
||||
return -EINVAL;
|
||||
|
||||
path = target;
|
||||
/* reject UNC path. */
|
||||
if (path[0] == '/' && path[1] == '/' &&
|
||||
!(path[2] == '?' && path[3] == '/'))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
/* target starts with "/??/" or "//?/"? */
|
||||
if ((path[0] == '/' && path[1] == '?' && path[2] == '?' && path[3] == '/') ||
|
||||
(path[0] == '/' && path[1] == '/' && path[2] == '?' && path[3] == '/'))
|
||||
path += 4;
|
||||
|
||||
/* target must start with a drive character or '/'. */
|
||||
if (ntfs_is_drive_letter(path)) {
|
||||
if (path[2] && path[2] != '/')
|
||||
return -EOPNOTSUPP;
|
||||
tail = path + 2;
|
||||
if (*tail == '/')
|
||||
tail++;
|
||||
} else if (*path == '/') {
|
||||
tail = path + 1;
|
||||
} else {
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
tail_len = strlen(tail);
|
||||
|
||||
buf = kmalloc(PATH_MAX, GFP_NOFS);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
link_path = dentry_path_raw(dentry, buf, PATH_MAX);
|
||||
if (IS_ERR(link_path)) {
|
||||
err = PTR_ERR(link_path);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* count '/' after the leading slash. */
|
||||
for (p = link_path + 1; *p; p++)
|
||||
if (*p == '/')
|
||||
up_levels++;
|
||||
|
||||
/* build "./" + ("../" * up_levels) + tail. */
|
||||
out_len = 2 + up_levels * 3 + tail_len;
|
||||
if (out_len >= PATH_MAX) {
|
||||
err = -ENAMETOOLONG;
|
||||
goto out;
|
||||
}
|
||||
|
||||
out = kmalloc(out_len + 1, GFP_NOFS);
|
||||
if (!out) {
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
memcpy(out, "./", 2);
|
||||
p = out + 2;
|
||||
while (up_levels--) {
|
||||
memcpy(p, "../", 3);
|
||||
p += 3;
|
||||
}
|
||||
memcpy(p, tail, tail_len + 1);
|
||||
|
||||
*translated = out;
|
||||
err = 0;
|
||||
out:
|
||||
kfree(buf);
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the index for new reparse data
|
||||
*/
|
||||
@@ -496,40 +759,163 @@ static int ntfs_set_ntfs_reparse_data(struct ntfs_inode *ni, char *value, size_t
|
||||
* Set reparse data for a WSL type symlink
|
||||
*/
|
||||
int ntfs_reparse_set_wsl_symlink(struct ntfs_inode *ni,
|
||||
const __le16 *target, int target_len)
|
||||
const char *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;
|
||||
|
||||
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_len = sizeof(struct reparse_point) + sizeof(data->type) +
|
||||
target_len;
|
||||
reparse = kvzalloc(reparse_len, GFP_NOFS);
|
||||
if (!reparse)
|
||||
return -ENOMEM;
|
||||
|
||||
ni->target = kstrdup(target, GFP_NOFS);
|
||||
if (!ni->target) {
|
||||
kvfree(reparse);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
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) + target_len);
|
||||
reparse->reserved = 0;
|
||||
data->type = cpu_to_le32(2);
|
||||
memcpy(data->link, target, target_len);
|
||||
err = ntfs_set_ntfs_reparse_data(ni,
|
||||
(char *)reparse, reparse_len);
|
||||
kvfree(reparse);
|
||||
if (err) {
|
||||
kfree(ni->target);
|
||||
ni->target = NULL;
|
||||
} else {
|
||||
ni->reparse_tag = IO_REPARSE_TAG_LX_SYMLINK;
|
||||
ni->reparse_flags = 0;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
int ntfs_reparse_set_native_symlink(struct ntfs_inode *ni,
|
||||
const char *target, int target_len)
|
||||
{
|
||||
int err = 0;
|
||||
bool is_absolute, prt_sub_shared = true;
|
||||
char *sub_name = NULL;
|
||||
char *prt_name = NULL;
|
||||
__le16 *sub_name_utf16 = NULL;
|
||||
__le16 *prt_name_utf16 = NULL;
|
||||
int sub_len, prt_len;
|
||||
int total_data_len, total_reparse_len;
|
||||
struct reparse_point *reparse = NULL;
|
||||
struct symlink_reparse_data *data;
|
||||
int i;
|
||||
|
||||
/* Determine if target is absolute (starts with drive letter like C:/ or C:\) */
|
||||
is_absolute = target_len > 2 &&
|
||||
ntfs_is_drive_letter(target) &&
|
||||
(target[2] == '/' || target[2] == '\\');
|
||||
|
||||
|
||||
/* Normalize and prepare NLS paths */
|
||||
prt_name = kstrdup(target, GFP_NOFS);
|
||||
if (!prt_name)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Replace '/' with '\' */
|
||||
for (i = 0; i < target_len; i++) {
|
||||
if (prt_name[i] == '/')
|
||||
prt_name[i] = '\\';
|
||||
}
|
||||
|
||||
if (is_absolute) {
|
||||
/* Prepend '\??\' to Substitutename */
|
||||
sub_name = kmalloc(target_len + 5, GFP_NOFS);
|
||||
if (!sub_name) {
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
snprintf(sub_name, target_len + 5, "\\??\\%s", prt_name);
|
||||
prt_sub_shared = false;
|
||||
} else {
|
||||
/* For relative symlinks (including absolute paths without drive letters),
|
||||
* SubstituteName and PrintName are identical.
|
||||
*/
|
||||
sub_name = prt_name;
|
||||
}
|
||||
|
||||
/* Convert NLS paths to UTF-16 */
|
||||
sub_len = ntfs_nlstoucs(ni->vol, sub_name, strlen(sub_name),
|
||||
&sub_name_utf16, PATH_MAX);
|
||||
if (sub_len < 0) {
|
||||
err = sub_len;
|
||||
goto out;
|
||||
}
|
||||
|
||||
prt_len = ntfs_nlstoucs(ni->vol, prt_name, strlen(prt_name),
|
||||
&prt_name_utf16, PATH_MAX);
|
||||
if (prt_len < 0) {
|
||||
err = prt_len;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Check for buffer size limits */
|
||||
total_data_len = sizeof(struct symlink_reparse_data) +
|
||||
(sub_len + prt_len) * sizeof(__le16);
|
||||
if (total_data_len > 16384) { /* 16KB max reparse tag size */
|
||||
err = -EFBIG;
|
||||
goto out;
|
||||
}
|
||||
|
||||
total_reparse_len = sizeof(struct reparse_point) + total_data_len;
|
||||
reparse = kvzalloc(total_reparse_len, GFP_NOFS);
|
||||
if (!reparse) {
|
||||
err = -ENOMEM;
|
||||
kfree(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;
|
||||
else
|
||||
kfree(utarget);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Pack fields in reparse buffer */
|
||||
reparse->reparse_tag = IO_REPARSE_TAG_SYMLINK;
|
||||
reparse->reparse_data_length = cpu_to_le16(total_data_len);
|
||||
reparse->reserved = 0;
|
||||
|
||||
data = (struct symlink_reparse_data *)reparse->reparse_data;
|
||||
data->substitute_name_offset = 0;
|
||||
data->substitute_name_length = cpu_to_le16(sub_len * sizeof(__le16));
|
||||
data->print_name_offset = data->substitute_name_length;
|
||||
data->print_name_length = cpu_to_le16(prt_len * sizeof(__le16));
|
||||
data->flags = is_absolute ? 0 : cpu_to_le32(SYMLINK_FLAG_RELATIVE);
|
||||
|
||||
/* Copy names to path_buffer */
|
||||
memcpy(data->path_buffer, sub_name_utf16, sub_len * sizeof(__le16));
|
||||
memcpy(data->path_buffer + sub_len, prt_name_utf16, prt_len * sizeof(__le16));
|
||||
|
||||
err = ntfs_set_ntfs_reparse_data(ni, (char *)reparse, total_reparse_len);
|
||||
if (!err) {
|
||||
int len = strlen(sub_name);
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
if (sub_name[i] == '\\')
|
||||
sub_name[i] = '/';
|
||||
}
|
||||
ni->target = sub_name;
|
||||
sub_name = NULL;
|
||||
if (prt_sub_shared)
|
||||
prt_name = NULL;
|
||||
ni->reparse_tag = IO_REPARSE_TAG_SYMLINK;
|
||||
ni->reparse_flags = is_absolute ? 0 :
|
||||
cpu_to_le32(SYMLINK_FLAG_RELATIVE);
|
||||
}
|
||||
|
||||
out:
|
||||
kfree(prt_name);
|
||||
if (!prt_sub_shared)
|
||||
kfree(sub_name);
|
||||
kvfree(sub_name_utf16);
|
||||
kvfree(prt_name_utf16);
|
||||
kvfree(reparse);
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -568,6 +954,10 @@ int ntfs_reparse_set_wsl_not_symlink(struct ntfs_inode *ni, mode_t mode)
|
||||
err = ntfs_set_ntfs_reparse_data(ni, (char *)reparse,
|
||||
reparse_len);
|
||||
kvfree(reparse);
|
||||
if (!err) {
|
||||
ni->reparse_tag = reparse_tag;
|
||||
ni->reparse_flags = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
|
||||
@@ -11,8 +11,12 @@ 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_translate_symlink_path(struct dentry *dentry, const char *target,
|
||||
char **translated);
|
||||
int ntfs_reparse_set_wsl_symlink(struct ntfs_inode *ni,
|
||||
const __le16 *target, int target_len);
|
||||
const char *target, int target_len);
|
||||
int ntfs_reparse_set_native_symlink(struct ntfs_inode *ni,
|
||||
const char *symname, int symlen);
|
||||
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);
|
||||
|
||||
@@ -763,7 +763,7 @@ struct runlist_element *ntfs_mapping_pairs_decompress(const struct ntfs_volume *
|
||||
buf = (u8 *)attr +
|
||||
le16_to_cpu(attr->data.non_resident.mapping_pairs_offset);
|
||||
attr_end = (u8 *)attr + le32_to_cpu(attr->length);
|
||||
if (unlikely(buf < (u8 *)attr || buf > attr_end)) {
|
||||
if (unlikely(buf < (u8 *)attr || buf >= attr_end)) {
|
||||
ntfs_error(vol->sb, "Corrupt attribute.");
|
||||
return ERR_PTR(-EIO);
|
||||
}
|
||||
@@ -811,7 +811,7 @@ struct runlist_element *ntfs_mapping_pairs_decompress(const struct ntfs_volume *
|
||||
*/
|
||||
b = *buf & 0xf;
|
||||
if (b) {
|
||||
if (unlikely(buf + b > attr_end))
|
||||
if (unlikely(buf + b >= attr_end))
|
||||
goto io_error;
|
||||
for (deltaxcn = (s8)buf[b--]; b; b--)
|
||||
deltaxcn = (deltaxcn << 8) + buf[b];
|
||||
@@ -855,12 +855,16 @@ struct runlist_element *ntfs_mapping_pairs_decompress(const struct ntfs_volume *
|
||||
u8 b2 = *buf & 0xf;
|
||||
|
||||
b = b2 + ((*buf >> 4) & 0xf);
|
||||
if (buf + b > attr_end)
|
||||
if (buf + b >= attr_end)
|
||||
goto io_error;
|
||||
for (deltaxcn = (s8)buf[b--]; b > b2; b--)
|
||||
deltaxcn = (deltaxcn << 8) + buf[b];
|
||||
/* Change the current lcn to its new value. */
|
||||
lcn += deltaxcn;
|
||||
if (unlikely(check_add_overflow(lcn, deltaxcn, &lcn))) {
|
||||
ntfs_error(vol->sb,
|
||||
"LCN overflow in mapping pairs array.");
|
||||
goto err_out;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
/*
|
||||
* On NTFS 1.2-, apparently can have lcn == -1 to
|
||||
@@ -1817,7 +1821,7 @@ struct runlist_element *ntfs_rl_punch_hole(struct runlist_element *dst_rl, int d
|
||||
!ntfs_rle_contain(s_rl, start_vcn))
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
begin_split = s_rl->vcn != start_vcn ? true : false;
|
||||
begin_split = s_rl->vcn != start_vcn;
|
||||
|
||||
e_rl = ntfs_rl_find_vcn_nolock(dst_rl, end_vcn);
|
||||
if (!e_rl ||
|
||||
@@ -1825,10 +1829,10 @@ struct runlist_element *ntfs_rl_punch_hole(struct runlist_element *dst_rl, int d
|
||||
!ntfs_rle_contain(e_rl, end_vcn))
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
end_split = e_rl->vcn + e_rl->length - 1 != end_vcn ? true : false;
|
||||
end_split = e_rl->vcn + e_rl->length - 1 != end_vcn;
|
||||
|
||||
/* @s_rl has to be split into left, punched hole, and right */
|
||||
one_split_3 = e_rl == s_rl && begin_split && end_split ? true : false;
|
||||
one_split_3 = e_rl == s_rl && begin_split && end_split;
|
||||
|
||||
punch_cnt = (int)(e_rl - s_rl) + 1;
|
||||
|
||||
@@ -1968,7 +1972,7 @@ struct runlist_element *ntfs_rl_collapse_range(struct runlist_element *dst_rl, i
|
||||
!ntfs_rle_contain(s_rl, start_vcn))
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
begin_split = s_rl->vcn != start_vcn ? true : false;
|
||||
begin_split = s_rl->vcn != start_vcn;
|
||||
|
||||
e_rl = ntfs_rl_find_vcn_nolock(dst_rl, end_vcn);
|
||||
if (!e_rl ||
|
||||
@@ -1976,10 +1980,10 @@ struct runlist_element *ntfs_rl_collapse_range(struct runlist_element *dst_rl, i
|
||||
!ntfs_rle_contain(e_rl, end_vcn))
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
end_split = e_rl->vcn + e_rl->length - 1 != end_vcn ? true : false;
|
||||
end_split = e_rl->vcn + e_rl->length - 1 != end_vcn;
|
||||
|
||||
/* @s_rl has to be split into left, collapsed, and right */
|
||||
one_split_3 = e_rl == s_rl && begin_split && end_split ? true : false;
|
||||
one_split_3 = e_rl == s_rl && begin_split && end_split;
|
||||
|
||||
punch_cnt = (int)(e_rl - s_rl) + 1;
|
||||
*punch_rl = kvcalloc(punch_cnt + 1, sizeof(struct runlist_element),
|
||||
|
||||
226
fs/ntfs/super.c
226
fs/ntfs/super.c
@@ -17,7 +17,6 @@
|
||||
|
||||
#include "sysctl.h"
|
||||
#include "logfile.h"
|
||||
#include "quota.h"
|
||||
#include "index.h"
|
||||
#include "ntfs.h"
|
||||
#include "ea.h"
|
||||
@@ -44,6 +43,28 @@ static const struct constant_table ntfs_param_enums[] = {
|
||||
{}
|
||||
};
|
||||
|
||||
enum {
|
||||
NATIVE_SYMLINK_RAW,
|
||||
NATIVE_SYMLINK_REL,
|
||||
};
|
||||
|
||||
static const struct constant_table ntfs_native_symlink_enums[] = {
|
||||
{ "raw", NATIVE_SYMLINK_RAW },
|
||||
{ "rel", NATIVE_SYMLINK_REL },
|
||||
{}
|
||||
};
|
||||
|
||||
enum {
|
||||
SYMLINK_WSL,
|
||||
SYMLINK_NATIVE,
|
||||
};
|
||||
|
||||
static const struct constant_table ntfs_symlink_enums[] = {
|
||||
{ "wsl", SYMLINK_WSL },
|
||||
{ "native", SYMLINK_NATIVE },
|
||||
{}
|
||||
};
|
||||
|
||||
enum {
|
||||
Opt_uid,
|
||||
Opt_gid,
|
||||
@@ -67,6 +88,8 @@ enum {
|
||||
Opt_acl,
|
||||
Opt_discard,
|
||||
Opt_nocase,
|
||||
Opt_native_symlink,
|
||||
Opt_symlink,
|
||||
};
|
||||
|
||||
static const struct fs_parameter_spec ntfs_parameters[] = {
|
||||
@@ -92,6 +115,8 @@ static const struct fs_parameter_spec ntfs_parameters[] = {
|
||||
fsparam_flag("discard", Opt_discard),
|
||||
fsparam_flag("sparse", Opt_sparse),
|
||||
fsparam_flag("nocase", Opt_nocase),
|
||||
fsparam_enum("native_symlink", Opt_native_symlink, ntfs_native_symlink_enums),
|
||||
fsparam_enum("symlink", Opt_symlink, ntfs_symlink_enums),
|
||||
{}
|
||||
};
|
||||
|
||||
@@ -216,6 +241,18 @@ static int ntfs_parse_param(struct fs_context *fc, struct fs_parameter *param)
|
||||
else
|
||||
NVolClearDisableSparse(vol);
|
||||
break;
|
||||
case Opt_native_symlink:
|
||||
if (result.uint_32 == NATIVE_SYMLINK_REL)
|
||||
NVolSetNativeSymlinkRel(vol);
|
||||
else
|
||||
NVolClearNativeSymlinkRel(vol);
|
||||
break;
|
||||
case Opt_symlink:
|
||||
if (result.uint_32 == SYMLINK_NATIVE)
|
||||
NVolSetSymlinkNative(vol);
|
||||
else
|
||||
NVolClearSymlinkNative(vol);
|
||||
break;
|
||||
case Opt_sparse:
|
||||
break;
|
||||
default:
|
||||
@@ -240,8 +277,7 @@ static int ntfs_reconfigure(struct fs_context *fc)
|
||||
* flags are set. Also, empty the logfile journal as it would become
|
||||
* stale as soon as something is written to the volume and mark the
|
||||
* volume dirty so that chkdsk is run if the volume is not umounted
|
||||
* cleanly. Finally, mark the quotas out of date so Windows rescans
|
||||
* the volume on boot and updates them.
|
||||
* cleanly.
|
||||
*
|
||||
* When remounting read-only, mark the volume clean if no volume errors
|
||||
* have occurred.
|
||||
@@ -274,12 +310,6 @@ static int ntfs_reconfigure(struct fs_context *fc)
|
||||
NVolSetErrors(vol);
|
||||
return -EROFS;
|
||||
}
|
||||
if (!ntfs_mark_quotas_out_of_date(vol)) {
|
||||
ntfs_error(sb, "Failed to mark quotas out of date%s",
|
||||
es);
|
||||
NVolSetErrors(vol);
|
||||
return -EROFS;
|
||||
}
|
||||
} else if (!sb_rdonly(sb) && (fc->sb_flags & SB_RDONLY)) {
|
||||
/* Remounting read-only. */
|
||||
if (!NVolErrors(vol)) {
|
||||
@@ -452,25 +482,36 @@ int ntfs_write_volume_label(struct ntfs_volume *vol, char *label)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!ntfs_attr_lookup(AT_VOLUME_NAME, NULL, 0, 0, 0, NULL, 0,
|
||||
ctx))
|
||||
ntfs_attr_record_rm(ctx);
|
||||
ret = ntfs_attr_lookup(AT_VOLUME_NAME, NULL, 0, 0, 0, NULL, 0,
|
||||
ctx);
|
||||
if (!ret)
|
||||
ret = ntfs_attr_record_rm(ctx);
|
||||
else if (ret == -ENOENT)
|
||||
ret = 0;
|
||||
ntfs_attr_put_search_ctx(ctx);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = ntfs_resident_attr_record_add(vol_ni, AT_VOLUME_NAME, AT_UNNAMED, 0,
|
||||
(u8 *)uname, uname_len * sizeof(__le16), 0);
|
||||
out:
|
||||
if (ret >= 0) {
|
||||
char *old_label;
|
||||
|
||||
mutex_lock(&vol->volume_label_lock);
|
||||
old_label = vol->volume_label;
|
||||
vol->volume_label = new_label;
|
||||
mutex_unlock(&vol->volume_label_lock);
|
||||
|
||||
kfree(old_label);
|
||||
mark_inode_dirty_sync(vol->vol_ino);
|
||||
ret = 0;
|
||||
}
|
||||
mutex_unlock(&vol_ni->mrec_lock);
|
||||
kvfree(uname);
|
||||
|
||||
if (ret >= 0) {
|
||||
kfree(vol->volume_label);
|
||||
vol->volume_label = new_label;
|
||||
mark_inode_dirty_sync(vol->vol_ino);
|
||||
ret = 0;
|
||||
} else {
|
||||
if (ret < 0)
|
||||
kfree(new_label);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1175,73 +1216,6 @@ static int check_windows_hibernation_status(struct ntfs_volume *vol)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* load_and_init_quota - load and setup the quota file for a volume if present
|
||||
* @vol: ntfs super block describing device whose quota file to load
|
||||
*
|
||||
* Return 'true' on success or 'false' on error. If $Quota is not present, we
|
||||
* leave vol->quota_ino as NULL and return success.
|
||||
*/
|
||||
static bool load_and_init_quota(struct ntfs_volume *vol)
|
||||
{
|
||||
static const __le16 Quota[7] = { cpu_to_le16('$'),
|
||||
cpu_to_le16('Q'), cpu_to_le16('u'),
|
||||
cpu_to_le16('o'), cpu_to_le16('t'),
|
||||
cpu_to_le16('a'), 0 };
|
||||
static __le16 Q[3] = { cpu_to_le16('$'),
|
||||
cpu_to_le16('Q'), 0 };
|
||||
struct ntfs_name *name = NULL;
|
||||
u64 mref;
|
||||
struct inode *tmp_ino;
|
||||
|
||||
ntfs_debug("Entering.");
|
||||
/*
|
||||
* Find the inode number for the quota file by looking up the filename
|
||||
* $Quota in the extended system files directory $Extend.
|
||||
*/
|
||||
inode_lock(vol->extend_ino);
|
||||
mref = ntfs_lookup_inode_by_name(NTFS_I(vol->extend_ino), Quota, 6,
|
||||
&name);
|
||||
inode_unlock(vol->extend_ino);
|
||||
kfree(name);
|
||||
if (IS_ERR_MREF(mref)) {
|
||||
/*
|
||||
* If the file does not exist, quotas are disabled and have
|
||||
* never been enabled on this volume, just return success.
|
||||
*/
|
||||
if (MREF_ERR(mref) == -ENOENT) {
|
||||
ntfs_debug("$Quota not present. Volume does not have quotas enabled.");
|
||||
/*
|
||||
* No need to try to set quotas out of date if they are
|
||||
* not enabled.
|
||||
*/
|
||||
NVolSetQuotaOutOfDate(vol);
|
||||
return true;
|
||||
}
|
||||
/* A real error occurred. */
|
||||
ntfs_error(vol->sb, "Failed to find inode number for $Quota.");
|
||||
return false;
|
||||
}
|
||||
/* Get the inode. */
|
||||
tmp_ino = ntfs_iget(vol->sb, MREF(mref));
|
||||
if (IS_ERR(tmp_ino)) {
|
||||
if (!IS_ERR(tmp_ino))
|
||||
iput(tmp_ino);
|
||||
ntfs_error(vol->sb, "Failed to load $Quota.");
|
||||
return false;
|
||||
}
|
||||
vol->quota_ino = tmp_ino;
|
||||
/* Get the $Q index allocation attribute. */
|
||||
tmp_ino = ntfs_index_iget(vol->quota_ino, Q, 2);
|
||||
if (IS_ERR(tmp_ino)) {
|
||||
ntfs_error(vol->sb, "Failed to load $Quota/$Q index.");
|
||||
return false;
|
||||
}
|
||||
vol->quota_q_ino = tmp_ino;
|
||||
ntfs_debug("Done.");
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* load_and_init_attrdef - load the attribute definitions table for a volume
|
||||
* @vol: ntfs super block describing device whose attrdef to load
|
||||
@@ -1323,7 +1297,6 @@ static bool load_and_init_upcase(struct ntfs_volume *vol)
|
||||
u8 *addr;
|
||||
pgoff_t index, max_index;
|
||||
unsigned int size;
|
||||
int i, max;
|
||||
|
||||
ntfs_debug("Entering.");
|
||||
/* Read upcase table and setup vol->upcase and vol->upcase_len. */
|
||||
@@ -1374,16 +1347,11 @@ static bool load_and_init_upcase(struct ntfs_volume *vol)
|
||||
mutex_unlock(&ntfs_lock);
|
||||
return true;
|
||||
}
|
||||
max = default_upcase_len;
|
||||
if (max > vol->upcase_len)
|
||||
max = vol->upcase_len;
|
||||
for (i = 0; i < max; i++)
|
||||
if (vol->upcase[i] != default_upcase[i])
|
||||
break;
|
||||
if (i == max) {
|
||||
if (default_upcase_len == vol->upcase_len &&
|
||||
!memcmp(vol->upcase, default_upcase,
|
||||
default_upcase_len * sizeof(*default_upcase))) {
|
||||
kvfree(vol->upcase);
|
||||
vol->upcase = default_upcase;
|
||||
vol->upcase_len = max;
|
||||
ntfs_nr_upcase_users++;
|
||||
mutex_unlock(&ntfs_lock);
|
||||
ntfs_debug("Volume specified $UpCase matches default. Using default.");
|
||||
@@ -1531,6 +1499,7 @@ static bool load_system_files(struct ntfs_volume *vol)
|
||||
vol->volume_label = NULL;
|
||||
}
|
||||
|
||||
ntfs_attr_reinit_search_ctx(ctx);
|
||||
if (ntfs_attr_lookup(AT_VOLUME_INFORMATION, NULL, 0, 0, 0, NULL, 0,
|
||||
ctx) || ctx->attr->non_resident || ctx->attr->flags) {
|
||||
ntfs_attr_put_search_ctx(ctx);
|
||||
@@ -1659,18 +1628,6 @@ static bool load_system_files(struct ntfs_volume *vol)
|
||||
ntfs_error(sb, "Failed to load $Extend.");
|
||||
goto iput_sec_err_out;
|
||||
}
|
||||
/* Find the quota file, load it if present, and set it up. */
|
||||
if (!load_and_init_quota(vol) &&
|
||||
vol->on_errors == ON_ERRORS_REMOUNT_RO) {
|
||||
static const char *es1 = "Failed to load $Quota";
|
||||
static const char *es2 = ". Run chkdsk.";
|
||||
|
||||
sb->s_flags |= SB_RDONLY;
|
||||
ntfs_error(sb, "%s. Mounting read-only%s", es1, es2);
|
||||
/* This will prevent a read-write remount. */
|
||||
NVolSetErrors(vol);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
iput_sec_err_out:
|
||||
@@ -1727,7 +1684,7 @@ static void ntfs_volume_free(struct ntfs_volume *vol)
|
||||
vol->upcase = NULL;
|
||||
}
|
||||
|
||||
if (!ntfs_nr_upcase_users && default_upcase) {
|
||||
if (!ntfs_nr_upcase_users) {
|
||||
kvfree(default_upcase);
|
||||
default_upcase = NULL;
|
||||
}
|
||||
@@ -1742,8 +1699,7 @@ static void ntfs_volume_free(struct ntfs_volume *vol)
|
||||
|
||||
unload_nls(vol->nls_map);
|
||||
|
||||
if (vol->lcn_empty_bits_per_page)
|
||||
kvfree(vol->lcn_empty_bits_per_page);
|
||||
kvfree(vol->lcn_empty_bits_per_page);
|
||||
kfree(vol->volume_label);
|
||||
kfree(vol);
|
||||
}
|
||||
@@ -1768,10 +1724,6 @@ static void ntfs_put_super(struct super_block *sb)
|
||||
|
||||
/* NTFS 3.0+ specific. */
|
||||
if (vol->major_ver >= 3) {
|
||||
if (vol->quota_q_ino)
|
||||
ntfs_commit_inode(vol->quota_q_ino);
|
||||
if (vol->quota_ino)
|
||||
ntfs_commit_inode(vol->quota_ino);
|
||||
if (vol->extend_ino)
|
||||
ntfs_commit_inode(vol->extend_ino);
|
||||
if (vol->secure_ino)
|
||||
@@ -1820,14 +1772,6 @@ static void ntfs_put_super(struct super_block *sb)
|
||||
|
||||
/* NTFS 3.0+ specific clean up. */
|
||||
if (vol->major_ver >= 3) {
|
||||
if (vol->quota_q_ino) {
|
||||
iput(vol->quota_q_ino);
|
||||
vol->quota_q_ino = NULL;
|
||||
}
|
||||
if (vol->quota_ino) {
|
||||
iput(vol->quota_ino);
|
||||
vol->quota_ino = NULL;
|
||||
}
|
||||
if (vol->extend_ino) {
|
||||
iput(vol->extend_ino);
|
||||
vol->extend_ino = NULL;
|
||||
@@ -1954,7 +1898,7 @@ s64 get_nr_free_clusters(struct ntfs_volume *vol)
|
||||
struct address_space *mapping = vol->lcnbmp_ino->i_mapping;
|
||||
struct folio *folio;
|
||||
pgoff_t index, max_index;
|
||||
struct file_ra_state *ra;
|
||||
struct file_ra_state ra = { 0 };
|
||||
|
||||
ntfs_debug("Entering.");
|
||||
/* Serialize accesses to the cluster bitmap. */
|
||||
@@ -1962,11 +1906,7 @@ s64 get_nr_free_clusters(struct ntfs_volume *vol)
|
||||
if (NVolFreeClusterKnown(vol))
|
||||
return atomic64_read(&vol->free_clusters);
|
||||
|
||||
ra = kzalloc(sizeof(*ra), GFP_NOFS);
|
||||
if (!ra)
|
||||
return 0;
|
||||
|
||||
file_ra_state_init(ra, mapping);
|
||||
file_ra_state_init(&ra, mapping);
|
||||
|
||||
/*
|
||||
* Convert the number of bits into bytes rounded up, then convert into
|
||||
@@ -1985,7 +1925,7 @@ s64 get_nr_free_clusters(struct ntfs_volume *vol)
|
||||
* Get folio from page cache, getting it from backing store
|
||||
* if necessary, and increment the use count.
|
||||
*/
|
||||
folio = ntfs_get_locked_folio(mapping, index, max_index, ra);
|
||||
folio = ntfs_get_locked_folio(mapping, index, max_index, &ra);
|
||||
|
||||
/* Ignore pages which errored synchronously. */
|
||||
if (IS_ERR(folio)) {
|
||||
@@ -2024,7 +1964,6 @@ s64 get_nr_free_clusters(struct ntfs_volume *vol)
|
||||
else
|
||||
atomic64_set(&vol->free_clusters, nr_free);
|
||||
|
||||
kfree(ra);
|
||||
NVolSetFreeClusterKnown(vol);
|
||||
wake_up_all(&vol->free_waitq);
|
||||
ntfs_debug("Exiting.");
|
||||
@@ -2079,15 +2018,11 @@ static unsigned long __get_nr_free_mft_records(struct ntfs_volume *vol,
|
||||
struct address_space *mapping = vol->mftbmp_ino->i_mapping;
|
||||
struct folio *folio;
|
||||
pgoff_t index;
|
||||
struct file_ra_state *ra;
|
||||
struct file_ra_state ra = { 0 };
|
||||
|
||||
ntfs_debug("Entering.");
|
||||
|
||||
ra = kzalloc(sizeof(*ra), GFP_NOFS);
|
||||
if (!ra)
|
||||
return 0;
|
||||
|
||||
file_ra_state_init(ra, mapping);
|
||||
file_ra_state_init(&ra, mapping);
|
||||
|
||||
/* Use multiples of 4 bytes, thus max_size is PAGE_SIZE / 4. */
|
||||
ntfs_debug("Reading $MFT/$BITMAP, max_index = 0x%lx, max_size = 0x%lx.",
|
||||
@@ -2099,7 +2034,7 @@ static unsigned long __get_nr_free_mft_records(struct ntfs_volume *vol,
|
||||
* Get folio from page cache, getting it from backing store
|
||||
* if necessary, and increment the use count.
|
||||
*/
|
||||
folio = ntfs_get_locked_folio(mapping, index, max_index, ra);
|
||||
folio = ntfs_get_locked_folio(mapping, index, max_index, &ra);
|
||||
|
||||
/* Ignore pages which errored synchronously. */
|
||||
if (IS_ERR(folio)) {
|
||||
@@ -2131,7 +2066,6 @@ static unsigned long __get_nr_free_mft_records(struct ntfs_volume *vol,
|
||||
else
|
||||
atomic64_set(&vol->free_mft_records, nr_free);
|
||||
|
||||
kfree(ra);
|
||||
ntfs_debug("Exiting.");
|
||||
return nr_free;
|
||||
}
|
||||
@@ -2476,14 +2410,6 @@ static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc)
|
||||
vol->vol_ino = NULL;
|
||||
/* NTFS 3.0+ specific clean up. */
|
||||
if (vol->major_ver >= 3) {
|
||||
if (vol->quota_q_ino) {
|
||||
iput(vol->quota_q_ino);
|
||||
vol->quota_q_ino = NULL;
|
||||
}
|
||||
if (vol->quota_ino) {
|
||||
iput(vol->quota_ino);
|
||||
vol->quota_ino = NULL;
|
||||
}
|
||||
if (vol->extend_ino) {
|
||||
iput(vol->extend_ino);
|
||||
vol->extend_ino = NULL;
|
||||
@@ -2530,8 +2456,6 @@ static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc)
|
||||
}
|
||||
/* Error exit code path. */
|
||||
unl_upcase_iput_tmp_ino_err_out_now:
|
||||
if (vol->lcn_empty_bits_per_page)
|
||||
kvfree(vol->lcn_empty_bits_per_page);
|
||||
/*
|
||||
* Decrease the number of upcase users and destroy the global default
|
||||
* upcase table if necessary.
|
||||
@@ -2551,6 +2475,9 @@ static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc)
|
||||
/* Errors at this stage are irrelevant. */
|
||||
err_out_now:
|
||||
sb->s_fs_info = NULL;
|
||||
kvfree(vol->lcn_empty_bits_per_page);
|
||||
kfree(vol->volume_label);
|
||||
unload_nls(vol->nls_map);
|
||||
kfree(vol);
|
||||
ntfs_debug("Failed, returning -EINVAL.");
|
||||
lockdep_on();
|
||||
@@ -2631,6 +2558,7 @@ static int ntfs_init_fs_context(struct fs_context *fc)
|
||||
NVolSetCaseSensitive(vol);
|
||||
init_rwsem(&vol->mftbmp_lock);
|
||||
init_rwsem(&vol->lcnbmp_lock);
|
||||
mutex_init(&vol->volume_label_lock);
|
||||
|
||||
fc->s_fs_info = vol;
|
||||
fc->ops = &ntfs_context_ops;
|
||||
@@ -2649,7 +2577,7 @@ MODULE_ALIAS_FS("ntfs");
|
||||
|
||||
static int ntfs_workqueue_init(void)
|
||||
{
|
||||
ntfs_wq = alloc_workqueue("ntfs-bg-io", 0, 0);
|
||||
ntfs_wq = alloc_workqueue("ntfs-bg-io", WQ_PERCPU, 0);
|
||||
if (!ntfs_wq)
|
||||
return -ENOMEM;
|
||||
return 0;
|
||||
|
||||
@@ -72,12 +72,11 @@
|
||||
* @vol_flags: Volume flags.
|
||||
* @major_ver: Ntfs major version of volume.
|
||||
* @minor_ver: Ntfs minor version of volume.
|
||||
* @volume_label_lock: protects @volume_label.
|
||||
* @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.
|
||||
@@ -133,6 +132,7 @@ struct ntfs_volume {
|
||||
struct inode *logfile_ino;
|
||||
struct inode *lcnbmp_ino;
|
||||
struct rw_semaphore lcnbmp_lock;
|
||||
struct mutex volume_label_lock;
|
||||
struct inode *vol_ino;
|
||||
__le16 vol_flags;
|
||||
u8 major_ver;
|
||||
@@ -141,8 +141,6 @@ struct ntfs_volume {
|
||||
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;
|
||||
@@ -165,7 +163,6 @@ struct ntfs_volume {
|
||||
* 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.
|
||||
@@ -180,13 +177,13 @@ struct ntfs_volume {
|
||||
*
|
||||
* NV_Discard Issue discard/TRIM commands for freed clusters.
|
||||
* NV_DisableSparse Disable creation of sparse regions.
|
||||
* NV_NativeSymlinkRel Translate absolute Windows reparse targets (native_symlink=rel).
|
||||
*/
|
||||
enum {
|
||||
NV_Errors,
|
||||
NV_ShowSystemFiles,
|
||||
NV_CaseSensitive,
|
||||
NV_LogFileEmpty,
|
||||
NV_QuotaOutOfDate,
|
||||
NV_UsnJrnlStamped,
|
||||
NV_ReadOnly,
|
||||
NV_Compression,
|
||||
@@ -198,6 +195,8 @@ enum {
|
||||
NV_CheckWindowsNames,
|
||||
NV_Discard,
|
||||
NV_DisableSparse,
|
||||
NV_NativeSymlinkRel,
|
||||
NV_SymlinkNative,
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -223,7 +222,6 @@ 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)
|
||||
@@ -235,6 +233,8 @@ DEFINE_NVOL_BIT_OPS(HideDotFiles)
|
||||
DEFINE_NVOL_BIT_OPS(CheckWindowsNames)
|
||||
DEFINE_NVOL_BIT_OPS(Discard)
|
||||
DEFINE_NVOL_BIT_OPS(DisableSparse)
|
||||
DEFINE_NVOL_BIT_OPS(NativeSymlinkRel)
|
||||
DEFINE_NVOL_BIT_OPS(SymlinkNative)
|
||||
|
||||
static inline void ntfs_inc_free_clusters(struct ntfs_volume *vol, s64 nr)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user