]>
Commit | Line | Data |
---|---|---|
ebf6f434 A |
1 | // |
2 | // Snapshot.cpp | |
3 | // ld64 | |
4 | // | |
5 | // Created by Josh Behnke on 8/25/11. | |
6 | // Copyright (c) 2011 Apple Inc. All rights reserved. | |
7 | // | |
8 | ||
9 | #include <string.h> | |
10 | #include <unistd.h> | |
11 | #include <stdio.h> | |
12 | #include <limits.h> | |
13 | #include <fcntl.h> | |
14 | #include <ctype.h> | |
15 | #include <sys/stat.h> | |
16 | #include <libgen.h> | |
17 | #include <time.h> | |
18 | #include <Block.h> | |
19 | ||
20 | #include "Snapshot.h" | |
21 | #include "Options.h" | |
22 | ||
23 | #include "compile_stubs.h" | |
24 | ||
25 | //#define STORE_PID_IN_SNAPSHOT 1 | |
26 | ||
27 | // Well known snapshot file/directory names. These appear in the root of the snapshot. | |
28 | // They are collected together here to make managing the namespace easier. | |
29 | static const char *frameworksString = "frameworks"; // directory containing framework stubs (mach-o files) | |
30 | static const char *dylibsString = "dylibs"; // directory containing dylib stubs (mach-o files) | |
31 | static const char *archiveFilesString = "archive_files"; // directory containing .a files | |
32 | static const char *origCommandLineString = "orig_command_line"; // text file containing the original command line | |
33 | static const char *linkCommandString = "link_command"; // text file containing the snapshot equivalent command line | |
34 | static const char *dataFilesString = "data_files"; // arbitrary data files referenced on the command line | |
35 | static const char *objectsString = "objects"; // directory containing object files | |
36 | static const char *frameworkStubsString = "framework_stubs"; // directory containing framework stub info (text files) | |
37 | static const char *dylibStubsString = "dylib_stubs"; // directory containing dylib stub info (text files) | |
38 | static const char *assertFileString = "assert_info"; // text file containing assertion failure logs | |
39 | static const char *compileFileString = "compile_stubs"; // text file containing compile_stubs script | |
40 | ||
41 | Snapshot *Snapshot::globalSnapshot = NULL; | |
42 | ||
43 | Snapshot::Snapshot() : fRecordArgs(false), fRecordObjects(false), fRecordDylibSymbols(false), fRecordArchiveFiles(false), fRecordUmbrellaFiles(false), fRecordDataFiles(false), fFrameworkArgAdded(false), fSnapshotLocation(NULL), fSnapshotName(NULL), fRootDir(NULL), fFilelistFile(-1), fCopiedArchives(NULL) | |
44 | { | |
45 | if (globalSnapshot != NULL) | |
46 | throw "only one snapshot supported"; | |
47 | globalSnapshot = this; | |
48 | } | |
49 | ||
50 | ||
51 | Snapshot::~Snapshot() | |
52 | { | |
53 | // Lots of things leak under the assumption the linker is about to exit. | |
54 | } | |
55 | ||
56 | ||
57 | void Snapshot::setSnapshotPath(const char *path) | |
58 | { | |
59 | if (fRootDir == NULL) { | |
60 | fSnapshotLocation = strdup(path); | |
61 | } | |
62 | } | |
63 | ||
64 | ||
65 | void Snapshot::setSnapshotMode(SnapshotMode mode) | |
66 | { | |
67 | if (fRootDir == NULL) { | |
68 | fRecordArgs = false; | |
69 | fRecordObjects = false; | |
70 | fRecordDylibSymbols = false; | |
71 | fRecordArchiveFiles = false; | |
72 | fRecordUmbrellaFiles = false; | |
73 | fRecordDataFiles = false; | |
74 | ||
75 | switch (mode) { | |
76 | case SNAPSHOT_DISABLED: | |
77 | break; | |
78 | case SNAPSHOT_DEBUG: | |
79 | fRecordArgs = fRecordObjects = fRecordDylibSymbols = fRecordArchiveFiles = fRecordUmbrellaFiles = fRecordDataFiles = true; | |
80 | break; | |
81 | default: | |
82 | break; | |
83 | } | |
84 | } | |
85 | } | |
86 | ||
87 | void Snapshot::setSnapshotName(const char *path) | |
88 | { | |
89 | if (fRootDir == NULL) { | |
90 | const char *base = basename((char *)path); | |
91 | time_t now = time(NULL); | |
92 | struct tm t; | |
93 | localtime_r(&now, &t); | |
94 | char buf[PATH_MAX]; | |
95 | snprintf(buf, sizeof(buf)-1, "%s-%4.4d-%2.2d-%2.2d-%2.2d%2.2d%2.2d.ld-snapshot", base, t.tm_year+1900, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec); | |
96 | fSnapshotName = strdup(buf); | |
97 | } | |
98 | } | |
99 | ||
100 | ||
101 | // Construct a path string in the snapshot. | |
102 | // subdir - an optional subdirectory name | |
103 | // file - the file name | |
104 | void Snapshot::buildPath(char *buf, const char *subdir, const char *file) | |
105 | { | |
106 | if (fRootDir == NULL) | |
107 | throw "snapshot not created"; | |
108 | ||
109 | strcpy(buf, fRootDir); | |
110 | strcat(buf, "/"); | |
111 | if (subdir) { | |
112 | strcat(buf, subdir); | |
113 | // implicitly create the subdirectory | |
114 | mkdir(buf, S_IRUSR|S_IWUSR|S_IXUSR); | |
115 | strcat(buf, "/"); | |
116 | } | |
117 | if (file != NULL) | |
118 | strcat(buf, basename((char *)file)); | |
119 | } | |
120 | ||
121 | ||
122 | // Construct a unique path string in the snapshot. If a path collision is detected then uniquing | |
123 | // is accomplished by appending a counter to the path until there is no preexisting file. | |
124 | // subdir - an optional subdirectory name | |
125 | // file - the file name | |
126 | void Snapshot::buildUniquePath(char *buf, const char *subdir, const char *file) | |
127 | { | |
128 | buildPath(buf, subdir, file); | |
129 | struct stat st; | |
130 | if (stat(buf, &st)==0) { | |
131 | // make it unique | |
132 | int counter=1; | |
133 | char *number = strrchr(buf, 0); | |
134 | number[0]='-'; | |
135 | number++; | |
136 | do { | |
137 | sprintf(number, "%d", counter++); | |
138 | } while (stat(buf, &st) == 0); | |
139 | } | |
140 | } | |
141 | ||
142 | ||
143 | // Copy a file to the snapshot. | |
144 | // sourcePath is the original file | |
145 | // subdir is an optional subdirectory in the snapshot | |
146 | // path is an optional out parameter containing the final uniqued path in the snapshot | |
147 | // where the file was copied | |
148 | void Snapshot::copyFileToSnapshot(const char *sourcePath, const char *subdir, char *path) | |
149 | { | |
150 | const int copyBufSize=(1<<14); // 16kb buffer | |
151 | static void *copyBuf = NULL; | |
152 | if (copyBuf == NULL) | |
153 | copyBuf = malloc(copyBufSize); | |
154 | ||
155 | char *file=basename((char *)sourcePath); | |
156 | char buf[PATH_MAX]; | |
157 | if (path == NULL) path = buf; | |
158 | buildUniquePath(path, subdir, file); | |
159 | int out_fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR); | |
160 | int in_fd = open(sourcePath, O_RDONLY); | |
161 | int len; | |
162 | if (out_fd != -1 && in_fd != -1) { | |
163 | do { | |
164 | len = read(in_fd, copyBuf, copyBufSize); | |
165 | if (len > 0) write(out_fd, copyBuf, len); | |
166 | } while (len == copyBufSize); | |
167 | } | |
168 | close(in_fd); | |
169 | close(out_fd); | |
170 | } | |
171 | ||
172 | ||
173 | // Create the snapshot root directory. | |
174 | void Snapshot::createSnapshot() | |
175 | { | |
176 | if (fRootDir == NULL) { | |
177 | // provide default name and location | |
178 | if (fSnapshotLocation == NULL) | |
179 | fSnapshotLocation = "/tmp"; | |
180 | if (fSnapshotName == NULL) { | |
181 | setSnapshotName("ld_snapshot"); | |
182 | } | |
183 | ||
184 | char buf[PATH_MAX]; | |
185 | fRootDir = (char *)fSnapshotLocation; | |
186 | buildUniquePath(buf, NULL, fSnapshotName); | |
187 | fRootDir = strdup(buf); | |
188 | if (mkdir(fRootDir, S_IRUSR|S_IWUSR|S_IXUSR)!=0) { | |
189 | warning("unable to create link snapshot directory: %s", fRootDir); | |
190 | fRootDir = NULL; | |
191 | setSnapshotMode(SNAPSHOT_DISABLED); // don't try to write anything if we can't create snapshot dir | |
192 | } | |
193 | ||
194 | buildPath(buf, NULL, compileFileString); | |
195 | int compileScript = open(buf, O_WRONLY|O_CREAT|O_TRUNC, S_IXUSR|S_IRUSR|S_IWUSR); | |
196 | write(compileScript, compile_stubs, strlen(compile_stubs)); | |
197 | close(compileScript); | |
198 | ||
199 | SnapshotLog::iterator it; | |
200 | for (it = fLog.begin(); it != fLog.end(); it++) { | |
201 | void (^logItem)(void) = *it; | |
202 | logItem(); | |
203 | Block_release(logItem); | |
204 | } | |
205 | fLog.erase(fLog.begin(), fLog.end()); | |
206 | ||
207 | if (fRecordArgs) { | |
208 | writeCommandLine(fRawArgs, origCommandLineString, true); | |
209 | writeCommandLine(fArgs); | |
210 | } | |
211 | ||
212 | #if STORE_PID_IN_SNAPSHOT | |
213 | char path[PATH_MAX]; | |
214 | buildUniquePath(path, NULL, pidString); | |
215 | int pidfile = open(path, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR); | |
216 | char pid_buf[32]; | |
217 | sprintf(pid_buf, "%lu\n", (long unsigned)getpid()); | |
218 | write(pidfile, pid_buf, strlen(pid_buf)); | |
219 | write(pidfile, "\n", 1); | |
220 | close(pidfile); | |
221 | #endif | |
222 | ||
223 | } | |
224 | } | |
225 | ||
226 | ||
227 | // Write the current command line vector to filename. | |
228 | void Snapshot::writeCommandLine(StringVector &args, const char *filename, bool includeCWD) | |
229 | { | |
230 | if (!isLazy() && fRecordArgs) { | |
231 | // Figure out the file name and open it. | |
232 | if (filename == NULL) | |
233 | filename = linkCommandString; | |
234 | char path[PATH_MAX]; | |
235 | buildPath(path, NULL, filename); | |
236 | int argsFile = open(path, O_WRONLY|O_CREAT|O_TRUNC, S_IXUSR|S_IRUSR|S_IWUSR); | |
237 | FILE *argsStream = fdopen(argsFile, "w"); | |
238 | ||
239 | if (includeCWD) | |
240 | fprintf(argsStream, "cd %s\n", getcwd(path, sizeof(path))); | |
241 | ||
242 | // iterate to write args, quoting as needed | |
243 | StringVector::iterator it; | |
244 | for (it = args.begin(); it != args.end(); it++) { | |
245 | const char *arg = *it; | |
246 | bool needQuotes = false; | |
247 | for (const char *c = arg; *c != 0 && !needQuotes; c++) { | |
248 | if (isspace(*c)) | |
249 | needQuotes = true; | |
250 | } | |
251 | if (it != args.begin()) fprintf(argsStream, " "); | |
252 | if (needQuotes) fprintf(argsStream, "\""); | |
253 | fprintf(argsStream, "%s", arg); | |
254 | if (needQuotes) fprintf(argsStream, "\""); | |
255 | } | |
256 | fprintf(argsStream, "\n"); | |
257 | fclose(argsStream); | |
258 | } | |
259 | } | |
260 | ||
261 | ||
262 | // Store the command line args in the snapshot. | |
263 | void Snapshot::recordRawArgs(int argc, const char *argv[]) | |
264 | { | |
265 | // first store the original command line as-is | |
266 | for (int i=0; i<argc; i++) { | |
267 | fRawArgs.push_back(argv[i]); | |
268 | } | |
269 | fArgs.insert(fArgs.begin(), argv[0]); | |
270 | fArgs.insert(fArgs.begin()+1, "-Z"); // don't search standard paths when running in the snapshot | |
271 | } | |
272 | ||
273 | ||
274 | // Adds one or more args to the snapshot link command. | |
275 | // argIndex is the index in the original raw args vector to start adding args | |
276 | // argCount is the count of args to copy from the raw args vector | |
277 | // fileArg is the index relative to argIndex of a file arg. The file is copied into the | |
278 | // snapshot and the path is fixed up in the snapshot link command. (skipped if fileArg==-1) | |
279 | void Snapshot::addSnapshotLinkArg(int argIndex, int argCount, int fileArg) | |
280 | { | |
281 | if (fRootDir == NULL) { | |
282 | fLog.push_back(Block_copy(^{ this->addSnapshotLinkArg(argIndex, argCount, fileArg); })); | |
283 | } else { | |
284 | char buf[PATH_MAX]; | |
285 | const char *subdir = dataFilesString; | |
286 | for (int i=0, arg=argIndex; i<argCount && argIndex+1<(int)fRawArgs.size(); i++, arg++) { | |
287 | if (i != fileArg) { | |
288 | fArgs.push_back(fRawArgs[arg]); | |
289 | } else { | |
290 | if (fRecordDataFiles) { | |
291 | copyFileToSnapshot(fRawArgs[arg], subdir, buf); | |
292 | fArgs.push_back(strdup(snapshotRelativePath(buf))); | |
293 | } else { | |
294 | // if we don't copy the file then just record the original path | |
295 | fArgs.push_back(strdup(fRawArgs[arg])); | |
296 | } | |
297 | } | |
298 | } | |
299 | } | |
300 | } | |
301 | ||
302 | // Record the -arch string | |
303 | void Snapshot::recordArch(const char *arch) | |
304 | { | |
305 | // must be called after recordRawArgs() | |
306 | if (fRawArgs.size() == 0) | |
307 | throw "raw args not set"; | |
308 | ||
309 | // only need to store the arch explicitly if it is not mentioned on the command line | |
310 | bool archInArgs = false; | |
311 | StringVector::iterator it; | |
312 | for (it = fRawArgs.begin(); it != fRawArgs.end() && !archInArgs; it++) { | |
313 | const char *arg = *it; | |
314 | if (strcmp(arg, "-arch") == 0) | |
315 | archInArgs = true; | |
316 | } | |
317 | ||
318 | if (!archInArgs) { | |
319 | if (fRootDir == NULL) { | |
320 | fLog.push_back(Block_copy(^{ this->recordArch(arch); })); | |
321 | } else { | |
322 | char path_buf[PATH_MAX]; | |
323 | buildUniquePath(path_buf, NULL, "arch"); | |
324 | int fd=open(path_buf, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR); | |
325 | write(fd, arch, strlen(arch)); | |
326 | close(fd); | |
327 | } | |
328 | } | |
329 | } | |
330 | ||
331 | // Record an object file in the snapshot. | |
332 | // path - the object file's path | |
333 | // fileContent - a pointer to the object file content | |
334 | // fileLength - the buffer size of fileContent | |
335 | void Snapshot::recordObjectFile(const char *path) | |
336 | { | |
337 | if (fRootDir == NULL) { | |
338 | fLog.push_back(Block_copy(^{ this->recordObjectFile(path); })); | |
339 | } else { | |
340 | if (fRecordObjects) { | |
341 | char path_buf[PATH_MAX]; | |
342 | copyFileToSnapshot(path, objectsString, path_buf); | |
343 | ||
344 | // lazily open the filelist file | |
345 | if (fFilelistFile == -1) { | |
346 | char filelist_path[PATH_MAX]; | |
347 | buildUniquePath(filelist_path, objectsString, "filelist"); | |
348 | fFilelistFile = open(filelist_path, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR); | |
349 | fArgs.push_back("-filelist"); | |
350 | fArgs.push_back(strdup(snapshotRelativePath(filelist_path))); | |
351 | writeCommandLine(fArgs); | |
352 | } | |
353 | ||
354 | // record the snapshot path in the filelist | |
355 | const char *relative_path = snapshotRelativePath(path_buf); | |
356 | write(fFilelistFile, relative_path, strlen(relative_path)); | |
357 | write(fFilelistFile, "\n", 1); | |
358 | } | |
359 | } | |
360 | } | |
361 | ||
362 | void Snapshot::addFrameworkArg(const char *framework) | |
363 | { | |
364 | bool found=false; | |
365 | for (unsigned i=0; i<fArgs.size()-1; i++) { | |
366 | if (strcmp(fArgs[i], "-framework") == 0 && strcmp(fArgs[i+1], framework) == 0) | |
367 | found = true; | |
368 | } | |
369 | if (!found) { | |
370 | if (!fFrameworkArgAdded) { | |
371 | fFrameworkArgAdded = true; | |
372 | fArgs.push_back("-Fframeworks"); | |
373 | } | |
374 | fArgs.push_back("-framework"); | |
375 | fArgs.push_back(strdup(framework)); | |
376 | writeCommandLine(fArgs); | |
377 | } | |
378 | } | |
379 | ||
380 | void Snapshot::addDylibArg(const char *dylib) | |
381 | { | |
382 | bool found=false; | |
383 | for (unsigned i=0; i<fArgs.size()-1; i++) { | |
384 | if (strcmp(fArgs[i], dylib) == 0) | |
385 | found = true; | |
386 | } | |
387 | if (!found) { | |
388 | char buf[ARG_MAX]; | |
389 | sprintf(buf, "%s/%s", dylibsString, dylib); | |
390 | fArgs.push_back(strdup(buf)); | |
391 | writeCommandLine(fArgs); | |
392 | } | |
393 | } | |
394 | ||
395 | // Record a dylib symbol reference in the snapshot. | |
396 | // (References are not written to the snapshot until writeStubDylibs() is called.) | |
397 | void Snapshot::recordDylibSymbol(ld::dylib::File* dylibFile, const char *name) | |
398 | { | |
399 | if (fRootDir == NULL) { | |
400 | fLog.push_back(Block_copy(^{ this->recordDylibSymbol(dylibFile, name); })); | |
401 | } else { | |
402 | if (fRecordDylibSymbols) { | |
403 | // find the dylib in the table | |
404 | DylibMap::iterator it; | |
405 | const char *dylibPath = dylibFile->path(); | |
406 | it = fDylibSymbols.find(dylibPath); | |
407 | bool isFramework = (strstr(dylibPath, "framework") != NULL); | |
408 | int dylibFd; | |
409 | if (it == fDylibSymbols.end()) { | |
410 | // Didn't find a file descriptor for this dylib. Create one and add it to the dylib map. | |
411 | char path_buf[PATH_MAX]; | |
412 | buildUniquePath(path_buf, isFramework ? frameworkStubsString : dylibStubsString, dylibPath); | |
413 | dylibFd = open(path_buf, O_WRONLY|O_APPEND|O_CREAT, S_IRUSR|S_IWUSR); | |
414 | fDylibSymbols.insert(std::pair<const char *, int>(dylibPath, dylibFd)); | |
415 | char *base_name = strdup(basename(path_buf)); | |
416 | if (isFramework) { | |
417 | addFrameworkArg(base_name); | |
418 | } else { | |
419 | addDylibArg(base_name); | |
420 | } | |
421 | writeCommandLine(fArgs); | |
422 | } else { | |
423 | dylibFd = it->second; | |
424 | } | |
425 | // Record the symbol. | |
426 | ||
427 | bool isIdentifier = (name[0] == '_'); | |
428 | for (const char *c = name; *c != 0 && isIdentifier; c++) | |
429 | if (!isalnum(*c) && *c!='_') | |
430 | isIdentifier = false; | |
431 | const char *prefix = "void "; | |
432 | const char *weakAttr = "__attribute__ ((weak)) "; | |
433 | const char *suffix = "(void){}\n"; | |
434 | if (isIdentifier) { | |
435 | write(dylibFd, prefix, strlen(prefix)); | |
436 | if (dylibFile->hasWeakExternals() && dylibFile->hasWeakDefinition(name)) | |
437 | write(dylibFd, weakAttr, strlen(weakAttr)); | |
438 | if (*name == '_') name++; | |
439 | write(dylibFd, name, strlen(name)); | |
440 | write(dylibFd, suffix, strlen(suffix)); | |
441 | } else { | |
442 | static int symbolCounter = 0; | |
443 | char buf[64+strlen(name)]; | |
444 | sprintf(buf, "void s_%5.5d(void) __asm(\"%s\");\nvoid s_%5.5d(){}\n", symbolCounter, name, symbolCounter); | |
445 | write(dylibFd, buf, strlen(buf)); | |
446 | symbolCounter++; | |
447 | } | |
448 | } | |
449 | } | |
450 | } | |
451 | ||
452 | ||
453 | // Record a .a archive in the snapshot. | |
454 | void Snapshot::recordArchive(const char *archiveFile) | |
455 | { | |
456 | if (fRootDir == NULL) { | |
457 | const char *copy = strdup(archiveFile); | |
458 | fLog.push_back(Block_copy(^{ this->recordArchive(archiveFile); ::free((void *)copy); })); | |
459 | } else { | |
460 | if (fRecordArchiveFiles) { | |
461 | // lazily create a vector of .a files that have been added | |
462 | if (fCopiedArchives == NULL) { | |
463 | fCopiedArchives = new StringVector; | |
464 | } | |
465 | ||
466 | // See if we have already added this .a | |
467 | StringVector::iterator it; | |
468 | bool found = false; | |
469 | for (it = fCopiedArchives->begin(); it != fCopiedArchives->end() && !found; it++) { | |
470 | if (strcmp(archiveFile, *it) == 0) | |
471 | found = true; | |
472 | } | |
473 | ||
474 | // If this is a new .a then copy it to the snapshot and add it to the snapshot link command. | |
475 | if (!found) { | |
476 | char path[PATH_MAX]; | |
477 | fCopiedArchives->push_back(archiveFile); | |
478 | copyFileToSnapshot(archiveFile, archiveFilesString, path); | |
479 | fArgs.push_back(strdup(snapshotRelativePath(path))); | |
480 | writeCommandLine(fArgs); | |
481 | } | |
482 | } | |
483 | } | |
484 | } | |
485 | ||
486 | void Snapshot::recordSubUmbrella(const char *frameworkPath) | |
487 | { | |
488 | if (fRootDir == NULL) { | |
489 | const char *copy = strdup(frameworkPath); | |
490 | fLog.push_back(Block_copy(^{ this->recordSubUmbrella(copy); ::free((void *)copy); })); | |
491 | } else { | |
492 | if (fRecordUmbrellaFiles) { | |
493 | const char *framework = basename((char *)frameworkPath); | |
494 | char buf[PATH_MAX], wrapper[PATH_MAX]; | |
495 | strcpy(wrapper, frameworksString); | |
496 | buildPath(buf, wrapper, NULL); // ensure the frameworks directory exists | |
497 | strcat(wrapper, "/"); | |
498 | strcat(wrapper, framework); | |
499 | strcat(wrapper, ".framework"); | |
500 | copyFileToSnapshot(frameworkPath, wrapper); | |
501 | addFrameworkArg(framework); | |
502 | } | |
503 | } | |
504 | } | |
505 | ||
506 | void Snapshot::recordSubLibrary(const char *dylibPath) | |
507 | { | |
508 | if (fRootDir == NULL) { | |
509 | const char *copy = strdup(dylibPath); | |
510 | fLog.push_back(Block_copy(^{ this->recordSubLibrary(copy); ::free((void *)copy); })); | |
511 | } else { | |
512 | if (fRecordUmbrellaFiles) { | |
513 | copyFileToSnapshot(dylibPath, dylibsString); | |
514 | addDylibArg(basename((char *)dylibPath)); | |
515 | } | |
516 | } | |
517 | } | |
518 | ||
519 | void Snapshot::recordAssertionMessage(const char *fmt, ...) | |
520 | { | |
521 | char *msg; | |
522 | va_list args; | |
523 | va_start(args, fmt); | |
524 | vasprintf(&msg, fmt, args); | |
525 | va_end(args); | |
526 | if (msg != NULL) { | |
527 | if (fRootDir == NULL) { | |
528 | fLog.push_back(Block_copy(^{ this->recordAssertionMessage("%s", msg); free(msg); })); | |
529 | } else { | |
530 | char path[PATH_MAX]; | |
531 | buildPath(path, NULL, assertFileString); | |
532 | int log = open(path, O_WRONLY|O_APPEND|O_CREAT, S_IRUSR|S_IWUSR); | |
533 | write(log, msg, strlen(msg)); | |
534 | close(log); | |
535 | free(msg); | |
536 | } | |
537 | } | |
538 | } |