1/*
2 * Copyright (c) 2000 Apple Computer, 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/*
24 * Copyright (C) 1995, 1996, 1997 Wolfgang Solfrank
25 * Copyright (c) 1995 Martin Husemann
26 *
27 * Redistribution and use in source and binary forms, with or without
28 * modification, are permitted provided that the following conditions
29 * are met:
30 * 1. Redistributions of source code must retain the above copyright
31 *    notice, this list of conditions and the following disclaimer.
32 * 2. Redistributions in binary form must reproduce the above copyright
33 *    notice, this list of conditions and the following disclaimer in the
34 *    documentation and/or other materials provided with the distribution.
35 * 3. All advertising materials mentioning features or use of this software
36 *    must display the following acknowledgement:
37 *	This product includes software developed by Martin Husemann
38 *	and Wolfgang Solfrank.
39 * 4. Neither the name of the University nor the names of its contributors
40 *    may be used to endorse or promote products derived from this software
41 *    without specific prior written permission.
42 *
43 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
44 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
45 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
46 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
47 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
48 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
49 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
50 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
51 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
52 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
53 */
54
55
56#include <sys/cdefs.h>
57#include <sys/stat.h>
58
59#include <stdlib.h>
60#include <string.h>
61#include <ctype.h>
62#include <stdio.h>
63#include <unistd.h>
64#include <fcntl.h>
65
66#include "ext.h"
67#include "fsutil.h"
68
69int checkfilesys(const char *fname)
70{
71	int dosfs = -1;
72	struct bootblock boot;
73	int finish_dosdirsection=0;
74	int mod = 0;
75	int ret = FSERROR;
76	int tryFatalAgain = 1;
77	int tryErrorAgain = 3;
78	int tryOthersAgain = 3;
79    char raw_path[64];
80
81    /*
82     * We accept device paths of the form "/dev/disk6s1" or "disk6s1"
83     * and convert them to the raw path "/dev/rdisk6s1".
84     *
85     * On iOS, we want to be able to run fsck_msdos as user "mobile"
86     * so that it can be spawned from userfsd, which also runs as "mobile".
87     * But since /dev/disk entries are owned by root, we can't open them
88     * directly.  userfsd has already used an SPI to open the disk device,
89     * so it will spawn fsck_msdos with that open file descriptor, and use
90     * a path of the form "/dev/fd/NUMBER".
91     *
92     * Unfortunately, iOS doesn't support /dev/fd/ (and if it did, sandboxing
93     * might not allow us to access it).  If we can search in /dev/fd, then
94     * we'll just try to open the path as-is.  Otherwise, we'll parse the file
95     * descriptor number from the path and use that descriptor directly
96     * (without going through open).
97     */
98	if (!strncmp(fname, "/dev/disk", 9))
99	{
100	    if (snprintf(raw_path, sizeof(raw_path), "/dev/r%s", &fname[5]) < sizeof(raw_path))
101            fname = raw_path;
102	    /* Else we just use the non-raw disk */
103	}
104	else if (!strncmp(fname, "disk", 4))
105	{
106	    if (snprintf(raw_path, sizeof(raw_path), "/dev/r%s", fname) < sizeof(raw_path))
107            fname = raw_path;
108	    /* Else the open below is likely to fail */
109	}
110	else if (!strncmp(fname, "/dev/fd/", 8))
111    {
112        /*
113         * If /dev/fd/ doesn't exist, or we can't access it,
114         * then parse the file descriptor ourselves.
115         */
116        if (access("/dev/fd", X_OK))
117        {
118            char *end_ptr;
119            dosfs = (int)strtol(&fname[8], &end_ptr, 10);
120            if (*end_ptr)
121            {
122                // TODO: is errx or err the right way to report the errors here?
123                pfatal("Invalid file descriptor path: %s", fname);
124                return 8;
125            }
126
127            struct stat info;
128            if (fstat(dosfs, &info))
129            {
130                perr("Cannot stat");
131                return 8;
132            }
133        }
134    }
135
136	rdonly = alwaysno || quick;
137	if (!preen)
138		printf("** %s", fname);
139
140    if (dosfs < 0)
141        dosfs = open(fname, rdonly ? O_RDONLY : O_RDWR | O_EXLOCK, 0);
142	if (dosfs < 0 && !rdonly) {
143		dosfs = open(fname, O_RDONLY, 0);
144		if (dosfs >= 0)
145			pwarn(" (NO WRITE)\n");
146		else if (!preen)
147			printf("\n");
148		rdonly = 1;
149	} else if (!preen)
150		printf("\n");
151
152	if (dosfs < 0) {
153		perr("Can't open");
154		return 8;
155	}
156
157	mod = readboot(dosfs, &boot);
158	if (mod & FSFATAL) {
159		close(dosfs);
160		return 8;
161	}
162
163	if (quick) {
164		/*
165		 * FAT12 volumes don't have a dirty bit.  If we were asked for
166		 * a quick check, then actually do a full scan without repairs.
167		 */
168		if (boot.ClustMask == CLUST12_MASK) {
169			/* Don't attempt to do any repairs */
170			rdonly = 1;
171			alwaysno = 1;
172			alwaysyes = 0;
173			quiet = 1;
174
175			/* Go verify the volume */
176			goto Again;
177		}
178		else if (isdirty(dosfs, &boot, boot.ValidFat >= 0 ? boot.ValidFat : 0)) {
179			pwarn("FILESYSTEM DIRTY; SKIPPING CHECKS\n");
180			ret = FSDIRTY;
181		}
182		else {
183			pwarn("FILESYSTEM CLEAN; SKIPPING CHECKS\n");
184			ret = 0;
185		}
186
187		close(dosfs);
188		return ret;
189	}
190
191Again:
192	boot.NumFiles = 0;	/* Reset file count in case we loop back here */
193	boot.NumBad = 0;
194
195	/*
196	 * [2771127] When there was no active FAT, this code used to
197	 * compare FAT #0 with all the other FATs.  That doubled the
198	 * already large memory usage, and doesn't seem very useful.
199	 * Microsoft's specification says the purpose of the alternate
200	 * FATs is in case a sector goes bad in the main FAT.  In fact,
201	 * a Windows 2000 system never even notices the FATs have
202	 * different values.  Besides, the filesystem itself only ever
203	 * uses FAT #0.
204	 */
205	if (!preen && !quiet) {
206		printf("** Phase 1 - Preparing FAT\n");
207        }
208
209	mod |= fat_init(dosfs, &boot);
210
211	if (mod & FSFATAL) {
212		close(dosfs);
213		return FSERROR;
214	}
215
216	if (!preen && !quiet)
217		printf("** Phase 2 - Checking Directories\n");
218
219	mod |= resetDosDirSection(&boot);
220	finish_dosdirsection = 1;
221	if (mod & FSFATAL)
222		goto out;
223	/* delay writing FATs */
224
225	mod |= handleDirTree(dosfs, &boot);
226	if (mod & FSFATAL)
227		goto out;
228
229	if (!preen && !quiet)
230		printf("** Phase 3 - Checking for Orphan Clusters\n");
231
232	/*
233	 * Should we skip this if (mod & FSERROR)?  It would be bad to free clusters
234	 * that are actually referenced by some directory entry, even if they are
235	 * beyond the logical file size (and the file size was not repaired).
236	 */
237	mod |= fat_free_unused();
238	if (mod & FSFATAL)
239		goto out;
240
241	if (quick) {
242		if (mod) {
243			printf("FILESYSTEM DIRTY\n");
244			ret = FSDIRTY;
245		}
246		else {
247			printf("FILESYSTEM CLEAN\n");
248			ret = 0;
249		}
250	}
251
252	if (boot.NumBad)
253		pwarn("%d files, %lld KiB free (%d clusters), %lld KiB bad (%d clusters)\n",
254			  boot.NumFiles,
255			  (long long) boot.NumFree * (long long) boot.ClusterSize / 1024LL, boot.NumFree,
256			  (long long) boot.NumBad * (long long) boot.ClusterSize / 1024LL, boot.NumBad);
257	else
258		pwarn("%d files, %lld KiB free (%d clusters)\n",
259			  boot.NumFiles,
260			  (long long) boot.NumFree * (long long) boot.ClusterSize / 1024LL, boot.NumFree);
261
262	/*
263	 * If we repaired everything, offer to mark the file system clean.
264	 */
265	if (mod && (mod & FSERROR) == 0) {
266		if (mod & FSDIRTY) {
267			if (ask(1, "MARK FILE SYSTEM CLEAN") == 0)
268				mod &= ~FSDIRTY;
269
270			if (mod & FSDIRTY) {
271				pwarn("MARKING FILE SYSTEM CLEAN\n");
272				mod |= fat_mark_clean();
273			} else {
274				pwarn("\n***** FILE SYSTEM IS LEFT MARKED AS DIRTY *****\n");
275			}
276		}
277	}
278
279	/*
280	 * We're done changing the FAT, so flush any changes.
281	 */
282	mod |= fat_flush();
283
284	/* Don't bother trying multiple times if we're not doing repairs */
285	if (mod && rdonly)
286		goto out;
287
288	if (((mod & FSFATAL) && (--tryFatalAgain > 0)) ||
289	    ((mod & FSERROR) && (--tryErrorAgain > 0)) ||
290	    ((mod & FSFIXFAT) && (--tryOthersAgain > 0))) {
291		mod = 0;
292		goto Again;
293	}
294	if ((mod & (FSFATAL | FSERROR)) == 0)
295		ret = 0;
296
297    out:
298	if (finish_dosdirsection)
299		finishDosDirSection();
300	fat_uninit();
301	freeUseMap();
302	close(dosfs);
303
304	if (mod & (FSFATMOD|FSDIRMOD))
305		pwarn("\n***** FILE SYSTEM WAS MODIFIED *****\n");
306
307	return ret;
308}
309