Module: NTFS

Included in:
FileObject
Defined in:
lib/fs/ntfs/boot_sect.rb,
lib/fs/ntfs/utils.rb,
lib/fs/ntfs/data_run.rb,
lib/fs/ntfs/mft_entry.rb,
lib/fs/ntfs/attrib_data.rb,
lib/fs/ntfs/attrib_type.rb,
lib/fs/MiqFS/modules/NTFS.rb,
lib/fs/ntfs/attrib_bitmap.rb,
lib/fs/ntfs/attrib_header.rb,
lib/fs/ntfs/attrib_file_name.rb,
lib/fs/ntfs/attrib_object_id.rb,
lib/fs/ntfs/attrib_index_root.rb,
lib/fs/ntfs/index_node_header.rb,
lib/fs/ntfs/attrib_volume_name.rb,
lib/fs/ntfs/index_record_header.rb,
lib/fs/ntfs/directory_index_node.rb,
lib/fs/ntfs/attrib_attribute_list.rb,
lib/fs/ntfs/attrib_index_allocation.rb,
lib/fs/ntfs/attrib_volume_information.rb,
lib/fs/ntfs/attrib_standard_information.rb

Overview

NTFS file system interface to MiqFS.

Defined Under Namespace

Modules: Utils Classes: AttribData, AttribHeader, AttributeList, Bitmap, BootSect, DataRun, DirectoryIndexNode, FileName, FileObject, IndexAllocation, IndexNodeHeader, IndexRecordHeader, IndexRoot, MftEntry, ObjectId, StandardInformation, VolumeInformation, VolumeName

Constant Summary collapse

BOOT_PARAMETERS_BLOCK =

The boot parameters block, sector 0 byte 0 of a bootable volume.

BinaryStruct.new([
  'a3', 'jmp_boot_loader',          # Jump to boot loader
  'a8', 'oem_name',                 # OEM Name (should be 'NTFS    ')

  # BIOS Parameter Block
  'S',  'bytes_per_sector',         # Bytes per sector. The size of a hardware sector. For most disks used in the United States, the value of this field is 512.
  'C1', 'sectors_per_cluster',      # Sectors per cluster
  'S',  'reserved_sectors',         # Reserved sectors. Always 0 because NTFS places the boot sector at the beginning of the partition. If the value is not 0, NTFS fails to mount the volume.
  'C1', 'fats',                     # Value must be 0 or NTFS fails to mount the volume
  'S',  'root_entries',             # Value must be 0 or NTFS fails to mount the volume
  'S',  'sectors',                  # Value must be 0 or NTFS fails to mount the volume
  'C1', 'media_descriptor',         # Provides information about the media being used. A value of 0xF8 indicates a hard disk and 0xF0 indicates a high-density 3.5-inch floppy disk. Media descriptor entries are a legacy of MS-DOS FAT16 disks and are not used in Windows Server 2003.
  'S',  'sectors_per_fat',          # Value must be 0 or NTFS fails to mount the volume
  'S',  'sectors_per_track',        # Required to boot Windows
  'S',  'number_of_heads',          # Required to boot Windows
  'L',  'hidden_sectors',           # Offset to the start of the partition relative to the disk in sectors.  Required to boot Windows
  'L',  'large_sectors',            # Must be 0

  # Extended BIOS Parameter Block
  'C1', 'physical_drive',           # 0x00 floppy, 0x80 hard disk
  'C1', 'current_head',             # Must be 0
  'C1', 'extended_boot_signature',  # 0x80
  'C1', 'reserved2',
  'Q',  'sectors_per_volume',        # Number of sectors in volume. Gives maximum volume size of 2^63 sectors. Assuming standard sector size of 512 bytes, the maximum byte size is approx. 4.7x10^21 bytes.
  'Q',  'mft_lcn',                   # Logical Cluster Number for the File $MFT.     Identifies the location of the                      MFT by using its logical cluster number.
  'Q',  'mftmirr_lcn',               # Logical Cluster Number for the File $MFTMirr. Identifies the location of the mirrored copy of the MFT by using its logical cluster number.
  'c1', 'clusters_per_mft_record',   # Mft record size in clusters.  NTFS creates a file record for each file and a folder record for each folder that is created on an NTFS volume. Files and folders smaller than this size are contained within the MFT. If this number is positive (up to 7F), then it represents clusters per MFT record. If the number is negative (80 to FF), then the size of the file record is 2 raised to the absolute value of this number.
  'a3', 'reserved0',
  'c1', 'clusters_per_index_record', # Index block size in clusters. The size of each index buffer, which is used to allocate space for directories. If this number is positive (up to 7F), then it represents clusters per MFT record. If the number is negative (80 to FF), then the size of the file record is 2 raised to the absolute value of this number.
  'a3', 'reserved1',
  'Q',  'volume_serial_number',      # Seems like only the low 32-bits are used
  'L',  'checksum',                  # Boot sector checksum

  'a426', 'boot_code',               # Boot loader
  'S',  'signature',                 # Sanity check: always 0xaa55
])
SIZEOF_BOOT_PARAMETERS_BLOCK =
BOOT_PARAMETERS_BLOCK.size
NTFS_MAGIC =
0xaa55
FILE_RECORD =

