1 /*********************************************************************** 2 * * 3 * This software is part of the ast package * 4 * Copyright (c) 1985-2007 AT&T Knowledge Ventures * 5 * and is licensed under the * 6 * Common Public License, Version 1.0 * 7 * by AT&T Knowledge Ventures * 8 * * 9 * A copy of the License is available at * 10 * http://www.opensource.org/licenses/cpl1.0.txt * 11 * (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * 12 * * 13 * Information and Software Systems Research * 14 * AT&T Research * 15 * Florham Park NJ * 16 * * 17 * Glenn Fowler <gsf@research.att.com> * 18 * David Korn <dgk@research.att.com> * 19 * Phong Vo <kpv@research.att.com> * 20 * * 21 ***********************************************************************/ 22 #pragma prototyped 23 24 /* 25 * string interface to confstr(),pathconf(),sysconf(),sysinfo() 26 * extended to allow some features to be set per-process 27 */ 28 29 static const char id[] = "\n@(#)$Id: getconf (AT&T Research) 2006-11-15 $\0\n"; 30 31 #include "univlib.h" 32 33 #include <ast.h> 34 #include <error.h> 35 #include <fs3d.h> 36 #include <ctype.h> 37 #include <regex.h> 38 #include <proc.h> 39 40 #include "conftab.h" 41 #include "FEATURE/libpath" 42 43 #ifndef _pth_getconf 44 #undef ASTCONF_system 45 #define ASTCONF_system 0 46 #endif 47 48 #if _sys_systeminfo 49 # if !_lib_sysinfo 50 # if _lib_systeminfo 51 # define _lib_sysinfo 1 52 # define sysinfo(a,b,c) systeminfo(a,b,c) 53 # else 54 # if _lib_syscall && _sys_syscall 55 # include <sys/syscall.h> 56 # if defined(SYS_systeminfo) 57 # define _lib_sysinfo 1 58 # define sysinfo(a,b,c) syscall(SYS_systeminfo,a,b,c) 59 # endif 60 # endif 61 # endif 62 # endif 63 #else 64 # undef _lib_sysinfo 65 #endif 66 67 #define OP_conformance 1 68 #define OP_fs_3d 2 69 #define OP_getconf 3 70 #define OP_hosttype 4 71 #define OP_libpath 5 72 #define OP_libprefix 6 73 #define OP_libsuffix 7 74 #define OP_path_attributes 8 75 #define OP_path_resolve 9 76 #define OP_universe 10 77 78 #define CONF_ERROR (CONF_USER<<0) 79 #define CONF_READONLY (CONF_USER<<1) 80 #define CONF_ALLOC (CONF_USER<<2) 81 82 #define INITIALIZE() do{if(!state.data)synthesize(NiL,NiL,NiL);}while(0) 83 84 #define MAXVAL 256 85 86 #if MAXVAL <= UNIV_SIZE 87 #undef MAXVAL 88 #define MAXVAL (UNIV_SIZE+1) 89 #endif 90 91 #ifndef _UNIV_DEFAULT 92 #define _UNIV_DEFAULT "att" 93 #endif 94 95 static char null[1]; 96 static char root[2] = "/"; 97 98 typedef struct Feature_s 99 { 100 struct Feature_s*next; 101 const char* name; 102 char* value; 103 char* strict; 104 short length; 105 short standard; 106 unsigned short flags; 107 short op; 108 } Feature_t; 109 110 typedef struct 111 { 112 Conf_t* conf; 113 const char* name; 114 unsigned short flags; 115 short call; 116 short standard; 117 short section; 118 } Lookup_t; 119 120 static Feature_t dynamic[] = 121 { 122 { 123 &dynamic[1], 124 "CONFORMANCE", 125 "ast", 126 "standard", 127 11, 128 CONF_AST, 129 0, 130 OP_conformance 131 }, 132 { 133 &dynamic[2], 134 "FS_3D", 135 &null[0], 136 "0", 137 5, 138 CONF_AST, 139 0, 140 OP_fs_3d 141 }, 142 { 143 &dynamic[3], 144 "GETCONF", 145 #ifdef _pth_getconf 146 _pth_getconf, 147 #else 148 &null[0], 149 #endif 150 0, 151 7, 152 CONF_AST, 153 CONF_READONLY, 154 OP_getconf 155 }, 156 { 157 &dynamic[4], 158 "HOSTTYPE", 159 HOSTTYPE, 160 0, 161 8, 162 CONF_AST, 163 CONF_READONLY, 164 OP_hosttype 165 }, 166 { 167 &dynamic[5], 168 "LIBPATH", 169 #ifdef CONF_LIBPATH 170 CONF_LIBPATH, 171 #else 172 &null[0], 173 #endif 174 0, 175 7, 176 CONF_AST, 177 0, 178 OP_libpath 179 }, 180 { 181 &dynamic[6], 182 "LIBPREFIX", 183 #ifdef CONF_LIBPREFIX 184 CONF_LIBPREFIX, 185 #else 186 "lib", 187 #endif 188 0, 189 9, 190 CONF_AST, 191 0, 192 OP_libprefix 193 }, 194 { 195 &dynamic[7], 196 "LIBSUFFIX", 197 #ifdef CONF_LIBSUFFIX 198 CONF_LIBSUFFIX, 199 #else 200 ".so", 201 #endif 202 0, 203 9, 204 CONF_AST, 205 0, 206 OP_libsuffix 207 }, 208 { 209 &dynamic[8], 210 "PATH_ATTRIBUTES", 211 #if _WINIX 212 "c", 213 #else 214 &null[0], 215 #endif 216 &null[0], 217 15, 218 CONF_AST, 219 CONF_READONLY, 220 OP_path_attributes 221 }, 222 { 223 &dynamic[9], 224 "PATH_RESOLVE", 225 &null[0], 226 "metaphysical", 227 12, 228 CONF_AST, 229 0, 230 OP_path_resolve 231 }, 232 { 233 0, 234 "UNIVERSE", 235 &null[0], 236 "att", 237 8, 238 CONF_AST, 239 0, 240 OP_universe 241 }, 242 { 243 0 244 } 245 }; 246 247 typedef struct 248 { 249 250 const char* id; 251 const char* name; 252 Feature_t* features; 253 254 /* default initialization from here down */ 255 256 int prefix; 257 int synthesizing; 258 259 char* data; 260 char* last; 261 262 Feature_t* recent; 263 264 Ast_confdisc_f notify; 265 266 } State_t; 267 268 static State_t state = { "getconf", "_AST_FEATURES", dynamic }; 269 270 static char* feature(const char*, const char*, const char*, int, Error_f); 271 272 /* 273 * return fmtbuf() copy of s 274 */ 275 276 static char* 277 buffer(char* s) 278 { 279 return strcpy(fmtbuf(strlen(s) + 1), s); 280 } 281 282 /* 283 * synthesize state for fp 284 * fp==0 initializes from getenv(state.name) 285 * value==0 just does lookup 286 * otherwise state is set to value 287 */ 288 289 static char* 290 synthesize(register Feature_t* fp, const char* path, const char* value) 291 { 292 register char* s; 293 register char* d; 294 register char* v; 295 register int n; 296 297 if (state.synthesizing) 298 return null; 299 if (!state.data) 300 { 301 char* se; 302 char* de; 303 char* ve; 304 305 state.prefix = strlen(state.name) + 1; 306 n = state.prefix + 3 * MAXVAL; 307 if (s = getenv(state.name)) 308 n += strlen(s) + 1; 309 n = roundof(n, 32); 310 if (!(state.data = newof(0, char, n, 0))) 311 return 0; 312 state.last = state.data + n - 1; 313 strcpy(state.data, state.name); 314 state.data += state.prefix - 1; 315 *state.data++ = '='; 316 if (s) 317 strcpy(state.data, s); 318 ve = state.data; 319 state.synthesizing = 1; 320 for (;;) 321 { 322 for (s = ve; isspace(*s); s++); 323 for (d = s; *d && !isspace(*d); d++); 324 for (se = d; isspace(*d); d++); 325 for (v = d; *v && !isspace(*v); v++); 326 for (de = v; isspace(*v); v++); 327 if (!*v) 328 break; 329 for (ve = v; *ve && !isspace(*ve); ve++); 330 if (*ve) 331 *ve = 0; 332 else 333 ve = 0; 334 *de = 0; 335 *se = 0; 336 feature(s, d, v, 0, 0); 337 *se = ' '; 338 *de = ' '; 339 if (!ve) 340 break; 341 *ve++ = ' '; 342 } 343 state.synthesizing = 0; 344 } 345 if (!fp) 346 return state.data; 347 if (!state.last) 348 { 349 if (!value) 350 return 0; 351 n = strlen(value); 352 goto ok; 353 } 354 s = (char*)fp->name; 355 n = fp->length; 356 d = state.data; 357 for (;;) 358 { 359 while (isspace(*d)) 360 d++; 361 if (!*d) 362 break; 363 if (strneq(d, s, n) && isspace(d[n])) 364 { 365 if (!value) 366 { 367 for (d += n + 1; *d && !isspace(*d); d++); 368 for (; isspace(*d); d++); 369 for (s = d; *s && !isspace(*s); s++); 370 n = s - d; 371 value = (const char*)d; 372 goto ok; 373 } 374 for (s = d + n + 1; *s && !isspace(*s); s++); 375 for (; isspace(*s); s++); 376 for (v = s; *s && !isspace(*s); s++); 377 n = s - v; 378 if (strneq(v, value, n)) 379 goto ok; 380 for (; isspace(*s); s++); 381 if (*s) 382 for (; *d = *s++; d++); 383 else if (d != state.data) 384 d--; 385 break; 386 } 387 for (; *d && !isspace(*d); d++); 388 for (; isspace(*d); d++); 389 for (; *d && !isspace(*d); d++); 390 for (; isspace(*d); d++); 391 for (; *d && !isspace(*d); d++); 392 } 393 if (!value) 394 { 395 if (!fp->op) 396 { 397 if (fp->flags & CONF_ALLOC) 398 fp->value[0] = 0; 399 else 400 fp->value = null; 401 } 402 return 0; 403 } 404 if (!value[0]) 405 value = "0"; 406 if (!path || !path[0] || path[0] == '/' && !path[1]) 407 path = "-"; 408 n += strlen(path) + strlen(value) + 3; 409 if (d + n >= state.last) 410 { 411 int c; 412 int i; 413 414 i = d - state.data; 415 state.data -= state.prefix; 416 c = n + state.last - state.data + 3 * MAXVAL; 417 c = roundof(c, 32); 418 if (!(state.data = newof(state.data, char, c, 0))) 419 return 0; 420 state.last = state.data + c - 1; 421 state.data += state.prefix; 422 d = state.data + i; 423 } 424 if (d != state.data) 425 *d++ = ' '; 426 for (s = (char*)fp->name; *d = *s++; d++); 427 *d++ = ' '; 428 for (s = (char*)path; *d = *s++; d++); 429 *d++ = ' '; 430 for (s = (char*)value; *d = *s++; d++); 431 setenviron(state.data - state.prefix); 432 if (state.notify) 433 (*state.notify)(NiL, NiL, state.data - state.prefix); 434 n = s - (char*)value - 1; 435 ok: 436 if (!(fp->flags & CONF_ALLOC)) 437 fp->value = 0; 438 if (n == 1 && (*value == '0' || *value == '-')) 439 n = 0; 440 if (!(fp->value = newof(fp->value, char, n, 1))) 441 fp->value = null; 442 else 443 { 444 fp->flags |= CONF_ALLOC; 445 memcpy(fp->value, value, n); 446 fp->value[n] = 0; 447 } 448 return fp->value; 449 } 450 451 /* 452 * initialize the value for fp 453 * if command!=0 then it is checked for on $PATH 454 * synthesize(fp,path,succeed) called on success 455 * otherwise synthesize(fp,path,fail) called 456 */ 457 458 static void 459 initialize(register Feature_t* fp, const char* path, const char* command, const char* succeed, const char* fail) 460 { 461 register char* p; 462 register int ok = 1; 463 464 switch (fp->op) 465 { 466 case OP_conformance: 467 ok = getenv("POSIXLY_CORRECT") != 0; 468 break; 469 case OP_hosttype: 470 ok = 1; 471 break; 472 case OP_path_attributes: 473 ok = 1; 474 break; 475 case OP_path_resolve: 476 ok = fs3d(FS3D_TEST); 477 break; 478 case OP_universe: 479 ok = streq(_UNIV_DEFAULT, "att"); 480 /*FALLTHROUGH...*/ 481 default: 482 if (p = getenv("PATH")) 483 { 484 register int r = 1; 485 register char* d = p; 486 Sfio_t* tmp; 487 488 if (tmp = sfstropen()) 489 { 490 for (;;) 491 { 492 switch (*p++) 493 { 494 case 0: 495 break; 496 case ':': 497 if (command && (fp->op != OP_universe || !ok)) 498 { 499 if (r = p - d - 1) 500 { 501 sfwrite(tmp, d, r); 502 sfputc(tmp, '/'); 503 sfputr(tmp, command, 0); 504 if ((d = sfstruse(tmp)) && !eaccess(d, X_OK)) 505 { 506 ok = 1; 507 if (fp->op != OP_universe) 508 break; 509 } 510 } 511 d = p; 512 } 513 r = 1; 514 continue; 515 case '/': 516 if (r) 517 { 518 r = 0; 519 if (fp->op == OP_universe) 520 { 521 if (strneq(p, "bin:", 4) || strneq(p, "usr/bin:", 8)) 522 break; 523 } 524 } 525 if (fp->op == OP_universe) 526 { 527 if (strneq(p, "5bin", 4)) 528 { 529 ok = 1; 530 break; 531 } 532 if (strneq(p, "bsd", 3) || strneq(p, "ucb", 3)) 533 { 534 ok = 0; 535 break; 536 } 537 } 538 continue; 539 default: 540 r = 0; 541 continue; 542 } 543 break; 544 } 545 sfclose(tmp); 546 } 547 else 548 ok = 1; 549 } 550 break; 551 } 552 synthesize(fp, path, ok ? succeed : fail); 553 } 554 555 /* 556 * format synthesized value 557 */ 558 559 static char* 560 format(register Feature_t* fp, const char* path, const char* value, int flags, Error_f conferror) 561 { 562 register Feature_t* sp; 563 register int n; 564 565 switch (fp->op) 566 { 567 568 case OP_conformance: 569 if (value && (streq(value, "strict") || streq(value, "posix") || streq(value, "xopen"))) 570 value = fp->strict; 571 n = streq(fp->value, fp->strict); 572 if (!synthesize(fp, path, value)) 573 initialize(fp, path, NiL, fp->strict, fp->value); 574 if (!n && streq(fp->value, fp->strict)) 575 for (sp = state.features; sp; sp = sp->next) 576 if (sp->strict && sp->op && sp->op != OP_conformance) 577 astconf(sp->name, path, sp->strict); 578 break; 579 580 case OP_fs_3d: 581 fp->value = fs3d(value ? value[0] ? FS3D_ON : FS3D_OFF : FS3D_TEST) ? "1" : null; 582 break; 583 584 case OP_hosttype: 585 break; 586 587 case OP_path_attributes: 588 #ifdef _PC_PATH_ATTRIBUTES 589 { 590 register char* s; 591 register char* e; 592 intmax_t v; 593 594 /* 595 * _PC_PATH_ATTRIBUTES is a bitmap for 'a' to 'z' 596 */ 597 598 if ((v = pathconf(path, _PC_PATH_ATTRIBUTES)) == -1L) 599 return 0; 600 s = fp->value; 601 e = s + sizeof(fp->value) - 1; 602 for (n = 'a'; n <= 'z'; n++) 603 if (v & (1 << (n - 'a'))) 604 { 605 *s++ = n; 606 if (s >= e) 607 break; 608 } 609 *s = 0; 610 } 611 #endif 612 break; 613 614 case OP_path_resolve: 615 if (!synthesize(fp, path, value)) 616 initialize(fp, path, NiL, "logical", "metaphysical"); 617 break; 618 619 case OP_universe: 620 #if _lib_universe 621 if (getuniverse(fp->value) < 0) 622 strcpy(fp->value, "att"); 623 if (value) 624 setuniverse(value); 625 #else 626 #ifdef UNIV_MAX 627 n = 0; 628 if (value) 629 { 630 while (n < univ_max && !streq(value, univ_name[n]) 631 n++; 632 if (n >= univ_max) 633 { 634 if (conferror) 635 (*conferror)(&state, &state, 2, "%s: %s: universe value too large", fp->name, value); 636 return 0; 637 } 638 } 639 #ifdef ATT_UNIV 640 n = setuniverse(n + 1); 641 if (!value && n > 0) 642 setuniverse(n); 643 #else 644 n = universe(value ? n + 1 : U_GET); 645 #endif 646 if (n <= 0 || n >= univ_max) 647 n = 1; 648 strcpy(fp->value, univ_name[n - 1]); 649 #else 650 if (!synthesize(fp, path, value)) 651 initialize(fp, path, "echo", "att", "ucb"); 652 #endif 653 #endif 654 break; 655 656 default: 657 synthesize(fp, path, value); 658 break; 659 660 } 661 return fp->value; 662 } 663 664 /* 665 * value==0 get feature name 666 * value!=0 set feature name 667 * 0 returned if error or not defined; otherwise previous value 668 */ 669 670 static char* 671 feature(const char* name, const char* path, const char* value, int flags, Error_f conferror) 672 { 673 register Feature_t* fp; 674 register int n; 675 676 if (value && (streq(value, "-") || streq(value, "0"))) 677 value = null; 678 for (fp = state.features; fp && !streq(fp->name, name); fp = fp->next); 679 #if DEBUG || DEBUG_astconf 680 error(-2, "astconf feature name=%s path=%s value=%s flags=%04x fp=%p", name, path, value, flags, fp); 681 #endif 682 if (!fp) 683 { 684 if (!value) 685 return 0; 686 if (state.notify && !(*state.notify)(name, path, value)) 687 return 0; 688 n = strlen(name); 689 if (!(fp = newof(0, Feature_t, 1, n + 1))) 690 { 691 if (conferror) 692 (*conferror)(&state, &state, 2, "%s: out of space", name); 693 return 0; 694 } 695 fp->name = (const char*)fp + sizeof(Feature_t); 696 strcpy((char*)fp->name, name); 697 fp->length = n; 698 fp->next = state.features; 699 state.features = fp; 700 } 701 else if (value) 702 { 703 if (fp->flags & CONF_READONLY) 704 { 705 if (conferror) 706 (*conferror)(&state, &state, 2, "%s: cannot set readonly symbol", fp->name); 707 return 0; 708 } 709 if (state.notify && !streq(fp->value, value) && !(*state.notify)(name, path, value)) 710 return 0; 711 } 712 else 713 state.recent = fp; 714 return format(fp, path, value, flags, conferror); 715 } 716 717 /* 718 * binary search for name in conf[] 719 */ 720 721 static int 722 lookup(register Lookup_t* look, const char* name, int flags) 723 { 724 register Conf_t* mid = (Conf_t*)conf; 725 register Conf_t* lo = mid; 726 register Conf_t* hi = mid + conf_elements; 727 register int v; 728 register int c; 729 char* e; 730 const Prefix_t* p; 731 732 static Conf_t num; 733 734 look->flags = 0; 735 look->call = -1; 736 look->standard = (flags & ASTCONF_AST) ? CONF_AST : -1; 737 look->section = -1; 738 while (*name == '_') 739 name++; 740 again: 741 for (p = prefix; p < &prefix[prefix_elements]; p++) 742 if (strneq(name, p->name, p->length) && ((c = name[p->length] == '_' || name[p->length] == '(') || (v = isdigit(name[p->length]) && name[p->length + 1] == '_'))) 743 { 744 if (p->call < 0) 745 { 746 if (look->standard >= 0) 747 break; 748 look->standard = p->standard; 749 } 750 else 751 { 752 if (look->call >= 0) 753 break; 754 look->call = p->call; 755 } 756 if (name[p->length] == '(') 757 { 758 look->conf = # 759 strncpy((char*)num.name, name, sizeof(num.name)); 760 num.call = p->call; 761 num.flags = *name == 'C' ? CONF_STRING : 0; 762 num.op = (short)strtol(name + p->length + 1, &e, 10); 763 if (*e++ != ')' || *e) 764 break; 765 return 1; 766 } 767 name += p->length + c; 768 if (look->section < 0 && !c && v) 769 { 770 look->section = name[0] - '0'; 771 name += 2; 772 } 773 goto again; 774 } 775 #if HUH_2006_02_10 776 if (look->section < 0) 777 look->section = 1; 778 #endif 779 look->name = name; 780 #if DEBUG || DEBUG_astconf 781 error(-2, "astconf normal name=%s standard=%d section=%d call=%d flags=%04x elements=%d", look->name, look->standard, look->section, look->call, flags, conf_elements); 782 #endif 783 c = *((unsigned char*)name); 784 while (lo <= hi) 785 { 786 mid = lo + (hi - lo) / 2; 787 #if DEBUG || DEBUG_astconf 788 error(-3, "astconf lookup name=%s mid=%s", name, mid->name); 789 #endif 790 if (!(v = c - *((unsigned char*)mid->name)) && !(v = strcmp(name, mid->name))) 791 { 792 hi = mid; 793 lo = (Conf_t*)conf; 794 do 795 { 796 if ((look->standard < 0 || look->standard == mid->standard) && 797 (look->section < 0 || look->section == mid->section) && 798 (look->call < 0 || look->call == mid->call)) 799 goto found; 800 } while (mid-- > lo && streq(mid->name, look->name)); 801 mid = hi; 802 hi = lo + conf_elements - 1; 803 while (++mid < hi && streq(mid->name, look->name)) 804 { 805 if ((look->standard < 0 || look->standard == mid->standard) && 806 (look->section < 0 || look->section == mid->section) && 807 (look->call < 0 || look->call == mid->call)) 808 goto found; 809 } 810 break; 811 } 812 else if (v > 0) 813 lo = mid + 1; 814 else 815 hi = mid - 1; 816 } 817 return 0; 818 found: 819 if (look->call < 0 && look->standard >= 0 && (look->section <= 1 || (mid->flags & CONF_MINMAX))) 820 look->flags |= CONF_MINMAX; 821 look->conf = mid; 822 #if DEBUG || DEBUG_astconf 823 error(-2, "astconf lookup name=%s standard=%d:%d section=%d:%d call=%d:%d", look->name, look->standard, mid->standard, look->section, mid->section, look->call, mid->call); 824 #endif 825 return 1; 826 } 827 828 /* 829 * return a tolower'd copy of s 830 */ 831 832 static char* 833 fmtlower(register const char* s) 834 { 835 register int c; 836 register char* t; 837 char* b; 838 839 b = t = fmtbuf(strlen(s) + 1); 840 while (c = *s++) 841 { 842 if (isupper(c)) 843 c = tolower(c); 844 *t++ = c; 845 } 846 *t = 0; 847 return b; 848 } 849 850 /* 851 * print value line for p 852 * if !name then value prefixed by "p->name=" 853 * if (flags & CONF_MINMAX) then default minmax value used 854 */ 855 856 static char* 857 print(Sfio_t* sp, register Lookup_t* look, const char* name, const char* path, int listflags, Error_f conferror) 858 { 859 register Conf_t* p = look->conf; 860 register int flags = look->flags; 861 char* call; 862 char* f; 863 const char* s; 864 int i; 865 int olderrno; 866 int drop; 867 int defined; 868 intmax_t v; 869 char buf[PATH_MAX]; 870 char flg[16]; 871 872 if (!name && !(p->flags & CONF_STRING) && (p->flags & (CONF_FEATURE|CONF_LIMIT|CONF_MINMAX)) && (p->flags & (CONF_LIMIT|CONF_PREFIXED)) != CONF_LIMIT) 873 flags |= CONF_PREFIXED; 874 olderrno = errno; 875 errno = 0; 876 #if DEBUG || DEBUG_astconf 877 error(-1, "astconf name=%s:%s standard=%d section=%d call=%s op=%d flags=|%s%s%s%s%s:|%s%s%s%s%s%s%s%s%s%s" 878 , name , p->name, p->standard, p->section, prefix[p->call + CONF_call].name, p->op 879 , (flags & CONF_FEATURE) ? "FEATURE|" : "" 880 , (flags & CONF_LIMIT) ? "LIMIT|" : "" 881 , (flags & CONF_MINMAX) ? "MINMAX|" : "" 882 , (flags & CONF_PREFIXED) ? "PREFIXED|" : "" 883 , (flags & CONF_STRING) ? "STRING|" : "" 884 , (p->flags & CONF_DEFER_CALL) ? "DEFER_CALL|" : "" 885 , (p->flags & CONF_DEFER_MM) ? "DEFER_MM|" : "" 886 , (p->flags & CONF_FEATURE) ? "FEATURE|" : "" 887 , (p->flags & CONF_LIMIT_DEF) ? "LIMIT_DEF|" : (p->flags & CONF_LIMIT) ? "LIMIT|" : "" 888 , (p->flags & CONF_MINMAX_DEF) ? "MINMAX_DEF|" : (p->flags & CONF_MINMAX) ? "MINMAX|" : "" 889 , (p->flags & CONF_NOUNDERSCORE) ? "NOUNDERSCORE|" : "" 890 , (p->flags & CONF_PREFIXED) ? "PREFIXED|" : "" 891 , (p->flags & CONF_PREFIX_ONLY) ? "PREFIX_ONLY|" : "" 892 , (p->flags & CONF_STANDARD) ? "STANDARD|" : "" 893 , (p->flags & CONF_STRING) ? "STRING|" : "" 894 , (p->flags & CONF_UNDERSCORE) ? "UNDERSCORE|" : "" 895 ); 896 #endif 897 flags |= CONF_LIMIT_DEF|CONF_MINMAX_DEF; 898 if (conferror && name) 899 { 900 if ((p->flags & CONF_PREFIX_ONLY) && look->standard < 0) 901 goto bad; 902 if (!(flags & CONF_MINMAX) || !(p->flags & CONF_MINMAX)) 903 { 904 switch (p->call) 905 { 906 case CONF_pathconf: 907 if (path == root) 908 { 909 (*conferror)(&state, &state, 2, "%s: path expected", name); 910 goto bad; 911 } 912 break; 913 default: 914 if (path != root) 915 { 916 (*conferror)(&state, &state, 2, "%s: path not expected", name); 917 goto bad; 918 } 919 break; 920 } 921 #ifdef _pth_getconf 922 if (p->flags & CONF_DEFER_CALL) 923 goto bad; 924 #endif 925 } 926 else 927 { 928 if (path != root) 929 { 930 (*conferror)(&state, &state, 2, "%s: path not expected", name); 931 goto bad; 932 } 933 #ifdef _pth_getconf 934 if ((p->flags & CONF_DEFER_MM) || !(p->flags & CONF_MINMAX_DEF)) 935 goto bad; 936 #endif 937 } 938 if (look->standard >= 0 && (name[0] != '_' && ((p->flags & CONF_UNDERSCORE) || look->section <= 1) || name[0] == '_' && (p->flags & CONF_NOUNDERSCORE)) || look->standard < 0 && name[0] == '_') 939 goto bad; 940 } 941 s = 0; 942 defined = 1; 943 switch (i = (p->op < 0 || (flags & CONF_MINMAX) && (p->flags & CONF_MINMAX_DEF)) ? 0 : p->call) 944 { 945 case CONF_confstr: 946 call = "confstr"; 947 #if _lib_confstr 948 if (!(v = confstr(p->op, buf, sizeof(buf)))) 949 { 950 defined = 0; 951 v = -1; 952 errno = EINVAL; 953 } 954 else if (v > 0) 955 { 956 buf[sizeof(buf) - 1] = 0; 957 s = (const char*)buf; 958 } 959 else 960 defined = 0; 961 break; 962 #else 963 goto predef; 964 #endif 965 case CONF_pathconf: 966 call = "pathconf"; 967 #if _lib_pathconf 968 if ((v = pathconf(path, p->op)) < 0) 969 defined = 0; 970 break; 971 #else 972 goto predef; 973 #endif 974 case CONF_sysconf: 975 call = "sysconf"; 976 #if _lib_sysconf 977 if ((v = sysconf(p->op)) < 0) 978 defined = 0; 979 break; 980 #else 981 goto predef; 982 #endif 983 case CONF_sysinfo: 984 call = "sysinfo"; 985 #if _lib_sysinfo 986 if ((v = sysinfo(p->op, buf, sizeof(buf))) >= 0) 987 { 988 buf[sizeof(buf) - 1] = 0; 989 s = (const char*)buf; 990 } 991 else 992 defined = 0; 993 break; 994 #else 995 goto predef; 996 #endif 997 default: 998 call = "synthesis"; 999 errno = EINVAL; 1000 v = -1; 1001 defined = 0; 1002 break; 1003 case 0: 1004 call = 0; 1005 if (p->flags & CONF_MINMAX_DEF) 1006 { 1007 if (!((p->flags & CONF_LIMIT_DEF))) 1008 flags |= CONF_MINMAX; 1009 listflags &= ~ASTCONF_system; 1010 } 1011 predef: 1012 if (look->standard == CONF_AST) 1013 { 1014 if (streq(look->name, "VERSION")) 1015 { 1016 v = _AST_VERSION; 1017 break; 1018 } 1019 } 1020 if (flags & CONF_MINMAX) 1021 { 1022 if ((p->flags & CONF_MINMAX_DEF) && (!(listflags & ASTCONF_system) || !(p->flags & CONF_DEFER_MM))) 1023 { 1024 v = p->minmax.number; 1025 s = p->minmax.string; 1026 break; 1027 } 1028 } 1029 else if ((p->flags & CONF_LIMIT_DEF) && (!(listflags & ASTCONF_system) || !(p->flags & CONF_DEFER_CALL))) 1030 { 1031 v = p->limit.number; 1032 s = p->limit.string; 1033 break; 1034 } 1035 flags &= ~(CONF_LIMIT_DEF|CONF_MINMAX_DEF); 1036 v = -1; 1037 errno = EINVAL; 1038 defined = 0; 1039 break; 1040 } 1041 if (!defined) 1042 { 1043 if (!errno) 1044 { 1045 if ((p->flags & CONF_FEATURE) || !(p->flags & (CONF_LIMIT|CONF_MINMAX))) 1046 flags &= ~(CONF_LIMIT_DEF|CONF_MINMAX_DEF); 1047 } 1048 else if (flags & CONF_PREFIXED) 1049 flags &= ~(CONF_LIMIT_DEF|CONF_MINMAX_DEF); 1050 else if (errno != EINVAL || !i) 1051 { 1052 if (!sp) 1053 { 1054 if (conferror) 1055 { 1056 if (call) 1057 (*conferror)(&state, &state, ERROR_SYSTEM|2, "%s: %s error", p->name, call); 1058 else if (!(listflags & ASTCONF_system)) 1059 (*conferror)(&state, &state, 2, "%s: unknown name", p->name); 1060 } 1061 goto bad; 1062 } 1063 else 1064 { 1065 flags &= ~(CONF_LIMIT_DEF|CONF_MINMAX_DEF); 1066 flags |= CONF_ERROR; 1067 } 1068 } 1069 } 1070 errno = olderrno; 1071 if ((listflags & ASTCONF_defined) && !(flags & (CONF_LIMIT_DEF|CONF_MINMAX_DEF))) 1072 goto bad; 1073 if ((drop = !sp) && !(sp = sfstropen())) 1074 goto bad; 1075 if (listflags & ASTCONF_table) 1076 { 1077 f = flg; 1078 if (p->flags & CONF_DEFER_CALL) 1079 *f++ = 'C'; 1080 if (p->flags & CONF_DEFER_MM) 1081 *f++ = 'D'; 1082 if (p->flags & CONF_FEATURE) 1083 *f++ = 'F'; 1084 if (p->flags & CONF_LIMIT) 1085 *f++ = 'L'; 1086 if (p->flags & CONF_MINMAX) 1087 *f++ = 'M'; 1088 if (p->flags & CONF_NOSECTION) 1089 *f++ = 'N'; 1090 if (p->flags & CONF_PREFIXED) 1091 *f++ = 'P'; 1092 if (p->flags & CONF_STANDARD) 1093 *f++ = 'S'; 1094 if (p->flags & CONF_UNDERSCORE) 1095 *f++ = 'U'; 1096 if (p->flags & CONF_NOUNDERSCORE) 1097 *f++ = 'V'; 1098 if (p->flags & CONF_PREFIX_ONLY) 1099 *f++ = 'W'; 1100 if (f == flg) 1101 *f++ = 'X'; 1102 *f = 0; 1103 sfprintf(sp, "%*s %*s %d %2s %4d %6s ", sizeof(p->name), p->name, sizeof(prefix[p->standard].name), prefix[p->standard].name, p->section, prefix[p->call + CONF_call].name, p->op, flg); 1104 if (p->flags & CONF_LIMIT_DEF) 1105 { 1106 if (p->limit.string) 1107 sfprintf(sp, "L[%s] ", (listflags & ASTCONF_quote) ? fmtquote(p->limit.string, "\"", "\"", strlen(p->limit.string), FMT_SHELL) : p->limit.string); 1108 else 1109 sfprintf(sp, "L[%I*d] ", sizeof(p->limit.number), p->limit.number); 1110 } 1111 if (p->flags & CONF_MINMAX_DEF) 1112 { 1113 if (p->minmax.string) 1114 sfprintf(sp, "M[%s] ", (listflags & ASTCONF_quote) ? fmtquote(p->minmax.string, "\"", "\"", strlen(p->minmax.string), FMT_SHELL) : p->minmax.string); 1115 else 1116 sfprintf(sp, "M[%I*d] ", sizeof(p->minmax.number), p->minmax.number); 1117 } 1118 if (flags & CONF_ERROR) 1119 sfprintf(sp, "error"); 1120 else if (defined) 1121 { 1122 if (s) 1123 sfprintf(sp, "%s", (listflags & ASTCONF_quote) ? fmtquote(s, "\"", "\"", strlen(s), FMT_SHELL) : s); 1124 else if (v != -1) 1125 sfprintf(sp, "%I*d", sizeof(v), v); 1126 else 1127 sfprintf(sp, "%I*u", sizeof(v), v); 1128 } 1129 sfprintf(sp, "\n"); 1130 } 1131 else 1132 { 1133 if (!(flags & CONF_PREFIXED) || (listflags & ASTCONF_base)) 1134 { 1135 if (!name) 1136 { 1137 if ((p->flags & (CONF_PREFIXED|CONF_STRING)) == (CONF_PREFIXED|CONF_STRING) && (!(listflags & ASTCONF_base) || p->standard != CONF_POSIX)) 1138 { 1139 if ((p->flags & CONF_UNDERSCORE) && !(listflags & ASTCONF_base)) 1140 sfprintf(sp, "_"); 1141 sfprintf(sp, "%s", (listflags & ASTCONF_lower) ? fmtlower(prefix[p->standard].name) : prefix[p->standard].name); 1142 if (p->section > 1) 1143 sfprintf(sp, "%d", p->section); 1144 sfprintf(sp, "_"); 1145 } 1146 sfprintf(sp, "%s=", (listflags & ASTCONF_lower) ? fmtlower(p->name) : p->name); 1147 } 1148 if (flags & CONF_ERROR) 1149 sfprintf(sp, "error"); 1150 else if (defined) 1151 { 1152 if (s) 1153 sfprintf(sp, "%s", (listflags & ASTCONF_quote) ? fmtquote(s, "\"", "\"", strlen(s), FMT_SHELL) : s); 1154 else if (v != -1) 1155 sfprintf(sp, "%I*d", sizeof(v), v); 1156 else 1157 sfprintf(sp, "%I*u", sizeof(v), v); 1158 } 1159 else 1160 sfprintf(sp, "undefined"); 1161 if (!name) 1162 sfprintf(sp, "\n"); 1163 } 1164 if (!name && !(listflags & ASTCONF_base) && !(p->flags & CONF_STRING) && (p->flags & (CONF_FEATURE|CONF_MINMAX))) 1165 { 1166 if (p->flags & CONF_UNDERSCORE) 1167 sfprintf(sp, "_"); 1168 sfprintf(sp, "%s", (listflags & ASTCONF_lower) ? fmtlower(prefix[p->standard].name) : prefix[p->standard].name); 1169 if (p->section > 1) 1170 sfprintf(sp, "%d", p->section); 1171 sfprintf(sp, "_%s=", (listflags & ASTCONF_lower) ? fmtlower(p->name) : p->name); 1172 if (v != -1) 1173 sfprintf(sp, "%I*d", sizeof(v), v); 1174 else if (defined) 1175 sfprintf(sp, "%I*u", sizeof(v), v); 1176 else 1177 sfprintf(sp, "undefined"); 1178 sfprintf(sp, "\n"); 1179 } 1180 } 1181 if (drop) 1182 { 1183 if (call = sfstruse(sp)) 1184 call = buffer(call); 1185 else 1186 call = "[ out of space ]"; 1187 sfclose(sp); 1188 return call; 1189 } 1190 bad: 1191 return (listflags & ASTCONF_error) ? (char*)0 : null; 1192 } 1193 1194 /* 1195 * return read stream to native getconf utility 1196 */ 1197 1198 static Sfio_t* 1199 nativeconf(Proc_t** pp, const char* operand) 1200 { 1201 #ifdef _pth_getconf 1202 Sfio_t* sp; 1203 char* cmd[3]; 1204 long ops[2]; 1205 1206 #if DEBUG || DEBUG_astconf 1207 error(-2, "astconf defer %s %s", _pth_getconf, operand); 1208 #endif 1209 cmd[0] = (char*)state.id; 1210 cmd[1] = (char*)operand; 1211 cmd[2] = 0; 1212 ops[0] = PROC_FD_DUP(open("/dev/null",O_WRONLY,0), 2, PROC_FD_CHILD); 1213 ops[1] = 0; 1214 if (*pp = procopen(_pth_getconf, cmd, environ, ops, PROC_READ)) 1215 { 1216 if (sp = sfnew(NiL, NiL, SF_UNBOUND, (*pp)->rfd, SF_READ)) 1217 { 1218 sfdisc(sp, SF_POPDISC); 1219 return sp; 1220 } 1221 procclose(*pp); 1222 } 1223 #endif 1224 return 0; 1225 } 1226 1227 /* 1228 * value==0 gets value for name 1229 * value!=0 sets value for name and returns previous value 1230 * path==0 implies path=="/" 1231 * 1232 * settable return values are in permanent store 1233 * non-settable return values copied to a tmp fmtbuf() buffer 1234 * 1235 * if (streq(astgetconf("PATH_RESOLVE", NiL, NiL, 0, 0), "logical")) 1236 * our_way(); 1237 * 1238 * universe = astgetconf("UNIVERSE", NiL, "att", 0, 0); 1239 * astgetconf("UNIVERSE", NiL, universe, 0, 0); 1240 * 1241 * if (flags&ASTCONF_error)!=0 then error return value is 0 1242 * otherwise 0 not returned 1243 */ 1244 1245 #define ALT 16 1246 1247 char* 1248 astgetconf(const char* name, const char* path, const char* value, int flags, Error_f conferror) 1249 { 1250 register char* s; 1251 int n; 1252 Lookup_t look; 1253 Sfio_t* tmp; 1254 1255 #if __OBSOLETE__ < 20080101 1256 if (pointerof(flags) == (void*)errorf) 1257 { 1258 conferror = errorf; 1259 flags = ASTCONF_error; 1260 } 1261 else if (conferror && conferror != errorf) 1262 conferror = 0; 1263 #endif 1264 if (!name) 1265 { 1266 if (path) 1267 return null; 1268 if (!(name = value)) 1269 { 1270 if (state.data) 1271 { 1272 Ast_confdisc_f notify; 1273 1274 #if _HUH20000515 /* doesn't work for shell builtins */ 1275 free(state.data - state.prefix); 1276 #endif 1277 state.data = 0; 1278 notify = state.notify; 1279 state.notify = 0; 1280 INITIALIZE(); 1281 state.notify = notify; 1282 } 1283 return null; 1284 } 1285 value = 0; 1286 } 1287 INITIALIZE(); 1288 if (!path) 1289 path = root; 1290 if (state.recent && streq(name, state.recent->name) && (s = format(state.recent, path, value, flags, conferror))) 1291 return s; 1292 if (lookup(&look, name, flags)) 1293 { 1294 if (value) 1295 { 1296 ro: 1297 errno = EINVAL; 1298 if (conferror) 1299 (*conferror)(&state, &state, 2, "%s: cannot set value", name); 1300 return (flags & ASTCONF_error) ? (char*)0 : null; 1301 } 1302 return print(NiL, &look, name, path, flags, conferror); 1303 } 1304 if ((n = strlen(name)) > 3 && n < (ALT + 3)) 1305 { 1306 if (streq(name + n - 3, "DEV")) 1307 { 1308 if (tmp = sfstropen()) 1309 { 1310 sfprintf(tmp, "/dev/"); 1311 for (s = (char*)name; s < (char*)name + n - 3; s++) 1312 sfputc(tmp, isupper(*s) ? tolower(*s) : *s); 1313 if ((s = sfstruse(tmp)) && !access(s, F_OK)) 1314 { 1315 if (value) 1316 goto ro; 1317 s = buffer(s); 1318 sfclose(tmp); 1319 return s; 1320 } 1321 sfclose(tmp); 1322 } 1323 } 1324 else if (streq(name + n - 3, "DIR")) 1325 { 1326 Lookup_t altlook; 1327 char altname[ALT]; 1328 1329 static const char* dirs[] = { "/usr/lib", "/usr", null }; 1330 1331 strcpy(altname, name); 1332 altname[n - 3] = 0; 1333 if (lookup(&altlook, altname, flags)) 1334 { 1335 if (value) 1336 { 1337 errno = EINVAL; 1338 if (conferror) 1339 (*conferror)(&state, &state, 2, "%s: cannot set value", altname); 1340 return (flags & ASTCONF_error) ? (char*)0 : null; 1341 } 1342 return print(NiL, &altlook, altname, path, flags, conferror); 1343 } 1344 for (s = altname; *s; s++) 1345 if (isupper(*s)) 1346 *s = tolower(*s); 1347 if (tmp = sfstropen()) 1348 { 1349 for (n = 0; n < elementsof(dirs); n++) 1350 { 1351 sfprintf(tmp, "%s/%s/.", dirs[n], altname); 1352 if ((s = sfstruse(tmp)) && !access(s, F_OK)) 1353 { 1354 if (value) 1355 goto ro; 1356 s = buffer(s); 1357 sfclose(tmp); 1358 return s; 1359 } 1360 } 1361 sfclose(tmp); 1362 } 1363 } 1364 } 1365 if ((look.standard < 0 || look.standard == CONF_AST) && look.call <= 0 && look.section <= 1 && (s = feature(look.name, path, value, flags, conferror))) 1366 return s; 1367 errno = EINVAL; 1368 if (conferror && !(flags & ASTCONF_system)) 1369 (*conferror)(&state, &state, 2, "%s: unknown name", name); 1370 return (flags & ASTCONF_error) ? (char*)0 : null; 1371 } 1372 1373 /* 1374 * astconf() never returns 0 1375 */ 1376 1377 char* 1378 astconf(const char* name, const char* path, const char* value) 1379 { 1380 return astgetconf(name, path, value, 0, 0); 1381 } 1382 1383 /* 1384 * set discipline function to be called when features change 1385 * old discipline function returned 1386 */ 1387 1388 Ast_confdisc_f 1389 astconfdisc(Ast_confdisc_f new_notify) 1390 { 1391 Ast_confdisc_f old_notify; 1392 1393 INITIALIZE(); 1394 old_notify = state.notify; 1395 state.notify = new_notify; 1396 return old_notify; 1397 } 1398 1399 /* 1400 * list all name=value entries on sp 1401 * path==0 implies path=="/" 1402 */ 1403 1404 void 1405 astconflist(Sfio_t* sp, const char* path, int flags, const char* pattern) 1406 { 1407 char* s; 1408 char* f; 1409 char* call; 1410 Feature_t* fp; 1411 Lookup_t look; 1412 regex_t re; 1413 regdisc_t redisc; 1414 int olderrno; 1415 char flg[8]; 1416 #ifdef _pth_getconf_a 1417 Proc_t* proc; 1418 Sfio_t* pp; 1419 #endif 1420 1421 INITIALIZE(); 1422 if (!path) 1423 path = root; 1424 else if (access(path, F_OK)) 1425 { 1426 errorf(&state, &state, 2, "%s: not found", path); 1427 return; 1428 } 1429 olderrno = errno; 1430 look.flags = 0; 1431 if (!(flags & (ASTCONF_read|ASTCONF_write|ASTCONF_parse))) 1432 flags |= ASTCONF_read|ASTCONF_write; 1433 else if (flags & ASTCONF_parse) 1434 flags |= ASTCONF_write; 1435 if (!(flags & (ASTCONF_matchcall|ASTCONF_matchname|ASTCONF_matchstandard))) 1436 pattern = 0; 1437 if (pattern) 1438 { 1439 memset(&redisc, 0, sizeof(redisc)); 1440 redisc.re_version = REG_VERSION; 1441 redisc.re_errorf = (regerror_t)errorf; 1442 re.re_disc = &redisc; 1443 if (regcomp(&re, pattern, REG_DISCIPLINE|REG_EXTENDED|REG_LENIENT|REG_NULL)) 1444 return; 1445 } 1446 if (flags & ASTCONF_read) 1447 { 1448 for (look.conf = (Conf_t*)conf; look.conf < (Conf_t*)&conf[conf_elements]; look.conf++) 1449 { 1450 if (pattern) 1451 { 1452 if (flags & ASTCONF_matchcall) 1453 { 1454 if (regexec(&re, prefix[look.conf->call + CONF_call].name, 0, NiL, 0)) 1455 continue; 1456 } 1457 else if (flags & ASTCONF_matchname) 1458 { 1459 if (regexec(&re, look.conf->name, 0, NiL, 0)) 1460 continue; 1461 } 1462 else if (flags & ASTCONF_matchstandard) 1463 { 1464 if (regexec(&re, prefix[look.conf->standard].name, 0, NiL, 0)) 1465 continue; 1466 } 1467 } 1468 print(sp, &look, NiL, path, flags, errorf); 1469 } 1470 #ifdef _pth_getconf_a 1471 if (pp = nativeconf(&proc, _pth_getconf_a)) 1472 { 1473 call = "GC"; 1474 while (f = sfgetr(pp, '\n', 1)) 1475 { 1476 for (s = f; *s && *s != '=' && *s != ':' && !isspace(*s); s++); 1477 if (*s) 1478 for (*s++ = 0; isspace(*s); s++); 1479 if (!lookup(&look, f, flags)) 1480 { 1481 if (flags & ASTCONF_table) 1482 { 1483 if (look.standard < 0) 1484 look.standard = 0; 1485 if (look.section < 1) 1486 look.section = 1; 1487 sfprintf(sp, "%*s %*s %d %2s %4d %5s %s\n", sizeof(conf[0].name), f, sizeof(prefix[look.standard].name), prefix[look.standard].name, look.section, call, 0, "N", s); 1488 } 1489 else if (flags & ASTCONF_parse) 1490 sfprintf(sp, "%s %s - %s\n", state.id, f, s); 1491 else 1492 sfprintf(sp, "%s=%s\n", f, (flags & ASTCONF_quote) ? fmtquote(s, "\"", "\"", strlen(s), FMT_SHELL) : s); 1493 } 1494 } 1495 sfclose(pp); 1496 procclose(proc); 1497 } 1498 #endif 1499 } 1500 if (flags & ASTCONF_write) 1501 { 1502 call = "AC"; 1503 for (fp = state.features; fp; fp = fp->next) 1504 { 1505 if (pattern) 1506 { 1507 if (flags & ASTCONF_matchcall) 1508 { 1509 if (regexec(&re, call, 0, NiL, 0)) 1510 continue; 1511 } 1512 else if (flags & ASTCONF_matchname) 1513 { 1514 if (regexec(&re, fp->name, 0, NiL, 0)) 1515 continue; 1516 } 1517 else if (flags & ASTCONF_matchstandard) 1518 { 1519 if (regexec(&re, prefix[fp->standard].name, 0, NiL, 0)) 1520 continue; 1521 } 1522 } 1523 if (!(s = feature(fp->name, path, NiL, 0, 0)) || !*s) 1524 s = "0"; 1525 if (flags & ASTCONF_table) 1526 { 1527 f = flg; 1528 if (fp->flags & CONF_ALLOC) 1529 *f++ = 'A'; 1530 if (fp->flags & CONF_READONLY) 1531 *f++ = 'R'; 1532 if (f == flg) 1533 *f++ = 'X'; 1534 *f = 0; 1535 sfprintf(sp, "%*s %*s %d %2s %4d %5s %s\n", sizeof(conf[0].name), fp->name, sizeof(prefix[fp->standard].name), prefix[fp->standard].name, 1, call, 0, flg, s); 1536 } 1537 else if (flags & ASTCONF_parse) 1538 sfprintf(sp, "%s %s - %s\n", state.id, (flags & ASTCONF_lower) ? fmtlower(fp->name) : fp->name, fmtquote(s, "\"", "\"", strlen(s), FMT_SHELL)); 1539 else 1540 sfprintf(sp, "%s=%s\n", (flags & ASTCONF_lower) ? fmtlower(fp->name) : fp->name, (flags & ASTCONF_quote) ? fmtquote(s, "\"", "\"", strlen(s), FMT_SHELL) : s); 1541 } 1542 } 1543 if (pattern) 1544 regfree(&re); 1545 errno = olderrno; 1546 } 1547