linux/fs/ntfs/namei.c
DaeMyung Kang 3086c49a07 ntfs: avoid leaking uninitialised bytes in new security descriptors
ntfs_sd_add_everyone() builds the on-disk security descriptor for a
newly created file by kmalloc()'ing a buffer and then partially
filling it in:

	sd = kmalloc(sd_len, GFP_NOFS);
	...
	sd->revision = 1;
	sd->control = SE_DACL_PRESENT | SE_SELF_RELATIVE;
	...

The buffer is then handed to ntfs_attr_add() and persisted as the
SECURITY_DESCRIPTOR attribute of the new MFT record.  The descriptor
covers a relative security descriptor header, two SIDs (owner and
group), an ACL header, and a single ACE, but several fields inside
those structures are never written before the buffer is committed
to disk:

  - struct security_descriptor_relative
        @alignment		(1 byte)
        @sacl			(4 bytes; SE_SACL_PRESENT is not set
                                 but the offset still reaches disk)

  - struct ntfs_sid (3 instances: owner, group, ACE.sid)
        identifier_authority.value[0..4] (5 bytes per SID, 15 total
                                          - only value[5] is set)

  - struct ntfs_acl
        @alignment1		(1 byte)
        @alignment2		(2 bytes)

That is 23 bytes of uninitialised slab memory persisted to disk for
every new file or directory the legacy ntfs driver creates.  The
"+ 4" trailing accounting in sd_len holds ace->sid.sub_authority[0],
which the existing code does explicitly write to zero, so it is
not part of the leak.

Anything later able to read the SECURITY_DESCRIPTOR attribute - the
same NTFS volume mounted on Windows or by another NTFS reader, an
offline forensics tool, an unprivileged user that ends up with read
access to the volume - can recover those bytes.  The leak persists
for the lifetime of the file on disk, not just the lifetime of the
kernel that wrote it.

Switch the allocation to kzalloc() so every byte the on-disk
descriptor covers is zero before the explicit initialisations run.
While there, replace the bare "return -1" allocation-failure path
with a proper -ENOMEM so the error reaches userspace as a meaningful
errno instead of an unrelated -EPERM.

Found by inspection while auditing fs/ntfs new-inode paths.

Fixes: af0db57d42 ("ntfs: update inode operations")
Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
2026-05-08 23:51:13 +09:00

1694 lines
48 KiB
C

// 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 (ntfs_check_bad_char(wc, wc_len))
return -EINVAL;
if (!NVolCheckWindowsNames(vol))
return 0;
/* 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. */
d_add(dent, NULL);
ntfs_debug("Done.");
return NULL;
}
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,
__le16 *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)) {
mode &= ~vol->dmask;
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;
}
} else {
mode &= ~vol->fmask;
}
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);
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)) {
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;
/* 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, bool excl)
{
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, S_IFDIR | 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);
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 (!(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);
mutex_lock_nested(&old_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT);
if (NInoBeingDeleted(old_ni) || NInoBeingDeleted(old_dir_ni)) {
err = -ENOENT;
goto unlock_old;
}
is_dir = S_ISDIR(old_inode->i_mode);
if (new_inode) {
new_ni = NTFS_I(new_inode);
mutex_lock_nested(&new_ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL_2);
if (old_dir != new_dir) {
mutex_lock_nested(&new_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT_2);
if (NInoBeingDeleted(new_dir_ni)) {
err = -ENOENT;
goto err_out;
}
}
if (NInoBeingDeleted(new_ni)) {
err = -ENOENT;
goto err_out;
}
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;
} else {
if (old_dir != new_dir) {
mutex_lock_nested(&new_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT_2);
if (NInoBeingDeleted(new_dir_ni)) {
err = -ENOENT;
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(&new_dir_ni->mrec_lock);
if (new_inode)
mutex_unlock(&new_ni->mrec_lock);
unlock_old:
mutex_unlock(&old_dir_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;
__le16 *utarget;
int usrc_len;
int utarget_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;
}
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);
kmem_cache_free(ntfs_name_cache, usrc);
kvfree(utarget);
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.");
err = -ENOMEM;
goto out;
}
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:
kfree(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,
};