One MFT file record, also called MFT Entry.

BinaryStruct.new([
  'a4', 'signature',                # Always 'FILE'
  'S',  'usa_offset',               # Offset to the Update Sequence Array (usa) from the start of the ntfs record.
  'S',  'usa_count',                # Number of u16 sized entries in the usa including the Update Sequence Number (usn),
  # thus the number of fixups is the usa_count minus 1.
  'Q',  'lsn',                      # $LogFile sequence number for this record.
  # Changed every time the record is modified
  'S',  'seq_num',                  # Number of times this MFT record has been reused
  'S',  'hard_link_count',          # Number of links to this file
  'S',  'offset_to_attrib',         # Byte offset to the first attribute in this mft record from the start of the mft record.
  # NOTE: Must be aligned to 8-byte boundary.
  'S',  'flags',                    # File record flags
  'L',  'bytes_in_use',             # Number of bytes used in this mft record.
  # NOTE: Must be aligned to 8-byte boundary.
  'L',  'bytes_allocated',          # Number of bytes allocated for this mft record. This should be equal
  # to the mft record size
  'Q',  'base_mft_record',          # This is zero for base mft records. When it is not zero it is a mft reference
  # pointing to the base mft record to which this record belongs (this is then
  # used to locate the attribute list attribute present in the base record which
  # describes this extension record and hence might need modification when the
  # extension record itself is modified, also locating the attribute list also
  # means finding the other potential extents, belonging to the non-base mft record).
  'S',  'next_attrib_id',           # The instance number that will be assigned to the next attribute added to this
  # mft record.
  # NOTE: Incremented each time after it is used.
  # NOTE: Every time the mft record is reused this number is set to zero.
  # NOTE: The first instance number is always 0

  #
  # The 2 fields below are specific to NTFS 3.1+ (Windows XP and above):
  #
  'S',  'unused1',                  # Reserved/alignment
  'L',  'mft_rec_num',              # Number of this mft record.

  # When (re)using the mft record, we place the update sequence array at this
  # offset, i.e. before we start with the attributes. This also makes sense,
  # otherwise we could run into problems with the update sequence array
  # containing in itself the last two bytes of a sector which would mean that
  # multi sector transfer protection wouldn't work. As you can't protect data
  # by overwriting it since you then can't get it back...
  # When reading we obviously use the data from the ntfs record header.
  #
  'S',  'fixup_seq_num',            # Magic word at end of sector
])
SIZEOF_FILE_RECORD =

Here follows the fixup array (WORD).

FILE_RECORD.size
AT_STANDARD_INFORMATION =

The _ATTRIB_TYPE enumeration. Every entry in the MFT is made of a list of attributes. These are the attrib types.

0x00000010
AT_ATTRIBUTE_LIST =

Base data: MAC times, version, owner, security, quota, permissions

0x00000020
AT_FILE_NAME =

Used for a list of attributes that spans one MFT file record

0x00000030
AT_VOLUME_VERSION =

MAC times, size, flags, (and of course) file name

0x00000040
AT_OBJECT_ID =

No information

0x00000040
AT_SECURITY_DESCRIPTOR =

Object GUID, birth GUIDs & domain GUID

0x00000050
AT_VOLUME_NAME =

User & Group SIDs, system and descr ACLs

0x00000060
AT_VOLUME_INFORMATION =

Vol name (label)

0x00000070
AT_DATA =

Major, minor & flags (chkdsk & others)

0x00000080
AT_INDEX_ROOT =

File data, aux stream data

0x00000090
AT_INDEX_ALLOCATION =

Root of an index - usually root of directory

0x000000a0
AT_BITMAP =

Larger index structures use index allocation for overflow

0x000000b0
0x000000c0
AT_REPARSE_POINT =

Haven’t seen it yet

0x000000c0
AT_EA_INFORMATION =

Reparse data

0x000000d0
AT_EA =

Extended attribute information

0x000000e0
AT_PROPERTY_SET =

Extended attribute data

0x000000f0
AT_LOGGED_UTILITY_STREAM =

Haven’t seen it

0x00000100
AT_END =

Encrypted File System data

0xffffffff
TypeName =

