mirror of
https://github.com/torvalds/linux.git
synced 2026-04-18 06:44:00 -04:00
Remove redundant out-of-bounds validations. Since ntfs_attr_find and ntfs_external_attr_find now validate the attribute value offsets and lengths against the bounds of the MFT record block, performing subsequent bounds checking in caller functions like ntfs_attr_lookup is no longer necessary. Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
1685 lines
47 KiB
C
1685 lines
47 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 = kmalloc(sd_len, GFP_NOFS);
|
|
if (!sd)
|
|
return -1;
|
|
|
|
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);
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
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,
|
|
};
|