1#!/usr/bin/env ruby 2# -*- ruby -*- 3# 4# Copyright (c) 2001-2004 Akinori MUSHA 5# 6# All rights reserved. 7# 8# Redistribution and use in source and binary forms, with or without 9# modification, are permitted provided that the following conditions 10# are met: 11# 1. Redistributions of source code must retain the above copyright 12# notice, this list of conditions and the following disclaimer. 13# 2. Redistributions in binary form must reproduce the above copyright 14# notice, this list of conditions and the following disclaimer in the 15# documentation and/or other materials provided with the distribution. 16# 17# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27# SUCH DAMAGE. 28# 29 30RCS_ID = %q$Idaemons: /home/cvs/sunshar/sunshar.rb,v 1.13 2004/02/28 14:15:47 knu Exp $ 31RCS_REVISION = RCS_ID.split[2] 32MYNAME = File.basename($0) 33 34require 'optparse' 35require 'fileutils' 36require 'shellwords' 37require 'stringio' 38 39$USAGE = 'usage' 40 41$strip_level = 0 42$force = false 43$dryrun = false 44$quiet = false 45$dir = nil 46 47def info(*s) 48 puts(*s) unless $quiet 49end 50 51def usage 52 print <<-EOF 53#{MYNAME} rev.#{RCS_REVISION} 54 55usage: #{MYNAME} [-fnq] [-p level] [file] 56 #{MYNAME} -h 57 -d dir chdir -- chdir to dir before extracting files 58 -f force -- allow overwriting, ignore errors 59 -h help -- show this help 60 -n dry run -- show what would have been extracted 61 -p N strip -- strip N levels from pathnames (cf. patch(1)\'s -p) 62 -q quiet -- be quiet 63 EOF 64end 65 66def main 67 params = ARGV.getopts("fhnq", "d:", "p:") 68 69 if params['h'] 70 usage 71 exit 0 72 end 73 74 if params['f'] 75 $force = true 76 end 77 78 if params['n'] 79 $dryrun = true 80 end 81 82 if params['q'] 83 $quiet = true 84 end 85 86 $dir = params['d'] || '.' 87 88 if not params['p'].nil? 89 $strip_level = params['p'].to_i rescue -1 90 91 if $strip_level < 0 92 STDERR.puts "negative value ignored: #{params['p']}" 93 end 94 end 95 96 nerrors = 0 97 98 if ARGV.empty? 99 info "extracting files from stdin into #{$dir}" 100 101 begin 102 Dir.chdir($dir) { 103 unshar_stream(STDIN) 104 } 105 106 info "done." 107 rescue => e 108 STDERR.puts "error in extracting stdin: #{e.message}" 109 nerrors += 1 110 end 111 else 112 for file in ARGV 113 info "extracting files from #{file} into #{$dir}" 114 115 begin 116 File.open(file) do |f| 117 Dir.chdir($dir) { 118 unshar_stream(f) 119 } 120 end 121 122 info "done." 123 rescue => e 124 STDERR.puts "error in extracting #{file}: #{e.message}" 125 nerrors += 1 126 end 127 end 128 end 129 130 exit nerrors 131end 132 133def unshar_stream(io) 134 e = nil 135 136 while line = io.gets 137 if /^(\s*)\# This is a shell archive/ =~ line 138 indent = $1.length 139 break 140 end 141 end 142 143 if io.eof? 144 raise "not a shell archive." 145 end 146 147 f = nil 148 prefix = nil 149 file = nil 150 boundary = nil 151 152 while line = io.gets 153 line.slice!(0, indent) 154 155 if f 156 if line.strip == boundary 157 f.close 158 f = nil 159 next 160 end 161 162 if line.sub!(/^#{Regexp.quote(prefix)}/, '') 163 f.print line 164 else 165 raise "line #{io.lineno}: broken archive: #{line}" 166 end 167 168 next 169 end 170 171 case line 172 when /^exit\s*$/ 173 break 174 when /^echo\s+(.+)$/ 175 # info $1 176 when /^mkdir\s+(?:-p\s+)?(.+)$/ 177 dir = nil 178 179 Shellwords.shellwords($1).each do |word| 180 if /^[^\-]/ =~ word 181 dir = word 182 break 183 end 184 end 185 186 next if dir.nil? 187 188 dir = strip_filename(dir.strip + '/') 189 if dir.chomp('/').empty? 190 next 191 end 192 193 begin 194 FileUtils.mkdir_p(dir) unless $dryrun 195 info "c - #{dir}" 196 rescue => e 197 info "c - #{dir} ... failed: #{e.message}" 198 raise e 199 end 200 when /sed\s+(.+)>(.+)<<(.+)/ 201 prefix = Shellwords.shellwords($1).first 202 file = Shellwords.shellwords($2).first 203 boundary = Shellwords.shellwords($3).first 204 205 next unless prefix && file && boundary 206 207 if /s(.)\^(.*)\1\1/ =~ prefix 208 prefix = $2 209 else 210 next 211 end 212 213 file = strip_filename(file) 214 215 next if file.empty? || boundary.empty? 216 217 overwrite = false 218 219 if File.exist?(file) 220 if $force 221 overwrite = true 222 else 223 info "x - #{file} ... skipped" 224 next 225 end 226 end 227 228 dir = File.dirname(file) 229 230 if !File.directory?(dir + "/.") 231 begin 232 FileUtils.mkdir_p(dir) unless $dryrun 233 info "d - #{dir}" 234 rescue => e 235 info "d - #{dir} ... failed: #{e.message}" 236 raise e 237 end 238 end 239 240 begin 241 f = $dryrun ? StringIO.new : File.open(file, 'w') 242 if overwrite 243 info "x - #{file} ... overwritten" 244 else 245 info "x - #{file}" 246 end 247 rescue => e 248 info "x - #{file} ... failed! (#{e.message})" 249 250 if $force 251 f = nil 252 next 253 else 254 raise e 255 end 256 end 257 end 258 end 259 260 raise e if e 261end 262 263def strip_filename(file) 264 sfile = file.gsub(%r"/{2,}", "/") 265 266 if 0 < $strip_level 267 sfile.sub!(%r"^([^/]*/){1,#{$strip_level}}", '') 268 end 269 270 case sfile 271 when %r"^[~/]" 272 raise "reference to absolute directory: #{file} (use -p N)" 273 when %r"(^|/)\.\.(?:/|$)" 274 raise "reference to parent directory: #{file} (use -p N)" 275 end 276 277 sfile 278end 279 280def signal_handler(sig) 281 info "\nInterrupted." 282 283 exit 255 284end 285 286if $0 == __FILE__ 287 for sig in [2, 3, 15] 288 trap(sig) do 289 signal_handler(sig) 290 end 291 end 292 293 main 294end 295