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