478 lines
11 KiB
C
478 lines
11 KiB
C
|
/*
|
||
|
* GRUB -- GRand Unified Bootloader
|
||
|
* Copyright (C) 2000, 2001 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., 51 Franklin Street - Fifth Floor, Boston,
|
||
|
* MA 02110-1301, USA.
|
||
|
*/
|
||
|
|
||
|
#ifdef FSYS_FAT
|
||
|
|
||
|
#include "shared.h"
|
||
|
#include "filesys.h"
|
||
|
#include "fat.h"
|
||
|
|
||
|
struct fat_superblock
|
||
|
{
|
||
|
int fat_offset;
|
||
|
int fat_length;
|
||
|
int fat_size;
|
||
|
int root_offset;
|
||
|
int root_max;
|
||
|
int data_offset;
|
||
|
|
||
|
int num_sectors;
|
||
|
int num_clust;
|
||
|
int clust_eof_marker;
|
||
|
int sects_per_clust;
|
||
|
int sectsize_bits;
|
||
|
int clustsize_bits;
|
||
|
int root_cluster;
|
||
|
|
||
|
int cached_fat;
|
||
|
int file_cluster;
|
||
|
int current_cluster_num;
|
||
|
int current_cluster;
|
||
|
};
|
||
|
|
||
|
/* pointer(s) into filesystem info buffer for DOS stuff */
|
||
|
#define FAT_SUPER ( (struct fat_superblock *) \
|
||
|
( FSYS_BUF + 32256) )/* 512 bytes long */
|
||
|
#define FAT_BUF ( FSYS_BUF + 30208 ) /* 4 sector FAT buffer */
|
||
|
#define NAME_BUF ( FSYS_BUF + 29184 ) /* Filename buffer (833 bytes) */
|
||
|
|
||
|
#define FAT_CACHE_SIZE 2048
|
||
|
|
||
|
int
|
||
|
fat_mount (void)
|
||
|
{
|
||
|
struct fat_bpb bpb;
|
||
|
__u32 magic, first_fat;
|
||
|
|
||
|
/* Check partition type for harddisk */
|
||
|
if (((current_drive & 0x80) || (current_slice != 0))
|
||
|
&& ! IS_PC_SLICE_TYPE_FAT (current_slice)
|
||
|
&& (! IS_PC_SLICE_TYPE_BSD_WITH_FS (current_slice, FS_MSDOS)))
|
||
|
return 0;
|
||
|
|
||
|
/* Read bpb */
|
||
|
if (! devread (0, 0, sizeof (bpb), (char *) &bpb))
|
||
|
return 0;
|
||
|
|
||
|
/* Check if the number of sectors per cluster is zero here, to avoid
|
||
|
zero division. */
|
||
|
if (bpb.sects_per_clust == 0)
|
||
|
return 0;
|
||
|
|
||
|
FAT_SUPER->sectsize_bits = log2 (bpb.bytes_per_sect);
|
||
|
FAT_SUPER->clustsize_bits
|
||
|
= FAT_SUPER->sectsize_bits + log2 (bpb.sects_per_clust);
|
||
|
|
||
|
/* Fill in info about super block */
|
||
|
FAT_SUPER->num_sectors = bpb.short_sectors
|
||
|
? bpb.short_sectors : bpb.long_sectors;
|
||
|
|
||
|
/* FAT offset and length */
|
||
|
FAT_SUPER->fat_offset = bpb.reserved_sects;
|
||
|
FAT_SUPER->fat_length =
|
||
|
bpb.fat_length ? bpb.fat_length : bpb.fat32_length;
|
||
|
|
||
|
/* Rootdir offset and length for FAT12/16 */
|
||
|
FAT_SUPER->root_offset =
|
||
|
FAT_SUPER->fat_offset + bpb.num_fats * FAT_SUPER->fat_length;
|
||
|
FAT_SUPER->root_max = FAT_DIRENTRY_LENGTH * bpb.dir_entries;
|
||
|
|
||
|
/* Data offset and number of clusters */
|
||
|
FAT_SUPER->data_offset =
|
||
|
FAT_SUPER->root_offset
|
||
|
+ ((FAT_SUPER->root_max - 1) >> FAT_SUPER->sectsize_bits) + 1;
|
||
|
FAT_SUPER->num_clust =
|
||
|
2 + ((FAT_SUPER->num_sectors - FAT_SUPER->data_offset)
|
||
|
/ bpb.sects_per_clust);
|
||
|
FAT_SUPER->sects_per_clust = bpb.sects_per_clust;
|
||
|
|
||
|
if (!bpb.fat_length)
|
||
|
{
|
||
|
/* This is a FAT32 */
|
||
|
if (bpb.dir_entries)
|
||
|
return 0;
|
||
|
|
||
|
if (bpb.flags & 0x0080)
|
||
|
{
|
||
|
/* FAT mirroring is disabled, get active FAT */
|
||
|
int active_fat = bpb.flags & 0x000f;
|
||
|
if (active_fat >= bpb.num_fats)
|
||
|
return 0;
|
||
|
FAT_SUPER->fat_offset += active_fat * FAT_SUPER->fat_length;
|
||
|
}
|
||
|
|
||
|
FAT_SUPER->fat_size = 8;
|
||
|
FAT_SUPER->root_cluster = bpb.root_cluster;
|
||
|
|
||
|
/* Yes the following is correct. FAT32 should be called FAT28 :) */
|
||
|
FAT_SUPER->clust_eof_marker = 0xffffff8;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (!FAT_SUPER->root_max)
|
||
|
return 0;
|
||
|
|
||
|
FAT_SUPER->root_cluster = -1;
|
||
|
if (FAT_SUPER->num_clust > FAT_MAX_12BIT_CLUST)
|
||
|
{
|
||
|
FAT_SUPER->fat_size = 4;
|
||
|
FAT_SUPER->clust_eof_marker = 0xfff8;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
FAT_SUPER->fat_size = 3;
|
||
|
FAT_SUPER->clust_eof_marker = 0xff8;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Now do some sanity checks */
|
||
|
|
||
|
if (bpb.bytes_per_sect != (1 << FAT_SUPER->sectsize_bits)
|
||
|
|| bpb.bytes_per_sect != SECTOR_SIZE
|
||
|
|| bpb.sects_per_clust != (1 << (FAT_SUPER->clustsize_bits
|
||
|
- FAT_SUPER->sectsize_bits))
|
||
|
|| FAT_SUPER->num_clust <= 2
|
||
|
|| (FAT_SUPER->fat_size * FAT_SUPER->num_clust / (2 * SECTOR_SIZE)
|
||
|
> FAT_SUPER->fat_length))
|
||
|
return 0;
|
||
|
|
||
|
/* kbs: Media check on first FAT entry [ported from PUPA] */
|
||
|
|
||
|
if (!devread(FAT_SUPER->fat_offset, 0,
|
||
|
sizeof(first_fat), (char *)&first_fat))
|
||
|
return 0;
|
||
|
|
||
|
if (FAT_SUPER->fat_size == 8)
|
||
|
{
|
||
|
first_fat &= 0x0fffffff;
|
||
|
magic = 0x0fffff00;
|
||
|
}
|
||
|
else if (FAT_SUPER->fat_size == 4)
|
||
|
{
|
||
|
first_fat &= 0x0000ffff;
|
||
|
magic = 0xff00;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
first_fat &= 0x00000fff;
|
||
|
magic = 0x0f00;
|
||
|
}
|
||
|
|
||
|
if (first_fat != (magic | bpb.media))
|
||
|
return 0;
|
||
|
|
||
|
FAT_SUPER->cached_fat = - 2 * FAT_CACHE_SIZE;
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
fat_read (char *buf, int len)
|
||
|
{
|
||
|
int logical_clust;
|
||
|
int offset;
|
||
|
int ret = 0;
|
||
|
int size;
|
||
|
|
||
|
if (FAT_SUPER->file_cluster < 0)
|
||
|
{
|
||
|
/* root directory for fat16 */
|
||
|
size = FAT_SUPER->root_max - filepos;
|
||
|
if (size > len)
|
||
|
size = len;
|
||
|
if (!devread(FAT_SUPER->root_offset, filepos, size, buf))
|
||
|
return 0;
|
||
|
filepos += size;
|
||
|
return size;
|
||
|
}
|
||
|
|
||
|
logical_clust = filepos >> FAT_SUPER->clustsize_bits;
|
||
|
offset = (filepos & ((1 << FAT_SUPER->clustsize_bits) - 1));
|
||
|
if (logical_clust < FAT_SUPER->current_cluster_num)
|
||
|
{
|
||
|
FAT_SUPER->current_cluster_num = 0;
|
||
|
FAT_SUPER->current_cluster = FAT_SUPER->file_cluster;
|
||
|
}
|
||
|
|
||
|
while (len > 0)
|
||
|
{
|
||
|
int sector;
|
||
|
while (logical_clust > FAT_SUPER->current_cluster_num)
|
||
|
{
|
||
|
/* calculate next cluster */
|
||
|
int fat_entry =
|
||
|
FAT_SUPER->current_cluster * FAT_SUPER->fat_size;
|
||
|
int next_cluster;
|
||
|
int cached_pos = (fat_entry - FAT_SUPER->cached_fat);
|
||
|
|
||
|
if (cached_pos < 0 ||
|
||
|
(cached_pos + FAT_SUPER->fat_size) > 2*FAT_CACHE_SIZE)
|
||
|
{
|
||
|
FAT_SUPER->cached_fat = (fat_entry & ~(2*SECTOR_SIZE - 1));
|
||
|
cached_pos = (fat_entry - FAT_SUPER->cached_fat);
|
||
|
sector = FAT_SUPER->fat_offset
|
||
|
+ FAT_SUPER->cached_fat / (2*SECTOR_SIZE);
|
||
|
if (!devread (sector, 0, FAT_CACHE_SIZE, (char*) FAT_BUF))
|
||
|
return 0;
|
||
|
}
|
||
|
next_cluster = * (unsigned long *) (FAT_BUF + (cached_pos >> 1));
|
||
|
if (FAT_SUPER->fat_size == 3)
|
||
|
{
|
||
|
if (cached_pos & 1)
|
||
|
next_cluster >>= 4;
|
||
|
next_cluster &= 0xFFF;
|
||
|
}
|
||
|
else if (FAT_SUPER->fat_size == 4)
|
||
|
next_cluster &= 0xFFFF;
|
||
|
|
||
|
if (next_cluster >= FAT_SUPER->clust_eof_marker)
|
||
|
return ret;
|
||
|
if (next_cluster < 2 || next_cluster >= FAT_SUPER->num_clust)
|
||
|
{
|
||
|
errnum = ERR_FSYS_CORRUPT;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
FAT_SUPER->current_cluster = next_cluster;
|
||
|
FAT_SUPER->current_cluster_num++;
|
||
|
}
|
||
|
|
||
|
sector = FAT_SUPER->data_offset +
|
||
|
((FAT_SUPER->current_cluster - 2) << (FAT_SUPER->clustsize_bits
|
||
|
- FAT_SUPER->sectsize_bits));
|
||
|
size = (1 << FAT_SUPER->clustsize_bits) - offset;
|
||
|
if (size > len)
|
||
|
size = len;
|
||
|
|
||
|
disk_read_func = disk_read_hook;
|
||
|
|
||
|
devread(sector, offset, size, buf);
|
||
|
|
||
|
disk_read_func = NULL;
|
||
|
|
||
|
len -= size;
|
||
|
buf += size;
|
||
|
ret += size;
|
||
|
filepos += size;
|
||
|
logical_clust++;
|
||
|
offset = 0;
|
||
|
}
|
||
|
return errnum ? 0 : ret;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
fat_dir (char *dirname)
|
||
|
{
|
||
|
char *rest, ch, dir_buf[FAT_DIRENTRY_LENGTH];
|
||
|
char *filename = (char *) NAME_BUF;
|
||
|
int attrib = FAT_ATTRIB_DIR;
|
||
|
#ifndef STAGE1_5
|
||
|
int do_possibilities = 0;
|
||
|
#endif
|
||
|
|
||
|
/* XXX I18N:
|
||
|
* the positions 2,4,6 etc are high bytes of a 16 bit unicode char
|
||
|
*/
|
||
|
static unsigned char longdir_pos[] =
|
||
|
{ 1, 3, 5, 7, 9, 14, 16, 18, 20, 22, 24, 28, 30 };
|
||
|
int slot = -2;
|
||
|
int alias_checksum = -1;
|
||
|
|
||
|
FAT_SUPER->file_cluster = FAT_SUPER->root_cluster;
|
||
|
filepos = 0;
|
||
|
FAT_SUPER->current_cluster_num = MAXINT;
|
||
|
|
||
|
/* main loop to find desired directory entry */
|
||
|
loop:
|
||
|
|
||
|
/* if we have a real file (and we're not just printing possibilities),
|
||
|
then this is where we want to exit */
|
||
|
|
||
|
if (!*dirname || isspace (*dirname))
|
||
|
{
|
||
|
if (attrib & FAT_ATTRIB_DIR)
|
||
|
{
|
||
|
errnum = ERR_BAD_FILETYPE;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
/* continue with the file/directory name interpretation */
|
||
|
|
||
|
while (*dirname == '/')
|
||
|
dirname++;
|
||
|
|
||
|
if (!(attrib & FAT_ATTRIB_DIR))
|
||
|
{
|
||
|
errnum = ERR_BAD_FILETYPE;
|
||
|
return 0;
|
||
|
}
|
||
|
/* Directories don't have a file size */
|
||
|
filemax = MAXINT;
|
||
|
|
||
|
for (rest = dirname; (ch = *rest) && !isspace (ch) && ch != '/'; rest++);
|
||
|
|
||
|
*rest = 0;
|
||
|
|
||
|
# ifndef STAGE1_5
|
||
|
if (print_possibilities && ch != '/')
|
||
|
do_possibilities = 1;
|
||
|
# endif
|
||
|
|
||
|
while (1)
|
||
|
{
|
||
|
if (fat_read (dir_buf, FAT_DIRENTRY_LENGTH) != FAT_DIRENTRY_LENGTH
|
||
|
|| dir_buf[0] == 0)
|
||
|
{
|
||
|
if (!errnum)
|
||
|
{
|
||
|
# ifndef STAGE1_5
|
||
|
if (print_possibilities < 0)
|
||
|
{
|
||
|
#if 0
|
||
|
putchar ('\n');
|
||
|
#endif
|
||
|
return 1;
|
||
|
}
|
||
|
# endif /* STAGE1_5 */
|
||
|
|
||
|
errnum = ERR_FILE_NOT_FOUND;
|
||
|
*rest = ch;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (FAT_DIRENTRY_ATTRIB (dir_buf) == FAT_ATTRIB_LONGNAME)
|
||
|
{
|
||
|
/* This is a long filename. The filename is build from back
|
||
|
* to front and may span multiple entries. To bind these
|
||
|
* entries together they all contain the same checksum over
|
||
|
* the short alias.
|
||
|
*
|
||
|
* The id field tells if this is the first entry (the last
|
||
|
* part) of the long filename, and also at which offset this
|
||
|
* belongs.
|
||
|
*
|
||
|
* We just write the part of the long filename this entry
|
||
|
* describes and continue with the next dir entry.
|
||
|
*/
|
||
|
int i, offset;
|
||
|
unsigned char id = FAT_LONGDIR_ID(dir_buf);
|
||
|
|
||
|
if ((id & 0x40))
|
||
|
{
|
||
|
id &= 0x3f;
|
||
|
slot = id;
|
||
|
filename[slot * 13] = 0;
|
||
|
alias_checksum = FAT_LONGDIR_ALIASCHECKSUM(dir_buf);
|
||
|
}
|
||
|
|
||
|
if (id != slot || slot == 0
|
||
|
|| alias_checksum != FAT_LONGDIR_ALIASCHECKSUM(dir_buf))
|
||
|
{
|
||
|
alias_checksum = -1;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
slot--;
|
||
|
offset = slot * 13;
|
||
|
|
||
|
for (i=0; i < 13; i++)
|
||
|
filename[offset+i] = dir_buf[longdir_pos[i]];
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (!FAT_DIRENTRY_VALID (dir_buf))
|
||
|
continue;
|
||
|
|
||
|
if (alias_checksum != -1 && slot == 0)
|
||
|
{
|
||
|
int i;
|
||
|
unsigned char sum;
|
||
|
|
||
|
slot = -2;
|
||
|
for (sum = 0, i = 0; i< 11; i++)
|
||
|
sum = ((sum >> 1) | (sum << 7)) + dir_buf[i];
|
||
|
|
||
|
if (sum == alias_checksum)
|
||
|
{
|
||
|
# ifndef STAGE1_5
|
||
|
if (do_possibilities)
|
||
|
goto print_filename;
|
||
|
# endif /* STAGE1_5 */
|
||
|
|
||
|
if (substring (dirname, filename) == 0)
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* XXX convert to 8.3 filename format here */
|
||
|
{
|
||
|
int i, j, c;
|
||
|
|
||
|
for (i = 0; i < 8 && (c = filename[i] = tolower (dir_buf[i]))
|
||
|
&& !isspace (c); i++);
|
||
|
|
||
|
filename[i++] = '.';
|
||
|
|
||
|
for (j = 0; j < 3 && (c = filename[i + j] = tolower (dir_buf[8 + j]))
|
||
|
&& !isspace (c); j++);
|
||
|
|
||
|
if (j == 0)
|
||
|
i--;
|
||
|
|
||
|
filename[i + j] = 0;
|
||
|
}
|
||
|
|
||
|
# ifndef STAGE1_5
|
||
|
if (do_possibilities)
|
||
|
{
|
||
|
print_filename:
|
||
|
if (substring (dirname, filename) <= 0)
|
||
|
{
|
||
|
if (print_possibilities > 0)
|
||
|
print_possibilities = -print_possibilities;
|
||
|
print_a_completion (filename);
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
# endif /* STAGE1_5 */
|
||
|
|
||
|
if (substring (dirname, filename) == 0)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
*(dirname = rest) = ch;
|
||
|
|
||
|
attrib = FAT_DIRENTRY_ATTRIB (dir_buf);
|
||
|
filemax = FAT_DIRENTRY_FILELENGTH (dir_buf);
|
||
|
filepos = 0;
|
||
|
FAT_SUPER->file_cluster = FAT_DIRENTRY_FIRST_CLUSTER (dir_buf);
|
||
|
FAT_SUPER->current_cluster_num = MAXINT;
|
||
|
|
||
|
/* go back to main loop at top of function */
|
||
|
goto loop;
|
||
|
}
|
||
|
|
||
|
#endif /* FSYS_FAT */
|