Class: NTFS::IndexRoot

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

Constant Summary collapse

DEBUG_TRACE_FIND =
false && $log
CT_BINARY =

Binary compare, MSB is first (does that mean big endian?)

0x00000000
CT_FILENAME =

UNICODE strings.

0x00000001
CT_UNICODE =

UNICODE, upper case first.

0x00000002
CT_ULONG =

Standard ULONG, 32-bits little endian.

0x00000010
CT_SID =

Security identifier.

0x00000011
CT_SECHASH =

First security hash, then security identifier.

0x00000012
CT_ULONGS =

Set of ULONGS? (doc is unclear - indicates GUID).

0x00000013

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(buf, boot_sector) ⇒ IndexRoot

Returns a new instance of IndexRoot.



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/fs/ntfs/attrib_index_root.rb', line 62

def initialize(buf, boot_sector)
  log_prefix = "MIQ(NTFS::IndexRoot.initialize)"

  raise "#{log_prefix} Nil buffer"        if buf.nil?
  raise "#{log_prefix} Nil boot sector"   if boot_sector.nil?

  buf                = buf.read(buf.length) if buf.kind_of?(DataRun)
  @air               = ATTRIB_INDEX_ROOT.decode(buf)
  buf                = buf[SIZEOF_ATTRIB_INDEX_ROOT..-1]

  # Get accessor data.
  @type              = @air['type']
  @collation_rule    = @air['collation_rule']
  @byteSize          = @air['index_block_size']
  @clusterSize       = @air['size_of_index_clus']

  @boot_sector       = boot_sector

  # Get node header & index.
  @foundEntries      = {}
  @indexNodeHeader   = IndexNodeHeader.new(buf)
  @indexEntries      = cleanAllocEntries(DirectoryIndexNode.nodeFactory(buf[@indexNodeHeader.startEntries..-1]))
  @indexAlloc        = {}
end

Instance Attribute Details

#indexObject (readonly)

Returns the value of attribute index.



60
61
62
# File 'lib/fs/ntfs/attrib_index_root.rb', line 60

def index
  @index
end

#indexAllocObject (readonly)

Returns the value of attribute indexAlloc.



60
61
62
# File 'lib/fs/ntfs/attrib_index_root.rb', line 60

def indexAlloc
  @indexAlloc
end

#nodeHeaderObject (readonly)

Returns the value of attribute nodeHeader.



60
61
62
# File 'lib/fs/ntfs/attrib_index_root.rb', line 60

def nodeHeader
  @nodeHeader
end

#typeObject (readonly)

Returns the value of attribute type.



60
61
62
# File 'lib/fs/ntfs/attrib_index_root.rb', line 60

def type
  @type
end

Class Method Details

.create_from_header(header, buf, bs) ⇒ Object



54
55
56
57
58
# File 'lib/fs/ntfs/attrib_index_root.rb', line 54

def self.create_from_header(header, buf, bs)
  return IndexRoot.new(buf, bs) if header.containsFileNameIndexes?
  $log.debug("Skipping #{header.typeName} for name <#{header.name}>") if $log
  nil
end

Instance Method Details

#allocations=(indexAllocations) ⇒ Object



91
92
93
94
95
96
97
# File 'lib/fs/ntfs/attrib_index_root.rb', line 91

def allocations=(indexAllocations)
  @indexAllocRuns = []
  if @indexNodeHeader.hasChildren? && indexAllocations
    indexAllocations.each { |alloc| @indexAllocRuns << [alloc.header, alloc.data_run] }
  end
  @indexAllocRuns
end

#bitmap=(bmp) ⇒ Object



99
100
101
102
103
104
105
# File 'lib/fs/ntfs/attrib_index_root.rb', line 99

def bitmap=(bmp)
  if @indexNodeHeader.hasChildren?
    @bitmap = bmp.data.unpack("b#{bmp.length * 8}") unless bmp.nil?
  end

  @bitmap
end

#cleanAllocEntries(entries) ⇒ Object



187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/fs/ntfs/attrib_index_root.rb', line 187

def cleanAllocEntries(entries)
  cleanEntries = []
  entries.each do |e|
    if e.isLast? || !(e.contentLen == 0 || (e.refMft[1] < 12 && e.name[0, 1] == "$"))
      cleanEntries << e
      # Since we are already looping through all entries to clean
      #   them we can store them in a lookup for optimization
      @foundEntries[e.name.downcase] = e unless e.isLast?
    end
  end
  cleanEntries
end

#dumpObject



238
239
240
241
242
243
244
245
246
247
# File 'lib/fs/ntfs/attrib_index_root.rb', line 238

def dump
  out = "\#<#{self.class}:0x#{'%08x' % object_id}>\n"
  out << "  Type                 : 0x#{'%08x' % @type}\n"
  out << "  Collation Rule       : #{@collation_rule}\n"
  out << "  Index size (bytes)   : #{@byteSize}\n"
  out << "  Index size (clusters): #{@clusterSize}\n"
  @indexEntries.each { |din| out << din.dump }
  out << "---\n"
  out
end

#find(name) ⇒ Object

Find a name in this index.



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/fs/ntfs/attrib_index_root.rb', line 108

def find(name)
  log_prefix = "MIQ(NTFS::IndexRoot.find)"

  name = name.downcase
  $log.debug "#{log_prefix} Searching for [#{name}]" if DEBUG_TRACE_FIND
  if @foundEntries.key?(name)
    $log.debug "#{log_prefix} Found [#{name}] (cached)" if DEBUG_TRACE_FIND
    return @foundEntries[name]
  end

  found = findInEntries(name, @indexEntries)
  if found.nil?
    # Fallback to full directory search if not found
    $log.debug "#{log_prefix} [#{name}] not found.  Performing full directory scan." if $log
    found = findBackup(name)
    $log.send(found.nil? ? :debug : :warn, "#{log_prefix} [#{name}] #{found.nil? ? "not " : ""}found in full directory scan.")  if $log
  end
  found
