disks.c revision 18830
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.69 1996/10/06 11:40:30 jkh 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
188    /* Set up the chunk array */
189    record_chunks(d);
190
191    while (chunking) {
192	char *val, geometry[80];
193
194	/* Now print our overall state */
195	print_chunks(d);
196	print_command_summary();
197	if (msg) {
198	    attrset(title_attr); mvprintw(23, 0, msg); attrset(A_NORMAL);
199	    beep();
200	    msg = NULL;
201	}
202	else {
203	    move(23, 0);
204	    clrtoeol();
205	}
206
207	/* Get command character */
208	key = getch();
209	switch (toupper(key)) {
210	case '\014':	/* ^L (redraw) */
211	    clear();
212	    msg = NULL;
213	    break;
214
215	case KEY_UP:
216	case '-':
217	    if (current_chunk != 0)
218		--current_chunk;
219	    break;
220
221	case KEY_DOWN:
222	case '+':
223	case '\r':
224	case '\n':
225	    if (chunk_info[current_chunk + 1])
226		++current_chunk;
227	    break;
228
229	case KEY_HOME:
230	    current_chunk = 0;
231	    break;
232
233	case KEY_END:
234	    while (chunk_info[current_chunk + 1])
235		++current_chunk;
236	    break;
237
238	case KEY_F(1):
239	case '?':
240	    systemDisplayHelp("slice");
241	    clear();
242	    break;
243
244	case 'A':
245	    rv = msgYesNo("Do you want to do this with a true partition entry\n"
246			  "so as to remain cooperative with any future possible\n"
247			  "operating systems on the drive(s)?");
248	    if (rv != 0) {
249		rv = !msgYesNo("This is dangerous in that it will make the drive totally\n"
250			       "uncooperative with other potential operating systems on the\n"
251			       "same disk.  It will lead instead to a totally dedicated disk,\n"
252			       "starting at the very first sector, bypassing all BIOS geometry\n"
253			       "considerations.  This precludes the existance of any boot\n"
254			       "manager or other stuff in sector 0, since the BSD bootstrap\n"
255			       "will live there.\n"
256			       "You will run into serious trouble with ST-506 and ESDI drives\n"
257			       "and possibly some IDE drives (e.g. drives running under the\n"
258			       "control of sort of disk manager).  SCSI drives are considerably\n"
259			       "less at risk.\n\n"
260			       "Do you insist on dedicating the entire disk this way?");
261	    }
262	    if (rv == -1)
263		rv = 0;
264	    All_FreeBSD(d, rv);
265	    if (rv)
266		d->bios_hd = d->bios_sect = d->bios_cyl = 1;
267	    else {
268		d->bios_hd = 64;
269		d->bios_sect = 32;
270		d->bios_cyl = Total / ONE_MEG;
271	    }
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 partition.";
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 = "Partition 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 partition 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				      "partition (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 = "Partition 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		d->bios_cyl = strtol(val, &val, 0);
349		d->bios_hd = strtol(val + 1, &val, 0);
350		d->bios_sect = strtol(val + 1, 0, 0);
351	    }
352	    clear();
353	    break;
354
355	case 'S':
356	    /* Set Bootable */
357	    chunk_info[current_chunk]->flags |= CHUNK_ACTIVE;
358	    break;
359
360	case 'U':
361	    if ((cp = variable_get(DISK_LABELLED)) && !strcmp(cp, "written")) {
362		msgConfirm("You've already written this information out - you\n"
363			   "can't undo it.");
364	    }
365	    else if (!msgYesNo("Are you SURE you want to Undo everything?")) {
366		d = Open_Disk(d->name);
367		if (!d) {
368		    msgConfirm("Can't reopen disk %s! Internal state is probably corrupted", d->name);
369		    clear();
370		    break;
371		}
372		Free_Disk(dev->private);
373		dev->private = d;
374		variable_unset(DISK_PARTITIONED);
375		variable_unset(DISK_LABELLED);
376		record_chunks(d);
377	    }
378	    clear();
379	    break;
380
381	case 'W':
382	    if ((cp = variable_get(DISK_LABELLED)) && !strcmp(cp, "written")) {
383		msgConfirm("You've already written this information out - if\n"
384			   "you wish to overwrite it, you'll have to restart.");
385	    }
386	    else if (!msgYesNo("WARNING:  This should only be used when modifying an EXISTING\n"
387			       "installation.  If you are installing FreeBSD for the first time\n"
388			       "then you should simply type Q when you're finished here and your\n"
389			       "changes will be committed in one batch automatically at the end of\n"
390			       "these questions.\n\n"
391			       "Are you absolutely sure you want to do this now?")) {
392		variable_set2(DISK_PARTITIONED, "yes");
393
394		/* Don't trash the MBR if the first (and therefore only) chunk is marked for a truly dedicated
395		 * disk (i.e., the disklabel starts at sector 0), even in cases where the user has requested
396		 * booteasy or a "standard" MBR -- both would be fatal in this case.
397		 */
398		if ((d->chunks->part->flags & CHUNK_FORCE_ALL) != CHUNK_FORCE_ALL
399		    && (mbrContents = getBootMgr(d->name)) != NULL)
400		    Set_Boot_Mgr(d, mbrContents);
401
402		if (DITEM_STATUS(diskPartitionWrite(NULL)) != DITEM_SUCCESS)
403		    msgConfirm("Disk partition write returned an error status!");
404		else
405		    msgConfirm("Wrote FDISK partition information out successfully.");
406	    }
407	    clear();
408	    break;
409
410	case '|':
411	    if (!msgYesNo("Are you SURE you want to go into Wizard mode?\n"
412			  "No seat belts whatsoever are provided!")) {
413		clear();
414		refresh();
415		slice_wizard(d);
416		variable_set2(DISK_PARTITIONED, "yes");
417		record_chunks(d);
418	    }
419	    else
420		msg = "Wise choice!";
421	    clear();
422	    break;
423
424	case 'Q':
425	    chunking = FALSE;
426	    /* Don't trash the MBR if the first (and therefore only) chunk is marked for a truly dedicated
427	     * disk (i.e., the disklabel starts at sector 0), even in cases where the user has requested
428	     * booteasy or a "standard" MBR -- both would be fatal in this case.
429	     */
430	    if ((d->chunks->part->flags & CHUNK_FORCE_ALL) != CHUNK_FORCE_ALL
431		&& (mbrContents = getBootMgr(d->name)) != NULL)
432		Set_Boot_Mgr(d, mbrContents);
433	    break;
434
435	default:
436	    beep();
437	    msg = "Type F1 or ? for help";
438	    break;
439	}
440    }
441    p = CheckRules(d);
442    if (p) {
443	char buf[FILENAME_MAX];
444
445	dialog_clear_norefresh();
446        use_helpline("Press F1 to read more about disk partitioning.");
447	use_helpfile(systemHelpFile("partition", buf));
448	dialog_mesgbox("Disk partitioning warning:", p, -1, -1);
449	free(p);
450    }
451    restorescr(w);
452}
453
454static int
455partitionHook(dialogMenuItem *selected)
456{
457    Device **devs = NULL;
458
459    devs = deviceFind(selected->prompt, DEVICE_TYPE_DISK);
460    if (!devs) {
461	msgConfirm("Unable to find disk %s!", selected->prompt);
462	return DITEM_FAILURE;
463    }
464    /* Toggle enabled status? */
465    if (!devs[0]->enabled) {
466	devs[0]->enabled = TRUE;
467	diskPartition(devs[0], (Disk *)devs[0]->private);
468    }
469    else
470	devs[0]->enabled = FALSE;
471    return DITEM_SUCCESS | DITEM_REDRAW;
472}
473
474static int
475partitionCheck(dialogMenuItem *selected)
476{
477    Device **devs = NULL;
478
479    devs = deviceFind(selected->prompt, DEVICE_TYPE_DISK);
480    if (!devs || devs[0]->enabled == FALSE)
481	return FALSE;
482    return TRUE;
483}
484
485int
486diskPartitionEditor(dialogMenuItem *self)
487{
488    DMenu *menu;
489    Device **devs;
490    int i, cnt;
491    char *cp;
492
493    cp = variable_get(VAR_DISK);
494    devs = deviceFind(cp, DEVICE_TYPE_DISK);
495    cnt = deviceCount(devs);
496    if (!cnt) {
497	msgConfirm("No disks found!  Please verify that your disk controller is being\n"
498		   "properly probed at boot time.  See the Hardware Guide on the\n"
499		   "Documentation menu for clues on diagnosing this type of problem.");
500	i = DITEM_FAILURE;
501    }
502    else if (cnt == 1) {
503	devs[0]->enabled = TRUE;
504	diskPartition(devs[0], (Disk *)devs[0]->private);
505	i = DITEM_SUCCESS;
506    }
507    else {
508	menu = deviceCreateMenu(&MenuDiskDevices, DEVICE_TYPE_DISK, partitionHook, partitionCheck);
509	if (!menu) {
510	    msgConfirm("No devices suitable for installation found!\n\n"
511		       "Please verify that your disk controller (and attached drives)\n"
512		       "were detected properly.  This can be done by pressing the\n"
513		       "[Scroll Lock] key and using the Arrow keys to move back to\n"
514		       "the boot messages.  Press [Scroll Lock] again to return.");
515	    i = DITEM_FAILURE;
516	}
517	else {
518	    i = dmenuOpenSimple(menu, FALSE) ? DITEM_SUCCESS : DITEM_FAILURE;
519	    free(menu);
520	}
521	i = i | DITEM_RECREATE;
522    }
523    return i;
524}
525
526int
527diskPartitionWrite(dialogMenuItem *self)
528{
529    Device **devs;
530    char *cp;
531    int i;
532
533    if ((cp = variable_get(DISK_PARTITIONED)) && strcmp(cp, "yes"))
534	return DITEM_SUCCESS;
535    else if (!cp) {
536	msgConfirm("You must partition the disk(s) before this option can be used.");
537	return DITEM_FAILURE;
538    }
539
540    devs = deviceFind(NULL, DEVICE_TYPE_DISK);
541    if (!devs) {
542	msgConfirm("Unable to find any disks to write to??");
543	return DITEM_FAILURE;
544    }
545    if (isDebug())
546	msgDebug("diskPartitionWrite: Examining %d devices\n", deviceCount(devs));
547
548    for (i = 0; devs[i]; i++) {
549	Chunk *c1;
550	Disk *d = (Disk *)devs[i]->private;
551
552	if (!devs[i]->enabled)
553	    continue;
554
555	Set_Boot_Blocks(d, boot1, boot2);
556	msgNotify("Writing partition information to drive %s", d->name);
557	if (!Fake && Write_Disk(d)) {
558	    msgConfirm("ERROR: Unable to write data to disk %s!", d->name);
559	    return DITEM_FAILURE;
560	}
561	/* Now scan for bad blocks, if necessary */
562	for (c1 = d->chunks->part; c1; c1 = c1->next) {
563	    if (c1->flags & CHUNK_BAD144) {
564		int ret;
565
566		msgNotify("Running bad block scan on partition %s", c1->name);
567		if (!Fake) {
568		    ret = vsystem("bad144 -v /dev/r%s 1234", c1->name);
569		    if (ret)
570			msgConfirm("Bad144 init on %s returned status of %d!", c1->name, ret);
571		    ret = vsystem("bad144 -v -s /dev/r%s", c1->name);
572		    if (ret)
573			msgConfirm("Bad144 scan on %s returned status of %d!", c1->name, ret);
574		}
575	    }
576	}
577    }
578    /* Now it's not "yes", but "written" */
579    variable_set2(DISK_PARTITIONED, "written");
580    return DITEM_SUCCESS;
581}
582