#! /bin/sh
# Start hfssh ... \
exec hfssh "$0" "$@"

#
# hfsutils - tools for reading and writing Macintosh HFS volumes
# Copyright (C) 1996-1998 Robert Leslie
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# $Id: hfs.tcl,v 1.9 1998/08/31 04:27:18 rob Exp $
#

proc ctime {secs} {
    return [clock format $secs -format "%c"]
}

proc getvol {var} {
    global curvol
    upvar $var vol

    if {! [info exists curvol]} {
	error "no volume is current"
    }

    set vol $curvol
}

proc hinfo {} {
    getvol vol

    if {[$vol islocked]} {
	set locked " (locked)"
    } else {
	set locked ""
    }

    puts stdout "Volume name is \"[$vol vname]\"$locked"
    puts stdout "Volume was created on [ctime [$vol crdate]]"
    puts stdout "Volume was last modified on [ctime [$vol mddate]]"
    puts stdout "Volume has [lindex [$vol size] 1] bytes free"
}

proc hmount {path {partno ""}} {
    global mounts curpath curvol

    set nparts [hfs nparts $path]
    if {$nparts >= 0} {
	puts stdout [concat "$path contains $nparts HFS"  \
		[ternary {$nparts == 1} "partition" "partitions"]]
    }

    if {[string length $partno] == 0} {
	if {$nparts > 0} {
	    puts stderr "partition unspecified; selecting number 1"
	    set partno 1
	} elseif {$nparts == -1} {
	    set partno 0
	} else {
	    set partno 1
	}
    }

    set vol [hfs mount $path $partno]

    if {[info exists mounts($path)]} {
	humount $path
    }

    set curpath $path
    set curvol $vol
    set mounts($path) $vol

    hinfo
}

proc humount {{path {}}} {
    global mounts curpath curvol

    if {[string length $path] == 0} {
	if {! [info exists curpath]} {
	    error "no volume is current"
	}

	set path $curpath
    } elseif {! [info exists mounts($path)]} {
	error "$path not mounted"
    }

    set vol $mounts($path)
    unset mounts($path)

    if {[string compare $vol $curvol] == 0} {
	unset curpath
	unset curvol
    }

    $vol umount
}

proc hvol {name} {
    global mounts curpath curvol

    if {[info exists mounts($name)]} {
	set curpath $name
	set curvol $mounts($name)
	return
    }

    error "unknown volume"
}

proc hpwd {} {
    getvol vol

    return "[join [$vol path] ":"]:"
}

proc hcd {{path ""}} {
    getvol vol

    set globbed [$vol glob [list $path]]
    if {[llength $globbed] != 1} {
	error "$path: ambiguous path"
    }
    set path [lindex $globbed 0]

    if {[string length $path] == 0} {
	set path "[$vol vname]:"
    }

    $vol cd $path
}

proc timestr {secs} {
    set ctime [ctime $secs]

    return "[string range $ctime 4 15][string range $ctime 19 23]"
}

proc ternary {test true false} {
    if {[uplevel expr $test]} {
	return $true
    } else {
	return $false
    }
}

proc hdir {{path ":"}} {
    getvol vol

    set globbed [$vol glob [list $path]]
    if {[llength $globbed] != 1} {
	error "$path: ambiguous path"
    }
    set path [lindex $globbed 0]

    foreach ent [$vol dir $path] {
	array set item $ent

	if {$item(kind) == "directory"} {
	    puts stdout [format "d%s %9lu item%s               %s %s"  \
		    [ternary {[lsearch $item(flags) "invis"] >= 0} "i" " "]  \
		    $item(size)  \
		    [ternary {$item(size) == 1} " " "s"]  \
		    [timestr $item(mddate)]  \
		    $item(name)]
	} else {
	    puts stdout [format "%s%s %4s/%4s %9lu %9lu %s %s"  \
		    [ternary {[lsearch $item(flags) "locked"] >= 0} "F" "f"]  \
		    [ternary {[lsearch $item(flags) "invis"] >= 0} "i" " "]  \
		    $item(type)  \
		    $item(creator)  \
		    $item(rsize)  \
		    $item(dsize)  \
		    [timestr $item(mddate)]  \
		    $item(name)]
	}
    }
}

proc hstat {{path ":"}} {
    getvol vol

    set globbed [$vol glob [list $path]]
    if {[llength $globbed] != 1} {
	error "$path: ambiguous path"
    }
    set path [lindex $globbed 0]

    array set item [$vol stat $path]

    foreach elt [lsort [array names item]] {
	if {[regexp {date$} $elt]} {
	    set value [ctime $item($elt)]
	} else {
	    set value $item($elt)
	}

	puts stdout [format "%-10s %s" "$elt:" $value]
    }
}

proc hmkdir {args} {
    getvol vol

    foreach arg [$vol glob $args] {
	$vol mkdir $arg
    }
}

proc hrmdir {args} {
    getvol vol

    foreach arg [$vol glob $args] {
	$vol rmdir $arg
    }
}

proc hcreate {path {type "TEXT"} {creator "UNIX"}} {
    getvol vol

    set file [$vol create $path $type $creator]
    $file close
}

proc htouch {args} {
    getvol vol

    foreach arg [$vol glob $args] {
	if [catch {$vol touch $arg}] {
	    hcreate $arg
	}
    }
}

proc hdel {args} {
    getvol vol

    foreach arg [$vol glob $args] {
	$vol delete $arg
    }
}

