]> git.proxmox.com Git - ceph.git/blame - ceph/src/tools/rbd_wnbd/rbd_wnbd.cc
import quincy beta 17.1.0
[ceph.git] / ceph / src / tools / rbd_wnbd / rbd_wnbd.cc
CommitLineData
f67539c2
TL
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
20effc67 56using namespace std;
f67539c2
TL
57using boost::locale::conv::utf_to_utf;
58
59std::wstring to_wstring(const std::string& str)
60{
61 return utf_to_utf<wchar_t>(str.c_str(), str.c_str() + str.size());
62}
63
64std::string to_string(const std::wstring& str)
65{
66 return utf_to_utf<char>(str.c_str(), str.c_str() + str.size());
67}
68
69bool is_process_running(DWORD pid)
70{
71 HANDLE process = OpenProcess(SYNCHRONIZE, FALSE, pid);
72 DWORD ret = WaitForSingleObject(process, 0);
73 CloseHandle(process);
74 return ret == WAIT_TIMEOUT;
75}
76
77DWORD WNBDActiveDiskIterator::fetch_list(
78 PWNBD_CONNECTION_LIST* conn_list)
79{
80 DWORD curr_buff_sz = 0;
81 DWORD buff_sz = 0;
82 DWORD err = 0;
83 PWNBD_CONNECTION_LIST tmp_list = NULL;
84
85 // We're using a loop because other connections may show up by the time
86 // we retry.
87 do {
88 if (tmp_list)
89 free(tmp_list);
90
91 if (buff_sz) {
92 tmp_list = (PWNBD_CONNECTION_LIST) calloc(1, buff_sz);
93 if (!tmp_list) {
94 derr << "Could not allocate " << buff_sz << " bytes." << dendl;
95 err = ERROR_NOT_ENOUGH_MEMORY;
96 break;
97 }
98 }
99
100 curr_buff_sz = buff_sz;
101 // If the buffer is too small, the return value is 0 and "BufferSize"
102 // will contain the required size. This is counterintuitive, but
103 // Windows drivers can't return a buffer as well as a non-zero status.
104 err = WnbdList(tmp_list, &buff_sz);
105 if (err)
106 break;
107 } while (curr_buff_sz < buff_sz);
108
109 if (err) {
110 if (tmp_list)
111 free(tmp_list);
112 } else {
113 *conn_list = tmp_list;
114 }
115 return err;
116}
117
118WNBDActiveDiskIterator::WNBDActiveDiskIterator()
119{
120 DWORD status = WNBDActiveDiskIterator::fetch_list(&conn_list);
121 if (status) {
122 error = EINVAL;
123 }
124}
125
126WNBDActiveDiskIterator::~WNBDActiveDiskIterator()
127{
128 if (conn_list) {
129 free(conn_list);
130 conn_list = NULL;
131 }
132}
133
134bool WNBDActiveDiskIterator::get(Config *cfg)
135{
136 index += 1;
137 *cfg = Config();
138
139 if (!conn_list || index >= (int)conn_list->Count) {
140 return false;
141 }
142
143 auto conn_info = conn_list->Connections[index];
144 auto conn_props = conn_info.Properties;
145
146 if (strncmp(conn_props.Owner, RBD_WNBD_OWNER_NAME, WNBD_MAX_OWNER_LENGTH)) {
147 dout(10) << "Ignoring disk: " << conn_props.InstanceName
148 << ". Owner: " << conn_props.Owner << dendl;
149 return this->get(cfg);
150 }
151
152 error = load_mapping_config_from_registry(conn_props.InstanceName, cfg);
153 if (error) {
154 derr << "Could not load registry disk info for: "
155 << conn_props.InstanceName << ". Error: " << error << dendl;
156 return false;
157 }
158
159 cfg->disk_number = conn_info.DiskNumber;
160 cfg->serial_number = std::string(conn_props.SerialNumber);
161 cfg->pid = conn_props.Pid;
162 cfg->active = cfg->disk_number > 0 && is_process_running(conn_props.Pid);
163 cfg->wnbd_mapped = true;
164
165 return true;
166}
167
168RegistryDiskIterator::RegistryDiskIterator()
169{
170 reg_key = new RegistryKey(g_ceph_context, HKEY_LOCAL_MACHINE,
171 SERVICE_REG_KEY, false);
172 if (!reg_key->hKey) {
173 if (!reg_key->missingKey)
174 error = EINVAL;
175 return;
176 }
177
178 if (RegQueryInfoKey(reg_key->hKey, NULL, NULL, NULL, &subkey_count,
179 NULL, NULL, NULL, NULL, NULL, NULL, NULL)) {
180 derr << "Could not query registry key: " << SERVICE_REG_KEY << dendl;
181 error = EINVAL;
182 return;
183 }
184}
185
186bool RegistryDiskIterator::get(Config *cfg)
187{
188 index += 1;
189 *cfg = Config();
190
191 if (!reg_key->hKey || !subkey_count) {
192 return false;
193 }
194
195 char subkey_name[MAX_PATH] = {0};
196 DWORD subkey_name_sz = MAX_PATH;
197 int err = RegEnumKeyEx(
198 reg_key->hKey, index, subkey_name, &subkey_name_sz,
199 NULL, NULL, NULL, NULL);
200 if (err == ERROR_NO_MORE_ITEMS) {
201 return false;
202 } else if (err) {
203 derr << "Could not enumerate registry. Error: " << err << dendl;
204 error = EINVAL;
205 return false;
206 }
207
208 if (load_mapping_config_from_registry(subkey_name, cfg)) {
209 error = EINVAL;
210 return false;
211 };
212
213 return true;
214}
215
216// Iterate over all RBD mappings, getting info from the registry and the driver.
217bool WNBDDiskIterator::get(Config *cfg)
218{
219 *cfg = Config();
220
221 bool found_active = active_iterator.get(cfg);
222 if (found_active) {
223 active_devices.insert(cfg->devpath);
224 return true;
225 }
226
227 error = active_iterator.get_error();
228 if (error) {
229 dout(5) << ": WNBD iterator error: " << error << dendl;
230 return false;
231 }
232
233 while(registry_iterator.get(cfg)) {
234 if (active_devices.find(cfg->devpath) != active_devices.end()) {
235 // Skip active devices that were already yielded.
236 continue;
237 }
238 return true;
239 }
240
241 error = registry_iterator.get_error();
242 if (error) {
243 dout(5) << ": Registry iterator error: " << error << dendl;
244 }
245 return false;
246}
247
248int get_exe_path(std::string& path) {
249 char buffer[MAX_PATH];
250 DWORD err = 0;
251
252 int ret = GetModuleFileNameA(NULL, buffer, MAX_PATH);
253 if (!ret || ret == MAX_PATH) {
254 err = GetLastError();
255 derr << "Could not retrieve executable path. "
256 << "Error: " << win32_strerror(err) << dendl;
257 return -EINVAL;
258 }
259
260 path = buffer;
261 return 0;
262}
263
264std::string get_cli_args() {
265 std::ostringstream cmdline;
266 for (int i=1; i<__argc; i++) {
267 if (i > 1)
268 cmdline << " ";
269 cmdline << std::quoted(__argv[i]);
270 }
271 return cmdline.str();
272}
273
274int send_map_request(std::string arguments) {
275 dout(15) << __func__ << ": command arguments: " << arguments << dendl;
276
277 BYTE request_buff[SERVICE_PIPE_BUFFSZ] = { 0 };
278 ServiceRequest* request = (ServiceRequest*) request_buff;
279 request->command = Connect;
280 arguments.copy(
281 (char*)request->arguments,
282 SERVICE_PIPE_BUFFSZ - FIELD_OFFSET(ServiceRequest, arguments));
283 ServiceReply reply = { 0 };
284
285 DWORD bytes_read = 0;
286 BOOL success = CallNamedPipe(
287 SERVICE_PIPE_NAME,
288 request_buff,
289 SERVICE_PIPE_BUFFSZ,
290 &reply,
291 sizeof(reply),
292 &bytes_read,
293 DEFAULT_MAP_TIMEOUT_MS);
294 if (!success) {
295 DWORD err = GetLastError();
296 derr << "Could not send device map request. "
297 << "Make sure that the ceph service is running. "
298 << "Error: " << win32_strerror(err) << dendl;
299 return -EINVAL;
300 }
301 if (reply.status) {
302 derr << "The ceph service failed to map the image. Error: "
303 << reply.status << dendl;
304 }
305
306 return reply.status;
307}
308
309// Spawn a subprocess using the specified "rbd-wnbd" command
310// arguments. A pipe is passed to the child process,
311// which will allow it to communicate the mapping status
312int map_device_using_suprocess(std::string arguments, int timeout_ms)
313{
314 STARTUPINFO si;
315 PROCESS_INFORMATION pi;
316 char ch;
317 DWORD err = 0, status = 0;
318 int exit_code = 0;
319 std::ostringstream command_line;
320 std::string exe_path;
321 // Windows async IO context
322 OVERLAPPED connect_o, read_o;
323 HANDLE connect_event = NULL, read_event = NULL;
324 // Used for waiting on multiple events that are going to be initialized later.
325 HANDLE wait_events[2] = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE};
326 DWORD bytes_read = 0;
327 // We may get a command line containing an old pipe handle when
328 // recreating mappings, so we'll have to replace it.
329 std::regex pipe_pattern("([\'\"]?--pipe-name[\'\"]? +[\'\"]?[^ ]+[\'\"]?)");
330
331 uuid_d uuid;
332 uuid.generate_random();
333 std::ostringstream pipe_name;
334 pipe_name << "\\\\.\\pipe\\rbd-wnbd-" << uuid;
335
336 // Create an unique named pipe to communicate with the child. */
337 HANDLE pipe_handle = CreateNamedPipe(
338 pipe_name.str().c_str(),
339 PIPE_ACCESS_INBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE |
340 FILE_FLAG_OVERLAPPED,
341 PIPE_WAIT,
342 1, // Only accept one instance
343 SERVICE_PIPE_BUFFSZ,
344 SERVICE_PIPE_BUFFSZ,
345 SERVICE_PIPE_TIMEOUT_MS,
346 NULL);
347 if (pipe_handle == INVALID_HANDLE_VALUE) {
348 err = GetLastError();
349 derr << "CreateNamedPipe failed: " << win32_strerror(err) << dendl;
350 exit_code = -ECHILD;
351 goto finally;
352 }
353 connect_event = CreateEvent(0, TRUE, FALSE, NULL);
354 read_event = CreateEvent(0, TRUE, FALSE, NULL);
355 if (!connect_event || !read_event) {
356 err = GetLastError();
357 derr << "CreateEvent failed: " << win32_strerror(err) << dendl;
358 exit_code = -ECHILD;
359 goto finally;
360 }
361 connect_o.hEvent = connect_event;
362 read_o.hEvent = read_event;
363
364 status = ConnectNamedPipe(pipe_handle, &connect_o);
365 err = GetLastError();
366 if (status || err != ERROR_IO_PENDING) {
367 if (status)
368 err = status;
369 derr << "ConnectNamedPipe failed: " << win32_strerror(err) << dendl;
370 exit_code = -ECHILD;
371 goto finally;
372 }
373 err = 0;
374
375 dout(5) << __func__ << ": command arguments: " << arguments << dendl;
376
377 // We'll avoid running arbitrary commands, instead using the executable
378 // path of this process (expected to be the full rbd-wnbd.exe path).
379 err = get_exe_path(exe_path);
380 if (err) {
381 exit_code = -EINVAL;
382 goto finally;
383 }
384 command_line << std::quoted(exe_path)
385 << " " << std::regex_replace(arguments, pipe_pattern, "")
386 << " --pipe-name " << pipe_name.str();
387
388 dout(5) << __func__ << ": command line: " << command_line.str() << dendl;
389
390 GetStartupInfo(&si);
391 // Create a detached child
392 if (!CreateProcess(NULL, (char*)command_line.str().c_str(),
393 NULL, NULL, FALSE, DETACHED_PROCESS,
394 NULL, NULL, &si, &pi)) {
395 err = GetLastError();
396 derr << "CreateProcess failed: " << win32_strerror(err) << dendl;
397 exit_code = -ECHILD;
398 goto finally;
399 }
400
401 wait_events[0] = connect_event;
402 wait_events[1] = pi.hProcess;
403 status = WaitForMultipleObjects(2, wait_events, FALSE, timeout_ms);
404 switch(status) {
405 case WAIT_OBJECT_0:
406 if (!GetOverlappedResult(pipe_handle, &connect_o, &bytes_read, TRUE)) {
407 err = GetLastError();
408 derr << "Couln't establish a connection with the child process. "
409 << "Error: " << win32_strerror(err) << dendl;
410 exit_code = -ECHILD;
411 goto clean_process;
412 }
413 // We have an incoming connection.
414 break;
415 case WAIT_OBJECT_0 + 1:
416 // The process has exited prematurely.
417 goto clean_process;
418 case WAIT_TIMEOUT:
419 derr << "Timed out waiting for child process connection." << dendl;
420 goto clean_process;
421 default:
422 derr << "Failed waiting for child process. Status: " << status << dendl;
423 goto clean_process;
424 }
425 // Block and wait for child to say it is ready.
426 dout(5) << __func__ << ": waiting for child notification." << dendl;
427 if (!ReadFile(pipe_handle, &ch, 1, NULL, &read_o)) {
428 err = GetLastError();
429 if (err != ERROR_IO_PENDING) {
430 derr << "Receiving child process reply failed with: "
431 << win32_strerror(err) << dendl;
432 exit_code = -ECHILD;
433 goto clean_process;
434 }
435 }
436 wait_events[0] = read_event;
437 wait_events[1] = pi.hProcess;
438 // The RBD daemon is expected to write back right after opening the
439 // pipe. We'll use the same timeout value for now.
440 status = WaitForMultipleObjects(2, wait_events, FALSE, timeout_ms);
441 switch(status) {
442 case WAIT_OBJECT_0:
443 if (!GetOverlappedResult(pipe_handle, &read_o, &bytes_read, TRUE)) {
444 err = GetLastError();
445 derr << "Receiving child process reply failed with: "
446 << win32_strerror(err) << dendl;
447 exit_code = -ECHILD;
448 goto clean_process;
449 }
450 break;
451 case WAIT_OBJECT_0 + 1:
452 // The process has exited prematurely.
453 goto clean_process;
454 case WAIT_TIMEOUT:
455 derr << "Timed out waiting for child process message." << dendl;
456 goto clean_process;
457 default:
458 derr << "Failed waiting for child process. Status: " << status << dendl;
459 goto clean_process;
460 }
461
462 dout(5) << __func__ << ": received child notification." << dendl;
463 goto finally;
464
465 clean_process:
466 if (!is_process_running(pi.dwProcessId)) {
467 GetExitCodeProcess(pi.hProcess, (PDWORD)&exit_code);
468 derr << "Daemon failed with: " << cpp_strerror(exit_code) << dendl;
469 } else {
470 // The process closed the pipe without notifying us or exiting.
471 // This is quite unlikely, but we'll terminate the process.
472 dout(5) << "Terminating unresponsive process." << dendl;
473 TerminateProcess(pi.hProcess, 1);
474 exit_code = -EINVAL;
475 }
476
477 finally:
478 if (exit_code)
479 derr << "Could not start RBD daemon." << dendl;
480 if (pipe_handle)
481 CloseHandle(pipe_handle);
482 if (connect_event)
483 CloseHandle(connect_event);
484 if (read_event)
485 CloseHandle(read_event);
486 return exit_code;
487}
488
489BOOL WINAPI console_handler_routine(DWORD dwCtrlType)
490{
491 dout(5) << "Received control signal: " << dwCtrlType
492 << ". Exiting." << dendl;
493
494 std::unique_lock l{shutdown_lock};
495 if (handler)
496 handler->shutdown();
497
498 return true;
499}
500
501int save_config_to_registry(Config* cfg)
502{
503 std::string strKey{ SERVICE_REG_KEY };
504 strKey.append("\\");
505 strKey.append(cfg->devpath);
506 auto reg_key = RegistryKey(
507 g_ceph_context, HKEY_LOCAL_MACHINE, strKey.c_str(), true);
508 if (!reg_key.hKey) {
509 return -EINVAL;
510 }
511
512 int ret_val = 0;
513 // Registry writes are immediately available to other processes.
514 // Still, we'll do a flush to ensure that the mapping can be
515 // recreated after a system crash.
516 if (reg_key.set("pid", getpid()) ||
517 reg_key.set("devpath", cfg->devpath) ||
518 reg_key.set("poolname", cfg->poolname) ||
519 reg_key.set("nsname", cfg->nsname) ||
520 reg_key.set("imgname", cfg->imgname) ||
521 reg_key.set("snapname", cfg->snapname) ||
522 reg_key.set("command_line", get_cli_args()) ||
523 reg_key.set("persistent", cfg->persistent) ||
524 reg_key.set("admin_sock_path", g_conf()->admin_socket) ||
525 reg_key.flush()) {
526 ret_val = -EINVAL;
527 }
528
529 return ret_val;
530}
531
532int remove_config_from_registry(Config* cfg)
533{
534 std::string strKey{ SERVICE_REG_KEY };
535 strKey.append("\\");
536 strKey.append(cfg->devpath);
537 return RegistryKey::remove(
538 g_ceph_context, HKEY_LOCAL_MACHINE, strKey.c_str());
539}
540
541int load_mapping_config_from_registry(string devpath, Config* cfg)
542{
543 std::string strKey{ SERVICE_REG_KEY };
544 strKey.append("\\");
545 strKey.append(devpath);
546 auto reg_key = RegistryKey(
547 g_ceph_context, HKEY_LOCAL_MACHINE, strKey.c_str(), false);
548 if (!reg_key.hKey) {
549 if (reg_key.missingKey)
550 return -ENOENT;
551 else
552 return -EINVAL;
553 }
554
555 reg_key.get("devpath", cfg->devpath);
556 reg_key.get("poolname", cfg->poolname);
557 reg_key.get("nsname", cfg->nsname);
558 reg_key.get("imgname", cfg->imgname);
559 reg_key.get("snapname", cfg->snapname);
560 reg_key.get("command_line", cfg->command_line);
561 reg_key.get("persistent", cfg->persistent);
562 reg_key.get("admin_sock_path", cfg->admin_sock_path);
563
564 return 0;
565}
566
567int restart_registered_mappings(
568 int worker_count,
569 int total_timeout,
570 int image_map_timeout)
571{
572 Config cfg;
573 WNBDDiskIterator iterator;
574 int err = 0, r;
575
576 int total_timeout_ms = max(total_timeout, total_timeout * 1000);
577 int image_map_timeout_ms = max(image_map_timeout, image_map_timeout * 1000);
578
579 LARGE_INTEGER start_t, counter_freq;
580 QueryPerformanceFrequency(&counter_freq);
581 QueryPerformanceCounter(&start_t);
582
583 boost::asio::thread_pool pool(worker_count);
584 while (iterator.get(&cfg)) {
585 if (cfg.command_line.empty()) {
586 derr << "Could not recreate mapping, missing command line: "
587 << cfg.devpath << dendl;
588 err = -EINVAL;
589 continue;
590 }
591 if (cfg.wnbd_mapped) {
592 dout(5) << __func__ << ": device already mapped: "
593 << cfg.devpath << dendl;
594 continue;
595 }
596 if (!cfg.persistent) {
597 dout(5) << __func__ << ": cleaning up non-persistent mapping: "
598 << cfg.devpath << dendl;
599 r = remove_config_from_registry(&cfg);
600 if (r) {
601 derr << __func__ << ": could not clean up non-persistent mapping: "
602 << cfg.devpath << dendl;
603 }
604 continue;
605 }
606
607 boost::asio::post(pool,
608 [&, cfg]() mutable
609 {
610 LARGE_INTEGER curr_t, elapsed_ms;
611 QueryPerformanceCounter(&curr_t);
612 elapsed_ms.QuadPart = curr_t.QuadPart - start_t.QuadPart;
613 elapsed_ms.QuadPart *= 1000;
614 elapsed_ms.QuadPart /= counter_freq.QuadPart;
615
616 int time_left_ms = max(
617 0,
618 total_timeout_ms - (int)elapsed_ms.QuadPart);
619 time_left_ms = min(image_map_timeout_ms, time_left_ms);
620 if (!time_left_ms) {
621 err = -ETIMEDOUT;
622 return;
623 }
624
625 dout(5) << "Remapping: " << cfg.devpath
626 << ". Timeout: " << time_left_ms << " ms." << dendl;
627
628 // We'll try to map all devices and return a non-zero value
629 // if any of them fails.
630 r = map_device_using_suprocess(cfg.command_line, time_left_ms);
631 if (r) {
632 err = r;
633 derr << "Could not create mapping: "
634 << cfg.devpath << ". Error: " << r << dendl;
635 } else {
636 dout(5) << "Successfully remapped: " << cfg.devpath << dendl;
637 }
638 });
639 }
640 pool.join();
641
642 r = iterator.get_error();
643 if (r) {
644 derr << "Could not fetch all mappings. Error: " << r << dendl;
645 err = r;
646 }
647
648 return err;
649}
650
651int disconnect_all_mappings(
652 bool unregister,
653 bool hard_disconnect,
654 int soft_disconnect_timeout,
655 int worker_count)
656{
657 // Although not generally recommended, soft_disconnect_timeout can be 0,
658 // which means infinite timeout.
659 ceph_assert(soft_disconnect_timeout >= 0);
660 ceph_assert(worker_count > 0);
661 int64_t timeout_ms = soft_disconnect_timeout * 1000;
662
663 Config cfg;
664 WNBDActiveDiskIterator iterator;
665 int err = 0, r;
666
667 boost::asio::thread_pool pool(worker_count);
668 LARGE_INTEGER start_t, counter_freq;
669 QueryPerformanceFrequency(&counter_freq);
670 QueryPerformanceCounter(&start_t);
671 while (iterator.get(&cfg)) {
672 boost::asio::post(pool,
673 [&, cfg]() mutable
674 {
675 LARGE_INTEGER curr_t, elapsed_ms;
676 QueryPerformanceCounter(&curr_t);
677 elapsed_ms.QuadPart = curr_t.QuadPart - start_t.QuadPart;
678 elapsed_ms.QuadPart *= 1000;
679 elapsed_ms.QuadPart /= counter_freq.QuadPart;
680
681 int64_t time_left_ms = max((int64_t)0, timeout_ms - elapsed_ms.QuadPart);
682
683 cfg.hard_disconnect = hard_disconnect || !time_left_ms;
684 cfg.hard_disconnect_fallback = true;
685 cfg.soft_disconnect_timeout = time_left_ms / 1000;
686
687 dout(5) << "Removing mapping: " << cfg.devpath
688 << ". Timeout: " << cfg.soft_disconnect_timeout
689 << "s. Hard disconnect: " << cfg.hard_disconnect
690 << dendl;
691
692 r = do_unmap(&cfg, unregister);
693 if (r) {
694 err = r;
695 derr << "Could not remove mapping: " << cfg.devpath
696 << ". Error: " << r << dendl;
697 } else {
698 dout(5) << "Successfully removed mapping: " << cfg.devpath << dendl;
699 }
700 });
701 }
702 pool.join();
703
704 r = iterator.get_error();
705 if (r) {
706 derr << "Could not fetch all mappings. Error: " << r << dendl;
707 err = r;
708 }
709
710 return err;
711}
712
713class RBDService : public ServiceBase {
714 private:
715 bool hard_disconnect;
716 int soft_disconnect_timeout;
717 int thread_count;
718 int service_start_timeout;
719 int image_map_timeout;
720 bool remap_failure_fatal;
721
722 public:
723 RBDService(bool _hard_disconnect,
724 int _soft_disconnect_timeout,
725 int _thread_count,
726 int _service_start_timeout,
727 int _image_map_timeout,
728 bool _remap_failure_fatal)
729 : ServiceBase(g_ceph_context)
730 , hard_disconnect(_hard_disconnect)
731 , soft_disconnect_timeout(_soft_disconnect_timeout)
732 , thread_count(_thread_count)
733 , service_start_timeout(_service_start_timeout)
734 , image_map_timeout(_image_map_timeout)
735 , remap_failure_fatal(_remap_failure_fatal)
736 {
737 }
738
739 static int execute_command(ServiceRequest* request)
740 {
741 switch(request->command) {
742 case Connect:
743 dout(5) << "Received device connect request. Command line: "
744 << (char*)request->arguments << dendl;
745 // TODO: use the configured service map timeout.
746 // TODO: add ceph.conf options.
747 return map_device_using_suprocess(
748 (char*)request->arguments, DEFAULT_MAP_TIMEOUT_MS);
749 default:
750 dout(5) << "Received unsupported command: "
751 << request->command << dendl;
752 return -ENOSYS;
753 }
754 }
755
756 static DWORD handle_connection(HANDLE pipe_handle)
757 {
758 PBYTE message[SERVICE_PIPE_BUFFSZ] = { 0 };
759 DWORD bytes_read = 0, bytes_written = 0;
760 DWORD err = 0;
761 DWORD reply_sz = 0;
762 ServiceReply reply = { 0 };
763
764 dout(20) << __func__ << ": Receiving message." << dendl;
765 BOOL success = ReadFile(
766 pipe_handle, message, SERVICE_PIPE_BUFFSZ,
767 &bytes_read, NULL);
768 if (!success || !bytes_read) {
769 err = GetLastError();
770 derr << "Could not read service command: "
771 << win32_strerror(err) << dendl;
772 goto exit;
773 }
774
775 dout(20) << __func__ << ": Executing command." << dendl;
776 reply.status = execute_command((ServiceRequest*) message);
777 reply_sz = sizeof(reply);
778
779 dout(20) << __func__ << ": Sending reply. Status: "
780 << reply.status << dendl;
781 success = WriteFile(
782 pipe_handle, &reply, reply_sz, &bytes_written, NULL);
783 if (!success || reply_sz != bytes_written) {
784 err = GetLastError();
785 derr << "Could not send service command result: "
786 << win32_strerror(err) << dendl;
787 }
788
789exit:
790 dout(20) << __func__ << ": Cleaning up connection." << dendl;
791 FlushFileBuffers(pipe_handle);
792 DisconnectNamedPipe(pipe_handle);
793 CloseHandle(pipe_handle);
794
795 return err;
796 }
797
798 // We have to support Windows server 2016. Unix sockets only work on
799 // WS 2019, so we can't use the Ceph admin socket abstraction.
800 // Getting the Ceph admin sockets to work with Windows named pipes
801 // would require quite a few changes.
802 static DWORD accept_pipe_connection() {
803 DWORD err = 0;
804 // We're currently using default ACLs, which grant full control to the
805 // LocalSystem account and administrator as well as the owner.
806 dout(20) << __func__ << ": opening new pipe instance" << dendl;
807 HANDLE pipe_handle = CreateNamedPipe(
808 SERVICE_PIPE_NAME,
809 PIPE_ACCESS_DUPLEX,
810 PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
811 PIPE_UNLIMITED_INSTANCES,
812 SERVICE_PIPE_BUFFSZ,
813 SERVICE_PIPE_BUFFSZ,
814 SERVICE_PIPE_TIMEOUT_MS,
815 NULL);
816 if (pipe_handle == INVALID_HANDLE_VALUE) {
817 err = GetLastError();
818 derr << "CreatePipe failed: " << win32_strerror(err) << dendl;
819 return -EINVAL;
820 }
821
822 dout(20) << __func__ << ": waiting for connections." << dendl;
823 BOOL connected = ConnectNamedPipe(pipe_handle, NULL);
824 if (!connected) {
825 err = GetLastError();
826 if (err != ERROR_PIPE_CONNECTED) {
827 derr << "Pipe connection failed: " << win32_strerror(err) << dendl;
828
829 CloseHandle(pipe_handle);
830 return err;
831 }
832 }
833
834 dout(20) << __func__ << ": Connection received." << dendl;
835 // We'll handle the connection in a separate thread and at the same time
836 // accept a new connection.
837 HANDLE handler_thread = CreateThread(
838 NULL, 0, (LPTHREAD_START_ROUTINE) handle_connection, pipe_handle, 0, 0);
839 if (!handler_thread) {
840 err = GetLastError();
841 derr << "Could not start pipe connection handler thread: "
842 << win32_strerror(err) << dendl;
843 CloseHandle(pipe_handle);
844 } else {
845 CloseHandle(handler_thread);
846 }
847
848 return err;
849 }
850
851 static int pipe_server_loop(LPVOID arg)
852 {
853 dout(5) << "Accepting admin pipe connections." << dendl;
854 while (1) {
855 // This call will block until a connection is received, which will
856 // then be handled in a separate thread. The function returns, allowing
857 // us to accept another simultaneous connection.
858 accept_pipe_connection();
859 }
860 return 0;
861 }
862
863 int create_pipe_server() {
864 HANDLE handler_thread = CreateThread(
865 NULL, 0, (LPTHREAD_START_ROUTINE) pipe_server_loop, NULL, 0, 0);
866 DWORD err = 0;
867
868 if (!handler_thread) {
869 err = GetLastError();
870 derr << "Could not start pipe server: " << win32_strerror(err) << dendl;
871 } else {
872 CloseHandle(handler_thread);
873 }
874
875 return err;
876 }
877
878 int run_hook() override {
879 // Restart registered mappings before accepting new ones.
880 int r = restart_registered_mappings(
881 thread_count, service_start_timeout, image_map_timeout);
882 if (r) {
883 if (remap_failure_fatal) {
884 derr << "Couldn't remap all images. Cleaning up." << dendl;
885 return r;
886 } else {
887 dout(0) << "Ignoring image remap failure." << dendl;
888 }
889 }
890
891 return create_pipe_server();
892 }
893
894 // Invoked when the service is requested to stop.
895 int stop_hook() override {
896 return disconnect_all_mappings(
897 false, hard_disconnect, soft_disconnect_timeout, thread_count);
898 }
899 // Invoked when the system is shutting down.
900 int shutdown_hook() override {
901 return stop_hook();
902 }
903};
904
905static void usage()
906{
907 const char* usage_str =R"(
908Usage: rbd-wnbd [options] map <image-or-snap-spec> Map an image to wnbd device
909 [options] unmap <device|image-or-snap-spec> Unmap wnbd device
910 [options] list List mapped wnbd devices
911 [options] show <image-or-snap-spec> Show mapped wnbd device
912 stats <image-or-snap-spec> Show IO counters
913 [options] service Windows service entrypoint,
914 handling device lifecycle
915
916Map options:
917 --device <device path> Optional mapping unique identifier
918 --exclusive Forbid writes by other clients
919 --read-only Map read-only
920 --non-persistent Do not recreate the mapping when the Ceph service
921 restarts. By default, mappings are persistent
922 --io-req-workers The number of workers that dispatch IO requests.
923 Default: 4
924 --io-reply-workers The number of workers that dispatch IO replies.
925 Default: 4
926
927Unmap options:
928 --hard-disconnect Skip attempting a soft disconnect
929 --no-hard-disconnect-fallback Immediately return an error if the soft
930 disconnect fails instead of attempting a hard
931 disconnect as fallback
932 --soft-disconnect-timeout Soft disconnect timeout in seconds. The soft
933 disconnect operation uses PnP to notify the
934 Windows storage stack that the device is going to
935 be disconnectd. Storage drivers can block this
936 operation if there are pending operations,
937 unflushed caches or open handles. Default: 15
938
939Service options:
940 --hard-disconnect Skip attempting a soft disconnect
941 --soft-disconnect-timeout Cummulative soft disconnect timeout in seconds,
942 used when disconnecting existing mappings. A hard
943 disconnect will be issued when hitting the timeout
944 --service-thread-count The number of workers used when mapping or
945 unmapping images. Default: 8
946 --start-timeout The service start timeout in seconds. Default: 120
947 --map-timeout Individual image map timeout in seconds. Default: 20
948 --remap-failure-fatal If set, the service will stop when failing to remap
949 an image at start time, unmapping images that have
950 been mapped so far.
951
952Show|List options:
953 --format plain|json|xml Output format (default: plain)
954 --pretty-format Pretty formatting (json and xml)
955
956Common options:
957 --wnbd-log-level libwnbd.dll log level
958
959)";
960
961 std::cout << usage_str;
962 generic_server_usage();
963}
964
965
966static Command cmd = None;
967
968int construct_devpath_if_missing(Config* cfg)
969{
970 // Windows doesn't allow us to request specific disk paths when mapping an
971 // image. This will just be used by rbd-wnbd and wnbd as an identifier.
972 if (cfg->devpath.empty()) {
973 if (cfg->imgname.empty()) {
974 derr << "Missing image name." << dendl;
975 return -EINVAL;
976 }
977
978 if (!cfg->poolname.empty()) {
979 cfg->devpath += cfg->poolname;
980 cfg->devpath += '/';
981 }
982 if (!cfg->nsname.empty()) {
983 cfg->devpath += cfg->nsname;
984 cfg->devpath += '/';
985 }
986
987 cfg->devpath += cfg->imgname;
988
989 if (!cfg->snapname.empty()) {
990 cfg->devpath += '@';
991 cfg->devpath += cfg->snapname;
992 }
993 }
994
995 return 0;
996}
997
998boost::intrusive_ptr<CephContext> do_global_init(
999 int argc, const char *argv[], Config *cfg)
1000{
20effc67 1001 auto args = argv_to_vec(argc, argv);
f67539c2
TL
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
1039static 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
1176close_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
1190static 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
1214static 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
1240static 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
1310static 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
1368static 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
1398static 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
1593static int rbd_wnbd(int argc, const char *argv[])
1594{
f67539c2 1595 Config cfg;
20effc67 1596 auto args = argv_to_vec(argc, argv);
f67539c2
TL
1597
1598 // Avoid using dout before calling "do_global_init"
1599 if (args.empty()) {
1600 std::cout << argv[0] << ": -h or --help for usage" << std::endl;
1601 exit(1);
1602 }
1603
1604 std::ostringstream err_msg;
20effc67 1605 int r = parse_args(args, &err_msg, &cmd, &cfg);
f67539c2
TL
1606 if (r == HELP_INFO) {
1607 usage();
1608 return 0;
1609 } else if (r == VERSION_INFO) {
1610 std::cout << pretty_version_to_str() << std::endl;
1611 return 0;
1612 } else if (r < 0) {
1613 std::cout << err_msg.str() << std::endl;
1614 return r;
1615 }
1616
1617 auto cct = do_global_init(argc, argv, &cfg);
1618
1619 WnbdSetLogger(WnbdHandler::LogMessage);
1620 WnbdSetLogLevel(cfg.wnbd_log_level);
1621
1622 switch (cmd) {
1623 case Connect:
1624 if (construct_devpath_if_missing(&cfg)) {
1625 return -EINVAL;
1626 }
1627 r = do_map(&cfg);
1628 if (r < 0)
1629 return r;
1630 break;
1631 case Disconnect:
1632 if (construct_devpath_if_missing(&cfg)) {
1633 return -EINVAL;
1634 }
1635 r = do_unmap(&cfg, true);
1636 if (r < 0)
1637 return r;
1638 break;
1639 case List:
1640 r = do_list_mapped_devices(cfg.format, cfg.pretty_format);
1641 if (r < 0)
1642 return r;
1643 break;
1644 case Show:
1645 if (construct_devpath_if_missing(&cfg)) {
1646 return r;
1647 }
1648 r = do_show_mapped_device(cfg.format, cfg.pretty_format, cfg.devpath);
1649 if (r < 0)
1650 return r;
1651 break;
1652 case Service:
1653 {
1654 RBDService service(cfg.hard_disconnect, cfg.soft_disconnect_timeout,
1655 cfg.service_thread_count,
1656 cfg.service_start_timeout,
1657 cfg.image_map_timeout,
1658 cfg.remap_failure_fatal);
1659 // This call will block until the service stops.
1660 r = RBDService::initialize(&service);
1661 if (r < 0)
1662 return r;
1663 break;
1664 }
1665 case Stats:
1666 if (construct_devpath_if_missing(&cfg)) {
1667 return -EINVAL;
1668 }
1669 return do_stats(cfg.devpath);
1670 default:
1671 usage();
1672 break;
1673 }
1674
1675 return 0;
1676}
1677
1678int main(int argc, const char *argv[])
1679{
1680 SetConsoleCtrlHandler(console_handler_routine, true);
1681 // Avoid the Windows Error Reporting dialog.
1682 SetErrorMode(GetErrorMode() | SEM_NOGPFAULTERRORBOX);
1683 int r = rbd_wnbd(argc, argv);
1684 if (r < 0) {
1685 return r;
1686 }
1687 return 0;
1688}