Support for cbfs. Also factor out the part which is common

for all archives to a separate module. This splits tar from cpio
	as they are very different but keeps cpio, cpio_be, odc and newc
	together since they're very similar.
This commit is contained in:
Vladimir 'phcoder' Serbinenko 2013-06-16 00:06:13 +02:00
parent a5b55c4b32
commit 5027af38cf
15 changed files with 1285 additions and 454 deletions

View file

@ -1145,6 +1145,16 @@ module = {
cppflags = '-I$(srcdir)/lib/posix_wrap -I$(srcdir)/lib/minilzo -DMINILZO_HAVE_CONFIG_H';
};
module = {
name = archelp;
common = fs/archelp.c;
};
module = {
name = cbfs;
common = fs/cbfs.c;
};
module = {
name = cpio;
common = fs/cpio.c;

View file

@ -78,8 +78,12 @@ get_uuid (const char *name, char **uuid, int getnative)
break;
/* Virtual disks. */
/* GRUB dynamically generated files. */
case GRUB_DISK_DEVICE_PROCFS_ID:
/* To access through host OS routines (grub-emu only). */
case GRUB_DISK_DEVICE_HOST_ID:
/* To access coreboot roms. */
case GRUB_DISK_DEVICE_CBFSDISK_ID:
/* GRUB-only memdisk. Can't match any of firmware devices. */
case GRUB_DISK_DEVICE_MEMDISK_ID:
grub_dprintf ("nativedisk", "Skipping native disk %s\n",

298
grub-core/fs/archelp.c Normal file
View file

@ -0,0 +1,298 @@
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2007,2008,2009,2013 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include <grub/archelp.h>
#include <grub/err.h>
#include <grub/fs.h>
#include <grub/disk.h>
#include <grub/dl.h>
GRUB_MOD_LICENSE ("GPLv3+");
static inline void
canonicalize (char *name)
{
char *iptr, *optr;
for (iptr = name, optr = name; *iptr; )
{
while (*iptr == '/')
iptr++;
if (iptr[0] == '.' && (iptr[1] == '/' || iptr[1] == 0))
{
iptr += 2;
continue;
}
if (iptr[0] == '.' && iptr[1] == '.' && (iptr[2] == '/' || iptr[2] == 0))
{
iptr += 3;
if (optr == name)
continue;
for (optr -= 2; optr >= name && *optr != '/'; optr--);
optr++;
continue;
}
while (*iptr && *iptr != '/')
*optr++ = *iptr++;
if (*iptr)
*optr++ = *iptr++;
}
*optr = 0;
}
static grub_err_t
handle_symlink (struct grub_archelp_data *data,
struct grub_archelp_ops *arcops,
const char *fn, char **name,
grub_uint32_t mode, int *restart)
{
grub_size_t flen;
char *target;
char *ptr;
char *lastslash;
grub_size_t prefixlen;
char *rest;
char *linktarget;
*restart = 0;
if ((mode & GRUB_ARCHELP_ATTR_TYPE) != GRUB_ARCHELP_ATTR_LNK
|| !arcops->get_link_target)
return GRUB_ERR_NONE;
flen = grub_strlen (fn);
if (grub_memcmp (*name, fn, flen) != 0
|| ((*name)[flen] != 0 && (*name)[flen] != '/'))
return GRUB_ERR_NONE;
rest = *name + flen;
lastslash = rest;
if (*rest)
rest++;
while (lastslash >= *name && *lastslash != '/')
lastslash--;
if (lastslash >= *name)
prefixlen = lastslash - *name;
else
prefixlen = 0;
if (prefixlen)
prefixlen++;
linktarget = arcops->get_link_target (data);
if (!linktarget)
return grub_errno;
if (linktarget[0] == '\0')
return GRUB_ERR_NONE;
target = grub_malloc (grub_strlen (linktarget) + grub_strlen (*name) + 2);
if (!target)
return grub_errno;
grub_strcpy (target + prefixlen, linktarget);
grub_free (linktarget);
if (target[prefixlen] == '/')
{
ptr = grub_stpcpy (target, target + prefixlen);
ptr = grub_stpcpy (ptr, rest);
*ptr = 0;
grub_dprintf ("archelp", "symlink redirected %s to %s\n",
*name, target);
grub_free (*name);
canonicalize (target);
*name = target;
*restart = 1;
return GRUB_ERR_NONE;
}
if (prefixlen)
{
grub_memcpy (target, *name, prefixlen);
target[prefixlen-1] = '/';
}
grub_strcat (target, rest);
grub_dprintf ("archelp", "symlink redirected %s to %s\n",
*name, target);
grub_free (*name);
canonicalize (target);
*name = target;
*restart = 1;
return GRUB_ERR_NONE;
}
grub_err_t
grub_archelp_dir (struct grub_archelp_data *data,
struct grub_archelp_ops *arcops,
const char *path_in,
grub_fs_dir_hook_t hook, void *hook_data)
{
char *prev, *name, *path, *ptr;
grub_size_t len;
int symlinknest = 0;
path = grub_strdup (path_in + 1);
if (!path)
return grub_errno;
canonicalize (path);
for (ptr = path + grub_strlen (path) - 1; ptr >= path && *ptr == '/'; ptr--)
*ptr = 0;
prev = 0;
len = grub_strlen (path);
while (1)
{
grub_int32_t mtime;
grub_uint32_t mode;
grub_err_t err;
if (arcops->find_file (data, &name, &mtime, &mode))
goto fail;
if (mode == GRUB_ARCHELP_ATTR_END)
break;
canonicalize (name);
if (grub_memcmp (path, name, len) == 0
&& (name[len] == 0 || name[len] == '/' || len == 0))
{
char *p, *n;
n = name + len;
while (*n == '/')
n++;
p = grub_strchr (n, '/');
if (p)
*p = 0;
if (((!prev) || (grub_strcmp (prev, name) != 0)) && *n != 0)
{
struct grub_dirhook_info info;
grub_memset (&info, 0, sizeof (info));
info.dir = (p != NULL) || ((mode & GRUB_ARCHELP_ATTR_TYPE)
== GRUB_ARCHELP_ATTR_DIR);
if (!(mode & GRUB_ARCHELP_ATTR_NOTIME))
{
info.mtime = mtime;
info.mtimeset = 1;
}
if (hook (n, &info, hook_data))
{
grub_free (name);
goto fail;
}
grub_free (prev);
prev = name;
}
else
{
int restart = 0;
err = handle_symlink (data, arcops, name,
&path, mode, &restart);
grub_free (name);
if (err)
goto fail;
if (restart)
{
len = grub_strlen (path);
if (++symlinknest == 8)
{
grub_error (GRUB_ERR_SYMLINK_LOOP,
N_("too deep nesting of symlinks"));
goto fail;
}
arcops->rewind (data);
}
}
}
else
grub_free (name);
}
fail:
grub_free (path);
grub_free (prev);
return grub_errno;
}
grub_err_t
grub_archelp_open (struct grub_archelp_data *data,
struct grub_archelp_ops *arcops,
const char *name_in)
{
char *fn;
char *name = grub_strdup (name_in + 1);
int symlinknest = 0;
if (!name)
return grub_errno;
canonicalize (name);
while (1)
{
grub_uint32_t mode;
int restart;
if (arcops->find_file (data, &fn, NULL, &mode))
goto fail;
if (mode == GRUB_ARCHELP_ATTR_END)
{
grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), name_in);
break;
}
canonicalize (fn);
if (handle_symlink (data, arcops, fn, &name, mode, &restart))
{
grub_free (fn);
goto fail;
}
if (restart)
{
arcops->rewind (data);
if (++symlinknest == 8)
{
grub_error (GRUB_ERR_SYMLINK_LOOP,
N_("too deep nesting of symlinks"));
goto fail;
}
goto no_match;
}
if (grub_strcmp (name, fn) != 0)
goto no_match;
grub_free (fn);
grub_free (name);
return GRUB_ERR_NONE;
no_match:
grub_free (fn);
}
fail:
grub_free (name);
return grub_errno;
}

