xref: /dragonfly/sbin/dump/traverse.c (revision b40e316c)
1 /*-
2  * Copyright (c) 1980, 1988, 1991, 1993
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *	This product includes software developed by the University of
16  *	California, Berkeley and its contributors.
17  * 4. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  *
33  * @(#)traverse.c	8.7 (Berkeley) 6/15/95
34  * $FreeBSD: src/sbin/dump/traverse.c,v 1.10.2.6 2003/04/14 20:10:35 johan Exp $
35  * $DragonFly: src/sbin/dump/traverse.c,v 1.8 2004/12/27 22:36:37 liamfoy Exp $
36  */
37 
38 #include <sys/param.h>
39 #include <sys/stat.h>
40 #ifdef sunos
41 #include <sys/vnode.h>
42 
43 #include <ufs/fs.h>
44 #include <ufs/fsdir.h>
45 #include <ufs/inode.h>
46 #else
47 #include <vfs/ufs/dir.h>
48 #include <vfs/ufs/dinode.h>
49 #include <vfs/ufs/fs.h>
50 #endif
51 
52 #include <protocols/dumprestore.h>
53 
54 #include <ctype.h>
55 #include <stdio.h>
56 #ifdef __STDC__
57 #include <errno.h>
58 #include <string.h>
59 #include <unistd.h>
60 #endif
61 
62 #include "dump.h"
63 
64 #define	HASDUMPEDFILE	0x1
65 #define	HASSUBDIRS	0x2
66 
67 #ifdef	FS_44INODEFMT
68 typedef	quad_t fsizeT;
69 #else
70 typedef	long fsizeT;
71 #endif
72 
73 static	int dirindir(ino_t ino, daddr_t blkno, int level, long *size,
74     long *tapesize, int nodump);
75 static	void dmpindir(ino_t ino, daddr_t blk, int level, fsizeT *size);
76 static	int searchdir(ino_t ino, daddr_t blkno, long size, long filesize,
77     long *tapesize, int nodump);
78 
79 /*
80  * This is an estimation of the number of TP_BSIZE blocks in the file.
81  * It estimates the number of blocks in files with holes by assuming
82  * that all of the blocks accounted for by di_blocks are data blocks
83  * (when some of the blocks are usually used for indirect pointers);
84  * hence the estimate may be high.
85  */
86 long
87 blockest(struct dinode *dp)
88 {
89 	long blkest, sizeest;
90 
91 	/*
92 	 * dp->di_size is the size of the file in bytes.
93 	 * dp->di_blocks stores the number of sectors actually in the file.
94 	 * If there are more sectors than the size would indicate, this just
95 	 *	means that there are indirect blocks in the file or unused
96 	 *	sectors in the last file block; we can safely ignore these
97 	 *	(blkest = sizeest below).
98 	 * If the file is bigger than the number of sectors would indicate,
99 	 *	then the file has holes in it.	In this case we must use the
100 	 *	block count to estimate the number of data blocks used, but
101 	 *	we use the actual size for estimating the number of indirect
102 	 *	dump blocks (sizeest vs. blkest in the indirect block
103 	 *	calculation).
104 	 */
105 	blkest = howmany(dbtob(dp->di_blocks), TP_BSIZE);
106 	sizeest = howmany(dp->di_size, TP_BSIZE);
107 	if (blkest > sizeest)
108 		blkest = sizeest;
109 	if (dp->di_size > sblock->fs_bsize * NDADDR) {
110 		/* calculate the number of indirect blocks on the dump tape */
111 		blkest +=
112 			howmany(sizeest - NDADDR * sblock->fs_bsize / TP_BSIZE,
113 			TP_NINDIR);
114 	}
115 	return (blkest + 1);
116 }
117 
118 /* Auxiliary macro to pick up files changed since previous dump. */
119 #define	CHANGEDSINCE(dp, t) \
120 	((dp)->di_mtime >= (t) || (dp)->di_ctime >= (t))
121 
122 /* The WANTTODUMP macro decides whether a file should be dumped. */
123 #ifdef UF_NODUMP
124 #define	WANTTODUMP(dp) \
125 	(CHANGEDSINCE(dp, spcl.c_ddate) && \
126 	 (nonodump || ((dp)->di_flags & UF_NODUMP) != UF_NODUMP))
127 #else
128 #define	WANTTODUMP(dp) CHANGEDSINCE(dp, spcl.c_ddate)
129 #endif
130 
131 /*
132  * Dump pass 1.
133  *
134  * Walk the inode list for a filesystem to find all allocated inodes
135  * that have been modified since the previous dump time. Also, find all
136  * the directories in the filesystem.
137  */
138 int
139 mapfiles(ino_t maxino, long *tapesize)
140 {
141 	int mode;
142 	ino_t ino;
143 	struct dinode *dp;
144 	int anydirskipped = 0;
145 
146 	for (ino = ROOTINO; ino < maxino; ino++) {
147 		dp = getino(ino);
148 		if ((mode = (dp->di_mode & IFMT)) == 0)
149 			continue;
150 		/*
151 		 * Everything must go in usedinomap so that a check
152 		 * for "in dumpdirmap but not in usedinomap" to detect
153 		 * dirs with nodump set has a chance of succeeding
154 		 * (this is used in mapdirs()).
155 		 */
156 		SETINO(ino, usedinomap);
157 		if (mode == IFDIR)
158 			SETINO(ino, dumpdirmap);
159 		if (WANTTODUMP(dp)) {
160 			SETINO(ino, dumpinomap);
161 			if (mode != IFREG && mode != IFDIR && mode != IFLNK)
162 				*tapesize += 1;
163 			else
164 				*tapesize += blockest(dp);
165 			continue;
166 		}
167 		if (mode == IFDIR) {
168 			if (!nonodump && (dp->di_flags & UF_NODUMP))
169 				CLRINO(ino, usedinomap);
170 			anydirskipped = 1;
171 		}
172 	}
173 	/*
174 	 * Restore gets very upset if the root is not dumped,
175 	 * so ensure that it always is dumped.
176 	 */
177 	SETINO(ROOTINO, dumpinomap);
178 	return (anydirskipped);
179 }
180 
181 /*
182  * Dump pass 2.
183  *
184  * Scan each directory on the filesystem to see if it has any modified
185  * files in it. If it does, and has not already been added to the dump
186  * list (because it was itself modified), then add it. If a directory
187  * has not been modified itself, contains no modified files and has no
188  * subdirectories, then it can be deleted from the dump list and from
189  * the list of directories. By deleting it from the list of directories,
190  * its parent may now qualify for the same treatment on this or a later
191  * pass using this algorithm.
192  */
193 int
194 mapdirs(ino_t maxino, long *tapesize)
195 {
196 	struct	dinode *dp;
197 	int i, isdir, nodump;
198 	char *map;
199 	ino_t ino;
200 	struct dinode di;
201 	long filesize;
202 	int ret, change = 0;
203 
204 	isdir = 0;		/* XXX just to get gcc to shut up */
205 	for (map = dumpdirmap, ino = 1; ino < maxino; ino++) {
206 		if (((ino - 1) % NBBY) == 0)	/* map is offset by 1 */
207 			isdir = *map++;
208 		else
209 			isdir >>= 1;
210 		/*
211 		 * If a directory has been removed from usedinomap, it
212 		 * either has the nodump flag set, or has inherited
213 		 * it.  Although a directory can't be in dumpinomap if
214 		 * it isn't in usedinomap, we have to go through it to
215 		 * propagate the nodump flag.
216 		 */
217 		nodump = !nonodump && (TSTINO(ino, usedinomap) == 0);
218 		if ((isdir & 1) == 0 || (TSTINO(ino, dumpinomap) && !nodump))
219 			continue;
220 		dp = getino(ino);
221 		di = *dp;	/* inode buf may change in searchdir(). */
222 		filesize = di.di_size;
223 		for (ret = 0, i = 0; filesize > 0 && i < NDADDR; i++) {
224 			if (di.di_db[i] != 0)
225 				ret |= searchdir(ino, di.di_db[i],
226 					(long)dblksize(sblock, dp, i),
227 					filesize, tapesize, nodump);
228 			if (ret & HASDUMPEDFILE)
229 				filesize = 0;
230 			else
231 				filesize -= sblock->fs_bsize;
232 		}
233 		for (i = 0; filesize > 0 && i < NIADDR; i++) {
234 			if (di.di_ib[i] == 0)
235 				continue;
236 			ret |= dirindir(ino, di.di_ib[i], i, &filesize,
237 			    tapesize, nodump);
238 		}
239 		if (ret & HASDUMPEDFILE) {
240 			SETINO(ino, dumpinomap);
241 			*tapesize += blockest(dp);
242 			change = 1;
243 			continue;
244 		}
245 		if (nodump) {
246 			if (ret & HASSUBDIRS)
247 				change = 1;	/* subdirs inherit nodump */
248 			CLRINO(ino, dumpdirmap);
249 		} else if ((ret & HASSUBDIRS) == 0)
250 			if (!TSTINO(ino, dumpinomap)) {
251 				CLRINO(ino, dumpdirmap);
252 				change = 1;
253 			}
254 	}
255 	return (change);
256 }
257 
258 /*
259  * Read indirect blocks, and pass the data blocks to be searched
260  * as directories. Quit as soon as any entry is found that will
261  * require the directory to be dumped.
262  */
263 static int
264 dirindir(ino_t ino, daddr_t blkno, int ind_level, long *filesize,
265          long *tapesize, int nodump)
266 {
267 	int ret = 0;
268 	int i;
269 	daddr_t	idblk[MAXNINDIR];
270 
271 	bread(fsbtodb(sblock, blkno), (char *)idblk, (int)sblock->fs_bsize);
272 	if (ind_level <= 0) {
273 		for (i = 0; *filesize > 0 && i < NINDIR(sblock); i++) {
274 			blkno = idblk[i];
275 			if (blkno != 0)
276 				ret |= searchdir(ino, blkno, sblock->fs_bsize,
277 					*filesize, tapesize, nodump);
278 			if (ret & HASDUMPEDFILE)
279 				*filesize = 0;
280 			else
281 				*filesize -= sblock->fs_bsize;
282 		}
283 		return (ret);
284 	}
285 	ind_level--;
286 	for (i = 0; *filesize > 0 && i < NINDIR(sblock); i++) {
287 		blkno = idblk[i];
288 		if (blkno != 0)
289 			ret |= dirindir(ino, blkno, ind_level, filesize,
290 			    tapesize, nodump);
291 	}
292 	return (ret);
293 }
294 
295 /*
296  * Scan a disk block containing directory information looking to see if
297  * any of the entries are on the dump list and to see if the directory
298  * contains any subdirectories.
299  */
300 static int
301 searchdir(ino_t ino, daddr_t blkno, long size, long filesize,
302           long *tapesize, int nodump)
303 {
304 	struct direct *dp;
305 	struct dinode *ip;
306 	long loc, ret = 0;
307 	char dblk[MAXBSIZE];
308 
309 	bread(fsbtodb(sblock, blkno), dblk, (int)size);
310 	if (filesize < size)
311 		size = filesize;
312 	for (loc = 0; loc < size; ) {
313 		dp = (struct direct *)(dblk + loc);
314 		if (dp->d_reclen == 0) {
315 			msg("corrupted directory, inumber %d\n", ino);
316 			break;
317 		}
318 		loc += dp->d_reclen;
319 		if (dp->d_ino == 0)
320 			continue;
321 		if (dp->d_name[0] == '.') {
322 			if (dp->d_name[1] == '\0')
323 				continue;
324 			if (dp->d_name[1] == '.' && dp->d_name[2] == '\0')
325 				continue;
326 		}
327 		if (nodump) {
328 			ip = getino(dp->d_ino);
329 			if (TSTINO(dp->d_ino, dumpinomap)) {
330 				CLRINO(dp->d_ino, dumpinomap);
331 				CLRINO(dp->d_ino, usedinomap);
332 				*tapesize -= blockest(ip);
333 			}
334 			/* Add back to dumpdirmap to propagate nodump. */
335 			if ((ip->di_mode & IFMT) == IFDIR) {
336 				SETINO(dp->d_ino, dumpdirmap);
337 				ret |= HASSUBDIRS;
338 			}
339 		} else {
340 			if (TSTINO(dp->d_ino, dumpinomap)) {
341 				ret |= HASDUMPEDFILE;
342 				if (ret & HASSUBDIRS)
343 					break;
344 			}
345 			if (TSTINO(dp->d_ino, dumpdirmap)) {
346 				ret |= HASSUBDIRS;
347 				if (ret & HASDUMPEDFILE)
348 					break;
349 			}
350 		}
351 	}
352 	return (ret);
353 }
354 
355 /*
356  * Dump passes 3 and 4.
357  *
358  * Dump the contents of an inode to tape.
359  */
360 void
361 dumpino(struct dinode *dp, ino_t ino)
362 {
363 	int ind_level, cnt;
364 	fsizeT size;
365 	char buf[TP_BSIZE];
366 
367 	if (newtape) {
368 		newtape = 0;
369 		dumpmap(dumpinomap, TS_BITS, ino);
370 	}
371 	CLRINO(ino, dumpinomap);
372 	spcl.c_dinode = *dp;
373 	spcl.c_type = TS_INODE;
374 	spcl.c_count = 0;
375 	switch (dp->di_mode & S_IFMT) {
376 
377 	case 0:
378 		/*
379 		 * Freed inode.
380 		 */
381 		return;
382 
383 	case S_IFLNK:
384 		/*
385 		 * Check for short symbolic link.
386 		 */
387 #ifdef FS_44INODEFMT
388 		if (dp->di_size > 0 &&
389 		    dp->di_size < sblock->fs_maxsymlinklen) {
390 			spcl.c_addr[0] = 1;
391 			spcl.c_count = 1;
392 			writeheader(ino);
393 			memmove(buf, dp->di_shortlink, (u_long)dp->di_size);
394 			buf[dp->di_size] = '\0';
395 			writerec(buf, 0);
396 			return;
397 		}
398 #endif
399 		/* fall through */
400 
401 	case S_IFDIR:
402 	case S_IFREG:
403 		if (dp->di_size > 0)
404 			break;
405 		/* fall through */
406 
407 	case S_IFIFO:
408 	case S_IFSOCK:
409 	case S_IFCHR:
410 	case S_IFBLK:
411 		writeheader(ino);
412 		return;
413 
414 	default:
415 		msg("Warning: undefined file type 0%o\n", dp->di_mode & IFMT);
416 		return;
417 	}
418 	if (dp->di_size > NDADDR * sblock->fs_bsize)
419 		cnt = NDADDR * sblock->fs_frag;
420 	else
421 		cnt = howmany(dp->di_size, sblock->fs_fsize);
422 	blksout(&dp->di_db[0], cnt, ino);
423 	if ((size = dp->di_size - NDADDR * sblock->fs_bsize) <= 0)
424 		return;
425 	for (ind_level = 0; ind_level < NIADDR; ind_level++) {
426 		dmpindir(ino, dp->di_ib[ind_level], ind_level, &size);
427 		if (size <= 0)
428 			return;
429 	}
430 }
431 
432 /*
433  * Read indirect blocks, and pass the data blocks to be dumped.
434  */
435 static void
436 dmpindir(ino_t ino, daddr_t blk, int ind_level, fsizeT *size)
437 {
438 	int i, cnt;
439 	daddr_t idblk[MAXNINDIR];
440 
441 	if (blk != 0)
442 		bread(fsbtodb(sblock, blk), (char *)idblk, (int) sblock->fs_bsize);
443 	else
444 		memset(idblk, 0, (int)sblock->fs_bsize);
445 	if (ind_level <= 0) {
446 		if (*size < NINDIR(sblock) * sblock->fs_bsize)
447 			cnt = howmany(*size, sblock->fs_fsize);
448 		else
449 			cnt = NINDIR(sblock) * sblock->fs_frag;
450 		*size -= NINDIR(sblock) * sblock->fs_bsize;
451 		blksout(&idblk[0], cnt, ino);
452 		return;
453 	}
454 	ind_level--;
455 	for (i = 0; i < NINDIR(sblock); i++) {
456 		dmpindir(ino, idblk[i], ind_level, size);
457 		if (*size <= 0)
458 			return;
459 	}
460 }
461 
462 /*
463  * Collect up the data into tape record sized buffers and output them.
464  */
465 void
466 blksout(daddr_t *blkp, int frags, ino_t ino)
467 {
468 	daddr_t *bp;
469 	int i, j, count, blks, tbperdb;
470 
471 	blks = howmany(frags * sblock->fs_fsize, TP_BSIZE);
472 	tbperdb = sblock->fs_bsize >> tp_bshift;
473 	for (i = 0; i < blks; i += TP_NINDIR) {
474 		if (i + TP_NINDIR > blks)
475 			count = blks;
476 		else
477 			count = i + TP_NINDIR;
478 		for (j = i; j < count; j++)
479 			if (blkp[j / tbperdb] != 0)
480 				spcl.c_addr[j - i] = 1;
481 			else
482 				spcl.c_addr[j - i] = 0;
483 		spcl.c_count = count - i;
484 		writeheader(ino);
485 		bp = &blkp[i / tbperdb];
486 		for (j = i; j < count; j += tbperdb, bp++)
487 			if (*bp != 0) {
488 				if (j + tbperdb <= count)
489 					dumpblock(*bp, (int)sblock->fs_bsize);
490 				else
491 					dumpblock(*bp, (count - j) * TP_BSIZE);
492 			}
493 		spcl.c_type = TS_ADDR;
494 	}
495 }
496 
497 /*
498  * Dump a map to the tape.
499  */
500 void
501 dumpmap(char *map, int type, ino_t ino)
502 {
503 	int i;
504 	char *cp;
505 
506 	spcl.c_type = type;
507 	spcl.c_count = howmany(mapsize * sizeof(char), TP_BSIZE);
508 	writeheader(ino);
509 	for (i = 0, cp = map; i < spcl.c_count; i++, cp += TP_BSIZE)
510 		writerec(cp, 0);
511 }
512 
513 /*
514  * Write a header record to the dump tape.
515  */
516 void
517 writeheader(ino_t ino)
518 {
519 	int32_t sum, cnt, *lp;
520 
521 	spcl.c_inumber = ino;
522 	spcl.c_magic = NFS_MAGIC;
523 	spcl.c_checksum = 0;
524 	lp = (int32_t *)&spcl;
525 	sum = 0;
526 	cnt = sizeof(union u_spcl) / (4 * sizeof(int32_t));
527 	while (--cnt >= 0) {
528 		sum += *lp++;
529 		sum += *lp++;
530 		sum += *lp++;
531 		sum += *lp++;
532 	}
533 	spcl.c_checksum = CHECKSUM - sum;
534 	writerec((char *)&spcl, 1);
535 }
536 
537 struct dinode *
538 getino(ino_t inum)
539 {
540 	static daddr_t minino, maxino;
541 	static struct dinode inoblock[MAXINOPB];
542 
543 	curino = inum;
544 	if (inum >= minino && inum < maxino)
545 		return (&inoblock[inum - minino]);
546 	bread(fsbtodb(sblock, ino_to_fsba(sblock, inum)), (char *)inoblock,
547 	    (int)sblock->fs_bsize);
548 	minino = inum - (inum % INOPB(sblock));
549 	maxino = minino + INOPB(sblock);
550 	return (&inoblock[inum - minino]);
551 }
552 
553 /*
554  * Read a chunk of data from the disk.
555  * Try to recover from hard errors by reading in sector sized pieces.
556  * Error recovery is attempted at most BREADEMAX times before seeking
557  * consent from the operator to continue.
558  */
559 int	breaderrors = 0;
560 #define	BREADEMAX 32
561 
562 void
563 bread(daddr_t blkno, char *buf, int size)
564 {
565 	int cnt, i;
566 
567 loop:
568 	cnt = cread(diskfd, buf, size, ((off_t)blkno << dev_bshift));
569 	if (cnt == size)
570 		return;
571 	if (blkno + (size / dev_bsize) > fsbtodb(sblock, sblock->fs_size)) {
572 		/*
573 		 * Trying to read the final fragment.
574 		 *
575 		 * NB - dump only works in TP_BSIZE blocks, hence
576 		 * rounds `dev_bsize' fragments up to TP_BSIZE pieces.
577 		 * It should be smarter about not actually trying to
578 		 * read more than it can get, but for the time being
579 		 * we punt and scale back the read only when it gets
580 		 * us into trouble. (mkm 9/25/83)
581 		 */
582 		size -= dev_bsize;
583 		goto loop;
584 	}
585 	if (cnt == -1)
586 		msg("read error from %s: %s: [block %d]: count=%d\n",
587 			disk, strerror(errno), blkno, size);
588 	else
589 		msg("short read error from %s: [block %d]: count=%d, got=%d\n",
590 			disk, blkno, size, cnt);
591 	if (++breaderrors > BREADEMAX) {
592 		msg("More than %d block read errors from %s\n",
593 			BREADEMAX, disk);
594 		broadcast("DUMP IS AILING!\n");
595 		msg("This is an unrecoverable error.\n");
596 		if (!query("Do you want to attempt to continue?")){
597 			dumpabort(0);
598 			/*NOTREACHED*/
599 		} else
600 			breaderrors = 0;
601 	}
602 	/*
603 	 * Zero buffer, then try to read each sector of buffer separately,
604 	 * and bypass the cache.
605 	 */
606 	memset(buf, 0, size);
607 	for (i = 0; i < size; i += dev_bsize, buf += dev_bsize, blkno++) {
608 		if ((cnt = pread(diskfd, buf, (int)dev_bsize,
609 		    ((off_t)blkno << dev_bshift))) == dev_bsize)
610 			continue;
611 		if (cnt == -1) {
612 			msg("read error from %s: %s: [sector %d]: count=%ld\n",
613 				disk, strerror(errno), blkno, dev_bsize);
614 			continue;
615 		}
616 		msg("short read error from %s: [sector %d]: count=%ld, got=%d\n",
617 			disk, blkno, dev_bsize, cnt);
618 	}
619 }
620