]> git.saurik.com Git - apple/dyld.git/blob - interlinked-dylibs/update_dyld_shared_cache.mm
bd7fbeaa277e3004c46ce295be3bcf6f0657b32c
[apple/dyld.git] / interlinked-dylibs / update_dyld_shared_cache.mm
1 /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*-
2 *
3 * Copyright (c) 2014 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 <mach/mach.h>
29 #include <mach/mach_time.h>
30 #include <limits.h>
31 #include <stdarg.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <math.h>
35 #include <fcntl.h>
36 #include <dlfcn.h>
37 #include <signal.h>
38 #include <errno.h>
39 #include <sys/uio.h>
40 #include <unistd.h>
41 #include <sys/param.h>
42 #include <sys/sysctl.h>
43 #include <sys/resource.h>
44 #include <dirent.h>
45 #include <rootless.h>
46
47 extern "C" {
48 #include <dscsym.h>
49 }
50
51 #include <vector>
52 #include <set>
53 #include <map>
54 #include <iostream>
55 #include <fstream>
56
57 #include "mega-dylib-utils.h"
58 #include "MultiCacheBuilder.h"
59 #include "MachOProxy.h"
60 #include "Manifest.h"
61 #include "Logging.h"
62
63 #if !__has_feature(objc_arc)
64 #error The use of libdispatch in this files requires it to be compiled with ARC in order to avoid leaks
65 #endif
66
67 const std::string anchorsDirPath = "/private/var/db/dyld/shared_region_roots";
68
69 bool parsePathsFile( const std::string& filePath, std::set<std::string>& paths ) {
70 verboseLog( "parsing .paths file '%s'", filePath.c_str() );
71 std::ifstream myfile( filePath );
72 if ( myfile.is_open() ) {
73 std::string line;
74 while ( std::getline(myfile, line) ) {
75 size_t pos = line.find('#');
76 if ( pos != std::string::npos )
77 line.resize(pos);
78 while ( line.size() != 0 && isspace(line.back()) ) {
79 line.pop_back();
80 }
81 if ( !line.empty() ) paths.insert( line );
82 }
83 myfile.close();
84
85 return true;
86 }
87 return false;
88 }
89
90 static bool parseDirectoryOfPathsFiles(const std::string& dirPath, std::set<std::string>& paths)
91 {
92 DIR* dir = ::opendir(dirPath.c_str());
93 if ( dir == NULL )
94 return false;
95
96 for (dirent* entry = ::readdir(dir); entry != NULL; entry = ::readdir(dir)) {
97 if ( entry->d_type == DT_REG || entry->d_type == DT_UNKNOWN ) {
98 // only look at regular files ending in .paths
99 if ( strcmp(&entry->d_name[entry->d_namlen-6], ".paths") == 0 ) {
100 struct stat statBuf;
101 std::string filePathStr = dirPath + "/" + entry->d_name;
102 const char* filePath = filePathStr.c_str();
103 if ( lstat(filePath, &statBuf) == -1 ) {
104 warning("can't access file '%s'", filePath);
105 }
106 else if ( S_ISREG(statBuf.st_mode) ) {
107 parsePathsFile(filePath, paths);
108 }
109 else {
110 warning("not a regular file '%s'", filePath);
111 }
112 }
113 else {
114 warning("ignoring file with wrong extension '%s'", entry->d_name);
115 }
116 }
117 }
118 ::closedir(dir);
119 return true;
120 }
121
122 static bool buildInitialPaths(const std::string& volumeRootPath, const std::string& overlayPath, std::set<std::string>& paths)
123 {
124 // in -root mode, look for roots in /rootpath/private/var/db/dyld/shared_region_roots
125 if ( volumeRootPath != "/" ) {
126 if ( parseDirectoryOfPathsFiles(volumeRootPath + "/" + anchorsDirPath, paths) )
127 return true;
128 // fallback to .paths files on boot volume
129 return parseDirectoryOfPathsFiles(anchorsDirPath, paths);
130 }
131
132 // in -overlay mode, look for .paths first in each /overlay/private/var/db/dyld/shared_region_roots
133 if ( !overlayPath.empty() ) {
134 parseDirectoryOfPathsFiles(overlayPath + "/" + anchorsDirPath, paths);
135 }
136
137 // look for .paths files in /private/var/db/dyld/shared_region_roots
138 return parseDirectoryOfPathsFiles(anchorsDirPath, paths);
139 }
140
141 bool fileExists(const std::string& path, bool& isSymLink)
142 {
143 struct stat statBuf;
144 if ( lstat(path.c_str(), &statBuf) == -1 )
145 return false;
146 isSymLink = S_ISLNK(statBuf.st_mode);
147 return S_ISREG(statBuf.st_mode) || isSymLink;
148 }
149
150 bool tryPath(const std::string& prefix, const std::string& path, std::string& foundPath, std::vector<std::string>& aliases)
151 {
152 foundPath = prefix + path;
153 bool isSymLink;
154 if ( !fileExists(foundPath, isSymLink) )
155 return false;
156 if ( isSymLink ) {
157 // handle case where install name is a symlink to real file (e.g. libstdc++.6.dylib -> libstdc++.6.0.9.dylib)
158 char pathInSymLink[MAXPATHLEN];
159 long len = ::readlink(foundPath.c_str(), pathInSymLink, sizeof(pathInSymLink));
160 if ( len != -1 ) {
161 pathInSymLink[len] = '\0';
162 if ( pathInSymLink[0] != '/' ) {
163 std::string aliasPath = path;
164 size_t pos = aliasPath.rfind('/');
165 if ( pos != std::string::npos ) {
166 std::string newPath = aliasPath.substr(0,pos+1) + pathInSymLink;
167 aliases.push_back(newPath);
168 }
169 }
170 }
171 }
172 char realPath[MAXPATHLEN];
173 if ( ::realpath(foundPath.c_str(), realPath) ) {
174 if ( foundPath != realPath ) {
175 std::string altPath = realPath;
176 if ( !prefix.empty() ) {
177 if ( altPath.substr(0, prefix.size()) == prefix ) {
178 altPath = altPath.substr(prefix.size());
179 }
180 }
181 if ( altPath != path )
182 aliases.push_back(path);
183 else
184 aliases.push_back(altPath);
185 }
186 }
187 return true;
188 }
189
190 bool improvePath(const char* volumeRootPath, const std::vector<const char*>& overlayPaths,
191 const std::string& path, std::string& foundPath, std::vector<std::string>& aliases)
192 {
193 for (const char* overlay : overlayPaths) {
194 if ( tryPath(overlay, path, foundPath, aliases) )
195 return true;
196 }
197 if ( volumeRootPath[0] != '\0' ) {
198 if ( tryPath(volumeRootPath, path, foundPath, aliases) )
199 return true;
200 }
201 return tryPath("", path, foundPath, aliases);
202 }
203
204 static bool runningOnHaswell()
205 {
206 // check system is capable of running x86_64h code
207 struct host_basic_info info;
208 mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT;
209 mach_port_t hostPort = mach_host_self();
210 kern_return_t result = host_info(hostPort, HOST_BASIC_INFO, (host_info_t)&info, &count);
211 mach_port_deallocate(mach_task_self(), hostPort);
212
213 return ( (result == KERN_SUCCESS) && (info.cpu_subtype == CPU_SUBTYPE_X86_64_H) );
214 }
215
216
217 #define TERMINATE_IF_LAST_ARG( s ) \
218 do { \
219 if ( i == argc - 1 ) terminate( s ); \
220 } while ( 0 )
221
222 int main(int argc, const char* argv[])
223 {
224 std::string rootPath;
225 std::string overlayPath;
226 std::string platform = "osx";
227 std::string dylibListFile;
228 bool universal = false;
229 bool force = false;
230 std::string cacheDir;
231 std::set<std::string> archStrs;
232 std::vector<Manifest::Anchor> anchors;
233
234 // parse command line options
235 for (int i = 1; i < argc; ++i) {
236 const char* arg = argv[i];
237 if (arg[0] == '-') {
238 if (strcmp(arg, "-debug") == 0) {
239 setVerbose(true);
240 } else if (strcmp(arg, "-verbose") == 0) {
241 setVerbose(true);
242 } else if (strcmp(arg, "-dont_map_local_symbols") == 0) {
243 //We are going to ignore this
244 } else if (strcmp(arg, "-iPhone") == 0) {
245 platform = "iphoneos";
246 } else if (strcmp(arg, "-dylib_list") == 0) {
247 TERMINATE_IF_LAST_ARG("-dylib_list missing argument");
248 dylibListFile = argv[++i];
249 } else if ((strcmp(arg, "-root") == 0) || (strcmp(arg, "--root") == 0)) {
250 TERMINATE_IF_LAST_ARG("-root missing path argument\n");
251 rootPath = argv[++i];
252 } else if (strcmp(arg, "-overlay") == 0) {
253 TERMINATE_IF_LAST_ARG("-overlay missing path argument\n");
254 overlayPath = argv[++i];
255 } else if (strcmp(arg, "-cache_dir") == 0) {
256 TERMINATE_IF_LAST_ARG("-cache_dir missing path argument\n");
257 cacheDir = argv[++i];
258 } else if (strcmp(arg, "-arch") == 0) {
259 TERMINATE_IF_LAST_ARG("-arch missing argument\n");
260 archStrs.insert(argv[++i]);
261 } else if (strcmp(arg, "-force") == 0) {
262 force = true;
263 } else if (strcmp(arg, "-sort_by_name") == 0) {
264 //No-op, we always do this now
265 } else if (strcmp(arg, "-universal_boot") == 0) {
266 universal = true;
267 } else {
268 //usage();
269 terminate("unknown option: %s\n", arg);
270 }
271 } else {
272 //usage();
273 terminate("unknown option: %s\n", arg);
274 }
275 }
276
277 setReturnNonZeroOnTerminate();
278 setWarnAnErrorPrefixes("update_dyld_shared_cache: warning: ", "update_dyld_shared_cache failed: ");
279
280 if ( !rootPath.empty() & !overlayPath.empty() )
281 terminate("-root and -overlay cannot be used together\n");
282
283 //FIXME realpath on root and overlays
284
285 if (rootPath.empty()) {
286 rootPath = "/";
287 }
288
289 if ( cacheDir.empty() ) {
290 // write cache file into -root or -overlay directory, if used
291 if ( rootPath != "/" )
292 cacheDir = rootPath + MACOSX_DYLD_SHARED_CACHE_DIR;
293 else if ( !overlayPath.empty() )
294 cacheDir = overlayPath + MACOSX_DYLD_SHARED_CACHE_DIR;
295 else
296 cacheDir = MACOSX_DYLD_SHARED_CACHE_DIR;
297 }
298
299 if (universal) {
300 if ( platform == "iphoneos" ) {
301 terminate("-iPhoneOS and -universal are incompatible\n");
302 }
303 archStrs.clear();
304 }
305
306 if (archStrs.size() == 0) {
307 if ( platform == "iphoneos" ) {
308 terminate("Must specify -arch(s) when using -iPhone\n");
309 }
310 else {
311 if ( universal ) {
312 // <rdar://problem/26182089> -universal_boot should make all possible dyld caches
313 archStrs.insert("i386");
314 archStrs.insert("x86_64");
315 archStrs.insert("x86_64h");
316 }
317 else {
318 // just make caches for this machine
319 archStrs.insert("i386");
320 archStrs.insert(runningOnHaswell() ? "x86_64h" : "x86_64");
321 }
322 }
323 }
324
325 int err = mkpath_np(cacheDir.c_str(), S_IRWXU | S_IRGRP|S_IXGRP | S_IROTH|S_IXOTH);
326 if (err != 0 && err != EEXIST) {
327 terminate("mkpath_np fail: %d", err);
328 }
329
330 std::set<std::string> paths;
331
332 if ( !dylibListFile.empty() ) {
333 if ( !parsePathsFile( dylibListFile, paths ) ) {
334 terminate( "could not build intiial paths\n" );
335 }
336 } else if ( !buildInitialPaths( rootPath, overlayPath, paths ) ) {
337 terminate( "could not build intiial paths\n" );
338 }
339
340 Manifest manifest(archStrs, overlayPath, rootPath, paths);
341
342 manifest.setPlatform(platform);
343
344 // If the path we are writing to is trusted then our sources need to be trusted
345 // <rdar://problem/21166835> Can't update the update_dyld_shared_cache on a non-boot volume
346 bool requireDylibsBeRootlessProtected = isProtectedBySIP(cacheDir);
347 manifest.calculateClosure( requireDylibsBeRootlessProtected );
348
349 for (const std::string& archStr : archStrs) {
350 std::string cachePath = cacheDir + "/dyld_shared_cache_" + archStr;
351 if (!force && manifest.sameContentsAsCacheAtPath("localhost", archStr, cachePath)) {
352 manifest.remove("localhost", archStr);
353 verboseLog("%s is already up to date", cachePath.c_str());
354 }
355 }
356
357 if (manifest.empty()) {
358 dumpLogAndExit(false);
359 }
360
361 // build caches
362 std::shared_ptr<MultiCacheBuilder> builder = std::make_shared<MultiCacheBuilder>(manifest, false, false, false, false, requireDylibsBeRootlessProtected);
363 builder->buildCaches(cacheDir);
364
365 // Save off spintrace data
366 std::string nuggetRoot = (overlayPath.empty() ? rootPath : overlayPath);
367 (void)dscsym_save_nuggets_for_current_caches(nuggetRoot.c_str());
368
369 // Now that all the build commands have been issued lets put a barrier in after then which can tear down the app after
370 // everything is written.
371 builder->logStats();
372 dumpLogAndExit(false);
373
374 dispatch_main();
375 }