]>
git.saurik.com Git - apple/dyld.git/blob - dyld3/shared-cache/FileUtils.cpp
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"
50 #include "JSONReader.h"
53 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
)
55 std::string fullDirPath
= pathPrefix
+ path
;
56 DIR* dir
= ::opendir(fullDirPath
.c_str());
57 if ( dir
== nullptr ) {
58 //fprintf(stderr, "can't read 'dir '%s', errno=%d\n", inputPath.c_str(), errno);
61 while (dirent
* entry
= readdir(dir
)) {
63 std::string dirAndFile
= path
+ (path
.back() != '/' ? "/" : "") + entry
->d_name
;
64 std::string fullDirAndFile
= pathPrefix
+ dirAndFile
;
65 switch ( entry
->d_type
) {
68 if ( ::lstat(fullDirAndFile
.c_str(), &statBuf
) == -1 )
70 if ( ! S_ISREG(statBuf
.st_mode
) )
72 fileCallback(dirAndFile
, statBuf
);
76 if ( strcmp(entry
->d_name
, ".") == 0 )
78 if ( strcmp(entry
->d_name
, "..") == 0 )
80 if ( dirFilter(dirAndFile
) )
83 iterateDirectoryTree(pathPrefix
, dirAndFile
, dirFilter
, fileCallback
, processFiles
, true);
86 // don't follow symlinks, dylib will be found through absolute path
94 bool safeSave(const void* buffer
, size_t bufferLen
, const std::string
& path
)
96 std::string pathTemplate
= path
+ "-XXXXXX";
97 size_t templateLen
= strlen(pathTemplate
.c_str())+2;
98 char pathTemplateSpace
[templateLen
];
99 strlcpy(pathTemplateSpace
, pathTemplate
.c_str(), templateLen
);
100 int fd
= mkstemp(pathTemplateSpace
);
102 ssize_t writtenSize
= pwrite(fd
, buffer
, bufferLen
, 0);
103 if ( (size_t)writtenSize
== bufferLen
) {
104 ::fchmod(fd
, S_IRUSR
|S_IWUSR
|S_IRGRP
|S_IROTH
); // mkstemp() makes file "rw-------", switch it to "rw-r--r--"
105 if ( ::rename(pathTemplateSpace
, path
.c_str()) == 0) {
107 return true; // success
111 ::unlink(pathTemplateSpace
);
113 return false; // failure
116 const void* mapFileReadOnly(const char* path
, size_t& mappedSize
)
119 if ( ::stat(path
, &statBuf
) != 0 )
122 int fd
= ::open(path
, O_RDONLY
);
126 const void *p
= ::mmap(NULL
, (size_t)statBuf
.st_size
, PROT_READ
, MAP_PRIVATE
, fd
, 0);
128 if ( p
!= MAP_FAILED
) {
129 mappedSize
= (size_t)statBuf
.st_size
;
136 bool fileExists(const std::string
& path
)
139 return ( ::stat(path
.c_str(), &statBuf
) == 0 );
142 // There is an order file specifying the order in which dylibs are laid out in
143 // general, as well as an order file specifying the order in which __DATA_DIRTY
144 // segments are laid out in particular.
146 // The syntax is one dylib (install name) per line. Blank lines are ignored.
147 // Comments start with the # character.
148 std::unordered_map
<std::string
, uint32_t> parseOrderFile(const std::string
& orderFileData
) {
149 std::unordered_map
<std::string
, uint32_t> order
;
151 if (orderFileData
.empty())
154 std::stringstream
myData(orderFileData
);
158 while ( std::getline(myData
, line
) ) {
159 size_t pos
= line
.find('#');
160 if ( pos
!= std::string::npos
)
162 while ( !line
.empty() && isspace(line
.back()) ) {
166 order
[line
] = count
++;
171 std::string
loadOrderFile(const std::string
& orderFilePath
) {
175 char* data
= (char*)mapFileReadOnly(orderFilePath
.c_str(), size
);
177 order
= std::string(data
, size
);
178 ::munmap((void*)data
, size
);
185 std::string
toolDir()
187 char buffer
[PATH_MAX
];
188 uint32_t bufsize
= PATH_MAX
;
189 int result
= _NSGetExecutablePath(buffer
, &bufsize
);
191 std::string path
= buffer
;
192 size_t pos
= path
.rfind('/');
193 if ( pos
!= std::string::npos
)
194 return path
.substr(0,pos
+1);
196 //warning("tool directory not found");
200 std::string
basePath(const std::string
& path
)
202 std::string::size_type slash_pos
= path
.rfind("/");
203 if (slash_pos
!= std::string::npos
) {
205 return path
.substr(slash_pos
);
211 std::string
dirPath(const std::string
& path
)
213 std::string::size_type slash_pos
= path
.rfind("/");
214 if (slash_pos
!= std::string::npos
) {
216 return path
.substr(0, slash_pos
);
218 char cwd
[MAXPATHLEN
];
219 (void)getcwd(cwd
, MAXPATHLEN
);
224 std::string
realPath(const std::string
& path
)
226 char resolvedPath
[PATH_MAX
];
227 if (realpath(dirPath(path
).c_str(), &resolvedPath
[0]) != nullptr) {
228 return std::string(resolvedPath
) + "/" + basePath(path
);
234 std::string
realFilePath(const std::string
& path
)
236 char resolvedPath
[PATH_MAX
];
237 if ( realpath(path
.c_str(), resolvedPath
) != nullptr )
238 return std::string(resolvedPath
);
244 std::string
normalize_absolute_file_path(std::string path
) {
245 std::vector
<std::string
> components
;
246 std::vector
<std::string
> processed_components
;
247 std::stringstream
ss(path
);
251 while (std::getline(ss
, item
, '/')) {
252 components
.push_back(item
);
255 if (components
[0] == ".") {
259 for (auto& component
: components
) {
260 if (component
.empty() || component
== ".")
262 else if (component
== ".." && processed_components
.size())
263 processed_components
.pop_back();
265 processed_components
.push_back(component
);
268 for (auto & component
: processed_components
) {
269 retval
= retval
+ "/" + component
;
276 #if BUILDING_CACHE_BUILDER
280 FileCache::FileCache(void)
282 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));
285 std::pair
<uint8_t*, struct stat
> FileCache::cacheLoad(Diagnostics
& diags
, const std::string path
)
287 __block
bool found
= false;
288 __block
std::pair
<uint8_t*, struct stat
> retval
;
289 std::string normalizedPath
= normalize_absolute_file_path(path
);
290 dispatch_sync(cache_queue
, ^{
291 auto entry
= entries
.find(normalizedPath
);
292 if (entry
!= entries
.end()) {
293 retval
= entry
->second
;
299 auto info
= fill(diags
, normalizedPath
);
300 dispatch_sync(cache_queue
, ^{
301 auto entry
= entries
.find(normalizedPath
);
302 if (entry
!= entries
.end()) {
303 retval
= entry
->second
;
305 retval
= entries
[normalizedPath
] = info
;
314 //FIXME error handling
315 std::pair
<uint8_t*, struct stat
> FileCache::fill(Diagnostics
& diags
, const std::string
& path
)
317 void* buffer_ptr
= nullptr;
318 struct stat stat_buf
;
319 struct statfs statfs_buf
;
320 bool localcopy
= true;
322 int fd
= ::open(path
.c_str(), O_RDONLY
, 0);
324 diags
.verbose("can't open file '%s', errno=%d\n", path
.c_str(), errno
);
325 return std::make_pair((uint8_t*)(-1), stat_buf
);
328 if (fstat(fd
, &stat_buf
) == -1) {
329 diags
.verbose("can't stat open file '%s', errno=%d\n", path
.c_str(), errno
);
331 return std::make_pair((uint8_t*)(-1), stat_buf
);
334 if (stat_buf
.st_size
< 4096) {
335 diags
.verbose("file too small '%s'\n", path
.c_str());
337 return std::make_pair((uint8_t*)(-1), stat_buf
);
340 if(fstatfs(fd
, &statfs_buf
) == 0) {
341 std::string fsName
= statfs_buf
.f_fstypename
;
342 if (fsName
== "hfs" || fsName
== "apfs") {
348 buffer_ptr
= mmap(NULL
, (size_t)stat_buf
.st_size
, PROT_READ
, MAP_PRIVATE
, fd
, 0);
349 if (buffer_ptr
== MAP_FAILED
) {
350 diags
.verbose("mmap() for file at %s failed, errno=%d\n", path
.c_str(), errno
);
352 return std::make_pair((uint8_t*)(-1), stat_buf
);
355 buffer_ptr
= malloc((size_t)stat_buf
.st_size
);
356 ssize_t readBytes
= pread(fd
, buffer_ptr
, (size_t)stat_buf
.st_size
, 0);
357 if (readBytes
== -1) {
358 diags
.verbose("Network read for file at %s failed, errno=%d\n", path
.c_str(), errno
);
360 return std::make_pair((uint8_t*)(-1), stat_buf
);
361 } else if (readBytes
!= stat_buf
.st_size
) {
362 diags
.verbose("Network read udnerrun for file at %s, expected %lld bytes, got %zd bytes\n", path
.c_str(), stat_buf
.st_size
, readBytes
);
364 return std::make_pair((uint8_t*)(-1), stat_buf
);
370 return std::make_pair((uint8_t*)buffer_ptr
, stat_buf
);
373 static void normalizePath(std::string
& path
) {
374 // Remove a bunch of stuff we don't need, like trailing slashes.
375 while ( !path
.empty() && (path
.back() == '/'))
379 void SymlinkResolver::addFile(Diagnostics
& diags
, std::string path
) {
380 if (path
.front() != '/') {
381 diags
.error("Path must start with '/'");
384 if (symlinks
.find(path
) != symlinks
.end()) {
385 diags
.error("Cannot add regular file as it is already a symlink");
388 filePaths
.insert(path
);
391 void SymlinkResolver::addSymlink(Diagnostics
& diags
, std::string fromPath
, std::string toPath
) {
392 normalizePath(fromPath
);
393 normalizePath(toPath
);
394 if (fromPath
.front() != '/') {
395 diags
.error("Path must start with '/'");
398 if (filePaths
.find(fromPath
) != filePaths
.end()) {
399 diags
.error("Cannot add symlink from '%s' as it is already a regular path", fromPath
.c_str());
402 auto itAndInserted
= symlinks
.insert({ fromPath
, toPath
});
403 if (!itAndInserted
.second
) {
404 // The path is already a symlink. Make sure its a dupe.
405 if (toPath
!= itAndInserted
.first
->second
) {
406 diags
.error("Duplicate symlink for path '%s'", fromPath
.c_str());
412 std::string
SymlinkResolver::realPath(Diagnostics
& diags
, const std::string
& originalPath
) const {
413 // First make sure the path doesn't have any magic in it.
414 std::string path
= originalPath
;
417 std::set
<std::string
> seenSymlinks
;
419 // Now see if any prefix is a symlink
420 if (path
.front() != '/')
423 std::string::size_type prev_pos
= 0;
424 while (prev_pos
!= std::string::npos
) {
425 std::string::size_type pos
= path
.find("/", prev_pos
+ 1);
427 // First look to see if this path component is special, eg, ., .., etc.
428 std::string component
= path
.substr(prev_pos
, pos
- prev_pos
);
429 if (component
== "/..") {
430 // Fold with the previous path component.
432 // This is the root path, and .. applied to / is just /
433 path
= path
.substr(3);
436 std::string::size_type lastSlashPos
= path
.rfind("/", prev_pos
- 1);
437 path
= path
.substr(0, lastSlashPos
) + path
.substr(pos
);
438 prev_pos
= lastSlashPos
;
441 } else if (component
== "/.") {
443 // Path starts with /./ so just remove the first one.
444 path
= path
.substr(2);
446 if (pos
== std::string::npos
) {
447 // Trailing . on the path
448 path
= path
.substr(0, prev_pos
);
450 path
= path
.substr(0, prev_pos
) + path
.substr(pos
);
454 } else if (component
== "/") {
455 // Path must contain // somewhere so strip out the duplicates.
457 // Path starts with // so just remove the first one.
458 path
= path
.substr(1);
460 if (pos
== std::string::npos
) {
461 // Trailing / on the path
462 path
= path
.substr(0, prev_pos
);
465 path
= path
.substr(0, pos
) + path
.substr(pos
+ 1);
471 // Path is not special, so see if it is a symlink to something.
472 std::string prefix
= path
.substr(0, pos
);
473 //printf("%s\n", prefix.c_str());
474 auto it
= symlinks
.find(prefix
);
475 if (it
== symlinks
.end()) {
476 // This is not a symlink so move to the next prefix.
481 // If we've already done this prefix then error out.
482 if (seenSymlinks
.count(prefix
)) {
483 diags
.error("Loop in symlink processing for '%s'", originalPath
.c_str());
484 return std::string();
487 seenSymlinks
.insert(prefix
);
489 // This is a symlink, so resolve the new path.
490 std::string toPath
= it
->second
;
491 if (toPath
.front() == '/') {
492 // Symlink points to an absolute address so substitute the whole prefix for the new path
493 // If we didn't substitute the last component of the path then there is also a path suffix.
494 std::string pathSuffix
= "";
495 if (pos
!= std::string::npos
) {
496 std::string::size_type nextSlashPos
= path
.find("/", pos
+ 1);
497 if (nextSlashPos
!= std::string::npos
)
498 pathSuffix
= path
.substr(nextSlashPos
);
500 path
= toPath
+ pathSuffix
;
505 // Symlink points to a relative path so we need to do more processing to get the real path.
507 // 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.
508 std::string prevPrefix
= path
.substr(0, prev_pos
);
509 //printf("prevPrefix %s\n", prevPrefix.c_str());
511 // If we didn't substitute the last component of the path then there is also a path suffix.
512 std::string pathSuffix
= "";
513 if (prefix
.size() != path
.size())
514 pathSuffix
= path
.substr(pos
);
516 // The new path is the remaining prefix, plus the symlink target, plus any remaining suffix from the original path.
517 path
= prevPrefix
+ "/" + toPath
+ pathSuffix
;
523 std::vector
<DyldSharedCache::FileAlias
> SymlinkResolver::getResolvedSymlinks(Diagnostics
& diags
) {
524 diags
.assertNoError();
525 std::vector
<DyldSharedCache::FileAlias
> aliases
;
526 for (auto& fromPathAndToPath
: symlinks
) {
527 std::string newPath
= realPath(diags
, fromPathAndToPath
.first
);
528 if (diags
.hasError()) {
533 if (filePaths
.count(newPath
)) {
534 aliases
.push_back({ newPath
, fromPathAndToPath
.first
});
535 // printf("symlink ('%s' -> '%s') resolved to '%s'\n", fromPathAndToPath.first.c_str(), fromPathAndToPath.second.c_str(), newPath.c_str());
541 #endif // BUILDING_CACHE_BUILDER