dyld-732.8.tar.gz
[apple/dyld.git] / dyld3 / shared-cache / FileUtils.cpp
1 /*
2 * Copyright (c) 2017 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24
25 #include <string.h>
26 #include <stdint.h>
27 #include <unistd.h>
28 #include <dirent.h>
29 #include <stdlib.h>
30 #include <fcntl.h>
31 #include <limits.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <sys/types.h>
35 #include <sys/param.h>
36 #include <sys/mount.h>
37 #include <sys/mman.h>
38 #include <dispatch/dispatch.h>
39 #include <mach-o/dyld.h>
40 #include <System/sys/csr.h>
41 #include <rootless.h>
42
43 #include <string>
44 #include <fstream>
45 #include <sstream>
46
47 #include "FileUtils.h"
48 #include "StringUtils.h"
49 #include "Diagnostics.h"
50
51 #if __MAC_OS_X_VERSION_MIN_REQUIRED < 101200
52 extern "C" int rootless_check_trusted_fd(int fd) __attribute__((weak_import));
53 #endif
54
55
56 void iterateDirectoryTree(const std::string& pathPrefix, const std::string& path, bool (^dirFilter)(const std::string& path), void (^fileCallback)(const std::string& path, const struct stat&), bool processFiles, bool recurse)
57 {
58 std::string fullDirPath = pathPrefix + path;
59 DIR* dir = ::opendir(fullDirPath.c_str());
60 if ( dir == nullptr ) {
61 //fprintf(stderr, "can't read 'dir '%s', errno=%d\n", inputPath.c_str(), errno);
62 return;
63 }
64 while (dirent* entry = readdir(dir)) {
65 struct stat statBuf;
66 std::string dirAndFile = path + "/" + entry->d_name;
67 std::string fullDirAndFile = pathPrefix + dirAndFile;
68 switch ( entry->d_type ) {
69 case DT_REG:
70 if ( processFiles ) {
71 if ( ::lstat(fullDirAndFile.c_str(), &statBuf) == -1 )
72 break;
73 if ( ! S_ISREG(statBuf.st_mode) )
74 break;
75 fileCallback(dirAndFile, statBuf);
76 }
77 break;
78 case DT_DIR:
79 if ( strcmp(entry->d_name, ".") == 0 )
80 break;
81 if ( strcmp(entry->d_name, "..") == 0 )
82 break;
83 if ( dirFilter(dirAndFile) )
84 break;
85 if (recurse)
86 iterateDirectoryTree(pathPrefix, dirAndFile, dirFilter, fileCallback, processFiles, true);
87 break;
88 case DT_LNK:
89 // don't follow symlinks, dylib will be found through absolute path
90 break;
91 }
92 }
93 ::closedir(dir);
94 }
95
96
97 bool safeSave(const void* buffer, size_t bufferLen, const std::string& path)
98 {
99 std::string pathTemplate = path + "-XXXXXX";
100 size_t templateLen = strlen(pathTemplate.c_str())+2;
101 char pathTemplateSpace[templateLen];
102 strlcpy(pathTemplateSpace, pathTemplate.c_str(), templateLen);
103 int fd = mkstemp(pathTemplateSpace);
104 if ( fd != -1 ) {
105 ssize_t writtenSize = pwrite(fd, buffer, bufferLen, 0);
106 if ( (size_t)writtenSize == bufferLen ) {
107 ::fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); // mkstemp() makes file "rw-------", switch it to "rw-r--r--"
108 if ( ::rename(pathTemplateSpace, path.c_str()) == 0) {
109 ::close(fd);
110 return true; // success
111 }
112 }
113 ::close(fd);
114 ::unlink(pathTemplateSpace);
115 }
116 return false; // failure
117 }
118
119 const void* mapFileReadOnly(const char* path, size_t& mappedSize)
120 {
121 struct stat statBuf;
122 if ( ::stat(path, &statBuf) != 0 )
123 return nullptr;
124
125 int fd = ::open(path, O_RDONLY);
126 if ( fd < 0 )
127 return nullptr;
128
129 const void *p = ::mmap(NULL, (size_t)statBuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
130 ::close(fd);
131 if ( p != MAP_FAILED ) {
132 mappedSize = (size_t)statBuf.st_size;
133 return p;
134 }
135
136 return nullptr;
137 }
138
139 static bool sipIsEnabled()
140 {
141 static bool rootlessEnabled;
142 static dispatch_once_t onceToken;
143 // Check to make sure file system protections are on at all
144 dispatch_once(&onceToken, ^{
145 rootlessEnabled = (csr_check(CSR_ALLOW_UNRESTRICTED_FS) != 0);
146 });
147 return rootlessEnabled;
148 }
149
150 bool isProtectedBySIP(const std::string& path)
151 {
152 if ( !sipIsEnabled() )
153 return false;
154
155 return (rootless_check_trusted(path.c_str()) == 0);
156 }
157
158 bool isProtectedBySIPExceptDyld(const std::string& path)
159 {
160 if ( !sipIsEnabled() )
161 return false;
162
163 return (rootless_check_trusted_class(path.c_str(), "dyld") == 0);
164 }
165
166 bool isProtectedBySIP(int fd)
167 {
168 if ( !sipIsEnabled() )
169 return false;
170
171 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200
172 return (rootless_check_trusted_fd(fd) == 0);
173 #else
174 // fallback to using rootless_check_trusted
175 char realPath[MAXPATHLEN];
176 if ( fcntl(fd, F_GETPATH, realPath) == 0 )
177 return (rootless_check_trusted(realPath) == 0);
178 return false;
179 #endif
180 }
181
182 bool fileExists(const std::string& path)
183 {
184 struct stat statBuf;
185 return ( ::stat(path.c_str(), &statBuf) == 0 );
186 }
187
188 // There is an order file specifying the order in which dylibs are laid out in
189 // general, as well as an order file specifying the order in which __DATA_DIRTY
190 // segments are laid out in particular.
191 //
192 // The syntax is one dylib (install name) per line. Blank lines are ignored.
193 // Comments start with the # character.
194 std::unordered_map<std::string, uint32_t> parseOrderFile(const std::string& orderFileData) {
195 std::unordered_map<std::string, uint32_t> order;
196
197 if (orderFileData.empty())
198 return order;
199
200 std::stringstream myData(orderFileData);
201
202 uint32_t count = 0;
203 std::string line;
204 while ( std::getline(myData, line) ) {
205 size_t pos = line.find('#');
206 if ( pos != std::string::npos )
207 line.resize(pos);
208 while ( !line.empty() && isspace(line.back()) ) {
209 line.pop_back();
210 }
211 if ( !line.empty() )
212 order[line] = count++;
213 }
214 return order;
215 }
216
217 std::string loadOrderFile(const std::string& orderFilePath) {
218 std::string order;
219
220 size_t size = 0;
221 char* data = (char*)mapFileReadOnly(orderFilePath.c_str(), size);
222 if (data) {
223 order = std::string(data, size);
224 ::munmap((void*)data, size);
225 }
226
227 return order;
228 }
229
230
231 std::string toolDir()
232 {
233 char buffer[PATH_MAX];
234 uint32_t bufsize = PATH_MAX;
235 int result = _NSGetExecutablePath(buffer, &bufsize);
236 if ( result == 0 ) {
237 std::string path = buffer;
238 size_t pos = path.rfind('/');
239 if ( pos != std::string::npos )
240 return path.substr(0,pos+1);
241 }
242 //warning("tool directory not found");
243 return "/tmp/";
244 }
245
246 std::string basePath(const std::string& path)
247 {
248 std::string::size_type slash_pos = path.rfind("/");
249 if (slash_pos != std::string::npos) {
250 slash_pos++;
251 return path.substr(slash_pos);
252 } else {
253 return path;
254 }
255 }
256
257 std::string dirPath(const std::string& path)
258 {
259 std::string::size_type slash_pos = path.rfind("/");
260 if (slash_pos != std::string::npos) {
261 slash_pos++;
262 return path.substr(0, slash_pos);
263 } else {
264 char cwd[MAXPATHLEN];
265 (void)getcwd(cwd, MAXPATHLEN);
266 return cwd;
267 }
268 }
269
270 std::string realPath(const std::string& path)
271 {
272 char resolvedPath[PATH_MAX];
273 if (realpath(dirPath(path).c_str(), &resolvedPath[0]) != nullptr) {
274 return std::string(resolvedPath) + "/" + basePath(path);
275 } else {
276 return "";
277 }
278 }
279
280 std::string realFilePath(const std::string& path)
281 {
282 char resolvedPath[PATH_MAX];
283 if ( realpath(path.c_str(), resolvedPath) != nullptr )
284 return std::string(resolvedPath);
285 else
286 return "";
287 }
288
289
290 std::string normalize_absolute_file_path(std::string path) {
291 std::vector<std::string> components;
292 std::vector<std::string> processed_components;
293 std::stringstream ss(path);
294 std::string retval;
295 std::string item;
296
297 while (std::getline(ss, item, '/')) {
298 components.push_back(item);
299 }
300
301 if (components[0] == ".") {
302 retval = ".";
303 }
304
305 for (auto& component : components) {
306 if (component.empty() || component == ".")
307 continue;
308 else if (component == ".." && processed_components.size())
309 processed_components.pop_back();
310 else
311 processed_components.push_back(component);
312 }
313
314 for (auto & component : processed_components) {
315 retval = retval + "/" + component;
316 }
317
318 return retval;
319 }
320
321
322 #if BUILDING_CACHE_BUILDER
323
324 FileCache fileCache;
325
326 FileCache::FileCache(void)
327 {
328 cache_queue = dispatch_queue_create("com.apple.dyld.cache.cache", dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0));
329 }
330
331 std::pair<uint8_t*, struct stat> FileCache::cacheLoad(Diagnostics& diags, const std::string path)
332 {
333 __block bool found = false;
334 __block std::pair<uint8_t*, struct stat> retval;
335 std::string normalizedPath = normalize_absolute_file_path(path);
336 dispatch_sync(cache_queue, ^{
337 auto entry = entries.find(normalizedPath);
338 if (entry != entries.end()) {
339 retval = entry->second;
340 found = true;
341 }
342 });
343
344 if (!found) {
345 auto info = fill(diags, normalizedPath);
346 dispatch_sync(cache_queue, ^{
347 auto entry = entries.find(normalizedPath);
348 if (entry != entries.end()) {
349 retval = entry->second;
350 } else {
351 retval = entries[normalizedPath] = info;
352 retval = info;
353 }
354 });
355 }
356
357 return retval;
358 }
359
360 //FIXME error handling
361 std::pair<uint8_t*, struct stat> FileCache::fill(Diagnostics& diags, const std::string& path)
362 {
363 void* buffer_ptr = nullptr;
364 struct stat stat_buf;
365 struct statfs statfs_buf;
366 bool localcopy = true;
367
368 int fd = ::open(path.c_str(), O_RDONLY, 0);
369 if (fd == -1) {
370 diags.verbose("can't open file '%s', errno=%d\n", path.c_str(), errno);
371 return std::make_pair((uint8_t*)(-1), stat_buf);
372 }
373
374 if (fstat(fd, &stat_buf) == -1) {
375 diags.verbose("can't stat open file '%s', errno=%d\n", path.c_str(), errno);
376 ::close(fd);
377 return std::make_pair((uint8_t*)(-1), stat_buf);
378 }
379
380 if (stat_buf.st_size < 4096) {
381 diags.verbose("file too small '%s'\n", path.c_str());
382 ::close(fd);
383 return std::make_pair((uint8_t*)(-1), stat_buf);
384 }
385
386 if(fstatfs(fd, &statfs_buf) == 0) {
387 std::string fsName = statfs_buf.f_fstypename;
388 if (fsName == "hfs" || fsName == "apfs") {
389 localcopy = false;
390 }
391 }
392
393 if (!localcopy) {
394 buffer_ptr = mmap(NULL, (size_t)stat_buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
395 if (buffer_ptr == MAP_FAILED) {
396 diags.verbose("mmap() for file at %s failed, errno=%d\n", path.c_str(), errno);
397 ::close(fd);
398 return std::make_pair((uint8_t*)(-1), stat_buf);
399 }
400 } else {
401 buffer_ptr = malloc((size_t)stat_buf.st_size);
402 ssize_t readBytes = pread(fd, buffer_ptr, (size_t)stat_buf.st_size, 0);
403 if (readBytes == -1) {
404 diags.verbose("Network read for file at %s failed, errno=%d\n", path.c_str(), errno);
405 ::close(fd);
406 return std::make_pair((uint8_t*)(-1), stat_buf);
407 } else if (readBytes != stat_buf.st_size) {
408 diags.verbose("Network read udnerrun for file at %s, expected %lld bytes, got %zd bytes\n", path.c_str(), stat_buf.st_size, readBytes);
409 ::close(fd);
410 return std::make_pair((uint8_t*)(-1), stat_buf);
411 }
412 }
413
414 ::close(fd);
415
416 return std::make_pair((uint8_t*)buffer_ptr, stat_buf);
417 }
418
419 static void normalizePath(std::string& path) {
420 // Remove a bunch of stuff we don't need, like trailing slashes.
421 while ( !path.empty() && (path.back() == '/'))
422 path.pop_back();
423 }
424
425 void SymlinkResolver::addFile(Diagnostics& diags, std::string path) {
426 if (path.front() != '/') {
427 diags.error("Path must start with '/'");
428 return;
429 }
430 if (symlinks.find(path) != symlinks.end()) {
431 diags.error("Cannot add regular file as it is already a symlink");
432 return;
433 }
434 filePaths.insert(path);
435 }
436
437 void SymlinkResolver::addSymlink(Diagnostics& diags, std::string fromPath, std::string toPath) {
438 normalizePath(fromPath);
439 normalizePath(toPath);
440 if (fromPath.front() != '/') {
441 diags.error("Path must start with '/'");
442 return;
443 }
444 if (filePaths.find(fromPath) != filePaths.end()) {
445 diags.error("Cannot add symlink from '%s' as it is already a regular path", fromPath.c_str());
446 return;
447 }
448 auto itAndInserted = symlinks.insert({ fromPath, toPath });
449 if (!itAndInserted.second) {
450 // The path is already a symlink. Make sure its a dupe.
451 if (toPath != itAndInserted.first->second) {
452 diags.error("Duplicate symlink for path '%s'", fromPath.c_str());
453 return;
454 }
455 }
456 }
457
458 std::string SymlinkResolver::realPath(Diagnostics& diags, const std::string& originalPath) const {
459 // First make sure the path doesn't have any magic in it.
460 std::string path = originalPath;
461 normalizePath(path);
462
463 std::set<std::string> seenSymlinks;
464
465 // Now see if any prefix is a symlink
466 if (path.front() != '/')
467 return path;
468
469 std::string::size_type prev_pos = 0;
470 while (prev_pos != std::string::npos) {
471 std::string::size_type pos = path.find("/", prev_pos + 1);
472
473 // First look to see if this path component is special, eg, ., .., etc.
474 std::string component = path.substr(prev_pos, pos - prev_pos);
475 if (component == "/..") {
476 // Fold with the previous path component.
477 if (prev_pos == 0) {
478 // This is the root path, and .. applied to / is just /
479 path = path.substr(3);
480 prev_pos = 0;
481 } else {
482 std::string::size_type lastSlashPos = path.rfind("/", prev_pos - 1);
483 path = path.substr(0, lastSlashPos) + path.substr(pos);
484 prev_pos = lastSlashPos;
485 }
486 continue;
487 } else if (component == "/.") {
488 if (prev_pos == 0) {
489 // Path starts with /./ so just remove the first one.
490 path = path.substr(2);
491 } else {
492 if (pos == std::string::npos) {
493 // Trailing . on the path
494 path = path.substr(0, prev_pos );
495 } else {
496 path = path.substr(0, prev_pos) + path.substr(pos);
497 }
498 }
499 continue;
500 } else if (component == "/") {
501 // Path must contain // somewhere so strip out the duplicates.
502 if (prev_pos == 0) {
503 // Path starts with // so just remove the first one.
504 path = path.substr(1);
505 } else {
506 if (pos == std::string::npos) {
507 // Trailing / on the path
508 path = path.substr(0, prev_pos);
509 prev_pos = pos;
510 } else {
511 path = path.substr(0, pos) + path.substr(pos + 1);
512 }
513 }
514 continue;
515 }
516
517 // Path is not special, so see if it is a symlink to something.
518 std::string prefix = path.substr(0, pos);
519 //printf("%s\n", prefix.c_str());
520 auto it = symlinks.find(prefix);
521 if (it == symlinks.end()) {
522 // This is not a symlink so move to the next prefix.
523 prev_pos = pos;
524 continue;
525 }
526
527 // If we've already done this prefix then error out.
528 if (seenSymlinks.count(prefix)) {
529 diags.error("Loop in symlink processing for '%s'", originalPath.c_str());
530 return std::string();
531 }
532
533 seenSymlinks.insert(prefix);
534
535 // This is a symlink, so resolve the new path.
536 std::string toPath = it->second;
537 if (toPath.front() == '/') {
538 // Symlink points to an absolute address so substitute the whole prefix for the new path
539 // If we didn't substitute the last component of the path then there is also a path suffix.
540 std::string pathSuffix = "";
541 if (pos != std::string::npos) {
542 std::string::size_type nextSlashPos = path.find("/", pos + 1);
543 if (nextSlashPos != std::string::npos)
544 pathSuffix = path.substr(nextSlashPos);
545 }
546 path = toPath + pathSuffix;
547 prev_pos = 0;
548 continue;
549 }
550
551 // Symlink points to a relative path so we need to do more processing to get the real path.
552
553 // First calculate which part of the previous prefix we'll keep. Eg, in /a/b/c where "b -> blah", we want to keep /a here.
554 std::string prevPrefix = path.substr(0, prev_pos);
555 //printf("prevPrefix %s\n", prevPrefix.c_str());
556
557 // If we didn't substitute the last component of the path then there is also a path suffix.
558 std::string pathSuffix = "";
559 if (prefix.size() != path.size())
560 pathSuffix = path.substr(pos);
561
562 // The new path is the remaining prefix, plus the symlink target, plus any remaining suffix from the original path.
563 path = prevPrefix + "/" + toPath + pathSuffix;
564 prev_pos = 0;
565 }
566 return path;
567 }
568
569 std::vector<DyldSharedCache::FileAlias> SymlinkResolver::getResolvedSymlinks(Diagnostics& diags) {
570 diags.assertNoError();
571 std::vector<DyldSharedCache::FileAlias> aliases;
572 for (auto& fromPathAndToPath : symlinks) {
573 std::string newPath = realPath(diags, fromPathAndToPath.first);
574 if (diags.hasError()) {
575 aliases.clear();
576 return aliases;
577 }
578
579 if (filePaths.count(newPath)) {
580 aliases.push_back({ newPath, fromPathAndToPath.first });
581 // printf("symlink ('%s' -> '%s') resolved to '%s'\n", fromPathAndToPath.first.c_str(), fromPathAndToPath.second.c_str(), newPath.c_str());
582 }
583 }
584 return aliases;
585 }
586
587 #endif // BUILDING_CACHE_BUILDER
588