#! /usr/bin/python3 -s
# -*- coding: UTF-8 -*-

# Copyright (c) 2018, Alibaba Group.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# - Redistributions of source code must retain the above copyright notice,
#   this list of conditions and the following disclaimer.
# - Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
# - Neither the name of the copyright holder nor the names of its contributors
#   may be used to endorse or promote products derived from this software
#   without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.


__version__ = '0.1.3'
__author__ = 'zhangyongde <yydzhang@linux.alibaba.com>'
__license__ = 'Copyright (c) 2023, Alibaba Group.'

import sys,os
import argparse
import rpm
import string
import random
import textwrap as tr
import xml.etree.cElementTree as ET
import logging
import logging.handlers
import re
import datetime
import shelve
import syslog
import requests
from subprocess import Popen, PIPE
import configparser

sys_handler = logging.handlers.SysLogHandler('/dev/log')
logging.basicConfig(level=logging.DEBUG, format="%(message)s")
logger = logging.getLogger(__name__)
logger.addHandler(sys_handler)

working_directory="/etc/livepatch-mgr"
hotfix_cache_db="hotfix_cache.db"
config_file="config.ini"

def get_updateinfo_path():
    updateinfo_prefix = load_config("updateinfo_path")

    uname_r = os.uname()[2]
    arch = uname_r.split(".")[-1]
    dist = uname_r.split(".")[-2]

    updateinfo_path = os.path.join(updateinfo_prefix, arch, dist, "updateinfo.xml")
    return updateinfo_path

hotfix_type = {
    'bugfix': 'BA',
    'security': 'SA',
    'bugfixsecurity': '',
}

def id_generator(size=10, chars=string.ascii_uppercase + string.digits):
    return ''.join(random.choice(chars) for _ in range(size))

random_id = id_generator()


def main(args):
    """
    Just do something
    """
    print(args)


def get_installed_live_patches():
    item_list = []

    # ('Linux', 'nodetool', '4.19.91-24.1.an7.x86_64', '#1 SMP Wed Jul 21 17:40:23 CST 2021', 'x86_64')
    uname_output = os.uname()
    kv = uname_output[2]

    # TODO do we need to list other kernels' installed live patches
    dir_path = "/var/khotfix/" + kv

    if os.path.isdir(dir_path):
        for name in os.listdir(dir_path):
            if os.path.isdir(os.path.join(dir_path, name)):
                item = {}
                item['hotfix_name'] = name

                with open(os.path.join(dir_path, name, 'description'), "r") as f:
                    item['description'] = f.read()
                item_list.append(item)

    return item_list

def is_module_loaded(name):
    modules = [x.split(' ')[0] for x in open('/proc/modules').readlines()]
    return name in modules

def get_running_live_patches():
    comm = "kpatch list"
    process = Popen(comm,stdout=PIPE, stderr=PIPE, shell=True)
    #TODO solve the yum concurrent lock issue
    stdout, stderr = process.communicate()
    process.wait()

    # eg. h_list = ['4121479', '4375449', '5260815', '5902278']
    # CVE_2023_0461 but name is CVE-2023-0461
    h_list = re.findall(r"kpatch_(.*) \[enabled\]", stdout.decode('utf-8'))
    item_list = []
    # ('Linux', 'nodetool', '4.19.91-24.1.an7.x86_64', '#1 SMP Wed Jul 21 17:40:23 CST 2021', 'x86_64')
    uname_output = os.uname()
    kv = uname_output[2]
    dir_path = "/var/khotfix/" + kv

    # read all installed livepatches
    if os.path.isdir(dir_path):
        for name in os.listdir(dir_path): # name: dirctory name show hotfix under /var/khotfix/{uname}/CVE-{}-{}            
            if os.path.isdir(os.path.join(dir_path, name)):
                c_name = name.replace("-","_") # c_name:adjust kpatch name kpatch_CVE_{}_{}
                if c_name in h_list or is_module_loaded(name):
                    item = {}
                    item['hotfix_name'] = name
                    with open(os.path.join(dir_path, name, 'description'), "r") as f:
                        item['description'] = f.read()
                    item_list.append(item)

    return item_list

