Hi Shengyong, Hi Jinbao,

Thank you for providing the patch. I have tested the patch and found a critical 
regression that can lead to data loss or unexpected failure during a no-op 
resize.

The issue arises because the fixed HDIO_GETGEO calculation, when applied to a 
legacy image created with start_sector=4097s, causes the segment_count to 
unexpectedly shrink due to the new alignment calculation.

1. Regression Reproduction Steps (The Volume Shrink Bug)
The issue happens when resizing an image created with a legacy mkfs.f2fs tool 
(which assumed start_sector=0) located at a physical offset that causes a new 
alignment shift (e.g., sector 4097).

A. Reproduction of Segment Count Reduction

This demonstrates the subtle change in geometry resulting in a reduction of 
total segments from 32767 to 32766 due to below code:
while (new_seg0_blkaddr < old_seg0_blkaddr)
        new_seg0_blkaddr += blks_per_seg;

# Initialize GPT and create a 64GB partition starting at sector 4097
parted -s /dev/sdb mklabel gpt
parted -s /dev/sdb mkpart primary 4097s 134221824s

# Create F2FS using the buggy, pre-fix mkfs (assuming start_sector=0)
mkfs.f2fs -f /dev/sdb1

# Run the patched resize.f2fs (which correctly identifies start_sector=4097)
# This will show segment count reducing from 32767 to 32766 in the new SB.
resize.f2fs -F -f -d1 /dev/sdb1

B. Reproduction of Resize Check Failure (Data Loss Risk)

This demonstrates that when the FS is full, the segment reduction causes 
valid_block_count > user_block_count, making resize.f2fs fail prematurely.

# Initialize partition
parted -s /dev/sdb mklabel gpt
parted -s /dev/sdb mkpart primary 4097s 134221824s

# Create F2FS using the buggy, pre-fix mkfs (assuming start_sector=0)
mkfs.f2fs -f /dev/sdb1
mount /dev/sdb1 /mnt/f2fs-test

# Fill the filesystem to capacity
# (Use the provided dd commands to fill the volume)
dd if=/dev/urandom of=/mnt/f2fs-test/fill_34359738368.dat bs=4096 count=8388608 
oflag=direct
dd if=/dev/urandom of=/mnt/f2fs-test/fill_17179869184.dat bs=4096 count=4194304 
oflag=direct
dd if=/dev/urandom of=/mnt/f2fs-test/fill_8589934592.dat bs=4096 count=2097152 
oflag=direct
dd if=/dev/urandom of=/mnt/f2fs-test/fill_4294967296.dat bs=4096 count=1048576 
oflag=direct
dd if=/dev/urandom of=/mnt/f2fs-test/fill_2147483648.dat bs=4096 count=524288 
oflag=direct
dd if=/dev/urandom of=/mnt/f2fs-test/fill_1073741824.dat bs=4096 count=262144 
oflag=direct
dd if=/dev/urandom of=/mnt/f2fs-test/fill_134217728.dat bs=4096 count=32768 
oflag=direct
dd if=/dev/urandom of=/mnt/f2fs-test/fill_67108864.dat bs=4096 count=16384 
oflag=direct
dd if=/dev/urandom of=/mnt/f2fs-test/fill_16777216.dat bs=4096 count=4096 
oflag=direct
dd if=/dev/urandom of=/mnt/f2fs-test/fill_4194304.dat bs=4096 count=1024 
oflag=direct
dd if=/dev/urandom of=/mnt/f2fs-test/fill_2097152.dat bs=4096 count=512 
oflag=direct

# Run the patched resize.f2fs
# This will fail the check f2fs_resize_check() and immediately abort.
resize.f2fs -F -f -d1 /dev/sdb1

2. Analysis and Proposed Solution
This issue confirms that the corrected geometry calculation can lead to 
undesirable outcomes for legacy images. When the new correct alignment requires 
a larger offset (zone_align_start_offset), it causes a reduction in the space 
allocated for the main_area.

I observe that this volume shrink and subsequent failure primarily occur during 
a no-op resize (resize.f2fs attempting to resize to the same size) when the 
filesystem is near full capacity. When normally growing the partition size, 
this issue can typically be avoided.

Could you please confirm if this failure during a no-op resize scenario is 
considered acceptable behavior for resize.f2fs?

If this behavior is considered an issue that needs to be addressed, I have two 
proposals regarding the geometric calculation for legacy images:

A. Proactive Geometry Override (Preferred Approach):
We should attempt to deduce the original start_sector from the existing 
Superblock (specifically by referencing old_sb->segment0_blkaddr). If the 
deduced legacy start_sector is inconsistent with the sector reported by 
HDIO_GETGEO (e.g., if the image was created using the old buggy logic), we 
should use the deduced/legacy start_sector to override the HDIO_GETGEO result 
for the duration of the resize calculation. This ensures the geometric 
calculation matches the existing on-disk layout, preventing the unexpected 
shrink and failure.

B. Maintain Legacy Bug (If deduction is too complex):
If implementing the reliable deduction logic (Proposal A) proves to be overly 
complex or risky, would it be better to maintain the original, incorrect 
HDIO_GETGEO behavior for resize.f2fs to prioritize compatibility with existing 
images over geometric correctness?

