b96a3e9142
- Peter Xu has a series (mm/gup: Unify hugetlb, speed up thp") which reduces the special-case code for handling hugetlb pages in GUP. It also speeds up GUP handling of transparent hugepages. - Peng Zhang provides some maple tree speedups ("Optimize the fast path of mas_store()"). - Sergey Senozhatsky has improved te performance of zsmalloc during compaction (zsmalloc: small compaction improvements"). - Domenico Cerasuolo has developed additional selftest code for zswap ("selftests: cgroup: add zswap test program"). - xu xin has doe some work on KSM's handling of zero pages. These changes are mainly to enable the user to better understand the effectiveness of KSM's treatment of zero pages ("ksm: support tracking KSM-placed zero-pages"). - Jeff Xu has fixes the behaviour of memfd's MEMFD_NOEXEC_SCOPE_NOEXEC_ENFORCED sysctl ("mm/memfd: fix sysctl MEMFD_NOEXEC_SCOPE_NOEXEC_ENFORCED"). - David Howells has fixed an fscache optimization ("mm, netfs, fscache: Stop read optimisation when folio removed from pagecache"). - Axel Rasmussen has given userfaultfd the ability to simulate memory poisoning ("add UFFDIO_POISON to simulate memory poisoning with UFFD"). - Miaohe Lin has contributed some routine maintenance work on the memory-failure code ("mm: memory-failure: remove unneeded PageHuge() check"). - Peng Zhang has contributed some maintenance work on the maple tree code ("Improve the validation for maple tree and some cleanup"). - Hugh Dickins has optimized the collapsing of shmem or file pages into THPs ("mm: free retracted page table by RCU"). - Jiaqi Yan has a patch series which permits us to use the healthy subpages within a hardware poisoned huge page for general purposes ("Improve hugetlbfs read on HWPOISON hugepages"). - Kemeng Shi has done some maintenance work on the pagetable-check code ("Remove unused parameters in page_table_check"). - More folioification work from Matthew Wilcox ("More filesystem folio conversions for 6.6"), ("Followup folio conversions for zswap"). And from ZhangPeng ("Convert several functions in page_io.c to use a folio"). - page_ext cleanups from Kemeng Shi ("minor cleanups for page_ext"). - Baoquan He has converted some architectures to use the GENERIC_IOREMAP ioremap()/iounmap() code ("mm: ioremap: Convert architectures to take GENERIC_IOREMAP way"). - Anshuman Khandual has optimized arm64 tlb shootdown ("arm64: support batched/deferred tlb shootdown during page reclamation/migration"). - Better maple tree lockdep checking from Liam Howlett ("More strict maple tree lockdep"). Liam also developed some efficiency improvements ("Reduce preallocations for maple tree"). - Cleanup and optimization to the secondary IOMMU TLB invalidation, from Alistair Popple ("Invalidate secondary IOMMU TLB on permission upgrade"). - Ryan Roberts fixes some arm64 MM selftest issues ("selftests/mm fixes for arm64"). - Kemeng Shi provides some maintenance work on the compaction code ("Two minor cleanups for compaction"). - Some reduction in mmap_lock pressure from Matthew Wilcox ("Handle most file-backed faults under the VMA lock"). - Aneesh Kumar contributes code to use the vmemmap optimization for DAX on ppc64, under some circumstances ("Add support for DAX vmemmap optimization for ppc64"). - page-ext cleanups from Kemeng Shi ("add page_ext_data to get client data in page_ext"), ("minor cleanups to page_ext header"). - Some zswap cleanups from Johannes Weiner ("mm: zswap: three cleanups"). - kmsan cleanups from ZhangPeng ("minor cleanups for kmsan"). - VMA handling cleanups from Kefeng Wang ("mm: convert to vma_is_initial_heap/stack()"). - DAMON feature work from SeongJae Park ("mm/damon/sysfs-schemes: implement DAMOS tried total bytes file"), ("Extend DAMOS filters for address ranges and DAMON monitoring targets"). - Compaction work from Kemeng Shi ("Fixes and cleanups to compaction"). - Liam Howlett has improved the maple tree node replacement code ("maple_tree: Change replacement strategy"). - ZhangPeng has a general code cleanup - use the K() macro more widely ("cleanup with helper macro K()"). - Aneesh Kumar brings memmap-on-memory to ppc64 ("Add support for memmap on memory feature on ppc64"). - pagealloc cleanups from Kemeng Shi ("Two minor cleanups for pcp list in page_alloc"), ("Two minor cleanups for get pageblock migratetype"). - Vishal Moola introduces a memory descriptor for page table tracking, "struct ptdesc" ("Split ptdesc from struct page"). - memfd selftest maintenance work from Aleksa Sarai ("memfd: cleanups for vm.memfd_noexec"). - MM include file rationalization from Hugh Dickins ("arch: include asm/cacheflush.h in asm/hugetlb.h"). - THP debug output fixes from Hugh Dickins ("mm,thp: fix sloppy text output"). - kmemleak improvements from Xiaolei Wang ("mm/kmemleak: use object_cache instead of kmemleak_initialized"). - More folio-related cleanups from Matthew Wilcox ("Remove _folio_dtor and _folio_order"). - A VMA locking scalability improvement from Suren Baghdasaryan ("Per-VMA lock support for swap and userfaults"). - pagetable handling cleanups from Matthew Wilcox ("New page table range API"). - A batch of swap/thp cleanups from David Hildenbrand ("mm/swap: stop using page->private on tail pages for THP_SWAP + cleanups"). - Cleanups and speedups to the hugetlb fault handling from Matthew Wilcox ("Change calling convention for ->huge_fault"). - Matthew Wilcox has also done some maintenance work on the MM subsystem documentation ("Improve mm documentation"). -----BEGIN PGP SIGNATURE----- iHUEABYIAB0WIQTTMBEPP41GrTpTJgfdBJ7gKXxAjgUCZO1JUQAKCRDdBJ7gKXxA jrMwAP47r/fS8vAVT3zp/7fXmxaJYTK27CTAM881Gw1SDhFM/wEAv8o84mDenCg6 Nfio7afS1ncD+hPYT8947UnLxTgn+ww= =Afws -----END PGP SIGNATURE----- Merge tag 'mm-stable-2023-08-28-18-26' of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm Pull MM updates from Andrew Morton: - Some swap cleanups from Ma Wupeng ("fix WARN_ON in add_to_avail_list") - Peter Xu has a series (mm/gup: Unify hugetlb, speed up thp") which reduces the special-case code for handling hugetlb pages in GUP. It also speeds up GUP handling of transparent hugepages. - Peng Zhang provides some maple tree speedups ("Optimize the fast path of mas_store()"). - Sergey Senozhatsky has improved te performance of zsmalloc during compaction (zsmalloc: small compaction improvements"). - Domenico Cerasuolo has developed additional selftest code for zswap ("selftests: cgroup: add zswap test program"). - xu xin has doe some work on KSM's handling of zero pages. These changes are mainly to enable the user to better understand the effectiveness of KSM's treatment of zero pages ("ksm: support tracking KSM-placed zero-pages"). - Jeff Xu has fixes the behaviour of memfd's MEMFD_NOEXEC_SCOPE_NOEXEC_ENFORCED sysctl ("mm/memfd: fix sysctl MEMFD_NOEXEC_SCOPE_NOEXEC_ENFORCED"). - David Howells has fixed an fscache optimization ("mm, netfs, fscache: Stop read optimisation when folio removed from pagecache"). - Axel Rasmussen has given userfaultfd the ability to simulate memory poisoning ("add UFFDIO_POISON to simulate memory poisoning with UFFD"). - Miaohe Lin has contributed some routine maintenance work on the memory-failure code ("mm: memory-failure: remove unneeded PageHuge() check"). - Peng Zhang has contributed some maintenance work on the maple tree code ("Improve the validation for maple tree and some cleanup"). - Hugh Dickins has optimized the collapsing of shmem or file pages into THPs ("mm: free retracted page table by RCU"). - Jiaqi Yan has a patch series which permits us to use the healthy subpages within a hardware poisoned huge page for general purposes ("Improve hugetlbfs read on HWPOISON hugepages"). - Kemeng Shi has done some maintenance work on the pagetable-check code ("Remove unused parameters in page_table_check"). - More folioification work from Matthew Wilcox ("More filesystem folio conversions for 6.6"), ("Followup folio conversions for zswap"). And from ZhangPeng ("Convert several functions in page_io.c to use a folio"). - page_ext cleanups from Kemeng Shi ("minor cleanups for page_ext"). - Baoquan He has converted some architectures to use the GENERIC_IOREMAP ioremap()/iounmap() code ("mm: ioremap: Convert architectures to take GENERIC_IOREMAP way"). - Anshuman Khandual has optimized arm64 tlb shootdown ("arm64: support batched/deferred tlb shootdown during page reclamation/migration"). - Better maple tree lockdep checking from Liam Howlett ("More strict maple tree lockdep"). Liam also developed some efficiency improvements ("Reduce preallocations for maple tree"). - Cleanup and optimization to the secondary IOMMU TLB invalidation, from Alistair Popple ("Invalidate secondary IOMMU TLB on permission upgrade"). - Ryan Roberts fixes some arm64 MM selftest issues ("selftests/mm fixes for arm64"). - Kemeng Shi provides some maintenance work on the compaction code ("Two minor cleanups for compaction"). - Some reduction in mmap_lock pressure from Matthew Wilcox ("Handle most file-backed faults under the VMA lock"). - Aneesh Kumar contributes code to use the vmemmap optimization for DAX on ppc64, under some circumstances ("Add support for DAX vmemmap optimization for ppc64"). - page-ext cleanups from Kemeng Shi ("add page_ext_data to get client data in page_ext"), ("minor cleanups to page_ext header"). - Some zswap cleanups from Johannes Weiner ("mm: zswap: three cleanups"). - kmsan cleanups from ZhangPeng ("minor cleanups for kmsan"). - VMA handling cleanups from Kefeng Wang ("mm: convert to vma_is_initial_heap/stack()"). - DAMON feature work from SeongJae Park ("mm/damon/sysfs-schemes: implement DAMOS tried total bytes file"), ("Extend DAMOS filters for address ranges and DAMON monitoring targets"). - Compaction work from Kemeng Shi ("Fixes and cleanups to compaction"). - Liam Howlett has improved the maple tree node replacement code ("maple_tree: Change replacement strategy"). - ZhangPeng has a general code cleanup - use the K() macro more widely ("cleanup with helper macro K()"). - Aneesh Kumar brings memmap-on-memory to ppc64 ("Add support for memmap on memory feature on ppc64"). - pagealloc cleanups from Kemeng Shi ("Two minor cleanups for pcp list in page_alloc"), ("Two minor cleanups for get pageblock migratetype"). - Vishal Moola introduces a memory descriptor for page table tracking, "struct ptdesc" ("Split ptdesc from struct page"). - memfd selftest maintenance work from Aleksa Sarai ("memfd: cleanups for vm.memfd_noexec"). - MM include file rationalization from Hugh Dickins ("arch: include asm/cacheflush.h in asm/hugetlb.h"). - THP debug output fixes from Hugh Dickins ("mm,thp: fix sloppy text output"). - kmemleak improvements from Xiaolei Wang ("mm/kmemleak: use object_cache instead of kmemleak_initialized"). - More folio-related cleanups from Matthew Wilcox ("Remove _folio_dtor and _folio_order"). - A VMA locking scalability improvement from Suren Baghdasaryan ("Per-VMA lock support for swap and userfaults"). - pagetable handling cleanups from Matthew Wilcox ("New page table range API"). - A batch of swap/thp cleanups from David Hildenbrand ("mm/swap: stop using page->private on tail pages for THP_SWAP + cleanups"). - Cleanups and speedups to the hugetlb fault handling from Matthew Wilcox ("Change calling convention for ->huge_fault"). - Matthew Wilcox has also done some maintenance work on the MM subsystem documentation ("Improve mm documentation"). * tag 'mm-stable-2023-08-28-18-26' of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm: (489 commits) maple_tree: shrink struct maple_tree maple_tree: clean up mas_wr_append() secretmem: convert page_is_secretmem() to folio_is_secretmem() nios2: fix flush_dcache_page() for usage from irq context hugetlb: add documentation for vma_kernel_pagesize() mm: add orphaned kernel-doc to the rst files. mm: fix clean_record_shared_mapping_range kernel-doc mm: fix get_mctgt_type() kernel-doc mm: fix kernel-doc warning from tlb_flush_rmaps() mm: remove enum page_entry_size mm: allow ->huge_fault() to be called without the mmap_lock held mm: move PMD_ORDER to pgtable.h mm: remove checks for pte_index memcg: remove duplication detection for mem_cgroup_uncharge_swap mm/huge_memory: work on folio->swap instead of page->private when splitting folio mm/swap: inline folio_set_swap_entry() and folio_swap_entry() mm/swap: use dedicated entry for swap in folio mm/swap: stop using page->private on tail pages for THP_SWAP selftests/mm: fix WARNING comparing pointer to 0 selftests: cgroup: fix test_kmem_memcg_deletion kernel mem check ...
2098 lines
49 KiB
C
2098 lines
49 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
*
|
|
* Copyright (C) 2019-2021 Paragon Software GmbH, All rights reserved.
|
|
*
|
|
*/
|
|
|
|
#include <linux/buffer_head.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/mpage.h>
|
|
#include <linux/namei.h>
|
|
#include <linux/nls.h>
|
|
#include <linux/uio.h>
|
|
#include <linux/writeback.h>
|
|
|
|
#include "debug.h"
|
|
#include "ntfs.h"
|
|
#include "ntfs_fs.h"
|
|
|
|
/*
|
|
* ntfs_read_mft - Read record and parses MFT.
|
|
*/
|
|
static struct inode *ntfs_read_mft(struct inode *inode,
|
|
const struct cpu_str *name,
|
|
const struct MFT_REF *ref)
|
|
{
|
|
int err = 0;
|
|
struct ntfs_inode *ni = ntfs_i(inode);
|
|
struct super_block *sb = inode->i_sb;
|
|
struct ntfs_sb_info *sbi = sb->s_fs_info;
|
|
mode_t mode = 0;
|
|
struct ATTR_STD_INFO5 *std5 = NULL;
|
|
struct ATTR_LIST_ENTRY *le;
|
|
struct ATTRIB *attr;
|
|
bool is_match = false;
|
|
bool is_root = false;
|
|
bool is_dir;
|
|
unsigned long ino = inode->i_ino;
|
|
u32 rp_fa = 0, asize, t32;
|
|
u16 roff, rsize, names = 0;
|
|
const struct ATTR_FILE_NAME *fname = NULL;
|
|
const struct INDEX_ROOT *root;
|
|
struct REPARSE_DATA_BUFFER rp; // 0x18 bytes
|
|
u64 t64;
|
|
struct MFT_REC *rec;
|
|
struct runs_tree *run;
|
|
struct timespec64 ctime;
|
|
|
|
inode->i_op = NULL;
|
|
/* Setup 'uid' and 'gid' */
|
|
inode->i_uid = sbi->options->fs_uid;
|
|
inode->i_gid = sbi->options->fs_gid;
|
|
|
|
err = mi_init(&ni->mi, sbi, ino);
|
|
if (err)
|
|
goto out;
|
|
|
|
if (!sbi->mft.ni && ino == MFT_REC_MFT && !sb->s_root) {
|
|
t64 = sbi->mft.lbo >> sbi->cluster_bits;
|
|
t32 = bytes_to_cluster(sbi, MFT_REC_VOL * sbi->record_size);
|
|
sbi->mft.ni = ni;
|
|
init_rwsem(&ni->file.run_lock);
|
|
|
|
if (!run_add_entry(&ni->file.run, 0, t64, t32, true)) {
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
err = mi_read(&ni->mi, ino == MFT_REC_MFT);
|
|
|
|
if (err)
|
|
goto out;
|
|
|
|
rec = ni->mi.mrec;
|
|
|
|
if (sbi->flags & NTFS_FLAGS_LOG_REPLAYING) {
|
|
;
|
|
} else if (ref->seq != rec->seq) {
|
|
err = -EINVAL;
|
|
ntfs_err(sb, "MFT: r=%lx, expect seq=%x instead of %x!", ino,
|
|
le16_to_cpu(ref->seq), le16_to_cpu(rec->seq));
|
|
goto out;
|
|
} else if (!is_rec_inuse(rec)) {
|
|
err = -ESTALE;
|
|
ntfs_err(sb, "Inode r=%x is not in use!", (u32)ino);
|
|
goto out;
|
|
}
|
|
|
|
if (le32_to_cpu(rec->total) != sbi->record_size) {
|
|
/* Bad inode? */
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if (!is_rec_base(rec)) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
/* Record should contain $I30 root. */
|
|
is_dir = rec->flags & RECORD_FLAG_DIR;
|
|
|
|
/* MFT_REC_MFT is not a dir */
|
|
if (is_dir && ino == MFT_REC_MFT) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
inode->i_generation = le16_to_cpu(rec->seq);
|
|
|
|
/* Enumerate all struct Attributes MFT. */
|
|
le = NULL;
|
|
attr = NULL;
|
|
|
|
/*
|
|
* To reduce tab pressure use goto instead of
|
|
* while( (attr = ni_enum_attr_ex(ni, attr, &le, NULL) ))
|
|
*/
|
|
next_attr:
|
|
run = NULL;
|
|
err = -EINVAL;
|
|
attr = ni_enum_attr_ex(ni, attr, &le, NULL);
|
|
if (!attr)
|
|
goto end_enum;
|
|
|
|
if (le && le->vcn) {
|
|
/* This is non primary attribute segment. Ignore if not MFT. */
|
|
if (ino != MFT_REC_MFT || attr->type != ATTR_DATA)
|
|
goto next_attr;
|
|
|
|
run = &ni->file.run;
|
|
asize = le32_to_cpu(attr->size);
|
|
goto attr_unpack_run;
|
|
}
|
|
|
|
roff = attr->non_res ? 0 : le16_to_cpu(attr->res.data_off);
|
|
rsize = attr->non_res ? 0 : le32_to_cpu(attr->res.data_size);
|
|
asize = le32_to_cpu(attr->size);
|
|
|
|
/*
|
|
* Really this check was done in 'ni_enum_attr_ex' -> ... 'mi_enum_attr'.
|
|
* There not critical to check this case again
|
|
*/
|
|
if (attr->name_len &&
|
|
sizeof(short) * attr->name_len + le16_to_cpu(attr->name_off) >
|
|
asize)
|
|
goto out;
|
|
|
|
if (attr->non_res) {
|
|
t64 = le64_to_cpu(attr->nres.alloc_size);
|
|
if (le64_to_cpu(attr->nres.data_size) > t64 ||
|
|
le64_to_cpu(attr->nres.valid_size) > t64)
|
|
goto out;
|
|
}
|
|
|
|
switch (attr->type) {
|
|
case ATTR_STD:
|
|
if (attr->non_res ||
|
|
asize < sizeof(struct ATTR_STD_INFO) + roff ||
|
|
rsize < sizeof(struct ATTR_STD_INFO))
|
|
goto out;
|
|
|
|
if (std5)
|
|
goto next_attr;
|
|
|
|
std5 = Add2Ptr(attr, roff);
|
|
|
|
#ifdef STATX_BTIME
|
|
nt2kernel(std5->cr_time, &ni->i_crtime);
|
|
#endif
|
|
nt2kernel(std5->a_time, &inode->i_atime);
|
|
ctime = inode_get_ctime(inode);
|
|
nt2kernel(std5->c_time, &ctime);
|
|
nt2kernel(std5->m_time, &inode->i_mtime);
|
|
|
|
ni->std_fa = std5->fa;
|
|
|
|
if (asize >= sizeof(struct ATTR_STD_INFO5) + roff &&
|
|
rsize >= sizeof(struct ATTR_STD_INFO5))
|
|
ni->std_security_id = std5->security_id;
|
|
goto next_attr;
|
|
|
|
case ATTR_LIST:
|
|
if (attr->name_len || le || ino == MFT_REC_LOG)
|
|
goto out;
|
|
|
|
err = ntfs_load_attr_list(ni, attr);
|
|
if (err)
|
|
goto out;
|
|
|
|
le = NULL;
|
|
attr = NULL;
|
|
goto next_attr;
|
|
|
|
case ATTR_NAME:
|
|
if (attr->non_res || asize < SIZEOF_ATTRIBUTE_FILENAME + roff ||
|
|
rsize < SIZEOF_ATTRIBUTE_FILENAME)
|
|
goto out;
|
|
|
|
fname = Add2Ptr(attr, roff);
|
|
if (fname->type == FILE_NAME_DOS)
|
|
goto next_attr;
|
|
|
|
names += 1;
|
|
if (name && name->len == fname->name_len &&
|
|
!ntfs_cmp_names_cpu(name, (struct le_str *)&fname->name_len,
|
|
NULL, false))
|
|
is_match = true;
|
|
|
|
goto next_attr;
|
|
|
|
case ATTR_DATA:
|
|
if (is_dir) {
|
|
/* Ignore data attribute in dir record. */
|
|
goto next_attr;
|
|
}
|
|
|
|
if (ino == MFT_REC_BADCLUST && !attr->non_res)
|
|
goto next_attr;
|
|
|
|
if (attr->name_len &&
|
|
((ino != MFT_REC_BADCLUST || !attr->non_res ||
|
|
attr->name_len != ARRAY_SIZE(BAD_NAME) ||
|
|
memcmp(attr_name(attr), BAD_NAME, sizeof(BAD_NAME))) &&
|
|
(ino != MFT_REC_SECURE || !attr->non_res ||
|
|
attr->name_len != ARRAY_SIZE(SDS_NAME) ||
|
|
memcmp(attr_name(attr), SDS_NAME, sizeof(SDS_NAME))))) {
|
|
/* File contains stream attribute. Ignore it. */
|
|
goto next_attr;
|
|
}
|
|
|
|
if (is_attr_sparsed(attr))
|
|
ni->std_fa |= FILE_ATTRIBUTE_SPARSE_FILE;
|
|
else
|
|
ni->std_fa &= ~FILE_ATTRIBUTE_SPARSE_FILE;
|
|
|
|
if (is_attr_compressed(attr))
|
|
ni->std_fa |= FILE_ATTRIBUTE_COMPRESSED;
|
|
else
|
|
ni->std_fa &= ~FILE_ATTRIBUTE_COMPRESSED;
|
|
|
|
if (is_attr_encrypted(attr))
|
|
ni->std_fa |= FILE_ATTRIBUTE_ENCRYPTED;
|
|
else
|
|
ni->std_fa &= ~FILE_ATTRIBUTE_ENCRYPTED;
|
|
|
|
if (!attr->non_res) {
|
|
ni->i_valid = inode->i_size = rsize;
|
|
inode_set_bytes(inode, rsize);
|
|
}
|
|
|
|
mode = S_IFREG | (0777 & sbi->options->fs_fmask_inv);
|
|
|
|
if (!attr->non_res) {
|
|
ni->ni_flags |= NI_FLAG_RESIDENT;
|
|
goto next_attr;
|
|
}
|
|
|
|
inode_set_bytes(inode, attr_ondisk_size(attr));
|
|
|
|
ni->i_valid = le64_to_cpu(attr->nres.valid_size);
|
|
inode->i_size = le64_to_cpu(attr->nres.data_size);
|
|
if (!attr->nres.alloc_size)
|
|
goto next_attr;
|
|
|
|
run = ino == MFT_REC_BITMAP ? &sbi->used.bitmap.run :
|
|
&ni->file.run;
|
|
break;
|
|
|
|
case ATTR_ROOT:
|
|
if (attr->non_res)
|
|
goto out;
|
|
|
|
root = Add2Ptr(attr, roff);
|
|
|
|
if (attr->name_len != ARRAY_SIZE(I30_NAME) ||
|
|
memcmp(attr_name(attr), I30_NAME, sizeof(I30_NAME)))
|
|
goto next_attr;
|
|
|
|
if (root->type != ATTR_NAME ||
|
|
root->rule != NTFS_COLLATION_TYPE_FILENAME)
|
|
goto out;
|
|
|
|
if (!is_dir)
|
|
goto next_attr;
|
|
|
|
is_root = true;
|
|
ni->ni_flags |= NI_FLAG_DIR;
|
|
|
|
err = indx_init(&ni->dir, sbi, attr, INDEX_MUTEX_I30);
|
|
if (err)
|
|
goto out;
|
|
|
|
mode = sb->s_root ?
|
|
(S_IFDIR | (0777 & sbi->options->fs_dmask_inv)) :
|
|
(S_IFDIR | 0777);
|
|
goto next_attr;
|
|
|
|
case ATTR_ALLOC:
|
|
if (!is_root || attr->name_len != ARRAY_SIZE(I30_NAME) ||
|
|
memcmp(attr_name(attr), I30_NAME, sizeof(I30_NAME)))
|
|
goto next_attr;
|
|
|
|
inode->i_size = le64_to_cpu(attr->nres.data_size);
|
|
ni->i_valid = le64_to_cpu(attr->nres.valid_size);
|
|
inode_set_bytes(inode, le64_to_cpu(attr->nres.alloc_size));
|
|
|
|
run = &ni->dir.alloc_run;
|
|
break;
|
|
|
|
case ATTR_BITMAP:
|
|
if (ino == MFT_REC_MFT) {
|
|
if (!attr->non_res)
|
|
goto out;
|
|
#ifndef CONFIG_NTFS3_64BIT_CLUSTER
|
|
/* 0x20000000 = 2^32 / 8 */
|
|
if (le64_to_cpu(attr->nres.alloc_size) >= 0x20000000)
|
|
goto out;
|
|
#endif
|
|
run = &sbi->mft.bitmap.run;
|
|
break;
|
|
} else if (is_dir && attr->name_len == ARRAY_SIZE(I30_NAME) &&
|
|
!memcmp(attr_name(attr), I30_NAME,
|
|
sizeof(I30_NAME)) &&
|
|
attr->non_res) {
|
|
run = &ni->dir.bitmap_run;
|
|
break;
|
|
}
|
|
goto next_attr;
|
|
|
|
case ATTR_REPARSE:
|
|
if (attr->name_len)
|
|
goto next_attr;
|
|
|
|
rp_fa = ni_parse_reparse(ni, attr, &rp);
|
|
switch (rp_fa) {
|
|
case REPARSE_LINK:
|
|
/*
|
|
* Normal symlink.
|
|
* Assume one unicode symbol == one utf8.
|
|
*/
|
|
inode->i_size = le16_to_cpu(rp.SymbolicLinkReparseBuffer
|
|
.PrintNameLength) /
|
|
sizeof(u16);
|
|
|
|
ni->i_valid = inode->i_size;
|
|
|
|
/* Clear directory bit. */
|
|
if (ni->ni_flags & NI_FLAG_DIR) {
|
|
indx_clear(&ni->dir);
|
|
memset(&ni->dir, 0, sizeof(ni->dir));
|
|
ni->ni_flags &= ~NI_FLAG_DIR;
|
|
} else {
|
|
run_close(&ni->file.run);
|
|
}
|
|
mode = S_IFLNK | 0777;
|
|
is_dir = false;
|
|
if (attr->non_res) {
|
|
run = &ni->file.run;
|
|
goto attr_unpack_run; // Double break.
|
|
}
|
|
break;
|
|
|
|
case REPARSE_COMPRESSED:
|
|
break;
|
|
|
|
case REPARSE_DEDUPLICATED:
|
|
break;
|
|
}
|
|
goto next_attr;
|
|
|
|
case ATTR_EA_INFO:
|
|
if (!attr->name_len &&
|
|
resident_data_ex(attr, sizeof(struct EA_INFO))) {
|
|
ni->ni_flags |= NI_FLAG_EA;
|
|
/*
|
|
* ntfs_get_wsl_perm updates inode->i_uid, inode->i_gid, inode->i_mode
|
|
*/
|
|
inode->i_mode = mode;
|
|
ntfs_get_wsl_perm(inode);
|
|
mode = inode->i_mode;
|
|
}
|
|
goto next_attr;
|
|
|
|
default:
|
|
goto next_attr;
|
|
}
|
|
|
|
attr_unpack_run:
|
|
roff = le16_to_cpu(attr->nres.run_off);
|
|
|
|
if (roff > asize) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
t64 = le64_to_cpu(attr->nres.svcn);
|
|
|
|
err = run_unpack_ex(run, sbi, ino, t64, le64_to_cpu(attr->nres.evcn),
|
|
t64, Add2Ptr(attr, roff), asize - roff);
|
|
if (err < 0)
|
|
goto out;
|
|
err = 0;
|
|
goto next_attr;
|
|
|
|
end_enum:
|
|
|
|
if (!std5)
|
|
goto out;
|
|
|
|
if (!is_match && name) {
|
|
/* Reuse rec as buffer for ascii name. */
|
|
err = -ENOENT;
|
|
goto out;
|
|
}
|
|
|
|
if (std5->fa & FILE_ATTRIBUTE_READONLY)
|
|
mode &= ~0222;
|
|
|
|
if (!names) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if (names != le16_to_cpu(rec->hard_links)) {
|
|
/* Correct minor error on the fly. Do not mark inode as dirty. */
|
|
rec->hard_links = cpu_to_le16(names);
|
|
ni->mi.dirty = true;
|
|
}
|
|
|
|
set_nlink(inode, names);
|
|
|
|
if (S_ISDIR(mode)) {
|
|
ni->std_fa |= FILE_ATTRIBUTE_DIRECTORY;
|
|
|
|
/*
|
|
* Dot and dot-dot should be included in count but was not
|
|
* included in enumeration.
|
|
* Usually a hard links to directories are disabled.
|
|
*/
|
|
inode->i_op = &ntfs_dir_inode_operations;
|
|
inode->i_fop = &ntfs_dir_operations;
|
|
ni->i_valid = 0;
|
|
} else if (S_ISLNK(mode)) {
|
|
ni->std_fa &= ~FILE_ATTRIBUTE_DIRECTORY;
|
|
inode->i_op = &ntfs_link_inode_operations;
|
|
inode->i_fop = NULL;
|
|
inode_nohighmem(inode);
|
|
} else if (S_ISREG(mode)) {
|
|
ni->std_fa &= ~FILE_ATTRIBUTE_DIRECTORY;
|
|
inode->i_op = &ntfs_file_inode_operations;
|
|
inode->i_fop = &ntfs_file_operations;
|
|
inode->i_mapping->a_ops = is_compressed(ni) ? &ntfs_aops_cmpr :
|
|
&ntfs_aops;
|
|
if (ino != MFT_REC_MFT)
|
|
init_rwsem(&ni->file.run_lock);
|
|
} else if (S_ISCHR(mode) || S_ISBLK(mode) || S_ISFIFO(mode) ||
|
|
S_ISSOCK(mode)) {
|
|
inode->i_op = &ntfs_special_inode_operations;
|
|
init_special_inode(inode, mode, inode->i_rdev);
|
|
} else if (fname && fname->home.low == cpu_to_le32(MFT_REC_EXTEND) &&
|
|
fname->home.seq == cpu_to_le16(MFT_REC_EXTEND)) {
|
|
/* Records in $Extend are not a files or general directories. */
|
|
inode->i_op = &ntfs_file_inode_operations;
|
|
} else {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if ((sbi->options->sys_immutable &&
|
|
(std5->fa & FILE_ATTRIBUTE_SYSTEM)) &&
|
|
!S_ISFIFO(mode) && !S_ISSOCK(mode) && !S_ISLNK(mode)) {
|
|
inode->i_flags |= S_IMMUTABLE;
|
|
} else {
|
|
inode->i_flags &= ~S_IMMUTABLE;
|
|
}
|
|
|
|
inode->i_mode = mode;
|
|
if (!(ni->ni_flags & NI_FLAG_EA)) {
|
|
/* If no xattr then no security (stored in xattr). */
|
|
inode->i_flags |= S_NOSEC;
|
|
}
|
|
|
|
if (ino == MFT_REC_MFT && !sb->s_root)
|
|
sbi->mft.ni = NULL;
|
|
|
|
unlock_new_inode(inode);
|
|
|
|
return inode;
|
|
|
|
out:
|
|
if (ino == MFT_REC_MFT && !sb->s_root)
|
|
sbi->mft.ni = NULL;
|
|
|
|
iget_failed(inode);
|
|
return ERR_PTR(err);
|
|
}
|
|
|
|
/*
|
|
* ntfs_test_inode
|
|
*
|
|
* Return: 1 if match.
|
|
*/
|
|
static int ntfs_test_inode(struct inode *inode, void *data)
|
|
{
|
|
struct MFT_REF *ref = data;
|
|
|
|
return ino_get(ref) == inode->i_ino;
|
|
}
|
|
|
|
static int ntfs_set_inode(struct inode *inode, void *data)
|
|
{
|
|
const struct MFT_REF *ref = data;
|
|
|
|
inode->i_ino = ino_get(ref);
|
|
return 0;
|
|
}
|
|
|
|
struct inode *ntfs_iget5(struct super_block *sb, const struct MFT_REF *ref,
|
|
const struct cpu_str *name)
|
|
{
|
|
struct inode *inode;
|
|
|
|
inode = iget5_locked(sb, ino_get(ref), ntfs_test_inode, ntfs_set_inode,
|
|
(void *)ref);
|
|
if (unlikely(!inode))
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
/* If this is a freshly allocated inode, need to read it now. */
|
|
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);
|
|
}
|
|
|
|
if (IS_ERR(inode) && name)
|
|
ntfs_set_state(sb->s_fs_info, NTFS_DIRTY_ERROR);
|
|
|
|
return inode;
|
|
}
|
|
|
|
enum get_block_ctx {
|
|
GET_BLOCK_GENERAL = 0,
|
|
GET_BLOCK_WRITE_BEGIN = 1,
|
|
GET_BLOCK_DIRECT_IO_R = 2,
|
|
GET_BLOCK_DIRECT_IO_W = 3,
|
|
GET_BLOCK_BMAP = 4,
|
|
};
|
|
|
|
static noinline int ntfs_get_block_vbo(struct inode *inode, u64 vbo,
|
|
struct buffer_head *bh, int create,
|
|
enum get_block_ctx ctx)
|
|
{
|
|
struct super_block *sb = inode->i_sb;
|
|
struct ntfs_sb_info *sbi = sb->s_fs_info;
|
|
struct ntfs_inode *ni = ntfs_i(inode);
|
|
struct folio *folio = bh->b_folio;
|
|
u8 cluster_bits = sbi->cluster_bits;
|
|
u32 block_size = sb->s_blocksize;
|
|
u64 bytes, lbo, valid;
|
|
u32 off;
|
|
int err;
|
|
CLST vcn, lcn, len;
|
|
bool new;
|
|
|
|
/* Clear previous state. */
|
|
clear_buffer_new(bh);
|
|
clear_buffer_uptodate(bh);
|
|
|
|
if (is_resident(ni)) {
|
|
ni_lock(ni);
|
|
err = attr_data_read_resident(ni, &folio->page);
|
|
ni_unlock(ni);
|
|
|
|
if (!err)
|
|
set_buffer_uptodate(bh);
|
|
bh->b_size = block_size;
|
|
return err;
|
|
}
|
|
|
|
vcn = vbo >> cluster_bits;
|
|
off = vbo & sbi->cluster_mask;
|
|
new = false;
|
|
|
|
err = attr_data_get_block(ni, vcn, 1, &lcn, &len, create ? &new : NULL,
|
|
create && sbi->cluster_size > PAGE_SIZE);
|
|
if (err)
|
|
goto out;
|
|
|
|
if (!len)
|
|
return 0;
|
|
|
|
bytes = ((u64)len << cluster_bits) - off;
|
|
|
|
if (lcn == SPARSE_LCN) {
|
|
if (!create) {
|
|
if (bh->b_size > bytes)
|
|
bh->b_size = bytes;
|
|
return 0;
|
|
}
|
|
WARN_ON(1);
|
|
}
|
|
|
|
if (new)
|
|
set_buffer_new(bh);
|
|
|
|
lbo = ((u64)lcn << cluster_bits) + off;
|
|
|
|
set_buffer_mapped(bh);
|
|
bh->b_bdev = sb->s_bdev;
|
|
bh->b_blocknr = lbo >> sb->s_blocksize_bits;
|
|
|
|
valid = ni->i_valid;
|
|
|
|
if (ctx == GET_BLOCK_DIRECT_IO_W) {
|
|
/* ntfs_direct_IO will update ni->i_valid. */
|
|
if (vbo >= valid)
|
|
set_buffer_new(bh);
|
|
} else if (create) {
|
|
/* Normal write. */
|
|
if (bytes > bh->b_size)
|
|
bytes = bh->b_size;
|
|
|
|
if (vbo >= valid)
|
|
set_buffer_new(bh);
|
|
|
|
if (vbo + bytes > valid) {
|
|
ni->i_valid = vbo + bytes;
|
|
mark_inode_dirty(inode);
|
|
}
|
|
} else if (vbo >= valid) {
|
|
/* Read out of valid data. */
|
|
clear_buffer_mapped(bh);
|
|
} else if (vbo + bytes <= valid) {
|
|
/* Normal read. */
|
|
} else if (vbo + block_size <= valid) {
|
|
/* Normal short read. */
|
|
bytes = block_size;
|
|
} else {
|
|
/*
|
|
* Read across valid size: vbo < valid && valid < vbo + block_size
|
|
*/
|
|
bytes = block_size;
|
|
|
|
if (folio) {
|
|
u32 voff = valid - vbo;
|
|
|
|
bh->b_size = block_size;
|
|
off = vbo & (PAGE_SIZE - 1);
|
|
folio_set_bh(bh, folio, off);
|
|
|
|
err = bh_read(bh, 0);
|
|
if (err < 0)
|
|
goto out;
|
|
folio_zero_segment(folio, off + voff, off + block_size);
|
|
}
|
|
}
|
|
|
|
if (bh->b_size > bytes)
|
|
bh->b_size = bytes;
|
|
|
|
#ifndef __LP64__
|
|
if (ctx == GET_BLOCK_DIRECT_IO_W || ctx == GET_BLOCK_DIRECT_IO_R) {
|
|
static_assert(sizeof(size_t) < sizeof(loff_t));
|
|
if (bytes > 0x40000000u)
|
|
bh->b_size = 0x40000000u;
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
int ntfs_get_block(struct inode *inode, sector_t vbn,
|
|
struct buffer_head *bh_result, int create)
|
|
{
|
|
return ntfs_get_block_vbo(inode, (u64)vbn << inode->i_blkbits,
|
|
bh_result, create, GET_BLOCK_GENERAL);
|
|
}
|
|
|
|
static int ntfs_get_block_bmap(struct inode *inode, sector_t vsn,
|
|
struct buffer_head *bh_result, int create)
|
|
{
|
|
return ntfs_get_block_vbo(inode,
|
|
(u64)vsn << inode->i_sb->s_blocksize_bits,
|
|
bh_result, create, GET_BLOCK_BMAP);
|
|
}
|
|
|
|
static sector_t ntfs_bmap(struct address_space *mapping, sector_t block)
|
|
{
|
|
return generic_block_bmap(mapping, block, ntfs_get_block_bmap);
|
|
}
|
|
|
|
static int ntfs_read_folio(struct file *file, struct folio *folio)
|
|
{
|
|
struct page *page = &folio->page;
|
|
int err;
|
|
struct address_space *mapping = page->mapping;
|
|
struct inode *inode = mapping->host;
|
|
struct ntfs_inode *ni = ntfs_i(inode);
|
|
|
|
if (is_resident(ni)) {
|
|
ni_lock(ni);
|
|
err = attr_data_read_resident(ni, page);
|
|
ni_unlock(ni);
|
|
if (err != E_NTFS_NONRESIDENT) {
|
|
unlock_page(page);
|
|
return err;
|
|
}
|
|
}
|
|
|
|
if (is_compressed(ni)) {
|
|
ni_lock(ni);
|
|
err = ni_readpage_cmpr(ni, page);
|
|
ni_unlock(ni);
|
|
return err;
|
|
}
|
|
|
|
/* Normal + sparse files. */
|
|
return mpage_read_folio(folio, ntfs_get_block);
|
|
}
|
|
|
|
static void ntfs_readahead(struct readahead_control *rac)
|
|
{
|
|
struct address_space *mapping = rac->mapping;
|
|
struct inode *inode = mapping->host;
|
|
struct ntfs_inode *ni = ntfs_i(inode);
|
|
u64 valid;
|
|
loff_t pos;
|
|
|
|
if (is_resident(ni)) {
|
|
/* No readahead for resident. */
|
|
return;
|
|
}
|
|
|
|
if (is_compressed(ni)) {
|
|
/* No readahead for compressed. */
|
|
return;
|
|
}
|
|
|
|
valid = ni->i_valid;
|
|
pos = readahead_pos(rac);
|
|
|
|
if (valid < i_size_read(inode) && pos <= valid &&
|
|
valid < pos + readahead_length(rac)) {
|
|
/* Range cross 'valid'. Read it page by page. */
|
|
return;
|
|
}
|
|
|
|
mpage_readahead(rac, ntfs_get_block);
|
|
}
|
|
|
|
static int ntfs_get_block_direct_IO_R(struct inode *inode, sector_t iblock,
|
|
struct buffer_head *bh_result, int create)
|
|
{
|
|
return ntfs_get_block_vbo(inode, (u64)iblock << inode->i_blkbits,
|
|
bh_result, create, GET_BLOCK_DIRECT_IO_R);
|
|
}
|
|
|
|
static int ntfs_get_block_direct_IO_W(struct inode *inode, sector_t iblock,
|
|
struct buffer_head *bh_result, int create)
|
|
{
|
|
return ntfs_get_block_vbo(inode, (u64)iblock << inode->i_blkbits,
|
|
bh_result, create, GET_BLOCK_DIRECT_IO_W);
|
|
}
|
|
|
|
static ssize_t ntfs_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
|
|
{
|
|
struct file *file = iocb->ki_filp;
|
|
struct address_space *mapping = file->f_mapping;
|
|
struct inode *inode = mapping->host;
|
|
struct ntfs_inode *ni = ntfs_i(inode);
|
|
loff_t vbo = iocb->ki_pos;
|
|
loff_t end;
|
|
int wr = iov_iter_rw(iter) & WRITE;
|
|
size_t iter_count = iov_iter_count(iter);
|
|
loff_t valid;
|
|
ssize_t ret;
|
|
|
|
if (is_resident(ni)) {
|
|
/* Switch to buffered write. */
|
|
ret = 0;
|
|
goto out;
|
|
}
|
|
|
|
ret = blockdev_direct_IO(iocb, inode, iter,
|
|
wr ? ntfs_get_block_direct_IO_W :
|
|
ntfs_get_block_direct_IO_R);
|
|
|
|
if (ret > 0)
|
|
end = vbo + ret;
|
|
else if (wr && ret == -EIOCBQUEUED)
|
|
end = vbo + iter_count;
|
|
else
|
|
goto out;
|
|
|
|
valid = ni->i_valid;
|
|
if (wr) {
|
|
if (end > valid && !S_ISBLK(inode->i_mode)) {
|
|
ni->i_valid = end;
|
|
mark_inode_dirty(inode);
|
|
}
|
|
} else if (vbo < valid && valid < end) {
|
|
/* Fix page. */
|
|
iov_iter_revert(iter, end - valid);
|
|
iov_iter_zero(end - valid, iter);
|
|
}
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
int ntfs_set_size(struct inode *inode, u64 new_size)
|
|
{
|
|
struct super_block *sb = inode->i_sb;
|
|
struct ntfs_sb_info *sbi = sb->s_fs_info;
|
|
struct ntfs_inode *ni = ntfs_i(inode);
|
|
int err;
|
|
|
|
/* Check for maximum file size. */
|
|
if (is_sparsed(ni) || is_compressed(ni)) {
|
|
if (new_size > sbi->maxbytes_sparse) {
|
|
err = -EFBIG;
|
|
goto out;
|
|
}
|
|
} else if (new_size > sbi->maxbytes) {
|
|
err = -EFBIG;
|
|
goto out;
|
|
}
|
|
|
|
ni_lock(ni);
|
|
down_write(&ni->file.run_lock);
|
|
|
|
err = attr_set_size(ni, ATTR_DATA, NULL, 0, &ni->file.run, new_size,
|
|
&ni->i_valid, true, NULL);
|
|
|
|
up_write(&ni->file.run_lock);
|
|
ni_unlock(ni);
|
|
|
|
mark_inode_dirty(inode);
|
|
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
static int ntfs_resident_writepage(struct folio *folio,
|
|
struct writeback_control *wbc, void *data)
|
|
{
|
|
struct address_space *mapping = data;
|
|
struct ntfs_inode *ni = ntfs_i(mapping->host);
|
|
int ret;
|
|
|
|
ni_lock(ni);
|
|
ret = attr_data_write_resident(ni, &folio->page);
|
|
ni_unlock(ni);
|
|
|
|
if (ret != E_NTFS_NONRESIDENT)
|
|
folio_unlock(folio);
|
|
mapping_set_error(mapping, ret);
|
|
return ret;
|
|
}
|
|
|
|
static int ntfs_writepages(struct address_space *mapping,
|
|
struct writeback_control *wbc)
|
|
{
|
|
if (is_resident(ntfs_i(mapping->host)))
|
|
return write_cache_pages(mapping, wbc, ntfs_resident_writepage,
|
|
mapping);
|
|
return mpage_writepages(mapping, wbc, ntfs_get_block);
|
|
}
|
|
|
|
static int ntfs_get_block_write_begin(struct inode *inode, sector_t vbn,
|
|
struct buffer_head *bh_result, int create)
|
|
{
|
|
return ntfs_get_block_vbo(inode, (u64)vbn << inode->i_blkbits,
|
|
bh_result, create, GET_BLOCK_WRITE_BEGIN);
|
|
}
|
|
|
|
int ntfs_write_begin(struct file *file, struct address_space *mapping,
|
|
loff_t pos, u32 len, struct page **pagep, void **fsdata)
|
|
{
|
|
int err;
|
|
struct inode *inode = mapping->host;
|
|
struct ntfs_inode *ni = ntfs_i(inode);
|
|
|
|
*pagep = NULL;
|
|
if (is_resident(ni)) {
|
|
struct page *page =
|
|
grab_cache_page_write_begin(mapping, pos >> PAGE_SHIFT);
|
|
|
|
if (!page) {
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
ni_lock(ni);
|
|
err = attr_data_read_resident(ni, page);
|
|
ni_unlock(ni);
|
|
|
|
if (!err) {
|
|
*pagep = page;
|
|
goto out;
|
|
}
|
|
unlock_page(page);
|
|
put_page(page);
|
|
|
|
if (err != E_NTFS_NONRESIDENT)
|
|
goto out;
|
|
}
|
|
|
|
err = block_write_begin(mapping, pos, len, pagep,
|
|
ntfs_get_block_write_begin);
|
|
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* ntfs_write_end - Address_space_operations::write_end.
|
|
*/
|
|
int ntfs_write_end(struct file *file, struct address_space *mapping, loff_t pos,
|
|
u32 len, u32 copied, struct page *page, void *fsdata)
|
|
{
|
|
struct inode *inode = mapping->host;
|
|
struct ntfs_inode *ni = ntfs_i(inode);
|
|
u64 valid = ni->i_valid;
|
|
bool dirty = false;
|
|
int err;
|
|
|
|
if (is_resident(ni)) {
|
|
ni_lock(ni);
|
|
err = attr_data_write_resident(ni, page);
|
|
ni_unlock(ni);
|
|
if (!err) {
|
|
dirty = true;
|
|
/* Clear any buffers in page. */
|
|
if (page_has_buffers(page)) {
|
|
struct buffer_head *head, *bh;
|
|
|
|
bh = head = page_buffers(page);
|
|
do {
|
|
clear_buffer_dirty(bh);
|
|
clear_buffer_mapped(bh);
|
|
set_buffer_uptodate(bh);
|
|
} while (head != (bh = bh->b_this_page));
|
|
}
|
|
SetPageUptodate(page);
|
|
err = copied;
|
|
}
|
|
unlock_page(page);
|
|
put_page(page);
|
|
} else {
|
|
err = generic_write_end(file, mapping, pos, len, copied, page,
|
|
fsdata);
|
|
}
|
|
|
|
if (err >= 0) {
|
|
if (!(ni->std_fa & FILE_ATTRIBUTE_ARCHIVE)) {
|
|
inode->i_mtime = inode_set_ctime_current(inode);
|
|
ni->std_fa |= FILE_ATTRIBUTE_ARCHIVE;
|
|
dirty = true;
|
|
}
|
|
|
|
if (valid != ni->i_valid) {
|
|
/* ni->i_valid is changed in ntfs_get_block_vbo. */
|
|
dirty = true;
|
|
}
|
|
|
|
if (pos + err > inode->i_size) {
|
|
inode->i_size = pos + err;
|
|
dirty = true;
|
|
}
|
|
|
|
if (dirty)
|
|
mark_inode_dirty(inode);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
int reset_log_file(struct inode *inode)
|
|
{
|
|
int err;
|
|
loff_t pos = 0;
|
|
u32 log_size = inode->i_size;
|
|
struct address_space *mapping = inode->i_mapping;
|
|
|
|
for (;;) {
|
|
u32 len;
|
|
void *kaddr;
|
|
struct page *page;
|
|
|
|
len = pos + PAGE_SIZE > log_size ? (log_size - pos) : PAGE_SIZE;
|
|
|
|
err = block_write_begin(mapping, pos, len, &page,
|
|
ntfs_get_block_write_begin);
|
|
if (err)
|
|
goto out;
|
|
|
|
kaddr = kmap_atomic(page);
|
|
memset(kaddr, -1, len);
|
|
kunmap_atomic(kaddr);
|
|
flush_dcache_page(page);
|
|
|
|
err = block_write_end(NULL, mapping, pos, len, len, page, NULL);
|
|
if (err < 0)
|
|
goto out;
|
|
pos += len;
|
|
|
|
if (pos >= log_size)
|
|
break;
|
|
balance_dirty_pages_ratelimited(mapping);
|
|
}
|
|
out:
|
|
mark_inode_dirty_sync(inode);
|
|
|
|
return err;
|
|
}
|
|
|
|
int ntfs3_write_inode(struct inode *inode, struct writeback_control *wbc)
|
|
{
|
|
return _ni_write_inode(inode, wbc->sync_mode == WB_SYNC_ALL);
|
|
}
|
|
|
|
int ntfs_sync_inode(struct inode *inode)
|
|
{
|
|
return _ni_write_inode(inode, 1);
|
|
}
|
|
|
|
/*
|
|
* writeback_inode - Helper function for ntfs_flush_inodes().
|
|
*
|
|
* This writes both the inode and the file data blocks, waiting
|
|
* for in flight data blocks before the start of the call. It
|
|
* does not wait for any io started during the call.
|
|
*/
|
|
static int writeback_inode(struct inode *inode)
|
|
{
|
|
int ret = sync_inode_metadata(inode, 0);
|
|
|
|
if (!ret)
|
|
ret = filemap_fdatawrite(inode->i_mapping);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* ntfs_flush_inodes
|
|
*
|
|
* Write data and metadata corresponding to i1 and i2. The io is
|
|
* started but we do not wait for any of it to finish.
|
|
*
|
|
* filemap_flush() is used for the block device, so if there is a dirty
|
|
* page for a block already in flight, we will not wait and start the
|
|
* io over again.
|
|
*/
|
|
int ntfs_flush_inodes(struct super_block *sb, struct inode *i1,
|
|
struct inode *i2)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (i1)
|
|
ret = writeback_inode(i1);
|
|
if (!ret && i2)
|
|
ret = writeback_inode(i2);
|
|
if (!ret)
|
|
ret = sync_blockdev_nowait(sb->s_bdev);
|
|
return ret;
|
|
}
|
|
|
|
int inode_write_data(struct inode *inode, const void *data, size_t bytes)
|
|
{
|
|
pgoff_t idx;
|
|
|
|
/* Write non resident data. */
|
|
for (idx = 0; bytes; idx++) {
|
|
size_t op = bytes > PAGE_SIZE ? PAGE_SIZE : bytes;
|
|
struct page *page = ntfs_map_page(inode->i_mapping, idx);
|
|
|
|
if (IS_ERR(page))
|
|
return PTR_ERR(page);
|
|
|
|
lock_page(page);
|
|
WARN_ON(!PageUptodate(page));
|
|
ClearPageUptodate(page);
|
|
|
|
memcpy(page_address(page), data, op);
|
|
|
|
flush_dcache_page(page);
|
|
SetPageUptodate(page);
|
|
unlock_page(page);
|
|
|
|
ntfs_unmap_page(page);
|
|
|
|
bytes -= op;
|
|
data = Add2Ptr(data, PAGE_SIZE);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* ntfs_reparse_bytes
|
|
*
|
|
* Number of bytes for REPARSE_DATA_BUFFER(IO_REPARSE_TAG_SYMLINK)
|
|
* for unicode string of @uni_len length.
|
|
*/
|
|
static inline u32 ntfs_reparse_bytes(u32 uni_len)
|
|
{
|
|
/* Header + unicode string + decorated unicode string. */
|
|
return sizeof(short) * (2 * uni_len + 4) +
|
|
offsetof(struct REPARSE_DATA_BUFFER,
|
|
SymbolicLinkReparseBuffer.PathBuffer);
|
|
}
|
|
|
|
static struct REPARSE_DATA_BUFFER *
|
|
ntfs_create_reparse_buffer(struct ntfs_sb_info *sbi, const char *symname,
|
|
u32 size, u16 *nsize)
|
|
{
|
|
int i, err;
|
|
struct REPARSE_DATA_BUFFER *rp;
|
|
__le16 *rp_name;
|
|
typeof(rp->SymbolicLinkReparseBuffer) *rs;
|
|
|
|
rp = kzalloc(ntfs_reparse_bytes(2 * size + 2), GFP_NOFS);
|
|
if (!rp)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
rs = &rp->SymbolicLinkReparseBuffer;
|
|
rp_name = rs->PathBuffer;
|
|
|
|
/* Convert link name to UTF-16. */
|
|
err = ntfs_nls_to_utf16(sbi, symname, size,
|
|
(struct cpu_str *)(rp_name - 1), 2 * size,
|
|
UTF16_LITTLE_ENDIAN);
|
|
if (err < 0)
|
|
goto out;
|
|
|
|
/* err = the length of unicode name of symlink. */
|
|
*nsize = ntfs_reparse_bytes(err);
|
|
|
|
if (*nsize > sbi->reparse.max_size) {
|
|
err = -EFBIG;
|
|
goto out;
|
|
}
|
|
|
|
/* Translate Linux '/' into Windows '\'. */
|
|
for (i = 0; i < err; i++) {
|
|
if (rp_name[i] == cpu_to_le16('/'))
|
|
rp_name[i] = cpu_to_le16('\\');
|
|
}
|
|
|
|
rp->ReparseTag = IO_REPARSE_TAG_SYMLINK;
|
|
rp->ReparseDataLength =
|
|
cpu_to_le16(*nsize - offsetof(struct REPARSE_DATA_BUFFER,
|
|
SymbolicLinkReparseBuffer));
|
|
|
|
/* PrintName + SubstituteName. */
|
|
rs->SubstituteNameOffset = cpu_to_le16(sizeof(short) * err);
|
|
rs->SubstituteNameLength = cpu_to_le16(sizeof(short) * err + 8);
|
|
rs->PrintNameLength = rs->SubstituteNameOffset;
|
|
|
|
/*
|
|
* TODO: Use relative path if possible to allow Windows to
|
|
* parse this path.
|
|
* 0-absolute path 1- relative path (SYMLINK_FLAG_RELATIVE).
|
|
*/
|
|
rs->Flags = 0;
|
|
|
|
memmove(rp_name + err + 4, rp_name, sizeof(short) * err);
|
|
|
|
/* Decorate SubstituteName. */
|
|
rp_name += err;
|
|
rp_name[0] = cpu_to_le16('\\');
|
|
rp_name[1] = cpu_to_le16('?');
|
|
rp_name[2] = cpu_to_le16('?');
|
|
rp_name[3] = cpu_to_le16('\\');
|
|
|
|
return rp;
|
|
out:
|
|
kfree(rp);
|
|
return ERR_PTR(err);
|
|
}
|
|
|
|
/*
|
|
* ntfs_create_inode
|
|
*
|
|
* Helper function for:
|
|
* - ntfs_create
|
|
* - ntfs_mknod
|
|
* - ntfs_symlink
|
|
* - ntfs_mkdir
|
|
* - ntfs_atomic_open
|
|
*
|
|
* NOTE: if fnd != NULL (ntfs_atomic_open) then @dir is locked
|
|
*/
|
|
struct inode *ntfs_create_inode(struct mnt_idmap *idmap, struct inode *dir,
|
|
struct dentry *dentry,
|
|
const struct cpu_str *uni, umode_t mode,
|
|
dev_t dev, const char *symname, u32 size,
|
|
struct ntfs_fnd *fnd)
|
|
{
|
|
int err;
|
|
struct super_block *sb = dir->i_sb;
|
|
struct ntfs_sb_info *sbi = sb->s_fs_info;
|
|
const struct qstr *name = &dentry->d_name;
|
|
CLST ino = 0;
|
|
struct ntfs_inode *dir_ni = ntfs_i(dir);
|
|
struct ntfs_inode *ni = NULL;
|
|
struct inode *inode = NULL;
|
|
struct ATTRIB *attr;
|
|
struct ATTR_STD_INFO5 *std5;
|
|
struct ATTR_FILE_NAME *fname;
|
|
struct MFT_REC *rec;
|
|
u32 asize, dsize, sd_size;
|
|
enum FILE_ATTRIBUTE fa;
|
|
__le32 security_id = SECURITY_ID_INVALID;
|
|
CLST vcn;
|
|
const void *sd;
|
|
u16 t16, nsize = 0, aid = 0;
|
|
struct INDEX_ROOT *root, *dir_root;
|
|
struct NTFS_DE *e, *new_de = NULL;
|
|
struct REPARSE_DATA_BUFFER *rp = NULL;
|
|
bool rp_inserted = false;
|
|
|
|
if (!fnd)
|
|
ni_lock_dir(dir_ni);
|
|
|
|
dir_root = indx_get_root(&dir_ni->dir, dir_ni, NULL, NULL);
|
|
if (!dir_root) {
|
|
err = -EINVAL;
|
|
goto out1;
|
|
}
|
|
|
|
if (S_ISDIR(mode)) {
|
|
/* Use parent's directory attributes. */
|
|
fa = dir_ni->std_fa | FILE_ATTRIBUTE_DIRECTORY |
|
|
FILE_ATTRIBUTE_ARCHIVE;
|
|
/*
|
|
* By default child directory inherits parent attributes.
|
|
* Root directory is hidden + system.
|
|
* Make an exception for children in root.
|
|
*/
|
|
if (dir->i_ino == MFT_REC_ROOT)
|
|
fa &= ~(FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM);
|
|
} else if (S_ISLNK(mode)) {
|
|
/* It is good idea that link should be the same type (file/dir) as target */
|
|
fa = FILE_ATTRIBUTE_REPARSE_POINT;
|
|
|
|
/*
|
|
* Linux: there are dir/file/symlink and so on.
|
|
* NTFS: symlinks are "dir + reparse" or "file + reparse"
|
|
* It is good idea to create:
|
|
* dir + reparse if 'symname' points to directory
|
|
* or
|
|
* file + reparse if 'symname' points to file
|
|
* Unfortunately kern_path hangs if symname contains 'dir'.
|
|
*/
|
|
|
|
/*
|
|
* struct path path;
|
|
*
|
|
* if (!kern_path(symname, LOOKUP_FOLLOW, &path)){
|
|
* struct inode *target = d_inode(path.dentry);
|
|
*
|
|
* if (S_ISDIR(target->i_mode))
|
|
* fa |= FILE_ATTRIBUTE_DIRECTORY;
|
|
* // if ( target->i_sb == sb ){
|
|
* // use relative path?
|
|
* // }
|
|
* path_put(&path);
|
|
* }
|
|
*/
|
|
} else if (S_ISREG(mode)) {
|
|
if (sbi->options->sparse) {
|
|
/* Sparsed regular file, cause option 'sparse'. */
|
|
fa = FILE_ATTRIBUTE_SPARSE_FILE |
|
|
FILE_ATTRIBUTE_ARCHIVE;
|
|
} else if (dir_ni->std_fa & FILE_ATTRIBUTE_COMPRESSED) {
|
|
/* Compressed regular file, if parent is compressed. */
|
|
fa = FILE_ATTRIBUTE_COMPRESSED | FILE_ATTRIBUTE_ARCHIVE;
|
|
} else {
|
|
/* Regular file, default attributes. */
|
|
fa = FILE_ATTRIBUTE_ARCHIVE;
|
|
}
|
|
} else {
|
|
fa = FILE_ATTRIBUTE_ARCHIVE;
|
|
}
|
|
|
|
/* If option "hide_dot_files" then set hidden attribute for dot files. */
|
|
if (sbi->options->hide_dot_files && name->name[0] == '.')
|
|
fa |= FILE_ATTRIBUTE_HIDDEN;
|
|
|
|
if (!(mode & 0222))
|
|
fa |= FILE_ATTRIBUTE_READONLY;
|
|
|
|
/* Allocate PATH_MAX bytes. */
|
|
new_de = __getname();
|
|
if (!new_de) {
|
|
err = -ENOMEM;
|
|
goto out1;
|
|
}
|
|
|
|
/* Mark rw ntfs as dirty. it will be cleared at umount. */
|
|
ntfs_set_state(sbi, NTFS_DIRTY_DIRTY);
|
|
|
|
/* Step 1: allocate and fill new mft record. */
|
|
err = ntfs_look_free_mft(sbi, &ino, false, NULL, NULL);
|
|
if (err)
|
|
goto out2;
|
|
|
|
ni = ntfs_new_inode(sbi, ino, S_ISDIR(mode) ? RECORD_FLAG_DIR : 0);
|
|
if (IS_ERR(ni)) {
|
|
err = PTR_ERR(ni);
|
|
ni = NULL;
|
|
goto out3;
|
|
}
|
|
inode = &ni->vfs_inode;
|
|
inode_init_owner(idmap, inode, dir, mode);
|
|
mode = inode->i_mode;
|
|
|
|
ni->i_crtime = current_time(inode);
|
|
|
|
rec = ni->mi.mrec;
|
|
rec->hard_links = cpu_to_le16(1);
|
|
attr = Add2Ptr(rec, le16_to_cpu(rec->attr_off));
|
|
|
|
/* Get default security id. */
|
|
sd = s_default_security;
|
|
sd_size = sizeof(s_default_security);
|
|
|
|
if (is_ntfs3(sbi)) {
|
|
security_id = dir_ni->std_security_id;
|
|
if (le32_to_cpu(security_id) < SECURITY_ID_FIRST) {
|
|
security_id = sbi->security.def_security_id;
|
|
|
|
if (security_id == SECURITY_ID_INVALID &&
|
|
!ntfs_insert_security(sbi, sd, sd_size,
|
|
&security_id, NULL))
|
|
sbi->security.def_security_id = security_id;
|
|
}
|
|
}
|
|
|
|
/* Insert standard info. */
|
|
std5 = Add2Ptr(attr, SIZEOF_RESIDENT);
|
|
|
|
if (security_id == SECURITY_ID_INVALID) {
|
|
dsize = sizeof(struct ATTR_STD_INFO);
|
|
} else {
|
|
dsize = sizeof(struct ATTR_STD_INFO5);
|
|
std5->security_id = security_id;
|
|
ni->std_security_id = security_id;
|
|
}
|
|
asize = SIZEOF_RESIDENT + dsize;
|
|
|
|
attr->type = ATTR_STD;
|
|
attr->size = cpu_to_le32(asize);
|
|
attr->id = cpu_to_le16(aid++);
|
|
attr->res.data_off = SIZEOF_RESIDENT_LE;
|
|
attr->res.data_size = cpu_to_le32(dsize);
|
|
|
|
std5->cr_time = std5->m_time = std5->c_time = std5->a_time =
|
|
kernel2nt(&ni->i_crtime);
|
|
|
|
std5->fa = ni->std_fa = fa;
|
|
|
|
attr = Add2Ptr(attr, asize);
|
|
|
|
/* Insert file name. */
|
|
err = fill_name_de(sbi, new_de, name, uni);
|
|
if (err)
|
|
goto out4;
|
|
|
|
mi_get_ref(&ni->mi, &new_de->ref);
|
|
|
|
fname = (struct ATTR_FILE_NAME *)(new_de + 1);
|
|
|
|
if (sbi->options->windows_names &&
|
|
!valid_windows_name(sbi, (struct le_str *)&fname->name_len)) {
|
|
err = -EINVAL;
|
|
goto out4;
|
|
}
|
|
|
|
mi_get_ref(&dir_ni->mi, &fname->home);
|
|
fname->dup.cr_time = fname->dup.m_time = fname->dup.c_time =
|
|
fname->dup.a_time = std5->cr_time;
|
|
fname->dup.alloc_size = fname->dup.data_size = 0;
|
|
fname->dup.fa = std5->fa;
|
|
fname->dup.ea_size = fname->dup.reparse = 0;
|
|
|
|
dsize = le16_to_cpu(new_de->key_size);
|
|
asize = ALIGN(SIZEOF_RESIDENT + dsize, 8);
|
|
|
|
attr->type = ATTR_NAME;
|
|
attr->size = cpu_to_le32(asize);
|
|
attr->res.data_off = SIZEOF_RESIDENT_LE;
|
|
attr->res.flags = RESIDENT_FLAG_INDEXED;
|
|
attr->id = cpu_to_le16(aid++);
|
|
attr->res.data_size = cpu_to_le32(dsize);
|
|
memcpy(Add2Ptr(attr, SIZEOF_RESIDENT), fname, dsize);
|
|
|
|
attr = Add2Ptr(attr, asize);
|
|
|
|
if (security_id == SECURITY_ID_INVALID) {
|
|
/* Insert security attribute. */
|
|
asize = SIZEOF_RESIDENT + ALIGN(sd_size, 8);
|
|
|
|
attr->type = ATTR_SECURE;
|
|
attr->size = cpu_to_le32(asize);
|
|
attr->id = cpu_to_le16(aid++);
|
|
attr->res.data_off = SIZEOF_RESIDENT_LE;
|
|
attr->res.data_size = cpu_to_le32(sd_size);
|
|
memcpy(Add2Ptr(attr, SIZEOF_RESIDENT), sd, sd_size);
|
|
|
|
attr = Add2Ptr(attr, asize);
|
|
}
|
|
|
|
attr->id = cpu_to_le16(aid++);
|
|
if (fa & FILE_ATTRIBUTE_DIRECTORY) {
|
|
/*
|
|
* Regular directory or symlink to directory.
|
|
* Create root attribute.
|
|
*/
|
|
dsize = sizeof(struct INDEX_ROOT) + sizeof(struct NTFS_DE);
|
|
asize = sizeof(I30_NAME) + SIZEOF_RESIDENT + dsize;
|
|
|
|
attr->type = ATTR_ROOT;
|
|
attr->size = cpu_to_le32(asize);
|
|
|
|
attr->name_len = ARRAY_SIZE(I30_NAME);
|
|
attr->name_off = SIZEOF_RESIDENT_LE;
|
|
attr->res.data_off =
|
|
cpu_to_le16(sizeof(I30_NAME) + SIZEOF_RESIDENT);
|
|
attr->res.data_size = cpu_to_le32(dsize);
|
|
memcpy(Add2Ptr(attr, SIZEOF_RESIDENT), I30_NAME,
|
|
sizeof(I30_NAME));
|
|
|
|
root = Add2Ptr(attr, sizeof(I30_NAME) + SIZEOF_RESIDENT);
|
|
memcpy(root, dir_root, offsetof(struct INDEX_ROOT, ihdr));
|
|
root->ihdr.de_off = cpu_to_le32(sizeof(struct INDEX_HDR));
|
|
root->ihdr.used = cpu_to_le32(sizeof(struct INDEX_HDR) +
|
|
sizeof(struct NTFS_DE));
|
|
root->ihdr.total = root->ihdr.used;
|
|
|
|
e = Add2Ptr(root, sizeof(struct INDEX_ROOT));
|
|
e->size = cpu_to_le16(sizeof(struct NTFS_DE));
|
|
e->flags = NTFS_IE_LAST;
|
|
} else if (S_ISLNK(mode)) {
|
|
/*
|
|
* Symlink to file.
|
|
* Create empty resident data attribute.
|
|
*/
|
|
asize = SIZEOF_RESIDENT;
|
|
|
|
/* Insert empty ATTR_DATA */
|
|
attr->type = ATTR_DATA;
|
|
attr->size = cpu_to_le32(SIZEOF_RESIDENT);
|
|
attr->name_off = SIZEOF_RESIDENT_LE;
|
|
attr->res.data_off = SIZEOF_RESIDENT_LE;
|
|
} else if (S_ISREG(mode)) {
|
|
/*
|
|
* Regular file. Create empty non resident data attribute.
|
|
*/
|
|
attr->type = ATTR_DATA;
|
|
attr->non_res = 1;
|
|
attr->nres.evcn = cpu_to_le64(-1ll);
|
|
if (fa & FILE_ATTRIBUTE_SPARSE_FILE) {
|
|
attr->size = cpu_to_le32(SIZEOF_NONRESIDENT_EX + 8);
|
|
attr->name_off = SIZEOF_NONRESIDENT_EX_LE;
|
|
attr->flags = ATTR_FLAG_SPARSED;
|
|
asize = SIZEOF_NONRESIDENT_EX + 8;
|
|
} else if (fa & FILE_ATTRIBUTE_COMPRESSED) {
|
|
attr->size = cpu_to_le32(SIZEOF_NONRESIDENT_EX + 8);
|
|
attr->name_off = SIZEOF_NONRESIDENT_EX_LE;
|
|
attr->flags = ATTR_FLAG_COMPRESSED;
|
|
attr->nres.c_unit = COMPRESSION_UNIT;
|
|
asize = SIZEOF_NONRESIDENT_EX + 8;
|
|
} else {
|
|
attr->size = cpu_to_le32(SIZEOF_NONRESIDENT + 8);
|
|
attr->name_off = SIZEOF_NONRESIDENT_LE;
|
|
asize = SIZEOF_NONRESIDENT + 8;
|
|
}
|
|
attr->nres.run_off = attr->name_off;
|
|
} else {
|
|
/*
|
|
* Node. Create empty resident data attribute.
|
|
*/
|
|
attr->type = ATTR_DATA;
|
|
attr->size = cpu_to_le32(SIZEOF_RESIDENT);
|
|
attr->name_off = SIZEOF_RESIDENT_LE;
|
|
if (fa & FILE_ATTRIBUTE_SPARSE_FILE)
|
|
attr->flags = ATTR_FLAG_SPARSED;
|
|
else if (fa & FILE_ATTRIBUTE_COMPRESSED)
|
|
attr->flags = ATTR_FLAG_COMPRESSED;
|
|
attr->res.data_off = SIZEOF_RESIDENT_LE;
|
|
asize = SIZEOF_RESIDENT;
|
|
ni->ni_flags |= NI_FLAG_RESIDENT;
|
|
}
|
|
|
|
if (S_ISDIR(mode)) {
|
|
ni->ni_flags |= NI_FLAG_DIR;
|
|
err = indx_init(&ni->dir, sbi, attr, INDEX_MUTEX_I30);
|
|
if (err)
|
|
goto out4;
|
|
} else if (S_ISLNK(mode)) {
|
|
rp = ntfs_create_reparse_buffer(sbi, symname, size, &nsize);
|
|
|
|
if (IS_ERR(rp)) {
|
|
err = PTR_ERR(rp);
|
|
rp = NULL;
|
|
goto out4;
|
|
}
|
|
|
|
/*
|
|
* Insert ATTR_REPARSE.
|
|
*/
|
|
attr = Add2Ptr(attr, asize);
|
|
attr->type = ATTR_REPARSE;
|
|
attr->id = cpu_to_le16(aid++);
|
|
|
|
/* Resident or non resident? */
|
|
asize = ALIGN(SIZEOF_RESIDENT + nsize, 8);
|
|
t16 = PtrOffset(rec, attr);
|
|
|
|
/*
|
|
* Below function 'ntfs_save_wsl_perm' requires 0x78 bytes.
|
|
* It is good idea to keep extened attributes resident.
|
|
*/
|
|
if (asize + t16 + 0x78 + 8 > sbi->record_size) {
|
|
CLST alen;
|
|
CLST clst = bytes_to_cluster(sbi, nsize);
|
|
|
|
/* Bytes per runs. */
|
|
t16 = sbi->record_size - t16 - SIZEOF_NONRESIDENT;
|
|
|
|
attr->non_res = 1;
|
|
attr->nres.evcn = cpu_to_le64(clst - 1);
|
|
attr->name_off = SIZEOF_NONRESIDENT_LE;
|
|
attr->nres.run_off = attr->name_off;
|
|
attr->nres.data_size = cpu_to_le64(nsize);
|
|
attr->nres.valid_size = attr->nres.data_size;
|
|
attr->nres.alloc_size =
|
|
cpu_to_le64(ntfs_up_cluster(sbi, nsize));
|
|
|
|
err = attr_allocate_clusters(sbi, &ni->file.run, 0, 0,
|
|
clst, NULL, ALLOCATE_DEF,
|
|
&alen, 0, NULL, NULL);
|
|
if (err)
|
|
goto out5;
|
|
|
|
err = run_pack(&ni->file.run, 0, clst,
|
|
Add2Ptr(attr, SIZEOF_NONRESIDENT), t16,
|
|
&vcn);
|
|
if (err < 0)
|
|
goto out5;
|
|
|
|
if (vcn != clst) {
|
|
err = -EINVAL;
|
|
goto out5;
|
|
}
|
|
|
|
asize = SIZEOF_NONRESIDENT + ALIGN(err, 8);
|
|
/* Write non resident data. */
|
|
err = ntfs_sb_write_run(sbi, &ni->file.run, 0, rp,
|
|
nsize, 0);
|
|
if (err)
|
|
goto out5;
|
|
} else {
|
|
attr->res.data_off = SIZEOF_RESIDENT_LE;
|
|
attr->res.data_size = cpu_to_le32(nsize);
|
|
memcpy(Add2Ptr(attr, SIZEOF_RESIDENT), rp, nsize);
|
|
}
|
|
/* Size of symlink equals the length of input string. */
|
|
inode->i_size = size;
|
|
|
|
attr->size = cpu_to_le32(asize);
|
|
|
|
err = ntfs_insert_reparse(sbi, IO_REPARSE_TAG_SYMLINK,
|
|
&new_de->ref);
|
|
if (err)
|
|
goto out5;
|
|
|
|
rp_inserted = true;
|
|
}
|
|
|
|
attr = Add2Ptr(attr, asize);
|
|
attr->type = ATTR_END;
|
|
|
|
rec->used = cpu_to_le32(PtrOffset(rec, attr) + 8);
|
|
rec->next_attr_id = cpu_to_le16(aid);
|
|
|
|
inode->i_generation = le16_to_cpu(rec->seq);
|
|
|
|
if (S_ISDIR(mode)) {
|
|
inode->i_op = &ntfs_dir_inode_operations;
|
|
inode->i_fop = &ntfs_dir_operations;
|
|
} else if (S_ISLNK(mode)) {
|
|
inode->i_op = &ntfs_link_inode_operations;
|
|
inode->i_fop = NULL;
|
|
inode->i_mapping->a_ops = &ntfs_aops;
|
|
inode->i_size = size;
|
|
inode_nohighmem(inode);
|
|
} else if (S_ISREG(mode)) {
|
|
inode->i_op = &ntfs_file_inode_operations;
|
|
inode->i_fop = &ntfs_file_operations;
|
|
inode->i_mapping->a_ops = is_compressed(ni) ? &ntfs_aops_cmpr :
|
|
&ntfs_aops;
|
|
init_rwsem(&ni->file.run_lock);
|
|
} else {
|
|
inode->i_op = &ntfs_special_inode_operations;
|
|
init_special_inode(inode, mode, dev);
|
|
}
|
|
|
|
#ifdef CONFIG_NTFS3_FS_POSIX_ACL
|
|
if (!S_ISLNK(mode) && (sb->s_flags & SB_POSIXACL)) {
|
|
err = ntfs_init_acl(idmap, inode, dir);
|
|
if (err)
|
|
goto out5;
|
|
} else
|
|
#endif
|
|
{
|
|
inode->i_flags |= S_NOSEC;
|
|
}
|
|
|
|
/*
|
|
* ntfs_init_acl and ntfs_save_wsl_perm update extended attribute.
|
|
* The packed size of extended attribute is stored in direntry too.
|
|
* 'fname' here points to inside new_de.
|
|
*/
|
|
ntfs_save_wsl_perm(inode, &fname->dup.ea_size);
|
|
|
|
/*
|
|
* update ea_size in file_name attribute too.
|
|
* Use ni_find_attr cause layout of MFT record may be changed
|
|
* in ntfs_init_acl and ntfs_save_wsl_perm.
|
|
*/
|
|
attr = ni_find_attr(ni, NULL, NULL, ATTR_NAME, NULL, 0, NULL, NULL);
|
|
if (attr) {
|
|
struct ATTR_FILE_NAME *fn;
|
|
|
|
fn = resident_data_ex(attr, SIZEOF_ATTRIBUTE_FILENAME);
|
|
if (fn)
|
|
fn->dup.ea_size = fname->dup.ea_size;
|
|
}
|
|
|
|
/* We do not need to update parent directory later */
|
|
ni->ni_flags &= ~NI_FLAG_UPDATE_PARENT;
|
|
|
|
/* Step 2: Add new name in index. */
|
|
err = indx_insert_entry(&dir_ni->dir, dir_ni, new_de, sbi, fnd, 0);
|
|
if (err)
|
|
goto out6;
|
|
|
|
/*
|
|
* Call 'd_instantiate' after inode->i_op is set
|
|
* but before finish_open.
|
|
*/
|
|
d_instantiate(dentry, inode);
|
|
|
|
/* Set original time. inode times (i_ctime) may be changed in ntfs_init_acl. */
|
|
inode->i_atime = inode->i_mtime = inode_set_ctime_to_ts(inode, ni->i_crtime);
|
|
dir->i_mtime = inode_set_ctime_to_ts(dir, ni->i_crtime);
|
|
|
|
mark_inode_dirty(dir);
|
|
mark_inode_dirty(inode);
|
|
|
|
/* Normal exit. */
|
|
goto out2;
|
|
|
|
out6:
|
|
if (rp_inserted)
|
|
ntfs_remove_reparse(sbi, IO_REPARSE_TAG_SYMLINK, &new_de->ref);
|
|
|
|
out5:
|
|
if (!S_ISDIR(mode))
|
|
run_deallocate(sbi, &ni->file.run, false);
|
|
|
|
out4:
|
|
clear_rec_inuse(rec);
|
|
clear_nlink(inode);
|
|
ni->mi.dirty = false;
|
|
discard_new_inode(inode);
|
|
out3:
|
|
ntfs_mark_rec_free(sbi, ino, false);
|
|
|
|
out2:
|
|
__putname(new_de);
|
|
kfree(rp);
|
|
|
|
out1:
|
|
if (!fnd)
|
|
ni_unlock(dir_ni);
|
|
|
|
if (err)
|
|
return ERR_PTR(err);
|
|
|
|
unlock_new_inode(inode);
|
|
|
|
return inode;
|
|
}
|
|
|
|
int ntfs_link_inode(struct inode *inode, struct dentry *dentry)
|
|
{
|
|
int err;
|
|
struct ntfs_inode *ni = ntfs_i(inode);
|
|
struct ntfs_sb_info *sbi = inode->i_sb->s_fs_info;
|
|
struct NTFS_DE *de;
|
|
|
|
/* Allocate PATH_MAX bytes. */
|
|
de = __getname();
|
|
if (!de)
|
|
return -ENOMEM;
|
|
|
|
/* Mark rw ntfs as dirty. It will be cleared at umount. */
|
|
ntfs_set_state(sbi, NTFS_DIRTY_DIRTY);
|
|
|
|
/* Construct 'de'. */
|
|
err = fill_name_de(sbi, de, &dentry->d_name, NULL);
|
|
if (err)
|
|
goto out;
|
|
|
|
err = ni_add_name(ntfs_i(d_inode(dentry->d_parent)), ni, de);
|
|
out:
|
|
__putname(de);
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* ntfs_unlink_inode
|
|
*
|
|
* inode_operations::unlink
|
|
* inode_operations::rmdir
|
|
*/
|
|
int ntfs_unlink_inode(struct inode *dir, const struct dentry *dentry)
|
|
{
|
|
int err;
|
|
struct ntfs_sb_info *sbi = dir->i_sb->s_fs_info;
|
|
struct inode *inode = d_inode(dentry);
|
|
struct ntfs_inode *ni = ntfs_i(inode);
|
|
struct ntfs_inode *dir_ni = ntfs_i(dir);
|
|
struct NTFS_DE *de, *de2 = NULL;
|
|
int undo_remove;
|
|
|
|
if (ntfs_is_meta_file(sbi, ni->mi.rno))
|
|
return -EINVAL;
|
|
|
|
/* Allocate PATH_MAX bytes. */
|
|
de = __getname();
|
|
if (!de)
|
|
return -ENOMEM;
|
|
|
|
ni_lock(ni);
|
|
|
|
if (S_ISDIR(inode->i_mode) && !dir_is_empty(inode)) {
|
|
err = -ENOTEMPTY;
|
|
goto out;
|
|
}
|
|
|
|
err = fill_name_de(sbi, de, &dentry->d_name, NULL);
|
|
if (err < 0)
|
|
goto out;
|
|
|
|
undo_remove = 0;
|
|
err = ni_remove_name(dir_ni, ni, de, &de2, &undo_remove);
|
|
|
|
if (!err) {
|
|
drop_nlink(inode);
|
|
dir->i_mtime = inode_set_ctime_current(dir);
|
|
mark_inode_dirty(dir);
|
|
inode_set_ctime_to_ts(inode, inode_get_ctime(dir));
|
|
if (inode->i_nlink)
|
|
mark_inode_dirty(inode);
|
|
} else if (!ni_remove_name_undo(dir_ni, ni, de, de2, undo_remove)) {
|
|
_ntfs_bad_inode(inode);
|
|
} else {
|
|
if (ni_is_dirty(dir))
|
|
mark_inode_dirty(dir);
|
|
if (ni_is_dirty(inode))
|
|
mark_inode_dirty(inode);
|
|
}
|
|
|
|
out:
|
|
ni_unlock(ni);
|
|
__putname(de);
|
|
return err;
|
|
}
|
|
|
|
void ntfs_evict_inode(struct inode *inode)
|
|
{
|
|
truncate_inode_pages_final(&inode->i_data);
|
|
|
|
invalidate_inode_buffers(inode);
|
|
clear_inode(inode);
|
|
|
|
ni_clear(ntfs_i(inode));
|
|
}
|
|
|
|
/*
|
|
* ntfs_translate_junction
|
|
*
|
|
* Translate a Windows junction target to the Linux equivalent.
|
|
* On junctions, targets are always absolute (they include the drive
|
|
* letter). We have no way of knowing if the target is for the current
|
|
* mounted device or not so we just assume it is.
|
|
*/
|
|
static int ntfs_translate_junction(const struct super_block *sb,
|
|
const struct dentry *link_de, char *target,
|
|
int target_len, int target_max)
|
|
{
|
|
int tl_len, err = target_len;
|
|
char *link_path_buffer = NULL, *link_path;
|
|
char *translated = NULL;
|
|
char *target_start;
|
|
int copy_len;
|
|
|
|
link_path_buffer = kmalloc(PATH_MAX, GFP_NOFS);
|
|
if (!link_path_buffer) {
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
/* Get link path, relative to mount point */
|
|
link_path = dentry_path_raw(link_de, link_path_buffer, PATH_MAX);
|
|
if (IS_ERR(link_path)) {
|
|
ntfs_err(sb, "Error getting link path");
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
translated = kmalloc(PATH_MAX, GFP_NOFS);
|
|
if (!translated) {
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
/* Make translated path a relative path to mount point */
|
|
strcpy(translated, "./");
|
|
++link_path; /* Skip leading / */
|
|
for (tl_len = sizeof("./") - 1; *link_path; ++link_path) {
|
|
if (*link_path == '/') {
|
|
if (PATH_MAX - tl_len < sizeof("../")) {
|
|
ntfs_err(sb,
|
|
"Link path %s has too many components",
|
|
link_path);
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
strcpy(translated + tl_len, "../");
|
|
tl_len += sizeof("../") - 1;
|
|
}
|
|
}
|
|
|
|
/* Skip drive letter */
|
|
target_start = target;
|
|
while (*target_start && *target_start != ':')
|
|
++target_start;
|
|
|
|
if (!*target_start) {
|
|
ntfs_err(sb, "Link target (%s) missing drive separator",
|
|
target);
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
/* Skip drive separator and leading /, if exists */
|
|
target_start += 1 + (target_start[1] == '/');
|
|
copy_len = target_len - (target_start - target);
|
|
|
|
if (PATH_MAX - tl_len <= copy_len) {
|
|
ntfs_err(sb, "Link target %s too large for buffer (%d <= %d)",
|
|
target_start, PATH_MAX - tl_len, copy_len);
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
/* translated path has a trailing / and target_start does not */
|
|
strcpy(translated + tl_len, target_start);
|
|
tl_len += copy_len;
|
|
if (target_max <= tl_len) {
|
|
ntfs_err(sb, "Target path %s too large for buffer (%d <= %d)",
|
|
translated, target_max, tl_len);
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
strcpy(target, translated);
|
|
err = tl_len;
|
|
|
|
out:
|
|
kfree(link_path_buffer);
|
|
kfree(translated);
|
|
return err;
|
|
}
|
|
|
|
static noinline int ntfs_readlink_hlp(const struct dentry *link_de,
|
|
struct inode *inode, char *buffer,
|
|
int buflen)
|
|
{
|
|
int i, err = -EINVAL;
|
|
struct ntfs_inode *ni = ntfs_i(inode);
|
|
struct super_block *sb = inode->i_sb;
|
|
struct ntfs_sb_info *sbi = sb->s_fs_info;
|
|
u64 size;
|
|
u16 ulen = 0;
|
|
void *to_free = NULL;
|
|
struct REPARSE_DATA_BUFFER *rp;
|
|
const __le16 *uname;
|
|
struct ATTRIB *attr;
|
|
|
|
/* Reparse data present. Try to parse it. */
|
|
static_assert(!offsetof(struct REPARSE_DATA_BUFFER, ReparseTag));
|
|
static_assert(sizeof(u32) == sizeof(rp->ReparseTag));
|
|
|
|
*buffer = 0;
|
|
|
|
attr = ni_find_attr(ni, NULL, NULL, ATTR_REPARSE, NULL, 0, NULL, NULL);
|
|
if (!attr)
|
|
goto out;
|
|
|
|
if (!attr->non_res) {
|
|
rp = resident_data_ex(attr, sizeof(struct REPARSE_DATA_BUFFER));
|
|
if (!rp)
|
|
goto out;
|
|
size = le32_to_cpu(attr->res.data_size);
|
|
} else {
|
|
size = le64_to_cpu(attr->nres.data_size);
|
|
rp = NULL;
|
|
}
|
|
|
|
if (size > sbi->reparse.max_size || size <= sizeof(u32))
|
|
goto out;
|
|
|
|
if (!rp) {
|
|
rp = kmalloc(size, GFP_NOFS);
|
|
if (!rp) {
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
to_free = rp;
|
|
/* Read into temporal buffer. */
|
|
err = ntfs_read_run_nb(sbi, &ni->file.run, 0, rp, size, NULL);
|
|
if (err)
|
|
goto out;
|
|
}
|
|
|
|
/* Microsoft Tag. */
|
|
switch (rp->ReparseTag) {
|
|
case IO_REPARSE_TAG_MOUNT_POINT:
|
|
/* Mount points and junctions. */
|
|
/* Can we use 'Rp->MountPointReparseBuffer.PrintNameLength'? */
|
|
if (size <= offsetof(struct REPARSE_DATA_BUFFER,
|
|
MountPointReparseBuffer.PathBuffer))
|
|
goto out;
|
|
uname = Add2Ptr(rp,
|
|
offsetof(struct REPARSE_DATA_BUFFER,
|
|
MountPointReparseBuffer.PathBuffer) +
|
|
le16_to_cpu(rp->MountPointReparseBuffer
|
|
.PrintNameOffset));
|
|
ulen = le16_to_cpu(rp->MountPointReparseBuffer.PrintNameLength);
|
|
break;
|
|
|
|
case IO_REPARSE_TAG_SYMLINK:
|
|
/* FolderSymbolicLink */
|
|
/* Can we use 'Rp->SymbolicLinkReparseBuffer.PrintNameLength'? */
|
|
if (size <= offsetof(struct REPARSE_DATA_BUFFER,
|
|
SymbolicLinkReparseBuffer.PathBuffer))
|
|
goto out;
|
|
uname = Add2Ptr(
|
|
rp, offsetof(struct REPARSE_DATA_BUFFER,
|
|
SymbolicLinkReparseBuffer.PathBuffer) +
|
|
le16_to_cpu(rp->SymbolicLinkReparseBuffer
|
|
.PrintNameOffset));
|
|
ulen = le16_to_cpu(
|
|
rp->SymbolicLinkReparseBuffer.PrintNameLength);
|
|
break;
|
|
|
|
case IO_REPARSE_TAG_CLOUD:
|
|
case IO_REPARSE_TAG_CLOUD_1:
|
|
case IO_REPARSE_TAG_CLOUD_2:
|
|
case IO_REPARSE_TAG_CLOUD_3:
|
|
case IO_REPARSE_TAG_CLOUD_4:
|
|
case IO_REPARSE_TAG_CLOUD_5:
|
|
case IO_REPARSE_TAG_CLOUD_6:
|
|
case IO_REPARSE_TAG_CLOUD_7:
|
|
case IO_REPARSE_TAG_CLOUD_8:
|
|
case IO_REPARSE_TAG_CLOUD_9:
|
|
case IO_REPARSE_TAG_CLOUD_A:
|
|
case IO_REPARSE_TAG_CLOUD_B:
|
|
case IO_REPARSE_TAG_CLOUD_C:
|
|
case IO_REPARSE_TAG_CLOUD_D:
|
|
case IO_REPARSE_TAG_CLOUD_E:
|
|
case IO_REPARSE_TAG_CLOUD_F:
|
|
err = sizeof("OneDrive") - 1;
|
|
if (err > buflen)
|
|
err = buflen;
|
|
memcpy(buffer, "OneDrive", err);
|
|
goto out;
|
|
|
|
default:
|
|
if (IsReparseTagMicrosoft(rp->ReparseTag)) {
|
|
/* Unknown Microsoft Tag. */
|
|
goto out;
|
|
}
|
|
if (!IsReparseTagNameSurrogate(rp->ReparseTag) ||
|
|
size <= sizeof(struct REPARSE_POINT)) {
|
|
goto out;
|
|
}
|
|
|
|
/* Users tag. */
|
|
uname = Add2Ptr(rp, sizeof(struct REPARSE_POINT));
|
|
ulen = le16_to_cpu(rp->ReparseDataLength) -
|
|
sizeof(struct REPARSE_POINT);
|
|
}
|
|
|
|
/* Convert nlen from bytes to UNICODE chars. */
|
|
ulen >>= 1;
|
|
|
|
/* Check that name is available. */
|
|
if (!ulen || uname + ulen > (__le16 *)Add2Ptr(rp, size))
|
|
goto out;
|
|
|
|
/* If name is already zero terminated then truncate it now. */
|
|
if (!uname[ulen - 1])
|
|
ulen -= 1;
|
|
|
|
err = ntfs_utf16_to_nls(sbi, uname, ulen, buffer, buflen);
|
|
|
|
if (err < 0)
|
|
goto out;
|
|
|
|
/* Translate Windows '\' into Linux '/'. */
|
|
for (i = 0; i < err; i++) {
|
|
if (buffer[i] == '\\')
|
|
buffer[i] = '/';
|
|
}
|
|
|
|
/* Always set last zero. */
|
|
buffer[err] = 0;
|
|
|
|
/* If this is a junction, translate the link target. */
|
|
if (rp->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
|
|
err = ntfs_translate_junction(sb, link_de, buffer, err, buflen);
|
|
|
|
out:
|
|
kfree(to_free);
|
|
return err;
|
|
}
|
|
|
|
static const char *ntfs_get_link(struct dentry *de, struct inode *inode,
|
|
struct delayed_call *done)
|
|
{
|
|
int err;
|
|
char *ret;
|
|
|
|
if (!de)
|
|
return ERR_PTR(-ECHILD);
|
|
|
|
ret = kmalloc(PAGE_SIZE, GFP_NOFS);
|
|
if (!ret)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
err = ntfs_readlink_hlp(de, inode, ret, PAGE_SIZE);
|
|
if (err < 0) {
|
|
kfree(ret);
|
|
return ERR_PTR(err);
|
|
}
|
|
|
|
set_delayed_call(done, kfree_link, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
// clang-format off
|
|
const struct inode_operations ntfs_link_inode_operations = {
|
|
.get_link = ntfs_get_link,
|
|
.setattr = ntfs3_setattr,
|
|
.listxattr = ntfs_listxattr,
|
|
};
|
|
|
|
const struct address_space_operations ntfs_aops = {
|
|
.read_folio = ntfs_read_folio,
|
|
.readahead = ntfs_readahead,
|
|
.writepages = ntfs_writepages,
|
|
.write_begin = ntfs_write_begin,
|
|
.write_end = ntfs_write_end,
|
|
.direct_IO = ntfs_direct_IO,
|
|
.bmap = ntfs_bmap,
|
|
.dirty_folio = block_dirty_folio,
|
|
.migrate_folio = buffer_migrate_folio,
|
|
.invalidate_folio = block_invalidate_folio,
|
|
};
|
|
|
|
const struct address_space_operations ntfs_aops_cmpr = {
|
|
.read_folio = ntfs_read_folio,
|
|
.readahead = ntfs_readahead,
|
|
};
|
|
// clang-format on
|