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 int g_ventoy_case_insensitive
= 0;
122 /* Helper for grub_fshelp_find_file. */
124 find_file_iter (const char *filename
, enum grub_fshelp_filetype filetype
,
125 grub_fshelp_node_t node
, void *data
)
127 struct grub_fshelp_find_file_iter_ctx
*ctx
= data
;
129 if (g_ventoy_case_insensitive
)
131 filetype
|= GRUB_FSHELP_CASE_INSENSITIVE
;
134 if (filetype
== GRUB_FSHELP_UNKNOWN
||
135 ((filetype
& GRUB_FSHELP_CASE_INSENSITIVE
)
136 ? grub_strcasecmp (ctx
->name
, filename
)
137 : grub_strcmp (ctx
->name
, filename
)))
143 /* The node is found, stop iterating over the nodes. */
144 *ctx
->foundnode
= node
;
145 *ctx
->foundtype
= filetype
;
150 directory_find_file (grub_fshelp_node_t node
, const char *name
, grub_fshelp_node_t
*foundnode
,
151 enum grub_fshelp_filetype
*foundtype
, iterate_dir_func iterate_dir
)
154 struct grub_fshelp_find_file_iter_ctx ctx
= {
155 .foundnode
= foundnode
,
156 .foundtype
= foundtype
,
159 found
= iterate_dir (node
, find_file_iter
, &ctx
);
165 return GRUB_ERR_NONE
;
169 find_file (char *currpath
,
170 iterate_dir_func iterate_dir
, lookup_file_func lookup_file
,
171 read_symlink_func read_symlink
,
172 struct grub_fshelp_find_file_ctx
*ctx
)
176 for (name
= currpath
; ; name
= next
)
179 grub_fshelp_node_t foundnode
= NULL
;
180 enum grub_fshelp_filetype foundtype
= 0;
182 /* Remove all leading slashes. */
186 /* Found the node! */
190 /* Extract the actual part from the pathname. */
191 for (next
= name
; *next
&& *next
!= '/'; next
++);
193 /* At this point it is expected that the current node is a
194 directory, check if this is true. */
195 if (ctx
->currnode
->type
!= GRUB_FSHELP_DIR
)
196 return grub_error (GRUB_ERR_BAD_FILE_TYPE
, N_("not a directory"));
198 /* Don't rely on fs providing actual . in the listing. */
199 if (next
- name
== 1 && name
[0] == '.')
202 /* Don't rely on fs providing actual .. in the listing. */
203 if (next
- name
== 2 && name
[0] == '.' && name
[1] == '.')
209 /* Iterate over the directory. */
213 err
= lookup_file (ctx
->currnode
->node
, name
, &foundnode
, &foundtype
);
215 err
= directory_find_file (ctx
->currnode
->node
, name
, &foundnode
, &foundtype
, iterate_dir
);
224 push_node (ctx
, foundnode
, foundtype
);
226 /* Read in the symlink and follow it. */
227 if (ctx
->currnode
->type
== GRUB_FSHELP_SYMLINK
)
231 /* Test if the symlink does not loop. */
232 if (++ctx
->symlinknest
== 8)
233 return grub_error (GRUB_ERR_SYMLINK_LOOP
,
234 N_("too deep nesting of symlinks"));
236 symlink
= read_symlink (ctx
->currnode
->node
);
241 /* The symlink is an absolute path, go back to the root inode. */
242 if (symlink
[0] == '/')
244 err
= go_to_root (ctx
);
250 /* Get from symlink to containing directory. */
255 /* Lookup the node the symlink points to. */
256 find_file (symlink
, iterate_dir
, lookup_file
, read_symlink
, ctx
);
264 return grub_error (GRUB_ERR_FILE_NOT_FOUND
, N_("file `%s' not found"),
269 grub_fshelp_find_file_real (const char *path
, grub_fshelp_node_t rootnode
,
270 grub_fshelp_node_t
*foundnode
,
271 iterate_dir_func iterate_dir
,
272 lookup_file_func lookup_file
,
273 read_symlink_func read_symlink
,
274 enum grub_fshelp_filetype expecttype
)
276 struct grub_fshelp_find_file_ctx ctx
= {
278 .rootnode
= rootnode
,
283 enum grub_fshelp_filetype foundtype
;
286 if (!path
|| path
[0] != '/')
288 return grub_error (GRUB_ERR_BAD_FILENAME
, N_("invalid file name `%s'"), path
);
291 err
= go_to_root (&ctx
);
295 duppath
= grub_strdup (path
);
298 err
= find_file (duppath
, iterate_dir
, lookup_file
, read_symlink
, &ctx
);
306 *foundnode
= ctx
.currnode
->node
;
307 foundtype
= ctx
.currnode
->type
;
308 /* Avoid the node being freed. */
309 ctx
.currnode
->node
= 0;
312 /* Check if the node that was found was of the expected type. */
313 if (expecttype
== GRUB_FSHELP_REG
&& foundtype
!= expecttype
)
314 return grub_error (GRUB_ERR_BAD_FILE_TYPE
, N_("not a regular file"));
315 else if (expecttype
== GRUB_FSHELP_DIR
&& foundtype
!= expecttype
)
316 return grub_error (GRUB_ERR_BAD_FILE_TYPE
, N_("not a directory"));
321 /* Lookup the node PATH. The node ROOTNODE describes the root of the
322 directory tree. The node found is returned in FOUNDNODE, which is
323 either a ROOTNODE or a new malloc'ed node. ITERATE_DIR is used to
324 iterate over all directory entries in the current node.
325 READ_SYMLINK is used to read the symlink if a node is a symlink.
326 EXPECTTYPE is the type node that is expected by the called, an
327 error is generated if the node is not of the expected type. */
329 grub_fshelp_find_file (const char *path
, grub_fshelp_node_t rootnode
,
330 grub_fshelp_node_t
*foundnode
,
331 iterate_dir_func iterate_dir
,
332 read_symlink_func read_symlink
,
333 enum grub_fshelp_filetype expecttype
)
335 return grub_fshelp_find_file_real (path
, rootnode
, foundnode
,
337 read_symlink
, expecttype
);
342 grub_fshelp_find_file_lookup (const char *path
, grub_fshelp_node_t rootnode
,
343 grub_fshelp_node_t
*foundnode
,
344 lookup_file_func lookup_file
,
345 read_symlink_func read_symlink
,
346 enum grub_fshelp_filetype expecttype
)
348 return grub_fshelp_find_file_real (path
, rootnode
, foundnode
,
350 read_symlink
, expecttype
);
354 /* Read LEN bytes from the file NODE on disk DISK into the buffer BUF,
355 beginning with the block POS. READ_HOOK should be set before
356 reading a block from the file. READ_HOOK_DATA is passed through as
357 the DATA argument to READ_HOOK. GET_BLOCK is used to translate
358 file blocks to disk blocks. The file is FILESIZE bytes big and the
359 blocks have a size of LOG2BLOCKSIZE (in log2). */
361 grub_fshelp_read_file (grub_disk_t disk
, grub_fshelp_node_t node
,
362 grub_disk_read_hook_t read_hook
, void *read_hook_data
,
363 grub_off_t pos
, grub_size_t len
, char *buf
,
364 grub_disk_addr_t (*get_block
) (grub_fshelp_node_t node
,
365 grub_disk_addr_t block
),
366 grub_off_t filesize
, int log2blocksize
,
367 grub_disk_addr_t blocks_start
)
369 grub_disk_addr_t i
, blockcnt
;
370 int blocksize
= 1 << (log2blocksize
+ GRUB_DISK_SECTOR_BITS
);
374 grub_error (GRUB_ERR_OUT_OF_RANGE
,
375 N_("attempt to read past the end of file"));
379 /* Adjust LEN so it we can't read past the end of the file. */
380 if (pos
+ len
> filesize
)
381 len
= filesize
- pos
;
383 blockcnt
= ((len
+ pos
) + blocksize
- 1) >> (log2blocksize
+ GRUB_DISK_SECTOR_BITS
);
385 for (i
= pos
>> (log2blocksize
+ GRUB_DISK_SECTOR_BITS
); i
< blockcnt
; i
++)
387 grub_disk_addr_t blknr
;
388 int blockoff
= pos
& (blocksize
- 1);
389 int blockend
= blocksize
;
393 blknr
= get_block (node
, i
);
397 blknr
= blknr
<< log2blocksize
;
400 if (i
== blockcnt
- 1)
402 blockend
= (len
+ pos
) & (blocksize
- 1);
404 /* The last portion is exactly blocksize. */
406 blockend
= blocksize
;
410 if (i
== (pos
>> (log2blocksize
+ GRUB_DISK_SECTOR_BITS
)))
412 skipfirst
= blockoff
;
413 blockend
-= skipfirst
;
416 /* If the block number is 0 this block is not stored on disk but
417 is zero filled instead. */
420 disk
->read_hook
= read_hook
;
421 disk
->read_hook_data
= read_hook_data
;
423 grub_disk_read (disk
, blknr
+ blocks_start
, skipfirst
,
429 else if (read_hook
!= (void *)grub_disk_blocklist_read
)
430 grub_memset (buf
, 0, blockend
);
432 buf
+= blocksize
- skipfirst
;