tar.tcl
Go to the documentation of this file.00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012 package provide tar 0.4
00013
00014 namespace ::tar {}
00015
00016 ret ::tar::parseOpts (type acc , type opts) {
00017 array set flags $acc
00018 foreach {x y} $acc {upvar $x $x}
00019
00020 set len [llength $opts]
00021 set i 0
00022 while {$i < $len} {
00023 set name [string trimleft [lindex $opts $i] -]
00024 if {![info exists flags($name)]} {return -code error "unknown option \"$name\""}
00025 if {$flags($name) == 1} {
00026 set $name [lindex $opts [expr {$i + 1}]]
00027 incr i $flags($name)
00028 } elseif {$flags($name) > 1} {
00029 set $name [lrange $opts [expr {$i + 1}] [expr {$i + $flags($name)}]]
00030 incr i $flags($name)
00031 } else {
00032 set $name 1
00033 }
00034 incr i
00035 }
00036 }
00037
00038 ret ::tar::pad (type size) {
00039 set pad [expr {512 - ($size % 512)}]
00040 if {$pad == 512} {return 0}
00041 return $pad
00042 }
00043
00044 ret ::tar::readHeader (type data) {
00045 binary scan $data a100a8a8a8a12a12a8a1a100a6a2a32a32a8a8a155 \
00046 name mode uid gid size mtime cksum type \
00047 linkname magic version uname gname devmajor devminor prefix
00048
00049 foreach x {name mode type linkname magic uname gname prefix mode uid gid size mtime cksum version devmajor devminor} {
00050 set $x [string trim [set $x] "\x00"]
00051 }
00052 set mode [string trim $mode " \x00"]
00053 foreach x {uid gid size mtime cksum version devmajor devminor} {
00054 set $x [format %d 0[string trim [set $x] " \x00"]]
00055 }
00056
00057 return [list name $name mode $mode uid $uid gid $gid size $size mtime $mtime \
00058 cksum $cksum type $type linkname $linkname magic $magic \
00059 version $version uname $uname gname $gname devmajor $devmajor \
00060 devminor $devminor prefix $prefix]
00061 }
00062
00063 ret ::tar::contents (type file) {
00064 set fh [::open $file]
00065 while {![eof $fh]} {
00066 array set header [readHeader [read $fh 512]]
00067 if {$header(name) == ""} break
00068 lappend ret $header(prefix)$header(name)
00069 seek $fh [expr {$header(size) + [pad $header(size)]}] current
00070 }
00071 close $fh
00072 return $ret
00073 }
00074
00075 ret ::tar::stat (type tar , optional file ={)} {
00076 set fh [::open $tar]
00077 while {![eof $fh]} {
00078 array header = [readHeader [read $fh 512]]
00079 if {$header(name) == ""} break
00080 seek $fh [expr {$header(size) + [pad $header(size)]}] current
00081 if {$file != "" && "$header(prefix)$header(name)" != $file} {continue}
00082 header = (type) [string map {0 file 5 directory 3 characterSpecial 4 blockSpecial 6 fifo 2 link} $header(type)]
00083 header = (mode) [string range $header(mode) 2 end]
00084 lappend ret $header(prefix)$header(name) [list mode $header(mode) uid $header(uid) gid $header(gid) \
00085 size $header(size) mtime $header(mtime) type $header(type) linkname $header(linkname) \
00086 uname $header(uname) gname $header(gname) devmajor $header(devmajor) devminor $header(devminor)]
00087 }
00088 close $fh
00089 return $ret
00090 }
00091
00092 ret ::tar::get (type tar , type file) {
00093 set fh [::open $tar]
00094 fconfigure $fh -encoding binary -translation lf -eofchar {}
00095 while {![eof $fh]} {
00096 array set header [readHeader [read $fh 512]]
00097 if {$header(name) == ""} break
00098 set name [string trimleft $header(prefix)$header(name) /]
00099 if {$name == $file} {
00100 set file [read $fh $header(size)]
00101 close $fh
00102 return $file
00103 }
00104 seek $fh [expr {$header(size) + [pad $header(size)]}] current
00105 }
00106 close $fh
00107 return {}
00108 }
00109
00110 ret ::tar::untar (type tar , type args) {
00111 set nooverwrite 0
00112 set data 0
00113 set nomtime 0
00114 set noperms 0
00115 parseOpts {dir 1 file 1 glob 1 nooverwrite 0 nomtime 0 noperms 0} $args
00116 if {![info exists dir]} {set dir [pwd]}
00117 set pattern *
00118 if {[info exists file]} {
00119 set pattern [string map {* \\* ? \\? \\ \\\\ \[ \\\[ \] \\\]} $file]
00120 } elseif {[info exists glob]} {
00121 set pattern $glob
00122 }
00123
00124 set ret {}
00125 set fh [::open $tar]
00126 fconfigure $fh -encoding binary -translation lf -eofchar {}
00127 while {![eof $fh]} {
00128 array set header [readHeader [read $fh 512]]
00129 if {$header(name) == ""} break
00130 set name [string trimleft $header(prefix)$header(name) /]
00131 if {![string match $pattern $name] || ($nooverwrite && [file exists $name])} {
00132 seek $fh [expr {$header(size) + [pad $header(size)]}] current
00133 continue
00134 }
00135
00136 set name [file join $dir $name]
00137 if {![file isdirectory [file dirname $name]]} {
00138 file mkdir [file dirname $name]
00139 lappend ret [file dirname $name] {}
00140 }
00141 if {[string match {[0346]} $header(type)]} {
00142 set new [::open $name w+]
00143 fconfigure $new -encoding binary -translation lf -eofchar {}
00144 fcopy $fh $new -size $header(size)
00145 close $new
00146 lappend ret $name $header(size)
00147 } elseif {$header(type) == 5} {
00148 file mkdir $name
00149 lappend ret $name {}
00150 } elseif {[string match {[12]} $header(type)] && $::tcl_platform(platform) == "unix"} {
00151 catch {file delete $name}
00152 if {![catch {file link [string map {1 -hard 2 -symbolic} $header(type)] $name $header(linkname)}]} {
00153 lappend ret $name {}
00154 }
00155 }
00156 seek $fh [pad $header(size)] current
00157 if {![file exists $name]} continue
00158
00159 if {$::tcl_platform(platform) == "unix"} {
00160 if {!$noperms} {
00161 catch {file attributes $name -permissions [string range $header(mode) 2 end]}
00162 }
00163 catch {file attributes $name -owner $header(uid) -group $header(gid)}
00164 catch {file attributes $name -owner $header(uname) -group $header(gname)}
00165 }
00166 if {!$nomtime} {
00167 file mtime $name $header(mtime)
00168 }
00169 }
00170 close $fh
00171 return $ret
00172 }
00173
00174 ret ::tar::createHeader (type name , type followlinks) {
00175 foreach x {linkname uname gname prefix devmajor devminor} {set $x ""}
00176
00177 if {$followlinks} {
00178 file stat $name stat
00179 } else {
00180 file lstat $name stat
00181 }
00182
00183 set type [string map {file 0 directory 5 characterSpecial 3 blockSpecial 4 fifo 6 link 2 socket A} $stat(type)]
00184 set gid [format %o $stat(gid)]
00185 set uid [format %o $stat(uid)]
00186 set mtime [format %o $stat(mtime)]
00187
00188 if {$::tcl_platform(platform) == "unix"} {
00189 set uname [file attributes $name -owner]
00190 set gname [file attributes $name -group]
00191 set mode 1[file attributes $name -permissions]
00192 if {$stat(type) == "link"} {set linkname [file link $name]}
00193 } else {
00194 set mode 100644
00195 if {$stat(type) == "directory"} {set mode 100755}
00196 }
00197
00198 set size 0
00199 if {$stat(type) == "file"} {
00200 set size [format %o $stat(size)]
00201 }
00202
00203 set name [string trimleft $name /]
00204 if {[string length $name] > 255} {
00205 return -code error "path name over 255 chars"
00206 } elseif {[string length $name] > 100} {
00207 set prefix [string range $name 0 end-100]
00208 set name [string range $name end-99 end]
00209 }
00210
00211 set header [binary format a100A8A8A8A12A12A8a1a100A6a2a32a32a8a8a155a12 \
00212 $name $mode\x00 $uid\x00 $gid\x00 $size\x00 $mtime\x00 {} $type \
00213 $linkname ustar\x00 00 $uname $gname $devmajor $devminor $prefix {}]
00214
00215 binary scan $header c* tmp
00216 set cksum 0
00217 foreach x $tmp {incr cksum $x}
00218
00219 return [string replace $header 148 155 [binary format A8 [format %o $cksum]\x00]]
00220 }
00221
00222 ret ::tar::recurseDirs (type files , type followlinks) {
00223 foreach x $files {
00224 if {[file isdirectory $x] && ([file type $x] != "link" || $followlinks)} {
00225 if {[set more [glob -dir $x -nocomplain *]] != ""} {
00226 eval lappend files [recurseDirs $more $followlinks]
00227 } else {
00228 lappend files $x
00229 }
00230 }
00231 }
00232 return $files
00233 }
00234
00235 ret ::tar::writefile (type in , type out , type followlinks) {
00236 puts -nonewline $out [createHeader $in $followlinks]
00237 set size 0
00238 if {[file type $in] == "file" || ($followlinks && [file type $in] == "link")} {
00239 set in [::open $in]
00240 fconfigure $in -encoding binary -translation lf -eofchar {}
00241 set size [fcopy $in $out]
00242 close $in
00243 }
00244 puts -nonewline $out [string repeat \x00 [pad $size]]
00245 }
00246
00247 ret ::tar::create (type tar , type files , type args) {
00248 set dereference 0
00249 parseOpts {dereference 0} $args
00250
00251 set fh [::open $tar w+]
00252 fconfigure $fh -encoding binary -translation lf -eofchar {}
00253 foreach x [recurseDirs $files $dereference] {
00254 writefile $x $fh $dereference
00255 }
00256 puts -nonewline $fh [string repeat \x00 1024]
00257
00258 close $fh
00259 return $tar
00260 }
00261
00262 ret ::tar::add (type tar , type files , type args) {
00263 set dereference 0
00264 parseOpts {dereference 0} $args
00265
00266 set fh [::open $tar r+]
00267 fconfigure $fh -encoding binary -translation lf -eofchar {}
00268 seek $fh -1024 end
00269
00270 foreach x [recurseDirs $files $dereference] {
00271 writefile $x $fh $dereference
00272 }
00273 puts -nonewline $fh [string repeat \x00 1024]
00274
00275 close $fh
00276 return $tar
00277 }
00278
00279 ret ::tar::remove (type tar , type files) {
00280 set n 0
00281 while {[file exists $tar$n.tmp]} {incr n}
00282 set tfh [::open $tar$n.tmp w]
00283 set fh [::open $tar r]
00284
00285 fconfigure $fh -encoding binary -translation lf -eofchar {}
00286 fconfigure $tfh -encoding binary -translation lf -eofchar {}
00287
00288 while {![eof $fh]} {
00289 array set header [readHeader [read $fh 512]]
00290 if {$header(name) == ""} {
00291 puts -nonewline $tfh [string repeat \x00 1024]
00292 break
00293 }
00294 set name $header(prefix)$header(name)
00295 set len [expr {$header(size) + [pad $header(size)]}]
00296 if {[lsearch $files $name] > -1} {
00297 seek $fh $len current
00298 } else {
00299 seek $fh -512 current
00300 fcopy $fh $tfh -size [expr {$len + 512}]
00301 }
00302 }
00303
00304 close $fh
00305 close $tfh
00306
00307 file rename -force $tar$n.tmp $tar
00308 }
00309