Skip to content
  • Mike Fleetwood's avatar
    Switch to faster minfo and mdir to read FAT16/32 usage (#569921) · 5a52b440
    Mike Fleetwood authored and Curtis Gedak's avatar Curtis Gedak committed
    A user reported that GParted was slow to refresh on FAT32 file systems:
    "can take very long, up to several minutes; can be reproduced by running
    dosfsck manually".  This is because the file system was large and almost
    full, and GParted performs a file system check just to to report the
    file system usage.
    
    Created a 4 GiB FAT32 file system and almost filled it with 4 KiB files,
    just over 970,000 files.
    
        # df -k /mnt/2
        Filesystem     1K-blocks     Used Available Used% Mounted on
        /dev/sdb2        4186108 39155384    270724   94% /mnt/2
        # df -i /mnt/2
        Filesystem     Inodes IUsed IFree IUse% Mounted on
        /dev/sdb2           0     0     0     - /mnt/2
        # find /mnt/2 -type f -print | wc -l
        971059
        # find /mnt/2 -type d -print | wc -l
        1949
    
    Testing performance of the current fsck.fat:
    
        # time fsck.fat -n -v /dev/sdb2 | \
        > egrep 'bytes per logical sector|bytes per cluster|sectors total|clusters$'
               512 bytes per logical sector
              4096 bytes per cluster
           8388608 sectors total
        /dev/sdb2: 973008 files, 978846/1046527 clusters
    
        real    0m11.552s
        user    0m2.183s
        sys     0m7.547s
    
    Free sectors in the file system according to fsck.fat:
        (1046527 - 978846) * 4096 / 512 = 541448 sectors
    
    Repeating this test while also using 'blktrace /dev/sdb2' and Ctrl-C
    around the test in a separate terminal, reports these numbers of I/Os
    being performed:
        Read requests   Read bytes
               15,563      165 MiB
    
    Prior to this commit [1] from 0.0.9, GParted used libparted's
    ped_file_system_get_resize_constraint() to report the minimum size to
    which a FAT* file system can be resized.  Use this test program [2] to
    performance test this method:
    
        # time ./fscons /dev/sdb2
        dev=/dev/sdb2
        sector_size=512
        min_size=7909522
        max_size=8388608
    
        real    0m2.673s
        user    0m0.070s
        sys     0m1.834s
    
    Free sectors in the file system according to libparted
    ped_file_system_get_resize_constraint():
        8388608 - 7909522 = 479086 sectors
    
    blktrace reports these numbers of I/Os being performed:
        Read requests   Read bytes
                7,821       71 MiB
    
    So using libparted resize constraint is a bit faster but is still
    reading too much data and is really too slow.  Also when testing GParted
    using this libparted method against a corrupted FAT32 file system, on
    every refresh, one popup dialog is displayed for each error libparted
    detects with the file system, each of which needs acknowledgement.
    Example popup:
    
                         Libparted Error
        \DIRNAME\FILENAME.EXT is 107724k, but is has 1920
        clusters (122880k).
    
                                     [ Cancel ][ Ignore ]
    
    There could be a huge number of such errors in a corrupted file system.
    Not really suitable for use by GParted.
    
    Test the performance of mtools' minfo command to report the file system
    figures:
    
        # time minfo -i /dev/sdb2 :: | \
        > egrep 'sector size:|cluster size:|small size:|big size:|free clusters='
        sector size: 512 bytes
        cluster size: 8 sectors
        small size: 0 sectors
        big size: 8388608 sectors
        free clusters=67681
    
        real    0m0.013s
        user    0m0.004s
        sys     0m0.019s
    
    Free sectors in the file system according to minfo:
        67681 * 8 = 541448 sectors
    
    blktrace reports these numbers of I/Os being performed by minfo:
        Read requests   Read bytes
                    1       16 KiB
    
    This matches with minfo just reading information from the BPB (BIOS
    Parameter Block) [3] from sector 0 and the FS Information Sector [4]
    usually in sector 1.  Note that the free cluster figure reported by
    minfo comes from the FS Information Sector because it only reports it
    for FAT32 file systems, not for FAT16 file systems.  Scanning the File
    Allocation Table (FAT) [5] to count free clusters is exactly what mdir,
    without the '-f' (fast) flag, does.  Test the performance of mdir:
    
        # export MTOOLS_SKIP_CHECK=1
        # time mdir -i /dev/sdb2 ::/ | fgrep 'bytes free'
                                277 221 376 bytes free
    
        real    0m0.023s
        user    0m0.011s
        sys     0m0.023s
    
    Free sectors in the file system according to mdir:
        277221376 / 512 = 541448 sectors
    
    blktrace reports these number of I/Os being performed by mdir:
        Read requests   Read bytes
                    5      448 KiB
    
    So minfo and mdir together provide the needed information and are 2 to 3
    orders of magnitude faster because they only read the needed BPB and FAT
    data from the drive.  Use these together to read the file system usage.
    
    [1] 61cd0ce7
        lots of stuff and cleanups, including fixing getting used/unused
        space of hfs/hfs+/fat16/fat32
    
    [2] fscons.c
    /* FILE:     fscons.c
     * SYNOPSIS: Report libparted's FS resize limits.
     * BUILD:    gcc -o fscons fscons.c -lparted -lparted-fs-resize
     */
    
    int main(int argc, const char *argv[])
    {
        PedDevice* dev = NULL;
        PedDisk* tab = NULL;
        PedPartition* ptn = NULL;
        PedFileSystem* fs = NULL;
        PedConstraint* cons = NULL;
    
        if (argc != 2)
        {
            fprintf(stderr, "Usage: fscons BLOCKDEV\n");
            exit(1);
        }
    
        dev = ped_device_get(argv[1]);
        if (dev == NULL)
        {
            fprintf(stderr, "ped_device_get(\"%s\") failed\n", argv[1]);
            exit(1);
        }
        printf("dev=%s\n", dev->path);
        printf("sector_size=%ld\n", dev->sector_size);
    
        tab = ped_disk_new(dev);
        if (tab == NULL)
        {
            fprintf(stderr, "ped_disk_new(dev) failed\n");
            exit(1);
        }
    
        ptn = ped_disk_get_partition_by_sector(tab, 0);
        if (ptn == NULL)
        {
            fprintf(stderr, "ped_disk_get_partition(tab, 0) failed\n");
            exit(1);
        }
    
        fs = ped_file_system_open(&ptn->geom);
        if (fs == NULL)
        {
            fprintf(stderr, "ped_file_system_open(&ptn->geom) failed\n");
            exit(1);
        }
    
        cons = ped_file_system_get_resize_constraint(fs);
        if (cons == NULL)
        {
            fprintf(stderr, "ped_file_system_get_resize_constraint(fs) failed\n");
            exit(1);
        }
        printf("min_size=%ld\n", cons->min_size);
        printf("max_size=%ld\n", cons->max_size);
    
        ped_constraint_destroy(cons);
        ped_file_system_close(fs);
        ped_disk_destroy(tab);
        ped_device_destroy(dev);
    
        return 0;
    }
    
    [3] Design of the FAT file system, BIOS Parameter Block
        https://en.wikipedia.org/wiki/Design_of_the_FAT_file_system#BIOS_Parameter_Block
    
    [4] Design of the FAT file system, FS Information Sector
        https://en.wikipedia.org/wiki/Design_of_the_FAT_file_system#FS_Information_Sector
    
    [5] Design of the FAT file system, File Allocation Table
        https://en.wikipedia.org/wiki/Design_of_the_FAT_file_system#File_Allocation_Table
    
    Bug 569921 - dosfsck -n delays device scan
    5a52b440