Reverse engineering the NetWare 386 filesystem format

I decided to take a look into the NetWare 386 filesystem, which was used in NetWare 3.x and 4.x and perhaps later versions as well. This post serves to give a high-level background on the design and layout. Tools to analyze and extract content from such a filesystem can be found at https://github.com/zhmu/nwfs386.

If you have any corrections, additional information or questions, please reach out to me by email at rink@rink.nu. I’ll try to update this post as necessary. I also accept pull requests on Github!

Overview

Even though NWFS386 was introduced in the late 80-ies, it is more than a filesystem: it performs bad sector remapping, mirroring and divides the partition into volumes, which can span multiple partitions. This is indeed no ordinary filesystem. A brief overview:

  • A physical disk can have a single NetWare partition
  • A NetWare partition can be mirrored (similar to RAID-1) and performs its own bad sector remapping (redirection)
  • A NetWare volume contains files/directories and is allocated to one or more partitions

Hence, a volume can span multiple partitions, which may can be mirrored as desired for added reliability. This is indeed much more than just a filesystem!

In-depth feature list

  • NWFS386 uses 32-bit block numbers and 32-bit file/directory identifiers
  • Important NWFS386 partition structures are stored 4 times
  • A volume has its own block size, which can be 4KB, 8KB, 16KB, 32KB or 64KB in size.
  • Volumes can span multiple partitions, and volumes may be extended at any time
  • All meta-data is read in memory when mounting
  • Volume meta-data (FAT chain, directory contents) are stored twice
  • File data is stored a single time

Layout

NetWare 386 uses the MBR to locate partitions. There can be a single partition per device, and this partition must have an ID of 0x65.

  • The first 16KB of the NetWare partition is not in use
  • The next 16KB contains the hotfix/mirror area. These detail the number of redirection sectors and data sectors available, and how many sectors are allocated to remap bad sectors (the redirection area). This information is replicated 4 times.
  • The redirection area hasn’t been thoroughly explored; the idea is that bad sectors are remapped here by the NetWare OS.
  • The volume area contains which volumes are stored on this partition. There can be multiple volumes stored on a single partition, and each volume can span multiple partitions. This area contains 16KB of data which is replicated 4 times.
  • Finally, the data area stores the FAT chain and data blocks for the files/directories on the volume.

We’ll go in depth on these areas in the next sections.

Hotfix header

Being the first structure in the partition, it describes how many sectors of the partition are allocated for block data as well as redirection purposes.

FieldTypeDescription
idbyte[8]Identification string (HOTFIX00)
v_idu32Identification code
?u16[4]?
data_area_sectorsu32Number of sectors for data
redir_area_sectorsu32Number of sectors for redirection
?u32[8]?

Our main field of interest is redir_area_sectors which contains the number of sectors used for the hotfix/mirror area and the redirection area. In other words, it allows to calculate where the volume area starts within the partition.

The next sector contains the mirror header as illustrated below.

Mirror header

The second structure in the partition, this describes whether the partition is mirrored – and if so, with with other partitions.

FieldTypeDescription
idbyte[8]Identification string (MIRROR00)
create_timeu32Creation timestamp
?u32[5]?
v_id1u32Hotfix area ID #1
v_id2u32Hotfix area ID #2

v_idN contain the hotfix header v_id values of all partitions that appear within the mirror. Hence, the v_id of the partition’s hotfix header should always be appear in any of the v_idN fields.

Note: there are likely be more than 2 area ID’s allowed, but I haven’t looked into this due to my inability to add SCSI disks to my qemu instance (the dc390 driver seems the only supported one in NetWare 3.x and I can’t get it to work) as I ran out of IDE devices. Any help would be appreciated.

Volume area

The volume area is located directly past the redirection area; the offset is 16384 + redir_area_sectors * 512 bytes within the partition.

FieldTypeDescription
magicbyte[16]“NetWare Volumes” + \0
num_volumesu32Number of volume entries
?u32[3]?

This header is followed by num_volumes times a volume header, which is detailed below. Regardless of the number of volumes, 16KB will be used to store the volume information. Furthermore, the volume information is replicated 4 times, which means 64KB is used for the volume area.

Volume entry

FieldTypeDescription
name_lengthu8Volume name length, in bytes
namebyte[19]Volume name
?u16?
segment_numu16Volume segment number
first_sectoru32Always 160 (?)
num_sectorsu32Number of sectors in this segment
total_blocksu32Total number of blocks in this volume
first_segment_blocku32First data block this segment contains
?u32?
block_valueu32Used to calculate the block size
rootdir_block_nru32Block number containing the directory
rootdir_copy_block_nru32Block number containing the directory copy
?u32?

