summaryrefslogtreecommitdiff
path: root/fs/ntfs3/dir.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/ntfs3/dir.c')
-rw-r--r--fs/ntfs3/dir.c162
1 files changed, 110 insertions, 52 deletions
diff --git a/fs/ntfs3/dir.c b/fs/ntfs3/dir.c
index 4652a56ad105..eb9152e9fa22 100644
--- a/fs/ntfs3/dir.c
+++ b/fs/ntfs3/dir.c
@@ -25,6 +25,11 @@ int ntfs_utf16_to_nls(struct ntfs_sb_info *sbi, const __le16 *name, u32 len,
static_assert(sizeof(wchar_t) == sizeof(__le16));
+ if (buf_len <= 0)
+ return -EINVAL;
+
+ buf_len -= 1;
+
if (!nls) {
/* UTF-16 -> UTF-8 */
ret = utf16s_to_utf8s((wchar_t *)name, len, UTF16_LITTLE_ENDIAN,
@@ -179,7 +184,7 @@ int ntfs_nls_to_utf16(struct ntfs_sb_info *sbi, const u8 *name, u32 name_len,
struct cpu_str *uni, u32 max_ulen,
enum utf16_endian endian)
{
- int ret, slen;
+ int ret, slen, i;
const u8 *end;
struct nls_table *nls = sbi->options->nls;
u16 *uname = uni->name;
@@ -189,50 +194,83 @@ int ntfs_nls_to_utf16(struct ntfs_sb_info *sbi, const u8 *name, u32 name_len,
if (!nls) {
/* utf8 -> utf16 */
ret = _utf8s_to_utf16s(name, name_len, endian, uname, max_ulen);
- uni->len = ret;
- return ret;
- }
-
- for (ret = 0, end = name + name_len; name < end; ret++, name += slen) {
- if (ret >= max_ulen)
- return -ENAMETOOLONG;
+ } else {
+ for (ret = 0, end = name + name_len; name < end;
+ ret++, name += slen) {
+ if (ret >= max_ulen)
+ return -ENAMETOOLONG;
- slen = nls->char2uni(name, end - name, uname + ret);
- if (!slen)
- return -EINVAL;
- if (slen < 0)
- return slen;
- }
+ slen = nls->char2uni(name, end - name, uname + ret);
+ if (!slen)
+ return -EINVAL;
+ if (slen < 0)
+ return slen;
+ }
#ifdef __BIG_ENDIAN
- if (endian == UTF16_LITTLE_ENDIAN) {
- int i = ret;
+ if (endian == UTF16_LITTLE_ENDIAN) {
+ i = ret;
- while (i--) {
- __cpu_to_le16s(uname);
- uname++;
+ while (i--) {
+ __cpu_to_le16s(uname);
+ uname++;
+ }
}
- }
#else
- if (endian == UTF16_BIG_ENDIAN) {
- int i = ret;
+ if (endian == UTF16_BIG_ENDIAN) {
+ i = ret;
- while (i--) {
- __cpu_to_be16s(uname);
- uname++;
+ while (i--) {
+ __cpu_to_be16s(uname);
+ uname++;
+ }
}
- }
#endif
+ }
uni->len = ret;
+ uni->ads_len = 0;
+ if (ret > 0 && sbi->options->ads) {
+ uname = uni->name;
+ /* Find delimiter in range [1 : ret-2). */
+ for (i = 1; i + 1 < ret; i++) {
+ if (uname[i] == ':') {
+ uni->ads_len = ret - i - 1;
+ uni->len = i;
+ uname[i] = 0;
+ ret = i;
+
+ uname += i + 1;
+ i = uni->ads_len;
+ /* Return ADS name as little endian. Always */
+#ifdef __BIG_ENDIAN
+ if (endian == UTF16_LITTLE_ENDIAN) {
+ while (i--) {
+ __cpu_to_le16s(uname);
+ uname++;
+ }
+ }
+#else
+ if (endian == UTF16_BIG_ENDIAN) {
+ while (i--) {
+ __cpu_to_be16s(uname);
+ uname++;
+ }
+ }
+#endif
+ break;
+ }
+ }
+ }
+
return ret;
}
/*
* dir_search_u - Helper function.
*/
-struct inode *dir_search_u(struct inode *dir, const struct cpu_str *uni,
- struct ntfs_fnd *fnd)
+struct inode *dir_search_flags(struct inode *dir, const struct cpu_str *uni,
+ struct ntfs_fnd *fnd, u32 flags)
{
int err = 0;
struct super_block *sb = dir->i_sb;
@@ -262,7 +300,7 @@ struct inode *dir_search_u(struct inode *dir, const struct cpu_str *uni,
goto out;
}
- inode = ntfs_iget5(sb, &e->ref, uni);
+ inode = ntfs_iget5_flags(sb, &e->ref, uni, flags);
if (!IS_ERR(inode) && is_bad_inode(inode)) {
iput(inode);
err = -EINVAL;
@@ -273,6 +311,12 @@ out:
return err == -ENOENT ? NULL : err ? ERR_PTR(err) : inode;
}
+static inline bool de_fname_fits(const struct NTFS_DE *e, u32 e_size,
+ const struct ATTR_FILE_NAME *fname)
+{
+ return sizeof(struct NTFS_DE) + fname_full_size(fname) <= e_size;
+}
+
/*
* returns false if 'ctx' if full
*/
@@ -281,7 +325,7 @@ static inline bool ntfs_dir_emit(struct ntfs_sb_info *sbi,
u8 *name, struct dir_context *ctx)
{
const struct ATTR_FILE_NAME *fname;
- unsigned long ino;
+ u64 ino;
int name_len;
u32 dt_type;
@@ -305,13 +349,13 @@ static inline bool ntfs_dir_emit(struct ntfs_sb_info *sbi,
if (sbi->options->nohidden && (fname->dup.fa & FILE_ATTRIBUTE_HIDDEN))
return true;
- if (fname->name_len + sizeof(struct NTFS_DE) > le16_to_cpu(e->size))
+ if (!de_fname_fits(e, le16_to_cpu(e->size), fname))
return true;
name_len = ntfs_utf16_to_nls(sbi, fname->name, fname->name_len, name,
PATH_MAX);
if (name_len <= 0) {
- ntfs_warn(sbi->sb, "failed to convert name for inode %lx.",
+ ntfs_warn(sbi->sb, "failed to convert name for inode %llx.",
ino);
return true;
}
@@ -489,10 +533,17 @@ static int ntfs_readdir(struct file *file, struct dir_context *ctx)
goto out;
}
+ /*
+ * Keep directory metadata stable for the whole walk. Loading subrecords
+ * once is not enough if concurrent writeback can still compact ATTR_LIST
+ * entries and free the record that ntfs_read_hdr() is currently walking.
+ */
+ ni_lock(ni);
+
root = indx_get_root(&ni->dir, ni, NULL, NULL);
if (!root) {
err = -EINVAL;
- goto out;
+ goto out_unlock;
}
if (pos >= sbi->record_size) {
@@ -503,7 +554,7 @@ static int ntfs_readdir(struct file *file, struct dir_context *ctx)
*/
err = ntfs_read_hdr(sbi, ni, &root->ihdr, 0, pos, name, ctx);
if (err)
- goto out;
+ goto out_unlock;
bit = 0;
}
@@ -514,7 +565,7 @@ static int ntfs_readdir(struct file *file, struct dir_context *ctx)
/* Get the next used index. */
err = indx_used_bit(&ni->dir, ni, &bit);
if (err)
- goto out;
+ goto out_unlock;
if (bit == MINUS_ONE_T) {
/* no more used indexes. end of dir. */
@@ -524,13 +575,13 @@ static int ntfs_readdir(struct file *file, struct dir_context *ctx)
if (bit >= max_bit) {
/* Corrupted directory. */
err = -EINVAL;
- goto out;
+ goto out_unlock;
}
err = indx_read_ra(&ni->dir, ni, bit << ni->dir.idx2vbn_bits,
&node, &file->f_ra);
if (err)
- goto out;
+ goto out_unlock;
/*
* Add each name from index in 'ctx'.
@@ -539,9 +590,12 @@ static int ntfs_readdir(struct file *file, struct dir_context *ctx)
((u64)bit << index_bits) + sbi->record_size,
pos, name, ctx);
if (err)
- goto out;
+ goto out_unlock;
}
+out_unlock:
+ ni_unlock(ni);
+
out:
kfree(name);
put_indx_node(node);
@@ -564,6 +618,23 @@ out:
return err;
}
+/*
+ * Return fname when @e passes the same checks as ntfs_dir_emit() before
+ * exposing an entry (valid key, non-DOS, fname fits in e->size).
+ */
+static inline const struct ATTR_FILE_NAME *
+de_countable_fname(const struct NTFS_DE *e, u32 e_size)
+{
+ const struct ATTR_FILE_NAME *fname;
+
+ fname = de_get_fname(e);
+ if (!fname || fname->type == FILE_NAME_DOS ||
+ !de_fname_fits(e, e_size, fname))
+ return NULL;
+
+ return fname;
+}
+
static int ntfs_dir_count(struct inode *dir, bool *is_empty, size_t *dirs,
size_t *files)
{
@@ -603,13 +674,10 @@ static int ntfs_dir_count(struct inode *dir, bool *is_empty, size_t *dirs,
if (de_is_last(e))
break;
- fname = de_get_fname(e);
+ fname = de_countable_fname(e, e_size);
if (!fname)
continue;
- if (fname->type == FILE_NAME_DOS)
- continue;
-
if (is_empty) {
*is_empty = false;
if (!dirs && !files)
@@ -676,14 +744,4 @@ const struct file_operations ntfs_dir_operations = {
#endif
.setlease = generic_setlease,
};
-
-#if IS_ENABLED(CONFIG_NTFS_FS)
-const struct file_operations ntfs_legacy_dir_operations = {
- .llseek = generic_file_llseek,
- .read = generic_read_dir,
- .iterate_shared = ntfs_readdir,
- .open = ntfs_file_open,
- .setlease = generic_setlease,
-};
-#endif
// clang-format on