/* -------------------------------------------------------------------------
 *  This file is part of the oGRAC project.
 * Copyright (c) 2024 Huawei Technologies Co.,Ltd.
 *
 * oGRAC 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.
 * -------------------------------------------------------------------------
 *
 * cm_disklock.c
 *
 *
 * IDENTIFICATION
 * src/common/cm_disklock.c
 *
 * -------------------------------------------------------------------------
 */
#include "cm_common_module.h"
#include "cm_disklock.h"
#include <time.h>
#include "cm_file.h"
#include "cm_date.h"
#include "cm_disk.h"

#define CM_LOCK_MAGIC (*((uint64 *)"CM_LOCK"))
#define CM_FILE_LOCK_CNT 11
#define CM_ENV_DISKLOCK_HOME (char *)"OGDB_HOME"

status_t cm_disk_lock_init(cm_disk_lock_t *lock, uint32 inst_id, uint32 uid)
{
    OG_LOG_DEBUG_INF("cm disk lock init, inst_id:%d", inst_id);
    uint32 temp_id = uid % CM_FILE_LOCK_CNT;
    const char *file_dev = "cm_disk";
    char file_name[OG_FILE_NAME_BUFFER_SIZE] = { 0 };
    cm_init_thread_lock(&lock->tlock);
    lock->flock.lock_time = time(NULL);
    lock->flock.magic = CM_LOCK_MAGIC;
    lock->id = uid;
    lock->inst_id = inst_id;
    lock->lh_lock.lock = 0;
    lock->lh_lock.shared_count = 0;
    lock->lh_lock.unused = 0;
    lock->lh_lock.stat = 0;
    lock->lh_lock.sid = 0;

    char *dlock_home = getenv(CM_ENV_DISKLOCK_HOME);
    if (dlock_home == NULL) {
        OG_LOG_RUN_ERR("env $OGDB_HOME not exists.");
        return OG_ERROR;
    }
    errno_t ret = snprintf_s(file_name, OG_FILE_NAME_BUFFER_SIZE, OG_MAX_FILE_NAME_LEN, "%s/cm_disklock/", dlock_home);
    PRTS_RETURN_IFERR(ret);
    if (!cm_dir_exist(file_name)) {
        OG_RETURN_IFERR(cm_create_dir(file_name));
    }
    ret = snprintf_s(file_name, OG_FILE_NAME_BUFFER_SIZE, OG_MAX_FILE_NAME_LEN, "%s/cm_disklock/%s_%u.lock", dlock_home,
                     file_dev, temp_id);
    PRTS_RETURN_IFERR(ret);

    ret = snprintf_s(lock->dev_name, OG_FILE_NAME_BUFFER_SIZE, OG_MAX_FILE_NAME_LEN, "%s_%u.lock", file_dev, temp_id);
    PRTS_RETURN_IFERR(ret);

    OG_RETURN_IFERR(cm_open_file(file_name, O_CREAT | O_RDWR | O_BINARY | O_CLOEXEC, &lock->fd));

    return OG_SUCCESS;
}

void cm_destory_lock(cm_disk_lock_t *lock)
{
    if (cm_record_disk_unlock(lock) != OG_SUCCESS) {
        OG_LOG_RUN_ERR("cm_destory_lock failed");
    }
    cm_close_file(lock->fd);
}

status_t cm_disk_save_lockinfo(cm_disk_lock_t *lock)
{
    if (cm_seek_file(lock->fd, 0, SEEK_SET) != 0) {
        (void)cm_unlock_record_fd(lock->fd, lock->id);
        OG_LOG_RUN_ERR("file lock failed:%s,%d:%s", lock->dev_name, errno, strerror(errno));
        return OG_ERROR;
    }

    if (cm_write_file(lock->fd, &lock->flock, sizeof(cm_flock_t)) != OG_SUCCESS) {
        (void)cm_unlock_record_fd(lock->fd, lock->id);
        OG_LOG_RUN_ERR("file lock failed:%s,%d:%s", lock->dev_name, errno, strerror(errno));
        return OG_ERROR;
    }
    return OG_SUCCESS;
}

status_t cm_disk_mutex_try_lock(cm_disk_lock_t *lock)
{
    if (lock->lh_lock.stat == LATCH_STATUS_X) {
        return OG_TIMEDOUT;
    }
    status_t ret;
    cm_latch_x(&lock->lh_lock, 0, NULL);

    if (cm_lockw_record_fd(lock->fd, lock->id) != OG_SUCCESS) {
        if (errno == EAGAIN) {
            cm_unlatch(&lock->lh_lock, NULL);
            return OG_TIMEDOUT;
        } else {
            cm_unlatch(&lock->lh_lock, NULL);
            OG_LOG_RUN_ERR("mutex record lock failed:%s,%d:%s", lock->dev_name, errno, strerror(errno));
            return OG_ERROR;
        }
    }
    lock->flock.lock_time = time(NULL);

    ret = cm_disk_save_lockinfo(lock);
    if (ret != OG_SUCCESS) {
        cm_unlatch(&lock->lh_lock, NULL);
        OG_LOG_DEBUG_ERR("try lock file failed:%s", lock->dev_name);
        return OG_ERROR;
    }
    return OG_SUCCESS;
}

