diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9979ea1..7c106b1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -6,7 +6,6 @@ name: ci on: [pull_request, push] - concurrency: group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} cancel-in-progress: true @@ -35,5 +34,5 @@ jobs: - uses: actions/upload-artifact@v4 if: always() with: - name: tester - path: tmp/test/tester.exe + name: tests + path: tmp diff --git a/.gitignore b/.gitignore index 6a74ea7..846f9c0 100644 --- a/.gitignore +++ b/.gitignore @@ -14,9 +14,14 @@ *.exp *.hex *.def +*.res +*.rc # Vscode /.vscode/ # ignore patch files -*.patch \ No newline at end of file +*.patch + +.clang-tidy +.clang-format \ No newline at end of file diff --git a/Makefile b/Makefile index 54646df..2fac1fa 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,12 @@ BUILD_LINK = /DEBUG BASE_CFLAGS = /EHsc CFLAGS = $(BASE_CFLAGS) $(BUILD_CFLAGS) $(CLFLAGS) LFLAGS = $(BUILD_LINK) $(LINKFLAGS) +# shlwapi: needed for basic path operations +# pathcch: needed for long path canonicalization +# advapi32: needed for win32 ACL interactions +API_LIBS = Shlwapi.lib \ +Pathcch.lib \ +Advapi32.lib SRCS = cl.obj \ execute.obj \ @@ -55,7 +61,7 @@ linker_invocation.obj all : install test cl.exe : $(SRCS) - link $(LFLAGS) $** Shlwapi.lib /out:cl.exe + link $(LFLAGS) $** $(API_LIBS) /out:cl.exe install : cl.exe mkdir $(PREFIX) @@ -151,7 +157,7 @@ test_pipe_overflow: build_and_check_test_sample set SPACK_CC=%SPACK_CC_TMP% build_zerowrite_test: test\writezero.obj - link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe + link $(LFLAGS) $** $(API_LIBS) /out:writezero.exe test_zerowrite: build_zerowrite_test echo "-----------------------" diff --git a/src/utils.cxx b/src/utils.cxx index 6bb8c6c..76f9783 100644 --- a/src/utils.cxx +++ b/src/utils.cxx @@ -6,12 +6,14 @@ #include "utils.h" #include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -28,11 +30,13 @@ #include #include #include +#include #include #include #include #include #include "shlwapi.h" +#include "PathCch.h" ////////////////////////////////////////////////////////// // String helper methods adding cxx20 features to cxx14 // @@ -181,6 +185,20 @@ std::string lstrip(const std::string& str, const std::string& substr) { return str.substr(substr.size(), str.size()); } +/** + * Strips pair of double or single quotes from front and back of string + * Checks single quotes before double + * + * Returns str with at most one fewer pair of enclosing single/double quotes + * + */ +std::string stripquotes(const std::string& str) { + // if we have single quotes, strip those + if (startswith(str, "'")) return strip(lstrip(str, "'"), "'"); + // if we have double, do that instead. + return strip(lstrip(str, "\""), "\""); +} + /** * combines list of strings into one string joined on join_char */ @@ -306,40 +324,34 @@ std::regex_constants::match_flag_type composeMatchTypes( return composed_flag; } -std::string regexSearch( +std::smatch regexSearch( const std::string& searchDomain, const std::string& regex, const std::vector& opts, const std::vector& flags) { - std::string result_str; std::regex_constants::syntax_option_type const opt = composeRegexOptions(opts); std::regex_constants::match_flag_type const flag = composeMatchTypes(flags); std::regex const reg(regex, opt); std::smatch match; if (!std::regex_search(searchDomain, match, reg, flag)) { - result_str = std::string(); - } else { - result_str = match.str(); - } - return result_str; + return std::smatch(); + } + return match; } -std::string regexMatch( +std::smatch regexMatch( const std::string& searchDomain, const std::string& regex, const std::vector& opts, const std::vector& flags) { - std::string result_str; std::regex_constants::syntax_option_type const opt = composeRegexOptions(opts); std::regex_constants::match_flag_type const flag = composeMatchTypes(flags); std::regex const reg(regex, opt); std::smatch match; if (!std::regex_match(searchDomain, match, reg, flag)) { - result_str = std::string(); - } else { - result_str = match.str(); + return std::smatch(); } - return result_str; + return match; } std::string regexReplace( @@ -529,7 +541,8 @@ void replace_path_characters(char* path, size_t len) { * null terminators. * \param bsize the lengh of the padding to add */ -char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { +char* pad_path(const char* pth, DWORD str_size, char padding_char, + DWORD bsize) { // If str_size > bsize we get inappropriate conversion // from signed to unsigned if (str_size > bsize) { @@ -543,13 +556,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { padded_path[i] = pth[j]; ++j; } else { - padded_path[i] = '|'; + padded_path[i] = padding_char; } } padded_path[bsize] = '\0'; return padded_path; } +std::string escape_backslash(const std::string& path) { + std::string escaped; + escaped.reserve(path.length() * 2); + for (char const c : path) { + if (c == '\\') { + escaped += "\\\\"; + } else { + escaped += c; + } + } + return escaped; +} + /** * Given a padded library path, return how much the path * has been padded @@ -587,15 +613,46 @@ std::string strip_padding(const std::string& lib) { * than the system MAX_PATH_LENGTH * (different from MAX_NAME_LEN) */ -std::string getSFN(const std::string& path) { +std::string getSFN(const std::string& path, const bool make_file = false) { // Use "disable string parsing" prefix in case // the path is too long std::string const escaped = R"(\\?\)" + path; + // We cannot get the sfn for a path that doesn't exist + // if we find that the sfn we're looking for doesn't exist + // create a stub of the file, and allow the subsequent + // commands to overwrite it + if (!PathFileExistsA(path.c_str())) { + if (!make_file) { + char message[50]; + std::snprintf(message, sizeof(message), "File %s does not exist to create an SFN name.", path.c_str()); + throw FileNotExist(message); + } + HANDLE h_file = CreateFileA(path.c_str(), GENERIC_WRITE, 0, nullptr, + CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr); + if (h_file == INVALID_HANDLE_VALUE) { + debug("File " + path + + " does not exist, nor can it be created, unable to " + "compute SFN\n"); + CloseHandle(h_file); + return std::string(); + } + CloseHandle(h_file); + } // Get SFN length so we can create buffer DWORD const sfn_size = GetShortPathNameA(escaped.c_str(), NULL, 0); //NOLINT char* sfn = new char[sfn_size + 1]; - GetShortPathNameA(escaped.c_str(), sfn, escaped.length()); + DWORD const res = GetShortPathNameA(escaped.c_str(), sfn, escaped.length()); + if (!res) { + + std::cerr << "Failed to process short name for " << path + << " Error: " << reportLastError() << "\n"; + throw SFNProcessingError("Unable to create SFN"); + } + if (!sfn && res) { + // buffer was too small + throw SFNProcessingError("Cannot create SFN name, cannot allocate sufficient space"); + } // sfn is null terminated per win32 api // Ensure we strip out the disable string parsing prefix std::string s_sfn = lstrip(sfn, R"(\\?\)"); @@ -611,7 +668,7 @@ std::string getSFN(const std::string& path) { */ std::string short_name(const std::string& path) { // Get SFN for path to name - std::string const new_abs_out = getSFN(path); + std::string const new_abs_out = getSFN(path, true); if (new_abs_out.length() > MAX_NAME_LEN) { std::cerr << "DLL path " << path << " too long to relocate.\n"; std::cerr << "Shortened DLL path " << new_abs_out @@ -623,6 +680,43 @@ std::string short_name(const std::string& path) { return new_abs_out; } +std::string MakePathAbsolute(const std::string& path) { + if (IsPathAbsolute(path)) { + return path; + } + // relative paths, assume they're relative to the CWD of the linker (as they have to be) + return join({GetCWD(), path}, "\\"); +} + +std::string CanonicalizePath(const std::string& path) { + std::wstring const wpath = ConvertASCIIToWide(path); + wchar_t canonicalized_path[PATHCCH_MAX_CCH]; + const size_t buffer_size = ARRAYSIZE(canonicalized_path); + + HRESULT const status = PathCchCanonicalizeEx( + canonicalized_path, buffer_size, wpath.c_str(), + PATHCCH_ALLOW_LONG_PATHS // Flags for long path support + ); + + if (!SUCCEEDED(status)) { + std::stringstream status_report; + status_report << "Cannot canonicalize path " + path + " error: " + << std::hex << status; + throw NameTooLongError(status_report.str().c_str()); + } + return ConvertWideToASCII(canonicalized_path); +} + +std::string EnsureValidLengthPath(const std::string& path) { + std::string proper_length_path = path; + if (path.length() > MAX_NAME_LEN) { + // Name is too long we need to attempt to shorten + std::string const short_path = short_name(path); + proper_length_path = short_path; + } + return proper_length_path; +} + /** * Mangles a string representing a path to have no path characters * instead path characters (i.e. \\, :, etc) are replaced with @@ -633,19 +727,10 @@ std::string short_name(const std::string& path) { std::string mangle_name(const std::string& name) { std::string abs_out; std::string mangled_abs_out; - if (IsPathAbsolute(name)) { - abs_out = name; - } else { - // relative paths, assume they're relative to the CWD of the linker (as they have to be) - abs_out = join({GetCWD(), name}, "\\"); - } + abs_out = MakePathAbsolute(name); + abs_out = CanonicalizePath(abs_out); // Now that we have the full path, check size - if (abs_out.length() > MAX_NAME_LEN) { - // Name is too long we need to attempt to shorten - std::string const new_abs_out = short_name(abs_out); - // If new, shortened path is too long, bail - abs_out = new_abs_out; - } + abs_out = EnsureValidLengthPath(abs_out); char* chr_abs_out = new char[abs_out.length() + 1]; strcpy(chr_abs_out, abs_out.c_str()); replace_path_characters(chr_abs_out, abs_out.length()); @@ -688,7 +773,7 @@ bool SpackInstalledLib(const std::string& lib) { return false; } std::string const stripped_lib = strip_padding(lib); - startswith(stripped_lib, prefix); + return startswith(stripped_lib, prefix); } LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} @@ -846,4 +931,19 @@ NameTooLongError::NameTooLongError(char const* const message) char const* NameTooLongError::what() const { return exception::what(); +} + + +FileNotExist::FileNotExist(char const* const message) + : std::runtime_error(message) {} + +char const* FileNotExist::what() const { + return exception::what(); +} + +SFNProcessingError::SFNProcessingError(char const* const message) + : std::runtime_error(message) {} + +char const* SFNProcessingError::what() const { + return exception::what(); } \ No newline at end of file diff --git a/src/utils.h b/src/utils.h index b04d929..5dda860 100644 --- a/src/utils.h +++ b/src/utils.h @@ -16,6 +16,9 @@ #include #include #include +#include +#include +#include #include "version.h" @@ -52,7 +55,8 @@ enum ExitConditions { LIB_REMOVE_FAILURE, NORMALIZE_NAME_FAILURE, COFF_PARSE_FAILURE, - FILE_RENAME_FAILURE + FILE_RENAME_FAILURE, + CANNOT_OPEN_FILE_FAILURE }; typedef std::vector StrList; @@ -95,6 +99,9 @@ std::string strip(const std::string& s, const std::string& substr); //Strips substr of LHS of the larger string std::string lstrip(const std::string& s, const std::string& substr); +//Strips off leading and trailing quotes +std::string stripquotes(const std::string& str); + // Joins vector of strings by join character std::string join(const StrList& args, const std::string& join_char = " "); @@ -142,7 +149,7 @@ std::string quoteAsNeeded(std::string& str); /// @param regex - regex used to search /// @param opts - optional argument, list of regex tuning options to adapt the search behavior /// @return Character sequence matching search regex -std::string regexSearch( +std::smatch regexSearch( const std::string& searchDomain, const std::string& regex, const std::vector& opts = {}, const std::vector& flags = {}); @@ -153,7 +160,7 @@ std::string regexSearch( /// @param regex - regex used to match /// @param opts - optional argument, list of regex tuning options to adapt the match behavior /// @return Character sequence matching regex -std::string regexMatch( +std::smatch regexMatch( const std::string& searchDomain, const std::string& regex, const std::vector& opts = {}, const std::vector& flags = {}); @@ -186,9 +193,14 @@ std::string short_name(const std::string& path); std::string mangle_name(const std::string& name); +std::string CanonicalizePath(const std::string& path); + int get_padding_length(const std::string& name); -char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); +char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', + DWORD bsize = MAX_NAME_LEN); + +std::string escape_backslash(const std::string& path); void replace_path_characters(char* path, size_t len); @@ -196,6 +208,10 @@ void replace_special_characters(char* mangled, size_t len); bool SpackInstalledLib(const std::string& lib); +std::string MakePathAbsolute(const std::string& path); + +std::string EnsureValidLengthPath(const std::string& path); + // File and File handle helpers // /** * @brief Returns boolean indicating whether @@ -270,8 +286,20 @@ const std::map path_to_special_characters{{'\\', '|'}, class NameTooLongError : public std::runtime_error { public: - NameTooLongError(char const* const message); + explicit NameTooLongError(char const* const message); virtual char const* what() const; }; +class FileNotExist : public std::runtime_error { + public: + explicit FileNotExist(char const* const message); + virtual char const * what() const; +}; + +class SFNProcessingError : public std::runtime_error { + public: + explicit SFNProcessingError(char const * const message); + virtual char const* what() const; +}; + static bool DEBUG = false; diff --git a/src/winrpath.cxx b/src/winrpath.cxx index f98e063..c895315 100644 --- a/src/winrpath.cxx +++ b/src/winrpath.cxx @@ -24,6 +24,7 @@ #include #include #include +#include /* * Checks a DLL name for special characters, if we're deploying, a path character, if we're @@ -104,6 +105,10 @@ bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { new_library_loc = short_name(new_library_loc); } catch (NameTooLongError& e) { return false; + } catch (FileNotExist &e) { + return false; + } catch (SFNProcessingError &e) { + return false; } } char* new_lib_pth = @@ -308,7 +313,9 @@ bool LibRename::ComputeDefFile() { std::string line; // Read until the output column titles while (std::getline(input_file, line)) { - std::string const res = regexSearch(line, R"(ordinal\s+name)"); + std::smatch search_res = regexSearch(line, R"(ordinal\s+name)"); + if (search_res.empty()) break; + std::string const res = search_res.str(); if (!res.empty()) { break; }