Skip to content

Commit 44fa4f7

Browse files
committed
- fix a problem when trying to create hardlinks across drives
- fix a display problem with the progress indicator - add fail-fast by checking all file patterns before scanning files - fix batch file script to make Ctrl+C work again
1 parent 54009be commit 44fa4f7

2 files changed

Lines changed: 125 additions & 77 deletions

File tree

finddupe.c

Lines changed: 124 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@
3838
// fixed a display problem with the progress indicator
3939
// Version 1.35 (c) Aug 2025 thomas694
4040
// fixed a problem with some special characters (e.g. right single/double quotation mark)
41+
// Version 1.36 (c) Dec 2025 thomas694
42+
// fix a problem when trying to create hardlinks across drives
43+
// fix a display problem with the progress indicator
44+
// add fail-fast by checking all file patterns before scanning files
45+
// fix batch file script to make Ctrl+C work again
4146
//
4247
// finddupe is free software: you can redistribute it and/or modify
4348
// it under the terms of the GNU General Public License as published by
@@ -53,7 +58,7 @@
5358
// along with this program. If not, see <http://www.gnu.org/licenses/>.
5459
//--------------------------------------------------------------------------
5560

56-
#define VERSION "1.35"
61+
#define VERSION "1.36"
5762

5863
#define REF_CODE
5964

@@ -425,11 +430,12 @@ static int EliminateDuplicate(FileData_t ThisFile, FileData_t DupeOf)
425430

