Changes for 6.12-rc3

New:
 	implement fallocate for compressed files;
 	add support for the compression attribute;
 	optimize large writes to sparse files.
 Fixed:
 	fix several potential deadlock scenarios;
 	fix various internal bugs detected by syzbot;
 	add checks before accessing NTFS structures during parsing;
 	correct the format of output messages.
 Refactored:
 	replace fsparam_flag_no with fsparam_flag in options parser;
 	remove unused functions and macros.
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEh0DEKNP0I9IjwfWEqbAzH4MkB7YFAmcFRkgACgkQqbAzH4Mk
 B7aQdQ//VT6lzBaxSZ0dm3xs/ygQYE3ter1OczAMQxh70O5px+Z0li5WCXQQe31W
 I74MmAy2G7C+JdzMkSc4F7+pFF21DO+1WQjODl9rP7yoHiLteiLFkMI+P5tHuFFT
 VPupAiCDQgm6QCtW2wofp/WR0ooxpLzC/yQsILrhUXJHdEY21oHLy/MQSOhVnv2k
 VeFdWA70+YS+JEQ+YyZBSeGeMVMQG9VROA57dZ+eAy1vgBUJGcKhYp9v4eiWG1uT
 A8rnV81FiHu+49OrE+ZwStguHkxu30dvlHuOqKW0pLW9/XoKCFf9w0WqLOGrAxLs
 lKznVgQZ521+QW+lNURMHBZt/bqQxQQInuOOH7jwLyBh0tmKoZlIU9Vw4V3yrm3t
 9NELb0FR/FeH02O89nMHl0SeaxDJvgFHDnfoOShuHaffur61d4RjChxqB4QcibOl
 Ibvtns71Uo89ngPIXdeTJK+7XZQVmAZHBXXYL+Rwr84Yv0tyn2q8DbrqzmphiYlR
 wbLyVBGqtx0ZcN/geuFLiVt2W/HSMPocXqrxv791ALX6cryL+CZdolQP7vWjt4Vh
 rKoaRZYN8fdcfT+BIYy95Kz0k12T7ColOoMfxVwugxK9ypF6tH6BJBwYXjCW4MOv
 AhkszJcsISxMKidsbzZA9xESM6M3sLHcW0swjAwWWRhc5zbSe6E=
 =j71V
 -----END PGP SIGNATURE-----

Merge tag 'ntfs3_for_6.12' of https://github.com/Paragon-Software-Group/linux-ntfs3

Pull ntfs3 updates from Konstantin Komarov:
"New:
   - implement fallocate for compressed files
   - add support for the compression attribute
   - optimize large writes to sparse files

 Fixes:
   - fix several potential deadlock scenarios
   - fix various internal bugs detected by syzbot
   - add checks before accessing NTFS structures during parsing
   - correct the format of output messages

  Refactoring:
   - replace fsparam_flag_no with fsparam_flag in options parser
   - remove unused functions and macros"

* tag 'ntfs3_for_6.12' of https://github.com/Paragon-Software-Group/linux-ntfs3: (25 commits)
  fs/ntfs3: Format output messages like others fs in kernel
  fs/ntfs3: Additional check in ntfs_file_release
  fs/ntfs3: Fix general protection fault in run_is_mapped_full
  fs/ntfs3: Sequential field availability check in mi_enum_attr()
  fs/ntfs3: Additional check in ni_clear()
  fs/ntfs3: Fix possible deadlock in mi_read
  ntfs3: Change to non-blocking allocation in ntfs_d_hash
  fs/ntfs3: Remove unused al_delete_le
  fs/ntfs3: Rename ntfs3_setattr into ntfs_setattr
  fs/ntfs3: Replace fsparam_flag_no -> fsparam_flag
  fs/ntfs3: Add support for the compression attribute
  fs/ntfs3: Implement fallocate for compressed files
  fs/ntfs3: Make checks in run_unpack more clear
  fs/ntfs3: Add rough attr alloc_size check
  fs/ntfs3: Stale inode instead of bad
  fs/ntfs3: Refactor enum_rstbl to suppress static checker
  fs/ntfs3: Fix sparse warning in ni_fiemap
  fs/ntfs3: Fix warning possible deadlock in ntfs_set_state
  fs/ntfs3: Fix sparse warning for bigendian
  fs/ntfs3: Separete common code for file_read/write iter/splice
  ...
This commit is contained in:
Linus Torvalds 2024-10-08 10:53:06 -07:00
commit 5b7c893ed5
14 changed files with 420 additions and 207 deletions

View File

