1 /* fshelp.c -- Filesystem helper functions */
2 /*
3 * 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 * (at your option) any later version.
10 *
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.
15 *
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/>.
18 */
19
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>
25 #include <stdlib.h>
26
27 GRUB_EXPORT(grub_fshelp_view);
28 GRUB_EXPORT(grub_fshelp_find_file);
29 GRUB_EXPORT(grub_fshelp_log2blksize);
30 GRUB_EXPORT(grub_fshelp_read_file);
31
32 int grub_fshelp_view = 0;
33
34 struct grub_fshelp_find_file_closure
35 {
36 grub_fshelp_node_t rootnode;
37 int (*iterate_dir) (grub_fshelp_node_t dir,
38 int (*hook)
39 (const char *filename,
40 enum grub_fshelp_filetype filetype,
41 grub_fshelp_node_t node, void *closure),
42 void *closure);
43 void *closure;
44 char *(*read_symlink) (grub_fshelp_node_t node);
45 int symlinknest;
46 enum grub_fshelp_filetype foundtype;
47 grub_fshelp_node_t currroot;
48 };
49
50 static void
free_node(grub_fshelp_node_t node,struct grub_fshelp_find_file_closure * c)51 free_node (grub_fshelp_node_t node, struct grub_fshelp_find_file_closure *c)
52 {
53 if (node != c->rootnode && node != c->currroot)
54 grub_free (node);
55 }
56
57 struct find_file_closure
58 {
59 char *name;
60 enum grub_fshelp_filetype *type;
61 grub_fshelp_node_t *oldnode;
62 grub_fshelp_node_t *currnode;
63 };
64
65 static int
iterate(const char * filename,enum grub_fshelp_filetype filetype,grub_fshelp_node_t node,void * closure)66 iterate (const char *filename,
67 enum grub_fshelp_filetype filetype,
68 grub_fshelp_node_t node,
69 void *closure)
70 {
71 struct find_file_closure *c = closure;
72
73 if (filetype == GRUB_FSHELP_UNKNOWN ||
74 (grub_strcmp (c->name, filename) &&
75 (! (filetype & GRUB_FSHELP_CASE_INSENSITIVE) ||
76 grub_strncasecmp (c->name, filename, GRUB_LONG_MAX))))
77 {
78 grub_free (node);
79 return 0;
80 }
81
82 /* The node is found, stop iterating over the nodes. */
83 *(c->type) = filetype & ~GRUB_FSHELP_CASE_INSENSITIVE;
84 *(c->oldnode) = *(c->currnode);
85 *(c->currnode) = node;
86
87 return 1;
88 }
89
90 static grub_err_t
find_file(const char * currpath,grub_fshelp_node_t currroot,grub_fshelp_node_t * currfound,struct grub_fshelp_find_file_closure * c)91 find_file (const char *currpath, grub_fshelp_node_t currroot,
92 grub_fshelp_node_t *currfound,
93 struct grub_fshelp_find_file_closure *c)
94 {
95 char *fpath = grub_malloc (grub_strlen (currpath) + 1);
96 char *name = fpath;
97 char *next;
98 enum grub_fshelp_filetype type = GRUB_FSHELP_DIR;
99 grub_fshelp_node_t currnode = currroot;
100 grub_fshelp_node_t oldnode = currroot;
101
102 c->currroot = currroot;
103
104 grub_strncpy (fpath, currpath, grub_strlen (currpath) + 1);
105
106 /* Remove all leading slashes. */
107 while (*name == '/')
108 name++;
109
110 if (! *name)
111 {
112 *currfound = currnode;
113 grub_free (fpath);
114 return 0;
115 }
116
117 for (;;)
118 {
119 int found;
120 struct find_file_closure cc;
121
122 /* Extract the actual part from the pathname. */
123 next = grub_strchr (name, '/');
124 if (next)
125 {
126 /* Remove all leading slashes. */
127 while (*next == '/')
128 *(next++) = '\0';
129 }
130
131 /* At this point it is expected that the current node is a
132 directory, check if this is true. */
133 if (type != GRUB_FSHELP_DIR)
134 {
135 free_node (currnode, c);
136 grub_free (fpath);
137 return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory");
138 }
139
140 cc.name = name;
141 cc.type = &type;
142 cc.oldnode = &oldnode;
143 cc.currnode = &currnode;
144 /* Iterate over the directory. */
145 found = c->iterate_dir (currnode, iterate, &cc);
146 if (! found)
147 {
148 if (grub_errno) {
149 grub_free (fpath);
150 return grub_errno;
151 }
152
153 break;
154 }
155
156 /* Read in the symlink and follow it. */
157 if (type == GRUB_FSHELP_SYMLINK)
158 {
159 char *symlink;
160
161 /* Test if the symlink does not loop. */
162 if (++(c->symlinknest) == 8)
163 {
164 free_node (currnode, c);
165 free_node (oldnode, c);
166 grub_free (fpath);
167 return grub_error (GRUB_ERR_SYMLINK_LOOP,
168 "too deep nesting of symlinks");
169 }
170
171 symlink = c->read_symlink (currnode);
172 free_node (currnode, c);
173
174 if (!symlink)
175 {
176 free_node (oldnode, c);
177 grub_free (fpath);
178 return grub_errno;
179 }
180
181 /* The symlink is an absolute path, go back to the root inode. */
182 if (symlink[0] == '/')
183 {
184 free_node (oldnode, c);
185 oldnode = c->rootnode;
186 }
187
188 /* Lookup the node the symlink points to. */
189 find_file (symlink, oldnode, &currnode, c);
190 type = c->foundtype;
191 grub_free (symlink);
192
193 if (grub_errno)
194 {
195 free_node (oldnode, c);
196 grub_free (fpath);
197 return grub_errno;
198 }
199 }
200
201 free_node (oldnode, c);
202
203 /* Found the node! */
204 if (! next || *next == '\0')
205 {
206 *currfound = currnode;
207 c->foundtype = type;
208 grub_free (fpath);
209 return 0;
210 }
211
212 name = next;
213 }
214
215 grub_free (fpath);
216 return grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found");
217 }
218
219 /* Lookup the node PATH. The node ROOTNODE describes the root of the
220 directory tree. The node found is returned in FOUNDNODE, which is
221 either a ROOTNODE or a new malloc'ed node. ITERATE_DIR is used to
222 iterate over all directory entries in the current node.
223 READ_SYMLINK is used to read the symlink if a node is a symlink.
224 EXPECTTYPE is the type node that is expected by the called, an
225 error is generated if the node is not of the expected type. Make
226 sure you use the NESTED_FUNC_ATTR macro for HOOK, this is required
227 because GCC has a nasty bug when using regparm=3. */
228 grub_err_t
grub_fshelp_find_file(const char * path,grub_fshelp_node_t rootnode,grub_fshelp_node_t * foundnode,int (* iterate_dir)(grub_fshelp_node_t dir,int (* hook)(const char * filename,enum grub_fshelp_filetype filetype,grub_fshelp_node_t node,void * closure),void * closure),void * closure,char * (* read_symlink)(grub_fshelp_node_t node),enum grub_fshelp_filetype expecttype)229 grub_fshelp_find_file (const char *path, grub_fshelp_node_t rootnode,
230 grub_fshelp_node_t *foundnode,
231 int (*iterate_dir) (grub_fshelp_node_t dir,
232 int (*hook)
233 (const char *filename,
234 enum grub_fshelp_filetype filetype,
235 grub_fshelp_node_t node,
236 void *closure),
237 void *closure),
238 void *closure,
239 char *(*read_symlink) (grub_fshelp_node_t node),
240 enum grub_fshelp_filetype expecttype)
241 {
242 grub_err_t err;
243 struct grub_fshelp_find_file_closure c;
244
245 c.rootnode = rootnode;
246 c.iterate_dir = iterate_dir;
247 c.closure = closure;
248 c.read_symlink = read_symlink;
249 c.symlinknest = 0;
250 c.foundtype = GRUB_FSHELP_DIR;
251
252 if (!path || path[0] != '/')
253 {
254 grub_error (GRUB_ERR_BAD_FILENAME, "bad filename");
255 return grub_errno;
256 }
257
258 err = find_file (path, rootnode, foundnode, &c);
259 if (err)
260 return err;
261
262 /* Check if the node that was found was of the expected type. */
263 if (expecttype == GRUB_FSHELP_REG && c.foundtype != expecttype)
264 return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a regular file");
265 else if (expecttype == GRUB_FSHELP_DIR && c.foundtype != expecttype)
266 return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory");
267
268 return 0;
269 }
270 unsigned long long grub_hack_lastoff = 0;
271
272 /* Read LEN bytes from the file NODE on disk DISK into the buffer BUF,
273 beginning with the block POS. READ_HOOK should be set before
274 reading a block from the file. GET_BLOCK is used to translate file
275 blocks to disk blocks. The file is FILESIZE bytes big and the
276 blocks have a size of LOG2BLOCKSIZE (in log2). */
277 grub_ssize_t
grub_fshelp_read_file(grub_disk_t disk,grub_fshelp_node_t node,void (* read_hook)(grub_disk_addr_t sector,unsigned offset,unsigned length,void * closure),void * closure,int flags,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),grub_off_t filesize,int log2blocksize)278 grub_fshelp_read_file (grub_disk_t disk, grub_fshelp_node_t node,
279 void (*read_hook) (grub_disk_addr_t sector,
280 unsigned offset,
281 unsigned length,
282 void *closure),
283 void *closure, int flags,
284 grub_off_t pos, grub_size_t len, char *buf,
285 grub_disk_addr_t (*get_block) (grub_fshelp_node_t node,
286 grub_disk_addr_t block),
287 grub_off_t filesize, int log2blocksize)
288 {
289 grub_disk_addr_t i, blockcnt;
290 int blocksize = 1 << (log2blocksize + GRUB_DISK_SECTOR_BITS);
291
292 /* Adjust LEN so it we can't read past the end of the file. */
293 if (pos + len > filesize)
294 len = filesize - pos;
295
296 if (len < 1 || len == 0xffffffff) {
297 return -1;
298 }
299
300 blockcnt = ((len + pos) + blocksize - 1) >>
301 (log2blocksize + GRUB_DISK_SECTOR_BITS);
302
303 for (i = pos >> (log2blocksize + GRUB_DISK_SECTOR_BITS); i < blockcnt; i++)
304 {
305 grub_disk_addr_t blknr;
306 int blockoff = pos & (blocksize - 1);
307 int blockend = blocksize;
308
309 int skipfirst = 0;
310
311 blknr = get_block (node, i);
312 if (grub_errno)
313 return -1;
314
315 blknr = blknr << log2blocksize;
316
317 /* Last block. */
318 if (i == blockcnt - 1)
319 {
320 blockend = (len + pos) & (blocksize - 1);
321
322 /* The last portion is exactly blocksize. */
323 if (! blockend)
324 blockend = blocksize;
325 }
326
327 /* First block. */
328 if (i == (pos >> (log2blocksize + GRUB_DISK_SECTOR_BITS)))
329 {
330 skipfirst = blockoff;
331 blockend -= skipfirst;
332 }
333
334 /* If the block number is 0 this block is not stored on disk but
335 is zero filled instead. */
336 if (blknr)
337 {
338 disk->read_hook = read_hook;
339 disk->closure = closure;
340
341 //printf ("blknr: %d\n", blknr);
342 grub_hack_lastoff = blknr * 512;
343 grub_disk_read_ex (disk, blknr, skipfirst, blockend, buf, flags);
344 disk->read_hook = 0;
345 if (grub_errno)
346 return -1;
347 }
348 else if (buf)
349 grub_memset (buf, 0, blockend);
350
351 if (buf)
352 buf += blocksize - skipfirst;
353 }
354
355 return len;
356 }
357
358 unsigned int
grub_fshelp_log2blksize(unsigned int blksize,unsigned int * pow)359 grub_fshelp_log2blksize (unsigned int blksize, unsigned int *pow)
360 {
361 int mod;
362
363 *pow = 0;
364 while (blksize > 1)
365 {
366 mod = blksize - ((blksize >> 1) << 1);
367 blksize >>= 1;
368
369 /* Check if it really is a power of two. */
370 if (mod)
371 return grub_error (GRUB_ERR_BAD_NUMBER,
372 "the blocksize is not a power of two");
373 (*pow)++;
374 }
375
376 return GRUB_ERR_NONE;
377 }
378