/* * 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_debug.h" #include "util/util_md5.h" #include "util/util_path.h" #include "util/util_string.h" #include #include #include OIIO_NAMESPACE_USING #include #include #if defined(_WIN32) # define DIR_SEP '\\' # define DIR_SEP_ALT '/' # include #else # define DIR_SEP '/' # include # include # include # include #endif #ifdef HAVE_SHLWAPI_H # include #endif #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& 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 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& 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 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; } static string line_directive(const string& base, const string& path, int line) { string escaped_path = path; /* First we make path relative. */ if(string_startswith(escaped_path, base.c_str())) { const string base_file = path_filename(base); const size_t base_len = base.length(); escaped_path = base_file + escaped_path.substr(base_len, escaped_path.length() - base_len); } /* Second, we replace all unsafe characters. */ string_replace(escaped_path, "\"", "\\\""); string_replace(escaped_path, "\'", "\\\'"); string_replace(escaped_path, "\?", "\\\?"); string_replace(escaped_path, "\\", "\\\\"); return string_printf("#line %d \"%s\"", line, escaped_path.c_str()); } static string path_source_replace_includes_recursive( const string& base, const string& source, const string& source_filepath) { /* 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. */ string result = ""; vector lines; string_split(lines, source, "\n", false); for(size_t i = 0; i < lines.size(); ++i) { string line = lines[i]; if(line[0] == '#') { string token = string_strip(line.substr(1, 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); string filepath = path_join(base, filename); if(!path_exists(filepath)) { filepath = path_join(path_dirname(source_filepath), filename); } string text; if(path_read_text(filepath, text)) { text = path_source_replace_includes_recursive( base, text, filepath); /* Use line directives for better error messages. */ line = line_directive(base, filepath, 1) + token.replace(0, n_end + 1, "\n" + text + "\n") + line_directive(base, source_filepath, i + 1); } } } } result += line + "\n"; } return result; } string path_source_replace_includes(const string& source, const string& path, const string& source_filename) { return path_source_replace_includes_recursive( path, source, path_join(path, source_filename)); } 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& 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