slowfat 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6abcf48f837234179e5df0d15ad3897130d20113
4
+ data.tar.gz: abd92e3c770f993e954555310073319b587fd314
5
+ SHA512:
6
+ metadata.gz: 2aac85c59372730819e52971a32ddb4965e0446c17b24714e1e9abc305c78224848dee6ac49b6ef93f0bdc867279ef4399a5e38f3f5cfc38c0dc625ad5ee921e
7
+ data.tar.gz: e912068bef784900cbffdc7a760165646e66a39125f37b592a0f03ec8df09935af228dc4ce69a64f283462f627fb2152c16aedfa31050edade8babe4c26e12d5
data/README.md ADDED
@@ -0,0 +1,2 @@
1
+ # ruby-slowfat
2
+ FAT library for Ruby, designed to perform minimum I/O for use on slow links such as serial connections.
data/Rakefile ADDED
@@ -0,0 +1,38 @@
1
+ require 'bundler'
2
+ Bundler.setup
3
+
4
+ begin
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new(:spec)
7
+ rescue LoadError
8
+ end
9
+
10
+ $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
11
+ require "slowfat/version"
12
+ require "yard"
13
+
14
+ YARD::Rake::YardocTask.new do |t|
15
+ t.files = ['lib/**/*.rb']
16
+ t.options = %w(--markup-provider=redcarpet --markup=markdown --main=README.md)
17
+ end
18
+
19
+ desc "Builds the gem"
20
+ task :gem => :build
21
+ task :build => :yard do
22
+ system "gem build slowfat.gemspec"
23
+ Dir.mkdir("pkg") unless Dir.exists?("pkg")
24
+ system "mv slowfat-#{SlowFat::VERSION}.gem pkg/"
25
+ end
26
+
27
+ task :install => :build do
28
+ system "sudo gem install pkg/slowfat-#{SlowFat::VERSION}.gem"
29
+ end
30
+
31
+ desc "Release the gem - Gemcutter"
32
+ task :release => :build do
33
+ system "git tag -a v#{SlowFat::VERSION} -m 'Tagging #{SlowFat::VERSION}'"
34
+ system "git push --tags"
35
+ system "gem push pkg/slowfat-#{SlowFat::VERSION}.gem"
36
+ end
37
+
38
+ task :default => [:spec]
data/lib/slowfat.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'slowfat/filesystem.rb'
2
+ require 'slowfat/bootsect.rb'
3
+ require 'slowfat/file_alloc_table.rb'
4
+ require 'slowfat/dir.rb'
5
+ require 'slowfat/data.rb'
6
+ require 'slowfat/fat_file.rb'
@@ -0,0 +1,52 @@
1
+ module SlowFat
2
+ class BootSector
3
+ attr_reader :oem_name
4
+
5
+ class DataError < StandardError
6
+ end
7
+
8
+ def initialize(data)
9
+ (@jmp, @oem_name, @bpb, @ebpb, @boot_code, @boot_signature) = data.unpack('a3a8a25a26a448v')
10
+ raise DataError, "Invalid boot signature in boot sector." if(@boot_signature != 0xAA55)
11
+ end
12
+
13
+ def bios_parameter_block
14
+ BiosParameterBlock.new @bpb
15
+ end
16
+
17
+ def extended_bios_parameter_block
18
+ ExtendedBiosParameterBlock.new @ebpb
19
+ end
20
+ end
21
+
22
+ class BiosParameterBlock
23
+ 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
+
25
+ def initialize(data)
26
+ (@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
+ end
28
+
29
+ def media_descriptor_type
30
+ case @media_descriptor_id
31
+ when 0xE5
32
+ :floppy_8inch
33
+ when 0xF0
34
+ :floppy_35inch_hd
35
+ when 0xF8
36
+ :fixed_disk
37
+ when 0xFD
38
+ :floppy_525inch_ld
39
+ else
40
+ :unknown
41
+ end
42
+ end
43
+ end
44
+
45
+ class ExtendedBiosParameterBlock
46
+ attr_reader :physical_drive_nbr, :boot_signature, :volume_serial, :volume_label, :fs_type
47
+
48
+ def initialize(data)
49
+ (@physical_drive_nbr, @flags, @boot_signature, @volume_serial, @volume_label, @fs_type) = data.unpack('CCCVa11a8')
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,30 @@
1
+ module SlowFat
2
+ class Data
3
+ attr_reader :base, :size
4
+
5
+ def initialize(backing:, base:, cluster_size:)
6
+ @backing = backing
7
+ @base = base
8
+ @cluster_size = cluster_size
9
+ end
10
+
11
+ def cluster_contents(cluster:, size:)
12
+ @backing.seek @base + @cluster_size * cluster
13
+ @backing.read size
14
+ end
15
+
16
+ def cluster_chain_contents(chain:, size:)
17
+ data = ""
18
+ chain.each_index do |idx|
19
+ cluster = chain[idx]
20
+ # read the whole cluster by default
21
+ read_size = @cluster_size
22
+ # on the last cluster of the file, read only as many
23
+ # bytes as we need to get the rest of the file, skip the padding
24
+ read_size = size % @cluster_size if idx == chain.length-1
25
+ data += cluster_contents(cluster: cluster, size: read_size)
26
+ end
27
+ data
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,103 @@
1
+ module SlowFat
2
+ class Directory
3
+ attr_reader :entries
4
+
5
+ def initialize(backing:, base:, max_entries:)
6
+ @backing = backing
7
+ @entries = []
8
+ (0..max_entries-1).each do |idx|
9
+ @backing.seek base+idx*32
10
+ dir_data = @backing.read(32)
11
+ entry = Dentry.new(dir_data)
12
+ break if entry.type == :end_of_dentries
13
+ @entries << entry
14
+ end
15
+ end
16
+
17
+ def file_entry(filename)
18
+ @entries.each do |dentry|
19
+ full_dentry_filename = dentry.extension.length > 0 ? "#{dentry.filename}.#{dentry.extension}".downcase : dentry.filename.downcase
20
+ return dentry if dentry.type == :file and filename.downcase == full_dentry_filename
21
+ end
22
+
23
+ nil
24
+ end
25
+
26
+ def dir_entry(filename)
27
+ @entries.each do |dentry|
28
+ full_dentry_filename = dentry.extension.length > 0 ? "#{dentry.filename}.#{dentry.extension}".downcase : dentry.filename.downcase
29
+ return dentry if dentry.type == :directory and filename.downcase == full_dentry_filename
30
+ end
31
+
32
+ nil
33
+ end
34
+
35
+ class Dentry
36
+ attr_reader :filename, :extension, :start_cluster, :size, :type
37
+
38
+ def initialize(dir_data)
39
+ case dir_data[0].ord
40
+ when 0x00
41
+ # end of directory entries
42
+ @type = :end_of_dentries
43
+ return
44
+ when 0x2E
45
+ # dot entry
46
+ @dotdir = true
47
+ when 0xE5
48
+ # deleted file
49
+ @deleted = true
50
+ end
51
+ (@filename, @extension, attrib_bits, reserved, mod_date, mod_time, @start_cluster, @size) = dir_data.unpack('A8A3Ca10vvvV')
52
+
53
+ @read_only = attrib_bits & 0x01 > 0
54
+ @hidden = attrib_bits & 0x02 > 0
55
+ @system = attrib_bits & 0x04 > 0
56
+ @archive = attrib_bits & 0x20 > 0
57
+ @device = attrib_bits & 0x40 > 0
58
+
59
+ @type = :file
60
+ @type = :volume_label if attrib_bits & 0x08 > 0
61
+ @type = :directory if attrib_bits & 0x10 > 0
62
+ end
63
+
64
+ def read_only?
65
+ @hidden
66
+ end
67
+
68
+ def hidden?
69
+ @hidden
70
+ end
71
+
72
+ def system?
73
+ @system
74
+ end
75
+
76
+ def archive?
77
+ @archive
78
+ end
79
+
80
+ def device?
81
+ @device
82
+ end
83
+
84
+ def deleted?
85
+ @deleted
86
+ end
87
+
88
+ def dotdir?
89
+ @dotdir
90
+ end
91
+ 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
+ end
103
+ end
@@ -0,0 +1,13 @@
1
+ module SlowFat
2
+ class FatFile
3
+ def initialize(filesystem:, dentry:)
4
+ @filesystem = filesystem
5
+ @dentry = dentry
6
+ @data = Data.new(backing: filesystem.backing, base: filesystem.data_base, cluster_size: filesystem.cluster_size)
7
+ end
8
+
9
+ def contents
10
+ @data.cluster_chain_contents chain: @filesystem.fats[0].chain_starting_at(@dentry.start_cluster), size: @dentry.size
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,38 @@
1
+ module SlowFat
2
+ class FileAllocationTable
3
+ attr_reader :chains, :base, :size
4
+
5
+ def initialize(backing:, base:, size:)
6
+ @backing = backing
7
+ @base = base
8
+ @size = size
9
+ end
10
+
11
+ def chain_starting_at(start_cluster)
12
+ current_cluster = start_cluster
13
+ current_chain = []
14
+ while current_cluster < @size/2
15
+ @backing.seek base + current_cluster * 2
16
+ (next_cluster, junk) = @backing.read(2).unpack('v')
17
+ if(current_cluster+1 == size/2) then
18
+ #printf("current: 0x%02X next: FAT END\n", current_cluster)
19
+ current_chain << current_cluster
20
+ return current_chain == [] ? [start_cluster] : current_chain
21
+ elsif(next_cluster >= 0xFFF8 and next_cluster <= 0xFFFF) then
22
+ # end of cluster marker
23
+ #printf("current: 0x%02X next: EOC (0x%02X)\n", current_cluster, current_cluster+1)
24
+ current_chain << current_cluster
25
+ return current_chain == [] ? [start_cluster] : current_chain
26
+ elsif(next_cluster == 0x0000) then
27
+ #printf("current: 0x%02X next: FREE (0x%02X)\n", current_cluster, current_cluster+1)
28
+ current_cluster = current_cluster + 1
29
+ else
30
+ # link to next cluster
31
+ #printf("current: 0x%02X next: 0x%02X\n", current_cluster, next_cluster)
32
+ current_chain << current_cluster
33
+ current_cluster = next_cluster
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,63 @@
1
+ module SlowFat
2
+ class Filesystem
3
+ attr_reader :backing, :rootdir_base, :rootdir, :data_base, :fats, :cluster_size
4
+
5
+ def initialize(backing:)
6
+ # TODO: this needs to be more flexible
7
+ @base = 0x0000
8
+ @backing = backing
9
+
10
+ fat_base = @base + 512
11
+ fat_size = bios_parameter_block.bytes_per_logsect * bios_parameter_block.logsects_per_fat
12
+ # TODO: shouldn't assume there's always two FATs
13
+ @fats = []
14
+ @fats[0] = FileAllocationTable.new(backing: backing, base: fat_base, size: fat_size)
15
+ @fats[1] = FileAllocationTable.new(backing: backing, base: fat_base + fat_size, size: fat_size)
16
+
17
+ # TODO: maybe shouldn't load the whole rootdir by default, I/O-wise?
18
+ @rootdir_base = fat_base + fat_size*2
19
+ @rootdir = Directory.new(backing: backing, base: rootdir_base, max_entries: bios_parameter_block.root_entries)
20
+ @cluster_size = bios_parameter_block.bytes_per_logsect * bios_parameter_block.logsects_per_cluster
21
+ @data_base = rootdir_base + bios_parameter_block.root_entries * 32 - @cluster_size*2
22
+ end
23
+
24
+ def bootsect
25
+ @backing.seek @base
26
+ BootSector.new @backing.read(512)
27
+ end
28
+
29
+ def bios_parameter_block
30
+ bootsect.bios_parameter_block
31
+ end
32
+
33
+ def extended_bios_parameter_block
34
+ bootsect.extended_bios_parameter_block
35
+ end
36
+
37
+ def dir(dirname)
38
+ path_elements = dirname.split('/')
39
+ current_dir_base = @rootdir_base
40
+ next_dir = nil
41
+ max_entries = 32
42
+ path_elements.each do |path_element|
43
+ this_dir = Directory.new(backing: @backing, base: current_dir_base, max_entries: max_entries)
44
+ next_dir = this_dir.dir_entry(path_element)
45
+ return nil if next_dir == nil
46
+ next_dir_base = @data_base + next_dir.start_cluster*@cluster_size
47
+ # all directories except the root have more entries available
48
+ max_entries = @cluster_size/32
49
+ current_dir_base = next_dir_base
50
+ end
51
+
52
+ Directory.new(backing: @backing, base: current_dir_base, max_entries: max_entries)
53
+ end
54
+
55
+ def file(path)
56
+ path_components = path.split('/')
57
+ dir_components = path_components[0..-2]
58
+ filename = path_components[-1]
59
+ dentry = dir(dir_components.join('/')).file_entry(filename)
60
+ FatFile.new filesystem: self, dentry: dentry
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,4 @@
1
+ module SlowFat
2
+ # The version number of the SlowFat module.
3
+ VERSION = '0.0.1'
4
+ end
metadata ADDED
@@ -0,0 +1,53 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: slowfat
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - sarahemm
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-04-17 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: FAT filesystem library for use with slow I/O (e.g. serial links)
14
+ email: github@sen.cx
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - README.md
20
+ - Rakefile
21
+ - lib/slowfat.rb
22
+ - lib/slowfat/bootsect.rb
23
+ - lib/slowfat/data.rb
24
+ - lib/slowfat/dir.rb
25
+ - lib/slowfat/fat_file.rb
26
+ - lib/slowfat/file_alloc_table.rb
27
+ - lib/slowfat/filesystem.rb
28
+ - lib/slowfat/version.rb
29
+ homepage: http://github.com/sarahemm/ruby-slowfat
30
+ licenses:
31
+ - MIT
32
+ metadata: {}
33
+ post_install_message:
34
+ rdoc_options: []
35
+ require_paths:
36
+ - lib
37
+ required_ruby_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ required_rubygems_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ requirements: []
48
+ rubyforge_project:
49
+ rubygems_version: 2.5.2.3
50
+ signing_key:
51
+ specification_version: 4
52
+ summary: Slow I/O FAT interface library
53
+ test_files: []