3. Resource Constraints & Test Coverage
The root cause of this issue is that after the bug in HDIO_GETGEO was fixed, 
the obtained value for start_sector differs from its previous value (which was 
implicitly 0). Consequently, the zone_align_start_offset calculation, which 
depends on start_sector, changed. Many crucial fields within the superblock, in 
turn, depend on zone_align_start_offset.

Further analysis reveals that the zone_align_start_offset exhibits a periodic 
change relative to start_sector. For instance, the zone_align_start_offset 
generated when start_sector is in the range of 0 to 4095 is identical to the 
one generated when start_sector is in the range of 4096 to 8191.

Based on this analysis, I have developed a test script that iterates through 
start_sector values from 4096 to 8191 to observe the behavior produced by 
resize.f2fs. The script is as follows:

#!/bin/bash

# Test script for resize.f2fs validation

# set -e

SOURCE_DIR="./data"
MOUNT_POINT="/mnt/f2fs-tmp"
SSD_DEVICE="/dev/sdb"
OLD_MKFS="mkfs.f2fs"
NEW_RESIZE="/path/to/fixed/resize.f2fs"
# Size of each test partition in GB
PARTITION_SIZE_GB=64
# Log file for all output
LOG_FILE="./test_resize.log"
# Statistics file for superblock inconsistencies
STATS_FILE="./sb_inconsistencies.txt"

# Output functions (output is automatically logged via exec redirect)
error() {
    echo "ERROR: $1" >&2
    exit 1
}

info() {
    echo "INFO: $1"
}

warn() {
    echo "WARN: $1"
}

# Parse superblock info from print_raw_sb_info output
parse_sb_info() {
    local sb_file="$1"
   
    # Extract values using awk
    # Note: Format is "field_name                           [0x    hex : 
decimal]"
    # The hex value may have leading spaces, so we match more flexibly
    awk '
    
/^segment_count[[:space:]]+\[0x[[:space:]]*[[:xdigit:]]+[[:space:]]*:[[:space:]]*[[:digit:]]+\]/
 {
        match($0, 
/\[0x[[:space:]]*([[:xdigit:]]+)[[:space:]]*:[[:space:]]*([[:digit:]]+)\]/, arr)
        print "segment_count=" arr[2]
    }
    
/^segment_count_ckpt[[:space:]]+\[0x[[:space:]]*[[:xdigit:]]+[[:space:]]*:[[:space:]]*[[:digit:]]+\]/
 {
        match($0, 
/\[0x[[:space:]]*([[:xdigit:]]+)[[:space:]]*:[[:space:]]*([[:digit:]]+)\]/, arr)
        print "segment_count_ckpt=" arr[2]
    }
    
/^segment_count_sit[[:space:]]+\[0x[[:space:]]*[[:xdigit:]]+[[:space:]]*:[[:space:]]*[[:digit:]]+\]/
 {
        match($0, 
/\[0x[[:space:]]*([[:xdigit:]]+)[[:space:]]*:[[:space:]]*([[:digit:]]+)\]/, arr)
        print "segment_count_sit=" arr[2]
    }
    
/^segment_count_nat[[:space:]]+\[0x[[:space:]]*[[:xdigit:]]+[[:space:]]*:[[:space:]]*[[:digit:]]+\]/
 {
        match($0, 
/\[0x[[:space:]]*([[:xdigit:]]+)[[:space:]]*:[[:space:]]*([[:digit:]]+)\]/, arr)
        print "segment_count_nat=" arr[2]
    }
    
/^segment_count_ssa[[:space:]]+\[0x[[:space:]]*[[:xdigit:]]+[[:space:]]*:[[:space:]]*[[:digit:]]+\]/
 {
        match($0, 
/\[0x[[:space:]]*([[:xdigit:]]+)[[:space:]]*:[[:space:]]*([[:digit:]]+)\]/, arr)
        print "segment_count_ssa=" arr[2]
    }
    
/^segment_count_main[[:space:]]+\[0x[[:space:]]*[[:xdigit:]]+[[:space:]]*:[[:space:]]*[[:digit:]]+\]/
 {
        match($0, 
/\[0x[[:space:]]*([[:xdigit:]]+)[[:space:]]*:[[:space:]]*([[:digit:]]+)\]/, arr)
        print "segment_count_main=" arr[2]
    }
    
/^section_count[[:space:]]+\[0x[[:space:]]*[[:xdigit:]]+[[:space:]]*:[[:space:]]*[[:digit:]]+\]/
 {
        match($0, 
/\[0x[[:space:]]*([[:xdigit:]]+)[[:space:]]*:[[:space:]]*([[:digit:]]+)\]/, arr)
        print "section_count=" arr[2]
    }
    
/^cp_payload[[:space:]]+\[0x[[:space:]]*[[:xdigit:]]+[[:space:]]*:[[:space:]]*[[:digit:]]+\]/
 {
        match($0, 
/\[0x[[:space:]]*([[:xdigit:]]+)[[:space:]]*:[[:space:]]*([[:digit:]]+)\]/, arr)
        print "cp_payload=" arr[2]
    }
    
/^segment0_blkaddr[[:space:]]+\[0x[[:space:]]*[[:xdigit:]]+[[:space:]]*:[[:space:]]*[[:digit:]]+\]/
 {
        match($0, 
/\[0x[[:space:]]*([[:xdigit:]]+)[[:space:]]*:[[:space:]]*([[:digit:]]+)\]/, arr)
        print "segment0_blkaddr=" arr[2]
    }
    
