package PVE::API2::VZDump;

use strict;
use warnings;

use PVE::AccessControl;
use PVE::Cluster;
use PVE::Exception qw(raise_param_exc);
use PVE::INotify;
use PVE::JSONSchema qw(get_standard_option);
use PVE::RPCEnvironment;
use PVE::Storage;
use PVE::Tools qw(extract_param);
use PVE::VZDump::Common;
use PVE::VZDump;

use PVE::API2::Backup;
use PVE::API2Tools;

use Data::Dumper; # fixme: remove

use base qw(PVE::RESTHandler);

my sub assert_param_permission_vzdump {
    my ($rpcenv, $user, $param) = @_;
    return if $user eq 'root@pam'; # always OK

    PVE::API2::Backup::assert_param_permission_common($rpcenv, $user, $param);

    if (defined($param->{maxfiles}) || defined($param->{'prune-backups'})) {
	if (my $storeid = PVE::VZDump::get_storage_param($param)) {
	    $rpcenv->check($user, "/storage/$storeid", [ 'Datastore.Allocate' ]);
	}
    }
}

__PACKAGE__->register_method ({
    name => 'vzdump',
    path => '',
    method => 'POST',
    description => "Create backup.",
    permissions => {
	description => "The user needs 'VM.Backup' permissions on any VM, and "
	    ."'Datastore.AllocateSpace' on the backup storage (and fleecing storage when fleecing "
	    ."is used). The 'tmpdir', 'dumpdir', 'script' and 'job-id' parameters are restricted "
	    ."to the 'root\@pam' user. The 'maxfiles' and 'prune-backups' settings require "
	    ."'Datastore.Allocate' on the backup storage. The 'bwlimit', 'performance' and "
	    ."'ionice' parameters require 'Sys.Modify' on '/'.",
	user => 'all',
    },
    protected => 1,
    proxyto => 'node',
    parameters => {
	additionalProperties => 0,
	properties => PVE::VZDump::Common::json_config_properties({
	    'job-id' => get_standard_option('pve-backup-jobid', {
		description => "The ID of the backup job. If set, the 'backup-job' metadata field"
		    . " of the backup notification will be set to this value. Only root\@pam"
		    . " can set this parameter.",
		optional => 1,
	    }),
	    stdout => {
		type => 'boolean',
		description => "Write tar to stdout, not to a file.",
		optional => 1,
	    },
        }),
    },
    returns => { type => 'string' },
    code => sub {
	my ($param) = @_;

	my $rpcenv = PVE::RPCEnvironment::get();

	my $user = $rpcenv->get_user();

	my $nodename = PVE::INotify::nodename();

	if ($rpcenv->{type} ne 'cli') {
	    raise_param_exc({ node => "option is only allowed on the command line interface."})
		if $param->{node} && $param->{node} ne $nodename;

	    raise_param_exc({ stdout => "option is only allowed on the command line interface."})
		if $param->{stdout};
	}

	assert_param_permission_vzdump($rpcenv, $user, $param);

	PVE::VZDump::verify_vzdump_parameters($param, 1);

	# silent exit if we run on wrong node
	return 'OK' if $param->{node} && $param->{node} ne $nodename;

	my $cmdline = PVE::VZDump::Common::command_line($param);

	my $vmids_per_node = PVE::VZDump::get_included_guests($param);

	my $local_vmids = delete $vmids_per_node->{$nodename} // [];

	# include IDs for deleted guests, and visibly fail later
	my $orphaned_vmids = delete $vmids_per_node->{''} // [];
	push @{$local_vmids}, @{$orphaned_vmids};

	my $skiplist = [ map { @$_ } values $vmids_per_node->%* ];

	if($param->{stop}){
	    PVE::VZDump::stop_running_backups();
	    return 'OK' if !scalar(@{$local_vmids});
	}

	# silent exit if specified VMs run on other nodes
	return "OK" if !scalar(@{$local_vmids}) && !$param->{all};

	PVE::VZDump::parse_mailto_exclude_path($param);

	die "you can only backup a single VM with option --stdout\n"
	    if $param->{stdout} && scalar(@{$local_vmids}) != 1;

	if (my $storeid = PVE::VZDump::get_storage_param($param)) {
	    $rpcenv->check($user, "/storage/$storeid", [ 'Datastore.AllocateSpace' ]);
	}

	my $worker = sub {
	    my $upid = shift;

	    $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub {
		die "interrupted by signal\n";
	    };

	    $param->{vmids} = $local_vmids;
	    my $vzdump = PVE::VZDump->new($cmdline, $param, $skiplist);

	    my $LOCK_FH = eval {
		$vzdump->getlock($upid); # only one process allowed
	    };
	    if (my $err = $@) {
		$vzdump->send_notification([], 0, $err);
		exit(-1);
	    }

	    if (defined($param->{ionice})) {
		if ($param->{ionice} > 7) {
		    PVE::VZDump::run_command(undef, "ionice -c3 -p $$");
		} else {
		    PVE::VZDump::run_command(undef, "ionice -c2 -n$param->{ionice} -p $$");
		}
	    }
	    $vzdump->exec_backup($rpcenv, $user);

	    close($LOCK_FH);
	};

	open STDOUT, '>/dev/null' if $param->{quiet} && !$param->{stdout};
	open STDERR, '>/dev/null' if $param->{quiet};

	if ($rpcenv->{type} eq 'cli') {
	    if ($param->{stdout}) {

		open my $saved_stdout, ">&STDOUT"
		    || die "can't dup STDOUT: $!\n";

		open STDOUT, '>&STDERR' ||
		    die "unable to redirect STDOUT: $!\n";

		$param->{stdout} = $saved_stdout;
	    }
	}

	my $taskid;
	$taskid = $local_vmids->[0] if scalar(@{$local_vmids}) == 1;

	return $rpcenv->fork_worker('vzdump', $taskid, $user, $worker);
   }});

