Module: Fat32

Included in:
FileObject
Defined in:
lib/fs/fat32/boot_sect.rb,
lib/fs/fat32/directory.rb,
lib/fs/fat32/file_data.rb,
lib/fs/MiqFS/modules/Fat32.rb,
lib/fs/fat32/directory_entry.rb

Overview

TODO: reserved1 is the infamous magic number. Somehow it works to preserve case on Windows XP. Nobody seems to know how. Here it is always set to 0 (which yields uppercase names on XP).

Defined Under Namespace

Classes: BootSect, Directory, DirectoryEntry, FileData, FileObject

Constant Summary collapse

BOOT_SECT =
BinaryStruct.new([
  'a3', 'jmp_boot',       # Jump to boot loader.
  'a8', 'oem_name',       # OEM Name in ASCII.
  'S',  'bytes_per_sec',  # Bytes per sector: 512, 1024, 2048 or 4096.
  'C',  'sec_per_clus',   # Sectors per cluster, size must be < 32K.
  'S',  'res_sec',        # Reserved sectors.
  'C',  'num_fats',       # Typically 2, but can be 1.
  'S',  'max_root',       # Max files in root dir - 0 FOR FAT32.
  'S',  'num_sec16',      # 16-bit number of sectors in file system (0 if 32-bits needed).
  'C',  'media_type',     # Ususally F8, but can be F0 for removeable.
  'S',  'fat_size16',     # 16-bit number of sectors in FAT, 0 FOR FAT32.
  'S',  'sec_per_track',  # Sectors per track.
  'S',  'num_heads',      # Number of heads.
  'L',  'pre_sec',        # Sectors before the start of the partition.
  'L',  'num_sec32',      # 32-bit number of sectors in the file system (0 if 16-bit num used).
  'L',  'fat_size32',     # 32-bit number of sectors in FAT.
  'S',  'fat_usage',      # Describes how FATs are used: See FU_ below.
  'S',  'version',        # Major & minor version numbers.
  'L',  'root_clus',      # Cluster location of root directory.
  'S',  'fsinfo_sec',     # Sector location of FSINFO structure .
  'S',  'boot_bkup',      # Sector location of boot sector backup.
  'a12',  'reserved1',    # Reserved.
  'C',  'drive_num',      # INT13 drive number.
  'C',  'unused1',        # Unused.
  'C',  'ex_sig',         # If 0x29, then the next three values are valid.
  'L',  'serial_num',     # Volume serial number.
  'a11',  'label',        # Volume label.
  'a8', 'fs_label',       # File system type label, not required.
  # NOTE: MS always uses "FAT32   ". For probe, seek to 66 & verify 0x29,
  # then seek to 82 & read 8, compare with "FAT32   ".
  'a420', nil,            # Unused.
  'S',  'signature',      # 0xaa55
])
SIZEOF_BOOT_SECT =
BOOT_SECT.size
DOS_SIGNATURE =
0xaa55
FSINFO =
BinaryStruct.new([
  'a4', 'sig1',       # Signature - 0x41615252 (RRaA).
  'a480', nil,        # Unused.
  'a4', 'sig2',       # Signature - 0x61417272 (rrAa).
  'L',  'free_clus',  # Number of free clusters.
  'L',  'next_free',  # Next free cluster.
  'a12',  nil,        # Unused.
  'L',  'sig3',       # Signature - 0xaa550000.
])
SIZEOF_FSINFO =
FSINFO.size
FSINFO_SIG1 =
"RRaA"
FSINFO_SIG2 =
"rrAa"
FSINFO_SIG3 =
0xaa550000
DEF_CACHE_SIZE =

Default directory cache size.