/^cp_blkaddr[[:space:]]+\[0x[[:space:]]*[[:xdigit:]]+[[:space:]]*:[[:space:]]*[[:digit:]]+\]/
 {
        match($0, 
/\[0x[[:space:]]*([[:xdigit:]]+)[[:space:]]*:[[:space:]]*([[:digit:]]+)\]/, arr)
        print "cp_blkaddr=" arr[2]
    }
    
/^sit_blkaddr[[:space:]]+\[0x[[:space:]]*[[:xdigit:]]+[[:space:]]*:[[:space:]]*[[:digit:]]+\]/
 {
        match($0, 
/\[0x[[:space:]]*([[:xdigit:]]+)[[:space:]]*:[[:space:]]*([[:digit:]]+)\]/, arr)
        print "sit_blkaddr=" arr[2]
    }
    
/^nat_blkaddr[[:space:]]+\[0x[[:space:]]*[[:xdigit:]]+[[:space:]]*:[[:space:]]*[[:digit:]]+\]/
 {
        match($0, 
/\[0x[[:space:]]*([[:xdigit:]]+)[[:space:]]*:[[:space:]]*([[:digit:]]+)\]/, arr)
        print "nat_blkaddr=" arr[2]
    }
    
/^ssa_blkaddr[[:space:]]+\[0x[[:space:]]*[[:xdigit:]]+[[:space:]]*:[[:space:]]*[[:digit:]]+\]/
 {
        match($0, 
/\[0x[[:space:]]*([[:xdigit:]]+)[[:space:]]*:[[:space:]]*([[:digit:]]+)\]/, arr)
        print "ssa_blkaddr=" arr[2]
    }
    
/^main_blkaddr[[:space:]]+\[0x[[:space:]]*[[:xdigit:]]+[[:space:]]*:[[:space:]]*[[:digit:]]+\]/
 {
        match($0, 
/\[0x[[:space:]]*([[:xdigit:]]+)[[:space:]]*:[[:space:]]*([[:digit:]]+)\]/, arr)
        print "main_blkaddr=" arr[2]
    }
    
/^log_blocks_per_seg[[:space:]]+\[0x[[:space:]]*[[:xdigit:]]+[[:space:]]*:[[:space:]]*[[:digit:]]+\]/
 {
        match($0, 
/\[0x[[:space:]]*([[:xdigit:]]+)[[:space:]]*:[[:space:]]*([[:digit:]]+)\]/, arr)
        print "log_blocks_per_seg=" arr[2]
    }
    ' "$sb_file"
}

# Fill partition with files using largest possible 2^n sizes
fill_partition() {
    local mount_point="$1"
    info "Filling partition at $mount_point"
   
    # Get available space in bytes
    local available_bytes
    available_bytes=$(df -B1 "$mount_point" | tail -1 | awk '{print $4}')
   
    if [ -z "$available_bytes" ] || [ "$available_bytes" -le 0 ]; then
        error "Failed to get available space or no space available"
    fi
   
    info "Available space: $((available_bytes / 1024 / 1024 / 1024))GB 
($available_bytes bytes)"
   
    # Find the largest 2^n that is <= available space
    # Start from 1 byte and multiply by 2 until we exceed available space, then 
go back one step
    local start_size_bytes=1  # Start from 1 byte
   
    # Find the largest 2^n that fits (1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 
1K, 2K, 4K, 8K, ...)
    while [ $((start_size_bytes * 2)) -le $available_bytes ]; do
        start_size_bytes=$((start_size_bytes * 2))
    done
   
    # Display the starting size in appropriate units
    if [ $start_size_bytes -ge $((1024 * 1024 * 1024)) ]; then
        local start_size_gb=$((start_size_bytes / 1024 / 1024 / 1024))
        info "Starting with ${start_size_gb}GB (largest 2^n that fits)"
    elif [ $start_size_bytes -ge $((1024 * 1024)) ]; then
        local start_size_mb=$((start_size_bytes / 1024 / 1024))
        info "Starting with ${start_size_mb}MB (largest 2^n that fits)"
    elif [ $start_size_bytes -ge 1024 ]; then
        local start_size_kb=$((start_size_bytes / 1024))
        info "Starting with ${start_size_kb}KB (largest 2^n that fits)"
    else
        info "Starting with ${start_size_bytes} bytes (largest 2^n that fits)"
    fi
   
    # Now fill with files: start from the calculated start_size_bytes and go 
down
    # For each size, try once. If space is not enough or creation fails, skip 
to next smaller size
    local file_index=0
    local size_bytes=$start_size_bytes  # Start from dynamically calculated size
    local min_size=$((1024 * 512))
   
    while [ $size_bytes -gt $min_size ]; do
        # Check if we have enough space
        local current_available
        current_available=$(df -B1 "$mount_point" | tail -1 | awk '{print $4}')
       
        if [ "$current_available" -lt "$size_bytes" ]; then
            # Not enough space, skip this size and try next smaller size
            info "Skipping ${size_bytes} bytes (not enough space: 
