grub/fs/fat.c
okuji 524a1e6a40 2006-06-04 Yoshinori K. Okuji <okuji@enbug.org>
Clean up the code to support 64-bit addressing in disks and
        files. This change is not enough for filesystems yet.

        * util/i386/pc/grub-setup.c (struct boot_blocklist): Change the
        type of "start" to grub_uint64_t.
        (setup): Change the types of KERNEL_SECTOR and FIRST_SECTOR to
        grub_disk_addr_t * and grub_disk_addr_t. Fix the format string in
        save_first_sector and save_blocklists. Use grub_le_to_cpu64 to
        convert addresses.

        * util/i386/pc/biosdisk.c (open_device): Change the type of SECTOR
        to grub_disk_addr_t.

        * partmap/gpt.c (gpt_partition_map_iterate): Fix the format
        string.

        * partmap/pc.c (pc_partition_map_iterate): Likewise.

        * partmap/amiga.c (amiga_partition_map_iterate): Cast RDSK.MAGIC
        to char *.

        * normal/script.c (grub_script_parse): Remove unused MEMFREE.

        * normal/parser.y (YYLTYPE_IS_TRIVIAL): New macro.

        * normal/lexer.c (grub_script_yyerror): Specify unused to LEX.

        * loader/i386/pc/multiboot.c (grub_multiboot_load_elf64): Cast -1
        to grub_off_t, to detect an error from grub_file_seek.
        (grub_multiboot_load_elf32): Likewise.

        * kern/misc.c (grub_strtoul): Use grub_strtoull. Return the
        maximum unsigned long value when an overflow is detected.
        (grub_strtoull): New function.
        (grub_divmod64): Likewise.
        (grub_lltoa): use grub_divmod64.

        * kern/fs.c (struct grub_fs_block): Change the type of "offset" to
        grub_disk_addr_t.
        (grub_fs_blocklist_open): Increase P if P is not NULL to advance
        the pointer to next character. Use grub_strtoull instead of
        grub_strtoul.
        (grub_fs_blocklist_read): Change the types of SECTOR, OFFSET and
        SIZE to grub_disk_addr_t, grub_off_t and grub_size_t,
        respectively.

        * kern/file.c (grub_file_read): Prevent an oveflow of LEN, as the
        return value is signed.
        (grub_file_seek): Change the type of OLD to grub_off_t. Do not
        test if OFFSET is less than zero, as OFFSET is unsigned now.

        * kern/disk.c (struct grub_disk_cache): Change the type of
        "sector" to grub_disk_addr_t.
        (grub_disk_cache_get_index): Change the type of SECTOR to
        grub_disk_addr_t. Calculate the hash with SECTOR casted to
        unsigned after shifting.
        (grub_disk_cache_invalidate): Change the type of SECTOR to
        grub_disk_addr_t.
        (grub_disk_cache_unlock): Likewise.
        (grub_disk_cache_store): Likewise.
        (grub_disk_check_range): Change the types of SECTOR, OFFSET, SIZE,
        START and LEN to grub_disk_addr_t *, grub_off_t *, grub_size_t,
        grub_disk_addr_t and grub_uint64_t, respectively.
        (grub_disk_read): Use an unsigned variable REAL_OFFSET for the
        body, as the value of OFFSET is tweaked by
        grub_disk_check_range. Change the types of START_SECTOR, LEN and
        POS to grub_disk_addr_t, grub_size_t and grub_size_t,
        respectively.
        (grub_disk_write): Use an unsigned variable REAL_OFFSET for the
        body, as the value of OFFSET is tweaked by
        grub_disk_check_range. Change the types of LEN and N to
        grub_size_t.

        * io/gzio.c (struct grub_gzio): Change the types of "data_offset"
        and "saved_offset" to grub_off_t.
        (test_header): Cast BUF to char *.
        (get_byte): Cast GZIO->DATA_OFFSET to grub_off_t. Cast GZIO->INBUF
        to char *.
        (grub_gzio_read): Change the types of OFFSET and SIZE to
        grub_off_t and grub_size_t, respectively.

        * include/grub/i386/pc/boot.h (GRUB_BOOT_MACHINE_FORCE_LBA):
        Removed.
        (GRUB_BOOT_MACHINE_BOOT_DRIVE): Changed to 0x4c.
        (GRUB_BOOT_MACHINE_KERNEL_ADDRESS): Changed to 0x40.
        (GRUB_BOOT_MACHINE_KERNEL_SEGMENT): Changed to 0x42.
        (GRUB_BOOT_MACHINE_DRIVE_CHECK): Changed to 0x4e.
        (GRUB_BOOT_MACHINE_LIST_SIZE): Increased to 12.

        * include/grub/types.h (grub_off_t): Unconditionally set to
        grub_uint64_t.
        (grub_disk_addr_t): Changed to grub_uint64_t.

        * include/grub/partition.h (struct grub_partition): Change the
        types of "start", "len" and "offset" to grub_disk_addr_t,
        grub_uint64_t and grub_disk_addr_t, respectively.
        (grub_partition_get_start): Return grub_disk_addr_t.
        (grub_partition_get_len): Return grub_uint64_t.

        * include/grub/misc.h (grub_strtoull): New prototype.
        (grub_divmod64): Likewise.

        * include/grub/fshelp.h (grub_fshelp_read_file): Change the types
        of SECTOR, LEN and FILESIZE to grub_disk_addr_t, grub_size_t and
        grub_off_t, respectively.
        All callers and references changed.

        * include/grub/fs.h (struct grub_fs): Change the type of LEN to
        grub_size_t in "read".
        All callers and references changed.

        * include/grub/file.h (struct grub_file): Change the types of
        "offset" and "size" to grub_off_t and grub_off_t,
        respectively. Change the type of SECTOR to grub_disk_addr_t in
        "read_hook".
        (grub_file_read): Change the type of LEN to grub_size_t.
        (grub_file_seek): Return grub_off_t. Change the type of OFFSET to
        grub_off_t.
        (grub_file_size): Return grub_off_t.
        (grub_file_tell): Likewise.
        All callers and references changed.

        * include/grub/disk.h (struct grub_disk_dev): Change the types of
        SECTOR and SIZE to grub_disk_addr_t and grub_size_t in "read" and
        "write".
        (struct grub_disk): Change the type of "total_sectors" to
        grub_uint64_t. Change the type of SECTOR to grub_disk_addr_t in
        "read_hook".
        (grub_disk_read): Change the types of SECTOR, OFFSET and SIZE to
        grub_disk_addr_t, grub_off_t and grub_size_t, respectively.
        (grub_disk_write): Likewise.
        All callers and references changed.

        * fs/iso9660.c (grub_iso9660_susp_iterate): Cast parameters to
        char * for grub_strncmp to silence gcc.
        (grub_iso9660_mount): Likewise.
        (grub_iso9660_mount): Likewise.
        (grub_iso9660_read_symlink): Likewise. Also, remove the nonsense
        return statement.
        (grub_iso9660_iterate_dir): Likewise.
        (grub_iso9660_label): Cast DATA->VOLDESC.VOLNAME to char *.

        * fs/hfs.c (grub_hfs_read_file): Change the types of SECTOR and
        LEN to grub_disk_addr_t and grub_size_t, respectively.

        * fs/hfsplus.c (grub_hfsplus_read_file): Likewise.

        * fs/jfs.c (grub_jfs_read_file): Likewise.

        * fs/minix.c (grub_jfs_read_file): Likewise.

        * fs/sfs.c (grub_jfs_read_file): Likewise.

        * fs/ufs.c (grub_jfs_read_file): Likewise.

        * fs/xfs.c (grub_jfs_read_file): Likewise.

        * fs/fat.c (grub_fat_read_data): Change the types of SECTOR, LEN
        and SIZE to grub_disk_addr_t, grub_size_t and grub_size_t,
        respectively.

        * fs/ext2.c (grub_ext2_read_block): When an error happens, set
        BLKNR to -1 instead of returning GRUB_ERRNO.
        (grub_ext2_read_file): Change the types of SECTOR and
        LEN to grub_disk_addr_t and grub_size_t, respectively.

        * fs/affs.c (grub_affs_read_file): Change the types of SECTOR and
        LEN to grub_disk_addr_t and grub_size_t, respectively.

        * font/manager.c (grub_font_get_glyph): Cast BITMAP to char * for
        grub_file_read.

        * disk/ieee1275/ofdisk.c (grub_ofdisk_read): Fix the format
        string. Do not cast SECTOR explicitly.

        * disk/i386/pc/biosdisk.c (grub_biosdisk_open): Change the type of
        TOTAL_SECTORS to grub_uint64_t. Do not mask DRP->TOTAL_SECTORS.
        (grub_biosdisk_rw): Change the types of SECTOR and SIZE to
        grub_disk_addr_t and grub_size_t, respectively. If the sector is
        over 2TB and LBA mode is not supported, raise an error.
        (get_safe_sectors): New function.
        (grub_biosdisk_read): Use get_safe_sectors.
        (grub_biosdisk_write): Likewise.

        * disk/efi/efidisk.c (grub_efidisk_read): Fix the format string.
        (grub_efidisk_write): Likewise.

        * disk/loopback.c (delete_loopback): Cosmetic changes.
        (grub_cmd_loopback): Likewise. Also, test NEWDEV->FILENAME
        correctly.
        (grub_loopback_open): Likewise.
        (grub_loopback_read): Likewise. Also, change the type of POS to
        grub_off_t, and fix the usage of grub_memset.

        * commands/i386/pc/play.c: Include grub/machine/time.h.

        * commands/ls.c (grub_ls_list_files): Use "llu" instead of "d" to
        print FILE->SIZE.

        * commands/configfile.c: Include grub/env.h.

        * commands/cmp.c (grub_cmd_cmp): Do not use ERR, but use
        GRUB_ERRNO directly instead. Change the type of POS to
        grub_off_t. Follow the coding standard.

        * commands/blocklist.c: Include grub/partition.h.
        (grub_cmd_blocklist): Return an error if the underlying device is
        not a disk. Take the starting sector of a partition into account,
        if a partition is used.

        * boot/i386/pc/diskboot.S (bootloop): Adapted to the new offset of
        a length field.
        (lba_mode): Support 64-bit addresses.
        (chs_mode): Likewise.
        (copy_buffer): Adapted to the new offsets of a length field and a
        segment field.
        (blocklist_default_start): Allocate 64-bit space.

        * boot/i386/pc/boot.S (force_lba): Removed.
        (boot_drive): Moved to under KERNEL_SECTOR.
        (kernel_sector): Moved to under KENREL_SEGMENT. Allocate 64-bit
        space.
        (real_start): Set %si earlier. Remove code for FORCE_LBA, since it
        is useless.
        (lba_mode): Refactored to support a 64-bit address. More size
        optimization.
        (setup_sectors): Likewise.