NT names for the attributes.

{
  AT_STANDARD_INFORMATION  => '$STANDARD_INFORMATION',
  AT_ATTRIBUTE_LIST        => '$ATTRIBUTE_LIST',
  AT_FILE_NAME             => '$FILE_NAME',
  # AT_VOLUME_VERSION        => '$VOLUME_VERSION',        # This type is depreciated.
  AT_OBJECT_ID             => '$OBJECT_ID',
  AT_SECURITY_DESCRIPTOR   => '$SECURITY_DESCRIPTOR',
  AT_VOLUME_NAME           => '$VOLUME_NAME',
  AT_VOLUME_INFORMATION    => '$VOLUME_INFORMATION',
  AT_DATA                  => '$DATA',
  AT_INDEX_ROOT            => '$INDEX_ROOT',
  AT_INDEX_ALLOCATION      => '$INDEX_ALLOCATION',
  AT_BITMAP                => '$BITMAP',
  # AT_SYMBOLIC_LINK         => '$SYMBOLIC_LINK',         # This type is depreciated.
  AT_REPARSE_POINT         => '$REPARSE_POINT',
  AT_EA_INFORMATION        => '$EA_INFORMATION',
  AT_EA                    => '$EA',
  AT_PROPERTY_SET          => '$PROPERTY_SET',
  AT_LOGGED_UTILITY_STREAM => '$LOGGED_UTILITY_STREAM'
}
DEF_CACHE_SIZE =

Default index cache size

50
STANDARD_ATTRIBUTE_HEADER =

Standard attribute header. Each attribute begins with one of these.

BinaryStruct.new([
  'L',  'attrib_type',      # The (32-bit) type of the attribute
  'L',  'length',           # Byte size of the resident part of the attribute
  # (aligned to 8-byte boundary).  Used to get to the next attribute
  'C',  'non_resident',     # If 0, attribute is resident.
  # If 1, attribute is non-resident
  'C',  'name_length',      # Unicode character size of name of attribute.
  # 0 if unnamed
  'S',  'name_offset',      # If name_length != 0, the byte offset to the beginning of the name
  # from the attribute record. Note that the name is stored as a
  # Unicode string.
  'S',  'flags',            # Attribute flags (see AF_ below)
  'S',  'attrib_id',        # The instance of this attribute record. This number is unique within this mft record
])
SIZEOF_STANDARD_ATTRIBUTE_HEADER =
STANDARD_ATTRIBUTE_HEADER.size
SAH_RESIDENT =

The resident struct.

BinaryStruct.new([
  'L',  'value_length',     # Byte size of attribute value
  'S',  'value_offset',     # Byte offset of the attribute value from the start of the attribute record
  'C',  'resident_flags',   # Flags of resident attributes (8-bit)
  'C',  nil,                # Reserved/alignment to 8-byte boundary
])
SIZEOF_SAH_RESIDENT =
SAH_RESIDENT.size
SAH_NONRESIDENT =

The non-resident struct.

BinaryStruct.new([
  'Q',  'first_vcn',        # Lowest valid virtual cluster number for this portion of the attribute value
  # or 0 if this is the only extent (usually the case). - Only when an attribute
  # list is used does lowest_vcn != 0 ever occur
  'Q',  'last_vcn',         # Highest valid vcn of this extent of the attribute value. - Usually there is
  # only one portion, so this usually equals the attribute value size in clusters
  # minus 1. Can be -1 for zero length files. Can be 0 for "single extent" attributes
  'S',  'mapping_pairs_offset', # Byte offset from the beginning of the structure to the mapping pairs array
  # which contains the mappings between the vcns and the logical cluster numbers (lcns).
  # When creating, place this at the end of this record header aligned to 8-byte boundary.
  'S',  'compression_unit', # The compression unit expressed as the log to the base 2 of the number of
  # clusters in a compression unit. 0 means not compressed. (This effectively limits the
  # compression unit size to be a power of two clusters.) WinNT4 only uses a value of 4.
  'L',  nil,                # Align to 8-byte boundary

  # The sizes below are only used when lowest_vcn is zero, as otherwise it would
  # be difficult to keep them up-to-date.

  'Q',  'allocated_size',   # Byte size of disk space allocated to hold the attribute value. Always is a
  # multiple of the cluster size. When a file is compressed, this field is a
  # multiple of the compression block size (2^compression_unit) and it
  # represents the logically allocated space rather than the actual on disk usage.
  'Q',  'data_size',        # Byte size of the attribute value.
  # Can be larger than allocated_size if attribute value is compressed or sparse
  'Q',  'initialized_size', # Byte size of initialized portion of the attribute value. Usually equals data_size
])
SIZEOF_SAH_NONRESIDENT =

