#!/usr/bin/perl use strict; use warnings; use Sys::Virt; use Fcntl; my $FILE; sub download_handler { my $st = shift; my $data = shift; my $nbytes = shift; return syswrite FILE, $data, $nbytes; } sub download_hole_handler { my $st = shift; my $offset = shift; my $pos = sysseek FILE, $offset, Fcntl::SEEK_CUR or die "Unable to seek in $FILE: $!"; truncate FILE, $pos; } sub download { my $vol = shift; my $st = shift; my $filename = shift; my $offset = 0; my $length = 0; open FILE, ">$filename" or die "unable to create $filename: $!"; eval { $vol->download($st, $offset, $length, Sys::Virt::StorageVol::VOL_DOWNLOAD_SPARSE_STREAM); $st->sparse_recv_all(\&download_handler, \&download_hole_handler); $st->finish(); }; if ($@) { unlink $filename if $@; close FILE; die $@; } close FILE or die "cannot save $filename: $!" } sub upload_handler { my $st = $_[0]; my $nbytes = $_[2]; return sysread FILE, $_[1], $nbytes; } sub upload_hole_handler { my $st = shift; my $in_data; my $section_len; # HACK, Perl lacks SEEK_DATA and SEEK_HOLE. my $SEEK_DATA = 3; my $SEEK_HOLE = 4; my $cur = sysseek FILE, 0, Fcntl::SEEK_CUR; eval { my $data = sysseek FILE, $cur, $SEEK_DATA; # There are three options: # 1) $data == $cur; $cur is in data # 2) $data > $cur; $cur is in a hole, next data at $data # 3) $data < 0; either $cur is in trailing hole, or $cur is beyond EOF. if (!defined($data)) { # case 3 $in_data = 0; my $end = sysseek FILE, 0, Fcntl::SEEK_END or die "Unable to get EOF position: $!"; $section_len = $end - $cur; } elsif ($data > $cur) { #case 2 $in_data = 0; $section_len = $data - $cur; } else { #case 1 my $hole = sysseek FILE, $data, $SEEK_HOLE; if (!defined($hole) or $hole eq $data) { die "Blah"; } $in_data = 1; $section_len = $hole - $data; } }; die "Blah" if ($@); # reposition file back sysseek FILE, $cur, Fcntl::SEEK_SET; return ($in_data, $section_len); } sub upload_skip_handler { my $st = shift; my $offset = shift; sysseek FILE, $offset, Fcntl::SEEK_CUR or die "Unable to seek in $FILE"; return 0; } sub upload { my $vol = shift; my $st = shift; my $filename = shift; my $offset = 0; my $length = 0; open FILE, "<$filename" or die "unable to open $filename: $!"; eval { $vol->upload($st, $offset, $length, Sys::Virt::StorageVol::VOL_UPLOAD_SPARSE_STREAM); $st->sparse_send_all(\&upload_handler, \&upload_hole_handler, \&upload_skip_handler); $st->finish(); }; if ($@) { close FILE; die $@; } close FILE or die "cannot close $filename: $!" } die "syntax: $0 URI --download/--upload VOLUME FILE" unless int(@ARGV) == 4; my $uri = shift @ARGV; my $action = shift @ARGV; my $volpath = shift @ARGV; my $filename = shift @ARGV; my $c = Sys::Virt->new(uri => $uri) or die "Unable to connect to $uri"; my $vol = $c->get_storage_volume_by_key($volpath) or die "No such volume"; my $st = $c->new_stream(); if ($action eq "--download") { download($vol, $st, $filename); } elsif ($action eq "--upload") { upload($vol, $st, $filename); } else { die "unknown action $action"; }