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