mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
ntfs: validate and reserve bad clusters at mount
The cluster allocator only consults $Bitmap and never loads $BadClus:$Bad. A free bitmap bit for a known bad cluster can therefore place that cluster in the free-space cache and make it available for new file allocations. Load the named nonresident $Bad stream and map every attribute extent before validating full-volume coverage and identity mappings for bad runs. Reserve only free bits in $Bitmap on writable mounts and flush the corrections before the initial free-space scan. Keep bad-cluster counts diagnostic so reservations are accounted for exactly once. Force read-only mounts with repair advice when $Bad cannot be loaded or validated, and prevent read-write remounts after such errors. Leave the bitmap unchanged on read-only mounts and fail mounts on memory allocation failure. Suppress errors=panic while loading $BadClus so damaged metadata can fall back to read-only. Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
This commit is contained in:
parent
8deea34496
commit
8506a8031e
|
|
@ -12,6 +12,59 @@
|
|||
#include "bitmap.h"
|
||||
#include "ntfs.h"
|
||||
|
||||
/*
|
||||
* Check a $BadClus run against $Bitmap and optionally reserve its free bits.
|
||||
* This is only used during mount, before the free-space scan starts. The
|
||||
* scan will account for the repaired bits, so do not use the regular bitmap
|
||||
* setters, which wait for that scan and assume every bit changes state.
|
||||
* @free_bits counts only bits that were free, including on read-only mounts.
|
||||
*/
|
||||
int ntfs_bitmap_check_used(struct ntfs_volume *vol, s64 start, s64 count,
|
||||
bool repair, s64 *free_bits)
|
||||
{
|
||||
struct address_space *mapping = vol->lcnbmp_ino->i_mapping;
|
||||
const unsigned int bits_per_page = PAGE_SIZE * BITS_PER_BYTE;
|
||||
struct folio *folio;
|
||||
u8 *bitmap;
|
||||
unsigned int bit, end, bits;
|
||||
s64 nr_free;
|
||||
|
||||
if (start < 0 || count <= 0 || start >= vol->nr_clusters ||
|
||||
count > vol->nr_clusters - start || NVolFreeClusterKnown(vol))
|
||||
return -EINVAL;
|
||||
if (repair && sb_rdonly(vol->sb))
|
||||
return -EROFS;
|
||||
|
||||
while (count) {
|
||||
folio = read_mapping_folio(mapping,
|
||||
start >> (PAGE_SHIFT + 3), NULL);
|
||||
if (IS_ERR(folio))
|
||||
return PTR_ERR(folio);
|
||||
|
||||
folio_lock(folio);
|
||||
bitmap = kmap_local_folio(folio, 0);
|
||||
bit = start & (bits_per_page - 1);
|
||||
bits = min_t(s64, count, bits_per_page - bit);
|
||||
end = bit + bits;
|
||||
nr_free = 0;
|
||||
for (bit = find_next_zero_bit_le(bitmap, end, bit); bit < end;
|
||||
bit = find_next_zero_bit_le(bitmap, end, bit + 1)) {
|
||||
nr_free++;
|
||||
if (repair)
|
||||
__set_bit_le(bit, bitmap);
|
||||
}
|
||||
kunmap_local(bitmap);
|
||||
if (repair && nr_free)
|
||||
folio_mark_dirty(folio);
|
||||
folio_unlock(folio);
|
||||
folio_put(folio);
|
||||
*free_bits += nr_free;
|
||||
start += bits;
|
||||
count -= bits;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ntfs_trim_fs(struct ntfs_volume *vol, struct fstrim_range *range)
|
||||
{
|
||||
size_t buf_clusters;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
#include "volume.h"
|
||||
|
||||
int ntfs_trim_fs(struct ntfs_volume *vol, struct fstrim_range *range);
|
||||
int ntfs_bitmap_check_used(struct ntfs_volume *vol, s64 start, s64 count,
|
||||
bool repair, s64 *free_bits);
|
||||
int __ntfs_bitmap_set_bits_in_run(struct inode *vi, const s64 start_bit,
|
||||
const s64 count, const u8 value, const bool is_rollback);
|
||||
|
||||
|
|
|
|||
163
fs/ntfs/super.c
163
fs/ntfs/super.c
|
|
@ -21,6 +21,7 @@
|
|||
#include "mft.h"
|
||||
#include "ntfs.h"
|
||||
#include "ea.h"
|
||||
#include "bitmap.h"
|
||||
#include "volume.h"
|
||||
|
||||
/* A global default upcase table and a corresponding reference count. */
|
||||
|
|
@ -1548,6 +1549,125 @@ static bool load_and_init_upcase(struct ntfs_volume *vol)
|
|||
return false;
|
||||
}
|
||||
|
||||
static int ntfs_validate_badclus(struct ntfs_volume *vol,
|
||||
const struct runlist *runlist, s64 *bad_clusters)
|
||||
{
|
||||
const struct runlist_element *rl = runlist->rl;
|
||||
s64 next_vcn = 0, bad = 0;
|
||||
size_t i;
|
||||
|
||||
if (!rl)
|
||||
return -EIO;
|
||||
|
||||
for (i = 0; i < runlist->count; i++) {
|
||||
if (!rl[i].length) {
|
||||
if (i != runlist->count - 1 ||
|
||||
rl[i].vcn != vol->nr_clusters ||
|
||||
rl[i].lcn != LCN_ENOENT ||
|
||||
next_vcn != vol->nr_clusters)
|
||||
return -EIO;
|
||||
*bad_clusters = bad;
|
||||
return 0;
|
||||
}
|
||||
if (rl[i].vcn != next_vcn || rl[i].length < 0 ||
|
||||
rl[i].length > vol->nr_clusters - next_vcn)
|
||||
return -EIO;
|
||||
if (rl[i].lcn != LCN_HOLE) {
|
||||
/* Each bad cluster is mapped to its own volume offset. */
|
||||
if (rl[i].lcn != rl[i].vcn)
|
||||
return -EIO;
|
||||
bad += rl[i].length;
|
||||
}
|
||||
next_vcn += rl[i].length;
|
||||
}
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the allocation of $BadClus:$Bad, not its data. initialized_size may
|
||||
* be zero even when the runlist contains bad clusters. Validate every run
|
||||
* before repairing $Bitmap, and let the later free-space scan account for
|
||||
* the reservations without subtracting the bad-cluster count again.
|
||||
*/
|
||||
static int load_and_check_badclus(struct ntfs_volume *vol)
|
||||
{
|
||||
static __le16 bad_name[] = {
|
||||
cpu_to_le16('$'), cpu_to_le16('B'),
|
||||
cpu_to_le16('a'), cpu_to_le16('d'),
|
||||
};
|
||||
struct super_block *sb = vol->sb;
|
||||
struct inode *base_ino, *bad_ino;
|
||||
struct ntfs_inode *ni;
|
||||
s64 free_bits = 0;
|
||||
u64 size = ntfs_cluster_to_bytes(vol, vol->nr_clusters);
|
||||
size_t i;
|
||||
int err;
|
||||
|
||||
base_ino = ntfs_iget(sb, FILE_BadClus);
|
||||
if (IS_ERR(base_ino))
|
||||
return PTR_ERR(base_ino);
|
||||
if (!S_ISREG(base_ino->i_mode)) {
|
||||
err = -EIO;
|
||||
goto iput_base;
|
||||
}
|
||||
bad_ino = ntfs_attr_iget(base_ino, AT_DATA, bad_name,
|
||||
ARRAY_SIZE(bad_name));
|
||||
if (IS_ERR(bad_ino)) {
|
||||
err = PTR_ERR(bad_ino);
|
||||
goto iput_base;
|
||||
}
|
||||
ni = NTFS_I(bad_ino);
|
||||
if (!NInoNonResident(ni) || NInoCompressed(ni) || NInoEncrypted(ni) ||
|
||||
ni->data_size != size || ni->allocated_size != size) {
|
||||
err = -EIO;
|
||||
goto iput_bad;
|
||||
}
|
||||
|
||||
down_write(&ni->runlist.lock);
|
||||
err = ntfs_attr_map_whole_runlist(ni);
|
||||
if (err)
|
||||
goto unlock;
|
||||
err = ntfs_validate_badclus(vol, &ni->runlist, &vol->bad_clusters);
|
||||
if (err)
|
||||
goto unlock;
|
||||
for (i = 0; i < ni->runlist.count - 1; i++) {
|
||||
struct runlist_element *rl = &ni->runlist.rl[i];
|
||||
|
||||
if (rl->lcn == LCN_HOLE)
|
||||
continue;
|
||||
err = ntfs_bitmap_check_used(vol, rl->lcn, rl->length,
|
||||
!sb_rdonly(sb), &free_bits);
|
||||
if (err)
|
||||
break;
|
||||
}
|
||||
unlock:
|
||||
up_write(&ni->runlist.lock);
|
||||
iput_bad:
|
||||
iput(bad_ino);
|
||||
iput_base:
|
||||
iput(base_ino);
|
||||
if (err)
|
||||
return err;
|
||||
if (free_bits) {
|
||||
/* A read-only mount must not later remount RW without repair. */
|
||||
if (sb_rdonly(sb))
|
||||
return -EIO;
|
||||
err = filemap_write_and_wait(vol->lcnbmp_ino->i_mapping);
|
||||
if (err)
|
||||
return err;
|
||||
err = blkdev_issue_flush(sb->s_bdev);
|
||||
if (err)
|
||||
return err;
|
||||
ntfs_warning(sb,
|
||||
"Marked %lld bad clusters as used in $Bitmap.",
|
||||
free_bits);
|
||||
}
|
||||
if (vol->bad_clusters)
|
||||
pr_info("volume contains %lld bad clusters, dev %s\n",
|
||||
vol->bad_clusters, sb->s_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The lcn and mft bitmap inodes are NTFS-internal inodes with
|
||||
* their own special locking rules:
|
||||
|
|
@ -1563,16 +1683,16 @@ static struct lock_class_key
|
|||
* Open the system files with normal access functions and complete setting up
|
||||
* the ntfs super block @vol.
|
||||
*
|
||||
* Return 'true' on success or 'false' on error.
|
||||
* Return 0 on success and -errno on error.
|
||||
*/
|
||||
static bool load_system_files(struct ntfs_volume *vol)
|
||||
static int load_system_files(struct ntfs_volume *vol)
|
||||
{
|
||||
struct super_block *sb = vol->sb;
|
||||
struct mft_record *m;
|
||||
struct volume_information *vi;
|
||||
struct ntfs_attr_search_ctx *ctx;
|
||||
struct restart_page_header *rp;
|
||||
int err;
|
||||
int err, ret = -EINVAL;
|
||||
u8 saved_on_errors;
|
||||
|
||||
ntfs_debug("Entering.");
|
||||
|
|
@ -1785,6 +1905,25 @@ static bool load_system_files(struct ntfs_volume *vol)
|
|||
"Errors were recorded during mount. Mounting read-only. Run chkdsk.");
|
||||
}
|
||||
|
||||
/* Check bad clusters after hibernation checks and before any writes. */
|
||||
saved_on_errors = vol->on_errors;
|
||||
if (saved_on_errors == ON_ERRORS_PANIC)
|
||||
vol->on_errors = ON_ERRORS_REMOUNT_RO;
|
||||
err = load_and_check_badclus(vol);
|
||||
vol->on_errors = saved_on_errors;
|
||||
if (err == -ENOMEM) {
|
||||
ret = err;
|
||||
ntfs_warning(sb, "Not enough memory to load $BadClus.");
|
||||
goto iput_root_err_out;
|
||||
}
|
||||
if (err) {
|
||||
sb->s_flags |= SB_RDONLY;
|
||||
NVolSetErrors(vol);
|
||||
ntfs_error(sb,
|
||||
"Failed to initialize $BadClus (%d). Mounting read-only. Run fsck.ntfs or chkdsk.",
|
||||
err);
|
||||
}
|
||||
|
||||
/* If (still) a read-write mount, empty the logfile. */
|
||||
if (!sb_rdonly(sb) &&
|
||||
vol->logfile_ino && !ntfs_empty_logfile(vol->logfile_ino) &&
|
||||
|
|
@ -1799,7 +1938,7 @@ static bool load_system_files(struct ntfs_volume *vol)
|
|||
}
|
||||
/* If on NTFS versions before 3.0, we are done. */
|
||||
if (unlikely(vol->major_ver < 3))
|
||||
return true;
|
||||
return 0;
|
||||
/* NTFS 3.0+ specific initialization. */
|
||||
/* Get the security descriptors inode. */
|
||||
vol->secure_ino = ntfs_iget(sb, FILE_Secure);
|
||||
|
|
@ -1818,7 +1957,7 @@ static bool load_system_files(struct ntfs_volume *vol)
|
|||
ntfs_error(sb, "Failed to load $Extend.");
|
||||
goto iput_sec_err_out;
|
||||
}
|
||||
return true;
|
||||
return 0;
|
||||
|
||||
iput_sec_err_out:
|
||||
iput(vol->secure_ino);
|
||||
|
|
@ -1854,7 +1993,7 @@ static bool load_system_files(struct ntfs_volume *vol)
|
|||
iput(vol->mftbmp_ino);
|
||||
iput_mirr_err_out:
|
||||
iput(vol->mftmirr_ino);
|
||||
return false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ntfs_volume_free(struct ntfs_volume *vol)
|
||||
|
|
@ -2460,7 +2599,7 @@ static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc)
|
|||
{
|
||||
char *boot;
|
||||
struct inode *tmp_ino;
|
||||
int blocksize, result;
|
||||
int blocksize, result, err = -EINVAL;
|
||||
pgoff_t lcn_bit_pages;
|
||||
struct ntfs_volume *vol = NTFS_SB(sb);
|
||||
int silent = fc->sb_flags & SB_SILENT;
|
||||
|
|
@ -2632,8 +2771,10 @@ static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc)
|
|||
* Open the system files with normal access functions and complete
|
||||
* setting up the ntfs super block.
|
||||
*/
|
||||
if (!load_system_files(vol)) {
|
||||
ntfs_error(sb, "Failed to load system files.");
|
||||
result = load_system_files(vol);
|
||||
if (result) {
|
||||
err = result;
|
||||
ntfs_warning(sb, "Failed to load system files (%d).", err);
|
||||
goto unl_upcase_iput_tmp_ino_err_out_now;
|
||||
}
|
||||
|
||||
|
|
@ -2742,9 +2883,9 @@ static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc)
|
|||
kfree(vol->volume_label);
|
||||
unload_nls(vol->nls_map);
|
||||
kfree(vol);
|
||||
ntfs_debug("Failed, returning -EINVAL.");
|
||||
ntfs_debug("Failed, returning %d.", err);
|
||||
lockdep_on();
|
||||
return -EINVAL;
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@
|
|||
* @nls_utf8: NLS table for UTF-8.
|
||||
* @free_waitq: Wait queue for threads waiting for free clusters or MFT records.
|
||||
* @free_clusters: Track the number of free clusters.
|
||||
* @bad_clusters: Number of clusters recorded in $BadClus:$Bad.
|
||||
* @free_mft_records: Track the free mft records.
|
||||
* @dirty_clusters: Number of clusters that are dirty.
|
||||
* @sparse_compression_unit: Size of compression/sparse unit in clusters.
|
||||
|
|
@ -123,6 +124,7 @@ struct ntfs_volume {
|
|||
u32 index_record_size_mask;
|
||||
u8 index_record_size_bits;
|
||||
s64 nr_clusters;
|
||||
s64 bad_clusters;
|
||||
s64 mft_lcn;
|
||||
s64 mftmirr_lcn;
|
||||
u64 serial_no;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user