#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <dirent.h>
#include <stdlib.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/mount.h>
#include <sys/mman.h>
#include <dispatch/dispatch.h>
#include <mach-o/dyld.h>
#include <System/sys/csr.h>
#include <rootless.h>
#include <string>
#include <fstream>
#include <sstream>
#include "FileUtils.h"
#include "Diagnostics.h"
#if __MAC_OS_X_VERSION_MIN_REQUIRED < 101200
extern "C" int rootless_check_trusted_fd(int fd) __attribute__((weak_import));
#endif
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)
{
std::string fullDirPath = pathPrefix + path;
DIR* dir = ::opendir(fullDirPath.c_str());
if ( dir == nullptr ) {
return;
}
while (dirent* entry = readdir(dir)) {
struct stat statBuf;
std::string dirAndFile = path + "/" + entry->d_name;
std::string fullDirAndFile = pathPrefix + dirAndFile;
switch ( entry->d_type ) {
case DT_REG:
if ( processFiles ) {
if ( ::lstat(fullDirAndFile.c_str(), &statBuf) == -1 )
break;
if ( ! S_ISREG(statBuf.st_mode) )
break;
fileCallback(dirAndFile, statBuf);
}
break;
case DT_DIR:
if ( strcmp(entry->d_name, ".") == 0 )
break;
if ( strcmp(entry->d_name, "..") == 0 )
break;
if ( dirFilter(dirAndFile) )
break;
iterateDirectoryTree(pathPrefix, dirAndFile, dirFilter, fileCallback);
break;
case DT_LNK:
break;
}
}
::closedir(dir);
}
bool safeSave(const void* buffer, size_t bufferLen, const std::string& path)
{
std::string pathTemplate = path + "-XXXXXX";
size_t templateLen = strlen(pathTemplate.c_str())+2;
char pathTemplateSpace[templateLen];
strlcpy(pathTemplateSpace, pathTemplate.c_str(), templateLen);
int fd = mkstemp(pathTemplateSpace);
if ( fd != -1 ) {
ssize_t writtenSize = pwrite(fd, buffer, bufferLen, 0);
if ( writtenSize == bufferLen ) {
::fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); if ( ::rename(pathTemplateSpace, path.c_str()) == 0) {
::close(fd);
return true; }
}
::close(fd);
::unlink(pathTemplateSpace);
}
return false; }
const void* mapFileReadOnly(const std::string& path, size_t& mappedSize)
{
struct stat statBuf;
if ( ::stat(path.c_str(), &statBuf) != 0 )
return nullptr;
int fd = ::open(path.c_str(), O_RDONLY);
if ( fd < 0 )
return nullptr;
const void *p = ::mmap(NULL, (size_t)statBuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
::close(fd);
if ( p != MAP_FAILED ) {
mappedSize = (size_t)statBuf.st_size;
return p;
}
return nullptr;
}
static bool sipIsEnabled()
{
static bool rootlessEnabled;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
rootlessEnabled = (csr_check(CSR_ALLOW_UNRESTRICTED_FS) != 0);
});
return rootlessEnabled;
}
bool isProtectedBySIP(const std::string& path)
{
if ( !sipIsEnabled() )
return false;
return (rootless_check_trusted(path.c_str()) == 0);
}
bool isProtectedBySIP(int fd)
{
if ( !sipIsEnabled() )
return false;
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200
return (rootless_check_trusted_fd(fd) == 0);
#else
char realPath[MAXPATHLEN];
if ( fcntl(fd, F_GETPATH, realPath) == 0 )
return (rootless_check_trusted(realPath) == 0);
return false;
#endif
}
bool fileExists(const std::string& path)
{
struct stat statBuf;
return ( ::stat(path.c_str(), &statBuf) == 0 );
}
std::unordered_map<std::string, uint32_t> loadOrderFile(const std::string& orderFile) {
std::unordered_map<std::string, uint32_t> order;
std::ifstream myfile(orderFile);
if ( myfile.is_open() ) {
uint32_t count = 0;
std::string line;
while ( std::getline(myfile, line) ) {
size_t pos = line.find('#');
if ( pos != std::string::npos )
line.resize(pos);
while ( !line.empty() && isspace(line.back()) ) {
line.pop_back();
}
if ( !line.empty() )
order[line] = count++;
}
myfile.close();
}
return order;
}
std::string toolDir()
{
char buffer[PATH_MAX];
uint32_t bufsize = PATH_MAX;
int result = _NSGetExecutablePath(buffer, &bufsize);
if ( result == 0 ) {
std::string path = buffer;
size_t pos = path.rfind('/');
if ( pos != std::string::npos )
return path.substr(0,pos+1);
}
return "/tmp/";
}
std::string basePath(const std::string& path)
{
std::string::size_type slash_pos = path.rfind("/");
if (slash_pos != std::string::npos) {
slash_pos++;
return path.substr(slash_pos);
} else {
return path;
}
}
std::string dirPath(const std::string& path)
{
std::string::size_type slash_pos = path.rfind("/");
if (slash_pos != std::string::npos) {
slash_pos++;
return path.substr(0, slash_pos);
} else {
char cwd[MAXPATHLEN];
(void)getcwd(cwd, MAXPATHLEN);
return cwd;
}
}
std::string realPath(const std::string& path)
{
char resolvedPath[PATH_MAX];
if (realpath(dirPath(path).c_str(), &resolvedPath[0]) != nullptr) {
return std::string(resolvedPath) + "/" + basePath(path);
} else {
return "";
}
}
std::string realFilePath(const std::string& path)
{
char resolvedPath[PATH_MAX];
if ( realpath(path.c_str(), resolvedPath) != nullptr )
return std::string(resolvedPath);
else
return "";
}
std::string normalize_absolute_file_path(std::string path) {
std::vector<std::string> components;
std::vector<std::string> processed_components;
std::stringstream ss(path);
std::string retval;
std::string item;
while (std::getline(ss, item, '/')) {
components.push_back(item);
}
if (components[0] == ".") {
retval = ".";
}
for (auto& component : components) {
if (component.empty() || component == ".")
continue;
else if (component == ".." && processed_components.size())
processed_components.pop_back();
else
processed_components.push_back(component);
}
for (auto & component : processed_components) {
retval = retval + "/" + component;
}
return retval;
}
#if BUILDING_CACHE_BUILDER
FileCache fileCache;
FileCache::FileCache(void)
{
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));
}
void FileCache::preflightCache(Diagnostics& diags, const std::unordered_set<std::string>& paths)
{
for (auto& path : paths) {
preflightCache(diags, path);
}
}
void FileCache::preflightCache(Diagnostics& diags, const std::string& path)
{
dispatch_async(cache_queue, ^{
std::string normalizedPath = normalize_absolute_file_path(path);
if (entries.count(normalizedPath) == 0) {
entries[normalizedPath] = fill(diags, normalizedPath);
}
});
}
std::pair<uint8_t*, struct stat> FileCache::cacheLoad(Diagnostics& diags, const std::string path)
{
__block bool found = false;
__block std::pair<uint8_t*, struct stat> retval;
std::string normalizedPath = normalize_absolute_file_path(path);
dispatch_sync(cache_queue, ^{
auto entry = entries.find(normalizedPath);
if (entry != entries.end()) {
retval = entry->second;
found = true;
}
});
if (!found) {
auto info = fill(diags, normalizedPath);
dispatch_sync(cache_queue, ^{
auto entry = entries.find(normalizedPath);
if (entry != entries.end()) {
retval = entry->second;
} else {
retval = entries[normalizedPath] = info;
retval = info;
}
});
}
return retval;
}
std::pair<uint8_t*, struct stat> FileCache::fill(Diagnostics& diags, const std::string& path)
{
void* buffer_ptr = nullptr;
struct stat stat_buf;
struct statfs statfs_buf;
bool localcopy = true;
int fd = ::open(path.c_str(), O_RDONLY, 0);
if (fd == -1) {
diags.verbose("can't open file '%s', errno=%d\n", path.c_str(), errno);
return std::make_pair((uint8_t*)(-1), stat_buf);
}
if (fstat(fd, &stat_buf) == -1) {
diags.verbose("can't stat open file '%s', errno=%d\n", path.c_str(), errno);
::close(fd);
return std::make_pair((uint8_t*)(-1), stat_buf);
}
if (stat_buf.st_size < 4096) {
diags.verbose("file too small '%s'\n", path.c_str());
::close(fd);
return std::make_pair((uint8_t*)(-1), stat_buf);
}
if(fstatfs(fd, &statfs_buf) == 0) {
std::string fsName = statfs_buf.f_fstypename;
if (fsName == "hfs" || fsName == "apfs") {
localcopy = false;
}
}
if (!localcopy) {
buffer_ptr = mmap(NULL, (size_t)stat_buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (buffer_ptr == MAP_FAILED) {
diags.verbose("mmap() for file at %s failed, errno=%d\n", path.c_str(), errno);
::close(fd);
return std::make_pair((uint8_t*)(-1), stat_buf);
}
} else {
buffer_ptr = malloc((size_t)stat_buf.st_size);
ssize_t readBytes = pread(fd, buffer_ptr, (size_t)stat_buf.st_size, 0);
if (readBytes == -1) {
diags.verbose("Network read for file at %s failed, errno=%d\n", path.c_str(), errno);
::close(fd);
return std::make_pair((uint8_t*)(-1), stat_buf);
} else if (readBytes != stat_buf.st_size) {
diags.verbose("Network read udnerrun for file at %s, expected %lld bytes, got %zd bytes\n", path.c_str(), stat_buf.st_size, readBytes);
::close(fd);
return std::make_pair((uint8_t*)(-1), stat_buf);
}
}
::close(fd);
return std::make_pair((uint8_t*)buffer_ptr, stat_buf);
}
#endif // BUILDING_CACHE_BUILDER