// list.c // Copyright : 2022-10-14 Yutaka Sawada // License : GPL #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 "common2.h" #include "md5_crc.h" #include "verify.h" #include "ini.h" #include "json.h" #include "list.h" //#define TIMER // 実験用 // recovery set のファイルのハッシュ値を調べる (空のファイルは除く) // 0x00 = ファイルが存在して完全である // 0x01 = ファイルが存在しない // 0x02 = ファイルが破損してる // 0x10 = ファイルに追加されてる static int check_file_hash( char *ascii_buf, // ファイル名が入ってる wchar_t *file_path, // 作業用 int num, // file_ctx におけるファイル番号 file_ctx_r *files, // 各ソース・ファイルの情報 source_ctx_r *s_blk) // 各ソース・ブロックの情報 { int i, bad_flag = 0; unsigned int meta_data[7]; __int64 file_size; // 存在するファイルのサイズは本来のサイズとは異なることもある HANDLE hFile; // ファイルが存在するか wcscpy(file_path, base_dir); wcscpy(file_path + base_len, list_buf + files[num].name); /* // ディスク・キャッシュを使わない場合の実験用 i = file_hash_direct(num, file_path, list_buf + files[num].name, files, s_blk); printf("file_hash_direct = %d\n", i); if (i == -2) return 2; // 検査中にキャンセルされた */ hFile = CreateFile(file_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OVERLAPPED, NULL); if (hFile == INVALID_HANDLE_VALUE){ // ファイルが存在しない bad_flag = 1; } else { // ファイルの状態が記録されてるか調べる i = check_ini_state(num, meta_data, hFile); if (i == -1){ // エラー CloseHandle(hFile); bad_flag = 1; // 属性を取得できないファイルは消失と判定する } } if (bad_flag == 0){ // ファイルが存在するなら状態を確認する memcpy(&file_size, meta_data, 8); if (file_size >= files[num].size){ // 本来のサイズまでのハッシュ値が一致するか確かめる if (i == -2){ // 検査結果の記録が無ければ i = file_hash_check(num, list_buf + files[num].name, hFile, 0, files, s_blk); if (i == -2) return 2; // 検査中にキャンセルされた // MD5-16k が一致した時だけ検査結果を記録する if (i != -1) write_ini_state(num, meta_data, i); } if (i != -3){ // ファイルのハッシュ値が異なる bad_flag = 2; // IO エラーでも破損扱いにして処理を続行する if (i > 0) // 先頭に完全なブロックが見つかってるなら bad_flag |= i << 8; // 上位 24-bit に完全なブロックの個数を記録する // MD5-16k だけ異なるのは検出ブロック 0個として扱う } else if (file_size > files[num].size){ // 本来のサイズまでのハッシュ値は同じ bad_flag = 16; // 末尾にゴミが付いてないか調べる } } else { // file_size < files[num].size bad_flag = 2; } CloseHandle(hFile); } files[num].state = bad_flag; if (bad_flag == 1){ // 消失 printf(" - Missing : \"%s\"\n", ascii_buf); } else { // 完全、破損または追加 (追加は特殊な破損状態) int b_last; if (bad_flag & 2){ // 破損なら上位 24-bit に完全なブロックの個数が記録されてる bad_flag >>= 8; b_last = files[num].b_off + bad_flag; // 途中まで有効と見なす for (i = files[num].b_off; i < b_last; i++) s_blk[i].exist = 2; first_num += bad_flag; // ブロック数を集計する print_progress_file(-1, first_num, NULL); // 重複を除外した合計ブロック数を表示する printf("%13I64d %8d : \"%s\"\n", file_size, bad_flag, ascii_buf); } else { // 完全か追加 b_last = files[num].b_off + files[num].b_num; for (i = files[num].b_off; i < b_last; i++) s_blk[i].exist = 1; // 全ブロックが有効と見なす first_num += files[num].b_num; // ブロック数を集計する print_progress_file(-1, first_num, NULL); // 重複を除外した合計ブロック数を表示する if (bad_flag == 0){ // サイズが同じでハッシュ値が一致したら完全 printf(" = Complete : \"%s\"\n", ascii_buf); } else { // 本来のサイズまでハッシュ値が一致したら追加 printf("%13I64d Appended : \"%s\"\n", file_size, ascii_buf); } } } return 0; } // ブロックのチェックサムが欠落してるファイルを検査する // 0x80 = ファイルが存在して完全である (全てのブロックが完全とみなす) // 0x81 = ファイルが存在しない // 0x82 = ファイルが破損してる // 0x90 = ファイルに追加されてる (全てのブロックが完全とみなす) int check_file_hash_incomplete( char *ascii_buf, // ファイル名が入ってる wchar_t *file_path, // 作業用 int num, // file_ctx におけるファイル番号 file_ctx_r *files, // 各ソース・ファイルの情報 source_ctx_r *s_blk) // 各ソース・ブロックの情報 { int i, bad_flag = 0; unsigned int meta_data[7]; __int64 file_size; // 存在するファイルのサイズは本来のサイズとは異なることもある HANDLE hFile; // ファイルが存在するか wcscpy(file_path, base_dir); wcscpy(file_path + base_len, list_buf + files[num].name); hFile = CreateFile(file_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OVERLAPPED, NULL); if (hFile == INVALID_HANDLE_VALUE){ // ファイルが存在しない bad_flag = 1; } else { // ファイルの状態が記録されてるか調べる i = check_ini_state(num, meta_data, hFile); if (i == -1){ // エラー CloseHandle(hFile); bad_flag = 1; // 属性を取得できないファイルは消失と判定する } } if (bad_flag == 0){ // ファイルが存在するなら状態を確認する memcpy(&file_size, meta_data, 8); if (file_size >= files[num].size){ // 本来のサイズまでのハッシュ値が一致するか確かめる if (i == -2){ // 検査結果の記録が無ければ i = file_hash_check(num, list_buf + files[num].name, hFile, INT_MAX, files, NULL); if (i == -2) return 2; // 検査中にキャンセルされた // MD5-16k が一致した時だけ検査結果を記録する if (i != -1) write_ini_state(num, meta_data, i); } if (i != -3){ // ファイルのハッシュ値が異なる bad_flag = 2; // IO エラーでも破損扱いにして処理を続行する } else if (file_size > files[num].size){ // 本来のサイズまでのハッシュ値は同じ bad_flag = 16; // 末尾にゴミが付いてないか調べる } } else { // file_size < files[num].size bad_flag = 2; } CloseHandle(hFile); } files[num].state = bad_flag | 0x80; // チェックサムが存在しない印 if (bad_flag == 1){ // 消失 printf(" - Missing : \"%s\"\n", ascii_buf); } else { // 完全、破損または追加 (追加は特殊な破損状態) if (bad_flag & 2){ // ブロック単位の検査ができないので全て破損として扱う printf("%13I64d Damaged : \"%s\"\n", file_size, ascii_buf); } else { // 完全か追加 // ブロック単位の検査はできないがとりあえず有効として扱う int b_last; b_last = files[num].b_off + files[num].b_num; for (i = files[num].b_off; i < b_last; i++) s_blk[i].exist = 1; // 全ブロックが有効と見なす first_num += files[num].b_num; // ブロック数を集計する print_progress_file(-1, first_num, NULL); // 重複を除外した合計ブロック数を表示する if (bad_flag == 0){ printf(" = Complete : \"%s\"\n", ascii_buf); } else { printf("%13I64d Appended : \"%s\"\n", file_size, ascii_buf); } } } return 0; } // 空ファイルやフォルダが存在するか調べる // non-recovery set のファイルが存在して破損してないかも調べる // 0 = ファイルが存在して完全である // 1 = ファイルが存在しない // 2 = ファイルが破損してる // 16 = ファイルに追加されてる // 64 = フォルダが存在する // 65 = フォルダが存在しない static int check_file_exist( char *ascii_buf, // ファイル名が入ってる wchar_t *file_path, // 作業用 int num, // file_ctx におけるファイル番号 file_ctx_r *files) // 各ソース・ファイルの情報 { int i, bad_flag = 0; unsigned int meta_data[7]; __int64 file_size; HANDLE hFile; wcscpy(file_path, base_dir); wcscpy(file_path + base_len, list_buf + files[num].name); i = (int)wcslen(file_path); if ((i > 1) && (file_path[i - 1] == '\\')){ // フォルダなら bad_flag = 64; i = GetFileAttributes(file_path); if ((i == INVALID_FILE_ATTRIBUTES) || ((i & FILE_ATTRIBUTE_DIRECTORY) == 0)){ bad_flag = 65; // フォルダが存在しない printf(" - Missing : \"%s\"\n", ascii_buf); } else { // フォルダが存在する printf(" 0 Complete : \"%s\"\n", ascii_buf); } files[num].state = bad_flag; return 0; } // ファイルが存在するか hFile = CreateFile(file_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OVERLAPPED, NULL); if (hFile == INVALID_HANDLE_VALUE){ // ファイルが存在しない bad_flag = 1; } else { // ファイルのサイズを調べる i = check_ini_state(num, meta_data, hFile); if (i == -1){ // エラー CloseHandle(hFile); bad_flag = 1; // 属性を取得できないファイルは消失と判定する } } if (bad_flag == 0){ // ファイルが存在するなら状態を確認する memcpy(&file_size, meta_data, 8); if (files[num].size == 0){ // サイズが 0 だと破損しようがない if (file_size > 0) // 末尾にゴミが付いてる bad_flag = 16; } else if (file_size >= files[num].size){ // 本来のサイズまでのハッシュ値が一致するか確かめる if (i == -2){ // 検査結果の記録が無ければ i = file_hash_check(num, list_buf + files[num].name, hFile, INT_MAX, files, NULL); if (i == -2) return 2; // 検査中にキャンセルされた // MD5-16k が一致した時だけ検査結果を記録する if (i != -1) write_ini_state(num, meta_data, i); } if (i != -3){ // ファイルのハッシュ値が異なる bad_flag = 2; // IO エラーでも破損扱いにして処理を続行する } else if (file_size > files[num].size){ // 本来のサイズまでのハッシュ値は同じ bad_flag = 16; // 末尾にゴミが付いてないか調べる } } else { // file_size < files[num].size bad_flag = 2; } CloseHandle(hFile); } files[num].state = bad_flag; switch (bad_flag){ case 0: // 完全なファイル printf(" = Complete : \"%s\"\n", ascii_buf); break; case 1: // 消失したファイル printf(" - Missing : \"%s\"\n", ascii_buf); break; case 2: // 破損したファイル printf("%13I64d Damaged : \"%s\"\n", file_size, ascii_buf); break; case 16: // 追加されたファイル printf("%13I64d Appended : \"%s\"\n", file_size, ascii_buf); break; } return 0; } // ソース・ファイルが完全かどうかを調べる // 0=完了, 1=エラー, 2=キャンセル int check_file_complete( char *ascii_buf, wchar_t *file_path, // 作業用 file_ctx_r *files, // 各ソース・ファイルの情報 source_ctx_r *s_blk) // 各ソース・ブロックの情報 { int i, rv; #ifdef TIMER unsigned int time_start = GetTickCount(); #endif printf("\nVerifying Input File :\n"); printf(" Size Status : Filename\n"); fflush(stdout); count_last = 0; first_num = 0; // 初めて見つけたソース・ブロックの数 for (i = 0; i < entity_num; i++){ if (cancel_progress() != 0) // キャンセル処理 return 2; utf16_to_cp(list_buf + files[i].name, ascii_buf, cp_output); if (files[i].size == 0){ // フォルダまたは空ファイルが存在するか調べる rv = check_file_exist(ascii_buf, file_path, i, files); } else { if (files[i].state & 0x80){ // チェックサムが欠落してても完全かどうかは判る rv = check_file_hash_incomplete(ascii_buf, file_path, i, files, s_blk); } else { // ソース・ファイルの状態 (完全、消失、破損) を判定する rv = check_file_hash(ascii_buf, file_path, i, files, s_blk); } } if (rv != 0) return rv; fflush(stdout); } for (i = entity_num; i < file_num; i++){ if (cancel_progress() != 0) // キャンセル処理 return 2; utf16_to_cp(list_buf + files[i].name, ascii_buf, cp_output); if (rv = check_file_exist(ascii_buf, file_path, i, files)) return rv; fflush(stdout); } #ifdef TIMER time_start = GetTickCount() - time_start; printf("\n hash %d.%03d sec", time_start / 1000, time_start % 1000); if (time_start > 0){ time_start = (int)((total_file_size * 125) / ((__int64)time_start * 131072)); printf(", %d MB/s\n", time_start); } else { printf("\n"); } #endif return 0; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ // SSD 上で複数ファイルを同時に検査する // MAX_MULTI_READ の2倍ぐらいにする? #define MAX_READ_NUM 10 int check_file_complete_multi( char *ascii_buf, wchar_t *file_path, // 作業用 file_ctx_r *files, // 各ソース・ファイルの情報 source_ctx_r *s_blk) // 各ソース・ブロックの情報 { int err = 0, i, rv; int num, bad_flag, multi_read, multi_num, id, id_prog; unsigned int time_last; __int64 file_size; // 存在するファイルのサイズは本来のサイズとは異なることもある HANDLE hFile; HANDLE hSub[MAX_READ_NUM]; FILE_CHECK_TH th[MAX_READ_NUM]; #ifdef TIMER unsigned int time_start = GetTickCount(); #endif memset(hSub, 0, sizeof(HANDLE) * MAX_READ_NUM); // Core数に応じてスレッド数を増やす if ((memory_use & 32) != 0){ // NVMe SSD if (cpu_num >= 8){ // 8 ~ 16 Cores multi_read = 4; } else { // 3 Cores + Hyper-threading, or 4 ~ 7 Cores multi_read = 3; } } else { // SATA SSD multi_read = 2; } if (multi_read > entity_num) multi_read = entity_num; multi_num = 0; #ifdef TIMER printf("\n cpu_num = %d, entity_num = %d, multi_read = %d\n", cpu_num, entity_num, multi_read); #endif id_prog = -1; for (i = 0; i < MAX_READ_NUM; i++) th[i].num = -1; // データ未使用の印 wcscpy(file_path, base_dir); printf("\nVerifying Input File :\n"); printf(" Size Status : Filename\n"); fflush(stdout); time_last = GetTickCount(); count_last = 0; first_num = 0; // 初めて見つけたソース・ブロックの数 for (num = 0; num < entity_num; num++){ if (cancel_progress() != 0){ // キャンセル処理 err = 2; goto error_end; } // ソース・ファイルの状態 (完全、消失、破損) を判定する // チェックサムが欠落してても完全かどうかは判る if (files[num].size > 0){ for (id = 0; id < MAX_READ_NUM; id++){ // 未使用のスレッドを探す if (th[id].num < 0) break; } if (id_prog < 0){ id_prog = id; prog_last = -1; } th[id].num = num; hSub[id] = NULL; // 検査以外はシングル・スレッドで行う th[id].flag = 0; wcscpy(file_path + base_len, list_buf + files[num].name); hFile = CreateFile(file_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OVERLAPPED, NULL); if (hFile == INVALID_HANDLE_VALUE){ // ファイルが存在しない th[id].flag = 1; } else { // ファイルの状態が記録されてるか調べる th[id].rv = check_ini_state(num, th[id].meta, hFile); if (th[id].rv == -1){ // エラー CloseHandle(hFile); th[id].flag = 1; // 属性を取得できないファイルは消失と判定する } } if (th[id].flag == 0){ // ファイルが存在するなら状態を確認する memcpy(&file_size, th[id].meta, 8); if (file_size >= files[num].size){ // 本来のサイズまでのハッシュ値が一致するか確かめる if (th[id].rv == -2){ // 検査結果の記録が無ければ //rv = file_hash_check(num, list_buf + files[num].name, hFile, 0, files, s_blk); th[id].hFile = hFile; th[id].files = files; if (files[num].state & 0x80){ th[id].flag |= 0x80; th[id].s_blk = NULL; } else { th[id].s_blk = s_blk; } th[id].rv = 0; //_mm_sfence(); // メモリーへの書き込みを完了してからスレッドを起動する hSub[id] = (HANDLE)_beginthreadex(NULL, STACK_SIZE, file_hash_background, (LPVOID)&(th[id]), 0, NULL); if (hSub[id] == NULL){ print_win32_err(); printf("error, sub-thread\n"); CloseHandle(hFile); err = 1; goto error_end; } multi_num++; } else { CloseHandle(hFile); } } else { // file_size < files[num].size CloseHandle(hFile); if (files[num].state & 0x80){ th[id].flag = 2 | 0x80; } else { th[id].flag = 2; } } } //printf("num = %d, multi_num = %d, id = %d, id_prog = %d\n", num, multi_num, id, id_prog); } id = id_prog; //printf("check: num = %d, multi_num = %d, id = %d, id_prog = %d\n", num, multi_num, id, id_prog); while ((id >= 0) && (id < MAX_READ_NUM)){ if (hSub[id]){ // 終了してるか調べる rv = WaitForSingleObject(hSub[id], 0); if (rv == WAIT_OBJECT_0){ // 終了してたら閉じる CloseHandle(hSub[id]); hSub[id] = NULL; multi_num--; } } if (hSub[id] == NULL){ //printf("id = %d, th[id].rv = %d, th[id].flag = %d\n", id, th[id].rv, th[id].flag); bad_flag = th[id].flag & ~0x80; if (bad_flag != 1){ // 消失以外 memcpy(&file_size, th[id].meta, 8); if (bad_flag != 2){ // 破損以外 rv = th[id].rv; // MD5-16k が一致した時だけ検査結果を記録する if (rv != -1) write_ini_state(th[id].num, th[id].meta, rv); if (rv != -3){ // ファイルのハッシュ値が異なる bad_flag = 2; // IO エラーでも破損扱いにして処理を続行する if (rv > 0) // 先頭に完全なブロックが見つかってるなら bad_flag |= rv << 8; // 上位 24-bit に完全なブロックの個数を記録する // MD5-16k だけ異なるのは検出ブロック 0個として扱う } else if (file_size > files[th[id].num].size){ // 本来のサイズまでのハッシュ値は同じ bad_flag = 16; // 末尾にゴミが付いてないか調べる } } } utf16_to_cp(list_buf + files[th[id].num].name, ascii_buf, cp_output); if (th[id].flag & 0x80){ files[th[id].num].state = bad_flag | 0x80; // チェックサムが存在しない印 } else { files[th[id].num].state = bad_flag; } if (bad_flag == 1){ // 消失 printf(" - Missing : \"%s\"\n", ascii_buf); } else { // 完全、破損または追加 (追加は特殊な破損状態) int b_last; if (bad_flag & 2){ // 破損なら上位 24-bit に完全なブロックの個数が記録されてる if (th[id].flag & 0x80){ // チェックサムが存在しない場合 printf("%13I64d Damaged : \"%s\"\n", file_size, ascii_buf); } else { bad_flag >>= 8; b_last = files[th[id].num].b_off + bad_flag; // 途中まで有効と見なす for (i = files[th[id].num].b_off; i < b_last; i++) s_blk[i].exist = 2; first_num += bad_flag; // ブロック数を集計する print_progress_file(-1, first_num, NULL); // 重複を除外した合計ブロック数を表示する printf("%13I64d %8d : \"%s\"\n", file_size, bad_flag, ascii_buf); } } else { // 完全か追加 b_last = files[th[id].num].b_off + files[th[id].num].b_num; for (i = files[th[id].num].b_off; i < b_last; i++) s_blk[i].exist = 1; // 全ブロックが有効と見なす first_num += files[th[id].num].b_num; // ブロック数を集計する print_progress_file(-1, first_num, NULL); // 重複を除外した合計ブロック数を表示する if (bad_flag == 0){ // サイズが同じでハッシュ値が一致したら完全 printf(" = Complete : \"%s\"\n", ascii_buf); } else { // 本来のサイズまでハッシュ値が一致したら追加 printf("%13I64d Appended : \"%s\"\n", file_size, ascii_buf); } } } fflush(stdout); time_last = GetTickCount(); th[id].num = -1; // 未使用に戻す if (id == id_prog) // 経過表示中のスレッドが結果を表示したら id_prog = -1; } // 動作中のスレッドが終了してるか調べる for (id = 0; id < MAX_READ_NUM; id++){ if (th[id].num >= 0){ if (hSub[id]){ rv = WaitForSingleObject(hSub[id], 0); if (rv == WAIT_OBJECT_0){ // 終了してたら閉じる CloseHandle(hSub[id]); hSub[id] = NULL; multi_num--; } } // 経過表示してない状態なら、他の終了したスレッドの結果も表示する if ((id_prog < 0) && (hSub[id] == NULL)){ id_prog = id; //printf("next: num = %d, multi_num = %d, id = %d, id_prog = %d\n", num, multi_num, id, id_prog); break; } } } if (id == MAX_READ_NUM){ // 経過表示中、または、他のスレッドが動いて無いなら if (id_prog < 0){ // 経過表示するスレッドを指定する for (i = 0; i < MAX_READ_NUM; i++){ if (th[i].num >= 0){ id_prog = i; prog_last = -1; break; } } if (id_prog < 0) // 動作中のスレッドがなければ待たない break; } // 最後のファイル、または、同時検査数や最大スレッド数に達してるなら、終わるのを待つ for (i = 0; i < MAX_READ_NUM; i++){ if (th[i].num < 0) break; } if ((num + 1 == entity_num) || (multi_num == multi_read) || (i == MAX_READ_NUM)){ id = id_prog; //printf("wait: num = %d, multi_num = %d, id = %d, id_prog = %d\n", num, multi_num, id, id_prog); if (hSub[id]){ // 終了してるか調べる rv = WaitForSingleObject(hSub[id], UPDATE_TIME / 2); if (rv == WAIT_OBJECT_0){ // 終了してたら閉じる CloseHandle(hSub[id]); hSub[id] = NULL; multi_num--; } // 経過表示 if (GetTickCount() - time_last >= UPDATE_TIME){ // 上位 12-bit にパーセント、下位 20-bit に検出ブロック数が記録されてる i = th[id].rv; rv = i >> 20; i = first_num + (i & 0x000FFFFF); if (print_progress_file(rv, i, list_buf + files[th[id].num].name)){ err = 2; goto error_end; } time_last = GetTickCount(); } } } } } } for (num = 0; num < entity_num; num++){ if (files[num].size > 0) continue; if (cancel_progress() != 0){ // キャンセル処理 err = 2; goto error_end; } // フォルダまたは空ファイルが存在するか調べる utf16_to_cp(list_buf + files[num].name, ascii_buf, cp_output); err = check_file_exist(ascii_buf, file_path, num, files); if (err) goto error_end; fflush(stdout); } for (num = entity_num; num < file_num; num++){ if (cancel_progress() != 0){ // キャンセル処理 err = 2; goto error_end; } utf16_to_cp(list_buf + files[num].name, ascii_buf, cp_output); err = check_file_exist(ascii_buf, file_path, num, files); if (err) goto error_end; fflush(stdout); } #ifdef TIMER time_start = GetTickCount() - time_start; printf("\n hash %d.%03d sec", time_start / 1000, time_start % 1000); if (time_start > 0){ time_start = (int)((total_file_size * 125) / ((__int64)time_start * 131072)); printf(", %d MB/s\n", time_start); } else { printf("\n"); } #endif error_end: if (err != 0){ // エラーが発生した場合 for (i = 0; i < MAX_READ_NUM; i++) // 終了指示をだす InterlockedExchange(&(th[i].flag), -1); } for (i = 0; i < MAX_READ_NUM; i++){ if (hSub[i]){ WaitForSingleObject(hSub[i], INFINITE); CloseHandle(hSub[i]); } } return err; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ // 指定されたフォルダが空かどうかを調べる static int is_folder_empty(wchar_t *folder_path) // フォルダのパス { int len; HANDLE hFind; WIN32_FIND_DATA FindData; len = (int)wcslen(folder_path); folder_path[len ] = '*'; folder_path[len + 1] = 0; hFind = FindFirstFile(folder_path, &FindData); folder_path[len ] = 0; // 元に戻す if (hFind != INVALID_HANDLE_VALUE){ do { if ((wcscmp(FindData.cFileName, L".") != 0) && (wcscmp(FindData.cFileName, L"..") != 0)){ len = 0; // 空ではない break; } } while (FindNextFile(hFind, &FindData)); } FindClose(hFind); return len; } // 指定フォルダ内の別名ファイルを探す // 0=正常終了, 1=エラー, 2=キャンセル static int recursive_search( char *ascii_buf, // 作業用 wchar_t *search_path, // 検索するファイルの親ディレクトリ、見つけた別名ファイルの相対パスが戻る int dir_len, // ディレクトリ部分の長さ file_ctx_r *files, // 各ソース・ファイルの情報 source_ctx_r *s_blk) // 各ソース・ブロックの情報 { __int64 file_size; // 存在するファイルのサイズは本来のサイズとは異なることもある int rv, i, num, b_last, source_len; unsigned int meta_data[7]; HANDLE hFile, hFind; WIN32_FIND_DATA FindData; // ソース・ファイル名の領域までしか比較しない (別名ファイルが検出されるのは一回だけ) source_len = list_len - 1; // 指定されたディレクトリ直下のファイルと比較する wcscpy(search_path + dir_len, L"*"); hFind = FindFirstFile(search_path, &FindData); if (hFind != INVALID_HANDLE_VALUE){ do { if (cancel_progress() != 0){ // キャンセル処理 FindClose(hFind); return 2; } if ((FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0){ // フォルダなら // 親ディレクトリは無視する if ((wcscmp(FindData.cFileName, L".") == 0) || (wcscmp(FindData.cFileName, L"..") == 0)) continue; // 発見したフォルダ名が長すぎる場合は無視する if (dir_len + wcslen(FindData.cFileName) + 2 >= MAX_LEN) // 末尾に「\*」が追加される continue; // 現在のディレクトリ部分に見つかったフォルダ名を連結する wcscpy(search_path + dir_len, FindData.cFileName); wcscat(search_path + dir_len, L"\\"); // 他のソース・ファイルは無視する if (search_file_path(list_buf + 1, source_len, search_path + base_len)) // 基準ディレクトリからの相対パスで比較する continue; // フォルダ名が検出対象と一致するかどうか確かめる for (num = entity_num; num < file_num; num++){ // 空フォルダは non-recovery set にしか存在しない if (files[num].state == 65){ // 消失したフォルダなら if (_wcsicmp(search_path + dir_len, offset_file_name(list_buf + files[num].name)) == 0){ // 他のファイルのサブ・ディレクトリは動かせないので、中身のあるフォルダは無視する if (is_folder_empty(search_path) == 0) continue; // 移動されたフォルダが見つかった files[num].name2 = list_len; // 移動されたフォルダ名を記録する if (add_file_path(search_path + base_len)){ printf("add_file_path\n"); FindClose(hFind); return 1; } utf16_to_cp(search_path + base_len, ascii_buf, cp_output); // 移動されたフォルダ名を表示する printf(" 0 Found : \"%s\"\n", ascii_buf); files[num].state = 64 | 32; // 移動されたフォルダ utf16_to_cp(list_buf + files[num].name, ascii_buf, cp_output); // 本来のフォルダ名を表示する printf(" = Moved : \"%s\"\n", ascii_buf); break; // 見つかった場合は、それ以降のソース・ファイルと比較しない } } } continue; } // 発見したファイル名が長すぎる場合は無視する if (dir_len + wcslen(FindData.cFileName) >= MAX_LEN) continue; // 現在のディレクトリ部分に見つかったファイル名を連結する wcscpy(search_path + dir_len, FindData.cFileName); // 破損してないリカバリ・ファイルは無視する if (search_file_path(recv_buf, recv_len, search_path)) // フル・パスで比較する continue; // 他のソース・ファイルは無視する if (search_file_path(list_buf + 1, source_len, search_path + base_len)) // 基準ディレクトリからの相対パスで比較する continue; // ファイル・サイズが検出対象のサイズと一致するかどうか file_size = ((__int64)(FindData.nFileSizeHigh) << 32) | (__int64)(FindData.nFileSizeLow); for (num = 0; num < file_num; num++){ if ((files[num].state & 0x03) && (files[num].size == file_size)){ if (file_size > 0){ //printf("find size %I64d, %S\n", file_size, search_path + base_len); // 別名ファイルのハッシュ値を調べる hFile = CreateFile(search_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OVERLAPPED, NULL); if (hFile != INVALID_HANDLE_VALUE){ // ファイルを開けたなら内容を確認する prog_last = -1; // 経過表示がまだの印 rv = check_ini_state(num, meta_data, hFile); if (rv == -2){ // 検査結果の記録が無ければ if ((num >= entity_num) || ((files[num].state & 0x80) != 0)){ // non-recovery set のファイルまたはチェックサムが欠落したソース・ファイル rv = file_hash_check(num, FindData.cFileName, hFile, INT_MAX, files, NULL); } else { // 普通のソース・ファイル if (files[num].state & 0x02){ // 本来のファイルが破損ならスライスの重複カウントを避ける rv = files[num].state >> 8; } else { rv = 0; } rv = file_hash_check(num, FindData.cFileName, hFile, rv, files, s_blk); } if (rv == -2){ CloseHandle(hFile); FindClose(hFind); return 2; // エラーは無視して続行するが、キャンセルなら中断する } // MD5-16k が一致した時だけ検査結果を記録する if (rv != -1) write_ini_state(num, meta_data, rv); } CloseHandle(hFile); if (rv == -3){ // ファイルのハッシュ値が一致した b_last = files[num].b_off + files[num].b_num; for (i = files[num].b_off; i < b_last; i++) s_blk[i].exist = 1; // 全ブロックが有効と見なす files[num].name2 = list_len; // 別名・移動のファイル名を記録する if (add_file_path(search_path + base_len)){ printf("add_file_path\n"); FindClose(hFind); return 1; } if (files[num].state & 0x01){ // 本来のファイルが消失してるなら files[num].state ^= 0x21; // 消失して別名・移動 0x01 -> 0x20 first_num += files[num].b_num; // ブロック数を集計する } else { files[num].state ^= 0x2A; // 破損して別名・移動 0x02 -> 0x28 (スライス個数は維持する) first_num += files[num].b_num - (files[num].state >> 8); // 新たなブロック数だけ追加する } print_progress_file(-1, first_num, NULL); // 重複を除外した利用可能なソース・ブロック数を表示する utf16_to_cp(search_path + base_len, ascii_buf, cp_output); // 別名・移動のファイル名を表示する printf("%13I64d Found : \"%s\"\n", file_size, ascii_buf); utf16_to_cp(list_buf + files[num].name, ascii_buf, cp_output); // 本来のファイル名を表示する if (compare_directory(list_buf + files[num].name, search_path + base_len) == 0){ // 同じ場所なら別名 printf(" = Misnamed : \"%s\"\n", ascii_buf); } else { // 別の場所なら移動 printf(" = Moved : \"%s\"\n", ascii_buf); } break; // 見つかった場合は、それ以降のソース・ファイルと比較しない } else { if (rv > 0){ // 別名の破損ファイル内にスライスを見つけたら、自動的に検査対象に追加する //printf("rv = %d, %S\n", rv, search_path + base_len); if (list2_buf == NULL){ list2_len = 0; list2_max = ALLOC_LEN; list2_buf = (wchar_t *)malloc(list2_max * 2); } if (list2_buf){ wchar_t *tmp_p; int len = (int)wcslen(search_path); if (!search_file_path(list2_buf, list2_len, search_path)){ // ファイル名が重複しないようにする if (list2_len + len >= list2_max){ // 領域が足りなくなるなら拡張する list2_max += ALLOC_LEN; tmp_p = (wchar_t *)realloc(list2_buf, list2_max * 2); if (tmp_p == NULL){ printf("realloc, %d\n", list2_max); FindClose(hFind); return 1; } else { list2_buf = tmp_p; } } // そのファイルを追加する //printf_cp("add external file, %s\n", search_path); wcscpy(list2_buf + list2_len, search_path); list2_len += len + 1; } } if (prog_last >= 0) // 途中までのスライス数を表示してた場合 print_progress_file(-1, first_num, NULL); // 元の数に戻しておく } print_progress_done(); // 経過表示があれば 100% にしておく } } } else { // サイズが 0 (空ファイル) の場合 if (files[num].state & 64) continue; // 比較対象がフォルダなら無視する (ファイル名の比較ではねられるから不要?) // ファイル名が同じかどうか確かめる if (_wcsicmp(FindData.cFileName, offset_file_name(list_buf + files[num].name)) == 0){ // 移動されたファイルが見つかった files[num].name2 = list_len; // 移動されたファイル名を記録する if (add_file_path(search_path + base_len)){ printf("add_file_path\n"); FindClose(hFind); return 1; } utf16_to_cp(search_path + base_len, ascii_buf, cp_output); // 移動されたファイル名を表示する printf(" 0 Found : \"%s\"\n", ascii_buf); files[num].state = 0x20; // 空ファイルは破損しないので、消失して移動のみ utf16_to_cp(list_buf + files[num].name, ascii_buf, cp_output); // 本来のファイル名を表示する printf(" = Moved : \"%s\"\n", ascii_buf); break; // 見つかった場合は、それ以降のソース・ファイルと比較しない } } } } } while (FindNextFile(hFind, &FindData)); // 次のファイルを検索する } FindClose(hFind); // 指定されたディレクトリ以下のフォルダ内も再帰的に調べる wcscpy(search_path + dir_len, L"*"); hFind = FindFirstFile(search_path, &FindData); if (hFind != INVALID_HANDLE_VALUE){ do { // フォルダ以外は無視する if ((FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) continue; // 親ディレクトリは無視する if ((wcscmp(FindData.cFileName, L".") == 0) || (wcscmp(FindData.cFileName, L"..") == 0)) continue; // 発見したフォルダ名が長すぎる場合は無視する if (dir_len + wcslen(FindData.cFileName) + 2 >= MAX_LEN) // 末尾に「\*」が追加される continue; // そのフォルダの内部を新たな検索対象にする wcscpy(search_path + dir_len, FindData.cFileName); wcscat(search_path + dir_len, L"\\"); // ディレクトリ記号は「\」に統一してある //printf_cp("inner search... %s\n", search_path + base_len); rv = recursive_search(ascii_buf, search_path, (int)wcslen(search_path), files, s_blk); if (rv != 0){ // エラー発生、またはキャンセルされた FindClose(hFind); return rv; } } while (FindNextFile(hFind, &FindData)); // 次のフォルダを検索する } FindClose(hFind); return 0; } // フォルダを無視して、指定フォルダ内の別名ファイルを探す(サブ・フォルダに移動したファイルは検出不能) // 0=正常終了, 1=エラー, 2=キャンセル static int direct_search( char *ascii_buf, // 作業用 wchar_t *search_path, // 検索するファイルの親ディレクトリ、見つけた別名ファイルの相対パスが戻る int dir_len, // ディレクトリ部分の長さ file_ctx_r *files, // 各ソース・ファイルの情報 source_ctx_r *s_blk) // 各ソース・ブロックの情報 { __int64 file_size; // 存在するファイルのサイズは本来のサイズとは異なることもある int rv, i, num, b_last, source_len; unsigned int meta_data[7]; HANDLE hFile, hFind; WIN32_FIND_DATA FindData; // ソース・ファイル名の領域までしか比較しない (別名ファイルが検出されるのは一回だけ) source_len = list_len - 1; // 指定されたディレクトリ直下のファイルとだけ比較する wcscpy(search_path + dir_len, L"*"); hFind = FindFirstFile(search_path, &FindData); if (hFind != INVALID_HANDLE_VALUE){ do { if (cancel_progress() != 0){ // キャンセル処理 FindClose(hFind); return 2; } if ((FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) // フォルダなら無視する continue; // 発見したファイル名が長すぎる場合は無視する if (dir_len + wcslen(FindData.cFileName) >= MAX_LEN) continue; // 現在のディレクトリ部分に見つかったファイル名を連結する wcscpy(search_path + dir_len, FindData.cFileName); // 破損してないリカバリ・ファイルは無視する if (search_file_path(recv_buf, recv_len, search_path)) // フル・パスで比較する continue; // 他のソース・ファイルは無視する if (search_file_path(list_buf + 1, source_len, search_path + base_len)) // 基準ディレクトリからの相対パスで比較する continue; // ファイル・サイズが検出対象のサイズと一致するかどうか file_size = ((__int64)(FindData.nFileSizeHigh) << 32) | (__int64)(FindData.nFileSizeLow); for (num = 0; num < file_num; num++){ if ((files[num].state & 0x03) && (files[num].size == file_size)){ if (file_size > 0){ // サイズが 0 (空ファイル) でなければ //printf("find size %I64d, %S\n", file_size, search_path + base_len); // 別名ファイルのハッシュ値を調べる hFile = CreateFile(search_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OVERLAPPED, NULL); if (hFile != INVALID_HANDLE_VALUE){ // ファイルを開けたなら内容を確認する prog_last = -1; // 経過表示がまだの印 rv = check_ini_state(num, meta_data, hFile); if (rv == -2){ // 検査結果の記録が無ければ if ((num >= entity_num) || ((files[num].state & 0x80) != 0)){ // non-recovery set のファイルまたはチェックサムが欠落したソース・ファイル rv = file_hash_check(num, FindData.cFileName, hFile, INT_MAX, files, NULL); } else { // 普通のソース・ファイル if (files[num].state & 0x02){ // 本来のファイルが破損ならスライスの重複カウントを避ける rv = files[num].state >> 8; } else { rv = 0; } rv = file_hash_check(num, FindData.cFileName, hFile, rv, files, s_blk); } if (rv == -2){ CloseHandle(hFile); FindClose(hFind); return 2; // エラーは無視して続行するが、キャンセルなら中断する } // MD5-16k が一致した時だけ検査結果を記録する if (rv != -1) write_ini_state(num, meta_data, rv); } CloseHandle(hFile); if (rv == -3){ // ファイルのハッシュ値が一致した b_last = files[num].b_off + files[num].b_num; for (i = files[num].b_off; i < b_last; i++) s_blk[i].exist = 1; // 全ブロックが有効と見なす files[num].name2 = list_len; // 別名・移動のファイル名を記録する if (add_file_path(search_path + base_len)){ printf("add_file_path\n"); FindClose(hFind); return 1; } if (files[num].state & 0x01){ // 本来のファイルが消失してるなら files[num].state ^= 0x21; // 消失して別名・移動 0x01 -> 0x20 first_num += files[num].b_num; // ブロック数を集計する } else { files[num].state ^= 0x2A; // 破損して別名・移動 0x02 -> 0x28 (スライス個数は維持する) first_num += files[num].b_num - (files[num].state >> 8); // 新たなブロック数だけ追加する } print_progress_file(-1, first_num, NULL); // 重複を除外した利用可能なソース・ブロック数を表示する utf16_to_cp(search_path + base_len, ascii_buf, cp_output); // 別名・移動のファイル名を表示する printf("%13I64d Found : \"%s\"\n", file_size, ascii_buf); utf16_to_cp(list_buf + files[num].name, ascii_buf, cp_output); // 本来のファイル名を表示する if (compare_directory(list_buf + files[num].name, search_path + base_len) == 0){ // 同じ場所なら別名 printf(" = Misnamed : \"%s\"\n", ascii_buf); } else { // 別の場所なら移動 printf(" = Moved : \"%s\"\n", ascii_buf); } break; // 見つかった場合は、それ以降のソース・ファイルと比較しない } else { if (rv > 0){ // 別名の破損ファイル内にスライスを見つけたら、自動的に検査対象に追加する //printf("rv = %d, %S\n", rv, search_path + base_len); if (list2_buf == NULL){ list2_len = 0; list2_max = ALLOC_LEN; list2_buf = (wchar_t *)malloc(list2_max * 2); } if (list2_buf){ wchar_t *tmp_p; int len = (int)wcslen(search_path); if (!search_file_path(list2_buf, list2_len, search_path)){ // ファイル名が重複しないようにする if (list2_len + len >= list2_max){ // 領域が足りなくなるなら拡張する list2_max += ALLOC_LEN; tmp_p = (wchar_t *)realloc(list2_buf, list2_max * 2); if (tmp_p == NULL){ printf("realloc, %d\n", list2_max); FindClose(hFind); return 1; } else { list2_buf = tmp_p; } } // そのファイルを追加する //printf_cp("add external file, %s\n", search_path); wcscpy(list2_buf + list2_len, search_path); list2_len += len + 1; } } if (prog_last >= 0) // 途中までのスライス数を表示してた場合 print_progress_file(-1, first_num, NULL); // 元の数に戻しておく } print_progress_done(); // 経過表示があれば 100% にしておく } } } } } } while (FindNextFile(hFind, &FindData)); // 次のファイルを検索する } FindClose(hFind); return 0; } // ソース・ファイルが不完全なら別名・移動ファイルを探す // 0=完了, 1=エラー, 2=キャンセル int search_misnamed_file( char *ascii_buf, // 作業用 wchar_t *file_path, // 作業用 file_ctx_r *files, // 各ソース・ファイルの情報 source_ctx_r *s_blk) // 各ソース・ブロックの情報 { wchar_t find_path[MAX_LEN]; int i, rv, num, b_last; // 順列検査でファイルが破損してれば上書修復する(作業ファイルを作らない) if (switch_v & 4){ for (num = 0; num < entity_num; num++){ if (files[num].state & 2) files[num].state |= 4; } } // 不完全なソース・ファイルが存在するか調べる i = 0; // 消失 0x01, 0x81, フォルダの消失 0x41 も rv = 0; // 破損 0x02, 0x82, 追加 0x10 も b_last = 0; // 消失や破損なら別名ファイルを探す for (num = 0; num < file_num; num++){ if (files[num].state & 0x01) i++; // 消失したファイルの数 if (files[num].state & 0x12) rv++; // 破損・追加のファイルの数 if (files[num].state & 0x03) // 消失 0x01、破損 0x02 なら別名ファイルを探す b_last++; // 探す必要があるファイルの数 } printf("\nComplete file count\t: %d\n", file_num - i - rv); if (b_last == 0){ // 探さない場合はファイル数の集計だけ printf("Misnamed file count\t: %d\n", 0); printf("Damaged file count\t: %d\n", rv); printf("Missing file count\t: %d\n", i); return 0; // 探さないで終わる } // 検査したファイルごとに結果を表示する printf("\nSearching misnamed file: %d\n", b_last); // 探す対象のファイル数 printf(" Size Status : Filename\n"); fflush(stdout); wcscpy(find_path, base_dir); if (switch_v & 8){ // 基準ディレクトリ直下のファイルだけを検査する if (rv = direct_search(ascii_buf, find_path, base_len, files, s_blk)) return rv; } else { // 基準ディレクトリ以下を再帰的に検索する if (rv = recursive_search(ascii_buf, find_path, base_len, files, s_blk)) return rv; } // 状態ごとにファイル数を集計する i = 0; // 消失 0x01, 0x81, フォルダの消失 0x41 も rv = 0; // 破損 0x02, 0x82, 追加 0x10 も b_last = 0; // 消失して別名・移動 0x20, 0xA0, 破損して別名・移動 0x28, 0xA8, フォルダの移動 0x60 for (num = 0; num < file_num; num++){ if (files[num].state & 0x01) i++; // 消失したファイルの数 if (files[num].state & 0x12) rv++; // 破損・追加のファイルの数 if (files[num].state & 0x20) b_last++; // 別名・移動の数 } printf("\n"); printf("Misnamed file count\t: %d\n", b_last); printf("Damaged file count\t: %d\n", rv); printf("Missing file count\t: %d\n", i); return 0; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ // 破損・分割・類似名のファイルから使えるスライスを探す // 0=完了, 1=エラー, 2=キャンセル int search_file_slice( char *ascii_buf, // 作業用 wchar_t *file_path, // 作業用 file_ctx_r *files, // 各ソース・ファイルの情報 source_ctx_r *s_blk) // 各ソース・ブロックの情報 { int rv, num; slice_ctx sc[1]; printf("\nInput File Slice found\t: %d\n", first_num); fflush(stdout); if (first_num > source_num) return 1; // 検出数が多すぎるのはどこか間違ってる if ((first_num == source_num) || (block_size <= 4)) return 0; // 探さないで終わる // スライス検査の準備をする memset(sc, 0, sizeof(slice_ctx)); rv = init_verification(files, s_blk, sc); if (rv < 0) // 致命的なエラーまたはスライス検査不要 return 0; if (rv == 1) // スライド検査不可あるいは不要 switch_v |= 1; // スライド検査しない場合は簡易検査にする //printf("\n init_verification = %d, switch_v = %d\n", rv, switch_v); // 検査したファイルごとに結果を表示する printf("\nFinding available slice:\n"); printf(" Size Status : Filename\n"); fflush(stdout); // 破損してるファイルと分割・類似名ファイルを検査する for (num = 0; num < entity_num; num++){ // 破損 0x02、追加 0x10、破損して別名 0x28 ならソース・ファイルを検査する // 消失 0x01、破損 0x02 で、そのファイルのスライスが未検出なら、分割ファイルを探す // 消失で分割ファイルが見つからない場合だけ、文字化けファイルも探す if ((files[num].state & 0x1B) == 0) continue; if (rv = cancel_progress()) // キャンセル処理 goto error_end; if (rv = check_file_slice(ascii_buf, file_path, num, files, s_blk, sc)) goto error_end; } // 指定された外部ファイルを検査する if ((first_num < source_num) && (list2_buf != NULL) && ((switch_v & 4) == 0)){ if (rv = check_external_file(ascii_buf, file_path, files, s_blk, sc)) goto error_end; } // 簡易検査と順列検査以外なら、そのファイルのスライスが全て検出されていても、分割ファイルを探す num = 0; if (switch_v & (1 | 4)) num = entity_num; for (; num < entity_num; num++){ if (first_num >= source_num) break; if (rv = cancel_progress()) // キャンセル処理 goto error_end; if (rv = search_file_split(ascii_buf, file_path, num, files, s_blk, sc)) goto error_end; } // 基準ディレクトリ内のその他のファイルを検査する if ((first_num < source_num) && ((switch_v & 4) == 0)){ if (rv = search_additional_file(ascii_buf, file_path, files, s_blk, sc)) goto error_end; } // 見つかったソース・ブロックの数 printf("\nInput File Slice found\t: %d\n", first_num); fflush(stdout); if (first_num > source_num){ rv = 1; // 検出数が多すぎるのはどこか間違ってる } else { rv = 0; } json_save_found(); error_end: if (sc->buf){ if (sc->h){ // サブ・スレッドを終了させる sc->size = 0; // 終了指示 SetEvent(sc->run); WaitForSingleObject(sc->h, INFINITE); CloseHandle(sc->h); } free(sc->buf); } if (sc->order) free(sc->order); if (sc->hFile_tmp) CloseHandle(sc->hFile_tmp); if (sc->flk_buf) free(sc->flk_buf); return rv; }