$current_available available)"
            size_bytes=$((size_bytes / 2))
            continue
        fi
       
        # Try to create file using dd with 4KB block size
        local file_path="$mount_point/fill_${file_index}_${size_bytes}.dat"
        local size_gb=$((size_bytes / 1024 / 1024 / 1024))
        local size_mb=$((size_bytes / 1024 / 1024))
        local block_count=$((size_bytes / 4096))  # 4KB blocks
       
        # Use dd with 4KB block size and direct I/O to bypass page cache
        # oflag=direct: use direct I/O for output, bypassing page cache
        if sudo dd if=/dev/urandom of="$file_path" bs=4096 count=$block_count 
oflag=direct 2>/dev/null; then
            if [ $size_gb -gt 0 ]; then
                info "Created file: fill_${file_index}_${size_bytes}.dat 
(${size_gb}GB)"
            else
                info "Created file: fill_${file_index}_${size_bytes}.dat 
(${size_mb}MB)"
            fi
            # Sync to flush dirty pages and free page cache to reduce memory 
pressure
            sync
            info "Synced file: fill_${file_index}_${size_bytes}.dat"
            file_index=$((file_index + 1))
            # Continue with next smaller size
            size_bytes=$((size_bytes / 2))
        else
            # Failed to create (likely out of space), skip this size and try 
next smaller
            sudo rm -f "$file_path" 2>/dev/null
            info "Failed to create ${size_bytes} bytes file, trying next 
smaller size"
            size_bytes=$((size_bytes / 2))
        fi
    done
   
    info "Partition filled. Created $file_index files."
}

# Verify data integrity
verify_data() {
    info "Verifying data integrity in $MOUNT_POINT"
   
    # Check if both directories exist
    if [ ! -d "$SOURCE_DIR" ]; then
        error "Source directory $SOURCE_DIR does not exist"
    fi
   
    if [ ! -d "$MOUNT_POINT" ]; then
        error "Mount point $MOUNT_POINT is not accessible"
    fi
   
    # Use diff -rq to quickly compare entire directory trees
    # This is much faster than comparing files one by one
    # -r: recursive, -q: quiet (only report if files differ)
    local diff_output
    if ! diff_output=$(diff -rq "$SOURCE_DIR" "$MOUNT_POINT" 2>&1); then
        error "Data integrity check failed. Differences found:\n$diff_output"
    fi
   
    info "Data integrity verified: all files match between $SOURCE_DIR and 
$MOUNT_POINT"
}

# Calculate expected values for variable fields
calculate_expected_values() {
    local old_sb="$1"
    local start_sector="$2"
   
    # Load old SB values
    eval $(parse_sb_info "$old_sb")
   
    # Check if parsing succeeded - all required fields must be present
    if [ -z "$segment0_blkaddr" ] || [ -z "$log_blocks_per_seg" ] || [ -z 
"$segment_count_ckpt" ] || \
       [ -z "$segment_count_sit" ] || [ -z "$segment_count_nat" ] || [ -z 
"$segment_count_ssa" ]; then
        error "Failed to parse superblock from $old_sb. File contents:\n$(head 
-20 "$old_sb")"
    fi
   
    local old_segment0_blkaddr=$segment0_blkaddr
    local blks_per_seg=$((2**log_blocks_per_seg))
   
    # Print parsed values that are actually used (to stderr to avoid being 
captured by eval)
    echo "INFO: calculate_expected_values: 
old_segment0_blkaddr=$old_segment0_blkaddr, 
segment_count_ckpt=$segment_count_ckpt, segment_count_sit=$segment_count_sit, 
segment_count_nat=$segment_count_nat, segment_count_ssa=$segment_count_ssa, 
log_blocks_per_seg=$log_blocks_per_seg, blks_per_seg=$blks_per_seg" >&2
   
    # Calculate alignment_bytes and zone_align_start_offset
    local alignment_bytes=$((4096 * 512))
    local zone_align_start_offset=$(( (start_sector * 512 + 2 * 4096 + 
alignment_bytes - 1) / alignment_bytes * alignment_bytes - start_sector * 512 ))
   
    # Calculate new segment0_blkaddr
    # segment0_blkaddr = zone_align_start_offset / 4096, then round up to >= 
old_segment0_blkaddr
    local new_segment0_blkaddr=$((zone_align_start_offset / 4096))
    while [ "$new_segment0_blkaddr" -lt "$old_segment0_blkaddr" ]; do
        new_segment0_blkaddr=$((new_segment0_blkaddr + 512))
    done
   
    # Calculate other addresses based on new_segment0_blkaddr
    local new_cp_blkaddr=$new_segment0_blkaddr
    local new_sit_blkaddr=$((new_cp_blkaddr + segment_count_ckpt * 
blks_per_seg))
    local new_nat_blkaddr=$((new_sit_blkaddr + segment_count_sit * 
blks_per_seg))
    local new_ssa_blkaddr=$((new_nat_blkaddr + segment_count_nat * 
blks_per_seg))
    local new_main_blkaddr=$((new_ssa_blkaddr + segment_count_ssa * 
blks_per_seg))
   
    echo "expected_segment0_blkaddr=$new_segment0_blkaddr"
    echo "expected_cp_blkaddr=$new_cp_blkaddr"
    echo "expected_sit_blkaddr=$new_sit_blkaddr"
    echo "expected_nat_blkaddr=$new_nat_blkaddr"
    echo "expected_ssa_blkaddr=$new_ssa_blkaddr"
    echo "expected_main_blkaddr=$new_main_blkaddr"
}

