#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) Huawei Technologies Co., Ltd. 2018-2022. All rights reserved.
import contextlib
import logging
import os
import platform
import shutil
import stat
import sys
import tarfile
import threading
import time
import subprocess

import yaml


PYTHON_2 = '2.7' in sys.version
MY_DIR = os.path.dirname(os.path.realpath(__file__))
is_windows = (platform.system() == "Windows")
CONF_DIR = '/startup_disk/conf'
PKG_CUR = '/startup_disk/image/pkg_cur'
PKG_CUR_REALPATH = os.path.realpath(PKG_CUR)
PATCH_CUR_DIR = os.path.join(PKG_CUR_REALPATH, "../hotpatch/patch_cur")
PATCH_TEMP_DIR = os.path.join(PKG_CUR_REALPATH, "../hotpatch/patch_temp")
BOOT_LINK = '/startup_disk/image/boot'
PKG_UPD_DIR = '/startup_disk/image/pkg_upd'
COFFER_BIN_SIZE = os.popen("df -BG /OSM/coffer_bin/ |tail -n 1 |awk '{print $2}'").read(2)
EXTRACT_DIR = '/OSM/coffer_bin/startup' if COFFER_BIN_SIZE != '1G' else '/OSM/coffer_data/startup'

OSM_SCRIPT_DIR = "/OSM/script"
DISK_INSTALL_DIR = '/OSM/coffer_bin'
MEMORY_INSTALL_DIR = '/OSM/component'

PROC_OSP_BSP = '/proc/osp/bsp'
HOOK_REPLACE = '/OSM/coffer_data/install_hook/replace'
HOOK_SCRIPT = '/OSM/coffer_data/install_hook/script'

NODE_TYPE = None
DATA_NODE_FLAG_FILE = '/startup_disk/conf/conf_local/data_node'
SPARE_PART_FLAG = '/startup_disk/conf/conf_local/spare_part_flag'
SPARE_PART_SETUP_SUCCESS_FLAG = '/startup_disk/conf/conf_local/spare_part_setup_success'
SPARE_EXEC_SCRIPT = 'python /OSM/script/spare_mgr.py'
RESCUE_MODE_FLAG = '/startup_disk/conf/conf_local/rescue_mode_flag'
VERIFY_EXEC_SCRIPT = '/OSM/script/startup_verify.py'
DATA_NODE_BOARD = ['PANGEA_V6R1C00_JBOD_SAS_EXP', 'PANGEA_V6R1C00_JBOF_PALM_EXP']
START_ERROR_ERROR_CODE = 203  # start failed record error code
CON_RESETS_ERR_CODE = 201  # more than three serious reset in 50 minutes
VERIFY_FAILED_ERR_CODE = 1073793484
PATCH_RESUME_FAIL_ERROR_CODE = '1073884723'
THIRD_PAUSE_FLAG = "/startup_disk/image/boot/thirdpauseflag"
RECORD_ABNORMAL_CMD = "sh /OSM/script/os_record_abnormal_status.sh -s %s"
RECORD_ABNORMAL_SCRIPT = RECORD_ABNORMAL_CMD % START_ERROR_ERROR_CODE
FACTORY_FLAG_FILE = "/startup_disk/conf/equ_test/checkconfig.sh"
SWM_DB_PATH = '/OSM/coffer_data/swm/swm.db'
HOOK_PATH = '/OSM/script/startup.hook'
UPGRADE_FLAG_PATH = '/OSM/coffer_data/swm/upgrade_flag'
UPG_BIOS_SCRIPT = '/OSM/script/upgrade_bios.py {0} &>/dev/null'
NEED_SYNC_FW_FLAG_AFTER_REBOOT = os.path.join(CONF_DIR, 'conf_local',
                                              'sync_fw_flag_after_reboot')
IMAGE_DIR = '/startup_disk/image'
CUR_EULER_MANIFEST = os.path.join(BOOT_LINK, 'manifest.yml')
UNROOT_FLAG_PATH = '/OSM/conf/unroot_flag'
COMPONENT_ERROR_FLAG_PATH = '/OSM/conf/component_error_flag'

COMPARE_RESULT_GREATER = 1
COMPARE_RESULT_LESS = -1
COMPARE_RESULT_EQUAL = 0

boot_type = {
    'os_rescue': 201,
    'os_memtest': 201,
    'os_only': 1073793434,
    'os_netboot': 1073793433,
    'os_boot_osp': None
}


class BiosUpgType(object):
    STAND = '1'
    FORCE = '8'
    UPG_SLAVE = '16'


def decode_str(value):
    if isinstance(value, bytes):
        return value.decode()
    return value


def wait_service_finish(timeout=300, interval=5):
    services = ['pxe_server', 'nvram_log', 'esyslog']
    times = timeout // (interval if interval else 1)
    cmd = 'systemctl status {service} | grep -E "FAILURE|SUCCESS|killed"'
    log.info('STARTUP: Begin wait service finish.')
    while times:
        for service in services:
            if os.system(cmd.format(service=service)):
                break
        else:
            break
        times -= 1
        time.sleep(interval)
    log.info('STARTUP: End wait service finish.')
    return


class CmptGetThread(threading.Thread):
    def __init__(self, func, *args):
        super(CmptGetThread, self).__init__()
        self.func = func
        self.args = args
        self.result = None

    def run(self):
        self.result = self.func(*self.args)
        return

    def get_result(self):
        return self.result


