blender/intern/cycles/util/util_path.cpp
Sergey Sharybin 8e1dd7ed81 Cycles: Remove unneeded include statements
Also try to move them from headers to implementation files as much as possible.
2018-01-19 15:19:45 +01:00

991 lines
22 KiB
C++

/*
* Copyright 2011-2013 Blender Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "util/util_md5.h"
#include "util/util_path.h"
#include "util/util_string.h"
#include <OpenImageIO/filesystem.h>
#include <OpenImageIO/strutil.h>
#include <OpenImageIO/sysutil.h>
OIIO_NAMESPACE_USING
#include <stdio.h>
#include <sys/stat.h>
#if defined(_WIN32)
# define DIR_SEP '\\'
# define DIR_SEP_ALT '/'
# include <direct.h>
#else
# define DIR_SEP '/'
# include <dirent.h>
# include <pwd.h>
# include <unistd.h>
# include <sys/types.h>
#endif
#ifdef HAVE_SHLWAPI_H
# include <shlwapi.h>
#endif
#include "util/util_map.h"
#include "util/util_windows.h"
CCL_NAMESPACE_BEGIN
#ifdef _WIN32
# if defined(_MSC_VER) || defined(__MINGW64__)
typedef struct _stat64 path_stat_t;
# elif defined(__MINGW32__)
typedef struct _stati64 path_stat_t;
# else
typedef struct _stat path_stat_t;
# endif
# ifndef S_ISDIR
# define S_ISDIR(x) (((x) & _S_IFDIR) == _S_IFDIR)
# endif
#else
typedef struct stat path_stat_t;
#endif
static string cached_path = "";
static string cached_user_path = "";
static string cached_xdg_cache_path = "";
namespace {
#ifdef _WIN32
class directory_iterator {
public:
class path_info {
public:
path_info(const string& path,
const WIN32_FIND_DATAW& find_data)
: path_(path),
find_data_(find_data)
{
}
string path() {
return path_join(path_, string_from_wstring(find_data_.cFileName));
}
protected:
const string& path_;
const WIN32_FIND_DATAW& find_data_;
};
directory_iterator()
: path_info_("", find_data_),
h_find_(INVALID_HANDLE_VALUE)
{
}
explicit directory_iterator(const string& path)
: path_(path),
path_info_(path, find_data_)
{
string wildcard = path;
if(wildcard[wildcard.size() - 1] != DIR_SEP) {
wildcard += DIR_SEP;
}
wildcard += "*";
h_find_ = FindFirstFileW(string_to_wstring(wildcard).c_str(),
&find_data_);
if(h_find_ != INVALID_HANDLE_VALUE) {
skip_dots();
}
}
~directory_iterator()
{
if(h_find_ != INVALID_HANDLE_VALUE) {
FindClose(h_find_);
}
}
directory_iterator& operator++()
{
step();
return *this;
}
path_info* operator-> ()
{
return &path_info_;
}
bool operator!=(const directory_iterator& other)
{
return h_find_ != other.h_find_;
}
protected:
bool step()
{
if(do_step()) {
return skip_dots();
}
return false;
}
bool do_step()
{
if(h_find_ != INVALID_HANDLE_VALUE) {
bool result = FindNextFileW(h_find_, &find_data_) == TRUE;
if(!result) {
FindClose(h_find_);
h_find_ = INVALID_HANDLE_VALUE;
}
return result;
}
return false;
}
bool skip_dots()
{
while(wcscmp(find_data_.cFileName, L".") == 0 ||
wcscmp(find_data_.cFileName, L"..") == 0)
{
if(!do_step()) {
return false;
}
}
return true;
}
string path_;
path_info path_info_;
WIN32_FIND_DATAW find_data_;
HANDLE h_find_;
};
#else /* _WIN32 */
class directory_iterator {
public:
class path_info {
public:
explicit path_info(const string& path)
: path_(path),
entry_(NULL)
{
}
string path() {
return path_join(path_, entry_->d_name);
}
void current_entry_set(const struct dirent *entry)
{
entry_ = entry;
}
protected:
const string& path_;
const struct dirent *entry_;
};
directory_iterator()
: path_info_(""),
name_list_(NULL),
num_entries_(-1),
cur_entry_(-1)
{
}
explicit directory_iterator(const string& path)
: path_(path),
path_info_(path_),
cur_entry_(0)
{
num_entries_ = scandir(path.c_str(),
&name_list_,
NULL,
alphasort);
if(num_entries_ < 0) {
perror("scandir");
}
else {
skip_dots();
}
}
~directory_iterator()
{
destroy_name_list();
}
directory_iterator& operator++()
{
step();
return *this;
}
path_info* operator-> ()
{
path_info_.current_entry_set(name_list_[cur_entry_]);
return &path_info_;
}
bool operator!=(const directory_iterator& other)
{
return name_list_ != other.name_list_;
}
protected:
bool step()
{
if(do_step()) {
return skip_dots();
}
return false;
}
bool do_step()
{
++cur_entry_;
if(cur_entry_ >= num_entries_) {
destroy_name_list();
return false;
}
return true;
}
/* Skip . and .. folders. */
bool skip_dots()
{
while(strcmp(name_list_[cur_entry_]->d_name, ".") == 0 ||
strcmp(name_list_[cur_entry_]->d_name, "..") == 0)
{
if(!step()) {
return false;
}
}
return true;
}
void destroy_name_list()
{
if(name_list_ == NULL) {
return;
}
for(int i = 0; i < num_entries_; ++i) {
free(name_list_[i]);
}
free(name_list_);
name_list_ = NULL;
}
string path_;
path_info path_info_;
struct dirent **name_list_;
int num_entries_, cur_entry_;
};
#endif /* _WIN32 */
size_t find_last_slash(const string& path)
{
for(size_t i = 0; i < path.size(); ++i) {
size_t index = path.size() - 1 - i;
#ifdef _WIN32
if(path[index] == DIR_SEP || path[index] == DIR_SEP_ALT)
#else
if(path[index] == DIR_SEP)
#endif
{
return index;
}
}
return string::npos;
}
} /* namespace */
static char *path_specials(const string& sub)
{
static bool env_init = false;
static char *env_shader_path;
static char *env_source_path;
if(!env_init) {
env_shader_path = getenv("CYCLES_SHADER_PATH");
/* NOTE: It is KERNEL in env variable for compatibility reasons. */
env_source_path = getenv("CYCLES_KERNEL_PATH");
env_init = true;
}
if(env_shader_path != NULL && sub == "shader") {
return env_shader_path;
}
else if(env_shader_path != NULL && sub == "source") {
return env_source_path;
}
return NULL;
}
#if defined(__linux__) || defined(__APPLE__)
static string path_xdg_cache_get()
{
const char *home = getenv("XDG_CACHE_HOME");
if(home) {
return string(home);
}
else {
home = getenv("HOME");
if(home == NULL) {
home = getpwuid(getuid())->pw_dir;
}
return path_join(string(home), ".cache");
}
}
#endif
void path_init(const string& path, const string& user_path)
{
cached_path = path;
cached_user_path = user_path;
#ifdef _MSC_VER
// workaround for https://svn.boost.org/trac/boost/ticket/6320
// indirectly init boost codec here since it's not thread safe, and can
// cause crashes when it happens in multithreaded image load
OIIO::Filesystem::exists(path);
#endif
}
string path_get(const string& sub)
{
char *special = path_specials(sub);
if(special != NULL)
return special;
if(cached_path == "")
cached_path = path_dirname(Sysutil::this_program_path());
return path_join(cached_path, sub);
}
string path_user_get(const string& sub)
{
if(cached_user_path == "")
cached_user_path = path_dirname(Sysutil::this_program_path());
return path_join(cached_user_path, sub);
}
string path_cache_get(const string& sub)
{
#if defined(__linux__) || defined(__APPLE__)
if(cached_xdg_cache_path == "") {
cached_xdg_cache_path = path_xdg_cache_get();
}
string result = path_join(cached_xdg_cache_path, "cycles");
return path_join(result, sub);
#else
/* TODO(sergey): What that should be on Windows? */
return path_user_get(path_join("cache", sub));
#endif
}
#if defined(__linux__) || defined(__APPLE__)
string path_xdg_home_get(const string& sub = "");
#endif
string path_filename(const string& path)
{
size_t index = find_last_slash(path);
if(index != string::npos) {
/* Corner cases to match boost behavior. */
#ifndef _WIN32
if(index == 0 && path.size() == 1) {
return path;
}
#endif
if(index == path.size() - 1) {
#ifdef _WIN32
if(index == 2) {
return string(1, DIR_SEP);
}
#endif
return ".";
}
return path.substr(index + 1, path.size() - index - 1);
}
return path;
}
string path_dirname(const string& path)
{
size_t index = find_last_slash(path);
if(index != string::npos) {
#ifndef _WIN32
if(index == 0 && path.size() > 1) {
return string(1, DIR_SEP);
}
#endif
return path.substr(0, index);
}
return "";
}
string path_join(const string& dir, const string& file)
{
if(dir.size() == 0) {
return file;
}
if(file.size() == 0) {
return dir;
}
string result = dir;
#ifndef _WIN32
if(result[result.size() - 1] != DIR_SEP &&
file[0] != DIR_SEP)
#else
if(result[result.size() - 1] != DIR_SEP &&
result[result.size() - 1] != DIR_SEP_ALT &&
file[0] != DIR_SEP &&
file[0] != DIR_SEP_ALT)
#endif
{
result += DIR_SEP;
}
result += file;
return result;
}
string path_escape(const string& path)
{
string result = path;
string_replace(result, " ", "\\ ");
return result;
}
bool path_is_relative(const string& path)
{
#ifdef _WIN32
# ifdef HAVE_SHLWAPI_H
return PathIsRelative(path.c_str());
# else /* HAVE_SHLWAPI_H */
if(path.size() >= 3) {
return !(((path[0] >= 'a' && path[0] <= 'z') ||
(path[0] >= 'A' && path[0] <= 'Z')) &&
path[1] == ':' && path[2] == DIR_SEP);
}
return true;
# endif /* HAVE_SHLWAPI_H */
#else /* _WIN32 */
if(path.size() == 0) {
return 1;
}
return path[0] != DIR_SEP;
#endif /* _WIN32 */
}
#ifdef _WIN32
/* Add a slash if the UNC path points to a share. */
static string path_unc_add_slash_to_share(const string& path)
{
size_t slash_after_server = path.find(DIR_SEP, 2);
if(slash_after_server != string::npos) {
size_t slash_after_share = path.find(DIR_SEP,
slash_after_server + 1);
if(slash_after_share == string::npos) {
return path + DIR_SEP;
}
}
return path;
}
/* Convert:
* \\?\UNC\server\share\folder\... to \\server\share\folder\...
* \\?\C:\ to C:\ and \\?\C:\folder\... to C:\folder\...
*/
static string path_unc_to_short(const string& path)
{
size_t len = path.size();
if((len > 3) &&
(path[0] == DIR_SEP) &&
(path[1] == DIR_SEP) &&
(path[2] == '?') &&
((path[3] == DIR_SEP) || (path[3] == DIR_SEP_ALT)))
{
if((len > 5) && (path[5] == ':')) {
return path.substr(4, len - 4);
}
else if((len > 7) &&
(path.substr(4, 3) == "UNC") &&
((path[7] == DIR_SEP) || (path[7] == DIR_SEP_ALT)))
{
return "\\\\" + path.substr(8, len - 8);
}
}
return path;
}
static string path_cleanup_unc(const string& path)
{
string result = path_unc_to_short(path);
if(path.size() > 2) {
/* It's possible path is now a non-UNC. */
if(result[0] == DIR_SEP && result[1] == DIR_SEP) {
return path_unc_add_slash_to_share(result);
}
}
return result;
}
/* Make path compatible for stat() functions. */
static string path_make_compatible(const string& path)
{
string result = path;
/* In Windows stat() doesn't recognize dir ending on a slash. */
if(result.size() > 3 && result[result.size() - 1] == DIR_SEP) {
result.resize(result.size() - 1);
}
/* Clean up UNC path. */
if((path.size() >= 3) && (path[0] == DIR_SEP) && (path[1] == DIR_SEP)) {
result = path_cleanup_unc(result);
}
/* Make sure volume-only path ends up wit ha directory separator. */
if(result.size() == 2 && result[1] == ':') {
result += DIR_SEP;
}
return result;
}
static int path_wstat(const wstring& path_wc, path_stat_t *st)
{
#if defined(_MSC_VER) || defined(__MINGW64__)
return _wstat64(path_wc.c_str(), st);
#elif defined(__MINGW32__)
return _wstati64(path_wc.c_str(), st);
#else
return _wstat(path_wc.c_str(), st);
#endif
}
static int path_stat(const string& path, path_stat_t *st)
{
wstring path_wc = string_to_wstring(path);
return path_wstat(path_wc, st);
}
#else /* _WIN32 */
static int path_stat(const string& path, path_stat_t *st)
{
return stat(path.c_str(), st);
}
#endif /* _WIN32 */
size_t path_file_size(const string& path)
{
path_stat_t st;
if(path_stat(path, &st) != 0) {
return -1;
}
return st.st_size;
}
bool path_exists(const string& path)
{
#ifdef _WIN32
string fixed_path = path_make_compatible(path);
wstring path_wc = string_to_wstring(fixed_path);
path_stat_t st;
if(path_wstat(path_wc, &st) != 0) {
return false;
}
return st.st_mode != 0;
#else /* _WIN32 */
struct stat st;
if(stat(path.c_str(), &st) != 0) {
return 0;
}
return st.st_mode != 0;
#endif /* _WIN32 */
}
bool path_is_directory(const string& path)
{
path_stat_t st;
if(path_stat(path, &st) != 0) {
return false;
}
return S_ISDIR(st.st_mode);
}
static void path_files_md5_hash_recursive(MD5Hash& hash, const string& dir)
{
if(path_exists(dir)) {
directory_iterator it(dir), it_end;
for(; it != it_end; ++it) {
if(path_is_directory(it->path())) {
path_files_md5_hash_recursive(hash, it->path());
}
else {
string filepath = it->path();
hash.append((const uint8_t*)filepath.c_str(), filepath.size());
hash.append_file(filepath);
}
}
}
}
string path_files_md5_hash(const string& dir)
{
/* computes md5 hash of all files in the directory */
MD5Hash hash;
path_files_md5_hash_recursive(hash, dir);
return hash.get_hex();
}
static bool create_directories_recursivey(const string& path)
{
if(path_is_directory(path)) {
/* Directory already exists, nothing to do. */
return true;
}
if(path_exists(path)) {
/* File exists and it's not a directory. */
return false;
}
string parent = path_dirname(path);
if(parent.size() > 0 && parent != path) {
if(!create_directories_recursivey(parent)) {
return false;
}
}
#ifdef _WIN32
wstring path_wc = string_to_wstring(path);
return _wmkdir(path_wc.c_str()) == 0;
#else
return mkdir(path.c_str(), 0777) == 0;
#endif
}
void path_create_directories(const string& filepath)
{
string path = path_dirname(filepath);
create_directories_recursivey(path);
}
bool path_write_binary(const string& path, const vector<uint8_t>& binary)
{
path_create_directories(path);
/* write binary file from memory */
FILE *f = path_fopen(path, "wb");
if(!f)
return false;
if(binary.size() > 0)
fwrite(&binary[0], sizeof(uint8_t), binary.size(), f);
fclose(f);
return true;
}
bool path_write_text(const string& path, string& text)
{
vector<uint8_t> binary(text.length(), 0);
std::copy(text.begin(), text.end(), binary.begin());
return path_write_binary(path, binary);
}
bool path_read_binary(const string& path, vector<uint8_t>& binary)
{
/* read binary file into memory */
FILE *f = path_fopen(path, "rb");
if(!f) {
binary.resize(0);
return false;
}
binary.resize(path_file_size(path));
if(binary.size() == 0) {
fclose(f);
return false;
}
if(fread(&binary[0], sizeof(uint8_t), binary.size(), f) != binary.size()) {
fclose(f);
return false;
}
fclose(f);
return true;
}
bool path_read_text(const string& path, string& text)
{
vector<uint8_t> binary;
if(!path_exists(path) || !path_read_binary(path, binary))
return false;
const char *str = (const char*)&binary[0];
size_t size = binary.size();
text = string(str, size);
return true;
}
uint64_t path_modified_time(const string& path)
{
path_stat_t st;
if(path_stat(path, &st) != 0) {
return 0;
}
return st.st_mtime;
}
bool path_remove(const string& path)
{
return remove(path.c_str()) == 0;
}
struct SourceReplaceState {
typedef map<string, string> ProcessedMapping;
/* Base director for all relative include headers. */
string base;
/* Result of processed files. */
ProcessedMapping processed_files;
/* Set of files which are considered "precompiled" and which are replaced
* with and empty string on a subsequent occurrence in include statement.
*/
set<string> precompiled_headers;
};
static string path_source_replace_includes_recursive(
const string& source,
const string& source_filepath,
SourceReplaceState *state);
static string line_directive(const SourceReplaceState& state,
const string& path,
const int line)
{
string unescaped_path = path;
/* First we make path relative. */
if(string_startswith(unescaped_path, state.base.c_str())) {
const string base_file = path_filename(state.base);
const size_t base_len = state.base.length();
unescaped_path = base_file +
unescaped_path.substr(base_len,
unescaped_path.length() - base_len);
}
/* Second, we replace all unsafe characters. */
const size_t length = unescaped_path.length();
string escaped_path = "";
for(size_t i = 0; i < length; ++i) {
const char ch = unescaped_path[i];
if(strchr("\"\'\?\\", ch) != NULL) {
escaped_path += "\\";
}
escaped_path += ch;
}
/* TODO(sergey): Check whether using std::to_string combined with several
* concatenation operations is any faster.
*/
return string_printf("#line %d \"%s\"", line, escaped_path.c_str());
}
static string path_source_handle_preprocessor(
const string& preprocessor_line,
const string& source_filepath,
const size_t line_number,
SourceReplaceState *state)
{
string result = preprocessor_line;
string token = string_strip(
preprocessor_line.substr(1, preprocessor_line.size() - 1));
if(string_startswith(token, "include")) {
token = string_strip(token.substr(7, token.size() - 7));
if(token[0] == '"') {
const size_t n_start = 1;
const size_t n_end = token.find("\"", n_start);
const string filename = token.substr(n_start, n_end - n_start);
const bool is_precompiled = string_endswith(token, "// PRECOMPILED");
string filepath = path_join(state->base, filename);
if(!path_exists(filepath)) {
filepath = path_join(path_dirname(source_filepath),
filename);
}
if(is_precompiled) {
state->precompiled_headers.insert(filepath);
}
string text;
if(path_read_text(filepath, text)) {
text = path_source_replace_includes_recursive(
text, filepath, state);
/* Use line directives for better error messages. */
result = line_directive(*state, filepath, 1) + "\n"
+ text + "\n"
+ line_directive(*state, source_filepath, line_number + 1);
}
}
}
return result;
}
/* Our own little c preprocessor that replaces #includes with the file
* contents, to work around issue of OpenCL drivers not supporting
* include paths with spaces in them.
*/
static string path_source_replace_includes_recursive(
const string& source,
const string& source_filepath,
SourceReplaceState *state)
{
/* Try to re-use processed file without spending time on replacing all
* include directives again.
*/
SourceReplaceState::ProcessedMapping::iterator replaced_file =
state->processed_files.find(source_filepath);
if(replaced_file != state->processed_files.end()) {
if(state->precompiled_headers.find(source_filepath) !=
state->precompiled_headers.end()) {
return "";
}
return replaced_file->second;
}
/* Perform full file processing. */
string result = "";
const size_t source_length = source.length();
size_t index = 0;
/* Information about where we are in the source. */
size_t line_number = 0, column_number = 1;
/* Currently gathered non-preprocessor token.
* Store as start/length rather than token itself to avoid overhead of
* memory re-allocations on each character concatenation.
*/
size_t token_start = 0, token_length = 0;
/* Denotes whether we're inside of preprocessor line, together with
* preprocessor line itself.
*
* TODO(sergey): Investigate whether using token start/end position
* gives measurable speedup.
*/
bool inside_preprocessor = false;
string preprocessor_line = "";
/* Actual loop over the whole source. */
while(index < source_length) {
const char ch = source[index];
if(ch == '\n') {
if(inside_preprocessor) {
result += path_source_handle_preprocessor(preprocessor_line,
source_filepath,
line_number,
state);
/* Start gathering net part of the token. */
token_start = index;
token_length = 0;
}
inside_preprocessor = false;
preprocessor_line = "";
column_number = 0;
++line_number;
}
else if(ch == '#' && column_number == 1 && !inside_preprocessor) {
/* Append all possible non-preprocessor token to the result. */
if(token_length != 0) {
result.append(source, token_start, token_length);
token_start = index;
token_length = 0;
}
inside_preprocessor = true;
}
if(inside_preprocessor) {
preprocessor_line += ch;
}
else {
++token_length;
}
++index;
++column_number;
}
/* Append possible tokens which happened before special events handled
* above.
*/
if(token_length != 0) {
result.append(source, token_start, token_length);
}
if(inside_preprocessor) {
result += path_source_handle_preprocessor(preprocessor_line,
source_filepath,
line_number,
state);
}
/* Store result for further reuse. */
state->processed_files[source_filepath] = result;
return result;
}
string path_source_replace_includes(const string& source,
const string& path,
const string& source_filename)
{
SourceReplaceState state;
state.base = path;
return path_source_replace_includes_recursive(
source,
path_join(path, source_filename),
&state);
}
FILE *path_fopen(const string& path, const string& mode)
{
#ifdef _WIN32
wstring path_wc = string_to_wstring(path);
wstring mode_wc = string_to_wstring(mode);
return _wfopen(path_wc.c_str(), mode_wc.c_str());
#else
return fopen(path.c_str(), mode.c_str());
#endif
}
void path_cache_clear_except(const string& name, const set<string>& except)
{
string dir = path_user_get("cache");
if(path_exists(dir)) {
directory_iterator it(dir), it_end;
for(; it != it_end; ++it) {
string filename = path_filename(it->path());
if(string_startswith(filename, name.c_str()))
if(except.find(filename) == except.end())
path_remove(it->path());
}
}
}
CCL_NAMESPACE_END