def rebuild_hotfix_cache():
    print("Updating Anolis Errata Information... Please wait...")
    # ('Linux', 'nodetool', '4.19.91-24.1.an7.x86_64', '#1 SMP Wed Jul 21 17:40:23 CST 2021', 'x86_64')
    uname_output = os.uname()
    arch = uname_output[-1]
    kv = uname_output[2]
    an_release = kv.split(".")[-2]
    item_list = []

    updateinfo_path = get_updateinfo_path()
    xml_path = load_config("errata_xml")

    try:
        path = os.path.dirname(os.path.realpath(updateinfo_path))
        os.makedirs(path)
    except OSError:
        if not os.path.isdir(path):
            raise

    resp = requests.get(xml_path)
    with open(updateinfo_path, 'wb+') as file:
        file.write(resp.content)

    # 5.10.134-13.an8.x86_64 -> 5.10.134-13
    kv_specific = kv.split('.'+an_release)[0]

    tree = ET.parse(updateinfo_path)
    updateList = tree.getroot().findall('Vulnerability')

    for update in updateList:
        if 'kernel hotfix' not in update.find("synpopsis").text:
            continue

        for rpm in update.find("product").find("item").find("product_package_info").find(arch).iter('item'):
            # rpm_name like kernel-hotfix-CVE-2023-0461-5.10.112-11.1
            # rpm_kv like 5.10.112-11.1
            rpm_kv = rpm.find("rpm_name").text.split('-')[-2] + "-" + rpm.find("rpm_name").text.split('-')[-1]

            pkg_name = rpm.find("rpm_name").text
            if rpm_kv == kv_specific:
                item = {}
                item['id'] = update.find("advisory_id").text
                item['publish_date'] = update.find("publish_date").text
                item['release'] = update.find("modules").find("item").text
                item['description'] = update.find("description").text
                item['pkg_name'] = pkg_name
                if update.find("cve").find("item").text:
                    item['cve_id'] = update.find("cve").find("item").text
                item['pkg_filename'] = rpm.find('rpm_filename').text
                item['pkg_url'] = rpm.find("rpm_url").text
                item_list.append(item)

    s = shelve.open(hotfix_cache_db, flag='c', writeback=True)
    try:
        s['hotfix_list'] = item_list
        s['timestamp'] = datetime.datetime.now()
    finally:
        s.close()

def get_avail_live_patches(initshell=False):
    s = shelve.open(hotfix_cache_db)
    item_list = []
    try:
        if 'timestamp' in s:
            last = s['timestamp']
            now = datetime.datetime.now()
            delta = now - last
            if delta > datetime.timedelta(days=7):
                s.close()
                if not initshell:
                    rebuild_hotfix_cache()
                else:
                    return None
        else:
            # first time, just build
            s.close()
            if not initshell:
                # if not initshell , user can bare to wait update
                rebuild_hotfix_cache()
            else:
                # if initshell, we can not take time to update
                return None
    finally:
        s.close()

    s = shelve.open(hotfix_cache_db)
    item_list = s['hotfix_list']
    s.close()
    return item_list

def output_avail_item(update):
    str = "=" * 80

    desc = tr.fill(update['description'], width=80)

    print(str)
    print("  " + update['pkg_name'])
    print(str)

    print('%15s %s' %("Update ID : ", update['id']))
    print('%15s %s' %("Release : ", update["release"]))
    print('%15s %s' %("Issued : ", update['issued']))
    print('%15s %s' %("Updated : ", update['updated']))
    print('%15s %s' %("Description : ", (desc[:160] + '...(more)') if len(desc) >160 else desc))
    print('%15s %s' %("CVE(s) : ", ",".join(update['cve'])))
    print('%15s %s' %("Severity : ", update['severity']))
    print('\n')

def print_info(update_id, cve, hotfix_id, description, first_time=[]):
    if first_time == []:
        str = "{:<20} {:<14} {:<10} {}".format("Update ID","CVE ID(s)", "Hotfix ID", "Description")
        print('%s' %(str))
        first_time.append('not first_time')

    desc = tr.fill(description, width=80)
    desc = (desc[:50] + '...(more)') if len(desc) >50 else desc
    content = "{:<20} {:<14} {:<10} {}".format(update_id, cve, hotfix_id, desc)
    print('%s' %(content))


