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 +7 -0
- data/README.md +2 -0
- data/Rakefile +38 -0
- data/lib/slowfat.rb +6 -0
- data/lib/slowfat/bootsect.rb +52 -0
- data/lib/slowfat/data.rb +30 -0
- data/lib/slowfat/dir.rb +103 -0
- data/lib/slowfat/fat_file.rb +13 -0
- data/lib/slowfat/file_alloc_table.rb +38 -0
- data/lib/slowfat/filesystem.rb +63 -0
- data/lib/slowfat/version.rb +4 -0
- metadata +53 -0
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
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,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
|
data/lib/slowfat/data.rb
ADDED
@@ -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
|
data/lib/slowfat/dir.rb
ADDED
@@ -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
|
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: []
|