Skip to content

Object (file) entry - 64 bytes (Phase 2)

Every file version on a directory device has one object entry: a 64-byte record in the object file holding the file name, type, access rights, file-type flags, size, dates, and the block pointer to the file's data. The object file is an indexed file: the object_file_ptr in the master block points at an index block, whose contiguous block-pointers each name a data page holding 32 object entries (2048 / 64).

Sources: real disk ~/repos/nd100x/SMD0.IMG (PACK-ONE); NDFS ndfs-c/src/object_entry.c + include/ndfs/object_entry.h (the reader that round-trips these bytes); ndtool --stat (NOT an independent cross-reader - it links the same ndfs-c library: target_link_libraries(ndtool ndfs), so it cannot disagree with it); carved 006-S3FS ROBJE/FOBJB/RINDX (producing/consuming code). On-disk multi-byte values are big-endian words.


1. Locating a real object entry

object_file_ptr = 0x400048FC -> INDEXED, block 44374B (18684). The index block at page 18684 begins:

0247e000: 0000 48fd 0000 2e3a ...   (block 44374B = 18684, byte 0)

First index entry 0000 48FD = CONTIGUOUS block 44375B (18685) - the first object data page. (Further index entries 0000 2E3A = 27072B/11834 and more list the object file's other data pages; the object file on PACK-ONE spans several pages totalling 188 files.) Data page 18685 holds 32 in-use entries; its first three (192 bytes):

0247e800: 8000 5349 4e54 5241 4e27 0000 0000 0000   <- entry 0: SINTRAN
0247e810: 0000 4441 5441 0000 0000 0007 0020 0000
0247e820: 0000 0000 0000 0000 b8af 2bf6 0000 0000
0247e830: 0000 0000 0000 003f ffff ffff 0000 0001
0247e840: 8000 4d41 434d 2d41 5245 4127 0000 0000   <- entry 1: MACM-AREA
0247e850: 0000 4441 5441 0001 0001 0007 0020 0000
0247e860: 0000 0001 0000 0000 b8af 2bf7 0000 0000
0247e870: 0000 0000 0000 0040 ffff ffff 0000 0040
0247e880: 9000 5345 4746 494c 3027 0000 0000 0000   <- entry 2: SEGFIL0
0247e890: 0000 4441 5441 0002 0002 00e7 0020 0000
0247e8a0: 0000 0002 0000 0045 b8af 2c0b 9b20 9940
0247e8b0: 9b20 9940 0000 2710 010a 4fff 0000 0080

2. Field layout (offsets within the 64-byte entry)

Off Field Size Verdict Notes
0 Header byte 1 VERIFIED bit 7 (0x80) = in use. Other bits of byte 0/1: see 4.1
1 Header low byte 1 INFERRED part of the 16-bit header word
2-17 Object name 16 VERIFIED ASCII, 0x27-terminated
18-21 File type text 4 VERIFIED e.g. DATA; 0x27-terminated if < 4
22-23 next_version 2 INFERRED version-chain word
24-25 prev_version 2 INFERRED version-chain word
26-27 Access bits 2 VERIFIED 3 x 5-bit tiers, see 4.2
28-29 File-type flags 2 VERIFIED L M A C I B P T, see 4.3
30-31 Device number 2 INFERRED 0 for ordinary disk files
32 Main directory index of reserving user 1 VERIFIED (ND manual + real pack) high byte of the LAST RESERVING USER word; see 4.4
33 User index of reserving user 1 VERIFIED (ND manual) low byte of the LAST RESERVING USER word; see 4.4
34 User index (owner) 1 VERIFIED index into the user file
35 (object slot low byte) 1 INFERRED word 34-35 = [user | file-slot]
36-37 current_open_count 2 INFERRED
38-39 total_open_count 2 INFERRED
40-43 Date created 4 VERIFIED ND timestamp
44-47 Last read date 4 VERIFIED ND timestamp (0 = never)
48-51 Last write date 4 VERIFIED ND timestamp (0 = never)
52-55 Pages in file 4 VERIFIED 32-bit page count
56-59 Bytes in file - 1 4 VERIFIED actual = stored + 1
60-63 File pointer 4 VERIFIED block pointer (see directory-label 3.2)

Reader offsets: ndfs_oe_from_bytes() (object_entry.c lines 43-84) reads name at +2, type at +18, next_version/prev_version/access_bits/file_type_flags/ device_number at +22/+24/+26/+28/+30, last-reserving-user at +32 (the reader still calls this field file_type - a misnomer, see 4.4), owner user index at +34, open counts at +36/+38, three dates at +40/+44/+48, pages at +52, bytes-1 at +56, file pointer at +60. Each offset below decodes correctly on the real bytes.


3. Worked decode - entry 0 (SINTRAN:DATA)

Off Bytes Field Value
0-1 80 00 Header in-use
2-17 53 49 4E 54 52 41 4E 27 00... Name SINTRAN
18-21 44 41 54 41 Type DATA
22-25 00 00 00 00 next/prev version 0 / 0
26-27 00 07 Access 0x0007 -> OWN=RWA, FRIEND=none, PUBLIC=none
28-29 00 20 Flags 0x0020 -> A (allocated)
30-31 00 00 Device 0
32-33 00 00 Last reserving user main dir index 0 / user index 0 = not reserved
34-35 00 00 User idx / slot owner index 0 (SYSTEM)
40-43 B8 AF 2B F6 Date created 1996-02-23 18:47:54 (per ndtool)
44-51 00... Last read / write never
52-55 00 00 00 3F Pages 63
56-59 FF FF FF FF Bytes - 1 wraps to 0 (byte count unknown; ndtool shows 0)
60-63 00 00 00 01 File pointer CONTIGUOUS, block 1 -> 63 pages at pages 1..63

ndtool --stat SYSTEM/SINTRAN:DATA independently reports: Type DATA, Contiguous, FileTypeAsText A, 63 pages, FilePointer BlockID 1, OWN=READ,WRITE,APPEND, FRIEND=NONE, PUBLIC=NONE, DateCreated 1996-02-23 18:47:54. Every value matches the byte decode. VERIFIED.

Two more real examples confirm the file-pointer / size fields: - MACM-AREA:DATA (entry 1): pages 00 00 00 40 = 64, pointer 00 00 00 40 = CONTIGUOUS block 64 -> occupies pages 64..127 (directly after SINTRAN). - SEGFIL0:DATA (entry 2): pages 00 00 27 10 = 10000, bytes-1 01 0A 4F FF -> 17453056 bytes, pointer 00 00 00 80 = CONTIGUOUS block 128. ndtool shows 17453056 bytes 10000 pages. VERIFIED.


4. Field semantics

4.1 Header word (bytes 0-1)

Bit 7 of byte 0 (0x80) = in use - the only header bit NDFS models (NDFS_OBJECT_IN_USE, object_entry.c line 33). VERIFIED. On the real disk most entries have byte 0 = 0x80, but SEGFIL0 has byte 0 = 0x90 (0x80 + 0x10, i.e. bit 4). The meaning of the extra byte-0 flag bits (and byte 1) is OPEN - likely write-open / modified / system-file markers; resolve against 006-S3FS COBJE/WOBJE (change/write object entry). NDFS preserves bytes it does not model via a verbatim raw[64] copy so they survive a round trip (object_entry.c lines 38-41, 100-114).

4.2 Access bits (bytes 26-27) - three 5-bit tiers

The 16-bit access word packs three permission tiers, each 5 bits:

 bit 15 | 14 13 12 11 10 | 9 8 7 6 5 | 4 3 2 1 0
 (unused)[     PUBLIC    ] [ FRIEND ] [   OWN    ]

CORRECTED 2026-08-02: this diagram previously showed bit 15 14 unused and PUBLIC as the 4 bits 13-10. All three tiers are 5 bits; only bit 15 is unused. Authority: ND-30.003.007 EN SINTRAN III System Supervisor gives the tier table as 15,14 | 10 | 9 | 5 | 4 | 0 with rows D C A W R for public, friend and own, and its worked example for a :SYMB file is 002377 ACCESS WORD, which decodes under the 5/5/5 split as:

Tier Bits Value Meaning
OWN 4-0 11111 R W A C D — all five
FRIEND 9-5 00111 R W A
PUBLIC 14-10 00001 R only

That matches ND's own stated SYSTEM defaults elsewhere in the same manual (DEFAULT FRIEND ACCESS : READ, WRITE, APPEND). Under the old 13-10 reading the tiers do not line up with those defaults.

Note ND-860228-2 line 28902 gives bit 14-9: public / bit 9-4: friend / bit 4-0: own — the ranges overlap and are arithmetically impossible. That line is garbled; use the System Supervisor version above, which the 002377 example confirms.

Tier Bits Shift
OWN 0-4 0
FRIEND 5-9 5
PUBLIC 10-14 10

(5 bits each; bit 15 unused. Confirmed by ND's 002377 worked example, above.)

Within a tier (5 bits), the letter mapping (NDFS object_entry.h lines 36-55):

Letter Bit Meaning
R 0 (0x01) Read
W 1 (0x02) Write
A 2 (0x04) Append
C 3 (0x08) Common (execute)
D 4 (0x10) Directory (delete)

Worked: SINTRAN access 0x0007 -> OWN = 0x07 = R+W+A, FRIEND = 0, PUBLIC = 0. SEGFIL0 access 0x00E7 -> OWN = 0x07 (RWA), FRIEND = 0x07 (RWA), PUBLIC = 0. VERIFIED — but note the evidence: the two real samples 0x0007 and 0x00E7 both keep every set bit inside the low 10, so they do not by themselves test the PUBLIC boundary, and ndtool is not an independent check (it links the same ndfs-c library: target_link_libraries(ndtool ndfs)). The tier split is carried by ND's 002377 example above, whose asymmetric 11111 / 00111 / 00001 decodes coherently under this orientation and not under the alternatives. The exact R/W/A/C/D letter-to-bit assignment is INFERRED from NDFS/ndtool (a reader that round-trips real access words); the official manual confirms the tier order public, friend, owner and the letter set RWACD (ND-60.128.5 SET-FILE-ACCESS, SET-DEFAULT-FILE-ACCESS), but does not pin each bit position.

4.3 File-type flags (bytes 28-29) - L M A C I B P T

file_type_flags bit field (NDFS object_entry.h lines 35-43):

Letter Bit Mask Meaning
T 0 0x0001 Terminal device file
P 1 0x0002 Peripheral device file
B 2 0x0004 (B) Spooling file
I 3 0x0008 Indexed file
C 4 0x0010 Contiguous file
A 5 0x0020 Allocated file
M 6 0x0040 Magnetic-tape file
L 7 0x0080 Library file

(The mnemonic order L M A C I B P T is high-bit-first.) On PACK-ONE, SINTRAN / MACM-AREA / SEGFIL0 all have flags 0x0020 = A (allocated); ndtool prints FileTypeAsText : A and FileType : Contiguous for them.

Evidence downgraded 2026-08-02 — the A bit is NOT verified by this. Three samples all carrying the identical value 0x0020 is one sample, not three: under any rotation of the mnemonic L M A C I B P T, bit 5 would carry a different letter and this check gives the same output. ndtool printing A re-prints our own object_entry.h constant (it links the same library). And FileType : Contiguous comes from the file POINTER's 2-bit type field at bytes 60-63, not from this flags word at all — it addresses a neighbouring fact.

To settle it: one indexed and one contiguous file whose flags words actually DIFFER, cross-read against ND-30.003.007 F.6's own letter key. The remaining bit meanings are INFERRED from NDFS names, corroborated by the manual's file-class vocabulary (indexed / contiguous / spooling / peripheral). A TERMINAL device object has type bytes 27 00 00 00 (empty type) - see object_entry.c line 52.

4.4 LAST RESERVING USER (bytes 32-33) [SETTLED 2026-08-02]

Bytes 32-33 are one word: the LAST RESERVING USER. Byte 32 (high) = main directory index; byte 33 (low) = user index. There is no file-type code byte anywhere in the entry — the file type is the 4 ASCII characters at bytes 18-21.

ND's own annotated @DUMP-OBJECT-ENTRY, ND-30.003.007 EN SINTRAN III System Supervisor, page 340, labels the word directly:

002377                 ACCESS WORD
000040                 OBJBL (BITS 017-014)/TEMP/L/M/A/C/I/S/P/T
000000                 DEVICE NUMBER
000000                 MAIN DIR INDEX / USER INDEX OF RESERVING USER      <-- bytes 32-33
000025                 OBJECT INDEX OF THIS ENTRY                         <-- bytes 34-35

and ND-860228-2 EN SINTRAN III Monitor Calls, appendix C, states it as a byte range:

| 32:33 | User index in main directory of reserving user. |
| 34:35 | Object index of this object entry. |

The same dump shows the type is ASCII, not a code: 051531 046502 TYPE (SYMB).

Confirmed independently on a real pack. On BIGDISK0-L.IMG, byte 32 is 0 on all 293 entries — across 43 PROG, 25 SYMB, 40 BPUN, 44 DATA and 10 other type strings. Under the old "0=DATA, 1=PROG, 2=SYMB" claim the PROG files would read 1 and the SYMB files 2. They do not. All-zero is exactly what "reserving user" predicts on a pack with nothing reserved.


What this section used to claim, and why its VERIFIED tag was worthless

It claimed byte 32 was a file-type code (0=DATA, 1=PROG, 2=SYMB, 3=TEXT), citing object_entry.h line 70, and marked it VERIFIED on this basis:

byte 32 = 0 and type text DATA agree on all three sample entries; ndtool prints Type: DATA

Both halves fail:

  • The sample could not discriminate. All three PACK-ONE entries were unreserved SYSTEM files, so byte 32 was 0x00 in every one — and 0 is also the proposed DATA code. Three all-zero samples are one sample, and it agrees with every hypothesis that predicts zero here.
  • The cross-check was circular. ndtool printing Type: DATA is ndfs-c echoing its own object_entry.h constant. ndtool links that library (ndfs-c/CMakeLists.txt: target_link_libraries(ndtool ndfs)), so it cannot disagree with it.

Code impact: all four ports still read byte 32 into a field named file_type and write it back. In practice they write 0 (the create path never sets it), so no pack has been corrupted — but the field is misnamed, and XAT carries it between packs as a "file type", which would move a stale reserving-user value. The field should be renamed to reserving_user_*.

The claim was: single byte, 0 = DATA, 1 = PROG, 2 = SYMB, 3 = TEXT (object_entry.h line 70), distinct from the 4-char type text at bytes 18-21.

The VERIFIED tag it carried rested on evidence that could not have failed:

  • The sample was non-discriminating. All three PACK-ONE entries (SINTRAN, MACM-AREA, SEGFIL0) are unreserved SYSTEM files, so byte 32 is 0x00 in every one — and 0 is also the proposed DATA code. Three all-zero samples cannot separate "file-type code" from any other field that happens to be zero here. That is one sample, not three.
  • The cross-check was circular. ndtool printing Type: DATA is ndfs-c echoing its own object_entry.h constant back. ndtool links that library (ndfs-c/CMakeLists.txt: target_link_libraries(ndtool ndfs)), so it cannot disagree.

The competing reading has manual support. See OBJECT-ENTRY-CARVE-BYTES-32-35-2026-07-30.md section 3.4 Q2 and section 7: ND-860228-2 appendix C and ND-30.003.007 F.6 both describe bytes 32-33 as the LAST RESERVING USER word (byte 32 = main directory index, byte 33 = user index), and neither manual documents a file-type-code byte anywhere in the entry. That would also account for byte 33, which this table still lists as OPEN.

Why it matters: a writer that stamps a file-type code into byte 32 overwrites a real directory index on a live pack.

To settle it: decode one :PROG or :SYMB file, or any file owned by a non-zero user or that has ever been reserved. Under the manual reading byte 33 tracks the user and byte 32 the directory; under the file-type-code reading byte 32 tracks the type suffix. A single non-DATA sample decides it. The annotated real @DUMP-OBJECT-ENTRY listing at ND-30.003.007 lines 13697-13725 is a ready-made vector.

Note this does not touch bytes 34-35, which are separately established (byte 34 = owner, VERIFIED against a live pack).

4.5 Owner (byte 34) and object-index word

Byte 34 is the user index of the owning account (0 = SYSTEM on PACK-ONE), resolved to a name via the user file. NDFS reads the 16-bit word at 34-35 as disk_object_index = [user | file-slot] (object_entry.c lines 65-67); the low byte (35) is the file slot. Byte 33 (low byte of the word at 32-33) is preserved but unmodelled - OPEN.

4.6 Size fields (bytes 52-59)

  • Pages in file (52-55): 32-bit page count.
  • Bytes in file - 1 (56-59): the actual byte length is stored minus one; the reader adds 1 (object_entry.c lines 79-81). 0xFFFFFFFF therefore means "byte count not tracked" (wraps to 0), as on the contiguous system files SINTRAN / MACM-AREA. VERIFIED.

4.7 File pointer (bytes 60-63)

Same 2-bit-type + 30-bit-block-id encoding as the master-block pointers (directory-label 3.2). For a contiguous file it is the first data page (SINTRAN -> block 1). For an indexed file it is the index block, walked by 006-S3FS RINDX/FINDX. VERIFIED for the contiguous case from real bytes; the indexed-walk is the subject of Phase 6.


5. Producing / consuming code (006-S3FS)

Addr (octal) Symbol Role
55563B / 55566B / 55750B FOBJB / ROBJE / WOBJE Find / read (MON 41) / write object block
61502B COBJE Change object entry
63726B / 64146B CROBJ / DLOBJ Create / delete object
51453B / 52066B RINDX / FINDX Read / walk index block (indexed object file)

The carved 41B-ReadObjectEntry (ROBJE) worker reads an object entry via FOBJB -> RINDX -> RBLOC, confirming the object file is an indexed file whose 64-byte records are read one 64-byte slot at a time. See .../re/mon-analysis/41B-ReadObjectEntry/.


6. Fields marked OPEN

  • Byte 0 extra flag bits (SEGFIL0 = 0x90) and byte 1 - header bits beyond in-use. Resolve via COBJE/WOBJE.
  • Byte 33 (low byte of the 32-33 word) - preserved-but-unmodelled.
  • Bytes 22-25 next_version/prev_version exact chaining semantics vs the 34-35 object-index word (INFERRED from NDFS; not byte-traced to the writer).

Provenance: real bytes SMD0.IMG; reader object_entry.c/.h; cross-reader ndtool --stat; producer 006-S3FS ROBJE/COBJE/WOBJE.