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