Document of c-modernization-kit (util) 1.0.0
Loading...
Searching...
No Matches
file-provider.c
Go to the documentation of this file.
1#include <trace-file-util.h>
2#include <stdlib.h>
3#include <string.h>
4#include <stdio.h>
5
6#ifdef _WIN32
7#include <windows.h>
8#else /* !_WIN32 */
9#include <fcntl.h>
10#include <unistd.h>
11#include <sys/stat.h>
12#include <sys/types.h>
13#include <pthread.h>
14#include <time.h>
15#include <limits.h>
16#endif /* _WIN32 */
17
18/* ===== 内部定数 ===== */
19
21#define TRACE_FILE_LINE_BUF 1100
22
24#define FILE_LOCK_TIMEOUT_MS 100
25
27#define TRACE_FILE_TS_LEN 23
28
30#define TRACE_FILE_SUFFIX_MAX 5
31
32/* プラットフォームごとのパス長上限 */
33#ifdef _WIN32
34#define TRACE_FILE_PATH_MAX MAX_PATH
35#else /* !_WIN32 */
36#ifndef PATH_MAX
37#define PATH_MAX 4096
38#endif /* PATH_MAX */
39#define TRACE_FILE_PATH_MAX PATH_MAX
40#endif /* _WIN32 */
41
42/* ===== 内部構造体 ===== */
43
48{
50 char *path;
52 size_t max_bytes;
57
58#ifdef _WIN32
60 HANDLE fh;
62 CRITICAL_SECTION cs;
64 int cs_initialized;
65#else /* !_WIN32 */
67 int fd;
69 pthread_mutex_t mutex;
74#endif /* _WIN32 */
75};
76
77/* ===== 内部ヘルパー関数 ===== */
78
82static char level_char(int level)
83{
84 switch (level)
85 {
86 case TRACE_FILE_LV_CRITICAL: return 'C';
87 case TRACE_FILE_LV_ERROR: return 'E';
88 case TRACE_FILE_LV_WARNING: return 'W';
89 case TRACE_FILE_LV_INFO: return 'I';
90 default: return 'V';
91 }
92}
93
99static void format_timestamp(char *buf, int buf_size)
100{
101#ifdef _WIN32
102 SYSTEMTIME st;
103 GetSystemTime(&st);
104 snprintf(buf, (size_t)buf_size,
105 "%04d-%02d-%02d %02d:%02d:%02d.%03d",
106 (int)st.wYear, (int)st.wMonth, (int)st.wDay,
107 (int)st.wHour, (int)st.wMinute, (int)st.wSecond,
108 (int)st.wMilliseconds);
109#else /* !_WIN32 */
110 struct timespec ts;
111 struct tm tm_val;
112 clock_gettime(CLOCK_REALTIME, &ts);
113 gmtime_r(&ts.tv_sec, &tm_val);
114 /* -Wformat-truncation の抑制: gmtime_r() が返す tm 構造体の各フィールドは POSIX で
115 * 範囲が保証されており (tm_mon: 0-11, tm_mday: 1-31 等)、出力は常に 23 文字以内に
116 * 収まる。GCC は int 型の理論上の最大範囲 [-2147483648, 2147483647] を使って静的
117 * 検証するため false positive が発生する。pragma はその誤報を局所的に抑制する。 */
118#pragma GCC diagnostic push
119#pragma GCC diagnostic ignored "-Wformat-truncation"
120 snprintf(buf, (size_t)buf_size,
121 "%04d-%02d-%02d %02d:%02d:%02d.%03d",
122 tm_val.tm_year + 1900, tm_val.tm_mon + 1, tm_val.tm_mday,
123 tm_val.tm_hour, tm_val.tm_min, tm_val.tm_sec,
124 (int)(ts.tv_nsec / 1000000));
125#pragma GCC diagnostic pop
126#endif /* _WIN32 */
127}
128
134{
135#ifdef _WIN32
136 LARGE_INTEGER pos;
137 LARGE_INTEGER size;
138
139 p->fh = CreateFileA(
140 p->path,
141 GENERIC_WRITE,
142 FILE_SHARE_READ | FILE_SHARE_DELETE,
143 NULL,
144 OPEN_ALWAYS,
145 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH,
146 NULL);
147
148 if (p->fh == INVALID_HANDLE_VALUE)
149 {
150 p->current_bytes = 0;
151 return -1;
152 }
153
154 /* 末尾へ移動して追記モードにする */
155 pos.QuadPart = 0;
156 SetFilePointerEx(p->fh, pos, NULL, FILE_END);
157
158 /* 既存ファイルサイズを取得してインメモリカウンタを初期化する */
159 if (GetFileSizeEx(p->fh, &size))
160 {
161 p->current_bytes = (size_t)size.QuadPart;
162 }
163 else
164 {
165 p->current_bytes = 0;
166 }
167
168 return 0;
169
170#else /* !_WIN32 */
171 struct stat st;
172
173 p->fd = open(p->path,
174 O_WRONLY | O_APPEND | O_CREAT | O_DSYNC,
175 0644);
176
177 if (p->fd == -1)
178 {
179 p->current_bytes = 0;
180 return -1;
181 }
182
183 /* 既存ファイルサイズを取得してインメモリカウンタを初期化する */
184 if (fstat(p->fd, &st) == 0)
185 {
186 p->current_bytes = (size_t)st.st_size;
187 }
188 else
189 {
190 p->current_bytes = 0;
191 }
192
193 return 0;
194#endif /* _WIN32 */
195}
196
203{
204 p->current_bytes = 0;
205
206#ifdef _WIN32
207 p->fh = CreateFileA(
208 p->path,
209 GENERIC_WRITE,
210 FILE_SHARE_READ | FILE_SHARE_DELETE,
211 NULL,
212 CREATE_ALWAYS,
213 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH,
214 NULL);
215
216 return (p->fh != INVALID_HANDLE_VALUE) ? 0 : -1;
217
218#else /* !_WIN32 */
219 p->fd = open(p->path,
220 O_WRONLY | O_APPEND | O_CREAT | O_TRUNC | O_DSYNC,
221 0644);
222
223 return (p->fd != -1) ? 0 : -1;
224#endif /* _WIN32 */
225}
226
231{
232#ifdef _WIN32
233 if (p->fh != INVALID_HANDLE_VALUE)
234 {
235 CloseHandle(p->fh);
236 p->fh = INVALID_HANDLE_VALUE;
237 }
238#else /* !_WIN32 */
239 if (p->fd != -1)
240 {
241 close(p->fd);
242 p->fd = -1;
243 }
244#endif /* _WIN32 */
245}
246
254{
255 /* パス構築用スタックバッファ */
256 char old_path[TRACE_FILE_PATH_MAX];
257 char new_path[TRACE_FILE_PATH_MAX];
258 int gen;
259
260 close_file(p);
261
262 /* 最老世代のファイルを削除する (失敗は無視) */
263 snprintf(new_path, sizeof(new_path), "%s.%d", p->path, p->generations);
264#ifdef _WIN32
265 DeleteFileA(new_path);
266#else /* !_WIN32 */
267 unlink(new_path);
268#endif /* _WIN32 */
269
270 /* path.(gen-1) → path.gen のカスケードリネーム */
271 for (gen = p->generations; gen >= 1; gen--)
272 {
273 /* 移動先: path.gen */
274 snprintf(new_path, sizeof(new_path), "%s.%d", p->path, gen);
275
276 /* 移動元: gen==1 のときは path そのもの */
277 if (gen == 1)
278 {
279 /* old_path に path をコピー */
280 snprintf(old_path, sizeof(old_path), "%s", p->path);
281 }
282 else
283 {
284 snprintf(old_path, sizeof(old_path), "%s.%d", p->path, gen - 1);
285 }
286
287#ifdef _WIN32
288 if (!MoveFileExA(old_path, new_path, MOVEFILE_REPLACE_EXISTING))
289 {
290 /* リネーム失敗: カスケードをここで打ち切る */
291 break;
292 }
293#else /* !_WIN32 */
294 if (rename(old_path, new_path) != 0)
295 {
296 /* リネーム失敗: カスケードをここで打ち切る */
297 break;
298 }
299#endif /* _WIN32 */
300 }
301
302 /* 新規ファイルを作成して開く (失敗しても fh=INVALID/fd=-1 のまま続行) */
304}
305
306/* ===== 公開 API ===== */
307
309 trace_file_provider_init(const char *path, size_t max_bytes, int generations)
310{
311 trace_file_provider_t *handle;
312 size_t path_len;
313
314 if (path == NULL)
315 {
316 return NULL;
317 }
318
319 path_len = strlen(path);
320
321 /* パスが長すぎてローテーションサフィックスを付加できない場合は拒否する */
322 if (path_len + TRACE_FILE_SUFFIX_MAX >= (size_t)TRACE_FILE_PATH_MAX)
323 {
324 return NULL;
325 }
326
327 handle = (trace_file_provider_t *)malloc(sizeof(trace_file_provider_t));
328 if (handle == NULL)
329 {
330 return NULL;
331 }
332
333 /* パス文字列をヒープへ複製する */
334 handle->path = (char *)malloc(path_len + 1);
335 if (handle->path == NULL)
336 {
337 free(handle);
338 return NULL;
339 }
340 memcpy(handle->path, path, path_len + 1);
341
342 handle->max_bytes = (max_bytes > 0) ? max_bytes : TRACE_FILE_DEFAULT_MAX_BYTES;
343 handle->generations = (generations > 0) ? generations : TRACE_FILE_DEFAULT_GENERATIONS;
344 handle->current_bytes = 0;
345
346 /* プラットフォームごとの同期プリミティブを初期化する */
347#ifdef _WIN32
348 handle->fh = INVALID_HANDLE_VALUE;
349 handle->cs_initialized = 0;
350 InitializeCriticalSectionAndSpinCount(&handle->cs, 1000);
351 handle->cs_initialized = 1;
352#else /* !_WIN32 */
353 handle->fd = -1;
354 handle->mutex_initialized = 0;
355 if (pthread_mutex_init(&handle->mutex, NULL) != 0)
356 {
357 free(handle->path);
358 free(handle);
359 return NULL;
360 }
361 handle->mutex_initialized = 1;
362#endif /* _WIN32 */
363
364 /* ファイルを開く; 失敗したらリソースを解放して NULL を返す */
365 if (open_file(handle) != 0)
366 {
367#ifdef _WIN32
368 if (handle->cs_initialized)
369 {
370 DeleteCriticalSection(&handle->cs);
371 }
372#else /* !_WIN32 */
373 if (handle->mutex_initialized)
374 {
375 pthread_mutex_destroy(&handle->mutex);
376 }
377#endif /* _WIN32 */
378 free(handle->path);
379 free(handle);
380 return NULL;
381 }
382
383 return handle;
384}
385
388 const char *message)
389{
390 char ts[TRACE_FILE_TS_LEN + 1];
391 char buf[TRACE_FILE_LINE_BUF];
392 int len;
393 int ret;
394
395 if (handle == NULL || message == NULL)
396 {
397 return 0;
398 }
399
400 /* タイムスタンプはロック外で取得する (共有状態へのアクセスなし) */
401 format_timestamp(ts, (int)sizeof(ts));
402
403 /* 1 行全体をスタックバッファへフォーマットする (syscall 回数を最小化) */
404 len = snprintf(buf, sizeof(buf), "%s %c %s\n", ts, level_char(level), message);
405 if (len <= 0)
406 {
407 return -1;
408 }
409 if (len >= (int)sizeof(buf))
410 {
411 /* 切り詰め: バッファ末尾を必ず改行で終端する */
412 len = (int)sizeof(buf) - 1;
413 buf[len - 1] = '\n';
414 }
415
416 /* ロック取得 (タイムアウト付き) */
417#ifdef _WIN32
418 {
419 DWORD deadline = GetTickCount() + (DWORD)FILE_LOCK_TIMEOUT_MS;
420 while (!TryEnterCriticalSection(&handle->cs))
421 {
422 if ((LONG)(GetTickCount() - deadline) >= 0)
423 {
424 return -1;
425 }
426 SwitchToThread();
427 }
428 }
429#else /* !_WIN32 */
430 {
431 struct timespec abs_timeout;
432 clock_gettime(CLOCK_REALTIME, &abs_timeout);
433 abs_timeout.tv_nsec += (long)FILE_LOCK_TIMEOUT_MS * 1000000L;
434 if (abs_timeout.tv_nsec >= 1000000000L)
435 {
436 abs_timeout.tv_sec += 1;
437 abs_timeout.tv_nsec -= 1000000000L;
438 }
439 if (pthread_mutex_timedlock(&handle->mutex, &abs_timeout) != 0)
440 {
441 return -1;
442 }
443 }
444#endif /* _WIN32 */
445
446 /* ファイルが未開の場合は書き込みをスキップする */
447#ifdef _WIN32
448 if (handle->fh == INVALID_HANDLE_VALUE)
449 {
450 LeaveCriticalSection(&handle->cs);
451 return -1;
452 }
453#else /* !_WIN32 */
454 if (handle->fd == -1)
455 {
456 pthread_mutex_unlock(&handle->mutex);
457 return -1;
458 }
459#endif /* _WIN32 */
460
461 /* ファイルへ書き込む (FILE_FLAG_WRITE_THROUGH / O_DSYNC により自動フラッシュ) */
462 ret = -1;
463#ifdef _WIN32
464 {
465 DWORD written = 0;
466 if (WriteFile(handle->fh, buf, (DWORD)len, &written, NULL)
467 && (DWORD)written == (DWORD)len)
468 {
469 ret = 0;
470 }
471 }
472#else /* !_WIN32 */
473 {
474 ssize_t written = write(handle->fd, buf, (size_t)len);
475 if (written == (ssize_t)len)
476 {
477 ret = 0;
478 }
479 }
480#endif /* _WIN32 */
481
482 /* 書き込み成功時: サイズを追跡しローテーション閾値を確認する */
483 if (ret == 0)
484 {
485 /* size_t のオーバーフローを防ぐ (実用上は発生しないが防御的に扱う) */
486 if (handle->current_bytes <= (size_t)-1 - (size_t)len)
487 {
488 handle->current_bytes += (size_t)len;
489 }
490
491 if (handle->current_bytes >= handle->max_bytes)
492 {
493 rotate_file(handle);
494 }
495 }
496
497 /* ロック解放 */
498#ifdef _WIN32
499 LeaveCriticalSection(&handle->cs);
500#else /* !_WIN32 */
501 pthread_mutex_unlock(&handle->mutex);
502#endif /* _WIN32 */
503
504 return ret;
505}
506
509{
510 if (handle == NULL)
511 {
512 return;
513 }
514
515 close_file(handle);
516
517#ifdef _WIN32
518 if (handle->cs_initialized)
519 {
520 DeleteCriticalSection(&handle->cs);
521 handle->cs_initialized = 0;
522 }
523#else /* !_WIN32 */
524 if (handle->mutex_initialized)
525 {
526 pthread_mutex_destroy(&handle->mutex);
527 handle->mutex_initialized = 0;
528 }
529#endif /* _WIN32 */
530
531 free(handle->path);
532 free(handle);
533}
#define TRACE_FILE_PATH_MAX
void TRACE_FILE_UTIL_API trace_file_provider_dispose(trace_file_provider_t *handle)
ファイルトレースプロバイダを終了する。
int TRACE_FILE_UTIL_API trace_file_provider_write(trace_file_provider_t *handle, int level, const char *message)
ファイルへトレースメッセージを書き込む。
#define TRACE_FILE_TS_LEN
タイムスタンプ部分の文字数 ("YYYY-MM-DD HH:MM:SS.mmm" = 23 文字)。
#define TRACE_FILE_LINE_BUF
1 行分のスタックバッファサイズ。
static void close_file(trace_file_provider_t *p)
開いているファイルを閉じる。未開の場合は何もしない (冪等)。
#define TRACE_FILE_SUFFIX_MAX
ローテーションパスのサフィックス最大長 (".999\0" = 5 文字)。
static int open_file_truncate(trace_file_provider_t *p)
ローテーション後の新規ファイルを空で作成して開く。 current_bytes は必ず 0 に設定される。
static void format_timestamp(char *buf, int buf_size)
現在時刻を "YYYY-MM-DD HH:MM:SS.mmm" (UTC) 形式でバッファへ書き込む。
#define FILE_LOCK_TIMEOUT_MS
ファイル書き込みロック取得のタイムアウト (ミリ秒)。
static int open_file(trace_file_provider_t *p)
ファイルを追記モードで開き current_bytes を初期サイズで初期化する。
static void rotate_file(trace_file_provider_t *p)
トレースファイルをローテーションする。
static char level_char(int level)
トレースレベル整数をレベル文字に変換する。
trace_file_provider_t *TRACE_FILE_UTIL_API trace_file_provider_init(const char *path, size_t max_bytes, int generations)
ファイルトレースプロバイダを初期化する。
ファイルトレースプロバイダハンドル構造体 (内部定義)。
int fd
ファイルディスクリプタ。-1 = 未開。
char * path
ヒープ確保済みファイルパス文字列。
size_t max_bytes
ファイル 1 世代あたりの最大バイト数。
pthread_mutex_t mutex
スレッド安全のための mutex。
int generations
保持する旧世代数。
int mutex_initialized
mutex が初期化済みかどうかのフラグ。
int _pad_end
パディング (構造体サイズを 8 バイト境界に揃える)。
size_t current_bytes
現ファイルへの書き込み済みバイト数 (インメモリ追跡)。
ファイルベーストレースプロバイダライブラリ。
#define TRACE_FILE_LV_ERROR
エラー (trace-util.h の TRACE_LV_ERROR と同値)。
#define TRACE_FILE_DEFAULT_GENERATIONS
保持するトレースファイル世代数の既定値。
#define TRACE_FILE_LV_INFO
情報 (trace-util.h の TRACE_LV_INFO と同値)。
#define TRACE_FILE_LV_WARNING
警告 (trace-util.h の TRACE_LV_WARNING と同値)。
#define TRACE_FILE_DEFAULT_MAX_BYTES
トレースファイル 1 世代あたりの既定最大サイズ (バイト)。
#define TRACE_FILE_UTIL_API
呼び出し規約マクロ。
struct trace_file_provider trace_file_provider_t
ファイルトレースプロバイダハンドル (不透明型)。
#define TRACE_FILE_LV_CRITICAL
致命的エラー (trace-util.h の TRACE_LV_CRITICAL と同値)。