diff --git a/src/base64.cc b/src/base64.cc index aeb0b6b..343e1a0 100644 --- a/src/base64.cc +++ b/src/base64.cc @@ -12,6 +12,7 @@ #include #include +#include static const char8_t base64_table[65] = u8"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; diff --git a/src/cli.cc b/src/cli.cc index 0635d30..9de6bc8 100644 --- a/src/cli.cc +++ b/src/cli.cc @@ -16,6 +16,13 @@ #include #include #include +#include + +#if defined(_WIN32) || defined(__MINGW32__) +#include +#include +#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::optionalc_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(); diff --git a/src/ogg.cc b/src/ogg.cc index 21bce59..d4b2020 100644 --- a/src/ogg.cc +++ b/src/ogg.cc @@ -12,6 +12,7 @@ #include #include +#include bool ot::is_opus_stream(const ogg_page& identification_header) { diff --git a/src/opus.cc b/src/opus.cc index 482b388..e9bdb14 100644 --- a/src/opus.cc +++ b/src/opus.cc @@ -25,6 +25,7 @@ #include #include +#include ot::opus_tags ot::parse_tags(const ogg_packet& packet) { diff --git a/src/opustags.h b/src/opustags.h index ec41596..25e5fb1 100644 --- a/src/opustags.h +++ b/src/opustags.h @@ -55,6 +55,17 @@ #define be32toh(x) OSSwapBigToHostInt32(x) #endif +#if defined(_WIN32) || defined(__MINGW32__) +#include +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); +} +inline uint32_t be32toh(uint32_t x) { return htobe32(x); } +#endif + using namespace std::literals; namespace ot { diff --git a/src/system.cc b/src/system.cc index 7bf1920..580d798 100644 --- a/src/system.cc +++ b/src/system.cc @@ -16,8 +16,18 @@ #include #include #include -#include #include +#include + +#ifndef _WIN32 +#include +#endif + +#if defined(_WIN32) || defined(__MINGW32__) +#include +#include +#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,6 +90,7 @@ 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() { @@ -79,6 +98,14 @@ void ot::partial_file::commit() return; file.reset(); copy_permissions(final_name.c_str(), temporary_name.c_str()); +#if defined(_WIN32) || defined(__MINGW32__) + // 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) + "\""; +#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); +#else std::string command = std::string(editor) + " -- " + shell_escape(path); +#endif + int status = system(command.c_str()); +#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; diff --git a/src/win32_compat.h b/src/win32_compat.h new file mode 100644 index 0000000..319a2cc --- /dev/null +++ b/src/win32_compat.h @@ -0,0 +1,60 @@ +#pragma once +#ifdef _WIN32 + +#include +#include +#include +#include +#include + +#define strncasecmp _strnicmp + +#ifndef getdelim +static inline ssize_t getdelim(char** lineptr, size_t* n, int delim, FILE* stream) { + if (lineptr == nullptr || n == nullptr || stream == nullptr) { + errno = EINVAL; + return -1; + } + size_t capacity = (*n > 0) ? *n : 128; + if (*lineptr == nullptr) { + *lineptr = static_cast(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(realloc(*lineptr, capacity)); + if (newbuf == nullptr) return -1; + *lineptr = newbuf; + *n = capacity; + } + (*lineptr)[pos++] = static_cast(c); + if (c == delim) break; + } + if (pos == 0 && c == EOF) return -1; + (*lineptr)[pos] = '\0'; + return static_cast(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