disks.c revision 13592
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.34 1995/12/11 16:32:31 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 * 3. All advertising materials mentioning features or use of this software
23 *    must display the following acknowledgement:
24 *	This product includes software developed by Jordan Hubbard
25 *	for the FreeBSD Project.
26 * 4. The name of Jordan Hubbard or the FreeBSD project may not be used to
27 *    endorse or promote products derived from this software without specific
28 *    prior written permission.
29 *
30 * THIS SOFTWARE IS PROVIDED BY JORDAN HUBBARD ``AS IS'' AND
31 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
32 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33 * ARE DISCLAIMED.  IN NO EVENT SHALL JORDAN HUBBARD OR HIS PETS BE LIABLE
34 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
35 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
36 * OR SERVICES; LOSS OF USE, DATA, LIFE OR PROFITS; OR BUSINESS INTERRUPTION)
37 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
38 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
39 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40 * SUCH DAMAGE.
41 *
42 */
43
44#include "sysinstall.h"
45#include <ctype.h>
46#include <sys/disklabel.h>
47
48/* Where we start displaying chunk information on the screen */
49#define CHUNK_START_ROW		5
50
51/* Where we keep track of MBR chunks */
52static struct chunk *chunk_info[16];
53static int current_chunk;
54
55static void
56record_chunks(Disk *d)
57{
58    struct chunk *c1;
59    int i = 0;
60    int last_free = 0;
61    if (!d->chunks)
62	msgFatal("No chunk list found for %s!", d->name);
63    current_chunk = 0;
64    for (c1 = d->chunks->part; c1; c1 = c1->next) {
65	if (c1->type == unused && c1->size > last_free) {
66	    last_free = c1->size;
67	    current_chunk = i;
68	}
69	chunk_info[i++] = c1;
70    }
71    chunk_info[i] = NULL;
72}
73
74static void
75print_chunks(Disk *d)
76{
77    int row;
78    int i;
79
80    if ((!d->bios_cyl || d->bios_cyl > 65536) || (!d->bios_hd || d->bios_hd > 256) || (!d->bios_sect || d->bios_sect >= 64)) {
81	int sz;
82
83	dialog_clear();
84	msgConfirm("WARNING:  The current geometry for %s is incorrect.  Using\n"
85		   "a default geometry of 64 heads and 32 sectors.  If this geometry\n"
86		   "is incorrect or you are unsure as to whether or not it's correct,\n"
87		   "please consult the Hardware Guide in the Documentation submenu\n"
88		   "or use the (G)eometry command to change it now.", d->name);
89	d->bios_hd = 64;
90	d->bios_sect = 32;
91	sz = 0;
92	for (i = 0; chunk_info[i]; i++)
93	    sz += chunk_info[i]->size;
94	if (sz)
95	    d->bios_cyl = sz / ONE_MEG;
96	else
97	    msgConfirm("Couldn't set geometry!  You'll have to do it by hand.");
98    }
99    attrset(A_NORMAL);
100    mvaddstr(0, 0, "Disk name:\t");
101    clrtobot();
102    attrset(A_REVERSE); addstr(d->name); attrset(A_NORMAL);
103    attrset(A_REVERSE); mvaddstr(0, 55, "FDISK Partition Editor"); attrset(A_NORMAL);
104    mvprintw(1, 0,
105	     "DISK Geometry:\t%lu cyls/%lu heads/%lu sectors",
106	     d->bios_cyl, d->bios_hd, d->bios_sect);
107    mvprintw(3, 1, "%10s %10s %10s %8s %8s %8s %8s %8s",
108	     "Offset", "Size", "End", "Name", "PType", "Desc",
109	     "Subtype", "Flags");
110    for (i = 0, row = CHUNK_START_ROW; chunk_info[i]; i++, row++) {
111	if (i == current_chunk)
112	    attrset(A_REVERSE);
113	mvprintw(row, 2, "%10ld %10lu %10lu %8s %8d %8s %8d\t%-6s",
114		 chunk_info[i]->offset, chunk_info[i]->size,
115		 chunk_info[i]->end, chunk_info[i]->name,
116		 chunk_info[i]->type, chunk_n[chunk_info[i]->type],
117		 chunk_info[i]->subtype, ShowChunkFlags(chunk_info[i]));
118	if (i == current_chunk)
119	    attrset(A_NORMAL);
120    }
121}
122
123static void
124print_command_summary()
125{
126    mvprintw(14, 0, "The following commands are supported (in upper or lower case):");
127    mvprintw(16, 0, "A = Use Entire Disk    B = Bad Block Scan     C = Create Partition");
128    mvprintw(17, 0, "D = Delete Partition   G = Set Drive Geometry S = Set Bootable");
129    mvprintw(18, 0, "U = Undo All Changes   Q = Finish             W = Write Changes");
130    mvprintw(20, 0, "The currently selected partition is displayed in ");
131    attrset(A_REVERSE); addstr("reverse"); attrset(A_NORMAL); addstr(" video.");
132    mvprintw(21, 0, "Use F1 or ? to get more help, arrow keys to move.");
133    move(0, 0);
134}
135
136/* Partition a disk based wholly on which variables are set */
137static void
138scriptPartition(Device *dev, Disk *d)
139{
140    char *cp;
141    int i, sz;
142
143    record_chunks(d);
144    cp = variable_get(VAR_GEOMETRY);
145    if (cp) {
146	msgDebug("Setting geometry from script to: %s\n", cp);
147	d->bios_cyl = strtol(cp, &cp, 0);
148	d->bios_hd = strtol(cp + 1, &cp, 0);
149	d->bios_sect = strtol(cp + 1, 0, 0);
150    }
151
152    cp = variable_get(VAR_DISKSPACE);
153    if (cp) {
154	if (!strcmp(cp, "free")) {
155	    /* Do free disk space case */
156	    for (i = 0; chunk_info[i]; i++) {
157		/* If a chunk is at least 10MB in size, use it. */
158		if (chunk_info[i]->type == unused && chunk_info[i]->size > (10 * ONE_MEG)) {
159		    Create_Chunk(d, chunk_info[i]->offset, chunk_info[i]->size, freebsd, 3,
160				 (chunk_info[i]->flags & CHUNK_ALIGN));
161		    variable_set2(DISK_PARTITIONED, "yes");
162		    break;
163		}
164	    }
165	    if (!chunk_info[i]) {
166		dialog_clear();
167		msgConfirm("Unable to find any free space on this disk!");
168		return;
169	    }
170	}
171	else if (!strcmp(cp, "all")) {
172	    /* Do all disk space case */
173	    msgDebug("Warning:  Devoting all of disk %s to FreeBSD.\n", d->name);
174
175	    All_FreeBSD(d, FALSE);
176	}
177	else if (!strcmp(cp, "exclusive")) {
178	    /* Do really-all-the-disk-space case */
179	    msgDebug("Warning:  Devoting all of disk %s to FreeBSD.\n", d->name);
180
181	    All_FreeBSD(d, TRUE);
182	}
183	else if ((sz = strtol(cp, &cp, 0))) {
184	    /* Look for sz bytes free */
185	    if (*cp && toupper(*cp) == 'M')
186		sz *= ONE_MEG;
187	    for (i = 0; chunk_info[i]; i++) {
188		/* If a chunk is at least sz MB, use it. */
189		if (chunk_info[i]->type == unused && chunk_info[i]->size >= sz) {
190		    Create_Chunk(d, chunk_info[i]->offset, sz, freebsd, 3, (chunk_info[i]->flags & CHUNK_ALIGN));
191		    variable_set2(DISK_PARTITIONED, "yes");
192		    break;
193		}
194	    }
195	    if (!chunk_info[i]) {
196		dialog_clear();
197		msgConfirm("Unable to find %d free blocks on this disk!", sz);
198		return;
199	    }
200	}
201	else if (!strcmp(cp, "existing")) {
202	    /* Do existing FreeBSD case */
203	    for (i = 0; chunk_info[i]; i++) {
204		if (chunk_info[i]->type == freebsd)
205		    break;
206	    }
207	    if (!chunk_info[i]) {
208		dialog_clear();
209		msgConfirm("Unable to find any existing FreeBSD partitions on this disk!");
210		return;
211	    }
212	}
213	else {
214	    dialog_clear();
215	    msgConfirm("`%s' is an invalid value for %s - is config file valid?", cp, VAR_DISKSPACE);
216	    return;
217	}
218	variable_set2(DISK_PARTITIONED, "yes");
219    }
220}
221
222static u_char *
223getBootMgr(char *dname)
224{
225    extern u_char mbr[], bteasy17[];
226    char str[80];
227    char *cp;
228    int i = 0;
229
230    cp = variable_get(VAR_BOOTMGR);
231    if (!cp) {
232	/* Figure out what kind of MBR the user wants */
233	sprintf(str, "Install Boot Manager for drive %s?", dname);
234	MenuMBRType.title = str;
235	dialog_clear();
236	i = dmenuOpenSimple(&MenuMBRType);
237    }
238    else {
239	if (!strncmp(cp, "boot", 4))
240	    BootMgr = 0;
241	else if (!strcmp(cp, "standard"))
242	    BootMgr = 1;
243	else
244	    BootMgr = 2;
245    }
246    if (cp || i) {
247	switch (BootMgr) {
248	case 0:
249	    return bteasy17;
250
251	case 1:
252	    return mbr;
253
254	case 2:
255	default:
256	    break;
257	}
258    }
259    return NULL;
260}
261
262void
263diskPartition(Device *dev, Disk *d)
264{
265    char *p;
266    int key = 0;
267    Boolean chunking;
268    char *msg = NULL;
269    u_char *mbrContents;
270
271    chunking = TRUE;
272    keypad(stdscr, TRUE);
273
274    clear();
275    record_chunks(d);
276    while (chunking) {
277	print_chunks(d);
278	print_command_summary();
279	if (msg) {
280	    standout(); mvprintw(23, 0, msg); standend();
281	    beep();
282	    msg = NULL;
283	}
284
285	key = toupper(getch());
286	switch (key) {
287
288	case '\014':	/* ^L */
289	    clear();
290	    print_command_summary();
291	    continue;
292
293	case KEY_UP:
294	case '-':
295	    if (current_chunk != 0)
296		--current_chunk;
297	    break;
298
299	case KEY_DOWN:
300	case '+':
301	case '\r':
302	case '\n':
303	    if (chunk_info[current_chunk + 1])
304		++current_chunk;
305	    break;
306
307	case KEY_HOME:
308	    current_chunk = 0;
309	    break;
310
311	case KEY_END:
312	    while (chunk_info[current_chunk + 1])
313		++current_chunk;
314	    break;
315
316	case KEY_F(1):
317	case '?':
318	    systemDisplayHelp("slice");
319	    break;
320
321	case 'A': {
322	    int rv;
323
324	    rv = msgYesNo("Do you want to do this with a true partition entry\n"
325			  "so as to remain cooperative with any future possible\n"
326			  "operating systems on the drive(s)?");
327	    if (rv) {
328		rv = !msgYesNo("This is dangerous in that it will make the drive totally\n"
329			       "uncooperative with other potential operating systems on the\n"
330			       "same disk.  It will lead instead to a totally dedicated disk,\n"
331			       "starting at the very first sector, bypassing all BIOS geometry\n"
332			       "considerations.  This precludes the existance of any boot\n"
333			       "manager or other stuff in sector 0, since the BSD bootstrap\n"
334			       "will live there.\n"
335			       "You will run into serious trouble with ST-506 and ESDI drives\n"
336			       "and possibly some IDE drives (e.g. drives running under the\n"
337			       "control of sort of disk manager).  SCSI drives are considerably\n"
338			       "less at risk.\n\n"
339			       "Do you insist on dedicating the entire disk this way?");
340	    }
341	    All_FreeBSD(d, rv);
342	    if (rv)
343		d->bios_hd = d->bios_sect = d->bios_cyl = 1;
344	    variable_set2(DISK_PARTITIONED, "yes");
345	    record_chunks(d);
346	}
347	    break;
348
349	case 'B':
350	    if (chunk_info[current_chunk]->type != freebsd)
351		msg = "Can only scan for bad blocks in FreeBSD partition.";
352	    else if (strncmp(d->name, "sd", 2) ||
353		     !msgYesNo("This typically makes sense only for ESDI, IDE or MFM drives.\n"
354			       "Are you sure you want to do this on a SCSI disk?")) {
355		if (chunk_info[current_chunk]->flags & CHUNK_BAD144)
356		    chunk_info[current_chunk]->flags &= ~CHUNK_BAD144;
357		else
358		    chunk_info[current_chunk]->flags |= CHUNK_BAD144;
359	    }
360	    break;
361
362	case 'C':
363	    if (chunk_info[current_chunk]->type != unused)
364		msg = "Partition in use, delete it first or move to an unused one.";
365	    else {
366		char *val, tmp[20], *cp;
367		int size;
368
369		snprintf(tmp, 20, "%d", chunk_info[current_chunk]->size);
370		val = msgGetInput(tmp, "Please specify the size for new FreeBSD partition in blocks, or append\n"
371				  "a trailing `M' for megabytes (e.g. 20M).");
372		if (val && (size = strtol(val, &cp, 0)) > 0) {
373		    if (*cp && toupper(*cp) == 'M')
374			size *= ONE_MEG;
375		    Create_Chunk(d, chunk_info[current_chunk]->offset, size, freebsd, 3,
376				 (chunk_info[current_chunk]->flags & CHUNK_ALIGN));
377		    variable_set2(DISK_PARTITIONED, "yes");
378		    record_chunks(d);
379		}
380	    }
381	    break;
382
383	case 'D':
384	    if (chunk_info[current_chunk]->type == unused)
385		msg = "Partition is already unused!";
386	    else {
387		Delete_Chunk(d, chunk_info[current_chunk]);
388		variable_set2(DISK_PARTITIONED, "yes");
389		record_chunks(d);
390	    }
391	    break;
392
393	case 'G': {
394	    char *val, geometry[80];
395
396	    snprintf(geometry, 80, "%lu/%lu/%lu", d->bios_cyl, d->bios_hd, d->bios_sect);
397	    val = msgGetInput(geometry, "Please specify the new geometry in cyl/hd/sect format.\n"
398			      "Don't forget to use the two slash (/) separator characters!\n"
399			      "It's not possible to parse the field without them.");
400	    if (val) {
401		d->bios_cyl = strtol(val, &val, 0);
402		d->bios_hd = strtol(val + 1, &val, 0);
403		d->bios_sect = strtol(val + 1, 0, 0);
404	    }
405	}
406	break;
407
408    case 'S':
409	/* Set Bootable */
410	chunk_info[current_chunk]->flags |= CHUNK_ACTIVE;
411	break;
412
413    case 'U':
414	    clear();
415	    if (msgYesNo("Are you SURE you want to Undo everything?"))
416		break;
417	    d = Open_Disk(d->name);
418	    if (!d) {
419		dialog_clear();
420		msgConfirm("Can't reopen disk %s! Internal state is probably corrupted", d->name);
421		return;
422	    }
423	    Free_Disk(dev->private);
424	    dev->private = d;
425	    variable_unset(DISK_PARTITIONED);
426	    record_chunks(d);
427	    break;
428
429	case 'W':
430	    if (!msgYesNo("Are you SURE you want to write this now?  You do also\n"
431			  "have the option of not modifying the disk until *all*\n"
432			  "configuration information has been entered, at which\n"
433			  "point you can do it all at once.  If you're unsure, then\n"
434			  "PLEASE CHOOSE NO at this dialog!  This option is DANGEROUS\n"
435			  "if you do not know EXACTLY what you are doing!")) {
436		variable_set2(DISK_PARTITIONED, "yes");
437		clear();
438
439		/* Don't trash the MBR if the first (and therefore only) chunk is marked for a truly dedicated
440		 * disk (i.e., the disklabel starts at sector 0), even in cases where the user has requested
441		 * booteasy or a "standard" MBR -- both would be fatal in this case.
442		 */
443		if ((d->chunks->part->flags & CHUNK_FORCE_ALL) != CHUNK_FORCE_ALL
444		    && (mbrContents = getBootMgr(d->name)) != NULL)
445		    Set_Boot_Mgr(d, mbrContents);
446
447		if (diskPartitionWrite(NULL) != RET_SUCCESS) {
448		    dialog_clear();
449		    msgConfirm("Disk partition write returned an error status!");
450		}
451		else {
452		    msgConfirm("Wrote FDISK partition information out successfully.");
453		}
454	    }
455	    break;
456
457	case '|':
458	    if (!msgYesNo("Are you SURE you want to go into Wizard mode?\n"
459			  "No seat belts whatsoever are provided!")) {
460		dialog_clear();
461		end_dialog();
462		DialogActive = FALSE;
463		slice_wizard(d);
464		variable_set2(DISK_PARTITIONED, "yes");
465		dialog_clear();
466		DialogActive = TRUE;
467		record_chunks(d);
468	    }
469	    else
470		msg = "Wise choice!";
471	    break;
472
473	case 'Q':
474	    chunking = FALSE;
475	    clear();
476	    /* Don't trash the MBR if the first (and therefore only) chunk is marked for a truly dedicated
477	     * disk (i.e., the disklabel starts at sector 0), even in cases where the user has requested
478	     * booteasy or a "standard" MBR -- both would be fatal in this case.
479	     */
480	    if ((d->chunks->part->flags & CHUNK_FORCE_ALL) != CHUNK_FORCE_ALL
481		&& (mbrContents = getBootMgr(d->name)) != NULL)
482		Set_Boot_Mgr(d, mbrContents);
483	    break;
484
485	default:
486	    beep();
487	    msg = "Type F1 or ? for help";
488	    break;
489	}
490    }
491    p = CheckRules(d);
492    if (p) {
493	dialog_clear();
494	msgConfirm(p);
495	free(p);
496    }
497    dialog_clear();
498}
499
500static int
501partitionHook(char *str)
502{
503    Device **devs = NULL;
504
505    /* Clip garbage off the ends */
506    string_prune(str);
507    str = string_skipwhite(str);
508    /* Try and open all the disks */
509    while (str) {
510	char *cp;
511
512	cp = index(str, '\n');
513	if (cp)
514	   *cp++ = 0;
515	if (!*str) {
516	    beep();
517	    return 0;
518	}
519	devs = deviceFind(str, DEVICE_TYPE_DISK);
520	if (!devs) {
521	    dialog_clear();
522	    msgConfirm("Unable to find disk %s!", str);
523	    return 0;
524	}
525	devs[0]->enabled = TRUE;
526	diskPartition(devs[0], (Disk *)devs[0]->private);
527	str = cp;
528    }
529    return devs ? 1 : 0;
530}
531
532int
533diskPartitionEditor(char *str)
534{
535    DMenu *menu;
536    Device **devs;
537    int i, cnt;
538    char *cp;
539
540    cp = variable_get(VAR_DISK);
541    devs = deviceFind(cp, DEVICE_TYPE_DISK);
542    cnt = deviceCount(devs);
543    if (!cnt) {
544	dialog_clear();
545	msgConfirm("No disks found!  Please verify that your disk controller is being\n"
546		   "properly probed at boot time.  See the Hardware Guide on the\n"
547		   "Documentation menu for clues on diagnosing this type of problem.");
548	i = RET_FAIL;
549    }
550    else if (cnt == 1) {
551	devs[0]->enabled = TRUE;
552	if (str && !strcmp(str, "script"))
553	    scriptPartition(devs[0], (Disk *)devs[0]->private);
554	else
555	    diskPartition(devs[0], (Disk *)devs[0]->private);
556	i = RET_SUCCESS;
557	variable_set2(DISK_SELECTED, "yes");
558    }
559    else {
560	menu = deviceCreateMenu(&MenuDiskDevices, DEVICE_TYPE_DISK, partitionHook);
561	if (!menu) {
562	    dialog_clear();
563	    msgConfirm("No devices suitable for installation found!\n\n"
564		       "Please verify that your disk controller (and attached drives)\n"
565		       "were detected properly.  This can be done by pressing the\n"
566		       "[Scroll Lock] key and using the Arrow keys to move back to\n"
567		       "the boot messages.  Press [Scroll Lock] again to return.");
568	    i = RET_FAIL;
569	}
570	else {
571	    if (!dmenuOpenSimple(menu))
572		i = RET_FAIL;
573	    else  {
574		i = RET_SUCCESS;
575		variable_set2(DISK_SELECTED, "yes");
576	    }
577	    free(menu);
578	}
579    }
580    return i;
581}
582
583int
584diskPartitionWrite(char *str)
585{
586    extern u_char boot1[], boot2[];
587    Device **devs;
588    char *cp;
589    int i;
590
591    if ((cp = variable_get(DISK_PARTITIONED)) && strcmp(cp, "yes"))
592	return RET_SUCCESS;
593    else if (!cp) {
594	dialog_clear();
595	msgConfirm("You must partition the disk(s) before this option can be used.");
596	return RET_FAIL;
597    }
598
599    devs = deviceFind(NULL, DEVICE_TYPE_DISK);
600    if (!devs) {
601	dialog_clear();
602	msgConfirm("Unable to find any disks to write to??");
603	return RET_FAIL;
604    }
605
606    for (i = 0; devs[i]; i++) {
607	Chunk *c1;
608	Disk *d = (Disk *)devs[i]->private;
609
610	if (!devs[i]->enabled)
611	    continue;
612
613	Set_Boot_Blocks(d, boot1, boot2);
614	msgNotify("Writing partition information to drive %s", d->name);
615	if (Write_Disk(d)) {
616	    dialog_clear();
617	    msgConfirm("ERROR: Unable to write data to disk %s!", d->name);
618	    return RET_FAIL;
619	}
620	/* Now scan for bad blocks, if necessary */
621	for (c1 = d->chunks->part; c1; c1 = c1->next) {
622	    if (c1->flags & CHUNK_BAD144) {
623		int ret;
624
625		msgNotify("Running bad block scan on partition %s", c1->name);
626		ret = vsystem("bad144 -v /dev/r%s 1234", c1->name);
627		if (ret) {
628		    dialog_clear();
629		    msgConfirm("Bad144 init on %s returned status of %d!", c1->name, ret);
630		}
631		ret = vsystem("bad144 -v -s /dev/r%s", c1->name);
632		if (ret) {
633		    dialog_clear();
634		    msgConfirm("Bad144 scan on %s returned status of %d!", c1->name, ret);
635		}
636	    }
637	}
638    }
639    /* Now it's not "yes", but "written" */
640    variable_set2(DISK_PARTITIONED, "written");
641    return RET_SUCCESS;
642}
643