# noinspection PyBroadException,PyPep8
class StartUp(object):
    """
    Startup manager.
    """

    STATE_INIT = 0
    STATE_SWM_INSTALLED = 1
    STATE_ALL_INSTALLED = 2
    STATE_ALL_STARTED = 3

    state_file = '/tmp/startup.stat'

    def __init__(self):
        """

        """
        self.lock = threading.RLock()
        self.state = self.load_state()
        self.result = True
        self.cmpt_list = []
        log.info("STARTUP: Load startup stat(%d).", self.state)
        return

    def load_state(self):
        """

        :return:
        """
        try:
            return int(open(self.state_file, 'r+').read())
        except Exception:
            return self.STATE_INIT

    def save_state(self, state):
        self.state = state
        open(self.state_file, 'w+').write(str(self.state))
        os.chmod(self.state_file, 0o640)
        return

    @staticmethod
    def _get_cmpt_path(pkg_dir, cmpt_name):
        dir_list = ['apollo', 'athena', 'posseidon', 'packages']
        for sub_dir in dir_list:
            sub_path = os.path.join(pkg_dir, sub_dir)
            if not os.path.isdir(sub_path):
                continue
            for root, _, files in os.walk(sub_path):
                cmpt_list = [x for x in files if x.startswith(cmpt_name + '-')]
                if cmpt_list:
                    return os.path.join(root, cmpt_list[0])

        log.warning("Can not find cmpt(%s) in dir(%s).", cmpt_name, pkg_dir)
        return ''

    @classmethod
    def _get_startup_cmpt_path(cls, cmpt_name):
        """
        Get component package path
        :param cmpt_name:
        :return:
        """
        # 升级过程中, 才加载upd目录下的组件, 并且判断组件软链接有效性
        if os.path.exists(UPGRADE_FLAG_PATH):
            cmpt_path = cls._get_cmpt_path(PKG_UPD_DIR, cmpt_name)
            if cmpt_path:
                if os.path.exists(cmpt_path):
                    return cmpt_path
                # 若软链接无效, 加载cur或补丁目录下组件且创建标记文件, 记录上电错误码
                if not os.path.exists(COMPONENT_ERROR_FLAG_PATH):
                    flag = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
                    file_handle = os.open(COMPONENT_ERROR_FLAG_PATH, flag, 0o640)
                    os.close(file_handle)
                log.error("STARTUP: The component(%s) is not exist in pkg_upd.", cmpt_name)

        if cmpt_name == 'swm' and cls.has_swm_patch():
            cmpt_path = cls._get_cmpt_path(PATCH_CUR_DIR, cmpt_name)
        else:
            cmpt_path = cls._get_cmpt_path(PKG_CUR, cmpt_name)
        if cmpt_path:
            return cmpt_path
        log.error("STARTUP: The component(%s) pkg is not exist.", cmpt_path)
        return None

    @staticmethod
    def has_swm_patch():
        if not os.path.exists(PATCH_CUR_DIR):
            return False
        file_list = os.listdir(PATCH_CUR_DIR)
        for file_name in file_list:
            if file_name.startswith('swm-'):
                return True
        return False

    @staticmethod
    def _get_swm_root(root_dir):
        swm_dir_list = [d for d in os.listdir(root_dir) if d.startswith('swm')]
        if not swm_dir_list:
            log.error("STARTUP: Swm component install dir is not exist")
            return ''
        if len(swm_dir_list) > 1:
            log.warning("STARTUP: Swm dir is more than one.")
            return ''
        swm_dir = os.path.join(root_dir, swm_dir_list[0])
        return swm_dir

    def exec_cmpt_cmd(self, cmpt, cmd_name):
        log.info("STARTUP: Component(%s) begin to run cmd(%s).", cmpt.name, cmd_name)
        ret = cmpt.exec_cmd(cmd_name)
        if not ret:
            log.error("STARTUP: Component(%s) run cmd(%s) failed, then retry.", cmpt.name, cmd_name)
            time.sleep(2)
            ret = cmpt.exec_cmd(cmd_name)
        if ret:
            log.info("STARTUP: Component(%s) run cmd(%s) successfully.", cmpt.name, cmd_name)
            return True
        log.error("STARTUP: Component(%s) run cmd(%s) failed finally.", cmpt.name, cmd_name)
        with self.lock:
            self.result = ret
        return False

    def install_swm(self):
        """
        Extract swm self and install swm
        :return:
        """
        self.change_initrd_osp_permit()
        self.handle_pkg_cur()
        swm_pkg = self._get_startup_cmpt_path('swm')
        if not swm_pkg:
            return False
        with tarfile.open(swm_pkg, 'r:gz') as tar:
            tar.extractall(EXTRACT_DIR)
        swm_dir = self._get_swm_root(EXTRACT_DIR)
        if not swm_dir:
            log.error("STARTUP: Swm was extracted incorrectly.")
            return False
        log.info("STARTUP: Begin to custom EulerOS.")
        os_custom_script = os.path.join(swm_dir, 'action', 'custom_os.sh')
        ret_code = os.system('sh {script:s}'.format(script=os_custom_script))
        if ret_code:
            log.error("STARTUP: Exec script (%s) to custom euler os failed", os_custom_script)
            return False
        log.info("STARTUP: Begin to install component(swm).")
        app_ctl = os.path.join(swm_dir, 'action', 'appctl')
        if os.system('%s install' % app_ctl):
            log.warning("STARTUP: Install component(swm) failed.")
        else:
            log.info("STARTUP: Install component(swm) successfully.")
        self.save_state(self.STATE_SWM_INSTALLED)
        sys.path.append(os.path.join(MEMORY_INSTALL_DIR, 'swm'))
        patch_mod = __import__('plat.util.hotpatch_util', fromlist=['hotpatch_util', ])
        patch_mod.PatchProcessInStartup.special_resume_handle(PATCH_TEMP_DIR)
        return True

    @classmethod
    def load_deploy_cfg(cls, cfg_path):
        """
        Load deploy info from swm.yml
        :param cfg_path:
        :return:
        """

        with open(cfg_path, 'r') as fp:
            cfg_info = yaml.safe_load(fp)
        # filter component support current apollo version
        cmpt_cfg = cfg_info.get('Components')
        cmpt_list = []
        for cfg in cmpt_cfg:
            if cfg.get('NodeType') not in ['all', NODE_TYPE]:
                continue
            apollo_min_version = cfg.get("ApolloMinVersion")
            if apollo_min_version:
                ret = cls.compare_cmpt_version(cls.get_cur_apollo_version(), apollo_min_version.split('-')[1])
                if ret not in [COMPARE_RESULT_GREATER, COMPARE_RESULT_EQUAL]:
                    continue
            cmpt_list.append(cfg.get('Name'))
        # stage
        deploy_cfg = cfg_info.get('DeployFlow')
        deploy_cmpt = []
        for stage in deploy_cfg.get('Stages'):
            deploy_cmpt.append([])
            for cmpt in stage.get('Components'):
                cmpt_name = cmpt.get('Name')
                if cmpt_name in cmpt_list:
                    deploy_cmpt[-1].append(cmpt_name)
        return deploy_cmpt

    @staticmethod
    def get_cur_apollo_version():
        cur_pkg_root = os.path.realpath(PKG_CUR)
        cur_apollo_path = os.path.basename(os.path.realpath(os.path.join(cur_pkg_root, 'apollo')))
        return cur_apollo_path.split('-')[1]

    @staticmethod
    def compare_cmpt_version(ver_1, ver_2):
        """

        :param ver_1:1.0.0.9
        :param ver_2:
        :return:
        """
        try:
            ver_list_1 = [int(i) for i in ver_1.split('.')]
            ver_list_2 = [int(i) for i in ver_2.split('.')]
            for i, ver, in enumerate(ver_list_1):
                if ver == ver_list_2[i]:
                    continue
                elif ver < ver_list_2[i]:
                    return COMPARE_RESULT_LESS
                else:
                    return COMPARE_RESULT_GREATER
            return COMPARE_RESULT_EQUAL
        except Exception:
            log.exception("Cmpt compare failed.")
            return None

    @staticmethod
    def get_euler_version():
        if not os.path.exists(CUR_EULER_MANIFEST):
            return ''
        with open(CUR_EULER_MANIFEST, 'r') as fp:
            euler_info = yaml.safe_load(fp)
        euler_ver = euler_info.get('Version', '').strip()
        log.info("STARTUP: Current euler version is %s.", euler_ver)
        return euler_ver

    def check_pkg_dir_euler_version(self, pkg_dir, euler_ver):
        euler_path = self._get_cmpt_path(pkg_dir, 'euler')
        if not euler_path:
            return False
        euler_ver_str = "euler-{0}-".format(euler_ver)
        if euler_ver_str in euler_path:
            return True
        return False

    @staticmethod
    def get_upgrade_info():
        fast_upgrade_flag = None
        src_ver = None
        dst_ver = None
        with open(UPGRADE_FLAG_PATH) as fp:
            upg_info = fp.readlines()
        for line in upg_info:
            if "fastupgflag" in line:
                fast_upgrade_flag = line.strip().split('=')[1]
            if "srcnumversion" in line:
                src_ver = line.strip().split('=')[1]
            if "dstnumversion" in line:
                dst_ver = line.strip().split('=')[1]
        log.info("STARTUP: fast flag(%s), src %s, dst %s",
                 fast_upgrade_flag, src_ver, dst_ver)
        return fast_upgrade_flag, src_ver, dst_ver

    @staticmethod
    def merge_two_cmpt_list(idx, src_cmpt, dst_cmpt_in_pkg_upd):
        new_cmpt_i = []
        src_cmpt_i = src_cmpt[idx]
        dst_cmpt_in_pkg_upd_i = dst_cmpt_in_pkg_upd[idx]
        m, n = len(src_cmpt_i), len(dst_cmpt_in_pkg_upd_i)
        p1, p2 = 0, 0
        while p1 < m or p2 < n:
            if p1 == m:
                if dst_cmpt_in_pkg_upd_i[p2] not in new_cmpt_i:
                    new_cmpt_i.append(dst_cmpt_in_pkg_upd_i[p2])
                p2 += 1
            elif p2 == n:
                if src_cmpt_i[p1] not in new_cmpt_i:
                    new_cmpt_i.append(src_cmpt_i[p1])
                p1 += 1
            elif dst_cmpt_in_pkg_upd_i[p2] in src_cmpt_i:
                if dst_cmpt_in_pkg_upd_i[p2] == src_cmpt_i[p1]:
                    new_cmpt_i.append(src_cmpt_i[p1])
                    p1 += 1
                    p2 += 1
                else:
                    new_cmpt_i.append(src_cmpt_i[p1])
                    p1 += 1
            else:
                new_cmpt_i.append(dst_cmpt_in_pkg_upd_i[p2])
                p2 += 1
        return new_cmpt_i

    def get_new_cmpt_list_by_src_dst(self, src_cmpt, dst_cmpt):
        dst_cmpt_in_pkg_upd = []
        for src_cmpt_list, dst_cmpt_list in zip(src_cmpt, dst_cmpt):
            cmpt_stage = []
            for cmpt_i in dst_cmpt_list:
                if not self._get_cmpt_path(PKG_UPD_DIR, cmpt_i) and cmpt_i not in src_cmpt_list:
                    continue
                cmpt_stage.append(cmpt_i)
            dst_cmpt_in_pkg_upd.append(cmpt_stage)
        new_cmpt = []
        for i in range(len(src_cmpt)):
            new_cmpt_stage_i = self.merge_two_cmpt_list(i, src_cmpt, dst_cmpt_in_pkg_upd)
            new_cmpt.append(new_cmpt_stage_i)
        return new_cmpt

    def generate_deploy_cmpt(self, src_cmpt, src_ver, dst_ver):
        cur_ver = os.readlink(PKG_CUR).split('/')[0]
        new_ver = dst_ver if cur_ver == src_ver else src_ver
        try:
            dst_cmpt = self.load_deploy_cfg(os.path.join(IMAGE_DIR, new_ver, 'system/swm.yml'))
            if len(src_cmpt) != len(dst_cmpt):
                log.warning("The total of stage of component is not equal.")
                return src_cmpt
            return self.get_new_cmpt_list_by_src_dst(src_cmpt, dst_cmpt)
        except Exception:
            log.exception("STARTUP: Generate deploy component happen error.")
            return src_cmpt

    def get_deploy_cmpt(self):
        src_cmpt = self.load_deploy_cfg(os.path.join(PKG_CUR, 'swm.yml'))
        apollo_link = os.path.join(PKG_CUR, 'apollo')
        if not os.path.islink(apollo_link):
            return src_cmpt
        apollo_version = os.readlink(apollo_link).split('-')[-1]
        if apollo_version == '1.0.0.1':
            return src_cmpt
        if os.path.exists(UPGRADE_FLAG_PATH):
            fast_upgrade_flag, src_ver, dst_ver = self.get_upgrade_info()
            if fast_upgrade_flag != "1":
                log.info("STARTUP: Not fast upgrade, no need resume new component.")
                return src_cmpt
            return self.generate_deploy_cmpt(src_cmpt, src_ver, dst_ver)
        elif os.path.exists(PKG_UPD_DIR):
            versions = [v for v in os.listdir(IMAGE_DIR) if v.isdigit() and v.startswith('76')]
            if len(versions) != 2:
                log.warning("The number of digital version directories is not 2.")
                return src_cmpt
            return self.generate_deploy_cmpt(src_cmpt, versions[0], versions[1])
        else:
            log.info("Not in upgrading status, skip it.")
            return src_cmpt

    def _load_cmpt_list(self):
        """

        :return:
        """
        if self.cmpt_list:
            return True
        sys.path.append(OSM_SCRIPT_DIR)
        sys.path.append(os.path.join(DISK_INSTALL_DIR, 'swm'))
        sys.path.append(os.path.join(MEMORY_INSTALL_DIR, 'swm'))
        cmpt_mod = __import__('cmpt', fromlist=['cmpt', ])
        deploy_cmpt = self.get_deploy_cmpt()
        t_pool = []
        result = {}
        for batch in deploy_cmpt:
            for cmpt_name in batch:
                cmpt_path = self._get_startup_cmpt_path(cmpt_name)
                if not cmpt_path:
                    continue
                cmpt_thread = CmptGetThread(cmpt_mod.Component.get, cmpt_name, os.path.dirname(cmpt_path), EXTRACT_DIR)
                t_pool.append((cmpt_name, cmpt_thread))
                cmpt_thread.start()
        for name, cmpt_thread in t_pool:
            cmpt_thread.join()
            ret = cmpt_thread.get_result()
            result[name] = ret
        for batch in deploy_cmpt:
            batch_cmpt = []
            for cmpt_name in batch:
                if cmpt_name not in result:
                    continue
                batch_cmpt.append(result.get(cmpt_name, None))
            self.cmpt_list.append(batch_cmpt)
        return True

    @staticmethod
    def modify_group_user():
        # modify 文件权限，软连接
        cmd = 'sh /OSM/script/os_modify_sftp_group_user_dir.sh'
        os.system(cmd)

    @staticmethod
    def resume_euler_special_patch():
        cmd = 'sh /OSM/script/os_upgrade_coldpatch.sh -a'
        os.system(cmd)

    @staticmethod
    def record_error_code(error_code=''):
        get_boot_type_cmd = 'cat /proc/cmdline | egrep -o -m 1 "os_boot_osp|os_only|os_rescue|os_memtest|os_netboot"'
        if PYTHON_2:
            import commands
            (ret, output) = commands.getstatusoutput(get_boot_type_cmd)
        else:
            ret, output = subprocess.getstatusoutput(get_boot_type_cmd)  # pylint: disable=E1101
        power_error_code = boot_type.get(output)
        if (power_error_code or error_code) and not os.path.exists(RESCUE_MODE_FLAG):
            log.error("STARTUP: abnomal boot type %s, need record the "
                      "error code.", power_error_code or error_code)
            record_cmd = RECORD_ABNORMAL_CMD % (power_error_code or error_code)
            os.system(record_cmd)

    @staticmethod
    def resume_euler_patch(resume_stage):
        try:
            if resume_stage == "after_install":
                wait_service_finish()
            cur_ver = os.readlink(PKG_CUR).split('/')[0]
            patch_path = os.path.join('/startup_disk/image/', cur_ver, 'hotpatch/patch_cur')
            patch_mod = __import__('plat.util.hotpatch_util', fromlist=['hotpatch_util', ])
            ret = patch_mod.PatchProcessInStartup.resume_euler_patch(patch_path, resume_stage)
            if not ret:
                log.error("STARTUP: Euler patch resume failed, stage %s return %s", resume_stage, ret)
            else:
                log.info("STARTUP: Euler patch resume stage % return %s", resume_stage, ret)
            return ret
        except Exception:
            log.exception("STARTUP: Resume stage %s excepted.", resume_stage)
            return False

    def get_fast_upgrade_info(self):
        fast_upgrade_flag = None
        with open(UPGRADE_FLAG_PATH) as fp:
            upg_info = fp.readlines()
        for line in upg_info:
            if "fastupgflag" in line:
                fast_upgrade_flag = line.strip().split('=')[1]
        log.info("STARTUP: fast flag(%s)", fast_upgrade_flag)
        return fast_upgrade_flag

    @staticmethod
    def change_pkg_cur_to_version(version):
        log.info("STARTUP: Change pkg_cur to version %s.", version)
        mount_rw = "mount -o remount,rw,async /startup_disk/image/"
        mount_ro = "mount -o remount,ro,async /startup_disk/image/"
        os.system(mount_rw)
        os.unlink(PKG_CUR)
        os.symlink('{0}/system'.format(version), PKG_CUR)
        os.system("fsync {0}".format(PKG_CUR))
        os.system(mount_ro)
        return

    @staticmethod
    def _drop_caches():
        print_mem_cmd = 'echo m > /proc/sysrq-trigger'
        drop_caches_cmd = 'echo 3 > /proc/sys/vm/drop_caches'
        for c in [print_mem_cmd, drop_caches_cmd, print_mem_cmd]:
            ret = os.system(c)
            log.info('STARTUP: Execute cmd(%s) ret(%s).', c, ret)
        if NODE_TYPE == 'DATA_NODE':
            log.info('STARTUP: Sleep 5s after drop caches in data node.')
            time.sleep(5)
        return

    @staticmethod
    def change_permission_for_unroot_scenes(script):
        if not os.path.exists(UNROOT_FLAG_PATH):
            return
        script_file = os.path.join('/OSM/script', script)
        if os.path.exists(script_file):
            os.system("sh {0}".format(script_file))
            os.system("chown -h -R :root /OSM/log/cur_debug/omm")
            log.info("STARTUP: Change permission for unroot scenes, file:%s.", script)
        return

    @classmethod
    def _change_initrd_osp_permit(cls):
        """
        使用setfacl工具修改initrd_osp文件权限，包中admin账户不可读，ibc账户可读
        setfacl工具是在去root的需求时新增的
        :return:
        """
        cmd = "which setfacl"
        ret = os.system(cmd)
        if ret:
            log.warning("STARTUP: Command setfacl is not exists.")
            return
        initrd_osp_path_link = "/startup_disk/image/boot/initrd"
        initrd_osp_path = os.path.realpath(initrd_osp_path_link)

        # 指定ibc账户读权限，指定nobody账户读权限（网络引导tftp使用的是nobody账户）
        st = os.stat(initrd_osp_path)
        ori_mode = stat.S_IMODE(st.st_mode)

        # 指定ibc、nobody账户读权限，必须先chmod，再使用setfacl，避免chmod命令覆盖了setfacl命令的权限
        ret = 1
        cmd = "setfacl -m u:ibc_os_hs:r--,u:nobody:r-- {path}".format(path=initrd_osp_path)
        for _ in range(3):
            if not ret:
                break
            os.chmod(initrd_osp_path, 0o600)
            ret = os.system(cmd)
            if ret:
                log.warning("STARTUP: Change initrd_osp acl permit result is %s.", ret)
                time.sleep(1)

        # 修改失败，恢复原有的权限值
        if ret:
            log.warning("STARTUP: Change initrd_osp acl permit failed, resume initrd_osp mod to %s.", oct(ori_mode))
            os.chmod(initrd_osp_path, ori_mode)
        return

    @classmethod
    def _change_initrd_osp_permit_back(cls):
        """
        争对615以下的版本快速升级到615并复位节点后，再快速升级到616的场景，给initrd的other组加上读权限
        :return:
        """
        initrd_osp_path_link = "/startup_disk/image/boot/initrd"
        initrd_osp_path = os.path.realpath(initrd_osp_path_link)

        # other组没有读权限时，加上读权限
        stat_info = os.stat(initrd_osp_path)
        if stat_info.st_mode & 0o004 == 0o004:
            log.info("STARTUP: No need to give other group read permit for initrd.")
            return
        log.info("STARTUP: Give other group read permit for initrd.")
        os.chmod(initrd_osp_path, stat_info.st_mode | 0o004)
        log.info("STARTUP: Give other group read permit for initrd success.")
        return

    def change_initrd_osp_permit(self):
        with self.remount_rw_ro():
            # 1.1.5.0及以后的内核tftp配置文件有-p参数，不会校验文件other组是否有读权限，才能将initrd_osp权限修改为600
            if self.compare_cmpt_version(self.get_cur_apollo_version(), '1.1.5.0') in\
                    [COMPARE_RESULT_GREATER, COMPARE_RESULT_EQUAL]:
                self._change_initrd_osp_permit()
            else:
                self._change_initrd_osp_permit_back()

    @contextlib.contextmanager
    def remount_rw_ro(self, disk=IMAGE_DIR):
        mount_rw = "mount -o remount,rw,async %s" % disk
        mount_ro = "mount -o remount,ro,async %s" % disk
        try:
            os.system(mount_rw)
            yield
        finally:
            os.system(mount_ro)

    def modify_pkg_cur(self, euler_ver):
        file_list = [per_file for per_file in os.listdir(IMAGE_DIR) if per_file.isdigit()]
        for version in file_list:
            if self.check_pkg_dir_euler_version(os.path.join(IMAGE_DIR, version, 'system'), euler_ver):
                self.change_pkg_cur_to_version(version)
                log.info("STARTUP: Pkg cur points to %s.", version)
                return
        log.warning("STARTUP: Failed to find compatible version.")
        return

    def handle_pkg_cur(self):
        """
        For the euler switch, package must match with euler os, else the node will can not start
        :return:
        """
        try:
            if os.path.exists(UPGRADE_FLAG_PATH):
                fast_upgrade_flag = self.get_fast_upgrade_info()
                if fast_upgrade_flag == "1":
                    log.info("STARTUP: No need to handle pkg_cur.")
                    return
            euler_ver = self.get_euler_version()
            if not euler_ver:
                log.warning("STARTUP: Get euler version failed.")
                return
            log.info("STARTUP: Get euler version %s", euler_ver)
            if self.check_pkg_dir_euler_version(PKG_CUR, euler_ver):
                log.info("STARTUP: Euler is compatible with pkg_cur.")
                return
            self.modify_pkg_cur(euler_ver)
            return
        except Exception:
            log.exception("STARTUP: Handle pkg_cur excepted.")
            return

    def install_other(self):
        """

        :return:
        """
        log.info("STARTUP: Begin to install components.")
        if not self._load_cmpt_list():
            return False
        # 启动盘设置读写
        mount = "mount -o remount,rw,async /startup_disk/image/"
        os.system(mount)
        error_code = ''
        try:
            for batch in self.cmpt_list[1:]:
                tpool = []
                # 不重复安装swm
                for cmpt in batch:
                    if cmpt == "swm":
                        continue
                    thread = threading.Thread(target=self.exec_cmpt_cmd, args=(cmpt, 'install',))
                    thread.start()
                    tpool.append(thread)
                for thread in tpool:
                    thread.join()
            self._drop_caches()
            # 变更权限
            self.modify_group_user()
            if not self.result:
                log.error("STARTUP: Any component install failed.")
                return False
            log.info("STARTUP: Install all components successfully.")
            self.save_state(self.STATE_ALL_INSTALLED)
            if not self.resume_euler_patch('after_install'):
                error_code = PATCH_RESUME_FAIL_ERROR_CODE
                return False
            return True
        except Exception:
            log.exception("install other exceped")
            return False
        finally:
            not_mount = "mount -o remount,ro,sync /startup_disk/image/"
            os.system(not_mount)
            ima_policy = "sh /OSM/script/ima_policy_load.sh"
            os.system(ima_policy)
            # 当前环境中使用该脚本完成ldconfig，待后续优化
            cmd = "/OSM/script/swm_second_load.sh"
            os.system(cmd)
            self.change_permission_for_unroot_scenes(script='changePermission.sh')
            self.resume_euler_special_patch()
            self.record_error_code(error_code=error_code)

    @staticmethod
    def data_node_rescue_mode():
        if NODE_TYPE != 'DATA_NODE':
            return False
        if os.readlink(BOOT_LINK) == 'boot_rescue':
            log.info("STARTUP: boot link to boot_rescue.")
            return True
        return False

    @staticmethod
    def exec_hook(stage):
        try:
            path_dict = {
                "before.install": os.path.join(HOOK_PATH, "before.install"),
                "after.install": os.path.join(HOOK_PATH, "after.install"),
                "after.start": os.path.join(HOOK_PATH, "after.start")
            }
            hook_dir = path_dict.get(stage)
            if not os.path.exists(hook_dir):
                log.info("STARTUP: hook dir(%s) does not exist.", hook_dir)
                return True
            log.info("STARTUP: start to exec hook, hook path(%s).", hook_dir)
            for script in os.listdir(hook_dir):
                cmd = 'chmod +x {0};{0} &>/dev/null'.format(
                    os.path.join(hook_dir, script))
                ret = os.system(cmd)
                log.info("STARTUP: exec script(%s), ret(%s).", script, ret)
            log.info("STARTUP: end to exec hook stage(%s).", stage)
        except Exception:
            log.exception("exec hook exception.")
        return True

    def hook_before_install(self):
        return self.exec_hook("before.install")

    def hook_after_install(self):
        return self.exec_hook("after.install")

    @staticmethod
    def check_pkg_cur():
        if not os.path.exists(VERIFY_EXEC_SCRIPT):
            log.info("STARTUP: No need to verify pkg.")
            return True
        ret = os.system(VERIFY_EXEC_SCRIPT)
        if ret != 0:
            log.error("STARTUP: Failed to verify pkg, result %s.", ret)
            cmd = RECORD_ABNORMAL_CMD % VERIFY_FAILED_ERR_CODE
            os.system(cmd)
            return False
        log.info("STARTUP: Verify pkg successfully.")
        return True

    def hook_after_start(self):
        return self.exec_hook("after.start")

    @staticmethod
    def start_spare_part_flow():
        """
        :return:
        """
        ret_val = os.system(SPARE_EXEC_SCRIPT)
        if ret_val:
            log.error("STARTUP: Spare exec result is %s.", ret_val)
            os.system('/OSM/script/os_record_abnormal_status.sh -s 203')
            return False
        return True

    def check_start_other(self):
        if os.path.exists(COMPONENT_ERROR_FLAG_PATH):
            log.error("STARTUP: Component error flag is exist.")
            os.system(RECORD_ABNORMAL_SCRIPT)
            os.remove(COMPONENT_ERROR_FLAG_PATH)
            return False, False

        if not os.system('cat /proc/cmdline |grep os_factory'):
            warn_str = "STARTUP: not support to start component in os_factory mode."
            log.warning(warn_str)
            return False, True

        if self.data_node_rescue_mode() and not os.path.exists(RESCUE_MODE_FLAG):
            cmd = "sh /OSM/script/os_record_abnormal_status.sh -s %s" % CON_RESETS_ERR_CODE
            os.system(cmd)
            return False, True
        return True, True

    def start_other(self):
        """

        :return:
        """

        can_start, ret = self.check_start_other()
        if not can_start:
            return ret

        #  power-on pause function
        log.info("STARTUP: Begin to start components.")
        if os.path.exists(THIRD_PAUSE_FLAG):
            log.warning("STARTUP: sys pause record flag exist, not start component.")
            return True
        if not self._load_cmpt_list():
            os.system(RECORD_ABNORMAL_SCRIPT)
            return False
        for batch in self.cmpt_list:
            # start 分组内也串行执行
            for cmpt in batch:
                if cmpt.name == 'swm':
                    continue
                elif cmpt.name == 'devm':
                    self.change_permission_for_unroot_scenes(script='hisi_dev_permision.sh')
                    self.change_permission_for_unroot_scenes(script='changeDevmPermission.sh')
                elif cmpt.name == 'app_data':
                    self.change_permission_for_unroot_scenes(script='changeSysPermission.sh')
                ret = self.exec_cmpt_cmd(cmpt, 'start')
                if not ret:
                    log.error("STARTUP: Any component start failed.")
                    os.system(RECORD_ABNORMAL_SCRIPT)
                    return False
        self.save_state(self.STATE_ALL_STARTED)
        log.info("STARTUP: Start all components successfully.")
        shutil.rmtree(EXTRACT_DIR, ignore_errors=True)
        if not self.resume_euler_patch('after_start'):
            return False
        if not os.path.exists(UPGRADE_FLAG_PATH):
            try:
                self.handle_dim()
            except Exception:
                log.warning("DimMgr: Handle dim exception.")
        return True

    def handle_dim(self):
        dim_state_file = "/sys/kernel/security/dim/runtime_status"
        if not os.path.exists(dim_state_file):
            log.info("STARTUP: Dim is closed, no need backup dim files.")
            return
        dim_cmd = "dim baseline_create"
        dim_policy = '/etc/dim/policy'
        dim_files = '/etc/dim/digest_list'
        dim_files_back = os.path.join(dim_files, 'dim_backup')
        if not os.path.exists(dim_policy):
            log.warning("STARTUP: DimMgr Not exist policy.")
            return

        subprocess.Popen('mkdir -p {0};chmod 440 {0}'.format(dim_files_back),
                         stderr=subprocess.STDOUT, stdout=subprocess.PIPE, shell=True)
        # 备份
        with open(dim_policy, 'r') as fd:
            read_lines = fd.readlines()
            # file_name_lists 为待保护的白名单文件名
            file_name_lists = [file_line.split('/')[-1].split('.')[0].strip()
                               for file_line in read_lines if file_line.strip()]
        for root, _, fs in os.walk(dim_files):
            for f in fs:
                if f.split('.')[0].strip() in file_name_lists:
                    subprocess.Popen('cp -arf {0} {1}'.format(
                        os.path.join(root, f), dim_files_back),
                        stderr=subprocess.STDOUT, stdout=subprocess.PIPE, shell=True)
        # 动态基线，先更新基线然后再度量
        self.resume_patch_dim_file(dim_files)
        subprocess.Popen("swm_diagnose -c '{0}'".format(dim_cmd),
                         stderr=subprocess.STDOUT, stdout=subprocess.PIPE, shell=True)
        log.info("STARTUP: DimMgr Backup dim files success.")
        return

    @staticmethod
    def resume_patch_dim_file(dim_files):
        cur_ver = os.readlink(PKG_CUR).split('/')[0]
        patch_path = os.path.join('/startup_disk/image/', cur_ver, 'hotpatch/patch_cur')
        if not os.path.exists(patch_path):
            log.info("STARTUP: No patch, no need resume dim file.")
            return
        dim_files_path = os.path.join(patch_path, "DIM")
        # 上电恢复补丁时，需要将hotpatch/patch_cur/DIM 目录下的文件拷贝到/etc/dim/digest_list，
        cmd = "cp -arf {0}/* {1}".format(dim_files_path, dim_files)
        subprocess.Popen(cmd, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, shell=True)
        log.info("STARTUP: Backup patch dim files success.cmd(%s)", cmd)
        return

    @staticmethod
    def check_if_bios_es():
        bios_es = "cat /proc/osp/bsp | grep 'CPU Version : ES' >/dev/null"
        if not os.system(bios_es):
            log.info("BIOS_UPD: this is es equipment.")
            return True
        return False

    @staticmethod
    def exec_upg_bios_cmd(params):
        cmd = UPG_BIOS_SCRIPT.format(params)
        ret = os.system(cmd)
        log.info("UPG_BIOS: exec cmd(%s), ret(%s)", cmd, ret)
        return ret

    def bg_upg_opposite_bios(self):
        param = " ".join(['-t', BiosUpgType.UPG_SLAVE, '-k'])
        thread = threading.Thread(target=self.exec_upg_bios_cmd, args=(param,))
        thread.setDaemon(True)
        thread.start()
        return

    def upgrade_opposite_bios(self):
        """
        滚动、离线升级复位后，如果上电过程中复位类型为update bios(证明是升级过程中升级BIOS后正常复位）
        则说明bios完成升级，需要后台升级另外一片BIOS
        :return:
        """
        if os.path.exists(NEED_SYNC_FW_FLAG_AFTER_REBOOT):
            self.bg_upg_opposite_bios()

    def upg_bios(self):
        params = None
        if os.path.exists(FACTORY_FLAG_FILE):
            if not self.check_if_bios_es():
                params = " ".join(['-r', '-t', BiosUpgType.FORCE])
            else:
                params = " ".join(['-r', '-t', BiosUpgType.STAND])
        elif not os.system("cat /sys/kernel/nvram/last_reset_reason | "
                           "grep -E \"^bios reset$|^update bios$\" &>/dev/null"):
            params = " ".join(['-r', '-t', BiosUpgType.STAND])
            log.info("STARTUP: there is bios switch or update bios.")
        if params:
            ret = self.exec_upg_bios_cmd(params)
            if ret:
                return False
        self.upgrade_opposite_bios()
        return True

    def start_swm(self):
        """
        :description:just start swm part
        :return:
        """
        if platform.machine() == 'aarch64' and \
                not os.path.exists('/.dockerenv') and not self.upg_bios():
            return False

        if os.path.exists(SPARE_PART_FLAG) and not os.path.exists(SPARE_PART_SETUP_SUCCESS_FLAG) and \
                NODE_TYPE != 'DATA_NODE':
            return self.start_spare_part_flow()

        log.info("STARTUP: Begin to start components.")
        if not self._load_cmpt_list():
            return False
        for batch in self.cmpt_list:
            for cmpt in batch:
                if cmpt.name != 'swm':
                    continue
                ret = self.exec_cmpt_cmd(cmpt, 'start')
                if not ret:
                    log.error("STARTUP: swm component start failed.")
                    return False
        log.info("STARTUP: Start swm components successfully.")
        return True

    @staticmethod
    def install_hook():
        if os.path.exists(HOOK_REPLACE):
            cmd = 'cd %s; cp -af * /' % HOOK_REPLACE
            os.system(cmd)
            log.info("STARTUP: install hook replace cmd(%s).", cmd)
        if os.path.exists(HOOK_SCRIPT):
            for script in os.listdir(HOOK_SCRIPT):
                cmd = 'sh %s/%s' % (HOOK_SCRIPT, script)
                os.system(cmd)
                log.info("STARTUP: install hook script(%s).", cmd)
        return True

    def prepare(self):
        """

        :return:
        """
        if self.STATE_INIT != self.state:
            return
        shutil.rmtree(EXTRACT_DIR, ignore_errors=True)
        os.makedirs(EXTRACT_DIR, 0o755)
        return

    def run(self):
        self.prepare()
        run_list = []
        if self.STATE_ALL_STARTED == self.state:
            return True
        if self.STATE_INIT == self.state:
            run_list = [self.hook_before_install, self.install_swm, self.install_other,
                        self.install_hook, self.hook_after_install,
                        self.check_pkg_cur, self.start_swm,
                        self.start_other, self.hook_after_start]
        elif self.STATE_SWM_INSTALLED == self.state:
            run_list = [self.install_other, self.install_hook,
                        self.hook_after_install, self.check_pkg_cur,
                        self.start_swm, self.start_other,
                        self.hook_after_start]
        elif self.STATE_ALL_INSTALLED == self.state:
            run_list = [self.install_hook, self.hook_after_install,
                        self.check_pkg_cur, self.start_swm,
                        self.start_other, self.hook_after_start]

        # 如果为os_only模式，且系统没有执行过startall，则只执行install，不执行start动作

        if not os.system('cat /proc/cmdline |grep os_only') \
                or not os.system('cat /proc/cmdline |grep os_rescue') \
                or not os.system('cat /proc/cmdline |grep os_netboot') \
                or not os.system('cat /proc/cmdline |grep os_memtest'):
            if self.state == self.STATE_INIT:
                log.info("STARTUP: System is os_only mode, do not start components.")
                run_list.remove(self.hook_after_install)
                run_list.remove(self.hook_after_start)
                run_list.remove(self.start_other)
                run_list.remove(self.install_hook)
                run_list.remove(self.check_pkg_cur)
                if os.system('cat /proc/cmdline |grep os_rescue'):
                    run_list.remove(self.start_swm)

        for func in run_list:
            if not func():
                return False
        return True


