-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at:
--         http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.


local skynet = require 'skynet'
local log = require 'mc.logging'
local file_sec = require 'utils.file'
local client = require 'manufacture.client'
local curl = require 'lcurl.core'
local utils = require 'mc.utils'
local utils_core = require 'utils.core'
local context = require 'mc.context'

local manufacture_dft = {}
local ipv4_obj = {}
local eth_obj = {}

local function get_network_obj()
    while true do
        client:ForeachIpv4Objects(function(o)
            if o.path == '/bmc/kepler/Managers/1/EthernetInterfaces/Ipv4' then
                ipv4_obj = o
            end
        end)

        client:ForeachEthernetInterfacesObjects(function(o)
            if o.path == '/bmc/kepler/Managers/1/EthernetInterfaces' then
                eth_obj = o
            end
        end)
        if ipv4_obj.IpMode and eth_obj.EthName then
            return
        end
        skynet.sleep(200)
    end
end

local function reset_network()
    if ipv4_obj.IpMode == 'DHCP' then
        log:notice('dft is dhcp mode, not need set ip')
        return
    end

    local ok, ret = pcall(ipv4_obj.SetIpMaskGateway, ipv4_obj,
                          context.get_context_or_default(), ipv4_obj.IpAddr,
                          ipv4_obj.SubnetMask, ipv4_obj.DefaultGateway)
    if not ok then
        log:error('set ipv4 static failed, ret(%s)', ret)
    end

    log:notice('ipaddr(%s) mask(%s) gateway(%s)', ipv4_obj.IpAddr, ipv4_obj.SubnetMask, ipv4_obj.DefaultGateway)
end

local function dft_change_tmp_capbility()
    local cmd_buf = '/bin/unmount tmp'
    os.execute(cmd_buf) -- 为了装备脚本可以执行不使用安全函数
    skynet.sleep(100)
    cmd_buf = '/bin/mount -t tmpfs -o nodev,nosuid,none,size=480M /tmp'
    os.execute(cmd_buf) -- 为了装备脚本可以执行不使用安全函数
end

local function dft_delete_iptables()
    -- 如果不支持multi，filter改为nat
    local cmd_buf = '/usr/sbin/iptables -F -t filter -w'
    os.execute(cmd_buf) -- 为了装备脚本可以执行不使用安全函数
end

local function start_dhcp_client()
    if ipv4_obj.IpMode == 'DHCP' then
        log:notice('dft is dhcp mode already')
        return 0
    end

    local PATH_DHCLIENT_OBJ = "/sbin/udhcpc"
    local DHCLIENT_ONLY_IP_DEFAULT_FILE = "/usr/share/udhcpc/default.script"
    local cmd_buf = string.format('%s -i %s -s %s -T 60 -t 10 > /dev/null 2>&1 &',
        PATH_DHCLIENT_OBJ, eth_obj.EthName, DHCLIENT_ONLY_IP_DEFAULT_FILE)
    local ok, ret = pcall(os.execute, cmd_buf) -- 为了装备脚本可以执行不使用安全函数
    if not ok then
        log:error('start dhcp client failed, ret(%s)', ret)
        return -1
    end

    return 0;
end

-- 将下载文件的.bin后缀改为.sh
local function get_dst_filename(package_name)
    local dst_filename = '/tmp/smm_down.sh'
    if package_name then
        local tmp_name = string.sub(package_name, 1, #package_name - 4)
        dst_filename = '/tmp/' .. tmp_name .. '.sh'
    end
    return dst_filename
end

local function get_tftp_url(package_name)
    local TFTP_DL_SERVER_IP<const> = "192.168.9.10"
    return string.format('tftp://%s/%s;mode=binary', TFTP_DL_SERVER_IP, package_name)
end

local function curl_easy_setopts(curl_handle, curl_url, curl_file)
    local DOWNLOAD_FILE_MAX_SIZE<const> = 0x100000

    curl.easy_setopt_url(curl_handle, curl_url)
    curl.easy_setopt_connecttimeout(curl_handle, 10)
    curl.easy_setopt_timeout(curl_handle, 60)
    curl.easy_setopt_write_data(curl_handle, curl_file)
    curl.easy_setopt_maxfilesize(curl_handle, DOWNLOAD_FILE_MAX_SIZE)
    curl.easy_setopt_tftp_blksize(curl_handle, 4096)
    curl.easy_setopt_verbose(curl_handle, 1)
end

-- 使用curl下载装备脚本
local function process_curl(curl_file, package_name)
    local curl_url = get_tftp_url(package_name)

    curl.global_init()
    local curl_handle = curl.easy_init()
    if not curl_handle then
        curl.global_cleanup()
        log:error('curl init failed')
        return -1
    end

    curl_easy_setopts(curl_handle, curl_url, curl_file)
    local ret = -1
    local retry = 3
    while ret ~= 0 and retry > 0 do
        ret = curl.easy_perform(curl_handle)
        if ret ~= 0 then
            dft_delete_iptables()
            skynet.sleep(3000)
        end
        retry = retry - 1
    end

    curl.easy_cleanup(curl_handle)
    curl.global_cleanup()
    if ret ~= 0 and retry == 0 then
        log:error('curl down failed')
        return -1
    end
    return 0
end

-- 执行装备脚本
local function smmdown_sh_process(dir_dst_filename)
    local cmd_buf = string.format('/bin/bash %s &', dir_dst_filename)
    log:notice('Start to do %s ', dir_dst_filename)
    os.execute(cmd_buf) -- 为了装备脚本可以执行不使用安全函数
    return 0
end

-- 校验脚本文件
local function smmdown_sh_process_file(dir_dst_filename)
    local file = file_sec.open_s(dir_dst_filename, "r")
    if not file then
        log:error('open (%s) failed', dir_dst_filename)
        return -1
    end

    local len = utils.close(file, pcall(file.seek, file, "end"))
    if len > 0 then
        -- 权限为读0400和执行权限0100
        utils_core.chmod_s(dir_dst_filename, utils.S_IRUSR | utils.S_IXUSR)
        return smmdown_sh_process(dir_dst_filename)
    end

    log:error('flie len is 0, please check the dft file')
    return -1
end

-- 下载装备脚本并执行
local function process_file(package_name)
    local dir_dst_filename = get_dst_filename(package_name)
    local curl_file = file_sec.open_s(dir_dst_filename, "wb")
    if not curl_file then
        log:error('open file name failed')
        return -1
    end

    local ret = utils.close(curl_file, pcall(process_curl, curl_file, package_name))
    if ret ~= 0 then
        return ret
    end

    log:notice('curl down success')
    return smmdown_sh_process_file(dir_dst_filename)
end

local function enter_dft_mode(package_name)
    dft_change_tmp_capbility()
    skynet.sleep(10000) -- 等待mount生效
    dft_delete_iptables()
    get_network_obj()
    if start_dhcp_client() ~= 0 then
        log:error('dft failed')
        return
    end

    skynet.sleep(3000) -- 等待DHCP生效
    if process_file(package_name) ~= 0 then
        log:error('dft failed')
        reset_network()
        return
    end

    log:notice('dft success')
end

function manufacture_dft:enter_fs_st(package_name)
    -- 只有配置了装备包路径才会跑装备脚本
    if package_name and package_name ~= '' then
        skynet.fork(function ()
            enter_dft_mode(package_name)
        end)
    end
end

return manufacture_dft
