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 +4 -4
- data/README.md +27 -2
- data/lib/slowfat/bootsect.rb +21 -0
- data/lib/slowfat/data.rb +16 -2
- data/lib/slowfat/dir.rb +63 -13
- data/lib/slowfat/fat_file.rb +9 -0
- data/lib/slowfat/file_alloc_table.rb +11 -0
- data/lib/slowfat/filesystem.rb +40 -4
- data/lib/slowfat/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 91f614012072f497abd7a42928629b09e82b215e
|
4
|
+
data.tar.gz: 99530e503ba9d5769c3bdc4c4bf2e3548c39dc5b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d94d90f02158398a6069e8f6b27ddf8785834d861d132e0f5d9cd205a10b4fb98bcca51a99a7279528aed8ff2cc6884f35d01ede389a5e7c67ee5cba9acec19f
|
7
|
+
data.tar.gz: e48cb89463beb0506277e9d158b8038d345cda5ffb375da49b26f3cfef5522288434a1f4b061c9799fda507b63c8c07f064fcc790533fa4326dab0af749c94ae
|
data/README.md
CHANGED
@@ -1,2 +1,27 @@
|
|
1
|
-
# ruby-slowfat
|
2
|
-
|
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)
|
data/lib/slowfat/bootsect.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
36
|
-
|
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
|
-
@
|
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
|
data/lib/slowfat/fat_file.rb
CHANGED
@@ -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 = []
|
data/lib/slowfat/filesystem.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
6
|
-
|
7
|
-
|
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]
|
data/lib/slowfat/version.rb
CHANGED
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.
|
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-
|
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
|