def set_node_type():
    global NODE_TYPE
    if os.getenv('BOARD_TYPE', None) in DATA_NODE_BOARD:
        cmd = 'mkdir -p /startup_disk/conf/conf_local/; touch %s; sync' % DATA_NODE_FLAG_FILE
        os.system(cmd)
    NODE_TYPE = 'CTL_NODE' if not os.path.exists(DATA_NODE_FLAG_FILE) else 'DATA_NODE'
    os.environ['NODE_TYPE'] = NODE_TYPE
    log.info('NODE_TYPE is %s.', NODE_TYPE)


def get_board_type():
    """
    The proc_osp_bsp.info has not been generated when this script is executed.
    :return:
    """
    with open(PROC_OSP_BSP, 'rb') as f:

        value_list = f.read().splitlines(False)
        for value in value_list:
            context = decode_str(value).lstrip().rstrip()
            if "Product Name is " in context:
                board_type = context.split(' ')[-1]
                return board_type
    return ""


# 设置环境变量
def set_env():
    os.environ['PATH'] = "/OSM/bin:/OSM/bin/script:/OSM/script:" + os.getenv('PATH')
    # 生成crash需要使用的环境变量
    os.environ['MALLOC_ARENA_MAX'] = "1"
    board_type = get_board_type()
    os.environ['BOARD_TYPE'] = board_type
    os.environ['DEVICE_TYPE'] = board_type
    set_node_type()