The volume’s block size is (256 / block_value) * 1024. The smallest block size is 1KB, whereas the largest would be 256KB. The installer does not allow you to create such large blocks, and I haven’t tried if they work at all.

If your volume spans multiple partitions, all partitions will have the volume listed in their volume area. However, the first segment will have first_segment_block = 0, whereas the second segment contains a non-zero value. Thus, all blocks within the segment are relative to first_segment_block, and given a block number you must determine which partition must be accessed.

FAT

The NetWare 386 filesystem was clearly inspired by the FAT file system, as it also uses a singly linked list to be able to find the next block of each file. Like FAT, this linked list is stored twice. As the entire FAT is read into memory at mount time and updated on disk as necessary, this is quite speedy.

The root directory is not fixed-length or fixed-size: the rootdir_block_nr and rootdir_copy_block_nr fields of the volume determine where the initial block of the root directory is – the FAT chain can then be used to determine the subsequent blocks as needed. This is similar to the approach taken in FAT32, except for the extra copy.

An important difference is that the root directory is the only directory stored in NWFS386. Every directory entry contains the ID of the directory in which the entry resides. There are a few magic values with their own respective entry content, such as volume information and additional trustee lists. The root directory uses ID 0.

Every FAT entry is completely incompatible with DOS FAT. Every entry is 128 bytes, which ensures it will never span across multiple sectors.

File entry

FieldTypeDescription
parent_dir_idu32Directory ID where the item resides
attru32Entry attributes (must not have directory bit set)
?byte[3]?
name_lenbyteFile name length, in bytes
namebyte[12]8.3 file name
create_timeu32File creation time
owner_idbu32Object ID of the current file owner
?u32[2]?
modify_timeu32File last modification time
modifier_idbu32Object ID of the last file modifier
lengthu32File length, in bytes
block_nru32First file block number
?u32?
trusteesTrustee[6]Trustees
?u32[2]?
delete_timeu32When was file deleted, zero if not deleted
delete_idbu32Object ID of who deleted the file
?u32[2]?
file_entryu32Unknown
?u32?

Directory entry

FieldTypeDescription
parent_dir_idu32Directory ID where the item resides
attru32Entry attributes (must have directory bit set)
?u8[3]?
name_lenu8File name length, in bytes
nameu8[12]8.3 file name
create_timeu32Directory creation time
owner_idbu32Directory owner object ID
?u32[2]?
modify_timeu32Directory last modification time
?u32?
trusteesTrustee[8]Trustees
?u16[2]?
inherited_rights_masku16Mask for inherited rights
subdir_indexu32Unknown
?u16[7]?
directory_idu32ID of this directory
?u16[2]?

Available entry

Available entries contain a parent_dir_id of 0xffff ffff. The other 124 bytes tend to be zeros.

Grant list

Whenever more trustees are added to a file/directory which cannot be stored in the corresponding FAT entry itself, a grant list will be added to the volume with the responding information. This hasn’t been decoded in too much detail.

FieldTypeDescription
parent_dir_idu320xffff fffe
?u32[5]?
trusteesTrustee[16]Trustees
?u32[2]?

Volume information

FieldTypeDescription
parent_dir_idu320xffff fffd
?u32[5]?
create_timeu32Volume creation time
owner_idu32Object ID of volume owner
?u32[2]?
modify_timeu32Last modification time
?u32?
trusteesTrustee[8]Trustees
?u32[8]?

Trustee structure

FieldTypeDescription
object_idbu32Object ID where the trustee applies to
rightsu16Bitmask containing trustee rights

The rights mask contains the following bits:

BitNetWare rightDescription
0RRead access
1WWrite acces
2Seems to be used internally?
3CAccess to create subentries
4EErase subentries
5AAccess control
6FFile scan
7MModify attributes
8SSupervisory (overrides all others)

Attribute bits

BitFILER flagDescription
0Ro (Rw if clear)Read-only
1HHidden
2SySystem
4Directory
5AArchive
7SShareable
12TTransactional
16PPurge
17RiRename Inhibit
18DiDelete Inhibit
19CiCopy Inhibit

Timestamp

A timestamp is a 32-bit value, which is to be interpreted as two 16-bit values: the high part is the date and the low part is the time.

PieceBitsDescription
Time11:15Hour
5:10Minute
0:4Second divided by 2
Date9:15Year minus 1980
5:8Month
0:4Day
This entry was posted in Reverse engineering and tagged , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *