Class: NTFS::MftEntry

Inherits:
Object
  • Object
show all
Defined in:
lib/fs/ntfs/mft_entry.rb

Overview

MftEntry represents one single MFT entry.

Constant Summary collapse

DEBUG_TRACE_MFT =
false && $log
MFT_RECORD_IN_USE =

Not set if file has been deleted

0x0001
MFT_RECORD_IS_DIRECTORY =

Set if record describes a directory

0x0002
MFT_RECORD_IS_4 =

MFT_RECORD_IS_4 exists on all $Extend sub-files. It seems that it marks it is a metadata file with MFT record >24, however, it is unknown if it is limited to metadata files only.

0x0004
MFT_RECORD_IS_VIEW_INDEX =

MFT_RECORD_IS_VIEW_INDEX exists on every metafile with a non directory index, that means an INDEX_ROOT and an INDEX_ALLOCATION with a name other than “$I30”. It is unknown if it is limited to metadata files only.

0x0008
EXPECTED_SIGNATURE =
'FILE'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(bs, recordNumber) ⇒ MftEntry

Returns a new instance of MftEntry.



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/fs/ntfs/mft_entry.rb', line 100

def initialize(bs, recordNumber)
  log_prefix = "MIQ(NTFS::MftEntry.initialize)"
  raise "#{log_prefix} Nil boot sector" if bs.nil?

  @attribs         = []
  @attribs_by_type = Hash.new { |h, k| h[k] = [] }

  # Buffer boot sector & seek to requested record.
  @boot_sector = bs
  bs.stream.seek(bs.mftRecToBytePos(recordNumber))

  # Get & decode the FILE_RECORD.
  @buf       = bs.stream.read(bs.bytesPerFileRec)
  @mft_entry = FILE_RECORD.decode(@buf)

  # Adjust for older versions (don't have unused1 and mft_rec_num).
  version = bs.version
  if !version.nil? && version < 4.0
    @mft_entry['fixup_seq_num'] = @mft_entry['unused1']
    @mft_entry['mft_rec_num']   = recordNumber
  end

  # Set accessor data.
  @sequenceNum = @mft_entry['seq_num']
  @recNum      = @mft_entry['mft_rec_num']
  @flags       = @mft_entry['flags']

  begin
    # Check for proper signature.
    NTFS::Utils.validate_signature(@mft_entry['signature'], EXPECTED_SIGNATURE)
    # Process per-sector "fixups" that NTFS uses to detect corruption of multi-sector data structures
    @buf = NTFS::Utils.process_fixups(@buf, @boot_sector.bytesPerSector, @mft_entry['usa_offset'], @mft_entry['usa_count'])
  rescue => err
    emsg = "#{log_prefix} Invalid MFT Entry <#{recordNumber}> because: <#{err.message}>"
    $log.error("#{emsg}\n#{dump}")
    raise emsg
  end

  @buf = @buf[@mft_entry['offset_to_attrib']..-1]

  loadAttributeHeaders
end

Instance Attribute Details

#attribsObject (readonly)

Returns the value of attribute attribs.



91
92
93
# File 'lib/fs/ntfs/mft_entry.rb', line 91

def attribs
  @attribs
end

#boot_sectorObject (readonly)

Returns the value of attribute boot_sector.



91
92
93
# File 'lib/fs/ntfs/mft_entry.rb', line 91

def boot_sector
  @boot_sector
end

#mft_entryObject (readonly)

Returns the value of attribute mft_entry.



91
92
93
# File 'lib/fs/ntfs/mft_entry.rb', line 91

def mft_entry
  @mft_entry
end

#recNumObject (readonly)

Returns the value of attribute recNum.



91
92
93
# File 'lib/fs/ntfs/mft_entry.rb', line 91

def recNum
  @recNum
end

#sequenceNumObject (readonly)

Returns the value of attribute sequenceNum.



91
92
93
# File 'lib/fs/ntfs/mft_entry.rb', line 91

def sequenceNum
  @sequenceNum
