2 * rbd-wnbd - RBD in userspace
4 * Copyright (C) 2020 SUSE LINUX GmbH
6 * This is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License version 2.1, as published by the Free Software
9 * Foundation. See file COPYING.
13 #include "include/int_types.h"
19 #include <sys/types.h>
20 #include <sys/socket.h>
23 #include <boost/locale/encoding_utf.hpp>
25 #include "wnbd_handler.h"
32 #include "common/Formatter.h"
33 #include "common/TextTable.h"
34 #include "common/ceph_argparse.h"
35 #include "common/config.h"
36 #include "common/debug.h"
37 #include "common/dout.h"
38 #include "common/errno.h"
39 #include "common/version.h"
40 #include "common/win32/service.h"
41 #include "common/admin_socket_client.h"
43 #include "global/global_init.h"
45 #include "include/uuid.h"
46 #include "include/rados/librados.hpp"
47 #include "include/rbd/librbd.hpp"
51 #define dout_context g_ceph_context
52 #define dout_subsys ceph_subsys_rbd
54 #define dout_prefix *_dout << "rbd-wnbd: "
57 using boost::locale::conv::utf_to_utf
;
59 std::wstring
to_wstring(const std::string
& str
)
61 return utf_to_utf
<wchar_t>(str
.c_str(), str
.c_str() + str
.size());
64 std::string
to_string(const std::wstring
& str
)
66 return utf_to_utf
<char>(str
.c_str(), str
.c_str() + str
.size());
69 bool is_process_running(DWORD pid
)
71 HANDLE process
= OpenProcess(SYNCHRONIZE
, FALSE
, pid
);
72 DWORD ret
= WaitForSingleObject(process
, 0);
74 return ret
== WAIT_TIMEOUT
;
77 DWORD
WNBDActiveDiskIterator::fetch_list(
78 PWNBD_CONNECTION_LIST
* conn_list
)
80 DWORD curr_buff_sz
= 0;
83 PWNBD_CONNECTION_LIST tmp_list
= NULL
;
85 // We're using a loop because other connections may show up by the time
92 tmp_list
= (PWNBD_CONNECTION_LIST
) calloc(1, buff_sz
);
94 derr
<< "Could not allocate " << buff_sz
<< " bytes." << dendl
;
95 err
= ERROR_NOT_ENOUGH_MEMORY
;
100 curr_buff_sz
= buff_sz
;
101 // If the buffer is too small, the return value is 0 and "BufferSize"
102 // will contain the required size. This is counterintuitive, but
103 // Windows drivers can't return a buffer as well as a non-zero status.
104 err
= WnbdList(tmp_list
, &buff_sz
);
107 } while (curr_buff_sz
< buff_sz
);
113 *conn_list
= tmp_list
;
118 WNBDActiveDiskIterator::WNBDActiveDiskIterator()
120 DWORD status
= WNBDActiveDiskIterator::fetch_list(&conn_list
);
126 WNBDActiveDiskIterator::~WNBDActiveDiskIterator()
134 bool WNBDActiveDiskIterator::get(Config
*cfg
)
139 if (!conn_list
|| index
>= (int)conn_list
->Count
) {
143 auto conn_info
= conn_list
->Connections
[index
];
144 auto conn_props
= conn_info
.Properties
;
146 if (strncmp(conn_props
.Owner
, RBD_WNBD_OWNER_NAME
, WNBD_MAX_OWNER_LENGTH
)) {
147 dout(10) << "Ignoring disk: " << conn_props
.InstanceName
148 << ". Owner: " << conn_props
.Owner
<< dendl
;
149 return this->get(cfg
);
152 error
= load_mapping_config_from_registry(conn_props
.InstanceName
, cfg
);
154 derr
<< "Could not load registry disk info for: "
155 << conn_props
.InstanceName
<< ". Error: " << error
<< dendl
;
159 cfg
->disk_number
= conn_info
.DiskNumber
;
160 cfg
->serial_number
= std::string(conn_props
.SerialNumber
);
161 cfg
->pid
= conn_props
.Pid
;
162 cfg
->active
= cfg
->disk_number
> 0 && is_process_running(conn_props
.Pid
);
163 cfg
->wnbd_mapped
= true;
168 RegistryDiskIterator::RegistryDiskIterator()
170 reg_key
= new RegistryKey(g_ceph_context
, HKEY_LOCAL_MACHINE
,
171 SERVICE_REG_KEY
, false);
172 if (!reg_key
->hKey
) {
173 if (!reg_key
->missingKey
)
178 if (RegQueryInfoKey(reg_key
->hKey
, NULL
, NULL
, NULL
, &subkey_count
,
179 NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
)) {
180 derr
<< "Could not query registry key: " << SERVICE_REG_KEY
<< dendl
;
186 bool RegistryDiskIterator::get(Config
*cfg
)
191 if (!reg_key
->hKey
|| !subkey_count
) {
195 char subkey_name
[MAX_PATH
] = {0};
196 DWORD subkey_name_sz
= MAX_PATH
;
197 int err
= RegEnumKeyEx(
198 reg_key
->hKey
, index
, subkey_name
, &subkey_name_sz
,
199 NULL
, NULL
, NULL
, NULL
);
200 if (err
== ERROR_NO_MORE_ITEMS
) {
203 derr
<< "Could not enumerate registry. Error: " << err
<< dendl
;
208 if (load_mapping_config_from_registry(subkey_name
, cfg
)) {
216 // Iterate over all RBD mappings, getting info from the registry and the driver.
217 bool WNBDDiskIterator::get(Config
*cfg
)
221 bool found_active
= active_iterator
.get(cfg
);
223 active_devices
.insert(cfg
->devpath
);
227 error
= active_iterator
.get_error();
229 dout(5) << ": WNBD iterator error: " << error
<< dendl
;
233 while(registry_iterator
.get(cfg
)) {
234 if (active_devices
.find(cfg
->devpath
) != active_devices
.end()) {
235 // Skip active devices that were already yielded.
241 error
= registry_iterator
.get_error();
243 dout(5) << ": Registry iterator error: " << error
<< dendl
;
248 int get_exe_path(std::string
& path
) {
249 char buffer
[MAX_PATH
];
252 int ret
= GetModuleFileNameA(NULL
, buffer
, MAX_PATH
);
253 if (!ret
|| ret
== MAX_PATH
) {
254 err
= GetLastError();
255 derr
<< "Could not retrieve executable path. "
256 << "Error: " << win32_strerror(err
) << dendl
;
264 std::string
get_cli_args() {
265 std::ostringstream cmdline
;
266 for (int i
=1; i
<__argc
; i
++) {
269 cmdline
<< std::quoted(__argv
[i
]);
271 return cmdline
.str();
274 int send_map_request(std::string arguments
) {
275 dout(15) << __func__
<< ": command arguments: " << arguments
<< dendl
;
277 BYTE request_buff
[SERVICE_PIPE_BUFFSZ
] = { 0 };
278 ServiceRequest
* request
= (ServiceRequest
*) request_buff
;
279 request
->command
= Connect
;
281 (char*)request
->arguments
,
282 SERVICE_PIPE_BUFFSZ
- FIELD_OFFSET(ServiceRequest
, arguments
));
283 ServiceReply reply
= { 0 };
285 DWORD bytes_read
= 0;
286 BOOL success
= CallNamedPipe(
293 DEFAULT_MAP_TIMEOUT_MS
);
295 DWORD err
= GetLastError();
296 derr
<< "Could not send device map request. "
297 << "Make sure that the ceph service is running. "
298 << "Error: " << win32_strerror(err
) << dendl
;
302 derr
<< "The ceph service failed to map the image. Error: "
303 << reply
.status
<< dendl
;
309 // Spawn a subprocess using the specified "rbd-wnbd" command
310 // arguments. A pipe is passed to the child process,
311 // which will allow it to communicate the mapping status
312 int map_device_using_suprocess(std::string arguments
, int timeout_ms
)
315 PROCESS_INFORMATION pi
;
317 DWORD err
= 0, status
= 0;
319 std::ostringstream command_line
;
320 std::string exe_path
;
321 // Windows async IO context
322 OVERLAPPED connect_o
, read_o
;
323 HANDLE connect_event
= NULL
, read_event
= NULL
;
324 // Used for waiting on multiple events that are going to be initialized later.
325 HANDLE wait_events
[2] = { INVALID_HANDLE_VALUE
, INVALID_HANDLE_VALUE
};
326 DWORD bytes_read
= 0;
327 // We may get a command line containing an old pipe handle when
328 // recreating mappings, so we'll have to replace it.
329 std::regex
pipe_pattern("([\'\"]?--pipe-name[\'\"]? +[\'\"]?[^ ]+[\'\"]?)");
332 uuid
.generate_random();
333 std::ostringstream pipe_name
;
334 pipe_name
<< "\\\\.\\pipe\\rbd-wnbd-" << uuid
;
336 // Create an unique named pipe to communicate with the child. */
337 HANDLE pipe_handle
= CreateNamedPipe(
338 pipe_name
.str().c_str(),
339 PIPE_ACCESS_INBOUND
| FILE_FLAG_FIRST_PIPE_INSTANCE
|
340 FILE_FLAG_OVERLAPPED
,
342 1, // Only accept one instance
345 SERVICE_PIPE_TIMEOUT_MS
,
347 if (pipe_handle
== INVALID_HANDLE_VALUE
) {
348 err
= GetLastError();
349 derr
<< "CreateNamedPipe failed: " << win32_strerror(err
) << dendl
;
353 connect_event
= CreateEvent(0, TRUE
, FALSE
, NULL
);
354 read_event
= CreateEvent(0, TRUE
, FALSE
, NULL
);
355 if (!connect_event
|| !read_event
) {
356 err
= GetLastError();
357 derr
<< "CreateEvent failed: " << win32_strerror(err
) << dendl
;
361 connect_o
.hEvent
= connect_event
;
362 read_o
.hEvent
= read_event
;
364 status
= ConnectNamedPipe(pipe_handle
, &connect_o
);
365 err
= GetLastError();
366 if (status
|| err
!= ERROR_IO_PENDING
) {
369 derr
<< "ConnectNamedPipe failed: " << win32_strerror(err
) << dendl
;
375 dout(5) << __func__
<< ": command arguments: " << arguments
<< dendl
;
377 // We'll avoid running arbitrary commands, instead using the executable
378 // path of this process (expected to be the full rbd-wnbd.exe path).
379 err
= get_exe_path(exe_path
);
384 command_line
<< std::quoted(exe_path
)
385 << " " << std::regex_replace(arguments
, pipe_pattern
, "")
386 << " --pipe-name " << pipe_name
.str();
388 dout(5) << __func__
<< ": command line: " << command_line
.str() << dendl
;
391 // Create a detached child
392 if (!CreateProcess(NULL
, (char*)command_line
.str().c_str(),
393 NULL
, NULL
, FALSE
, DETACHED_PROCESS
,
394 NULL
, NULL
, &si
, &pi
)) {
395 err
= GetLastError();
396 derr
<< "CreateProcess failed: " << win32_strerror(err
) << dendl
;
401 wait_events
[0] = connect_event
;
402 wait_events
[1] = pi
.hProcess
;
403 status
= WaitForMultipleObjects(2, wait_events
, FALSE
, timeout_ms
);
406 if (!GetOverlappedResult(pipe_handle
, &connect_o
, &bytes_read
, TRUE
)) {
407 err
= GetLastError();
408 derr
<< "Couln't establish a connection with the child process. "
409 << "Error: " << win32_strerror(err
) << dendl
;
413 // We have an incoming connection.
415 case WAIT_OBJECT_0
+ 1:
416 // The process has exited prematurely.
419 derr
<< "Timed out waiting for child process connection." << dendl
;
422 derr
<< "Failed waiting for child process. Status: " << status
<< dendl
;
425 // Block and wait for child to say it is ready.
426 dout(5) << __func__
<< ": waiting for child notification." << dendl
;
427 if (!ReadFile(pipe_handle
, &ch
, 1, NULL
, &read_o
)) {
428 err
= GetLastError();
429 if (err
!= ERROR_IO_PENDING
) {
430 derr
<< "Receiving child process reply failed with: "
431 << win32_strerror(err
) << dendl
;
436 wait_events
[0] = read_event
;
437 wait_events
[1] = pi
.hProcess
;
438 // The RBD daemon is expected to write back right after opening the
439 // pipe. We'll use the same timeout value for now.
440 status
= WaitForMultipleObjects(2, wait_events
, FALSE
, timeout_ms
);
443 if (!GetOverlappedResult(pipe_handle
, &read_o
, &bytes_read
, TRUE
)) {
444 err
= GetLastError();
445 derr
<< "Receiving child process reply failed with: "
446 << win32_strerror(err
) << dendl
;
451 case WAIT_OBJECT_0
+ 1:
452 // The process has exited prematurely.
455 derr
<< "Timed out waiting for child process message." << dendl
;
458 derr
<< "Failed waiting for child process. Status: " << status
<< dendl
;
462 dout(5) << __func__
<< ": received child notification." << dendl
;
466 if (!is_process_running(pi
.dwProcessId
)) {
467 GetExitCodeProcess(pi
.hProcess
, (PDWORD
)&exit_code
);
468 derr
<< "Daemon failed with: " << cpp_strerror(exit_code
) << dendl
;
470 // The process closed the pipe without notifying us or exiting.
471 // This is quite unlikely, but we'll terminate the process.
472 dout(5) << "Terminating unresponsive process." << dendl
;
473 TerminateProcess(pi
.hProcess
, 1);
479 derr
<< "Could not start RBD daemon." << dendl
;
481 CloseHandle(pipe_handle
);
483 CloseHandle(connect_event
);
485 CloseHandle(read_event
);
489 BOOL WINAPI
console_handler_routine(DWORD dwCtrlType
)
491 dout(5) << "Received control signal: " << dwCtrlType
492 << ". Exiting." << dendl
;
494 std::unique_lock l
{shutdown_lock
};
501 int save_config_to_registry(Config
* cfg
)
503 std::string strKey
{ SERVICE_REG_KEY
};
505 strKey
.append(cfg
->devpath
);
506 auto reg_key
= RegistryKey(
507 g_ceph_context
, HKEY_LOCAL_MACHINE
, strKey
.c_str(), true);
513 // Registry writes are immediately available to other processes.
514 // Still, we'll do a flush to ensure that the mapping can be
515 // recreated after a system crash.
516 if (reg_key
.set("pid", getpid()) ||
517 reg_key
.set("devpath", cfg
->devpath
) ||
518 reg_key
.set("poolname", cfg
->poolname
) ||
519 reg_key
.set("nsname", cfg
->nsname
) ||
520 reg_key
.set("imgname", cfg
->imgname
) ||
521 reg_key
.set("snapname", cfg
->snapname
) ||
522 reg_key
.set("command_line", get_cli_args()) ||
523 reg_key
.set("persistent", cfg
->persistent
) ||
524 reg_key
.set("admin_sock_path", g_conf()->admin_socket
) ||
532 int remove_config_from_registry(Config
* cfg
)
534 std::string strKey
{ SERVICE_REG_KEY
};
536 strKey
.append(cfg
->devpath
);
537 return RegistryKey::remove(
538 g_ceph_context
, HKEY_LOCAL_MACHINE
, strKey
.c_str());
541 int load_mapping_config_from_registry(string devpath
, Config
* cfg
)
543 std::string strKey
{ SERVICE_REG_KEY
};
545 strKey
.append(devpath
);
546 auto reg_key
= RegistryKey(
547 g_ceph_context
, HKEY_LOCAL_MACHINE
, strKey
.c_str(), false);
549 if (reg_key
.missingKey
)
555 reg_key
.get("devpath", cfg
->devpath
);
556 reg_key
.get("poolname", cfg
->poolname
);
557 reg_key
.get("nsname", cfg
->nsname
);
558 reg_key
.get("imgname", cfg
->imgname
);
559 reg_key
.get("snapname", cfg
->snapname
);
560 reg_key
.get("command_line", cfg
->command_line
);
561 reg_key
.get("persistent", cfg
->persistent
);
562 reg_key
.get("admin_sock_path", cfg
->admin_sock_path
);
567 int restart_registered_mappings(
570 int image_map_timeout
)
573 WNBDDiskIterator iterator
;
576 int total_timeout_ms
= max(total_timeout
, total_timeout
* 1000);
577 int image_map_timeout_ms
= max(image_map_timeout
, image_map_timeout
* 1000);
579 LARGE_INTEGER start_t
, counter_freq
;
580 QueryPerformanceFrequency(&counter_freq
);
581 QueryPerformanceCounter(&start_t
);
583 boost::asio::thread_pool
pool(worker_count
);
584 while (iterator
.get(&cfg
)) {
585 if (cfg
.command_line
.empty()) {
586 derr
<< "Could not recreate mapping, missing command line: "
587 << cfg
.devpath
<< dendl
;
591 if (cfg
.wnbd_mapped
) {
592 dout(5) << __func__
<< ": device already mapped: "
593 << cfg
.devpath
<< dendl
;
596 if (!cfg
.persistent
) {
597 dout(5) << __func__
<< ": cleaning up non-persistent mapping: "
598 << cfg
.devpath
<< dendl
;
599 r
= remove_config_from_registry(&cfg
);
601 derr
<< __func__
<< ": could not clean up non-persistent mapping: "
602 << cfg
.devpath
<< dendl
;
607 boost::asio::post(pool
,
610 LARGE_INTEGER curr_t
, elapsed_ms
;
611 QueryPerformanceCounter(&curr_t
);
612 elapsed_ms
.QuadPart
= curr_t
.QuadPart
- start_t
.QuadPart
;
613 elapsed_ms
.QuadPart
*= 1000;
614 elapsed_ms
.QuadPart
/= counter_freq
.QuadPart
;
616 int time_left_ms
= max(
618 total_timeout_ms
- (int)elapsed_ms
.QuadPart
);
619 time_left_ms
= min(image_map_timeout_ms
, time_left_ms
);
625 dout(5) << "Remapping: " << cfg
.devpath
626 << ". Timeout: " << time_left_ms
<< " ms." << dendl
;
628 // We'll try to map all devices and return a non-zero value
629 // if any of them fails.
630 r
= map_device_using_suprocess(cfg
.command_line
, time_left_ms
);
633 derr
<< "Could not create mapping: "
634 << cfg
.devpath
<< ". Error: " << r
<< dendl
;
636 dout(5) << "Successfully remapped: " << cfg
.devpath
<< dendl
;
642 r
= iterator
.get_error();
644 derr
<< "Could not fetch all mappings. Error: " << r
<< dendl
;
651 int disconnect_all_mappings(
653 bool hard_disconnect
,
654 int soft_disconnect_timeout
,
657 // Although not generally recommended, soft_disconnect_timeout can be 0,
658 // which means infinite timeout.
659 ceph_assert(soft_disconnect_timeout
>= 0);
660 ceph_assert(worker_count
> 0);
661 int64_t timeout_ms
= soft_disconnect_timeout
* 1000;
664 WNBDActiveDiskIterator iterator
;
667 boost::asio::thread_pool
pool(worker_count
);
668 LARGE_INTEGER start_t
, counter_freq
;
669 QueryPerformanceFrequency(&counter_freq
);
670 QueryPerformanceCounter(&start_t
);
671 while (iterator
.get(&cfg
)) {
672 boost::asio::post(pool
,
675 LARGE_INTEGER curr_t
, elapsed_ms
;
676 QueryPerformanceCounter(&curr_t
);
677 elapsed_ms
.QuadPart
= curr_t
.QuadPart
- start_t
.QuadPart
;
678 elapsed_ms
.QuadPart
*= 1000;
679 elapsed_ms
.QuadPart
/= counter_freq
.QuadPart
;
681 int64_t time_left_ms
= max((int64_t)0, timeout_ms
- elapsed_ms
.QuadPart
);
683 cfg
.hard_disconnect
= hard_disconnect
|| !time_left_ms
;
684 cfg
.hard_disconnect_fallback
= true;
685 cfg
.soft_disconnect_timeout
= time_left_ms
/ 1000;
687 dout(5) << "Removing mapping: " << cfg
.devpath
688 << ". Timeout: " << cfg
.soft_disconnect_timeout
689 << "s. Hard disconnect: " << cfg
.hard_disconnect
692 r
= do_unmap(&cfg
, unregister
);
695 derr
<< "Could not remove mapping: " << cfg
.devpath
696 << ". Error: " << r
<< dendl
;
698 dout(5) << "Successfully removed mapping: " << cfg
.devpath
<< dendl
;
704 r
= iterator
.get_error();
706 derr
<< "Could not fetch all mappings. Error: " << r
<< dendl
;
713 class RBDService
: public ServiceBase
{
715 bool hard_disconnect
;
716 int soft_disconnect_timeout
;
718 int service_start_timeout
;
719 int image_map_timeout
;
720 bool remap_failure_fatal
;
723 RBDService(bool _hard_disconnect
,
724 int _soft_disconnect_timeout
,
726 int _service_start_timeout
,
727 int _image_map_timeout
,
728 bool _remap_failure_fatal
)
729 : ServiceBase(g_ceph_context
)
730 , hard_disconnect(_hard_disconnect
)
731 , soft_disconnect_timeout(_soft_disconnect_timeout
)
732 , thread_count(_thread_count
)
733 , service_start_timeout(_service_start_timeout
)
734 , image_map_timeout(_image_map_timeout
)
735 , remap_failure_fatal(_remap_failure_fatal
)
739 static int execute_command(ServiceRequest
* request
)
741 switch(request
->command
) {
743 dout(5) << "Received device connect request. Command line: "
744 << (char*)request
->arguments
<< dendl
;
745 // TODO: use the configured service map timeout.
746 // TODO: add ceph.conf options.
747 return map_device_using_suprocess(
748 (char*)request
->arguments
, DEFAULT_MAP_TIMEOUT_MS
);
750 dout(5) << "Received unsupported command: "
751 << request
->command
<< dendl
;
756 static DWORD
handle_connection(HANDLE pipe_handle
)
758 PBYTE message
[SERVICE_PIPE_BUFFSZ
] = { 0 };
759 DWORD bytes_read
= 0, bytes_written
= 0;
762 ServiceReply reply
= { 0 };
764 dout(20) << __func__
<< ": Receiving message." << dendl
;
765 BOOL success
= ReadFile(
766 pipe_handle
, message
, SERVICE_PIPE_BUFFSZ
,
768 if (!success
|| !bytes_read
) {
769 err
= GetLastError();
770 derr
<< "Could not read service command: "
771 << win32_strerror(err
) << dendl
;
775 dout(20) << __func__
<< ": Executing command." << dendl
;
776 reply
.status
= execute_command((ServiceRequest
*) message
);
777 reply_sz
= sizeof(reply
);
779 dout(20) << __func__
<< ": Sending reply. Status: "
780 << reply
.status
<< dendl
;
782 pipe_handle
, &reply
, reply_sz
, &bytes_written
, NULL
);
783 if (!success
|| reply_sz
!= bytes_written
) {
784 err
= GetLastError();
785 derr
<< "Could not send service command result: "
786 << win32_strerror(err
) << dendl
;
790 dout(20) << __func__
<< ": Cleaning up connection." << dendl
;
791 FlushFileBuffers(pipe_handle
);
792 DisconnectNamedPipe(pipe_handle
);
793 CloseHandle(pipe_handle
);
798 // We have to support Windows server 2016. Unix sockets only work on
799 // WS 2019, so we can't use the Ceph admin socket abstraction.
800 // Getting the Ceph admin sockets to work with Windows named pipes
801 // would require quite a few changes.
802 static DWORD
accept_pipe_connection() {
804 // We're currently using default ACLs, which grant full control to the
805 // LocalSystem account and administrator as well as the owner.
806 dout(20) << __func__
<< ": opening new pipe instance" << dendl
;
807 HANDLE pipe_handle
= CreateNamedPipe(
810 PIPE_TYPE_MESSAGE
| PIPE_READMODE_MESSAGE
| PIPE_WAIT
,
811 PIPE_UNLIMITED_INSTANCES
,
814 SERVICE_PIPE_TIMEOUT_MS
,
816 if (pipe_handle
== INVALID_HANDLE_VALUE
) {
817 err
= GetLastError();
818 derr
<< "CreatePipe failed: " << win32_strerror(err
) << dendl
;
822 dout(20) << __func__
<< ": waiting for connections." << dendl
;
823 BOOL connected
= ConnectNamedPipe(pipe_handle
, NULL
);
825 err
= GetLastError();
826 if (err
!= ERROR_PIPE_CONNECTED
) {
827 derr
<< "Pipe connection failed: " << win32_strerror(err
) << dendl
;
829 CloseHandle(pipe_handle
);
834 dout(20) << __func__
<< ": Connection received." << dendl
;
835 // We'll handle the connection in a separate thread and at the same time
836 // accept a new connection.
837 HANDLE handler_thread
= CreateThread(
838 NULL
, 0, (LPTHREAD_START_ROUTINE
) handle_connection
, pipe_handle
, 0, 0);
839 if (!handler_thread
) {
840 err
= GetLastError();
841 derr
<< "Could not start pipe connection handler thread: "
842 << win32_strerror(err
) << dendl
;
843 CloseHandle(pipe_handle
);
845 CloseHandle(handler_thread
);
851 static int pipe_server_loop(LPVOID arg
)
853 dout(5) << "Accepting admin pipe connections." << dendl
;
855 // This call will block until a connection is received, which will
856 // then be handled in a separate thread. The function returns, allowing
857 // us to accept another simultaneous connection.
858 accept_pipe_connection();
863 int create_pipe_server() {
864 HANDLE handler_thread
= CreateThread(
865 NULL
, 0, (LPTHREAD_START_ROUTINE
) pipe_server_loop
, NULL
, 0, 0);
868 if (!handler_thread
) {
869 err
= GetLastError();
870 derr
<< "Could not start pipe server: " << win32_strerror(err
) << dendl
;
872 CloseHandle(handler_thread
);
878 int run_hook() override
{
879 // Restart registered mappings before accepting new ones.
880 int r
= restart_registered_mappings(
881 thread_count
, service_start_timeout
, image_map_timeout
);
883 if (remap_failure_fatal
) {
884 derr
<< "Couldn't remap all images. Cleaning up." << dendl
;
887 dout(0) << "Ignoring image remap failure." << dendl
;
891 return create_pipe_server();
894 // Invoked when the service is requested to stop.
895 int stop_hook() override
{
896 return disconnect_all_mappings(
897 false, hard_disconnect
, soft_disconnect_timeout
, thread_count
);
899 // Invoked when the system is shutting down.
900 int shutdown_hook() override
{
907 const char* usage_str
=R
"(
908 Usage: rbd-wnbd [options] map <image-or-snap-spec> Map an image to wnbd device
909 [options] unmap <device|image-or-snap-spec> Unmap wnbd device
910 [options] list List mapped wnbd devices
911 [options] show <image-or-snap-spec> Show mapped wnbd device
912 stats <image-or-snap-spec> Show IO counters
913 [options] service Windows service entrypoint,
914 handling device lifecycle
917 --device <device path> Optional mapping unique identifier
918 --exclusive Forbid writes by other clients
919 --read-only Map read-only
920 --non-persistent Do not recreate the mapping when the Ceph service
921 restarts. By default, mappings are persistent
922 --io-req-workers The number of workers that dispatch IO requests.
924 --io-reply-workers The number of workers that dispatch IO replies.
928 --hard-disconnect Skip attempting a soft disconnect
929 --no-hard-disconnect-fallback Immediately return an error if the soft
930 disconnect fails instead of attempting a hard
931 disconnect as fallback
932 --soft-disconnect-timeout Soft disconnect timeout in seconds. The soft
933 disconnect operation uses PnP to notify the
934 Windows storage stack that the device is going to
935 be disconnectd. Storage drivers can block this
936 operation if there are pending operations,
937 unflushed caches or open handles. Default: 15
940 --hard-disconnect Skip attempting a soft disconnect
941 --soft-disconnect-timeout Cummulative soft disconnect timeout in seconds,
942 used when disconnecting existing mappings. A hard
943 disconnect will be issued when hitting the timeout
944 --service-thread-count The number of workers used when mapping or
945 unmapping images. Default: 8
946 --start-timeout The service start timeout in seconds. Default: 120
947 --map-timeout Individual image map timeout in seconds. Default: 20
948 --remap-failure-fatal If set, the service will stop when failing to remap
949 an image at start time, unmapping images that have
953 --format plain|json|xml Output format (default: plain)
954 --pretty-format Pretty formatting (json and xml)
957 --wnbd-log-level libwnbd.dll log level
961 std::cout
<< usage_str
;
962 generic_server_usage();
966 static Command cmd
= None
;
968 int construct_devpath_if_missing(Config
* cfg
)
970 // Windows doesn't allow us to request specific disk paths when mapping an
971 // image. This will just be used by rbd-wnbd and wnbd as an identifier.
972 if (cfg
->devpath
.empty()) {
973 if (cfg
->imgname
.empty()) {
974 derr
<< "Missing image name." << dendl
;
978 if (!cfg
->poolname
.empty()) {
979 cfg
->devpath
+= cfg
->poolname
;
982 if (!cfg
->nsname
.empty()) {
983 cfg
->devpath
+= cfg
->nsname
;
987 cfg
->devpath
+= cfg
->imgname
;
989 if (!cfg
->snapname
.empty()) {
991 cfg
->devpath
+= cfg
->snapname
;
998 boost::intrusive_ptr
<CephContext
> do_global_init(
999 int argc
, const char *argv
[], Config
*cfg
)
1001 auto args
= argv_to_vec(argc
, argv
);
1003 code_environment_t code_env
;
1008 code_env
= CODE_ENVIRONMENT_DAEMON
;
1009 flags
= CINIT_FLAG_UNPRIVILEGED_DAEMON_DEFAULTS
;
1012 code_env
= CODE_ENVIRONMENT_DAEMON
;
1013 flags
= CINIT_FLAG_UNPRIVILEGED_DAEMON_DEFAULTS
|
1014 CINIT_FLAG_NO_MON_CONFIG
|
1015 CINIT_FLAG_NO_DAEMON_ACTIONS
;
1018 code_env
= CODE_ENVIRONMENT_UTILITY
;
1019 flags
= CINIT_FLAG_NO_MON_CONFIG
;
1023 global_pre_init(NULL
, args
, CEPH_ENTITY_TYPE_CLIENT
, code_env
, flags
);
1024 // Avoid cluttering the console when spawning a mapping that will run
1025 // in the background.
1026 if (g_conf()->daemonize
&& cfg
->parent_pipe
.empty()) {
1027 flags
|= CINIT_FLAG_NO_DAEMON_ACTIONS
;
1029 auto cct
= global_init(NULL
, args
, CEPH_ENTITY_TYPE_CLIENT
,
1030 code_env
, flags
, FALSE
);
1032 // There's no fork on Windows, we should be safe calling this anytime.
1033 common_init_finish(g_ceph_context
);
1034 global_init_chdir(g_ceph_context
);
1039 static int do_map(Config
*cfg
)
1043 librados::Rados rados
;
1045 librados::IoCtx io_ctx
;
1046 librbd::Image image
;
1047 librbd::image_info_t info
;
1048 HANDLE parent_pipe_handle
= INVALID_HANDLE_VALUE
;
1051 if (g_conf()->daemonize
&& cfg
->parent_pipe
.empty()) {
1052 return send_map_request(get_cli_args());
1055 dout(0) << "Mapping RBD image: " << cfg
->devpath
<< dendl
;
1057 r
= rados
.init_with_context(g_ceph_context
);
1059 derr
<< "rbd-wnbd: couldn't initialize rados: " << cpp_strerror(r
)
1064 r
= rados
.connect();
1066 derr
<< "rbd-wnbd: couldn't connect to rados: " << cpp_strerror(r
)
1071 r
= rados
.ioctx_create(cfg
->poolname
.c_str(), io_ctx
);
1073 derr
<< "rbd-wnbd: couldn't create IO context: " << cpp_strerror(r
)
1078 io_ctx
.set_namespace(cfg
->nsname
);
1080 r
= rbd
.open(io_ctx
, image
, cfg
->imgname
.c_str());
1082 derr
<< "rbd-wnbd: couldn't open rbd image: " << cpp_strerror(r
)
1087 if (cfg
->exclusive
) {
1088 r
= image
.lock_acquire(RBD_LOCK_MODE_EXCLUSIVE
);
1090 derr
<< "rbd-wnbd: failed to acquire exclusive lock: " << cpp_strerror(r
)
1096 if (!cfg
->snapname
.empty()) {
1097 r
= image
.snap_set(cfg
->snapname
.c_str());
1099 derr
<< "rbd-wnbd: couldn't use snapshot: " << cpp_strerror(r
)
1105 r
= image
.stat(info
, sizeof(info
));
1109 if (info
.size
> _UI64_MAX
) {
1111 derr
<< "rbd-wnbd: image is too large (" << byte_u_t(info
.size
)
1112 << ", max is " << byte_u_t(_UI64_MAX
) << ")" << dendl
;
1116 // We're storing mapping details in the registry even for non-persistent
1117 // mappings. This allows us to easily retrieve mapping details such
1118 // as the rbd pool or admin socket path.
1119 // We're cleaning up the registry entry when the non-persistent mapping
1120 // gets disconnected or when the ceph service restarts.
1121 r
= save_config_to_registry(cfg
);
1125 handler
= new WnbdHandler(image
, cfg
->devpath
,
1126 info
.size
/ RBD_WNBD_BLKSIZE
,
1128 !cfg
->snapname
.empty() || cfg
->readonly
,
1129 g_conf().get_val
<bool>("rbd_cache"),
1130 cfg
->io_req_workers
,
1131 cfg
->io_reply_workers
);
1132 r
= handler
->start();
1134 r
= r
== ERROR_ALREADY_EXISTS
? -EEXIST
: -EINVAL
;
1138 // We're informing the parent processes that the initialization
1140 if (!cfg
->parent_pipe
.empty()) {
1141 parent_pipe_handle
= CreateFile(
1142 cfg
->parent_pipe
.c_str(), GENERIC_WRITE
, 0, NULL
,
1143 OPEN_EXISTING
, 0, NULL
);
1144 if (parent_pipe_handle
== INVALID_HANDLE_VALUE
) {
1145 derr
<< "Could not open parent pipe: " << win32_strerror(err
) << dendl
;
1146 } else if (!WriteFile(parent_pipe_handle
, "a", 1, NULL
, NULL
)) {
1147 // TODO: consider exiting in this case. The parent didn't wait for us,
1148 // maybe it was killed after a timeout.
1149 err
= GetLastError();
1150 derr
<< "Failed to communicate with the parent: "
1151 << win32_strerror(err
) << dendl
;
1153 dout(5) << __func__
<< ": submitted parent notification." << dendl
;
1156 if (parent_pipe_handle
!= INVALID_HANDLE_VALUE
)
1157 CloseHandle(parent_pipe_handle
);
1159 global_init_postfork_finish(g_ceph_context
);
1163 handler
->shutdown();
1165 // The registry record shouldn't be removed for (already) running mappings.
1166 if (!cfg
->persistent
) {
1167 dout(5) << __func__
<< ": cleaning up non-persistent mapping: "
1168 << cfg
->devpath
<< dendl
;
1169 r
= remove_config_from_registry(cfg
);
1171 derr
<< __func__
<< ": could not clean up non-persistent mapping: "
1172 << cfg
->devpath
<< dendl
;
1177 std::unique_lock l
{shutdown_lock
};
1190 static int do_unmap(Config
*cfg
, bool unregister
)
1192 WNBD_REMOVE_OPTIONS remove_options
= {0};
1193 remove_options
.Flags
.HardRemove
= cfg
->hard_disconnect
;
1194 remove_options
.Flags
.HardRemoveFallback
= cfg
->hard_disconnect_fallback
;
1195 remove_options
.SoftRemoveTimeoutMs
= cfg
->soft_disconnect_timeout
* 1000;
1196 remove_options
.SoftRemoveRetryIntervalMs
= SOFT_REMOVE_RETRY_INTERVAL
* 1000;
1198 int err
= WnbdRemoveEx(cfg
->devpath
.c_str(), &remove_options
);
1199 if (err
&& err
!= ERROR_FILE_NOT_FOUND
) {
1204 err
= remove_config_from_registry(cfg
);
1206 derr
<< "rbd-wnbd: failed to unregister device: "
1207 << cfg
->devpath
<< ". Error: " << err
<< dendl
;
1214 static int parse_imgpath(const std::string
&imgpath
, Config
*cfg
,
1215 std::ostream
*err_msg
)
1217 std::regex
pattern("^(?:([^/]+)/(?:([^/@]+)/)?)?([^@]+)(?:@([^/@]+))?$");
1219 if (!std::regex_match(imgpath
, match
, pattern
)) {
1220 derr
<< "rbd-wnbd: invalid spec '" << imgpath
<< "'" << dendl
;
1224 if (match
[1].matched
) {
1225 cfg
->poolname
= match
[1];
1228 if (match
[2].matched
) {
1229 cfg
->nsname
= match
[2];
1232 cfg
->imgname
= match
[3];
1234 if (match
[4].matched
)
1235 cfg
->snapname
= match
[4];
1240 static int do_list_mapped_devices(const std::string
&format
, bool pretty_format
)
1242 std::unique_ptr
<ceph::Formatter
> f
;
1245 if (format
== "json") {
1246 f
.reset(new JSONFormatter(pretty_format
));
1247 } else if (format
== "xml") {
1248 f
.reset(new XMLFormatter(pretty_format
));
1249 } else if (!format
.empty() && format
!= "plain") {
1250 derr
<< "rbd-wnbd: invalid output format: " << format
<< dendl
;
1255 f
->open_array_section("devices");
1257 tbl
.define_column("id", TextTable::LEFT
, TextTable::LEFT
);
1258 tbl
.define_column("pool", TextTable::LEFT
, TextTable::LEFT
);
1259 tbl
.define_column("namespace", TextTable::LEFT
, TextTable::LEFT
);
1260 tbl
.define_column("image", TextTable::LEFT
, TextTable::LEFT
);
1261 tbl
.define_column("snap", TextTable::LEFT
, TextTable::LEFT
);
1262 tbl
.define_column("device", TextTable::LEFT
, TextTable::LEFT
);
1263 tbl
.define_column("disk_number", TextTable::LEFT
, TextTable::LEFT
);
1264 tbl
.define_column("status", TextTable::LEFT
, TextTable::LEFT
);
1268 WNBDDiskIterator wnbd_disk_iterator
;
1270 while (wnbd_disk_iterator
.get(&cfg
)) {
1271 const char* status
= cfg
.active
?
1272 WNBD_STATUS_ACTIVE
: WNBD_STATUS_INACTIVE
;
1275 f
->open_object_section("device");
1276 f
->dump_int("id", cfg
.pid
? cfg
.pid
: -1);
1277 f
->dump_string("device", cfg
.devpath
);
1278 f
->dump_string("pool", cfg
.poolname
);
1279 f
->dump_string("namespace", cfg
.nsname
);
1280 f
->dump_string("image", cfg
.imgname
);
1281 f
->dump_string("snap", cfg
.snapname
);
1282 f
->dump_int("disk_number", cfg
.disk_number
? cfg
.disk_number
: -1);
1283 f
->dump_string("status", status
);
1286 if (cfg
.snapname
.empty()) {
1289 tbl
<< (cfg
.pid
? cfg
.pid
: -1) << cfg
.poolname
<< cfg
.nsname
1290 << cfg
.imgname
<< cfg
.snapname
<< cfg
.devpath
1291 << cfg
.disk_number
<< status
<< TextTable::endrow
;
1294 int error
= wnbd_disk_iterator
.get_error();
1296 derr
<< "Could not get disk list: " << error
<< dendl
;
1302 f
->flush(std::cout
);
1310 static int do_show_mapped_device(std::string format
, bool pretty_format
,
1311 std::string devpath
)
1313 std::unique_ptr
<ceph::Formatter
> f
;
1316 if (format
.empty() || format
== "plain") {
1318 pretty_format
= true;
1320 if (format
== "json") {
1321 f
.reset(new JSONFormatter(pretty_format
));
1322 } else if (format
== "xml") {
1323 f
.reset(new XMLFormatter(pretty_format
));
1325 derr
<< "rbd-wnbd: invalid output format: " << format
<< dendl
;
1330 int error
= load_mapping_config_from_registry(devpath
, &cfg
);
1332 derr
<< "Could not load registry disk info for: "
1333 << devpath
<< ". Error: " << error
<< dendl
;
1337 WNBD_CONNECTION_INFO conn_info
= { 0 };
1338 // If the device is currently disconnected but there is a persistent
1339 // mapping record, we'll show that.
1340 DWORD ret
= WnbdShow(devpath
.c_str(), &conn_info
);
1341 if (ret
&& ret
!= ERROR_FILE_NOT_FOUND
) {
1345 auto conn_props
= conn_info
.Properties
;
1346 cfg
.active
= conn_info
.DiskNumber
> 0 && is_process_running(conn_props
.Pid
);
1347 f
->open_object_section("device");
1348 f
->dump_int("id", conn_props
.Pid
? conn_props
.Pid
: -1);
1349 f
->dump_string("device", cfg
.devpath
);
1350 f
->dump_string("pool", cfg
.poolname
);
1351 f
->dump_string("namespace", cfg
.nsname
);
1352 f
->dump_string("image", cfg
.imgname
);
1353 f
->dump_string("snap", cfg
.snapname
);
1354 f
->dump_int("persistent", cfg
.persistent
);
1355 f
->dump_int("disk_number", conn_info
.DiskNumber
? conn_info
.DiskNumber
: -1);
1356 f
->dump_string("status", cfg
.active
? WNBD_STATUS_ACTIVE
: WNBD_STATUS_INACTIVE
);
1357 f
->dump_string("pnp_device_id", to_string(conn_info
.PNPDeviceID
));
1358 f
->dump_int("readonly", conn_props
.Flags
.ReadOnly
);
1359 f
->dump_int("block_size", conn_props
.BlockSize
);
1360 f
->dump_int("block_count", conn_props
.BlockCount
);
1361 f
->dump_int("flush_enabled", conn_props
.Flags
.FlushSupported
);
1363 f
->flush(std::cout
);
1368 static int do_stats(std::string search_devpath
)
1371 WNBDDiskIterator wnbd_disk_iterator
;
1373 while (wnbd_disk_iterator
.get(&cfg
)) {
1374 if (cfg
.devpath
!= search_devpath
)
1377 AdminSocketClient client
= AdminSocketClient(cfg
.admin_sock_path
);
1379 std::string result
= client
.do_request("{\"prefix\":\"wnbd stats\"}",
1381 if (!result
.empty()) {
1382 std::cerr
<< "Admin socket error: " << result
<< std::endl
;
1386 std::cout
<< output
<< std::endl
;
1389 int error
= wnbd_disk_iterator
.get_error();
1394 derr
<< "Could not find the specified disk." << dendl
;
1398 static int parse_args(std::vector
<const char*>& args
,
1399 std::ostream
*err_msg
,
1400 Command
*command
, Config
*cfg
)
1402 std::string conf_file_list
;
1403 std::string cluster
;
1404 CephInitParameters iparams
= ceph_argparse_early_args(
1405 args
, CEPH_ENTITY_TYPE_CLIENT
, &cluster
, &conf_file_list
);
1407 ConfigProxy config
{false};
1408 config
->name
= iparams
.name
;
1409 config
->cluster
= cluster
;
1411 if (!conf_file_list
.empty()) {
1412 config
.parse_config_files(conf_file_list
.c_str(), nullptr, 0);
1414 config
.parse_config_files(nullptr, nullptr, 0);
1416 config
.parse_env(CEPH_ENTITY_TYPE_CLIENT
);
1417 config
.parse_argv(args
);
1418 cfg
->poolname
= config
.get_val
<std::string
>("rbd_default_pool");
1420 std::vector
<const char*>::iterator i
;
1421 std::ostringstream err
;
1423 // TODO: consider using boost::program_options like Device.cc does.
1424 // This should simplify argument parsing. Also, some arguments must be tied
1425 // to specific commands, for example the disconnect timeout. Luckily,
1426 // this is enforced by the "rbd device" wrapper.
1427 for (i
= args
.begin(); i
!= args
.end(); ) {
1428 if (ceph_argparse_flag(args
, i
, "-h", "--help", (char*)NULL
)) {
1430 } else if (ceph_argparse_flag(args
, i
, "-v", "--version", (char*)NULL
)) {
1431 return VERSION_INFO
;
1432 } else if (ceph_argparse_witharg(args
, i
, &cfg
->devpath
, "--device", (char *)NULL
)) {
1433 } else if (ceph_argparse_witharg(args
, i
, &cfg
->format
, err
, "--format",
1435 } else if (ceph_argparse_flag(args
, i
, "--read-only", (char *)NULL
)) {
1436 cfg
->readonly
= true;
1437 } else if (ceph_argparse_flag(args
, i
, "--exclusive", (char *)NULL
)) {
1438 cfg
->exclusive
= true;
1439 } else if (ceph_argparse_flag(args
, i
, "--non-persistent", (char *)NULL
)) {
1440 cfg
->persistent
= false;
1441 } else if (ceph_argparse_flag(args
, i
, "--pretty-format", (char *)NULL
)) {
1442 cfg
->pretty_format
= true;
1443 } else if (ceph_argparse_flag(args
, i
, "--remap-failure-fatal", (char *)NULL
)) {
1444 cfg
->remap_failure_fatal
= true;
1445 } else if (ceph_argparse_witharg(args
, i
, &cfg
->parent_pipe
, err
,
1446 "--pipe-name", (char *)NULL
)) {
1447 if (!err
.str().empty()) {
1448 *err_msg
<< "rbd-wnbd: " << err
.str();
1451 } else if (ceph_argparse_witharg(args
, i
, (int*)&cfg
->wnbd_log_level
,
1452 err
, "--wnbd-log-level", (char *)NULL
)) {
1453 if (!err
.str().empty()) {
1454 *err_msg
<< "rbd-wnbd: " << err
.str();
1457 if (cfg
->wnbd_log_level
< 0) {
1458 *err_msg
<< "rbd-wnbd: Invalid argument for wnbd-log-level";
1461 } else if (ceph_argparse_witharg(args
, i
, (int*)&cfg
->io_req_workers
,
1462 err
, "--io-req-workers", (char *)NULL
)) {
1463 if (!err
.str().empty()) {
1464 *err_msg
<< "rbd-wnbd: " << err
.str();
1467 if (cfg
->io_req_workers
<= 0) {
1468 *err_msg
<< "rbd-wnbd: Invalid argument for io-req-workers";
1471 } else if (ceph_argparse_witharg(args
, i
, (int*)&cfg
->io_reply_workers
,
1472 err
, "--io-reply-workers", (char *)NULL
)) {
1473 if (!err
.str().empty()) {
1474 *err_msg
<< "rbd-wnbd: " << err
.str();
1477 if (cfg
->io_reply_workers
<= 0) {
1478 *err_msg
<< "rbd-wnbd: Invalid argument for io-reply-workers";
1481 } else if (ceph_argparse_witharg(args
, i
, (int*)&cfg
->service_thread_count
,
1482 err
, "--service-thread-count", (char *)NULL
)) {
1483 if (!err
.str().empty()) {
1484 *err_msg
<< "rbd-wnbd: " << err
.str();
1487 if (cfg
->service_thread_count
<= 0) {
1488 *err_msg
<< "rbd-wnbd: Invalid argument for service-thread-count";
1491 } else if (ceph_argparse_flag(args
, i
, "--hard-disconnect", (char *)NULL
)) {
1492 cfg
->hard_disconnect
= true;
1493 } else if (ceph_argparse_flag(args
, i
,
1494 "--no-hard-disconnect-fallback", (char *)NULL
)) {
1495 cfg
->hard_disconnect_fallback
= false;
1496 } else if (ceph_argparse_witharg(args
, i
,
1497 (int*)&cfg
->soft_disconnect_timeout
,
1498 err
, "--soft-disconnect-timeout",
1500 if (!err
.str().empty()) {
1501 *err_msg
<< "rbd-wnbd: " << err
.str();
1504 if (cfg
->soft_disconnect_timeout
< 0) {
1505 *err_msg
<< "rbd-wnbd: Invalid argument for soft-disconnect-timeout";
1508 } else if (ceph_argparse_witharg(args
, i
,
1509 (int*)&cfg
->service_start_timeout
,
1510 err
, "--start-timeout",
1512 if (!err
.str().empty()) {
1513 *err_msg
<< "rbd-wnbd: " << err
.str();
1516 if (cfg
->service_start_timeout
<= 0) {
1517 *err_msg
<< "rbd-wnbd: Invalid argument for start-timeout";
1520 } else if (ceph_argparse_witharg(args
, i
,
1521 (int*)&cfg
->image_map_timeout
,
1522 err
, "--map-timeout",
1524 if (!err
.str().empty()) {
1525 *err_msg
<< "rbd-wnbd: " << err
.str();
1528 if (cfg
->image_map_timeout
<= 0) {
1529 *err_msg
<< "rbd-wnbd: Invalid argument for map-timeout";
1538 if (args
.begin() != args
.end()) {
1539 if (strcmp(*args
.begin(), "map") == 0) {
1541 } else if (strcmp(*args
.begin(), "unmap") == 0) {
1543 } else if (strcmp(*args
.begin(), "list") == 0) {
1545 } else if (strcmp(*args
.begin(), "show") == 0) {
1547 } else if (strcmp(*args
.begin(), "service") == 0) {
1549 } else if (strcmp(*args
.begin(), "stats") == 0) {
1551 } else if (strcmp(*args
.begin(), "help") == 0) {
1554 *err_msg
<< "rbd-wnbd: unknown command: " << *args
.begin();
1557 args
.erase(args
.begin());
1561 *err_msg
<< "rbd-wnbd: must specify command";
1570 if (args
.begin() == args
.end()) {
1571 *err_msg
<< "rbd-wnbd: must specify wnbd device or image-or-snap-spec";
1574 if (parse_imgpath(*args
.begin(), cfg
, err_msg
) < 0) {
1577 args
.erase(args
.begin());
1584 if (args
.begin() != args
.end()) {
1585 *err_msg
<< "rbd-wnbd: unknown args: " << *args
.begin();
1593 static int rbd_wnbd(int argc
, const char *argv
[])
1596 auto args
= argv_to_vec(argc
, argv
);
1598 // Avoid using dout before calling "do_global_init"
1600 std::cout
<< argv
[0] << ": -h or --help for usage" << std::endl
;
1604 std::ostringstream err_msg
;
1605 int r
= parse_args(args
, &err_msg
, &cmd
, &cfg
);
1606 if (r
== HELP_INFO
) {
1609 } else if (r
== VERSION_INFO
) {
1610 std::cout
<< pretty_version_to_str() << std::endl
;
1613 std::cout
<< err_msg
.str() << std::endl
;
1617 auto cct
= do_global_init(argc
, argv
, &cfg
);
1619 WnbdSetLogger(WnbdHandler::LogMessage
);
1620 WnbdSetLogLevel(cfg
.wnbd_log_level
);
1624 if (construct_devpath_if_missing(&cfg
)) {
1632 if (construct_devpath_if_missing(&cfg
)) {
1635 r
= do_unmap(&cfg
, true);
1640 r
= do_list_mapped_devices(cfg
.format
, cfg
.pretty_format
);
1645 if (construct_devpath_if_missing(&cfg
)) {
1648 r
= do_show_mapped_device(cfg
.format
, cfg
.pretty_format
, cfg
.devpath
);
1654 RBDService
service(cfg
.hard_disconnect
, cfg
.soft_disconnect_timeout
,
1655 cfg
.service_thread_count
,
1656 cfg
.service_start_timeout
,
1657 cfg
.image_map_timeout
,
1658 cfg
.remap_failure_fatal
);
1659 // This call will block until the service stops.
1660 r
= RBDService::initialize(&service
);
1666 if (construct_devpath_if_missing(&cfg
)) {
1669 return do_stats(cfg
.devpath
);
1678 int main(int argc
, const char *argv
[])
1680 SetConsoleCtrlHandler(console_handler_routine
, true);
1681 // Avoid the Windows Error Reporting dialog.
1682 SetErrorMode(GetErrorMode() | SEM_NOGPFAULTERRORBOX
);
1683 int r
= rbd_wnbd(argc
, argv
);