xref: /netbsd-src/usr.sbin/installboot/ffs.c (revision eb961d0e02b7a46a9acfa877b02df48df6637278)
1 /*	$NetBSD: ffs.c,v 1.18 2006/02/18 12:39:38 dsl Exp $	*/
2 
3 /*-
4  * Copyright (c) 2002 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Matt Fredette.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. All advertising materials mentioning features or use of this software
19  *    must display the following acknowledgement:
20  *	This product includes software developed by the NetBSD
21  *	Foundation, Inc. and its contributors.
22  * 4. Neither the name of The NetBSD Foundation nor the names of its
23  *    contributors may be used to endorse or promote products derived
24  *    from this software without specific prior written permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
27  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
30  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36  * POSSIBILITY OF SUCH DAMAGE.
37  */
38 
39 #if HAVE_NBTOOL_CONFIG_H
40 #include "nbtool_config.h"
41 #endif
42 
43 #include <sys/cdefs.h>
44 #if defined(__RCSID) && !defined(__lint)
45 __RCSID("$NetBSD: ffs.c,v 1.18 2006/02/18 12:39:38 dsl Exp $");
46 #endif	/* !__lint */
47 
48 #include <sys/param.h>
49 
50 #if !HAVE_NBTOOL_CONFIG_H
51 #include <sys/mount.h>
52 #endif
53 
54 #include <assert.h>
55 #include <err.h>
56 #include <errno.h>
57 #include <fcntl.h>
58 #include <stdarg.h>
59 #include <stdio.h>
60 #include <stdlib.h>
61 #include <string.h>
62 #include <unistd.h>
63 
64 #include "installboot.h"
65 
66 #undef DIRBLKSIZ
67 
68 #include <ufs/ufs/dinode.h>
69 #include <ufs/ufs/dir.h>
70 #include <ufs/ffs/fs.h>
71 #include <ufs/ffs/ffs_extern.h>
72 #ifndef NO_FFS_SWAP
73 #include <ufs/ufs/ufs_bswap.h>
74 #else
75 #define	ffs_sb_swap(fs_a, fs_b)
76 #define	ffs_dinode1_swap(inode_a, inode_b)
77 #define	ffs_dinode2_swap(inode_a, inode_b)
78 #endif
79 
80 static int	ffs_read_disk_block(ib_params *, uint64_t, int, char *);
81 static int	ffs_find_disk_blocks_ufs1(ib_params *, ino_t,
82 		    int (*)(ib_params *, void *, uint64_t, uint32_t), void *);
83 static int	ffs_find_disk_blocks_ufs2(ib_params *, ino_t,
84 		    int (*)(ib_params *, void *, uint64_t, uint32_t), void *);
85 static int	ffs_findstage2_ino(ib_params *, void *, uint64_t, uint32_t);
86 static int	ffs_findstage2_blocks(ib_params *, void *, uint64_t, uint32_t);
87 
88 static int is_ufs2;
89 
90 
91 /* This reads a disk block from the filesystem. */
92 static int
93 ffs_read_disk_block(ib_params *params, uint64_t blkno, int size, char *blk)
94 {
95 	int	rv;
96 
97 	assert(params != NULL);
98 	assert(blk != NULL);
99 	assert(params->filesystem != NULL);
100 	assert(params->fsfd != -1);
101 	assert(blkno >= 0);
102 	assert(size > 0);
103 	assert(blk != NULL);
104 
105 	rv = pread(params->fsfd, blk, size, blkno * DEV_BSIZE);
106 	if (rv == -1) {
107 		warn("Reading block %llu in `%s'",
108 		    (unsigned long long)blkno, params->filesystem);
109 		return (0);
110 	} else if (rv != size) {
111 		warnx("Reading block %llu in `%s': short read",
112 		    (unsigned long long)blkno, params->filesystem);
113 		return (0);
114 	}
115 
116 	return (1);
117 }
118 
119 /*
120  * This iterates over the data blocks belonging to an inode,
121  * making a callback each iteration with the disk block number
122  * and the size.
123  */
124 static int
125 ffs_find_disk_blocks_ufs1(ib_params *params, ino_t ino,
126 	int (*callback)(ib_params *, void *, uint64_t, uint32_t),
127 	void *state)
128 {
129 	char		sbbuf[SBLOCKSIZE];
130 	struct fs	*fs;
131 	char		inodebuf[MAXBSIZE];
132 	struct ufs1_dinode	*inode;
133 	int		level_i;
134 	int32_t	blk, lblk, nblk;
135 	int		rv;
136 #define LEVELS 4
137 	struct {
138 		int32_t		*blknums;
139 		unsigned long	blkcount;
140 		char		diskbuf[MAXBSIZE];
141 	} level[LEVELS];
142 
143 	assert(params != NULL);
144 	assert(params->fstype != NULL);
145 	assert(callback != NULL);
146 	assert(state != NULL);
147 
148 	/* Read the superblock. */
149 	if (!ffs_read_disk_block(params, params->fstype->sblockloc, SBLOCKSIZE,
150 	    sbbuf))
151 		return (0);
152 	fs = (struct fs *)sbbuf;
153 	if (params->fstype->needswap)
154 		ffs_sb_swap(fs, fs);
155 
156 	if (fs->fs_inopb <= 0) {
157 		warnx("Bad inopb %d in superblock in `%s'",
158 		    fs->fs_inopb, params->filesystem);
159 		return (0);
160 	}
161 
162 	/* Read the inode. */
163 	if (! ffs_read_disk_block(params, fsbtodb(fs, ino_to_fsba(fs, ino)),
164 		fs->fs_bsize, inodebuf))
165 		return (0);
166 	inode = (struct ufs1_dinode *)inodebuf;
167 	inode += ino_to_fsbo(fs, ino);
168 	if (params->fstype->needswap)
169 		ffs_dinode1_swap(inode, inode);
170 
171 	/* Get the block count and initialize for our block walk. */
172 	nblk = howmany(inode->di_size, fs->fs_bsize);
173 	lblk = 0;
174 	level_i = 0;
175 	level[0].blknums = &inode->di_db[0];
176 	level[0].blkcount = NDADDR;
177 	level[1].blknums = &inode->di_ib[0];
178 	level[1].blkcount = 1;
179 	level[2].blknums = &inode->di_ib[1];
180 	level[2].blkcount = 1;
181 	level[3].blknums = &inode->di_ib[2];
182 	level[3].blkcount = 1;
183 
184 	/* Walk the data blocks. */
185 	while (nblk > 0) {
186 
187 		/*
188 		 * If there are no more blocks at this indirection
189 		 * level, move up one indirection level and loop.
190 		 */
191 		if (level[level_i].blkcount == 0) {
192 			if (++level_i == LEVELS)
193 				break;
194 			continue;
195 		}
196 
197 		/* Get the next block at this level. */
198 		blk = *(level[level_i].blknums++);
199 		level[level_i].blkcount--;
200 		if (params->fstype->needswap)
201 			blk = bswap32(blk);
202 
203 #if 0
204 		fprintf(stderr, "ino %lu blk %lu level %d\n", ino, blk,
205 		    level_i);
206 #endif
207 
208 		/*
209 		 * If we're not at the direct level, descend one
210 		 * level, read in that level's new block list,
211 		 * and loop.
212 		 */
213 		if (level_i > 0) {
214 			level_i--;
215 			if (blk == 0)
216 				memset(level[level_i].diskbuf, 0, MAXBSIZE);
217 			else if (! ffs_read_disk_block(params,
218 				fsbtodb(fs, blk),
219 				fs->fs_bsize, level[level_i].diskbuf))
220 				return (0);
221 			/* XXX ondisk32 */
222 			level[level_i].blknums =
223 				(int32_t *)level[level_i].diskbuf;
224 			level[level_i].blkcount = NINDIR(fs);
225 			continue;
226 		}
227 
228 		/* blk is the next direct level block. */
229 #if 0
230 		fprintf(stderr, "ino %lu db %lu blksize %lu\n", ino,
231 		    fsbtodb(fs, blk), sblksize(fs, inode->di_size, lblk));
232 #endif
233 		rv = (*callback)(params, state,
234 		    fsbtodb(fs, blk), sblksize(fs, inode->di_size, lblk));
235 		lblk++;
236 		nblk--;
237 		if (rv != 1)
238 			return (rv);
239 	}
240 
241 	if (nblk != 0) {
242 		warnx("Inode %llu in `%s' ran out of blocks?",
243 		    (unsigned long long)ino, params->filesystem);
244 		return (0);
245 	}
246 
247 	return (1);
248 }
249 
250 /*
251  * This iterates over the data blocks belonging to an inode,
252  * making a callback each iteration with the disk block number
253  * and the size.
254  */
255 static int
256 ffs_find_disk_blocks_ufs2(ib_params *params, ino_t ino,
257 	int (*callback)(ib_params *, void *, uint64_t, uint32_t),
258 	void *state)
259 {
260 	char		sbbuf[SBLOCKSIZE];
261 	struct fs	*fs;
262 	char		inodebuf[MAXBSIZE];
263 	struct ufs2_dinode	*inode;
264 	int		level_i;
265 	int64_t	blk, lblk, nblk;
266 	int		rv;
267 #define LEVELS 4
268 	struct {
269 		int64_t		*blknums;
270 		unsigned long	blkcount;
271 		char		diskbuf[MAXBSIZE];
272 	} level[LEVELS];
273 
274 	assert(params != NULL);
275 	assert(params->fstype != NULL);
276 	assert(callback != NULL);
277 	assert(state != NULL);
278 
279 	/* Read the superblock. */
280 	if (!ffs_read_disk_block(params, params->fstype->sblockloc, SBLOCKSIZE,
281 	    sbbuf))
282 		return (0);
283 	fs = (struct fs *)sbbuf;
284 	if (params->fstype->needswap)
285 		ffs_sb_swap(fs, fs);
286 
287 	if (fs->fs_inopb <= 0) {
288 		warnx("Bad inopb %d in superblock in `%s'",
289 		    fs->fs_inopb, params->filesystem);
290 		return (0);
291 	}
292 
293 	/* Read the inode. */
294 	if (! ffs_read_disk_block(params, fsbtodb(fs, ino_to_fsba(fs, ino)),
295 		fs->fs_bsize, inodebuf))
296 		return (0);
297 	inode = (struct ufs2_dinode *)inodebuf;
298 	inode += ino_to_fsbo(fs, ino);
299 	if (params->fstype->needswap)
300 		ffs_dinode2_swap(inode, inode);
301 
302 	/* Get the block count and initialize for our block walk. */
303 	nblk = howmany(inode->di_size, fs->fs_bsize);
304 	lblk = 0;
305 	level_i = 0;
306 	level[0].blknums = &inode->di_db[0];
307 	level[0].blkcount = NDADDR;
308 	level[1].blknums = &inode->di_ib[0];
309 	level[1].blkcount = 1;
310 	level[2].blknums = &inode->di_ib[1];
311 	level[2].blkcount = 1;
312 	level[3].blknums = &inode->di_ib[2];
313 	level[3].blkcount = 1;
314 
315 	/* Walk the data blocks. */
316 	while (nblk > 0) {
317 
318 		/*
319 		 * If there are no more blocks at this indirection
320 		 * level, move up one indirection level and loop.
321 		 */
322 		if (level[level_i].blkcount == 0) {
323 			if (++level_i == LEVELS)
324 				break;
325 			continue;
326 		}
327 
328 		/* Get the next block at this level. */
329 		blk = *(level[level_i].blknums++);
330 		level[level_i].blkcount--;
331 		if (params->fstype->needswap)
332 			blk = bswap64(blk);
333 
334 #if 0
335 		fprintf(stderr, "ino %lu blk %llu level %d\n", ino,
336 		    (unsigned long long)blk, level_i);
337 #endif
338 
339 		/*
340 		 * If we're not at the direct level, descend one
341 		 * level, read in that level's new block list,
342 		 * and loop.
343 		 */
344 		if (level_i > 0) {
345 			level_i--;
346 			if (blk == 0)
347 				memset(level[level_i].diskbuf, 0, MAXBSIZE);
348 			else if (! ffs_read_disk_block(params,
349 				fsbtodb(fs, blk),
350 				fs->fs_bsize, level[level_i].diskbuf))
351 				return (0);
352 			level[level_i].blknums =
353 				(int64_t *)level[level_i].diskbuf;
354 			level[level_i].blkcount = NINDIR(fs);
355 			continue;
356 		}
357 
358 		/* blk is the next direct level block. */
359 #if 0
360 		fprintf(stderr, "ino %lu db %llu blksize %lu\n", ino,
361 		    fsbtodb(fs, blk), sblksize(fs, inode->di_size, lblk));
362 #endif
363 		rv = (*callback)(params, state,
364 		    fsbtodb(fs, blk), sblksize(fs, inode->di_size, lblk));
365 		lblk++;
366 		nblk--;
367 		if (rv != 1)
368 			return (rv);
369 	}
370 
371 	if (nblk != 0) {
372 		warnx("Inode %llu in `%s' ran out of blocks?",
373 		    (unsigned long long)ino, params->filesystem);
374 		return (0);
375 	}
376 
377 	return (1);
378 }
379 
380 /*
381  * This callback reads a block of the root directory,
382  * searches for an entry for the secondary bootstrap,
383  * and saves the inode number if one is found.
384  */
385 static int
386 ffs_findstage2_ino(ib_params *params, void *_ino,
387 	uint64_t blk, uint32_t blksize)
388 {
389 	char		dirbuf[MAXBSIZE];
390 	struct direct	*de, *ede;
391 	uint32_t	ino;
392 
393 	assert(params != NULL);
394 	assert(params->fstype != NULL);
395 	assert(params->stage2 != NULL);
396 	assert(_ino != NULL);
397 
398 	/* Skip directory holes. */
399 	if (blk == 0)
400 		return (1);
401 
402 	/* Read the directory block. */
403 	if (! ffs_read_disk_block(params, blk, blksize, dirbuf))
404 		return (0);
405 
406 	/* Loop over the directory entries. */
407 	de = (struct direct *)&dirbuf[0];
408 	ede = (struct direct *)&dirbuf[blksize];
409 	while (de < ede) {
410 		ino = de->d_fileno;
411 		if (params->fstype->needswap) {
412 			ino = bswap32(ino);
413 			de->d_reclen = bswap16(de->d_reclen);
414 		}
415 		if (ino != 0 && strcmp(de->d_name, params->stage2) == 0) {
416 			*((uint32_t *)_ino) = ino;
417 			return (2);
418 		}
419 		if (de->d_reclen == 0)
420 			break;
421 		de = (struct direct *)((char *)de + de->d_reclen);
422 	}
423 
424 	return (1);
425 }
426 
427 struct findblks_state {
428 	uint32_t	maxblk;
429 	uint32_t	nblk;
430 	ib_block	*blocks;
431 };
432 
433 /* This callback records the blocks of the secondary bootstrap. */
434 static int
435 ffs_findstage2_blocks(ib_params *params, void *_state,
436 	uint64_t blk, uint32_t blksize)
437 {
438 	struct findblks_state *state = _state;
439 
440 	assert(params != NULL);
441 	assert(params->stage2 != NULL);
442 	assert(_state != NULL);
443 
444 	if (state->nblk == state->maxblk) {
445 		warnx("Secondary bootstrap `%s' has too many blocks (max %d)",
446 		    params->stage2, state->maxblk);
447 		return (0);
448 	}
449 	state->blocks[state->nblk].block = blk;
450 	state->blocks[state->nblk].blocksize = blksize;
451 	state->nblk++;
452 	return (1);
453 }
454 
455 /*
456  *	publicly visible functions
457  */
458 
459 static off_t sblock_try[] = SBLOCKSEARCH;
460 
461 int
462 ffs_match(ib_params *params)
463 {
464 	char		sbbuf[SBLOCKSIZE];
465 	struct fs	*fs;
466 	int i;
467 	off_t loc;
468 
469 	assert(params != NULL);
470 	assert(params->fstype != NULL);
471 
472 	fs = (struct fs *)sbbuf;
473 	for (i = 0; sblock_try[i] != -1; i++) {
474 		loc = sblock_try[i] / DEV_BSIZE;
475 		if (!ffs_read_disk_block(params, loc, SBLOCKSIZE, sbbuf))
476 			continue;
477 		switch (fs->fs_magic) {
478 		case FS_UFS2_MAGIC:
479 			is_ufs2 = 1;
480 			/* FALLTHROUGH */
481 		case FS_UFS1_MAGIC:
482 			params->fstype->needswap = 0;
483 			params->fstype->blocksize = fs->fs_bsize;
484 			params->fstype->sblockloc = loc;
485 			break;
486 #ifndef FFS_NO_SWAP
487 		case FS_UFS2_MAGIC_SWAPPED:
488 			is_ufs2 = 1;
489 			/* FALLTHROUGH */
490 		case FS_UFS1_MAGIC_SWAPPED:
491 			params->fstype->needswap = 1;
492 			params->fstype->blocksize = bswap32(fs->fs_bsize);
493 			params->fstype->sblockloc = loc;
494 			break;
495 #endif
496 		default:
497 			continue;
498 		}
499 		if (!is_ufs2 && sblock_try[i] == SBLOCK_UFS2)
500 			continue;
501 		return 1;
502 	}
503 
504 	return (0);
505 }
506 
507 int
508 ffs_findstage2(ib_params *params, uint32_t *maxblk, ib_block *blocks)
509 {
510 	int			rv;
511 	uint32_t		ino;
512 	struct findblks_state	state;
513 
514 	assert(params != NULL);
515 	assert(params->stage2 != NULL);
516 	assert(maxblk != NULL);
517 	assert(blocks != NULL);
518 
519 	if (params->flags & IB_STAGE2START)
520 		return (hardcode_stage2(params, maxblk, blocks));
521 
522 	/* The secondary bootstrap must be clearly in /. */
523 	if (params->stage2[0] == '/')
524 		params->stage2++;
525 	if (strchr(params->stage2, '/') != NULL) {
526 		warnx("The secondary bootstrap `%s' must be in /",
527 		    params->stage2);
528 		return (0);
529 	}
530 
531 	/* Get the inode number of the secondary bootstrap. */
532 	if (is_ufs2)
533 		rv = ffs_find_disk_blocks_ufs2(params, ROOTINO,
534 		    ffs_findstage2_ino, &ino);
535 	else
536 		rv = ffs_find_disk_blocks_ufs1(params, ROOTINO,
537 		    ffs_findstage2_ino, &ino);
538 	if (rv != 2) {
539 		warnx("Could not find secondary bootstrap `%s' in `%s'",
540 		    params->stage2, params->filesystem);
541 		return (0);
542 	}
543 
544 	/* Record the disk blocks of the secondary bootstrap. */
545 	state.maxblk = *maxblk;
546 	state.nblk = 0;
547 	state.blocks = blocks;
548 	if (is_ufs2)
549 		rv = ffs_find_disk_blocks_ufs2(params, ino,
550 		    ffs_findstage2_blocks, &state);
551 	else
552 		rv = ffs_find_disk_blocks_ufs1(params, ino,
553 		    ffs_findstage2_blocks, &state);
554 	if (! rv) {
555 		return (0);
556 	}
557 
558 	*maxblk = state.nblk;
559 	return (1);
560 }
561