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
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+
482513static 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//--------------------------------------------------------------------------
768799static 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//--------------------------------------------------------------------------
9931080int _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 }
0 commit comments