common: Windows Unicode CLI support

Windows CLI arguments use either ANSI (main()) or UTF-16 (wmain()).
Meanwhile, Ceph libraries expect UTF-8 and raise exceptions when
trying to use Unicode CLI arguments or log Unicode output:

  rbd.exe create test_unicode_șțăâ --size=32M
  terminate called after throwing an instance of 'std::runtime_error'
    what():  invalid utf8

We'll use a Windows application manifest, setting the "activeCodePage"
property [1][2]. This enables the Windows UCRT UTF-8 mode so that
functions that receive char* arguments will expect UTF-8 instead of ANSI,
including main(). One exception is CreateProcess, which will need the
UTF-16 form (CreateProcessW).

Despite the locale being set to utf-8, we'll have to explicitly set
the console output to utf-8 using SetConsoleOutputCP(CP_UTF8).

In order to use the UTF-8 locale, we'll have to switch the mingw-llvm
runtime from msvcrt to ucrt.

This also fixes ceph-dokan crashes that currently occur when non-ANSI
paths are logged.

[1] https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests#activecodepage
[2] https://learn.microsoft.com/en-us/windows/apps/design/globalizing/use-utf8-code-page

Signed-off-by: Lucian Petrut <lpetrut@cloudbasesolutions.com>
This commit is contained in:
Lucian Petrut 2023-08-18 12:25:52 +00:00
parent e877333f07
commit 750a9483d1
13 changed files with 47 additions and 14 deletions

View File

@ -129,8 +129,8 @@ EOL
if [[ -n $USE_MINGW_LLVM ]]; then
cat >> $MINGW_CMAKE_FILE <<EOL
add_definitions(-I$mingwX64IncludeDir)
add_definitions(-march=native)
add_definitions(-Wno-unknown-attributes)
add_compile_options(-march=native)
add_compile_options(-Wno-unknown-attributes)
EOL
fi
fi

View File

