1 /* minix.c - The minix filesystem, version 1 and 2. */
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/file.h>
22 #include <grub/mm.h>
23 #include <grub/misc.h>
24 #include <grub/disk.h>
25 #include <grub/types.h>
26
27 #define GRUB_MINIX_MAGIC 0x137F
28 #define GRUB_MINIX2_MAGIC 0x2468
29 #define GRUB_MINIX_MAGIC_30 0x138F
30 #define GRUB_MINIX2_MAGIC_30 0x2478
31 #define GRUB_MINIX_BSIZE 1024U
32 #define GRUB_MINIX_LOG2_BSIZE 1
33 #define GRUB_MINIX_ROOT_INODE 1
34 #define GRUB_MINIX_MAX_SYMLNK_CNT 8
35 #define GRUB_MINIX_SBLOCK 2
36
37 #define GRUB_MINIX_IFDIR 0040000U
38 #define GRUB_MINIX_IFLNK 0120000U
39
40 #define GRUB_MINIX_INODE(data,field) (data->version == 1 ? \
41 data->inode. field : data->inode2. field)
42 #define GRUB_MINIX_INODE_ENDIAN(data,field,bits1,bits2) (data->version == 1 ? \
43 grub_le_to_cpu##bits1 (data->inode.field) : \
44 grub_le_to_cpu##bits2 (data->inode2.field))
45 #define GRUB_MINIX_INODE_SIZE(data) GRUB_MINIX_INODE_ENDIAN (data,size,16,32)
46 #define GRUB_MINIX_INODE_MODE(data) GRUB_MINIX_INODE_ENDIAN (data,mode,16,16)
47 #define GRUB_MINIX_INODE_DIR_ZONES(data,blk) GRUB_MINIX_INODE_ENDIAN \
48 (data,dir_zones[blk],16,32)
49 #define GRUB_MINIX_INODE_INDIR_ZONE(data) \
50 GRUB_MINIX_INODE_ENDIAN (data,indir_zone,16,32)
51 #define GRUB_MINIX_INODE_DINDIR_ZONE(data) \
52 GRUB_MINIX_INODE_ENDIAN (data,double_indir_zone,16,32)
53 #define GRUB_MINIX_INODE_BLKSZ(data) (data->version == 1 ? 2 : 4)
54 #define GRUB_MINIX_LOG2_ZONESZ (GRUB_MINIX_LOG2_BSIZE \
55 + grub_le_to_cpu16 (sblock->log2_zone_size))
56 #define GRUB_MINIX_ZONESZ (GRUB_MINIX_BSIZE \
57 << grub_le_to_cpu16 (sblock->log2_zone_size))
58
59 struct grub_minix_sblock
60 {
61 grub_uint16_t inode_cnt;
62 grub_uint16_t zone_cnt;
63 grub_uint16_t inode_bmap_size;
64 grub_uint16_t zone_bmap_size;
65 grub_uint16_t first_data_zone;
66 grub_uint16_t log2_zone_size;
67 grub_uint32_t max_file_size;
68 grub_uint16_t magic;
69 };
70
71 struct grub_minix_inode
72 {
73 grub_uint16_t mode;
74 grub_uint16_t uid;
75 grub_uint16_t size;
76 grub_uint32_t ctime;
77 grub_uint8_t gid;
78 grub_uint8_t nlinks;
79 grub_uint16_t dir_zones[7];
80 grub_uint16_t indir_zone;
81 grub_uint16_t double_indir_zone;
82 };
83
84 struct grub_minix2_inode
85 {
86 grub_uint16_t mode;
87 grub_uint16_t nlinks;
88 grub_uint16_t uid;
89 grub_uint16_t gid;
90 grub_uint32_t size;
91 grub_uint32_t atime;
92 grub_uint32_t mtime;
93 grub_uint32_t ctime;
94 grub_uint32_t dir_zones[7];
95 grub_uint32_t indir_zone;
96 grub_uint32_t double_indir_zone;
97 grub_uint32_t unused;
98
99 };
100
101 /* Information about a "mounted" minix filesystem. */
102 struct grub_minix_data
103 {
104 struct grub_minix_sblock sblock;
105 struct grub_minix_inode inode;
106 struct grub_minix2_inode inode2;
107 int ino;
108 int linknest;
109 grub_disk_t disk;
110 int version;
111 int filename_size;
112 };
113
114
115 static grub_err_t grub_minix_find_file (struct grub_minix_data *data,
116 const char *path);
117
118 /* Read the block pointer in ZONE, on the offset NUM. */
119 static int
grub_get_indir(int zone,int num,struct grub_minix_data * data)120 grub_get_indir (int zone, int num, struct grub_minix_data *data)
121 {
122 struct grub_minix_sblock *sblock = &data->sblock;
123 if (data->version == 1)
124 {
125 grub_uint16_t indir16;
126 grub_disk_read (data->disk,
127 zone << GRUB_MINIX_LOG2_ZONESZ,
128 sizeof (grub_uint16_t) * num,
129 sizeof (grub_uint16_t), (char *) &indir16);
130 return grub_le_to_cpu16 (indir16);
131 }
132 else
133 {
134 grub_uint32_t indir32;
135 grub_disk_read (data->disk,
136 zone << GRUB_MINIX_LOG2_ZONESZ,
137 sizeof (grub_uint32_t) * num,
138 sizeof (grub_uint32_t), (char *) &indir32);
139 return grub_le_to_cpu32 (indir32);
140 }
141 }
142
143 static int
grub_minix_get_file_block(struct grub_minix_data * data,unsigned int blk)144 grub_minix_get_file_block (struct grub_minix_data *data, unsigned int blk)
145 {
146 struct grub_minix_sblock *sblock = &data->sblock;
147 int indir;
148
149 /* Direct block. */
150 if (blk < 7)
151 return GRUB_MINIX_INODE_DIR_ZONES (data, blk);
152
153 /* Indirect block. */
154 blk -= 7;
155 if (blk < GRUB_MINIX_ZONESZ / GRUB_MINIX_INODE_BLKSZ (data))
156 {
157 indir = grub_get_indir (GRUB_MINIX_INODE_INDIR_ZONE (data), blk, data);
158 return indir;
159 }
160
161 /* Double indirect block. */
162 blk -= GRUB_MINIX_ZONESZ / GRUB_MINIX_INODE_BLKSZ (data);
163 if (blk < (GRUB_MINIX_ZONESZ / GRUB_MINIX_INODE_BLKSZ (data))
164 * (GRUB_MINIX_ZONESZ / GRUB_MINIX_INODE_BLKSZ (data)))
165 {
166 indir = grub_get_indir (GRUB_MINIX_INODE_DINDIR_ZONE (data),
167 blk / GRUB_MINIX_ZONESZ, data);
168
169 indir = grub_get_indir (indir, blk % GRUB_MINIX_ZONESZ, data);
170
171 return indir;
172 }
173
174 /* This should never happen. */
175 grub_error (GRUB_ERR_OUT_OF_RANGE, "file bigger than maximum size");
176
177 return 0;
178 }
179
180
181 /* Read LEN bytes from the file described by DATA starting with byte
182 POS. Return the amount of read bytes in READ. */
183 static grub_ssize_t
grub_minix_read_file(struct grub_minix_data * data,void (* read_hook)(grub_disk_addr_t sector,unsigned offset,unsigned length,void * closure),void * closure,int pos,grub_disk_addr_t len,char * buf)184 grub_minix_read_file (struct grub_minix_data *data,
185 void (*read_hook) (grub_disk_addr_t sector,
186 unsigned offset, unsigned length,
187 void *closure),
188 void *closure,
189 int pos, grub_disk_addr_t len, char *buf)
190 {
191 struct grub_minix_sblock *sblock = &data->sblock;
192 int i;
193 int blockcnt;
194
195 /* Adjust len so it we can't read past the end of the file. */
196 if (len + pos > GRUB_MINIX_INODE_SIZE (data))
197 len = GRUB_MINIX_INODE_SIZE (data) - pos;
198
199 blockcnt = (len + pos + GRUB_MINIX_BSIZE - 1) / GRUB_MINIX_BSIZE;
200
201 for (i = pos / GRUB_MINIX_BSIZE; i < blockcnt; i++)
202 {
203 int blknr;
204 int blockoff = pos % GRUB_MINIX_BSIZE;
205 int blockend = GRUB_MINIX_BSIZE;
206
207 int skipfirst = 0;
208
209 blknr = grub_minix_get_file_block (data, i);
210 if (grub_errno)
211 return -1;
212
213 /* Last block. */
214 if (i == blockcnt - 1)
215 {
216 blockend = (len + pos) % GRUB_MINIX_BSIZE;
217
218 if (!blockend)
219 blockend = GRUB_MINIX_BSIZE;
220 }
221
222 /* First block. */
223 if (i == (pos / (int) GRUB_MINIX_BSIZE))
224 {
225 skipfirst = blockoff;
226 blockend -= skipfirst;
227 }
228
229 data->disk->read_hook = read_hook;
230 data->disk->closure = closure;
231 grub_disk_read (data->disk, blknr << GRUB_MINIX_LOG2_ZONESZ,
232 skipfirst, blockend, buf);
233
234 data->disk->read_hook = 0;
235 if (grub_errno)
236 return -1;
237
238 buf += GRUB_MINIX_BSIZE - skipfirst;
239 }
240
241 return len;
242 }
243
244
245 /* Read inode INO from the mounted filesystem described by DATA. This
246 inode is used by default now. */
247 static grub_err_t
grub_minix_read_inode(struct grub_minix_data * data,int ino)248 grub_minix_read_inode (struct grub_minix_data *data, int ino)
249 {
250 struct grub_minix_sblock *sblock = &data->sblock;
251
252 /* Block in which the inode is stored. */
253 int block;
254 data->ino = ino;
255
256 /* The first inode in minix is inode 1. */
257 ino--;
258
259 block = ((2 + grub_le_to_cpu16 (sblock->inode_bmap_size)
260 + grub_le_to_cpu16 (sblock->zone_bmap_size))
261 << GRUB_MINIX_LOG2_BSIZE);
262
263 if (data->version == 1)
264 {
265 block += ino / (GRUB_DISK_SECTOR_SIZE / sizeof (struct grub_minix_inode));
266 int offs = (ino % (GRUB_DISK_SECTOR_SIZE
267 / sizeof (struct grub_minix_inode))
268 * sizeof (struct grub_minix_inode));
269
270 grub_disk_read (data->disk, block, offs,
271 sizeof (struct grub_minix_inode), &data->inode);
272 }
273 else
274 {
275 block += ino / (GRUB_DISK_SECTOR_SIZE
276 / sizeof (struct grub_minix2_inode));
277 int offs = (ino
278 % (GRUB_DISK_SECTOR_SIZE / sizeof (struct grub_minix2_inode))
279 * sizeof (struct grub_minix2_inode));
280
281 grub_disk_read (data->disk, block, offs,
282 sizeof (struct grub_minix2_inode),&data->inode2);
283 }
284
285 return GRUB_ERR_NONE;
286 }
287
288
289 /* Lookup the symlink the current inode points to. INO is the inode
290 number of the directory the symlink is relative to. */
291 static grub_err_t
grub_minix_lookup_symlink(struct grub_minix_data * data,int ino)292 grub_minix_lookup_symlink (struct grub_minix_data *data, int ino)
293 {
294 #ifndef _MSC_VER
295 char symlink[GRUB_MINIX_INODE_SIZE (data) + 1];
296 #else
297 char *symlink = grub_malloc(GRUB_MINIX_INODE_SIZE (data) + 1);
298 #endif
299 if (++data->linknest > GRUB_MINIX_MAX_SYMLNK_CNT)
300 return grub_error (GRUB_ERR_SYMLINK_LOOP, "too deep nesting of symlinks");
301
302 if (grub_minix_read_file (data, 0, 0, 0,
303 GRUB_MINIX_INODE_SIZE (data), symlink) < 0)
304 return grub_errno;
305
306 symlink[GRUB_MINIX_INODE_SIZE (data)] = '\0';
307
308 /* The symlink is an absolute path, go back to the root inode. */
309 if (symlink[0] == '/')
310 ino = GRUB_MINIX_ROOT_INODE;
311
312 /* Now load in the old inode. */
313 if (grub_minix_read_inode (data, ino))
314 return grub_errno;
315
316 grub_minix_find_file (data, symlink);
317 if (grub_errno)
318 grub_error (grub_errno, "cannot follow symlink `%s'", symlink);
319
320 return grub_errno;
321 }
322
323
324 /* Find the file with the pathname PATH on the filesystem described by
325 DATA. */
326 static grub_err_t
grub_minix_find_file(struct grub_minix_data * data,const char * path)327 grub_minix_find_file (struct grub_minix_data *data, const char *path)
328 {
329 char * fpath = grub_malloc(grub_strlen (path) + 1);
330 char *name = fpath;
331 char *next;
332 unsigned int pos = 0;
333 int dirino;
334
335 grub_strcpy (fpath, path);
336
337 /* Skip the first slash. */
338 if (name[0] == '/')
339 {
340 name++;
341 if (!*name)
342 {
343 grub_free (fpath);
344 return 0;
345 }
346 }
347
348 /* Extract the actual part from the pathname. */
349 next = grub_strchr (name, '/');
350 if (next)
351 {
352 next[0] = '\0';
353 next++;
354 }
355
356 do
357 {
358 grub_uint16_t ino;
359 char * filename = grub_malloc(data->filename_size + 1);
360 if (grub_strlen (name) == 0)
361 {
362 grub_free (fpath);
363 grub_free (filename);
364 return GRUB_ERR_NONE;
365 }
366
367 if (grub_minix_read_file (data, 0, 0, pos, sizeof (ino),
368 (char *) &ino) < 0)
369 {
370 grub_free (fpath);
371 grub_free (filename);
372 return grub_errno;
373 }
374 if (grub_minix_read_file (data, 0, 0, pos + sizeof (ino),
375 data->filename_size, (char *) filename)< 0)
376 {
377 grub_free (fpath);
378 grub_free (filename);
379 return grub_errno;
380 }
381
382 filename[data->filename_size] = '\0';
383
384 /* Check if the current direntry matches the current part of the
385 pathname. */
386 if (!grub_strcmp (name, filename))
387 {
388 dirino = data->ino;
389 grub_minix_read_inode (data, grub_le_to_cpu16 (ino));
390
391 /* Follow the symlink. */
392 if ((GRUB_MINIX_INODE_MODE (data)
393 & GRUB_MINIX_IFLNK) == GRUB_MINIX_IFLNK)
394 {
395 grub_minix_lookup_symlink (data, dirino);
396 if (grub_errno)
397 {
398 grub_free (fpath);
399 grub_free (filename);
400 return grub_errno;
401 }
402 }
403
404 if (!next)
405 {
406 grub_free (fpath);
407 grub_free (filename);
408 return 0;
409 }
410
411 pos = 0;
412
413 name = next;
414 next = grub_strchr (name, '/');
415 if (next)
416 {
417 next[0] = '\0';
418 next++;
419 }
420
421 if ((GRUB_MINIX_INODE_MODE (data)
422 & GRUB_MINIX_IFDIR) != GRUB_MINIX_IFDIR)
423 {
424 grub_free (fpath);
425 grub_free (filename);
426 return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory");
427 }
428 grub_free (filename);
429 continue;
430 }
431
432 pos += sizeof (ino) + data->filename_size;
433 } while (pos < GRUB_MINIX_INODE_SIZE (data));
434
435 grub_free (fpath);
436 grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found");
437 return grub_errno;
438 }
439
440
441 /* Mount the filesystem on the disk DISK. */
442 static struct grub_minix_data *
grub_minix_mount(grub_disk_t disk)443 grub_minix_mount (grub_disk_t disk)
444 {
445 struct grub_minix_data *data;
446
447 data = grub_malloc (sizeof (struct grub_minix_data));
448 if (!data)
449 return 0;
450
451 /* Read the superblock. */
452 grub_disk_read (disk, GRUB_MINIX_SBLOCK, 0,
453 sizeof (struct grub_minix_sblock),&data->sblock);
454 if (grub_errno)
455 goto fail;
456
457 if (grub_le_to_cpu16 (data->sblock.magic) == GRUB_MINIX_MAGIC)
458 {
459 data->version = 1;
460 data->filename_size = 14;
461 }
462 else if (grub_le_to_cpu16 (data->sblock.magic) == GRUB_MINIX2_MAGIC)
463 {
464 data->version = 2;
465 data->filename_size = 14;
466 }
467 else if (grub_le_to_cpu16 (data->sblock.magic) == GRUB_MINIX_MAGIC_30)
468 {
469 data->version = 1;
470 data->filename_size = 30;
471 }
472 else if (grub_le_to_cpu16 (data->sblock.magic) == GRUB_MINIX2_MAGIC_30)
473 {
474 data->version = 2;
475 data->filename_size = 30;
476 }
477 else
478 goto fail;
479
480 data->disk = disk;
481 data->linknest = 0;
482
483 return data;
484
485 fail:
486 grub_free (data);
487 grub_error (GRUB_ERR_BAD_FS, "not a minix filesystem");
488 return 0;
489 }
490
491 static grub_err_t
grub_minix_dir(grub_device_t device,const char * path,int (* hook)(const char * filename,const struct grub_dirhook_info * info,void * closure),void * closure)492 grub_minix_dir (grub_device_t device, const char *path,
493 int (*hook) (const char *filename,
494 const struct grub_dirhook_info *info,
495 void *closure),
496 void *closure)
497 {
498 struct grub_minix_data *data = 0;
499 unsigned int pos = 0;
500
501 data = grub_minix_mount (device->disk);
502 if (!data)
503 return grub_errno;
504
505 grub_minix_read_inode (data, GRUB_MINIX_ROOT_INODE);
506 if (grub_errno)
507 goto fail;
508
509 grub_minix_find_file (data, path);
510 if (grub_errno)
511 goto fail;
512
513 if ((GRUB_MINIX_INODE_MODE (data) & GRUB_MINIX_IFDIR) != GRUB_MINIX_IFDIR)
514 {
515 grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory");
516 goto fail;
517 }
518
519 if (hook)
520 while (pos < GRUB_MINIX_INODE_SIZE (data))
521 {
522 grub_uint16_t ino;
523 #ifndef _MSC_VER
524 char filename[data->filename_size + 1];
525 #else
526 char * filename = grub_malloc(data->filename_size + 1);
527 #endif
528 int dirino = data->ino;
529 struct grub_dirhook_info info;
530 grub_memset (&info, 0, sizeof (info));
531
532
533 if (grub_minix_read_file (data, 0, 0, pos, sizeof (ino),
534 (char *) &ino) < 0)
535 return grub_errno;
536
537 if (grub_minix_read_file (data, 0, 0, pos + sizeof (ino),
538 data->filename_size,
539 (char *) filename) < 0)
540 return grub_errno;
541 filename[data->filename_size] = '\0';
542
543 /* The filetype is not stored in the dirent. Read the inode to
544 find out the filetype. This *REALLY* sucks. */
545 grub_minix_read_inode (data, grub_le_to_cpu16 (ino));
546 info.dir = ((GRUB_MINIX_INODE_MODE (data)
547 & GRUB_MINIX_IFDIR) == GRUB_MINIX_IFDIR);
548 if (hook (filename, &info, closure))
549 break;
550
551 /* Load the old inode back in. */
552 grub_minix_read_inode (data, dirino);
553
554 pos += sizeof (ino) + data->filename_size;
555 }
556
557 fail:
558 grub_free (data);
559 return grub_errno;
560 }
561
562
563 /* Open a file named NAME and initialize FILE. */
564 static grub_err_t
grub_minix_open(struct grub_file * file,const char * name)565 grub_minix_open (struct grub_file *file, const char *name)
566 {
567 struct grub_minix_data *data;
568 data = grub_minix_mount (file->device->disk);
569 if (!data)
570 return grub_errno;
571
572 /* Open the inode op the root directory. */
573 grub_minix_read_inode (data, GRUB_MINIX_ROOT_INODE);
574 if (grub_errno)
575 {
576 grub_free (data);
577 return grub_errno;
578 }
579
580 if (!name || name[0] != '/')
581 {
582 grub_error (GRUB_ERR_BAD_FILENAME, "bad filename");
583 return grub_errno;
584 }
585
586 /* Traverse the directory tree to the node that should be
587 opened. */
588 grub_minix_find_file (data, name);
589 if (grub_errno)
590 {
591 grub_free (data);
592 return grub_errno;
593 }
594
595 file->data = data;
596 file->size = GRUB_MINIX_INODE_SIZE (data);
597
598 return GRUB_ERR_NONE;
599 }
600
601
602 static grub_ssize_t
grub_minix_read(grub_file_t file,char * buf,grub_size_t len)603 grub_minix_read (grub_file_t file, char *buf, grub_size_t len)
604 {
605 struct grub_minix_data *data =
606 (struct grub_minix_data *) file->data;
607
608 return grub_minix_read_file (data, file->read_hook, file->closure,
609 file->offset, len, buf);
610 }
611
612
613 static grub_err_t
grub_minix_close(grub_file_t file)614 grub_minix_close (grub_file_t file)
615 {
616 grub_free (file->data);
617
618 return GRUB_ERR_NONE;
619 }
620
621
622 static grub_err_t
grub_minix_label(grub_device_t device,char ** label)623 grub_minix_label (grub_device_t device ,
624 char **label )
625 {
626 return GRUB_ERR_NONE;
627 }
628
629
630
631 struct grub_fs grub_minix_fs =
632 {
633 .name = "minix",
634 .dir = grub_minix_dir,
635 .open = grub_minix_open,
636 .read = grub_minix_read,
637 .close = grub_minix_close,
638 .label = grub_minix_label,
639 .next = 0
640 };
641
642