|
|
1.1 ! root 1: /* ! 2: * Copyright (c) 1999 Apple Computer, Inc. All rights reserved. ! 3: * ! 4: * @APPLE_LICENSE_HEADER_START@ ! 5: * ! 6: * "Portions Copyright (c) 1999 Apple Computer, Inc. All Rights ! 7: * Reserved. This file contains Original Code and/or Modifications of ! 8: * Original Code as defined in and that are subject to the Apple Public ! 9: * Source License Version 1.0 (the 'License'). You may not use this file ! 10: * except in compliance with the License. Please obtain a copy of the ! 11: * License at http://www.apple.com/publicsource and read it before using ! 12: * this file. ! 13: * ! 14: * The Original Code and all software distributed under the License are ! 15: * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER ! 16: * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, ! 17: * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, ! 18: * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the ! 19: * License for the specific language governing rights and limitations ! 20: * under the License." ! 21: * ! 22: * @APPLE_LICENSE_HEADER_END@ ! 23: */ ! 24: /* NXBundle.m ! 25: Copyright 1990, 1991, NeXT, Inc. ! 26: IPC, November 1990 ! 27: */ ! 28: ! 29: #ifndef KERNEL ! 30: #ifdef SHLIB ! 31: #import "shlib.h" ! 32: #endif SHLIB ! 33: ! 34: ! 35: #import "NXBundle.h" ! 36: #import "NXBundlePrivate.h" ! 37: #import "maptable.h" ! 38: #import "NXStringTable.h" ! 39: #import "objc-load.h" ! 40: #import "hashtable.h" ! 41: #import "objc-private.h" ! 42: ! 43: #import <sys/file.h> ! 44: #import <sys/dir.h> ! 45: #import <sys/stat.h> ! 46: #import <sys/types.h> ! 47: ! 48: #import <strings.h> ! 49: ! 50: static void warning(const char *format, ...) {} ! 51: ! 52: /* For the LProj cache */ ! 53: static id bundleTable = nil; ! 54: static id mainBundleStringTableTable = nil; ! 55: ! 56: #define BUNDLE_SUFFIX "bundle" ! 57: ! 58: /******* Bundle ********/ ! 59: ! 60: @implementation NXBundle ! 61: ! 62: static NXMapTable *classToBundle = NULL; ! 63: ! 64: static NXBundle *mainBundle = nil; ! 65: static NXMapTable *pathToBundle = NULL; ! 66: ! 67: - initForDirectory:(const char *)bp { ! 68: struct stat statInfo; ! 69: id bb; ! 70: ! 71: if (! pathToBundle) pathToBundle = NXCreateMapTable(NXStrValueMapPrototype, 1); ! 72: bb = NXMapGet(pathToBundle, bp); ! 73: if (bb) { ! 74: [self free]; ! 75: return bb; ! 76: } else { ! 77: BOOL allOK = YES; ! 78: ! 79: if (stat(bp, &statInfo) == 0) { ! 80: while (allOK && (statInfo.st_mode & S_IFLNK)) { ! 81: char linkPath[MAXPATHLEN + 1]; ! 82: ! 83: allOK = ((readlink(bp, linkPath, sizeof(linkPath)) != -1) ! 84: && (stat(linkPath, &statInfo) == 0)); ! 85: } ! 86: allOK = allOK ! 87: && ((statInfo.st_mode & (S_IFDIR | S_IREAD | S_IEXEC)) ! 88: ? YES : NO); ! 89: } ! 90: if (allOK) { ! 91: _directory = NXCopyStringBuffer(bp); ! 92: _codeLoaded = NO; ! 93: NXMapInsert(pathToBundle, _directory, self); ! 94: return self; ! 95: } else { ! 96: [self free]; ! 97: return nil; ! 98: } ! 99: } ! 100: } ! 101: ! 102: - free ! 103: { ! 104: if (_codeLoaded) ! 105: return self; ! 106: else { ! 107: NXMapRemove(pathToBundle, _directory); ! 108: free(_directory); ! 109: return [super free]; ! 110: } ! 111: } ! 112: ! 113: + mainBundle { ! 114: if (! mainBundle) { ! 115: char prog[MAXPATHLEN]; ! 116: char *last; ! 117: char **argv; ! 118: #if defined(__DYNAMIC__) ! 119: extern char ***_NSGetArgv(); ! 120: argv = *_NSGetArgv(); ! 121: #else ! 122: extern char **NXArgv; ! 123: argv = NXArgv; ! 124: #endif ! 125: if (argv[0][0] != '/') { getwd(prog); strcat(prog, "/"); } ! 126: else prog[0] = 0; ! 127: strcat(prog, argv[0]); ! 128: last = rindex(prog, '/'); ! 129: last[0] = 0; ! 130: mainBundle = [[self alloc] initForDirectory: prog]; ! 131: mainBundle->_codeLoaded = YES; ! 132: } ! 133: return mainBundle; ! 134: } ! 135: ! 136: + bundleForClass:class { ! 137: id bundle; ! 138: if (! classToBundle) return [self mainBundle]; ! 139: bundle = NXMapGet(classToBundle, class); ! 140: return (bundle) ? bundle : [self mainBundle]; ! 141: } ! 142: ! 143: - (const char *)directory { ! 144: return _directory; ! 145: } ! 146: ! 147: static Class globalPrincipalClass = NULL; ! 148: static id globalBundle = nil; ! 149: ! 150: static void loadCallback(Class cls, Category cat ) { ! 151: warning("Loading %s '%s'\n", (cat) ? "category of" : "class", cls->name); ! 152: if (cat) return; ! 153: if (! classToBundle) classToBundle = NXCreateMapTable(NXPtrValueMapPrototype, 1); ! 154: NXMapInsert(classToBundle, cls, globalBundle); ! 155: if (! globalPrincipalClass) globalPrincipalClass = cls; ! 156: } ! 157: ! 158: static NXStream* bundleErrorStream = 0; ! 159: ! 160: + (NXStream*) setErrorStream:(NXStream*)stream ! 161: { ! 162: NXStream* s = bundleErrorStream; ! 163: bundleErrorStream = stream; ! 164: return s; ! 165: } ! 166: ! 167: - (BOOL)ensureLoaded { ! 168: if (! _codeLoaded) { ! 169: char name[MAXPATHLEN]; ! 170: char path[MAXPATHLEN]; ! 171: char *moduleList[2] = {path, NULL}; ! 172: NXStream *errorStream; ! 173: long err; ! 174: char *slash, *dot; ! 175: ! 176: globalPrincipalClass = NULL; ! 177: globalBundle = self; ! 178: slash = rindex(_directory, '/'); ! 179: if (slash) { ! 180: strcpy(name, slash + 1); ! 181: } else { ! 182: strcpy(name, _directory); ! 183: } ! 184: dot = rindex(name, '.'); ! 185: if (dot) ! 186: *dot = '\0'; ! 187: if ([self getPath: path forResource: name ofType: NULL]) { ! 188: errorStream = NXOpenMemory(NULL, 0, NX_WRITEONLY); ! 189: ! 190: err = objc_loadModules(moduleList, errorStream, loadCallback, ! 191: NULL, NULL); ! 192: if (err) { ! 193: char *streamBuf; ! 194: int len, maxLen; ! 195: ! 196: NXGetMemoryBuffer(errorStream, &streamBuf, &len, &maxLen); ! 197: if (!bundleErrorStream) ! 198: { ! 199: _NXLogError("Error loading %s", path); ! 200: if (len) { ! 201: streamBuf[len] = 0; ! 202: _NXLogError(streamBuf); ! 203: } ! 204: _NXLogError("\n"); ! 205: } ! 206: else ! 207: { ! 208: NXPrintf (bundleErrorStream, "Error loading %s", path); ! 209: if (len) { ! 210: streamBuf[len] = 0; ! 211: NXPrintf (bundleErrorStream, streamBuf); ! 212: } ! 213: NXPrintf (bundleErrorStream, "\n"); ! 214: } ! 215: } else { ! 216: _principalClass = globalPrincipalClass; ! 217: _codeLoaded = YES; ! 218: } ! 219: NXCloseMemory(errorStream, NX_FREEBUFFER|NX_TRUNCATEBUFFER); ! 220: if (err) return NO; ! 221: } else { ! 222: if (! bundleErrorStream) ! 223: _NXLogError("Couldn't find %s in bundle for %s", name, _directory); ! 224: else ! 225: NXPrintf (bundleErrorStream, ! 226: "Couldn't find %s in bundle for %s", name, _directory); ! 227: return NO; ! 228: } ! 229: ! 230: } ! 231: return YES; ! 232: } ! 233: ! 234: - classNamed:(const char *)className { ! 235: if (![self ensureLoaded]) return nil; ! 236: return objc_getClass((char *)className); ! 237: } ! 238: ! 239: - principalClass { ! 240: if (![self ensureLoaded]) return nil; ! 241: return (id)_principalClass; ! 242: } ! 243: ! 244: - setVersion:(int)version { ! 245: _bundleVersion = version; ! 246: return self; ! 247: } ! 248: ! 249: - (int)version { ! 250: return _bundleVersion; ! 251: } ! 252: ! 253: /******* International Routines ********/ ! 254: ! 255: static BOOL systemHasLanguages = YES; ! 256: static const char *const *systemLanguages = NULL; ! 257: ! 258: static const char *const *getDefaultSystemLanguages(void) { ! 259: int count, length; ! 260: const char *language, *separator; ! 261: char **defaultSystemLanguages = NULL; ! 262: ! 263: if (systemHasLanguages) { ! 264: language = "English"; ! 265: if (language && *language) { ! 266: count = 1; ! 267: separator = strchr(language, ';'); ! 268: while (separator) { ! 269: count++; ! 270: separator = strchr(separator+1, ';'); ! 271: } ! 272: defaultSystemLanguages = malloc((count + 1) * sizeof(char *)); ! 273: defaultSystemLanguages[count] = NULL; ! 274: for (count = 0; language; count++) { ! 275: separator = strchr(language, ';'); ! 276: if (separator) { ! 277: length = separator - language; ! 278: separator++; ! 279: } else { ! 280: length = strlen(language); ! 281: } ! 282: while (*language == ' ' || *language == '\t') { ! 283: language++; length--; ! 284: } ! 285: while (language[length-1] == ' ' || language[length-1] == '\t') length--; ! 286: defaultSystemLanguages[count] = malloc((length + 1) * sizeof(char)); ! 287: strncpy(defaultSystemLanguages[count], language, length); ! 288: defaultSystemLanguages[count][length] = '\0'; ! 289: language = separator; ! 290: } ! 291: } else { ! 292: systemHasLanguages = NO; ! 293: } ! 294: } ! 295: ! 296: return defaultSystemLanguages; ! 297: } ! 298: ! 299: #ifdef DEBUG ! 300: static int ACCESS(const char *path, int way) ! 301: { ! 302: int retval = access(path, way); ! 303: printf("access(%s, R_OK) returns %d\n", path, retval); ! 304: return retval; ! 305: } ! 306: #else ! 307: #define ACCESS access ! 308: #endif ! 309: ! 310: #define LINE_BUFFER_SIZE 256 ! 311: ! 312: /* The passed-in path is the .lproj directory to look in for the version. It's not clear what the best way to encode a version into an .lproj directory, but this is probably as good as any...it parses through a file named "version" (if found) looking for an integer value starting a line. If it finds one, that's the version. If no version file exists in the .lproj directory, then its version is 0. */ ! 313: ! 314: static int readLprojVersion(char *path) { ! 315: FILE *file; ! 316: int version = 0; ! 317: int pathLength = strlen(path); ! 318: ! 319: strcat(path, "/version"); ! 320: file = fopen(path, "r"); ! 321: if (file) { ! 322: char *eof; ! 323: char lineBuffer[LINE_BUFFER_SIZE+1]; ! 324: *lineBuffer = '\0'; ! 325: do { ! 326: eof = fgets(lineBuffer, LINE_BUFFER_SIZE, file); ! 327: version = atoi(lineBuffer); ! 328: } while (!eof && !version); ! 329: } ! 330: path[pathLength] = '\0'; ! 331: fclose(file); ! 332: ! 333: return version; ! 334: } ! 335: ! 336: /* Adds the passed extension to the passed name iff the extension is not already on that name. */ ! 337: ! 338: static void addExtension(char *name, const char *extension) ! 339: { ! 340: if (extension) { ! 341: const char *existingExtension = strrchr(name, '.'); ! 342: if (existingExtension && extension[0] != '.') existingExtension++; ! 343: if (!existingExtension || strcmp(existingExtension, extension)) { ! 344: if (extension[0] != '.' && extension[0]) strcat(name, "."); ! 345: strcat(name, extension); ! 346: } ! 347: } ! 348: } ! 349: ! 350: /* This function takes a string and does a readdir() on the passed directory and adds the names of all the files in the directory to that string. Returns a potentially newly realloc()'ed version of the string. */ ! 351: ! 352: static char *addDirectoryEntriesTo(char *files, const char *directory) ! 353: { ! 354: DIR *dirp; ! 355: struct direct *dp; ! 356: int length; ! 357: ! 358: length = strlen(files); ! 359: if ((dirp = opendir(directory))) { ! 360: dp = readdir(dirp); /* Skip . */ ! 361: dp = readdir(dirp); /* Skip .. */ ! 362: for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) { ! 363: files = realloc(files, length + dp->d_namlen + 2); ! 364: strncpy(files + length, dp->d_name, dp->d_namlen); ! 365: length += dp->d_namlen; ! 366: files[length++] = '/'; ! 367: } ! 368: files[length] = '\0'; ! 369: closedir(dirp); ! 370: } ! 371: ! 372: return files; ! 373: } ! 374: ! 375: /* There's a little bit of an efficiency hack in this function. The NXMapTable lprojTable is a map table with keys and values both null-terminated strings. The keys are the path up to and including the .lproj directory. The value is of the form "version/file1/file2/file3/file4". atoi() is used to suck the version out of the front. */ ! 376: ! 377: static BOOL isFileInValidLproj(char *path, const char *name, const char *extension, int bundleVersion) ! 378: { ! 379: char *lprojInfo, *slash; ! 380: int length, version = -1; ! 381: char buffer[MAXPATHLEN+1]; ! 382: static NXMapTable *lprojTable = NULL; ! 383: ! 384: if (!lprojTable) lprojTable = NXCreateMapTable(NXStrValueMapPrototype, 0); ! 385: ! 386: lprojInfo = NXMapGet(lprojTable, path); ! 387: ! 388: if (!lprojInfo) { ! 389: if (ACCESS(path, R_OK) >= 0) version = readLprojVersion(path); ! 390: sprintf(buffer, "%d/", version); ! 391: lprojInfo = NXCopyStringBuffer(buffer); ! 392: lprojInfo = addDirectoryEntriesTo(lprojInfo, path); ! 393: NXMapInsert(lprojTable, NXCopyStringBuffer(path), lprojInfo); ! 394: } else { ! 395: version = atoi(lprojInfo); ! 396: } ! 397: ! 398: if (version == bundleVersion && (lprojInfo = strchr(lprojInfo, '/'))) { ! 399: strcpy(buffer, name); ! 400: slash = buffer; ! 401: while (*slash == '/') slash++; ! 402: if ((slash = strchr(buffer, '/'))) { ! 403: *(slash+1) = '\0'; ! 404: } else { ! 405: addExtension(buffer, extension); ! 406: strcat(buffer, "/"); ! 407: } ! 408: length = strlen(buffer); ! 409: while (*++lprojInfo) { ! 410: if (!strncmp(lprojInfo, buffer, length)) { ! 411: if (path[strlen(path)-1] != '/') strcat(path, "/"); ! 412: strcat(path, name); ! 413: addExtension(path, extension); ! 414: return slash ? (ACCESS(path, R_OK) >= 0) : YES; ! 415: } ! 416: lprojInfo = strchr(lprojInfo, '/'); ! 417: if (!lprojInfo) return NO; /* should never happen */ ! 418: } ! 419: } ! 420: ! 421: return NO; ! 422: } ! 423: ! 424: /* Returns a path to the appropriate shadow .lproj in /NextLibrary/Languages. */ ! 425: ! 426: static BOOL getPathToNextLibraryLanguages(char *path, const char *realPath, const char *language) ! 427: { ! 428: const char *s, *t; ! 429: char scratch[ MAXPATHLEN+1 ]; ! 430: ! 431: s = strchr(realPath, '.'); ! 432: while (s) { ! 433: if (!strncmp(s, ".app", 4)) { ! 434: char *p; ! 435: strcpy(scratch, realPath); ! 436: p = scratch + (s - realPath); ! 437: t = p+4; ! 438: if (*t == '/') t++; ! 439: *p = '\0'; ! 440: s = strrchr(scratch, '/'); ! 441: if (s) { ! 442: if (*t) { ! 443: sprintf(path, "/NextLibrary/Languages/%s%s.lproj/%s", language, s, t); ! 444: } else { ! 445: sprintf(path, "/NextLibrary/Languages/%s%s.lproj", language, s); ! 446: } ! 447: } ! 448: return YES; ! 449: } else { ! 450: s = strchr(s+1, '.'); ! 451: } ! 452: } ! 453: if (!s && !strncmp(realPath, "/usr/lib/", 9)) { ! 454: s = realPath+8; ! 455: while (*s == '/') s++; ! 456: if (*s) { ! 457: s = strchr(s, '/'); ! 458: if (s) { ! 459: char *tmp; ! 460: ! 461: strcpy(scratch, realPath); ! 462: tmp = scratch + (s - realPath); ! 463: *tmp++ = '\0'; ! 464: s = tmp; ! 465: } ! 466: if (s && *s) { ! 467: sprintf(path, "/NextLibrary/Languages/%s%s.lproj/%s", language, scratch+8, s); ! 468: } else { ! 469: sprintf(path, "/NextLibrary/Languages/%s%s.lproj", language, realPath+8); ! 470: } ! 471: return YES; ! 472: } ! 473: } ! 474: ! 475: return NO; ! 476: } ! 477: ! 478: + (BOOL)getPath:(char *)path forResource:(const char *)name ! 479: ofType:(const char *)ext inDirectory: (const char *)bundlePath ! 480: withVersion: (int)version ! 481: { ! 482: const char *const *cur; ! 483: char searchPath[MAXPATHLEN+1]; ! 484: ! 485: if (!name) return NO; ! 486: if (!path) path = searchPath; ! 487: ! 488: if (!systemLanguages) systemLanguages = getDefaultSystemLanguages(); ! 489: ! 490: if (systemLanguages) { ! 491: for (cur = systemLanguages; *cur; cur++) { ! 492: /* First look for a language in the bundle. */ ! 493: if (bundlePath) { ! 494: sprintf(path, "%s/%s.lproj", bundlePath, *cur); ! 495: if (isFileInValidLproj(path, name, ext, version)) return YES; ! 496: } ! 497: /* Then look in /NextLibrary/Languages. */ ! 498: if (getPathToNextLibraryLanguages(path, bundlePath, *cur)) { ! 499: if (isFileInValidLproj(path, name, ext, version)) return YES; ! 500: } ! 501: } ! 502: } ! 503: ! 504: sprintf(path, "%s", bundlePath); ! 505: if (isFileInValidLproj(path, name, ext, version)) return YES; ! 506: ! 507: return NO; ! 508: } ! 509: ! 510: -(BOOL)getPath: (char *)path forResource: (const char *)name ! 511: ofType: (const char *)ext ! 512: { ! 513: return [[self class] getPath: path forResource: name ofType: ext ! 514: inDirectory: _directory withVersion: _bundleVersion]; ! 515: } ! 516: ! 517: + setSystemLanguages:(const char *const *)languageList { ! 518: systemLanguages = languageList; ! 519: if (!languageList) systemHasLanguages = YES; ! 520: return self; ! 521: } ! 522: ! 523: @end ! 524: ! 525: const char *NXLoadLocalizedStringFromTableInBundle(const char *table, NXBundle *bundle, const char *key, const char *value) ! 526: { ! 527: static NXZone *LocalizedStringZone = NULL; ! 528: ! 529: #define LSZONE (LocalizedStringZone ? LocalizedStringZone : (LocalizedStringZone = NXDefaultMallocZone())) ! 530: ! 531: const char *tableValue; ! 532: id stringTable, stringTableTable; ! 533: char path[MAXPATHLEN + 1]; ! 534: ! 535: if (!key) return value ? value : ""; ! 536: if (!bundle) bundle = [NXBundle mainBundle]; ! 537: if (!table) table = "Localizable"; ! 538: ! 539: if (bundle != [NXBundle mainBundle]) { ! 540: stringTableTable = [bundleTable valueForKey:bundle]; ! 541: } else { ! 542: stringTableTable = mainBundleStringTableTable; ! 543: } ! 544: ! 545: if (!(stringTable = [stringTableTable valueForKey:table])) { ! 546: [bundle getPath:path forResource:table ofType:"strings"]; ! 547: stringTable = [NXStringTable newFromFile: path]; ! 548: if (!stringTableTable) { ! 549: if (bundle != [NXBundle mainBundle]) { ! 550: if (!bundleTable) bundleTable = [[HashTable allocFromZone:LSZONE] initKeyDesc:"!"]; ! 551: if (bundleTable) { ! 552: stringTableTable = [[HashTable allocFromZone:LSZONE] initKeyDesc:"*"]; ! 553: if (stringTableTable) [bundleTable insertKey:bundle value:stringTableTable]; ! 554: } ! 555: } else { ! 556: mainBundleStringTableTable = stringTableTable = [[HashTable allocFromZone:LSZONE] initKeyDesc:"*"]; ! 557: } ! 558: } ! 559: if (stringTableTable && !stringTable) stringTable = [[NXStringTable allocFromZone:LSZONE] init]; ! 560: if (stringTable) [stringTableTable insertKey:table value:stringTable]; ! 561: } ! 562: ! 563: if (!(tableValue = [stringTable valueForStringKey:key])) { ! 564: tableValue = value ? value : key; ! 565: [stringTable insertKey:key value:(void *)tableValue]; ! 566: } ! 567: ! 568: return tableValue; ! 569: } ! 570: const char *NXLoadLocalStringFromTableInBundle(const char *table, NXBundle *bundle, const char *key, const char *value) ! 571: { ! 572: return NXLoadLocalizedStringFromTableInBundle(table, bundle, key, value); ! 573: } ! 574: @implementation NXBundle (Compatability) ! 575: ! 576: - initForPath: (const char *)path { return [self initForDirectory: path]; } ! 577: - (const char *)path { return [self directory]; } ! 578: + forClass: class { return [self bundleForClass: class]; } ! 579: + newFromPath:(const char *)bp { return [[self alloc] initForPath: bp]; } ! 580: - initFromPath:(const char *)bp { return [self initForPath: bp]; } ! 581: - (const char *)bundlePath { return [self path]; } ! 582: - (BOOL)codeLoaded { return _codeLoaded; } ! 583: - firstClass { return [self principalClass]; } ! 584: - (BOOL)getResourcePath:(char *)path forName:(const char *)name type:(const char *)ext { return [self getPath: path forResource: name ofType: ext]; } ! 585: - getClass: (const char *)className { return [self classNamed: className]; } ! 586: - classForName: (const char *)className { return [self classNamed: className]; } ! 587: ! 588: @end ! 589: ! 590: @implementation NXBundle (Private) ! 591: ! 592: static void noFree(void *ptr) {} ! 593: static void freeObjects(void *ptr) ! 594: { ! 595: [(HashTable *)ptr freeObjects]; ! 596: [(HashTable *)ptr free]; ! 597: } ! 598: ! 599: + (void)_invalidateLprojCache ! 600: { ! 601: if (bundleTable) { ! 602: [bundleTable freeKeys: noFree values: freeObjects]; ! 603: [bundleTable free]; ! 604: bundleTable = nil; ! 605: } ! 606: if (mainBundleStringTableTable) { ! 607: [mainBundleStringTableTable freeObjects]; ! 608: [mainBundleStringTableTable free]; ! 609: mainBundleStringTableTable = nil; ! 610: } ! 611: } ! 612: ! 613: @end ! 614: #endif
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.