File:  [Apple Darwin 0.x] / objc / NXBundle.m
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs
Tue Apr 24 19:13:57 2018 UTC (8 years, 2 months ago) by root
Branches: MAIN, Apple
CVS tags: HEAD, Darwin03, Darwin01
Darwin 0.1 In-kernel Objective-C runtime

/*
 * Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * "Portions Copyright (c) 1999 Apple Computer, Inc.  All Rights
 * Reserved.  This file contains Original Code and/or Modifications of
 * Original Code as defined in and that are subject to the Apple Public
 * Source License Version 1.0 (the 'License').  You may not use this file
 * except in compliance with the License.  Please obtain a copy of the
 * License at http://www.apple.com/publicsource and read it before using
 * this file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
 * License for the specific language governing rights and limitations
 * under the License."
 * 
 * @APPLE_LICENSE_HEADER_END@
 */
/*	NXBundle.m
	Copyright 1990, 1991, NeXT, Inc.
	IPC, November 1990
*/

#ifndef KERNEL
#ifdef SHLIB
#import "shlib.h"
#endif SHLIB


#import "NXBundle.h"
#import "NXBundlePrivate.h"
#import "maptable.h"
#import "NXStringTable.h"
#import "objc-load.h"
#import "hashtable.h"
#import "objc-private.h"

#import <sys/file.h>
#import <sys/dir.h>
#import <sys/stat.h>
#import <sys/types.h>

#import <strings.h>

static void warning(const char *format, ...) {}

/* For the LProj cache */
static id bundleTable = nil;
static id mainBundleStringTableTable = nil;

#define BUNDLE_SUFFIX	"bundle"

/*******	Bundle		********/

@implementation NXBundle

static NXMapTable *classToBundle = NULL;

static NXBundle *mainBundle = nil;
static NXMapTable *pathToBundle = NULL;

- initForDirectory:(const char *)bp {
    struct stat statInfo;
    id	bb;
    
    if (! pathToBundle) pathToBundle = NXCreateMapTable(NXStrValueMapPrototype, 1);
    bb = NXMapGet(pathToBundle, bp);
    if (bb) {
	[self free];
	return bb;
    } else {
	BOOL allOK = YES;
	
	if (stat(bp, &statInfo) == 0) {
	    while (allOK && (statInfo.st_mode & S_IFLNK)) {
		char linkPath[MAXPATHLEN + 1];
		
		allOK = ((readlink(bp, linkPath, sizeof(linkPath)) != -1)
		         && (stat(linkPath, &statInfo) == 0));
	    }
	    allOK = allOK
	            && ((statInfo.st_mode & (S_IFDIR | S_IREAD | S_IEXEC))
		        ? YES : NO);
	}
	if (allOK) {
	    _directory = NXCopyStringBuffer(bp);
	    _codeLoaded = NO;
	    NXMapInsert(pathToBundle, _directory, self);
	    return self;
	} else {
	    [self free];
	    return nil;
	}
    }
}

- free
{
    if (_codeLoaded)
	return self;
    else {
	NXMapRemove(pathToBundle, _directory);
	free(_directory);
	return [super free];
    }
}

+ mainBundle {
    if (! mainBundle) {
	char	prog[MAXPATHLEN];
	char	*last;
	char 	**argv;
#if defined(__DYNAMIC__)
        extern char ***_NSGetArgv();
        argv = *_NSGetArgv();
#else
		extern char **NXArgv;
        argv = NXArgv;
#endif
        if (argv[0][0] != '/') { getwd(prog); strcat(prog, "/"); }
	else prog[0] = 0;
	strcat(prog, argv[0]);
	last = rindex(prog, '/');
	last[0] = 0;
	mainBundle = [[self alloc] initForDirectory: prog];
	mainBundle->_codeLoaded = YES;
    }
    return mainBundle;
}

+ bundleForClass:class {
    id	bundle;
    if (! classToBundle) return [self mainBundle];
    bundle = NXMapGet(classToBundle, class);
    return (bundle) ? bundle : [self mainBundle];
}
    
- (const char *)directory {
    return _directory;
}

static Class globalPrincipalClass = NULL;
static id globalBundle = nil;

static void loadCallback(Class cls, Category cat ) {
    warning("Loading %s '%s'\n", (cat) ? "category of" : "class", cls->name);
    if (cat) return;
    if (! classToBundle) classToBundle = NXCreateMapTable(NXPtrValueMapPrototype, 1);
    NXMapInsert(classToBundle, cls, globalBundle);
    if (! globalPrincipalClass) globalPrincipalClass = cls;
}

static NXStream* bundleErrorStream = 0;

+ (NXStream*) setErrorStream:(NXStream*)stream
{
  NXStream* s = bundleErrorStream;
  bundleErrorStream = stream;
  return s;
}

