1 /* 2 * (C) Copyright 2000-2003 3 * Wolfgang Denk, DENX Software Engineering, wd@denx.de. 4 * 5 * See file CREDITS for list of people who contributed to this 6 * project. 7 * 8 * This program is free software; you can redistribute it and/or 9 * modify it under the terms of the GNU General Public License as 10 * published by the Free Software Foundation; either version 2 of 11 * the License, or (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU General Public License for more details. 17 * 18 * You should have received a copy of the GNU General Public License 19 * along with this program; if not, write to the Free Software 20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, 21 * MA 02111-1307 USA 22 */ 23 24 #include <errno.h> 25 #include <fcntl.h> 26 #include <stdio.h> 27 #include <stdlib.h> 28 #include <stddef.h> 29 #include <string.h> 30 #include <sys/types.h> 31 #include <sys/ioctl.h> 32 #include <sys/stat.h> 33 #include <unistd.h> 34 #include <linux/mtd/mtd.h> 35 #include "fw_env.h" 36 37 typedef unsigned char uchar; 38 39 #define CMD_GETENV "fw_printenv" 40 #define CMD_SETENV "fw_setenv" 41 42 typedef struct envdev_s { 43 uchar devname[16]; /* Device name */ 44 ulong devoff; /* Device offset */ 45 ulong env_size; /* environment size */ 46 ulong erase_size; /* device erase size */ 47 } envdev_t; 48 49 static envdev_t envdevices[2]; 50 static int curdev; 51 52 #define DEVNAME(i) envdevices[(i)].devname 53 #define DEVOFFSET(i) envdevices[(i)].devoff 54 #define ENVSIZE(i) envdevices[(i)].env_size 55 #define DEVESIZE(i) envdevices[(i)].erase_size 56 57 #define CFG_ENV_SIZE ENVSIZE(curdev) 58 59 #define ENV_SIZE getenvsize() 60 61 typedef struct environment_s { 62 ulong crc; /* CRC32 over data bytes */ 63 uchar flags; /* active or obsolete */ 64 uchar *data; 65 } env_t; 66 67 static env_t environment; 68 69 static int HaveRedundEnv = 0; 70 71 static uchar active_flag = 1; 72 static uchar obsolete_flag = 0; 73 74 75 #define XMK_STR(x) #x 76 #define MK_STR(x) XMK_STR(x) 77 78 static uchar default_environment[] = { 79 #if defined(CONFIG_BOOTARGS) 80 "bootargs=" CONFIG_BOOTARGS "\0" 81 #endif 82 #if defined(CONFIG_BOOTCOMMAND) 83 "bootcmd=" CONFIG_BOOTCOMMAND "\0" 84 #endif 85 #if defined(CONFIG_RAMBOOTCOMMAND) 86 "ramboot=" CONFIG_RAMBOOTCOMMAND "\0" 87 #endif 88 #if defined(CONFIG_NFSBOOTCOMMAND) 89 "nfsboot=" CONFIG_NFSBOOTCOMMAND "\0" 90 #endif 91 #if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0) 92 "bootdelay=" MK_STR (CONFIG_BOOTDELAY) "\0" 93 #endif 94 #if defined(CONFIG_BAUDRATE) && (CONFIG_BAUDRATE >= 0) 95 "baudrate=" MK_STR (CONFIG_BAUDRATE) "\0" 96 #endif 97 #ifdef CONFIG_LOADS_ECHO 98 "loads_echo=" MK_STR (CONFIG_LOADS_ECHO) "\0" 99 #endif 100 #ifdef CONFIG_ETHADDR 101 "ethaddr=" MK_STR (CONFIG_ETHADDR) "\0" 102 #endif 103 #ifdef CONFIG_ETH1ADDR 104 "eth1addr=" MK_STR (CONFIG_ETH1ADDR) "\0" 105 #endif 106 #ifdef CONFIG_ETH2ADDR 107 "eth2addr=" MK_STR (CONFIG_ETH2ADDR) "\0" 108 #endif 109 #ifdef CONFIG_ETH3ADDR 110 "eth3addr=" MK_STR (CONFIG_ETH3ADDR) "\0" 111 #endif 112 #ifdef CONFIG_ETHPRIME 113 "ethprime=" CONFIG_ETHPRIME "\0" 114 #endif 115 #ifdef CONFIG_IPADDR 116 "ipaddr=" MK_STR (CONFIG_IPADDR) "\0" 117 #endif 118 #ifdef CONFIG_SERVERIP 119 "serverip=" MK_STR (CONFIG_SERVERIP) "\0" 120 #endif 121 #ifdef CFG_AUTOLOAD 122 "autoload=" CFG_AUTOLOAD "\0" 123 #endif 124 #ifdef CONFIG_ROOTPATH 125 "rootpath=" MK_STR (CONFIG_ROOTPATH) "\0" 126 #endif 127 #ifdef CONFIG_GATEWAYIP 128 "gatewayip=" MK_STR (CONFIG_GATEWAYIP) "\0" 129 #endif 130 #ifdef CONFIG_NETMASK 131 "netmask=" MK_STR (CONFIG_NETMASK) "\0" 132 #endif 133 #ifdef CONFIG_HOSTNAME 134 "hostname=" MK_STR (CONFIG_HOSTNAME) "\0" 135 #endif 136 #ifdef CONFIG_BOOTFILE 137 "bootfile=" MK_STR (CONFIG_BOOTFILE) "\0" 138 #endif 139 #ifdef CONFIG_LOADADDR 140 "loadaddr=" MK_STR (CONFIG_LOADADDR) "\0" 141 #endif 142 #ifdef CONFIG_PREBOOT 143 "preboot=" CONFIG_PREBOOT "\0" 144 #endif 145 #ifdef CONFIG_CLOCKS_IN_MHZ 146 "clocks_in_mhz=" "1" "\0" 147 #endif 148 #if defined(CONFIG_PCI_BOOTDELAY) && (CONFIG_PCI_BOOTDELAY > 0) 149 "pcidelay=" MK_STR (CONFIG_PCI_BOOTDELAY) "\0" 150 #endif 151 #ifdef CONFIG_EXTRA_ENV_SETTINGS 152 CONFIG_EXTRA_ENV_SETTINGS 153 #endif 154 "\0" /* Termimate env_t data with 2 NULs */ 155 }; 156 157 static int flash_io (int mode); 158 static uchar *envmatch (uchar * s1, uchar * s2); 159 static int env_init (void); 160 static int parse_config (void); 161 162 #if defined(CONFIG_FILE) 163 static int get_config (char *); 164 #endif 165 static inline ulong getenvsize (void) 166 { 167 ulong rc = CFG_ENV_SIZE - sizeof (long); 168 169 if (HaveRedundEnv) 170 rc -= sizeof (char); 171 return rc; 172 } 173 174 /* 175 * Search the environment for a variable. 176 * Return the value, if found, or NULL, if not found. 177 */ 178 unsigned char *fw_getenv (unsigned char *name) 179 { 180 uchar *env, *nxt; 181 182 if (env_init ()) 183 return (NULL); 184 185 for (env = environment.data; *env; env = nxt + 1) { 186 uchar *val; 187 188 for (nxt = env; *nxt; ++nxt) { 189 if (nxt >= &environment.data[ENV_SIZE]) { 190 fprintf (stderr, "## Error: " 191 "environment not terminated\n"); 192 return (NULL); 193 } 194 } 195 val = envmatch (name, env); 196 if (!val) 197 continue; 198 return (val); 199 } 200 return (NULL); 201 } 202 203 /* 204 * Print the current definition of one, or more, or all 205 * environment variables 206 */ 207 void fw_printenv (int argc, char *argv[]) 208 { 209 uchar *env, *nxt; 210 int i, n_flag; 211 212 if (env_init ()) 213 return; 214 215 if (argc == 1) { /* Print all env variables */ 216 for (env = environment.data; *env; env = nxt + 1) { 217 for (nxt = env; *nxt; ++nxt) { 218 if (nxt >= &environment.data[ENV_SIZE]) { 219 fprintf (stderr, "## Error: " 220 "environment not terminated\n"); 221 return; 222 } 223 } 224 225 printf ("%s\n", env); 226 } 227 return; 228 } 229 230 if (strcmp (argv[1], "-n") == 0) { 231 n_flag = 1; 232 ++argv; 233 --argc; 234 if (argc != 2) { 235 fprintf (stderr, "## Error: " 236 "`-n' option requires exactly one argument\n"); 237 return; 238 } 239 } else { 240 n_flag = 0; 241 } 242 243 for (i = 1; i < argc; ++i) { /* print single env variables */ 244 uchar *name = argv[i]; 245 uchar *val = NULL; 246 247 for (env = environment.data; *env; env = nxt + 1) { 248 249 for (nxt = env; *nxt; ++nxt) { 250 if (nxt >= &environment.data[ENV_SIZE]) { 251 fprintf (stderr, "## Error: " 252 "environment not terminated\n"); 253 return; 254 } 255 } 256 val = envmatch (name, env); 257 if (val) { 258 if (!n_flag) { 259 fputs (name, stdout); 260 putc ('=', stdout); 261 } 262 puts (val); 263 break; 264 } 265 } 266 if (!val) 267 fprintf (stderr, "## Error: \"%s\" not defined\n", name); 268 } 269 } 270 271 /* 272 * Deletes or sets environment variables. Returns errno style error codes: 273 * 0 - OK 274 * EINVAL - need at least 1 argument 275 * EROFS - certain variables ("ethaddr", "serial#") cannot be 276 * modified or deleted 277 * 278 */ 279 int fw_setenv (int argc, char *argv[]) 280 { 281 int i, len; 282 uchar *env, *nxt; 283 uchar *oldval = NULL; 284 uchar *name; 285 286 if (argc < 2) { 287 return (EINVAL); 288 } 289 290 if (env_init ()) 291 return (errno); 292 293 name = argv[1]; 294 295 /* 296 * search if variable with this name already exists 297 */ 298 for (nxt = env = environment.data; *env; env = nxt + 1) { 299 for (nxt = env; *nxt; ++nxt) { 300 if (nxt >= &environment.data[ENV_SIZE]) { 301 fprintf (stderr, "## Error: " 302 "environment not terminated\n"); 303 return (EINVAL); 304 } 305 } 306 if ((oldval = envmatch (name, env)) != NULL) 307 break; 308 } 309 310 /* 311 * Delete any existing definition 312 */ 313 if (oldval) { 314 /* 315 * Ethernet Address and serial# can be set only once 316 */ 317 if ((strcmp (name, "ethaddr") == 0) || 318 (strcmp (name, "serial#") == 0)) { 319 fprintf (stderr, "Can't overwrite \"%s\"\n", name); 320 return (EROFS); 321 } 322 323 if (*++nxt == '\0') { 324 *env = '\0'; 325 } else { 326 for (;;) { 327 *env = *nxt++; 328 if ((*env == '\0') && (*nxt == '\0')) 329 break; 330 ++env; 331 } 332 } 333 *++env = '\0'; 334 } 335 336 /* Delete only ? */ 337 if (argc < 3) 338 goto WRITE_FLASH; 339 340 /* 341 * Append new definition at the end 342 */ 343 for (env = environment.data; *env || *(env + 1); ++env); 344 if (env > environment.data) 345 ++env; 346 /* 347 * Overflow when: 348 * "name" + "=" + "val" +"\0\0" > CFG_ENV_SIZE - (env-environment) 349 */ 350 len = strlen (name) + 2; 351 /* add '=' for first arg, ' ' for all others */ 352 for (i = 2; i < argc; ++i) { 353 len += strlen (argv[i]) + 1; 354 } 355 if (len > (&environment.data[ENV_SIZE] - env)) { 356 fprintf (stderr, 357 "Error: environment overflow, \"%s\" deleted\n", 358 name); 359 return (-1); 360 } 361 while ((*env = *name++) != '\0') 362 env++; 363 for (i = 2; i < argc; ++i) { 364 uchar *val = argv[i]; 365 366 *env = (i == 2) ? '=' : ' '; 367 while ((*++env = *val++) != '\0'); 368 } 369 370 /* end is marked with double '\0' */ 371 *++env = '\0'; 372 373 WRITE_FLASH: 374 375 /* Update CRC */ 376 environment.crc = crc32 (0, environment.data, ENV_SIZE); 377 378 /* write environment back to flash */ 379 if (flash_io (O_RDWR)) { 380 fprintf (stderr, "Error: can't write fw_env to flash\n"); 381 return (-1); 382 } 383 384 return (0); 385 } 386 387 static int flash_io (int mode) 388 { 389 int fd, fdr, rc, otherdev, len, resid; 390 erase_info_t erase; 391 char *data = NULL; 392 393 if ((fd = open (DEVNAME (curdev), mode)) < 0) { 394 fprintf (stderr, 395 "Can't open %s: %s\n", 396 DEVNAME (curdev), strerror (errno)); 397 return (-1); 398 } 399 400 len = sizeof (environment.crc); 401 if (HaveRedundEnv) { 402 len += sizeof (environment.flags); 403 } 404 405 if (mode == O_RDWR) { 406 if (HaveRedundEnv) { 407 /* switch to next partition for writing */ 408 otherdev = !curdev; 409 if ((fdr = open (DEVNAME (otherdev), mode)) < 0) { 410 fprintf (stderr, 411 "Can't open %s: %s\n", 412 DEVNAME (otherdev), 413 strerror (errno)); 414 return (-1); 415 } 416 } else { 417 otherdev = curdev; 418 fdr = fd; 419 } 420 printf ("Unlocking flash...\n"); 421 erase.length = DEVESIZE (otherdev); 422 erase.start = DEVOFFSET (otherdev); 423 ioctl (fdr, MEMUNLOCK, &erase); 424 425 if (HaveRedundEnv) { 426 erase.length = DEVESIZE (curdev); 427 erase.start = DEVOFFSET (curdev); 428 ioctl (fd, MEMUNLOCK, &erase); 429 environment.flags = active_flag; 430 } 431 432 printf ("Done\n"); 433 resid = DEVESIZE (otherdev) - CFG_ENV_SIZE; 434 if (resid) { 435 if ((data = malloc (resid)) == NULL) { 436 fprintf (stderr, 437 "Cannot malloc %d bytes: %s\n", 438 resid, 439 strerror (errno)); 440 return (-1); 441 } 442 if (lseek (fdr, DEVOFFSET (otherdev) + CFG_ENV_SIZE, SEEK_SET) 443 == -1) { 444 fprintf (stderr, "seek error on %s: %s\n", 445 DEVNAME (otherdev), 446 strerror (errno)); 447 return (-1); 448 } 449 if ((rc = read (fdr, data, resid)) != resid) { 450 fprintf (stderr, 451 "read error on %s: %s\n", 452 DEVNAME (otherdev), 453 strerror (errno)); 454 return (-1); 455 } 456 } 457 458 printf ("Erasing old environment...\n"); 459 460 erase.length = DEVESIZE (otherdev); 461 erase.start = DEVOFFSET (otherdev); 462 if (ioctl (fdr, MEMERASE, &erase) != 0) { 463 fprintf (stderr, "MTD erase error on %s: %s\n", 464 DEVNAME (otherdev), 465 strerror (errno)); 466 return (-1); 467 } 468 469 printf ("Done\n"); 470 471 printf ("Writing environment to %s...\n", DEVNAME (otherdev)); 472 if (lseek (fdr, DEVOFFSET (otherdev), SEEK_SET) == -1) { 473 fprintf (stderr, 474 "seek error on %s: %s\n", 475 DEVNAME (otherdev), strerror (errno)); 476 return (-1); 477 } 478 if (write (fdr, &environment, len) != len) { 479 fprintf (stderr, 480 "CRC write error on %s: %s\n", 481 DEVNAME (otherdev), strerror (errno)); 482 return (-1); 483 } 484 if (write (fdr, environment.data, ENV_SIZE) != ENV_SIZE) { 485 fprintf (stderr, 486 "Write error on %s: %s\n", 487 DEVNAME (otherdev), strerror (errno)); 488 return (-1); 489 } 490 if (resid) { 491 if (write (fdr, data, resid) != resid) { 492 fprintf (stderr, 493 "write error on %s: %s\n", 494 DEVNAME (curdev), strerror (errno)); 495 return (-1); 496 } 497 free (data); 498 } 499 if (HaveRedundEnv) { 500 /* change flag on current active env partition */ 501 if (lseek (fd, DEVOFFSET (curdev) + sizeof (ulong), SEEK_SET) 502 == -1) { 503 fprintf (stderr, "seek error on %s: %s\n", 504 DEVNAME (curdev), strerror (errno)); 505 return (-1); 506 } 507 if (write (fd, &obsolete_flag, sizeof (obsolete_flag)) != 508 sizeof (obsolete_flag)) { 509 fprintf (stderr, 510 "Write error on %s: %s\n", 511 DEVNAME (curdev), strerror (errno)); 512 return (-1); 513 } 514 } 515 printf ("Done\n"); 516 printf ("Locking ...\n"); 517 erase.length = DEVESIZE (otherdev); 518 erase.start = DEVOFFSET (otherdev); 519 ioctl (fdr, MEMLOCK, &erase); 520 if (HaveRedundEnv) { 521 erase.length = DEVESIZE (curdev); 522 erase.start = DEVOFFSET (curdev); 523 ioctl (fd, MEMLOCK, &erase); 524 if (close (fdr)) { 525 fprintf (stderr, 526 "I/O error on %s: %s\n", 527 DEVNAME (otherdev), 528 strerror (errno)); 529 return (-1); 530 } 531 } 532 printf ("Done\n"); 533 } else { 534 535 if (lseek (fd, DEVOFFSET (curdev), SEEK_SET) == -1) { 536 fprintf (stderr, 537 "seek error on %s: %s\n", 538 DEVNAME (curdev), strerror (errno)); 539 return (-1); 540 } 541 if (read (fd, &environment, len) != len) { 542 fprintf (stderr, 543 "CRC read error on %s: %s\n", 544 DEVNAME (curdev), strerror (errno)); 545 return (-1); 546 } 547 if ((rc = read (fd, environment.data, ENV_SIZE)) != ENV_SIZE) { 548 fprintf (stderr, 549 "Read error on %s: %s\n", 550 DEVNAME (curdev), strerror (errno)); 551 return (-1); 552 } 553 } 554 555 if (close (fd)) { 556 fprintf (stderr, 557 "I/O error on %s: %s\n", 558 DEVNAME (curdev), strerror (errno)); 559 return (-1); 560 } 561 562 /* everything ok */ 563 return (0); 564 } 565 566 /* 567 * s1 is either a simple 'name', or a 'name=value' pair. 568 * s2 is a 'name=value' pair. 569 * If the names match, return the value of s2, else NULL. 570 */ 571 572 static uchar *envmatch (uchar * s1, uchar * s2) 573 { 574 575 while (*s1 == *s2++) 576 if (*s1++ == '=') 577 return (s2); 578 if (*s1 == '\0' && *(s2 - 1) == '=') 579 return (s2); 580 return (NULL); 581 } 582 583 /* 584 * Prevent confusion if running from erased flash memory 585 */ 586 static int env_init (void) 587 { 588 int crc1, crc1_ok; 589 uchar *addr1; 590 591 int crc2, crc2_ok; 592 uchar flag1, flag2, *addr2; 593 594 if (parse_config ()) /* should fill envdevices */ 595 return 1; 596 597 if ((addr1 = calloc (1, ENV_SIZE)) == NULL) { 598 fprintf (stderr, 599 "Not enough memory for environment (%ld bytes)\n", 600 ENV_SIZE); 601 return (errno); 602 } 603 604 /* read environment from FLASH to local buffer */ 605 environment.data = addr1; 606 curdev = 0; 607 if (flash_io (O_RDONLY)) { 608 return (errno); 609 } 610 611 crc1_ok = ((crc1 = crc32 (0, environment.data, ENV_SIZE)) 612 == environment.crc); 613 if (!HaveRedundEnv) { 614 if (!crc1_ok) { 615 fprintf (stderr, 616 "Warning: Bad CRC, using default environment\n"); 617 environment.data = default_environment; 618 free (addr1); 619 } 620 } else { 621 flag1 = environment.flags; 622 623 curdev = 1; 624 if ((addr2 = calloc (1, ENV_SIZE)) == NULL) { 625 fprintf (stderr, 626 "Not enough memory for environment (%ld bytes)\n", 627 ENV_SIZE); 628 return (errno); 629 } 630 environment.data = addr2; 631 632 if (flash_io (O_RDONLY)) { 633 return (errno); 634 } 635 636 crc2_ok = ((crc2 = crc32 (0, environment.data, ENV_SIZE)) 637 == environment.crc); 638 flag2 = environment.flags; 639 640 if (crc1_ok && !crc2_ok) { 641 environment.data = addr1; 642 environment.flags = flag1; 643 environment.crc = crc1; 644 curdev = 0; 645 free (addr2); 646 } else if (!crc1_ok && crc2_ok) { 647 environment.data = addr2; 648 environment.flags = flag2; 649 environment.crc = crc2; 650 curdev = 1; 651 free (addr1); 652 } else if (!crc1_ok && !crc2_ok) { 653 fprintf (stderr, 654 "Warning: Bad CRC, using default environment\n"); 655 environment.data = default_environment; 656 curdev = 0; 657 free (addr2); 658 free (addr1); 659 } else if (flag1 == active_flag && flag2 == obsolete_flag) { 660 environment.data = addr1; 661 environment.flags = flag1; 662 environment.crc = crc1; 663 curdev = 0; 664 free (addr2); 665 } else if (flag1 == obsolete_flag && flag2 == active_flag) { 666 environment.data = addr2; 667 environment.flags = flag2; 668 environment.crc = crc2; 669 curdev = 1; 670 free (addr1); 671 } else if (flag1 == flag2) { 672 environment.data = addr1; 673 environment.flags = flag1; 674 environment.crc = crc1; 675 curdev = 0; 676 free (addr2); 677 } else if (flag1 == 0xFF) { 678 environment.data = addr1; 679 environment.flags = flag1; 680 environment.crc = crc1; 681 curdev = 0; 682 free (addr2); 683 } else if (flag2 == 0xFF) { 684 environment.data = addr2; 685 environment.flags = flag2; 686 environment.crc = crc2; 687 curdev = 1; 688 free (addr1); 689 } 690 } 691 return (0); 692 } 693 694 695 static int parse_config () 696 { 697 struct stat st; 698 699 #if defined(CONFIG_FILE) 700 /* Fills in DEVNAME(), ENVSIZE(), DEVESIZE(). Or don't. */ 701 if (get_config (CONFIG_FILE)) { 702 fprintf (stderr, 703 "Cannot parse config file: %s\n", strerror (errno)); 704 return 1; 705 } 706 #else 707 strcpy (DEVNAME (0), DEVICE1_NAME); 708 DEVOFFSET (0) = DEVICE1_OFFSET; 709 ENVSIZE (0) = ENV1_SIZE; 710 DEVESIZE (0) = DEVICE1_ESIZE; 711 #ifdef HAVE_REDUND 712 strcpy (DEVNAME (1), DEVICE2_NAME); 713 DEVOFFSET (1) = DEVICE2_OFFSET; 714 ENVSIZE (1) = ENV2_SIZE; 715 DEVESIZE (1) = DEVICE2_ESIZE; 716 HaveRedundEnv = 1; 717 #endif 718 #endif 719 if (stat (DEVNAME (0), &st)) { 720 fprintf (stderr, 721 "Cannot access MTD device %s: %s\n", 722 DEVNAME (0), strerror (errno)); 723 return 1; 724 } 725 726 if (HaveRedundEnv && stat (DEVNAME (1), &st)) { 727 fprintf (stderr, 728 "Cannot access MTD device %s: %s\n", 729 DEVNAME (1), strerror (errno)); 730 return 1; 731 } 732 return 0; 733 } 734 735 #if defined(CONFIG_FILE) 736 static int get_config (char *fname) 737 { 738 FILE *fp; 739 int i = 0; 740 int rc; 741 char dump[128]; 742 743 if ((fp = fopen (fname, "r")) == NULL) { 744 return 1; 745 } 746 747 while ((i < 2) && ((rc = fscanf (fp, "%s %lx %lx %lx", 748 DEVNAME (i), 749 &DEVOFFSET (i), 750 &ENVSIZE (i), 751 &DEVESIZE (i) )) != EOF)) { 752 753 /* Skip incomplete conversions and comment strings */ 754 if ((rc < 3) || (*DEVNAME (i) == '#')) { 755 fgets (dump, sizeof (dump), fp); /* Consume till end */ 756 continue; 757 } 758 759 i++; 760 } 761 fclose (fp); 762 763 HaveRedundEnv = i - 1; 764 if (!i) { /* No valid entries found */ 765 errno = EINVAL; 766 return 1; 767 } else 768 return 0; 769 } 770 #endif 771