16bc50445SMarcel Moolenaar /*- 2*4d846d26SWarner Losh * SPDX-License-Identifier: BSD-2-Clause 33728855aSPedro F. Giffuni * 4f6aa3fccSMarcel Moolenaar * Copyright (c) 2007, 2008 Marcel Moolenaar 56bc50445SMarcel Moolenaar * All rights reserved. 66bc50445SMarcel Moolenaar * 76bc50445SMarcel Moolenaar * Redistribution and use in source and binary forms, with or without 86bc50445SMarcel Moolenaar * modification, are permitted provided that the following conditions 96bc50445SMarcel Moolenaar * are met: 106bc50445SMarcel Moolenaar * 116bc50445SMarcel Moolenaar * 1. Redistributions of source code must retain the above copyright 126bc50445SMarcel Moolenaar * notice, this list of conditions and the following disclaimer. 136bc50445SMarcel Moolenaar * 2. Redistributions in binary form must reproduce the above copyright 146bc50445SMarcel Moolenaar * notice, this list of conditions and the following disclaimer in the 156bc50445SMarcel Moolenaar * documentation and/or other materials provided with the distribution. 166bc50445SMarcel Moolenaar * 176bc50445SMarcel Moolenaar * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 186bc50445SMarcel Moolenaar * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 196bc50445SMarcel Moolenaar * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 206bc50445SMarcel Moolenaar * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 216bc50445SMarcel Moolenaar * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 226bc50445SMarcel Moolenaar * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 236bc50445SMarcel Moolenaar * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 246bc50445SMarcel Moolenaar * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 256bc50445SMarcel Moolenaar * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 266bc50445SMarcel Moolenaar * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 276bc50445SMarcel Moolenaar */ 286bc50445SMarcel Moolenaar 296bc50445SMarcel Moolenaar #include <sys/cdefs.h> 306bc50445SMarcel Moolenaar __FBSDID("$FreeBSD$"); 316bc50445SMarcel Moolenaar 326bc50445SMarcel Moolenaar #include <sys/param.h> 336bc50445SMarcel Moolenaar #include <sys/bio.h> 346bc50445SMarcel Moolenaar #include <sys/diskmbr.h> 356bc50445SMarcel Moolenaar #include <sys/endian.h> 366bc50445SMarcel Moolenaar #include <sys/kernel.h> 376bc50445SMarcel Moolenaar #include <sys/kobj.h> 386bc50445SMarcel Moolenaar #include <sys/limits.h> 396bc50445SMarcel Moolenaar #include <sys/lock.h> 406bc50445SMarcel Moolenaar #include <sys/malloc.h> 416bc50445SMarcel Moolenaar #include <sys/mutex.h> 426bc50445SMarcel Moolenaar #include <sys/queue.h> 436bc50445SMarcel Moolenaar #include <sys/sbuf.h> 446bc50445SMarcel Moolenaar #include <sys/systm.h> 45cb08c2ccSAlexander Leidinger #include <sys/sysctl.h> 466bc50445SMarcel Moolenaar #include <geom/geom.h> 470dd7f00cSAndrey V. Elsukov #include <geom/geom_int.h> 486bc50445SMarcel Moolenaar #include <geom/part/g_part.h> 496bc50445SMarcel Moolenaar 506bc50445SMarcel Moolenaar #include "g_part_if.h" 516bc50445SMarcel Moolenaar 52cb08c2ccSAlexander Leidinger FEATURE(geom_part_mbr, "GEOM partitioning class for MBR support"); 53cb08c2ccSAlexander Leidinger 54fb86534cSAndrey V. Elsukov SYSCTL_DECL(_kern_geom_part); 557029da5cSPawel Biernacki static SYSCTL_NODE(_kern_geom_part, OID_AUTO, mbr, 567029da5cSPawel Biernacki CTLFLAG_RW | CTLFLAG_MPSAFE, 0, 575b990a94SBaptiste Daroussin "GEOM_PART_MBR Master Boot Record"); 58fb86534cSAndrey V. Elsukov 5936b16d1fSAndrey V. Elsukov static u_int enforce_chs = 0; 60fb86534cSAndrey V. Elsukov SYSCTL_UINT(_kern_geom_part_mbr, OID_AUTO, enforce_chs, 6136b16d1fSAndrey V. Elsukov CTLFLAG_RWTUN, &enforce_chs, 0, "Enforce alignment to CHS addressing"); 62fb86534cSAndrey V. Elsukov 636bc50445SMarcel Moolenaar #define MBRSIZE 512 646bc50445SMarcel Moolenaar 656bc50445SMarcel Moolenaar struct g_part_mbr_table { 666bc50445SMarcel Moolenaar struct g_part_table base; 676bc50445SMarcel Moolenaar u_char mbr[MBRSIZE]; 686bc50445SMarcel Moolenaar }; 696bc50445SMarcel Moolenaar 706bc50445SMarcel Moolenaar struct g_part_mbr_entry { 716bc50445SMarcel Moolenaar struct g_part_entry base; 726bc50445SMarcel Moolenaar struct dos_partition ent; 736bc50445SMarcel Moolenaar }; 746bc50445SMarcel Moolenaar 756bc50445SMarcel Moolenaar static int g_part_mbr_add(struct g_part_table *, struct g_part_entry *, 766bc50445SMarcel Moolenaar struct g_part_parms *); 774d32fcb4SMarcel Moolenaar static int g_part_mbr_bootcode(struct g_part_table *, struct g_part_parms *); 786bc50445SMarcel Moolenaar static int g_part_mbr_create(struct g_part_table *, struct g_part_parms *); 796bc50445SMarcel Moolenaar static int g_part_mbr_destroy(struct g_part_table *, struct g_part_parms *); 80f4fddf53SWarner Losh static void g_part_mbr_dumpconf(struct g_part_table *, struct g_part_entry *, 815db67052SMarcel Moolenaar struct sbuf *, const char *); 826bc50445SMarcel Moolenaar static int g_part_mbr_dumpto(struct g_part_table *, struct g_part_entry *); 836bc50445SMarcel Moolenaar static int g_part_mbr_modify(struct g_part_table *, struct g_part_entry *, 846bc50445SMarcel Moolenaar struct g_part_parms *); 85f4fddf53SWarner Losh static const char *g_part_mbr_name(struct g_part_table *, struct g_part_entry *, 866bc50445SMarcel Moolenaar char *, size_t); 876bc50445SMarcel Moolenaar static int g_part_mbr_probe(struct g_part_table *, struct g_consumer *); 886bc50445SMarcel Moolenaar static int g_part_mbr_read(struct g_part_table *, struct g_consumer *); 89f6aa3fccSMarcel Moolenaar static int g_part_mbr_setunset(struct g_part_table *, struct g_part_entry *, 90f6aa3fccSMarcel Moolenaar const char *, unsigned int); 916bc50445SMarcel Moolenaar static const char *g_part_mbr_type(struct g_part_table *, struct g_part_entry *, 926bc50445SMarcel Moolenaar char *, size_t); 936bc50445SMarcel Moolenaar static int g_part_mbr_write(struct g_part_table *, struct g_consumer *); 943f71c319SMarcel Moolenaar static int g_part_mbr_resize(struct g_part_table *, struct g_part_entry *, 953f71c319SMarcel Moolenaar struct g_part_parms *); 966bc50445SMarcel Moolenaar 976bc50445SMarcel Moolenaar static kobj_method_t g_part_mbr_methods[] = { 986bc50445SMarcel Moolenaar KOBJMETHOD(g_part_add, g_part_mbr_add), 994d32fcb4SMarcel Moolenaar KOBJMETHOD(g_part_bootcode, g_part_mbr_bootcode), 1006bc50445SMarcel Moolenaar KOBJMETHOD(g_part_create, g_part_mbr_create), 1016bc50445SMarcel Moolenaar KOBJMETHOD(g_part_destroy, g_part_mbr_destroy), 1025db67052SMarcel Moolenaar KOBJMETHOD(g_part_dumpconf, g_part_mbr_dumpconf), 1036bc50445SMarcel Moolenaar KOBJMETHOD(g_part_dumpto, g_part_mbr_dumpto), 1046bc50445SMarcel Moolenaar KOBJMETHOD(g_part_modify, g_part_mbr_modify), 1053f71c319SMarcel Moolenaar KOBJMETHOD(g_part_resize, g_part_mbr_resize), 1066bc50445SMarcel Moolenaar KOBJMETHOD(g_part_name, g_part_mbr_name), 1076bc50445SMarcel Moolenaar KOBJMETHOD(g_part_probe, g_part_mbr_probe), 1086bc50445SMarcel Moolenaar KOBJMETHOD(g_part_read, g_part_mbr_read), 109f6aa3fccSMarcel Moolenaar KOBJMETHOD(g_part_setunset, g_part_mbr_setunset), 1106bc50445SMarcel Moolenaar KOBJMETHOD(g_part_type, g_part_mbr_type), 1116bc50445SMarcel Moolenaar KOBJMETHOD(g_part_write, g_part_mbr_write), 1126bc50445SMarcel Moolenaar { 0, 0 } 1136bc50445SMarcel Moolenaar }; 1146bc50445SMarcel Moolenaar 1156bc50445SMarcel Moolenaar static struct g_part_scheme g_part_mbr_scheme = { 1166bc50445SMarcel Moolenaar "MBR", 1176bc50445SMarcel Moolenaar g_part_mbr_methods, 1186bc50445SMarcel Moolenaar sizeof(struct g_part_mbr_table), 1196bc50445SMarcel Moolenaar .gps_entrysz = sizeof(struct g_part_mbr_entry), 1206bc50445SMarcel Moolenaar .gps_minent = NDOSPART, 1216bc50445SMarcel Moolenaar .gps_maxent = NDOSPART, 1224d32fcb4SMarcel Moolenaar .gps_bootcodesz = MBRSIZE, 1236bc50445SMarcel Moolenaar }; 1244ffca444SMarcel Moolenaar G_PART_SCHEME_DECLARE(g_part_mbr); 12574d6c131SKyle Evans MODULE_VERSION(geom_part_mbr, 0); 1266bc50445SMarcel Moolenaar 12788007f61SAndrey V. Elsukov static struct g_part_mbr_alias { 12888007f61SAndrey V. Elsukov u_char typ; 12988007f61SAndrey V. Elsukov int alias; 13088007f61SAndrey V. Elsukov } mbr_alias_match[] = { 13188007f61SAndrey V. Elsukov { DOSPTYP_386BSD, G_PART_ALIAS_FREEBSD }, 132a0a8412bSEd Maste { DOSPTYP_APPLE_BOOT, G_PART_ALIAS_APPLE_BOOT }, 133a0a8412bSEd Maste { DOSPTYP_APPLE_UFS, G_PART_ALIAS_APPLE_UFS }, 13476db6c87SEd Maste { DOSPTYP_EFI, G_PART_ALIAS_EFI }, 13588007f61SAndrey V. Elsukov { DOSPTYP_EXT, G_PART_ALIAS_EBR }, 136a0a8412bSEd Maste { DOSPTYP_EXTLBA, G_PART_ALIAS_EBR }, 13710f29053SGavin Atkinson { DOSPTYP_FAT16, G_PART_ALIAS_MS_FAT16 }, 13888007f61SAndrey V. Elsukov { DOSPTYP_FAT32, G_PART_ALIAS_MS_FAT32 }, 139b525a10aSEd Maste { DOSPTYP_FAT32LBA, G_PART_ALIAS_MS_FAT32LBA }, 140a0a8412bSEd Maste { DOSPTYP_HFS, G_PART_ALIAS_APPLE_HFS }, 14148ef46e5SAndrey V. Elsukov { DOSPTYP_LDM, G_PART_ALIAS_MS_LDM_DATA }, 14288007f61SAndrey V. Elsukov { DOSPTYP_LINLVM, G_PART_ALIAS_LINUX_LVM }, 14388007f61SAndrey V. Elsukov { DOSPTYP_LINRAID, G_PART_ALIAS_LINUX_RAID }, 144a0a8412bSEd Maste { DOSPTYP_LINSWP, G_PART_ALIAS_LINUX_SWAP }, 145a0a8412bSEd Maste { DOSPTYP_LINUX, G_PART_ALIAS_LINUX_DATA }, 146a0a8412bSEd Maste { DOSPTYP_NTFS, G_PART_ALIAS_MS_NTFS }, 1471ee0f089SNathan Whitehorn { DOSPTYP_PPCBOOT, G_PART_ALIAS_PREP_BOOT }, 148b20e4de3SDmitry Morozovsky { DOSPTYP_VMFS, G_PART_ALIAS_VMFS }, 149b20e4de3SDmitry Morozovsky { DOSPTYP_VMKDIAG, G_PART_ALIAS_VMKDIAG }, 15088007f61SAndrey V. Elsukov }; 15188007f61SAndrey V. Elsukov 1526bc50445SMarcel Moolenaar static int 1536bc50445SMarcel Moolenaar mbr_parse_type(const char *type, u_char *dp_typ) 1546bc50445SMarcel Moolenaar { 1556bc50445SMarcel Moolenaar const char *alias; 1566bc50445SMarcel Moolenaar char *endp; 1576bc50445SMarcel Moolenaar long lt; 15888007f61SAndrey V. Elsukov int i; 1596bc50445SMarcel Moolenaar 1606bc50445SMarcel Moolenaar if (type[0] == '!') { 1616bc50445SMarcel Moolenaar lt = strtol(type + 1, &endp, 0); 1626bc50445SMarcel Moolenaar if (type[1] == '\0' || *endp != '\0' || lt <= 0 || lt >= 256) 1636bc50445SMarcel Moolenaar return (EINVAL); 1646bc50445SMarcel Moolenaar *dp_typ = (u_char)lt; 1656bc50445SMarcel Moolenaar return (0); 1666bc50445SMarcel Moolenaar } 16763b6b7a7SPedro F. Giffuni for (i = 0; i < nitems(mbr_alias_match); i++) { 16888007f61SAndrey V. Elsukov alias = g_part_alias_name(mbr_alias_match[i].alias); 16988007f61SAndrey V. Elsukov if (strcasecmp(type, alias) == 0) { 17088007f61SAndrey V. Elsukov *dp_typ = mbr_alias_match[i].typ; 1716bc50445SMarcel Moolenaar return (0); 1726bc50445SMarcel Moolenaar } 173c6b2b6fcSRui Paulo } 1746bc50445SMarcel Moolenaar return (EINVAL); 1756bc50445SMarcel Moolenaar } 1766bc50445SMarcel Moolenaar 177028de878SMarcel Moolenaar static int 178028de878SMarcel Moolenaar mbr_probe_bpb(u_char *bpb) 179028de878SMarcel Moolenaar { 180028de878SMarcel Moolenaar uint16_t secsz; 181028de878SMarcel Moolenaar uint8_t clstsz; 182028de878SMarcel Moolenaar 183028de878SMarcel Moolenaar #define PO2(x) ((x & (x - 1)) == 0) 184028de878SMarcel Moolenaar secsz = le16dec(bpb); 185028de878SMarcel Moolenaar if (secsz < 512 || secsz > 4096 || !PO2(secsz)) 186028de878SMarcel Moolenaar return (0); 187028de878SMarcel Moolenaar clstsz = bpb[2]; 188028de878SMarcel Moolenaar if (clstsz < 1 || clstsz > 128 || !PO2(clstsz)) 189028de878SMarcel Moolenaar return (0); 190028de878SMarcel Moolenaar #undef PO2 191028de878SMarcel Moolenaar 192028de878SMarcel Moolenaar return (1); 193028de878SMarcel Moolenaar } 194028de878SMarcel Moolenaar 1950081f96eSMarcel Moolenaar static void 1960081f96eSMarcel Moolenaar mbr_set_chs(struct g_part_table *table, uint32_t lba, u_char *cylp, u_char *hdp, 1970081f96eSMarcel Moolenaar u_char *secp) 1980081f96eSMarcel Moolenaar { 1990081f96eSMarcel Moolenaar uint32_t cyl, hd, sec; 2000081f96eSMarcel Moolenaar 2010081f96eSMarcel Moolenaar sec = lba % table->gpt_sectors + 1; 2020081f96eSMarcel Moolenaar lba /= table->gpt_sectors; 2030081f96eSMarcel Moolenaar hd = lba % table->gpt_heads; 2040081f96eSMarcel Moolenaar lba /= table->gpt_heads; 2050081f96eSMarcel Moolenaar cyl = lba; 2060081f96eSMarcel Moolenaar if (cyl > 1023) 2070081f96eSMarcel Moolenaar sec = hd = cyl = ~0; 2080081f96eSMarcel Moolenaar 2090081f96eSMarcel Moolenaar *cylp = cyl & 0xff; 2100081f96eSMarcel Moolenaar *hdp = hd & 0xff; 2110081f96eSMarcel Moolenaar *secp = (sec & 0x3f) | ((cyl >> 2) & 0xc0); 2120081f96eSMarcel Moolenaar } 2130081f96eSMarcel Moolenaar 2146bc50445SMarcel Moolenaar static int 215bc1e8f56SAndrey V. Elsukov mbr_align(struct g_part_table *basetable, uint32_t *start, uint32_t *size) 216bc1e8f56SAndrey V. Elsukov { 217bc1e8f56SAndrey V. Elsukov uint32_t sectors; 218bc1e8f56SAndrey V. Elsukov 219fb86534cSAndrey V. Elsukov if (enforce_chs == 0) 220fb86534cSAndrey V. Elsukov return (0); 221bc1e8f56SAndrey V. Elsukov sectors = basetable->gpt_sectors; 222bc1e8f56SAndrey V. Elsukov if (*size < sectors) 223bc1e8f56SAndrey V. Elsukov return (EINVAL); 224bc1e8f56SAndrey V. Elsukov if (start != NULL && (*start % sectors)) { 225bc1e8f56SAndrey V. Elsukov *size += (*start % sectors) - sectors; 226bc1e8f56SAndrey V. Elsukov *start -= (*start % sectors) - sectors; 227bc1e8f56SAndrey V. Elsukov } 228bc1e8f56SAndrey V. Elsukov if (*size % sectors) 229bc1e8f56SAndrey V. Elsukov *size -= (*size % sectors); 230bc1e8f56SAndrey V. Elsukov if (*size < sectors) 231bc1e8f56SAndrey V. Elsukov return (EINVAL); 232bc1e8f56SAndrey V. Elsukov return (0); 233bc1e8f56SAndrey V. Elsukov } 234bc1e8f56SAndrey V. Elsukov 235bc1e8f56SAndrey V. Elsukov static int 2366bc50445SMarcel Moolenaar g_part_mbr_add(struct g_part_table *basetable, struct g_part_entry *baseentry, 2376bc50445SMarcel Moolenaar struct g_part_parms *gpp) 2386bc50445SMarcel Moolenaar { 2396bc50445SMarcel Moolenaar struct g_part_mbr_entry *entry; 240bc1e8f56SAndrey V. Elsukov uint32_t start, size; 2416bc50445SMarcel Moolenaar 2426bc50445SMarcel Moolenaar if (gpp->gpp_parms & G_PART_PARM_LABEL) 2436bc50445SMarcel Moolenaar return (EINVAL); 2446bc50445SMarcel Moolenaar 2456bc50445SMarcel Moolenaar entry = (struct g_part_mbr_entry *)baseentry; 2466bc50445SMarcel Moolenaar start = gpp->gpp_start; 2476bc50445SMarcel Moolenaar size = gpp->gpp_size; 248bc1e8f56SAndrey V. Elsukov if (mbr_align(basetable, &start, &size) != 0) 2496bc50445SMarcel Moolenaar return (EINVAL); 2506bc50445SMarcel Moolenaar if (baseentry->gpe_deleted) 2516bc50445SMarcel Moolenaar bzero(&entry->ent, sizeof(entry->ent)); 2526bc50445SMarcel Moolenaar 25342a783c1SRui Paulo KASSERT(baseentry->gpe_start <= start, ("%s", __func__)); 25442a783c1SRui Paulo KASSERT(baseentry->gpe_end >= start + size - 1, ("%s", __func__)); 2556bc50445SMarcel Moolenaar baseentry->gpe_start = start; 2566bc50445SMarcel Moolenaar baseentry->gpe_end = start + size - 1; 2576bc50445SMarcel Moolenaar entry->ent.dp_start = start; 2586bc50445SMarcel Moolenaar entry->ent.dp_size = size; 2590081f96eSMarcel Moolenaar mbr_set_chs(basetable, baseentry->gpe_start, &entry->ent.dp_scyl, 2600081f96eSMarcel Moolenaar &entry->ent.dp_shd, &entry->ent.dp_ssect); 2610081f96eSMarcel Moolenaar mbr_set_chs(basetable, baseentry->gpe_end, &entry->ent.dp_ecyl, 2620081f96eSMarcel Moolenaar &entry->ent.dp_ehd, &entry->ent.dp_esect); 2636bc50445SMarcel Moolenaar return (mbr_parse_type(gpp->gpp_type, &entry->ent.dp_typ)); 2646bc50445SMarcel Moolenaar } 2656bc50445SMarcel Moolenaar 2666bc50445SMarcel Moolenaar static int 2674d32fcb4SMarcel Moolenaar g_part_mbr_bootcode(struct g_part_table *basetable, struct g_part_parms *gpp) 2684d32fcb4SMarcel Moolenaar { 2694d32fcb4SMarcel Moolenaar struct g_part_mbr_table *table; 27061162e85SAndrey V. Elsukov uint32_t dsn; 2714d32fcb4SMarcel Moolenaar 27261162e85SAndrey V. Elsukov if (gpp->gpp_codesize != MBRSIZE) 27361162e85SAndrey V. Elsukov return (ENODEV); 27461162e85SAndrey V. Elsukov 2754d32fcb4SMarcel Moolenaar table = (struct g_part_mbr_table *)basetable; 27661162e85SAndrey V. Elsukov dsn = *(uint32_t *)(table->mbr + DOSDSNOFF); 27761162e85SAndrey V. Elsukov bcopy(gpp->gpp_codeptr, table->mbr, DOSPARTOFF); 278cdd2df88SDag-Erling Smørgrav if (dsn != 0 && !gpp->gpp_skip_dsn) 27961162e85SAndrey V. Elsukov *(uint32_t *)(table->mbr + DOSDSNOFF) = dsn; 2804d32fcb4SMarcel Moolenaar return (0); 2814d32fcb4SMarcel Moolenaar } 2824d32fcb4SMarcel Moolenaar 2834d32fcb4SMarcel Moolenaar static int 2846bc50445SMarcel Moolenaar g_part_mbr_create(struct g_part_table *basetable, struct g_part_parms *gpp) 2856bc50445SMarcel Moolenaar { 2866bc50445SMarcel Moolenaar struct g_provider *pp; 2876bc50445SMarcel Moolenaar struct g_part_mbr_table *table; 2886bc50445SMarcel Moolenaar 2896bc50445SMarcel Moolenaar pp = gpp->gpp_provider; 2900081f96eSMarcel Moolenaar if (pp->sectorsize < MBRSIZE) 2916bc50445SMarcel Moolenaar return (ENOSPC); 2926bc50445SMarcel Moolenaar 2930081f96eSMarcel Moolenaar basetable->gpt_first = basetable->gpt_sectors; 294db48d4a9SAndrey V. Elsukov basetable->gpt_last = MIN(pp->mediasize / pp->sectorsize, 295db48d4a9SAndrey V. Elsukov UINT32_MAX) - 1; 2966bc50445SMarcel Moolenaar 2976bc50445SMarcel Moolenaar table = (struct g_part_mbr_table *)basetable; 2986bc50445SMarcel Moolenaar le16enc(table->mbr + DOSMAGICOFFSET, DOSMAGIC); 2996bc50445SMarcel Moolenaar return (0); 3006bc50445SMarcel Moolenaar } 3016bc50445SMarcel Moolenaar 3026bc50445SMarcel Moolenaar static int 3036bc50445SMarcel Moolenaar g_part_mbr_destroy(struct g_part_table *basetable, struct g_part_parms *gpp) 3046bc50445SMarcel Moolenaar { 3056bc50445SMarcel Moolenaar 3066bc50445SMarcel Moolenaar /* Wipe the first sector to clear the partitioning. */ 3076bc50445SMarcel Moolenaar basetable->gpt_smhead |= 1; 3086bc50445SMarcel Moolenaar return (0); 3096bc50445SMarcel Moolenaar } 3106bc50445SMarcel Moolenaar 311f4fddf53SWarner Losh static void 312edfbbfd5SWarner Losh g_part_mbr_efimedia(struct g_part_mbr_table *table, struct g_part_mbr_entry *entry, 313edfbbfd5SWarner Losh struct sbuf *sb) 314edfbbfd5SWarner Losh { 315edfbbfd5SWarner Losh uint32_t dsn; 316edfbbfd5SWarner Losh 317edfbbfd5SWarner Losh dsn = le32dec(table->mbr + DOSDSNOFF); 318edfbbfd5SWarner Losh sbuf_printf(sb, "HD(%d,MBR,%#08x,%#jx,%#jx)", 319edfbbfd5SWarner Losh entry->base.gpe_index, dsn, (intmax_t)entry->base.gpe_start, 320edfbbfd5SWarner Losh (intmax_t)(entry->base.gpe_end - entry->base.gpe_start + 1)); 321edfbbfd5SWarner Losh } 322edfbbfd5SWarner Losh 323edfbbfd5SWarner Losh static void 324d65b6588SWarner Losh g_part_mbr_dumpconf(struct g_part_table *basetable, struct g_part_entry *baseentry, 3255db67052SMarcel Moolenaar struct sbuf *sb, const char *indent) 3265db67052SMarcel Moolenaar { 3275db67052SMarcel Moolenaar struct g_part_mbr_entry *entry; 328d65b6588SWarner Losh struct g_part_mbr_table *table; 3295db67052SMarcel Moolenaar 330d65b6588SWarner Losh table = (struct g_part_mbr_table *)basetable; 3315db67052SMarcel Moolenaar entry = (struct g_part_mbr_entry *)baseentry; 332a3354bb4SMarcel Moolenaar if (indent == NULL) { 333a3354bb4SMarcel Moolenaar /* conftxt: libdisk compatibility */ 3345db67052SMarcel Moolenaar sbuf_printf(sb, " xs MBR xt %u", entry->ent.dp_typ); 335a3354bb4SMarcel Moolenaar } else if (entry != NULL) { 336a3354bb4SMarcel Moolenaar /* confxml: partition entry information */ 337a3354bb4SMarcel Moolenaar sbuf_printf(sb, "%s<rawtype>%u</rawtype>\n", indent, 338a3354bb4SMarcel Moolenaar entry->ent.dp_typ); 339f6aa3fccSMarcel Moolenaar if (entry->ent.dp_flag & 0x80) 340f6aa3fccSMarcel Moolenaar sbuf_printf(sb, "%s<attrib>active</attrib>\n", indent); 341edfbbfd5SWarner Losh sbuf_printf(sb, "%s<efimedia>", indent); 342edfbbfd5SWarner Losh g_part_mbr_efimedia(table, entry, sb); 34349ee0fceSAlexander Motin sbuf_cat(sb, "</efimedia>\n"); 344a3354bb4SMarcel Moolenaar } else { 345a3354bb4SMarcel Moolenaar /* confxml: scheme information */ 346a3354bb4SMarcel Moolenaar } 3475db67052SMarcel Moolenaar } 3485db67052SMarcel Moolenaar 3495db67052SMarcel Moolenaar static int 3506bc50445SMarcel Moolenaar g_part_mbr_dumpto(struct g_part_table *table, struct g_part_entry *baseentry) 3516bc50445SMarcel Moolenaar { 3526bc50445SMarcel Moolenaar struct g_part_mbr_entry *entry; 3536bc50445SMarcel Moolenaar 354b5bad281SDon Lewis /* Allow dumping to a FreeBSD partition or Linux swap partition only. */ 3556bc50445SMarcel Moolenaar entry = (struct g_part_mbr_entry *)baseentry; 356b5bad281SDon Lewis return ((entry->ent.dp_typ == DOSPTYP_386BSD || 357b5bad281SDon Lewis entry->ent.dp_typ == DOSPTYP_LINSWP) ? 1 : 0); 3586bc50445SMarcel Moolenaar } 3596bc50445SMarcel Moolenaar 3606bc50445SMarcel Moolenaar static int 3616bc50445SMarcel Moolenaar g_part_mbr_modify(struct g_part_table *basetable, 3626bc50445SMarcel Moolenaar struct g_part_entry *baseentry, struct g_part_parms *gpp) 3636bc50445SMarcel Moolenaar { 3646bc50445SMarcel Moolenaar struct g_part_mbr_entry *entry; 3656bc50445SMarcel Moolenaar 3666bc50445SMarcel Moolenaar if (gpp->gpp_parms & G_PART_PARM_LABEL) 3676bc50445SMarcel Moolenaar return (EINVAL); 3686bc50445SMarcel Moolenaar 3696bc50445SMarcel Moolenaar entry = (struct g_part_mbr_entry *)baseentry; 3706bc50445SMarcel Moolenaar if (gpp->gpp_parms & G_PART_PARM_TYPE) 3716bc50445SMarcel Moolenaar return (mbr_parse_type(gpp->gpp_type, &entry->ent.dp_typ)); 3726bc50445SMarcel Moolenaar return (0); 3736bc50445SMarcel Moolenaar } 3746bc50445SMarcel Moolenaar 3753f71c319SMarcel Moolenaar static int 3763f71c319SMarcel Moolenaar g_part_mbr_resize(struct g_part_table *basetable, 3773f71c319SMarcel Moolenaar struct g_part_entry *baseentry, struct g_part_parms *gpp) 3783f71c319SMarcel Moolenaar { 3793f71c319SMarcel Moolenaar struct g_part_mbr_entry *entry; 380884c8e4fSAndrey V. Elsukov struct g_provider *pp; 381bc1e8f56SAndrey V. Elsukov uint32_t size; 3823f71c319SMarcel Moolenaar 383884c8e4fSAndrey V. Elsukov if (baseentry == NULL) { 384884c8e4fSAndrey V. Elsukov pp = LIST_FIRST(&basetable->gpt_gp->consumer)->provider; 385884c8e4fSAndrey V. Elsukov basetable->gpt_last = MIN(pp->mediasize / pp->sectorsize, 386884c8e4fSAndrey V. Elsukov UINT32_MAX) - 1; 387884c8e4fSAndrey V. Elsukov return (0); 388884c8e4fSAndrey V. Elsukov } 3893f71c319SMarcel Moolenaar size = gpp->gpp_size; 390bc1e8f56SAndrey V. Elsukov if (mbr_align(basetable, NULL, &size) != 0) 3913f71c319SMarcel Moolenaar return (EINVAL); 3920dd7f00cSAndrey V. Elsukov /* XXX: prevent unexpected shrinking. */ 3930dd7f00cSAndrey V. Elsukov pp = baseentry->gpe_pp; 394c4c88d47SAlexander Motin if ((g_debugflags & G_F_FOOTSHOOTING) == 0 && size < gpp->gpp_size && 395579259eaSAndrey V. Elsukov pp->mediasize / pp->sectorsize > size) 3960dd7f00cSAndrey V. Elsukov return (EBUSY); 3973f71c319SMarcel Moolenaar entry = (struct g_part_mbr_entry *)baseentry; 3983f71c319SMarcel Moolenaar baseentry->gpe_end = baseentry->gpe_start + size - 1; 3993f71c319SMarcel Moolenaar entry->ent.dp_size = size; 4003f71c319SMarcel Moolenaar mbr_set_chs(basetable, baseentry->gpe_end, &entry->ent.dp_ecyl, 4013f71c319SMarcel Moolenaar &entry->ent.dp_ehd, &entry->ent.dp_esect); 4023f71c319SMarcel Moolenaar return (0); 4033f71c319SMarcel Moolenaar } 4043f71c319SMarcel Moolenaar 405f4fddf53SWarner Losh static const char * 4066bc50445SMarcel Moolenaar g_part_mbr_name(struct g_part_table *table, struct g_part_entry *baseentry, 4076bc50445SMarcel Moolenaar char *buf, size_t bufsz) 4086bc50445SMarcel Moolenaar { 4096bc50445SMarcel Moolenaar 4106bc50445SMarcel Moolenaar snprintf(buf, bufsz, "s%d", baseentry->gpe_index); 4116bc50445SMarcel Moolenaar return (buf); 4126bc50445SMarcel Moolenaar } 4136bc50445SMarcel Moolenaar 4146bc50445SMarcel Moolenaar static int 4156bc50445SMarcel Moolenaar g_part_mbr_probe(struct g_part_table *table, struct g_consumer *cp) 4166bc50445SMarcel Moolenaar { 4175d68db5bSMarcel Moolenaar char psn[8]; 4186bc50445SMarcel Moolenaar struct g_provider *pp; 4196291ef2dSMarcel Moolenaar u_char *buf, *p; 420028de878SMarcel Moolenaar int error, index, res, sum; 4216291ef2dSMarcel Moolenaar uint16_t magic; 4226bc50445SMarcel Moolenaar 4236bc50445SMarcel Moolenaar pp = cp->provider; 4246bc50445SMarcel Moolenaar 4256bc50445SMarcel Moolenaar /* Sanity-check the provider. */ 4266bc50445SMarcel Moolenaar if (pp->sectorsize < MBRSIZE || pp->mediasize < pp->sectorsize) 4276bc50445SMarcel Moolenaar return (ENOSPC); 4286291ef2dSMarcel Moolenaar if (pp->sectorsize > 4096) 4296291ef2dSMarcel Moolenaar return (ENXIO); 4306bc50445SMarcel Moolenaar 4315d68db5bSMarcel Moolenaar /* We don't nest under an MBR (see EBR instead). */ 4325d68db5bSMarcel Moolenaar error = g_getattr("PART::scheme", cp, &psn); 4335d68db5bSMarcel Moolenaar if (error == 0 && strcmp(psn, g_part_mbr_scheme.name) == 0) 4345d68db5bSMarcel Moolenaar return (ELOOP); 4355d68db5bSMarcel Moolenaar 4366bc50445SMarcel Moolenaar /* Check that there's a MBR. */ 4376bc50445SMarcel Moolenaar buf = g_read_data(cp, 0L, pp->sectorsize, &error); 4386bc50445SMarcel Moolenaar if (buf == NULL) 4396bc50445SMarcel Moolenaar return (error); 4406291ef2dSMarcel Moolenaar 4416291ef2dSMarcel Moolenaar /* We goto out on mismatch. */ 4426291ef2dSMarcel Moolenaar res = ENXIO; 4436291ef2dSMarcel Moolenaar 4446291ef2dSMarcel Moolenaar magic = le16dec(buf + DOSMAGICOFFSET); 4456291ef2dSMarcel Moolenaar if (magic != DOSMAGIC) 4466291ef2dSMarcel Moolenaar goto out; 4476291ef2dSMarcel Moolenaar 4486291ef2dSMarcel Moolenaar for (index = 0; index < NDOSPART; index++) { 4496291ef2dSMarcel Moolenaar p = buf + DOSPARTOFF + index * DOSPARTSIZE; 4506291ef2dSMarcel Moolenaar if (p[0] != 0 && p[0] != 0x80) 4516291ef2dSMarcel Moolenaar goto out; 4526291ef2dSMarcel Moolenaar } 4536291ef2dSMarcel Moolenaar 454028de878SMarcel Moolenaar /* 455028de878SMarcel Moolenaar * If the partition table does not consist of all zeroes, 456028de878SMarcel Moolenaar * assume we have a MBR. If it's all zeroes, we could have 457028de878SMarcel Moolenaar * a boot sector. For example, a boot sector that doesn't 458028de878SMarcel Moolenaar * have boot code -- common on non-i386 hardware. In that 459028de878SMarcel Moolenaar * case we check if we have a possible BPB. If so, then we 460028de878SMarcel Moolenaar * assume we have a boot sector instead. 461028de878SMarcel Moolenaar */ 462028de878SMarcel Moolenaar sum = 0; 463028de878SMarcel Moolenaar for (index = 0; index < NDOSPART * DOSPARTSIZE; index++) 464028de878SMarcel Moolenaar sum += buf[DOSPARTOFF + index]; 465028de878SMarcel Moolenaar if (sum != 0 || !mbr_probe_bpb(buf + 0x0b)) 4666291ef2dSMarcel Moolenaar res = G_PART_PROBE_PRI_NORM; 4676291ef2dSMarcel Moolenaar 4686291ef2dSMarcel Moolenaar out: 4696bc50445SMarcel Moolenaar g_free(buf); 4706291ef2dSMarcel Moolenaar return (res); 4716bc50445SMarcel Moolenaar } 4726bc50445SMarcel Moolenaar 4736bc50445SMarcel Moolenaar static int 4746bc50445SMarcel Moolenaar g_part_mbr_read(struct g_part_table *basetable, struct g_consumer *cp) 4756bc50445SMarcel Moolenaar { 4766bc50445SMarcel Moolenaar struct dos_partition ent; 4776bc50445SMarcel Moolenaar struct g_provider *pp; 4786bc50445SMarcel Moolenaar struct g_part_mbr_table *table; 4796bc50445SMarcel Moolenaar struct g_part_mbr_entry *entry; 4806bc50445SMarcel Moolenaar u_char *buf, *p; 4819854b4eeSAndrey V. Elsukov off_t chs, msize, first; 4820081f96eSMarcel Moolenaar u_int sectors, heads; 4830081f96eSMarcel Moolenaar int error, index; 4846bc50445SMarcel Moolenaar 4856bc50445SMarcel Moolenaar pp = cp->provider; 4866bc50445SMarcel Moolenaar table = (struct g_part_mbr_table *)basetable; 4879854b4eeSAndrey V. Elsukov first = basetable->gpt_sectors; 4884675b2b6SAndrey V. Elsukov msize = MIN(pp->mediasize / pp->sectorsize, UINT32_MAX); 4896bc50445SMarcel Moolenaar 4906bc50445SMarcel Moolenaar buf = g_read_data(cp, 0L, pp->sectorsize, &error); 4916bc50445SMarcel Moolenaar if (buf == NULL) 4926bc50445SMarcel Moolenaar return (error); 4936bc50445SMarcel Moolenaar 4946bc50445SMarcel Moolenaar bcopy(buf, table->mbr, sizeof(table->mbr)); 4956bc50445SMarcel Moolenaar for (index = NDOSPART - 1; index >= 0; index--) { 4966bc50445SMarcel Moolenaar p = buf + DOSPARTOFF + index * DOSPARTSIZE; 4976bc50445SMarcel Moolenaar ent.dp_flag = p[0]; 4986bc50445SMarcel Moolenaar ent.dp_shd = p[1]; 4996bc50445SMarcel Moolenaar ent.dp_ssect = p[2]; 5006bc50445SMarcel Moolenaar ent.dp_scyl = p[3]; 5016bc50445SMarcel Moolenaar ent.dp_typ = p[4]; 5026bc50445SMarcel Moolenaar ent.dp_ehd = p[5]; 5036bc50445SMarcel Moolenaar ent.dp_esect = p[6]; 5046bc50445SMarcel Moolenaar ent.dp_ecyl = p[7]; 5056bc50445SMarcel Moolenaar ent.dp_start = le32dec(p + 8); 5066bc50445SMarcel Moolenaar ent.dp_size = le32dec(p + 12); 5076bc50445SMarcel Moolenaar if (ent.dp_typ == 0 || ent.dp_typ == DOSPTYP_PMBR) 5086bc50445SMarcel Moolenaar continue; 5096bc50445SMarcel Moolenaar if (ent.dp_start == 0 || ent.dp_size == 0) 5106bc50445SMarcel Moolenaar continue; 5110081f96eSMarcel Moolenaar sectors = ent.dp_esect & 0x3f; 5120081f96eSMarcel Moolenaar if (sectors > basetable->gpt_sectors && 5130081f96eSMarcel Moolenaar !basetable->gpt_fixgeom) { 5140081f96eSMarcel Moolenaar g_part_geometry_heads(msize, sectors, &chs, &heads); 5150081f96eSMarcel Moolenaar if (chs != 0) { 5160081f96eSMarcel Moolenaar basetable->gpt_sectors = sectors; 5170081f96eSMarcel Moolenaar basetable->gpt_heads = heads; 5180081f96eSMarcel Moolenaar } 5190081f96eSMarcel Moolenaar } 5209854b4eeSAndrey V. Elsukov if (ent.dp_start < first) 5219854b4eeSAndrey V. Elsukov first = ent.dp_start; 5226bc50445SMarcel Moolenaar entry = (struct g_part_mbr_entry *)g_part_new_entry(basetable, 5236bc50445SMarcel Moolenaar index + 1, ent.dp_start, ent.dp_start + ent.dp_size - 1); 5246bc50445SMarcel Moolenaar entry->ent = ent; 5256bc50445SMarcel Moolenaar } 5260081f96eSMarcel Moolenaar 5270081f96eSMarcel Moolenaar basetable->gpt_entries = NDOSPART; 5280081f96eSMarcel Moolenaar basetable->gpt_first = basetable->gpt_sectors; 529db48d4a9SAndrey V. Elsukov basetable->gpt_last = msize - 1; 5300081f96eSMarcel Moolenaar 5319854b4eeSAndrey V. Elsukov if (first < basetable->gpt_first) 5329854b4eeSAndrey V. Elsukov basetable->gpt_first = 1; 5339854b4eeSAndrey V. Elsukov 534b1da166eSAndrey V. Elsukov g_free(buf); 5356bc50445SMarcel Moolenaar return (0); 5366bc50445SMarcel Moolenaar } 5376bc50445SMarcel Moolenaar 538f6aa3fccSMarcel Moolenaar static int 539f6aa3fccSMarcel Moolenaar g_part_mbr_setunset(struct g_part_table *table, struct g_part_entry *baseentry, 540f6aa3fccSMarcel Moolenaar const char *attrib, unsigned int set) 541f6aa3fccSMarcel Moolenaar { 542f6aa3fccSMarcel Moolenaar struct g_part_entry *iter; 543f6aa3fccSMarcel Moolenaar struct g_part_mbr_entry *entry; 544f6aa3fccSMarcel Moolenaar int changed; 545f6aa3fccSMarcel Moolenaar 5463bd22a9cSMarcel Moolenaar if (baseentry == NULL) 5473bd22a9cSMarcel Moolenaar return (ENODEV); 548f6aa3fccSMarcel Moolenaar if (strcasecmp(attrib, "active") != 0) 549f6aa3fccSMarcel Moolenaar return (EINVAL); 550f6aa3fccSMarcel Moolenaar 551f6aa3fccSMarcel Moolenaar /* Only one entry can have the active attribute. */ 552f6aa3fccSMarcel Moolenaar LIST_FOREACH(iter, &table->gpt_entry, gpe_entry) { 553f6aa3fccSMarcel Moolenaar if (iter->gpe_deleted) 554f6aa3fccSMarcel Moolenaar continue; 555f6aa3fccSMarcel Moolenaar changed = 0; 556f6aa3fccSMarcel Moolenaar entry = (struct g_part_mbr_entry *)iter; 557f6aa3fccSMarcel Moolenaar if (iter == baseentry) { 558f6aa3fccSMarcel Moolenaar if (set && (entry->ent.dp_flag & 0x80) == 0) { 559f6aa3fccSMarcel Moolenaar entry->ent.dp_flag |= 0x80; 560f6aa3fccSMarcel Moolenaar changed = 1; 561f6aa3fccSMarcel Moolenaar } else if (!set && (entry->ent.dp_flag & 0x80)) { 562f6aa3fccSMarcel Moolenaar entry->ent.dp_flag &= ~0x80; 563f6aa3fccSMarcel Moolenaar changed = 1; 564f6aa3fccSMarcel Moolenaar } 565f6aa3fccSMarcel Moolenaar } else { 566f6aa3fccSMarcel Moolenaar if (set && (entry->ent.dp_flag & 0x80)) { 567f6aa3fccSMarcel Moolenaar entry->ent.dp_flag &= ~0x80; 568f6aa3fccSMarcel Moolenaar changed = 1; 569f6aa3fccSMarcel Moolenaar } 570f6aa3fccSMarcel Moolenaar } 571f6aa3fccSMarcel Moolenaar if (changed && !iter->gpe_created) 572f6aa3fccSMarcel Moolenaar iter->gpe_modified = 1; 573f6aa3fccSMarcel Moolenaar } 574f6aa3fccSMarcel Moolenaar return (0); 575f6aa3fccSMarcel Moolenaar } 576f6aa3fccSMarcel Moolenaar 5776bc50445SMarcel Moolenaar static const char * 5786bc50445SMarcel Moolenaar g_part_mbr_type(struct g_part_table *basetable, struct g_part_entry *baseentry, 5796bc50445SMarcel Moolenaar char *buf, size_t bufsz) 5806bc50445SMarcel Moolenaar { 5816bc50445SMarcel Moolenaar struct g_part_mbr_entry *entry; 58288007f61SAndrey V. Elsukov int i; 5836bc50445SMarcel Moolenaar 5846bc50445SMarcel Moolenaar entry = (struct g_part_mbr_entry *)baseentry; 58563b6b7a7SPedro F. Giffuni for (i = 0; i < nitems(mbr_alias_match); i++) { 58688007f61SAndrey V. Elsukov if (mbr_alias_match[i].typ == entry->ent.dp_typ) 58788007f61SAndrey V. Elsukov return (g_part_alias_name(mbr_alias_match[i].alias)); 588c6b2b6fcSRui Paulo } 58988007f61SAndrey V. Elsukov snprintf(buf, bufsz, "!%d", entry->ent.dp_typ); 5906bc50445SMarcel Moolenaar return (buf); 5916bc50445SMarcel Moolenaar } 5926bc50445SMarcel Moolenaar 5936bc50445SMarcel Moolenaar static int 5946bc50445SMarcel Moolenaar g_part_mbr_write(struct g_part_table *basetable, struct g_consumer *cp) 5956bc50445SMarcel Moolenaar { 5966bc50445SMarcel Moolenaar struct g_part_entry *baseentry; 5976bc50445SMarcel Moolenaar struct g_part_mbr_entry *entry; 5986bc50445SMarcel Moolenaar struct g_part_mbr_table *table; 5996bc50445SMarcel Moolenaar u_char *p; 6006bc50445SMarcel Moolenaar int error, index; 6016bc50445SMarcel Moolenaar 6026bc50445SMarcel Moolenaar table = (struct g_part_mbr_table *)basetable; 6036bc50445SMarcel Moolenaar baseentry = LIST_FIRST(&basetable->gpt_entry); 6046bc50445SMarcel Moolenaar for (index = 1; index <= basetable->gpt_entries; index++) { 6056bc50445SMarcel Moolenaar p = table->mbr + DOSPARTOFF + (index - 1) * DOSPARTSIZE; 6066bc50445SMarcel Moolenaar entry = (baseentry != NULL && index == baseentry->gpe_index) 6076bc50445SMarcel Moolenaar ? (struct g_part_mbr_entry *)baseentry : NULL; 6086bc50445SMarcel Moolenaar if (entry != NULL && !baseentry->gpe_deleted) { 6096bc50445SMarcel Moolenaar p[0] = entry->ent.dp_flag; 6106bc50445SMarcel Moolenaar p[1] = entry->ent.dp_shd; 6116bc50445SMarcel Moolenaar p[2] = entry->ent.dp_ssect; 6126bc50445SMarcel Moolenaar p[3] = entry->ent.dp_scyl; 6136bc50445SMarcel Moolenaar p[4] = entry->ent.dp_typ; 6146bc50445SMarcel Moolenaar p[5] = entry->ent.dp_ehd; 6156bc50445SMarcel Moolenaar p[6] = entry->ent.dp_esect; 6166bc50445SMarcel Moolenaar p[7] = entry->ent.dp_ecyl; 6176bc50445SMarcel Moolenaar le32enc(p + 8, entry->ent.dp_start); 6186bc50445SMarcel Moolenaar le32enc(p + 12, entry->ent.dp_size); 6196bc50445SMarcel Moolenaar } else 6206bc50445SMarcel Moolenaar bzero(p, DOSPARTSIZE); 6216bc50445SMarcel Moolenaar 6226bc50445SMarcel Moolenaar if (entry != NULL) 6236bc50445SMarcel Moolenaar baseentry = LIST_NEXT(baseentry, gpe_entry); 6246bc50445SMarcel Moolenaar } 6256bc50445SMarcel Moolenaar 6266bc50445SMarcel Moolenaar error = g_write_data(cp, 0, table->mbr, cp->provider->sectorsize); 6276bc50445SMarcel Moolenaar return (error); 6286bc50445SMarcel Moolenaar } 629