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