disks.c revision 19515
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: disks.c,v 1.71 1996/11/05 19:53:20 phk Exp $
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_hd <= 1 && d->bios_sect <= 1) {
81	All_FreeBSD(d, TRUE);
82	d->bios_hd = d->bios_sect = d->bios_cyl = 1;
83    }
84    else if (d->bios_cyl > 65536 || d->bios_hd > 256 || d->bios_sect >= 64) {
85	dialog_clear_norefresh();
86	msgConfirm("WARNING:  A geometry of %d/%d/%d for %s is incorrect.  Using\n"
87		   "a default geometry of 64 heads and 32 sectors.  If this geometry\n"
88		   "is incorrect or you are unsure as to whether or not it's correct,\n"
89		   "please consult the Hardware Guide in the Documentation submenu\n"
90		   "or use the (G)eometry command to change it now.", d->bios_cyl, d->bios_hd, d->bios_sect, d->name);
91	d->bios_hd = 64;
92	d->bios_sect = 32;
93	d->bios_cyl = Total / ONE_MEG;
94    }
95    attrset(A_NORMAL);
96    mvaddstr(0, 0, "Disk name:\t");
97    clrtobot();
98    attrset(A_REVERSE); addstr(d->name); attrset(A_NORMAL);
99    attrset(A_REVERSE); mvaddstr(0, 55, "FDISK Partition Editor"); attrset(A_NORMAL);
100    mvprintw(1, 0,
101	     "DISK Geometry:\t%lu cyls/%lu heads/%lu sectors",
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, chunk_n[chunk_info[i]->type],
113		 chunk_info[i]->subtype, ShowChunkFlags(chunk_info[i]));
114	if (i == current_chunk)
115	    attrset(A_NORMAL);
116    }
117}
118
119static void
120print_command_summary()
121{
122    mvprintw(14, 0, "The following commands are supported (in upper or lower case):");
123    mvprintw(16, 0, "A = Use Entire Disk    B = Bad Block Scan     C = Create Partition");
124    mvprintw(17, 0, "D = Delete Partition   G = Set Drive Geometry S = Set Bootable");
125    mvprintw(18, 0, "U = Undo All Changes   Q = Finish");
126    if (!RunningAsInit)
127	mvprintw(18, 46, "W = Write Changes");
128    mvprintw(21, 0, "Use F1 or ? to get more help, arrow keys to select.");
129    move(0, 0);
130}
131
132static u_char *
133getBootMgr(char *dname)
134{
135    extern u_char mbr[], bteasy17[];
136    char str[80];
137    char *cp;
138    int i = 0;
139
140    cp = variable_get(VAR_BOOTMGR);
141    if (!cp) {
142	/* Figure out what kind of MBR the user wants */
143	sprintf(str, "Install Boot Manager for drive %s?", dname);
144	MenuMBRType.title = str;
145	i = dmenuOpenSimple(&MenuMBRType, FALSE);
146    }
147    else {
148	if (!strncmp(cp, "boot", 4))
149	    BootMgr = 0;
150	else if (!strcmp(cp, "standard"))
151	    BootMgr = 1;
152	else
153	    BootMgr = 2;
154    }
155    if (cp || i) {
156	switch (BootMgr) {
157	case 0:
158	    return bteasy17;
159
160	case 1:
161	    return mbr;
162
163	case 2:
164	default:
165	    break;
166	}
167    }
168    return NULL;
169}
170
171void
172diskPartition(Device *dev, Disk *d)
173{
174    char *cp, *p;
175    int rv, key = 0;
176    Boolean chunking;
177    char *msg = NULL;
178    u_char *mbrContents;
179    WINDOW *w = savescr();
180
181    chunking = TRUE;
182    keypad(stdscr, TRUE);
183
184    /* Flush both the dialog and curses library views of the screen
185       since we don't always know who called us */
186    dialog_clear_norefresh(), clear();
187    current_chunk = 0;
188
189    /* Set up the chunk array */
190    record_chunks(d);
191
192    while (chunking) {
193	char *val, geometry[80];
194
195	/* Now print our overall state */
196	print_chunks(d);
197	print_command_summary();
198	if (msg) {
199	    attrset(title_attr); mvprintw(23, 0, msg); attrset(A_NORMAL);
200	    beep();
201	    msg = NULL;
202	}
203	else {
204	    move(23, 0);
205	    clrtoeol();
206	}
207
208	/* Get command character */
209	key = getch();
210	switch (toupper(key)) {
211	case '\014':	/* ^L (redraw) */
212	    clear();
213	    msg = NULL;
214	    break;
215
216	case KEY_UP:
217	case '-':
218	    if (current_chunk != 0)
219		--current_chunk;
220	    break;
221
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	    if (rv)
267		d->bios_hd = d->bios_sect = d->bios_cyl = 1;
268	    variable_set2(DISK_PARTITIONED, "yes");
269	    record_chunks(d);
270	    clear();
271	    break;
272
273	case 'B':
274	    if (chunk_info[current_chunk]->type != freebsd)
275		msg = "Can only scan for bad blocks in FreeBSD partition.";
276	    else if (strncmp(d->name, "sd", 2) ||
277		     !msgYesNo("This typically makes sense only for ESDI, IDE or MFM drives.\n"
278			       "Are you sure you want to do this on a SCSI disk?")) {
279		if (chunk_info[current_chunk]->flags & CHUNK_BAD144)
280		    chunk_info[current_chunk]->flags &= ~CHUNK_BAD144;
281		else
282		    chunk_info[current_chunk]->flags |= CHUNK_BAD144;
283	    }
284	    clear();
285	    break;
286
287	case 'C':
288	    if (chunk_info[current_chunk]->type != unused)
289		msg = "Partition in use, delete it first or move to an unused one.";
290	    else {
291		char *val, tmp[20], *cp;
292		int size, subtype;
293		chunk_e partitiontype;
294
295		snprintf(tmp, 20, "%d", chunk_info[current_chunk]->size);
296		val = msgGetInput(tmp, "Please specify the size for new FreeBSD partition in blocks\n"
297				  "or append a trailing `M' for megabytes (e.g. 20M).");
298		if (val && (size = strtol(val, &cp, 0)) > 0) {
299		    if (*cp && toupper(*cp) == 'M')
300			size *= ONE_MEG;
301		    strcpy(tmp, "165");
302		    val = msgGetInput(tmp, "Enter type of partition to create:\n\n"
303				      "Pressing Enter will choose the default, a native FreeBSD\n"
304				      "partition (type 165).  You can choose other types, 6 for a\n"
305				      "DOS partition or 131 for a Linux partition, for example.\n\n"
306				      "Note:  If you choose a non-FreeBSD partition type, it will not\n"
307				      "be formatted or otherwise prepared, it will simply reserve space\n"
308				      "for you to use another tool, such as DOS FORMAT, to later format\n"
309				      "and use the partition.");
310		    if (val && (subtype = strtol(val, NULL, 0)) > 0) {
311			if (subtype==165)
312			    partitiontype=freebsd;
313			else if (subtype==6)
314			    partitiontype=fat;
315			else
316			    partitiontype=unknown;
317			Create_Chunk(d, chunk_info[current_chunk]->offset, size, partitiontype, subtype,
318				     (chunk_info[current_chunk]->flags & CHUNK_ALIGN));
319			variable_set2(DISK_PARTITIONED, "yes");
320			record_chunks(d);
321		    }
322		}
323		clear();
324	    }
325	    break;
326
327	case KEY_DC:
328	case 'D':
329	    if (chunk_info[current_chunk]->type == unused)
330		msg = "Partition is already unused!";
331	    else {
332		Delete_Chunk(d, chunk_info[current_chunk]);
333		variable_set2(DISK_PARTITIONED, "yes");
334		record_chunks(d);
335	    }
336	    break;
337
338	case 'G':
339	    snprintf(geometry, 80, "%lu/%lu/%lu", d->bios_cyl, d->bios_hd, d->bios_sect);
340	    val = msgGetInput(geometry, "Please specify the new geometry in cyl/hd/sect format.\n"
341			      "Don't forget to use the two slash (/) separator characters!\n"
342			      "It's not possible to parse the field without them.");
343	    if (val) {
344		d->bios_cyl = strtol(val, &val, 0);
345		d->bios_hd = strtol(val + 1, &val, 0);
346		d->bios_sect = strtol(val + 1, 0, 0);
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		d = Open_Disk(d->name);
363		if (!d) {
364		    msgConfirm("Can't reopen disk %s! Internal state is probably corrupted", d->name);
365		    clear();
366		    break;
367		}
368		Free_Disk(dev->private);
369		dev->private = d;
370		variable_unset(DISK_PARTITIONED);
371		variable_unset(DISK_LABELLED);
372		record_chunks(d);
373	    }
374	    clear();
375	    break;
376
377	case 'W':
378	    if ((cp = variable_get(DISK_LABELLED)) && !strcmp(cp, "written")) {
379		msgConfirm("You've already written this information out - if\n"
380			   "you wish to overwrite it, you'll have to restart.");
381	    }
382	    else if (!msgYesNo("WARNING:  This should only be used when modifying an EXISTING\n"
383			       "installation.  If you are installing FreeBSD for the first time\n"
384			       "then you should simply type Q when you're finished here and your\n"
385			       "changes will be committed in one batch automatically at the end of\n"
386			       "these questions.\n\n"
387			       "Are you absolutely sure you want to do this now?")) {
388		variable_set2(DISK_PARTITIONED, "yes");
389
390		/* Don't trash the MBR if the first (and therefore only) chunk is marked for a truly dedicated
391		 * disk (i.e., the disklabel starts at sector 0), even in cases where the user has requested
392		 * booteasy or a "standard" MBR -- both would be fatal in this case.
393		 */
394		if ((d->chunks->part->flags & CHUNK_FORCE_ALL) != CHUNK_FORCE_ALL
395		    && (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 'Q':
421	    chunking = FALSE;
422	    /* Don't trash the MBR if the first (and therefore only) chunk is marked for a truly dedicated
423	     * disk (i.e., the disklabel starts at sector 0), even in cases where the user has requested
424	     * booteasy or a "standard" MBR -- both would be fatal in this case.
425	     */
426	    if ((d->chunks->part->flags & CHUNK_FORCE_ALL) != CHUNK_FORCE_ALL
427		&& (mbrContents = getBootMgr(d->name)) != NULL)
428		Set_Boot_Mgr(d, mbrContents);
429	    break;
430
431	default:
432	    beep();
433	    msg = "Type F1 or ? for help";
434	    break;
435	}
436    }
437    p = CheckRules(d);
438    if (p) {
439	char buf[FILENAME_MAX];
440
441	dialog_clear_norefresh();
442        use_helpline("Press F1 to read more about disk partitioning.");
443	use_helpfile(systemHelpFile("partition", buf));
444	dialog_mesgbox("Disk partitioning warning:", p, -1, -1);
445	free(p);
446    }
447    restorescr(w);
448}
449
450static int
451partitionHook(dialogMenuItem *selected)
452{
453    Device **devs = NULL;
454
455    devs = deviceFind(selected->prompt, DEVICE_TYPE_DISK);
456    if (!devs) {
457	msgConfirm("Unable to find disk %s!", selected->prompt);
458	return DITEM_FAILURE;
459    }
460    /* Toggle enabled status? */
461    if (!devs[0]->enabled) {
462	devs[0]->enabled = TRUE;
463	diskPartition(devs[0], (Disk *)devs[0]->private);
464    }
465    else
466	devs[0]->enabled = FALSE;
467    return DITEM_SUCCESS | DITEM_REDRAW;
468}
469
470static int
471partitionCheck(dialogMenuItem *selected)
472{
473    Device **devs = NULL;
474
475    devs = deviceFind(selected->prompt, DEVICE_TYPE_DISK);
476    if (!devs || devs[0]->enabled == FALSE)
477	return FALSE;
478    return TRUE;
479}
480
481int
482diskPartitionEditor(dialogMenuItem *self)
483{
484    DMenu *menu;
485    Device **devs;
486    int i, cnt;
487    char *cp;
488
489    cp = variable_get(VAR_DISK);
490    devs = deviceFind(cp, DEVICE_TYPE_DISK);
491    cnt = deviceCount(devs);
492    if (!cnt) {
493	msgConfirm("No disks found!  Please verify that your disk controller is being\n"
494		   "properly probed at boot time.  See the Hardware Guide on the\n"
495		   "Documentation menu for clues on diagnosing this type of problem.");
496	i = DITEM_FAILURE;
497    }
498    else if (cnt == 1) {
499	devs[0]->enabled = TRUE;
500	diskPartition(devs[0], (Disk *)devs[0]->private);
501	i = DITEM_SUCCESS;
502    }
503    else {
504	menu = deviceCreateMenu(&MenuDiskDevices, DEVICE_TYPE_DISK, partitionHook, partitionCheck);
505	if (!menu) {
506	    msgConfirm("No devices suitable for installation found!\n\n"
507		       "Please verify that your disk controller (and attached drives)\n"
508		       "were detected properly.  This can be done by pressing the\n"
509		       "[Scroll Lock] key and using the Arrow keys to move back to\n"
510		       "the boot messages.  Press [Scroll Lock] again to return.");
511	    i = DITEM_FAILURE;
512	}
513	else {
514	    i = dmenuOpenSimple(menu, FALSE) ? DITEM_SUCCESS : DITEM_FAILURE;
515	    free(menu);
516	}
517	i = i | DITEM_RECREATE;
518    }
519    return i;
520}
521
522int
523diskPartitionWrite(dialogMenuItem *self)
524{
525    Device **devs;
526    char *cp;
527    int i;
528
529    if ((cp = variable_get(DISK_PARTITIONED)) && strcmp(cp, "yes"))
530	return DITEM_SUCCESS;
531    else if (!cp) {
532	msgConfirm("You must partition the disk(s) before this option can be used.");
533	return DITEM_FAILURE;
534    }
535
536    devs = deviceFind(NULL, DEVICE_TYPE_DISK);
537    if (!devs) {
538	msgConfirm("Unable to find any disks to write to??");
539	return DITEM_FAILURE;
540    }
541    if (isDebug())
542	msgDebug("diskPartitionWrite: Examining %d devices\n", deviceCount(devs));
543
544    for (i = 0; devs[i]; i++) {
545	Chunk *c1;
546	Disk *d = (Disk *)devs[i]->private;
547
548	if (!devs[i]->enabled)
549	    continue;
550
551	Set_Boot_Blocks(d, boot1, boot2);
552	msgNotify("Writing partition information to drive %s", d->name);
553	if (!Fake && Write_Disk(d)) {
554	    msgConfirm("ERROR: Unable to write data to disk %s!", d->name);
555	    return DITEM_FAILURE;
556	}
557	/* Now scan for bad blocks, if necessary */
558	for (c1 = d->chunks->part; c1; c1 = c1->next) {
559	    if (c1->flags & CHUNK_BAD144) {
560		int ret;
561
562		msgNotify("Running bad block scan on partition %s", c1->name);
563		if (!Fake) {
564		    ret = vsystem("bad144 -v /dev/r%s 1234", c1->name);
565		    if (ret)
566			msgConfirm("Bad144 init on %s returned status of %d!", c1->name, ret);
567		    ret = vsystem("bad144 -v -s /dev/r%s", c1->name);
568		    if (ret)
569			msgConfirm("Bad144 scan on %s returned status of %d!", c1->name, ret);
570		}
571	    }
572	}
573    }
574    /* Now it's not "yes", but "written" */
575    variable_set2(DISK_PARTITIONED, "written");
576    return DITEM_SUCCESS;
577}
578