status_t cm_disk_latchs_try_lock(cm_disk_lock_t *lock)
{
    status_t ret;
    cm_latch_s(&lock->lh_lock, 0, OG_FALSE, NULL);
    if (cm_lockr_record_fd(lock->fd, lock->id) != OG_SUCCESS) {
        if (errno == EAGAIN) {
            cm_unlatch(&lock->lh_lock, NULL);
            return OG_TIMEDOUT;
        } else {
            cm_unlatch(&lock->lh_lock, NULL);
            OG_LOG_RUN_ERR("read record lock failed:%s,%d:%s", lock->dev_name, errno, strerror(errno));
            return OG_ERROR;
        }
    }
    lock->flock.lock_time = time(NULL);

    ret = cm_disk_save_lockinfo(lock);
    if (ret != OG_SUCCESS) {
        cm_unlatch(&lock->lh_lock, NULL);
        OG_LOG_DEBUG_ERR("try lock file failed:%s", lock->dev_name);
        return OG_ERROR;
    }
    return OG_SUCCESS;
}

status_t cm_disk_file_lock(cm_disk_lock_t *lock, uint8 lock_type)
{
    status_t ret;
    while (1) {
        switch (lock_type) {
            case DISK_LOCK_MUTEX:
                ret = cm_disk_mutex_try_lock(lock);
                break;
            case DISK_LOCK_LATCHX:
                ret = cm_disk_mutex_try_lock(lock);
                break;
            case DISK_LOCK_LATCHS:
                ret = cm_disk_latchs_try_lock(lock);
                break;
            default:
                ret = OG_ERROR;
                break;
        }
        if (ret == OG_TIMEDOUT) {
            cm_sleep(CM_LOCK_TRY_INTERVAL);
        } else {
            return ret;
        }
    }
    return OG_ERROR;
}

status_t cm_disk_timed_file_lock(cm_disk_lock_t *lock, uint32 timeout_ms, uint8 lock_type)
{
    status_t ret;
    date_t start_time;
    date_t end_time;

    if (timeout_ms == 0) {
        ret = cm_disk_file_lock(lock, lock_type);
        return ret;
    }
    start_time = cm_monotonic_now();
    while (1) {
        switch (lock_type) {
            case DISK_LOCK_MUTEX:
                ret = cm_disk_mutex_try_lock(lock);
                break;
            case DISK_LOCK_LATCHX:
                ret = cm_disk_mutex_try_lock(lock);
                break;
            case DISK_LOCK_LATCHS:
                ret = cm_disk_latchs_try_lock(lock);
                break;
            default:
                ret = OG_ERROR;
                break;
        }
        if (ret == OG_SUCCESS) {
            return ret;
        } else if (ret == OG_TIMEDOUT) {
            end_time = cm_monotonic_now();
            if (end_time > start_time + timeout_ms * MICROSECS_PER_MILLISEC) {
                OG_LOG_DEBUG_ERR("cm_disk_timed_file_lock timeout:%s.", lock->dev_name);
                return OG_ERROR;
            }
            cm_sleep(CM_LOCK_TRY_INTERVAL);
        } else {
            OG_LOG_DEBUG_ERR("cm_disk_timed_file_lock failed:%s.", lock->dev_name);
            return ret;
        }
    }

    return OG_ERROR;
}

status_t cm_disk_mutex_lock(cm_disk_lock_t *lock, uint32 timeout_ms)
{
    status_t ret = cm_disk_timed_file_lock(lock, timeout_ms, DISK_LOCK_MUTEX);
    if (ret != OG_SUCCESS) {
        OG_LOG_DEBUG_ERR("cm_disk_mutex_lock failed");
        return ret;
    }
    return ret;
}

status_t cm_disk_latchx_lock(cm_disk_lock_t *lock, uint32 timeout_ms)
{
    status_t ret = cm_disk_timed_file_lock(lock, timeout_ms, DISK_LOCK_LATCHX);
    if (ret != OG_SUCCESS) {
        OG_LOG_DEBUG_ERR("cm_disk_latchx_lock failed");
        return ret;
    }
    return ret;
}

status_t cm_disk_latchs_lock(cm_disk_lock_t *lock, uint32 timeout_ms)
{
    status_t ret = cm_disk_timed_file_lock(lock, timeout_ms, DISK_LOCK_LATCHS);
    if (ret != OG_SUCCESS) {
        OG_LOG_DEBUG_ERR("cm_disk_latchs_lock failed");
        return ret;
    }
    return ret;
}

status_t cm_record_disk_unlock(cm_disk_lock_t *lock)
{
    status_t ret;
    ret = cm_unlock_record_fd(lock->fd, lock->id);
    if (ret != OG_SUCCESS) {
        OG_LOG_RUN_ERR("cm_record_disk_unlock failed:%s,%d:%s", lock->dev_name, errno, strerror(errno));
    }
    cm_unlatch(&lock->lh_lock, NULL);
    return ret;
}

status_t cm_disk_mutex_unlock(cm_disk_lock_t *lock)
{
    return cm_record_disk_unlock(lock);
}

status_t cm_disk_latch_unlock(cm_disk_lock_t *lock)
{
    return cm_record_disk_unlock(lock);
}