If the attribute is named (name_length is not 0), then the name (in UNICODE) follows here. Here follows the attribute data (if resident) or the data runs (if non-resident).

SAH_NONRESIDENT.size
ATTRIB_FILE_NAME =

FILE_NAME_ATTR - Attribute: Filename (0x30)

NOTE: Always resident. NOTE: All fields, except the parent_directory, are only updated when the

filename is changed. Until then, they just become out of sync with
reality and the more up to date values are present in the standard
information attribute.

NOTE: There is conflicting information about the meaning of each of the time

fields but the meaning as defined below has been verified to be
correct by practical experimentation on Windows NT4 SP6a and is hence
assumed to be the one and only correct interpretation.

The $FILE_NAME attribute.

BinaryStruct.new([
  'Q',  'ref_to_parent_dir',  # Directory this filename is referenced from
  'Q',  'time_created',       # Time file was created
  'Q',  'time_altered',       # Time the data attribute was last modified
  'Q',  'time_mft_changed',   # Time this mft record    was last modified
  'Q',  'time_read',          # Last time this mft record was accessed
  'Q',  'allocated_size',     # Byte size of on-disk allocated space for the data attribute.
  # So for normal $DATA, this is the allocated_size from the unnamed
  # $DATA attribute and for compressed and/or sparse $DATA, this is
  # the compressed_size from the unnamed $DATA attribute.
  # NOTE: This is a multiple of the cluster size.
  'Q',  'data_size',          # Byte size of actual data in data attribute
  'L',  'flags',              # Flags describing the file
  'L',  'reparse_point_tag',  # Type of reparse point, present only in reparse points and only if there are no EAs
  'C1', 'name_length',        # Length of file name in (Unicode) characters
  'C1', 'namespace',          # Namespace of the file name
])
SIZEOF_ATTRIB_FILE_NAME =

File name (in UNICODE) is appended to the previous structure.

ATTRIB_FILE_NAME.size
ATTRIB_INDEX_ROOT =

INDEX_ROOT - Attribute: Index root (0x90).

NOTE: Always resident.

This is followed by a sequence of index entries (INDEX_ENTRY structures) as described by the index header.

When a directory is small enough to fit inside the index root then this is the only attribute describing the directory. When the directory is too large to fit in the index root, on the other hand, two additional attributes are present: an index allocation attribute, containing sub-nodes of the B+ directory tree (see below), and a bitmap attribute, describing which virtual cluster numbers (vcns) in the index allocation attribute are in use by an index block.

NOTE: The root directory (FILE_root) contains an entry for itself. Other directories do not contain entries for themselves, though.

BinaryStruct.new([
  'L',  'type',                     # Type of the indexed attribute. Is FILE_NAME for directories, zero
  # for view indexes. No other values allowed.
  'L',  'collation_rule',           # Collation rule used to sort the index entries. If type is $FILE_NAME,
  # this must be COLLATION_FILE_NAME
  'L',  'index_block_size',         # Size of index block in bytes (in the index allocation attribute)
  'C',  'clusters_per_index_block', # Size of index block in clusters (in the index allocation attribute),
  # when an index block is >= than a cluster, otherwise sectors per index block
  'a3', nil,                        # Reserved/align to 8-byte boundary
])
SIZEOF_ATTRIB_INDEX_ROOT =

Here follows a node header.

ATTRIB_INDEX_ROOT.size
INDEX_NODE_HEADER =

INDEX_HEADER

This is the header for indexes, describing the INDEX_ENTRY records, which follow the INDEX_HEADER. Together the index header and the index entries make up a complete index.

IMPORTANT NOTE: The offset, length and size structure members are counted relative to the start of the index header structure and not relative to the start of the index root or index allocation structures themselves.

BinaryStruct.new([
  'L',  'entries_offset',     # Byte offset from the INDEX_HEADER to first INDEX_ENTRY, aligned to 8-byte boundary
  'L',  'index_length',       # Data size in byte of the INDEX_ENTRY's, including the INDEX_HEADER, aligned to 8.
  'L',  'allocated_size',     # Offset to end of allocated index entry list buffer (relative to start of node header).

  #
  # For the index root attribute, the above two numbers are always
  # equal, as the attribute is resident and it is resized as needed.
  #
  # For the index allocation attribute, the attribute is not resident
  # and the allocated_size is equal to the index_block_size specified
  # by the corresponding INDEX_ROOT attribute minus the INDEX_BLOCK
  # size not counting the INDEX_HEADER part (i.e. minus -24).
  #

  'L',  'flags',              # See NH_ below.
])
SIZEOF_INDEX_NODE_HEADER =
INDEX_NODE_HEADER.size
INDEX_RECORD_HEADER =