50
DIR_ENT_SFN =
BinaryStruct.new([
  'a11',  'name',         # If name[0] = 0, unallocated; if name[0] = 0xe5, deleted. DOES NOT INCLUDE DOT.
  'C',  'attributes',     # See FA_ below. If 0x0f then LFN entry.
  'C',  'reserved1',      # Reserved.
  'C',  'ctime_tos',      # Created time, tenths of second.
  'S',  'ctime_hms',      # Created time, hours, minutes & seconds.
  'S',  'ctime_day',      # Created day.
  'S',  'atime_day',      # Accessed day.
  'S',  'first_clus_hi',  # Hi 16-bits of first cluster address.
  'S',  'mtime_hms',      # Modified time, hours, minutes & seconds.
  'S',  'mtime_day',      # Modified day.
  'S',  'first_clus_lo',  # Lo 16-bits of first cluster address.
  'L',  'file_size',      # Size of file (0 for directories).
])
DIR_ENT_LFN =
BinaryStruct.new([
  'C',  'seq_num',    # Sequence number, bit 6 marks end, 0xe5 if deleted.
  'a10',  'name',     # UNICODE chars 1-5 of name.
  'C',  'attributes', # Always 0x0f.
  'C',  'reserved1',  # Reserved.
  'C',  'checksum',   # Checksum of SFN entry, all LFN entries must match.
  'a12',  'name2',    # UNICODE chars 6-11 of name.
  'S',  'reserved2',  # Reserved.
  'a4', 'name3'       # UNICODE chars 12-13 of name.
])
CHARS_PER_LFN =
13
LFN_NAME_MAXLEN =
260
DIR_ENT_SIZE =
32
ATTRIB_OFFSET =
11

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#boot_sectorObject

Members (these become members of an MiqFS instance).



15
16
17
# File 'lib/fs/MiqFS/modules/Fat32.rb', line 15

def boot_sector
  @boot_sector
end

#cache_hitsObject

Members (these become members of an MiqFS instance).



15
16
17
# File 'lib/fs/MiqFS/modules/Fat32.rb', line 15

def cache_hits
  @cache_hits
end

#dir_cacheObject

Members (these become members of an MiqFS instance).



15
16
17
# File 'lib/fs/MiqFS/modules/Fat32.rb', line 15

def dir_cache
  @dir_cache
end

#drive_rootObject

Members (these become members of an MiqFS instance).



15
16
17
# File 'lib/fs/MiqFS/modules/Fat32.rb', line 15

def drive_root
  @drive_root
end

#rootDirEntObject

Returns the value of attribute rootDirEnt.



16
17
18
# File 'lib/fs/MiqFS/modules/Fat32.rb', line 16

def rootDirEnt
  @rootDirEnt
end

Instance Method Details

#fs_dirEntries(p) ⇒ Object

Returns String array of all names, sans path.



89
90
91
92
93
94
# File 'lib/fs/MiqFS/modules/Fat32.rb', line 89

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

#fs_dirMkdir(p) ⇒ Object

Make a directory. Parent must exist.



97
98
99
100
101
102
103
104
# File 'lib/fs/MiqFS/modules/Fat32.rb', line 97

def fs_dirMkdir(p)
  de = ifs_getFile(p)
  raise "Name already exists: #{p}" unless de.nil?
  parent, name = File.split(p)
  parent = ifs_getDir(parent)
  raise "Parent directory must exist: #{p}" if parent.nil?
  parent.mkdir(name)
end

#fs_dirRmdir(p) ⇒ Object

Remove a directory.



107
108
109
110
# File 'lib/fs/MiqFS/modules/Fat32.rb', line 107

def fs_dirRmdir(p)
  raise "Directory [#{p}] is not empty" if ifs_getDir(p).globNames.size > 2
  fs_fileDelete(p)
end

#fs_fileAtime(p) ⇒ Object

Returns Ruby Time object.



161
162
163
164
165
# File 'lib/fs/MiqFS/modules/Fat32.rb', line 161

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

#fs_fileAtime_obj(fobj) ⇒ Object

Returns a Ruby Time object.



188
189
190
# File 'lib/fs/MiqFS/modules/Fat32.rb', line 188

def fs_fileAtime_obj(fobj)
  fobj.de.aTime
end

#fs_fileClose(fobj) ⇒ Object

Write changes & destroy.