end

#findBackup(name) ⇒ Object



155
156
157
# File 'lib/fs/ntfs/attrib_index_root.rb', line 155

def findBackup(name)
  globEntriesByName[name]
end

#findInEntries(name, entries) ⇒ Object



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/fs/ntfs/attrib_index_root.rb', line 134

def findInEntries(name, entries)
  log_prefix = "MIQ(NTFS::IndexRoot.findInEntries)"

  if @foundEntries.key?(name)
    $log.debug "#{log_prefix} Found [#{name}] in #{entries.collect { |e| e.isLast? ? "**last**" : e.name.downcase }.inspect}" if DEBUG_TRACE_FIND
    return @foundEntries[name]
  end

  $log.debug "#{log_prefix} Searching for [#{name}] in #{entries.collect { |e| e.isLast? ? "**last**" : e.name.downcase }.inspect}" if DEBUG_TRACE_FIND
  # TODO: Uses linear search within an index entry; switch to more performant search eventually
  entries.each do |e|
    $log.debug "#{log_prefix} before [#{e.isLast? ? "**last**" : e.name.downcase}]" if DEBUG_TRACE_FIND
    if e.isLast? || name < e.name.downcase
      $log.debug "#{log_prefix} #{e.hasChild? ? "Sub-search in child vcn [#{e.child}]" : "No sub-search"}" if DEBUG_TRACE_FIND
      return e.hasChild? ? findInEntries(name, getIndexAllocEntries(e.child)) : nil
    end
  end
  $log.debug "#{log_prefix} Did not find [#{name}]" if DEBUG_TRACE_FIND
  nil
end

#getIndexAllocEntries(vcn) ⇒ Object



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/fs/ntfs/attrib_index_root.rb', line 159

def getIndexAllocEntries(vcn)
  unless @indexAlloc.key?(vcn)
    log_prefix = "MIQ(NTFS::IndexRoot.getIndexAllocEntries)"

    begin
      raise "not allocated"    if @bitmap[vcn, 1] == "0"
      header, run = @indexAllocRuns.detect { |h, _r| vcn >= h.specific['first_vcn'] && vcn <= h.specific['last_vcn'] }
      raise "header not found" if header.nil?
      raise "run not found"    if run.nil?

      run.seekToVcn(vcn - header.specific['first_vcn'])
      buf = run.read(@byteSize)

      raise "buffer not found" if buf.nil?
      raise "buffer signature is expected to be INDX, but is [#{buf[0, 4].inspect}]" if buf[0, 4] != "INDX"
      irh = IndexRecordHeader.new(buf, @boot_sector.bytesPerSector)
      buf = irh.data[IndexRecordHeader.size..-1]
      inh = IndexNodeHeader.new(buf)
      @indexAlloc[vcn] = cleanAllocEntries(DirectoryIndexNode.nodeFactory(buf[inh.startEntries..-1]))
    rescue => err
      $log.warn "#{log_prefix} Unable to read data from index allocation at vcn [#{vcn}] because <#{err.message}>\n#{dump}" if $log
      @indexAlloc[vcn] = []
    end
  end

  @indexAlloc[vcn]
end

#globAllEntries(entries) ⇒ Object



229
230
231
232
233
234
235
236
# File 'lib/fs/ntfs/attrib_index_root.rb', line 229

def globAllEntries(entries)
  ret = []
  entries.each do |e|
    ret += globAllEntries(getIndexAllocEntries(e.child)) if e.hasChild?
    ret << e unless e.isLast?
  end
  ret
end

#globEntriesObject



200
201
202
203
204
205
206
207
208
209
210
# File 'lib/fs/ntfs/attrib_index_root.rb', line 200

def globEntries
  return @globEntries unless @globEntries.nil?

  # Since we are reading all entries, retrieve all of the data in one call
  @indexAllocRuns.each do |_h, r|
    r.rewind
    r.read(r.length)
  end

  @globEntries = globAllEntries(@indexEntries)
end

#globEntriesByNameObject



212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/fs/ntfs/attrib_index_root.rb', line 212

def globEntriesByName
  log_prefix = "MIQ(NTFS::IndexRoot.globEntriesByName)"

  if @globbedEntriesByName
    $log.debug "#{log_prefix} Using cached globEntries." if DEBUG_TRACE_FIND
    return @foundEntries
  end

  $log.debug "#{log_prefix} Initializing globEntries:" if DEBUG_TRACE_FIND
  globEntries.each do |e|
    $log.debug "#{log_prefix} #{e.isLast? ? "**last**" : e.name.downcase}" if DEBUG_TRACE_FIND
    @foundEntries[e.name.downcase] = e
  end
  @globbedEntriesByName = true
  @foundEntries
end

#globNamesObject

Return all names in this index as a sorted string array.



129
130
131
132
# File 'lib/fs/ntfs/attrib_index_root.rb', line 129

def globNames
  @globNames = globEntries.collect { |e| e.namespace == NTFS::FileName::NS_DOS ? nil : e.name.downcase }.compact if @globNames.nil?
  @globNames
end

#to_sObject



87
88
89
# File 'lib/fs/ntfs/attrib_index_root.rb', line 87

def to_s
  @type.to_s
end