]> git.proxmox.com Git - ceph.git/blob - ceph/src/tools/rbd_wnbd/rbd_wnbd.cc
update source to Ceph Pacific 16.2.2
[ceph.git] / ceph / src / tools / rbd_wnbd / rbd_wnbd.cc
1 /*
2 * rbd-wnbd - RBD in userspace
3 *
4 * Copyright (C) 2020 SUSE LINUX GmbH
5 *
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.
10 *
11 */
12
13 #include "include/int_types.h"
14
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <stddef.h>
18 #include <errno.h>
19 #include <sys/types.h>
20 #include <sys/socket.h>
21 #include <unistd.h>
22
23 #include <boost/locale/encoding_utf.hpp>
24
25 #include "wnbd_handler.h"
26 #include "rbd_wnbd.h"
27
28 #include <fstream>
29 #include <memory>
30 #include <regex>
31
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"
42
43 #include "global/global_init.h"
44
45 #include "include/uuid.h"
46 #include "include/rados/librados.hpp"
47 #include "include/rbd/librbd.hpp"
48
49 #include <shellapi.h>
50
51 #define dout_context g_ceph_context
52 #define dout_subsys ceph_subsys_rbd
53 #undef dout_prefix
54 #define dout_prefix *_dout << "rbd-wnbd: "
55
56 using boost::locale::conv::utf_to_utf;
57
58 std::wstring to_wstring(const std::string& str)
59 {
60 return utf_to_utf<wchar_t>(str.c_str(), str.c_str() + str.size());
61 }
62
63 std::string to_string(const std::wstring& str)
64 {
65 return utf_to_utf<char>(str.c_str(), str.c_str() + str.size());
66 }
67
68 bool is_process_running(DWORD pid)
69 {
70 HANDLE process = OpenProcess(SYNCHRONIZE, FALSE, pid);
71 DWORD ret = WaitForSingleObject(process, 0);
72 CloseHandle(process);
73 return ret == WAIT_TIMEOUT;
74 }
75
76 DWORD WNBDActiveDiskIterator::fetch_list(
77 PWNBD_CONNECTION_LIST* conn_list)
78 {
79 DWORD curr_buff_sz = 0;
80 DWORD buff_sz = 0;
81 DWORD err = 0;
82 PWNBD_CONNECTION_LIST tmp_list = NULL;
83
84 // We're using a loop because other connections may show up by the time
85 // we retry.
86 do {
87 if (tmp_list)
88 free(tmp_list);
89
90 if (buff_sz) {
91 tmp_list = (PWNBD_CONNECTION_LIST) calloc(1, buff_sz);
92 if (!tmp_list) {
93 derr << "Could not allocate " << buff_sz << " bytes." << dendl;
94 err = ERROR_NOT_ENOUGH_MEMORY;
95 break;
96 }
97 }
98
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);
104 if (err)
105 break;
106 } while (curr_buff_sz < buff_sz);
107
108 if (err) {
109 if (tmp_list)
110 free(tmp_list);
111 } else {
112 *conn_list = tmp_list;
113 }
114 return err;
115 }
116
117 WNBDActiveDiskIterator::WNBDActiveDiskIterator()
118 {
119 DWORD status = WNBDActiveDiskIterator::fetch_list(&conn_list);
120 if (status) {
121 error = EINVAL;
122 }
123 }
124
125 WNBDActiveDiskIterator::~WNBDActiveDiskIterator()
126 {
127 if (conn_list) {
128 free(conn_list);
129 conn_list = NULL;
130 }
131 }
132
133 bool WNBDActiveDiskIterator::get(Config *cfg)
134 {
135 index += 1;
136 *cfg = Config();
137
138 if (!conn_list || index >= (int)conn_list->Count) {
139 return false;
140 }
141
142 auto conn_info = conn_list->Connections[index];
143 auto conn_props = conn_info.Properties;
144
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);
149 }
150
151 error = load_mapping_config_from_registry(conn_props.InstanceName, cfg);
152 if (error) {
153 derr << "Could not load registry disk info for: "
154 << conn_props.InstanceName << ". Error: " << error << dendl;
155 return false;
156 }
157
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;
163
164 return true;
165 }
166
167 RegistryDiskIterator::RegistryDiskIterator()
168 {
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)
173 error = EINVAL;
174 return;
175 }
176
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;
180 error = EINVAL;
181 return;
182 }
183 }
184
185 bool RegistryDiskIterator::get(Config *cfg)
186 {
187 index += 1;
188 *cfg = Config();
189
190 if (!reg_key->hKey || !subkey_count) {
191 return false;
192 }
193
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) {
200 return false;
201 } else if (err) {
202 derr << "Could not enumerate registry. Error: " << err << dendl;
203 error = EINVAL;
204 return false;
205 }
206
207 if (load_mapping_config_from_registry(subkey_name, cfg)) {
208 error = EINVAL;
209 return false;
210 };
211
212 return true;
213 }
214
215 // Iterate over all RBD mappings, getting info from the registry and the driver.
216 bool WNBDDiskIterator::get(Config *cfg)
217 {
218 *cfg = Config();
219
220 bool found_active = active_iterator.get(cfg);
221 if (found_active) {
222 active_devices.insert(cfg->devpath);
223 return true;
224 }
225
226 error = active_iterator.get_error();
227 if (error) {
228 dout(5) << ": WNBD iterator error: " << error << dendl;
229 return false;
230 }
231
232 while(registry_iterator.get(cfg)) {
233 if (active_devices.find(cfg->devpath) != active_devices.end()) {
234 // Skip active devices that were already yielded.
235 continue;
236 }
237 return true;
238 }
239
240 error = registry_iterator.get_error();
241 if (error) {
242 dout(5) << ": Registry iterator error: " << error << dendl;
243 }
244 return false;
245 }
246
247 int get_exe_path(std::string& path) {
248 char buffer[MAX_PATH];
249 DWORD err = 0;
250
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;
256 return -EINVAL;
257 }
258
259 path = buffer;
260 return 0;
261 }
262
263 std::string get_cli_args() {
264 std::ostringstream cmdline;
265 for (int i=1; i<__argc; i++) {
266 if (i > 1)
267 cmdline << " ";
268 cmdline << std::quoted(__argv[i]);
269 }
270 return cmdline.str();
271 }
272
273 int send_map_request(std::string arguments) {
274 dout(15) << __func__ << ": command arguments: " << arguments << dendl;
275
276 BYTE request_buff[SERVICE_PIPE_BUFFSZ] = { 0 };
277 ServiceRequest* request = (ServiceRequest*) request_buff;
278 request->command = Connect;
279 arguments.copy(
280 (char*)request->arguments,
281 SERVICE_PIPE_BUFFSZ - FIELD_OFFSET(ServiceRequest, arguments));
282 ServiceReply reply = { 0 };
283
284 DWORD bytes_read = 0;
285 BOOL success = CallNamedPipe(
286 SERVICE_PIPE_NAME,
287 request_buff,
288 SERVICE_PIPE_BUFFSZ,
289 &reply,
290 sizeof(reply),
291 &bytes_read,
292 DEFAULT_MAP_TIMEOUT_MS);
293 if (!success) {
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;
298 return -EINVAL;
299 }
300 if (reply.status) {
301 derr << "The ceph service failed to map the image. Error: "
302 << reply.status << dendl;
303 }
304
305 return reply.status;
306 }
307
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)
312 {
313 STARTUPINFO si;
314 PROCESS_INFORMATION pi;
315 char ch;
316 DWORD err = 0, status = 0;
317 int exit_code = 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[\'\"]? +[\'\"]?[^ ]+[\'\"]?)");
329
330 uuid_d uuid;
331 uuid.generate_random();
332 std::ostringstream pipe_name;
333 pipe_name << "\\\\.\\pipe\\rbd-wnbd-" << uuid;
334
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,
340 PIPE_WAIT,
341 1, // Only accept one instance
342 SERVICE_PIPE_BUFFSZ,
343 SERVICE_PIPE_BUFFSZ,
344 SERVICE_PIPE_TIMEOUT_MS,
345 NULL);
346 if (pipe_handle == INVALID_HANDLE_VALUE) {
347 err = GetLastError();
348 derr << "CreateNamedPipe failed: " << win32_strerror(err) << dendl;
349 exit_code = -ECHILD;
350 goto finally;
351 }
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;
357 exit_code = -ECHILD;
358 goto finally;
359 }
360 connect_o.hEvent = connect_event;
361 read_o.hEvent = read_event;
362
363 status = ConnectNamedPipe(pipe_handle, &connect_o);
364 err = GetLastError();
365 if (status || err != ERROR_IO_PENDING) {
366 if (status)
367 err = status;
368 derr << "ConnectNamedPipe failed: " << win32_strerror(err) << dendl;
369 exit_code = -ECHILD;
370 goto finally;
371 }
372 err = 0;
373
374 dout(5) << __func__ << ": command arguments: " << arguments << dendl;
375
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);
379 if (err) {
380 exit_code = -EINVAL;
381 goto finally;
382 }
383 command_line << std::quoted(exe_path)
384 << " " << std::regex_replace(arguments, pipe_pattern, "")
385 << " --pipe-name " << pipe_name.str();
386
387 dout(5) << __func__ << ": command line: " << command_line.str() << dendl;
388
389 GetStartupInfo(&si);
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;
396 exit_code = -ECHILD;
397 goto finally;
398 }
399
400 wait_events[0] = connect_event;
401 wait_events[1] = pi.hProcess;
402 status = WaitForMultipleObjects(2, wait_events, FALSE, timeout_ms);
403 switch(status) {
404 case WAIT_OBJECT_0:
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;
409 exit_code = -ECHILD;
410 goto clean_process;
411 }
412 // We have an incoming connection.
413 break;
414 case WAIT_OBJECT_0 + 1:
415 // The process has exited prematurely.
416 goto clean_process;
417 case WAIT_TIMEOUT:
418 derr << "Timed out waiting for child process connection." << dendl;
419 goto clean_process;
420 default:
421 derr << "Failed waiting for child process. Status: " << status << dendl;
422 goto clean_process;
423 }
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;
431 exit_code = -ECHILD;
432 goto clean_process;
433 }
434 }
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);
440 switch(status) {
441 case WAIT_OBJECT_0:
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;
446 exit_code = -ECHILD;
447 goto clean_process;
448 }
449 break;
450 case WAIT_OBJECT_0 + 1:
451 // The process has exited prematurely.
452 goto clean_process;
453 case WAIT_TIMEOUT:
454 derr << "Timed out waiting for child process message." << dendl;
455 goto clean_process;
456 default:
457 derr << "Failed waiting for child process. Status: " << status << dendl;
458 goto clean_process;
459 }
460
461 dout(5) << __func__ << ": received child notification." << dendl;
462 goto finally;
463
464 clean_process:
465 if (!is_process_running(pi.dwProcessId)) {
466 GetExitCodeProcess(pi.hProcess, (PDWORD)&exit_code);
467 derr << "Daemon failed with: " << cpp_strerror(exit_code) << dendl;
468 } else {
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);
473 exit_code = -EINVAL;
474 }
475
476 finally:
477 if (exit_code)
478 derr << "Could not start RBD daemon." << dendl;
479 if (pipe_handle)
480 CloseHandle(pipe_handle);
481 if (connect_event)
482 CloseHandle(connect_event);
483 if (read_event)
484 CloseHandle(read_event);
485 return exit_code;
486 }
487
488 BOOL WINAPI console_handler_routine(DWORD dwCtrlType)
489 {
490 dout(5) << "Received control signal: " << dwCtrlType
491 << ". Exiting." << dendl;
492
493 std::unique_lock l{shutdown_lock};
494 if (handler)
495 handler->shutdown();
496
497 return true;
498 }
499
500 int save_config_to_registry(Config* cfg)
501 {
502 std::string strKey{ SERVICE_REG_KEY };
503 strKey.append("\\");
504 strKey.append(cfg->devpath);
505 auto reg_key = RegistryKey(
506 g_ceph_context, HKEY_LOCAL_MACHINE, strKey.c_str(), true);
507 if (!reg_key.hKey) {
508 return -EINVAL;
509 }
510
511 int ret_val = 0;
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) ||
524 reg_key.flush()) {
525 ret_val = -EINVAL;
526 }
527
528 return ret_val;
529 }
530
531 int remove_config_from_registry(Config* cfg)
532 {
533 std::string strKey{ SERVICE_REG_KEY };
534 strKey.append("\\");
535 strKey.append(cfg->devpath);
536 return RegistryKey::remove(
537 g_ceph_context, HKEY_LOCAL_MACHINE, strKey.c_str());
538 }
539
540 int load_mapping_config_from_registry(string devpath, Config* cfg)
541 {
542 std::string strKey{ SERVICE_REG_KEY };
543 strKey.append("\\");
544 strKey.append(devpath);
545 auto reg_key = RegistryKey(
546 g_ceph_context, HKEY_LOCAL_MACHINE, strKey.c_str(), false);
547 if (!reg_key.hKey) {
548 if (reg_key.missingKey)
549 return -ENOENT;
550 else
551 return -EINVAL;
552 }
553
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);
562
563 return 0;
564 }
565
566 int restart_registered_mappings(
567 int worker_count,
568 int total_timeout,
569 int image_map_timeout)
570 {
571 Config cfg;
572 WNBDDiskIterator iterator;
573 int err = 0, r;
574
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);
577
578 LARGE_INTEGER start_t, counter_freq;
579 QueryPerformanceFrequency(&counter_freq);
580 QueryPerformanceCounter(&start_t);
581
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;
587 err = -EINVAL;
588 continue;
589 }
590 if (cfg.wnbd_mapped) {
591 dout(5) << __func__ << ": device already mapped: "
592 << cfg.devpath << dendl;
593 continue;
594 }
595 if (!cfg.persistent) {
596 dout(5) << __func__ << ": cleaning up non-persistent mapping: "
597 << cfg.devpath << dendl;
598 r = remove_config_from_registry(&cfg);
599 if (r) {
600 derr << __func__ << ": could not clean up non-persistent mapping: "
601 << cfg.devpath << dendl;
602 }
603 continue;
604 }
605
606 boost::asio::post(pool,
607 [&, cfg]() mutable
608 {
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;
614
615 int time_left_ms = max(
616 0,
617 total_timeout_ms - (int)elapsed_ms.QuadPart);
618 time_left_ms = min(image_map_timeout_ms, time_left_ms);
619 if (!time_left_ms) {
620 err = -ETIMEDOUT;
621 return;
622 }
623
624 dout(5) << "Remapping: " << cfg.devpath
625 << ". Timeout: " << time_left_ms << " ms." << dendl;
626
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);
630 if (r) {
631 err = r;
632 derr << "Could not create mapping: "
633 << cfg.devpath << ". Error: " << r << dendl;
634 } else {
635 dout(5) << "Successfully remapped: " << cfg.devpath << dendl;
636 }
637 });
638 }
639 pool.join();
640
641 r = iterator.get_error();
642 if (r) {
643 derr << "Could not fetch all mappings. Error: " << r << dendl;
644 err = r;
645 }
646
647 return err;
648 }
649
650 int disconnect_all_mappings(
651 bool unregister,
652 bool hard_disconnect,
653 int soft_disconnect_timeout,
654 int worker_count)
655 {
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;
661
662 Config cfg;
663 WNBDActiveDiskIterator iterator;
664 int err = 0, r;
665
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,
672 [&, cfg]() mutable
673 {
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;
679
680 int64_t time_left_ms = max((int64_t)0, timeout_ms - elapsed_ms.QuadPart);
681
682 cfg.hard_disconnect = hard_disconnect || !time_left_ms;
683 cfg.hard_disconnect_fallback = true;
684 cfg.soft_disconnect_timeout = time_left_ms / 1000;
685
686 dout(5) << "Removing mapping: " << cfg.devpath
687 << ". Timeout: " << cfg.soft_disconnect_timeout
688 << "s. Hard disconnect: " << cfg.hard_disconnect
689 << dendl;
690
691 r = do_unmap(&cfg, unregister);
692 if (r) {
693 err = r;
694 derr << "Could not remove mapping: " << cfg.devpath
695 << ". Error: " << r << dendl;
696 } else {
697 dout(5) << "Successfully removed mapping: " << cfg.devpath << dendl;
698 }
699 });
700 }
701 pool.join();
702
703 r = iterator.get_error();
704 if (r) {
705 derr << "Could not fetch all mappings. Error: " << r << dendl;
706 err = r;
707 }
708
709 return err;
710 }
711
712 class RBDService : public ServiceBase {
713 private:
714 bool hard_disconnect;
715 int soft_disconnect_timeout;
716 int thread_count;
717 int service_start_timeout;
718 int image_map_timeout;
719 bool remap_failure_fatal;
720
721 public:
722 RBDService(bool _hard_disconnect,
723 int _soft_disconnect_timeout,
724 int _thread_count,
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)
735 {
736 }
737
738 static int execute_command(ServiceRequest* request)
739 {
740 switch(request->command) {
741 case Connect:
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);
748 default:
749 dout(5) << "Received unsupported command: "
750 << request->command << dendl;
751 return -ENOSYS;
752 }
753 }
754
755 static DWORD handle_connection(HANDLE pipe_handle)
756 {
757 PBYTE message[SERVICE_PIPE_BUFFSZ] = { 0 };
758 DWORD bytes_read = 0, bytes_written = 0;
759 DWORD err = 0;
760 DWORD reply_sz = 0;
761 ServiceReply reply = { 0 };
762
763 dout(20) << __func__ << ": Receiving message." << dendl;
764 BOOL success = ReadFile(
765 pipe_handle, message, SERVICE_PIPE_BUFFSZ,
766 &bytes_read, NULL);
767 if (!success || !bytes_read) {
768 err = GetLastError();
769 derr << "Could not read service command: "
770 << win32_strerror(err) << dendl;
771 goto exit;
772 }
773
774 dout(20) << __func__ << ": Executing command." << dendl;
775 reply.status = execute_command((ServiceRequest*) message);
776 reply_sz = sizeof(reply);
777
778 dout(20) << __func__ << ": Sending reply. Status: "
779 << reply.status << dendl;
780 success = WriteFile(
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;
786 }
787
788 exit:
789 dout(20) << __func__ << ": Cleaning up connection." << dendl;
790 FlushFileBuffers(pipe_handle);
791 DisconnectNamedPipe(pipe_handle);
792 CloseHandle(pipe_handle);
793
794 return err;
795 }
796
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() {
802 DWORD err = 0;
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(
807 SERVICE_PIPE_NAME,
808 PIPE_ACCESS_DUPLEX,
809 PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
810 PIPE_UNLIMITED_INSTANCES,
811 SERVICE_PIPE_BUFFSZ,
812 SERVICE_PIPE_BUFFSZ,
813 SERVICE_PIPE_TIMEOUT_MS,
814 NULL);
815 if (pipe_handle == INVALID_HANDLE_VALUE) {
816 err = GetLastError();
817 derr << "CreatePipe failed: " << win32_strerror(err) << dendl;
818 return -EINVAL;
819 }
820
821 dout(20) << __func__ << ": waiting for connections." << dendl;
822 BOOL connected = ConnectNamedPipe(pipe_handle, NULL);
823 if (!connected) {
824 err = GetLastError();
825 if (err != ERROR_PIPE_CONNECTED) {
826 derr << "Pipe connection failed: " << win32_strerror(err) << dendl;
827
828 CloseHandle(pipe_handle);
829 return err;
830 }
831 }
832
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);
843 } else {
844 CloseHandle(handler_thread);
845 }
846
847 return err;
848 }
849
850 static int pipe_server_loop(LPVOID arg)
851 {
852 dout(5) << "Accepting admin pipe connections." << dendl;
853 while (1) {
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();
858 }
859 return 0;
860 }
861
862 int create_pipe_server() {
863 HANDLE handler_thread = CreateThread(
864 NULL, 0, (LPTHREAD_START_ROUTINE) pipe_server_loop, NULL, 0, 0);
865 DWORD err = 0;
866
867 if (!handler_thread) {
868 err = GetLastError();
869 derr << "Could not start pipe server: " << win32_strerror(err) << dendl;
870 } else {
871 CloseHandle(handler_thread);
872 }
873
874 return err;
875 }
876
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);
881 if (r) {
882 if (remap_failure_fatal) {
883 derr << "Couldn't remap all images. Cleaning up." << dendl;
884 return r;
885 } else {
886 dout(0) << "Ignoring image remap failure." << dendl;
887 }
888 }
889
890 return create_pipe_server();
891 }
892
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);
897 }
898 // Invoked when the system is shutting down.
899 int shutdown_hook() override {
900 return stop_hook();
901 }
902 };
903
904 static void usage()
905 {
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
914
915 Map options:
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.
922 Default: 4
923 --io-reply-workers The number of workers that dispatch IO replies.
924 Default: 4
925
926 Unmap options:
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
937
938 Service options:
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
949 been mapped so far.
950
951 Show|List options:
952 --format plain|json|xml Output format (default: plain)
953 --pretty-format Pretty formatting (json and xml)
954
955 Common options:
956 --wnbd-log-level libwnbd.dll log level
957
958 )";
959
960 std::cout << usage_str;
961 generic_server_usage();
962 }
963
964
965 static Command cmd = None;
966
967 int construct_devpath_if_missing(Config* cfg)
968 {
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;
974 return -EINVAL;
975 }
976
977 if (!cfg->poolname.empty()) {
978 cfg->devpath += cfg->poolname;
979 cfg->devpath += '/';
980 }
981 if (!cfg->nsname.empty()) {
982 cfg->devpath += cfg->nsname;
983 cfg->devpath += '/';
984 }
985
986 cfg->devpath += cfg->imgname;
987
988 if (!cfg->snapname.empty()) {
989 cfg->devpath += '@';
990 cfg->devpath += cfg->snapname;
991 }
992 }
993
994 return 0;
995 }
996
997 boost::intrusive_ptr<CephContext> do_global_init(
998 int argc, const char *argv[], Config *cfg)
999 {
1000 std::vector<const char*> args;
1001 argv_to_vec(argc, argv, args);
1002
1003 code_environment_t code_env;
1004 int flags;
1005
1006 switch(cmd) {
1007 case Connect:
1008 code_env = CODE_ENVIRONMENT_DAEMON;
1009 flags = CINIT_FLAG_UNPRIVILEGED_DAEMON_DEFAULTS;
1010 break;
1011 case Service:
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;
1016 break;
1017 default:
1018 code_env = CODE_ENVIRONMENT_UTILITY;
1019 flags = CINIT_FLAG_NO_MON_CONFIG;
1020 break;
1021 }
1022
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;
1028 }
1029 auto cct = global_init(NULL, args, CEPH_ENTITY_TYPE_CLIENT,
1030 code_env, flags, FALSE);
1031
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);
1035
1036 return cct;
1037 }
1038
1039 static int do_map(Config *cfg)
1040 {
1041 int r;
1042
1043 librados::Rados rados;
1044 librbd::RBD rbd;
1045 librados::IoCtx io_ctx;
1046 librbd::Image image;
1047 librbd::image_info_t info;
1048 HANDLE parent_pipe_handle = INVALID_HANDLE_VALUE;
1049 int err = 0;
1050
1051 if (g_conf()->daemonize && cfg->parent_pipe.empty()) {
1052 return send_map_request(get_cli_args());
1053 }
1054
1055 dout(0) << "Mapping RBD image: " << cfg->devpath << dendl;
1056
1057 r = rados.init_with_context(g_ceph_context);
1058 if (r < 0) {
1059 derr << "rbd-wnbd: couldn't initialize rados: " << cpp_strerror(r)
1060 << dendl;
1061 goto close_ret;
1062 }
1063
1064 r = rados.connect();
1065 if (r < 0) {
1066 derr << "rbd-wnbd: couldn't connect to rados: " << cpp_strerror(r)
1067 << dendl;
1068 goto close_ret;
1069 }
1070
1071 r = rados.ioctx_create(cfg->poolname.c_str(), io_ctx);
1072 if (r < 0) {
1073 derr << "rbd-wnbd: couldn't create IO context: " << cpp_strerror(r)
1074 << dendl;
1075 goto close_ret;
1076 }
1077
1078 io_ctx.set_namespace(cfg->nsname);
1079
1080 r = rbd.open(io_ctx, image, cfg->imgname.c_str());
1081 if (r < 0) {
1082 derr << "rbd-wnbd: couldn't open rbd image: " << cpp_strerror(r)
1083 << dendl;
1084 goto close_ret;
1085 }
1086
1087 if (cfg->exclusive) {
1088 r = image.lock_acquire(RBD_LOCK_MODE_EXCLUSIVE);
1089 if (r < 0) {
1090 derr << "rbd-wnbd: failed to acquire exclusive lock: " << cpp_strerror(r)
1091 << dendl;
1092 goto close_ret;
1093 }
1094 }
1095
1096 if (!cfg->snapname.empty()) {
1097 r = image.snap_set(cfg->snapname.c_str());
1098 if (r < 0) {
1099 derr << "rbd-wnbd: couldn't use snapshot: " << cpp_strerror(r)
1100 << dendl;
1101 goto close_ret;
1102 }
1103 }
1104
1105 r = image.stat(info, sizeof(info));
1106 if (r < 0)
1107 goto close_ret;
1108
1109 if (info.size > _UI64_MAX) {
1110 r = -EFBIG;
1111 derr << "rbd-wnbd: image is too large (" << byte_u_t(info.size)
1112 << ", max is " << byte_u_t(_UI64_MAX) << ")" << dendl;
1113 goto close_ret;
1114 }
1115
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);
1122 if (r < 0)
1123 goto close_ret;
1124
1125 handler = new WnbdHandler(image, cfg->devpath,
1126 info.size / RBD_WNBD_BLKSIZE,
1127 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();
1133 if (r) {
1134 r = r == ERROR_ALREADY_EXISTS ? -EEXIST : -EINVAL;
1135 goto close_ret;
1136 }
1137
1138 // We're informing the parent processes that the initialization
1139 // was successful.
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;
1152 } else {
1153 dout(5) << __func__ << ": submitted parent notification." << dendl;
1154 }
1155
1156 if (parent_pipe_handle != INVALID_HANDLE_VALUE)
1157 CloseHandle(parent_pipe_handle);
1158
1159 global_init_postfork_finish(g_ceph_context);
1160 }
1161
1162 handler->wait();
1163 handler->shutdown();
1164
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);
1170 if (r) {
1171 derr << __func__ << ": could not clean up non-persistent mapping: "
1172 << cfg->devpath << dendl;
1173 }
1174 }
1175
1176 close_ret:
1177 std::unique_lock l{shutdown_lock};
1178
1179 image.close();
1180 io_ctx.close();
1181 rados.shutdown();
1182 if (handler) {
1183 delete handler;
1184 handler = nullptr;
1185 }
1186
1187 return r;
1188 }
1189
1190 static int do_unmap(Config *cfg, bool unregister)
1191 {
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;
1197
1198 int err = WnbdRemoveEx(cfg->devpath.c_str(), &remove_options);
1199 if (err && err != ERROR_FILE_NOT_FOUND) {
1200 return -EINVAL;
1201 }
1202
1203 if (unregister) {
1204 err = remove_config_from_registry(cfg);
1205 if (err) {
1206 derr << "rbd-wnbd: failed to unregister device: "
1207 << cfg->devpath << ". Error: " << err << dendl;
1208 return -EINVAL;
1209 }
1210 }
1211 return 0;
1212 }
1213
1214 static int parse_imgpath(const std::string &imgpath, Config *cfg,
1215 std::ostream *err_msg)
1216 {
1217 std::regex pattern("^(?:([^/]+)/(?:([^/@]+)/)?)?([^@]+)(?:@([^/@]+))?$");
1218 std::smatch match;
1219 if (!std::regex_match(imgpath, match, pattern)) {
1220 derr << "rbd-wnbd: invalid spec '" << imgpath << "'" << dendl;
1221 return -EINVAL;
1222 }
1223
1224 if (match[1].matched) {
1225 cfg->poolname = match[1];
1226 }
1227
1228 if (match[2].matched) {
1229 cfg->nsname = match[2];
1230 }
1231
1232 cfg->imgname = match[3];
1233
1234 if (match[4].matched)
1235 cfg->snapname = match[4];
1236
1237 return 0;
1238 }
1239
1240 static int do_list_mapped_devices(const std::string &format, bool pretty_format)
1241 {
1242 std::unique_ptr<ceph::Formatter> f;
1243 TextTable tbl;
1244
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;
1251 return -EINVAL;
1252 }
1253
1254 if (f) {
1255 f->open_array_section("devices");
1256 } else {
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);
1265 }
1266
1267 Config cfg;
1268 WNBDDiskIterator wnbd_disk_iterator;
1269
1270 while (wnbd_disk_iterator.get(&cfg)) {
1271 const char* status = cfg.active ?
1272 WNBD_STATUS_ACTIVE : WNBD_STATUS_INACTIVE;
1273
1274 if (f) {
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);
1284 f->close_section();
1285 } else {
1286 if (cfg.snapname.empty()) {
1287 cfg.snapname = "-";
1288 }
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;
1292 }
1293 }
1294 int error = wnbd_disk_iterator.get_error();
1295 if (error) {
1296 derr << "Could not get disk list: " << error << dendl;
1297 return -error;
1298 }
1299
1300 if (f) {
1301 f->close_section();
1302 f->flush(std::cout);
1303 } else {
1304 std::cout << tbl;
1305 }
1306
1307 return 0;
1308 }
1309
1310 static int do_show_mapped_device(std::string format, bool pretty_format,
1311 std::string devpath)
1312 {
1313 std::unique_ptr<ceph::Formatter> f;
1314 TextTable tbl;
1315
1316 if (format.empty() || format == "plain") {
1317 format = "json";
1318 pretty_format = true;
1319 }
1320 if (format == "json") {
1321 f.reset(new JSONFormatter(pretty_format));
1322 } else if (format == "xml") {
1323 f.reset(new XMLFormatter(pretty_format));
1324 } else {
1325 derr << "rbd-wnbd: invalid output format: " << format << dendl;
1326 return -EINVAL;
1327 }
1328
1329 Config cfg;
1330 int error = load_mapping_config_from_registry(devpath, &cfg);
1331 if (error) {
1332 derr << "Could not load registry disk info for: "
1333 << devpath << ". Error: " << error << dendl;
1334 return error;
1335 }
1336
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) {
1342 return -EINVAL;
1343 }
1344
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);
1362 f->close_section();
1363 f->flush(std::cout);
1364
1365 return 0;
1366 }
1367
1368 static int do_stats(std::string search_devpath)
1369 {
1370 Config cfg;
1371 WNBDDiskIterator wnbd_disk_iterator;
1372
1373 while (wnbd_disk_iterator.get(&cfg)) {
1374 if (cfg.devpath != search_devpath)
1375 continue;
1376
1377 AdminSocketClient client = AdminSocketClient(cfg.admin_sock_path);
1378 std::string output;
1379 std::string result = client.do_request("{\"prefix\":\"wnbd stats\"}",
1380 &output);
1381 if (!result.empty()) {
1382 std::cerr << "Admin socket error: " << result << std::endl;
1383 return -EINVAL;
1384 }
1385
1386 std::cout << output << std::endl;
1387 return 0;
1388 }
1389 int error = wnbd_disk_iterator.get_error();
1390 if (!error) {
1391 error = ENOENT;
1392 }
1393
1394 derr << "Could not find the specified disk." << dendl;
1395 return -error;
1396 }
1397
1398 static int parse_args(std::vector<const char*>& args,
1399 std::ostream *err_msg,
1400 Command *command, Config *cfg)
1401 {
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);
1406
1407 ConfigProxy config{false};
1408 config->name = iparams.name;
1409 config->cluster = cluster;
1410
1411 if (!conf_file_list.empty()) {
1412 config.parse_config_files(conf_file_list.c_str(), nullptr, 0);
1413 } else {
1414 config.parse_config_files(nullptr, nullptr, 0);
1415 }
1416 config.parse_env(CEPH_ENTITY_TYPE_CLIENT);
1417 config.parse_argv(args);
1418 cfg->poolname = config.get_val<std::string>("rbd_default_pool");
1419
1420 std::vector<const char*>::iterator i;
1421 std::ostringstream err;
1422
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)) {
1429 return HELP_INFO;
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",
1434 (char *)NULL)) {
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();
1449 return -EINVAL;
1450 }
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();
1455 return -EINVAL;
1456 }
1457 if (cfg->wnbd_log_level < 0) {
1458 *err_msg << "rbd-wnbd: Invalid argument for wnbd-log-level";
1459 return -EINVAL;
1460 }
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();
1465 return -EINVAL;
1466 }
1467 if (cfg->io_req_workers <= 0) {
1468 *err_msg << "rbd-wnbd: Invalid argument for io-req-workers";
1469 return -EINVAL;
1470 }
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();
1475 return -EINVAL;
1476 }
1477 if (cfg->io_reply_workers <= 0) {
1478 *err_msg << "rbd-wnbd: Invalid argument for io-reply-workers";
1479 return -EINVAL;
1480 }
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();
1485 return -EINVAL;
1486 }
1487 if (cfg->service_thread_count <= 0) {
1488 *err_msg << "rbd-wnbd: Invalid argument for service-thread-count";
1489 return -EINVAL;
1490 }
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",
1499 (char *)NULL)) {
1500 if (!err.str().empty()) {
1501 *err_msg << "rbd-wnbd: " << err.str();
1502 return -EINVAL;
1503 }
1504 if (cfg->soft_disconnect_timeout < 0) {
1505 *err_msg << "rbd-wnbd: Invalid argument for soft-disconnect-timeout";
1506 return -EINVAL;
1507 }
1508 } else if (ceph_argparse_witharg(args, i,
1509 (int*)&cfg->service_start_timeout,
1510 err, "--start-timeout",
1511 (char *)NULL)) {
1512 if (!err.str().empty()) {
1513 *err_msg << "rbd-wnbd: " << err.str();
1514 return -EINVAL;
1515 }
1516 if (cfg->service_start_timeout <= 0) {
1517 *err_msg << "rbd-wnbd: Invalid argument for start-timeout";
1518 return -EINVAL;
1519 }
1520 } else if (ceph_argparse_witharg(args, i,
1521 (int*)&cfg->image_map_timeout,
1522 err, "--map-timeout",
1523 (char *)NULL)) {
1524 if (!err.str().empty()) {
1525 *err_msg << "rbd-wnbd: " << err.str();
1526 return -EINVAL;
1527 }
1528 if (cfg->image_map_timeout <= 0) {
1529 *err_msg << "rbd-wnbd: Invalid argument for map-timeout";
1530 return -EINVAL;
1531 }
1532 } else {
1533 ++i;
1534 }
1535 }
1536
1537 Command cmd = None;
1538 if (args.begin() != args.end()) {
1539 if (strcmp(*args.begin(), "map") == 0) {
1540 cmd = Connect;
1541 } else if (strcmp(*args.begin(), "unmap") == 0) {
1542 cmd = Disconnect;
1543 } else if (strcmp(*args.begin(), "list") == 0) {
1544 cmd = List;
1545 } else if (strcmp(*args.begin(), "show") == 0) {
1546 cmd = Show;
1547 } else if (strcmp(*args.begin(), "service") == 0) {
1548 cmd = Service;
1549 } else if (strcmp(*args.begin(), "stats") == 0) {
1550 cmd = Stats;
1551 } else if (strcmp(*args.begin(), "help") == 0) {
1552 return HELP_INFO;
1553 } else {
1554 *err_msg << "rbd-wnbd: unknown command: " << *args.begin();
1555 return -EINVAL;
1556 }
1557 args.erase(args.begin());
1558 }
1559
1560 if (cmd == None) {
1561 *err_msg << "rbd-wnbd: must specify command";
1562 return -EINVAL;
1563 }
1564
1565 switch (cmd) {
1566 case Connect:
1567 case Disconnect:
1568 case Show:
1569 case Stats:
1570 if (args.begin() == args.end()) {
1571 *err_msg << "rbd-wnbd: must specify wnbd device or image-or-snap-spec";
1572 return -EINVAL;
1573 }
1574 if (parse_imgpath(*args.begin(), cfg, err_msg) < 0) {
1575 return -EINVAL;
1576 }
1577 args.erase(args.begin());
1578 break;
1579 default:
1580 //shut up gcc;
1581 break;
1582 }
1583
1584 if (args.begin() != args.end()) {
1585 *err_msg << "rbd-wnbd: unknown args: " << *args.begin();
1586 return -EINVAL;
1587 }
1588
1589 *command = cmd;
1590 return 0;
1591 }
1592
1593 static int rbd_wnbd(int argc, const char *argv[])
1594 {
1595 int r;
1596 Config cfg;
1597 std::vector<const char*> args;
1598 argv_to_vec(argc, argv, args);
1599
1600 // Avoid using dout before calling "do_global_init"
1601 if (args.empty()) {
1602 std::cout << argv[0] << ": -h or --help for usage" << std::endl;
1603 exit(1);
1604 }
1605
1606 std::ostringstream err_msg;
1607 r = parse_args(args, &err_msg, &cmd, &cfg);
1608 if (r == HELP_INFO) {
1609 usage();
1610 return 0;
1611 } else if (r == VERSION_INFO) {
1612 std::cout << pretty_version_to_str() << std::endl;
1613 return 0;
1614 } else if (r < 0) {
1615 std::cout << err_msg.str() << std::endl;
1616 return r;
1617 }
1618
1619 auto cct = do_global_init(argc, argv, &cfg);
1620
1621 WnbdSetLogger(WnbdHandler::LogMessage);
1622 WnbdSetLogLevel(cfg.wnbd_log_level);
1623
1624 switch (cmd) {
1625 case Connect:
1626 if (construct_devpath_if_missing(&cfg)) {
1627 return -EINVAL;
1628 }
1629 r = do_map(&cfg);
1630 if (r < 0)
1631 return r;
1632 break;
1633 case Disconnect:
1634 if (construct_devpath_if_missing(&cfg)) {
1635 return -EINVAL;
1636 }
1637 r = do_unmap(&cfg, true);
1638 if (r < 0)
1639 return r;
1640 break;
1641 case List:
1642 r = do_list_mapped_devices(cfg.format, cfg.pretty_format);
1643 if (r < 0)
1644 return r;
1645 break;
1646 case Show:
1647 if (construct_devpath_if_missing(&cfg)) {
1648 return r;
1649 }
1650 r = do_show_mapped_device(cfg.format, cfg.pretty_format, cfg.devpath);
1651 if (r < 0)
1652 return r;
1653 break;
1654 case Service:
1655 {
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);
1663 if (r < 0)
1664 return r;
1665 break;
1666 }
1667 case Stats:
1668 if (construct_devpath_if_missing(&cfg)) {
1669 return -EINVAL;
1670 }
1671 return do_stats(cfg.devpath);
1672 default:
1673 usage();
1674 break;
1675 }
1676
1677 return 0;
1678 }
1679
1680 int main(int argc, const char *argv[])
1681 {
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);
1686 if (r < 0) {
1687 return r;
1688 }
1689 return 0;
1690 }