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
->rbd_concurrent_management_ops
, false), last_offset(0) {
43 void update_size(size_t new_size
)
45 if (fd
== STDIN_FILENO
) {
50 void update_progress(uint64_t off
)
53 pc
.update_progress(off
, size
);
58 void update_progress()
60 uint64_t off
= last_offset
;
61 if (fd
!= STDIN_FILENO
) {
62 off
= lseek(fd
, 0, SEEK_CUR
);
78 class C_ImportDiff
: public Context
{
80 C_ImportDiff(ImportDiffContext
*idiffctx
, bufferlist data
, uint64_t offset
,
81 uint64_t length
, bool discard
)
82 : m_idiffctx(idiffctx
), m_data(data
), m_offset(offset
), m_length(length
),
84 // use block offset (stdin) or import file position to report
86 if (m_idiffctx
->fd
== STDIN_FILENO
) {
87 m_prog_offset
= offset
;
89 m_prog_offset
= lseek(m_idiffctx
->fd
, 0, SEEK_CUR
);
95 if (m_idiffctx
->throttle
.pending_error()) {
96 return m_idiffctx
->throttle
.wait_for_ret();
99 C_OrderedThrottle
*ctx
= m_idiffctx
->throttle
.start_op(this);
100 librbd::RBD::AioCompletion
*aio_completion
=
101 new librbd::RBD::AioCompletion(ctx
, &utils::aio_context_callback
);
105 r
= m_idiffctx
->image
->aio_discard(m_offset
, m_length
, aio_completion
);
107 r
= m_idiffctx
->image
->aio_write2(m_offset
, m_length
, m_data
,
108 aio_completion
, LIBRADOS_OP_FLAG_FADVISE_NOCACHE
);
112 aio_completion
->release();
119 void finish(int r
) override
121 m_idiffctx
->update_progress(m_prog_offset
);
122 m_idiffctx
->throttle
.end_op(r
);
126 ImportDiffContext
*m_idiffctx
;
131 uint64_t m_prog_offset
;
134 static int do_image_snap_from(ImportDiffContext
*idiffctx
)
138 r
= utils::read_string(idiffctx
->fd
, 4096, &from
); // 4k limit to make sure we don't get a garbage string
144 r
= idiffctx
->image
->snap_exists2(from
.c_str(), &exists
);
150 std::cerr
<< "start snapshot '" << from
151 << "' does not exist in the image, aborting" << std::endl
;
155 idiffctx
->update_progress();
159 static int do_image_snap_to(ImportDiffContext
*idiffctx
, std::string
*tosnap
)
163 r
= utils::read_string(idiffctx
->fd
, 4096, &to
); // 4k limit to make sure we don't get a garbage string
169 r
= idiffctx
->image
->snap_exists2(to
.c_str(), &exists
);
175 std::cerr
<< "end snapshot '" << to
<< "' already exists, aborting" << std::endl
;
180 idiffctx
->update_progress();
185 static int do_image_resize(ImportDiffContext
*idiffctx
)
188 char buf
[sizeof(uint64_t)];
190 r
= safe_read_exact(idiffctx
->fd
, buf
, sizeof(buf
));
196 bl
.append(buf
, sizeof(buf
));
197 bufferlist::iterator p
= bl
.begin();
198 ::decode(end_size
, p
);
201 idiffctx
->image
->size(&cur_size
);
202 if (cur_size
!= end_size
) {
203 idiffctx
->image
->resize(end_size
);
206 idiffctx
->update_size(end_size
);
207 idiffctx
->update_progress();
211 static int do_image_io(ImportDiffContext
*idiffctx
, bool discard
, size_t sparse_size
)
215 r
= safe_read_exact(idiffctx
->fd
, buf
, sizeof(buf
));
221 bl
.append(buf
, sizeof(buf
));
222 bufferlist::iterator p
= bl
.begin();
224 uint64_t image_offset
, buffer_length
;
225 ::decode(image_offset
, p
);
226 ::decode(buffer_length
, p
);
229 bufferptr bp
= buffer::create(buffer_length
);
230 r
= safe_read_exact(idiffctx
->fd
, bp
.c_str(), buffer_length
);
235 size_t buffer_offset
= 0;
236 while (buffer_offset
< buffer_length
) {
237 size_t write_length
= 0;
239 utils::calc_sparse_extent(bp
, sparse_size
, buffer_offset
, buffer_length
,
240 &write_length
, &zeroed
);
241 assert(write_length
> 0);
245 bufferptr
write_ptr(bp
, buffer_offset
, write_length
);
246 write_bl
.push_back(write_ptr
);
247 assert(write_bl
.length() == write_length
);
250 C_ImportDiff
*ctx
= new C_ImportDiff(idiffctx
, write_bl
,
251 image_offset
+ buffer_offset
,
252 write_length
, zeroed
);
258 buffer_offset
+= write_length
;
262 C_ImportDiff
*ctx
= new C_ImportDiff(idiffctx
, data
, image_offset
,
263 buffer_length
, true);
269 static int validate_banner(int fd
, std::string banner
)
272 char buf
[banner
.size() + 1];
273 r
= safe_read_exact(fd
, buf
, banner
.size());
278 buf
[banner
.size()] = '\0';
279 if (strcmp(buf
, banner
.c_str())) {
280 std::cerr
<< "invalid banner '" << buf
<< "', expected '" << banner
<< "'" << std::endl
;
287 static int skip_tag(int fd
, uint64_t length
)
291 if (fd
== STDIN_FILENO
) {
292 // read the appending data out to skip this tag.
294 uint64_t len
= min
<uint64_t>(length
, sizeof(buf
));
296 r
= safe_read_exact(fd
, buf
, len
);
300 len
= min
<uint64_t>(length
, sizeof(buf
));
303 // lseek to skip this tag
304 off64_t offs
= lseek64(fd
, length
, SEEK_CUR
);
313 static int read_tag(int fd
, __u8 end_tag
, int format
, __u8
*tag
, uint64_t *readlen
)
318 r
= safe_read_exact(fd
, &read_tag
, sizeof(read_tag
));
324 if (read_tag
!= end_tag
&& format
== 2) {
325 char buf
[sizeof(uint64_t)];
326 r
= safe_read_exact(fd
, buf
, sizeof(buf
));
332 bl
.append(buf
, sizeof(buf
));
333 bufferlist::iterator p
= bl
.begin();
334 ::decode(*readlen
, p
);
340 int do_import_diff_fd(librados::Rados
&rados
, librbd::Image
&image
, int fd
,
341 bool no_progress
, int format
, size_t sparse_size
)
346 bool from_stdin
= (fd
== STDIN_FILENO
);
348 struct stat stat_buf
;
349 r
= ::fstat(fd
, &stat_buf
);
353 size
= (uint64_t)stat_buf
.st_size
;
356 r
= validate_banner(fd
, (format
== 1 ? utils::RBD_DIFF_BANNER
:
357 utils::RBD_DIFF_BANNER_V2
));
362 std::string skip_partial_discard
;
363 r
= rados
.conf_get("rbd_skip_partial_discard", skip_partial_discard
);
364 if (r
< 0 || skip_partial_discard
!= "false") {
365 dout(1) << "disabling sparse import" << dendl
;
370 // begin image import
372 ImportDiffContext
idiffctx(&image
, fd
, size
, no_progress
);
377 r
= read_tag(fd
, RBD_DIFF_END
, format
, &tag
, &length
);
378 if (r
< 0 || tag
== RBD_DIFF_END
) {
382 if (tag
== RBD_DIFF_FROM_SNAP
) {
383 r
= do_image_snap_from(&idiffctx
);
384 } else if (tag
== RBD_DIFF_TO_SNAP
) {
385 r
= do_image_snap_to(&idiffctx
, &tosnap
);
386 } else if (tag
== RBD_DIFF_IMAGE_SIZE
) {
387 r
= do_image_resize(&idiffctx
);
388 } else if (tag
== RBD_DIFF_WRITE
|| tag
== RBD_DIFF_ZERO
) {
389 r
= do_image_io(&idiffctx
, (tag
== RBD_DIFF_ZERO
), sparse_size
);
391 std::cerr
<< "unrecognized tag byte " << (int)tag
<< " in stream; skipping"
393 r
= skip_tag(fd
, length
);
397 int temp_r
= idiffctx
.throttle
.wait_for_ret();
398 r
= (r
< 0) ? r
: temp_r
; // preserve original error
399 if (r
== 0 && tosnap
.length()) {
400 idiffctx
.image
->snap_create(tosnap
.c_str());
407 int do_import_diff(librados::Rados
&rados
, librbd::Image
&image
,
408 const char *path
, bool no_progress
, size_t sparse_size
)
413 if (strcmp(path
, "-") == 0) {
416 fd
= open(path
, O_RDONLY
);
419 std::cerr
<< "rbd: error opening " << path
<< std::endl
;
423 r
= do_import_diff_fd(rados
, image
, fd
, no_progress
, 1, sparse_size
);
430 namespace at
= argument_types
;
431 namespace po
= boost::program_options
;
433 void get_arguments_diff(po::options_description
*positional
,
434 po::options_description
*options
) {
435 at::add_path_options(positional
, options
,
436 "import file (or '-' for stdin)");
437 at::add_image_spec_options(positional
, options
, at::ARGUMENT_MODIFIER_NONE
);
438 at::add_sparse_size_option(options
);
439 at::add_no_progress_option(options
);
442 int execute_diff(const po::variables_map
&vm
) {
444 int r
= utils::get_path(vm
, utils::get_positional_argument(vm
, 0), &path
);
449 size_t arg_index
= 1;
450 std::string pool_name
;
451 std::string image_name
;
452 std::string snap_name
;
453 r
= utils::get_pool_image_snapshot_names(
454 vm
, at::ARGUMENT_MODIFIER_NONE
, &arg_index
, &pool_name
, &image_name
,
455 &snap_name
, utils::SNAPSHOT_PRESENCE_NONE
, utils::SPEC_VALIDATION_NONE
);
460 size_t sparse_size
= utils::RBD_DEFAULT_SPARSE_SIZE
;
461 if (vm
.count(at::IMAGE_SPARSE_SIZE
)) {
462 sparse_size
= vm
[at::IMAGE_SPARSE_SIZE
].as
<size_t>();
465 librados::Rados rados
;
466 librados::IoCtx io_ctx
;
468 r
= utils::init_and_open_image(pool_name
, image_name
, "", "", false,
469 &rados
, &io_ctx
, &image
);
474 r
= do_import_diff(rados
, image
, path
.c_str(),
475 vm
[at::NO_PROGRESS
].as
<bool>(), sparse_size
);
477 cerr
<< "rbd: import-diff failed: " << cpp_strerror(r
) << std::endl
;
483 Shell::Action
action_diff(
484 {"import-diff"}, {}, "Import an incremental diff.", "", &get_arguments_diff
,
487 class C_Import
: public Context
{
489 C_Import(SimpleThrottle
&simple_throttle
, librbd::Image
&image
,
490 bufferlist
&bl
, uint64_t offset
)
491 : m_throttle(simple_throttle
), m_image(image
),
493 new librbd::RBD::AioCompletion(this, &utils::aio_context_callback
)),
494 m_bufferlist(bl
), m_offset(offset
)
500 m_throttle
.start_op();
502 int op_flags
= LIBRADOS_OP_FLAG_FADVISE_SEQUENTIAL
|
503 LIBRADOS_OP_FLAG_FADVISE_NOCACHE
;
504 int r
= m_image
.aio_write2(m_offset
, m_bufferlist
.length(), m_bufferlist
,
505 m_aio_completion
, op_flags
);
507 std::cerr
<< "rbd: error requesting write to destination image"
509 m_aio_completion
->release();
510 m_throttle
.end_op(r
);
514 void finish(int r
) override
517 std::cerr
<< "rbd: error writing to destination image at offset "
518 << m_offset
<< ": " << cpp_strerror(r
) << std::endl
;
520 m_throttle
.end_op(r
);
524 SimpleThrottle
&m_throttle
;
525 librbd::Image
&m_image
;
526 librbd::RBD::AioCompletion
*m_aio_completion
;
527 bufferlist m_bufferlist
;
531 static int decode_and_set_image_option(int fd
, uint64_t imageopt
, librbd::ImageOptions
& opts
)
534 char buf
[sizeof(uint64_t)];
536 r
= safe_read_exact(fd
, buf
, sizeof(buf
));
542 bl
.append(buf
, sizeof(buf
));
543 bufferlist::iterator it
;
549 if (opts
.get(imageopt
, &val
) != 0) {
550 opts
.set(imageopt
, val
);
556 static int do_import_header(int fd
, int import_format
, uint64_t &size
, librbd::ImageOptions
& opts
)
558 // There is no header in v1 image.
559 if (import_format
== 1) {
563 if (fd
== STDIN_FILENO
|| size
< utils::RBD_IMAGE_BANNER_V2
.size()) {
568 r
= validate_banner(fd
, utils::RBD_IMAGE_BANNER_V2
);
573 // As V1 format for image is already deprecated, import image in V2 by default.
574 uint64_t image_format
= 2;
575 if (opts
.get(RBD_IMAGE_OPTION_FORMAT
, &image_format
) != 0) {
576 opts
.set(RBD_IMAGE_OPTION_FORMAT
, image_format
);
582 r
= read_tag(fd
, RBD_EXPORT_IMAGE_END
, image_format
, &tag
, &length
);
583 if (r
< 0 || tag
== RBD_EXPORT_IMAGE_END
) {
587 if (tag
== RBD_EXPORT_IMAGE_ORDER
) {
588 r
= decode_and_set_image_option(fd
, RBD_IMAGE_OPTION_ORDER
, opts
);
589 } else if (tag
== RBD_EXPORT_IMAGE_FEATURES
) {
590 r
= decode_and_set_image_option(fd
, RBD_IMAGE_OPTION_FEATURES
, opts
);
591 } else if (tag
== RBD_EXPORT_IMAGE_STRIPE_UNIT
) {
592 r
= decode_and_set_image_option(fd
, RBD_IMAGE_OPTION_STRIPE_UNIT
, opts
);
593 } else if (tag
== RBD_EXPORT_IMAGE_STRIPE_COUNT
) {
594 r
= decode_and_set_image_option(fd
, RBD_IMAGE_OPTION_STRIPE_COUNT
, opts
);
596 std::cerr
<< "rbd: invalid tag in image properties zone: " << tag
<< "Skip it."
598 r
= skip_tag(fd
, length
);
605 static int do_import_v2(librados::Rados
&rados
, int fd
, librbd::Image
&image
,
606 uint64_t size
, size_t imgblklen
,
607 utils::ProgressContext
&pc
, size_t sparse_size
)
610 r
= validate_banner(fd
, utils::RBD_IMAGE_DIFFS_BANNER_V2
);
615 char buf
[sizeof(uint64_t)];
616 r
= safe_read_exact(fd
, buf
, sizeof(buf
));
621 bl
.append(buf
, sizeof(buf
));
622 bufferlist::iterator p
= bl
.begin();
624 ::decode(diff_num
, p
);
626 for (size_t i
= 0; i
< diff_num
; i
++) {
627 r
= do_import_diff_fd(rados
, image
, fd
, true, 2, sparse_size
);
630 std::cerr
<< "rbd: import-diff failed: " << cpp_strerror(r
) << std::endl
;
633 pc
.update_progress(i
+ 1, diff_num
);
639 static int do_import_v1(int fd
, librbd::Image
&image
, uint64_t size
,
640 size_t imgblklen
, utils::ProgressContext
&pc
,
644 size_t reqlen
= imgblklen
; // amount requested from read
645 ssize_t readlen
; // amount received from one read
646 size_t blklen
= 0; // amount accumulated from reads to fill blk
647 char *p
= new char[imgblklen
];
648 uint64_t image_pos
= 0;
649 bool from_stdin
= (fd
== STDIN_FILENO
);
650 boost::scoped_ptr
<SimpleThrottle
> throttle
;
653 throttle
.reset(new SimpleThrottle(1, false));
655 throttle
.reset(new SimpleThrottle(
656 g_conf
->rbd_concurrent_management_ops
, false));
659 reqlen
= min
<uint64_t>(reqlen
, size
);
660 // loop body handles 0 return, as we may have a block to flush
661 while ((readlen
= ::read(fd
, p
+ blklen
, reqlen
)) >= 0) {
662 if (throttle
->pending_error()) {
667 // if read was short, try again to fill the block before writing
668 if (readlen
&& ((size_t)readlen
< reqlen
)) {
673 pc
.update_progress(image_pos
, size
);
675 bufferptr
blkptr(p
, blklen
);
676 // resize output image by binary expansion as we go for stdin
677 if (from_stdin
&& (image_pos
+ (size_t)blklen
) > size
) {
679 r
= image
.resize(size
);
681 std::cerr
<< "rbd: can't resize image during import" << std::endl
;
686 // write as much as we got; perhaps less than imgblklen
687 // but skip writing zeros to create sparse images
688 size_t buffer_offset
= 0;
689 while (buffer_offset
< blklen
) {
690 size_t write_length
= 0;
692 utils::calc_sparse_extent(blkptr
, sparse_size
, buffer_offset
, blklen
,
693 &write_length
, &zeroed
);
697 bufferptr
write_ptr(blkptr
, buffer_offset
, write_length
);
698 write_bl
.push_back(write_ptr
);
699 assert(write_bl
.length() == write_length
);
701 C_Import
*ctx
= new C_Import(*throttle
, image
, write_bl
,
702 image_pos
+ buffer_offset
);
706 buffer_offset
+= write_length
;
709 // done with whole block, whether written or not
711 if (!from_stdin
&& image_pos
>= size
)
713 // if read had returned 0, we're at EOF and should quit
719 r
= throttle
->wait_for_ret();
724 if (fd
== STDIN_FILENO
) {
725 r
= image
.resize(image_pos
);
727 std::cerr
<< "rbd: final image resize failed" << std::endl
;
736 static int do_import(librados::Rados
&rados
, librbd::RBD
&rbd
,
737 librados::IoCtx
& io_ctx
, const char *imgname
,
738 const char *path
, librbd::ImageOptions
& opts
,
739 bool no_progress
, int import_format
, size_t sparse_size
)
742 struct stat stat_buf
;
743 utils::ProgressContext
pc("Importing image", no_progress
);
748 if (opts
.get(RBD_IMAGE_OPTION_ORDER
, &order
) != 0) {
749 order
= g_conf
->rbd_default_order
;
752 // try to fill whole imgblklen blocks for sparsification
753 size_t imgblklen
= 1 << order
;
757 bool from_stdin
= !strcmp(path
, "-");
760 size
= 1ULL << order
;
762 if ((fd
= open(path
, O_RDONLY
)) < 0) {
764 std::cerr
<< "rbd: error opening " << path
<< std::endl
;
768 if ((fstat(fd
, &stat_buf
)) < 0) {
770 std::cerr
<< "rbd: stat error " << path
<< std::endl
;
773 if (S_ISDIR(stat_buf
.st_mode
)) {
775 std::cerr
<< "rbd: cannot import a directory" << std::endl
;
778 if (stat_buf
.st_size
)
779 size
= (uint64_t)stat_buf
.st_size
;
782 int64_t bdev_size
= 0;
783 r
= get_block_device_size(fd
, &bdev_size
);
785 std::cerr
<< "rbd: unable to get size of file/block device"
789 assert(bdev_size
>= 0);
790 size
= (uint64_t) bdev_size
;
792 #ifdef HAVE_POSIX_FADVISE
793 posix_fadvise(fd
, 0, 0, POSIX_FADV_SEQUENTIAL
);
797 r
= do_import_header(fd
, import_format
, size
, opts
);
799 std::cerr
<< "rbd: import header failed." << std::endl
;
803 r
= rbd
.create4(io_ctx
, imgname
, size
, opts
);
805 std::cerr
<< "rbd: image creation failed" << std::endl
;
809 r
= rbd
.open(io_ctx
, image
, imgname
);
811 std::cerr
<< "rbd: failed to open image" << std::endl
;
815 if (import_format
== 1) {
816 r
= do_import_v1(fd
, image
, size
, imgblklen
, pc
, sparse_size
);
818 r
= do_import_v2(rados
, fd
, image
, size
, imgblklen
, pc
, sparse_size
);
821 std::cerr
<< "rbd: failed to import image" << std::endl
;
829 rbd
.remove(io_ctx
, imgname
);
841 void get_arguments(po::options_description
*positional
,
842 po::options_description
*options
) {
843 at::add_path_options(positional
, options
,
844 "import file (or '-' for stdin)");
845 at::add_image_spec_options(positional
, options
, at::ARGUMENT_MODIFIER_DEST
);
846 at::add_create_image_options(options
, true);
847 at::add_sparse_size_option(options
);
848 at::add_no_progress_option(options
);
849 at::add_export_format_option(options
);
851 // TODO legacy rbd allowed import to accept both 'image'/'dest' and
852 // 'pool'/'dest-pool'
853 at::add_pool_option(options
, at::ARGUMENT_MODIFIER_NONE
, " (deprecated)");
854 at::add_image_option(options
, at::ARGUMENT_MODIFIER_NONE
, " (deprecated)");
857 int execute(const po::variables_map
&vm
) {
859 int r
= utils::get_path(vm
, utils::get_positional_argument(vm
, 0), &path
);
864 // odd check to support legacy / deprecated behavior of import
865 std::string deprecated_pool_name
;
866 if (vm
.count(at::POOL_NAME
)) {
867 deprecated_pool_name
= vm
[at::POOL_NAME
].as
<std::string
>();
868 std::cerr
<< "rbd: --pool is deprecated for import, use --dest-pool"
872 std::string deprecated_image_name
;
873 if (vm
.count(at::IMAGE_NAME
)) {
874 deprecated_image_name
= vm
[at::IMAGE_NAME
].as
<std::string
>();
875 std::cerr
<< "rbd: --image is deprecated for import, use --dest"
878 deprecated_image_name
= path
.substr(path
.find_last_of("/") + 1);
881 std::string deprecated_snap_name
;
882 r
= utils::extract_spec(deprecated_image_name
, &deprecated_pool_name
,
883 &deprecated_image_name
, &deprecated_snap_name
,
884 utils::SPEC_VALIDATION_FULL
);
889 size_t sparse_size
= utils::RBD_DEFAULT_SPARSE_SIZE
;
890 if (vm
.count(at::IMAGE_SPARSE_SIZE
)) {
891 sparse_size
= vm
[at::IMAGE_SPARSE_SIZE
].as
<size_t>();
894 size_t arg_index
= 1;
895 std::string pool_name
= deprecated_pool_name
;
896 std::string image_name
;
897 std::string snap_name
= deprecated_snap_name
;
898 r
= utils::get_pool_image_snapshot_names(
899 vm
, at::ARGUMENT_MODIFIER_DEST
, &arg_index
, &pool_name
, &image_name
,
900 &snap_name
, utils::SNAPSHOT_PRESENCE_NONE
, utils::SPEC_VALIDATION_FULL
,
906 if (image_name
.empty()) {
907 image_name
= deprecated_image_name
;
910 librbd::ImageOptions opts
;
911 r
= utils::get_image_options(vm
, true, &opts
);
916 librados::Rados rados
;
917 librados::IoCtx io_ctx
;
918 r
= utils::init(pool_name
, &rados
, &io_ctx
);
924 if (vm
.count("export-format"))
925 format
= vm
["export-format"].as
<uint64_t>();
928 r
= do_import(rados
, rbd
, io_ctx
, image_name
.c_str(), path
.c_str(),
929 opts
, vm
[at::NO_PROGRESS
].as
<bool>(), format
, sparse_size
);
931 std::cerr
<< "rbd: import failed: " << cpp_strerror(r
) << std::endl
;
938 Shell::Action
action(
939 {"import"}, {}, "Import image from file.", at::get_long_features_help(),
940 &get_arguments
, &execute
);
942 } // namespace import
943 } // namespace action