Class: Fat32::Directory

Inherits:
Object
  • Object
show all
Defined in:
lib/fs/fat32/directory.rb

Constant Summary collapse

MAX_ENT_SIZE =

Maximum LFN entry span in bytes (LFN entries can span clusters).

640
FE_DIR =

Find entry flags.

0
FE_FILE =
1
FE_EITHER =
2
GF_W98 =

Get free entry behaviors. Windows 98 returns the first deleted or unallocated entry. Windows XP returns the first unallocated entry. Advantage W98: less allocation, advantage WXP: deleted entries are not overwritten.

0
GF_WXP =
1

Instance Method Summary collapse

Constructor Details

#initialize(bs, cluster = nil) ⇒ Directory

Initialization



35
36
37
38
39
40
41
42
43
44
# File 'lib/fs/fat32/directory.rb', line 35

def initialize(bs, cluster = nil)
  raise "Nil boot sector" if bs.nil?
  cluster = bs.rootCluster if cluster.nil?

  @bs = bs
  # Allocate one cluster if cluster is zero.
  cluster = @bs.allocClusters(0) if cluster == 0
  @cluster = cluster
  @data, @all_clusters = getDirData
end

Instance Method Details

#countFreeEntries(behavior, buf) ⇒ Object

Return the number of contiguous free entries starting at buf according to behavior.



174
175
176
177
178
179
180
181
182
183
184
# File 'lib/fs/fat32/directory.rb', line 174

def countFreeEntries(behavior, buf)
  num_free = 0
  0.step(buf.size - 1, DIR_ENT_SIZE) do |offset|
    if isFree(buf[offset], behavior)
      num_free += 1
    else
      return num_free
    end
  end
  num_free
end

#createFile(name) ⇒ Object



143
144
145
146
147
148
149
150
151
152
153
# File 'lib/fs/fat32/directory.rb', line 143

def createFile(name)
  de = DirectoryEntry.new; de.name = name
  until findEntry(de.shortName).nil?
    raise "Duplicate file name: #{de.shortName}" unless de.shortName.include?("~")
    de.incShortName
  end
  de.parentOffset, de.parentCluster = getFirstFreeEntry(de.numEnts)
  de.writeEntry(@bs)
  @data, @all_clusters = getDirData
  de
end

#findEntry(name, flags = FE_EITHER) ⇒ Object

Return a DirectoryEntry for a specific file (or subdirectory).



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/fs/fat32/directory.rb', line 72

def findEntry(name, flags = FE_EITHER)
  de = nil # found directory entry.
  skip_next = found = false
  offset = 0

  # Look for appropriate records.
  0.step(@data.length - 1, DIR_ENT_SIZE) do|offset|
    # Check allocation status (ignore if deleted, done if not allocated).
    alloc_flags = @data[offset]
    next if alloc_flags == DirectoryEntry::AF_DELETED
    break if alloc_flags == DirectoryEntry::AF_NOT_ALLOCATED

    # Skip LFN entries unless it's the first (last iteration already chewed them all up).
    attrib = @data[offset + ATTRIB_OFFSET]
    if attrib == DirectoryEntry::FA_LFN && (alloc_flags & DirectoryEntry::AF_LFN_LAST != DirectoryEntry::AF_LFN_LAST)
      # Also skip the next entry (it's the base entry for the last dir ent).
      skip_next = true; next
    end
    if skip_next
      skip_next = false; next
    end

    # If a specific type of record was requested, look for only that type.
    # NOTE: You know, it's possible to look ahead and see what the base entry is.
    if flags != FE_EITHER && attrib != DirectoryEntry::FA_LFN
      next if flags == FE_DIR && (attrib & DirectoryEntry::FA_DIRECTORY == 0)
      next if flags == FE_FILE && (attrib & DirectoryEntry::FA_DIRECTORY != 0)
    end

    # Potential match... get a DirectoryEntry & stop if found.
    de = DirectoryEntry.new(@data[offset, MAX_ENT_SIZE])
    # TODO: - what if the name ends with a dot & there's another dot in the name?
    if de.name.downcase == name.downcase || de.shortName.downcase == name.downcase
      found = true
      break
    end
  end
  return nil unless found
  parentLoc = offset.divmod(@bs.bytesPerCluster)
  de.parentCluster = @all_clusters[parentLoc[0]].number
  de.parentOffset = parentLoc[1]
  de
end

#getClusterStatus(offset) ⇒ Object



217
218
219
220
# File 'lib/fs/fat32/directory.rb', line 217

def getClusterStatus(offset)
  idx = offset.divmod(@bs.bytesPerCluster)[0]
  @all_clusters[idx]
