slowfat 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []