disks.c revision 19992
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.72 1996/11/07 16:40:10 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_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, 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 *cp, *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    current_chunk = 0;
184
185    /* Set up the chunk array */
186    record_chunks(d);
187
188    while (chunking) {
189	char *val, geometry[80];
190
191	/* Now print our overall state */
192	print_chunks(d);
193	print_command_summary();
194	if (msg) {
195	    attrset(title_attr); mvprintw(23, 0, msg); attrset(A_NORMAL);
196	    beep();
197	    msg = NULL;
198	}
199	else {
200	    move(23, 0);
201	    clrtoeol();
202	}
203
204	/* Get command character */
205	key = getch();
206	switch (toupper(key)) {
207	case '\014':	/* ^L (redraw) */
208	    clear();
209	    msg = NULL;
210	    break;
211
212	case KEY_UP:
213	case '-':
214	    if (current_chunk != 0)
215		--current_chunk;
216	    break;
217
218	case KEY_DOWN:
219	case '+':
220	case '\r':
221	case '\n':
222	    if (chunk_info[current_chunk + 1])
223		++current_chunk;
224	    break;
225
226	case KEY_HOME:
227	    current_chunk = 0;
228	    break;
229
230	case KEY_END:
231	    while (chunk_info[current_chunk + 1])
232		++current_chunk;
233	    break;
234
235	case KEY_F(1):
236	case '?':
237	    systemDisplayHelp("slice");
238	    clear();
239	    break;
240
241	case 'A':
242	    rv = msgYesNo("Do you want to do this with a true partition entry\n"
243			  "so as to remain cooperative with any future possible\n"
244			  "operating systems on the drive(s)?");
245	    if (rv != 0) {
246		rv = !msgYesNo("This is dangerous in that it will make the drive totally\n"
247			       "uncooperative with other potential operating systems on the\n"
248			       "same disk.  It will lead instead to a totally dedicated disk,\n"
249			       "starting at the very first sector, bypassing all BIOS geometry\n"
250			       "considerations.  This precludes the existance of any boot\n"
251			       "manager or other stuff in sector 0, since the BSD bootstrap\n"
252			       "will live there.\n"
253			       "You will run into serious trouble with ST-506 and ESDI drives\n"
254			       "and possibly some IDE drives (e.g. drives running under the\n"
255			       "control of sort of disk manager).  SCSI drives are considerably\n"
256			       "less at risk.\n\n"
257			       "Do you insist on dedicating the entire disk this way?");
258	    }
259	    if (rv == -1)
260		rv = 0;
261	    All_FreeBSD(d, rv);
262	    variable_set2(DISK_PARTITIONED, "yes");
263	    record_chunks(d);
264	    clear();
265	    break;
266
267	case 'B':
268	    if (chunk_info[current_chunk]->type != freebsd)
269		msg = "Can only scan for bad blocks in FreeBSD partition.";
270	    else if (strncmp(d->name, "sd", 2) ||
271		     !msgYesNo("This typically makes sense only for ESDI, IDE or MFM drives.\n"
272			       "Are you sure you want to do this on a SCSI disk?")) {
273		if (chunk_info[current_chunk]->flags & CHUNK_BAD144)
274		    chunk_info[current_chunk]->flags &= ~CHUNK_BAD144;
275		else
276		    chunk_info[current_chunk]->flags |= CHUNK_BAD144;
277	    }
278	    clear();
279	    break;
280
281	case 'C':
282	    if (chunk_info[current_chunk]->type != unused)
283		msg = "Partition in use, delete it first or move to an unused one.";
284	    else {
285		char *val, tmp[20], *cp;
286		int size, subtype;
287		chunk_e partitiontype;
288
289		snprintf(tmp, 20, "%d", chunk_info[current_chunk]->size);
290		val = msgGetInput(tmp, "Please specify the size for new FreeBSD partition in blocks\n"
291				  "or append a trailing `M' for megabytes (e.g. 20M).");
292		if (val && (size = strtol(val, &cp, 0)) > 0) {
293		    if (*cp && toupper(*cp) == 'M')
294			size *= ONE_MEG;
295		    strcpy(tmp, "165");
296		    val = msgGetInput(tmp, "Enter type of partition to create:\n\n"
297				      "Pressing Enter will choose the default, a native FreeBSD\n"
298				      "partition (type 165).  You can choose other types, 6 for a\n"
299				      "DOS partition or 131 for a Linux partition, for example.\n\n"
300				      "Note:  If you choose a non-FreeBSD partition type, it will not\n"
301				      "be formatted or otherwise prepared, it will simply reserve space\n"
302				      "for you to use another tool, such as DOS FORMAT, to later format\n"
303				      "and use the partition.");
304		    if (val && (subtype = strtol(val, NULL, 0)) > 0) {
305			if (subtype==165)
306			    partitiontype=freebsd;
307			else if (subtype==6)
308			    partitiontype=fat;
309			else
310			    partitiontype=unknown;
311			Create_Chunk(d, chunk_info[current_chunk]->offset, size, partitiontype, subtype,
312				     (chunk_info[current_chunk]->flags & CHUNK_ALIGN));
313			variable_set2(DISK_PARTITIONED, "yes");
314			record_chunks(d);
315		    }
316		}
317		clear();
318	    }
319	    break;
320
321	case KEY_DC:
322	case 'D':
323	    if (chunk_info[current_chunk]->type == unused)
324		msg = "Partition is already unused!";
325	    else {
326		Delete_Chunk(d, chunk_info[current_chunk]);
327		variable_set2(DISK_PARTITIONED, "yes");
328		record_chunks(d);
329	    }
330	    break;
331
332	case 'G':
333	    snprintf(geometry, 80, "%lu/%lu/%lu", d->bios_cyl, d->bios_hd, d->bios_sect);
334	    val = msgGetInput(geometry, "Please specify the new geometry in cyl/hd/sect format.\n"
335			      "Don't forget to use the two slash (/) separator characters!\n"
336			      "It's not possible to parse the field without them.");
337	    if (val) {
338		long nc, nh, ns;
339		nc = strtol(val, &val, 0);
340		nh = strtol(val + 1, &val, 0);
341		ns = strtol(val + 1, 0, 0);
342		Set_Bios_Geom(d, nc, nh, ns);
343	    }
344	    clear();
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 ((cp = variable_get(DISK_LABELLED)) && !strcmp(cp, "written")) {
354		msgConfirm("You've already written this information out - you\n"
355			   "can't undo it.");
356	    }
357	    else if (!msgYesNo("Are you SURE you want to Undo everything?")) {
358		d = Open_Disk(d->name);
359		if (!d) {
360		    msgConfirm("Can't reopen disk %s! Internal state is probably corrupted", d->name);
361		    clear();
362		    break;
363		}
364		Free_Disk(dev->private);
365		dev->private = d;
366		variable_unset(DISK_PARTITIONED);
367		variable_unset(DISK_LABELLED);
368		record_chunks(d);
369	    }
370	    clear();
371	    break;
372
373	case 'W':
374	    if ((cp = variable_get(DISK_LABELLED)) && !strcmp(cp, "written")) {
375		msgConfirm("You've already written this information out - if\n"
376			   "you wish to overwrite it, you'll have to restart.");
377	    }
378	    else if (!msgYesNo("WARNING:  This should only be used when modifying an EXISTING\n"
379			       "installation.  If you are installing FreeBSD for the first time\n"
380			       "then you should simply type Q when you're finished here and your\n"
381			       "changes will be committed in one batch automatically at the end of\n"
382			       "these questions.\n\n"
383			       "Are you absolutely sure you want to do this now?")) {
384		variable_set2(DISK_PARTITIONED, "yes");
385
386		/* Don't trash the MBR if the first (and therefore only) chunk is marked for a truly dedicated
387		 * disk (i.e., the disklabel starts at sector 0), even in cases where the user has requested
388		 * booteasy or a "standard" MBR -- both would be fatal in this case.
389		 */
390		if ((d->chunks->part->flags & CHUNK_FORCE_ALL) != CHUNK_FORCE_ALL
391		    && (mbrContents = getBootMgr(d->name)) != NULL)
392		    Set_Boot_Mgr(d, mbrContents);
393
394		if (DITEM_STATUS(diskPartitionWrite(NULL)) != DITEM_SUCCESS)
395		    msgConfirm("Disk partition write returned an error status!");
396		else
397		    msgConfirm("Wrote FDISK partition information out successfully.");
398	    }
399	    clear();
400	    break;
401
402	case '|':
403	    if (!msgYesNo("Are you SURE you want to go into Wizard mode?\n"
404			  "No seat belts whatsoever are provided!")) {
405		clear();
406		refresh();
407		slice_wizard(d);
408		variable_set2(DISK_PARTITIONED, "yes");
409		record_chunks(d);
410	    }
411	    else
412		msg = "Wise choice!";
413	    clear();
414	    break;
415
416	case 'Q':
417	    chunking = FALSE;
418	    /* Don't trash the MBR if the first (and therefore only) chunk is marked for a truly dedicated
419	     * disk (i.e., the disklabel starts at sector 0), even in cases where the user has requested
420	     * booteasy or a "standard" MBR -- both would be fatal in this case.
421	     */
422	    if ((d->chunks->part->flags & CHUNK_FORCE_ALL) != CHUNK_FORCE_ALL
423		&& (mbrContents = getBootMgr(d->name)) != NULL)
424		Set_Boot_Mgr(d, mbrContents);
425	    break;
426
427	default:
428	    beep();
429	    msg = "Type F1 or ? for help";
430	    break;
431	}
432    }
433    p = CheckRules(d);
434    if (p) {
435	char buf[FILENAME_MAX];
436
437	dialog_clear_norefresh();
438        use_helpline("Press F1 to read more about disk partitioning.");
439	use_helpfile(systemHelpFile("partition", buf));
440	dialog_mesgbox("Disk partitioning warning:", p, -1, -1);
441	free(p);
442    }
443    restorescr(w);
444}
445
446static int
447partitionHook(dialogMenuItem *selected)
448{
449    Device **devs = NULL;
450
451    devs = deviceFind(selected->prompt, DEVICE_TYPE_DISK);
452    if (!devs) {
453	msgConfirm("Unable to find disk %s!", selected->prompt);
454	return DITEM_FAILURE;
455    }
456    /* Toggle enabled status? */
457    if (!devs[0]->enabled) {
458	devs[0]->enabled = TRUE;
459	diskPartition(devs[0], (Disk *)devs[0]->private);
460    }
461    else
462	devs[0]->enabled = FALSE;
463    return DITEM_SUCCESS | DITEM_REDRAW;
464}
465
466static int
467partitionCheck(dialogMenuItem *selected)
468{
469    Device **devs = NULL;
470
471    devs = deviceFind(selected->prompt, DEVICE_TYPE_DISK);
472    if (!devs || devs[0]->enabled == FALSE)
473	return FALSE;
474    return TRUE;
475}
476
477int
478diskPartitionEditor(dialogMenuItem *self)
479{
480    DMenu *menu;
481    Device **devs;
482    int i, cnt;
483    char *cp;
484
485    cp = variable_get(VAR_DISK);
486    devs = deviceFind(cp, DEVICE_TYPE_DISK);
487    cnt = deviceCount(devs);
488    if (!cnt) {
489	msgConfirm("No disks found!  Please verify that your disk controller is being\n"
490		   "properly probed at boot time.  See the Hardware Guide on the\n"
491		   "Documentation menu for clues on diagnosing this type of problem.");
492	i = DITEM_FAILURE;
493    }
494    else if (cnt == 1) {
495	devs[0]->enabled = TRUE;
496	diskPartition(devs[0], (Disk *)devs[0]->private);
497	i = DITEM_SUCCESS;
498    }
499    else {
500	menu = deviceCreateMenu(&MenuDiskDevices, DEVICE_TYPE_DISK, partitionHook, partitionCheck);
501	if (!menu) {
502	    msgConfirm("No devices suitable for installation found!\n\n"
503		       "Please verify that your disk controller (and attached drives)\n"
504		       "were detected properly.  This can be done by pressing the\n"
505		       "[Scroll Lock] key and using the Arrow keys to move back to\n"
506		       "the boot messages.  Press [Scroll Lock] again to return.");
507	    i = DITEM_FAILURE;
508	}
509	else {
510	    i = dmenuOpenSimple(menu, FALSE) ? DITEM_SUCCESS : DITEM_FAILURE;
511	    free(menu);
512	}
513	i = i | DITEM_RECREATE;
514    }
515    return i;
516}
517
518int
519diskPartitionWrite(dialogMenuItem *self)
520{
521    Device **devs;
522    char *cp;
523    int i;
524
525    if ((cp = variable_get(DISK_PARTITIONED)) && strcmp(cp, "yes"))
526	return DITEM_SUCCESS;
527    else if (!cp) {
528	msgConfirm("You must partition the disk(s) before this option can be used.");
529	return DITEM_FAILURE;
530    }
531
532    devs = deviceFind(NULL, DEVICE_TYPE_DISK);
533    if (!devs) {
534	msgConfirm("Unable to find any disks to write to??");
535	return DITEM_FAILURE;
536    }
537    if (isDebug())
538	msgDebug("diskPartitionWrite: Examining %d devices\n", deviceCount(devs));
539
540    for (i = 0; devs[i]; i++) {
541	Chunk *c1;
542	Disk *d = (Disk *)devs[i]->private;
543
544	if (!devs[i]->enabled)
545	    continue;
546
547	Set_Boot_Blocks(d, boot1, boot2);
548	msgNotify("Writing partition information to drive %s", d->name);
549	if (!Fake && Write_Disk(d)) {
550	    msgConfirm("ERROR: Unable to write data to disk %s!", d->name);
551	    return DITEM_FAILURE;
552	}
553	/* Now scan for bad blocks, if necessary */
554	for (c1 = d->chunks->part; c1; c1 = c1->next) {
555	    if (c1->flags & CHUNK_BAD144) {
556		int ret;
557
558		msgNotify("Running bad block scan on partition %s", c1->name);
559		if (!Fake) {
560		    ret = vsystem("bad144 -v /dev/r%s 1234", c1->name);
561		    if (ret)
562			msgConfirm("Bad144 init on %s returned status of %d!", c1->name, ret);
563		    ret = vsystem("bad144 -v -s /dev/r%s", c1->name);
564		    if (ret)
565			msgConfirm("Bad144 scan on %s returned status of %d!", c1->name, ret);
566		}
567	    }
568	}
569    }
570    /* Now it's not "yes", but "written" */
571    variable_set2(DISK_PARTITIONED, "written");
572    return DITEM_SUCCESS;
573}
574