Annotation of src/distrib/utils/sysinst/mbr.c, Revision 1.14
1.14 ! bouyer 1: /* $NetBSD: mbr.c,v 1.13 1999/04/11 22:40:20 bouyer Exp $ */
1.1 phil 2:
3: /*
4: * Copyright 1997 Piermont Information Systems Inc.
5: * All rights reserved.
1.6 jonathan 6: * * Copyright 1997 Piermont Information Systems Inc.
7: * All rights reserved.
1.1 phil 8: *
9: * Written by Philip A. Nelson for Piermont Information Systems Inc.
10: *
11: * Redistribution and use in source and binary forms, with or without
12: * modification, are permitted provided that the following conditions
13: * are met:
14: * 1. Redistributions of source code must retain the above copyright
15: * notice, this list of conditions and the following disclaimer.
16: * 2. Redistributions in binary form must reproduce the above copyright
17: * notice, this list of conditions and the following disclaimer in the
18: * documentation and/or other materials provided with the distribution.
19: * 3. All advertising materials mentioning features or use of this software
20: * must display the following acknowledgement:
21: * This product includes software develooped for the NetBSD Project by
22: * Piermont Information Systems Inc.
23: * 4. The name of Piermont Information Systems Inc. may not be used to endorse
24: * or promote products derived from this software without specific prior
25: * written permission.
26: *
27: * THIS SOFTWARE IS PROVIDED BY PIERMONT INFORMATION SYSTEMS INC. ``AS IS''
28: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30: * ARE DISCLAIMED. IN NO EVENT SHALL PIERMONT INFORMATION SYSTEMS INC. BE
31: * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
37: * THE POSSIBILITY OF SUCH DAMAGE.
38: *
39: */
40:
1.10 fvdl 41: /*
42: * Following applies to the geometry guessing code
43: */
44:
45: /*
46: * Mach Operating System
47: * Copyright (c) 1992 Carnegie Mellon University
48: * All Rights Reserved.
49: *
50: * Permission to use, copy, modify and distribute this software and its
51: * documentation is hereby granted, provided that both the copyright
52: * notice and this permission notice appear in all copies of the
53: * software, derivative works or modified versions, and any portions
54: * thereof, and that both notices appear in supporting documentation.
55: *
56: * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
57: * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
58: * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
59: *
60: * Carnegie Mellon requests users of this software to return to
61: *
62: * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU
63: * School of Computer Science
64: * Carnegie Mellon University
65: * Pittsburgh PA 15213-3890
66: *
67: * any improvements or extensions that they make and grant Carnegie Mellon
68: * the rights to redistribute these changes.
69: */
70:
1.6 jonathan 71: /* mbr.c -- DOS Master Boot Record editing code */
1.1 phil 72:
1.10 fvdl 73: #include <sys/param.h>
74: #include <sys/types.h>
1.1 phil 75: #include <stdio.h>
1.10 fvdl 76: #include <unistd.h>
77: #include <fcntl.h>
1.6 jonathan 78: #include <util.h>
1.1 phil 79: #include "defs.h"
1.10 fvdl 80: #include "mbr.h"
1.1 phil 81: #include "md.h"
82: #include "msg_defs.h"
83: #include "menu_defs.h"
1.10 fvdl 84: #include "endian.h"
1.1 phil 85:
86: struct part_id {
87: int id;
88: char *name;
89: } part_ids[] = {
90: {0, "unused"},
1.10 fvdl 91: {MBR_PTYPE_FAT12, "Primary DOS, 12 bit FAT"},
92: {MBR_PTYPE_FAT16S, "Primary DOS, 16 bit FAT <32M"},
93: {MBR_PTYPE_EXT, "Extended DOS"},
94: {MBR_PTYPE_FAT16B, "Primary DOS, 16-bit FAT >32MB"},
95: {MBR_PTYPE_NTFS, "NTFS"},
96: {MBR_PTYPE_LNXSWAP, "Linux swap"},
97: {MBR_PTYPE_LNXEXT2, "Linux native"},
98: {MBR_PTYPE_386BSD, "old NetBSD/FreeBSD/386BSD"},
99: {MBR_PTYPE_NETBSD, "NetBSD"},
1.1 phil 100: {-1, "Unknown"},
101: };
102:
1.10 fvdl 103: int dosptyp_nbsd = MBR_PTYPE_NETBSD;
104:
105: static int get_mapping __P((struct mbr_partition *, int, int *, int *, int *,
106: long *absolute));
107: static void convert_mbr_chs __P((int, int, int, u_int8_t *, u_int8_t *,
108: u_int8_t *, u_int32_t));
1.6 jonathan 109:
110:
111: /*
112: * First, geometry stuff...
113: */
1.7 mrg 114: int
115: check_geom()
1.6 jonathan 116: {
1.7 mrg 117:
1.6 jonathan 118: return bcyl <= 1024 && bsec < 64 && bcyl > 0 && bhead > 0 && bsec > 0;
119: }
120:
121: /*
122: * get C/H/S geometry from user via menu interface and
123: * store in globals.
124: */
1.7 mrg 125: void
1.10 fvdl 126: set_bios_geom(cyl, head, sec)
127: int cyl, head, sec;
1.6 jonathan 128: {
129: char res[80];
1.7 mrg 130:
1.6 jonathan 131: msg_display_add(MSG_setbiosgeom);
1.10 fvdl 132: snprintf(res, 80, "%d", cyl);
1.8 rvb 133: msg_prompt_add(MSG_cylinders, res, res, 80);
1.6 jonathan 134: bcyl = atoi(res);
1.8 rvb 135:
1.10 fvdl 136: snprintf(res, 80, "%d", head);
1.8 rvb 137: msg_prompt_add(MSG_heads, res, res, 80);
1.6 jonathan 138: bhead = atoi(res);
1.8 rvb 139:
1.10 fvdl 140: snprintf(res, 80, "%d", sec);
1.8 rvb 141: msg_prompt_add(MSG_sectors, res, res, 80);
1.6 jonathan 142: bsec = atoi(res);
143: }
144:
1.7 mrg 145: void
146: disp_cur_geom()
147: {
1.6 jonathan 148:
149: msg_display_add(MSG_realgeom, dlcyl, dlhead, dlsec);
150: msg_display_add(MSG_biosgeom, bcyl, bhead, bsec);
151: }
152:
153:
154: /*
155: * Then, the partition stuff...
156: */
157: int
1.7 mrg 158: otherpart(id)
159: int id;
1.6 jonathan 160: {
1.7 mrg 161:
1.10 fvdl 162: return (id != 0 && id != MBR_PTYPE_386BSD && id != MBR_PTYPE_NETBSD);
1.6 jonathan 163: }
164:
165: int
1.7 mrg 166: ourpart(id)
167: int id;
1.6 jonathan 168: {
1.7 mrg 169:
1.10 fvdl 170: return (id == MBR_PTYPE_386BSD || id == MBR_PTYPE_NETBSD);
1.6 jonathan 171: }
172:
173: /*
174: * Let user change incore Master Boot Record partitions via menu.
175: */
1.7 mrg 176: int
1.10 fvdl 177: edit_mbr(partition)
178: struct mbr_partition *partition;
1.6 jonathan 179: {
180: int i, j;
181:
182: /* Ask full/part */
1.10 fvdl 183:
184: /* XXX this sucks ("part" is used in menus, no param passing there) */
185: part = partition;
1.7 mrg 186: msg_display(MSG_fullpart, diskdev);
187: process_menu(MENU_fullpart);
1.6 jonathan 188:
189: /* DOS fdisk label checking and value setting. */
190: if (usefull) {
191: int otherparts = 0;
192: int ourparts = 0;
193:
194: int i;
195: /* Count nonempty, non-BSD partitions. */
1.10 fvdl 196: for (i = 0; i < NMBRPART; i++) {
1.14 ! bouyer 197: otherparts += otherpart(part[i].mbrp_typ);
1.6 jonathan 198: /* check for dualboot *bsd too */
1.14 ! bouyer 199: ourparts += ourpart(part[i].mbrp_typ);
1.6 jonathan 200: }
201:
202: /* Ask if we really want to blow away non-NetBSD stuff */
203: if (otherparts != 0 || ourparts > 1) {
1.7 mrg 204: msg_display(MSG_ovrwrite);
205: process_menu(MENU_noyes);
1.6 jonathan 206: if (!yesno) {
1.9 garbled 207: if (logging)
208: (void)fprintf(log, "User answered no to destroy other data, aborting.\n");
1.6 jonathan 209: return 0;
210: }
211: }
212:
213: /* Set the partition information for full disk usage. */
1.14 ! bouyer 214: part[0].mbrp_typ = part[0].mbrp_flag = 0;
! 215: part[0].mbrp_start = part[0].mbrp_size = 0;
! 216: part[1].mbrp_typ = part[0].mbrp_flag = 0;
! 217: part[1].mbrp_start = part[0].mbrp_size = 0;
! 218: part[2].mbrp_typ = part[0].mbrp_flag = 0;
! 219: part[2].mbrp_start = part[0].mbrp_size = 0;
1.10 fvdl 220: part[3].mbrp_typ = dosptyp_nbsd;
221: part[3].mbrp_size = bsize - bsec;
222: part[3].mbrp_start = bsec;
223: part[3].mbrp_flag = 0x80;
1.6 jonathan 224:
225: ptstart = bsec;
226: ptsize = bsize - bsec;
227: fsdsize = dlsize;
228: fsptsize = dlsize - bsec;
229: fsdmb = fsdsize / MEG;
230: activepart = 3;
231: } else {
232: int numbsd, overlap;
233: int numfreebsd, freebsdpart; /* dual-boot */
234:
235: /* Ask for sizes, which partitions, ... */
236: ask_sizemult();
237: bsdpart = freebsdpart = -1;
238: activepart = -1;
1.7 mrg 239: for (i = 0; i<4; i++)
1.13 bouyer 240: if (part[i].mbrp_flag != 0) {
1.6 jonathan 241: activepart = i;
1.13 bouyer 242: part[i].mbrp_flag = 0;
243: }
1.6 jonathan 244: do {
245: process_menu (MENU_editparttable);
246: numbsd = 0;
247: bsdpart = -1;
248: freebsdpart = -1;
249: numfreebsd = 0;
250: overlap = 0;
251: yesno = 0;
252: for (i=0; i<4; i++) {
1.7 mrg 253: /* Count 386bsd/FreeBSD/NetBSD(old) partitions */
1.10 fvdl 254: if (part[i].mbrp_typ == MBR_PTYPE_386BSD) {
1.6 jonathan 255: freebsdpart = i;
256: numfreebsd++;
257: }
258: /* Count NetBSD-only partitions */
1.10 fvdl 259: if (part[i].mbrp_typ == MBR_PTYPE_NETBSD) {
1.6 jonathan 260: bsdpart = i;
261: numbsd++;
262: }
263: for (j = i+1; j<4; j++)
1.10 fvdl 264: if (partsoverlap(part, i,j))
1.6 jonathan 265: overlap = 1;
266: }
267:
268: /* If no new-NetBSD partition, use 386bsd instead */
269: if (numbsd == 0 && numfreebsd > 0) {
270: numbsd = numfreebsd;
271: bsdpart = freebsdpart;
272: /* XXX check partition type? */
273: }
274:
275: /* Check for overlap or multiple native partitions */
276: if (overlap || numbsd != 1) {
1.7 mrg 277: msg_display(MSG_reeditpart);
278: process_menu(MENU_yesno);
1.6 jonathan 279: }
280: } while (yesno && (numbsd != 1 || overlap));
1.11 fvdl 281:
282: if (activepart != -1)
283: part[activepart].mbrp_flag = 0x80;
1.6 jonathan 284:
285: if (numbsd == 0) {
1.7 mrg 286: msg_display(MSG_nobsdpart);
287: process_menu(MENU_ok);
1.6 jonathan 288: return 0;
289: }
290:
291: if (numbsd > 1) {
1.7 mrg 292: msg_display(MSG_multbsdpart, bsdpart);
293: process_menu(MENU_ok);
1.6 jonathan 294: }
295:
1.10 fvdl 296: ptstart = part[bsdpart].mbrp_start;
297: ptsize = part[bsdpart].mbrp_size;
1.6 jonathan 298: fsdsize = dlsize;
299: if (ptstart + ptsize < bsize)
300: fsptsize = ptsize;
301: else
302: fsptsize = dlsize - ptstart;
303: fsdmb = fsdsize / MEG;
304:
305: /* Ask if a boot selector is wanted. XXXX */
306: }
307:
308: /* Compute minimum NetBSD partition sizes (in sectors). */
309: minfsdmb = (80 + 4*rammb) * (MEG / sectorsize);
310:
311: return 1;
312: }
313:
1.7 mrg 314: int
1.10 fvdl 315: partsoverlap(part, i, j)
316: struct mbr_partition *part;
1.7 mrg 317: int i;
318: int j;
1.1 phil 319: {
1.7 mrg 320:
1.10 fvdl 321: if (part[i].mbrp_size == 0 || part[j].mbrp_size == 0)
1.1 phil 322: return 0;
323:
1.2 phil 324: return
1.10 fvdl 325: (part[i].mbrp_start < part[j].mbrp_start &&
326: part[i].mbrp_start + part[i].mbrp_size > part[j].mbrp_start)
1.1 phil 327: ||
1.10 fvdl 328: (part[i].mbrp_start > part[j].mbrp_start &&
329: part[i].mbrp_start < part[j].mbrp_start + part[j].mbrp_size)
1.1 phil 330: ||
1.10 fvdl 331: (part[i].mbrp_start == part[j].mbrp_start);
1.1 phil 332: }
333:
1.7 mrg 334: void
1.10 fvdl 335: disp_cur_part(part, sel, disp)
336: struct mbr_partition *part;
1.7 mrg 337: int sel;
338: int disp;
1.1 phil 339: {
1.4 fvdl 340: int i, j, start, stop, rsize, rend;
1.1 phil 341:
342: if (disp < 0)
343: start = 0, stop = 4;
344: else
345: start = disp, stop = disp+1;
1.7 mrg 346: msg_display_add(MSG_part_head, multname, multname, multname);
347: for (i = start; i < stop; i++) {
348: if (sel == i)
349: msg_standout();
1.10 fvdl 350: if (part[i].mbrp_size == 0 && part[i].mbrp_start == 0)
1.7 mrg 351: msg_printf_add("%d %36s ", i, "");
1.4 fvdl 352: else {
1.10 fvdl 353: rsize = part[i].mbrp_size / sizemult;
354: if (part[i].mbrp_size % sizemult)
1.4 fvdl 355: rsize++;
1.10 fvdl 356: rend = (part[i].mbrp_start + part[i].mbrp_size) / sizemult;
357: if ((part[i].mbrp_size + part[i].mbrp_size) % sizemult)
1.4 fvdl 358: rend++;
359: msg_printf_add("%d %12d%12d%12d ", i,
1.10 fvdl 360: part[i].mbrp_start / sizemult, rsize, rend);
1.4 fvdl 361: }
1.1 phil 362: for (j = 0; part_ids[j].id != -1 &&
1.10 fvdl 363: part_ids[j].id != part[i].mbrp_typ; j++);
364: msg_printf_add("%s\n", part_ids[j].name);
365: if (sel == i)
366: msg_standend();
367: }
368: }
369:
370: int
371: read_mbr(disk, buf, len)
372: char *disk, *buf;
373: int len;
374: {
375: char diskpath[MAXPATHLEN];
376: int fd, i;
377: struct mbr_partition *mbrp;
378:
379: /* Open the disk. */
380: fd = opendisk(disk, O_RDONLY, diskpath, sizeof(diskpath), 0);
381: if (fd < 0)
382: return -1;
383:
384: if (lseek(fd, MBR_BBSECTOR * MBR_SECSIZE, SEEK_SET) < 0) {
385: close(fd);
386: return -1;
387: }
388: if (read(fd, buf, len) < len) {
389: close(fd);
390: return -1;
391: }
392:
393: if (valid_mbr(buf)) {
394: mbrp = (struct mbr_partition *)&buf[MBR_PARTOFF];
395: for (i = 0; i < NMBRPART; i++) {
396: if (mbrp[i].mbrp_typ != 0) {
397: mbrp[i].mbrp_start =
398: le_to_native32(mbrp[i].mbrp_start);
399: mbrp[i].mbrp_size =
400: le_to_native32(mbrp[i].mbrp_size);
401: }
402: }
403: }
404:
405: (void)close(fd);
406: return 0;
407: }
408:
409: int
410: write_mbr(disk, buf, len)
411: char *disk, *buf;
412: int len;
413: {
414: char diskpath[MAXPATHLEN];
415: int fd, i, ret = 0;
416: struct mbr_partition *mbrp;
417: u_int32_t pstart, psize;
418:
419: /* Open the disk. */
420: fd = opendisk(disk, O_WRONLY, diskpath, sizeof(diskpath), 0);
421: if (fd < 0)
422: return -1;
423:
424: if (lseek(fd, MBR_BBSECTOR * MBR_SECSIZE, SEEK_SET) < 0) {
425: close(fd);
426: return -1;
427: }
428:
429: mbrp = (struct mbr_partition *)&buf[MBR_PARTOFF];
430: for (i = 0; i < NMBRPART; i++) {
1.14 ! bouyer 431: if (mbrp[i].mbrp_start == 0 &&
! 432: mbrp[i].mbrp_size == 0) {
! 433: mbrp[i].mbrp_scyl = 0;
! 434: mbrp[i].mbrp_shd = 0;
! 435: mbrp[i].mbrp_ssect = 0;
! 436: mbrp[i].mbrp_ecyl = 0;
! 437: mbrp[i].mbrp_ehd = 0;
! 438: mbrp[i].mbrp_esect = 0;
! 439: } else {
1.10 fvdl 440: pstart = mbrp[i].mbrp_start;
441: psize = mbrp[i].mbrp_size;
442: mbrp[i].mbrp_start = native_to_le32(pstart);
443: mbrp[i].mbrp_size = native_to_le32(psize);
444: convert_mbr_chs(bcyl, bhead, bsec,
445: &mbrp[i].mbrp_scyl, &mbrp[i].mbrp_shd,
446: &mbrp[i].mbrp_ssect, pstart);
447: convert_mbr_chs(bcyl, bhead, bsec,
448: &mbrp[i].mbrp_ecyl, &mbrp[i].mbrp_ehd,
449: &mbrp[i].mbrp_esect, pstart + psize);
450: }
451: }
452:
453: if (write(fd, buf, len) < 0)
454: ret = -1;
455:
456: (void)close(fd);
457: return ret;
458: }
459:
460: int
461: valid_mbr(buf)
462: char *buf;
463: {
1.12 fvdl 464: u_int16_t magic;
1.10 fvdl 465:
1.12 fvdl 466: magic = *((u_int16_t *)&buf[MBR_MAGICOFF]);
1.10 fvdl 467:
468: return (le_to_native16(magic) == MBR_MAGIC);
469: }
470:
471: static void
472: convert_mbr_chs(cyl, head, sec, cylp, headp, secp, relsecs)
473: int cyl, head, sec;
474: u_int8_t *cylp, *headp, *secp;
475: u_int32_t relsecs;
476: {
477: unsigned int tcyl, temp, thead, tsec;
478:
479: temp = head * sec;
480: tcyl = relsecs / temp;
481:
482: if (tcyl >= 1024) {
483: *cylp = *headp = *secp = 0xff;
484: return;
485: }
486:
487: relsecs %= temp;
488: thead = relsecs / sec;
489:
490: tsec = (relsecs % sec) + 1;
491:
492: *cylp = MBR_PUT_LSCYL(tcyl);
493: *headp = thead;
494: *secp = MBR_PUT_MSCYLANDSEC(tcyl, tsec);
495: }
496:
497: /*
498: * This function is ONLY to be used as a last resort to provide a
499: * hint for the user. Ports should provide a more reliable way
500: * of getting the BIOS geometry. The i386 code, for example,
501: * uses the BIOS geometry as passed on from the bootblocks,
502: * and only uses this as a hint to the user when that information
503: * is not present, or a match could not be made with a NetBSD
504: * device.
505: */
506: int
507: guess_biosgeom_from_mbr(buf, cyl, head, sec)
508: char *buf;
509: int *cyl, *head, *sec;
510: {
511: struct mbr_partition *parts = (struct mbr_partition *)&buf[MBR_PARTOFF];
512: int cylinders = -1, heads = -1, sectors = -1, i, j;
513: int c1, h1, s1, c2, h2, s2;
514: long a1, a2;
515: quad_t num, denom;
516:
517: *cyl = *head = *sec = -1;
518:
519: /* Try to deduce the number of heads from two different mappings. */
520: for (i = 0; i < NMBRPART * 2; i++) {
521: if (get_mapping(parts, i, &c1, &h1, &s1, &a1) < 0)
522: continue;
523: for (j = 0; j < 8; j++) {
524: if (get_mapping(parts, j, &c2, &h2, &s2, &a2) < 0)
525: continue;
526: num = (quad_t)h1*(a2-s2) - (quad_t)h2*(a1-s1);
527: denom = (quad_t)c2*(a1-s1) - (quad_t)c1*(a2-s2);
528: if (denom != 0 && num % denom == 0) {
529: heads = num / denom;
530: break;
531: }
532: }
533: if (heads != -1)
534: break;
535: }
536:
537: if (heads == -1)
538: return -1;
539:
540: /* Now figure out the number of sectors from a single mapping. */
541: for (i = 0; i < NMBRPART * 2; i++) {
542: if (get_mapping(parts, i, &c1, &h1, &s1, &a1) < 0)
543: continue;
544: num = a1 - s1;
545: denom = c1 * heads + h1;
546: if (denom != 0 && num % denom == 0) {
547: sectors = num / denom;
548: break;
549: }
550: }
551:
552: if (sectors == -1)
553: return -1;
554:
555: /*
556: * Estimate the number of cylinders.
557: * XXX relies on get_disks having been called.
558: */
559: cylinders = disk->dd_totsec / heads / sectors;
560:
561: /* Now verify consistency with each of the partition table entries.
562: * Be willing to shove cylinders up a little bit to make things work,
563: * but translation mismatches are fatal. */
564: for (i = 0; i < NMBRPART * 2; i++) {
565: if (get_mapping(parts, i, &c1, &h1, &s1, &a1) < 0)
566: continue;
567: if (sectors * (c1 * heads + h1) + s1 != a1)
568: return -1;
569: if (c1 >= cylinders)
570: cylinders = c1 + 1;
571: }
572:
573: /* Everything checks out. Reset the geometry to use for further
574: * calculations. */
575: *cyl = cylinders;
576: *head = heads;
577: *sec = sectors;
578: return 0;
579: }
580:
581: static int
582: get_mapping(parts, i, cylinder, head, sector, absolute)
583: struct mbr_partition *parts;
584: int i, *cylinder, *head, *sector;
585: long *absolute;
586: {
587: struct mbr_partition *part = &parts[i / 2];
588:
589: if (part->mbrp_typ == 0)
590: return -1;
591: if (i % 2 == 0) {
592: *cylinder = MBR_PCYL(part->mbrp_scyl, part->mbrp_ssect);
593: *head = part->mbrp_shd;
594: *sector = MBR_PSECT(part->mbrp_ssect) - 1;
595: *absolute = part->mbrp_start;
596: } else {
597: *cylinder = MBR_PCYL(part->mbrp_ecyl, part->mbrp_esect);
598: *head = part->mbrp_ehd;
599: *sector = MBR_PSECT(part->mbrp_esect) - 1;
600: *absolute = part->mbrp_start + part->mbrp_size - 1;
1.3 phil 601: }
1.10 fvdl 602: return 0;
1.1 phil 603: }
CVSweb <webmaster@jp.NetBSD.org>