1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 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 29 #include <sys/param.h> 30 #include <sys/sysctl.h> 31 32 #include <errno.h> 33 #include <inttypes.h> 34 #include <libutil.h> 35 #include <stdio.h> 36 #include <stdlib.h> 37 #include <string.h> 38 #include <unistd.h> 39 40 #include <libgeom.h> 41 #include <bsddialog.h> 42 43 #include "partedit.h" 44 45 #define MIN_FREE_SPACE (1023*1024*1024) /* Just under 1 GB */ 46 47 static char *wizard_partition(struct gmesh *mesh, const char *disk); 48 49 /* 50 * Determine default swap (partition) size in bytes for a given amount of free 51 * disk space in bytes. The algorithm should likely be revisited in light of 52 * contemporary memory and disk sizes. 53 */ 54 static intmax_t 55 swap_size(intmax_t available) 56 { 57 intmax_t swapsize; 58 unsigned long swap_maxpages; 59 size_t sz; 60 61 swapsize = MIN(available/20, 4*1024*1024*1024LL); 62 sz = sizeof(swap_maxpages); 63 if (sysctlbyname("vm.swap_maxpages", &swap_maxpages, &sz, NULL, 0) == 0) 64 swapsize = MIN(swapsize, (intmax_t)swap_maxpages * getpagesize()); 65 66 return (swapsize); 67 } 68 69 int 70 part_wizard(const char *fsreq) 71 { 72 char *disk, *schemeroot; 73 const char *fstype; 74 struct gmesh mesh; 75 int error; 76 struct bsddialog_conf conf; 77 78 bsddialog_initconf(&conf); 79 80 if (fsreq != NULL) 81 fstype = fsreq; 82 else 83 fstype = "ufs"; 84 85 startwizard: 86 error = geom_gettree(&mesh); 87 if (error != 0) 88 return (1); 89 90 bsddialog_backtitle(&conf, OSNAME " Installer"); 91 disk = boot_disk_select(&mesh); 92 if (disk == NULL) { 93 geom_deletetree(&mesh); 94 return (1); 95 } 96 97 bsddialog_clear(0); 98 bsddialog_backtitle(&conf, OSNAME " Installer"); 99 schemeroot = wizard_partition(&mesh, disk); 100 free(disk); 101 geom_deletetree(&mesh); 102 if (schemeroot == NULL) 103 return (1); 104 105 bsddialog_clear(0); 106 bsddialog_backtitle(&conf, OSNAME " Installer"); 107 error = geom_gettree(&mesh); 108 if (error != 0) { 109 free(schemeroot); 110 return (1); 111 } 112 113 error = wizard_makeparts(&mesh, schemeroot, fstype, 1); 114 free(schemeroot); 115 geom_deletetree(&mesh); 116 if (error) 117 goto startwizard; 118 119 return (0); 120 } 121 122 char * 123 boot_disk_select(struct gmesh *mesh) 124 { 125 struct gclass *classp; 126 struct gconfig *gc; 127 struct ggeom *gp; 128 struct gprovider *pp; 129 struct bsddialog_menuitem *disks = NULL; 130 const char *type, *desc; 131 char diskdesc[512]; 132 char *chosen; 133 int i, button, fd, selected, n = 0; 134 struct bsddialog_conf conf; 135 136 bsddialog_initconf(&conf); 137 138 LIST_FOREACH(classp, &mesh->lg_class, lg_class) { 139 if (strcmp(classp->lg_name, "DISK") != 0 && 140 strcmp(classp->lg_name, "RAID") != 0 && 141 strcmp(classp->lg_name, "MD") != 0) 142 continue; 143 144 LIST_FOREACH(gp, &classp->lg_geom, lg_geom) { 145 if (LIST_EMPTY(&gp->lg_provider)) 146 continue; 147 148 LIST_FOREACH(pp, &gp->lg_provider, lg_provider) { 149 desc = type = NULL; 150 LIST_FOREACH(gc, &pp->lg_config, lg_config) { 151 if (strcmp(gc->lg_name, "type") == 0) 152 type = gc->lg_val; 153 if (strcmp(gc->lg_name, "descr") == 0) 154 desc = gc->lg_val; 155 } 156 157 /* Skip swap-backed md and WORM devices */ 158 if (strcmp(classp->lg_name, "MD") == 0 && 159 type != NULL && strcmp(type, "swap") == 0) 160 continue; 161 if (strncmp(pp->lg_name, "cd", 2) == 0) 162 continue; 163 /* 164 * Check if the disk is available to be opened for 165 * write operations, it helps prevent the USB 166 * stick used to boot from being listed as an option 167 */ 168 fd = g_open(pp->lg_name, 1); 169 if (fd == -1) { 170 continue; 171 } 172 g_close(fd); 173 174 disks = realloc(disks, (++n)*sizeof(disks[0])); 175 disks[n-1].name = pp->lg_name; 176 humanize_number(diskdesc, 7, pp->lg_mediasize, 177 "B", HN_AUTOSCALE, HN_DECIMAL); 178 if (strncmp(pp->lg_name, "ad", 2) == 0) 179 strcat(diskdesc, " ATA Hard Disk"); 180 else if (strncmp(pp->lg_name, "md", 2) == 0) 181 strcat(diskdesc, " Memory Disk"); 182 else 183 strcat(diskdesc, " Disk"); 184 185 if (desc != NULL) 186 snprintf(diskdesc, sizeof(diskdesc), 187 "%s <%s>", diskdesc, desc); 188 189 disks[n-1].prefix = ""; 190 disks[n-1].on = false; 191 disks[n-1].depth = 0; 192 disks[n-1].desc = strdup(diskdesc); 193 disks[n-1].bottomdesc = ""; 194 } 195 } 196 } 197 198 if (n > 1) { 199 conf.title = "Partitioning"; 200 button = bsddialog_menu(&conf, 201 "Select the disk on which to install " OSNAME ".", 0, 0, 0, 202 n, disks, &selected); 203 204 chosen = (button == BSDDIALOG_OK) ? 205 strdup(disks[selected].name) : NULL; 206 } else if (n == 1) { 207 chosen = strdup(disks[0].name); 208 } else { 209 chosen = NULL; 210 } 211 212 for (i = 0; i < n; i++) 213 free((char*)disks[i].desc); 214 215 return (chosen); 216 } 217 218 static struct gprovider * 219 provider_for_name(struct gmesh *mesh, const char *name) 220 { 221 struct gclass *classp; 222 struct gprovider *pp = NULL; 223 struct ggeom *gp; 224 225 LIST_FOREACH(classp, &mesh->lg_class, lg_class) { 226 LIST_FOREACH(gp, &classp->lg_geom, lg_geom) { 227 if (LIST_EMPTY(&gp->lg_provider)) 228 continue; 229 230 LIST_FOREACH(pp, &gp->lg_provider, lg_provider) 231 if (strcmp(pp->lg_name, name) == 0) 232 break; 233 234 if (pp != NULL) break; 235 } 236 237 if (pp != NULL) break; 238 } 239 240 return (pp); 241 } 242 243 static char * 244 wizard_partition(struct gmesh *mesh, const char *disk) 245 { 246 struct gclass *classp; 247 struct ggeom *gpart = NULL; 248 struct gconfig *gc; 249 char *retval = NULL; 250 const char *scheme = NULL; 251 char message[512]; 252 int choice; 253 struct bsddialog_conf conf; 254 255 bsddialog_initconf(&conf); 256 257 LIST_FOREACH(classp, &mesh->lg_class, lg_class) 258 if (strcmp(classp->lg_name, "PART") == 0) 259 break; 260 261 if (classp != NULL) { 262 LIST_FOREACH(gpart, &classp->lg_geom, lg_geom) 263 if (strcmp(gpart->lg_name, disk) == 0) 264 break; 265 } 266 267 if (gpart != NULL) { 268 LIST_FOREACH(gc, &gpart->lg_config, lg_config) { 269 if (strcmp(gc->lg_name, "scheme") == 0) { 270 scheme = gc->lg_val; 271 break; 272 } 273 } 274 } 275 276 /* Treat uncommitted scheme deletions as no scheme */ 277 if (scheme != NULL && strcmp(scheme, "(none)") == 0) 278 scheme = NULL; 279 280 query: 281 conf.button.ok_label = "Entire Disk"; 282 conf.button.cancel_label = "Partition"; 283 if (gpart != NULL) 284 conf.button.default_cancel = true; 285 286 snprintf(message, sizeof(message), "Would you like to use this entire " 287 "disk (%s) for " OSNAME " or partition it to share it with other " 288 "operating systems? Using the entire disk will erase any data " 289 "currently stored there.", disk); 290 conf.title = "Partition"; 291 choice = bsddialog_yesno(&conf, message, 9, 45); 292 293 conf.button.ok_label = NULL; 294 conf.button.cancel_label = NULL; 295 conf.button.default_cancel = false; 296 297 if (choice == BSDDIALOG_NO && scheme != NULL && !is_scheme_bootable(scheme)) { 298 char warning[512]; 299 int subchoice; 300 301 snprintf(warning, sizeof(warning), 302 "The existing partition scheme on this " 303 "disk (%s) is not bootable on this platform. To install " 304 OSNAME ", it must be repartitioned. This will destroy all " 305 "data on the disk. Are you sure you want to proceed?", 306 scheme); 307 conf.title = "Non-bootable Disk"; 308 subchoice = bsddialog_yesno(&conf, warning, 0, 0); 309 if (subchoice != BSDDIALOG_YES) 310 goto query; 311 312 gpart_destroy(gpart); 313 scheme = choose_part_type(default_scheme()); 314 if (scheme == NULL) 315 return NULL; 316 gpart_partition(disk, scheme); 317 } 318 319 if (scheme == NULL || choice == 0) { 320 if (gpart != NULL && scheme != NULL) { 321 /* Erase partitioned disk */ 322 conf.title = "Confirmation"; 323 choice = bsddialog_yesno(&conf, "This will erase " 324 "the disk. Are you sure you want to proceed?", 0, 0); 325 if (choice != BSDDIALOG_YES) 326 goto query; 327 328 gpart_destroy(gpart); 329 } 330 331 scheme = choose_part_type(default_scheme()); 332 if (scheme == NULL) 333 return NULL; 334 gpart_partition(disk, scheme); 335 } 336 337 if (strcmp(scheme, "MBR") == 0) { 338 struct gmesh submesh; 339 340 if (geom_gettree(&submesh) == 0) { 341 gpart_create(provider_for_name(&submesh, disk), 342 "freebsd", NULL, NULL, &retval, 343 choice /* Non-interactive for "Entire Disk" */); 344 geom_deletetree(&submesh); 345 } 346 } else { 347 retval = strdup(disk); 348 } 349 350 return (retval); 351 } 352 353 int 354 wizard_makeparts(struct gmesh *mesh, const char *disk, const char *fstype, 355 int interactive) 356 { 357 struct gclass *classp; 358 struct ggeom *gp; 359 struct gprovider *pp; 360 char *fsnames[] = {"freebsd-ufs", "freebsd-zfs"}; 361 char *fsname; 362 struct gmesh submesh; 363 char swapsizestr[10], rootsizestr[10]; 364 intmax_t swapsize, available; 365 int error, retval; 366 struct bsddialog_conf conf; 367 368 if (strcmp(fstype, "zfs") == 0) { 369 fsname = fsnames[1]; 370 } else { 371 /* default to UFS */ 372 fsname = fsnames[0]; 373 } 374 375 LIST_FOREACH(classp, &mesh->lg_class, lg_class) 376 if (strcmp(classp->lg_name, "PART") == 0) 377 break; 378 379 LIST_FOREACH(gp, &classp->lg_geom, lg_geom) 380 if (strcmp(gp->lg_name, disk) == 0) 381 break; 382 383 pp = provider_for_name(mesh, disk); 384 385 available = gpart_max_free(gp, NULL)*pp->lg_sectorsize; 386 if (interactive && available < MIN_FREE_SPACE) { 387 char availablestr[10], neededstr[10], message[512]; 388 humanize_number(availablestr, 7, available, "B", HN_AUTOSCALE, 389 HN_DECIMAL); 390 humanize_number(neededstr, 7, MIN_FREE_SPACE, "B", HN_AUTOSCALE, 391 HN_DECIMAL); 392 snprintf(message, sizeof(message), 393 "There is not enough free space on %s to " 394 "install " OSNAME " (%s free, %s required). Would you like " 395 "to choose another disk or to open the partition editor?", 396 disk, availablestr, neededstr); 397 398 bsddialog_initconf(&conf); 399 conf.button.ok_label = "Another Disk"; 400 conf.button.cancel_label = "Editor"; 401 conf.title = "Warning"; 402 retval = bsddialog_yesno(&conf, message, 0, 0); 403 404 return (!retval); /* Editor -> return 0 */ 405 } 406 407 swapsize = swap_size(available); 408 humanize_number(swapsizestr, 7, swapsize, "B", HN_AUTOSCALE, 409 HN_NOSPACE | HN_DECIMAL); 410 humanize_number(rootsizestr, 7, available - swapsize - 1024*1024, 411 "B", HN_AUTOSCALE, HN_NOSPACE | HN_DECIMAL); 412 413 error = geom_gettree(&submesh); 414 if (error != 0) 415 return (error); 416 pp = provider_for_name(&submesh, disk); 417 gpart_create(pp, fsname, rootsizestr, "/", NULL, 0); 418 geom_deletetree(&submesh); 419 420 error = geom_gettree(&submesh); 421 if (error != 0) 422 return (error); 423 pp = provider_for_name(&submesh, disk); 424 gpart_create(pp, "freebsd-swap", swapsizestr, NULL, NULL, 0); 425 geom_deletetree(&submesh); 426 427 return (0); 428 } 429