/*
 * Copyright (c) 2022 Huawei Technologies Co.,Ltd.
 *
 * GR 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.
 * -------------------------------------------------------------------------
 *
 * gr_session.c
 *
 *
 * IDENTIFICATION
 *    src/common/gr_session.c
 *
 * -------------------------------------------------------------------------
 */

#include "gr_session.h"
#include "cm_utils.h"
#include "gr_diskgroup.h"
#include "gr_malloc.h"
#include "gr_file.h"
#include "cm_system.h"
#include "gr_thv.h"
#include "gr_filesystem.h"
#include "gr_resource_mgr.h"
#include "gr_error_handler.h"

#ifdef __cplusplus
extern "C" {
#endif
status_t gr_session_fd_add(gr_session_t *session, int64 fd, uint64 ftid, const char *file_name)
{
    gr_session_fd_entry_t *ent = GR_MALLOC_STRUCT(gr_session_fd_entry_t);
    if (ent == NULL) {
        GR_ERROR_RETURN(GR_ERR_CATEGORY_RESOURCE, ERR_GR_NO_SPACE, CM_ERROR,
                       "Failed to allocate memory for session FD entry");
    }
    ent->fd = fd;
    ent->ftid = ftid;
    ent->next = NULL;
    if (file_name != NULL) {
        errno_t err = strncpy_s(ent->file_name, sizeof(ent->file_name), file_name, sizeof(ent->file_name) - 1);
        if (SECUREC_UNLIKELY(err != EOK)) {
            cm_free(ent);
            GR_ERROR_RETURN(GR_ERR_CATEGORY_SYSTEM, ERR_SYSTEM_CALL, CM_ERROR,
                           "Failed to copy file name, errno: %d", err);
        }
    } else {
        ent->file_name[0] = '\0';
    }

    cm_spin_lock(&session->fd_lock, NULL);
    ent->next = session->fd_list_head;
    session->fd_list_head = ent;
    session->fd_count++;
    cm_spin_unlock(&session->fd_lock);
    return CM_SUCCESS;
}

bool32 gr_session_fd_remove(gr_session_t *session, int64 fd)
{
    cm_spin_lock(&session->fd_lock, NULL);
    gr_session_fd_entry_t *prev = NULL;
    gr_session_fd_entry_t *curr = session->fd_list_head;
    while (curr != NULL) {
        if (curr->fd == fd) {
            if (prev == NULL) {
                session->fd_list_head = curr->next;
            } else {
                prev->next = curr->next;
            }
            session->fd_count--;
            /* push entry into freelist for reuse instead of freeing immediately */
            curr->next = session->fd_free_list;
            session->fd_free_list = curr;
            session->fd_free_count++;
            cm_spin_unlock(&session->fd_lock);
            return CM_TRUE;
        }
        prev = curr;
        curr = curr->next;
    }
    cm_spin_unlock(&session->fd_lock);
    return CM_FALSE;
}

void gr_session_fd_close_all(gr_session_t *session)
{
    if (session == NULL) {
        GR_ERROR_HANDLE(GR_ERR_CATEGORY_SESSION, ERR_GR_SESSION_INVALID_ID,
                      "gr_session_fd_close_all: session is NULL");
        return;
    }

    LOG_DEBUG_INF("[GR_SESSION]session %u starting to close all file descriptors", session->id);
    
    cm_spin_lock(&session->fd_lock, NULL);
    gr_session_fd_entry_t *curr = session->fd_list_head;
    uint32_t fd_count = session->fd_count;
    session->fd_list_head = NULL;
    session->fd_count = 0;
    cm_spin_unlock(&session->fd_lock);

    LOG_DEBUG_INF("[GR_SESSION]session %u found %u file descriptors to close", session->id, fd_count);

    uint32_t closed_count = 0;
    while (curr != NULL) {
        gr_session_fd_entry_t *next = curr->next;
        LOG_DEBUG_INF("[GR_SESSION]session %u closing fd %lld (entry %u/%u)", 
                     session->id, curr->fd, closed_count + 1, fd_count);
        
        status_t close_status = gr_filesystem_close(curr->fd, CM_FALSE);
        if (close_status != CM_SUCCESS) {
            LOG_RUN_WAR("[GR_SESSION]session %u failed to close fd %lld, status: %d", 
                       session->id, curr->fd, close_status);
        } else {
            closed_count++;
        }

        /* after closing fd, recycle entry into freelist for future reuse */
        cm_spin_lock(&session->fd_lock, NULL);
        curr->next = session->fd_free_list;
        session->fd_free_list = curr;
        session->fd_free_count++;
        cm_spin_unlock(&session->fd_lock);

        curr = next;
    }

    LOG_RUN_INF("[GR_SESSION]session %u completed closing file descriptors: %u/%u closed successfully", 
               session->id, closed_count, fd_count);
}

gr_session_ctrl_t g_gr_session_ctrl = {0};

status_t gr_extend_session(uint32_t extend_num)
{
    uint32_t old_alloc_sessions = g_gr_session_ctrl.alloc_sessions;
    uint32_t new_alloc_sessions = g_gr_session_ctrl.alloc_sessions + extend_num;
    if (new_alloc_sessions > g_gr_session_ctrl.total) {
        GR_ERROR_RETURN(GR_ERR_CATEGORY_SESSION, ERR_GR_SESSION_EXTEND, CM_ERROR,
                       "Failed to extend session, expect new alloc sessions %u, but max is %u",
                       new_alloc_sessions, g_gr_session_ctrl.total);
    }
    for (uint32_t i = old_alloc_sessions; i < new_alloc_sessions; i++) {
        g_gr_session_ctrl.sessions[i] = (gr_session_t *)cm_malloc(sizeof(gr_session_t));
        if (g_gr_session_ctrl.sessions[i] == NULL) {
            GR_ERROR_RETURN(GR_ERR_CATEGORY_RESOURCE, ERR_GR_SESSION_EXTEND, CM_ERROR,
                           "Failed to alloc memory for session %u", i);
            // Cleanup already allocated sessions
            for (uint32_t j = old_alloc_sessions; j < i; j++) {
                CM_FREE_PTR(g_gr_session_ctrl.sessions[j]);
                g_gr_session_ctrl.sessions[j] = NULL;
            }
            g_gr_session_ctrl.alloc_sessions = old_alloc_sessions;
        }
        
        errno_t err = memset_s(g_gr_session_ctrl.sessions[i], sizeof(gr_session_t), 0, sizeof(gr_session_t));
        if (err != EOK) {
            GR_ERROR_RETURN(GR_ERR_CATEGORY_SYSTEM, ERR_GR_SESSION_EXTEND, CM_ERROR,
                           "Failed to initialize session %u memory, errno: %d", i, err);
            CM_FREE_PTR(g_gr_session_ctrl.sessions[i]);
            g_gr_session_ctrl.sessions[i] = NULL;
            // Cleanup already allocated sessions
            for (uint32_t j = old_alloc_sessions; j < i; j++) {
                CM_FREE_PTR(g_gr_session_ctrl.sessions[j]);
                g_gr_session_ctrl.sessions[j] = NULL;
            }
            g_gr_session_ctrl.alloc_sessions = old_alloc_sessions;
        }
        
        g_gr_session_ctrl.sessions[i]->id = i;
        g_gr_session_ctrl.sessions[i]->is_used = CM_FALSE;
        g_gr_session_ctrl.sessions[i]->is_closed = CM_TRUE;
        g_gr_session_ctrl.sessions[i]->put_log = CM_FALSE;
        g_gr_session_ctrl.sessions[i]->objectid = i;
        g_gr_session_ctrl.sessions[i]->is_holding_hotpatch_latch = CM_FALSE;
        g_gr_session_ctrl.alloc_sessions++;
        
        LOG_DEBUG_INF("Allocated session %u successfully.", i);
    }
    LOG_RUN_INF("Succeed to extend sessions to %u.", g_gr_session_ctrl.alloc_sessions);
    return CM_SUCCESS;
}

status_t gr_init_session_pool(uint32_t max_session_num)
{
    uint32_t gr_session_size = (uint32_t)(max_session_num * sizeof(gr_session_t *));
    g_gr_session_ctrl.sessions = cm_malloc(gr_session_size);
    if (g_gr_session_ctrl.sessions == NULL) {
        return ERR_GR_SESSION_CREATE;
    }
    errno_t errcode = memset_s(g_gr_session_ctrl.sessions, gr_session_size, 0, gr_session_size);
    securec_check_ret(errcode);
    g_gr_session_ctrl.alloc_sessions = 0;
    uint32_t extend_num = max_session_num >= GR_SESSION_NUM_PER_GROUP ? GR_SESSION_NUM_PER_GROUP : max_session_num;
    g_gr_session_ctrl.total = max_session_num;
    status_t status = gr_extend_session(extend_num);
    if (status != CM_SUCCESS) {
        return status;
    }
    g_gr_session_ctrl.is_inited = CM_TRUE;
    return CM_SUCCESS;
}

gr_session_ctrl_t *gr_get_session_ctrl(void)
{
    return &g_gr_session_ctrl;
}

uint32_t gr_get_uwression_startid(void)
{
    gr_config_t *inst_cfg = gr_get_inst_cfg();
    uint32_t start_sid = (uint32_t)GR_BACKGROUND_TASK_NUM;
    if (inst_cfg->params.nodes_list.inst_cnt > 1) {
        start_sid = start_sid + inst_cfg->params.channel_num + inst_cfg->params.work_thread_cnt;
    }
    return start_sid;
}

uint32_t gr_get_max_total_session_cnt(void)
{
    gr_config_t *inst_cfg = gr_get_inst_cfg();
    return gr_get_uwression_startid() + inst_cfg->params.cfg_session_num;
}

uint32_t gr_get_recover_task_idx(void)
{
    return (gr_get_uwression_startid() - (uint32_t)GR_BACKGROUND_TASK_NUM);
}

uint32_t gr_get_delay_clean_task_idx(void)
{
    return (gr_get_uwression_startid() - (uint32_t)GR_BACKGROUND_TASK_NUM) + GR_DELAY_CLEAN_BACKGROUND_TASK;
}

uint32_t gr_get_alarm_check_task_idx(void)
{
    return (gr_get_uwression_startid() - (uint32_t)GR_BACKGROUND_TASK_NUM) + GR_ALARM_CHECK_TASK;
}

static status_t gr_init_session(gr_session_t *session, const cs_pipe_t *pipe)
{
    gr_latch_stack_t *latch_stack = &session->latch_stack;
    errno_t errcode = memset_s(latch_stack, sizeof(gr_latch_stack_t), 0, sizeof(gr_latch_stack_t));
    securec_check_ret(errcode);
    session->is_direct = CM_TRUE;
    session->connected = CM_FALSE;
    if (pipe != NULL) {
        session->pipe = *pipe;
        session->connected = CM_TRUE;
    }
    session->is_closed = CM_FALSE;
    session->proto_type = PROTO_TYPE_UNKNOWN;
    session->status = GR_SESSION_STATUS_IDLE;
    session->client_version = GR_PROTO_VERSION;
    session->proto_version = GR_PROTO_VERSION;
    session->is_holding_hotpatch_latch = CM_FALSE;
    GR_RETURN_IF_ERROR(init_session_hash_mgr(session));
    // init session fd tracking
    GS_INIT_SPIN_LOCK(session->fd_lock);
    session->fd_list_head = NULL;
    session->fd_free_list = NULL;
    session->fd_count = 0;
    session->fd_free_count = 0;
    // init session directory handle tracking
    GS_INIT_SPIN_LOCK(session->dir_lock);
    session->dir_list_head = NULL;
    session->dir_count = 0;
    return CM_SUCCESS;
}

gr_session_t *gr_get_reserv_session(uint32_t idx)
{
    gr_session_ctrl_t *session_ctrl = gr_get_session_ctrl();
    gr_session_t *session = session_ctrl->sessions[idx];
    return session;
}

status_t gr_create_session(const cs_pipe_t *pipe, gr_session_t **session)
{
    uint32_t i, id;

    *session = NULL;
    id = GR_INVALID_ID32;
    cm_spin_lock(&g_gr_session_ctrl.lock, NULL);

    uint32_t start_sid = gr_get_uwression_startid();
    uint32_t end_sid = gr_get_max_total_session_cnt();
    status_t status;
    for (i = start_sid; i < end_sid; i++) {
        if (i >= g_gr_session_ctrl.alloc_sessions) {
            uint32_t extend_num =
                g_gr_session_ctrl.total - g_gr_session_ctrl.alloc_sessions >= GR_SESSION_NUM_PER_GROUP ?
                    GR_SESSION_NUM_PER_GROUP :
                    g_gr_session_ctrl.total - g_gr_session_ctrl.alloc_sessions;
            status = gr_extend_session(extend_num);
            if (status != CM_SUCCESS) {
                cm_spin_unlock(&g_gr_session_ctrl.lock);
                return status;
            }
        }
        if (g_gr_session_ctrl.sessions[i]->is_used == CM_FALSE) {
            id = i;
            break;
        }
    }
    if (id == GR_INVALID_ID32) {
        LOG_DEBUG_INF("No sessions are available.");
        cm_spin_unlock(&g_gr_session_ctrl.lock);
        return ERR_GR_SESSION_CREATE;
    }
    *session = g_gr_session_ctrl.sessions[i];
    LOG_DEBUG_INF("Session[%u] is available.", id);
    cm_spin_lock(&(*session)->lock, NULL);
    g_gr_session_ctrl.used_count++;
    (*session)->is_used = CM_TRUE;
    cm_spin_unlock(&(*session)->lock);
    cm_spin_unlock(&g_gr_session_ctrl.lock);
    GR_RETURN_IF_ERROR(gr_init_session(*session, pipe));
    return CM_SUCCESS;
}

void gr_destroy_session_inner(gr_session_t *session)
{
    if (session->connected == CM_TRUE) {
        cs_disconnect(&session->pipe);
        session->connected = CM_FALSE;
    }
    g_gr_session_ctrl.used_count--;
    session->is_closed = CM_TRUE;
    session->is_used = CM_FALSE;
    errno_t ret = memset_sp(&session->cli_info, sizeof(session->cli_info), 0, sizeof(session->cli_info));
    securec_check_panic(ret);
    session->client_version = GR_PROTO_VERSION;
    session->proto_version = GR_PROTO_VERSION;
    session->put_log = CM_FALSE;
    session->is_holding_hotpatch_latch = CM_FALSE;

    /* free hash manager memory if allocated */
    CM_FREE_PTR(session->hash_mgr.hash_items);

    /* free any remaining fd entries and freelist nodes to avoid leaks */
    cm_spin_lock(&session->fd_lock, NULL);
    gr_session_fd_entry_t *curr = session->fd_list_head;
    session->fd_list_head = NULL;
    session->fd_count = 0;
    gr_session_fd_entry_t *free_curr = session->fd_free_list;
    session->fd_free_list = NULL;
    session->fd_free_count = 0;
    cm_spin_unlock(&session->fd_lock);

    while (curr != NULL) {
        gr_session_fd_entry_t *next = curr->next;
        CM_FREE_PTR(curr);
        curr = next;
    }
    while (free_curr != NULL) {
        gr_session_fd_entry_t *next = free_curr->next;
        CM_FREE_PTR(free_curr);
        free_curr = next;
    }

    /* free any remaining directory entries to avoid leaks (handles should be closed already) */
    cm_spin_lock(&session->dir_lock, NULL);
    gr_session_dir_entry_t *dir_curr = session->dir_list_head;
    session->dir_list_head = NULL;
    session->dir_count = 0;
    cm_spin_unlock(&session->dir_lock);

    while (dir_curr != NULL) {
        gr_session_dir_entry_t *next = dir_curr->next;
        CM_FREE_PTR(dir_curr);
        dir_curr = next;
    }
}

status_t gr_session_dir_add(gr_session_t *session, uint64 handle)
{
    gr_session_dir_entry_t *ent = GR_MALLOC_STRUCT(gr_session_dir_entry_t);
    if (ent == NULL) {
        GR_ERROR_RETURN(GR_ERR_CATEGORY_RESOURCE, ERR_GR_NO_SPACE, CM_ERROR,
                       "Failed to allocate memory for session DIR entry");
    }
    ent->handle = handle;
    ent->next = NULL;

    cm_spin_lock(&session->dir_lock, NULL);
    ent->next = session->dir_list_head;
    session->dir_list_head = ent;
    session->dir_count++;
    cm_spin_unlock(&session->dir_lock);
    return CM_SUCCESS;
}

bool32 gr_session_dir_remove(gr_session_t *session, uint64 handle)
{
    cm_spin_lock(&session->dir_lock, NULL);
    gr_session_dir_entry_t *prev = NULL;
    gr_session_dir_entry_t *curr = session->dir_list_head;
    while (curr != NULL) {
        if (curr->handle == handle) {
            if (prev == NULL) {
                session->dir_list_head = curr->next;
            } else {
                prev->next = curr->next;
            }
            session->dir_count--;
            cm_spin_unlock(&session->dir_lock);
            CM_FREE_PTR(curr);
            return CM_TRUE;
        }
        prev = curr;
        curr = curr->next;
    }
    cm_spin_unlock(&session->dir_lock);
    return CM_FALSE;
}

void gr_session_dir_close_all(gr_session_t *session)
{
    if (session == NULL) {
        GR_ERROR_HANDLE(GR_ERR_CATEGORY_SESSION, ERR_GR_SESSION_INVALID_ID,
                      "gr_session_dir_close_all: session is NULL");
        return;
    }

    LOG_DEBUG_INF("[GR_SESSION]session %u starting to close all directory handles", session->id);

    cm_spin_lock(&session->dir_lock, NULL);
    gr_session_dir_entry_t *curr = session->dir_list_head;
    uint32_t dir_count = session->dir_count;
    session->dir_list_head = NULL;
    session->dir_count = 0;
    cm_spin_unlock(&session->dir_lock);

    LOG_DEBUG_INF("[GR_SESSION]session %u found %u directory handles to close", session->id, dir_count);

    uint32_t closed_count = 0;
    while (curr != NULL) {
        gr_session_dir_entry_t *next = curr->next;
        LOG_DEBUG_INF("[GR_SESSION]session %u closing dir handle %llu (entry %u/%u)",
                     session->id, (unsigned long long)curr->handle, closed_count + 1, dir_count);

        status_t close_status = gr_filesystem_closedir(curr->handle);
        if (close_status != CM_SUCCESS) {
            LOG_RUN_WAR("[GR_SESSION]session %u failed to close dir handle %llu, status: %d",
                       session->id, (unsigned long long)curr->handle, close_status);
        } else {
            closed_count++;
        }

        CM_FREE_PTR(curr);
        curr = next;
    }

    LOG_RUN_INF("[GR_SESSION]session %u completed closing directory handles: %u/%u closed successfully",
               session->id, closed_count, dir_count);
}

void gr_destroy_session(gr_session_t *session)
{
    cm_spin_lock(&g_gr_session_ctrl.lock, NULL);
    cm_spin_lock(&session->shm_lock, NULL);
    LOG_DEBUG_INF("Succeed to lock session %u shm lock", session->id);
    gr_destroy_session_inner(session);
    cm_spin_unlock(&session->shm_lock);
    LOG_DEBUG_INF("Succeed to unlock session %u shm lock", session->id);
    cm_spin_unlock(&g_gr_session_ctrl.lock);
}

gr_session_t *gr_get_session(uint32_t sid)
{
    if (sid >= g_gr_session_ctrl.alloc_sessions || sid >= g_gr_session_ctrl.total) {
        return NULL;
    }
    return g_gr_session_ctrl.sessions[sid];
}

// only used by api-client or by clean
bool32 gr_unlock_shm_meta_s_with_stack(gr_session_t *session, gr_shared_latch_t *shared_latch, bool32 is_try_lock)
{
    CM_ASSERT(session != NULL);
    // Cannot call checkcm_panic_log with gr_is_server
    CM_ASSERT(shared_latch->latch.stat != LATCH_STATUS_IDLE);
    session->latch_stack.stack_top_bak = session->latch_stack.stack_top;
    session->latch_stack.op = LATCH_SHARED_OP_UNLATCH;

    spin_statis_t *stat_spin = NULL;
    uint32_t sid = GR_SESSIONID_IN_LOCK(session->id);
    if (!is_try_lock) {
        cm_spin_lock_by_sid(sid, &shared_latch->latch.lock, stat_spin);
    } else {
        bool32 is_locked = cm_spin_try_lock(&shared_latch->latch.lock);
        if (!is_locked) {
            return CM_FALSE;
        }
    }
    // for shared latch in shm, need to backup first
    gr_set_latch_extent(&shared_latch->latch_extent, shared_latch->latch.stat, shared_latch->latch.shared_count);

    // begin to change latch
    session->latch_stack.op = LATCH_SHARED_OP_UNLATCH_BEG;

    CM_ASSERT(shared_latch->latch.shared_count > 0);
    shared_latch->latch.shared_count--;
    if (shared_latch->latch.shared_count == 0) {
        if (shared_latch->latch.stat == LATCH_STATUS_S) {
            shared_latch->latch.stat = LATCH_STATUS_IDLE;
        }
        shared_latch->latch.sid = 0;
    }
    shared_latch->latch_extent.shared_sid_count -= sid;

    cm_spin_unlock(&shared_latch->latch.lock);

    // Put this after the unlock to make sure: when error happens after unlock, do NOT operate the unlatched latch
    // Begin to change stack
    CM_ASSERT(session->latch_stack.stack_top);
    // In normal case, should be stack_top-- first, then set [stack_top].type = GR_LATCH_OFFSET_INVALID
    // but may NOT do [stack_top].type = GR_LATCH_OFFSET_INVALID when some error happens,
    // so leave the stack_top-- on the second step
    session->latch_stack.latch_offset_stack[session->latch_stack.stack_top - 1].type = GR_LATCH_OFFSET_INVALID;
    session->latch_stack.stack_top--;
    session->latch_stack.op = LATCH_SHARED_OP_UNLATCH_END;
    return CM_TRUE;
}


static bool32 gr_need_clean_session_latch(gr_session_t *session, uint64 cli_pid, int64 start_time)
{
    if (cli_pid == 0 || !session->is_used || !session->connected || cm_sys_process_alived(cli_pid, start_time)) {
        return CM_FALSE;
    }
    return CM_TRUE;
}

void gr_clean_session_latch(gr_session_t *session, bool32 is_daemon)
{
    if (!session->is_direct) {
        LOG_DEBUG_INF("Clean sid:%u is not direct.", GR_SESSIONID_IN_LOCK(session->id));
        return;
    }
    
    uint64 cli_pid = session->cli_info.cli_pid;
    int64 start_time = session->cli_info.start_time;
    if (is_daemon && !gr_need_clean_session_latch(session, cli_pid, start_time)) {
        LOG_RUN_INF("[CLEAN_LATCH]session id %u, pid %llu, start_time %lld, process name:%s need check next time.",
            session->id, cli_pid, start_time, session->cli_info.process_name);
        return;
    }
    
    LOG_RUN_INF("[CLEAN_LATCH]session id %u, pid %llu, start_time %lld, process name:%s in lock.", session->id, cli_pid,
        start_time, session->cli_info.process_name);
    
    LOG_DEBUG_INF("Clean sid:%u latch_stack op:%u, stack_top:%hu completed (simplified).", GR_SESSIONID_IN_LOCK(session->id),
        session->latch_stack.op, session->latch_stack.stack_top);
    
    session->latch_stack.op = LATCH_SHARED_OP_NONE;
    session->latch_stack.stack_top = GR_MAX_LATCH_STACK_BOTTON;
}

void gr_server_session_lock(gr_session_t *session)
{
    // session->lock to control the concurrency of cleaning session latch thread
    cm_spin_lock(&session->lock, NULL);
    while (!cm_spin_timed_lock(&session->shm_lock, GR_SERVER_SESS_TIMEOUT)) {
        bool32 alived = cm_sys_process_alived(session->cli_info.cli_pid, session->cli_info.start_time);
        if (!alived) {
            // unlock if the client goes offline
            LOG_DEBUG_INF("Process:%s is not alive, pid:%llu, start_time:%lld.", session->cli_info.process_name,
                session->cli_info.cli_pid, session->cli_info.start_time);
            cm_spin_unlock(&session->shm_lock);
            LOG_DEBUG_INF("Succeed to unlock session %u shm lock", session->id);
            continue;
        }
        LOG_DEBUG_INF("Process:%s is alive, pid:%llu, start_time:%lld.", session->cli_info.process_name,
            session->cli_info.cli_pid, session->cli_info.start_time);
        cm_sleep(CM_SLEEP_500_FIXED);
    }
    LOG_DEBUG_INF("Succeed to lock session %u shm lock", session->id);
}

void gr_server_session_unlock(gr_session_t *session)
{
    cm_spin_unlock(&session->shm_lock);
    LOG_DEBUG_INF("Succeed to unlock session %u shm lock", session->id);
    cm_spin_unlock(&session->lock);
    LOG_DEBUG_INF("Succeed to unlock session %u lock", session->id);
}

void cm_spin_lock_init(spinlock_t *lock)
{
    CM_ASSERT(lock != NULL);
    *lock = 0;
}

status_t init_session_hash_mgr(gr_session_t *session)
{
    session_hash_mgr_t *mgr = &session->hash_mgr;
    mgr->hash_count = 0;
    mgr->hash_capacity = MAX_FILE_HASH_COUNT;
    mgr->hash_items = (file_hash_info_t *)malloc(
        sizeof(file_hash_info_t) * mgr->hash_capacity);
    
    if (mgr->hash_items == NULL) {
        return CM_ERROR;
    }
    
    cm_spin_lock_init(&mgr->lock);
    return CM_SUCCESS;
}

// Add or Update Hash Information
status_t update_file_hash(gr_session_t *session, uint32_t file_handle, const uint8_t *new_hash)
{
    session_hash_mgr_t *mgr = &session->hash_mgr;
    errno_t err = 0;

    if (new_hash == NULL) {
        LOG_RUN_ERR("[hash]: invalid param, failed to update file hash.");
    }
    
    cm_spin_lock(&mgr->lock, NULL);
    
    // Search Existing Records
    for (uint32_t i = 0; i < mgr->hash_count; i++) {
        if (SECUREC_LIKELY(mgr->hash_items[i].file_handle == file_handle)) {
            err = memcpy_s(mgr->hash_items[i].prev_hash, SHA256_DIGEST_LENGTH, 
                            mgr->hash_items[i].curr_hash, SHA256_DIGEST_LENGTH);
            if (err != EOK) {
                LOG_RUN_ERR("[hash]: failed to update pre_hash, error code is %d.\n", err);
                return CM_ERROR;
            }
            
            err = memcpy_s(mgr->hash_items[i].curr_hash, SHA256_DIGEST_LENGTH,
                            new_hash, SHA256_DIGEST_LENGTH);
            if (err != EOK) {
                LOG_RUN_ERR("[hash]: failed to update curr_hash, error code is %d.\n", err);
                return CM_ERROR;
            }
            mgr->hash_items[i].last_update_time = cm_current_time();
            cm_spin_unlock(&mgr->lock);
            return CM_SUCCESS;
        }
    }
    
    // Add a new record
    if (mgr->hash_count >= mgr->hash_capacity) {
        cm_spin_unlock(&mgr->lock);
        return CM_ERROR;
    }
    
    file_hash_info_t *new_item = &mgr->hash_items[mgr->hash_count];
    new_item->file_handle = file_handle;
    err = memcpy_s(new_item->curr_hash, SHA256_DIGEST_LENGTH, 
                    new_hash, SHA256_DIGEST_LENGTH);
    if (err != EOK) {
        LOG_RUN_ERR("[hash]: failed to insert hash information, error code is %d.\n", err);
        return CM_ERROR;
    }
    err = memset_s(new_item->prev_hash, SHA256_DIGEST_LENGTH, 0, SHA256_DIGEST_LENGTH);
    if (err != EOK) {
        CM_THROW_ERROR(ERR_SYSTEM_CALL, err);
        return CM_ERROR;
    }
    new_item->last_update_time = cm_current_time();
    mgr->hash_count++;
    
    cm_spin_unlock(&mgr->lock);
    return CM_SUCCESS;
}

status_t get_file_hash(gr_session_t *session, uint32_t file_handle, uint8_t *curr_hash, uint8_t *prev_hash)
{
    session_hash_mgr_t *mgr = &session->hash_mgr;
    status_t status = CM_ERROR;
    errno_t err = 0;

    if (curr_hash == NULL || prev_hash == NULL) {
        LOG_RUN_ERR("[hash]: invalid param, failed to get hash");
        return CM_ERROR;
    }
    
    cm_spin_lock(&mgr->lock, NULL);
    
    for (uint32_t i = 0; i < mgr->hash_count; i++) {
        if (mgr->hash_items[i].file_handle == file_handle) {
            err = memcpy_s(curr_hash, SHA256_DIGEST_LENGTH,
                        mgr->hash_items[i].curr_hash, SHA256_DIGEST_LENGTH);
            if (err != EOK) {
                LOG_RUN_ERR("[hash]: failed to get curr_hash, error code is %d.\n", err);
                return CM_ERROR;
            }
            err = memcpy_s(prev_hash, SHA256_DIGEST_LENGTH,
                        mgr->hash_items[i].prev_hash, SHA256_DIGEST_LENGTH);
            if (err != EOK) {
                LOG_RUN_ERR("[hash]: failed to get prev_hash, error code is %d.\n", err);
                return CM_ERROR;
            }
            status = CM_SUCCESS;
            break;
        }
    }
    
    cm_spin_unlock(&mgr->lock);
    return status;
}

status_t generate_random_sha256(unsigned char *hash)
{
    if (hash == NULL) {
        LOG_RUN_ERR("invalid param, hash is NULL");
        return GR_ERROR;
    }

    if (RAND_bytes(hash, SHA256_DIGEST_LENGTH) != 1) {
        LOG_RUN_ERR("failed to generate sha256");
        return GR_ERROR;
    }

    return GR_SUCCESS;
}

#ifdef __cplusplus
}
#endif