#include "dirut.h" #include "iterator.h" #include "filelist.h" #include "fts.h" #include "pathsplit.h" #include "path.h" #include <util/generic/yexception.h> #include <util/system/compiler.h> #include <util/system/fs.h> #include <util/system/maxlen.h> #include <util/system/yassert.h> void SlashFolderLocal(TString& folder) { if (!folder) return; #ifdef _win32_ size_t pos; while ((pos = folder.find('/')) != TString::npos) folder.replace(pos, 1, LOCSLASH_S); #endif if (folder[folder.size() - 1] != LOCSLASH_C) folder.append(LOCSLASH_S); } #ifndef _win32_ bool correctpath(TString& folder) { return resolvepath(folder, "/"); } bool resolvepath(TString& folder, const TString& home) { Y_ASSERT(home && home.at(0) == '/'); if (!folder) { return false; } // may be from windows char* ptr = folder.begin(); while ((ptr = strchr(ptr, '\\')) != nullptr) *ptr = '/'; if (folder.at(0) == '~') { if (folder.length() == 1 || folder.at(1) == '/') { folder = GetHomeDir() + (folder.data() + 1); } else { char* buf = (char*)alloca(folder.length() + 1); strcpy(buf, folder.data() + 1); char* p = strchr(buf, '/'); if (p) *p++ = 0; passwd* pw = getpwnam(buf); if (pw) { folder = pw->pw_dir; folder += "/"; if (p) folder += p; } else { return false; // unknown user } } } int len = folder.length() + home.length() + 1; char* path = (char*)alloca(len); if (folder.at(0) != '/') { strcpy(path, home.data()); strcpy(strrchr(path, '/') + 1, folder.data()); // the last char must be '/' if it's a dir } else { strcpy(path, folder.data()); } len = strlen(path) + 1; // grabbed from url.cpp char* newpath = (char*)alloca(len + 2); const char** pp = (const char**)alloca(len * sizeof(char*)); int i = 0; for (char* s = path; s;) { pp[i++] = s; s = strchr(s, '/'); if (s) *s++ = 0; } for (int j = 1; j < i;) { const char*& p = pp[j]; if (strcmp(p, ".") == 0 || strcmp(p, "") == 0) { if (j == i - 1) { p = ""; break; } else { memmove(pp + j, pp + j + 1, (i - j - 1) * sizeof(p)); --i; } } else if (strcmp(p, "..") == 0) { if (j == i - 1) { if (j == 1) { p = ""; } else { --i; pp[j - 1] = ""; } break; } else { if (j == 1) { memmove(pp + j, pp + j + 1, (i - j - 1) * sizeof(p)); --i; } else { memmove(pp + j - 1, pp + j + 1, (i - j - 1) * sizeof(p)); i -= 2; --j; } } } else ++j; } char* s = newpath; for (int k = 0; k < i; k++) { s = strchr(strcpy(s, pp[k]), 0); *s++ = '/'; } *(--s) = 0; folder = newpath; return true; } #else using dir_type = enum { dt_empty, dt_error, dt_up, dt_dir }; // precondition: *ptr != '\\' || *ptr == 0 (cause dt_error) // postcondition: *ptr != '\\' template <typename T> static int next_dir(T*& ptr) { int has_blank = 0; int has_dot = 0; int has_letter = 0; int has_ctrl = 0; while (*ptr && *ptr != '\\') { int c = (unsigned char)*ptr++; switch (c) { case ' ': ++has_blank; break; case '.': ++has_dot; break; case '/': case ':': case '*': case '?': case '"': case '<': case '>': case '|': ++has_ctrl; break; default: if (c == 127 || c < ' ') ++has_ctrl; else ++has_letter; } } if (*ptr) ++ptr; if (has_ctrl) return dt_error; if (has_letter) return dt_dir; if (has_dot && has_blank) return dt_error; if (has_dot == 1) return dt_empty; if (has_dot == 2) return dt_up; return dt_error; } using disk_type = enum { dk_noflags = 0, dk_unc = 1, dk_hasdrive = 2, dk_fromroot = 4, dk_error = 8 }; // root slash (if any) - part of disk template <typename T> static int skip_disk(T*& ptr) { int result = dk_noflags; if (!*ptr) return result; if (ptr[0] == '\\' && ptr[1] == '\\') { result |= dk_unc | dk_fromroot; ptr += 2; if (next_dir(ptr) != dt_dir) return dk_error; // has no host name if (next_dir(ptr) != dt_dir) return dk_error; // has no share name } else { if (*ptr && *(ptr + 1) == ':') { result |= dk_hasdrive; ptr += 2; } if (*ptr == '\\' || *ptr == '/') { ++ptr; result |= dk_fromroot; } } return result; } int correctpath(char* cpath, const char* path) { if (!path || !*path) { *cpath = 0; return 1; } char* ptr = (char*)path; char* cptr = cpath; int counter = 0; while (*ptr) { char c = *ptr; if (c == '/') c = '\\'; if (c == '\\') ++counter; else counter = 0; if (counter <= 1) { *cptr = c; ++cptr; } ++ptr; } *cptr = 0; // replace '/' by '\' int dk = skip_disk(cpath); if (dk == dk_error) return 0; char* ptr1 = ptr = cpath; int level = 0; while (*ptr) { switch (next_dir(ptr)) { case dt_dir: ++level; break; case dt_empty: memmove(ptr1, ptr, strlen(ptr) + 1); ptr = ptr1; break; case dt_up: --level; if (level >= 0) { *--ptr1 = 0; ptr1 = strrchr(cpath, '\\'); if (!ptr1) ptr1 = cpath; else ++ptr1; memmove(ptr1, ptr, strlen(ptr) + 1); ptr = ptr1; break; } else if (level == -1 && (dk & dk_hasdrive)) { if (*ptr && *(ptr + 1) == ':' && *(cpath - 2) == ':') { memmove(cpath - 3, ptr, strlen(ptr) + 1); return 1; } } if (dk & dk_fromroot) return 0; break; case dt_error: default: return 0; } ptr1 = ptr; } if ((ptr > cpath || ptr == cpath && dk & dk_unc) && *(ptr - 1) == '\\') *(ptr - 1) = 0; return 1; } static inline int normchar(unsigned char c) { return (c < 'a' || c > 'z') ? c : c - 32; } static inline char* strslashcat(char* a, const char* b) { size_t len = strlen(a); if (len && a[len - 1] != '\\') a[len++] = '\\'; strcpy(a + len, b); return a; } int resolvepath(char* apath, const char* rpath, const char* cpath) { const char* redisk = rpath; if (!rpath || !*rpath) return 0; int rdt = skip_disk(redisk); if (rdt == dk_error) return 0; if (rdt & dk_unc || rdt & dk_hasdrive && rdt & dk_fromroot) { return correctpath(apath, rpath); } const char* cedisk = cpath; if (!cpath || !*cpath) return 0; int cdt = skip_disk(cedisk); if (cdt == dk_error) return 0; char* tpath = (char*)alloca(strlen(rpath) + strlen(cpath) + 3); // rdt&dk_hasdrive && !rdt&dk_fromroot if (rdt & dk_hasdrive) { if (!(cdt & dk_fromroot)) return 0; if (cdt & dk_hasdrive && normchar(*rpath) != normchar(*cpath)) return 0; memcpy(tpath, rpath, 2); memcpy(tpath + 2, cedisk, strlen(cedisk) + 1); strslashcat(tpath, redisk); // !rdt&dk_hasdrive && rdt&dk_fromroot } else if (rdt & dk_fromroot) { if (!(cdt & dk_hasdrive) && !(cdt & dk_unc)) return 0; memcpy(tpath, cpath, cedisk - cpath); tpath[cedisk - cpath] = 0; strslashcat(tpath, redisk); // !rdt&dk_hasdrive && !rdt&dk_fromroot } else { if (!(cdt & dk_fromroot) || !(cdt & dk_hasdrive) && !(cdt & dk_unc)) return 0; strslashcat(strcpy(tpath, cpath), redisk); } return correctpath(apath, tpath); } bool correctpath(TString& filename) { char* ptr = (char*)alloca(filename.size() + 2); if (correctpath(ptr, filename.data())) { filename = ptr; return true; } return false; } bool resolvepath(TString& folder, const TString& home) { char* ptr = (char*)alloca(folder.size() + 3 + home.size()); if (resolvepath(ptr, folder.data(), home.data())) { folder = ptr; return true; } return false; } #endif // !defined _win32_ char GetDirectorySeparator() { return LOCSLASH_C; } const char* GetDirectorySeparatorS() { return LOCSLASH_S; } void RemoveDirWithContents(TString dirName) { SlashFolderLocal(dirName); TDirIterator dir(dirName, TDirIterator::TOptions(FTS_NOSTAT)); for (auto it = dir.begin(); it != dir.end(); ++it) { switch (it->fts_info) { case FTS_F: case FTS_DEFAULT: case FTS_DP: case FTS_SL: case FTS_SLNONE: if (!NFs::Remove(it->fts_path)) ythrow TSystemError() << "error while removing " << it->fts_path; break; } } } int mkpath(char* path, int mode) { return NFs::MakeDirectoryRecursive(path, NFs::EFilePermission(mode)) ? 0 : -1; } // Implementation of realpath in FreeBSD (version 9.0 and less) and GetFullPathName in Windows // did not require last component of the file name to exist (other implementations will fail // if it does not). Use RealLocation if that behaviour is required. TString RealPath(const TString& path) { TTempBuf result; Y_ASSERT(result.Size() > MAX_PATH); //TMP_BUF_LEN > MAX_PATH #ifdef _win_ if (GetFullPathName(path.data(), result.Size(), result.Data(), nullptr) == 0) #else if (realpath(path.data(), result.Data()) == nullptr) #endif ythrow TFileError() << "RealPath failed \"" << path << "\""; return result.Data(); } TString RealLocation(const TString& path) { if (NFs::Exists(path)) return RealPath(path); TString dirpath = GetDirName(path); if (NFs::Exists(dirpath)) return RealPath(dirpath) + GetDirectorySeparatorS() + GetFileNameComponent(path.data()); ythrow TFileError() << "RealLocation failed \"" << path << "\""; } int MakeTempDir(char path[/*FILENAME_MAX*/], const char* prefix) { int ret; TString sysTmp; #ifdef _win32_ if (!prefix || *prefix == '/') { #else if (!prefix) { #endif sysTmp = GetSystemTempDir(); prefix = sysTmp.data(); } if ((ret = ResolvePath(prefix, nullptr, path, 1)) != 0) return ret; if (!TFileStat(path).IsDir()) return ENOENT; if ((strlcat(path, "tmpXXXXXX", FILENAME_MAX) > FILENAME_MAX - 100)) return EINVAL; if (!(mkdtemp(path))) return errno ? errno : EINVAL; strcat(path, LOCSLASH_S); return 0; } bool IsDir(const TString& path) { return TFileStat(path).IsDir(); } TString GetHomeDir() { TString s(getenv("HOME")); if (!s) { #ifndef _win32_ passwd* pw = nullptr; s = getenv("USER"); if (s) pw = getpwnam(s.data()); else pw = getpwuid(getuid()); if (pw) s = pw->pw_dir; else #endif { char* cur_dir = getcwd(nullptr, 0); s = cur_dir; free(cur_dir); } } return s; } void MakeDirIfNotExist(const char* path, int mode) { if (!NFs::MakeDirectory(path, NFs::EFilePermission(mode)) && !NFs::Exists(path)) { ythrow TSystemError() << "failed to create directory " << path; } } void MakePathIfNotExist(const char* path, int mode) { NFs::MakeDirectoryRecursive(path, NFs::EFilePermission(mode)); if (!NFs::Exists(path) || !TFileStat(path).IsDir()) { ythrow TSystemError() << "failed to create directory " << path; } } const char* GetFileNameComponent(const char* f) { const char* p = strrchr(f, LOCSLASH_C); #ifdef _win_ // "/" is also valid char separator on Windows const char* p2 = strrchr(f, '/'); if (p2 > p) p = p2; #endif if (p) { return p + 1; } return f; } TString GetSystemTempDir() { #ifdef _win_ char buffer[1024]; DWORD size = GetTempPath(1024, buffer); if (!size) { ythrow TSystemError() << "failed to get system temporary directory"; } return TString(buffer, size); #else const char* var = "TMPDIR"; const char* def = "/tmp"; const char* r = getenv(var); const char* result = r ? r : def; return result[0] == '/' ? result : ResolveDir(result); #endif } TString ResolveDir(const char* path) { return ResolvePath(path, true); } bool SafeResolveDir(const char* path, TString& result) { try { result = ResolvePath(path, true); return true; } catch (...) { return false; } } TString GetDirName(const TString& path) { return TFsPath(path).Dirname(); } #ifdef _win32_ char* realpath(const char* pathname, char resolved_path[MAXPATHLEN]) { // partial implementation: no path existence check return _fullpath(resolved_path, pathname, MAXPATHLEN - 1); } #endif TString GetBaseName(const TString& path) { return TFsPath(path).Basename(); } static bool IsAbsolutePath(const char* str) { return str && TPathSplitTraitsLocal::IsAbsolutePath(TStringBuf(str, NStringPrivate::GetStringLengthWithLimit(str, 3))); } int ResolvePath(const char* rel, const char* abs, char res[/*MAXPATHLEN*/], bool isdir) { char t[MAXPATHLEN * 2 + 3]; size_t len; *res = 0; if (!rel || !*rel) return EINVAL; if (!IsAbsolutePath(rel) && IsAbsolutePath(abs)) { len = strlcpy(t, abs, sizeof(t)); if (len >= sizeof(t) - 3) return EINVAL; if (t[len - 1] != LOCSLASH_C) t[len++] = LOCSLASH_C; len += strlcpy(t + len, rel, sizeof(t) - len); } else { len = strlcpy(t, rel, sizeof(t)); } if (len >= sizeof(t) - 3) return EINVAL; if (isdir && t[len - 1] != LOCSLASH_C) { t[len++] = LOCSLASH_C; t[len] = 0; } if (!realpath(t, res)) { if (!isdir && realpath(GetDirName(t).data(), res)) { len = strlen(res); if (res[len - 1] != LOCSLASH_C) { res[len++] = LOCSLASH_C; res[len] = 0; } strcpy(res + len, GetBaseName(t).data()); return 0; } return errno ? errno : ENOENT; } if (isdir) { len = strlen(res); if (res[len - 1] != LOCSLASH_C) { res[len++] = LOCSLASH_C; res[len] = 0; } } return 0; } TString ResolvePath(const char* rel, const char* abs, bool isdir) { char buf[PATH_MAX]; if (ResolvePath(rel, abs, buf, isdir)) ythrow yexception() << "cannot resolve path: \"" << rel << "\""; return buf; } TString ResolvePath(const char* path, bool isDir) { return ResolvePath(path, nullptr, isDir); } TString StripFileComponent(const TString& fileName) { TString dir = IsDir(fileName) ? fileName : GetDirName(fileName); if (!dir.empty() && dir.back() != GetDirectorySeparator()) { dir.append(GetDirectorySeparator()); } return dir; }