diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 498b2662868af..1297df32405aa 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -173,6 +173,7 @@ elseif(AIX) list(APPEND common_srcs aix_errno.cc) elseif(WIN32) list(APPEND common_srcs win32/errno.cc) + list(APPEND common_srcs win32/wstring.cc) endif() if(WITH_EVENTTRACE) diff --git a/src/common/win32/service.cc b/src/common/win32/service.cc index 846c5d09dfaa8..7cf7620bf87bc 100644 --- a/src/common/win32/service.cc +++ b/src/common/win32/service.cc @@ -69,14 +69,14 @@ void WINAPI ServiceBase::run() s_service->set_status(SERVICE_START_PENDING); // TODO: should we expect exceptions? - ldout(s_service->cct, 5) << "Starting service." << dendl; + ldout(s_service->cct, 0) << "Starting service." << dendl; int err = s_service->run_hook(); if (err) { lderr(s_service->cct) << "Failed to start service. Error code: " << err << dendl; s_service->shutdown(true); } else { - ldout(s_service->cct, 5) << "Successfully started service." << dendl; + ldout(s_service->cct, 0) << "Successfully started service." << dendl; s_service->set_status(SERVICE_RUNNING); } } @@ -98,7 +98,7 @@ void ServiceBase::shutdown(bool ignore_errors) set_status(original_state); } } else { - dout(5) << "Shutdown hook completed." << dendl; + dout(0) << "Shutdown hook completed." << dendl; set_status(SERVICE_STOPPED); } } @@ -113,7 +113,7 @@ void ServiceBase::stop() derr << "Service stop hook failed. Error code: " << err << dendl; set_status(original_state); } else { - dout(5) << "Successfully stopped service." << dendl; + dout(0) << "Successfully stopped service." << dendl; set_status(SERVICE_STOPPED); } } diff --git a/src/common/win32/wstring.cc b/src/common/win32/wstring.cc new file mode 100644 index 0000000000000..1f9b49a58f7b8 --- /dev/null +++ b/src/common/win32/wstring.cc @@ -0,0 +1,27 @@ +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2022 Cloudbase Solutions + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "wstring.h" + +#include + +using boost::locale::conv::utf_to_utf; + +std::wstring to_wstring(const std::string& str) +{ + return utf_to_utf(str.c_str(), str.c_str() + str.size()); +} + +std::string to_string(const std::wstring& str) +{ + return utf_to_utf(str.c_str(), str.c_str() + str.size()); +} diff --git a/src/common/win32/wstring.h b/src/common/win32/wstring.h new file mode 100644 index 0000000000000..cb308c70dbbb6 --- /dev/null +++ b/src/common/win32/wstring.h @@ -0,0 +1,18 @@ +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2022 Cloudbase Solutions + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#pragma once + +#include + +std::wstring to_wstring(const std::string& str); +std::string to_string(const std::wstring& wstr); diff --git a/src/dokan/ceph_dokan.cc b/src/dokan/ceph_dokan.cc index 4738d096ac4ba..9e115222cab2f 100644 --- a/src/dokan/ceph_dokan.cc +++ b/src/dokan/ceph_dokan.cc @@ -35,6 +35,7 @@ #include "common/dout.h" #include "common/errno.h" #include "common/version.h" +#include "common/win32/wstring.h" #include "global/global_init.h" diff --git a/src/dokan/options.cc b/src/dokan/options.cc index 4cfe08cdbe2ca..4c968e13af5d6 100644 --- a/src/dokan/options.cc +++ b/src/dokan/options.cc @@ -17,6 +17,7 @@ #include "common/ceph_argparse.h" #include "common/config.h" +#include "common/win32/wstring.h" #include "global/global_init.h" diff --git a/src/dokan/utils.cc b/src/dokan/utils.cc index 4705359c4d032..576cceb9916e4 100644 --- a/src/dokan/utils.cc +++ b/src/dokan/utils.cc @@ -12,20 +12,6 @@ #include "utils.h" -#include - -using boost::locale::conv::utf_to_utf; - -std::wstring to_wstring(const std::string& str) -{ - return utf_to_utf(str.c_str(), str.c_str() + str.size()); -} - -std::string to_string(const std::wstring& str) -{ - return utf_to_utf(str.c_str(), str.c_str() + str.size()); -} - void to_filetime(time_t t, LPFILETIME pft) { // Note that LONGLONG is a 64-bit value diff --git a/src/dokan/utils.h b/src/dokan/utils.h index aa52b4d3a3c7b..0fb27818bf862 100644 --- a/src/dokan/utils.h +++ b/src/dokan/utils.h @@ -10,10 +10,9 @@ * */ -#include "include/compat.h" +#pragma once -std::wstring to_wstring(const std::string& str); -std::string to_string(const std::wstring& str); +#include "include/compat.h" void to_filetime(time_t t, LPFILETIME pft); void to_unix_time(FILETIME ft, time_t *t); diff --git a/src/include/win32/fs_compat.h b/src/include/win32/fs_compat.h index 2fce1b72e856c..318c8fab75681 100644 --- a/src/include/win32/fs_compat.h +++ b/src/include/win32/fs_compat.h @@ -13,6 +13,8 @@ // Those definitions allow handling information coming from Ceph and should // not be passed to Windows functions. +#pragma once + #define S_IFLNK 0120000 #define S_ISTYPE(m, TYPE) ((m & S_IFMT) == TYPE) diff --git a/src/tools/rbd_wnbd/CMakeLists.txt b/src/tools/rbd_wnbd/CMakeLists.txt index 38f4639612984..86c41b2eeb6f1 100644 --- a/src/tools/rbd_wnbd/CMakeLists.txt +++ b/src/tools/rbd_wnbd/CMakeLists.txt @@ -1,9 +1,10 @@ -add_executable(rbd-wnbd wnbd_handler.cc rbd_wnbd.cc) +add_executable(rbd-wnbd rbd_wnbd.cc wnbd_handler.cc wnbd_wmi.cc) set_target_properties( rbd-wnbd PROPERTIES COMPILE_FLAGS "-fpermissive -I${WNBD_INCLUDE_DIRS}") target_link_libraries( rbd-wnbd setupapi rpcrt4 + wbemuuid oleaut32 ${WNBD_LIBRARIES} ${Boost_FILESYSTEM_LIBRARY} librbd librados global) diff --git a/src/tools/rbd_wnbd/rbd_wnbd.cc b/src/tools/rbd_wnbd/rbd_wnbd.cc index a2a772c940302..2e8f3ee0434bc 100644 --- a/src/tools/rbd_wnbd/rbd_wnbd.cc +++ b/src/tools/rbd_wnbd/rbd_wnbd.cc @@ -10,8 +10,14 @@ * */ +#include +// LOCK_WRITE is also defined by objidl.h, we have to avoid +// a collision. +#undef LOCK_WRITE + #include "include/int_types.h" +#include #include #include #include @@ -20,9 +26,8 @@ #include #include -#include - #include "wnbd_handler.h" +#include "wnbd_wmi.h" #include "rbd_wnbd.h" #include @@ -38,6 +43,7 @@ #include "common/errno.h" #include "common/version.h" #include "common/win32/service.h" +#include "common/win32/wstring.h" #include "common/admin_socket_client.h" #include "global/global_init.h" @@ -54,17 +60,17 @@ #define dout_prefix *_dout << "rbd-wnbd: " using namespace std; -using boost::locale::conv::utf_to_utf; -std::wstring to_wstring(const std::string& str) -{ - return utf_to_utf(str.c_str(), str.c_str() + str.size()); -} - -std::string to_string(const std::wstring& str) -{ - return utf_to_utf(str.c_str(), str.c_str() + str.size()); -} +// Wait 2s before recreating the wmi subscription in case of errors +#define WMI_SUBSCRIPTION_RETRY_INTERVAL 2 +// SCSI adapter modification events aren't received until the entire polling +// interval has elapsed (unlike other WMI classes, such as Msvm_ComputerSystem). +// With longer intervals, it even seems to miss events. For this reason, +// we're using a relatively short interval but have adapter state monitoring +// as an optional feature, mainly used for dev / driver certification purposes. +#define WNBD_ADAPTER_WMI_POLL_INTERVAL 2 +// Wait for wmi events up to two seconds +#define WMI_EVENT_TIMEOUT 2 bool is_process_running(DWORD pid) { @@ -118,8 +124,16 @@ DWORD WNBDActiveDiskIterator::fetch_list( WNBDActiveDiskIterator::WNBDActiveDiskIterator() { DWORD status = WNBDActiveDiskIterator::fetch_list(&conn_list); - if (status) { - error = EINVAL; + switch (status) { + case 0: + // no error + break; + case ERROR_OPEN_FAILED: + error = -ENOENT; + break; + default: + error = -EINVAL; + break; } } @@ -171,14 +185,14 @@ RegistryDiskIterator::RegistryDiskIterator() SERVICE_REG_KEY, false); if (!reg_key->hKey) { if (!reg_key->missingKey) - error = EINVAL; + error = -EINVAL; return; } if (RegQueryInfoKey(reg_key->hKey, NULL, NULL, NULL, &subkey_count, NULL, NULL, NULL, NULL, NULL, NULL, NULL)) { derr << "Could not query registry key: " << SERVICE_REG_KEY << dendl; - error = EINVAL; + error = -EINVAL; return; } } @@ -201,12 +215,12 @@ bool RegistryDiskIterator::get(Config *cfg) return false; } else if (err) { derr << "Could not enumerate registry. Error: " << err << dendl; - error = EINVAL; + error = -EINVAL; return false; } if (load_mapping_config_from_registry(subkey_name, cfg)) { - error = EINVAL; + error = -EINVAL; return false; }; @@ -469,7 +483,7 @@ int map_device_using_suprocess(std::string arguments, int timeout_ms) } else { // The process closed the pipe without notifying us or exiting. // This is quite unlikely, but we'll terminate the process. - dout(5) << "Terminating unresponsive process." << dendl; + dout(0) << "Terminating unresponsive process." << dendl; TerminateProcess(pi.hProcess, 1); exit_code = -EINVAL; } @@ -488,7 +502,7 @@ int map_device_using_suprocess(std::string arguments, int timeout_ms) BOOL WINAPI console_handler_routine(DWORD dwCtrlType) { - dout(5) << "Received control signal: " << dwCtrlType + dout(0) << "Received control signal: " << dwCtrlType << ". Exiting." << dendl; std::unique_lock l{shutdown_lock}; @@ -571,7 +585,10 @@ int restart_registered_mappings( { Config cfg; WNBDDiskIterator iterator; - int err = 0, r; + int r; + std::atomic err = 0; + + dout(0) << "remounting persistent disks" << dendl; int total_timeout_ms = max(total_timeout, total_timeout * 1000); int image_map_timeout_ms = max(image_map_timeout, image_map_timeout * 1000); @@ -589,12 +606,12 @@ int restart_registered_mappings( continue; } if (cfg.wnbd_mapped) { - dout(5) << __func__ << ": device already mapped: " + dout(1) << __func__ << ": device already mapped: " << cfg.devpath << dendl; continue; } if (!cfg.persistent) { - dout(5) << __func__ << ": cleaning up non-persistent mapping: " + dout(1) << __func__ << ": cleaning up non-persistent mapping: " << cfg.devpath << dendl; r = remove_config_from_registry(&cfg); if (r) { @@ -605,7 +622,8 @@ int restart_registered_mappings( } boost::asio::post(pool, - [&, cfg]() mutable + [cfg, start_t, counter_freq, total_timeout_ms, + image_map_timeout_ms, &err]() { LARGE_INTEGER curr_t, elapsed_ms; QueryPerformanceCounter(&curr_t); @@ -622,18 +640,18 @@ int restart_registered_mappings( return; } - dout(5) << "Remapping: " << cfg.devpath + dout(1) << "Remapping: " << cfg.devpath << ". Timeout: " << time_left_ms << " ms." << dendl; // We'll try to map all devices and return a non-zero value // if any of them fails. - r = map_device_using_suprocess(cfg.command_line, time_left_ms); + int r = map_device_using_suprocess(cfg.command_line, time_left_ms); if (r) { err = r; derr << "Could not create mapping: " << cfg.devpath << ". Error: " << r << dendl; } else { - dout(5) << "Successfully remapped: " << cfg.devpath << dendl; + dout(1) << "Successfully remapped: " << cfg.devpath << dendl; } }); } @@ -662,7 +680,8 @@ int disconnect_all_mappings( Config cfg; WNBDActiveDiskIterator iterator; - int err = 0, r; + int r; + std::atomic err = 0; boost::asio::thread_pool pool(worker_count); LARGE_INTEGER start_t, counter_freq; @@ -670,7 +689,8 @@ int disconnect_all_mappings( QueryPerformanceCounter(&start_t); while (iterator.get(&cfg)) { boost::asio::post(pool, - [&, cfg]() mutable + [cfg, start_t, counter_freq, timeout_ms, + hard_disconnect, unregister, &err]() mutable { LARGE_INTEGER curr_t, elapsed_ms; QueryPerformanceCounter(&curr_t); @@ -684,25 +704,29 @@ int disconnect_all_mappings( cfg.hard_disconnect_fallback = true; cfg.soft_disconnect_timeout = time_left_ms / 1000; - dout(5) << "Removing mapping: " << cfg.devpath + dout(1) << "Removing mapping: " << cfg.devpath << ". Timeout: " << cfg.soft_disconnect_timeout << "s. Hard disconnect: " << cfg.hard_disconnect << dendl; - r = do_unmap(&cfg, unregister); + int r = do_unmap(&cfg, unregister); if (r) { err = r; derr << "Could not remove mapping: " << cfg.devpath << ". Error: " << r << dendl; } else { - dout(5) << "Successfully removed mapping: " << cfg.devpath << dendl; + dout(1) << "Successfully removed mapping: " << cfg.devpath << dendl; } }); } pool.join(); r = iterator.get_error(); - if (r) { + if (r == -ENOENT) { + dout(0) << __func__ << ": wnbd adapter unavailable, " + << "assuming that no wnbd mappings exist." << dendl; + err = 0; + } else if (r) { derr << "Could not fetch all mappings. Error: " << r << dendl; err = r; } @@ -718,6 +742,14 @@ class RBDService : public ServiceBase { int service_start_timeout; int image_map_timeout; bool remap_failure_fatal; + bool adapter_monitoring_enabled; + + std::thread adapter_monitor_thread; + + ceph::mutex start_lock = ceph::make_mutex("RBDService::StartLocker"); + ceph::mutex shutdown_lock = ceph::make_mutex("RBDService::ShutdownLocker"); + bool started = false; + std::atomic stop_requsted = false; public: RBDService(bool _hard_disconnect, @@ -725,7 +757,8 @@ class RBDService : public ServiceBase { int _thread_count, int _service_start_timeout, int _image_map_timeout, - bool _remap_failure_fatal) + bool _remap_failure_fatal, + bool _adapter_monitoring_enabled) : ServiceBase(g_ceph_context) , hard_disconnect(_hard_disconnect) , soft_disconnect_timeout(_soft_disconnect_timeout) @@ -733,6 +766,7 @@ class RBDService : public ServiceBase { , service_start_timeout(_service_start_timeout) , image_map_timeout(_image_map_timeout) , remap_failure_fatal(_remap_failure_fatal) + , adapter_monitoring_enabled(_adapter_monitoring_enabled) { } @@ -740,14 +774,14 @@ class RBDService : public ServiceBase { { switch(request->command) { case Connect: - dout(5) << "Received device connect request. Command line: " + dout(1) << "Received device connect request. Command line: " << (char*)request->arguments << dendl; // TODO: use the configured service map timeout. // TODO: add ceph.conf options. return map_device_using_suprocess( (char*)request->arguments, DEFAULT_MAP_TIMEOUT_MS); default: - dout(5) << "Received unsupported command: " + dout(1) << "Received unsupported command: " << request->command << dendl; return -ENOSYS; } @@ -875,7 +909,79 @@ class RBDService : public ServiceBase { return err; } + void monitor_wnbd_adapter() + { + dout(5) << __func__ << ": initializing COM" << dendl; + // Initialize the Windows COM library for this thread. + COMBootstrapper com_bootstrapper; + HRESULT hres = com_bootstrapper.initialize(); + if (FAILED(hres)) { + return; + } + + WmiSubscription subscription = subscribe_wnbd_adapter_events( + WNBD_ADAPTER_WMI_POLL_INTERVAL); + dout(5) << __func__ << ": initializing wmi subscription" << dendl; + hres = subscription.initialize(); + + dout(0) << "monitoring wnbd adapter state changes" << dendl; + // The event watcher will wait at most WMI_EVENT_TIMEOUT (2s) + // and exit the loop if the service is being stopped. + while (!stop_requsted) { + IWbemClassObject* object; + ULONG returned = 0; + + if (FAILED(hres)) { + derr << "couldn't retrieve wnbd adapter events, wmi hresult: " + << hres << ". Reestablishing wmi listener in " + << WMI_SUBSCRIPTION_RETRY_INTERVAL << " seconds." << dendl; + subscription.close(); + Sleep(WMI_SUBSCRIPTION_RETRY_INTERVAL * 1000); + + dout(20) << "recreating wnbd adapter wmi subscription" << dendl; + subscription = subscribe_wnbd_adapter_events( + WNBD_ADAPTER_WMI_POLL_INTERVAL); + hres = subscription.initialize(); + continue; + } + + dout(20) << "fetching wnbd adapter events" << dendl; + hres = subscription.next( + WMI_EVENT_TIMEOUT * 1000, + 1, // we'll process one event at a time + &object, + &returned); + + if (!FAILED(hres) && returned) { + if (WBEM_S_NO_ERROR == object->InheritsFrom(L"__InstanceCreationEvent")) { + dout(0) << "wnbd adapter (re)created, remounting disks" << dendl; + restart_registered_mappings( + thread_count, service_start_timeout, image_map_timeout); + } else if (WBEM_S_NO_ERROR == object->InheritsFrom(L"__InstanceDeletionEvent")) { + dout(0) << "wnbd adapter removed" << dendl; + // nothing to do here + } else if (WBEM_S_NO_ERROR == object->InheritsFrom(L"__InstanceModificationEvent")) { + dout(0) << "wnbd adapter changed" << dendl; + // TODO: look for state changes and log the availability/status + } + + object->Release(); + } + } + + dout(10) << "service stop requested, wnbd event monitor exited" << dendl; + } + int run_hook() override { + std::unique_lock l{start_lock}; + if (started) { + // The run hook is only supposed to be called once per process, + // however we're staying cautious. + derr << "Service already running." << dendl; + return -EALREADY; + } + + started = true; // Restart registered mappings before accepting new ones. int r = restart_registered_mappings( thread_count, service_start_timeout, image_map_timeout); @@ -888,14 +994,33 @@ class RBDService : public ServiceBase { } } + if (adapter_monitoring_enabled) { + adapter_monitor_thread = std::thread(&monitor_wnbd_adapter, this); + } else { + dout(0) << "WNBD adapter monitoring disabled." << dendl; + } + return create_pipe_server(); } // Invoked when the service is requested to stop. int stop_hook() override { - return disconnect_all_mappings( + std::unique_lock l{shutdown_lock}; + + stop_requsted = true; + + int r = disconnect_all_mappings( false, hard_disconnect, soft_disconnect_timeout, thread_count); + + if (adapter_monitor_thread.joinable()) { + dout(10) << "waiting for wnbd event monitor thread" << dendl; + adapter_monitor_thread.join(); + dout(10) << "wnbd event monitor stopped" << dendl; + } + + return r; } + // Invoked when the system is shutting down. int shutdown_hook() override { return stop_hook(); @@ -966,17 +1091,21 @@ Unmap options: unflushed caches or open handles. Default: 15 Service options: - --hard-disconnect Skip attempting a soft disconnect - --soft-disconnect-timeout Cummulative soft disconnect timeout in seconds, - used when disconnecting existing mappings. A hard - disconnect will be issued when hitting the timeout - --service-thread-count The number of workers used when mapping or - unmapping images. Default: 8 - --start-timeout The service start timeout in seconds. Default: 120 - --map-timeout Individual image map timeout in seconds. Default: 20 - --remap-failure-fatal If set, the service will stop when failing to remap - an image at start time, unmapping images that have - been mapped so far. + --hard-disconnect Skip attempting a soft disconnect + --soft-disconnect-timeout Cummulative soft disconnect timeout in seconds, + used when disconnecting existing mappings. A hard + disconnect will be issued when hitting the timeout + --service-thread-count The number of workers used when mapping or + unmapping images. Default: 8 + --start-timeout The service start timeout in seconds. Default: 120 + --map-timeout Individual image map timeout in seconds. Default: 20 + --remap-failure-fatal If set, the service will stop when failing to remap + an image at start time, unmapping images that have + been mapped so far. + --adapter-monitoring-enabled If set, the service will monitor WNBD adapter WMI + events and remount the images when the adapter gets + recreated. Mainly used for development and driver + certification purposes. Show|List options: --format plain|json|xml Output format (default: plain) @@ -1342,7 +1471,7 @@ static int do_list_mapped_devices(const std::string &format, bool pretty_format) int error = wnbd_disk_iterator.get_error(); if (error) { derr << "Could not get disk list: " << error << dendl; - return -error; + return error; } if (f) { @@ -1436,11 +1565,11 @@ static int do_stats(std::string search_devpath) } int error = wnbd_disk_iterator.get_error(); if (!error) { - error = ENOENT; + error = -ENOENT; } derr << "Could not find the specified disk." << dendl; - return -error; + return error; } static int parse_args(std::vector& args, @@ -1490,6 +1619,8 @@ static int parse_args(std::vector& args, cfg->pretty_format = true; } else if (ceph_argparse_flag(args, i, "--remap-failure-fatal", (char *)NULL)) { cfg->remap_failure_fatal = true; + } else if (ceph_argparse_flag(args, i, "--adapter-monitoring-enabled", (char *)NULL)) { + cfg->adapter_monitoring_enabled = true; } else if (ceph_argparse_witharg(args, i, &cfg->parent_pipe, err, "--pipe-name", (char *)NULL)) { if (!err.str().empty()) { @@ -1703,7 +1834,8 @@ static int rbd_wnbd(int argc, const char *argv[]) cfg.service_thread_count, cfg.service_start_timeout, cfg.image_map_timeout, - cfg.remap_failure_fatal); + cfg.remap_failure_fatal, + cfg.adapter_monitoring_enabled); // This call will block until the service stops. r = RBDService::initialize(&service); if (r < 0) diff --git a/src/tools/rbd_wnbd/rbd_wnbd.h b/src/tools/rbd_wnbd/rbd_wnbd.h index d17eb792b0ac0..ac298e3180f1d 100644 --- a/src/tools/rbd_wnbd/rbd_wnbd.h +++ b/src/tools/rbd_wnbd/rbd_wnbd.h @@ -67,6 +67,7 @@ struct Config { int service_start_timeout = DEFAULT_SERVICE_START_TIMEOUT; int image_map_timeout = DEFAULT_IMAGE_MAP_TIMEOUT; bool remap_failure_fatal = false; + bool adapter_monitoring_enabled = false; // TODO: consider moving those fields to a separate structure. Those // provide connection information without actually being configurable. diff --git a/src/tools/rbd_wnbd/wnbd_wmi.cc b/src/tools/rbd_wnbd/wnbd_wmi.cc new file mode 100644 index 0000000000000..f49fa4cc60e30 --- /dev/null +++ b/src/tools/rbd_wnbd/wnbd_wmi.cc @@ -0,0 +1,261 @@ +/* + * Ceph - scalable distributed file system + * + * Copyright (c) 2019 SUSE LLC + * Copyright (C) 2022 Cloudbase Solutions + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "wnbd_wmi.h" + +#include "common/debug.h" +#include "common/win32/wstring.h" + +#define dout_context g_ceph_context +#define dout_subsys ceph_subsys_rbd +#undef dout_prefix +#define dout_prefix *_dout << "rbd-wnbd: " + +// Initializes the COM library for use by the calling thread using +// COINIT_MULTITHREADED. +static HRESULT co_initialize_basic() +{ + dout(10) << "initializing COM library" << dendl; + + HRESULT hres = CoInitializeEx(0, COINIT_MULTITHREADED); + if (FAILED(hres)) { + derr << "CoInitializeEx failed. HRESULT: " << hres << dendl; + return hres; + } + + // CoInitializeSecurity must be called once per process. + static bool com_security_flags_set = false; + + if (!com_security_flags_set) { + hres = CoInitializeSecurity( + NULL, -1, NULL, NULL, + RPC_C_AUTHN_LEVEL_DEFAULT, + RPC_C_IMP_LEVEL_IMPERSONATE, + NULL, + EOAC_NONE, + NULL); + if (FAILED(hres)) { + derr << "CoInitializeSecurity failed. HRESULT: " << hres << dendl; + CoUninitialize(); + return hres; + } + + com_security_flags_set = true; + } + + return 0; +} + +// co_uninitialize must be called once for every successful +// co_initialize_basic call. Any WMI objects (including connections, +// event subscriptions, etc) must be released beforehand. +static void co_uninitialize() +{ + dout(10) << "closing COM library" << dendl; + CoUninitialize(); +} + +HRESULT COMBootstrapper::initialize() +{ + std::unique_lock l{init_lock}; + + HRESULT hres = co_initialize_basic(); + if (!FAILED(hres)) { + initialized = true; + } + return hres; +} + +void COMBootstrapper::cleanup() +{ + if (initialized) { + co_uninitialize(); + initialized = false; + } +} + +void WmiConnection::close() +{ + dout(20) << "closing wmi conn: " << this + << ", svc: " << wbem_svc + << ", loc: " << wbem_loc << dendl; + if (wbem_svc != NULL) { + wbem_svc->Release(); + wbem_svc = NULL; + } + if (wbem_loc != NULL) { + wbem_loc->Release(); + wbem_loc = NULL; + } +} + +HRESULT WmiConnection::initialize() +{ + HRESULT hres = CoCreateInstance( + CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, + IID_IWbemLocator, (LPVOID*)&wbem_loc); + if (FAILED(hres)) { + derr << "CoCreateInstance failed. HRESULT: " << hres << dendl; + return hres; + } + + hres = wbem_loc->ConnectServer( + _bstr_t(ns.c_str()).GetBSTR(), NULL, NULL, NULL, + WBEM_FLAG_CONNECT_USE_MAX_WAIT, NULL, NULL, + &wbem_svc); + if (FAILED(hres)) { + derr << "Could not connect to WMI service. HRESULT: " << hres << dendl; + return hres; + } + + if (!wbem_svc) { + hres = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, + ERROR_INVALID_HANDLE); + derr << "WMI connection failed, no WMI service object received." << dendl; + return hres; + } + + hres = CoSetProxyBlanket( + wbem_svc, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, + RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE); + if (FAILED(hres)) { + derr << "CoSetProxyBlanket failed. HRESULT:" << hres << dendl; + } + + return hres; +} + +HRESULT get_property_str( + IWbemClassObject* cls_obj, + const std::wstring& property, + std::wstring& value) +{ + VARIANT vt_prop; + VariantInit(&vt_prop); + HRESULT hres = cls_obj->Get(property.c_str(), 0, &vt_prop, 0, 0); + if (!FAILED(hres)) { + VARIANT vt_bstr_prop; + VariantInit(&vt_bstr_prop); + hres = VariantChangeType(&vt_bstr_prop, &vt_prop, 0, VT_BSTR); + if (!FAILED(hres)) { + value = vt_bstr_prop.bstrVal; + } + VariantClear(&vt_bstr_prop); + } + VariantClear(&vt_prop); + + if (FAILED(hres)) { + derr << "Could not get WMI property: " << to_string(property) + << ". HRESULT: " << hres << dendl; + } + return hres; +} + +HRESULT get_property_int( + IWbemClassObject* cls_obj, + const std::wstring& property, + uint32_t& value) +{ + VARIANT vt_prop; + VariantInit(&vt_prop); + HRESULT hres = cls_obj->Get(property.c_str(), 0, &vt_prop, 0, 0); + if (!FAILED(hres)) { + VARIANT vt_uint_prop; + VariantInit(&vt_uint_prop); + hres = VariantChangeType(&vt_uint_prop, &vt_prop, 0, VT_UINT); + if (!FAILED(hres)) { + value = vt_uint_prop.intVal; + } + VariantClear(&vt_uint_prop); + } + VariantClear(&vt_prop); + + if (FAILED(hres)) { + derr << "Could not get WMI property: " << to_string(property) + << ". HRESULT: " << hres << dendl; + } + return hres; +} + +HRESULT WmiSubscription::initialize() +{ + HRESULT hres = conn.initialize(); + if (FAILED(hres)) { + derr << "Could not create WMI connection" << dendl; + return hres; + } + + hres = conn.wbem_svc->ExecNotificationQuery( + _bstr_t(L"WQL").GetBSTR(), + _bstr_t(query.c_str()).GetBSTR(), + WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, + NULL, + &event_enum); + + if (FAILED(hres)) { + derr << "Notification query failed, unable to subscribe to " + << "WMI events. HRESULT: " << hres << dendl; + } else { + dout(20) << "wmi subscription initialized: " << this + << ", event enum: " << event_enum + << ", conn: " << &conn << ", conn svc: " << conn.wbem_svc << dendl; + } + + return hres; +} + +void WmiSubscription::close() +{ + dout(20) << "closing wmi subscription: " << this + << ", event enum: " << event_enum << dendl; + if (event_enum != NULL) { + event_enum->Release(); + event_enum = NULL; + } +} + +HRESULT WmiSubscription::next( + long timeout, + ULONG count, + IWbemClassObject **objects, + ULONG *returned) +{ + if (!event_enum) { + HRESULT hres = MAKE_HRESULT( + SEVERITY_ERROR, FACILITY_WIN32, + ERROR_INVALID_HANDLE); + derr << "WMI subscription uninitialized." << dendl; + return hres; + } + + HRESULT hres = event_enum->Next(timeout, count, objects, returned); + if (FAILED(hres)) { + derr << "Unable to retrieve WMI events. HRESULT: " + << hres << dendl; + } + return hres; +} + +WmiSubscription subscribe_wnbd_adapter_events( + uint32_t interval) +{ + std::wostringstream query_stream; + query_stream + << L"SELECT * FROM __InstanceOperationEvent " + << L"WITHIN " << interval + << L"WHERE TargetInstance ISA 'Win32_ScsiController' " + << L"AND TargetInstance.Description=" + << L"'WNBD SCSI Virtual Adapter'"; + + return WmiSubscription(L"root\\cimv2", query_stream.str()); +} diff --git a/src/tools/rbd_wnbd/wnbd_wmi.h b/src/tools/rbd_wnbd/wnbd_wmi.h new file mode 100644 index 0000000000000..4d802d986035a --- /dev/null +++ b/src/tools/rbd_wnbd/wnbd_wmi.h @@ -0,0 +1,109 @@ +/* + * Ceph - scalable distributed file system + * + * Copyright (c) 2019 SUSE LLC + * Copyright (C) 2022 Cloudbase Solutions + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#pragma once +#include + +#define _WIN32_DCOM +#include + +#include +#include + +#include "common/ceph_mutex.h" + +// Convenience helper for initializing and cleaning up the +// Windows COM library using "COINIT_MULTITHREADED" concurrency mode. +// Any WMI objects (including connections, event subscriptions, etc) +// must be released before the COM library gets closed. +class COMBootstrapper +{ +private: + bool initialized = false; + + ceph::mutex init_lock = ceph::make_mutex("COMBootstrapper::InitLocker"); + +public: + HRESULT initialize(); + void cleanup(); + + ~COMBootstrapper() + { + cleanup(); + } +}; + +class WmiConnection +{ +private: + std::wstring ns; +public: + IWbemLocator* wbem_loc; + IWbemServices* wbem_svc; + + WmiConnection(std::wstring ns) + : ns(ns) + , wbem_loc(nullptr) + , wbem_svc(nullptr) + { + } + ~WmiConnection() + { + close(); + } + + HRESULT initialize(); + void close(); +}; + +HRESULT get_property_str( + IWbemClassObject* cls_obj, + const std::wstring& property, + std::wstring& value); +HRESULT get_property_int( + IWbemClassObject* cls_obj, + const std::wstring& property, + uint32_t& value); + +class WmiSubscription +{ +private: + std::wstring query; + + WmiConnection conn; + IEnumWbemClassObject *event_enum; + +public: + WmiSubscription(std::wstring ns, std::wstring query) + : query(query) + , conn(WmiConnection(ns)) + , event_enum(nullptr) + { + } + ~WmiSubscription() + { + close(); + } + + HRESULT initialize(); + void close(); + + // IEnumWbemClassObject::Next wrapper + HRESULT next( + long timeout, + ULONG count, + IWbemClassObject **objects, + ULONG *returned); +}; + +WmiSubscription subscribe_wnbd_adapter_events(uint32_t interval);