2006-06-04 15:56:55 +00:00

824 lines
18 KiB
C

/* fat.c - FAT filesystem */
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2000,2001,2002,2003,2004,2005 Free Software Foundation, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <grub/fs.h>
#include <grub/disk.h>
#include <grub/file.h>
#include <grub/types.h>
#include <grub/misc.h>
#include <grub/mm.h>
#include <grub/err.h>
#include <grub/dl.h>
#define GRUB_FAT_DIR_ENTRY_SIZE 32
#define GRUB_FAT_ATTR_READ_ONLY 0x01
#define GRUB_FAT_ATTR_HIDDEN 0x02
#define GRUB_FAT_ATTR_SYSTEM 0x04
#define GRUB_FAT_ATTR_VOLUME_ID 0x08
#define GRUB_FAT_ATTR_DIRECTORY 0x10
#define GRUB_FAT_ATTR_ARCHIVE 0x20
#define GRUB_FAT_ATTR_LONG_NAME (GRUB_FAT_ATTR_READ_ONLY \
| GRUB_FAT_ATTR_HIDDEN \
| GRUB_FAT_ATTR_SYSTEM \
| GRUB_FAT_ATTR_VOLUME_ID)
#define GRUB_FAT_ATTR_VALID (GRUB_FAT_ATTR_READ_ONLY \
| GRUB_FAT_ATTR_HIDDEN \
| GRUB_FAT_ATTR_SYSTEM \
| GRUB_FAT_ATTR_DIRECTORY \
| GRUB_FAT_ATTR_ARCHIVE)
struct grub_fat_bpb
{
grub_uint8_t jmp_boot[3];
grub_uint8_t oem_name[8];
grub_uint16_t bytes_per_sector;
grub_uint8_t sectors_per_cluster;
grub_uint16_t num_reserved_sectors;
grub_uint8_t num_fats;
grub_uint16_t num_root_entries;
grub_uint16_t num_total_sectors_16;
grub_uint8_t media;
grub_uint16_t sectors_per_fat_16;
grub_uint16_t sectors_per_track;
grub_uint16_t num_heads;
grub_uint32_t num_hidden_sectors;
grub_uint32_t num_total_sectors_32;
/* The following fields are only used by FAT32. */
grub_uint32_t sectors_per_fat_32;
grub_uint16_t extended_flags;
grub_uint16_t fs_version;
grub_uint32_t root_cluster;
grub_uint16_t fs_info;
grub_uint16_t backup_boot_sector;
} __attribute__ ((packed));
struct grub_fat_dir_entry
{
grub_uint8_t name[11];
grub_uint8_t attr;
grub_uint8_t nt_reserved;
grub_uint8_t c_time_tenth;
grub_uint16_t c_time;
grub_uint16_t c_date;
grub_uint16_t a_date;
grub_uint16_t first_cluster_high;
grub_uint16_t w_time;
grub_uint16_t w_date;
grub_uint16_t first_cluster_low;
grub_uint32_t file_size;
} __attribute__ ((packed));
struct grub_fat_long_name_entry
{
grub_uint8_t id;
grub_uint16_t name1[5];
grub_uint8_t attr;
grub_uint8_t reserved;
grub_uint8_t checksum;
grub_uint16_t name2[6];
grub_uint16_t first_cluster;
grub_uint16_t name3[2];
} __attribute__ ((packed));
struct grub_fat_data
{
int logical_sector_bits;
grub_uint32_t num_sectors;
grub_uint16_t fat_sector;
grub_uint32_t sectors_per_fat;
int fat_size;
grub_uint32_t root_cluster;
grub_uint32_t root_sector;
grub_uint32_t num_root_sectors;
int cluster_bits;
grub_uint32_t cluster_eof_mark;
grub_uint32_t cluster_sector;
grub_uint32_t num_clusters;
grub_uint8_t attr;
grub_ssize_t file_size;
grub_uint32_t file_cluster;
grub_uint32_t cur_cluster_num;
grub_uint32_t cur_cluster;
};
#ifndef GRUB_UTIL
static grub_dl_t my_mod;
#endif
static int
fat_log2 (unsigned x)
{
int i;
if (x == 0)
return -1;
for (i = 0; (x & 1) == 0; i++)
x >>= 1;
if (x != 1)
return -1;
return i;
}
static struct grub_fat_data *
grub_fat_mount (grub_disk_t disk)
{
struct grub_fat_bpb bpb;
struct grub_fat_data *data = 0;
grub_uint32_t first_fat, magic;
if (! disk)
goto fail;
data = (struct grub_fat_data *) grub_malloc (sizeof (*data));
if (! data)
goto fail;
/* Read the BPB. */
if (grub_disk_read (disk, 0, 0, sizeof (bpb), (char *) &bpb))
goto fail;
/* Get the sizes of logical sectors and clusters. */
data->logical_sector_bits =
fat_log2 (grub_le_to_cpu16 (bpb.bytes_per_sector));
if (data->logical_sector_bits < GRUB_DISK_SECTOR_BITS)
goto fail;
data->logical_sector_bits -= GRUB_DISK_SECTOR_BITS;
data->cluster_bits = fat_log2 (bpb.sectors_per_cluster);
if (data->cluster_bits < 0)
goto fail;
data->cluster_bits += data->logical_sector_bits;
/* Get information about FATs. */
data->fat_sector = (grub_le_to_cpu16 (bpb.num_reserved_sectors)
<< data->logical_sector_bits);
if (data->fat_sector == 0)
goto fail;
data->sectors_per_fat = ((bpb.sectors_per_fat_16
? grub_le_to_cpu16 (bpb.sectors_per_fat_16)
: grub_le_to_cpu32 (bpb.sectors_per_fat_32))
<< data->logical_sector_bits);
if (data->sectors_per_fat == 0)
goto fail;
/* Get the number of sectors in this volume. */
data->num_sectors = ((bpb.num_total_sectors_16
? grub_le_to_cpu16 (bpb.num_total_sectors_16)
: grub_le_to_cpu32 (bpb.num_total_sectors_32))
<< data->logical_sector_bits);
if (data->num_sectors == 0)
goto fail;
/* Get information about the root directory. */
if (bpb.num_fats == 0)
goto fail;
data->root_sector = data->fat_sector + bpb.num_fats * data->sectors_per_fat;
data->num_root_sectors
= ((((grub_uint32_t) grub_le_to_cpu16 (bpb.num_root_entries)
* GRUB_FAT_DIR_ENTRY_SIZE
+ grub_le_to_cpu16 (bpb.bytes_per_sector) - 1)
>> (data->logical_sector_bits + GRUB_DISK_SECTOR_BITS))
<< (data->logical_sector_bits));
data->cluster_sector = data->root_sector + data->num_root_sectors;
data->num_clusters = (((data->num_sectors - data->cluster_sector)
>> (data->cluster_bits + data->logical_sector_bits))
+ 2);
if (data->num_clusters <= 2)
goto fail;
if (! bpb.sectors_per_fat_16)
{
/* FAT32. */
grub_uint16_t flags = grub_le_to_cpu16 (bpb.extended_flags);
data->root_cluster = grub_le_to_cpu32 (bpb.root_cluster);
data->fat_size = 32;
data->cluster_eof_mark = 0x0ffffff8;
if (flags & 0x80)
{
/* Get an active FAT. */
unsigned active_fat = flags & 0xf;
if (active_fat > bpb.num_fats)
goto fail;
data->fat_sector += active_fat * data->sectors_per_fat;
}
if (bpb.num_root_entries != 0 || bpb.fs_version != 0)
goto fail;
}
else
{
/* FAT12 or FAT16. */
data->root_cluster = ~0UL;
if (data->num_clusters <= 4085 + 2)
{
/* FAT12. */
data->fat_size = 12;
data->cluster_eof_mark = 0x0ff8;
}
else
{
/* FAT16. */
data->fat_size = 16;
data->cluster_eof_mark = 0xfff8;
}
}
/* More sanity checks. */
if (data->num_sectors <= data->fat_sector)
goto fail;
if (grub_disk_read (disk,
data->fat_sector,
0,
sizeof (first_fat),
(char *) &first_fat))
goto fail;
first_fat = grub_le_to_cpu32 (first_fat);
if (data->fat_size == 32)
{
first_fat &= 0x0fffffff;
magic = 0x0fffff00;
}
else if (data->fat_size == 16)
{
first_fat &= 0x0000ffff;
magic = 0xff00;
}
else
{
first_fat &= 0x00000fff;
magic = 0x0f00;
}
/* Ignore the 3rd bit, because some BIOSes assigns 0xF0 to the media
descriptor, even if it is a so-called superfloppy (e.g. an USB key).
The check may be too strict for this kind of stupid BIOSes, as
they overwrite the media descriptor. */
if ((first_fat | 0x8) != (magic | bpb.media | 0x8))
goto fail;
/* Start from the root directory. */
data->file_cluster = data->root_cluster;
data->cur_cluster_num = ~0UL;
data->attr = GRUB_FAT_ATTR_DIRECTORY;
return data;
fail:
grub_free (data);
grub_error (GRUB_ERR_BAD_FS, "not a fat filesystem");
return 0;
}
static grub_ssize_t
grub_fat_read_data (grub_disk_t disk, struct grub_fat_data *data,
void (*read_hook) (grub_disk_addr_t sector,
unsigned offset, unsigned length),
grub_off_t offset, grub_size_t len, char *buf)
{
grub_size_t size;
grub_uint32_t logical_cluster;
unsigned logical_cluster_bits;
grub_ssize_t ret = 0;
unsigned long sector;
/* This is a special case. FAT12 and FAT16 doesn't have the root directory
in clusters. */
if (data->file_cluster == ~0UL)
{
size = (data->num_root_sectors << GRUB_DISK_SECTOR_BITS) - offset;
if (size > len)
size = len;
if (grub_disk_read (disk, data->root_sector, offset, size, buf))
return -1;
return size;
}
/* Calculate the logical cluster number and offset. */
logical_cluster_bits = (data->cluster_bits
+ data->logical_sector_bits
+ GRUB_DISK_SECTOR_BITS);
logical_cluster = offset >> logical_cluster_bits;
offset &= (1 << logical_cluster_bits) - 1;
if (logical_cluster < data->cur_cluster_num)
{
data->cur_cluster_num = 0;
data->cur_cluster = data->file_cluster;
}
while (len)
{
while (logical_cluster > data->cur_cluster_num)
{
/* Find next cluster. */
grub_uint32_t next_cluster;
unsigned long fat_offset;
switch (data->fat_size)
{
case 32:
fat_offset = data->cur_cluster << 2;
break;
case 16:
fat_offset = data->cur_cluster << 1;
break;
default:
/* case 12: */
fat_offset = data->cur_cluster + (data->cur_cluster >> 1);
break;
}
/* Read the FAT. */
if (grub_disk_read (disk, data->fat_sector, fat_offset,
(data->fat_size + 7) >> 3,
(char *) &next_cluster))
return -1;
next_cluster = grub_le_to_cpu32 (next_cluster);
switch (data->fat_size)
{
case 16:
next_cluster &= 0xFFFF;
break;
case 12:
if (data->cur_cluster & 1)
next_cluster >>= 4;
next_cluster &= 0x0FFF;
break;
}
#if 0
grub_printf ("%s:%d: fat_size=%d, next_cluster=%u\n",
__FILE__, __LINE__, data->fat_size, next_cluster);
#endif
/* Check the end. */
if (next_cluster >= data->cluster_eof_mark)
return ret;
if (next_cluster < 2 || next_cluster >= data->num_clusters)
{
grub_error (GRUB_ERR_BAD_FS, "invalid cluster %u",
next_cluster);
return -1;
}
data->cur_cluster = next_cluster;
data->cur_cluster_num++;
}
/* Read the data here. */
sector = (data->cluster_sector
+ ((data->cur_cluster - 2)
<< (data->cluster_bits + data->logical_sector_bits)));
size = (1 << logical_cluster_bits) - offset;
if (size > len)
size = len;
disk->read_hook = read_hook;
grub_disk_read (disk, sector, offset, size, buf);
disk->read_hook = 0;
if (grub_errno)
return -1;
len -= size;
buf += size;
ret += size;
logical_cluster++;
offset = 0;
}
return ret;
}
/* Find the underlying directory or file in PATH and return the
next path. If there is no next path or an error occurs, return NULL.
If HOOK is specified, call it with each file name. */
static char *
grub_fat_find_dir (grub_disk_t disk, struct grub_fat_data *data,
const char *path,
int (*hook) (const char *filename, int dir))
{
struct grub_fat_dir_entry dir;
char *dirname, *dirp;
char *filename, *filep = 0;
grub_uint16_t *unibuf;
int slot = -1, slots = -1;
int checksum = -1;
grub_ssize_t offset = -sizeof(dir);
int call_hook;
if (! (data->attr & GRUB_FAT_ATTR_DIRECTORY))
{
grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory");
return 0;
}
/* Extract a directory name. */
while (*path == '/')
path++;
dirp = grub_strchr (path, '/');
if (dirp)
{
unsigned len = dirp - path;
dirname = grub_malloc (len + 1);
if (! dirname)
return 0;
grub_memcpy (dirname, path, len);
dirname[len] = '\0';
}
else
/* This is actually a file. */
dirname = grub_strdup (path);
call_hook = (! dirp && hook);
/* Allocate space enough to hold a long name. */
filename = grub_malloc (0x40 * 13 * 4 + 1);
unibuf = (grub_uint16_t *) grub_malloc (0x40 * 13 * 2);
if (! filename || ! unibuf)
{
grub_free (filename);
grub_free (unibuf);
grub_free (dirname);
return 0;
}
while (1)
{
unsigned i;
/* Adjust the offset. */
offset += sizeof (dir);
/* Read a directory entry. */
if ((grub_fat_read_data (disk, data, 0,
offset, sizeof (dir), (char *) &dir)
!= sizeof (dir))
|| dir.name[0] == 0)
{
if (grub_errno == GRUB_ERR_NONE && ! call_hook)
grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found");
break;
}
/* Handle long name entries. */
if (dir.attr == GRUB_FAT_ATTR_LONG_NAME)
{
struct grub_fat_long_name_entry *long_name
= (struct grub_fat_long_name_entry *) &dir;
grub_uint8_t id = long_name->id;
if (id & 0x40)
{
id &= 0x3f;
slots = slot = id;
checksum = long_name->checksum;
}
if (id != slot || slot == 0 || checksum != long_name->checksum)
{
checksum = -1;
continue;
}
slot--;
grub_memcpy (unibuf + slot * 13, long_name->name1, 5 * 2);
grub_memcpy (unibuf + slot * 13 + 5, long_name->name2, 6 * 2);
grub_memcpy (unibuf + slot * 13 + 11, long_name->name3, 2 * 2);
continue;
}
/* Check if this entry is valid. */
if (dir.name[0] == 0xe5 || (dir.attr & ~GRUB_FAT_ATTR_VALID))
continue;
/* This is a workaround for Japanese. */
if (dir.name[0] == 0x05)
dir.name[0] = 0xe5;
if (checksum != -1 && slot == 0)
{
grub_uint8_t sum;
for (sum = 0, i = 0; i < sizeof (dir.name); i++)
sum = ((sum >> 1) | (sum << 7)) + dir.name[i];
if (sum == checksum)
{
int u;
for (u = 0; u < slots * 13; u++)
unibuf[u] = grub_le_to_cpu16 (unibuf[u]);
*grub_utf16_to_utf8 ((grub_uint8_t *) filename, unibuf,
slots * 13) = '\0';
if (*dirname == '\0' && call_hook)
{
if (hook (filename, dir.attr & GRUB_FAT_ATTR_DIRECTORY))
break;
checksum = -1;
continue;
}
if (grub_strcmp (dirname, filename) == 0)
{
if (call_hook)
hook (filename, dir.attr & GRUB_FAT_ATTR_DIRECTORY);
break;
}
}
checksum = -1;
}
/* Convert the 8.3 file name. */
filep = filename;
for (i = 0; i < 8 && dir.name[i] && ! grub_isspace (dir.name[i]); i++)
*filep++ = grub_tolower (dir.name[i]);
*filep = '.';
for (i = 8; i < 11 && dir.name[i] && ! grub_isspace (dir.name[i]); i++)
*++filep = grub_tolower (dir.name[i]);
if (*filep != '.')
filep++;
*filep = '\0';
if (*dirname == '\0' && call_hook)
{
if (hook (filename, dir.attr & GRUB_FAT_ATTR_DIRECTORY))
break;
}
else if (grub_strcmp (dirname, filename) == 0)
{
if (call_hook)
hook (filename, dir.attr & GRUB_FAT_ATTR_DIRECTORY);
break;
}
}
grub_free (filename);
grub_free (dirname);
data->attr = dir.attr;
data->file_size = grub_le_to_cpu32 (dir.file_size);
data->file_cluster = ((grub_le_to_cpu16 (dir.first_cluster_high) << 16)
| grub_le_to_cpu16 (dir.first_cluster_low));
data->cur_cluster_num = ~0UL;
return dirp;
}
static grub_err_t
grub_fat_dir (grub_device_t device, const char *path,
int (*hook) (const char *filename, int dir))
{
struct grub_fat_data *data = 0;
grub_disk_t disk = device->disk;
grub_size_t len;
char *dirname = 0;
char *p;
#ifndef GRUB_UTIL
grub_dl_ref (my_mod);
#endif
data = grub_fat_mount (disk);
if (! data)
goto fail;
/* Make sure that DIRNAME terminates with '/'. */
len = grub_strlen (path);
dirname = grub_malloc (len + 1 + 1);
if (! dirname)
goto fail;
grub_memcpy (dirname, path, len);
p = dirname + len;
if (path[len - 1] != '/')
*p++ = '/';
*p = '\0';
p = dirname;
do
{
p = grub_fat_find_dir (disk, data, p, hook);
}
while (p && grub_errno == GRUB_ERR_NONE);
fail:
grub_free (dirname);
grub_free (data);
#ifndef GRUB_UTIL
grub_dl_unref (my_mod);
#endif
return grub_errno;
}
static grub_err_t
grub_fat_open (grub_file_t file, const char *name)
{
struct grub_fat_data *data = 0;
char *p = (char *) name;
#ifndef GRUB_UTIL
grub_dl_ref (my_mod);
#endif
data = grub_fat_mount (file->device->disk);
if (! data)
goto fail;
do
{
p = grub_fat_find_dir (file->device->disk, data, p, 0);
if (grub_errno != GRUB_ERR_NONE)
goto fail;
}
while (p);
if (data->attr & GRUB_FAT_ATTR_DIRECTORY)
{
grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a file");
goto fail;
}
file->data = data;
file->size = data->file_size;
return GRUB_ERR_NONE;
fail:
grub_free (data);
#ifndef GRUB_UTIL
grub_dl_unref (my_mod);
#endif
return grub_errno;
}
static grub_ssize_t
grub_fat_read (grub_file_t file, char *buf, grub_size_t len)
{
return grub_fat_read_data (file->device->disk, file->data, file->read_hook,
file->offset, len, buf);
}
static grub_err_t
grub_fat_close (grub_file_t file)
{
grub_free (file->data);
#ifndef GRUB_UTIL
grub_dl_unref (my_mod);
#endif
return grub_errno;
}
static grub_err_t
grub_fat_label (grub_device_t device, char **label)
{
struct grub_fat_data *data;
grub_disk_t disk = device->disk;
grub_ssize_t offset = -sizeof(struct grub_fat_dir_entry);
#ifndef GRUB_UTIL
grub_dl_ref (my_mod);
#endif
data = grub_fat_mount (disk);
if (! data)
goto fail;
if (! (data->attr & GRUB_FAT_ATTR_DIRECTORY))
{
grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory");
return 0;
}
while (1)
{
struct grub_fat_dir_entry dir;
/* Adjust the offset. */
offset += sizeof (dir);
/* Read a directory entry. */
if ((grub_fat_read_data (disk, data, 0,
offset, sizeof (dir), (char *) &dir)
!= sizeof (dir))
|| dir.name[0] == 0)
{
if (grub_errno != GRUB_ERR_NONE)
goto fail;
else
{
*label = 0;
return GRUB_ERR_NONE;
}
}
if (dir.attr == GRUB_FAT_ATTR_VOLUME_ID)
{
*label = grub_strndup ((char *) dir.name, 11);
return GRUB_ERR_NONE;
}
}
*label = 0;
fail:
#ifndef GRUB_UTIL
grub_dl_unref (my_mod);
#endif
grub_free (data);
return grub_errno;
}
static struct grub_fs grub_fat_fs =
{
.name = "fat",
.dir = grub_fat_dir,
.open = grub_fat_open,
.read = grub_fat_read,
.close = grub_fat_close,
.label = grub_fat_label,
.next = 0
};
GRUB_MOD_INIT(fat)
{
grub_fs_register (&grub_fat_fs);
#ifndef GRUB_UTIL
my_mod = mod;
#endif
}
GRUB_MOD_FINI(fat)
{
grub_fs_unregister (&grub_fat_fs);
}