def print_installed_live_patches(bugfix, security):
    available_live_patches = get_avail_live_patches()
    installed_live_patches = get_installed_live_patches()
    h_type = ''

    if bugfix or security:
        h_type = hotfix_type[bugfix+security]

    print('Installed patch modules:')
    #TODO: prettifty the output when no cve
    for i_item in installed_live_patches:
        cve = ''
        for a_item in available_live_patches:
            if i_item['hotfix_name'] in a_item['pkg_name'] and h_type in a_item["id"]:
                if a_item['cve_id']:
                    cve = a_item['cve_id']

                print_info(a_item["id"], cve, i_item['hotfix_name'], a_item["description"])

                break

def print_available_live_patches(bugfix, security):
    h_type = ''

    if bugfix or security:
        h_type = hotfix_type[bugfix+security]

    available_live_patches = get_avail_live_patches()

    print('Available and not installed patch modules:')

    for item in available_live_patches:
        if h_type in item['id']:
            #output_avail_item(item)
            # kernel-hotfix-5902278-21.an7.x86_64 -> 5902278
            hotfix_id = item['pkg_name'].split("-")[2]

            #desc = tr.fill(item['description'], width=80)
            #cve = ",".join(item['cve'])
            #desc = (desc[:50] + '...(more)') if len(desc) >50 else desc
            print_info(item['id'], cve, hotfix_id, item['description'])

def print_available_not_installed_live_patches(bugfix, security):
    available_live_patches = get_avail_live_patches()
    installed_live_patches = get_installed_live_patches()
    h_type = ''

    if bugfix or security:
        h_type = hotfix_type[bugfix+security]

    print('Available and not installed patch modules:')
    #TODO: prettifty the output when no cve
    for a_item in available_live_patches:
        cve = ''
        found = False
        for i_item in installed_live_patches:
            if i_item['hotfix_name'] in a_item['pkg_name'] and h_type in a_item["id"]:
                found = True
                break
        # this available is an CVE
        if a_item['cve_id']:
            cve = a_item['cve_id']
        if not found and h_type in a_item["id"]:
            # kernel-hotfix-CVE-2023-31248-5.10.134-12 -> 2023-31248
            if cve:
                splited_name = a_item['pkg_name'].split("-")
                hotfix_id=splited_name[2]+"_"+splited_name[3]+"_"+splited_name[4]
            else:
                # kernel-hotfix-5902278-21.an7.x86_64 -> 5902278
                hotfix_id = a_item['pkg_name'].split("-")[2]
            print_info(a_item["id"], cve, hotfix_id, a_item["description"])

def print_running_live_patches(bugfix, security):
    available_live_patches = get_avail_live_patches()
    running_live_patches = get_running_live_patches()
    h_type = ''

    if bugfix or security:
        h_type = hotfix_type[bugfix+security]

    print('Loaded patch modules:')
    #TODO: prettifty the output when no cve
    for r_item in running_live_patches:
        cve = ''
        for a_item in available_live_patches:
            if r_item['hotfix_name'] in a_item['cve_id'] and h_type in a_item["id"]:
                # if a_item['cve']:
                #   cve=",".join(a_item['cve'])
                if a_item['cve_id']:
                    cve = a_item['cve_id']
                print_info(a_item["id"], cve, r_item['hotfix_name'], a_item["description"])
                break


def list(available, installed, running, bugfix, security):
    # ('Linux', 'nodetool', '4.19.91-24.1.an7.x86_64', '#1 SMP Wed Jul 21 17:40:23 CST 2021', 'x86_64')
    uname_output = os.uname()
    arch = uname_output[-1]
    kv = uname_output[2]
    is_all = True

    # an7\an8 etc
    an = kv.split('.')[-2]

    # 24.1 or 19.1 alike
    kv_specific = kv.split('-')[-1].split('.'+an)[0]

    if available:
        is_all = False
        print_available_live_patches(bugfix, security)

    if installed:
        is_all = False
        print_installed_live_patches(bugfix, security)

    if running:
        is_all = False
        print_running_live_patches(bugfix, security)

    if is_all:
        print_running_live_patches(bugfix, security)
        print('\n')
        print_installed_live_patches(bugfix, security)
        print('\n')
        print_available_not_installed_live_patches(bugfix, security)

