xref: /openbsd/sbin/fsck_msdos/boot.c (revision 09467b48)
1 /*	$OpenBSD: boot.c,v 1.24 2016/10/10 00:34:50 bluhm Exp $	*/
2 /*	$NetBSD: boot.c,v 1.5 1997/10/17 11:19:23 ws Exp $	*/
3 
4 /*
5  * Copyright (C) 1995, 1997 Wolfgang Solfrank
6  * Copyright (c) 1995 Martin Husemann
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
18  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20  * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
21  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #include <sys/param.h>	/* DEV_BSIZE powerof2 */
30 #include <sys/disklabel.h>
31 
32 #include <stdlib.h>
33 #include <string.h>
34 #include <ctype.h>
35 #include <stdio.h>
36 #include <unistd.h>
37 
38 #include "ext.h"
39 
40 int
41 readboot(int dosfs, struct bootblock *boot)
42 {
43 	u_char *block = NULL;
44 	u_char *fsinfo = NULL;
45 	u_char *backup = NULL;
46 	int ret = FSOK, secsize = lab.d_secsize, fsinfosz;
47 	off_t o;
48 	ssize_t n;
49 
50 	if (secsize < DOSBOOTBLOCKSIZE) {
51 		xperror("sector size < DOSBOOTBLOCKSIZE");
52 		goto fail;
53 	}
54 	if (DOSBOOTBLOCKSIZE != DEV_BSIZE) {
55 		xperror("DOSBOOTBLOCKSIZE != DEV_BSIZE");
56 		goto fail;
57 	}
58 
59 	block = malloc(secsize);
60 	if (block == NULL) {
61 		xperror("could not malloc boot block");
62 		goto fail;
63 	}
64 
65 	if ((o = lseek(dosfs, 0, SEEK_SET)) == -1) {
66 		xperror("could not seek boot block");
67 		goto fail;
68 	}
69 
70 	n = read(dosfs, block, secsize);
71 	if (n == -1 || n != secsize) {
72 		xperror("could not read boot block");
73 		goto fail;
74 	}
75 
76 	if (block[510] != 0x55 || block[511] != 0xaa) {
77 		pfatal("Invalid signature in boot block: %02x%02x\n",
78 		    block[511], block[510]);
79 	}
80 
81 	memset(boot, 0, sizeof *boot);
82 	boot->ValidFat = -1;
83 
84 	/* decode bios parameter block */
85 	boot->BytesPerSec = block[11] + (block[12] << 8);
86 	if (boot->BytesPerSec == 0 || boot->BytesPerSec != secsize) {
87 		pfatal("Invalid sector size: %u\n", boot->BytesPerSec);
88 		goto fail;
89 	}
90 	boot->SecPerClust = block[13];
91 	if (boot->SecPerClust == 0 || !powerof2(boot->SecPerClust)) {
92 		pfatal("Invalid cluster size: %u\n", boot->SecPerClust);
93 		goto fail;
94 	}
95 	boot->ResSectors = block[14] + (block[15] << 8);
96 	boot->FATs = block[16];
97 	if (boot->FATs == 0) {
98 		pfatal("Invalid number of FATs: %u\n", boot->FATs);
99 		goto fail;
100 	}
101 	boot->RootDirEnts = block[17] + (block[18] << 8);
102 	boot->Sectors = block[19] + (block[20] << 8);
103 	boot->Media = block[21];
104 	boot->FATsmall = block[22] + (block[23] << 8);
105 	boot->SecPerTrack = block[24] + (block[25] << 8);
106 	boot->Heads = block[26] + (block[27] << 8);
107 	boot->HiddenSecs = block[28] + (block[29] << 8) + (block[30] << 16) + (block[31] << 24);
108 	boot->HugeSectors = block[32] + (block[33] << 8) + (block[34] << 16) + (block[35] << 24);
109 
110 	boot->FATsecs = boot->FATsmall;
111 
112 	if (!boot->RootDirEnts) {
113 		boot->flags |= FAT32;
114 		boot->FATsecs = block[36] + (block[37] << 8)
115 				+ (block[38] << 16) + (block[39] << 24);
116 		if (block[40] & 0x80)
117 			boot->ValidFat = block[40] & 0x0f;
118 
119 		/* check version number: */
120 		if (block[42] || block[43]) {
121 			/* Correct?				XXX */
122 			pfatal("Unknown filesystem version: %x.%x\n",
123 			       block[43], block[42]);
124 			goto fail;
125 		}
126 		boot->RootCl = block[44] + (block[45] << 8)
127 			       + (block[46] << 16) + (block[47] << 24);
128 		boot->FSInfo = block[48] + (block[49] << 8);
129 		boot->Backup = block[50] + (block[51] << 8);
130 
131 		o = lseek(dosfs, boot->FSInfo * secsize, SEEK_SET);
132 		if (o == -1 || o != boot->FSInfo * secsize) {
133 			xperror("could not seek fsinfo block");
134 			goto fail;
135 		}
136 
137 		if ((2 * DOSBOOTBLOCKSIZE) < secsize)
138 			fsinfosz = secsize;
139 		else
140 			fsinfosz = 2 * secsize;
141 		fsinfo = malloc(fsinfosz);
142 		if (fsinfo == NULL) {
143 			xperror("could not malloc fsinfo");
144 			goto fail;
145 		}
146 		n = read(dosfs, fsinfo, fsinfosz);
147 		if (n == -1 || n != fsinfosz) {
148 			xperror("could not read fsinfo block");
149 			goto fail;
150 		}
151 
152 		if (memcmp(fsinfo, "RRaA", 4)
153 		    || memcmp(fsinfo + 0x1e4, "rrAa", 4)
154 		    || fsinfo[0x1fc]
155 		    || fsinfo[0x1fd]
156 		    || fsinfo[0x1fe] != 0x55
157 		    || fsinfo[0x1ff] != 0xaa
158 		    || fsinfo[0x3fc]
159 		    || fsinfo[0x3fd]
160 		    || fsinfo[0x3fe] != 0x55
161 		    || fsinfo[0x3ff] != 0xaa) {
162 			pwarn("Invalid signature in fsinfo block\n");
163 			if (ask(0, "fix")) {
164 				memcpy(fsinfo, "RRaA", 4);
165 				memcpy(fsinfo + 0x1e4, "rrAa", 4);
166 				fsinfo[0x1fc] = fsinfo[0x1fd] = 0;
167 				fsinfo[0x1fe] = 0x55;
168 				fsinfo[0x1ff] = 0xaa;
169 				fsinfo[0x3fc] = fsinfo[0x3fd] = 0;
170 				fsinfo[0x3fe] = 0x55;
171 				fsinfo[0x3ff] = 0xaa;
172 
173 				o = lseek(dosfs, boot->FSInfo * secsize,
174 				    SEEK_SET);
175 				if (o == -1 || o != boot->FSInfo * secsize) {
176 					xperror("Unable to seek FSInfo");
177 					goto fail;
178 				}
179 				n = write(dosfs, fsinfo, fsinfosz);
180 				if (n == -1 || n != fsinfosz) {
181 					xperror("Unable to write FSInfo");
182 					goto fail;
183 				}
184 				ret = FSBOOTMOD;
185 			} else
186 				boot->FSInfo = 0;
187 		}
188 		if (boot->FSInfo) {
189 			boot->FSFree = fsinfo[0x1e8] + (fsinfo[0x1e9] << 8)
190 				       + (fsinfo[0x1ea] << 16)
191 				       + (fsinfo[0x1eb] << 24);
192 			boot->FSNext = fsinfo[0x1ec] + (fsinfo[0x1ed] << 8)
193 				       + (fsinfo[0x1ee] << 16)
194 				       + (fsinfo[0x1ef] << 24);
195 		}
196 
197 		o = lseek(dosfs, boot->Backup * secsize, SEEK_SET);
198 		if (o == -1 || o != boot->Backup * secsize) {
199 			xperror("could not seek backup bootblock");
200 			goto fail;
201 		}
202 		backup = malloc(2 * secsize); /* In case we check fsinfo. */
203 		if (backup == NULL) {
204 			xperror("could not malloc backup boot block");
205 			goto fail;
206 		}
207 		n = read(dosfs, backup, secsize);
208 		if (n == -1 || n != secsize) {
209 			xperror("could not read backup bootblock");
210 			goto fail;
211 		}
212 
213 		/*
214 		 * Check that the backup boot block matches the primary one.
215 		 * We don't check every byte, since some vendor utilities
216 		 * seem to overwrite the boot code when they feel like it,
217 		 * without changing the backup block.  Specifically, we check
218 		 * the two-byte signature at the end, the BIOS parameter
219 		 * block (which starts after the 3-byte JMP and the 8-byte
220 		 * OEM name/version) and the filesystem information that
221 		 * follows the BPB (bsBPB[53] and bsExt[26] for FAT32, so we
222 		 * check 79 bytes).
223 		 */
224 		if (backup[510] != 0x55 || backup[511] != 0xaa) {
225 			pfatal("Invalid signature in backup boot block: %02x%02x\n", backup[511], backup[510]);
226 		}
227 		if (memcmp(block + 11, backup + 11, 79)) {
228 			pfatal("backup doesn't compare to primary bootblock\n");
229 			goto fail;
230 		}
231 		/* Check backup FSInfo?					XXX */
232 	}
233 
234 	if (boot->FATsecs == 0) {
235 		pfatal("Invalid number of FAT sectors: %u\n", boot->FATsecs);
236 		goto fail;
237 	}
238 
239 	boot->ClusterOffset = (boot->RootDirEnts * 32 + secsize - 1)
240 	    / secsize
241 	    + boot->ResSectors
242 	    + boot->FATs * boot->FATsecs
243 	    - CLUST_FIRST * boot->SecPerClust;
244 
245 	if (boot->Sectors) {
246 		boot->HugeSectors = 0;
247 		boot->NumSectors = boot->Sectors;
248 	} else
249 		boot->NumSectors = boot->HugeSectors;
250 
251 	if (boot->ClusterOffset > boot->NumSectors) {
252 		pfatal("Cluster offset too large (%u clusters)\n",
253 		    boot->ClusterOffset);
254 		goto fail;
255 	}
256 	boot->NumClusters = (boot->NumSectors - boot->ClusterOffset) / boot->SecPerClust;
257 
258 	if (boot->flags&FAT32)
259 		boot->ClustMask = CLUST32_MASK;
260 	else if (boot->NumClusters < (CLUST_RSRVD&CLUST12_MASK))
261 		boot->ClustMask = CLUST12_MASK;
262 	else if (boot->NumClusters < (CLUST_RSRVD&CLUST16_MASK))
263 		boot->ClustMask = CLUST16_MASK;
264 	else {
265 		pfatal("Filesystem too big (%u clusters) for non-FAT32 partition\n",
266 		       boot->NumClusters);
267 		goto fail;
268 	}
269 
270 	switch (boot->ClustMask) {
271 	case CLUST32_MASK:
272 		boot->NumFatEntries = (boot->FATsecs * secsize) / 4;
273 		break;
274 	case CLUST16_MASK:
275 		boot->NumFatEntries = (boot->FATsecs * secsize) / 2;
276 		break;
277 	default:
278 		boot->NumFatEntries = (boot->FATsecs * secsize * 2) / 3;
279 		break;
280 	}
281 
282 	if (boot->NumFatEntries < boot->NumClusters) {
283 		pfatal("FAT size too small, %u entries won't fit into %u sectors\n",
284 		       boot->NumClusters, boot->FATsecs);
285 		goto fail;
286 	}
287 	boot->ClusterSize = boot->SecPerClust * secsize;
288 
289 	boot->NumFiles = 1;
290 	boot->NumFree = 0;
291 
292 	free(backup);
293 	free(block);
294 	free(fsinfo);
295 	return ret;
296 fail:
297 	free(backup);
298 	free(block);
299 	free(fsinfo);
300 	return FSFATAL;
301 }
302 
303 int
304 writefsinfo(int dosfs, struct bootblock *boot)
305 {
306 	u_char *fsinfo = NULL;
307 	int secsize = lab.d_secsize, fsinfosz;
308 	off_t o;
309 	ssize_t n;
310 
311 	if ((2 * DOSBOOTBLOCKSIZE) < secsize)
312 		fsinfosz = secsize;
313 	else
314 		fsinfosz = 2 * secsize;
315 
316 	fsinfo = malloc(fsinfosz);
317 	if (fsinfo == NULL) {
318 		xperror("could not malloc fsinfo block");
319 		goto fail;
320 	}
321 
322 	o = lseek(dosfs, boot->FSInfo * secsize, SEEK_SET);
323 	if (o == -1 || o != boot->FSInfo * secsize) {
324 		xperror("could not seek fsinfo block");
325 		goto fail;
326 	}
327 
328 	n = read(dosfs, fsinfo, fsinfosz);
329 	if (n == -1 || n != fsinfosz) {
330 		xperror("could not read fsinfo block");
331 		goto fail;
332 	}
333 
334 	fsinfo[0x1e8] = (u_char)boot->FSFree;
335 	fsinfo[0x1e9] = (u_char)(boot->FSFree >> 8);
336 	fsinfo[0x1ea] = (u_char)(boot->FSFree >> 16);
337 	fsinfo[0x1eb] = (u_char)(boot->FSFree >> 24);
338 	fsinfo[0x1ec] = (u_char)boot->FSNext;
339 	fsinfo[0x1ed] = (u_char)(boot->FSNext >> 8);
340 	fsinfo[0x1ee] = (u_char)(boot->FSNext >> 16);
341 	fsinfo[0x1ef] = (u_char)(boot->FSNext >> 24);
342 
343 	o = lseek(dosfs, o, SEEK_SET);
344 	if (o == -1 || o != boot->FSInfo * boot->BytesPerSec) {
345 		xperror("Unable to seek FSInfo");
346 		goto fail;
347 	}
348 	n = write(dosfs, fsinfo, fsinfosz);
349 	if (n == -1 || n != fsinfosz) {
350 		xperror("Unable to write FSInfo");
351 		goto fail;
352 	}
353 
354 	free(fsinfo);
355 
356 	/*
357 	 * Technically, we should return FSBOOTMOD here.
358 	 *
359 	 * However, since Win95 OSR2 (the first M$ OS that has
360 	 * support for FAT32) doesn't maintain the FSINFO block
361 	 * correctly, it has to be fixed pretty often.
362 	 *
363 	 * Therefore, we handle the FSINFO block only informally,
364 	 * fixing it if necessary, but otherwise ignoring the
365 	 * fact that it was incorrect.
366 	 */
367 	return 0;
368 fail:
369 	free(fsinfo);
370 	return FSFATAL;
371 }
372