proc hrename {src dst} {
    getvol vol

    set globbed [$vol glob [list $src]]
    if {[llength $globbed] != 1} {
	error "$src: ambiguous path"
    }
    set src [lindex $globbed 0]

    $vol rename $src $dst
}

proc hbless {path} {
    getvol vol

    set globbed [$vol glob [list $path]]
    if {[llength $globbed] != 1} {
	error "$path: ambiguous path"
    }
    set path [lindex $globbed 0]

    $vol bless $path
}

proc hcat {path} {
    getvol vol

    set globbed [$vol glob [list $path]]
    if {[llength $globbed] != 1} {
	error "$path: ambiguous path"
    }
    set path [lindex $globbed 0]

    set file [$vol open $path]

    while {1} {
	set buf [$file read 512]
	if {[string length $buf] == 0} {
	    $file close
	    break
	}

	regsub -all "\r" $buf "\n" buf

	puts -nonewline stdout $buf
    }
}

proc hcopyout {path {dest "."} {mode ""}} {
    getvol vol

    set globbed [$vol glob [list $path]]
    if {[llength $globbed] != 1} {
	error "$path: ambiguous path"
    }
    set path [lindex $globbed 0]

    if {[string length $mode] == 0} {
	array set item [$vol stat $path]

	if {$item(kind) == "directory"} {
	    error "can't copy whole directories"
	} elseif {[regexp {^TEXT|ttro$} $item(type)]} {
	    set mode text
	} else {
	    set mode macb
	}
    }

    $vol copyout $mode $path $dest
}

proc hcopyin {path {dest ":"} {mode ""}} {
    getvol vol

    set globbed [$vol glob [list $path]]
    if {[llength $globbed] != 1} {
	error "$path: ambiguous path"
    }
    set path [lindex $globbed 0]

    if {[string length $mode] == 0} {
	if {[regexp {\.bin$} $path]} {
	    set mode macb
	} elseif {[regexp {\.hqx$} $path]} {
	    set mode binh
	} elseif {[regexp {\.(txt|c|h)$} $path]} {
	    set mode text
	} elseif {[regexp {\.(sit|sea|cpt|tar|gz|Z|gif|jpg)$} $path]} {
	    set mode raw
	} elseif {[catch {exec file -L $path} type] == 0 &&  \
		[regexp {text} $type]} {
	    set mode text
	} else {
	    set mode raw
	}
    }

    $vol copyin $mode $path $dest
}

proc hformat {path {partno 0} {vname "Untitled"} {badblocks {}}} {
    global mounts

    if {[info exists mounts($path)]} {
	humount $path
    }

    hfs format $path $partno $vname $badblocks

    hmount $path $partno
}

###############################################################################

proc help {{what ""}} {
    if {[string length $what]} {
	if {[catch {info args h$what} msg]} {
	    puts stdout "Sorry, no help for \"$what\""
	} else {
	    puts stdout "$what $msg"
	}
    } else {
	puts stdout {
info		Show current volume information
mount		Open a new HFS volume
umount		Close an HFS volume
vol		Select an open volume
pwd		Show the current working directory path
cd		Change the current working directory
dir		Show a directory listing
stat		Show details about a given path
mkdir		Create a new directory
rmdir		Delete an empty directory
create		Create an empty file
touch		Update modification date or create a file
del		Delete a file
rename		Rename a file, directory, or volume
cat		Display file's data fork contents
copyout		Copy a complete file out into the native filesystem
copyin		Copy a MacBinary/BinHex/text/raw file into HFS
format		Create an empty HFS volume
}
    }
}

proc ? {args} {
    eval help $args
}

###############################################################################

proc version {} {
    puts stdout "[hfs version] - [hfs copyright]"
}

proc license {} {
    puts -nonewline stdout "\n[hfs license]"
}

proc author {} {
    puts stdout [hfs author]
}

if {[string compare [lindex $argv 0] "--license"] == 0} {
    license
    exit
}

version

if {[string compare [lindex $argv 0] "--version"] == 0} {
    puts stdout "`$argv0 --license' for licensing information."
    exit
}

puts stdout "This is free software but comes with ABSOLUTELY NO WARRANTY."
if {$hfs_interactive} {
    puts stdout "Type `license' for details."
}
puts stdout ""

###############################################################################

proc echo {args} {
    puts stdout [join $args " "]
}

proc quit {} {
    exit
}

###############################################################################

# Apparently some shells don't grok "$@" correctly
if {$argc == 1 && [string length [lindex $argv 0]] == 0} {
    incr argc -1
    set argv [lreplace $argv 0 0]
}

if {$argc > 0} {
    eval hmount $argv
}

while {1} {
    if {$hfs_interactive} {
	puts -nonewline stdout "hfs> "
	flush stdout
    }

    if {[gets stdin line] == -1} {
	exit
    }

    while {! [info complete $line]} {
	if {[gets stdin more] == -1} {
	    break
	} else {
	    set line "$line$more"
	}
    }

    if {[string length [info procs "h[lindex $line 0]"]] > 0} {
	set result [catch {eval h$line} msg]
    } else {
	set result [catch {eval $line} msg]
    }

    if {[string length $msg] > 0} {
	if {$result == 1} {
	    puts stdout "Error: $msg"
	    if {! $hfs_interactive} {
		exit 1
	    }
	} else {
	    puts stdout $msg
	}
    }
}
