EuroAssembler Index Manual Download Source Macros


Sitemap Links Forum Tests Projects

dirslinc.htm
Data
Data
Procedures
ByDate
ByName
ByVolume
DirVolume
EraseOlderFile
Error
ExcludeDir
LoadAndCompareScans
LoadNewer
LoadOlder
MainCon
ParseToBuffer
ReadFile_ini
Scan
SetMaxDaysAgo
SetToday
StoreFileName
StoreIndex

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
The main procedure executed both in console and in graphic mode.
It reads arguments and then it decides by /ScanNow=. If /SN=true, it scans all directories, writes a scan file to /ScanDir and terminates.
When /ScanNow=false (default), EuroDirs opens a graphic window and the user can browse directories, change the /ViewDirs mode or change older and newer scan files.
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::
Scan

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=.

Called by
MainCon.
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
This is a recursive procedure which will scan a single directory.
The directory name in Dir may or may not be terminated with a backslash. It is always zero terminated.
Input
Dir contains name of the examined folder, encoded in UTF-8.
Output
ZF=0, RAX=total data volume of Dir's files and subdirectories.
Error
ZF=1, RAX=0 if the directory does not exist or if it is excluded.
Called by
Scan. It also calls itself.
Clobbers
RCX,RDX,RSI,RDI,R8,R11
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
Procedure StoreIndex takes the directory name from Dir and stores the name and index to File_names and File_index.
Input
Dir
Output
Files are updated.
Called by
Scan and DirVolume.
Clobbers
RAX,RBX,RDX
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
LoadAndCompareScans

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.

Input
-
Output
CF=0, Buffer1,Buffer2,Buffer3,Buffer4,Buffer5 are loaded with the differences and sorted.
Error
CF=1 less than two scans were found.
Called by
DirVolume.
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
Procedure ExcludeDir returns Carry flag when the directory name in Dir is among the ArgExcludeDirs.
Directory name does not end with / unless it is the root directory.
Input
ExcludedDirsPtr, ExcludedDirsEnd
Output
CF=1 if Dir is excluded from scanning.
CF=0 Dir is not excluded, perform the scan.
Called by
Scan.
Clobbers
RAX
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
Procedure ParseToBuffer will parse a multiple semicolon-separated pathes pointed to by RSI and for each path it stores two QWORDs to the buffer: directory size and pointer.
Called by
Scan.
Input
RAX=output empty BUFFER
RSI=pointer to ArgIncludeDirs or ArgExcludeDirs.
Output
The buffer at RAX is filled with two QWORDS {size and pointer) for each semicolon-separated directory.
When the directory name ends with /, it is omitted if it's not the root directory /.
Error
Abort by jump to ErrorBuffer.
Called by
Scan
Clobbers
RAX,RBX,RDX,RSI,RDI
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
LoadOlder
Load one directory name and volume from File_scanOlder mapped at OlderInMemory..OlderEndInMemory.
Each record has variable size: /dir_name 0x09 dir_volume_decimal 0x0A
Input
OlderPtr is pointer to one record,
OlderEndInMemory points to the end of records.
Output
R9=volume,
RBX=RDI=pointer to a directory name,
RDX=directory name size or -1 when at EOF.
OlderPtr is updated.
Preserves
R8,RCX,RSI,RBP
Clobbers
RAX,R10,R11
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
LoadNewer
Load one directory name and volume from File_scanNewer mapped at NewerInMemory..NewerEndInMemory.
Each record has variable size: /dir_name 0x09 dir_volume_decimal 0x0A
Input
NewerPtr is pointer to one record,
NewerEndInMemory points to the end of records.
Output
R8=volume,
RBP=RSI=pointer to a directory name,
RCX=R12=directory name size or -1 when at EOF.
NewerPtr is updated.
Preserves
R9,RBX,RDX,RDI
Clobbers
RAX,R10
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
Procedure ByName is a callback function to sort array of indexes by directory name.
Input
RSI,RDI point to SCAN_INDEX records to compare.
Clobbers
RAX,RDX,RSI,RDI,R10,R11,R12
Called by
Scan.
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
Procedure ByDate is a callback function to sort a table of DateMask records in ScanDirBuffer.
Input
RSI,RDI point to DateMask records to compare.
Clobbers
RAX,RDX,R10,R11,R12
Called by
Scan.
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
Procedure ByVolume is a callback function to sort a table of SCAN_INDEX records by volume and then by directory name.
Input
RSI,RDI point to SCAN_INDEX records to compare.
R8 contains [NewerInMemory] or [OlderInMemory].
ArgDescending specifies the sort direction.
Clobbers
RAX,RDX,RSI,RDI
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
Procedure SetToday will set a nominal local time of a scan into string variable Today.
Called by
MainCon.
Clobbers
RAX,RBX,RCX,RSI,RDI
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
Procedure SetMaxDaysAgo will set a current time minus /MaxDays= into string variable MaxDaysAgo.
Called by
MainCon.
Uses
SetToday.
Clobbers
RAX,RBX,RCX,RSI,RDI
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
Procedure EraseOlderFile is called as a callback from FileEach in Scan.
Input
RBX=^FILE64 with the name.
RDI=^WIN32_FIND_DATAW
Clobbers
RAX,RCX,RSI,RDI
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:
StoreFileName
Procedure StoreFileName is called as a callback from FileEach.
It stores the ASCII strings such as "2024.12.31_11.00",0 from the scan file name to ScanDirBuffer.
Input
RSI=pointer to file name without path, e.g. eurodirs_2024.12.31_11.00.scan.
[ScanDirBuffer] is the buffer for storing the dates.
Clobbers
RAX,RSI
StoreFileName:PROC
    BufferStore [ScanDirBuffer],RSI,SIZE# DateMask - 1 ; 16 bytes.
    JC ErrorBuffer
    RET
   ENDP StoreFileName
ReadFile_ini
Procedure ExcludeDir reads File_ini (UTF-8) and parses its each line into configuration, in UTF-8, too.
It writes information about the file name and whether it was found.
Called by
MainCon.
Input
File_ini is an assigned file.
Clobbers
RAX,RCX,RDX,RSI,RDI
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
Error
Simplified error handling will abort the program with errorlevel 8.
Input
RBX=^FILE64 which caused the error.
Called by
Scan
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

▲Back to the top▲