def yum_install_pkg(pkg_name):
    comm = "yum install -y " + pkg_name
    process = Popen(comm,stdout=PIPE, stderr=PIPE, shell=True)
    #TODO solve the yum concurrent lock issue
    stdout, stderr = process.communicate()
    process.wait()
    print(stdout.decode('utf-8'))

def update(bugfix, security, cves, ids):
    available_live_patches = get_avail_live_patches()
    is_all = True

    if bugfix:
        is_all = False
        for item in available_live_patches:
            if "BA" in item["id"]:
                yum_install_pkg(item["pkg_name"])
    if security:
        is_all = False
        for item in available_live_patches:
            if "SA" in item["id"]:
                yum_install_pkg(item["pkg_name"])
    if cves:
        is_all = False
        cve_list=cves.split(',')
        for cve in cve_list:
            for item in available_live_patches:
                if cve in item['cve_id']:
                    yum_install_pkg(item['pkg_name'])

    if ids:
        is_all = False
        id_list=ids.split(',')
        for eid in id_list:
            for item in available_live_patches:
                if eid == item['id']:
                    yum_install_pkg(item['pkg_name'])

    if is_all:
        for item in available_live_patches:
            yum_install_pkg(item["pkg_name"])


def uninstall():
    pass

def kpatch_load_live_patches(arg, manual_hotfix=False):
    comm = "kpatch load "
    if not manual_hotfix:
        if arg == "all":
            comm = comm + "--all"
        else:
            comm = comm + arg
    else:
        comm="insmod " + arg

    process = Popen(comm,stdout=PIPE, stderr=PIPE, shell=True)
    stdout, stderr = process.communicate()
    process.wait()

    if stdout:
        logger.info(stdout.decode('utf-8'))
    if stderr:
        logger.error(stderr.decode('utf-8'))

def kpatch_unload_live_patches(arg, manual_hotfix=False):
    comm = "kpatch unload "
    if not manual_hotfix:
        if arg == "all":
            comm = comm + "--all"
        else:
            comm = comm + arg
    else:
        comm = "rmmod " + arg

    process = Popen(comm,stdout=PIPE, stderr=PIPE, shell=True)
    stdout, stderr = process.communicate()
    process.wait()

    if stdout:
        logger.info(stdout.decode('utf-8'))
    if stderr:
        logger.error(stderr.decode('utf-8'))

def load_live_patches_by_name(patch_module, installed_live_patches):
    # ('Linux', 'nodetool', '4.19.91-24.1.an7.x86_64', '#1 SMP Wed Jul 21 17:40:23 CST 2021', 'x86_64')
    uname_output = os.uname()
    # get kernel version
    kv = uname_output[2]

    for i_item in installed_live_patches:
        if i_item['hotfix_name'] in patch_module :
            ko_dir = "/var/khotfix/" + kv + "/" + i_item['hotfix_name'] + "/"
            logger.info("loading %s" %('kernel-hotfix-' + i_item['hotfix_name']))
            # manual hotfix ?
            if os.path.isfile(ko_dir + i_item['hotfix_name'] + ".ko"):
                # /var/khotfix/4.19.91-21.an7.x86_64/5692820/5692820.ko
                kpatch_load_live_patches(ko_dir + i_item['hotfix_name'] + ".ko", True)
            else:
                # /var/khotfix/4.19.91-21.an7.x86_64/5902278/kpatch-5902278.ko
                kpatch_load_live_patches(ko_dir + "kpatch-" + i_item['hotfix_name'] + ".ko")

