2009-07-21 Pavel Roskin <proski@gnu.org>
[grub2/phcoder.git] / fs / minix.c
blob44218fb8959c0d722875101c4cb5b502cfe21869
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.
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.
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.
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/>.
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>
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
38 #define GRUB_MINIX_IFDIR 0040000U
39 #define GRUB_MINIX_IFLNK 0120000U
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))
60 struct grub_minix_sblock
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;
72 struct grub_minix_inode
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;
85 struct grub_minix2_inode
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;
102 /* Information about a "mounted" minix filesystem. */
103 struct grub_minix_data
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;
115 static grub_dl_t my_mod;
117 static grub_err_t grub_minix_find_file (struct grub_minix_data *data,
118 const char *path);
120 static int
121 grub_minix_get_file_block (struct grub_minix_data *data, unsigned int blk)
123 struct grub_minix_sblock *sblock = &data->sblock;
124 int indir;
126 auto int grub_get_indir (int, int);
128 /* Read the block pointer in ZONE, on the offset NUM. */
129 int grub_get_indir (int zone, int num)
131 if (data->version == 1)
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);
140 else
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);
151 /* Direct block. */
152 if (blk < 7)
153 return GRUB_MINIX_INODE_DIR_ZONES (data, blk);
155 /* Indirect block. */
156 blk -= 7;
157 if (blk < GRUB_MINIX_ZONESZ / GRUB_MINIX_INODE_BLKSZ (data))
159 indir = grub_get_indir (GRUB_MINIX_INODE_INDIR_ZONE (data), blk);
160 return indir;
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)))
168 indir = grub_get_indir (GRUB_MINIX_INODE_DINDIR_ZONE (data),
169 blk / GRUB_MINIX_ZONESZ);
171 indir = grub_get_indir (indir, blk % GRUB_MINIX_ZONESZ);
173 return indir;
176 /* This should never happen. */
177 grub_error (GRUB_ERR_OUT_OF_RANGE, "file bigger than maximum size");
179 return 0;
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)
191 struct grub_minix_sblock *sblock = &data->sblock;
192 int i;
193 int blockcnt;
195 /* Adjust len so it we can't read past the end of the file. */
196 if (len > GRUB_MINIX_INODE_SIZE (data))
197 len = GRUB_MINIX_INODE_SIZE (data);
199 blockcnt = (len + pos + GRUB_MINIX_BSIZE - 1) / GRUB_MINIX_BSIZE;
201 for (i = pos / GRUB_MINIX_BSIZE; i < blockcnt; i++)
203 int blknr;
204 int blockoff = pos % GRUB_MINIX_BSIZE;
205 int blockend = GRUB_MINIX_BSIZE;
207 int skipfirst = 0;
209 blknr = grub_minix_get_file_block (data, i);
210 if (grub_errno)
211 return -1;
213 /* Last block. */
214 if (i == blockcnt - 1)
216 blockend = (len + pos) % GRUB_MINIX_BSIZE;
218 if (!blockend)
219 blockend = GRUB_MINIX_BSIZE;
222 /* First block. */
223 if (i == (pos / (int) GRUB_MINIX_BSIZE))
225 skipfirst = blockoff;
226 blockend -= skipfirst;
229 data->disk->read_hook = read_hook;
230 grub_disk_read (data->disk, blknr << GRUB_MINIX_LOG2_ZONESZ,
231 skipfirst, blockend, buf);
233 data->disk->read_hook = 0;
234 if (grub_errno)
235 return -1;
237 buf += GRUB_MINIX_BSIZE - skipfirst;
240 return len;
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)
249 struct grub_minix_sblock *sblock = &data->sblock;
251 /* Block in which the inode is stored. */
252 int block;
253 data->ino = ino;
255 /* The first inode in minix is inode 1. */
256 ino--;
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);
262 if (data->version == 1)
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));
269 grub_disk_read (data->disk, block, offs,
270 sizeof (struct grub_minix_inode), &data->inode);
272 else
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));
280 grub_disk_read (data->disk, block, offs,
281 sizeof (struct grub_minix2_inode),&data->inode2);
284 return GRUB_ERR_NONE;
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)
293 char symlink[GRUB_MINIX_INODE_SIZE (data) + 1];
295 if (++data->linknest > GRUB_MINIX_MAX_SYMLNK_CNT)
296 return grub_error (GRUB_ERR_SYMLINK_LOOP, "too deep nesting of symlinks");
298 if (grub_minix_read_file (data, 0, 0,
299 GRUB_MINIX_INODE_SIZE (data), symlink) < 0)
300 return grub_errno;
302 symlink[GRUB_MINIX_INODE_SIZE (data)] = '\0';
304 /* The symlink is an absolute path, go back to the root inode. */
305 if (symlink[0] == '/')
306 ino = GRUB_MINIX_ROOT_INODE;
308 /* Now load in the old inode. */
309 if (grub_minix_read_inode (data, ino))
310 return grub_errno;
312 grub_minix_find_file (data, symlink);
313 if (grub_errno)
314 grub_error (grub_errno, "Can not follow symlink `%s'.", symlink);
316 return grub_errno;
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)
325 char fpath[grub_strlen (path) + 1];
326 char *name = fpath;
327 char *next;
328 unsigned int pos = 0;
329 int dirino;
331 grub_strcpy (fpath, path);
333 /* Skip the first slash. */
334 if (name[0] == '/')
336 name++;
337 if (!*name)
338 return 0;
341 /* Extract the actual part from the pathname. */
342 next = grub_strchr (name, '/');
343 if (next)
345 next[0] = '\0';
346 next++;
351 grub_uint16_t ino;
352 char filename[data->filename_size + 1];
354 if (grub_strlen (name) == 0)
355 return GRUB_ERR_NONE;
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;
364 filename[data->filename_size] = '\0';
366 /* Check if the current direntry matches the current part of the
367 pathname. */
368 if (!grub_strcmp (name, filename))
370 dirino = data->ino;
371 grub_minix_read_inode (data, grub_le_to_cpu16 (ino));
373 /* Follow the symlink. */
374 if ((GRUB_MINIX_INODE_MODE (data)
375 & GRUB_MINIX_IFLNK) == GRUB_MINIX_IFLNK)
377 grub_minix_lookup_symlink (data, dirino);
378 if (grub_errno)
379 return grub_errno;
382 if (!next)
383 return 0;
385 pos = 0;
387 name = next;
388 next = grub_strchr (name, '/');
389 if (next)
391 next[0] = '\0';
392 next++;
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");
399 continue;
402 pos += sizeof (ino) + data->filename_size;
403 } while (pos < GRUB_MINIX_INODE_SIZE (data));
405 grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found");
406 return grub_errno;
410 /* Mount the filesystem on the disk DISK. */
411 static struct grub_minix_data *
412 grub_minix_mount (grub_disk_t disk)
414 struct grub_minix_data *data;
416 data = grub_malloc (sizeof (struct grub_minix_data));
417 if (!data)
418 return 0;
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;
426 if (grub_le_to_cpu16 (data->sblock.magic) == GRUB_MINIX_MAGIC)
428 data->version = 1;
429 data->filename_size = 14;
431 else if (grub_le_to_cpu16 (data->sblock.magic) == GRUB_MINIX2_MAGIC)
433 data->version = 2;
434 data->filename_size = 14;
436 else if (grub_le_to_cpu16 (data->sblock.magic) == GRUB_MINIX_MAGIC_30)
438 data->version = 1;
439 data->filename_size = 30;
441 else if (grub_le_to_cpu16 (data->sblock.magic) == GRUB_MINIX2_MAGIC_30)
443 data->version = 2;
444 data->filename_size = 30;
446 else
447 goto fail;
449 data->disk = disk;
450 data->linknest = 0;
452 return data;
454 fail:
455 grub_free (data);
456 grub_error (GRUB_ERR_BAD_FS, "not a minix filesystem");
457 return 0;
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))
465 struct grub_minix_data *data = 0;
466 struct grub_minix_sblock *sblock;
467 unsigned int pos = 0;
469 data = grub_minix_mount (device->disk);
470 if (!data)
471 return grub_errno;
473 grub_minix_read_inode (data, GRUB_MINIX_ROOT_INODE);
474 if (grub_errno)
475 goto fail;
477 sblock = &data->sblock;
479 grub_minix_find_file (data, path);
480 if (grub_errno)
481 goto fail;
483 if ((GRUB_MINIX_INODE_MODE (data) & GRUB_MINIX_IFDIR) != GRUB_MINIX_IFDIR)
485 grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory");
486 goto fail;
489 while (pos < GRUB_MINIX_INODE_SIZE (data))
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));
498 if (grub_minix_read_file (data, 0, pos, sizeof (ino),
499 (char *) &ino) < 0)
500 return grub_errno;
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';
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;
516 /* Load the old inode back in. */
517 grub_minix_read_inode (data, dirino);
519 pos += sizeof (ino) + data->filename_size;
522 fail:
523 grub_free (data);
524 return grub_errno;
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)
532 struct grub_minix_data *data;
533 data = grub_minix_mount (file->device->disk);
534 if (!data)
535 return grub_errno;
537 /* Open the inode op the root directory. */
538 grub_minix_read_inode (data, GRUB_MINIX_ROOT_INODE);
539 if (grub_errno)
541 grub_free (data);
542 return grub_errno;
545 if (!name || name[0] != '/')
547 grub_error (GRUB_ERR_BAD_FILENAME, "bad filename");
548 return grub_errno;
551 /* Traverse the directory tree to the node that should be
552 opened. */
553 grub_minix_find_file (data, name);
554 if (grub_errno)
556 grub_free (data);
557 return grub_errno;
560 file->data = data;
561 file->size = GRUB_MINIX_INODE_SIZE (data);
563 return GRUB_ERR_NONE;
567 static grub_ssize_t
568 grub_minix_read (grub_file_t file, char *buf, grub_size_t len)
570 struct grub_minix_data *data =
571 (struct grub_minix_data *) file->data;
573 return grub_minix_read_file (data, file->read_hook, file->offset, len, buf);
577 static grub_err_t
578 grub_minix_close (grub_file_t file)
580 grub_free (file->data);
582 return GRUB_ERR_NONE;
586 static grub_err_t
587 grub_minix_label (grub_device_t device __attribute ((unused)),
588 char **label __attribute ((unused)))
590 return GRUB_ERR_NONE;
594 static struct grub_fs grub_minix_fs =
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
605 GRUB_MOD_INIT(minix)
607 grub_fs_register (&grub_minix_fs);
608 my_mod = mod;
611 GRUB_MOD_FINI(minix)
613 grub_fs_unregister (&grub_minix_fs);