        -:    0:Source:console-util.c
        -:    0:Graph:/__w/c-modernization-kit/c-modernization-kit/test/util/src/libutilTest/consoleTest/obj/console-util.gcno
        -:    0:Data:/__w/c-modernization-kit/c-modernization-kit/test/util/src/libutilTest/consoleTest/obj/console-util.gcda
        -:    0:Runs:1
        -:    0:Programs:1
        -:    1:/**
        -:    2: *  @file           console-util.c
        -:    3: *  @brief          コンソール UTF-8 ヘルパー実装。
        -:    4: *  @details        Windows 環境: stdout / stderr を匿名パイプに差し替え、
        -:    5: *                  読み取りスレッドがコンソール (TTY) には WriteConsoleW で UTF-16 として
        -:    6: *                  出力し、パイプやファイルへは UTF-8 バイト列をそのまま転送します。\n
        -:    7: *                  Linux 環境: console_init / console_dispose は no-op です。
        -:    8: */
        -:    9:
        -:   10:#include <console-util.h>
        -:   11:
        -:   12:/* ===== Windows 実装 ===== */
        -:   13:
        -:   14:#ifdef _WIN32
        -:   15:
        -:   16:#include <windows.h>
        -:   17:#include <io.h>      /* _open_osfhandle, _dup, _dup2, _close, _fileno */
        -:   18:#include <fcntl.h>   /* _O_WRONLY, _O_BINARY */
        -:   19:#include <stdio.h>   /* FILE, setvbuf, fflush, stdout, stderr */
        -:   20:#include <string.h>  /* memmove */
        -:   21:
        -:   22:/* パイプ読み取りスレッドのバッファサイズ (バイト) */
        -:   23:#define READ_BUF_SIZE 4096
        -:   24:
        -:   25:/* ストリームごとの内部状態 */
        -:   26:typedef struct {
        -:   27:    HANDLE orig_handle; /* DuplicateHandle で保存した元の Windows ハンドル */
        -:   28:    HANDLE pipe_read;   /* 匿名パイプの読み取り端 */
        -:   29:    int    orig_crt_fd; /* _dup で保存した元の CRT ファイルディスクリプタ */
        -:   30:    HANDLE thread;      /* 読み取りスレッドハンドル */
        -:   31:    LONG   active;      /* 初期化済みフラグ (InterlockedExchange で操作) */
        -:   32:} stream_state_t;
        -:   33:
        -:   34:/* グローバル状態 (ゼロ初期化) */
        -:   35:static stream_state_t s_stdout_state;
        -:   36:static stream_state_t s_stderr_state;
        -:   37:
        -:   38:/* ===== UTF-8 境界ユーティリティ ===== */
        -:   39:
        -:   40:/*
        -:   41: * buf[0..len) の末尾にある不完全な UTF-8 マルチバイトシーケンスを除いた
        -:   42: * 有効バイト数を返す。buf 全体が完全なシーケンスで終端している場合は len を返す。
        -:   43: *
        -:   44: * UTF-8 エンコーディング:
        -:   45: *   0xxxxxxx : 1 バイト (ASCII)
        -:   46: *   110xxxxx : 2 バイト先頭
        -:   47: *   1110xxxx : 3 バイト先頭
        -:   48: *   11110xxx : 4 バイト先頭
        -:   49: *   10xxxxxx : 継続バイト
        -:   50: */
        -:   51:static int utf8_complete_length(const char *buf, int len)
        -:   52:{
        -:   53:    int i;
        -:   54:    unsigned char lead;
        -:   55:    int seq_len;
        -:   56:
        -:   57:    if (len == 0) return 0;
        -:   58:
        -:   59:    /* 末尾から最後の非継続バイト (リードバイトまたは ASCII) を探す */
        -:   60:    i = len - 1;
        -:   61:    while (i >= 0 && ((unsigned char)buf[i] & 0xC0) == 0x80) {
        -:   62:        i--;
        -:   63:    }
        -:   64:    if (i < 0) return 0; /* 全バイトが継続バイト (異常データ): 安全のため 0 を返す */
        -:   65:
        -:   66:    lead = (unsigned char)buf[i];
        -:   67:    if      ((lead & 0x80) == 0x00) seq_len = 1; /* ASCII */
        -:   68:    else if ((lead & 0xE0) == 0xC0) seq_len = 2;
        -:   69:    else if ((lead & 0xF0) == 0xE0) seq_len = 3;
        -:   70:    else if ((lead & 0xF8) == 0xF0) seq_len = 4;
        -:   71:    else return len; /* 不正なリードバイト: 破損データとして境界を変更しない */
        -:   72:
        -:   73:    /* シーケンスが完結しているか確認 */
        -:   74:    return (len - i >= seq_len) ? len : i;
        -:   75:}
        -:   76:
        -:   77:/* ===== 読み取りスレッド ===== */
        -:   78:
        -:   79:/*
        -:   80: * パイプから UTF-8 バイト列を読み取り、出力先に応じて書き出す。
        -:   81: *   - コンソール (TTY): WriteConsoleW で UTF-16 として出力。失敗時は WriteFile でフォールバック。
        -:   82: *   - パイプ / ファイル: WriteFile で UTF-8 バイト列をそのまま転送。
        -:   83: * マルチバイト文字の途中で分割された場合は次回の読み取りまで末尾バイトを保留する。
        -:   84: */
        -:   85:static DWORD WINAPI reader_thread_proc(LPVOID param)
        -:   86:{
        -:   87:    stream_state_t *s = (stream_state_t *)param;
        -:   88:    char    buf[READ_BUF_SIZE];
        -:   89:    wchar_t wbuf[READ_BUF_SIZE]; /* UTF-16 変換バッファ (最大 READ_BUF_SIZE 文字) */
        -:   90:    int     pending_len = 0;
        -:   91:    DWORD   mode        = 0;
        -:   92:    BOOL    is_console  = (GetFileType(s->orig_handle) == FILE_TYPE_CHAR)
        -:   93:                       && GetConsoleMode(s->orig_handle, &mode);
        -:   94:
        -:   95:    for (;;) {
        -:   96:        DWORD nread = 0;
        -:   97:        BOOL  ok    = ReadFile(s->pipe_read,
        -:   98:                               buf + pending_len,
        -:   99:                               (DWORD)(READ_BUF_SIZE - pending_len),
        -:  100:                               &nread, NULL);
        -:  101:        if (!ok || nread == 0) break; /* パイプが閉じた / エラー */
        -:  102:
        -:  103:        {
        -:  104:            int total = pending_len + (int)nread;
        -:  105:
        -:  106:            if (is_console) {
        -:  107:                /* 完全な UTF-8 シーケンスの境界を計算し、不完全な末尾を保留する */
        -:  108:                int complete   = utf8_complete_length(buf, total);
        -:  109:                int new_pending = total - complete;
        -:  110:
        -:  111:                if (complete > 0) {
        -:  112:                    int wlen = MultiByteToWideChar(CP_UTF8, 0,
        -:  113:                                                   buf, complete,
        -:  114:                                                   wbuf, READ_BUF_SIZE);
        -:  115:                    if (wlen > 0) {
        -:  116:                        DWORD nw = 0;
        -:  117:                        if (!WriteConsoleW(s->orig_handle, wbuf, (DWORD)wlen, &nw, NULL)) {
        -:  118:                            /* WriteConsoleW 失敗: UTF-8 バイト列をそのまま書き戻す */
        -:  119:                            WriteFile(s->orig_handle, buf, (DWORD)complete, &nw, NULL);
        -:  120:                        }
        -:  121:                    }
        -:  122:                }
        -:  123:
        -:  124:                /* 不完全な末尾シーケンスを buf 先頭に移動して次回へ持ち越す */
        -:  125:                if (new_pending > 0) {
        -:  126:                    memmove(buf, buf + complete, (size_t)new_pending);
        -:  127:                }
        -:  128:                pending_len = new_pending;
        -:  129:            } else {
        -:  130:                /* パイプ / ファイル: UTF-8 バイト列をそのまま転送 */
        -:  131:                DWORD nw = 0;
        -:  132:                WriteFile(s->orig_handle, buf, (DWORD)total, &nw, NULL);
        -:  133:                pending_len = 0;
        -:  134:            }
        -:  135:        }
        -:  136:    }
        -:  137:
        -:  138:    return 0;
        -:  139:}
        -:  140:
        -:  141:/* ===== ストリーム初期化 / 解放 ===== */
        -:  142:
        -:  143:/*
        -:  144: * 指定したストリームを匿名パイプに差し替え、読み取りスレッドを起動する。
        -:  145: *
        -:  146: * 処理順:
        -:  147: *   1. GetStdHandle → DuplicateHandle で元ハンドルを保存
        -:  148: *   2. CreatePipe で匿名パイプ (read, write) を作成
        -:  149: *   3. _dup で元 CRT FD を保存
        -:  150: *   4. _open_osfhandle + _dup2 で crt_stream を書き込み端に向ける
        -:  151: *   5. setvbuf で CRT バッファを無効化
        -:  152: *   6. CreateThread で読み取りスレッドを起動
        -:  153: */
        -:  154:static int init_stream(stream_state_t *s, DWORD std_handle_id, FILE *crt_stream)
        -:  155:{
        -:  156:    HANDLE proc;
        -:  157:    HANDLE orig;
        -:  158:    HANDLE pipe_read;
        -:  159:    HANDLE pipe_write;
        -:  160:    SECURITY_ATTRIBUTES sa;
        -:  161:    int    new_fd;
        -:  162:
        -:  163:    orig = GetStdHandle(std_handle_id);
        -:  164:    if (orig == NULL || orig == INVALID_HANDLE_VALUE) return -1;
        -:  165:
        -:  166:    /* 元ハンドルを安全に複製して保存 */
        -:  167:    proc = GetCurrentProcess();
        -:  168:    if (!DuplicateHandle(proc, orig, proc, &s->orig_handle,
        -:  169:                         0, FALSE, DUPLICATE_SAME_ACCESS)) return -1;
        -:  170:
        -:  171:    /* 匿名パイプを作成 (書き込み端は継承可能に設定) */
        -:  172:    sa.nLength              = sizeof(sa);
        -:  173:    sa.lpSecurityDescriptor = NULL;
        -:  174:    sa.bInheritHandle       = TRUE;
        -:  175:    if (!CreatePipe(&pipe_read, &pipe_write, &sa, 0)) {
        -:  176:        CloseHandle(s->orig_handle);
        -:  177:        return -1;
        -:  178:    }
        -:  179:    /* 読み取り端は子プロセスに継承しない */
        -:  180:    SetHandleInformation(pipe_read, HANDLE_FLAG_INHERIT, 0);
        -:  181:    s->pipe_read = pipe_read;
        -:  182:
        -:  183:    /* 元 CRT FD を保存 */
        -:  184:    s->orig_crt_fd = _dup(_fileno(crt_stream));
        -:  185:    if (s->orig_crt_fd == -1) {
        -:  186:        CloseHandle(s->orig_handle);
        -:  187:        CloseHandle(pipe_read);
        -:  188:        CloseHandle(pipe_write);
        -:  189:        return -1;
        -:  190:    }
        -:  191:
        -:  192:    /* パイプ書き込み端から CRT FD を作成し、stdout / stderr を差し替える */
        -:  193:    new_fd = _open_osfhandle((intptr_t)pipe_write, _O_WRONLY | _O_BINARY);
        -:  194:    if (new_fd == -1) {
        -:  195:        _close(s->orig_crt_fd);
        -:  196:        CloseHandle(s->orig_handle);
        -:  197:        CloseHandle(pipe_read);
        -:  198:        CloseHandle(pipe_write);
        -:  199:        return -1;
        -:  200:    }
        -:  201:
        -:  202:    if (_dup2(new_fd, _fileno(crt_stream)) != 0) {
        -:  203:        _close(new_fd); /* pipe_write を解放 (_open_osfhandle が所有) */
        -:  204:        _close(s->orig_crt_fd);
        -:  205:        CloseHandle(s->orig_handle);
        -:  206:        CloseHandle(pipe_read);
        -:  207:        return -1;
        -:  208:    }
        -:  209:    _close(new_fd); /* _dup2 済み。pipe_write の元ハンドルを解放。FD 1 は独立した複製を保持。 */
        -:  210:    setvbuf(crt_stream, NULL, _IONBF, 0);
        -:  211:
        -:  212:    /* 読み取りスレッドを起動 */
        -:  213:    s->thread = CreateThread(NULL, 0, reader_thread_proc, s, 0, NULL);
        -:  214:    if (s->thread == NULL) {
        -:  215:        /* 差し替えを元に戻す (_dup2 で書き込み端の複製も閉じる) */
        -:  216:        _dup2(s->orig_crt_fd, _fileno(crt_stream));
        -:  217:        _close(s->orig_crt_fd);
        -:  218:        CloseHandle(s->orig_handle);
        -:  219:        CloseHandle(pipe_read);
        -:  220:        return -1;
        -:  221:    }
        -:  222:
        -:  223:    InterlockedExchange(&s->active, 1);
        -:  224:    return 0;
        -:  225:}
        -:  226:
        -:  227:/*
        -:  228: * 指定したストリームを元のハンドルに戻し、読み取りスレッドを停止する。
        -:  229: * active が 0 の場合 (未初期化) は何もしない。
        -:  230: *
        -:  231: * 処理順:
        -:  232: *   1. fflush で CRT バッファを吐き出す
        -:  233: *   2. _dup2 で crt_stream を元の FD に戻す → パイプ書き込み端を閉じて EOF を送出
        -:  234: *   3. WaitForSingleObject でスレッド終了を待つ
        -:  235: *   4. ハンドルを解放する
        -:  236: */
        -:  237:static void dispose_stream(stream_state_t *s, FILE *crt_stream)
        -:  238:{
        -:  239:    /* active を 1 → 0 に変更。戻り値が 0 なら元々 inactive なので何もしない。 */
        -:  240:    if (!InterlockedCompareExchange(&s->active, 0, 1)) return;
        -:  241:
        -:  242:    fflush(crt_stream);
        -:  243:
        -:  244:    /* crt_stream を元の FD に戻す。これにより FD 1 / 2 の書き込み端複製が閉じ、
        -:  245:     * スレッドの ReadFile が ERROR_BROKEN_PIPE で返る (EOF 相当)。 */
        -:  246:    _dup2(s->orig_crt_fd, _fileno(crt_stream));
        -:  247:    _close(s->orig_crt_fd);
        -:  248:
        -:  249:    /* スレッド終了を最大 5 秒待つ */
        -:  250:    WaitForSingleObject(s->thread, 5000);
        -:  251:
        -:  252:    CloseHandle(s->pipe_read);
        -:  253:    CloseHandle(s->orig_handle);
        -:  254:    CloseHandle(s->thread);
        -:  255:
        -:  256:    s->orig_crt_fd = -1;
        -:  257:    s->pipe_read   = NULL;
        -:  258:    s->orig_handle = NULL;
        -:  259:    s->thread      = NULL;
        -:  260:}
        -:  261:
        -:  262:/* ===== 公開 API ===== */
        -:  263:
        -:  264:void CONSOLE_UTIL_API console_init(void)
        -:  265:{
        -:  266:    /* 二重初期化を防ぐ (マルチスレッド安全ではないが init はシングルスレッドで呼ぶ想定) */
        -:  267:    if (s_stdout_state.active) return;
        -:  268:
        -:  269:    if (init_stream(&s_stdout_state, STD_OUTPUT_HANDLE, stdout) != 0)
        -:  270:    {
        -:  271:        fprintf(stderr, "console_init: stdout の初期化に失敗しました。\n");
        -:  272:        return;
        -:  273:    }
        -:  274:
        -:  275:    if (init_stream(&s_stderr_state, STD_ERROR_HANDLE, stderr) != 0) {
        -:  276:        /* stderr の初期化に失敗した場合は stdout の差し替えをロールバックする */
        -:  277:        dispose_stream(&s_stdout_state, stdout);
        -:  278:        fprintf(stderr, "console_init: stderr の初期化に失敗しました。\n");
        -:  279:        return;
        -:  280:    }
        -:  281:}
        -:  282:
        -:  283:void CONSOLE_UTIL_API console_dispose(void)
        -:  284:{
        -:  285:    /* stderr → stdout の順で解放する */
        -:  286:    dispose_stream(&s_stderr_state, stderr);
        -:  287:    dispose_stream(&s_stdout_state, stdout);
        -:  288:}
        -:  289:
        -:  290:#else /* !_WIN32 */
        -:  291:
        -:  292:/* ===== Linux / 非 Windows 実装 (no-op) ===== */
        -:  293:
        1:  294:void CONSOLE_UTIL_API console_init(void)    {}
        2:  295:void CONSOLE_UTIL_API console_dispose(void) {}
        -:  296:
        -:  297:#endif /* _WIN32 */
