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 char *(*read_symlink_func
) (grub_fshelp_node_t node
);
35 /* Context for grub_fshelp_find_file. */
36 struct grub_fshelp_find_file_ctx
39 grub_fshelp_node_t rootnode
, currroot
, currnode
, oldnode
;
40 enum grub_fshelp_filetype foundtype
;
44 enum grub_fshelp_filetype type
;
47 /* Helper for find_file_iter. */
49 free_node (grub_fshelp_node_t node
, struct grub_fshelp_find_file_ctx
*ctx
)
51 if (node
!= ctx
->rootnode
&& node
!= ctx
->currroot
)
55 /* Helper for grub_fshelp_find_file. */
57 find_file_iter (const char *filename
, enum grub_fshelp_filetype filetype
,
58 grub_fshelp_node_t node
, void *data
)
60 struct grub_fshelp_find_file_ctx
*ctx
= data
;
62 if (filetype
== GRUB_FSHELP_UNKNOWN
||
63 ((filetype
& GRUB_FSHELP_CASE_INSENSITIVE
)
64 ? grub_strncasecmp (ctx
->name
, filename
, ctx
->next
- ctx
->name
)
65 : grub_strncmp (ctx
->name
, filename
, ctx
->next
- ctx
->name
))
66 || filename
[ctx
->next
- ctx
->name
])
72 /* The node is found, stop iterating over the nodes. */
73 ctx
->type
= filetype
& ~GRUB_FSHELP_CASE_INSENSITIVE
;
74 ctx
->oldnode
= ctx
->currnode
;
81 find_file (const char *currpath
, grub_fshelp_node_t currroot
,
82 grub_fshelp_node_t
*currfound
,
83 iterate_dir_func iterate_dir
, read_symlink_func read_symlink
,
84 struct grub_fshelp_find_file_ctx
*ctx
)
86 ctx
->currroot
= currroot
;
88 ctx
->type
= GRUB_FSHELP_DIR
;
89 ctx
->currnode
= currroot
;
90 ctx
->oldnode
= currroot
;
96 /* Remove all leading slashes. */
97 while (*ctx
->name
== '/')
100 /* Found the node! */
103 *currfound
= ctx
->currnode
;
104 ctx
->foundtype
= ctx
->type
;
108 /* Extract the actual part from the pathname. */
109 for (ctx
->next
= ctx
->name
; *ctx
->next
&& *ctx
->next
!= '/'; ctx
->next
++);
111 /* At this point it is expected that the current node is a
112 directory, check if this is true. */
113 if (ctx
->type
!= GRUB_FSHELP_DIR
)
115 free_node (ctx
->currnode
, ctx
);
117 return grub_error (GRUB_ERR_BAD_FILE_TYPE
, N_("not a directory"));
120 /* Iterate over the directory. */
121 found
= iterate_dir (ctx
->currnode
, find_file_iter
, ctx
);
124 free_node (ctx
->currnode
, ctx
);
132 /* Read in the symlink and follow it. */
133 if (ctx
->type
== GRUB_FSHELP_SYMLINK
)
138 /* Test if the symlink does not loop. */
139 if (++ctx
->symlinknest
== 8)
141 free_node (ctx
->currnode
, ctx
);
142 free_node (ctx
->oldnode
, ctx
);
145 return grub_error (GRUB_ERR_SYMLINK_LOOP
,
146 N_("too deep nesting of symlinks"));
149 symlink
= read_symlink (ctx
->currnode
);
150 free_node (ctx
->currnode
, ctx
);
155 free_node (ctx
->oldnode
, ctx
);
160 /* The symlink is an absolute path, go back to the root inode. */
161 if (symlink
[0] == '/')
163 free_node (ctx
->oldnode
, ctx
);
164 ctx
->oldnode
= ctx
->rootnode
;
167 /* Lookup the node the symlink points to. */
169 find_file (symlink
, ctx
->oldnode
, &ctx
->currnode
,
170 iterate_dir
, read_symlink
, ctx
);
172 ctx
->type
= ctx
->foundtype
;
177 free_node (ctx
->oldnode
, ctx
);
183 if (ctx
->oldnode
!= ctx
->currnode
)
185 free_node (ctx
->oldnode
, ctx
);
189 ctx
->name
= ctx
->next
;
192 return grub_error (GRUB_ERR_FILE_NOT_FOUND
, N_("file `%s' not found"),
196 /* Lookup the node PATH. The node ROOTNODE describes the root of the
197 directory tree. The node found is returned in FOUNDNODE, which is
198 either a ROOTNODE or a new malloc'ed node. ITERATE_DIR is used to
199 iterate over all directory entries in the current node.
200 READ_SYMLINK is used to read the symlink if a node is a symlink.
201 EXPECTTYPE is the type node that is expected by the called, an
202 error is generated if the node is not of the expected type. */
204 grub_fshelp_find_file (const char *path
, grub_fshelp_node_t rootnode
,
205 grub_fshelp_node_t
*foundnode
,
206 iterate_dir_func iterate_dir
,
207 read_symlink_func read_symlink
,
208 enum grub_fshelp_filetype expecttype
)
210 struct grub_fshelp_find_file_ctx ctx
= {
212 .rootnode
= rootnode
,
213 .foundtype
= GRUB_FSHELP_DIR
,
218 if (!path
|| path
[0] != '/')
220 grub_error (GRUB_ERR_BAD_FILENAME
, N_("invalid file name `%s'"), path
);
224 err
= find_file (path
, rootnode
, foundnode
, iterate_dir
, read_symlink
, &ctx
);
228 /* Check if the node that was found was of the expected type. */
229 if (expecttype
== GRUB_FSHELP_REG
&& ctx
.foundtype
!= expecttype
)
230 return grub_error (GRUB_ERR_BAD_FILE_TYPE
, N_("not a regular file"));
231 else if (expecttype
== GRUB_FSHELP_DIR
&& ctx
.foundtype
!= expecttype
)
232 return grub_error (GRUB_ERR_BAD_FILE_TYPE
, N_("not a directory"));
237 /* Read LEN bytes from the file NODE on disk DISK into the buffer BUF,
238 beginning with the block POS. READ_HOOK should be set before
239 reading a block from the file. READ_HOOK_DATA is passed through as
240 the DATA argument to READ_HOOK. GET_BLOCK is used to translate
241 file blocks to disk blocks. The file is FILESIZE bytes big and the
242 blocks have a size of LOG2BLOCKSIZE (in log2). */
244 grub_fshelp_read_file (grub_disk_t disk
, grub_fshelp_node_t node
,
245 grub_disk_read_hook_t read_hook
, void *read_hook_data
,
246 grub_off_t pos
, grub_size_t len
, char *buf
,
247 grub_disk_addr_t (*get_block
) (grub_fshelp_node_t node
,
248 grub_disk_addr_t block
),
249 grub_off_t filesize
, int log2blocksize
,
250 grub_disk_addr_t blocks_start
)
252 grub_disk_addr_t i
, blockcnt
;
253 int blocksize
= 1 << (log2blocksize
+ GRUB_DISK_SECTOR_BITS
);
257 grub_error (GRUB_ERR_OUT_OF_RANGE
,
258 N_("attempt to read past the end of file"));
262 /* Adjust LEN so it we can't read past the end of the file. */
263 if (pos
+ len
> filesize
)
264 len
= filesize
- pos
;
266 blockcnt
= ((len
+ pos
) + blocksize
- 1) >> (log2blocksize
+ GRUB_DISK_SECTOR_BITS
);
268 for (i
= pos
>> (log2blocksize
+ GRUB_DISK_SECTOR_BITS
); i
< blockcnt
; i
++)
270 grub_disk_addr_t blknr
;
271 int blockoff
= pos
& (blocksize
- 1);
272 int blockend
= blocksize
;
276 blknr
= get_block (node
, i
);
280 blknr
= blknr
<< log2blocksize
;
283 if (i
== blockcnt
- 1)
285 blockend
= (len
+ pos
) & (blocksize
- 1);
287 /* The last portion is exactly blocksize. */
289 blockend
= blocksize
;
293 if (i
== (pos
>> (log2blocksize
+ GRUB_DISK_SECTOR_BITS
)))
295 skipfirst
= blockoff
;
296 blockend
-= skipfirst
;
299 /* If the block number is 0 this block is not stored on disk but
300 is zero filled instead. */
303 disk
->read_hook
= read_hook
;
304 disk
->read_hook_data
= read_hook_data
;
306 grub_disk_read (disk
, blknr
+ blocks_start
, skipfirst
,
313 grub_memset (buf
, 0, blockend
);
315 buf
+= blocksize
- skipfirst
;