This is a CON module of EuroTool program EuroDirs for Linux.
EUROASM CPU=X64, AutoAlign=yes, Unicode=no
dirslinc PROGRAM Format=COFF,Width=64
%DROPMACRO * ; Forget macros from previous assembly, if any.
INCLUDEHEAD1 argument.htm ; Include header of this file.
INCLUDE1 linabi.htm, cpuext64.htm, linsfile.htm, linf64.htm,
INCLUDE1 memory64.htm, string64.htm, status32.htm, sort64.htm, time.htm
HEAD
SCAN_INDEX STRUC ; A structure which describes one scaned directory.
.Ptr D DWORD ; Relative pointer to the directory name in UTF-8. Related to [NamesInMemory].
.Size D DWORD ; Size of the directory name in bytes.
.Volume D QWORD ; Total amount of bytes in all files in this directory.
ENDSTRUC SCAN_INDEX
ENDHEAD
[.rodata] ; Text constants.
Ext_names DB ".names",0 ; The extension of temporary names file.
Ext_index DB ".index",0 ; The extension of temporary index file.
Ext_scan DB ".scan",0 ; The extension of scan file.
DateMask DB "????.??.??_??.??",0 ; File date mask.
EuroDirs:: DB "EuroDirs",0
Version:: DB " version %^Date",0
Help:: DB 0xEF,0xBB,0xBF ; BOM UTF-8.
DB "; EuroDirs default configuration:",10
DB "ScanNow=no ; Perform the scan when /SN=yes. Otherwise compare scans files.",10
DB "ScanDir=""."" ; Directory for scan files.",10
DB "TimeZone=0 ; TZ of scan files in hours. /TZ=0 for UTC, /TZ=1 for Prague etc.",10
DB "LeaveTemporary=no ; Do not erase *.name and *.index files (for debugging).",10
DB "MaxDays=32 ; Older scan files will be deleted before creating a new scan.",10
DB "InputFile= ; Old scan file to compare. Default is the oldest in ScanDir.",10
DB "OutputFile= ; New scan file to compare. Default is the newest in ScanDir.",10
DB "ExcludeDirs= ; Do not scan dirs beginning with it. Default is empty.",10
DB "IncludeDirs= ; Scan those dirs only. Default is scan all.",10
DB "ViewDirs=Inc ; Ins|Inc|Unch|Dec|Del - select one of 5 views.",10
DB 0
Comparing DB 10,"Comparing directories from two scan files:",0
Report1:: DB " new created directories,",0
Report2:: DB " increased directories,",0
Report3:: DB " directories which did not change,",0
Report4:: DB " decreased directories,",0
Report5:: DB " deleted directories.",0
[.data] ; Text variables.
Today DB "yyyy.mm.dd_hh.mm",0 ; Today's date during the scan.
MaxDaysAgo DB "yyyy.mm.dd_hh.mm",0 ; Today minus ArgMaxDays.
[.bss] ; Working memory variables.
SystemTime DS SYSTEMTIME ; A time structure.
ScanIndex DS SCAN_INDEX ; A directory index.
File_ini DS FILE64 ; Configuration FILE64.
File_names DS FILE64 ; Temporary FILE64 for directory names (UTF-8).
File_index DS FILE64 ; Temporary FILE64 for directory indexes.
File_scan DS FILE64 ; Scan FILE64 (UTF-8), alphabetically sorted.
File_scanOlder:: DS FILE64 ; Older scan FILE64 to compare.
File_scanNewer:: DS FILE64 ; Newer scan FILE64 to compare.
Dir:: D 1K*BYTE ; The working directory which is scanned.
Total D QWORD ; Total size of all files and subdirectories.
NamesInMemory D QWORD ; Pointer to the contents of File_names mapped in memory.
NamesSize D QWORD ; Size of the contents of File_names mapped in memory.
IndexInMemory:: D QWORD ; Pointer to the contents of File_index mapped in memory.
IndexEndInMemory:: D QWORD ; Size of the contents of File_index mapped in memory.
IndexNumber:: D QWORD ; Number of scanned subdirectories.
IncludedDirsBuffer D QWORD ; BUFFER for the parsed directories of ArgIncludedDirs.
IncludedDirsPtr D QWORD ; Pointer to IncludedDirsBuffer contents.
IncludedDirsEnd D QWORD ; Pointer to the end of IncludedDirsBuffer contents.
ExcludedDirsBuffer D QWORD ; BUFFER for the parsed directories of ArgExcludedDirs.
ExcludedDirsPtr D QWORD ; Pointer to ExcludedDirsBuffer contents.
ExcludedDirsEnd D QWORD ; Pointer to the end of ExcludedDirsBuffer contents.
ScanDirBuffer D QWORD ; BUFFER for the dates of old scans.
ScanDirPtr D QWORD ; Pointer to ScanDirBuffer contents.
ScanDirEnd D QWORD ; Pointer to the end of ScanDirBuffer contents.
ScanDirNr D QWORD ; Number of scans in ScanDir.
OlderPtr D QWORD ; Pointer to the old scan mapped in memory, variable.
OlderInMemory:: D QWORD ; Pointer to the beginning of old scan mapped in memory.
OlderEndInMemory D QWORD ; Pointer to the end of old scan mapped in memory.
NewerPtr D QWORD ; Pointer to the new scan mapped in memory, variable.
NewerInMemory:: D QWORD ; Pointer to the beginning of new scan mapped in memory.
NewerEndInMemory D QWORD ; Pointer to the end of new scan mapped in memory.
Buffers:: ; Array of buffers.
Buffer1:: D QWORD ; Pointer to BUFFER with INDEXes of inserted dirs.
Buffer2:: D QWORD ; Pointer to BUFFER with INDEXes of increased dirs.
Buffer3:: D QWORD ; Pointer to BUFFER with INDEXes of unchanged dirs.
Buffer4:: D QWORD ; Pointer to BUFFER with INDEXes of decreased dirs.
Buffer5:: D QWORD ; Pointer to BUFFER with INDEXes of deleted dirs.
BufferSelected:: D QWORD ; One of Buffer1..Buffer5 selected by a button or /ViewDirs.
MaxLineNr:: D DWORD ; Ordinal number of the last line in the selected buffer.
ArgNr D DWORD ; Binary number of the current cmd-line argument.
Numbers:: ; Array of numbers (ANSI).
Number1: D 20*BYTE ; Decimal number (ANSI) of inserted dirs.
Number2: D 20*BYTE ; Decimal number (ANSI) of increased dirs.
Number3: D 20*BYTE ; Decimal number (ANSI) of unchanged dirs.
Number4: D 20*BYTE ; Decimal number (ANSI) of decreased dirs.
Number5: D 20*BYTE ; Decimal number (ANSI) of deleted dirs.
NumberA:: D 20*BYTE ; A working space for numbers. Zero terminated, e. g. "1234567",0.
Number_A:: D 26*BYTE ; Number but with separators _, e. g. "1_234_567".
[.text]
MainCon:: PROC
StdOutput EuroDirs,Version,Eol=yes
; Try to load arguments from the configuration file "/etc/eurodirs.ini".
FileAssign File_ini,=B'/etc/eurotool/eurodirs.ini'
FileExists? File_ini
JNC .15:
FileMkDir File_ini ; Create /etc/eurotool/ if it didn't exist.
JC .10:
LEA RDI,[Help]
GetLength$ RDI, Unicode=no
FileStore File_ini, RDI, RCX
JNC .15:
.10:StdOutput =B'Configuration "',File_ini.Name, =B'" could not be saved.',Eol=yes, Unicode=no
JMP .20:
.15:CALL ReadFile_ini
.20:; And finally read arguments from the command-line.
MOV EAX,[ArgNr]
INC EAX ; The next argument.
MOV [ArgNr],EAX
GetArg RAX, Frame=RSP ; Returns in RSI,RCX the line with one argument, e. g. /ScanDir="/var/log/eurodirs".
JC .30: ; When they are no more arguments.
CALL ArgParse:: ; Use ArgParse to translate it to a public symbol Arg***.
JNC .20:
StdOutput ErrorMessage::,EscDefColor::, Eol=yes
JNSt [Status::],ArgHelpRequired,.25:
LEA RDI,[Help+3]
StdOutput RDI, Eol=yes
.25:TerminateProgram 8
.30:; Arguments are retrieved. Check if ArgScanDir ends with slash.
LEA RDI,[ArgScanDir::]
GetLengthUTF8 RDI ; Returns RCX=size in bytes,
MOV AX,'/'
CMP [RDI+RCX-1],AL
JE .40
MOV [RDI+RCX],AX
.40:; Create buffers for SCAN_INDEX objects. They will be used when /SN=no.
BufferCreate Size=16K
JC ErrorBuffer
MOV [ScanDirBuffer],RAX
BufferCreate Size=4M
JC ErrorBuffer
MOV [Buffer1],RAX
BufferCreate Size=4M
JC ErrorBuffer
MOV [Buffer2],RAX
BufferCreate Size=8M
JC ErrorBuffer
MOV [Buffer3],RAX
BufferCreate Size=4M
JC ErrorBuffer
MOV [Buffer4],RAX
BufferCreate Size=4M
JC ErrorBuffer
MOV [Buffer5],RAX
JSt [Status::],ArgScanNow,.80: ; Should the computer be scanned now?
CALL LoadAndCompareScans ; If not, compare two scans to Buffer1..5.
JC .Terminate: ; On error terminate.
JMP MainGui:: ; Otherwise show the graphic version.
.80:CALL Scan: ; Compute and save the Scan and then terminate.
.Terminate::
FileClose File_scanOlder, File_scanNewer
BufferDestroy [ScanDirBuffer],[Buffer1],[Buffer2],[Buffer3],[Buffer4],[Buffer5]
TerminateProgram 0
ENDP MainCon::
Procedure Scan is called when an argument /ScanNow=yes was detected.
EuroDirs is usually called with this argument from TaskScheduler daily or weekly
and it creates a scan file "YYYY.MM.DD_HH.MM.scan"
in the directory specified by /ScanDir=.
Scan: PROC ; Scan now.
CALL SetToday ; Specify the nominal date_time for the scan.
CALL SetMaxDaysAgo
LEA RDI,[ArgScanDir::]
GetLengthUTF8 RDI
MOV AL,'/'
CMP [RDI+RCX-1],AL
JE .10:
MOV [RDI+RCX],AL ; Make sure that ScanDir ends with /.
.10:FileAssign File_scan,RDI,DateMask,Ext_scan ; Find files "ScanDir/????.??.??_??.??.scan"
FileEach File_scan,EraseOlderFile ; Erase files older than ArgMaxDays.
; Create files with extensions .scan, .names, .index
LEA RBX,[File_scan]
FileAssign RBX,RDI,Today,Ext_scan
FileCreate RBX
JNC .20:
CMP AX,-EACCES
JNE ErrorFile:
StdOutput =B"Permission denied. Use sudo.",Eol=yes
JMP ErrorWrite
.20:LEA RBX,[File_names]
FileAssign RBX,RDI,Today,Ext_names
FileCreate RBX
JC ErrorWrite
LEA RBX,[File_index]
FileAssign RBX,RDI,Today,Ext_index
FileCreate RBX
JC ErrorWrite
; Prepare excluded directories.
BufferCreate Size=4K
MOV [ExcludedDirsBuffer],RAX
LEA RSI,[ArgExcludeDirs::]
CALL ParseToBuffer
BufferRetrieve [ExcludedDirsBuffer]
LEA RDX,[RSI+RCX]
MOV [ExcludedDirsPtr],RSI
MOV [ExcludedDirsEnd],RDX
; Scan included dirs (if not empty) or all directories otherwise. Omit excluded dirs.
BufferCreate Size=4K
MOV [IncludedDirsBuffer],RAX
LEA RSI,[ArgIncludeDirs::]
CMPB [RSI],0
JNZ .30:
MOVW [RSI],'/' ; When /IncludeDirs was empty, use / instead.
.30:CALL ParseToBuffer ; RSI=^ArgIndludeDirs, RAX=IncludeDirsBuffer.
BufferRetrieve [IncludedDirsBuffer]
LEA RDX,[RSI+RCX]
MOV [IncludedDirsPtr],RSI
MOV [IncludedDirsEnd],RDX
.40:MOV RSI,[IncludedDirsPtr] ; Scan the next directory from IncludedDirs.
CMP RSI,[IncludedDirsEnd]
JNB .50:
LODSQ ; 1st QWORD is a size, 2nd QWORD is a pointer to the directory name.
MOV ECX,EAX
LODSQ
MOV [IncludedDirsPtr],RSI
MOV RSI,RAX
LEA RDI,[Dir]
JRCXZ .40:
REP MOVSB ; Included directory will be put to Dir.
XOR EAX,EAX
STOSB
CALL ExcludeDir ; Skip if the folder in Dir is one of the excluded directories.
JC .40: ; Dir is excluded, ignore it.
LEA RDI,[Dir]
StdOutput =B"Scanning directory """,RDI,=B"""",Eol=yes
CALL DirVolume ; The main recursive procedure will scan Dir and all its subdirectories.
ADD [Total],RAX
CALL StoreIndex
JMP .40:
.50:; DirVolume is now scanned and written to File_names and File_index.
MOV RBX,[IncludedDirsBuffer]
BufferDestroy RBX
MOV RBX,[ExcludedDirsBuffer]
BufferDestroy RBX
FileClose File_names,File_index ; Temporarily close files.
LEA RBX,[File_names]
FileMapOpen RBX ; Directory names will be sorted by name.
JC ErrorMap
MOV [NamesInMemory],RSI
MOV [NamesSize],RAX
LEA RBX,[File_index]
FileMapCreate RBX
JC ErrorMap
MOV [IndexInMemory],RDI
ADD RDI,RAX
MOV [IndexEndInMemory],RDI
SHR EAX,4 ; Size of SCAN_INDEX is 16.
MOV [IndexNumber],RAX
LEA RDI,[NumberA]
MOV RBX,RDI
StoD RDI
MOVB [RDI],0
StdOutput =B'Sorting ',RBX,=B' directories...',Eol=yes
LEA RDX,[ByName]
MOV RSI,[IndexInMemory]
MOV RAX,[IndexNumber]
ShellSort RSI,RAX,16,RDX ; Sort the indexes by name.
LEA RDI,[File_scan.Name]
StdOutput =B'Writing scan "',RDI,=B'"',Eol=yes
.60:CMP RSI,[IndexEndInMemory] ; Write the sorted indexes to a scan file.
JAE .80:
MOV RAX,[RSI+SCAN_INDEX.Volume]
LEA RDI,[NumberA]
MOV RBX,RDI
StoD RDI
SUB RDI,RBX
MOV EDX,[RSI+SCAN_INDEX.Ptr]
MOV ECX,[RSI+SCAN_INDEX.Size]
ADD RDX,[NamesInMemory]
FileWrite File_scan,RDX,RCX,=B 0x09,1,RBX,RDI,=B 0x0A,1 ; DirNameInUTF8, HT, DirVolume, LF.
ADD RSI,SIZE# SCAN_INDEX
JMP .60:
.80:FileClose File_names,File_index,File_scan
JSt [Status::],ArgLeaveTemporary,.90:
FileDelete File_names,File_index
.90:StdOutput =B"Scan terminated.",Eol=yes
RET
ENDP Scan
DirVolume: PROC
PUSH RCX,RSI,RDI,R12
XOR EAX,EAX
PUSH RAX ; Accumulator for the total volume of the directory in Dir.
CALL ExcludeDir
JC .90: ; if the directry should be excluded.
LEA RDI,[Dir] ; RDI is a directory, always slash-terminated.
MOV ECX,SIZE# Dir
XOR EAX,EAX
REPNE SCASB
LEA R12,[RDI-1] ; End of directory name (R12 points at the terminating NUL).
LEA RDI,[Dir]
LinABI open,RDI,O_RDONLY+O_DIRECTORY,0,Fastmode=yes
MOV RDI,RAX ; The returned directory handle.
%TempBufSize %SETA (SIZE# FILE64.Name+7)&~7 ; Buffer of NAME_MAX rounded up to QWORD (264).
%StatSize %SETA (SIZE# STAT+7)&~7 ; Buffer for STAT structure (144), reused for SCAN_INDEX structure (16).
SUB RSP,%TempBufSize
SUB RSP,%StatSize ; RSP=^STAT.
.10: LEA RSI,[RSP+%StatSize] ; RSI=^Temporary buffer for several DIRENT64 entries.
LinABI getdents64,RDI,RSI,%TempBufSize
TEST RAX
JZ .80: ; No more files/directories.
CMP RAX,-ERR_MAX
JNC .80: ; Jump on error (EBADF).
LEA RCX,[RSI+RAX] ; End of this buffer.
.20: CMP RSI,RCX ; At the end of buffer?
JNB .10: ; If yes, load the next portion.
LEA RDX,[RSI+DIRENT64.d_name]
MOV R10,RSI
MOV R11,RDI ; Temporary save RSI,RDI.
MOV RSI,RDX
MOV RDI,R12
MOV AL,'/'
CMP [RDI-1],AL
JE .30:
STOSB
.30: LODSB
STOSB ; Append the file/subdirectory name to the path in Dir.
CMP AL,0
JNE .30:
MOV RDI,R11 ; Restore RDI,RSI.
MOV RSI,R10
CMPB [RSI+DIRENT64.d_type],DT_DIR ; Is it a directory?
JE .40:
; Treat other file types as an ordinary file.
MOV R11,RDI
LEA RDI,[Dir] ; A regular file.
MOV RDX,RSP ; RDX=^Buffer for STAT structure.
LinABI lstat,RDI,RDX,Fastmode=no
MOV RAX,[RDX+STAT.st_size]
LEA RDX,[RSP+%TempBufSize+%StatSize] ; RDX=^QWORD accumulator for total volume.
ADD [RDX],RAX ; Accumulate the size of files in subdirectory Dir.
MOVB [R12],0 ; Remove the file/subdirectory name from Dir.
MOV RDI,R11
JMP .60:
.40: ; RDX is a subdirectory name without path. Dir is the same name with path.
CMPW [RDX],'.'
JE .60:
CMPW [RDX],'..'
JNE .50:
CMPB [RDX+2],0
JE .60: ; Ignore "." and ".." directories.
.50: ; Dir is a subdirectory name, other than "." and "..".
CALL ExcludeDir
JC .55:
CALL DirVolume ; Recursive call itself. Does not change RCX,RSI,RDI.
LEA RDX,[RSP+%TempBufSize+%StatSize] ; RDX=^accumulator for total volume.
ADD [RDX],RAX ; Add the subdirectory size.
CALL StoreIndex
.55: MOVB [R12],0
.60: MOVZXW EAX,[RSI+DIRENT64.d_reclen]
ADD RSI,RAX
JMP .20:
.80:ADD RSP,%TempBufSize+%StatSize
.90:POP RAX ; Return the total volume of the directory Dir in RAX.
POP R12,RDI,RSI,RCX
RET
ENDP DirVolume
StoreIndex PROC PUSH RCX,RDI LEA RDX,[ScanIndex] ; Store the directory name+volume to temporary File_names and File_index. MOV [RDX+SCAN_INDEX.Volume],RAX LEA RDI,[Dir] MOV RBX,RDI XOR EAX,EAX MOV ECX,SIZE# Dir REPNE SCASB DEC RDI SUB RDI,RBX MOV RAX,[File_names.Pos] MOV [RDX+SCAN_INDEX.Ptr],EAX MOV [RDX+SCAN_INDEX.Size],EDI FileWrite File_names,RBX,RDI LEA RBX,[File_names] JC ErrorWrite: FileWrite File_index,RDX,SIZE# SCAN_INDEX LEA RBX,[File_index] JC ErrorWrite: POP RDI,RCX RET ENDP StoreIndex
Global procedure LoadAndCompareScans checks arguments.
If /ScanDir is empty it fills it with .
.
If /InputFile is empty (default), it assigns it with the oldest scan from /ScanDir.
If /OutputFile is empty (default), it assigns it with the newest scan from /ScanDir.
If there are less than two scans in /ScanDir, LoadAndCompareScans ends with CF=1.
By default it selects the oldest and the newest scan in ScanDir.
The procedure fills the five buffers with ScanFiles records (indexes) with differences between those two scans.
LoadAndCompareScans:: PROC
; Clear buffers from previous run of LoadAndCompareScans procedure.
BufferClear [ScanDirBuffer],[Buffer1],[Buffer2],[Buffer3],[Buffer4],[Buffer5]
; Load dates of all scans into a ScanDirBuffer.
LEA RDI,[ArgScanDir::]
FileAssign File_scan, RDI, DateMask, Ext_scan, Unicode=no
FileEach File_scan,StoreFileName ; Store the date from file names to ScanDirBuffer.
FileClose File_scan
BufferRetrieve [ScanDirBuffer]
CMP ECX,2*16 ; At least two scans are required.
JAE .20:
StdOutput =B"At least two scan files are required in /ScanDir=""",RDI,=B""".",Eol=yes
StdOutput =B"Please use 'sudo ./eurodirs.x -ScanNow=yes'",Eol=yes
STC
JMP .90:
.20:MOV RAX,RCX
ADD RCX,RSI
SHR RAX,4 ; Each item in ScanDirBuffer has 16 bytes.
MOV [ScanDirPtr],RSI
MOV [ScanDirEnd],RCX
MOV [ScanDirNr],RAX
ShellSort RSI, RAX, 16, ByDate
FileClose File_scanOlder,File_scanNewer ; Close files, if opened by previous comparison.
LEA RDI,[ArgInputFile::] ; If ArgInputFile is specified, it will be used as File_scanOlder.
LEA RBX,[File_scanOlder]
MOV RDX,RDI
CMPB [RDI],0
JNE .25:
LEA RSI,[ArgScanDir::]
GetLength$ RSI
REP MOVSB
MOV RSI,[ScanDirPtr] ; ArgInputFile is empty (default), use the first date of the sorted ScanDirPtr.
MOV ECX,16
REP MOVSB
LEA RSI,[Ext_scan]
MOV CL,6
REP MOVSB
.25:FileAssign RBX,RDX
.30:FileMapOpen RBX
JC ErrorMap:
TEST RAX
JZ ErrorZeroSize
MOV [OlderPtr],RSI
MOV [OlderInMemory],RSI
ADD RAX,RSI
MOV [OlderEndInMemory],RAX
LEA RDI,[ArgOutputFile::] ; If ArgOutputFile is specified, it will be used as File_scanNewer.
LEA RBX,[File_scanNewer]
MOV RDX,RDI
CMPB [RDI],0
JNE .35:
LEA RSI,[ArgScanDir::]
GetLength$ RSI
REP MOVSB
MOV RSI,[ScanDirEnd]
MOV ECX,16
SUB RSI,RCX
REP MOVSB
LEA RSI,[Ext_scan]
MOV CL,6
REP MOVSB
.35:FileAssign RBX,RDX
.40:FileMapOpen RBX
JC ErrorMap:
TEST RAX
JZ ErrorZeroSize
MOV [NewerPtr],RSI
MOV [NewerInMemory],RSI
ADD RAX,RSI
MOV [NewerEndInMemory],RAX
; Check if newer > older.
RstSt [Status::],ArgSwappedDates
LEA RSI,[File_scanNewer.Name]
LEA RDI,[File_scanOlder.Name]
GetLength$ RDI
MOV EDX,ECX
GetLength$ RSI
CMP ECX,EDX
JNE .45:
REPE CMPSB
JGE .45:
SetSt [Status::],ArgSwappedDates
.45:; Comparing two scan files starts.
StdOutput Comparing,Eol=yes
StdOutput =B"Old file: """,File_scanOlder.Name,=B"""",Eol=yes
StdOutput =B"New file: """,File_scanNewer.Name,=B"""",Eol=yes
JNSt [Status::],ArgSwappedDates,.50:
StdOutput =B"Warning: old scan is younger than the new scan.",Eol=yes
.50: ; Start to compare File_scanOlder and File_scanNewer.
.LoadBoth:CALL LoadNewer: ; Output: R8=Volume,RBP=RSI=Pointer to a dir-name,RCX=R12=Size or -1 when at EOF.
.LoadOld: CALL LoadOlder: ; Output: R9=Volume,RBX=RDI=Pointer to a dir-name,RDX=Size or -1 when at EOF.
.Compare: CMP RCX,-1
JNE .55:
; File_scanNewer is over or higher. Keep loading File_scanOlder, store it to Buffer5.
CMP RDX,-1
JE .EndCompare: ; Both files are at end.
JMP .NewIsHigher:
.55:CMP RDX,-1
JE .OldIsHigher: ; File_scanOlder is over or higher. Keep loading File_scanNewer, store it to Buffer1.
CMP ECX,EDX
JBE .60:
MOV ECX,EDX
.60:REPE CMPSB ; Compare at the matching size.
MOV RDI,RBX
JB .OldIsHigher:
JA .NewIsHigher:
CMP R12,RDX ; When names match, look at their sizes.
JB .OldIsHigher:
JA .NewIsHigher:
; NoneIsHigher; Directory names are equal.
; ScanIndex goes to Buffer2,3 or 4. SCAN_INDEX.Ptr is relative to [NewerInMemory].
SUB RBP,[NewerInMemory]
MOV [ScanIndex.Ptr],EBP
MOV [ScanIndex.Size],EDX
SUB R8,R9 ; Volume difference is in R8.
MOV [ScanIndex.Volume],R8
JNE .75:
MOV RBX,[Buffer3]
MOV [ScanIndex.Volume],R9
JMP .80:
.75:MOV RBX,[Buffer4]
JB .80:
MOV RBX,[Buffer2]
.80:BufferStore RBX,ScanIndex,SIZE# ScanIndex
JC ErrorBuffer
JMP .LoadBoth:
.NewIsHigher: ; Line retrieved from File_scanNewer is higher or it already ended.
; Keep the line (in R8,RBP,R12) and store the index of line retrieved from File_scanOlder (R9,RBX=RDI,RDX).
; ScanIndex goes to Buffer5, SCAN_INDEX.Ptr is relative to [OlderInMemory].
SUB RBX,[OlderInMemory]
MOV [ScanIndex.Ptr],EBX
MOV [ScanIndex.Size],EDX
NEG R9
MOV [ScanIndex.Volume],R9
MOV RBX,[Buffer5]
BufferStore RBX,ScanIndex,SIZE# ScanIndex
JC ErrorBuffer
MOV RSI,RBP
MOV RCX,R12
JMP .LoadOld:
.OldIsHigher:; Line retrieved from File_scanOlder is higher or it already ended.
; Keep the line (in R9,RBX=RDI,RDX) and store the index of line retrieved from File_scanNewer (R8,RBP=RSI,R12=RCX).
; ScanIndex goes to Buffer1, SCAN_INDEX.Ptr is relative to [NewerInMemory],
SUB RBP,[NewerInMemory]
MOV [ScanIndex.Ptr],EBP
MOV [ScanIndex.Size],R12D
MOV [ScanIndex.Volume],R8
MOV R10,RBX
MOV RBX,[Buffer1]
BufferStore RBX,ScanIndex,SIZE# ScanIndex
JC ErrorBuffer
MOV RBX,R10
CALL LoadNewer:
JMP .Compare:
.EndCompare:
Nr %FOR 1..5 ; Set the lengths of five buffers to NumberxA and NumberxW.
BufferRetrieve [Buffer%Nr]
MOV EAX,ECX
SHR EAX,4
LEA RDI,[Number%Nr]
StoD RDI
XOR EAX,EAX
STOSB
%ENDFOR Nr
SetSt [Status::],ArgDescending
MOV R8,[NewerInMemory]
BufferRetrieve [Buffer1]
SHR ECX,4 ; Let RCX=number of SCAN_INDEX records (16 bytes each).
ShellSort RSI,RCX,16,ByVolume ; ArgDescending, R8=NewerInMemory
BufferRetrieve [Buffer2]
SHR ECX,4 ; Let RCX=number of SCAN_INDEX records (16 bytes each).
ShellSort RSI,RCX,16,ByVolume ; ArgDescending, R8=NewerInMemory
BufferRetrieve [Buffer3]
SHR ECX,4 ; Let RCX=number of SCAN_INDEX records (16 bytes each).
ShellSort RSI,RCX,16,ByVolume ; ArgDescending, R8=NewerInMemory
RstSt [Status::],ArgDescending
BufferRetrieve [Buffer4]
SHR ECX,4 ; Let RCX=number of SCAN_INDEX records (16 bytes each).
ShellSort RSI,RCX,16,ByVolume ; ArgAscending, R8=NewerInMemory
MOV R8,[OlderInMemory]
BufferRetrieve [Buffer5]
SHR ECX,4 ; Let RCX=number of SCAN_INDEX records (16 bytes each).
ShellSort RSI,RCX,16,ByVolume ; ArgAscending, R8=OlderInMemory
StdOutput EuroDirs, =B" has found", Eol=yes
Nr %FOR 1..5
StdOutput Number%Nr, Report%Nr, Eol=yes
%ENDFOR Nr
CLC
.90:RET
ENDP LoadAndCompareScans
ExcludeDir PROC
PUSH RCX,RSI,RDI
MOV RSI,[ExcludedDirsPtr]
.10: CMP RSI,[ExcludedDirsEnd]
JAE .90:
LODSQ ; The first QWORD is directory name size,
MOV RCX,RAX
LODSQ ; the second is a pointer to the excluded name.
JRCXZ .10:
XCHG RSI,RAX
LEA RDI,[Dir]
REPE CMPSB
XCHG RAX,RSI
JNE .10:
STC ; Yes, it is excluded.
.90:POP RDI,RSI,RCX
RET
ENDP ExcludeDir
ParseToBuffer PROC
MOV RBX,RAX ; The output buffer.
.10:MOV RDX,RSI ; Pointer at the (perhaps multiple) directories.
.20:LODSB
CMP AL,0
JE .50:
CMP AL,';' ; ; is the folder names separator.
JNE .20:
CALL .50: ; Store to buffer and continue.
JMP .10:
.50:; Tail-procedure which will store the size RSI-1-RDX and pointer RDX to the buffer RBX.
LEA RAX,[RSI-1]
SUB RAX,RDX
JZ .90: ; Ignore empty directory.
CMPB [RAX+RDX-1],'/'
JNE .70:
DEC RAX
CMP RAX,RDX
JA .70:
INC RAX
.70:PUSH RAX ; Save folder name size on stack.
MOV RAX,RSP
BufferStore RBX,RAX,8 ; Dir name size in bytes.
POP RAX
JC ErrorBuffer
PUSH RDX ; Save pointer to folder name on stack.
MOV RAX,RSP
BufferStore RBX,RAX,8 ; Dir ptr.
POP RAX
JC ErrorBuffer
.90:RET
ENDP ParseToBuffer
/dir_name 0x09 dir_volume_decimal 0x0A
LoadOlder PROC
MOV R10,RCX ; Save preserved RCX.
MOV RDI,[OlderPtr]
MOV RCX,[OlderEndInMemory]
MOV RBX,RDI
MOV RDX,RDI
SUB RCX,RDI
JNA .90:
MOV AL,9
REPNE SCASB
MOV R11,RSI
MOV RSI,RDI
DEC RDI ; Go at HT.
SUB RDI,RDX
XCHG RDI,RDX
LodD RSI
MOV R9,RAX
INC RSI ; Skip LF.
MOV [OlderPtr],RSI
MOV RSI,R11
MOV RCX,R10 ; Restore preserved RCX.
RET ; Normal end.
.90:MOV RDX,-1
MOV RCX,R10 ; Restore preserved RCX.
RET ; End at EOF.
ENDP LoadOlder
/dir_name 0x09 dir_volume_decimal 0x0A
LoadNewer PROC
MOV R10,RDI ; Save preserved RDI.
MOV RDI,[NewerPtr]
MOV RCX,[NewerEndInMemory]
MOV RBP,RDI
SUB RCX,RDI
JNA .90:
MOV AL,9
REPNE SCASB
MOV RSI,RDI
DEC RDI
SUB RDI,RBP
MOV ECX,EDI
LodD RSI
MOV R8,RAX
INC RSI ; Skip LF.
MOV [NewerPtr],RSI
MOV RDI,R10 ; Restore preserved RDI.
MOV RSI,RBP ; Normal end.
MOV R12,RCX
RET ; Normal end.
.90:MOV RDI,R10 ; Restore preserved RDI.
MOV RCX,-1
MOV R12,RCX
RET ; End at EOF.
ENDP LoadNewer
ByName: PROC
MOV R10,RCX
MOV R11,RSI
MOV R12,RDI
MOV EDX,[RDI+SCAN_INDEX.Size]
MOV ECX,[RSI+SCAN_INDEX.Size]
CMP ECX,EDX
JBE .10:
MOV ECX,EDX
.10:MOV ESI,[RSI+SCAN_INDEX.Ptr]
MOV EDI,[RDI+SCAN_INDEX.Ptr]
ADD RSI,[NamesInMemory]
ADD RDI,[NamesInMemory]
DEC ECX
XCHG RSI,RDI
REPE CMPSB
JA .90:
JB .50:
CMP EDX,[R11+SCAN_INDEX.Size]
JAE .90
.50:MOV RAX,[R11+SCAN_INDEX.Ptr]
MOV RDX,[R11+SCAN_INDEX.Volume]
XCHG RAX,[R12+SCAN_INDEX.Ptr]
XCHG RDX,[R12+SCAN_INDEX.Volume]
MOV [R11+SCAN_INDEX.Ptr],RAX
MOV [R11+SCAN_INDEX.Volume],RDX
.90:MOV RCX,R10
RET
ENDP ByName:
ByDate: PROC
XCHG RSI,RDI
PUSH RCX,RSI,RDI
MOV ECX,SIZE# DateMask
REPE CMPSB
POP RDI,RSI,RCX
JAE .90:
MOV RAX,[RSI+0]
MOV RDX,[RSI+8]
XCHG [RDI+0],RAX
XCHG [RDI+8],RDX
MOV [RSI+0],RAX
MOV [RSI+8],RDX
.90:RET
ENDP ByDate
ByVolume:PROC
JSt [Status::],ArgDescending,.10:
XCHG RSI,RDI
.10:MOV RAX,[RSI+SCAN_INDEX.Volume]
MOV RDX,[RDI+SCAN_INDEX.Volume]
CMP RDX,RAX
CLC
JG .90:
MOV R10,RCX
MOV R11,RSI
MOV R12,RDI
XCHG RSI,RDI
JL .60:
MOV ECX,[RSI+SCAN_INDEX.Size]
MOV ESI,[RSI+SCAN_INDEX.Ptr]
MOV EDX,[RDI+SCAN_INDEX.Size]
MOV EDI,[RDI+SCAN_INDEX.Ptr]
ADD RSI,R8
ADD RDI,R8
CMP ECX,EDX
JBE .20:
MOV ECX,EDX
.20:REPE CMPSB
JA .80:
JB .50:
CMP EDX,[R11+SCAN_INDEX.Size]
JAE .80:
.50:MOV RAX,[R11+SCAN_INDEX.Volume]
MOV RDX,[R12+SCAN_INDEX.Volume]
.60:MOV [R12+SCAN_INDEX.Volume],RAX
MOV [R11+SCAN_INDEX.Volume],RDX
MOV RAX,[R11+SCAN_INDEX.Ptr]
MOV RDX,[R12+SCAN_INDEX.Ptr]
MOV [R12+SCAN_INDEX.Ptr],RAX
MOV [R11+SCAN_INDEX.Ptr],RDX
STC
.80:MOV RCX,R10
.90:RET
ENDP ByVolume:
SetToday PROC
MOV ECX,[ArgTimeZone::] ; 0, 1, .. 23.
MOV EAX,60*60
MUL ECX
MOV ESI,EAX
LinABI time,0 ; Returns RAX=seconds since 1.1.1970 00:00:00 UTC.
LEA RBX,[SystemTime]
Time2SystemTime RBX, TZ=RSI ; Convert time RAX to a SystemTime structure.
LEA RDI,[Today]
.50:MOVZXW EAX,[RBX+SYSTEMTIME.wYear]
StoD RDI,Size=4,Signed=no,Align=right,LeadingZeroes=yes
MOV AL,'.'
STOSB
MOVZXW EAX,[RBX+SYSTEMTIME.wMonth]
StoD RDI,Size=2,Signed=no,Align=right,LeadingZeroes=yes
MOV AL,'.'
STOSB
MOVZXW EAX,[RBX+SYSTEMTIME.wDay]
StoD RDI,Size=2,Signed=no,Align=right,LeadingZeroes=yes
MOV AL,'_'
STOSB
MOVZXW EAX,[RBX+SYSTEMTIME.wHour]
StoD RDI,Size=2,Signed=no,Align=right,LeadingZeroes=yes
MOV AL,'.'
STOSB
MOVZXW EAX,[RBX+SYSTEMTIME.wMinute]
StoD RDI,Size=2,Signed=no,Align=right,LeadingZeroes=yes
RET
ENDP SetToday
SetMaxDaysAgo PROC MOV ECX,[ArgTimeZone::] MOV EAX,60*60 MUL ECX MOV EDI,EAX MOV EAX,24*60*60 MOV ECX,[ArgMaxDays::] ; Default is 32 days. MUL ECX MOV ESI,EAX LinABI time,0 ; Returns RAX=seconds since 1.1.1970 00:00:00 UTC. SUB EAX,ESI LEA RBX,[SystemTime] Time2SystemTime RBX,TZ=RDI ; Convert time RAX to a SystemTime structure. LEA RDI,[MaxDaysAgo] ; MaxDaysAgo DB "yyyy.mm.dd_hh.mm",0 ; Today-ArgMaxDays in bytes. JMP SetToday.50: ; Use the tail of procedure SetToday. ENDP SetMaxDaysAgo
EraseOlderFile: PROC
LEA RSI,[RBX+FILE64.Name]
GetLength$ RSI
LEA RSI,[RSI+RCX-4-SIZE# MaxDaysAgo]
LEA RDI,[MaxDaysAgo]
MOV ECX,SIZE# MaxDaysAgo -1
REPE CMPSB ; Compare datetime encoded in the file name.
JAE .90:
LEA RAX,[RBX+FILE64.Name]
StdOutput =B"Old scan """,RAX,=B""" was deleted.",Eol=yes
FileDelete RBX
.90:RET
ENDP EraseOlderFile:
"2024.12.31_11.00",0from the scan file name to ScanDirBuffer.
eurodirs_2024.12.31_11.00.scan.
StoreFileName:PROC
BufferStore [ScanDirBuffer],RSI,SIZE# DateMask - 1 ; 16 bytes.
JC ErrorBuffer
RET
ENDP StoreFileName
ReadFile_ini PROC
SetSt [Status::],ArgFromFile
StdOutput =B"Configuration """,File_ini.Name,=B'"'
FileStreamOpen File_ini,BufSize=4K
JNC .10:
StdOutput =B" was not found.",Eol=yes
JMP .90:
.10:FileStreamReadLn File_ini
JBE .80:
MOV ECX,EAX
; The first line may begin with BOM.
MOV AX,[RSI]
CMPW AX,0xBBEF ; UTF-8 BOM?
JNE .20:
ADD RSI,3 ; Skip the BOM.
SUB ECX,3
JB .80:
JMP .20:
.15:FileStreamReadLn File_ini ; Read a line to RSI,RAX.
JBE .80:
MOV ECX,EAX
.20:CALL ArgParse::
JNC .15:
.Help:StdOutput ErrorMessage::,Eol=yes
JNSt [Status::],ArgHelpRequired,.70:
LEA RDI,[Help+3]
StdOutput RDI, Eol=yes
.70:TerminateProgram 8
.80:FileClose File_ini
StdOutput =B" was accepted.",Eol=yes
.90:RstSt [Status::],ArgFromFile
RET
ENDP ReadFile_ini
Tidy: LEA RDI,[TermIO::]
LinABI ioctl,0,TCGETS,RDI ; Load current local terminal status.
SetSt [RDI+TERMIO.c_lflag],ICANON+ECHO
LinABI ioctl,0,TCSETS,RDI ; Enable terminal echo.
FileClose File_scan, File_scanOlder, File_scanNewer
StdOutput EscShowCursor::,EscDisableMouse::,EscErase::,Eol=yes
RET
ErrorBuffer:
CALL Tidy:
StdOutput =B"Error allocating memory.",Eol=yes
TerminateProgram 8
ErrorZeroSize:; RBX=^FILE64
CALL Tidy:
StdOutput =B'Error: zero size of the file "'
JMP Msg
ErrorMap: ; RBX=^FILE64
CALL Tidy:
StdOutput =B'Error: memory-mapping the file "'
JMP Msg
ErrorFile: ; RBX=^FILE64
CALL Tidy:
StdOutput =B'Error: could not open the file "'
JMP Msg
ErrorWrite: ; RBX=^FILE64
CALL Tidy:
StdOutput =B'Error writing to the file "'
Msg: LEA RAX,[RBX+FILE64.Name]
StdOutput RAX,=B'"',Eol=yes
TerminateProgram 8
ENDPROGRAM dirslinc