]> git.saurik.com Git - apple/dyld.git/blob - interlinked-dylibs/update_dyld_shared_cache.mm
dyld-421.2.tar.gz
[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 "MachOProxy.h"
58 #include "manifest.h"
59 #include "mega-dylib-utils.h"
60 #include "Logging.h"
61
62 #import "MultiCacheBuilder.h"
63
64 #if !__has_feature(objc_arc)
65 #error The use of libdispatch in this files requires it to be compiled with ARC in order to avoid leaks
66 #endif
67
68 const std::string anchorsDirPath = "/private/var/db/dyld/shared_region_roots";
69
70 bool parsePathsFile( const std::string& filePath, std::set<std::string>& paths ) {
71 verboseLog( "parsing .paths file '%s'", filePath.c_str() );
72 std::ifstream myfile( filePath );
73 if ( myfile.is_open() ) {
74 std::string line;
75 while ( std::getline(myfile, line) ) {
76 size_t pos = line.find('#');
77 if ( pos != std::string::npos )
78 line.resize(pos);
79 while ( line.size() != 0 && isspace(line.back()) ) {
80 line.pop_back();
81 }
82 if ( !line.empty() ) paths.insert( line );
83 }
84 myfile.close();
85
86 return true;
87 }
88 return false;
89 }
90
91 static bool parseDirectoryOfPathsFiles(const std::string& dirPath, std::set<std::string>& paths)
92 {
93 DIR* dir = ::opendir(dirPath.c_str());
94 if ( dir == NULL )
95 return false;
96
97 for (dirent* entry = ::readdir(dir); entry != NULL; entry = ::readdir(dir)) {
98 if ( entry->d_type == DT_REG || entry->d_type == DT_UNKNOWN ) {
99 // only look at regular files ending in .paths
100 if ( strcmp(&entry->d_name[entry->d_namlen-6], ".paths") == 0 ) {
101 struct stat statBuf;
102 std::string filePathStr = dirPath + "/" + entry->d_name;
103 const char* filePath = filePathStr.c_str();
104 if ( lstat(filePath, &statBuf) == -1 ) {
105 warning("can't access file '%s'", filePath);
106 }
107 else if ( S_ISREG(statBuf.st_mode) ) {
108 parsePathsFile(filePath, paths);
109 }
110 else {
111 warning("not a regular file '%s'", filePath);
112 }
113 }
114 else {
115 warning("ignoring file with wrong extension '%s'", entry->d_name);
116 }
117 }
118 }
119 ::closedir(dir);
120 return true;
121 }
122
123 static bool buildInitialPaths(const std::string& volumeRootPath, const std::string& overlayPath, std::set<std::string>& paths)
124 {
125 // in -root mode, look for roots in /rootpath/private/var/db/dyld/shared_region_roots
126 if ( volumeRootPath != "/" ) {
127 if ( parseDirectoryOfPathsFiles(volumeRootPath + "/" + anchorsDirPath, paths) )
128 return true;
129 // fallback to .paths files on boot volume
130 return parseDirectoryOfPathsFiles(anchorsDirPath, paths);
131 }
132
133 // in -overlay mode, look for .paths first in each /overlay/private/var/db/dyld/shared_region_roots
134 if ( !overlayPath.empty() ) {
135 parseDirectoryOfPathsFiles(overlayPath + "/" + anchorsDirPath, paths);
136 }
137
138 // look for .paths files in /private/var/db/dyld/shared_region_roots
139 return parseDirectoryOfPathsFiles(anchorsDirPath, paths);
140 }
141
142 bool fileExists(const std::string& path, bool& isSymLink)
143 {
144 struct stat statBuf;
145 if ( lstat(path.c_str(), &statBuf) == -1 )
146 return false;
147 isSymLink = S_ISLNK(statBuf.st_mode);
148 return S_ISREG(statBuf.st_mode) || isSymLink;
149 }
150
151 bool tryPath(const std::string& prefix, const std::string& path, std::string& foundPath, std::vector<std::string>& aliases)
152 {
153 foundPath = prefix + path;
154 bool isSymLink;
155 if ( !fileExists(foundPath, isSymLink) )
156 return false;
157 if ( isSymLink ) {
158 // handle case where install name is a symlink to real file (e.g. libstdc++.6.dylib -> libstdc++.6.0.9.dylib)
159 char pathInSymLink[MAXPATHLEN];
160 long len = ::readlink(foundPath.c_str(), pathInSymLink, sizeof(pathInSymLink));
161 if ( len != -1 ) {
162 pathInSymLink[len] = '\0';
163 if ( pathInSymLink[0] != '/' ) {
164 std::string aliasPath = path;
165 size_t pos = aliasPath.rfind('/');
166 if ( pos != std::string::npos ) {
167 std::string newPath = aliasPath.substr(0,pos+1) + pathInSymLink;
168 aliases.push_back(newPath);
169 }
170 }
171 }
172 }
173 char realPath[MAXPATHLEN];
174 if ( ::realpath(foundPath.c_str(), realPath) ) {
175 if ( foundPath != realPath ) {
176 std::string altPath = realPath;
177 if ( !prefix.empty() ) {
178 if ( altPath.substr(0, prefix.size()) == prefix ) {
179 altPath = altPath.substr(prefix.size());
180 }
181 }
182 if ( altPath != path )
183 aliases.push_back(path);
184 else
185 aliases.push_back(altPath);
186 }
187 }
188 return true;
189 }
190
191 bool improvePath(const char* volumeRootPath, const std::vector<const char*>& overlayPaths,
192 const std::string& path, std::string& foundPath, std::vector<std::string>& aliases)
193 {
194 for (const char* overlay : overlayPaths) {
195 if ( tryPath(overlay, path, foundPath, aliases) )
196 return true;
197 }
198 if ( volumeRootPath[0] != '\0' ) {
199 if ( tryPath(volumeRootPath, path, foundPath, aliases) )
200 return true;
201 }
202 return tryPath("", path, foundPath, aliases);
203 }
204
205 std::string fileExists( const std::string& path ) {
206 const uint8_t* p = (uint8_t*)( -1 );
207 struct stat stat_buf;
208 bool rootless;
209
210 std::tie( p, stat_buf, rootless ) = fileCache.cacheLoad( path );
211 if ( p != (uint8_t*)( -1 ) ) {
212 return normalize_absolute_file_path( path );
213 }
214
215 return "";
216 }
217
218 void populateManifest(Manifest& manifest, std::set<std::string> archs, const std::string& overlayPath,
219 const std::string& rootPath, const std::set<std::string>& paths) {
220 for ( const auto& arch : archs ) {
221 auto fallback = fallbackArchStringForArchString(arch);
222 std::set<std::string> allArchs = archs;
223 std::set<std::string> processedPaths;
224 std::set<std::string> unprocessedPaths = paths;
225 std::set<std::string> pathsToProcess;
226 std::set_difference( unprocessedPaths.begin(), unprocessedPaths.end(), processedPaths.begin(), processedPaths.end(),
227 std::inserter( pathsToProcess, pathsToProcess.begin() ) );
228 while ( !pathsToProcess.empty() ) {
229 for (const std::string path : pathsToProcess) {
230 processedPaths.insert(path);
231 std::string fullPath;
232 if ( rootPath != "/" ) {
233 // with -root, only look in the root path volume
234 fullPath = fileExists(rootPath + path);
235 }
236 else {
237 // with -overlay, look first in overlay dir
238 if ( !overlayPath.empty() )
239 fullPath = fileExists(overlayPath + path);
240 // if not in overlay, look in boot volume
241 if ( fullPath.empty() )
242 fullPath = fileExists(path);
243 }
244 if ( fullPath.empty() )
245 continue;
246 auto proxies = MachOProxy::findDylibInfo(fullPath, true, true);
247 auto proxy = proxies.find(arch);
248 if (proxy == proxies.end())
249 proxy = proxies.find(fallback);
250 if (proxy == proxies.end())
251 continue;
252
253 for ( const auto& dependency : proxy->second->dependencies ) {
254 unprocessedPaths.insert( dependency );
255 }
256
257 if ( proxy->second->installName.empty() ) {
258 continue;
259 }
260
261 proxy->second->addAlias( path );
262 manifest.architectureFiles[arch].dylibs.insert(std::make_pair(proxy->second->installName,
263 Manifest::File(proxy->second)));
264 manifest.configurations["localhost"].architectures[arch].anchors.push_back( proxy->second->installName );
265 }
266
267 pathsToProcess.clear();
268 std::set_difference( unprocessedPaths.begin(), unprocessedPaths.end(), processedPaths.begin(), processedPaths.end(),
269 std::inserter( pathsToProcess, pathsToProcess.begin() ) );
270 }
271 }
272 }
273
274 static bool runningOnHaswell()
275 {
276 // check system is capable of running x86_64h code
277 struct host_basic_info info;
278 mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT;
279 mach_port_t hostPort = mach_host_self();
280 kern_return_t result = host_info(hostPort, HOST_BASIC_INFO, (host_info_t)&info, &count);
281 mach_port_deallocate(mach_task_self(), hostPort);
282
283 return ( (result == KERN_SUCCESS) && (info.cpu_subtype == CPU_SUBTYPE_X86_64_H) );
284 }
285
286
287 #define TERMINATE_IF_LAST_ARG( s ) \
288 do { \
289 if ( i == argc - 1 ) terminate( s ); \
290 } while ( 0 )
291
292 int main(int argc, const char* argv[])
293 {
294 std::string rootPath;
295 std::string overlayPath;
296 std::string platform = "osx";
297 std::string dylibListFile;
298 bool universal = false;
299 bool force = false;
300 std::string cacheDir;
301 std::set<std::string> archStrs;
302 std::vector<Manifest::Anchor> anchors;
303
304 // parse command line options
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 setVerbose(true);
310 } else if (strcmp(arg, "-verbose") == 0) {
311 setVerbose(true);
312 } else if (strcmp(arg, "-dont_map_local_symbols") == 0) {
313 //We are going to ignore this
314 } else if (strcmp(arg, "-iPhone") == 0) {
315 platform = "iphoneos";
316 } else if (strcmp(arg, "-dylib_list") == 0) {
317 TERMINATE_IF_LAST_ARG("-dylib_list missing argument");
318 dylibListFile = argv[++i];
319 } else if ((strcmp(arg, "-root") == 0) || (strcmp(arg, "--root") == 0)) {
320 TERMINATE_IF_LAST_ARG("-root missing path argument\n");
321 rootPath = argv[++i];
322 } else if (strcmp(arg, "-overlay") == 0) {
323 TERMINATE_IF_LAST_ARG("-overlay missing path argument\n");
324 overlayPath = argv[++i];
325 } else if (strcmp(arg, "-cache_dir") == 0) {
326 TERMINATE_IF_LAST_ARG("-cache_dir missing path argument\n");
327 cacheDir = argv[++i];
328 } else if (strcmp(arg, "-arch") == 0) {
329 TERMINATE_IF_LAST_ARG("-arch missing argument\n");
330 archStrs.insert(argv[++i]);
331 } else if (strcmp(arg, "-force") == 0) {
332 force = true;
333 } else if (strcmp(arg, "-sort_by_name") == 0) {
334 //No-op, we always do this now
335 } else if (strcmp(arg, "-universal_boot") == 0) {
336 universal = true;
337 } else {
338 //usage();
339 terminate("unknown option: %s\n", arg);
340 }
341 } else {
342 //usage();
343 terminate("unknown option: %s\n", arg);
344 }
345 }
346
347 setReturnNonZeroOnTerminate();
348 setWarnAnErrorPrefixes("update_dyld_shared_cache: warning: ", "update_dyld_shared_cache failed: ");
349
350 if ( !rootPath.empty() & !overlayPath.empty() )
351 terminate("-root and -overlay cannot be used together\n");
352
353 //FIXME realpath on root and overlays
354
355 if (rootPath.empty()) {
356 rootPath = "/";
357 }
358
359 if ( cacheDir.empty() ) {
360 // write cache file into -root or -overlay directory, if used
361 if ( rootPath != "/" )
362 cacheDir = rootPath + MACOSX_DYLD_SHARED_CACHE_DIR;
363 else if ( !overlayPath.empty() )
364 cacheDir = overlayPath + MACOSX_DYLD_SHARED_CACHE_DIR;
365 else
366 cacheDir = MACOSX_DYLD_SHARED_CACHE_DIR;
367 }
368
369 if (universal) {
370 if ( platform == "iphoneos" ) {
371 terminate("-iPhoneOS and -universal are incompatible\n");
372 }
373 archStrs.clear();
374 }
375
376 if (archStrs.size() == 0) {
377 if ( platform == "iphoneos" ) {
378 terminate("Must specify -arch(s) when using -iPhone\n");
379 }
380 else {
381 if ( universal ) {
382 // <rdar://problem/26182089> -universal_boot should make all possible dyld caches
383 archStrs.insert("i386");
384 archStrs.insert("x86_64");
385 archStrs.insert("x86_64h");
386 }
387 else {
388 // just make caches for this machine
389 archStrs.insert("i386");
390 archStrs.insert(runningOnHaswell() ? "x86_64h" : "x86_64");
391 }
392 }
393 }
394
395 int err = mkpath_np(cacheDir.c_str(), S_IRWXU | S_IRGRP|S_IXGRP | S_IROTH|S_IXOTH);
396 if (err != 0 && err != EEXIST) {
397 terminate("mkpath_np fail: %d", err);
398 }
399
400 Manifest manifest;
401
402 std::set<std::string> paths;
403
404 if ( !dylibListFile.empty() ) {
405 if ( !parsePathsFile( dylibListFile, paths ) ) {
406 terminate( "could not build intiial paths\n" );
407 }
408 } else if ( !buildInitialPaths( rootPath, overlayPath, paths ) ) {
409 terminate( "could not build intiial paths\n" );
410 }
411
412 manifest.platform = platform;
413 populateManifest( manifest, archStrs, overlayPath, rootPath, paths );
414
415 // If the path we are writing to is trusted then our sources need to be trusted
416 // <rdar://problem/21166835> Can't update the update_dyld_shared_cache on a non-boot volume
417 bool requireDylibsBeRootlessProtected = isProtectedBySIP(cacheDir);
418 manifest.calculateClosure( requireDylibsBeRootlessProtected );
419 manifest.pruneClosure();
420
421 for (const std::string& archStr : archStrs) {
422 std::string cachePath = cacheDir + "/dyld_shared_cache_" + archStr;
423 if ( manifest.sameContentsAsCacheAtPath("localhost", archStr, cachePath) && !force ) {
424 manifest.configurations["localhost"].architectures.erase(archStr);
425 verboseLog("%s is already up to date", cachePath.c_str());
426 }
427 }
428
429 // If caches already up to date, do nothing
430 if ( manifest.configurations["localhost"].architectures.empty() )
431 dumpLogAndExit(false);
432
433 // build caches
434 std::shared_ptr<MultiCacheBuilder> builder = std::make_shared<MultiCacheBuilder>(manifest, false, false, false, false, requireDylibsBeRootlessProtected);
435 builder->buildCaches(cacheDir);
436
437 // Save off spintrace data
438 std::string nuggetRoot = (overlayPath.empty() ? rootPath : overlayPath);
439 (void)dscsym_save_nuggets_for_current_caches(nuggetRoot.c_str());
440
441 // Now that all the build commands have been issued lets put a barrier in after then which can tear down the app after
442 // everything is written.
443 builder->logStats();
444 dumpLogAndExit(false);
445
446 dispatch_main();
447 }
448