1/*
2 * Copyright (c) 1999-2000, 2002-2008 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23#include <sys/types.h>
24#include <sys/stat.h>
25#include <sys/param.h>
26#include <sys/ucred.h>
27#include <sys/mount.h>
28#include <sys/ioctl.h>
29#include <sys/disk.h>
30#include <sys/sysctl.h>
31#include <setjmp.h>
32
33#include <hfs/hfs_mount.h>
34
35#include <errno.h>
36#include <fcntl.h>
37#include <stdio.h>
38#include <string.h>
39#include <unistd.h>
40#include <stdlib.h>
41#include <ctype.h>
42#include <signal.h>
43
44#include <TargetConditionals.h>
45
46#include "fsck_hfs.h"
47#include "fsck_msgnums.h"
48#include "fsck_hfs_msgnums.h"
49
50#include "fsck_debug.h"
51#include "dfalib/CheckHFS.h"
52
53/*
54 * These definitions are duplicated from xnu's hfs_readwrite.c, and could live
55 * in a shared header file if desired. On the other hand, the freeze and thaw
56 * commands are not really supposed to be public.
57 */
58#ifndef    F_FREEZE_FS
59#define F_FREEZE_FS     53              /* "freeze" all fs operations */
60#define F_THAW_FS       54              /* "thaw" all fs operations */
61#endif  // F_FREEZE_FS
62
63/* Global Variables for front end */
64const char *cdevname;		/* name of device being checked */
65char	*progname;
66char	lflag;			/* live fsck */
67char	nflag;			/* assume a no response */
68char	yflag;			/* assume a yes response */
69char	preen;			/* just fix normal inconsistencies */
70char	force;			/* force fsck even if clean (preen only) */
71char	quick;			/* quick check returns clean, dirty, or failure */
72char	debug;			/* output debugging info */
73char	disable_journal;	/* If debug, and set, do not simulate journal replay */
74char	scanflag;		/* Scan entire disk for bad blocks */
75#if	!TARGET_OS_EMBEDDED
76char	embedded = 0;
77#else
78char	embedded = 1;
79#endif
80
81char	hotroot;		/* checking root device */
82char	hotmount;		/* checking read-only mounted device */
83char 	guiControl; 	/* this app should output info for gui control */
84char	xmlControl;	/* Output XML (plist) messages -- implies guiControl as well */
85char	rebuildBTree;  	/* Rebuild requested btree files */
86int	rebuildOptions;	/* Options to indicate which btree should be rebuilt */
87char	modeSetting;	/* set the mode when creating "lost+found" directory */
88char	errorOnExit = 0;	/* Exit on first error */
89int		upgrading;		/* upgrading format */
90int		lostAndFoundMode = 0; /* octal mode used when creating "lost+found" directory */
91uint64_t reqCacheSize;	/* Cache size requested by the caller (may be specified by the user via -c) */
92
93int	fsmodified;		/* 1 => write done to file system */
94int	fsreadfd;		/* file descriptor for reading file system */
95int	fswritefd;		/* file descriptor for writing file system */
96Cache_t fscache;
97
98/*
99 * Variables used to map physical block numbers to file paths
100 */
101enum { BLOCK_LIST_INCREMENT = 512 };
102int gBlkListEntries = 0;
103u_int64_t *gBlockList = NULL;
104int gFoundBlockEntries = 0;
105struct found_blocks *gFoundBlocksList = NULL;
106long gBlockSize = 512;
107static void ScanDisk(int);
108static int getblocklist(const char *filepath);
109
110
111static int checkfilesys __P((char * filesys));
112static int setup __P(( char *dev, int *canWritePtr ));
113static void usage __P((void));
114static void getWriteAccess __P(( char *dev, int *canWritePtr ));
115extern char *unrawname __P((char *name));
116
117int
118main(argc, argv)
119 int	argc;
120 char	*argv[];
121{
122	int ch;
123	int ret;
124	extern int optind;
125	extern char *optarg;
126	char * lastChar;
127
128	if ((progname = strrchr(*argv, '/')))
129		++progname;
130	else
131		progname = *argv;
132
133	while ((ch = getopt(argc, argv, "b:B:c:D:e:Edfglm:npqrR:SuyxJ")) != EOF) {
134		switch (ch) {
135		case 'b':
136			gBlockSize = atoi(optarg);
137			if ((gBlockSize < 512) || (gBlockSize & (gBlockSize-1))) {
138				(void) fprintf(stderr, "%s invalid block size %d\n",
139					progname, gBlockSize);
140				exit(2);
141			}
142			break;
143		case 'S':
144			scanflag = 1;
145			break;
146		case 'B':
147			getblocklist(optarg);
148			break;
149		case 'c':
150			/* Cache size to use in fsck_hfs */
151			reqCacheSize = strtoull(optarg, &lastChar, 0);
152			if (*lastChar) {
153				switch (tolower(*lastChar)) {
154					case 'g':
155						reqCacheSize *= 1024ULL;
156						/* fall through */
157					case 'm':
158						reqCacheSize *= 1024ULL;
159						/* fall through */
160					case 'k':
161						reqCacheSize *= 1024ULL;
162						break;
163					default:
164						reqCacheSize = 0;
165						break;
166				};
167			}
168			break;
169
170		case 'd':
171			debug++;
172			break;
173
174		case 'J':
175			disable_journal++;
176			break;
177		case 'D':
178			/* Input value should be in hex example: -D 0x5 */
179			cur_debug_level = strtoul(optarg, NULL, 0);
180			if (cur_debug_level == 0) {
181				(void) fplog (stderr, "%s: invalid debug development argument.  Assuming zero\n", progname);
182			}
183			break;
184
185		case 'e':
186			if (optarg) {
187				if (strcasecmp(optarg, "embedded") == 0)
188					embedded = 1;
189				else if (strcasecmp(optarg, "desktop") == 0)
190					embedded = 0;
191			}
192			break;
193
194		case 'E':
195			/* Exit on first error, after logging it */
196			errorOnExit = 1;
197			break;
198		case 'f':
199			force++;
200			break;
201
202		case 'g':
203			guiControl++;
204			break;
205
206		case 'x':
207			guiControl = 1;
208			xmlControl++;
209			break;
210
211		case 'l':
212			lflag++;
213			nflag++;
214			yflag = 0;
215			force++;
216			break;
217
218		case 'm':
219			modeSetting++;
220			lostAndFoundMode = strtol( optarg, NULL, 8 );
221			if ( lostAndFoundMode == 0 )
222			{
223				(void) fplog(stderr, "%s: invalid mode argument \n", progname);
224				usage();
225			}
226			break;
227
228		case 'n':
229			nflag++;
230			yflag = 0;
231			break;
232
233		case 'p':
234			preen++;
235			break;
236
237		case 'q':
238		 	quick++;
239			break;
240
241		case 'r':
242			// rebuild catalog btree
243			rebuildBTree++;
244			rebuildOptions |= REBUILD_CATALOG;
245			break;
246
247		case 'R':
248			if (optarg) {
249				char *cp = optarg;
250				while (*cp) {
251					switch (*cp) {
252						case 'a':
253							// rebuild attribute btree
254							rebuildBTree++;
255							rebuildOptions |= REBUILD_ATTRIBUTE;
256							break;
257
258						case 'c':
259							// rebuild catalog btree
260							rebuildBTree++;
261							rebuildOptions |= REBUILD_CATALOG;
262							break;
263
264						case 'e':
265							// rebuild extents overflow btree
266							rebuildBTree++;
267							rebuildOptions |= REBUILD_EXTENTS;
268							break;
269
270						default:
271							fprintf(stderr, "%s: unknown btree rebuild code `%c' (%#x)\n", progname, *cp, *cp);
272							exit(2);
273					}
274					cp++;
275				}
276				break;
277			}
278
279		case 'y':
280			yflag++;
281			nflag = 0;
282			break;
283
284		case 'u':
285		case '?':
286		default:
287			usage();
288		}
289	}
290
291	argc -= optind;
292	argv += optind;
293
294	if (debug == 0 && disable_journal != 0)
295		disable_journal = 0;
296
297	if (gBlkListEntries != 0 && gBlockSize == 0)
298		gBlockSize = 512;
299
300	if (guiControl)
301		debug = 0; /* debugging is for command line only */
302
303	if (signal(SIGINT, SIG_IGN) != SIG_IGN)
304		(void)signal(SIGINT, catch);
305
306	if (argc < 1) {
307		(void) fplog(stderr, "%s: missing special-device\n", progname);
308		usage();
309	}
310
311	ret = 0;
312	while (argc-- > 0)
313		ret |= checkfilesys(blockcheck(*argv++));
314
315	exit(ret);
316}
317
318int fs_fd=-1;  // fd to the root-dir of the fs we're checking (only w/lfag == 1)
319
320void
321cleanup_fs_fd(void)
322{
323    if (fs_fd >= 0) {
324	fcntl(fs_fd, F_THAW_FS, NULL);
325	close(fs_fd);
326	fs_fd = -1;
327    }
328}
329
330static char *
331mountpoint(const char *cdev)
332{
333	char *retval = NULL;
334	struct statfs *fsinfo;
335	char *unraw = NULL;
336	int result;
337	int i;
338
339	unraw = strdup(cdev);
340	unrawname(unraw);
341
342	if (unraw == NULL)
343		goto done;
344
345	result = getmntinfo(&fsinfo, MNT_NOWAIT);
346
347	for (i = 0; i < result; i++) {
348		if (strcmp(unraw, fsinfo[i].f_mntfromname) == 0) {
349			retval = strdup(fsinfo[i].f_mntonname);
350			break;
351		}
352	}
353
354done:
355	if (unraw)
356		free(unraw);
357
358	return retval;
359}
360
361static int
362checkfilesys(char * filesys)
363{
364	int flags;
365	int result = 0;
366	int chkLev, repLev, logLev;
367	int canWrite;
368	char *mntonname = NULL;
369	fsck_ctx_t context = NULL;
370	flags = 0;
371	cdevname = filesys;
372	canWrite = 0;
373	hotmount = hotroot;	// hotroot will be 1 or 0 by this time
374
375	//
376	// initialize the printing/logging without actually printing anything
377	// DO NOT DELETE THIS or else you can deadlock during a live fsck
378	// when something is printed and we try to create the log file.
379	//
380	plog("");
381
382	context = fsckCreate();
383
384	mntonname = mountpoint(cdevname);
385	if (hotroot) {
386		if (mntonname)
387			free(mntonname);
388		mntonname = strdup("/");
389	}
390
391	if (lflag) {
392		struct stat fs_stat;
393
394		/*
395		 * Ensure that, if we're doing a live verify, that we're not trying
396		 * to do input or output to the same device.  This would cause a deadlock.
397		 */
398
399		if (stat(cdevname, &fs_stat) != -1 &&
400			(((fs_stat.st_mode & S_IFMT) == S_IFCHR) ||
401			 ((fs_stat.st_mode & S_IFMT) == S_IFBLK))) {
402			struct stat io_stat;
403
404			if (fstat(fileno(stdin), &io_stat) != -1 &&
405				(fs_stat.st_rdev == io_stat.st_dev)) {
406					plog("ERROR: input redirected from target volume for live verify.\n");
407					return EEXIT;
408			}
409			if (fstat(fileno(stdout), &io_stat) != -1 &&
410				(fs_stat.st_rdev == io_stat.st_dev)) {
411					plog("ERROR:  output redirected to target volume for live verify.\n");
412					return EEXIT;
413			}
414			if (fstat(fileno(stderr), &io_stat) != -1 &&
415				(fs_stat.st_rdev == io_stat.st_dev)) {
416					plog("ERROR:  error output redirected to target volume for live verify.\n");
417					return EEXIT;
418			}
419
420		}
421	}
422
423	/*
424	 * If the device is mounted somewhere, then we need to make sure that it's
425	 * a read-only device, or that a live-verify has been requested.
426	 */
427	if (mntonname != NULL) {
428		struct statfs stfs_buf;
429
430		if (statfs(mntonname, &stfs_buf) == 0) {
431			if (lflag) {
432				// Need to try to freeze it
433				fs_fd = open(mntonname, O_RDONLY);
434				if (fs_fd < 0) {
435					plog("ERROR: could not open %s to freeze the volume.\n", mntonname);
436					free(mntonname);
437					return 0;
438				}
439
440				if (fcntl(fs_fd, F_FREEZE_FS, NULL) != 0) {
441					free(mntonname);
442					plog("ERROR: could not freeze volume (%s)\n", strerror(errno));
443					return 0;
444				}
445			} else if (stfs_buf.f_flags & MNT_RDONLY) {
446				hotmount = 1;
447			}
448		}
449	}
450
451	if (debug && preen)
452		pwarn("starting\n");
453
454	if (setup( filesys, &canWrite ) == 0) {
455		if (preen)
456			pfatal("CAN'T CHECK FILE SYSTEM.");
457		result = EEXIT;
458		goto ExitThisRoutine;
459	}
460
461	if (preen == 0) {
462		if (hotroot && !guiControl)
463		    plog("** Root file system\n");
464	}
465
466	/* start with defaults for dfa back-end */
467	chkLev = kAlwaysCheck;
468	repLev = kMajorRepairs;
469	logLev = kVerboseLog;
470
471	if (yflag)
472		repLev = kMajorRepairs;
473
474	if (quick) {
475		chkLev = kNeverCheck;
476		repLev = kNeverRepair;
477		logLev = kFatalLog;
478	} else if (force) {
479		chkLev = kForceCheck;
480	}
481	if (preen) {
482		repLev = kMinorRepairs;
483		chkLev = force ? kAlwaysCheck : kDirtyCheck;
484		logLev = kFatalLog;
485	}
486	if (debug)
487		logLev = kDebugLog;
488
489	if (nflag)
490		repLev = kNeverRepair;
491
492	if ( rebuildBTree ) {
493		chkLev = kPartialCheck;
494		repLev = kForceRepairs;  // this will force rebuild of B-Tree file
495	}
496
497	fsckSetVerbosity(context, logLev);
498	/* All of fsck_hfs' output should go thorugh logstring */
499	fsckSetOutput(context, NULL);
500	/* Setup writer that will output to standard out */
501	fsckSetWriter(context, &outstring);
502	/* Setup logger that will write to log file */
503	fsckSetLogger(context, &logstring);
504	if (guiControl) {
505		if (xmlControl)
506			fsckSetOutputStyle(context, fsckOutputXML);
507		else
508			fsckSetOutputStyle(context, fsckOutputGUI);
509	} else {
510		fsckSetOutputStyle(context, fsckOutputTraditional);
511	}
512
513	if (errorOnExit && nflag) {
514		chkLev = kMajorCheck;
515	}
516
517	/*
518	 * go check HFS volume...
519	 */
520
521	if (rebuildOptions && canWrite == 0) {
522		plog("BTree rebuild requested but writing disabled\n");
523		result = EEXIT;
524		goto ExitThisRoutine;
525	}
526
527	if (gBlockList != NULL && scanflag != 0) {
528		plog("Cannot scan for bad blocks and ask for listed blocks to file mapping\n");
529		result = EEXIT;
530		goto ExitThisRoutine;
531	}
532	if (scanflag != 0) {
533		plog("Scanning entire disk for bad blocks\n");
534		ScanDisk(fsreadfd);
535	}
536
537	result = CheckHFS( filesys, fsreadfd, fswritefd, chkLev, repLev, context,
538						lostAndFoundMode, canWrite, &fsmodified,
539						lflag, rebuildOptions );
540	if (debug)
541		plog("\tCheckHFS returned %d, fsmodified = %d\n", result, fsmodified);
542
543	if (!hotmount) {
544		ckfini(1);
545		if (quick) {
546			if (result == 0) {
547				pwarn("QUICKCHECK ONLY; FILESYSTEM CLEAN\n");
548				result = 0;
549				goto ExitThisRoutine;
550			} else if (result == R_Dirty) {
551				pwarn("QUICKCHECK ONLY; FILESYSTEM DIRTY\n");
552				result = DIRTYEXIT;
553				goto ExitThisRoutine;
554			} else if (result == R_BadSig) {
555				pwarn("QUICKCHECK ONLY; NO HFS SIGNATURE FOUND\n");
556				result = DIRTYEXIT;
557				goto ExitThisRoutine;
558			} else {
559				result = EEXIT;
560				goto ExitThisRoutine;
561			}
562		}
563	} else {
564		struct statfs stfs_buf;
565
566		/*
567		 * Check to see if root is mounted read-write.
568		 */
569		if (statfs(mntonname, &stfs_buf) == 0)
570			flags = stfs_buf.f_flags;
571		else
572			flags = 0;
573		ckfini(flags & MNT_RDONLY);
574	}
575
576	/* XXX free any allocated memory here */
577
578	if (hotmount && fsmodified) {
579		struct hfs_mount_args args;
580		/*
581		 * We modified the root.  Do a mount update on
582		 * it, unless it is read-write, so we can continue.
583		 */
584		if (!preen)
585			fsckPrint(context, fsckVolumeModified);
586		if (flags & MNT_RDONLY) {
587			bzero(&args, sizeof(args));
588			flags |= MNT_UPDATE | MNT_RELOAD;
589			if (debug)
590				fprintf(stderr, "doing update / reload mount for %s now\n", mntonname);
591			if (mount("hfs", mntonname, flags, &args) == 0) {
592				if (result != 0)
593					result = EEXIT;
594				goto ExitThisRoutine;
595			} else {
596				//if (debug)
597					fprintf(stderr, "update/reload mount for %s failed: %s\n", mntonname, strerror(errno));
598			}
599		}
600		if (!preen)
601			plog("\n***** REBOOT NOW *****\n");
602		sync();
603		result = FIXEDROOTEXIT;
604		goto ExitThisRoutine;
605	}
606
607	if (result != 0 && result != MAJOREXIT)
608		result = EEXIT;
609
610ExitThisRoutine:
611	if (lflag) {
612	    if (fs_fd >= 0) {
613		fcntl(fs_fd, F_THAW_FS, NULL);
614		close(fs_fd);
615		fs_fd = -1;
616	    }
617	}
618	if (mntonname)
619		free(mntonname);
620
621	if (context)
622		fsckDestroy(context);
623
624	return (result);
625}
626
627
628/*
629 * Setup for I/O to device
630 * Return 1 if successful, 0 if unsuccessful.
631 * canWrite - 1 if we can safely write to the raw device or 0 if not.
632 */
633static int
634setup( char *dev, int *canWritePtr )
635{
636	struct stat statb;
637	int devBlockSize;
638	uint32_t cacheBlockSize;
639	uint32_t cacheTotalBlocks;
640	int preTouchMem = 0;
641
642	fswritefd = -1;
643	*canWritePtr = 0;
644
645	if (stat(dev, &statb) < 0) {
646		plog("Can't stat %s: %s\n", dev, strerror(errno));
647		return (0);
648	}
649	if ((statb.st_mode & S_IFMT) != S_IFCHR) {
650		pfatal("%s is not a character device", dev);
651		if (reply("CONTINUE") == 0)
652			return (0);
653	}
654	/* Always attempt to replay the journal */
655	if (!nflag && !quick) {
656		// We know we have a character device by now.
657		if (strncmp(dev, "/dev/rdisk", 10) == 0) {
658			char block_device[MAXPATHLEN+1];
659			int rv;
660			snprintf(block_device, sizeof(block_device), "/dev/%s", dev + 6);
661			rv = journal_replay(block_device);
662			if (debug)
663				plog("journal_replay(%s) returned %d\n", block_device, rv);
664		}
665	}
666	/* attempt to get write access to the block device and if not check if volume is */
667	/* mounted read-only.  */
668	if (nflag == 0 && quick == 0) {
669		getWriteAccess( dev, canWritePtr );
670	}
671
672	if (nflag || quick || (fswritefd = open(dev, O_RDWR | (hotmount ? 0 : O_EXLOCK))) < 0) {
673		fswritefd = -1;
674		if (preen) {
675			pfatal("** %s (NO WRITE ACCESS)\n", dev);
676		}
677	}
678
679	if (preen == 0 && !guiControl) {
680		if (nflag || quick || fswritefd == -1) {
681			plog("** %s (NO WRITE)\n", dev);
682		} else {
683			plog("** %s\n", dev);
684		}
685	}
686
687	if (fswritefd == -1) {
688		if ((fsreadfd = open(dev, O_RDONLY)) < 0) {
689			plog("Can't open %s: %s\n", dev, strerror(errno));
690			return (0);
691		}
692	} else {
693		fsreadfd = dup(fswritefd);
694		if (fsreadfd < 0) {
695			plog("Can't dup fd for reading on %s: %s\n", dev, strerror(errno));
696			close(fswritefd);
697			return(0);
698		}
699	}
700
701
702	/* Get device block size to initialize cache */
703	if (ioctl(fsreadfd, DKIOCGETBLOCKSIZE, &devBlockSize) < 0) {
704		pfatal ("Can't get device block size\n");
705		return (0);
706	}
707
708	 /*
709	  * Calculate the cache block size and total blocks.
710	  *
711	  * If a quick check was requested, we'll only be checking to see if
712	  * the volume was cleanly unmounted or journalled, so we won't need
713	  * a lot of cache.  Since lots of quick checks can be run in parallel
714	  * when a new disk with several partitions comes on line, let's avoid
715	  * the memory usage when we don't need it.
716	  */
717	if (reqCacheSize == 0 && quick == 0) {
718		/*
719		 * Auto-pick the cache size.  The cache code will deal with minimum
720		 * maximum values, so we just need to find out the size of memory, and
721		 * how much of it we'll use.
722		 *
723		 * If we're looking at the root device, and it's not a live verify (lflag),
724		 * then we will use half of physical memory; otherwise, we'll use an eigth.
725		 *
726		 */
727		uint64_t memSize;
728		size_t dsize = sizeof(memSize);
729		int rv;
730
731		rv = sysctlbyname("hw.memsize", &memSize, &dsize, NULL, 0);
732		if (rv == -1) {
733			(void)fplog(stderr, "sysctlbyname failed, not auto-setting cache size\n");
734		} else {
735			int d = (hotroot && !lflag) ? 2 : 8;
736			int safeMode = 0;
737			dsize = sizeof(safeMode);
738			rv = sysctlbyname("kern.safeboot", &safeMode, &dsize, NULL, 0);
739			if (rv != -1 && safeMode != 0 && hotroot && !lflag) {
740#define kMaxSafeModeMem	((size_t)2 * 1024 * 1024 * 1024)	/* 2Gbytes, means cache will max out at 1gbyte */
741				if (debug) {
742					(void)fplog(stderr, "Safe mode and single-user, setting memsize to a maximum of 2gbytes\n");
743				}
744				memSize = (memSize < kMaxSafeModeMem) ? memSize : kMaxSafeModeMem;
745			}
746			reqCacheSize = memSize / d;
747		}
748	}
749
750	CalculateCacheSizes(reqCacheSize, &cacheBlockSize, &cacheTotalBlocks, debug);
751
752	preTouchMem = (hotroot != 0) && (lflag != 0);
753	/* Initialize the cache */
754	if (CacheInit (&fscache, fsreadfd, fswritefd, devBlockSize,
755			cacheBlockSize, cacheTotalBlocks, CacheHashSize, preTouchMem) != EOK) {
756		pfatal("Can't initialize disk cache\n");
757		return (0);
758	}
759
760	return (1);
761}
762
763
764// This routine will attempt to open the block device with write access for the target
765// volume in order to block others from mounting the volume with write access while we
766// check / repair it.  If we cannot get write access then we check to see if the volume
767// has been mounted read-only.  If it is read-only then we should be OK to write to
768// the raw device.  Note that this does not protect use from someone upgrading the mount
769// from read-only to read-write.
770
771static void getWriteAccess( char *dev, int *canWritePtr )
772{
773	int					i;
774	int					myMountsCount;
775	void *				myPtr;
776	char *				myCharPtr;
777	struct statfs *			myBufPtr;
778	void *				myNamePtr;
779	int				blockDevice_fd = -1;
780
781	myPtr = NULL;
782	myNamePtr = malloc( strlen(dev) + 2 );
783	if ( myNamePtr == NULL )
784		return;
785
786	strcpy( (char *)myNamePtr, dev );
787	if ( (myCharPtr = strrchr( (char *)myNamePtr, '/' )) != 0 ) {
788		if ( myCharPtr[1] == 'r' ) {
789			memmove(&myCharPtr[1], &myCharPtr[2], strlen(&myCharPtr[2]) + 1);
790			blockDevice_fd = open( (char *)myNamePtr, O_WRONLY | (hotmount ? 0 : O_EXLOCK) );
791		}
792	}
793
794	if ( blockDevice_fd > 0 ) {
795		// we got write access to the block device so we can safely write to raw device
796		*canWritePtr = 1;
797		goto ExitThisRoutine;
798	}
799
800	// get count of mounts then get the info for each
801	myMountsCount = getfsstat( NULL, 0, MNT_NOWAIT );
802	if ( myMountsCount < 0 )
803		goto ExitThisRoutine;
804
805	myPtr = (void *) malloc( sizeof(struct statfs) * myMountsCount );
806	if ( myPtr == NULL )
807		goto ExitThisRoutine;
808	myMountsCount = getfsstat( 	myPtr,
809								(int)(sizeof(struct statfs) * myMountsCount),
810								MNT_NOWAIT );
811	if ( myMountsCount < 0 )
812		goto ExitThisRoutine;
813
814	myBufPtr = (struct statfs *) myPtr;
815	for ( i = 0; i < myMountsCount; i++ )
816	{
817		if ( strcmp( myBufPtr->f_mntfromname, myNamePtr ) == 0 ) {
818			if ( myBufPtr->f_flags & MNT_RDONLY )
819				*canWritePtr = 1;
820			goto ExitThisRoutine;
821		}
822		myBufPtr++;
823	}
824	*canWritePtr = 1;  // single user will get us here, f_mntfromname is not /dev/diskXXXX
825
826ExitThisRoutine:
827	if ( myPtr != NULL )
828		free( myPtr );
829
830	if ( myNamePtr != NULL )
831		free( myNamePtr );
832
833	if (blockDevice_fd != -1) {
834		close(blockDevice_fd);
835	}
836
837	return;
838
839} /* getWriteAccess */
840
841
842static void
843usage()
844{
845	(void) fplog(stderr, "usage: %s [-b [size] B [path] c [size] e [mode] ESdfglx m [mode] npqruy] special-device\n", progname);
846	(void) fplog(stderr, "  b size = size of physical blocks (in bytes) for -B option\n");
847	(void) fplog(stderr, "  B path = file containing physical block numbers to map to paths\n");
848	(void) fplog(stderr, "  c size = cache size (ex. 512m, 1g)\n");
849	(void) fplog(stderr, "  e mode = emulate 'embedded' or 'desktop'\n");
850	(void) fplog(stderr, "  E = exit on first major error\n");
851	(void) fplog(stderr, "  d = output debugging info\n");
852	(void) fplog(stderr, "  f = force fsck even if clean (preen only) \n");
853	(void) fplog(stderr, "  g = GUI output mode\n");
854	(void) fplog(stderr, "  x = XML output mode\n");
855	(void) fplog(stderr, "  l = live fsck (lock down and test-only)\n");
856	(void) fplog(stderr, "  m arg = octal mode used when creating lost+found directory \n");
857	(void) fplog(stderr, "  n = assume a no response \n");
858	(void) fplog(stderr, "  p = just fix normal inconsistencies \n");
859	(void) fplog(stderr, "  q = quick check returns clean, dirty, or failure \n");
860	(void) fplog(stderr, "  r = rebuild catalog btree \n");
861	(void) fplog(stderr, "  S = Scan disk for bad blocks\n");
862	(void) fplog(stderr, "  u = usage \n");
863	(void) fplog(stderr, "  y = assume a yes response \n");
864
865	exit(1);
866}
867
868
869static void
870AddBlockToList(long long block)
871{
872
873	if ((gBlkListEntries % BLOCK_LIST_INCREMENT) == 0) {
874		void *tmp;
875
876//		gBlkListEntries += BLOCK_LIST_INCREMENT;
877		tmp = realloc(gBlockList, (gBlkListEntries + BLOCK_LIST_INCREMENT) * sizeof(u_int64_t));
878		if (tmp == NULL) {
879			pfatal("Can't allocate memory for block list (%llu entries).\n", gBlkListEntries);
880		}
881		gBlockList = (u_int64_t*)tmp;
882	}
883	gBlockList[gBlkListEntries++] = block;
884	return;
885}
886
887static int printStatus;
888static void
889siginfo(int signo)
890{
891	printStatus = 1;
892}
893
894static void
895ScanDisk(int fd)
896{
897	uint32_t devBlockSize = 512;
898	uint64_t devBlockTotal;
899	off_t diskSize;
900	uint8_t *buffer = NULL;
901	size_t bufSize = 1024 * 1024;
902	ssize_t nread;
903	off_t curPos = 0;
904	void (*oldhandler)(int);
905	uint32_t numErrors = 0;
906	uint32_t maxErrors = 40;	// Something more variable?
907
908	oldhandler = signal(SIGINFO, &siginfo);
909
910#define PRSTAT \
911	do { \
912		if (diskSize) { \
913			fprintf(stderr, "Scanning offset %lld of %lld (%d%%)\n", \
914				curPos, diskSize, (int)((curPos * 100) / diskSize)); \
915		} else { \
916			fprintf(stderr, "Scanning offset %lld\n", curPos); \
917		} \
918		printStatus = 0; \
919	} while (0)
920
921	if (ioctl(fd, DKIOCGETBLOCKSIZE, &devBlockSize) == -1) {
922		devBlockSize = 512;
923	}
924
925	if (ioctl(fd, DKIOCGETBLOCKCOUNT, &devBlockTotal) == -1) {
926		diskSize = 0;
927	} else
928		diskSize = devBlockTotal * devBlockSize;
929
930	while (buffer == NULL && bufSize >= devBlockSize) {
931		buffer = malloc(bufSize);
932		if (buffer == NULL) {
933			bufSize /= 2;
934		}
935	}
936	if (buffer == NULL) {
937		pfatal("Cannot allocate buffer for disk scan.\n");
938	}
939
940loop:
941
942	if (printStatus) {
943		PRSTAT;
944	}
945	while ((nread = pread(fd, buffer, bufSize, curPos)) == bufSize) {
946		curPos += bufSize;
947		if (printStatus) {
948			PRSTAT;
949		}
950	}
951
952	if (nread == 0) {
953		/* We're done with the disk */
954		goto done;
955	}
956	if (nread == -1) {
957		if (errno == EIO) {
958			/* Try reading devBlockSize blocks */
959			size_t total;
960			for (total = 0; total < bufSize; total += devBlockSize) {
961				nread = pread(fd, buffer, devBlockSize, curPos + total);
962				if (nread == -1) {
963					if (errno == EIO) {
964						if (debug)
965							fprintf(stderr, "Bad block at offset %lld\n", curPos + total);
966						AddBlockToList((curPos + total) / gBlockSize);
967						if (++numErrors > maxErrors) {
968							if (debug)
969								fprintf(stderr, "Got %u errors, maxing out so stopping scan\n", numErrors);
970							goto done;
971						}
972						continue;
973					} else {
974						pfatal("Got a non I/O error reading disk at offset %llu: %s\n",
975							curPos + total, strerror(errno));
976						// Hey, pfatal wasn't fatal!
977						// But that seems to work out for us for some reason.
978					}
979				}
980				if (nread == 0) {
981					/* End of disk, somehow. */
982					goto done;
983				}
984				if (nread != devBlockSize) {
985					pwarn("During disk scan, did not get block size (%zd) read, got %zd instead.  Skipping rest of this block.\n", (size_t)devBlockSize, nread);
986					continue;
987				}
988			}
989			curPos += total;
990			goto loop;
991		} else if (errno == EINTR) {
992			goto loop;
993		} else {
994			pfatal("Got a non I/O error reading disk at offset %llu:  %s\n", curPos, strerror(errno));
995			exit(EEXIT);
996		}
997	}
998	if (nread < bufSize) {
999		if ((nread % devBlockSize) == 0) {
1000			curPos += nread;
1001		} else {
1002			curPos = curPos + (((nread % devBlockSize) + 1) * devBlockSize);
1003		}
1004		goto loop;
1005	}
1006	goto loop;
1007done:
1008	if (buffer)
1009		free(buffer);
1010	signal(SIGINFO, oldhandler);
1011	return;
1012
1013}
1014
1015static int
1016getblocklist(const char *filepath)
1017{
1018	FILE * file;
1019	long long block;
1020	size_t blockListCount;	/* Number of elements allocated to gBlockList array */
1021
1022	blockListCount = BLOCK_LIST_INCREMENT;
1023	gBlockList = (u_int64_t *) malloc(blockListCount * sizeof(u_int64_t));
1024	if (gBlockList == NULL)
1025		pfatal("Can't allocate memory for block list.\n");
1026
1027//	printf("getblocklist: processing blocklist %s...\n", filepath);
1028
1029	if ((file = fopen(filepath, "r")) == NULL)
1030		pfatal("Can't open %s\n", filepath);
1031
1032	while (fscanf(file, "%lli", &block) > 0) {
1033		AddBlockToList(block);
1034	}
1035
1036	(void) fclose(file);
1037
1038	printf("%d blocks to match:\n", gBlkListEntries);
1039
1040	return (0);
1041}
1042