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