end

Instance Method Details

#attributeDataObject



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/fs/ntfs/mft_entry.rb', line 166

def attributeData
  if @attributeData.nil?
    dataArray = getAttributes(AT_DATA)

    unless dataArray.nil?
      dataArray.compact!
      if dataArray.size > 0
        @attributeData = dataArray.shift
        dataArray.each { |datum| @attributeData.data.addRun(datum.run) }
      end
    end
  end

  @attributeData
end

#attributeListObject



186
187
188
# File 'lib/fs/ntfs/mft_entry.rb', line 186

def attributeList
  @attributeList ||= loadFirstAttribute(AT_ATTRIBUTE_LIST)
end

#createAttribute(offset, header) ⇒ Object



229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/fs/ntfs/mft_entry.rb', line 229

def createAttribute(offset, header)
  $log.debug "NtfsMftEntry.createAttribute >> type=#{TypeName[header.type]} header=#{header.inspect}" if DEBUG_TRACE_MFT

  buf = header.get_value(@buf[offset..-1], @boot_sector)

  return StandardInformation.new(buf)                             if header.type == AT_STANDARD_INFORMATION
  return FileName.new(buf)                                        if header.type == AT_FILE_NAME
  return ObjectId.new(buf)                                        if header.type == AT_OBJECT_ID
  return VolumeName.new(buf)                                      if header.type == AT_VOLUME_NAME
  return VolumeInformation.new(buf)                               if header.type == AT_VOLUME_INFORMATION
  return AttributeList.new(buf, @boot_sector)                     if header.type == AT_ATTRIBUTE_LIST
  return AttribData.create_from_header(header, buf)               if header.type == AT_DATA
  return IndexRoot.create_from_header(header, buf, @boot_sector)  if header.type == AT_INDEX_ROOT
  return IndexAllocation.create_from_header(header, buf)          if header.type == AT_INDEX_ALLOCATION
  return Bitmap.create_from_header(header, buf)                   if header.type == AT_BITMAP

  # Attribs are unrecognized if they don't appear in TypeName.
  unless TypeName.key?(header.type)
    msg = "MIQ(NTFS::MftEntry.createAttribute) Unrecognized attribute type: 0x#{'%08x' % header.type} -- header: #{header.inspect}"
    $log.warn(msg) if $log
    raise(msg)
  end

  nil
end

#dumpObject



255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/fs/ntfs/mft_entry.rb', line 255

def dump
  ref = NTFS::Utils.MkRef(@mft_entry['base_mft_record'])

  out = "\#<#{self.class}:0x#{'%08x' % object_id}>\n"
  out << "  Signature       : #{@mft_entry['signature']}\n"
  out << "  USA Offset      : #{@mft_entry['usa_offset']}\n"
  out << "  USA Count       : #{@mft_entry['usa_count']}\n"
  out << "  Log file seq num: #{@mft_entry['lsn']}\n"
  out << "  Sequence number : #{@mft_entry['seq_num']}\n"
  out << "  Hard link count : #{@mft_entry['hard_link_count']}\n"
  out << "  Offset to attrib: #{@mft_entry['offset_to_attrib']}\n"
  out << "  Flags           : 0x#{'%04x' % @mft_entry['flags']}\n"
  out << "  Real size of rec: #{@mft_entry['bytes_in_use']}\n"
  out << "  Alloc siz of rec: #{@mft_entry['bytes_allocated']}\n"
  out << "  Ref to base rec : seq #{ref[0]}, entry #{ref[1]}\n"
  out << "  Next attrib id  : #{@mft_entry['next_attrib_id']}\n"
  out << "  Unused1         : #{@mft_entry['unused1']}\n"
  out << "  MFT rec num     : #{@mft_entry['mft_rec_num']}\n"
  out << "  Fixup seq num   : 0x#{'%04x' % @mft_entry['fixup_seq_num']}\n"
  @attribs.each do |hash|
    begin
      header = hash["header"]
      out << header.dump

      attrib = hash["attr"]
      out << attrib.dump unless attrib.nil?
    rescue NoMethodError
    end
  end

  out
