]> code.delx.au - refind/blob - gptsync/gptsync.c
ChromeOS icon.
[refind] / gptsync / gptsync.c
1 /*
2 * gptsync/gptsync.c
3 * Platform-independent code for syncing GPT and MBR
4 *
5 * Copyright (c) 2006-2007 Christoph Pfisterer
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are
10 * met:
11 *
12 * * Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 *
15 * * Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the
18 * distribution.
19 *
20 * * Neither the name of Christoph Pfisterer nor the names of the
21 * contributors may be used to endorse or promote products derived
22 * from this software without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
27 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
28 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
30 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
31 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
32 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
33 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 */
36 /* Changes copyright (c) 2013 Roderick W. Smith */
37
38 #include "gptsync.h"
39
40 #include "syslinux_mbr.h"
41
42 //
43 // MBR functions
44 //
45
46 static UINTN check_mbr(VOID)
47 {
48 UINTN i, k;
49 BOOLEAN found = FALSE;
50
51 // check each entry
52 for (i = 0; i < mbr_part_count; i++) {
53 // check for overlap
54 for (k = 0; k < mbr_part_count; k++) {
55 if (k != i && !(mbr_parts[i].start_lba > mbr_parts[k].end_lba || mbr_parts[k].start_lba > mbr_parts[i].end_lba)) {
56 Print(L"Status: MBR partition table is invalid, partitions overlap.\n");
57 return EFI_UNSUPPORTED;
58 }
59 }
60
61 // check for extended partitions
62 if (mbr_parts[i].mbr_type == 0x05 || mbr_parts[i].mbr_type == 0x0f || mbr_parts[i].mbr_type == 0x85) {
63 Print(L"Status: Extended partition found in MBR table, will not touch this disk.\n",
64 gpt_parts[i].gpt_parttype->name);
65 return EFI_UNSUPPORTED;
66 }
67
68 // Check for matching GPT partitition; if not found, flag error
69 if ((mbr_parts[i].mbr_type != 0xEE) && (mbr_parts[i].mbr_type != 0x00)) {
70 found = FALSE;
71 for (k = 0; (k < gpt_part_count) && !found; k++) {
72 if ((mbr_parts[i].start_lba == gpt_parts[k].start_lba) && (mbr_parts[i].end_lba == gpt_parts[k].end_lba)) {
73 found = TRUE;
74 } // if
75 } // for
76 if (!found) {
77 Print(L"Status: Found MBR partition with no matching GPT partition. Re-syncing could\n");
78 Print(L"destroy data; will not touch this disk.\n");
79 return EFI_UNSUPPORTED;
80 } // if
81 } // if
82
83 } // for
84
85 return 0;
86 } // UINTN check_mbr()
87
88 static UINTN write_mbr(VOID)
89 {
90 UINTN status;
91 UINTN i, k;
92 UINT8 active;
93 UINT64 lba;
94 MBR_PART_INFO *table;
95 BOOLEAN have_bootcode;
96
97 Print(L"\nWriting new MBR...\n");
98
99 // read MBR data
100 status = read_sector(0, sector);
101 if (status != 0)
102 return status;
103
104 // write partition table
105 *((UINT16 *)(sector + 510)) = 0xaa55;
106
107 table = (MBR_PART_INFO *)(sector + 446);
108 active = 0x80;
109 for (i = 0; i < 4; i++) {
110 for (k = 0; k < new_mbr_part_count; k++) {
111 if (new_mbr_parts[k].index == i)
112 break;
113 }
114 if (k >= new_mbr_part_count) {
115 // unused entry
116 table[i].flags = 0;
117 table[i].start_chs[0] = 0;
118 table[i].start_chs[1] = 0;
119 table[i].start_chs[2] = 0;
120 table[i].type = 0;
121 table[i].end_chs[0] = 0;
122 table[i].end_chs[1] = 0;
123 table[i].end_chs[2] = 0;
124 table[i].start_lba = 0;
125 table[i].size = 0;
126 } else {
127 if (new_mbr_parts[k].active) {
128 table[i].flags = active;
129 active = 0x00;
130 } else
131 table[i].flags = 0x00;
132 table[i].start_chs[0] = 0xfe;
133 table[i].start_chs[1] = 0xff;
134 table[i].start_chs[2] = 0xff;
135 table[i].type = new_mbr_parts[k].mbr_type;
136 table[i].end_chs[0] = 0xfe;
137 table[i].end_chs[1] = 0xff;
138 table[i].end_chs[2] = 0xff;
139
140 lba = new_mbr_parts[k].start_lba;
141 if (lba > MAX_MBR_LBA) {
142 Print(L"Warning: Partition %d starts beyond 2 TiB limit\n", i+1);
143 lba = MAX_MBR_LBA;
144 }
145 table[i].start_lba = (UINT32)lba;
146
147 lba = new_mbr_parts[k].end_lba + 1 - new_mbr_parts[k].start_lba;
148 if (lba > MAX_MBR_LBA) {
149 Print(L"Warning: Partition %d extends beyond 2 TiB limit\n", i+1);
150 lba = MAX_MBR_LBA;
151 }
152 table[i].size = (UINT32)lba;
153 }
154 }
155
156 // add boot code if necessary
157 have_bootcode = FALSE;
158 for (i = 0; i < MBR_BOOTCODE_SIZE; i++) {
159 if (sector[i] != 0) {
160 have_bootcode = TRUE;
161 break;
162 }
163 }
164 if (!have_bootcode) {
165 // no boot code found in the MBR, add the syslinux MBR code
166 SetMem(sector, MBR_BOOTCODE_SIZE, 0);
167 CopyMem(sector, syslinux_mbr, SYSLINUX_MBR_SIZE);
168 }
169
170 // write MBR data
171 status = write_sector(0, sector);
172 if (status != 0)
173 return status;
174
175 Print(L"MBR updated successfully!\n");
176
177 return 0;
178 }
179
180 //
181 // GPT functions
182 //
183
184 static UINTN check_gpt(VOID)
185 {
186 UINTN i, k;
187
188 if (gpt_part_count == 0) {
189 Print(L"Status: No GPT partition table, no need to sync.\n");
190 return EFI_UNSUPPORTED;
191 }
192
193 // check each entry
194 for (i = 0; i < gpt_part_count; i++) {
195 // check sanity
196 if (gpt_parts[i].end_lba < gpt_parts[i].start_lba) {
197 Print(L"Status: GPT partition table is invalid.\n");
198 return EFI_UNSUPPORTED;
199 }
200 // check for overlap
201 for (k = 0; k < gpt_part_count; k++) {
202 if (k != i && !(gpt_parts[i].start_lba > gpt_parts[k].end_lba || gpt_parts[k].start_lba > gpt_parts[i].end_lba)) {
203 Print(L"Status: GPT partition table is invalid, partitions overlap.\n");
204 return EFI_UNSUPPORTED;
205 }
206 }
207
208 // check for partitions kind
209 if (gpt_parts[i].gpt_parttype->kind == GPT_KIND_FATAL) {
210 Print(L"Status: GPT partition of type '%s' found, will not touch this disk.\n",
211 gpt_parts[i].gpt_parttype->name);
212 return EFI_UNSUPPORTED;
213 }
214 }
215
216 return 0;
217 } // VOID check_gpt()
218
219 //
220 // compare GPT and MBR tables
221 //
222
223 #define ACTION_NONE (0)
224 #define ACTION_NOP (1)
225 #define ACTION_REWRITE (2)
226
227 // Copy a single GPT entry to the new_mbr_parts array.
228 static VOID copy_gpt_to_new_mbr(UINTN gpt_num, UINTN mbr_num) {
229 new_mbr_parts[mbr_num].index = mbr_num;
230 new_mbr_parts[mbr_num].start_lba = gpt_parts[gpt_num].start_lba;
231 new_mbr_parts[mbr_num].end_lba = gpt_parts[gpt_num].end_lba;
232 new_mbr_parts[mbr_num].mbr_type = gpt_parts[gpt_num].mbr_type;
233 new_mbr_parts[mbr_num].active = FALSE;
234 } // VOID copy_gpt_to_new_mbr()
235
236 // A simple bubble sort for the MBR partitions.
237 static VOID sort_mbr(PARTITION_INFO *parts) {
238 PARTITION_INFO one_part;
239 int c, d;
240
241 if (parts == NULL)
242 return;
243
244 for (c = 0 ; c < 3; c++) {
245 for (d = 1 ; d < 3 - c; d++) {
246 if ((parts[d].start_lba > parts[d + 1].start_lba) && (parts[d].start_lba > 0) && (parts[d + 1].start_lba > 0)) {
247 one_part = parts[d];
248 parts[d] = parts[d + 1];
249 parts[d + 1] = one_part;
250 parts[d].index = d;
251 parts[d + 1].index = d + 1;
252 } // if
253 } // for
254 } // for
255 } // VOID sort_mbr()
256
257 // Generate a hybrid MBR based on the current GPT. Stores the result in the
258 // new_mbr_parts[] array.
259 static VOID generate_hybrid_mbr(VOID) {
260 UINTN i, k, iter, count_active;
261 UINT64 first_used_lba;
262
263 new_mbr_part_count = 1;
264 first_used_lba = (UINT64) MAX_MBR_LBA + (UINT64) 1;
265
266 // Copy partitions in three passes....
267 // First, do FAT and NTFS partitions....
268 i = 0;
269 do {
270 if ((gpt_parts[i].start_lba > 0) && (gpt_parts[i].end_lba > 0) &&
271 (gpt_parts[i].end_lba <= MAX_MBR_LBA) && /* Within MBR limits */
272 (gpt_parts[i].gpt_parttype->kind == GPT_KIND_BASIC_DATA) && /* MS Basic Data GPT type code */
273 (gpt_parts[i].mbr_type != 0x83)) { /* Not containing Linux filesystem */
274 copy_gpt_to_new_mbr(i, new_mbr_part_count);
275 if (new_mbr_parts[new_mbr_part_count].start_lba < first_used_lba)
276 first_used_lba = new_mbr_parts[new_mbr_part_count].start_lba;
277
278 new_mbr_part_count++;
279 }
280 i++;
281 } while (i < gpt_part_count && new_mbr_part_count <= 3);
282
283 // Second, do Linux partitions. Note that we start from the END of the
284 // partition list, so as to maximize the space covered by the 0xEE
285 // partition if there are several Linux partitions before other hybridized
286 // partitions.
287 i = gpt_part_count - 1; // Note that gpt_part_count can't be 0; filtered by check_gpt()
288 while (i >= 0 && new_mbr_part_count <= 3) {
289 if ((gpt_parts[i].start_lba > 0) && (gpt_parts[i].end_lba > 0) &&
290 (gpt_parts[i].end_lba <= MAX_MBR_LBA) &&
291 ((gpt_parts[i].gpt_parttype->kind == GPT_KIND_DATA) || (gpt_parts[i].gpt_parttype->kind == GPT_KIND_BASIC_DATA)) &&
292 (gpt_parts[i].mbr_type == 0x83)) {
293 copy_gpt_to_new_mbr(i, new_mbr_part_count);
294 if (new_mbr_parts[new_mbr_part_count].start_lba < first_used_lba)
295 first_used_lba = new_mbr_parts[new_mbr_part_count].start_lba;
296
297 new_mbr_part_count++;
298 }
299 i--;
300 } // while
301
302 // Third, do anything that's left to cover uncovered spaces; but this requires
303 // first creating the EFI protective entry, since we don't want to bother with
304 // anything already covered by this entry....
305 new_mbr_parts[0].index = 0;
306 new_mbr_parts[0].start_lba = 1;
307 new_mbr_parts[0].end_lba = (disk_size() > first_used_lba) ? (first_used_lba - 1) : disk_size() - 1;
308 if (new_mbr_parts[0].end_lba > MAX_MBR_LBA)
309 new_mbr_parts[0].end_lba = MAX_MBR_LBA;
310 new_mbr_parts[0].mbr_type = 0xEE;
311 i = 0;
312 while (i < gpt_part_count && new_mbr_part_count <= 3) {
313 if ((gpt_parts[i].start_lba > new_mbr_parts[0].end_lba) && (gpt_parts[i].end_lba > 0) &&
314 (gpt_parts[i].end_lba <= MAX_MBR_LBA) &&
315 (gpt_parts[i].gpt_parttype->kind != GPT_KIND_BASIC_DATA) &&
316 (gpt_parts[i].mbr_type != 0x83)) {
317 copy_gpt_to_new_mbr(i, new_mbr_part_count);
318 new_mbr_part_count++;
319 }
320 i++;
321 } // while
322
323 // find matching partitions in the old MBR table, copy undetected details....
324 for (i = 1; i < new_mbr_part_count; i++) {
325 for (k = 0; k < mbr_part_count; k++) {
326 if (mbr_parts[k].start_lba == new_mbr_parts[i].start_lba) {
327 // keep type if not detected
328 if (new_mbr_parts[i].mbr_type == 0)
329 new_mbr_parts[i].mbr_type = mbr_parts[k].mbr_type;
330 // keep active flag
331 new_mbr_parts[i].active = mbr_parts[k].active;
332 break;
333 } // if
334 } // for (k...)
335 if (new_mbr_parts[i].mbr_type == 0) {
336 // final fallback: set to a (hopefully) unused type
337 new_mbr_parts[i].mbr_type = 0xc0;
338 } // if
339 } // for (i...)
340
341 sort_mbr(new_mbr_parts);
342
343 // make sure there's exactly one active partition
344 for (iter = 0; iter < 3; iter++) {
345 // check
346 count_active = 0;
347 for (i = 0; i < new_mbr_part_count; i++)
348 if (new_mbr_parts[i].active)
349 count_active++;
350 if (count_active == 1)
351 break;
352
353 // set active on the first matching partition
354 if (count_active == 0) {
355 for (i = 0; i < new_mbr_part_count; i++) {
356 if ((iter >= 0 && (new_mbr_parts[i].mbr_type == 0x07 || // NTFS
357 new_mbr_parts[i].mbr_type == 0x0b || // FAT32
358 new_mbr_parts[i].mbr_type == 0x0c)) || // FAT32 (LBA)
359 (iter >= 1 && (new_mbr_parts[i].mbr_type == 0x83)) || // Linux
360 (iter >= 2 && i > 0)) {
361 new_mbr_parts[i].active = TRUE;
362 break;
363 }
364 }
365 } else if (count_active > 1 && iter == 0) {
366 // too many active partitions, try deactivating the ESP / EFI Protective entry
367 if ((new_mbr_parts[0].mbr_type == 0xee || new_mbr_parts[0].mbr_type == 0xef) &&
368 new_mbr_parts[0].active) {
369 new_mbr_parts[0].active = FALSE;
370 }
371 } else if (count_active > 1 && iter > 0) {
372 // too many active partitions, deactivate all but the first one
373 count_active = 0;
374 for (i = 0; i < new_mbr_part_count; i++)
375 if (new_mbr_parts[i].active) {
376 if (count_active > 0)
377 new_mbr_parts[i].active = FALSE;
378 count_active++;
379 }
380 }
381 } // for
382 } // VOID generate_hybrid_mbr()
383
384 // Examine partitions and decide whether a rewrite is in order.
385 // Note that this function MAY ask user for advice.
386 // Note that this function assumes the new hybrid MBR has already
387 // been computed and stored in new_mbr_parts[].
388 static BOOLEAN should_rewrite(VOID) {
389 BOOLEAN retval = TRUE, all_identical = TRUE, invalid;
390 UINTN i, num_existing_hybrid = 0, num_new_hybrid = 0;
391
392 // Check to see if the proposed table is identical to the current one;
393 // if so, synchronizing is pointless....
394 for (i = 0; i < 4; i++) {
395 if ((new_mbr_parts[i].mbr_type != 0xEE) && (mbr_parts[i].mbr_type != 0xEE) &&
396 ((new_mbr_parts[i].active != mbr_parts[i].active) ||
397 (new_mbr_parts[i].start_lba != mbr_parts[i].start_lba) ||
398 (new_mbr_parts[i].end_lba != mbr_parts[i].end_lba) ||
399 (new_mbr_parts[i].mbr_type != mbr_parts[i].mbr_type)))
400 all_identical = FALSE;
401
402 // while we're looping, count the number of old & new hybrid partitions....
403 if ((mbr_parts[i].mbr_type != 0x00) && (mbr_parts[i].mbr_type != 0xEE))
404 num_existing_hybrid++;
405 if ((new_mbr_parts[i].mbr_type != 0x00) && (new_mbr_parts[i].mbr_type != 0xEE))
406 num_new_hybrid++;
407 } // for
408
409 if (all_identical) {
410 Print(L"Tables are synchronized, no need to sync.\n");
411 return FALSE;
412 }
413
414 // If there's nothing to hybridize, but an existing hybrid MBR exists, offer to replace
415 // the hybrid MBR with a protective MBR.
416 if ((num_new_hybrid == 0) && (num_existing_hybrid > 0)) {
417 Print(L"Found no partitions that could be hybridized, but an existing hybrid MBR exists.\n");
418 Print(L"If you proceed, a fresh protective MBR will be created. Do you want to create\n");
419 invalid = input_boolean(STR("this new protective MBR, erasing the hybrid MBR? [y/N] "), &retval);
420 if (invalid)
421 retval = FALSE;
422 } // if
423
424 // If existing hybrid MBR that's NOT identical to the new one, ask the user
425 // before overwriting the old one.
426 if ((num_new_hybrid > 0) && (num_existing_hybrid > 0)) {
427 Print(L"Existing hybrid MBR detected, but it's not identical to what this program\n");
428 Print(L"would generate. Do you want to see the hybrid MBR that this program would\n");
429 invalid = input_boolean(STR("generate? [y/N] "), &retval);
430 if (invalid)
431 retval = FALSE;
432 } // if
433
434 return retval;
435 } // BOOLEAN should_rewrite()
436
437 static UINTN analyze(VOID)
438 {
439 UINTN i, detected_parttype;
440 CHARN *fsname;
441 UINTN status;
442
443 new_mbr_part_count = 0;
444
445 // determine correct MBR types for GPT partitions
446 if (gpt_part_count == 0) {
447 Print(L"Status: No GPT partitions defined, nothing to sync.\n");
448 return 0;
449 }
450 for (i = 0; i < gpt_part_count; i++) {
451 gpt_parts[i].mbr_type = gpt_parts[i].gpt_parttype->mbr_type;
452 if (gpt_parts[i].gpt_parttype->kind == GPT_KIND_BASIC_DATA) {
453 // Basic Data: need to look at data in the partition
454 status = detect_mbrtype_fs(gpt_parts[i].start_lba, &detected_parttype, &fsname);
455 if (status != 0)
456 Print(L"Warning: Error %d when detecting filesystem type!\n", status);
457 if (detected_parttype)
458 gpt_parts[i].mbr_type = detected_parttype;
459 else
460 gpt_parts[i].mbr_type = 0x0b; // fallback: FAT32
461 }
462 // NOTE: mbr_type may still be 0 if content detection fails for exotic GPT types or file systems
463 } // for
464
465 // generate the new table
466 generate_hybrid_mbr();
467 if (!should_rewrite())
468 return EFI_ABORTED;
469
470 // display table
471 Print(L"\nProposed new MBR partition table:\n");
472 Print(L" # A Start LBA End LBA Type\n");
473 for (i = 0; i < new_mbr_part_count; i++) {
474 Print(L" %d %s %12lld %12lld %02x %s\n",
475 new_mbr_parts[i].index + 1,
476 new_mbr_parts[i].active ? STR("*") : STR(" "),
477 new_mbr_parts[i].start_lba,
478 new_mbr_parts[i].end_lba,
479 new_mbr_parts[i].mbr_type,
480 mbr_parttype_name(new_mbr_parts[i].mbr_type));
481 }
482
483 return 0;
484 } // UINTN analyze()
485
486 //
487 // sync algorithm entry point
488 //
489
490 UINTN gptsync(VOID)
491 {
492 UINTN status = 0;
493 UINTN status_gpt, status_mbr;
494 BOOLEAN proceed = FALSE;
495
496 Print(L"gptsync version %s\ncopyright (c) 2006-2007 Christoph Pfisterer & 2013 Roderick W. Smith\n", VERSION);
497
498 // get full information from disk
499 status_gpt = read_gpt();
500 status_mbr = read_mbr();
501 if (status_gpt != 0 || status_mbr != 0)
502 return (status_gpt || status_mbr);
503
504 // cross-check current situation
505 Print(L"\n");
506 status = check_gpt(); // check GPT for consistency
507 if (status != 0)
508 return status;
509 status = check_mbr(); // check MBR for consistency
510 if (status != 0)
511 return status;
512 status = analyze(); // analyze the situation & compose new MBR table
513 if (status != 0)
514 return status;
515 if (new_mbr_part_count == 0)
516 return status;
517
518 // offer user the choice what to do
519 status = input_boolean(STR("\nMay I update the MBR as printed above? [y/N] "), &proceed);
520 if (status != 0 || proceed != TRUE)
521 return status;
522
523 // adjust the MBR and write it back
524 status = write_mbr();
525 if (status != 0)
526 return status;
527
528 return status;
529 }