__PACKAGE__->register_method ({
    name => 'defaults',
    path => 'defaults',
    method => 'GET',
    description => "Get the currently configured vzdump defaults.",
    permissions => {
	description => "The user needs 'Datastore.Audit' or 'Datastore.AllocateSpace' " .
	    "permissions for the specified storage (or default storage if none specified). Some " .
	    "properties are only returned when the user has 'Sys.Audit' permissions for the node.",
	user => 'all',
    },
    proxyto => 'node',
    parameters => {
	additionalProperties => 0,
	properties => {
	    node => get_standard_option('pve-node'),
	    storage => get_standard_option('pve-storage-id', { optional => 1 }),
	},
    },
    returns => {
	type => 'object',
	additionalProperties => 0,
	properties => PVE::VZDump::Common::json_config_properties(),
    },
    code => sub {
	my ($param) = @_;

	my $node = extract_param($param, 'node');
	my $storage = extract_param($param, 'storage');

	my $rpcenv = PVE::RPCEnvironment::get();
	my $authuser = $rpcenv->get_user();

	my $res = PVE::VZDump::read_vzdump_defaults();

	$res->{storage} = $storage if defined($storage);

	if (!defined($res->{dumpdir}) && !defined($res->{storage})) {
	    $res->{storage} = 'local';
	}

	if (defined($res->{storage})) {
	    $rpcenv->check_any(
		$authuser,
		"/storage/$res->{storage}",
		['Datastore.Audit', 'Datastore.AllocateSpace'],
	    );

	    my $info = PVE::VZDump::storage_info($res->{storage});
	    for my $key (qw(dumpdir prune-backups)) {
		$res->{$key} = $info->{$key} if defined($info->{$key});
	    }
	}

	if (defined($res->{'prune-backups'})) {
	    $res->{'prune-backups'} = PVE::JSONSchema::print_property_string(
		$res->{'prune-backups'},
		'prune-backups',
	    );
	}

	$res->{mailto} = join(",", @{$res->{mailto}})
	    if defined($res->{mailto});

	$res->{'exclude-path'} = join(",", @{$res->{'exclude-path'}})
	    if defined($res->{'exclude-path'});

	# normal backup users don't need to know these
	if (!$rpcenv->check($authuser, "/nodes/$node", ['Sys.Audit'], 1)) {
	    delete $res->{mailto};
	    delete $res->{tmpdir};
	    delete $res->{dumpdir};
	    delete $res->{script};
	    delete $res->{ionice};
	}

	my $pool = $res->{pool};
	if (defined($pool) &&
	    !$rpcenv->check($authuser, "/pool/$pool", ['Pool.Audit'], 1)) {
	    delete $res->{pool};
	}

	return $res;
    }});

__PACKAGE__->register_method ({
    name => 'extractconfig',
    path => 'extractconfig',
    method => 'GET',
    description => "Extract configuration from vzdump backup archive.",
    permissions => {
	description => "The user needs 'VM.Backup' permissions on the backed up guest ID, and 'Datastore.AllocateSpace' on the backup storage.",
	user => 'all',
    },
    protected => 1,
    proxyto => 'node',
    parameters => {
	additionalProperties => 0,
	properties => {
	    node => get_standard_option('pve-node'),
	    volume => {
		description => "Volume identifier",
		type => 'string',
		completion => \&PVE::Storage::complete_volume,
	    },
	},
    },
    returns => { type => 'string' },
    code => sub {
	my ($param) = @_;

	my $volume = extract_param($param, 'volume');

	my $rpcenv = PVE::RPCEnvironment::get();
	my $authuser = $rpcenv->get_user();

	my $storage_cfg = PVE::Storage::config();
	PVE::Storage::check_volume_access(
	    $rpcenv,
	    $authuser,
	    $storage_cfg,
	    undef,
	    $volume,
	    'backup',
	);

	if (PVE::Storage::parse_volume_id($volume, 1)) {
	    my (undef, undef, $ownervm) = PVE::Storage::parse_volname($storage_cfg, $volume);
	    $rpcenv->check($authuser, "/vms/$ownervm", ['VM.Backup']);
	}

	return PVE::Storage::extract_vzdump_config($storage_cfg, $volume);
    }});

1;