227
228
229
230
# File 'lib/fs/MiqFS/modules/Fat32.rb', line 227

def fs_fileClose(fobj)
  fobj.data.close
  fobj = nil
end

#fs_fileCtime(p) ⇒ Object

Returns Ruby Time object.



168
169
170
171
172
# File 'lib/fs/MiqFS/modules/Fat32.rb', line 168

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

#fs_fileCtime_obj(fobj) ⇒ Object

Returns a Ruby Time object.



193
194
195
# File 'lib/fs/MiqFS/modules/Fat32.rb', line 193

def fs_fileCtime_obj(fobj)
  fobj.de.cTime
end

#fs_fileDelete(p) ⇒ Object

Delete file.



154
155
156
157
158
# File 'lib/fs/MiqFS/modules/Fat32.rb', line 154

def fs_fileDelete(p)
  de = ifs_getFile(p)
  return if de.nil?
  de.delete(@boot_sector)
end

#fs_fileDirectory?(p) ⇒ Boolean

Returns true if name is a directory.

Returns:

  • (Boolean)


135
136
137
138
139
# File 'lib/fs/MiqFS/modules/Fat32.rb', line 135

def fs_fileDirectory?(p)
  de = ifs_getFile(p)
  return false if de.nil?
  de.isDir?
end

#fs_fileExists?(p) ⇒ Boolean

Returns true if name exists, false if not.

Returns:

  • (Boolean)


117
118
119
120
121
122
# File 'lib/fs/MiqFS/modules/Fat32.rb', line 117

def fs_fileExists?(p)
  return true if p == "/" || p == "\\"
  de = ifs_getFile(p)
  return false if de.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… (FAT32 supports .lnk files at this level, so this should also).

Returns:

  • (Boolean)


127
128
129
130
131
132
# File 'lib/fs/MiqFS/modules/Fat32.rb', line 127

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

#fs_fileMtime(p) ⇒ Object

Returns Ruby Time object.



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

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

#fs_fileMtime_obj(fobj) ⇒ Object

Returns a Ruby Time obect.



198
199
200
# File 'lib/fs/MiqFS/modules/Fat32.rb', line 198

def fs_fileMtime_obj(fobj)
  fobj.de.mTime
end

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

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



206
207
208
209
210
# File 'lib/fs/MiqFS/modules/Fat32.rb', line 206

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

#fs_fileRead(fobj, len) ⇒ Object

Returns a Ruby String object.



218
219
220
# File 'lib/fs/MiqFS/modules/Fat32.rb', line 218

def fs_fileRead(fobj, len)
  fobj.data.read(len)
end

#fs_fileSeek(fobj, offset, whence) ⇒ Object

Returns current file position.



213
214
215
# File 'lib/fs/MiqFS/modules/Fat32.rb', line 213

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

#fs_fileSize(p) ⇒ Object

Returns size in bytes.



147
148
149
150
151
# File 'lib/fs/MiqFS/modules/Fat32.rb', line 147

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

#fs_fileSize_obj(fobj) ⇒ Object

In these, fobj is a FileObject.



183
184
185
# File 'lib/fs/MiqFS/modules/Fat32.rb', line 183

def fs_fileSize_obj(fobj)
  fobj.de.length
end

#fs_fileWrite(fobj, buf, len) ⇒ Object



222
223
224
# File 'lib/fs/MiqFS/modules/Fat32.rb', line 222

def fs_fileWrite(fobj, buf, len)
  fobj.data.write(buf, len)
end

#fs_freeBytesObject

Returns free space on file system in bytes.



80
81
82
# File 'lib/fs/MiqFS/modules/Fat32.rb', line 80

def fs_freeBytes
  @boot_sector.freeClusters * @boot_sector.bytesPerCluster
end

#fs_initObject

File system interface.



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/fs/MiqFS/modules/Fat32.rb', line 56