end

#getDirDataObject



197
198
199
200
201
202
203
204
205
206
207
# File 'lib/fs/fat32/directory.rb', line 197

def getDirData
  allClusters = []
  clus = @cluster
  allClusters << mkClusterStatus(clus, 0)
  buf = @bs.getCluster(clus); data = nil
  while (data = @bs.getNextCluster(clus)) != nil
    clus = data[0]; buf += data[1]
    allClusters << mkClusterStatus(clus, 0)
  end
  return buf, allClusters
end

#getFirstFreeEntry(num_entries = 1, behavior = GF_W98) ⇒ Object

Get free entry or entries in directory data. If not exist, allocate cluster.



159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/fs/fat32/directory.rb', line 159

def getFirstFreeEntry(num_entries = 1, behavior = GF_W98)
  0.step(@data.size - 1, DIR_ENT_SIZE) do |offset|
    next if @data[offset] != DirectoryEntry::AF_NOT_ALLOCATED && @data[offset] != DirectoryEntry::AF_DELETED
    num = countFreeEntries(behavior, @data[offset..-1])
    return offset.divmod(@bs.bytesPerCluster)[1], getClusterStatus(offset).number if num >= num_entries
  end

  # Must allocate another cluster.
  cluster = @bs.allocClusters(@cluster)
  @data += MemoryBuffer.create(@bs.bytesPerCluster)
  @all_clusters << mkClusterStatus(cluster, 0)
  return 0, cluster
end

#globNamesObject

Return all names in directory as a sorted string array.



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/fs/fat32/directory.rb', line 50

def globNames
  names = []
  cluster = @cluster
  mf = StringIO.new(@bs.getCluster(cluster))
  loop do
    (@bs.bytesPerCluster / DIR_ENT_SIZE - 1).times do
      de = DirectoryEntry.new(mf.read)
      break if de.name == ''
      names << de.name.downcase if de.name !=
                                   DirectoryEntry::AF_DELETED && de.name[0] != DirectoryEntry::AF_DELETED
      mf = StringIO.new(de.unused)
      break if mf.size == 0
    end
    data = @bs.getNextCluster(cluster)
    break if data.nil?
    cluster = data[0]
    mf = StringIO.new(data[1])
  end
  names.sort!
end

#isFree(allocStatus, behavior) ⇒ Object



186
187
188
189
190
191
192
193
194
195
# File 'lib/fs/fat32/directory.rb', line 186

def isFree(allocStatus, behavior)
  if behavior == GF_W98
    return true if allocStatus == DirectoryEntry::AF_NOT_ALLOCATED || allocStatus == DirectoryEntry::AF_DELETED
  elsif behavior == GF_WXP
    return true if allocStatus == DirectoryEntry::AF_NOT_ALLOCATED
  else
    raise "Fat32Directory#isFree: Unknown behavior: #{behavior}"
  end
  false
end

#mkClusterStatus(num, dirty) ⇒ Object

TODO: - Loose this idea.



210
211
212
213
214
215
# File 'lib/fs/fat32/directory.rb', line 210

def mkClusterStatus(num, dirty)
  status = OpenStruct.new
  status.number = num
  status.dirty = dirty
  status
end

#mkdir(name) ⇒ Object



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/fat32/directory.rb', line 116

def mkdir(name)
  dir = createFile(name)
  data = FileData.new(dir, @bs)
  dir.setAttribute(DirectoryEntry::FA_ARCHIVE, false)
  dir.setAttribute(DirectoryEntry::FA_DIRECTORY)
  dir.writeEntry(@bs)

  # Write dot and double dot directories.
  dot = DirectoryEntry.new; dotdot = DirectoryEntry.new
  dot.name = "."; dotdot.name = ".."
  dot.setAttribute(DirectoryEntry::FA_ARCHIVE, false)
  dot.setAttribute(DirectoryEntry::FA_DIRECTORY)
  dotdot.setAttribute(DirectoryEntry::FA_ARCHIVE, false)
  dotdot.setAttribute(DirectoryEntry::FA_DIRECTORY)
  buf = dot.raw + dotdot.raw
  data.write(buf)
  dir.firstCluster = data.firstCluster
  dir.writeEntry(@bs)

  # Update firsCluster in . and .. (if .. is root then it's 0, not 2).
  dot.firstCluster = dir.firstCluster
  dotdot.firstCluster = dir.parentCluster == 2 ? 0 : dir.parentCluster
  buf = dot.raw + dotdot.raw
  data.rewind
  data.write(buf)
end