/* Implementation of GNUstep Distributed Notification Center Copyright (C) 1998 Free Software Foundation, Inc. Written by: Richard Frith-Macdonald Created: October 1998 This file is part of the GNUstep Project This library is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. You should have received a copy of the GNU General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "config.h" #include #include #include #ifdef __MINGW__ #include "process.h" #endif #include #ifdef HAVE_SYSLOG_H #include #endif #include #ifndef NSIG #define NSIG 32 #endif static int is_daemon = 0; /* Currently running as daemon. */ static char ebuf[2048]; #ifdef HAVE_SYSLOG static int log_priority; static void gdnc_log (int prio) { if (is_daemon) { syslog (log_priority | prio, ebuf); } else if (prio == LOG_INFO) { write (1, ebuf, strlen (ebuf)); write (1, "\n", 1); } else { write (2, ebuf, strlen (ebuf)); write (2, "\n", 1); } if (prio == LOG_CRIT) { if (is_daemon) { syslog (LOG_CRIT, "exiting."); } else { fprintf (stderr, "exiting.\n"); fflush (stderr); } exit(EXIT_FAILURE); } } #else #define LOG_CRIT 2 #define LOG_DEBUG 0 #define LOG_ERR 1 #define LOG_INFO 0 #define LOG_WARNING 0 void gdnc_log (int prio) { write (2, ebuf, strlen (ebuf)); write (2, "\n", 1); if (prio == LOG_CRIT) { fprintf (stderr, "exiting.\n"); fflush (stderr); exit(EXIT_FAILURE); } } #endif static void ihandler(int sig) { static BOOL beenHere = NO; BOOL action; const char *e; /* * Deal with recursive call of handler. */ if (beenHere == YES) { abort(); } beenHere = YES; /* * If asked to terminate, do so cleanly. */ if (sig == SIGTERM) { exit(EXIT_FAILURE); } #ifdef DEBUG action = YES; // abort() by default. #else action = NO; // exit() by default. #endif e = getenv("CRASH_ON_ABORT"); if (e != 0) { if (strcasecmp(e, "yes") == 0 || strcasecmp(e, "true") == 0) action = YES; else if (strcasecmp(e, "no") == 0 || strcasecmp(e, "false") == 0) action = NO; else if (isdigit(*e) && *e != '0') action = YES; else action = NO; } if (action == YES) { abort(); } else { fprintf(stderr, "gdnc killed by signal %d\n", sig); exit(sig); } } #include "gdnc.h" /* * The following dummy class is here solely as a workaround for pre 3.3 * versions of gcc where protocols didn't work properly unless implemented * in the source where the '@protocol()' directive is used. */ @interface NSDistributedNotificationCenterDummy : NSObject - (oneway void) postNotificationName: (NSString*)name object: (NSString*)object userInfo: (NSData*)info selector: (NSString*)aSelector to: (unsigned long)observer; @end @implementation NSDistributedNotificationCenterDummy - (oneway void) postNotificationName: (NSString*)name object: (NSString*)object userInfo: (NSData*)info selector: (NSString*)aSelector to: (unsigned long)observer { } @end @interface GDNCNotification : NSObject { @public NSString *name; NSString *object; NSData *info; } + (GDNCNotification*) notificationWithName: (NSString*)notificationName object: (NSString*)notificationObject data: (NSData*)notificationData; @end @implementation GDNCNotification - (void) dealloc { RELEASE(name); RELEASE(object); RELEASE(info); [super dealloc]; } + (GDNCNotification*) notificationWithName: (NSString*)notificationName object: (NSString*)notificationObject data: (NSData*)notificationData { GDNCNotification *tmp = [GDNCNotification alloc]; tmp->name = RETAIN(notificationName); tmp->object = RETAIN(notificationObject); tmp->info = RETAIN(notificationData); return AUTORELEASE(tmp); } @end /* * Information about a notification observer. */ @interface GDNCClient : NSObject { @public BOOL suspended; id client; NSMutableArray *observers; } @end @implementation GDNCClient - (void) dealloc { RELEASE(observers); [super dealloc]; } - (id) init { observers = [NSMutableArray new]; return self; } @end /* * Information about a notification observer. */ @interface GDNCObserver : NSObject { @public unsigned observer; NSString *notificationName; NSString *notificationObject; NSString *selector; GDNCClient *client; NSMutableArray *queue; NSNotificationSuspensionBehavior behavior; } @end @implementation GDNCObserver - (void) dealloc { RELEASE(queue); RELEASE(selector); RELEASE(notificationName); RELEASE(notificationObject); [super dealloc]; } - (id) init { queue = [[NSMutableArray alloc] initWithCapacity: 1]; return self; } @end @interface GDNCServer : NSObject { NSConnection *conn; NSMapTable *connections; NSHashTable *allObservers; NSMutableDictionary *observersForNames; NSMutableDictionary *observersForObjects; } - (void) addObserver: (unsigned long)anObserver selector: (NSString*)aSelector name: (NSString*)notificationname object: (NSString*)anObject suspensionBehavior: (NSNotificationSuspensionBehavior)suspensionBehavior for: (id)client; - (BOOL) connection: (NSConnection*)ancestor shouldMakeNewConnection: (NSConnection*)newConn; - (id) connectionBecameInvalid: (NSNotification*)notification; - (void) postNotificationName: (NSString*)notificationName object: (NSString*)anObject userInfo: (NSData*)d deliverImmediately: (BOOL)deliverImmediately for: (id)client; - (void) removeObserver: (GDNCObserver*)observer; - (void) removeObserversForClients: (NSMapTable*)clients; - (void) removeObserver: (unsigned long)anObserver name: (NSString*)notificationname object: (NSString*)anObject for: (id)client; - (void) setSuspended: (BOOL)flag for: (id)client; @end @implementation GDNCServer - (void) dealloc { NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; NSMapEnumerator enumerator; NSConnection *connection; NSMapTable *clients; if (conn) { [nc removeObserver: self name: NSConnectionDidDieNotification object: conn]; DESTROY(conn); } /* * Free all the client map tables in the connections map table and * ignore notifications from those connections. */ enumerator = NSEnumerateMapTable(connections); while (NSNextMapEnumeratorPair(&enumerator, (void**)&connection, (void**)&clients) == YES) { [nc removeObserver: self name: NSConnectionDidDieNotification object: connection]; [self removeObserversForClients: clients]; NSFreeMapTable(clients); } /* * Now free the connections map itsself and the table of observers. */ NSFreeMapTable(connections); NSFreeHashTable(allObservers); /* * And release the maps of notification names and objects. */ RELEASE(observersForNames); RELEASE(observersForObjects); [super dealloc]; } - (id) init { NSString *hostname; NSString *service = GDNC_SERVICE; BOOL isLocal = NO; connections = NSCreateMapTable(NSObjectMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 0); allObservers = NSCreateHashTable(NSNonOwnedPointerHashCallBacks, 0); observersForNames = [NSMutableDictionary new]; observersForObjects = [NSMutableDictionary new]; if ([[NSUserDefaults standardUserDefaults] boolForKey: @"GSNetwork"] == YES) { service = GDNC_NETWORK; } hostname = [[NSUserDefaults standardUserDefaults] stringForKey: @"NSHost"]; if ([hostname length] == 0 || [hostname isEqualToString: @"localhost"] == YES || [hostname isEqualToString: @"127.0.0.1"] == YES) { isLocal = YES; } /* * If this is the local server for the current host, * use the loopback network interface. Otherwise * create a public connection. */ if (0 && isLocal == YES && service != GDNC_NETWORK) { NSPort *port = [NSSocketPort portWithNumber: 0 onHost: [NSHost localHost] forceAddress: @"127.0.0.1" listener: YES]; conn = [[NSConnection alloc] initWithReceivePort: port sendPort: nil]; } else { conn = [NSConnection defaultConnection]; } [conn setRootObject: self]; if (isLocal == YES || [[NSHost hostWithName: hostname] isEqual: [NSHost currentHost]] == YES) { if ([conn registerName: service] == NO) { NSLog(@"gdnc - unable to register with name server as %@ - quiting.", service); DESTROY(self); return self; } } else { NSHost *host = [NSHost hostWithName: hostname]; NSPort *port = [conn receivePort]; NSPortNameServer *ns = [NSPortNameServer systemDefaultPortNameServer]; NSArray *a; unsigned c; if (host == nil) { NSLog(@"gdnc - unknown NSHost argument ... %@ - quiting.", hostname); DESTROY(self); return self; } a = [host names]; c = [a count]; while (c-- > 0) { NSString *name = [a objectAtIndex: c]; name = [service stringByAppendingFormat: @"-%@", name]; if ([ns registerPort: port forName: name] == NO) { } } a = [host addresses]; c = [a count]; while (c-- > 0) { NSString *name = [a objectAtIndex: c]; name = [service stringByAppendingFormat: @"-%@", name]; if ([ns registerPort: port forName: name] == NO) { } } } /* * Get notifications for new connections and connection losses. */ [conn setDelegate: self]; [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(connectionBecameInvalid:) name: NSConnectionDidDieNotification object: conn]; return self; } - (void) addObserver: (unsigned long)anObserver selector: (NSString*)aSelector name: (NSString*)notificationName object: (NSString*)anObject suspensionBehavior: (NSNotificationSuspensionBehavior)suspensionBehavior for: (id)client { GDNCClient *info; NSMapTable *clients; GDNCObserver *obs; NSConnection *connection; connection = [(NSDistantObject*)client connectionForProxy]; clients = (NSMapTable*)NSMapGet(connections, connection); if (clients == 0) { [NSException raise: NSInvalidArgumentException format: @"Unknown connection for new observer"]; } info = (GDNCClient*)NSMapGet(clients, client); if (info == nil) { [NSException raise: NSInvalidArgumentException format: @"Unknown client for new observer"]; } /* * Create new observer info and add to array of observers for this * client and the table of all observers. */ obs = [GDNCObserver new]; obs->observer = anObserver; obs->client = info; obs->behavior = suspensionBehavior; obs->selector = [aSelector copy]; [info->observers addObject: obs]; RELEASE(obs); NSHashInsert(allObservers, obs); /* * Now add the observer to the lists of observers interested in it's * particular notification names and objects. */ if (anObject) { NSMutableArray *objList; objList = [observersForObjects objectForKey: anObject]; if (objList == nil) { objList = [NSMutableArray new]; [observersForObjects setObject: objList forKey: anObject]; RELEASE(objList); } /* * If possible use an existing string as the key. */ if ([objList count] > 0) { GDNCObserver *tmp = [objList objectAtIndex: 0]; anObject = tmp->notificationObject; } obs->notificationObject = RETAIN(anObject); [objList addObject: obs]; } if (notificationName) { NSMutableArray *namList; namList = [observersForNames objectForKey: notificationName]; if (namList == nil) { namList = [NSMutableArray new]; [observersForNames setObject: namList forKey: notificationName]; RELEASE(namList); } /* * If possible use an existing string as the key. */ if ([namList count] > 0) { GDNCObserver *tmp = [namList objectAtIndex: 0]; notificationName = tmp->notificationObject; } obs->notificationName = RETAIN(notificationName); [namList addObject: obs]; } } - (BOOL) connection: (NSConnection*)ancestor shouldMakeNewConnection: (NSConnection*)newConn; { NSMapTable *table; [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(connectionBecameInvalid:) name: NSConnectionDidDieNotification object: newConn]; [newConn setDelegate: self]; /* * Create a new map table entry for this connection with a value that * is a table (normally with a single entry) containing registered * clients (proxies for NSDistributedNotificationCenter objects). */ table = NSCreateMapTable(NSObjectMapKeyCallBacks, NSObjectMapValueCallBacks, 0); NSMapInsert(connections, newConn, table); return YES; } - (id) connectionBecameInvalid: (NSNotification*)notification { id connection = [notification object]; [[NSNotificationCenter defaultCenter] removeObserver: self name: NSConnectionDidDieNotification object: connection]; if (connection == conn) { NSLog(@"argh - gdnc server root connection has been destroyed."); exit(EXIT_FAILURE); } else { NSMapTable *table; /* * Remove all clients registered via this connection * (should normally only be 1) - then the connection. */ table = NSMapGet(connections, connection); NSMapRemove(connections, connection); if (table != 0) { [self removeObserversForClients: table]; NSFreeMapTable(table); } } return nil; } - (void) registerClient: (id)client { NSMapTable *table; GDNCClient *info; table = NSMapGet(connections, [(NSDistantObject*)client connectionForProxy]); if (table == 0) { [NSException raise: NSInternalInconsistencyException format: @"registration with unknown connection"]; } if (NSMapGet(table, client) != 0) { [NSException raise: NSInternalInconsistencyException format: @"registration with registered client"]; } info = [GDNCClient new]; if ([(id)client isProxy] == YES) { Protocol *p = @protocol(GDNCClient); [(id)client setProtocolForProxy: p]; } info->client = client; NSMapInsert(table, client, info); RELEASE(info); } - (void) postNotificationName: (NSString*)notificationName object: (NSString*)notificationObject userInfo: (NSData*)d deliverImmediately: (BOOL)deliverImmediately for: (id)client { NSMutableArray *observers = [NSMutableArray array]; NSMutableArray *byName; NSMutableArray *byObject; unsigned pos; GDNCNotification *notification = nil; byName = [observersForNames objectForKey: notificationName]; byObject = [observersForObjects objectForKey: notificationObject]; /* * Build up a list of all those observers that should get sent this. */ for (pos = [byName count]; pos > 0; pos--) { GDNCObserver *obs = [byName objectAtIndex: pos - 1]; if (obs->notificationObject == nil || [obs->notificationObject isEqual: notificationObject]) { [observers addObject: obs]; } } for (pos = [byObject count]; pos > 0; pos--) { GDNCObserver *obs = [byObject objectAtIndex: pos - 1]; if (obs->notificationName == nil || [obs->notificationName isEqual: notificationName]) { if ([observers indexOfObjectIdenticalTo: obs] == NSNotFound) { [observers addObject: obs]; } } } /* * Build notification object to queue for observer. */ if ([observers count] > 0) { notification = [GDNCNotification notificationWithName: notificationName object: notificationObject data: d]; } /* * Add the object to the queue for this observer depending on suspension * state of the client NSDistributedNotificationCenter etc. */ for (pos = [observers count]; pos > 0; pos--) { GDNCObserver *obs = [observers objectAtIndex: pos - 1]; if (obs->client->suspended == NO || deliverImmediately == YES) { [obs->queue addObject: notification]; } else { switch (obs->behavior) { case NSNotificationSuspensionBehaviorDrop: break; case NSNotificationSuspensionBehaviorCoalesce: [obs->queue removeAllObjects]; [obs->queue addObject: notification]; break; case NSNotificationSuspensionBehaviorHold: [obs->queue addObject: notification]; break; case NSNotificationSuspensionBehaviorDeliverImmediately: [obs->queue addObject: notification]; break; } } } /* * Now perform the actual posting of the notification to the observers in * our array. */ for (pos = [observers count]; pos > 0; pos--) { GDNCObserver *obs = [observers objectAtIndex: pos - 1]; if (obs->client->suspended == NO || deliverImmediately == YES) { /* * Post notifications to the observer until: * an exception (obs is set to nil) * the queue is empty ([obs->queue count] == 0) * the observer is removed (obs is not in allObservers) */ while (obs != nil && [obs->queue count] > 0 && NSHashGet(allObservers, obs) != 0) { NS_DURING { GDNCNotification *n; n = RETAIN([obs->queue objectAtIndex: 0]); [obs->queue removeObjectAtIndex: 0]; [obs->client->client postNotificationName: n->name object: n->object userInfo: n->info selector: obs->selector to: obs->observer]; RELEASE(n); } NS_HANDLER { DESTROY(obs); } NS_ENDHANDLER } } } } - (void) removeObserver: (GDNCObserver*)obs { if (obs->notificationObject) { NSMutableArray *objList; objList = [observersForObjects objectForKey: obs->notificationObject]; if (objList != nil) { [objList removeObjectIdenticalTo: obs]; } } if (obs->notificationName) { NSMutableArray *namList; namList = [observersForNames objectForKey: obs->notificationName]; if (namList != nil) { [namList removeObjectIdenticalTo: obs]; } } NSHashRemove(allObservers, obs); [obs->client->observers removeObjectIdenticalTo: obs]; } - (void) removeObserversForClients: (NSMapTable*)clients { NSMapEnumerator enumerator; NSObject *client; GDNCClient *info; enumerator = NSEnumerateMapTable(clients); while (NSNextMapEnumeratorPair(&enumerator, (void**)&client, (void**)&info) == YES) { while ([info->observers count] > 0) { [self removeObserver: [info->observers objectAtIndex: 0]]; } } } - (void) removeObserver: (unsigned long)anObserver name: (NSString*)notificationName object: (NSString*)notificationObject for: (id)client { if (anObserver == 0) { if (notificationName == nil) { NSMutableArray *observers; /* * No notification name - so remove all with matching object. */ observers = [observersForObjects objectForKey: notificationObject]; while ([observers count] > 0) { GDNCObserver *obs; obs = [observers objectAtIndex: 0]; [self removeObserver: obs]; } } else if (notificationObject == nil) { NSMutableArray *observers; /* * No notification object - so remove all with matching name. */ observers = [observersForObjects objectForKey: notificationName]; while ([observers count] > 0) { GDNCObserver *obs; obs = [observers objectAtIndex: 0]; [self removeObserver: obs]; } } else { NSMutableArray *byName; NSMutableArray *byObject; unsigned pos; /* * Remove observers that match both name and object. */ byName = [observersForObjects objectForKey: notificationName]; byObject = [observersForObjects objectForKey: notificationName]; for (pos = [byName count]; pos > 0; pos--) { GDNCObserver *obs; obs = [byName objectAtIndex: pos - 1]; if ([byObject indexOfObjectIdenticalTo: obs] != NSNotFound) { [self removeObserver: obs]; } } for (pos = [byObject count]; pos > 0; pos--) { GDNCObserver *obs; obs = [byObject objectAtIndex: pos - 1]; if ([byName indexOfObjectIdenticalTo: obs] != NSNotFound) { [self removeObserver: obs]; } } } } else { NSMapTable *table; GDNCClient *info; /* * If an observer object (as an unsigned) was specified then * the observer MUST be from this client - so we can look * through the per-client list of objects. */ table = NSMapGet(connections, [(NSDistantObject*)client connectionForProxy]); if (table == 0) { [NSException raise: NSInternalInconsistencyException format: @"removeObserver with unknown connection"]; } info = (GDNCClient*)NSMapGet(table, client); if (info != nil) { unsigned pos = [info->observers count]; while (pos > 0) { GDNCObserver *obs = [info->observers objectAtIndex: --pos]; if (obs->observer == anObserver) { if (notificationName == nil || [notificationName isEqual: obs->notificationName]) { if (notificationObject == nil || [notificationObject isEqual: obs->notificationObject]) { [self removeObserver: obs]; } } } } } } } - (void) setSuspended: (BOOL)flag for: (id)client { NSMapTable *table; GDNCClient *info; table = NSMapGet(connections, [(NSDistantObject*)client connectionForProxy]); if (table == 0) { [NSException raise: NSInternalInconsistencyException format: @"setSuspended: with unknown connection"]; } info = (GDNCClient*)NSMapGet(table, client); if (info == nil) { [NSException raise: NSInternalInconsistencyException format: @"setSuspended: with unregistered client"]; } info->suspended = flag; } - (void) unregisterClient: (id)client { NSMapTable *table; GDNCClient *info; table = NSMapGet(connections, [(NSDistantObject*)client connectionForProxy]); if (table == 0) { [NSException raise: NSInternalInconsistencyException format: @"unregistration with unknown connection"]; } info = (GDNCClient*)NSMapGet(table, client); if (info == nil) { [NSException raise: NSInternalInconsistencyException format: @"unregistration with unregistered client"]; } while ([info->observers count] > 0) { [self removeObserver: [info->observers objectAtIndex: 0]]; } NSMapRemove(table, client); } @end int main(int argc, char** argv, char** env) { int c; GDNCServer *server; NSString *str; BOOL shouldFork = YES; BOOL debugging = NO; CREATE_AUTORELEASE_POOL(pool); #ifdef GS_PASS_ARGUMENTS [NSProcessInfo initializeWithArguments:argv count:argc environment:env]; #endif [NSObject enableDoubleReleaseCheck: YES]; if (argc > 1 && strcmp(argv[argc-1], "-f") == 0) { shouldFork = NO; } str = [[NSUserDefaults standardUserDefaults] stringForKey: @"debug"]; if (str != nil && [str caseInsensitiveCompare: @"yes"] == NSOrderedSame) { shouldFork = NO; debugging = YES; } RELEASE(pool); #ifdef __MINGW__ if (shouldFork) { char **a = malloc((argc+2) * sizeof(char*)); memcpy(a, argv, argc*sizeof(char*)); a[argc] = "-f"; a[argc+1] = 0; if (_spawnv(_P_NOWAIT, argv[0], a) == -1) { fprintf(stderr, "gdnc - spawn failed - bye.\n"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } #else if (shouldFork) { is_daemon = 1; switch (fork()) { case -1: fprintf(stderr, "gdnc - fork failed - bye.\n"); exit(EXIT_FAILURE); case 0: /* * Try to run in background. */ #ifdef NeXT setpgrp(0, getpid()); #else setsid(); #endif break; default: exit(EXIT_SUCCESS); } } /* * Ensure we don't have any open file descriptors which may refer * to sockets bound to ports we may try to use. * * Use '/dev/null' for stdin and stdout. */ for (c = 0; c < FD_SETSIZE; c++) { if (is_daemon || (c != 2)) { (void)close(c); } } if (open("/dev/null", O_RDONLY) != 0) { sprintf(ebuf, "failed to open stdin from /dev/null (%s)\n", strerror(errno)); gdnc_log(LOG_CRIT); exit(EXIT_FAILURE); } if (open("/dev/null", O_WRONLY) != 1) { sprintf(ebuf, "failed to open stdout from /dev/null (%s)\n", strerror(errno)); gdnc_log(LOG_CRIT); exit(EXIT_FAILURE); } if (is_daemon && open("/dev/null", O_WRONLY) != 2) { sprintf(ebuf, "failed to open stderr from /dev/null (%s)\n", strerror(errno)); gdnc_log(LOG_CRIT); exit(EXIT_FAILURE); } #endif /* !MINGW */ { #if GS_WITH_GC == 0 CREATE_AUTORELEASE_POOL(pool); #endif NSUserDefaults *defs; int sym; for (sym = 0; sym < NSIG; sym++) { signal(sym, ihandler); } #ifndef __MINGW__ signal(SIGPIPE, SIG_IGN); signal(SIGTTOU, SIG_IGN); signal(SIGTTIN, SIG_IGN); signal(SIGHUP, SIG_IGN); #endif signal(SIGTERM, ihandler); /* * Make gdnc logging go to syslog unless overridden by user. */ defs = [NSUserDefaults standardUserDefaults]; [defs registerDefaults: [NSDictionary dictionaryWithObjectsAndKeys: @"YES", @"GSLogSyslog", nil]]; server = [GDNCServer new]; /* * Close standard input, output, and error to run as daemon. */ [[NSFileHandle fileHandleWithStandardInput] closeFile]; [[NSFileHandle fileHandleWithStandardOutput] closeFile]; #ifndef __MINGW__ if (debugging == NO) { [[NSFileHandle fileHandleWithStandardError] closeFile]; } #endif RELEASE(pool); } if (server != nil) { CREATE_AUTORELEASE_POOL(pool); [[NSRunLoop currentRunLoop] run]; RELEASE(pool); } exit(EXIT_SUCCESS); }