dyld-625.13.tar.gz
[apple/dyld.git] / dyld3 / shared-cache / dyld_shared_cache_builder.mm
1 /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*-
2  *
3  * Copyright (c) 2016 Apple Inc. All rights reserved.
4  *
5  * @APPLE_LICENSE_HEADER_START@
6  *
7  * This file contains Original Code and/or Modifications of Original Code
8  * as defined in and that are subject to the Apple Public Source License
9  * Version 2.0 (the 'License'). You may not use this file except in
10  * compliance with the License. Please obtain a copy of the License at
11  * http://www.opensource.apple.com/apsl/ and read it before using this
12  * file.
13  *
14  * The Original Code and all software distributed under the License are
15  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
16  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
17  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
19  * Please see the License for the specific language governing rights and
20  * limitations under the License.
21  *
22  * @APPLE_LICENSE_HEADER_END@
23  */
24
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <sys/mman.h>
28 #include <sys/resource.h>
29 #include <mach/mach.h>
30 #include <mach/mach_time.h>
31 #include <limits.h>
32 #include <stdarg.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <math.h>
36 #include <fcntl.h>
37 #include <dlfcn.h>
38 #include <signal.h>
39 #include <errno.h>
40 #include <sys/uio.h>
41 #include <unistd.h>
42 #include <sys/param.h>
43 #include <sys/sysctl.h>
44 #include <sys/resource.h>
45 #include <dirent.h>
46 #include <libgen.h>
47 #include <pthread.h>
48 #include <fts.h>
49
50 #include <vector>
51 #include <array>
52 #include <set>
53 #include <map>
54 #include <unordered_set>
55 #include <algorithm>
56
57 #include <spawn.h>
58
59 #include <Bom/Bom.h>
60
61 #include "Manifest.h"
62 #include "Diagnostics.h"
63 #include "DyldSharedCache.h"
64 #include "BuilderUtils.h"
65 #include "FileUtils.h"
66 #include "JSONWriter.h"
67 #include "StringUtils.h"
68 #include "mrm_shared_cache_builder.h"
69
70 #if !__has_feature(objc_arc)
71 #error The use of libdispatch in this files requires it to be compiled with ARC in order to avoid leaks
72 #endif
73
74 extern char** environ;
75
76 static dispatch_queue_t build_queue;
77
78 int runCommandAndWait(Diagnostics& diags, const char* args[])
79 {
80     pid_t pid;
81     int   status;
82     int   res = posix_spawn(&pid, args[0], nullptr, nullptr, (char**)args, environ);
83     if (res != 0)
84         diags.error("Failed to spawn %s: %s (%d)", args[0], strerror(res), res);
85
86     do {
87         res = waitpid(pid, &status, 0);
88     } while (res == -1 && errno == EINTR);
89     if (res != -1) {
90         if (WIFEXITED(status)) {
91             res = WEXITSTATUS(status);
92         } else {
93             res = -1;
94         }
95     }
96
97     return res;
98 }
99
100 void processRoots(Diagnostics& diags, std::set<std::string>& roots, const char *tempRootsDir)
101 {
102     std::set<std::string> processedRoots;
103     struct stat           sb;
104     int                   res = 0;
105     const char*           args[8];
106
107     for (const auto& root : roots) {
108         res = stat(root.c_str(), &sb);
109
110         if (res == 0 && S_ISDIR(sb.st_mode)) {
111             processedRoots.insert(root);
112             continue;
113         }
114
115         char tempRootDir[MAXPATHLEN];
116         strlcpy(tempRootDir, tempRootsDir, MAXPATHLEN);
117         strlcat(tempRootDir, "/XXXXXXXX", MAXPATHLEN);
118         mkdtemp(tempRootDir);
119
120         if (endsWith(root, ".cpio") || endsWith(root, ".cpio.gz") || endsWith(root, ".cpgz") || endsWith(root, ".cpio.bz2") || endsWith(root, ".cpbz2") || endsWith(root, ".pax") || endsWith(root, ".pax.gz") || endsWith(root, ".pgz") || endsWith(root, ".pax.bz2") || endsWith(root, ".pbz2")) {
121             args[0] = (char*)"/usr/bin/ditto";
122             args[1] = (char*)"-x";
123             args[2] = (char*)root.c_str();
124             args[3] = tempRootDir;
125             args[4] = nullptr;
126         } else if (endsWith(root, ".tar")) {
127             args[0] = (char*)"/usr/bin/tar";
128             args[1] = (char*)"xf";
129             args[2] = (char*)root.c_str();
130             args[3] = (char*)"-C";
131             args[4] = tempRootDir;
132             args[5] = nullptr;
133         } else if (endsWith(root, ".tar.gz") || endsWith(root, ".tgz")) {
134             args[0] = (char*)"/usr/bin/tar";
135             args[1] = (char*)"xzf";
136             args[2] = (char*)root.c_str();
137             args[3] = (char*)"-C";
138             args[4] = tempRootDir;
139             args[5] = nullptr;
140         } else if (endsWith(root, ".tar.bz2")
141             || endsWith(root, ".tbz2")
142             || endsWith(root, ".tbz")) {
143             args[0] = (char*)"/usr/bin/tar";
144             args[1] = (char*)"xjf";
145             args[2] = (char*)root.c_str();
146             args[3] = (char*)"-C";
147             args[4] = tempRootDir;
148             args[5] = nullptr;
149         } else if (endsWith(root, ".xar")) {
150             args[0] = (char*)"/usr/bin/xar";
151             args[1] = (char*)"-xf";
152             args[2] = (char*)root.c_str();
153             args[3] = (char*)"-C";
154             args[4] = tempRootDir;
155             args[5] = nullptr;
156         } else if (endsWith(root, ".zip")) {
157             args[0] = (char*)"/usr/bin/ditto";
158             args[1] = (char*)"-xk";
159             args[2] = (char*)root.c_str();
160             args[3] = tempRootDir;
161             args[4] = nullptr;
162         } else {
163             diags.error("unknown archive type: %s", root.c_str());
164             continue;
165         }
166
167         if (res != runCommandAndWait(diags, args)) {
168             fprintf(stderr, "Could not expand archive %s: %s (%d)", root.c_str(), strerror(res), res);
169             exit(-1);
170         }
171         for (auto& existingRoot : processedRoots) {
172             if (existingRoot == tempRootDir)
173                 return;
174         }
175
176         processedRoots.insert(tempRootDir);
177     }
178
179     roots = processedRoots;
180 }
181
182 bool writeRootList(const std::string& dstRoot, const std::set<std::string>& roots)
183 {
184     mkpath_np(dstRoot.c_str(), 0755);
185     if (roots.size() == 0)
186         return false;
187
188     std::string rootFile = dstRoot + "/roots.txt";
189     FILE*       froots = ::fopen(rootFile.c_str(), "w");
190     if (froots == NULL)
191         return false;
192
193     for (auto& root : roots) {
194         fprintf(froots, "%s\n", root.c_str());
195     }
196
197     ::fclose(froots);
198     return true;
199 }
200
201 BOMCopierCopyOperation filteredCopyExcludingPaths(BOMCopier copier, const char* path, BOMFSObjType type, off_t size)
202 {
203     std::string absolutePath = &path[1];
204     void *userData = BOMCopierUserData(copier);
205     std::set<std::string> *cachePaths = (std::set<std::string>*)userData;
206     if (cachePaths->count(absolutePath)) {
207         return BOMCopierSkipFile;
208     }
209     return BOMCopierContinue;
210 }
211
212 BOMCopierCopyOperation filteredCopyIncludingPaths(BOMCopier copier, const char* path, BOMFSObjType type, off_t size)
213 {
214     std::string absolutePath = &path[1];
215     void *userData = BOMCopierUserData(copier);
216     std::set<std::string> *cachePaths = (std::set<std::string>*)userData;
217     for (const std::string& cachePath : *cachePaths) {
218         if (startsWith(cachePath, absolutePath))
219             return BOMCopierContinue;
220     }
221     if (cachePaths->count(absolutePath)) {
222         return BOMCopierContinue;
223     }
224     return BOMCopierSkipFile;
225 }
226
227 static std::string dispositionToString(Disposition disposition) {
228     switch (disposition) {
229         case Unknown:
230             return "Unknown";
231         case InternalDevelopment:
232             return "InternalDevelopment";
233         case Customer:
234             return "Customer";
235         case InternalMinDevelopment:
236             return "InternalMinDevelopment";
237     }
238 }
239
240 static std::string platformToString(Platform platform) {
241     switch (platform) {
242         case unknown:
243             return "unknown";
244         case macOS:
245             return "macOS";
246         case iOS:
247             return "iOS";
248         case tvOS:
249             return "tvOS";
250         case watchOS:
251             return "watchOS";
252         case bridgeOS:
253             return "bridgeOS";
254         case iOSMac:
255             return "iOSMac";
256         case iOS_simulator:
257             return "iOS_simulator";
258         case tvOS_simulator:
259             return "tvOS_simulator";
260         case watchOS_simulator:
261             return "watchOS_simulator";
262     }
263 }
264
265 static dyld3::json::Node getBuildOptionsNode(BuildOptions_v1 buildOptions) {
266     dyld3::json::Node buildOptionsNode;
267     buildOptionsNode.map["version"].value             = dyld3::json::decimal(buildOptions.version);
268     buildOptionsNode.map["updateName"].value          = buildOptions.updateName;
269     buildOptionsNode.map["deviceName"].value          = buildOptions.deviceName;
270     buildOptionsNode.map["disposition"].value         = dispositionToString(buildOptions.disposition);
271     buildOptionsNode.map["platform"].value            = platformToString(buildOptions.platform);
272     for (unsigned i = 0; i != buildOptions.numArchs; ++i) {
273         dyld3::json::Node archNode;
274         archNode.value = buildOptions.archs[i];
275         buildOptionsNode.map["archs"].array.push_back(archNode);
276     }
277     buildOptionsNode.map["verboseDiagnostics"].value  = buildOptions.verboseDiagnostics ? "true" : "false";
278     return buildOptionsNode;
279 }
280
281 int main(int argc, const char* argv[])
282 {
283     @autoreleasepool {
284         __block Diagnostics   diags;
285         std::set<std::string> roots;
286         std::string           dylibCacheDir;
287         std::string           artifactDir;
288         std::string           release;
289         bool                  emitDevCaches = true;
290         bool                  emitElidedDylibs = true;
291         bool                  listConfigs = false;
292         bool                  copyRoots = false;
293         bool                  debug = false;
294         bool                  useMRM = false;
295         std::string           dstRoot;
296         std::string           emitJSONPath;
297         std::string           configuration;
298         std::string           resultPath;
299         std::string           baselineDifferenceResultPath;
300         bool                  baselineCopyRoots = false;
301         char* tempRootsDir = strdup("/tmp/dyld_shared_cache_builder.XXXXXX");
302
303         mkdtemp(tempRootsDir);
304
305         for (int i = 1; i < argc; ++i) {
306             const char* arg = argv[i];
307             if (arg[0] == '-') {
308                 if (strcmp(arg, "-debug") == 0) {
309                     diags = Diagnostics(true);
310                     debug = true;
311                 } else if (strcmp(arg, "-list_configs") == 0) {
312                     listConfigs = true;
313                 } else if (strcmp(arg, "-root") == 0) {
314                     roots.insert(realPath(argv[++i]));
315                 } else if (strcmp(arg, "-copy_roots") == 0) {
316                     copyRoots = true;
317                 } else if (strcmp(arg, "-dylib_cache") == 0) {
318                     dylibCacheDir = realPath(argv[++i]);
319                 } else if (strcmp(arg, "-artifact") == 0) {
320                     artifactDir = realPath(argv[++i]);
321                 } else if (strcmp(arg, "-no_development_cache") == 0) {
322                     emitDevCaches = false;
323                 } else if (strcmp(arg, "-no_overflow_dylibs") == 0) {
324                     emitElidedDylibs = false;
325                 } else if (strcmp(arg, "-development_cache") == 0) {
326                     emitDevCaches = true;
327                 } else if (strcmp(arg, "-overflow_dylibs") == 0) {
328                     emitElidedDylibs = true;
329                 } else if (strcmp(arg, "-mrm") == 0) {
330                     useMRM = true;
331                 } else if (strcmp(arg, "-emit_json") == 0) {
332                     emitJSONPath = realPath(argv[++i]);
333                 } else if (strcmp(arg, "-dst_root") == 0) {
334                     dstRoot = realPath(argv[++i]);
335                 } else if (strcmp(arg, "-release") == 0) {
336                     release = argv[++i];
337                 } else if (strcmp(arg, "-results") == 0) {
338                     resultPath = realPath(argv[++i]);
339                 } else if (strcmp(arg, "-baseline_diff_results") == 0) {
340                     baselineDifferenceResultPath = realPath(argv[++i]);
341                 } else if (strcmp(arg, "-baseline_copy_roots") == 0) {
342                     baselineCopyRoots = true;
343                 } else {
344                     //usage();
345                     fprintf(stderr, "unknown option: %s\n", arg);
346                     exit(-1);
347                 }
348             } else {
349                 if (!configuration.empty()) {
350                     fprintf(stderr, "You may only specify one configuration\n");
351                     exit(-1);
352                 }
353                 configuration = argv[i];
354             }
355         }
356
357         time_t mytime = time(0);
358         fprintf(stderr, "Started: %s", asctime(localtime(&mytime)));
359         writeRootList(dstRoot, roots);
360         processRoots(diags, roots, tempRootsDir);
361
362         struct rlimit rl = { OPEN_MAX, OPEN_MAX };
363         (void)setrlimit(RLIMIT_NOFILE, &rl);
364
365         if (dylibCacheDir.empty() && artifactDir.empty() && release.empty()) {
366             fprintf(stderr, "you must specify either -dylib_cache, -artifact or -release\n");
367             exit(-1);
368         } else if (!dylibCacheDir.empty() && !release.empty()) {
369             fprintf(stderr, "you may not use -dylib_cache and -release at the same time\n");
370             exit(-1);
371         } else if (!dylibCacheDir.empty() && !artifactDir.empty()) {
372             fprintf(stderr, "you may not use -dylib_cache and -artifact at the same time\n");
373             exit(-1);
374         }
375
376         if ((configuration.empty() || dstRoot.empty()) && !listConfigs) {
377             fprintf(stderr, "Must specify a configuration and a valid -dst_root OR -list_configs\n");
378             exit(-1);
379         }
380
381         if (!baselineDifferenceResultPath.empty() && (roots.size() > 1)) {
382             fprintf(stderr, "Cannot use -baseline_diff_results with more that one -root\n");
383             exit(-1);
384         }
385
386         if (!artifactDir.empty()) {
387             // Find the dylib cache dir from inside the artifact dir
388             struct stat stat_buf;
389             if (stat(artifactDir.c_str(), &stat_buf) != 0) {
390                 fprintf(stderr, "Could not find artifact path '%s'\n", artifactDir.c_str());
391                 exit(-1);
392             }
393             std::string dir = artifactDir + "/AppleInternal/Developer/DylibCaches";
394             if (stat(dir.c_str(), &stat_buf) != 0) {
395                 fprintf(stderr, "Could not find artifact path '%s'\n", dir.c_str());
396                 exit(-1);
397             }
398
399             if (!release.empty()) {
400                 // Use the given release
401                 dylibCacheDir = dir + "/" + release + ".dlc";
402             } else {
403                 // Find a release directory
404                 __block std::vector<std::string> subDirectories;
405                 iterateDirectoryTree("", dir, ^(const std::string& dirPath) {
406                     subDirectories.push_back(dirPath);
407                     return false;
408                 }, nullptr, false, false);
409
410                 if (subDirectories.empty()) {
411                     fprintf(stderr, "Could not find dlc subdirectories inside '%s'\n", dir.c_str());
412                     exit(-1);
413                 }
414
415                 if (subDirectories.size() > 1) {
416                     fprintf(stderr, "Found too many subdirectories inside artifact path '%s'.  Use -release to select one\n", dir.c_str());
417                     exit(-1);
418                 }
419
420                 dylibCacheDir = subDirectories.front();
421             }
422         }
423
424         if (dylibCacheDir.empty()) {
425             dylibCacheDir = std::string("/AppleInternal/Developer/DylibCaches/") + release + ".dlc";
426         }
427
428         //Move into the dir so we can use relative path manifests
429         chdir(dylibCacheDir.c_str());
430
431         dispatch_async(dispatch_get_main_queue(), ^{
432             // If we only want a list of configuations, then tell the manifest to only parse the data and not
433             // actually get all the macho's.
434             bool onlyParseManifest = listConfigs && configuration.empty();
435             auto manifest = dyld3::Manifest(diags, dylibCacheDir + "/Manifest.plist", roots, onlyParseManifest);
436
437             if (manifest.build().empty()) {
438                 fprintf(stderr, "No manifest found at '%s/Manifest.plist'\n", dylibCacheDir.c_str());
439                 exit(-1);
440             }
441             fprintf(stderr, "Building Caches for %s\n", manifest.build().c_str());
442
443             if (listConfigs) {
444                 manifest.forEachConfiguration([](const std::string& configName) {
445                     printf("%s\n", configName.c_str());
446                 });
447                 // If we weren't passed a configuration then exit
448                 if (configuration.empty())
449                     exit(0);
450             }
451
452             if (!manifest.filterForConfig(configuration)) {
453                 fprintf(stderr, "No config %s. Please run with -list_configs to see configurations available for this %s.\n",
454                     configuration.c_str(), manifest.build().c_str());
455                 exit(-1);
456             }
457
458             (void)mkpath_np((dstRoot + "/System/Library/Caches/com.apple.dyld/").c_str(), 0755);
459             bool cacheBuildSuccess = false;
460             if (useMRM) {
461
462                 FILE* jsonFile = nullptr;
463                 if (!emitJSONPath.empty()) {
464                     jsonFile = fopen(emitJSONPath.c_str(), "w");
465                     if (!jsonFile) {
466                         diags.verbose("can't open file '%s', errno=%d\n", emitJSONPath.c_str(), errno);
467                         return;
468                     }
469                 }
470                 dyld3::json::Node buildInvocationNode;
471
472                 // Find the archs for the configuration we want.
473                 __block std::set<std::string> validArchs;
474                 manifest.configuration(configuration).forEachArchitecture(^(const std::string& path) {
475                     validArchs.insert(path);
476                 });
477
478                 if (validArchs.size() != 1) {
479                     fprintf(stderr, "MRM doesn't support more than one arch per configuration: %s\n",
480                             configuration.c_str());
481                     exit(-1);
482                 }
483
484                 const char* archs[validArchs.size()];
485                 uint64_t archIndex = 0;
486                 for (const std::string& arch : validArchs) {
487                     archs[archIndex++] = arch.c_str();
488                 }
489
490                 BuildOptions_v1 buildOptions;
491                 buildOptions.version                            = 1;
492                 buildOptions.updateName                         = manifest.build().c_str();
493                 buildOptions.deviceName                         = configuration.c_str();
494                 buildOptions.disposition                        = Disposition::Unknown;
495                 buildOptions.platform                           = (Platform)manifest.platform();
496                 buildOptions.archs                              = archs;
497                 buildOptions.numArchs                           = validArchs.size();
498                 buildOptions.verboseDiagnostics                 = debug;
499                 buildOptions.isLocallyBuiltCache                = true;
500
501                 __block struct SharedCacheBuilder* sharedCacheBuilder = createSharedCacheBuilder(&buildOptions);
502                 buildInvocationNode.map["build-options"] = getBuildOptionsNode(buildOptions);
503
504                 std::set<std::string> requiredBinaries =  {
505                     "/usr/lib/libSystem.B.dylib"
506                 };
507
508                 // Get the file data for every MachO in the BOM.
509                 __block dyld3::json::Node filesNode;
510                 __block std::vector<std::pair<const void*, size_t>> mappedFiles;
511                 manifest.forEachMachO(configuration, ^(const std::string &buildPath, const std::string &runtimePath, const std::string &arch, bool shouldBeExcludedIfLeaf) {
512
513                     // Filter based on arch as the Manifest adds the file once for each UUID.
514                     if (!validArchs.count(arch))
515                         return;
516
517                     struct stat stat_buf;
518                     int fd = ::open(buildPath.c_str(), O_RDONLY, 0);
519                     if (fd == -1) {
520                         diags.verbose("can't open file '%s', errno=%d\n", buildPath.c_str(), errno);
521                         return;
522                     }
523
524                     if (fstat(fd, &stat_buf) == -1) {
525                         diags.verbose("can't stat open file '%s', errno=%d\n", buildPath.c_str(), errno);
526                         ::close(fd);
527                         return;
528                     }
529
530                     const void* buffer = mmap(NULL, (size_t)stat_buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
531                     if (buffer == MAP_FAILED) {
532                         diags.verbose("mmap() for file at %s failed, errno=%d\n", buildPath.c_str(), errno);
533                         ::close(fd);
534                     }
535                     ::close(fd);
536
537                     mappedFiles.emplace_back(buffer, (size_t)stat_buf.st_size);
538                     FileFlags fileFlags = FileFlags::NoFlags;
539                     if (requiredBinaries.count(runtimePath))
540                         fileFlags = FileFlags::RequiredClosure;
541                     addFile(sharedCacheBuilder, runtimePath.c_str(), (uint8_t*)buffer, (size_t)stat_buf.st_size, fileFlags);
542
543                     dyld3::json::Node fileNode;
544                     fileNode.map["path"].value  = runtimePath;
545                     fileNode.map["flags"].value = "NoFlags";
546                     filesNode.array.push_back(fileNode);
547                 });
548
549                 __block dyld3::json::Node symlinksNode;
550                 manifest.forEachSymlink(configuration, ^(const std::string &fromPath, const std::string &toPath) {
551                     addSymlink(sharedCacheBuilder, fromPath.c_str(), toPath.c_str());
552
553                     dyld3::json::Node symlinkNode;
554                     symlinkNode.map["from-path"].value  = fromPath;
555                     symlinkNode.map["to-path"].value    = toPath;
556                     symlinksNode.array.push_back(symlinkNode);
557                 });
558
559                 buildInvocationNode.map["symlinks"] = symlinksNode;
560
561                 std::string orderFileData;
562                 if (!manifest.dylibOrderFile().empty()) {
563                     orderFileData = loadOrderFile(manifest.dylibOrderFile());
564                     if (!orderFileData.empty()) {
565                         addFile(sharedCacheBuilder, "*order file data*", (uint8_t*)orderFileData.data(), orderFileData.size(), FileFlags::DylibOrderFile);
566                         dyld3::json::Node fileNode;
567                         fileNode.map["path"].value  = manifest.dylibOrderFile();
568                         fileNode.map["flags"].value = "DylibOrderFile";
569                         filesNode.array.push_back(fileNode);
570                     }
571                 }
572
573                 std::string dirtyDataOrderFileData;
574                 if (!manifest.dirtyDataOrderFile().empty()) {
575                     dirtyDataOrderFileData = loadOrderFile(manifest.dirtyDataOrderFile());
576                     if (!dirtyDataOrderFileData.empty()) {
577                         addFile(sharedCacheBuilder, "*dirty data order file data*", (uint8_t*)dirtyDataOrderFileData.data(), dirtyDataOrderFileData.size(), FileFlags::DirtyDataOrderFile);
578                         dyld3::json::Node fileNode;
579                         fileNode.map["path"].value  = manifest.dirtyDataOrderFile();
580                         fileNode.map["flags"].value = "DirtyDataOrderFile";
581                         filesNode.array.push_back(fileNode);
582                     }
583                 }
584
585                 buildInvocationNode.map["files"] = filesNode;
586
587                 if (jsonFile) {
588                     dyld3::json::printJSON(buildInvocationNode, 0, jsonFile);
589                     fclose(jsonFile);
590                     jsonFile = nullptr;
591                 }
592
593                 cacheBuildSuccess = runSharedCacheBuilder(sharedCacheBuilder);
594
595                 if (!cacheBuildSuccess) {
596                     for (uint64 i = 0, e = getErrorCount(sharedCacheBuilder); i != e; ++i) {
597                         const char* errorMessage = getError(sharedCacheBuilder, i);
598                         fprintf(stderr, "ERROR: %s\n", errorMessage);
599                     }
600                 }
601
602                 // Now emit each cache we generated, or the errors for them.
603                 for (uint64 i = 0, e = getCacheResultCount(sharedCacheBuilder); i != e; ++i) {
604                     BuildResult result;
605                     getCacheResult(sharedCacheBuilder, i, &result);
606                     if (result.numErrors) {
607                         for (uint64_t errorIndex = 0; errorIndex != result.numErrors; ++errorIndex) {
608                             fprintf(stderr, "[%s] ERROR: %s\n", result.loggingPrefix, result.errors[errorIndex]);
609                         }
610                         cacheBuildSuccess = false;
611                         continue;
612                     }
613                     if (result.numWarnings) {
614                         for (uint64_t warningIndex = 0; warningIndex != result.numWarnings; ++warningIndex) {
615                             fprintf(stderr, "[%s] WARNING: %s\n", result.loggingPrefix, result.warnings[warningIndex]);
616                         }
617                     }
618                 }
619
620                 // If we built caches, then write everything out.
621                 // TODO: Decide if we should we write any good caches anyway?
622                 if (cacheBuildSuccess) {
623                     for (uint64 i = 0, e = getFileResultCount(sharedCacheBuilder); i != e; ++i) {
624                         FileResult result;
625                         getFileResult(sharedCacheBuilder, i, &result);
626
627                         if (!result.data)
628                             continue;
629
630                         const std::string path = dstRoot + result.path;
631                         std::string pathTemplate = path + "-XXXXXX";
632                         size_t templateLen = strlen(pathTemplate.c_str())+2;
633                         char pathTemplateSpace[templateLen];
634                         strlcpy(pathTemplateSpace, pathTemplate.c_str(), templateLen);
635                         int fd = mkstemp(pathTemplateSpace);
636                         if ( fd != -1 ) {
637                             ::ftruncate(fd, result.size);
638                             uint64_t writtenSize = pwrite(fd, result.data, result.size, 0);
639                             if ( writtenSize == result.size ) {
640                                 ::fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); // mkstemp() makes file "rw-------", switch it to "rw-r--r--"
641                                 if ( ::rename(pathTemplateSpace, path.c_str()) == 0) {
642                                     ::close(fd);
643                                     continue; // success
644                                 }
645                             }
646                             else {
647                                 fprintf(stderr, "ERROR: could not write file %s\n", pathTemplateSpace);
648                                 cacheBuildSuccess = false;
649                             }
650                             ::close(fd);
651                             ::unlink(pathTemplateSpace);
652                         }
653                         else {
654                             fprintf(stderr, "ERROR: could not open file %s\n", pathTemplateSpace);
655                             cacheBuildSuccess = false;
656                         }
657                     }
658                 }
659
660                 destroySharedCacheBuilder(sharedCacheBuilder);
661
662                 for (auto mappedFile : mappedFiles)
663                     ::munmap((void*)mappedFile.first, mappedFile.second);
664             } else {
665                 manifest.calculateClosure();
666
667                 cacheBuildSuccess = build(diags, manifest, dstRoot, false, debug, false, false, emitDevCaches, true);
668             }
669
670             if (!cacheBuildSuccess) {
671                 exit(-1);
672             }
673
674             // Compare this cache to the baseline cache and see if we have any roots to copy over
675             if (!baselineDifferenceResultPath.empty() || baselineCopyRoots) {
676                 std::set<std::string> baselineDylibs = manifest.resultsForConfiguration(configuration);
677
678                 std::set<std::string> newDylibs;
679                 manifest.forEachConfiguration([&manifest, &newDylibs](const std::string& configName) {
680                     for (auto& arch : manifest.configuration(configName).architectures) {
681                         for (auto& dylib : arch.second.results.dylibs) {
682                             if (dylib.second.included) {
683                                 newDylibs.insert(manifest.installNameForUUID(dylib.first));
684                             }
685                         }
686                     }
687                 });
688
689                 if (baselineCopyRoots) {
690                     // Work out the set of dylibs in the old cache but not the new one
691                     std::set<std::string> dylibsMissingFromNewCache;
692                     for (const std::string& baselineDylib : baselineDylibs) {
693                         if (!newDylibs.count(baselineDylib))
694                             dylibsMissingFromNewCache.insert(baselineDylib);
695                     }
696
697                     if (!dylibsMissingFromNewCache.empty()) {
698                         BOMCopier copier = BOMCopierNewWithSys(BomSys_default());
699                         BOMCopierSetUserData(copier, (void*)&dylibsMissingFromNewCache);
700                         BOMCopierSetCopyFileStartedHandler(copier, filteredCopyIncludingPaths);
701                         std::string dylibCacheRootDir = realFilePath(dylibCacheDir + "/Root");
702                         if (dylibCacheRootDir == "") {
703                             fprintf(stderr, "Could not find dylib Root directory to copy baseline roots from\n");
704                             exit(1);
705                         }
706                         BOMCopierCopy(copier, dylibCacheRootDir.c_str(), dstRoot.c_str());
707                         BOMCopierFree(copier);
708
709                         for (const std::string& dylibMissingFromNewCache : dylibsMissingFromNewCache) {
710                             diags.verbose("Dylib missing from new cache: '%s'\n", dylibMissingFromNewCache.c_str());
711                         }
712                     }
713                 }
714
715                 if (!baselineDifferenceResultPath.empty()) {
716                     auto cppToObjStr = [](const std::string& str) {
717                         return [NSString stringWithUTF8String:str.c_str()];
718                     };
719
720                     // Work out the set of dylibs in the cache and taken from the -root
721                     NSMutableArray<NSString*>* dylibsFromRoots = [NSMutableArray array];
722                     for (auto& root : roots) {
723                         for (const std::string& dylibInstallName : newDylibs) {
724                             struct stat sb;
725                             std::string filePath = root + "/" + dylibInstallName;
726                             if (!stat(filePath.c_str(), &sb)) {
727                                 [dylibsFromRoots addObject:cppToObjStr(dylibInstallName)];
728                             }
729                         }
730                     }
731
732                     // Work out the set of dylibs in the new cache but not in the baseline cache.
733                     NSMutableArray<NSString*>* dylibsMissingFromBaselineCache = [NSMutableArray array];
734                     for (const std::string& newDylib : newDylibs) {
735                         if (!baselineDylibs.count(newDylib))
736                             [dylibsMissingFromBaselineCache addObject:cppToObjStr(newDylib)];
737                     }
738
739                     NSMutableDictionary* cacheDict = [[NSMutableDictionary alloc] init];
740                     cacheDict[@"root-paths-in-cache"] = dylibsFromRoots;
741                     cacheDict[@"device-paths-to-delete"] = dylibsMissingFromBaselineCache;
742
743                     NSError* error = nil;
744                     NSData*  outData = [NSPropertyListSerialization dataWithPropertyList:cacheDict
745                                                                                   format:NSPropertyListBinaryFormat_v1_0
746                                                                                  options:0
747                                                                                    error:&error];
748                     (void)[outData writeToFile:cppToObjStr(baselineDifferenceResultPath) atomically:YES];
749                 }
750             }
751
752             if (copyRoots) {
753                 std::set<std::string> cachePaths;
754                 manifest.forEachConfiguration([&manifest, &cachePaths](const std::string& configName) {
755                     for (auto& arch : manifest.configuration(configName).architectures) {
756                         for (auto& dylib : arch.second.results.dylibs) {
757                             if (dylib.second.included) {
758                                 cachePaths.insert(manifest.installNameForUUID(dylib.first));
759                             }
760                         }
761                     }
762                 });
763
764                 BOMCopier copier = BOMCopierNewWithSys(BomSys_default());
765                 BOMCopierSetUserData(copier, (void*)&cachePaths);
766                 BOMCopierSetCopyFileStartedHandler(copier, filteredCopyExcludingPaths);
767                 for (auto& root : roots) {
768                     BOMCopierCopy(copier, root.c_str(), dstRoot.c_str());
769                 }
770                 BOMCopierFree(copier);
771             }
772
773             int err = sync_volume_np(dstRoot.c_str(), SYNC_VOLUME_FULLSYNC | SYNC_VOLUME_WAIT);
774             if (err) {
775                 fprintf(stderr, "Volume sync failed errnor=%d (%s)\n", err, strerror(err));
776             }
777
778             // Now that all the build commands have been issued lets put a barrier in after then which can tear down the app after
779             // everything is written.
780
781             if (!resultPath.empty()) {
782                 manifest.write(resultPath);
783             }
784
785             const char* args[8];
786             args[0] = (char*)"/bin/rm";
787             args[1] = (char*)"-rf";
788             args[2] = (char*)tempRootsDir;
789             args[3] = nullptr;
790             (void)runCommandAndWait(diags, args);
791
792             for (const std::string& warn : diags.warnings()) {
793                 fprintf(stderr, "dyld_shared_cache_builder: warning: %s\n", warn.c_str());
794             }
795             exit(0);
796         });
797     }
798
799     dispatch_main();
800
801     return 0;
802 }