diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-02 12:17:24 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-02 12:17:24 -0700 |
| commit | 3f1fe48a36b0b6722dc3fd421d93512bac138e9a (patch) | |
| tree | b767d7f6e26bc64334d3f14f8422d187239dc45d /fs/ntfs/namei.c | |
| download | linux-stable-3f1fe48a36b0b6722dc3fd421d93512bac138e9a.tar.gz linux-stable-3f1fe48a36b0b6722dc3fd421d93512bac138e9a.zip | |
Merge tag 'io_uring-7.3-20261002' of git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linuxgrafted
Pull io_uring fixes from Jens Axboe:
- Fix a task_work add use-after-free with SQPOLL.
The sqpoll thread could pop and complete the last request while
io_req_normal_work_add() was still looking at them after the mpscq
push.
Use the same approach as DEFER_TASKRUN to protect from that, holding
an RCU read lock across the add, and have exit wait for an RCU grace
period for SQPOLL rings as well.
- CQE32 ring fixes: correct the free entry check for 32b CQEs, zero the
big_cqe for aux CQEs, and only post the dummy skip CQE on CQE_MIXED
rings
- Mark the source filter table as COW when cloning bpf filters, so
registering another filter on the source doesn't modify the shared
table in place
- Initialize the task context before running the BPF loop
- Requeue zcrx multishot receives stopped by a local resource
- End a TX_TIMESTAMP multishot cmd when the CQ is full (lollipopkit)
* tag 'io_uring-7.3-20261002' of git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux:
io_uring: fix task_work add use-after-free with SQPOLL
io_uring/cmd_net: end TX_TIMESTAMP multishot when the CQ is full
io_uring/zcrx: requeue multishot receives stopped by a local resource
io_uring: initialize task context before running the BPF loop
io_uring: zero big_cqe for aux CQEs on CQE32 rings
io_uring: fix free entry check for 32b CQEs on CQE32 rings
io_uring: only post the dummy skip CQE on CQE_MIXED rings
io_uring/bpf_filter: mark source as COW when cloning filters
Diffstat (limited to 'fs/ntfs/namei.c')
| -rw-r--r-- | fs/ntfs/namei.c | 1678 |
1 files changed, 1678 insertions, 0 deletions
diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c new file mode 100644 index 000000000..fdf52fac4 --- /dev/null +++ b/fs/ntfs/namei.c @@ -0,0 +1,1678 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NTFS kernel directory inode operations. + * + * Copyright (c) 2001-2006 Anton Altaparmakov + * Copyright (c) 2025 LG Electronics Co., Ltd. + */ + +#include <linux/exportfs.h> +#include <linux/iversion.h> + +#include "ntfs.h" +#include "time.h" +#include "index.h" +#include "reparse.h" +#include "object_id.h" +#include "ea.h" + +static const __le16 aux_name_le[3] = { + cpu_to_le16('A'), cpu_to_le16('U'), cpu_to_le16('X') +}; + +static const __le16 con_name_le[3] = { + cpu_to_le16('C'), cpu_to_le16('O'), cpu_to_le16('N') +}; + +static const __le16 com_name_le[3] = { + cpu_to_le16('C'), cpu_to_le16('O'), cpu_to_le16('M') +}; + +static const __le16 lpt_name_le[3] = { + cpu_to_le16('L'), cpu_to_le16('P'), cpu_to_le16('T') +}; + +static const __le16 nul_name_le[3] = { + cpu_to_le16('N'), cpu_to_le16('U'), cpu_to_le16('L') +}; + +static const __le16 prn_name_le[3] = { + cpu_to_le16('P'), cpu_to_le16('R'), cpu_to_le16('N') +}; + +static inline int ntfs_check_bad_char(const __le16 *wc, unsigned int wc_len) +{ + int i; + + for (i = 0; i < wc_len; i++) { + u16 c = le16_to_cpu(wc[i]); + + if (c < 0x0020 || + c == 0x0022 || c == 0x002A || c == 0x002F || + c == 0x003A || c == 0x003C || c == 0x003E || + c == 0x003F || c == 0x005C || c == 0x007C) + return -EINVAL; + } + + return 0; +} + +static int ntfs_check_bad_windows_name(struct ntfs_volume *vol, + const __le16 *wc, + unsigned int wc_len) +{ + if (!NVolCheckWindowsNames(vol)) + return 0; + + if (ntfs_check_bad_char(wc, wc_len)) + return -EINVAL; + + /* Check for trailing space or dot. */ + if (wc_len > 0 && + (wc[wc_len - 1] == cpu_to_le16(' ') || + wc[wc_len - 1] == cpu_to_le16('.'))) + return -EINVAL; + + if (wc_len == 3 || (wc_len > 3 && wc[3] == cpu_to_le16('.'))) { + __le16 *upcase = vol->upcase; + u32 size = vol->upcase_len; + + if (ntfs_are_names_equal(wc, 3, aux_name_le, 3, IGNORE_CASE, upcase, size) || + ntfs_are_names_equal(wc, 3, con_name_le, 3, IGNORE_CASE, upcase, size) || + ntfs_are_names_equal(wc, 3, nul_name_le, 3, IGNORE_CASE, upcase, size) || + ntfs_are_names_equal(wc, 3, prn_name_le, 3, IGNORE_CASE, upcase, size)) + return -EINVAL; + } + + if (wc_len == 4 || (wc_len > 4 && wc[4] == cpu_to_le16('.'))) { + __le16 *upcase = vol->upcase; + u32 size = vol->upcase_len, port; + + if (ntfs_are_names_equal(wc, 3, com_name_le, 3, IGNORE_CASE, upcase, size) || + ntfs_are_names_equal(wc, 3, lpt_name_le, 3, IGNORE_CASE, upcase, size)) { + port = le16_to_cpu(wc[3]); + if (port >= '1' && port <= '9') + return -EINVAL; + } + } + return 0; +} + +/* + * ntfs_lookup - find the inode represented by a dentry in a directory inode + * @dir_ino: directory inode in which to look for the inode + * @dent: dentry representing the inode to look for + * @flags: lookup flags + * + * In short, ntfs_lookup() looks for the inode represented by the dentry @dent + * in the directory inode @dir_ino and if found attaches the inode to the + * dentry @dent. + * + * In more detail, the dentry @dent specifies which inode to look for by + * supplying the name of the inode in @dent->d_name.name. ntfs_lookup() + * converts the name to Unicode and walks the contents of the directory inode + * @dir_ino looking for the converted Unicode name. If the name is found in the + * directory, the corresponding inode is loaded by calling ntfs_iget() on its + * inode number and the inode is associated with the dentry @dent via a call to + * d_splice_alias(). + * + * If the name is not found in the directory, a NULL inode is inserted into the + * dentry @dent via a call to d_add(). The dentry is then termed a negative + * dentry. + * + * Only if an actual error occurs, do we return an error via ERR_PTR(). + * + * In order to handle the case insensitivity issues of NTFS with regards to the + * dcache and the dcache requiring only one dentry per directory, we deal with + * dentry aliases that only differ in case in ->ntfs_lookup() while maintaining + * a case sensitive dcache. This means that we get the full benefit of dcache + * speed when the file/directory is looked up with the same case as returned by + * ->ntfs_readdir() but that a lookup for any other case (or for the short file + * name) will not find anything in dcache and will enter ->ntfs_lookup() + * instead, where we search the directory for a fully matching file name + * (including case) and if that is not found, we search for a file name that + * matches with different case and if that has non-POSIX semantics we return + * that. We actually do only one search (case sensitive) and keep tabs on + * whether we have found a case insensitive match in the process. + * + * To simplify matters for us, we do not treat the short vs long filenames as + * two hard links but instead if the lookup matches a short filename, we + * return the dentry for the corresponding long filename instead. + * + * There are three cases we need to distinguish here: + * + * 1) @dent perfectly matches (i.e. including case) a directory entry with a + * file name in the WIN32 or POSIX namespaces. In this case + * ntfs_lookup_inode_by_name() will return with name set to NULL and we + * just d_splice_alias() @dent. + * 2) @dent matches (not including case) a directory entry with a file name in + * the WIN32 namespace. In this case ntfs_lookup_inode_by_name() will return + * with name set to point to a kmalloc()ed ntfs_name structure containing + * the properly cased little endian Unicode name. We convert the name to the + * current NLS code page, search if a dentry with this name already exists + * and if so return that instead of @dent. At this point things are + * complicated by the possibility of 'disconnected' dentries due to NFS + * which we deal with appropriately (see the code comments). The VFS will + * then destroy the old @dent and use the one we returned. If a dentry is + * not found, we allocate a new one, d_splice_alias() it, and return it as + * above. + * 3) @dent matches either perfectly or not (i.e. we don't care about case) a + * directory entry with a file name in the DOS namespace. In this case + * ntfs_lookup_inode_by_name() will return with name set to point to a + * kmalloc()ed ntfs_name structure containing the mft reference (cpu endian) + * of the inode. We use the mft reference to read the inode and to find the + * file name in the WIN32 namespace corresponding to the matched short file + * name. We then convert the name to the current NLS code page, and proceed + * searching for a dentry with this name, etc, as in case 2), above. + * + * Locking: Caller must hold i_mutex on the directory. + */ +static struct dentry *ntfs_lookup(struct inode *dir_ino, struct dentry *dent, + unsigned int flags) +{ + struct ntfs_volume *vol = NTFS_SB(dir_ino->i_sb); + struct inode *dent_inode; + __le16 *uname; + struct ntfs_name *name = NULL; + u64 mref; + unsigned long dent_ino; + int uname_len; + + ntfs_debug("Looking up %pd in directory inode 0x%llx.", + dent, NTFS_I(dir_ino)->mft_no); + /* Convert the name of the dentry to Unicode. */ + uname_len = ntfs_nlstoucs(vol, dent->d_name.name, dent->d_name.len, + &uname, NTFS_MAX_NAME_LEN); + if (uname_len < 0) { + if (uname_len != -ENAMETOOLONG) + ntfs_debug("Failed to convert name to Unicode."); + return ERR_PTR(uname_len); + } + mutex_lock(&NTFS_I(dir_ino)->mrec_lock); + mref = ntfs_lookup_inode_by_name(NTFS_I(dir_ino), uname, uname_len, + &name); + mutex_unlock(&NTFS_I(dir_ino)->mrec_lock); + kmem_cache_free(ntfs_name_cache, uname); + if (!IS_ERR_MREF(mref)) { + dent_ino = MREF(mref); + ntfs_debug("Found inode 0x%lx. Calling ntfs_iget.", dent_ino); + dent_inode = ntfs_iget(vol->sb, dent_ino); + if (!IS_ERR(dent_inode)) { + /* Consistency check. */ + if (MSEQNO(mref) == NTFS_I(dent_inode)->seq_no || + dent_ino == FILE_MFT) { + /* Perfect WIN32/POSIX match. -- Case 1. */ + if (!name) { + ntfs_debug("Done. (Case 1.)"); + return d_splice_alias(dent_inode, dent); + } + /* + * We are too indented. Handle imperfect + * matches and short file names further below. + */ + goto handle_name; + } + ntfs_error(vol->sb, + "Found stale reference to inode 0x%lx (reference sequence number = 0x%x, inode sequence number = 0x%x), returning -EIO. Run chkdsk.", + dent_ino, MSEQNO(mref), + NTFS_I(dent_inode)->seq_no); + iput(dent_inode); + dent_inode = ERR_PTR(-EIO); + } else + ntfs_error(vol->sb, "ntfs_iget(0x%lx) failed with error code %li.", + dent_ino, PTR_ERR(dent_inode)); + kfree(name); + /* Return the error code. */ + return ERR_CAST(dent_inode); + } + kfree(name); + /* It is guaranteed that @name is no longer allocated at this point. */ + if (MREF_ERR(mref) == -ENOENT) { + ntfs_debug("Entry was not found, adding negative dentry."); + /* The dcache will handle negative entries. */ + ntfs_debug("Done."); + return d_splice_alias(NULL, dent); + } + ntfs_error(vol->sb, "ntfs_lookup_ino_by_name() failed with error code %i.", + -MREF_ERR(mref)); + return ERR_PTR(MREF_ERR(mref)); +handle_name: + { + struct mft_record *m; + struct ntfs_attr_search_ctx *ctx; + struct ntfs_inode *ni = NTFS_I(dent_inode); + int err; + struct qstr nls_name; + + nls_name.name = NULL; + if (name->type != FILE_NAME_DOS) { /* Case 2. */ + ntfs_debug("Case 2."); + nls_name.len = (unsigned int)ntfs_ucstonls(vol, + (__le16 *)&name->name, name->len, + (unsigned char **)&nls_name.name, 0); + kfree(name); + } else /* if (name->type == FILE_NAME_DOS) */ { /* Case 3. */ + struct file_name_attr *fn; + + ntfs_debug("Case 3."); + kfree(name); + + /* Find the WIN32 name corresponding to the matched DOS name. */ + ni = NTFS_I(dent_inode); + m = map_mft_record(ni); + if (IS_ERR(m)) { + err = PTR_ERR(m); + m = NULL; + ctx = NULL; + goto err_out; + } + ctx = ntfs_attr_get_search_ctx(ni, m); + if (unlikely(!ctx)) { + err = -ENOMEM; + goto err_out; + } + do { + struct attr_record *a; + + err = ntfs_attr_lookup(AT_FILE_NAME, NULL, 0, 0, 0, + NULL, 0, ctx); + if (unlikely(err)) { + ntfs_error(vol->sb, + "Inode corrupt: No WIN32 namespace counterpart to DOS file name. Run chkdsk."); + if (err == -ENOENT) + err = -EIO; + goto err_out; + } + /* Consistency checks. */ + a = ctx->attr; + if (a->non_resident || a->flags) + goto eio_err_out; + fn = (struct file_name_attr *)((u8 *)ctx->attr + le16_to_cpu( + ctx->attr->data.resident.value_offset)); + } while (fn->file_name_type != FILE_NAME_WIN32); + + /* Convert the found WIN32 name to current NLS code page. */ + nls_name.len = (unsigned int)ntfs_ucstonls(vol, + (__le16 *)&fn->file_name, fn->file_name_length, + (unsigned char **)&nls_name.name, 0); + + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(ni); + } + m = NULL; + ctx = NULL; + + /* Check if a conversion error occurred. */ + if ((int)nls_name.len < 0) { + err = (int)nls_name.len; + goto err_out; + } + nls_name.hash = full_name_hash(dent, nls_name.name, nls_name.len); + + dent = d_add_ci(dent, dent_inode, &nls_name); + kfree(nls_name.name); + return dent; + +eio_err_out: + ntfs_error(vol->sb, "Illegal file name attribute. Run chkdsk."); + err = -EIO; +err_out: + if (ctx) + ntfs_attr_put_search_ctx(ctx); + if (m) + unmap_mft_record(ni); + iput(dent_inode); + ntfs_error(vol->sb, "Failed, returning error code %i.", err); + return ERR_PTR(err); + } +} + +static int ntfs_sd_add_everyone(struct ntfs_inode *ni) +{ + struct security_descriptor_relative *sd; + struct ntfs_acl *acl; + struct ntfs_ace *ace; + struct ntfs_sid *sid; + int ret, sd_len; + + /* Create SECURITY_DESCRIPTOR attribute (everyone has full access). */ + /* + * Calculate security descriptor length. We have 2 sub-authorities in + * owner and group SIDs, So add 8 bytes to every SID. + */ + sd_len = sizeof(struct security_descriptor_relative) + 2 * + (sizeof(struct ntfs_sid) + 8) + sizeof(struct ntfs_acl) + + sizeof(struct ntfs_ace) + 4; + sd = kzalloc(sd_len, GFP_NOFS); + if (!sd) + return -ENOMEM; + + sd->revision = 1; + sd->control = SE_DACL_PRESENT | SE_SELF_RELATIVE; + + sid = (struct ntfs_sid *)((u8 *)sd + sizeof(struct security_descriptor_relative)); + sid->revision = 1; + sid->sub_authority_count = 2; + sid->sub_authority[0] = cpu_to_le32(SECURITY_BUILTIN_DOMAIN_RID); + sid->sub_authority[1] = cpu_to_le32(DOMAIN_ALIAS_RID_ADMINS); + sid->identifier_authority.value[5] = 5; + sd->owner = cpu_to_le32((u8 *)sid - (u8 *)sd); + + sid = (struct ntfs_sid *)((u8 *)sid + sizeof(struct ntfs_sid) + 8); + sid->revision = 1; + sid->sub_authority_count = 2; + sid->sub_authority[0] = cpu_to_le32(SECURITY_BUILTIN_DOMAIN_RID); + sid->sub_authority[1] = cpu_to_le32(DOMAIN_ALIAS_RID_ADMINS); + sid->identifier_authority.value[5] = 5; + sd->group = cpu_to_le32((u8 *)sid - (u8 *)sd); + + acl = (struct ntfs_acl *)((u8 *)sid + sizeof(struct ntfs_sid) + 8); + acl->revision = 2; + acl->size = cpu_to_le16(sizeof(struct ntfs_acl) + sizeof(struct ntfs_ace) + 4); + acl->ace_count = cpu_to_le16(1); + sd->dacl = cpu_to_le32((u8 *)acl - (u8 *)sd); + + ace = (struct ntfs_ace *)((u8 *)acl + sizeof(struct ntfs_acl)); + ace->type = ACCESS_ALLOWED_ACE_TYPE; + ace->flags = OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE; + ace->size = cpu_to_le16(sizeof(struct ntfs_ace) + 4); + ace->mask = cpu_to_le32(0x1f01ff); + ace->sid.revision = 1; + ace->sid.sub_authority_count = 1; + ace->sid.sub_authority[0] = 0; + ace->sid.identifier_authority.value[5] = 1; + + ret = ntfs_attr_add(ni, AT_SECURITY_DESCRIPTOR, AT_UNNAMED, 0, (u8 *)sd, + sd_len); + if (ret) + ntfs_error(ni->vol->sb, "Failed to add SECURITY_DESCRIPTOR\n"); + + kfree(sd); + return ret; +} + +static struct ntfs_inode *__ntfs_create(struct mnt_idmap *idmap, struct inode *dir, + __le16 *name, u8 name_len, mode_t mode, dev_t dev, + const char *target, int target_len) +{ + struct ntfs_inode *dir_ni = NTFS_I(dir); + struct ntfs_volume *vol = dir_ni->vol; + struct ntfs_inode *ni; + bool rollback_data = false, rollback_sd = false, rollback_reparse = false; + struct file_name_attr *fn = NULL; + struct standard_information *si = NULL; + int err = 0, fn_len, si_len; + struct inode *vi; + struct mft_record *ni_mrec, *dni_mrec; + struct super_block *sb = dir_ni->vol->sb; + __le64 parent_mft_ref; + u64 child_mft_ref; + __le16 ea_size; + + vi = new_inode(vol->sb); + if (!vi) + return ERR_PTR(-ENOMEM); + + ntfs_init_big_inode(vi); + ni = NTFS_I(vi); + ni->vol = dir_ni->vol; + ni->name_len = 0; + ni->name = NULL; + + /* + * Set the appropriate mode, attribute type, and name. For + * directories, also setup the index values to the defaults. + */ + if (S_ISDIR(mode)) { + NInoSetMstProtected(ni); + ni->itype.index.block_size = 4096; + ni->itype.index.block_size_bits = ntfs_ffs(4096) - 1; + ni->itype.index.collation_rule = COLLATION_FILE_NAME; + if (vol->cluster_size <= ni->itype.index.block_size) { + ni->itype.index.vcn_size = vol->cluster_size; + ni->itype.index.vcn_size_bits = + vol->cluster_size_bits; + } else { + ni->itype.index.vcn_size = vol->sector_size; + ni->itype.index.vcn_size_bits = + vol->sector_size_bits; + } + } + + if (IS_RDONLY(vi)) + mode &= ~0222; + + inode_init_owner(idmap, vi, dir, mode); + + mode = vi->i_mode; + +#ifdef CONFIG_NTFS_FS_POSIX_ACL + if (!S_ISLNK(mode) && (sb->s_flags & SB_POSIXACL)) { + err = ntfs_init_acl(idmap, vi, dir); + if (err) + goto err_out; + } else +#endif + { + vi->i_flags |= S_NOSEC; + } + + if (uid_valid(vol->uid)) + vi->i_uid = vol->uid; + + if (gid_valid(vol->gid)) + vi->i_gid = vol->gid; + + /* + * Set the file size to 0, the ntfs inode sizes are set to 0 by + * the call to ntfs_init_big_inode() below. + */ + vi->i_size = 0; + vi->i_blocks = 0; + + inode_inc_iversion(vi); + + simple_inode_init_ts(vi); + ni->i_crtime = inode_get_ctime(vi); + + inode_set_mtime_to_ts(dir, ni->i_crtime); + inode_set_ctime_to_ts(dir, ni->i_crtime); + mark_inode_dirty(dir); + + err = ntfs_mft_record_alloc(dir_ni->vol, mode, &ni, NULL, + &ni_mrec, -1); + if (err) { + iput(vi); + return ERR_PTR(err); + } + + /* + * Prevent iget and writeback from finding this inode. + * Caller must call d_instantiate_new instead of d_instantiate. + */ + spin_lock(&vi->i_lock); + inode_state_set(vi, I_NEW | I_CREATING); + spin_unlock(&vi->i_lock); + + /* Add the inode to the inode hash for the superblock. */ + vi->i_ino = (unsigned long)ni->mft_no; + inode_set_iversion(vi, 1); + insert_inode_hash(vi); + + mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); + mutex_lock_nested(&dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT); + if (NInoBeingDeleted(dir_ni)) { + err = -ENOENT; + goto err_out; + } + + dni_mrec = map_mft_record(dir_ni); + if (IS_ERR(dni_mrec)) { + ntfs_error(dir_ni->vol->sb, "failed to map mft record for file 0x%llx.\n", + dir_ni->mft_no); + err = -EIO; + goto err_out; + } + parent_mft_ref = MK_LE_MREF(dir_ni->mft_no, + le16_to_cpu(dni_mrec->sequence_number)); + unmap_mft_record(dir_ni); + + /* + * Create STANDARD_INFORMATION attribute. Write STANDARD_INFORMATION + * version 1.2, windows will upgrade it to version 3 if needed. + */ + si_len = offsetof(struct standard_information, file_attributes) + + sizeof(__le32) + 12; + si = kzalloc(si_len, GFP_NOFS); + if (!si) { + err = -ENOMEM; + goto err_out; + } + + si->creation_time = si->last_data_change_time = utc2ntfs(ni->i_crtime); + si->last_mft_change_time = si->last_access_time = si->creation_time; + + if (!S_ISREG(mode) && !S_ISDIR(mode)) + si->file_attributes = FILE_ATTR_SYSTEM; + + /* Add STANDARD_INFORMATION to inode. */ + err = ntfs_attr_add(ni, AT_STANDARD_INFORMATION, AT_UNNAMED, 0, (u8 *)si, + si_len); + if (err) { + ntfs_error(sb, "Failed to add STANDARD_INFORMATION attribute.\n"); + goto err_out; + } + + err = ntfs_sd_add_everyone(ni); + if (err) + goto err_out; + rollback_sd = true; + + if (S_ISDIR(mode)) { + struct index_root *ir = NULL; + struct index_entry *ie; + int ir_len, index_len; + + /* Create struct index_root attribute. */ + index_len = sizeof(struct index_header) + sizeof(struct index_entry_header); + ir_len = offsetof(struct index_root, index) + index_len; + ir = kzalloc(ir_len, GFP_NOFS); + if (!ir) { + err = -ENOMEM; + goto err_out; + } + ir->type = AT_FILE_NAME; + ir->collation_rule = COLLATION_FILE_NAME; + ir->index_block_size = cpu_to_le32(ni->vol->index_record_size); + if (ni->vol->cluster_size <= ni->vol->index_record_size) + ir->clusters_per_index_block = + NTFS_B_TO_CLU(vol, ni->vol->index_record_size); + else + ir->clusters_per_index_block = + ni->vol->index_record_size >> ni->vol->sector_size_bits; + ir->index.entries_offset = cpu_to_le32(sizeof(struct index_header)); + ir->index.index_length = cpu_to_le32(index_len); + ir->index.allocated_size = cpu_to_le32(index_len); + ie = (struct index_entry *)((u8 *)ir + sizeof(struct index_root)); + ie->length = cpu_to_le16(sizeof(struct index_entry_header)); + ie->key_length = 0; + ie->flags = INDEX_ENTRY_END; + + /* Add struct index_root attribute to inode. */ + err = ntfs_attr_add(ni, AT_INDEX_ROOT, I30, 4, (u8 *)ir, ir_len); + if (err) { + kfree(ir); + ntfs_error(vi->i_sb, "Failed to add struct index_root attribute.\n"); + goto err_out; + } + kfree(ir); + err = ntfs_attr_open(ni, AT_INDEX_ROOT, I30, 4); + if (err) + goto err_out; + } else { + /* Add DATA attribute to inode. */ + err = ntfs_attr_add(ni, AT_DATA, AT_UNNAMED, 0, NULL, 0); + if (err) { + ntfs_error(dir_ni->vol->sb, "Failed to add DATA attribute.\n"); + goto err_out; + } + rollback_data = true; + + err = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0); + if (err) + goto err_out; + + if (S_ISLNK(mode)) { + 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) || + S_ISFIFO(mode)) { + si->file_attributes = FILE_ATTRIBUTE_RECALL_ON_OPEN; + ni->flags = FILE_ATTRIBUTE_RECALL_ON_OPEN; + err = ntfs_reparse_set_wsl_not_symlink(ni, mode); + if (!err) + rollback_reparse = true; + } + if (err) + goto err_out; + } + + err = ntfs_ea_set_wsl_inode(vi, dev, &ea_size, + NTFS_EA_UID | NTFS_EA_GID | NTFS_EA_MODE); + if (err) + goto err_out; + + /* Create FILE_NAME attribute. */ + fn_len = sizeof(struct file_name_attr) + name_len * sizeof(__le16); + fn = kzalloc(fn_len, GFP_NOFS); + if (!fn) { + err = -ENOMEM; + goto err_out; + } + + fn->file_attributes |= ni->flags; + fn->parent_directory = parent_mft_ref; + fn->file_name_length = name_len; + fn->file_name_type = FILE_NAME_POSIX; + fn->type.ea.packed_ea_size = ea_size; + if (S_ISDIR(mode)) { + fn->file_attributes = FILE_ATTR_DUP_FILE_NAME_INDEX_PRESENT; + fn->allocated_size = fn->data_size = 0; + } else { + fn->data_size = cpu_to_le64(ni->data_size); + fn->allocated_size = cpu_to_le64(ni->allocated_size); + } + if (!S_ISREG(mode) && !S_ISDIR(mode)) { + fn->file_attributes = FILE_ATTR_SYSTEM; + if (rollback_reparse) + fn->file_attributes |= FILE_ATTR_REPARSE_POINT; + } + if (NVolHideDotFiles(vol) && name_len > 0 && name[0] == cpu_to_le16('.')) + fn->file_attributes |= FILE_ATTR_HIDDEN; + fn->creation_time = fn->last_data_change_time = utc2ntfs(ni->i_crtime); + fn->last_mft_change_time = fn->last_access_time = fn->creation_time; + memcpy(fn->file_name, name, name_len * sizeof(__le16)); + + /* Add FILE_NAME attribute to inode. */ + err = ntfs_attr_add(ni, AT_FILE_NAME, AT_UNNAMED, 0, (u8 *)fn, fn_len); + if (err) { + ntfs_error(sb, "Failed to add FILE_NAME attribute.\n"); + goto err_out; + } + + child_mft_ref = MK_MREF(ni->mft_no, + le16_to_cpu(ni_mrec->sequence_number)); + /* Set hard links count and directory flag. */ + ni_mrec->link_count = cpu_to_le16(1); + mark_mft_record_dirty(ni); + + /* Add FILE_NAME attribute to index. */ + err = ntfs_index_add_filename(dir_ni, fn, child_mft_ref); + if (err) { + ntfs_debug("Failed to add entry to the index"); + goto err_out; + } + + unmap_mft_record(ni); + mutex_unlock(&dir_ni->mrec_lock); + mutex_unlock(&ni->mrec_lock); + + ni->flags = fn->file_attributes | + (ni->flags & FILE_ATTRIBUTE_RECALL_ON_OPEN); + /* Set the sequence number. */ + vi->i_generation = ni->seq_no; + set_nlink(vi, 1); + ntfs_set_vfs_operations(vi, mode, dev); + + /* Done! */ + kfree(fn); + kfree(si); + ntfs_debug("Done.\n"); + return ni; + +err_out: + if (rollback_sd) + ntfs_attr_remove(ni, AT_SECURITY_DESCRIPTOR, AT_UNNAMED, 0); + + if (rollback_data) + ntfs_attr_remove(ni, AT_DATA, AT_UNNAMED, 0); + + if (rollback_reparse) + ntfs_delete_reparse_index(ni); + /* + * Free extent MFT records (should not exist any with current + * ntfs_create implementation, but for any case if something will be + * changed in the future). + */ + while (ni->nr_extents != 0) { + int err2; + + err2 = ntfs_mft_record_free(ni->vol, *(ni->ext.extent_ntfs_inos)); + if (err2) + ntfs_error(sb, + "Failed to free extent MFT record. Leaving inconsistent metadata.\n"); + ntfs_inode_close(*(ni->ext.extent_ntfs_inos)); + } + if (ntfs_mft_record_free(ni->vol, ni)) + ntfs_error(sb, + "Failed to free MFT record. Leaving inconsistent metadata. Run chkdsk.\n"); + unmap_mft_record(ni); + kfree(fn); + kfree(si); + + mutex_unlock(&dir_ni->mrec_lock); + mutex_unlock(&ni->mrec_lock); + + remove_inode_hash(vi); + discard_new_inode(vi); + return ERR_PTR(err); +} + +static int ntfs_create(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode) +{ + struct ntfs_volume *vol = NTFS_SB(dir->i_sb); + struct ntfs_inode *ni; + __le16 *uname; + int uname_len, err; + + if (NVolShutdown(vol)) + return -EIO; + + uname_len = ntfs_nlstoucs(vol, dentry->d_name.name, dentry->d_name.len, + &uname, NTFS_MAX_NAME_LEN); + if (uname_len < 0) { + if (uname_len != -ENAMETOOLONG) + ntfs_error(vol->sb, "Failed to convert name to unicode."); + return uname_len; + } + + err = ntfs_check_bad_windows_name(vol, uname, uname_len); + if (err) { + kmem_cache_free(ntfs_name_cache, uname); + return err; + } + + if (!(vol->vol_flags & VOLUME_IS_DIRTY)) + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + + ni = __ntfs_create(idmap, dir, uname, uname_len, S_IFREG | mode, 0, NULL, 0); + kmem_cache_free(ntfs_name_cache, uname); + if (IS_ERR(ni)) + return PTR_ERR(ni); + + d_instantiate_new(dentry, VFS_I(ni)); + + return 0; +} + +static int ntfs_check_unlinkable_dir(struct ntfs_attr_search_ctx *ctx, struct file_name_attr *fn) +{ + int link_count; + int ret; + struct ntfs_inode *ni = ctx->base_ntfs_ino ? ctx->base_ntfs_ino : ctx->ntfs_ino; + struct mft_record *ni_mrec = ctx->base_mrec ? ctx->base_mrec : ctx->mrec; + + ret = ntfs_check_empty_dir(ni, ni_mrec); + if (!ret || ret != -ENOTEMPTY) + return ret; + + link_count = le16_to_cpu(ni_mrec->link_count); + /* + * Directory is non-empty, so we can unlink only if there is more than + * one "real" hard link, i.e. links aren't different DOS and WIN32 names + */ + if ((link_count == 1) || + (link_count == 2 && fn->file_name_type == FILE_NAME_DOS)) { + ret = -ENOTEMPTY; + ntfs_debug("Non-empty directory without hard links\n"); + goto no_hardlink; + } + + ret = 0; +no_hardlink: + return ret; +} + +static int ntfs_test_inode_attr(struct inode *vi, void *data) +{ + struct ntfs_inode *ni = NTFS_I(vi); + u64 mft_no = (u64)(uintptr_t)data; + + if (ni->mft_no != mft_no) + return 0; + if (NInoAttr(ni) || ni->nr_extents == -1) + return 1; + else + return 0; +} + +/* + * ntfs_delete - delete file or directory from ntfs volume + * @ni: ntfs inode for object to delte + * @dir_ni: ntfs inode for directory in which delete object + * @name: unicode name of the object to delete + * @name_len: length of the name in unicode characters + * @need_lock: whether mrec lock is needed or not + * + * Delete the specified name from the directory index @dir_ni and decrement + * the link count of the target inode @ni. + * + * Return 0 on success and -errno on error. + */ +static int ntfs_delete(struct ntfs_inode *ni, struct ntfs_inode *dir_ni, + __le16 *name, u8 name_len, bool need_lock) +{ + struct ntfs_attr_search_ctx *actx = NULL; + struct file_name_attr *fn = NULL; + bool looking_for_dos_name = false, looking_for_win32_name = false; + bool case_sensitive_match = true; + int err = 0; + struct mft_record *ni_mrec; + struct super_block *sb; + bool link_count_zero = false; + + ntfs_debug("Entering.\n"); + + if (need_lock == true) { + mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); + mutex_lock_nested(&dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT); + } + + sb = dir_ni->vol->sb; + + if (ni->nr_extents == -1) + ni = ni->ext.base_ntfs_ino; + if (dir_ni->nr_extents == -1) + dir_ni = dir_ni->ext.base_ntfs_ino; + /* + * Search for FILE_NAME attribute with such name. If it's in POSIX or + * WIN32_AND_DOS namespace, then simply remove it from index and inode. + * If filename in DOS or in WIN32 namespace, then remove DOS name first, + * only then remove WIN32 name. + */ + actx = ntfs_attr_get_search_ctx(ni, NULL); + if (!actx) { + ntfs_error(sb, "%s, Failed to get search context", __func__); + if (need_lock) { + mutex_unlock(&dir_ni->mrec_lock); + mutex_unlock(&ni->mrec_lock); + } + return -ENOMEM; + } +search: + while ((err = ntfs_attr_lookup(AT_FILE_NAME, AT_UNNAMED, 0, CASE_SENSITIVE, + 0, NULL, 0, actx)) == 0) { +#ifdef DEBUG + unsigned char *s; +#endif + bool case_sensitive = IGNORE_CASE; + + fn = (struct file_name_attr *)((u8 *)actx->attr + + le16_to_cpu(actx->attr->data.resident.value_offset)); +#ifdef DEBUG + s = ntfs_attr_name_get(ni->vol, fn->file_name, fn->file_name_length); + ntfs_debug("name: '%s' type: %d dos: %d win32: %d case: %d\n", + s, fn->file_name_type, + looking_for_dos_name, looking_for_win32_name, + case_sensitive_match); + ntfs_attr_name_free(&s); +#endif + if (looking_for_dos_name) { + if (fn->file_name_type == FILE_NAME_DOS) + break; + continue; + } + if (looking_for_win32_name) { + if (fn->file_name_type == FILE_NAME_WIN32) + break; + continue; + } + + /* Ignore hard links from other directories */ + if (dir_ni->mft_no != MREF_LE(fn->parent_directory)) { + ntfs_debug("MFT record numbers don't match (%llu != %lu)\n", + dir_ni->mft_no, + MREF_LE(fn->parent_directory)); + continue; + } + + if (fn->file_name_type == FILE_NAME_POSIX || case_sensitive_match) + case_sensitive = CASE_SENSITIVE; + + if (ntfs_names_are_equal(fn->file_name, fn->file_name_length, + name, name_len, case_sensitive, + ni->vol->upcase, ni->vol->upcase_len)) { + if (fn->file_name_type == FILE_NAME_WIN32) { + looking_for_dos_name = true; + ntfs_attr_reinit_search_ctx(actx); + continue; + } + if (fn->file_name_type == FILE_NAME_DOS) + looking_for_dos_name = true; + break; + } + } + if (err) { + /* + * If case sensitive search failed, then try once again + * ignoring case. + */ + if (err == -ENOENT && case_sensitive_match) { + case_sensitive_match = false; + ntfs_attr_reinit_search_ctx(actx); + goto search; + } + goto err_out; + } + + err = ntfs_check_unlinkable_dir(actx, fn); + if (err) + goto err_out; + + err = ntfs_index_remove(dir_ni, fn, le32_to_cpu(actx->attr->data.resident.value_length)); + if (err) + goto err_out; + + err = ntfs_attr_record_rm(actx); + if (err) + goto err_out; + + ni_mrec = actx->base_mrec ? actx->base_mrec : actx->mrec; + ni_mrec->link_count = cpu_to_le16(le16_to_cpu(ni_mrec->link_count) - 1); + if (!S_ISDIR(VFS_I(ni)->i_mode)) + drop_nlink(VFS_I(ni)); + + mark_mft_record_dirty(ni); + if (looking_for_dos_name) { + looking_for_dos_name = false; + looking_for_win32_name = true; + ntfs_attr_reinit_search_ctx(actx); + goto search; + } + + /* + * For directories, Drop VFS nlink only when mft record link count + * becomes zero. Because we fixes VFS nlink to 1 for directories. + */ + if (S_ISDIR(VFS_I(ni)->i_mode) && !le16_to_cpu(ni_mrec->link_count)) + drop_nlink(VFS_I(ni)); + + /* + * If hard link count is not equal to zero then we are done. In other + * case there are no reference to this inode left, so we should free all + * non-resident attributes and mark all MFT record as not in use. + */ + if (ni_mrec->link_count == 0) { + NInoSetBeingDeleted(ni); + ntfs_delete_reparse_index(ni); + ntfs_delete_object_id_index(ni); + link_count_zero = true; + } + + ntfs_attr_put_search_ctx(actx); + if (need_lock == true) { + mutex_unlock(&dir_ni->mrec_lock); + mutex_unlock(&ni->mrec_lock); + } + + /* + * If hard link count is not equal to zero then we are done. In other + * case there are no reference to this inode left, so we should free all + * non-resident attributes and mark all MFT record as not in use. + */ + if (link_count_zero == true) { + struct inode *attr_vi; + + while ((attr_vi = ilookup5(sb, ni->mft_no, ntfs_test_inode_attr, + (void *)(uintptr_t)ni->mft_no)) != NULL) { + clear_nlink(attr_vi); + iput(attr_vi); + } + } + ntfs_debug("Done.\n"); + return 0; +err_out: + ntfs_attr_put_search_ctx(actx); + if (need_lock) { + mutex_unlock(&dir_ni->mrec_lock); + mutex_unlock(&ni->mrec_lock); + } + return err; +} + +static int ntfs_unlink(struct inode *dir, struct dentry *dentry) +{ + struct inode *vi = dentry->d_inode; + struct super_block *sb = dir->i_sb; + struct ntfs_volume *vol = NTFS_SB(sb); + int err = 0; + struct ntfs_inode *ni = NTFS_I(vi); + __le16 *uname = NULL; + int uname_len; + + if (NVolShutdown(vol)) + return -EIO; + + uname_len = ntfs_nlstoucs(vol, dentry->d_name.name, dentry->d_name.len, + &uname, NTFS_MAX_NAME_LEN); + if (uname_len < 0) { + if (uname_len != -ENAMETOOLONG) + ntfs_error(sb, "Failed to convert name to Unicode."); + return -ENOMEM; + } + + err = ntfs_check_bad_windows_name(vol, uname, uname_len); + if (err) { + kmem_cache_free(ntfs_name_cache, uname); + return err; + } + + if (!(vol->vol_flags & VOLUME_IS_DIRTY)) + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + + err = ntfs_delete(ni, NTFS_I(dir), uname, uname_len, true); + if (err) + goto out; + + inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir)); + mark_inode_dirty(dir); + inode_set_ctime_to_ts(vi, inode_get_ctime(dir)); + if (vi->i_nlink) + mark_inode_dirty(vi); +out: + kmem_cache_free(ntfs_name_cache, uname); + return err; +} + +static struct dentry *ntfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode) +{ + struct super_block *sb = dir->i_sb; + struct ntfs_volume *vol = NTFS_SB(sb); + int err = 0; + struct ntfs_inode *ni; + __le16 *uname; + int uname_len; + + if (NVolShutdown(vol)) + return ERR_PTR(-EIO); + + uname_len = ntfs_nlstoucs(vol, dentry->d_name.name, dentry->d_name.len, + &uname, NTFS_MAX_NAME_LEN); + if (uname_len < 0) { + if (uname_len != -ENAMETOOLONG) + ntfs_error(sb, "Failed to convert name to unicode."); + return ERR_PTR(-ENOMEM); + } + + err = ntfs_check_bad_windows_name(vol, uname, uname_len); + if (err) { + kmem_cache_free(ntfs_name_cache, uname); + return ERR_PTR(err); + } + + if (!(vol->vol_flags & VOLUME_IS_DIRTY)) + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + + ni = __ntfs_create(idmap, dir, uname, uname_len, mode, 0, NULL, 0); + kmem_cache_free(ntfs_name_cache, uname); + if (IS_ERR(ni)) { + err = PTR_ERR(ni); + return ERR_PTR(err); + } + + d_instantiate_new(dentry, VFS_I(ni)); + return NULL; +} + +static int ntfs_rmdir(struct inode *dir, struct dentry *dentry) +{ + struct inode *vi = dentry->d_inode; + struct super_block *sb = dir->i_sb; + struct ntfs_volume *vol = NTFS_SB(sb); + int err = 0; + struct ntfs_inode *ni; + __le16 *uname = NULL; + int uname_len; + + if (NVolShutdown(vol)) + return -EIO; + + ni = NTFS_I(vi); + uname_len = ntfs_nlstoucs(vol, dentry->d_name.name, dentry->d_name.len, + &uname, NTFS_MAX_NAME_LEN); + if (uname_len < 0) { + if (uname_len != -ENAMETOOLONG) + ntfs_error(sb, "Failed to convert name to unicode."); + return -ENOMEM; + } + + err = ntfs_check_bad_windows_name(vol, uname, uname_len); + if (err) { + kmem_cache_free(ntfs_name_cache, uname); + return err; + } + + if (!(vol->vol_flags & VOLUME_IS_DIRTY)) + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + + err = ntfs_delete(ni, NTFS_I(dir), uname, uname_len, true); + if (err) + goto out; + + inode_set_mtime_to_ts(vi, inode_set_atime_to_ts(vi, current_time(vi))); +out: + kmem_cache_free(ntfs_name_cache, uname); + return err; +} + +/* + * __ntfs_link - create hard link for file or directory + * @ni: ntfs inode for object to create hard link + * @dir_ni: ntfs inode for directory in which new link should be placed + * @name: unicode name of the new link + * @name_len: length of the name in unicode characters + * + * Create a new hard link. This involves adding an entry to the directory + * index and adding a new FILE_NAME attribute to the target inode. + * + * Return 0 on success and -errno on error. + */ +static int __ntfs_link(struct ntfs_inode *ni, struct ntfs_inode *dir_ni, + __le16 *name, u8 name_len) +{ + struct super_block *sb; + struct inode *vi = VFS_I(ni); + struct file_name_attr *fn = NULL; + int fn_len, err = 0; + struct mft_record *dir_mrec = NULL, *ni_mrec = NULL; + + ntfs_debug("Entering.\n"); + + sb = dir_ni->vol->sb; + if (NInoBeingDeleted(dir_ni) || NInoBeingDeleted(ni)) + return -ENOENT; + + ni_mrec = map_mft_record(ni); + if (IS_ERR(ni_mrec)) { + err = -EIO; + goto err_out; + } + + if (le16_to_cpu(ni_mrec->link_count) == 0) { + err = -ENOENT; + goto err_out; + } + + /* Create FILE_NAME attribute. */ + fn_len = sizeof(struct file_name_attr) + name_len * sizeof(__le16); + + fn = kzalloc(fn_len, GFP_NOFS); + if (!fn) { + err = -ENOMEM; + goto err_out; + } + + dir_mrec = map_mft_record(dir_ni); + if (IS_ERR(dir_mrec)) { + err = -EIO; + goto err_out; + } + + fn->parent_directory = MK_LE_MREF(dir_ni->mft_no, + le16_to_cpu(dir_mrec->sequence_number)); + unmap_mft_record(dir_ni); + fn->file_name_length = name_len; + fn->file_name_type = FILE_NAME_POSIX; + fn->file_attributes = ni->flags; + if (ni_mrec->flags & MFT_RECORD_IS_DIRECTORY) { + fn->file_attributes |= FILE_ATTR_DUP_FILE_NAME_INDEX_PRESENT; + fn->allocated_size = fn->data_size = 0; + } else { + if (NInoSparse(ni) || NInoCompressed(ni)) + fn->allocated_size = + cpu_to_le64(ni->itype.compressed.size); + else + fn->allocated_size = cpu_to_le64(ni->allocated_size); + fn->data_size = cpu_to_le64(ni->data_size); + } + if (NVolHideDotFiles(dir_ni->vol) && name_len > 0 && name[0] == cpu_to_le16('.')) + fn->file_attributes |= FILE_ATTR_HIDDEN; + + fn->creation_time = utc2ntfs(ni->i_crtime); + fn->last_data_change_time = utc2ntfs(inode_get_mtime(vi)); + fn->last_mft_change_time = utc2ntfs(inode_get_ctime(vi)); + fn->last_access_time = utc2ntfs(inode_get_atime(vi)); + memcpy(fn->file_name, name, name_len * sizeof(__le16)); + + /* Add FILE_NAME attribute to index. */ + err = ntfs_index_add_filename(dir_ni, fn, MK_MREF(ni->mft_no, + le16_to_cpu(ni_mrec->sequence_number))); + if (err) { + ntfs_error(sb, "Failed to add filename to the index"); + goto err_out; + } + /* Add FILE_NAME attribute to inode. */ + err = ntfs_attr_add(ni, AT_FILE_NAME, AT_UNNAMED, 0, (u8 *)fn, fn_len); + if (err) { + ntfs_error(sb, "Failed to add FILE_NAME attribute.\n"); + /* Try to remove just added attribute from index. */ + if (ntfs_index_remove(dir_ni, fn, fn_len)) + goto rollback_failed; + goto err_out; + } + /* Increment hard links count. */ + ni_mrec->link_count = cpu_to_le16(le16_to_cpu(ni_mrec->link_count) + 1); + if (!S_ISDIR(vi->i_mode)) + inc_nlink(VFS_I(ni)); + + /* Done! */ + mark_mft_record_dirty(ni); + kfree(fn); + unmap_mft_record(ni); + + ntfs_debug("Done.\n"); + + return 0; +rollback_failed: + ntfs_error(sb, "Rollback failed. Leaving inconsistent metadata.\n"); +err_out: + kfree(fn); + if (!IS_ERR_OR_NULL(ni_mrec)) + unmap_mft_record(ni); + return err; +} + +static int ntfs_rename(struct mnt_idmap *idmap, struct inode *old_dir, + struct dentry *old_dentry, struct inode *new_dir, + struct dentry *new_dentry, unsigned int flags) +{ + struct inode *old_inode, *new_inode = NULL; + int err = 0; + int is_dir; + struct super_block *sb = old_dir->i_sb; + __le16 *uname_new = NULL; + __le16 *uname_old = NULL; + int new_name_len; + int old_name_len; + struct ntfs_volume *vol = NTFS_SB(sb); + struct ntfs_inode *old_ni, *new_ni = NULL; + struct ntfs_inode *old_dir_ni = NTFS_I(old_dir), *new_dir_ni = NTFS_I(new_dir); + bool new_dir_first = false; + + if (NVolShutdown(old_dir_ni->vol)) + return -EIO; + + if (flags & (RENAME_EXCHANGE | RENAME_WHITEOUT)) + return -EINVAL; + + new_name_len = ntfs_nlstoucs(NTFS_I(new_dir)->vol, new_dentry->d_name.name, + new_dentry->d_name.len, &uname_new, + NTFS_MAX_NAME_LEN); + if (new_name_len < 0) { + if (new_name_len != -ENAMETOOLONG) + ntfs_error(sb, "Failed to convert name to unicode."); + return -ENOMEM; + } + + err = ntfs_check_bad_windows_name(vol, uname_new, new_name_len); + if (err) { + kmem_cache_free(ntfs_name_cache, uname_new); + return err; + } + + old_name_len = ntfs_nlstoucs(NTFS_I(old_dir)->vol, old_dentry->d_name.name, + old_dentry->d_name.len, &uname_old, + NTFS_MAX_NAME_LEN); + if (old_name_len < 0) { + kmem_cache_free(ntfs_name_cache, uname_new); + if (old_name_len != -ENAMETOOLONG) + ntfs_error(sb, "Failed to convert name to unicode."); + return -ENOMEM; + } + + old_inode = old_dentry->d_inode; + new_inode = new_dentry->d_inode; + old_ni = NTFS_I(old_inode); + if (new_inode) + new_ni = NTFS_I(new_inode); + if (old_dir != new_dir) + new_dir_first = is_subdir(new_dentry->d_parent, + old_dentry->d_parent); + + if (!(vol->vol_flags & VOLUME_IS_DIRTY)) + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + + mutex_lock_nested(&old_ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); + if (new_ni) + mutex_lock_nested(&new_ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL_2); + + if (old_dir == new_dir) { + mutex_lock_nested(&old_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT); + } else if (new_dir_first) { + mutex_lock_nested(&new_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT); + mutex_lock_nested(&old_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT_2); + } else { + mutex_lock_nested(&old_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT); + mutex_lock_nested(&new_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT_2); + } + + if (NInoBeingDeleted(old_ni) || NInoBeingDeleted(old_dir_ni) || + (new_ni && NInoBeingDeleted(new_ni)) || + (old_dir != new_dir && NInoBeingDeleted(new_dir_ni))) { + err = -ENOENT; + goto err_out; + } + + is_dir = S_ISDIR(old_inode->i_mode); + + if (new_inode) { + if (is_dir) { + struct mft_record *ni_mrec; + + ni_mrec = map_mft_record(NTFS_I(new_inode)); + if (IS_ERR(ni_mrec)) { + err = -EIO; + goto err_out; + } + err = ntfs_check_empty_dir(NTFS_I(new_inode), ni_mrec); + unmap_mft_record(NTFS_I(new_inode)); + if (err) + goto err_out; + } + + err = ntfs_delete(new_ni, new_dir_ni, uname_new, new_name_len, false); + if (err) + goto err_out; + } + + err = __ntfs_link(old_ni, new_dir_ni, uname_new, new_name_len); + if (err) + goto err_out; + + err = ntfs_delete(old_ni, old_dir_ni, uname_old, old_name_len, false); + if (err) { + int err2; + + ntfs_error(sb, "Failed to delete old ntfs inode(%llu) in old dir, err : %d\n", + old_ni->mft_no, err); + err2 = ntfs_delete(old_ni, new_dir_ni, uname_new, new_name_len, false); + if (err2) + ntfs_error(sb, "Failed to delete old ntfs inode in new dir, err : %d\n", + err2); + goto err_out; + } + + simple_rename_timestamp(old_dir, old_dentry, new_dir, new_dentry); + mark_inode_dirty(old_inode); + mark_inode_dirty(old_dir); + if (old_dir != new_dir) + mark_inode_dirty(new_dir); + if (new_inode) + mark_inode_dirty(old_inode); + + inode_inc_iversion(new_dir); + +err_out: + if (old_dir == new_dir) { + mutex_unlock(&old_dir_ni->mrec_lock); + } else if (new_dir_first) { + mutex_unlock(&old_dir_ni->mrec_lock); + mutex_unlock(&new_dir_ni->mrec_lock); + } else { + mutex_unlock(&new_dir_ni->mrec_lock); + mutex_unlock(&old_dir_ni->mrec_lock); + } + if (new_ni) + mutex_unlock(&new_ni->mrec_lock); + mutex_unlock(&old_ni->mrec_lock); + if (uname_new) + kmem_cache_free(ntfs_name_cache, uname_new); + if (uname_old) + kmem_cache_free(ntfs_name_cache, uname_old); + + return err; +} + +static int ntfs_symlink(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, const char *symname) +{ + struct super_block *sb = dir->i_sb; + struct ntfs_volume *vol = NTFS_SB(sb); + struct inode *vi; + int err = 0; + struct ntfs_inode *ni; + __le16 *usrc; + int usrc_len; + int symlen = strlen(symname); + + if (NVolShutdown(vol)) + return -EIO; + + usrc_len = ntfs_nlstoucs(vol, dentry->d_name.name, + dentry->d_name.len, &usrc, NTFS_MAX_NAME_LEN); + if (usrc_len < 0) { + if (usrc_len != -ENAMETOOLONG) + ntfs_error(sb, "Failed to convert name to Unicode."); + err = -ENOMEM; + goto out; + } + + err = ntfs_check_bad_windows_name(vol, usrc, usrc_len); + if (err) { + 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, + symname, symlen); + kmem_cache_free(ntfs_name_cache, usrc); + if (IS_ERR(ni)) { + err = PTR_ERR(ni); + goto out; + } + + vi = VFS_I(ni); + vi->i_size = symlen; + d_instantiate_new(dentry, vi); +out: + return err; +} + +static int ntfs_mknod(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode, dev_t rdev) +{ + struct super_block *sb = dir->i_sb; + struct ntfs_volume *vol = NTFS_SB(sb); + int err = 0; + struct ntfs_inode *ni; + __le16 *uname = NULL; + int uname_len; + + if (NVolShutdown(vol)) + return -EIO; + + uname_len = ntfs_nlstoucs(vol, dentry->d_name.name, + dentry->d_name.len, &uname, NTFS_MAX_NAME_LEN); + if (uname_len < 0) { + if (uname_len != -ENAMETOOLONG) + ntfs_error(sb, "Failed to convert name to Unicode."); + return -ENOMEM; + } + + err = ntfs_check_bad_windows_name(vol, uname, uname_len); + if (err) { + kmem_cache_free(ntfs_name_cache, uname); + return err; + } + + if (!(vol->vol_flags & VOLUME_IS_DIRTY)) + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + + switch (mode & S_IFMT) { + case S_IFCHR: + case S_IFBLK: + ni = __ntfs_create(idmap, dir, uname, uname_len, + mode, rdev, NULL, 0); + break; + default: + ni = __ntfs_create(idmap, dir, uname, uname_len, + mode, 0, NULL, 0); + } + + kmem_cache_free(ntfs_name_cache, uname); + if (IS_ERR(ni)) { + err = PTR_ERR(ni); + goto out; + } + + d_instantiate_new(dentry, VFS_I(ni)); +out: + return err; +} + +static int ntfs_link(struct dentry *old_dentry, struct inode *dir, + struct dentry *dentry) +{ + struct inode *vi = old_dentry->d_inode; + struct super_block *sb = vi->i_sb; + struct ntfs_volume *vol = NTFS_SB(sb); + __le16 *uname = NULL; + int uname_len; + int err; + struct ntfs_inode *ni = NTFS_I(vi), *dir_ni = NTFS_I(dir); + + if (NVolShutdown(vol)) + return -EIO; + + uname_len = ntfs_nlstoucs(vol, dentry->d_name.name, + dentry->d_name.len, &uname, NTFS_MAX_NAME_LEN); + if (uname_len < 0) { + if (uname_len != -ENAMETOOLONG) + ntfs_error(sb, "Failed to convert name to unicode."); + return -ENOMEM; + } + + if (!(vol->vol_flags & VOLUME_IS_DIRTY)) + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + + ihold(vi); + mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); + mutex_lock_nested(&dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT); + err = __ntfs_link(NTFS_I(vi), NTFS_I(dir), uname, uname_len); + if (err) { + mutex_unlock(&dir_ni->mrec_lock); + mutex_unlock(&ni->mrec_lock); + iput(vi); + pr_err("failed to create link, err = %d\n", err); + goto out; + } + + inode_inc_iversion(dir); + simple_inode_init_ts(dir); + + inode_inc_iversion(vi); + simple_inode_init_ts(vi); + + /* timestamp is already written, so mark_inode_dirty() is unneeded. */ + d_instantiate(dentry, vi); + mutex_unlock(&dir_ni->mrec_lock); + mutex_unlock(&ni->mrec_lock); + +out: + kmem_cache_free(ntfs_name_cache, uname); + return err; +} + +/* + * Inode operations for directories. + */ +const struct inode_operations ntfs_dir_inode_ops = { + .lookup = ntfs_lookup, /* VFS: Lookup directory. */ + .create = ntfs_create, + .unlink = ntfs_unlink, + .mkdir = ntfs_mkdir, + .rmdir = ntfs_rmdir, + .rename = ntfs_rename, + .get_acl = ntfs_get_acl, + .set_acl = ntfs_set_acl, + .listxattr = ntfs_listxattr, + .setattr = ntfs_setattr, + .getattr = ntfs_getattr, + .symlink = ntfs_symlink, + .mknod = ntfs_mknod, + .link = ntfs_link, +}; + +/* + * ntfs_get_parent - find the dentry of the parent of a given directory dentry + * @child_dent: dentry of the directory whose parent directory to find + * + * Find the dentry for the parent directory of the directory specified by the + * dentry @child_dent. This function is called from + * fs/exportfs/expfs.c::find_exported_dentry() which in turn is called from the + * default ->decode_fh() which is export_decode_fh() in the same file. + * + * Note: ntfs_get_parent() is called with @d_inode(child_dent)->i_mutex down. + * + * Return the dentry of the parent directory on success or the error code on + * error (IS_ERR() is true). + */ +static struct dentry *ntfs_get_parent(struct dentry *child_dent) +{ + struct inode *vi = d_inode(child_dent); + struct ntfs_inode *ni = NTFS_I(vi); + struct mft_record *mrec; + struct ntfs_attr_search_ctx *ctx; + struct attr_record *attr; + struct file_name_attr *fn; + unsigned long parent_ino; + int err; + + ntfs_debug("Entering for inode 0x%llx.", ni->mft_no); + /* Get the mft record of the inode belonging to the child dentry. */ + mrec = map_mft_record(ni); + if (IS_ERR(mrec)) + return ERR_CAST(mrec); + /* Find the first file name attribute in the mft record. */ + ctx = ntfs_attr_get_search_ctx(ni, mrec); + if (unlikely(!ctx)) { + unmap_mft_record(ni); + return ERR_PTR(-ENOMEM); + } +try_next: + err = ntfs_attr_lookup(AT_FILE_NAME, NULL, 0, CASE_SENSITIVE, 0, NULL, + 0, ctx); + if (unlikely(err)) { + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(ni); + if (err == -ENOENT) + ntfs_error(vi->i_sb, + "Inode 0x%llx does not have a file name attribute. Run chkdsk.", + ni->mft_no); + return ERR_PTR(err); + } + attr = ctx->attr; + if (unlikely(attr->non_resident)) + goto try_next; + fn = (struct file_name_attr *)((u8 *)attr + + le16_to_cpu(attr->data.resident.value_offset)); + if (unlikely((u8 *)fn + le32_to_cpu(attr->data.resident.value_length) > + (u8 *)attr + le32_to_cpu(attr->length))) + goto try_next; + /* Get the inode number of the parent directory. */ + parent_ino = MREF_LE(fn->parent_directory); + /* Release the search context and the mft record of the child. */ + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(ni); + + return d_obtain_alias(ntfs_iget(vi->i_sb, parent_ino)); +} + +static struct inode *ntfs_nfs_get_inode(struct super_block *sb, + u64 ino, u32 generation) +{ + struct inode *inode; + + inode = ntfs_iget(sb, ino); + if (!IS_ERR(inode)) { + if (inode->i_generation != generation) { + iput(inode); + inode = ERR_PTR(-ESTALE); + } + } + + return inode; +} + +static struct dentry *ntfs_fh_to_dentry(struct super_block *sb, struct fid *fid, + int fh_len, int fh_type) +{ + return generic_fh_to_dentry(sb, fid, fh_len, fh_type, + ntfs_nfs_get_inode); +} + +static struct dentry *ntfs_fh_to_parent(struct super_block *sb, struct fid *fid, + int fh_len, int fh_type) +{ + return generic_fh_to_parent(sb, fid, fh_len, fh_type, + ntfs_nfs_get_inode); +} + +/* + * Export operations allowing NFS exporting of mounted NTFS partitions. + */ +const struct export_operations ntfs_export_ops = { + .encode_fh = generic_encode_ino32_fh, + .get_parent = ntfs_get_parent, /* Find the parent of a given directory. */ + .fh_to_dentry = ntfs_fh_to_dentry, + .fh_to_parent = ntfs_fh_to_parent, +}; |
