2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4 * Copyright (C) 1995, 1997 Wolfgang Solfrank
5 * Copyright (c) 1995 Martin Husemann
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 #include <sys/param.h>
40 readboot(int dosfs
, struct bootblock
*boot
)
42 u_char block
[DOSBOOTBLOCKSIZE
];
43 u_char fsinfo
[2 * DOSBOOTBLOCKSIZE
];
46 if ((size_t)read(dosfs
, block
, sizeof block
) != sizeof block
) {
47 perr("could not read boot block");
51 if (block
[510] != 0x55 || block
[511] != 0xaa) {
52 pfatal("Invalid signature in boot block: %02x%02x",
53 block
[511], block
[510]);
57 memset(boot
, 0, sizeof *boot
);
60 /* Decode BIOS Parameter Block */
62 /* Bytes per sector: can only be 512, 1024, 2048 and 4096. */
63 boot
->bpbBytesPerSec
= block
[11] + (block
[12] << 8);
64 if (boot
->bpbBytesPerSec
< DOSBOOTBLOCKSIZE_REAL
||
65 boot
->bpbBytesPerSec
> DOSBOOTBLOCKSIZE
||
66 !powerof2(boot
->bpbBytesPerSec
)) {
67 pfatal("Invalid sector size: %u", boot
->bpbBytesPerSec
);
71 /* Sectors per cluster: can only be: 1, 2, 4, 8, 16, 32, 64, 128. */
72 boot
->bpbSecPerClust
= block
[13];
73 if (boot
->bpbSecPerClust
== 0 || !powerof2(boot
->bpbSecPerClust
)) {
74 pfatal("Invalid cluster size: %u", boot
->bpbSecPerClust
);
78 /* Reserved sectors: must be non-zero */
79 boot
->bpbResSectors
= block
[14] + (block
[15] << 8);
80 if (boot
->bpbResSectors
< 1) {
81 pfatal("Invalid reserved sectors: %u",
87 boot
->bpbFATs
= block
[16];
88 if (boot
->bpbFATs
== 0) {
89 pfatal("Invalid number of FATs: %u", boot
->bpbFATs
);
93 /* Root directory entries for FAT12 and FAT16 */
94 boot
->bpbRootDirEnts
= block
[17] + (block
[18] << 8);
95 if (!boot
->bpbRootDirEnts
) {
96 /* bpbRootDirEnts = 0 suggests that we are FAT32 */
100 /* Total sectors (16 bits) */
101 boot
->bpbSectors
= block
[19] + (block
[20] << 8);
102 if (boot
->bpbSectors
!= 0 && (boot
->flags
& FAT32
)) {
103 pfatal("Invalid 16-bit total sector count on FAT32: %u",
108 /* Media type: ignored */
109 boot
->bpbMedia
= block
[21];
111 /* FAT12/FAT16: 16-bit count of sectors per FAT */
112 boot
->bpbFATsmall
= block
[22] + (block
[23] << 8);
113 if (boot
->bpbFATsmall
!= 0 && (boot
->flags
& FAT32
)) {
114 pfatal("Invalid 16-bit FAT sector count on FAT32: %u",
119 /* Legacy CHS geometry numbers: ignored */
120 boot
->SecPerTrack
= block
[24] + (block
[25] << 8);
121 boot
->bpbHeads
= block
[26] + (block
[27] << 8);
123 /* Hidden sectors: ignored */
124 boot
->bpbHiddenSecs
= block
[28] + (block
[29] << 8) +
125 (block
[30] << 16) + (block
[31] << 24);
127 /* Total sectors (32 bits) */
128 boot
->bpbHugeSectors
= block
[32] + (block
[33] << 8) +
129 (block
[34] << 16) + (block
[35] << 24);
130 if (boot
->bpbHugeSectors
== 0) {
131 if (boot
->flags
& FAT32
) {
132 pfatal("FAT32 with sector count of zero");
134 } else if (boot
->bpbSectors
== 0) {
135 pfatal("FAT with sector count of zero");
138 boot
->NumSectors
= boot
->bpbSectors
;
140 if (boot
->bpbSectors
!= 0) {
141 pfatal("Invalid FAT sector count");
144 boot
->NumSectors
= boot
->bpbHugeSectors
;
147 if (boot
->flags
& FAT32
) {
148 /* If the OEM Name field is EXFAT, it's not FAT32, so bail */
149 if (!memcmp(&block
[3], "EXFAT ", 8)) {
150 pfatal("exFAT filesystem is not supported.");
154 /* 32-bit count of sectors per FAT */
155 boot
->FATsecs
= block
[36] + (block
[37] << 8)
156 + (block
[38] << 16) + (block
[39] << 24);
158 if (block
[40] & 0x80)
159 boot
->ValidFat
= block
[40] & 0x0f;
161 /* FAT32 version, bail out if not 0.0 */
162 if (block
[42] || block
[43]) {
163 pfatal("Unknown file system version: %x.%x",
164 block
[43], block
[42]);
169 * Cluster number of the first cluster of root directory.
171 * Should be 2 but do not require it.
173 boot
->bpbRootClust
= block
[44] + (block
[45] << 8)
174 + (block
[46] << 16) + (block
[47] << 24);
176 /* Sector number of the FSInfo structure, usually 1 */
177 boot
->bpbFSInfo
= block
[48] + (block
[49] << 8);
179 /* Sector number of the backup boot block, ignored */
180 boot
->bpbBackup
= block
[50] + (block
[51] << 8);
182 /* Check basic parameters */
183 if (boot
->bpbFSInfo
== 0) {
185 * Either the BIOS Parameter Block has been corrupted,
186 * or this is not a FAT32 filesystem, most likely an
189 pfatal("Invalid FAT32 Extended BIOS Parameter Block");
193 /* Read in and verify the FSInfo block */
194 if (lseek(dosfs
, boot
->bpbFSInfo
* boot
->bpbBytesPerSec
,
195 SEEK_SET
) != boot
->bpbFSInfo
* boot
->bpbBytesPerSec
196 || read(dosfs
, fsinfo
, sizeof fsinfo
) != sizeof fsinfo
) {
197 perr("could not read fsinfo block");
200 if (memcmp(fsinfo
, "RRaA", 4)
201 || memcmp(fsinfo
+ 0x1e4, "rrAa", 4)
204 || fsinfo
[0x1fe] != 0x55
205 || fsinfo
[0x1ff] != 0xaa
208 || fsinfo
[0x3fe] != 0x55
209 || fsinfo
[0x3ff] != 0xaa) {
210 pwarn("Invalid signature in fsinfo block\n");
212 memcpy(fsinfo
, "RRaA", 4);
213 memcpy(fsinfo
+ 0x1e4, "rrAa", 4);
214 fsinfo
[0x1fc] = fsinfo
[0x1fd] = 0;
215 fsinfo
[0x1fe] = 0x55;
216 fsinfo
[0x1ff] = 0xaa;
217 fsinfo
[0x3fc] = fsinfo
[0x3fd] = 0;
218 fsinfo
[0x3fe] = 0x55;
219 fsinfo
[0x3ff] = 0xaa;
220 if (lseek(dosfs
, boot
->bpbFSInfo
*
221 boot
->bpbBytesPerSec
, SEEK_SET
)
222 != boot
->bpbFSInfo
* boot
->bpbBytesPerSec
223 || write(dosfs
, fsinfo
, sizeof fsinfo
)
225 perr("Unable to write bpbFSInfo");
232 /* We appear to have a valid FSInfo block, decode */
233 boot
->FSFree
= fsinfo
[0x1e8] + (fsinfo
[0x1e9] << 8)
234 + (fsinfo
[0x1ea] << 16)
235 + (fsinfo
[0x1eb] << 24);
236 boot
->FSNext
= fsinfo
[0x1ec] + (fsinfo
[0x1ed] << 8)
237 + (fsinfo
[0x1ee] << 16)
238 + (fsinfo
[0x1ef] << 24);
241 /* !FAT32: FAT12/FAT16 */
242 boot
->FATsecs
= boot
->bpbFATsmall
;
245 if (boot
->FATsecs
< 1 || boot
->FATsecs
> UINT32_MAX
/ boot
->bpbFATs
) {
246 pfatal("Invalid FATs(%u) with FATsecs(%zu)",
247 boot
->bpbFATs
, (size_t)boot
->FATsecs
);
251 boot
->FirstCluster
= (boot
->bpbRootDirEnts
* 32 +
252 boot
->bpbBytesPerSec
- 1) / boot
->bpbBytesPerSec
+
253 boot
->bpbResSectors
+ boot
->bpbFATs
* boot
->FATsecs
;
255 if (boot
->FirstCluster
+ boot
->bpbSecPerClust
> boot
->NumSectors
) {
256 pfatal("Cluster offset too large (%u clusters)\n",
262 * The number of clusters is derived from available data sectors,
263 * divided by sectors per cluster.
266 (boot
->NumSectors
- boot
->FirstCluster
) / boot
->bpbSecPerClust
;
268 if (boot
->flags
& FAT32
) {
269 if (boot
->NumClusters
> (CLUST_RSRVD
& CLUST32_MASK
)) {
270 pfatal("Filesystem too big (%u clusters) for FAT32 partition",
274 if (boot
->NumClusters
< (CLUST_RSRVD
& CLUST16_MASK
)) {
275 pfatal("Filesystem too small (%u clusters) for FAT32 partition",
279 boot
->ClustMask
= CLUST32_MASK
;
281 if (boot
->bpbRootClust
< CLUST_FIRST
||
282 boot
->bpbRootClust
>= boot
->NumClusters
) {
283 pfatal("Root directory starts with cluster out of range(%u)",
287 } else if (boot
->NumClusters
< (CLUST_RSRVD
&CLUST12_MASK
)) {
288 boot
->ClustMask
= CLUST12_MASK
;
289 } else if (boot
->NumClusters
< (CLUST_RSRVD
&CLUST16_MASK
)) {
290 boot
->ClustMask
= CLUST16_MASK
;
292 pfatal("Filesystem too big (%u clusters) for non-FAT32 partition",
297 switch (boot
->ClustMask
) {
299 boot
->NumFatEntries
= (boot
->FATsecs
* boot
->bpbBytesPerSec
) / 4;
302 boot
->NumFatEntries
= (boot
->FATsecs
* boot
->bpbBytesPerSec
) / 2;
305 boot
->NumFatEntries
= (boot
->FATsecs
* boot
->bpbBytesPerSec
* 2) / 3;
309 if (boot
->NumFatEntries
< boot
->NumClusters
) {
310 pfatal("FAT size too small, %u entries won't fit into %u sectors\n",
311 boot
->NumClusters
, boot
->FATsecs
);
316 * There are two reserved clusters. To avoid adding CLUST_FIRST every
317 * time we perform boundary checks, we increment the NumClusters by 2,
318 * which is CLUST_FIRST to denote the first out-of-range cluster number.
320 boot
->NumClusters
+= CLUST_FIRST
;
322 boot
->ClusterSize
= boot
->bpbBytesPerSec
* boot
->bpbSecPerClust
;
331 writefsinfo(int dosfs
, struct bootblock
*boot
)
333 u_char fsinfo
[2 * DOSBOOTBLOCKSIZE
];
335 if (lseek(dosfs
, boot
->bpbFSInfo
* boot
->bpbBytesPerSec
, SEEK_SET
)
336 != boot
->bpbFSInfo
* boot
->bpbBytesPerSec
337 || read(dosfs
, fsinfo
, sizeof fsinfo
) != sizeof fsinfo
) {
338 perr("could not read fsinfo block");
341 fsinfo
[0x1e8] = (u_char
)boot
->FSFree
;
342 fsinfo
[0x1e9] = (u_char
)(boot
->FSFree
>> 8);
343 fsinfo
[0x1ea] = (u_char
)(boot
->FSFree
>> 16);
344 fsinfo
[0x1eb] = (u_char
)(boot
->FSFree
>> 24);
345 fsinfo
[0x1ec] = (u_char
)boot
->FSNext
;
346 fsinfo
[0x1ed] = (u_char
)(boot
->FSNext
>> 8);
347 fsinfo
[0x1ee] = (u_char
)(boot
->FSNext
>> 16);
348 fsinfo
[0x1ef] = (u_char
)(boot
->FSNext
>> 24);
349 if (lseek(dosfs
, boot
->bpbFSInfo
* boot
->bpbBytesPerSec
, SEEK_SET
)
350 != boot
->bpbFSInfo
* boot
->bpbBytesPerSec
351 || write(dosfs
, fsinfo
, sizeof fsinfo
)
353 perr("Unable to write bpbFSInfo");
357 * Technically, we should return FSBOOTMOD here.
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.
363 * Therefore, we handle the FSINFO block only informally,
364 * fixing it if necessary, but otherwise ignoring the
365 * fact that it was incorrect.