2 * Copyright (c) 2017 Apple Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
32 #include <sys/types.h>
34 #include <sys/types.h>
35 #include <sys/param.h>
36 #include <sys/mount.h>
38 #include <dispatch/dispatch.h>
39 #include <mach-o/dyld.h>
40 #include <System/sys/csr.h>
47 #include "FileUtils.h"
48 #include "StringUtils.h"
49 #include "Diagnostics.h"
51 #if __MAC_OS_X_VERSION_MIN_REQUIRED < 101200
52 extern "C" int rootless_check_trusted_fd(int fd
) __attribute__((weak_import
));
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
)
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);
64 while (dirent
* entry
= readdir(dir
)) {
66 std::string dirAndFile
= path
+ "/" + entry
->d_name
;
67 std::string fullDirAndFile
= pathPrefix
+ dirAndFile
;
68 switch ( entry
->d_type
) {
71 if ( ::lstat(fullDirAndFile
.c_str(), &statBuf
) == -1 )
73 if ( ! S_ISREG(statBuf
.st_mode
) )
75 fileCallback(dirAndFile
, statBuf
);
79 if ( strcmp(entry
->d_name
, ".") == 0 )
81 if ( strcmp(entry
->d_name
, "..") == 0 )
83 if ( dirFilter(dirAndFile
) )
86 iterateDirectoryTree(pathPrefix
, dirAndFile
, dirFilter
, fileCallback
, processFiles
, true);
89 // don't follow symlinks, dylib will be found through absolute path
97 bool safeSave(const void* buffer
, size_t bufferLen
, const std::string
& path
)
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
);
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) {
110 return true; // success
114 ::unlink(pathTemplateSpace
);
116 return false; // failure
119 const void* mapFileReadOnly(const char* path
, size_t& mappedSize
)
122 if ( ::stat(path
, &statBuf
) != 0 )
125 int fd
= ::open(path
, O_RDONLY
);
129 const void *p
= ::mmap(NULL
, (size_t)statBuf
.st_size
, PROT_READ
, MAP_PRIVATE
, fd
, 0);
131 if ( p
!= MAP_FAILED
) {
132 mappedSize
= (size_t)statBuf
.st_size
;
139 static bool sipIsEnabled()
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);
147 return rootlessEnabled
;
150 bool isProtectedBySIP(const std::string
& path
)
152 if ( !sipIsEnabled() )
155 return (rootless_check_trusted(path
.c_str()) == 0);
158 bool isProtectedBySIPExceptDyld(const std::string
& path
)
160 if ( !sipIsEnabled() )
163 return (rootless_check_trusted_class(path
.c_str(), "dyld") == 0);
166 bool isProtectedBySIP(int fd
)
168 if ( !sipIsEnabled() )
171 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200
172 return (rootless_check_trusted_fd(fd
) == 0);
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);
182 bool fileExists(const std::string
& path
)
185 return ( ::stat(path
.c_str(), &statBuf
) == 0 );
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.
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
;
197 if (orderFileData
.empty())
200 std::stringstream
myData(orderFileData
);
204 while ( std::getline(myData
, line
) ) {
205 size_t pos
= line
.find('#');
206 if ( pos
!= std::string::npos
)
208 while ( !line
.empty() && isspace(line
.back()) ) {
212 order
[line
] = count
++;
217 std::string
loadOrderFile(const std::string
& orderFilePath
) {
221 char* data
= (char*)mapFileReadOnly(orderFilePath
.c_str(), size
);
223 order
= std::string(data
, size
);
224 ::munmap((void*)data
, size
);
231 std::string
toolDir()
233 char buffer
[PATH_MAX
];
234 uint32_t bufsize
= PATH_MAX
;
235 int result
= _NSGetExecutablePath(buffer
, &bufsize
);
237 std::string path
= buffer
;
238 size_t pos
= path
.rfind('/');
239 if ( pos
!= std::string::npos
)
240 return path
.substr(0,pos
+1);
242 //warning("tool directory not found");
246 std::string
basePath(const std::string
& path
)
248 std::string::size_type slash_pos
= path
.rfind("/");
249 if (slash_pos
!= std::string::npos
) {
251 return path
.substr(slash_pos
);
257 std::string
dirPath(const std::string
& path
)
259 std::string::size_type slash_pos
= path
.rfind("/");
260 if (slash_pos
!= std::string::npos
) {
262 return path
.substr(0, slash_pos
);
264 char cwd
[MAXPATHLEN
];
265 (void)getcwd(cwd
, MAXPATHLEN
);
270 std::string
realPath(const std::string
& path
)
272 char resolvedPath
[PATH_MAX
];
273 if (realpath(dirPath(path
).c_str(), &resolvedPath
[0]) != nullptr) {
274 return std::string(resolvedPath
) + "/" + basePath(path
);
280 std::string
realFilePath(const std::string
& path
)
282 char resolvedPath
[PATH_MAX
];
283 if ( realpath(path
.c_str(), resolvedPath
) != nullptr )
284 return std::string(resolvedPath
);
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
);
297 while (std::getline(ss
, item
, '/')) {
298 components
.push_back(item
);
301 if (components
[0] == ".") {
305 for (auto& component
: components
) {
306 if (component
.empty() || component
== ".")
308 else if (component
== ".." && processed_components
.size())
309 processed_components
.pop_back();
311 processed_components
.push_back(component
);
314 for (auto & component
: processed_components
) {
315 retval
= retval
+ "/" + component
;
322 #if BUILDING_CACHE_BUILDER
326 FileCache::FileCache(void)
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));
331 std::pair
<uint8_t*, struct stat
> FileCache::cacheLoad(Diagnostics
& diags
, const std::string path
)
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
;
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
;
351 retval
= entries
[normalizedPath
] = info
;
360 //FIXME error handling
361 std::pair
<uint8_t*, struct stat
> FileCache::fill(Diagnostics
& diags
, const std::string
& path
)
363 void* buffer_ptr
= nullptr;
364 struct stat stat_buf
;
365 struct statfs statfs_buf
;
366 bool localcopy
= true;
368 int fd
= ::open(path
.c_str(), O_RDONLY
, 0);
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
);
374 if (fstat(fd
, &stat_buf
) == -1) {
375 diags
.verbose("can't stat open file '%s', errno=%d\n", path
.c_str(), errno
);
377 return std::make_pair((uint8_t*)(-1), stat_buf
);
380 if (stat_buf
.st_size
< 4096) {
381 diags
.verbose("file too small '%s'\n", path
.c_str());
383 return std::make_pair((uint8_t*)(-1), stat_buf
);
386 if(fstatfs(fd
, &statfs_buf
) == 0) {
387 std::string fsName
= statfs_buf
.f_fstypename
;
388 if (fsName
== "hfs" || fsName
== "apfs") {
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
);
398 return std::make_pair((uint8_t*)(-1), stat_buf
);
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
);
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
);
410 return std::make_pair((uint8_t*)(-1), stat_buf
);
416 return std::make_pair((uint8_t*)buffer_ptr
, stat_buf
);
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() == '/'))
425 void SymlinkResolver::addFile(Diagnostics
& diags
, std::string path
) {
426 if (path
.front() != '/') {
427 diags
.error("Path must start with '/'");
430 if (symlinks
.find(path
) != symlinks
.end()) {
431 diags
.error("Cannot add regular file as it is already a symlink");
434 filePaths
.insert(path
);
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 '/'");
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());
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());
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
;
463 std::set
<std::string
> seenSymlinks
;
465 // Now see if any prefix is a symlink
466 if (path
.front() != '/')
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);
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.
478 // This is the root path, and .. applied to / is just /
479 path
= path
.substr(3);
482 std::string::size_type lastSlashPos
= path
.rfind("/", prev_pos
- 1);
483 path
= path
.substr(0, lastSlashPos
) + path
.substr(pos
);
484 prev_pos
= lastSlashPos
;
487 } else if (component
== "/.") {
489 // Path starts with /./ so just remove the first one.
490 path
= path
.substr(2);
492 if (pos
== std::string::npos
) {
493 // Trailing . on the path
494 path
= path
.substr(0, prev_pos
);
496 path
= path
.substr(0, prev_pos
) + path
.substr(pos
);
500 } else if (component
== "/") {
501 // Path must contain // somewhere so strip out the duplicates.
503 // Path starts with // so just remove the first one.
504 path
= path
.substr(1);
506 if (pos
== std::string::npos
) {
507 // Trailing / on the path
508 path
= path
.substr(0, prev_pos
);
511 path
= path
.substr(0, pos
) + path
.substr(pos
+ 1);
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.
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();
533 seenSymlinks
.insert(prefix
);
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
);
546 path
= toPath
+ pathSuffix
;
551 // Symlink points to a relative path so we need to do more processing to get the real path.
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());
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
);
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
;
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()) {
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());
587 #endif // BUILDING_CACHE_BUILDER