INDEX_BLOCK - Attribute: Index allocation (0xa0).

NOTE: Always non-resident (doesn’t make sense to be resident anyway!).

This is an array of index blocks. Each index block starts with an INDEX_BLOCK structure containing an index header, followed by a sequence of index entries (INDEX_ENTRY structures), as described by the INDEX_HEADER.

BinaryStruct.new([
  'a4', 'signature',          # Always 'INDX'
  'S',  'usa_offset',         # Offset to the Update Sequence Array (usa) from the start of the ntfs record.
  'S',  'usa_count',          # Number of u16 sized entries in the usa including the Update Sequence Number (usn),
  # thus the number of fixups is the usa_count minus 1.
  'Q',  'lsn',                # $LogFile sequence number of the last modification of this index block
  'Q',  'index_block_vcn',    # VCN of this record in the full index stream.
])
SIZEOF_INDEX_RECORD_HEADER =

Here follows the fixup array. Here follows an index node header.

INDEX_RECORD_HEADER.size
DIR_INDEX_NODE =
BinaryStruct.new([
  'Q',  'mft_ref',          # MFT file reference for file name (goofy ref).
  'S',  'length',           # Length of entry.
  'S',  'content_len',      # Length of $FILE_NAME attrib
  'L',  'flags',            # See IN_ below (note: these will eventually become general flags)
])
SIZEOF_DIR_INDEX_NODE =

Here follows a $FILE_NAME attribute if content_len > 0. Last 8 bytes starting on 8 byte boundary are the VCN of the child node in $INDEX_ALLOCATION (if IN_HAS_CHILD is set).

DIR_INDEX_NODE.size
ATTRIB_ATTRIBUTE_LIST =

ATTR_LIST_ENTRY - Attribute: Attribute list (0x20).

  • Can be either resident or non-resident.

  • Value consists of a sequence of variable length, 8-byte aligned,

ATTR_LIST_ENTRY records.

  • The attribute list attribute contains one entry for each attribute of

the file in which the list is located, except for the list attribute itself. The list is sorted: first by attribute type, second by attribute name (if present), third by instance number. The extents of one non-resident attribute (if present) immediately follow after the initial extent. They are ordered by lowest_vcn and have their instance set to zero. It is not allowed to have two attributes with all sorting keys equal.

  • Further restrictions:

  • If not resident, the vcn to lcn mapping array has to fit inside the base mft record.

  • The attribute list attribute value has a maximum size of 256kb. This is imposed by the Windows cache manager.

  • Attribute lists are only used when the attributes of mft record do not

fit inside the mft record despite all attributes (that can be made non-resident) having been made non-resident. This can happen e.g. when:

- File has a large number of hard links (lots of file name
  attributes present).
- The mapping pairs array of some non-resident attribute becomes so
  large due to fragmentation that it overflows the mft record.
- The security descriptor is very complex (not applicable to
  NTFS 3.0 volumes).
- There are many named streams.
BinaryStruct.new([
  'L',  'attrib_type',    # Type of referenced attribute
  'S',  'length',         # Byte size of this entry
  'C',  'name_length',    # Size in Unicode chars of the name of the attribute or 0 if unnamed
  'C',  'name_offset',    # Byte offset (from start of entry) to beginning of attribute name
  'Q',  'first_vcn',      # Lowest virtual cluster number of this portion of the attribute value.
  # This is usually 0. It is non-zero for the case where one attribute
  # does not fit into one mft record and thus several mft records are
  # allocated to hold this attribute. In the latter case, each mft
  # record holds one extent of the attribute and there is one attribute
  # list entry for each extent.
  # NOTE: This is DEFINITELY a signed value! The windows driver uses cmp, followed
  # by jg when comparing this, thus it treats it as signed.
  'Q',  'mft_reference',  # The reference of the mft record holding the ATTR_RECORD for this
  # portion of the attribute value
  'S',  'attrib_id',      # If lowest_vcn = 0, the instance of the attribute being referenced; otherwise 0.

])
SIZEOF_ATTRIB_ATTRIBUTE_LIST =
ATTRIB_ATTRIBUTE_LIST.size
ATTRIB_VOLUME_INFORMATION =

VOLUME_INFORMATION - Attribute: Volume information (0x70).

NOTE: Always resident. NOTE: Present only in FILE_Volume. NOTE: Windows 2000 uses NTFS 3.0 while Windows NT4 service pack 6a uses NTFS 1.2.

BinaryStruct.new([
  'Q',  nil,          # No information.
  'C1', 'ver_major',  # File system major version number.
  'C1', 'ver_minor',  # File system minor version number.
  'S',  'flags',      # Volume flags (see VF_ below).
])
ATTRIB_STANDARD_INFORMATION =

