dyld-750.5.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 #include <fstream>
57 #include <regex>
58
59 #include <spawn.h>
60
61 #include <Bom/Bom.h>
62 #include <Foundation/NSData.h>
63 #include <Foundation/NSDictionary.h>
64 #include <Foundation/NSPropertyList.h>
65 #include <Foundation/NSString.h>
66
67 #include "Diagnostics.h"
68 #include "DyldSharedCache.h"
69 #include "FileUtils.h"
70 #include "JSONReader.h"
71 #include "JSONWriter.h"
72 #include "StringUtils.h"
73 #include "mrm_shared_cache_builder.h"
74
75 #if !__has_feature(objc_arc)
76 #error The use of libdispatch in this files requires it to be compiled with ARC in order to avoid leaks
77 #endif
78
79 extern char** environ;
80
81 static dispatch_queue_t build_queue;
82
83 int runCommandAndWait(Diagnostics& diags, const char* args[])
84 {
85     pid_t pid;
86     int   status;
87     int   res = posix_spawn(&pid, args[0], nullptr, nullptr, (char**)args, environ);
88     if (res != 0)
89         diags.error("Failed to spawn %s: %s (%d)", args[0], strerror(res), res);
90
91     do {
92         res = waitpid(pid, &status, 0);
93     } while (res == -1 && errno == EINTR);
94     if (res != -1) {
95         if (WIFEXITED(status)) {
96             res = WEXITSTATUS(status);
97         } else {
98             res = -1;
99         }
100     }
101
102     return res;
103 }
104
105 void processRoots(Diagnostics& diags, std::set<std::string>& roots, const char *tempRootsDir)
106 {
107     std::set<std::string> processedRoots;
108     struct stat           sb;
109     int                   res = 0;
110     const char*           args[8];
111
112     for (const auto& root : roots) {
113         res = stat(root.c_str(), &sb);
114
115         if (res == 0 && S_ISDIR(sb.st_mode)) {
116             processedRoots.insert(root);
117             continue;
118         }
119
120         char tempRootDir[MAXPATHLEN];
121         strlcpy(tempRootDir, tempRootsDir, MAXPATHLEN);
122         strlcat(tempRootDir, "/XXXXXXXX", MAXPATHLEN);
123         mkdtemp(tempRootDir);
124
125         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")) {
126             args[0] = (char*)"/usr/bin/ditto";
127             args[1] = (char*)"-x";
128             args[2] = (char*)root.c_str();
129             args[3] = tempRootDir;
130             args[4] = nullptr;
131         } else if (endsWith(root, ".tar")) {
132             args[0] = (char*)"/usr/bin/tar";
133             args[1] = (char*)"xf";
134             args[2] = (char*)root.c_str();
135             args[3] = (char*)"-C";
136             args[4] = tempRootDir;
137             args[5] = nullptr;
138         } else if (endsWith(root, ".tar.gz") || endsWith(root, ".tgz")) {
139             args[0] = (char*)"/usr/bin/tar";
140             args[1] = (char*)"xzf";
141             args[2] = (char*)root.c_str();
142             args[3] = (char*)"-C";
143             args[4] = tempRootDir;
144             args[5] = nullptr;
145         } else if (endsWith(root, ".tar.bz2")
146             || endsWith(root, ".tbz2")
147             || endsWith(root, ".tbz")) {
148             args[0] = (char*)"/usr/bin/tar";
149             args[1] = (char*)"xjf";
150             args[2] = (char*)root.c_str();
151             args[3] = (char*)"-C";
152             args[4] = tempRootDir;
153             args[5] = nullptr;
154         } else if (endsWith(root, ".zip")) {
155             args[0] = (char*)"/usr/bin/ditto";
156             args[1] = (char*)"-xk";
157             args[2] = (char*)root.c_str();
158             args[3] = tempRootDir;
159             args[4] = nullptr;
160         } else {
161             diags.error("unknown archive type: %s", root.c_str());
162             continue;
163         }
164
165         if (res != runCommandAndWait(diags, args)) {
166             fprintf(stderr, "Could not expand archive %s: %s (%d)", root.c_str(), strerror(res), res);
167             exit(-1);
168         }
169         for (auto& existingRoot : processedRoots) {
170             if (existingRoot == tempRootDir)
171                 return;
172         }
173
174         processedRoots.insert(tempRootDir);
175     }
176
177     roots = processedRoots;
178 }
179
180 void writeRootList(const std::string& dstRoot, const std::set<std::string>& roots)
181 {
182     if (roots.size() == 0)
183         return;
184
185     std::string rootFile = dstRoot + "/roots.txt";
186     FILE*       froots = ::fopen(rootFile.c_str(), "w");
187     if (froots == NULL)
188         return;
189
190     for (auto& root : roots) {
191         fprintf(froots, "%s\n", root.c_str());
192     }
193
194     ::fclose(froots);
195 }
196
197 BOMCopierCopyOperation filteredCopyIncludingPaths(BOMCopier copier, const char* path, BOMFSObjType type, off_t size)
198 {
199     std::string absolutePath = &path[1];
200     void *userData = BOMCopierUserData(copier);
201     std::set<std::string> *cachePaths = (std::set<std::string>*)userData;
202     for (const std::string& cachePath : *cachePaths) {
203         if (startsWith(cachePath, absolutePath))
204             return BOMCopierContinue;
205     }
206     if (cachePaths->count(absolutePath)) {
207         return BOMCopierContinue;
208     }
209     return BOMCopierSkipFile;
210 }
211
212 static Disposition stringToDisposition(Diagnostics& diags, const std::string& str) {
213     if (diags.hasError())
214         return Unknown;
215     if (str == "Unknown")
216         return Unknown;
217     if (str == "InternalDevelopment")
218         return InternalDevelopment;
219     if (str == "Customer")
220         return Customer;
221     if (str == "InternalMinDevelopment")
222         return InternalMinDevelopment;
223     return Unknown;
224 }
225
226 static Platform stringToPlatform(Diagnostics& diags, const std::string& str) {
227     if (diags.hasError())
228         return unknown;
229     if (str == "unknown")
230         return unknown;
231     if (str == "macOS")
232         return macOS;
233     if (str == "iOS")
234         return iOS;
235     if (str == "tvOS")
236         return tvOS;
237     if (str == "watchOS")
238         return watchOS;
239     if (str == "bridgeOS")
240         return bridgeOS;
241     if (str == "iOSMac")
242         return iOSMac;
243     if (str == "UIKitForMac")
244         return iOSMac;
245     if (str == "iOS_simulator")
246         return iOS_simulator;
247     if (str == "tvOS_simulator")
248         return tvOS_simulator;
249     if (str == "watchOS_simulator")
250         return watchOS_simulator;
251     return unknown;
252 }
253
254 static FileFlags stringToFileFlags(Diagnostics& diags, const std::string& str) {
255     if (diags.hasError())
256         return NoFlags;
257     if (str == "NoFlags")
258         return NoFlags;
259     if (str == "MustBeInCache")
260         return MustBeInCache;
261     if (str == "ShouldBeExcludedFromCacheIfUnusedLeaf")
262         return ShouldBeExcludedFromCacheIfUnusedLeaf;
263     if (str == "RequiredClosure")
264         return RequiredClosure;
265     if (str == "DylibOrderFile")
266         return DylibOrderFile;
267     if (str == "DirtyDataOrderFile")
268         return DirtyDataOrderFile;
269     return NoFlags;
270 }
271
272 struct SharedCacheBuilderOptions {
273     Diagnostics           diags;
274     std::set<std::string> roots;
275     std::string           dylibCacheDir;
276     std::string           artifactDir;
277     std::string           release;
278     bool                  emitDevCaches = true;
279     bool                  emitElidedDylibs = true;
280     bool                  listConfigs = false;
281     bool                  copyRoots = false;
282     bool                  debug = false;
283     bool                  useMRM = false;
284     std::string           dstRoot;
285     std::string           emitJSONPath;
286     std::string           buildAllPath;
287     std::string           resultPath;
288     std::string           baselineDifferenceResultPath;
289     std::string           baselineCacheMapPath;
290     bool                  baselineCopyRoots = false;
291 };
292
293 static void loadMRMFiles(Diagnostics& diags,
294                          MRMSharedCacheBuilder* sharedCacheBuilder,
295                          const std::vector<std::tuple<std::string, std::string, FileFlags>>& inputFiles,
296                          std::vector<std::pair<const void*, size_t>>& mappedFiles,
297                          const std::set<std::string>& baselineCacheFiles) {
298
299     for (const std::tuple<std::string, std::string, FileFlags>& inputFile : inputFiles) {
300         const std::string& buildPath   = std::get<0>(inputFile);
301         const std::string& runtimePath = std::get<1>(inputFile);
302         FileFlags   fileFlags   = std::get<2>(inputFile);
303
304         struct stat stat_buf;
305         int fd = ::open(buildPath.c_str(), O_RDONLY, 0);
306         if (fd == -1) {
307             if (baselineCacheFiles.count(runtimePath)) {
308                 diags.error("can't open file '%s', errno=%d\n", buildPath.c_str(), errno);
309                 return;
310             } else {
311                 diags.verbose("can't open file '%s', errno=%d\n", buildPath.c_str(), errno);
312                 continue;
313             }
314         }
315
316         if (fstat(fd, &stat_buf) == -1) {
317             if (baselineCacheFiles.count(runtimePath)) {
318                 diags.error("can't stat open file '%s', errno=%d\n", buildPath.c_str(), errno);
319                 ::close(fd);
320                 return;
321             } else {
322                 diags.verbose("can't stat open file '%s', errno=%d\n", buildPath.c_str(), errno);
323                 ::close(fd);
324                 continue;
325             }
326         }
327
328         const void* buffer = mmap(NULL, (size_t)stat_buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
329         if (buffer == MAP_FAILED) {
330             diags.error("mmap() for file at %s failed, errno=%d\n", buildPath.c_str(), errno);
331             ::close(fd);
332         }
333         ::close(fd);
334
335         mappedFiles.emplace_back(buffer, (size_t)stat_buf.st_size);
336
337         addFile(sharedCacheBuilder, runtimePath.c_str(), (uint8_t*)buffer, (size_t)stat_buf.st_size, fileFlags);
338     }
339 }
340
341 static void unloadMRMFiles(std::vector<std::pair<const void*, size_t>>& mappedFiles) {
342     for (auto mappedFile : mappedFiles)
343         ::munmap((void*)mappedFile.first, mappedFile.second);
344 }
345
346 static void writeMRMResults(bool cacheBuildSuccess, MRMSharedCacheBuilder* sharedCacheBuilder, const SharedCacheBuilderOptions& options) {
347     if (!cacheBuildSuccess) {
348         uint64_t errorCount = 0;
349         if (const char* const* errors = getErrors(sharedCacheBuilder, &errorCount)) {
350             for (uint64_t i = 0, e = errorCount; i != e; ++i) {
351                 const char* errorMessage = errors[i];
352                 fprintf(stderr, "ERROR: %s\n", errorMessage);
353             }
354         }
355     }
356
357     // Now emit each cache we generated, or the errors for them.
358     uint64_t cacheResultCount = 0;
359     if (const CacheResult* const* cacheResults = getCacheResults(sharedCacheBuilder, &cacheResultCount)) {
360         for (uint64_t i = 0, e = cacheResultCount; i != e; ++i) {
361             const CacheResult& result = *(cacheResults[i]);
362             // Always print the warnings if we have roots, even if there are errors
363             if ( (result.numErrors == 0) || !options.roots.empty() ) {
364                 for (uint64_t warningIndex = 0; warningIndex != result.numWarnings; ++warningIndex) {
365                     fprintf(stderr, "[%s] WARNING: %s\n", result.loggingPrefix, result.warnings[warningIndex]);
366                 }
367             }
368             if (result.numErrors) {
369                 for (uint64_t errorIndex = 0; errorIndex != result.numErrors; ++errorIndex) {
370                     fprintf(stderr, "[%s] ERROR: %s\n", result.loggingPrefix, result.errors[errorIndex]);
371                 }
372                 cacheBuildSuccess = false;
373             }
374         }
375     }
376
377     if (!cacheBuildSuccess) {
378         exit(-1);
379     }
380
381     // If we built caches, then write everything out.
382     // TODO: Decide if we should we write any good caches anyway?
383     if (cacheBuildSuccess && !options.dstRoot.empty()) {
384         uint64_t fileResultCount = 0;
385         if (const FileResult* const* fileResults = getFileResults(sharedCacheBuilder, &fileResultCount)) {
386             for (uint64_t i = 0, e = fileResultCount; i != e; ++i) {
387                 const FileResult& result = *(fileResults[i]);
388
389                 switch (result.behavior) {
390                     case AddFile:
391                         break;
392                     case ChangeFile:
393                         continue;
394                 }
395
396                 if (!result.data)
397                     continue;
398
399                 const std::string path = options.dstRoot + result.path;
400                 std::string pathTemplate = path + "-XXXXXX";
401                 size_t templateLen = strlen(pathTemplate.c_str())+2;
402                 char pathTemplateSpace[templateLen];
403                 strlcpy(pathTemplateSpace, pathTemplate.c_str(), templateLen);
404                 int fd = mkstemp(pathTemplateSpace);
405                 if ( fd != -1 ) {
406                     ::ftruncate(fd, result.size);
407                     uint64_t writtenSize = pwrite(fd, result.data, result.size, 0);
408                     if ( writtenSize == result.size ) {
409                         ::fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); // mkstemp() makes file "rw-------", switch it to "rw-r--r--"
410                         if ( ::rename(pathTemplateSpace, path.c_str()) == 0) {
411                             ::close(fd);
412                             continue; // success
413                         }
414                     }
415                     else {
416                         fprintf(stderr, "ERROR: could not write file %s\n", pathTemplateSpace);
417                         cacheBuildSuccess = false;
418                     }
419                     ::close(fd);
420                     ::unlink(pathTemplateSpace);
421                 }
422                 else {
423                     fprintf(stderr, "ERROR: could not open file %s\n", pathTemplateSpace);
424                     cacheBuildSuccess = false;
425                 }
426             }
427         }
428     }
429 }
430
431 static void buildCacheFromJSONManifest(Diagnostics& diags, const SharedCacheBuilderOptions& options,
432                                        const std::string& jsonManifestPath) {
433     dyld3::json::Node manifestNode = dyld3::json::readJSON(diags, jsonManifestPath.c_str());
434     if (diags.hasError())
435         return;
436
437     // Top level node should be a map of the options, files, and symlinks.
438     if (manifestNode.map.empty()) {
439         diags.error("Expected map for JSON manifest node\n");
440         return;
441     }
442
443     // Parse the nodes in the top level manifest node
444     const dyld3::json::Node& versionNode          = dyld3::json::getRequiredValue(diags, manifestNode, "version");
445     uint64_t manifestVersion                      = dyld3::json::parseRequiredInt(diags, versionNode);
446     if (diags.hasError())
447         return;
448
449     const uint64_t supportedManifestVersion = 1;
450     if (manifestVersion != supportedManifestVersion) {
451         diags.error("JSON manfiest version of %lld is unsupported.  Supported version is %lld\n",
452                     manifestVersion, supportedManifestVersion);
453         return;
454     }
455     const dyld3::json::Node& buildOptionsNode     = dyld3::json::getRequiredValue(diags, manifestNode, "buildOptions");
456     const dyld3::json::Node& filesNode            = dyld3::json::getRequiredValue(diags, manifestNode, "files");
457     const dyld3::json::Node* symlinksNode         = dyld3::json::getOptionalValue(diags, manifestNode, "symlinks");
458
459     // Parse the archs
460     const dyld3::json::Node& archsNode = dyld3::json::getRequiredValue(diags, buildOptionsNode, "archs");
461     if (diags.hasError())
462         return;
463     if (archsNode.array.empty()) {
464         diags.error("Build options archs node is not an array\n");
465         return;
466     }
467     const char* archs[archsNode.array.size()];
468     uint64_t archIndex = 0;
469     for (const dyld3::json::Node& archNode : archsNode.array) {
470         archs[archIndex++] = dyld3::json::parseRequiredString(diags, archNode).c_str();
471     }
472
473     // Parse the rest of the options node.
474     BuildOptions_v1 buildOptions;
475     buildOptions.version                            = dyld3::json::parseRequiredInt(diags, dyld3::json::getRequiredValue(diags, buildOptionsNode, "version"));
476     buildOptions.updateName                         = dyld3::json::parseRequiredString(diags, dyld3::json::getRequiredValue(diags, buildOptionsNode, "updateName")).c_str();
477     buildOptions.deviceName                         = dyld3::json::parseRequiredString(diags, dyld3::json::getRequiredValue(diags, buildOptionsNode, "deviceName")).c_str();
478     buildOptions.disposition                        = stringToDisposition(diags, dyld3::json::parseRequiredString(diags, dyld3::json::getRequiredValue(diags, buildOptionsNode, "disposition")));
479     buildOptions.platform                           = stringToPlatform(diags, dyld3::json::parseRequiredString(diags, dyld3::json::getRequiredValue(diags, buildOptionsNode, "platform")));
480     buildOptions.archs                              = archs;
481     buildOptions.numArchs                           = archsNode.array.size();
482     buildOptions.verboseDiagnostics                 = options.debug;
483     buildOptions.isLocallyBuiltCache                = true;
484
485     if (diags.hasError())
486         return;
487
488     // Override the disposition if we don't want certaion caches.
489     if (!options.emitDevCaches) {
490         switch (buildOptions.disposition) {
491             case Unknown:
492                 // Nothing we can do here as we can't assume what caches are built here.
493                 break;
494             case InternalDevelopment:
495                 // This builds both caches, but we don't want dev
496                 buildOptions.disposition = Customer;
497                 break;
498             case Customer:
499                 // This is already only the customer cache
500                 break;
501             case InternalMinDevelopment:
502                 diags.error("Cannot request no dev cache for InternalMinDevelopment as that is already only a dev cache\n");
503                 break;
504         }
505     }
506
507     if (diags.hasError())
508         return;
509
510     struct MRMSharedCacheBuilder* sharedCacheBuilder = createSharedCacheBuilder(&buildOptions);
511
512     // Parse the files
513     if (filesNode.array.empty()) {
514         diags.error("Build options files node is not an array\n");
515         return;
516     }
517
518     std::vector<std::tuple<std::string, std::string, FileFlags>> inputFiles;
519     for (const dyld3::json::Node& fileNode : filesNode.array) {
520         const std::string& path = dyld3::json::parseRequiredString(diags, dyld3::json::getRequiredValue(diags, fileNode, "path")).c_str();
521         FileFlags fileFlags     = stringToFileFlags(diags, dyld3::json::parseRequiredString(diags, dyld3::json::getRequiredValue(diags, fileNode, "flags")));
522
523         // We can optionally have a sourcePath entry which is the path to get the source content from instead of the install path
524         std::string sourcePath;
525         const dyld3::json::Node* sourcePathNode = dyld3::json::getOptionalValue(diags, fileNode, "sourcePath");
526         if ( sourcePathNode != nullptr ) {
527             if (!sourcePathNode->array.empty()) {
528                 diags.error("sourcePath node cannot be an array\n");
529                 return;
530             }
531             if (!sourcePathNode->map.empty()) {
532                 diags.error("sourcePath node cannot be a map\n");
533                 return;
534             }
535             sourcePath = sourcePathNode->value;
536         } else {
537             sourcePath = path;
538         }
539
540         // Check if one of the -root's has this path
541         bool foundInOverlay = false;
542         for (const std::string& overlay : options.roots) {
543             struct stat sb;
544             std::string filePath = overlay + path;
545             if (!stat(filePath.c_str(), &sb)) {
546                 foundInOverlay = true;
547                 diags.verbose("Taking '%s' from overlay instead of dylib cache\n", path.c_str());
548                 inputFiles.push_back({ filePath, path, fileFlags });
549                 break;
550             }
551         }
552
553         if (foundInOverlay)
554             continue;
555
556         // Build paths are relative to the build artifact root directory.
557         std::string buildPath;
558         switch (fileFlags) {
559             case NoFlags:
560             case MustBeInCache:
561             case ShouldBeExcludedFromCacheIfUnusedLeaf:
562             case RequiredClosure:
563                 buildPath = "." + sourcePath;
564                 break;
565             case DylibOrderFile:
566             case DirtyDataOrderFile:
567                 buildPath = "." + sourcePath;
568                 break;
569         }
570         inputFiles.push_back({ buildPath, path, fileFlags });
571     }
572
573     if (diags.hasError())
574         return;
575
576     // Parse the baseline from the map if we have it
577     std::set<std::string> baselineDylibs;
578     if ( !options.baselineCacheMapPath.empty() ) {
579         dyld3::json::Node mapNode = dyld3::json::readJSON(diags, options.baselineCacheMapPath.c_str());
580         if (diags.hasError())
581             return;
582
583         // Top level node should be a map of the version and files
584         if (mapNode.map.empty()) {
585             diags.error("Expected map for JSON cache map node\n");
586             return;
587         }
588
589         // Parse the nodes in the top level manifest node
590         const dyld3::json::Node& versionNode     = dyld3::json::getRequiredValue(diags, mapNode, "version");
591         uint64_t mapVersion                      = dyld3::json::parseRequiredInt(diags, versionNode);
592         if (diags.hasError())
593             return;
594
595         const uint64_t supportedMapVersion = 1;
596         if (mapVersion != supportedMapVersion) {
597             diags.error("JSON map version of %lld is unsupported.  Supported version is %lld\n",
598                         mapVersion, supportedMapVersion);
599             return;
600         }
601
602         // Parse the images
603         const dyld3::json::Node& imagesNode = dyld3::json::getRequiredValue(diags, mapNode, "images");
604         if (diags.hasError())
605             return;
606         if (imagesNode.array.empty()) {
607             diags.error("Images node is not an array\n");
608             return;
609         }
610
611         for (const dyld3::json::Node& imageNode : imagesNode.array) {
612             const dyld3::json::Node& pathNode = dyld3::json::getRequiredValue(diags, imageNode, "path");
613             if (pathNode.value.empty()) {
614                 diags.error("Image path node is not a string\n");
615                 return;
616             }
617             baselineDylibs.insert(pathNode.value);
618         }
619     }
620
621     std::vector<std::pair<const void*, size_t>> mappedFiles;
622     loadMRMFiles(diags, sharedCacheBuilder, inputFiles, mappedFiles, baselineDylibs);
623
624     if (diags.hasError())
625         return;
626
627     // Parse the symlinks if we have them
628     if (symlinksNode) {
629         if (symlinksNode->array.empty()) {
630             diags.error("Build options symlinks node is not an array\n");
631             return;
632         }
633         for (const dyld3::json::Node& symlinkNode : symlinksNode->array) {
634             const std::string& fromPath = dyld3::json::parseRequiredString(diags, dyld3::json::getRequiredValue(diags, symlinkNode, "path")).c_str();
635             const std::string& toPath   = dyld3::json::parseRequiredString(diags, dyld3::json::getRequiredValue(diags, symlinkNode, "target")).c_str();
636             addSymlink(sharedCacheBuilder, fromPath.c_str(), toPath.c_str());
637         }
638     }
639
640     if (diags.hasError())
641         return;
642
643     // Don't create a directory if we are skipping writes, which means we have no dstRoot set
644     if (!options.dstRoot.empty())
645         (void)mkpath_np((options.dstRoot + "/System/Library/Caches/com.apple.dyld/").c_str(), 0755);
646
647     // Actually build the cache.
648     bool cacheBuildSuccess = runSharedCacheBuilder(sharedCacheBuilder);
649
650     // Compare this cache to the baseline cache and see if we have any roots to copy over
651     if (!options.baselineDifferenceResultPath.empty() || options.baselineCopyRoots) {
652         std::set<std::string> newDylibs;
653         if (cacheBuildSuccess) {
654             uint64_t fileResultCount = 0;
655             if (const char* const* fileResults = getFilesToRemove(sharedCacheBuilder, &fileResultCount)) {
656                 for (uint64_t i = 0; i != fileResultCount; ++i)
657                     newDylibs.insert(fileResults[i]);
658             }
659         }
660
661         if (options.baselineCopyRoots) {
662             // Work out the set of dylibs in the old cache but not the new one
663             std::set<std::string> dylibsMissingFromNewCache;
664             for (const std::string& baselineDylib : baselineDylibs) {
665                 if (!newDylibs.count(baselineDylib))
666                     dylibsMissingFromNewCache.insert(baselineDylib);
667             }
668
669             if (!dylibsMissingFromNewCache.empty()) {
670                 BOMCopier copier = BOMCopierNewWithSys(BomSys_default());
671                 BOMCopierSetUserData(copier, (void*)&dylibsMissingFromNewCache);
672                 BOMCopierSetCopyFileStartedHandler(copier, filteredCopyIncludingPaths);
673                 std::string dylibCacheRootDir = realFilePath(options.dylibCacheDir);
674                 if (dylibCacheRootDir == "") {
675                     fprintf(stderr, "Could not find dylib Root directory to copy baseline roots from\n");
676                     exit(1);
677                 }
678                 BOMCopierCopy(copier, dylibCacheRootDir.c_str(), options.dstRoot.c_str());
679                 BOMCopierFree(copier);
680
681                 for (const std::string& dylibMissingFromNewCache : dylibsMissingFromNewCache) {
682                     diags.verbose("Dylib missing from new cache: '%s'\n", dylibMissingFromNewCache.c_str());
683                 }
684             }
685         }
686
687         if (!options.baselineDifferenceResultPath.empty()) {
688             auto cppToObjStr = [](const std::string& str) {
689                 return [NSString stringWithUTF8String:str.c_str()];
690             };
691
692             // Work out the set of dylibs in the cache and taken from the -root
693             NSMutableArray<NSString*>* dylibsFromRoots = [NSMutableArray array];
694             for (auto& root : options.roots) {
695                 for (const std::string& dylibInstallName : newDylibs) {
696                     struct stat sb;
697                     std::string filePath = root + "/" + dylibInstallName;
698                     if (!stat(filePath.c_str(), &sb)) {
699                         [dylibsFromRoots addObject:cppToObjStr(dylibInstallName)];
700                     }
701                 }
702             }
703
704             // Work out the set of dylibs in the new cache but not in the baseline cache.
705             NSMutableArray<NSString*>* dylibsMissingFromBaselineCache = [NSMutableArray array];
706             for (const std::string& newDylib : newDylibs) {
707                 if (!baselineDylibs.count(newDylib))
708                     [dylibsMissingFromBaselineCache addObject:cppToObjStr(newDylib)];
709             }
710
711             NSMutableDictionary* cacheDict = [[NSMutableDictionary alloc] init];
712             cacheDict[@"root-paths-in-cache"] = dylibsFromRoots;
713             cacheDict[@"device-paths-to-delete"] = dylibsMissingFromBaselineCache;
714
715             NSError* error = nil;
716             NSData*  outData = [NSPropertyListSerialization dataWithPropertyList:cacheDict
717                                                                           format:NSPropertyListBinaryFormat_v1_0
718                                                                          options:0
719                                                                            error:&error];
720             (void)[outData writeToFile:cppToObjStr(options.baselineDifferenceResultPath) atomically:YES];
721         }
722     }
723
724     writeMRMResults(cacheBuildSuccess, sharedCacheBuilder, options);
725
726     destroySharedCacheBuilder(sharedCacheBuilder);
727
728     unloadMRMFiles(mappedFiles);
729 }
730
731 int main(int argc, const char* argv[])
732 {
733     @autoreleasepool {
734         __block Diagnostics diags;
735         SharedCacheBuilderOptions options;
736         std::string jsonManifestPath;
737         char* tempRootsDir = strdup("/tmp/dyld_shared_cache_builder.XXXXXX");
738
739         mkdtemp(tempRootsDir);
740
741         for (int i = 1; i < argc; ++i) {
742             const char* arg = argv[i];
743             if (arg[0] == '-') {
744                 if (strcmp(arg, "-debug") == 0) {
745                     diags = Diagnostics(true);
746                     options.debug = true;
747                 } else if (strcmp(arg, "-list_configs") == 0) {
748                     options.listConfigs = true;
749                 } else if (strcmp(arg, "-root") == 0) {
750                     std::string realpath = realPath(argv[++i]);
751                     if ( realpath.empty() || !fileExists(realpath) ) {
752                         fprintf(stderr, "-root path doesn't exist: %s\n", argv[i]);
753                         exit(-1);
754                     }
755                     options.roots.insert(realpath);
756                 } else if (strcmp(arg, "-copy_roots") == 0) {
757                     options.copyRoots = true;
758                 } else if (strcmp(arg, "-dylib_cache") == 0) {
759                     options.dylibCacheDir = realPath(argv[++i]);
760                 } else if (strcmp(arg, "-artifact") == 0) {
761                     options.artifactDir = realPath(argv[++i]);
762                 } else if (strcmp(arg, "-no_development_cache") == 0) {
763                     options.emitDevCaches = false;
764                 } else if (strcmp(arg, "-no_overflow_dylibs") == 0) {
765                     options.emitElidedDylibs = false;
766                 } else if (strcmp(arg, "-development_cache") == 0) {
767                     options.emitDevCaches = true;
768                 } else if (strcmp(arg, "-overflow_dylibs") == 0) {
769                     options.emitElidedDylibs = true;
770                 } else if (strcmp(arg, "-mrm") == 0) {
771                     options.useMRM = true;
772                 } else if (strcmp(arg, "-emit_json") == 0) {
773                     options.emitJSONPath = realPath(argv[++i]);
774                 } else if (strcmp(arg, "-json_manifest") == 0) {
775                     jsonManifestPath = realPath(argv[++i]);
776                 } else if (strcmp(arg, "-build_all") == 0) {
777                     options.buildAllPath = realPath(argv[++i]);
778                 } else if (strcmp(arg, "-dst_root") == 0) {
779                     options.dstRoot = realPath(argv[++i]);
780                 } else if (strcmp(arg, "-release") == 0) {
781                     options.release = argv[++i];
782                 } else if (strcmp(arg, "-results") == 0) {
783                     options.resultPath = realPath(argv[++i]);
784                 } else if (strcmp(arg, "-baseline_diff_results") == 0) {
785                     options.baselineDifferenceResultPath = realPath(argv[++i]);
786                 } else if (strcmp(arg, "-baseline_copy_roots") == 0) {
787                     options.baselineCopyRoots = true;
788                 } else if (strcmp(arg, "-baseline_cache_map") == 0) {
789                     options.baselineCacheMapPath = realPath(argv[++i]);
790                 } else {
791                     //usage();
792                     fprintf(stderr, "unknown option: %s\n", arg);
793                     exit(-1);
794                 }
795             } else {
796                 fprintf(stderr, "unknown option: %s\n", arg);
797                 exit(-1);
798             }
799         }
800         (void)options.emitElidedDylibs; // not implemented yet
801
802         time_t mytime = time(0);
803         fprintf(stderr, "Started: %s", asctime(localtime(&mytime)));
804         processRoots(diags, options.roots, tempRootsDir);
805
806         struct rlimit rl = { OPEN_MAX, OPEN_MAX };
807         (void)setrlimit(RLIMIT_NOFILE, &rl);
808
809         if (options.dylibCacheDir.empty() && options.artifactDir.empty() && options.release.empty()) {
810             fprintf(stderr, "you must specify either -dylib_cache, -artifact or -release\n");
811             exit(-1);
812         } else if (!options.dylibCacheDir.empty() && !options.release.empty()) {
813             fprintf(stderr, "you may not use -dylib_cache and -release at the same time\n");
814             exit(-1);
815         } else if (!options.dylibCacheDir.empty() && !options.artifactDir.empty()) {
816             fprintf(stderr, "you may not use -dylib_cache and -artifact at the same time\n");
817             exit(-1);
818         }
819
820         if (jsonManifestPath.empty() && options.buildAllPath.empty()) {
821             fprintf(stderr, "Must specify a -json_manifest path OR a -build_all path\n");
822             exit(-1);
823         }
824
825         if (!options.buildAllPath.empty()) {
826             if (!options.dstRoot.empty()) {
827                 fprintf(stderr, "Cannot combine -dst_root and -build_all\n");
828                 exit(-1);
829             }
830             if (!jsonManifestPath.empty()) {
831                 fprintf(stderr, "Cannot combine -json_manifest and -build_all\n");
832                 exit(-1);
833             }
834             if (!options.baselineDifferenceResultPath.empty()) {
835                 fprintf(stderr, "Cannot combine -baseline_diff_results and -build_all\n");
836                 exit(-1);
837             }
838             if (options.baselineCopyRoots) {
839                 fprintf(stderr, "Cannot combine -baseline_copy_roots and -build_all\n");
840                 exit(-1);
841             }
842             if (!options.baselineCacheMapPath.empty()) {
843                 fprintf(stderr, "Cannot combine -baseline_cache_map and -build_all\n");
844                 exit(-1);
845             }
846         } else if (!options.listConfigs) {
847             if (options.dstRoot.empty()) {
848                 fprintf(stderr, "Must specify a valid -dst_root OR -list_configs\n");
849                 exit(-1);
850             }
851
852             if (jsonManifestPath.empty()) {
853                 fprintf(stderr, "Must specify a -json_manifest path OR -list_configs\n");
854                 exit(-1);
855             }
856         }
857
858         if (!options.baselineDifferenceResultPath.empty() && (options.roots.size() > 1)) {
859             fprintf(stderr, "Cannot use -baseline_diff_results with more that one -root\n");
860             exit(-1);
861         }
862
863         // Some options don't work with a JSON manifest
864         if (!jsonManifestPath.empty()) {
865             if (!options.resultPath.empty()) {
866                 fprintf(stderr, "Cannot use -results with -json_manifest\n");
867                 exit(-1);
868             }
869             if (!options.baselineDifferenceResultPath.empty() && options.baselineCacheMapPath.empty()) {
870                 fprintf(stderr, "Must use -baseline_cache_map with -baseline_diff_results when using -json_manifest\n");
871                 exit(-1);
872             }
873             if (options.baselineCopyRoots && options.baselineCacheMapPath.empty()) {
874                 fprintf(stderr, "Must use -baseline_cache_map with -baseline_copy_roots when using -json_manifest\n");
875                 exit(-1);
876             }
877         } else {
878             if (!options.baselineCacheMapPath.empty()) {
879                 fprintf(stderr, "Cannot use -baseline_cache_map without -json_manifest\n");
880                 exit(-1);
881             }
882         }
883
884         if (!options.baselineCacheMapPath.empty()) {
885             if (options.baselineDifferenceResultPath.empty() && options.baselineCopyRoots) {
886                 fprintf(stderr, "Must use -baseline_cache_map with -baseline_diff_results or -baseline_copy_roots\n");
887                 exit(-1);
888             }
889         }
890
891         // Find all the JSON files if we use -build_all
892         __block std::vector<std::string> jsonPaths;
893         if (!options.buildAllPath.empty()) {
894             struct stat stat_buf;
895             if (stat(options.buildAllPath.c_str(), &stat_buf) != 0) {
896                 fprintf(stderr, "Could not find -build_all path '%s'\n", options.buildAllPath.c_str());
897                 exit(-1);
898             }
899
900             if ( (stat_buf.st_mode & S_IFMT) != S_IFDIR ) {
901                 fprintf(stderr, "-build_all path is not a directory '%s'\n", options.buildAllPath.c_str());
902                 exit(-1);
903             }
904
905             auto processFile = ^(const std::string& path, const struct stat& statBuf) {
906                 if ( !endsWith(path, ".json") )
907                     return;
908
909                 jsonPaths.push_back(path);
910             };
911
912             iterateDirectoryTree("", options.buildAllPath,
913                                  ^(const std::string& dirPath) { return false; },
914                                  processFile, true /* process files */, false /* recurse */);
915
916             if (jsonPaths.empty()) {
917                 fprintf(stderr, "Didn't find any .json files inside -build_all path: %s\n", options.buildAllPath.c_str());
918                 exit(-1);
919             }
920
921             if (options.listConfigs) {
922                 for (const std::string& path : jsonPaths) {
923                     fprintf(stderr, "Found config: %s\n", path.c_str());
924                 }
925                 exit(-1);
926             }
927         }
928
929         if (!options.artifactDir.empty()) {
930             // Find the dylib cache dir from inside the artifact dir
931             struct stat stat_buf;
932             if (stat(options.artifactDir.c_str(), &stat_buf) != 0) {
933                 fprintf(stderr, "Could not find artifact path '%s'\n", options.artifactDir.c_str());
934                 exit(-1);
935             }
936             std::string dir = options.artifactDir + "/AppleInternal/Developer/DylibCaches";
937             if (stat(dir.c_str(), &stat_buf) != 0) {
938                 fprintf(stderr, "Could not find artifact path '%s'\n", dir.c_str());
939                 exit(-1);
940             }
941
942             if (!options.release.empty()) {
943                 // Use the given release
944                 options.dylibCacheDir = dir + "/" + options.release + ".dlc";
945             } else {
946                 // Find a release directory
947                 __block std::vector<std::string> subDirectories;
948                 iterateDirectoryTree("", dir, ^(const std::string& dirPath) {
949                     subDirectories.push_back(dirPath);
950                     return false;
951                 }, nullptr, false, false);
952
953                 if (subDirectories.empty()) {
954                     fprintf(stderr, "Could not find dlc subdirectories inside '%s'\n", dir.c_str());
955                     exit(-1);
956                 }
957
958                 if (subDirectories.size() > 1) {
959                     fprintf(stderr, "Found too many subdirectories inside artifact path '%s'.  Use -release to select one\n", dir.c_str());
960                     exit(-1);
961                 }
962
963                 options.dylibCacheDir = subDirectories.front();
964             }
965         }
966
967         if (options.dylibCacheDir.empty()) {
968             options.dylibCacheDir = std::string("/AppleInternal/Developer/DylibCaches/") + options.release + ".dlc";
969         }
970
971         //Move into the dir so we can use relative path manifests
972         chdir(options.dylibCacheDir.c_str());
973
974         dispatch_async(dispatch_get_main_queue(), ^{
975             if (!options.buildAllPath.empty()) {
976                 bool requiresConcurrencyLimit = false;
977                 dispatch_semaphore_t concurrencyLimit = NULL;
978                 // Try build 1 cache per 8GB of RAM
979                 uint64_t memSize = 0;
980                 size_t sz = sizeof(memSize);
981                 if ( sysctlbyname("hw.memsize", &memSize, &sz, NULL, 0) == 0 ) {
982                     uint64_t maxThreads = std::max(memSize / 0x200000000ULL, 1ULL);
983                     fprintf(stderr, "Detected %lldGb or less of memory, limiting concurrency to %lld threads\n",
984                             memSize / (1 << 30), maxThreads);
985                     requiresConcurrencyLimit = true;
986                     concurrencyLimit = dispatch_semaphore_create(maxThreads);
987                 }
988
989                 dispatch_apply(jsonPaths.size(), DISPATCH_APPLY_AUTO, ^(size_t index) {
990                     // Horrible hack to limit concurrency in low spec build machines.
991                     if (requiresConcurrencyLimit) { dispatch_semaphore_wait(concurrencyLimit, DISPATCH_TIME_FOREVER); }
992
993                     const std::string& jsonPath = jsonPaths[index];
994                     buildCacheFromJSONManifest(diags, options, jsonPath);
995
996                     if (requiresConcurrencyLimit) { dispatch_semaphore_signal(concurrencyLimit); }
997                 });
998             } else {
999                 buildCacheFromJSONManifest(diags, options, jsonManifestPath);
1000             }
1001
1002             const char* args[8];
1003             args[0] = (char*)"/bin/rm";
1004             args[1] = (char*)"-rf";
1005             args[2] = (char*)tempRootsDir;
1006             args[3] = nullptr;
1007             (void)runCommandAndWait(diags, args);
1008
1009             if (diags.hasError()) {
1010                 fprintf(stderr, "dyld_shared_cache_builder: error: %s", diags.errorMessage().c_str());
1011                 exit(-1);
1012             }
1013
1014             for (const std::string& warn : diags.warnings()) {
1015                 fprintf(stderr, "dyld_shared_cache_builder: warning: %s\n", warn.c_str());
1016             }
1017
1018             // Finally, write the roots.txt to tell us which roots we pulled in
1019             if (!options.dstRoot.empty())
1020                 writeRootList(options.dstRoot + "/System/Library/Caches/com.apple.dyld", options.roots);
1021             exit(0);
1022         });
1023     }
1024
1025     dispatch_main();
1026
1027     return 0;
1028 }