disks.c revision 12781
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.33 1995/12/07 10:33:39 peter 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.\n"
333			       "You will run into serious trouble with ST-506 and ESDI drives\n"
334			       "and possibly some IDE drives (e.g. drives running under the\n"
335			       "control of sort of disk manager).  SCSI drives are considerably\n"
336			       "less at risk.\n\n"
337			       "Do you insist on dedicating the entire disk this way?");
338	    }
339	    All_FreeBSD(d, rv);
340	    if (rv)
341		d->bios_hd = d->bios_sect = d->bios_cyl = 1;
342	    variable_set2(DISK_PARTITIONED, "yes");
343	    record_chunks(d);
344	}
345	    break;
346
347	case 'B':
348	    if (chunk_info[current_chunk]->type != freebsd)
349		msg = "Can only scan for bad blocks in FreeBSD partition.";
350	    else if (strncmp(d->name, "sd", 2) ||
351		     !msgYesNo("This typically makes sense only for ESDI, IDE or MFM drives.\n"
352			       "Are you sure you want to do this on a SCSI disk?")) {
353		if (chunk_info[current_chunk]->flags & CHUNK_BAD144)
354		    chunk_info[current_chunk]->flags &= ~CHUNK_BAD144;
355		else
356		    chunk_info[current_chunk]->flags |= CHUNK_BAD144;
357	    }
358	    break;
359
360	case 'C':
361	    if (chunk_info[current_chunk]->type != unused)
362		msg = "Partition in use, delete it first or move to an unused one.";
363	    else {
364		char *val, tmp[20], *cp;
365		int size;
366
367		snprintf(tmp, 20, "%d", chunk_info[current_chunk]->size);
368		val = msgGetInput(tmp, "Please specify the size for new FreeBSD partition in blocks, or append\n"
369				  "a trailing `M' for megabytes (e.g. 20M).");
370		if (val && (size = strtol(val, &cp, 0)) > 0) {
371		    if (*cp && toupper(*cp) == 'M')
372			size *= ONE_MEG;
373		    Create_Chunk(d, chunk_info[current_chunk]->offset, size, freebsd, 3,
374				 (chunk_info[current_chunk]->flags & CHUNK_ALIGN));
375		    variable_set2(DISK_PARTITIONED, "yes");
376		    record_chunks(d);
377		}
378	    }
379	    break;
380
381	case 'D':
382	    if (chunk_info[current_chunk]->type == unused)
383		msg = "Partition is already unused!";
384	    else {
385		Delete_Chunk(d, chunk_info[current_chunk]);
386		variable_set2(DISK_PARTITIONED, "yes");
387		record_chunks(d);
388	    }
389	    break;
390
391	case 'G': {
392	    char *val, geometry[80];
393
394	    snprintf(geometry, 80, "%lu/%lu/%lu", d->bios_cyl, d->bios_hd, d->bios_sect);
395	    val = msgGetInput(geometry, "Please specify the new geometry in cyl/hd/sect format.\n"
396			      "Don't forget to use the two slash (/) separator characters!\n"
397			      "It's not possible to parse the field without them.");
398	    if (val) {
399		d->bios_cyl = strtol(val, &val, 0);
400		d->bios_hd = strtol(val + 1, &val, 0);
401		d->bios_sect = strtol(val + 1, 0, 0);
402	    }
403	}
404	break;
405
406    case 'S':
407	/* Set Bootable */
408	chunk_info[current_chunk]->flags |= CHUNK_ACTIVE;
409	break;
410
411    case 'U':
412	    clear();
413	    if (msgYesNo("Are you SURE you want to Undo everything?"))
414		break;
415	    d = Open_Disk(d->name);
416	    if (!d) {
417		dialog_clear();
418		msgConfirm("Can't reopen disk %s! Internal state is probably corrupted", d->name);
419		return;
420	    }
421	    Free_Disk(dev->private);
422	    dev->private = d;
423	    variable_unset(DISK_PARTITIONED);
424	    record_chunks(d);
425	    break;
426
427	case 'W':
428	    if (!msgYesNo("Are you SURE you want to write this now?  You do also\n"
429			  "have the option of not modifying the disk until *all*\n"
430			  "configuration information has been entered, at which\n"
431			  "point you can do it all at once.  If you're unsure, then\n"
432			  "PLEASE CHOOSE NO at this dialog!  This option is DANGEROUS\n"
433			  "if you do not know EXACTLY what you are doing!")) {
434		variable_set2(DISK_PARTITIONED, "yes");
435		clear();
436
437		/* Don't trash the MBR if the first (and therefore only) chunk is marked for a truly dedicated
438		 * disk (i.e., the disklabel starts at sector 0), even in cases where the user has requested
439		 * booteasy or a "standard" MBR -- both would be fatal in this case.
440		 */
441		if ((d->chunks->part->flags & CHUNK_FORCE_ALL) != CHUNK_FORCE_ALL
442		    && (mbrContents = getBootMgr(d->name)) != NULL)
443		    Set_Boot_Mgr(d, mbrContents);
444
445		if (diskPartitionWrite(NULL) != RET_SUCCESS) {
446		    dialog_clear();
447		    msgConfirm("Disk partition write returned an error status!");
448		}
449		else {
450		    msgConfirm("Wrote FDISK partition information out successfully.");
451		}
452	    }
453	    break;
454
455	case '|':
456	    if (!msgYesNo("Are you SURE you want to go into Wizard mode?\n"
457			  "No seat belts whatsoever are provided!")) {
458		dialog_clear();
459		end_dialog();
460		DialogActive = FALSE;
461		slice_wizard(d);
462		variable_set2(DISK_PARTITIONED, "yes");
463		dialog_clear();
464		DialogActive = TRUE;
465		record_chunks(d);
466	    }
467	    else
468		msg = "Wise choice!";
469	    break;
470
471	case 'Q':
472	    chunking = FALSE;
473	    clear();
474	    /* Don't trash the MBR if the first (and therefore only) chunk is marked for a truly dedicated
475	     * disk (i.e., the disklabel starts at sector 0), even in cases where the user has requested
476	     * booteasy or a "standard" MBR -- both would be fatal in this case.
477	     */
478	    if ((d->chunks->part->flags & CHUNK_FORCE_ALL) != CHUNK_FORCE_ALL
479		&& (mbrContents = getBootMgr(d->name)) != NULL)
480		Set_Boot_Mgr(d, mbrContents);
481	    break;
482
483	default:
484	    beep();
485	    msg = "Type F1 or ? for help";
486	    break;
487	}
488    }
489    p = CheckRules(d);
490    if (p) {
491	dialog_clear();
492	msgConfirm(p);
493	free(p);
494    }
495    dialog_clear();
496}
497
498static int
499partitionHook(char *str)
500{
501    Device **devs = NULL;
502
503    /* Clip garbage off the ends */
504    string_prune(str);
505    str = string_skipwhite(str);
506    /* Try and open all the disks */
507    while (str) {
508	char *cp;
509
510	cp = index(str, '\n');
511	if (cp)
512	   *cp++ = 0;
513	if (!*str) {
514	    beep();
515	    return 0;
516	}
517	devs = deviceFind(str, DEVICE_TYPE_DISK);
518	if (!devs) {
519	    dialog_clear();
520	    msgConfirm("Unable to find disk %s!", str);
521	    return 0;
522	}
523	devs[0]->enabled = TRUE;
524	diskPartition(devs[0], (Disk *)devs[0]->private);
525	str = cp;
526    }
527    return devs ? 1 : 0;
528}
529
530int
531diskPartitionEditor(char *str)
532{
533    DMenu *menu;
534    Device **devs;
535    int i, cnt;
536    char *cp;
537
538    cp = variable_get(VAR_DISK);
539    devs = deviceFind(cp, DEVICE_TYPE_DISK);
540    cnt = deviceCount(devs);
541    if (!cnt) {
542	dialog_clear();
543	msgConfirm("No disks found!  Please verify that your disk controller is being\n"
544		   "properly probed at boot time.  See the Hardware Guide on the\n"
545		   "Documentation menu for clues on diagnosing this type of problem.");
546	i = RET_FAIL;
547    }
548    else if (cnt == 1) {
549	devs[0]->enabled = TRUE;
550	if (str && !strcmp(str, "script"))
551	    scriptPartition(devs[0], (Disk *)devs[0]->private);
552	else
553	    diskPartition(devs[0], (Disk *)devs[0]->private);
554	i = RET_SUCCESS;
555	variable_set2(DISK_SELECTED, "yes");
556    }
557    else {
558	menu = deviceCreateMenu(&MenuDiskDevices, DEVICE_TYPE_DISK, partitionHook);
559	if (!menu) {
560	    dialog_clear();
561	    msgConfirm("No devices suitable for installation found!\n\n"
562		       "Please verify that your disk controller (and attached drives)\n"
563		       "were detected properly.  This can be done by pressing the\n"
564		       "[Scroll Lock] key and using the Arrow keys to move back to\n"
565		       "the boot messages.  Press [Scroll Lock] again to return.");
566	    i = RET_FAIL;
567	}
568	else {
569	    if (!dmenuOpenSimple(menu))
570		i = RET_FAIL;
571	    else  {
572		i = RET_SUCCESS;
573		variable_set2(DISK_SELECTED, "yes");
574	    }
575	    free(menu);
576	}
577    }
578    return i;
579}
580
581int
582diskPartitionWrite(char *str)
583{
584    extern u_char boot1[], boot2[];
585    Device **devs;
586    char *cp;
587    int i;
588
589    if ((cp = variable_get(DISK_PARTITIONED)) && strcmp(cp, "yes"))
590	return RET_SUCCESS;
591    else if (!cp) {
592	dialog_clear();
593	msgConfirm("You must partition the disk(s) before this option can be used.");
594	return RET_FAIL;
595    }
596
597    devs = deviceFind(NULL, DEVICE_TYPE_DISK);
598    if (!devs) {
599	dialog_clear();
600	msgConfirm("Unable to find any disks to write to??");
601	return RET_FAIL;
602    }
603
604    for (i = 0; devs[i]; i++) {
605	Chunk *c1;
606	Disk *d = (Disk *)devs[i]->private;
607
608	if (!devs[i]->enabled)
609	    continue;
610
611	Set_Boot_Blocks(d, boot1, boot2);
612	msgNotify("Writing partition information to drive %s", d->name);
613	if (Write_Disk(d)) {
614	    dialog_clear();
615	    msgConfirm("ERROR: Unable to write data to disk %s!", d->name);
616	    return RET_FAIL;
617	}
618	/* Now scan for bad blocks, if necessary */
619	for (c1 = d->chunks->part; c1; c1 = c1->next) {
620	    if (c1->flags & CHUNK_BAD144) {
621		int ret;
622
623		msgNotify("Running bad block scan on partition %s", c1->name);
624		ret = vsystem("bad144 -v /dev/r%s 1234", c1->name);
625		if (ret) {
626		    dialog_clear();
627		    msgConfirm("Bad144 init on %s returned status of %d!", c1->name, ret);
628		}
629		ret = vsystem("bad144 -v -s /dev/r%s", c1->name);
630		if (ret) {
631		    dialog_clear();
632		    msgConfirm("Bad144 scan on %s returned status of %d!", c1->name, ret);
633		}
634	    }
635	}
636    }
637    /* Now it's not "yes", but "written" */
638    variable_set2(DISK_PARTITIONED, "written");
639    return RET_SUCCESS;
640}
641