STANDARD_INFORMATION - Attribute: Standard information (0x10).

NOTE: Always resident. NOTE: Present in all base file records on a volume. NOTE: There is conflicting information about the meaning of each of the time

fields but the meaning as defined below has been verified to be
correct by practical experimentation on Windows NT4 SP6a and is hence
assumed to be the one and only correct interpretation.
BinaryStruct.new([
  'Q',  'time_created',     # Time file was created. Updated when a filename is changed(?)
  'Q',  'time_altered',     # Time the data attribute was last modified
  'Q',  'time_mft_changed', # Time this mft record was last modified
  'Q',  'time_read',        # Approximate time when the file was last accessed (obviously
  # this is not updated on read-only volumes). In Windows this
  # is only updated when accessed if some time delta has passed
  # since the last update. Also, last access times updates can be
  # disabled altogether for speed
  'L',  'dos_permissions',  # These are the flags we know as 'attributes' (see FP_ below)

  #
  # If a volume has been upgraded from a previous NTFS version, then thes following
  # fields are present only if the file has been accessed since the upgrade.
  # Recognize the difference by comparing the length of the resident attribute
  # value. If it is 48, then the following fields are missing. If it is 72 then
  # the fields are present. Maybe just check like this:
  #  if (resident.ValueLength < sizeof(STANDARD_INFORMATION)) {
  #   Assume NTFS 1.2- format.
  #   If (volume version is 3.0+)
  #     Upgrade attribute to NTFS 3.0 format.
  #   else
  #     Use NTFS 1.2- format for access.
  #  } else
  #   Use NTFS 3.0 format for access.
  # Only problem is that it might be legal to set the length of the value to
  # arbitrarily large values thus spoiling this check. - But chkdsk probably
  # views that as a corruption, assuming that it behaves like this for all
  # attributes.
  #

  'L',  'max_versions',     # Maximum allowed versions for file. Zero if version numbering is disabled.
  'L',  'ver_num',          # This file's version (if any). Set to zero if maximum_versions is zero
  'L',  'class_id',         # Class id from bidirectional class id index (?)
  'L',  'owner_id',         # Owner_id of the user owning the file. Translate via $Q index
  # in FILE_Extend /$Quota to the quota control entry for the user
  # owning the file. Zero if quotas are disabled.
  'L',  'security_id',      # This is a key in the $SII index and the $SDS data stream in the file $Secure
  'Q',  'quota_charged',    # Number of bytes this file uses from the user's quota - total size of all streams - if zero then quotas are disabled
  'Q',  'update_seq_num',   # This is a direct index into the file $UsnJrnl - if zero then the USN journal is disabled
])

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#boot_sectorObject

Members (these become members of an MiqFS instance).



12
13
14
# File 'lib/fs/MiqFS/modules/NTFS.rb', line 12

def boot_sector
  @boot_sector
end

#cache_hitsObject

Members (these become members of an MiqFS instance).



12
13
14
# File 'lib/fs/MiqFS/modules/NTFS.rb', line 12

def cache_hits
  @cache_hits
end

#drive_rootObject

Members (these become members of an MiqFS instance).



12
13
14
# File 'lib/fs/MiqFS/modules/NTFS.rb', line 12

def drive_root
  @drive_root
end

#index_cacheObject

Members (these become members of an MiqFS instance).



12
13
14
# File 'lib/fs/MiqFS/modules/NTFS.rb', line 12

def index_cache
  @index_cache
end

Instance Method Details

#fs_dirEntries(p) ⇒ Object

Returns String array of all names, sans path.



75
76
77
78
79
80
# File 'lib/fs/MiqFS/modules/NTFS.rb', line 75

def fs_dirEntries(p)
  # Get path index (directory).
  index = ifs_getDir(p)
  return nil if index.nil?
  index.globNames
end

#fs_fileAtime(p) ⇒ Object

Returns Ruby Time object. NOTE: I did find files with 0 time, but they should never appear in this context.



123
124
125
126
127
# File 'lib/fs/MiqFS/modules/NTFS.rb', line 123

def fs_fileAtime(p)
  file = ifs_getFile(p)
  return nil if file.nil?
  file.afn.aTime
end

#fs_fileAtime_obj(fobj) ⇒ Object

Returns a Ruby Time object.



151
152
153
# File 'lib/fs/MiqFS/modules/NTFS.rb', line 151

def fs_fileAtime_obj(fobj)
  fobj.din.afn.aTime
end

#fs_fileClose(_fobj) ⇒ Object

Unless there’s a way to explicitly destroy an object there’s nothing to do here.



