mk4tcl.tcl at [fc9a2d4694]

File kitsh/buildsrc/kitsh-0.0/mk4tcl.tcl artifact 320b85c042 part of check-in fc9a2d4694


#! /usr/bin/env tclsh
# ReadKit, a viewer/extractor/converter for starkits which does not
# require TclKit or MetaKit.  This file was generated by "rkgen.tcl".
#
# June 2002, Jean-Claude Wippler <jcw@equi4.com>

# this is needed so often that I just drop copies of it all over the place
if {![info exists auto_index(lassign)] && [info commands lassign] == ""} {
  proc lassign {l args} {
    foreach v $l a $args { uplevel 1 [list set $a $v] }
  }
}

catch {
	load {} zlib
}
catch {
	package require zlib
}

if {[info comm mmap] == ""} {
    # mmap and mvec primitives in pure Tcl (a C version is present in critlib)

    namespace export mmap mvec

    namespace eval v {
	array set mmap_data {}
	array set mvec_shifts {
    - -1    0 -1
    1  0    2  1    4  2    8   3
    16 4   16r 4
    32 5   32r 5   32f 5   32fr 5
    64 6   64r 6   64f 6   64fr 6 }
    }

    proc mmap {fd args} {
	upvar #0 v::mmap_data($fd) data
	# special case if fd is the name of a variable (qualified or global)
	if {[uplevel #0 [list info exists $fd]]} {
	    upvar #0 $fd var
	    set data $var
	}
	# cache a full copy of the file to simulate memory mapping
	if {![info exists data]} {
	    set pos [tell $fd]
	    seek $fd 0 end
	    set end [tell $fd]
	    seek $fd 0
	    set trans [fconfigure $fd -translation]
	    fconfigure $fd -translation binary
	    set data [read $fd $end]
	    fconfigure $fd -translation $trans
	    seek $fd $pos
	}
	set total [string length $data]
	if {[llength $args] == 0} {
	    return $total
	}
	foreach {off len} $args break
	if {$len < 0} {
	    set len $total
	}
	if {$len < 0 || $len > $total - $off} {
	    set len [expr {$total - $off}]
	}
	binary scan $data @${off}a$len s
	return $s
    }

    proc mvec {v args} {
	foreach {mode data off len} $v break
	if {[info exists v::mvec_shifts($mode)]} {
	    # use _mvec_get to access elements
	    set shift $v::mvec_shifts($mode)
	    if {[llength $v] < 4} {
		set len $off
	    }
	    set get [list _mvec_get $shift $v *]
	} else {
	    # virtual mode, set to evaluate script
	    set shift ""
	    set len [lindex $v end]
	    set get $v
	}
	# try to derive vector length from data length if not specified
	if {$len == "" || $len < 0} {
	    set len 0
	    if {$shift >= 0} {
		if {[llength $v] < 4} {
		    set n [string length $data]
		} else {
		    set n [mmap $data]
		}
		set len [expr {($n << 3) >> $shift}]
	    }
	}
	set nargs [llength $args]
	# with just a varname as arg, return info about this vector
	if {$nargs == 0} {
	    if {$shift == ""} {
		return [list $len {} $v]
	    }
	    return [list $len $mode $shift]
	}
	foreach {pos count pred cond} $args break
	# with an index as second arg, do a single access and return element
	if {$nargs == 1} {
	    return [uplevel 1 [lreplace $get end end $pos]]
	}
	if {$count < 0} {
	    set count $len
	}
	if {$count > $len - $pos && $shift != -1} {
	    set count [expr {$len - $pos}]
	}
	if {$nargs == 4} {
	    upvar $pred x
	}
	set r {}
	incr count $pos
	# loop through specified range to build result vector
	# with four args, used that as predicate function to filter
	# with five args, use fourth as loop var and apply fifth as condition
	for {set x $pos} {$x < $count} {incr x} {
	    set y [uplevel 1 [lreplace $get end end $x]]
	    switch $nargs {
		3 {
			if {![uplevel 1 [list $pred $v $x $y]]} continue
		    }
		4 {
			if {![uplevel 1 [list expr $cond]]} continue
		    }
	    }
	    lappend r $y
	}
	return $r
    }

    proc _mvec_get {shift desc index} {
	foreach {mode data off len} $desc break
	switch -- $mode {
	    - {
		    return $index
		}
	    0 {
		    return $data
		}
	}
	if {[llength $desc] < 4} {
	    set off [expr {($index << $shift) >> 3}]
	} else {
	    # don't load more than 8 bytes from the proper offset
	    incr off [expr {($index << $shift) >> 3}]
	    set data [mmap $data $off 8]
	    set off 0
	}
	switch -- $mode {
	    1 {
		    binary scan $data @${off}c value
		    return [expr {($value>>($index&7)) &1}]
		}
	    2 {
		    binary scan $data @${off}c value
		    return [expr {($value>>(($index&3) <<1)) &3}]
		}
	    4 {
		    binary scan $data @${off}c value
		    return [expr {($value>>(($index&1) <<2)) &15}]
		}
	    8 {
		    set w 1
		    set f c
		}
	    16 {
		    set w 2
		    set f s
		}
	    16r {
		    set w 2
		    set f S
		}
	    32 {
		    set w 4
		    set f i
		}
	    32r {
		    set w 4
		    set f I
		}
	    32fr -
	    32f {
		    set w 4
		    set f f
		}
	    64 -
	    64r {
		    set w 8
		    set f i2
		}
	    64fr -
	    64f {
		    set w 8
		    set f d
		}
	}

	binary scan $data @$off$f value
	return $value
    }

    # vim: ft=tcl

}

if {[info comm dbopen] == ""} {
    # Decoder for MetaKit datafiles in Tcl

    # requires mmap/mvec primitives:
    #source [file join [info dirname [info script]] mvprim.tcl]

    namespace export dbopen dbclose dbtree access vnames vlen

    namespace eval v {
	variable widths {
    {8 16  1 32  2  4}
    {4  8  1 16  2  0}
    {2  4  8  1  0 16}
    {2  4  0  8  1  0}
    {1  2  4  0  8  0}
    {1  2  4  0  0  8}
    {1  2  0  4  0  0} }
    }

    proc fetch {file} {
	if {$file == ""} {
	    error "temp storages not supported"
	}
	set v::data [open $file]
	set v::seqn 0
    }

    proc byte_seg {off len} {
	incr off $v::zero
	return [mmap $v::data $off $len]
    }

    proc int_seg {off cnt} {
	set vec [list 32r [byte_seg $off [expr {4*$cnt}]]]
	return [mvec $vec 0 $cnt]
    }

    proc get_s {len} {
	set s [byte_seg $v::curr $len]
	incr v::curr $len
	return $s
    }

    proc get_v {} {
	set v 0
	while 1 {
	    set char [mvec $v::byte $v::curr]
	    incr v::curr
	    set v [expr {$v*128+($char&0xff)}]
	    if {$char < 0} {
		return [incr v -128]
	    }
	}
    }

    proc get_p {rows vs vo} {
	upvar $vs size $vo off
	set off 0
	if {$rows == 0} {
	    set size 0
	} else {
	    set size [get_v]
	    if {$size > 0} {
		set off [get_v]
	    }
	}
    }

    proc header {{end ""}} {
	set v::zero 0
	if {$end == ""} {
	    set end [mmap $v::data]
	}
	set v::byte [list 8 $v::data $v::zero $end]
	lassign [int_seg [expr {$end-16}] 4] t1 t2 t3 t4
	set v::zero [expr {$end-$t2-16}]
	incr end -$v::zero
	set v::byte [list 8 $v::data $v::zero $end]
	lassign [int_seg 0 2] h1 h2
	lassign [int_seg [expr {$h2-8}] 2] e1 e2
	set v::info(mkend) $h2
	set v::info(mktoc) $e2
	set v::info(mklen) [expr {$e1 & 0xffffff}]
	set v::curr $e2
    }

    proc layout {fmt} {
	regsub -all { } $fmt "" fmt
	regsub -all {(\w+)\[} $fmt "{\\1 {" fmt
	regsub -all {\]} $fmt "}}" fmt
	regsub -all {,} $fmt " " fmt
	return $fmt
    }

    proc descparse {desc} {
	set names {}
	set types {}
	foreach x $desc {
	    if {[llength $x] == 1} {
		lassign [split $x :] name type
		if {$type == ""} {
		    set type S
		}
	    } else {
		lassign $x name type
	    }
	    lappend names $name
	    lappend types $type
	}
	return [list $names $types]
    }

    proc numvec {rows type} {
	get_p $rows size off
	if {$size == 0} {
	    return {0 0}
	}
	set w [expr {int(($size<<3) /$rows)}]
	if {$rows <= 7 && 0 < $size && $size <= 6} {
	    set w [lindex [lindex $v::widths [expr {$rows-1}]] [expr {$size-1}]]
	}
	if {$w == 0} {
	    error "numvec?"
	}
	switch $type\
	      F {
		    set w 32f
		}\
	      D {
		    set w 64f
		}
	incr off $v::zero
	return [list $w $v::data $off $rows]
    }

    proc lazy_str {self rows type pos sizes msize moff index} {
	set soff {}
	for {set i 0} {$i < $rows} {incr i} {
	    set n [mvec $sizes $i]
	    lappend soff $pos
	    incr pos $n
	}
	if {$msize > 0} {
	    set slen [mvec $sizes 0 $rows]
	    set v::curr $moff
	    set limit [expr {$moff+$msize}]
	    for {set row 0} {$v::curr < $limit} {incr row} {
		incr row [get_v]
		get_p 1 ms mo
		set soff [lreplace $soff $row $row $mo]
		set slen [lreplace $slen $row $row $ms]
	    }
	    set sizes [list lindex $slen $rows]
	}
	if {$type == "S"} {
	    set adj -1
	} else {
	    set adj 0
	}
	set v::node($self) [list get_str $soff $sizes $adj $rows]
	return [mvec $v::node($self) $index]
    }

    proc get_str {soff sizes adj index} {
	set n [mvec $sizes $index]
	return [byte_seg [lindex $soff $index] [incr n $adj]]
    }

    proc lazy_sub {self desc size off rows index} {
	set v::curr $off
	lassign [descparse $desc] names types
	set subs {}
	for {set i 0} {$i < $rows} {incr i} {
	    if {[get_v] != 0} {
		error "lazy_sub?"
	    }
	    lappend subs [prepare $types]
	}
	set v::node($self) [list get_sub $names $subs $rows]
	return [mvec $v::node($self) $index]
    }

#proc backtrace {{level_adj 0}} {
#                        set ret [list]          
#
#                        set level [expr 0 - $level_adj]
#                        for {set i [expr [info level] - $level_adj]} {$i > 1} {incr i -1} {
#                                incr level -1
#                                set ret [linsert $ret 0 [lindex [info level $level] 0]]
#                        }
#                        set ret [linsert $ret 0 GLOBAL]
#        
#                        return $ret
#}

    proc get_sub {names subs index} {
#puts stderr "DEBUG: get_sub: [list $names $subs $index]"
#puts "backtrace: [backtrace]"
	lassign [lindex $subs $index] rows handlers
	return [list get_view $names $rows $handlers $rows]
    }

    proc prepare {types} {
	set r [get_v]
	set handlers {}
	foreach x $types {
	    set n [incr v::seqn]
	    lappend handlers $n
	    switch $x {
		I -
		L -
		F -
		D {
			set v::node($n) [numvec $r $x]
		    }
		B -
		S {
			get_p $r size off
			set sizes {0 0}
			if {$size > 0} {
			    set sizes [numvec $r I]
			}
			get_p $r msize moff
			set v::node($n) [list lazy_str $n $r $x $off $sizes\
			  $msize $moff $r]
		    }
		default {
			get_p $r size off
			set v::node($n) [list lazy_sub $n $x $size $off $r $r]
		    }
	    }
	}
	return [list $r $handlers]
    }

    proc get_view {names rows handlers index} {
	return [list get_prop $names $rows $handlers $index [llength $names]]
    }

    proc get_prop {names rows handlers index ident} {
	set col [lsearch -exact $names $ident]
	if {$col < 0} {
	    error "unknown property: $ident"
	}
	set h [lindex $handlers $col]
	set ret [mvec $v::node($h) $index]

	return $ret
    }

    proc dbopen {db file} {
	# open datafile, stores datafile descriptors and starts building tree
	if {$db == ""} {
	    set r {}
	    foreach {k v} [array get v::dbs] {
		lappend r $k [lindex $v 0]
	    }
	    return $r
	}
	fetch $file
	header
	if {[get_v] != 0} {
	    error "dbopen?"
	}
	set desc [layout [get_s [get_v]]]
	lassign [descparse $desc] names types
	set root [get_sub $names [list [prepare $types]] 0]
	set v::dbs($db) [list $file $v::data $desc [mvec $root 0]]
	return $db
    }

    proc dbclose {db} {
	# close datafile, get rid of stored info
	unset v::dbs($db)
	set v::data "" ;# it may be big 
    }

    proc dbtree {db} {
	# datafile selection, first step in access navigation loop
	return [lindex $v::dbs($db) 3]
    }

    proc access {spec} {
	# this is the main access navigation loop
	set s [split $spec ".!"]
	set x [list dbtree [array size v::dbs]]
	foreach y $s {
	    set x [mvec $x $y]
	}
	return $x
    }

    proc vnames {view} {
	# return a list of property names
	if {[lindex $view 0] != "get_view"} {
	    error "vnames?"
	}
	return [lindex $view 1]
    }

    proc vlen {view} {
	# return the number of rows in this view
	if {[lindex $view 0] != "get_view"} {
	    error "vlen?"
	}
	return [lindex $view 2]
    }

    # vim: ft=tcl

}

if {[info comm mk_file] == ""} {
    # Compatibility layer for MetaKit

    # requires dbopen/dbclose/dbtree/access/vnames/vlen/mvec primitives
    #source [file join [info dirname [info script]] decode.tcl]

    namespace export mk_*

    proc mk_file {cmd args} {
#set indent [string repeat "    " [info level]]
#puts stderr "${indent}DEBUG: readkit::file $cmd $args"
	lassign $args db file
	switch $cmd {
	    open {
		    return [dbopen $db $file]
		}
	    close {
		    dbclose $db
		}
	    views {
		    return [vnames [dbtree $db]]
		}
	    commit {

		}
	    default {
		    error "mk_file $cmd?"
		}
	}
    }

    proc mk_view {cmd path args} {
#set indent [string repeat "    " [info level]]
#puts stderr "${indent}DEBUG: readkit::view $cmd $path $args"
	lassign $args a1
	switch $cmd {
	    info {
		    return [vnames [access $path]]
		}
	    layout {
		    set layout "NOTYET"
		    if {[llength $args] > 0 && $layout != $a1} {
			#error "view restructuring not supported"
		    }
		    return $layout
		}
	    size {
		    set len [vlen [access $path]]
		    if {[llength $args] > 0 && $len != $a1} {
			error "view resizing not supported"
		    }
		    return [vlen [access $path]]
		}
	    default {
		    error "mk_view $cmd?"
		}
	}
    }

    proc mk_cursor {cmd cursor args} {
#set indent [string repeat "    " [info level]]
#puts stderr "${indent}DEBUG: readkit::cursor $cmd $cursor $args"
	upvar $cursor v
	switch $cmd {
	    create {
		    NOTYET
		}
	    incr {
		    NOTYET
		}
	    pos -
	    position {
		    if {$args != ""} {
			regsub {!-?\d+$} $v {} v
			append v !$args
			return $args
		    }
		    if {![regexp {\d+$} $v n]} {
			set n -1
		    }
		    return $n
		}
	    default {
		    error "mk_cursor $cmd?"
		}
	}
    }

    proc mk_get {path args} {
#set indent [string repeat "    " [info level]]
#puts stderr "${indent}DEBUG: readkit::get $path $args"
	set rowref [access $path]
	set sized 0
	if {[lindex $args 0] == "-size"} {
	    set sized 1
	    set args [lrange $args 1 end]
	}
	set ids 0
	if {[llength $args] == 0} {
	    set args [vnames $rowref]
	    set ids 1
	}
	set r {}
	foreach x $args {
	    if {$ids} {
		lappend r $x
	    }
	    set v [mvec $rowref $x]
if {[string range $v 0 8] == "get_view "} {
# XXX: ?!?!?: TODO: FIX
set v 1
}
	    if {$sized} {
		lappend r [string length $v]
	    } else {
		lappend r $v
	    }
	}
	if {[llength $args] == 1} {
	    set r [lindex $r 0]
	}

	return $r
    }

    proc mk_loop {cursor path args} {
#set indent [string repeat "    " [info level]]
#puts stderr "${indent}DEBUG: readkit::loop $cursor $path ..."
	upvar $cursor v
	if {[llength $args] == 0} {
	    set args [list $path]
	    set path $v
	    regsub {!-?\d+$} $path {} path
	}
	lassign $args a1 a2 a3 a4
	set rowref [access $path]
	set first 0
	set limit [vlen $rowref]
	set step 1
	switch [llength $args] {
	    1 {
		    set body $a1
		}
	    2 {
		    set first $a1
		    set body $a2
		}
	    3 {
		    set first $a1
		    set limit $a2
		    set body $a3
		}
	    4 {
		    set first $a1
		    set limit $a2
		    set step $a3
		    set body $a4
		}
	    default {
		    error "mk_loop arg count?"
		}
	}
	set code 0
	for {set i $first} {$i < $limit} {incr i $step} {
	    set v $path!$i
	    set code [catch [list uplevel 1 $body] err]
	    switch $code {
		1 -
		2 {
			return -code $code $err
		    }
		3 {
			break
		    }
	    }
	}
    }

    proc mk_select {path args} {
#set indent [string repeat "    " [info level]]
#puts stderr "${indent}DEBUG: readkit::select $path $args"
	# only handle the simplest case: exact matches
	if {[lindex $args 0] == "-count"} {
		set maxitems [lindex $args 1]
		set args [lrange $args 2 end]
	}

	set currmatchmode "caseinsensitive"

	set keys {}
	set value {}
	set matchmodes {}
	for {set idx 0} {$idx < [llength $args]} {incr idx 2} {
		switch -glob -- [lindex $args $idx] {
			"-glob" {
				set currmatchmode "glob"
				incr idx -1
				continue
			}
			"-*" {
				error "Unhandled option: [lindex $args $idx]"
			}
		}

		set k [lindex $args $idx]
		set v [lindex $args [expr {$idx+1}]]

		lappend keys $k
		lappend values $v
		lappend matchmodes $currmatchmode
	}
	set r {}
	mk_loop c $path {
		set x [eval mk_get $c $keys]
		set matchCnt 0
		for {set idx 0} {$idx < [llength $x]} {incr idx} {
			set val [lindex $values $idx]
			set chkval [lindex $x $idx]
			set matchmode [lindex $matchmodes $idx]

			switch -- $matchmode {
				"caseinsensitive" {
					if {$val == $chkval} {
						incr matchCnt
					}
				}
				"glob" {
					if {[string match $val $chkval]} {
						incr matchCnt
					}
				}
			}

		}
		if {$matchCnt == [llength $keys]} {
			lappend r [mk_cursor position c]
		}
	}

	if {[info exists maxitems]} {
		set r [lrange $r 0 [expr $maxitems - 1]]
	}

	return $r
    }

    proc mk__rechan {path prop cmd chan args} {
#set indent [string repeat "    " [info level]]
#puts stderr "${indent}DEBUG: readkit::_rechan $path $prop $cmd $chan $args"

        set key [list $path $prop]
        if {![info exists ::mk__cache($key)]} {
          set ::mk__cache($key) [readkit::get $path $prop]
        }
        if {![info exists ::mk__offset($key)]} {
          set ::mk__offset($key) 0
        }
        set data $::mk__cache($key)
        set offset $::mk__offset($key)

        switch -- $cmd {
            "read" {
                set count [lindex $args 0]
                set retval [string range $data $offset [expr {$offset + $count - 1}]]

                set readbytes [string length $retval]

                incr offset $readbytes
            }
            "close" {
                unset -nocomplain ::mk__cache($key)
                unset -nocomplain ::mk__offset($key)
                return
            }
            default {
#puts stderr "${indent}DEBUG: readkit::_rechan: Called for cmd $cmd"
                return -code error "Not implemented: cmd = $cmd"
            }
        }

        set ::mk__offset($key) $offset

	return $retval
    }

    proc mk_channel {path prop {mode "r"}} {
#set indent [string repeat "    " [info level]]
#puts stderr "${indent}DEBUG: readkit::channel $path $prop $mode"
	set fd [rechan [list mk__rechan $path $prop] 2]

	return $fd
    }
    # vim: ft=tcl

}

# set up the MetaKit compatibility definitions
foreach x {file view cursor get loop select channel} {
    interp alias {} ::readkit::$x {} ::mk_$x
}



# mk4vfs.tcl -- Mk4tcl Virtual File System driver
# Copyright (C) 1997-2003 Sensus Consulting Ltd. All Rights Reserved.
# Matt Newman <matt@sensus.org> and Jean-Claude Wippler <jcw@equi4.com>
#
# $Id: mk4vfs.tcl,v 1.41 2008/04/15 21:11:53 andreas_kupries Exp $
#
# 05apr02 jcw	1.3	fixed append mode & close,
#			privatized memchan_handler
#			added zip, crc back in
# 28apr02 jcw	1.4	reorged memchan and pkg dependencies
# 22jun02 jcw	1.5	fixed recursive dir deletion
# 16oct02 jcw	1.6	fixed periodic commit once a change is made
# 20jan03 jcw	1.7	streamed zlib decompress mode, reduces memory usage
# 01feb03 jcw	1.8	fix mounting a symlink, cleanup mount/unmount procs
# 04feb03 jcw	1.8	whoops, restored vfs::mkcl::Unmount logic
# 17mar03 jcw	1.9	start with mode translucent or readwrite
# 18oct05 jcw	1.10	add fallback to MK Compatible Lite driver (vfs::mkcl)

# Removed provision of the backward compatible name. Moved to separate
# file/package.
catch {
	load {} vfs
}
package require vfs

# things that can no longer really be left out (but this is the wrong spot!)
# be as non-invasive as possible, using these definitions as last resort

namespace eval vfs::mkcl {
    proc Mount {mkfile local args} {
	if {$mkfile != ""} {
	  # dereference a symlink, otherwise mounting on it fails (why?)
	  catch {
	    set mkfile [file join [file dirname $mkfile] \
	    			  [file readlink $mkfile]]
	  }
	  set mkfile [file normalize $mkfile]
	}
	set db [eval [list ::mkcl_vfs::_mount $mkfile] $args]
	::vfs::filesystem mount $local [list ::vfs::mkcl::handler $db]
	::vfs::RegisterMount $local [list ::vfs::mkcl::Unmount $db]
	return $db
    }

    proc Unmount {db local} {
	vfs::filesystem unmount $local
	::mkcl_vfs::_umount $db
    }

    proc attributes {db} { return [list "state" "commit"] }
    
    # Can use this to control commit/nocommit or whatever.
    # I'm not sure yet of what functionality jcw needs.
    proc commit {db args} {
	switch -- [llength $args] {
	    0 {
		if {$::mkcl_vfs::v::mode($db) == "readonly"} {
		    return 0
		} else {
		    # To Do: read the commit state
		    return 1
		}
	    }
	    1 {
		set val [lindex $args 0]
		if {$val != 0 && $val != 1} {
		    return -code error \
		      "invalid commit value $val, must be 0,1"
		}
		# To Do: set the commit state.
	    }
	    default {
		return -code error "Wrong num args"
	    }
	}
    }
    
    proc state {db args} {
	switch -- [llength $args] {
	    0 {
		return $::mkcl_vfs::v::mode($db)
	    }
	    1 {
		set val [lindex $args 0]
		if {[lsearch -exact [::vfs::states] $val] == -1} {
		    return -code error \
		      "invalid state $val, must be one of: [vfs::states]"
		}
		set ::mkcl_vfs::v::mode($db) $val
		::mkcl_vfs::setupCommits $db
	    }
	    default {
		return -code error "Wrong num args"
	    }
	}
    }
    
    proc handler {db cmd root relative actualpath args} {
	#puts stderr "handler: $db - $cmd - $root - $relative - $actualpath - $args"
	if {$cmd == "matchindirectory"} {
	    eval [list $cmd $db $relative $actualpath] $args
	} elseif {$cmd == "fileattributes"} {
	    eval [list $cmd $db $root $relative] $args
	} else {
	    eval [list $cmd $db $relative] $args
	}
    }

    proc utime {db path actime modtime} {
	::mkcl_vfs::stat $db $path sb
	
	if { $sb(type) == "file" } {
	    readkit::set $sb(ino) date $modtime
	}
    }

    proc matchindirectory {db path actualpath pattern type} {
	set newres [list]
	if {![string length $pattern]} {
	    # check single file
	    if {[catch {access $db $path 0}]} {
		return {}
	    }
	    set res [list $actualpath]
	    set actualpath ""
	} else {
	    set res [::mkcl_vfs::getdir $db $path $pattern]
	}
	foreach p [::vfs::matchCorrectTypes $type $res $actualpath] {
	    lappend newres [file join $actualpath $p]
	}
	return $newres
    }

    proc stat {db name} {
	::mkcl_vfs::stat $db $name sb

	set sb(ino) 0
	array get sb
    }

    proc access {db name mode} {
	if {$mode & 2} {
	    if {$::mkcl_vfs::v::mode($db) == "readonly"} {
		vfs::filesystem posixerror $::vfs::posix(EROFS)
	    }
	}
	# We can probably do this more efficiently, can't we?
	::mkcl_vfs::stat $db $name sb
    }

    proc open {db file mode permissions} {
	# return a list of two elements:
	# 1. first element is the Tcl channel name which has been opened
	# 2. second element (optional) is a command to evaluate when
	#  the channel is closed.
	switch -glob -- $mode {
	    {}  -
	    r {
		::mkcl_vfs::stat $db $file sb

		if { $sb(csize) != $sb(size) } {
		    if {$::mkcl_vfs::zstreamed} {
		      set fd [readkit::channel $sb(ino) contents r]
		      fconfigure $fd -translation binary
		      set fd [vfs::zstream decompress $fd $sb(csize) $sb(size)]
		    } else {
		      set fd [vfs::memchan]
		      fconfigure $fd -translation binary
		      set s [readkit::get $sb(ino) contents]
		      puts -nonewline $fd [vfs::zip -mode decompress $s]

		      fconfigure $fd -translation auto
		      seek $fd 0
		    }
		} elseif { $::mkcl_vfs::direct } {
		    set fd [vfs::memchan]
		    fconfigure $fd -translation binary
		    puts -nonewline $fd [readkit::get $sb(ino) contents]

		    fconfigure $fd -translation auto
		    seek $fd 0
		} else {
		    set fd [readkit::channel $sb(ino) contents r]
		}
		return [list $fd]
	    }
	    a {
		if {$::mkcl_vfs::v::mode($db) == "readonly"} {
		    vfs::filesystem posixerror $::vfs::posix(EROFS)
		}
		if { [catch {::mkcl_vfs::stat $db $file sb }] } {
		    # Create file
		    ::mkcl_vfs::stat $db [file dirname $file] sb
		    set tail [file tail $file]
		    set fview $sb(ino).files
		    if {[info exists mkcl_vfs::v::fcache($fview)]} {
			lappend mkcl_vfs::v::fcache($fview) $tail
		    }
		    set now [clock seconds]
		    set sb(ino) [readkit::row append $fview \
			    name $tail size 0 date $now ]

		    if { [string match *z* $mode] || $mkcl_vfs::compress } {
			set sb(csize) -1  ;# HACK - force compression
		    } else {
			set sb(csize) 0
		    }
		}

		set fd [vfs::memchan]
		fconfigure $fd -translation binary
		set s [readkit::get $sb(ino) contents]

		if { $sb(csize) != $sb(size) && $sb(csize) > 0 } {
		    append mode z
		    puts -nonewline $fd [vfs::zip -mode decompress $s]
		} else {
		    if { $mkcl_vfs::compress } { append mode z }
		    puts -nonewline $fd $s
		    #set fd [readkit::channel $sb(ino) contents a]
		}
		fconfigure $fd -translation auto
		seek $fd 0 end
		return [list $fd [list mkcl_vfs::do_close $db $fd $mode $sb(ino)]]
	    }
	    w*  {
		if {$::mkcl_vfs::v::mode($db) == "readonly"} {
		    vfs::filesystem posixerror $::vfs::posix(EROFS)
		}
		if { [catch {::mkcl_vfs::stat $db $file sb }] } {
		    # Create file
		    ::mkcl_vfs::stat $db [file dirname $file] sb
		    set tail [file tail $file]
		    set fview $sb(ino).files
		    if {[info exists mkcl_vfs::v::fcache($fview)]} {
			lappend mkcl_vfs::v::fcache($fview) $tail
		    }
		    set now [clock seconds]
		    set sb(ino) [readkit::row append $fview \
			    name $tail size 0 date $now ]
		}

		if { [string match *z* $mode] || $mkcl_vfs::compress } {
		    append mode z
		    set fd [vfs::memchan]
		} else {
		    set fd [readkit::channel $sb(ino) contents w]
		}
		return [list $fd [list mkcl_vfs::do_close $db $fd $mode $sb(ino)]]
	    }
	    default   {
		error "illegal access mode \"$mode\""
	    }
	}
    }

    proc createdirectory {db name} {
	mkcl_vfs::mkdir $db $name
    }

    proc removedirectory {db name recursive} {
	mkcl_vfs::delete $db $name $recursive
    }

    proc deletefile {db name} {
	mkcl_vfs::delete $db $name
    }

    proc fileattributes {db root relative args} {
	switch -- [llength $args] {
	    0 {
		# list strings
		return [::vfs::listAttributes]
	    }
	    1 {
		# get value
		set index [lindex $args 0]
		return [::vfs::attributesGet $root $relative $index]

	    }
	    2 {
		# set value
		if {$::mkcl_vfs::v::mode($db) == "readonly"} {
		    vfs::filesystem posixerror $::vfs::posix(EROFS)
		}
		set index [lindex $args 0]
		set val [lindex $args 1]
		return [::vfs::attributesSet $root $relative $index $val]
	    }
	}
    }
}

namespace eval mkcl_vfs {
    variable compress 1     ;# HACK - needs to be part of "Super-Block"
    variable flush    5000  ;# Auto-Commit frequency
    variable direct   0	    ;# read through a memchan, or from Mk4tcl if zero
    variable zstreamed 1    ;# decompress on the fly (needs zlib 1.1)

    namespace eval v {
	variable seq      0
	variable mode	    ;# array key is db, value is mode 
	             	     # (readwrite/translucent/readonly)
	variable timer	    ;# array key is db, set to afterid, periodicCommit

	array set cache {}
	array set fcache {}

	array set mode {exe translucent}
    }

    proc init {db} {
	readkit::view layout $db.dirs \
		{name:S parent:I {files {name:S size:I date:I contents:M}}}

	if { [readkit::view size $db.dirs] == 0 } {
	    readkit::row append $db.dirs name <root> parent -1
	}
    }

    proc _mount {{file ""} args} {
	set db mk4vfs[incr v::seq]

	if {$file == ""} {
	    readkit::file open $db
	    init $db
	    set v::mode($db) "translucent"
	} else {
	    eval [list readkit::file open $db $file] $args
	    
	    init $db
	    
	    set mode 0
	    foreach arg $args {
		switch -- $arg {
		    -readonly   { set mode 1 }
		    -nocommit   { set mode 2 }
		}
	    }
	    if {$mode == 0} {
		periodicCommit $db
	    }
	    set v::mode($db) [lindex {translucent readwrite readwrite} $mode]
	}
	return $db
    }

    proc periodicCommit {db} {
	variable flush
	set v::timer($db) [after $flush [list ::mkcl_vfs::periodicCommit $db]]
	readkit::file commit $db
	return ;# 2005-01-20 avoid returning a value
    }

    proc _umount {db args} {
	catch {after cancel $v::timer($db)}
	array unset v::mode $db
	array unset v::timer $db
	array unset v::cache $db,*
	array unset v::fcache $db.*
	readkit::file close $db
    }

    proc stat {db path {arr ""}} {
	set sp [::file split $path]
	set tail [lindex $sp end]

	set parent 0
	set view $db.dirs
	set type directory

	foreach ele [lrange $sp 0 end-1] {
	    if {[info exists v::cache($db,$parent,$ele)]} {
		set parent $v::cache($db,$parent,$ele)
	    } else {
		set row [readkit::select $view -count 1 parent $parent name $ele]
		if { $row == "" } {
		    vfs::filesystem posixerror $::vfs::posix(ENOENT)
		}
		set v::cache($db,$parent,$ele) $row
		set parent $row
	    }
	}
	
	# Now check if final comp is a directory or a file
	# CACHING is required - it can deliver a x15 speed-up!
	
	if { [string equal $tail "."] || [string equal $tail ":"] \
	  || [string equal $tail ""] } {
	    set row $parent

	} elseif { [info exists v::cache($db,$parent,$tail)] } {
	    set row $v::cache($db,$parent,$tail)
	} else {
	    # File?
	    set fview $view!$parent.files
	    # create a name cache of files in this directory
	    if {![info exists v::fcache($fview)]} {
		# cache only a limited number of directories
		if {[array size v::fcache] >= 10} {
		    array unset v::fcache *
		}
		set v::fcache($fview) {}
		readkit::loop c $fview {
		    lappend v::fcache($fview) [readkit::get $c name]
		}
	    }
	    set row [lsearch -exact $v::fcache($fview) $tail]
	    #set row [readkit::select $fview -count 1 name $tail]
	    #if {$row == ""} { set row -1 }
	    if { $row != -1 } {
		set type file
		set view $view!$parent.files
	    } else {
		# Directory?
		set row [readkit::select $view -count 1 parent $parent name $tail]
		if { $row != "" } {
		    set v::cache($db,$parent,$tail) $row
		} else { 
		    vfs::filesystem posixerror $::vfs::posix(ENOENT)
		}
	    }
	}
 
        if {![string length $arr]} {
            # The caller doesn't need more detailed information.
            return 1
        }
 
	set cur $view!$row

	upvar 1 $arr sb

	set sb(type)    $type
	set sb(view)    $view
	set sb(ino)     $cur

	if { [string equal $type "directory"] } {
	    set sb(atime) 0
	    set sb(ctime) 0
	    set sb(gid)   0
	    set sb(mode)  0777
	    set sb(mtime) 0
	    set sb(nlink) [expr { [readkit::get $cur files] + 1 }]
	    set sb(size)  0
	    set sb(csize) 0
	    set sb(uid)   0
	} else {
	    set mtime   [readkit::get $cur date]
	    set sb(atime) $mtime
	    set sb(ctime) $mtime
	    set sb(gid)   0
	    set sb(mode)  0777
	    set sb(mtime) $mtime
	    set sb(nlink) 1
	    set sb(size)  [readkit::get $cur size]
	    set sb(csize) [readkit::get $cur -size contents]
	    set sb(uid)   0
	}
    }

    proc do_close {db fd mode cur} {
	if {![regexp {[aw]} $mode]} {
	    error "mkcl_vfs::do_close called with bad mode: $mode"
	}

	readkit::set $cur size -1 date [clock seconds]
	flush $fd
	if { [string match *z* $mode] } {
	    fconfigure $fd -translation binary
	    seek $fd 0
	    set data [read $fd]
	    set cdata [vfs::zip -mode compress $data]
	    set len [string length $data]
	    set clen [string length $cdata]
	    if { $clen < $len } {
		readkit::set $cur size $len contents $cdata
	    } else {
		readkit::set $cur size $len contents $data
	    }
	} else {
	    readkit::set $cur size [readkit::get $cur -size contents]
	}
	# 16oct02 new logic to start a periodic commit timer if not yet running
	setupCommits $db
	return ""
    }

    proc setupCommits {db} {
	if {$v::mode($db) eq "readwrite" && ![info exists v::timer($db)]} {
	    periodicCommit $db
	    readkit::file autocommit $db
	}
    }

    proc mkdir {db path} {
	if {$v::mode($db) == "readonly"} {
	    vfs::filesystem posixerror $::vfs::posix(EROFS)
	}
	set sp [::file split $path]
	set parent 0
	set view $db.dirs

	set npath {}
	# This actually does more work than is needed. Tcl's
	# vfs only requires us to create the last piece, and
	# Tcl already knows it is not a file.
	foreach ele $sp {
	    set npath [file join $npath $ele]

	    if {![catch {stat $db $npath sb}] } {
		if { $sb(type) != "directory" } {
		    vfs::filesystem posixerror $::vfs::posix(EROFS)
		}
		set parent [readkit::cursor position sb(ino)]
		continue
	    }
	    #set parent [readkit::cursor position sb(ino)]
	    set cur [readkit::row append $view name $ele parent $parent]
	    set parent [readkit::cursor position cur]
	}
	setupCommits $db
	return ""
    }

    proc getdir {db path {pat *}} {
	if {[catch { stat $db $path sb }] || $sb(type) != "directory" } {
	    return
	}

	# Match directories
	set parent [readkit::cursor position sb(ino)] 
	foreach row [readkit::select $sb(view) parent $parent -glob name $pat] {
	    set hits([readkit::get $sb(view)!$row name]) 1
	}
	# Match files
	set view $sb(view)!$parent.files
	foreach row [readkit::select $view -glob name $pat] {
	    set hits([readkit::get $view!$row name]) 1
	}
	return [lsort [array names hits]]
    }

    proc mtime {db path time} {
	if {$v::mode($db) == "readonly"} {
	    vfs::filesystem posixerror $::vfs::posix(EROFS)
	}
	stat $db $path sb
	if { $sb(type) == "file" } {
	    readkit::set $sb(ino) date $time
	}
	return $time
    }

    proc delete {db path {recursive 0}} {
	#puts stderr "mk4delete db $db path $path recursive $recursive"
	if {$v::mode($db) == "readonly"} {
	    vfs::filesystem posixerror $::vfs::posix(EROFS)
	}
	stat $db $path sb
	if {$sb(type) == "file" } {
	    readkit::row delete $sb(ino)
	    if {[regexp {(.*)!(\d+)} $sb(ino) - v r] \
		    && [info exists v::fcache($v)]} {
		set v::fcache($v) [lreplace $v::fcache($v) $r $r]
	    }
	} else {
	    # just mark dirs as deleted
	    set contents [getdir $db $path *]
	    if {$recursive} {
		# We have to delete these manually, else
		# they (or their cache) may conflict with
		# something later
		foreach f $contents {
		    delete $db [file join $path $f] $recursive
		}
	    } else {
		if {[llength $contents]} {
		    vfs::filesystem posixerror $::vfs::posix(ENOTEMPTY)
		}
	    }
	    array unset v::cache \
		    "$db,[readkit::get $sb(ino) parent],[file tail $path]"
	    
	    # flag with -99, because parent -1 is not reserved for the root dir
	    # deleted entries never get re-used, should be cleaned up one day
	    readkit::set $sb(ino) parent -99 name ""
	    # get rid of file entries to release the space in the datafile
	    readkit::view size $sb(ino).files 0
	}
	setupCommits $db
	return ""
    }
}

package provide readkit 0.8
package provide vfs::mkcl 2.4.0.1