def unload_live_patches_by_name(patch_module, installed_live_patches):
    # ('Linux', 'nodetool', '4.19.91-24.1.an7.x86_64', '#1 SMP Wed Jul 21 17:40:23 CST 2021', 'x86_64')
    uname_output = os.uname()
    # get kernel version
    kv = uname_output[2]

    for i_item in installed_live_patches:
        if i_item['hotfix_name'] in patch_module :
            ko_dir = "/var/khotfix/" + kv + "/" + i_item['hotfix_name'] + "/"
            logger.info("unoading %s" %('kernel-hotfix-' + i_item['hotfix_name']))
            # manual hotfix ?
            if os.path.isfile(ko_dir + i_item['hotfix_name'] + ".ko"):
                # /var/khotfix/4.19.91-21.an7.x86_64/5692820/5692820.ko
                kpatch_unload_live_patches(ko_dir + i_item['hotfix_name'] + ".ko", True)
            else:
                # /var/khotfix/4.19.91-21.an7.x86_64/5902278/kpatch-5902278.ko
                kpatch_unload_live_patches(ko_dir + "kpatch-" + i_item['hotfix_name'] + ".ko")

def get_hotfix_ids_by_cve_id(cve_string, available_live_patches):
    cve_pattern = r'CVE-\d{4}-\d{4,7}'
    cves = re.findall(cve_pattern, cve_string)
    hotfix_ids = []
        
    for cve in cves:
        for item in available_live_patches:
            if cve in item['cve_id']:
                h_id = item['pkg_name'].split("-")[2]
                hotfix_ids.append(h_id)

    return hotfix_ids

def get_hotfix_ids_by_update_id(update_id_string, available_live_patches):
    pattern = r'CVE-\d{4}:\d{4}'
    uids = re.findall(pattern, update_id_string)
    hotfix_ids = []
        
    for uid in uids:
        for item in available_live_patches:
            if uid in item['id']:
                h_id = item['pkg_name'].split("-")[2]
                hotfix_ids.append(h_id)

    return hotfix_ids


def load(bugfix, security, patch_module):
    is_all = True
    # ('Linux', 'nodetool', '4.19.91-24.1.an7.x86_64', '#1 SMP Wed Jul 21 17:40:23 CST 2021', 'x86_64')
    uname_output = os.uname()
    # get kernel version
    kv = uname_output[2]
    # an7\an8\an23
    al = kv.split('.')[-2]

    # 24.1 or 19.1 alike
    kv_specific = kv.split('-')[-1].split('.'+al)[0]
    arch = uname_output[-1]

    available_live_patches = get_avail_live_patches()
    installed_live_patches = get_installed_live_patches()

    if bugfix:
        is_all = False
        for i_item in installed_live_patches:
            for a_item in available_live_patches:
                if i_item['hotfix_name'] in a_item['pkg_name'] and "BA" in a_item["id"]:
                    ko_dir = "/var/khotfix/" + kv + "/" + i_item['hotfix_name'] + "/"
                    logger.info("loading %s" %(a_item['pkg_name']))
                    # manual hotfix ?
                    if os.path.isfile(ko_dir + i_item['hotfix_name'] + ".ko"):
                        # /var/khotfix/4.19.91-21.an7.x86_64/5692820/5692820.ko
                        kpatch_load_live_patches(ko_dir + i_item['hotfix_name'] + ".ko") 
                    else:
                        # /var/khotfix/4.19.91-21.an7.x86_64/5902278/kpatch-5902278.ko
                        kpatch_load_live_patches(ko_dir + "kpatch-" + i_item['hotfix_name'] + ".ko")
                    break

    if security:
        is_all = False
        for i_item in installed_live_patches:
            for a_item in available_live_patches:
                if i_item['hotfix_name'] in a_item['pkg_name'] and "SA" in a_item["id"]:
                    ko_dir = "/var/khotfix/" + kv + "/" + i_item['hotfix_name'] + "/"
                    logger.info("loading %s" %(a_item['pkg_name']))
                    # manual hotfix ?
                    if os.path.isfile(ko_dir + i_item['hotfix_name'] + ".ko"):
                        # /var/khotfix/4.19.91-21.an7.x86_64/5692820/5692820.ko
                        kpatch_load_live_patches(ko_dir + i_item['hotfix_name'] + ".ko") 
                    else:
                        # /var/khotfix/4.19.91-21.an7.x86_64/5902278/kpatch-5902278.ko
                        kpatch_load_live_patches(ko_dir + "kpatch-" + i_item['hotfix_name'] + ".ko")
                    break

    if patch_module != random_id:
        is_all = False

        # load by CVE IDs
        hotfix_ids = get_hotfix_ids_by_cve_id(patch_module, available_live_patches)
        for h_id in hotfix_ids:
            load_live_patches_by_name(h_id, installed_live_patches)

        # load by update IDs
        hotfix_ids = get_hotfix_ids_by_update_id(patch_module, available_live_patches)
        for h_id in hotfix_ids:
            load_live_patches_by_name(h_id, installed_live_patches)

        patch_module = os.path.basename(patch_module)
        # replace '-' to '_' for module name
        patch_module = patch_module.replace('-', '_')
        # if any, strip the file extension name
        patch_module = os.path.splitext(patch_module)[0]

        load_live_patches_by_name(patch_module, installed_live_patches)

    if is_all:
        for i_item in installed_live_patches:
            ko_dir = "/var/khotfix/" + kv + "/" + i_item['hotfix_name'] + "/"
            logger.info("loading %s" %('kernel-hotfix-' + i_item['hotfix_name'] +"-"+kv_specific+"."+arch))
            # manual hotfix ?
            if os.path.isfile(ko_dir + i_item['hotfix_name'] + ".ko"):
                # /var/khotfix/4.19.91-21.an7.x86_64/5692820/5692820.ko
                kpatch_load_live_patches(ko_dir + i_item['hotfix_name'] + ".ko")
            else:
                # /var/khotfix/4.19.91-21.an7.x86_64/5902278/kpatch-5902278.ko
                kpatch_load_live_patches(ko_dir + "kpatch-" + i_item['hotfix_name'] + ".ko")

