fshelp.c 11.4 KB
Newer Older
1 2 3
/* fshelp.c -- Filesystem helper functions */
/*
 *  GRUB  --  GRand Unified Bootloader
4
 *  Copyright (C) 2004,2005,2006,2007,2008  Free Software Foundation, Inc.
5
 *
6
 *  GRUB is free software: you can redistribute it and/or modify
7
 *  it under the terms of the GNU General Public License as published by
8
 *  the Free Software Foundation, either version 3 of the License, or
9 10
 *  (at your option) any later version.
 *
11
 *  GRUB is distributed in the hope that it will be useful,
12 13 14 15 16
 *  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
17
 *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
18 19 20 21 22 23 24
 */

#include <grub/err.h>
#include <grub/mm.h>
#include <grub/misc.h>
#include <grub/disk.h>
#include <grub/fshelp.h>
25
#include <grub/dl.h>
26
#include <grub/i18n.h>
27

28
GRUB_MOD_LICENSE ("GPLv3+");
29

30 31 32
typedef int (*iterate_dir_func) (grub_fshelp_node_t dir,
				 grub_fshelp_iterate_dir_hook_t hook,
				 void *data);
33 34 35 36
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);
37 38
typedef char *(*read_symlink_func) (grub_fshelp_node_t node);

39 40 41 42 43 44
struct stack_element {
  struct stack_element *parent;
  grub_fshelp_node_t node;
  enum grub_fshelp_filetype type;
};

45 46
/* Context for grub_fshelp_find_file.  */
struct grub_fshelp_find_file_ctx
47
{
48
  /* Inputs.  */
49
  const char *path;
50 51 52
  grub_fshelp_node_t rootnode;

  /* Global options. */
53
  int symlinknest;
54 55 56

  /* Current file being traversed and its parents.  */
  struct stack_element *currnode;
57 58 59 60 61 62
};

/* Helper for find_file_iter.  */
static void
free_node (grub_fshelp_node_t node, struct grub_fshelp_find_file_ctx *ctx)
{
63
  if (node != ctx->rootnode)
64 65
    grub_free (node);
}
66

67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
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;
};

120 121 122 123 124
/* Helper for grub_fshelp_find_file.  */
static int
find_file_iter (const char *filename, enum grub_fshelp_filetype filetype,
		grub_fshelp_node_t node, void *data)
{
125
  struct grub_fshelp_find_file_iter_ctx *ctx = data;
126

127
  if (filetype == GRUB_FSHELP_UNKNOWN ||
128
      ((filetype & GRUB_FSHELP_CASE_INSENSITIVE)
129 130
       ? grub_strcasecmp (ctx->name, filename)
       : grub_strcmp (ctx->name, filename)))
131
    {
132 133 134
      grub_free (node);
      return 0;
    }
135

136
  /* The node is found, stop iterating over the nodes.  */
137 138
  *ctx->foundnode = node;
  *ctx->foundtype = filetype;
139 140
  return 1;
}
141

142
static grub_err_t
143 144
directory_find_file (grub_fshelp_node_t node, const char *name, grub_fshelp_node_t *foundnode,
		     enum grub_fshelp_filetype *foundtype, iterate_dir_func iterate_dir)
145
{
146 147 148 149 150 151 152 153 154 155 156 157 158 159
  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;
}
160

161 162 163 164 165 166 167 168 169
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)
{
  char *name, *next;
  grub_err_t err;
  for (name = currpath; ; name = next)
170
    {
171 172 173
      char c;
      grub_fshelp_node_t foundnode = NULL;
      enum grub_fshelp_filetype foundtype = 0;
174

175
      /* Remove all leading slashes.  */
176 177
      while (*name == '/')
	name++;
178 179

      /* Found the node!  */
180 181
      if (! *name)
	return 0;
182

183
      /* Extract the actual part from the pathname.  */
184
      for (next = name; *next && *next != '/'; next++);
185

186 187
      /* At this point it is expected that the current node is a
	 directory, check if this is true.  */
188 189
      if (ctx->currnode->type != GRUB_FSHELP_DIR)
	return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a directory"));
190

191 192 193
      /* Don't rely on fs providing actual . in the listing.  */
      if (next - name == 1 && name[0] == '.')
	continue;
194

195 196 197 198 199
      /* Don't rely on fs providing actual .. in the listing.  */
      if (next - name == 2 && name[0] == '.' && name[1] == '.')
	{
	  go_up_a_level (ctx);
	  continue;
200 201
	}

202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
      /* Iterate over the directory.  */
      c = *next;
      *next = '\0';
      if (lookup_file)
	err = lookup_file (ctx->currnode->node, name, &foundnode, &foundtype);
      else
	err = directory_find_file (ctx->currnode->node, name, &foundnode, &foundtype, iterate_dir);
      *next = c;

      if (err)
	return err;

      if (!foundnode)
	break;

      push_node (ctx, foundnode, foundtype);
 
219
      /* Read in the symlink and follow it.  */
220
      if (ctx->currnode->type == GRUB_FSHELP_SYMLINK)
221 222
	{
	  char *symlink;
223

224 225
	  /* Test if the symlink does not loop.  */
	  if (++ctx->symlinknest == 8)
226 227
	    return grub_error (GRUB_ERR_SYMLINK_LOOP,
			       N_("too deep nesting of symlinks"));
228

229
	  symlink = read_symlink (ctx->currnode->node);
230

231
	  if (!symlink)
232
	    return grub_errno;
233

234 235
	  /* The symlink is an absolute path, go back to the root inode.  */
	  if (symlink[0] == '/')
236
	    {
237 238 239 240 241 242 243 244
	      err = go_to_root (ctx);
	      if (err)
		return err;
	    }
	  else
	    {
	      /* Get from symlink to containing directory. */
	      go_up_a_level (ctx);
245
	    }
246

247

248
	  /* Lookup the node the symlink points to.  */
249
	  find_file (symlink, iterate_dir, lookup_file, read_symlink, ctx);
250
	  grub_free (symlink);
251

252
	  if (grub_errno)
253
	    return grub_errno;
254
	}
255 256
    }

257 258 259 260
  return grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("file `%s' not found"),
		     ctx->path);
}