# noinspection PyBroadException,PyPep8
def exec_cmd():
    exec_cmd_list = [
        'taskset -cp 0-255 %d' % os.getpid(),
        'cp -af %s/manifest.yml /OSM/conf/' % PKG_CUR,
    ]
    try:
        for cmd in exec_cmd_list:
            os.system(cmd)
        set_env()
        result = StartUp().run()
        return 0 if result else 1
    except Exception:
        log.exception("STARTUP: Deploy system failed.")
        return 2


def wait_installed():
    su = StartUp()
    for _dummy in range(900):
        su.load_state()
        if su.load_state() >= su.STATE_ALL_INSTALLED:
            log.info("STARTUP: Wait system installed successfully.")
            return 0
        time.sleep(1)
    log.error("STARTUP: Wait system installed timeout.")
    return 1


def clean_before_startup():
    spare_tmp_dir = '/OSM/coffer_data/_tmp_swm_spare_pkg'
    if os.path.exists(spare_tmp_dir):
        shutil.rmtree(spare_tmp_dir, ignore_errors=True)
    return


if __name__ == '__main__':
    log = logging.getLogger("startup")
    formatter = logging.Formatter(fmt="[%(asctime)s][%(levelname)s][%(message)s][%(filename)s, %(lineno)d]",
                                  datefmt="%Y-%m-%d %H:%M:%S")
    file_handler = logging.FileHandler(filename="/OSM/log/cur_debug/startup.log")
    file_handler.setFormatter(formatter)
    log.addHandler(file_handler)
    log.setLevel(logging.INFO)
    os.chmod("/OSM/log/cur_debug/startup.log", 0o640)
    clean_before_startup()
    fork_ret = os.fork()
    if fork_ret:
        sys.exit(wait_installed())
    else:
        sys.exit(exec_cmd())
