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