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