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