426431
if (BatchFile){
427432
// put command in batch file
428-
if (DelDuplicates || !Hardlinked)
433+
BOOL isSameVolume = IsSameVolume(ThisFile.FileName, DupeOf.FileName);
434+
if (DelDuplicates || !Hardlinked && isSameVolume)
429435
ftprintf(BatchFile, TEXT("del %s\"%s\"\n"), (IsReadonly ? TEXT("/F ") : TEXT("")),
430436
EscapeBatchName(ThisFile.FileName));
431437
if (!DelDuplicates){
432-
if (!Hardlinked){
438+
if (!Hardlinked && isSameVolume){
433439
ftprintf(BatchFile, TEXT("fsutil hardlink create \"%s\" \"%s\"\n"),
434440
ThisFile.FileName, DupeOf.FileName);
435441
if (IsReadonly){
@@ -444,12 +450,15 @@ static int EliminateDuplicate(FileData_t ThisFile, FileData_t DupeOf)
444450
}else if (MakeHardLinks || DelDuplicates){
445451
if (MakeHardLinks && Hardlinked) return 0; // Nothign to do.
446452

447-
if (_tunlink(ThisFile.FileName)){
448-
ClearProgressInd();
449-
_ftprintf(stderr, TEXT("Delete of '%s' failed\n"), DupeOf.FileName);
450-
exit (EXIT_FAILURE);
451-
}
452-
if (MakeHardLinks){
453+
BOOL isSameVolume = IsSameVolume(ThisFile.FileName, DupeOf.FileName);
454+
455+
if (DelDuplicates || MakeHardLinks && isSameVolume)
456+
if (_tunlink(ThisFile.FileName)){
457+
ClearProgressInd();
458+
_ftprintf(stderr, TEXT("Delete of '%s' failed\n"), DupeOf.FileName);
459+
exit (EXIT_FAILURE);
460+
}
461+
if (MakeHardLinks && isSameVolume){
453462
if (CreateHardLink(ThisFile.FileName, DupeOf.FileName, NULL) == 0){
454463
// Uh-oh. Better stop before we mess up more stuff!
455464
ClearProgressInd();
@@ -471,14 +480,36 @@ static int EliminateDuplicate(FileData_t ThisFile, FileData_t DupeOf)
471480
}
472481
ClearProgressInd();
473482
_tprintf(TEXT(" Created hardlink\n"));
474-
}else{
483+
}else if (DelDuplicates){
475484
ClearProgressInd();
476485
_tprintf(TEXT(" Deleted duplicate\n"));
477486
}
478487
}
479488
return 2;
480489
}
481490

491+
static BOOL IsLocalPath(const TCHAR* path)
492+
{
493+
if (!path || _tcslen(path) < 3)
494+
return FALSE;
495+
496+
if (path[0] == _T('\\') && path[1] == _T('\\'))
497+
return FALSE;
498+
499+
if (_istalpha(path[0]) && path[1] == _T(':') && path[2] == _T('\\'))
500+
return TRUE;
501+
502+
return FALSE;
503+
}
504+
505+
static BOOL IsSameVolume(TCHAR* path1, TCHAR* path2)
506+
{
507+
if (!IsLocalPath(path1) || !IsLocalPath(path2))
508+
return FALSE;
509+
510+
return _totupper(path1[0]) == _totupper(path2[0]);
511+
}
512+
482513
static int ReadFileAndCalculateCRC(TCHAR* fileName, UINT64 fileSize, Checksum_t* checksum)
483514
{
484515
#define CHUNK_SIZE 0x10000
@@ -767,6 +798,8 @@ BOOL OpenTheFile(const TCHAR* FileName, HANDLE* FileHandle)
767798
//--------------------------------------------------------------------------
768799
static void ProcessFile(const TCHAR* FileName)
769800
{
801+
static int winWidth = 0;
802+
770803
UINT64 FileSize;
771804
Checksum_t CheckSum;
772805
DWORD ticksCompare = 0;
@@ -775,6 +808,15 @@ static void ProcessFile(const TCHAR* FileName)
775808
DWORD ticksByteRead = 0;
776809
DWORD ticksCRC = 0;
777810

811+
if (winWidth == 0) {
812+
CONSOLE_SCREEN_BUFFER_INFO csbi;
813+
HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
814+
if (GetConsoleScreenBufferInfo(h, &csbi))
815+
winWidth = csbi.srWindow.Right - csbi.srWindow.Left + 1 - 25;
816+
else
817+
winWidth = 55;
818+
}
819+
778820
if (MeasureDurations) ticksCompare = GetTickCount();
779821

780822
// replace linear list search with hashset lookup
@@ -791,28 +833,32 @@ static void ProcessFile(const TCHAR* FileName)
791833
FileData_t ThisFile;
792834
memset(&ThisFile, 0, sizeof(ThisFile));
793835
{
794-
static int LastPrint, Now;
836+
static int LastPrint = 0, Now, lastLength = 0;
795837
Now = GetTickCount();
796838
if ((unsigned)(Now-LastPrint) > 200){
797839
if (ShowProgress){
798-
TCHAR ShowName[55];
840+
TCHAR* ShowName = (TCHAR*)malloc((winWidth) * sizeof(TCHAR));
799841
int l = _tcslen(FileName);
800842
#ifdef UNICODE
801-
wmemset(ShowName, L'\0', sizeof(ShowName) / sizeof(ShowName[0]));
843+
wmemset(ShowName, L'\0', winWidth);
802844
#else
803-
memset(ShowName, ' ', sizeof(ShowName));
845+
memset(ShowName, '\0', winWidth);
804846
#endif
805-
if (l > 53) l = 53;
847+
if (l > winWidth-2) l = winWidth-2;
806848
#ifdef UNICODE
807849
wmemcpy(ShowName, FileName, l);
808-
if (l >= 53) wmemcpy(ShowName + 53, L"…", 1);
850+
if (l >= winWidth-2) wmemcpy(ShowName + winWidth-2, L"…", 1);
851+
if (lastLength > l) wmemset(ShowName + l, L' ', (size_t)(lastLength - l));
809852
#else
810853
memcpy(ShowName, FileName, l);
811-
if (l >= 53) memcpy(ShowName + 53, "…", 1);
854+
if (l >= winWidth-2) memcpy(ShowName + winWidth-2, "…", 1);
855+
if (lastLength > l) memcpy(ShowName + l, ' ', (size_t)(lastLength - l));
812856
#endif
813857

814858
_tprintf(TEXT("Scanned %4d files: %s\r"), FilesMatched, ShowName);
859+
free(ShowName);
815860
LastPrint = Now;
861+
lastLength = l;
816862
ProgressIndicatorVisible = 1;
817863
}
818864
fflush(stdout);
@@ -964,20 +1010,17 @@ static void Usage (void)
9641010
exit(EXIT_FAILURE);
9651011
}
9661012

967-
static void CheckFileSystem(TCHAR drive)
1013+
static void CheckFileSystem(TCHAR* path)
9681014
{
9691015
if (!(BatchFileName || MakeHardLinks)) return;
9701016

971-
TCHAR lpRootPathName[4];
972-
_tcsncpy(lpRootPathName, TEXT("C:\\\0"), 4);
973-
_tcsncpy(lpRootPathName, &drive, 1);
974-
TCHAR lpFileSystemNameBuffer[MAX_PATH + 1];
1017+
TCHAR lpRootPathName[4] = { 0 };
1018+
_tcsncpy(lpRootPathName, path, 3);
1019+
TCHAR lpFileSystemNameBuffer[MAX_PATH + 1] = { 0 };
9751020
#ifdef UNICODE
976-
wmemset(lpFileSystemNameBuffer, L' ', sizeof(lpFileSystemNameBuffer) / sizeof(lpFileSystemNameBuffer[0]));
9771021
BOOL ret = GetVolumeInformationW(lpRootPathName, NULL, 0, 0, 0, 0, lpFileSystemNameBuffer, MAX_PATH + 1);
9781022
#else
979-
memset(lpFileSystemNameBuffer, ' ', sizeof(lpFileSystemNameBuffer));
980-
BOOL ret = GetVolumeInformationW(lpRootPathName, NULL, 0, 0, 0, 0, lpFileSystemNameBuffer, MAX_PATH + 1);
1023+
BOOL ret = GetVolumeInformationA(lpRootPathName, NULL, 0, 0, 0, 0, lpFileSystemNameBuffer, MAX_PATH + 1);
9811024
#endif
9821025
if (_tcscmp(lpFileSystemNameBuffer, TEXT("NTFS")))
9831026
{
@@ -987,16 +1030,59 @@ static void CheckFileSystem(TCHAR drive)
9871030
}
9881031
}
9891032

1033+
void CheckFilePattern(TCHAR* filePattern)
1034+
{
1035+
static TCHAR DefaultDrive = '\0';
1036+
static TCHAR DriveUsed = '\0';
1037+
TCHAR Drive;
1038+
int a;
1039+
1040+
if (DefaultDrive == '\0') {
1041+
TCHAR CurrentDir[_MAX_PATH];
1042+
_tgetcwd(CurrentDir, _MAX_PATH);
1043+
DefaultDrive = tolower(CurrentDir[0]);
1044+
}
1045+
1046+
for (a=0;;a++){
1047+
if (filePattern[a] == '\0') break;
1048+
if (filePattern[a] == '/') filePattern[a] = '\\';
1049+
}
1050+
1051+
if (_tcslen(filePattern) >= 2 && filePattern[0] == '\\' && filePattern[1] == '\\' && (BatchFileName || MakeHardLinks))
1052+
{
1053+
ClearProgressInd();
1054+
_ftprintf(stderr, TEXT("Cannot make hardlinks on network shares\n"));
1055+
exit(EXIT_FAILURE);
1056+
}
1057+
1058+
if (filePattern[1] == ':'){
1059+
Drive = tolower(filePattern[0]);
1060+
}else{
1061+
Drive = DefaultDrive;
1062+
}
1063+
if (DriveUsed == '\0') DriveUsed = Drive;
1064+
if (DriveUsed != Drive){
1065+
if (BatchFileName || MakeHardLinks){
1066+
ClearProgressInd();
1067+
_ftprintf(stderr, TEXT("Error: Hardlinking across different drives not possible\n"));
1068+
exit(EXIT_FAILURE);
1069+
}
1070+
}
1071+
1072+
if (_tcslen(filePattern) >= 3 && filePattern[1] == ':' && filePattern[2] == '\\') {
1073+
CheckFileSystem(filePattern);
1074+
}
1075+
}
1076+
9901077
//--------------------------------------------------------------------------
9911078
// The main program.
9921079
//--------------------------------------------------------------------------
9931080
int _tmain (int argc, TCHAR **argv)
9941081
{
9951082
int argn;
9961083
TCHAR * arg;
997-
TCHAR DefaultDrive;
998-
TCHAR DriveUsed = '\0';
9991084
int indexFirstRef = 0;
1085+
int filePatternStart = 0;
10001086

10011087
PrintDuplicates = 1;
10021088
PrintFileSigs = 0;
@@ -1027,9 +1113,10 @@ int _tmain (int argc, TCHAR **argv)
10271113

10281114
for (argn=1;argn<argc;argn++){
10291115
arg = argv[argn];
1030-
if (arg[0] != '-') break; // Filenames from here on.
1031-
1032-
if (!_tcscmp(arg,TEXT("-h"))){
1116+
if (arg[0] != '-') {
1117+
CheckFilePattern(arg);
1118+
if (filePatternStart == 0) filePatternStart = argn;
1119+
}else if (!_tcscmp(arg,TEXT("-h"))){
10331120
Usage();
10341121
exit(EXIT_FAILURE);
10351122
}else if (!_tcscmp(arg,TEXT("-bat"))){
@@ -1051,7 +1138,8 @@ int _tmain (int argc, TCHAR **argv)
10511138
}else if (!_tcscmp(arg,TEXT("-listlink"))){
10521139
HardlinkSearchMode = 1;
10531140
}else if (!_tcscmp(arg,TEXT("-ref"))){
1054-
break;
1141+
if (filePatternStart == 0) filePatternStart = argn;
1142+
CheckFilePattern(argv[++argn]);
10551143
}else if (!_tcscmp(arg,TEXT("-z"))){
10561144
SkipZeroLength = 0;
10571145
}else if (!_tcscmp(arg,TEXT("-u"))){
@@ -1081,6 +1169,8 @@ int _tmain (int argc, TCHAR **argv)
10811169
}
10821170
}
10831171

1172+
if (filePatternStart > 0) argn = filePatternStart;
1173+
10841174
if (argn > argc){
10851175
_ftprintf(stderr, TEXT("Missing argument! Use -h for help\n"));
10861176
exit(EXIT_FAILURE);
@@ -1146,26 +1236,18 @@ int _tmain (int argc, TCHAR **argv)
11461236
_ftprintf(BatchFile, TEXT(" echo.\n"));
11471237
_ftprintf(BatchFile, TEXT(" echo Set code page to 65001. Rerun script to execute hardlink commands.\n"));
11481238
_ftprintf(BatchFile, TEXT(" chcp 65001\n"));
1149-
_ftprintf(BatchFile, TEXT(") else (\n"));
1239+
_ftprintf(BatchFile, TEXT(" exit /b\n"));
1240+
_ftprintf(BatchFile, TEXT(")\n"));
11501241
#endif
11511242
_ftprintf(BatchFile, TEXT("chcp 65001\n\n"));
11521243
}
11531244

11541245
memset(&DupeStats, 0, sizeof(DupeStats));
11551246

1156-
{
1157-
TCHAR CurrentDir[_MAX_PATH];
1158-
_tgetcwd(CurrentDir, _MAX_PATH);
1159-
DefaultDrive = tolower(CurrentDir[0]);
1160-
CheckFileSystem(DefaultDrive);
1161-
}
1162-
11631247
FilenameSet = kh_init(hset);
11641248
FileDataMap = kh_init(hmap);
11651249

11661250
for (;argn<argc;argn++){
1167-
int a;
1168-
TCHAR Drive;
11691251
FilesMatched = 0;
11701252

11711253
if (!_tcscmp(argv[argn],TEXT("-ref"))){
@@ -1176,43 +1258,12 @@ int _tmain (int argc, TCHAR **argv)
11761258
ReferenceFiles = 0;
11771259
}
11781260

1179-
for (a=0;;a++){
1180-
if (argv[argn][a] == '\0') break;
1181-
if (argv[argn][a] == '/') argv[argn][a] = '\\';
1182-
}
1183-
1184-
if (argv[argn][1] == ':'){
1185-
Drive = tolower(argv[argn][0]);
1186-
}else{
1187-
Drive = DefaultDrive;
1188-
}
1189-
if (DriveUsed == '\0') DriveUsed = Drive;
1190-
if (DriveUsed != Drive){
1191-
if (MakeHardLinks){
1192-
_ftprintf(stderr, TEXT("Error: Hardlinking across different drives not possible\n"));
1193-
kh_destroy(hset, FilenameSet);
1194-
kh_destroy(hmap, FileDataMap);
1195-
return EXIT_FAILURE;
1196-
}
1197-
}
1198-
1199-
if (_tcslen(argv[argn]) >= 2 && argv[argn][0] == '\\' && argv[argn][1] == '\\' && (BatchFileName || MakeHardLinks))
1200-
{
1201-
ClearProgressInd();
1202-
_ftprintf(stderr, TEXT("Cannot make hardlinks on network shares\n"));
1203-
kh_destroy(hset, FilenameSet);
1204-
kh_destroy(hmap, FileDataMap);
1205-
return EXIT_FAILURE;
1206-
}
1207-
else if (_tcslen(argv[argn]) >= 3 && argv[argn][1] == ':' && argv[argn][2] == '\\') {
1208-
CheckFileSystem(argv[argn][0]);
1209-
}
1210-
12111261
// Use my globbing module to do fancier wildcard expansion with recursive
12121262
// subdirectories under Windows.
12131263
MyGlob(argv[argn], FollowReparse, ProcessFile);
12141264

12151265
if (!FilesMatched){
1266+
ClearProgressInd();
12161267
_ftprintf(stderr, TEXT("Error: No files matched '%s'\n"), argv[argn]);
12171268
}
12181269
}
@@ -1236,9 +1287,6 @@ int _tmain (int argc, TCHAR **argv)
12361287
}
12371288

12381289
if (BatchFile){
1239-
#ifdef UNICODE
1240-
_ftprintf(BatchFile, TEXT(")\n"));
1241-
#endif
12421290
fclose(BatchFile);
12431291
BatchFile = NULL;
12441292
}

version.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#define STRINGIZE(s) STRINGIZE2(s)
44

55
#define VERSION_MAJOR 1
6-
#define VERSION_MINOR 35
6+
#define VERSION_MINOR 36
77
#define VERSION_REVISION 0
88
#define VERSION_BUILD 0
99

0 commit comments

Comments
 (0)