- (BOOL)ensureLoaded {
    if (! _codeLoaded) {
	char 		name[MAXPATHLEN];
	char		path[MAXPATHLEN];
	char		*moduleList[2] = {path, NULL};
	NXStream	*errorStream;
	long		err;
	char		*slash, *dot;

	globalPrincipalClass = NULL;
	globalBundle = self;
	slash = rindex(_directory, '/');
	if (slash) {
	    strcpy(name, slash + 1);
	} else {
	    strcpy(name, _directory);
	}
	dot = rindex(name, '.');
	if (dot)
	    *dot = '\0';
	if ([self getPath: path forResource: name ofType: NULL]) {
	    errorStream = NXOpenMemory(NULL, 0, NX_WRITEONLY);

	    err = objc_loadModules(moduleList, errorStream, loadCallback,
				    NULL, NULL);
	    if (err) {
		char *streamBuf;
		int len, maxLen;
		
		NXGetMemoryBuffer(errorStream, &streamBuf, &len, &maxLen);
		if (!bundleErrorStream)
		  {
		    _NXLogError("Error loading %s", path);
		    if (len) {
		      streamBuf[len] = 0;
		      _NXLogError(streamBuf);
		    }	
		    _NXLogError("\n");
		  }
		else
		  {
		    NXPrintf (bundleErrorStream, "Error loading %s", path);
		    if (len) {
		      streamBuf[len] = 0;
		      NXPrintf (bundleErrorStream, streamBuf);
		    }
		    NXPrintf (bundleErrorStream, "\n");
		  }
	    } else {
		_principalClass = globalPrincipalClass;
		_codeLoaded = YES;
	    }
	    NXCloseMemory(errorStream, NX_FREEBUFFER|NX_TRUNCATEBUFFER);
	    if (err) return NO;
	} else {
	  if (! bundleErrorStream)
	    _NXLogError("Couldn't find %s in bundle for %s", name, _directory);
	  else
	    NXPrintf (bundleErrorStream,
		      "Couldn't find %s in bundle for %s", name, _directory);
	  return NO;
	}
	  
    }
    return YES;
}

- classNamed:(const char *)className {
    if (![self ensureLoaded]) return nil;
    return objc_getClass((char *)className); 
}

- principalClass {
    if (![self ensureLoaded]) return nil;
    return (id)_principalClass;
}

- setVersion:(int)version {
    _bundleVersion = version;
    return self;
}

- (int)version {
    return _bundleVersion;
}

/*******	International Routines		********/

static BOOL systemHasLanguages = YES;
static const char *const *systemLanguages = NULL;

static const char *const *getDefaultSystemLanguages(void) {
    int count, length;
    const char *language, *separator;
    char **defaultSystemLanguages = NULL;

    if (systemHasLanguages) {
	language = "English";
	if (language && *language) {
	    count = 1;
	    separator = strchr(language, ';');
	    while (separator) {
		count++;
		separator = strchr(separator+1, ';');
	    }
	    defaultSystemLanguages = malloc((count + 1) * sizeof(char *));
	    defaultSystemLanguages[count] = NULL;
	    for (count = 0; language; count++) {
		separator = strchr(language, ';');
		if (separator) {
		    length = separator - language;
		    separator++;
		} else {
		    length = strlen(language);
		}
		while (*language == ' ' || *language == '\t') {
		    language++; length--;
		}
		while (language[length-1] == ' ' || language[length-1] == '\t') length--;
		defaultSystemLanguages[count] = malloc((length + 1) * sizeof(char));
		strncpy(defaultSystemLanguages[count], language, length);
		defaultSystemLanguages[count][length] = '\0';
		language = separator;
	    }
	} else {
	    systemHasLanguages = NO;
	}
    }

    return defaultSystemLanguages;
}

#ifdef DEBUG
static int ACCESS(const char *path, int way)
{
    int retval = access(path, way);
    printf("access(%s, R_OK) returns %d\n", path, retval);
    return retval;
}
#else
#define ACCESS access
#endif

#define LINE_BUFFER_SIZE 256

/* 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. */
 
static int readLprojVersion(char *path) {
    FILE *file;
    int version = 0;
    int pathLength = strlen(path);

    strcat(path, "/version");
    file = fopen(path, "r");
    if (file) {
	char *eof;
	char lineBuffer[LINE_BUFFER_SIZE+1];
	*lineBuffer = '\0';
	do {
	    eof = fgets(lineBuffer, LINE_BUFFER_SIZE, file);
	    version = atoi(lineBuffer);
	} while (!eof && !version);
    }
    path[pathLength] = '\0';
    fclose(file);

    return version;
}

/* Adds the passed extension to the passed name iff the extension is not already on that name. */

