-
Notifications
You must be signed in to change notification settings - Fork 15
Windows compatibility fixes (spaces, stubs, includes) #86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,13 @@ | |
| #include <sys/stat.h> | ||
| #include <unistd.h> | ||
| #include <algorithm> | ||
| #include <cstdint> | ||
|
|
||
| #if defined(_WIN32) || defined(__MINGW32__) | ||
| #include <io.h> | ||
| #include <fcntl.h> | ||
| #include "win32_compat.h" | ||
| #endif | ||
|
|
||
| static const char help_message[] = | ||
| PROJECT_NAME " version " PROJECT_VERSION | ||
|
|
@@ -442,7 +449,7 @@ static void edit_tags_interactively(ot::opus_tags& tags, const std::optional<std | |
| } | ||
|
|
||
| // Applying the new tags. | ||
| tags_file = fopen(tags_path.c_str(), "re"); | ||
| tags_file = fopen(tags_path.c_str(), "rb"); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why remove the e flag? |
||
| if (tags_file == nullptr) | ||
| throw ot::status {ot::st::standard_error, "Error opening " + tags_path + ": " + strerror(errno)}; | ||
| try { | ||
|
|
@@ -479,7 +486,7 @@ static void output_cover(const ot::opus_tags& tags, const ot::options &opt) | |
| throw ot::status {ot::st::error, "Could not identify '" + opt.cover_out.value() + "': " + strerror(errno)}; | ||
| } | ||
|
|
||
| output = fopen(opt.cover_out->c_str(), "w"); | ||
| output = fopen(opt.cover_out->c_str(), "wb"); | ||
| if (output == nullptr) | ||
| throw ot::status {ot::st::standard_error, "Could not open '" + opt.cover_out.value() + "' for writing: " + strerror(errno)}; | ||
| } | ||
|
|
@@ -557,7 +564,7 @@ static void run_single(const ot::options& opt, const std::string& path_in, const | |
| ot::file input; | ||
| if (path_in == "-") | ||
| input = stdin; | ||
| else if ((input = fopen(path_in.c_str(), "re")) == nullptr) | ||
| else if ((input = fopen(path_in.c_str(), "rb")) == nullptr) | ||
| throw ot::status {ot::st::standard_error, | ||
| "Could not open '" + path_in + "' for reading: " + strerror(errno)}; | ||
| ot::ogg_reader reader(input.get()); | ||
|
|
@@ -598,7 +605,7 @@ static void run_single(const ot::options& opt, const std::string& path_in, const | |
| /* The output file exists. */ | ||
| if (!S_ISREG(output_info.st_mode)) { | ||
| /* Special files are opened for writing directly. */ | ||
| if ((final_output = fopen(path_out->c_str(), "we")) == nullptr) | ||
| if ((final_output = fopen(path_out->c_str(), "wb")) == nullptr) | ||
| throw ot::status {ot::st::standard_error, | ||
| "Could not open '" + path_out.value() + "' for writing: " + strerror(errno)}; | ||
| output = final_output.get(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -55,6 +55,17 @@ | |
| #define be32toh(x) OSSwapBigToHostInt32(x) | ||
| #endif | ||
|
|
||
| #if defined(_WIN32) || defined(__MINGW32__) | ||
| #include <cstdint> | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rather than include cstdint in the .h and in every .cc, including it in opustags.h only is enough since all the .cc include it. |
||
| inline uint32_t htole32(uint32_t x) { return x; } | ||
| inline uint32_t le32toh(uint32_t x) { return x; } | ||
| inline uint32_t htobe32(uint32_t x) { | ||
| return ((x & 0xffu) << 24) | ((x & 0xff00u) << 8) | | ||
| ((x & 0xff0000u) >> 8) | ((x >> 24) & 0xffu); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are the Also, doesn’t Windows already provide such a function, like htonl? |
||
| } | ||
| inline uint32_t be32toh(uint32_t x) { return htobe32(x); } | ||
| #endif | ||
|
|
||
| using namespace std::literals; | ||
|
|
||
| namespace ot { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,8 +16,18 @@ | |
| #include <stdlib.h> | ||
| #include <string.h> | ||
| #include <sys/stat.h> | ||
| #include <sys/wait.h> | ||
| #include <unistd.h> | ||
| #include <cstdint> | ||
|
|
||
| #ifndef _WIN32 | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I find it curious to mix |
||
| #include <sys/wait.h> | ||
| #endif | ||
|
|
||
| #if defined(_WIN32) || defined(__MINGW32__) | ||
| #include <io.h> | ||
| #include <fcntl.h> | ||
| #include "win32_compat.h" | ||
| #endif | ||
|
|
||
| void ot::close_file(FILE* file) | ||
| { | ||
|
|
@@ -40,6 +50,14 @@ void ot::partial_file::open(const char* destination) | |
| strerror(errno)}; | ||
| } | ||
|
|
||
| #if defined(_WIN32) || defined(__MINGW32__) | ||
| // Windows does not use Unix-style file modes the same way. | ||
| // Just leave the default permissions created by the OS. | ||
| static void copy_permissions(const char* /*source*/, const char* /*dest*/) | ||
| { | ||
| // no-op on Windows | ||
| } | ||
| #else | ||
| static mode_t get_umask() | ||
| { | ||
| // libc doesn’t seem to provide a way to get umask without changing it, so we need this workaround. | ||
|
|
@@ -72,13 +90,22 @@ static void copy_permissions(const char* source, const char* dest) | |
| if (chmod(dest, target_mode) == -1) | ||
| fprintf(stderr, "warning: Could not set mode of %s: %s\n", dest, strerror(errno)); | ||
| } | ||
| #endif | ||
|
|
||
| void ot::partial_file::commit() | ||
| { | ||
| if (file == nullptr) | ||
| return; | ||
| file.reset(); | ||
| copy_permissions(final_name.c_str(), temporary_name.c_str()); | ||
| #if defined(_WIN32) || defined(__MINGW32__) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could put the call to copy_permissions in an |
||
| // Windows rename() refuses to overwrite an existing file | ||
| if (remove(final_name.c_str()) != 0 && errno != ENOENT) { | ||
| throw status {st::standard_error, | ||
| "Could not remove original file '" + final_name + "': " + | ||
| strerror(errno) + "."}; | ||
| } | ||
| #endif | ||
| if (rename(temporary_name.c_str(), final_name.c_str()) == -1) | ||
| throw status {st::standard_error, | ||
| "Could not move the result file '" + temporary_name + "' to '" + | ||
|
|
@@ -219,6 +246,10 @@ std::string ot::decode_utf8(std::u8string_view in) | |
|
|
||
| std::string ot::shell_escape(std::string_view word) | ||
| { | ||
| #ifdef _WIN32 | ||
| // Windows cmd.exe: double quotes protect spaces. | ||
| return "\"" + std::string(word) + "\""; | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If word contains a double quote, that’s an open door to serious shell injection vulnerabilities. |
||
| #else | ||
| std::string escaped_word; | ||
| // Pre-allocate the result, assuming most of the time enclosing it in single quotes is enough. | ||
| escaped_word.reserve(2 + word.size()); | ||
|
|
@@ -235,13 +266,27 @@ std::string ot::shell_escape(std::string_view word) | |
| escaped_word += '\''; | ||
|
|
||
| return escaped_word; | ||
| #endif | ||
| } | ||
|
|
||
| void ot::run_editor(std::string_view editor, std::string_view path) | ||
| { | ||
| #if defined(_WIN32) || defined(__MINGW32__) | ||
| std::string command = std::string(editor) + " " + shell_escape(path); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I use Neovim. I tested
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The issue with dropping the -- argument is that whenever the file name begins with a -, the editor would interpret is as an option. That could lead to unintended behavior. Maybe the surest way would be to make the path absolute. |
||
| #else | ||
| std::string command = std::string(editor) + " -- " + shell_escape(path); | ||
| #endif | ||
|
|
||
| int status = system(command.c_str()); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this is the only line in common between Windows and Unix, you might as well make on big |
||
|
|
||
| #if defined(_WIN32) || defined(__MINGW32__) | ||
| // On Windows, system() returns the exit code directly (or -1 on error) | ||
| if (status == -1) | ||
| throw ot::status {st::standard_error, "system() error: "s + strerror(errno)}; | ||
| else if (status != 0) | ||
| throw ot::status {st::child_process_failed, | ||
| "Child process exited with " + std::to_string(status)}; | ||
| #else | ||
| if (status == -1) | ||
| throw ot::status {st::standard_error, "waitpid error: "s + strerror(errno)}; | ||
| else if (!WIFEXITED(status)) | ||
|
|
@@ -250,6 +295,7 @@ void ot::run_editor(std::string_view editor, std::string_view path) | |
| else if (WEXITSTATUS(status) != 0) | ||
| throw ot::status {st::child_process_failed, | ||
| "Child process exited with " + std::to_string(WEXITSTATUS(status))}; | ||
| #endif | ||
| } | ||
|
|
||
| timespec ot::get_file_timestamp(const char* path) | ||
|
|
@@ -262,6 +308,9 @@ timespec ot::get_file_timestamp(const char* path) | |
| mtime = st.st_mtim; | ||
| #elif defined(HAVE_STAT_ST_MTIMESPEC) | ||
| mtime = st.st_mtimespec; | ||
| #elif defined(_WIN32) || defined(__MINGW32__) | ||
| mtime.tv_sec = st.st_mtime; | ||
| mtime.tv_nsec = 0; | ||
| #else | ||
| mtime.tv_sec = st.st_mtime; | ||
| mtime.tv_nsec = st.st_mtimensec; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| #pragma once | ||
| #ifdef _WIN32 | ||
|
|
||
| #include <io.h> | ||
| #include <fcntl.h> | ||
| #include <stdlib.h> | ||
| #include <errno.h> | ||
| #include <cstring> | ||
|
|
||
| #define strncasecmp _strnicmp | ||
|
|
||
| #ifndef getdelim | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is this for? Is it possible that getdelim is sometimes defined as a macro, sometimes not, but never defined as a function?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is so it uses the windows equivalent of strncasecmp, I'll work on all these changes and make it suitable. I have a busy week coming up and start work in 2 hours, I'l try to clean all this up and explain on Saturday. Sorry for the lack of explanation on these changes and having to wait until Saturday.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I misunderstood your question. I added the #ifndef getdelim because I wanted to prevent a duplicate definition if the platform already supplied getdelim(). I realize now that this only checks for a preprocessor macro, not whether the function is already provided.
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Exactly. If the _WIN32 macro check is not enough, we should rely on CMake because that’s precisely what it’s for. See CheckFunctionExists. |
||
| static inline ssize_t getdelim(char** lineptr, size_t* n, int delim, FILE* stream) { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function is huge for an inline function. You could move the definition into system.cc and add the declaration into opustags.h instead so that it is built and linked like a regular function. By the way, where does the implementation come from?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wrote the getdelim (and mkstemps) implementations myself to get the Windows build working. I looked at a few public versions for reference while writing it, but this is my own code. Happy to move both functions out of the header into system.cc (and just declare them in the header or in opustags.h) if you prefer that style. I can also clean them up further if you’d like.
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you. Since C++ is loaded with footguns so I’ll make sure to review it attentively, unless we find a battle-tested alternative. Maybe we could replace getdelim with std::getline, provided the code does not rely too much on stdio. Regarding mkstemps, Windows seems to have _mktemp that could do some of the work.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice, I will look into _mktemp, Windows seems very messy. I'll look into all this more thoroughly on Saturday including using getline instead of getdelim. My PR was pretty much out of excitement that I actually got it to compile and work on Windows. I've been wanting / needing opustags for Windows for a while. |
||
| if (lineptr == nullptr || n == nullptr || stream == nullptr) { | ||
| errno = EINVAL; | ||
| return -1; | ||
| } | ||
| size_t capacity = (*n > 0) ? *n : 128; | ||
| if (*lineptr == nullptr) { | ||
| *lineptr = static_cast<char*>(malloc(capacity)); | ||
| if (*lineptr == nullptr) return -1; | ||
| *n = capacity; | ||
| } | ||
| size_t pos = 0; | ||
| int c; | ||
| while ((c = fgetc(stream)) != EOF) { | ||
| if (pos + 1 >= capacity) { | ||
| capacity *= 2; | ||
| char* newbuf = static_cast<char*>(realloc(*lineptr, capacity)); | ||
| if (newbuf == nullptr) return -1; | ||
| *lineptr = newbuf; | ||
| *n = capacity; | ||
| } | ||
| (*lineptr)[pos++] = static_cast<char>(c); | ||
| if (c == delim) break; | ||
| } | ||
| if (pos == 0 && c == EOF) return -1; | ||
| (*lineptr)[pos] = '\0'; | ||
| return static_cast<ssize_t>(pos); | ||
| } | ||
| #endif | ||
|
|
||
| #ifndef mkstemps | ||
| static inline int mkstemps(char* tmpl, int suffixlen) { | ||
| size_t len = strlen(tmpl); | ||
| if (len < (size_t)(6 + suffixlen)) return -1; | ||
| char* xxxx = tmpl + len - 6 - suffixlen; | ||
| static const char chars[] = "abcdefghijklmnopqrstuvwxyz0123456789"; | ||
| for (int attempt = 0; attempt < 100; ++attempt) { | ||
| for (int i = 0; i < 6; ++i) | ||
| xxxx[i] = chars[rand() % (sizeof(chars) - 1)]; | ||
| int fd = _open(tmpl, _O_CREAT | _O_EXCL | _O_RDWR | _O_BINARY, _S_IREAD | _S_IWRITE); | ||
| if (fd != -1) return fd; | ||
| if (errno != EEXIST) return -1; | ||
| } | ||
| return -1; | ||
| } | ||
| #endif | ||
|
|
||
| #endif | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why check both
_WIN32and__MINGW32__. Isn’t _WIN32 enough?