end

#getAttributes(attribType) ⇒ Object



207
208
209
210
# File 'lib/fs/ntfs/mft_entry.rb', line 207

def getAttributes(attribType)
  $log.debug "NtfsMftEntry.getAttributes        - MFT(#{@recNum}) getting Attr: #{TypeName[attribType]}" if DEBUG_TRACE_MFT
  attributeList.nil? ? loadAttributes(attribType) : attributeList.loadAttributes(attribType)
end

#getFirstAttribute(attribType) ⇒ Object



203
204
205
# File 'lib/fs/ntfs/mft_entry.rb', line 203

def getFirstAttribute(attribType)
  getAttributes(attribType).first
end

#indexRootObject



156
157
158
159
160
161
162
163
164
# File 'lib/fs/ntfs/mft_entry.rb', line 156

def indexRoot
  if @indexRoot.nil?
    @indexRoot             = getFirstAttribute(AT_INDEX_ROOT)
    @indexRoot.bitmap      = getFirstAttribute(AT_BITMAP)      unless @indexRoot.nil?
    @indexRoot.allocations = getAttributes(AT_INDEX_ALLOCATION) unless @indexRoot.nil?
  end

  @indexRoot
end

#isDeleted?Boolean

Returns:

  • (Boolean)


148
149
150
# File 'lib/fs/ntfs/mft_entry.rb', line 148

def isDeleted?
  !NTFS::Utils.gotBit?(@flags, MFT_RECORD_IN_USE)
end

#isDir?Boolean

Returns:

  • (Boolean)


152
153
154
# File 'lib/fs/ntfs/mft_entry.rb', line 152

def isDir?
  NTFS::Utils.gotBit?(@flags, MFT_RECORD_IS_DIRECTORY)
end

#loadAttributeHeadersObject



190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/fs/ntfs/mft_entry.rb', line 190

def loadAttributeHeaders
  offset = 0
  while h = AttribHeader.new(@buf[offset..-1])
    break if h.type.nil? || h.type == AT_END
    $log.debug "NtfsMftEntry.loadAttributeHeaders - MFT(#{@recNum}) adding  Attr: #{h.typeName}" if DEBUG_TRACE_MFT
    attrib = {"type" => h.type, "offset" => offset, "header" => h}
    @attribs << attrib
    @attribs_by_type[h.type] << attrib
    offset += h.length
  end
  @attribs_by_type.each { |k, v| $log.debug "NtfsMftEntry.loadAttributeHeaders - MFT(#{@recNum}) Attr: #{TypeName[k]} => Count: #{v.size}" }  if DEBUG_TRACE_MFT
end

#loadAttributes(attribType) ⇒ Object



216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/fs/ntfs/mft_entry.rb', line 216

def loadAttributes(attribType)
  result  = []
  if @attribs_by_type.key?(attribType)
    $log.debug "NtfsMftEntry.loadAttributes       - MFT(#{@recNum}) loading Attr: #{TypeName[attribType]}" if DEBUG_TRACE_MFT

    @attribs_by_type[attribType].each do |attrib|
      attrib["attr"] = createAttribute(attrib["offset"], attrib["header"]) unless attrib.key?('attr')
      result << attrib["attr"]
    end
  end
  result
end

#loadFirstAttribute(attribType) ⇒ Object



212
213
214
# File 'lib/fs/ntfs/mft_entry.rb', line 212

def loadFirstAttribute(attribType)
  loadAttributes(attribType).first
end

#rootAttributeDataObject



182
183
184
# File 'lib/fs/ntfs/mft_entry.rb', line 182

def rootAttributeData
  loadFirstAttribute(AT_DATA)
end

#to_sObject

For string rep, if valid return record number.



144
145
146
# File 'lib/fs/ntfs/mft_entry.rb', line 144

def to_s
  @mft_entry['mft_rec_num'].to_s
end