/* * Copyright (C) 2001-2003 Hewlett-Packard Co. * Contributed by Stephane Eranian * * This file is part of the ELILO, the EFI Linux boot loader. * * ELILO 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, or (at your option) * any later version. * * ELILO 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 ELILO; see the file COPYING. If not, write to the Free * Software Foundation, 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * * Please check out the elilo.txt for complete documentation on how * to use this program. * * The ext2 code in this file is derived from aboot-0.7 (the Linux/Alpha * bootloader) and credits are attributed to: * * This file has been ported from the DEC 32-bit Linux version * by David Mosberger (davidm@cs.arizona.edu). */ #include #include #include "elilo.h" #define FS_NAME L"ext2fs" #ifdef PATH_MAX #error You must have included some Linux header file by error #endif #define PATH_MAX 4095 #define EXT2FS_PATH_MAXLEN PATH_MAX #include "fs/ext2fs.h" #include "fs/ext2_private.h" /* * should we decide to spin off ext2, let's say to a boottime driver * we would have to change this */ #define EXT2FS_MEMTYPE EfiLoaderData /* ext2 file size is __u32 */ #define EXT2_FILESIZE_MAX (0x100000000UL) /* * Number of simultaneous open files. This needs to be high because * directories are kept open while traversing long path names. */ #define MAX_OPEN_FILES 32 typedef struct inode_table_entry { struct ext2_inode inode; int inumber; int free; unsigned short old_mode; UINT32 pos; /* current position in file ext2 uses __u32 !*/ } inode_entry_t; typedef struct { struct ext2_super_block sb; struct ext2_group_desc *gds; struct ext2_inode *root_inode; int ngroups; int directlim; /* Maximum direct blkno */ int ind1lim; /* Maximum single-indir blkno */ int ind2lim; /* Maximum double-indir blkno */ int ptrs_per_blk; /* ptrs/indirect block */ CHAR8 blkbuf[EXT2_MAX_BLOCK_SIZE]; int cached_iblkno; CHAR8 iblkbuf[EXT2_MAX_BLOCK_SIZE]; int cached_diblkno; CHAR8 diblkbuf[EXT2_MAX_BLOCK_SIZE]; long blocksize; inode_entry_t inode_table[MAX_OPEN_FILES]; /* fields added to fit the protocol construct */ EFI_BLOCK_IO *blkio; UINT32 mediaid; EFI_HANDLE dev; } ext2fs_priv_state_t; typedef union { ext2fs_interface_t pub_intf; struct { ext2fs_interface_t pub_intf; ext2fs_priv_state_t priv_data; } ext2fs_priv; } ext2fs_t; #define FS_PRIVATE(n) (&(((ext2fs_t *)n)->ext2fs_priv.priv_data)) typedef union { EFI_HANDLE *dev; ext2fs_t *intf; } dev_tab_t; static dev_tab_t *dev_tab; /* holds all devices we found */ static UINTN ndev; /* how many entries in dev_tab */ static EFI_GUID Ext2FsProtocol = EXT2FS_PROTOCOL; static INTN read_bytes(EFI_BLOCK_IO *blkio, UINT32 mediaid, UINTN offset, VOID *addr, UINTN size) { EFI_STATUS status; UINT8 *buffer; UINTN count, buffer_size; EFI_LBA base; INTN ret = EFI_INVALID_PARAMETER; base = offset / blkio->Media->BlockSize; count = (size + blkio->Media->BlockSize -1) / blkio->Media->BlockSize; buffer_size = count * blkio->Media->BlockSize; DBG_PRT((L"readblock(%x, %d,%d) base=%d count=%d", blkio, offset, size, base, count)); /* * slow but avoid large buffer on the stack */ buffer = (UINT8 *)alloc(buffer_size, EfiLoaderData); if (buffer == NULL) { ERR_PRT((L"cannot allocate ext2fs buffer size=%d", buffer_size)); return ret; } DBG_PRT((L"readblock(%x, %d, %d, %d, %x)", blkio, mediaid, base, buffer_size, buffer)); status = uefi_call_wrapper(blkio->ReadBlocks, 5, blkio, mediaid, base, buffer_size, buffer); if (EFI_ERROR(status)) { ERR_PRT((L"readblock(%d,%d)=%r", base, buffer_size, status)); goto error; } DBG_PRT((L"readblock(%d,%d)->%r", offset, buffer_size, status)); Memcpy(addr, buffer+(offset-(base*blkio->Media->BlockSize)), size); ret = 0; error: free(buffer); return ret; } /* * Read the specified inode from the disk and return it to the user. * Returns NULL if the inode can't be read... */ static struct ext2_inode * ext2_iget(ext2fs_priv_state_t *e2fs, int ino) { int i; struct ext2_inode *ip; struct inode_table_entry *itp = 0; int group; long offset; ip = 0; for (i = 0; i < MAX_OPEN_FILES; i++) { DBG_PRT((L"ext2_iget: looping, entry %d inode %d free %d", i, e2fs->inode_table[i].inumber, e2fs->inode_table[i].free)); if (e2fs->inode_table[i].free) { itp = &e2fs->inode_table[i]; ip = &itp->inode; break; } } if (!ip) { ERR_PRT((L"ext2_iget: no free inodes")); return NULL; } group = (ino-1) / e2fs->sb.s_inodes_per_group; DBG_PRT((L" itp-inode_table=%d bg_inode_table=%d group=%d ino=%d\n", (UINTN)(itp-e2fs->inode_table), (UINTN)(e2fs->gds[group].bg_inode_table), (UINTN)group, (UINTN)ino)); offset = ((long) e2fs->gds[group].bg_inode_table * e2fs->blocksize) + (((ino - 1) % EXT2_INODES_PER_GROUP(&e2fs->sb)) * EXT2_INODE_SIZE(&e2fs->sb)); DBG_PRT((L"ext2_iget: reading %d bytes at offset %d" " ((%d * %d) + ((%d) %% %d) * %d) " "(inode %d -> table %d)", sizeof(struct ext2_inode), (UINTN)offset, (UINTN)e2fs->gds[group].bg_inode_table, (UINTN)e2fs->blocksize, (UINTN)(ino - 1), (UINTN)EXT2_INODES_PER_GROUP(&e2fs->sb), EXT2_INODE_SIZE(&e2fs->sb), (UINTN)ino, (UINTN) (itp - e2fs->inode_table))); if (read_bytes(e2fs->blkio, e2fs->mediaid, offset, ip, sizeof(struct ext2_inode))) { ERR_PRT((L"ext2_iget: read error")); return NULL; } DBG_PRT((L"mode=%x uid=%d size=%d gid=%d links=%d flags=%d", (UINTN)ip->i_mode, (UINTN)ip->i_uid, (UINTN)ip->i_size, (UINTN)ip->i_gid, (UINTN)ip->i_flags)); itp->free = 0; itp->inumber = ino; itp->old_mode = ip->i_mode; return ip; } /* * Release our hold on an inode. Since this is a read-only application, * don't worry about putting back any changes... */ static void ext2_iput(ext2fs_priv_state_t *e2fs, struct ext2_inode *ip) { struct inode_table_entry *itp; /* Find and free the inode table slot we used... */ itp = (struct inode_table_entry *)ip; DBG_PRT((L"ext2_iput: inode %d table %d", itp->inumber, (int) (itp - e2fs->inode_table))); itp->inumber = 0; itp->free = 1; } /* * Map a block offset into a file into an absolute block number. * (traverse the indirect blocks if necessary). Note: Double-indirect * blocks allow us to map over 64Mb on a 1k file system. Therefore, for * our purposes, we will NOT bother with triple indirect blocks. * * The "allocate" argument is set if we want to *allocate* a block * and we don't already have one allocated. */ static int ext2_blkno(ext2fs_priv_state_t *e2fs, struct ext2_inode *ip, int blkoff) { unsigned int *lp; unsigned int *ilp; unsigned int *dlp; int blkno; int iblkno; int diblkno; long offset; ilp = (unsigned int *)e2fs->iblkbuf; dlp = (unsigned int *)e2fs->diblkbuf; lp = (unsigned int *)e2fs->blkbuf; /* If it's a direct block, it's easy! */ if (blkoff <= e2fs->directlim) { return ip->i_block[blkoff]; } /* Is it a single-indirect? */ if (blkoff <= e2fs->ind1lim) { iblkno = ip->i_block[EXT2_IND_BLOCK]; if (iblkno == 0) { return 0; } /* Read the indirect block */ if (e2fs->cached_iblkno != iblkno) { offset = iblkno * e2fs->blocksize; if (read_bytes(e2fs->blkio, e2fs->mediaid, offset, e2fs->iblkbuf, e2fs->blocksize)) { ERR_PRT((L"ext2_blkno: error on iblk read")); return 0; } e2fs->cached_iblkno = iblkno; } blkno = ilp[blkoff-(e2fs->directlim+1)]; return blkno; } /* Is it a double-indirect? */ if (blkoff <= e2fs->ind2lim) { /* Find the double-indirect block */ diblkno = ip->i_block[EXT2_DIND_BLOCK]; if (diblkno == 0) { return 0; } /* Read in the double-indirect block */ if (e2fs->cached_diblkno != diblkno) { offset = diblkno * e2fs->blocksize; if (read_bytes(e2fs->blkio, e2fs->mediaid, offset, e2fs->diblkbuf, e2fs->blocksize)) { ERR_PRT((L"ext2_blkno: err reading dindr blk")); return 0; } e2fs->cached_diblkno = diblkno; } /* Find the single-indirect block pointer ... */ iblkno = dlp[(blkoff - (e2fs->ind1lim+1)) / e2fs->ptrs_per_blk]; if (iblkno == 0) { return 0; } /* Read the indirect block */ if (e2fs->cached_iblkno != iblkno) { offset = iblkno * e2fs->blocksize; if (read_bytes(e2fs->blkio, e2fs->mediaid, offset, e2fs->iblkbuf, e2fs->blocksize)) { ERR_PRT((L"ext2_blkno: err on iblk read")); return 0; } e2fs->cached_iblkno = iblkno; } /* Find the block itself. */ blkno = ilp[(blkoff-(e2fs->ind1lim+1)) % e2fs->ptrs_per_blk]; return blkno; } if (blkoff > e2fs->ind2lim) { ERR_PRT((L"ext2_blkno: block number too large: %d", blkoff)); return 0; } return -1; } static int ext2_breadi(ext2fs_priv_state_t *e2fs, struct ext2_inode *ip, long blkno, long nblks, CHAR8 *buffer) { long dev_blkno, ncontig, offset, nbytes, tot_bytes; tot_bytes = 0; if ((blkno+nblks)*e2fs->blocksize > ip->i_size) nblks = (ip->i_size + e2fs->blocksize) / e2fs->blocksize - blkno; while (nblks) { /* * Contiguous reads are a lot faster, so we try to group * as many blocks as possible: */ ncontig = 0; nbytes = 0; dev_blkno = ext2_blkno(e2fs,ip, blkno); do { ++blkno; ++ncontig; --nblks; nbytes += e2fs->blocksize; } while (nblks && ext2_blkno(e2fs, ip, blkno) == dev_blkno + ncontig); if (dev_blkno == 0) { /* This is a "hole" */ Memset(buffer, 0, nbytes); } else { /* Read it for real */ offset = dev_blkno*e2fs->blocksize; DBG_PRT((L"ext2_bread: reading %d bytes at offset %d", nbytes, offset)); if (read_bytes(e2fs->blkio, e2fs->mediaid, offset, buffer, nbytes)) { ERR_PRT((L"ext2_bread: read error")); return -1; } } buffer += nbytes; tot_bytes += nbytes; } return tot_bytes; } static struct ext2_dir_entry_2 * ext2_readdiri(ext2fs_priv_state_t *e2fs, struct ext2_inode *dir_inode, int rewind) { struct ext2_dir_entry_2 *dp; static int diroffset = 0, blockoffset = 0; /* Reading a different directory, invalidate previous state */ if (rewind) { diroffset = 0; blockoffset = 0; /* read first block */ if (ext2_breadi(e2fs, dir_inode, 0, 1, e2fs->blkbuf) < 0) return NULL; } DBG_PRT((L"ext2_readdiri: blkoffset %d diroffset %d len %d", blockoffset, diroffset, dir_inode->i_size)); if (blockoffset >= e2fs->blocksize) { diroffset += e2fs->blocksize; if (diroffset >= dir_inode->i_size) return NULL; ERR_PRT((L"ext2_readdiri: reading block at %d", diroffset)); /* assume that this will read the whole block */ if (ext2_breadi(e2fs, dir_inode, diroffset / e2fs->blocksize, 1, e2fs->blkbuf) < 0) return NULL; blockoffset = 0; } dp = (struct ext2_dir_entry_2 *) (e2fs->blkbuf + blockoffset); blockoffset += dp->rec_len; #if 0 Print(L"ext2_readdiri: returning %x = "); { INTN i; for(i=0; i < dp->name_len; i++) Print(L"%c", (CHAR16)dp->name[i]); Print(L"\n");} #endif return dp; } /* * the string 'name' is modified by this call as per the parsing that * is done in strtok_simple() */ static struct ext2_inode * ext2_namei(ext2fs_priv_state_t *e2fs, CHAR8 *name) { CHAR8 *component; struct ext2_inode *dir_inode; struct ext2_dir_entry_2 *dp; int next_ino; /* start at the root: */ if (!e2fs->root_inode) e2fs->root_inode = ext2_iget(e2fs, EXT2_ROOT_INO); dir_inode = e2fs->root_inode; if (!dir_inode) return NULL; component = strtok_simple(name, '/'); while (component) { int component_length; int rewind = 0; /* * Search for the specified component in the current * directory inode. */ next_ino = -1; component_length = strlena(component); DBG_PRT((L"ext2_namei: component = %a", component)); /* rewind the first time through */ while ((dp = ext2_readdiri(e2fs, dir_inode, !rewind++))) { if ((dp->name_len == component_length) && (strncmpa(component, dp->name, component_length) == 0)) { /* Found it! */ DBG_PRT((L"ext2_namei: found entry %a", component)); next_ino = dp->inode; break; } DBG_PRT((L"ext2_namei: looping")); } DBG_PRT((L"ext2_namei: next_ino = %d", next_ino)); /* * At this point, we're done with this directory whether * we've succeeded or failed... */ if (dir_inode != e2fs->root_inode) ext2_iput(e2fs, dir_inode); /* * If next_ino is negative, then we've failed (gone * all the way through without finding anything) */ if (next_ino < 0) { return NULL; } /* * Otherwise, we can get this inode and find the next * component string... */ dir_inode = ext2_iget(e2fs, next_ino); if (!dir_inode) return NULL; component = strtok_simple(NULL, '/'); } /* * If we get here, then we got through all the components. * Whatever we got must match up with the last one. */ return dir_inode; } /* * Read block number "blkno" from the specified file. */ static int ext2_bread(ext2fs_priv_state_t *e2fs, int fd, long blkno, long nblks, char *buffer) { struct ext2_inode * ip; ip = &e2fs->inode_table[fd].inode; return ext2_breadi(e2fs, ip, blkno, nblks, buffer); } #if 0 /* * Note: don't mix any kind of file lookup or other I/O with this or * you will lose horribly (as it reuses blkbuf) */ static const char * ext2_readdir(ext2fs_priv_state_t *e2fs, int fd, int rewind) { struct ext2_inode * ip = &e2fs->inode_table[fd].inode; struct ext2_dir_entry_2 * ent; if (!S_ISDIR(ip->i_mode)) { ERR_PRT((L"fd %d (inode %d) is not a directory (mode %x)", fd, e2fs->inode_table[fd].inumber, ip->i_mode)); return NULL; } ent = ext2_readdiri(e2fs, ip, rewind); if (ent) { ent->name[ent->name_len] = '\0'; return ent->name; } else { return NULL; } } #endif static int ext2_fstat(ext2fs_priv_state_t *e2fs, int fd, ext2fs_stat_t *buf) { struct ext2_inode * ip = &e2fs->inode_table[fd].inode; Memset(buf, 0, sizeof(*buf)); /* fill in relevant fields */ buf->st_ino = e2fs->inode_table[fd].inumber; buf->st_mode = ip->i_mode; buf->st_nlink = ip->i_links_count; buf->st_uid = ip->i_uid; buf->st_gid = ip->i_gid; buf->st_size = ip->i_size; buf->st_atime = ip->i_atime; buf->st_mtime = ip->i_mtime; buf->st_ctime = ip->i_ctime; return 0; /* NOTHING CAN GO WROGN! */ } static EFI_STATUS ext2fs_fstat(ext2fs_interface_t *this, UINTN fd, ext2fs_stat_t *st) { ext2fs_priv_state_t *e2fs; if (this == NULL || fd > MAX_OPEN_FILES || st == NULL) return EFI_INVALID_PARAMETER; e2fs = FS_PRIVATE(this); ext2_fstat(e2fs, fd, st); return EFI_SUCCESS; } static EFI_STATUS ext2fs_seek(ext2fs_interface_t *this, UINTN fd, UINT64 newpos) { ext2fs_priv_state_t *e2fs; if (this == NULL || fd > MAX_OPEN_FILES || newpos >= EXT2_FILESIZE_MAX) return EFI_INVALID_PARAMETER; e2fs = FS_PRIVATE(this); if (newpos > (UINT64)e2fs->inode_table[fd].inode.i_size) return EFI_INVALID_PARAMETER; e2fs->inode_table[fd].pos = newpos; return EFI_SUCCESS; } static EFI_STATUS ext2fs_read(ext2fs_interface_t *this, UINTN fd, VOID *buf, UINTN *size) { ext2fs_priv_state_t *e2fs; UINTN count, nc, bofs, bnum, pos; EFI_STATUS ret = EFI_INVALID_PARAMETER; CHAR8 *block; if (this == NULL || size == NULL || buf == NULL || fd > MAX_OPEN_FILES) return EFI_INVALID_PARAMETER; e2fs = FS_PRIVATE(this); count = MIN(*size, e2fs->inode_table[fd].inode.i_size - e2fs->inode_table[fd].pos); if (count == 0) { *size = 0; return EFI_SUCCESS; } block = e2fs->blkbuf; *size = 0; pos = e2fs->inode_table[fd].pos; DBG_PRT((L"size=%d i_size=%d count=%d pos=%ld", *size,e2fs->inode_table[fd].inode.i_size, count, pos)); while (count) { bnum = pos / e2fs->blocksize; bofs = pos % e2fs->blocksize; nc = MIN(count, e2fs->blocksize - bofs); DBG_PRT((L"bnum =%d bofs=%d nc=%d *size=%d", bnum, bofs, nc, *size)); if (ext2_bread(e2fs, fd, bnum, 1, block) == -1) goto error; #if 0 { int i; char *p = block+bofs; for(i=MIN(nc, 64); i>=0 ; i--, p++) { if (i % 16 == 0) Print(L"\n"); Print(L"%02x ", (UINTN)*p & 0xff); } } #endif Memcpy(buf, block+bofs, nc); count -= nc; pos += nc; buf += nc; *size += nc; } e2fs->inode_table[fd].pos += *size; ret = EFI_SUCCESS; error: DBG_PRT((L"*size=%d ret=%r", *size, ret)); return ret; } static struct ext2_inode * ext2_follow_link(ext2fs_priv_state_t *e2fs, struct ext2_inode * from, const char * base) { char *linkto; if (from->i_blocks) { linkto = e2fs->blkbuf; if (ext2_breadi(e2fs, from, 0, 1, e2fs->blkbuf) == -1) return NULL; DBG_PRT((L"long link!")); } else { linkto = (char*)from->i_block; } DBG_PRT((L"symlink to %s", linkto)); /* Resolve relative links */ if (linkto[0] != '/') { char *end = strrchra(base, '/'); if (end) { //char fullname[(end - base + 1) + strlena(linkto) + 1]; char fullname[EXT2FS_PATH_MAXLEN]; if (((end - base + 1) + strlena(linkto) + 1) >= EXT2FS_PATH_MAXLEN) { Print(L"%s: filename too long, can't resolve\n", __FUNCTION__); return NULL; } strncpya(fullname, base, end - base + 1); fullname[end - base + 1] = '\0'; strcata(fullname, linkto); DBG_PRT((L"resolved to %s", fullname)); return ext2_namei(e2fs, fullname); } else { /* Assume it's in the root */ return ext2_namei(e2fs, linkto); } } else { return ext2_namei(e2fs, linkto); } } static int ext2_open(ext2fs_priv_state_t *e2fs, char *filename) { /* * Unix-like open routine. Returns a small integer (actually * an index into the inode table... */ struct ext2_inode * ip; ip = ext2_namei(e2fs, filename); if (ip) { struct inode_table_entry *itp; while (S_ISLNK(ip->i_mode)) { ip = ext2_follow_link(e2fs, ip, filename); if (!ip) return -1; } itp = (struct inode_table_entry *)ip; return itp - e2fs->inode_table; } else return -1; } static void ext2_close(ext2fs_priv_state_t *e2fs, int fd) { /* blah, hack, don't close the root inode ever */ if (&e2fs->inode_table[fd].inode != e2fs->root_inode) ext2_iput(e2fs, &e2fs->inode_table[fd].inode); } static EFI_STATUS ext2fs_close(ext2fs_interface_t *this, UINTN fd) { ext2fs_priv_state_t *e2fs; if (this == NULL || fd > MAX_OPEN_FILES) return EFI_INVALID_PARAMETER; e2fs = FS_PRIVATE(this); ext2_close(e2fs, fd); return EFI_SUCCESS; } static EFI_STATUS ext2fs_open(ext2fs_interface_t *this, CHAR16 *name, UINTN *fd) { ext2fs_priv_state_t *e2fs; CHAR8 filename[EXT2FS_PATH_MAXLEN]; /* XXX: kind of big for a stack object */ INTN tmp; DBG_PRT((L"name:%s fd=%x", name, fd)); if (this == NULL || name == NULL || fd == NULL || StrLen(name) >=EXT2FS_PATH_MAXLEN) return EFI_INVALID_PARAMETER; e2fs = FS_PRIVATE(this); /* * XXX: for security reasons, we may have to force a prefix like /boot to all filenames */ StrnXCpy(filename, name, EXT2FS_PATH_MAXLEN); DBG_PRT((L"ASCII name:%a UTF-name:%s", filename, name)); tmp = ext2_open(e2fs, filename); if (tmp != -1) { *fd = (UINTN)tmp; e2fs->inode_table[tmp].pos = 0; /* reset file position */ } DBG_PRT((L"name: %s fd=%d tmp=%d", name, *fd, tmp)); return tmp == -1 ? EFI_NOT_FOUND : EFI_SUCCESS; } static EFI_STATUS ext2fs_name(ext2fs_interface_t *this, CHAR16 *name, UINTN maxlen) { if (name == NULL || maxlen < 1) return EFI_INVALID_PARAMETER; StrnCpy(name, FS_NAME, maxlen-1); name[maxlen-1] = CHAR_NULL; return EFI_SUCCESS; } static INTN ext2fs_init_state(ext2fs_t *ext2fs, EFI_HANDLE dev, EFI_BLOCK_IO *blkio, struct ext2_super_block *sb) { ext2fs_priv_state_t *e2fs = FS_PRIVATE(ext2fs); UINTN i; EFI_STATUS status; Memset(ext2fs, 0, sizeof(*ext2fs)); e2fs->dev = dev; e2fs->blkio = blkio; e2fs->mediaid = blkio->Media->MediaId; /* fools gcc builtin memcpy */ Memcpy(&e2fs->sb, sb, sizeof(*sb)); e2fs->ngroups = (sb->s_blocks_count - sb->s_first_data_block + EXT2_BLOCKS_PER_GROUP(sb) - 1) / EXT2_BLOCKS_PER_GROUP(sb); e2fs->gds = (struct ext2_group_desc *)alloc(e2fs->ngroups * sizeof(struct ext2_group_desc), EXT2FS_MEMTYPE); if (e2fs->gds == NULL) { ERR_PRT((L"failed to allocate gds")); return EFI_OUT_OF_RESOURCES; } e2fs->blocksize = EXT2_BLOCK_SIZE(sb); DBG_PRT((L"gds_size=%d gds_offset=%d ngroups=%d blocksize=%d", e2fs->ngroups * sizeof(struct ext2_group_desc), e2fs->blocksize * (EXT2_MIN_BLOCK_SIZE/e2fs->blocksize + 1), e2fs->ngroups, (UINTN)e2fs->blocksize)); /* read in the group descriptors (immediately follows superblock) */ status = read_bytes(blkio, e2fs->mediaid, e2fs->blocksize * (EXT2_MIN_BLOCK_SIZE/e2fs->blocksize + 1), e2fs->gds, e2fs->ngroups * sizeof(struct ext2_group_desc)); if (EFI_ERROR(status)) { ERR_PRT((L"cannot read gds: %r", status)); free(e2fs->gds); return EFI_INVALID_PARAMETER; } #if 0 { int i; char *p = (char *)e2fs->gds; for(i=e2fs->ngroups*sizeof(*e2fs->gds); i ; i--, p++) { if (i % 16 == 0) Print(L"\n"); Print(L"%02x ", (UINTN)*p & 0xff); } } #endif e2fs->cached_diblkno = -1; e2fs->cached_iblkno = -1; /* initialize the inode table */ for (i = 0; i < MAX_OPEN_FILES; i++) { e2fs->inode_table[i].free = 1; e2fs->inode_table[i].inumber = 0; } /* clear the root inode pointer (very important!) */ e2fs->root_inode = NULL; /* * Calculate direct/indirect block limits for this file system * (blocksize dependent): ext2_blocksize = EXT2_BLOCK_SIZE(&sb); */ e2fs->directlim = EXT2_NDIR_BLOCKS - 1; e2fs->ptrs_per_blk = e2fs->blocksize/sizeof(unsigned int); e2fs->ind1lim = e2fs->ptrs_per_blk + e2fs->directlim; e2fs->ind2lim = (e2fs->ptrs_per_blk * e2fs->ptrs_per_blk) + e2fs->directlim; ext2fs->pub_intf.ext2fs_name = ext2fs_name; ext2fs->pub_intf.ext2fs_open = ext2fs_open; ext2fs->pub_intf.ext2fs_read = ext2fs_read; ext2fs->pub_intf.ext2fs_close = ext2fs_close; ext2fs->pub_intf.ext2fs_seek = ext2fs_seek; ext2fs->pub_intf.ext2fs_fstat = ext2fs_fstat; return EFI_SUCCESS; } static EFI_STATUS ext2fs_install_one(EFI_HANDLE dev, VOID **intf) { struct ext2_super_block sb; long sb_block = 1; EFI_STATUS status; EFI_BLOCK_IO *blkio; ext2fs_t *ext2fs; status = uefi_call_wrapper(BS->HandleProtocol, 3, dev, &Ext2FsProtocol, (VOID **)&ext2fs); if (status == EFI_SUCCESS) { ERR_PRT((L"Warning: found existing %s protocol on device", FS_NAME)); goto found; } status = uefi_call_wrapper(BS->HandleProtocol, 3, dev, &BlockIoProtocol, (VOID **)&blkio); if (EFI_ERROR(status)) return EFI_INVALID_PARAMETER; VERB_PRT(5, { EFI_DEVICE_PATH *dp; CHAR16 *str; dp = DevicePathFromHandle(dev); str = DevicePathToStr(dp); Print(L"dev:%s\nLogical partition: %s BlockSize: %d WriteCaching: %s \n", str, blkio->Media->LogicalPartition ? L"Yes": L"No", blkio->Media->BlockSize, blkio->Media->WriteCaching ? L"Yes":L"No"); FreePool(str); }); if (blkio->Media->LogicalPartition == FALSE) return EFI_INVALID_PARAMETER; #if 0 /* * Used to be necessary on some older versions of EFI to avoid getting * stuck. Now can cause problems with some SCSI controllers when enabled. * Does not seem necessary with EFI 12.38 */ blkio->Reset(blkio, FALSE); #endif status = read_bytes(blkio, blkio->Media->MediaId, sb_block * EXT2_MIN_BLOCK_SIZE, &sb, sizeof(sb)); if (EFI_ERROR(status)) { DBG_PRT((L"cannot read superblock: %r", status)); return EFI_INVALID_PARAMETER; } if (sb.s_magic != EXT2_SUPER_MAGIC) { DBG_PRT((L"bad magic 0x%x\n", sb.s_magic)); return EFI_INVALID_PARAMETER; } ext2fs = (ext2fs_t *)alloc(sizeof(*ext2fs), EXT2FS_MEMTYPE); if (ext2fs == NULL) return EFI_OUT_OF_RESOURCES; status = ext2fs_init_state(ext2fs, dev, blkio, &sb); if (status != EFI_SUCCESS) { free(ext2fs); return status; } status = LibInstallProtocolInterfaces(&dev, &Ext2FsProtocol, ext2fs, NULL); if (EFI_ERROR(status)) { ERR_PRT((L"Cannot install %s protocol: %r", FS_NAME, status)); free(ext2fs); return status; } found: if (intf) *intf = (VOID *)ext2fs; VERB_PRT(3, { EFI_DEVICE_PATH *dp; CHAR16 *str; dp = DevicePathFromHandle(dev); str = DevicePathToStr(dp); Print(L"dev:%s %s detected\n", str, FS_NAME); FreePool(str); }); return EFI_SUCCESS; } EFI_STATUS ext2fs_install(VOID) { UINTN size = 0; UINTN i; EFI_STATUS status; VOID *intf; uefi_call_wrapper(BS->LocateHandle, 5, ByProtocol, &BlockIoProtocol, NULL, &size, NULL); if (size == 0) return EFI_UNSUPPORTED; /* no device found, oh well */ DBG_PRT((L"size=%d", size)); dev_tab = (dev_tab_t *)alloc(size, EfiLoaderData); if (dev_tab == NULL) { ERR_PRT((L"failed to allocate handle table")); return EFI_OUT_OF_RESOURCES; } status = uefi_call_wrapper(BS->LocateHandle, 5, ByProtocol, &BlockIoProtocol, NULL, &size, (VOID **)dev_tab); if (status != EFI_SUCCESS) { ERR_PRT((L"failed to get handles: %r", status)); free(dev_tab); return status; } ndev = size / sizeof(EFI_HANDLE); for(i=0; i < ndev; i++) { intf = NULL; ext2fs_install_one(dev_tab[i].dev, &intf); /* override device handle with interface pointer */ dev_tab[i].intf = intf; } return EFI_SUCCESS; } EFI_STATUS ext2fs_uninstall(VOID) { ext2fs_priv_state_t *e2fs; EFI_STATUS status; UINTN i; for(i=0; i < ndev; i++) { if (dev_tab[i].intf == NULL) continue; e2fs = FS_PRIVATE(dev_tab[i].intf); status = uefi_call_wrapper(BS->UninstallProtocolInterface, 3, e2fs->dev, &Ext2FsProtocol, dev_tab[i].intf); if (EFI_ERROR(status)) { ERR_PRT((L"Uninstall %s error: %r", FS_NAME, status)); continue; } VERB_PRT(3, { EFI_DEVICE_PATH *dp; CHAR16 *str; dp = DevicePathFromHandle(e2fs->dev); str = DevicePathToStr(dp); Print(L"uninstalled %s on %s\n", FS_NAME, str); FreePool(str); }); free(dev_tab[i].intf); } if (dev_tab) free(dev_tab); return EFI_SUCCESS; }