#!/usr/bin/env ruby
#
# frozen_string_literal: true

# ---------------------------------------------------------------------------- #
# Copyright 2002-2025, OpenNebula Project, OpenNebula Systems                  #
#                                                                              #
# Licensed under the Apache License, Version 2.0 (the "License"); you may      #
# not use this file except in compliance with the License. You may obtain      #
# a copy of the License at                                                     #
#                                                                              #
# http://www.apache.org/licenses/LICENSE-2.0                                   #
#                                                                              #
# Unless required by applicable law or agreed to in writing, software          #
# distributed under the License is distributed on an "AS IS" BASIS,            #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.     #
# See the License for the specific language governing permissions and          #
# limitations under the License.                                               #
# ---------------------------------------------------------------------------- #

# cpds: copies an image back to its datastore (executed for the disk-saveas operation)
# ARGUMENTS: host:remote_system_ds/disk.i fe:SOURCE snap_id vm_id ds_id
#   - fe is the front-end hostname
#   - SOURCE is the path of the disk image in the form DS_BASE_PATH/disk
#   - host is the target host to deploy the VM
#   - remote_system_ds is the path for the system datastore in the host
#   - snap_id the ID of the snapshot to save. If the ID is -1 it saves the current state and not a
#     snapshot.
#   - vm_id is the id of the VM
#   - ds_id is the target datastore (the original datastore for the image)

require_relative '../lib/tm_action'
require_relative '../lib/kvm'
require_relative '../lib/shell'

include TransferManager::KVM

arg_src    = ARGV[0]
arg_dst    = ARGV[1]
arg_snapid = ARGV[2]
arg_vmid   = ARGV[3]
_arg_dsid  = ARGV[4]

#-------------------------------------------------------------------------------
# Helper functions
#-------------------------------------------------------------------------------
def hotplug_export(cpds, src, deploy_id, target, temp_path)
    OpenNebula::DriverLogger.log_info 'VM is running, trying blockcopy, fsfreeze, suspend'

    freeze, thaw = fsfreeze(cpds.vm, deploy_id, 'TRY')
    blkcmd       = blockcopy(deploy_id, target, temp_path)

    cpds_cmd_export = <<~SCRIPT
        #{TransferManager::Shell.retry_if_no_error}

        touch #{temp_path}

        if ! retry_if_no_error "active block job" 3 5 #{blkcmd}; then
            set -e -o pipefail

            #{freeze}
            cp #{src.path} #{temp_path}
            #{thaw}
        fi
    SCRIPT

    rc = cpds.ssh(:host => src.host,
                  :cmds => cpds_cmd_export,
                  :err_msg => <<~ERRMSG.chomp,
                      Error creating export for domain #{deploy_id} of disk \
                      #{target} at #{temp_path}
                  ERRMSG
                  :nostdout => false,
                  :nostderr => false)

    src.update("#{src.host}:#{temp_path}")

    [rc.code == 0, true]
end

def poweroff_export(cpds, src, deploy_id, target, temp_path)
    cpds_cmd_export = <<~SCRIPT
        set -e -o pipefail
        if [ -L "#{src.path}" ] && file -L "#{src.path}" | grep -q 'has backing file'; then
            qemu-img convert -O qcow2 "$(readlink -f "#{src.path}")" "#{temp_path}";
            exit 42;
        fi
    SCRIPT

    rc = cpds.ssh(:host => src.host,
                  :cmds => cpds_cmd_export,
                  :ok_rc => 42,
                  :err_msg => <<~ERRMSG.chomp,
                      Error creating export for domain #{deploy_id}  of disk \
                      #{target} at #{temp_path}
                  ERRMSG
                  :nostdout => false,
                  :nostderr => false)

    src.update("#{src.host}:#{temp_path}") if rc.code == 42

    [[0, 42].include?(rc.code), rc.code == 42]
end

#-------------------------------------------------------------------------------
# Parse and set SRC/DST Paths. SRC will point to the snapshot, if set via arg
#-------------------------------------------------------------------------------
cpds = TransferManager::Action.new(:vm_id => arg_vmid, :action_name => 'cpds')

src = TransferManager::Action::Location.new(arg_src)
dst = TransferManager::Action::Location.new(arg_dst)

src.path = Pathname.new("#{src.path}.snap/#{arg_snapid}") if arg_snapid != '-1'

#-------------------------------------------------------------------------------
# Get Image and VM information
#-------------------------------------------------------------------------------
disk_id     = src.disk_id
disk_target = cpds.disk_target(disk_id)

deploy_id     = cpds.vm.deploy_id
vm_mad        = cpds.vm_mad
lcm_state_str = cpds.vm.lcm_state_str

#-------------------------------------------------------------------------------
# Generate a tmp disk image to save it back
#-------------------------------------------------------------------------------
# For current image of the running VMs, don't touch the image directly,
# but export the content via blockcopy. If that's not possible (old QEMU),
# domfsfreeze or suspend the domain before.

if lcm_state_str == 'HOTPLUG_SAVEAS' && vm_mad != 'kvm'
    OpenNebula::DriverLogger.report "cpds: Live operation not supported on #{vm_mad}"
    exit(1)
end

src_temp_path = `mktemp -u #{src.path}.XXXXXXXX`.chomp

success, do_cleanup =
    if lcm_state_str == 'HOTPLUG_SAVEAS' && arg_snapid == '-1'
        hotplug_export(cpds, src, deploy_id, disk_target, src_temp_path)
    else
        poweroff_export(cpds, src, deploy_id, disk_target, src_temp_path)
    end

if !success
    OpenNebula::DriverLogger.report "cpds: Cannot export disk (#{src.path}) for saving"
    exit(1)
end

#-------------------------------------------------------------------------------
# Move the generated image (now in src object)
#-------------------------------------------------------------------------------
OpenNebula::DriverLogger.log_info "Moving #{src} to datastore as #{dst}"

begin
    src_cmd = "tar -C #{src.dir} --transform='flags=r;s|#{src.base}|#{dst.base}|' " \
              "-chSf - #{src.base}"
    dst_cmd = "tar -xSf - -C #{dst.dir}"

    rc = LocalCommand.run_sh(<<~SCRIPT)
        set -e -o pipefail
        ssh #{src.host} \"#{src_cmd}\" | #{dst_cmd}
    SCRIPT

    exit(rc.code) if rc.code != 0
ensure
    cpds.ssh(:host => src.host,
             :cmds => "rm #{src_temp_path}",
             :err_msg => "Error deleting #{src_temp_path}",
             :nostdout => false,
             :nostderr => false) if do_cleanup
end
