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