// par1_cmd.c // Copyright : 2022-02-16 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 "common1.h" #include "par1.h" #include "ini.h" #include "version.h" void print_help(void) { printf( "Usage\n" "c(reate) [f,fu,r,n,p,m,c,d,in,u] [input files]\n" "v(erify) [ vs,vd,d,i,u,b,br] \n" "r(epair) [m,vs,vd,d,i,u,b,br] \n" "l(ist) [u,h] \n" "\nOption\n" " /f : Use file-list instead of files\n" " /fu : Use file-list which is encoded with UTF-8\n" " /r : Rate of redundancy (%%)\n" " /n : Number of parity volumes\n" " /p : First parity volume number\n" " /m : Memory usage\n" " /vs: Skip verification by recent result\n" " /vd\"*\": Set directory of recent result\n" " /c\"*\" : Set comment\n" " /d\"*\" : Set directory of input files\n" " /i : Recreate index file\n" " /in : Do not create index file\n" " /u : Console output is encoded with UTF-8\n" " /b : Backup existing files at repair\n" " /br : Send existing files into recycle bin at repair\n" " /h : List hash value of input files\n" ); } // 動作環境の表示 static void print_environment(void) { MEMORYSTATUSEX statex; // 「\\?\」は常に付加されてるので表示する際には無視する printf_cp("Base Directory\t: \"%s\"\n", base_dir); printf_cp("Recovery File\t: \"%s\"\n", recovery_file); printf("Memory usage\t: "); if (memory_use == 0){ printf("Auto"); } else { printf("%d/8", memory_use); } statex.dwLength = sizeof(statex); if (GlobalMemoryStatusEx(&statex)) printf(" (%I64d MB available)", statex.ullAvailPhys >> 20); printf("\n\n"); } // 格納ファイル・リストを読み込んでバッファーに書き込む static wchar_t * read_list( wchar_t *list_path, // リストのパス int *file_num, // データ・ファイルの数 int *list_len, // ファイル・リストの文字数 int *block_num, // ソース・ブロックの数 __int64 *block_size, // ブロック・サイズ (最大ファイル・サイズ) int switch_f) // 1=CP_OEMCP, 2=CP_UTF8 { char buf[MAX_LEN * 3]; wchar_t *list_buf, file_name[MAX_LEN], file_path[MAX_LEN], *tmp_p; int len, base_len, l_max, l_off = 0; __int64 file_size; FILE *fp; WIN32_FILE_ATTRIBUTE_DATA AttrData; base_len = wcslen(base_dir); if (switch_f == 2){ switch_f = CP_UTF8; } else { switch_f = CP_OEMCP; } l_max = ALLOC_LEN; list_buf = malloc(l_max); if (list_buf == NULL){ printf("malloc, %d\n", l_max); return NULL; } // 読み込むファイルを開く fp = _wfopen(list_path, L"rb"); if (fp == NULL){ utf16_to_cp(list_path, buf); free(list_buf); printf("cannot open file-list, %s\n", buf); return NULL; } // 一行ずつ読み込む while (fgets(buf, MAX_LEN * 3, fp)){ if (ferror(fp)) break; buf[MAX_LEN * 3 - 1] = 0; // 末尾に改行があれば削除する for (len = 0; len < MAX_LEN * 3; len++){ if (buf[len] == 0) break; if ((buf[len] == '\n') || (buf[len] == '\r')){ buf[len] = 0; break; } } if (buf[0] == 0) continue; // 改行だけなら次の行へ // 読み込んだ内容をユニコードに変換する if (!MultiByteToWideChar(switch_f, 0, buf, -1, file_name, MAX_LEN)){ fclose(fp); free(list_buf); printf("MultiByteToWideChar, %s\n", buf); return NULL; } // ファイルが基準ディレクトリ以下に存在することを確認する len = copy_path_prefix(file_path, MAX_LEN - ADD_LEN, file_name, base_dir); // 絶対パスにしてから比較する if (len == 0){ fclose(fp); free(list_buf); printf_cp("filename is invalid, %s\n", file_name); return NULL; } if ((len <= base_len) || (_wcsnicmp(base_dir, file_path, base_len) != 0)){ // 基準ディレクトリ外なら fclose(fp); free(list_buf); printf("out of base-directory, %s\n", buf); return NULL; } wcscpy(file_name, file_path + base_len); if (wcschr(file_name, '\\') != NULL){ // サブ・ディレクトリは拒否する fclose(fp); free(list_buf); printf_cp("sub-directory is not supported, %s\n", file_name); return NULL; } // ファイルが存在するか確かめる if (!GetFileAttributesEx(file_path, GetFileExInfoStandard, &AttrData)){ print_win32_err(); fclose(fp); free(list_buf); printf("input file is not found, %s\n", buf); return NULL; } if (AttrData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY){ fclose(fp); free(list_buf); printf("folder is not supported, %s\n", buf); return NULL; } file_size = ((unsigned __int64)AttrData.nFileSizeHigh << 32) | (unsigned __int64)AttrData.nFileSizeLow; if (*block_size < file_size) *block_size = file_size; // ファイル名が重複しないようにする if (search_file_path(list_buf, l_off, file_name)) continue; // バッファー容量が足りなければ再確保する len = wcslen(file_name); if ((l_off + len) * 2 >= l_max){ l_max += ALLOC_LEN; tmp_p = (wchar_t *)realloc(list_buf, l_max); if (tmp_p == NULL){ free(list_buf); printf("realloc, %d\n", l_max); return NULL; } else { list_buf = tmp_p; } } // リストにコピーする wcscpy(list_buf + l_off, file_name); l_off += (len + 1); *file_num += 1; if (file_size > 0) *block_num += 1; // 空のファイルはブロックの計算に含めない } *list_len = l_off; fclose(fp); /* fp = fopen("list_buf.txt", "wb"); len = 0; while (len < l_off){ fwprintf(fp, L"%s\n", list_buf + len); len += (wcslen(list_buf + len) + 1); } fclose(fp); */ return list_buf; } // ファイルを検索してファイル・リストに追加する static wchar_t * search_files( wchar_t *list_buf, // ファイル・リスト wchar_t *search_path, // 検索するファイルのフル・パス int dir_len, // ディレクトリ部分の長さ int single_file, // -1 = *や?で検索指定、0~ = 単独指定 int *file_num, // データ・ファイルの数 int *list_max, // ファイル・リストの確保サイズ int *list_len, // ファイル・リストの文字数 int *block_num, // ソース・ブロックの数 __int64 *block_size) // ブロック・サイズ (最大ファイル・サイズ) { wchar_t *tmp_p; int len, l_max, l_off; __int64 file_size; HANDLE hFind; WIN32_FIND_DATA FindData; if (list_buf == NULL){ l_off = 0; l_max = ALLOC_LEN; list_buf = malloc(l_max); if (list_buf == NULL){ printf("malloc, %d\n", l_max); return NULL; } } else { l_max = *list_max; l_off = *list_len; } // 検索する hFind = FindFirstFile(search_path, &FindData); if (hFind == INVALID_HANDLE_VALUE) return list_buf; // 見つからなかったらそのまま do { if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) continue; // フォルダは無視する if ((single_file < 0) && (FindData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)) continue; // 検索中は隠し属性が付いてるファイルを無視する // フォルダは無視する、ファイル名が重複しないようにする if (!search_file_path(list_buf, l_off, FindData.cFileName)){ //printf("FindData.cFileName =\n%S\n\n", FindData.cFileName); len = wcslen(FindData.cFileName); if (dir_len + len >= MAX_LEN - ADD_LEN){ FindClose(hFind); free(list_buf); printf("filename is too long\n"); return NULL; } if ((l_off + len) * 2 >= l_max){ // 領域が足りなくなるなら拡張する l_max += ALLOC_LEN; tmp_p = (wchar_t *)realloc(list_buf, l_max); if (tmp_p == NULL){ FindClose(hFind); free(list_buf); printf("realloc, %d\n", l_max); return NULL; } else { list_buf = tmp_p; } } file_size = ((unsigned __int64)FindData.nFileSizeHigh << 32) | (unsigned __int64)FindData.nFileSizeLow; if (*block_size < file_size) *block_size = file_size; // リストにコピーする wcscpy(list_buf + l_off, FindData.cFileName); l_off += len + 1; *file_num += 1; if (file_size > 0) *block_num += 1; // 空のファイルはブロックの計算に含めない } } while (FindNextFile(hFind, &FindData)); // 次のファイルを検索する FindClose(hFind); *list_max = l_max; *list_len = l_off; return list_buf; } // リカバリ・ファイルとソース・ファイルが同じ名前にならないようにする static int check_recovery_match( int file_num, wchar_t *list_buf, int list_len, int switch_p) // インデックス・ファイルを作らない { wchar_t *tmp_p; int i, num, len, list_off = 0, recovery_len, base_len; // 基準ディレクトリ外にリカバリ・ファイルが存在するなら問題なし base_len = wcslen(base_dir); if (_wcsnicmp(recovery_file, base_dir, base_len) != 0) return 0; // リカバリ・ファイル (*.PXX) の名前の基 recovery_len = wcslen(recovery_file); recovery_len -= base_len + 2; // 末尾の 2文字を除くファイル名の長さ // ファイルごとに比較する for (num = 0; num < file_num; num++){ tmp_p = list_buf + list_off; while (list_buf[list_off] != 0) list_off++; list_off++; // ファイル名との前方一致を調べる if (_wcsnicmp(recovery_file + base_len, tmp_p, recovery_len) == 0){ // リカバリ・ファイルと基準が同じ len = wcslen(tmp_p); // 拡張子も同じか調べる if (len == recovery_len + 2){ // インデックス・ファイルと比較する if (switch_p == 0){ if (_wcsicmp(tmp_p + recovery_len, L"ar") == 0){ printf_cp("filename is invalid, %s\n", tmp_p); return 1; } } // リカバリ・ファイル (*.PXX) と比較する for (i = 1; i <= 99; i++){ if ((tmp_p[recovery_len ] == '0' + (i / 10)) && (tmp_p[recovery_len + 1] == '0' + (i % 10))){ printf_cp("filename is invalid, %s\n", tmp_p); return 1; } } } } } return 0; } wmain(int argc, wchar_t *argv[]) { wchar_t file_path[MAX_LEN], *list_buf; wchar_t par_comment[COMMENT_LEN], *tmp_p; int i, j; int switch_b = 0, switch_p = 0, switch_f = 0, switch_r = 0, switch_h = 0; int file_num = 0, source_num = 0, list_len = 0, parity_num = 0, first_vol = 1; __int64 block_size = 0; printf("Parchive 1.0 client version " FILE_VERSION " by Yutaka Sawada\n\n"); if (argc < 3){ printf("Self-Test: "); i = par1_checksum(file_path); if (i == 0){ printf("Success"); } else if (i == 2){ printf("PE checksum is different"); } else if (i == 3){ printf("CRC-32 is different"); } else { printf("Error\0thedummytext"); } printf("\n\n"); print_help(); return 0; } // 初期化 recovery_file[0] = 0; par_comment[0] = 0; base_dir[0] = 0; ini_path[0] = 0; cp_output = GetConsoleOutputCP(); memory_use = 0; // コマンド switch (argv[1][0]){ case 'c': // create case 'v': // verify case 'r': // repair case 'l': // list break; default: print_help(); return 0; } // オプションとリカバリ・ファイルの指定 for (i = 2; i < argc; i++){ tmp_p = argv[i]; // オプション if (((tmp_p[0] == '/') || (tmp_p[0] == '-')) && (tmp_p[0] == argv[2][0])){ tmp_p++; // 先頭の識別文字をとばす // オプション if (wcscmp(tmp_p, L"b") == 0){ switch_b = 1; } else if (wcscmp(tmp_p, L"br") == 0){ switch_b = 2; } else if (wcscmp(tmp_p, L"f") == 0){ switch_f = 1; } else if (wcscmp(tmp_p, L"fu") == 0){ switch_f = 2; } else if (wcscmp(tmp_p, L"i") == 0){ switch_p = 1; } else if (wcscmp(tmp_p, L"in") == 0){ switch_p = 2; } else if (wcscmp(tmp_p, L"u") == 0){ cp_output = CP_UTF8; } else if (wcscmp(tmp_p, L"h") == 0){ switch_h = 1; // オプション (数値) } else if (wcsncmp(tmp_p, L"n", 1) == 0){ j = 1; while ((j < 1 + 4) && (tmp_p[j] >= 48) && (tmp_p[j] <= 57)){ parity_num = (parity_num * 10) + (tmp_p[j] - 48); j++; } } else if (wcsncmp(tmp_p, L"r", 1) == 0){ j = 1; while ((j < 1 + 5) && (tmp_p[j] >= 48) && (tmp_p[j] <= 57)){ switch_r = (switch_r * 10) + (tmp_p[j] - 48); j++; } } else if (wcsncmp(tmp_p, L"p", 1) == 0){ j = 1; first_vol = 0; while ((j < 1 + 4) && (tmp_p[j] >= 48) && (tmp_p[j] <= 57)){ first_vol = (first_vol * 10) + (tmp_p[j] - 48); j++; } if (first_vol < 1) first_vol = 1; if (first_vol > 99) first_vol = 99; } else if (wcsncmp(tmp_p, L"m", 1) == 0){ if ((tmp_p[1] >= 48) && (tmp_p[1] <= 55)) // 0~7 の範囲 memory_use = tmp_p[1] - 48; } else if (wcsncmp(tmp_p, L"vs", 2) == 0){ recent_data = 0; j = 2; while ((j < 2 + 2) && (tmp_p[j] >= '0') && (tmp_p[j] <= '9')){ recent_data = (recent_data * 10) + (tmp_p[j] - '0'); j++; } if ((recent_data == 8) || (recent_data > 15)) recent_data = 0; // オプション (文字列) } else if (wcsncmp(tmp_p, L"c", 1) == 0){ tmp_p++; if (wcslen(tmp_p) >= COMMENT_LEN){ printf("comment is too long\n"); return 1; } wcscpy(par_comment, tmp_p); //printf("comment = %S\n", tmp_p); } else if (wcsncmp(tmp_p, L"vd", 2) == 0){ tmp_p += 2; j = copy_path_prefix(ini_path, MAX_LEN - INI_NAME_LEN - 1, tmp_p, NULL); if (j == 0){ printf("save-directory is invalid\n"); return 1; } if (ini_path[j - 1] != '\\'){ // 末尾が「\」でなければ付けておく ini_path[j ] = '\\'; ini_path[j + 1] = 0; } //printf("Save Directory : %S\n", ini_path); j = GetFileAttributes(ini_path); if ((j == INVALID_FILE_ATTRIBUTES) || !(j & FILE_ATTRIBUTE_DIRECTORY)){ printf("save-directory is invalid\n"); return 1; } } else if (wcsncmp(tmp_p, L"d", 1) == 0){ tmp_p++; j = copy_path_prefix(base_dir, MAX_LEN - 2, tmp_p, NULL); // 末尾に追加される分の余裕を見ておく if (j == 0){ printf("base-directory is invalid\n"); return 1; } if (base_dir[j - 1] != '\\'){ // 末尾が「\」でなければ付けておく base_dir[j ] = '\\'; base_dir[j + 1] = 0; } j = GetFileAttributes(base_dir); if ((j == INVALID_FILE_ATTRIBUTES) || !(j & FILE_ATTRIBUTE_DIRECTORY)){ printf("base-directory is invalid\n"); return 1; } } else { // 未対応のオプション printf_cp("invalid option, %s\n", tmp_p - 1); return 1; } continue; } // オプション以外ならリカバリ・ファイル j = copy_path_prefix(recovery_file, MAX_LEN - 4, tmp_p, NULL); // 「.par」が追加されるかも if (j == 0){ printf("PAR filename is invalid\n"); return 1; } if (argv[1][0] == 'c'){ // 作成なら // 指定されたファイル名が適切か調べる if (sanitize_filename(offset_file_name(recovery_file)) != 0){ printf("PAR filename is invalid\n"); return 1; } // 拡張子が「.par」以外なら標準の拡張子を追加する j = wcslen(recovery_file); // 浄化でファイル名が短縮されるかもしれない if (_wcsicmp(recovery_file + (j - 4), L".par")) wcscpy(recovery_file + j, L".par"); } else { // 検査や修復なら if (_wcsicmp(recovery_file + (j - 4), L".par")){ // 拡張子が「.par」以外でも「.p??」ならよい if ((recovery_file[j - 4] != '.') || ((recovery_file[j - 3] != 'p') && (recovery_file[j - 3] != 'P')) || (recovery_file[j - 2] < 48) || (recovery_file[j - 2] > 57) || (recovery_file[j - 1] < 48) || (recovery_file[j - 1] > 57) ){ wcscpy(recovery_file + j, L".par"); } } j = GetFileAttributes(recovery_file); if ((j == INVALID_FILE_ATTRIBUTES) || (j & FILE_ATTRIBUTE_DIRECTORY)){ wchar_t search_path[MAX_LEN]; int name_len, dir_len, path_len, find_flag = 0; HANDLE hFind; WIN32_FIND_DATA FindData; path_len = wcslen(recovery_file); if (wcspbrk(offset_file_name(recovery_file), L"*?") != NULL){ // 「*」や「?」で検索する場合は指定された拡張子を優先する hFind = FindFirstFile(recovery_file, &FindData); if (hFind != INVALID_HANDLE_VALUE){ get_base_dir(recovery_file, search_path); dir_len = wcslen(search_path); do { //printf("file name = %S\n", FindData.cFileName); name_len = wcslen(FindData.cFileName); if ((dir_len + name_len < MAX_LEN) && // ファイル名が長すぎない (_wcsicmp(FindData.cFileName + (name_len - 4), recovery_file + (path_len - 4)) == 0)){ find_flag = 1; break; // 見つけたファイル名で問題なし } } while (FindNextFile(hFind, &FindData)); // 次のファイルを検索する FindClose(hFind); } } if (find_flag == 0){ // リカバリ・ファイルの拡張子を「.p??」にして検索する wcscpy(search_path, recovery_file); search_path[path_len - 2] = '?'; search_path[path_len - 1] = '?'; hFind = FindFirstFile(search_path, &FindData); if (hFind != INVALID_HANDLE_VALUE){ get_base_dir(recovery_file, search_path); dir_len = wcslen(search_path); do { //printf("file name = %S\n", FindData.cFileName); name_len = wcslen(FindData.cFileName); if ((dir_len + name_len < MAX_LEN) && // ファイル名が長すぎない (FindData.cFileName[name_len - 4] == '.') && ((FindData.cFileName[name_len - 3] == 'p') || (FindData.cFileName[name_len - 3] == 'P')) && (FindData.cFileName[name_len - 2] >= '0') && (FindData.cFileName[name_len - 2] <= '9') && (FindData.cFileName[name_len - 1] >= '0') && (FindData.cFileName[name_len - 1] <= '9')){ find_flag = 1; break; // 見つけたファイル名で問題なし } } while (FindNextFile(hFind, &FindData)); // 次のファイルを検索する FindClose(hFind); } } if (find_flag == 0){ printf("valid file is not found\n"); return 1; } wcscpy(recovery_file + dir_len, FindData.cFileName); } } break; } //printf("m = %d, f = %d, p = %d, n = %d\n", switch_b, switch_f, switch_p, parity_num); if (recovery_file[0] == 0){ // リカバリ・ファイルが指定されてないなら printf("PAR file is not specified\n"); return 1; } // input file の位置が指定されて無くて、 // 最初のソース・ファイル指定に絶対パスが含まれてるなら、それを使う if (argv[1][0] == 'c'){ if ((base_dir[0] == 0) && (switch_f == 0) && (i + 1 < argc)){ tmp_p = argv[i + 1]; if (is_full_path(tmp_p) != 0){ // 絶対パスなら if (copy_path_prefix(file_path, MAX_LEN - 2, tmp_p, NULL) == 0){ printf("base-directory is invalid\n"); return 1; } get_base_dir(file_path, base_dir); // 最初のソース・ファイルの位置にする if (len_without_prefix(base_dir) == 0) // サブ・ディレクトリが無ければ base_dir[0] = 0; } } } if (base_dir[0] == 0) // input file の位置が指定されてないなら get_base_dir(recovery_file, base_dir); // リカバリ・ファイルの位置にする // 環境の表示 print_environment(); switch (argv[1][0]){ case 'c': // リカバリ・ファイル作成ならソース・ファイルのリストがいる i++; if (i >= argc){ printf("input file is not specified\n"); return 1; } if (switch_f){ // ファイル・リストの読み込み list_buf = read_list(argv[i], &file_num, &list_len, &source_num, &block_size, switch_f); if (list_buf == NULL) return 1; } else { // 入力ファイルの指定 int base_len, list_max; base_len = wcslen(base_dir); list_max = 0; list_buf = NULL; for (; i < argc; i++){ // 絶対パスや、親パスへの移動を含むパスは危険なのでファイル名だけにする tmp_p = argv[i]; j = copy_path_prefix(file_path, MAX_LEN - ADD_LEN, tmp_p, base_dir); // 絶対パスにしてから比較する if (j == 0){ free(list_buf); printf_cp("filename is invalid, %s\n", tmp_p); return 1; } if ((j <= base_len) || (_wcsnicmp(base_dir, file_path, base_len) != 0)){ // 基準ディレクトリ外なら free(list_buf); printf_cp("out of base-directory, %s\n", tmp_p); return 1; } if (wcschr(file_path + base_len, '\\') != NULL){ free(list_buf); printf_cp("sub-directory is not supported, %s\n", file_path + base_len); return 1; } //printf("%d = %S\nsearch = %S\n", i, argv[i], file_path); // 「*」や「?」で検索しない場合、ファイルが見つからなければエラーにする j = -1; if (wcspbrk(file_path + base_len, L"*?") == NULL) j = file_num; // ファイルを検索する list_buf = search_files(list_buf, file_path, base_len, j, &file_num, &list_max, &list_len, &source_num, &block_size); if (list_buf == NULL) return 1; if ((j != -1) && (j == file_num)){ // ファイルが見つかったか確かめる free(list_buf); printf_cp("input file is not found, %s\n", file_path + base_len); return 1; } } } if (list_len == 0){ if (list_buf) free(list_buf); printf("input file is not found\n"); return 1; } if (check_recovery_match(file_num, list_buf, list_len, switch_p & 2)){ free(list_buf); return 1; } // ファイル・リストの内容を並び替える (リストの末尾は null 1個にすること) sort_list(list_buf, list_len); //printf("file_num = %d, source_num = %d, block_size = %I64d\n", file_num, source_num, block_size); if ((parity_num == 0) && (switch_r > 0)){ parity_num = (source_num * switch_r) / 100; if (parity_num == 0) parity_num = 1; } printf("Input File count: %d\n", file_num); printf("Data File count : %d\n", source_num); printf("Max file size\t: %I64d\n", block_size); printf("Parity Volume count\t: %d\n", parity_num); if (first_vol > 1) printf("Parity Volume start\t: %d\n", first_vol); if (source_num != 0){ i = 100 * parity_num / source_num; } else { i = 0; } printf("Redundancy rate : %d%%\n", i); if (source_num + parity_num + first_vol - 1 > 256){ free(list_buf); printf("too many total volumes %d\n", source_num + parity_num + first_vol - 1); return 1; } if (parity_num + first_vol - 1 > 99){ free(list_buf); printf("too many parity volumes %d\n", parity_num + first_vol - 1); return 1; } i = par1_create(switch_p & 2, source_num, block_size, parity_num, first_vol, file_num, list_buf, list_len, par_comment); break; case 'v': case 'r': // 検査結果ファイルの位置が指定されてないなら if ((recent_data != 0) && (ini_path[0] == 0)){ // 実行ファイルのディレクトリにする j = GetModuleFileName(NULL, ini_path, MAX_LEN); if ((j == 0) || (j >= MAX_LEN)){ printf("GetModuleFileName\n"); return 1; } while (j > 0){ if (ini_path[j - 1] == '\\'){ ini_path[j] = 0; break; } j--; } if (j >= MAX_LEN - INI_NAME_LEN - 1){ printf("save-directory is invalid\n"); return 1; } } if (argv[1][0] == 'v'){ i = par1_verify(switch_b, switch_p & 1, par_comment); } else { i = par1_repair(switch_b, switch_p & 1, par_comment); } break; case 'l': i = par1_list(switch_h, par_comment); break; } //printf("ExitCode: 0x%02X\n", i); return i; }