382
grub-core/fs/cbfs.c Normal file
View file

@ -0,0 +1,382 @@
/* cbfs.c - cbfs and tar filesystem. */
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2007,2008,2009,2013 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include <grub/misc.h>
#include <grub/disk.h>
#include <grub/archelp.h>
#include <grub/file.h>
#include <grub/mm.h>
#include <grub/dl.h>
#include <grub/i18n.h>
#include <grub/cbfs_core.h>
GRUB_MOD_LICENSE ("GPLv3+");
struct grub_archelp_data
{
grub_disk_t disk;
grub_off_t hofs, next_hofs;
grub_off_t dofs;
grub_off_t size;
grub_off_t cbfs_start;
grub_off_t cbfs_end;
grub_off_t cbfs_align;
};
static grub_err_t
grub_cbfs_find_file (struct grub_archelp_data *data, char **name,
grub_int32_t *mtime,
grub_uint32_t *mode)
{
grub_size_t offset;
for (;;
data->dofs = data->hofs + offset,
data->next_hofs = ALIGN_UP (data->dofs + data->size, data->cbfs_align))
{
struct cbfs_file hd;
grub_size_t namesize;
data->hofs = data->next_hofs;
if (data->hofs >= data->cbfs_end)
{
*mode = GRUB_ARCHELP_ATTR_END;
return GRUB_ERR_NONE;
}
if (grub_disk_read (data->disk, 0, data->hofs, sizeof (hd), &hd))
return grub_errno;
if (grub_memcmp (hd.magic, CBFS_FILE_MAGIC, sizeof (hd.magic)) != 0)
{
*mode = GRUB_ARCHELP_ATTR_END;
return GRUB_ERR_NONE;
}
data->size = grub_be_to_cpu32 (hd.len);
(void) mtime;
offset = grub_be_to_cpu32 (hd.offset);
if (mode)
*mode = GRUB_ARCHELP_ATTR_FILE | GRUB_ARCHELP_ATTR_NOTIME;
namesize = offset;
if (namesize >= sizeof (hd))
namesize -= sizeof (hd);
if (namesize == 0)
continue;
*name = grub_malloc (namesize + 1);
if (*name == NULL)
return grub_errno;
if (grub_disk_read (data->disk, 0, data->hofs + sizeof (hd),
namesize, *name))
{
grub_free (*name);
return grub_errno;
}
if ((*name)[0] == '\0')
{
grub_free (*name);
*name = NULL;
continue;
}
(*name)[namesize] = 0;
data->dofs = data->hofs + offset;
data->next_hofs = ALIGN_UP (data->dofs + data->size, data->cbfs_align);
return GRUB_ERR_NONE;
}
}
static void
grub_cbfs_rewind (struct grub_archelp_data *data)
{
data->next_hofs = data->cbfs_start;
}
static struct grub_archelp_ops arcops =
{
.find_file = grub_cbfs_find_file,
.rewind = grub_cbfs_rewind
};
static int
validate_head (struct cbfs_header *head)
{
return (head->magic == grub_cpu_to_be32_compile_time (CBFS_HEADER_MAGIC)
&& (head->version
== grub_cpu_to_be32_compile_time (CBFS_HEADER_VERSION1)
|| head->version
== grub_cpu_to_be32_compile_time (CBFS_HEADER_VERSION2))
&& (grub_be_to_cpu32 (head->bootblocksize)
< grub_be_to_cpu32 (head->romsize))
&& (grub_be_to_cpu32 (head->offset)
< grub_be_to_cpu32 (head->romsize))
&& (grub_be_to_cpu32 (head->offset)
+ grub_be_to_cpu32 (head->bootblocksize)
< grub_be_to_cpu32 (head->romsize))
&& head->align != 0
&& (head->align & (head->align - 1)) == 0
&& head->romsize != 0);
}
static struct grub_archelp_data *
grub_cbfs_mount (grub_disk_t disk)
{
struct cbfs_file hd;
struct grub_archelp_data *data;
grub_uint32_t ptr;
grub_off_t header_off;
struct cbfs_header head;
if (grub_disk_read (disk, grub_disk_get_size (disk) - 1,
GRUB_DISK_SECTOR_SIZE - sizeof (ptr),
sizeof (ptr), &ptr))
goto fail;
ptr = grub_cpu_to_le32 (ptr);
header_off = (grub_disk_get_size (disk) << GRUB_DISK_SECTOR_BITS)
+ (grub_int32_t) ptr;
if (grub_disk_read (disk, 0, header_off,
sizeof (head), &head))
goto fail;
if (!validate_head (&head))
goto fail;
data = (struct grub_archelp_data *) grub_zalloc (sizeof (*data));
if (!data)
goto fail;
data->cbfs_start = (grub_disk_get_size (disk) << GRUB_DISK_SECTOR_BITS)
- (grub_be_to_cpu32 (head.romsize) - grub_be_to_cpu32 (head.offset));
data->cbfs_end = (grub_disk_get_size (disk) << GRUB_DISK_SECTOR_BITS)
- grub_be_to_cpu32 (head.bootblocksize);
data->cbfs_align = grub_be_to_cpu32 (head.align);
if (data->cbfs_start >= (grub_disk_get_size (disk) << GRUB_DISK_SECTOR_BITS))
goto fail;
if (data->cbfs_end > (grub_disk_get_size (disk) << GRUB_DISK_SECTOR_BITS))
data->cbfs_end = (grub_disk_get_size (disk) << GRUB_DISK_SECTOR_BITS);
data->next_hofs = data->cbfs_start;
if (grub_disk_read (disk, 0, data->cbfs_start, sizeof (hd), &hd))
goto fail;
if (grub_memcmp (hd.magic, CBFS_FILE_MAGIC, sizeof (CBFS_FILE_MAGIC) - 1))
goto fail;
data->disk = disk;
return data;
fail:
grub_error (GRUB_ERR_BAD_FS, "not a cbfs filesystem");
return 0;
}
static grub_err_t
grub_cbfs_dir (grub_device_t device, const char *path_in,
grub_fs_dir_hook_t hook, void *hook_data)
{
struct grub_archelp_data *data;
grub_err_t err;
data = grub_cbfs_mount (device->disk);
if (!data)
return grub_errno;
err = grub_archelp_dir (data, &arcops,
path_in, hook, hook_data);
grub_free (data);
return err;
}
static grub_err_t
grub_cbfs_open (grub_file_t file, const char *name_in)
{
struct grub_archelp_data *data;
grub_err_t err;
data = grub_cbfs_mount (file->device->disk);
if (!data)
return grub_errno;
err = grub_archelp_open (data, &arcops, name_in);
if (err)
{
grub_free (data);
}
else
{
file->data = data;
file->size = data->size;
}
return err;
}
static grub_ssize_t
grub_cbfs_read (grub_file_t file, char *buf, grub_size_t len)
{
struct grub_archelp_data *data;
data = file->data;
return (grub_disk_read (data->disk, 0, data->dofs + file->offset,
len, buf)) ? -1 : (grub_ssize_t) len;
}
static grub_err_t
grub_cbfs_close (grub_file_t file)
{
struct grub_archelp_data *data;
data = file->data;
grub_free (data);
return grub_errno;
}
#if (defined (__i386__) || defined (__x86_64__)) && !defined (GRUB_UTIL)
static char *cbfsdisk_addr;
static grub_off_t cbfsdisk_size = 0;
static int
grub_cbfsdisk_iterate (grub_disk_dev_iterate_hook_t hook, void *hook_data,
grub_disk_pull_t pull)
{
if (pull != GRUB_DISK_PULL_NONE)
return 0;
return hook ("cbfsdisk", hook_data);
}
static grub_err_t
grub_cbfsdisk_open (const char *name, grub_disk_t disk)
{
if (grub_strcmp (name, "cbfsdisk"))
return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a cbfsdisk");
disk->total_sectors = cbfsdisk_size / GRUB_DISK_SECTOR_SIZE;
disk->id = (unsigned long) "cbfs";
return GRUB_ERR_NONE;
}
static void
grub_cbfsdisk_close (grub_disk_t disk __attribute((unused)))
{
}
static grub_err_t
grub_cbfsdisk_read (grub_disk_t disk __attribute((unused)),
grub_disk_addr_t sector,
grub_size_t size, char *buf)
{
grub_memcpy (buf, cbfsdisk_addr + (sector << GRUB_DISK_SECTOR_BITS),
size << GRUB_DISK_SECTOR_BITS);
return 0;
}
static grub_err_t
grub_cbfsdisk_write (grub_disk_t disk __attribute__ ((unused)),
grub_disk_addr_t sector __attribute__ ((unused)),
grub_size_t size __attribute__ ((unused)),
const char *buf __attribute__ ((unused)))
{
return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
"rom flashing isn't implemented yet");
}
static struct grub_disk_dev grub_cbfsdisk_dev =
{
.name = "cbfsdisk",
.id = GRUB_DISK_DEVICE_CBFSDISK_ID,
.iterate = grub_cbfsdisk_iterate,
.open = grub_cbfsdisk_open,
.close = grub_cbfsdisk_close,
.read = grub_cbfsdisk_read,
.write = grub_cbfsdisk_write,
.next = 0
};
static void
init_cbfsdisk (void)
{
grub_uint32_t ptr;
struct cbfs_header *head;
ptr = *(grub_uint32_t *) 0xfffffffc;
head = (struct cbfs_header *) ptr;
if (!validate_head (head))
return;
cbfsdisk_size = ALIGN_UP (grub_be_to_cpu32 (head->romsize),
GRUB_DISK_SECTOR_SIZE);
cbfsdisk_addr = (void *) (grub_addr_t) (0x100000000ULL - cbfsdisk_size);
grub_disk_dev_register (&grub_cbfsdisk_dev);
}
static void
fini_cbfsdisk (void)
{
if (! cbfsdisk_size)
return;
grub_disk_dev_unregister (&grub_cbfsdisk_dev);
}
#endif
static struct grub_fs grub_cbfs_fs = {
.name = "cbfs",
.dir = grub_cbfs_dir,
.open = grub_cbfs_open,
.read = grub_cbfs_read,
.close = grub_cbfs_close,
#ifdef GRUB_UTIL
.reserved_first_sector = 0,
.blocklist_install = 0,
#endif
};
GRUB_MOD_INIT (cbfs)
{
#if (defined (__i386__) || defined (__x86_64__)) && !defined (GRUB_UTIL)
init_cbfsdisk ();
#endif
grub_fs_register (&grub_cbfs_fs);
}
GRUB_MOD_FINI (cbfs)
{
grub_fs_unregister (&grub_cbfs_fs);
#if (defined (__i386__) || defined (__x86_64__)) && !defined (GRUB_UTIL)
fini_cbfsdisk ();
#endif
}

