1 /* fshelp.c -- Filesystem helper functions */
3 * GRUB -- GRand Unified Bootloader
4 * Copyright (C) 2004,2005,2006,2007,2008 Free Software Foundation, Inc.
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 * (at your option) any later version.
11 * GRUB is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
22 #include <grub/misc.h>
23 #include <grub/disk.h>
24 #include <grub/fshelp.h>
26 #include <grub/i18n.h>
28 GRUB_MOD_LICENSE ("GPLv3+");
30 typedef int (*iterate_dir_func
) (grub_fshelp_node_t dir
,
31 grub_fshelp_iterate_dir_hook_t hook
,
33 typedef grub_err_t (*lookup_file_func
) (grub_fshelp_node_t dir
,
35 grub_fshelp_node_t
*foundnode
,
36 enum grub_fshelp_filetype
*foundtype
);
37 typedef char *(*read_symlink_func
) (grub_fshelp_node_t node
);
39 struct stack_element
{
40 struct stack_element
*parent
;
41 grub_fshelp_node_t node
;
42 enum grub_fshelp_filetype type
;
45 /* Context for grub_fshelp_find_file. */
46 struct grub_fshelp_find_file_ctx
50 grub_fshelp_node_t rootnode
;
55 /* Current file being traversed and its parents. */
56 struct stack_element
*currnode
;
59 /* Helper for find_file_iter. */
61 free_node (grub_fshelp_node_t node
, struct grub_fshelp_find_file_ctx
*ctx
)
63 if (node
!= ctx
->rootnode
)
68 pop_element (struct grub_fshelp_find_file_ctx
*ctx
)
70 struct stack_element
*el
;
72 ctx
->currnode
= el
->parent
;
73 free_node (el
->node
, ctx
);
78 free_stack (struct grub_fshelp_find_file_ctx
*ctx
)
85 go_up_a_level (struct grub_fshelp_find_file_ctx
*ctx
)
87 if (!ctx
->currnode
->parent
)
93 push_node (struct grub_fshelp_find_file_ctx
*ctx
, grub_fshelp_node_t node
, enum grub_fshelp_filetype filetype
)
95 struct stack_element
*nst
;
96 nst
= grub_malloc (sizeof (*nst
));
100 nst
->type
= filetype
& ~GRUB_FSHELP_CASE_INSENSITIVE
;
101 nst
->parent
= ctx
->currnode
;
103 return GRUB_ERR_NONE
;
107 go_to_root (struct grub_fshelp_find_file_ctx
*ctx
)
110 return push_node (ctx
, ctx
->rootnode
, GRUB_FSHELP_DIR
);
113 struct grub_fshelp_find_file_iter_ctx
116 grub_fshelp_node_t
*foundnode
;
117 enum grub_fshelp_filetype
*foundtype
;
120 /* Helper for grub_fshelp_find_file. */
122 find_file_iter (const char *filename
, enum grub_fshelp_filetype filetype
,
123 grub_fshelp_node_t node
, void *data
)
125 struct grub_fshelp_find_file_iter_ctx
*ctx
= data
;
127 if (filetype
== GRUB_FSHELP_UNKNOWN
||
128 ((filetype
& GRUB_FSHELP_CASE_INSENSITIVE
)
129 ? grub_strcasecmp (ctx
->name
, filename
)
130 : grub_strcmp (ctx
->name
, filename
)))
136 /* The node is found, stop iterating over the nodes. */
137 *ctx
->foundnode
= node
;
138 *ctx
->foundtype
= filetype
;
143 directory_find_file (grub_fshelp_node_t node
, const char *name
, grub_fshelp_node_t
*foundnode
,
144 enum grub_fshelp_filetype
*foundtype
, iterate_dir_func iterate_dir
)
147 struct grub_fshelp_find_file_iter_ctx ctx
= {
148 .foundnode
= foundnode
,
149 .foundtype
= foundtype
,
152 found
= iterate_dir (node
, find_file_iter
, &ctx
);
158 return GRUB_ERR_NONE
;
162 find_file (char *currpath
,
163 iterate_dir_func iterate_dir
, lookup_file_func lookup_file
,
164 read_symlink_func read_symlink
,
165 struct grub_fshelp_find_file_ctx
*ctx
)
169 for (name
= currpath
; ; name
= next
)
172 grub_fshelp_node_t foundnode
= NULL
;
173 enum grub_fshelp_filetype foundtype
= 0;
175 /* Remove all leading slashes. */
179 /* Found the node! */
183 /* Extract the actual part from the pathname. */
184 for (next
= name
; *next
&& *next
!= '/'; next
++);
186 /* At this point it is expected that the current node is a
187 directory, check if this is true. */
188 if (ctx
->currnode
->type
!= GRUB_FSHELP_DIR
)
189 return grub_error (GRUB_ERR_BAD_FILE_TYPE
, N_("not a directory"));
191 /* Don't rely on fs providing actual . in the listing. */
192 if (next
- name
== 1 && name
[0] == '.')
195 /* Don't rely on fs providing actual .. in the listing. */
196 if (next
- name
== 2 && name
[0] == '.' && name
[1] == '.')
202 /* Iterate over the directory. */
206 err
= lookup_file (ctx
->currnode
->node
, name
, &foundnode
, &foundtype
);
208 err
= directory_find_file (ctx
->currnode
->node
, name
, &foundnode
, &foundtype
, iterate_dir
);
217 push_node (ctx
, foundnode
, foundtype
);
219 /* Read in the symlink and follow it. */
220 if (ctx
->currnode
->type
== GRUB_FSHELP_SYMLINK
)
224 /* Test if the symlink does not loop. */
225 if (++ctx
->symlinknest
== 8)
226 return grub_error (GRUB_ERR_SYMLINK_LOOP
,
227 N_("too deep nesting of symlinks"));
229 symlink
= read_symlink (ctx
->currnode
->node
);
234 /* The symlink is an absolute path, go back to the root inode. */
235 if (symlink
[0] == '/')
237 err
= go_to_root (ctx
);
243 /* Get from symlink to containing directory. */
248 /* Lookup the node the symlink points to. */
249 find_file (symlink
, iterate_dir
, lookup_file
, read_symlink
, ctx
);
257 return grub_error (GRUB_ERR_FILE_NOT_FOUND
, N_("file `%s' not found"),
262 grub_fshelp_find_file_real (const char *path
, grub_fshelp_node_t rootnode
,
263 grub_fshelp_node_t
*foundnode
,
264 iterate_dir_func iterate_dir
,
265 lookup_file_func lookup_file
,
266 read_symlink_func read_symlink
,
267 enum grub_fshelp_filetype expecttype
)
269 struct grub_fshelp_find_file_ctx ctx
= {
271 .rootnode
= rootnode
,
276 enum grub_fshelp_filetype foundtype
;
279 if (!path
|| path
[0] != '/')
281 return grub_error (GRUB_ERR_BAD_FILENAME
, N_("invalid file name `%s'"), path
);
284 err
= go_to_root (&ctx
);
288 duppath
= grub_strdup (path
);
291 err
= find_file (duppath
, iterate_dir
, lookup_file
, read_symlink
, &ctx
);
299 *foundnode
= ctx
.currnode
->node
;
300 foundtype
= ctx
.currnode
->type
;
301 /* Avoid the node being freed. */
302 ctx
.currnode
->node
= 0;
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"));
314 /* Lookup the node PATH. The node ROOTNODE describes the root of the
315 directory tree. The node found is returned in FOUNDNODE, which is
316 either a ROOTNODE or a new malloc'ed node. ITERATE_DIR is used to
317 iterate over all directory entries in the current node.
318 READ_SYMLINK is used to read the symlink if a node is a symlink.
319 EXPECTTYPE is the type node that is expected by the called, an
320 error is generated if the node is not of the expected type. */
322 grub_fshelp_find_file (const char *path
, grub_fshelp_node_t rootnode
,
323 grub_fshelp_node_t
*foundnode
,
324 iterate_dir_func iterate_dir
,
325 read_symlink_func read_symlink
,
326 enum grub_fshelp_filetype expecttype
)
328 return grub_fshelp_find_file_real (path
, rootnode
, foundnode
,
330 read_symlink
, expecttype
);
335 grub_fshelp_find_file_lookup (const char *path
, grub_fshelp_node_t rootnode
,
336 grub_fshelp_node_t
*foundnode
,
337 lookup_file_func lookup_file
,
338 read_symlink_func read_symlink
,
339 enum grub_fshelp_filetype expecttype
)
341 return grub_fshelp_find_file_real (path
, rootnode
, foundnode
,
343 read_symlink
, expecttype
);
347 /* Read LEN bytes from the file NODE on disk DISK into the buffer BUF,
348 beginning with the block POS. READ_HOOK should be set before
349 reading a block from the file. READ_HOOK_DATA is passed through as
350 the DATA argument to READ_HOOK. GET_BLOCK is used to translate
351 file blocks to disk blocks. The file is FILESIZE bytes big and the
352 blocks have a size of LOG2BLOCKSIZE (in log2). */
354 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 grub_off_t pos
, grub_size_t len
, char *buf
,
357 grub_disk_addr_t (*get_block
) (grub_fshelp_node_t node
,
358 grub_disk_addr_t block
),
359 grub_off_t filesize
, int log2blocksize
,
360 grub_disk_addr_t blocks_start
)
362 grub_disk_addr_t i
, blockcnt
;
363 int blocksize
= 1 << (log2blocksize
+ GRUB_DISK_SECTOR_BITS
);
367 grub_error (GRUB_ERR_OUT_OF_RANGE
,
368 N_("attempt to read past the end of file"));
372 /* Adjust LEN so it we can't read past the end of the file. */
373 if (pos
+ len
> filesize
)
374 len
= filesize
- pos
;
376 blockcnt
= ((len
+ pos
) + blocksize
- 1) >> (log2blocksize
+ GRUB_DISK_SECTOR_BITS
);
378 for (i
= pos
>> (log2blocksize
+ GRUB_DISK_SECTOR_BITS
); i
< blockcnt
; i
++)
380 grub_disk_addr_t blknr
;
381 int blockoff
= pos
& (blocksize
- 1);
382 int blockend
= blocksize
;
386 blknr
= get_block (node
, i
);
390 blknr
= blknr
<< log2blocksize
;
393 if (i
== blockcnt
- 1)
395 blockend
= (len
+ pos
) & (blocksize
- 1);
397 /* The last portion is exactly blocksize. */
399 blockend
= blocksize
;
403 if (i
== (pos
>> (log2blocksize
+ GRUB_DISK_SECTOR_BITS
)))
405 skipfirst
= blockoff
;
406 blockend
-= skipfirst
;
409 /* If the block number is 0 this block is not stored on disk but
410 is zero filled instead. */
413 disk
->read_hook
= read_hook
;
414 disk
->read_hook_data
= read_hook_data
;
416 grub_disk_read (disk
, blknr
+ blocks_start
, skipfirst
,
422 else if (read_hook
!= (grub_disk_read_hook_t
)grub_disk_blocklist_read
)
423 grub_memset (buf
, 0, blockend
);
425 buf
+= blocksize
- skipfirst
;