2009-11-21 Samuel Thibault <samuel.thibault@ens-lyon.org>
[grub2.git] / fs / fshelp.c
blobd0b1e493e711250378cf60208e4fb940c3acbd1c
1 /* fshelp.c -- Filesystem helper functions */
2 /*
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/>.
20 #include <grub/err.h>
21 #include <grub/mm.h>
22 #include <grub/misc.h>
23 #include <grub/disk.h>
24 #include <grub/fshelp.h>
27 /* Lookup the node PATH. The node ROOTNODE describes the root of the
28 directory tree. The node found is returned in FOUNDNODE, which is
29 either a ROOTNODE or a new malloc'ed node. ITERATE_DIR is used to
30 iterate over all directory entries in the current node.
31 READ_SYMLINK is used to read the symlink if a node is a symlink.
32 EXPECTTYPE is the type node that is expected by the called, an
33 error is generated if the node is not of the expected type. Make
34 sure you use the NESTED_FUNC_ATTR macro for HOOK, this is required
35 because GCC has a nasty bug when using regparm=3. */
36 grub_err_t
37 grub_fshelp_find_file (const char *path, grub_fshelp_node_t rootnode,
38 grub_fshelp_node_t *foundnode,
39 int (*iterate_dir) (grub_fshelp_node_t dir,
40 int NESTED_FUNC_ATTR (*hook)
41 (const char *filename,
42 enum grub_fshelp_filetype filetype,
43 grub_fshelp_node_t node)),
44 char *(*read_symlink) (grub_fshelp_node_t node),
45 enum grub_fshelp_filetype expecttype)
47 grub_err_t err;
48 enum grub_fshelp_filetype foundtype = GRUB_FSHELP_DIR;
49 int symlinknest = 0;
51 auto grub_err_t NESTED_FUNC_ATTR find_file (const char *currpath,
52 grub_fshelp_node_t currroot,
53 grub_fshelp_node_t *currfound);
55 grub_err_t NESTED_FUNC_ATTR find_file (const char *currpath,
56 grub_fshelp_node_t currroot,
57 grub_fshelp_node_t *currfound)
59 char fpath[grub_strlen (currpath) + 1];
60 char *name = fpath;
61 char *next;
62 // unsigned int pos = 0;
63 enum grub_fshelp_filetype type = GRUB_FSHELP_DIR;
64 grub_fshelp_node_t currnode = currroot;
65 grub_fshelp_node_t oldnode = currroot;
67 auto int NESTED_FUNC_ATTR iterate (const char *filename,
68 enum grub_fshelp_filetype filetype,
69 grub_fshelp_node_t node);
71 auto void free_node (grub_fshelp_node_t node);
73 void free_node (grub_fshelp_node_t node)
75 if (node != rootnode && node != currroot)
76 grub_free (node);
79 int NESTED_FUNC_ATTR iterate (const char *filename,
80 enum grub_fshelp_filetype filetype,
81 grub_fshelp_node_t node)
83 if (filetype == GRUB_FSHELP_UNKNOWN ||
84 (grub_strcmp (name, filename) &&
85 (! (filetype & GRUB_FSHELP_CASE_INSENSITIVE) ||
86 grub_strncasecmp (name, filename, GRUB_LONG_MAX))))
88 grub_free (node);
89 return 0;
92 /* The node is found, stop iterating over the nodes. */
93 type = filetype & ~GRUB_FSHELP_CASE_INSENSITIVE;
94 oldnode = currnode;
95 currnode = node;
97 return 1;
100 grub_strncpy (fpath, currpath, grub_strlen (currpath) + 1);
102 /* Remove all leading slashes. */
103 while (*name == '/')
104 name++;
106 if (! *name)
108 *currfound = currnode;
109 return 0;
112 for (;;)
114 int found;
116 /* Extract the actual part from the pathname. */
117 next = grub_strchr (name, '/');
118 if (next)
120 /* Remove all leading slashes. */
121 while (*next == '/')
122 *(next++) = '\0';
125 /* At this point it is expected that the current node is a
126 directory, check if this is true. */
127 if (type != GRUB_FSHELP_DIR)
129 free_node (currnode);
130 return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory");
133 /* Iterate over the directory. */
134 found = iterate_dir (currnode, iterate);
135 if (! found)
137 if (grub_errno)
138 return grub_errno;
140 break;
143 /* Read in the symlink and follow it. */
144 if (type == GRUB_FSHELP_SYMLINK)
146 char *symlink;
148 /* Test if the symlink does not loop. */
149 if (++symlinknest == 8)
151 free_node (currnode);
152 free_node (oldnode);
153 return grub_error (GRUB_ERR_SYMLINK_LOOP,
154 "too deep nesting of symlinks");
157 symlink = read_symlink (currnode);
158 free_node (currnode);
160 if (!symlink)
162 free_node (oldnode);
163 return grub_errno;
166 /* The symlink is an absolute path, go back to the root inode. */
167 if (symlink[0] == '/')
169 free_node (oldnode);
170 oldnode = rootnode;
173 /* Lookup the node the symlink points to. */
174 find_file (symlink, oldnode, &currnode);
175 type = foundtype;
176 grub_free (symlink);
178 if (grub_errno)
180 free_node (oldnode);
181 return grub_errno;
185 free_node (oldnode);
187 /* Found the node! */
188 if (! next || *next == '\0')
190 *currfound = currnode;
191 foundtype = type;
192 return 0;
195 name = next;
198 return grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found");
201 if (!path || path[0] != '/')
203 grub_error (GRUB_ERR_BAD_FILENAME, "bad filename");
204 return grub_errno;
207 err = find_file (path, rootnode, foundnode);
208 if (err)
209 return err;
211 /* Check if the node that was found was of the expected type. */
212 if (expecttype == GRUB_FSHELP_REG && foundtype != expecttype)
213 return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a regular file");
214 else if (expecttype == GRUB_FSHELP_DIR && foundtype != expecttype)
215 return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory");
217 return 0;
220 /* Read LEN bytes from the file NODE on disk DISK into the buffer BUF,
221 beginning with the block POS. READ_HOOK should be set before
222 reading a block from the file. GET_BLOCK is used to translate file
223 blocks to disk blocks. The file is FILESIZE bytes big and the
224 blocks have a size of LOG2BLOCKSIZE (in log2). */
225 grub_ssize_t
226 grub_fshelp_read_file (grub_disk_t disk, grub_fshelp_node_t node,
227 void NESTED_FUNC_ATTR (*read_hook) (grub_disk_addr_t sector,
228 unsigned offset,
229 unsigned length),
230 grub_off_t pos, grub_size_t len, char *buf,
231 grub_disk_addr_t (*get_block) (grub_fshelp_node_t node,
232 grub_disk_addr_t block),
233 grub_off_t filesize, int log2blocksize)
235 grub_disk_addr_t i, blockcnt;
236 int blocksize = 1 << (log2blocksize + GRUB_DISK_SECTOR_BITS);
238 /* Adjust LEN so it we can't read past the end of the file. */
239 if (pos + len > filesize)
240 len = filesize - pos;
242 blockcnt = ((len + pos) + blocksize - 1) >> (log2blocksize + GRUB_DISK_SECTOR_BITS);
244 for (i = pos >> (log2blocksize + GRUB_DISK_SECTOR_BITS); i < blockcnt; i++)
246 grub_disk_addr_t blknr;
247 int blockoff = pos & (blocksize - 1);
248 int blockend = blocksize;
250 int skipfirst = 0;
252 blknr = get_block (node, i);
253 if (grub_errno)
254 return -1;
256 blknr = blknr << log2blocksize;
258 /* Last block. */
259 if (i == blockcnt - 1)
261 blockend = (len + pos) & (blocksize - 1);
263 /* The last portion is exactly blocksize. */
264 if (! blockend)
265 blockend = blocksize;
268 /* First block. */
269 if (i == (pos >> (log2blocksize + GRUB_DISK_SECTOR_BITS)))
271 skipfirst = blockoff;
272 blockend -= skipfirst;
275 /* If the block number is 0 this block is not stored on disk but
276 is zero filled instead. */
277 if (blknr)
279 disk->read_hook = read_hook;
281 grub_disk_read (disk, blknr, skipfirst,
282 blockend, buf);
283 disk->read_hook = 0;
284 if (grub_errno)
285 return -1;
287 else
288 grub_memset (buf, 0, blockend);
290 buf += blocksize - skipfirst;
293 return len;
296 unsigned int
297 grub_fshelp_log2blksize (unsigned int blksize, unsigned int *pow)
299 int mod;
301 *pow = 0;
302 while (blksize > 1)
304 mod = blksize - ((blksize >> 1) << 1);
305 blksize >>= 1;
307 /* Check if it really is a power of two. */
308 if (mod)
309 return grub_error (GRUB_ERR_BAD_NUMBER,
310 "the blocksize is not a power of two");
311 (*pow)++;
314 return GRUB_ERR_NONE;