View file

@ -53,7 +53,6 @@ read_number (const grub_uint16_t *arr, grub_size_t size)
GRUB_MOD_INIT (cpio)
{
grub_fs_register (&grub_cpio_fs);
my_mod = mod;
}
GRUB_MOD_FINI (cpio)

View file

@ -53,7 +53,6 @@ read_number (const grub_uint16_t *arr, grub_size_t size)
GRUB_MOD_INIT (cpio_be)
{
grub_fs_register (&grub_cpio_fs);
my_mod = mod;
}
GRUB_MOD_FINI (cpio_be)

View file

@ -23,68 +23,29 @@
#include <grub/disk.h>
#include <grub/dl.h>
#include <grub/i18n.h>
#include <grub/archelp.h>
GRUB_MOD_LICENSE ("GPLv3+");
#define ATTR_TYPE 0160000
#define ATTR_FILE 0100000
#define ATTR_DIR 0040000
#define ATTR_LNK 0120000
struct grub_cpio_data
struct grub_archelp_data
{
grub_disk_t disk;
grub_off_t hofs;
grub_off_t next_hofs;
grub_off_t dofs;
grub_off_t size;
#ifdef MODE_USTAR
char *linkname;
grub_size_t linkname_alloc;
#endif
};
static grub_dl_t my_mod;
static inline void
canonicalize (char *name)
{
char *iptr, *optr;
for (iptr = name, optr = name; *iptr; )
{
while (*iptr == '/')
iptr++;
if (iptr[0] == '.' && (iptr[1] == '/' || iptr[1] == 0))
{
iptr += 2;
continue;
}
if (iptr[0] == '.' && iptr[1] == '.' && (iptr[2] == '/' || iptr[2] == 0))
{
iptr += 3;
if (optr == name)
continue;
for (optr -= 2; optr >= name && *optr != '/'; optr--);
optr++;
continue;
}
while (*iptr && *iptr != '/')
*optr++ = *iptr++;
if (*iptr)
*optr++ = *iptr++;
}
*optr = 0;
}
static grub_err_t
grub_cpio_find_file (struct grub_cpio_data *data, char **name,
grub_int32_t *mtime, grub_disk_addr_t *ofs,
grub_uint32_t *mode)
grub_cpio_find_file (struct grub_archelp_data *data, char **name,
grub_int32_t *mtime, grub_uint32_t *mode)
{
#ifndef MODE_USTAR
struct head hd;
grub_size_t namesize;
grub_uint32_t modeval;
data->hofs = data->next_hofs;
if (grub_disk_read (data->disk, 0, data->hofs, sizeof (hd), &hd))
return grub_errno;
@ -118,153 +79,57 @@ grub_cpio_find_file (struct grub_cpio_data *data, char **name,
if (data->size == 0 && modeval == 0 && namesize == 11
&& grub_memcmp(*name, "TRAILER!!!", 11) == 0)
{
*ofs = 0;
*mode = GRUB_ARCHELP_ATTR_END;
grub_free (*name);
return GRUB_ERR_NONE;
}
canonicalize (*name);
data->dofs = data->hofs + ALIGN_CPIO (sizeof (hd) + namesize);
*ofs = data->dofs + ALIGN_CPIO (data->size);
#else
struct head hd;
int reread = 0, have_longname = 0, have_longlink = 0;
for (reread = 0; reread < 3; reread++)
{
if (grub_disk_read (data->disk, 0, data->hofs, sizeof (hd), &hd))
return grub_errno;
if (!hd.name[0] && !hd.prefix[0])
{
*ofs = 0;
return GRUB_ERR_NONE;
}
if (grub_memcmp (hd.magic, MAGIC, sizeof (MAGIC) - 1))
return grub_error (GRUB_ERR_BAD_FS, "invalid tar archive");
if (hd.typeflag == 'L')
{
grub_err_t err;
grub_size_t namesize = read_number (hd.size, sizeof (hd.size));
*name = grub_malloc (namesize + 1);
if (*name == NULL)
return grub_errno;
err = grub_disk_read (data->disk, 0,
data->hofs + GRUB_DISK_SECTOR_SIZE, namesize,
*name);
(*name)[namesize] = 0;
if (err)
return err;
data->hofs += GRUB_DISK_SECTOR_SIZE
+ ((namesize + GRUB_DISK_SECTOR_SIZE - 1) &
~(GRUB_DISK_SECTOR_SIZE - 1));
have_longname = 1;
continue;
}
if (hd.typeflag == 'K')
{
grub_err_t err;
grub_size_t linksize = read_number (hd.size, sizeof (hd.size));
if (data->linkname_alloc < linksize + 1)
{
char *n;
n = grub_malloc (2 * (linksize + 1));
if (!n)
return grub_errno;
grub_free (data->linkname);
data->linkname = n;
data->linkname_alloc = 2 * (linksize + 1);
}
err = grub_disk_read (data->disk, 0,
data->hofs + GRUB_DISK_SECTOR_SIZE, linksize,
data->linkname);
if (err)
return err;
data->linkname[linksize] = 0;
data->hofs += GRUB_DISK_SECTOR_SIZE
+ ((linksize + GRUB_DISK_SECTOR_SIZE - 1) &
~(GRUB_DISK_SECTOR_SIZE - 1));
have_longlink = 1;
continue;
}
if (!have_longname)
{
grub_size_t extra_size = 0;
while (extra_size < sizeof (hd.prefix)
&& hd.prefix[extra_size])
extra_size++;
*name = grub_malloc (sizeof (hd.name) + extra_size + 2);
if (*name == NULL)
return grub_errno;
if (hd.prefix[0])
{
grub_memcpy (*name, hd.prefix, extra_size);
(*name)[extra_size++] = '/';
}
grub_memcpy (*name + extra_size, hd.name, sizeof (hd.name));
(*name)[extra_size + sizeof (hd.name)] = 0;
}
data->size = read_number (hd.size, sizeof (hd.size));
data->dofs = data->hofs + GRUB_DISK_SECTOR_SIZE;
*ofs = data->dofs + ((data->size + GRUB_DISK_SECTOR_SIZE - 1) &
~(GRUB_DISK_SECTOR_SIZE - 1));
if (mtime)
*mtime = read_number (hd.mtime, sizeof (hd.mtime));
if (mode)
{
*mode = read_number (hd.mode, sizeof (hd.mode));
switch (hd.typeflag)
{
/* Hardlink. */
case '1':
/* Symlink. */
case '2':
*mode |= ATTR_LNK;
break;
case '0':
*mode |= ATTR_FILE;
break;
case '5':
*mode |= ATTR_DIR;
break;
}
}
if (!have_longlink)
{
if (data->linkname_alloc < 101)
{
char *n;
n = grub_malloc (101);
if (!n)
return grub_errno;
grub_free (data->linkname);
data->linkname = n;
data->linkname_alloc = 101;
}
grub_memcpy (data->linkname, hd.linkname, sizeof (hd.linkname));
data->linkname[100] = 0;
}
canonicalize (*name);
return GRUB_ERR_NONE;
}
#endif
data->next_hofs = data->dofs + ALIGN_CPIO (data->size);
return GRUB_ERR_NONE;
}
static struct grub_cpio_data *
static char *
grub_cpio_get_link_target (struct grub_archelp_data *data)
{
char *ret;
grub_err_t err;
if (data->size == 0)
return grub_strdup ("");
ret = grub_malloc (data->size + 1);
if (!ret)
return NULL;
err = grub_disk_read (data->disk, 0, data->dofs, data->size,
ret);
if (err)
{
grub_free (ret);
return NULL;
}
ret[data->size] = '\0';
return ret;
}
static void
grub_cpio_rewind (struct grub_archelp_data *data)
{
data->next_hofs = 0;
}
static struct grub_archelp_ops arcops =
{
.find_file = grub_cpio_find_file,
.get_link_target = grub_cpio_get_link_target,
.rewind = grub_cpio_rewind
};
static struct grub_archelp_data *
grub_cpio_mount (grub_disk_t disk)
{
struct head hd;
struct grub_cpio_data *data;
struct grub_archelp_data *data;
if (grub_disk_read (disk, 0, 0, sizeof (hd), &hd))
goto fail;
@ -276,7 +141,7 @@ grub_cpio_mount (grub_disk_t disk)
)
goto fail;
data = (struct grub_cpio_data *) grub_zalloc (sizeof (*data));
data = (struct grub_archelp_data *) grub_zalloc (sizeof (*data));
if (!data)
goto fail;
@ -289,293 +154,52 @@ fail:
return 0;
}
static grub_err_t
handle_symlink (struct grub_cpio_data *data,
const char *fn, char **name,
grub_uint32_t mode, int *restart)
{
grub_size_t flen;
char *target;
#ifndef MODE_USTAR
grub_err_t err;
#endif
char *ptr;
char *lastslash;
grub_size_t prefixlen;
char *rest;
grub_size_t size;
*restart = 0;
if ((mode & ATTR_TYPE) != ATTR_LNK)
return GRUB_ERR_NONE;
flen = grub_strlen (fn);
if (grub_memcmp (*name, fn, flen) != 0
|| ((*name)[flen] != 0 && (*name)[flen] != '/'))
return GRUB_ERR_NONE;
rest = *name + flen;
lastslash = rest;
if (*rest)
rest++;
while (lastslash >= *name && *lastslash != '/')
lastslash--;
if (lastslash >= *name)
prefixlen = lastslash - *name;
else
prefixlen = 0;
if (prefixlen)
prefixlen++;
#ifdef MODE_USTAR
size = grub_strlen (data->linkname);
#else
size = data->size;
#endif
if (size == 0)
return GRUB_ERR_NONE;
target = grub_malloc (size + grub_strlen (*name) + 2);
if (!target)
return grub_errno;
#ifdef MODE_USTAR
grub_memcpy (target + prefixlen, data->linkname, size);
#else
err = grub_disk_read (data->disk, 0, data->dofs, data->size,
target + prefixlen);
if (err)
return err;
#endif
if (target[prefixlen] == '/')
{
grub_memmove (target, target + prefixlen, size);
ptr = target + size;
ptr = grub_stpcpy (ptr, rest);
*ptr = 0;
grub_dprintf ("cpio", "symlink redirected %s to %s\n",
*name, target);
grub_free (*name);
canonicalize (target);
*name = target;
*restart = 1;
return GRUB_ERR_NONE;
}
if (prefixlen)
{
grub_memcpy (target, *name, prefixlen);
target[prefixlen-1] = '/';
}
ptr = target + prefixlen + size;
ptr = grub_stpcpy (ptr, rest);
*ptr = 0;
grub_dprintf ("cpio", "symlink redirected %s to %s\n",
*name, target);
grub_free (*name);
canonicalize (target);
*name = target;
*restart = 1;
return GRUB_ERR_NONE;
}
static grub_err_t
grub_cpio_dir (grub_device_t device, const char *path_in,
grub_fs_dir_hook_t hook, void *hook_data)
{
struct grub_cpio_data *data;
grub_disk_addr_t ofs;
char *prev, *name, *path, *ptr;
grub_size_t len;
int symlinknest = 0;
path = grub_strdup (path_in + 1);
if (!path)
return grub_errno;
canonicalize (path);
for (ptr = path + grub_strlen (path) - 1; ptr >= path && *ptr == '/'; ptr--)
*ptr = 0;
grub_dl_ref (my_mod);
prev = 0;
struct grub_archelp_data *data;
grub_err_t err;
data = grub_cpio_mount (device->disk);
if (!data)
{
grub_free (path);
return grub_errno;
}
return grub_errno;
len = grub_strlen (path);
data->hofs = 0;
while (1)
{
grub_int32_t mtime;
grub_uint32_t mode;
grub_err_t err;
err = grub_archelp_dir (data, &arcops,
path_in, hook, hook_data);
if (grub_cpio_find_file (data, &name, &mtime, &ofs, &mode))
goto fail;
if (!ofs)
break;
if (grub_memcmp (path, name, len) == 0
&& (name[len] == 0 || name[len] == '/' || len == 0))
{
char *p, *n;
n = name + len;
while (*n == '/')
n++;
p = grub_strchr (n, '/');
if (p)
*p = 0;
if (((!prev) || (grub_strcmp (prev, name) != 0)) && *n != 0)
{
struct grub_dirhook_info info;
grub_memset (&info, 0, sizeof (info));
info.dir = (p != NULL) || ((mode & ATTR_TYPE) == ATTR_DIR);
info.mtime = mtime;
info.mtimeset = 1;
if (hook (n, &info, hook_data))
{
grub_free (name);
goto fail;
}
grub_free (prev);
prev = name;
}
else
{
int restart = 0;
err = handle_symlink (data, name, &path, mode, &restart);
grub_free (name);
if (err)
goto fail;
if (restart)
{
len = grub_strlen (path);
if (++symlinknest == 8)
{
grub_error (GRUB_ERR_SYMLINK_LOOP,
N_("too deep nesting of symlinks"));
goto fail;
}
ofs = 0;
}
}
}
else
grub_free (name);
data->hofs = ofs;
}
fail:
grub_free (path);
grub_free (prev);
#ifdef MODE_USTAR
grub_free (data->linkname);
#endif
grub_free (data);
grub_dl_unref (my_mod);
return grub_errno;
return err;
}
static grub_err_t
grub_cpio_open (grub_file_t file, const char *name_in)
{
struct grub_cpio_data *data;
grub_disk_addr_t ofs;
char *fn;
char *name = grub_strdup (name_in + 1);
int symlinknest = 0;
if (!name)
return grub_errno;
canonicalize (name);
grub_dl_ref (my_mod);
struct grub_archelp_data *data;
grub_err_t err;
data = grub_cpio_mount (file->device->disk);
if (!data)
return grub_errno;
err = grub_archelp_open (data, &arcops, name_in);
if (err)
{
grub_free (name);
return grub_errno;
grub_free (data);
}
data->hofs = 0;
while (1)
else
{
grub_uint32_t mode;
int restart;
if (grub_cpio_find_file (data, &fn, NULL, &ofs, &mode))
goto fail;
if (!ofs)
{
grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), name_in);
break;
}
if (handle_symlink (data, fn, &name, mode, &restart))
{
grub_free (fn);
goto fail;
}
if (restart)
{
ofs = 0;
if (++symlinknest == 8)
{
grub_error (GRUB_ERR_SYMLINK_LOOP,
N_("too deep nesting of symlinks"));
goto fail;
}
goto no_match;
}
if (grub_strcmp (name, fn) != 0)
goto no_match;
file->data = data;
file->size = data->size;
grub_free (fn);
grub_free (name);
return GRUB_ERR_NONE;
no_match:
grub_free (fn);
data->hofs = ofs;
}
fail:
#ifdef MODE_USTAR
grub_free (data->linkname);
#endif
grub_free (data);
grub_free (name);
grub_dl_unref (my_mod);
return grub_errno;
return err;
}
static grub_ssize_t
grub_cpio_read (grub_file_t file, char *buf, grub_size_t len)
{
struct grub_cpio_data *data;
struct grub_archelp_data *data;
data = file->data;
return (grub_disk_read (data->disk, 0, data->dofs + file->offset,
@ -585,16 +209,11 @@ grub_cpio_read (grub_file_t file, char *buf, grub_size_t len)
static grub_err_t
grub_cpio_close (grub_file_t file)
{
struct grub_cpio_data *data;
struct grub_archelp_data *data;
data = file->data;
#ifdef MODE_USTAR
grub_free (data->linkname);
#endif
grub_free (data);
grub_dl_unref (my_mod);
return grub_errno;
}

View file

@ -65,7 +65,6 @@ read_number (const char *str, grub_size_t size)
GRUB_MOD_INIT (newc)
{
grub_fs_register (&grub_cpio_fs);
my_mod = mod;
}
GRUB_MOD_FINI (newc)

View file

@ -20,6 +20,7 @@
#include <grub/misc.h>
#define ALIGN_CPIO(x) x
#define MAGIC "070707"
struct head
{
@ -52,7 +53,6 @@ read_number (const char *str, grub_size_t size)
GRUB_MOD_INIT (odc)
{
grub_fs_register (&grub_cpio_fs);
my_mod = mod;
}
GRUB_MOD_FINI (odc)

View file

@ -18,8 +18,15 @@
*/
#include <grub/misc.h>
#include <grub/disk.h>
#include <grub/archelp.h>
#define MODE_USTAR 1
#include <grub/file.h>
#include <grub/mm.h>
#include <grub/dl.h>
#include <grub/i18n.h>
GRUB_MOD_LICENSE ("GPLv3+");
/* tar support */
#define MAGIC "ustar"
@ -52,14 +59,277 @@ read_number (const char *str, grub_size_t size)
return ret;
}
#define FSNAME "tarfs"
struct grub_archelp_data
{
grub_disk_t disk;
grub_off_t hofs, next_hofs;
grub_off_t dofs;
grub_off_t size;
char *linkname;
grub_size_t linkname_alloc;
};
#include "cpio_common.c"
static grub_err_t
grub_cpio_find_file (struct grub_archelp_data *data, char **name,
grub_int32_t *mtime,
grub_uint32_t *mode)
{
struct head hd;
int reread = 0, have_longname = 0, have_longlink = 0;
data->hofs = data->next_hofs;
for (reread = 0; reread < 3; reread++)
{
if (grub_disk_read (data->disk, 0, data->hofs, sizeof (hd), &hd))
return grub_errno;
if (!hd.name[0] && !hd.prefix[0])
{
*mode = GRUB_ARCHELP_ATTR_END;
return GRUB_ERR_NONE;
}
if (grub_memcmp (hd.magic, MAGIC, sizeof (MAGIC) - 1))
return grub_error (GRUB_ERR_BAD_FS, "invalid tar archive");
if (hd.typeflag == 'L')
{
grub_err_t err;
grub_size_t namesize = read_number (hd.size, sizeof (hd.size));
*name = grub_malloc (namesize + 1);
if (*name == NULL)
return grub_errno;
err = grub_disk_read (data->disk, 0,
data->hofs + GRUB_DISK_SECTOR_SIZE, namesize,
*name);
(*name)[namesize] = 0;
if (err)
return err;
data->hofs += GRUB_DISK_SECTOR_SIZE
+ ((namesize + GRUB_DISK_SECTOR_SIZE - 1) &
~(GRUB_DISK_SECTOR_SIZE - 1));
have_longname = 1;
continue;
}
if (hd.typeflag == 'K')
{
grub_err_t err;
grub_size_t linksize = read_number (hd.size, sizeof (hd.size));
if (data->linkname_alloc < linksize + 1)
{
char *n;
n = grub_malloc (2 * (linksize + 1));
if (!n)
return grub_errno;
grub_free (data->linkname);
data->linkname = n;
data->linkname_alloc = 2 * (linksize + 1);
}
err = grub_disk_read (data->disk, 0,
data->hofs + GRUB_DISK_SECTOR_SIZE, linksize,
data->linkname);
if (err)
return err;
data->linkname[linksize] = 0;
data->hofs += GRUB_DISK_SECTOR_SIZE
+ ((linksize + GRUB_DISK_SECTOR_SIZE - 1) &
~(GRUB_DISK_SECTOR_SIZE - 1));
have_longlink = 1;
continue;
}
if (!have_longname)
{
grub_size_t extra_size = 0;
while (extra_size < sizeof (hd.prefix)
&& hd.prefix[extra_size])
extra_size++;
*name = grub_malloc (sizeof (hd.name) + extra_size + 2);
if (*name == NULL)
return grub_errno;
if (hd.prefix[0])
{
grub_memcpy (*name, hd.prefix, extra_size);
(*name)[extra_size++] = '/';
}
grub_memcpy (*name + extra_size, hd.name, sizeof (hd.name));
(*name)[extra_size + sizeof (hd.name)] = 0;
}
data->size = read_number (hd.size, sizeof (hd.size));
data->dofs = data->hofs + GRUB_DISK_SECTOR_SIZE;
data->next_hofs = data->dofs + ((data->size + GRUB_DISK_SECTOR_SIZE - 1) &
~(GRUB_DISK_SECTOR_SIZE - 1));
if (mtime)
*mtime = read_number (hd.mtime, sizeof (hd.mtime));
if (mode)
{
*mode = read_number (hd.mode, sizeof (hd.mode));
switch (hd.typeflag)
{
/* Hardlink. */
case '1':
/* Symlink. */
case '2':
*mode |= GRUB_ARCHELP_ATTR_LNK;
break;
case '0':
*mode |= GRUB_ARCHELP_ATTR_FILE;
break;
case '5':
*mode |= GRUB_ARCHELP_ATTR_DIR;
break;
}
}
if (!have_longlink)
{
if (data->linkname_alloc < 101)
{
char *n;
n = grub_malloc (101);
if (!n)
return grub_errno;
grub_free (data->linkname);
data->linkname = n;
data->linkname_alloc = 101;
}
grub_memcpy (data->linkname, hd.linkname, sizeof (hd.linkname));
data->linkname[100] = 0;
}
return GRUB_ERR_NONE;
}
return GRUB_ERR_NONE;
}
static char *
grub_cpio_get_link_target (struct grub_archelp_data *data)
{
return grub_strdup (data->linkname);
}
static void
grub_cpio_rewind (struct grub_archelp_data *data)
{
data->next_hofs = 0;
}
static struct grub_archelp_ops arcops =
{
.find_file = grub_cpio_find_file,
.get_link_target = grub_cpio_get_link_target,
.rewind = grub_cpio_rewind
};
static struct grub_archelp_data *
grub_cpio_mount (grub_disk_t disk)
{
struct head hd;
struct grub_archelp_data *data;
if (grub_disk_read (disk, 0, 0, sizeof (hd), &hd))
goto fail;
if (grub_memcmp (hd.magic, MAGIC, sizeof (MAGIC) - 1))
goto fail;
data = (struct grub_archelp_data *) grub_zalloc (sizeof (*data));
if (!data)
goto fail;
data->disk = disk;
return data;
fail:
grub_error (GRUB_ERR_BAD_FS, "not a tarfs filesystem");
return 0;
}
static grub_err_t
grub_cpio_dir (grub_device_t device, const char *path_in,
grub_fs_dir_hook_t hook, void *hook_data)
{
struct grub_archelp_data *data;
grub_err_t err;
data = grub_cpio_mount (device->disk);
if (!data)
return grub_errno;
err = grub_archelp_dir (data, &arcops,
path_in, hook, hook_data);
grub_free (data->linkname);
grub_free (data);
return err;
}
static grub_err_t
grub_cpio_open (grub_file_t file, const char *name_in)
{
struct grub_archelp_data *data;
grub_err_t err;
data = grub_cpio_mount (file->device->disk);
if (!data)
return grub_errno;
err = grub_archelp_open (data, &arcops, name_in);
if (err)
{
grub_free (data->linkname);
grub_free (data);
}
else
{
file->data = data;
file->size = data->size;
}
return err;
}
static grub_ssize_t
grub_cpio_read (grub_file_t file, char *buf, grub_size_t len)
{
struct grub_archelp_data *data;
data = file->data;
return (grub_disk_read (data->disk, 0, data->dofs + file->offset,
len, buf)) ? -1 : (grub_ssize_t) len;
}
static grub_err_t
grub_cpio_close (grub_file_t file)
{
struct grub_archelp_data *data;
data = file->data;
grub_free (data->linkname);
grub_free (data);
return grub_errno;
}
static struct grub_fs grub_cpio_fs = {
.name = "tarfs",
.dir = grub_cpio_dir,
.open = grub_cpio_open,
.read = grub_cpio_read,
.close = grub_cpio_close,
#ifdef GRUB_UTIL
.reserved_first_sector = 0,
.blocklist_install = 0,
#endif
};
GRUB_MOD_INIT (tar)
{
grub_fs_register (&grub_cpio_fs);
my_mod = mod;
}
GRUB_MOD_FINI (tar)