192
193
194
# File 'lib/fs/MiqFS/modules/NTFS.rb', line 192

def fs_fileClose(_fobj)
  fobj = nil
end

#fs_fileCtime(p) ⇒ Object

Returns Ruby Time object.



130
131
132
133
134
# File 'lib/fs/MiqFS/modules/NTFS.rb', line 130

def fs_fileCtime(p)
  file = ifs_getFile(p)
  return nil if file.nil?
  file.afn.cTime
end

#fs_fileCtime_obj(fobj) ⇒ Object

Returns a Ruby Time object.



156
157
158
# File 'lib/fs/MiqFS/modules/NTFS.rb', line 156

def fs_fileCtime_obj(fobj)
  fobj.din.afn.cTime
end

#fs_fileDirectory?(p) ⇒ Boolean

Returns true if name is a directory.

Returns:

  • (Boolean)


101
102
103
104
105
106
107
# File 'lib/fs/MiqFS/modules/NTFS.rb', line 101

def fs_fileDirectory?(p)
  file = ifs_getFile(p)
  return false if file.nil?
  file.resolve(boot_sector)
  return true if file.isDir?
  false
end

#fs_fileExists?(p) ⇒ Boolean

Returns true if name exists, false if not.

Returns:

  • (Boolean)


83
84
85
86
87
# File 'lib/fs/MiqFS/modules/NTFS.rb', line 83

def fs_fileExists?(p)
  file = ifs_getFile(p)
  return false if file.nil?
  true
end

#fs_fileFile?(p) ⇒ Boolean

Returns true if name is a regular file. NOTE: If a name isn’t a directory, it’s always a file - so far… (it could be a reparse point [a.k.a. hard link], but that’s for later).

Returns:

  • (Boolean)


92
93
94
95
96
97
98
# File 'lib/fs/MiqFS/modules/NTFS.rb', line 92

def fs_fileFile?(p)
  file = ifs_getFile(p)
  return false if file.nil?
  file.resolve(boot_sector)
  return false if file.isDir?
  true
end

#fs_fileMtime(p) ⇒ Object

Returns Ruby Time object.



137
138
139
140
141
# File 'lib/fs/MiqFS/modules/NTFS.rb', line 137

def fs_fileMtime(p)
  file = ifs_getFile(p)
  return nil if file.nil?
  file.afn.mTime
end

#fs_fileMtime_obj(fobj) ⇒ Object

Returns a Ruby Time obect.



161
162
163
# File 'lib/fs/MiqFS/modules/NTFS.rb', line 161

def fs_fileMtime_obj(fobj)
  fobj.din.afn.mTime
end

#fs_fileOpen(p, _mode = "r") ⇒ Object

New FileObject instance. NOTE: FileObject must have access to NTFS members. This is kind of like a ‘skip this’ thing. NTFS methods use stuff owned by MiqFS, so this is necessary.



174
175
176
177
178
# File 'lib/fs/MiqFS/modules/NTFS.rb', line 174

def fs_fileOpen(p, _mode = "r")
  fobj = FileObject.new(p, self)
  fobj.open
  fobj
end

#fs_fileRead(fobj, len) ⇒ Object

Returns a Ruby String object.



186
187
188
189
# File 'lib/fs/MiqFS/modules/NTFS.rb', line 186

def fs_fileRead(fobj, len)
  return nil if fobj.data.nil?
  fobj.data.read(len)
end

#fs_fileSeek(fobj, offset, whence) ⇒ Object

Seek to the requested position



181
182
183
# File 'lib/fs/MiqFS/modules/NTFS.rb', line 181

def fs_fileSeek(fobj, offset, whence)
  fobj.seek(offset, whence)
end

#fs_fileSize(p) ⇒ Object

Returns size in bytes.



115
116
117
118
119
# File 'lib/fs/MiqFS/modules/NTFS.rb', line 115

def fs_fileSize(p)
  file = ifs_getFile(p)
  return nil if file.nil?
  file.afn.length
end

#fs_fileSize_obj(fobj) ⇒ Object

File size the faster way.



166
167
168
# File 'lib/fs/MiqFS/modules/NTFS.rb', line 166

def fs_fileSize_obj(fobj)
  fobj.din.afn.length
end

#fs_freeBytesObject

Returns free space on file system in bytes.



68
69
70
# File 'lib/fs/MiqFS/modules/NTFS.rb', line 68

def fs_freeBytes
  boot_sector.freeBytes
end

#fs_initObject

File system interface.



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/fs/MiqFS/modules/NTFS.rb', line 43

