fshelp: Add handling of "." and ".." and grub_fshelp_find_file_lookup.
Recent tests have discovered that many of our filesystems have flawed handling of "." and "..". Rather than attempting to fix it in filesystems themselves, make the common code fshelp aware of "." and ".." and handle them in this layer. Add grub_fshelp_find_file_lookup for easy conversion of BFS, HFS and exFAT which have the same problem and don't use fshelp.
This commit is contained in:
parent
4622f4e1ee
commit
fa93b0e4f5
2 changed files with 228 additions and 107 deletions
|
@ -30,169 +30,287 @@ GRUB_MOD_LICENSE ("GPLv3+");
|
||||||
typedef int (*iterate_dir_func) (grub_fshelp_node_t dir,
|
typedef int (*iterate_dir_func) (grub_fshelp_node_t dir,
|
||||||
grub_fshelp_iterate_dir_hook_t hook,
|
grub_fshelp_iterate_dir_hook_t hook,
|
||||||
void *data);
|
void *data);
|
||||||
|
typedef grub_err_t (*lookup_file_func) (grub_fshelp_node_t dir,
|
||||||
|
const char *name,
|
||||||
|
grub_fshelp_node_t *foundnode,
|
||||||
|
enum grub_fshelp_filetype *foundtype);
|
||||||
typedef char *(*read_symlink_func) (grub_fshelp_node_t node);
|
typedef char *(*read_symlink_func) (grub_fshelp_node_t node);
|
||||||
|
|
||||||
|
struct stack_element {
|
||||||
|
struct stack_element *parent;
|
||||||
|
grub_fshelp_node_t node;
|
||||||
|
enum grub_fshelp_filetype type;
|
||||||
|
};
|
||||||
|
|
||||||
/* Context for grub_fshelp_find_file. */
|
/* Context for grub_fshelp_find_file. */
|
||||||
struct grub_fshelp_find_file_ctx
|
struct grub_fshelp_find_file_ctx
|
||||||
{
|
{
|
||||||
|
/* Inputs. */
|
||||||
const char *path;
|
const char *path;
|
||||||
grub_fshelp_node_t rootnode, currroot, currnode, oldnode;
|
grub_fshelp_node_t rootnode;
|
||||||
enum grub_fshelp_filetype foundtype;
|
|
||||||
|
/* Global options. */
|
||||||
int symlinknest;
|
int symlinknest;
|
||||||
const char *name;
|
|
||||||
const char *next;
|
/* Current file being traversed and its parents. */
|
||||||
enum grub_fshelp_filetype type;
|
struct stack_element *currnode;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Helper for find_file_iter. */
|
/* Helper for find_file_iter. */
|
||||||
static void
|
static void
|
||||||
free_node (grub_fshelp_node_t node, struct grub_fshelp_find_file_ctx *ctx)
|
free_node (grub_fshelp_node_t node, struct grub_fshelp_find_file_ctx *ctx)
|
||||||
{
|
{
|
||||||
if (node != ctx->rootnode && node != ctx->currroot)
|
if (node != ctx->rootnode)
|
||||||
grub_free (node);
|
grub_free (node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
pop_element (struct grub_fshelp_find_file_ctx *ctx)
|
||||||
|
{
|
||||||
|
struct stack_element *el;
|
||||||
|
el = ctx->currnode;
|
||||||
|
ctx->currnode = el->parent;
|
||||||
|
free_node (el->node, ctx);
|
||||||
|
grub_free (el);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
free_stack (struct grub_fshelp_find_file_ctx *ctx)
|
||||||
|
{
|
||||||
|
while (ctx->currnode)
|
||||||
|
pop_element (ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
go_up_a_level (struct grub_fshelp_find_file_ctx *ctx)
|
||||||
|
{
|
||||||
|
if (!ctx->currnode->parent)
|
||||||
|
return;
|
||||||
|
pop_element (ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static grub_err_t
|
||||||
|
push_node (struct grub_fshelp_find_file_ctx *ctx, grub_fshelp_node_t node, enum grub_fshelp_filetype filetype)
|
||||||
|
{
|
||||||
|
struct stack_element *nst;
|
||||||
|
nst = grub_malloc (sizeof (*nst));
|
||||||
|
if (!nst)
|
||||||
|
return grub_errno;
|
||||||
|
nst->node = node;
|
||||||
|
nst->type = filetype & ~GRUB_FSHELP_CASE_INSENSITIVE;
|
||||||
|
nst->parent = ctx->currnode;
|
||||||
|
ctx->currnode = nst;
|
||||||
|
return GRUB_ERR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static grub_err_t
|
||||||
|
go_to_root (struct grub_fshelp_find_file_ctx *ctx)
|
||||||
|
{
|
||||||
|
free_stack (ctx);
|
||||||
|
return push_node (ctx, ctx->rootnode, GRUB_FSHELP_DIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct grub_fshelp_find_file_iter_ctx
|
||||||
|
{
|
||||||
|
const char *name;
|
||||||
|
grub_fshelp_node_t *foundnode;
|
||||||
|
enum grub_fshelp_filetype *foundtype;
|
||||||
|
};
|
||||||
|
|
||||||
/* Helper for grub_fshelp_find_file. */
|
/* Helper for grub_fshelp_find_file. */
|
||||||
static int
|
static int
|
||||||
find_file_iter (const char *filename, enum grub_fshelp_filetype filetype,
|
find_file_iter (const char *filename, enum grub_fshelp_filetype filetype,
|
||||||
grub_fshelp_node_t node, void *data)
|
grub_fshelp_node_t node, void *data)
|
||||||
{
|
{
|
||||||
struct grub_fshelp_find_file_ctx *ctx = data;
|
struct grub_fshelp_find_file_iter_ctx *ctx = data;
|
||||||
|
|
||||||
if (filetype == GRUB_FSHELP_UNKNOWN ||
|
if (filetype == GRUB_FSHELP_UNKNOWN ||
|
||||||
((filetype & GRUB_FSHELP_CASE_INSENSITIVE)
|
((filetype & GRUB_FSHELP_CASE_INSENSITIVE)
|
||||||
? grub_strncasecmp (ctx->name, filename, ctx->next - ctx->name)
|
? grub_strcasecmp (ctx->name, filename)
|
||||||
: grub_strncmp (ctx->name, filename, ctx->next - ctx->name))
|
: grub_strcmp (ctx->name, filename)))
|
||||||
|| filename[ctx->next - ctx->name])
|
|
||||||
{
|
{
|
||||||
grub_free (node);
|
grub_free (node);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The node is found, stop iterating over the nodes. */
|
/* The node is found, stop iterating over the nodes. */
|
||||||
ctx->type = filetype & ~GRUB_FSHELP_CASE_INSENSITIVE;
|
*ctx->foundnode = node;
|
||||||
ctx->oldnode = ctx->currnode;
|
*ctx->foundtype = filetype;
|
||||||
ctx->currnode = node;
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static grub_err_t
|
static grub_err_t
|
||||||
find_file (const char *currpath, grub_fshelp_node_t currroot,
|
directory_find_file (grub_fshelp_node_t node, const char *name, grub_fshelp_node_t *foundnode,
|
||||||
grub_fshelp_node_t *currfound,
|
enum grub_fshelp_filetype *foundtype, iterate_dir_func iterate_dir)
|
||||||
iterate_dir_func iterate_dir, read_symlink_func read_symlink,
|
{
|
||||||
|
int found;
|
||||||
|
struct grub_fshelp_find_file_iter_ctx ctx = {
|
||||||
|
.foundnode = foundnode,
|
||||||
|
.foundtype = foundtype,
|
||||||
|
.name = name
|
||||||
|
};
|
||||||
|
found = iterate_dir (node, find_file_iter, &ctx);
|
||||||
|
if (! found)
|
||||||
|
{
|
||||||
|
if (grub_errno)
|
||||||
|
return grub_errno;
|
||||||
|
}
|
||||||
|
return GRUB_ERR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static grub_err_t
|
||||||
|
find_file (char *currpath,
|
||||||
|
iterate_dir_func iterate_dir, lookup_file_func lookup_file,
|
||||||
|
read_symlink_func read_symlink,
|
||||||
struct grub_fshelp_find_file_ctx *ctx)
|
struct grub_fshelp_find_file_ctx *ctx)
|
||||||
{
|
{
|
||||||
ctx->currroot = currroot;
|
char *name, *next;
|
||||||
ctx->name = currpath;
|
grub_err_t err;
|
||||||
ctx->type = GRUB_FSHELP_DIR;
|
for (name = currpath; ; name = next)
|
||||||
ctx->currnode = currroot;
|
|
||||||
ctx->oldnode = currroot;
|
|
||||||
|
|
||||||
for (;;)
|
|
||||||
{
|
{
|
||||||
int found;
|
char c;
|
||||||
|
grub_fshelp_node_t foundnode = NULL;
|
||||||
|
enum grub_fshelp_filetype foundtype = 0;
|
||||||
|
|
||||||
/* Remove all leading slashes. */
|
/* Remove all leading slashes. */
|
||||||
while (*ctx->name == '/')
|
while (*name == '/')
|
||||||
ctx->name++;
|
name++;
|
||||||
|
|
||||||
/* Found the node! */
|
/* Found the node! */
|
||||||
if (! *ctx->name)
|
if (! *name)
|
||||||
{
|
return 0;
|
||||||
*currfound = ctx->currnode;
|
|
||||||
ctx->foundtype = ctx->type;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Extract the actual part from the pathname. */
|
/* Extract the actual part from the pathname. */
|
||||||
for (ctx->next = ctx->name; *ctx->next && *ctx->next != '/'; ctx->next++);
|
for (next = name; *next && *next != '/'; next++);
|
||||||
|
|
||||||
/* At this point it is expected that the current node is a
|
/* At this point it is expected that the current node is a
|
||||||
directory, check if this is true. */
|
directory, check if this is true. */
|
||||||
if (ctx->type != GRUB_FSHELP_DIR)
|
if (ctx->currnode->type != GRUB_FSHELP_DIR)
|
||||||
|
return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a directory"));
|
||||||
|
|
||||||
|
/* Don't rely on fs providing actual . in the listing. */
|
||||||
|
if (next - name == 1 && name[0] == '.')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Don't rely on fs providing actual .. in the listing. */
|
||||||
|
if (next - name == 2 && name[0] == '.' && name[1] == '.')
|
||||||
{
|
{
|
||||||
free_node (ctx->currnode, ctx);
|
go_up_a_level (ctx);
|
||||||
ctx->currnode = 0;
|
continue;
|
||||||
return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a directory"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Iterate over the directory. */
|
/* Iterate over the directory. */
|
||||||
found = iterate_dir (ctx->currnode, find_file_iter, ctx);
|
c = *next;
|
||||||
if (! found)
|
*next = '\0';
|
||||||
{
|
if (lookup_file)
|
||||||
free_node (ctx->currnode, ctx);
|
err = lookup_file (ctx->currnode->node, name, &foundnode, &foundtype);
|
||||||
ctx->currnode = 0;
|
else
|
||||||
if (grub_errno)
|
err = directory_find_file (ctx->currnode->node, name, &foundnode, &foundtype, iterate_dir);
|
||||||
return grub_errno;
|
*next = c;
|
||||||
|
|
||||||
break;
|
if (err)
|
||||||
}
|
return err;
|
||||||
|
|
||||||
|
if (!foundnode)
|
||||||
|
break;
|
||||||
|
|
||||||
|
push_node (ctx, foundnode, foundtype);
|
||||||
|
|
||||||
/* Read in the symlink and follow it. */
|
/* Read in the symlink and follow it. */
|
||||||
if (ctx->type == GRUB_FSHELP_SYMLINK)
|
if (ctx->currnode->type == GRUB_FSHELP_SYMLINK)
|
||||||
{
|
{
|
||||||
char *symlink;
|
char *symlink;
|
||||||
const char *next;
|
|
||||||
|
|
||||||
/* Test if the symlink does not loop. */
|
/* Test if the symlink does not loop. */
|
||||||
if (++ctx->symlinknest == 8)
|
if (++ctx->symlinknest == 8)
|
||||||
{
|
return grub_error (GRUB_ERR_SYMLINK_LOOP,
|
||||||
free_node (ctx->currnode, ctx);
|
N_("too deep nesting of symlinks"));
|
||||||
free_node (ctx->oldnode, ctx);
|
|
||||||
ctx->currnode = 0;
|
|
||||||
ctx->oldnode = 0;
|
|
||||||
return grub_error (GRUB_ERR_SYMLINK_LOOP,
|
|
||||||
N_("too deep nesting of symlinks"));
|
|
||||||
}
|
|
||||||
|
|
||||||
symlink = read_symlink (ctx->currnode);
|
symlink = read_symlink (ctx->currnode->node);
|
||||||
free_node (ctx->currnode, ctx);
|
|
||||||
ctx->currnode = 0;
|
|
||||||
|
|
||||||
if (!symlink)
|
if (!symlink)
|
||||||
{
|
return grub_errno;
|
||||||
free_node (ctx->oldnode, ctx);
|
|
||||||
ctx->oldnode = 0;
|
|
||||||
return grub_errno;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The symlink is an absolute path, go back to the root inode. */
|
/* The symlink is an absolute path, go back to the root inode. */
|
||||||
if (symlink[0] == '/')
|
if (symlink[0] == '/')
|
||||||
{
|
{
|
||||||
free_node (ctx->oldnode, ctx);
|
err = go_to_root (ctx);
|
||||||
ctx->oldnode = ctx->rootnode;
|
if (err)
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Get from symlink to containing directory. */
|
||||||
|
go_up_a_level (ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Lookup the node the symlink points to. */
|
/* Lookup the node the symlink points to. */
|
||||||
next = ctx->next;
|
find_file (symlink, iterate_dir, lookup_file, read_symlink, ctx);
|
||||||
find_file (symlink, ctx->oldnode, &ctx->currnode,
|
|
||||||
iterate_dir, read_symlink, ctx);
|
|
||||||
ctx->next = next;
|
|
||||||
ctx->type = ctx->foundtype;
|
|
||||||
grub_free (symlink);
|
grub_free (symlink);
|
||||||
|
|
||||||
if (grub_errno)
|
if (grub_errno)
|
||||||
{
|
return grub_errno;
|
||||||
free_node (ctx->oldnode, ctx);
|
|
||||||
ctx->oldnode = 0;
|
|
||||||
return grub_errno;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctx->oldnode != ctx->currnode)
|
|
||||||
{
|
|
||||||
free_node (ctx->oldnode, ctx);
|
|
||||||
ctx->oldnode = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx->name = ctx->next;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("file `%s' not found"),
|
return grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("file `%s' not found"),
|
||||||
ctx->path);
|
ctx->path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static grub_err_t
|
||||||
|
grub_fshelp_find_file_real (const char *path, grub_fshelp_node_t rootnode,
|
||||||
|
grub_fshelp_node_t *foundnode,
|
||||||
|
iterate_dir_func iterate_dir,
|
||||||
|
lookup_file_func lookup_file,
|
||||||
|
read_symlink_func read_symlink,
|
||||||
|
enum grub_fshelp_filetype expecttype)
|
||||||
|
{
|
||||||
|
struct grub_fshelp_find_file_ctx ctx = {
|
||||||
|
.path = path,
|
||||||
|
.rootnode = rootnode,
|
||||||
|
.symlinknest = 0,
|
||||||
|
.currnode = 0
|
||||||
|
};
|
||||||
|
grub_err_t err;
|
||||||
|
enum grub_fshelp_filetype foundtype;
|
||||||
|
char *duppath;
|
||||||
|
|
||||||
|
if (!path || path[0] != '/')
|
||||||
|
{
|
||||||
|
return grub_error (GRUB_ERR_BAD_FILENAME, N_("invalid file name `%s'"), path);
|
||||||
|
}
|
||||||
|
|
||||||
|
err = go_to_root (&ctx);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
duppath = grub_strdup (path);
|
||||||
|
if (!duppath)
|
||||||
|
return grub_errno;
|
||||||
|
err = find_file (duppath, iterate_dir, lookup_file, read_symlink, &ctx);
|
||||||
|
grub_free (duppath);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
free_stack (&ctx);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
*foundnode = ctx.currnode->node;
|
||||||
|
foundtype = ctx.currnode->type;
|
||||||
|
/* Avoid the node being freed. */
|
||||||
|
ctx.currnode->node = 0;
|
||||||
|
free_stack (&ctx);
|
||||||
|
|
||||||
|
/* Check if the node that was found was of the expected type. */
|
||||||
|
if (expecttype == GRUB_FSHELP_REG && foundtype != expecttype)
|
||||||
|
return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a regular file"));
|
||||||
|
else if (expecttype == GRUB_FSHELP_DIR && foundtype != expecttype)
|
||||||
|
return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a directory"));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Lookup the node PATH. The node ROOTNODE describes the root of the
|
/* Lookup the node PATH. The node ROOTNODE describes the root of the
|
||||||
directory tree. The node found is returned in FOUNDNODE, which is
|
directory tree. The node found is returned in FOUNDNODE, which is
|
||||||
either a ROOTNODE or a new malloc'ed node. ITERATE_DIR is used to
|
either a ROOTNODE or a new malloc'ed node. ITERATE_DIR is used to
|
||||||
|
@ -207,31 +325,23 @@ grub_fshelp_find_file (const char *path, grub_fshelp_node_t rootnode,
|
||||||
read_symlink_func read_symlink,
|
read_symlink_func read_symlink,
|
||||||
enum grub_fshelp_filetype expecttype)
|
enum grub_fshelp_filetype expecttype)
|
||||||
{
|
{
|
||||||
struct grub_fshelp_find_file_ctx ctx = {
|
return grub_fshelp_find_file_real (path, rootnode, foundnode,
|
||||||
.path = path,
|
iterate_dir, NULL,
|
||||||
.rootnode = rootnode,
|
read_symlink, expecttype);
|
||||||
.foundtype = GRUB_FSHELP_DIR,
|
|
||||||
.symlinknest = 0
|
|
||||||
};
|
|
||||||
grub_err_t err;
|
|
||||||
|
|
||||||
if (!path || path[0] != '/')
|
}
|
||||||
{
|
|
||||||
grub_error (GRUB_ERR_BAD_FILENAME, N_("invalid file name `%s'"), path);
|
|
||||||
return grub_errno;
|
|
||||||
}
|
|
||||||
|
|
||||||
err = find_file (path, rootnode, foundnode, iterate_dir, read_symlink, &ctx);
|
grub_err_t
|
||||||
if (err)
|
grub_fshelp_find_file_lookup (const char *path, grub_fshelp_node_t rootnode,
|
||||||
return err;
|
grub_fshelp_node_t *foundnode,
|
||||||
|
lookup_file_func lookup_file,
|
||||||
|
read_symlink_func read_symlink,
|
||||||
|
enum grub_fshelp_filetype expecttype)
|
||||||
|
{
|
||||||
|
return grub_fshelp_find_file_real (path, rootnode, foundnode,
|
||||||
|
NULL, lookup_file,
|
||||||
|
read_symlink, expecttype);
|
||||||
|
|
||||||
/* Check if the node that was found was of the expected type. */
|
|
||||||
if (expecttype == GRUB_FSHELP_REG && ctx.foundtype != expecttype)
|
|
||||||
return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a regular file"));
|
|
||||||
else if (expecttype == GRUB_FSHELP_DIR && ctx.foundtype != expecttype)
|
|
||||||
return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a directory"));
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Read LEN bytes from the file NODE on disk DISK into the buffer BUF,
|
/* Read LEN bytes from the file NODE on disk DISK into the buffer BUF,
|
||||||
|
|
|
@ -62,6 +62,17 @@ EXPORT_FUNC(grub_fshelp_find_file) (const char *path,
|
||||||
enum grub_fshelp_filetype expect);
|
enum grub_fshelp_filetype expect);
|
||||||
|
|
||||||
|
|
||||||
|
grub_err_t
|
||||||
|
EXPORT_FUNC(grub_fshelp_find_file_lookup) (const char *path,
|
||||||
|
grub_fshelp_node_t rootnode,
|
||||||
|
grub_fshelp_node_t *foundnode,
|
||||||
|
grub_err_t (*lookup_file) (grub_fshelp_node_t dir,
|
||||||
|
const char *name,
|
||||||
|
grub_fshelp_node_t *foundnode,
|
||||||
|
enum grub_fshelp_filetype *foundtype),
|
||||||
|
char *(*read_symlink) (grub_fshelp_node_t node),
|
||||||
|
enum grub_fshelp_filetype expect);
|
||||||
|
|
||||||
/* Read LEN bytes from the file NODE on disk DISK into the buffer BUF,
|
/* Read LEN bytes from the file NODE on disk DISK into the buffer BUF,
|
||||||
beginning with the block POS. READ_HOOK should be set before
|
beginning with the block POS. READ_HOOK should be set before
|
||||||
reading a block from the file. GET_BLOCK is used to translate file
|
reading a block from the file. GET_BLOCK is used to translate file
|
||||||
|
|
Loading…
Reference in a new issue