# Record inconsistency to stats file
record_inconsistency() {
    local start_sector="$1"
    local field="$2"
    local old_value="$3"
    local new_value="$4"
    echo "[start_sector=$start_sector] $field: old=$old_value, new=$new_value" 
>> "$STATS_FILE"
    warn "Inconsistency detected: $field changed from $old_value to $new_value 
(start_sector=$start_sector)"
}

# Verify superblock consistency
verify_sb_consistency() {
    local old_sb_file="$1"
    local new_sb_file="$2"
    local start_sector="$3"
   
    info "Verifying superblock consistency"
   
    # Parse both superblocks
    eval $(parse_sb_info "$old_sb_file")
   
    # Check if parsing succeeded - all required fields must be present
    if [ -z "$segment_count" ] || [ -z "$segment_count_ckpt" ] || [ -z 
"$segment_count_sit" ] || \
       [ -z "$segment_count_nat" ] || [ -z "$segment_count_ssa" ] || [ -z 
"$segment_count_main" ] || \
       [ -z "$section_count" ] || [ -z "$cp_payload" ]; then
        error "Failed to parse old superblock from $old_sb_file. File 
contents:\n$(head -20 "$old_sb_file")"
    fi
   
    local old_segment_count=$segment_count
    local old_segment_count_sit=$segment_count_sit
    local old_segment_count_nat=$segment_count_nat
    local old_segment_count_ssa=$segment_count_ssa
    local old_segment_count_main=$segment_count_main
    local old_section_count=$section_count
    local old_cp_payload=$cp_payload
    local old_segment_count_ckpt=$segment_count_ckpt
   
    eval $(parse_sb_info "$new_sb_file")
   
    # Check if parsing succeeded - all required fields must be present
    if [ -z "$segment_count" ] || [ -z "$segment_count_ckpt" ] || [ -z 
"$segment_count_sit" ] || \
       [ -z "$segment_count_nat" ] || [ -z "$segment_count_ssa" ] || [ -z 
"$segment_count_main" ] || \
       [ -z "$section_count" ] || [ -z "$cp_payload" ]; then
        error "Failed to parse new superblock from $new_sb_file. File 
contents:\n$(head -20 "$new_sb_file")"
    fi
    local new_segment_count=$segment_count
    local new_segment_count_sit=$segment_count_sit
    local new_segment_count_nat=$segment_count_nat
    local new_segment_count_ssa=$segment_count_ssa
    local new_segment_count_main=$segment_count_main
    local new_section_count=$section_count
    local new_cp_payload=$cp_payload
    local new_segment_count_ckpt=$segment_count_ckpt
   
    # Print parsed values for debugging
    info "Parsed old SB: segment_count=$old_segment_count, 
segment_count_sit=$old_segment_count_sit, 
segment_count_nat=$old_segment_count_nat, 
segment_count_ssa=$old_segment_count_ssa, 
segment_count_main=$old_segment_count_main, section_count=$old_section_count, 
cp_payload=$old_cp_payload, segment_count_ckpt=$old_segment_count_ckpt"
    info "Parsed new SB: segment_count=$new_segment_count, 
segment_count_sit=$new_segment_count_sit, 
segment_count_nat=$new_segment_count_nat, 
segment_count_ssa=$new_segment_count_ssa, 
segment_count_main=$new_segment_count_main, section_count=$new_section_count, 
cp_payload=$new_cp_payload, segment_count_ckpt=$new_segment_count_ckpt"
   
    # Verify unchanged fields (record inconsistencies instead of exiting)
    if [ "$old_segment_count" != "$new_segment_count" ]; then
        record_inconsistency "$start_sector" "segment_count" 
"$old_segment_count" "$new_segment_count"
    fi
   
    if [ "$old_segment_count_sit" != "$new_segment_count_sit" ]; then
        record_inconsistency "$start_sector" "segment_count_sit" 
"$old_segment_count_sit" "$new_segment_count_sit"
    fi
   
    if [ "$old_segment_count_nat" != "$new_segment_count_nat" ]; then
        record_inconsistency "$start_sector" "segment_count_nat" 
"$old_segment_count_nat" "$new_segment_count_nat"
    fi
   
    if [ "$old_segment_count_ssa" != "$new_segment_count_ssa" ]; then
        record_inconsistency "$start_sector" "segment_count_ssa" 
"$old_segment_count_ssa" "$new_segment_count_ssa"
    fi
   
    if [ "$old_segment_count_main" != "$new_segment_count_main" ]; then
        record_inconsistency "$start_sector" "segment_count_main" 
"$old_segment_count_main" "$new_segment_count_main"
    fi
   
    if [ "$old_section_count" != "$new_section_count" ]; then
        record_inconsistency "$start_sector" "section_count" 
"$old_section_count" "$new_section_count"
    fi
   
    if [ "$old_cp_payload" != "$new_cp_payload" ]; then
        record_inconsistency "$start_sector" "cp_payload" "$old_cp_payload" 
"$new_cp_payload"
    fi
   
    if [ "$old_segment_count_ckpt" != "$new_segment_count_ckpt" ]; then
        record_inconsistency "$start_sector" "segment_count_ckpt" 
"$old_segment_count_ckpt" "$new_segment_count_ckpt"
    fi
   
    info "Unchanged fields verified"
}

