/src/util-linux/libblkid/src/partitions/dos.c
Line | Count | Source |
1 | | /* |
2 | | * MS-DOS partition parsing code |
3 | | * |
4 | | * Copyright (C) 2009 Karel Zak <kzak@redhat.com> |
5 | | * |
6 | | * This file may be redistributed under the terms of the |
7 | | * GNU Lesser General Public License. |
8 | | * |
9 | | * Inspired by fdisk, partx, Linux kernel and libparted. |
10 | | */ |
11 | | #include <inttypes.h> |
12 | | #include <stdio.h> |
13 | | #include <string.h> |
14 | | #include <stdlib.h> |
15 | | #include <stdint.h> |
16 | | |
17 | | #include "partitions.h" |
18 | | #include "superblocks/superblocks.h" |
19 | | #include "aix.h" |
20 | | |
21 | | /* see superblocks/vfat.c */ |
22 | | extern int blkid_probe_is_vfat(blkid_probe pr); |
23 | | /* see superblocks/exfat.c */ |
24 | | extern int blkid_probe_is_exfat(blkid_probe pr); |
25 | | |
26 | | static const struct dos_subtypes { |
27 | | unsigned char type; |
28 | | const struct blkid_idinfo *id; |
29 | | } dos_nested[] = { |
30 | | { MBR_FREEBSD_PARTITION, &bsd_pt_idinfo }, |
31 | | { MBR_NETBSD_PARTITION, &bsd_pt_idinfo }, |
32 | | { MBR_OPENBSD_PARTITION, &bsd_pt_idinfo }, |
33 | | { MBR_UNIXWARE_PARTITION, &unixware_pt_idinfo }, |
34 | | { MBR_SOLARIS_X86_PARTITION, &solaris_x86_pt_idinfo }, |
35 | | { MBR_MINIX_PARTITION, &minix_pt_idinfo } |
36 | | }; |
37 | | |
38 | | static inline int is_extended(const struct dos_partition *p) |
39 | 0 | { |
40 | 0 | return (p->sys_ind == MBR_DOS_EXTENDED_PARTITION || |
41 | 0 | p->sys_ind == MBR_W95_EXTENDED_PARTITION || |
42 | 0 | p->sys_ind == MBR_LINUX_EXTENDED_PARTITION); |
43 | 0 | } |
44 | | |
45 | | static int parse_dos_extended(blkid_probe pr, blkid_parttable tab, |
46 | | uint32_t ex_start, uint32_t ex_size, int ssf) |
47 | 0 | { |
48 | 0 | blkid_partlist ls = blkid_probe_get_partlist(pr); |
49 | 0 | uint32_t cur_start = ex_start, cur_size = ex_size; |
50 | 0 | uint64_t ex_end = (uint64_t) ex_start + ex_size; |
51 | 0 | const unsigned char *data; |
52 | 0 | int ct_nodata = 0; /* count ext.partitions without data partitions */ |
53 | 0 | int i; |
54 | |
|
55 | 0 | DBG(LOWPROBE, ul_debug("parse EBR [start=%"PRIu32", size=%"PRIu32"]", ex_start/ssf, ex_size/ssf)); |
56 | 0 | if (ex_start == 0) { |
57 | 0 | DBG(LOWPROBE, ul_debug("Bad offset in primary extended partition -- ignore")); |
58 | 0 | return 0; |
59 | 0 | } |
60 | | |
61 | 0 | while (1) { |
62 | 0 | const struct dos_partition *p, *p0; |
63 | 0 | uint64_t start = 0, size; |
64 | |
|
65 | 0 | if (++ct_nodata > 100) |
66 | 0 | return BLKID_PROBE_OK; |
67 | 0 | data = blkid_probe_get_sector(pr, cur_start); |
68 | 0 | if (!data) { |
69 | 0 | if (errno) |
70 | 0 | return -errno; |
71 | 0 | goto leave; /* malformed partition? */ |
72 | 0 | } |
73 | | |
74 | 0 | if (!mbr_is_valid_magic(data)) |
75 | 0 | goto leave; |
76 | | |
77 | 0 | p0 = mbr_get_partition(data, 0); |
78 | | |
79 | | /* Usually, the first entry is the real data partition, |
80 | | * the 2nd entry is the next extended partition, or empty, |
81 | | * and the 3rd and 4th entries are unused. |
82 | | * However, DRDOS sometimes has the extended partition as |
83 | | * the first entry (when the data partition is empty), |
84 | | * and OS/2 seems to use all four entries. |
85 | | * -- Linux kernel fs/partitions/dos.c |
86 | | * |
87 | | * See also http://en.wikipedia.org/wiki/Extended_boot_record |
88 | | */ |
89 | | |
90 | | /* Parse data partition */ |
91 | 0 | for (p = p0, i = 0; i < 4; i++, p++) { |
92 | 0 | uint32_t abs_start; |
93 | 0 | uint64_t abs; |
94 | 0 | blkid_partition par; |
95 | | |
96 | | /* the start is relative to the parental ext.partition */ |
97 | 0 | start = (uint64_t) dos_partition_get_start(p) * ssf; |
98 | 0 | size = (uint64_t) dos_partition_get_size(p) * ssf; |
99 | |
|
100 | 0 | if (!size || is_extended(p)) |
101 | 0 | continue; |
102 | | |
103 | 0 | abs = (uint64_t) cur_start + start; |
104 | | |
105 | | /* data partition must be within the extended area */ |
106 | 0 | if (abs < ex_start || abs + size > ex_end) { |
107 | 0 | DBG(LOWPROBE, ul_debug("#%d: EBR data partition outside " |
108 | 0 | "extended -- ignore", i + 1)); |
109 | 0 | continue; |
110 | 0 | } |
111 | 0 | abs_start = (uint32_t) abs; |
112 | |
|
113 | 0 | if (i >= 2) { |
114 | | /* extra check to detect real data on |
115 | | * 3rd and 4th entries */ |
116 | 0 | if (start + size > cur_size) |
117 | 0 | continue; |
118 | 0 | } |
119 | | |
120 | | /* Avoid recursive non-empty links, see ct_nodata counter */ |
121 | 0 | if (blkid_partlist_get_partition_by_start(ls, abs_start)) { |
122 | 0 | DBG(LOWPROBE, ul_debug("#%d: EBR duplicate data partition [abs start=%u] -- ignore", |
123 | 0 | i + 1, abs_start)); |
124 | 0 | continue; |
125 | 0 | } |
126 | | |
127 | 0 | par = blkid_partlist_add_partition(ls, tab, abs_start, size); |
128 | 0 | if (!par) |
129 | 0 | return -ENOMEM; |
130 | | |
131 | 0 | blkid_partition_set_type(par, p->sys_ind); |
132 | 0 | blkid_partition_set_flags(par, p->boot_ind); |
133 | 0 | blkid_partition_gen_uuid(par); |
134 | 0 | ct_nodata = 0; |
135 | 0 | } |
136 | | /* The first nested ext.partition should be a link to the next |
137 | | * logical partition. Everything other (recursive ext.partitions) |
138 | | * is junk. |
139 | | */ |
140 | 0 | for (p = p0, i = 0; i < 4; i++, p++) { |
141 | 0 | start = (uint64_t) dos_partition_get_start(p) * ssf; |
142 | 0 | size = (uint64_t) dos_partition_get_size(p) * ssf; |
143 | |
|
144 | 0 | if (size && is_extended(p)) { |
145 | 0 | if (start == 0) |
146 | 0 | DBG(LOWPROBE, ul_debug("#%d: EBR link offset is zero -- ignore", i + 1)); |
147 | 0 | else |
148 | 0 | break; |
149 | 0 | } |
150 | 0 | } |
151 | 0 | if (i == 4) |
152 | 0 | goto leave; |
153 | | |
154 | 0 | { |
155 | 0 | uint64_t next = (uint64_t) ex_start + start; |
156 | |
|
157 | 0 | if (next + size > ex_end) { |
158 | 0 | DBG(LOWPROBE, ul_debug("EBR link outside " |
159 | 0 | "extended area -- leave")); |
160 | 0 | goto leave; |
161 | 0 | } |
162 | 0 | if (next <= cur_start) { |
163 | 0 | DBG(LOWPROBE, ul_debug("EBR link does not " |
164 | 0 | "advance -- leave")); |
165 | 0 | goto leave; |
166 | 0 | } |
167 | 0 | cur_start = (uint32_t) next; |
168 | 0 | cur_size = size; |
169 | 0 | } |
170 | 0 | } |
171 | 0 | leave: |
172 | 0 | return BLKID_PROBE_OK; |
173 | 0 | } |
174 | | |
175 | | static inline int is_lvm(blkid_probe pr) |
176 | 102 | { |
177 | 102 | struct blkid_prval *v = __blkid_probe_lookup_value(pr, "TYPE"); |
178 | | |
179 | 102 | return (v && v->data && strcmp((char *) v->data, "LVM2_member") == 0); |
180 | 102 | } |
181 | | |
182 | | static inline int is_empty_mbr(const unsigned char *mbr) |
183 | 0 | { |
184 | 0 | const struct dos_partition *p = mbr_get_partition(mbr, 0); |
185 | 0 | int i, nparts = 0; |
186 | |
|
187 | 0 | for (i = 0; i < 4; i++) { |
188 | 0 | if (dos_partition_get_size(p) > 0) |
189 | 0 | nparts++; |
190 | 0 | p++; |
191 | 0 | } |
192 | |
|
193 | 0 | return nparts == 0; |
194 | 0 | } |
195 | | |
196 | | static int probe_dos_pt(blkid_probe pr, |
197 | | const struct blkid_idmag *mag __attribute__((__unused__))) |
198 | 1.12k | { |
199 | 1.12k | int i; |
200 | 1.12k | int ssf; |
201 | 1.12k | blkid_parttable tab = NULL; |
202 | 1.12k | blkid_partlist ls; |
203 | 1.12k | const struct dos_partition *p0, *p; |
204 | 1.12k | const unsigned char *data; |
205 | 1.12k | uint64_t start, size; |
206 | 1.12k | uint32_t id; |
207 | 1.12k | char idstr[UUID_STR_LEN]; |
208 | | |
209 | | |
210 | 1.12k | data = blkid_probe_get_sector(pr, 0); |
211 | 1.12k | if (!data) { |
212 | 0 | if (errno) |
213 | 0 | return -errno; |
214 | 0 | goto nothing; |
215 | 0 | } |
216 | | |
217 | | /* ignore disks with AIX magic number -- for more details see aix.c */ |
218 | 1.12k | if (memcmp(data, BLKID_AIX_MAGIC_STRING, BLKID_AIX_MAGIC_STRLEN) == 0) |
219 | 0 | goto nothing; |
220 | | |
221 | 1.12k | p0 = mbr_get_partition(data, 0); |
222 | | |
223 | | /* |
224 | | * Reject PT where boot indicator is not 0 or 0x80. |
225 | | */ |
226 | 2.59k | for (p = p0, i = 0; i < 4; i++, p++) |
227 | 2.29k | if (p->boot_ind != 0 && p->boot_ind != 0x80) { |
228 | 826 | DBG(LOWPROBE, ul_debug("missing boot indicator -- ignore")); |
229 | 826 | goto nothing; |
230 | 826 | } |
231 | | |
232 | | /* |
233 | | * GPT uses valid MBR |
234 | | */ |
235 | 1.48k | for (p = p0, i = 0; i < 4; i++, p++) { |
236 | 1.19k | if (p->sys_ind == MBR_GPT_PARTITION) { |
237 | 2 | DBG(LOWPROBE, ul_debug("probably GPT -- ignore")); |
238 | 2 | goto nothing; |
239 | 2 | } |
240 | 1.19k | } |
241 | | |
242 | | /* |
243 | | * Now that the 55aa signature is present, this is probably |
244 | | * either the boot sector of a FAT filesystem or a DOS-type |
245 | | * partition table. |
246 | | */ |
247 | 296 | if (blkid_probe_is_vfat(pr) == 1 || blkid_probe_is_exfat(pr) == 1) { |
248 | 166 | DBG(LOWPROBE, ul_debug("probably FAT -- ignore")); |
249 | 166 | goto nothing; |
250 | 166 | } |
251 | | |
252 | | /* Another false positive is NTFS */ |
253 | 130 | if (blkid_probe_is_ntfs(pr) == 1) { |
254 | 28 | DBG(LOWPROBE, ul_debug("probably NTFS -- ignore")); |
255 | 28 | goto nothing; |
256 | 28 | } |
257 | | |
258 | | /* |
259 | | * Ugly exception, if the device contains a valid LVM physical volume |
260 | | * and empty MBR (=no partition defined) then it's LVM and MBR should |
261 | | * be ignored. Crazy people use it to boot from LVM devices. |
262 | | */ |
263 | 102 | if (is_lvm(pr) && is_empty_mbr(data)) { |
264 | 0 | DBG(LOWPROBE, ul_debug("empty MBR on LVM device -- ignore")); |
265 | 0 | goto nothing; |
266 | 0 | } |
267 | | |
268 | 102 | blkid_probe_use_wiper(pr, MBR_PT_OFFSET, 512 - MBR_PT_OFFSET); |
269 | | |
270 | 102 | id = mbr_get_id(data); |
271 | 102 | if (id) |
272 | 60 | snprintf(idstr, sizeof(idstr), "%08x", id); |
273 | | |
274 | | /* |
275 | | * Well, all checks pass, it's MS-DOS partition table |
276 | | */ |
277 | 102 | if (blkid_partitions_need_typeonly(pr)) { |
278 | | /* Non-binary interface -- caller does not ask for details |
279 | | * about partitions, just set generic variables only. */ |
280 | 102 | if (id) |
281 | 60 | blkid_partitions_strcpy_ptuuid(pr, idstr); |
282 | 102 | return 0; |
283 | 102 | } |
284 | | |
285 | 0 | ls = blkid_probe_get_partlist(pr); |
286 | 0 | if (!ls) |
287 | 0 | goto nothing; |
288 | | |
289 | | /* sector size factor (the start and size are in the real sectors, but |
290 | | * we need to convert all sizes to 512 logical sectors |
291 | | */ |
292 | 0 | ssf = blkid_probe_get_sectorsize(pr) / 512; |
293 | | |
294 | | /* allocate a new partition table */ |
295 | 0 | tab = blkid_partlist_new_parttable(ls, "dos", MBR_PT_OFFSET); |
296 | 0 | if (!tab) |
297 | 0 | return -ENOMEM; |
298 | | |
299 | 0 | if (id) |
300 | 0 | blkid_parttable_set_id(tab, (unsigned char *) idstr); |
301 | | |
302 | | /* Parse primary partitions */ |
303 | 0 | for (p = p0, i = 0; i < 4; i++, p++) { |
304 | 0 | blkid_partition par; |
305 | |
|
306 | 0 | start = (uint64_t) dos_partition_get_start(p) * ssf; |
307 | 0 | size = (uint64_t) dos_partition_get_size(p) * ssf; |
308 | |
|
309 | 0 | if (!size) { |
310 | | /* Linux kernel ignores empty partitions, but partno for |
311 | | * the empty primary partitions is not reused */ |
312 | 0 | blkid_partlist_increment_partno(ls); |
313 | 0 | continue; |
314 | 0 | } |
315 | 0 | par = blkid_partlist_add_partition(ls, tab, start, size); |
316 | 0 | if (!par) |
317 | 0 | return -ENOMEM; |
318 | | |
319 | 0 | blkid_partition_set_type(par, p->sys_ind); |
320 | 0 | blkid_partition_set_flags(par, p->boot_ind); |
321 | 0 | blkid_partition_gen_uuid(par); |
322 | 0 | } |
323 | | |
324 | | /* Linux uses partition numbers greater than 4 |
325 | | * for all logical partition and all nested partition tables (bsd, ..) |
326 | | */ |
327 | 0 | blkid_partlist_set_partno(ls, 5); |
328 | | |
329 | | /* Parse logical partitions */ |
330 | 0 | for (p = p0, i = 0; i < 4; i++, p++) { |
331 | 0 | start = (uint64_t) dos_partition_get_start(p) * ssf; |
332 | 0 | size = (uint64_t) dos_partition_get_size(p) * ssf; |
333 | |
|
334 | 0 | if (!size) |
335 | 0 | continue; |
336 | 0 | if (is_extended(p) && |
337 | 0 | parse_dos_extended(pr, tab, start, size, ssf) == -1) |
338 | 0 | goto nothing; |
339 | 0 | } |
340 | | |
341 | | /* Parse subtypes (nested partitions) on large disks */ |
342 | 0 | if (!blkid_probe_is_tiny(pr)) { |
343 | 0 | int nparts = blkid_partlist_numof_partitions(ls); |
344 | |
|
345 | 0 | DBG(LOWPROBE, ul_debug("checking for subtypes")); |
346 | |
|
347 | 0 | for (i = 0; i < nparts; i++) { |
348 | 0 | size_t n; |
349 | 0 | int type; |
350 | 0 | blkid_partition pa = blkid_partlist_get_partition(ls, i); |
351 | |
|
352 | 0 | if (pa == NULL |
353 | 0 | || blkid_partition_get_size(pa) == 0 |
354 | 0 | || blkid_partition_is_extended(pa) |
355 | 0 | || blkid_partition_is_logical(pa)) |
356 | 0 | continue; |
357 | | |
358 | 0 | type = blkid_partition_get_type(pa); |
359 | |
|
360 | 0 | for (n = 0; n < ARRAY_SIZE(dos_nested); n++) { |
361 | 0 | int rc; |
362 | |
|
363 | 0 | if (dos_nested[n].type != type) |
364 | 0 | continue; |
365 | | |
366 | 0 | rc = blkid_partitions_do_subprobe(pr, pa, |
367 | 0 | dos_nested[n].id); |
368 | 0 | if (rc < 0) |
369 | 0 | return rc; |
370 | 0 | break; |
371 | 0 | } |
372 | 0 | } |
373 | 0 | } |
374 | 0 | return BLKID_PROBE_OK; |
375 | | |
376 | 1.02k | nothing: |
377 | 1.02k | return BLKID_PROBE_NONE; |
378 | 0 | } |
379 | | |
380 | | |
381 | | const struct blkid_idinfo dos_pt_idinfo = |
382 | | { |
383 | | .name = "dos", |
384 | | .probefunc = probe_dos_pt, |
385 | | .magics = |
386 | | { |
387 | | /* DOS master boot sector: |
388 | | * |
389 | | * 0 | Code Area |
390 | | * 440 | Optional Disk signature |
391 | | * 446 | Partition table |
392 | | * 510 | 0x55 |
393 | | * 511 | 0xAA |
394 | | */ |
395 | | { .magic = "\x55\xAA", .len = 2, .sboff = 510 }, |
396 | | { NULL } |
397 | | } |
398 | | }; |
399 | | |