def fs_init
  # puts "Fat32::fs_init(#{@dobj.dInfo.fileName})"
  self.fsType = "FAT32"

  # Initialize bs & read root dir.
  @dobj.seek(0, IO::SEEK_SET)
  self.boot_sector = BootSect.new(@dobj)
  self.drive_root = Directory.new(@boot_sector)
  self.dir_cache = LruHash.new(DEF_CACHE_SIZE)
  self.cache_hits = 0

  # Volume info: Note that fsId is a long in this case (not a UUID)
  # and volName could be modified by a file in the root with the label attrib set.
  self.fsId = boot_sector.fsId
  self.volName = boot_sector.volName

  # Spoof root dir ent - Fat32 has no "." in root.
  self.rootDirEnt = DirectoryEntry.new
  rootDirEnt.setAttribute(DirectoryEntry::FA_DIRECTORY)
  rootDirEnt.firstCluster = 0
  rootDirEnt.zeroTime
end

#fs_isSymLink?(_p) ⇒ Boolean

FAT file systems don’t do symbolic links.

Returns:

  • (Boolean)


142
143
144
# File 'lib/fs/MiqFS/modules/Fat32.rb', line 142

def fs_isSymLink?(_p)
  false
end

#ifs_getDir(p, miqfs = nil) ⇒ Object

Return a Directory object for a path. Raise error if path doesn’t exist.



279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
# File 'lib/fs/MiqFS/modules/Fat32.rb', line 279

def ifs_getDir(p, miqfs = nil)
  # 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)

  # Check for this path in the cache.
  if miqfs.dir_cache.key?(p)
    miqfs.cache_hits += 1
    return Directory.new(miqfs.boot_sector, miqfs.dir_cache[p])
  end

  # Return root if lone separator.
  return Directory.new(miqfs.boot_sector) if p == "/" || p == "\\"

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

  # Find first cluster of target dir.
  cluster = miqfs.boot_sector.rootCluster
  loop do
    break if names.empty?
    dir = Directory.new(miqfs.boot_sector, cluster)
    de = dir.findEntry(names.shift, Directory::FE_DIR)
    raise "Can't find directory: \'#{p}\'" if de.nil?
    cluster = de.firstCluster
  end

  # Save cluster in the cache & return a Directory.
  miqfs.dir_cache[p] = cluster
  Directory.new(miqfs.boot_sector, cluster)
end

#ifs_getFile(p, miqfs = nil) ⇒ Object

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



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/fs/MiqFS/modules/Fat32.rb', line 235

def ifs_getFile(p, miqfs = nil)
  # If this is being called from a FileObject instance, then MiqFS owns contained instance members.
  # If this is being called from an Fat32 module method, then self owns contained instance members.
  miqfs = self if miqfs.nil?

  # If root dir return spoof dir ent.
  return rootDirEnt if p == "/" || p == "\\"

  # Preprocess path.
  p = unnormalizePath(p)
  dir, fil = File.split(p)

  # Look for file in dir, but don't barf if it doesn't exist.
  # NOTE: if p is a directory that's ok, find it.
  begin
    dirObj = ifs_getDir(dir, miqfs)
    return nil if dirObj.nil?
    # Do NOT explicitly pass FE_FILE.
    dirEnt = dirObj.findEntry(fil)
    return nil if dirEnt.nil?
  rescue RuntimeError
    return nil
  end
  dirEnt
end

#ifs_putFile(p, miqfs = nil) ⇒ Object

Create a directory entry.



262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/fs/MiqFS/modules/Fat32.rb', line 262

def ifs_putFile(p, miqfs = nil)
  # If this is being called from a FileObject instance, then MiqFS owns contained instance members.
  # If this is being called from an Fat32 module method, then self owns contained instance members.
  miqfs = self if miqfs.nil?

  # Preprocess path.
  p = unnormalizePath(p)
  dir, fil = File.split(p)

  # Parent directory must exist.
  dir = ifs_getDir(dir, miqfs)
  return nil if dir.nil?
  dir.createFile(fil)
end

#unnormalizePath(p) ⇒ Object

Wack leading drive leter & colon.



315
316
317
# File 'lib/fs/MiqFS/modules/Fat32.rb', line 315

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