| /* minix.c - The minix filesystem, version 1 and 2. */ |
| /* |
| * GRUB -- GRand Unified Bootloader |
| * Copyright (C) 2004,2005,2006,2007,2008 Free Software Foundation, Inc. |
| * |
| * GRUB 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 3 of the License, or |
| * (at your option) any later version. |
| * |
| * GRUB 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 GRUB. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include <grub/err.h> |
| #include <grub/file.h> |
| #include <grub/mm.h> |
| #include <grub/misc.h> |
| #include <grub/disk.h> |
| #include <grub/dl.h> |
| #include <grub/types.h> |
| |
| #define GRUB_MINIX_MAGIC 0x137F |
| #define GRUB_MINIX2_MAGIC 0x2468 |
| #define GRUB_MINIX_MAGIC_30 0x138F |
| #define GRUB_MINIX2_MAGIC_30 0x2478 |
| #define GRUB_MINIX_BSIZE 1024U |
| #define GRUB_MINIX_LOG2_BSIZE 1 |
| #define GRUB_MINIX_ROOT_INODE 1 |
| #define GRUB_MINIX_MAX_SYMLNK_CNT 8 |
| #define GRUB_MINIX_SBLOCK 2 |
| |
| #define GRUB_MINIX_IFDIR 0040000U |
| #define GRUB_MINIX_IFLNK 0120000U |
| |
| #define GRUB_MINIX_INODE(data,field) (data->version == 1 ? \ |
| data->inode. field : data->inode2. field) |
| #define GRUB_MINIX_INODE_ENDIAN(data,field,bits1,bits2) (data->version == 1 ? \ |
| grub_le_to_cpu##bits1 (data->inode.field) : \ |
| grub_le_to_cpu##bits2 (data->inode2.field)) |
| #define GRUB_MINIX_INODE_SIZE(data) GRUB_MINIX_INODE_ENDIAN (data,size,16,32) |
| #define GRUB_MINIX_INODE_MODE(data) GRUB_MINIX_INODE_ENDIAN (data,mode,16,16) |
| #define GRUB_MINIX_INODE_DIR_ZONES(data,blk) GRUB_MINIX_INODE_ENDIAN \ |
| (data,dir_zones[blk],16,32) |
| #define GRUB_MINIX_INODE_INDIR_ZONE(data) \ |
| GRUB_MINIX_INODE_ENDIAN (data,indir_zone,16,32) |
| #define GRUB_MINIX_INODE_DINDIR_ZONE(data) \ |
| GRUB_MINIX_INODE_ENDIAN (data,double_indir_zone,16,32) |
| #define GRUB_MINIX_INODE_BLKSZ(data) (data->version == 1 ? 2 : 4) |
| #define GRUB_MINIX_LOG2_ZONESZ (GRUB_MINIX_LOG2_BSIZE \ |
| + grub_le_to_cpu16 (sblock->log2_zone_size)) |
| #define GRUB_MINIX_ZONESZ (GRUB_MINIX_BSIZE \ |
| << grub_le_to_cpu16 (sblock->log2_zone_size)) |
| |
| struct grub_minix_sblock |
| { |
| grub_uint16_t inode_cnt; |
| grub_uint16_t zone_cnt; |
| grub_uint16_t inode_bmap_size; |
| grub_uint16_t zone_bmap_size; |
| grub_uint16_t first_data_zone; |
| grub_uint16_t log2_zone_size; |
| grub_uint32_t max_file_size; |
| grub_uint16_t magic; |
| }; |
| |
| struct grub_minix_inode |
| { |
| grub_uint16_t mode; |
| grub_uint16_t uid; |
| grub_uint16_t size; |
| grub_uint32_t ctime; |
| grub_uint8_t gid; |
| grub_uint8_t nlinks; |
| grub_uint16_t dir_zones[7]; |
| grub_uint16_t indir_zone; |
| grub_uint16_t double_indir_zone; |
| }; |
| |
| struct grub_minix2_inode |
| { |
| grub_uint16_t mode; |
| grub_uint16_t nlinks; |
| grub_uint16_t uid; |
| grub_uint16_t gid; |
| grub_uint32_t size; |
| grub_uint32_t atime; |
| grub_uint32_t mtime; |
| grub_uint32_t ctime; |
| grub_uint32_t dir_zones[7]; |
| grub_uint32_t indir_zone; |
| grub_uint32_t double_indir_zone; |
| grub_uint32_t unused; |
| |
| }; |
| |
| /* Information about a "mounted" minix filesystem. */ |
| struct grub_minix_data |
| { |
| struct grub_minix_sblock sblock; |
| struct grub_minix_inode inode; |
| struct grub_minix2_inode inode2; |
| int ino; |
| int linknest; |
| grub_disk_t disk; |
| int version; |
| int filename_size; |
| }; |
| |
| static grub_dl_t my_mod; |
| |
| static grub_err_t grub_minix_find_file (struct grub_minix_data *data, |
| const char *path); |
| |
| static int |
| grub_minix_get_file_block (struct grub_minix_data *data, unsigned int blk) |
| { |
| struct grub_minix_sblock *sblock = &data->sblock; |
| int indir; |
| |
| auto int grub_get_indir (int, int); |
| |
| /* Read the block pointer in ZONE, on the offset NUM. */ |
| int grub_get_indir (int zone, int num) |
| { |
| if (data->version == 1) |
| { |
| grub_uint16_t indir16; |
| grub_disk_read (data->disk, |
| zone << GRUB_MINIX_LOG2_ZONESZ, |
| sizeof (grub_uint16_t) * num, |
| sizeof (grub_uint16_t), (char *) &indir16); |
| return grub_le_to_cpu16 (indir16); |
| } |
| else |
| { |
| grub_uint32_t indir32; |
| grub_disk_read (data->disk, |
| zone << GRUB_MINIX_LOG2_ZONESZ, |
| sizeof (grub_uint32_t) * num, |
| sizeof (grub_uint32_t), (char *) &indir32); |
| return grub_le_to_cpu32 (indir32); |
| } |
| } |
| |
| /* Direct block. */ |
| if (blk < 7) |
| return GRUB_MINIX_INODE_DIR_ZONES (data, blk); |
| |
| /* Indirect block. */ |
| blk -= 7; |
| if (blk < GRUB_MINIX_ZONESZ / GRUB_MINIX_INODE_BLKSZ (data)) |
| { |
| indir = grub_get_indir (GRUB_MINIX_INODE_INDIR_ZONE (data), blk); |
| return indir; |
| } |
| |
| /* Double indirect block. */ |
| blk -= GRUB_MINIX_ZONESZ / GRUB_MINIX_INODE_BLKSZ (data); |
| if (blk < (GRUB_MINIX_ZONESZ / GRUB_MINIX_INODE_BLKSZ (data)) |
| * (GRUB_MINIX_ZONESZ / GRUB_MINIX_INODE_BLKSZ (data))) |
| { |
| indir = grub_get_indir (GRUB_MINIX_INODE_DINDIR_ZONE (data), |
| blk / GRUB_MINIX_ZONESZ); |
| |
| indir = grub_get_indir (indir, blk % GRUB_MINIX_ZONESZ); |
| |
| return indir; |
| } |
| |
| /* This should never happen. */ |
| grub_error (GRUB_ERR_OUT_OF_RANGE, "file bigger than maximum size"); |
| |
| return 0; |
| } |
| |
| |
| /* Read LEN bytes from the file described by DATA starting with byte |
| POS. Return the amount of read bytes in READ. */ |
| static grub_ssize_t |
| grub_minix_read_file (struct grub_minix_data *data, |
| void NESTED_FUNC_ATTR (*read_hook) (grub_disk_addr_t sector, |
| unsigned offset, unsigned length), |
| int pos, grub_disk_addr_t len, char *buf) |
| { |
| struct grub_minix_sblock *sblock = &data->sblock; |
| int i; |
| int blockcnt; |
| |
| /* Adjust len so it we can't read past the end of the file. */ |
| if (len + pos > GRUB_MINIX_INODE_SIZE (data)) |
| len = GRUB_MINIX_INODE_SIZE (data) - pos; |
| |
| blockcnt = (len + pos + GRUB_MINIX_BSIZE - 1) / GRUB_MINIX_BSIZE; |
| |
| for (i = pos / GRUB_MINIX_BSIZE; i < blockcnt; i++) |
| { |
| int blknr; |
| int blockoff = pos % GRUB_MINIX_BSIZE; |
| int blockend = GRUB_MINIX_BSIZE; |
| |
| int skipfirst = 0; |
| |
| blknr = grub_minix_get_file_block (data, i); |
| if (grub_errno) |
| return -1; |
| |
| /* Last block. */ |
| if (i == blockcnt - 1) |
| { |
| blockend = (len + pos) % GRUB_MINIX_BSIZE; |
| |
| if (!blockend) |
| blockend = GRUB_MINIX_BSIZE; |
| } |
| |
| /* First block. */ |
| if (i == (pos / (int) GRUB_MINIX_BSIZE)) |
| { |
| skipfirst = blockoff; |
| blockend -= skipfirst; |
| } |
| |
| data->disk->read_hook = read_hook; |
| grub_disk_read (data->disk, blknr << GRUB_MINIX_LOG2_ZONESZ, |
| skipfirst, blockend, buf); |
| |
| data->disk->read_hook = 0; |
| if (grub_errno) |
| return -1; |
| |
| buf += GRUB_MINIX_BSIZE - skipfirst; |
| } |
| |
| return len; |
| } |
| |
| |
| /* Read inode INO from the mounted filesystem described by DATA. This |
| inode is used by default now. */ |
| static grub_err_t |
| grub_minix_read_inode (struct grub_minix_data *data, int ino) |
| { |
| struct grub_minix_sblock *sblock = &data->sblock; |
| |
| /* Block in which the inode is stored. */ |
| int block; |
| data->ino = ino; |
| |
| /* The first inode in minix is inode 1. */ |
| ino--; |
| |
| block = ((2 + grub_le_to_cpu16 (sblock->inode_bmap_size) |
| + grub_le_to_cpu16 (sblock->zone_bmap_size)) |
| << GRUB_MINIX_LOG2_BSIZE); |
| |
| if (data->version == 1) |
| { |
| block += ino / (GRUB_DISK_SECTOR_SIZE / sizeof (struct grub_minix_inode)); |
| int offs = (ino % (GRUB_DISK_SECTOR_SIZE |
| / sizeof (struct grub_minix_inode)) |
| * sizeof (struct grub_minix_inode)); |
| |
| grub_disk_read (data->disk, block, offs, |
| sizeof (struct grub_minix_inode), &data->inode); |
| } |
| else |
| { |
| block += ino / (GRUB_DISK_SECTOR_SIZE |
| / sizeof (struct grub_minix2_inode)); |
| int offs = (ino |
| % (GRUB_DISK_SECTOR_SIZE / sizeof (struct grub_minix2_inode)) |
| * sizeof (struct grub_minix2_inode)); |
| |
| grub_disk_read (data->disk, block, offs, |
| sizeof (struct grub_minix2_inode),&data->inode2); |
| } |
| |
| return GRUB_ERR_NONE; |
| } |
| |
| |
| /* Lookup the symlink the current inode points to. INO is the inode |
| number of the directory the symlink is relative to. */ |
| static grub_err_t |
| grub_minix_lookup_symlink (struct grub_minix_data *data, int ino) |
| { |
| char symlink[GRUB_MINIX_INODE_SIZE (data) + 1]; |
| |
| if (++data->linknest > GRUB_MINIX_MAX_SYMLNK_CNT) |
| return grub_error (GRUB_ERR_SYMLINK_LOOP, "too deep nesting of symlinks"); |
| |
| if (grub_minix_read_file (data, 0, 0, |
| GRUB_MINIX_INODE_SIZE (data), symlink) < 0) |
| return grub_errno; |
| |
| symlink[GRUB_MINIX_INODE_SIZE (data)] = '\0'; |
| |
| /* The symlink is an absolute path, go back to the root inode. */ |
| if (symlink[0] == '/') |
| ino = GRUB_MINIX_ROOT_INODE; |
| |
| /* Now load in the old inode. */ |
| if (grub_minix_read_inode (data, ino)) |
| return grub_errno; |
| |
| grub_minix_find_file (data, symlink); |
| if (grub_errno) |
| grub_error (grub_errno, "Can not follow symlink `%s'.", symlink); |
| |
| return grub_errno; |
| } |
| |
| |
| /* Find the file with the pathname PATH on the filesystem described by |
| DATA. */ |
| static grub_err_t |
| grub_minix_find_file (struct grub_minix_data *data, const char *path) |
| { |
| char fpath[grub_strlen (path) + 1]; |
| char *name = fpath; |
| char *next; |
| unsigned int pos = 0; |
| int dirino; |
| |
| grub_strcpy (fpath, path); |
| |
| /* Skip the first slash. */ |
| if (name[0] == '/') |
| { |
| name++; |
| if (!*name) |
| return 0; |
| } |
| |
| /* Extract the actual part from the pathname. */ |
| next = grub_strchr (name, '/'); |
| if (next) |
| { |
| next[0] = '\0'; |
| next++; |
| } |
| |
| do |
| { |
| grub_uint16_t ino; |
| char filename[data->filename_size + 1]; |
| |
| if (grub_strlen (name) == 0) |
| return GRUB_ERR_NONE; |
| |
| if (grub_minix_read_file (data, 0, pos, sizeof (ino), |
| (char *) &ino) < 0) |
| return grub_errno; |
| if (grub_minix_read_file (data, 0, pos + sizeof (ino), |
| data->filename_size, (char *) filename)< 0) |
| return grub_errno; |
| |
| filename[data->filename_size] = '\0'; |
| |
| /* Check if the current direntry matches the current part of the |
| pathname. */ |
| if (!grub_strcmp (name, filename)) |
| { |
| dirino = data->ino; |
| grub_minix_read_inode (data, grub_le_to_cpu16 (ino)); |
| |
| /* Follow the symlink. */ |
| if ((GRUB_MINIX_INODE_MODE (data) |
| & GRUB_MINIX_IFLNK) == GRUB_MINIX_IFLNK) |
| { |
| grub_minix_lookup_symlink (data, dirino); |
| if (grub_errno) |
| return grub_errno; |
| } |
| |
| if (!next) |
| return 0; |
| |
| pos = 0; |
| |
| name = next; |
| next = grub_strchr (name, '/'); |
| if (next) |
| { |
| next[0] = '\0'; |
| next++; |
| } |
| |
| if ((GRUB_MINIX_INODE_MODE (data) |
| & GRUB_MINIX_IFDIR) != GRUB_MINIX_IFDIR) |
| return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory"); |
| |
| continue; |
| } |
| |
| pos += sizeof (ino) + data->filename_size; |
| } while (pos < GRUB_MINIX_INODE_SIZE (data)); |
| |
| grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found"); |
| return grub_errno; |
| } |
| |
| |
| /* Mount the filesystem on the disk DISK. */ |
| static struct grub_minix_data * |
| grub_minix_mount (grub_disk_t disk) |
| { |
| struct grub_minix_data *data; |
| |
| data = grub_malloc (sizeof (struct grub_minix_data)); |
| if (!data) |
| return 0; |
| |
| /* Read the superblock. */ |
| grub_disk_read (disk, GRUB_MINIX_SBLOCK, 0, |
| sizeof (struct grub_minix_sblock),&data->sblock); |
| if (grub_errno) |
| goto fail; |
| |
| if (grub_le_to_cpu16 (data->sblock.magic) == GRUB_MINIX_MAGIC) |
| { |
| data->version = 1; |
| data->filename_size = 14; |
| } |
| else if (grub_le_to_cpu16 (data->sblock.magic) == GRUB_MINIX2_MAGIC) |
| { |
| data->version = 2; |
| data->filename_size = 14; |
| } |
| else if (grub_le_to_cpu16 (data->sblock.magic) == GRUB_MINIX_MAGIC_30) |
| { |
| data->version = 1; |
| data->filename_size = 30; |
| } |
| else if (grub_le_to_cpu16 (data->sblock.magic) == GRUB_MINIX2_MAGIC_30) |
| { |
| data->version = 2; |
| data->filename_size = 30; |
| } |
| else |
| goto fail; |
| |
| data->disk = disk; |
| data->linknest = 0; |
| |
| return data; |
| |
| fail: |
| grub_free (data); |
| grub_error (GRUB_ERR_BAD_FS, "not a minix filesystem"); |
| return 0; |
| } |
| |
| static grub_err_t |
| grub_minix_dir (grub_device_t device, const char *path, |
| int (*hook) (const char *filename, |
| const struct grub_dirhook_info *info)) |
| { |
| struct grub_minix_data *data = 0; |
| struct grub_minix_sblock *sblock; |
| unsigned int pos = 0; |
| |
| data = grub_minix_mount (device->disk); |
| if (!data) |
| return grub_errno; |
| |
| grub_minix_read_inode (data, GRUB_MINIX_ROOT_INODE); |
| if (grub_errno) |
| goto fail; |
| |
| sblock = &data->sblock; |
| |
| grub_minix_find_file (data, path); |
| if (grub_errno) |
| goto fail; |
| |
| if ((GRUB_MINIX_INODE_MODE (data) & GRUB_MINIX_IFDIR) != GRUB_MINIX_IFDIR) |
| { |
| grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory"); |
| goto fail; |
| } |
| |
| while (pos < GRUB_MINIX_INODE_SIZE (data)) |
| { |
| grub_uint16_t ino; |
| char filename[data->filename_size + 1]; |
| int dirino = data->ino; |
| struct grub_dirhook_info info; |
| grub_memset (&info, 0, sizeof (info)); |
| |
| |
| if (grub_minix_read_file (data, 0, pos, sizeof (ino), |
| (char *) &ino) < 0) |
| return grub_errno; |
| |
| if (grub_minix_read_file (data, 0, pos + sizeof (ino), |
| data->filename_size, |
| (char *) filename) < 0) |
| return grub_errno; |
| filename[data->filename_size] = '\0'; |
| |
| /* The filetype is not stored in the dirent. Read the inode to |
| find out the filetype. This *REALLY* sucks. */ |
| grub_minix_read_inode (data, grub_le_to_cpu16 (ino)); |
| info.dir = ((GRUB_MINIX_INODE_MODE (data) |
| & GRUB_MINIX_IFDIR) == GRUB_MINIX_IFDIR); |
| if (hook (filename, &info) ? 1 : 0) |
| break; |
| |
| /* Load the old inode back in. */ |
| grub_minix_read_inode (data, dirino); |
| |
| pos += sizeof (ino) + data->filename_size; |
| } |
| |
| fail: |
| grub_free (data); |
| return grub_errno; |
| } |
| |
| |
| /* Open a file named NAME and initialize FILE. */ |
| static grub_err_t |
| grub_minix_open (struct grub_file *file, const char *name) |
| { |
| struct grub_minix_data *data; |
| data = grub_minix_mount (file->device->disk); |
| if (!data) |
| return grub_errno; |
| |
| /* Open the inode op the root directory. */ |
| grub_minix_read_inode (data, GRUB_MINIX_ROOT_INODE); |
| if (grub_errno) |
| { |
| grub_free (data); |
| return grub_errno; |
| } |
| |
| if (!name || name[0] != '/') |
| { |
| grub_error (GRUB_ERR_BAD_FILENAME, "bad filename"); |
| return grub_errno; |
| } |
| |
| /* Traverse the directory tree to the node that should be |
| opened. */ |
| grub_minix_find_file (data, name); |
| if (grub_errno) |
| { |
| grub_free (data); |
| return grub_errno; |
| } |
| |
| file->data = data; |
| file->size = GRUB_MINIX_INODE_SIZE (data); |
| |
| return GRUB_ERR_NONE; |
| } |
| |
| |
| static grub_ssize_t |
| grub_minix_read (grub_file_t file, char *buf, grub_size_t len) |
| { |
| struct grub_minix_data *data = |
| (struct grub_minix_data *) file->data; |
| |
| return grub_minix_read_file (data, file->read_hook, file->offset, len, buf); |
| } |
| |
| |
| static grub_err_t |
| grub_minix_close (grub_file_t file) |
| { |
| grub_free (file->data); |
| |
| return GRUB_ERR_NONE; |
| } |
| |
| |
| static grub_err_t |
| grub_minix_label (grub_device_t device __attribute ((unused)), |
| char **label __attribute ((unused))) |
| { |
| return GRUB_ERR_NONE; |
| } |
| |
| |
| static struct grub_fs grub_minix_fs = |
| { |
| .name = "minix", |
| .dir = grub_minix_dir, |
| .open = grub_minix_open, |
| .read = grub_minix_read, |
| .close = grub_minix_close, |
| .label = grub_minix_label, |
| .next = 0 |
| }; |
| |
| GRUB_MOD_INIT(minix) |
| { |
| grub_fs_register (&grub_minix_fs); |
| my_mod = mod; |
| } |
| |
| GRUB_MOD_FINI(minix) |
| { |
| grub_fs_unregister (&grub_minix_fs); |
| } |