linux/fs/ntfs3/namei.c

620 lines
13 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0
/*
*
* Copyright (C) 2019-2021 Paragon Software GmbH, All rights reserved.
*
*/
#include <linux/fs.h>
#include <linux/nls.h>
#include <linux/ctype.h>
#include <linux/posix_acl.h>
#include "debug.h"
#include "ntfs.h"
#include "ntfs_fs.h"
/*
* fill_name_de - Format NTFS_DE in @buf.
*/
int fill_name_de(struct ntfs_sb_info *sbi, void *buf, const struct qstr *name,
const struct cpu_str *uni)
{
int err;
struct NTFS_DE *e = buf;
u16 data_size;
struct ATTR_FILE_NAME *fname = (struct ATTR_FILE_NAME *)(e + 1);
#ifndef CONFIG_NTFS3_64BIT_CLUSTER
e->ref.high = fname->home.high = 0;
#endif
if (uni) {
#ifdef __BIG_ENDIAN
int ulen = uni->len;
__le16 *uname = fname->name;
const u16 *name_cpu = uni->name;
while (ulen--)
*uname++ = cpu_to_le16(*name_cpu++);
#else
memcpy(fname->name, uni->name, uni->len * sizeof(u16));
#endif
fname->name_len = uni->len;
} else {
/* Convert input string to unicode. */
err = ntfs_nls_to_utf16(sbi, name->name, name->len,
(struct cpu_str *)&fname->name_len,
NTFS_NAME_LEN, UTF16_LITTLE_ENDIAN);
if (err < 0)
return err;
}
fname->type = FILE_NAME_POSIX;
data_size = fname_full_size(fname);
fs/ntfs3: Use kernel ALIGN macros over driver specific The static checkers (Smatch) were complaining because QuadAlign() was buggy. If you try to align something higher than UINT_MAX it got truncated to a u32. Smatch warning was: fs/ntfs3/attrib.c:383 attr_set_size_res() warn: was expecting a 64 bit value instead of '~7' So that this will not happen again we will change all these macros to kernel made ones. This can also help some other static analyzing tools to give us better warnings. Patch was generated with Coccinelle script and after that some style issue was hand fixed. Coccinelle script: virtual patch @alloc depends on patch@ expression x; @@ ( - #define QuadAlign(n) (((n) + 7u) & (~7u)) | - QuadAlign(x) + ALIGN(x, 8) | - #define IsQuadAligned(n) (!((size_t)(n)&7u)) | - IsQuadAligned(x) + IS_ALIGNED(x, 8) | - #define Quad2Align(n) (((n) + 15u) & (~15u)) | - Quad2Align(x) + ALIGN(x, 16) | - #define IsQuad2Aligned(n) (!((size_t)(n)&15u)) | - IsQuad2Aligned(x) + IS_ALIGNED(x, 16) | - #define Quad4Align(n) (((n) + 31u) & (~31u)) | - Quad4Align(x) + ALIGN(x, 32) | - #define IsSizeTAligned(n) (!((size_t)(n) & (sizeof(size_t) - 1))) | - IsSizeTAligned(x) + IS_ALIGNED(x, sizeof(size_t)) | - #define DwordAlign(n) (((n) + 3u) & (~3u)) | - DwordAlign(x) + ALIGN(x, 4) | - #define IsDwordAligned(n) (!((size_t)(n)&3u)) | - IsDwordAligned(x) + IS_ALIGNED(x, 4) | - #define WordAlign(n) (((n) + 1u) & (~1u)) | - WordAlign(x) + ALIGN(x, 2) | - #define IsWordAligned(n) (!((size_t)(n)&1u)) | - IsWordAligned(x) + IS_ALIGNED(x, 2) | ) Reported-by: Dan Carpenter <dan.carpenter@oracle.com> Signed-off-by: Kari Argillander <kari.argillander@gmail.com> Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
2021-08-26 16:56:29 +08:00
e->size = cpu_to_le16(ALIGN(data_size, 8) + sizeof(struct NTFS_DE));
e->key_size = cpu_to_le16(data_size);
e->flags = 0;
e->res = 0;
return 0;
}
/*
* ntfs_lookup - inode_operations::lookup
*/
static struct dentry *ntfs_lookup(struct inode *dir, struct dentry *dentry,
u32 flags)
{
struct ntfs_inode *ni = ntfs_i(dir);
struct cpu_str *uni = __getname();
struct inode *inode;
int err;
if (!uni)
inode = ERR_PTR(-ENOMEM);
else {
err = ntfs_nls_to_utf16(ni->mi.sbi, dentry->d_name.name,
dentry->d_name.len, uni, NTFS_NAME_LEN,
UTF16_HOST_ENDIAN);
if (err < 0)
inode = ERR_PTR(err);
else {
ni_lock(ni);
inode = dir_search_u(dir, uni, NULL);
ni_unlock(ni);
}
__putname(uni);
}
return d_splice_alias(inode, dentry);
}
/*
* ntfs_create - inode_operations::create
*/
static int ntfs_create(struct user_namespace *mnt_userns, struct inode *dir,
struct dentry *dentry, umode_t mode, bool excl)
{
struct inode *inode;
inode = ntfs_create_inode(mnt_userns, dir, dentry, NULL, S_IFREG | mode,
0, NULL, 0, NULL);
return IS_ERR(inode) ? PTR_ERR(inode) : 0;
}
/*
* ntfs_mknod
*
* inode_operations::mknod
*/
static int ntfs_mknod(struct user_namespace *mnt_userns, struct inode *dir,
struct dentry *dentry, umode_t mode, dev_t rdev)
{
struct inode *inode;
inode = ntfs_create_inode(mnt_userns, dir, dentry, NULL, mode, rdev,
NULL, 0, NULL);
return IS_ERR(inode) ? PTR_ERR(inode) : 0;
}
/*
* ntfs_link - inode_operations::link
*/
static int ntfs_link(struct dentry *ode, struct inode *dir, struct dentry *de)
{
int err;
struct inode *inode = d_inode(ode);
struct ntfs_inode *ni = ntfs_i(inode);
if (S_ISDIR(inode->i_mode))
return -EPERM;
if (inode->i_nlink >= NTFS_LINK_MAX)
return -EMLINK;
ni_lock_dir(ntfs_i(dir));
if (inode != dir)
ni_lock(ni);
inc_nlink(inode);
ihold(inode);
err = ntfs_link_inode(inode, de);
if (!err) {
dir->i_ctime = dir->i_mtime = inode->i_ctime =
current_time(dir);
mark_inode_dirty(inode);
mark_inode_dirty(dir);
d_instantiate(de, inode);
} else {
drop_nlink(inode);
iput(inode);
}
if (inode != dir)
ni_unlock(ni);
ni_unlock(ntfs_i(dir));
return err;
}
/*
* ntfs_unlink - inode_operations::unlink
*/
static int ntfs_unlink(struct inode *dir, struct dentry *dentry)
{
struct ntfs_inode *ni = ntfs_i(dir);
int err;
ni_lock_dir(ni);
err = ntfs_unlink_inode(dir, dentry);
ni_unlock(ni);
return err;
}
/*
* ntfs_symlink - inode_operations::symlink
*/
static int ntfs_symlink(struct user_namespace *mnt_userns, struct inode *dir,
struct dentry *dentry, const char *symname)
{
u32 size = strlen(symname);
struct inode *inode;
inode = ntfs_create_inode(mnt_userns, dir, dentry, NULL, S_IFLNK | 0777,
0, symname, size, NULL);
return IS_ERR(inode) ? PTR_ERR(inode) : 0;
}
/*
* ntfs_mkdir- inode_operations::mkdir
*/
static int ntfs_mkdir(struct user_namespace *mnt_userns, struct inode *dir,
struct dentry *dentry, umode_t mode)
{
struct inode *inode;
inode = ntfs_create_inode(mnt_userns, dir, dentry, NULL, S_IFDIR | mode,
0, NULL, 0, NULL);
return IS_ERR(inode) ? PTR_ERR(inode) : 0;
}
/*
* ntfs_rmdir - inode_operations::rmdir
*/
static int ntfs_rmdir(struct inode *dir, struct dentry *dentry)
{
struct ntfs_inode *ni = ntfs_i(dir);
int err;
ni_lock_dir(ni);
err = ntfs_unlink_inode(dir, dentry);
ni_unlock(ni);
return err;
}
/*
* ntfs_rename - inode_operations::rename
*/
static int ntfs_rename(struct user_namespace *mnt_userns, struct inode *dir,
struct dentry *dentry, struct inode *new_dir,
struct dentry *new_dentry, u32 flags)
{
int err;
struct super_block *sb = dir->i_sb;
struct ntfs_sb_info *sbi = sb->s_fs_info;
struct ntfs_inode *dir_ni = ntfs_i(dir);
struct ntfs_inode *new_dir_ni = ntfs_i(new_dir);
struct inode *inode = d_inode(dentry);
struct ntfs_inode *ni = ntfs_i(inode);
struct inode *new_inode = d_inode(new_dentry);
struct NTFS_DE *de, *new_de;
bool is_same, is_bad;
/*
* de - memory of PATH_MAX bytes:
* [0-1024) - original name (dentry->d_name)
* [1024-2048) - paired to original name, usually DOS variant of dentry->d_name
* [2048-3072) - new name (new_dentry->d_name)
*/
static_assert(SIZEOF_ATTRIBUTE_FILENAME_MAX + SIZEOF_RESIDENT < 1024);
static_assert(SIZEOF_ATTRIBUTE_FILENAME_MAX + sizeof(struct NTFS_DE) <
1024);
static_assert(PATH_MAX >= 4 * 1024);
if (flags & ~RENAME_NOREPLACE)
return -EINVAL;
is_same = dentry->d_name.len == new_dentry->d_name.len &&
!memcmp(dentry->d_name.name, new_dentry->d_name.name,
dentry->d_name.len);
if (is_same && dir == new_dir) {
/* Nothing to do. */
return 0;
}
if (ntfs_is_meta_file(sbi, inode->i_ino)) {
/* Should we print an error? */
return -EINVAL;
}
if (new_inode) {
/* Target name exists. Unlink it. */
dget(new_dentry);
ni_lock_dir(new_dir_ni);
err = ntfs_unlink_inode(new_dir, new_dentry);
ni_unlock(new_dir_ni);
dput(new_dentry);
if (err)
return err;
}
/* Allocate PATH_MAX bytes. */
de = __getname();
if (!de)
return -ENOMEM;
/* Translate dentry->d_name into unicode form. */
err = fill_name_de(sbi, de, &dentry->d_name, NULL);
if (err < 0)
goto out;
if (is_same) {
/* Reuse 'de'. */
new_de = de;
} else {
/* Translate new_dentry->d_name into unicode form. */
new_de = Add2Ptr(de, 2048);
err = fill_name_de(sbi, new_de, &new_dentry->d_name, NULL);
if (err < 0)
goto out;
}
ni_lock_dir(dir_ni);
ni_lock(ni);
is_bad = false;
err = ni_rename(dir_ni, new_dir_ni, ni, de, new_de, &is_bad);
if (is_bad) {
/* Restore after failed rename failed too. */
_ntfs_bad_inode(inode);
} else if (!err) {
inode->i_ctime = dir->i_ctime = dir->i_mtime =
current_time(dir);
mark_inode_dirty(inode);
mark_inode_dirty(dir);
if (dir != new_dir) {
new_dir->i_mtime = new_dir->i_ctime = dir->i_ctime;
mark_inode_dirty(new_dir);
}
if (IS_DIRSYNC(dir))
ntfs_sync_inode(dir);
if (IS_DIRSYNC(new_dir))
ntfs_sync_inode(inode);
}
ni_unlock(ni);
ni_unlock(dir_ni);
out:
__putname(de);
return err;
}
/*
* ntfs_atomic_open
*
* inode_operations::atomic_open
*/
static int ntfs_atomic_open(struct inode *dir, struct dentry *dentry,
struct file *file, u32 flags, umode_t mode)
{
int err;
struct inode *inode;
struct ntfs_fnd *fnd = NULL;
struct ntfs_inode *ni = ntfs_i(dir);
struct dentry *d = NULL;
struct cpu_str *uni = __getname();
bool locked = false;
if (!uni)
return -ENOMEM;
err = ntfs_nls_to_utf16(ni->mi.sbi, dentry->d_name.name,
dentry->d_name.len, uni, NTFS_NAME_LEN,
UTF16_HOST_ENDIAN);
if (err < 0)
goto out;
#ifdef CONFIG_NTFS3_FS_POSIX_ACL
if (IS_POSIXACL(dir)) {
/*
* Load in cache current acl to avoid ni_lock(dir):
* ntfs_create_inode -> ntfs_init_acl -> posix_acl_create ->
* ntfs_get_acl -> ntfs_get_acl_ex -> ni_lock
*/
struct posix_acl *p = get_acl(dir, ACL_TYPE_DEFAULT);
if (IS_ERR(p)) {
err = PTR_ERR(p);
goto out;
}
posix_acl_release(p);
}
#endif
if (d_in_lookup(dentry)) {
ni_lock_dir(ni);
locked = true;
fnd = fnd_get();
if (!fnd) {
err = -ENOMEM;
goto out1;
}
d = d_splice_alias(dir_search_u(dir, uni, fnd), dentry);
if (IS_ERR(d)) {
err = PTR_ERR(d);
d = NULL;
goto out2;
}
if (d)
dentry = d;
}
if (!(flags & O_CREAT) || d_really_is_positive(dentry)) {
err = finish_no_open(file, d);
goto out2;
}
file->f_mode |= FMODE_CREATED;
/*
* fnd contains tree's path to insert to.
* If fnd is not NULL then dir is locked.
*/
/*
* Unfortunately I don't know how to get here correct 'struct nameidata *nd'
* or 'struct user_namespace *mnt_userns'.
* See atomic_open in fs/namei.c.
* This is why xfstest/633 failed.
* Looks like ntfs_atomic_open must accept 'struct user_namespace *mnt_userns' as argument.
*/
inode = ntfs_create_inode(&init_user_ns, dir, dentry, uni, mode, 0,
NULL, 0, fnd);
err = IS_ERR(inode) ? PTR_ERR(inode)
: finish_open(file, dentry, ntfs_file_open);
dput(d);
out2:
fnd_put(fnd);
out1:
if (locked)
ni_unlock(ni);
out:
__putname(uni);
return err;
}
struct dentry *ntfs3_get_parent(struct dentry *child)
{
struct inode *inode = d_inode(child);
struct ntfs_inode *ni = ntfs_i(inode);
struct ATTR_LIST_ENTRY *le = NULL;
struct ATTRIB *attr = NULL;
struct ATTR_FILE_NAME *fname;
while ((attr = ni_find_attr(ni, attr, &le, ATTR_NAME, NULL, 0, NULL,
NULL))) {
fname = resident_data_ex(attr, SIZEOF_ATTRIBUTE_FILENAME);
if (!fname)
continue;
return d_obtain_alias(
ntfs_iget5(inode->i_sb, &fname->home, NULL));
}
return ERR_PTR(-ENOENT);
}
/*
* dentry_operations::d_hash
*/
static int ntfs_d_hash(const struct dentry *dentry, struct qstr *name)
{
struct ntfs_sb_info *sbi;
const char *n = name->name;
unsigned int len = name->len;
unsigned long hash;
struct cpu_str *uni;
unsigned int c;
int err;
/* First try fast implementation. */
hash = init_name_hash(dentry);
for (;;) {
if (!len--) {
name->hash = end_name_hash(hash);
return 0;
}
c = *n++;
if (c >= 0x80)
break;
hash = partial_name_hash(toupper(c), hash);
}
/*
* Try slow way with current upcase table
*/
uni = __getname();
if (!uni)
return -ENOMEM;
sbi = dentry->d_sb->s_fs_info;
err = ntfs_nls_to_utf16(sbi, name->name, name->len, uni, NTFS_NAME_LEN,
UTF16_HOST_ENDIAN);
if (err < 0)
goto out;
if (!err) {
err = -EINVAL;
goto out;
}
hash = ntfs_names_hash(uni->name, uni->len, sbi->upcase,
init_name_hash(dentry));
name->hash = end_name_hash(hash);
err = 0;
out:
__putname(uni);
return err;
}
/*
* dentry_operations::d_compare
*/
static int ntfs_d_compare(const struct dentry *dentry, unsigned int len1,
const char *str, const struct qstr *name)
{
struct ntfs_sb_info *sbi;
int ret;
const char *n1 = str;
const char *n2 = name->name;
unsigned int len2 = name->len;
unsigned int lm = min(len1, len2);
unsigned char c1, c2;
struct cpu_str *uni1;
struct le_str *uni2;
/* First try fast implementation. */
for (;;) {
fs/ntfs3: Don't use uni1 uninitialized in ntfs_d_compare() Clang warns: fs/ntfs3/namei.c:445:7: error: variable 'uni1' is used uninitialized whenever 'if' condition is true [-Werror,-Wsometimes-uninitialized] if (toupper(c1) != toupper(c2)) { ^~~~~~~~~~~~~~~~~~~~~~~~~~ ./include/linux/ctype.h:64:20: note: expanded from macro 'toupper' #define toupper(c) __toupper(c) ^ fs/ntfs3/namei.c:487:12: note: uninitialized use occurs here __putname(uni1); ^~~~ ./include/linux/fs.h:2789:65: note: expanded from macro '__putname' #define __putname(name) kmem_cache_free(names_cachep, (void *)(name)) ^~~~ fs/ntfs3/namei.c:445:3: note: remove the 'if' if its condition is always false if (toupper(c1) != toupper(c2)) { ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ fs/ntfs3/namei.c:434:7: error: variable 'uni1' is used uninitialized whenever 'if' condition is true [-Werror,-Wsometimes-uninitialized] if (!lm--) { ^~~~~ fs/ntfs3/namei.c:487:12: note: uninitialized use occurs here __putname(uni1); ^~~~ ./include/linux/fs.h:2789:65: note: expanded from macro '__putname' #define __putname(name) kmem_cache_free(names_cachep, (void *)(name)) ^~~~ fs/ntfs3/namei.c:434:3: note: remove the 'if' if its condition is always false if (!lm--) { ^~~~~~~~~~~~ fs/ntfs3/namei.c:430:22: note: initialize the variable 'uni1' to silence this warning struct cpu_str *uni1, *uni2; ^ = NULL 2 errors generated. There is no point in calling __putname() in these particular error paths, as there has been no corresponding __getname() call yet. Just return directly in these blocks to clear up the warning. Fixes: a3a956c78efa ("fs/ntfs3: Add option "nocase"") Link: https://github.com/ClangBuiltLinux/linux/issues/1729 Signed-off-by: Nathan Chancellor <nathan@kernel.org> Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
2022-10-04 22:41:45 +08:00
if (!lm--)
return len1 != len2;
if ((c1 = *n1++) == (c2 = *n2++))
continue;
if (c1 >= 0x80 || c2 >= 0x80)
break;
fs/ntfs3: Don't use uni1 uninitialized in ntfs_d_compare() Clang warns: fs/ntfs3/namei.c:445:7: error: variable 'uni1' is used uninitialized whenever 'if' condition is true [-Werror,-Wsometimes-uninitialized] if (toupper(c1) != toupper(c2)) { ^~~~~~~~~~~~~~~~~~~~~~~~~~ ./include/linux/ctype.h:64:20: note: expanded from macro 'toupper' #define toupper(c) __toupper(c) ^ fs/ntfs3/namei.c:487:12: note: uninitialized use occurs here __putname(uni1); ^~~~ ./include/linux/fs.h:2789:65: note: expanded from macro '__putname' #define __putname(name) kmem_cache_free(names_cachep, (void *)(name)) ^~~~ fs/ntfs3/namei.c:445:3: note: remove the 'if' if its condition is always false if (toupper(c1) != toupper(c2)) { ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ fs/ntfs3/namei.c:434:7: error: variable 'uni1' is used uninitialized whenever 'if' condition is true [-Werror,-Wsometimes-uninitialized] if (!lm--) { ^~~~~ fs/ntfs3/namei.c:487:12: note: uninitialized use occurs here __putname(uni1); ^~~~ ./include/linux/fs.h:2789:65: note: expanded from macro '__putname' #define __putname(name) kmem_cache_free(names_cachep, (void *)(name)) ^~~~ fs/ntfs3/namei.c:434:3: note: remove the 'if' if its condition is always false if (!lm--) { ^~~~~~~~~~~~ fs/ntfs3/namei.c:430:22: note: initialize the variable 'uni1' to silence this warning struct cpu_str *uni1, *uni2; ^ = NULL 2 errors generated. There is no point in calling __putname() in these particular error paths, as there has been no corresponding __getname() call yet. Just return directly in these blocks to clear up the warning. Fixes: a3a956c78efa ("fs/ntfs3: Add option "nocase"") Link: https://github.com/ClangBuiltLinux/linux/issues/1729 Signed-off-by: Nathan Chancellor <nathan@kernel.org> Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
2022-10-04 22:41:45 +08:00
if (toupper(c1) != toupper(c2))
return 1;
}
/*
* Try slow way with current upcase table
*/
sbi = dentry->d_sb->s_fs_info;
uni1 = __getname();
if (!uni1)
return -ENOMEM;
ret = ntfs_nls_to_utf16(sbi, str, len1, uni1, NTFS_NAME_LEN,
UTF16_HOST_ENDIAN);
if (ret < 0)
goto out;
if (!ret) {
ret = -EINVAL;
goto out;
}
uni2 = Add2Ptr(uni1, 2048);
ret = ntfs_nls_to_utf16(sbi, name->name, name->len,
(struct cpu_str *)uni2, NTFS_NAME_LEN,
UTF16_LITTLE_ENDIAN);
if (ret < 0)
goto out;
if (!ret) {
ret = -EINVAL;
goto out;
}
ret = !ntfs_cmp_names_cpu(uni1, uni2, sbi->upcase, false) ? 0 : 1;
out:
__putname(uni1);
return ret;
}
// clang-format off
const struct inode_operations ntfs_dir_inode_operations = {
.lookup = ntfs_lookup,
.create = ntfs_create,
.link = ntfs_link,
.unlink = ntfs_unlink,
.symlink = ntfs_symlink,
.mkdir = ntfs_mkdir,
.rmdir = ntfs_rmdir,
.mknod = ntfs_mknod,
.rename = ntfs_rename,
.permission = ntfs_permission,
.get_acl = ntfs_get_acl,
.set_acl = ntfs_set_acl,
.setattr = ntfs3_setattr,
.getattr = ntfs_getattr,
.listxattr = ntfs_listxattr,
.atomic_open = ntfs_atomic_open,
.fiemap = ntfs_fiemap,
};
const struct inode_operations ntfs_special_inode_operations = {
.setattr = ntfs3_setattr,
.getattr = ntfs_getattr,
.listxattr = ntfs_listxattr,
.get_acl = ntfs_get_acl,
.set_acl = ntfs_set_acl,
};
const struct dentry_operations ntfs_dentry_ops = {
.d_hash = ntfs_d_hash,
.d_compare = ntfs_d_compare,
};
// clang-format on