archiverb 0.9.0

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.
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2013 Caleb Crane
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a
4
+ copy of this software and associated documentation files (the "Software"),
5
+ to deal in the Software without restriction, including without limitation
6
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
7
+ and/or sell copies of the Software, and to permit persons to whom the
8
+ Software is furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included
11
+ in all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
15
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
16
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
17
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
18
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
19
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,102 @@
1
+ Archiverb provides Ruby bindings for creating
2
+ [tar](http://en.wikipedia.org/wiki/Tar_(computing) and
3
+ [ar](http://en.wikipedia.org/wiki/Ar_(Unix) archives in memory.
4
+
5
+ ## Install
6
+
7
+ ``gem install archiverb``
8
+
9
+ ## Use
10
+
11
+ ```ruby
12
+ require "archiverb/ar"
13
+ require "archiverb/tar"
14
+ ```
15
+
16
+ ## Adding files from the file system
17
+
18
+ ```ruby
19
+ archive = Archiverb::Ar.new(File.expand_path("../henryIV.ar", __FILE__))
20
+ archive.add(File.expand_path("../spec/data/heneryIV.txt", __FILE__))
21
+ archive.add(File.expand_path("../spec/data/heneryIV-westmoreland.txt", __FILE__))
22
+
23
+ # archive will be written to henryIV.ar
24
+ archive.write
25
+ ```
26
+
27
+ ## Read an archive from the file system
28
+
29
+ ```ruby
30
+ archive = Archiverb::Ar.new(File.expand_path("../henryIV.ar", __FILE__))
31
+ archive.read
32
+
33
+ archive.names # => ["heneryIV.txt", "heneryIV-westmoreland.txt"]
34
+ archive.files # => [#<Archiverb::File:0x007f8d7b90acf8 @name="heneryIV.txt" ... >, ...]
35
+ ```
36
+
37
+ ## Adding files from memory
38
+
39
+ ```ruby
40
+ archive = Archiverb::Ar.new(File.expand_path("../henryIV.ar", __FILE__))
41
+
42
+ contents = IO.read((File.expand_path("../spec/data/heneryIV.txt", __FILE__)))
43
+ archive.add("henryIV.txt", contents)
44
+
45
+ contents = IO.read((File.expand_path("../spec/data/heneryIV-westmoreland.txt", __FILE__)))
46
+ archive.add("henryIV-westmoreland.txt", contents)
47
+
48
+ archive.write
49
+ ```
50
+
51
+
52
+ ```ruby
53
+ archive = Archiverb::Tar.new(File.expand_path("../henryIV.tar", __FILE__))
54
+
55
+ archive.add("data/", :mode => 0744)
56
+
57
+ contents = IO.read((File.expand_path("../spec/data/heneryIV.txt", __FILE__)))
58
+ archive.add("data/henryIV.txt", contents)
59
+
60
+ contents = IO.read((File.expand_path("../spec/data/heneryIV-westmoreland.txt", __FILE__)))
61
+ archive.add("data/henryIV-westmoreland.txt", contents)
62
+
63
+ archive.write
64
+ ```
65
+
66
+ ## Working with Gzip Files
67
+
68
+ ### Writing to a Gzip file
69
+
70
+ To create a gzipped tar archive, populate a ``Archiverb::Tar`` object in
71
+ memory then create a ``GzipWriter`` object and pass it to
72
+ ``Archiverb::Tar#write``.
73
+
74
+ ```ruby
75
+ require "zlib"
76
+
77
+ path = File.expand_path("../henryIV.tar", __FILE__)
78
+
79
+ archive = Archiverb::Tar.new
80
+ archive.add("data/henryIV.txt",
81
+ IO.read((File.expand_path("../spec/data/heneryIV.txt", __FILE__))))
82
+ archive.add("data/henryIV-westmoreland.txt",
83
+ IO.read((File.expand_path("../spec/data/heneryIV-westmoreland.txt", __FILE__))))
84
+
85
+ Zlib::GzipWriter.open(path) do |gz|
86
+ archive.write(gz)
87
+ end
88
+ ```
89
+
90
+
91
+ ### Reading from a Gzip file
92
+
93
+ ```ruby
94
+ require "zlib"
95
+
96
+ File.open(File.expand_path("../henryIV.tgz", __FILE__)) do |f|
97
+ gz = Zlib::GzipReader.new(f)
98
+ archive = Archiverb::Tar.new(gz)
99
+ archive.read
100
+ archive.names # => ["data/henryIV.txt", "data/henryIV-westmoreland.txt"]
101
+ end
102
+ ```
@@ -0,0 +1,156 @@
1
+ require "archiverb/stat"
2
+ require "archiverb/file"
3
+
4
+ # Provides a common interface for working with different types of
5
+ # archive formats.
6
+ #
7
+ # @example
8
+ # arc = Archiverb::Ar.new("junk.ar", "a.txt", "b.txt", "c.txt")
9
+ # arc.write("/tmp/junk.ar") # => creates /tmp/junk.ar with {a,b,c}.txt
10
+ class Archiverb
11
+ module Error; end
12
+ class StandarError < ::StandardError; include Error; end
13
+ class ArgumentError < ::ArgumentError; include Error; end
14
+ class InvalidFormat < StandardError; end
15
+ class WrongChksum < StandardError; end
16
+ class AbstractMethod < StandardError; end
17
+
18
+ include Enumerable
19
+
20
+ def initialize(path_or_io = nil, *files, &blk)
21
+ raise NotImplementedError if self.class == Archiverb
22
+ @opts = files.last.is_a?(Hash) ? files.pop : {}
23
+ files.each { |file| add(file) }
24
+ if block_given?
25
+ r, w = IO.pipe
26
+ @source = lambda do
27
+ blk.call(w)
28
+ w.close unless w.closed?
29
+ r
30
+ end
31
+ else
32
+ @source = lambda { path_or_io.is_a?(String) ? ::File.new(path_or_io, "a+").tap{|f| f.rewind } : path_or_io }
33
+ end
34
+ @out = path_or_io
35
+ @files = {}
36
+ end
37
+
38
+ def path
39
+ @out
40
+ end
41
+
42
+ def files
43
+ @files.values
44
+ end
45
+
46
+ def [](file)
47
+ @files[file]
48
+ end
49
+
50
+ # Iterate over each file.
51
+ # @yield { |name, file| }
52
+ def each(&blk)
53
+ @files.each(&blk)
54
+ end
55
+
56
+ def names
57
+ @files.keys
58
+ end
59
+
60
+ def count
61
+ @files.keys.length
62
+ end
63
+
64
+ # Pulls each file out of the archive and places them in #files.
65
+ # @todo take a block and yield each file as it's read without storing it in #files
66
+ def read
67
+ return self if @source.nil?
68
+ io = @source.call
69
+ io.respond_to?(:binmode) && io.binmode
70
+ preprocess(io)
71
+ while (header = next_header(io))
72
+ @files[header[:name]] = File.new(header[:name], read_file(header, io), Stat.new(header))
73
+ end
74
+ io.close
75
+ self
76
+ end
77
+
78
+ # Add a file to the archive.
79
+ # @param [String, File, IO]
80
+ # @param [Hash] opts options to pass to Stat.new
81
+ # @param [IO, ::File, String, StringIO] io
82
+ def add(name, opts = {}, io = nil, &blk)
83
+ if block_given?
84
+ @files[name] = File.new(name, *IO.pipe, &blk)
85
+ return self
86
+ end
87
+
88
+ if io
89
+ opts, io = io, opts if io.is_a?(Hash)
90
+ elsif !opts.is_a?(Hash)
91
+ opts, io = {}, opts
92
+ end
93
+
94
+ if io
95
+ if io.is_a?(String) || io.is_a?(StringIO) || io.is_a?(IO) || io.is_a?(::File)
96
+ @files[name] = File.new(name, io, Stat.new(io, opts))
97
+ else
98
+ raise ArgumentError.new("unsupported data source: #{io.class}")
99
+ end
100
+ else
101
+ case name
102
+ when String
103
+ fio = ::File.exists?(name) ? ::File.new(name, "r") : ""
104
+ @files[name] = File.new(name, fio, Stat.new(fio, opts))
105
+ when ::File
106
+ @files[name.path] = File.new(name.path, name, Stat.new(name, opts))
107
+ else
108
+ opts[:name] = name.respond_to?(:path) ? name.path : name.__id__.to_s if opts[:name].nil?
109
+ @files[opts[:name]] = File.new(opts[:name], name, Stat.new(name, opts))
110
+ end
111
+ end
112
+
113
+ self
114
+ end
115
+
116
+ def write(path = @out, &blk)
117
+ if block_given?
118
+ # use a pipe instead?
119
+ yield StringIO.new.tap { |io| write_to(io) }.string
120
+ elsif path.is_a?(String)
121
+ ::File.open(path, "w") { |io| write_to(io) }
122
+ else
123
+ path.respond_to?(:truncate) && path.truncate
124
+ write_to(path)
125
+ end
126
+ self
127
+ end
128
+
129
+ private
130
+
131
+
132
+ # Abstract method
133
+ # Write all files in the archive, in the archive format, to the given IO
134
+ # @return [Hash] must have :name, :mtime, :uid, :gid, and mode
135
+ def write_io(io)
136
+ raise AbstractMethod
137
+ end
138
+
139
+ # Abstract method
140
+ # Get the next header for the next file
141
+ def next_header(io)
142
+ raise AbstractMethod
143
+ end
144
+
145
+ # Abstract method
146
+ # Perform any preprocessing and validation on the archive.
147
+ # Inheriting classes aren't requried to implement this method.
148
+ def preprocess(io)
149
+ end
150
+
151
+ # Abstract method
152
+ # Given a file header and an IO that is the archive retrieve the file.
153
+ def read_file(header, io)
154
+ raise AbstractMethod
155
+ end
156
+ end # class::Archiverb
@@ -0,0 +1,60 @@
1
+ require 'archiverb'
2
+
3
+ class Archiverb
4
+ class Ar < Archiverb
5
+
6
+ private
7
+
8
+ def write_to(io)
9
+ io.write("!<arch>\n")
10
+ @files.each do |_, file|
11
+ normal = [:name, :mtime, :uid, :gid, :mode].inject({}){|n,k| n.tap{ n[k] = file.send(k) } }
12
+ normal[:mtime] = normal[:mtime].to_i
13
+ normal[:raw] = file.read
14
+ if normal[:name].length > 16
15
+ normal[:name] = "#1/#{file.name.length + 3}"
16
+ normal[:raw] = "#{file.name}\0\0\0" + normal[:raw]
17
+ normal[:size] = file.name.length + 3
18
+ end
19
+ normal[:size] = normal[:raw].length
20
+ printf(io, "%-16s%-12u%-6d%-6d%-8o%-10u`\n", *[:name, :mtime, :uid, :gid, :mode, :size].map{|k| normal[k]})
21
+ io.write(normal[:raw])
22
+ io.write("\n") if io.pos % 2 == 1
23
+ end
24
+ io.close
25
+ self
26
+ end
27
+
28
+ def next_header(io)
29
+ return nil if io.eof?
30
+ io.read(1) if io.pos % 2 == 1
31
+ header = {}
32
+ header[:name] = io.read(16) || (return nil)
33
+ header[:name].strip!
34
+ header[:mtime] = io.read(12) || (return nil)
35
+ header[:uid] = io.read(6).to_i
36
+ header[:gid] = io.read(6).to_i
37
+ header[:mode] = io.read(8).to_i(8)
38
+ header[:size] = io.read(10).to_i
39
+ header[:magic] = io.read(2)
40
+ raise InvalidFormat unless header[:magic] == "`\n"
41
+ if header[:name][0..2] == "#1/"
42
+ # bsd format extended file name
43
+ header[:name] = io.read(header[:name][3..-1].to_i)
44
+ header[:size] -= header[:name].length
45
+ header[:name] = header[:name][0..-4]
46
+ # @todo support gnu format for extended file name
47
+ end
48
+ header
49
+ end # next_header(io)
50
+
51
+ def preprocess(io)
52
+ raise InvalidFormat unless io.read(8) == "!<arch>\n"
53
+ end
54
+
55
+ def read_file(header, io)
56
+ io.read(header[:size])
57
+ end
58
+
59
+ end # class::Ar
60
+ end # class::Archiverb
@@ -0,0 +1,76 @@
1
+ require "stringio"
2
+
3
+ class Archiverb
4
+ class File
5
+ # the basename of the file
6
+ attr_reader :name
7
+ # the directory path leading to the file
8
+ attr_reader :dir
9
+ # the path and name of the file
10
+ attr_reader :path
11
+ # octal mode
12
+ attr_accessor :mode
13
+ # The user id and group id of the file. Set #stat.uname and #stat.gname
14
+ # to force ownership by name rather than id.
15
+ attr_reader :uid, :gid
16
+ # @return [Time] modification time
17
+ attr_reader :mtime
18
+
19
+ attr_reader :size
20
+ alias :bytes :size
21
+
22
+ # the raw io object, you can add to it prior to calling read
23
+ attr_reader :io
24
+ # [Archiverb::Stat]
25
+ attr_reader :stat
26
+
27
+ def initialize(name, io, buff=io, stat=nil, &blk)
28
+ buff,stat = stat, buff if buff.is_a?(Stat) || buff.is_a?(::File::Stat)
29
+ stat = Stat.new(io) if stat.nil?
30
+
31
+ @name = ::File.basename(name)
32
+ @dir = ::File.dirname(name)
33
+ @path = name
34
+ @mtime = stat.mtime.is_a?(Fixnum) ? Time.at(stat.mtime) : stat.mtime
35
+ @uid = stat.uid
36
+ @gid = stat.gid
37
+ @mode = stat.mode
38
+ @size = stat.size
39
+
40
+ @readbuff = buff
41
+ @readback = blk unless blk.nil?
42
+ @io = io.is_a?(String) ? StringIO.new(io) : io
43
+ @io.binmode
44
+ if @io.respond_to?(:path) && ::File.directory?(@io.path)
45
+ @size = 0
46
+ end
47
+ @stat = stat
48
+ end # initialize(io, stat)
49
+
50
+ def read
51
+ return @raw if @raw
52
+
53
+ if @readback && @readbuff
54
+ @readback.call(@readbuff)
55
+ @readbuff.close_write
56
+ end
57
+
58
+ @io.rewind unless @stat.pipe?
59
+
60
+ if @io.respond_to?(:path) && ::File.directory?(@io.path)
61
+ @raw = ""
62
+ else
63
+ @raw = @io.read
64
+ @size = @raw.length
65
+ end
66
+ @io.close
67
+ @raw
68
+ end # read
69
+
70
+ # Prevents future access to the contents of the file and hopefully frees up memory.
71
+ def close
72
+ @raw = nil
73
+ end # close
74
+
75
+ end # class::File
76
+ end # class::Archiverb
@@ -0,0 +1,55 @@
1
+ require "ostruct"
2
+ class Archiverb
3
+ class Stat < OpenStruct
4
+ @@reqdatrs = [ :dev , :dev_major , :dev_minor , :ino , :mode , :nlink ,
5
+ :gid , :uid , :rdev_major , :rdev_minor , :size , :blksize ,
6
+ :blocks , :atime , :mtime , :ctime , :ftype , :pipe? ,
7
+ :rdev , :symlink?
8
+ ]
9
+ def initialize(io, start = {})
10
+ return super(Hash[@@reqdatrs.map{|m| [m, def_v(m)]}].merge(io)) if io.is_a?(Hash)
11
+ return super(stat_hash(io).merge(start)) if io.is_a?(::File::Stat)
12
+
13
+ statm = [:lstat, :stat].find{|m| io.respond_to?(m)}
14
+ return super(Hash[@@reqdatrs.map{|m| [m, def_v(m)]}].merge(stat_hash(io)).merge(start)) if statm.nil?
15
+
16
+ hash = stat_hash(io.send(statm))
17
+ hash[:readlink] = ::File.readlink(io) if hash[:symlink?]
18
+ return super(hash.merge(start))
19
+ end
20
+
21
+ # ASCII representation of the owner and group of the file respectively.
22
+ # In TAR, if found, the user and group IDs are used rather than the values
23
+ # in the uid and gid fields.
24
+ attr_accessor :uname, :gname
25
+
26
+
27
+ private
28
+
29
+ def def_v(attr)
30
+ case attr
31
+ when :atime, :ctime, :mtime
32
+ Time.new
33
+ when :size
34
+ 0
35
+ when :gid
36
+ Process.egid
37
+ when :uid
38
+ Process.euid
39
+ when :mode
40
+ 0644
41
+ when :ftype
42
+ "file"
43
+ else
44
+ false
45
+ end
46
+ end
47
+
48
+ def stat_hash(stat)
49
+ @@reqdatrs.inject({}) do |h , meth|
50
+ h[meth] = stat.respond_to?(meth) ? stat.send(meth) : def_v(meth)
51
+ h
52
+ end
53
+ end # stat_hash(stat, syms
54
+ end # class::Stat < Openstruct
55
+ end # class::Archiverb
@@ -0,0 +1,214 @@
1
+ require 'archiverb'
2
+ require 'etc'
3
+
4
+ class Archiverb
5
+ # GNU tar implementation
6
+ # @see http://en.wikipedia.org/wiki/Tar_(file_format)
7
+ # @see http://www.gnu.org/software/tar/manual/html_node/Standard.html
8
+ # @see http://www.subspacefield.org/~vax/tar_format.html
9
+ class Tar < Archiverb
10
+ TMAGIC = 'ustar'
11
+ TVERSION = "00"
12
+ OLDGNU_MAGIC = "ustar \0"
13
+ REGTYPE = '0' # regular file
14
+ AREGTYPE = "\0" # regular file
15
+ LNKTYPE = '1' # link
16
+ SYMTYPE = '2' # reserved (symlink)
17
+ CHRTYPE = '3' # character special
18
+ BLKTYPE = '4' # block special
19
+ DIRTYPE = '5' # directory
20
+ FIFOTYPE = '6' # FIFO special
21
+ CONTTYPE = '7' # reserved (contiguous file)
22
+ XHDTYPE = 'x' # extended header referring to next file in archive
23
+ XGLTYPE = 'g' # global extended header
24
+
25
+
26
+ private
27
+
28
+ def write_to(io)
29
+ @files.each do |name, file|
30
+ if name.length > 100
31
+ name, prefix = name[0..99], name[100..-1]
32
+ if prefix > 155
33
+ raise ArgumentError.new("file name cannot exceed 255 characters: #{name}#{prefix}")
34
+ end
35
+ else
36
+ prefix = ""
37
+ end
38
+ header = "#{name}" + ("\0" * (100 - name.length))
39
+ # @todo double check the modes on links
40
+ # input header: data/henryIV.txt0000755000076500000240000000000011706250470015332 2heneryIV.txtustar calebstaff
41
+ # output header: data/henryIV.txt0050423000076500000240000000001411706305252015333 2heneryIV.txtustar calebstaff
42
+
43
+ # offset 100
44
+ header += sprintf("%.7o\0", file.mode)
45
+ # offset 108
46
+ header += sprintf("%.7o\0", file.uid)
47
+ # offset 116
48
+ header += sprintf("%.7o\0", file.gid)
49
+ # offset 124
50
+ header += sprintf("%.11o\0", file.size)
51
+ # offset 136
52
+ header += sprintf("%.11o\0", file.mtime.to_i)
53
+ # offset 148
54
+ header += " " * 8 # write 8 blanks for the checksum, we'll replace it later
55
+
56
+ # offset 156
57
+ if [LNKTYPE, SYMTYPE].include?(type = tar_type(file.stat))
58
+ header += sprintf("%.1o", type)
59
+ raise ArgumentError.new("#{name} link type files' stat objects must contain a readlink") if file.stat.readlink.nil?
60
+ # offset 157
61
+ header += "#{file.stat.readlink}\0" + ("\0" * (99 - file.stat.readlink.length))
62
+ else
63
+ type = DIRTYPE if name[-1] == "/"
64
+ header += sprintf("%.1o", type)
65
+ # offset 157
66
+ header += "\0"*100
67
+ end
68
+
69
+ # offset 257
70
+ header += OLDGNU_MAGIC
71
+
72
+ uname = file.stat.uname || Etc.getpwuid(file.uid).name
73
+ gname = file.stat.gname || Etc.getgrgid(file.gid).name
74
+ # offset 265
75
+ header += "#{uname}\0" + ("\0" * (31 - uname.length))
76
+ # offset 297
77
+ header += "#{gname}\0" + ("\0" * (31 - gname.length))
78
+
79
+ # offset 329
80
+ if type == CHRTYPE || type ==BLKTYPE
81
+ header += sprintf("%.7o\0", file.stat.dev_major)
82
+ header += sprintf("%.7o\0", file.stat.dev_minor)
83
+ else
84
+ header += "\0" * 16
85
+ end
86
+
87
+ # offset 345
88
+ header += "#{prefix}" + ("\0" * (155 - (prefix || "").length))
89
+
90
+ # offset 500
91
+ header += "\0" * 12
92
+
93
+ header = header[0..147] + chksum(header) + header[156..-1]
94
+ io.write(header)
95
+ io.write(file.read).tap do |len|
96
+ unless (overflow = len % 512) == 0
97
+ io.write("\0" * (512 - (overflow)))
98
+ end
99
+ end
100
+ end # name, file
101
+
102
+ io.write("\0" * 1024)
103
+ self
104
+ end # write_to(io)
105
+
106
+ def next_header(io)
107
+ return nil if io.eof?
108
+ if (raw = io.read(512)).strip == ""
109
+ return nil if(raw = io.read(512)).strip == ""
110
+ end # raw.strip == ""
111
+
112
+ header = {}
113
+ header[:name] = raw[0..99].strip
114
+ check = chksum(raw)
115
+ header[:chksum] = raw[148..155]
116
+ raise WrongChksum.new("#{header[:name]}: #{header[:chksum]} expected to be #{check}") unless header[:chksum] == check
117
+ header[:mode] = raw[100..107].to_i(8)
118
+ header[:uid] = Integer(raw[108..115].strip)
119
+ header[:gid] = Integer(raw[116..123].strip)
120
+ header[:size] = Integer(raw[124..135].strip)
121
+ header[:mtime] = raw[136..147].strip
122
+ header[:mtime] = "0#{header[:mtime]}" if header[:mtime].length == 11
123
+ header[:mtime] = Time.at(Integer(header[:mtime]))
124
+ # @todo check for XHDTYPE, or XGLTYPE
125
+ header[:ftype] = stat_type(raw[156])
126
+ header[:readlink] = raw[157..256].strip
127
+ header[:magic] = raw[257..262].strip
128
+ header[:version] = raw[263..264].strip
129
+ header[:uname] = raw[265..296].strip
130
+ header[:gname] = raw[297..328].strip
131
+ header[:dev_major] = raw[329..336].strip
132
+ header[:dev_minor] = raw[337..344].strip
133
+ header[:prefix] = raw[345..500].strip
134
+ header.merge!(pull_ustar(raw)) if header[:magic] == TMAGIC
135
+ header
136
+ end
137
+
138
+ def tar_type(stat)
139
+ case stat.ftype
140
+ when 'file'
141
+ REGTYPE
142
+ when 'directory'
143
+ DIRTYPE
144
+ when 'characterSpecial'
145
+ CHRTYPE
146
+ when 'blockSpecial'
147
+ BLKTYPE
148
+ when 'fifo'
149
+ FIFOTYPE
150
+ when 'link'
151
+ #LNKTYPE
152
+ SYMTYPE
153
+ else
154
+ warn "file type: #{stat.ftype} is not supported; treating it as a regular file"
155
+ REGTYPE
156
+ end
157
+ end # tar_type(stat)
158
+
159
+ def stat_type(bit)
160
+ case bit
161
+ when REGTYPE, AREGTYPE
162
+ 'file'
163
+ when LNKTYPE, SYMTYPE
164
+ 'link'
165
+ when CHRTYPE
166
+ 'characterSpecial'
167
+ when BLKTYPE
168
+ 'blockSpecial'
169
+ when DIRTYPE
170
+ 'directory'
171
+ when FIFOTYPE
172
+ 'fifo'
173
+ when CONTTYPE, XHDTYPE, XGLTYPE
174
+ 'unknown'
175
+ else
176
+ warn "file type #{bit} is not supported; treating it as a regular file"
177
+ 'regular'
178
+ end
179
+ end # stat_type(bit)
180
+ def read_file(header, io)
181
+ io.read(header[:size]).tap do |raw|
182
+ if (diff = header[:size] % 512) != 0
183
+ io.read(512 - diff)
184
+ end
185
+ end # raw
186
+ end
187
+
188
+ def pull_ustar(raw)
189
+ header = {}
190
+ header[:prefix] = raw[345].strip
191
+ header[:fill2] = raw[346].strip
192
+ header[:fill3] = raw[347..354].strip
193
+ header[:isextended] = raw[355]
194
+ header[:sparse] = raw[356..451].strip
195
+ header[:realsize] = raw[452..463].strip
196
+ header[:offset] = raw[464..475].strip
197
+ header[:atime] = raw[476..487].strip
198
+ header[:ctime] = raw[488..499].strip
199
+ header[:mfill] = raw[500..507].strip
200
+ header[:xmagic] = raw[508..511].strip
201
+ header
202
+ end # pull_ustar(header)
203
+
204
+ # The checksum is calculated by taking the sum of the unsigned byte
205
+ # values of the header block with the eight checksum bytes taken to
206
+ # be ascii spaces (decimal value 32). It is stored as a six digit
207
+ # octal number with leading zeroes followed by a NUL and then a
208
+ # space.
209
+ def chksum(header)
210
+ sprintf("%.6o\0 ", (header[0..147] + header[156..500]).each_byte.inject(256) { |s,b| s+b })
211
+ end
212
+
213
+ end # class::Tar < Archiverb
214
+ end # class::Archiverb
@@ -0,0 +1,3 @@
1
+ class Archiverb
2
+ VERSION = '0.9.0'
3
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: archiverb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Caleb Crane
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-07 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ description: ''
31
+ email:
32
+ - archiverb@simulacre.org
33
+ executables: []
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - lib/archiverb/ar.rb
38
+ - lib/archiverb/file.rb
39
+ - lib/archiverb/stat.rb
40
+ - lib/archiverb/tar.rb
41
+ - lib/archiverb/version.rb
42
+ - lib/archiverb.rb
43
+ - README.md
44
+ - LICENSE.txt
45
+ homepage: http://github.com/simulacre/archiverb
46
+ licenses:
47
+ - MIT
48
+ post_install_message:
49
+ rdoc_options: []
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ! '>='
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ! '>='
62
+ - !ruby/object:Gem::Version
63
+ version: 1.3.6
64
+ requirements: []
65
+ rubyforge_project:
66
+ rubygems_version: 1.8.24
67
+ signing_key:
68
+ specification_version: 3
69
+ summary: Native Ruby implementations of tar and ar archives
70
+ test_files: []
71
+ has_rdoc: