Files
MultiPar/source/sfv_md5/ini.c
2024-11-30 13:08:09 +09:00

415 lines
14 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ini.c
// Copyright : 2024-11-30 Yutaka Sawada
// License : The MIT license
#ifndef _UNICODE
#define _UNICODE
#endif
#ifndef UNICODE
#define UNICODE
#endif
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0601 // Windows 7 or later
#endif
#include <malloc.h>
#include <stdio.h>
#include <windows.h>
#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 はソース・ファイル番号 032767
*/
#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 = 検査結果の再利用機能を無効にする(読み込まないし、記録もしない)
17= 前回の検査結果を読み込んで、今回のを記録する。
指定された期間よりも経過した古い記録は削除される。
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;
}
}