archiverb 0.9.0

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