growl agent should handle port busy
[openafs.git] / src / platform / DARWIN / growlagent / main.m
1 /*
2  Copyright (c) The Growl Project, 2004-2005
3  All rights reserved.
4
5
6  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
7
8
9  1. Redistributions of source code must retain the above copyright
10  notice, this list of conditions and the following disclaimer.
11  2. Redistributions in binary form must reproduce the above copyright
12  notice, this list of conditions and the following disclaimer in the
13  documentation and/or other materials provided with the distribution.
14  3. Neither the name of Growl nor the names of its contributors
15  may be used to endorse or promote products derived from this software
16  without specific prior written permission.
17
18
19  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
20
21 */
22 #import <Foundation/Foundation.h>
23 #import "GrowlDefines.h"
24 #import "GrowlPathway.h"
25 #include "CFGrowlAdditions.h"
26
27 #include <mach-o/dyld.h>
28 #include <unistd.h>
29 #include <getopt.h>
30 #include <netinet/in.h>
31 #include <sys/types.h>
32 #include <sys/socket.h>
33 #include <netdb.h>
34 #include <sys/param.h>
35
36 #define VMON_SOCKET     2106
37
38 #define STORESTR "store$"
39 #define FETCHSTR "fetch$"
40 #define WARNSTR  "warn$"  
41 #define STOREOFFT 6
42 #define FETCHOFFT 6
43 #define WARNOFFT  6
44
45 #define NOTIFICATION_NAME CFSTR("OpenAFS Venus Monitor")
46 #define APPLICATION_NAME CFSTR("afshelper")
47
48 #define STRINGIFY(x) STRINGIFY2(x)
49 #define STRINGIFY2(x) #x
50
51 static void notificationDismissed(CFNotificationCenterRef center,
52                                                                   void *observer,
53                                                                   CFStringRef name,
54                                                                   const void *object,
55                                                                   CFDictionaryRef userInfo) {
56 #pragma unused(center,observer,name,object,userInfo)
57         /*CFRunLoopStop(CFRunLoopGetCurrent());*/
58 }
59
60 void getPath(char **selfPathPtr) 
61 {
62         uint32_t selfPathSize = MAXPATHLEN;
63         if(!(*selfPathPtr = malloc(selfPathSize)))
64         {
65                 exit(-1);
66         }
67         if(_NSGetExecutablePath(*selfPathPtr, &selfPathSize) == -1)
68         {
69                 // Try reallocating selfPath with the size returned by the func
70                 if(!(*selfPathPtr = realloc(*selfPathPtr, selfPathSize + 1)))
71                 {
72                         NSLog(@"Could not allocate memory to hold executable path.");
73                         exit(-1);
74                 }
75                 if(_NSGetExecutablePath(*selfPathPtr, &selfPathSize) != 0)
76                 {
77                         NSLog(@"Could not get executable path.");
78                         exit(-1);
79                 }
80         }
81 }
82
83 static void
84 MyTransmit(CFNotificationCenterRef distCenter, CFDictionaryRef registerInfo, CFMutableDictionaryRef notificationInfo)
85 {
86         NSConnection *connection = [NSConnection connectionWithRegisteredName:@"GrowlApplicationBridgePathway" host:nil];
87         if (connection) {
88                 //Post to Growl via GrowlApplicationBridgePathway
89                 @try {
90                         NSDistantObject *theProxy = [connection rootProxy];
91                         [theProxy setProtocolForProxy:@protocol(GrowlNotificationProtocol)];
92                         id<GrowlNotificationProtocol> growlProxy = (id)theProxy;
93                         [growlProxy registerApplicationWithDictionary:(NSDictionary *)registerInfo];
94                         [growlProxy postNotificationWithDictionary:(NSDictionary *)notificationInfo];
95                 } @catch(NSException *e) {
96                         NSLog(@"exception while sending notification: %@", e);
97                 }
98         } else {
99                 //Post to Growl via NSDistributedNotificationCenter
100                 NSLog(@"could not find local GrowlApplicationBridgePathway, falling back to NSDNC");
101                 CFNotificationCenterPostNotificationWithOptions(distCenter, (CFStringRef)GROWL_APP_REGISTRATION, NULL, registerInfo, kCFNotificationPostToAllSessions);
102                 CFNotificationCenterPostNotificationWithOptions(distCenter, (CFStringRef)GROWL_NOTIFICATION, NULL, notificationInfo, kCFNotificationPostToAllSessions);
103         }
104 }
105
106 static void
107 BuildNotificationInfo(char *recvbuf, CFNotificationCenterRef dcref, CFDictionaryRef regref, CFDataRef icon)
108 {
109         int priority;
110         CFNumberRef priorityNumber;
111         CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
112         CFStringRef clickContext = CFUUIDCreateString(kCFAllocatorDefault, uuid);
113         CFMutableDictionaryRef notificationInfo = CFDictionaryCreateMutable(kCFAllocatorDefault ,9, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
114         CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_NAME, NOTIFICATION_NAME);
115         CFDictionarySetValue(notificationInfo, GROWL_APP_NAME, APPLICATION_NAME);
116         CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_STICKY, kCFBooleanFalse);
117         CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_ICON, icon);
118         CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_CLICK_CONTEXT, clickContext);
119         CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_TITLE, CFSTR("OpenAFS")/*title*/);
120         //CFRelease(title);
121 #if 0
122         /* if fetching ever provides more data we could use this */
123         CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_PROGRESS, progressNumber);
124 #endif
125         if (!strncmp(recvbuf, FETCHSTR, sizeof(FETCHSTR)-1)) {
126                 CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_DESCRIPTION, CFStringCreateWithCString(kCFAllocatorDefault, recvbuf+FETCHOFFT, kCFStringEncodingUTF8));
127                 priority = -1;
128                 CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_IDENTIFIER, CFSTR("AFSFetch"));
129         } else if (!strncmp(recvbuf, STORESTR, sizeof(STORESTR)-1)) {
130                 CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_DESCRIPTION, CFStringCreateWithCString(kCFAllocatorDefault, recvbuf+STOREOFFT, kCFStringEncodingUTF8));
131                 priority = -1;
132                 CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_IDENTIFIER, CFSTR("AFSStore"));
133         } else if (!strncmp(recvbuf, WARNSTR, sizeof(WARNSTR)-1)) {
134                 CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_DESCRIPTION, CFStringCreateWithCString(kCFAllocatorDefault, recvbuf+WARNOFFT, kCFStringEncodingUTF8));
135                 priority = +1;
136                 CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_IDENTIFIER, CFSTR("AFSWarn"));
137         } else {
138                 CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_DESCRIPTION, CFStringCreateWithCString(kCFAllocatorDefault, recvbuf, kCFStringEncodingUTF8));
139                 priority = -2;
140                 CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_IDENTIFIER, CFSTR("AFS"));
141         }
142         priorityNumber = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &priority);
143         CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_PRIORITY, priorityNumber);
144         CFRelease(priorityNumber);      
145         CFRelease(clickContext);
146         MyTransmit(dcref, regref, notificationInfo);
147 }
148
149 struct GrowlCBContext {
150         CFNotificationCenterRef dcref;
151         CFDictionaryRef regref;
152         CFDataRef icon;
153 };
154
155 static void MySocketReadCallBack(CFSocketRef socket, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info)
156 {
157         struct GrowlCBContext* callbackInfo = (struct GrowlCBContext * )info;
158         uint8_t recvBuffer[1024];
159         int result;
160         int recvSocket = CFSocketGetNative( socket );
161         if (!info) return;
162         if ( callbackType != kCFSocketReadCallBack ) return;
163
164         result = recvfrom( recvSocket, recvBuffer, sizeof(recvBuffer), 0, NULL, NULL );
165         if (result) {
166                 recvBuffer[result-1] = '\0';
167                 BuildNotificationInfo((char *)recvBuffer, callbackInfo->dcref, callbackInfo->regref, callbackInfo->icon);
168         }
169 }
170
171 int main(int argc, const char **argv) {
172         BOOL         wait = NO;
173         int          code = EXIT_SUCCESS;
174         CFDataRef icon = NULL;
175         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
176
177         // get icon data for application name
178         char *selfPath;
179         getPath(&selfPath);
180         CFStringRef appPath = CFStringCreateWithCString(kCFAllocatorDefault, selfPath, kCFStringEncodingUTF8);
181         NSURL *appURL = [NSURL fileURLWithPath:(NSString *)appPath];
182         icon = (CFDataRef)copyIconDataForURL(appURL);
183         free(selfPath);
184
185         // Register with Growl
186         CFStringRef name = NOTIFICATION_NAME;
187         CFArrayRef defaultAndAllNotifications = CFArrayCreate(kCFAllocatorDefault, (const void **)&name, 1, &kCFTypeArrayCallBacks);
188         CFTypeRef registerKeys[4] = {
189                 GROWL_APP_NAME,
190                 GROWL_NOTIFICATIONS_ALL,
191                 GROWL_NOTIFICATIONS_DEFAULT,
192                 GROWL_APP_ICON
193         };
194         CFTypeRef registerValues[4] = {
195                 APPLICATION_NAME,
196                 defaultAndAllNotifications,
197                 defaultAndAllNotifications,
198                 icon
199         };
200         CFDictionaryRef registerInfo = CFDictionaryCreate(kCFAllocatorDefault, registerKeys, registerValues, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
201         CFRelease(defaultAndAllNotifications);
202         CFRelease(icon);
203
204         CFNotificationCenterRef distCenter;
205         {
206                 distCenter = CFNotificationCenterGetDistributedCenter();
207                 if (wait) {
208                         CFMutableStringRef notificationName = CFStringCreateMutable(kCFAllocatorDefault, 0);
209                         CFStringRef applicationName1 = APPLICATION_NAME;
210                         CFStringAppend(notificationName, applicationName1);
211                         CFStringAppend(notificationName, (CFStringRef)GROWL_NOTIFICATION_CLICKED);
212                         CFNotificationCenterAddObserver(distCenter, "openafsgrowl", notificationDismissed, notificationName, NULL, CFNotificationSuspensionBehaviorCoalesce);
213                         CFStringReplaceAll(notificationName, applicationName1);
214                         CFStringAppend(notificationName, (CFStringRef)GROWL_NOTIFICATION_TIMED_OUT);
215                         CFNotificationCenterAddObserver(distCenter, "openafsgrowl", notificationDismissed, notificationName, NULL, CFNotificationSuspensionBehaviorCoalesce);
216                         CFRelease(notificationName);
217                 }
218         }
219         struct sockaddr_in sin = { .sin_family = AF_INET, .sin_port = htons(VMON_SOCKET), .sin_addr.s_addr = INADDR_ANY };
220         CFDataRef theAddress = CFDataCreateWithBytesNoCopy(NULL, (UInt8 *)&sin, sizeof(struct sockaddr_in), kCFAllocatorNull);
221         CFSocketSignature MarinerSignature = { PF_INET, SOCK_DGRAM, IPPROTO_UDP, theAddress };
222         struct GrowlCBContext growlContext = {distCenter, registerInfo, icon};
223         CFSocketContext MarinerSocketContext = {0, (void *)&growlContext, NULL, NULL, NULL };
224         
225         CFSocketRef MarinerSocket = CFSocketCreateWithSocketSignature(kCFAllocatorDefault, &MarinerSignature, kCFSocketReadCallBack, &MySocketReadCallBack, &MarinerSocketContext);
226
227         if (!MarinerSocket)
228                 goto fail;
229         
230         CFSocketSetSocketFlags(MarinerSocket, kCFSocketCloseOnInvalidate|kCFSocketAutomaticallyReenableReadCallBack);
231         
232         CFRunLoopSourceRef MarinerRunLoopSource = CFSocketCreateRunLoopSource(NULL, MarinerSocket, 0);
233         if (!MarinerRunLoopSource)
234                 goto fail;
235
236         CFRunLoopAddSource(CFRunLoopGetCurrent(), MarinerRunLoopSource, kCFRunLoopCommonModes);
237
238         /* Run the run loop until it is manually cancelled */
239         CFRunLoopRun();
240
241         CFRelease(registerInfo);
242         CFRelease(MarinerRunLoopSource);
243         CFSocketInvalidate(MarinerSocket);
244         CFRelease(MarinerSocket);
245 fail:
246         /* CFRelease(notificationInfo); */
247         [pool release];
248
249         return code;
250 }