1 /* $NetBSD: main.c,v 1.1.1.15 2010/04/23 20:54:07 joerg Exp $ */ 2 3 #if HAVE_CONFIG_H 4 #include "config.h" 5 #endif 6 #include <nbcompat.h> 7 #if HAVE_SYS_CDEFS_H 8 #include <sys/cdefs.h> 9 #endif 10 __RCSID("$NetBSD: main.c,v 1.1.1.15 2010/04/23 20:54:07 joerg Exp $"); 11 12 /*- 13 * Copyright (c) 1999-2009 The NetBSD Foundation, Inc. 14 * All rights reserved. 15 * 16 * This code is derived from software contributed to The NetBSD Foundation 17 * by Hubert Feyrer <hubert@feyrer.de> and 18 * by Joerg Sonnenberger <joerg@NetBSD.org>. 19 * 20 * Redistribution and use in source and binary forms, with or without 21 * modification, are permitted provided that the following conditions 22 * are met: 23 * 1. Redistributions of source code must retain the above copyright 24 * notice, this list of conditions and the following disclaimer. 25 * 2. Redistributions in binary form must reproduce the above copyright 26 * notice, this list of conditions and the following disclaimer in the 27 * documentation and/or other materials provided with the distribution. 28 * 29 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 30 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 31 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 32 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 33 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 34 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 35 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 36 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 37 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 38 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 39 * POSSIBILITY OF SUCH DAMAGE. 40 */ 41 42 #if HAVE_SYS_TYPES_H 43 #include <sys/types.h> 44 #endif 45 #if HAVE_SYS_STAT_H 46 #include <sys/stat.h> 47 #endif 48 #if HAVE_DIRENT_H 49 #include <dirent.h> 50 #endif 51 #if HAVE_ERR_H 52 #include <err.h> 53 #endif 54 #if HAVE_ERRNO_H 55 #include <errno.h> 56 #endif 57 #if HAVE_FCNTL_H 58 #include <fcntl.h> 59 #endif 60 #ifndef NETBSD 61 #include <nbcompat/md5.h> 62 #else 63 #include <md5.h> 64 #endif 65 #if HAVE_LIMITS_H 66 #include <limits.h> 67 #endif 68 #if HAVE_STDIO_H 69 #include <stdio.h> 70 #endif 71 #if HAVE_STRING_H 72 #include <string.h> 73 #endif 74 75 #ifndef BOOTSTRAP 76 #include <archive.h> 77 #include <fetch.h> 78 #endif 79 80 #include "admin.h" 81 #include "lib.h" 82 83 #define DEFAULT_SFX ".t[bg]z" /* default suffix for ls{all,best} */ 84 85 struct pkgdb_count { 86 size_t files; 87 size_t directories; 88 size_t packages; 89 }; 90 91 static const char Options[] = "C:K:SVbd:qs:v"; 92 93 int quiet, verbose; 94 95 static void set_unset_variable(char **, Boolean); 96 97 /* print usage message and exit */ 98 void 99 usage(void) 100 { 101 (void) fprintf(stderr, "usage: %s [-bqSVv] [-C config] [-d lsdir] [-K pkg_dbdir] [-s sfx] command [args ...]\n" 102 "Where 'commands' and 'args' are:\n" 103 " rebuild - rebuild pkgdb from +CONTENTS files\n" 104 " rebuild-tree - rebuild +REQUIRED_BY files from forward deps\n" 105 " check [pkg ...] - check md5 checksum of installed files\n" 106 " add pkg ... - add pkg files to database\n" 107 " delete pkg ... - delete file entries for pkg in database\n" 108 " set variable=value pkg ... - set installation variable for package\n" 109 " unset variable pkg ... - unset installation variable for package\n" 110 " lsall /path/to/pkgpattern - list all pkgs matching the pattern\n" 111 " lsbest /path/to/pkgpattern - list pkgs matching the pattern best\n" 112 " dump - dump database\n" 113 " pmatch pattern pkg - returns true if pkg matches pattern, otherwise false\n" 114 " fetch-pkg-vulnerabilities [-s] - fetch new vulnerability file\n" 115 " check-pkg-vulnerabilities [-s] <file> - check syntax and checksums of the vulnerability file\n" 116 " audit [-es] [-t type] ... - check installed packages for vulnerabilities\n" 117 " audit-pkg [-es] [-t type] ... - check listed packages for vulnerabilities\n" 118 " audit-batch [-es] [-t type] ... - check packages in listed files for vulnerabilities\n" 119 " audit-history [-t type] ... - print all advisories for package names\n" 120 " check-license <condition> - check if condition is acceptable\n" 121 " check-single-license <license> - check if license is acceptable\n" 122 " config-var name - print current value of the configuration variable\n" 123 " check-signature ... - verify the signature of packages\n" 124 " x509-sign-package pkg spkg key cert - create X509 signature\n" 125 " gpg-sign-package pkg spkg - create GPG signature\n", 126 getprogname()); 127 exit(EXIT_FAILURE); 128 } 129 130 /* 131 * add1pkg(<pkg>) 132 * adds the files listed in the +CONTENTS of <pkg> into the 133 * pkgdb.byfile.db database file in the current package dbdir. It 134 * returns the number of files added to the database file. 135 */ 136 static int 137 add_pkg(const char *pkgdir, void *vp) 138 { 139 FILE *f; 140 plist_t *p; 141 package_t Plist; 142 char *contents; 143 char *PkgName, *dirp; 144 char file[MaxPathSize]; 145 struct pkgdb_count *count; 146 147 if (!pkgdb_open(ReadWrite)) 148 err(EXIT_FAILURE, "cannot open pkgdb"); 149 150 count = vp; 151 ++count->packages; 152 153 contents = pkgdb_pkg_file(pkgdir, CONTENTS_FNAME); 154 if ((f = fopen(contents, "r")) == NULL) 155 errx(EXIT_FAILURE, "%s: can't open `%s'", pkgdir, CONTENTS_FNAME); 156 free(contents); 157 158 read_plist(&Plist, f); 159 if ((p = find_plist(&Plist, PLIST_NAME)) == NULL) { 160 errx(EXIT_FAILURE, "Package `%s' has no @name, aborting.", pkgdir); 161 } 162 163 PkgName = p->name; 164 dirp = NULL; 165 for (p = Plist.head; p; p = p->next) { 166 switch(p->type) { 167 case PLIST_FILE: 168 if (dirp == NULL) { 169 errx(EXIT_FAILURE, "@cwd not yet found, please send-pr!"); 170 } 171 (void) snprintf(file, sizeof(file), "%s/%s", dirp, p->name); 172 if (!(isfile(file) || islinktodir(file))) { 173 if (isbrokenlink(file)) { 174 warnx("%s: Symlink `%s' exists and is in %s but target does not exist!", 175 PkgName, file, CONTENTS_FNAME); 176 } else { 177 warnx("%s: File `%s' is in %s but not on filesystem!", 178 PkgName, file, CONTENTS_FNAME); 179 } 180 } else { 181 pkgdb_store(file, PkgName); 182 ++count->files; 183 } 184 break; 185 case PLIST_PKGDIR: 186 add_pkgdir(PkgName, dirp, p->name); 187 ++count->directories; 188 break; 189 case PLIST_CWD: 190 if (strcmp(p->name, ".") != 0) 191 dirp = p->name; 192 else 193 dirp = pkgdb_pkg_dir(pkgdir); 194 break; 195 case PLIST_IGNORE: 196 p = p->next; 197 break; 198 case PLIST_SHOW_ALL: 199 case PLIST_SRC: 200 case PLIST_CMD: 201 case PLIST_CHMOD: 202 case PLIST_CHOWN: 203 case PLIST_CHGRP: 204 case PLIST_COMMENT: 205 case PLIST_NAME: 206 case PLIST_UNEXEC: 207 case PLIST_DISPLAY: 208 case PLIST_PKGDEP: 209 case PLIST_DIR_RM: 210 case PLIST_OPTION: 211 case PLIST_PKGCFL: 212 case PLIST_BLDDEP: 213 break; 214 } 215 } 216 free_plist(&Plist); 217 fclose(f); 218 pkgdb_close(); 219 220 return 0; 221 } 222 223 static void 224 delete1pkg(const char *pkgdir) 225 { 226 if (!pkgdb_open(ReadWrite)) 227 err(EXIT_FAILURE, "cannot open pkgdb"); 228 (void) pkgdb_remove_pkg(pkgdir); 229 pkgdb_close(); 230 } 231 232 static void 233 rebuild(void) 234 { 235 char *cachename; 236 struct pkgdb_count count; 237 238 count.files = 0; 239 count.directories = 0; 240 count.packages = 0; 241 242 cachename = pkgdb_get_database(); 243 if (unlink(cachename) != 0 && errno != ENOENT) 244 err(EXIT_FAILURE, "unlink %s", cachename); 245 246 setbuf(stdout, NULL); 247 248 iterate_pkg_db(add_pkg, &count); 249 250 printf("\n"); 251 printf("Stored %" PRIzu " file%s and %zu explicit director%s" 252 " from %"PRIzu " package%s in %s.\n", 253 count.files, count.files == 1 ? "" : "s", 254 count.directories, count.directories == 1 ? "y" : "ies", 255 count.packages, count.packages == 1 ? "" : "s", 256 cachename); 257 } 258 259 static int 260 lspattern(const char *pkg, void *vp) 261 { 262 const char *dir = vp; 263 printf("%s/%s\n", dir, pkg); 264 return 0; 265 } 266 267 static int 268 lsbasepattern(const char *pkg, void *vp) 269 { 270 puts(pkg); 271 return 0; 272 } 273 274 static int 275 remove_required_by(const char *pkgname, void *cookie) 276 { 277 char *path; 278 279 path = pkgdb_pkg_file(pkgname, REQUIRED_BY_FNAME); 280 281 if (unlink(path) == -1 && errno != ENOENT) 282 err(EXIT_FAILURE, "Cannot remove %s", path); 283 284 free(path); 285 286 return 0; 287 } 288 289 static void 290 add_required_by(const char *pattern, const char *required_by) 291 { 292 char *best_installed, *path; 293 int fd; 294 size_t len; 295 296 best_installed = find_best_matching_installed_pkg(pattern); 297 if (best_installed == NULL) { 298 warnx("Dependency %s of %s unresolved", pattern, required_by); 299 return; 300 } 301 302 path = pkgdb_pkg_file(best_installed, REQUIRED_BY_FNAME); 303 free(best_installed); 304 305 if ((fd = open(path, O_WRONLY | O_APPEND | O_CREAT, 0644)) == -1) 306 errx(EXIT_FAILURE, "Cannot write to %s", path); 307 free(path); 308 309 len = strlen(required_by); 310 if (write(fd, required_by, len) != (ssize_t)len || 311 write(fd, "\n", 1) != 1 || 312 close(fd) == -1) 313 errx(EXIT_FAILURE, "Cannot write to %s", path); 314 } 315 316 317 static int 318 add_depends_of(const char *pkgname, void *cookie) 319 { 320 FILE *fp; 321 plist_t *p; 322 package_t plist; 323 char *path; 324 325 path = pkgdb_pkg_file(pkgname, CONTENTS_FNAME); 326 if ((fp = fopen(path, "r")) == NULL) 327 errx(EXIT_FAILURE, "Cannot read %s of package %s", 328 CONTENTS_FNAME, pkgname); 329 free(path); 330 read_plist(&plist, fp); 331 fclose(fp); 332 333 for (p = plist.head; p; p = p->next) { 334 if (p->type == PLIST_PKGDEP) 335 add_required_by(p->name, pkgname); 336 } 337 338 free_plist(&plist); 339 340 return 0; 341 } 342 343 static void 344 rebuild_tree(void) 345 { 346 if (iterate_pkg_db(remove_required_by, NULL) == -1) 347 errx(EXIT_FAILURE, "cannot iterate pkgdb"); 348 if (iterate_pkg_db(add_depends_of, NULL) == -1) 349 errx(EXIT_FAILURE, "cannot iterate pkgdb"); 350 } 351 352 int 353 main(int argc, char *argv[]) 354 { 355 Boolean use_default_sfx = TRUE; 356 Boolean show_basename_only = FALSE; 357 char lsdir[MaxPathSize]; 358 char sfx[MaxPathSize]; 359 char *lsdirp = NULL; 360 int ch; 361 362 setprogname(argv[0]); 363 364 if (argc < 2) 365 usage(); 366 367 while ((ch = getopt(argc, argv, Options)) != -1) 368 switch (ch) { 369 case 'C': 370 config_file = optarg; 371 break; 372 373 case 'K': 374 pkgdb_set_dir(optarg, 3); 375 break; 376 377 case 'S': 378 sfx[0] = 0x0; 379 use_default_sfx = FALSE; 380 break; 381 382 case 'V': 383 show_version(); 384 /* NOTREACHED */ 385 386 case 'b': 387 show_basename_only = TRUE; 388 break; 389 390 case 'd': 391 (void) strlcpy(lsdir, optarg, sizeof(lsdir)); 392 lsdirp = lsdir; 393 break; 394 395 case 'q': 396 quiet = 1; 397 break; 398 399 case 's': 400 (void) strlcpy(sfx, optarg, sizeof(sfx)); 401 use_default_sfx = FALSE; 402 break; 403 404 case 'v': 405 ++verbose; 406 break; 407 408 default: 409 usage(); 410 /* NOTREACHED */ 411 } 412 413 argc -= optind; 414 argv += optind; 415 416 if (argc <= 0) { 417 usage(); 418 } 419 420 /* 421 * config-var is reading the config file implicitly, 422 * so skip it here. 423 */ 424 if (strcasecmp(argv[0], "config-var") != 0) 425 pkg_install_config(); 426 427 if (use_default_sfx) 428 (void) strlcpy(sfx, DEFAULT_SFX, sizeof(sfx)); 429 430 if (strcasecmp(argv[0], "pmatch") == 0) { 431 432 char *pattern, *pkg; 433 434 argv++; /* "pmatch" */ 435 436 if (argv[0] == NULL || argv[1] == NULL) { 437 usage(); 438 } 439 440 pattern = argv[0]; 441 pkg = argv[1]; 442 443 if (pkg_match(pattern, pkg)){ 444 return 0; 445 } else { 446 return 1; 447 } 448 449 } else if (strcasecmp(argv[0], "rebuild") == 0) { 450 451 rebuild(); 452 printf("Done.\n"); 453 454 455 } else if (strcasecmp(argv[0], "rebuild-tree") == 0) { 456 457 rebuild_tree(); 458 printf("Done.\n"); 459 460 } else if (strcasecmp(argv[0], "check") == 0) { 461 argv++; /* "check" */ 462 463 check(argv); 464 465 if (!quiet) { 466 printf("Done.\n"); 467 } 468 469 } else if (strcasecmp(argv[0], "lsall") == 0) { 470 argv++; /* "lsall" */ 471 472 while (*argv != NULL) { 473 /* args specified */ 474 int rc; 475 const char *basep, *dir; 476 477 dir = lsdirp ? lsdirp : dirname_of(*argv); 478 basep = basename_of(*argv); 479 480 if (show_basename_only) 481 rc = match_local_files(dir, use_default_sfx, 1, basep, lsbasepattern, NULL); 482 else 483 rc = match_local_files(dir, use_default_sfx, 1, basep, lspattern, __UNCONST(dir)); 484 if (rc == -1) 485 errx(EXIT_FAILURE, "Error from match_local_files(\"%s\", \"%s\", ...)", 486 dir, basep); 487 488 argv++; 489 } 490 491 } else if (strcasecmp(argv[0], "lsbest") == 0) { 492 argv++; /* "lsbest" */ 493 494 while (*argv != NULL) { 495 /* args specified */ 496 const char *basep, *dir; 497 char *p; 498 499 dir = lsdirp ? lsdirp : dirname_of(*argv); 500 basep = basename_of(*argv); 501 502 p = find_best_matching_file(dir, basep, use_default_sfx, 1); 503 504 if (p) { 505 if (show_basename_only) 506 printf("%s\n", p); 507 else 508 printf("%s/%s\n", dir, p); 509 free(p); 510 } 511 512 argv++; 513 } 514 } else if (strcasecmp(argv[0], "list") == 0 || 515 strcasecmp(argv[0], "dump") == 0) { 516 517 pkgdb_dump(); 518 519 } else if (strcasecmp(argv[0], "add") == 0) { 520 struct pkgdb_count count; 521 522 count.files = 0; 523 count.directories = 0; 524 count.packages = 0; 525 526 for (++argv; *argv != NULL; ++argv) 527 add_pkg(*argv, &count); 528 } else if (strcasecmp(argv[0], "delete") == 0) { 529 argv++; /* "delete" */ 530 while (*argv != NULL) { 531 delete1pkg(*argv); 532 argv++; 533 } 534 } else if (strcasecmp(argv[0], "set") == 0) { 535 argv++; /* "set" */ 536 set_unset_variable(argv, FALSE); 537 } else if (strcasecmp(argv[0], "unset") == 0) { 538 argv++; /* "unset" */ 539 set_unset_variable(argv, TRUE); 540 } else if (strcasecmp(argv[0], "config-var") == 0) { 541 argv++; 542 if (argv == NULL || argv[1] != NULL) 543 errx(EXIT_FAILURE, "config-var takes exactly one argument"); 544 pkg_install_show_variable(argv[0]); 545 } else if (strcasecmp(argv[0], "check-license") == 0) { 546 if (argv[1] == NULL) 547 errx(EXIT_FAILURE, "check-license takes exactly one argument"); 548 549 load_license_lists(); 550 551 switch (acceptable_pkg_license(argv[1])) { 552 case 0: 553 puts("no"); 554 return 0; 555 case 1: 556 puts("yes"); 557 return 0; 558 case -1: 559 errx(EXIT_FAILURE, "invalid license condition"); 560 } 561 } else if (strcasecmp(argv[0], "check-single-license") == 0) { 562 if (argv[1] == NULL) 563 errx(EXIT_FAILURE, "check-license takes exactly one argument"); 564 load_license_lists(); 565 566 switch (acceptable_license(argv[1])) { 567 case 0: 568 puts("no"); 569 return 0; 570 case 1: 571 puts("yes"); 572 return 0; 573 case -1: 574 errx(EXIT_FAILURE, "invalid license"); 575 } 576 } 577 #ifndef BOOTSTRAP 578 else if (strcasecmp(argv[0], "findbest") == 0) { 579 struct url *url; 580 char *output; 581 int rc; 582 583 process_pkg_path(); 584 585 rc = 0; 586 for (++argv; *argv != NULL; ++argv) { 587 url = find_best_package(NULL, *argv, 1); 588 if (url == NULL) { 589 rc = 1; 590 continue; 591 } 592 output = fetchStringifyURL(url); 593 puts(output); 594 fetchFreeURL(url); 595 free(output); 596 } 597 598 return rc; 599 } else if (strcasecmp(argv[0], "fetch-pkg-vulnerabilities") == 0) { 600 fetch_pkg_vulnerabilities(--argc, ++argv); 601 } else if (strcasecmp(argv[0], "check-pkg-vulnerabilities") == 0) { 602 check_pkg_vulnerabilities(--argc, ++argv); 603 } else if (strcasecmp(argv[0], "audit") == 0) { 604 audit_pkgdb(--argc, ++argv); 605 } else if (strcasecmp(argv[0], "audit-pkg") == 0) { 606 audit_pkg(--argc, ++argv); 607 } else if (strcasecmp(argv[0], "audit-batch") == 0) { 608 audit_batch(--argc, ++argv); 609 } else if (strcasecmp(argv[0], "audit-history") == 0) { 610 audit_history(--argc, ++argv); 611 } else if (strcasecmp(argv[0], "check-signature") == 0) { 612 struct archive *pkg; 613 int rc; 614 615 rc = 0; 616 for (--argc, ++argv; argc > 0; --argc, ++argv) { 617 char *archive_name; 618 619 pkg = open_archive(*argv, &archive_name); 620 if (pkg == NULL) { 621 warnx("%s could not be opened", *argv); 622 continue; 623 } 624 if (pkg_full_signature_check(archive_name, &pkg)) 625 rc = 1; 626 free(archive_name); 627 if (!pkg) 628 archive_read_finish(pkg); 629 } 630 return rc; 631 } else if (strcasecmp(argv[0], "x509-sign-package") == 0) { 632 #ifdef HAVE_SSL 633 --argc; 634 ++argv; 635 if (argc != 4) 636 errx(EXIT_FAILURE, "x509-sign-package takes exactly four arguments"); 637 pkg_sign_x509(argv[0], argv[1], argv[2], argv[3]); 638 #else 639 errx(EXIT_FAILURE, "OpenSSL support is not included"); 640 #endif 641 } else if (strcasecmp(argv[0], "gpg-sign-package") == 0) { 642 --argc; 643 ++argv; 644 if (argc != 2) 645 errx(EXIT_FAILURE, "gpg-sign-package takes exactly two arguments"); 646 pkg_sign_gpg(argv[0], argv[1]); 647 } 648 #endif 649 else { 650 usage(); 651 } 652 653 return 0; 654 } 655 656 struct set_installed_info_arg { 657 char *variable; 658 char *value; 659 int got_match; 660 }; 661 662 static int 663 set_installed_info_var(const char *name, void *cookie) 664 { 665 struct set_installed_info_arg *arg = cookie; 666 char *filename; 667 int retval; 668 669 filename = pkgdb_pkg_file(name, INSTALLED_INFO_FNAME); 670 671 retval = var_set(filename, arg->variable, arg->value); 672 673 free(filename); 674 arg->got_match = 1; 675 676 return retval; 677 } 678 679 static void 680 set_unset_variable(char **argv, Boolean unset) 681 { 682 struct set_installed_info_arg arg; 683 char *eq; 684 char *variable; 685 int ret = 0; 686 687 if (argv[0] == NULL || argv[1] == NULL) 688 usage(); 689 690 variable = NULL; 691 692 if (unset) { 693 arg.variable = argv[0]; 694 arg.value = NULL; 695 } else { 696 eq = NULL; 697 if ((eq=strchr(argv[0], '=')) == NULL) 698 usage(); 699 700 variable = xmalloc(eq-argv[0]+1); 701 strlcpy(variable, argv[0], eq-argv[0]+1); 702 703 arg.variable = variable; 704 arg.value = eq+1; 705 706 if (strcmp(variable, AUTOMATIC_VARNAME) == 0 && 707 strcasecmp(arg.value, "yes") != 0 && 708 strcasecmp(arg.value, "no") != 0) { 709 errx(EXIT_FAILURE, 710 "unknown value `%s' for " AUTOMATIC_VARNAME, 711 arg.value); 712 } 713 } 714 if (strpbrk(arg.variable, "ABCDEFGHIJKLMNOPQRSTUVWXYZ") != NULL) { 715 free(variable); 716 errx(EXIT_FAILURE, 717 "variable name must not contain uppercase letters"); 718 } 719 720 argv++; 721 while (*argv != NULL) { 722 arg.got_match = 0; 723 if (match_installed_pkgs(*argv, set_installed_info_var, &arg) == -1) 724 errx(EXIT_FAILURE, "Cannot process pkdbdb"); 725 if (arg.got_match == 0) { 726 char *pattern; 727 728 if (ispkgpattern(*argv)) { 729 warnx("no matching pkg for `%s'", *argv); 730 ret++; 731 } else { 732 pattern = xasprintf("%s-[0-9]*", *argv); 733 734 if (match_installed_pkgs(pattern, set_installed_info_var, &arg) == -1) 735 errx(EXIT_FAILURE, "Cannot process pkdbdb"); 736 737 if (arg.got_match == 0) { 738 warnx("cannot find package %s", *argv); 739 ++ret; 740 } 741 free(pattern); 742 } 743 } 744 745 argv++; 746 } 747 748 if (ret > 0) 749 exit(EXIT_FAILURE); 750 751 free(variable); 752 753 return; 754 } 755