def unload(bugfix, security, patch_module):
    is_all = True
    # ('Linux', 'nodetool', '4.19.91-24.1.an7.x86_64', '#1 SMP Wed Jul 21 17:40:23 CST 2021', 'x86_64')
    uname_output = os.uname()
    # get kernel version
    kv = uname_output[2]
    # an7\an8
    al = kv.split('.')[-2]

    # 24.1 or 19.1 alike
    kv_specific = kv.split('-')[-1].split('.'+al)[0]
    arch = uname_output[-1]

    available_live_patches = get_avail_live_patches()
    installed_live_patches = get_installed_live_patches()

    if bugfix:
        is_all = False
        for i_item in installed_live_patches:
            for a_item in available_live_patches:
                if i_item['hotfix_name'] in a_item['pkg_name'] and "BA" in a_item["id"]:
                    ko_dir = "/var/khotfix/" + kv + "/" + i_item['hotfix_name'] + "/"
                    logger.info("unloading %s" %(a_item['pkg_name']))
                    # manual hotfix ?
                    if os.path.isfile(ko_dir + i_item['hotfix_name'] + ".ko"):
                        # /var/khotfix/4.19.91-21.an7.x86_64/5692820/5692820.ko
                        kpatch_unload_live_patches(ko_dir + i_item['hotfix_name'] + ".ko", True)
                    else:
                        # /var/khotfix/4.19.91-21.an7.x86_64/5902278/kpatch-5902278.ko
                        kpatch_unload_live_patches(ko_dir + "kpatch-" + i_item['hotfix_name'] + ".ko")
                    break

    if security:
        is_all = False
        for i_item in installed_live_patches:
            for a_item in available_live_patches:
                if i_item['hotfix_name'] in a_item['pkg_name'] and "SA" in a_item["id"]:
                    ko_dir = "/var/khotfix/" + kv + "/" + i_item['hotfix_name'] + "/"
                    logger.info("unloading %s" %(a_item['pkg_name']))
                    # manual hotfix ?
                    if os.path.isfile(ko_dir + i_item['hotfix_name'] + ".ko"):
                        # /var/khotfix/4.19.91-21.an7.x86_64/5692820/5692820.ko
                        kpatch_unload_live_patches(ko_dir + i_item['hotfix_name'] + ".ko", True)
                    else:
                        # /var/khotfix/4.19.91-21.an7.x86_64/5902278/kpatch-5902278.ko
                        kpatch_unload_live_patches(ko_dir + "kpatch-" + i_item['hotfix_name'] + ".ko")
                    break

    if patch_module != random_id:
        is_all = False
        # load by CVE IDs
        hotfix_ids = get_hotfix_ids_by_cve_id(patch_module, available_live_patches)
        for h_id in hotfix_ids:
            unload_live_patches_by_name(h_id, installed_live_patches)

        # load by update IDs
        hotfix_ids = get_hotfix_ids_by_update_id(patch_module, available_live_patches)
        for h_id in hotfix_ids:
            unload_live_patches_by_name(h_id, installed_live_patches)

        patch_module = os.path.basename(patch_module)
        # replace '-' to '_' for module name
        patch_module = patch_module.replace('-', '_')
        # if any, strip the file extension name
        patch_module = os.path.splitext(patch_module)[0]

        unload_live_patches_by_name(patch_module, installed_live_patches)

    if is_all:
        for i_item in installed_live_patches:
            ko_dir = "/var/khotfix/" + kv + "/" + i_item['hotfix_name'] + "/"
            logger.info("unloading %s" %('kernel-hotfix-' + i_item['hotfix_name'] +"-"+kv_specific+"."+arch))
            # manual hotfix ?
            if os.path.isfile(ko_dir + i_item['hotfix_name'] + ".ko"):
                # /var/khotfix/4.19.91-21.an7.x86_64/5692820/5692820.ko
                kpatch_unload_live_patches(ko_dir + i_item['hotfix_name'] + ".ko", True)
            else:
                # /var/khotfix/4.19.91-21.an7.x86_64/5902278/kpatch-5902278.ko
                kpatch_unload_live_patches(ko_dir + "kpatch-" + i_item['hotfix_name'] + ".ko")

