]> CyberLeo.Net >> Repos - FreeBSD/stable/9.git/blob - usr.sbin/bsdinstall/partedit/gpart_ops.c
MFC r226083:
[FreeBSD/stable/9.git] / usr.sbin / bsdinstall / partedit / gpart_ops.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 <errno.h>
31 #include <libutil.h>
32 #include <inttypes.h>
33
34 #include <libgeom.h>
35 #include <dialog.h>
36 #include <dlg_keys.h>
37
38 #include "partedit.h"
39
40 #define GPART_FLAGS "x" /* Do not commit changes by default */
41
42 static void
43 gpart_show_error(const char *title, const char *explanation, const char *errstr)
44 {
45         char *errmsg;
46         char message[512];
47         int error;
48
49         if (explanation == NULL)
50                 explanation = "";
51
52         error = strtol(errstr, &errmsg, 0);
53         if (errmsg != errstr) {
54                 while (errmsg[0] == ' ')
55                         errmsg++;
56                 if (errmsg[0] != '\0')
57                         sprintf(message, "%s%s. %s", explanation,
58                             strerror(error), errmsg);
59                 else
60                         sprintf(message, "%s%s", explanation, strerror(error));
61         } else {
62                 sprintf(message, "%s%s", explanation, errmsg);
63         }
64
65         dialog_msgbox(title, message, 0, 0, TRUE);
66 }
67
68 static int
69 scheme_supports_labels(const char *scheme)
70 {
71         if (strcmp(scheme, "APM") == 0)
72                 return (1);
73         if (strcmp(scheme, "GPT") == 0)
74                 return (1);
75         if (strcmp(scheme, "PC98") == 0)
76                 return (1);
77
78         return (0);
79 }
80
81 static void
82 newfs_command(const char *fstype, char *command, int use_default)
83 {
84         if (strcmp(fstype, "freebsd-ufs") == 0) {
85                 int i;
86                 DIALOG_LISTITEM items[] = {
87                         {"UFS1", "UFS Version 1",
88                             "Use version 1 of the UFS file system instead "
89                             "of version 2 (not recommended)", 0 },
90                         {"SU", "Softupdates",
91                             "Enable softupdates (default)", 1 },
92                         {"SUJ", "Softupdates journaling",
93                             "Enable file system journaling (default - "
94                             "turn off for SSDs)", 1 },
95                         {"TRIM", "Enable SSD TRIM support",
96                             "Enable TRIM support, useful on solid-state drives",
97                             0 },
98                 };
99
100                 if (!use_default) {
101                         int choice;
102                         choice = dlg_checklist("UFS Options", "", 0, 0, 0,
103                             sizeof(items)/sizeof(items[0]), items, NULL,
104                             FLAG_CHECK, &i);
105                         if (choice == 1) /* Cancel */
106                                 return;
107                 }
108
109                 strcpy(command, "newfs ");
110                 for (i = 0; i < (int)(sizeof(items)/sizeof(items[0])); i++) {
111                         if (items[i].state == 0)
112                                 continue;
113                         if (strcmp(items[i].name, "UFS1") == 0)
114                                 strcat(command, "-O1 ");
115                         else if (strcmp(items[i].name, "SU") == 0)
116                                 strcat(command, "-U ");
117                         else if (strcmp(items[i].name, "SUJ") == 0)
118                                 strcat(command, "-j ");
119                         else if (strcmp(items[i].name, "TRIM") == 0)
120                                 strcat(command, "-t ");
121                 }
122         } else if (strcmp(fstype, "fat32") == 0 || strcmp(fstype, "efi") == 0) {
123                 int i;
124                 DIALOG_LISTITEM items[] = {
125                         {"FAT32", "FAT Type 32",
126                             "Create a FAT32 filesystem (default)", 1 },
127                         {"FAT16", "FAT Type 16",
128                             "Create a FAT16 filesystem", 0 },
129                         {"FAT12", "FAT Type 12",
130                             "Create a FAT12 filesystem", 0 },
131                 };
132
133                 if (!use_default) {
134                         int choice;
135                         choice = dlg_checklist("FAT Options", "", 0, 0, 0,
136                             sizeof(items)/sizeof(items[0]), items, NULL,
137                             FLAG_RADIO, &i);
138                         if (choice == 1) /* Cancel */
139                                 return;
140                 }
141
142                 strcpy(command, "newfs_msdos ");
143                 for (i = 0; i < (int)(sizeof(items)/sizeof(items[0])); i++) {
144                         if (items[i].state == 0)
145                                 continue;
146                         if (strcmp(items[i].name, "FAT32") == 0)
147                                 strcat(command, "-F 32 ");
148                         else if (strcmp(items[i].name, "FAT16") == 0)
149                                 strcat(command, "-F 16 ");
150                         else if (strcmp(items[i].name, "SUJ") == 0)
151                                 strcat(command, "-F 12 ");
152                 }
153         } else {
154                 if (!use_default)
155                         dialog_msgbox("Error", "No configurable options exist "
156                             "for this filesystem.", 0, 0, TRUE);
157                 command[0] = '\0';
158         }
159 }
160
161 int
162 gpart_partition(const char *lg_name, const char *scheme)
163 {
164         int cancel, choice;
165         struct gctl_req *r;
166         const char *errstr;
167
168         DIALOG_LISTITEM items[] = {
169                 {"APM", "Apple Partition Map",
170                     "Bootable on PowerPC Apple Hardware", 0 },
171                 {"BSD", "BSD Labels",
172                     "Bootable on most x86 systems", 0 },
173                 {"GPT", "GUID Partition Table",
174                     "Bootable on most x86 systems", 0 },
175                 {"MBR", "DOS Partitions",
176                     "Bootable on most x86 systems", 0 },
177                 {"PC98", "NEC PC9801 Partition Table",
178                     "Bootable on NEC PC9801 systems", 0 },
179                 {"VTOC8", "Sun VTOC8 Partition Table",
180                     "Bootable on Sun SPARC systems", 0 },
181         };
182
183 schememenu:
184         if (scheme == NULL) {
185                 dialog_vars.default_item = __DECONST(char *, default_scheme());
186                 cancel = dlg_menu("Partition Scheme",
187                     "Select a partition scheme for this volume:", 0, 0, 0,
188                     sizeof(items) / sizeof(items[0]), items, &choice, NULL);
189                 dialog_vars.default_item = NULL;
190
191                 if (cancel)
192                         return (-1);
193
194                 if (!is_scheme_bootable(items[choice].name)) {
195                         char message[512];
196                         sprintf(message, "This partition scheme (%s) is not "
197                             "bootable on this platform. Are you sure you want "
198                             "to proceed?", items[choice].name);
199                         dialog_vars.defaultno = TRUE;
200                         cancel = dialog_yesno("Warning", message, 0, 0);
201                         dialog_vars.defaultno = FALSE;
202                         if (cancel) /* cancel */
203                                 goto schememenu;
204                 }
205
206                 scheme = items[choice].name;
207         }
208
209         r = gctl_get_handle();
210         gctl_ro_param(r, "class", -1, "PART");
211         gctl_ro_param(r, "arg0", -1, lg_name);
212         gctl_ro_param(r, "flags", -1, GPART_FLAGS);
213         gctl_ro_param(r, "scheme", -1, scheme);
214         gctl_ro_param(r, "verb", -1, "create");
215
216         errstr = gctl_issue(r);
217         if (errstr != NULL && errstr[0] != '\0') {
218                 gpart_show_error("Error", NULL, errstr);
219                 gctl_free(r);
220                 scheme = NULL;
221                 goto schememenu;
222         }
223         gctl_free(r);
224
225         if (bootcode_path(scheme) != NULL)
226                 get_part_metadata(lg_name, 1)->bootcode = 1;
227         return (0);
228 }
229
230 static void
231 gpart_activate(struct gprovider *pp)
232 {
233         struct gconfig *gc;
234         struct gctl_req *r;
235         const char *errstr, *scheme;
236         const char *attribute = NULL;
237         intmax_t idx;
238
239         /*
240          * Some partition schemes need this partition to be marked 'active'
241          * for it to be bootable.
242          */
243         LIST_FOREACH(gc, &pp->lg_geom->lg_config, lg_config) {
244                 if (strcmp(gc->lg_name, "scheme") == 0) {
245                         scheme = gc->lg_val;
246                         break;
247                 }
248         }
249
250         if (strcmp(scheme, "MBR") == 0 || strcmp(scheme, "EBR") == 0 ||
251             strcmp(scheme, "PC98") == 0)
252                 attribute = "active";
253         else
254                 return;
255
256         LIST_FOREACH(gc, &pp->lg_config, lg_config) {
257                 if (strcmp(gc->lg_name, "index") == 0) {
258                         idx = atoi(gc->lg_val);
259                         break;
260                 }
261         }
262
263         r = gctl_get_handle();
264         gctl_ro_param(r, "class", -1, "PART");
265         gctl_ro_param(r, "arg0", -1, pp->lg_geom->lg_name);
266         gctl_ro_param(r, "verb", -1, "set");
267         gctl_ro_param(r, "attrib", -1, attribute);
268         gctl_ro_param(r, "index", sizeof(idx), &idx);
269
270         errstr = gctl_issue(r);
271         if (errstr != NULL && errstr[0] != '\0') 
272                 gpart_show_error("Error", "Error marking partition active:",
273                     errstr);
274         gctl_free(r);
275 }
276
277 static void
278 gpart_bootcode(struct ggeom *gp)
279 {
280         const char *bootcode;
281         struct gconfig *gc;
282         struct gctl_req *r;
283         const char *errstr, *scheme;
284         uint8_t *boot;
285         size_t bootsize, bytes;
286         int bootfd;
287
288         /*
289          * Write default bootcode to the newly partitioned disk, if that
290          * applies on this platform.
291          */
292         LIST_FOREACH(gc, &gp->lg_config, lg_config) {
293                 if (strcmp(gc->lg_name, "scheme") == 0) {
294                         scheme = gc->lg_val;
295                         break;
296                 }
297         }
298
299         bootcode = bootcode_path(scheme);
300         if (bootcode == NULL) 
301                 return;
302
303         bootfd = open(bootcode, O_RDONLY);
304         if (bootfd <= 0) {
305                 dialog_msgbox("Bootcode Error", strerror(errno), 0, 0,
306                     TRUE);
307                 return;
308         }
309                 
310         bootsize = lseek(bootfd, 0, SEEK_END);
311         boot = malloc(bootsize);
312         lseek(bootfd, 0, SEEK_SET);
313         bytes = 0;
314         while (bytes < bootsize)
315                 bytes += read(bootfd, boot + bytes, bootsize - bytes);
316         close(bootfd);
317
318         r = gctl_get_handle();
319         gctl_ro_param(r, "class", -1, "PART");
320         gctl_ro_param(r, "arg0", -1, gp->lg_name);
321         gctl_ro_param(r, "verb", -1, "bootcode");
322         gctl_ro_param(r, "bootcode", bootsize, boot);
323
324         errstr = gctl_issue(r);
325         if (errstr != NULL && errstr[0] != '\0') 
326                 gpart_show_error("Bootcode Error", NULL, errstr);
327         gctl_free(r);
328         free(boot);
329 }
330
331 static void
332 gpart_partcode(struct gprovider *pp)
333 {
334         struct gconfig *gc;
335         const char *scheme;
336         const char *indexstr;
337         char message[255], command[255];
338
339         LIST_FOREACH(gc, &pp->lg_geom->lg_config, lg_config) {
340                 if (strcmp(gc->lg_name, "scheme") == 0) {
341                         scheme = gc->lg_val;
342                         break;
343                 }
344         }
345
346         /* Make sure this partition scheme needs partcode on this platform */
347         if (partcode_path(scheme) == NULL)
348                 return;
349
350         LIST_FOREACH(gc, &pp->lg_config, lg_config) {
351                 if (strcmp(gc->lg_name, "index") == 0) {
352                         indexstr = gc->lg_val;
353                         break;
354                 }
355         }
356
357         /* Shell out to gpart for partcode for now */
358         sprintf(command, "gpart bootcode -p %s -i %s %s",
359             partcode_path(scheme), indexstr, pp->lg_geom->lg_name);
360         if (system(command) != 0) {
361                 sprintf(message, "Error installing partcode on partition %s",
362                     pp->lg_name);
363                 dialog_msgbox("Error", message, 0, 0, TRUE);
364         }
365 }
366
367 void
368 gpart_destroy(struct ggeom *lg_geom)
369 {
370         struct gctl_req *r;
371         struct gprovider *pp;
372         const char *errstr;
373         int force = 1;
374
375         /* Delete all child metadata */
376         LIST_FOREACH(pp, &lg_geom->lg_provider, lg_provider)
377                 gpart_delete(pp);
378
379         /* Revert any local changes to get this geom into a pristine state */
380         r = gctl_get_handle();
381         gctl_ro_param(r, "class", -1, "PART");
382         gctl_ro_param(r, "arg0", -1, lg_geom->lg_name);
383         gctl_ro_param(r, "verb", -1, "undo");
384         gctl_issue(r); /* Ignore errors -- these are non-fatal */
385         gctl_free(r);
386
387         /* Now destroy the geom itself */
388         r = gctl_get_handle();
389         gctl_ro_param(r, "class", -1, "PART");
390         gctl_ro_param(r, "arg0", -1, lg_geom->lg_name);
391         gctl_ro_param(r, "flags", -1, GPART_FLAGS);
392         gctl_ro_param(r, "force", sizeof(force), &force);
393         gctl_ro_param(r, "verb", -1, "destroy");
394         errstr = gctl_issue(r);
395         if (errstr != NULL && errstr[0] != '\0') 
396                 gpart_show_error("Error", NULL, errstr);
397         gctl_free(r);
398
399         /* And any metadata associated with the partition scheme itself */
400         delete_part_metadata(lg_geom->lg_name);
401 }
402
403 void
404 gpart_edit(struct gprovider *pp)
405 {
406         struct gctl_req *r;
407         struct gconfig *gc;
408         struct gconsumer *cp;
409         struct ggeom *geom;
410         const char *errstr, *oldtype, *scheme;
411         struct partition_metadata *md;
412         char sizestr[32];
413         char newfs[64];
414         intmax_t idx;
415         int hadlabel, choice, junk, nitems;
416         unsigned i;
417
418         DIALOG_FORMITEM items[] = {
419                 {0, "Type:", 5, 0, 0, FALSE, "", 11, 0, 12, 15, 0,
420                     FALSE, "Filesystem type (e.g. freebsd-ufs, freebsd-swap)",
421                     FALSE},
422                 {0, "Size:", 5, 1, 0, FALSE, "", 11, 1, 12, 0, 0,
423                     FALSE, "Partition size. Append K, M, G for kilobytes, "
424                     "megabytes or gigabytes.", FALSE},
425                 {0, "Mountpoint:", 11, 2, 0, FALSE, "", 11, 2, 12, 15, 0,
426                     FALSE, "Path at which to mount this partition (leave blank "
427                     "for swap, set to / for root filesystem)", FALSE},
428                 {0, "Label:", 7, 3, 0, FALSE, "", 11, 3, 12, 15, 0, FALSE,
429                     "Partition name. Not all partition schemes support this.",
430                     FALSE},
431         };
432
433         /*
434          * Find the PART geom we are manipulating. This may be a consumer of
435          * this provider, or its parent. Check the consumer case first.
436          */
437         geom = NULL;
438         LIST_FOREACH(cp, &pp->lg_consumers, lg_consumers)
439                 if (strcmp(cp->lg_geom->lg_class->lg_name, "PART") == 0) {
440                         /* Check for zombie geoms, treating them as blank */
441                         scheme = NULL;
442                         LIST_FOREACH(gc, &cp->lg_geom->lg_config, lg_config) {
443                                 if (strcmp(gc->lg_name, "scheme") == 0) {
444                                         scheme = gc->lg_val;
445                                         break;
446                                 }
447                         }
448                         if (scheme == NULL || strcmp(scheme, "(none)") == 0) {
449                                 gpart_partition(cp->lg_geom->lg_name, NULL);
450                                 return;
451                         }
452
453                         /* Destroy the geom and all sub-partitions */
454                         gpart_destroy(cp->lg_geom);
455
456                         /* Now re-partition and return */
457                         gpart_partition(cp->lg_geom->lg_name, NULL);
458                         return;
459                 }
460
461         if (geom == NULL && strcmp(pp->lg_geom->lg_class->lg_name, "PART") == 0)
462                 geom = pp->lg_geom;
463
464         if (geom == NULL) {
465                 /* Disk not partitioned, so partition it */
466                 gpart_partition(pp->lg_name, NULL);
467                 return;
468         }
469
470         LIST_FOREACH(gc, &geom->lg_config, lg_config) {
471                 if (strcmp(gc->lg_name, "scheme") == 0) {
472                         scheme = gc->lg_val;
473                         break;
474                 }
475         }
476
477         nitems = scheme_supports_labels(scheme) ? 4 : 3;
478
479         /* Edit editable parameters of a partition */
480         hadlabel = 0;
481         LIST_FOREACH(gc, &pp->lg_config, lg_config) {
482                 if (strcmp(gc->lg_name, "type") == 0) {
483                         oldtype = gc->lg_val;
484                         items[0].text = gc->lg_val;
485                 }
486                 if (strcmp(gc->lg_name, "label") == 0 && gc->lg_val != NULL) {
487                         hadlabel = 1;
488                         items[3].text = gc->lg_val;
489                 }
490                 if (strcmp(gc->lg_name, "index") == 0)
491                         idx = atoi(gc->lg_val);
492         }
493
494         TAILQ_FOREACH(md, &part_metadata, metadata) {
495                 if (md->name != NULL && strcmp(md->name, pp->lg_name) == 0) {
496                         if (md->fstab != NULL)
497                                 items[2].text = md->fstab->fs_file;
498                         break;
499                 }
500         }
501
502         humanize_number(sizestr, 7, pp->lg_mediasize, "B", HN_AUTOSCALE,
503             HN_NOSPACE | HN_DECIMAL);
504         items[1].text = sizestr;
505
506 editpart:
507         choice = dlg_form("Edit Partition", "", 0, 0, 0, nitems, items, &junk);
508
509         if (choice) /* Cancel pressed */
510                 return;
511
512         /* Check if the label has a / in it */
513         if (strchr(items[3].text, '/') != NULL) {
514                 dialog_msgbox("Error", "Label contains a /, which is not an "
515                     "allowed character.", 0, 0, TRUE);
516                 goto editpart;
517         }
518
519         r = gctl_get_handle();
520         gctl_ro_param(r, "class", -1, "PART");
521         gctl_ro_param(r, "arg0", -1, geom->lg_name);
522         gctl_ro_param(r, "flags", -1, GPART_FLAGS);
523         gctl_ro_param(r, "verb", -1, "modify");
524         gctl_ro_param(r, "index", sizeof(idx), &idx);
525         if (hadlabel || items[3].text[0] != '\0')
526                 gctl_ro_param(r, "label", -1, items[3].text);
527         gctl_ro_param(r, "type", -1, items[0].text);
528         errstr = gctl_issue(r);
529         if (errstr != NULL && errstr[0] != '\0') {
530                 gpart_show_error("Error", NULL, errstr);
531                 gctl_free(r);
532                 goto editpart;
533         }
534         gctl_free(r);
535
536         newfs_command(items[0].text, newfs, 1);
537         set_default_part_metadata(pp->lg_name, scheme, items[0].text,
538             items[2].text, (strcmp(oldtype, items[0].text) != 0) ?
539             newfs : NULL);
540
541         for (i = 0; i < (sizeof(items) / sizeof(items[0])); i++)
542                 if (items[i].text_free)
543                         free(items[i].text);
544 }
545
546 void
547 set_default_part_metadata(const char *name, const char *scheme,
548     const char *type, const char *mountpoint, const char *newfs)
549 {
550         struct partition_metadata *md;
551
552         /* Set part metadata */
553         md = get_part_metadata(name, 1);
554
555         if (newfs) {
556                 if (md->newfs != NULL) {
557                         free(md->newfs);
558                         md->newfs = NULL;
559                 }
560
561                 if (newfs != NULL && newfs[0] != '\0') {
562                         md->newfs = malloc(strlen(newfs) + strlen(" /dev/") +
563                             strlen(name) + 1);
564                         sprintf(md->newfs, "%s /dev/%s", newfs, name);
565                 }
566         }
567
568         if (strcmp(type, "freebsd-swap") == 0)
569                 mountpoint = "none";
570         if (strcmp(type, "freebsd-boot") == 0)
571                 md->bootcode = 1;
572
573         /* VTOC8 needs partcode in UFS partitions */
574         if (strcmp(scheme, "VTOC8") == 0 && strcmp(type, "freebsd-ufs") == 0)
575                 md->bootcode = 1;
576
577         if (mountpoint == NULL || mountpoint[0] == '\0') {
578                 if (md->fstab != NULL) {
579                         free(md->fstab->fs_spec);
580                         free(md->fstab->fs_file);
581                         free(md->fstab->fs_vfstype);
582                         free(md->fstab->fs_mntops);
583                         free(md->fstab->fs_type);
584                         free(md->fstab);
585                         md->fstab = NULL;
586                 }
587         } else {
588                 if (md->fstab == NULL) {
589                         md->fstab = malloc(sizeof(struct fstab));
590                 } else {
591                         free(md->fstab->fs_spec);
592                         free(md->fstab->fs_file);
593                         free(md->fstab->fs_vfstype);
594                         free(md->fstab->fs_mntops);
595                         free(md->fstab->fs_type);
596                 }
597                 md->fstab->fs_spec = malloc(strlen(name) + 6);
598                 sprintf(md->fstab->fs_spec, "/dev/%s", name);
599                 md->fstab->fs_file = strdup(mountpoint);
600                 /* Get VFS from text after freebsd-, if possible */
601                 if (strncmp("freebsd-", type, 8) == 0)
602                         md->fstab->fs_vfstype = strdup(&type[8]);
603                 else if (strcmp("fat32", type) == 0 || strcmp("efi", type) == 0)
604                         md->fstab->fs_vfstype = strdup("msdosfs");
605                 else
606                         md->fstab->fs_vfstype = strdup(type); /* Guess */
607                 if (strcmp(type, "freebsd-swap") == 0) {
608                         md->fstab->fs_type = strdup(FSTAB_SW);
609                         md->fstab->fs_freq = 0;
610                         md->fstab->fs_passno = 0;
611                 } else {
612                         md->fstab->fs_type = strdup(FSTAB_RW);
613                         if (strcmp(mountpoint, "/") == 0) {
614                                 md->fstab->fs_freq = 1;
615                                 md->fstab->fs_passno = 1;
616                         } else {
617                                 md->fstab->fs_freq = 2;
618                                 md->fstab->fs_passno = 2;
619                         }
620                 }
621                 md->fstab->fs_mntops = strdup(md->fstab->fs_type);
622         }
623 }
624
625 static
626 int part_compare(const void *xa, const void *xb)
627 {
628         struct gprovider **a = (struct gprovider **)xa;
629         struct gprovider **b = (struct gprovider **)xb;
630         intmax_t astart, bstart;
631         struct gconfig *gc;
632         
633         astart = bstart = 0;
634         LIST_FOREACH(gc, &(*a)->lg_config, lg_config)
635                 if (strcmp(gc->lg_name, "start") == 0) {
636                         astart = strtoimax(gc->lg_val, NULL, 0);
637                         break;
638                 }
639         LIST_FOREACH(gc, &(*b)->lg_config, lg_config)
640                 if (strcmp(gc->lg_name, "start") == 0) {
641                         bstart = strtoimax(gc->lg_val, NULL, 0);
642                         break;
643                 }
644
645         if (astart < bstart)
646                 return -1;
647         else if (astart > bstart)
648                 return 1;
649         else
650                 return 0;
651 }
652
653 intmax_t
654 gpart_max_free(struct ggeom *geom, intmax_t *npartstart)
655 {
656         struct gconfig *gc;
657         struct gprovider *pp, **providers;
658         intmax_t lastend;
659         intmax_t start, end;
660         intmax_t maxsize, maxstart;
661         intmax_t partstart, partend;
662         int i, nparts;
663
664         /* Now get the maximum free size and free start */
665         start = end = 0;
666         LIST_FOREACH(gc, &geom->lg_config, lg_config) {
667                 if (strcmp(gc->lg_name, "first") == 0)
668                         start = strtoimax(gc->lg_val, NULL, 0);
669                 if (strcmp(gc->lg_name, "last") == 0)
670                         end = strtoimax(gc->lg_val, NULL, 0);
671         }
672
673         i = nparts = 0;
674         LIST_FOREACH(pp, &geom->lg_provider, lg_provider)
675                 nparts++;
676         providers = calloc(nparts, sizeof(providers[0]));
677         LIST_FOREACH(pp, &geom->lg_provider, lg_provider)
678                 providers[i++] = pp;
679         qsort(providers, nparts, sizeof(providers[0]), part_compare);
680
681         lastend = start - 1;
682         maxsize = 0;
683         for (i = 0; i < nparts; i++) {
684                 pp = providers[i];
685
686                 LIST_FOREACH(gc, &pp->lg_config, lg_config) {
687                         if (strcmp(gc->lg_name, "start") == 0)
688                                 partstart = strtoimax(gc->lg_val, NULL, 0);
689                         if (strcmp(gc->lg_name, "end") == 0)
690                                 partend = strtoimax(gc->lg_val, NULL, 0);
691                 }
692
693                 if (partstart - lastend > maxsize) {
694                         maxsize = partstart - lastend - 1;
695                         maxstart = lastend + 1;
696                 }
697
698                 lastend = partend;
699         }
700
701         if (end - lastend > maxsize) {
702                 maxsize = end - lastend - 1;
703                 maxstart = lastend + 1;
704         }
705
706         pp = LIST_FIRST(&geom->lg_consumer)->lg_provider;
707
708         /* Compute beginning of new partition and maximum available space */
709         if (pp->lg_stripesize > 0 &&
710             (maxstart*pp->lg_sectorsize % pp->lg_stripesize) != 0) {
711                 intmax_t offset = (pp->lg_stripesize -
712                     ((maxstart*pp->lg_sectorsize) % pp->lg_stripesize)) /
713                     pp->lg_sectorsize;
714                 maxstart += offset;
715                 maxsize -= offset;
716         }
717
718         if (npartstart != NULL)
719                 *npartstart = maxstart;
720
721         return (maxsize);
722 }
723
724 void
725 gpart_create(struct gprovider *pp, char *default_type, char *default_size,
726      char *default_mountpoint, char **partname, int interactive)
727 {
728         struct gctl_req *r;
729         struct gconfig *gc;
730         struct gconsumer *cp;
731         struct ggeom *geom;
732         const char *errstr, *scheme;
733         char sizestr[32], startstr[32], output[64];
734         char newfs[64], options_fstype[64];
735         intmax_t maxsize, size, sector, firstfree, stripe;
736         uint64_t bytes;
737         int nitems, choice, junk;
738         unsigned i;
739
740         DIALOG_FORMITEM items[] = {
741                 {0, "Type:", 5, 0, 0, FALSE, "freebsd-ufs", 11, 0, 12, 15, 0,
742                     FALSE, "Filesystem type (e.g. freebsd-ufs, freebsd-swap)",
743                     FALSE},
744                 {0, "Size:", 5, 1, 0, FALSE, "", 11, 1, 12, 15, 0,
745                     FALSE, "Partition size. Append K, M, G for kilobytes, "
746                     "megabytes or gigabytes.", FALSE},
747                 {0, "Mountpoint:", 11, 2, 0, FALSE, "", 11, 2, 12, 15, 0,
748                     FALSE, "Path at which to mount partition (blank for "
749                     "swap, / for root filesystem)", FALSE},
750                 {0, "Label:", 7, 3, 0, FALSE, "", 11, 3, 12, 15, 0, FALSE,
751                     "Partition name. Not all partition schemes support this.",
752                     FALSE},
753         };
754
755         if (partname != NULL)
756                 *partname = NULL;
757
758         /* Record sector and stripe sizes */
759         sector = pp->lg_sectorsize;
760         stripe = pp->lg_stripesize;
761
762         /*
763          * Find the PART geom we are manipulating. This may be a consumer of
764          * this provider, or its parent. Check the consumer case first.
765          */
766         geom = NULL;
767         LIST_FOREACH(cp, &pp->lg_consumers, lg_consumers)
768                 if (strcmp(cp->lg_geom->lg_class->lg_name, "PART") == 0) {
769                         geom = cp->lg_geom;
770                         break;
771                 }
772
773         if (geom == NULL && strcmp(pp->lg_geom->lg_class->lg_name, "PART") == 0)
774                 geom = pp->lg_geom;
775
776         /* Now get the partition scheme */
777         scheme = NULL;
778         if (geom != NULL) {
779                 LIST_FOREACH(gc, &geom->lg_config, lg_config) 
780                         if (strcmp(gc->lg_name, "scheme") == 0)
781                                 scheme = gc->lg_val;
782         }
783
784         if (geom == NULL || scheme == NULL || strcmp(scheme, "(none)") == 0) {
785                 if (gpart_partition(pp->lg_name, NULL) == 0)
786                         dialog_msgbox("",
787                             "The partition table has been successfully created."
788                             " Please press Create again to create partitions.",
789                             0, 0, TRUE);
790
791                 return;
792         }
793
794         /*
795          * If we still don't have a geom, either the user has
796          * canceled partitioning or there has been an error which has already
797          * been displayed, so bail.
798          */
799         if (geom == NULL)
800                 return;
801
802         maxsize = size = gpart_max_free(geom, &firstfree);
803         if (size <= 0) {
804                 dialog_msgbox("Error", "No free space left on device.", 0, 0,
805                     TRUE);
806                 return;
807         }
808
809         humanize_number(sizestr, 7, size*sector, "B", HN_AUTOSCALE,
810             HN_NOSPACE | HN_DECIMAL);
811         items[1].text = sizestr;
812
813         /* Special-case the MBR default type for nested partitions */
814         if (strcmp(scheme, "MBR") == 0 || strcmp(scheme, "PC98") == 0) {
815                 items[0].text = "freebsd";
816                 items[0].help = "Filesystem type (e.g. freebsd, fat32)";
817         }
818
819         nitems = scheme_supports_labels(scheme) ? 4 : 3;
820
821         if (default_type != NULL)
822                 items[0].text = default_type;
823         if (default_size != NULL)
824                 items[1].text = default_size;
825         if (default_mountpoint != NULL)
826                 items[2].text = default_mountpoint;
827
828         /* Default options */
829         strncpy(options_fstype, items[0].text,
830             sizeof(options_fstype));
831         newfs_command(options_fstype, newfs, 1);
832 addpartform:
833         if (interactive) {
834                 dialog_vars.extra_label = "Options";
835                 dialog_vars.extra_button = TRUE;
836                 choice = dlg_form("Add Partition", "", 0, 0, 0, nitems,
837                     items, &junk);
838                 dialog_vars.extra_button = FALSE;
839                 switch (choice) {
840                 case 0: /* OK */
841                         break;
842                 case 1: /* Cancel */
843                         return;
844                 case 3: /* Options */
845                         strncpy(options_fstype, items[0].text,
846                             sizeof(options_fstype));
847                         newfs_command(options_fstype, newfs, 0);
848                         goto addpartform;
849                 }
850         }
851
852         /*
853          * If the user changed the fs type after specifying options, undo
854          * their choices in favor of the new filesystem's defaults.
855          */
856         if (strcmp(options_fstype, items[0].text) != 0) {
857                 strncpy(options_fstype, items[0].text, sizeof(options_fstype));
858                 newfs_command(options_fstype, newfs, 1);
859         }
860
861         size = maxsize;
862         if (strlen(items[1].text) > 0) {
863                 if (expand_number(items[1].text, &bytes) != 0) {
864                         char error[512];
865
866                         sprintf(error, "Invalid size: %s\n", strerror(errno));
867                         dialog_msgbox("Error", error, 0, 0, TRUE);
868                         goto addpartform;
869                 }
870                 size = MIN((intmax_t)(bytes/sector), maxsize);
871         }
872
873         /* Check if the label has a / in it */
874         if (strchr(items[3].text, '/') != NULL) {
875                 dialog_msgbox("Error", "Label contains a /, which is not an "
876                     "allowed character.", 0, 0, TRUE);
877                 goto addpartform;
878         }
879
880         /* Warn if no mountpoint set */
881         if (strcmp(items[0].text, "freebsd-ufs") == 0 &&
882             items[2].text[0] != '/') {
883                 dialog_vars.defaultno = TRUE;
884                 choice = dialog_yesno("Warning",
885                     "This partition does not have a valid mountpoint "
886                     "(for the partition from which you intend to boot the "
887                     "operating system, the mountpoint should be /). Are you "
888                     "sure you want to continue?"
889                 , 0, 0);
890                 dialog_vars.defaultno = FALSE;
891                 if (choice == 1) /* cancel */
892                         goto addpartform;
893         }
894
895         /* If this is the root partition, check that this scheme is bootable */
896         if (strcmp(items[2].text, "/") == 0 && !is_scheme_bootable(scheme)) {
897                 char message[512];
898                 sprintf(message, "This partition scheme (%s) is not bootable "
899                     "on this platform. Are you sure you want to proceed?",
900                     scheme);
901                 dialog_vars.defaultno = TRUE;
902                 choice = dialog_yesno("Warning", message, 0, 0);
903                 dialog_vars.defaultno = FALSE;
904                 if (choice == 1) /* cancel */
905                         goto addpartform;
906         }
907
908         /*
909          * If this is the root partition, and we need a boot partition, ask
910          * the user to add one.
911          */
912         if (strcmp(items[2].text, "/") == 0 && bootpart_size(scheme) > 0) {
913                 if (interactive)
914                         choice = dialog_yesno("Boot Partition",
915                             "This partition scheme requires a boot partition "
916                             "for the disk to be bootable. Would you like to "
917                             "make one now?", 0, 0);
918                 else
919                         choice = 0;
920
921                 if (choice == 0) { /* yes */
922                         r = gctl_get_handle();
923                         gctl_ro_param(r, "class", -1, "PART");
924                         gctl_ro_param(r, "arg0", -1, geom->lg_name);
925                         gctl_ro_param(r, "flags", -1, GPART_FLAGS);
926                         gctl_ro_param(r, "verb", -1, "add");
927                         gctl_ro_param(r, "type", -1, "freebsd-boot");
928                         snprintf(sizestr, sizeof(sizestr), "%jd",
929                             bootpart_size(scheme) / sector);
930                         gctl_ro_param(r, "size", -1, sizestr);
931                         snprintf(startstr, sizeof(startstr), "%jd", firstfree);
932                         gctl_ro_param(r, "start", -1, startstr);
933                         gctl_rw_param(r, "output", sizeof(output), output);
934                         errstr = gctl_issue(r);
935                         if (errstr != NULL && errstr[0] != '\0') 
936                                 gpart_show_error("Error", NULL, errstr);
937                         gctl_free(r);
938
939                         get_part_metadata(strtok(output, " "), 1)->bootcode = 1;
940
941                         /* Now adjust the part we are really adding forward */
942                         firstfree += bootpart_size(scheme) / sector;
943                         size -= (bootpart_size(scheme) + stripe)/sector;
944                         if (stripe > 0 && (firstfree*sector % stripe) != 0) 
945                                 firstfree += (stripe - ((firstfree*sector) %
946                                     stripe)) / sector;
947                 }
948         }
949         
950         r = gctl_get_handle();
951         gctl_ro_param(r, "class", -1, "PART");
952         gctl_ro_param(r, "arg0", -1, geom->lg_name);
953         gctl_ro_param(r, "flags", -1, GPART_FLAGS);
954         gctl_ro_param(r, "verb", -1, "add");
955
956         gctl_ro_param(r, "type", -1, items[0].text);
957         snprintf(sizestr, sizeof(sizestr), "%jd", size);
958         gctl_ro_param(r, "size", -1, sizestr);
959         snprintf(startstr, sizeof(startstr), "%jd", firstfree);
960         gctl_ro_param(r, "start", -1, startstr);
961         if (items[3].text[0] != '\0')
962                 gctl_ro_param(r, "label", -1, items[3].text);
963         gctl_rw_param(r, "output", sizeof(output), output);
964
965         errstr = gctl_issue(r);
966         if (errstr != NULL && errstr[0] != '\0') {
967                 gpart_show_error("Error", NULL, errstr);
968                 gctl_free(r);
969                 goto addpartform;
970         }
971
972         if (strcmp(items[0].text, "freebsd-boot") == 0)
973                 get_part_metadata(strtok(output, " "), 1)->bootcode = 1;
974         else if (strcmp(items[0].text, "freebsd") == 0)
975                 gpart_partition(strtok(output, " "), "BSD");
976         else
977                 set_default_part_metadata(strtok(output, " "), scheme,
978                     items[0].text, items[2].text, newfs);
979
980         for (i = 0; i < (sizeof(items) / sizeof(items[0])); i++)
981                 if (items[i].text_free)
982                         free(items[i].text);
983         gctl_free(r);
984
985         if (partname != NULL)
986                 *partname = strdup(strtok(output, " "));
987 }
988         
989 void
990 gpart_delete(struct gprovider *pp)
991 {
992         struct gconfig *gc;
993         struct ggeom *geom;
994         struct gconsumer *cp;
995         struct gctl_req *r;
996         const char *errstr;
997         intmax_t idx;
998         int is_partition;
999
1000         /* Is it a partition? */
1001         is_partition = (strcmp(pp->lg_geom->lg_class->lg_name, "PART") == 0);
1002
1003         /* Find out if this is the root of a gpart geom */
1004         geom = NULL;
1005         LIST_FOREACH(cp, &pp->lg_consumers, lg_consumers)
1006                 if (strcmp(cp->lg_geom->lg_class->lg_name, "PART") == 0) {
1007                         geom = cp->lg_geom;
1008                         break;
1009                 }
1010
1011         /* If so, destroy all children */
1012         if (geom != NULL) {
1013                 gpart_destroy(geom);
1014
1015                 /* If this is a partition, revert it, so it can be deleted */
1016                 if (is_partition) {
1017                         r = gctl_get_handle();
1018                         gctl_ro_param(r, "class", -1, "PART");
1019                         gctl_ro_param(r, "arg0", -1, geom->lg_name);
1020                         gctl_ro_param(r, "verb", -1, "undo");
1021                         gctl_issue(r); /* Ignore non-fatal errors */
1022                         gctl_free(r);
1023                 }
1024         }
1025
1026         /*
1027          * If this is not a partition, see if that is a problem, complain if
1028          * necessary, and return always, since we need not do anything further,
1029          * error or no.
1030          */
1031         if (!is_partition) {
1032                 if (geom == NULL)
1033                         dialog_msgbox("Error",
1034                             "Only partitions can be deleted.", 0, 0, TRUE);
1035                 return;
1036         }
1037
1038         r = gctl_get_handle();
1039         gctl_ro_param(r, "class", -1, pp->lg_geom->lg_class->lg_name);
1040         gctl_ro_param(r, "arg0", -1, pp->lg_geom->lg_name);
1041         gctl_ro_param(r, "flags", -1, GPART_FLAGS);
1042         gctl_ro_param(r, "verb", -1, "delete");
1043
1044         LIST_FOREACH(gc, &pp->lg_config, lg_config) {
1045                 if (strcmp(gc->lg_name, "index") == 0) {
1046                         idx = atoi(gc->lg_val);
1047                         gctl_ro_param(r, "index", sizeof(idx), &idx);
1048                         break;
1049                 }
1050         }
1051
1052         errstr = gctl_issue(r);
1053         if (errstr != NULL && errstr[0] != '\0') {
1054                 gpart_show_error("Error", NULL, errstr);
1055                 gctl_free(r);
1056                 return;
1057         }
1058
1059         gctl_free(r);
1060
1061         delete_part_metadata(pp->lg_name);
1062 }
1063
1064 void
1065 gpart_revert_all(struct gmesh *mesh)
1066 {
1067         struct gclass *classp;
1068         struct gconfig *gc;
1069         struct ggeom *gp;
1070         struct gctl_req *r;
1071         const char *errstr;
1072         const char *modified;
1073
1074         LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
1075                 if (strcmp(classp->lg_name, "PART") == 0)
1076                         break;
1077         }
1078
1079         if (strcmp(classp->lg_name, "PART") != 0) {
1080                 dialog_msgbox("Error", "gpart not found!", 0, 0, TRUE);
1081                 return;
1082         }
1083
1084         LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
1085                 modified = "true"; /* XXX: If we don't know (kernel too old),
1086                                     * assume there are modifications. */
1087                 LIST_FOREACH(gc, &gp->lg_config, lg_config) {
1088                         if (strcmp(gc->lg_name, "modified") == 0) {
1089                                 modified = gc->lg_val;
1090                                 break;
1091                         }
1092                 }
1093
1094                 if (strcmp(modified, "false") == 0)
1095                         continue;
1096
1097                 r = gctl_get_handle();
1098                 gctl_ro_param(r, "class", -1, "PART");
1099                 gctl_ro_param(r, "arg0", -1, gp->lg_name);
1100                 gctl_ro_param(r, "verb", -1, "undo");
1101
1102                 errstr = gctl_issue(r);
1103                 if (errstr != NULL && errstr[0] != '\0') 
1104                         gpart_show_error("Error", NULL, errstr);
1105                 gctl_free(r);
1106         }
1107 }
1108
1109 void
1110 gpart_commit(struct gmesh *mesh)
1111 {
1112         struct partition_metadata *md;
1113         struct gclass *classp;
1114         struct ggeom *gp;
1115         struct gconfig *gc;
1116         struct gconsumer *cp;
1117         struct gprovider *pp;
1118         struct gctl_req *r;
1119         const char *errstr;
1120         const char *modified;
1121
1122         LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
1123                 if (strcmp(classp->lg_name, "PART") == 0)
1124                         break;
1125         }
1126
1127         if (strcmp(classp->lg_name, "PART") != 0) {
1128                 dialog_msgbox("Error", "gpart not found!", 0, 0, TRUE);
1129                 return;
1130         }
1131
1132         LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
1133                 modified = "true"; /* XXX: If we don't know (kernel too old),
1134                                     * assume there are modifications. */
1135                 LIST_FOREACH(gc, &gp->lg_config, lg_config) {
1136                         if (strcmp(gc->lg_name, "modified") == 0) {
1137                                 modified = gc->lg_val;
1138                                 break;
1139                         }
1140                 }
1141
1142                 if (strcmp(modified, "false") == 0)
1143                         continue;
1144
1145                 /* Add bootcode if necessary, before the commit */
1146                 md = get_part_metadata(gp->lg_name, 0);
1147                 if (md != NULL && md->bootcode)
1148                         gpart_bootcode(gp);
1149
1150                 /* Now install partcode on its partitions, if necessary */
1151                 LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
1152                         md = get_part_metadata(pp->lg_name, 0);
1153                         if (md == NULL || !md->bootcode)
1154                                 continue;
1155                 
1156                         /* Mark this partition active if that's required */
1157                         gpart_activate(pp);
1158
1159                         /* Check if the partition has sub-partitions */
1160                         LIST_FOREACH(cp, &pp->lg_consumers, lg_consumers)
1161                                 if (strcmp(cp->lg_geom->lg_class->lg_name,
1162                                     "PART") == 0)
1163                                         break;
1164
1165                         if (cp == NULL) /* No sub-partitions */
1166                                 gpart_partcode(pp);
1167                 }
1168
1169                 r = gctl_get_handle();
1170                 gctl_ro_param(r, "class", -1, "PART");
1171                 gctl_ro_param(r, "arg0", -1, gp->lg_name);
1172                 gctl_ro_param(r, "verb", -1, "commit");
1173
1174                 errstr = gctl_issue(r);
1175                 if (errstr != NULL && errstr[0] != '\0') 
1176                         gpart_show_error("Error", NULL, errstr);
1177                 gctl_free(r);
1178         }
1179 }
1180