|
|
1.1 ! root 1: # include <stdio.h> ! 2: # include <ingres.h> ! 3: # include <aux.h> ! 4: # include <version.h> ! 5: # include <opsys.h> ! 6: # include <access.h> ! 7: # include <lock.h> ! 8: # include <signal.h> ! 9: # include <sccs.h> ! 10: # include <setjmp.h> ! 11: # include <pwd.h> ! 12: ! 13: SCCSID(@(#)initucode.c 8.5 2/8/85) ! 14: ! 15: /* ! 16: ** INITUCODE -- initialize standalone process ! 17: ** ! 18: ** This function initializes a standalone process, initializing ! 19: ** a lot of global variables, scanning the argument vector for ! 20: ** some special flags (-u and +-w), seperating flags and ! 21: ** parameters, and so forth. ! 22: ** ! 23: ** Every standalone program should begin with the lines: ! 24: ** i = initucode(argc, argv, ...); ! 25: ** switch (i) ! 26: ** ... ! 27: ** ! 28: ** On a return of 2, 3, or 4, essentially none of the processing ! 29: ** is done (particularly true with return 4). Virtually nothing ! 30: ** can be done in the calling program except print a "usage" ! 31: ** message and exit. The exception to this is that 'Pathname' ! 32: ** is set, so that it can be used in the error printing. For ! 33: ** example, ingres.c cats file .../files/usage on this sort of ! 34: ** error. ! 35: ** ! 36: ** If it is preferable to not lock the database at this time, ! 37: ** the 'waitmode' parameter should be passed as -1. This still ! 38: ** causes the 'Wait_action' variable to be initialized, but the ! 39: ** database is not actually locked. It can be locked by calling: ! 40: ** db_lock(Dbpath, M_EXCL); ! 41: ** at the proper time. ! 42: ** ! 43: ** For the main effects of this routine, see the "Side Effects" ! 44: ** section below. ! 45: ** ! 46: ** Parameters: ! 47: ** argc -- argc from main. ! 48: ** argv -- argv from main. ! 49: ** dbflag -- TRUE -- take the first parameter as the ! 50: ** database name. ! 51: ** FALSE -- don't take the first parameter as ! 52: ** the database name. ! 53: ** paramlist -- a pointer to an array[4] of pointers ! 54: ** to character; set to the extra fields of ! 55: ** the users file entry for the real user ! 56: ** executing the code (not the user on the ! 57: ** -u flag). If NULL, this is ignored. ! 58: ** waitmode -- M_EXCL -- set an exclusive lock on the ! 59: ** database. ! 60: ** M_SHARE -- set a shared lock on the database. ! 61: ** -1 -- don't set a lock on the database. ! 62: ** However, other stuff (Wait_action) is ! 63: ** still set up so that the lock can be ! 64: ** placed later by calling 'db_lock'. ! 65: ** ! 66: ** Returns: ! 67: ** 0 -- everything is ok. ! 68: ** 1(NODB) -- the database does not exist. ! 69: ** 2(NOACCESS)-- you are not authorized to access this database. ! 70: ** 3(INVALIDUSR)-- you are not a valid INGRES user. ! 71: ** 4(NODBNAME)-- no database name was specified (only if dbflag ! 72: ** == TRUE). ! 73: ** 5(INDIRECT)-- everything is ok, but there was an indirect ! 74: ** taken. ! 75: ** 6(INDNODB)-- there was an indirect taken, but there was no ! 76: ** database there. ! 77: ** ! 78: ** If dbflag == FALSE, you can only get returns 0 and ! 79: ** 3. ! 80: ** ! 81: ** Side Effects: ! 82: ** A lot of variables are set, as follows: ! 83: ** ! 84: ** Dbpath -- set to the pathname of the database (only ! 85: ** if dbflag == TRUE). It is set even if the ! 86: ** database does not exist. ! 87: ** Parmvect -- set to the parameters from argv, that is, ! 88: ** anything not beginning with '+' or '-'. ! 89: ** Flagvect -- set to the flags from argv, that is, ! 90: ** everything beginning with '+' or '-'. The ! 91: ** flags '+w', '-w', and '-u' are stripped out, ! 92: ** however. ! 93: ** Wait_action -- set to the appropriate action (A_SLP ! 94: ** or A_RTN) based on the +-w flags and whether ! 95: ** we are running in background or not. ! 96: ** This is automatically used by 'db_lock()'. ! 97: ** Usercode -- set to the persons effective user code ! 98: ** (that is, after the -u processing). Only ! 99: ** the INGRES user or the DBA can use the -u ! 100: ** flag. ! 101: ** Pathname -- set to the pathname of the INGRES subtree. ! 102: ** Status -- an integer set to the user status field ! 103: ** of the users file for the real user. ! 104: ** Ing_uid -- set to the user id of the INGRES user. ! 105: ** ! 106: ** The rubout signal (signal 2) is caught, and refered ! 107: ** to the standard rubout processor (see rub.c); thus, ! 108: ** a routine called 'rubproc' must be defined in the ! 109: ** standalone code (which will just call exit, in the ! 110: ** normal case). ! 111: ** ! 112: ** The 'adminhdr' part of the 'Admin' struct is filled ! 113: ** in. This is not done with readadmin() and is not ! 114: ** equivalent to an 'admininit()', but it does make ! 115: ** the DBA and database status available. ! 116: ** ! 117: ** This routine can also exit immediately with an ! 118: ** error message. ! 119: ** ! 120: ** Defined Constants: ! 121: ** MAXPARGS -- the maximum number of parameter type ! 122: ** arguments to any standalone program. ! 123: ** MAXFARGS -- the maximum number of flag type arg- ! 124: ** uments to any standalong program (not inclu- ! 125: ** ding flags in the users file, and the +-w ! 126: ** and -u flags). ! 127: ** ! 128: ** Files: ! 129: ** /etc/passwd -- to get the pathname for user "ingres". ! 130: ** .../files/users -- to get all the per-user information, ! 131: ** and to process the -u flag. ! 132: ** ! 133: ** Compilation Flags: ! 134: ** xB_UNIX, xV6_UNIX -- see comments in aux.h ! 135: ** ! 136: ** Trace Flags: ! 137: ** none ! 138: */ ! 139: ! 140: ! 141: # define MAXFARGS 15 /* maximum flag-type arguments */ ! 142: # define MAXPARGS 20 /* maximum parameter-type args */ ! 143: ! 144: char *Usercode; /* the usercode of the effective user */ ! 145: char *Pathname; /* path of INGRES subtree */ ! 146: int Status; /* the user status of the real user */ ! 147: int Rubignored; /* set if rubouts ignored */ ! 148: /* (also in initproc for system processes) */ ! 149: int Wait_action; /* the action on the db_lock */ ! 150: char *Dbpath; /* the pathname of the database */ ! 151: char *Flagvect[MAXFARGS+1]; /* the flags from argv */ ! 152: char *Parmvect[MAXPARGS+1]; /* the parameters from argv */ ! 153: int Ing_uid; /* the user id of the INGRES user */ ! 154: jmp_buf Initbuf; /* Buffer to go back to initucode with */ ! 155: ! 156: initucode(argc, argv, dbflag, paramlist, waitmode) ! 157: int argc; ! 158: char **argv; ! 159: int dbflag; ! 160: char *paramlist[4]; ! 161: int waitmode; ! 162: { ! 163: register char *p; ! 164: char *q; ! 165: char c; ! 166: FILE *iop; ! 167: static char sbuf[MAXLINE * 2]; ! 168: register char *sbufp; ! 169: char buf[MAXLINE+1]; ! 170: register int i; ! 171: int npermit; ! 172: int rtval; ! 173: char *field[UF_NFIELDS]; ! 174: int actualuid; ! 175: auto int uid; ! 176: auto int gid; ! 177: int waitflag; ! 178: char *userflag; ! 179: struct sgttyb gttydummy; ! 180: int fvi, pvi; ! 181: char **avp; ! 182: char usr_ovrd[3]; ! 183: extern rubcatch(); ! 184: static short tvect[100]; ! 185: bool nobuffer; ! 186: struct passwd *pwd; ! 187: struct passwd *getpwnam(); ! 188: # ifdef xV7_UNIX ! 189: extern char *getenv(); ! 190: # endif xV7_UNIX ! 191: ! 192: /* ! 193: ** Set up interrupts. ! 194: */ ! 195: ! 196: if ( setjmp(Initbuf) ) ! 197: exit(-1); ! 198: if (signal(SIGINT, SIG_IGN) == SIG_DFL) ! 199: signal(SIGINT, rubcatch); ! 200: # ifdef xV6_UNIX ! 201: for (avp = argv; *avp != 0 && *avp != (char *) -1; avp++) ! 202: continue; ! 203: *avp = NULL; ! 204: # endif ! 205: ! 206: /* ! 207: ** Do basic initialization, such as setting trace flags. ! 208: */ ! 209: ! 210: nobuffer = tTrace(argv, 'T', tvect, 100); ! 211: if (!nobuffer) ! 212: set_so_buf(); ! 213: sbufp = sbuf; ! 214: ! 215: /* ! 216: ** Get pathname of INGRES subtree from /etc/passwd file ! 217: ** entry for USERINGRES (presumably "ingres") and save it ! 218: ** in 'Pathname'. ! 219: ** ! 220: ** This algorithm suggested by Jim Popa. ! 221: */ ! 222: ! 223: if ( (pwd = getpwnam(USERINGRES)) == NULL ) ! 224: syserr("initucode: No user %s in password file",USERINGRES); ! 225: # ifdef xV7_UNIX ! 226: Pathname = getenv("INGPATH"); ! 227: if (Pathname == NULL) ! 228: { ! 229: # endif xV7_UNIX ! 230: Pathname = sbufp; ! 231: sbufp += smove(pwd->pw_dir, sbufp) + 1; ! 232: # ifdef PATHEXT ! 233: sbufp += smove(PATHEXT, sbufp - 1); ! 234: # endif PATHEXT ! 235: # ifdef xV7_UNIX ! 236: } ! 237: # endif xV7_UNIX ! 238: ! 239: /* create the INGRES user id */ ! 240: Ing_uid = pwd->pw_uid; ! 241: # ifdef xV6_UNIX ! 242: Ing_uid &= I1MASK; ! 243: # endif ! 244: # ifdef xB_UNIX ! 245: gid = pwd->pw_gid; ! 246: Ing_uid = (Ing_uid & I1MASK) | ((gid & I1MASK) << 8); ! 247: # endif ! 248: endpwent(); ! 249: ! 250: /* ! 251: ** Scan the argument vector. The following flags are pulled ! 252: ** out of the vector (and argc and argv are adjusted so it ! 253: ** looks like they never existed): ! 254: ** +w, -w -- (don't) wait for the database to be free. ! 255: ** -uxxx -- run as user xxx. If first character is a ! 256: ** colon, the format must be '-u:xx' where 'xx' is the ! 257: ** internal user code. ! 258: */ ! 259: ! 260: avp = argv; ! 261: fvi = 0; ! 262: pvi = 0; ! 263: waitflag = 0; ! 264: userflag = NULL; ! 265: usr_ovrd[0] = 0; ! 266: ! 267: for (i = argc; --i > 0; ) ! 268: { ! 269: p = *++avp; ! 270: if (p[0] == '+') ! 271: { ! 272: if (p[1] == 'w') ! 273: waitflag = 1; ! 274: else ! 275: goto boring; ! 276: } ! 277: else if (p[0] == '-') ! 278: { ! 279: switch (p[1]) ! 280: { ! 281: case 'w': ! 282: waitflag = -1; ! 283: break; ! 284: ! 285: case 'u': ! 286: if (p[2] == ':') ! 287: { ! 288: if (p[3] == 0 || p[4] == 0 || p[5] != 0) ! 289: { ! 290: printf("Bad flag %s\n", p); ! 291: exit(-1); ! 292: } ! 293: smove(&p[3], usr_ovrd); ! 294: } ! 295: else ! 296: userflag = &p[2]; ! 297: break; ! 298: ! 299: default: ! 300: /* not an interesting flag */ ! 301: boring: ! 302: if (fvi >= MAXFARGS) ! 303: { ! 304: printf("Too many flags\n"); ! 305: exit(-1); ! 306: } ! 307: Flagvect[fvi++] = p; ! 308: break; ! 309: } ! 310: } ! 311: else ! 312: { ! 313: /* not a flag: save in Parmvect */ ! 314: if (pvi >= MAXPARGS) ! 315: { ! 316: printf("Too many parmameters\n"); ! 317: exit(-1); ! 318: } ! 319: Parmvect[pvi++] = p; ! 320: } ! 321: } ! 322: ! 323: if (pvi <= 0 && dbflag) ! 324: { ! 325: return (NODBNAME); /* no database name specified */ ! 326: } ! 327: ! 328: /* ! 329: ** Scan the "users" file. ! 330: */ ! 331: ! 332: if ((iop = fopen(ztack(Pathname, "/files/users"), "r")) == NULL) ! 333: syserr("initucode: open error"); ! 334: ! 335: /* get uid (out of loop) for test */ ! 336: # ifdef xV6_UNIX ! 337: actualuid = getuid() & I1MASK; ! 338: # endif ! 339: # ifndef xV6_UNIX ! 340: actualuid = getuid(); ! 341: # endif ! 342: ! 343: /* scan users file, one line at a time */ ! 344: rtval = INVALIDUSR; ! 345: while ((Usercode == NULL || userflag != NULL) && fgets(buf, MAXLINE, iop) != NULL) ! 346: { ! 347: ! 348: /* decode users file entry */ ! 349: i = 0; ! 350: field[0] = buf; ! 351: for (p = buf; *p != '\n' && *p != '\0'; p++) ! 352: { ! 353: if (*p == ':') ! 354: { ! 355: *p = 0; ! 356: i++; ! 357: field[i] = p + 1; ! 358: } ! 359: } ! 360: *p = '\0'; ! 361: ! 362: /* check for correct number of fields */ ! 363: if (i != UF_NFIELDS - 1) ! 364: syserr("initucode: users fmt %s", buf); ! 365: ! 366: /* ! 367: ** Check to see if this entry is the override user. ! 368: ** If so, save his user code in usr_ovrd. ! 369: */ ! 370: ! 371: if (userflag != NULL && sequal(userflag, field[UF_NAME])) ! 372: { ! 373: smove(field[UF_UCODE], usr_ovrd); ! 374: userflag = NULL; ! 375: } ! 376: ! 377: /* don't bother with this shit if not needed */ ! 378: if (Usercode != NULL) ! 379: continue; ! 380: ! 381: /* ! 382: ** Build the user id of this entry into 'uid' ! 383: ** and see if it is this user. ! 384: */ ! 385: ! 386: uid = atoi(field[UF_UID]); ! 387: ! 388: # ifdef xB_UNIX ! 389: gid = atoi(field[UF_GID]); ! 390: uid = (uid & I1MASK) | ((gid & I1MASK) << 8); ! 391: # endif ! 392: ! 393: # ifdef xV6_UNIX ! 394: if ((uid & I1MASK) != actualuid) ! 395: continue; ! 396: # endif ! 397: # ifndef xV6_UNIX ! 398: if (uid != actualuid) ! 399: continue; ! 400: # endif ! 401: ! 402: /* ! 403: ** We now have the real user entry. ! 404: ** Fetch the usercode, the status bits, and other ! 405: ** fields from the users file, and save them in ! 406: ** a safe place (sbuf). ! 407: */ ! 408: ! 409: Usercode = sbufp; ! 410: sbufp += smove(field[UF_UCODE], sbufp) + 1; ! 411: Status = oatoi(field[UF_STAT]); ! 412: if (paramlist != NULL) ! 413: { ! 414: for (i = 0; i < 4; i++) ! 415: { ! 416: paramlist[i] = sbufp; ! 417: sbufp += smove(field[UF_FLAGS + i], sbufp) + 1; ! 418: } ! 419: } ! 420: ! 421: /* validate access permission */ ! 422: rtval = 0; ! 423: if (!dbflag || (Status & U_SUPER) != 0) ! 424: continue; ! 425: p = field[UF_DBLIST]; ! 426: if (*p == 0) ! 427: continue; ! 428: ! 429: /* select permission/no-permission */ ! 430: npermit = 0; ! 431: if (*p == '-') ! 432: { ! 433: p++; ! 434: npermit++; ! 435: } ! 436: ! 437: /* scan for database listed */ ! 438: if (!npermit) ! 439: rtval = NOACCESS; ! 440: for (c = *p; c != 0; p = q + 1) ! 441: { ! 442: for (q = p; *q != ',' && *q != 0; q++) ! 443: continue; ! 444: c = *q; ! 445: *q = 0; ! 446: if (sequal(Parmvect[0], p)) ! 447: { ! 448: rtval = npermit ? NOACCESS : 0; ! 449: break; ! 450: } ! 451: } ! 452: } ! 453: fclose(iop); ! 454: ! 455: if (rtval != 0) ! 456: return (rtval); ! 457: ! 458: /* ! 459: ** Check for existance of the database. This is done by ! 460: ** first building the pathname of the database into ! 461: ** 'Dbpath', and then reading the admin file (just ! 462: ** the adhdr part). ! 463: */ ! 464: ! 465: if (dbflag) ! 466: { ! 467: Dbpath = sbufp; ! 468: switch (i = initdbpath(Parmvect[0], Dbpath, TRUE)) ! 469: { ! 470: case DBEXIST: ! 471: rtval = 0; ! 472: break; ! 473: ! 474: case PTR2DB: ! 475: rtval = INDIRECT; ! 476: break; ! 477: ! 478: case NODBS: ! 479: rtval = NODB; ! 480: break; ! 481: ! 482: case PTR2NODBS: ! 483: rtval = INDNODB; ! 484: break; ! 485: ! 486: default: ! 487: syserr("initucode: initdbpath %d", i); ! 488: } ! 489: sbufp += length(Dbpath) + 1; ! 490: ! 491: if (rtval == 0 || rtval == INDIRECT) ! 492: { ! 493: i = open(ztack(Dbpath, "/admin"), O_RDONLY); ! 494: if (i < 0) ! 495: rtval += 1; ! 496: else ! 497: { ! 498: /* open and check admin file */ ! 499: checkadmin(i); ! 500: close(i); ! 501: } ! 502: } ! 503: } ! 504: ! 505: /* ! 506: ** Check to see if the name on the -u flag is valid, and ! 507: ** that this user is allowed to use it. ! 508: */ ! 509: ! 510: if (userflag != NULL) ! 511: { ! 512: printf("Invalid user name %s\n", userflag); ! 513: exit(-1); ! 514: } ! 515: if (usr_ovrd[0] != '\0') ! 516: { ! 517: if ((Status & U_SUPER) == 0) ! 518: { ! 519: if (!dbflag || !bequal(Admin.adhdr.adowner, Usercode, UCODE_SZ)) ! 520: { ! 521: printf("You may not use the -u flag\n"); ! 522: exit(-1); ! 523: } ! 524: } ! 525: bmove(usr_ovrd, Usercode, UCODE_SZ); ! 526: } ! 527: ! 528: /* ! 529: ** Process the +-w flag. ! 530: ** First, determine the locking mode. If +w, always ! 531: ** wait; if -w, never wait; if unspecified, wait if in ! 532: ** background, but print error and exit if running ! 533: ** interactive. ! 534: */ ! 535: ! 536: if (waitflag > 0 || (waitflag == 0 && gtty(0, >tydummy) < 0)) ! 537: Wait_action = A_SLP; ! 538: else ! 539: Wait_action = A_RTN; ! 540: if (dbflag && waitmode >= 0) ! 541: db_lock(waitmode); ! 542: ! 543: /* ! 544: ** Return authorization value. ! 545: */ ! 546: ! 547: return (rtval); ! 548: } ! 549: /* ! 550: ** DB_LOCK -- lock database ! 551: ** ! 552: ** Locks the database. Everyone should do this before using any ! 553: ** database. ! 554: ** ! 555: ** Parameters: ! 556: ** database -- the pathname of the database. ! 557: ** mode -- M_EXCL -- get an exclusive lock. ! 558: ** M_SHARE -- get a shared lock. ! 559: ** ! 560: ** Returns: ! 561: ** none ! 562: ** ! 563: ** Side Effects: ! 564: ** Alockdes is opened. ! 565: */ ! 566: ! 567: struct lockreq Lock; /* the database lock structure */ ! 568: ! 569: db_lock(mode) ! 570: int mode; ! 571: { ! 572: if ((Admin.adhdr.adflags & A_DBCONCUR) == 0) ! 573: return; ! 574: if (Alockdes < 0) ! 575: Alockdes = start_up_lock_driver(); ! 576: if (setdbl(Wait_action, mode) < 0) ! 577: { ! 578: printf("Database temporarily unavailable\n"); ! 579: exit(1); ! 580: } ! 581: } ! 582: /* ! 583: ** INITDBPATH -- initialize the pathname of the database ! 584: ** ! 585: ** The pathname of a specified database is created. Indirection ! 586: ** via a file is supported, so that if the pathname is a file, ! 587: ** the first line of the file is read and used as the pathname ! 588: ** of the real database. ! 589: ** ! 590: ** Parameters: ! 591: ** database -- the name of the database. If NULL, ! 592: ** the pathname of datadir is returned. ! 593: ** dbbuf -- a buffer into which the pathname should ! 594: ** be dumped. ! 595: ** follow -- if set, follow the indirect chain of ! 596: ** database pathnames. ! 597: ** ! 598: ** Returns: ! 599: ** 0(DBEXIST)-- database exists in datadir ! 600: ** 1(PTR2DB)-- database exists, but I followed a pointer. ! 601: ** 2(NODBS)-- database doesn't exist in datadir. ! 602: ** 3(PRT2NODBS)-- database doesn't exist, but I followed a pointer. ! 603: ** ! 604: ** Side Effects: ! 605: ** none. ! 606: */ ! 607: ! 608: initdbpath(database, dbpath, follow) ! 609: char *database; ! 610: char *dbpath; ! 611: int follow; ! 612: { ! 613: struct stat ibuf; ! 614: register char *d; ! 615: register FILE *f; ! 616: register int phase; ! 617: int retval; ! 618: int uid; ! 619: extern char *index(); ! 620: ! 621: d = dbpath; ! 622: ! 623: if (database == NULL) ! 624: { ! 625: # ifndef xDBPATH ! 626: concat(Pathname, "/data/base/", d); ! 627: # else ! 628: smove(xDBPATH, d); ! 629: # endif ! 630: return (DBEXIST); ! 631: } ! 632: ! 633: /* get the basic pathname */ ! 634: concat(ztack(Pathname, "/datadir/"), database, d); ! 635: ! 636: /* ! 637: ** Iterate looking for database. ! 638: ** "Phase" is what we are trying: ! 639: ** -1 -- looking in datadir ! 640: ** 0 -- looking in data/base ! 641: ** 1 -- following indirect. ! 642: */ ! 643: ! 644: retval = NODBS; ! 645: for (phase = -1;;) ! 646: { ! 647: /* find out what sort of filesystem node this is */ ! 648: if (stat(d, &ibuf) < 0) ! 649: { ! 650: if (phase < 0) ! 651: { ! 652: # ifdef xDBPATH ! 653: concat(xDBPATH, database, d); ! 654: # else ! 655: concat(ztack(Pathname, "/data/base/"), database, d); ! 656: # endif ! 657: phase = 0; ! 658: continue; ! 659: } ! 660: else ! 661: return (retval); ! 662: } ! 663: ! 664: /* set up the lock structure for future use */ ! 665: bmove(&ibuf, Lock.dbnode, 4); ! 666: ! 667: retval -= 2; ! 668: if ((ibuf.st_mode & S_IFMT) == S_IFDIR) ! 669: return (retval); ! 670: ! 671: /* if second time through, the database must be a directory */ ! 672: if (phase > 0) ! 673: syserr("initdbpath: not direc"); ! 674: ! 675: /* if we shouldn't follow the chain, say it exists */ ! 676: if (!follow) ! 677: return (PTR2NODBS); ! 678: ! 679: /* it's a file -- see if we can use it */ ! 680: uid = ibuf.st_uid; ! 681: # ifdef xB_UNIX ! 682: uid = (uid & I1MASK) | ((ibuf.st_gid & I1MASK) << 8); ! 683: # endif ! 684: # ifdef xV6_UNIX ! 685: uid &= I1MASK; ! 686: # endif ! 687: if (uid != Ing_uid || (ibuf.st_mode & 0777) != 0600) ! 688: return (PTR2NODBS); ! 689: ! 690: f = fopen(d, "r"); ! 691: if (f == NULL) ! 692: syserr("initdbpath: fopen"); ! 693: ! 694: /* read the pathname of the database */ ! 695: if (fgets(d, MAXLINE, f) == NULL || d[0] != '/') ! 696: syserr("initdbpath: bad indirect"); ! 697: *index(d, '\n') = '\0'; ! 698: fclose(f); ! 699: ! 700: /* prepare for next iteration */ ! 701: retval = 3; ! 702: phase = 1; ! 703: } ! 704: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.