xref: /freebsd/usr.sbin/bsdinstall/partedit/partedit.c (revision 5dcd9c10612684d1c823670cbb5b4715028784e7)
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 
45 static int apply_changes(struct gmesh *mesh);
46 static struct partedit_item *read_geom_mesh(struct gmesh *mesh, int *nitems);
47 static void add_geom_children(struct ggeom *gp, int recurse,
48     struct partedit_item **items, int *nitems);
49 static void init_fstab_metadata(void);
50 static void get_mount_points(struct partedit_item *items, int nitems);
51 static int validate_setup(void);
52 
53 int
54 main(int argc, const char **argv) {
55 	struct partition_metadata *md;
56 	const char *prompt;
57 	struct partedit_item *items;
58 	struct gmesh mesh;
59 	int i, op, nitems, nscroll;
60 	int error;
61 
62 	TAILQ_INIT(&part_metadata);
63 
64 	init_fstab_metadata();
65 
66 	init_dialog(stdin, stdout);
67 	if (strcmp(basename(argv[0]), "sade") != 0)
68 		dialog_vars.backtitle = __DECONST(char *, "FreeBSD Installer");
69 	dialog_vars.item_help = TRUE;
70 	nscroll = i = 0;
71 
72 	if (strcmp(basename(argv[0]), "autopart") == 0) { /* Guided */
73 		prompt = "Please review the disk setup. When complete, press "
74 		    "the Exit button.";
75 		part_wizard();
76 	} else {
77 		prompt = "Create partitions for FreeBSD. No changes will be "
78 		    "made until you select Exit.";
79 	}
80 
81 	/* Show the part editor either immediately, or to confirm wizard */
82 	while (1) {
83 		error = geom_gettree(&mesh);
84 		items = read_geom_mesh(&mesh, &nitems);
85 		get_mount_points(items, nitems);
86 		dlg_clear();
87 		dlg_put_backtitle();
88 
89 		if (i >= nitems)
90 			i = nitems - 1;
91 		op = diskeditor_show("Partition Editor", prompt,
92 		    items, nitems, &i, &nscroll);
93 
94 		switch (op) {
95 		case 0: /* Create */
96 			gpart_create((struct gprovider *)(items[i].cookie),
97 			    NULL, NULL, NULL, NULL, 1);
98 			break;
99 		case 1: /* Delete */
100 			gpart_delete((struct gprovider *)(items[i].cookie));
101 			break;
102 		case 2: /* Modify */
103 			gpart_edit((struct gprovider *)(items[i].cookie));
104 			break;
105 		case 3: /* Revert */
106 			gpart_revert_all(&mesh);
107 			while ((md = TAILQ_FIRST(&part_metadata)) != NULL) {
108 				if (md->fstab != NULL) {
109 					free(md->fstab->fs_spec);
110 					free(md->fstab->fs_file);
111 					free(md->fstab->fs_vfstype);
112 					free(md->fstab->fs_mntops);
113 					free(md->fstab->fs_type);
114 					free(md->fstab);
115 				}
116 				if (md->newfs != NULL)
117 					free(md->newfs);
118 				free(md->name);
119 
120 				TAILQ_REMOVE(&part_metadata, md, metadata);
121 				free(md);
122 			}
123 			init_fstab_metadata();
124 			break;
125 		case 4: /* Auto */
126 			part_wizard();
127 			break;
128 		}
129 
130 		error = 0;
131 		if (op == 5 && validate_setup()) { /* Finished */
132 			dialog_vars.extra_button = TRUE;
133 			dialog_vars.extra_label =
134 			    __DECONST(char *, "Abort");
135 			dialog_vars.ok_label = __DECONST(char *, "Save");
136 			op = dialog_yesno("Confirmation", "Your changes will "
137 			    "now be written to disk. If you have chosen to "
138 			    "overwrite existing data, it will be PERMANENTLY "
139 			    "ERASED. Are you sure you want to proceed?", 0, 0);
140 			dialog_vars.extra_button = FALSE;
141 			dialog_vars.ok_label = NULL;
142 
143 			if (op == 0) { /* Save */
144 				error = apply_changes(&mesh);
145 				break;
146 			} else if (op == 3) { /* Don't save => Quit */
147 				gpart_revert_all(&mesh);
148 				error =	-1;
149 				break;
150 			}
151 		}
152 
153 		geom_deletetree(&mesh);
154 		free(items);
155 	}
156 
157 
158 	geom_deletetree(&mesh);
159 	free(items);
160 	end_dialog();
161 
162 	return (error);
163 }
164 
165 struct partition_metadata *
166 get_part_metadata(const char *name, int create)
167 {
168 	struct partition_metadata *md;
169 
170 	TAILQ_FOREACH(md, &part_metadata, metadata)
171 		if (md->name != NULL && strcmp(md->name, name) == 0)
172 			break;
173 
174 	if (md == NULL && create) {
175 		md = calloc(1, sizeof(*md));
176 		md->name = strdup(name);
177 		TAILQ_INSERT_TAIL(&part_metadata, md, metadata);
178 	}
179 
180 	return (md);
181 }
182 
183 void
184 delete_part_metadata(const char *name) {
185 	struct partition_metadata *md;
186 
187 	TAILQ_FOREACH(md, &part_metadata, metadata) {
188 		if (md->name != NULL && strcmp(md->name, name) == 0) {
189 			if (md->fstab != NULL) {
190 				free(md->fstab->fs_spec);
191 				free(md->fstab->fs_file);
192 				free(md->fstab->fs_vfstype);
193 				free(md->fstab->fs_mntops);
194 				free(md->fstab->fs_type);
195 				free(md->fstab);
196 			}
197 			if (md->newfs != NULL)
198 				free(md->newfs);
199 			free(md->name);
200 
201 			TAILQ_REMOVE(&part_metadata, md, metadata);
202 			free(md);
203 			break;
204 		}
205 	}
206 }
207 
208 static int
209 validate_setup(void)
210 {
211 	struct partition_metadata *md;
212 	int root_found = FALSE;
213 
214 	TAILQ_FOREACH(md, &part_metadata, metadata) {
215 		if (md->fstab != NULL && strcmp(md->fstab->fs_file, "/") == 0)
216 			root_found = TRUE;
217 
218 		/* XXX: Check for duplicate mountpoints */
219 	}
220 
221 	if (!root_found) {
222 		dialog_msgbox("Error", "No root partition was found. "
223 		    "The root FreeBSD partition must have a mountpoint of '/'.",
224 		0, 0, TRUE);
225 		return (FALSE);
226 	}
227 
228 	return (TRUE);
229 }
230 
231 static int
232 apply_changes(struct gmesh *mesh)
233 {
234 	struct partition_metadata *md;
235 	char message[512];
236 	int i, nitems, error;
237 	const char **items;
238 	const char *fstab_path;
239 	FILE *fstab;
240 
241 	nitems = 1; /* Partition table changes */
242 	TAILQ_FOREACH(md, &part_metadata, metadata) {
243 		if (md->newfs != NULL)
244 			nitems++;
245 	}
246 	items = calloc(nitems * 2, sizeof(const char *));
247 	items[0] = "Writing partition tables";
248 	items[1] = "7"; /* In progress */
249 	i = 1;
250 	TAILQ_FOREACH(md, &part_metadata, metadata) {
251 		if (md->newfs != NULL) {
252 			char *item;
253 			item = malloc(255);
254 			sprintf(item, "Initializing %s", md->name);
255 			items[i*2] = item;
256 			items[i*2 + 1] = "Pending";
257 			i++;
258 		}
259 	}
260 
261 	i = 0;
262 	dialog_mixedgauge("Initializing",
263 	    "Initializing file systems. Please wait.", 0, 0, i*100/nitems,
264 	    nitems, __DECONST(char **, items));
265 	gpart_commit(mesh);
266 	items[i*2 + 1] = "3";
267 	i++;
268 
269 	if (getenv("BSDINSTALL_LOG") == NULL)
270 		setenv("BSDINSTALL_LOG", "/dev/null", 1);
271 
272 	TAILQ_FOREACH(md, &part_metadata, metadata) {
273 		if (md->newfs != NULL) {
274 			items[i*2 + 1] = "7"; /* In progress */
275 			dialog_mixedgauge("Initializing",
276 			    "Initializing file systems. Please wait.", 0, 0,
277 			    i*100/nitems, nitems, __DECONST(char **, items));
278 			sprintf(message, "(echo %s; %s) >>%s 2>>%s",
279 			    md->newfs, md->newfs, getenv("BSDINSTALL_LOG"),
280 			    getenv("BSDINSTALL_LOG"));
281 			error = system(message);
282 			items[i*2 + 1] = (error == 0) ? "3" : "1";
283 			i++;
284 		}
285 	}
286 	dialog_mixedgauge("Initializing",
287 	    "Initializing file systems. Please wait.", 0, 0,
288 	    i*100/nitems, nitems, __DECONST(char **, items));
289 
290 	for (i = 1; i < nitems; i++)
291 		free(__DECONST(char *, items[i*2]));
292 	free(items);
293 
294 	if (getenv("PATH_FSTAB") != NULL)
295 		fstab_path = getenv("PATH_FSTAB");
296 	else
297 		fstab_path = "/etc/fstab";
298 	fstab = fopen(fstab_path, "w+");
299 	if (fstab == NULL) {
300 		sprintf(message, "Cannot open fstab file %s for writing (%s)\n",
301 		    getenv("PATH_FSTAB"), strerror(errno));
302 		dialog_msgbox("Error", message, 0, 0, TRUE);
303 		return (-1);
304 	}
305 	fprintf(fstab, "# Device\tMountpoint\tFStype\tOptions\tDump\tPass#\n");
306 	TAILQ_FOREACH(md, &part_metadata, metadata) {
307 		if (md->fstab != NULL)
308 			fprintf(fstab, "%s\t%s\t%s\t%s\t%d\t%d\n",
309 			    md->fstab->fs_spec, md->fstab->fs_file,
310 			    md->fstab->fs_vfstype, md->fstab->fs_mntops,
311 			    md->fstab->fs_freq, md->fstab->fs_passno);
312 	}
313 	fclose(fstab);
314 
315 	return (0);
316 }
317 
318 static struct partedit_item *
319 read_geom_mesh(struct gmesh *mesh, int *nitems) {
320 	struct gclass *classp;
321 	struct ggeom *gp;
322 	struct partedit_item *items;
323 
324 	*nitems = 0;
325 	items = NULL;
326 
327 	/*
328 	 * Build the device table. First add all disks (and CDs).
329 	 */
330 
331 	LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
332 		if (strcmp(classp->lg_name, "DISK") != 0 &&
333 		     strcmp(classp->lg_name, "MD") != 0)
334 			continue;
335 
336 		/* Now recurse into all children */
337 		LIST_FOREACH(gp, &classp->lg_geom, lg_geom)
338 			add_geom_children(gp, 0, &items, nitems);
339 	}
340 
341 	return (items);
342 }
343 
344 static void
345 add_geom_children(struct ggeom *gp, int recurse, struct partedit_item **items,
346     int *nitems) {
347 	struct gconsumer *cp;
348 	struct gprovider *pp;
349 	struct gconfig *gc;
350 
351 	if (strcmp(gp->lg_class->lg_name, "PART") == 0 &&
352 	    !LIST_EMPTY(&gp->lg_config)) {
353 		LIST_FOREACH(gc, &gp->lg_config, lg_config) {
354 			if (strcmp(gc->lg_name, "scheme") == 0)
355 				(*items)[*nitems-1].type = gc->lg_val;
356 		}
357 	}
358 
359 	if (LIST_EMPTY(&gp->lg_provider))
360 		return;
361 
362 	LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
363 		if (strcmp(gp->lg_class->lg_name, "LABEL") == 0)
364 			continue;
365 
366 		/* Skip WORM media */
367 		if (strncmp(pp->lg_name, "cd", 2) == 0)
368 			continue;
369 
370 		*items = realloc(*items,
371 		    (*nitems+1)*sizeof(struct partedit_item));
372 		(*items)[*nitems].indentation = recurse;
373 		(*items)[*nitems].name = pp->lg_name;
374 		(*items)[*nitems].size = pp->lg_mediasize;
375 		(*items)[*nitems].mountpoint = NULL;
376 		(*items)[*nitems].type = "";
377 		(*items)[*nitems].cookie = pp;
378 
379 		LIST_FOREACH(gc, &pp->lg_config, lg_config) {
380 			if (strcmp(gc->lg_name, "type") == 0)
381 				(*items)[*nitems].type = gc->lg_val;
382 		}
383 
384 		/* Skip swap-backed MD devices */
385 		if (strcmp(gp->lg_class->lg_name, "MD") == 0 &&
386 		    strcmp((*items)[*nitems].type, "swap") == 0)
387 			continue;
388 
389 		(*nitems)++;
390 
391 		LIST_FOREACH(cp, &pp->lg_consumers, lg_consumers)
392 			add_geom_children(cp->lg_geom, recurse+1, items,
393 			    nitems);
394 
395 		/* Only use first provider for acd */
396 		if (strcmp(gp->lg_class->lg_name, "ACD") == 0)
397 			break;
398 	}
399 }
400 
401 static void
402 init_fstab_metadata(void)
403 {
404 	struct fstab *fstab;
405 	struct partition_metadata *md;
406 
407 	setfsent();
408 	while ((fstab = getfsent()) != NULL) {
409 		md = calloc(1, sizeof(struct partition_metadata));
410 
411 		md->name = NULL;
412 		if (strncmp(fstab->fs_spec, "/dev/", 5) == 0)
413 			md->name = strdup(&fstab->fs_spec[5]);
414 
415 		md->fstab = malloc(sizeof(struct fstab));
416 		md->fstab->fs_spec = strdup(fstab->fs_spec);
417 		md->fstab->fs_file = strdup(fstab->fs_file);
418 		md->fstab->fs_vfstype = strdup(fstab->fs_vfstype);
419 		md->fstab->fs_mntops = strdup(fstab->fs_mntops);
420 		md->fstab->fs_type = strdup(fstab->fs_type);
421 		md->fstab->fs_freq = fstab->fs_freq;
422 		md->fstab->fs_passno = fstab->fs_passno;
423 
424 		md->newfs = NULL;
425 
426 		TAILQ_INSERT_TAIL(&part_metadata, md, metadata);
427 	}
428 }
429 
430 static void
431 get_mount_points(struct partedit_item *items, int nitems)
432 {
433 	struct partition_metadata *md;
434 	int i;
435 
436 	for (i = 0; i < nitems; i++) {
437 		TAILQ_FOREACH(md, &part_metadata, metadata) {
438 			if (md->name != NULL && md->fstab != NULL &&
439 			    strcmp(md->name, items[i].name) == 0) {
440 				items[i].mountpoint = md->fstab->fs_file;
441 				break;
442 			}
443 		}
444 	}
445 }
446