Last month I wrote a program to read out FILEDIR.SAG. It was useful to get the name of the programmers which did the last save and/or catalog on a module plus the date of the regarding event. The program is tested on Windows XP and Solaris 8.
One questions remains: What do all the fields mean, which I marked with question marks? Any ideas?
*
* Read out FILEDIR.SAG on Open Systems
* ************************************
*
define data local
01 #options
02 filedir (A) DYNAMIC const
*
* uncomment the following line for an example on Solaris
* <'/sag/nat/fuser/SYSTEM/FILEDIR.SAG'>
*
* uncomment the following line for an example on windows
<'C:\Programme\Software AG\Natural\Natapps\Fuser\SYSTEM\FILEDIR.SAG'>
*
01 #header136 (B136) /* for SUN_SOLA
01 redefine #header136
02 STRUCTURE /* for move by name
03 NUMBER_OF_MODULES (I02) /* Number of modules in FILEDIR.SAG
03 unknown_01 (I02) /* ???? always 1
03 unknown_02 (A08) /* ???? always space
03 unknown_03 (B04) /* ???? always H'00530000'
03 unknown_04 (I04) /* ???? always 0
03 unknown_05 (I04) /* ???? always 0 /* missed on Windows
03 unknown_06 (I04) /* ???? always 1
03 unknown_07 (I04) /* ???? always 1
03 HEADER_SIZE (I04) /* Size of Header in Bytes
03 LINE_SIZE (I04) /* Size of Line in Bytes
03 unknown_08 (I04) /* ???? always 0 /* missed on Windows
03 CHANGE_COUNTER (I04) /* Number of changes (stow = +2)
03 unknown_09 (I04/1:22) /* ???? always 0
*
01 #header116 (B116) /* for WNT-X86 and LINUX
01 redefine #header116
02 STRUCTURE /* for move by name
03 NUMBER_OF_MODULES (I02) /* Number of modules in FILEDIR.SAG
03 unknown_01 (I02) /* Size of Header in Bytes
03 unknown_02 (A08)
03 unknown_03 (B04)
03 unknown_04 (I04)
03 unknown_06 (I04)
03 unknown_07 (I04)
03 HEADER_SIZE (I04) /* Size of Header in Bytes
03 LINE_SIZE (I04) /* Size of Line in Bytes
03 CHANGE_COUNTER (I04)
03 unknown_09 (I04/1:19)
*
01 #line (B288)
01 redefine #line
02 LINE_TYPE (I04)
* /* 1 = normal
* /* 2 = assignment long names to short names for Subroutines
* /* 5 = assignment long names to short names for Classes
* /* 6 = assignment long names to short names for Functions
02 MODULE_LONG (A32) /* long names for subroutine etc.
02 unknown_01 (I04) /* ???? always 0
02 unknown_02 (I04) /* ???? always 0
02 MODULE (A08) /* short module name on filesystem
02 unknown_03 (I04) /* ???? always 0
02 SUB_TYPE (I04) /* ???? probably
* /* 1 = normal
* /* 3 = Dialog with OCX
* /* 5 = new Source-Format of DDM
02 unknown_04 (I04) /* ???? always 1
02 MODULE_TYPE (I04)
* /* decimal value of character, e.g.
* /* 65 --> "A" --> Parameter
* /* 67 --> "C" --> Copycode
02 DDM_DBID (I04)
02 DDM_FILE_NO (I04)
02 MODE (I04) /* 0 = REPORT, 1 = STRUCTURED
02 SOURCE_CATALOG (I04)
* /* 1 = only source available
* /* 2 = only catalog available
* /* 3 = both available
02 SOURCE_USER_ID (A08)
02 unknown_05 (I04) /* ???? always 0
02 SOURCE_SIZE (I04)
02 SOURCE_DATE_DAY (I04)
02 SOURCE_DATE_MONTH (I04)
02 SOURCE_DATE_YEAR (I04) /* sometimes only the last 2 digits
02 SOURCE_DATE_HOUR (I04)
02 SOURCE_DATE_MIN (I04)
02 unknown_06 (I04/1:8) /* ???? always 0
02 CATALOG_USER_ID (A08)
02 unknown_07 (I04) /* ???? always 0
02 CATALOG_SIZE (I04)
02 CATALOG_DATE_DAY (I04)
02 CATALOG_DATE_MONTH (I04)
02 CATALOG_DATE_YEAR (I04) /* sometimes only the last 2 digits
02 CATALOG_DATE_HOUR (I04)
02 CATALOG_DATE_MIN (I04)
02 unknown_08 (I04/1:25) /* ???? always 0
*
01 #line_type2 (B288)
01 redefine #line_type2
02 LINE_TYPE (I04)
02 MODULE_LONG (A32)
02 unknown_01 (I04) /* ???? always 0
02 MODULE (A08)
02 unknown_02 (I04/1:60) /* ???? always 0
*
01 #header
02 NUMBER_OF_MODULES (I02)
02 HEADER_SIZE (I04)
02 LINE_SIZE (I04)
02 CHANGE_COUNTER (I04)
*
end-define
*
define work file 1 #options.filedir type 'UNFORMATTED'
*
reset #header
*
decide on first value of *OPSYS
*
value 'SUN_SOLA' /* --> read first 136 Bytes as a header
read work file 1 once #header136
move by name #header136.STRUCTURE to #header
if #header.header_size ne 136
write 'invalid header size for OPSYS' *OPSYS
stop
end-if
*
value 'WNT-X86' /* --> read first 116 Bytes as a header
read work file 1 once #header116
move by name #header116.STRUCTURE to #header
if #header.header_size ne 116
write 'invalid header size for OPSYS' *OPSYS
stop
end-if
*
none value
write 'operating system' *OPSYS 'not supported'
stop
*
end-decide
*
write nohdr
'HEADER INFORMATION' /
'----------------------------------' /
'Number of Modules :' 6X #header.NUMBER_OF_MODULES /
'HEADER_SIZE in Bytes :' #header.HEADER_SIZE /
'LINE_SIZE in Bytes :' #header.LINE_SIZE /
'CHANGE_COUNTER :' #header.CHANGE_COUNTER
*
NEWPAGE
*
if #header.line_size ne 288
write 'invalid line size' #header.line_size
stop
end-if
*
read work file 1 #line
*
decide on first value of #line.LINE_TYPE
value 1
perform display_linetype1
*
value 2,5,6
move #line to #line_type2
write #line_type2.MODULE_LONG '-->' #line_type2.MODULE
*
none value
write 'invalid linetype' #line.LINE_TYPE 'detected'
stop
end-decide
*
SKIP 1
end-work
*
close work file 1
*
*
DEFINE SUBROUTINE DISPLAY_LINETYPE1
*
display
* '=' #line.LINE_TYPE
* '=' #line.SUB_TYPE
* '=' #line.MODULE_LONG
'=' #line.MODULE /
'TYPE' #line.MODULE_TYPE
'=' #line.DDM_DBID /
'=' #line.DDM_FILE_NO
'=' #line.MODE /
'SRC+CAT' #line.SOURCE_CATALOG
'SRC-USER' #line.SOURCE_USER_ID /
'CAT-USER' #line.CATALOG_USER_ID
'SRC-SIZE' #line.SOURCE_SIZE /
'CAT-SIZE' #line.CATALOG_SIZE
* 'SRC-DAY' #line.SOURCE_DATE_DAY /
* 'CAT-DAY' #line.CATALOG_DATE_DAY
* 'SRC-MONTH' #line.SOURCE_DATE_MONTH /
* 'CAT-MONTH' #line.CATALOG_DATE_MONTH
'SRC-YEAR' #line.SOURCE_DATE_YEAR /
'CAT-YEAR' #line.CATALOG_DATE_YEAR
* 'SRC-HOUR' #line.SOURCE_DATE_HOUR /
* 'CAT-HOUR' #line.CATALOG_DATE_HOUR
* 'SRC-MIN' #line.SOURCE_DATE_MIN /
* 'CAT-MIN' #line.CATALOG_DATE_MIN
*
END-SUBROUTINE /* DISPLAY_LINE
*
end
It’s almost like creating DDMs for the MF FUSR/FNAT and reading those files directly instead of using the supplied APIs, but I’m sure no one has ever done that
If it’s already in human-readable form I’m not sure it would be concidered reverse engineering since there isn’t anything to reverse, it’s just formatting… although since there are I and B format fields being used the output does need to be translated somewhat.
It would be interesting to hear what the “official” response would be.
@Ralph Zbrog: Reverse engineering? No I’m not that good! If you hexdump the FILEDIR.SAG you can almost read it like a newspaper.
But if this is treated as reverse engineering by SAG, I will delete this post immediately.
@Wolfgang Winter: I promise: I’ve searched for that in SYSEXT approx. half an hour. I thought about using USR1057N for that, but it was way to slow for my over 10.000 modules to list. So I thougt it is easier to read out FILEDIR.SAG by myself.
And to be honest: I didn’t read out FILEDIR.SAG the first time, so it doesn’t take that much time to do a “quick Natural hack”. About two years ago, we got a problem with unreadable FILEDIR.SAG on NFS-Mounted devices (between Linux and Windows). At that time, the Software AG couldn’t help us immediately. But that problem became obsolete by installing the Natural Development server.
After your tips and hints I tried to solve the problem just using SYSEXT-Modules. I just want to learn …
But then I realized, that USR0330 doesn’t ouput DDM-Modules. So I tried to get the DDM-Information from anywhere else. But USR1056N doesn’t deliver user and time information. Any ideas to solve that?
Natural 6.2.2 has a new Structure for the FILEDIR.SAG. I adjust the code from Matthias to process 6.2.2-files and i tried to use a class oriented approach.
The result is shown below (it is the load-Method for the FileDir-Class):
define data
Parameter
1 #pobjColl Handle of Object /* SAG.Global.Collection
1 #pobjParameter Handle of Object /* SOS.Nat.FileDir
* <usercode name="oda" class='SOS.Nat.FileDir' >
* Object using oFileD
* Object using oClsDflt /* Interface iSAGClassDefault
* </usercode> <!-- name="oda" class='SOS.Nat.FileDir' -->
*
Local using oFileDir
local using def-test
*
* <usercode name="localdata" >
Local
1 #objCollection handle of object /* SAG.Global.Collection
1 #objI handle of object /* SOS.Nat.FileDir
1 #ISN (P10)
1 #flgEscape (L)
*
Local
1 #iCount (i4)
1 #objTemp Handle of Object
1 #strName (a8)
*
1 #DateAndTime (A14)
1 Redefine #DateAndTime
2 #YYYY (a4)
2 #DateWithoutYear (A10)
2 Redefine #DateWithoutYear
3 #MM (a2)
3 #DD (a2)
3 #HH (A2)
3 #II (A2)
3 #SS (A2)
Local
1 #objFileDir Handle of Object /* SAG.IO.File
1 #flgT (l)
1 #I (i4)
* change it to your requirements
1 filedirPath (A256) init <'/sag/shr/fuser/'>
1 filedirLib (A256)
1 filedirFile (A11) const <'FILEDIR.SAG'>
*
1 #FileDirSAGName (A256)
1 #FileDirXMLName (A256)
1 #sav-Date (A20)
1 #cat-Date (A20)
*
1 #header80 (B80) /* Nat6.2.2
1 Redefine #header80
2 Structure
3 #text (a4)
3 #Filler-1 (i4/1:5)
3 Number_of_Modules (i4)
3 #Text2 (a8)
3 #Filler-2 (i4/1:4)
3 Header_Size (i4)
3 Line_Size (i4)
3 CHANGE_COUNT (I4) /* Number of changes (stow = +2)
*
1 #header136 (B136)
1 redefine #header136
2 STRUCTURE
3 NUMBER_OF_MODULES (I02) /* Number of modules in FILEDIR.SAG
3 f_01 (I02)
3 f_02 (A08)
3 f_03 (B04)
3 f_04 (I04)
3 f_05 (I04)
3 f_06 (I04)
3 f_07 (I04)
3 HEADER_SIZE (I04) /* Size of Header in Bytes
3 LINE_SIZE (I04) /* Size of Line in Bytes
3 f_08 (I04)
3 CHANGE_COUNTER (I04) /* Number of changes on SRC and GP
3 f_09 (I04/1:22)
*
1 #header116 (B116) /* for WNT-X86 and LINUX
1 redefine #header116
2 STRUCTURE
3 NUMBER_OF_MODULES (I02) /* Number of modules in FILEDIR.SAG
3 f_01 (I02)
3 f_02 (A08)
3 f_03 (B04)
3 f_04 (I04)
3 f_06 (I04)
3 f_07 (I04)
3 HEADER_SIZE (I04) /* Size of Header in Bytes
3 LINE_SIZE (I04) /* Size of Line in Bytes
3 CHANGE_COUNTER (I04)
3 f_09 (I04/1:19)
*
1 #l (B312) /* Nat 6.2.2
1 redefine #l
2 Structure
3 LINE_TYPE (I4)
3 MODULE_LONG (A32) /* long names for subroutine etc.
3 f_02 (I4)
3 MODULE (a8) /* short module name on filesystem
3 f_03 (I4)
3 f_030 (I4)
3 SUB_TYPE (I4)
* /* 1 = normal
* /* 3 = Dialog with OCX
* /* 5 = new Source-Format of DDM
3 f_04 (I4)
3 MODULE_TYPE (I4) /* internal Object Type:
3 Redefine Module_Type
4 Filler-MT (B3)
4 ModuleTypeA (a1)
3 DDM_DBID (I4)
3 DDM_FILE_NO (I4)
3 MODE (I4) /* 0 = REPORT, 1 = STRUCTURED
3 Src_CATALOG (I4)
* 1 = source only, 2 = catalog only, 3 = both available
3 Src_SIZE (I4)
3 SD_YEAR (I4) /* sometimes only the last 2 digits
3 SD_MONTH (I4)
3 SD_DAY (I4)
3 SD_HOUR (I4)
3 SD_MIN (I4)
3 SD_Secnds (I4) /* ???? always 0 nat622
3 Src_USER_ID (a8)
3 f_07 (I4) /* ???? always 0
3 Cat_SIZE (I4)
3 Cat_DATE_YEAR (I4) /* sometimes only the last 2 digits
3 Cat_DATE_MONTH (I4)
3 Cat_DATE_DAY (I4)
3 Cat_DATE_HOUR (I4)
3 Cat_DATE_MIN (I4)
3 Cat_Date_Secnds (I4)
3 Cat_USER_ID (a8)
*
1 #l-old (B288)
1 redefine #l-old
2 Structure
3 LINE_TYPE (I04)
3 MODULE_LONG (A32) /* long names for subroutine etc.
3 f_01 (I04)
3 f_02 (I04)
3 MODULE (A08) /* short module name on filesystem
3 f_03 (I04)
3 SUB_TYPE (I04)
* 1 = normal, 3 = Dialog with OCX, 5 = new Source-Format of DDM
3 f_04 (I04)
3 MODULE_TYPE (I04)
3 DDM_DBID (I04)
3 DDM_FILE_NO (I04)
3 MODE (I04) /* 0 = REPORT, 1 = STRUCTURED
3 Src_CATALOG (I04)
3 Src_USER_ID (A08)
3 f_05 (I04)
3 Src_SIZE (I04)
3 SD_DAY (I04)
3 SD_MONTH (I04)
3 SD_YEAR (I04) /* sometimes only the last 2 digits
3 SD_HOUR (I04)
3 SD_MIN (I04)
3 f_06 (I04/1:8)
3 Cat_USER_ID (A08)
3 f_07 (I04)
3 Cat_SIZE (I04)
3 Cat_DATE_DAY (I04)
3 Cat_DATE_MONTH (I04)
3 Cat_DATE_YEAR (I04) /* sometimes only the last 2 digits
3 Cat_DATE_HOUR (I04)
3 Cat_DATE_MIN (I04)
3 f_08 (I04/1:25)
*
1 #l_type2 (B312)
1 redefine #l_type2
2 LINE_TYPE (I4)
2 MODULE_LONG (A32)
2 f_01 (I4)
2 MODULE (a8)
2 f_02 (I4/1:60)
*
1 #header
2 NUMBER_OF_MODULES (I2)
2 HEADER_SIZE (I4)
2 LINE_SIZE (I4)
2 CHANGE_COUNT (I4)
*
end-define
Send Method 'Clear' to #pobjColl
FileDirLib := #pobjParameter.LibraryName
*
Perform LoadData
*
Define Subroutine LoadData
* <usercode name="loaddata">
Compress filedirPath filedirLib '/' filedirFile into #FileDirSAGName leaving no space
*
create object #objFileDir of Class 'SAG.IO.FILE'
#objFileDir.FileName := #FileDirSAGName
send 'Exist' to #objFileDir with #flgT
if not #flgT Then
write 'not found:' #FileDirSAGName (al=70)
escape Routine
end-if
*
define work file 1 #FileDirSAGName type 'UNFORMATTED'
*
reset #header
read work file 1 once #header80
*
if #header80.#text = mask(NNNN) Then
move by name #header80.Structure to #header
read work file 1 #l
Perform ProcessLine
end-work
*
else
close work file 1
decide on first value of *OPSYS
*
value 'SUN_SOLA', 'HPUX_64'
read work file 1 once #header136
move by name #header136.STRUCTURE to #header
if #header.header_size ne 136
write 'invalid header size for OPSYS' *OPSYS
escape routine
end-if
*
value 'WNT-X86'
read work file 1 once #header116
move by name #header116.STRUCTURE to #header
if #header.header_size ne 116
write 'invalid header size for OPSYS' *OPSYS
escape routine
end-if
*
none value
write 'operating system' *OPSYS 'not supported'
escape routine
*
end-decide
*
read work file 1 #l-old
move by name #l-old.structure to #l.Structure
Perform ProcessLine
end-work
*
end-if
*
close work file 1
* </usercode> <!-- name="loaddata" -->
ignore
End-Subroutine /* LoadData
* <usercode name="subroutines">
Define Subroutine ProcessLine
*
examine #l.Module for H'00' and replace with ' '
examine #l.Module_long for H'00' and replace with ' '
examine #l.Src_USER_ID for H'00' and replace with ' '
examine #l.Cat_USER_ID for H'00' and replace with ' '
*
send Method 'New' to #pobjColl with #objI
if #objI eq null-handle then
escape routine
end-if
*
decide on first value of #l.LINE_TYPE
value 1
reset #Object-Data.#Saved-Date
reset #Object-Data.#Compiled-Date
*
if #l.Src_CATALOG = 1 or= 3 Then
if #l.SD_Year < 50 then
add 2000 to #l.SD_Year
else
add 1900 to #l.SD_Year
end-if
*
Move Edited #l.SD_Year (em=9999) to #yyyy
Move Edited #l.SD_Month (em=99) to #mm
Move Edited #l.SD_Day (em=99) to #dd
Move Edited #l.SD_hour (em=99) to #hh
Move Edited #l.SD_Min (em=99) to #ii
if #DateWithoutYear ne '00000000' Then
Move edited #DateAndTime to
#Object-Data.#saved-Date (em=yyyymmddhhii)
end-if
end-if
*
if #l.Src_CATALOG = 2 or= 3 then
if #l.Cat_Date_Year < 50 then
add 2000 to #l.Cat_Date_Year
else
add 1900 to #l.Cat_Date_Year
end-if
*
Move Edited #l.Cat_Date_Year (em=9999) to #yyyy
Move Edited #l.Cat_Date_Month (em=99) to #mm
Move Edited #l.Cat_Date_Day (em=99) to #dd
Move Edited #l.Cat_Date_hour (em=99) to #hh
Move Edited #l.Cat_Date_Min (em=99) to #ii
if #DateWithoutYear ne '00000000' Then
Move edited #DateAndTime to
#Object-Data.#Compiled-Date (em=yyyymmddhhii)
end-if
end-if
*
#objI.Line_Type := #l.LINE_TYPE
#objI.Sub_Type := #l.SUB_TYPE
#objI.Long_Name := #l.MODULE_LONG
#objI.Name := #l.MODULE
#objI.Object_Type := #l.MODULE_TYPE
#objI.DBID := #l.DDM_DBID
#objI.File_No := #l.DDM_FILE_NO
#objI.Mode := #l.MODE
#objI.Object_Form := #l.Src_CATALOG
#objI.Saved_by := #l.Src_USER_ID
#objI.Compiled_by := #l.Cat_USER_ID
#objI.Src_Size := #l.Src_SIZE
#objI.Object_Size := #l.Cat_SIZE
#objI.Saved_Date := #Object-Data.#saved-Date
#objI.Compiled_Date := #Object-Data.#Compiled-Date
*
decide on first value of #l.Module_Type
Value 52
#objI.FileName_Extension := 'NS4'
Value 55
#objI.FileName_Extension := 'NS7'
Value 65
#objI.FileName_Extension := 'NSA'
Value 67
#objI.FileName_Extension := 'NSC'
Value 68
#objI.FileName_Extension := 'NSD'
Value 76
#objI.FileName_Extension := 'NSL'
Value 77
#objI.FileName_Extension := 'NSM'
Value 78
#objI.FileName_Extension := 'NSN'
Value 80
#objI.FileName_Extension := 'NSP'
Value 83
#objI.FileName_Extension := 'NSS'
Value 84
#objI.FileName_Extension := 'NST'
none value
#objI.FileName_Extension := '???'
end-decide
*
value 2 , 5 , 6
move #l to #l_type2
#objI.Sub_Type := #l.SUB_TYPE
#objI.Line_Type := #l.LINE_TYPE
#objI.Name := #l_type2.MODULE
#objI.Long_Name := #l_type2.MODULE_LONG
send 'ItemByKey' to #pobjColl with #l_type2.Module #objTemp
if #objTemp ne null-handle then
#objTemp.Long_Name := #l_type2.Module_long
end-if
none value
write 'invalid linetype' #l.LINE_TYPE 'detected'
escape Bottom
end-decide
*
* </usercode> <!-- name="subroutines" -->
End-Subroutine /* ProcessLine
*
Define Subroutine getNewItem
send Method 'New' to #pobjColl with #objI
if #objI ne null-handle then
ignore
else
write *program 'Null-Handle, terminate ...'
escape bottom
end-if
End-Subroutine /* getNewItem
*
end /* SOS.Nat.FileDir::Load (cFILED00.nsn)
(sorry for the strange indentation, but the maximum message length here is 10000 characters. “struct” is your friend )
if someone wants to get the code of the class pls give me a hint.
Has someone else experiences with (local) natural-classes (not COM or DCOM)?
Beware that FILEDIR.SAG layout also changes with NATURAL 6.2.
(you are probably aware of it but if not …)
Header is X’50’ byes and each entry X’138’.
I am in same position as you - could use USR* exits but too slow.
Also there is no USR* which gives just the dir info.
There appears to be an exit which gives minimal dir info and the
read source code (1st…) entry which will give the dir info but is slow.