# Verify variable fields
verify_variable_fields() {
    local old_sb_file="$1"
    local new_sb_file="$2"
    local start_sector="$3"
   
    info "Verifying variable fields"
   
    # Parse new SB values
    eval $(parse_sb_info "$new_sb_file")
   
    # Check if parsing succeeded - all required fields must be present
    if [ -z "$segment0_blkaddr" ] || [ -z "$cp_blkaddr" ] || [ -z 
"$sit_blkaddr" ] || \
       [ -z "$nat_blkaddr" ] || [ -z "$ssa_blkaddr" ] || [ -z "$main_blkaddr" 
]; then
        error "Failed to parse new superblock from $new_sb_file. File 
contents:\n$(head -20 "$new_sb_file")"
    fi
   
    local new_segment0_blkaddr=$segment0_blkaddr
    local new_cp_blkaddr=$cp_blkaddr
    local new_sit_blkaddr=$sit_blkaddr
    local new_nat_blkaddr=$nat_blkaddr
    local new_ssa_blkaddr=$ssa_blkaddr
    local new_main_blkaddr=$main_blkaddr
   
    # Calculate expected values (stdout for eval, stderr for info messages)
    eval $(calculate_expected_values "$old_sb_file" "$start_sector")
   
    # Print parsed and expected values for debugging
    info "Parsed new SB variable fields: 
segment0_blkaddr=$new_segment0_blkaddr, cp_blkaddr=$new_cp_blkaddr, 
sit_blkaddr=$new_sit_blkaddr, nat_blkaddr=$new_nat_blkaddr, 
ssa_blkaddr=$new_ssa_blkaddr, main_blkaddr=$new_main_blkaddr"
    info "Expected variable fields     : 
segment0_blkaddr=$expected_segment0_blkaddr, cp_blkaddr=$expected_cp_blkaddr, 
sit_blkaddr=$expected_sit_blkaddr, nat_blkaddr=$expected_nat_blkaddr, 
ssa_blkaddr=$expected_ssa_blkaddr, main_blkaddr=$expected_main_blkaddr"
   
    # Record inconsistencies instead of exiting
    if [ "$new_segment0_blkaddr" != "$expected_segment0_blkaddr" ]; then
        record_inconsistency "$start_sector" "segment0_blkaddr" 
"$expected_segment0_blkaddr" "$new_segment0_blkaddr"
    fi
   
    if [ "$new_cp_blkaddr" != "$expected_cp_blkaddr" ]; then
        record_inconsistency "$start_sector" "cp_blkaddr" 
"$expected_cp_blkaddr" "$new_cp_blkaddr"
    fi
   
    if [ "$new_sit_blkaddr" != "$expected_sit_blkaddr" ]; then
        record_inconsistency "$start_sector" "sit_blkaddr" 
"$expected_sit_blkaddr" "$new_sit_blkaddr"
    fi
   
    if [ "$new_nat_blkaddr" != "$expected_nat_blkaddr" ]; then
        record_inconsistency "$start_sector" "nat_blkaddr" 
"$expected_nat_blkaddr" "$new_nat_blkaddr"
    fi
   
    if [ "$new_ssa_blkaddr" != "$expected_ssa_blkaddr" ]; then
        record_inconsistency "$start_sector" "ssa_blkaddr" 
"$expected_ssa_blkaddr" "$new_ssa_blkaddr"
    fi
   
    if [ "$new_main_blkaddr" != "$expected_main_blkaddr" ]; then
        record_inconsistency "$start_sector" "main_blkaddr" 
"$expected_main_blkaddr" "$new_main_blkaddr"
    fi
   
    info "Variable fields verified"
}

# Cleanup function
cleanup() {
    sudo umount "$MOUNT_POINT" 2>/dev/null || true
}

# Main test function for a single partition
test_partition() {
    local start_sector="$1"
    local partition_dev="${SSD_DEVICE}1"  # Always use partition 1
    local old_sb_file="old_sb_${start_sector}.txt"
    local new_sb_file="new_sb_${start_sector}.txt"
   
    # Setup cleanup trap
    trap "cleanup" EXIT INT TERM
   
    info "Testing partition starting at sector $start_sector (partition: 
$partition_dev)"
   
    # Calculate partition end sector (GB to sectors: GB * 1024 * 1024 * 1024 / 
512)
    # end_sector is inclusive, so we need to subtract 1
    local end_sector=$((start_sector + PARTITION_SIZE_GB * 1024 * 1024 * 1024 / 
512 - 1))
   
    # Check if partition would exceed device size
    local device_size_sectors=$(sudo blockdev --getsz "$SSD_DEVICE" 2>/dev/null 
|| echo "0")
    if [ "$device_size_sectors" -gt 0 ] && [ "$end_sector" -ge 
"$device_size_sectors" ]; then
        error "Partition end sector $end_sector exceeds device size 
$device_size_sectors"
    fi
   
    # Delete existing partition if it exists (we'll always use partition 1)
    sudo parted -s "$SSD_DEVICE" rm 1 2>/dev/null || true
   
    # Create new partition starting at start_sector (always as partition 1)
    info "Creating partition: start=${start_sector}s, end=${end_sector}s"
    local parted_output
    parted_output=$(sudo parted -s "$SSD_DEVICE" mkpart primary 
${start_sector}s ${end_sector}s 2>&1)
    local parted_status=$?
    if [ $parted_status -ne 0 ]; then
        error "Failed to create partition (start=${start_sector}s, 