@ -18,6 +18,7 @@
#include "common/SubProcess.h"
#include "common/errno.h"
#include "common/win32/wstring.h"
#include "include/ceph_assert.h"
#include "include/compat.h"
@ -174,8 +175,9 @@ int SubProcess::spawn() {
for (auto& arg : cmd_args) {
cmdline << " " << std::quoted(arg);
}
std::wstring cmdline_w = to_wstring(cmdline.str());
STARTUPINFO si = {0};
STARTUPINFOW si = {0};
PROCESS_INFORMATION pi = {0};
SECURITY_ATTRIBUTES sa = {0};
@ -224,8 +226,8 @@ int SubProcess::spawn() {
// We've transfered ownership from those handles.
stdin_w = stdout_r = stderr_r = INVALID_HANDLE_VALUE;
if (!CreateProcess(
NULL, const_cast<char*>(cmdline.str().c_str()),
if (!CreateProcessW(
NULL, const_cast<wchar_t*>(cmdline_w.c_str()),
NULL, NULL, /* No special security attributes */
1, /* Inherit handles marked as inheritable */
0, /* No special flags */

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<application>
<windowsSettings>
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
</windowsSettings>
</application>
</assembly>

View File

@ -0,0 +1,2 @@
#include <winuser.h>
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "code_page.manifest"

View File

@ -2,7 +2,8 @@ set(ceph_dokan_srcs
ceph_dokan.cc
dbg.cc
utils.cc
options.cc)
options.cc
../common/win32/code_page.rc)
add_executable(ceph-dokan ${ceph_dokan_srcs})
target_link_libraries(ceph-dokan ${DOKAN_LIBRARIES}
${GSSAPI_LIBRARIES}

View File

@ -1043,6 +1043,8 @@ boost::intrusive_ptr<CephContext> do_global_init(
int main(int argc, const char** argv)
{
SetConsoleOutputCP(CP_UTF8);
if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)ConsoleHandler, TRUE)) {
cerr << "Couldn't initialize console event handler." << std::endl;
return -EINVAL;

View File

@ -6,6 +6,9 @@ set(rados_srcs
${PROJECT_SOURCE_DIR}/src/common/util.cc
${PROJECT_SOURCE_DIR}/src/common/obj_bencher.cc
${PROJECT_SOURCE_DIR}/src/osd/ECUtil.cc)
if(WIN32)
list(APPEND rados_srcs ../common/win32/code_page.rc)
endif()
add_executable(rados ${rados_srcs})
target_link_libraries(rados librados global ${BLKID_LIBRARIES} ${CMAKE_DL_LIBS})

View File

@ -4047,6 +4047,9 @@ static int rados_tool_common(const std::map < std::string, std::string > &opts,
int main(int argc, const char **argv)
{
#ifdef _WIN32
SetConsoleOutputCP(CP_UTF8);
#endif
auto args = argv_to_vec(argc, argv);
if (args.empty()) {
cerr << argv[0] << ": -h or --help for usage" << std::endl;

View File

@ -55,6 +55,9 @@ set(rbd_srcs
action/Ubbd.cc
action/Watch.cc
action/Wnbd.cc)
if(WIN32)
list(APPEND rbd_srcs ../../common/win32/code_page.rc)
endif()
add_executable(rbd ${rbd_srcs}
$<TARGET_OBJECTS:common_texttable_obj>)

View File

@ -5,6 +5,9 @@
int main(int argc, const char **argv)
{
#ifdef _WIN32
SetConsoleOutputCP(CP_UTF8);
#endif
rbd::Shell shell;
return shell.execute(argc, argv);
}

View File

@ -1,4 +1,7 @@
add_executable(rbd-wnbd rbd_wnbd.cc wnbd_handler.cc wnbd_wmi.cc)
add_executable(
rbd-wnbd
rbd_wnbd.cc wnbd_handler.cc wnbd_wmi.cc
../../common/win32/code_page.rc)
set_target_properties(
rbd-wnbd PROPERTIES COMPILE_FLAGS
"-fpermissive -I${WNBD_INCLUDE_DIRS}")

View File

@ -331,7 +331,7 @@ int send_map_request(std::string arguments) {
// which will allow it to communicate the mapping status
int map_device_using_suprocess(std::string arguments, int timeout_ms)
{
STARTUPINFO si;
STARTUPINFOW si;
PROCESS_INFORMATION pi;
char ch;
DWORD err = 0, status = 0;
@ -407,11 +407,12 @@ int map_device_using_suprocess(std::string arguments, int timeout_ms)
dout(5) << __func__ << ": command line: " << command_line.str() << dendl;
GetStartupInfo(&si);
GetStartupInfoW(&si);
// Create a detached child
if (!CreateProcess(NULL, (char*)command_line.str().c_str(),
NULL, NULL, FALSE, DETACHED_PROCESS,
NULL, NULL, &si, &pi)) {
if (!CreateProcessW(
NULL, const_cast<wchar_t*>(to_wstring(command_line.str()).c_str()),
NULL, NULL, FALSE, DETACHED_PROCESS,
NULL, NULL, &si, &pi)) {
err = GetLastError();
derr << "CreateProcess failed: " << win32_strerror(err) << dendl;
exit_code = -ECHILD;
@ -1904,6 +1905,8 @@ int main(int argc, const char *argv[])
SetConsoleCtrlHandler(console_handler_routine, true);
// Avoid the Windows Error Reporting dialog.
SetErrorMode(GetErrorMode() | SEM_NOGPFAULTERRORBOX);
SetConsoleOutputCP(CP_UTF8);
int r = rbd_wnbd(argc, argv);
if (r < 0) {
return r;

View File

@ -40,8 +40,8 @@ dokanTag="v2.0.5.1000"
dokanSrcDir="${depsSrcDir}/dokany"
dokanLibDir="${depsToolsetDir}/dokany/lib"
mingwLlvmUrl="https://github.com/mstorsjo/llvm-mingw/releases/download/20230320/llvm-mingw-20230320-msvcrt-ubuntu-18.04-x86_64.tar.xz"
mingwLlvmSha256Sum="bc97745e702fb9e8f2a16f7d09dd5061ceeef16554dd12e542f619ce937e8d7a"
mingwLlvmUrl="https://github.com/mstorsjo/llvm-mingw/releases/download/20230320/llvm-mingw-20230320-ucrt-ubuntu-18.04-x86_64.tar.xz"
mingwLlvmSha256Sum="bc367753dea829d219be32e2e64e2d15d03158ce8e700ae5210ca3d78e6a07ea"
mingwLlvmDir="${DEPS_DIR}/mingw-llvm"
function _make() {