jacktang-hacker-slides 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,136 @@
1
+ module Archive
2
+ module Tar
3
+ # = Archive::Tar::PosixHeader
4
+ # Implements the POSIX tar header as a Ruby class. The structure of
5
+ # the POSIX tar header is:
6
+ #
7
+ # struct tarfile_entry_posix
8
+ # { // pack/unpack
9
+ # char name[100]; // ASCII (+ Z unless filled) a100/Z100
10
+ # char mode[8]; // 0 padded, octal, null a8 /A8
11
+ # char uid[8]; // ditto a8 /A8
12
+ # char gid[8]; // ditto a8 /A8
13
+ # char size[12]; // 0 padded, octal, null a12 /A12
14
+ # char mtime[12]; // 0 padded, octal, null a12 /A12
15
+ # char checksum[8]; // 0 padded, octal, null, space a8 /A8
16
+ # char typeflag[1]; // see below a /a
17
+ # char linkname[100]; // ASCII + (Z unless filled) a100/Z100
18
+ # char magic[6]; // "ustar\0" a6 /A6
19
+ # char version[2]; // "00" a2 /A2
20
+ # char uname[32]; // ASCIIZ a32 /Z32
21
+ # char gname[32]; // ASCIIZ a32 /Z32
22
+ # char devmajor[8]; // 0 padded, octal, null a8 /A8
23
+ # char devminor[8]; // 0 padded, octal, null a8 /A8
24
+ # char prefix[155]; // ASCII (+ Z unless filled) a155/Z155
25
+ # };
26
+ #
27
+ # The +typeflag+ may be one of the following known values:
28
+ #
29
+ # <tt>"0"</tt>:: Regular file. NULL should be treated as a synonym, for
30
+ # compatibility purposes.
31
+ # <tt>"1"</tt>:: Hard link.
32
+ # <tt>"2"</tt>:: Symbolic link.
33
+ # <tt>"3"</tt>:: Character device node.
34
+ # <tt>"4"</tt>:: Block device node.
35
+ # <tt>"5"</tt>:: Directory.
36
+ # <tt>"6"</tt>:: FIFO node.
37
+ # <tt>"7"</tt>:: Reserved.
38
+ #
39
+ # POSIX indicates that "A POSIX-compliant implementation must treat any
40
+ # unrecognized typeflag value as a regular file."
41
+ class PosixHeader
42
+ FIELDS = %w(name mode uid gid size mtime checksum typeflag linkname) +
43
+ %w(magic version uname gname devmajor devminor prefix)
44
+
45
+ FIELDS.each { |field| attr_reader field.intern }
46
+
47
+ HEADER_PACK_FORMAT = "a100a8a8a8a12a12a7aaa100a6a2a32a32a8a8a155"
48
+ HEADER_UNPACK_FORMAT = "Z100A8A8A8A12A12A8aZ100A6A2Z32Z32A8A8Z155"
49
+
50
+ # Creates a new PosixHeader from a data stream.
51
+ def self.new_from_stream(stream)
52
+ data = stream.read(512)
53
+ fields = data.unpack(HEADER_UNPACK_FORMAT)
54
+ name = fields.shift
55
+ mode = fields.shift.oct
56
+ uid = fields.shift.oct
57
+ gid = fields.shift.oct
58
+ size = fields.shift.oct
59
+ mtime = fields.shift.oct
60
+ checksum = fields.shift.oct
61
+ typeflag = fields.shift
62
+ linkname = fields.shift
63
+ magic = fields.shift
64
+ version = fields.shift.oct
65
+ uname = fields.shift
66
+ gname = fields.shift
67
+ devmajor = fields.shift.oct
68
+ devminor = fields.shift.oct
69
+ prefix = fields.shift
70
+
71
+ empty = (data == "\0" * 512)
72
+
73
+ new(:name => name, :mode => mode, :uid => uid, :gid => gid,
74
+ :size => size, :mtime => mtime, :checksum => checksum,
75
+ :typeflag => typeflag, :magic => magic, :version => version,
76
+ :uname => uname, :gname => gname, :devmajor => devmajor,
77
+ :devminor => devminor, :prefix => prefix, :empty => empty)
78
+ end
79
+
80
+ # Creates a new PosixHeader. A PosixHeader cannot be created unless the
81
+ # #name, #size, #prefix, and #mode are provided.
82
+ def initialize(vals)
83
+ unless vals[:name] && vals[:size] && vals[:prefix] && vals[:mode]
84
+ raise ArgumentError
85
+ end
86
+
87
+ vals[:mtime] ||= 0
88
+ vals[:checksum] ||= ""
89
+ vals[:typeflag] ||= "0"
90
+ vals[:magic] ||= "ustar"
91
+ vals[:version] ||= "00"
92
+
93
+ FIELDS.each do |field|
94
+ instance_variable_set("@#{field}", vals[field.intern])
95
+ end
96
+ @empty = vals[:empty]
97
+ end
98
+
99
+ def empty?
100
+ @empty
101
+ end
102
+
103
+ def to_s
104
+ update_checksum
105
+ header(@checksum)
106
+ end
107
+
108
+ # Update the checksum field.
109
+ def update_checksum
110
+ hh = header(" " * 8)
111
+ @checksum = oct(calculate_checksum(hh), 6)
112
+ end
113
+
114
+ private
115
+ def oct(num, len)
116
+ if num.nil?
117
+ "\0" * (len + 1)
118
+ else
119
+ "%0#{len}o" % num
120
+ end
121
+ end
122
+
123
+ def calculate_checksum(hdr)
124
+ hdr.unpack("C*").inject { |aa, bb| aa + bb }
125
+ end
126
+
127
+ def header(chksum)
128
+ arr = [name, oct(mode, 7), oct(uid, 7), oct(gid, 7), oct(size, 11),
129
+ oct(mtime, 11), chksum, " ", typeflag, linkname, magic, version,
130
+ uname, gname, oct(devmajor, 7), oct(devminor, 7), prefix]
131
+ str = arr.pack(HEADER_PACK_FORMAT)
132
+ str + "\0" * ((512 - str.size) % 512)
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,199 @@
1
+ module Archive
2
+ module Tar
3
+ module Minitar
4
+ # The class that reads a tar format archive from a data stream. The data
5
+ # stream may be sequential or random access, but certain features only work
6
+ # with random access data streams.
7
+ class Reader
8
+ # This marks the EntryStream closed for reading without closing the
9
+ # actual data stream.
10
+ module InvalidEntryStream
11
+ def read(len = nil); raise ClosedStream; end
12
+ def getc; raise ClosedStream; end
13
+ def rewind; raise ClosedStream; end
14
+ end
15
+
16
+ # EntryStreams are pseudo-streams on top of the main data stream.
17
+ class EntryStream
18
+ Archive::Tar::PosixHeader::FIELDS.each do |field|
19
+ attr_reader field.intern
20
+ end
21
+
22
+ def initialize(header, anIO)
23
+ @io = anIO
24
+ @name = header.name
25
+ @mode = header.mode
26
+ @uid = header.uid
27
+ @gid = header.gid
28
+ @size = header.size
29
+ @mtime = header.mtime
30
+ @checksum = header.checksum
31
+ @typeflag = header.typeflag
32
+ @linkname = header.linkname
33
+ @magic = header.magic
34
+ @version = header.version
35
+ @uname = header.uname
36
+ @gname = header.gname
37
+ @devmajor = header.devmajor
38
+ @devminor = header.devminor
39
+ @prefix = header.prefix
40
+ @read = 0
41
+ @orig_pos = @io.pos
42
+ end
43
+
44
+ # Reads +len+ bytes (or all remaining data) from the entry. Returns
45
+ # +nil+ if there is no more data to read.
46
+ def read(len = nil)
47
+ return nil if @read >= @size
48
+ len ||= @size - @read
49
+ max_read = [len, @size - @read].min
50
+ ret = @io.read(max_read)
51
+ @read += ret.size
52
+ ret
53
+ end
54
+
55
+ # Reads one byte from the entry. Returns +nil+ if there is no more data
56
+ # to read.
57
+ def getc
58
+ return nil if @read >= @size
59
+ ret = @io.getc
60
+ @read += 1 if ret
61
+ ret
62
+ end
63
+
64
+ # Returns +true+ if the entry represents a directory.
65
+ def directory?
66
+ @typeflag == "5"
67
+ end
68
+ alias_method :directory, :directory?
69
+
70
+ # Returns +true+ if the entry represents a plain file.
71
+ def file?
72
+ @typeflag == "0"
73
+ end
74
+ alias_method :file, :file?
75
+
76
+ # Returns +true+ if the current read pointer is at the end of the
77
+ # EntryStream data.
78
+ def eof?
79
+ @read >= @size
80
+ end
81
+
82
+ # Returns the current read pointer in the EntryStream.
83
+ def pos
84
+ @read
85
+ end
86
+
87
+ # Sets the current read pointer to the beginning of the EntryStream.
88
+ def rewind
89
+ raise NonSeekableStream unless @io.respond_to?(:pos=)
90
+ @io.pos = @orig_pos
91
+ @read = 0
92
+ end
93
+
94
+ def bytes_read
95
+ @read
96
+ end
97
+
98
+ # Returns the full and proper name of the entry.
99
+ def full_name
100
+ if @prefix != ""
101
+ File.join(@prefix, @name)
102
+ else
103
+ @name
104
+ end
105
+ end
106
+
107
+ # Closes the entry.
108
+ def close
109
+ invalidate
110
+ end
111
+
112
+ private
113
+ def invalidate
114
+ extend InvalidEntryStream
115
+ end
116
+ end
117
+
118
+ # With no associated block, +Reader::open+ is a synonym for
119
+ # +Reader::new+. If the optional code block is given, it will be passed
120
+ # the new _writer_ as an argument and the Reader object will
121
+ # automatically be closed when the block terminates. In this instance,
122
+ # +Reader::open+ returns the value of the block.
123
+ def self.open(anIO)
124
+ reader = Reader.new(anIO)
125
+
126
+ return reader unless block_given?
127
+
128
+ begin
129
+ res = yield reader
130
+ ensure
131
+ reader.close
132
+ end
133
+
134
+ res
135
+ end
136
+
137
+ # Creates and returns a new Reader object.
138
+ def initialize(anIO)
139
+ @io = anIO
140
+ @init_pos = anIO.pos
141
+ end
142
+
143
+ # Iterates through each entry in the data stream.
144
+ def each(&block)
145
+ each_entry(&block)
146
+ end
147
+
148
+ # Resets the read pointer to the beginning of data stream. Do not call
149
+ # this during a #each or #each_entry iteration. This only works with
150
+ # random access data streams that respond to #rewind and #pos.
151
+ def rewind
152
+ if @init_pos == 0
153
+ raise NonSeekableStream unless @io.respond_to?(:rewind)
154
+ @io.rewind
155
+ else
156
+ raise NonSeekableStream unless @io.respond_to?(:pos=)
157
+ @io.pos = @init_pos
158
+ end
159
+ end
160
+
161
+ # Iterates through each entry in the data stream.
162
+ def each_entry
163
+ loop do
164
+ return if @io.eof?
165
+
166
+ header = Archive::Tar::PosixHeader.new_from_stream(@io)
167
+ return if header.empty?
168
+
169
+ entry = EntryStream.new(header, @io)
170
+ size = entry.size
171
+
172
+ yield entry
173
+
174
+ skip = (512 - (size % 512)) % 512
175
+
176
+ if @io.respond_to?(:seek)
177
+ # avoid reading...
178
+ @io.seek(size - entry.bytes_read, IO::SEEK_CUR)
179
+ else
180
+ pending = size - entry.bytes_read
181
+ while pending > 0
182
+ bread = @io.read([pending, 4096].min).size
183
+ raise UnexpectedEOF if @io.eof?
184
+ pending -= bread
185
+ end
186
+ end
187
+ @io.read(skip) # discard trailing zeros
188
+ # make sure nobody can use #read, #getc or #rewind anymore
189
+ entry.close
190
+ end
191
+ end
192
+
193
+ def close
194
+ end
195
+ end
196
+
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,206 @@
1
+ module Archive
2
+ module Tar
3
+ module Minitar
4
+
5
+ # The class that writes a tar format archive to a data stream.
6
+ class Writer
7
+ # A stream wrapper that can only be written to. Any attempt to read
8
+ # from this restricted stream will result in a NameError being thrown.
9
+ class RestrictedStream
10
+ def initialize(anIO)
11
+ @io = anIO
12
+ end
13
+
14
+ def write(data)
15
+ @io.write(data)
16
+ end
17
+ end
18
+
19
+ # A RestrictedStream that also has a size limit.
20
+ class BoundedStream < Archive::Tar::Minitar::Writer::RestrictedStream
21
+ # The exception raised when the user attempts to write more data to
22
+ # a BoundedStream than has been allocated.
23
+ class FileOverflow < RuntimeError; end
24
+
25
+ # The maximum number of bytes that may be written to this data
26
+ # stream.
27
+ attr_reader :limit
28
+ # The current total number of bytes written to this data stream.
29
+ attr_reader :written
30
+
31
+ def initialize(io, limit)
32
+ @io = io
33
+ @limit = limit
34
+ @written = 0
35
+ end
36
+
37
+ def write(data)
38
+ raise FileOverflow if (data.size + @written) > @limit
39
+ @io.write(data)
40
+ @written += data.size
41
+ data.size
42
+ end
43
+ end
44
+
45
+ # With no associated block, +Writer::open+ is a synonym for
46
+ # +Writer::new+. If the optional code block is given, it will be
47
+ # passed the new _writer_ as an argument and the Writer object will
48
+ # automatically be closed when the block terminates. In this instance,
49
+ # +Writer::open+ returns the value of the block.
50
+ def self.open(anIO)
51
+ writer = Writer.new(anIO)
52
+
53
+ return writer unless block_given?
54
+
55
+ begin
56
+ res = yield writer
57
+ ensure
58
+ writer.close
59
+ end
60
+
61
+ res
62
+ end
63
+
64
+ # Creates and returns a new Writer object.
65
+ def initialize(anIO)
66
+ @io = anIO
67
+ @closed = false
68
+ end
69
+
70
+ # Adds a file to the archive as +name+. +opts+ must contain the
71
+ # following values:
72
+ #
73
+ # <tt>:mode</tt>:: The Unix file permissions mode value.
74
+ # <tt>:size</tt>:: The size, in bytes.
75
+ #
76
+ # +opts+ may contain the following values:
77
+ #
78
+ # <tt>:uid</tt>: The Unix file owner user ID number.
79
+ # <tt>:gid</tt>: The Unix file owner group ID number.
80
+ # <tt>:mtime</tt>:: The *integer* modification time value.
81
+ #
82
+ # It will not be possible to add more than <tt>opts[:size]</tt> bytes
83
+ # to the file.
84
+ def add_file_simple(name, opts = {}) # :yields BoundedStream:
85
+ raise Archive::Tar::Minitar::BlockRequired unless block_given?
86
+ raise Archive::Tar::ClosedStream if @closed
87
+
88
+ name, prefix = split_name(name)
89
+
90
+ header = { :name => name, :mode => opts[:mode], :mtime => opts[:mtime],
91
+ :size => opts[:size], :gid => opts[:gid], :uid => opts[:uid],
92
+ :prefix => prefix }
93
+ header = Archive::Tar::PosixHeader.new(header).to_s
94
+ @io.write(header)
95
+
96
+ os = BoundedStream.new(@io, opts[:size])
97
+ yield os
98
+ # FIXME: what if an exception is raised in the block?
99
+
100
+ min_padding = opts[:size] - os.written
101
+ @io.write("\0" * min_padding)
102
+ remainder = (512 - (opts[:size] % 512)) % 512
103
+ @io.write("\0" * remainder)
104
+ end
105
+
106
+ # Adds a file to the archive as +name+. +opts+ must contain the
107
+ # following value:
108
+ #
109
+ # <tt>:mode</tt>:: The Unix file permissions mode value.
110
+ #
111
+ # +opts+ may contain the following values:
112
+ #
113
+ # <tt>:uid</tt>: The Unix file owner user ID number.
114
+ # <tt>:gid</tt>: The Unix file owner group ID number.
115
+ # <tt>:mtime</tt>:: The *integer* modification time value.
116
+ #
117
+ # The file's size will be determined from the amount of data written
118
+ # to the stream.
119
+ #
120
+ # For #add_file to be used, the Archive::Tar::Minitar::Writer must be
121
+ # wrapping a stream object that is seekable (e.g., it responds to
122
+ # #pos=). Otherwise, #add_file_simple must be used.
123
+ #
124
+ # +opts+ may be modified during the writing to the stream.
125
+ def add_file(name, opts = {}) # :yields RestrictedStream, +opts+:
126
+ raise Archive::Tar::Minitar::BlockRequired unless block_given?
127
+ raise Archive::Tar::Minitar::ClosedStream if @closed
128
+ raise Archive::Tar::Minitar::NonSeekableStream unless @io.respond_to?(:pos=)
129
+
130
+ name, prefix = split_name(name)
131
+ init_pos = @io.pos
132
+ @io.write("\0" * 512) # placeholder for the header
133
+
134
+ yield RestrictedStream.new(@io), opts
135
+ # FIXME: what if an exception is raised in the block?
136
+
137
+ size = @io.pos - (init_pos + 512)
138
+ remainder = (512 - (size % 512)) % 512
139
+ @io.write("\0" * remainder)
140
+
141
+ final_pos = @io.pos
142
+ @io.pos = init_pos
143
+
144
+ header = { :name => name, :mode => opts[:mode], :mtime => opts[:mtime],
145
+ :size => size, :gid => opts[:gid], :uid => opts[:uid],
146
+ :prefix => prefix }
147
+ header = Archive::Tar::PosixHeader.new(header).to_s
148
+ @io.write(header)
149
+ @io.pos = final_pos
150
+ end
151
+
152
+ # Creates a directory in the tar.
153
+ def mkdir(name, opts = {})
154
+ raise ClosedStream if @closed
155
+ name, prefix = split_name(name)
156
+ header = { :name => name, :mode => opts[:mode], :typeflag => "5",
157
+ :size => 0, :gid => opts[:gid], :uid => opts[:uid],
158
+ :mtime => opts[:mtime], :prefix => prefix }
159
+ header = Archive::Tar::PosixHeader.new(header).to_s
160
+ @io.write(header)
161
+ nil
162
+ end
163
+
164
+ # Passes the #flush method to the wrapped stream, used for buffered
165
+ # streams.
166
+ def flush
167
+ raise ClosedStream if @closed
168
+ @io.flush if @io.respond_to?(:flush)
169
+ end
170
+
171
+ # Closes the Writer.
172
+ def close
173
+ return if @closed
174
+ @io.write("\0" * 1024)
175
+ @closed = true
176
+ end
177
+
178
+ private
179
+ def split_name(name)
180
+ raise FileNameTooLong if name.size > 256
181
+ if name.size <= 100
182
+ prefix = ""
183
+ else
184
+ parts = name.split(/\//)
185
+ newname = parts.pop
186
+
187
+ nxt = ""
188
+
189
+ loop do
190
+ nxt = parts.pop
191
+ break if newname.size + 1 + nxt.size > 100
192
+ newname = "#{nxt}/#{newname}"
193
+ end
194
+
195
+ prefix = (parts + [nxt]).join("/")
196
+
197
+ name = newname
198
+
199
+ raise FileNameTooLong if name.size > 100 || prefix.size > 155
200
+ end
201
+ return name, prefix
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end