end=${end_sector}s): $parted_output"
    fi
   
    # Wait for kernel to recognize the new partition
    sudo partprobe "$SSD_DEVICE" 2>/dev/null || true
   
    # Verify partition exists (always partition 1)
    partition_dev="${SSD_DEVICE}1"
    if [ ! -b "$partition_dev" ]; then
        error "Partition $partition_dev does not exist after creation"
    fi
   
    # Verify partition start and end sectors match our configuration
    # Use parted -m for machine-readable output
    local actual_info=$(sudo parted -s -m "$SSD_DEVICE" unit s print | grep 
"^1:" || error "Failed to get partition info")
    local actual_start=$(echo "$actual_info" | cut -d: -f2 | sed 's/s$//')
    local actual_end=$(echo "$actual_info" | cut -d: -f3 | sed 's/s$//')
   
    if [ -z "$actual_start" ] || [ -z "$actual_end" ]; then
        error "Failed to parse partition start/end sectors"
    fi
   
    if [ "$actual_start" != "$start_sector" ]; then
        error "Partition start sector mismatch: expected $start_sector, got 
$actual_start"
    fi
   
    if [ "$actual_end" != "$end_sector" ]; then
        error "Partition end sector mismatch: expected $end_sector, got 
$actual_end"
    fi
   
    info "Partition verified: start=$actual_start, end=$actual_end"
   
    # Create filesystem with old mkfs
    info "Creating filesystem on $partition_dev"
    local mkfs_output
    mkfs_output=$(sudo "$OLD_MKFS" -f "$partition_dev" 2>&1)
    local mkfs_status=$?
    if [ $mkfs_status -ne 0 ]; then
        error "Failed to create filesystem on $partition_dev: $mkfs_output"
    fi
    info "Filesystem created successfully"
   
    # Mount and fill partition with data
    sudo mkdir -p "$MOUNT_POINT"
    sudo mount -t f2fs "$partition_dev" "$MOUNT_POINT" || error "Failed to 
mount"
   
    # Check if SOURCE_DIR already exists (from previous test)
    if [ -d "$SOURCE_DIR" ] && [ -n "$(ls -A "$SOURCE_DIR" 2>/dev/null)" ]; then
        # SOURCE_DIR exists and has files, copy from SOURCE_DIR to mount point
        info "SOURCE_DIR already exists, copying files from $SOURCE_DIR to 
