mirror of
https://github.com/torvalds/linux.git
synced 2026-11-06 21:58:01 +01:00
ntfs: add pathname access for named streams
Add an optional Windows-style pathname interface for named $DATA streams, enabled with streams_interface=windows. The option defaults to none, and the named-stream ioctls remain available regardless of this setting. Support lookup, creation, and removal of named streams for existing regular files and directories. A stream is opened as a regular file through the base-name and stream-name form in the final path component. A stream can be created only for an existing file or directory. The pathname interface accepts a base file name and stream name only. It rejects paths that also specify an NTFS attribute type such as $DATA. In windows mode, ordinary filenames containing a colon are hidden from directory listings and cannot be accessed through the pathname interface. Match stream names case-insensitively. Streams and their base objects report the same inode number. Removing the base object while a stream is open detaches the stream from the namespace. Since NTFS has no orphan-stream list, a crash can leave the stream data unreachable on disk. Expose named streams as regular file descriptors for the file operations requested by Wine, including read, write, fsync, fdatasync, sync_file_range, file locking, mmap, futimes, fstat, ftruncate, SEEK_DATA, SEEK_HOLE, fallocate, and pathname unlink. Stream timestamps are shared with the base file. fstat reports the base metadata and inode number with the stream's own size and allocation. Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
This commit is contained in:
parent
48996054b7
commit
3f11c48d4f
|
|
@ -5487,34 +5487,46 @@ int ntfs_attr_exist(struct ntfs_inode *ni, const __le32 type, __le16 *name,
|
|||
return !ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* If @attr_vi is non-NULL, the attribute inode reference is returned to the
|
||||
* caller, which must release it after dropping ni->mrec_lock.
|
||||
*/
|
||||
int ntfs_attr_remove(struct ntfs_inode *ni, const __le32 type, __le16 *name,
|
||||
u32 name_len)
|
||||
u32 name_len, struct inode **attr_vi)
|
||||
{
|
||||
int err;
|
||||
struct inode *attr_vi;
|
||||
struct inode *vi;
|
||||
struct ntfs_inode *attr_ni;
|
||||
|
||||
ntfs_debug("Entering\n");
|
||||
|
||||
if (attr_vi)
|
||||
*attr_vi = NULL;
|
||||
if (!ni)
|
||||
return -EINVAL;
|
||||
|
||||
attr_vi = ntfs_attr_iget(VFS_I(ni), type, name, name_len);
|
||||
if (IS_ERR(attr_vi)) {
|
||||
err = PTR_ERR(attr_vi);
|
||||
ntfs_error(ni->vol->sb,
|
||||
"Failed to open attribute 0x%02x of inode 0x%llx",
|
||||
type, (unsigned long long)ni->mft_no);
|
||||
vi = ntfs_attr_iget(VFS_I(ni), type, name, name_len);
|
||||
if (IS_ERR(vi)) {
|
||||
err = PTR_ERR(vi);
|
||||
ntfs_error(ni->vol->sb, "Failed to open attribute 0x%02x of inode 0x%llx",
|
||||
type, (unsigned long long)ni->mft_no);
|
||||
return err;
|
||||
}
|
||||
attr_ni = NTFS_I(attr_vi);
|
||||
attr_ni = NTFS_I(vi);
|
||||
|
||||
err = ntfs_attr_rm(attr_ni);
|
||||
if (err)
|
||||
ntfs_error(ni->vol->sb,
|
||||
"Failed to remove attribute 0x%02x of inode 0x%llx",
|
||||
type, (unsigned long long)ni->mft_no);
|
||||
iput(attr_vi);
|
||||
if (err) {
|
||||
ntfs_error(ni->vol->sb, "Failed to remove attribute 0x%02x of inode 0x%llx",
|
||||
type, (unsigned long long)ni->mft_no);
|
||||
} else {
|
||||
NInoClearDirty(attr_ni);
|
||||
clear_nlink(vi);
|
||||
remove_inode_hash(vi);
|
||||
}
|
||||
if (attr_vi)
|
||||
*attr_vi = vi;
|
||||
else
|
||||
iput(vi);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ int ntfs_attr_rm(struct ntfs_inode *ni);
|
|||
int ntfs_attr_exist(struct ntfs_inode *ni, const __le32 type, __le16 *name,
|
||||
u32 name_len);
|
||||
int ntfs_attr_remove(struct ntfs_inode *ni, const __le32 type, __le16 *name,
|
||||
u32 name_len);
|
||||
u32 name_len, struct inode **attr_vi);
|
||||
int ntfs_attr_record_rm(struct ntfs_attr_search_ctx *ctx);
|
||||
int ntfs_attr_record_move_to(struct ntfs_attr_search_ctx *ctx, struct ntfs_inode *ni);
|
||||
int ntfs_attr_add(struct ntfs_inode *ni, __le32 type,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
*/
|
||||
|
||||
#include <linux/blkdev.h>
|
||||
#include <linux/string.h>
|
||||
|
||||
#include "dir.h"
|
||||
#include "mft.h"
|
||||
|
|
@ -624,7 +625,6 @@ static inline int ntfs_filldir(struct ntfs_volume *vol,
|
|||
ntfs_debug("Skipping hidden file.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
name_len = ntfs_ucstonls(vol, (__le16 *)&ie->key.file_name.file_name,
|
||||
ie->key.file_name.file_name_length, &name,
|
||||
NTFS_MAX_NAME_LEN * NLS_MAX_CHARSET_SIZE + 1);
|
||||
|
|
@ -633,7 +633,10 @@ static inline int ntfs_filldir(struct ntfs_volume *vol,
|
|||
(long long)MREF_LE(ie->data.dir.indexed_file));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (NVolStreamsWindows(vol) && strchr((char *)name, ':')) {
|
||||
ntfs_debug("Skipping file name containing a stream separator.");
|
||||
return 0;
|
||||
}
|
||||
mref = MREF_LE(ie->data.dir.indexed_file);
|
||||
if (ie->key.file_name.file_attributes & FILE_ATTR_REPARSE_POINT)
|
||||
dt_type = ntfs_reparse_tag_dt_types(vol, mref);
|
||||
|
|
|
|||
21
fs/ntfs/ea.c
21
fs/ntfs/ea.c
|
|
@ -246,7 +246,7 @@ static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len,
|
|||
goto out;
|
||||
|
||||
if (ntfs_attr_exist(ni, AT_EA, AT_UNNAMED, 0)) {
|
||||
err = ntfs_attr_remove(ni, AT_EA, AT_UNNAMED, 0);
|
||||
err = ntfs_attr_remove(ni, AT_EA, AT_UNNAMED, 0, NULL);
|
||||
if (err)
|
||||
goto out;
|
||||
}
|
||||
|
|
@ -301,11 +301,12 @@ static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len,
|
|||
p_ea_info->ea_query_length = cpu_to_le32(ea_info_qsize);
|
||||
|
||||
if ((flags & XATTR_REPLACE) && !val_size && !ea_info_qsize) {
|
||||
err = ntfs_attr_remove(ni, AT_EA, AT_UNNAMED, 0);
|
||||
err = ntfs_attr_remove(ni, AT_EA, AT_UNNAMED, 0, NULL);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
err = ntfs_attr_remove(ni, AT_EA_INFORMATION, AT_UNNAMED, 0);
|
||||
err = ntfs_attr_remove(ni, AT_EA_INFORMATION, AT_UNNAMED,
|
||||
0, NULL);
|
||||
if (err) {
|
||||
/* Restore the original $EA if $EA_INFORMATION removal failed. */
|
||||
ntfs_attr_add(ni, AT_EA, AT_UNNAMED, 0, old_ea_buf,
|
||||
|
|
@ -610,6 +611,14 @@ static int ntfs_getxattr(const struct xattr_handler *handler,
|
|||
struct ntfs_inode *ni = NTFS_I(inode);
|
||||
int err;
|
||||
|
||||
/*
|
||||
* Stream permissions and privileges belong to the base inode. This
|
||||
* also lets VFS killpriv helpers find the base security attributes.
|
||||
*/
|
||||
if (ntfs_inode_is_named_stream(ni)) {
|
||||
ni = ni->ext.base_ntfs_ino;
|
||||
inode = VFS_I(ni);
|
||||
}
|
||||
if (NVolShutdown(ni->vol))
|
||||
return -EIO;
|
||||
|
||||
|
|
@ -716,8 +725,8 @@ static int ntfs_new_attr_flags(struct ntfs_inode *ni, __le32 fattr)
|
|||
goto err_out;
|
||||
|
||||
ntfs_attr_reinit_search_ctx(ctx);
|
||||
err = ntfs_attr_lookup(ni->type, ni->name,
|
||||
ni->name_len, CASE_SENSITIVE,
|
||||
err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len,
|
||||
CASE_SENSITIVE,
|
||||
0, NULL, 0, ctx);
|
||||
if (err) {
|
||||
err = -EINVAL;
|
||||
|
|
@ -883,6 +892,8 @@ static int ntfs_setxattr(const struct xattr_handler *handler,
|
|||
int err;
|
||||
__le32 fattr;
|
||||
|
||||
if (ntfs_inode_is_named_stream(ni))
|
||||
return -EOPNOTSUPP;
|
||||
if (NVolShutdown(ni->vol))
|
||||
return -EIO;
|
||||
|
||||
|
|
|
|||
117
fs/ntfs/file.c
117
fs/ntfs/file.c
|
|
@ -17,8 +17,10 @@
|
|||
#include <linux/falloc.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/filelock.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/overflow.h>
|
||||
#include <linux/security.h>
|
||||
#include <linux/slab.h>
|
||||
#include <uapi/linux/ntfs.h>
|
||||
|
||||
#include "lcnalloc.h"
|
||||
|
|
@ -142,6 +144,11 @@ int ntfs_file_release(struct inode *vi, struct file *filp)
|
|||
return 0;
|
||||
}
|
||||
|
||||
struct ntfs_fsync_attr {
|
||||
struct list_head list;
|
||||
struct inode *inode;
|
||||
};
|
||||
|
||||
/*
|
||||
* ntfs_file_fsync - sync a file to disk
|
||||
* @filp: file to be synced
|
||||
|
|
@ -171,6 +178,8 @@ int ntfs_file_fsync(struct file *filp, loff_t start, loff_t end,
|
|||
int err, ret = 0;
|
||||
struct inode *parent_vi, *ia_vi;
|
||||
struct ntfs_attr_search_ctx *ctx;
|
||||
struct ntfs_fsync_attr *attr, *next;
|
||||
LIST_HEAD(attrs);
|
||||
bool non_resident, stream;
|
||||
|
||||
ntfs_debug("Entering for inode 0x%llx.", ni->mft_no);
|
||||
|
|
@ -195,8 +204,7 @@ int ntfs_file_fsync(struct file *filp, loff_t start, loff_t end,
|
|||
|
||||
/*
|
||||
* file_write_and_wait_range() already flushed this stream mapping.
|
||||
* Do not walk sibling attribute mappings while holding the base MFT
|
||||
* lock; ordinary file fsync retains its existing behavior below.
|
||||
* A stream fsync does not need to flush sibling attribute mappings.
|
||||
*/
|
||||
if (stream)
|
||||
goto sync_volume;
|
||||
|
|
@ -232,22 +240,53 @@ int ntfs_file_fsync(struct file *filp, loff_t start, loff_t end,
|
|||
name = (__le16 *)((u8 *)ctx->attr + le16_to_cpu(ctx->attr->name_offset));
|
||||
if (ctx->attr->type == AT_DATA && ctx->attr->name_length == 0)
|
||||
continue;
|
||||
if (ctx->attr->type == AT_DATA &&
|
||||
ntfs_stream_unlinked(ni, name, ctx->attr->name_length))
|
||||
continue;
|
||||
|
||||
attr = kmalloc_obj(*attr, GFP_NOFS);
|
||||
if (!attr) {
|
||||
err = -ENOMEM;
|
||||
break;
|
||||
}
|
||||
attr_vi = ntfs_attr_iget(vi, ctx->attr->type,
|
||||
name, ctx->attr->name_length);
|
||||
if (IS_ERR(attr_vi))
|
||||
if (IS_ERR(attr_vi)) {
|
||||
kfree(attr);
|
||||
continue;
|
||||
spin_lock(&attr_vi->i_lock);
|
||||
if (inode_state_read_once(attr_vi) & I_DIRTY_PAGES) {
|
||||
spin_unlock(&attr_vi->i_lock);
|
||||
filemap_write_and_wait(attr_vi->i_mapping);
|
||||
} else
|
||||
spin_unlock(&attr_vi->i_lock);
|
||||
iput(attr_vi);
|
||||
}
|
||||
if (ntfs_inode_is_named_stream(NTFS_I(attr_vi)))
|
||||
atomic_inc(&NTFS_I(attr_vi)->stream_open_count);
|
||||
attr->inode = attr_vi;
|
||||
list_add_tail(&attr->list, &attrs);
|
||||
}
|
||||
}
|
||||
mutex_unlock(&ni->mrec_lock);
|
||||
ntfs_attr_put_search_ctx(ctx);
|
||||
if (err != -ENOENT && !ret)
|
||||
ret = err;
|
||||
|
||||
/* Attribute writeback takes the base inode's MFT record lock. */
|
||||
list_for_each_entry_safe(attr, next, &attrs, list) {
|
||||
struct inode *attr_vi = attr->inode;
|
||||
|
||||
err = filemap_write_and_wait(attr_vi->i_mapping);
|
||||
if (err && !ret)
|
||||
ret = err;
|
||||
if (ntfs_inode_is_named_stream(NTFS_I(attr_vi))) {
|
||||
err = ntfs_stream_put(attr_vi);
|
||||
if (err && !ret)
|
||||
ret = err;
|
||||
}
|
||||
iput(attr_vi);
|
||||
list_del(&attr->list);
|
||||
kfree(attr);
|
||||
}
|
||||
|
||||
/* Attribute writeback may have updated the base mapping pairs. */
|
||||
err = write_inode_now(vi, 1);
|
||||
if (err && !ret)
|
||||
ret = err;
|
||||
|
||||
sync_volume:
|
||||
write_inode_now(vol->mftbmp_ino, 1);
|
||||
|
|
@ -295,7 +334,6 @@ int ntfs_setattr_size(struct inode *vi, struct iattr *attr)
|
|||
{
|
||||
struct ntfs_inode *ni = NTFS_I(vi);
|
||||
struct ntfs_inode *base_ni = ntfs_base_inode(ni);
|
||||
struct inode *time_vi = vi;
|
||||
bool stream = ntfs_inode_is_named_stream(ni);
|
||||
int err;
|
||||
loff_t old_size = vi->i_size;
|
||||
|
|
@ -333,7 +371,6 @@ int ntfs_setattr_size(struct inode *vi, struct iattr *attr)
|
|||
mutex_unlock(&base_ni->mrec_lock);
|
||||
if (err)
|
||||
goto out_unlock_mapping;
|
||||
time_vi = VFS_I(base_ni);
|
||||
} else {
|
||||
filemap_invalidate_lock(vi->i_mapping);
|
||||
}
|
||||
|
|
@ -361,14 +398,10 @@ int ntfs_setattr_size(struct inode *vi, struct iattr *attr)
|
|||
goto out_unlock_mapping;
|
||||
}
|
||||
|
||||
if (stream) {
|
||||
inode_set_mtime_to_ts(time_vi,
|
||||
inode_set_ctime_current(time_vi));
|
||||
mark_inode_dirty(time_vi);
|
||||
}
|
||||
|
||||
out_unlock_mapping:
|
||||
filemap_invalidate_unlock(vi->i_mapping);
|
||||
if (!err && stream)
|
||||
ntfs_stream_update_base_time(base_ni);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
|
@ -500,6 +533,24 @@ int ntfs_getattr(const struct mnt_idmap *idmap, const struct path *path,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Validate a stream before VFS write handling removes privileges or updates
|
||||
* timestamps on its base inode.
|
||||
*/
|
||||
static int ntfs_file_modified(struct kiocb *iocb)
|
||||
{
|
||||
struct inode *inode = file_inode(iocb->ki_filp);
|
||||
int err;
|
||||
|
||||
if (ntfs_inode_is_named_stream(NTFS_I(inode))) {
|
||||
err = ntfs_stream_validate_for_mutation(inode,
|
||||
iocb->ki_flags & IOCB_NOWAIT);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
return kiocb_modified(iocb);
|
||||
}
|
||||
|
||||
loff_t ntfs_file_llseek(struct file *file, loff_t offset, int whence)
|
||||
{
|
||||
struct inode *inode = file->f_mapping->host;
|
||||
|
|
@ -710,7 +761,7 @@ ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
|
|||
if (ret <= 0)
|
||||
goto out_lock;
|
||||
|
||||
err = file_modified(iocb->ki_filp);
|
||||
err = ntfs_file_modified(iocb);
|
||||
if (err) {
|
||||
ret = err;
|
||||
goto out_lock;
|
||||
|
|
@ -788,13 +839,24 @@ static vm_fault_t ntfs_filemap_page_mkwrite(struct vm_fault *vmf)
|
|||
{
|
||||
struct inode *inode = file_inode(vmf->vma->vm_file);
|
||||
struct address_space *mapping = inode->i_mapping;
|
||||
bool stream = ntfs_inode_is_named_stream(NTFS_I(inode));
|
||||
int err;
|
||||
vm_fault_t ret;
|
||||
|
||||
if (NInoWofCompressed(NTFS_I(inode)))
|
||||
return VM_FAULT_SIGBUS;
|
||||
|
||||
sb_start_pagefault(inode->i_sb);
|
||||
file_update_time(vmf->vma->vm_file);
|
||||
if (stream) {
|
||||
err = ntfs_stream_validate_for_mutation(inode, false);
|
||||
if (err)
|
||||
goto out_error;
|
||||
err = file_update_time(vmf->vma->vm_file);
|
||||
if (err)
|
||||
goto out_error;
|
||||
} else {
|
||||
file_update_time(vmf->vma->vm_file);
|
||||
}
|
||||
|
||||
/*
|
||||
* Serialize against truncate/fallocate which hold the lock
|
||||
|
|
@ -805,6 +867,10 @@ static vm_fault_t ntfs_filemap_page_mkwrite(struct vm_fault *vmf)
|
|||
filemap_invalidate_unlock_shared(mapping);
|
||||
sb_end_pagefault(inode->i_sb);
|
||||
return ret;
|
||||
|
||||
out_error:
|
||||
sb_end_pagefault(inode->i_sb);
|
||||
return vmf_error(err);
|
||||
}
|
||||
|
||||
static const struct vm_operations_struct ntfs_file_vm_ops = {
|
||||
|
|
@ -1281,6 +1347,13 @@ long ntfs_fallocate(struct file *file, int mode, loff_t offset, loff_t len)
|
|||
inode_unlock(vi);
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
if (stream) {
|
||||
err = ntfs_stream_validate_for_mutation(vi, false);
|
||||
if (err) {
|
||||
inode_unlock(vi);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
old_size = i_size_read(vi);
|
||||
|
||||
inode_dio_wait(vi);
|
||||
|
|
@ -1322,9 +1395,7 @@ long ntfs_fallocate(struct file *file, int mode, loff_t offset, loff_t len)
|
|||
|
||||
if (!err) {
|
||||
if (stream) {
|
||||
inode_set_mtime_to_ts(VFS_I(base_ni),
|
||||
inode_set_ctime_current(VFS_I(base_ni)));
|
||||
mark_inode_dirty(VFS_I(base_ni));
|
||||
ntfs_stream_update_base_time(base_ni);
|
||||
} else {
|
||||
NInoSetFileNameDirty(ni);
|
||||
inode_set_mtime_to_ts(vi, inode_set_ctime_current(vi));
|
||||
|
|
|
|||
|
|
@ -1547,6 +1547,10 @@ static int ntfs_read_locked_attr_inode(struct inode *base_vi, struct inode *vi)
|
|||
vi->i_blocks = ni->itype.compressed.size >> 9;
|
||||
else
|
||||
vi->i_blocks = ni->allocated_size >> 9;
|
||||
if (ni->type == AT_DATA && ni->name_len) {
|
||||
vi->i_op = &ntfs_stream_inode_ops;
|
||||
vi->i_fop = &ntfs_stream_file_ops;
|
||||
}
|
||||
/*
|
||||
* Make sure the base inode does not go away and attach it to the
|
||||
* attribute inode.
|
||||
|
|
@ -2537,6 +2541,10 @@ int ntfs_show_options(struct seq_file *sf, struct dentry *root)
|
|||
seq_puts(sf, ",symlink=native");
|
||||
else
|
||||
seq_puts(sf, ",symlink=wsl");
|
||||
if (NVolStreamsWindows(vol))
|
||||
seq_puts(sf, ",streams_interface=windows");
|
||||
else
|
||||
seq_puts(sf, ",streams_interface=none");
|
||||
if (vol->sb->s_flags & SB_POSIXACL)
|
||||
seq_puts(sf, ",acl");
|
||||
return 0;
|
||||
|
|
@ -2597,15 +2605,19 @@ int ntfs_extend_initialized_size(struct inode *vi, const loff_t offset,
|
|||
int ntfs_truncate_vfs(struct inode *vi, loff_t new_size, loff_t i_size)
|
||||
{
|
||||
struct ntfs_inode *ni = NTFS_I(vi);
|
||||
struct ntfs_inode *mrec_ni = ntfs_base_inode(ni);
|
||||
int err;
|
||||
|
||||
mutex_lock(&ni->mrec_lock);
|
||||
mutex_lock(&mrec_ni->mrec_lock);
|
||||
err = __ntfs_attr_truncate_vfs(ni, new_size, i_size);
|
||||
mutex_unlock(&ni->mrec_lock);
|
||||
mutex_unlock(&mrec_ni->mrec_lock);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
inode_set_mtime_to_ts(vi, inode_set_ctime_current(vi));
|
||||
if (ntfs_inode_is_named_stream(ni))
|
||||
ntfs_stream_update_base_time(mrec_ni);
|
||||
else
|
||||
inode_set_mtime_to_ts(vi, inode_set_ctime_current(vi));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -90,11 +90,7 @@ static int ntfs_read_iomap_begin_resident(struct inode *inode, loff_t offset, lo
|
|||
int err = 0;
|
||||
char *kattr;
|
||||
|
||||
if (NInoAttr(ni))
|
||||
base_ni = ni->ext.base_ntfs_ino;
|
||||
else
|
||||
base_ni = ni;
|
||||
|
||||
base_ni = ntfs_base_inode(ni);
|
||||
mutex_lock(&base_ni->mrec_lock);
|
||||
|
||||
ctx = ntfs_attr_get_search_ctx(base_ni, NULL);
|
||||
|
|
@ -140,7 +136,8 @@ static int ntfs_read_iomap_begin_resident(struct inode *inode, loff_t offset, lo
|
|||
if (ctx)
|
||||
ntfs_attr_put_search_ctx(ctx);
|
||||
|
||||
if (!err && keep_mrec_lock && iomap->type == IOMAP_INLINE) {
|
||||
if (!err && keep_mrec_lock &&
|
||||
iomap->type == IOMAP_INLINE) {
|
||||
iomap->private = base_ni;
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -390,6 +387,8 @@ static int ntfs_write_simple_iomap_begin_non_resident(struct inode *inode, loff_
|
|||
loff_t length, struct iomap *iomap)
|
||||
{
|
||||
struct ntfs_inode *ni = NTFS_I(inode);
|
||||
struct ntfs_inode *mrec_ni = ntfs_inode_is_named_stream(ni) ?
|
||||
ntfs_base_inode(ni) : ni;
|
||||
struct ntfs_volume *vol = ni->vol;
|
||||
loff_t vcn_ofs, rl_length;
|
||||
struct runlist_element *rl, *rlc;
|
||||
|
|
@ -408,7 +407,7 @@ static int ntfs_write_simple_iomap_begin_non_resident(struct inode *inode, loff_
|
|||
up_read(&ni->runlist.lock);
|
||||
err = ntfs_map_runlist(ni, vcn);
|
||||
if (err) {
|
||||
mutex_unlock(&ni->mrec_lock);
|
||||
mutex_unlock(&mrec_ni->mrec_lock);
|
||||
return -ENOENT;
|
||||
}
|
||||
down_read(&ni->runlist.lock);
|
||||
|
|
@ -422,7 +421,7 @@ static int ntfs_write_simple_iomap_begin_non_resident(struct inode *inode, loff_
|
|||
rl = __ntfs_attr_find_vcn_nolock(&ni->runlist, vcn);
|
||||
if (IS_ERR(rl)) {
|
||||
up_write(&ni->runlist.lock);
|
||||
mutex_unlock(&ni->mrec_lock);
|
||||
mutex_unlock(&mrec_ni->mrec_lock);
|
||||
return -EIO;
|
||||
}
|
||||
lcn = ntfs_rl_vcn_to_lcn(rl, vcn);
|
||||
|
|
@ -443,7 +442,7 @@ static int ntfs_write_simple_iomap_begin_non_resident(struct inode *inode, loff_
|
|||
*/
|
||||
if (lcn == LCN_RL_NOT_MAPPED) {
|
||||
up_write(&ni->runlist.lock);
|
||||
mutex_unlock(&ni->mrec_lock);
|
||||
mutex_unlock(&mrec_ni->mrec_lock);
|
||||
return map_err == -ENOMEM ? -ENOMEM : -EIO;
|
||||
}
|
||||
|
||||
|
|
@ -453,7 +452,7 @@ static int ntfs_write_simple_iomap_begin_non_resident(struct inode *inode, loff_
|
|||
"runlist(vcn : %lld, length : %lld) is corrupted\n",
|
||||
rl->vcn, rl->length);
|
||||
up_write(&ni->runlist.lock);
|
||||
mutex_unlock(&ni->mrec_lock);
|
||||
mutex_unlock(&mrec_ni->mrec_lock);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
|
|
@ -467,7 +466,7 @@ static int ntfs_write_simple_iomap_begin_non_resident(struct inode *inode, loff_
|
|||
if (max_clu_count < 0) {
|
||||
err = max_clu_count;
|
||||
up_write(&ni->runlist.lock);
|
||||
mutex_unlock(&ni->mrec_lock);
|
||||
mutex_unlock(&mrec_ni->mrec_lock);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
|
@ -482,7 +481,7 @@ static int ntfs_write_simple_iomap_begin_non_resident(struct inode *inode, loff_
|
|||
GFP_NOFS);
|
||||
if (!rlc) {
|
||||
up_write(&ni->runlist.lock);
|
||||
mutex_unlock(&ni->mrec_lock);
|
||||
mutex_unlock(&mrec_ni->mrec_lock);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
|
|
@ -499,7 +498,7 @@ static int ntfs_write_simple_iomap_begin_non_resident(struct inode *inode, loff_
|
|||
if (IS_ERR(rl)) {
|
||||
ntfs_error(vol->sb, "Failed to merge runlists");
|
||||
up_write(&ni->runlist.lock);
|
||||
mutex_unlock(&ni->mrec_lock);
|
||||
mutex_unlock(&mrec_ni->mrec_lock);
|
||||
kvfree(rlc);
|
||||
return PTR_ERR(rl);
|
||||
}
|
||||
|
|
@ -509,7 +508,7 @@ static int ntfs_write_simple_iomap_begin_non_resident(struct inode *inode, loff_
|
|||
ni->i_dealloc_clusters += max_clu_count;
|
||||
}
|
||||
up_write(&ni->runlist.lock);
|
||||
mutex_unlock(&ni->mrec_lock);
|
||||
mutex_unlock(&mrec_ni->mrec_lock);
|
||||
|
||||
if (lcn < LCN_DELALLOC)
|
||||
ntfs_hold_dirty_clusters(vol, max_clu_count);
|
||||
|
|
@ -561,7 +560,7 @@ static int ntfs_write_simple_iomap_begin_non_resident(struct inode *inode, loff_
|
|||
}
|
||||
} else {
|
||||
up_write(&ni->runlist.lock);
|
||||
mutex_unlock(&ni->mrec_lock);
|
||||
mutex_unlock(&mrec_ni->mrec_lock);
|
||||
|
||||
iomap->type = IOMAP_MAPPED;
|
||||
iomap->addr = ntfs_cluster_to_bytes(vol, lcn) + vcn_ofs;
|
||||
|
|
@ -586,6 +585,8 @@ static int ntfs_write_da_iomap_begin_non_resident(struct inode *inode,
|
|||
struct iomap *iomap, int ntfs_iomap_flags)
|
||||
{
|
||||
struct ntfs_inode *ni = NTFS_I(inode);
|
||||
struct ntfs_inode *mrec_ni = ntfs_inode_is_named_stream(ni) ?
|
||||
ntfs_base_inode(ni) : ni;
|
||||
struct ntfs_volume *vol = ni->vol;
|
||||
loff_t vcn_ofs, rl_length;
|
||||
s64 vcn, start_lcn, lcn_count;
|
||||
|
|
@ -604,7 +605,7 @@ static int ntfs_write_da_iomap_begin_non_resident(struct inode *inode,
|
|||
max_clu_count, &balloc, update_mp,
|
||||
ntfs_iomap_flags & NTFS_IOMAP_FLAGS_WRITEBACK);
|
||||
up_write(&ni->runlist.lock);
|
||||
mutex_unlock(&ni->mrec_lock);
|
||||
mutex_unlock(&mrec_ni->mrec_lock);
|
||||
if (err) {
|
||||
ni->i_dealloc_clusters = 0;
|
||||
return err;
|
||||
|
|
@ -658,8 +659,8 @@ static int ntfs_write_da_iomap_begin_non_resident(struct inode *inode,
|
|||
|
||||
if (ntfs_iomap_flags & NTFS_IOMAP_FLAGS_MKWRITE &&
|
||||
iomap->offset + iomap->length > ni->initialized_size) {
|
||||
err = ntfs_attr_set_initialized_size(ni, iomap->offset +
|
||||
iomap->length);
|
||||
err = ntfs_attr_set_initialized_size(ni,
|
||||
iomap->offset + iomap->length);
|
||||
}
|
||||
|
||||
return err;
|
||||
|
|
@ -669,13 +670,15 @@ static int ntfs_write_iomap_begin_resident(struct inode *inode, loff_t offset,
|
|||
struct iomap *iomap)
|
||||
{
|
||||
struct ntfs_inode *ni = NTFS_I(inode);
|
||||
struct ntfs_inode *mrec_ni = ntfs_inode_is_named_stream(ni) ?
|
||||
ntfs_base_inode(ni) : ni;
|
||||
struct attr_record *a;
|
||||
struct ntfs_attr_search_ctx *ctx;
|
||||
struct ntfs_attr_search_ctx *ctx = NULL;
|
||||
u32 attr_len;
|
||||
int err = 0;
|
||||
char *kattr;
|
||||
|
||||
ctx = ntfs_attr_get_search_ctx(ni, NULL);
|
||||
ctx = ntfs_attr_get_search_ctx(mrec_ni, NULL);
|
||||
if (!ctx) {
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
|
|
@ -698,13 +701,14 @@ static int ntfs_write_iomap_begin_resident(struct inode *inode, loff_t offset,
|
|||
iomap->inline_data = kattr;
|
||||
iomap->offset = 0;
|
||||
iomap->length = attr_len;
|
||||
iomap->private = mrec_ni;
|
||||
|
||||
out:
|
||||
if (ctx)
|
||||
ntfs_attr_put_search_ctx(ctx);
|
||||
|
||||
if (err)
|
||||
mutex_unlock(&ni->mrec_lock);
|
||||
mutex_unlock(&mrec_ni->mrec_lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
|
@ -713,7 +717,12 @@ static int ntfs_write_iomap_begin_non_resident(struct inode *inode, loff_t offse
|
|||
loff_t length, unsigned int flags,
|
||||
struct iomap *iomap, int ntfs_iomap_flags)
|
||||
{
|
||||
mutex_lock(&NTFS_I(inode)->mrec_lock);
|
||||
struct ntfs_inode *ni = NTFS_I(inode);
|
||||
struct ntfs_inode *mrec_ni = ntfs_inode_is_named_stream(ni) ?
|
||||
ntfs_base_inode(ni) : ni;
|
||||
|
||||
mutex_lock(&mrec_ni->mrec_lock);
|
||||
|
||||
if (ntfs_iomap_flags & NTFS_IOMAP_FLAGS_BEGIN)
|
||||
return ntfs_write_simple_iomap_begin_non_resident(inode, offset,
|
||||
length, iomap);
|
||||
|
|
@ -729,12 +738,15 @@ static int __ntfs_write_iomap_begin(struct inode *inode, loff_t offset,
|
|||
struct iomap *iomap, int ntfs_iomap_flags)
|
||||
{
|
||||
struct ntfs_inode *ni = NTFS_I(inode);
|
||||
struct ntfs_inode *mrec_ni;
|
||||
|
||||
if (NVolShutdown(ni->vol))
|
||||
return -EIO;
|
||||
|
||||
if (!NInoNonResident(ni)) {
|
||||
mutex_lock(&ni->mrec_lock);
|
||||
mrec_ni = ntfs_inode_is_named_stream(ni) ?
|
||||
ntfs_base_inode(ni) : ni;
|
||||
mutex_lock(&mrec_ni->mrec_lock);
|
||||
return ntfs_write_iomap_begin_resident(inode, offset, iomap);
|
||||
}
|
||||
return ntfs_write_iomap_begin_non_resident(inode, offset, length, flags,
|
||||
|
|
@ -753,10 +765,10 @@ static int ntfs_write_iomap_end_resident(struct inode *inode, loff_t pos,
|
|||
loff_t length, ssize_t written,
|
||||
unsigned int flags, struct iomap *iomap)
|
||||
{
|
||||
struct ntfs_inode *ni = NTFS_I(inode);
|
||||
struct ntfs_inode *base_ni = iomap->private;
|
||||
|
||||
mark_mft_record_dirty(ni);
|
||||
mutex_unlock(&ni->mrec_lock);
|
||||
mark_mft_record_dirty(base_ni);
|
||||
mutex_unlock(&base_ni->mrec_lock);
|
||||
return written;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
#include <linux/file.h>
|
||||
#include <linux/namei.h>
|
||||
#include <linux/overflow.h>
|
||||
|
||||
#include <uapi/linux/ntfs.h>
|
||||
|
|
@ -176,6 +177,598 @@ bool ntfs_stream_unlinked(struct ntfs_inode *base_ni,
|
|||
return unlinked;
|
||||
}
|
||||
|
||||
/*
|
||||
* ntfs_stream_path_parse() - Split a pathname stream component
|
||||
* @vol: NTFS volume
|
||||
* @qname: final pathname component
|
||||
* @path: receives slices of @qname on success
|
||||
*
|
||||
* Parse the ``file:stream`` form when the Windows pathname interface is
|
||||
* enabled. This does not convert or validate the component names.
|
||||
*
|
||||
* Return: 1 if stream syntax was parsed, 0 if streams are disabled or no
|
||||
* stream separator is present, or -EINVAL for malformed syntax.
|
||||
*/
|
||||
int ntfs_stream_path_parse(struct ntfs_volume *vol,
|
||||
const struct qstr *qname, struct ntfs_stream_path *path)
|
||||
{
|
||||
const unsigned char *colon;
|
||||
|
||||
if (!NVolStreamsWindows(vol))
|
||||
return 0;
|
||||
|
||||
colon = memchr(qname->name, ':', qname->len);
|
||||
if (!colon)
|
||||
return 0;
|
||||
if (colon == qname->name || colon + 1 == qname->name + qname->len)
|
||||
return -EINVAL;
|
||||
if (memchr(colon + 1, ':', qname->name + qname->len - colon - 1))
|
||||
return -EINVAL;
|
||||
|
||||
path->base_name = (const char *)qname->name;
|
||||
path->base_len = colon - qname->name;
|
||||
path->stream_name = (const char *)colon + 1;
|
||||
path->stream_len = qname->name + qname->len - colon - 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* ntfs_stream_path_has_colon() - Check for a pathname stream separator
|
||||
* @vol: NTFS volume
|
||||
* @qname: final pathname component
|
||||
*
|
||||
* Return: true if the Windows pathname interface is enabled and @qname
|
||||
* contains a colon, or false otherwise.
|
||||
*/
|
||||
bool ntfs_stream_path_has_colon(struct ntfs_volume *vol,
|
||||
const struct qstr *qname)
|
||||
{
|
||||
return NVolStreamsWindows(vol) &&
|
||||
memchr(qname->name, ':', qname->len);
|
||||
}
|
||||
|
||||
/*
|
||||
* ntfs_stream_path_check() - Reject stream syntax for unsupported operations
|
||||
* @vol: NTFS volume
|
||||
* @qname: final pathname component
|
||||
*
|
||||
* Return: 0 for an ordinary name or when streams are disabled,
|
||||
* -EOPNOTSUPP for parsed stream syntax, or -EINVAL for malformed syntax.
|
||||
*/
|
||||
int ntfs_stream_path_check(struct ntfs_volume *vol,
|
||||
const struct qstr *qname)
|
||||
{
|
||||
struct ntfs_stream_path path;
|
||||
int ret;
|
||||
|
||||
ret = ntfs_stream_path_parse(vol, qname, &path);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return ret ? -EOPNOTSUPP : 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* ntfs_stream_lookup_inode_by_name() - Look up a base inode by name
|
||||
* @dir_ino: directory containing the base file or directory
|
||||
* @uname: UTF-16LE base name
|
||||
* @uname_len: Length of @uname in UTF-16 code units
|
||||
*
|
||||
* Resolve the directory entry and verify its MFT reference before returning
|
||||
* the inode.
|
||||
*
|
||||
* Return: Referenced inode on success, or ERR_PTR() on failure.
|
||||
*/
|
||||
struct inode *ntfs_stream_lookup_inode_by_name(struct inode *dir_ino,
|
||||
__le16 *uname, int uname_len)
|
||||
{
|
||||
struct ntfs_volume *vol = NTFS_SB(dir_ino->i_sb);
|
||||
struct ntfs_name *name = NULL;
|
||||
struct inode *inode;
|
||||
u64 mref;
|
||||
|
||||
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);
|
||||
kfree(name);
|
||||
|
||||
if (IS_ERR_MREF(mref))
|
||||
return ERR_PTR(MREF_ERR(mref));
|
||||
|
||||
inode = ntfs_iget(vol->sb, MREF(mref));
|
||||
if (IS_ERR(inode))
|
||||
return inode;
|
||||
if (MSEQNO(mref) != NTFS_I(inode)->seq_no &&
|
||||
MREF(mref) != FILE_MFT) {
|
||||
iput(inode);
|
||||
return ERR_PTR(-EIO);
|
||||
}
|
||||
return inode;
|
||||
}
|
||||
|
||||
/*
|
||||
* Recheck that a stream dentry still names the same base inode and DATA
|
||||
* attribute.
|
||||
*/
|
||||
static int ntfs_stream_d_revalidate(struct inode *dir,
|
||||
const struct qstr *qname, struct dentry *dentry,
|
||||
unsigned int flags)
|
||||
{
|
||||
struct inode *inode = d_inode(dentry);
|
||||
struct ntfs_inode *ni;
|
||||
struct ntfs_stream_path path;
|
||||
struct inode *base_vi;
|
||||
__le16 *base_name = NULL, *stream_name = NULL;
|
||||
int base_len, stream_len, err, ret = 0;
|
||||
|
||||
if (flags & LOOKUP_RCU)
|
||||
return -ECHILD;
|
||||
if (!inode)
|
||||
return 0;
|
||||
if (!inode->i_nlink)
|
||||
return 0;
|
||||
if (NVolShutdown(NTFS_SB(dir->i_sb)))
|
||||
return 0;
|
||||
|
||||
ni = NTFS_I(inode);
|
||||
if (!ntfs_inode_is_named_stream(ni))
|
||||
return 0;
|
||||
|
||||
err = ntfs_stream_path_parse(NTFS_SB(dir->i_sb), qname, &path);
|
||||
if (err <= 0)
|
||||
return 0;
|
||||
base_len = ntfs_nlstoucs(NTFS_SB(dir->i_sb), path.base_name,
|
||||
path.base_len, &base_name, NTFS_MAX_NAME_LEN);
|
||||
if (base_len < 0)
|
||||
goto out;
|
||||
stream_len = ntfs_nlstoucs(NTFS_SB(dir->i_sb), path.stream_name,
|
||||
path.stream_len, &stream_name, NTFS_MAX_NAME_LEN);
|
||||
if (stream_len < 0)
|
||||
goto out;
|
||||
if (ntfs_check_stream_name(stream_name, stream_len))
|
||||
goto out;
|
||||
if (stream_len != ni->name_len ||
|
||||
!ntfs_names_are_equal(stream_name, stream_len, ni->name,
|
||||
ni->name_len, IGNORE_CASE, ni->vol->upcase,
|
||||
ni->vol->upcase_len))
|
||||
goto out;
|
||||
|
||||
base_vi = ntfs_stream_lookup_inode_by_name(dir, base_name, base_len);
|
||||
if (IS_ERR(base_vi))
|
||||
goto out;
|
||||
if (NTFS_I(base_vi) != ni->ext.base_ntfs_ino) {
|
||||
iput(base_vi);
|
||||
goto out;
|
||||
}
|
||||
|
||||
mutex_lock(&NTFS_I(base_vi)->mrec_lock);
|
||||
err = ntfs_stream_inode_validate(inode);
|
||||
mutex_unlock(&NTFS_I(base_vi)->mrec_lock);
|
||||
iput(base_vi);
|
||||
if (!err)
|
||||
ret = 1;
|
||||
out:
|
||||
if (stream_name)
|
||||
kmem_cache_free(ntfs_name_cache, stream_name);
|
||||
if (base_name)
|
||||
kmem_cache_free(ntfs_name_cache, base_name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Revalidate stream paths while leaving ordinary NTFS dentries cacheable. */
|
||||
static int ntfs_dentry_revalidate(struct inode *dir,
|
||||
const struct qstr *qname, struct dentry *dentry,
|
||||
unsigned int flags)
|
||||
{
|
||||
struct inode *inode = d_inode(dentry);
|
||||
struct ntfs_volume *vol = NTFS_SB(dir->i_sb);
|
||||
|
||||
if (ntfs_stream_path_has_colon(vol, qname))
|
||||
return ntfs_stream_d_revalidate(dir, qname, dentry, flags);
|
||||
if (inode && ntfs_inode_is_named_stream(NTFS_I(inode)))
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Drop stream-path aliases so future lookups recheck their backing attribute. */
|
||||
static int ntfs_dentry_delete(const struct dentry *dentry)
|
||||
{
|
||||
struct inode *inode = d_inode(dentry);
|
||||
struct ntfs_volume *vol = NTFS_SB(dentry->d_sb);
|
||||
|
||||
if (ntfs_stream_path_has_colon(vol, &dentry->d_name) ||
|
||||
(inode && ntfs_inode_is_named_stream(NTFS_I(inode))))
|
||||
return always_delete_dentry(dentry);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dentry_operations ntfs_dentry_ops = {
|
||||
.d_revalidate = ntfs_dentry_revalidate,
|
||||
.d_delete = ntfs_dentry_delete,
|
||||
};
|
||||
|
||||
/*
|
||||
* ntfs_set_default_dentry_ops() - Install stream-aware dentry operations
|
||||
* @sb: superblock on which to install the operations
|
||||
*
|
||||
* Ensure pathname stream dentries are revalidated and not kept as stale
|
||||
* aliases.
|
||||
*/
|
||||
void ntfs_set_default_dentry_ops(struct super_block *sb)
|
||||
{
|
||||
set_default_d_op(sb, &ntfs_dentry_ops);
|
||||
}
|
||||
|
||||
/*
|
||||
* ntfs_lookup_stream() - Look up a named stream by pathname
|
||||
* @dir_ino: directory containing the base file or directory
|
||||
* @dent: dentry for the pathname component
|
||||
* @path: parsed base and stream name slices
|
||||
*
|
||||
* Find the existing base inode and its named DATA attribute, then splice the
|
||||
* stream inode into @dent.
|
||||
*
|
||||
* Return: NULL if @dent was instantiated, an alternate dentry if a
|
||||
* disconnected alias was spliced, or ERR_PTR() on failure.
|
||||
*/
|
||||
struct dentry *ntfs_lookup_stream(struct inode *dir_ino,
|
||||
struct dentry *dent, const struct ntfs_stream_path *path)
|
||||
{
|
||||
struct ntfs_volume *vol = NTFS_SB(dir_ino->i_sb);
|
||||
struct inode *base_vi, *stream_vi;
|
||||
__le16 *base_uname = NULL, *stream_uname = NULL;
|
||||
int base_len, stream_len, err;
|
||||
|
||||
if (NVolShutdown(vol))
|
||||
return ERR_PTR(-EIO);
|
||||
|
||||
base_len = ntfs_nlstoucs(vol, path->base_name, path->base_len,
|
||||
&base_uname, NTFS_MAX_NAME_LEN);
|
||||
if (base_len < 0)
|
||||
return ERR_PTR(base_len);
|
||||
|
||||
stream_len = ntfs_nlstoucs(vol, path->stream_name, path->stream_len,
|
||||
&stream_uname, NTFS_MAX_NAME_LEN);
|
||||
if (stream_len < 0) {
|
||||
kmem_cache_free(ntfs_name_cache, base_uname);
|
||||
return ERR_PTR(stream_len);
|
||||
}
|
||||
err = ntfs_check_stream_name(stream_uname, stream_len);
|
||||
if (err) {
|
||||
kmem_cache_free(ntfs_name_cache, base_uname);
|
||||
kmem_cache_free(ntfs_name_cache, stream_uname);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
base_vi = ntfs_stream_lookup_inode_by_name(dir_ino, base_uname,
|
||||
base_len);
|
||||
kmem_cache_free(ntfs_name_cache, base_uname);
|
||||
if (IS_ERR(base_vi)) {
|
||||
err = PTR_ERR(base_vi);
|
||||
kmem_cache_free(ntfs_name_cache, stream_uname);
|
||||
if (err == -ENOENT)
|
||||
return d_splice_alias(NULL, dent);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
if (!S_ISREG(base_vi->i_mode) && !S_ISDIR(base_vi->i_mode)) {
|
||||
iput(base_vi);
|
||||
kmem_cache_free(ntfs_name_cache, stream_uname);
|
||||
return ERR_PTR(-ENOTDIR);
|
||||
}
|
||||
|
||||
mutex_lock(&NTFS_I(base_vi)->mrec_lock);
|
||||
stream_vi = ntfs_attr_iget(base_vi, AT_DATA, stream_uname, stream_len);
|
||||
if (!IS_ERR(stream_vi)) {
|
||||
if (NInoStreamUnlinked(NTFS_I(stream_vi)))
|
||||
err = -ENOENT;
|
||||
else
|
||||
err = ntfs_stream_inode_validate(stream_vi);
|
||||
}
|
||||
mutex_unlock(&NTFS_I(base_vi)->mrec_lock);
|
||||
if (!IS_ERR(stream_vi) && err) {
|
||||
iput(stream_vi);
|
||||
stream_vi = ERR_PTR(err);
|
||||
}
|
||||
iput(base_vi);
|
||||
kmem_cache_free(ntfs_name_cache, stream_uname);
|
||||
|
||||
if (IS_ERR(stream_vi)) {
|
||||
err = PTR_ERR(stream_vi);
|
||||
if (err == -ENOENT || err == -ESTALE)
|
||||
return d_splice_alias(NULL, dent);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
return d_splice_alias(stream_vi, dent);
|
||||
}
|
||||
|
||||
/*
|
||||
* ntfs_stream_path_names() - Convert and validate pathname stream names
|
||||
* @vol: NTFS volume
|
||||
* @qname: final pathname component
|
||||
* @base_name: receives the allocated UTF-16LE base name
|
||||
* @base_len: receives the base name length in UTF-16 code units
|
||||
* @stream_name: receives the allocated UTF-16LE stream name
|
||||
* @stream_len: receives the stream name length in UTF-16 code units
|
||||
*
|
||||
* Return: 1 if stream names were produced, 0 if @qname has no stream syntax,
|
||||
* or a negative errno. The caller owns both name buffers on success.
|
||||
*/
|
||||
int ntfs_stream_path_names(struct ntfs_volume *vol,
|
||||
const struct qstr *qname, __le16 **base_name, int *base_len,
|
||||
__le16 **stream_name, int *stream_len)
|
||||
{
|
||||
struct ntfs_stream_path path;
|
||||
int err;
|
||||
|
||||
*base_name = NULL;
|
||||
*stream_name = NULL;
|
||||
err = ntfs_stream_path_parse(vol, qname, &path);
|
||||
if (err <= 0)
|
||||
return err;
|
||||
|
||||
*base_len = ntfs_nlstoucs(vol, path.base_name, path.base_len,
|
||||
base_name, NTFS_MAX_NAME_LEN);
|
||||
if (*base_len < 0)
|
||||
return *base_len;
|
||||
|
||||
*stream_len = ntfs_nlstoucs(vol, path.stream_name, path.stream_len,
|
||||
stream_name, NTFS_MAX_NAME_LEN);
|
||||
if (*stream_len < 0) {
|
||||
kmem_cache_free(ntfs_name_cache, *base_name);
|
||||
*base_name = NULL;
|
||||
return *stream_len;
|
||||
}
|
||||
|
||||
err = ntfs_check_bad_windows_name(vol, *base_name, *base_len);
|
||||
if (err)
|
||||
goto out_free;
|
||||
err = ntfs_check_stream_name(*stream_name, *stream_len);
|
||||
if (err)
|
||||
goto out_free;
|
||||
err = ntfs_check_bad_windows_name(vol, *stream_name, *stream_len);
|
||||
if (err)
|
||||
goto out_free;
|
||||
return 1;
|
||||
|
||||
out_free:
|
||||
kmem_cache_free(ntfs_name_cache, *stream_name);
|
||||
kmem_cache_free(ntfs_name_cache, *base_name);
|
||||
*stream_name = NULL;
|
||||
*base_name = NULL;
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* ntfs_create_named_stream() - Create a named DATA stream
|
||||
* @idmap: mount idmap used for permission checks
|
||||
* @dir: directory containing the base object
|
||||
* @base_name: UTF-16LE base name
|
||||
* @base_len: length of @base_name in UTF-16 code units
|
||||
* @stream_name: UTF-16LE stream name
|
||||
* @stream_len: length of @stream_name in UTF-16 code units
|
||||
*
|
||||
* Create the stream on an existing regular file or directory.
|
||||
*
|
||||
* Return: Referenced stream inode on success, or ERR_PTR() on failure.
|
||||
*/
|
||||
struct inode *ntfs_create_named_stream(const struct mnt_idmap *idmap,
|
||||
struct inode *dir, __le16 *base_name, int base_len,
|
||||
__le16 *stream_name, int stream_len)
|
||||
{
|
||||
struct ntfs_inode *base_ni;
|
||||
struct inode *base_vi, *stream_vi;
|
||||
struct inode *rollback_vi = NULL;
|
||||
struct ntfs_attr_search_ctx *ctx;
|
||||
bool stream_created = false;
|
||||
int err, rollback_err;
|
||||
|
||||
base_vi = ntfs_stream_lookup_inode_by_name(dir, base_name, base_len);
|
||||
if (IS_ERR(base_vi))
|
||||
return base_vi;
|
||||
if (!S_ISREG(base_vi->i_mode) && !S_ISDIR(base_vi->i_mode)) {
|
||||
iput(base_vi);
|
||||
return ERR_PTR(-ENOTDIR);
|
||||
}
|
||||
err = inode_permission(idmap, base_vi, MAY_OPEN | MAY_WRITE);
|
||||
if (err)
|
||||
goto out_iput;
|
||||
if (IS_APPEND(base_vi) || IS_IMMUTABLE(base_vi)) {
|
||||
err = -EPERM;
|
||||
goto out_iput;
|
||||
}
|
||||
if (!(NTFS_SB(base_vi->i_sb)->vol_flags & VOLUME_IS_DIRTY)) {
|
||||
err = ntfs_set_volume_flags(NTFS_SB(base_vi->i_sb),
|
||||
VOLUME_IS_DIRTY);
|
||||
if (err)
|
||||
goto out_iput;
|
||||
}
|
||||
|
||||
base_ni = NTFS_I(base_vi);
|
||||
mutex_lock(&base_ni->mrec_lock);
|
||||
if (NVolShutdown(base_ni->vol)) {
|
||||
err = -EIO;
|
||||
goto out_unlock;
|
||||
}
|
||||
if (NInoBeingDeleted(base_ni) || !base_vi->i_nlink) {
|
||||
err = -ENOENT;
|
||||
goto out_unlock;
|
||||
}
|
||||
if (IS_APPEND(base_vi) || IS_IMMUTABLE(base_vi)) {
|
||||
err = -EPERM;
|
||||
goto out_unlock;
|
||||
}
|
||||
ctx = ntfs_attr_get_search_ctx(base_ni, NULL);
|
||||
if (!ctx) {
|
||||
err = -ENOMEM;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
err = ntfs_attr_lookup(AT_DATA, stream_name, stream_len,
|
||||
IGNORE_CASE, 0, NULL, 0, ctx);
|
||||
if (!err) {
|
||||
if (ntfs_stream_unlinked(base_ni, stream_name, stream_len))
|
||||
err = -EBUSY;
|
||||
else
|
||||
err = -EEXIST;
|
||||
} else if (err == -ENOENT) {
|
||||
if (NVolShutdown(base_ni->vol)) {
|
||||
err = -EIO;
|
||||
goto out_put_ctx;
|
||||
}
|
||||
err = ntfs_attr_add(base_ni, AT_DATA, stream_name, stream_len,
|
||||
NULL, 0);
|
||||
if (!err) {
|
||||
stream_created = true;
|
||||
mark_mft_record_dirty(base_ni);
|
||||
}
|
||||
}
|
||||
ntfs_attr_put_search_ctx(ctx);
|
||||
if (err)
|
||||
goto out_unlock;
|
||||
|
||||
stream_vi = ntfs_attr_iget(base_vi, AT_DATA, stream_name, stream_len);
|
||||
if (!IS_ERR(stream_vi))
|
||||
err = ntfs_stream_inode_validate(stream_vi);
|
||||
else
|
||||
err = PTR_ERR(stream_vi);
|
||||
if (err) {
|
||||
if (stream_created) {
|
||||
rollback_err = ntfs_attr_remove(base_ni, AT_DATA,
|
||||
stream_name, stream_len, &rollback_vi);
|
||||
if (rollback_err) {
|
||||
ntfs_error(base_ni->vol->sb,
|
||||
"Failed to roll back named stream creation.\n");
|
||||
if (rollback_err == -ENOMEM ||
|
||||
rollback_err == -EINTR ||
|
||||
rollback_err == -ERESTARTSYS) {
|
||||
err = rollback_err;
|
||||
} else {
|
||||
NVolSetErrors(base_ni->vol);
|
||||
NVolSetShutdown(base_ni->vol);
|
||||
err = -EIO;
|
||||
}
|
||||
}
|
||||
}
|
||||
mutex_unlock(&base_ni->mrec_lock);
|
||||
if (stream_created)
|
||||
ntfs_stream_update_base_time(base_ni);
|
||||
if (!IS_ERR(stream_vi))
|
||||
iput(stream_vi);
|
||||
if (rollback_vi)
|
||||
iput(rollback_vi);
|
||||
iput(base_vi);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
mutex_unlock(&base_ni->mrec_lock);
|
||||
if (stream_created)
|
||||
ntfs_stream_update_base_time(base_ni);
|
||||
iput(base_vi);
|
||||
return stream_vi;
|
||||
|
||||
out_put_ctx:
|
||||
ntfs_attr_put_search_ctx(ctx);
|
||||
out_unlock:
|
||||
mutex_unlock(&base_ni->mrec_lock);
|
||||
out_iput:
|
||||
iput(base_vi);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
/*
|
||||
* ntfs_unlink_named_stream() - Remove a pathname named stream
|
||||
* @dir: directory containing the base object
|
||||
* @dentry: dentry for the named stream
|
||||
*
|
||||
* Return: 0 on success, or a negative errno.
|
||||
*/
|
||||
int ntfs_unlink_named_stream(struct inode *dir, struct dentry *dentry)
|
||||
{
|
||||
struct ntfs_volume *vol = NTFS_SB(dir->i_sb);
|
||||
struct inode *base_vi;
|
||||
__le16 *base_name = NULL, *stream_name = NULL;
|
||||
int base_len, stream_len, path_result, err;
|
||||
|
||||
path_result = ntfs_stream_path_names(vol, &dentry->d_name, &base_name,
|
||||
&base_len, &stream_name, &stream_len);
|
||||
if (path_result < 0)
|
||||
return path_result;
|
||||
if (!path_result)
|
||||
return -EINVAL;
|
||||
|
||||
base_vi = ntfs_stream_lookup_inode_by_name(dir, base_name, base_len);
|
||||
if (IS_ERR(base_vi)) {
|
||||
err = PTR_ERR(base_vi);
|
||||
goto out_free;
|
||||
}
|
||||
if (!S_ISREG(base_vi->i_mode) && !S_ISDIR(base_vi->i_mode)) {
|
||||
err = -ENOTDIR;
|
||||
goto out_iput;
|
||||
}
|
||||
if (!ntfs_inode_is_named_stream(NTFS_I(dentry->d_inode))) {
|
||||
err = -ESTALE;
|
||||
goto out_iput;
|
||||
}
|
||||
if (NTFS_I(dentry->d_inode)->ext.base_ntfs_ino != NTFS_I(base_vi)) {
|
||||
err = -ESTALE;
|
||||
goto out_iput;
|
||||
}
|
||||
|
||||
/*
|
||||
* VFS authorizes pathname removal via the parent directory, not base
|
||||
* file write access. The shared helper enforces NTFS delete restrictions.
|
||||
*/
|
||||
err = ntfs_remove_named_stream(NTFS_I(base_vi), stream_name,
|
||||
stream_len, dentry->d_inode);
|
||||
|
||||
out_iput:
|
||||
iput(base_vi);
|
||||
out_free:
|
||||
kmem_cache_free(ntfs_name_cache, stream_name);
|
||||
kmem_cache_free(ntfs_name_cache, base_name);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Obtain a dentry for the base inode behind a stream inode. */
|
||||
static struct dentry *ntfs_stream_base_dentry(struct inode *inode)
|
||||
{
|
||||
struct inode *base_vi =
|
||||
VFS_I(NTFS_I(inode)->ext.base_ntfs_ino);
|
||||
struct dentry *dentry;
|
||||
|
||||
dentry = d_find_alias(base_vi);
|
||||
if (dentry)
|
||||
return dentry;
|
||||
if (!igrab(base_vi))
|
||||
return ERR_PTR(-ESTALE);
|
||||
return d_obtain_alias(base_vi);
|
||||
}
|
||||
|
||||
/*
|
||||
* ntfs_stream_validate_for_mutation() - Validate a stream before mutation
|
||||
* @inode: stream inode to validate
|
||||
* @nowait: do not wait for the base MFT-record lock
|
||||
*
|
||||
* Return: 0 if the backing DATA attribute is valid, -EAGAIN if a nonblocking
|
||||
* lock attempt fails, or another negative errno.
|
||||
*/
|
||||
int ntfs_stream_validate_for_mutation(struct inode *inode, bool nowait)
|
||||
{
|
||||
struct ntfs_inode *base_ni = NTFS_I(inode)->ext.base_ntfs_ino;
|
||||
int err;
|
||||
|
||||
if (nowait) {
|
||||
if (!mutex_trylock(&base_ni->mrec_lock))
|
||||
return -EAGAIN;
|
||||
} else {
|
||||
mutex_lock(&base_ni->mrec_lock);
|
||||
}
|
||||
err = ntfs_stream_inode_validate(inode);
|
||||
mutex_unlock(&base_ni->mrec_lock);
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Hold a temporary stream reference so unlink cannot remove its attribute
|
||||
* while an operation is using it.
|
||||
|
|
@ -201,6 +794,213 @@ static int ntfs_stream_claim(struct inode *inode)
|
|||
return err;
|
||||
}
|
||||
|
||||
/* Stream permission checks use the base inode's permissions. */
|
||||
static int ntfs_stream_permission(const struct mnt_idmap *idmap,
|
||||
struct inode *inode, int mask)
|
||||
{
|
||||
return inode_permission(idmap,
|
||||
VFS_I(NTFS_I(inode)->ext.base_ntfs_ino), mask);
|
||||
}
|
||||
|
||||
/* Report base metadata with the stream's own size and allocation. */
|
||||
static int ntfs_stream_getattr(const struct mnt_idmap *idmap,
|
||||
const struct path *path, struct kstat *stat,
|
||||
unsigned int request_mask, unsigned int query_flags)
|
||||
{
|
||||
struct inode *inode = d_backing_inode(path->dentry);
|
||||
struct ntfs_inode *ni = NTFS_I(inode);
|
||||
struct inode *base_vi = VFS_I(ni->ext.base_ntfs_ino);
|
||||
|
||||
generic_fillattr(idmap, request_mask, base_vi, stat);
|
||||
stat->size = i_size_read(inode);
|
||||
stat->blocks = (((u64)ni->i_dealloc_clusters <<
|
||||
NTFS_SB(inode->i_sb)->cluster_size_bits) >> 9) +
|
||||
inode->i_blocks;
|
||||
stat->blksize = NTFS_SB(inode->i_sb)->cluster_size;
|
||||
stat->result_mask |= STATX_BTIME;
|
||||
stat->btime = NTFS_I(base_vi)->i_crtime;
|
||||
|
||||
if (NInoCompressed(ni))
|
||||
stat->attributes |= STATX_ATTR_COMPRESSED;
|
||||
if (NInoEncrypted(ni))
|
||||
stat->attributes |= STATX_ATTR_ENCRYPTED;
|
||||
if (base_vi->i_flags & S_IMMUTABLE)
|
||||
stat->attributes |= STATX_ATTR_IMMUTABLE;
|
||||
if (base_vi->i_flags & S_APPEND)
|
||||
stat->attributes |= STATX_ATTR_APPEND;
|
||||
stat->attributes_mask |= STATX_ATTR_COMPRESSED | STATX_ATTR_ENCRYPTED |
|
||||
STATX_ATTR_IMMUTABLE | STATX_ATTR_APPEND;
|
||||
stat->mode = (stat->mode & ~S_IFMT) | S_IFREG;
|
||||
|
||||
if (request_mask & STATX_DIOALIGN) {
|
||||
unsigned int align =
|
||||
bdev_logical_block_size(inode->i_sb->s_bdev);
|
||||
|
||||
stat->result_mask |= STATX_DIOALIGN;
|
||||
if (!NInoCompressed(ni) && !NInoEncrypted(ni)) {
|
||||
stat->dio_mem_align = align;
|
||||
stat->dio_offset_align = align;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Apply size changes to the stream and route shared inode metadata changes to
|
||||
* its base inode.
|
||||
*/
|
||||
static int ntfs_stream_setattr_common(const struct mnt_idmap *idmap,
|
||||
struct inode *inode, struct iattr *attr)
|
||||
{
|
||||
struct ntfs_inode *ni = NTFS_I(inode);
|
||||
struct inode *base_vi = VFS_I(ni->ext.base_ntfs_ino);
|
||||
struct dentry *base_dentry = NULL;
|
||||
struct iattr base_attr = *attr;
|
||||
int err;
|
||||
|
||||
base_attr.ia_valid &= ~ATTR_FILE;
|
||||
base_attr.ia_file = NULL;
|
||||
if (base_attr.ia_valid & (ATTR_KILL_SUID | ATTR_KILL_SGID))
|
||||
base_attr.ia_valid &= ~ATTR_MODE;
|
||||
else if (base_attr.ia_valid & ATTR_MODE)
|
||||
base_attr.ia_mode = (base_attr.ia_mode & ~S_IFMT) |
|
||||
(base_vi->i_mode & S_IFMT);
|
||||
|
||||
if (attr->ia_valid & ATTR_SIZE) {
|
||||
err = inode_permission(idmap, base_vi, MAY_WRITE);
|
||||
if (err)
|
||||
goto out;
|
||||
if (IS_APPEND(base_vi) || IS_IMMUTABLE(base_vi)) {
|
||||
err = -EPERM;
|
||||
goto out;
|
||||
}
|
||||
if (!(ni->vol->vol_flags & VOLUME_IS_DIRTY)) {
|
||||
err = ntfs_set_volume_flags(ni->vol, VOLUME_IS_DIRTY);
|
||||
if (err)
|
||||
goto out;
|
||||
}
|
||||
base_attr.ia_valid &= ~ATTR_SIZE;
|
||||
}
|
||||
|
||||
if ((attr->ia_valid & ATTR_SIZE) || base_attr.ia_valid) {
|
||||
base_dentry = ntfs_stream_base_dentry(inode);
|
||||
if (IS_ERR(base_dentry)) {
|
||||
err = PTR_ERR(base_dentry);
|
||||
base_dentry = NULL;
|
||||
goto out;
|
||||
}
|
||||
inode_lock_nested(base_vi, I_MUTEX_PARENT);
|
||||
err = ntfs_stream_validate_for_mutation(inode, false);
|
||||
if (err) {
|
||||
inode_unlock(base_vi);
|
||||
goto out;
|
||||
}
|
||||
if (base_attr.ia_valid)
|
||||
err = notify_change(idmap, base_dentry, &base_attr,
|
||||
NULL);
|
||||
else
|
||||
err = 0;
|
||||
if (!err)
|
||||
ntfs_stream_inode_refresh(inode);
|
||||
inode_unlock(base_vi);
|
||||
if (err)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (attr->ia_valid & ATTR_SIZE)
|
||||
err = ntfs_setattr_size(inode, attr);
|
||||
else
|
||||
err = 0;
|
||||
out:
|
||||
dput(base_dentry);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Hold the stream against unlink while applying VFS setattr operations. */
|
||||
static int ntfs_stream_setattr(const struct mnt_idmap *idmap,
|
||||
struct dentry *dentry, struct iattr *attr)
|
||||
{
|
||||
struct inode *inode = d_inode(dentry);
|
||||
struct ntfs_inode *ni = NTFS_I(inode);
|
||||
bool claimed = false;
|
||||
int err;
|
||||
|
||||
if (NVolShutdown(ni->vol))
|
||||
return -EIO;
|
||||
|
||||
if (!(attr->ia_valid & ATTR_FILE)) {
|
||||
err = ntfs_stream_claim(inode);
|
||||
if (err)
|
||||
return err;
|
||||
claimed = true;
|
||||
}
|
||||
|
||||
err = ntfs_stream_setattr_common(idmap, inode, attr);
|
||||
if (claimed)
|
||||
ntfs_stream_put(inode);
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Apply stream timestamp updates to the base inode after validating the
|
||||
* backing attribute under its MFT-record lock. Nonblocking callers get
|
||||
* -EAGAIN.
|
||||
*/
|
||||
static int ntfs_stream_update_time_common(struct inode *inode,
|
||||
enum fs_update_time type, unsigned int flags)
|
||||
{
|
||||
struct ntfs_inode *base_ni = NTFS_I(inode)->ext.base_ntfs_ino;
|
||||
struct inode *base_vi = VFS_I(base_ni);
|
||||
int err;
|
||||
|
||||
if (NVolShutdown(base_ni->vol))
|
||||
return -EIO;
|
||||
if (flags & IOCB_NOWAIT)
|
||||
return -EAGAIN;
|
||||
|
||||
mutex_lock(&base_ni->mrec_lock);
|
||||
if (NVolShutdown(base_ni->vol)) {
|
||||
err = -EIO;
|
||||
goto out_mrec;
|
||||
}
|
||||
err = ntfs_stream_inode_validate(inode);
|
||||
if (!err) {
|
||||
err = generic_update_time(base_vi, type, flags);
|
||||
if (!err)
|
||||
ntfs_stream_inode_refresh(inode);
|
||||
}
|
||||
out_mrec:
|
||||
mutex_unlock(&base_ni->mrec_lock);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ntfs_stream_update_time(struct inode *inode,
|
||||
enum fs_update_time type, unsigned int flags)
|
||||
{
|
||||
return ntfs_stream_update_time_common(inode, type, flags);
|
||||
}
|
||||
|
||||
static ssize_t ntfs_stream_listxattr(struct dentry *dentry, char *buffer,
|
||||
size_t size)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_NTFS_FS_POSIX_ACL
|
||||
static struct posix_acl *ntfs_stream_get_acl(const struct mnt_idmap *idmap,
|
||||
struct dentry *dentry, int type)
|
||||
{
|
||||
return ERR_PTR(-EOPNOTSUPP);
|
||||
}
|
||||
|
||||
static int ntfs_stream_set_acl(const struct mnt_idmap *idmap,
|
||||
struct dentry *dentry, struct posix_acl *acl, int type)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Remove an unlinked stream's DATA attribute after its final active reference
|
||||
* is released. Base-inode deletion handles the attribute when the base is
|
||||
|
|
@ -335,8 +1135,9 @@ int ntfs_remove_named_stream(struct ntfs_inode *ni, __le16 *uname,
|
|||
clear_nlink(attr_vi);
|
||||
mutex_unlock(&ni->mrec_lock);
|
||||
|
||||
ntfs_stream_update_base_time(ni);
|
||||
err = ntfs_stream_finish_remove(attr_vi);
|
||||
if (!err)
|
||||
ntfs_stream_update_base_time(ni);
|
||||
iput(attr_vi);
|
||||
return err;
|
||||
|
||||
|
|
@ -349,6 +1150,80 @@ int ntfs_remove_named_stream(struct ntfs_inode *ni, __le16 *uname,
|
|||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Validate access against the base inode and hold the stream against unlink
|
||||
* until the file is released.
|
||||
*/
|
||||
static int ntfs_stream_file_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct ntfs_inode *ni = NTFS_I(inode);
|
||||
struct ntfs_inode *base_ni = ni->ext.base_ntfs_ino;
|
||||
struct inode *base_vi = VFS_I(base_ni);
|
||||
int mask = MAY_OPEN;
|
||||
int err;
|
||||
|
||||
if (file->f_mode & FMODE_READ)
|
||||
mask |= MAY_READ;
|
||||
if (file->f_mode & FMODE_WRITE)
|
||||
mask |= MAY_WRITE;
|
||||
err = inode_permission(file_mnt_idmap(file), base_vi, mask);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
mutex_lock(&base_ni->mrec_lock);
|
||||
if (NInoStreamUnlinked(ni))
|
||||
err = -ENOENT;
|
||||
else
|
||||
err = ntfs_stream_inode_validate(inode);
|
||||
if (err)
|
||||
goto out_unlock;
|
||||
if ((file->f_mode & FMODE_WRITE) &&
|
||||
(IS_IMMUTABLE(base_vi) ||
|
||||
(IS_APPEND(base_vi) && !(file->f_flags & O_APPEND)))) {
|
||||
err = -EPERM;
|
||||
goto out_unlock;
|
||||
}
|
||||
if ((file->f_flags & O_TRUNC) && IS_APPEND(base_vi)) {
|
||||
err = -EPERM;
|
||||
goto out_unlock;
|
||||
}
|
||||
if ((file->f_flags & O_NOATIME) &&
|
||||
!inode_owner_or_capable(file_mnt_idmap(file), base_vi)) {
|
||||
err = -EPERM;
|
||||
goto out_unlock;
|
||||
}
|
||||
err = ntfs_file_open(inode, file);
|
||||
if (err)
|
||||
goto out_unlock;
|
||||
if (file->f_mode & FMODE_WRITE) {
|
||||
err = get_write_access(base_vi);
|
||||
if (err)
|
||||
goto out_unlock;
|
||||
file->private_data = base_vi;
|
||||
}
|
||||
|
||||
atomic_inc(&ni->stream_open_count);
|
||||
out_unlock:
|
||||
mutex_unlock(&base_ni->mrec_lock);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Drop open-time references and complete any deferred stream unlink. */
|
||||
static int ntfs_stream_file_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
int err, remove_err;
|
||||
|
||||
err = ntfs_file_release(inode, file);
|
||||
if (file->private_data) {
|
||||
put_write_access(file->private_data);
|
||||
file->private_data = NULL;
|
||||
}
|
||||
remove_err = ntfs_stream_put(inode);
|
||||
if (!err)
|
||||
err = remove_err;
|
||||
return err;
|
||||
}
|
||||
|
||||
enum ntfs_stream_ioctl_op {
|
||||
NTFS_STREAM_IOCTL_READ,
|
||||
NTFS_STREAM_IOCTL_WRITE,
|
||||
|
|
@ -618,6 +1493,9 @@ static long ntfs_ioctl_stream(struct file *filp, unsigned long arg,
|
|||
file_accessed(filp);
|
||||
} else {
|
||||
inode_lock(stream_vi);
|
||||
inode_dio_wait(stream_vi);
|
||||
/* Exclude faults before taking MFT record and folio locks. */
|
||||
filemap_invalidate_lock(stream_vi->i_mapping);
|
||||
err = inode_newsize_ok(stream_vi, pos + data_size);
|
||||
if (!err) {
|
||||
ret = ntfs_inode_attr_pwrite(stream_vi, pos, data_size,
|
||||
|
|
@ -627,6 +1505,7 @@ static long ntfs_ioctl_stream(struct file *filp, unsigned long arg,
|
|||
else
|
||||
req->bytes_returned = ret;
|
||||
}
|
||||
filemap_invalidate_unlock(stream_vi->i_mapping);
|
||||
inode_unlock(stream_vi);
|
||||
}
|
||||
if (claimed) {
|
||||
|
|
@ -914,3 +1793,29 @@ int ntfs_ioctl_list_streams(struct file *filp, unsigned long arg)
|
|||
kvfree(kbuf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
const struct file_operations ntfs_stream_file_ops = {
|
||||
.llseek = ntfs_file_llseek,
|
||||
.read_iter = ntfs_file_read_iter,
|
||||
.write_iter = ntfs_file_write_iter,
|
||||
.fsync = ntfs_file_fsync,
|
||||
.fallocate = ntfs_fallocate,
|
||||
.mmap_prepare = ntfs_file_mmap_prepare,
|
||||
.open = ntfs_stream_file_open,
|
||||
.release = ntfs_stream_file_release,
|
||||
.splice_read = ntfs_file_splice_read,
|
||||
.splice_write = iter_file_splice_write,
|
||||
};
|
||||
|
||||
const struct inode_operations ntfs_stream_inode_ops = {
|
||||
.permission = ntfs_stream_permission,
|
||||
.setattr = ntfs_stream_setattr,
|
||||
.getattr = ntfs_stream_getattr,
|
||||
.listxattr = ntfs_stream_listxattr,
|
||||
#ifdef CONFIG_NTFS_FS_POSIX_ACL
|
||||
.get_acl = ntfs_stream_get_acl,
|
||||
.set_acl = ntfs_stream_set_acl,
|
||||
#endif
|
||||
.update_time = ntfs_stream_update_time,
|
||||
.fiemap = ntfs_fiemap,
|
||||
};
|
||||
|
|
|
|||
132
fs/ntfs/namei.c
132
fs/ntfs/namei.c
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include <linux/exportfs.h>
|
||||
#include <linux/iversion.h>
|
||||
#include <linux/namei.h>
|
||||
|
||||
#include "ntfs.h"
|
||||
#include "time.h"
|
||||
|
|
@ -15,6 +16,7 @@
|
|||
#include "reparse.h"
|
||||
#include "object_id.h"
|
||||
#include "ea.h"
|
||||
#include "stream.h"
|
||||
|
||||
static const __le16 aux_name_le[3] = {
|
||||
cpu_to_le16('A'), cpu_to_le16('U'), cpu_to_le16('X')
|
||||
|
|
@ -57,7 +59,18 @@ static inline int ntfs_check_bad_char(const __le16 *wc, unsigned int wc_len)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int ntfs_check_bad_windows_name(struct ntfs_volume *vol,
|
||||
/*
|
||||
* ntfs_check_bad_windows_name() - Validate a name against Windows rules
|
||||
* @vol: NTFS volume
|
||||
* @wc: UTF-16LE name
|
||||
* @wc_len: length of @wc in UTF-16 code units
|
||||
*
|
||||
* When Windows-name checks are enabled, reject disallowed characters,
|
||||
* trailing spaces or dots, and reserved DOS device names.
|
||||
*
|
||||
* Return: 0 if valid, or -EINVAL otherwise.
|
||||
*/
|
||||
int ntfs_check_bad_windows_name(struct ntfs_volume *vol,
|
||||
const __le16 *wc,
|
||||
unsigned int wc_len)
|
||||
{
|
||||
|
|
@ -167,19 +180,29 @@ static int ntfs_check_bad_windows_name(struct ntfs_volume *vol,
|
|||
*
|
||||
* 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 ntfs_stream_path stream_path;
|
||||
struct inode *dent_inode;
|
||||
__le16 *uname;
|
||||
struct ntfs_name *name = NULL;
|
||||
u64 mref;
|
||||
unsigned long dent_ino;
|
||||
int uname_len;
|
||||
int stream_path_len;
|
||||
|
||||
ntfs_debug("Looking up %pd in directory inode 0x%llx.",
|
||||
dent, NTFS_I(dir_ino)->mft_no);
|
||||
stream_path_len = ntfs_stream_path_parse(vol, &dent->d_name,
|
||||
&stream_path);
|
||||
if (stream_path_len < 0)
|
||||
return ERR_PTR(stream_path_len);
|
||||
if (stream_path_len)
|
||||
return ntfs_lookup_stream(dir_ino, dent, &stream_path);
|
||||
|
||||
/* 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);
|
||||
|
|
@ -405,6 +428,7 @@ static struct ntfs_inode *__ntfs_create(const struct mnt_idmap *idmap, struct in
|
|||
struct inode *vi;
|
||||
struct mft_record *ni_mrec, *dni_mrec;
|
||||
struct super_block *sb = dir_ni->vol->sb;
|
||||
struct inode *rollback_data_vi = NULL, *rollback_sd_vi = NULL;
|
||||
__le64 parent_mft_ref;
|
||||
u64 child_mft_ref;
|
||||
__le16 ea_size;
|
||||
|
|
@ -694,11 +718,25 @@ static struct ntfs_inode *__ntfs_create(const struct mnt_idmap *idmap, struct in
|
|||
return ni;
|
||||
|
||||
err_out:
|
||||
if (rollback_sd)
|
||||
ntfs_attr_remove(ni, AT_SECURITY_DESCRIPTOR, AT_UNNAMED, 0);
|
||||
if (rollback_sd) {
|
||||
int rollback_err;
|
||||
|
||||
if (rollback_data)
|
||||
ntfs_attr_remove(ni, AT_DATA, AT_UNNAMED, 0);
|
||||
rollback_err = ntfs_attr_remove(ni,
|
||||
AT_SECURITY_DESCRIPTOR, AT_UNNAMED, 0,
|
||||
&rollback_sd_vi);
|
||||
if (rollback_err)
|
||||
ntfs_error(sb,
|
||||
"Failed to roll back security descriptor.\n");
|
||||
}
|
||||
|
||||
if (rollback_data) {
|
||||
int rollback_err;
|
||||
|
||||
rollback_err = ntfs_attr_remove(ni, AT_DATA, AT_UNNAMED,
|
||||
0, &rollback_data_vi);
|
||||
if (rollback_err)
|
||||
ntfs_error(sb, "Failed to roll back DATA attribute.\n");
|
||||
}
|
||||
|
||||
if (rollback_reparse)
|
||||
ntfs_delete_reparse_index(ni);
|
||||
|
|
@ -726,6 +764,10 @@ static struct ntfs_inode *__ntfs_create(const struct mnt_idmap *idmap, struct in
|
|||
mutex_unlock(&dir_ni->mrec_lock);
|
||||
mutex_unlock(&ni->mrec_lock);
|
||||
|
||||
if (rollback_data_vi)
|
||||
iput(rollback_data_vi);
|
||||
if (rollback_sd_vi)
|
||||
iput(rollback_sd_vi);
|
||||
remove_inode_hash(vi);
|
||||
discard_new_inode(vi);
|
||||
return ERR_PTR(err);
|
||||
|
|
@ -736,12 +778,35 @@ static int ntfs_create(const struct mnt_idmap *idmap, struct inode *dir,
|
|||
{
|
||||
struct ntfs_volume *vol = NTFS_SB(dir->i_sb);
|
||||
struct ntfs_inode *ni;
|
||||
__le16 *uname;
|
||||
int uname_len, err;
|
||||
struct inode *stream_vi = NULL;
|
||||
__le16 *uname, *base_name = NULL, *stream_name = NULL;
|
||||
int uname_len, stream_len, path_result, err;
|
||||
|
||||
if (NVolShutdown(vol))
|
||||
return -EIO;
|
||||
|
||||
path_result = ntfs_stream_path_names(vol, &dentry->d_name, &base_name,
|
||||
&uname_len, &stream_name, &stream_len);
|
||||
if (path_result) {
|
||||
if (path_result < 0)
|
||||
return path_result;
|
||||
stream_vi = ntfs_create_named_stream(idmap, dir, base_name,
|
||||
uname_len, stream_name, stream_len);
|
||||
if (IS_ERR(stream_vi)) {
|
||||
err = PTR_ERR(stream_vi);
|
||||
goto out_free_stream_names;
|
||||
}
|
||||
kmem_cache_free(ntfs_name_cache, stream_name);
|
||||
kmem_cache_free(ntfs_name_cache, base_name);
|
||||
d_instantiate(dentry, stream_vi);
|
||||
return 0;
|
||||
|
||||
out_free_stream_names:
|
||||
kmem_cache_free(ntfs_name_cache, stream_name);
|
||||
kmem_cache_free(ntfs_name_cache, base_name);
|
||||
return err;
|
||||
}
|
||||
|
||||
uname_len = ntfs_nlstoucs(vol, dentry->d_name.name, dentry->d_name.len,
|
||||
&uname, NTFS_MAX_NAME_LEN);
|
||||
if (uname_len < 0) {
|
||||
|
|
@ -758,7 +823,8 @@ static int ntfs_create(const struct mnt_idmap *idmap, struct inode *dir,
|
|||
|
||||
ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY);
|
||||
|
||||
ni = __ntfs_create(idmap, dir, uname, uname_len, S_IFREG | mode, 0, NULL, 0);
|
||||
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);
|
||||
|
|
@ -850,10 +916,10 @@ static int ntfs_delete(struct ntfs_inode *ni, struct ntfs_inode *dir_ni,
|
|||
struct file_name_attr *fn = NULL;
|
||||
bool looking_for_dos_name = false, looking_for_win32_name = false;
|
||||
bool case_sensitive_match = true;
|
||||
bool link_count_zero = false;
|
||||
int err = 0;
|
||||
struct mft_record *ni_mrec;
|
||||
struct super_block *sb;
|
||||
bool link_count_zero = false;
|
||||
|
||||
ntfs_debug("Entering.\n");
|
||||
|
||||
|
|
@ -1025,6 +1091,11 @@ static int ntfs_unlink(struct inode *dir, struct dentry *dentry)
|
|||
if (NVolShutdown(vol))
|
||||
return -EIO;
|
||||
|
||||
if (ntfs_stream_path_has_colon(vol, &dentry->d_name))
|
||||
return ntfs_unlink_named_stream(dir, dentry);
|
||||
if (NInoAttr(ni))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
uname_len = ntfs_nlstoucs(vol, dentry->d_name.name, dentry->d_name.len,
|
||||
&uname, NTFS_MAX_NAME_LEN);
|
||||
if (uname_len < 0) {
|
||||
|
|
@ -1068,6 +1139,10 @@ static struct dentry *ntfs_mkdir(const struct mnt_idmap *idmap, struct inode *di
|
|||
if (NVolShutdown(vol))
|
||||
return ERR_PTR(-EIO);
|
||||
|
||||
err = ntfs_stream_path_check(vol, &dentry->d_name);
|
||||
if (err)
|
||||
return ERR_PTR(err);
|
||||
|
||||
uname_len = ntfs_nlstoucs(vol, dentry->d_name.name, dentry->d_name.len,
|
||||
&uname, NTFS_MAX_NAME_LEN);
|
||||
if (uname_len < 0) {
|
||||
|
|
@ -1084,7 +1159,8 @@ static struct dentry *ntfs_mkdir(const struct mnt_idmap *idmap, struct inode *di
|
|||
|
||||
ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY);
|
||||
|
||||
ni = __ntfs_create(idmap, dir, uname, uname_len, mode, 0, NULL, 0);
|
||||
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);
|
||||
|
|
@ -1108,6 +1184,10 @@ static int ntfs_rmdir(struct inode *dir, struct dentry *dentry)
|
|||
if (NVolShutdown(vol))
|
||||
return -EIO;
|
||||
|
||||
err = ntfs_stream_path_check(vol, &dentry->d_name);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
ni = NTFS_I(vi);
|
||||
uname_len = ntfs_nlstoucs(vol, dentry->d_name.name, dentry->d_name.len,
|
||||
&uname, NTFS_MAX_NAME_LEN);
|
||||
|
|
@ -1272,6 +1352,13 @@ static int ntfs_rename(const struct mnt_idmap *idmap, struct inode *old_dir,
|
|||
if (NVolShutdown(old_dir_ni->vol))
|
||||
return -EIO;
|
||||
|
||||
err = ntfs_stream_path_check(vol, &old_dentry->d_name);
|
||||
if (err)
|
||||
return err;
|
||||
err = ntfs_stream_path_check(vol, &new_dentry->d_name);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (flags & (RENAME_EXCHANGE | RENAME_WHITEOUT))
|
||||
return -EINVAL;
|
||||
|
||||
|
|
@ -1419,6 +1506,10 @@ static int ntfs_symlink(const struct mnt_idmap *idmap, struct inode *dir,
|
|||
if (NVolShutdown(vol))
|
||||
return -EIO;
|
||||
|
||||
err = ntfs_stream_path_check(vol, &dentry->d_name);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
usrc_len = ntfs_nlstoucs(vol, dentry->d_name.name,
|
||||
dentry->d_name.len, &usrc, NTFS_MAX_NAME_LEN);
|
||||
if (usrc_len < 0) {
|
||||
|
|
@ -1464,6 +1555,10 @@ static int ntfs_mknod(const struct mnt_idmap *idmap, struct inode *dir,
|
|||
if (NVolShutdown(vol))
|
||||
return -EIO;
|
||||
|
||||
err = ntfs_stream_path_check(vol, &dentry->d_name);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
uname_len = ntfs_nlstoucs(vol, dentry->d_name.name,
|
||||
dentry->d_name.len, &uname, NTFS_MAX_NAME_LEN);
|
||||
if (uname_len < 0) {
|
||||
|
|
@ -1516,6 +1611,13 @@ static int ntfs_link(struct dentry *old_dentry, struct inode *dir,
|
|||
if (NVolShutdown(vol))
|
||||
return -EIO;
|
||||
|
||||
if (NInoAttr(ni))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
err = ntfs_stream_path_check(vol, &dentry->d_name);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
uname_len = ntfs_nlstoucs(vol, dentry->d_name.name,
|
||||
dentry->d_name.len, &uname, NTFS_MAX_NAME_LEN);
|
||||
if (uname_len < 0) {
|
||||
|
|
@ -1669,11 +1771,19 @@ static struct dentry *ntfs_fh_to_parent(struct super_block *sb, struct fid *fid,
|
|||
ntfs_nfs_get_inode);
|
||||
}
|
||||
|
||||
static int ntfs_encode_fh(struct inode *inode, u32 *fh, int *max_len,
|
||||
struct inode *parent)
|
||||
{
|
||||
if (ntfs_inode_is_named_stream(NTFS_I(inode)))
|
||||
return -EOPNOTSUPP;
|
||||
return generic_encode_ino32_fh(inode, fh, max_len, parent);
|
||||
}
|
||||
|
||||
/*
|
||||
* Export operations allowing NFS exporting of mounted NTFS partitions.
|
||||
*/
|
||||
const struct export_operations ntfs_export_ops = {
|
||||
.encode_fh = generic_encode_ino32_fh,
|
||||
.encode_fh = ntfs_encode_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,
|
||||
|
|
|
|||
|
|
@ -7,8 +7,38 @@
|
|||
struct ntfs_inode;
|
||||
struct ntfs_volume;
|
||||
|
||||
struct ntfs_stream_path {
|
||||
const char *base_name;
|
||||
unsigned int base_len;
|
||||
const char *stream_name;
|
||||
unsigned int stream_len;
|
||||
};
|
||||
|
||||
int ntfs_check_bad_windows_name(struct ntfs_volume *vol,
|
||||
const __le16 *name, unsigned int name_len);
|
||||
int ntfs_check_stream_name(const __le16 *name, unsigned int name_len);
|
||||
|
||||
int ntfs_stream_path_parse(struct ntfs_volume *vol,
|
||||
const struct qstr *qname, struct ntfs_stream_path *path);
|
||||
bool ntfs_stream_path_has_colon(struct ntfs_volume *vol,
|
||||
const struct qstr *qname);
|
||||
int ntfs_stream_path_check(struct ntfs_volume *vol,
|
||||
const struct qstr *qname);
|
||||
int ntfs_stream_path_names(struct ntfs_volume *vol,
|
||||
const struct qstr *qname, __le16 **base_name, int *base_len,
|
||||
__le16 **stream_name, int *stream_len);
|
||||
|
||||
struct inode *ntfs_stream_lookup_inode_by_name(struct inode *dir,
|
||||
__le16 *name, int name_len);
|
||||
struct dentry *ntfs_lookup_stream(struct inode *dir, struct dentry *dentry,
|
||||
const struct ntfs_stream_path *path);
|
||||
struct inode *ntfs_create_named_stream(const struct mnt_idmap *idmap,
|
||||
struct inode *dir, __le16 *base_name, int base_len,
|
||||
__le16 *stream_name, int stream_len);
|
||||
int ntfs_unlink_named_stream(struct inode *dir, struct dentry *dentry);
|
||||
|
||||
void ntfs_set_default_dentry_ops(struct super_block *sb);
|
||||
|
||||
void ntfs_stream_inode_refresh(struct inode *inode);
|
||||
int ntfs_stream_inode_validate(struct inode *inode);
|
||||
void ntfs_stream_update_base_time(struct ntfs_inode *base_ni);
|
||||
|
|
@ -18,11 +48,19 @@ int ntfs_stream_put(struct inode *inode);
|
|||
int ntfs_remove_named_stream(struct ntfs_inode *ni, __le16 *name,
|
||||
u32 name_len, struct inode *expected_inode);
|
||||
|
||||
int ntfs_stream_validate_for_mutation(struct inode *inode, bool nowait);
|
||||
long ntfs_ioctl_stream_read(struct file *file, unsigned long arg);
|
||||
long ntfs_ioctl_stream_write(struct file *file, unsigned long arg);
|
||||
long ntfs_ioctl_stream_remove(struct file *file, unsigned long arg);
|
||||
int ntfs_ioctl_list_streams(struct file *file, unsigned long arg);
|
||||
|
||||
extern const struct file_operations ntfs_stream_file_ops;
|
||||
extern const struct inode_operations ntfs_stream_inode_ops;
|
||||
|
||||
/*
|
||||
* Common file operations used by both ordinary files and named streams.
|
||||
* Their implementations remain in file.c.
|
||||
*/
|
||||
int ntfs_file_open(struct inode *inode, struct file *file);
|
||||
int ntfs_file_release(struct inode *inode, struct file *file);
|
||||
int ntfs_file_fsync(struct file *file, loff_t start, loff_t end,
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
#include "ea.h"
|
||||
#include "bitmap.h"
|
||||
#include "volume.h"
|
||||
#include "stream.h"
|
||||
|
||||
/* A global default upcase table and a corresponding reference count. */
|
||||
static __le16 *default_upcase;
|
||||
|
|
@ -67,6 +68,17 @@ static const struct constant_table ntfs_symlink_enums[] = {
|
|||
{}
|
||||
};
|
||||
|
||||
enum {
|
||||
STREAMS_INTERFACE_NONE,
|
||||
STREAMS_INTERFACE_WINDOWS,
|
||||
};
|
||||
|
||||
static const struct constant_table ntfs_streams_interface_enums[] = {
|
||||
{ "none", STREAMS_INTERFACE_NONE },
|
||||
{ "windows", STREAMS_INTERFACE_WINDOWS },
|
||||
{}
|
||||
};
|
||||
|
||||
enum {
|
||||
Opt_uid,
|
||||
Opt_gid,
|
||||
|
|
@ -92,6 +104,7 @@ enum {
|
|||
Opt_nocase,
|
||||
Opt_native_symlink,
|
||||
Opt_symlink,
|
||||
Opt_streams_interface,
|
||||
};
|
||||
|
||||
static const struct fs_parameter_spec ntfs_parameters[] = {
|
||||
|
|
@ -119,6 +132,8 @@ static const struct fs_parameter_spec ntfs_parameters[] = {
|
|||
fsparam_flag("nocase", Opt_nocase),
|
||||
fsparam_enum("native_symlink", Opt_native_symlink, ntfs_native_symlink_enums),
|
||||
fsparam_enum("symlink", Opt_symlink, ntfs_symlink_enums),
|
||||
fsparam_enum("streams_interface", Opt_streams_interface,
|
||||
ntfs_streams_interface_enums),
|
||||
{}
|
||||
};
|
||||
|
||||
|
|
@ -255,6 +270,22 @@ static int ntfs_parse_param(struct fs_context *fc, struct fs_parameter *param)
|
|||
else
|
||||
NVolClearSymlinkNative(vol);
|
||||
break;
|
||||
case Opt_streams_interface: {
|
||||
bool streams_windows =
|
||||
result.uint_32 == STREAMS_INTERFACE_WINDOWS;
|
||||
|
||||
if (fc->purpose == FS_CONTEXT_FOR_RECONFIGURE &&
|
||||
NVolStreamsWindows(vol) != streams_windows) {
|
||||
ntfs_error(vol->sb,
|
||||
"Cannot change streams_interface on remount.");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (streams_windows)
|
||||
NVolSetStreamsWindows(vol);
|
||||
else
|
||||
NVolClearStreamsWindows(vol);
|
||||
break;
|
||||
}
|
||||
case Opt_sparse:
|
||||
break;
|
||||
default:
|
||||
|
|
@ -2725,6 +2756,8 @@ static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc)
|
|||
* operations and associated address space operations to function.
|
||||
*/
|
||||
sb->s_op = &ntfs_sops;
|
||||
if (NVolStreamsWindows(vol))
|
||||
ntfs_set_default_dentry_ops(sb);
|
||||
tmp_ino = new_inode(sb);
|
||||
if (!tmp_ino) {
|
||||
if (!silent)
|
||||
|
|
|
|||
|
|
@ -197,6 +197,7 @@ struct ntfs_volume {
|
|||
* NV_DisableSparse Disable creation of sparse regions.
|
||||
* NV_NativeSymlinkRel Translate absolute Windows reparse targets (native_symlink=rel).
|
||||
* NV_MftBootstrap Mount is still assembling $MFT's own runlist.
|
||||
* NV_StreamsWindows Interpret the final path component as file:stream.
|
||||
*/
|
||||
enum {
|
||||
NV_Errors,
|
||||
|
|
@ -218,6 +219,7 @@ enum {
|
|||
NV_NativeSymlinkRel,
|
||||
NV_SymlinkNative,
|
||||
NV_MftBootstrap,
|
||||
NV_StreamsWindows,
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
@ -258,6 +260,7 @@ DEFINE_NVOL_BIT_OPS(DisableSparse)
|
|||
DEFINE_NVOL_BIT_OPS(NativeSymlinkRel)
|
||||
DEFINE_NVOL_BIT_OPS(SymlinkNative)
|
||||
DEFINE_NVOL_BIT_OPS(MftBootstrap)
|
||||
DEFINE_NVOL_BIT_OPS(StreamsWindows)
|
||||
|
||||
static inline void ntfs_inc_free_clusters(struct ntfs_volume *vol, s64 nr)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -311,7 +311,8 @@ static int parse_wof_chunk_table(struct ntfs_inode *base_ni,
|
|||
goto out_unlock_mrec;
|
||||
}
|
||||
ret = ntfs_attr_lookup(ni->type, ni->name, ni->name_len,
|
||||
CASE_SENSITIVE, 0, NULL, 0, ctx);
|
||||
CASE_SENSITIVE,
|
||||
0, NULL, 0, ctx);
|
||||
if (ret)
|
||||
goto out_put_ctx;
|
||||
|
||||
|
|
@ -399,7 +400,8 @@ static int ntfs_read_wof_chunk(struct ntfs_volume *vol,
|
|||
}
|
||||
|
||||
err = ntfs_attr_lookup(wof_ni->type, wof_ni->name, wof_ni->name_len,
|
||||
CASE_SENSITIVE, 0, NULL, 0, ctx);
|
||||
CASE_SENSITIVE,
|
||||
0, NULL, 0, ctx);
|
||||
if (err)
|
||||
goto out_put_ctx;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user