|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | /* | 
|  | * | 
|  | * Copyright (C) 2019-2021 Paragon Software GmbH, All rights reserved. | 
|  | * | 
|  | *  Directory handling functions for NTFS-based filesystems. | 
|  | * | 
|  | */ | 
|  |  | 
|  | #include <linux/fs.h> | 
|  | #include <linux/nls.h> | 
|  |  | 
|  | #include "debug.h" | 
|  | #include "ntfs.h" | 
|  | #include "ntfs_fs.h" | 
|  |  | 
|  | /* Convert little endian UTF-16 to NLS string. */ | 
|  | int ntfs_utf16_to_nls(struct ntfs_sb_info *sbi, const __le16 *name, u32 len, | 
|  | u8 *buf, int buf_len) | 
|  | { | 
|  | int ret, warn; | 
|  | u8 *op; | 
|  | struct nls_table *nls = sbi->options->nls; | 
|  |  | 
|  | static_assert(sizeof(wchar_t) == sizeof(__le16)); | 
|  |  | 
|  | if (!nls) { | 
|  | /* UTF-16 -> UTF-8 */ | 
|  | ret = utf16s_to_utf8s(name, len, UTF16_LITTLE_ENDIAN, buf, | 
|  | buf_len); | 
|  | buf[ret] = '\0'; | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | op = buf; | 
|  | warn = 0; | 
|  |  | 
|  | while (len--) { | 
|  | u16 ec; | 
|  | int charlen; | 
|  | char dump[5]; | 
|  |  | 
|  | if (buf_len < NLS_MAX_CHARSET_SIZE) { | 
|  | ntfs_warn(sbi->sb, | 
|  | "filename was truncated while converting."); | 
|  | break; | 
|  | } | 
|  |  | 
|  | ec = le16_to_cpu(*name++); | 
|  | charlen = nls->uni2char(ec, op, buf_len); | 
|  |  | 
|  | if (charlen > 0) { | 
|  | op += charlen; | 
|  | buf_len -= charlen; | 
|  | continue; | 
|  | } | 
|  |  | 
|  | *op++ = '_'; | 
|  | buf_len -= 1; | 
|  | if (warn) | 
|  | continue; | 
|  |  | 
|  | warn = 1; | 
|  | hex_byte_pack(&dump[0], ec >> 8); | 
|  | hex_byte_pack(&dump[2], ec); | 
|  | dump[4] = 0; | 
|  |  | 
|  | ntfs_err(sbi->sb, "failed to convert \"%s\" to %s", dump, | 
|  | nls->charset); | 
|  | } | 
|  |  | 
|  | *op = '\0'; | 
|  | return op - buf; | 
|  | } | 
|  |  | 
|  | // clang-format off | 
|  | #define PLANE_SIZE	0x00010000 | 
|  |  | 
|  | #define SURROGATE_PAIR	0x0000d800 | 
|  | #define SURROGATE_LOW	0x00000400 | 
|  | #define SURROGATE_BITS	0x000003ff | 
|  | // clang-format on | 
|  |  | 
|  | /* | 
|  | * put_utf16 - Modified version of put_utf16 from fs/nls/nls_base.c | 
|  | * | 
|  | * Function is sparse warnings free. | 
|  | */ | 
|  | static inline void put_utf16(wchar_t *s, unsigned int c, | 
|  | enum utf16_endian endian) | 
|  | { | 
|  | static_assert(sizeof(wchar_t) == sizeof(__le16)); | 
|  | static_assert(sizeof(wchar_t) == sizeof(__be16)); | 
|  |  | 
|  | switch (endian) { | 
|  | default: | 
|  | *s = (wchar_t)c; | 
|  | break; | 
|  | case UTF16_LITTLE_ENDIAN: | 
|  | *(__le16 *)s = __cpu_to_le16(c); | 
|  | break; | 
|  | case UTF16_BIG_ENDIAN: | 
|  | *(__be16 *)s = __cpu_to_be16(c); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * _utf8s_to_utf16s | 
|  | * | 
|  | * Modified version of 'utf8s_to_utf16s' allows to | 
|  | * detect -ENAMETOOLONG without writing out of expected maximum. | 
|  | */ | 
|  | static int _utf8s_to_utf16s(const u8 *s, int inlen, enum utf16_endian endian, | 
|  | wchar_t *pwcs, int maxout) | 
|  | { | 
|  | u16 *op; | 
|  | int size; | 
|  | unicode_t u; | 
|  |  | 
|  | op = pwcs; | 
|  | while (inlen > 0 && *s) { | 
|  | if (*s & 0x80) { | 
|  | size = utf8_to_utf32(s, inlen, &u); | 
|  | if (size < 0) | 
|  | return -EINVAL; | 
|  | s += size; | 
|  | inlen -= size; | 
|  |  | 
|  | if (u >= PLANE_SIZE) { | 
|  | if (maxout < 2) | 
|  | return -ENAMETOOLONG; | 
|  |  | 
|  | u -= PLANE_SIZE; | 
|  | put_utf16(op++, | 
|  | SURROGATE_PAIR | | 
|  | ((u >> 10) & SURROGATE_BITS), | 
|  | endian); | 
|  | put_utf16(op++, | 
|  | SURROGATE_PAIR | SURROGATE_LOW | | 
|  | (u & SURROGATE_BITS), | 
|  | endian); | 
|  | maxout -= 2; | 
|  | } else { | 
|  | if (maxout < 1) | 
|  | return -ENAMETOOLONG; | 
|  |  | 
|  | put_utf16(op++, u, endian); | 
|  | maxout--; | 
|  | } | 
|  | } else { | 
|  | if (maxout < 1) | 
|  | return -ENAMETOOLONG; | 
|  |  | 
|  | put_utf16(op++, *s++, endian); | 
|  | inlen--; | 
|  | maxout--; | 
|  | } | 
|  | } | 
|  | return op - pwcs; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * ntfs_nls_to_utf16 - Convert input string to UTF-16. | 
|  | * @name:	Input name. | 
|  | * @name_len:	Input name length. | 
|  | * @uni:	Destination memory. | 
|  | * @max_ulen:	Destination memory. | 
|  | * @endian:	Endian of target UTF-16 string. | 
|  | * | 
|  | * This function is called: | 
|  | * - to create NTFS name | 
|  | * - to create symlink | 
|  | * | 
|  | * Return: UTF-16 string length or error (if negative). | 
|  | */ | 
|  | 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; | 
|  | const u8 *end; | 
|  | struct nls_table *nls = sbi->options->nls; | 
|  | u16 *uname = uni->name; | 
|  |  | 
|  | static_assert(sizeof(wchar_t) == sizeof(u16)); | 
|  |  | 
|  | 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; | 
|  |  | 
|  | 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; | 
|  |  | 
|  | while (i--) { | 
|  | __cpu_to_le16s(uname); | 
|  | uname++; | 
|  | } | 
|  | } | 
|  | #else | 
|  | if (endian == UTF16_BIG_ENDIAN) { | 
|  | int i = ret; | 
|  |  | 
|  | while (i--) { | 
|  | __cpu_to_be16s(uname); | 
|  | uname++; | 
|  | } | 
|  | } | 
|  | #endif | 
|  |  | 
|  | uni->len = ret; | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * dir_search_u - Helper function. | 
|  | */ | 
|  | struct inode *dir_search_u(struct inode *dir, const struct cpu_str *uni, | 
|  | struct ntfs_fnd *fnd) | 
|  | { | 
|  | int err = 0; | 
|  | struct super_block *sb = dir->i_sb; | 
|  | struct ntfs_sb_info *sbi = sb->s_fs_info; | 
|  | struct ntfs_inode *ni = ntfs_i(dir); | 
|  | struct NTFS_DE *e; | 
|  | int diff; | 
|  | struct inode *inode = NULL; | 
|  | struct ntfs_fnd *fnd_a = NULL; | 
|  |  | 
|  | if (!fnd) { | 
|  | fnd_a = fnd_get(); | 
|  | if (!fnd_a) { | 
|  | err = -ENOMEM; | 
|  | goto out; | 
|  | } | 
|  | fnd = fnd_a; | 
|  | } | 
|  |  | 
|  | err = indx_find(&ni->dir, ni, NULL, uni, 0, sbi, &diff, &e, fnd); | 
|  |  | 
|  | if (err) | 
|  | goto out; | 
|  |  | 
|  | if (diff) { | 
|  | err = -ENOENT; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | inode = ntfs_iget5(sb, &e->ref, uni); | 
|  | if (!IS_ERR(inode) && is_bad_inode(inode)) { | 
|  | iput(inode); | 
|  | err = -EINVAL; | 
|  | } | 
|  | out: | 
|  | fnd_put(fnd_a); | 
|  |  | 
|  | return err == -ENOENT ? NULL : err ? ERR_PTR(err) : inode; | 
|  | } | 
|  |  | 
|  | static inline int ntfs_filldir(struct ntfs_sb_info *sbi, struct ntfs_inode *ni, | 
|  | const struct NTFS_DE *e, u8 *name, | 
|  | struct dir_context *ctx) | 
|  | { | 
|  | const struct ATTR_FILE_NAME *fname; | 
|  | unsigned long ino; | 
|  | int name_len; | 
|  | u32 dt_type; | 
|  |  | 
|  | fname = Add2Ptr(e, sizeof(struct NTFS_DE)); | 
|  |  | 
|  | if (fname->type == FILE_NAME_DOS) | 
|  | return 0; | 
|  |  | 
|  | if (!mi_is_ref(&ni->mi, &fname->home)) | 
|  | return 0; | 
|  |  | 
|  | ino = ino_get(&e->ref); | 
|  |  | 
|  | if (ino == MFT_REC_ROOT) | 
|  | return 0; | 
|  |  | 
|  | /* Skip meta files. Unless option to show metafiles is set. */ | 
|  | if (!sbi->options->showmeta && ntfs_is_meta_file(sbi, ino)) | 
|  | return 0; | 
|  |  | 
|  | if (sbi->options->nohidden && (fname->dup.fa & FILE_ATTRIBUTE_HIDDEN)) | 
|  | return 0; | 
|  |  | 
|  | 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.", | 
|  | ino); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | dt_type = (fname->dup.fa & FILE_ATTRIBUTE_DIRECTORY) ? DT_DIR : DT_REG; | 
|  |  | 
|  | return !dir_emit(ctx, (s8 *)name, name_len, ino, dt_type); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * ntfs_read_hdr - Helper function for ntfs_readdir(). | 
|  | */ | 
|  | static int ntfs_read_hdr(struct ntfs_sb_info *sbi, struct ntfs_inode *ni, | 
|  | const struct INDEX_HDR *hdr, u64 vbo, u64 pos, | 
|  | u8 *name, struct dir_context *ctx) | 
|  | { | 
|  | int err; | 
|  | const struct NTFS_DE *e; | 
|  | u32 e_size; | 
|  | u32 end = le32_to_cpu(hdr->used); | 
|  | u32 off = le32_to_cpu(hdr->de_off); | 
|  |  | 
|  | for (;; off += e_size) { | 
|  | if (off + sizeof(struct NTFS_DE) > end) | 
|  | return -1; | 
|  |  | 
|  | e = Add2Ptr(hdr, off); | 
|  | e_size = le16_to_cpu(e->size); | 
|  | if (e_size < sizeof(struct NTFS_DE) || off + e_size > end) | 
|  | return -1; | 
|  |  | 
|  | if (de_is_last(e)) | 
|  | return 0; | 
|  |  | 
|  | /* Skip already enumerated. */ | 
|  | if (vbo + off < pos) | 
|  | continue; | 
|  |  | 
|  | if (le16_to_cpu(e->key_size) < SIZEOF_ATTRIBUTE_FILENAME) | 
|  | return -1; | 
|  |  | 
|  | ctx->pos = vbo + off; | 
|  |  | 
|  | /* Submit the name to the filldir callback. */ | 
|  | err = ntfs_filldir(sbi, ni, e, name, ctx); | 
|  | if (err) | 
|  | return err; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * ntfs_readdir - file_operations::iterate_shared | 
|  | * | 
|  | * Use non sorted enumeration. | 
|  | * We have an example of broken volume where sorted enumeration | 
|  | * counts each name twice. | 
|  | */ | 
|  | static int ntfs_readdir(struct file *file, struct dir_context *ctx) | 
|  | { | 
|  | const struct INDEX_ROOT *root; | 
|  | u64 vbo; | 
|  | size_t bit; | 
|  | loff_t eod; | 
|  | int err = 0; | 
|  | struct inode *dir = file_inode(file); | 
|  | struct ntfs_inode *ni = ntfs_i(dir); | 
|  | struct super_block *sb = dir->i_sb; | 
|  | struct ntfs_sb_info *sbi = sb->s_fs_info; | 
|  | loff_t i_size = i_size_read(dir); | 
|  | u32 pos = ctx->pos; | 
|  | u8 *name = NULL; | 
|  | struct indx_node *node = NULL; | 
|  | u8 index_bits = ni->dir.index_bits; | 
|  |  | 
|  | /* Name is a buffer of PATH_MAX length. */ | 
|  | static_assert(NTFS_NAME_LEN * 4 < PATH_MAX); | 
|  |  | 
|  | eod = i_size + sbi->record_size; | 
|  |  | 
|  | if (pos >= eod) | 
|  | return 0; | 
|  |  | 
|  | if (!dir_emit_dots(file, ctx)) | 
|  | return 0; | 
|  |  | 
|  | /* Allocate PATH_MAX bytes. */ | 
|  | name = __getname(); | 
|  | if (!name) | 
|  | return -ENOMEM; | 
|  |  | 
|  | if (!ni->mi_loaded && ni->attr_list.size) { | 
|  | /* | 
|  | * Directory inode is locked for read. | 
|  | * Load all subrecords to avoid 'write' access to 'ni' during | 
|  | * directory reading. | 
|  | */ | 
|  | ni_lock(ni); | 
|  | if (!ni->mi_loaded && ni->attr_list.size) { | 
|  | err = ni_load_all_mi(ni); | 
|  | if (!err) | 
|  | ni->mi_loaded = true; | 
|  | } | 
|  | ni_unlock(ni); | 
|  | if (err) | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | root = indx_get_root(&ni->dir, ni, NULL, NULL); | 
|  | if (!root) { | 
|  | err = -EINVAL; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | if (pos >= sbi->record_size) { | 
|  | bit = (pos - sbi->record_size) >> index_bits; | 
|  | } else { | 
|  | err = ntfs_read_hdr(sbi, ni, &root->ihdr, 0, pos, name, ctx); | 
|  | if (err) | 
|  | goto out; | 
|  | bit = 0; | 
|  | } | 
|  |  | 
|  | if (!i_size) { | 
|  | ctx->pos = eod; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | for (;;) { | 
|  | vbo = (u64)bit << index_bits; | 
|  | if (vbo >= i_size) { | 
|  | ctx->pos = eod; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | err = indx_used_bit(&ni->dir, ni, &bit); | 
|  | if (err) | 
|  | goto out; | 
|  |  | 
|  | if (bit == MINUS_ONE_T) { | 
|  | ctx->pos = eod; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | vbo = (u64)bit << index_bits; | 
|  | if (vbo >= i_size) { | 
|  | ntfs_inode_err(dir, "Looks like your dir is corrupt"); | 
|  | err = -EINVAL; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | err = indx_read(&ni->dir, ni, bit << ni->dir.idx2vbn_bits, | 
|  | &node); | 
|  | if (err) | 
|  | goto out; | 
|  |  | 
|  | err = ntfs_read_hdr(sbi, ni, &node->index->ihdr, | 
|  | vbo + sbi->record_size, pos, name, ctx); | 
|  | if (err) | 
|  | goto out; | 
|  |  | 
|  | bit += 1; | 
|  | } | 
|  |  | 
|  | out: | 
|  |  | 
|  | __putname(name); | 
|  | put_indx_node(node); | 
|  |  | 
|  | if (err == -ENOENT) { | 
|  | err = 0; | 
|  | ctx->pos = pos; | 
|  | } | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static int ntfs_dir_count(struct inode *dir, bool *is_empty, size_t *dirs, | 
|  | size_t *files) | 
|  | { | 
|  | int err = 0; | 
|  | struct ntfs_inode *ni = ntfs_i(dir); | 
|  | struct NTFS_DE *e = NULL; | 
|  | struct INDEX_ROOT *root; | 
|  | struct INDEX_HDR *hdr; | 
|  | const struct ATTR_FILE_NAME *fname; | 
|  | u32 e_size, off, end; | 
|  | u64 vbo = 0; | 
|  | size_t drs = 0, fles = 0, bit = 0; | 
|  | loff_t i_size = ni->vfs_inode.i_size; | 
|  | struct indx_node *node = NULL; | 
|  | u8 index_bits = ni->dir.index_bits; | 
|  |  | 
|  | if (is_empty) | 
|  | *is_empty = true; | 
|  |  | 
|  | root = indx_get_root(&ni->dir, ni, NULL, NULL); | 
|  | if (!root) | 
|  | return -EINVAL; | 
|  |  | 
|  | hdr = &root->ihdr; | 
|  |  | 
|  | for (;;) { | 
|  | end = le32_to_cpu(hdr->used); | 
|  | off = le32_to_cpu(hdr->de_off); | 
|  |  | 
|  | for (; off + sizeof(struct NTFS_DE) <= end; off += e_size) { | 
|  | e = Add2Ptr(hdr, off); | 
|  | e_size = le16_to_cpu(e->size); | 
|  | if (e_size < sizeof(struct NTFS_DE) || | 
|  | off + e_size > end) | 
|  | break; | 
|  |  | 
|  | if (de_is_last(e)) | 
|  | break; | 
|  |  | 
|  | fname = de_get_fname(e); | 
|  | if (!fname) | 
|  | continue; | 
|  |  | 
|  | if (fname->type == FILE_NAME_DOS) | 
|  | continue; | 
|  |  | 
|  | if (is_empty) { | 
|  | *is_empty = false; | 
|  | if (!dirs && !files) | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | if (fname->dup.fa & FILE_ATTRIBUTE_DIRECTORY) | 
|  | drs += 1; | 
|  | else | 
|  | fles += 1; | 
|  | } | 
|  |  | 
|  | if (vbo >= i_size) | 
|  | goto out; | 
|  |  | 
|  | err = indx_used_bit(&ni->dir, ni, &bit); | 
|  | if (err) | 
|  | goto out; | 
|  |  | 
|  | if (bit == MINUS_ONE_T) | 
|  | goto out; | 
|  |  | 
|  | vbo = (u64)bit << index_bits; | 
|  | if (vbo >= i_size) | 
|  | goto out; | 
|  |  | 
|  | err = indx_read(&ni->dir, ni, bit << ni->dir.idx2vbn_bits, | 
|  | &node); | 
|  | if (err) | 
|  | goto out; | 
|  |  | 
|  | hdr = &node->index->ihdr; | 
|  | bit += 1; | 
|  | vbo = (u64)bit << ni->dir.idx2vbn_bits; | 
|  | } | 
|  |  | 
|  | out: | 
|  | put_indx_node(node); | 
|  | if (dirs) | 
|  | *dirs = drs; | 
|  | if (files) | 
|  | *files = fles; | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | bool dir_is_empty(struct inode *dir) | 
|  | { | 
|  | bool is_empty = false; | 
|  |  | 
|  | ntfs_dir_count(dir, &is_empty, NULL, NULL); | 
|  |  | 
|  | return is_empty; | 
|  | } | 
|  |  | 
|  | // clang-format off | 
|  | const struct file_operations ntfs_dir_operations = { | 
|  | .llseek		= generic_file_llseek, | 
|  | .read		= generic_read_dir, | 
|  | .iterate_shared	= ntfs_readdir, | 
|  | .fsync		= generic_file_fsync, | 
|  | .open		= ntfs_file_open, | 
|  | }; | 
|  | // clang-format on |