def sync():
    try:
        print("livepatch-mgr now sync the data with anolis errata...")
        rebuild_hotfix_cache()
        print("data buffer sync finished...")
    except Exception as e:
        print(str(e))

def remind(enable, disable):
    if enable:
        # copy livepatch_reminder.sh
        cmd="sudo cp -f /etc/livepatch-mgr/livepatch_reminder.sh /etc/profile.d"
        output = os.system(cmd)
        if output == 0:
            print("livepatch-mgr reminder enabled...")
        else:
            print("livepatch-mgr reminder enable failed...")
        exit(0)
    if disable:
        # disable the reminder
        cmd="sudo rm -rf /etc/profile.d/livepatch_reminder.sh"
        output=os.system(cmd)
        if output == 0:
            print("livepatch-mgr reminder disabled...")
        else:
            print("livepatch-mgr reminder disable failed...")
        exit(0)

    print("****************** Livepatch-mgr Security Reminder ******************")
    available_live_patches = get_avail_live_patches(initshell=True)
    installed_live_patches = get_installed_live_patches()
    running_live_patched = get_running_live_patches()
    h_type= ''
    avail_not_install_pkg = 0
    installed_not_run_pkg = 0

    if available_live_patches is None:
        # error of getting datas
        print("\033[31m Error:\033[0m Hotfix data cache is missing \n please use \"livepatch-mgr sync\" to rebuild")
        print("**********************************************************************")
        exit(0)

    for a_item in available_live_patches:
        found = False
        for i_item in installed_live_patches:
            if i_item['hotfix_name'] in a_item['pkg_name'] and h_type in a_item["id"]:
                found = True
                break
        if not found:
            avail_not_install_pkg += 1

    for i_item in installed_live_patches:
        running = False
        for r_item in running_live_patched:
            if i_item['hotfix_name'] == r_item['hotfix_name']:
                running = True
                break
        if not running:
            installed_not_run_pkg += 1
    
    print("Your system have \033[31m[{}]\033[0m security packages available to install".format(avail_not_install_pkg))
    print("Your system have \033[31m[{}]\033[0m security packages installed but not loaded".format(installed_not_run_pkg))

    if avail_not_install_pkg == 0 and installed_not_run_pkg == 0:
        print("Conclusion : \033[32m Safe \033[0m")
    else:
        print("Conclusion : \033[33m UnSafe \033[0m Use \"livepatch-mgr\" to fix it")
    print("*********************************************************************")


