1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
4 #include "tools/rbd/ArgumentTypes.h"
5 #include "tools/rbd/Shell.h"
6 #include "tools/rbd/Utils.h"
7 #include "include/Context.h"
8 #include "common/blkdev.h"
9 #include "common/debug.h"
10 #include "common/errno.h"
11 #include "common/Throttle.h"
12 #include "include/compat.h"
13 #include "include/encoding.h"
14 #include "common/debug.h"
15 #include "common/errno.h"
16 #include "common/safe_io.h"
18 #include <boost/program_options.hpp>
19 #include <boost/scoped_ptr.hpp>
20 #include "include/assert.h"
22 #define dout_context g_ceph_context
23 #define dout_subsys ceph_subsys_rbd
29 struct ImportDiffContext
{
33 utils::ProgressContext pc
;
34 OrderedThrottle throttle
;
37 ImportDiffContext(librbd::Image
*image
, int fd
, size_t size
, bool no_progress
)
38 : image(image
), fd(fd
), size(size
), pc("Importing image diff", no_progress
),
39 throttle((fd
== STDIN_FILENO
) ? 1 :
40 g_conf
->get_val
<int64_t>("rbd_concurrent_management_ops"),
45 void update_size(size_t new_size
)
47 if (fd
== STDIN_FILENO
) {
52 void update_progress(uint64_t off
)
55 pc
.update_progress(off
, size
);
60 void update_progress()
62 uint64_t off
= last_offset
;
63 if (fd
!= STDIN_FILENO
) {
64 off
= lseek(fd
, 0, SEEK_CUR
);
80 class C_ImportDiff
: public Context
{
82 C_ImportDiff(ImportDiffContext
*idiffctx
, bufferlist data
, uint64_t offset
,
83 uint64_t length
, bool discard
)
84 : m_idiffctx(idiffctx
), m_data(data
), m_offset(offset
), m_length(length
),
86 // use block offset (stdin) or import file position to report
88 if (m_idiffctx
->fd
== STDIN_FILENO
) {
89 m_prog_offset
= offset
;
91 m_prog_offset
= lseek(m_idiffctx
->fd
, 0, SEEK_CUR
);
97 if (m_idiffctx
->throttle
.pending_error()) {
98 return m_idiffctx
->throttle
.wait_for_ret();
101 C_OrderedThrottle
*ctx
= m_idiffctx
->throttle
.start_op(this);
102 librbd::RBD::AioCompletion
*aio_completion
=
103 new librbd::RBD::AioCompletion(ctx
, &utils::aio_context_callback
);
107 r
= m_idiffctx
->image
->aio_discard(m_offset
, m_length
, aio_completion
);
109 r
= m_idiffctx
->image
->aio_write2(m_offset
, m_length
, m_data
,
110 aio_completion
, LIBRADOS_OP_FLAG_FADVISE_NOCACHE
);
114 aio_completion
->release();
121 void finish(int r
) override
123 m_idiffctx
->update_progress(m_prog_offset
);
124 m_idiffctx
->throttle
.end_op(r
);
128 ImportDiffContext
*m_idiffctx
;
133 uint64_t m_prog_offset
;
136 static int do_image_snap_from(ImportDiffContext
*idiffctx
)
140 r
= utils::read_string(idiffctx
->fd
, 4096, &from
); // 4k limit to make sure we don't get a garbage string
146 r
= idiffctx
->image
->snap_exists2(from
.c_str(), &exists
);
152 std::cerr
<< "start snapshot '" << from
153 << "' does not exist in the image, aborting" << std::endl
;
157 idiffctx
->update_progress();
161 static int do_image_snap_to(ImportDiffContext
*idiffctx
, std::string
*tosnap
)
165 r
= utils::read_string(idiffctx
->fd
, 4096, &to
); // 4k limit to make sure we don't get a garbage string
171 r
= idiffctx
->image
->snap_exists2(to
.c_str(), &exists
);
177 std::cerr
<< "end snapshot '" << to
<< "' already exists, aborting" << std::endl
;
182 idiffctx
->update_progress();
187 static int do_image_resize(ImportDiffContext
*idiffctx
)
190 char buf
[sizeof(uint64_t)];
192 r
= safe_read_exact(idiffctx
->fd
, buf
, sizeof(buf
));
198 bl
.append(buf
, sizeof(buf
));
199 bufferlist::iterator p
= bl
.begin();
200 ::decode(end_size
, p
);
203 idiffctx
->image
->size(&cur_size
);
204 if (cur_size
!= end_size
) {
205 idiffctx
->image
->resize(end_size
);
208 idiffctx
->update_size(end_size
);
209 idiffctx
->update_progress();
213 static int do_image_io(ImportDiffContext
*idiffctx
, bool discard
, size_t sparse_size
)
217 r
= safe_read_exact(idiffctx
->fd
, buf
, sizeof(buf
));
223 bl
.append(buf
, sizeof(buf
));
224 bufferlist::iterator p
= bl
.begin();
226 uint64_t image_offset
, buffer_length
;
227 ::decode(image_offset
, p
);
228 ::decode(buffer_length
, p
);
231 bufferptr bp
= buffer::create(buffer_length
);
232 r
= safe_read_exact(idiffctx
->fd
, bp
.c_str(), buffer_length
);
237 size_t buffer_offset
= 0;
238 while (buffer_offset
< buffer_length
) {
239 size_t write_length
= 0;
241 utils::calc_sparse_extent(bp
, sparse_size
, buffer_offset
, buffer_length
,
242 &write_length
, &zeroed
);
243 assert(write_length
> 0);
247 bufferptr
write_ptr(bp
, buffer_offset
, write_length
);
248 write_bl
.push_back(write_ptr
);
249 assert(write_bl
.length() == write_length
);
252 C_ImportDiff
*ctx
= new C_ImportDiff(idiffctx
, write_bl
,
253 image_offset
+ buffer_offset
,
254 write_length
, zeroed
);
260 buffer_offset
+= write_length
;
264 C_ImportDiff
*ctx
= new C_ImportDiff(idiffctx
, data
, image_offset
,
265 buffer_length
, true);
271 static int validate_banner(int fd
, std::string banner
)
274 char buf
[banner
.size() + 1];
275 r
= safe_read_exact(fd
, buf
, banner
.size());
280 buf
[banner
.size()] = '\0';
281 if (strcmp(buf
, banner
.c_str())) {
282 std::cerr
<< "invalid banner '" << buf
<< "', expected '" << banner
<< "'" << std::endl
;
289 static int skip_tag(int fd
, uint64_t length
)
293 if (fd
== STDIN_FILENO
) {
294 // read the appending data out to skip this tag.
296 uint64_t len
= min
<uint64_t>(length
, sizeof(buf
));
298 r
= safe_read_exact(fd
, buf
, len
);
302 len
= min
<uint64_t>(length
, sizeof(buf
));
305 // lseek to skip this tag
306 off64_t offs
= lseek64(fd
, length
, SEEK_CUR
);
315 static int read_tag(int fd
, __u8 end_tag
, int format
, __u8
*tag
, uint64_t *readlen
)
320 r
= safe_read_exact(fd
, &read_tag
, sizeof(read_tag
));
326 if (read_tag
!= end_tag
&& format
== 2) {
327 char buf
[sizeof(uint64_t)];
328 r
= safe_read_exact(fd
, buf
, sizeof(buf
));
334 bl
.append(buf
, sizeof(buf
));
335 bufferlist::iterator p
= bl
.begin();
336 ::decode(*readlen
, p
);
342 int do_import_diff_fd(librados::Rados
&rados
, librbd::Image
&image
, int fd
,
343 bool no_progress
, int format
, size_t sparse_size
)
348 bool from_stdin
= (fd
== STDIN_FILENO
);
350 struct stat stat_buf
;
351 r
= ::fstat(fd
, &stat_buf
);
355 size
= (uint64_t)stat_buf
.st_size
;
358 r
= validate_banner(fd
, (format
== 1 ? utils::RBD_DIFF_BANNER
:
359 utils::RBD_DIFF_BANNER_V2
));
364 std::string skip_partial_discard
;
365 r
= rados
.conf_get("rbd_skip_partial_discard", skip_partial_discard
);
366 if (r
< 0 || skip_partial_discard
!= "false") {
367 dout(1) << "disabling sparse import" << dendl
;
372 // begin image import
374 ImportDiffContext
idiffctx(&image
, fd
, size
, no_progress
);
379 r
= read_tag(fd
, RBD_DIFF_END
, format
, &tag
, &length
);
380 if (r
< 0 || tag
== RBD_DIFF_END
) {
384 if (tag
== RBD_DIFF_FROM_SNAP
) {
385 r
= do_image_snap_from(&idiffctx
);
386 } else if (tag
== RBD_DIFF_TO_SNAP
) {
387 r
= do_image_snap_to(&idiffctx
, &tosnap
);
388 } else if (tag
== RBD_DIFF_IMAGE_SIZE
) {
389 r
= do_image_resize(&idiffctx
);
390 } else if (tag
== RBD_DIFF_WRITE
|| tag
== RBD_DIFF_ZERO
) {
391 r
= do_image_io(&idiffctx
, (tag
== RBD_DIFF_ZERO
), sparse_size
);
393 std::cerr
<< "unrecognized tag byte " << (int)tag
<< " in stream; skipping"
395 r
= skip_tag(fd
, length
);
399 int temp_r
= idiffctx
.throttle
.wait_for_ret();
400 r
= (r
< 0) ? r
: temp_r
; // preserve original error
401 if (r
== 0 && tosnap
.length()) {
402 idiffctx
.image
->snap_create(tosnap
.c_str());
409 int do_import_diff(librados::Rados
&rados
, librbd::Image
&image
,
410 const char *path
, bool no_progress
, size_t sparse_size
)
415 if (strcmp(path
, "-") == 0) {
418 fd
= open(path
, O_RDONLY
);
421 std::cerr
<< "rbd: error opening " << path
<< std::endl
;
425 r
= do_import_diff_fd(rados
, image
, fd
, no_progress
, 1, sparse_size
);
432 namespace at
= argument_types
;
433 namespace po
= boost::program_options
;
435 void get_arguments_diff(po::options_description
*positional
,
436 po::options_description
*options
) {
437 at::add_path_options(positional
, options
,
438 "import file (or '-' for stdin)");
439 at::add_image_spec_options(positional
, options
, at::ARGUMENT_MODIFIER_NONE
);
440 at::add_sparse_size_option(options
);
441 at::add_no_progress_option(options
);
444 int execute_diff(const po::variables_map
&vm
) {
446 int r
= utils::get_path(vm
, utils::get_positional_argument(vm
, 0), &path
);
451 size_t arg_index
= 1;
452 std::string pool_name
;
453 std::string image_name
;
454 std::string snap_name
;
455 r
= utils::get_pool_image_snapshot_names(
456 vm
, at::ARGUMENT_MODIFIER_NONE
, &arg_index
, &pool_name
, &image_name
,
457 &snap_name
, utils::SNAPSHOT_PRESENCE_NONE
, utils::SPEC_VALIDATION_NONE
);
462 size_t sparse_size
= utils::RBD_DEFAULT_SPARSE_SIZE
;
463 if (vm
.count(at::IMAGE_SPARSE_SIZE
)) {
464 sparse_size
= vm
[at::IMAGE_SPARSE_SIZE
].as
<size_t>();
467 librados::Rados rados
;
468 librados::IoCtx io_ctx
;
470 r
= utils::init_and_open_image(pool_name
, image_name
, "", "", false,
471 &rados
, &io_ctx
, &image
);
476 r
= do_import_diff(rados
, image
, path
.c_str(),
477 vm
[at::NO_PROGRESS
].as
<bool>(), sparse_size
);
479 cerr
<< "rbd: import-diff failed: " << cpp_strerror(r
) << std::endl
;
485 Shell::Action
action_diff(
486 {"import-diff"}, {}, "Import an incremental diff.", "", &get_arguments_diff
,
489 class C_Import
: public Context
{
491 C_Import(SimpleThrottle
&simple_throttle
, librbd::Image
&image
,
492 bufferlist
&bl
, uint64_t offset
)
493 : m_throttle(simple_throttle
), m_image(image
),
495 new librbd::RBD::AioCompletion(this, &utils::aio_context_callback
)),
496 m_bufferlist(bl
), m_offset(offset
)
502 m_throttle
.start_op();
504 int op_flags
= LIBRADOS_OP_FLAG_FADVISE_SEQUENTIAL
|
505 LIBRADOS_OP_FLAG_FADVISE_NOCACHE
;
506 int r
= m_image
.aio_write2(m_offset
, m_bufferlist
.length(), m_bufferlist
,
507 m_aio_completion
, op_flags
);
509 std::cerr
<< "rbd: error requesting write to destination image"
511 m_aio_completion
->release();
512 m_throttle
.end_op(r
);
516 void finish(int r
) override
519 std::cerr
<< "rbd: error writing to destination image at offset "
520 << m_offset
<< ": " << cpp_strerror(r
) << std::endl
;
522 m_throttle
.end_op(r
);
526 SimpleThrottle
&m_throttle
;
527 librbd::Image
&m_image
;
528 librbd::RBD::AioCompletion
*m_aio_completion
;
529 bufferlist m_bufferlist
;
533 static int decode_and_set_image_option(int fd
, uint64_t imageopt
, librbd::ImageOptions
& opts
)
536 char buf
[sizeof(uint64_t)];
538 r
= safe_read_exact(fd
, buf
, sizeof(buf
));
544 bl
.append(buf
, sizeof(buf
));
545 bufferlist::iterator it
;
551 if (opts
.get(imageopt
, &val
) != 0) {
552 opts
.set(imageopt
, val
);
558 static int do_import_header(int fd
, int import_format
, uint64_t &size
, librbd::ImageOptions
& opts
)
560 // There is no header in v1 image.
561 if (import_format
== 1) {
565 if (fd
== STDIN_FILENO
|| size
< utils::RBD_IMAGE_BANNER_V2
.size()) {
570 r
= validate_banner(fd
, utils::RBD_IMAGE_BANNER_V2
);
575 // As V1 format for image is already deprecated, import image in V2 by default.
576 uint64_t image_format
= 2;
577 if (opts
.get(RBD_IMAGE_OPTION_FORMAT
, &image_format
) != 0) {
578 opts
.set(RBD_IMAGE_OPTION_FORMAT
, image_format
);
584 r
= read_tag(fd
, RBD_EXPORT_IMAGE_END
, image_format
, &tag
, &length
);
585 if (r
< 0 || tag
== RBD_EXPORT_IMAGE_END
) {
589 if (tag
== RBD_EXPORT_IMAGE_ORDER
) {
590 r
= decode_and_set_image_option(fd
, RBD_IMAGE_OPTION_ORDER
, opts
);
591 } else if (tag
== RBD_EXPORT_IMAGE_FEATURES
) {
592 r
= decode_and_set_image_option(fd
, RBD_IMAGE_OPTION_FEATURES
, opts
);
593 } else if (tag
== RBD_EXPORT_IMAGE_STRIPE_UNIT
) {
594 r
= decode_and_set_image_option(fd
, RBD_IMAGE_OPTION_STRIPE_UNIT
, opts
);
595 } else if (tag
== RBD_EXPORT_IMAGE_STRIPE_COUNT
) {
596 r
= decode_and_set_image_option(fd
, RBD_IMAGE_OPTION_STRIPE_COUNT
, opts
);
598 std::cerr
<< "rbd: invalid tag in image properties zone: " << tag
<< "Skip it."
600 r
= skip_tag(fd
, length
);
607 static int do_import_v2(librados::Rados
&rados
, int fd
, librbd::Image
&image
,
608 uint64_t size
, size_t imgblklen
,
609 utils::ProgressContext
&pc
, size_t sparse_size
)
612 r
= validate_banner(fd
, utils::RBD_IMAGE_DIFFS_BANNER_V2
);
617 char buf
[sizeof(uint64_t)];
618 r
= safe_read_exact(fd
, buf
, sizeof(buf
));
623 bl
.append(buf
, sizeof(buf
));
624 bufferlist::iterator p
= bl
.begin();
626 ::decode(diff_num
, p
);
628 for (size_t i
= 0; i
< diff_num
; i
++) {
629 r
= do_import_diff_fd(rados
, image
, fd
, true, 2, sparse_size
);
632 std::cerr
<< "rbd: import-diff failed: " << cpp_strerror(r
) << std::endl
;
635 pc
.update_progress(i
+ 1, diff_num
);
641 static int do_import_v1(int fd
, librbd::Image
&image
, uint64_t size
,
642 size_t imgblklen
, utils::ProgressContext
&pc
,
646 size_t reqlen
= imgblklen
; // amount requested from read
647 ssize_t readlen
; // amount received from one read
648 size_t blklen
= 0; // amount accumulated from reads to fill blk
649 char *p
= new char[imgblklen
];
650 uint64_t image_pos
= 0;
651 bool from_stdin
= (fd
== STDIN_FILENO
);
652 boost::scoped_ptr
<SimpleThrottle
> throttle
;
655 throttle
.reset(new SimpleThrottle(1, false));
657 throttle
.reset(new SimpleThrottle(
658 g_conf
->get_val
<int64_t>("rbd_concurrent_management_ops"), false));
661 reqlen
= min
<uint64_t>(reqlen
, size
);
662 // loop body handles 0 return, as we may have a block to flush
663 while ((readlen
= ::read(fd
, p
+ blklen
, reqlen
)) >= 0) {
664 if (throttle
->pending_error()) {
669 // if read was short, try again to fill the block before writing
670 if (readlen
&& ((size_t)readlen
< reqlen
)) {
675 pc
.update_progress(image_pos
, size
);
677 bufferptr
blkptr(p
, blklen
);
678 // resize output image by binary expansion as we go for stdin
679 if (from_stdin
&& (image_pos
+ (size_t)blklen
) > size
) {
681 r
= image
.resize(size
);
683 std::cerr
<< "rbd: can't resize image during import" << std::endl
;
688 // write as much as we got; perhaps less than imgblklen
689 // but skip writing zeros to create sparse images
690 size_t buffer_offset
= 0;
691 while (buffer_offset
< blklen
) {
692 size_t write_length
= 0;
694 utils::calc_sparse_extent(blkptr
, sparse_size
, buffer_offset
, blklen
,
695 &write_length
, &zeroed
);
699 bufferptr
write_ptr(blkptr
, buffer_offset
, write_length
);
700 write_bl
.push_back(write_ptr
);
701 assert(write_bl
.length() == write_length
);
703 C_Import
*ctx
= new C_Import(*throttle
, image
, write_bl
,
704 image_pos
+ buffer_offset
);
708 buffer_offset
+= write_length
;
711 // done with whole block, whether written or not
713 if (!from_stdin
&& image_pos
>= size
)
715 // if read had returned 0, we're at EOF and should quit
721 r
= throttle
->wait_for_ret();
726 if (fd
== STDIN_FILENO
) {
727 r
= image
.resize(image_pos
);
729 std::cerr
<< "rbd: final image resize failed" << std::endl
;
738 static int do_import(librados::Rados
&rados
, librbd::RBD
&rbd
,
739 librados::IoCtx
& io_ctx
, const char *imgname
,
740 const char *path
, librbd::ImageOptions
& opts
,
741 bool no_progress
, int import_format
, size_t sparse_size
)
744 struct stat stat_buf
;
745 utils::ProgressContext
pc("Importing image", no_progress
);
750 if (opts
.get(RBD_IMAGE_OPTION_ORDER
, &order
) != 0) {
751 order
= g_conf
->get_val
<int64_t>("rbd_default_order");
754 // try to fill whole imgblklen blocks for sparsification
755 size_t imgblklen
= 1 << order
;
759 bool from_stdin
= !strcmp(path
, "-");
762 size
= 1ULL << order
;
764 if ((fd
= open(path
, O_RDONLY
)) < 0) {
766 std::cerr
<< "rbd: error opening " << path
<< std::endl
;
770 if ((fstat(fd
, &stat_buf
)) < 0) {
772 std::cerr
<< "rbd: stat error " << path
<< std::endl
;
775 if (S_ISDIR(stat_buf
.st_mode
)) {
777 std::cerr
<< "rbd: cannot import a directory" << std::endl
;
780 if (stat_buf
.st_size
)
781 size
= (uint64_t)stat_buf
.st_size
;
784 int64_t bdev_size
= 0;
785 r
= get_block_device_size(fd
, &bdev_size
);
787 std::cerr
<< "rbd: unable to get size of file/block device"
791 assert(bdev_size
>= 0);
792 size
= (uint64_t) bdev_size
;
794 #ifdef HAVE_POSIX_FADVISE
795 posix_fadvise(fd
, 0, 0, POSIX_FADV_SEQUENTIAL
);
799 r
= do_import_header(fd
, import_format
, size
, opts
);
801 std::cerr
<< "rbd: import header failed." << std::endl
;
805 r
= rbd
.create4(io_ctx
, imgname
, size
, opts
);
807 std::cerr
<< "rbd: image creation failed" << std::endl
;
811 r
= rbd
.open(io_ctx
, image
, imgname
);
813 std::cerr
<< "rbd: failed to open image" << std::endl
;
817 if (import_format
== 1) {
818 r
= do_import_v1(fd
, image
, size
, imgblklen
, pc
, sparse_size
);
820 r
= do_import_v2(rados
, fd
, image
, size
, imgblklen
, pc
, sparse_size
);
823 std::cerr
<< "rbd: failed to import image" << std::endl
;
831 rbd
.remove(io_ctx
, imgname
);
843 void get_arguments(po::options_description
*positional
,
844 po::options_description
*options
) {
845 at::add_path_options(positional
, options
,
846 "import file (or '-' for stdin)");
847 at::add_image_spec_options(positional
, options
, at::ARGUMENT_MODIFIER_DEST
);
848 at::add_create_image_options(options
, true);
849 at::add_sparse_size_option(options
);
850 at::add_no_progress_option(options
);
851 at::add_export_format_option(options
);
853 // TODO legacy rbd allowed import to accept both 'image'/'dest' and
854 // 'pool'/'dest-pool'
855 at::add_pool_option(options
, at::ARGUMENT_MODIFIER_NONE
, " (deprecated)");
856 at::add_image_option(options
, at::ARGUMENT_MODIFIER_NONE
, " (deprecated)");
859 int execute(const po::variables_map
&vm
) {
861 int r
= utils::get_path(vm
, utils::get_positional_argument(vm
, 0), &path
);
866 // odd check to support legacy / deprecated behavior of import
867 std::string deprecated_pool_name
;
868 if (vm
.count(at::POOL_NAME
)) {
869 deprecated_pool_name
= vm
[at::POOL_NAME
].as
<std::string
>();
870 std::cerr
<< "rbd: --pool is deprecated for import, use --dest-pool"
874 std::string deprecated_image_name
;
875 if (vm
.count(at::IMAGE_NAME
)) {
876 deprecated_image_name
= vm
[at::IMAGE_NAME
].as
<std::string
>();
877 std::cerr
<< "rbd: --image is deprecated for import, use --dest"
880 deprecated_image_name
= path
.substr(path
.find_last_of("/") + 1);
883 std::string deprecated_snap_name
;
884 r
= utils::extract_spec(deprecated_image_name
, &deprecated_pool_name
,
885 &deprecated_image_name
, &deprecated_snap_name
,
886 utils::SPEC_VALIDATION_FULL
);
891 size_t sparse_size
= utils::RBD_DEFAULT_SPARSE_SIZE
;
892 if (vm
.count(at::IMAGE_SPARSE_SIZE
)) {
893 sparse_size
= vm
[at::IMAGE_SPARSE_SIZE
].as
<size_t>();
896 size_t arg_index
= 1;
897 std::string pool_name
= deprecated_pool_name
;
898 std::string image_name
;
899 std::string snap_name
= deprecated_snap_name
;
900 r
= utils::get_pool_image_snapshot_names(
901 vm
, at::ARGUMENT_MODIFIER_DEST
, &arg_index
, &pool_name
, &image_name
,
902 &snap_name
, utils::SNAPSHOT_PRESENCE_NONE
, utils::SPEC_VALIDATION_FULL
,
908 if (image_name
.empty()) {
909 image_name
= deprecated_image_name
;
912 librbd::ImageOptions opts
;
913 r
= utils::get_image_options(vm
, true, &opts
);
918 librados::Rados rados
;
919 librados::IoCtx io_ctx
;
920 r
= utils::init(pool_name
, &rados
, &io_ctx
);
926 if (vm
.count("export-format"))
927 format
= vm
["export-format"].as
<uint64_t>();
930 r
= do_import(rados
, rbd
, io_ctx
, image_name
.c_str(), path
.c_str(),
931 opts
, vm
[at::NO_PROGRESS
].as
<bool>(), format
, sparse_size
);
933 std::cerr
<< "rbd: import failed: " << cpp_strerror(r
) << std::endl
;
940 Shell::Action
action(
941 {"import"}, {}, "Import image from file.", at::get_long_features_help(),
942 &get_arguments
, &execute
);
944 } // namespace import
945 } // namespace action