static void addExtension(char *name, const char *extension)
{
    if (extension) {
	const char *existingExtension = strrchr(name, '.');
	if (existingExtension && extension[0] != '.') existingExtension++;
	if (!existingExtension || strcmp(existingExtension, extension)) {
	    if (extension[0] != '.' && extension[0]) strcat(name, ".");
	    strcat(name, extension);
	}
    }
}

/* 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. */

static char *addDirectoryEntriesTo(char *files, const char *directory)
{
    DIR *dirp;
    struct direct *dp;
    int length;

    length = strlen(files);
    if ((dirp = opendir(directory))) {
	dp = readdir(dirp); /* Skip . */
	dp = readdir(dirp); /* Skip .. */
	for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
	    files = realloc(files, length + dp->d_namlen + 2);
	    strncpy(files + length, dp->d_name, dp->d_namlen);
	    length += dp->d_namlen;
	    files[length++] = '/';
	}
	files[length] = '\0';
	closedir(dirp);
    }

    return files;
}

/* 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. */

static BOOL isFileInValidLproj(char *path, const char *name, const char *extension, int bundleVersion)
{
    char *lprojInfo, *slash;
    int length, version = -1;
    char buffer[MAXPATHLEN+1];
    static NXMapTable *lprojTable = NULL;

    if (!lprojTable) lprojTable = NXCreateMapTable(NXStrValueMapPrototype, 0);

    lprojInfo = NXMapGet(lprojTable, path);

    if (!lprojInfo) {
	if (ACCESS(path, R_OK) >= 0) version = readLprojVersion(path);
	sprintf(buffer, "%d/", version);
	lprojInfo = NXCopyStringBuffer(buffer);
	lprojInfo = addDirectoryEntriesTo(lprojInfo, path);
	NXMapInsert(lprojTable, NXCopyStringBuffer(path), lprojInfo);
    } else {
	version = atoi(lprojInfo);
    }

    if (version == bundleVersion && (lprojInfo = strchr(lprojInfo, '/'))) {
	strcpy(buffer, name);
        slash = buffer;
	while (*slash == '/') slash++;
	if ((slash = strchr(buffer, '/'))) {
	    *(slash+1) = '\0';
	} else {
	    addExtension(buffer, extension);
	    strcat(buffer, "/");
	}
	length = strlen(buffer);
	while (*++lprojInfo) {
	    if (!strncmp(lprojInfo, buffer, length)) {
		if (path[strlen(path)-1] != '/') strcat(path, "/");
		strcat(path, name);
		addExtension(path, extension);
		return slash ? (ACCESS(path, R_OK) >= 0) : YES;
	    }
	    lprojInfo = strchr(lprojInfo, '/');
	    if (!lprojInfo) return NO;	/* should never happen */
	}
    }

    return NO;
}

/* Returns a path to the appropriate shadow .lproj in /NextLibrary/Languages. */

static BOOL getPathToNextLibraryLanguages(char *path, const char *realPath, const char *language)
{
    const char *s, *t;
    char scratch[ MAXPATHLEN+1 ];

    s = strchr(realPath, '.');
    while (s) {
	if (!strncmp(s, ".app", 4)) {
	    char *p;
	    strcpy(scratch, realPath);
	    p = scratch + (s - realPath);
	    t = p+4;
	    if (*t == '/') t++;
	    *p = '\0';
	    s = strrchr(scratch, '/');
	    if (s) {
		if (*t) {
		    sprintf(path, "/NextLibrary/Languages/%s%s.lproj/%s", language, s, t);
		} else {
		    sprintf(path, "/NextLibrary/Languages/%s%s.lproj", language, s);
		}
	    }
	    return YES;
	} else {
	    s = strchr(s+1, '.');
	}
    }
    if (!s && !strncmp(realPath, "/usr/lib/", 9)) {
	s = realPath+8;
	while (*s == '/') s++;
	if (*s) {
	    s = strchr(s, '/');
	    if (s) {
		char *tmp;
		
		strcpy(scratch, realPath);
		tmp = scratch + (s - realPath);
		*tmp++ = '\0';
		s = tmp;
	    }
	    if (s && *s) {
		sprintf(path, "/NextLibrary/Languages/%s%s.lproj/%s", language, scratch+8, s);
	    } else {
		sprintf(path, "/NextLibrary/Languages/%s%s.lproj", language, realPath+8);
	    }
	    return YES;
	}
    }

    return NO;
}

