dyld-433.5.tar.gz
[apple/dyld.git] / interlinked-dylibs / 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 <dispatch/dispatch.h>
26 #include <Security/Security.h>
27 #include <Security/SecCodeSigner.h>
28
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <sys/mman.h>
32 #include <sys/resource.h>
33 #include <mach/mach.h>
34 #include <mach/mach_time.h>
35 #include <limits.h>
36 #include <stdarg.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <math.h>
40 #include <fcntl.h>
41 #include <dlfcn.h>
42 #include <signal.h>
43 #include <errno.h>
44 #include <sys/uio.h>
45 #include <unistd.h>
46 #include <sys/param.h>
47 #include <sys/sysctl.h>
48 #include <sys/resource.h>
49 #include <dirent.h>
50 #include <libgen.h>
51 #include <pthread.h>
52 #include <fts.h>
53
54 #include <vector>
55 #include <array>
56 #include <set>
57 #include <map>
58 #include <algorithm>
59
60 #include <spawn.h>
61
62 #include <Bom/Bom.h>
63
64 #include "mega-dylib-utils.h"
65
66 #include "MultiCacheBuilder.h"
67
68 #include "MachOProxy.h"
69 #include "Manifest.h"
70 #include "Logging.h"
71
72 #if !__has_feature(objc_arc)
73 #error The use of libdispatch in this files requires it to be compiled with ARC in order to avoid leaks
74 #endif
75
76 extern char** environ;
77
78 static dispatch_queue_t build_queue;
79
80 inline bool has_suffix(std::string const & value, std::string const & ending)
81 {
82         if (ending.size() > value.size()) return false;
83         return std::equal(ending.rbegin(), ending.rend(), value.rbegin());
84 }
85
86 static const char *tempRootDirTemplate = "/tmp/dyld_shared_cache_builder.XXXXXX";
87 static char *tempRootDir = nullptr;
88
89 void processRoot( const std::string &root, std::set<std::string> &roots ) {
90     struct stat sb;
91     int res = 0;
92         pid_t pid;
93         int status;
94         const char* args[8];
95
96         res = stat(root.c_str(), &sb);
97
98         if (res == 0 && S_ISDIR(sb.st_mode)) {
99             roots.insert( root );
100             return;
101         } else if ( has_suffix( root, ".cpio" ) || has_suffix( root, ".cpio.gz" ) || has_suffix( root, ".cpgz" ) ||
102                     has_suffix( root, ".cpio.bz2" ) || has_suffix( root, ".cpbz2" ) || has_suffix( root, ".pax" ) ||
103                     has_suffix( root, ".pax.gz" ) || has_suffix( root, ".pgz" ) || has_suffix( root, ".pax.bz2" ) ||
104                     has_suffix( root, ".pbz2" ) ) {
105                 args[0] = (char *)"/usr/bin/ditto";
106                 args[1] = (char *)"-x";
107                 args[2] = (char *)root.c_str();
108                 args[3] = tempRootDir;
109                 args[4] = nullptr;
110         } else if (has_suffix(root, ".tar")) {
111                 args[0] = (char *)"/usr/bin/tar";
112                 args[1] = (char *)"xf";
113                 args[2] = (char *)root.c_str();
114                 args[3] = (char *)"-C";
115                 args[4] = tempRootDir;
116                 args[5] = nullptr;
117         } else if (has_suffix(root, ".tar.gz") || has_suffix(root, ".tgz")) {
118                 args[0] = (char *)"/usr/bin/tar";
119                 args[1] = (char *)"xzf";
120                 args[2] = (char *)root.c_str();
121                 args[3] = (char *)"-C";
122                 args[4] = tempRootDir;
123                 args[5] = nullptr;
124         } else if (has_suffix(root, ".tar.bz2")
125                            || has_suffix(root, ".tbz2")
126                            || has_suffix(root, ".tbz")) {
127                 args[0] = (char *)"/usr/bin/tar";
128                 args[1] = (char *)"xjf";
129                 args[2] = (char *)root.c_str();
130                 args[3] = (char *)"-C";
131                 args[4] = tempRootDir;
132                 args[5] = nullptr;
133         } else if (has_suffix(root, ".xar")) {
134                 args[0] = (char *)"/usr/bin/xar";
135                 args[1] = (char *)"-xf";
136                 args[2] = (char *)root.c_str();
137                 args[3] = (char *)"-C";
138                 args[4] = tempRootDir;
139                 args[5] = nullptr;
140         } else if (has_suffix(root, ".zip")) {
141                 args[0] = (char *)"/usr/bin/ditto";
142                 args[1] = (char *)"-xk";
143                 args[2] = (char *)root.c_str();
144                 args[3] = tempRootDir;
145                 args[4] = nullptr;
146         } else {
147                 terminate("unknown archive type: %s", root.c_str());
148         }
149
150         res = posix_spawn(&pid, args[0], nullptr, nullptr, (char**)args, environ);
151         if (res != 0) terminate("Failed to spawn %s: %s (%d)", args[0], strerror(res), res);
152
153         do {
154                 res = waitpid(pid, &status, 0);
155         } while (res == -1 && errno == EINTR);
156         if (res != -1) {
157                 if (WIFEXITED(status)) {
158                         res = WEXITSTATUS(status);
159                 } else {
160                         res = -1;
161                 }
162         }
163
164         if (res != 0) terminate("Could not expand archive %s: %s (%d)", root.c_str(), strerror(res), res);
165
166         for (auto &existingRoot : roots) {
167                 if (existingRoot == tempRootDir)
168                         return;
169         }
170
171         roots.insert( tempRootDir );
172 }
173
174 bool writeRootList( const std::string &dstRoot, const std::set<std::string> &roots ) {
175     if ( roots.size() == 0 ) return false;
176
177     std::string rootFile = dstRoot + "/roots.txt";
178         FILE* froots = ::fopen(rootFile.c_str(), "w");
179         if ( froots == NULL )
180                 return false;
181
182         for (auto &root : roots) {
183                         fprintf(froots, "%s\n", root.c_str());
184         }
185
186         ::fclose(froots);
187         return true;
188 }
189
190 std::string realPath(const std::string& path) {
191     char resolvedPath[PATH_MAX];
192     if (realpath( path.c_str(), &resolvedPath[0]) != nullptr)  {
193         return resolvedPath;
194     } else {
195         return "";
196     }
197 }
198
199 std::set<std::string> cachePaths;
200
201 BOMCopierCopyOperation filteredCopy(BOMCopier copier, const char *path, BOMFSObjType type, off_t size) {
202         std::string absolutePath = &path[1];
203         if (cachePaths.count(absolutePath)) {
204                 return BOMCopierSkipFile;
205         }
206         return BOMCopierContinue;
207 }
208
209 int main (int argc, const char * argv[]) {
210         @autoreleasepool {
211                 std::set<std::string>    roots;
212                 std::string              dylibCacheDir;
213                 std::string              release;
214                 bool                     emitDevCaches = true;
215                 bool                     emitElidedDylibs = true;
216                 bool                     listConfigs = false;
217                 bool                     copyRoots = false;
218                 std::string              dstRoot;
219                 std::string              configuration;
220                 std::string              resultPath;
221
222                 time_t mytime = time(0);
223                 log("Started: %s", asctime(localtime(&mytime)));
224
225                 tempRootDir = strdup(tempRootDirTemplate);
226                 mkdtemp(tempRootDir);
227
228                 for (int i = 1; i < argc; ++i) {
229                         const char* arg = argv[i];
230                         if (arg[0] == '-') {
231                                 if (strcmp(arg, "-debug") == 0) {
232                                         setVerbose(true);
233                                 } else if (strcmp(arg, "-list_configs") == 0) {
234                                         listConfigs = true;
235                                 } else if (strcmp(arg, "-root") == 0) {
236                                         std::string root = realPath(argv[++i]);
237                                         processRoot(root, roots);
238                                 } else if (strcmp(arg, "-copy_roots") == 0) {
239                                         copyRoots = true;
240                                 } else if (strcmp(arg, "-dylib_cache") == 0) {
241                                         dylibCacheDir = realPath(argv[++i]);
242                                 } else if (strcmp(arg, "-no_development_cache") == 0) {
243                                         emitDevCaches = false;
244                                 } else if (strcmp(arg, "-no_overflow_dylibs") == 0) {
245                                         emitElidedDylibs = false;
246                                 } else if (strcmp(arg, "-development_cache") == 0) {
247                                         emitDevCaches = true;
248                                 } else if (strcmp(arg, "-overflow_dylibs") == 0) {
249                                         emitElidedDylibs = true;
250                                 } else if (strcmp(arg, "-dst_root") == 0) {
251                                         dstRoot = realPath(argv[++i]);
252                                 } else if (strcmp(arg, "-release") == 0) {
253                                         release = argv[++i];
254                                 } else if (strcmp(arg, "-results") == 0) {
255                                         resultPath = realPath(argv[++i]);
256                                 } else {
257                                         //usage();
258                                         terminate("unknown option: %s\n", arg);
259                                 }
260                         } else {
261                                 if (!configuration.empty()) {
262                                         terminate("You may only specify one configruation");
263                                 }
264                                 configuration = argv[i];
265                         }
266                 }
267
268                 struct rlimit rl = {OPEN_MAX, OPEN_MAX};
269                 (void)setrlimit(RLIMIT_NOFILE, &rl);
270
271                 if (dylibCacheDir.empty() && release.empty()) {
272                         terminate("you must specify either -dylib_cache or -release");
273                 } else if (!dylibCacheDir.empty() && !release.empty()) {
274                         terminate("you may not use -dylib_cache and -release at the same time");
275                 }
276
277                 if ((configuration.empty() || dstRoot.empty()) && !listConfigs) {
278                         terminate("Must specify a configuration and a -dst_root OR -list_configs\n");
279                 }
280
281
282                 if (dylibCacheDir.empty()) {
283                         dylibCacheDir = std::string("/AppleInternal/Developer/DylibCaches/") + release + ".dlc";
284                 }
285
286                 //Move into the dir so we can use relative path manifests
287                 chdir(dylibCacheDir.c_str());
288
289                 auto manifest = Manifest(dylibCacheDir + "/Manifest.plist", roots);
290
291         if (manifest.build().empty()) {
292             terminate("No manifest found at '%s/Manifest.plist'\n", dylibCacheDir.c_str());
293                 }
294         log("Building Caches for %s", manifest.build().c_str());
295
296         if (listConfigs) {
297             manifest.forEachConfiguration([](const std::string& configName) {
298                 printf("%s\n", configName.c_str());
299             });
300             exit(0);
301                 }
302
303         if (!manifest.filterForConfig(configuration)) {
304             terminate("No config %s. Please run with -list_configs to see configurations available for this %s.\n",
305                 configuration.c_str(), manifest.build().c_str());
306         }
307         manifest.calculateClosure(false);
308         manifest.checkLinks();
309
310         // FIXME: Plumb through no_development
311
312         std::shared_ptr<MultiCacheBuilder> builder = std::make_shared<MultiCacheBuilder>(manifest, false, false, true);
313         builder->buildCaches(dstRoot);
314         writeRootList(dstRoot, roots);
315
316         if (copyRoots) {
317             manifest.forEachConfiguration([&manifest](const std::string& configName) {
318                 for (auto& arch : manifest.configuration(configName).architectures) {
319                     for (auto& dylib : arch.second.results.dylibs) {
320                         if (dylib.second.included) {
321                             MachOProxy* proxy = MachOProxy::forIdentifier(dylib.first, arch.first);
322                             cachePaths.insert(proxy->installName);
323                             for (auto& alias : proxy->installNameAliases) {
324                                 cachePaths.insert(alias);
325                             }
326                         }
327                     }
328                 }
329             });
330
331             BOMCopier copier = BOMCopierNewWithSys(BomSys_default());
332             BOMCopierSetCopyFileStartedHandler(copier, filteredCopy);
333             for (auto& root : roots) {
334                 BOMCopierCopy(copier, root.c_str(), dstRoot.c_str());
335                         }
336                         BOMCopierFree(copier);
337                 }
338
339         // Create an empty FIPS data in the root
340         (void)mkpath_np((dstRoot + "/private/var/db/FIPS/").c_str(), 0755);
341         int fd = open((dstRoot + "/private/var/db/FIPS/fips_data").c_str(), O_CREAT | O_TRUNC, 0644);
342         close(fd);
343
344                 // Now that all the build commands have been issued lets put a barrier in after then which can tear down the app after
345                 // everything is written.
346
347                 builder->logStats();
348                 if (!resultPath.empty()) {
349                         manifest.write(resultPath);
350                 }
351
352                 pid_t       pid;
353                 int         res = 0;
354                 int         status;
355                 const char* args[8];
356
357                 args[0] = (char*)"/bin/rm";
358                 args[1] = (char*)"-rf";
359                 args[2] = (char*)tempRootDir;
360         args[3] = nullptr;
361
362         res = posix_spawn(&pid, args[0], nullptr, nullptr, (char**)args, environ);
363                 if (res != 0)
364                         terminate("Failed to spawn %s: %s (%d)", args[0], strerror(res), res);
365
366                 do {
367                         res = waitpid(pid, &status, 0);
368                 } while (res == -1 && errno == EINTR);
369                 if (res != -1) {
370                         if (WIFEXITED(status)) {
371                                 res = WEXITSTATUS(status);
372                         } else {
373                                 res = -1;
374                         }
375                 }
376
377                 dumpLogAndExit();
378         }
379
380     dispatch_main();
381
382     return 0;
383 }