slowfat 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6abcf48f837234179e5df0d15ad3897130d20113
4
- data.tar.gz: abd92e3c770f993e954555310073319b587fd314
3
+ metadata.gz: 91f614012072f497abd7a42928629b09e82b215e
4
+ data.tar.gz: 99530e503ba9d5769c3bdc4c4bf2e3548c39dc5b
5
5
  SHA512:
6
- metadata.gz: 2aac85c59372730819e52971a32ddb4965e0446c17b24714e1e9abc305c78224848dee6ac49b6ef93f0bdc867279ef4399a5e38f3f5cfc38c0dc625ad5ee921e
7
- data.tar.gz: e912068bef784900cbffdc7a760165646e66a39125f37b592a0f03ec8df09935af228dc4ce69a64f283462f627fb2152c16aedfa31050edade8babe4c26e12d5
6
+ metadata.gz: d94d90f02158398a6069e8f6b27ddf8785834d861d132e0f5d9cd205a10b4fb98bcca51a99a7279528aed8ff2cc6884f35d01ede389a5e7c67ee5cba9acec19f
7
+ data.tar.gz: e48cb89463beb0506277e9d158b8038d345cda5ffb375da49b26f3cfef5522288434a1f4b061c9799fda507b63c8c07f064fcc790533fa4326dab0af749c94ae
data/README.md CHANGED
@@ -1,2 +1,27 @@
1
- # ruby-slowfat
2
- FAT library for Ruby, designed to perform minimum I/O for use on slow links such as serial connections.
1
+ # ruby-slowfat - FAT library for Ruby designed for slow links/media
2
+
3
+ ## Description
4
+
5
+ Ruby class/gem to access FAT filesystems (currently only FAT16) over slow links or media, such as serial connections. Prioritizes minimum I/O whenever possible, and flexible backing (files, sockets, etc.).
6
+
7
+ ## Features
8
+ * FAT16 support
9
+ * Read and format directory listings
10
+ * Read contents of files
11
+
12
+ ## TODO
13
+ * FAT12 support
14
+ * Write support
15
+ * Optimize a couple places where we might do more I/O than required
16
+
17
+ ## Use
18
+ require 'slowfat'
19
+
20
+ img = File.open("test_fat16.img", 'r')
21
+ fs = Fat::Filesystem.new(backing: img, base: 0x7E00)
22
+ print fs.dir("WINDOWS").to_s
23
+ print fs.file("WINDOWS/SETUP.TXT").contents
24
+
25
+
26
+ ## Full Documentation
27
+ YARD docs included, also available on [RubyDoc.info](https://www.rubydoc.info/github/sarahemm/ruby-slowfat/master)
@@ -1,24 +1,40 @@
1
1
  module SlowFat
2
+ ##
3
+ # BootSector handles accessing information inside the boot sector, such as BPB, signature, and boot code.
2
4
  class BootSector
5
+ # @return [String] the OEM Name contained in this boot sector.
3
6
  attr_reader :oem_name
4
7
 
8
+ ##
9
+ # DataError is raised if unexpected data is encountered when handling the filesystem.
5
10
  class DataError < StandardError
6
11
  end
7
12
 
13
+ ##
14
+ # Initialize a new BootSector object (normally only called from Filesystem)
15
+ # @param data [String] raw data making up this boot sector
8
16
  def initialize(data)
9
17
  (@jmp, @oem_name, @bpb, @ebpb, @boot_code, @boot_signature) = data.unpack('a3a8a25a26a448v')
10
18
  raise DataError, "Invalid boot signature in boot sector." if(@boot_signature != 0xAA55)
11
19
  end
12
20
 
21
+ ##
22
+ # Parse out and return a BIOS Parameter Block from this boot sector
23
+ # @return [BiosParameterBlock] the BPB parsed out of this boot sector
13
24
  def bios_parameter_block
14
25
  BiosParameterBlock.new @bpb
15
26
  end
16
27
 
28
+ ##
29
+ # Parse out and return an Extended BIOS Parameter Block from this boot sector
30
+ # @return [ExtendedBiosParameterBlock] the EBPB parsed out of this boot sector
17
31
  def extended_bios_parameter_block
18
32
  ExtendedBiosParameterBlock.new @ebpb
19
33
  end
20
34
  end
21
35
 
36
+ ##
37
+ # BiosParameterBlock holds information from a BPB.
22
38
  class BiosParameterBlock
23
39
  attr_reader :bytes_per_logsect, :logsects_per_cluster, :reserved_logsects, :fats, :root_entries, :logsects, :media_descriptor, :logsects_per_fat, :physects_per_track, :heads, :hidden_logsects, :large_total_logsects
24
40
 
@@ -26,6 +42,9 @@ module SlowFat
26
42
  (@bytes_per_logsect, @logsects_per_cluster, @reserved_logsects, @fats, @root_entries, @total_logsects, @media_descriptor, @logsects_per_fat, @physects_per_track, @heads, @hidden_logsects, @large_total_logsects) = data.unpack('vCvCvvCvvvVV')
27
43
  end
28
44
 
45
+ ##
46
+ # Return the type of media this boot sector is on
47
+ # @return [Symbol] the type of media described by the media descriptor in the boot sector
29
48
  def media_descriptor_type
30
49
  case @media_descriptor_id
31
50
  when 0xE5
@@ -42,6 +61,8 @@ module SlowFat
42
61
  end
43
62
  end
44
63
 
64
+ ##
65
+ # ExtendedBiosParameterBlock holds information from an EBPB.
45
66
  class ExtendedBiosParameterBlock
46
67
  attr_reader :physical_drive_nbr, :boot_signature, :volume_serial, :volume_label, :fs_type
47
68
 
data/lib/slowfat/data.rb CHANGED
@@ -1,18 +1,32 @@
1
1
  module SlowFat
2
+ ##
3
+ # Data is used to access the actual contents of FAT clusters.
2
4
  class Data
3
- attr_reader :base, :size
4
-
5
+ ##
6
+ # Initialize a new Data object (normally only called from FatFile)
7
+ # @param backing [IO] the storage containing the filesystem (e.g. open file)
8
+ # @param base [Integer] the start of the data area within the backing
9
+ # @param cluster_size [Integer] the size of each cluster in the filesystem
5
10
  def initialize(backing:, base:, cluster_size:)
6
11
  @backing = backing
7
12
  @base = base
8
13
  @cluster_size = cluster_size
9
14
  end
10
15
 
16
+ ##
17
+ # Return the contents of a given cluster.
18
+ # @param cluster [Integer] the cluster number to obtain data from
19
+ # @param size [Integer] the size of data to obtain
20
+ # @return [String] the data obtained from the given cluster
11
21
  def cluster_contents(cluster:, size:)
12
22
  @backing.seek @base + @cluster_size * cluster
13
23
  @backing.read size
14
24
  end
15
25
 
26
+ ##
27
+ # Return the contents of all clusters in given chain
28
+ # @param chain [Integer] the chain of clusters to obtain data from
29
+ # @param size [Integer] the total size of data to obtain
16
30
  def cluster_chain_contents(chain:, size:)
17
31
  data = ""
18
32
  chain.each_index do |idx|
data/lib/slowfat/dir.rb CHANGED
@@ -1,7 +1,15 @@
1
1
  module SlowFat
2
+ ##
3
+ # Directory represents one directory on a FAT filesystem.
2
4
  class Directory
5
+ # @return [Array<Dentry>] an array of directory entries within this directory
3
6
  attr_reader :entries
4
7
 
8
+ ##
9
+ # Initialize a new Directory object (normally only called from Filesystem.dir)
10
+ # @param backing [IO] the storage containing the filesystem (e.g. open file)
11
+ # @param base [Integer] the location of the beginning of this directory structure within the backing device
12
+ # @param max_entries [Integer] the maximum number of entries that can be in this directory
5
13
  def initialize(backing:, base:, max_entries:)
6
14
  @backing = backing
7
15
  @entries = []
@@ -14,6 +22,10 @@ module SlowFat
14
22
  end
15
23
  end
16
24
 
25
+ ##
26
+ # Return a directory entry for a specific file within this directory
27
+ # @param filename [String] the name of the file to access
28
+ # @return [Dentry] the directory entry object matching the requested file
17
29
  def file_entry(filename)
18
30
  @entries.each do |dentry|
19
31
  full_dentry_filename = dentry.extension.length > 0 ? "#{dentry.filename}.#{dentry.extension}".downcase : dentry.filename.downcase
@@ -23,6 +35,10 @@ module SlowFat
23
35
  nil
24
36
  end
25
37
 
38
+ ##
39
+ # Return a directory entry for a specific subdirectory within this directory
40
+ # @param filename [String] the name of the directory to access
41
+ # @return [Dentry] the directory entry object matching the requested subdirectory
26
42
  def dir_entry(filename)
27
43
  @entries.each do |dentry|
28
44
  full_dentry_filename = dentry.extension.length > 0 ? "#{dentry.filename}.#{dentry.extension}".downcase : dentry.filename.downcase
@@ -32,9 +48,39 @@ module SlowFat
32
48
  nil
33
49
  end
34
50
 
35
- class Dentry
36
- attr_reader :filename, :extension, :start_cluster, :size, :type
51
+ ##
52
+ # Convert a Directory into a vaguely DOS-style file listing
53
+ # @return [String] the contents of this directory, formatted as a human-readable listing
54
+ def to_s
55
+ buf = ""
56
+ @entries.each do |dentry|
57
+ if(dentry.type == :file) then
58
+ buf += sprintf("%-8s %-3s %d\n", dentry.filename, dentry.extension, dentry.size)
59
+ elsif(dentry.type == :directory)
60
+ buf += sprintf("%-8s %-3s <DIR>\n", dentry.filename, dentry.extension)
61
+ end
62
+ end
63
+
64
+ buf
65
+ end
37
66
 
67
+ ##
68
+ # Dentry represents one entry inside a Directory.
69
+ class Dentry
70
+ # @return [String] the name of the file or other directory entry
71
+ attr_reader :filename
72
+ # @return [String] the file extension
73
+ attr_reader :extension
74
+ # @return [Integer] the starting cluster of this file in the backing
75
+ attr_reader :start_cluster
76
+ # @return [Integer] the size of the file in bytes
77
+ attr_reader :size
78
+ # @return [Symbol] the type of item this dentry describes
79
+ attr_reader :type
80
+
81
+ ##
82
+ # Initialize a new directory entry (normally only called from Directory)
83
+ # @param dir_data [String] the data making up this dentry in the backing
38
84
  def initialize(dir_data)
39
85
  case dir_data[0].ord
40
86
  when 0x00
@@ -61,43 +107,47 @@ module SlowFat
61
107
  @type = :directory if attrib_bits & 0x10 > 0
62
108
  end
63
109
 
110
+ ##
111
+ # Returns true if this dentry has the read only attribute set
64
112
  def read_only?
65
- @hidden
113
+ @read_only
66
114
  end
67
115
 
116
+ ##
117
+ # Returns true if this dentry has the hidden attribute set
68
118
  def hidden?
69
119
  @hidden
70
120
  end
71
121
 
122
+ ##
123
+ # Returns true if this dentry has the system attribute set
72
124
  def system?
73
125
  @system
74
126
  end
75
127
 
128
+ ##
129
+ # Returns true if this dentry has the archive attribute set
76
130
  def archive?
77
131
  @archive
78
132
  end
79
133
 
134
+ ##
135
+ # Returns true if this dentry has the device attribute set
80
136
  def device?
81
137
  @device
82
138
  end
83
139
 
140
+ ##
141
+ # Returns true if this dentry is a file that has been deleted
84
142
  def deleted?
85
143
  @deleted
86
144
  end
87
145
 
146
+ ##
147
+ # Returns true if this dentry is a dot directory (. or ..)
88
148
  def dotdir?
89
149
  @dotdir
90
150
  end
91
151
  end
92
-
93
- def dump
94
- @entries.each do |dentry|
95
- if(dentry.type == :file) then
96
- printf("%-8s %-3s %d\n", dentry.filename, dentry.extension, dentry.size)
97
- elsif(dentry.type == :directory)
98
- printf("%-8s %-3s <DIR>\n", dentry.filename, dentry.extension)
99
- end
100
- end
101
- end
102
152
  end
103
153
  end
@@ -1,11 +1,20 @@
1
1
  module SlowFat
2
+ ##
3
+ # FatFile represents a single file on a FAT filesystem
2
4
  class FatFile
5
+ ##
6
+ # Initialize a new Directory object (normally only called from Directory's functions)
7
+ # @param filesystem [Filesystem] the filesystem containing this file
8
+ # @param dentry [Dentry] the directory entry pointing to this file
3
9
  def initialize(filesystem:, dentry:)
4
10
  @filesystem = filesystem
5
11
  @dentry = dentry
6
12
  @data = Data.new(backing: filesystem.backing, base: filesystem.data_base, cluster_size: filesystem.cluster_size)
7
13
  end
8
14
 
15
+ ##
16
+ # Return the entire contents of a file
17
+ # @return [String] the contents of this file
9
18
  def contents
10
19
  @data.cluster_chain_contents chain: @filesystem.fats[0].chain_starting_at(@dentry.start_cluster), size: @dentry.size
11
20
  end
@@ -1,13 +1,24 @@
1
1
  module SlowFat
2
+ ##
3
+ # FileAllocationTable represents one FAT (of two on a disk, most commonly)
2
4
  class FileAllocationTable
3
5
  attr_reader :chains, :base, :size
4
6
 
7
+ ##
8
+ # Initialize a new FileAllocatinTable object (normally only called from Filesystem)
9
+ # @param backing [IO] the storage containing the filesystem (e.g. open file)
10
+ # @param base [Integer] the location of the beginning of this FAT structure within the backing device
11
+ # @param size [Integer] the total size of this FAT
5
12
  def initialize(backing:, base:, size:)
6
13
  @backing = backing
7
14
  @base = base
8
15
  @size = size
9
16
  end
10
17
 
18
+ ##
19
+ # Retrieve a full chain of cluster numbers, starting at a given cluster
20
+ # @param start_cluster [Integer] the cluster number to retrieve a chain starting from
21
+ # @return [Array<Integer>] a list of clusters in the chain, starting from the cluster provided
11
22
  def chain_starting_at(start_cluster)
12
23
  current_cluster = start_cluster
13
24
  current_chain = []
@@ -1,11 +1,29 @@
1
+ ##
2
+ # The SlowFat module handles the entire filesystem access process.
1
3
  module SlowFat
4
+ ##
5
+ # Filesystem coordinates overall FAT filesystem access.
2
6
  class Filesystem
3
- attr_reader :backing, :rootdir_base, :rootdir, :data_base, :fats, :cluster_size
7
+ # @return [IO] the backing IO object for this filesystem
8
+ attr_reader :backing
9
+ # @return [Integer] the location where the root directory information starts within the backing
10
+ attr_reader :rootdir_base
11
+ # @return [Directory] the Directory object containing the root directory
12
+ attr_reader :rootdir
13
+ # @return [Integer] the location where the data starts within the backing
14
+ attr_reader :data_base
15
+ # @return [Array<FileAllocationTable>] array of file allocation tables
16
+ attr_reader :fats
17
+ # @return [Integer] the cluster size used on this filesystem
18
+ attr_reader :cluster_size
4
19
 
5
- def initialize(backing:)
6
- # TODO: this needs to be more flexible
7
- @base = 0x0000
20
+ ##
21
+ # Set up a new filesystem connection
22
+ # @param backing [IO] the storage containing the filesystem (e.g. open file)
23
+ # @param base [Integer] the start of the filesystem within the backing device
24
+ def initialize(backing:, base: 0x0000)
8
25
  @backing = backing
26
+ @base = base
9
27
 
10
28
  fat_base = @base + 512
11
29
  fat_size = bios_parameter_block.bytes_per_logsect * bios_parameter_block.logsects_per_fat
@@ -21,20 +39,34 @@ module SlowFat
21
39
  @data_base = rootdir_base + bios_parameter_block.root_entries * 32 - @cluster_size*2
22
40
  end
23
41
 
42
+ ##
43
+ # Access the boot sector of this filesystem.
44
+ # @return [BootSector] the boot sector on this filesystem
24
45
  def bootsect
25
46
  @backing.seek @base
26
47
  BootSector.new @backing.read(512)
27
48
  end
28
49
 
50
+ ##
51
+ # Access the BIOS Parameter Block of this filesystem.
52
+ # @return [BiosParameterBlock] the BPB on this filesystem
29
53
  def bios_parameter_block
30
54
  bootsect.bios_parameter_block
31
55
  end
32
56
 
57
+ ##
58
+ # Access the Extended BIOS Parameter Block of this filesystem.
59
+ # @return [ExtendedBiosParameterBlock] the EBPB on this filesystem
33
60
  def extended_bios_parameter_block
34
61
  bootsect.extended_bios_parameter_block
35
62
  end
36
63
 
64
+ ##
65
+ # Access a directory within this filesystem.
66
+ # @param dirname [String] the path to retrieve a directory object for
67
+ # @return [Directory] the directory object matching the requested path
37
68
  def dir(dirname)
69
+ # TODO: accept either / or \
38
70
  path_elements = dirname.split('/')
39
71
  current_dir_base = @rootdir_base
40
72
  next_dir = nil
@@ -52,6 +84,10 @@ module SlowFat
52
84
  Directory.new(backing: @backing, base: current_dir_base, max_entries: max_entries)
53
85
  end
54
86
 
87
+ ##
88
+ # Access a file within this filesystem
89
+ # @param path [String] the path to retrieve a file object for
90
+ # @return [FatFile] the file object matching the requested path
55
91
  def file(path)
56
92
  path_components = path.split('/')
57
93
  dir_components = path_components[0..-2]
@@ -1,4 +1,4 @@
1
1
  module SlowFat
2
2
  # The version number of the SlowFat module.
3
- VERSION = '0.0.1'
3
+ VERSION = '0.0.2'
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: slowfat
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - sarahemm
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-04-17 00:00:00.000000000 Z
11
+ date: 2021-04-18 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: FAT filesystem library for use with slow I/O (e.g. serial links)
14
14
  email: github@sen.cx