disks.c revision 23721
1/*
2 * The new sysinstall program.
3 *
4 * This is probably the last program in the `sysinstall' line - the next
5 * generation being essentially a complete rewrite.
6 *
7 * $Id$
8 *
9 * Copyright (c) 1995
10 *	Jordan Hubbard.  All rights reserved.
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 *    notice, this list of conditions and the following disclaimer,
17 *    verbatim and that no modifications are made prior to this
18 *    point in the file.
19 * 2. Redistributions in binary form must reproduce the above copyright
20 *    notice, this list of conditions and the following disclaimer in the
21 *    documentation and/or other materials provided with the distribution.
22 *
23 * THIS SOFTWARE IS PROVIDED BY JORDAN HUBBARD ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED.  IN NO EVENT SHALL JORDAN HUBBARD OR HIS PETS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, LIFE OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 *
35 */
36
37#include "sysinstall.h"
38#include <ctype.h>
39#include <sys/disklabel.h>
40
41/* Where we start displaying chunk information on the screen */
42#define CHUNK_START_ROW		5
43
44/* Where we keep track of MBR chunks */
45static struct chunk *chunk_info[16];
46static int current_chunk;
47
48static void
49record_chunks(Disk *d)
50{
51    struct chunk *c1 = NULL;
52    int i = 0;
53    int last_free = 0;
54
55    if (!d->chunks)
56	msgFatal("No chunk list found for %s!", d->name);
57
58    for (c1 = d->chunks->part; c1; c1 = c1->next) {
59	if (c1->type == unused && c1->size > last_free) {
60	    last_free = c1->size;
61	    current_chunk = i;
62	}
63	chunk_info[i++] = c1;
64    }
65    chunk_info[i] = NULL;
66    if (current_chunk >= i)
67	current_chunk = i - 1;
68}
69
70static int Total;
71
72static void
73print_chunks(Disk *d)
74{
75    int row;
76    int i;
77
78    for (i = Total = 0; chunk_info[i]; i++)
79	Total += chunk_info[i]->size;
80    if (d->bios_cyl > 65536 || d->bios_hd > 256 || d->bios_sect >= 64) {
81	dialog_clear_norefresh();
82	msgConfirm("WARNING:  A geometry of %d/%d/%d for %s is incorrect.  Using\n"
83		   "a more likely geometry.  If this geometry is incorrect or you\n"
84		   "are unsure as to whether or not it's correct, please consult\n"
85		   "the Hardware Guide in the Documentation submenu or use the\n"
86		   "(G)eometry command to change it now.\n\n"
87		   "Remember: you need to enter whatever your BIOS thinks the\n"
88		   "geometry were!  For IDE, that's what you have told in the BIOS\n"
89		   "setup, for SCSI, it's the translation mode your controller is\n"
90		   "using.  Do NOT use a ``physical geometry''.",
91	  d->bios_cyl, d->bios_hd, d->bios_sect, d->name);
92	Sanitize_Bios_Geom(d);
93    }
94    attrset(A_NORMAL);
95    mvaddstr(0, 0, "Disk name:\t");
96    clrtobot();
97    attrset(A_REVERSE); addstr(d->name); attrset(A_NORMAL);
98    attrset(A_REVERSE); mvaddstr(0, 55, "FDISK Partition Editor"); attrset(A_NORMAL);
99    mvprintw(1, 0,
100	     "DISK Geometry:\t%lu cyls/%lu heads/%lu sectors = %lu sectors",
101	     d->bios_cyl, d->bios_hd, d->bios_sect,
102	     d->bios_cyl * d->bios_hd * d->bios_sect);
103    mvprintw(3, 1, "%10s %10s %10s %8s %8s %8s %8s %8s",
104	     "Offset", "Size", "End", "Name", "PType", "Desc",
105	     "Subtype", "Flags");
106    for (i = 0, row = CHUNK_START_ROW; chunk_info[i]; i++, row++) {
107	if (i == current_chunk)
108	    attrset(ATTR_SELECTED);
109	mvprintw(row, 2, "%10ld %10lu %10lu %8s %8d %8s %8d\t%-6s",
110		 chunk_info[i]->offset, chunk_info[i]->size,
111		 chunk_info[i]->end, chunk_info[i]->name,
112		 chunk_info[i]->type,
113		 slice_type_name(chunk_info[i]->type, chunk_info[i]->subtype),
114		 chunk_info[i]->subtype, ShowChunkFlags(chunk_info[i]));
115	if (i == current_chunk)
116	    attrset(A_NORMAL);
117    }
118}
119
120static void
121print_command_summary()
122{
123    mvprintw(14, 0, "The following commands are supported (in upper or lower case):");
124    mvprintw(16, 0, "A = Use Entire Disk    B = Bad Block Scan       C = Create Slice");
125    mvprintw(17, 0, "D = Delete Slice       G = Set Drive Geometry   S = Set Bootable");
126    mvprintw(18, 0, "U = Undo All Changes   Q = Finish");
127    if (!RunningAsInit)
128	mvprintw(18, 48, "W = Write Changes");
129    mvprintw(21, 0, "Use F1 or ? to get more help, arrow keys to select.");
130    move(0, 0);
131}
132
133static u_char *
134getBootMgr(char *dname)
135{
136    extern u_char mbr[], bteasy17[];
137    char str[80];
138    char *cp;
139    int i = 0;
140
141    cp = variable_get(VAR_BOOTMGR);
142    if (!cp) {
143	/* Figure out what kind of MBR the user wants */
144	sprintf(str, "Install Boot Manager for drive %s?", dname);
145	MenuMBRType.title = str;
146	i = dmenuOpenSimple(&MenuMBRType, FALSE);
147    }
148    else {
149	if (!strncmp(cp, "boot", 4))
150	    BootMgr = 0;
151	else if (!strcmp(cp, "standard"))
152	    BootMgr = 1;
153	else
154	    BootMgr = 2;
155    }
156    if (cp || i) {
157	switch (BootMgr) {
158	case 0:
159	    return bteasy17;
160
161	case 1:
162	    return mbr;
163
164	case 2:
165	default:
166	    break;
167	}
168    }
169    return NULL;
170}
171
172void
173diskPartition(Device *dev, Disk *d)
174{
175    char *cp, *p;
176    int rv, key = 0;
177    Boolean chunking;
178    char *msg = NULL;
179    u_char *mbrContents;
180    WINDOW *w = savescr();
181
182    chunking = TRUE;
183    keypad(stdscr, TRUE);
184
185    /* Flush both the dialog and curses library views of the screen
186       since we don't always know who called us */
187    dialog_clear_norefresh(), clear();
188    current_chunk = 0;
189
190    /* Set up the chunk array */
191    record_chunks(d);
192
193    while (chunking) {
194	char *val, geometry[80];
195
196	/* Now print our overall state */
197	if (d)
198	    print_chunks(d);
199	print_command_summary();
200	if (msg) {
201	    attrset(title_attr); mvprintw(23, 0, msg); attrset(A_NORMAL);
202	    beep();
203	    msg = NULL;
204	}
205	else {
206	    move(23, 0);
207	    clrtoeol();
208	}
209
210	/* Get command character */
211	key = getch();
212	switch (toupper(key)) {
213	case '\014':	/* ^L (redraw) */
214	    clear();
215	    msg = NULL;
216	    break;
217
218	case '\020':	/* ^P */
219	case KEY_UP:
220	case '-':
221	    if (current_chunk != 0)
222		--current_chunk;
223	    break;
224
225	case '\016':	/* ^N */
226	case KEY_DOWN:
227	case '+':
228	case '\r':
229	case '\n':
230	    if (chunk_info[current_chunk + 1])
231		++current_chunk;
232	    break;
233
234	case KEY_HOME:
235	    current_chunk = 0;
236	    break;
237
238	case KEY_END:
239	    while (chunk_info[current_chunk + 1])
240		++current_chunk;
241	    break;
242
243	case KEY_F(1):
244	case '?':
245	    systemDisplayHelp("slice");
246	    clear();
247	    break;
248
249	case 'A':
250	    rv = msgYesNo("Do you want to do this with a true partition entry\n"
251			  "so as to remain cooperative with any future possible\n"
252			  "operating systems on the drive(s)?");
253	    if (rv != 0) {
254		rv = !msgYesNo("This is dangerous in that it will make the drive totally\n"
255			       "uncooperative with other potential operating systems on the\n"
256			       "same disk.  It will lead instead to a totally dedicated disk,\n"
257			       "starting at the very first sector, bypassing all BIOS geometry\n"
258			       "considerations.  This precludes the existance of any boot\n"
259			       "manager or other stuff in sector 0, since the BSD bootstrap\n"
260			       "will live there.\n"
261			       "You will run into serious trouble with ST-506 and ESDI drives\n"
262			       "and possibly some IDE drives (e.g. drives running under the\n"
263			       "control of sort of disk manager).  SCSI drives are considerably\n"
264			       "less at risk.\n\n"
265			       "If, on the other hand, your goal is a dedicated FreeBSD machine\n"
266			       "and nothing else, this option is for you.\n\n"
267			       "Do you insist on dedicating the entire disk this way?");
268	    }
269	    if (rv == -1)
270		rv = 0;
271	    All_FreeBSD(d, rv);
272	    variable_set2(DISK_PARTITIONED, "yes");
273	    record_chunks(d);
274	    clear();
275	    break;
276
277	case 'B':
278	    if (chunk_info[current_chunk]->type != freebsd)
279		msg = "Can only scan for bad blocks in FreeBSD slice.";
280	    else if (strncmp(d->name, "sd", 2) ||
281		     !msgYesNo("This typically makes sense only for ESDI, IDE or MFM drives.\n"
282			       "Are you sure you want to do this on a SCSI disk?")) {
283		if (chunk_info[current_chunk]->flags & CHUNK_BAD144)
284		    chunk_info[current_chunk]->flags &= ~CHUNK_BAD144;
285		else
286		    chunk_info[current_chunk]->flags |= CHUNK_BAD144;
287	    }
288	    clear();
289	    break;
290
291	case 'C':
292	    if (chunk_info[current_chunk]->type != unused)
293		msg = "Slice in use, delete it first or move to an unused one.";
294	    else {
295		char *val, tmp[20], *cp;
296		int size, subtype;
297		chunk_e partitiontype;
298
299		snprintf(tmp, 20, "%d", chunk_info[current_chunk]->size);
300		val = msgGetInput(tmp, "Please specify the size for new FreeBSD slice in blocks\n"
301				  "or append a trailing `M' for megabytes (e.g. 20M).");
302		if (val && (size = strtol(val, &cp, 0)) > 0) {
303		    if (*cp && toupper(*cp) == 'M')
304			size *= ONE_MEG;
305		    strcpy(tmp, "165");
306		    val = msgGetInput(tmp, "Enter type of partition to create:\n\n"
307				      "Pressing Enter will choose the default, a native FreeBSD\n"
308				      "slice (type 165).  You can choose other types, 6 for a\n"
309				      "DOS partition or 131 for a Linux partition, for example.\n\n"
310				      "Note:  If you choose a non-FreeBSD partition type, it will not\n"
311				      "be formatted or otherwise prepared, it will simply reserve space\n"
312				      "for you to use another tool, such as DOS FORMAT, to later format\n"
313				      "and use the partition.");
314		    if (val && (subtype = strtol(val, NULL, 0)) > 0) {
315			if (subtype==165)
316			    partitiontype=freebsd;
317			else if (subtype==6)
318			    partitiontype=fat;
319			else
320			    partitiontype=unknown;
321			Create_Chunk(d, chunk_info[current_chunk]->offset, size, partitiontype, subtype,
322				     (chunk_info[current_chunk]->flags & CHUNK_ALIGN));
323			variable_set2(DISK_PARTITIONED, "yes");
324			record_chunks(d);
325		    }
326		}
327		clear();
328	    }
329	    break;
330
331	case KEY_DC:
332	case 'D':
333	    if (chunk_info[current_chunk]->type == unused)
334		msg = "Slice is already unused!";
335	    else {
336		Delete_Chunk(d, chunk_info[current_chunk]);
337		variable_set2(DISK_PARTITIONED, "yes");
338		record_chunks(d);
339	    }
340	    break;
341
342	case 'G':
343	    snprintf(geometry, 80, "%lu/%lu/%lu", d->bios_cyl, d->bios_hd, d->bios_sect);
344	    val = msgGetInput(geometry, "Please specify the new geometry in cyl/hd/sect format.\n"
345			      "Don't forget to use the two slash (/) separator characters!\n"
346			      "It's not possible to parse the field without them.");
347	    if (val) {
348		long nc, nh, ns;
349		nc = strtol(val, &val, 0);
350		nh = strtol(val + 1, &val, 0);
351		ns = strtol(val + 1, 0, 0);
352		Set_Bios_Geom(d, nc, nh, ns);
353	    }
354	    clear();
355	    break;
356
357	case 'S':
358	    /* Set Bootable */
359	    chunk_info[current_chunk]->flags |= CHUNK_ACTIVE;
360	    break;
361
362	case 'U':
363	    if ((cp = variable_get(DISK_LABELLED)) && !strcmp(cp, "written")) {
364		msgConfirm("You've already written this information out - you\n"
365			   "can't undo it.");
366	    }
367	    else if (!msgYesNo("Are you SURE you want to Undo everything?")) {
368		char cp[BUFSIZ];
369
370		sstrncpy(cp, d->name, sizeof cp);
371		Free_Disk(dev->private);
372		d = Open_Disk(cp);
373		if (!d)
374		    msgConfirm("Can't reopen disk %s! Internal state is probably corrupted", cp);
375		dev->private = d;
376		variable_unset(DISK_PARTITIONED);
377		variable_unset(DISK_LABELLED);
378		if (d)
379		    record_chunks(d);
380	    }
381	    clear();
382	    break;
383
384	case 'W':
385	    if ((cp = variable_get(DISK_LABELLED)) && !strcmp(cp, "written")) {
386		msgConfirm("You've already written this information out - if\n"
387			   "you wish to overwrite it, you'll have to restart.");
388	    }
389	    else if (!msgYesNo("WARNING:  This should only be used when modifying an EXISTING\n"
390			       "installation.  If you are installing FreeBSD for the first time\n"
391			       "then you should simply type Q when you're finished here and your\n"
392			       "changes will be committed in one batch automatically at the end of\n"
393			       "these questions.\n\n"
394			       "Are you absolutely sure you want to do this now?")) {
395		variable_set2(DISK_PARTITIONED, "yes");
396
397		/* Don't trash the MBR if the first (and therefore only) chunk is marked for a truly dedicated
398		 * disk (i.e., the disklabel starts at sector 0), even in cases where the user has requested
399		 * booteasy or a "standard" MBR -- both would be fatal in this case.
400		 */
401		if (!(d->chunks->part->flags & CHUNK_FORCE_ALL) && (mbrContents = getBootMgr(d->name)) != NULL)
402		    Set_Boot_Mgr(d, mbrContents);
403
404		if (DITEM_STATUS(diskPartitionWrite(NULL)) != DITEM_SUCCESS)
405		    msgConfirm("Disk partition write returned an error status!");
406		else
407		    msgConfirm("Wrote FDISK partition information out successfully.");
408	    }
409	    clear();
410	    break;
411
412	case '|':
413	    if (!msgYesNo("Are you SURE you want to go into Wizard mode?\n"
414			  "No seat belts whatsoever are provided!")) {
415		clear();
416		refresh();
417		slice_wizard(d);
418		variable_set2(DISK_PARTITIONED, "yes");
419		record_chunks(d);
420	    }
421	    else
422		msg = "Wise choice!";
423	    clear();
424	    break;
425
426	case '\033':	/* ESC */
427	case 'Q':
428	    chunking = FALSE;
429	    /* Don't trash the MBR if the first (and therefore only) chunk is marked for a truly dedicated
430	     * disk (i.e., the disklabel starts at sector 0), even in cases where the user has requested
431	     * booteasy or a "standard" MBR -- both would be fatal in this case.
432	     */
433	    if ((d->chunks->part->flags & CHUNK_FORCE_ALL) != CHUNK_FORCE_ALL
434		&& (mbrContents = getBootMgr(d->name)) != NULL)
435		Set_Boot_Mgr(d, mbrContents);
436	    break;
437
438	default:
439	    beep();
440	    msg = "Type F1 or ? for help";
441	    break;
442	}
443    }
444    p = CheckRules(d);
445    if (p) {
446	char buf[FILENAME_MAX];
447
448	dialog_clear_norefresh();
449        use_helpline("Press F1 to read more about disk slices.");
450	use_helpfile(systemHelpFile("partition", buf));
451	dialog_mesgbox("Disk slicing warning:", p, -1, -1);
452	free(p);
453    }
454    restorescr(w);
455}
456
457static int
458partitionHook(dialogMenuItem *selected)
459{
460    Device **devs = NULL;
461
462    devs = deviceFind(selected->prompt, DEVICE_TYPE_DISK);
463    if (!devs) {
464	msgConfirm("Unable to find disk %s!", selected->prompt);
465	return DITEM_FAILURE;
466    }
467    /* Toggle enabled status? */
468    if (!devs[0]->enabled) {
469	devs[0]->enabled = TRUE;
470	diskPartition(devs[0], (Disk *)devs[0]->private);
471    }
472    else
473	devs[0]->enabled = FALSE;
474    return DITEM_SUCCESS | DITEM_REDRAW;
475}
476
477static int
478partitionCheck(dialogMenuItem *selected)
479{
480    Device **devs = NULL;
481
482    devs = deviceFind(selected->prompt, DEVICE_TYPE_DISK);
483    if (!devs || devs[0]->enabled == FALSE)
484	return FALSE;
485    return TRUE;
486}
487
488int
489diskPartitionEditor(dialogMenuItem *self)
490{
491    DMenu *menu;
492    Device **devs;
493    int i, cnt;
494    char *cp;
495
496    cp = variable_get(VAR_DISK);
497    devs = deviceFind(cp, DEVICE_TYPE_DISK);
498    cnt = deviceCount(devs);
499    if (!cnt) {
500	msgConfirm("No disks found!  Please verify that your disk controller is being\n"
501		   "properly probed at boot time.  See the Hardware Guide on the\n"
502		   "Documentation menu for clues on diagnosing this type of problem.");
503	i = DITEM_FAILURE;
504    }
505    else if (cnt == 1) {
506	devs[0]->enabled = TRUE;
507	diskPartition(devs[0], (Disk *)devs[0]->private);
508	i = DITEM_SUCCESS;
509    }
510    else {
511	menu = deviceCreateMenu(&MenuDiskDevices, DEVICE_TYPE_DISK, partitionHook, partitionCheck);
512	if (!menu) {
513	    msgConfirm("No devices suitable for installation found!\n\n"
514		       "Please verify that your disk controller (and attached drives)\n"
515		       "were detected properly.  This can be done by pressing the\n"
516		       "[Scroll Lock] key and using the Arrow keys to move back to\n"
517		       "the boot messages.  Press [Scroll Lock] again to return.");
518	    i = DITEM_FAILURE;
519	}
520	else {
521	    i = dmenuOpenSimple(menu, FALSE) ? DITEM_SUCCESS : DITEM_FAILURE;
522	    free(menu);
523	}
524	i = i | DITEM_RESTORE;
525    }
526    return i;
527}
528
529int
530diskPartitionWrite(dialogMenuItem *self)
531{
532    Device **devs;
533    char *cp;
534    int i;
535
536    if ((cp = variable_get(DISK_PARTITIONED)) && strcmp(cp, "yes"))
537	return DITEM_SUCCESS;
538    else if (!cp) {
539	msgConfirm("You must partition the disk(s) before this option can be used.");
540	return DITEM_FAILURE;
541    }
542
543    devs = deviceFind(NULL, DEVICE_TYPE_DISK);
544    if (!devs) {
545	msgConfirm("Unable to find any disks to write to??");
546	return DITEM_FAILURE;
547    }
548    if (isDebug())
549	msgDebug("diskPartitionWrite: Examining %d devices\n", deviceCount(devs));
550
551    for (i = 0; devs[i]; i++) {
552	Chunk *c1;
553	Disk *d = (Disk *)devs[i]->private;
554
555	if (!devs[i]->enabled)
556	    continue;
557
558	Set_Boot_Blocks(d, boot1, boot2);
559	msgNotify("Writing partition information to drive %s", d->name);
560	if (!Fake && Write_Disk(d)) {
561	    msgConfirm("ERROR: Unable to write data to disk %s!", d->name);
562	    return DITEM_FAILURE;
563	}
564	/* Now scan for bad blocks, if necessary */
565	for (c1 = d->chunks->part; c1; c1 = c1->next) {
566	    if (c1->flags & CHUNK_BAD144) {
567		int ret;
568
569		msgNotify("Running bad block scan on slice %s", c1->name);
570		if (!Fake) {
571		    ret = vsystem("bad144 -v /dev/r%s 1234", c1->name);
572		    if (ret)
573			msgConfirm("Bad144 init on %s returned status of %d!", c1->name, ret);
574		    ret = vsystem("bad144 -v -s /dev/r%s", c1->name);
575		    if (ret)
576			msgConfirm("Bad144 scan on %s returned status of %d!", c1->name, ret);
577		}
578	    }
579	}
580    }
581    /* Now it's not "yes", but "written" */
582    variable_set2(DISK_PARTITIONED, "written");
583    return DITEM_SUCCESS;
584}
585