261 262 263 264 265 266 267
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)
268 269 270 271
{
  struct grub_fshelp_find_file_ctx ctx = {
    .path = path,
    .rootnode = rootnode,
272 273
    .symlinknest = 0,
    .currnode = 0
274 275
  };
  grub_err_t err;
276 277
  enum grub_fshelp_filetype foundtype;
  char *duppath;
278

279 280
  if (!path || path[0] != '/')
    {
281
      return grub_error (GRUB_ERR_BAD_FILENAME, N_("invalid file name `%s'"), path);
282
    }
283

284
  err = go_to_root (&ctx);
285 286
  if (err)
    return err;
287

288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
  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);

305
  /* Check if the node that was found was of the expected type.  */
306
  if (expecttype == GRUB_FSHELP_REG && foundtype != expecttype)
307
    return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a regular file"));
308
  else if (expecttype == GRUB_FSHELP_DIR && foundtype != expecttype)
309
    return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a directory"));
310

311 312 313
  return 0;
}

314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
/* Lookup the node PATH.  The node ROOTNODE describes the root of the
   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
   iterate over all directory entries in the current node.
   READ_SYMLINK is used to read the symlink if a node is a symlink.
   EXPECTTYPE is the type node that is expected by the called, an
   error is generated if the node is not of the expected type.  */
grub_err_t
grub_fshelp_find_file (const char *path, grub_fshelp_node_t rootnode,
		       grub_fshelp_node_t *foundnode,
		       iterate_dir_func iterate_dir,
		       read_symlink_func read_symlink,
		       enum grub_fshelp_filetype expecttype)
{
  return grub_fshelp_find_file_real (path, rootnode, foundnode,
				     iterate_dir, NULL, 
				     read_symlink, expecttype);

}

grub_err_t
grub_fshelp_find_file_lookup (const char *path, grub_fshelp_node_t rootnode,
			      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);

}

347 348
/* 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
349 350 351
   reading a block from the file.  READ_HOOK_DATA is passed through as
   the DATA argument to READ_HOOK.  GET_BLOCK is used to translate
   file blocks to disk blocks.  The file is FILESIZE bytes big and the
352 353 354
   blocks have a size of LOG2BLOCKSIZE (in log2).  */
grub_ssize_t
grub_fshelp_read_file (grub_disk_t disk, grub_fshelp_node_t node,
355
		       grub_disk_read_hook_t read_hook, void *read_hook_data,
356 357 358
		       grub_off_t pos, grub_size_t len, char *buf,
		       grub_disk_addr_t (*get_block) (grub_fshelp_node_t node,
                                                      grub_disk_addr_t block),
359 360
		       grub_off_t filesize, int log2blocksize,
		       grub_disk_addr_t blocks_start)
361
{
362
  grub_disk_addr_t i, blockcnt;
363 364
  int blocksize = 1 << (log2blocksize + GRUB_DISK_SECTOR_BITS);

365 366 367 368 369 370 371
  if (pos > filesize)
    {
      grub_error (GRUB_ERR_OUT_OF_RANGE,
		  N_("attempt to read past the end of file"));
      return -1;
    }

372
  /* Adjust LEN so it we can't read past the end of the file.  */
373 374
  if (pos + len > filesize)
    len = filesize - pos;
375

376
  blockcnt = ((len + pos) + blocksize - 1) >> (log2blocksize + GRUB_DISK_SECTOR_BITS);
377

378
  for (i = pos >> (log2blocksize + GRUB_DISK_SECTOR_BITS); i < blockcnt; i++)
379
    {
380 381
      grub_disk_addr_t blknr;
      int blockoff = pos & (blocksize - 1);
382 383 384 385 386 387 388
      int blockend = blocksize;

      int skipfirst = 0;

      blknr = get_block (node, i);
      if (grub_errno)
	return -1;
389

390 391 392 393 394
      blknr = blknr << log2blocksize;

      /* Last block.  */
      if (i == blockcnt - 1)
	{
395
	  blockend = (len + pos) & (blocksize - 1);
396

397
	  /* The last portion is exactly blocksize.  */
398
	  if (! blockend)
399 400 401 402
	    blockend = blocksize;
	}

      /* First block.  */
403
      if (i == (pos >> (log2blocksize + GRUB_DISK_SECTOR_BITS)))
404 405 406 407
	{
	  skipfirst = blockoff;
	  blockend -= skipfirst;
	}
408

409 410 411 412
      /* If the block number is 0 this block is not stored on disk but
	 is zero filled instead.  */
      if (blknr)
	{
413
	  disk->read_hook = read_hook;
414
	  disk->read_hook_data = read_hook_data;
415

416
	  grub_disk_read (disk, blknr + blocks_start, skipfirst,
417 418 419 420 421 422
			  blockend, buf);
	  disk->read_hook = 0;
	  if (grub_errno)
	    return -1;
	}
      else
423
	grub_memset (buf, 0, blockend);
424 425 426 427 428 429

      buf += blocksize - skipfirst;
    }

  return len;
}