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 "Diagnostics.h"
50 #if __MAC_OS_X_VERSION_MIN_REQUIRED < 101200
51 extern "C" int rootless_check_trusted_fd(int fd
) __attribute__((weak_import
));
55 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
)
57 std::string fullDirPath
= pathPrefix
+ path
;
58 DIR* dir
= ::opendir(fullDirPath
.c_str());
59 if ( dir
== nullptr ) {
60 //fprintf(stderr, "can't read 'dir '%s', errno=%d\n", inputPath.c_str(), errno);
63 while (dirent
* entry
= readdir(dir
)) {
65 std::string dirAndFile
= path
+ "/" + entry
->d_name
;
66 std::string fullDirAndFile
= pathPrefix
+ dirAndFile
;
67 switch ( entry
->d_type
) {
70 if ( ::lstat(fullDirAndFile
.c_str(), &statBuf
) == -1 )
72 if ( ! S_ISREG(statBuf
.st_mode
) )
74 fileCallback(dirAndFile
, statBuf
);
78 if ( strcmp(entry
->d_name
, ".") == 0 )
80 if ( strcmp(entry
->d_name
, "..") == 0 )
82 if ( dirFilter(dirAndFile
) )
84 iterateDirectoryTree(pathPrefix
, dirAndFile
, dirFilter
, fileCallback
);
87 // don't follow symlinks, dylib will be found through absolute path
95 bool safeSave(const void* buffer
, size_t bufferLen
, const std::string
& path
)
97 std::string pathTemplate
= path
+ "-XXXXXX";
98 size_t templateLen
= strlen(pathTemplate
.c_str())+2;
99 char pathTemplateSpace
[templateLen
];
100 strlcpy(pathTemplateSpace
, pathTemplate
.c_str(), templateLen
);
101 int fd
= mkstemp(pathTemplateSpace
);
103 ssize_t writtenSize
= pwrite(fd
, buffer
, bufferLen
, 0);
104 if ( writtenSize
== bufferLen
) {
105 ::fchmod(fd
, S_IRUSR
|S_IWUSR
|S_IRGRP
|S_IROTH
); // mkstemp() makes file "rw-------", switch it to "rw-r--r--"
106 if ( ::rename(pathTemplateSpace
, path
.c_str()) == 0) {
108 return true; // success
112 ::unlink(pathTemplateSpace
);
114 return false; // failure
117 const void* mapFileReadOnly(const std::string
& path
, size_t& mappedSize
)
120 if ( ::stat(path
.c_str(), &statBuf
) != 0 )
123 int fd
= ::open(path
.c_str(), O_RDONLY
);
127 const void *p
= ::mmap(NULL
, (size_t)statBuf
.st_size
, PROT_READ
, MAP_PRIVATE
, fd
, 0);
129 if ( p
!= MAP_FAILED
) {
130 mappedSize
= (size_t)statBuf
.st_size
;
137 static bool sipIsEnabled()
139 static bool rootlessEnabled
;
140 static dispatch_once_t onceToken
;
141 // Check to make sure file system protections are on at all
142 dispatch_once(&onceToken
, ^{
143 rootlessEnabled
= (csr_check(CSR_ALLOW_UNRESTRICTED_FS
) != 0);
145 return rootlessEnabled
;
148 bool isProtectedBySIP(const std::string
& path
)
150 if ( !sipIsEnabled() )
153 return (rootless_check_trusted(path
.c_str()) == 0);
156 bool isProtectedBySIP(int fd
)
158 if ( !sipIsEnabled() )
161 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200
162 return (rootless_check_trusted_fd(fd
) == 0);
164 // fallback to using rootless_check_trusted
165 char realPath
[MAXPATHLEN
];
166 if ( fcntl(fd
, F_GETPATH
, realPath
) == 0 )
167 return (rootless_check_trusted(realPath
) == 0);
172 bool fileExists(const std::string
& path
)
175 return ( ::stat(path
.c_str(), &statBuf
) == 0 );
178 // There is an order file specifying the order in which dylibs are laid out in
179 // general, as well as an order file specifying the order in which __DATA_DIRTY
180 // segments are laid out in particular.
182 // The syntax is one dylib (install name) per line. Blank lines are ignored.
183 // Comments start with the # character.
184 std::unordered_map
<std::string
, uint32_t> loadOrderFile(const std::string
& orderFile
) {
185 std::unordered_map
<std::string
, uint32_t> order
;
187 std::ifstream
myfile(orderFile
);
188 if ( myfile
.is_open() ) {
191 while ( std::getline(myfile
, line
) ) {
192 size_t pos
= line
.find('#');
193 if ( pos
!= std::string::npos
)
195 while ( !line
.empty() && isspace(line
.back()) ) {
199 order
[line
] = count
++;
208 std::string
toolDir()
210 char buffer
[PATH_MAX
];
211 uint32_t bufsize
= PATH_MAX
;
212 int result
= _NSGetExecutablePath(buffer
, &bufsize
);
214 std::string path
= buffer
;
215 size_t pos
= path
.rfind('/');
216 if ( pos
!= std::string::npos
)
217 return path
.substr(0,pos
+1);
219 //warning("tool directory not found");
223 std::string
basePath(const std::string
& path
)
225 std::string::size_type slash_pos
= path
.rfind("/");
226 if (slash_pos
!= std::string::npos
) {
228 return path
.substr(slash_pos
);
234 std::string
dirPath(const std::string
& path
)
236 std::string::size_type slash_pos
= path
.rfind("/");
237 if (slash_pos
!= std::string::npos
) {
239 return path
.substr(0, slash_pos
);
241 char cwd
[MAXPATHLEN
];
242 (void)getcwd(cwd
, MAXPATHLEN
);
247 std::string
realPath(const std::string
& path
)
249 char resolvedPath
[PATH_MAX
];
250 if (realpath(dirPath(path
).c_str(), &resolvedPath
[0]) != nullptr) {
251 return std::string(resolvedPath
) + "/" + basePath(path
);
257 std::string
realFilePath(const std::string
& path
)
259 char resolvedPath
[PATH_MAX
];
260 if ( realpath(path
.c_str(), resolvedPath
) != nullptr )
261 return std::string(resolvedPath
);
267 std::string
normalize_absolute_file_path(std::string path
) {
268 std::vector
<std::string
> components
;
269 std::vector
<std::string
> processed_components
;
270 std::stringstream
ss(path
);
274 while (std::getline(ss
, item
, '/')) {
275 components
.push_back(item
);
278 if (components
[0] == ".") {
282 for (auto& component
: components
) {
283 if (component
.empty() || component
== ".")
285 else if (component
== ".." && processed_components
.size())
286 processed_components
.pop_back();
288 processed_components
.push_back(component
);
291 for (auto & component
: processed_components
) {
292 retval
= retval
+ "/" + component
;
299 #if BUILDING_CACHE_BUILDER
303 FileCache::FileCache(void)
305 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));
308 void FileCache::preflightCache(Diagnostics
& diags
, const std::unordered_set
<std::string
>& paths
)
310 for (auto& path
: paths
) {
311 preflightCache(diags
, path
);
315 void FileCache::preflightCache(Diagnostics
& diags
, const std::string
& path
)
317 dispatch_async(cache_queue
, ^{
318 std::string normalizedPath
= normalize_absolute_file_path(path
);
319 if (entries
.count(normalizedPath
) == 0) {
320 entries
[normalizedPath
] = fill(diags
, normalizedPath
);
325 std::pair
<uint8_t*, struct stat
> FileCache::cacheLoad(Diagnostics
& diags
, const std::string path
)
327 __block
bool found
= false;
328 __block
std::pair
<uint8_t*, struct stat
> retval
;
329 std::string normalizedPath
= normalize_absolute_file_path(path
);
330 dispatch_sync(cache_queue
, ^{
331 auto entry
= entries
.find(normalizedPath
);
332 if (entry
!= entries
.end()) {
333 retval
= entry
->second
;
339 auto info
= fill(diags
, normalizedPath
);
340 dispatch_sync(cache_queue
, ^{
341 auto entry
= entries
.find(normalizedPath
);
342 if (entry
!= entries
.end()) {
343 retval
= entry
->second
;
345 retval
= entries
[normalizedPath
] = info
;
354 //FIXME error handling
355 std::pair
<uint8_t*, struct stat
> FileCache::fill(Diagnostics
& diags
, const std::string
& path
)
357 void* buffer_ptr
= nullptr;
358 struct stat stat_buf
;
359 struct statfs statfs_buf
;
360 bool localcopy
= true;
362 int fd
= ::open(path
.c_str(), O_RDONLY
, 0);
364 diags
.verbose("can't open file '%s', errno=%d\n", path
.c_str(), errno
);
365 return std::make_pair((uint8_t*)(-1), stat_buf
);
368 if (fstat(fd
, &stat_buf
) == -1) {
369 diags
.verbose("can't stat open file '%s', errno=%d\n", path
.c_str(), errno
);
371 return std::make_pair((uint8_t*)(-1), stat_buf
);
374 if (stat_buf
.st_size
< 4096) {
375 diags
.verbose("file too small '%s'\n", path
.c_str());
377 return std::make_pair((uint8_t*)(-1), stat_buf
);
380 if(fstatfs(fd
, &statfs_buf
) == 0) {
381 std::string fsName
= statfs_buf
.f_fstypename
;
382 if (fsName
== "hfs" || fsName
== "apfs") {
388 buffer_ptr
= mmap(NULL
, (size_t)stat_buf
.st_size
, PROT_READ
, MAP_PRIVATE
, fd
, 0);
389 if (buffer_ptr
== MAP_FAILED
) {
390 diags
.verbose("mmap() for file at %s failed, errno=%d\n", path
.c_str(), errno
);
392 return std::make_pair((uint8_t*)(-1), stat_buf
);
395 buffer_ptr
= malloc((size_t)stat_buf
.st_size
);
396 ssize_t readBytes
= pread(fd
, buffer_ptr
, (size_t)stat_buf
.st_size
, 0);
397 if (readBytes
== -1) {
398 diags
.verbose("Network read for file at %s failed, errno=%d\n", path
.c_str(), errno
);
400 return std::make_pair((uint8_t*)(-1), stat_buf
);
401 } else if (readBytes
!= stat_buf
.st_size
) {
402 diags
.verbose("Network read udnerrun for file at %s, expected %lld bytes, got %zd bytes\n", path
.c_str(), stat_buf
.st_size
, readBytes
);
404 return std::make_pair((uint8_t*)(-1), stat_buf
);
410 return std::make_pair((uint8_t*)buffer_ptr
, stat_buf
);
413 #endif // BUILDING_CACHE_BUILDER