g_part_bsd.c revision 174326
1174326Smarcel/*- 2174326Smarcel * Copyright (c) 2007 Marcel Moolenaar 3174326Smarcel * All rights reserved. 4174326Smarcel * 5174326Smarcel * Redistribution and use in source and binary forms, with or without 6174326Smarcel * modification, are permitted provided that the following conditions 7174326Smarcel * are met: 8174326Smarcel * 9174326Smarcel * 1. Redistributions of source code must retain the above copyright 10174326Smarcel * notice, this list of conditions and the following disclaimer. 11174326Smarcel * 2. Redistributions in binary form must reproduce the above copyright 12174326Smarcel * notice, this list of conditions and the following disclaimer in the 13174326Smarcel * documentation and/or other materials provided with the distribution. 14174326Smarcel * 15174326Smarcel * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 16174326Smarcel * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17174326Smarcel * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 18174326Smarcel * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 19174326Smarcel * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 20174326Smarcel * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21174326Smarcel * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22174326Smarcel * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23174326Smarcel * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 24174326Smarcel * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25174326Smarcel */ 26174326Smarcel 27174326Smarcel#include <sys/cdefs.h> 28174326Smarcel__FBSDID("$FreeBSD: head/sys/geom/part/g_part_bsd.c 174326 2007-12-06 02:32:42Z marcel $"); 29174326Smarcel 30174326Smarcel#include <sys/param.h> 31174326Smarcel#include <sys/bio.h> 32174326Smarcel#include <sys/disklabel.h> 33174326Smarcel#include <sys/endian.h> 34174326Smarcel#include <sys/kernel.h> 35174326Smarcel#include <sys/kobj.h> 36174326Smarcel#include <sys/limits.h> 37174326Smarcel#include <sys/lock.h> 38174326Smarcel#include <sys/malloc.h> 39174326Smarcel#include <sys/mutex.h> 40174326Smarcel#include <sys/queue.h> 41174326Smarcel#include <sys/sbuf.h> 42174326Smarcel#include <sys/systm.h> 43174326Smarcel#include <geom/geom.h> 44174326Smarcel#include <geom/part/g_part.h> 45174326Smarcel 46174326Smarcel#include "g_part_if.h" 47174326Smarcel 48174326Smarcelstruct g_part_bsd_table { 49174326Smarcel struct g_part_table base; 50174326Smarcel u_char *label; 51174326Smarcel}; 52174326Smarcel 53174326Smarcelstruct g_part_bsd_entry { 54174326Smarcel struct g_part_entry base; 55174326Smarcel struct partition part; 56174326Smarcel}; 57174326Smarcel 58174326Smarcelstatic int g_part_bsd_add(struct g_part_table *, struct g_part_entry *, 59174326Smarcel struct g_part_parms *); 60174326Smarcelstatic int g_part_bsd_create(struct g_part_table *, struct g_part_parms *); 61174326Smarcelstatic int g_part_bsd_destroy(struct g_part_table *, struct g_part_parms *); 62174326Smarcelstatic int g_part_bsd_dumpto(struct g_part_table *, struct g_part_entry *); 63174326Smarcelstatic int g_part_bsd_modify(struct g_part_table *, struct g_part_entry *, 64174326Smarcel struct g_part_parms *); 65174326Smarcelstatic char *g_part_bsd_name(struct g_part_table *, struct g_part_entry *, 66174326Smarcel char *, size_t); 67174326Smarcelstatic int g_part_bsd_probe(struct g_part_table *, struct g_consumer *); 68174326Smarcelstatic int g_part_bsd_read(struct g_part_table *, struct g_consumer *); 69174326Smarcelstatic const char *g_part_bsd_type(struct g_part_table *, struct g_part_entry *, 70174326Smarcel char *, size_t); 71174326Smarcelstatic int g_part_bsd_write(struct g_part_table *, struct g_consumer *); 72174326Smarcel 73174326Smarcelstatic kobj_method_t g_part_bsd_methods[] = { 74174326Smarcel KOBJMETHOD(g_part_add, g_part_bsd_add), 75174326Smarcel KOBJMETHOD(g_part_create, g_part_bsd_create), 76174326Smarcel KOBJMETHOD(g_part_destroy, g_part_bsd_destroy), 77174326Smarcel KOBJMETHOD(g_part_dumpto, g_part_bsd_dumpto), 78174326Smarcel KOBJMETHOD(g_part_modify, g_part_bsd_modify), 79174326Smarcel KOBJMETHOD(g_part_name, g_part_bsd_name), 80174326Smarcel KOBJMETHOD(g_part_probe, g_part_bsd_probe), 81174326Smarcel KOBJMETHOD(g_part_read, g_part_bsd_read), 82174326Smarcel KOBJMETHOD(g_part_type, g_part_bsd_type), 83174326Smarcel KOBJMETHOD(g_part_write, g_part_bsd_write), 84174326Smarcel { 0, 0 } 85174326Smarcel}; 86174326Smarcel 87174326Smarcelstatic struct g_part_scheme g_part_bsd_scheme = { 88174326Smarcel "BSD", 89174326Smarcel g_part_bsd_methods, 90174326Smarcel sizeof(struct g_part_bsd_table), 91174326Smarcel .gps_entrysz = sizeof(struct g_part_bsd_entry), 92174326Smarcel .gps_minent = 8, 93174326Smarcel .gps_maxent = 20, 94174326Smarcel}; 95174326SmarcelG_PART_SCHEME_DECLARE(g_part_bsd_scheme); 96174326Smarcel 97174326Smarcelstatic int 98174326Smarcelbsd_parse_type(const char *type, uint8_t *fstype) 99174326Smarcel{ 100174326Smarcel const char *alias; 101174326Smarcel char *endp; 102174326Smarcel long lt; 103174326Smarcel 104174326Smarcel if (type[0] == '!') { 105174326Smarcel lt = strtol(type + 1, &endp, 0); 106174326Smarcel if (type[1] == '\0' || *endp != '\0' || lt <= 0 || lt >= 256) 107174326Smarcel return (EINVAL); 108174326Smarcel *fstype = (u_int)lt; 109174326Smarcel return (0); 110174326Smarcel } 111174326Smarcel alias = g_part_alias_name(G_PART_ALIAS_FREEBSD_SWAP); 112174326Smarcel if (!strcasecmp(type, alias)) { 113174326Smarcel *fstype = FS_SWAP; 114174326Smarcel return (0); 115174326Smarcel } 116174326Smarcel alias = g_part_alias_name(G_PART_ALIAS_FREEBSD_UFS); 117174326Smarcel if (!strcasecmp(type, alias)) { 118174326Smarcel *fstype = FS_BSDFFS; 119174326Smarcel return (0); 120174326Smarcel } 121174326Smarcel alias = g_part_alias_name(G_PART_ALIAS_FREEBSD_VINUM); 122174326Smarcel if (!strcasecmp(type, alias)) { 123174326Smarcel *fstype = FS_VINUM; 124174326Smarcel return (0); 125174326Smarcel } 126174326Smarcel return (EINVAL); 127174326Smarcel} 128174326Smarcel 129174326Smarcelstatic int 130174326Smarcelg_part_bsd_add(struct g_part_table *basetable, struct g_part_entry *baseentry, 131174326Smarcel struct g_part_parms *gpp) 132174326Smarcel{ 133174326Smarcel struct g_part_bsd_entry *entry; 134174326Smarcel struct g_part_bsd_table *table; 135174326Smarcel uint32_t start, size, sectors; 136174326Smarcel 137174326Smarcel if (gpp->gpp_parms & G_PART_PARM_LABEL) 138174326Smarcel return (EINVAL); 139174326Smarcel 140174326Smarcel sectors = basetable->gpt_sectors; 141174326Smarcel 142174326Smarcel entry = (struct g_part_bsd_entry *)baseentry; 143174326Smarcel table = (struct g_part_bsd_table *)basetable; 144174326Smarcel 145174326Smarcel start = gpp->gpp_start; 146174326Smarcel size = gpp->gpp_size; 147174326Smarcel if (size < sectors) 148174326Smarcel return (EINVAL); 149174326Smarcel if (start % sectors) { 150174326Smarcel size = size - sectors + (start % sectors); 151174326Smarcel start = start - (start % sectors) + sectors; 152174326Smarcel } 153174326Smarcel if (size % sectors) 154174326Smarcel size = size - (size % sectors); 155174326Smarcel if (size < sectors) 156174326Smarcel return (EINVAL); 157174326Smarcel 158174326Smarcel KASSERT(baseentry->gpe_start <= start, (__func__)); 159174326Smarcel KASSERT(baseentry->gpe_end >= start + size - 1, (__func__)); 160174326Smarcel baseentry->gpe_start = start; 161174326Smarcel baseentry->gpe_end = start + size - 1; 162174326Smarcel entry->part.p_size = size; 163174326Smarcel entry->part.p_offset = start + basetable->gpt_offset; 164174326Smarcel entry->part.p_fsize = 0; 165174326Smarcel entry->part.p_frag = 0; 166174326Smarcel entry->part.p_cpg = 0; 167174326Smarcel return (bsd_parse_type(gpp->gpp_type, &entry->part.p_fstype)); 168174326Smarcel} 169174326Smarcel 170174326Smarcelstatic int 171174326Smarcelg_part_bsd_create(struct g_part_table *basetable, struct g_part_parms *gpp) 172174326Smarcel{ 173174326Smarcel struct g_consumer *cp; 174174326Smarcel struct g_provider *pp; 175174326Smarcel struct g_part_entry *baseentry; 176174326Smarcel struct g_part_bsd_entry *entry; 177174326Smarcel struct g_part_bsd_table *table; 178174326Smarcel u_char *ptr; 179174326Smarcel uint64_t msize; 180174326Smarcel uint32_t ncyls, secpercyl; 181174326Smarcel 182174326Smarcel pp = gpp->gpp_provider; 183174326Smarcel cp = LIST_FIRST(&pp->consumers); 184174326Smarcel 185174326Smarcel if (pp->sectorsize < sizeof(struct disklabel)) 186174326Smarcel return (ENOSPC); 187174326Smarcel 188174326Smarcel msize = pp->mediasize / pp->sectorsize; 189174326Smarcel secpercyl = basetable->gpt_sectors * basetable->gpt_heads; 190174326Smarcel ncyls = msize / secpercyl; 191174326Smarcel 192174326Smarcel table = (struct g_part_bsd_table *)basetable; 193174326Smarcel ptr = table->label = g_malloc(pp->sectorsize, M_WAITOK | M_ZERO); 194174326Smarcel 195174326Smarcel le32enc(ptr + 0, DISKMAGIC); /* d_magic */ 196174326Smarcel le32enc(ptr + 40, pp->sectorsize); /* d_secsize */ 197174326Smarcel le32enc(ptr + 44, basetable->gpt_sectors); /* d_nsectors */ 198174326Smarcel le32enc(ptr + 48, basetable->gpt_heads); /* d_ntracks */ 199174326Smarcel le32enc(ptr + 52, ncyls); /* d_ncylinders */ 200174326Smarcel le32enc(ptr + 56, secpercyl); /* d_secpercyl */ 201174326Smarcel le32enc(ptr + 60, ncyls * secpercyl); /* d_secperunit */ 202174326Smarcel le16enc(ptr + 72, 3600); /* d_rpm */ 203174326Smarcel le32enc(ptr + 132, DISKMAGIC); /* d_magic2 */ 204174326Smarcel le16enc(ptr + 138, basetable->gpt_entries); /* d_npartitions */ 205174326Smarcel le32enc(ptr + 140, BBSIZE); /* d_bbsize */ 206174326Smarcel 207174326Smarcel basetable->gpt_first = 0; 208174326Smarcel basetable->gpt_last = ncyls * secpercyl - 1; 209174326Smarcel basetable->gpt_isleaf = 1; 210174326Smarcel 211174326Smarcel baseentry = g_part_new_entry(basetable, RAW_PART + 1, 212174326Smarcel basetable->gpt_first, basetable->gpt_last); 213174326Smarcel baseentry->gpe_internal = 1; 214174326Smarcel entry = (struct g_part_bsd_entry *)baseentry; 215174326Smarcel entry->part.p_size = basetable->gpt_last + 1; 216174326Smarcel entry->part.p_offset = basetable->gpt_offset; 217174326Smarcel 218174326Smarcel return (0); 219174326Smarcel} 220174326Smarcel 221174326Smarcelstatic int 222174326Smarcelg_part_bsd_destroy(struct g_part_table *basetable, struct g_part_parms *gpp) 223174326Smarcel{ 224174326Smarcel 225174326Smarcel /* Wipe the second sector to clear the partitioning. */ 226174326Smarcel basetable->gpt_smhead |= 2; 227174326Smarcel return (0); 228174326Smarcel} 229174326Smarcel 230174326Smarcelstatic int 231174326Smarcelg_part_bsd_dumpto(struct g_part_table *table, struct g_part_entry *baseentry) 232174326Smarcel{ 233174326Smarcel struct g_part_bsd_entry *entry; 234174326Smarcel 235174326Smarcel /* Allow dumping to a swap partition only. */ 236174326Smarcel entry = (struct g_part_bsd_entry *)baseentry; 237174326Smarcel return ((entry->part.p_fstype == FS_SWAP) ? 1 : 0); 238174326Smarcel} 239174326Smarcel 240174326Smarcelstatic int 241174326Smarcelg_part_bsd_modify(struct g_part_table *basetable, 242174326Smarcel struct g_part_entry *baseentry, struct g_part_parms *gpp) 243174326Smarcel{ 244174326Smarcel struct g_part_bsd_entry *entry; 245174326Smarcel 246174326Smarcel if (gpp->gpp_parms & G_PART_PARM_LABEL) 247174326Smarcel return (EINVAL); 248174326Smarcel 249174326Smarcel entry = (struct g_part_bsd_entry *)baseentry; 250174326Smarcel if (gpp->gpp_parms & G_PART_PARM_TYPE) 251174326Smarcel return (bsd_parse_type(gpp->gpp_type, &entry->part.p_fstype)); 252174326Smarcel return (0); 253174326Smarcel} 254174326Smarcel 255174326Smarcelstatic char * 256174326Smarcelg_part_bsd_name(struct g_part_table *table, struct g_part_entry *baseentry, 257174326Smarcel char *buf, size_t bufsz) 258174326Smarcel{ 259174326Smarcel 260174326Smarcel snprintf(buf, bufsz, "%c", 'a' + baseentry->gpe_index - 1); 261174326Smarcel return (buf); 262174326Smarcel} 263174326Smarcel 264174326Smarcelstatic int 265174326Smarcelg_part_bsd_probe(struct g_part_table *table, struct g_consumer *cp) 266174326Smarcel{ 267174326Smarcel struct g_provider *pp; 268174326Smarcel u_char *buf; 269174326Smarcel uint32_t magic1, magic2; 270174326Smarcel int error; 271174326Smarcel 272174326Smarcel pp = cp->provider; 273174326Smarcel 274174326Smarcel /* Sanity-check the provider. */ 275174326Smarcel if (pp->sectorsize < sizeof(struct disklabel) || 276174326Smarcel pp->mediasize < BBSIZE) 277174326Smarcel return (ENOSPC); 278174326Smarcel 279174326Smarcel /* Check that there's a disklabel. */ 280174326Smarcel buf = g_read_data(cp, pp->sectorsize, pp->sectorsize, &error); 281174326Smarcel if (buf == NULL) 282174326Smarcel return (error); 283174326Smarcel magic1 = le32dec(buf + 0); 284174326Smarcel magic2 = le32dec(buf + 132); 285174326Smarcel g_free(buf); 286174326Smarcel return ((magic1 == DISKMAGIC && magic2 == DISKMAGIC) 287174326Smarcel ? G_PART_PROBE_PRI_NORM : ENXIO); 288174326Smarcel} 289174326Smarcel 290174326Smarcelstatic int 291174326Smarcelg_part_bsd_read(struct g_part_table *basetable, struct g_consumer *cp) 292174326Smarcel{ 293174326Smarcel struct g_provider *pp; 294174326Smarcel struct g_part_bsd_table *table; 295174326Smarcel struct g_part_entry *baseentry; 296174326Smarcel struct g_part_bsd_entry *entry; 297174326Smarcel struct partition part; 298174326Smarcel u_char *buf, *p; 299174326Smarcel off_t chs, msize; 300174326Smarcel u_int sectors, heads; 301174326Smarcel int error, index; 302174326Smarcel 303174326Smarcel pp = cp->provider; 304174326Smarcel table = (struct g_part_bsd_table *)basetable; 305174326Smarcel msize = pp->mediasize / pp->sectorsize; 306174326Smarcel 307174326Smarcel buf = g_read_data(cp, pp->sectorsize, pp->sectorsize, &error); 308174326Smarcel if (buf == NULL) 309174326Smarcel return (error); 310174326Smarcel 311174326Smarcel table->label = buf; 312174326Smarcel 313174326Smarcel if (le32dec(buf + 40) != pp->sectorsize) 314174326Smarcel goto invalid_label; 315174326Smarcel sectors = le32dec(buf + 44); 316174326Smarcel if (sectors < 1 || sectors > 63) 317174326Smarcel goto invalid_label; 318174326Smarcel if (sectors != basetable->gpt_sectors) { 319174326Smarcel if (basetable->gpt_fixgeom) 320174326Smarcel goto invalid_label; 321174326Smarcel g_part_geometry_heads(msize, sectors, &chs, &heads); 322174326Smarcel if (chs == 0) 323174326Smarcel goto invalid_label; 324174326Smarcel basetable->gpt_sectors = sectors; 325174326Smarcel basetable->gpt_heads = heads; 326174326Smarcel } 327174326Smarcel heads = le32dec(buf + 48); 328174326Smarcel if (heads < 1 || heads > 255) 329174326Smarcel goto invalid_label; 330174326Smarcel if (heads != basetable->gpt_heads) { 331174326Smarcel if (basetable->gpt_fixgeom) 332174326Smarcel goto invalid_label; 333174326Smarcel basetable->gpt_heads = heads; 334174326Smarcel } 335174326Smarcel chs = le32dec(buf + 52) * heads * sectors; 336174326Smarcel if (chs < 1 || chs > msize) 337174326Smarcel goto invalid_label; 338174326Smarcel 339174326Smarcel basetable->gpt_first = 0; 340174326Smarcel basetable->gpt_last = chs - 1; 341174326Smarcel basetable->gpt_isleaf = 1; 342174326Smarcel 343174326Smarcel basetable->gpt_entries = le16dec(buf + 138); 344174326Smarcel if (basetable->gpt_entries < g_part_bsd_scheme.gps_minent || 345174326Smarcel basetable->gpt_entries > g_part_bsd_scheme.gps_maxent) 346174326Smarcel goto invalid_label; 347174326Smarcel 348174326Smarcel for (index = basetable->gpt_entries - 1; index >= 0; index--) { 349174326Smarcel p = buf + 148 + index * 16; 350174326Smarcel part.p_size = le32dec(p + 0); 351174326Smarcel part.p_offset = le32dec(p + 4); 352174326Smarcel part.p_fsize = le32dec(p + 8); 353174326Smarcel part.p_fstype = p[12]; 354174326Smarcel part.p_frag = p[13]; 355174326Smarcel part.p_cpg = le16dec(p + 14); 356174326Smarcel if (part.p_size == 0) 357174326Smarcel continue; 358174326Smarcel if (part.p_fstype == FS_UNUSED && index != RAW_PART) 359174326Smarcel continue; 360174326Smarcel if (part.p_offset < basetable->gpt_offset) 361174326Smarcel continue; 362174326Smarcel baseentry = g_part_new_entry(basetable, index + 1, 363174326Smarcel part.p_offset - basetable->gpt_offset, 364174326Smarcel part.p_offset - basetable->gpt_offset + part.p_size - 1); 365174326Smarcel entry = (struct g_part_bsd_entry *)baseentry; 366174326Smarcel entry->part = part; 367174326Smarcel if (part.p_fstype == FS_UNUSED) 368174326Smarcel baseentry->gpe_internal = 1; 369174326Smarcel } 370174326Smarcel 371174326Smarcel return (0); 372174326Smarcel 373174326Smarcel invalid_label: 374174326Smarcel printf("GEOM: %s: invalid disklabel.\n", pp->name); 375174326Smarcel g_free(table->label); 376174326Smarcel return (EINVAL); 377174326Smarcel} 378174326Smarcel 379174326Smarcelstatic const char * 380174326Smarcelg_part_bsd_type(struct g_part_table *basetable, struct g_part_entry *baseentry, 381174326Smarcel char *buf, size_t bufsz) 382174326Smarcel{ 383174326Smarcel struct g_part_bsd_entry *entry; 384174326Smarcel int type; 385174326Smarcel 386174326Smarcel entry = (struct g_part_bsd_entry *)baseentry; 387174326Smarcel type = entry->part.p_fstype; 388174326Smarcel if (type == FS_SWAP) 389174326Smarcel return (g_part_alias_name(G_PART_ALIAS_FREEBSD_SWAP)); 390174326Smarcel if (type == FS_BSDFFS) 391174326Smarcel return (g_part_alias_name(G_PART_ALIAS_FREEBSD_UFS)); 392174326Smarcel if (type == FS_VINUM) 393174326Smarcel return (g_part_alias_name(G_PART_ALIAS_FREEBSD_VINUM)); 394174326Smarcel snprintf(buf, bufsz, "!%d", type); 395174326Smarcel return (buf); 396174326Smarcel} 397174326Smarcel 398174326Smarcelstatic int 399174326Smarcelg_part_bsd_write(struct g_part_table *basetable, struct g_consumer *cp) 400174326Smarcel{ 401174326Smarcel struct g_provider *pp; 402174326Smarcel struct g_part_entry *baseentry; 403174326Smarcel struct g_part_bsd_entry *entry; 404174326Smarcel struct g_part_bsd_table *table; 405174326Smarcel uint16_t sum; 406174326Smarcel u_char *p, *pe; 407174326Smarcel int error, index; 408174326Smarcel 409174326Smarcel pp = cp->provider; 410174326Smarcel table = (struct g_part_bsd_table *)basetable; 411174326Smarcel baseentry = LIST_FIRST(&basetable->gpt_entry); 412174326Smarcel for (index = 1; index <= basetable->gpt_entries; index++) { 413174326Smarcel p = table->label + 148 + (index - 1) * 16; 414174326Smarcel entry = (baseentry != NULL && index == baseentry->gpe_index) 415174326Smarcel ? (struct g_part_bsd_entry *)baseentry : NULL; 416174326Smarcel if (entry != NULL && !baseentry->gpe_deleted) { 417174326Smarcel le32enc(p + 0, entry->part.p_size); 418174326Smarcel le32enc(p + 4, entry->part.p_offset); 419174326Smarcel le32enc(p + 8, entry->part.p_fsize); 420174326Smarcel p[12] = entry->part.p_fstype; 421174326Smarcel p[13] = entry->part.p_frag; 422174326Smarcel le16enc(p + 14, entry->part.p_cpg); 423174326Smarcel } else 424174326Smarcel bzero(p, 16); 425174326Smarcel 426174326Smarcel if (entry != NULL) 427174326Smarcel baseentry = LIST_NEXT(baseentry, gpe_entry); 428174326Smarcel } 429174326Smarcel 430174326Smarcel /* Calculate checksum. */ 431174326Smarcel le16enc(table->label + 136, 0); 432174326Smarcel pe = table->label + 148 + basetable->gpt_entries * 16; 433174326Smarcel sum = 0; 434174326Smarcel for (p = table->label; p < pe; p += 2) 435174326Smarcel sum ^= le16dec(p); 436174326Smarcel le16enc(table->label + 136, sum); 437174326Smarcel 438174326Smarcel error = g_write_data(cp, pp->sectorsize, table->label, pp->sectorsize); 439174326Smarcel return (error); 440174326Smarcel} 441