1110603Sphk/*- 2110603Sphk * Copyright (c) 2003 Poul-Henning Kamp 3110603Sphk * All rights reserved. 4110603Sphk * 5110603Sphk * Redistribution and use in source and binary forms, with or without 6110603Sphk * modification, are permitted provided that the following conditions 7110603Sphk * are met: 8110603Sphk * 1. Redistributions of source code must retain the above copyright 9110603Sphk * notice, this list of conditions and the following disclaimer. 10110603Sphk * 2. Redistributions in binary form must reproduce the above copyright 11110603Sphk * notice, this list of conditions and the following disclaimer in the 12110603Sphk * documentation and/or other materials provided with the distribution. 13110603Sphk * 3. The names of the authors may not be used to endorse or promote 14110603Sphk * products derived from this software without specific prior written 15110603Sphk * permission. 16110603Sphk * 17110603Sphk * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18110603Sphk * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19110603Sphk * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20110603Sphk * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21110603Sphk * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22110603Sphk * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23110603Sphk * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24110603Sphk * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25110603Sphk * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26110603Sphk * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27110603Sphk * SUCH DAMAGE. 28110603Sphk * 29110603Sphk * $FreeBSD$ 30110603Sphk */ 31110603Sphk 32110603Sphk#include <stdio.h> 33110603Sphk#include <inttypes.h> 34110603Sphk#include <stdlib.h> 35110603Sphk#include <string.h> 36110603Sphk#include <unistd.h> 37110603Sphk#include <errno.h> 38110603Sphk#include <fcntl.h> 39110603Sphk#include <ctype.h> 40110603Sphk#include <sys/stat.h> 41110603Sphk#include <sys/mman.h> 42110603Sphk#include <sys/queue.h> 43110603Sphk#include <sys/sbuf.h> 44110603Sphk#include <sys/sysctl.h> 45110603Sphk#include <err.h> 46110603Sphk#include <bsdxml.h> 47110603Sphk#include <libgeom.h> 48110603Sphk 49110603Sphkstruct mystate { 50110603Sphk struct gmesh *mesh; 51110603Sphk struct gclass *class; 52110603Sphk struct ggeom *geom; 53110603Sphk struct gprovider *provider; 54110603Sphk struct gconsumer *consumer; 55110603Sphk int level; 56110603Sphk struct sbuf *sbuf[20]; 57110603Sphk struct gconf *config; 58234107Sjmallett int nident; 59242130Sjh XML_Parser parser; 60242130Sjh int error; 61110603Sphk}; 62110603Sphk 63110603Sphkstatic void 64110603SphkStartElement(void *userData, const char *name, const char **attr) 65110603Sphk{ 66110603Sphk struct mystate *mt; 67110603Sphk void *id; 68110603Sphk void *ref; 69110603Sphk int i; 70110603Sphk 71110603Sphk mt = userData; 72110603Sphk mt->level++; 73181463Sdes mt->sbuf[mt->level] = sbuf_new_auto(); 74110603Sphk id = NULL; 75126786Sjhb ref = NULL; 76110603Sphk for (i = 0; attr[i] != NULL; i += 2) { 77110603Sphk if (!strcmp(attr[i], "id")) { 78253249Shrs id = (void *)strtoul(attr[i + 1], NULL, 0); 79234107Sjmallett mt->nident++; 80110603Sphk } else if (!strcmp(attr[i], "ref")) { 81253249Shrs ref = (void *)strtoul(attr[i + 1], NULL, 0); 82110603Sphk } else 83110603Sphk printf("%*.*s[%s = %s]\n", 84110603Sphk mt->level + 1, mt->level + 1, "", 85110603Sphk attr[i], attr[i + 1]); 86110603Sphk } 87110603Sphk if (!strcmp(name, "class") && mt->class == NULL) { 88110603Sphk mt->class = calloc(1, sizeof *mt->class); 89180369Slulf if (mt->class == NULL) { 90242130Sjh mt->error = errno; 91242130Sjh XML_StopParser(mt->parser, 0); 92180369Slulf warn("Cannot allocate memory during processing of '%s' " 93180369Slulf "element", name); 94180369Slulf return; 95180369Slulf } 96126786Sjhb mt->class->lg_id = id; 97126786Sjhb LIST_INSERT_HEAD(&mt->mesh->lg_class, mt->class, lg_class); 98126786Sjhb LIST_INIT(&mt->class->lg_geom); 99126786Sjhb LIST_INIT(&mt->class->lg_config); 100110603Sphk return; 101110603Sphk } 102110603Sphk if (!strcmp(name, "geom") && mt->geom == NULL) { 103110603Sphk mt->geom = calloc(1, sizeof *mt->geom); 104180369Slulf if (mt->geom == NULL) { 105242130Sjh mt->error = errno; 106242130Sjh XML_StopParser(mt->parser, 0); 107180369Slulf warn("Cannot allocate memory during processing of '%s' " 108180369Slulf "element", name); 109180369Slulf return; 110180369Slulf } 111126786Sjhb mt->geom->lg_id = id; 112126786Sjhb LIST_INSERT_HEAD(&mt->class->lg_geom, mt->geom, lg_geom); 113126786Sjhb LIST_INIT(&mt->geom->lg_provider); 114126786Sjhb LIST_INIT(&mt->geom->lg_consumer); 115126786Sjhb LIST_INIT(&mt->geom->lg_config); 116110603Sphk return; 117110603Sphk } 118110603Sphk if (!strcmp(name, "class") && mt->geom != NULL) { 119126786Sjhb mt->geom->lg_class = ref; 120110603Sphk return; 121110603Sphk } 122110603Sphk if (!strcmp(name, "consumer") && mt->consumer == NULL) { 123110603Sphk mt->consumer = calloc(1, sizeof *mt->consumer); 124180369Slulf if (mt->consumer == NULL) { 125242130Sjh mt->error = errno; 126242130Sjh XML_StopParser(mt->parser, 0); 127180369Slulf warn("Cannot allocate memory during processing of '%s' " 128180369Slulf "element", name); 129180369Slulf return; 130180369Slulf } 131126786Sjhb mt->consumer->lg_id = id; 132126786Sjhb LIST_INSERT_HEAD(&mt->geom->lg_consumer, mt->consumer, 133126786Sjhb lg_consumer); 134126786Sjhb LIST_INIT(&mt->consumer->lg_config); 135110603Sphk return; 136110603Sphk } 137110603Sphk if (!strcmp(name, "geom") && mt->consumer != NULL) { 138126786Sjhb mt->consumer->lg_geom = ref; 139110603Sphk return; 140110603Sphk } 141110603Sphk if (!strcmp(name, "provider") && mt->consumer != NULL) { 142126786Sjhb mt->consumer->lg_provider = ref; 143110603Sphk return; 144110603Sphk } 145110603Sphk if (!strcmp(name, "provider") && mt->provider == NULL) { 146110603Sphk mt->provider = calloc(1, sizeof *mt->provider); 147180369Slulf if (mt->provider == NULL) { 148242130Sjh mt->error = errno; 149242130Sjh XML_StopParser(mt->parser, 0); 150180369Slulf warn("Cannot allocate memory during processing of '%s' " 151180369Slulf "element", name); 152180369Slulf return; 153180369Slulf } 154126786Sjhb mt->provider->lg_id = id; 155126786Sjhb LIST_INSERT_HEAD(&mt->geom->lg_provider, mt->provider, 156126786Sjhb lg_provider); 157126786Sjhb LIST_INIT(&mt->provider->lg_consumers); 158126786Sjhb LIST_INIT(&mt->provider->lg_config); 159110603Sphk return; 160110603Sphk } 161110603Sphk if (!strcmp(name, "geom") && mt->provider != NULL) { 162126786Sjhb mt->provider->lg_geom = ref; 163110603Sphk return; 164110603Sphk } 165110603Sphk if (!strcmp(name, "config")) { 166110603Sphk if (mt->provider != NULL) { 167126786Sjhb mt->config = &mt->provider->lg_config; 168110603Sphk return; 169110603Sphk } 170110603Sphk if (mt->consumer != NULL) { 171126786Sjhb mt->config = &mt->consumer->lg_config; 172110603Sphk return; 173110603Sphk } 174110603Sphk if (mt->geom != NULL) { 175126786Sjhb mt->config = &mt->geom->lg_config; 176110603Sphk return; 177110603Sphk } 178110603Sphk if (mt->class != NULL) { 179126786Sjhb mt->config = &mt->class->lg_config; 180110603Sphk return; 181110603Sphk } 182110603Sphk } 183110603Sphk} 184110603Sphk 185110603Sphkstatic void 186110603SphkEndElement(void *userData, const char *name) 187110603Sphk{ 188110603Sphk struct mystate *mt; 189110603Sphk struct gconfig *gc; 190110603Sphk char *p; 191110603Sphk 192110603Sphk mt = userData; 193242130Sjh p = NULL; 194242130Sjh if (sbuf_finish(mt->sbuf[mt->level]) == 0) 195242130Sjh p = strdup(sbuf_data(mt->sbuf[mt->level])); 196242130Sjh sbuf_delete(mt->sbuf[mt->level]); 197242130Sjh mt->sbuf[mt->level] = NULL; 198242130Sjh mt->level--; 199180369Slulf if (p == NULL) { 200242130Sjh mt->error = errno; 201242130Sjh XML_StopParser(mt->parser, 0); 202180369Slulf warn("Cannot allocate memory during processing of '%s' " 203180369Slulf "element", name); 204180369Slulf return; 205180369Slulf } 206110603Sphk if (strlen(p) == 0) { 207110603Sphk free(p); 208110603Sphk p = NULL; 209110603Sphk } 210110603Sphk 211110603Sphk if (!strcmp(name, "name")) { 212110603Sphk if (mt->provider != NULL) { 213126786Sjhb mt->provider->lg_name = p; 214110603Sphk return; 215110603Sphk } else if (mt->geom != NULL) { 216126786Sjhb mt->geom->lg_name = p; 217110603Sphk return; 218110603Sphk } else if (mt->class != NULL) { 219126786Sjhb mt->class->lg_name = p; 220110603Sphk return; 221110603Sphk } 222110603Sphk } 223110603Sphk if (!strcmp(name, "rank") && mt->geom != NULL) { 224126786Sjhb mt->geom->lg_rank = strtoul(p, NULL, 0); 225110603Sphk free(p); 226110603Sphk return; 227110603Sphk } 228110603Sphk if (!strcmp(name, "mode") && mt->provider != NULL) { 229126786Sjhb mt->provider->lg_mode = p; 230110603Sphk return; 231110603Sphk } 232110603Sphk if (!strcmp(name, "mode") && mt->consumer != NULL) { 233126786Sjhb mt->consumer->lg_mode = p; 234110603Sphk return; 235110603Sphk } 236110603Sphk if (!strcmp(name, "mediasize") && mt->provider != NULL) { 237126786Sjhb mt->provider->lg_mediasize = strtoumax(p, NULL, 0); 238110603Sphk free(p); 239110603Sphk return; 240110603Sphk } 241110603Sphk if (!strcmp(name, "sectorsize") && mt->provider != NULL) { 242126786Sjhb mt->provider->lg_sectorsize = strtoul(p, NULL, 0); 243110603Sphk free(p); 244110603Sphk return; 245110603Sphk } 246202454Sdelphij if (!strcmp(name, "stripesize") && mt->provider != NULL) { 247202454Sdelphij mt->provider->lg_stripesize = strtoumax(p, NULL, 0); 248202454Sdelphij free(p); 249202454Sdelphij return; 250202454Sdelphij } 251202454Sdelphij if (!strcmp(name, "stripeoffset") && mt->provider != NULL) { 252202454Sdelphij mt->provider->lg_stripeoffset = strtoumax(p, NULL, 0); 253202454Sdelphij free(p); 254202454Sdelphij return; 255202454Sdelphij } 256110603Sphk 257110603Sphk if (!strcmp(name, "config")) { 258110603Sphk mt->config = NULL; 259110603Sphk return; 260110603Sphk } 261110603Sphk 262110603Sphk if (mt->config != NULL) { 263180369Slulf gc = calloc(1, sizeof *gc); 264180369Slulf if (gc == NULL) { 265242130Sjh mt->error = errno; 266242130Sjh XML_StopParser(mt->parser, 0); 267180369Slulf warn("Cannot allocate memory during processing of '%s' " 268180369Slulf "element", name); 269180369Slulf return; 270180369Slulf } 271126786Sjhb gc->lg_name = strdup(name); 272180369Slulf if (gc->lg_name == NULL) { 273242130Sjh mt->error = errno; 274242130Sjh XML_StopParser(mt->parser, 0); 275180369Slulf warn("Cannot allocate memory during processing of '%s' " 276180369Slulf "element", name); 277180369Slulf return; 278180369Slulf } 279126786Sjhb gc->lg_val = p; 280126786Sjhb LIST_INSERT_HEAD(mt->config, gc, lg_config); 281110603Sphk return; 282110603Sphk } 283110603Sphk 284110603Sphk if (p != NULL) { 285253469Sscottl#if DEBUG_LIBGEOM > 0 286112340Sphk printf("Unexpected XML: name=%s data=\"%s\"\n", name, p); 287253469Sscottl#endif 288110603Sphk free(p); 289110603Sphk } 290110603Sphk 291110603Sphk if (!strcmp(name, "consumer") && mt->consumer != NULL) { 292110603Sphk mt->consumer = NULL; 293110603Sphk return; 294110603Sphk } 295110603Sphk if (!strcmp(name, "provider") && mt->provider != NULL) { 296110603Sphk mt->provider = NULL; 297110603Sphk return; 298110603Sphk } 299110603Sphk if (!strcmp(name, "geom") && mt->consumer != NULL) { 300110603Sphk return; 301110603Sphk } 302110603Sphk if (!strcmp(name, "geom") && mt->provider != NULL) { 303110603Sphk return; 304110603Sphk } 305110603Sphk if (!strcmp(name, "geom") && mt->geom != NULL) { 306110603Sphk mt->geom = NULL; 307110603Sphk return; 308110603Sphk } 309110603Sphk if (!strcmp(name, "class") && mt->geom != NULL) { 310110603Sphk return; 311110603Sphk } 312110603Sphk if (!strcmp(name, "class") && mt->class != NULL) { 313110603Sphk mt->class = NULL; 314110603Sphk return; 315110603Sphk } 316110603Sphk} 317110603Sphk 318110603Sphkstatic void 319110603SphkCharData(void *userData , const XML_Char *s , int len) 320110603Sphk{ 321110603Sphk struct mystate *mt; 322110603Sphk const char *b, *e; 323110603Sphk 324110603Sphk mt = userData; 325110603Sphk 326110603Sphk b = s; 327110603Sphk e = s + len - 1; 328110603Sphk while (isspace(*b) && b < e) 329110603Sphk b++; 330110603Sphk while (isspace(*e) && e > b) 331110603Sphk e--; 332110603Sphk if (e != b || (*b && !isspace(*b))) 333110603Sphk sbuf_bcat(mt->sbuf[mt->level], b, e - b + 1); 334110603Sphk} 335110603Sphk 336110603Sphkstruct gident * 337112340Sphkgeom_lookupid(struct gmesh *gmp, const void *id) 338110603Sphk{ 339234107Sjmallett struct gident *gip; 340110603Sphk 341234107Sjmallett for (gip = gmp->lg_ident; gip->lg_id != NULL; gip++) 342234107Sjmallett if (gip->lg_id == id) 343234107Sjmallett return (gip); 344110603Sphk return (NULL); 345110603Sphk} 346110603Sphk 347110603Sphkint 348110603Sphkgeom_xml2tree(struct gmesh *gmp, char *p) 349110603Sphk{ 350110603Sphk XML_Parser parser; 351110603Sphk struct mystate *mt; 352110603Sphk struct gclass *cl; 353110603Sphk struct ggeom *ge; 354110603Sphk struct gprovider *pr; 355110603Sphk struct gconsumer *co; 356242130Sjh int error, i; 357110603Sphk 358110603Sphk memset(gmp, 0, sizeof *gmp); 359126786Sjhb LIST_INIT(&gmp->lg_class); 360110603Sphk parser = XML_ParserCreate(NULL); 361213451Semaste if (parser == NULL) 362213451Semaste return (ENOMEM); 363110603Sphk mt = calloc(1, sizeof *mt); 364213451Semaste if (mt == NULL) { 365213451Semaste XML_ParserFree(parser); 366110603Sphk return (ENOMEM); 367213451Semaste } 368110603Sphk mt->mesh = gmp; 369242130Sjh mt->parser = parser; 370242130Sjh error = 0; 371110603Sphk XML_SetUserData(parser, mt); 372110603Sphk XML_SetElementHandler(parser, StartElement, EndElement); 373110603Sphk XML_SetCharacterDataHandler(parser, CharData); 374110603Sphk i = XML_Parse(parser, p, strlen(p), 1); 375242130Sjh if (mt->error != 0) 376242130Sjh error = mt->error; 377242130Sjh else if (i != 1) { 378242130Sjh error = XML_GetErrorCode(parser) == XML_ERROR_NO_MEMORY ? 379242130Sjh ENOMEM : EILSEQ; 380242130Sjh } 381213451Semaste XML_ParserFree(parser); 382242130Sjh if (error != 0) { 383213451Semaste free(mt); 384242130Sjh return (error); 385213451Semaste } 386234107Sjmallett gmp->lg_ident = calloc(sizeof *gmp->lg_ident, mt->nident + 1); 387234107Sjmallett free(mt); 388234107Sjmallett if (gmp->lg_ident == NULL) 389233646Sjmallett return (ENOMEM); 390234107Sjmallett i = 0; 391110603Sphk /* Collect all identifiers */ 392126786Sjhb LIST_FOREACH(cl, &gmp->lg_class, lg_class) { 393234107Sjmallett gmp->lg_ident[i].lg_id = cl->lg_id; 394234107Sjmallett gmp->lg_ident[i].lg_ptr = cl; 395234107Sjmallett gmp->lg_ident[i].lg_what = ISCLASS; 396234107Sjmallett i++; 397126786Sjhb LIST_FOREACH(ge, &cl->lg_geom, lg_geom) { 398234107Sjmallett gmp->lg_ident[i].lg_id = ge->lg_id; 399234107Sjmallett gmp->lg_ident[i].lg_ptr = ge; 400234107Sjmallett gmp->lg_ident[i].lg_what = ISGEOM; 401234107Sjmallett i++; 402126786Sjhb LIST_FOREACH(pr, &ge->lg_provider, lg_provider) { 403234107Sjmallett gmp->lg_ident[i].lg_id = pr->lg_id; 404234107Sjmallett gmp->lg_ident[i].lg_ptr = pr; 405234107Sjmallett gmp->lg_ident[i].lg_what = ISPROVIDER; 406234107Sjmallett i++; 407110603Sphk } 408126786Sjhb LIST_FOREACH(co, &ge->lg_consumer, lg_consumer) { 409234107Sjmallett gmp->lg_ident[i].lg_id = co->lg_id; 410234107Sjmallett gmp->lg_ident[i].lg_ptr = co; 411234107Sjmallett gmp->lg_ident[i].lg_what = ISCONSUMER; 412234107Sjmallett i++; 413110603Sphk } 414110603Sphk } 415110603Sphk } 416110603Sphk /* Substitute all identifiers */ 417126786Sjhb LIST_FOREACH(cl, &gmp->lg_class, lg_class) { 418126786Sjhb LIST_FOREACH(ge, &cl->lg_geom, lg_geom) { 419126786Sjhb ge->lg_class = 420126786Sjhb geom_lookupid(gmp, ge->lg_class)->lg_ptr; 421126786Sjhb LIST_FOREACH(pr, &ge->lg_provider, lg_provider) { 422126786Sjhb pr->lg_geom = 423126786Sjhb geom_lookupid(gmp, pr->lg_geom)->lg_ptr; 424110603Sphk } 425126786Sjhb LIST_FOREACH(co, &ge->lg_consumer, lg_consumer) { 426126786Sjhb co->lg_geom = 427126786Sjhb geom_lookupid(gmp, co->lg_geom)->lg_ptr; 428126786Sjhb if (co->lg_provider != NULL) { 429126786Sjhb co->lg_provider = 430126786Sjhb geom_lookupid(gmp, 431126786Sjhb co->lg_provider)->lg_ptr; 432126748Sphk LIST_INSERT_HEAD( 433126786Sjhb &co->lg_provider->lg_consumers, 434126786Sjhb co, lg_consumers); 435126748Sphk } 436110603Sphk } 437110603Sphk } 438110603Sphk } 439110603Sphk return (0); 440110603Sphk} 441110603Sphk 442110603Sphkint 443110603Sphkgeom_gettree(struct gmesh *gmp) 444110603Sphk{ 445110603Sphk char *p; 446110603Sphk int error; 447110603Sphk 448110603Sphk p = geom_getxml(); 449146561Sphk if (p == NULL) 450146561Sphk return (errno); 451110603Sphk error = geom_xml2tree(gmp, p); 452110603Sphk free(p); 453110603Sphk return (error); 454110603Sphk} 455110603Sphk 456110603Sphkstatic void 457110603Sphkdelete_config(struct gconf *gp) 458110603Sphk{ 459110603Sphk struct gconfig *cf; 460110603Sphk 461110603Sphk for (;;) { 462110603Sphk cf = LIST_FIRST(gp); 463110603Sphk if (cf == NULL) 464110603Sphk return; 465126786Sjhb LIST_REMOVE(cf, lg_config); 466126786Sjhb free(cf->lg_name); 467126786Sjhb free(cf->lg_val); 468110603Sphk free(cf); 469110603Sphk } 470110603Sphk} 471110603Sphk 472110603Sphkvoid 473110603Sphkgeom_deletetree(struct gmesh *gmp) 474110603Sphk{ 475110603Sphk struct gclass *cl; 476110603Sphk struct ggeom *ge; 477110603Sphk struct gprovider *pr; 478110603Sphk struct gconsumer *co; 479110603Sphk 480126786Sjhb free(gmp->lg_ident); 481126786Sjhb gmp->lg_ident = NULL; 482110603Sphk for (;;) { 483126786Sjhb cl = LIST_FIRST(&gmp->lg_class); 484110603Sphk if (cl == NULL) 485110603Sphk break; 486126786Sjhb LIST_REMOVE(cl, lg_class); 487126786Sjhb delete_config(&cl->lg_config); 488126786Sjhb if (cl->lg_name) free(cl->lg_name); 489110603Sphk for (;;) { 490126786Sjhb ge = LIST_FIRST(&cl->lg_geom); 491110603Sphk if (ge == NULL) 492110603Sphk break; 493126786Sjhb LIST_REMOVE(ge, lg_geom); 494126786Sjhb delete_config(&ge->lg_config); 495126786Sjhb if (ge->lg_name) free(ge->lg_name); 496110603Sphk for (;;) { 497126786Sjhb pr = LIST_FIRST(&ge->lg_provider); 498110603Sphk if (pr == NULL) 499110603Sphk break; 500126786Sjhb LIST_REMOVE(pr, lg_provider); 501126786Sjhb delete_config(&pr->lg_config); 502126786Sjhb if (pr->lg_name) free(pr->lg_name); 503126786Sjhb if (pr->lg_mode) free(pr->lg_mode); 504110603Sphk free(pr); 505110603Sphk } 506110603Sphk for (;;) { 507126786Sjhb co = LIST_FIRST(&ge->lg_consumer); 508110603Sphk if (co == NULL) 509110603Sphk break; 510126786Sjhb LIST_REMOVE(co, lg_consumer); 511126786Sjhb delete_config(&co->lg_config); 512126786Sjhb if (co->lg_mode) free(co->lg_mode); 513110603Sphk free(co); 514110603Sphk } 515110603Sphk free(ge); 516110603Sphk } 517110603Sphk free(cl); 518110603Sphk } 519110603Sphk} 520