]> git.proxmox.com Git - ceph.git/blob - ceph/src/tools/rbd/action/Kernel.cc
1f1951fc8842539b269ca3d2b00d80d4bff87623
[ceph.git] / ceph / src / tools / rbd / action / Kernel.cc
1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
3
4 #include "acconfig.h"
5 #include "tools/rbd/ArgumentTypes.h"
6 #include "tools/rbd/Shell.h"
7 #include "tools/rbd/Utils.h"
8 #include "include/krbd.h"
9 #include "include/stringify.h"
10 #include "include/uuid.h"
11 #include "common/config.h"
12 #include "common/errno.h"
13 #include "common/safe_io.h"
14 #include "common/strtol.h"
15 #include "common/Formatter.h"
16 #include "msg/msg_types.h"
17 #include "global/global_context.h"
18 #include <iostream>
19 #include <boost/algorithm/string/predicate.hpp>
20 #include <boost/scope_exit.hpp>
21 #include <boost/program_options.hpp>
22
23 namespace rbd {
24 namespace action {
25 namespace kernel {
26
27 namespace at = argument_types;
28 namespace po = boost::program_options;
29
30 namespace {
31
32 std::map<std::string, std::string> map_options; // used for both map and unmap
33
34 } // anonymous namespace
35
36 static std::string map_option_uuid_cb(const char *value_char)
37 {
38 uuid_d u;
39 if (!u.parse(value_char))
40 return "";
41
42 return stringify(u);
43 }
44
45 static std::string map_option_ip_cb(const char *value_char)
46 {
47 entity_addr_t a;
48 const char *endptr;
49 if (!a.parse(value_char, &endptr) ||
50 endptr != value_char + strlen(value_char)) {
51 return "";
52 }
53
54 return stringify(a.get_sockaddr());
55 }
56
57 static std::string map_option_int_cb(const char *value_char)
58 {
59 std::string err;
60 int d = strict_strtol(value_char, 10, &err);
61 if (!err.empty() || d < 0)
62 return "";
63
64 return stringify(d);
65 }
66
67 static void put_map_option(const std::string &key, std::string val)
68 {
69 map_options[key] = val;
70 }
71
72 static int put_map_option_value(const std::string &opt, const char *value_char,
73 std::string (*parse_cb)(const char *))
74 {
75 if (!value_char || *value_char == '\0') {
76 std::cerr << "rbd: " << opt << " option requires a value" << std::endl;
77 return -EINVAL;
78 }
79
80 std::string value = parse_cb(value_char);
81 if (value.empty()) {
82 std::cerr << "rbd: invalid " << opt << " value '" << value_char << "'"
83 << std::endl;
84 return -EINVAL;
85 }
86
87 put_map_option(opt, opt + "=" + value);
88 return 0;
89 }
90
91 static int parse_map_options(char *options)
92 {
93 for (char *this_char = strtok(options, ", ");
94 this_char != NULL;
95 this_char = strtok(NULL, ",")) {
96 char *value_char;
97
98 if ((value_char = strchr(this_char, '=')) != NULL)
99 *value_char++ = '\0';
100
101 if (!strcmp(this_char, "fsid")) {
102 if (put_map_option_value("fsid", value_char, map_option_uuid_cb))
103 return -EINVAL;
104 } else if (!strcmp(this_char, "ip")) {
105 if (put_map_option_value("ip", value_char, map_option_ip_cb))
106 return -EINVAL;
107 } else if (!strcmp(this_char, "share") || !strcmp(this_char, "noshare")) {
108 put_map_option("share", this_char);
109 } else if (!strcmp(this_char, "crc") || !strcmp(this_char, "nocrc")) {
110 put_map_option("crc", this_char);
111 } else if (!strcmp(this_char, "cephx_require_signatures") ||
112 !strcmp(this_char, "nocephx_require_signatures")) {
113 put_map_option("cephx_require_signatures", this_char);
114 } else if (!strcmp(this_char, "tcp_nodelay") ||
115 !strcmp(this_char, "notcp_nodelay")) {
116 put_map_option("tcp_nodelay", this_char);
117 } else if (!strcmp(this_char, "cephx_sign_messages") ||
118 !strcmp(this_char, "nocephx_sign_messages")) {
119 put_map_option("cephx_sign_messages", this_char);
120 } else if (!strcmp(this_char, "mount_timeout")) {
121 if (put_map_option_value("mount_timeout", value_char, map_option_int_cb))
122 return -EINVAL;
123 } else if (!strcmp(this_char, "osdkeepalive")) {
124 if (put_map_option_value("osdkeepalive", value_char, map_option_int_cb))
125 return -EINVAL;
126 } else if (!strcmp(this_char, "osd_idle_ttl")) {
127 if (put_map_option_value("osd_idle_ttl", value_char, map_option_int_cb))
128 return -EINVAL;
129 } else if (!strcmp(this_char, "rw") || !strcmp(this_char, "ro")) {
130 put_map_option("rw", this_char);
131 } else if (!strcmp(this_char, "queue_depth")) {
132 if (put_map_option_value("queue_depth", value_char, map_option_int_cb))
133 return -EINVAL;
134 } else if (!strcmp(this_char, "lock_on_read")) {
135 put_map_option("lock_on_read", this_char);
136 } else if (!strcmp(this_char, "exclusive")) {
137 put_map_option("exclusive", this_char);
138 } else {
139 std::cerr << "rbd: unknown map option '" << this_char << "'" << std::endl;
140 return -EINVAL;
141 }
142 }
143
144 return 0;
145 }
146
147 static int parse_unmap_options(char *options)
148 {
149 for (char *this_char = strtok(options, ", ");
150 this_char != NULL;
151 this_char = strtok(NULL, ",")) {
152 char *value_char;
153
154 if ((value_char = strchr(this_char, '=')) != NULL)
155 *value_char++ = '\0';
156
157 if (!strcmp(this_char, "force")) {
158 put_map_option("force", this_char);
159 } else {
160 std::cerr << "rbd: unknown unmap option '" << this_char << "'" << std::endl;
161 return -EINVAL;
162 }
163 }
164
165 return 0;
166 }
167
168 static int do_kernel_showmapped(Formatter *f)
169 {
170 #if defined(WITH_KRBD)
171 struct krbd_ctx *krbd;
172 int r;
173
174 r = krbd_create_from_context(g_ceph_context, &krbd);
175 if (r < 0)
176 return r;
177
178 r = krbd_showmapped(krbd, f);
179
180 krbd_destroy(krbd);
181 return r;
182 #else
183 return -1;
184 #endif
185
186 }
187
188 static int get_unsupported_features(librbd::Image &image,
189 uint64_t *unsupported_features)
190 {
191 char buf[20];
192 uint64_t features, supported_features;
193 int r;
194
195 r = safe_read_file("/sys/bus/rbd/", "supported_features", buf,
196 sizeof(buf) - 1);
197 if (r < 0)
198 return r;
199
200 buf[r] = '\0';
201 try {
202 supported_features = std::stoull(buf, nullptr, 16);
203 } catch (...) {
204 return -EINVAL;
205 }
206
207 r = image.features(&features);
208 if (r < 0)
209 return r;
210
211 *unsupported_features = features & ~supported_features;
212 return 0;
213 }
214
215 /*
216 * hint user to check syslog for krbd related messages and provide suggestions
217 * based on errno return by krbd_map(). also note that even if some librbd calls
218 * fail, we at least dump the "try dmesg..." message to aid debugging.
219 */
220 static void print_error_description(const char *poolname, const char *imgname,
221 const char *snapname, int maperrno)
222 {
223 int r;
224 uint8_t oldformat;
225 librados::Rados rados;
226 librados::IoCtx ioctx;
227 librbd::Image image;
228
229 if (maperrno == -ENOENT)
230 goto done;
231
232 r = utils::init_and_open_image(poolname, imgname, "", snapname,
233 true, &rados, &ioctx, &image);
234 if (r < 0)
235 goto done;
236
237 r = image.old_format(&oldformat);
238 if (r < 0)
239 goto done;
240
241 /*
242 * kernel returns -ENXIO when mapping a V2 image due to unsupported feature
243 * set - so, hint about that too...
244 */
245 if (!oldformat && (maperrno == -ENXIO)) {
246 uint64_t unsupported_features;
247 bool need_terminate = true;
248
249 std::cout << "RBD image feature set mismatch. ";
250 r = get_unsupported_features(image, &unsupported_features);
251 if (r == 0 && (unsupported_features & ~RBD_FEATURES_ALL) == 0) {
252 uint64_t immutable = RBD_FEATURES_ALL & ~(RBD_FEATURES_MUTABLE |
253 RBD_FEATURES_DISABLE_ONLY);
254 if (unsupported_features & immutable) {
255 std::cout << "This image cannot be mapped because the following "
256 << "immutable features are unsupported by the kernel:";
257 unsupported_features &= immutable;
258 need_terminate = false;
259 } else {
260 std::cout << "You can disable features unsupported by the kernel "
261 << "with \"rbd feature disable ";
262
263 if (poolname != utils::get_default_pool_name()) {
264 std::cout << poolname << "/";
265 }
266 std::cout << imgname;
267 }
268 } else {
269 std::cout << "Try disabling features unsupported by the kernel "
270 << "with \"rbd feature disable";
271 unsupported_features = 0;
272 }
273 for (auto it : at::ImageFeatures::FEATURE_MAPPING) {
274 if (it.first & unsupported_features) {
275 std::cout << " " << it.second;
276 }
277 }
278 if (need_terminate)
279 std::cout << "\"";
280 std::cout << "." << std::endl;
281 }
282
283 done:
284 std::cout << "In some cases useful info is found in syslog - try \"dmesg | tail\"." << std::endl;
285 }
286
287 static int do_kernel_map(const char *poolname, const char *imgname,
288 const char *snapname)
289 {
290 #if defined(WITH_KRBD)
291 struct krbd_ctx *krbd;
292 std::ostringstream oss;
293 char *devnode;
294 int r;
295
296 r = krbd_create_from_context(g_ceph_context, &krbd);
297 if (r < 0)
298 return r;
299
300 for (std::map<std::string, std::string>::iterator it = map_options.begin();
301 it != map_options.end(); ) {
302 // for compatibility with < 3.7 kernels, assume that rw is on by
303 // default and omit it even if it was specified by the user
304 // (see ceph.git commit fb0f1986449b)
305 if (it->first == "rw" && it->second == "rw") {
306 map_options.erase(it);
307 } else {
308 if (it != map_options.begin())
309 oss << ",";
310 oss << it->second;
311 ++it;
312 }
313 }
314
315 r = krbd_map(krbd, poolname, imgname, snapname, oss.str().c_str(), &devnode);
316 if (r < 0) {
317 print_error_description(poolname, imgname, snapname, r);
318 goto out;
319 }
320
321 std::cout << devnode << std::endl;
322
323 free(devnode);
324 out:
325 krbd_destroy(krbd);
326 return r;
327 #else
328 return -1;
329 #endif
330 }
331
332 static int do_kernel_unmap(const char *dev, const char *poolname,
333 const char *imgname, const char *snapname)
334 {
335 #if defined(WITH_KRBD)
336 struct krbd_ctx *krbd;
337 std::ostringstream oss;
338 int r;
339
340 r = krbd_create_from_context(g_ceph_context, &krbd);
341 if (r < 0)
342 return r;
343
344 for (auto it = map_options.cbegin(); it != map_options.cend(); ++it) {
345 if (it != map_options.cbegin())
346 oss << ",";
347 oss << it->second;
348 }
349
350 if (dev)
351 r = krbd_unmap(krbd, dev, oss.str().c_str());
352 else
353 r = krbd_unmap_by_spec(krbd, poolname, imgname, snapname,
354 oss.str().c_str());
355
356 krbd_destroy(krbd);
357 return r;
358 #else
359 return -1;
360 #endif
361
362 }
363
364 void get_show_arguments(po::options_description *positional,
365 po::options_description *options) {
366 at::add_format_options(options);
367 }
368
369 int execute_show(const po::variables_map &vm) {
370 at::Format::Formatter formatter;
371 int r = utils::get_formatter(vm, &formatter);
372 if (r < 0) {
373 return r;
374 }
375
376 utils::init_context();
377
378 r = do_kernel_showmapped(formatter.get());
379 if (r < 0) {
380 std::cerr << "rbd: showmapped failed: " << cpp_strerror(r) << std::endl;
381 return r;
382 }
383 return 0;
384 }
385
386 void get_map_arguments(po::options_description *positional,
387 po::options_description *options) {
388 at::add_image_or_snap_spec_options(positional, options,
389 at::ARGUMENT_MODIFIER_NONE);
390 options->add_options()
391 ("options,o", po::value<std::string>(), "map options")
392 ("read-only", po::bool_switch(), "map read-only")
393 ("exclusive", po::bool_switch(), "disable automatic exclusive lock transitions");
394 }
395
396 int execute_map(const po::variables_map &vm) {
397 size_t arg_index = 0;
398 std::string pool_name;
399 std::string image_name;
400 std::string snap_name;
401 int r = utils::get_pool_image_snapshot_names(
402 vm, at::ARGUMENT_MODIFIER_NONE, &arg_index, &pool_name, &image_name,
403 &snap_name, utils::SNAPSHOT_PRESENCE_PERMITTED,
404 utils::SPEC_VALIDATION_NONE);
405 if (r < 0) {
406 return r;
407 }
408
409 if (vm["read-only"].as<bool>()) {
410 put_map_option("rw", "ro");
411 }
412 if (vm["exclusive"].as<bool>()) {
413 put_map_option("exclusive", "exclusive");
414 }
415
416 // parse default options first so they can be overwritten by cli options
417 char *default_map_options = strdup(g_conf->rbd_default_map_options.c_str());
418 BOOST_SCOPE_EXIT( (default_map_options) ) {
419 free(default_map_options);
420 } BOOST_SCOPE_EXIT_END;
421
422 if (parse_map_options(default_map_options)) {
423 std::cerr << "rbd: couldn't parse default map options" << std::endl;
424 return -EINVAL;
425 }
426
427 if (vm.count("options")) {
428 char *cli_map_options = strdup(vm["options"].as<std::string>().c_str());
429 BOOST_SCOPE_EXIT( (cli_map_options) ) {
430 free(cli_map_options);
431 } BOOST_SCOPE_EXIT_END;
432
433 if (parse_map_options(cli_map_options)) {
434 std::cerr << "rbd: couldn't parse map options" << std::endl;
435 return -EINVAL;
436 }
437 }
438
439 utils::init_context();
440
441 r = do_kernel_map(pool_name.c_str(), image_name.c_str(), snap_name.c_str());
442 if (r < 0) {
443 std::cerr << "rbd: map failed: " << cpp_strerror(r) << std::endl;
444 return r;
445 }
446
447 return 0;
448 }
449
450 void get_unmap_arguments(po::options_description *positional,
451 po::options_description *options) {
452 positional->add_options()
453 ("image-or-snap-or-device-spec",
454 "image, snapshot, or device specification\n"
455 "[<pool-name>/]<image-name>[@<snapshot-name>] or <device-path>");
456 at::add_pool_option(options, at::ARGUMENT_MODIFIER_NONE);
457 at::add_image_option(options, at::ARGUMENT_MODIFIER_NONE);
458 at::add_snap_option(options, at::ARGUMENT_MODIFIER_NONE);
459 options->add_options()
460 ("options,o", po::value<std::string>(), "unmap options");
461 }
462
463 int execute_unmap(const po::variables_map &vm) {
464 std::string device_name = utils::get_positional_argument(vm, 0);
465 if (!boost::starts_with(device_name, "/dev/")) {
466 device_name.clear();
467 }
468
469 size_t arg_index = 0;
470 std::string pool_name;
471 std::string image_name;
472 std::string snap_name;
473 int r;
474 if (device_name.empty()) {
475 r = utils::get_pool_image_snapshot_names(
476 vm, at::ARGUMENT_MODIFIER_NONE, &arg_index, &pool_name, &image_name,
477 &snap_name, utils::SNAPSHOT_PRESENCE_PERMITTED,
478 utils::SPEC_VALIDATION_NONE, false);
479 if (r < 0) {
480 return r;
481 }
482 }
483
484 if (device_name.empty() && image_name.empty()) {
485 std::cerr << "rbd: unmap requires either image name or device path"
486 << std::endl;
487 return -EINVAL;
488 }
489
490 if (vm.count("options")) {
491 char *cli_unmap_options = strdup(vm["options"].as<std::string>().c_str());
492 BOOST_SCOPE_EXIT( (cli_unmap_options) ) {
493 free(cli_unmap_options);
494 } BOOST_SCOPE_EXIT_END;
495
496 if (parse_unmap_options(cli_unmap_options)) {
497 std::cerr << "rbd: couldn't parse unmap options" << std::endl;
498 return -EINVAL;
499 }
500 }
501
502 utils::init_context();
503
504 r = do_kernel_unmap(device_name.empty() ? nullptr : device_name.c_str(),
505 pool_name.c_str(), image_name.c_str(),
506 snap_name.empty() ? nullptr : snap_name.c_str());
507 if (r < 0) {
508 std::cerr << "rbd: unmap failed: " << cpp_strerror(r) << std::endl;
509 return r;
510 }
511 return 0;
512 }
513
514 Shell::SwitchArguments switched_arguments({"read-only", "exclusive"});
515 Shell::Action action_show(
516 {"showmapped"}, {}, "Show the rbd images mapped by the kernel.", "",
517 &get_show_arguments, &execute_show);
518
519 Shell::Action action_map(
520 {"map"}, {}, "Map image to a block device using the kernel.", "",
521 &get_map_arguments, &execute_map);
522
523 Shell::Action action_unmap(
524 {"unmap"}, {}, "Unmap a rbd device that was used by the kernel.", "",
525 &get_unmap_arguments, &execute_unmap);
526
527 } // namespace kernel
528 } // namespace action
529 } // namespace rbd