def fs_init
  self.fsType = "NTFS"

  # Initialize Boot Sector
  @dobj.seek(0, IO::SEEK_SET)
  self.boot_sector = BootSect.new(@dobj)
  boot_sector.setup
  self.drive_root  = boot_sector.rootDir

  # Init cache.
  self.index_cache = LruHash.new(DEF_CACHE_SIZE)
  self.cache_hits  = 0

  # Expose fsId & volName.
  @fsId, @volName = getVolumeInfo
end

#fs_isSymLink?(_p) ⇒ Boolean

Returns true if name is a symbolic link.

Returns:

  • (Boolean)


110
111
112
# File 'lib/fs/MiqFS/modules/NTFS.rb', line 110

def fs_isSymLink?(_p)
  false
end

#getVolumeInfoObject



60
61
62
63
64
65
# File 'lib/fs/MiqFS/modules/NTFS.rb', line 60

def getVolumeInfo
  vi = boot_sector.volumeInfo
  volName = vi["name"]
  fsId    = vi["objectId"]
  return fsId, volName
end

#ifs_getDir(p, miqfs = nil) ⇒ Object

Return an index of a path. Raise error if path doesn’t exist.



239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/fs/MiqFS/modules/NTFS.rb', line 239

def ifs_getDir(p, miqfs = nil)
  $log.debug "NTFS.ifs_getDir >> p=#{p}" if $log && $log.debug?

  # If this is being called from a FileObject instance, then MiqFS owns contained instance members.
  # If this is being called from an NTFS module method, then self owns contained instance members.
  miqfs = self if miqfs.nil?

  # Wack leading drive.
  p = unnormalizePath(p).downcase

  # Get an array of directory names, kill off the first (it's always empty).
  names = p.split(/[\\\/]/)
  names.shift

  # Get the index for this directory
  index = ifs_getIndex(names, miqfs)
  $log.debug "NTFS.ifs_getDir << #{index.nil? ? "Directory not found " : ""}p=#{p}" if $log && $log.debug?
  index
end

#ifs_getFile(p, miqfs = nil) ⇒ Object

Return DirectoryIndexNode for a given file or nil if not exist.



199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/fs/MiqFS/modules/NTFS.rb', line 199

def ifs_getFile(p, miqfs = nil)
  $log.debug "NTFS.ifs_getFile >> p=#{p}" if $log && $log.debug?

  # If this is being called from a FileObject instance, then MiqFS owns contained instance members.
  # If this is being called from an NTFS module method, then self owns contained instance members.
  miqfs = self if miqfs.nil?

  # Wack leading drive.
  p = unnormalizePath(p).downcase

  # Get directory & file as separate strings.
  dir = p.split(/[\\\/]/)
  fname = dir[dir.size - 1]
  fname = "." if fname.nil? # Special case: if fname is nil then dir is root.
  dir = dir.size > 2 ? dir[0...dir.size - 1].join('/') : '/'

  # Check for this file in the cache.
  cache_name = "#{dir == '/' ? '' : dir}/#{fname}"
  if miqfs.index_cache.key?(cache_name)
    miqfs.cache_hits += 1
    file = miqfs.index_cache[cache_name]
    $log.debug "NTFS.ifs_getFile << (cached) p=#{p} din=#{file}" if $log && $log.debug?
    return file
  end

  # Look for file in dir, but don't error if it doesn't exist.
  # NOTE: if p is a directory that's ok, find it.
  file = nil
  index = ifs_getDir(dir, miqfs)
  unless index.nil?
    $log.debug "NTFS.ifs_getFile -- getting din for #{fname} in <#{index.class.name}>" if $log && $log.debug?
    file = index.find(fname)
  end

  $log.debug "NTFS.ifs_getFile << p=#{p} din=#{file}" if $log && $log.debug?
  miqfs.index_cache[cache_name] = file
end

#ifs_getIndex(names, miqfs) ⇒ Object

Return DirectoryIndexNode for given directory or nil if not exist.



260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/fs/MiqFS/modules/NTFS.rb', line 260

def ifs_getIndex(names, miqfs)
  return miqfs.drive_root if names.empty?

  # Check for this path in the cache.
  fname = names.join('/')
  if miqfs.index_cache.key?(fname)
    miqfs.cache_hits += 1
    return miqfs.index_cache[fname]
  end

  name = names.pop
  index = ifs_getIndex(names, miqfs)
  return nil if index.nil?

  din = index.find(name)
  return nil if din.nil?

  index = din.resolve(miqfs.boot_sector).indexRoot
  return nil if index.nil?

  miqfs.index_cache[fname] = index
end

#unnormalizePath(p) ⇒ Object

Wack leading drive leter & colon.



284
285
286
# File 'lib/fs/MiqFS/modules/NTFS.rb', line 284

def unnormalizePath(p)
  p[1] == 58 ? p[2, p.size] : p
end