xref: /netbsd-src/usr.sbin/installboot/ext2fs.c (revision 267197ec1eebfcb9810ea27a89625b6ddf68e3e7)
1 /*	$NetBSD: ext2fs.c,v 1.1 2008/02/02 17:01:03 tsutsui Exp $	*/
2 
3 /*
4  * Copyright (c) 1997 Manuel Bouyer.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. All advertising materials mentioning features or use of this software
15  *    must display the following acknowledgement:
16  *      This product includes software developed by Manuel Bouyer.
17  * 4. The name of the author may not be used to endorse or promote products
18  *    derived from this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
24  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 /*-
33  * Copyright (c) 2002 The NetBSD Foundation, Inc.
34  * All rights reserved.
35  *
36  * This code is derived from software contributed to The NetBSD Foundation
37  * by Matt Fredette.
38  *
39  * Redistribution and use in source and binary forms, with or without
40  * modification, are permitted provided that the following conditions
41  * are met:
42  * 1. Redistributions of source code must retain the above copyright
43  *    notice, this list of conditions and the following disclaimer.
44  * 2. Redistributions in binary form must reproduce the above copyright
45  *    notice, this list of conditions and the following disclaimer in the
46  *    documentation and/or other materials provided with the distribution.
47  * 3. All advertising materials mentioning features or use of this software
48  *    must display the following acknowledgement:
49  *	This product includes software developed by the NetBSD
50  *	Foundation, Inc. and its contributors.
51  * 4. Neither the name of The NetBSD Foundation nor the names of its
52  *    contributors may be used to endorse or promote products derived
53  *    from this software without specific prior written permission.
54  *
55  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
56  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
57  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
58  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
59  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
60  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
61  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
62  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
63  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
64  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
65  * POSSIBILITY OF SUCH DAMAGE.
66  */
67 
68 #if HAVE_NBTOOL_CONFIG_H
69 #include "nbtool_config.h"
70 #endif
71 
72 #include <sys/cdefs.h>
73 #if defined(__RCSID) && !defined(__lint)
74 __RCSID("$NetBSD: ext2fs.c,v 1.1 2008/02/02 17:01:03 tsutsui Exp $");
75 #endif	/* !__lint */
76 
77 #include <sys/param.h>
78 
79 #if !HAVE_NBTOOL_CONFIG_H
80 #include <sys/mount.h>
81 #endif
82 
83 #include <assert.h>
84 #include <err.h>
85 #include <errno.h>
86 #include <fcntl.h>
87 #include <stdarg.h>
88 #include <stdio.h>
89 #include <stdlib.h>
90 #include <string.h>
91 #include <unistd.h>
92 
93 #include "installboot.h"
94 
95 #include <ufs/ext2fs/ext2fs_dinode.h>
96 #include <ufs/ext2fs/ext2fs_dir.h>
97 #include <ufs/ext2fs/ext2fs.h>
98 
99 static int	ext2fs_read_disk_block(ib_params *, uint64_t, int, uint8_t []);
100 static int	ext2fs_read_sblock(ib_params *, struct m_ext2fs *fs);
101 static int	ext2fs_read_gdblock(ib_params *, struct m_ext2fs *fs);
102 static int	ext2fs_find_disk_blocks(ib_params *, ino_t,
103 		    int (*)(ib_params *, void *, uint64_t, uint32_t), void *);
104 static int	ext2fs_findstage2_ino(ib_params *, void *, uint64_t, uint32_t);
105 static int	ext2fs_findstage2_blocks(ib_params *, void *, uint64_t,
106 		    uint32_t);
107 
108 
109 /* This reads a disk block from the file system. */
110 /* XXX: should be shared with ffs.c? */
111 static int
112 ext2fs_read_disk_block(ib_params *params, uint64_t blkno, int size,
113     uint8_t blk[])
114 {
115 	int rv;
116 
117 	assert(params != NULL);
118 	assert(params->filesystem != NULL);
119 	assert(params->fsfd != -1);
120 	assert(size > 0);
121 	assert(blk != NULL);
122 
123 	rv = pread(params->fsfd, blk, size, blkno * DEV_BSIZE);
124 	if (rv == -1) {
125 		warn("Reading block %llu in `%s'",
126 		    (unsigned long long)blkno, params->filesystem);
127 		return 0;
128 	} else if (rv != size) {
129 		warnx("Reading block %llu in `%s': short read",
130 		    (unsigned long long)blkno, params->filesystem);
131 		return 0;
132 	}
133 
134 	return 1;
135 }
136 
137 static int
138 ext2fs_read_sblock(ib_params *params, struct m_ext2fs *fs)
139 {
140 	uint8_t sbbuf[SBSIZE];
141 
142 	if (ext2fs_read_disk_block(params, SBOFF / DEV_BSIZE, SBSIZE,
143 	    sbbuf) == 0)
144 
145 	e2fs_sbload((void *)sbbuf, &fs->e2fs);
146 
147 	if (fs->e2fs.e2fs_magic != E2FS_MAGIC)
148 		return 0;
149 
150 	if (fs->e2fs.e2fs_rev > E2FS_REV1 ||
151 	    (fs->e2fs.e2fs_rev == E2FS_REV1 &&
152 	     (fs->e2fs.e2fs_first_ino != EXT2_FIRSTINO ||
153 	      fs->e2fs.e2fs_inode_size != EXT2_DINODE_SIZE ||
154 	      (fs->e2fs.e2fs_features_incompat & ~EXT2F_INCOMPAT_SUPP) != 0)))
155 		return 0;
156 
157 	fs->e2fs_ncg =
158 	    howmany(fs->e2fs.e2fs_bcount - fs->e2fs.e2fs_first_dblock,
159 	    fs->e2fs.e2fs_bpg);
160 	/* XXX assume hw bsize = 512 */
161 	fs->e2fs_fsbtodb = fs->e2fs.e2fs_log_bsize + 1;
162 	fs->e2fs_bsize = MINBSIZE << fs->e2fs.e2fs_log_bsize;
163 	fs->e2fs_bshift = LOG_MINBSIZE + fs->e2fs.e2fs_log_bsize;
164 	fs->e2fs_qbmask = fs->e2fs_bsize - 1;
165 	fs->e2fs_bmask = ~fs->e2fs_qbmask;
166 	fs->e2fs_ngdb =
167 	    howmany(fs->e2fs_ncg, fs->e2fs_bsize / sizeof(struct ext2_gd));
168 	fs->e2fs_ipb = fs->e2fs_bsize / EXT2_DINODE_SIZE;
169 	fs->e2fs_itpg = fs->e2fs.e2fs_ipg / fs->e2fs_ipb;
170 
171 	return 1;
172 }
173 
174 static int
175 ext2fs_read_gdblock(ib_params *params, struct m_ext2fs *fs)
176 {
177 	uint8_t gdbuf[MAXBSIZE];
178 	uint32_t gdpb;
179 	int i;
180 
181 	gdpb = fs->e2fs_bsize / sizeof(struct ext2_gd);
182 
183 	for (i = 0; i < fs->e2fs_ngdb; i++) {
184 		if (ext2fs_read_disk_block(params, fsbtodb(fs,
185 		    fs->e2fs.e2fs_first_dblock + 1 /* superblock */ + i),
186 		    SBSIZE, gdbuf) == 0)
187 			return 0;
188 
189 		e2fs_cgload((struct ext2_gd *)gdbuf, &fs->e2fs_gd[gdpb * i],
190 		    (i == (fs->e2fs_ngdb - 1)) ?
191 		    (fs->e2fs_ncg - gdpb * i) * sizeof(struct ext2_gd):
192 		    fs->e2fs_bsize);
193 	}
194 
195 	return 1;
196 }
197 
198 /*
199  * This iterates over the data blocks belonging to an inode,
200  * making a callback each iteration with the disk block number
201  * and the size.
202  */
203 static int
204 ext2fs_find_disk_blocks(ib_params *params, ino_t ino,
205 	int (*callback)(ib_params *, void *, uint64_t, uint32_t),
206 	void *state)
207 {
208 	uint8_t sbbuf[sizeof(struct m_ext2fs)];
209 	struct m_ext2fs *fs;
210 	uint8_t inodebuf[MAXBSIZE];
211 	struct ext2fs_dinode inode_store, *inode;
212 	int level_i;
213 	int32_t blk, lblk, nblk;
214 	int rv;
215 #define LEVELS 4
216 	struct {
217 		uint32_t *blknums;
218 		unsigned long blkcount;
219 		uint8_t diskbuf[MAXBSIZE];
220 	} level[LEVELS];
221 
222 	assert(params != NULL);
223 	assert(params->fstype != NULL);
224 	assert(callback != NULL);
225 	assert(state != NULL);
226 
227 	/* Read the superblock. */
228 	fs = (void *)sbbuf;
229 	if (ext2fs_read_sblock(params, fs) == 0)
230 		return 0;
231 
232 	fs->e2fs_gd = malloc(sizeof(struct ext2_gd) * fs->e2fs_ncg);
233 	if (fs->e2fs_gd == NULL) {
234 		warnx("Can't allocate memofy for group descriptors");
235 		return 0;
236 	}
237 
238 	if (ext2fs_read_gdblock(params, fs) == 0) {
239 		warnx("Can't read group descriptors");
240 		return 0;
241 	}
242 
243 	if (fs->e2fs_ipb <= 0) {
244 		warnx("Bad ipb %d in superblock in `%s'",
245 		    fs->e2fs_ipb, params->filesystem);
246 		return 0;
247 	}
248 
249 	/* Read the inode. */
250 	if (ext2fs_read_disk_block(params,
251 		fsbtodb(fs, ino_to_fsba(fs, ino)) + params->fstype->offset,
252 		fs->e2fs_bsize, inodebuf))
253 		return 0;
254 	inode = (void *)inodebuf;
255 	e2fs_iload(&inode[ino_to_fsbo(fs, ino)], &inode_store);
256 	inode = &inode_store;
257 
258 	/* Get the block count and initialize for our block walk. */
259 	nblk = howmany(inode->e2di_size, fs->e2fs_bsize);
260 	lblk = 0;
261 	level_i = 0;
262 	level[0].blknums = &inode->e2di_blocks[0];
263 	level[0].blkcount = NDADDR;
264 	level[1].blknums = &inode->e2di_blocks[NDADDR + 0];
265 	level[1].blkcount = 1;
266 	level[2].blknums = &inode->e2di_blocks[NDADDR + 1];
267 	level[2].blkcount = 1;
268 	level[3].blknums = &inode->e2di_blocks[NDADDR + 2];
269 	level[3].blkcount = 1;
270 
271 	/* Walk the data blocks. */
272 	while (nblk > 0) {
273 
274 		/*
275 		 * If there are no more blocks at this indirection
276 		 * level, move up one indirection level and loop.
277 		 */
278 		if (level[level_i].blkcount == 0) {
279 			if (++level_i == LEVELS)
280 				break;
281 			continue;
282 		}
283 
284 		/* Get the next block at this level. */
285 		blk = fs2h32(*(level[level_i].blknums++));
286 		level[level_i].blkcount--;
287 
288 #if 0
289 		fprintf(stderr, "ino %lu blk %lu level %d\n", ino, blk,
290 		    level_i);
291 #endif
292 
293 		/*
294 		 * If we're not at the direct level, descend one
295 		 * level, read in that level's new block list,
296 		 * and loop.
297 		 */
298 		if (level_i > 0) {
299 			level_i--;
300 			if (blk == 0)
301 				memset(level[level_i].diskbuf, 0, MAXBSIZE);
302 			else if (ext2fs_read_disk_block(params,
303 				fsbtodb(fs, blk) + params->fstype->offset,
304 				fs->e2fs_bsize, level[level_i].diskbuf) == 0)
305 				return 0;
306 			/* XXX ondisk32 */
307 			level[level_i].blknums =
308 			    (uint32_t *)level[level_i].diskbuf;
309 			level[level_i].blkcount = NINDIR(fs);
310 			continue;
311 		}
312 
313 		/* blk is the next direct level block. */
314 #if 0
315 		fprintf(stderr, "ino %lu db %lu blksize %lu\n", ino,
316 		    fsbtodb(fs, blk), sblksize(fs, inode->di_size, lblk));
317 #endif
318 		rv = (*callback)(params, state,
319 		    fsbtodb(fs, blk) + params->fstype->offset, fs->e2fs_bsize);
320 		lblk++;
321 		nblk--;
322 		if (rv != 1)
323 			return rv;
324 	}
325 
326 	if (nblk != 0) {
327 		warnx("Inode %llu in `%s' ran out of blocks?",
328 		    (unsigned long long)ino, params->filesystem);
329 		return 0;
330 	}
331 
332 	return 1;
333 }
334 
335 /*
336  * This callback reads a block of the root directory,
337  * searches for an entry for the secondary bootstrap,
338  * and saves the inode number if one is found.
339  */
340 static int
341 ext2fs_findstage2_ino(ib_params *params, void *_ino,
342 	uint64_t blk, uint32_t blksize)
343 {
344 	uint8_t dirbuf[MAXBSIZE];
345 	struct ext2fs_direct *de, *ede;
346 	uint32_t ino;
347 
348 	assert(params != NULL);
349 	assert(params->fstype != NULL);
350 	assert(params->stage2 != NULL);
351 	assert(_ino != NULL);
352 
353 	/* Skip directory holes. */
354 	if (blk == 0)
355 		return 1;
356 
357 	/* Read the directory block. */
358 	if (ext2fs_read_disk_block(params, blk, blksize, dirbuf) == 0)
359 		return 0;
360 
361 	/* Loop over the directory entries. */
362 	de = (struct ext2fs_direct *)&dirbuf[0];
363 	ede = (struct ext2fs_direct *)&dirbuf[blksize];
364 	while (de < ede) {
365 		ino = fs2h32(de->e2d_ino);
366 		if (ino != 0 && strcmp(de->e2d_name, params->stage2) == 0) {
367 			*((uint32_t *)_ino) = ino;
368 			return (2);
369 		}
370 		if (fs2h16(de->e2d_reclen) == 0)
371 			break;
372 		de = (struct ext2fs_direct *)((char *)de +
373 		    fs2h16(de->e2d_reclen));
374 	}
375 
376 	return 1;
377 }
378 
379 struct findblks_state {
380 	uint32_t	maxblk;
381 	uint32_t	nblk;
382 	ib_block	*blocks;
383 };
384 
385 /* This callback records the blocks of the secondary bootstrap. */
386 static int
387 ext2fs_findstage2_blocks(ib_params *params, void *_state,
388 	uint64_t blk, uint32_t blksize)
389 {
390 	struct findblks_state *state = _state;
391 
392 	assert(params != NULL);
393 	assert(params->stage2 != NULL);
394 	assert(_state != NULL);
395 
396 	if (state->nblk == state->maxblk) {
397 		warnx("Secondary bootstrap `%s' has too many blocks (max %d)",
398 		    params->stage2, state->maxblk);
399 		return (0);
400 	}
401 	state->blocks[state->nblk].block = blk;
402 	state->blocks[state->nblk].blocksize = blksize;
403 	state->nblk++;
404 	return 1;
405 }
406 
407 /*
408  *	publicly visible functions
409  */
410 
411 int
412 ext2fs_match(ib_params *params)
413 {
414 	uint8_t sbbuf[sizeof(struct m_ext2fs)];
415 	struct m_ext2fs *fs;
416 
417 	assert(params != NULL);
418 	assert(params->fstype != NULL);
419 
420 	/* Read the superblock. */
421 	fs = (void *)sbbuf;
422 	if (ext2fs_read_sblock(params, fs) == 0)
423 		return 0;
424 
425 	params->fstype->needswap = 0;
426 	params->fstype->blocksize = fs->e2fs_bsize;
427 	params->fstype->offset = 0;
428 
429 	return 1;
430 }
431 
432 int
433 ext2fs_findstage2(ib_params *params, uint32_t *maxblk, ib_block *blocks)
434 {
435 	int rv;
436 	uint32_t ino;
437 	struct findblks_state state;
438 
439 	assert(params != NULL);
440 	assert(params->stage2 != NULL);
441 	assert(maxblk != NULL);
442 	assert(blocks != NULL);
443 
444 	if (params->flags & IB_STAGE2START)
445 		return hardcode_stage2(params, maxblk, blocks);
446 
447 	/* The secondary bootstrap must be clearly in /. */
448 	if (params->stage2[0] == '/')
449 		params->stage2++;
450 	if (strchr(params->stage2, '/') != NULL) {
451 		warnx("The secondary bootstrap `%s' must be in /",
452 		    params->stage2);
453 		return 0;
454 	}
455 
456 	/* Get the inode number of the secondary bootstrap. */
457 	rv = ext2fs_find_disk_blocks(params, EXT2_ROOTINO,
458 	    ext2fs_findstage2_ino, &ino);
459 	if (rv != 2) {
460 		warnx("Could not find secondary bootstrap `%s' in `%s'",
461 		    params->stage2, params->filesystem);
462 		return 0;
463 	}
464 
465 	/* Record the disk blocks of the secondary bootstrap. */
466 	state.maxblk = *maxblk;
467 	state.nblk = 0;
468 	state.blocks = blocks;
469 		rv = ext2fs_find_disk_blocks(params, ino,
470 		    ext2fs_findstage2_blocks, &state);
471 	if (rv == 0)
472 		return 0;
473 
474 	*maxblk = state.nblk;
475 	return 1;
476 }
477