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