// ini.c // Copyright : 2022-02-16 Yutaka Sawada // License : The MIT license #ifndef _UNICODE #define _UNICODE #endif #ifndef UNICODE #define UNICODE #endif #ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0600 // Windows Vista or later #endif #include #include #include #include "common.h" #include "crc.h" #include "ini.h" /* ファイル名は 0_#.bin (2+32+4 = 38文字) 「#」部分はチェックサム・ファイルを UTF-16 にエンコードした内容の MD5ハッシュ値を 16進数表記にする。 ファイル・フォーマット 2: 検査結果の書式バージョン 4: 6バイト目以降からの CRC-32 チェックサム 4: チェックサム・ファイルの UTF-16 エンコード後の文字数 4: チェックサム・ファイルに記録されてるソース・ファイルの数 前半 12バイトでファイル項目を識別して、後半 18バイトに状態を保存する。 4: ボリュームのシリアル番号 8: ファイルのオブジェクトID 8: ソース・ファイルのサイズ 4: ソース・ファイルの作成日時 4: ソース・ファイルの更新日時 2: 下位 1-bit はファイルの状態 0=完全, 1=破損あるいはエラー 上位 15-bit はソース・ファイル番号 0~32767 */ #define INI_VERSION 0x1270 // 検査結果の書式が決まった時のバージョン #define REUSE_MIN 16384 // ファイル・サイズがこれより大きければ検査結果を利用する (16KB 以上 2GB 未満) #define HEADER_SIZE 14 #define STATE_SIZE 30 #define STATE_READ 136 //#define VERBOSE 1 // 状態を冗長に出力する static HANDLE hIniBin = NULL; // バイナリ・データ用の検査結果ファイル static int ini_off; // 検査結果をどのくらいの期間保存するか int recent_data = 0; /* 0 = 検査結果の再利用機能を無効にする(読み込まないし、記録もしない) 1~7= 前回の検査結果を読み込んで、今回のを記録する。 指定された期間よりも経過した古い記録は削除される。 1= 1日, 2= 3日, 3= 1週間, 4= 半月, 5= 1ヶ月, 6= 1年, 7= 無制限 +8 = 同じセットの記録を削除する、今回の結果は記録する。 他のセットは指定された期間よりも古いものだけ削除する。 */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ // File Time (UTC) を UNIX Time (UTC) に変換する unsigned int time_f_u(FILETIME *file_time) { unsigned __int64 int8; // 1970/01/01 の File Time との差を求める memcpy(&int8, file_time, 8); int8 = (int8 - 116444736000000000) / 10000000; return (unsigned int)int8; } #ifdef VERBOSE // File Time (UTC) を日付の文字列に変換する static void time_f_date(FILETIME *ft, char *buf) { int len; SYSTEMTIME st_utc, st; FileTimeToSystemTime(ft, &st_utc); if (SystemTimeToTzSpecificLocalTime(NULL, &st_utc, &st) == 0) memcpy(&st, &st_utc, sizeof(SYSTEMTIME)); len = GetDateFormatA(LOCALE_USER_DEFAULT, DATE_SHORTDATE | LOCALE_USE_CP_ACP, &st, NULL, buf, 32); if (len > 0){ buf[len - 1] = ' '; len = GetTimeFormatA(LOCALE_USER_DEFAULT, LOCALE_USE_CP_ACP, &st, NULL, buf + len, 32 - len); } if (len == 0){ wsprintfA(buf, "%4d/%02d/%02d %2d:%02d:%02d", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond); } } #endif // 2GB 未満のファイルの開始位置以降のハッシュ値を計算する static unsigned int file_crc_part(HANDLE hFile) { unsigned char buf[4096]; unsigned int len, crc = 0xFFFFFFFF; // 末尾まで読み込む do { if (!ReadFile(hFile, buf, 4096, &len, NULL) || (len == 0)) break; // CRC-32 計算 crc = crc_update(crc, buf, len); } while (len > 0); return crc ^ 0xFFFFFFFF; } // .BIN ファイルの offset バイト目から size バイトのデータを buf に読み込む static int file_read_bin( int offset, unsigned char *buf, unsigned int size) { unsigned int rv; // ファイルの位置を offsetバイト目にする rv = SetFilePointer(hIniBin, offset, NULL, FILE_BEGIN); if (rv == INVALID_SET_FILE_POINTER) return 1; // size バイトを読み込む if ((!ReadFile(hIniBin, buf, size, &rv, NULL)) || (size != rv)) return 1; // 指定サイズを読み込めなかったらエラーになる return 0; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ // 検査結果ファイルを利用する為の関数 // ini_path で指定されてる検査結果ファイルを削除する static void delete_ini_file(void) { // 開いてるなら閉じる if (hIniBin != NULL){ CloseHandle(hIniBin); hIniBin = NULL; } DeleteFile(ini_path); // .bin ファイルを削除する recent_data = 0; // これ以降は検査結果は利用できない } // 検査するチェックサム・ファイルが同じであれば、再検査する必要は無い int check_ini_file(unsigned char *set_hash, unsigned int set_len) { unsigned char set_data[HEADER_SIZE * 2]; wchar_t path[MAX_LEN], ini_name[INI_NAME_LEN + 1]; int i, match; unsigned int file_time, time_now, time_limit; HANDLE hFind; WIN32_FIND_DATA FindData; FILETIME ft; if (recent_data == 0) return 0; // 設定ファイルのパス wsprintf(ini_name, L"0_%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X.bin", set_hash[0], set_hash[1], set_hash[2], set_hash[3], set_hash[4], set_hash[5], set_hash[6], set_hash[7], set_hash[8], set_hash[9], set_hash[10], set_hash[11],set_hash[12], set_hash[13], set_hash[14], set_hash[15]); // 期限を設定する switch (recent_data & 7){ case 1: time_limit = 3600 * 24; // 1 day break; case 2: time_limit = 3600 * 24 * 3; // 3 days break; case 3: time_limit = 3600 * 24 * 7; // 1 week break; case 4: time_limit = 3600 * 24 * 15; // half month break; case 5: time_limit = 3600 * 24 * 30; // 1 month break; case 6: time_limit = 3600 * 24 * 365; // 1 year break; case 7: time_limit = 0xFFFFFFFF; // unlimited break; default: // 期間が指定されてない recent_data = 0; return 0; } GetSystemTimeAsFileTime(&ft); // 現在のシステム時刻 (UTC) を取得する time_now = time_f_u(&ft); // UNIX Time に変換する // 記録の日時を調べて、古いのは削除する match = 0; wcscpy(path, ini_path); wcscat(path, L"0_*.bin"); // 文字数は後でチェックする hFind = FindFirstFile(path, &FindData); if (hFind != INVALID_HANDLE_VALUE){ do { if (((FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) && (wcslen(FindData.cFileName) == INI_NAME_LEN)){ // 指定時間より古い記録は削除する file_time = time_f_u(&(FindData.ftLastWriteTime)); // 更新日時 if (time_now - file_time > time_limit){ wcscpy(path, ini_path); wcscat(path, FindData.cFileName); DeleteFile(path); #if VERBOSE > 1 time_f_date(&(FindData.ftLastWriteTime), (char *)path); printf("Delete result: %S, %s\n", FindData.cFileName, (char *)path); #endif } else if (wcscmp(FindData.cFileName, ini_name) == 0){ if (recent_data & 8){ // 検査結果を読み込まないが、今回のは書き込む場合 // 以前の検査結果が存在すれば削除する wcscpy(path, ini_path); wcscat(path, ini_name); DeleteFile(path); #if VERBOSE > 1 printf("Delete result: %S\n", ini_name); #endif } else { match = 1; // 設定ファイルが存在する } #ifdef VERBOSE ft = FindData.ftLastWriteTime; // 検査結果の更新日時 #endif } } } while (FindNextFile(hFind, &FindData)); // 次のファイルを検索する FindClose(hFind); } // バイナリ・データ用の設定ファイルを開く wcscat(ini_path, ini_name); hIniBin = CreateFile(ini_path, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_FLAG_RANDOM_ACCESS, NULL); if (hIniBin == INVALID_HANDLE_VALUE){ hIniBin = NULL; recent_data = 0; // 開けない場合は再利用しない return 0; } //printf("ini file path = %S\n", ini_path); // 既に同名の (Set ID が同じ) ファイルが存在する場合 set_data[0] = INI_VERSION >> 8; // 検査結果の書式バージョン set_data[1] = INI_VERSION & 0xFF; memset(set_data + 2, 0, 4); // CRC-32 は後で書き込む memcpy(set_data + 6, &set_len, 4); memcpy(set_data + 10, &file_num, 4); if (match != 0){ // ID が同じでも Set 内容が異なる場合は初期化する i = file_read_data(hIniBin, 0, set_data + HEADER_SIZE, HEADER_SIZE); if ((set_data[HEADER_SIZE ] != (INI_VERSION >> 8)) || (set_data[HEADER_SIZE + 1] != (INI_VERSION & 0xFF))){ i = 1; // 古いバージョンの検査結果は参照しない } if (i == 0){ // 検査結果が破損してないか確かめる if (SetFilePointer(hIniBin, 6, 0, FILE_BEGIN) != INVALID_SET_FILE_POINTER) time_now = file_crc_part(hIniBin); i = memcmp(&time_now, set_data + (HEADER_SIZE + 2), 4); } if (i == 0) // チェックサム・ファイルのデータを比較する i = memcmp(set_data + 6, set_data + (HEADER_SIZE + 6), HEADER_SIZE - 6); if (i != 0) // ID が同じでも Set 内容が異なる場合は初期化する match = 0; } // 一致しなかった場合は今回のデータを記録する if (match == 0){ // 今回のデータを書き込む if (file_write_data(hIniBin, 0, set_data, HEADER_SIZE) != 0){ delete_ini_file(); return 0; } if (SetEndOfFile(hIniBin) == 0){ delete_ini_file(); return 0; } return 0; #ifdef VERBOSE } else { time_f_date(&ft, (char *)path); printf("Date of result: %s\n", (char *)path); #endif } return 1; } // 検査結果ファイルを閉じる void close_ini_file(void) { if ((recent_data != 0) && (hIniBin != NULL)){ unsigned int new_crc, old_crc; // 閉じる前に検査結果の CRC-32 を計算して記録しておく FlushFileBuffers(hIniBin); file_read_data(hIniBin, 2, (unsigned char *)&old_crc, 4); new_crc = file_crc_part(hIniBin); if (new_crc != old_crc) // 検査結果が同じなら更新しない file_write_data(hIniBin, 2, (unsigned char *)&new_crc, 4); CloseHandle(hIniBin); hIniBin = NULL; } } // 検査結果が記録されてるかどうか // -1=エラー, -2=記録なし, 0=完全, 2=破損 int check_ini_state( int num, // ファイル番号 unsigned int meta[7], // サイズ、作成日時、更新日時、ボリューム番号、オブジェクト番号 HANDLE hFile) // そのファイルのハンドル { unsigned char buf[STATE_SIZE * STATE_READ]; unsigned int rv, off, state; BY_HANDLE_FILE_INFORMATION fi; // 現在のファイル属性を取得する memset(&fi, 0, sizeof(BY_HANDLE_FILE_INFORMATION)); if (GetFileInformationByHandle(hFile, &fi) != 0){ meta[0] = fi.nFileSizeLow; meta[1] = fi.nFileSizeHigh; if ((recent_data == 0) || ((fi.nFileSizeLow <= REUSE_MIN) && (fi.nFileSizeHigh == 0))) return -2; // 小さなファイルは記録を参照しない meta[2] = time_f_u(&(fi.ftCreationTime)); meta[3] = time_f_u(&(fi.ftLastWriteTime)); meta[4] = fi.dwVolumeSerialNumber; meta[5] = fi.nFileIndexLow; meta[6] = fi.nFileIndexHigh; } else { meta[0] = 0; meta[1] = 0; return -1; // 属性の読み取りエラー } // ヘッダーの直後から開始する if (SetFilePointer(hIniBin, HEADER_SIZE, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER){ delete_ini_file(); return -2; } ini_off = HEADER_SIZE; // ファイル項目の位置 // 一度に STATE_READ 個ずつ読み込む while (ReadFile(hIniBin, buf, STATE_SIZE * STATE_READ, &rv, NULL) != 0){ if (rv < STATE_SIZE) break; // ファイル識別番号から同じファイルの記録を探す for (off = 0; off < rv; off += STATE_SIZE){ if (memcmp(buf + off, meta + 4, 12) == 0){ ini_off += off; // 同じファイルの記録があった state = 0; memcpy(&state, buf + (off + 28), 2); #ifdef VERBOSE printf("check state, num = %d, offset = %d, state = %d\n", num, HEADER_SIZE + off, (state & 1) << 1); #endif // 番号が一致したら、ファイルのサイズと日時を比較する if (((state >> 1) == (num & 0x7FFF)) && (memcmp(buf + (off + 12), meta, 16) == 0)) return (state & 1) << 1; return -2; // 状態が変化してる } } ini_off += rv; } ini_off = 0; return -2; // これ以上の検査記録は無い } // ソース・ファイル状態は完全か破損だけ (消失だと検査しない) void write_ini_state( int num, // ファイル番号 unsigned int meta[7], // サイズ、作成日時、更新日時、ボリューム番号、オブジェクト番号 int state) // 状態、0=完全, 2=破損 { unsigned char buf[STATE_SIZE]; if ((recent_data == 0) || ((meta[0] <= REUSE_MIN) && (meta[1] == 0))) return; // 小さなファイルは検査結果を記録しない if (ini_off == 0){ // 記録が無ければ末尾に追加する ini_off = GetFileSize(hIniBin, NULL); if (ini_off == INVALID_FILE_SIZE){ delete_ini_file(); return; } } // 今回の状態を書き込む memcpy(buf, meta + 4, 12); memcpy(buf + 12, meta, 16); buf[28] = (unsigned char)((state >> 1) | (num << 1)); buf[29] = (unsigned char)(num >> 7); #ifdef VERBOSE printf("write state, num = %d, offset = %d, state = %d\n", num, ini_off, state); #endif if (file_write_data(hIniBin, ini_off, buf, STATE_SIZE) != 0){ delete_ini_file(); return; } }