1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2011 Nathan Whitehorn
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
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.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 *
28 * $FreeBSD$
29 */
30
31 #include <sys/param.h>
32
33 #include <dialog.h>
34 #include <dlg_keys.h>
35 #include <err.h>
36 #include <errno.h>
37 #include <fstab.h>
38 #include <inttypes.h>
39 #include <libgeom.h>
40 #include <libutil.h>
41 #include <stdlib.h>
42 #include <sysexits.h>
43
44 #include "diskeditor.h"
45 #include "partedit.h"
46
47 struct pmetadata_head part_metadata;
48 static int sade_mode = 0;
49
50 static int apply_changes(struct gmesh *mesh);
51 static void apply_workaround(struct gmesh *mesh);
52 static struct partedit_item *read_geom_mesh(struct gmesh *mesh, int *nitems);
53 static void add_geom_children(struct ggeom *gp, int recurse,
54 struct partedit_item **items, int *nitems);
55 static void init_fstab_metadata(void);
56 static void get_mount_points(struct partedit_item *items, int nitems);
57 static int validate_setup(void);
58
59 static void
sigint_handler(int sig)60 sigint_handler(int sig)
61 {
62 struct gmesh mesh;
63
64 /* Revert all changes and exit dialog-mode cleanly on SIGINT */
65 geom_gettree(&mesh);
66 gpart_revert_all(&mesh);
67 geom_deletetree(&mesh);
68
69 end_dialog();
70
71 exit(1);
72 }
73
74 int
main(int argc,const char ** argv)75 main(int argc, const char **argv)
76 {
77 struct partition_metadata *md;
78 const char *progname, *prompt;
79 struct partedit_item *items = NULL;
80 struct gmesh mesh;
81 int i, op, nitems, nscroll;
82 int error;
83
84 progname = getprogname();
85 if (strcmp(progname, "sade") == 0)
86 sade_mode = 1;
87
88 TAILQ_INIT(&part_metadata);
89
90 init_fstab_metadata();
91
92 init_dialog(stdin, stdout);
93 if (!sade_mode)
94 dialog_vars.backtitle = __DECONST(char *, "FreeBSD Installer");
95 dialog_vars.item_help = TRUE;
96 nscroll = i = 0;
97
98 /* Revert changes on SIGINT */
99 signal(SIGINT, sigint_handler);
100
101 if (strcmp(progname, "autopart") == 0) { /* Guided */
102 prompt = "Please review the disk setup. When complete, press "
103 "the Finish button.";
104 /* Experimental ZFS autopartition support */
105 if (argc > 1 && strcmp(argv[1], "zfs") == 0) {
106 part_wizard("zfs");
107 } else {
108 part_wizard("ufs");
109 }
110 } else if (strcmp(progname, "scriptedpart") == 0) {
111 error = scripted_editor(argc, argv);
112 prompt = NULL;
113 if (error != 0) {
114 end_dialog();
115 return (error);
116 }
117 } else {
118 prompt = "Create partitions for FreeBSD. No changes will be "
119 "made until you select Finish.";
120 }
121
122 /* Show the part editor either immediately, or to confirm wizard */
123 while (prompt != NULL) {
124 dlg_clear();
125 dlg_put_backtitle();
126
127 error = geom_gettree(&mesh);
128 if (error == 0)
129 items = read_geom_mesh(&mesh, &nitems);
130 if (error || items == NULL) {
131 dialog_msgbox("Error", "No disks found. If you need to "
132 "install a kernel driver, choose Shell at the "
133 "installation menu.", 0, 0, TRUE);
134 break;
135 }
136
137 get_mount_points(items, nitems);
138
139 if (i >= nitems)
140 i = nitems - 1;
141 op = diskeditor_show("Partition Editor", prompt,
142 items, nitems, &i, &nscroll);
143
144 switch (op) {
145 case 0: /* Create */
146 gpart_create((struct gprovider *)(items[i].cookie),
147 NULL, NULL, NULL, NULL, 1);
148 break;
149 case 1: /* Delete */
150 gpart_delete((struct gprovider *)(items[i].cookie));
151 break;
152 case 2: /* Modify */
153 gpart_edit((struct gprovider *)(items[i].cookie));
154 break;
155 case 3: /* Revert */
156 gpart_revert_all(&mesh);
157 while ((md = TAILQ_FIRST(&part_metadata)) != NULL) {
158 if (md->fstab != NULL) {
159 free(md->fstab->fs_spec);
160 free(md->fstab->fs_file);
161 free(md->fstab->fs_vfstype);
162 free(md->fstab->fs_mntops);
163 free(md->fstab->fs_type);
164 free(md->fstab);
165 }
166 if (md->newfs != NULL)
167 free(md->newfs);
168 free(md->name);
169
170 TAILQ_REMOVE(&part_metadata, md, metadata);
171 free(md);
172 }
173 init_fstab_metadata();
174 break;
175 case 4: /* Auto */
176 part_wizard("ufs");
177 break;
178 }
179
180 error = 0;
181 if (op == 5) { /* Finished */
182 dialog_vars.ok_label = __DECONST(char *, "Commit");
183 dialog_vars.extra_label =
184 __DECONST(char *, "Revert & Exit");
185 dialog_vars.extra_button = TRUE;
186 dialog_vars.cancel_label = __DECONST(char *, "Back");
187 op = dialog_yesno("Confirmation", "Your changes will "
188 "now be written to disk. If you have chosen to "
189 "overwrite existing data, it will be PERMANENTLY "
190 "ERASED. Are you sure you want to commit your "
191 "changes?", 0, 0);
192 dialog_vars.ok_label = NULL;
193 dialog_vars.extra_button = FALSE;
194 dialog_vars.cancel_label = NULL;
195
196 if (op == 0 && validate_setup()) { /* Save */
197 error = apply_changes(&mesh);
198 if (!error)
199 apply_workaround(&mesh);
200 break;
201 } else if (op == 3) { /* Quit */
202 gpart_revert_all(&mesh);
203 error = -1;
204 break;
205 }
206 }
207
208 geom_deletetree(&mesh);
209 free(items);
210 }
211
212 if (prompt == NULL) {
213 error = geom_gettree(&mesh);
214 if (validate_setup()) {
215 error = apply_changes(&mesh);
216 } else {
217 gpart_revert_all(&mesh);
218 error = -1;
219 }
220 }
221
222 geom_deletetree(&mesh);
223 free(items);
224 end_dialog();
225
226 return (error);
227 }
228
229 struct partition_metadata *
get_part_metadata(const char * name,int create)230 get_part_metadata(const char *name, int create)
231 {
232 struct partition_metadata *md;
233
234 TAILQ_FOREACH(md, &part_metadata, metadata)
235 if (md->name != NULL && strcmp(md->name, name) == 0)
236 break;
237
238 if (md == NULL && create) {
239 md = calloc(1, sizeof(*md));
240 md->name = strdup(name);
241 TAILQ_INSERT_TAIL(&part_metadata, md, metadata);
242 }
243
244 return (md);
245 }
246
247 void
delete_part_metadata(const char * name)248 delete_part_metadata(const char *name)
249 {
250 struct partition_metadata *md;
251
252 TAILQ_FOREACH(md, &part_metadata, metadata) {
253 if (md->name != NULL && strcmp(md->name, name) == 0) {
254 if (md->fstab != NULL) {
255 free(md->fstab->fs_spec);
256 free(md->fstab->fs_file);
257 free(md->fstab->fs_vfstype);
258 free(md->fstab->fs_mntops);
259 free(md->fstab->fs_type);
260 free(md->fstab);
261 }
262 if (md->newfs != NULL)
263 free(md->newfs);
264 free(md->name);
265
266 TAILQ_REMOVE(&part_metadata, md, metadata);
267 free(md);
268 break;
269 }
270 }
271 }
272
273 static int
validate_setup(void)274 validate_setup(void)
275 {
276 struct partition_metadata *md, *root = NULL;
277 int cancel;
278
279 TAILQ_FOREACH(md, &part_metadata, metadata) {
280 if (md->fstab != NULL && strcmp(md->fstab->fs_file, "/") == 0)
281 root = md;
282
283 /* XXX: Check for duplicate mountpoints */
284 }
285
286 if (root == NULL) {
287 dialog_msgbox("Error", "No root partition was found. "
288 "The root FreeBSD partition must have a mountpoint of '/'.",
289 0, 0, TRUE);
290 return (FALSE);
291 }
292
293 /*
294 * Check for root partitions that we aren't formatting, which is
295 * usually a mistake
296 */
297 if (root->newfs == NULL && !sade_mode) {
298 dialog_vars.defaultno = TRUE;
299 cancel = dialog_yesno("Warning", "The chosen root partition "
300 "has a preexisting filesystem. If it contains an existing "
301 "FreeBSD system, please update it with freebsd-update "
302 "instead of installing a new system on it. The partition "
303 "can also be erased by pressing \"No\" and then deleting "
304 "and recreating it. Are you sure you want to proceed?",
305 0, 0);
306 dialog_vars.defaultno = FALSE;
307 if (cancel)
308 return (FALSE);
309 }
310
311 return (TRUE);
312 }
313
314 static int
mountpoint_sorter(const void * xa,const void * xb)315 mountpoint_sorter(const void *xa, const void *xb)
316 {
317 struct partition_metadata *a = *(struct partition_metadata **)xa;
318 struct partition_metadata *b = *(struct partition_metadata **)xb;
319
320 if (a->fstab == NULL && b->fstab == NULL)
321 return 0;
322 if (a->fstab == NULL)
323 return 1;
324 if (b->fstab == NULL)
325 return -1;
326
327 return strcmp(a->fstab->fs_file, b->fstab->fs_file);
328 }
329
330 static int
apply_changes(struct gmesh * mesh)331 apply_changes(struct gmesh *mesh)
332 {
333 struct partition_metadata *md;
334 char message[512];
335 int i, nitems, error;
336 const char **items;
337 const char *fstab_path;
338 FILE *fstab;
339
340 nitems = 1; /* Partition table changes */
341 TAILQ_FOREACH(md, &part_metadata, metadata) {
342 if (md->newfs != NULL)
343 nitems++;
344 }
345 items = calloc(nitems * 2, sizeof(const char *));
346 items[0] = "Writing partition tables";
347 items[1] = "7"; /* In progress */
348 i = 1;
349 TAILQ_FOREACH(md, &part_metadata, metadata) {
350 if (md->newfs != NULL) {
351 char *item;
352 item = malloc(255);
353 sprintf(item, "Initializing %s", md->name);
354 items[i*2] = item;
355 items[i*2 + 1] = "Pending";
356 i++;
357 }
358 }
359
360 i = 0;
361 dialog_mixedgauge("Initializing",
362 "Initializing file systems. Please wait.", 0, 0, i*100/nitems,
363 nitems, __DECONST(char **, items));
364 gpart_commit(mesh);
365 items[i*2 + 1] = "3";
366 i++;
367
368 if (getenv("BSDINSTALL_LOG") == NULL)
369 setenv("BSDINSTALL_LOG", "/dev/null", 1);
370
371 TAILQ_FOREACH(md, &part_metadata, metadata) {
372 if (md->newfs != NULL) {
373 items[i*2 + 1] = "7"; /* In progress */
374 dialog_mixedgauge("Initializing",
375 "Initializing file systems. Please wait.", 0, 0,
376 i*100/nitems, nitems, __DECONST(char **, items));
377 sprintf(message, "(echo %s; %s) >>%s 2>>%s",
378 md->newfs, md->newfs, getenv("BSDINSTALL_LOG"),
379 getenv("BSDINSTALL_LOG"));
380 error = system(message);
381 items[i*2 + 1] = (error == 0) ? "3" : "1";
382 i++;
383 }
384 }
385 dialog_mixedgauge("Initializing",
386 "Initializing file systems. Please wait.", 0, 0,
387 i*100/nitems, nitems, __DECONST(char **, items));
388
389 for (i = 1; i < nitems; i++)
390 free(__DECONST(char *, items[i*2]));
391 free(items);
392
393 /* Sort filesystems for fstab so that mountpoints are ordered */
394 {
395 struct partition_metadata **tobesorted;
396 struct partition_metadata *tmp;
397 int nparts = 0;
398 TAILQ_FOREACH(md, &part_metadata, metadata)
399 nparts++;
400 tobesorted = malloc(sizeof(struct partition_metadata *)*nparts);
401 nparts = 0;
402 TAILQ_FOREACH_SAFE(md, &part_metadata, metadata, tmp) {
403 tobesorted[nparts++] = md;
404 TAILQ_REMOVE(&part_metadata, md, metadata);
405 }
406 qsort(tobesorted, nparts, sizeof(tobesorted[0]),
407 mountpoint_sorter);
408
409 /* Now re-add everything */
410 while (nparts-- > 0)
411 TAILQ_INSERT_HEAD(&part_metadata,
412 tobesorted[nparts], metadata);
413 free(tobesorted);
414 }
415
416 if (getenv("PATH_FSTAB") != NULL)
417 fstab_path = getenv("PATH_FSTAB");
418 else
419 fstab_path = "/etc/fstab";
420 fstab = fopen(fstab_path, "w+");
421 if (fstab == NULL) {
422 sprintf(message, "Cannot open fstab file %s for writing (%s)\n",
423 getenv("PATH_FSTAB"), strerror(errno));
424 dialog_msgbox("Error", message, 0, 0, TRUE);
425 return (-1);
426 }
427 fprintf(fstab, "# Device\tMountpoint\tFStype\tOptions\tDump\tPass#\n");
428 TAILQ_FOREACH(md, &part_metadata, metadata) {
429 if (md->fstab != NULL)
430 fprintf(fstab, "%s\t%s\t\t%s\t%s\t%d\t%d\n",
431 md->fstab->fs_spec, md->fstab->fs_file,
432 md->fstab->fs_vfstype, md->fstab->fs_mntops,
433 md->fstab->fs_freq, md->fstab->fs_passno);
434 }
435 fclose(fstab);
436
437 return (0);
438 }
439
440 static void
apply_workaround(struct gmesh * mesh)441 apply_workaround(struct gmesh *mesh)
442 {
443 struct gclass *classp;
444 struct ggeom *gp;
445 struct gconfig *gc;
446 const char *scheme = NULL, *modified = NULL;
447
448 LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
449 if (strcmp(classp->lg_name, "PART") == 0)
450 break;
451 }
452
453 if (strcmp(classp->lg_name, "PART") != 0) {
454 dialog_msgbox("Error", "gpart not found!", 0, 0, TRUE);
455 return;
456 }
457
458 LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
459 LIST_FOREACH(gc, &gp->lg_config, lg_config) {
460 if (strcmp(gc->lg_name, "scheme") == 0) {
461 scheme = gc->lg_val;
462 } else if (strcmp(gc->lg_name, "modified") == 0) {
463 modified = gc->lg_val;
464 }
465 }
466
467 if (scheme && strcmp(scheme, "GPT") == 0 &&
468 modified && strcmp(modified, "true") == 0) {
469 if (getenv("WORKAROUND_LENOVO"))
470 gpart_set_root(gp->lg_name, "lenovofix");
471 if (getenv("WORKAROUND_GPTACTIVE"))
472 gpart_set_root(gp->lg_name, "active");
473 }
474 }
475 }
476
477 static struct partedit_item *
read_geom_mesh(struct gmesh * mesh,int * nitems)478 read_geom_mesh(struct gmesh *mesh, int *nitems)
479 {
480 struct gclass *classp;
481 struct ggeom *gp;
482 struct partedit_item *items;
483
484 *nitems = 0;
485 items = NULL;
486
487 /*
488 * Build the device table. First add all disks (and CDs).
489 */
490
491 LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
492 if (strcmp(classp->lg_name, "DISK") != 0 &&
493 strcmp(classp->lg_name, "MD") != 0)
494 continue;
495
496 /* Now recurse into all children */
497 LIST_FOREACH(gp, &classp->lg_geom, lg_geom)
498 add_geom_children(gp, 0, &items, nitems);
499 }
500
501 return (items);
502 }
503
504 static void
add_geom_children(struct ggeom * gp,int recurse,struct partedit_item ** items,int * nitems)505 add_geom_children(struct ggeom *gp, int recurse, struct partedit_item **items,
506 int *nitems)
507 {
508 struct gconsumer *cp;
509 struct gprovider *pp;
510 struct gconfig *gc;
511
512 if (strcmp(gp->lg_class->lg_name, "PART") == 0 &&
513 !LIST_EMPTY(&gp->lg_config)) {
514 LIST_FOREACH(gc, &gp->lg_config, lg_config) {
515 if (strcmp(gc->lg_name, "scheme") == 0)
516 (*items)[*nitems-1].type = gc->lg_val;
517 }
518 }
519
520 if (LIST_EMPTY(&gp->lg_provider))
521 return;
522
523 LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
524 if (strcmp(gp->lg_class->lg_name, "LABEL") == 0)
525 continue;
526
527 /* Skip WORM media */
528 if (strncmp(pp->lg_name, "cd", 2) == 0)
529 continue;
530
531 *items = realloc(*items,
532 (*nitems+1)*sizeof(struct partedit_item));
533 (*items)[*nitems].indentation = recurse;
534 (*items)[*nitems].name = pp->lg_name;
535 (*items)[*nitems].size = pp->lg_mediasize;
536 (*items)[*nitems].mountpoint = NULL;
537 (*items)[*nitems].type = "";
538 (*items)[*nitems].cookie = pp;
539
540 LIST_FOREACH(gc, &pp->lg_config, lg_config) {
541 if (strcmp(gc->lg_name, "type") == 0)
542 (*items)[*nitems].type = gc->lg_val;
543 }
544
545 /* Skip swap-backed MD devices */
546 if (strcmp(gp->lg_class->lg_name, "MD") == 0 &&
547 strcmp((*items)[*nitems].type, "swap") == 0)
548 continue;
549
550 (*nitems)++;
551
552 LIST_FOREACH(cp, &pp->lg_consumers, lg_consumers)
553 add_geom_children(cp->lg_geom, recurse+1, items,
554 nitems);
555
556 /* Only use first provider for acd */
557 if (strcmp(gp->lg_class->lg_name, "ACD") == 0)
558 break;
559 }
560 }
561
562 static void
init_fstab_metadata(void)563 init_fstab_metadata(void)
564 {
565 struct fstab *fstab;
566 struct partition_metadata *md;
567
568 setfsent();
569 while ((fstab = getfsent()) != NULL) {
570 md = calloc(1, sizeof(struct partition_metadata));
571
572 md->name = NULL;
573 if (strncmp(fstab->fs_spec, "/dev/", 5) == 0)
574 md->name = strdup(&fstab->fs_spec[5]);
575
576 md->fstab = malloc(sizeof(struct fstab));
577 md->fstab->fs_spec = strdup(fstab->fs_spec);
578 md->fstab->fs_file = strdup(fstab->fs_file);
579 md->fstab->fs_vfstype = strdup(fstab->fs_vfstype);
580 md->fstab->fs_mntops = strdup(fstab->fs_mntops);
581 md->fstab->fs_type = strdup(fstab->fs_type);
582 md->fstab->fs_freq = fstab->fs_freq;
583 md->fstab->fs_passno = fstab->fs_passno;
584
585 md->newfs = NULL;
586
587 TAILQ_INSERT_TAIL(&part_metadata, md, metadata);
588 }
589 }
590
591 static void
get_mount_points(struct partedit_item * items,int nitems)592 get_mount_points(struct partedit_item *items, int nitems)
593 {
594 struct partition_metadata *md;
595 int i;
596
597 for (i = 0; i < nitems; i++) {
598 TAILQ_FOREACH(md, &part_metadata, metadata) {
599 if (md->name != NULL && md->fstab != NULL &&
600 strcmp(md->name, items[i].name) == 0) {
601 items[i].mountpoint = md->fstab->fs_file;
602 break;
603 }
604 }
605 }
606 }
607