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