From nobody Sun Feb 8 07:08:26 2026 Received: from mail-pl1-f194.google.com (mail-pl1-f194.google.com [209.85.214.194]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1B3D83EBF05 for ; Fri, 6 Feb 2026 07:26:42 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.194 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770362803; cv=none; b=ZCM5t8iN85jEk9UUVCFg6YLwUNnuz9OGykjOnK2qPUv2lVO/LwNOoLS3lQo3r1pTAD2P0A/s/YFDqylMT5muvX5uk1JenzUHL8suvlVTXpTFlb8/LTGnyNpjeYDccaY6hJxRdF7Ze/XutHCvSxcANFkwcERXH4bX42bTnSLLlAE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770362803; c=relaxed/simple; bh=LYaXjOrAAALz0ft5ay0dqj1+ANLo1yKDPbO/yu458qg=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=g83r+8zwXdTomyxOmrrQIpYruBcWpsZkEaHvGm34LFnX3MrTAMzimVZMs3cS4hVaOP3j2U83mQ7fzbeQSsUHfgCUHeUq2wODCZiLKrd4VrLPtj8HlAcL/DgaX9Wq3bmCaLUb/uKhUt49qL9klNqK6mC3YSxtmU29w8EqO3N6nSU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=fail (p=quarantine dis=none) header.from=kernel.org; spf=pass smtp.mailfrom=gmail.com; arc=none smtp.client-ip=209.85.214.194 Authentication-Results: smtp.subspace.kernel.org; dmarc=fail (p=quarantine dis=none) header.from=kernel.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Received: by mail-pl1-f194.google.com with SMTP id d9443c01a7336-2a0c09bb78cso15478705ad.0 for ; Thu, 05 Feb 2026 23:26:42 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1770362802; x=1770967602; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=5X9igdNOv1Lnyzs38HrX6K/zdxXaMGWd/HuBAa4R2ns=; b=P89B/pe9ZjXRNRZWfPUo1vnfVQlI9EdJTx6ejO2VbllxBTRb6lUw5neemJauTu24XV ECq43B6G+0lZ06qqmNjLROYjosmV7MSieuCQoFBNBRKPejP4aZ3je26DevDRYR/jB/E8 ArD+kjnm0ZYy7I730IRgn3ug4zenAmiBThRdP9IyYSBe73Bkcisou41X7pAcaIqS0QSA ScSrS7jC5U4MRa1st9eiN4EIplwURSoopWBC+dLXr+zwxFVGAwqp+sTnmG6IAcLPHJiF pm0JY0lKYzV4we+aHqhtUcYclU9y3xmhOSsSeWVW0Ph0DtBCKG3vzDisVbfm3V+/Ghx0 qNBQ== X-Forwarded-Encrypted: i=1; AJvYcCVlD7xy+LpJCw+bCQ7JO9IUl07Ezr/vi67vq9AUroEQ85Sf+kineJ6QbU9vwwZQK17A4Dp2pprsq0HRlNQ=@vger.kernel.org X-Gm-Message-State: AOJu0YxbVTOSGUTByr5j12cQmf5nvwuZS183UYfNkn6V6H+VdMRPXA/o FUcmQ846ywBfdUumho4DS5Nb1EXDpFanswqiR46EYzrA6VBiSPWtiaTM X-Gm-Gg: AZuq6aIGGlTIM/GJ4T7qnFH4PfAUCiAOvyrtcBNFpz3ZY2j4q0yQ1SHajdgnGNfuBV4 H/UQsJy27EnLoe//n/nDslWbk/8nqf+CF+PowXVLX5hW01Z9QbmBI5/Enr2R9MzGdjFJL1hUnMc BfNk0AaX5uTo7qzwUNoPNmSK2H46Xw5W+yea/nyYrV/tWLqilUahT2Y/6KNuHyUH3kaAB7CyK+K HucRcB9z14N9a8BED4k8vpS1SEFMr/nf9aCNFeww2f/fyMreitepL3qNemorb8jiZMru++lNxto g0t7eqzqFuj0b1g0cN13ugbzdjurdogfpueLZGzkw0yuIupJ/vxReBiyALTJOxdwzJs4SX8GeCS 5eJMufg3BjeJN2zpgb+Vr01jt1zL4TTkkzyP454F8p03fdEbtU0mCcnRNoj8cTvK4zQXaIkbwcJ lL7mZLANji6Q+F7m3hJpG4fI8M8Q== X-Received: by 2002:a17:903:2312:b0:2a7:cb46:7069 with SMTP id d9443c01a7336-2a9411d4da7mr49913705ad.25.1770362801331; Thu, 05 Feb 2026 23:26:41 -0800 (PST) Received: from localhost.localdomain ([1.227.206.162]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2a951c7a047sm13575125ad.27.2026.02.05.23.26.37 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 05 Feb 2026 23:26:39 -0800 (PST) From: Namjae Jeon To: viro@zeniv.linux.org.uk, brauner@kernel.org, hch@lst.de, tytso@mit.edu, willy@infradead.org, jack@suse.cz, djwong@kernel.org, dsterba@suse.com, pali@kernel.org, amir73il@gmail.com, xiang@kernel.org Cc: linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, Namjae Jeon , Hyunchul Lee Subject: [PATCH v8 05/17] ntfs: update inode operations Date: Fri, 6 Feb 2026 16:18:48 +0900 Message-Id: <20260206071900.6800-6-linkinjeon@kernel.org> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20260206071900.6800-1-linkinjeon@kernel.org> References: <20260206071900.6800-1-linkinjeon@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add extent inode loading via ntfs_extent_inode_open() and ntfs_inode_attach_all_extents(). Allow dynamic creation of with ntfs_inode_add_attrlist() when the base MFT record overflows. Introduce ntfs_inode_free_space() to move attributes out of the base record on demand. Implement direct attribute I/O through ntfs_inode_attr_pread() and ntfs_inode_attr_pwrite(). Implement .create, .unlink, .mkdir, .rmdir, .rename, .symlink, .mknod, .link callbacks. Introduce ntfs_non_resident_dealloc_clusters() to free clusters of non-resident attributes during inode eviction. Add ntfs_drop_big_inode() logic to safely truncate and deallocate clusters. Acked-by: Christoph Hellwig Signed-off-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/inode.c | 3659 ++++++++++++++++++++++++++++------------------- fs/ntfs/namei.c | 1597 +++++++++++++++++++-- 2 files changed, 3639 insertions(+), 1617 deletions(-) diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index aba1e22db4e9..e822c97f33e8 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -1,33 +1,26 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * inode.c - NTFS kernel inode handling. + * NTFS kernel inode handling. * * Copyright (c) 2001-2014 Anton Altaparmakov and Tuxera Inc. + * Copyright (c) 2025 LG Electronics Co., Ltd. */ =20 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "aops.h" -#include "attrib.h" -#include "bitmap.h" -#include "dir.h" -#include "debug.h" -#include "inode.h" +#include +#include + #include "lcnalloc.h" -#include "malloc.h" -#include "mft.h" #include "time.h" #include "ntfs.h" +#include "index.h" +#include "attrlist.h" +#include "reparse.h" +#include "ea.h" +#include "attrib.h" +#include "iomap.h" +#include "object_id.h" =20 -/** +/* * ntfs_test_inode - compare two (possibly fake) inodes for equality * @vi: vfs inode which to test * @data: data which is being tested with @@ -45,12 +38,12 @@ */ int ntfs_test_inode(struct inode *vi, void *data) { - ntfs_attr *na =3D (ntfs_attr *)data; - ntfs_inode *ni; + struct ntfs_attr *na =3D data; + struct ntfs_inode *ni =3D NTFS_I(vi); =20 if (vi->i_ino !=3D na->mft_no) return 0; - ni =3D NTFS_I(vi); + /* If !NInoAttr(ni), @vi is a normal file or directory inode. */ if (likely(!NInoAttr(ni))) { /* If not looking for a normal inode this is a mismatch. */ @@ -63,14 +56,17 @@ int ntfs_test_inode(struct inode *vi, void *data) if (ni->name_len !=3D na->name_len) return 0; if (na->name_len && memcmp(ni->name, na->name, - na->name_len * sizeof(ntfschar))) + na->name_len * sizeof(__le16))) + return 0; + if (!ni->ext.base_ntfs_ino) return 0; } + /* Match! */ return 1; } =20 -/** +/* * ntfs_init_locked_inode - initialize an inode * @vi: vfs inode to initialize * @data: data which to initialize @vi to @@ -83,31 +79,31 @@ int ntfs_test_inode(struct inode *vi, void *data) * respectively. Although that is not strictly necessary as * ntfs_read_locked_inode() will fill them in later. * - * Return 0 on success and -errno on error. + * Return 0 on success and error. * * NOTE: This function runs with the inode->i_lock spin lock held so it is= not * allowed to sleep. (Hence the GFP_ATOMIC allocation.) */ static int ntfs_init_locked_inode(struct inode *vi, void *data) { - ntfs_attr *na =3D (ntfs_attr *)data; - ntfs_inode *ni =3D NTFS_I(vi); + struct ntfs_attr *na =3D data; + struct ntfs_inode *ni =3D NTFS_I(vi); =20 vi->i_ino =3D na->mft_no; =20 - ni->type =3D na->type; if (na->type =3D=3D AT_INDEX_ALLOCATION) NInoSetMstProtected(ni); + else + ni->type =3D na->type; =20 ni->name =3D na->name; ni->name_len =3D na->name_len; + ni->folio =3D NULL; + atomic_set(&ni->count, 1); =20 /* If initializing a normal inode, we are done. */ - if (likely(na->type =3D=3D AT_UNUSED)) { - BUG_ON(na->name); - BUG_ON(na->name_len); + if (likely(na->type =3D=3D AT_UNUSED)) return 0; - } =20 /* It is a fake inode. */ NInoSetAttr(ni); @@ -122,9 +118,8 @@ static int ntfs_init_locked_inode(struct inode *vi, voi= d *data) if (na->name_len && na->name !=3D I30) { unsigned int i; =20 - BUG_ON(!na->name); - i =3D na->name_len * sizeof(ntfschar); - ni->name =3D kmalloc(i + sizeof(ntfschar), GFP_ATOMIC); + i =3D na->name_len * sizeof(__le16); + ni->name =3D kmalloc(i + sizeof(__le16), GFP_ATOMIC); if (!ni->name) return -ENOMEM; memcpy(ni->name, na->name, i); @@ -138,7 +133,7 @@ static int ntfs_read_locked_attr_inode(struct inode *ba= se_vi, struct inode *vi); static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi); =20 -/** +/* * ntfs_iget - obtain a struct inode corresponding to a specific normal in= ode * @sb: super block of mounted volume * @mft_no: mft record number / inode number to obtain @@ -158,7 +153,7 @@ struct inode *ntfs_iget(struct super_block *sb, unsigne= d long mft_no) { struct inode *vi; int err; - ntfs_attr na; + struct ntfs_attr na; =20 na.mft_no =3D mft_no; na.type =3D AT_UNUSED; @@ -173,7 +168,7 @@ struct inode *ntfs_iget(struct super_block *sb, unsigne= d long mft_no) err =3D 0; =20 /* If this is a freshly allocated inode, need to read it now. */ - if (vi->i_state & I_NEW) { + if (inode_state_read_once(vi) & I_NEW) { err =3D ntfs_read_locked_inode(vi); unlock_new_inode(vi); } @@ -188,7 +183,7 @@ struct inode *ntfs_iget(struct super_block *sb, unsigne= d long mft_no) return vi; } =20 -/** +/* * ntfs_attr_iget - obtain a struct inode corresponding to an attribute * @base_vi: vfs base inode containing the attribute * @type: attribute type @@ -211,15 +206,15 @@ struct inode *ntfs_iget(struct super_block *sb, unsig= ned long mft_no) * value with IS_ERR() and if true, the function failed and the error code= is * obtained from PTR_ERR(). */ -struct inode *ntfs_attr_iget(struct inode *base_vi, ATTR_TYPE type, - ntfschar *name, u32 name_len) +struct inode *ntfs_attr_iget(struct inode *base_vi, __le32 type, + __le16 *name, u32 name_len) { struct inode *vi; int err; - ntfs_attr na; + struct ntfs_attr na; =20 /* Make sure no one calls ntfs_attr_iget() for indices. */ - BUG_ON(type =3D=3D AT_INDEX_ALLOCATION); + WARN_ON(type =3D=3D AT_INDEX_ALLOCATION); =20 na.mft_no =3D base_vi->i_ino; na.type =3D type; @@ -230,11 +225,10 @@ struct inode *ntfs_attr_iget(struct inode *base_vi, A= TTR_TYPE type, ntfs_init_locked_inode, &na); if (unlikely(!vi)) return ERR_PTR(-ENOMEM); - err =3D 0; =20 /* If this is a freshly allocated inode, need to read it now. */ - if (vi->i_state & I_NEW) { + if (inode_state_read_once(vi) & I_NEW) { err =3D ntfs_read_locked_attr_inode(base_vi, vi); unlock_new_inode(vi); } @@ -250,7 +244,7 @@ struct inode *ntfs_attr_iget(struct inode *base_vi, ATT= R_TYPE type, return vi; } =20 -/** +/* * ntfs_index_iget - obtain a struct inode corresponding to an index * @base_vi: vfs base inode containing the index related attributes * @name: Unicode name of the index @@ -269,12 +263,12 @@ struct inode *ntfs_attr_iget(struct inode *base_vi, A= TTR_TYPE type, * value with IS_ERR() and if true, the function failed and the error code= is * obtained from PTR_ERR(). */ -struct inode *ntfs_index_iget(struct inode *base_vi, ntfschar *name, +struct inode *ntfs_index_iget(struct inode *base_vi, __le16 *name, u32 name_len) { struct inode *vi; int err; - ntfs_attr na; + struct ntfs_attr na; =20 na.mft_no =3D base_vi->i_ino; na.type =3D AT_INDEX_ALLOCATION; @@ -289,7 +283,7 @@ struct inode *ntfs_index_iget(struct inode *base_vi, nt= fschar *name, err =3D 0; =20 /* If this is a freshly allocated inode, need to read it now. */ - if (vi->i_state & I_NEW) { + if (inode_state_read_once(vi) & I_NEW) { err =3D ntfs_read_locked_index_inode(base_vi, vi); unlock_new_inode(vi); } @@ -307,12 +301,14 @@ struct inode *ntfs_index_iget(struct inode *base_vi, = ntfschar *name, =20 struct inode *ntfs_alloc_big_inode(struct super_block *sb) { - ntfs_inode *ni; + struct ntfs_inode *ni; =20 ntfs_debug("Entering."); ni =3D alloc_inode_sb(sb, ntfs_big_inode_cache, GFP_NOFS); if (likely(ni !=3D NULL)) { ni->state =3D 0; + ni->type =3D 0; + ni->mft_no =3D 0; return VFS_I(ni); } ntfs_error(sb, "Allocation of NTFS big inode structure failed."); @@ -324,9 +320,96 @@ void ntfs_free_big_inode(struct inode *inode) kmem_cache_free(ntfs_big_inode_cache, NTFS_I(inode)); } =20 -static inline ntfs_inode *ntfs_alloc_extent_inode(void) +static int ntfs_non_resident_dealloc_clusters(struct ntfs_inode *ni) +{ + struct super_block *sb =3D ni->vol->sb; + struct ntfs_attr_search_ctx *actx; + int err =3D 0; + + actx =3D ntfs_attr_get_search_ctx(ni, NULL); + if (!actx) + return -ENOMEM; + WARN_ON(actx->mrec->link_count !=3D 0); + + /** + * ntfs_truncate_vfs cannot be called in evict() context due + * to some limitations, which are the @ni vfs inode is marked + * with I_FREEING, and etc. + */ + if (NInoRunlistDirty(ni)) { + err =3D ntfs_cluster_free_from_rl(ni->vol, ni->runlist.rl); + if (err) + ntfs_error(sb, + "Failed to free clusters. Leaving inconsistent metadata.\n"); + } + + while ((err =3D ntfs_attrs_walk(actx)) =3D=3D 0) { + if (actx->attr->non_resident && + (!NInoRunlistDirty(ni) || actx->attr->type !=3D AT_DATA)) { + struct runlist_element *rl; + size_t new_rl_count; + + rl =3D ntfs_mapping_pairs_decompress(ni->vol, actx->attr, NULL, + &new_rl_count); + if (IS_ERR(rl)) { + err =3D PTR_ERR(rl); + ntfs_error(sb, + "Failed to decompress runlist. Leaving inconsistent metadata.\n"); + continue; + } + + err =3D ntfs_cluster_free_from_rl(ni->vol, rl); + if (err) + ntfs_error(sb, + "Failed to free attribute clusters. Leaving inconsistent metadata.= \n"); + kvfree(rl); + } + } + + ntfs_release_dirty_clusters(ni->vol, ni->i_dealloc_clusters); + ntfs_attr_put_search_ctx(actx); + return err; +} + +int ntfs_drop_big_inode(struct inode *inode) +{ + struct ntfs_inode *ni =3D NTFS_I(inode); + + if (!inode_unhashed(inode) && inode_state_read_once(inode) & I_SYNC) { + if (ni->type =3D=3D AT_DATA || ni->type =3D=3D AT_INDEX_ALLOCATION) { + if (!inode->i_nlink) { + struct ntfs_inode *ni =3D NTFS_I(inode); + + if (ni->data_size =3D=3D 0) + return 0; + + /* To avoid evict_inode call simultaneously */ + atomic_inc(&inode->i_count); + spin_unlock(&inode->i_lock); + + truncate_setsize(VFS_I(ni), 0); + ntfs_truncate_vfs(VFS_I(ni), 0, 1); + + sb_start_intwrite(inode->i_sb); + i_size_write(inode, 0); + ni->allocated_size =3D ni->initialized_size =3D ni->data_size =3D 0; + + truncate_inode_pages_final(inode->i_mapping); + sb_end_intwrite(inode->i_sb); + + spin_lock(&inode->i_lock); + atomic_dec(&inode->i_count); + } + } + return 0; + } + + return inode_generic_drop(inode); +} + +static inline struct ntfs_inode *ntfs_alloc_extent_inode(void) { - ntfs_inode *ni; + struct ntfs_inode *ni; =20 ntfs_debug("Entering."); ni =3D kmem_cache_alloc(ntfs_inode_cache, GFP_NOFS); @@ -338,22 +421,28 @@ static inline ntfs_inode *ntfs_alloc_extent_inode(voi= d) return NULL; } =20 -static void ntfs_destroy_extent_inode(ntfs_inode *ni) +static void ntfs_destroy_extent_inode(struct ntfs_inode *ni) { ntfs_debug("Entering."); - BUG_ON(ni->page); + if (!atomic_dec_and_test(&ni->count)) - BUG(); + WARN_ON(1); + if (ni->folio) + folio_put(ni->folio); + kfree(ni->mrec); kmem_cache_free(ntfs_inode_cache, ni); } =20 +static struct lock_class_key attr_inode_mrec_lock_class; +static struct lock_class_key attr_list_inode_mrec_lock_class; + /* * The attribute runlist lock has separate locking rules from the * normal runlist lock, so split the two lock-classes: */ static struct lock_class_key attr_list_rl_lock_class; =20 -/** +/* * __ntfs_init_inode - initialize ntfs specific part of an inode * @sb: super block of mounted volume * @ni: freshly allocated ntfs inode which to initialize @@ -362,10 +451,8 @@ static struct lock_class_key attr_list_rl_lock_class; * * NOTE: ni->mft_no, ni->state, ni->type, ni->name, and ni->name_len are l= eft * untouched. Make sure to initialize them elsewhere. - * - * Return zero on success and -ENOMEM on error. */ -void __ntfs_init_inode(struct super_block *sb, ntfs_inode *ni) +void __ntfs_init_inode(struct super_block *sb, struct ntfs_inode *ni) { ntfs_debug("Entering."); rwlock_init(&ni->size_lock); @@ -375,13 +462,21 @@ void __ntfs_init_inode(struct super_block *sb, ntfs_i= node *ni) ni->vol =3D NTFS_SB(sb); ntfs_init_runlist(&ni->runlist); mutex_init(&ni->mrec_lock); - ni->page =3D NULL; - ni->page_ofs =3D 0; + if (ni->type =3D=3D AT_ATTRIBUTE_LIST) { + lockdep_set_class(&ni->mrec_lock, + &attr_list_inode_mrec_lock_class); + lockdep_set_class(&ni->runlist.lock, + &attr_list_rl_lock_class); + } else if (NInoAttr(ni)) { + lockdep_set_class(&ni->mrec_lock, + &attr_inode_mrec_lock_class); + } + + ni->folio =3D NULL; + ni->folio_ofs =3D 0; + ni->mrec =3D NULL; ni->attr_list_size =3D 0; ni->attr_list =3D NULL; - ntfs_init_runlist(&ni->attr_list_rl); - lockdep_set_class(&ni->attr_list_rl.lock, - &attr_list_rl_lock_class); ni->itype.index.block_size =3D 0; ni->itype.index.vcn_size =3D 0; ni->itype.index.collation_rule =3D 0; @@ -390,6 +485,11 @@ void __ntfs_init_inode(struct super_block *sb, ntfs_in= ode *ni) mutex_init(&ni->extent_lock); ni->nr_extents =3D 0; ni->ext.base_ntfs_ino =3D NULL; + ni->flags =3D 0; + ni->mft_lcn[0] =3D LCN_RL_NOT_MAPPED; + ni->mft_lcn_count =3D 0; + ni->target =3D NULL; + ni->i_dealloc_clusters =3D 0; } =20 /* @@ -399,10 +499,10 @@ void __ntfs_init_inode(struct super_block *sb, ntfs_i= node *ni) */ static struct lock_class_key extent_inode_mrec_lock_key; =20 -inline ntfs_inode *ntfs_new_extent_inode(struct super_block *sb, +inline struct ntfs_inode *ntfs_new_extent_inode(struct super_block *sb, unsigned long mft_no) { - ntfs_inode *ni =3D ntfs_alloc_extent_inode(); + struct ntfs_inode *ni =3D ntfs_alloc_extent_inode(); =20 ntfs_debug("Entering."); if (likely(ni !=3D NULL)) { @@ -416,7 +516,7 @@ inline ntfs_inode *ntfs_new_extent_inode(struct super_b= lock *sb, return ni; } =20 -/** +/* * ntfs_is_extended_system_file - check if a file is in the $Extend direct= ory * @ctx: initialized attribute search context * @@ -425,11 +525,13 @@ inline ntfs_inode *ntfs_new_extent_inode(struct super= _block *sb, * directory. * * Return values: + * 3: file is $ObjId in $Extend directory + * 2: file is $Reparse in $Extend directory * 1: file is in $Extend directory * 0: file is not in $Extend directory * -errno: failed to determine if the file is in the $Extend directory */ -static int ntfs_is_extended_system_file(ntfs_attr_search_ctx *ctx) +static int ntfs_is_extended_system_file(struct ntfs_attr_search_ctx *ctx) { int nr_links, err; =20 @@ -442,8 +544,8 @@ static int ntfs_is_extended_system_file(ntfs_attr_searc= h_ctx *ctx) /* Loop through all hard links. */ while (!(err =3D ntfs_attr_lookup(AT_FILE_NAME, NULL, 0, 0, 0, NULL, 0, ctx))) { - FILE_NAME_ATTR *file_name_attr; - ATTR_RECORD *attr =3D ctx->attr; + struct file_name_attr *file_name_attr; + struct attr_record *attr =3D ctx->attr; u8 *p, *p2; =20 nr_links--; @@ -451,51 +553,96 @@ static int ntfs_is_extended_system_file(ntfs_attr_sea= rch_ctx *ctx) * Maximum sanity checking as we are called on an inode that * we suspect might be corrupt. */ - p =3D (u8*)attr + le32_to_cpu(attr->length); - if (p < (u8*)ctx->mrec || (u8*)p > (u8*)ctx->mrec + + p =3D (u8 *)attr + le32_to_cpu(attr->length); + if (p < (u8 *)ctx->mrec || (u8 *)p > (u8 *)ctx->mrec + le32_to_cpu(ctx->mrec->bytes_in_use)) { err_corrupt_attr: - ntfs_error(ctx->ntfs_ino->vol->sb, "Corrupt file name " - "attribute. You should run chkdsk."); + ntfs_error(ctx->ntfs_ino->vol->sb, + "Corrupt file name attribute. You should run chkdsk."); return -EIO; } if (attr->non_resident) { - ntfs_error(ctx->ntfs_ino->vol->sb, "Non-resident file " - "name. You should run chkdsk."); + ntfs_error(ctx->ntfs_ino->vol->sb, + "Non-resident file name. You should run chkdsk."); return -EIO; } if (attr->flags) { - ntfs_error(ctx->ntfs_ino->vol->sb, "File name with " - "invalid flags. You should run " - "chkdsk."); + ntfs_error(ctx->ntfs_ino->vol->sb, + "File name with invalid flags. You should run chkdsk."); return -EIO; } if (!(attr->data.resident.flags & RESIDENT_ATTR_IS_INDEXED)) { - ntfs_error(ctx->ntfs_ino->vol->sb, "Unindexed file " - "name. You should run chkdsk."); + ntfs_error(ctx->ntfs_ino->vol->sb, + "Unindexed file name. You should run chkdsk."); return -EIO; } - file_name_attr =3D (FILE_NAME_ATTR*)((u8*)attr + + file_name_attr =3D (struct file_name_attr *)((u8 *)attr + le16_to_cpu(attr->data.resident.value_offset)); p2 =3D (u8 *)file_name_attr + le32_to_cpu(attr->data.resident.value_leng= th); - if (p2 < (u8*)attr || p2 > p) + if (p2 < (u8 *)attr || p2 > p) goto err_corrupt_attr; /* This attribute is ok, but is it in the $Extend directory? */ - if (MREF_LE(file_name_attr->parent_directory) =3D=3D FILE_Extend) + if (MREF_LE(file_name_attr->parent_directory) =3D=3D FILE_Extend) { + unsigned char *s; + + s =3D ntfs_attr_name_get(ctx->ntfs_ino->vol, + file_name_attr->file_name, + file_name_attr->file_name_length); + if (!s) + return 1; + if (!strcmp("$Reparse", s)) { + ntfs_attr_name_free(&s); + return 2; /* it's reparse point file */ + } + if (!strcmp("$ObjId", s)) { + ntfs_attr_name_free(&s); + return 3; /* it's object id file */ + } + ntfs_attr_name_free(&s); return 1; /* YES, it's an extended system file. */ + } } if (unlikely(err !=3D -ENOENT)) return err; if (unlikely(nr_links)) { - ntfs_error(ctx->ntfs_ino->vol->sb, "Inode hard link count " - "doesn't match number of name attributes. You " - "should run chkdsk."); + ntfs_error(ctx->ntfs_ino->vol->sb, + "Inode hard link count doesn't match number of name attributes. You sho= uld run chkdsk."); return -EIO; } return 0; /* NO, it is not an extended system file. */ } =20 -/** +static struct lock_class_key ntfs_dir_inval_lock_key; + +void ntfs_set_vfs_operations(struct inode *inode, mode_t mode, dev_t dev) +{ + if (S_ISDIR(mode)) { + if (!NInoAttr(NTFS_I(inode))) { + inode->i_op =3D &ntfs_dir_inode_ops; + inode->i_fop =3D &ntfs_dir_ops; + } + inode->i_mapping->a_ops =3D &ntfs_aops; + lockdep_set_class(&inode->i_mapping->invalidate_lock, + &ntfs_dir_inval_lock_key); + } else if (S_ISLNK(mode)) { + inode->i_op =3D &ntfs_symlink_inode_operations; + inode->i_mapping->a_ops =3D &ntfs_aops; + } else if (S_ISCHR(mode) || S_ISBLK(mode) || S_ISFIFO(mode) || S_ISSOCK(m= ode)) { + inode->i_op =3D &ntfs_special_inode_operations; + init_special_inode(inode, inode->i_mode, dev); + } else { + if (!NInoAttr(NTFS_I(inode))) { + inode->i_op =3D &ntfs_file_inode_ops; + inode->i_fop =3D &ntfs_file_ops; + } + if (inode->i_ino =3D=3D FILE_MFT) + inode->i_mapping->a_ops =3D &ntfs_mft_aops; + else + inode->i_mapping->a_ops =3D &ntfs_aops; + } +} + +/* * ntfs_read_locked_inode - read an inode from its device * @vi: inode to read * @@ -518,26 +665,38 @@ static int ntfs_is_extended_system_file(ntfs_attr_sea= rch_ctx *ctx) * we need to do that using the IS_* macros defined in include/linux/fs= .h. * In any case ntfs_read_locked_inode() has nothing to do with i_flags. * - * Return 0 on success and -errno on error. In the error case, the inode = will - * have had make_bad_inode() executed on it. + * Return 0 on success and -errno on error. */ static int ntfs_read_locked_inode(struct inode *vi) { - ntfs_volume *vol =3D NTFS_SB(vi->i_sb); - ntfs_inode *ni; - struct inode *bvi; - MFT_RECORD *m; - ATTR_RECORD *a; - STANDARD_INFORMATION *si; - ntfs_attr_search_ctx *ctx; + struct ntfs_volume *vol =3D NTFS_SB(vi->i_sb); + struct ntfs_inode *ni; + struct mft_record *m; + struct attr_record *a; + struct standard_information *si; + struct ntfs_attr_search_ctx *ctx; int err =3D 0; + __le16 *name =3D I30; + unsigned int name_len =3D 4, flags =3D 0; + int extend_sys =3D 0; + dev_t dev =3D 0; + bool vol_err =3D true; =20 ntfs_debug("Entering for i_ino 0x%lx.", vi->i_ino); =20 - /* Setup the generic vfs inode parts now. */ - vi->i_uid =3D vol->uid; - vi->i_gid =3D vol->gid; - vi->i_mode =3D 0; + if (uid_valid(vol->uid)) { + vi->i_uid =3D vol->uid; + flags |=3D NTFS_VOL_UID; + } else + vi->i_uid =3D GLOBAL_ROOT_UID; + + if (gid_valid(vol->gid)) { + vi->i_gid =3D vol->gid; + flags |=3D NTFS_VOL_GID; + } else + vi->i_gid =3D GLOBAL_ROOT_GID; + + vi->i_mode =3D 0777; =20 /* * Initialize the ntfs specific part of @vi special casing @@ -552,6 +711,7 @@ static int ntfs_read_locked_inode(struct inode *vi) err =3D PTR_ERR(m); goto err_out; } + ctx =3D ntfs_attr_get_search_ctx(ni, m); if (!ctx) { err =3D -ENOMEM; @@ -559,9 +719,11 @@ static int ntfs_read_locked_inode(struct inode *vi) } =20 if (!(m->flags & MFT_RECORD_IN_USE)) { - ntfs_error(vi->i_sb, "Inode is not in use!"); + err =3D -ENOENT; + vol_err =3D false; goto unm_err_out; } + if (m->base_mft_record) { ntfs_error(vi->i_sb, "Inode is an extent inode!"); goto unm_err_out; @@ -570,61 +732,28 @@ static int ntfs_read_locked_inode(struct inode *vi) /* Transfer information from mft record into vfs and ntfs inodes. */ vi->i_generation =3D ni->seq_no =3D le16_to_cpu(m->sequence_number); =20 - /* - * FIXME: Keep in mind that link_count is two for files which have both - * a long file name and a short file name as separate entries, so if - * we are hiding short file names this will be too high. Either we need - * to account for the short file names by subtracting them or we need - * to make sure we delete files even though i_nlink is not zero which - * might be tricky due to vfs interactions. Need to think about this - * some more when implementing the unlink command. - */ + if (le16_to_cpu(m->link_count) < 1) { + ntfs_error(vi->i_sb, "Inode link count is 0!"); + goto unm_err_out; + } set_nlink(vi, le16_to_cpu(m->link_count)); - /* - * FIXME: Reparse points can have the directory bit set even though - * they would be S_IFLNK. Need to deal with this further below when we - * implement reparse points / symbolic links but it will do for now. - * Also if not a directory, it could be something else, rather than - * a regular file. But again, will do for now. - */ - /* Everyone gets all permissions. */ - vi->i_mode |=3D S_IRWXUGO; + /* If read-only, no one gets write permissions. */ if (IS_RDONLY(vi)) - vi->i_mode &=3D ~S_IWUGO; - if (m->flags & MFT_RECORD_IS_DIRECTORY) { - vi->i_mode |=3D S_IFDIR; - /* - * Apply the directory permissions mask set in the mount - * options. - */ - vi->i_mode &=3D ~vol->dmask; - /* Things break without this kludge! */ - if (vi->i_nlink > 1) - set_nlink(vi, 1); - } else { - vi->i_mode |=3D S_IFREG; - /* Apply the file permissions mask set in the mount options. */ - vi->i_mode &=3D ~vol->fmask; - } + vi->i_mode &=3D ~0222; + /* * Find the standard information attribute in the mft record. At this * stage we haven't setup the attribute list stuff yet, so this could * in fact fail if the standard information is in an extent record, but * I don't think this actually ever happens. */ + ntfs_attr_reinit_search_ctx(ctx); err =3D ntfs_attr_lookup(AT_STANDARD_INFORMATION, NULL, 0, 0, 0, NULL, 0, ctx); if (unlikely(err)) { - if (err =3D=3D -ENOENT) { - /* - * TODO: We should be performing a hot fix here (if the - * recover mount option is set) by creating a new - * attribute. - */ - ntfs_error(vi->i_sb, "$STANDARD_INFORMATION attribute " - "is missing."); - } + if (err =3D=3D -ENOENT) + ntfs_error(vi->i_sb, "$STANDARD_INFORMATION attribute is missing."); goto unm_err_out; } a =3D ctx->attr; @@ -635,7 +764,7 @@ static int ntfs_read_locked_inode(struct inode *vi) ntfs_error(vi->i_sb, "Corrupt standard information attribute in inode."); goto unm_err_out; } - si =3D (STANDARD_INFORMATION*)((u8*)a + + si =3D (struct standard_information *)((u8 *)a + le16_to_cpu(a->data.resident.value_offset)); =20 /* Transfer information from the standard information into vi. */ @@ -648,6 +777,8 @@ static int ntfs_read_locked_inode(struct inode *vi) * mtime is the last change of the data within the file. Not changed * when only metadata is changed, e.g. a rename doesn't affect mtime. */ + ni->i_crtime =3D ntfs2utc(si->creation_time); + inode_set_mtime_to_ts(vi, ntfs2utc(si->last_data_change_time)); /* * ctime is the last change of the metadata of the file. This obviously @@ -660,135 +791,143 @@ static int ntfs_read_locked_inode(struct inode *vi) * for example but changed whenever the file is written to. */ inode_set_atime_to_ts(vi, ntfs2utc(si->last_access_time)); + ni->flags =3D si->file_attributes; =20 /* Find the attribute list attribute if present. */ ntfs_attr_reinit_search_ctx(ctx); err =3D ntfs_attr_lookup(AT_ATTRIBUTE_LIST, NULL, 0, 0, 0, NULL, 0, ctx); if (err) { if (unlikely(err !=3D -ENOENT)) { - ntfs_error(vi->i_sb, "Failed to lookup attribute list " - "attribute."); + ntfs_error(vi->i_sb, "Failed to lookup attribute list attribute."); goto unm_err_out; } - } else /* if (!err) */ { + } else { if (vi->i_ino =3D=3D FILE_MFT) goto skip_attr_list_load; ntfs_debug("Attribute list found in inode 0x%lx.", vi->i_ino); NInoSetAttrList(ni); a =3D ctx->attr; if (a->flags & ATTR_COMPRESSION_MASK) { - ntfs_error(vi->i_sb, "Attribute list attribute is " - "compressed."); + ntfs_error(vi->i_sb, + "Attribute list attribute is compressed."); goto unm_err_out; } if (a->flags & ATTR_IS_ENCRYPTED || a->flags & ATTR_IS_SPARSE) { if (a->non_resident) { - ntfs_error(vi->i_sb, "Non-resident attribute " - "list attribute is encrypted/" - "sparse."); + ntfs_error(vi->i_sb, + "Non-resident attribute list attribute is encrypted/sparse."); goto unm_err_out; } - ntfs_warning(vi->i_sb, "Resident attribute list " - "attribute in inode 0x%lx is marked " - "encrypted/sparse which is not true. " - "However, Windows allows this and " - "chkdsk does not detect or correct it " - "so we will just ignore the invalid " - "flags and pretend they are not set.", - vi->i_ino); + ntfs_warning(vi->i_sb, + "Resident attribute list attribute in inode 0x%lx is marked encrypted/= sparse which is not true. However, Windows allows this and chkdsk does not= detect or correct it so we will just ignore the invalid flags and pretend = they are not set.", + vi->i_ino); } /* Now allocate memory for the attribute list. */ ni->attr_list_size =3D (u32)ntfs_attr_size(a); - ni->attr_list =3D ntfs_malloc_nofs(ni->attr_list_size); + if (!ni->attr_list_size) { + ntfs_error(vi->i_sb, "Attr_list_size is zero"); + goto unm_err_out; + } + ni->attr_list =3D kvzalloc(ni->attr_list_size, GFP_NOFS); if (!ni->attr_list) { - ntfs_error(vi->i_sb, "Not enough memory to allocate " - "buffer for attribute list."); + ntfs_error(vi->i_sb, + "Not enough memory to allocate buffer for attribute list."); err =3D -ENOMEM; goto unm_err_out; } if (a->non_resident) { NInoSetAttrListNonResident(ni); if (a->data.non_resident.lowest_vcn) { - ntfs_error(vi->i_sb, "Attribute list has non " - "zero lowest_vcn."); - goto unm_err_out; - } - /* - * Setup the runlist. No need for locking as we have - * exclusive access to the inode at this time. - */ - ni->attr_list_rl.rl =3D ntfs_mapping_pairs_decompress(vol, - a, NULL); - if (IS_ERR(ni->attr_list_rl.rl)) { - err =3D PTR_ERR(ni->attr_list_rl.rl); - ni->attr_list_rl.rl =3D NULL; - ntfs_error(vi->i_sb, "Mapping pairs " - "decompression failed."); + ntfs_error(vi->i_sb, "Attribute list has non zero lowest_vcn."); goto unm_err_out; } + /* Now load the attribute list. */ - if ((err =3D load_attribute_list(vol, &ni->attr_list_rl, - ni->attr_list, ni->attr_list_size, - sle64_to_cpu(a->data.non_resident. - initialized_size)))) { - ntfs_error(vi->i_sb, "Failed to load " - "attribute list attribute."); + err =3D load_attribute_list(ni, ni->attr_list, ni->attr_list_size); + if (err) { + ntfs_error(vi->i_sb, "Failed to load attribute list attribute."); goto unm_err_out; } } else /* if (!a->non_resident) */ { - if ((u8*)a + le16_to_cpu(a->data.resident.value_offset) + if ((u8 *)a + le16_to_cpu(a->data.resident.value_offset) + le32_to_cpu( a->data.resident.value_length) > - (u8*)ctx->mrec + vol->mft_record_size) { - ntfs_error(vi->i_sb, "Corrupt attribute list " - "in inode."); + (u8 *)ctx->mrec + vol->mft_record_size) { + ntfs_error(vi->i_sb, "Corrupt attribute list in inode."); goto unm_err_out; } /* Now copy the attribute list. */ - memcpy(ni->attr_list, (u8*)a + le16_to_cpu( + memcpy(ni->attr_list, (u8 *)a + le16_to_cpu( a->data.resident.value_offset), le32_to_cpu( a->data.resident.value_length)); } } skip_attr_list_load: + err =3D ntfs_attr_lookup(AT_EA_INFORMATION, NULL, 0, 0, 0, NULL, 0, ctx); + if (!err) + NInoSetHasEA(ni); + + ntfs_ea_get_wsl_inode(vi, &dev, flags); + + if (m->flags & MFT_RECORD_IS_DIRECTORY) { + vi->i_mode |=3D S_IFDIR; + /* + * Apply the directory permissions mask set in the mount + * options. + */ + vi->i_mode &=3D ~vol->dmask; + /* Things break without this kludge! */ + if (vi->i_nlink > 1) + set_nlink(vi, 1); + } else { + if (ni->flags & FILE_ATTR_REPARSE_POINT) { + unsigned int mode; + + mode =3D ntfs_make_symlink(ni); + if (mode) + vi->i_mode |=3D mode; + else { + vi->i_mode &=3D ~S_IFLNK; + vi->i_mode |=3D S_IFREG; + } + } else + vi->i_mode |=3D S_IFREG; + /* Apply the file permissions mask set in the mount options. */ + vi->i_mode &=3D ~vol->fmask; + } + /* * If an attribute list is present we now have the attribute list value * in ntfs_ino->attr_list and it is ntfs_ino->attr_list_size bytes. */ if (S_ISDIR(vi->i_mode)) { - loff_t bvi_size; - ntfs_inode *bni; - INDEX_ROOT *ir; + struct index_root *ir; u8 *ir_end, *index_end; =20 +view_index_meta: /* It is a directory, find index root attribute. */ ntfs_attr_reinit_search_ctx(ctx); - err =3D ntfs_attr_lookup(AT_INDEX_ROOT, I30, 4, CASE_SENSITIVE, + err =3D ntfs_attr_lookup(AT_INDEX_ROOT, name, name_len, CASE_SENSITIVE, 0, NULL, 0, ctx); if (unlikely(err)) { - if (err =3D=3D -ENOENT) { - // FIXME: File is corrupt! Hot-fix with empty - // index root attribute if recovery option is - // set. - ntfs_error(vi->i_sb, "$INDEX_ROOT attribute " - "is missing."); - } + if (err =3D=3D -ENOENT) + ntfs_error(vi->i_sb, "$INDEX_ROOT attribute is missing."); goto unm_err_out; } a =3D ctx->attr; /* Set up the state. */ if (unlikely(a->non_resident)) { - ntfs_error(vol->sb, "$INDEX_ROOT attribute is not " - "resident."); + ntfs_error(vol->sb, + "$INDEX_ROOT attribute is not resident."); goto unm_err_out; } /* Ensure the attribute name is placed before the value. */ if (unlikely(a->name_length && (le16_to_cpu(a->name_offset) >=3D le16_to_cpu(a->data.resident.value_offset)))) { - ntfs_error(vol->sb, "$INDEX_ROOT attribute name is " - "placed after the attribute value."); + ntfs_error(vol->sb, + "$INDEX_ROOT attribute name is placed after the attribute value."); goto unm_err_out; } /* @@ -797,66 +936,75 @@ static int ntfs_read_locked_inode(struct inode *vi) * encrypted. However index root cannot be both compressed and * encrypted. */ - if (a->flags & ATTR_COMPRESSION_MASK) + if (a->flags & ATTR_COMPRESSION_MASK) { NInoSetCompressed(ni); + ni->flags |=3D FILE_ATTR_COMPRESSED; + } if (a->flags & ATTR_IS_ENCRYPTED) { if (a->flags & ATTR_COMPRESSION_MASK) { - ntfs_error(vi->i_sb, "Found encrypted and " - "compressed attribute."); + ntfs_error(vi->i_sb, "Found encrypted and compressed attribute."); goto unm_err_out; } NInoSetEncrypted(ni); + ni->flags |=3D FILE_ATTR_ENCRYPTED; } - if (a->flags & ATTR_IS_SPARSE) + if (a->flags & ATTR_IS_SPARSE) { NInoSetSparse(ni); - ir =3D (INDEX_ROOT*)((u8*)a + + ni->flags |=3D FILE_ATTR_SPARSE_FILE; + } + ir =3D (struct index_root *)((u8 *)a + le16_to_cpu(a->data.resident.value_offset)); - ir_end =3D (u8*)ir + le32_to_cpu(a->data.resident.value_length); - if (ir_end > (u8*)ctx->mrec + vol->mft_record_size) { - ntfs_error(vi->i_sb, "$INDEX_ROOT attribute is " - "corrupt."); + ir_end =3D (u8 *)ir + le32_to_cpu(a->data.resident.value_length); + if (ir_end > (u8 *)ctx->mrec + vol->mft_record_size) { + ntfs_error(vi->i_sb, "$INDEX_ROOT attribute is corrupt."); goto unm_err_out; } - index_end =3D (u8*)&ir->index + + index_end =3D (u8 *)&ir->index + le32_to_cpu(ir->index.index_length); if (index_end > ir_end) { ntfs_error(vi->i_sb, "Directory index is corrupt."); goto unm_err_out; } - if (ir->type !=3D AT_FILE_NAME) { - ntfs_error(vi->i_sb, "Indexed attribute is not " - "$FILE_NAME."); - goto unm_err_out; - } - if (ir->collation_rule !=3D COLLATION_FILE_NAME) { - ntfs_error(vi->i_sb, "Index collation rule is not " - "COLLATION_FILE_NAME."); - goto unm_err_out; + + if (extend_sys) { + if (ir->type) { + ntfs_error(vi->i_sb, "Indexed attribute is not zero."); + goto unm_err_out; + } + } else { + if (ir->type !=3D AT_FILE_NAME) { + ntfs_error(vi->i_sb, "Indexed attribute is not $FILE_NAME."); + goto unm_err_out; + } + + if (ir->collation_rule !=3D COLLATION_FILE_NAME) { + ntfs_error(vi->i_sb, + "Index collation rule is not COLLATION_FILE_NAME."); + goto unm_err_out; + } } + ni->itype.index.collation_rule =3D ir->collation_rule; ni->itype.index.block_size =3D le32_to_cpu(ir->index_block_size); if (ni->itype.index.block_size & (ni->itype.index.block_size - 1)) { - ntfs_error(vi->i_sb, "Index block size (%u) is not a " - "power of two.", + ntfs_error(vi->i_sb, "Index block size (%u) is not a power of two.", ni->itype.index.block_size); goto unm_err_out; } if (ni->itype.index.block_size > PAGE_SIZE) { - ntfs_error(vi->i_sb, "Index block size (%u) > " - "PAGE_SIZE (%ld) is not " - "supported. Sorry.", - ni->itype.index.block_size, - PAGE_SIZE); + ntfs_error(vi->i_sb, + "Index block size (%u) > PAGE_SIZE (%ld) is not supported.", + ni->itype.index.block_size, + PAGE_SIZE); err =3D -EOPNOTSUPP; goto unm_err_out; } if (ni->itype.index.block_size < NTFS_BLOCK_SIZE) { - ntfs_error(vi->i_sb, "Index block size (%u) < " - "NTFS_BLOCK_SIZE (%i) is not " - "supported. Sorry.", - ni->itype.index.block_size, - NTFS_BLOCK_SIZE); + ntfs_error(vi->i_sb, + "Index block size (%u) < NTFS_BLOCK_SIZE (%i) is not supported.", + ni->itype.index.block_size, + NTFS_BLOCK_SIZE); err =3D -EOPNOTSUPP; goto unm_err_out; } @@ -872,127 +1020,28 @@ static int ntfs_read_locked_inode(struct inode *vi) } =20 /* Setup the index allocation attribute, even if not present. */ - NInoSetMstProtected(ni); - ni->type =3D AT_INDEX_ALLOCATION; - ni->name =3D I30; - ni->name_len =3D 4; - - if (!(ir->index.flags & LARGE_INDEX)) { - /* No index allocation. */ - vi->i_size =3D ni->initialized_size =3D - ni->allocated_size =3D 0; - /* We are done with the mft record, so we release it. */ - ntfs_attr_put_search_ctx(ctx); - unmap_mft_record(ni); - m =3D NULL; - ctx =3D NULL; - goto skip_large_dir_stuff; - } /* LARGE_INDEX: Index allocation present. Setup state. */ - NInoSetIndexAllocPresent(ni); - /* Find index allocation attribute. */ - ntfs_attr_reinit_search_ctx(ctx); - err =3D ntfs_attr_lookup(AT_INDEX_ALLOCATION, I30, 4, - CASE_SENSITIVE, 0, NULL, 0, ctx); - if (unlikely(err)) { - if (err =3D=3D -ENOENT) - ntfs_error(vi->i_sb, "$INDEX_ALLOCATION " - "attribute is not present but " - "$INDEX_ROOT indicated it is."); - else - ntfs_error(vi->i_sb, "Failed to lookup " - "$INDEX_ALLOCATION " - "attribute."); - goto unm_err_out; - } - a =3D ctx->attr; - if (!a->non_resident) { - ntfs_error(vi->i_sb, "$INDEX_ALLOCATION attribute " - "is resident."); - goto unm_err_out; - } - /* - * Ensure the attribute name is placed before the mapping pairs - * array. - */ - if (unlikely(a->name_length && (le16_to_cpu(a->name_offset) >=3D - le16_to_cpu( - a->data.non_resident.mapping_pairs_offset)))) { - ntfs_error(vol->sb, "$INDEX_ALLOCATION attribute name " - "is placed after the mapping pairs " - "array."); - goto unm_err_out; - } - if (a->flags & ATTR_IS_ENCRYPTED) { - ntfs_error(vi->i_sb, "$INDEX_ALLOCATION attribute " - "is encrypted."); - goto unm_err_out; - } - if (a->flags & ATTR_IS_SPARSE) { - ntfs_error(vi->i_sb, "$INDEX_ALLOCATION attribute " - "is sparse."); - goto unm_err_out; - } - if (a->flags & ATTR_COMPRESSION_MASK) { - ntfs_error(vi->i_sb, "$INDEX_ALLOCATION attribute " - "is compressed."); - goto unm_err_out; - } - if (a->data.non_resident.lowest_vcn) { - ntfs_error(vi->i_sb, "First extent of " - "$INDEX_ALLOCATION attribute has non " - "zero lowest_vcn."); - goto unm_err_out; - } - vi->i_size =3D sle64_to_cpu(a->data.non_resident.data_size); - ni->initialized_size =3D sle64_to_cpu( - a->data.non_resident.initialized_size); - ni->allocated_size =3D sle64_to_cpu( - a->data.non_resident.allocated_size); - /* - * We are done with the mft record, so we release it. Otherwise - * we would deadlock in ntfs_attr_iget(). - */ + ni->type =3D AT_INDEX_ROOT; + ni->name =3D name; + ni->name_len =3D name_len; + vi->i_size =3D ni->initialized_size =3D ni->data_size =3D + le32_to_cpu(a->data.resident.value_length); + ni->allocated_size =3D (ni->data_size + 7) & ~7; + /* We are done with the mft record, so we release it. */ ntfs_attr_put_search_ctx(ctx); unmap_mft_record(ni); m =3D NULL; ctx =3D NULL; - /* Get the index bitmap attribute inode. */ - bvi =3D ntfs_attr_iget(vi, AT_BITMAP, I30, 4); - if (IS_ERR(bvi)) { - ntfs_error(vi->i_sb, "Failed to get bitmap attribute."); - err =3D PTR_ERR(bvi); - goto unm_err_out; - } - bni =3D NTFS_I(bvi); - if (NInoCompressed(bni) || NInoEncrypted(bni) || - NInoSparse(bni)) { - ntfs_error(vi->i_sb, "$BITMAP attribute is compressed " - "and/or encrypted and/or sparse."); - goto iput_unm_err_out; - } - /* Consistency check bitmap size vs. index allocation size. */ - bvi_size =3D i_size_read(bvi); - if ((bvi_size << 3) < (vi->i_size >> - ni->itype.index.block_size_bits)) { - ntfs_error(vi->i_sb, "Index bitmap too small (0x%llx) " - "for index allocation (0x%llx).", - bvi_size << 3, vi->i_size); - goto iput_unm_err_out; - } - /* No longer need the bitmap attribute inode. */ - iput(bvi); -skip_large_dir_stuff: /* Setup the operations for this inode. */ - vi->i_op =3D &ntfs_dir_inode_ops; - vi->i_fop =3D &ntfs_dir_ops; - vi->i_mapping->a_ops =3D &ntfs_mst_aops; + ntfs_set_vfs_operations(vi, S_IFDIR, 0); + if (ir->index.flags & LARGE_INDEX) + NInoSetIndexAllocPresent(ni); } else { /* It is a file. */ ntfs_attr_reinit_search_ctx(ctx); =20 /* Setup the data attribute, even if not present. */ ni->type =3D AT_DATA; - ni->name =3D NULL; + ni->name =3D AT_UNNAMED; ni->name_len =3D 0; =20 /* Find first extent of the unnamed data attribute. */ @@ -1001,8 +1050,7 @@ static int ntfs_read_locked_inode(struct inode *vi) vi->i_size =3D ni->initialized_size =3D ni->allocated_size =3D 0; if (err !=3D -ENOENT) { - ntfs_error(vi->i_sb, "Failed to lookup $DATA " - "attribute."); + ntfs_error(vi->i_sb, "Failed to lookup $DATA attribute."); goto unm_err_out; } /* @@ -1020,11 +1068,24 @@ static int ntfs_read_locked_inode(struct inode *vi) * name of this inode from the mft record as the name * contains the back reference to the parent directory. */ - if (ntfs_is_extended_system_file(ctx) > 0) + extend_sys =3D ntfs_is_extended_system_file(ctx); + if (extend_sys > 0) { + if (m->flags & MFT_RECORD_IS_VIEW_INDEX) { + if (extend_sys =3D=3D 2) { + name =3D reparse_index_name; + name_len =3D 2; + goto view_index_meta; + } else if (extend_sys =3D=3D 3) { + name =3D objid_index_name; + name_len =3D 2; + goto view_index_meta; + } + } goto no_data_attr_special_case; - // FIXME: File is corrupt! Hot-fix with empty data - // attribute if recovery option is set. - ntfs_error(vi->i_sb, "$DATA attribute is missing."); + } + + err =3D extend_sys; + ntfs_error(vi->i_sb, "$DATA attribute is missing, err : %d", err); goto unm_err_out; } a =3D ctx->attr; @@ -1032,63 +1093,66 @@ static int ntfs_read_locked_inode(struct inode *vi) if (a->flags & (ATTR_COMPRESSION_MASK | ATTR_IS_SPARSE)) { if (a->flags & ATTR_COMPRESSION_MASK) { NInoSetCompressed(ni); + ni->flags |=3D FILE_ATTR_COMPRESSED; if (vol->cluster_size > 4096) { - ntfs_error(vi->i_sb, "Found " - "compressed data but " - "compression is " - "disabled due to " - "cluster size (%i) > " - "4kiB.", - vol->cluster_size); + ntfs_error(vi->i_sb, + "Found compressed data but compression is disabled due to cluster si= ze (%i) > 4kiB.", + vol->cluster_size); goto unm_err_out; } if ((a->flags & ATTR_COMPRESSION_MASK) !=3D ATTR_IS_COMPRESSED) { - ntfs_error(vi->i_sb, "Found unknown " - "compression method " - "or corrupt file."); + ntfs_error(vi->i_sb, + "Found unknown compression method or corrupt file."); goto unm_err_out; } } - if (a->flags & ATTR_IS_SPARSE) + if (a->flags & ATTR_IS_SPARSE) { NInoSetSparse(ni); + ni->flags |=3D FILE_ATTR_SPARSE_FILE; + } } if (a->flags & ATTR_IS_ENCRYPTED) { if (NInoCompressed(ni)) { - ntfs_error(vi->i_sb, "Found encrypted and " - "compressed data."); + ntfs_error(vi->i_sb, "Found encrypted and compressed data."); goto unm_err_out; } NInoSetEncrypted(ni); + ni->flags |=3D FILE_ATTR_ENCRYPTED; } if (a->non_resident) { NInoSetNonResident(ni); if (NInoCompressed(ni) || NInoSparse(ni)) { - if (NInoCompressed(ni) && a->data.non_resident. - compression_unit !=3D 4) { - ntfs_error(vi->i_sb, "Found " - "non-standard " - "compression unit (%u " - "instead of 4). " - "Cannot handle this.", - a->data.non_resident. - compression_unit); + if (NInoCompressed(ni) && + a->data.non_resident.compression_unit !=3D 4) { + ntfs_error(vi->i_sb, + "Found non-standard compression unit (%u instead of 4). Cannot hand= le this.", + a->data.non_resident.compression_unit); + err =3D -EOPNOTSUPP; + goto unm_err_out; + } + + if (NInoSparse(ni) && + a->data.non_resident.compression_unit && + a->data.non_resident.compression_unit !=3D + vol->sparse_compression_unit) { + ntfs_error(vi->i_sb, + "Found non-standard compression unit (%u instead of 0 or %d). Ca= nnot handle this.", + a->data.non_resident.compression_unit, + vol->sparse_compression_unit); err =3D -EOPNOTSUPP; goto unm_err_out; } + + if (a->data.non_resident.compression_unit) { ni->itype.compressed.block_size =3D 1U << - (a->data.non_resident. - compression_unit + + (a->data.non_resident.compression_unit + vol->cluster_size_bits); ni->itype.compressed.block_size_bits =3D - ffs(ni->itype. - compressed. - block_size) - 1; + ffs(ni->itype.compressed.block_size) - 1; ni->itype.compressed.block_clusters =3D - 1U << a->data. - non_resident. - compression_unit; + 1U << a->data.non_resident.compression_unit; } else { ni->itype.compressed.block_size =3D 0; ni->itype.compressed.block_size_bits =3D @@ -1096,32 +1160,26 @@ static int ntfs_read_locked_inode(struct inode *vi) ni->itype.compressed.block_clusters =3D 0; } - ni->itype.compressed.size =3D sle64_to_cpu( - a->data.non_resident. - compressed_size); + ni->itype.compressed.size =3D le64_to_cpu( + a->data.non_resident.compressed_size); } if (a->data.non_resident.lowest_vcn) { - ntfs_error(vi->i_sb, "First extent of $DATA " - "attribute has non zero " - "lowest_vcn."); + ntfs_error(vi->i_sb, + "First extent of $DATA attribute has non zero lowest_vcn."); goto unm_err_out; } - vi->i_size =3D sle64_to_cpu( - a->data.non_resident.data_size); - ni->initialized_size =3D sle64_to_cpu( - a->data.non_resident.initialized_size); - ni->allocated_size =3D sle64_to_cpu( - a->data.non_resident.allocated_size); + vi->i_size =3D ni->data_size =3D le64_to_cpu(a->data.non_resident.data_= size); + ni->initialized_size =3D le64_to_cpu(a->data.non_resident.initialized_s= ize); + ni->allocated_size =3D le64_to_cpu(a->data.non_resident.allocated_size); } else { /* Resident attribute. */ - vi->i_size =3D ni->initialized_size =3D le32_to_cpu( + vi->i_size =3D ni->data_size =3D ni->initialized_size =3D le32_to_cpu( a->data.resident.value_length); ni->allocated_size =3D le32_to_cpu(a->length) - le16_to_cpu( a->data.resident.value_offset); if (vi->i_size > ni->allocated_size) { - ntfs_error(vi->i_sb, "Resident data attribute " - "is corrupt (size exceeds " - "allocation)."); + ntfs_error(vi->i_sb, + "Resident data attribute is corrupt (size exceeds allocation)."); goto unm_err_out; } } @@ -1132,14 +1190,13 @@ static int ntfs_read_locked_inode(struct inode *vi) m =3D NULL; ctx =3D NULL; /* Setup the operations for this inode. */ - vi->i_op =3D &ntfs_file_inode_ops; - vi->i_fop =3D &ntfs_file_ops; - vi->i_mapping->a_ops =3D &ntfs_normal_aops; - if (NInoMstProtected(ni)) - vi->i_mapping->a_ops =3D &ntfs_mst_aops; - else if (NInoCompressed(ni)) - vi->i_mapping->a_ops =3D &ntfs_compressed_aops; + ntfs_set_vfs_operations(vi, vi->i_mode, dev); } + + if (NVolSysImmutable(vol) && (ni->flags & FILE_ATTR_SYSTEM) && + !S_ISFIFO(vi->i_mode) && !S_ISSOCK(vi->i_mode) && !S_ISLNK(vi->i_mode= )) + vi->i_flags |=3D S_IMMUTABLE; + /* * The number of 512-byte blocks used on disk (for stat). This is in so * far inaccurate as it doesn't account for any named streams or other @@ -1155,10 +1212,9 @@ static int ntfs_read_locked_inode(struct inode *vi) vi->i_blocks =3D ni->itype.compressed.size >> 9; else vi->i_blocks =3D ni->allocated_size >> 9; + ntfs_debug("Done."); return 0; -iput_unm_err_out: - iput(bvi); unm_err_out: if (!err) err =3D -EIO; @@ -1167,15 +1223,16 @@ static int ntfs_read_locked_inode(struct inode *vi) if (m) unmap_mft_record(ni); err_out: - ntfs_error(vol->sb, "Failed with error code %i. Marking corrupt " - "inode 0x%lx as bad. Run chkdsk.", err, vi->i_ino); - make_bad_inode(vi); - if (err !=3D -EOPNOTSUPP && err !=3D -ENOMEM) + if (err !=3D -EOPNOTSUPP && err !=3D -ENOMEM && vol_err =3D=3D true) { + ntfs_error(vol->sb, + "Failed with error code %i. Marking corrupt inode 0x%lx as bad. Run c= hkdsk.", + err, vi->i_ino); NVolSetErrors(vol); + } return err; } =20 -/** +/* * ntfs_read_locked_attr_inode - read an attribute inode from its base ino= de * @base_vi: base inode * @vi: attribute inode to read @@ -1192,27 +1249,23 @@ static int ntfs_read_locked_inode(struct inode *vi) * A: i_state has I_NEW set, hence the inode is locked, also * i_count is set to 1, so it is not going to go away * - * Return 0 on success and -errno on error. In the error case, the inode = will - * have had make_bad_inode() executed on it. + * Return 0 on success and -errno on error. * * Note this cannot be called for AT_INDEX_ALLOCATION. */ static int ntfs_read_locked_attr_inode(struct inode *base_vi, struct inode= *vi) { - ntfs_volume *vol =3D NTFS_SB(vi->i_sb); - ntfs_inode *ni, *base_ni; - MFT_RECORD *m; - ATTR_RECORD *a; - ntfs_attr_search_ctx *ctx; + struct ntfs_volume *vol =3D NTFS_SB(vi->i_sb); + struct ntfs_inode *ni =3D NTFS_I(vi), *base_ni =3D NTFS_I(base_vi); + struct mft_record *m; + struct attr_record *a; + struct ntfs_attr_search_ctx *ctx; int err =3D 0; =20 ntfs_debug("Entering for i_ino 0x%lx.", vi->i_ino); =20 ntfs_init_big_inode(vi); =20 - ni =3D NTFS_I(vi); - base_ni =3D NTFS_I(base_vi); - /* Just mirror the values from the base inode. */ vi->i_uid =3D base_vi->i_uid; vi->i_gid =3D base_vi->i_gid; @@ -1244,28 +1297,22 @@ static int ntfs_read_locked_attr_inode(struct inode= *base_vi, struct inode *vi) if (a->flags & (ATTR_COMPRESSION_MASK | ATTR_IS_SPARSE)) { if (a->flags & ATTR_COMPRESSION_MASK) { NInoSetCompressed(ni); + ni->flags |=3D FILE_ATTR_COMPRESSED; if ((ni->type !=3D AT_DATA) || (ni->type =3D=3D AT_DATA && ni->name_len)) { - ntfs_error(vi->i_sb, "Found compressed " - "non-data or named data " - "attribute. Please report " - "you saw this message to " - "linux-ntfs-dev@lists." - "sourceforge.net"); + ntfs_error(vi->i_sb, + "Found compressed non-data or named data attribute."); goto unm_err_out; } if (vol->cluster_size > 4096) { - ntfs_error(vi->i_sb, "Found compressed " - "attribute but compression is " - "disabled due to cluster size " - "(%i) > 4kiB.", - vol->cluster_size); + ntfs_error(vi->i_sb, + "Found compressed attribute but compression is disabled due to cluste= r size (%i) > 4kiB.", + vol->cluster_size); goto unm_err_out; } if ((a->flags & ATTR_COMPRESSION_MASK) !=3D ATTR_IS_COMPRESSED) { - ntfs_error(vi->i_sb, "Found unknown " - "compression method."); + ntfs_error(vi->i_sb, "Found unknown compression method."); goto unm_err_out; } } @@ -1274,21 +1321,19 @@ static int ntfs_read_locked_attr_inode(struct inode= *base_vi, struct inode *vi) * to compress all files. */ if (NInoMstProtected(ni) && ni->type !=3D AT_INDEX_ROOT) { - ntfs_error(vi->i_sb, "Found mst protected attribute " - "but the attribute is %s. Please " - "report you saw this message to " - "linux-ntfs-dev@lists.sourceforge.net", - NInoCompressed(ni) ? "compressed" : - "sparse"); + ntfs_error(vi->i_sb, + "Found mst protected attribute but the attribute is %s.", + NInoCompressed(ni) ? "compressed" : "sparse"); goto unm_err_out; } - if (a->flags & ATTR_IS_SPARSE) + if (a->flags & ATTR_IS_SPARSE) { NInoSetSparse(ni); + ni->flags |=3D FILE_ATTR_SPARSE_FILE; + } } if (a->flags & ATTR_IS_ENCRYPTED) { if (NInoCompressed(ni)) { - ntfs_error(vi->i_sb, "Found encrypted and compressed " - "data."); + ntfs_error(vi->i_sb, "Found encrypted and compressed data."); goto unm_err_out; } /* @@ -1296,42 +1341,38 @@ static int ntfs_read_locked_attr_inode(struct inode= *base_vi, struct inode *vi) * encrypt all files. */ if (NInoMstProtected(ni) && ni->type !=3D AT_INDEX_ROOT) { - ntfs_error(vi->i_sb, "Found mst protected attribute " - "but the attribute is encrypted. " - "Please report you saw this message " - "to linux-ntfs-dev@lists.sourceforge." - "net"); + ntfs_error(vi->i_sb, + "Found mst protected attribute but the attribute is encrypted."); goto unm_err_out; } if (ni->type !=3D AT_DATA) { - ntfs_error(vi->i_sb, "Found encrypted non-data " - "attribute."); + ntfs_error(vi->i_sb, + "Found encrypted non-data attribute."); goto unm_err_out; } NInoSetEncrypted(ni); + ni->flags |=3D FILE_ATTR_ENCRYPTED; } if (!a->non_resident) { /* Ensure the attribute name is placed before the value. */ if (unlikely(a->name_length && (le16_to_cpu(a->name_offset) >=3D le16_to_cpu(a->data.resident.value_offset)))) { - ntfs_error(vol->sb, "Attribute name is placed after " - "the attribute value."); + ntfs_error(vol->sb, + "Attribute name is placed after the attribute value."); goto unm_err_out; } if (NInoMstProtected(ni)) { - ntfs_error(vi->i_sb, "Found mst protected attribute " - "but the attribute is resident. " - "Please report you saw this message to " - "linux-ntfs-dev@lists.sourceforge.net"); + ntfs_error(vi->i_sb, + "Found mst protected attribute but the attribute is resident."); goto unm_err_out; } - vi->i_size =3D ni->initialized_size =3D le32_to_cpu( + vi->i_size =3D ni->initialized_size =3D ni->data_size =3D le32_to_cpu( a->data.resident.value_length); ni->allocated_size =3D le32_to_cpu(a->length) - le16_to_cpu(a->data.resident.value_offset); if (vi->i_size > ni->allocated_size) { - ntfs_error(vi->i_sb, "Resident attribute is corrupt " - "(size exceeds allocation)."); + ntfs_error(vi->i_sb, + "Resident attribute is corrupt (size exceeds allocation)."); goto unm_err_out; } } else { @@ -1343,56 +1384,43 @@ static int ntfs_read_locked_attr_inode(struct inode= *base_vi, struct inode *vi) if (unlikely(a->name_length && (le16_to_cpu(a->name_offset) >=3D le16_to_cpu( a->data.non_resident.mapping_pairs_offset)))) { - ntfs_error(vol->sb, "Attribute name is placed after " - "the mapping pairs array."); + ntfs_error(vol->sb, + "Attribute name is placed after the mapping pairs array."); goto unm_err_out; } if (NInoCompressed(ni) || NInoSparse(ni)) { - if (NInoCompressed(ni) && a->data.non_resident. - compression_unit !=3D 4) { - ntfs_error(vi->i_sb, "Found non-standard " - "compression unit (%u instead " - "of 4). Cannot handle this.", - a->data.non_resident. - compression_unit); + if (NInoCompressed(ni) && a->data.non_resident.compression_unit !=3D 4)= { + ntfs_error(vi->i_sb, + "Found non-standard compression unit (%u instead of 4). Cannot handl= e this.", + a->data.non_resident.compression_unit); err =3D -EOPNOTSUPP; goto unm_err_out; } if (a->data.non_resident.compression_unit) { ni->itype.compressed.block_size =3D 1U << - (a->data.non_resident. - compression_unit + + (a->data.non_resident.compression_unit + vol->cluster_size_bits); ni->itype.compressed.block_size_bits =3D - ffs(ni->itype.compressed. - block_size) - 1; + ffs(ni->itype.compressed.block_size) - 1; ni->itype.compressed.block_clusters =3D 1U << - a->data.non_resident. - compression_unit; + a->data.non_resident.compression_unit; } else { ni->itype.compressed.block_size =3D 0; ni->itype.compressed.block_size_bits =3D 0; ni->itype.compressed.block_clusters =3D 0; } - ni->itype.compressed.size =3D sle64_to_cpu( + ni->itype.compressed.size =3D le64_to_cpu( a->data.non_resident.compressed_size); } if (a->data.non_resident.lowest_vcn) { - ntfs_error(vi->i_sb, "First extent of attribute has " - "non-zero lowest_vcn."); + ntfs_error(vi->i_sb, "First extent of attribute has non-zero lowest_vcn= ."); goto unm_err_out; } - vi->i_size =3D sle64_to_cpu(a->data.non_resident.data_size); - ni->initialized_size =3D sle64_to_cpu( - a->data.non_resident.initialized_size); - ni->allocated_size =3D sle64_to_cpu( - a->data.non_resident.allocated_size); - } - vi->i_mapping->a_ops =3D &ntfs_normal_aops; - if (NInoMstProtected(ni)) - vi->i_mapping->a_ops =3D &ntfs_mst_aops; - else if (NInoCompressed(ni)) - vi->i_mapping->a_ops =3D &ntfs_compressed_aops; + vi->i_size =3D ni->data_size =3D le64_to_cpu(a->data.non_resident.data_s= ize); + ni->initialized_size =3D le64_to_cpu(a->data.non_resident.initialized_si= ze); + ni->allocated_size =3D le64_to_cpu(a->data.non_resident.allocated_size); + } + vi->i_mapping->a_ops =3D &ntfs_aops; if ((NInoCompressed(ni) || NInoSparse(ni)) && ni->type !=3D AT_INDEX_ROOT) vi->i_blocks =3D ni->itype.compressed.size >> 9; else @@ -1401,7 +1429,10 @@ static int ntfs_read_locked_attr_inode(struct inode = *base_vi, struct inode *vi) * Make sure the base inode does not go away and attach it to the * attribute inode. */ - igrab(base_vi); + if (!igrab(base_vi)) { + err =3D -ENOENT; + goto unm_err_out; + } ni->ext.base_ntfs_ino =3D base_ni; ni->nr_extents =3D -1; =20 @@ -1418,18 +1449,17 @@ static int ntfs_read_locked_attr_inode(struct inode= *base_vi, struct inode *vi) ntfs_attr_put_search_ctx(ctx); unmap_mft_record(base_ni); err_out: - ntfs_error(vol->sb, "Failed with error code %i while reading attribute " - "inode (mft_no 0x%lx, type 0x%x, name_len %i). " - "Marking corrupt inode and base inode 0x%lx as bad. " - "Run chkdsk.", err, vi->i_ino, ni->type, ni->name_len, + if (err !=3D -ENOENT) + ntfs_error(vol->sb, + "Failed with error code %i while reading attribute inode (mft_no 0x%lx,= type 0x%x, name_len %i). Marking corrupt inode and base inode 0x%lx as ba= d. Run chkdsk.", + err, vi->i_ino, ni->type, ni->name_len, base_vi->i_ino); - make_bad_inode(vi); - if (err !=3D -ENOMEM) + if (err !=3D -ENOENT && err !=3D -ENOMEM) NVolSetErrors(vol); return err; } =20 -/** +/* * ntfs_read_locked_index_inode - read an index inode from its base inode * @base_vi: base inode * @vi: index inode to read @@ -1459,26 +1489,25 @@ static int ntfs_read_locked_attr_inode(struct inode= *base_vi, struct inode *vi) * A: i_state has I_NEW set, hence the inode is locked, also * i_count is set to 1, so it is not going to go away * - * Return 0 on success and -errno on error. In the error case, the inode = will - * have had make_bad_inode() executed on it. + * Return 0 on success and -errno on error. */ static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inod= e *vi) { loff_t bvi_size; - ntfs_volume *vol =3D NTFS_SB(vi->i_sb); - ntfs_inode *ni, *base_ni, *bni; + struct ntfs_volume *vol =3D NTFS_SB(vi->i_sb); + struct ntfs_inode *ni =3D NTFS_I(vi), *base_ni =3D NTFS_I(base_vi), *bni; struct inode *bvi; - MFT_RECORD *m; - ATTR_RECORD *a; - ntfs_attr_search_ctx *ctx; - INDEX_ROOT *ir; + struct mft_record *m; + struct attr_record *a; + struct ntfs_attr_search_ctx *ctx; + struct index_root *ir; u8 *ir_end, *index_end; int err =3D 0; =20 ntfs_debug("Entering for i_ino 0x%lx.", vi->i_ino); + lockdep_assert_held(&base_ni->mrec_lock); + ntfs_init_big_inode(vi); - ni =3D NTFS_I(vi); - base_ni =3D NTFS_I(base_vi); /* Just mirror the values from the base inode. */ vi->i_uid =3D base_vi->i_uid; vi->i_gid =3D base_vi->i_gid; @@ -1505,8 +1534,7 @@ static int ntfs_read_locked_index_inode(struct inode = *base_vi, struct inode *vi) CASE_SENSITIVE, 0, NULL, 0, ctx); if (unlikely(err)) { if (err =3D=3D -ENOENT) - ntfs_error(vi->i_sb, "$INDEX_ROOT attribute is " - "missing."); + ntfs_error(vi->i_sb, "$INDEX_ROOT attribute is missing."); goto unm_err_out; } a =3D ctx->attr; @@ -1518,55 +1546,41 @@ static int ntfs_read_locked_index_inode(struct inod= e *base_vi, struct inode *vi) /* Ensure the attribute name is placed before the value. */ if (unlikely(a->name_length && (le16_to_cpu(a->name_offset) >=3D le16_to_cpu(a->data.resident.value_offset)))) { - ntfs_error(vol->sb, "$INDEX_ROOT attribute name is placed " - "after the attribute value."); - goto unm_err_out; - } - /* - * Compressed/encrypted/sparse index root is not allowed, except for - * directories of course but those are not dealt with here. - */ - if (a->flags & (ATTR_COMPRESSION_MASK | ATTR_IS_ENCRYPTED | - ATTR_IS_SPARSE)) { - ntfs_error(vi->i_sb, "Found compressed/encrypted/sparse index " - "root attribute."); + ntfs_error(vol->sb, + "$INDEX_ROOT attribute name is placed after the attribute value."); goto unm_err_out; } - ir =3D (INDEX_ROOT*)((u8*)a + le16_to_cpu(a->data.resident.value_offset)); - ir_end =3D (u8*)ir + le32_to_cpu(a->data.resident.value_length); - if (ir_end > (u8*)ctx->mrec + vol->mft_record_size) { + + ir =3D (struct index_root *)((u8 *)a + le16_to_cpu(a->data.resident.value= _offset)); + ir_end =3D (u8 *)ir + le32_to_cpu(a->data.resident.value_length); + if (ir_end > (u8 *)ctx->mrec + vol->mft_record_size) { ntfs_error(vi->i_sb, "$INDEX_ROOT attribute is corrupt."); goto unm_err_out; } - index_end =3D (u8*)&ir->index + le32_to_cpu(ir->index.index_length); + index_end =3D (u8 *)&ir->index + le32_to_cpu(ir->index.index_length); if (index_end > ir_end) { ntfs_error(vi->i_sb, "Index is corrupt."); goto unm_err_out; } - if (ir->type) { - ntfs_error(vi->i_sb, "Index type is not 0 (type is 0x%x).", - le32_to_cpu(ir->type)); - goto unm_err_out; - } + ni->itype.index.collation_rule =3D ir->collation_rule; ntfs_debug("Index collation rule is 0x%x.", le32_to_cpu(ir->collation_rule)); ni->itype.index.block_size =3D le32_to_cpu(ir->index_block_size); if (!is_power_of_2(ni->itype.index.block_size)) { - ntfs_error(vi->i_sb, "Index block size (%u) is not a power of " - "two.", ni->itype.index.block_size); + ntfs_error(vi->i_sb, "Index block size (%u) is not a power of two.", + ni->itype.index.block_size); goto unm_err_out; } if (ni->itype.index.block_size > PAGE_SIZE) { - ntfs_error(vi->i_sb, "Index block size (%u) > PAGE_SIZE " - "(%ld) is not supported. Sorry.", + ntfs_error(vi->i_sb, "Index block size (%u) > PAGE_SIZE (%ld) is not sup= ported.", ni->itype.index.block_size, PAGE_SIZE); err =3D -EOPNOTSUPP; goto unm_err_out; } if (ni->itype.index.block_size < NTFS_BLOCK_SIZE) { - ntfs_error(vi->i_sb, "Index block size (%u) < NTFS_BLOCK_SIZE " - "(%i) is not supported. Sorry.", + ntfs_error(vi->i_sb, + "Index block size (%u) < NTFS_BLOCK_SIZE (%i) is not supported.", ni->itype.index.block_size, NTFS_BLOCK_SIZE); err =3D -EOPNOTSUPP; goto unm_err_out; @@ -1580,51 +1594,45 @@ static int ntfs_read_locked_index_inode(struct inod= e *base_vi, struct inode *vi) ni->itype.index.vcn_size =3D vol->sector_size; ni->itype.index.vcn_size_bits =3D vol->sector_size_bits; } - /* Check for presence of index allocation attribute. */ - if (!(ir->index.flags & LARGE_INDEX)) { - /* No index allocation. */ - vi->i_size =3D ni->initialized_size =3D ni->allocated_size =3D 0; - /* We are done with the mft record, so we release it. */ - ntfs_attr_put_search_ctx(ctx); - unmap_mft_record(base_ni); - m =3D NULL; - ctx =3D NULL; - goto skip_large_index_stuff; - } /* LARGE_INDEX: Index allocation present. Setup state. */ - NInoSetIndexAllocPresent(ni); + /* Find index allocation attribute. */ ntfs_attr_reinit_search_ctx(ctx); err =3D ntfs_attr_lookup(AT_INDEX_ALLOCATION, ni->name, ni->name_len, CASE_SENSITIVE, 0, NULL, 0, ctx); if (unlikely(err)) { - if (err =3D=3D -ENOENT) - ntfs_error(vi->i_sb, "$INDEX_ALLOCATION attribute is " - "not present but $INDEX_ROOT " - "indicated it is."); - else - ntfs_error(vi->i_sb, "Failed to lookup " - "$INDEX_ALLOCATION attribute."); + if (err =3D=3D -ENOENT) { + /* No index allocation. */ + vi->i_size =3D ni->initialized_size =3D ni->allocated_size =3D 0; + /* We are done with the mft record, so we release it. */ + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(base_ni); + m =3D NULL; + ctx =3D NULL; + goto skip_large_index_stuff; + } else + ntfs_error(vi->i_sb, "Failed to lookup $INDEX_ALLOCATION attribute."); goto unm_err_out; } + NInoSetIndexAllocPresent(ni); + NInoSetNonResident(ni); + ni->type =3D AT_INDEX_ALLOCATION; + a =3D ctx->attr; if (!a->non_resident) { - ntfs_error(vi->i_sb, "$INDEX_ALLOCATION attribute is " - "resident."); + ntfs_error(vi->i_sb, "$INDEX_ALLOCATION attribute is resident."); goto unm_err_out; } /* * Ensure the attribute name is placed before the mapping pairs array. */ if (unlikely(a->name_length && (le16_to_cpu(a->name_offset) >=3D - le16_to_cpu( - a->data.non_resident.mapping_pairs_offset)))) { - ntfs_error(vol->sb, "$INDEX_ALLOCATION attribute name is " - "placed after the mapping pairs array."); + le16_to_cpu(a->data.non_resident.mapping_pairs_offset)))) { + ntfs_error(vol->sb, + "$INDEX_ALLOCATION attribute name is placed after the mapping pairs arr= ay."); goto unm_err_out; } if (a->flags & ATTR_IS_ENCRYPTED) { - ntfs_error(vi->i_sb, "$INDEX_ALLOCATION attribute is " - "encrypted."); + ntfs_error(vi->i_sb, "$INDEX_ALLOCATION attribute is encrypted."); goto unm_err_out; } if (a->flags & ATTR_IS_SPARSE) { @@ -1632,19 +1640,18 @@ static int ntfs_read_locked_index_inode(struct inod= e *base_vi, struct inode *vi) goto unm_err_out; } if (a->flags & ATTR_COMPRESSION_MASK) { - ntfs_error(vi->i_sb, "$INDEX_ALLOCATION attribute is " - "compressed."); + ntfs_error(vi->i_sb, + "$INDEX_ALLOCATION attribute is compressed."); goto unm_err_out; } if (a->data.non_resident.lowest_vcn) { - ntfs_error(vi->i_sb, "First extent of $INDEX_ALLOCATION " - "attribute has non zero lowest_vcn."); + ntfs_error(vi->i_sb, + "First extent of $INDEX_ALLOCATION attribute has non zero lowest_vcn."); goto unm_err_out; } - vi->i_size =3D sle64_to_cpu(a->data.non_resident.data_size); - ni->initialized_size =3D sle64_to_cpu( - a->data.non_resident.initialized_size); - ni->allocated_size =3D sle64_to_cpu(a->data.non_resident.allocated_size); + vi->i_size =3D ni->data_size =3D le64_to_cpu(a->data.non_resident.data_si= ze); + ni->initialized_size =3D le64_to_cpu(a->data.non_resident.initialized_siz= e); + ni->allocated_size =3D le64_to_cpu(a->data.non_resident.allocated_size); /* * We are done with the mft record, so we release it. Otherwise * we would deadlock in ntfs_attr_iget(). @@ -1663,28 +1670,29 @@ static int ntfs_read_locked_index_inode(struct inod= e *base_vi, struct inode *vi) bni =3D NTFS_I(bvi); if (NInoCompressed(bni) || NInoEncrypted(bni) || NInoSparse(bni)) { - ntfs_error(vi->i_sb, "$BITMAP attribute is compressed and/or " - "encrypted and/or sparse."); + ntfs_error(vi->i_sb, + "$BITMAP attribute is compressed and/or encrypted and/or sparse."); goto iput_unm_err_out; } /* Consistency check bitmap size vs. index allocation size. */ bvi_size =3D i_size_read(bvi); if ((bvi_size << 3) < (vi->i_size >> ni->itype.index.block_size_bits)) { - ntfs_error(vi->i_sb, "Index bitmap too small (0x%llx) for " - "index allocation (0x%llx).", bvi_size << 3, - vi->i_size); + ntfs_error(vi->i_sb, + "Index bitmap too small (0x%llx) for index allocation (0x%llx).", + bvi_size << 3, vi->i_size); goto iput_unm_err_out; } iput(bvi); skip_large_index_stuff: /* Setup the operations for this index inode. */ - vi->i_mapping->a_ops =3D &ntfs_mst_aops; + ntfs_set_vfs_operations(vi, S_IFDIR, 0); vi->i_blocks =3D ni->allocated_size >> 9; /* * Make sure the base inode doesn't go away and attach it to the * index inode. */ - igrab(base_vi); + if (!igrab(base_vi)) + goto unm_err_out; ni->ext.base_ntfs_ino =3D base_ni; ni->nr_extents =3D -1; =20 @@ -1700,17 +1708,101 @@ static int ntfs_read_locked_index_inode(struct ino= de *base_vi, struct inode *vi) if (m) unmap_mft_record(base_ni); err_out: - ntfs_error(vi->i_sb, "Failed with error code %i while reading index " - "inode (mft_no 0x%lx, name_len %i.", err, vi->i_ino, - ni->name_len); - make_bad_inode(vi); + ntfs_error(vi->i_sb, + "Failed with error code %i while reading index inode (mft_no 0x%lx, name= _len %i.", + err, vi->i_ino, ni->name_len); if (err !=3D -EOPNOTSUPP && err !=3D -ENOMEM) NVolSetErrors(vol); return err; } =20 /* - * The MFT inode has special locking, so teach the lock validator + * load_attribute_list_mount - load an attribute list into memory + * @vol: ntfs volume from which to read + * @rl: runlist of the attribute list + * @al_start: destination buffer + * @size: size of the destination buffer in bytes + * @initialized_size: initialized size of the attribute list + * + * Walk the runlist @rl and load all clusters from it copying them into + * the linear buffer @al. The maximum number of bytes copied to @al is @si= ze + * bytes. Note, @size does not need to be a multiple of the cluster size. = If + * @initialized_size is less than @size, the region in @al between + * @initialized_size and @size will be zeroed and not read from disk. + * + * Return 0 on success or -errno on error. + */ +static int load_attribute_list_mount(struct ntfs_volume *vol, + struct runlist_element *rl, u8 *al_start, const s64 size, + const s64 initialized_size) +{ + s64 lcn; + u8 *al =3D al_start; + u8 *al_end =3D al + initialized_size; + struct super_block *sb; + int err =3D 0; + loff_t rl_byte_off, rl_byte_len; + + ntfs_debug("Entering."); + if (!vol || !rl || !al || size <=3D 0 || initialized_size < 0 || + initialized_size > size) + return -EINVAL; + if (!initialized_size) { + memset(al, 0, size); + return 0; + } + sb =3D vol->sb; + + /* Read all clusters specified by the runlist one run at a time. */ + while (rl->length) { + lcn =3D ntfs_rl_vcn_to_lcn(rl, rl->vcn); + ntfs_debug("Reading vcn =3D 0x%llx, lcn =3D 0x%llx.", + (unsigned long long)rl->vcn, + (unsigned long long)lcn); + /* The attribute list cannot be sparse. */ + if (lcn < 0) { + ntfs_error(sb, "ntfs_rl_vcn_to_lcn() failed. Cannot read attribute list= ."); + goto err_out; + } + + rl_byte_off =3D ntfs_cluster_to_bytes(vol, lcn); + rl_byte_len =3D ntfs_cluster_to_bytes(vol, rl->length); + + if (al + rl_byte_len > al_end) + rl_byte_len =3D al_end - al; + + err =3D ntfs_bdev_read(sb->s_bdev, rl_byte_off, + round_up(rl_byte_len, SECTOR_SIZE), + al); + if (err) { + ntfs_error(sb, "Cannot read attribute list."); + goto err_out; + } + + if (al + rl_byte_len >=3D al_end) { + if (initialized_size < size) + goto initialize; + goto done; + } + + al +=3D rl_byte_len; + rl++; + } + if (initialized_size < size) { +initialize: + memset(al_start + initialized_size, 0, size - initialized_size); + } +done: + return err; + /* Real overflow! */ + ntfs_error(sb, "Attribute list buffer overflow. Read attribute list is tr= uncated."); +err_out: + err =3D -EIO; + goto done; +} + +/* + * The MFT inode has special locking, so teach the lock validator * about this by splitting off the locking rules of the MFT from * the locking rules of other inodes. The MFT inode can never be * accessed from the VFS side (or even internally), only by the @@ -1718,7 +1810,7 @@ static int ntfs_read_locked_index_inode(struct inode = *base_vi, struct inode *vi) */ static struct lock_class_key mft_ni_runlist_lock_key, mft_ni_mrec_lock_key; =20 -/** +/* * ntfs_read_inode_mount - special read_inode for mount time use only * @vi: inode to read * @@ -1746,31 +1838,29 @@ static struct lock_class_key mft_ni_runlist_lock_ke= y, mft_ni_mrec_lock_key; */ int ntfs_read_inode_mount(struct inode *vi) { - VCN next_vcn, last_vcn, highest_vcn; - s64 block; + s64 next_vcn, last_vcn, highest_vcn; struct super_block *sb =3D vi->i_sb; - ntfs_volume *vol =3D NTFS_SB(sb); - struct buffer_head *bh; - ntfs_inode *ni; - MFT_RECORD *m =3D NULL; - ATTR_RECORD *a; - ntfs_attr_search_ctx *ctx; + struct ntfs_volume *vol =3D NTFS_SB(sb); + struct ntfs_inode *ni =3D NTFS_I(vi); + struct mft_record *m =3D NULL; + struct attr_record *a; + struct ntfs_attr_search_ctx *ctx; unsigned int i, nr_blocks; int err; + size_t new_rl_count; =20 ntfs_debug("Entering."); =20 /* Initialize the ntfs specific part of @vi. */ ntfs_init_big_inode(vi); =20 - ni =3D NTFS_I(vi); =20 /* Setup the data attribute. It is special as it is mst protected. */ NInoSetNonResident(ni); NInoSetMstProtected(ni); NInoSetSparseDisabled(ni); ni->type =3D AT_DATA; - ni->name =3D NULL; + ni->name =3D AT_UNNAMED; ni->name_len =3D 0; /* * This sets up our little cheat allowing us to reuse the async read io @@ -1788,32 +1878,28 @@ int ntfs_read_inode_mount(struct inode *vi) vol->mft_record_size); goto err_out; } + i =3D vol->mft_record_size; if (i < sb->s_blocksize) i =3D sb->s_blocksize; - m =3D (MFT_RECORD*)ntfs_malloc_nofs(i); + + m =3D kzalloc(i, GFP_NOFS); if (!m) { ntfs_error(sb, "Failed to allocate buffer for $MFT record 0."); goto err_out; } =20 /* Determine the first block of the $MFT/$DATA attribute. */ - block =3D vol->mft_lcn << vol->cluster_size_bits >> - sb->s_blocksize_bits; - nr_blocks =3D vol->mft_record_size >> sb->s_blocksize_bits; + nr_blocks =3D ntfs_bytes_to_sector(vol, vol->mft_record_size); if (!nr_blocks) nr_blocks =3D 1; =20 /* Load $MFT/$DATA's first mft record. */ - for (i =3D 0; i < nr_blocks; i++) { - bh =3D sb_bread(sb, block++); - if (!bh) { - ntfs_error(sb, "Device read failed."); - goto err_out; - } - memcpy((char*)m + (i << sb->s_blocksize_bits), bh->b_data, - sb->s_blocksize); - brelse(bh); + err =3D ntfs_bdev_read(sb->s_bdev, ntfs_cluster_to_bytes(vol, vol->mft_lc= n) >> + SECTOR_SHIFT, i, (char *)m); + if (err) { + ntfs_error(sb, "Device read failed."); + goto err_out; } =20 if (le32_to_cpu(m->bytes_allocated) !=3D vol->mft_record_size) { @@ -1823,16 +1909,13 @@ int ntfs_read_inode_mount(struct inode *vi) } =20 /* Apply the mst fixups. */ - if (post_read_mst_fixup((NTFS_RECORD*)m, vol->mft_record_size)) { - /* FIXME: Try to use the $MFTMirr now. */ + if (post_read_mst_fixup((struct ntfs_record *)m, vol->mft_record_size)) { ntfs_error(sb, "MST fixup failed. $MFT is corrupt."); goto err_out; } =20 - /* Sanity check offset to the first attribute */ - if (le16_to_cpu(m->attrs_offset) >=3D le32_to_cpu(m->bytes_allocated)) { - ntfs_error(sb, "Incorrect mft offset to the first attribute %u in superb= lock.", - le16_to_cpu(m->attrs_offset)); + if (ntfs_mft_record_check(vol, m, FILE_MFT)) { + ntfs_error(sb, "ntfs_mft_record_check failed. $MFT is corrupt."); goto err_out; } =20 @@ -1840,7 +1923,7 @@ int ntfs_read_inode_mount(struct inode *vi) vi->i_generation =3D ni->seq_no =3D le16_to_cpu(m->sequence_number); =20 /* Provides read_folio() for map_mft_record(). */ - vi->i_mapping->a_ops =3D &ntfs_mst_aops; + vi->i_mapping->a_ops =3D &ntfs_mft_aops; =20 ctx =3D ntfs_attr_get_search_ctx(ni, m); if (!ctx) { @@ -1852,39 +1935,34 @@ int ntfs_read_inode_mount(struct inode *vi) err =3D ntfs_attr_lookup(AT_ATTRIBUTE_LIST, NULL, 0, 0, 0, NULL, 0, ctx); if (err) { if (unlikely(err !=3D -ENOENT)) { - ntfs_error(sb, "Failed to lookup attribute list " - "attribute. You should run chkdsk."); + ntfs_error(sb, + "Failed to lookup attribute list attribute. You should run chkdsk."); goto put_err_out; } } else /* if (!err) */ { - ATTR_LIST_ENTRY *al_entry, *next_al_entry; + struct attr_list_entry *al_entry, *next_al_entry; u8 *al_end; - static const char *es =3D " Not allowed. $MFT is corrupt. " - "You should run chkdsk."; + static const char *es =3D " Not allowed. $MFT is corrupt. You should = run chkdsk."; =20 ntfs_debug("Attribute list attribute found in $MFT."); NInoSetAttrList(ni); a =3D ctx->attr; if (a->flags & ATTR_COMPRESSION_MASK) { - ntfs_error(sb, "Attribute list attribute is " - "compressed.%s", es); + ntfs_error(sb, + "Attribute list attribute is compressed.%s", + es); goto put_err_out; } if (a->flags & ATTR_IS_ENCRYPTED || a->flags & ATTR_IS_SPARSE) { if (a->non_resident) { - ntfs_error(sb, "Non-resident attribute list " - "attribute is encrypted/" - "sparse.%s", es); + ntfs_error(sb, + "Non-resident attribute list attribute is encrypted/sparse.%s", + es); goto put_err_out; } - ntfs_warning(sb, "Resident attribute list attribute " - "in $MFT system file is marked " - "encrypted/sparse which is not true. " - "However, Windows allows this and " - "chkdsk does not detect or correct it " - "so we will just ignore the invalid " - "flags and pretend they are not set."); + ntfs_warning(sb, + "Resident attribute list attribute in $MFT system file is marked encry= pted/sparse which is not true. However, Windows allows this and chkdsk doe= s not detect or correct it so we will just ignore the invalid flags and pre= tend they are not set."); } /* Now allocate memory for the attribute list. */ ni->attr_list_size =3D (u32)ntfs_attr_size(a); @@ -1892,89 +1970,75 @@ int ntfs_read_inode_mount(struct inode *vi) ntfs_error(sb, "Attr_list_size is zero"); goto put_err_out; } - ni->attr_list =3D ntfs_malloc_nofs(ni->attr_list_size); + ni->attr_list =3D kvzalloc(round_up(ni->attr_list_size, SECTOR_SIZE), + GFP_NOFS); if (!ni->attr_list) { - ntfs_error(sb, "Not enough memory to allocate buffer " - "for attribute list."); + ntfs_error(sb, "Not enough memory to allocate buffer for attribute list= ."); goto put_err_out; } if (a->non_resident) { + struct runlist_element *rl; + size_t new_rl_count; + NInoSetAttrListNonResident(ni); if (a->data.non_resident.lowest_vcn) { - ntfs_error(sb, "Attribute list has non zero " - "lowest_vcn. $MFT is corrupt. " - "You should run chkdsk."); + ntfs_error(sb, + "Attribute list has non zero lowest_vcn. $MFT is corrupt. You should = run chkdsk."); goto put_err_out; } - /* Setup the runlist. */ - ni->attr_list_rl.rl =3D ntfs_mapping_pairs_decompress(vol, - a, NULL); - if (IS_ERR(ni->attr_list_rl.rl)) { - err =3D PTR_ERR(ni->attr_list_rl.rl); - ni->attr_list_rl.rl =3D NULL; - ntfs_error(sb, "Mapping pairs decompression " - "failed with error code %i.", - -err); + + rl =3D ntfs_mapping_pairs_decompress(vol, a, NULL, &new_rl_count); + if (IS_ERR(rl)) { + err =3D PTR_ERR(rl); + ntfs_error(sb, + "Mapping pairs decompression failed with error code %i.", + -err); goto put_err_out; } - /* Now load the attribute list. */ - if ((err =3D load_attribute_list(vol, &ni->attr_list_rl, - ni->attr_list, ni->attr_list_size, - sle64_to_cpu(a->data. - non_resident.initialized_size)))) { - ntfs_error(sb, "Failed to load attribute list " - "attribute with error code %i.", - -err); + + err =3D load_attribute_list_mount(vol, rl, ni->attr_list, ni->attr_list= _size, + le64_to_cpu(a->data.non_resident.initialized_size)); + kvfree(rl); + if (err) { + ntfs_error(sb, + "Failed to load attribute list with error code %i.", + -err); goto put_err_out; } } else /* if (!ctx.attr->non_resident) */ { - if ((u8*)a + le16_to_cpu( + if ((u8 *)a + le16_to_cpu( a->data.resident.value_offset) + - le32_to_cpu( - a->data.resident.value_length) > - (u8*)ctx->mrec + vol->mft_record_size) { - ntfs_error(sb, "Corrupt attribute list " - "attribute."); + le32_to_cpu(a->data.resident.value_length) > + (u8 *)ctx->mrec + vol->mft_record_size) { + ntfs_error(sb, "Corrupt attribute list attribute."); goto put_err_out; } /* Now copy the attribute list. */ - memcpy(ni->attr_list, (u8*)a + le16_to_cpu( + memcpy(ni->attr_list, (u8 *)a + le16_to_cpu( a->data.resident.value_offset), - le32_to_cpu( - a->data.resident.value_length)); + le32_to_cpu(a->data.resident.value_length)); } /* The attribute list is now setup in memory. */ - /* - * FIXME: I don't know if this case is actually possible. - * According to logic it is not possible but I have seen too - * many weird things in MS software to rely on logic... Thus we - * perform a manual search and make sure the first $MFT/$DATA - * extent is in the base inode. If it is not we abort with an - * error and if we ever see a report of this error we will need - * to do some magic in order to have the necessary mft record - * loaded and in the right place in the page cache. But - * hopefully logic will prevail and this never happens... - */ - al_entry =3D (ATTR_LIST_ENTRY*)ni->attr_list; - al_end =3D (u8*)al_entry + ni->attr_list_size; + al_entry =3D (struct attr_list_entry *)ni->attr_list; + al_end =3D (u8 *)al_entry + ni->attr_list_size; for (;; al_entry =3D next_al_entry) { /* Out of bounds check. */ - if ((u8*)al_entry < ni->attr_list || - (u8*)al_entry > al_end) + if ((u8 *)al_entry < ni->attr_list || + (u8 *)al_entry > al_end) goto em_put_err_out; /* Catch the end of the attribute list. */ - if ((u8*)al_entry =3D=3D al_end) + if ((u8 *)al_entry =3D=3D al_end) goto em_put_err_out; if (!al_entry->length) goto em_put_err_out; - if ((u8*)al_entry + 6 > al_end || (u8*)al_entry + - le16_to_cpu(al_entry->length) > al_end) + if ((u8 *)al_entry + 6 > al_end || + (u8 *)al_entry + le16_to_cpu(al_entry->length) > al_end) goto em_put_err_out; - next_al_entry =3D (ATTR_LIST_ENTRY*)((u8*)al_entry + + next_al_entry =3D (struct attr_list_entry *)((u8 *)al_entry + le16_to_cpu(al_entry->length)); if (le32_to_cpu(al_entry->type) > le32_to_cpu(AT_DATA)) goto em_put_err_out; - if (AT_DATA !=3D al_entry->type) + if (al_entry->type !=3D AT_DATA) continue; /* We want an unnamed attribute. */ if (al_entry->name_length) @@ -1985,12 +2049,8 @@ int ntfs_read_inode_mount(struct inode *vi) /* First entry has to be in the base mft record. */ if (MREF_LE(al_entry->mft_reference) !=3D vi->i_ino) { /* MFT references do not match, logic fails. */ - ntfs_error(sb, "BUG: The first $DATA extent " - "of $MFT is not in the base " - "mft record. Please report " - "you saw this message to " - "linux-ntfs-dev@lists." - "sourceforge.net"); + ntfs_error(sb, + "BUG: The first $DATA extent of $MFT is not in the base mft record."); goto put_err_out; } else { /* Sequence numbers must match. */ @@ -2010,26 +2070,22 @@ int ntfs_read_inode_mount(struct inode *vi) next_vcn =3D last_vcn =3D highest_vcn =3D 0; while (!(err =3D ntfs_attr_lookup(AT_DATA, NULL, 0, 0, next_vcn, NULL, 0, ctx))) { - runlist_element *nrl; + struct runlist_element *nrl; =20 /* Cache the current attribute. */ a =3D ctx->attr; /* $MFT must be non-resident. */ if (!a->non_resident) { - ntfs_error(sb, "$MFT must be non-resident but a " - "resident extent was found. $MFT is " - "corrupt. Run chkdsk."); + ntfs_error(sb, + "$MFT must be non-resident but a resident extent was found. $MFT is co= rrupt. Run chkdsk."); goto put_err_out; } /* $MFT must be uncompressed and unencrypted. */ if (a->flags & ATTR_COMPRESSION_MASK || a->flags & ATTR_IS_ENCRYPTED || a->flags & ATTR_IS_SPARSE) { - ntfs_error(sb, "$MFT must be uncompressed, " - "non-sparse, and unencrypted but a " - "compressed/sparse/encrypted extent " - "was found. $MFT is corrupt. Run " - "chkdsk."); + ntfs_error(sb, + "$MFT must be uncompressed, non-sparse, and unencrypted but a compress= ed/sparse/encrypted extent was found. $MFT is corrupt. Run chkdsk."); goto put_err_out; } /* @@ -2038,35 +2094,31 @@ int ntfs_read_inode_mount(struct inode *vi) * as we have exclusive access to the inode at this time and we * are a mount in progress task, too. */ - nrl =3D ntfs_mapping_pairs_decompress(vol, a, ni->runlist.rl); + nrl =3D ntfs_mapping_pairs_decompress(vol, a, &ni->runlist, + &new_rl_count); if (IS_ERR(nrl)) { - ntfs_error(sb, "ntfs_mapping_pairs_decompress() " - "failed with error code %ld. $MFT is " - "corrupt.", PTR_ERR(nrl)); + ntfs_error(sb, + "ntfs_mapping_pairs_decompress() failed with error code %ld.", + PTR_ERR(nrl)); goto put_err_out; } ni->runlist.rl =3D nrl; + ni->runlist.count =3D new_rl_count; =20 /* Are we in the first extent? */ if (!next_vcn) { if (a->data.non_resident.lowest_vcn) { - ntfs_error(sb, "First extent of $DATA " - "attribute has non zero " - "lowest_vcn. $MFT is corrupt. " - "You should run chkdsk."); + ntfs_error(sb, + "First extent of $DATA attribute has non zero lowest_vcn. $MFT is cor= rupt. You should run chkdsk."); goto put_err_out; } /* Get the last vcn in the $DATA attribute. */ - last_vcn =3D sle64_to_cpu( - a->data.non_resident.allocated_size) - >> vol->cluster_size_bits; + last_vcn =3D ntfs_bytes_to_cluster(vol, + le64_to_cpu(a->data.non_resident.allocated_size)); /* Fill in the inode size. */ - vi->i_size =3D sle64_to_cpu( - a->data.non_resident.data_size); - ni->initialized_size =3D sle64_to_cpu( - a->data.non_resident.initialized_size); - ni->allocated_size =3D sle64_to_cpu( - a->data.non_resident.allocated_size); + vi->i_size =3D le64_to_cpu(a->data.non_resident.data_size); + ni->initialized_size =3D le64_to_cpu(a->data.non_resident.initialized_s= ize); + ni->allocated_size =3D le64_to_cpu(a->data.non_resident.allocated_size); /* * Verify the number of mft records does not exceed * 2^32 - 1. @@ -2095,18 +2147,12 @@ int ntfs_read_inode_mount(struct inode *vi) * ntfs_read_inode() on extents of $MFT/$DATA. But lets * hope this never happens... */ - ntfs_read_locked_inode(vi); - if (is_bad_inode(vi)) { - ntfs_error(sb, "ntfs_read_inode() of $MFT " - "failed. BUG or corrupt $MFT. " - "Run chkdsk and if no errors " - "are found, please report you " - "saw this message to " - "linux-ntfs-dev@lists." - "sourceforge.net"); + err =3D ntfs_read_locked_inode(vi); + if (err) { + ntfs_error(sb, "ntfs_read_inode() of $MFT failed.\n"); ntfs_attr_put_search_ctx(ctx); /* Revert to the safe super operations. */ - ntfs_free(m); + kfree(m); return -1; } /* @@ -2124,7 +2170,7 @@ int ntfs_read_inode_mount(struct inode *vi) } =20 /* Get the lowest vcn for the next extent. */ - highest_vcn =3D sle64_to_cpu(a->data.non_resident.highest_vcn); + highest_vcn =3D le64_to_cpu(a->data.non_resident.highest_vcn); next_vcn =3D highest_vcn + 1; =20 /* Only one extent or error, which we catch below. */ @@ -2132,27 +2178,21 @@ int ntfs_read_inode_mount(struct inode *vi) break; =20 /* Avoid endless loops due to corruption. */ - if (next_vcn < sle64_to_cpu( - a->data.non_resident.lowest_vcn)) { - ntfs_error(sb, "$MFT has corrupt attribute list " - "attribute. Run chkdsk."); + if (next_vcn < le64_to_cpu(a->data.non_resident.lowest_vcn)) { + ntfs_error(sb, "$MFT has corrupt attribute list attribute. Run chkdsk."= ); goto put_err_out; } } if (err !=3D -ENOENT) { - ntfs_error(sb, "Failed to lookup $MFT/$DATA attribute extent. " - "$MFT is corrupt. Run chkdsk."); + ntfs_error(sb, "Failed to lookup $MFT/$DATA attribute extent. Run chkdsk= .\n"); goto put_err_out; } if (!a) { - ntfs_error(sb, "$MFT/$DATA attribute not found. $MFT is " - "corrupt. Run chkdsk."); + ntfs_error(sb, "$MFT/$DATA attribute not found. $MFT is corrupt. Run chk= dsk."); goto put_err_out; } if (highest_vcn && highest_vcn !=3D last_vcn - 1) { - ntfs_error(sb, "Failed to load the complete runlist for " - "$MFT/$DATA. Driver bug or corrupt $MFT. " - "Run chkdsk."); + ntfs_error(sb, "Failed to load the complete runlist for $MFT/$DATA. Run = chkdsk."); ntfs_debug("highest_vcn =3D 0x%llx, last_vcn - 1 =3D 0x%llx", (unsigned long long)highest_vcn, (unsigned long long)last_vcn - 1); @@ -2160,7 +2200,7 @@ int ntfs_read_inode_mount(struct inode *vi) } ntfs_attr_put_search_ctx(ctx); ntfs_debug("Done."); - ntfs_free(m); + kfree(m); =20 /* * Split the locking rules of the MFT inode from the @@ -2172,69 +2212,78 @@ int ntfs_read_inode_mount(struct inode *vi) return 0; =20 em_put_err_out: - ntfs_error(sb, "Couldn't find first extent of $DATA attribute in " - "attribute list. $MFT is corrupt. Run chkdsk."); + ntfs_error(sb, + "Couldn't find first extent of $DATA attribute in attribute list. $MFT i= s corrupt. Run chkdsk."); put_err_out: ntfs_attr_put_search_ctx(ctx); err_out: ntfs_error(sb, "Failed. Marking inode as bad."); - make_bad_inode(vi); - ntfs_free(m); + kfree(m); return -1; } =20 -static void __ntfs_clear_inode(ntfs_inode *ni) +static void __ntfs_clear_inode(struct ntfs_inode *ni) { /* Free all alocated memory. */ - down_write(&ni->runlist.lock); - if (ni->runlist.rl) { - ntfs_free(ni->runlist.rl); + if (NInoNonResident(ni) && ni->runlist.rl) { + kvfree(ni->runlist.rl); ni->runlist.rl =3D NULL; } - up_write(&ni->runlist.lock); =20 if (ni->attr_list) { - ntfs_free(ni->attr_list); + kvfree(ni->attr_list); ni->attr_list =3D NULL; } =20 - down_write(&ni->attr_list_rl.lock); - if (ni->attr_list_rl.rl) { - ntfs_free(ni->attr_list_rl.rl); - ni->attr_list_rl.rl =3D NULL; - } - up_write(&ni->attr_list_rl.lock); - - if (ni->name_len && ni->name !=3D I30) { - /* Catch bugs... */ - BUG_ON(!ni->name); + if (ni->name_len && ni->name !=3D I30 && + ni->name !=3D reparse_index_name && + ni->name !=3D objid_index_name) { + WARN_ON(!ni->name); kfree(ni->name); } } =20 -void ntfs_clear_extent_inode(ntfs_inode *ni) +void ntfs_clear_extent_inode(struct ntfs_inode *ni) { ntfs_debug("Entering for inode 0x%lx.", ni->mft_no); =20 - BUG_ON(NInoAttr(ni)); - BUG_ON(ni->nr_extents !=3D -1); - -#ifdef NTFS_RW - if (NInoDirty(ni)) { - if (!is_bad_inode(VFS_I(ni->ext.base_ntfs_ino))) - ntfs_error(ni->vol->sb, "Clearing dirty extent inode! " - "Losing data! This is a BUG!!!"); - // FIXME: Do something!!! - } -#endif /* NTFS_RW */ + WARN_ON(NInoAttr(ni)); + WARN_ON(ni->nr_extents !=3D -1); =20 __ntfs_clear_inode(ni); - - /* Bye, bye... */ ntfs_destroy_extent_inode(ni); } =20 -/** +static int ntfs_delete_base_inode(struct ntfs_inode *ni) +{ + struct super_block *sb =3D ni->vol->sb; + int err; + + if (NInoAttr(ni) || ni->nr_extents =3D=3D -1) + return 0; + + err =3D ntfs_non_resident_dealloc_clusters(ni); + + /* + * Deallocate extent mft records and free extent inodes. + * No need to lock as no one else has a reference. + */ + while (ni->nr_extents) { + err =3D ntfs_mft_record_free(ni->vol, *(ni->ext.extent_ntfs_inos)); + if (err) + ntfs_error(sb, + "Failed to free extent MFT record. Leaving inconsistent metadata.\n"); + ntfs_inode_close(*(ni->ext.extent_ntfs_inos)); + } + + /* Deallocate base mft record */ + err =3D ntfs_mft_record_free(ni->vol, ni); + if (err) + ntfs_error(sb, "Failed to free base MFT record. Leaving inconsistent met= adata.\n"); + return err; +} + +/* * ntfs_evict_big_inode - clean up the ntfs specific part of an inode * @vi: vfs inode pending annihilation * @@ -2246,35 +2295,45 @@ void ntfs_clear_extent_inode(ntfs_inode *ni) */ void ntfs_evict_big_inode(struct inode *vi) { - ntfs_inode *ni =3D NTFS_I(vi); + struct ntfs_inode *ni =3D NTFS_I(vi); =20 truncate_inode_pages_final(&vi->i_data); - clear_inode(vi); =20 -#ifdef NTFS_RW - if (NInoDirty(ni)) { - bool was_bad =3D (is_bad_inode(vi)); + if (!vi->i_nlink) { + if (!NInoAttr(ni)) { + /* Never called with extent inodes */ + WARN_ON(ni->nr_extents =3D=3D -1); + ntfs_delete_base_inode(ni); + } + goto release; + } =20 + if (NInoDirty(ni)) { /* Committing the inode also commits all extent inodes. */ ntfs_commit_inode(vi); =20 - if (!was_bad && (is_bad_inode(vi) || NInoDirty(ni))) { - ntfs_error(vi->i_sb, "Failed to commit dirty inode " - "0x%lx. Losing data!", vi->i_ino); - // FIXME: Do something!!! + if (NInoDirty(ni)) { + ntfs_debug("Failed to commit dirty inode 0x%lx. Losing data!", + vi->i_ino); + NInoClearAttrListDirty(ni); + NInoClearDirty(ni); } } -#endif /* NTFS_RW */ =20 /* No need to lock at this stage as no one else has a reference. */ if (ni->nr_extents > 0) { int i; =20 - for (i =3D 0; i < ni->nr_extents; i++) - ntfs_clear_extent_inode(ni->ext.extent_ntfs_inos[i]); - kfree(ni->ext.extent_ntfs_inos); + for (i =3D 0; i < ni->nr_extents; i++) { + if (ni->ext.extent_ntfs_inos[i]) + ntfs_clear_extent_inode(ni->ext.extent_ntfs_inos[i]); + } + ni->nr_extents =3D 0; + kvfree(ni->ext.extent_ntfs_inos); } =20 +release: + clear_inode(vi); __ntfs_clear_inode(ni); =20 if (NInoAttr(ni)) { @@ -2285,13 +2344,16 @@ void ntfs_evict_big_inode(struct inode *vi) ni->ext.base_ntfs_ino =3D NULL; } } - BUG_ON(ni->page); + if (!atomic_dec_and_test(&ni->count)) - BUG(); - return; + WARN_ON(1); + if (ni->folio) + folio_put(ni->folio); + kfree(ni->mrec); + kvfree(ni->target); } =20 -/** +/* * ntfs_show_options - show mount options in /proc/mounts * @sf: seq_file in which to write our mount options * @root: root of the mounted tree whose mount options to display @@ -2303,640 +2365,363 @@ void ntfs_evict_big_inode(struct inode *vi) */ int ntfs_show_options(struct seq_file *sf, struct dentry *root) { - ntfs_volume *vol =3D NTFS_SB(root->d_sb); + struct ntfs_volume *vol =3D NTFS_SB(root->d_sb); int i; =20 - seq_printf(sf, ",uid=3D%i", from_kuid_munged(&init_user_ns, vol->uid)); - seq_printf(sf, ",gid=3D%i", from_kgid_munged(&init_user_ns, vol->gid)); + if (uid_valid(vol->uid)) + seq_printf(sf, ",uid=3D%i", from_kuid_munged(&init_user_ns, vol->uid)); + if (gid_valid(vol->gid)) + seq_printf(sf, ",gid=3D%i", from_kgid_munged(&init_user_ns, vol->gid)); if (vol->fmask =3D=3D vol->dmask) seq_printf(sf, ",umask=3D0%o", vol->fmask); else { seq_printf(sf, ",fmask=3D0%o", vol->fmask); seq_printf(sf, ",dmask=3D0%o", vol->dmask); } - seq_printf(sf, ",nls=3D%s", vol->nls_map->charset); + seq_printf(sf, ",iocharset=3D%s", vol->nls_map->charset); if (NVolCaseSensitive(vol)) - seq_printf(sf, ",case_sensitive"); + seq_puts(sf, ",case_sensitive"); + else + seq_puts(sf, ",nocase"); if (NVolShowSystemFiles(vol)) - seq_printf(sf, ",show_sys_files"); - if (!NVolSparseEnabled(vol)) - seq_printf(sf, ",disable_sparse"); + seq_puts(sf, ",show_sys_files,showmeta"); for (i =3D 0; on_errors_arr[i].val; i++) { - if (on_errors_arr[i].val & vol->on_errors) + if (on_errors_arr[i].val =3D=3D vol->on_errors) seq_printf(sf, ",errors=3D%s", on_errors_arr[i].str); } seq_printf(sf, ",mft_zone_multiplier=3D%i", vol->mft_zone_multiplier); + if (NVolSysImmutable(vol)) + seq_puts(sf, ",sys_immutable"); + if (!NVolShowHiddenFiles(vol)) + seq_puts(sf, ",nohidden"); + if (NVolHideDotFiles(vol)) + seq_puts(sf, ",hide_dot_files"); + if (NVolCheckWindowsNames(vol)) + seq_puts(sf, ",windows_names"); + if (NVolDiscard(vol)) + seq_puts(sf, ",discard"); + if (NVolDisableSparse(vol)) + seq_puts(sf, ",disable_sparse"); + if (vol->sb->s_flags & SB_POSIXACL) + seq_puts(sf, ",acl"); return 0; } =20 -#ifdef NTFS_RW - -static const char *es =3D " Leaving inconsistent metadata. Unmount and r= un " - "chkdsk."; - -/** - * ntfs_truncate - called when the i_size of an ntfs inode is changed - * @vi: inode for which the i_size was changed - * - * We only support i_size changes for normal files at present, i.e. not - * compressed and not encrypted. This is enforced in ntfs_setattr(), see - * below. - * - * The kernel guarantees that @vi is a regular file (S_ISREG() is true) and - * that the change is allowed. - * - * This implies for us that @vi is a file inode rather than a directory, i= ndex, - * or attribute inode as well as that @vi is a base inode. - * - * Returns 0 on success or -errno on error. - * - * Called with ->i_mutex held. - */ -int ntfs_truncate(struct inode *vi) +int ntfs_extend_initialized_size(struct inode *vi, const loff_t offset, + const loff_t new_size, bool bsync) { - s64 new_size, old_size, nr_freed, new_alloc_size, old_alloc_size; - VCN highest_vcn; + struct ntfs_inode *ni =3D NTFS_I(vi); + loff_t old_init_size; unsigned long flags; - ntfs_inode *base_ni, *ni =3D NTFS_I(vi); - ntfs_volume *vol =3D ni->vol; - ntfs_attr_search_ctx *ctx; - MFT_RECORD *m; - ATTR_RECORD *a; - const char *te =3D " Leaving file length out of sync with i_size."; - int err, mp_size, size_change, alloc_change; - - ntfs_debug("Entering for inode 0x%lx.", vi->i_ino); - BUG_ON(NInoAttr(ni)); - BUG_ON(S_ISDIR(vi->i_mode)); - BUG_ON(NInoMstProtected(ni)); - BUG_ON(ni->nr_extents < 0); -retry_truncate: - /* - * Lock the runlist for writing and map the mft record to ensure it is - * safe to mess with the attribute runlist and sizes. - */ - down_write(&ni->runlist.lock); - if (!NInoAttr(ni)) - base_ni =3D ni; - else - base_ni =3D ni->ext.base_ntfs_ino; - m =3D map_mft_record(base_ni); - if (IS_ERR(m)) { - err =3D PTR_ERR(m); - ntfs_error(vi->i_sb, "Failed to map mft record for inode 0x%lx " - "(error code %d).%s", vi->i_ino, err, te); - ctx =3D NULL; - m =3D NULL; - goto old_bad_out; - } - ctx =3D ntfs_attr_get_search_ctx(base_ni, m); - if (unlikely(!ctx)) { - ntfs_error(vi->i_sb, "Failed to allocate a search context for " - "inode 0x%lx (not enough memory).%s", - vi->i_ino, te); - err =3D -ENOMEM; - goto old_bad_out; - } - err =3D ntfs_attr_lookup(ni->type, ni->name, ni->name_len, - CASE_SENSITIVE, 0, NULL, 0, ctx); - if (unlikely(err)) { - if (err =3D=3D -ENOENT) { - ntfs_error(vi->i_sb, "Open attribute is missing from " - "mft record. Inode 0x%lx is corrupt. " - "Run chkdsk.%s", vi->i_ino, te); - err =3D -EIO; - } else - ntfs_error(vi->i_sb, "Failed to lookup attribute in " - "inode 0x%lx (error code %d).%s", - vi->i_ino, err, te); - goto old_bad_out; - } - m =3D ctx->mrec; - a =3D ctx->attr; - /* - * The i_size of the vfs inode is the new size for the attribute value. - */ - new_size =3D i_size_read(vi); - /* The current size of the attribute value is the old size. */ - old_size =3D ntfs_attr_size(a); - /* Calculate the new allocated size. */ - if (NInoNonResident(ni)) - new_alloc_size =3D (new_size + vol->cluster_size - 1) & - ~(s64)vol->cluster_size_mask; - else - new_alloc_size =3D (new_size + 7) & ~7; - /* The current allocated size is the old allocated size. */ + int err; + read_lock_irqsave(&ni->size_lock, flags); - old_alloc_size =3D ni->allocated_size; + old_init_size =3D ni->initialized_size; read_unlock_irqrestore(&ni->size_lock, flags); - /* - * The change in the file size. This will be 0 if no change, >0 if the - * size is growing, and <0 if the size is shrinking. - */ - size_change =3D -1; - if (new_size - old_size >=3D 0) { - size_change =3D 1; - if (new_size =3D=3D old_size) - size_change =3D 0; - } - /* As above for the allocated size. */ - alloc_change =3D -1; - if (new_alloc_size - old_alloc_size >=3D 0) { - alloc_change =3D 1; - if (new_alloc_size =3D=3D old_alloc_size) - alloc_change =3D 0; - } - /* - * If neither the size nor the allocation are being changed there is - * nothing to do. - */ - if (!size_change && !alloc_change) - goto unm_done; - /* If the size is changing, check if new size is allowed in $AttrDef. */ - if (size_change) { - err =3D ntfs_attr_size_bounds_check(vol, ni->type, new_size); - if (unlikely(err)) { - if (err =3D=3D -ERANGE) { - ntfs_error(vol->sb, "Truncate would cause the " - "inode 0x%lx to %simum size " - "for its attribute type " - "(0x%x). Aborting truncate.", - vi->i_ino, - new_size > old_size ? "exceed " - "the max" : "go under the min", - le32_to_cpu(ni->type)); - err =3D -EFBIG; - } else { - ntfs_error(vol->sb, "Inode 0x%lx has unknown " - "attribute type 0x%x. " - "Aborting truncate.", - vi->i_ino, - le32_to_cpu(ni->type)); - err =3D -EIO; - } - /* Reset the vfs inode size to the old size. */ - i_size_write(vi, old_size); - goto err_out; - } - } - if (NInoCompressed(ni) || NInoEncrypted(ni)) { - ntfs_warning(vi->i_sb, "Changes in inode size are not " - "supported yet for %s files, ignoring.", - NInoCompressed(ni) ? "compressed" : - "encrypted"); - err =3D -EOPNOTSUPP; - goto bad_out; - } - if (a->non_resident) - goto do_non_resident_truncate; - BUG_ON(NInoNonResident(ni)); - /* Resize the attribute record to best fit the new attribute size. */ - if (new_size < vol->mft_record_size && - !ntfs_resident_attr_value_resize(m, a, new_size)) { - /* The resize succeeded! */ - flush_dcache_mft_record_page(ctx->ntfs_ino); - mark_mft_record_dirty(ctx->ntfs_ino); - write_lock_irqsave(&ni->size_lock, flags); - /* Update the sizes in the ntfs inode and all is done. */ - ni->allocated_size =3D le32_to_cpu(a->length) - - le16_to_cpu(a->data.resident.value_offset); - /* - * Note ntfs_resident_attr_value_resize() has already done any - * necessary data clearing in the attribute record. When the - * file is being shrunk vmtruncate() will already have cleared - * the top part of the last partial page, i.e. since this is - * the resident case this is the page with index 0. However, - * when the file is being expanded, the page cache page data - * between the old data_size, i.e. old_size, and the new_size - * has not been zeroed. Fortunately, we do not need to zero it - * either since on one hand it will either already be zero due - * to both read_folio and writepage clearing partial page data - * beyond i_size in which case there is nothing to do or in the - * case of the file being mmap()ped at the same time, POSIX - * specifies that the behaviour is unspecified thus we do not - * have to do anything. This means that in our implementation - * in the rare case that the file is mmap()ped and a write - * occurred into the mmap()ped region just beyond the file size - * and writepage has not yet been called to write out the page - * (which would clear the area beyond the file size) and we now - * extend the file size to incorporate this dirty region - * outside the file size, a write of the page would result in - * this data being written to disk instead of being cleared. - * Given both POSIX and the Linux mmap(2) man page specify that - * this corner case is undefined, we choose to leave it like - * that as this is much simpler for us as we cannot lock the - * relevant page now since we are holding too many ntfs locks - * which would result in a lock reversal deadlock. - */ - ni->initialized_size =3D new_size; - write_unlock_irqrestore(&ni->size_lock, flags); - goto unm_done; - } - /* If the above resize failed, this must be an attribute extension. */ - BUG_ON(size_change < 0); - /* - * We have to drop all the locks so we can call - * ntfs_attr_make_non_resident(). This could be optimised by try- - * locking the first page cache page and only if that fails dropping - * the locks, locking the page, and redoing all the locking and - * lookups. While this would be a huge optimisation, it is not worth - * it as this is definitely a slow code path as it only ever can happen - * once for any given file. - */ - ntfs_attr_put_search_ctx(ctx); - unmap_mft_record(base_ni); - up_write(&ni->runlist.lock); - /* - * Not enough space in the mft record, try to make the attribute - * non-resident and if successful restart the truncation process. - */ - err =3D ntfs_attr_make_non_resident(ni, old_size); - if (likely(!err)) - goto retry_truncate; - /* - * Could not make non-resident. If this is due to this not being - * permitted for this attribute type or there not being enough space, - * try to make other attributes non-resident. Otherwise fail. - */ - if (unlikely(err !=3D -EPERM && err !=3D -ENOSPC)) { - ntfs_error(vol->sb, "Cannot truncate inode 0x%lx, attribute " - "type 0x%x, because the conversion from " - "resident to non-resident attribute failed " - "with error code %i.", vi->i_ino, - (unsigned)le32_to_cpu(ni->type), err); - if (err !=3D -ENOMEM) - err =3D -EIO; - goto conv_err_out; - } - /* TODO: Not implemented from here, abort. */ - if (err =3D=3D -ENOSPC) - ntfs_error(vol->sb, "Not enough space in the mft record/on " - "disk for the non-resident attribute value. " - "This case is not implemented yet."); - else /* if (err =3D=3D -EPERM) */ - ntfs_error(vol->sb, "This attribute type may not be " - "non-resident. This case is not implemented " - "yet."); - err =3D -EOPNOTSUPP; - goto conv_err_out; -#if 0 - // TODO: Attempt to make other attributes non-resident. - if (!err) - goto do_resident_extend; - /* - * Both the attribute list attribute and the standard information - * attribute must remain in the base inode. Thus, if this is one of - * these attributes, we have to try to move other attributes out into - * extent mft records instead. - */ - if (ni->type =3D=3D AT_ATTRIBUTE_LIST || - ni->type =3D=3D AT_STANDARD_INFORMATION) { - // TODO: Attempt to move other attributes into extent mft - // records. - err =3D -EOPNOTSUPP; - if (!err) - goto do_resident_extend; - goto err_out; - } - // TODO: Attempt to move this attribute to an extent mft record, but - // only if it is not already the only attribute in an mft record in - // which case there would be nothing to gain. - err =3D -EOPNOTSUPP; - if (!err) - goto do_resident_extend; - /* There is nothing we can do to make enough space. )-: */ - goto err_out; -#endif -do_non_resident_truncate: - BUG_ON(!NInoNonResident(ni)); - if (alloc_change < 0) { - highest_vcn =3D sle64_to_cpu(a->data.non_resident.highest_vcn); - if (highest_vcn > 0 && - old_alloc_size >> vol->cluster_size_bits > - highest_vcn + 1) { - /* - * This attribute has multiple extents. Not yet - * supported. - */ - ntfs_error(vol->sb, "Cannot truncate inode 0x%lx, " - "attribute type 0x%x, because the " - "attribute is highly fragmented (it " - "consists of multiple extents) and " - "this case is not implemented yet.", - vi->i_ino, - (unsigned)le32_to_cpu(ni->type)); - err =3D -EOPNOTSUPP; - goto bad_out; - } - } - /* - * If the size is shrinking, need to reduce the initialized_size and - * the data_size before reducing the allocation. - */ - if (size_change < 0) { - /* - * Make the valid size smaller (i_size is already up-to-date). - */ - write_lock_irqsave(&ni->size_lock, flags); - if (new_size < ni->initialized_size) { - ni->initialized_size =3D new_size; - a->data.non_resident.initialized_size =3D - cpu_to_sle64(new_size); - } - a->data.non_resident.data_size =3D cpu_to_sle64(new_size); - write_unlock_irqrestore(&ni->size_lock, flags); - flush_dcache_mft_record_page(ctx->ntfs_ino); - mark_mft_record_dirty(ctx->ntfs_ino); - /* If the allocated size is not changing, we are done. */ - if (!alloc_change) - goto unm_done; - /* - * If the size is shrinking it makes no sense for the - * allocation to be growing. - */ - BUG_ON(alloc_change > 0); - } else /* if (size_change >=3D 0) */ { - /* - * The file size is growing or staying the same but the - * allocation can be shrinking, growing or staying the same. - */ - if (alloc_change > 0) { - /* - * We need to extend the allocation and possibly update - * the data size. If we are updating the data size, - * since we are not touching the initialized_size we do - * not need to worry about the actual data on disk. - * And as far as the page cache is concerned, there - * will be no pages beyond the old data size and any - * partial region in the last page between the old and - * new data size (or the end of the page if the new - * data size is outside the page) does not need to be - * modified as explained above for the resident - * attribute truncate case. To do this, we simply drop - * the locks we hold and leave all the work to our - * friendly helper ntfs_attr_extend_allocation(). - */ - ntfs_attr_put_search_ctx(ctx); - unmap_mft_record(base_ni); - up_write(&ni->runlist.lock); - err =3D ntfs_attr_extend_allocation(ni, new_size, - size_change > 0 ? new_size : -1, -1); - /* - * ntfs_attr_extend_allocation() will have done error - * output already. - */ - goto done; - } - if (!alloc_change) - goto alloc_done; - } - /* alloc_change < 0 */ - /* Free the clusters. */ - nr_freed =3D ntfs_cluster_free(ni, new_alloc_size >> - vol->cluster_size_bits, -1, ctx); - m =3D ctx->mrec; - a =3D ctx->attr; - if (unlikely(nr_freed < 0)) { - ntfs_error(vol->sb, "Failed to release cluster(s) (error code " - "%lli). Unmount and run chkdsk to recover " - "the lost cluster(s).", (long long)nr_freed); - NVolSetErrors(vol); - nr_freed =3D 0; - } - /* Truncate the runlist. */ - err =3D ntfs_rl_truncate_nolock(vol, &ni->runlist, - new_alloc_size >> vol->cluster_size_bits); - /* - * If the runlist truncation failed and/or the search context is no - * longer valid, we cannot resize the attribute record or build the - * mapping pairs array thus we mark the inode bad so that no access to - * the freed clusters can happen. - */ - if (unlikely(err || IS_ERR(m))) { - ntfs_error(vol->sb, "Failed to %s (error code %li).%s", - IS_ERR(m) ? - "restore attribute search context" : - "truncate attribute runlist", - IS_ERR(m) ? PTR_ERR(m) : err, es); - err =3D -EIO; - goto bad_out; - } - /* Get the size for the shrunk mapping pairs array for the runlist. */ - mp_size =3D ntfs_get_size_for_mapping_pairs(vol, ni->runlist.rl, 0, -1); - if (unlikely(mp_size <=3D 0)) { - ntfs_error(vol->sb, "Cannot shrink allocation of inode 0x%lx, " - "attribute type 0x%x, because determining the " - "size for the mapping pairs failed with error " - "code %i.%s", vi->i_ino, - (unsigned)le32_to_cpu(ni->type), mp_size, es); - err =3D -EIO; - goto bad_out; - } - /* - * Shrink the attribute record for the new mapping pairs array. Note, - * this cannot fail since we are making the attribute smaller thus by - * definition there is enough space to do so. - */ - err =3D ntfs_attr_record_resize(m, a, mp_size + - le16_to_cpu(a->data.non_resident.mapping_pairs_offset)); - BUG_ON(err); - /* - * Generate the mapping pairs array directly into the attribute record. - */ - err =3D ntfs_mapping_pairs_build(vol, (u8*)a + - le16_to_cpu(a->data.non_resident.mapping_pairs_offset), - mp_size, ni->runlist.rl, 0, -1, NULL); - if (unlikely(err)) { - ntfs_error(vol->sb, "Cannot shrink allocation of inode 0x%lx, " - "attribute type 0x%x, because building the " - "mapping pairs failed with error code %i.%s", - vi->i_ino, (unsigned)le32_to_cpu(ni->type), - err, es); - err =3D -EIO; - goto bad_out; - } - /* Update the allocated/compressed size as well as the highest vcn. */ - a->data.non_resident.highest_vcn =3D cpu_to_sle64((new_alloc_size >> - vol->cluster_size_bits) - 1); - write_lock_irqsave(&ni->size_lock, flags); - ni->allocated_size =3D new_alloc_size; - a->data.non_resident.allocated_size =3D cpu_to_sle64(new_alloc_size); - if (NInoSparse(ni) || NInoCompressed(ni)) { - if (nr_freed) { - ni->itype.compressed.size -=3D nr_freed << - vol->cluster_size_bits; - BUG_ON(ni->itype.compressed.size < 0); - a->data.non_resident.compressed_size =3D cpu_to_sle64( - ni->itype.compressed.size); - vi->i_blocks =3D ni->itype.compressed.size >> 9; - } - } else - vi->i_blocks =3D new_alloc_size >> 9; - write_unlock_irqrestore(&ni->size_lock, flags); - /* - * We have shrunk the allocation. If this is a shrinking truncate we - * have already dealt with the initialized_size and the data_size above - * and we are done. If the truncate is only changing the allocation - * and not the data_size, we are also done. If this is an extending - * truncate, need to extend the data_size now which is ensured by the - * fact that @size_change is positive. - */ -alloc_done: - /* - * If the size is growing, need to update it now. If it is shrinking, - * we have already updated it above (before the allocation change). - */ - if (size_change > 0) - a->data.non_resident.data_size =3D cpu_to_sle64(new_size); - /* Ensure the modified mft record is written out. */ - flush_dcache_mft_record_page(ctx->ntfs_ino); - mark_mft_record_dirty(ctx->ntfs_ino); -unm_done: - ntfs_attr_put_search_ctx(ctx); - unmap_mft_record(base_ni); - up_write(&ni->runlist.lock); -done: - /* Update the mtime and ctime on the base inode. */ - /* normally ->truncate shouldn't update ctime or mtime, - * but ntfs did before so it got a copy & paste version - * of file_update_time. one day someone should fix this - * for real. - */ - if (!IS_NOCMTIME(VFS_I(base_ni)) && !IS_RDONLY(VFS_I(base_ni))) { - struct timespec64 now =3D current_time(VFS_I(base_ni)); - struct timespec64 ctime =3D inode_get_ctime(VFS_I(base_ni)); - struct timespec64 mtime =3D inode_get_mtime(VFS_I(base_ni)); - int sync_it =3D 0; =20 - if (!timespec64_equal(&mtime, &now) || - !timespec64_equal(&ctime, &now)) - sync_it =3D 1; - inode_set_ctime_to_ts(VFS_I(base_ni), now); - inode_set_mtime_to_ts(VFS_I(base_ni), now); + if (!NInoNonResident(ni)) + return -EINVAL; + if (old_init_size >=3D new_size) + return 0; =20 - if (sync_it) - mark_inode_dirty_sync(VFS_I(base_ni)); - } + err =3D ntfs_attr_map_whole_runlist(ni); + if (err) + return err; =20 - if (likely(!err)) { - NInoClearTruncateFailed(ni); - ntfs_debug("Done."); + if (!NInoCompressed(ni) && old_init_size < offset) { + err =3D iomap_zero_range(vi, old_init_size, + offset - old_init_size, + NULL, &ntfs_seek_iomap_ops, + &ntfs_iomap_folio_ops, NULL); + if (err) + return err; + if (bsync) + err =3D filemap_write_and_wait_range(vi->i_mapping, + old_init_size, + offset - 1); } + + + mutex_lock(&ni->mrec_lock); + err =3D ntfs_attr_set_initialized_size(ni, new_size); + mutex_unlock(&ni->mrec_lock); + if (err) + truncate_setsize(vi, old_init_size); return err; -old_bad_out: - old_size =3D -1; -bad_out: - if (err !=3D -ENOMEM && err !=3D -EOPNOTSUPP) - NVolSetErrors(vol); - if (err !=3D -EOPNOTSUPP) - NInoSetTruncateFailed(ni); - else if (old_size >=3D 0) - i_size_write(vi, old_size); -err_out: - if (ctx) - ntfs_attr_put_search_ctx(ctx); - if (m) - unmap_mft_record(base_ni); - up_write(&ni->runlist.lock); -out: - ntfs_debug("Failed. Returning error code %i.", err); - return err; -conv_err_out: - if (err !=3D -ENOMEM && err !=3D -EOPNOTSUPP) - NVolSetErrors(vol); - if (err !=3D -EOPNOTSUPP) - NInoSetTruncateFailed(ni); - else - i_size_write(vi, old_size); - goto out; } =20 -/** - * ntfs_truncate_vfs - wrapper for ntfs_truncate() that has no return value - * @vi: inode for which the i_size was changed - * - * Wrapper for ntfs_truncate() that has no return value. - * - * See ntfs_truncate() description above for details. - */ -#ifdef NTFS_RW -void ntfs_truncate_vfs(struct inode *vi) { - ntfs_truncate(vi); +int ntfs_truncate_vfs(struct inode *vi, loff_t new_size, loff_t i_size) +{ + struct ntfs_inode *ni =3D NTFS_I(vi); + int err; + + mutex_lock(&ni->mrec_lock); + err =3D __ntfs_attr_truncate_vfs(ni, new_size, i_size); + mutex_unlock(&ni->mrec_lock); + if (err < 0) + return err; + + inode_set_mtime_to_ts(vi, inode_set_ctime_current(vi)); + return 0; } -#endif =20 -/** - * ntfs_setattr - called from notify_change() when an attribute is being c= hanged - * @idmap: idmap of the mount the inode was found from - * @dentry: dentry whose attributes to change - * @attr: structure describing the attributes and the changes - * - * We have to trap VFS attempts to truncate the file described by @dentry = as - * soon as possible, because we do not implement changes in i_size yet. S= o we - * abort all i_size changes here. - * - * We also abort all changes of user, group, and mode as we do not impleme= nt - * the NTFS ACLs yet. +/* + * ntfs_inode_sync_standard_information - update standard information attr= ibute + * @vi: inode to update standard information + * @m: mft record * - * Called with ->i_mutex held. + * Return 0 on success or -errno on error. */ -int ntfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, - struct iattr *attr) +static int ntfs_inode_sync_standard_information(struct inode *vi, struct m= ft_record *m) { - struct inode *vi =3D d_inode(dentry); - int err; - unsigned int ia_valid =3D attr->ia_valid; + struct ntfs_inode *ni =3D NTFS_I(vi); + struct ntfs_attr_search_ctx *ctx; + struct standard_information *si; + __le64 nt; + int err =3D 0; + bool modified =3D false; =20 - err =3D setattr_prepare(&nop_mnt_idmap, dentry, attr); - if (err) - goto out; - /* We do not support NTFS ACLs yet. */ - if (ia_valid & (ATTR_UID | ATTR_GID | ATTR_MODE)) { - ntfs_warning(vi->i_sb, "Changes in user/group/mode are not " - "supported yet, ignoring."); - err =3D -EOPNOTSUPP; - goto out; + /* Update the access times in the standard information attribute. */ + ctx =3D ntfs_attr_get_search_ctx(ni, m); + if (unlikely(!ctx)) + return -ENOMEM; + err =3D ntfs_attr_lookup(AT_STANDARD_INFORMATION, NULL, 0, + CASE_SENSITIVE, 0, NULL, 0, ctx); + if (unlikely(err)) { + ntfs_attr_put_search_ctx(ctx); + return err; } - if (ia_valid & ATTR_SIZE) { - if (attr->ia_size !=3D i_size_read(vi)) { - ntfs_inode *ni =3D NTFS_I(vi); - /* - * FIXME: For now we do not support resizing of - * compressed or encrypted files yet. - */ - if (NInoCompressed(ni) || NInoEncrypted(ni)) { - ntfs_warning(vi->i_sb, "Changes in inode size " - "are not supported yet for " - "%s files, ignoring.", - NInoCompressed(ni) ? - "compressed" : "encrypted"); - err =3D -EOPNOTSUPP; - } else { - truncate_setsize(vi, attr->ia_size); - ntfs_truncate_vfs(vi); - } - if (err || ia_valid =3D=3D ATTR_SIZE) - goto out; - } else { - /* - * We skipped the truncate but must still update - * timestamps. - */ - ia_valid |=3D ATTR_MTIME | ATTR_CTIME; - } + si =3D (struct standard_information *)((u8 *)ctx->attr + + le16_to_cpu(ctx->attr->data.resident.value_offset)); + if (si->file_attributes !=3D ni->flags) { + si->file_attributes =3D ni->flags; + modified =3D true; } - if (ia_valid & ATTR_ATIME) - inode_set_atime_to_ts(vi, attr->ia_atime); - if (ia_valid & ATTR_MTIME) - inode_set_mtime_to_ts(vi, attr->ia_mtime); - if (ia_valid & ATTR_CTIME) - inode_set_ctime_to_ts(vi, attr->ia_ctime); - mark_inode_dirty(vi); -out: + + /* Update the creation times if they have changed. */ + nt =3D utc2ntfs(ni->i_crtime); + if (si->creation_time !=3D nt) { + ntfs_debug("Updating creation time for inode 0x%lx: old =3D 0x%llx, new = =3D 0x%llx", + vi->i_ino, le64_to_cpu(si->creation_time), + le64_to_cpu(nt)); + si->creation_time =3D nt; + modified =3D true; + } + + /* Update the access times if they have changed. */ + nt =3D utc2ntfs(inode_get_mtime(vi)); + if (si->last_data_change_time !=3D nt) { + ntfs_debug("Updating mtime for inode 0x%lx: old =3D 0x%llx, new =3D 0x%l= lx", + vi->i_ino, le64_to_cpu(si->last_data_change_time), + le64_to_cpu(nt)); + si->last_data_change_time =3D nt; + modified =3D true; + } + + nt =3D utc2ntfs(inode_get_ctime(vi)); + if (si->last_mft_change_time !=3D nt) { + ntfs_debug("Updating ctime for inode 0x%lx: old =3D 0x%llx, new =3D 0x%l= lx", + vi->i_ino, le64_to_cpu(si->last_mft_change_time), + le64_to_cpu(nt)); + si->last_mft_change_time =3D nt; + modified =3D true; + } + nt =3D utc2ntfs(inode_get_atime(vi)); + if (si->last_access_time !=3D nt) { + ntfs_debug("Updating atime for inode 0x%lx: old =3D 0x%llx, new =3D 0x%l= lx", + vi->i_ino, + le64_to_cpu(si->last_access_time), + le64_to_cpu(nt)); + si->last_access_time =3D nt; + modified =3D true; + } + + /* + * If we just modified the standard information attribute we need to + * mark the mft record it is in dirty. We do this manually so that + * mark_inode_dirty() is not called which would redirty the inode and + * hence result in an infinite loop of trying to write the inode. + * There is no need to mark the base inode nor the base mft record + * dirty, since we are going to write this mft record below in any case + * and the base mft record may actually not have been modified so it + * might not need to be written out. + * NOTE: It is not a problem when the inode for $MFT itself is being + * written out as ntfs_mft_mark_dirty() will only set I_DIRTY_PAGES + * on the $MFT inode and hence ntfs_write_inode() will not be + * re-invoked because of it which in turn is ok since the dirtied mft + * record will be cleaned and written out to disk below, i.e. before + * this function returns. + */ + if (modified) + NInoSetDirty(ctx->ntfs_ino); + ntfs_attr_put_search_ctx(ctx); + + return err; +} + +/* + * ntfs_inode_sync_filename - update FILE_NAME attributes + * @ni: ntfs inode to update FILE_NAME attributes + * + * Update all FILE_NAME attributes for inode @ni in the index. + * + * Return 0 on success or error. + */ +int ntfs_inode_sync_filename(struct ntfs_inode *ni) +{ + struct inode *index_vi; + struct super_block *sb =3D VFS_I(ni)->i_sb; + struct ntfs_attr_search_ctx *ctx =3D NULL; + struct ntfs_index_context *ictx; + struct ntfs_inode *index_ni; + struct file_name_attr *fn; + struct file_name_attr *fnx; + struct reparse_point *rpp; + __le32 reparse_tag; + int err =3D 0; + unsigned long flags; + + ntfs_debug("Entering for inode %lld\n", (long long)ni->mft_no); + + ctx =3D ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) + return -ENOMEM; + + /* Collect the reparse tag, if any */ + reparse_tag =3D cpu_to_le32(0); + if (ni->flags & FILE_ATTR_REPARSE_POINT) { + if (!ntfs_attr_lookup(AT_REPARSE_POINT, NULL, + 0, CASE_SENSITIVE, 0, NULL, 0, ctx)) { + rpp =3D (struct reparse_point *)((u8 *)ctx->attr + + le16_to_cpu(ctx->attr->data.resident.value_offset)); + reparse_tag =3D rpp->reparse_tag; + } + ntfs_attr_reinit_search_ctx(ctx); + } + + /* Walk through all FILE_NAME attributes and update them. */ + while (!(err =3D ntfs_attr_lookup(AT_FILE_NAME, NULL, 0, 0, 0, NULL, 0, c= tx))) { + fn =3D (struct file_name_attr *)((u8 *)ctx->attr + + le16_to_cpu(ctx->attr->data.resident.value_offset)); + if (MREF_LE(fn->parent_directory) =3D=3D ni->mft_no) + continue; + + index_vi =3D ntfs_iget(sb, MREF_LE(fn->parent_directory)); + if (IS_ERR(index_vi)) { + ntfs_error(sb, "Failed to open inode %lld with index", + (long long)MREF_LE(fn->parent_directory)); + continue; + } + + index_ni =3D NTFS_I(index_vi); + + mutex_lock_nested(&index_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT); + if (NInoBeingDeleted(ni)) { + iput(index_vi); + mutex_unlock(&index_ni->mrec_lock); + continue; + } + + ictx =3D ntfs_index_ctx_get(index_ni, I30, 4); + if (!ictx) { + ntfs_error(sb, "Failed to get index ctx, inode %lld", + (long long)index_ni->mft_no); + iput(index_vi); + mutex_unlock(&index_ni->mrec_lock); + continue; + } + + err =3D ntfs_index_lookup(fn, sizeof(struct file_name_attr), ictx); + if (err) { + ntfs_debug("Index lookup failed, inode %lld", + (long long)index_ni->mft_no); + ntfs_index_ctx_put(ictx); + iput(index_vi); + mutex_unlock(&index_ni->mrec_lock); + continue; + } + /* Update flags and file size. */ + fnx =3D (struct file_name_attr *)ictx->data; + fnx->file_attributes =3D + (fnx->file_attributes & ~FILE_ATTR_VALID_FLAGS) | + (ni->flags & FILE_ATTR_VALID_FLAGS); + if (ctx->mrec->flags & MFT_RECORD_IS_DIRECTORY) + fnx->data_size =3D fnx->allocated_size =3D 0; + else { + read_lock_irqsave(&ni->size_lock, flags); + if (NInoSparse(ni) || NInoCompressed(ni)) + fnx->allocated_size =3D cpu_to_le64(ni->itype.compressed.size); + else + fnx->allocated_size =3D cpu_to_le64(ni->allocated_size); + fnx->data_size =3D cpu_to_le64(ni->data_size); + + /* + * The file name record has also to be fixed if some + * attribute update implied the unnamed data to be + * made non-resident + */ + fn->allocated_size =3D fnx->allocated_size; + fn->data_size =3D fnx->data_size; + read_unlock_irqrestore(&ni->size_lock, flags); + } + + /* update or clear the reparse tag in the index */ + fnx->type.rp.reparse_point_tag =3D reparse_tag; + fnx->creation_time =3D fn->creation_time; + fnx->last_data_change_time =3D fn->last_data_change_time; + fnx->last_mft_change_time =3D fn->last_mft_change_time; + fnx->last_access_time =3D fn->last_access_time; + ntfs_index_entry_mark_dirty(ictx); + ntfs_icx_ib_sync_write(ictx); + NInoSetDirty(ctx->ntfs_ino); + ntfs_index_ctx_put(ictx); + mutex_unlock(&index_ni->mrec_lock); + iput(index_vi); + } + /* Check for real error occurred. */ + if (err !=3D -ENOENT) { + ntfs_error(sb, "Attribute lookup failed, err : %d, inode %lld", err, + (long long)ni->mft_no); + } else + err =3D 0; + + ntfs_attr_put_search_ctx(ctx); return err; } =20 -/** +int ntfs_get_block_mft_record(struct ntfs_inode *mft_ni, struct ntfs_inode= *ni) +{ + s64 vcn; + struct runlist_element *rl; + + if (ni->mft_lcn[0] !=3D LCN_RL_NOT_MAPPED) + return 0; + + vcn =3D (s64)ni->mft_no << mft_ni->vol->mft_record_size_bits >> + mft_ni->vol->cluster_size_bits; + + rl =3D mft_ni->runlist.rl; + if (!rl) { + ntfs_error(mft_ni->vol->sb, "$MFT runlist is not present"); + return -EIO; + } + + /* Seek to element containing target vcn. */ + while (rl->length && rl[1].vcn <=3D vcn) + rl++; + ni->mft_lcn[0] =3D ntfs_rl_vcn_to_lcn(rl, vcn); + ni->mft_lcn_count =3D 1; + + if (mft_ni->vol->cluster_size < mft_ni->vol->mft_record_size && + (rl->length - (vcn - rl->vcn)) <=3D 1) { + rl++; + ni->mft_lcn[1] =3D ntfs_rl_vcn_to_lcn(rl, vcn + 1); + ni->mft_lcn_count++; + } + return 0; +} + +/* * __ntfs_write_inode - write out a dirty inode * @vi: inode to write out * @sync: if true, write out synchronously @@ -2947,130 +2732,132 @@ int ntfs_setattr(struct mnt_idmap *idmap, struct = dentry *dentry, * is done using write_mft_record(). * * If @sync is false, just schedule the write to happen but do not wait fo= r i/o - * completion. In 2.6 kernels, scheduling usually happens just by virtue = of - * marking the page (and in this case mft record) dirty but we do not impl= ement - * this yet as write_mft_record() largely ignores the @sync parameter and - * always performs synchronous writes. + * completion. * * Return 0 on success and -errno on error. */ int __ntfs_write_inode(struct inode *vi, int sync) { - sle64 nt; - ntfs_inode *ni =3D NTFS_I(vi); - ntfs_attr_search_ctx *ctx; - MFT_RECORD *m; - STANDARD_INFORMATION *si; + struct ntfs_inode *ni =3D NTFS_I(vi); + struct ntfs_inode *mft_ni =3D NTFS_I(ni->vol->mft_ino); + struct mft_record *m; int err =3D 0; - bool modified =3D false; + bool need_iput =3D false; =20 ntfs_debug("Entering for %sinode 0x%lx.", NInoAttr(ni) ? "attr " : "", vi->i_ino); + + if (NVolShutdown(ni->vol)) + return -EIO; + /* * Dirty attribute inodes are written via their real inodes so just * clean them here. Access time updates are taken care off when the * real inode is written. */ - if (NInoAttr(ni)) { + if (NInoAttr(ni) || ni->nr_extents =3D=3D -1) { NInoClearDirty(ni); ntfs_debug("Done."); return 0; } + + /* igrab prevents vi from being evicted while mrec_lock is hold. */ + if (igrab(vi) !=3D NULL) + need_iput =3D true; + + mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); /* Map, pin, and lock the mft record belonging to the inode. */ m =3D map_mft_record(ni); if (IS_ERR(m)) { + mutex_unlock(&ni->mrec_lock); err =3D PTR_ERR(m); goto err_out; } - /* Update the access times in the standard information attribute. */ - ctx =3D ntfs_attr_get_search_ctx(ni, m); - if (unlikely(!ctx)) { - err =3D -ENOMEM; - goto unm_err_out; + + if (NInoNonResident(ni) && NInoRunlistDirty(ni)) { + down_write(&ni->runlist.lock); + err =3D ntfs_attr_update_mapping_pairs(ni, 0); + if (!err) + NInoClearRunlistDirty(ni); + up_write(&ni->runlist.lock); } - err =3D ntfs_attr_lookup(AT_STANDARD_INFORMATION, NULL, 0, - CASE_SENSITIVE, 0, NULL, 0, ctx); - if (unlikely(err)) { - ntfs_attr_put_search_ctx(ctx); + + err =3D ntfs_inode_sync_standard_information(vi, m); + if (err) goto unm_err_out; - } - si =3D (STANDARD_INFORMATION*)((u8*)ctx->attr + - le16_to_cpu(ctx->attr->data.resident.value_offset)); - /* Update the access times if they have changed. */ - nt =3D utc2ntfs(inode_get_mtime(vi)); - if (si->last_data_change_time !=3D nt) { - ntfs_debug("Updating mtime for inode 0x%lx: old =3D 0x%llx, " - "new =3D 0x%llx", vi->i_ino, (long long) - sle64_to_cpu(si->last_data_change_time), - (long long)sle64_to_cpu(nt)); - si->last_data_change_time =3D nt; - modified =3D true; - } - nt =3D utc2ntfs(inode_get_ctime(vi)); - if (si->last_mft_change_time !=3D nt) { - ntfs_debug("Updating ctime for inode 0x%lx: old =3D 0x%llx, " - "new =3D 0x%llx", vi->i_ino, (long long) - sle64_to_cpu(si->last_mft_change_time), - (long long)sle64_to_cpu(nt)); - si->last_mft_change_time =3D nt; - modified =3D true; - } - nt =3D utc2ntfs(inode_get_atime(vi)); - if (si->last_access_time !=3D nt) { - ntfs_debug("Updating atime for inode 0x%lx: old =3D 0x%llx, " - "new =3D 0x%llx", vi->i_ino, - (long long)sle64_to_cpu(si->last_access_time), - (long long)sle64_to_cpu(nt)); - si->last_access_time =3D nt; - modified =3D true; - } + /* - * If we just modified the standard information attribute we need to - * mark the mft record it is in dirty. We do this manually so that - * mark_inode_dirty() is not called which would redirty the inode and - * hence result in an infinite loop of trying to write the inode. - * There is no need to mark the base inode nor the base mft record - * dirty, since we are going to write this mft record below in any case - * and the base mft record may actually not have been modified so it - * might not need to be written out. - * NOTE: It is not a problem when the inode for $MFT itself is being - * written out as mark_ntfs_record_dirty() will only set I_DIRTY_PAGES - * on the $MFT inode and hence __ntfs_write_inode() will not be - * re-invoked because of it which in turn is ok since the dirtied mft - * record will be cleaned and written out to disk below, i.e. before - * this function returns. + * when being umounted and inodes are evicted, write_inode() + * is called with all inodes being marked with I_FREEING. + * then ntfs_inode_sync_filename() waits infinitly because + * of ntfs_iget. This situation happens only where sync_filesysem() + * from umount fails because of a disk unplug and etc. + * the absent of SB_ACTIVE means umounting. */ - if (modified) { - flush_dcache_mft_record_page(ctx->ntfs_ino); - if (!NInoTestSetDirty(ctx->ntfs_ino)) - mark_ntfs_record_dirty(ctx->ntfs_ino->page, - ctx->ntfs_ino->page_ofs); - } - ntfs_attr_put_search_ctx(ctx); + if ((vi->i_sb->s_flags & SB_ACTIVE) && NInoTestClearFileNameDirty(ni)) + ntfs_inode_sync_filename(ni); + /* Now the access times are updated, write the base mft record. */ - if (NInoDirty(ni)) + if (NInoDirty(ni)) { + down_read(&mft_ni->runlist.lock); + err =3D ntfs_get_block_mft_record(mft_ni, ni); + up_read(&mft_ni->runlist.lock); + if (err) + goto unm_err_out; + err =3D write_mft_record(ni, m, sync); + if (err) + ntfs_error(vi->i_sb, "write_mft_record failed, err : %d\n", err); + } + unmap_mft_record(ni); + + /* Map any unmapped extent mft records with LCNs. */ + down_read(&mft_ni->runlist.lock); + mutex_lock(&ni->extent_lock); + if (ni->nr_extents > 0) { + int i; + + for (i =3D 0; i < ni->nr_extents; i++) { + err =3D ntfs_get_block_mft_record(mft_ni, + ni->ext.extent_ntfs_inos[i]); + if (err) { + mutex_unlock(&ni->extent_lock); + up_read(&mft_ni->runlist.lock); + mutex_unlock(&ni->mrec_lock); + goto err_out; + } + } + } + mutex_unlock(&ni->extent_lock); + up_read(&mft_ni->runlist.lock); + /* Write all attached extent mft records. */ mutex_lock(&ni->extent_lock); if (ni->nr_extents > 0) { - ntfs_inode **extent_nis =3D ni->ext.extent_ntfs_inos; + struct ntfs_inode **extent_nis =3D ni->ext.extent_ntfs_inos; int i; =20 ntfs_debug("Writing %i extent inodes.", ni->nr_extents); for (i =3D 0; i < ni->nr_extents; i++) { - ntfs_inode *tni =3D extent_nis[i]; + struct ntfs_inode *tni =3D extent_nis[i]; =20 if (NInoDirty(tni)) { - MFT_RECORD *tm =3D map_mft_record(tni); + struct mft_record *tm; int ret; =20 + mutex_lock(&tni->mrec_lock); + tm =3D map_mft_record(tni); if (IS_ERR(tm)) { + mutex_unlock(&tni->mrec_lock); if (!err || err =3D=3D -ENOMEM) err =3D PTR_ERR(tm); continue; } + ret =3D write_mft_record(tni, tm, sync); unmap_mft_record(tni); + mutex_unlock(&tni->mrec_lock); + if (unlikely(ret)) { if (!err || err =3D=3D -ENOMEM) err =3D ret; @@ -3079,24 +2866,956 @@ int __ntfs_write_inode(struct inode *vi, int sync) } } mutex_unlock(&ni->extent_lock); - unmap_mft_record(ni); + mutex_unlock(&ni->mrec_lock); + if (unlikely(err)) goto err_out; + if (need_iput) + iput(vi); ntfs_debug("Done."); return 0; unm_err_out: unmap_mft_record(ni); + mutex_unlock(&ni->mrec_lock); err_out: - if (err =3D=3D -ENOMEM) { - ntfs_warning(vi->i_sb, "Not enough memory to write inode. " - "Marking the inode dirty again, so the VFS " - "retries later."); + if (err =3D=3D -ENOMEM) mark_inode_dirty(vi); - } else { + else { ntfs_error(vi->i_sb, "Failed (error %i): Run chkdsk.", -err); NVolSetErrors(ni->vol); } + if (need_iput) + iput(vi); return err; } =20 -#endif /* NTFS_RW */ +/* + * ntfs_extent_inode_open - load an extent inode and attach it to its base + * @base_ni: base ntfs inode + * @mref: mft reference of the extent inode to load (in little endian) + * + * First check if the extent inode @mref is already attached to the base n= tfs + * inode @base_ni, and if so, return a pointer to the attached extent inod= e. + * + * If the extent inode is not already attached to the base inode, allocate= an + * ntfs_inode structure and initialize it for the given inode @mref. @mref + * specifies the inode number / mft record to read, including the sequence + * number, which can be 0 if no sequence number checking is to be performe= d. + * + * Then, allocate a buffer for the mft record, read the mft record from the + * volume @base_ni->vol, and attach it to the ntfs_inode structure (->mrec= ). + * The mft record is mst deprotected and sanity checked for validity and we + * abort if deprotection or checks fail. + * + * Finally attach the ntfs inode to its base inode @base_ni and return a + * pointer to the ntfs_inode structure on success or NULL on error, with e= rrno + * set to the error code. + * + * Note, extent inodes are never closed directly. They are automatically + * disposed off by the closing of the base inode. + */ +static struct ntfs_inode *ntfs_extent_inode_open(struct ntfs_inode *base_n= i, + const __le64 mref) +{ + u64 mft_no =3D MREF_LE(mref); + struct ntfs_inode *ni =3D NULL; + struct ntfs_inode **extent_nis; + int i; + struct mft_record *ni_mrec; + struct super_block *sb; + + if (!base_ni) + return NULL; + + sb =3D base_ni->vol->sb; + ntfs_debug("Opening extent inode %lld (base mft record %lld).\n", + (unsigned long long)mft_no, + (unsigned long long)base_ni->mft_no); + + /* Is the extent inode already open and attached to the base inode? */ + if (base_ni->nr_extents > 0) { + extent_nis =3D base_ni->ext.extent_ntfs_inos; + for (i =3D 0; i < base_ni->nr_extents; i++) { + u16 seq_no; + + ni =3D extent_nis[i]; + if (mft_no !=3D ni->mft_no) + continue; + ni_mrec =3D map_mft_record(ni); + if (IS_ERR(ni_mrec)) { + ntfs_error(sb, "failed to map mft record for %lu", + ni->mft_no); + goto out; + } + /* Verify the sequence number if given. */ + seq_no =3D MSEQNO_LE(mref); + if (seq_no && + seq_no !=3D le16_to_cpu(ni_mrec->sequence_number)) { + ntfs_error(sb, "Found stale extent mft reference mft=3D%lld", + (long long)ni->mft_no); + unmap_mft_record(ni); + goto out; + } + unmap_mft_record(ni); + goto out; + } + } + /* Wasn't there, we need to load the extent inode. */ + ni =3D ntfs_new_extent_inode(base_ni->vol->sb, mft_no); + if (!ni) + goto out; + + ni->seq_no =3D (u16)MSEQNO_LE(mref); + ni->nr_extents =3D -1; + ni->ext.base_ntfs_ino =3D base_ni; + /* Attach extent inode to base inode, reallocating memory if needed. */ + if (!(base_ni->nr_extents & 3)) { + i =3D (base_ni->nr_extents + 4) * sizeof(struct ntfs_inode *); + + extent_nis =3D kvzalloc(i, GFP_NOFS); + if (!extent_nis) + goto err_out; + if (base_ni->nr_extents) { + memcpy(extent_nis, base_ni->ext.extent_ntfs_inos, + i - 4 * sizeof(struct ntfs_inode *)); + kvfree(base_ni->ext.extent_ntfs_inos); + } + base_ni->ext.extent_ntfs_inos =3D extent_nis; + } + base_ni->ext.extent_ntfs_inos[base_ni->nr_extents++] =3D ni; + +out: + ntfs_debug("\n"); + return ni; +err_out: + ntfs_destroy_ext_inode(ni); + ni =3D NULL; + goto out; +} + +/* + * ntfs_inode_attach_all_extents - attach all extents for target inode + * @ni: opened ntfs inode for which perform attach + * + * Return 0 on success and error. + */ +int ntfs_inode_attach_all_extents(struct ntfs_inode *ni) +{ + struct attr_list_entry *ale; + u64 prev_attached =3D 0; + + if (!ni) { + ntfs_debug("Invalid arguments.\n"); + return -EINVAL; + } + + if (NInoAttr(ni)) + ni =3D ni->ext.base_ntfs_ino; + + ntfs_debug("Entering for inode 0x%llx.\n", (long long) ni->mft_no); + + /* Inode haven't got attribute list, thus nothing to attach. */ + if (!NInoAttrList(ni)) + return 0; + + if (!ni->attr_list) { + ntfs_debug("Corrupt in-memory struct.\n"); + return -EINVAL; + } + + /* Walk through attribute list and attach all extents. */ + ale =3D (struct attr_list_entry *)ni->attr_list; + while ((u8 *)ale < ni->attr_list + ni->attr_list_size) { + if (ni->mft_no !=3D MREF_LE(ale->mft_reference) && + prev_attached !=3D MREF_LE(ale->mft_reference)) { + if (!ntfs_extent_inode_open(ni, ale->mft_reference)) { + ntfs_debug("Couldn't attach extent inode.\n"); + return -1; + } + prev_attached =3D MREF_LE(ale->mft_reference); + } + ale =3D (struct attr_list_entry *)((u8 *)ale + le16_to_cpu(ale->length)); + } + return 0; +} + +/* + * ntfs_inode_add_attrlist - add attribute list to inode and fill it + * @ni: opened ntfs inode to which add attribute list + * + * Return 0 on success or error. + */ +int ntfs_inode_add_attrlist(struct ntfs_inode *ni) +{ + int err; + struct ntfs_attr_search_ctx *ctx; + u8 *al =3D NULL, *aln; + int al_len =3D 0; + struct attr_list_entry *ale =3D NULL; + struct mft_record *ni_mrec; + u32 attr_al_len; + + if (!ni) + return -EINVAL; + + ntfs_debug("inode %llu\n", (unsigned long long) ni->mft_no); + + if (NInoAttrList(ni) || ni->nr_extents) { + ntfs_error(ni->vol->sb, "Inode already has attribute list"); + return -EEXIST; + } + + ni_mrec =3D map_mft_record(ni); + if (IS_ERR(ni_mrec)) + return -EIO; + + /* Form attribute list. */ + ctx =3D ntfs_attr_get_search_ctx(ni, ni_mrec); + if (!ctx) { + err =3D -ENOMEM; + goto err_out; + } + + /* Walk through all attributes. */ + while (!(err =3D ntfs_attr_lookup(AT_UNUSED, NULL, 0, 0, 0, NULL, 0, ctx)= )) { + int ale_size; + + if (ctx->attr->type =3D=3D AT_ATTRIBUTE_LIST) { + err =3D -EIO; + ntfs_error(ni->vol->sb, "Attribute list already present"); + goto put_err_out; + } + + ale_size =3D (sizeof(struct attr_list_entry) + sizeof(__le16) * + ctx->attr->name_length + 7) & ~7; + al_len +=3D ale_size; + + aln =3D kvrealloc(al, al_len, GFP_NOFS); + if (!aln) { + err =3D -ENOMEM; + ntfs_error(ni->vol->sb, "Failed to realloc %d bytes", al_len); + goto put_err_out; + } + ale =3D (struct attr_list_entry *)(aln + ((u8 *)ale - al)); + al =3D aln; + + memset(ale, 0, ale_size); + + /* Add attribute to attribute list. */ + ale->type =3D ctx->attr->type; + ale->length =3D cpu_to_le16((sizeof(struct attr_list_entry) + + sizeof(__le16) * ctx->attr->name_length + 7) & ~7); + ale->name_length =3D ctx->attr->name_length; + ale->name_offset =3D (u8 *)ale->name - (u8 *)ale; + if (ctx->attr->non_resident) + ale->lowest_vcn =3D + ctx->attr->data.non_resident.lowest_vcn; + else + ale->lowest_vcn =3D 0; + ale->mft_reference =3D MK_LE_MREF(ni->mft_no, + le16_to_cpu(ni_mrec->sequence_number)); + ale->instance =3D ctx->attr->instance; + memcpy(ale->name, (u8 *)ctx->attr + + le16_to_cpu(ctx->attr->name_offset), + ctx->attr->name_length * sizeof(__le16)); + ale =3D (struct attr_list_entry *)(al + al_len); + } + + /* Check for real error occurred. */ + if (err !=3D -ENOENT) { + ntfs_error(ni->vol->sb, "%s: Attribute lookup failed, inode %lld", + __func__, (long long)ni->mft_no); + goto put_err_out; + } + + /* Set in-memory attribute list. */ + ni->attr_list =3D al; + ni->attr_list_size =3D al_len; + NInoSetAttrList(ni); + + attr_al_len =3D offsetof(struct attr_record, data.resident.reserved) + 1 + + ((al_len + 7) & ~7); + /* Free space if there is not enough it for $ATTRIBUTE_LIST. */ + if (le32_to_cpu(ni_mrec->bytes_allocated) - + le32_to_cpu(ni_mrec->bytes_in_use) < attr_al_len) { + if (ntfs_inode_free_space(ni, (int)attr_al_len)) { + /* Failed to free space. */ + err =3D -ENOSPC; + ntfs_error(ni->vol->sb, "Failed to free space for attrlist"); + goto rollback; + } + } + + /* Add $ATTRIBUTE_LIST to mft record. */ + err =3D ntfs_resident_attr_record_add(ni, AT_ATTRIBUTE_LIST, AT_UNNAMED, = 0, + NULL, al_len, 0); + if (err < 0) { + ntfs_error(ni->vol->sb, "Couldn't add $ATTRIBUTE_LIST to MFT"); + goto rollback; + } + + err =3D ntfs_attrlist_update(ni); + if (err < 0) + goto remove_attrlist_record; + + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(ni); + return 0; + +remove_attrlist_record: + /* Prevent ntfs_attr_recorm_rm from freeing attribute list. */ + ni->attr_list =3D NULL; + NInoClearAttrList(ni); + /* Remove $ATTRIBUTE_LIST record. */ + ntfs_attr_reinit_search_ctx(ctx); + if (!ntfs_attr_lookup(AT_ATTRIBUTE_LIST, NULL, 0, + CASE_SENSITIVE, 0, NULL, 0, ctx)) { + if (ntfs_attr_record_rm(ctx)) + ntfs_error(ni->vol->sb, "Rollback failed to remove attrlist"); + } else { + ntfs_error(ni->vol->sb, "Rollback failed to find attrlist"); + } + + /* Setup back in-memory runlist. */ + ni->attr_list =3D al; + ni->attr_list_size =3D al_len; + NInoSetAttrList(ni); +rollback: + /* + * Scan attribute list for attributes that placed not in the base MFT + * record and move them to it. + */ + ntfs_attr_reinit_search_ctx(ctx); + ale =3D (struct attr_list_entry *)al; + while ((u8 *)ale < al + al_len) { + if (MREF_LE(ale->mft_reference) !=3D ni->mft_no) { + if (!ntfs_attr_lookup(ale->type, ale->name, + ale->name_length, + CASE_SENSITIVE, + le64_to_cpu(ale->lowest_vcn), + NULL, 0, ctx)) { + if (ntfs_attr_record_move_to(ctx, ni)) + ntfs_error(ni->vol->sb, + "Rollback failed to move attribute"); + } else { + ntfs_error(ni->vol->sb, "Rollback failed to find attr"); + } + ntfs_attr_reinit_search_ctx(ctx); + } + ale =3D (struct attr_list_entry *)((u8 *)ale + le16_to_cpu(ale->length)); + } + + /* Remove in-memory attribute list. */ + ni->attr_list =3D NULL; + ni->attr_list_size =3D 0; + NInoClearAttrList(ni); + NInoClearAttrListDirty(ni); +put_err_out: + ntfs_attr_put_search_ctx(ctx); +err_out: + kvfree(al); + unmap_mft_record(ni); + return err; +} + +/* + * ntfs_inode_close - close an ntfs inode and free all associated memory + * @ni: ntfs inode to close + * + * Make sure the ntfs inode @ni is clean. + * + * If the ntfs inode @ni is a base inode, close all associated extent inod= es, + * then deallocate all memory attached to it, and finally free the ntfs in= ode + * structure itself. + * + * If it is an extent inode, we disconnect it from its base inode before we + * destroy it. + * + * It is OK to pass NULL to this function, it is just noop in this case. + * + * Return 0 on success or error. + */ +int ntfs_inode_close(struct ntfs_inode *ni) +{ + int err =3D -1; + struct ntfs_inode **tmp_nis; + struct ntfs_inode *base_ni; + s32 i; + + if (!ni) + return 0; + + ntfs_debug("Entering for inode %lld\n", (long long)ni->mft_no); + + /* Is this a base inode with mapped extent inodes? */ + /* + * If the inode is an extent inode, disconnect it from the + * base inode before destroying it. + */ + base_ni =3D ni->ext.base_ntfs_ino; + for (i =3D 0; i < base_ni->nr_extents; ++i) { + tmp_nis =3D base_ni->ext.extent_ntfs_inos; + if (tmp_nis[i] !=3D ni) + continue; + /* Found it. Disconnect. */ + memmove(tmp_nis + i, tmp_nis + i + 1, + (base_ni->nr_extents - i - 1) * + sizeof(struct ntfs_inode *)); + /* Buffer should be for multiple of four extents. */ + if ((--base_ni->nr_extents) & 3) + break; + /* + * ElectricFence is unhappy with realloc(x,0) as free(x) + * thus we explicitly separate these two cases. + */ + if (base_ni->nr_extents) { + /* Resize the memory buffer. */ + tmp_nis =3D kvrealloc(tmp_nis, base_ni->nr_extents * + sizeof(struct ntfs_inode *), GFP_NOFS); + /* Ignore errors, they don't really matter. */ + if (tmp_nis) + base_ni->ext.extent_ntfs_inos =3D tmp_nis; + } else if (tmp_nis) { + kvfree(tmp_nis); + base_ni->ext.extent_ntfs_inos =3D NULL; + } + break; + } + + if (NInoDirty(ni)) + ntfs_error(ni->vol->sb, "Releasing dirty inode %lld!\n", + (long long)ni->mft_no); + if (NInoAttrList(ni) && ni->attr_list) + kvfree(ni->attr_list); + ntfs_destroy_ext_inode(ni); + err =3D 0; + ntfs_debug("\n"); + return err; +} + +void ntfs_destroy_ext_inode(struct ntfs_inode *ni) +{ + ntfs_debug("Entering."); + if (ni =3D=3D NULL) + return; + + ntfs_attr_close(ni); + + if (NInoDirty(ni)) + ntfs_error(ni->vol->sb, "Releasing dirty ext inode %lld!\n", + (long long)ni->mft_no); + if (NInoAttrList(ni) && ni->attr_list) + kvfree(ni->attr_list); + kfree(ni->mrec); + kmem_cache_free(ntfs_inode_cache, ni); +} + +static struct ntfs_inode *ntfs_inode_base(struct ntfs_inode *ni) +{ + if (ni->nr_extents =3D=3D -1) + return ni->ext.base_ntfs_ino; + return ni; +} + +static int ntfs_attr_position(__le32 type, struct ntfs_attr_search_ctx *ct= x) +{ + int err; + + err =3D ntfs_attr_lookup(type, NULL, 0, CASE_SENSITIVE, 0, NULL, + 0, ctx); + if (err) { + __le32 atype; + + if (err !=3D -ENOENT) + return err; + + atype =3D ctx->attr->type; + if (atype =3D=3D AT_END) + return -ENOSPC; + + /* + * if ntfs_external_attr_lookup return -ENOENT, ctx->al_entry + * could point to an attribute in an extent mft record, but + * ctx->attr and ctx->ntfs_ino always points to an attibute in + * a base mft record. + */ + if (ctx->al_entry && + MREF_LE(ctx->al_entry->mft_reference) !=3D ctx->ntfs_ino->mft_no) { + ntfs_attr_reinit_search_ctx(ctx); + err =3D ntfs_attr_lookup(atype, NULL, 0, CASE_SENSITIVE, 0, NULL, + 0, ctx); + if (err) + return err; + } + } + return 0; +} + +/* + * ntfs_inode_free_space - free space in the MFT record of inode + * @ni: ntfs inode in which MFT record free space + * @size: amount of space needed to free + * + * Return 0 on success or error. + */ +int ntfs_inode_free_space(struct ntfs_inode *ni, int size) +{ + struct ntfs_attr_search_ctx *ctx; + int freed, err; + struct mft_record *ni_mrec; + struct super_block *sb; + + if (!ni || size < 0) + return -EINVAL; + ntfs_debug("Entering for inode %lld, size %d\n", + (unsigned long long)ni->mft_no, size); + + sb =3D ni->vol->sb; + ni_mrec =3D map_mft_record(ni); + if (IS_ERR(ni_mrec)) + return -EIO; + + freed =3D (le32_to_cpu(ni_mrec->bytes_allocated) - + le32_to_cpu(ni_mrec->bytes_in_use)); + + unmap_mft_record(ni); + + if (size <=3D freed) + return 0; + + ctx =3D ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) { + ntfs_error(sb, "%s, Failed to get search context", __func__); + return -ENOMEM; + } + + /* + * Chkdsk complain if $STANDARD_INFORMATION is not in the base MFT + * record. + * + * Also we can't move $ATTRIBUTE_LIST from base MFT_RECORD, so position + * search context on first attribute after $STANDARD_INFORMATION and + * $ATTRIBUTE_LIST. + * + * Why we reposition instead of simply skip this attributes during + * enumeration? Because in case we have got only in-memory attribute + * list ntfs_attr_lookup will fail when it will try to find + * $ATTRIBUTE_LIST. + */ + err =3D ntfs_attr_position(AT_FILE_NAME, ctx); + if (err) + goto put_err_out; + + while (1) { + int record_size; + + /* + * Check whether attribute is from different MFT record. If so, + * find next, because we don't need such. + */ + while (ctx->ntfs_ino->mft_no !=3D ni->mft_no) { +retry: + err =3D ntfs_attr_lookup(AT_UNUSED, NULL, 0, CASE_SENSITIVE, + 0, NULL, 0, ctx); + if (err) { + if (err !=3D -ENOENT) + ntfs_error(sb, "Attr lookup failed #2"); + else if (ctx->attr->type =3D=3D AT_END) + err =3D -ENOSPC; + else + err =3D 0; + + if (err) + goto put_err_out; + } + } + + if (ntfs_inode_base(ctx->ntfs_ino)->mft_no =3D=3D FILE_MFT && + ctx->attr->type =3D=3D AT_DATA) + goto retry; + + if (ctx->attr->type =3D=3D AT_INDEX_ROOT) + goto retry; + + record_size =3D le32_to_cpu(ctx->attr->length); + + /* Move away attribute. */ + err =3D ntfs_attr_record_move_away(ctx, 0); + if (err) { + ntfs_error(sb, "Failed to move out attribute #2"); + break; + } + freed +=3D record_size; + + /* Check whether we done. */ + if (size <=3D freed) { + ntfs_attr_put_search_ctx(ctx); + return 0; + } + + /* + * Reposition to first attribute after $STANDARD_INFORMATION and + * $ATTRIBUTE_LIST (see comments upwards). + */ + ntfs_attr_reinit_search_ctx(ctx); + err =3D ntfs_attr_position(AT_FILE_NAME, ctx); + if (err) + break; + } +put_err_out: + ntfs_attr_put_search_ctx(ctx); + if (err =3D=3D -ENOSPC) + ntfs_debug("No attributes left that can be moved out.\n"); + return err; +} + +s64 ntfs_inode_attr_pread(struct inode *vi, s64 pos, s64 count, u8 *buf) +{ + struct address_space *mapping =3D vi->i_mapping; + struct folio *folio; + struct ntfs_inode *ni =3D NTFS_I(vi); + s64 isize; + u32 attr_len, total =3D 0, offset; + pgoff_t index; + int err =3D 0; + + WARN_ON(!NInoAttr(ni)); + if (!count) + return 0; + + mutex_lock(&ni->mrec_lock); + isize =3D i_size_read(vi); + if (pos > isize) { + mutex_unlock(&ni->mrec_lock); + return -EINVAL; + } + if (pos + count > isize) + count =3D isize - pos; + + if (!NInoNonResident(ni)) { + struct ntfs_attr_search_ctx *ctx; + u8 *attr; + + ctx =3D ntfs_attr_get_search_ctx(ni->ext.base_ntfs_ino, NULL); + if (!ctx) { + ntfs_error(vi->i_sb, "Failed to get attr search ctx"); + err =3D -ENOMEM; + mutex_unlock(&ni->mrec_lock); + goto out; + } + + err =3D ntfs_attr_lookup(ni->type, ni->name, ni->name_len, CASE_SENSITIV= E, + 0, NULL, 0, ctx); + if (err) { + ntfs_error(vi->i_sb, "Failed to look up attr %#x", ni->type); + ntfs_attr_put_search_ctx(ctx); + mutex_unlock(&ni->mrec_lock); + goto out; + } + + attr =3D (u8 *)ctx->attr + le16_to_cpu(ctx->attr->data.resident.value_of= fset); + memcpy(buf, (u8 *)attr + pos, count); + ntfs_attr_put_search_ctx(ctx); + mutex_unlock(&ni->mrec_lock); + return count; + } + mutex_unlock(&ni->mrec_lock); + + index =3D pos >> PAGE_SHIFT; + do { + /* Update @index and get the next folio. */ + folio =3D read_mapping_folio(mapping, index, NULL); + if (IS_ERR(folio)) + break; + + offset =3D offset_in_folio(folio, pos); + attr_len =3D min_t(size_t, (size_t)count, folio_size(folio) - offset); + + folio_lock(folio); + memcpy_from_folio(buf, folio, offset, attr_len); + folio_unlock(folio); + folio_put(folio); + + total +=3D attr_len; + buf +=3D attr_len; + pos +=3D attr_len; + count -=3D attr_len; + index++; + } while (count); +out: + return err ? (s64)err : total; +} + +static inline int ntfs_enlarge_attribute(struct inode *vi, s64 pos, s64 co= unt, + struct ntfs_attr_search_ctx *ctx) +{ + struct ntfs_inode *ni =3D NTFS_I(vi); + struct super_block *sb =3D vi->i_sb; + int ret; + + if (pos + count <=3D ni->initialized_size) + return 0; + + if (NInoEncrypted(ni) && NInoNonResident(ni)) + return -EACCES; + + if (NInoCompressed(ni)) + return -EOPNOTSUPP; + + if (pos + count > ni->data_size) { + if (ntfs_attr_truncate(ni, pos + count)) { + ntfs_debug("Failed to truncate attribute"); + return -1; + } + + ntfs_attr_reinit_search_ctx(ctx); + ret =3D ntfs_attr_lookup(ni->type, + ni->name, ni->name_len, CASE_SENSITIVE, + 0, NULL, 0, ctx); + if (ret) { + ntfs_error(sb, "Failed to look up attr %#x", ni->type); + return ret; + } + } + + if (!NInoNonResident(ni)) { + if (likely(i_size_read(vi) < ni->data_size)) + i_size_write(vi, ni->data_size); + return 0; + } + + if (pos + count > ni->initialized_size) { + ctx->attr->data.non_resident.initialized_size =3D cpu_to_le64(pos + coun= t); + mark_mft_record_dirty(ctx->ntfs_ino); + ni->initialized_size =3D pos + count; + if (i_size_read(vi) < ni->initialized_size) + i_size_write(vi, ni->initialized_size); + } + return 0; +} + +static s64 __ntfs_inode_resident_attr_pwrite(struct inode *vi, + s64 pos, s64 count, u8 *buf, + struct ntfs_attr_search_ctx *ctx) +{ + struct ntfs_inode *ni =3D NTFS_I(vi); + struct folio *folio; + struct address_space *mapping =3D vi->i_mapping; + u8 *addr; + int err =3D 0; + + WARN_ON(NInoNonResident(ni)); + if (pos + count > PAGE_SIZE) { + ntfs_error(vi->i_sb, "Out of write into resident attr %#x", ni->type); + return -EINVAL; + } + + /* Copy to mft record page */ + addr =3D (u8 *)ctx->attr + le16_to_cpu(ctx->attr->data.resident.value_off= set); + memcpy(addr + pos, buf, count); + mark_mft_record_dirty(ctx->ntfs_ino); + + /* Keep the first page clean and uptodate */ + folio =3D __filemap_get_folio(mapping, 0, FGP_WRITEBEGIN | FGP_NOFS, + mapping_gfp_mask(mapping)); + if (IS_ERR(folio)) { + err =3D PTR_ERR(folio); + ntfs_error(vi->i_sb, "Failed to read a page 0 for attr %#x: %d", + ni->type, err); + goto out; + } + if (!folio_test_uptodate(folio)) + folio_fill_tail(folio, 0, addr, + le32_to_cpu(ctx->attr->data.resident.value_length)); + else + memcpy_to_folio(folio, offset_in_folio(folio, pos), buf, count); + folio_mark_uptodate(folio); + folio_unlock(folio); + folio_put(folio); +out: + return err ? err : count; +} + +static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi, + s64 pos, s64 count, u8 *buf, + struct ntfs_attr_search_ctx *ctx, + bool sync) +{ + struct ntfs_inode *ni =3D NTFS_I(vi); + struct address_space *mapping =3D vi->i_mapping; + struct folio *folio; + pgoff_t index; + unsigned long offset, length; + size_t attr_len; + s64 ret =3D 0, written =3D 0; + + WARN_ON(!NInoNonResident(ni)); + + index =3D pos >> PAGE_SHIFT; + while (count) { + if (count =3D=3D PAGE_SIZE) { + folio =3D __filemap_get_folio(vi->i_mapping, index, + FGP_CREAT | FGP_LOCK, + mapping_gfp_mask(mapping)); + if (IS_ERR(folio)) { + ret =3D -ENOMEM; + break; + } + } else { + folio =3D read_mapping_folio(mapping, index, NULL); + if (IS_ERR(folio)) { + ret =3D PTR_ERR(folio); + ntfs_error(vi->i_sb, "Failed to read a page %lu for attr %#x: %ld", + index, ni->type, PTR_ERR(folio)); + break; + } + + folio_lock(folio); + } + + if (count =3D=3D PAGE_SIZE) { + offset =3D 0; + attr_len =3D count; + } else { + offset =3D offset_in_folio(folio, pos); + attr_len =3D min_t(size_t, (size_t)count, folio_size(folio) - offset); + } + memcpy_to_folio(folio, offset, buf, attr_len); + + if (sync) { + struct ntfs_volume *vol =3D ni->vol; + s64 lcn, lcn_count; + unsigned int lcn_folio_off =3D 0; + struct bio *bio; + u64 rl_length =3D 0; + s64 vcn; + struct runlist_element *rl; + + lcn_count =3D max_t(s64, 1, ntfs_bytes_to_cluster(vol, attr_len)); + vcn =3D ntfs_pidx_to_cluster(vol, folio->index); + + do { + down_write(&ni->runlist.lock); + rl =3D ntfs_attr_vcn_to_rl(ni, vcn, &lcn); + if (IS_ERR(rl)) { + ret =3D PTR_ERR(rl); + up_write(&ni->runlist.lock); + goto err_unlock_folio; + } + + rl_length =3D rl->length - (vcn - rl->vcn); + if (rl_length < lcn_count) { + lcn_count -=3D rl_length; + } else { + rl_length =3D lcn_count; + lcn_count =3D 0; + } + up_write(&ni->runlist.lock); + + if (vol->cluster_size_bits > PAGE_SHIFT) { + lcn_folio_off =3D folio->index << PAGE_SHIFT; + lcn_folio_off &=3D vol->cluster_size_mask; + } + + bio =3D bio_alloc(vol->sb->s_bdev, 1, REQ_OP_WRITE, + GFP_NOIO); + bio->bi_iter.bi_sector =3D + ntfs_bytes_to_sector(vol, + ntfs_cluster_to_bytes(vol, lcn) + + lcn_folio_off); + + length =3D min_t(unsigned long, + ntfs_cluster_to_bytes(vol, rl_length), + folio_size(folio)); + if (!bio_add_folio(bio, folio, length, offset)) { + ret =3D -EIO; + bio_put(bio); + goto err_unlock_folio; + } + + submit_bio_wait(bio); + bio_put(bio); + vcn +=3D rl_length; + offset +=3D length; + } while (lcn_count !=3D 0); + + folio_mark_uptodate(folio); + } else { + folio_mark_uptodate(folio); + folio_mark_dirty(folio); + } +err_unlock_folio: + folio_unlock(folio); + folio_put(folio); + + if (ret) + break; + + written +=3D attr_len; + buf +=3D attr_len; + pos +=3D attr_len; + count -=3D attr_len; + index++; + + cond_resched(); + } + + return ret ? ret : written; +} + +s64 ntfs_inode_attr_pwrite(struct inode *vi, s64 pos, s64 count, u8 *buf, = bool sync) +{ + struct ntfs_inode *ni =3D NTFS_I(vi); + struct ntfs_attr_search_ctx *ctx; + s64 ret; + + WARN_ON(!NInoAttr(ni)); + + ctx =3D ntfs_attr_get_search_ctx(ni->ext.base_ntfs_ino, NULL); + if (!ctx) { + ntfs_error(vi->i_sb, "Failed to get attr search ctx"); + return -ENOMEM; + } + + ret =3D ntfs_attr_lookup(ni->type, ni->name, ni->name_len, CASE_SENSITIVE, + 0, NULL, 0, ctx); + if (ret) { + ntfs_attr_put_search_ctx(ctx); + ntfs_error(vi->i_sb, "Failed to look up attr %#x", ni->type); + return ret; + } + + mutex_lock(&ni->mrec_lock); + ret =3D ntfs_enlarge_attribute(vi, pos, count, ctx); + mutex_unlock(&ni->mrec_lock); + if (ret) + goto out; + + if (NInoNonResident(ni)) + ret =3D __ntfs_inode_non_resident_attr_pwrite(vi, pos, count, buf, ctx, = sync); + else + ret =3D __ntfs_inode_resident_attr_pwrite(vi, pos, count, buf, ctx); +out: + ntfs_attr_put_search_ctx(ctx); + return ret; +} + +struct folio *ntfs_get_locked_folio(struct address_space *mapping, + pgoff_t index, pgoff_t end_index, struct file_ra_state *ra) +{ + struct folio *folio; + + folio =3D filemap_lock_folio(mapping, index); + if (IS_ERR(folio)) { + if (PTR_ERR(folio) !=3D -ENOENT) + return folio; + + page_cache_sync_readahead(mapping, ra, NULL, index, + end_index - index); + folio =3D read_mapping_folio(mapping, index, NULL); + if (!IS_ERR(folio)) + folio_lock(folio); + } + + return folio; +} diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c index d7498ddc4a72..a21eeaec57b4 100644 --- a/fs/ntfs/namei.c +++ b/fs/ntfs/namei.c @@ -1,23 +1,104 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * namei.c - NTFS kernel directory inode operations. Part of the Linux-NTFS - * project. + * NTFS kernel directory inode operations. * * Copyright (c) 2001-2006 Anton Altaparmakov + * Copyright (c) 2025 LG Electronics Co., Ltd. */ =20 -#include #include -#include -#include +#include =20 -#include "attrib.h" -#include "debug.h" -#include "dir.h" -#include "mft.h" #include "ntfs.h" +#include "time.h" +#include "index.h" +#include "reparse.h" +#include "object_id.h" +#include "ea.h" =20 -/** +static const __le16 aux_name_le[3] =3D { + cpu_to_le16('A'), cpu_to_le16('U'), cpu_to_le16('X') +}; + +static const __le16 con_name_le[3] =3D { + cpu_to_le16('C'), cpu_to_le16('O'), cpu_to_le16('N') +}; + +static const __le16 com_name_le[3] =3D { + cpu_to_le16('C'), cpu_to_le16('O'), cpu_to_le16('M') +}; + +static const __le16 lpt_name_le[3] =3D { + cpu_to_le16('L'), cpu_to_le16('P'), cpu_to_le16('T') +}; + +static const __le16 nul_name_le[3] =3D { + cpu_to_le16('N'), cpu_to_le16('U'), cpu_to_le16('L') +}; + +static const __le16 prn_name_le[3] =3D { + cpu_to_le16('P'), cpu_to_le16('R'), cpu_to_le16('N') +}; + +static inline int ntfs_check_bad_char(const __le16 *wc, unsigned int wc_le= n) +{ + int i; + + for (i =3D 0; i < wc_len; i++) { + u16 c =3D le16_to_cpu(wc[i]); + + if (c < 0x0020 || + c =3D=3D 0x0022 || c =3D=3D 0x002A || c =3D=3D 0x002F || + c =3D=3D 0x003A || c =3D=3D 0x003C || c =3D=3D 0x003E || + c =3D=3D 0x003F || c =3D=3D 0x005C || c =3D=3D 0x007C) + return -EINVAL; + } + + return 0; +} + +static int ntfs_check_bad_windows_name(struct ntfs_volume *vol, + const __le16 *wc, + unsigned int wc_len) +{ + if (ntfs_check_bad_char(wc, wc_len)) + return -EINVAL; + + if (!NVolCheckWindowsNames(vol)) + return 0; + + /* Check for trailing space or dot. */ + if (wc_len > 0 && + (wc[wc_len - 1] =3D=3D cpu_to_le16(' ') || + wc[wc_len - 1] =3D=3D cpu_to_le16('.'))) + return -EINVAL; + + if (wc_len =3D=3D 3 || (wc_len > 3 && wc[3] =3D=3D cpu_to_le16('.'))) { + __le16 *upcase =3D vol->upcase; + u32 size =3D vol->upcase_len; + + if (ntfs_are_names_equal(wc, 3, aux_name_le, 3, IGNORE_CASE, upcase, siz= e) || + ntfs_are_names_equal(wc, 3, con_name_le, 3, IGNORE_CASE, upcase, siz= e) || + ntfs_are_names_equal(wc, 3, nul_name_le, 3, IGNORE_CASE, upcase, siz= e) || + ntfs_are_names_equal(wc, 3, prn_name_le, 3, IGNORE_CASE, upcase, siz= e)) + return -EINVAL; + } + + if (wc_len =3D=3D 4 || (wc_len > 4 && wc[4] =3D=3D cpu_to_le16('.'))) { + __le16 *upcase =3D vol->upcase; + u32 size =3D vol->upcase_len, port; + + if (ntfs_are_names_equal(wc, 3, com_name_le, 3, IGNORE_CASE, upcase, siz= e) || + ntfs_are_names_equal(wc, 3, lpt_name_le, 3, IGNORE_CASE, upcase, siz= e)) { + port =3D le16_to_cpu(wc[3]); + if (port >=3D '1' && port <=3D '9') + return -EINVAL; + } + } + return 0; +} + +/* * ntfs_lookup - find the inode represented by a dentry in a directory ino= de * @dir_ino: directory inode in which to look for the inode * @dent: dentry representing the inode to look for @@ -89,11 +170,11 @@ static struct dentry *ntfs_lookup(struct inode *dir_ino, struct dentry *de= nt, unsigned int flags) { - ntfs_volume *vol =3D NTFS_SB(dir_ino->i_sb); + struct ntfs_volume *vol =3D NTFS_SB(dir_ino->i_sb); struct inode *dent_inode; - ntfschar *uname; - ntfs_name *name =3D NULL; - MFT_REF mref; + __le16 *uname; + struct ntfs_name *name =3D NULL; + u64 mref; unsigned long dent_ino; int uname_len; =20 @@ -101,15 +182,16 @@ static struct dentry *ntfs_lookup(struct inode *dir_i= no, struct dentry *dent, dent, dir_ino->i_ino); /* Convert the name of the dentry to Unicode. */ uname_len =3D ntfs_nlstoucs(vol, dent->d_name.name, dent->d_name.len, - &uname); + &uname, NTFS_MAX_NAME_LEN); if (uname_len < 0) { if (uname_len !=3D -ENAMETOOLONG) - ntfs_error(vol->sb, "Failed to convert name to " - "Unicode."); + ntfs_debug("Failed to convert name to Unicode."); return ERR_PTR(uname_len); } + mutex_lock(&NTFS_I(dir_ino)->mrec_lock); mref =3D ntfs_lookup_inode_by_name(NTFS_I(dir_ino), uname, uname_len, &name); + mutex_unlock(&NTFS_I(dir_ino)->mrec_lock); kmem_cache_free(ntfs_name_cache, uname); if (!IS_ERR_MREF(mref)) { dent_ino =3D MREF(mref); @@ -117,9 +199,8 @@ static struct dentry *ntfs_lookup(struct inode *dir_ino= , struct dentry *dent, dent_inode =3D ntfs_iget(vol->sb, dent_ino); if (!IS_ERR(dent_inode)) { /* Consistency check. */ - if (is_bad_inode(dent_inode) || MSEQNO(mref) =3D=3D - NTFS_I(dent_inode)->seq_no || - dent_ino =3D=3D FILE_MFT) { + if (MSEQNO(mref) =3D=3D NTFS_I(dent_inode)->seq_no || + dent_ino =3D=3D FILE_MFT) { /* Perfect WIN32/POSIX match. -- Case 1. */ if (!name) { ntfs_debug("Done. (Case 1.)"); @@ -131,22 +212,20 @@ static struct dentry *ntfs_lookup(struct inode *dir_i= no, struct dentry *dent, */ goto handle_name; } - ntfs_error(vol->sb, "Found stale reference to inode " - "0x%lx (reference sequence number =3D " - "0x%x, inode sequence number =3D 0x%x), " - "returning -EIO. Run chkdsk.", - dent_ino, MSEQNO(mref), - NTFS_I(dent_inode)->seq_no); + ntfs_error(vol->sb, + "Found stale reference to inode 0x%lx (reference sequence number =3D 0= x%x, inode sequence number =3D 0x%x), returning -EIO. Run chkdsk.", + dent_ino, MSEQNO(mref), + NTFS_I(dent_inode)->seq_no); iput(dent_inode); dent_inode =3D ERR_PTR(-EIO); } else - ntfs_error(vol->sb, "ntfs_iget(0x%lx) failed with " - "error code %li.", dent_ino, - PTR_ERR(dent_inode)); + ntfs_error(vol->sb, "ntfs_iget(0x%lx) failed with error code %li.", + dent_ino, PTR_ERR(dent_inode)); kfree(name); /* Return the error code. */ return ERR_CAST(dent_inode); } + kfree(name); /* It is guaranteed that @name is no longer allocated at this point. */ if (MREF_ERR(mref) =3D=3D -ENOENT) { ntfs_debug("Entry was not found, adding negative dentry."); @@ -155,118 +234,1362 @@ static struct dentry *ntfs_lookup(struct inode *di= r_ino, struct dentry *dent, ntfs_debug("Done."); return NULL; } - ntfs_error(vol->sb, "ntfs_lookup_ino_by_name() failed with error " - "code %i.", -MREF_ERR(mref)); + ntfs_error(vol->sb, "ntfs_lookup_ino_by_name() failed with error code %i.= ", + -MREF_ERR(mref)); return ERR_PTR(MREF_ERR(mref)); - // TODO: Consider moving this lot to a separate function! (AIA) handle_name: - { - MFT_RECORD *m; - ntfs_attr_search_ctx *ctx; - ntfs_inode *ni =3D NTFS_I(dent_inode); - int err; - struct qstr nls_name; - - nls_name.name =3D NULL; - if (name->type !=3D FILE_NAME_DOS) { /* Case 2. */ - ntfs_debug("Case 2."); - nls_name.len =3D (unsigned)ntfs_ucstonls(vol, - (ntfschar*)&name->name, name->len, - (unsigned char**)&nls_name.name, 0); - kfree(name); - } else /* if (name->type =3D=3D FILE_NAME_DOS) */ { /* Case 3. */ - FILE_NAME_ATTR *fn; + { + struct mft_record *m; + struct ntfs_attr_search_ctx *ctx; + struct ntfs_inode *ni =3D NTFS_I(dent_inode); + int err; + struct qstr nls_name; =20 - ntfs_debug("Case 3."); - kfree(name); + nls_name.name =3D NULL; + if (name->type !=3D FILE_NAME_DOS) { /* Case 2. */ + ntfs_debug("Case 2."); + nls_name.len =3D (unsigned int)ntfs_ucstonls(vol, + (__le16 *)&name->name, name->len, + (unsigned char **)&nls_name.name, 0); + kfree(name); + } else /* if (name->type =3D=3D FILE_NAME_DOS) */ { /* Case 3. */ + struct file_name_attr *fn; + + ntfs_debug("Case 3."); + kfree(name); =20 - /* Find the WIN32 name corresponding to the matched DOS name. */ - ni =3D NTFS_I(dent_inode); - m =3D map_mft_record(ni); - if (IS_ERR(m)) { - err =3D PTR_ERR(m); - m =3D NULL; - ctx =3D NULL; + /* Find the WIN32 name corresponding to the matched DOS name. */ + ni =3D NTFS_I(dent_inode); + m =3D map_mft_record(ni); + if (IS_ERR(m)) { + err =3D PTR_ERR(m); + m =3D NULL; + ctx =3D NULL; + goto err_out; + } + ctx =3D ntfs_attr_get_search_ctx(ni, m); + if (unlikely(!ctx)) { + err =3D -ENOMEM; + goto err_out; + } + do { + struct attr_record *a; + u32 val_len; + + err =3D ntfs_attr_lookup(AT_FILE_NAME, NULL, 0, 0, 0, + NULL, 0, ctx); + if (unlikely(err)) { + ntfs_error(vol->sb, + "Inode corrupt: No WIN32 namespace counterpart to DOS file name. Run= chkdsk."); + if (err =3D=3D -ENOENT) + err =3D -EIO; + goto err_out; + } + /* Consistency checks. */ + a =3D ctx->attr; + if (a->non_resident || a->flags) + goto eio_err_out; + val_len =3D le32_to_cpu(a->data.resident.value_length); + if (le16_to_cpu(a->data.resident.value_offset) + + val_len > le32_to_cpu(a->length)) + goto eio_err_out; + fn =3D (struct file_name_attr *)((u8 *)ctx->attr + le16_to_cpu( + ctx->attr->data.resident.value_offset)); + if ((u32)(fn->file_name_length * sizeof(__le16) + + sizeof(struct file_name_attr)) > val_len) + goto eio_err_out; + } while (fn->file_name_type !=3D FILE_NAME_WIN32); + + /* Convert the found WIN32 name to current NLS code page. */ + nls_name.len =3D (unsigned int)ntfs_ucstonls(vol, + (__le16 *)&fn->file_name, fn->file_name_length, + (unsigned char **)&nls_name.name, 0); + + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(ni); + } + m =3D NULL; + ctx =3D NULL; + + /* Check if a conversion error occurred. */ + if ((int)nls_name.len < 0) { + err =3D (int)nls_name.len; goto err_out; } - ctx =3D ntfs_attr_get_search_ctx(ni, m); - if (unlikely(!ctx)) { + nls_name.hash =3D full_name_hash(dent, nls_name.name, nls_name.len); + + dent =3D d_add_ci(dent, dent_inode, &nls_name); + kfree(nls_name.name); + return dent; + +eio_err_out: + ntfs_error(vol->sb, "Illegal file name attribute. Run chkdsk."); + err =3D -EIO; +err_out: + if (ctx) + ntfs_attr_put_search_ctx(ctx); + if (m) + unmap_mft_record(ni); + iput(dent_inode); + ntfs_error(vol->sb, "Failed, returning error code %i.", err); + return ERR_PTR(err); + } +} + +static int ntfs_sd_add_everyone(struct ntfs_inode *ni) +{ + struct security_descriptor_relative *sd; + struct ntfs_acl *acl; + struct ntfs_ace *ace; + struct ntfs_sid *sid; + int ret, sd_len; + + /* Create SECURITY_DESCRIPTOR attribute (everyone has full access). */ + /* + * Calculate security descriptor length. We have 2 sub-authorities in + * owner and group SIDs, So add 8 bytes to every SID. + */ + sd_len =3D sizeof(struct security_descriptor_relative) + 2 * + (sizeof(struct ntfs_sid) + 8) + sizeof(struct ntfs_acl) + + sizeof(struct ntfs_ace) + 4; + sd =3D kmalloc(sd_len, GFP_NOFS); + if (!sd) + return -1; + + sd->revision =3D 1; + sd->control =3D SE_DACL_PRESENT | SE_SELF_RELATIVE; + + sid =3D (struct ntfs_sid *)((u8 *)sd + sizeof(struct security_descriptor_= relative)); + sid->revision =3D 1; + sid->sub_authority_count =3D 2; + sid->sub_authority[0] =3D cpu_to_le32(SECURITY_BUILTIN_DOMAIN_RID); + sid->sub_authority[1] =3D cpu_to_le32(DOMAIN_ALIAS_RID_ADMINS); + sid->identifier_authority.value[5] =3D 5; + sd->owner =3D cpu_to_le32((u8 *)sid - (u8 *)sd); + + sid =3D (struct ntfs_sid *)((u8 *)sid + sizeof(struct ntfs_sid) + 8); + sid->revision =3D 1; + sid->sub_authority_count =3D 2; + sid->sub_authority[0] =3D cpu_to_le32(SECURITY_BUILTIN_DOMAIN_RID); + sid->sub_authority[1] =3D cpu_to_le32(DOMAIN_ALIAS_RID_ADMINS); + sid->identifier_authority.value[5] =3D 5; + sd->group =3D cpu_to_le32((u8 *)sid - (u8 *)sd); + + acl =3D (struct ntfs_acl *)((u8 *)sid + sizeof(struct ntfs_sid) + 8); + acl->revision =3D 2; + acl->size =3D cpu_to_le16(sizeof(struct ntfs_acl) + sizeof(struct ntfs_ac= e) + 4); + acl->ace_count =3D cpu_to_le16(1); + sd->dacl =3D cpu_to_le32((u8 *)acl - (u8 *)sd); + + ace =3D (struct ntfs_ace *)((u8 *)acl + sizeof(struct ntfs_acl)); + ace->type =3D ACCESS_ALLOWED_ACE_TYPE; + ace->flags =3D OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE; + ace->size =3D cpu_to_le16(sizeof(struct ntfs_ace) + 4); + ace->mask =3D cpu_to_le32(0x1f01ff); + ace->sid.revision =3D 1; + ace->sid.sub_authority_count =3D 1; + ace->sid.sub_authority[0] =3D 0; + ace->sid.identifier_authority.value[5] =3D 1; + + ret =3D ntfs_attr_add(ni, AT_SECURITY_DESCRIPTOR, AT_UNNAMED, 0, (u8 *)sd, + sd_len); + if (ret) + ntfs_error(ni->vol->sb, "Failed to add SECURITY_DESCRIPTOR\n"); + + kfree(sd); + return ret; +} + +static struct ntfs_inode *__ntfs_create(struct mnt_idmap *idmap, struct in= ode *dir, + __le16 *name, u8 name_len, mode_t mode, dev_t dev, + __le16 *target, int target_len) +{ + struct ntfs_inode *dir_ni =3D NTFS_I(dir); + struct ntfs_volume *vol =3D dir_ni->vol; + struct ntfs_inode *ni; + bool rollback_data =3D false, rollback_sd =3D false, rollback_reparse =3D= false; + struct file_name_attr *fn =3D NULL; + struct standard_information *si =3D NULL; + int err =3D 0, fn_len, si_len; + struct inode *vi; + struct mft_record *ni_mrec, *dni_mrec; + struct super_block *sb =3D dir_ni->vol->sb; + __le64 parent_mft_ref; + u64 child_mft_ref; + __le16 ea_size; + + vi =3D new_inode(vol->sb); + if (!vi) + return ERR_PTR(-ENOMEM); + + ntfs_init_big_inode(vi); + ni =3D NTFS_I(vi); + ni->vol =3D dir_ni->vol; + ni->name_len =3D 0; + ni->name =3D NULL; + + /* + * Set the appropriate mode, attribute type, and name. For + * directories, also setup the index values to the defaults. + */ + if (S_ISDIR(mode)) { + mode &=3D ~vol->dmask; + + NInoSetMstProtected(ni); + ni->itype.index.block_size =3D 4096; + ni->itype.index.block_size_bits =3D ntfs_ffs(4096) - 1; + ni->itype.index.collation_rule =3D COLLATION_FILE_NAME; + if (vol->cluster_size <=3D ni->itype.index.block_size) { + ni->itype.index.vcn_size =3D vol->cluster_size; + ni->itype.index.vcn_size_bits =3D + vol->cluster_size_bits; + } else { + ni->itype.index.vcn_size =3D vol->sector_size; + ni->itype.index.vcn_size_bits =3D + vol->sector_size_bits; + } + } else { + mode &=3D ~vol->fmask; + } + + if (IS_RDONLY(vi)) + mode &=3D ~0222; + + inode_init_owner(idmap, vi, dir, mode); + + mode =3D vi->i_mode; + +#ifdef CONFIG_NTFS_FS_POSIX_ACL + if (!S_ISLNK(mode) && (sb->s_flags & SB_POSIXACL)) { + err =3D ntfs_init_acl(idmap, vi, dir); + if (err) + goto err_out; + } else +#endif + { + vi->i_flags |=3D S_NOSEC; + } + + if (uid_valid(vol->uid)) + vi->i_uid =3D vol->uid; + + if (gid_valid(vol->gid)) + vi->i_gid =3D vol->gid; + + /* + * Set the file size to 0, the ntfs inode sizes are set to 0 by + * the call to ntfs_init_big_inode() below. + */ + vi->i_size =3D 0; + vi->i_blocks =3D 0; + + inode_inc_iversion(vi); + + simple_inode_init_ts(vi); + ni->i_crtime =3D inode_get_ctime(vi); + + inode_set_mtime_to_ts(dir, ni->i_crtime); + inode_set_ctime_to_ts(dir, ni->i_crtime); + mark_inode_dirty(dir); + + err =3D ntfs_mft_record_alloc(dir_ni->vol, mode, &ni, NULL, + &ni_mrec); + if (err) { + iput(vi); + return ERR_PTR(err); + } + + /* + * Prevent iget and writeback from finding this inode. + * Caller must call d_instantiate_new instead of d_instantiate. + */ + spin_lock(&vi->i_lock); + inode_state_set(vi, I_NEW | I_CREATING); + spin_unlock(&vi->i_lock); + + /* Add the inode to the inode hash for the superblock. */ + vi->i_ino =3D ni->mft_no; + inode_set_iversion(vi, 1); + insert_inode_hash(vi); + + mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); + mutex_lock_nested(&dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT); + if (NInoBeingDeleted(dir_ni)) { + err =3D -ENOENT; + goto err_out; + } + + dni_mrec =3D map_mft_record(dir_ni); + if (IS_ERR(dni_mrec)) { + ntfs_error(dir_ni->vol->sb, "failed to map mft record for file %ld.\n", + dir_ni->mft_no); + err =3D -EIO; + goto err_out; + } + parent_mft_ref =3D MK_LE_MREF(dir_ni->mft_no, + le16_to_cpu(dni_mrec->sequence_number)); + unmap_mft_record(dir_ni); + + /* + * Create STANDARD_INFORMATION attribute. Write STANDARD_INFORMATION + * version 1.2, windows will upgrade it to version 3 if needed. + */ + si_len =3D offsetof(struct standard_information, file_attributes) + + sizeof(__le32) + 12; + si =3D kzalloc(si_len, GFP_NOFS); + if (!si) { + err =3D -ENOMEM; + goto err_out; + } + + si->creation_time =3D si->last_data_change_time =3D utc2ntfs(ni->i_crtime= ); + si->last_mft_change_time =3D si->last_access_time =3D si->creation_time; + + if (!S_ISREG(mode) && !S_ISDIR(mode)) + si->file_attributes =3D FILE_ATTR_SYSTEM; + + /* Add STANDARD_INFORMATION to inode. */ + err =3D ntfs_attr_add(ni, AT_STANDARD_INFORMATION, AT_UNNAMED, 0, (u8 *)s= i, + si_len); + if (err) { + ntfs_error(sb, "Failed to add STANDARD_INFORMATION attribute.\n"); + goto err_out; + } + + err =3D ntfs_sd_add_everyone(ni); + if (err) + goto err_out; + rollback_sd =3D true; + + if (S_ISDIR(mode)) { + struct index_root *ir =3D NULL; + struct index_entry *ie; + int ir_len, index_len; + + /* Create struct index_root attribute. */ + index_len =3D sizeof(struct index_header) + sizeof(struct index_entry_he= ader); + ir_len =3D offsetof(struct index_root, index) + index_len; + ir =3D kzalloc(ir_len, GFP_NOFS); + if (!ir) { err =3D -ENOMEM; goto err_out; } - do { - ATTR_RECORD *a; - u32 val_len; - - err =3D ntfs_attr_lookup(AT_FILE_NAME, NULL, 0, 0, 0, - NULL, 0, ctx); - if (unlikely(err)) { - ntfs_error(vol->sb, "Inode corrupt: No WIN32 " - "namespace counterpart to DOS " - "file name. Run chkdsk."); - if (err =3D=3D -ENOENT) - err =3D -EIO; - goto err_out; + ir->type =3D AT_FILE_NAME; + ir->collation_rule =3D COLLATION_FILE_NAME; + ir->index_block_size =3D cpu_to_le32(ni->vol->index_record_size); + if (ni->vol->cluster_size <=3D ni->vol->index_record_size) + ir->clusters_per_index_block =3D + NTFS_B_TO_CLU(vol, ni->vol->index_record_size); + else + ir->clusters_per_index_block =3D + ni->vol->index_record_size >> ni->vol->sector_size_bits; + ir->index.entries_offset =3D cpu_to_le32(sizeof(struct index_header)); + ir->index.index_length =3D cpu_to_le32(index_len); + ir->index.allocated_size =3D cpu_to_le32(index_len); + ie =3D (struct index_entry *)((u8 *)ir + sizeof(struct index_root)); + ie->length =3D cpu_to_le16(sizeof(struct index_entry_header)); + ie->key_length =3D 0; + ie->flags =3D INDEX_ENTRY_END; + + /* Add struct index_root attribute to inode. */ + err =3D ntfs_attr_add(ni, AT_INDEX_ROOT, I30, 4, (u8 *)ir, ir_len); + if (err) { + kfree(ir); + ntfs_error(vi->i_sb, "Failed to add struct index_root attribute.\n"); + goto err_out; + } + kfree(ir); + err =3D ntfs_attr_open(ni, AT_INDEX_ROOT, I30, 4); + if (err) + goto err_out; + } else { + /* Add DATA attribute to inode. */ + err =3D ntfs_attr_add(ni, AT_DATA, AT_UNNAMED, 0, NULL, 0); + if (err) { + ntfs_error(dir_ni->vol->sb, "Failed to add DATA attribute.\n"); + goto err_out; + } + rollback_data =3D true; + + err =3D ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0); + if (err) + goto err_out; + + if (S_ISLNK(mode)) { + err =3D ntfs_reparse_set_wsl_symlink(ni, target, target_len); + if (!err) + rollback_reparse =3D true; + } else if (S_ISBLK(mode) || S_ISCHR(mode) || S_ISSOCK(mode) || + S_ISFIFO(mode)) { + si->file_attributes =3D FILE_ATTRIBUTE_RECALL_ON_OPEN; + ni->flags =3D FILE_ATTRIBUTE_RECALL_ON_OPEN; + err =3D ntfs_reparse_set_wsl_not_symlink(ni, mode); + if (!err) + rollback_reparse =3D true; + } + if (err) + goto err_out; + } + + err =3D ntfs_ea_set_wsl_inode(vi, dev, &ea_size, + NTFS_EA_UID | NTFS_EA_GID | NTFS_EA_MODE); + if (err) + goto err_out; + + /* Create FILE_NAME attribute. */ + fn_len =3D sizeof(struct file_name_attr) + name_len * sizeof(__le16); + fn =3D kzalloc(fn_len, GFP_NOFS); + if (!fn) { + err =3D -ENOMEM; + goto err_out; + } + + fn->file_attributes |=3D ni->flags; + fn->parent_directory =3D parent_mft_ref; + fn->file_name_length =3D name_len; + fn->file_name_type =3D FILE_NAME_POSIX; + fn->type.ea.packed_ea_size =3D ea_size; + if (S_ISDIR(mode)) { + fn->file_attributes =3D FILE_ATTR_DUP_FILE_NAME_INDEX_PRESENT; + fn->allocated_size =3D fn->data_size =3D 0; + } else { + fn->data_size =3D cpu_to_le64(ni->data_size); + fn->allocated_size =3D cpu_to_le64(ni->allocated_size); + } + if (!S_ISREG(mode) && !S_ISDIR(mode)) { + fn->file_attributes =3D FILE_ATTR_SYSTEM; + if (rollback_reparse) + fn->file_attributes |=3D FILE_ATTR_REPARSE_POINT; + } + if (NVolHideDotFiles(vol) && name_len > 0 && name[0] =3D=3D cpu_to_le16('= .')) + fn->file_attributes |=3D FILE_ATTR_HIDDEN; + fn->creation_time =3D fn->last_data_change_time =3D utc2ntfs(ni->i_crtime= ); + fn->last_mft_change_time =3D fn->last_access_time =3D fn->creation_time; + memcpy(fn->file_name, name, name_len * sizeof(__le16)); + + /* Add FILE_NAME attribute to inode. */ + err =3D ntfs_attr_add(ni, AT_FILE_NAME, AT_UNNAMED, 0, (u8 *)fn, fn_len); + if (err) { + ntfs_error(sb, "Failed to add FILE_NAME attribute.\n"); + goto err_out; + } + + child_mft_ref =3D MK_MREF(ni->mft_no, + le16_to_cpu(ni_mrec->sequence_number)); + /* Set hard links count and directory flag. */ + ni_mrec->link_count =3D cpu_to_le16(1); + mark_mft_record_dirty(ni); + + /* Add FILE_NAME attribute to index. */ + err =3D ntfs_index_add_filename(dir_ni, fn, child_mft_ref); + if (err) { + ntfs_debug("Failed to add entry to the index"); + goto err_out; + } + + unmap_mft_record(ni); + mutex_unlock(&dir_ni->mrec_lock); + mutex_unlock(&ni->mrec_lock); + + ni->flags =3D fn->file_attributes; + /* Set the sequence number. */ + vi->i_generation =3D ni->seq_no; + set_nlink(vi, 1); + ntfs_set_vfs_operations(vi, mode, dev); + + /* Done! */ + kfree(fn); + kfree(si); + ntfs_debug("Done.\n"); + return ni; + +err_out: + if (rollback_sd) + ntfs_attr_remove(ni, AT_SECURITY_DESCRIPTOR, AT_UNNAMED, 0); + + if (rollback_data) + ntfs_attr_remove(ni, AT_DATA, AT_UNNAMED, 0); + + if (rollback_reparse) + ntfs_delete_reparse_index(ni); + /* + * Free extent MFT records (should not exist any with current + * ntfs_create implementation, but for any case if something will be + * changed in the future). + */ + while (ni->nr_extents !=3D 0) { + int err2; + + err2 =3D ntfs_mft_record_free(ni->vol, *(ni->ext.extent_ntfs_inos)); + if (err2) + ntfs_error(sb, + "Failed to free extent MFT record. Leaving inconsistent metadata.\n"); + ntfs_inode_close(*(ni->ext.extent_ntfs_inos)); + } + if (ntfs_mft_record_free(ni->vol, ni)) + ntfs_error(sb, + "Failed to free MFT record. Leaving inconsistent metadata. Run chkdsk.\= n"); + unmap_mft_record(ni); + kfree(fn); + kfree(si); + + mutex_unlock(&dir_ni->mrec_lock); + mutex_unlock(&ni->mrec_lock); + + remove_inode_hash(vi); + discard_new_inode(vi); + return ERR_PTR(err); +} + +static int ntfs_create(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode, bool excl) +{ + struct ntfs_volume *vol =3D NTFS_SB(dir->i_sb); + struct ntfs_inode *ni; + __le16 *uname; + int uname_len, err; + + if (NVolShutdown(vol)) + return -EIO; + + uname_len =3D ntfs_nlstoucs(vol, dentry->d_name.name, dentry->d_name.len, + &uname, NTFS_MAX_NAME_LEN); + if (uname_len < 0) { + if (uname_len !=3D -ENAMETOOLONG) + ntfs_error(vol->sb, "Failed to convert name to unicode."); + return uname_len; + } + + err =3D ntfs_check_bad_windows_name(vol, uname, uname_len); + if (err) { + kmem_cache_free(ntfs_name_cache, uname); + return err; + } + + if (!(vol->vol_flags & VOLUME_IS_DIRTY)) + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + + ni =3D __ntfs_create(idmap, dir, uname, uname_len, S_IFREG | mode, 0, NUL= L, 0); + kmem_cache_free(ntfs_name_cache, uname); + if (IS_ERR(ni)) + return PTR_ERR(ni); + + d_instantiate_new(dentry, VFS_I(ni)); + + return 0; +} + +static int ntfs_check_unlinkable_dir(struct ntfs_attr_search_ctx *ctx, str= uct file_name_attr *fn) +{ + int link_count; + int ret; + struct ntfs_inode *ni =3D ctx->base_ntfs_ino ? ctx->base_ntfs_ino : ctx->= ntfs_ino; + struct mft_record *ni_mrec =3D ctx->base_mrec ? ctx->base_mrec : ctx->mre= c; + + ret =3D ntfs_check_empty_dir(ni, ni_mrec); + if (!ret || ret !=3D -ENOTEMPTY) + return ret; + + link_count =3D le16_to_cpu(ni_mrec->link_count); + /* + * Directory is non-empty, so we can unlink only if there is more than + * one "real" hard link, i.e. links aren't different DOS and WIN32 names + */ + if ((link_count =3D=3D 1) || + (link_count =3D=3D 2 && fn->file_name_type =3D=3D FILE_NAME_DOS)) { + ret =3D -ENOTEMPTY; + ntfs_debug("Non-empty directory without hard links\n"); + goto no_hardlink; + } + + ret =3D 0; +no_hardlink: + return ret; +} + +static int ntfs_test_inode_attr(struct inode *vi, void *data) +{ + struct ntfs_inode *ni =3D NTFS_I(vi); + unsigned long mft_no =3D (unsigned long)data; + + if (ni->mft_no !=3D mft_no) + return 0; + if (NInoAttr(ni) || ni->nr_extents =3D=3D -1) + return 1; + else + return 0; +} + +/* + * ntfs_delete - delete file or directory from ntfs volume + * @ni: ntfs inode for object to delte + * @dir_ni: ntfs inode for directory in which delete object + * @name: unicode name of the object to delete + * @name_len: length of the name in unicode characters + * @need_lock: whether mrec lock is needed or not + * + * Delete the specified name from the directory index @dir_ni and decrement + * the link count of the target inode @ni. + * + * Return 0 on success and -errno on error. + */ +static int ntfs_delete(struct ntfs_inode *ni, struct ntfs_inode *dir_ni, + __le16 *name, u8 name_len, bool need_lock) +{ + struct ntfs_attr_search_ctx *actx =3D NULL; + struct file_name_attr *fn =3D NULL; + bool looking_for_dos_name =3D false, looking_for_win32_name =3D false; + bool case_sensitive_match =3D true; + int err =3D 0; + struct mft_record *ni_mrec; + struct super_block *sb; + bool link_count_zero =3D false; + + ntfs_debug("Entering.\n"); + + if (need_lock =3D=3D true) { + mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); + mutex_lock_nested(&dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT); + } + + sb =3D dir_ni->vol->sb; + + if (ni->nr_extents =3D=3D -1) + ni =3D ni->ext.base_ntfs_ino; + if (dir_ni->nr_extents =3D=3D -1) + dir_ni =3D dir_ni->ext.base_ntfs_ino; + /* + * Search for FILE_NAME attribute with such name. If it's in POSIX or + * WIN32_AND_DOS namespace, then simply remove it from index and inode. + * If filename in DOS or in WIN32 namespace, then remove DOS name first, + * only then remove WIN32 name. + */ + actx =3D ntfs_attr_get_search_ctx(ni, NULL); + if (!actx) { + ntfs_error(sb, "%s, Failed to get search context", __func__); + if (need_lock) { + mutex_unlock(&dir_ni->mrec_lock); + mutex_unlock(&ni->mrec_lock); + } + return -ENOMEM; + } +search: + while ((err =3D ntfs_attr_lookup(AT_FILE_NAME, AT_UNNAMED, 0, CASE_SENSIT= IVE, + 0, NULL, 0, actx)) =3D=3D 0) { +#ifdef DEBUG + unsigned char *s; +#endif + bool case_sensitive =3D IGNORE_CASE; + + fn =3D (struct file_name_attr *)((u8 *)actx->attr + + le16_to_cpu(actx->attr->data.resident.value_offset)); +#ifdef DEBUG + s =3D ntfs_attr_name_get(ni->vol, fn->file_name, fn->file_name_length); + ntfs_debug("name: '%s' type: %d dos: %d win32: %d case: %d\n", + s, fn->file_name_type, + looking_for_dos_name, looking_for_win32_name, + case_sensitive_match); + ntfs_attr_name_free(&s); +#endif + if (looking_for_dos_name) { + if (fn->file_name_type =3D=3D FILE_NAME_DOS) + break; + continue; + } + if (looking_for_win32_name) { + if (fn->file_name_type =3D=3D FILE_NAME_WIN32) + break; + continue; + } + + /* Ignore hard links from other directories */ + if (dir_ni->mft_no !=3D MREF_LE(fn->parent_directory)) { + ntfs_debug("MFT record numbers don't match (%lu !=3D %lu)\n", + dir_ni->mft_no, + MREF_LE(fn->parent_directory)); + continue; + } + + if (fn->file_name_type =3D=3D FILE_NAME_POSIX || case_sensitive_match) + case_sensitive =3D CASE_SENSITIVE; + + if (ntfs_names_are_equal(fn->file_name, fn->file_name_length, + name, name_len, case_sensitive, + ni->vol->upcase, ni->vol->upcase_len)) { + if (fn->file_name_type =3D=3D FILE_NAME_WIN32) { + looking_for_dos_name =3D true; + ntfs_attr_reinit_search_ctx(actx); + continue; } - /* Consistency checks. */ - a =3D ctx->attr; - if (a->non_resident || a->flags) - goto eio_err_out; - val_len =3D le32_to_cpu(a->data.resident.value_length); - if (le16_to_cpu(a->data.resident.value_offset) + - val_len > le32_to_cpu(a->length)) - goto eio_err_out; - fn =3D (FILE_NAME_ATTR*)((u8*)ctx->attr + le16_to_cpu( - ctx->attr->data.resident.value_offset)); - if ((u32)(fn->file_name_length * sizeof(ntfschar) + - sizeof(FILE_NAME_ATTR)) > val_len) - goto eio_err_out; - } while (fn->file_name_type !=3D FILE_NAME_WIN32); - - /* Convert the found WIN32 name to current NLS code page. */ - nls_name.len =3D (unsigned)ntfs_ucstonls(vol, - (ntfschar*)&fn->file_name, fn->file_name_length, - (unsigned char**)&nls_name.name, 0); + if (fn->file_name_type =3D=3D FILE_NAME_DOS) + looking_for_dos_name =3D true; + break; + } + } + if (err) { + /* + * If case sensitive search failed, then try once again + * ignoring case. + */ + if (err =3D=3D -ENOENT && case_sensitive_match) { + case_sensitive_match =3D false; + ntfs_attr_reinit_search_ctx(actx); + goto search; + } + goto err_out; + } =20 - ntfs_attr_put_search_ctx(ctx); - unmap_mft_record(ni); + err =3D ntfs_check_unlinkable_dir(actx, fn); + if (err) + goto err_out; + + err =3D ntfs_index_remove(dir_ni, fn, le32_to_cpu(actx->attr->data.reside= nt.value_length)); + if (err) + goto err_out; + + err =3D ntfs_attr_record_rm(actx); + if (err) + goto err_out; + + ni_mrec =3D actx->base_mrec ? actx->base_mrec : actx->mrec; + ni_mrec->link_count =3D cpu_to_le16(le16_to_cpu(ni_mrec->link_count) - 1); + drop_nlink(VFS_I(ni)); + + mark_mft_record_dirty(ni); + if (looking_for_dos_name) { + looking_for_dos_name =3D false; + looking_for_win32_name =3D true; + ntfs_attr_reinit_search_ctx(actx); + goto search; + } + + /* + * If hard link count is not equal to zero then we are done. In other + * case there are no reference to this inode left, so we should free all + * non-resident attributes and mark all MFT record as not in use. + */ + if (ni_mrec->link_count =3D=3D 0) { + NInoSetBeingDeleted(ni); + ntfs_delete_reparse_index(ni); + ntfs_delete_object_id_index(ni); + link_count_zero =3D true; + } + + ntfs_attr_put_search_ctx(actx); + if (need_lock =3D=3D true) { + mutex_unlock(&dir_ni->mrec_lock); + mutex_unlock(&ni->mrec_lock); + } + + /* + * If hard link count is not equal to zero then we are done. In other + * case there are no reference to this inode left, so we should free all + * non-resident attributes and mark all MFT record as not in use. + */ + if (link_count_zero =3D=3D true) { + struct inode *attr_vi; + + while ((attr_vi =3D ilookup5(sb, ni->mft_no, ntfs_test_inode_attr, + (void *)ni->mft_no)) !=3D NULL) { + clear_nlink(attr_vi); + iput(attr_vi); + } + } + ntfs_debug("Done.\n"); + return 0; +err_out: + ntfs_attr_put_search_ctx(actx); + if (need_lock) { + mutex_unlock(&dir_ni->mrec_lock); + mutex_unlock(&ni->mrec_lock); + } + return err; +} + +static int ntfs_unlink(struct inode *dir, struct dentry *dentry) +{ + struct inode *vi =3D dentry->d_inode; + struct super_block *sb =3D dir->i_sb; + struct ntfs_volume *vol =3D NTFS_SB(sb); + int err =3D 0; + struct ntfs_inode *ni =3D NTFS_I(vi); + __le16 *uname =3D NULL; + int uname_len; + + if (NVolShutdown(vol)) + return -EIO; + + uname_len =3D ntfs_nlstoucs(vol, dentry->d_name.name, dentry->d_name.len, + &uname, NTFS_MAX_NAME_LEN); + if (uname_len < 0) { + if (uname_len !=3D -ENAMETOOLONG) + ntfs_error(sb, "Failed to convert name to Unicode."); + return -ENOMEM; + } + + err =3D ntfs_check_bad_windows_name(vol, uname, uname_len); + if (err) { + kmem_cache_free(ntfs_name_cache, uname); + return err; + } + + if (!(vol->vol_flags & VOLUME_IS_DIRTY)) + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + + err =3D ntfs_delete(ni, NTFS_I(dir), uname, uname_len, true); + if (err) + goto out; + + inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir)); + mark_inode_dirty(dir); + inode_set_ctime_to_ts(vi, inode_get_ctime(dir)); + if (vi->i_nlink) + mark_inode_dirty(vi); +out: + kmem_cache_free(ntfs_name_cache, uname); + return err; +} + +static struct dentry *ntfs_mkdir(struct mnt_idmap *idmap, struct inode *di= r, + struct dentry *dentry, umode_t mode) +{ + struct super_block *sb =3D dir->i_sb; + struct ntfs_volume *vol =3D NTFS_SB(sb); + int err =3D 0; + struct ntfs_inode *ni; + __le16 *uname; + int uname_len; + + if (NVolShutdown(vol)) + return ERR_PTR(-EIO); + + uname_len =3D ntfs_nlstoucs(vol, dentry->d_name.name, dentry->d_name.len, + &uname, NTFS_MAX_NAME_LEN); + if (uname_len < 0) { + if (uname_len !=3D -ENAMETOOLONG) + ntfs_error(sb, "Failed to convert name to unicode."); + return ERR_PTR(-ENOMEM); + } + + err =3D ntfs_check_bad_windows_name(vol, uname, uname_len); + if (err) { + kmem_cache_free(ntfs_name_cache, uname); + return ERR_PTR(err); + } + + if (!(vol->vol_flags & VOLUME_IS_DIRTY)) + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + + ni =3D __ntfs_create(idmap, dir, uname, uname_len, S_IFDIR | mode, 0, NUL= L, 0); + kmem_cache_free(ntfs_name_cache, uname); + if (IS_ERR(ni)) { + err =3D PTR_ERR(ni); + return ERR_PTR(err); } - m =3D NULL; - ctx =3D NULL; =20 - /* Check if a conversion error occurred. */ - if ((signed)nls_name.len < 0) { - err =3D (signed)nls_name.len; + d_instantiate_new(dentry, VFS_I(ni)); + return ERR_PTR(err); +} + +static int ntfs_rmdir(struct inode *dir, struct dentry *dentry) +{ + struct inode *vi =3D dentry->d_inode; + struct super_block *sb =3D dir->i_sb; + struct ntfs_volume *vol =3D NTFS_SB(sb); + int err =3D 0; + struct ntfs_inode *ni; + __le16 *uname =3D NULL; + int uname_len; + + if (NVolShutdown(vol)) + return -EIO; + + ni =3D NTFS_I(vi); + uname_len =3D ntfs_nlstoucs(vol, dentry->d_name.name, dentry->d_name.len, + &uname, NTFS_MAX_NAME_LEN); + if (uname_len < 0) { + if (uname_len !=3D -ENAMETOOLONG) + ntfs_error(sb, "Failed to convert name to unicode."); + return -ENOMEM; + } + + err =3D ntfs_check_bad_windows_name(vol, uname, uname_len); + if (err) { + kmem_cache_free(ntfs_name_cache, uname); + return err; + } + + if (!(vol->vol_flags & VOLUME_IS_DIRTY)) + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + + err =3D ntfs_delete(ni, NTFS_I(dir), uname, uname_len, true); + if (err) + goto out; + + inode_set_mtime_to_ts(vi, inode_set_atime_to_ts(vi, current_time(vi))); +out: + kmem_cache_free(ntfs_name_cache, uname); + return err; +} + +/* + * __ntfs_link - create hard link for file or directory + * @ni: ntfs inode for object to create hard link + * @dir_ni: ntfs inode for directory in which new link should be placed + * @name: unicode name of the new link + * @name_len: length of the name in unicode characters + * + * Create a new hard link. This involves adding an entry to the directory + * index and adding a new FILE_NAME attribute to the target inode. + * + * Return 0 on success and -errno on error. + */ +static int __ntfs_link(struct ntfs_inode *ni, struct ntfs_inode *dir_ni, + __le16 *name, u8 name_len) +{ + struct super_block *sb; + struct inode *vi =3D VFS_I(ni); + struct file_name_attr *fn =3D NULL; + int fn_len, err =3D 0; + struct mft_record *dir_mrec =3D NULL, *ni_mrec =3D NULL; + + ntfs_debug("Entering.\n"); + + sb =3D dir_ni->vol->sb; + if (NInoBeingDeleted(dir_ni) || NInoBeingDeleted(ni)) + return -ENOENT; + + ni_mrec =3D map_mft_record(ni); + if (IS_ERR(ni_mrec)) { + err =3D -EIO; goto err_out; } - nls_name.hash =3D full_name_hash(dent, nls_name.name, nls_name.len); =20 - dent =3D d_add_ci(dent, dent_inode, &nls_name); - kfree(nls_name.name); - return dent; + if (le16_to_cpu(ni_mrec->link_count) =3D=3D 0) { + err =3D -ENOENT; + goto err_out; + } =20 -eio_err_out: - ntfs_error(vol->sb, "Illegal file name attribute. Run chkdsk."); - err =3D -EIO; + /* Create FILE_NAME attribute. */ + fn_len =3D sizeof(struct file_name_attr) + name_len * sizeof(__le16); + if (name_len > NTFS_MAX_NAME_LEN) { + err =3D -EIO; + goto err_out; + } + fn =3D kzalloc(fn_len, GFP_NOFS); + if (!fn) { + err =3D -ENOMEM; + goto err_out; + } + + dir_mrec =3D map_mft_record(dir_ni); + if (IS_ERR(dir_mrec)) { + err =3D -EIO; + goto err_out; + } + + fn->parent_directory =3D MK_LE_MREF(dir_ni->mft_no, + le16_to_cpu(dir_mrec->sequence_number)); + unmap_mft_record(dir_ni); + fn->file_name_length =3D name_len; + fn->file_name_type =3D FILE_NAME_POSIX; + fn->file_attributes =3D ni->flags; + if (ni_mrec->flags & MFT_RECORD_IS_DIRECTORY) { + fn->file_attributes |=3D FILE_ATTR_DUP_FILE_NAME_INDEX_PRESENT; + fn->allocated_size =3D fn->data_size =3D 0; + } else { + if (NInoSparse(ni) || NInoCompressed(ni)) + fn->allocated_size =3D + cpu_to_le64(ni->itype.compressed.size); + else + fn->allocated_size =3D cpu_to_le64(ni->allocated_size); + fn->data_size =3D cpu_to_le64(ni->data_size); + } + if (NVolHideDotFiles(dir_ni->vol) && name_len > 0 && name[0] =3D=3D cpu_t= o_le16('.')) + fn->file_attributes |=3D FILE_ATTR_HIDDEN; + + fn->creation_time =3D utc2ntfs(ni->i_crtime); + fn->last_data_change_time =3D utc2ntfs(inode_get_mtime(vi)); + fn->last_mft_change_time =3D utc2ntfs(inode_get_ctime(vi)); + fn->last_access_time =3D utc2ntfs(inode_get_atime(vi)); + memcpy(fn->file_name, name, name_len * sizeof(__le16)); + + /* Add FILE_NAME attribute to index. */ + err =3D ntfs_index_add_filename(dir_ni, fn, MK_MREF(ni->mft_no, + le16_to_cpu(ni_mrec->sequence_number))); + if (err) { + ntfs_error(sb, "Failed to add filename to the index"); + goto err_out; + } + /* Add FILE_NAME attribute to inode. */ + err =3D ntfs_attr_add(ni, AT_FILE_NAME, AT_UNNAMED, 0, (u8 *)fn, fn_len); + if (err) { + ntfs_error(sb, "Failed to add FILE_NAME attribute.\n"); + /* Try to remove just added attribute from index. */ + if (ntfs_index_remove(dir_ni, fn, fn_len)) + goto rollback_failed; + goto err_out; + } + /* Increment hard links count. */ + ni_mrec->link_count =3D cpu_to_le16(le16_to_cpu(ni_mrec->link_count) + 1); + inc_nlink(VFS_I(ni)); + + /* Done! */ + mark_mft_record_dirty(ni); + kfree(fn); + unmap_mft_record(ni); + + ntfs_debug("Done.\n"); + + return 0; +rollback_failed: + ntfs_error(sb, "Rollback failed. Leaving inconsistent metadata.\n"); err_out: - if (ctx) - ntfs_attr_put_search_ctx(ctx); - if (m) + kfree(fn); + if (!IS_ERR_OR_NULL(ni_mrec)) unmap_mft_record(ni); - iput(dent_inode); - ntfs_error(vol->sb, "Failed, returning error code %i.", err); - return ERR_PTR(err); - } + return err; +} + +static int ntfs_rename(struct mnt_idmap *idmap, struct inode *old_dir, + struct dentry *old_dentry, struct inode *new_dir, + struct dentry *new_dentry, unsigned int flags) +{ + struct inode *old_inode, *new_inode =3D NULL; + int err =3D 0; + int is_dir; + struct super_block *sb =3D old_dir->i_sb; + __le16 *uname_new =3D NULL; + __le16 *uname_old =3D NULL; + int new_name_len; + int old_name_len; + struct ntfs_volume *vol =3D NTFS_SB(sb); + struct ntfs_inode *old_ni, *new_ni =3D NULL; + struct ntfs_inode *old_dir_ni =3D NTFS_I(old_dir), *new_dir_ni =3D NTFS_I= (new_dir); + + if (NVolShutdown(old_dir_ni->vol)) + return -EIO; + + if (flags & (RENAME_EXCHANGE | RENAME_WHITEOUT)) + return -EINVAL; + + new_name_len =3D ntfs_nlstoucs(NTFS_I(new_dir)->vol, new_dentry->d_name.n= ame, + new_dentry->d_name.len, &uname_new, + NTFS_MAX_NAME_LEN); + if (new_name_len < 0) { + if (new_name_len !=3D -ENAMETOOLONG) + ntfs_error(sb, "Failed to convert name to unicode."); + return -ENOMEM; + } + + err =3D ntfs_check_bad_windows_name(vol, uname_new, new_name_len); + if (err) { + kmem_cache_free(ntfs_name_cache, uname_new); + return err; + } + + old_name_len =3D ntfs_nlstoucs(NTFS_I(old_dir)->vol, old_dentry->d_name.n= ame, + old_dentry->d_name.len, &uname_old, + NTFS_MAX_NAME_LEN); + if (old_name_len < 0) { + kmem_cache_free(ntfs_name_cache, uname_new); + if (old_name_len !=3D -ENAMETOOLONG) + ntfs_error(sb, "Failed to convert name to unicode."); + return -ENOMEM; + } + + old_inode =3D old_dentry->d_inode; + new_inode =3D new_dentry->d_inode; + old_ni =3D NTFS_I(old_inode); + + if (!(vol->vol_flags & VOLUME_IS_DIRTY)) + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + + mutex_lock_nested(&old_ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); + mutex_lock_nested(&old_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT); + + if (NInoBeingDeleted(old_ni) || NInoBeingDeleted(old_dir_ni)) { + err =3D -ENOENT; + goto unlock_old; + } + + is_dir =3D S_ISDIR(old_inode->i_mode); + + if (new_inode) { + new_ni =3D NTFS_I(new_inode); + mutex_lock_nested(&new_ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL_2); + if (old_dir !=3D new_dir) { + mutex_lock_nested(&new_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT_2); + if (NInoBeingDeleted(new_dir_ni)) { + err =3D -ENOENT; + goto err_out; + } + } + + if (NInoBeingDeleted(new_ni)) { + err =3D -ENOENT; + goto err_out; + } + + if (is_dir) { + struct mft_record *ni_mrec; + + ni_mrec =3D map_mft_record(NTFS_I(new_inode)); + if (IS_ERR(ni_mrec)) { + err =3D -EIO; + goto err_out; + } + err =3D ntfs_check_empty_dir(NTFS_I(new_inode), ni_mrec); + unmap_mft_record(NTFS_I(new_inode)); + if (err) + goto err_out; + } + + err =3D ntfs_delete(new_ni, new_dir_ni, uname_new, new_name_len, false); + if (err) + goto err_out; + } else { + if (old_dir !=3D new_dir) { + mutex_lock_nested(&new_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT_2); + if (NInoBeingDeleted(new_dir_ni)) { + err =3D -ENOENT; + goto err_out; + } + } + } + + err =3D __ntfs_link(old_ni, new_dir_ni, uname_new, new_name_len); + if (err) + goto err_out; + + err =3D ntfs_delete(old_ni, old_dir_ni, uname_old, old_name_len, false); + if (err) { + int err2; + + ntfs_error(sb, "Failed to delete old ntfs inode(%ld) in old dir, err : %= d\n", + old_ni->mft_no, err); + err2 =3D ntfs_delete(old_ni, new_dir_ni, uname_new, new_name_len, false); + if (err2) + ntfs_error(sb, "Failed to delete old ntfs inode in new dir, err : %d\n", + err2); + goto err_out; + } + + simple_rename_timestamp(old_dir, old_dentry, new_dir, new_dentry); + mark_inode_dirty(old_inode); + mark_inode_dirty(old_dir); + if (old_dir !=3D new_dir) + mark_inode_dirty(new_dir); + if (new_inode) + mark_inode_dirty(old_inode); + + inode_inc_iversion(new_dir); + +err_out: + if (old_dir !=3D new_dir) + mutex_unlock(&new_dir_ni->mrec_lock); + if (new_inode) + mutex_unlock(&new_ni->mrec_lock); + +unlock_old: + mutex_unlock(&old_dir_ni->mrec_lock); + mutex_unlock(&old_ni->mrec_lock); + if (uname_new) + kmem_cache_free(ntfs_name_cache, uname_new); + if (uname_old) + kmem_cache_free(ntfs_name_cache, uname_old); + + return err; +} + +static int ntfs_symlink(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, const char *symname) +{ + struct super_block *sb =3D dir->i_sb; + struct ntfs_volume *vol =3D NTFS_SB(sb); + struct inode *vi; + int err =3D 0; + struct ntfs_inode *ni; + __le16 *usrc; + __le16 *utarget; + int usrc_len; + int utarget_len; + int symlen =3D strlen(symname); + + if (NVolShutdown(vol)) + return -EIO; + + usrc_len =3D ntfs_nlstoucs(vol, dentry->d_name.name, + dentry->d_name.len, &usrc, NTFS_MAX_NAME_LEN); + if (usrc_len < 0) { + if (usrc_len !=3D -ENAMETOOLONG) + ntfs_error(sb, "Failed to convert name to Unicode."); + err =3D -ENOMEM; + goto out; + } + + err =3D ntfs_check_bad_windows_name(vol, usrc, usrc_len); + if (err) { + kmem_cache_free(ntfs_name_cache, usrc); + goto out; + } + + utarget_len =3D ntfs_nlstoucs(vol, symname, symlen, &utarget, + PATH_MAX); + if (utarget_len < 0) { + if (utarget_len !=3D -ENAMETOOLONG) + ntfs_error(sb, "Failed to convert target name to Unicode."); + err =3D -ENOMEM; + kmem_cache_free(ntfs_name_cache, usrc); + goto out; + } + + if (!(vol->vol_flags & VOLUME_IS_DIRTY)) + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + + ni =3D __ntfs_create(idmap, dir, usrc, usrc_len, S_IFLNK | 0777, 0, + utarget, utarget_len); + kmem_cache_free(ntfs_name_cache, usrc); + kvfree(utarget); + if (IS_ERR(ni)) { + err =3D PTR_ERR(ni); + goto out; + } + + vi =3D VFS_I(ni); + vi->i_size =3D symlen; + d_instantiate_new(dentry, vi); +out: + return err; +} + +static int ntfs_mknod(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode, dev_t rdev) +{ + struct super_block *sb =3D dir->i_sb; + struct ntfs_volume *vol =3D NTFS_SB(sb); + int err =3D 0; + struct ntfs_inode *ni; + __le16 *uname =3D NULL; + int uname_len; + + if (NVolShutdown(vol)) + return -EIO; + + uname_len =3D ntfs_nlstoucs(vol, dentry->d_name.name, + dentry->d_name.len, &uname, NTFS_MAX_NAME_LEN); + if (uname_len < 0) { + if (uname_len !=3D -ENAMETOOLONG) + ntfs_error(sb, "Failed to convert name to Unicode."); + return -ENOMEM; + } + + err =3D ntfs_check_bad_windows_name(vol, uname, uname_len); + if (err) { + kmem_cache_free(ntfs_name_cache, uname); + return err; + } + + if (!(vol->vol_flags & VOLUME_IS_DIRTY)) + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + + switch (mode & S_IFMT) { + case S_IFCHR: + case S_IFBLK: + ni =3D __ntfs_create(idmap, dir, uname, uname_len, + mode, rdev, NULL, 0); + break; + default: + ni =3D __ntfs_create(idmap, dir, uname, uname_len, + mode, 0, NULL, 0); + } + + kmem_cache_free(ntfs_name_cache, uname); + if (IS_ERR(ni)) { + err =3D PTR_ERR(ni); + goto out; + } + + d_instantiate_new(dentry, VFS_I(ni)); +out: + return err; +} + +static int ntfs_link(struct dentry *old_dentry, struct inode *dir, + struct dentry *dentry) +{ + struct inode *vi =3D old_dentry->d_inode; + struct super_block *sb =3D vi->i_sb; + struct ntfs_volume *vol =3D NTFS_SB(sb); + __le16 *uname =3D NULL; + int uname_len; + int err; + struct ntfs_inode *ni =3D NTFS_I(vi), *dir_ni =3D NTFS_I(dir); + + if (NVolShutdown(vol)) + return -EIO; + + uname_len =3D ntfs_nlstoucs(vol, dentry->d_name.name, + dentry->d_name.len, &uname, NTFS_MAX_NAME_LEN); + if (uname_len < 0) { + if (uname_len !=3D -ENAMETOOLONG) + ntfs_error(sb, "Failed to convert name to unicode."); + err =3D -ENOMEM; + goto out; + } + + if (!(vol->vol_flags & VOLUME_IS_DIRTY)) + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + + ihold(vi); + mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); + mutex_lock_nested(&dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT); + err =3D __ntfs_link(NTFS_I(vi), NTFS_I(dir), uname, uname_len); + if (err) { + mutex_unlock(&dir_ni->mrec_lock); + mutex_unlock(&ni->mrec_lock); + iput(vi); + pr_err("failed to create link, err =3D %d\n", err); + goto out; + } + + inode_inc_iversion(dir); + simple_inode_init_ts(dir); + + inode_inc_iversion(vi); + simple_inode_init_ts(vi); + + /* timestamp is already written, so mark_inode_dirty() is unneeded. */ + d_instantiate(dentry, vi); + mutex_unlock(&dir_ni->mrec_lock); + mutex_unlock(&ni->mrec_lock); + +out: + kfree(uname); + return err; } =20 /* * Inode operations for directories. */ const struct inode_operations ntfs_dir_inode_ops =3D { - .lookup =3D ntfs_lookup, /* VFS: Lookup directory. */ + .lookup =3D ntfs_lookup, /* VFS: Lookup directory. */ + .create =3D ntfs_create, + .unlink =3D ntfs_unlink, + .mkdir =3D ntfs_mkdir, + .rmdir =3D ntfs_rmdir, + .rename =3D ntfs_rename, + .get_acl =3D ntfs_get_acl, + .set_acl =3D ntfs_set_acl, + .listxattr =3D ntfs_listxattr, + .setattr =3D ntfs_setattr, + .getattr =3D ntfs_getattr, + .symlink =3D ntfs_symlink, + .mknod =3D ntfs_mknod, + .link =3D ntfs_link, }; =20 -/** +/* * ntfs_get_parent - find the dentry of the parent of a given directory de= ntry * @child_dent: dentry of the directory whose parent directory to find * @@ -275,9 +1598,6 @@ const struct inode_operations ntfs_dir_inode_ops =3D { * fs/exportfs/expfs.c::find_exported_dentry() which in turn is called fro= m the * default ->decode_fh() which is export_decode_fh() in the same file. * - * The code is based on the ext3 ->get_parent() implementation found in - * fs/ext3/namei.c::ext3_get_parent(). - * * Note: ntfs_get_parent() is called with @d_inode(child_dent)->i_mutex do= wn. * * Return the dentry of the parent directory on success or the error code = on @@ -286,11 +1606,11 @@ const struct inode_operations ntfs_dir_inode_ops =3D= { static struct dentry *ntfs_get_parent(struct dentry *child_dent) { struct inode *vi =3D d_inode(child_dent); - ntfs_inode *ni =3D NTFS_I(vi); - MFT_RECORD *mrec; - ntfs_attr_search_ctx *ctx; - ATTR_RECORD *attr; - FILE_NAME_ATTR *fn; + struct ntfs_inode *ni =3D NTFS_I(vi); + struct mft_record *mrec; + struct ntfs_attr_search_ctx *ctx; + struct attr_record *attr; + struct file_name_attr *fn; unsigned long parent_ino; int err; =20 @@ -312,18 +1632,18 @@ static struct dentry *ntfs_get_parent(struct dentry = *child_dent) ntfs_attr_put_search_ctx(ctx); unmap_mft_record(ni); if (err =3D=3D -ENOENT) - ntfs_error(vi->i_sb, "Inode 0x%lx does not have a " - "file name attribute. Run chkdsk.", - vi->i_ino); + ntfs_error(vi->i_sb, + "Inode 0x%lx does not have a file name attribute. Run chkdsk.", + vi->i_ino); return ERR_PTR(err); } attr =3D ctx->attr; if (unlikely(attr->non_resident)) goto try_next; - fn =3D (FILE_NAME_ATTR *)((u8 *)attr + + fn =3D (struct file_name_attr *)((u8 *)attr + le16_to_cpu(attr->data.resident.value_offset)); if (unlikely((u8 *)fn + le32_to_cpu(attr->data.resident.value_length) > - (u8*)attr + le32_to_cpu(attr->length))) + (u8 *)attr + le32_to_cpu(attr->length))) goto try_next; /* Get the inode number of the parent directory. */ parent_ino =3D MREF_LE(fn->parent_directory); @@ -341,7 +1661,7 @@ static struct inode *ntfs_nfs_get_inode(struct super_b= lock *sb, =20 inode =3D ntfs_iget(sb, ino); if (!IS_ERR(inode)) { - if (is_bad_inode(inode) || inode->i_generation !=3D generation) { + if (inode->i_generation !=3D generation) { iput(inode); inode =3D ERR_PTR(-ESTALE); } @@ -366,27 +1686,10 @@ static struct dentry *ntfs_fh_to_parent(struct super= _block *sb, struct fid *fid, =20 /* * Export operations allowing NFS exporting of mounted NTFS partitions. - * - * We use the default ->encode_fh() for now. Note that they - * use 32 bits to store the inode number which is an unsigned long so on 6= 4-bit - * architectures is usually 64 bits so it would all fail horribly on huge - * volumes. I guess we need to define our own encode and decode fh functi= ons - * that store 64-bit inode numbers at some point but for now we will ignor= e the - * problem... - * - * We also use the default ->get_name() helper (used by ->decode_fh() via - * fs/exportfs/expfs.c::find_exported_dentry()) as that is completely fs - * independent. - * - * The default ->get_parent() just returns -EACCES so we have to provide o= ur - * own and the default ->get_dentry() is incompatible with NTFS due to not - * allowing the inode number 0 which is used in NTFS for the system file $= MFT - * and due to using iget() whereas NTFS needs ntfs_iget(). */ const struct export_operations ntfs_export_ops =3D { - .encode_fh =3D generic_encode_ino32_fh, - .get_parent =3D ntfs_get_parent, /* Find the parent of a given - directory. */ + .encode_fh =3D generic_encode_ino32_fh, + .get_parent =3D ntfs_get_parent, /* Find the parent of a given directory.= */ .fh_to_dentry =3D ntfs_fh_to_dentry, .fh_to_parent =3D ntfs_fh_to_parent, }; --=20 2.25.1