]> git.proxmox.com Git - grub2.git/blob - fs/ext2.c
shinori K. Okuji <okuji@enbug.org>
[grub2.git] / fs / ext2.c
1 /* ext2.c - Second Extended filesystem */
2 /*
3 * PUPA -- Preliminary Universal Programming Architecture for GRUB
4 * Copyright (C) 2003, 2004 Free Software Foundation, Inc.
5 *
6 * This program 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 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program 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 this program; if not, write to the Free Software
18 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 */
20
21 /* Magic value used to identify an ext2 filesystem. */
22 #define EXT2_MAGIC 0xEF53
23 /* Amount of indirect blocks in an inode. */
24 #define INDIRECT_BLOCKS 12
25 /* Maximum lenght of a pathname. */
26 #define EXT2_PATH_MAX 4096
27 /* Maximum nesting of symlinks, used to prevent a loop. */
28 #define EXT2_MAX_SYMLINKCNT 8
29
30 /* Filetype used in directory entry. */
31 #define FILETYPE_DIRECTORY 2
32 #define FILETYPE_SYMLINK 7
33
34 #include <pupa/err.h>
35 #include <pupa/file.h>
36 #include <pupa/mm.h>
37 #include <pupa/misc.h>
38 #include <pupa/disk.h>
39 #include <pupa/dl.h>
40 #include <pupa/types.h>
41
42 /* Log2 size of ext2 block in 512 blocks. */
43 #define LOG2_EXT2_BLOCK_SIZE(data) \
44 (pupa_le_to_cpu32 (data->sblock.log2_block_size) + 1)
45
46 /* Log2 size of ext2 block in bytes. */
47 #define LOG2_BLOCK_SIZE(data) \
48 (pupa_le_to_cpu32 (data->sblock.log2_block_size) + 10)
49
50 /* The size of an ext2 block in bytes. */
51 #define EXT2_BLOCK_SIZE(data) (1 << LOG2_BLOCK_SIZE(data))
52
53 /* The ext2 superblock. */
54 struct pupa_ext_sblock
55 {
56 pupa_uint32_t total_inodes;
57 pupa_uint32_t total_blocks;
58 pupa_uint32_t reserved_blocks;
59 pupa_uint32_t free_blocks;
60 pupa_uint32_t free_inodes;
61 pupa_uint32_t first_data_block;
62 pupa_uint32_t log2_block_size;
63 pupa_uint32_t log2_fragment_size;
64 pupa_uint32_t blocks_per_group;
65 pupa_uint32_t fragments_per_group;
66 pupa_uint32_t inodes_per_group;
67 pupa_uint32_t mtime;
68 pupa_uint32_t utime;
69 pupa_uint16_t mnt_count;
70 pupa_uint16_t max_mnt_count;
71 pupa_uint16_t magic;
72 pupa_uint16_t fs_state;
73 pupa_uint16_t error_handling;
74 pupa_uint16_t minor_revision_level;
75 pupa_uint32_t lastcheck;
76 pupa_uint32_t checkinterval;
77 pupa_uint32_t creator_os;
78 pupa_uint32_t revision_level;
79 pupa_uint16_t uid_reserved;
80 pupa_uint16_t gid_reserved;
81 pupa_uint32_t first_inode;
82 pupa_uint16_t inode_size;
83 pupa_uint16_t block_group_number;
84 pupa_uint32_t feature_compatibility;
85 pupa_uint32_t feature_incompat;
86 pupa_uint32_t feature_ro_compat;
87 pupa_uint32_t unique_id[4];
88 char volume_name[16];
89 char last_mounted_on[64];
90 pupa_uint32_t compression_info;
91 };
92
93 /* The ext2 blockgroup. */
94 struct ext2_block_group
95 {
96 pupa_uint32_t block_id;
97 pupa_uint32_t inode_id;
98 pupa_uint32_t inode_table_id;
99 pupa_uint16_t free_blocks;
100 pupa_uint16_t free_inodes;
101 pupa_uint16_t pad;
102 pupa_uint32_t reserved[3];
103 };
104
105 /* The ext2 inode. */
106 struct pupa_ext2_inode
107 {
108 pupa_uint16_t mode;
109 pupa_uint16_t uid;
110 pupa_uint32_t size;
111 pupa_uint32_t atime;
112 pupa_uint32_t ctime;
113 pupa_uint32_t mtime;
114 pupa_uint32_t dtime;
115 pupa_uint16_t gid;
116 pupa_uint16_t nlinks;
117 pupa_uint32_t blockcnt; /* Blocks of 512 bytes!! */
118 pupa_uint32_t flags;
119 pupa_uint32_t osd1;
120 union
121 {
122 struct datablocks
123 {
124 pupa_uint32_t dir_blocks[INDIRECT_BLOCKS];
125 pupa_uint32_t indir_block;
126 pupa_uint32_t double_indir_block;
127 pupa_uint32_t tripple_indir_block;
128 } blocks;
129 char symlink[60];
130 };
131 pupa_uint32_t version;
132 pupa_uint32_t acl;
133 pupa_uint32_t dir_acl;
134 pupa_uint32_t fragment_addr;
135 pupa_uint32_t osd2[3];
136 };
137
138 /* The header of an ext2 directory entry. */
139 struct ext2_dirent
140 {
141 pupa_uint32_t inode;
142 pupa_uint16_t direntlen;
143 pupa_uint8_t namelen;
144 pupa_uint8_t filetype;
145 };
146
147 /* Information about a "mounted" ext2 filesystem. */
148 struct pupa_ext2_data
149 {
150 struct pupa_ext_sblock sblock;
151 pupa_disk_t disk;
152 struct pupa_ext2_inode inode;
153 };
154
155 #ifndef PUPA_UTIL
156 static pupa_dl_t my_mod;
157 #endif
158 \f
159 /* Read into BLKGRP the blockgroup descriptor of blockgroup GROUP of
160 the mounted filesystem DATA. */
161 inline static pupa_err_t
162 pupa_ext2_blockgroup (struct pupa_ext2_data *data, int group,
163 struct ext2_block_group *blkgrp)
164 {
165 return pupa_disk_read (data->disk,
166 ((pupa_le_to_cpu32 (data->sblock.first_data_block) + 1)
167 << LOG2_EXT2_BLOCK_SIZE (data)),
168 group * sizeof (struct ext2_block_group),
169 sizeof (struct ext2_block_group), (char *) blkgrp);
170 }
171
172 /* Return in BLOCK the on disk block number of block FILEBLOCK in the
173 opened file descibed by DATA. If this block is not stored on disk
174 in case of a sparse file return 0. */
175 static pupa_err_t
176 pupa_ext2_get_file_block (struct pupa_ext2_data *data,
177 int fileblock, int *block)
178 {
179 int blknr;
180 struct pupa_ext2_inode *inode = &data->inode;
181
182 /* Direct blocks. */
183 if (fileblock < INDIRECT_BLOCKS)
184 blknr = pupa_le_to_cpu32 (inode->blocks.dir_blocks[fileblock]);
185 /* Indirect. */
186 else if (fileblock < INDIRECT_BLOCKS + EXT2_BLOCK_SIZE (data) / 4)
187 {
188 pupa_uint32_t indir[EXT2_BLOCK_SIZE (data) / 4];
189
190 if (pupa_disk_read (data->disk,
191 pupa_le_to_cpu32 (inode->blocks.indir_block)
192 << LOG2_EXT2_BLOCK_SIZE (data),
193 0, EXT2_BLOCK_SIZE (data), (char *) indir))
194 return pupa_errno;
195
196 blknr = pupa_le_to_cpu32 (indir[fileblock - INDIRECT_BLOCKS]);
197 }
198 /* Double indirect. */
199 else if (fileblock < INDIRECT_BLOCKS + EXT2_BLOCK_SIZE (data) / 4
200 * (EXT2_BLOCK_SIZE (data) / 4 + 1))
201 {
202 unsigned int perblock = EXT2_BLOCK_SIZE (data) / 4;
203 unsigned int rblock = fileblock - (INDIRECT_BLOCKS
204 + EXT2_BLOCK_SIZE (data) / 4);
205 pupa_uint32_t indir[EXT2_BLOCK_SIZE (data) / 4];
206
207 if (pupa_disk_read (data->disk,
208 pupa_le_to_cpu32 (inode->blocks.double_indir_block)
209 << LOG2_EXT2_BLOCK_SIZE (data),
210 0, EXT2_BLOCK_SIZE (data), (char *) indir))
211 return pupa_errno;
212
213 if (pupa_disk_read (data->disk,
214 pupa_le_to_cpu32 (indir[rblock / perblock])
215 << LOG2_EXT2_BLOCK_SIZE (data),
216 0, EXT2_BLOCK_SIZE (data), (char *) indir))
217 return pupa_errno;
218
219
220 blknr = pupa_le_to_cpu32 (indir[rblock % perblock]);
221 }
222 /* Tripple indirect. */
223 else
224 {
225 pupa_error (PUPA_ERR_NOT_IMPLEMENTED_YET,
226 "ext2fs doesn't support tripple indirect blocks");
227 return pupa_errno;
228 }
229
230 *block = blknr;
231
232 return 0;
233 }
234
235 /* Read LEN bytes from the file described by DATA starting with byte
236 POS. Return the amount of read bytes in READ. */
237 static pupa_ssize_t
238 pupa_ext2_read_file (struct pupa_ext2_data *data,
239 void (*read_hook) (unsigned long sector,
240 unsigned offset, unsigned length),
241 int pos, unsigned int len, char *buf)
242 {
243 int i;
244 int blockcnt;
245
246 /* Adjust len so it we can't read past the end of the file. */
247 if (len > pupa_le_to_cpu32 (data->inode.size))
248 len = pupa_le_to_cpu32 (data->inode.size);
249
250 blockcnt = ((len + pos)
251 + EXT2_BLOCK_SIZE (data) - 1) / EXT2_BLOCK_SIZE (data);
252
253 for (i = pos / EXT2_BLOCK_SIZE (data); i < blockcnt; i++)
254 {
255 int blknr;
256 int blockoff = pos % EXT2_BLOCK_SIZE (data);
257 int blockend = EXT2_BLOCK_SIZE (data);
258
259 int skipfirst = 0;
260
261 pupa_ext2_get_file_block (data, i, &blknr);
262 if (pupa_errno)
263 return -1;
264
265 blknr = blknr << LOG2_EXT2_BLOCK_SIZE (data);
266
267 /* Last block. */
268 if (i == blockcnt - 1)
269 {
270 blockend = (len + pos) % EXT2_BLOCK_SIZE (data);
271
272 /* The last portion is exactly EXT2_BLOCK_SIZE (data). */
273 if (!blockend)
274 blockend = EXT2_BLOCK_SIZE (data);
275 }
276
277 /* First block. */
278 if (i == pos / EXT2_BLOCK_SIZE (data))
279 {
280 skipfirst = blockoff;
281 blockend -= skipfirst;
282 }
283
284 /* If the block number is 0 this block is not stored on disk but
285 is zero filled instead. */
286 if (blknr)
287 {
288 data->disk->read_hook = read_hook;
289 pupa_disk_read (data->disk, blknr, skipfirst,
290 blockend, buf);
291 data->disk->read_hook = 0;
292 if (pupa_errno)
293 return -1;
294 }
295 else
296 pupa_memset (buf, EXT2_BLOCK_SIZE (data) - skipfirst, 0);
297
298 buf += EXT2_BLOCK_SIZE (data) - skipfirst;
299 }
300
301 return len;
302 }
303
304
305 /* Read the inode INO for the file described by DATA into INODE. */
306 static pupa_err_t
307 pupa_ext2_read_inode (struct pupa_ext2_data *data,
308 int ino, struct pupa_ext2_inode *inode)
309 {
310 struct ext2_block_group blkgrp;
311 struct pupa_ext_sblock *sblock = &data->sblock;
312 int inodes_per_block;
313
314 unsigned int blkno;
315 unsigned int blkoff;
316
317 /* It is easier to calculate if the first inode is 0. */
318 ino--;
319
320 pupa_ext2_blockgroup (data, ino / pupa_le_to_cpu32 (sblock->inodes_per_group),
321 &blkgrp);
322 if (pupa_errno)
323 return pupa_errno;
324
325 inodes_per_block = EXT2_BLOCK_SIZE (data) / 128;
326 blkno = (ino % pupa_le_to_cpu32 (sblock->inodes_per_group))
327 / inodes_per_block;
328 blkoff = (ino % pupa_le_to_cpu32 (sblock->inodes_per_group))
329 % inodes_per_block;
330
331 /* Read the inode. */
332 if (pupa_disk_read (data->disk,
333 ((pupa_le_to_cpu32 (blkgrp.inode_table_id) + blkno)
334 << LOG2_EXT2_BLOCK_SIZE (data)),
335 sizeof (struct pupa_ext2_inode) * blkoff,
336 sizeof (struct pupa_ext2_inode), (char *) inode))
337 return pupa_errno;
338
339 return 0;
340 }
341
342 static struct pupa_ext2_data *
343 pupa_ext2_mount (pupa_disk_t disk)
344 {
345 struct pupa_ext2_data *data;
346
347 data = pupa_malloc (sizeof (struct pupa_ext2_data));
348 if (!data)
349 return 0;
350
351 /* Read the superblock. */
352 pupa_disk_read (disk, 1 * 2, 0, sizeof (struct pupa_ext_sblock),
353 (char *) &data->sblock);
354 if (pupa_errno)
355 goto fail;
356
357 /* Make sure this is an ext2 filesystem. */
358 if (pupa_le_to_cpu16 (data->sblock.magic) != EXT2_MAGIC)
359 goto fail;
360
361 data->disk = disk;
362 return data;
363
364 fail:
365 pupa_error (PUPA_ERR_BAD_FS, "not an ext2 filesystem");
366 pupa_free (data);
367 return 0;
368 }
369
370 /* Find the file with the pathname PATH on the filesystem described by
371 DATA. Return its inode number in INO. */
372 static pupa_err_t
373 pupa_ext2_find_file (struct pupa_ext2_data *data, const char *path, int *ino)
374 {
375 int blocksize = EXT2_BLOCK_SIZE (data)
376 << pupa_le_to_cpu32 (data->sblock.log2_block_size);
377 struct pupa_ext2_inode *inode = &data->inode;
378 int currinode = 2;
379 int symlinkcnt = 0;
380
381 char fpath[EXT2_PATH_MAX];
382 char *name = fpath;
383
384 pupa_strncpy (fpath, path, EXT2_PATH_MAX);
385
386 if (!name || name[0] != '/')
387 {
388 pupa_error (PUPA_ERR_BAD_FILENAME, "bad filename");
389 return pupa_errno;
390 }
391
392 /* Skip the first slash. */
393 name++;
394 if (!*name)
395 {
396 *ino = 2;
397 return 0;
398 }
399
400 /* Remove trailing "/". */
401 if (name[pupa_strlen (name) - 1] =='/')
402 name[pupa_strlen (name) - 1] = '\0';
403
404 while (*name)
405 {
406 unsigned int fpos = 0;
407 char *next;
408 int namesize;
409
410 /* Extract the actual part from the pathname. */
411 next = pupa_strchr (name, '/');
412 if (next)
413 {
414 next[0] = '\0';
415 next++;
416 }
417
418 namesize = pupa_strlen (name);
419
420 /* Open the file. */
421 pupa_ext2_read_inode (data, currinode, inode);
422 if (pupa_errno)
423 goto fail;
424
425 /* Search the file. */
426 while (fpos < pupa_le_to_cpu32 (inode->size))
427 {
428 struct ext2_dirent dirent;
429
430 /* Read the directory entry. */
431 pupa_ext2_read_file (data, 0, fpos, sizeof (struct ext2_dirent),
432 (char *) &dirent);
433 if (pupa_errno)
434 goto fail;
435
436 if (dirent.namelen != 0)
437 {
438 char filename[dirent.namelen + 1];
439
440 /* Read the filename part of this directory entry. */
441 pupa_ext2_read_file (data, 0, fpos
442 + sizeof (struct ext2_dirent),
443 dirent.namelen, filename);
444 if (pupa_errno)
445 goto fail;
446
447 filename[dirent.namelen] = '\0';
448
449 /* Check if the current directory entry described the
450 file we are looking for. */
451 if (dirent.namelen == namesize
452 && !pupa_strncmp (name, filename, namesize))
453 {
454 /* If this is a symlink, follow it. */
455 if (dirent.filetype == FILETYPE_SYMLINK)
456 {
457 /* XXX: Use malloc instead? */
458 char symlink[blocksize];
459
460 if (++symlinkcnt == EXT2_MAX_SYMLINKCNT)
461 {
462 pupa_error (PUPA_ERR_SYMLINK_LOOP,
463 "too deep nesting of symlinks");
464 goto fail;
465 }
466
467 /* Read the symlink. */
468 pupa_ext2_read_inode (data,
469 pupa_le_to_cpu32 (dirent.inode),
470 inode);
471
472 /* If the filesize of the symlink is bigger than
473 60 the symlink is stored in a separate block,
474 otherwise it is stored in the inode. */
475 if (pupa_le_to_cpu32 (inode->size) <= 60)
476 pupa_strncpy (symlink,
477 inode->symlink,
478 pupa_le_to_cpu32 (inode->size));
479 else
480 {
481 pupa_ext2_read_file (data, 0, 0,
482 pupa_le_to_cpu32 (inode->size),
483 symlink);
484 if (pupa_errno)
485 goto fail;
486 }
487
488 symlink[pupa_le_to_cpu32 (inode->size)] = '\0';
489
490 /* Check if the symlink is absolute or relative. */
491 if (symlink[0] == '/')
492 {
493 pupa_strncpy (fpath, symlink, EXT2_PATH_MAX);
494 name = fpath;
495 currinode = 2;
496 }
497 else
498 {
499 char *bak = 0;
500
501 if (next)
502 {
503 bak = pupa_strdup (next);
504 if (!bak)
505 goto fail;
506 }
507
508 /* Relative symlink, construct the new path. */
509 pupa_strcpy (fpath, symlink);
510 name = fpath;
511
512 if (next)
513 {
514 pupa_strcat (name, "/");
515 pupa_strcat (name, bak);
516 pupa_free (bak);
517 }
518 }
519
520 fpos = 0;
521 break;
522 }
523
524 if (next)
525 {
526 currinode = pupa_le_to_cpu32 (dirent.inode);
527 name = next;
528
529 if (dirent.filetype != FILETYPE_DIRECTORY)
530 {
531 pupa_error (PUPA_ERR_BAD_FILE_TYPE,
532 "not a directory");
533 goto fail;
534 }
535 break;
536 }
537 else /* Found it! */
538 {
539 *ino = pupa_le_to_cpu32 (dirent.inode);
540 return 0;
541 }
542 }
543 }
544
545 /* Move to next directory entry. */
546 fpos += pupa_le_to_cpu16 (dirent.direntlen);
547 }
548
549 /* The complete directory was read and no matching file was
550 found. */
551 if (fpos >= pupa_le_to_cpu32 (inode->size))
552 {
553 pupa_error (PUPA_ERR_FILE_NOT_FOUND, "file not found");
554 goto fail;
555 }
556 }
557
558 fail:
559 return pupa_errno;
560 }
561
562 /* Open a file named NAME and initialize FILE. */
563 static pupa_err_t
564 pupa_ext2_open (struct pupa_file *file, const char *name)
565 {
566 struct pupa_ext2_data *data;
567 int ino;
568
569 #ifndef PUPA_UTIL
570 pupa_dl_ref (my_mod);
571 #endif
572
573 data = pupa_ext2_mount (file->device->disk);
574 if (!data)
575 goto fail;
576
577 pupa_ext2_find_file (data, name, &ino);
578 if (pupa_errno)
579 goto fail;
580
581 pupa_ext2_read_inode (data, ino, &data->inode);
582 if (pupa_errno)
583 goto fail;
584
585 if (!(pupa_le_to_cpu16 (data->inode.mode) & 0100000))
586 {
587 pupa_error (PUPA_ERR_BAD_FILE_TYPE, "not a regular file");
588 goto fail;
589 }
590
591 file->size = pupa_le_to_cpu32 (data->inode.size);
592 file->data = data;
593 file->offset = 0;
594
595 return 0;
596
597 fail:
598
599 pupa_free (data);
600
601 #ifndef PUPA_UTIL
602 pupa_dl_unref (my_mod);
603 #endif
604
605 return pupa_errno;
606 }
607
608 static pupa_err_t
609 pupa_ext2_close (pupa_file_t file)
610 {
611 pupa_free (file->data);
612
613 #ifndef PUPA_UTIL
614 pupa_dl_unref (my_mod);
615 #endif
616
617 return PUPA_ERR_NONE;
618 }
619
620 /* Read LEN bytes data from FILE into BUF. */
621 static pupa_ssize_t
622 pupa_ext2_read (pupa_file_t file, char *buf, pupa_ssize_t len)
623 {
624 struct pupa_ext2_data *data =
625 (struct pupa_ext2_data *) file->data;
626
627 return pupa_ext2_read_file (data, file->read_hook, file->offset, len, buf);
628 }
629
630
631 static pupa_err_t
632 pupa_ext2_dir (pupa_device_t device, const char *path,
633 int (*hook) (const char *filename, int dir))
634 {
635 struct pupa_ext2_data *data = 0;;
636
637 int ino;
638 unsigned int fpos = 0;
639
640 #ifndef PUPA_UTIL
641 pupa_dl_ref (my_mod);
642 #endif
643
644 data = pupa_ext2_mount (device->disk);
645 if (!data)
646 goto fail;
647
648 pupa_ext2_find_file (data, (char *) path, &ino);
649 if (pupa_errno)
650 goto fail;
651
652 pupa_ext2_read_inode (data, ino, &data->inode);
653 if (pupa_errno)
654 goto fail;
655
656 if (!(pupa_le_to_cpu16 (data->inode.mode) & 040000))
657 {
658 pupa_error (PUPA_ERR_BAD_FILE_TYPE, "not a directory");
659 goto fail;
660 }
661
662 /* Search the file. */
663 while (fpos < pupa_le_to_cpu32 (data->inode.size))
664 {
665 struct ext2_dirent dirent;
666
667 pupa_ext2_read_file (data, 0, fpos, sizeof (struct ext2_dirent),
668 (char *) &dirent);
669 if (pupa_errno)
670 goto fail;
671
672 if (dirent.namelen != 0)
673 {
674 char filename[dirent.namelen + 1];
675
676 pupa_ext2_read_file (data, 0, fpos + sizeof (struct ext2_dirent),
677 dirent.namelen, filename);
678 if (pupa_errno)
679 goto fail;
680
681 filename[dirent.namelen] = '\0';
682
683 hook (filename, dirent.filetype == FILETYPE_DIRECTORY);
684 }
685
686 fpos += pupa_le_to_cpu16 (dirent.direntlen);
687 }
688
689 fail:
690
691 pupa_free (data);
692
693 #ifndef PUPA_UTIL
694 pupa_dl_unref (my_mod);
695 #endif
696
697 return pupa_errno;
698 }
699
700 static pupa_err_t
701 pupa_ext2_label (pupa_device_t device, char **label)
702 {
703 struct pupa_ext2_data *data;
704 pupa_disk_t disk = device->disk;
705
706 #ifndef PUPA_UTIL
707 pupa_dl_ref (my_mod);
708 #endif
709
710 data = pupa_ext2_mount (disk);
711 if (data)
712 *label = pupa_strndup (data->sblock.volume_name, 14);
713 else
714 *label = 0;
715
716 #ifndef PUPA_UTIL
717 pupa_dl_unref (my_mod);
718 #endif
719
720 pupa_free (data);
721
722 return pupa_errno;
723 }
724
725 \f
726 static struct pupa_fs pupa_ext2_fs =
727 {
728 .name = "ext2",
729 .dir = pupa_ext2_dir,
730 .open = pupa_ext2_open,
731 .read = pupa_ext2_read,
732 .close = pupa_ext2_close,
733 .label = pupa_ext2_label,
734 .next = 0
735 };
736
737 #ifdef PUPA_UTIL
738 void
739 pupa_ext2_init (void)
740 {
741 pupa_fs_register (&pupa_ext2_fs);
742 }
743
744 void
745 pupa_ext2_fini (void)
746 {
747 pupa_fs_unregister (&pupa_ext2_fs);
748 }
749 #else /* ! PUPA_UTIL */
750 PUPA_MOD_INIT
751 {
752 pupa_fs_register (&pupa_ext2_fs);
753 my_mod = mod;
754 }
755
756 PUPA_MOD_FINI
757 {
758 pupa_fs_unregister (&pupa_ext2_fs);
759 }
760 #endif /* ! PUPA_UTIL */