]> git.proxmox.com Git - grub2.git/blob - grub-core/fs/minix.c
merge with mainline
[grub2.git] / grub-core / 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 static grub_dl_t my_mod;
116 \f
117 static grub_err_t grub_minix_find_file (struct grub_minix_data *data,
118 const char *path);
119
120 static int
121 grub_minix_get_file_block (struct grub_minix_data *data, unsigned int blk)
122 {
123 struct grub_minix_sblock *sblock = &data->sblock;
124 int indir;
125
126 auto int grub_get_indir (int, int);
127
128 /* Read the block pointer in ZONE, on the offset NUM. */
129 int grub_get_indir (int zone, int num)
130 {
131 if (data->version == 1)
132 {
133 grub_uint16_t indir16;
134 grub_disk_read (data->disk,
135 zone << GRUB_MINIX_LOG2_ZONESZ,
136 sizeof (grub_uint16_t) * num,
137 sizeof (grub_uint16_t), (char *) &indir16);
138 return grub_le_to_cpu16 (indir16);
139 }
140 else
141 {
142 grub_uint32_t indir32;
143 grub_disk_read (data->disk,
144 zone << GRUB_MINIX_LOG2_ZONESZ,
145 sizeof (grub_uint32_t) * num,
146 sizeof (grub_uint32_t), (char *) &indir32);
147 return grub_le_to_cpu32 (indir32);
148 }
149 }
150
151 /* Direct block. */
152 if (blk < 7)
153 return GRUB_MINIX_INODE_DIR_ZONES (data, blk);
154
155 /* Indirect block. */
156 blk -= 7;
157 if (blk < GRUB_MINIX_ZONESZ / GRUB_MINIX_INODE_BLKSZ (data))
158 {
159 indir = grub_get_indir (GRUB_MINIX_INODE_INDIR_ZONE (data), blk);
160 return indir;
161 }
162
163 /* Double indirect block. */
164 blk -= GRUB_MINIX_ZONESZ / GRUB_MINIX_INODE_BLKSZ (data);
165 if (blk < (GRUB_MINIX_ZONESZ / GRUB_MINIX_INODE_BLKSZ (data))
166 * (GRUB_MINIX_ZONESZ / GRUB_MINIX_INODE_BLKSZ (data)))
167 {
168 indir = grub_get_indir (GRUB_MINIX_INODE_DINDIR_ZONE (data),
169 blk / GRUB_MINIX_ZONESZ);
170
171 indir = grub_get_indir (indir, blk % GRUB_MINIX_ZONESZ);
172
173 return indir;
174 }
175
176 /* This should never happen. */
177 grub_error (GRUB_ERR_OUT_OF_RANGE, "file bigger than maximum size");
178
179 return 0;
180 }
181
182
183 /* Read LEN bytes from the file described by DATA starting with byte
184 POS. Return the amount of read bytes in READ. */
185 static grub_ssize_t
186 grub_minix_read_file (struct grub_minix_data *data,
187 void NESTED_FUNC_ATTR (*read_hook) (grub_disk_addr_t sector,
188 unsigned offset, unsigned length),
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 grub_disk_read (data->disk, blknr << GRUB_MINIX_LOG2_ZONESZ,
231 skipfirst, blockend, buf);
232
233 data->disk->read_hook = 0;
234 if (grub_errno)
235 return -1;
236
237 buf += GRUB_MINIX_BSIZE - skipfirst;
238 }
239
240 return len;
241 }
242
243
244 /* Read inode INO from the mounted filesystem described by DATA. This
245 inode is used by default now. */
246 static grub_err_t
247 grub_minix_read_inode (struct grub_minix_data *data, int ino)
248 {
249 struct grub_minix_sblock *sblock = &data->sblock;
250
251 /* Block in which the inode is stored. */
252 int block;
253 data->ino = ino;
254
255 /* The first inode in minix is inode 1. */
256 ino--;
257
258 block = ((2 + grub_le_to_cpu16 (sblock->inode_bmap_size)
259 + grub_le_to_cpu16 (sblock->zone_bmap_size))
260 << GRUB_MINIX_LOG2_BSIZE);
261
262 if (data->version == 1)
263 {
264 block += ino / (GRUB_DISK_SECTOR_SIZE / sizeof (struct grub_minix_inode));
265 int offs = (ino % (GRUB_DISK_SECTOR_SIZE
266 / sizeof (struct grub_minix_inode))
267 * sizeof (struct grub_minix_inode));
268
269 grub_disk_read (data->disk, block, offs,
270 sizeof (struct grub_minix_inode), &data->inode);
271 }
272 else
273 {
274 block += ino / (GRUB_DISK_SECTOR_SIZE
275 / sizeof (struct grub_minix2_inode));
276 int offs = (ino
277 % (GRUB_DISK_SECTOR_SIZE / sizeof (struct grub_minix2_inode))
278 * sizeof (struct grub_minix2_inode));
279
280 grub_disk_read (data->disk, block, offs,
281 sizeof (struct grub_minix2_inode),&data->inode2);
282 }
283
284 return GRUB_ERR_NONE;
285 }
286
287
288 /* Lookup the symlink the current inode points to. INO is the inode
289 number of the directory the symlink is relative to. */
290 static grub_err_t
291 grub_minix_lookup_symlink (struct grub_minix_data *data, int ino)
292 {
293 char symlink[GRUB_MINIX_INODE_SIZE (data) + 1];
294
295 if (++data->linknest > GRUB_MINIX_MAX_SYMLNK_CNT)
296 return grub_error (GRUB_ERR_SYMLINK_LOOP, "too deep nesting of symlinks");
297
298 if (grub_minix_read_file (data, 0, 0,
299 GRUB_MINIX_INODE_SIZE (data), symlink) < 0)
300 return grub_errno;
301
302 symlink[GRUB_MINIX_INODE_SIZE (data)] = '\0';
303
304 /* The symlink is an absolute path, go back to the root inode. */
305 if (symlink[0] == '/')
306 ino = GRUB_MINIX_ROOT_INODE;
307
308 /* Now load in the old inode. */
309 if (grub_minix_read_inode (data, ino))
310 return grub_errno;
311
312 grub_minix_find_file (data, symlink);
313 if (grub_errno)
314 grub_error (grub_errno, "cannot follow symlink `%s'", symlink);
315
316 return grub_errno;
317 }
318
319
320 /* Find the file with the pathname PATH on the filesystem described by
321 DATA. */
322 static grub_err_t
323 grub_minix_find_file (struct grub_minix_data *data, const char *path)
324 {
325 char fpath[grub_strlen (path) + 1];
326 char *name = fpath;
327 char *next;
328 unsigned int pos = 0;
329 int dirino;
330
331 grub_strcpy (fpath, path);
332
333 /* Skip the first slash. */
334 if (name[0] == '/')
335 {
336 name++;
337 if (!*name)
338 return 0;
339 }
340
341 /* Extract the actual part from the pathname. */
342 next = grub_strchr (name, '/');
343 if (next)
344 {
345 next[0] = '\0';
346 next++;
347 }
348
349 do
350 {
351 grub_uint16_t ino;
352 char filename[data->filename_size + 1];
353
354 if (grub_strlen (name) == 0)
355 return GRUB_ERR_NONE;
356
357 if (grub_minix_read_file (data, 0, pos, sizeof (ino),
358 (char *) &ino) < 0)
359 return grub_errno;
360 if (grub_minix_read_file (data, 0, pos + sizeof (ino),
361 data->filename_size, (char *) filename)< 0)
362 return grub_errno;
363
364 filename[data->filename_size] = '\0';
365
366 /* Check if the current direntry matches the current part of the
367 pathname. */
368 if (!grub_strcmp (name, filename))
369 {
370 dirino = data->ino;
371 grub_minix_read_inode (data, grub_le_to_cpu16 (ino));
372
373 /* Follow the symlink. */
374 if ((GRUB_MINIX_INODE_MODE (data)
375 & GRUB_MINIX_IFLNK) == GRUB_MINIX_IFLNK)
376 {
377 grub_minix_lookup_symlink (data, dirino);
378 if (grub_errno)
379 return grub_errno;
380 }
381
382 if (!next)
383 return 0;
384
385 pos = 0;
386
387 name = next;
388 next = grub_strchr (name, '/');
389 if (next)
390 {
391 next[0] = '\0';
392 next++;
393 }
394
395 if ((GRUB_MINIX_INODE_MODE (data)
396 & GRUB_MINIX_IFDIR) != GRUB_MINIX_IFDIR)
397 return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory");
398
399 continue;
400 }
401
402 pos += sizeof (ino) + data->filename_size;
403 } while (pos < GRUB_MINIX_INODE_SIZE (data));
404
405 grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found");
406 return grub_errno;
407 }
408
409
410 /* Mount the filesystem on the disk DISK. */
411 static struct grub_minix_data *
412 grub_minix_mount (grub_disk_t disk)
413 {
414 struct grub_minix_data *data;
415
416 data = grub_malloc (sizeof (struct grub_minix_data));
417 if (!data)
418 return 0;
419
420 /* Read the superblock. */
421 grub_disk_read (disk, GRUB_MINIX_SBLOCK, 0,
422 sizeof (struct grub_minix_sblock),&data->sblock);
423 if (grub_errno)
424 goto fail;
425
426 if (grub_le_to_cpu16 (data->sblock.magic) == GRUB_MINIX_MAGIC)
427 {
428 data->version = 1;
429 data->filename_size = 14;
430 }
431 else if (grub_le_to_cpu16 (data->sblock.magic) == GRUB_MINIX2_MAGIC)
432 {
433 data->version = 2;
434 data->filename_size = 14;
435 }
436 else if (grub_le_to_cpu16 (data->sblock.magic) == GRUB_MINIX_MAGIC_30)
437 {
438 data->version = 1;
439 data->filename_size = 30;
440 }
441 else if (grub_le_to_cpu16 (data->sblock.magic) == GRUB_MINIX2_MAGIC_30)
442 {
443 data->version = 2;
444 data->filename_size = 30;
445 }
446 else
447 goto fail;
448
449 data->disk = disk;
450 data->linknest = 0;
451
452 return data;
453
454 fail:
455 grub_free (data);
456 grub_error (GRUB_ERR_BAD_FS, "not a minix filesystem");
457 return 0;
458 }
459 \f
460 static grub_err_t
461 grub_minix_dir (grub_device_t device, const char *path,
462 int (*hook) (const char *filename,
463 const struct grub_dirhook_info *info))
464 {
465 struct grub_minix_data *data = 0;
466 struct grub_minix_sblock *sblock;
467 unsigned int pos = 0;
468
469 data = grub_minix_mount (device->disk);
470 if (!data)
471 return grub_errno;
472
473 grub_minix_read_inode (data, GRUB_MINIX_ROOT_INODE);
474 if (grub_errno)
475 goto fail;
476
477 sblock = &data->sblock;
478
479 grub_minix_find_file (data, path);
480 if (grub_errno)
481 goto fail;
482
483 if ((GRUB_MINIX_INODE_MODE (data) & GRUB_MINIX_IFDIR) != GRUB_MINIX_IFDIR)
484 {
485 grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory");
486 goto fail;
487 }
488
489 while (pos < GRUB_MINIX_INODE_SIZE (data))
490 {
491 grub_uint16_t ino;
492 char filename[data->filename_size + 1];
493 int dirino = data->ino;
494 struct grub_dirhook_info info;
495 grub_memset (&info, 0, sizeof (info));
496
497
498 if (grub_minix_read_file (data, 0, pos, sizeof (ino),
499 (char *) &ino) < 0)
500 return grub_errno;
501
502 if (grub_minix_read_file (data, 0, pos + sizeof (ino),
503 data->filename_size,
504 (char *) filename) < 0)
505 return grub_errno;
506 filename[data->filename_size] = '\0';
507
508 /* The filetype is not stored in the dirent. Read the inode to
509 find out the filetype. This *REALLY* sucks. */
510 grub_minix_read_inode (data, grub_le_to_cpu16 (ino));
511 info.dir = ((GRUB_MINIX_INODE_MODE (data)
512 & GRUB_MINIX_IFDIR) == GRUB_MINIX_IFDIR);
513 if (hook (filename, &info) ? 1 : 0)
514 break;
515
516 /* Load the old inode back in. */
517 grub_minix_read_inode (data, dirino);
518
519 pos += sizeof (ino) + data->filename_size;
520 }
521
522 fail:
523 grub_free (data);
524 return grub_errno;
525 }
526
527
528 /* Open a file named NAME and initialize FILE. */
529 static grub_err_t
530 grub_minix_open (struct grub_file *file, const char *name)
531 {
532 struct grub_minix_data *data;
533 data = grub_minix_mount (file->device->disk);
534 if (!data)
535 return grub_errno;
536
537 /* Open the inode op the root directory. */
538 grub_minix_read_inode (data, GRUB_MINIX_ROOT_INODE);
539 if (grub_errno)
540 {
541 grub_free (data);
542 return grub_errno;
543 }
544
545 if (!name || name[0] != '/')
546 {
547 grub_error (GRUB_ERR_BAD_FILENAME, "bad filename");
548 return grub_errno;
549 }
550
551 /* Traverse the directory tree to the node that should be
552 opened. */
553 grub_minix_find_file (data, name);
554 if (grub_errno)
555 {
556 grub_free (data);
557 return grub_errno;
558 }
559
560 file->data = data;
561 file->size = GRUB_MINIX_INODE_SIZE (data);
562
563 return GRUB_ERR_NONE;
564 }
565
566
567 static grub_ssize_t
568 grub_minix_read (grub_file_t file, char *buf, grub_size_t len)
569 {
570 struct grub_minix_data *data =
571 (struct grub_minix_data *) file->data;
572
573 return grub_minix_read_file (data, file->read_hook, file->offset, len, buf);
574 }
575
576
577 static grub_err_t
578 grub_minix_close (grub_file_t file)
579 {
580 grub_free (file->data);
581
582 return GRUB_ERR_NONE;
583 }
584
585
586 static grub_err_t
587 grub_minix_label (grub_device_t device __attribute ((unused)),
588 char **label __attribute ((unused)))
589 {
590 return GRUB_ERR_NONE;
591 }
592
593 \f
594 static struct grub_fs grub_minix_fs =
595 {
596 .name = "minix",
597 .dir = grub_minix_dir,
598 .open = grub_minix_open,
599 .read = grub_minix_read,
600 .close = grub_minix_close,
601 .label = grub_minix_label,
602 .next = 0
603 };
604
605 GRUB_MOD_INIT(minix)
606 {
607 grub_fs_register (&grub_minix_fs);
608 my_mod = mod;
609 }
610
611 GRUB_MOD_FINI(minix)
612 {
613 grub_fs_unregister (&grub_minix_fs);
614 }