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