@ -976,15 +976,17 @@ int attr_data_get_block(struct ntfs_inode *ni, CLST vcn, CLST clen, CLST *lcn,
goto out;
/* Check for compressed frame. */
err = attr_is_frame_compressed(ni, attr, vcn >> NTFS_LZNT_CUNIT, &hint);
err = attr_is_frame_compressed(ni, attr_b, vcn >> NTFS_LZNT_CUNIT,
&hint);
if (err)
goto out;
if (hint) {
/* if frame is compressed - don't touch it. */
*lcn = COMPRESSED_LCN;
*len = hint;
err = -EOPNOTSUPP;
/* length to the end of frame. */
*len = NTFS_LZNT_CLUSTERS - (vcn & (NTFS_LZNT_CLUSTERS - 1));
err = 0;
goto out;
}
@ -1027,16 +1029,16 @@ int attr_data_get_block(struct ntfs_inode *ni, CLST vcn, CLST clen, CLST *lcn,
/* Check if 'vcn' and 'vcn0' in different attribute segments. */
if (vcn < svcn || evcn1 <= vcn) {
/* Load attribute for truncated vcn. */
attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA, NULL, 0,
&vcn, &mi);
if (!attr) {
struct ATTRIB *attr2;
/* Load runs for truncated vcn. */
attr2 = ni_find_attr(ni, attr_b, &le_b, ATTR_DATA, NULL,
0, &vcn, &mi);
if (!attr2) {
err = -EINVAL;
goto out;
}
svcn = le64_to_cpu(attr->nres.svcn);
evcn1 = le64_to_cpu(attr->nres.evcn) + 1;
err = attr_load_runs(attr, ni, run, NULL);
evcn1 = le64_to_cpu(attr2->nres.evcn) + 1;
err = attr_load_runs(attr2, ni, run, NULL);
if (err)
goto out;
}
@ -1517,6 +1519,9 @@ out:
/*
* attr_is_frame_compressed - Used to detect compressed frame.
*
* attr - base (primary) attribute segment.
* Only base segments contains valid 'attr->nres.c_unit'
*/
int attr_is_frame_compressed(struct ntfs_inode *ni, struct ATTRIB *attr,
CLST frame, CLST *clst_data)
@ -2600,3 +2605,74 @@ int attr_force_nonresident(struct ntfs_inode *ni)
return err;
}
/*
* Change the compression of data attribute
*/
int attr_set_compress(struct ntfs_inode *ni, bool compr)
{
struct ATTRIB *attr;
struct mft_inode *mi;
attr = ni_find_attr(ni, NULL, NULL, ATTR_DATA, NULL, 0, NULL, &mi);
if (!attr)
return -ENOENT;
if (is_attr_compressed(attr) == !!compr) {
/* Already required compressed state. */
return 0;
}
if (attr->non_res) {
u16 run_off;
u32 run_size;
char *run;
if (attr->nres.data_size) {
/*
* There are rare cases when it possible to change
* compress state without big changes.
* TODO: Process these cases.
*/
return -EOPNOTSUPP;
}
run_off = le16_to_cpu(attr->nres.run_off);
run_size = le32_to_cpu(attr->size) - run_off;
run = Add2Ptr(attr, run_off);
if (!compr) {
/* remove field 'attr->nres.total_size'. */
memmove(run - 8, run, run_size);
run_off -= 8;
}
if (!mi_resize_attr(mi, attr, compr ? +8 : -8)) {
/*
* Ignore rare case when there are no 8 bytes in record with attr.
* TODO: split attribute.
*/
return -EOPNOTSUPP;
}
if (compr) {
/* Make a gap for 'attr->nres.total_size'. */
memmove(run + 8, run, run_size);
run_off += 8;
attr->nres.total_size = attr->nres.alloc_size;
}
attr->nres.run_off = cpu_to_le16(run_off);
}
/* Update data attribute flags. */
if (compr) {
attr->flags |= ATTR_FLAG_COMPRESSED;
attr->nres.c_unit = NTFS_LZNT_CUNIT;
} else {
attr->flags &= ~ATTR_FLAG_COMPRESSED;
attr->nres.c_unit = 0;
}
mi->dirty = true;
return 0;
}

View File

@ -382,59 +382,6 @@ bool al_remove_le(struct ntfs_inode *ni, struct ATTR_LIST_ENTRY *le)
return true;
}
/*
* al_delete_le - Delete first le from the list which matches its parameters.
*/
bool al_delete_le(struct ntfs_inode *ni, enum ATTR_TYPE type, CLST vcn,
const __le16 *name, u8 name_len, const struct MFT_REF *ref)
{
u16 size;
struct ATTR_LIST_ENTRY *le;
size_t off;
typeof(ni->attr_list) *al = &ni->attr_list;
/* Scan forward to the first le that matches the input. */
le = al_find_ex(ni, NULL, type, name, name_len, &vcn);
if (!le)
return false;
off = PtrOffset(al->le, le);
next:
if (off >= al->size)
return false;
if (le->type != type)
return false;
if (le->name_len != name_len)
return false;
if (name_len && ntfs_cmp_names(le_name(le), name_len, name, name_len,
ni->mi.sbi->upcase, true))
return false;
if (le64_to_cpu(le->vcn) != vcn)
return false;
/*
* The caller specified a segment reference, so we have to
* scan through the matching entries until we find that segment
* reference or we run of matching entries.
*/
if (ref && memcmp(ref, &le->ref, sizeof(*ref))) {
off += le16_to_cpu(le->size);
le = Add2Ptr(al->le, off);
goto next;
}
/* Save on stack the size of 'le'. */
size = le16_to_cpu(le->size);
/* Delete the le. */
memmove(le, Add2Ptr(le, size), al->size - (off + size));
al->size -= size;
al->dirty = true;
return true;
}
int al_update(struct ntfs_inode *ni, int sync)
{
int err;

View File

@ -82,13 +82,14 @@ int ntfs_fileattr_set(struct mnt_idmap *idmap, struct dentry *dentry,
struct fileattr *fa)
{
struct inode *inode = d_inode(dentry);
struct ntfs_inode *ni = ntfs_i(inode);
u32 flags = fa->flags;
unsigned int new_fl = 0;
if (fileattr_has_fsx(fa))
return -EOPNOTSUPP;
if (flags & ~(FS_IMMUTABLE_FL | FS_APPEND_FL))
if (flags & ~(FS_IMMUTABLE_FL | FS_APPEND_FL | FS_COMPR_FL))
return -EOPNOTSUPP;
if (flags & FS_IMMUTABLE_FL)
@ -97,6 +98,15 @@ int ntfs_fileattr_set(struct mnt_idmap *idmap, struct dentry *dentry,
if (flags & FS_APPEND_FL)
new_fl |= S_APPEND;
/* Allowed to change compression for empty files and for directories only. */
if (!is_dedup(ni) && !is_encrypted(ni) &&
(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode))) {
/* Change compress state. */
int err = ni_set_compress(inode, flags & FS_COMPR_FL);
if (err)
return err;
}
inode_set_flags(inode, new_fl, S_IMMUTABLE | S_APPEND);
inode_set_ctime_current(inode);
@ -407,6 +417,42 @@ static int ntfs_extend(struct inode *inode, loff_t pos, size_t count,
err = 0;
}
if (file && is_sparsed(ni)) {
/*
* This code optimizes large writes to sparse file.
* TODO: merge this fragment with fallocate fragment.
*/
struct ntfs_sb_info *sbi = ni->mi.sbi;
CLST vcn = pos >> sbi->cluster_bits;
CLST cend = bytes_to_cluster(sbi, end);
CLST cend_v = bytes_to_cluster(sbi, ni->i_valid);
CLST lcn, clen;
bool new;
if (cend_v > cend)
cend_v = cend;
/*
* Allocate and zero new clusters.
* Zeroing these clusters may be too long.
*/
for (; vcn < cend_v; vcn += clen) {
err = attr_data_get_block(ni, vcn, cend_v - vcn, &lcn,
&clen, &new, true);
if (err)
goto out;
}
/*
* Allocate but not zero new clusters.
*/
for (; vcn < cend; vcn += clen) {
err = attr_data_get_block(ni, vcn, cend - vcn, &lcn,
&clen, &new, false);
if (err)
goto out;
}
}
inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
mark_inode_dirty(inode);
@ -483,7 +529,7 @@ static int ntfs_truncate(struct inode *inode, loff_t new_size)
}
/*
* ntfs_fallocate
* ntfs_fallocate - file_operations::ntfs_fallocate
*
* Preallocate space for a file. This implements ntfs's fallocate file
* operation, which gets called from sys_fallocate system call. User
@ -618,6 +664,8 @@ static long ntfs_fallocate(struct file *file, int mode, loff_t vbo, loff_t len)
ni_lock(ni);
err = attr_collapse_range(ni, vbo, len);
ni_unlock(ni);
if (err)
goto out;
} else if (mode & FALLOC_FL_INSERT_RANGE) {
/* Check new size. */
err = inode_newsize_ok(inode, new_size);
@ -740,10 +788,10 @@ out:
}
/*
* ntfs3_setattr - inode_operations::setattr
* ntfs_setattr - inode_operations::setattr
*/
int ntfs3_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
struct iattr *attr)
int ntfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
struct iattr *attr)
{
struct inode *inode = d_inode(dentry);
struct ntfs_inode *ni = ntfs_i(inode);
@ -803,10 +851,12 @@ out:
return err;
}
static ssize_t ntfs_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
/*
* check_read_restriction:
* common code for ntfs_file_read_iter and ntfs_file_splice_read
*/
static int check_read_restriction(struct inode *inode)
{
struct file *file = iocb->ki_filp;
struct inode *inode = file_inode(file);
struct ntfs_inode *ni = ntfs_i(inode);
if (unlikely(ntfs3_forced_shutdown(inode->i_sb)))
@ -817,56 +867,58 @@ static ssize_t ntfs_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
return -EOPNOTSUPP;
}
#ifndef CONFIG_NTFS3_LZX_XPRESS
if (ni->ni_flags & NI_FLAG_COMPRESSED_MASK) {
ntfs_inode_warn(
inode,
"activate CONFIG_NTFS3_LZX_XPRESS to read external compressed files");
return -EOPNOTSUPP;
}
#endif
if (is_dedup(ni)) {
ntfs_inode_warn(inode, "read deduplicated not supported");
return -EOPNOTSUPP;
}
return 0;
}
/*
* ntfs_file_read_iter - file_operations::read_iter
*/
static ssize_t ntfs_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
{
struct file *file = iocb->ki_filp;
struct inode *inode = file_inode(file);
struct ntfs_inode *ni = ntfs_i(inode);
ssize_t err;
err = check_read_restriction(inode);
if (err)
return err;
if (is_compressed(ni) && (iocb->ki_flags & IOCB_DIRECT)) {
ntfs_inode_warn(inode, "direct i/o + compressed not supported");
return -EOPNOTSUPP;
}
#ifndef CONFIG_NTFS3_LZX_XPRESS
if (ni->ni_flags & NI_FLAG_COMPRESSED_MASK) {
ntfs_inode_warn(
inode,
"activate CONFIG_NTFS3_LZX_XPRESS to read external compressed files");
return -EOPNOTSUPP;
}
#endif
if (is_dedup(ni)) {
ntfs_inode_warn(inode, "read deduplicated not supported");
return -EOPNOTSUPP;
}
return generic_file_read_iter(iocb, iter);
}
/*
* ntfs_file_splice_read - file_operations::splice_read
*/
static ssize_t ntfs_file_splice_read(struct file *in, loff_t *ppos,
struct pipe_inode_info *pipe, size_t len,
unsigned int flags)
{
struct inode *inode = file_inode(in);
struct ntfs_inode *ni = ntfs_i(inode);
ssize_t err;
if (unlikely(ntfs3_forced_shutdown(inode->i_sb)))
return -EIO;
if (is_encrypted(ni)) {
ntfs_inode_warn(inode, "encrypted i/o not supported");
return -EOPNOTSUPP;
}
#ifndef CONFIG_NTFS3_LZX_XPRESS
if (ni->ni_flags & NI_FLAG_COMPRESSED_MASK) {
ntfs_inode_warn(
inode,
"activate CONFIG_NTFS3_LZX_XPRESS to read external compressed files");
return -EOPNOTSUPP;
}
#endif
if (is_dedup(ni)) {
ntfs_inode_warn(inode, "read deduplicated not supported");
return -EOPNOTSUPP;
}
err = check_read_restriction(inode);
if (err)
return err;
return filemap_splice_read(in, ppos, pipe, len, flags);
}
@ -1134,14 +1186,11 @@ out:
}
/*
* ntfs_file_write_iter - file_operations::write_iter
* check_write_restriction:
* common code for ntfs_file_write_iter and ntfs_file_splice_write
*/
static ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
static int check_write_restriction(struct inode *inode)
{
struct file *file = iocb->ki_filp;
struct inode *inode = file_inode(file);
ssize_t ret;
int err;
struct ntfs_inode *ni = ntfs_i(inode);
if (unlikely(ntfs3_forced_shutdown(inode->i_sb)))
@ -1152,13 +1201,31 @@ static ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
return -EOPNOTSUPP;
}
if (is_compressed(ni) && (iocb->ki_flags & IOCB_DIRECT)) {
ntfs_inode_warn(inode, "direct i/o + compressed not supported");
if (is_dedup(ni)) {
ntfs_inode_warn(inode, "write into deduplicated not supported");
return -EOPNOTSUPP;
}
if (is_dedup(ni)) {
ntfs_inode_warn(inode, "write into deduplicated not supported");
return 0;
}
/*
* ntfs_file_write_iter - file_operations::write_iter
*/
static ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
struct file *file = iocb->ki_filp;
struct inode *inode = file_inode(file);
struct ntfs_inode *ni = ntfs_i(inode);
ssize_t ret;
int err;
err = check_write_restriction(inode);
if (err)
return err;
if (is_compressed(ni) && (iocb->ki_flags & IOCB_DIRECT)) {
ntfs_inode_warn(inode, "direct i/o + compressed not supported");
return -EOPNOTSUPP;
}
@ -1246,7 +1313,14 @@ static int ntfs_file_release(struct inode *inode, struct file *file)
/* If we are last writer on the inode, drop the block reservation. */
if (sbi->options->prealloc &&
((file->f_mode & FMODE_WRITE) &&
atomic_read(&inode->i_writecount) == 1)) {
atomic_read(&inode->i_writecount) == 1)
/*
* The only file when inode->i_fop = &ntfs_file_operations and
* init_rwsem(&ni->file.run_lock) is not called explicitly is MFT.
*
* Add additional check here.
*/
&& inode->i_ino != MFT_REC_MFT) {
ni_lock(ni);
down_write(&ni->file.run_lock);
@ -1282,10 +1356,27 @@ int ntfs_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
return err;
}
/*
* ntfs_file_splice_write - file_operations::splice_write
*/
static ssize_t ntfs_file_splice_write(struct pipe_inode_info *pipe,
struct file *file, loff_t *ppos,
size_t len, unsigned int flags)
{
ssize_t err;
struct inode *inode = file_inode(file);
err = check_write_restriction(inode);
if (err)
return err;
return iter_file_splice_write(pipe, file, ppos, len, flags);
}
// clang-format off
const struct inode_operations ntfs_file_inode_operations = {
.getattr = ntfs_getattr,
.setattr = ntfs3_setattr,
.setattr = ntfs_setattr,
.listxattr = ntfs_listxattr,
.get_acl = ntfs_get_acl,
.set_acl = ntfs_set_acl,
@ -1303,10 +1394,10 @@ const struct file_operations ntfs_file_operations = {
.compat_ioctl = ntfs_compat_ioctl,
#endif
.splice_read = ntfs_file_splice_read,
.splice_write = ntfs_file_splice_write,
.mmap = ntfs_file_mmap,
.open = ntfs_file_open,
.fsync = generic_file_fsync,
.splice_write = iter_file_splice_write,
.fallocate = ntfs_fallocate,
.release = ntfs_file_release,
};

View File

@ -102,7 +102,9 @@ void ni_clear(struct ntfs_inode *ni)
{
struct rb_node *node;
if (!ni->vfs_inode.i_nlink && ni->mi.mrec && is_rec_inuse(ni->mi.mrec))
if (!ni->vfs_inode.i_nlink && ni->mi.mrec &&
is_rec_inuse(ni->mi.mrec) &&
!(ni->mi.sbi->flags & NTFS_FLAGS_LOG_REPLAYING))
ni_delete_all(ni);
al_destroy(ni);
@ -1900,13 +1902,13 @@ enum REPARSE_SIGN ni_parse_reparse(struct ntfs_inode *ni, struct ATTRIB *attr,
/*
* fiemap_fill_next_extent_k - a copy of fiemap_fill_next_extent
* but it accepts kernel address for fi_extents_start
* but it uses 'fe_k' instead of fieinfo->fi_extents_start
*/
static int fiemap_fill_next_extent_k(struct fiemap_extent_info *fieinfo,
u64 logical, u64 phys, u64 len, u32 flags)
struct fiemap_extent *fe_k, u64 logical,
u64 phys, u64 len, u32 flags)
{
struct fiemap_extent extent;
struct fiemap_extent __user *dest = fieinfo->fi_extents_start;
/* only count the extents */
if (fieinfo->fi_extents_max == 0) {
@ -1930,8 +1932,7 @@ static int fiemap_fill_next_extent_k(struct fiemap_extent_info *fieinfo,
extent.fe_length = len;
extent.fe_flags = flags;
dest += fieinfo->fi_extents_mapped;
memcpy(dest, &extent, sizeof(extent));
memcpy(fe_k + fieinfo->fi_extents_mapped, &extent, sizeof(extent));
fieinfo->fi_extents_mapped++;
if (fieinfo->fi_extents_mapped == fieinfo->fi_extents_max)
@ -1949,7 +1950,6 @@ int ni_fiemap(struct ntfs_inode *ni, struct fiemap_extent_info *fieinfo,
__u64 vbo, __u64 len)
{
int err = 0;
struct fiemap_extent __user *fe_u = fieinfo->fi_extents_start;
struct fiemap_extent *fe_k = NULL;
struct ntfs_sb_info *sbi = ni->mi.sbi;
u8 cluster_bits = sbi->cluster_bits;
@ -2008,7 +2008,6 @@ int ni_fiemap(struct ntfs_inode *ni, struct fiemap_extent_info *fieinfo,
err = -ENOMEM;
goto out;
}
fieinfo->fi_extents_start = fe_k;
end = vbo + len;
alloc_size = le64_to_cpu(attr->nres.alloc_size);
@ -2098,8 +2097,8 @@ int ni_fiemap(struct ntfs_inode *ni, struct fiemap_extent_info *fieinfo,
if (vbo + dlen >= end)
flags |= FIEMAP_EXTENT_LAST;
err = fiemap_fill_next_extent_k(fieinfo, vbo, lbo, dlen,
flags);
err = fiemap_fill_next_extent_k(fieinfo, fe_k, vbo, lbo,
dlen, flags);
if (err < 0)
break;
@ -2120,7 +2119,7 @@ int ni_fiemap(struct ntfs_inode *ni, struct fiemap_extent_info *fieinfo,
if (vbo + bytes >= end)
flags |= FIEMAP_EXTENT_LAST;
err = fiemap_fill_next_extent_k(fieinfo, vbo, lbo, bytes,
err = fiemap_fill_next_extent_k(fieinfo, fe_k, vbo, lbo, bytes,
flags);
if (err < 0)
break;
@ -2137,15 +2136,13 @@ int ni_fiemap(struct ntfs_inode *ni, struct fiemap_extent_info *fieinfo,
/*
* Copy to user memory out of lock
*/
if (copy_to_user(fe_u, fe_k,
if (copy_to_user(fieinfo->fi_extents_start, fe_k,
fieinfo->fi_extents_max *
sizeof(struct fiemap_extent))) {
err = -EFAULT;
}
out:
/* Restore original pointer. */
fieinfo->fi_extents_start = fe_u;
kfree(fe_k);
return err;
}
@ -3455,3 +3452,75 @@ out:
return 0;
}
/*
* ni_set_compress
*
* Helper for 'ntfs_fileattr_set'.
* Changes compression for empty files and directories only.
*/
int ni_set_compress(struct inode *inode, bool compr)
{
int err;
struct ntfs_inode *ni = ntfs_i(inode);
struct ATTR_STD_INFO *std;
const char *bad_inode;
if (is_compressed(ni) == !!compr)
return 0;
if (is_sparsed(ni)) {
/* sparse and compress not compatible. */
return -EOPNOTSUPP;
}
if (!S_ISREG(inode->i_mode) && !S_ISDIR(inode->i_mode)) {
/*Skip other inodes. (symlink,fifo,...) */
return -EOPNOTSUPP;
}
bad_inode = NULL;
ni_lock(ni);
std = ni_std(ni);
if (!std) {
bad_inode = "no std";
goto out;
}
if (S_ISREG(inode->i_mode)) {
err = attr_set_compress(ni, compr);
if (err) {
if (err == -ENOENT) {
/* Fix on the fly? */
/* Each file must contain data attribute. */
bad_inode = "no data attribute";
}
goto out;
}
}
ni->std_fa = std->fa;
if (compr)
std->fa |= FILE_ATTRIBUTE_COMPRESSED;
else
std->fa &= ~FILE_ATTRIBUTE_COMPRESSED;
if (ni->std_fa != std->fa) {
ni->std_fa = std->fa;
ni->mi.dirty = true;
}
/* update duplicate information and directory entries in ni_write_inode.*/
ni->ni_flags |= NI_FLAG_UPDATE_PARENT;
err = 0;
out:
ni_unlock(ni);
if (bad_inode) {
ntfs_bad_inode(inode, bad_inode);
err = -EINVAL;
}
return err;
}

View File

@ -609,14 +609,29 @@ static inline void add_client(struct CLIENT_REC *ca, u16 index, __le16 *head)
*head = cpu_to_le16(index);
}
/*
* Enumerate restart table.
*
* @t - table to enumerate.
* @c - current enumerated element.
*
* enumeration starts with @c == NULL
* returns next element or NULL
*/
static inline void *enum_rstbl(struct RESTART_TABLE *t, void *c)
{
__le32 *e;
u32 bprt;
u16 rsize = t ? le16_to_cpu(t->size) : 0;
u16 rsize;
if (!t)
return NULL;
rsize = le16_to_cpu(t->size);
if (!c) {
if (!t || !t->total)
/* start enumeration. */
if (!t->total)
return NULL;
e = Add2Ptr(t, sizeof(struct RESTART_TABLE));
} else {

View File

@ -536,11 +536,15 @@ struct inode *ntfs_iget5(struct super_block *sb, const struct MFT_REF *ref,
if (inode->i_state & I_NEW)
inode = ntfs_read_mft(inode, name, ref);
else if (ref->seq != ntfs_i(inode)->mi.mrec->seq) {
/* Inode overlaps? */
_ntfs_bad_inode(inode);
/*
* Sequence number is not expected.
* Looks like inode was reused but caller uses the old reference
*/
iput(inode);
inode = ERR_PTR(-ESTALE);
}
if (IS_ERR(inode) && name)
if (IS_ERR(inode))
ntfs_set_state(sb->s_fs_info, NTFS_DIRTY_ERROR);
return inode;
@ -605,7 +609,8 @@ static noinline int ntfs_get_block_vbo(struct inode *inode, u64 vbo,
bytes = ((u64)len << cluster_bits) - off;
if (lcn == SPARSE_LCN) {
if (lcn >= sbi->used.bitmap.nbits) {
/* This case includes resident/compressed/sparse. */
if (!create) {
if (bh->b_size > bytes)
bh->b_size = bytes;
@ -1672,7 +1677,10 @@ out6:
attr = ni_find_attr(ni, NULL, NULL, ATTR_EA, NULL, 0, NULL, NULL);
if (attr && attr->non_res) {
/* Delete ATTR_EA, if non-resident. */
attr_set_size(ni, ATTR_EA, NULL, 0, NULL, 0, NULL, false, NULL);
struct runs_tree run;
run_init(&run);
attr_set_size(ni, ATTR_EA, NULL, 0, &run, 0, NULL, false, NULL);
run_close(&run);
}
if (rp_inserted)
@ -2076,7 +2084,7 @@ static const char *ntfs_get_link(struct dentry *de, struct inode *inode,
// clang-format off
const struct inode_operations ntfs_link_inode_operations = {
.get_link = ntfs_get_link,
.setattr = ntfs3_setattr,
.setattr = ntfs_setattr,
.listxattr = ntfs_listxattr,
};

View File

@ -512,8 +512,7 @@ static int lzx_decompress_block(const struct lzx_decompressor *d,
* the same code. (For R0, the swap is a no-op.)
*/
match_offset = recent_offsets[offset_slot];
recent_offsets[offset_slot] = recent_offsets[0];
recent_offsets[0] = match_offset;
swap(recent_offsets[offset_slot], recent_offsets[0]);
} else {
/* Explicit offset */

View File

@ -236,6 +236,9 @@ static inline ssize_t decompress_chunk(u8 *unc, u8 *unc_end, const u8 *cmpr,
/* Do decompression until pointers are inside range. */
while (up < unc_end && cmpr < cmpr_end) {
// return err if more than LZNT_CHUNK_SIZE bytes are written
if (up - unc > LZNT_CHUNK_SIZE)
return -EINVAL;
/* Correct index */
while (unc + s_max_off[index] < up)
index += 1;

View File

@ -81,7 +81,7 @@ static struct dentry *ntfs_lookup(struct inode *dir, struct dentry *dentry,
if (err < 0)
inode = ERR_PTR(err);
else {
ni_lock(ni);
ni_lock_dir(ni);
inode = dir_search_u(dir, uni, NULL);
ni_unlock(ni);
}
@ -395,7 +395,7 @@ static int ntfs_d_hash(const struct dentry *dentry, struct qstr *name)
/*
* Try slow way with current upcase table
*/
uni = __getname();
uni = kmem_cache_alloc(names_cachep, GFP_NOWAIT);
if (!uni)
return -ENOMEM;
@ -417,7 +417,7 @@ static int ntfs_d_hash(const struct dentry *dentry, struct qstr *name)
err = 0;
out:
__putname(uni);
kmem_cache_free(names_cachep, uni);
return err;
}
@ -503,7 +503,7 @@ const struct inode_operations ntfs_dir_inode_operations = {
.rename = ntfs_rename,
.get_acl = ntfs_get_acl,
.set_acl = ntfs_set_acl,
.setattr = ntfs3_setattr,
.setattr = ntfs_setattr,
.getattr = ntfs_getattr,
.listxattr = ntfs_listxattr,
.fiemap = ntfs_fiemap,
@ -512,7 +512,7 @@ const struct inode_operations ntfs_dir_inode_operations = {
};
const struct inode_operations ntfs_special_inode_operations = {
.setattr = ntfs3_setattr,
.setattr = ntfs_setattr,
.getattr = ntfs_getattr,
.listxattr = ntfs_listxattr,
.get_acl = ntfs_get_acl,

View File

@ -334,7 +334,7 @@ struct mft_inode {
/* Nested class for ntfs_inode::ni_lock. */
enum ntfs_inode_mutex_lock_class {
NTFS_INODE_MUTEX_DIRTY,
NTFS_INODE_MUTEX_DIRTY = 1,
NTFS_INODE_MUTEX_SECURITY,
NTFS_INODE_MUTEX_OBJID,
NTFS_INODE_MUTEX_REPARSE,
@ -453,6 +453,7 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes);
int attr_insert_range(struct ntfs_inode *ni, u64 vbo, u64 bytes);
int attr_punch_hole(struct ntfs_inode *ni, u64 vbo, u64 bytes, u32 *frame_size);
int attr_force_nonresident(struct ntfs_inode *ni);
int attr_set_compress(struct ntfs_inode *ni, bool compr);
/* Functions from attrlist.c */
void al_destroy(struct ntfs_inode *ni);
@ -471,8 +472,6 @@ int al_add_le(struct ntfs_inode *ni, enum ATTR_TYPE type, const __le16 *name,
u8 name_len, CLST svcn, __le16 id, const struct MFT_REF *ref,
struct ATTR_LIST_ENTRY **new_le);
bool al_remove_le(struct ntfs_inode *ni, struct ATTR_LIST_ENTRY *le);
bool al_delete_le(struct ntfs_inode *ni, enum ATTR_TYPE type, CLST vcn,
const __le16 *name, u8 name_len, const struct MFT_REF *ref);
int al_update(struct ntfs_inode *ni, int sync);
static inline size_t al_aligned(size_t size)
{
@ -502,8 +501,8 @@ int ntfs_fileattr_set(struct mnt_idmap *idmap, struct dentry *dentry,
struct fileattr *fa);
int ntfs_getattr(struct mnt_idmap *idmap, const struct path *path,
struct kstat *stat, u32 request_mask, u32 flags);
int ntfs3_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
struct iattr *attr);
int ntfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
struct iattr *attr);
int ntfs_file_open(struct inode *inode, struct file *file);
int ntfs_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
__u64 start, __u64 len);
@ -588,6 +587,7 @@ int ni_rename(struct ntfs_inode *dir_ni, struct ntfs_inode *new_dir_ni,
bool *is_bad);
bool ni_is_dirty(struct inode *inode);
int ni_set_compress(struct inode *inode, bool compr);
/* Globals from fslog.c */
bool check_index_header(const struct INDEX_HDR *hdr, size_t bytes);

View File

@ -223,29 +223,21 @@ struct ATTRIB *mi_enum_attr(struct mft_inode *mi, struct ATTRIB *attr)
prev_type = 0;
attr = Add2Ptr(rec, off);
} else {
/* Check if input attr inside record. */
/*
* We don't need to check previous attr here. There is
* a bounds checking in the previous round.
*/
off = PtrOffset(rec, attr);
if (off >= used)
return NULL;
asize = le32_to_cpu(attr->size);
if (asize < SIZEOF_RESIDENT) {
/* Impossible 'cause we should not return such attribute. */
return NULL;
}
/* Overflow check. */
if (off + asize < off)
return NULL;
prev_type = le32_to_cpu(attr->type);
attr = Add2Ptr(attr, asize);
off += asize;
}
asize = le32_to_cpu(attr->size);
/* Can we use the first field (attr->type). */
/* NOTE: this code also checks attr->size availability. */
if (off + 8 > used) {
static_assert(ALIGN(sizeof(enum ATTR_TYPE), 8) == 8);
return NULL;
@ -265,6 +257,8 @@ struct ATTRIB *mi_enum_attr(struct mft_inode *mi, struct ATTRIB *attr)
if (t32 < prev_type)
return NULL;
asize = le32_to_cpu(attr->size);
/* Check overflow and boundary. */
if (off + asize < off || off + asize > used)
return NULL;
@ -293,6 +287,10 @@ struct ATTRIB *mi_enum_attr(struct mft_inode *mi, struct ATTRIB *attr)
if (attr->non_res != 1)
return NULL;
/* Can we use memory including attr->nres.valid_size? */
if (asize < SIZEOF_NONRESIDENT)
return NULL;
t16 = le16_to_cpu(attr->nres.run_off);
if (t16 > asize)
return NULL;
@ -319,7 +317,8 @@ struct ATTRIB *mi_enum_attr(struct mft_inode *mi, struct ATTRIB *attr)
if (!attr->nres.svcn && is_attr_ext(attr)) {
/* First segment of sparse/compressed attribute */
if (asize + 8 < SIZEOF_NONRESIDENT_EX)
/* Can we use memory including attr->nres.total_size? */
if (asize < SIZEOF_NONRESIDENT_EX)
return NULL;
tot_size = le64_to_cpu(attr->nres.total_size);
@ -329,10 +328,10 @@ struct ATTRIB *mi_enum_attr(struct mft_inode *mi, struct ATTRIB *attr)
if (tot_size > alloc_size)
return NULL;
} else {
if (asize + 8 < SIZEOF_NONRESIDENT)
if (attr->nres.c_unit)
return NULL;
if (attr->nres.c_unit)
if (alloc_size > mi->sbi->volume.size)
return NULL;
}

View File

@ -959,7 +959,7 @@ int run_unpack(struct runs_tree *run, struct ntfs_sb_info *sbi, CLST ino,
* Large positive number requires to store 5 bytes
* e.g.: 05 FF 7E FF FF 00 00 00
*/
if (size_size > 8)
if (size_size > sizeof(len))
return -EINVAL;
len = run_unpack_s64(run_buf, size_size, 0);
@ -971,7 +971,7 @@ int run_unpack(struct runs_tree *run, struct ntfs_sb_info *sbi, CLST ino,
if (!offset_size)
lcn = SPARSE_LCN64;
else if (offset_size <= 8) {
else if (offset_size <= sizeof(s64)) {
s64 dlcn;
/* Initial value of dlcn is -1 or 0. */
@ -984,8 +984,10 @@ int run_unpack(struct runs_tree *run, struct ntfs_sb_info *sbi, CLST ino,
return -EINVAL;
lcn = prev_lcn + dlcn;
prev_lcn = lcn;
} else
} else {
/* The size of 'dlcn' can't be > 8. */
return -EINVAL;
}
next_vcn = vcn64 + len;
/* Check boundary. */

View File

@ -90,7 +90,7 @@ void ntfs_printk(const struct super_block *sb, const char *fmt, ...)
level = printk_get_level(fmt);
vaf.fmt = printk_skip_level(fmt);
vaf.va = &args;
printk("%c%cntfs3: %s: %pV\n", KERN_SOH_ASCII, level, sb->s_id, &vaf);
printk("%c%cntfs3(%s): %pV\n", KERN_SOH_ASCII, level, sb->s_id, &vaf);
va_end(args);
}
@ -124,10 +124,15 @@ void ntfs_inode_printk(struct inode *inode, const char *fmt, ...)
struct dentry *de = d_find_alias(inode);
if (de) {
int len;
spin_lock(&de->d_lock);
snprintf(name, sizeof(s_name_buf), " \"%s\"",
de->d_name.name);
len = snprintf(name, sizeof(s_name_buf), " \"%s\"",
de->d_name.name);
spin_unlock(&de->d_lock);
if (len <= 0)
name[0] = 0;
else if (len >= sizeof(s_name_buf))
name[sizeof(s_name_buf) - 1] = 0;
} else {
name[0] = 0;
}
@ -140,7 +145,7 @@ void ntfs_inode_printk(struct inode *inode, const char *fmt, ...)
vaf.fmt = printk_skip_level(fmt);
vaf.va = &args;
printk("%c%cntfs3: %s: ino=%lx,%s %pV\n", KERN_SOH_ASCII, level,
printk("%c%cntfs3(%s): ino=%lx,%s %pV\n", KERN_SOH_ASCII, level,
sb->s_id, inode->i_ino, name ? name : "", &vaf);
va_end(args);
@ -259,23 +264,23 @@ enum Opt {
// clang-format off
static const struct fs_parameter_spec ntfs_fs_parameters[] = {
fsparam_uid("uid", Opt_uid),
fsparam_gid("gid", Opt_gid),
fsparam_u32oct("umask", Opt_umask),
fsparam_u32oct("dmask", Opt_dmask),
fsparam_u32oct("fmask", Opt_fmask),
fsparam_flag_no("sys_immutable", Opt_immutable),
fsparam_flag_no("discard", Opt_discard),
fsparam_flag_no("force", Opt_force),
fsparam_flag_no("sparse", Opt_sparse),
fsparam_flag_no("hidden", Opt_nohidden),
fsparam_flag_no("hide_dot_files", Opt_hide_dot_files),
fsparam_flag_no("windows_names", Opt_windows_names),
fsparam_flag_no("showmeta", Opt_showmeta),
fsparam_flag_no("acl", Opt_acl),
fsparam_string("iocharset", Opt_iocharset),
fsparam_flag_no("prealloc", Opt_prealloc),
fsparam_flag_no("case", Opt_nocase),
fsparam_uid("uid", Opt_uid),
fsparam_gid("gid", Opt_gid),
fsparam_u32oct("umask", Opt_umask),
fsparam_u32oct("dmask", Opt_dmask),
fsparam_u32oct("fmask", Opt_fmask),
fsparam_flag("sys_immutable", Opt_immutable),
fsparam_flag("discard", Opt_discard),
fsparam_flag("force", Opt_force),
fsparam_flag("sparse", Opt_sparse),
fsparam_flag("nohidden", Opt_nohidden),
fsparam_flag("hide_dot_files", Opt_hide_dot_files),
fsparam_flag("windows_names", Opt_windows_names),
fsparam_flag("showmeta", Opt_showmeta),
fsparam_flag("acl", Opt_acl),
fsparam_string("iocharset", Opt_iocharset),
fsparam_flag("prealloc", Opt_prealloc),
fsparam_flag("nocase", Opt_nocase),
{}
};
// clang-format on
@ -345,28 +350,28 @@ static int ntfs_fs_parse_param(struct fs_context *fc,
opts->fmask = 1;
break;
case Opt_immutable:
opts->sys_immutable = result.negated ? 0 : 1;
opts->sys_immutable = 1;
break;
case Opt_discard:
opts->discard = result.negated ? 0 : 1;
opts->discard = 1;
break;
case Opt_force:
opts->force = result.negated ? 0 : 1;
opts->force = 1;
break;
case Opt_sparse:
opts->sparse = result.negated ? 0 : 1;
opts->sparse = 1;
break;
case Opt_nohidden:
opts->nohidden = result.negated ? 1 : 0;
opts->nohidden = 1;
break;
case Opt_hide_dot_files:
opts->hide_dot_files = result.negated ? 0 : 1;
opts->hide_dot_files = 1;
break;
case Opt_windows_names:
opts->windows_names = result.negated ? 0 : 1;
opts->windows_names = 1;
break;
case Opt_showmeta:
opts->showmeta = result.negated ? 0 : 1;
opts->showmeta = 1;
break;
case Opt_acl:
if (!result.negated)
@ -385,10 +390,10 @@ static int ntfs_fs_parse_param(struct fs_context *fc,
param->string = NULL;
break;
case Opt_prealloc:
opts->prealloc = result.negated ? 0 : 1;
opts->prealloc = 1;
break;
case Opt_nocase:
opts->nocase = result.negated ? 1 : 0;
opts->nocase = 1;
break;
default:
/* Should not be here unless we forget add case. */
@ -1491,11 +1496,10 @@ static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc)
#ifdef __BIG_ENDIAN
{
const __le16 *src = sbi->upcase;
u16 *dst = sbi->upcase;
for (i = 0; i < 0x10000; i++)
*dst++ = le16_to_cpu(*src++);
__swab16s(dst++);
}
#endif

View File

@ -705,7 +705,7 @@ int ntfs_init_acl(struct mnt_idmap *idmap, struct inode *inode,
#endif
/*
* ntfs_acl_chmod - Helper for ntfs3_setattr().
* ntfs_acl_chmod - Helper for ntfs_setattr().
*/
int ntfs_acl_chmod(struct mnt_idmap *idmap, struct dentry *dentry)
{