Line data Source code
1 0 : /*
2 :
3 : OOLogOutputHandler.m
4 : By Jens Ayton
5 :
6 :
7 : Copyright (C) 2007-2013 Jens Ayton and contributors
8 :
9 : Permission is hereby granted, free of charge, to any person obtaining a copy
10 : of this software and associated documentation files (the "Software"), to deal
11 : in the Software without restriction, including without limitation the rights
12 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 : copies of the Software, and to permit persons to whom the Software is
14 : furnished to do so, subject to the following conditions:
15 :
16 : The above copyright notice and this permission notice shall be included in all
17 : copies or substantial portions of the Software.
18 :
19 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25 : SOFTWARE.
26 :
27 : */
28 :
29 :
30 0 : #define OOLOG_POISON_NSLOG 0
31 :
32 : #import "OOLogOutputHandler.h"
33 : #import "OOLogging.h"
34 : #import "OOAsyncQueue.h"
35 : #include <stdlib.h>
36 : #include <stdio.h>
37 : #import "NSThreadOOExtensions.h"
38 : #import "NSFileManagerOOExtensions.h"
39 :
40 :
41 : #undef NSLog // We need to be able to call the real NSLog.
42 :
43 :
44 : #if OOLITE_MAC_OS_X
45 :
46 : #include <dlfcn.h>
47 :
48 : #ifndef NDEBUG
49 :
50 0 : #define SET_CRASH_REPORTER_INFO 1
51 :
52 : // Function to set "Application Specific Information" field in crash reporter log in Leopard.
53 : // Extremely unsupported, so not used in release builds.
54 : static void InitCrashReporterInfo(void);
55 : static void SetCrashReporterInfo(const char *info);
56 0 : static BOOL sCrashReporterInfoAvailable = NO;
57 :
58 : #endif
59 :
60 :
61 0 : typedef void (*LogCStringFunctionProc)(const char *string, unsigned length, BOOL withSyslogBanner);
62 0 : typedef LogCStringFunctionProc (*LogCStringFunctionGetterProc)(void);
63 0 : typedef void (*LogCStringFunctionSetterProc)(LogCStringFunctionProc);
64 :
65 0 : static LogCStringFunctionGetterProc _NSLogCStringFunction = NULL;
66 0 : static LogCStringFunctionSetterProc _NSSetLogCStringFunction = NULL;
67 :
68 : static void LoadLogCStringFunctions(void);
69 : static void OONSLogCStringFunction(const char *string, unsigned length, BOOL withSyslogBanner);
70 :
71 : static NSString *GetAppName(void);
72 :
73 0 : static LogCStringFunctionProc sDefaultLogCStringFunction = NULL;
74 :
75 : #elif OOLITE_GNUSTEP
76 :
77 : static void OONSLogPrintfHandler(NSString *message);
78 :
79 : #else
80 : #error Unknown platform!
81 : #endif
82 :
83 : static BOOL DirectoryExistCreatingIfNecessary(NSString *path);
84 :
85 :
86 0 : #define kFlushInterval 2.0 // Lower bound on interval between explicit log file flushes.
87 :
88 :
89 0 : @interface OOAsyncLogger: NSObject
90 : {
91 : @private
92 0 : OOAsyncQueue *messageQueue;
93 0 : NSConditionLock *threadStateMonitor;
94 0 : NSFileHandle *logFile;
95 0 : NSTimer *flushTimer;
96 : }
97 :
98 0 : - (void)asyncLogMessage:(NSString *)message;
99 0 : - (void)endLogging;
100 :
101 0 : - (void)changeFile;
102 :
103 : // Internal
104 0 : - (BOOL)startLogging;
105 0 : - (void)loggerThread;
106 0 : - (void)flushLog;
107 :
108 : @end
109 :
110 :
111 0 : static BOOL sInited = NO;
112 0 : static BOOL sWriteToStderr = YES;
113 0 : static BOOL sWriteToStdout = NO;
114 0 : static BOOL sSaturated = NO;
115 0 : static OOAsyncLogger *sLogger = nil;
116 0 : static NSString *sLogFileName = @"Latest.log";
117 :
118 :
119 0 : void OOLogOutputHandlerInit(void)
120 : {
121 : if (sInited) return;
122 :
123 : #if SET_CRASH_REPORTER_INFO
124 : InitCrashReporterInfo();
125 : #endif
126 :
127 : sLogger = [[OOAsyncLogger alloc] init];
128 : sInited = YES;
129 :
130 : if (sLogger != nil)
131 : {
132 : sWriteToStderr = [[NSUserDefaults standardUserDefaults] boolForKey:@"logging-echo-to-stderr"];
133 : }
134 : else
135 : {
136 : sWriteToStderr = YES;
137 : }
138 :
139 : #if OOLITE_MAC_OS_X
140 : LoadLogCStringFunctions();
141 : if (_NSSetLogCStringFunction != NULL)
142 : {
143 : sDefaultLogCStringFunction = _NSLogCStringFunction();
144 : _NSSetLogCStringFunction(OONSLogCStringFunction);
145 : }
146 : else
147 : {
148 : OOLog(@"logging.nsLogFilter.install.failed", @"Failed to install NSLog() filter; system messages will not be logged in log file.");
149 : }
150 : #elif GNUSTEP
151 : NSRecursiveLock *lock = GSLogLock();
152 : [lock lock];
153 : _NSLog_printf_handler = OONSLogPrintfHandler;
154 : [lock unlock];
155 : #endif
156 :
157 : atexit(OOLogOutputHandlerClose);
158 : }
159 :
160 :
161 0 : void OOLogOutputHandlerClose(void)
162 : {
163 : if (sInited)
164 : {
165 : sWriteToStderr = YES;
166 : sInited = NO;
167 :
168 : [sLogger endLogging];
169 : DESTROY(sLogger);
170 :
171 : #if OOLITE_MAC_OS_X
172 : if (sDefaultLogCStringFunction != NULL && _NSSetLogCStringFunction != NULL)
173 : {
174 : _NSSetLogCStringFunction(sDefaultLogCStringFunction);
175 : sDefaultLogCStringFunction = NULL;
176 : }
177 : #elif GNUSTEP
178 : NSRecursiveLock *lock = GSLogLock();
179 : [lock lock];
180 : _NSLog_printf_handler = NULL;
181 : [lock unlock];
182 : #endif
183 : }
184 : }
185 :
186 0 : void OOLogOutputHandlerStartLoggingToStdout()
187 : {
188 : sWriteToStdout = true;
189 : }
190 0 : void OOLogOutputHandlerStopLoggingToStdout()
191 : {
192 : sWriteToStdout = false;
193 : }
194 :
195 0 : void OOLogOutputHandlerPrint(NSString *string)
196 : {
197 : if (sInited && sLogger != nil && !sWriteToStdout) [sLogger asyncLogMessage:string];
198 :
199 : BOOL doCStringStuff = sWriteToStderr || sWriteToStdout;
200 : #if SET_CRASH_REPORTER_INFO
201 : doCStringStuff = doCStringStuff || sCrashReporterInfoAvailable;
202 : #endif
203 :
204 : if (doCStringStuff)
205 : {
206 : const char *cStr = [[string stringByAppendingString:@"\n"] UTF8String];
207 : if (sWriteToStdout)
208 : fputs(cStr, stdout);
209 : else if (sWriteToStderr)
210 : fputs(cStr, stderr);
211 :
212 : #if SET_CRASH_REPORTER_INFO
213 : if (sCrashReporterInfoAvailable) SetCrashReporterInfo(cStr);
214 : #endif
215 : }
216 :
217 : }
218 :
219 :
220 0 : NSString *OOLogHandlerGetLogPath(void)
221 : {
222 : return [OOLogHandlerGetLogBasePath() stringByAppendingPathComponent:sLogFileName];
223 : }
224 :
225 :
226 0 : void OOLogOutputHandlerChangeLogFile(NSString *newLogName)
227 : {
228 : if (![sLogFileName isEqual:newLogName])
229 : {
230 : sLogFileName = [newLogName copy];
231 : [sLogger changeFile];
232 : }
233 : }
234 :
235 :
236 0 : enum
237 : {
238 : kConditionReadyToDealloc = 1,
239 : kConditionWorking
240 : };
241 :
242 :
243 : @implementation OOAsyncLogger
244 :
245 0 : - (id)init
246 : {
247 : BOOL OK = YES;
248 : NSString *logPath = nil;
249 : NSString *oldPath = nil;
250 : NSFileManager *fmgr = nil;
251 :
252 : self = [super init];
253 : if (self == nil) OK = NO;
254 :
255 : if (OK)
256 : {
257 : fmgr = [NSFileManager defaultManager];
258 : logPath = OOLogHandlerGetLogPath();
259 :
260 : // If there is an existing file, move it to Previous.log.
261 : if ([fmgr fileExistsAtPath:logPath])
262 : {
263 : oldPath = [OOLogHandlerGetLogBasePath() stringByAppendingPathComponent:@"Previous.log"];
264 : [fmgr oo_removeItemAtPath:oldPath];
265 : if (![fmgr oo_moveItemAtPath:logPath toPath:oldPath])
266 : {
267 : if (![fmgr oo_removeItemAtPath:logPath])
268 : {
269 : NSLog(@"Log setup: could not move or delete existing log at %@, will log to stdout instead.", logPath);
270 : OK = NO;
271 : }
272 : }
273 : }
274 : }
275 :
276 : if (OK) OK = [self startLogging];
277 :
278 : if (!OK) DESTROY(self);
279 :
280 : return self;
281 : }
282 :
283 :
284 0 : - (void)dealloc
285 : {
286 : DESTROY(messageQueue);
287 : DESTROY(threadStateMonitor);
288 : DESTROY(logFile);
289 : // We don't own a reference to flushTimer.
290 :
291 : [super dealloc];
292 : }
293 :
294 :
295 : - (BOOL)startLogging
296 : {
297 : BOOL OK = YES;
298 : NSString *logPath = nil;
299 : NSFileManager *fmgr = nil;
300 :
301 : fmgr = [NSFileManager defaultManager];
302 :
303 : if (OK)
304 : {
305 : messageQueue = [[OOAsyncQueue alloc] init];
306 : if (messageQueue == nil) OK = NO;
307 : }
308 :
309 : if (OK)
310 : {
311 : // set up threadStateMonitor -- used as a binary semaphore of sorts to check when the worker thread starts and stops.
312 : threadStateMonitor = [[NSConditionLock alloc] initWithCondition:kConditionReadyToDealloc];
313 : if (threadStateMonitor == nil) OK = NO;
314 : [threadStateMonitor setName:@"OOLogOutputHandler thread state monitor"];
315 : }
316 :
317 : if (OK)
318 : {
319 : // Create work thread to actually handle messages.
320 : // This needs to be done early to avoid messy state if something goes wrong.
321 : [NSThread detachNewThreadSelector:@selector(loggerThread) toTarget:self withObject:nil];
322 : // Wait for it to start.
323 : if (![threadStateMonitor lockWhenCondition:kConditionWorking beforeDate:[NSDate dateWithTimeIntervalSinceNow:5.0]])
324 : {
325 : // If it doesn't signal a start within five seconds, assume something's wrong.
326 : // Send kill signal, just in case it comes to life...
327 : [messageQueue enqueue:@"die"];
328 : // ...and stop -dealloc from waiting for thread death
329 : [threadStateMonitor release];
330 : threadStateMonitor = nil;
331 : OK = NO;
332 : }
333 : [threadStateMonitor unlockWithCondition:kConditionWorking];
334 : }
335 :
336 : if (OK)
337 : {
338 : logPath = OOLogHandlerGetLogPath();
339 : OK = (logPath != nil);
340 : }
341 :
342 : if (OK)
343 : {
344 : // Create shiny new log file
345 : OK = [fmgr createFileAtPath:logPath contents:nil attributes:nil];
346 : if (OK)
347 : {
348 : logFile = [[NSFileHandle fileHandleForWritingAtPath:logPath] retain];
349 : OK = (logFile != nil);
350 : }
351 : if (!OK)
352 : {
353 : NSLog(@"Log setup: could not open log at %@, will log to stdout instead.", logPath);
354 : OK = NO;
355 : }
356 : }
357 :
358 : return OK;
359 : }
360 :
361 :
362 : - (void)endLogging
363 : {
364 : NSString *postamble = nil;
365 :
366 : if (messageQueue != nil && threadStateMonitor != nil)
367 : {
368 : // We're fully inited; write postamble, wait for worker thread to terminate cleanly, and close file.
369 : postamble = [NSString stringWithFormat:@"\nClosing log at %@.", [NSDate date]];
370 : [self asyncLogMessage:postamble];
371 : [messageQueue enqueue:@"die"]; // Kill message
372 : [threadStateMonitor lockWhenCondition:kConditionReadyToDealloc];
373 : [threadStateMonitor unlock];
374 :
375 : [logFile closeFile];
376 : }
377 : }
378 :
379 :
380 : - (void)changeFile
381 : {
382 : [self endLogging];
383 : if (![self startLogging]) sWriteToStderr = YES;
384 : }
385 :
386 :
387 : - (void)asyncLogMessage:(NSString *)message
388 : {
389 : // Don't log of saturated flag is set.
390 : if (sSaturated) return;
391 :
392 : if (message != nil)
393 : {
394 : message = [message stringByAppendingString:@"\n"];
395 :
396 : #if OOLITE_WINDOWS
397 : // Convert Unix line endings to Windows ones.
398 : NSArray *messageComponents = [message componentsSeparatedByString:@"\n"];
399 : message = [messageComponents componentsJoinedByString:@"\r\n"];
400 : #endif
401 :
402 : [messageQueue enqueue:[message dataUsingEncoding:NSUTF8StringEncoding]];
403 :
404 : if (flushTimer == nil)
405 : {
406 : // No pending flush
407 : flushTimer = [NSTimer scheduledTimerWithTimeInterval:kFlushInterval target:self selector:@selector(flushLog) userInfo:nil repeats:NO];
408 : }
409 : }
410 : }
411 :
412 :
413 : - (void)flushLog
414 : {
415 : flushTimer = nil;
416 : [messageQueue enqueue:@"flush"];
417 : }
418 :
419 :
420 : - (void)loggerThread
421 : {
422 : id message = nil;
423 : NSAutoreleasePool *rootPool = nil, *pool = nil;
424 : NSUInteger size = 0;
425 :
426 : rootPool = [[NSAutoreleasePool alloc] init];
427 : [NSThread ooSetCurrentThreadName:@"OOLogOutputHandler logging thread"];
428 :
429 : // Signal readiness
430 : [messageQueue retain];
431 : [threadStateMonitor lock];
432 : [threadStateMonitor unlockWithCondition:kConditionWorking];
433 :
434 : @try
435 : {
436 : for (;;)
437 : {
438 : pool = [[NSAutoreleasePool alloc] init];
439 :
440 : message = [messageQueue dequeue];
441 :
442 : if (!sSaturated && [message isKindOfClass:[NSData class]])
443 : {
444 : size += [message length];
445 : if (size > 1 << 30) // 1 GiB
446 : {
447 : sSaturated = YES;
448 : #if OOLITE_WINDOWS
449 : message = @"\r\n\r\n\r\n***** LOG TRUNCATED DUE TO EXCESSIVE LENGTH *****\r\n";
450 : #else
451 : message = @"\n\n\n***** LOG TRUNCATED DUE TO EXCESSIVE LENGTH *****\n";
452 : #endif
453 : message = [message dataUsingEncoding:NSUTF8StringEncoding];
454 : }
455 :
456 : [logFile writeData:message];
457 : }
458 : else if ([message isEqual:@"flush"])
459 : {
460 : [logFile synchronizeFile];
461 : }
462 : else if ([message isEqual:@"die"])
463 : {
464 : break;
465 : }
466 :
467 : [pool release];
468 : }
469 : }
470 : @catch (NSException *exception) {}
471 : [pool release];
472 :
473 : // Clean up; after this, ivars are out of bounds.
474 : [messageQueue release];
475 : [threadStateMonitor lock];
476 : [threadStateMonitor unlockWithCondition:kConditionReadyToDealloc];
477 :
478 : [rootPool release];
479 : }
480 :
481 : @end
482 :
483 :
484 : #if OOLITE_MAC_OS_X
485 :
486 : /* LoadLogCStringFunctions()
487 :
488 : We wish to make NSLogv() call our custom function OONSLogCStringFunction()
489 : rather than printing to stdout, by calling _NSSetLogCStringFunction().
490 : Additionally, in order to close the logger cleanly, we wish to be able to
491 : restore the standard logger, which requires us to call
492 : _NSLogCStringFunction(). These functions are private.
493 : _NSLogCStringFunction() is undocumented. _NSSetLogCStringFunction() is
494 : documented at http://docs.info.apple.com/article.html?artnum=70081 ,
495 : with the warning:
496 :
497 : Be aware that this code references private APIs; this is an
498 : unsupported workaround and users should use these instructions at
499 : their own risk. Apple will not guarantee or provide support for
500 : this procedure.
501 :
502 : The approach taken here is to load the functions dynamically. This makes
503 : us safe in the case of Apple removing the functions. In the unlikely event
504 : that they change the functions' paramters without renaming them, we would
505 : have a problem.
506 : */
507 0 : static void LoadLogCStringFunctions(void)
508 : {
509 : CFBundleRef foundationBundle = NULL;
510 : LogCStringFunctionGetterProc getter = NULL;
511 : LogCStringFunctionSetterProc setter = NULL;
512 :
513 : foundationBundle = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.Foundation"));
514 : if (foundationBundle != NULL)
515 : {
516 : getter = CFBundleGetFunctionPointerForName(foundationBundle, CFSTR("_NSLogCStringFunction"));
517 : setter = CFBundleGetFunctionPointerForName(foundationBundle, CFSTR("_NSSetLogCStringFunction"));
518 :
519 : if (getter != NULL && setter != NULL)
520 : {
521 : _NSLogCStringFunction = getter;
522 : _NSSetLogCStringFunction = setter;
523 : }
524 : }
525 : }
526 :
527 :
528 0 : static void OONSLogCStringFunction(const char *string, unsigned length, BOOL withSyslogBanner)
529 : {
530 : if (OOLogWillDisplayMessagesInClass(@"system"))
531 : {
532 : OOLogWithFunctionFileAndLine(@"system", NULL, NULL, 0, @"%s", string);
533 : }
534 : }
535 :
536 : #elif OOLITE_GNUSTEP
537 :
538 : static void OONSLogPrintfHandler(NSString *message)
539 : {
540 : if (OOLogWillDisplayMessagesInClass(@"gnustep"))
541 : {
542 : OOLogWithFunctionFileAndLine(@"gnustep", NULL, NULL, 0, @"%@", message);
543 : }
544 : }
545 :
546 : #endif
547 :
548 :
549 0 : static BOOL DirectoryExistCreatingIfNecessary(NSString *path)
550 : {
551 : BOOL exists, directory;
552 : NSFileManager *fmgr = [NSFileManager defaultManager];
553 :
554 : exists = [fmgr fileExistsAtPath:path isDirectory:&directory];
555 :
556 : if (exists && !directory)
557 : {
558 : NSLog(@"Log setup: expected %@ to be a folder, but it is a file.", path);
559 : return NO;
560 : }
561 : if (!exists)
562 : {
563 : if (![fmgr oo_createDirectoryAtPath:path attributes:nil])
564 : {
565 : NSLog(@"Log setup: could not create folder %@.", path);
566 : return NO;
567 : }
568 : }
569 :
570 : return YES;
571 : }
572 :
573 :
574 : #if OOLITE_MAC_OS_X
575 :
576 : static void ExcludeFromTimeMachine(NSString *path);
577 :
578 :
579 0 : NSString *OOLogHandlerGetLogBasePath(void)
580 : {
581 : static NSString *basePath = nil;
582 :
583 : if (basePath == nil)
584 : {
585 : // ~/Library
586 : basePath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
587 :
588 : // ~/Library/Logs
589 : basePath = [basePath stringByAppendingPathComponent:@"Logs"];
590 : if (!DirectoryExistCreatingIfNecessary(basePath)) return nil;
591 :
592 : // ~/Library/Logs/Oolite
593 : basePath = [basePath stringByAppendingPathComponent:GetAppName()];
594 : if (!DirectoryExistCreatingIfNecessary(basePath)) return nil;
595 : ExcludeFromTimeMachine(basePath);
596 :
597 : [basePath retain];
598 : }
599 :
600 : return basePath;
601 : }
602 :
603 :
604 0 : static void ExcludeFromTimeMachine(NSString *path)
605 : {
606 : OSStatus (*CSBackupSetItemExcluded)(NSURL *item, Boolean exclude, Boolean excludeByPath) = NULL;
607 : CFBundleRef carbonCoreBundle = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.CoreServices.CarbonCore"));
608 : if (carbonCoreBundle)
609 : {
610 : CSBackupSetItemExcluded = CFBundleGetFunctionPointerForName(carbonCoreBundle, CFSTR("CSBackupSetItemExcluded"));
611 : if (CSBackupSetItemExcluded != NULL)
612 : {
613 : (void)CSBackupSetItemExcluded([NSURL fileURLWithPath:path], YES, NO);
614 : }
615 : }
616 : }
617 :
618 :
619 0 : static NSString *GetAppName(void)
620 : {
621 : static NSString *appName = nil;
622 : NSBundle *bundle = nil;
623 :
624 : if (appName == nil)
625 : {
626 : bundle = [NSBundle mainBundle];
627 : appName = [bundle objectForInfoDictionaryKey:@"CFBundleName"];
628 : if (appName == nil) appName = [bundle bundleIdentifier];
629 : if (appName == nil) appName = @"<unknown application>";
630 : [appName retain];
631 : }
632 :
633 : return appName;
634 : }
635 :
636 : #elif OOLITE_LINUX
637 :
638 : NSString *OOLogHandlerGetLogBasePath(void)
639 : {
640 : static NSString *basePath = nil;
641 :
642 : if (basePath == nil)
643 : {
644 : // ~
645 : basePath = NSHomeDirectory();
646 :
647 : // ~/.Oolite
648 : basePath = [basePath stringByAppendingPathComponent:@".Oolite"];
649 : if (!DirectoryExistCreatingIfNecessary(basePath)) return nil;
650 :
651 : // ~/.Oolite/Logs
652 : basePath = [basePath stringByAppendingPathComponent:@"Logs"];
653 : if (!DirectoryExistCreatingIfNecessary(basePath)) return nil;
654 :
655 : [basePath retain];
656 : }
657 :
658 : return basePath;
659 : }
660 :
661 : #elif OOLITE_WINDOWS
662 :
663 : NSString *OOLogHandlerGetLogBasePath(void)
664 : {
665 : static NSString *basePath = nil;
666 :
667 : if (basePath == nil)
668 : {
669 : // <Install path>\Oolite
670 : basePath = NSHomeDirectory();
671 :
672 : // <Install path>\Oolite\Logs
673 : basePath = [basePath stringByAppendingPathComponent:@"Logs"];
674 : if (!DirectoryExistCreatingIfNecessary(basePath)) return nil;
675 :
676 : [basePath retain];
677 : }
678 :
679 : return basePath;
680 : }
681 :
682 : #endif
683 :
684 :
685 : #if SET_CRASH_REPORTER_INFO
686 :
687 0 : static char **sCrashReporterInfo = NULL;
688 0 : static char *sOldCrashReporterInfo = NULL;
689 0 : static NSLock *sCrashReporterInfoLock = nil;
690 :
691 : // Evil hackery based on http://www.allocinit.net/blog/2008/01/04/application-specific-information-in-leopard-crash-reports/
692 0 : static void InitCrashReporterInfo(void)
693 : {
694 : sCrashReporterInfo = dlsym(RTLD_DEFAULT, "__crashreporter_info__");
695 : if (sCrashReporterInfo != NULL)
696 : {
697 : sCrashReporterInfoLock = [[NSLock alloc] init];
698 : if (sCrashReporterInfoLock != nil)
699 : {
700 : sCrashReporterInfoAvailable = YES;
701 : }
702 : else
703 : {
704 : sCrashReporterInfo = NULL;
705 : }
706 : }
707 : }
708 :
709 0 : static void SetCrashReporterInfo(const char *info)
710 : {
711 : char *copy = NULL, *old = NULL;
712 :
713 : /* Don't do anything if setup failed or the string is NULL or empty.
714 : (The NULL and empty checks may not be desirable in other uses.)
715 : */
716 : if (!sCrashReporterInfoAvailable || info == NULL || *info == '\0') return;
717 :
718 : // Copy the string, which we assume to be dynamic...
719 : copy = strdup(info);
720 : if (copy == NULL) return;
721 :
722 : /* ...and swap it in.
723 : Note that we keep a separate pointer to the old value, in case
724 : something else overwrites __crashreporter_info__.
725 : */
726 : [sCrashReporterInfoLock lock];
727 : *sCrashReporterInfo = copy;
728 : old = sOldCrashReporterInfo;
729 : sOldCrashReporterInfo = copy;
730 : [sCrashReporterInfoLock unlock];
731 :
732 : // Delete our old string.
733 : if (old != NULL) free(old);
734 : }
735 :
736 : #endif
|