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