/**
 * @file    trace_debug.c
 * @brief   调试跟踪功能实现
 * @details 提供标准输出重定向与恢复等调试辅助接口，供调试/排障时输出
 *          跟踪信息使用
 */
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>

/* 伪终端设备路径缓冲长度 */
#define TRACE_DBG_PTS_PATH_LEN 32

/* 公共错误码 */
#define TRACE_DBG_RET_OK                 0  /**< 成功 */
#define TRACE_DBG_ERR_CODE_REDIRECT_FAIL 1  /**< 标准输出重定向失败 */

/* 重定向前标准输出文件描述符备份，-1 表示未备份 */
int g_trace_dbg_stdout_bak_fd = -1;

/* 重定向前 stdout 是否为全缓冲（stdout 指向非终端时）：为 1 时重定向期间
 * 临时切换为行缓冲保证带换行输出实时可见，恢复时还原为全缓冲 */
static int g_trace_dbg_stdout_was_full_buffered = 0;

/**
 * @brief 将标准输出重定向到伪终端设备
 *
 * @details 根据 pts 编号构造 /dev/pts/pts_no 设备路径，打开该设备后将
 *          标准输出文件描述符 dup2 到该设备；重定向前先刷新 stdout 缓冲
 *          并备份标准输出文件描述符，供 trace_dbg_restore_stdout 恢复；
 *          缓冲模式自适应：stdout 原为终端（行缓冲）时不修改缓冲模式；
 *          原为非终端（全缓冲，如 systemd/脚本启动）时临时切换为行缓冲，
 *          保证带换行输出实时写入 pts，恢复时由 trace_dbg_restore_stdout
 *          还原为全缓冲；
 *          子进程继承标准输出文件描述符，因此重定向后创建的子进程
 *          输出同样写入该设备；
 *          设备以 O_NONBLOCK 方式打开：pts 对端未读取或已关闭导致
 *          输出缓冲满时，write 立即失败返回而非阻塞调用线程
 *
 * @param pts_no [in] 伪终端编号，对应 /dev/pts/pts_no 设备
 *
 * @return TRACE_DBG_RET_OK 重定向成功；TRACE_DBG_ERR_CODE_REDIRECT_FAIL
 *         打开设备失败（设备不存在或无权限）
 */
int32_t trace_dbg_redirect_stdout(uint32_t pts_no)
{
    char pts_path[TRACE_DBG_PTS_PATH_LEN] = {0};
    int fd;

    /* 重定向前刷新 stdout 缓冲，避免缓冲数据写入新设备 */
    fflush(stdout);

    /* 记录重定向前缓冲模式：stdio 首次使用时由 isatty 决定缓冲模式且
     * dup2 不触发重新判断，故非终端 stdout 为重定向期间的全缓冲 */
    g_trace_dbg_stdout_was_full_buffered = !isatty(STDOUT_FILENO);

    /* 备份重定向前标准输出文件描述符，供恢复接口使用 */
    if (g_trace_dbg_stdout_bak_fd == -1) {
        g_trace_dbg_stdout_bak_fd = dup(STDOUT_FILENO);
        if (g_trace_dbg_stdout_bak_fd == -1) {
            /* dup 失败（如 fd 耗尽）时备份不可用，拒绝重定向避免 stdout 无法恢复 */
            return TRACE_DBG_ERR_CODE_REDIRECT_FAIL;
        }
    }

    snprintf(pts_path, sizeof(pts_path), "/dev/pts/%u", pts_no);

    /* O_NOCTTY 避免将该伪终端设为进程控制终端；
     * O_NONBLOCK 使 pts 对端（master）未读或已关闭、输出缓冲满时
     * write 立即返回 EAGAIN 而非阻塞，避免 printf 写入阻塞拖死整个进程 */
    fd = open(pts_path, O_WRONLY | O_NOCTTY | O_NONBLOCK);
    if (fd == -1) {
        return TRACE_DBG_ERR_CODE_REDIRECT_FAIL;
    }

    if (dup2(fd, STDOUT_FILENO) == -1) {
        close(fd);
        return TRACE_DBG_ERR_CODE_REDIRECT_FAIL;
    }

    close(fd);

    /* 原 stdout 为非终端（全缓冲）时临时切换为行缓冲，使带换行输出
     * 实时写入 pts；终端场景保持原缓冲模式不变 */
    if (g_trace_dbg_stdout_was_full_buffered) {
        setvbuf(stdout, NULL, _IOLBF, 0);
    }

    return TRACE_DBG_RET_OK;
}

/**
 * @brief 恢复标准输出
 *
 * @details 将重定向前备份的标准输出文件描述符 dup2 还原到 1 号
 *          文件描述符，并关闭备份描述符；若重定向时曾临时切换缓冲模式
 *          （原 stdout 为全缓冲）则还原为全缓冲，否则不修改缓冲模式；
 *          未重定向过时直接返回成功
 *
 * @return TRACE_DBG_RET_OK 恢复成功；TRACE_DBG_ERR_CODE_REDIRECT_FAIL
 *         dup2 还原失败
 */
int32_t trace_dbg_restore_stdout(void)
{
    if (g_trace_dbg_stdout_bak_fd == -1) {
        g_trace_dbg_stdout_was_full_buffered = 0;
        return TRACE_DBG_RET_OK;
    }

    /* 恢复前刷新 stdout 缓冲，确保重定向期间输出完整写出 */
    fflush(stdout);

    if (dup2(g_trace_dbg_stdout_bak_fd, STDOUT_FILENO) == -1) {
        return TRACE_DBG_ERR_CODE_REDIRECT_FAIL;
    }
    close(g_trace_dbg_stdout_bak_fd);
    g_trace_dbg_stdout_bak_fd = -1;

    /* 重定向前为全缓冲时还原缓冲模式，恢复 stdout 原有状态 */
    if (g_trace_dbg_stdout_was_full_buffered) {
        g_trace_dbg_stdout_was_full_buffered = 0;
        setvbuf(stdout, NULL, _IOFBF, 0);
    }

    return TRACE_DBG_RET_OK;
}

//空函数，在text段，用于占位
int trace_dbg_nop_stub(void)
{
    __asm__ __volatile__ (
        "nop\n"
        "nop\n"
        "nop\n"
        "nop\n"
        "nop\n"
        "nop\n"
        "nop\n"
        "nop\n"
        :::"memory"
    );
    return 0;
}