def is_prerequisite_statified():
    if os.geteuid() != 0:
        logger.error("This command has to be run under the root user.")
        return False

    ts = rpm.TransactionSet()
    mi = ts.dbMatch( 'name', 'kpatch')

    try:
        h = next(mi)
    except StopIteration:
        logger.error("kpatch package not installed, please run yum install kpatch")
        return False

    return True

def load_config(para):
    config = configparser.ConfigParser()
    config.read(config_file)
    # sections
    sections = config.sections()

    if para == "updateinfo_path":
        return config.get("updateinfo", "updateinfo_directory")

    if para == "errata_xml":
        enabled_repo = config.get("errata_xml", "enabled_repo")
        return config.get("errata_xml", enabled_repo)
    

if __name__ == '__main__':
    # create the top-level parser
    parser = argparse.ArgumentParser(prog='livepatch-mgr')
    
    # create sub-parser
    sub_parsers = parser.add_subparsers(dest='subparser',help='sub-command help')

    # create the parser for the "list" sub-command
    parser_list= sub_parsers.add_parser('list', help='list live patches')
    parser_list.add_argument('--available', '-a', action='store_true',help='list all available live patches for the running kernel')
    parser_list.add_argument('--installed', '-i', action='store_true',help='list all installed live patches for the running kernel')
    parser_list.add_argument('--running', '-r', action='store_true',help='list all running live patches for the running kernel')
    parser_list.add_argument('--bugfix', action='store_const', const="bugfix", default='', help='list bugfix live patches for the running kernel')
    parser_list.add_argument('--security', action='store_const',const="security", default='', help='list security live patches for the running kernel')
    
    #create the parser for the "update" sub-command
    parser_update = sub_parsers.add_parser('update', help='update all available live patches for the running kernel')
    parser_update.add_argument('--bugfix',   action='store_true', help='update bugfix live patches for the running kernel')
    parser_update.add_argument('--security', action='store_true', help='update security live patches for the running kernel')
    parser_update.add_argument('--cves', help='update the given CVE list for the running kernel')
    parser_update.add_argument('--ids', help='update the given errata ID list for the running kernel')

    #create the parser for the "load" sub-command
    parser_load = sub_parsers.add_parser('load', help='load all installed live patches for the running kernel')
    parser_load.add_argument('--bugfix',   action='store_true', help='load bugfix type of installed live patches for the running kernel')
    parser_load.add_argument('--security',   action='store_true', help='load security type of installed live patches for the running kernel')
    parser_load.add_argument('patch_module', nargs='?', default=random_id, help='load given live patch module for the running kernel')

    #create the parser for the "unload" sub-command
    parser_unload = sub_parsers.add_parser('unload', help='unload all available live patches for the running kernel')
    parser_unload.add_argument('--bugfix',   action='store_true', help='unload bugfix type of installed live patches for the running kernel')
    parser_unload.add_argument('--security',   action='store_true', help='unload security type of installed live patches for the running kernel')
    parser_unload.add_argument('patch_module', nargs='?', default=random_id, help='unload given live patch module for the running kernel')

    #create the parser for "sync" sub-command
    parser_refresh = sub_parsers.add_parser('sync', help="force to refresh sync CVE data buffer")

    #create the parser for "remind" sub-command
    parser_remind = sub_parsers.add_parser('remind', help="to simply remind the number of package to be take care of")
    parser_remind.add_argument('--enable', action='store_true', help="enable the remind when login shell")
    parser_remind.add_argument('--disable', action='store_true', help="disable the remind when login shell")

    args = parser.parse_args()
    #main(args)

    if not is_prerequisite_statified():
        logger.error("Prerequisite not statified!")
        sys.exit(os.EX_UNAVAILABLE)

    kwargs = vars(args)

    # change the woring directory to /etc/livepatch-mgr
    os.chdir(working_directory)

    try:
        globals()[kwargs.pop('subparser')](**kwargs)
    except Exception as e:
        if str(e) == "None":
            print("No parameter is given to livepatch-mgr")
        else:
            print(str(e))