+ (BOOL)getPath:(char *)path forResource:(const char *)name
         ofType:(const char *)ext inDirectory: (const char *)bundlePath
    withVersion: (int)version
{
    const char		*const *cur;
    char		searchPath[MAXPATHLEN+1];

    if (!name) return NO;
    if (!path) path = searchPath;

    if (!systemLanguages) systemLanguages = getDefaultSystemLanguages();

    if (systemLanguages) {
	for (cur = systemLanguages; *cur; cur++) {
	    /* First look for a language in the bundle. */
	    if (bundlePath) {
		sprintf(path, "%s/%s.lproj", bundlePath, *cur);
		if (isFileInValidLproj(path, name, ext, version)) return YES;
	    }
	    /* Then look in /NextLibrary/Languages. */
	    if (getPathToNextLibraryLanguages(path, bundlePath, *cur)) {
		if (isFileInValidLproj(path, name, ext, version)) return YES;
	    }
	}
    }

    sprintf(path, "%s", bundlePath);
    if (isFileInValidLproj(path, name, ext, version)) return YES;

    return NO;
}

-(BOOL)getPath: (char *)path forResource: (const char *)name
        ofType: (const char *)ext
{
    return [[self class] getPath: path forResource: name ofType: ext
                     inDirectory: _directory withVersion: _bundleVersion];
}

+ setSystemLanguages:(const char *const *)languageList {
    systemLanguages = languageList;
    if (!languageList) systemHasLanguages = YES;
    return self;
}

@end

const char *NXLoadLocalizedStringFromTableInBundle(const char *table, NXBundle *bundle, const char *key, const char *value)
{
    static NXZone *LocalizedStringZone = NULL;

    #define LSZONE (LocalizedStringZone ? LocalizedStringZone : (LocalizedStringZone = NXDefaultMallocZone()))

    const char *tableValue;
    id stringTable, stringTableTable;
    char path[MAXPATHLEN + 1];

    if (!key) return value ? value : "";
    if (!bundle) bundle = [NXBundle mainBundle];
    if (!table) table = "Localizable";

    if (bundle != [NXBundle mainBundle]) {
	stringTableTable = [bundleTable valueForKey:bundle];
    } else {
	stringTableTable = mainBundleStringTableTable;
    }

    if (!(stringTable = [stringTableTable valueForKey:table])) {
	[bundle getPath:path forResource:table ofType:"strings"];
	stringTable = [NXStringTable newFromFile: path];
	if (!stringTableTable) {
	    if (bundle != [NXBundle mainBundle]) {
		if (!bundleTable) bundleTable = [[HashTable allocFromZone:LSZONE] initKeyDesc:"!"];
		if (bundleTable) {
		    stringTableTable = [[HashTable allocFromZone:LSZONE] initKeyDesc:"*"];
		    if (stringTableTable) [bundleTable insertKey:bundle value:stringTableTable];
		}
	    } else {
		mainBundleStringTableTable = stringTableTable = [[HashTable allocFromZone:LSZONE] initKeyDesc:"*"];
	    }
	}
	if (stringTableTable && !stringTable) stringTable = [[NXStringTable allocFromZone:LSZONE] init];
	if (stringTable) [stringTableTable insertKey:table value:stringTable];
    }

    if (!(tableValue = [stringTable valueForStringKey:key])) {
	tableValue = value ? value : key;
	[stringTable insertKey:key value:(void *)tableValue];
    }

    return tableValue;
}
const char *NXLoadLocalStringFromTableInBundle(const char *table, NXBundle *bundle, const char *key, const char *value)
{
  return NXLoadLocalizedStringFromTableInBundle(table, bundle, key, value);
}
@implementation NXBundle (Compatability)

- initForPath: (const char *)path { return [self initForDirectory: path]; }
- (const char *)path { return [self directory]; }
+ forClass: class { return [self bundleForClass: class]; }
+ newFromPath:(const char *)bp { return [[self alloc] initForPath: bp]; }
- initFromPath:(const char *)bp { return [self initForPath: bp]; }
- (const char *)bundlePath { return [self path]; }
- (BOOL)codeLoaded { return _codeLoaded; }
- firstClass { return [self principalClass]; }
- (BOOL)getResourcePath:(char *)path forName:(const char *)name type:(const char *)ext { return [self getPath: path forResource: name ofType: ext]; }
- getClass: (const char *)className { return [self classNamed: className]; }
- classForName: (const char *)className { return [self classNamed: className]; }

@end

@implementation NXBundle (Private)

static void noFree(void *ptr) {}
static void freeObjects(void *ptr)
{
    [(HashTable *)ptr freeObjects];
    [(HashTable *)ptr free];
}

+ (void)_invalidateLprojCache
{
    if (bundleTable) {
	[bundleTable freeKeys: noFree values: freeObjects];
	[bundleTable free];
	bundleTable = nil;
    }
    if (mainBundleStringTableTable) {
	[mainBundleStringTableTable freeObjects];
	[mainBundleStringTableTable free];
	mainBundleStringTableTable = nil;
    }
}

@end
#endif

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.