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