jacktang-hacker-slides 1.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.
@@ -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