提交 dd072330 编写于 作者: A Anton Altaparmakov

NTFS: Implement fs/ntfs/inode.[hc]::ntfs_truncate(). It only supports

      uncompressed and unencrypted files.
Signed-off-by: NAnton Altaparmakov <aia21@cantab.net>
上级 2d86829b
...@@ -3,16 +3,14 @@ ToDo/Notes: ...@@ -3,16 +3,14 @@ ToDo/Notes:
- In between ntfs_prepare/commit_write, need exclusion between - In between ntfs_prepare/commit_write, need exclusion between
simultaneous file extensions. This is given to us by holding i_sem simultaneous file extensions. This is given to us by holding i_sem
on the inode. The only places in the kernel when a file is resized on the inode. The only places in the kernel when a file is resized
are prepare/commit write and truncate for both of which i_sem is are prepare/commit write and ntfs_truncate() for both of which i_sem
held. Just have to be careful in readpage/writepage and all other is held. Just have to be careful in read-/writepage and other helpers
helpers not running under i_sem that we play nice... not running under i_sem that we play nice... Also need to be careful
Also need to be careful with initialized_size extention in with initialized_size extention in ntfs_prepare_write and writepage.
ntfs_prepare_write. Basically, just be _very_ careful in this code... UPDATE: The only things that need to be checked are
UPDATE: The only things that need to be checked are read/writepage prepare/commit_write as well as the compressed write and the other
which do not hold i_sem. Note writepage cannot change i_size but it attribute resize/write cases like index attributes, etc. For now
needs to cope with a concurrent i_size change, just like readpage. none of these are implemented so are safe.
Also both need to cope with concurrent changes to the other sizes,
i.e. initialized/allocated/compressed size, as well.
- Implement mft.c::sync_mft_mirror_umount(). We currently will just - Implement mft.c::sync_mft_mirror_umount(). We currently will just
leave the volume dirty on umount if the final iput(vol->mft_ino) leave the volume dirty on umount if the final iput(vol->mft_ino)
causes a write of any mirrored mft records due to the mft mirror causes a write of any mirrored mft records due to the mft mirror
...@@ -50,6 +48,8 @@ ToDo/Notes: ...@@ -50,6 +48,8 @@ ToDo/Notes:
- Add fs/ntfs/attrib.[hc]::ntfs_attr_extend_allocation(), a function to - Add fs/ntfs/attrib.[hc]::ntfs_attr_extend_allocation(), a function to
extend the allocation of an attributes. Optionally, the data size, extend the allocation of an attributes. Optionally, the data size,
but not the initialized size can be extended, too. but not the initialized size can be extended, too.
- Implement fs/ntfs/inode.[hc]::ntfs_truncate(). It only supports
uncompressed and unencrypted files.
2.1.24 - Lots of bug fixes and support more clean journal states. 2.1.24 - Lots of bug fixes and support more clean journal states.
......
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
#include "debug.h" #include "debug.h"
#include "inode.h" #include "inode.h"
#include "attrib.h" #include "attrib.h"
#include "lcnalloc.h"
#include "malloc.h" #include "malloc.h"
#include "mft.h" #include "mft.h"
#include "time.h" #include "time.h"
...@@ -2291,11 +2292,16 @@ int ntfs_show_options(struct seq_file *sf, struct vfsmount *mnt) ...@@ -2291,11 +2292,16 @@ int ntfs_show_options(struct seq_file *sf, struct vfsmount *mnt)
#ifdef NTFS_RW #ifdef NTFS_RW
static const char *es = " Leaving inconsistent metadata. Unmount and run "
"chkdsk.";
/** /**
* ntfs_truncate - called when the i_size of an ntfs inode is changed * ntfs_truncate - called when the i_size of an ntfs inode is changed
* @vi: inode for which the i_size was changed * @vi: inode for which the i_size was changed
* *
* We do not support i_size changes yet. * 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 * The kernel guarantees that @vi is a regular file (S_ISREG() is true) and
* that the change is allowed. * that the change is allowed.
...@@ -2306,80 +2312,499 @@ int ntfs_show_options(struct seq_file *sf, struct vfsmount *mnt) ...@@ -2306,80 +2312,499 @@ int ntfs_show_options(struct seq_file *sf, struct vfsmount *mnt)
* Returns 0 on success or -errno on error. * Returns 0 on success or -errno on error.
* *
* Called with ->i_sem held. In all but one case ->i_alloc_sem is held for * Called with ->i_sem held. In all but one case ->i_alloc_sem is held for
* writing. The only case where ->i_alloc_sem is not held is * writing. The only case in the kernel where ->i_alloc_sem is not held is
* mm/filemap.c::generic_file_buffered_write() where vmtruncate() is called * mm/filemap.c::generic_file_buffered_write() where vmtruncate() is called
* with the current i_size as the offset which means that it is a noop as far * with the current i_size as the offset. The analogous place in NTFS is in
* as ntfs_truncate() is concerned. * fs/ntfs/file.c::ntfs_file_buffered_write() where we call vmtruncate() again
* without holding ->i_alloc_sem.
*/ */
int ntfs_truncate(struct inode *vi) int ntfs_truncate(struct inode *vi)
{ {
ntfs_inode *ni = NTFS_I(vi); s64 new_size, old_size, nr_freed, new_alloc_size, old_alloc_size;
VCN highest_vcn;
unsigned long flags;
ntfs_inode *base_ni, *ni = NTFS_I(vi);
ntfs_volume *vol = ni->vol; ntfs_volume *vol = ni->vol;
ntfs_attr_search_ctx *ctx; ntfs_attr_search_ctx *ctx;
MFT_RECORD *m; MFT_RECORD *m;
ATTR_RECORD *a; ATTR_RECORD *a;
const char *te = " Leaving file length out of sync with i_size."; const char *te = " Leaving file length out of sync with i_size.";
int err; int err, mp_size, size_change, alloc_change;
u32 attr_len;
ntfs_debug("Entering for inode 0x%lx.", vi->i_ino); ntfs_debug("Entering for inode 0x%lx.", vi->i_ino);
BUG_ON(NInoAttr(ni)); BUG_ON(NInoAttr(ni));
BUG_ON(S_ISDIR(vi->i_mode));
BUG_ON(NInoMstProtected(ni));
BUG_ON(ni->nr_extents < 0); BUG_ON(ni->nr_extents < 0);
m = map_mft_record(ni); 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 = ni;
else
base_ni = ni->ext.base_ntfs_ino;
m = map_mft_record(base_ni);
if (IS_ERR(m)) { if (IS_ERR(m)) {
err = PTR_ERR(m); err = PTR_ERR(m);
ntfs_error(vi->i_sb, "Failed to map mft record for inode 0x%lx " ntfs_error(vi->i_sb, "Failed to map mft record for inode 0x%lx "
"(error code %d).%s", vi->i_ino, err, te); "(error code %d).%s", vi->i_ino, err, te);
ctx = NULL; ctx = NULL;
m = NULL; m = NULL;
goto err_out; goto old_bad_out;
} }
ctx = ntfs_attr_get_search_ctx(ni, m); ctx = ntfs_attr_get_search_ctx(base_ni, m);
if (unlikely(!ctx)) { if (unlikely(!ctx)) {
ntfs_error(vi->i_sb, "Failed to allocate a search context for " ntfs_error(vi->i_sb, "Failed to allocate a search context for "
"inode 0x%lx (not enough memory).%s", "inode 0x%lx (not enough memory).%s",
vi->i_ino, te); vi->i_ino, te);
err = -ENOMEM; err = -ENOMEM;
goto err_out; goto old_bad_out;
} }
err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len,
CASE_SENSITIVE, 0, NULL, 0, ctx); CASE_SENSITIVE, 0, NULL, 0, ctx);
if (unlikely(err)) { if (unlikely(err)) {
if (err == -ENOENT) if (err == -ENOENT) {
ntfs_error(vi->i_sb, "Open attribute is missing from " ntfs_error(vi->i_sb, "Open attribute is missing from "
"mft record. Inode 0x%lx is corrupt. " "mft record. Inode 0x%lx is corrupt. "
"Run chkdsk.", vi->i_ino); "Run chkdsk.%s", vi->i_ino, te);
else err = -EIO;
} else
ntfs_error(vi->i_sb, "Failed to lookup attribute in " ntfs_error(vi->i_sb, "Failed to lookup attribute in "
"inode 0x%lx (error code %d).", "inode 0x%lx (error code %d).%s",
vi->i_ino, err); vi->i_ino, err, te);
goto err_out; goto old_bad_out;
} }
m = ctx->mrec;
a = ctx->attr; a = ctx->attr;
/* If the size has not changed there is nothing to do. */ /*
if (ntfs_attr_size(a) == i_size_read(vi)) * The i_size of the vfs inode is the new size for the attribute value.
*/
new_size = i_size_read(vi);
/* The current size of the attribute value is the old size. */
old_size = ntfs_attr_size(a);
/* Calculate the new allocated size. */
if (NInoNonResident(ni))
new_alloc_size = (new_size + vol->cluster_size - 1) &
~(s64)vol->cluster_size_mask;
else
new_alloc_size = (new_size + 7) & ~7;
/* The current allocated size is the old allocated size. */
read_lock_irqsave(&ni->size_lock, flags);
old_alloc_size = ni->allocated_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 = -1;
if (new_size - old_size >= 0) {
size_change = 1;
if (new_size == old_size)
size_change = 0;
}
/* As above for the allocated size. */
alloc_change = -1;
if (new_alloc_size - old_alloc_size >= 0) {
alloc_change = 1;
if (new_alloc_size == old_alloc_size)
alloc_change = 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 = ntfs_attr_size_bounds_check(vol, ni->type, new_size);
if (unlikely(err)) {
if (err == -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 = -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 = -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 = -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)) {
unsigned long flags;
/* 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 = 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 readpage 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
* occured 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 = 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 = 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 != -EPERM && err != -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 != -ENOMEM)
err = -EIO;
goto conv_err_out;
}
/* TODO: Not implemented from here, abort. */
if (err == -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 == -EPERM) */
ntfs_error(vol->sb, "This attribute type may not be "
"non-resident. This case is not implemented "
"yet.");
err = -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 == AT_ATTRIBUTE_LIST ||
ni->type == AT_STANDARD_INFORMATION) {
// TODO: Attempt to move other attributes into extent mft
// records.
err = -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 = -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 = 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 = -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 = new_size;
a->data.non_resident.initialized_size =
cpu_to_sle64(new_size);
}
a->data.non_resident.data_size = 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 >= 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 = 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; goto done;
// TODO: Implement the truncate... }
ntfs_error(vi->i_sb, "Inode size has changed but this is not " if (!alloc_change)
"implemented yet. Resetting inode size to old value. " goto alloc_done;
" This is most likely a bug in the ntfs driver!"); }
i_size_write(vi, ntfs_attr_size(a)); /* alloc_change < 0 */
done: /* Free the clusters. */
nr_freed = ntfs_cluster_free(ni, new_alloc_size >>
vol->cluster_size_bits, -1, ctx);
m = ctx->mrec;
a = 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 = 0;
}
/* Truncate the runlist. */
err = 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 = -EIO;
goto bad_out;
}
/* Get the size for the shrunk mapping pairs array for the runlist. */
mp_size = ntfs_get_size_for_mapping_pairs(vol, ni->runlist.rl, 0, -1);
if (unlikely(mp_size <= 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 = -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.
*/
attr_len = le32_to_cpu(a->length);
err = 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 = 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 = -EIO;
goto bad_out;
}
/* Update the allocated/compressed size as well as the highest vcn. */
a->data.non_resident.highest_vcn = cpu_to_sle64((new_alloc_size >>
vol->cluster_size_bits) - 1);
write_lock_irqsave(&ni->size_lock, flags);
ni->allocated_size = new_alloc_size;
a->data.non_resident.allocated_size = cpu_to_sle64(new_alloc_size);
if (NInoSparse(ni) || NInoCompressed(ni)) {
if (nr_freed) {
ni->itype.compressed.size -= nr_freed <<
vol->cluster_size_bits;
BUG_ON(ni->itype.compressed.size < 0);
a->data.non_resident.compressed_size = cpu_to_sle64(
ni->itype.compressed.size);
vi->i_blocks = ni->itype.compressed.size >> 9;
}
} else
vi->i_blocks = 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 = 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); ntfs_attr_put_search_ctx(ctx);
unmap_mft_record(ni); unmap_mft_record(base_ni);
up_write(&ni->runlist.lock);
done:
/* Update the mtime and ctime on the base inode. */
inode_update_time(VFS_I(base_ni), 1);
if (likely(!err)) {
NInoClearTruncateFailed(ni); NInoClearTruncateFailed(ni);
ntfs_debug("Done."); ntfs_debug("Done.");
return 0; }
err_out: return err;
if (err != -ENOMEM) { old_bad_out:
NVolSetErrors(vol); old_size = -1;
bad_out:
if (err != -ENOMEM && err != -EOPNOTSUPP) {
make_bad_inode(vi); make_bad_inode(vi);
make_bad_inode(VFS_I(base_ni));
NVolSetErrors(vol);
} }
if (err != -EOPNOTSUPP)
NInoSetTruncateFailed(ni);
else if (old_size >= 0)
i_size_write(vi, old_size);
err_out:
if (ctx) if (ctx)
ntfs_attr_put_search_ctx(ctx); ntfs_attr_put_search_ctx(ctx);
if (m) if (m)
unmap_mft_record(ni); unmap_mft_record(base_ni);
NInoSetTruncateFailed(ni); up_write(&ni->runlist.lock);
out:
ntfs_debug("Failed. Returning error code %i.", err);
return err; return err;
conv_err_out:
if (err != -ENOMEM && err != -EOPNOTSUPP) {
make_bad_inode(vi);
make_bad_inode(VFS_I(base_ni));
NVolSetErrors(vol);
}
if (err != -EOPNOTSUPP)
NInoSetTruncateFailed(ni);
else
i_size_write(vi, old_size);
goto out;
} }
/** /**
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册