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: "
56 using boost::locale::conv::utf_to_utf
;
58 std::wstring
to_wstring(const std::string
& str
)
60 return utf_to_utf
<wchar_t>(str
.c_str(), str
.c_str() + str
.size());
63 std::string
to_string(const std::wstring
& str
)
65 return utf_to_utf
<char>(str
.c_str(), str
.c_str() + str
.size());
68 bool is_process_running(DWORD pid
)
70 HANDLE process
= OpenProcess(SYNCHRONIZE
, FALSE
, pid
);
71 DWORD ret
= WaitForSingleObject(process
, 0);
73 return ret
== WAIT_TIMEOUT
;
76 DWORD
WNBDActiveDiskIterator::fetch_list(
77 PWNBD_CONNECTION_LIST
* conn_list
)
79 DWORD curr_buff_sz
= 0;
82 PWNBD_CONNECTION_LIST tmp_list
= NULL
;
84 // We're using a loop because other connections may show up by the time
91 tmp_list
= (PWNBD_CONNECTION_LIST
) calloc(1, buff_sz
);
93 derr
<< "Could not allocate " << buff_sz
<< " bytes." << dendl
;
94 err
= ERROR_NOT_ENOUGH_MEMORY
;
99 curr_buff_sz
= buff_sz
;
100 // If the buffer is too small, the return value is 0 and "BufferSize"
101 // will contain the required size. This is counterintuitive, but
102 // Windows drivers can't return a buffer as well as a non-zero status.
103 err
= WnbdList(tmp_list
, &buff_sz
);
106 } while (curr_buff_sz
< buff_sz
);
112 *conn_list
= tmp_list
;
117 WNBDActiveDiskIterator::WNBDActiveDiskIterator()
119 DWORD status
= WNBDActiveDiskIterator::fetch_list(&conn_list
);
125 WNBDActiveDiskIterator::~WNBDActiveDiskIterator()
133 bool WNBDActiveDiskIterator::get(Config
*cfg
)
138 if (!conn_list
|| index
>= (int)conn_list
->Count
) {
142 auto conn_info
= conn_list
->Connections
[index
];
143 auto conn_props
= conn_info
.Properties
;
145 if (strncmp(conn_props
.Owner
, RBD_WNBD_OWNER_NAME
, WNBD_MAX_OWNER_LENGTH
)) {
146 dout(10) << "Ignoring disk: " << conn_props
.InstanceName
147 << ". Owner: " << conn_props
.Owner
<< dendl
;
148 return this->get(cfg
);
151 error
= load_mapping_config_from_registry(conn_props
.InstanceName
, cfg
);
153 derr
<< "Could not load registry disk info for: "
154 << conn_props
.InstanceName
<< ". Error: " << error
<< dendl
;
158 cfg
->disk_number
= conn_info
.DiskNumber
;
159 cfg
->serial_number
= std::string(conn_props
.SerialNumber
);
160 cfg
->pid
= conn_props
.Pid
;
161 cfg
->active
= cfg
->disk_number
> 0 && is_process_running(conn_props
.Pid
);
162 cfg
->wnbd_mapped
= true;
167 RegistryDiskIterator::RegistryDiskIterator()
169 reg_key
= new RegistryKey(g_ceph_context
, HKEY_LOCAL_MACHINE
,
170 SERVICE_REG_KEY
, false);
171 if (!reg_key
->hKey
) {
172 if (!reg_key
->missingKey
)
177 if (RegQueryInfoKey(reg_key
->hKey
, NULL
, NULL
, NULL
, &subkey_count
,
178 NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
)) {
179 derr
<< "Could not query registry key: " << SERVICE_REG_KEY
<< dendl
;
185 bool RegistryDiskIterator::get(Config
*cfg
)
190 if (!reg_key
->hKey
|| !subkey_count
) {
194 char subkey_name
[MAX_PATH
] = {0};
195 DWORD subkey_name_sz
= MAX_PATH
;
196 int err
= RegEnumKeyEx(
197 reg_key
->hKey
, index
, subkey_name
, &subkey_name_sz
,
198 NULL
, NULL
, NULL
, NULL
);
199 if (err
== ERROR_NO_MORE_ITEMS
) {
202 derr
<< "Could not enumerate registry. Error: " << err
<< dendl
;
207 if (load_mapping_config_from_registry(subkey_name
, cfg
)) {
215 // Iterate over all RBD mappings, getting info from the registry and the driver.
216 bool WNBDDiskIterator::get(Config
*cfg
)
220 bool found_active
= active_iterator
.get(cfg
);
222 active_devices
.insert(cfg
->devpath
);
226 error
= active_iterator
.get_error();
228 dout(5) << ": WNBD iterator error: " << error
<< dendl
;
232 while(registry_iterator
.get(cfg
)) {
233 if (active_devices
.find(cfg
->devpath
) != active_devices
.end()) {
234 // Skip active devices that were already yielded.
240 error
= registry_iterator
.get_error();
242 dout(5) << ": Registry iterator error: " << error
<< dendl
;
247 int get_exe_path(std::string
& path
) {
248 char buffer
[MAX_PATH
];
251 int ret
= GetModuleFileNameA(NULL
, buffer
, MAX_PATH
);
252 if (!ret
|| ret
== MAX_PATH
) {
253 err
= GetLastError();
254 derr
<< "Could not retrieve executable path. "
255 << "Error: " << win32_strerror(err
) << dendl
;
263 std::string
get_cli_args() {
264 std::ostringstream cmdline
;
265 for (int i
=1; i
<__argc
; i
++) {
268 cmdline
<< std::quoted(__argv
[i
]);
270 return cmdline
.str();
273 int send_map_request(std::string arguments
) {
274 dout(15) << __func__
<< ": command arguments: " << arguments
<< dendl
;
276 BYTE request_buff
[SERVICE_PIPE_BUFFSZ
] = { 0 };
277 ServiceRequest
* request
= (ServiceRequest
*) request_buff
;
278 request
->command
= Connect
;
280 (char*)request
->arguments
,
281 SERVICE_PIPE_BUFFSZ
- FIELD_OFFSET(ServiceRequest
, arguments
));
282 ServiceReply reply
= { 0 };
284 DWORD bytes_read
= 0;
285 BOOL success
= CallNamedPipe(
292 DEFAULT_MAP_TIMEOUT_MS
);
294 DWORD err
= GetLastError();
295 derr
<< "Could not send device map request. "
296 << "Make sure that the ceph service is running. "
297 << "Error: " << win32_strerror(err
) << dendl
;
301 derr
<< "The ceph service failed to map the image. Error: "
302 << reply
.status
<< dendl
;
308 // Spawn a subprocess using the specified "rbd-wnbd" command
309 // arguments. A pipe is passed to the child process,
310 // which will allow it to communicate the mapping status
311 int map_device_using_suprocess(std::string arguments
, int timeout_ms
)
314 PROCESS_INFORMATION pi
;
316 DWORD err
= 0, status
= 0;
318 std::ostringstream command_line
;
319 std::string exe_path
;
320 // Windows async IO context
321 OVERLAPPED connect_o
, read_o
;
322 HANDLE connect_event
= NULL
, read_event
= NULL
;
323 // Used for waiting on multiple events that are going to be initialized later.
324 HANDLE wait_events
[2] = { INVALID_HANDLE_VALUE
, INVALID_HANDLE_VALUE
};
325 DWORD bytes_read
= 0;
326 // We may get a command line containing an old pipe handle when
327 // recreating mappings, so we'll have to replace it.
328 std::regex
pipe_pattern("([\'\"]?--pipe-name[\'\"]? +[\'\"]?[^ ]+[\'\"]?)");
331 uuid
.generate_random();
332 std::ostringstream pipe_name
;
333 pipe_name
<< "\\\\.\\pipe\\rbd-wnbd-" << uuid
;
335 // Create an unique named pipe to communicate with the child. */
336 HANDLE pipe_handle
= CreateNamedPipe(
337 pipe_name
.str().c_str(),
338 PIPE_ACCESS_INBOUND
| FILE_FLAG_FIRST_PIPE_INSTANCE
|
339 FILE_FLAG_OVERLAPPED
,
341 1, // Only accept one instance
344 SERVICE_PIPE_TIMEOUT_MS
,
346 if (pipe_handle
== INVALID_HANDLE_VALUE
) {
347 err
= GetLastError();
348 derr
<< "CreateNamedPipe failed: " << win32_strerror(err
) << dendl
;
352 connect_event
= CreateEvent(0, TRUE
, FALSE
, NULL
);
353 read_event
= CreateEvent(0, TRUE
, FALSE
, NULL
);
354 if (!connect_event
|| !read_event
) {
355 err
= GetLastError();
356 derr
<< "CreateEvent failed: " << win32_strerror(err
) << dendl
;
360 connect_o
.hEvent
= connect_event
;
361 read_o
.hEvent
= read_event
;
363 status
= ConnectNamedPipe(pipe_handle
, &connect_o
);
364 err
= GetLastError();
365 if (status
|| err
!= ERROR_IO_PENDING
) {
368 derr
<< "ConnectNamedPipe failed: " << win32_strerror(err
) << dendl
;
374 dout(5) << __func__
<< ": command arguments: " << arguments
<< dendl
;
376 // We'll avoid running arbitrary commands, instead using the executable
377 // path of this process (expected to be the full rbd-wnbd.exe path).
378 err
= get_exe_path(exe_path
);
383 command_line
<< std::quoted(exe_path
)
384 << " " << std::regex_replace(arguments
, pipe_pattern
, "")
385 << " --pipe-name " << pipe_name
.str();
387 dout(5) << __func__
<< ": command line: " << command_line
.str() << dendl
;
390 // Create a detached child
391 if (!CreateProcess(NULL
, (char*)command_line
.str().c_str(),
392 NULL
, NULL
, FALSE
, DETACHED_PROCESS
,
393 NULL
, NULL
, &si
, &pi
)) {
394 err
= GetLastError();
395 derr
<< "CreateProcess failed: " << win32_strerror(err
) << dendl
;
400 wait_events
[0] = connect_event
;
401 wait_events
[1] = pi
.hProcess
;
402 status
= WaitForMultipleObjects(2, wait_events
, FALSE
, timeout_ms
);
405 if (!GetOverlappedResult(pipe_handle
, &connect_o
, &bytes_read
, TRUE
)) {
406 err
= GetLastError();
407 derr
<< "Couln't establish a connection with the child process. "
408 << "Error: " << win32_strerror(err
) << dendl
;
412 // We have an incoming connection.
414 case WAIT_OBJECT_0
+ 1:
415 // The process has exited prematurely.
418 derr
<< "Timed out waiting for child process connection." << dendl
;
421 derr
<< "Failed waiting for child process. Status: " << status
<< dendl
;
424 // Block and wait for child to say it is ready.
425 dout(5) << __func__
<< ": waiting for child notification." << dendl
;
426 if (!ReadFile(pipe_handle
, &ch
, 1, NULL
, &read_o
)) {
427 err
= GetLastError();
428 if (err
!= ERROR_IO_PENDING
) {
429 derr
<< "Receiving child process reply failed with: "
430 << win32_strerror(err
) << dendl
;
435 wait_events
[0] = read_event
;
436 wait_events
[1] = pi
.hProcess
;
437 // The RBD daemon is expected to write back right after opening the
438 // pipe. We'll use the same timeout value for now.
439 status
= WaitForMultipleObjects(2, wait_events
, FALSE
, timeout_ms
);
442 if (!GetOverlappedResult(pipe_handle
, &read_o
, &bytes_read
, TRUE
)) {
443 err
= GetLastError();
444 derr
<< "Receiving child process reply failed with: "
445 << win32_strerror(err
) << dendl
;
450 case WAIT_OBJECT_0
+ 1:
451 // The process has exited prematurely.
454 derr
<< "Timed out waiting for child process message." << dendl
;
457 derr
<< "Failed waiting for child process. Status: " << status
<< dendl
;
461 dout(5) << __func__
<< ": received child notification." << dendl
;
465 if (!is_process_running(pi
.dwProcessId
)) {
466 GetExitCodeProcess(pi
.hProcess
, (PDWORD
)&exit_code
);
467 derr
<< "Daemon failed with: " << cpp_strerror(exit_code
) << dendl
;
469 // The process closed the pipe without notifying us or exiting.
470 // This is quite unlikely, but we'll terminate the process.
471 dout(5) << "Terminating unresponsive process." << dendl
;
472 TerminateProcess(pi
.hProcess
, 1);
478 derr
<< "Could not start RBD daemon." << dendl
;
480 CloseHandle(pipe_handle
);
482 CloseHandle(connect_event
);
484 CloseHandle(read_event
);
488 BOOL WINAPI
console_handler_routine(DWORD dwCtrlType
)
490 dout(5) << "Received control signal: " << dwCtrlType
491 << ". Exiting." << dendl
;
493 std::unique_lock l
{shutdown_lock
};
500 int save_config_to_registry(Config
* cfg
)
502 std::string strKey
{ SERVICE_REG_KEY
};
504 strKey
.append(cfg
->devpath
);
505 auto reg_key
= RegistryKey(
506 g_ceph_context
, HKEY_LOCAL_MACHINE
, strKey
.c_str(), true);
512 // Registry writes are immediately available to other processes.
513 // Still, we'll do a flush to ensure that the mapping can be
514 // recreated after a system crash.
515 if (reg_key
.set("pid", getpid()) ||
516 reg_key
.set("devpath", cfg
->devpath
) ||
517 reg_key
.set("poolname", cfg
->poolname
) ||
518 reg_key
.set("nsname", cfg
->nsname
) ||
519 reg_key
.set("imgname", cfg
->imgname
) ||
520 reg_key
.set("snapname", cfg
->snapname
) ||
521 reg_key
.set("command_line", get_cli_args()) ||
522 reg_key
.set("persistent", cfg
->persistent
) ||
523 reg_key
.set("admin_sock_path", g_conf()->admin_socket
) ||
531 int remove_config_from_registry(Config
* cfg
)
533 std::string strKey
{ SERVICE_REG_KEY
};
535 strKey
.append(cfg
->devpath
);
536 return RegistryKey::remove(
537 g_ceph_context
, HKEY_LOCAL_MACHINE
, strKey
.c_str());
540 int load_mapping_config_from_registry(string devpath
, Config
* cfg
)
542 std::string strKey
{ SERVICE_REG_KEY
};
544 strKey
.append(devpath
);
545 auto reg_key
= RegistryKey(
546 g_ceph_context
, HKEY_LOCAL_MACHINE
, strKey
.c_str(), false);
548 if (reg_key
.missingKey
)
554 reg_key
.get("devpath", cfg
->devpath
);
555 reg_key
.get("poolname", cfg
->poolname
);
556 reg_key
.get("nsname", cfg
->nsname
);
557 reg_key
.get("imgname", cfg
->imgname
);
558 reg_key
.get("snapname", cfg
->snapname
);
559 reg_key
.get("command_line", cfg
->command_line
);
560 reg_key
.get("persistent", cfg
->persistent
);
561 reg_key
.get("admin_sock_path", cfg
->admin_sock_path
);
566 int restart_registered_mappings(
569 int image_map_timeout
)
572 WNBDDiskIterator iterator
;
575 int total_timeout_ms
= max(total_timeout
, total_timeout
* 1000);
576 int image_map_timeout_ms
= max(image_map_timeout
, image_map_timeout
* 1000);
578 LARGE_INTEGER start_t
, counter_freq
;
579 QueryPerformanceFrequency(&counter_freq
);
580 QueryPerformanceCounter(&start_t
);
582 boost::asio::thread_pool
pool(worker_count
);
583 while (iterator
.get(&cfg
)) {
584 if (cfg
.command_line
.empty()) {
585 derr
<< "Could not recreate mapping, missing command line: "
586 << cfg
.devpath
<< dendl
;
590 if (cfg
.wnbd_mapped
) {
591 dout(5) << __func__
<< ": device already mapped: "
592 << cfg
.devpath
<< dendl
;
595 if (!cfg
.persistent
) {
596 dout(5) << __func__
<< ": cleaning up non-persistent mapping: "
597 << cfg
.devpath
<< dendl
;
598 r
= remove_config_from_registry(&cfg
);
600 derr
<< __func__
<< ": could not clean up non-persistent mapping: "
601 << cfg
.devpath
<< dendl
;
606 boost::asio::post(pool
,
609 LARGE_INTEGER curr_t
, elapsed_ms
;
610 QueryPerformanceCounter(&curr_t
);
611 elapsed_ms
.QuadPart
= curr_t
.QuadPart
- start_t
.QuadPart
;
612 elapsed_ms
.QuadPart
*= 1000;
613 elapsed_ms
.QuadPart
/= counter_freq
.QuadPart
;
615 int time_left_ms
= max(
617 total_timeout_ms
- (int)elapsed_ms
.QuadPart
);
618 time_left_ms
= min(image_map_timeout_ms
, time_left_ms
);
624 dout(5) << "Remapping: " << cfg
.devpath
625 << ". Timeout: " << time_left_ms
<< " ms." << dendl
;
627 // We'll try to map all devices and return a non-zero value
628 // if any of them fails.
629 r
= map_device_using_suprocess(cfg
.command_line
, time_left_ms
);
632 derr
<< "Could not create mapping: "
633 << cfg
.devpath
<< ". Error: " << r
<< dendl
;
635 dout(5) << "Successfully remapped: " << cfg
.devpath
<< dendl
;
641 r
= iterator
.get_error();
643 derr
<< "Could not fetch all mappings. Error: " << r
<< dendl
;
650 int disconnect_all_mappings(
652 bool hard_disconnect
,
653 int soft_disconnect_timeout
,
656 // Although not generally recommended, soft_disconnect_timeout can be 0,
657 // which means infinite timeout.
658 ceph_assert(soft_disconnect_timeout
>= 0);
659 ceph_assert(worker_count
> 0);
660 int64_t timeout_ms
= soft_disconnect_timeout
* 1000;
663 WNBDActiveDiskIterator iterator
;
666 boost::asio::thread_pool
pool(worker_count
);
667 LARGE_INTEGER start_t
, counter_freq
;
668 QueryPerformanceFrequency(&counter_freq
);
669 QueryPerformanceCounter(&start_t
);
670 while (iterator
.get(&cfg
)) {
671 boost::asio::post(pool
,
674 LARGE_INTEGER curr_t
, elapsed_ms
;
675 QueryPerformanceCounter(&curr_t
);
676 elapsed_ms
.QuadPart
= curr_t
.QuadPart
- start_t
.QuadPart
;
677 elapsed_ms
.QuadPart
*= 1000;
678 elapsed_ms
.QuadPart
/= counter_freq
.QuadPart
;
680 int64_t time_left_ms
= max((int64_t)0, timeout_ms
- elapsed_ms
.QuadPart
);
682 cfg
.hard_disconnect
= hard_disconnect
|| !time_left_ms
;
683 cfg
.hard_disconnect_fallback
= true;
684 cfg
.soft_disconnect_timeout
= time_left_ms
/ 1000;
686 dout(5) << "Removing mapping: " << cfg
.devpath
687 << ". Timeout: " << cfg
.soft_disconnect_timeout
688 << "s. Hard disconnect: " << cfg
.hard_disconnect
691 r
= do_unmap(&cfg
, unregister
);
694 derr
<< "Could not remove mapping: " << cfg
.devpath
695 << ". Error: " << r
<< dendl
;
697 dout(5) << "Successfully removed mapping: " << cfg
.devpath
<< dendl
;
703 r
= iterator
.get_error();
705 derr
<< "Could not fetch all mappings. Error: " << r
<< dendl
;
712 class RBDService
: public ServiceBase
{
714 bool hard_disconnect
;
715 int soft_disconnect_timeout
;
717 int service_start_timeout
;
718 int image_map_timeout
;
719 bool remap_failure_fatal
;
722 RBDService(bool _hard_disconnect
,
723 int _soft_disconnect_timeout
,
725 int _service_start_timeout
,
726 int _image_map_timeout
,
727 bool _remap_failure_fatal
)
728 : ServiceBase(g_ceph_context
)
729 , hard_disconnect(_hard_disconnect
)
730 , soft_disconnect_timeout(_soft_disconnect_timeout
)
731 , thread_count(_thread_count
)
732 , service_start_timeout(_service_start_timeout
)
733 , image_map_timeout(_image_map_timeout
)
734 , remap_failure_fatal(_remap_failure_fatal
)
738 static int execute_command(ServiceRequest
* request
)
740 switch(request
->command
) {
742 dout(5) << "Received device connect request. Command line: "
743 << (char*)request
->arguments
<< dendl
;
744 // TODO: use the configured service map timeout.
745 // TODO: add ceph.conf options.
746 return map_device_using_suprocess(
747 (char*)request
->arguments
, DEFAULT_MAP_TIMEOUT_MS
);
749 dout(5) << "Received unsupported command: "
750 << request
->command
<< dendl
;
755 static DWORD
handle_connection(HANDLE pipe_handle
)
757 PBYTE message
[SERVICE_PIPE_BUFFSZ
] = { 0 };
758 DWORD bytes_read
= 0, bytes_written
= 0;
761 ServiceReply reply
= { 0 };
763 dout(20) << __func__
<< ": Receiving message." << dendl
;
764 BOOL success
= ReadFile(
765 pipe_handle
, message
, SERVICE_PIPE_BUFFSZ
,
767 if (!success
|| !bytes_read
) {
768 err
= GetLastError();
769 derr
<< "Could not read service command: "
770 << win32_strerror(err
) << dendl
;
774 dout(20) << __func__
<< ": Executing command." << dendl
;
775 reply
.status
= execute_command((ServiceRequest
*) message
);
776 reply_sz
= sizeof(reply
);
778 dout(20) << __func__
<< ": Sending reply. Status: "
779 << reply
.status
<< dendl
;
781 pipe_handle
, &reply
, reply_sz
, &bytes_written
, NULL
);
782 if (!success
|| reply_sz
!= bytes_written
) {
783 err
= GetLastError();
784 derr
<< "Could not send service command result: "
785 << win32_strerror(err
) << dendl
;
789 dout(20) << __func__
<< ": Cleaning up connection." << dendl
;
790 FlushFileBuffers(pipe_handle
);
791 DisconnectNamedPipe(pipe_handle
);
792 CloseHandle(pipe_handle
);
797 // We have to support Windows server 2016. Unix sockets only work on
798 // WS 2019, so we can't use the Ceph admin socket abstraction.
799 // Getting the Ceph admin sockets to work with Windows named pipes
800 // would require quite a few changes.
801 static DWORD
accept_pipe_connection() {
803 // We're currently using default ACLs, which grant full control to the
804 // LocalSystem account and administrator as well as the owner.
805 dout(20) << __func__
<< ": opening new pipe instance" << dendl
;
806 HANDLE pipe_handle
= CreateNamedPipe(
809 PIPE_TYPE_MESSAGE
| PIPE_READMODE_MESSAGE
| PIPE_WAIT
,
810 PIPE_UNLIMITED_INSTANCES
,
813 SERVICE_PIPE_TIMEOUT_MS
,
815 if (pipe_handle
== INVALID_HANDLE_VALUE
) {
816 err
= GetLastError();
817 derr
<< "CreatePipe failed: " << win32_strerror(err
) << dendl
;
821 dout(20) << __func__
<< ": waiting for connections." << dendl
;
822 BOOL connected
= ConnectNamedPipe(pipe_handle
, NULL
);
824 err
= GetLastError();
825 if (err
!= ERROR_PIPE_CONNECTED
) {
826 derr
<< "Pipe connection failed: " << win32_strerror(err
) << dendl
;
828 CloseHandle(pipe_handle
);
833 dout(20) << __func__
<< ": Connection received." << dendl
;
834 // We'll handle the connection in a separate thread and at the same time
835 // accept a new connection.
836 HANDLE handler_thread
= CreateThread(
837 NULL
, 0, (LPTHREAD_START_ROUTINE
) handle_connection
, pipe_handle
, 0, 0);
838 if (!handler_thread
) {
839 err
= GetLastError();
840 derr
<< "Could not start pipe connection handler thread: "
841 << win32_strerror(err
) << dendl
;
842 CloseHandle(pipe_handle
);
844 CloseHandle(handler_thread
);
850 static int pipe_server_loop(LPVOID arg
)
852 dout(5) << "Accepting admin pipe connections." << dendl
;
854 // This call will block until a connection is received, which will
855 // then be handled in a separate thread. The function returns, allowing
856 // us to accept another simultaneous connection.
857 accept_pipe_connection();
862 int create_pipe_server() {
863 HANDLE handler_thread
= CreateThread(
864 NULL
, 0, (LPTHREAD_START_ROUTINE
) pipe_server_loop
, NULL
, 0, 0);
867 if (!handler_thread
) {
868 err
= GetLastError();
869 derr
<< "Could not start pipe server: " << win32_strerror(err
) << dendl
;
871 CloseHandle(handler_thread
);
877 int run_hook() override
{
878 // Restart registered mappings before accepting new ones.
879 int r
= restart_registered_mappings(
880 thread_count
, service_start_timeout
, image_map_timeout
);
882 if (remap_failure_fatal
) {
883 derr
<< "Couldn't remap all images. Cleaning up." << dendl
;
886 dout(0) << "Ignoring image remap failure." << dendl
;
890 return create_pipe_server();
893 // Invoked when the service is requested to stop.
894 int stop_hook() override
{
895 return disconnect_all_mappings(
896 false, hard_disconnect
, soft_disconnect_timeout
, thread_count
);
898 // Invoked when the system is shutting down.
899 int shutdown_hook() override
{
906 const char* usage_str
=R
"(
907 Usage: rbd-wnbd [options] map <image-or-snap-spec> Map an image to wnbd device
908 [options] unmap <device|image-or-snap-spec> Unmap wnbd device
909 [options] list List mapped wnbd devices
910 [options] show <image-or-snap-spec> Show mapped wnbd device
911 stats <image-or-snap-spec> Show IO counters
912 [options] service Windows service entrypoint,
913 handling device lifecycle
916 --device <device path> Optional mapping unique identifier
917 --exclusive Forbid writes by other clients
918 --read-only Map read-only
919 --non-persistent Do not recreate the mapping when the Ceph service
920 restarts. By default, mappings are persistent
921 --io-req-workers The number of workers that dispatch IO requests.
923 --io-reply-workers The number of workers that dispatch IO replies.
927 --hard-disconnect Skip attempting a soft disconnect
928 --no-hard-disconnect-fallback Immediately return an error if the soft
929 disconnect fails instead of attempting a hard
930 disconnect as fallback
931 --soft-disconnect-timeout Soft disconnect timeout in seconds. The soft
932 disconnect operation uses PnP to notify the
933 Windows storage stack that the device is going to
934 be disconnectd. Storage drivers can block this
935 operation if there are pending operations,
936 unflushed caches or open handles. Default: 15
939 --hard-disconnect Skip attempting a soft disconnect
940 --soft-disconnect-timeout Cummulative soft disconnect timeout in seconds,
941 used when disconnecting existing mappings. A hard
942 disconnect will be issued when hitting the timeout
943 --service-thread-count The number of workers used when mapping or
944 unmapping images. Default: 8
945 --start-timeout The service start timeout in seconds. Default: 120
946 --map-timeout Individual image map timeout in seconds. Default: 20
947 --remap-failure-fatal If set, the service will stop when failing to remap
948 an image at start time, unmapping images that have
952 --format plain|json|xml Output format (default: plain)
953 --pretty-format Pretty formatting (json and xml)
956 --wnbd-log-level libwnbd.dll log level
960 std::cout
<< usage_str
;
961 generic_server_usage();
965 static Command cmd
= None
;
967 int construct_devpath_if_missing(Config
* cfg
)
969 // Windows doesn't allow us to request specific disk paths when mapping an
970 // image. This will just be used by rbd-wnbd and wnbd as an identifier.
971 if (cfg
->devpath
.empty()) {
972 if (cfg
->imgname
.empty()) {
973 derr
<< "Missing image name." << dendl
;
977 if (!cfg
->poolname
.empty()) {
978 cfg
->devpath
+= cfg
->poolname
;
981 if (!cfg
->nsname
.empty()) {
982 cfg
->devpath
+= cfg
->nsname
;
986 cfg
->devpath
+= cfg
->imgname
;
988 if (!cfg
->snapname
.empty()) {
990 cfg
->devpath
+= cfg
->snapname
;
997 boost::intrusive_ptr
<CephContext
> do_global_init(
998 int argc
, const char *argv
[], Config
*cfg
)
1000 std::vector
<const char*> args
;
1001 argv_to_vec(argc
, argv
, args
);
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
[])
1597 std::vector
<const char*> args
;
1598 argv_to_vec(argc
, argv
, args
);
1600 // Avoid using dout before calling "do_global_init"
1602 std::cout
<< argv
[0] << ": -h or --help for usage" << std::endl
;
1606 std::ostringstream err_msg
;
1607 r
= parse_args(args
, &err_msg
, &cmd
, &cfg
);
1608 if (r
== HELP_INFO
) {
1611 } else if (r
== VERSION_INFO
) {
1612 std::cout
<< pretty_version_to_str() << std::endl
;
1615 std::cout
<< err_msg
.str() << std::endl
;
1619 auto cct
= do_global_init(argc
, argv
, &cfg
);
1621 WnbdSetLogger(WnbdHandler::LogMessage
);
1622 WnbdSetLogLevel(cfg
.wnbd_log_level
);
1626 if (construct_devpath_if_missing(&cfg
)) {
1634 if (construct_devpath_if_missing(&cfg
)) {
1637 r
= do_unmap(&cfg
, true);
1642 r
= do_list_mapped_devices(cfg
.format
, cfg
.pretty_format
);
1647 if (construct_devpath_if_missing(&cfg
)) {
1650 r
= do_show_mapped_device(cfg
.format
, cfg
.pretty_format
, cfg
.devpath
);
1656 RBDService
service(cfg
.hard_disconnect
, cfg
.soft_disconnect_timeout
,
1657 cfg
.service_thread_count
,
1658 cfg
.service_start_timeout
,
1659 cfg
.image_map_timeout
,
1660 cfg
.remap_failure_fatal
);
1661 // This call will block until the service stops.
1662 r
= RBDService::initialize(&service
);
1668 if (construct_devpath_if_missing(&cfg
)) {
1671 return do_stats(cfg
.devpath
);
1680 int main(int argc
, const char *argv
[])
1682 SetConsoleCtrlHandler(console_handler_routine
, true);
1683 // Avoid the Windows Error Reporting dialog.
1684 SetErrorMode(GetErrorMode() | SEM_NOGPFAULTERRORBOX
);
1685 int r
= rbd_wnbd(argc
, argv
);