        -:    0:Source:trace-provider.c
        -:    0:Graph:/__w/c-modernization-kit/c-modernization-kit/test/util/src/libutilTest/trace_providerTest/obj/trace-provider.gcno
        -:    0:Data:/__w/c-modernization-kit/c-modernization-kit/test/util/src/libutilTest/trace_providerTest/obj/trace-provider.gcda
        -:    0:Runs:1
        -:    0:Programs:1
        -:    1:#include <trace-util.h>
        -:    2:#include <trace-file-util.h>
        -:    3:#include <stdlib.h>
        -:    4:#include <string.h>
        -:    5:#include <stdarg.h>
        -:    6:#include <stdio.h>
        -:    7:#include <inttypes.h>
        -:    8:
        -:    9:/* ===== Windows: TraceLogging プロバイダ定義 ===== */
        -:   10:
        -:   11:#ifdef _WIN32
        -:   12:
        -:   13:#include <windows.h>
        -:   14:#include <TraceLoggingProvider.h>
        -:   15:
        -:   16:/* trace-util が管理するデフォルトプロバイダ定義 */
        -:   17:TRACELOGGING_DEFINE_PROVIDER(
        -:   18:    s_trace_provider,
        -:   19:    TRACE_DEFAULT_PROVIDER_NAME,
        -:   20:    TRACE_DEFAULT_PROVIDER_GUID);
        -:   21:
        -:   22:/** デフォルトプロバイダの参照カウント。 */
        -:   23:static volatile LONG s_trace_ref = 0;
        -:   24:
        -:   25:/** デフォルトプロバイダの共有 ETW ハンドル。 */
        -:   26:static etw_provider_t *s_etw_handle = NULL;
        -:   27:
        -:   28:#else /* !_WIN32 */
        -:   29:#include <time.h>
        -:   30:#include <pthread.h>
        -:   31:#endif /* _WIN32 */
        -:   32:
        -:   33:/**
        -:   34: *  @brief  トレースプロバイダハンドル構造体 (内部定義)。
        -:   35: */
        -:   36:struct trace_provider
        -:   37:{
        -:   38:    /** アプリケーション管理識別番号 (診断用)。0 = 識別番号なし。 */
        -:   39:    int64_t identifier;
        -:   40:
        -:   41:#ifdef _WIN32
        -:   42:    /** サービス名 (ETW "Trace" イベントの "Service" フィールド)。 */
        -:   43:    char *service_name;
        -:   44:#else /* !_WIN32 */
        -:   45:    /** syslog プロバイダハンドル (Linux)。 */
        -:   46:    syslog_provider_t *syslog_handle;
        -:   47:#endif /* _WIN32 */
        -:   48:
        -:   49:    /** OS トレース (ETW/syslog) のスレッショルドレベル。デフォルト: TRACE_LV_INFO。 */
        -:   50:    enum trace_level os_level;
        -:   51:
        -:   52:    /** ファイルトレースのスレッショルドレベル。デフォルト: TRACE_LV_ERROR。 */
        -:   53:    enum trace_level file_level;
        -:   54:
        -:   55:    /** ファイルトレースプロバイダハンドル。NULL = ファイルトレース無効。 */
        -:   56:    trace_file_provider_t *file_handle;
        -:   57:
        -:   58:    /** stderr トレースのスレッショルドレベル。デフォルト: TRACE_LV_NONE。 */
        -:   59:    enum trace_level stderr_level;
        -:   60:
        -:   61:    /** 実行状態フラグ (0=停止中, 1=実行中)。 */
        -:   62:    volatile int running;
        -:   63:
        -:   64:#ifdef _WIN32
        -:   65:    /** 読み書きロック。write 系は共有ロック、設定変更・stop・dispose は排他ロック。
        -:   66:     *  SRWLOCK は初期化関数のみ必要で破棄関数は不要。 */
        -:   67:    SRWLOCK config_rwlock;
        -:   68:#else /* !_WIN32 */
        -:   69:    /** 読み書きロック。write 系は共有ロック、設定変更・stop・dispose は排他ロック。 */
        -:   70:    pthread_rwlock_t config_rwlock;
        -:   71:    /** config_rwlock が初期化済みかどうかのフラグ。 */
        -:   72:    int              config_rwlock_initialized;
        -:   73:    /** パディング (構造体サイズを 8 バイト境界に揃える)。 */
        -:   74:    int              _pad_end;
        -:   75:#endif /* _WIN32 */
        -:   76:};
        -:   77:
        -:   78:#ifdef _WIN32
        -:   79:
        -:   80:/**
        -:   81: *  @brief  enum trace_level を ETW Level (1-5) に変換する。
        -:   82: */
        -:   83:static int to_etw_level(enum trace_level lv)
        -:   84:{
        -:   85:    switch (lv)
        -:   86:    {
        -:   87:    case TRACE_LV_CRITICAL: return 1;
        -:   88:    case TRACE_LV_ERROR:    return 2;
        -:   89:    case TRACE_LV_WARNING:  return 3;
        -:   90:    case TRACE_LV_INFO:     return 4;
        -:   91:    default:              return 5;
        -:   92:    }
        -:   93:}
        -:   94:
        -:   95:#else /* !_WIN32 */
        -:   96:
        -:   97:#include <syslog.h>
        -:   98:
        -:   99:/**
        -:  100: *  @brief  enum trace_level を syslog severity に変換する。
        -:  101: */
        1:  102:static int to_syslog_level(enum trace_level lv)
        -:  103:{
        1:  104:    switch (lv)
        -:  105:    {
    #####:  106:    case TRACE_LV_CRITICAL: return LOG_CRIT;
    #####:  107:    case TRACE_LV_ERROR:    return LOG_ERR;
    #####:  108:    case TRACE_LV_WARNING:  return LOG_WARNING;
        1:  109:    case TRACE_LV_INFO:     return LOG_INFO;
    #####:  110:    case TRACE_LV_VERBOSE:  return LOG_DEBUG;
    #####:  111:    case TRACE_LV_NONE:     /* fall through */
    #####:  112:    default:                return LOG_DEBUG;
        -:  113:    }
        -:  114:}
        -:  115:
        -:  116:#endif /* _WIN32 */
        -:  117:
        -:  118:/** プロセス名取得失敗時のフォールバック名。 */
        -:  119:#define FALLBACK_NAME "unknown"
        -:  120:
        -:  121:#ifdef _WIN32
        -:  122:
        -:  123:/**
        -:  124: *  @brief  自プロセスの実行ファイル名 (ベースネーム) を取得する。
        -:  125: *  @param[out]  buf       パス格納バッファ。
        -:  126: *  @param[in]   buf_size  バッファサイズ。
        -:  127: *  @return      ベースネームへのポインタ (buf 内または FALLBACK_NAME)。
        -:  128: */
        -:  129:static const char *get_process_basename(char *buf, size_t buf_size)
        -:  130:{
        -:  131:    DWORD len;
        -:  132:    const char *sep;
        -:  133:
        -:  134:    len = GetModuleFileNameA(NULL, buf, (DWORD)buf_size);
        -:  135:    if (len == 0 || len >= (DWORD)buf_size)
        -:  136:    {
        -:  137:        return FALLBACK_NAME;
        -:  138:    }
        -:  139:
        -:  140:    sep = strrchr(buf, '\\');
        -:  141:    if (sep == NULL)
        -:  142:    {
        -:  143:        sep = strrchr(buf, '/');
        -:  144:    }
        -:  145:    return sep ? sep + 1 : buf;
        -:  146:}
        -:  147:#else /* !_WIN32 */
        -:  148:#include <unistd.h>
        -:  149:
        -:  150:/**
        -:  151: *  @brief  自プロセスの実行ファイル名 (ベースネーム) を取得する。
        -:  152: *  @param[out]  buf       パス格納バッファ。
        -:  153: *  @param[in]   buf_size  バッファサイズ。
        -:  154: *  @return      ベースネームへのポインタ (buf 内または FALLBACK_NAME)。
        -:  155: */
        2:  156:static const char *get_process_basename(char *buf, size_t buf_size)
        -:  157:{
        -:  158:    ssize_t len;
        -:  159:    const char *slash;
        -:  160:
        2:  161:    len = readlink("/proc/self/exe", buf, buf_size - 1);
        2:  162:    if (len <= 0)
        -:  163:    {
    #####:  164:        return FALLBACK_NAME;
        -:  165:    }
        2:  166:    buf[len] = '\0';
        -:  167:
        2:  168:    slash = strrchr(buf, '/');
       2*:  169:    return slash ? slash + 1 : buf;
        -:  170:}
        -:  171:#endif /* _WIN32 */
        -:  172:
        -:  173:/**
        -:  174: *  @brief  設定・状態変更用の排他ロックを取得する。
        -:  175: *          trace_start / trace_stop / trace_modify_* / trace_dispose で使用する。
        -:  176: */
        3:  177:static void config_lock_exclusive(trace_provider_t *handle)
        -:  178:{
        -:  179:#ifdef _WIN32
        -:  180:    AcquireSRWLockExclusive(&handle->config_rwlock);
        -:  181:#else /* !_WIN32 */
        3:  182:    pthread_rwlock_wrlock(&handle->config_rwlock);
        -:  183:#endif /* _WIN32 */
        3:  184:}
        -:  185:
        -:  186:/**
        -:  187: *  @brief  排他ロックを解放する。
        -:  188: */
        3:  189:static void config_unlock_exclusive(trace_provider_t *handle)
        -:  190:{
        -:  191:#ifdef _WIN32
        -:  192:    ReleaseSRWLockExclusive(&handle->config_rwlock);
        -:  193:#else /* !_WIN32 */
        3:  194:    pthread_rwlock_unlock(&handle->config_rwlock);
        -:  195:#endif /* _WIN32 */
        3:  196:}
        -:  197:
        -:  198:/** 共有ロック取得のタイムアウト (ミリ秒)。 */
        -:  199:#define LOCK_TIMEOUT_MS 100
        -:  200:
        -:  201:/**
        -:  202: *  @brief  書き込み系 API 用の共有ロックをタイムアウト付きで取得する。
        -:  203: *          複数スレッドが同時に取得できる。
        -:  204: *          排他ロック保持中はブロックするが、LOCK_TIMEOUT_MS 経過で諦める。
        -:  205: *  @return 取得成功: 0 / タイムアウト: -1。
        -:  206: */
        1:  207:static int config_lock_shared_timed(trace_provider_t *handle)
        -:  208:{
        -:  209:#ifdef _WIN32
        -:  210:    /* SRWLOCK には timed 取得 API がないため spin + TryAcquireSRWLockShared で代替する */
        -:  211:    DWORD deadline = GetTickCount() + (DWORD)LOCK_TIMEOUT_MS;
        -:  212:    while (!TryAcquireSRWLockShared(&handle->config_rwlock))
        -:  213:    {
        -:  214:        if ((LONG)(GetTickCount() - deadline) >= 0)
        -:  215:        {
        -:  216:            return -1;
        -:  217:        }
        -:  218:        SwitchToThread();
        -:  219:    }
        -:  220:    return 0;
        -:  221:#else /* !_WIN32 */
        -:  222:    struct timespec abs_timeout;
        1:  223:    clock_gettime(CLOCK_REALTIME, &abs_timeout);
        1:  224:    abs_timeout.tv_nsec += (long)LOCK_TIMEOUT_MS * 1000000L;
        1:  225:    if (abs_timeout.tv_nsec >= 1000000000L)
        -:  226:    {
    #####:  227:        abs_timeout.tv_sec  += 1;
    #####:  228:        abs_timeout.tv_nsec -= 1000000000L;
        -:  229:    }
       1*:  230:    return (pthread_rwlock_timedrdlock(&handle->config_rwlock, &abs_timeout) == 0) ? 0 : -1;
        -:  231:#endif /* _WIN32 */
        -:  232:}
        -:  233:
        -:  234:/**
        -:  235: *  @brief  共有ロックを解放する。
        -:  236: */
        1:  237:static void config_unlock_shared(trace_provider_t *handle)
        -:  238:{
        -:  239:#ifdef _WIN32
        -:  240:    ReleaseSRWLockShared(&handle->config_rwlock);
        -:  241:#else /* !_WIN32 */
        1:  242:    pthread_rwlock_unlock(&handle->config_rwlock);
        -:  243:#endif /* _WIN32 */
        1:  244:}
        -:  245:
        -:  246:/**
        -:  247: *  @brief  name と identifier から有効識別名 (effective name) を生成する。
        -:  248: *  @details  name == NULL の場合はプロセスのベースネームを使用する。\n
        -:  249: *            identifier == 0 の場合は name をそのままコピーして返す。\n
        -:  250: *            identifier > 0 の場合は "<name>-<identifier>" を生成して返す。\n
        -:  251: *            戻り値はヒープ上に確保されるため、呼び出し元が free する必要がある。
        -:  252: *  @return  ヒープ上に確保された有効識別名。失敗時 NULL。
        -:  253: */
        1:  254:static char *build_effective_name(const char *name, int64_t identifier)
        -:  255:{
        -:  256:    char path_buf[256];
        -:  257:    const char *base;
        -:  258:    char *result;
        -:  259:
       1*:  260:    base = (name != NULL) ? name : get_process_basename(path_buf, sizeof(path_buf));
        -:  261:
        1:  262:    if (identifier == 0)
        -:  263:    {
        -:  264:#ifdef _WIN32
        -:  265:        return _strdup(base);
        -:  266:#else
        1:  267:        return strdup(base);
        -:  268:#endif
        -:  269:    }
        -:  270:
        -:  271:    {
    #####:  272:        int id_len = snprintf(NULL, 0, "%" PRId64, identifier);
    #####:  273:        size_t base_len = strlen(base);
        -:  274:        /* "<base>-<identifier>\0" */
    #####:  275:        size_t total = base_len + 1 /* '-' */ + (size_t)id_len + 1;
        -:  276:
    #####:  277:        result = (char *)malloc(total);
    #####:  278:        if (result == NULL)
        -:  279:        {
    #####:  280:            return NULL;
        -:  281:        }
    #####:  282:        snprintf(result, total, "%s-%" PRId64, base, identifier);
    #####:  283:        return result;
        -:  284:    }
        -:  285:}
        -:  286:
        -:  287:trace_provider_t *TRACE_UTIL_API
        1:  288:    trace_init(void)
        -:  289:{
        -:  290:    trace_provider_t *handle;
        -:  291:    char path_buf[256];
        -:  292:    const char *effective_name;
        -:  293:
        1:  294:    effective_name = get_process_basename(path_buf, sizeof(path_buf));
        -:  295:
        -:  296:#ifdef _WIN32
        -:  297:    {
        -:  298:        char *svc;
        -:  299:
        -:  300:        svc = _strdup(effective_name);
        -:  301:        if (svc == NULL)
        -:  302:        {
        -:  303:            return NULL;
        -:  304:        }
        -:  305:
        -:  306:        handle = (trace_provider_t *)malloc(sizeof(trace_provider_t));
        -:  307:        if (handle == NULL)
        -:  308:        {
        -:  309:            free(svc);
        -:  310:            return NULL;
        -:  311:        }
        -:  312:
        -:  313:        handle->identifier    = 0;
        -:  314:        handle->service_name  = svc;
        -:  315:        handle->os_level      = TRACE_DEFAULT_OS_LEVEL;
        -:  316:        handle->file_level    = TRACE_DEFAULT_FILE_LEVEL;
        -:  317:        handle->file_handle   = NULL;
        -:  318:        handle->stderr_level  = TRACE_DEFAULT_STDERR_LEVEL;
        -:  319:        handle->running       = 0;
        -:  320:
        -:  321:        InitializeSRWLock(&handle->config_rwlock);
        -:  322:
        -:  323:        if (InterlockedIncrement(&s_trace_ref) == 1)
        -:  324:        {
        -:  325:            s_etw_handle = etw_provider_init(s_trace_provider);
        -:  326:            if (s_etw_handle == NULL)
        -:  327:            {
        -:  328:                InterlockedDecrement(&s_trace_ref);
        -:  329:                free(handle->service_name);
        -:  330:                free(handle);
        -:  331:                return NULL;
        -:  332:            }
        -:  333:        }
        -:  334:    }
        -:  335:#else /* !_WIN32 */
        -:  336:    {
        -:  337:        syslog_provider_t *sp;
        -:  338:
        1:  339:        sp = syslog_provider_init(effective_name, LOG_USER);
        1:  340:        if (sp == NULL)
        -:  341:        {
    #####:  342:            return NULL;
        -:  343:        }
        -:  344:
        1:  345:        handle = (trace_provider_t *)malloc(sizeof(trace_provider_t));
        1:  346:        if (handle == NULL)
        -:  347:        {
    #####:  348:            syslog_provider_dispose(sp);
    #####:  349:            return NULL;
        -:  350:        }
        -:  351:
        1:  352:        handle->identifier    = 0;
        1:  353:        handle->syslog_handle = sp;
        1:  354:        handle->os_level      = TRACE_DEFAULT_OS_LEVEL;
        1:  355:        handle->file_level    = TRACE_DEFAULT_FILE_LEVEL;
        1:  356:        handle->file_handle   = NULL;
        1:  357:        handle->stderr_level  = TRACE_DEFAULT_STDERR_LEVEL;
        1:  358:        handle->running       = 0;
        -:  359:
        1:  360:        if (pthread_rwlock_init(&handle->config_rwlock, NULL) != 0)
        -:  361:        {
    #####:  362:            syslog_provider_dispose(sp);
    #####:  363:            free(handle);
    #####:  364:            return NULL;
        -:  365:        }
        1:  366:        handle->config_rwlock_initialized = 1;
        -:  367:    }
        -:  368:#endif /* _WIN32 */
        -:  369:
        1:  370:    return handle;
        -:  371:}
        -:  372:
        -:  373:int TRACE_UTIL_API
        1:  374:    trace_start(trace_provider_t *handle)
        -:  375:{
        1:  376:    if (handle == NULL)
        -:  377:    {
    #####:  378:        return -1;
        -:  379:    }
        -:  380:
        1:  381:    config_lock_exclusive(handle);
        -:  382:
        1:  383:    if (handle->running)
        -:  384:    {
    #####:  385:        config_unlock_exclusive(handle);
    #####:  386:        return 0;
        -:  387:    }
        -:  388:
        1:  389:    handle->running = 1;
        1:  390:    config_unlock_exclusive(handle);
        1:  391:    return 0;
        -:  392:}
        -:  393:
        -:  394:int TRACE_UTIL_API
        1:  395:    trace_stop(trace_provider_t *handle)
        -:  396:{
        1:  397:    if (handle == NULL)
        -:  398:    {
    #####:  399:        return -1;
        -:  400:    }
        -:  401:
        1:  402:    config_lock_exclusive(handle);
        -:  403:
        1:  404:    if (!handle->running)
        -:  405:    {
    #####:  406:        config_unlock_exclusive(handle);
    #####:  407:        return 0;
        -:  408:    }
        -:  409:
        1:  410:    handle->running = 0;
        1:  411:    config_unlock_exclusive(handle);
        1:  412:    return 0;
        -:  413:}
        -:  414:
        -:  415:/** 切り詰め後の本文最大バイト数 (null 終端を除く)。 */
        -:  416:#define MAX_BODY (TRACE_MESSAGE_MAX_BYTES - 1)
        -:  417:
        -:  418:/**
        -:  419: *  @brief  UTF-8 安全な切り詰め位置を返す。
        -:  420: *  @details  pos バイト目以前で、UTF-8 マルチバイトシーケンスの途中を
        -:  421: *            避けた安全な切断位置を返す。
        -:  422: */
    #####:  423:static size_t utf8_safe_truncate(const char *s, size_t pos)
        -:  424:{
        -:  425:    /* pos が文字列長以下なら調整不要 */
    #####:  426:    while (pos > 0 && ((unsigned char)s[pos] & 0xC0) == 0x80)
        -:  427:    {
        -:  428:        /* 継続バイト (10xxxxxx) を跨がないよう先頭バイトまで戻る */
    #####:  429:        pos--;
        -:  430:    }
    #####:  431:    return pos;
        -:  432:}
        -:  433:
        -:  434:/**
        -:  435: *  @brief  下層プロバイダに文字列を書き込む (内部ヘルパー)。
        -:  436: */
        1:  437:static int write_to_provider(trace_provider_t *handle, enum trace_level level, const char *msg)
        -:  438:{
        -:  439:#ifdef _WIN32
        -:  440:    return etw_provider_write(s_etw_handle, to_etw_level(level),
        -:  441:                              handle->service_name, msg);
        -:  442:#else /* !_WIN32 */
        1:  443:    return syslog_provider_write(handle->syslog_handle, to_syslog_level(level), msg);
        -:  444:#endif /* _WIN32 */
        -:  445:}
        -:  446:
        -:  447:/**
        -:  448: *  @brief  メッセージレベルがスレッショルド以内かを判定する。
        -:  449: *  @return 出力すべき場合は 1、そうでなければ 0。
        -:  450: */
        2:  451:static int should_output(enum trace_level msg_level, enum trace_level threshold)
        -:  452:{
        2:  453:    if (threshold == TRACE_LV_NONE)
        -:  454:    {
        1:  455:        return 0;
        -:  456:    }
        1:  457:    return (int)msg_level <= (int)threshold;
        -:  458:}
        -:  459:
        -:  460:/** stderr タイムスタンプバッファサイズ ("YYYY-MM-DD HH:MM:SS.mmm\0" = 24)。 */
        -:  461:#define STDERR_TS_BUF_SIZE 24
        -:  462:
        -:  463:/**
        -:  464: *  @brief  stderr にタイムスタンプ + レベル文字 + メッセージを 1 行書き込む。
        -:  465: *  @details  ファイルトレースと同一フォーマット: "YYYY-MM-DD HH:MM:SS.mmm L msg\n"\n
        -:  466: *            タイムスタンプは UTC。fprintf(stderr) は MT-Safe のため追加ロック不要。
        -:  467: */
    #####:  468:static void write_stderr_entry(enum trace_level level, const char *msg)
        -:  469:{
        -:  470:    char ts[STDERR_TS_BUF_SIZE];
        -:  471:    static const char lc_table[] = {'C', 'E', 'W', 'I', 'V'};
        -:  472:    char lc;
        -:  473:
        -:  474:#ifdef _WIN32
        -:  475:    SYSTEMTIME st;
        -:  476:    GetSystemTime(&st);
        -:  477:    snprintf(ts, sizeof(ts),
        -:  478:             "%04d-%02d-%02d %02d:%02d:%02d.%03d",
        -:  479:             (int)st.wYear,  (int)st.wMonth,  (int)st.wDay,
        -:  480:             (int)st.wHour,  (int)st.wMinute, (int)st.wSecond,
        -:  481:             (int)st.wMilliseconds);
        -:  482:#else /* !_WIN32 */
        -:  483:    struct timespec tsp;
        -:  484:    struct tm       tm_val;
    #####:  485:    clock_gettime(CLOCK_REALTIME, &tsp);
    #####:  486:    gmtime_r(&tsp.tv_sec, &tm_val);
        -:  487:    /* -Wformat-truncation の抑制: gmtime_r() が返す tm 構造体の各フィールドは POSIX で
        -:  488:     * 範囲が保証されており (tm_mon: 0-11, tm_mday: 1-31 等)、出力は常に 23 文字以内に
        -:  489:     * 収まる。GCC は int 型の理論上の最大範囲 [-2147483648, 2147483647] を使って静的
        -:  490:     * 検証するため false positive が発生する。pragma はその誤報を局所的に抑制する。 */
        -:  491:#pragma GCC diagnostic push
        -:  492:#pragma GCC diagnostic ignored "-Wformat-truncation"
    #####:  493:    snprintf(ts, sizeof(ts),
        -:  494:             "%04d-%02d-%02d %02d:%02d:%02d.%03d",
    #####:  495:             tm_val.tm_year + 1900, tm_val.tm_mon + 1, tm_val.tm_mday,
        -:  496:             tm_val.tm_hour,        tm_val.tm_min,      tm_val.tm_sec,
    #####:  497:             (int)(tsp.tv_nsec / 1000000));
        -:  498:#pragma GCC diagnostic pop
        -:  499:#endif /* _WIN32 */
        -:  500:
    #####:  501:    lc = ((int)level < (int)TRACE_LV_NONE) ? lc_table[(int)level] : 'V';
    #####:  502:    fprintf(stderr, "%s %c %s\n", ts, lc, msg);
    #####:  503:}
        -:  504:
        -:  505:/**
        -:  506: *  @brief  OS プロバイダとファイルプロバイダの両方へメッセージを書き込む。
        -:  507: *  @details レベルフィルタリングを行い、各出力先の条件に合致する場合のみ書き込む。
        -:  508: *           両方とも出力不要の場合は何もしない。
        -:  509: *  @return  成功 0 / いずれかの書き込み失敗 -1。
        -:  510: */
        1:  511:static int write_dual(trace_provider_t *handle, enum trace_level level, const char *msg)
        -:  512:{
        1:  513:    int os_result   = 0;
        1:  514:    int file_result = 0;
        -:  515:
        -:  516:    /* OS トレース出力 */
        1:  517:    if (should_output(level, handle->os_level))
        -:  518:    {
        1:  519:        os_result = write_to_provider(handle, level, msg);
        -:  520:    }
        -:  521:
        -:  522:    /* ファイルトレース出力 */
       1*:  523:    if (handle->file_handle != NULL && should_output(level, handle->file_level))
        -:  524:    {
    #####:  525:        file_result = trace_file_provider_write(handle->file_handle, (int)level, msg);
        -:  526:    }
        -:  527:
        -:  528:    /* stderr 出力 */
        1:  529:    if (should_output(level, handle->stderr_level))
        -:  530:    {
    #####:  531:        write_stderr_entry(level, msg);
        -:  532:    }
        -:  533:
       1*:  534:    return (os_result != 0 || file_result != 0) ? -1 : 0;
        -:  535:}
        -:  536:
        -:  537:int TRACE_UTIL_API
        1:  538:    trace_write(trace_provider_t *handle, enum trace_level level, const char *message)
        -:  539:{
        -:  540:    const char *msg;
        -:  541:    char buf[TRACE_MESSAGE_MAX_BYTES];
        -:  542:    size_t len;
        -:  543:    int ret;
        -:  544:
        1:  545:    if (handle == NULL || message == NULL)
        -:  546:    {
    #####:  547:        return 0;
        -:  548:    }
        -:  549:
        1:  550:    if (config_lock_shared_timed(handle) != 0)
        -:  551:    {
    #####:  552:        return -1;
        -:  553:    }
        -:  554:
        1:  555:    if (!handle->running)
        -:  556:    {
    #####:  557:        config_unlock_shared(handle);
    #####:  558:        return -1;
        -:  559:    }
        -:  560:
        1:  561:    msg = message;
        1:  562:    len = strlen(message);
        -:  563:
        1:  564:    if (len > MAX_BODY)
        -:  565:    {
    #####:  566:        size_t safe_len = utf8_safe_truncate(message, MAX_BODY);
    #####:  567:        memcpy(buf, message, safe_len);
    #####:  568:        buf[safe_len] = '\0';
    #####:  569:        msg = buf;
        -:  570:    }
        -:  571:
        1:  572:    ret = write_dual(handle, level, msg);
        1:  573:    config_unlock_shared(handle);
        1:  574:    return ret;
        -:  575:}
        -:  576:
        -:  577:int TRACE_UTIL_API
    #####:  578:    trace_writef(trace_provider_t *handle, enum trace_level level, const char *format, ...)
        -:  579:{
        -:  580:    va_list args;
        -:  581:    char buf[TRACE_MESSAGE_MAX_BYTES];
        -:  582:    int ret;
        -:  583:
    #####:  584:    if (handle == NULL || format == NULL)
        -:  585:    {
    #####:  586:        return 0;
        -:  587:    }
        -:  588:
    #####:  589:    if (config_lock_shared_timed(handle) != 0)
        -:  590:    {
    #####:  591:        return -1;
        -:  592:    }
        -:  593:
    #####:  594:    if (!handle->running)
        -:  595:    {
    #####:  596:        config_unlock_shared(handle);
    #####:  597:        return -1;
        -:  598:    }
        -:  599:
    #####:  600:    va_start(args, format);
    #####:  601:    vsnprintf(buf, sizeof(buf), format, args);
    #####:  602:    va_end(args);
        -:  603:
    #####:  604:    ret = write_dual(handle, level, buf);
    #####:  605:    config_unlock_shared(handle);
    #####:  606:    return ret;
        -:  607:}
        -:  608:
        -:  609:/** HEX 変換用テーブル。 */
        -:  610:static const char hex_chars[] = "0123456789ABCDEF";
        -:  611:
        -:  612:/** "..." サフィックスの長さ。 */
        -:  613:#define ELLIPSIS_LEN 3
        -:  614:
        -:  615:/**
        -:  616: *  @brief  HEX ダンプ出力の内部実装。
        -:  617: *  @details  ラベルは呼び出し元で事前にフォーマット済みの文字列を受け取る。
        -:  618: *            データが収まらない場合は切り詰めて "..." を付与する。
        -:  619: *            呼び出し元が共有ロックを保持した状態で呼ぶこと。
        -:  620: */
    #####:  621:static int hex_write_impl(trace_provider_t *handle, enum trace_level level,
        -:  622:                           const void *data, size_t size, const char *label)
        -:  623:{
        -:  624:    char buf[TRACE_MESSAGE_MAX_BYTES];
    #####:  625:    const unsigned char *bytes = (const unsigned char *)data;
    #####:  626:    size_t pos = 0;
        -:  627:    size_t remaining;
        -:  628:    size_t max_data_bytes;
    #####:  629:    int truncated = 0;
        -:  630:    size_t i;
        -:  631:
    #####:  632:    if (handle == NULL || data == NULL || size == 0)
        -:  633:    {
    #####:  634:        return 0;
        -:  635:    }
        -:  636:
        -:  637:    /* ラベル付与 */
    #####:  638:    if (label != NULL && label[0] != '\0')
        -:  639:    {
    #####:  640:        size_t lbl_len = strlen(label);
        -:  641:        /* ラベル + ": " がバッファに収まるか検査 */
    #####:  642:        if (lbl_len + 2 >= MAX_BODY)
        -:  643:        {
        -:  644:            /* ラベルだけで上限超過 — ラベルのみ出力 */
    #####:  645:            size_t copy_len = lbl_len < MAX_BODY ? lbl_len : MAX_BODY;
    #####:  646:            memcpy(buf, label, copy_len);
    #####:  647:            buf[copy_len] = '\0';
    #####:  648:            return write_dual(handle, level, buf);
        -:  649:        }
    #####:  650:        memcpy(buf, label, lbl_len);
    #####:  651:        buf[lbl_len] = ':';
    #####:  652:        buf[lbl_len + 1] = ' ';
    #####:  653:        pos = lbl_len + 2;
        -:  654:    }
        -:  655:
        -:  656:    /* HEX データ出力可能な残りバイト数 */
    #####:  657:    remaining = MAX_BODY - pos;
        -:  658:
        -:  659:    /* 全データが収まるか判定:
        -:  660:       N バイト → 3*N - 1 文字 (最終バイトはスペースなし) */
    #####:  661:    max_data_bytes = (remaining + 1) / 3;
        -:  662:
    #####:  663:    if (size > max_data_bytes)
        -:  664:    {
        -:  665:        /* 切り詰めが必要: "..." (3文字) の分を確保
        -:  666:           各バイト "XX " (3文字) + "..." (3文字) → M*3 + 3 ≤ remaining
        -:  667:           M ≤ (remaining - 3) / 3 */
    #####:  668:        truncated = 1;
    #####:  669:        if (remaining < ELLIPSIS_LEN)
        -:  670:        {
        -:  671:            /* "..." すら入らない */
    #####:  672:            buf[pos] = '\0';
    #####:  673:            return write_dual(handle, level, buf);
        -:  674:        }
    #####:  675:        max_data_bytes = (remaining - ELLIPSIS_LEN) / 3;
    #####:  676:        if (max_data_bytes == 0)
        -:  677:        {
        -:  678:            /* HEX 1 バイトも入らない — "..." のみ */
    #####:  679:            memcpy(buf + pos, "...", ELLIPSIS_LEN);
    #####:  680:            pos += ELLIPSIS_LEN;
    #####:  681:            buf[pos] = '\0';
    #####:  682:            return write_dual(handle, level, buf);
        -:  683:        }
    #####:  684:        size = max_data_bytes;
        -:  685:    }
        -:  686:
        -:  687:    /* HEX 文字列を構築 */
    #####:  688:    for (i = 0; i < size; i++)
        -:  689:    {
    #####:  690:        if (i > 0)
        -:  691:        {
    #####:  692:            buf[pos++] = ' ';
        -:  693:        }
    #####:  694:        buf[pos++] = hex_chars[(bytes[i] >> 4) & 0x0F];
    #####:  695:        buf[pos++] = hex_chars[bytes[i] & 0x0F];
        -:  696:    }
        -:  697:
        -:  698:    /* 切り詰め時は "..." を付与 */
    #####:  699:    if (truncated)
        -:  700:    {
    #####:  701:        buf[pos++] = ' ';
    #####:  702:        buf[pos++] = '.';
    #####:  703:        buf[pos++] = '.';
    #####:  704:        buf[pos++] = '.';
        -:  705:    }
    #####:  706:    buf[pos] = '\0';
        -:  707:
    #####:  708:    return write_dual(handle, level, buf);
        -:  709:}
        -:  710:
        -:  711:int TRACE_UTIL_API
    #####:  712:    trace_hex_write(trace_provider_t *handle, enum trace_level level,
        -:  713:                  const void *data, size_t size, const char *message)
        -:  714:{
        -:  715:    int ret;
        -:  716:
    #####:  717:    if (handle == NULL || data == NULL || size == 0)
        -:  718:    {
    #####:  719:        return 0;
        -:  720:    }
        -:  721:
    #####:  722:    if (config_lock_shared_timed(handle) != 0)
        -:  723:    {
    #####:  724:        return -1;
        -:  725:    }
        -:  726:
    #####:  727:    if (!handle->running)
        -:  728:    {
    #####:  729:        config_unlock_shared(handle);
    #####:  730:        return -1;
        -:  731:    }
        -:  732:
    #####:  733:    ret = hex_write_impl(handle, level, data, size, message);
    #####:  734:    config_unlock_shared(handle);
    #####:  735:    return ret;
        -:  736:}
        -:  737:
        -:  738:int TRACE_UTIL_API
    #####:  739:    trace_hex_writef(trace_provider_t *handle, enum trace_level level,
        -:  740:                   const void *data, size_t size, const char *format, ...)
        -:  741:{
        -:  742:    char label[TRACE_MESSAGE_MAX_BYTES];
        -:  743:    int ret;
        -:  744:
    #####:  745:    if (handle == NULL || data == NULL || size == 0)
        -:  746:    {
    #####:  747:        return 0;
        -:  748:    }
        -:  749:
    #####:  750:    if (config_lock_shared_timed(handle) != 0)
        -:  751:    {
    #####:  752:        return -1;
        -:  753:    }
        -:  754:
    #####:  755:    if (!handle->running)
        -:  756:    {
    #####:  757:        config_unlock_shared(handle);
    #####:  758:        return -1;
        -:  759:    }
        -:  760:
    #####:  761:    if (format != NULL)
        -:  762:    {
        -:  763:        va_list args;
    #####:  764:        va_start(args, format);
    #####:  765:        vsnprintf(label, sizeof(label), format, args);
    #####:  766:        va_end(args);
    #####:  767:        ret = hex_write_impl(handle, level, data, size, label);
        -:  768:    }
        -:  769:    else
        -:  770:    {
    #####:  771:        ret = hex_write_impl(handle, level, data, size, NULL);
        -:  772:    }
        -:  773:
    #####:  774:    config_unlock_shared(handle);
    #####:  775:    return ret;
        -:  776:}
        -:  777:
        -:  778:void TRACE_UTIL_API
        1:  779:    trace_dispose(trace_provider_t *handle)
        -:  780:{
        1:  781:    if (handle == NULL)
        -:  782:    {
    #####:  783:        return;
        -:  784:    }
        -:  785:
        -:  786:    /* 排他ロックで running=0 をセットし、進行中の write 完了を待つ。
        -:  787:     * trace_stop 返却後は新規 write は -1 を返し、write 中の処理は完了している。 */
        1:  788:    trace_stop(handle);
        -:  789:
        -:  790:    /* ファイルトレースプロバイダの解放 */
        1:  791:    if (handle->file_handle != NULL)
        -:  792:    {
    #####:  793:        trace_file_provider_dispose(handle->file_handle);
    #####:  794:        handle->file_handle = NULL;
        -:  795:    }
        -:  796:
        -:  797:#ifdef _WIN32
        -:  798:    if (InterlockedDecrement(&s_trace_ref) == 0)
        -:  799:    {
        -:  800:        etw_provider_dispose(s_etw_handle);
        -:  801:        s_etw_handle = NULL;
        -:  802:    }
        -:  803:    free(handle->service_name);
        -:  804:    /* SRWLOCK は破棄関数不要 */
        -:  805:#else /* !_WIN32 */
        1:  806:    syslog_provider_dispose(handle->syslog_handle);
        1:  807:    if (handle->config_rwlock_initialized)
        -:  808:    {
        1:  809:        pthread_rwlock_destroy(&handle->config_rwlock);
        -:  810:    }
        -:  811:#endif /* _WIN32 */
        -:  812:
        1:  813:    free(handle);
        -:  814:}
        -:  815:
        -:  816:int TRACE_UTIL_API
        1:  817:    trace_modify_name(trace_provider_t *handle, const char *name, int64_t identifier)
        -:  818:{
        -:  819:    char *effective;
        -:  820:
        1:  821:    if (handle == NULL)
        -:  822:    {
    #####:  823:        return -1;
        -:  824:    }
        -:  825:
        1:  826:    if (identifier < 0)
        -:  827:    {
    #####:  828:        return -1;
        -:  829:    }
        -:  830:
        1:  831:    config_lock_exclusive(handle);
        -:  832:
        1:  833:    if (handle->running)
        -:  834:    {
    #####:  835:        config_unlock_exclusive(handle);
    #####:  836:        return -1;
        -:  837:    }
        -:  838:
        1:  839:    effective = build_effective_name(name, identifier);
        1:  840:    if (effective == NULL)
        -:  841:    {
    #####:  842:        config_unlock_exclusive(handle);
    #####:  843:        return -1;
        -:  844:    }
        -:  845:
        -:  846:#ifdef _WIN32
        -:  847:    free(handle->service_name);
        -:  848:    handle->service_name = effective;
        -:  849:    handle->identifier   = identifier;
        -:  850:    config_unlock_exclusive(handle);
        -:  851:    return 0;
        -:  852:#else /* !_WIN32 */
        -:  853:    {
        1:  854:        int rc = syslog_provider_rename(handle->syslog_handle, effective);
        1:  855:        free(effective);
        1:  856:        if (rc != 0)
        -:  857:        {
    #####:  858:            config_unlock_exclusive(handle);
    #####:  859:            return -1;
        -:  860:        }
        1:  861:        handle->identifier = identifier;
        1:  862:        config_unlock_exclusive(handle);
        1:  863:        return 0;
        -:  864:    }
        -:  865:#endif /* _WIN32 */
        -:  866:}
        -:  867:
        -:  868:int TRACE_UTIL_API
    #####:  869:    trace_modify_ostrc(trace_provider_t *handle, enum trace_level level)
        -:  870:{
    #####:  871:    if (handle == NULL)
        -:  872:    {
    #####:  873:        return -1;
        -:  874:    }
        -:  875:
    #####:  876:    config_lock_exclusive(handle);
        -:  877:
    #####:  878:    if (handle->running)
        -:  879:    {
    #####:  880:        config_unlock_exclusive(handle);
    #####:  881:        return -1;
        -:  882:    }
        -:  883:
    #####:  884:    handle->os_level = level;
    #####:  885:    config_unlock_exclusive(handle);
    #####:  886:    return 0;
        -:  887:}
        -:  888:
        -:  889:int TRACE_UTIL_API
    #####:  890:    trace_modify_filetrc(trace_provider_t *handle, const char *path,
        -:  891:                         enum trace_level level, size_t max_bytes, int generations)
        -:  892:{
    #####:  893:    int result = 0;
        -:  894:
    #####:  895:    if (handle == NULL)
        -:  896:    {
    #####:  897:        return -1;
        -:  898:    }
        -:  899:
    #####:  900:    config_lock_exclusive(handle);
        -:  901:
    #####:  902:    if (handle->running)
        -:  903:    {
    #####:  904:        config_unlock_exclusive(handle);
    #####:  905:        return -1;
        -:  906:    }
        -:  907:
        -:  908:    /* 既存のファイルプロバイダを解放する */
    #####:  909:    if (handle->file_handle != NULL)
        -:  910:    {
    #####:  911:        trace_file_provider_dispose(handle->file_handle);
    #####:  912:        handle->file_handle = NULL;
        -:  913:    }
        -:  914:
    #####:  915:    handle->file_level = level;
        -:  916:
        -:  917:    /* path が NULL の場合はファイルトレースを無効化して終了 */
    #####:  918:    if (path != NULL)
        -:  919:    {
        -:  920:        /* 新しいファイルプロバイダを初期化する */
    #####:  921:        handle->file_handle = trace_file_provider_init(path, max_bytes, generations);
    #####:  922:        if (handle->file_handle == NULL)
        -:  923:        {
    #####:  924:            result = -1;
        -:  925:        }
        -:  926:    }
        -:  927:
    #####:  928:    config_unlock_exclusive(handle);
    #####:  929:    return result;
        -:  930:}
        -:  931:
        -:  932:int TRACE_UTIL_API
    #####:  933:    trace_modify_stderrtrc(trace_provider_t *handle, enum trace_level level)
        -:  934:{
    #####:  935:    if (handle == NULL)
        -:  936:    {
    #####:  937:        return -1;
        -:  938:    }
        -:  939:
    #####:  940:    config_lock_exclusive(handle);
        -:  941:
    #####:  942:    if (handle->running)
        -:  943:    {
    #####:  944:        config_unlock_exclusive(handle);
    #####:  945:        return -1;
        -:  946:    }
        -:  947:
    #####:  948:    handle->stderr_level = level;
    #####:  949:    config_unlock_exclusive(handle);
    #####:  950:    return 0;
        -:  951:}
        -:  952:
        -:  953:enum trace_level TRACE_UTIL_API
    #####:  954:    trace_get_ostrc(trace_provider_t *handle)
        -:  955:{
        -:  956:    enum trace_level lv;
        -:  957:
    #####:  958:    if (handle == NULL)
        -:  959:    {
    #####:  960:        return TRACE_LV_NONE;
        -:  961:    }
    #####:  962:    if (config_lock_shared_timed(handle) != 0)
        -:  963:    {
    #####:  964:        return TRACE_LV_NONE;
        -:  965:    }
    #####:  966:    lv = handle->os_level;
    #####:  967:    config_unlock_shared(handle);
    #####:  968:    return lv;
        -:  969:}
        -:  970:
        -:  971:enum trace_level TRACE_UTIL_API
    #####:  972:    trace_get_filetrc(trace_provider_t *handle)
        -:  973:{
        -:  974:    enum trace_level lv;
        -:  975:
    #####:  976:    if (handle == NULL)
        -:  977:    {
    #####:  978:        return TRACE_LV_NONE;
        -:  979:    }
    #####:  980:    if (config_lock_shared_timed(handle) != 0)
        -:  981:    {
    #####:  982:        return TRACE_LV_NONE;
        -:  983:    }
    #####:  984:    lv = handle->file_level;
    #####:  985:    config_unlock_shared(handle);
    #####:  986:    return lv;
        -:  987:}
        -:  988:
        -:  989:enum trace_level TRACE_UTIL_API
    #####:  990:    trace_get_stderrtrc(trace_provider_t *handle)
        -:  991:{
        -:  992:    enum trace_level lv;
        -:  993:
    #####:  994:    if (handle == NULL)
        -:  995:    {
    #####:  996:        return TRACE_LV_NONE;
        -:  997:    }
    #####:  998:    if (config_lock_shared_timed(handle) != 0)
        -:  999:    {
    #####: 1000:        return TRACE_LV_NONE;
        -: 1001:    }
    #####: 1002:    lv = handle->stderr_level;
    #####: 1003:    config_unlock_shared(handle);
    #####: 1004:    return lv;
        -: 1005:}