$MOUNT_POINT"
        sudo cp -r "$SOURCE_DIR"/* "$MOUNT_POINT/" || error "Failed to copy 
files from $SOURCE_DIR to $MOUNT_POINT"
    else
        # SOURCE_DIR doesn't exist, fill partition and copy to SOURCE_DIR
        fill_partition "$MOUNT_POINT"
       
        # Copy filled files to SOURCE_DIR as backup for verification
        info "Copying filled files to $SOURCE_DIR for verification"
        rm -rf "$SOURCE_DIR"
        mkdir -p "$SOURCE_DIR"
        sudo cp -r "$MOUNT_POINT"/* "$SOURCE_DIR/" || error "Failed to copy 
files to $SOURCE_DIR"
        sudo chown -R $(whoami) "$SOURCE_DIR" || true  # Change ownership so we 
can access later
    fi
   
    sudo umount "$MOUNT_POINT" || error "Failed to umount"
   
    # Run resize and capture output
    # -F: skip interactive input, -f: force, -d1: debug level 1
    # Output is automatically logged via exec redirect, but we also save to 
file for parsing
    info "Running resize for partition starting at sector $start_sector"
    sudo "$NEW_RESIZE" -F -f -d1 "$partition_dev" 2>&1 | tee 
"resize_output_${start_sector}.txt" || error "Failed to resize"

    # Ensure resize output contains three superblocks (mount + old + new)
    local sb_total
    sb_total=$(grep -c '^\| Super block' "resize_output_${start_sector}.txt" || 
echo 0)
    if [ "$sb_total" -lt 3 ]; then
        error "resize output incomplete for sector $start_sector: expected at 
least 3 super blocks, got $sb_total"
    fi
   
    # Extract superblock info from output
    # Use "| Super block" as unique identifier to avoid conflicts with other 
output
    # Skip the first superblock (from mount), extract the 2nd (old) and 3rd 
(new) ones
    awk '
    BEGIN {
        sb_count=0
        in_sb=0
        prev_line=""
    }
    /^\| Super block/ {
        # Start of new superblock section
        sb_count++
        # Skip the first superblock (from mount), only process 2nd and 3rd
        if (sb_count == 1) {
            in_sb=0
            prev_line = $0
            next
        }
        # Process 2nd (old) and 3rd (new) superblocks
        in_sb=1
        # Include the separator line before "| Super block"
        if (prev_line ~ 
/^\+--------------------------------------------------------\+/) {
            print prev_line > (sb_count == 2 ? "'"$old_sb_file"'" : 
"'"$new_sb_file"'")
        }
        print > (sb_count == 2 ? "'"$old_sb_file"'" : "'"$new_sb_file"'")
        prev_line = $0
        next
    }
    in_sb && sb_count >= 2 {
        # Collect all lines until next "| Super block" or end of file
        print > (sb_count == 2 ? "'"$old_sb_file"'" : "'"$new_sb_file"'")
        prev_line = $0
        next
    }
    {
        prev_line = $0
    }
    ' "resize_output_${start_sector}.txt"
   
    # Check if files were created and have content
    if [ ! -s "$old_sb_file" ]; then
        error "Failed to extract old superblock. Output file size: $(wc -l < 
"$old_sb_file" 2>/dev/null || echo 0) lines. First 20 lines of resize 
output:\n$(head -20 "resize_output_${start_sector}.txt")"
    fi
    if [ ! -s "$new_sb_file" ]; then
        error "Failed to extract new superblock. Output file size: $(wc -l < 
"$new_sb_file" 2>/dev/null || echo 0) lines"
    fi
   
    # Verify superblock consistency
    verify_sb_consistency "$old_sb_file" "$new_sb_file" "$start_sector"
   
    # Verify variable fields
    verify_variable_fields "$old_sb_file" "$new_sb_file" "$start_sector"
   
    # Mount and verify data
    sudo mount -t f2fs "$partition_dev" "$MOUNT_POINT" || error "Failed to 
mount after resize"
    verify_data
    sudo umount "$MOUNT_POINT" || error "Failed to umount after resize"
   
    # Cleanup files (partition cleanup handled by trap)
    # rm -f "$old_sb_file" "$new_sb_file" "resize_output_${start_sector}.txt"
   
    # Remove trap
    trap - EXIT INT TERM
   
    info "Partition test $start_sector completed successfully"
}

# Main execution
main() {
    # Redirect all output to both terminal and log file
    exec > >(tee "$LOG_FILE") 2>&1
   
    echo "=== Test started at $(date) ==="
    echo ""
    echo "=== Configuration ==="
    echo "SOURCE_DIR: $SOURCE_DIR"
    echo "MOUNT_POINT: $MOUNT_POINT"
    echo "SSD_DEVICE: $SSD_DEVICE"
    echo "OLD_MKFS: $OLD_MKFS"
    echo "NEW_RESIZE: $NEW_RESIZE"
    echo "PARTITION_SIZE_GB: $PARTITION_SIZE_GB"
    echo "LOG_FILE: $LOG_FILE"
    echo "STATS_FILE: $STATS_FILE"
    echo ""
   
    # Check prerequisites
    command -v "$OLD_MKFS" >/dev/null 2>&1 || error "mkfs.f2fs command not 
found: $OLD_MKFS"
    [ -f "$NEW_RESIZE" ] || error "New resize.f2fs not found: $NEW_RESIZE"
   
    # Check if SSD_DEVICE is set
    if [ -z "$SSD_DEVICE" ]; then
        error "SSD_DEVICE is not set. Please set it to your SSD device (e.g., 
/dev/sdb)"
        error "Usage: SSD_DEVICE=/dev/sdb $0"
        exit 1
    fi
   
    # Verify SSD device exists
    if [ ! -b "$SSD_DEVICE" ]; then
        error "SSD device $SSD_DEVICE does not exist or is not a block device"
    fi
   
    # Warn user about data destruction
    warn "WARNING: This script will create and destroy partitions on 
$SSD_DEVICE"
    warn "All existing data on $SSD_DEVICE will be lost!"
    echo ""
    read -p "Press Enter to continue or Ctrl+C to abort..."
   
    info "Starting resize.f2fs test suite"
   
    # Initialize statistics file
    echo "=== Superblock Inconsistencies ===" > "$STATS_FILE"
    echo "Format: [start_sector=X] field: old=value, new=value" >> "$STATS_FILE"
    echo "" >> "$STATS_FILE"
   
    # Initialize partition table (only once)
    warn "Initializing partition table on $SSD_DEVICE"
    sudo parted -s "$SSD_DEVICE" mklabel gpt 2>/dev/null || error "Failed to 
create partition table"
   
    # Test each partition (start from sector 4096, as GPT requires partitions 
to start after sector 34)
    local total_tests=4096
    local current_test=0
    for start_sector in {4096..8191}; do
        current_test=$((current_test + 1))
        info "Progress: $current_test/$total_tests (sector $start_sector)"
        test_partition "$start_sector"
    done
   
    info "All tests completed successfully"
}

# Run main function
main "$@"

But as an individual researcher with limited hardware, my testing environment 
is currently constrained by my academic work.

Please note that I only have access to a single SSD around 100GB for my 
testing, which limits the scope of my regression validation (especially for 
large-scale performance or capacity checks). Given your corporate environment, 
your team would be much better positioned to run extensive, large-scale 
regression tests on the final fix. Of course, whenever I have spare time, I 
will also continue running this script for further testing.

I kindly request that you use these reproducers and the test script to validate 
the fix on your end once implemented. If there is anything further I can do to 
assist with the analysis or implementation, please do not hesitate to reach out.

Note on Script Validation Logic:

The validation logic within this script for checking new vs. old superblock 
fields (specifically the difference in field values) may not be perfectly 
accurate or comprehensive. However, this is acceptable, as these checks are 
primarily for informational purposes and will not be used as the definitive 
criteria for judging the bug.

The true criteria for bug judgment remain:
1.  Whether resize.f2fs executes successfully without failure.
2.  More importantly, whether the original created files remains fully 
consistent after the execution of resize.f2fs.

Please feel free to reach out to me if you have any questions or require 
clarification regarding the script or the expected outcomes.

Thanks,
Xiaole He



_______________________________________________
Linux-f2fs-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/linux-f2fs-devel

Reply via email to