# small 9p implementation in tcl by Axel.Belinfante@cs.utwente.nl # this is based on the python 9P implementation by Tim Newsham. # http://lava.net/~newsham/plan9/ package require Tcl 8.5 ;# we need 'chan create' package require Tcl 8.4 ;# we need 64bit int support package require vfs 1.0 ;# this is what we build upon # TODO: # check we have all handlers (createdirectory, deletefile, ...?) # do error reporting right # (we introduced 'filesystem posixerror'; # still, we should be able to give better error messages, # e.g. when a walk fails we _know_ which is the first non-existent part) # check wich 9p messages do implicit clunk (only remove??) # report failure of utime actime setting? # better packaging? # ------------------------ set debug 0 ;# set to 1 to enable all debugging/verboseness proc debug {s} { if {$::debug} { puts stderr $s } } set ::vfs::debug $::debug vfs::filesystem internalerror report proc report {} { if {$::debug} { puts stderr "9pvfs internal error: $::errorInfo" } } # ------------------------ namespace eval 9p { variable PORT 564 variable VERSION "9P2000" variable NOTAG 0xffff variable NOFID 0xffffffff } proc 9p::_lfilter {l s} { set f [lsearch -all $l $s] while {[llength $f] > 0} { set i [lindex $f end] set f [lrange $f 0 end-1] set l [lreplace $l $i $i] } return $l } proc 9p::_dump {buf} { binary scan $buf c* X set r {} foreach h $X { lappend r [format %02x [expr $h & 0xff]] } return [join $r " "] } proc 9p::verbose {self {val {}}} { upvar #0 G_[set self](verbose) verbose set v $verbose if {$val != {}} { set verbose $val } return $v } namespace eval 9p::mode { variable DMDIR 020000000000 variable QDIR 0x80 variable OREAD 0 variable OWRITE 1 variable ORDWR 2 variable OEXEC 3 variable OTRUNC 0x10 variable ORCLOSE 0x40 } namespace eval 9p::proto { variable cmdName set firstNum 100 set enumCmd [list \ version \ auth \ attach \ error \ flush \ walk \ open \ create \ read \ write \ clunk \ remove \ stat \ wstat \ ] proc enumCmd {num alist} { variable cmdName foreach name $alist { set cmdName($num) T$name set cmdName([expr $num+1]) R$name variable T$name set T$name $num variable R$name set R$name [expr $num+1] incr num 2 } variable Tmax set Tmax $num } enumCmd $firstNum $enumCmd } # Class for marshalling data. # This class provies helpers for marshalling data. Integers are encoded # as little endian. All encoders and decoders rely on _encX and _decX. # These methods append bytes to self.bytes for output and remove bytes # from the beginning of self.bytes for input. To use another scheme # only these two methods need be overriden. namespace eval 9p::marshal { variable MAXSIZE [expr 1024 * 1024] ;# XXX set msgFmts { Tversion "4S" Rversion "4S" Tauth "4SS" Rauth "Q" Terror "" Rerror "S" Tflush "2" Rflush "" Tattach "44SS" Rattach "Q" Twalk "[Twalk]" Rwalk "[Rwalk]" Topen "41" Ropen "Q4" Tcreate "4S41" Rcreate "Q4" Tread "484" Rread "D" Twrite "48D" Rwrite "4" Tclunk "4" Rclunk "" Tremove "4" Rremove "" Tstat "4" Rstat "[Stat]" Twstat "4[Stat]" Rwstat "" } proc splitFmt {fmt} { set idx 0 set r {} while {$idx < [string length $fmt]} { if {[string index $fmt $idx] == {[}} { set fmt [string range $fmt [expr $idx + 1] end] set idx2 [string first {]} $fmt ] if {$idx2 < 0} { error "no close square bracket" } set name [string range $fmt 0 [expr $idx2 - 1]] set idx $idx2 } else { set name [string range $fmt $idx $idx] } incr idx lappend r $name } return $r } proc prep {fmttab} { variable msgEncodes variable msgDecodes foreach {k v} $fmttab { variable ::9p::proto::$k set kk [set ::9p::proto::$k] set fmts [splitFmt $v] set msgEncodes($kk) {} set msgDecodes($kk) {} foreach fmt $fmts { lappend msgEncodes($kk) enc[set fmt] lappend msgDecodes($kk) dec[set fmt] } } } variable fmtName foreach {n v} $msgFmts { variable ::9p::proto::$n set fmtName([set ::9p::proto::$n]) $v } prep $msgFmts } proc 9p::marshal::applyFuncs {self funcs {vals None}} { set x {} if {[string compare $vals None] != 0} { foreach f $funcs v $vals { lappend x [$f $self $v] } } else { foreach f $funcs { lappend x [$f $self] } } if {[llength $x] == 1} { set x [lindex $x 0] } return $x } proc 9p::marshal::setBuf {self {str ""}} { upvar #0 C_[set self](buf) buf set buf $str } proc 9p::marshal::getBuf {self} { upvar #0 C_[set self](buf) buf return $buf } proc 9p::marshal::delBuf {self} { upvar #0 C_[set self](buf) buf catch {unset buf} } proc 9p::marshal::lenBuf {self} { upvar #0 C_[set self](buf) buf return [string length $buf] } proc 9p::marshal::appendBuf {self x} { upvar #0 C_[set self](buf) buf append buf $x } proc 9p::marshal::firstofBuf {self l} { upvar #0 C_[set self](buf) buf set ll [string length $buf] if {$ll < $l} { error "firstofBuf: short buf (wanted $l, avail $ll)" } set x [string range $buf 0 [expr $l - 1]] set buf [string range $buf $l end] return $x } proc 9p::marshal::rangeBuf {self beg end} { upvar #0 C_[set self](buf) buf set x [string range $buf $beg $end] return $x } proc 9p::marshal::replaceBuf {self beg end data} { upvar #0 C_[set self](buf) buf set buf [string replace $buf $beg $end $data] } proc 9p::marshal::checkSize {v mask} { if {$v != [expr $v & $mask]} { error "Invalid value $v" } } proc 9p::marshal::checkLen {x l} { set ll [string length $x] if {$ll != $l} { error "Wrong length $ll, expected $l: $x" } } proc 9p::marshal::encX {self x} { appendBuf $self $x } proc 9p::marshal::decX {self l} { return [firstofBuf $self $l] } proc 9p::marshal::enc1 {self x} { checkSize $x [expr wide(0xff)] return [encX $self [binary format c $x]] } proc 9p::marshal::dec1 {self} { binary scan [decX $self 1] c x return [expr $x & 0xff] } proc 9p::marshal::enc2 {self x} { checkSize $x [expr wide(0xffff)] return [encX $self [binary format s $x]] } proc 9p::marshal::dec2 {self} { binary scan [decX $self 2] s x return [expr $x & 0xffff] } proc 9p::marshal::enc4 {self x} { checkSize $x [expr wide(0xffffffff)] return [encX $self [binary format i $x]] } proc 9p::marshal::dec4 {self} { binary scan [decX $self 4] i x return [expr $x & 0xffffffff] } proc 9p::marshal::enc8 {self x} { checkSize $x [expr wide(0xffffffffffffffff)] return [encX $self [binary format w $x]] } proc 9p::marshal::dec8 {self} { binary scan [decX $self 8] w x return [expr $x & 0xffffffffffffffff] } proc 9p::marshal::encS {self x} { enc2 $self [string length $x] encX $self $x } proc 9p::marshal::decS {self} { set l [dec2 $self] return [decX $self $l] } proc 9p::marshal::encD {self x} { enc4 $self [string length $x] encX $self $x } proc 9p::marshal::decD {self} { set l [dec4 $self] return [decX $self $l] } proc 9p::marshal::encQ {self q} { set type [lindex $q 0] set vers [lindex $q 1] set path [lindex $q 2] enc1 $self $type enc4 $self $vers enc8 $self $path } proc 9p::marshal::decQ {self} { return [list [dec1 $self] [dec4 $self] [dec8 $self]] } proc 9p::marshal::encTwalk {self x} { set fid [lindex $x 0] set newfid [lindex $x 1] set names [lindex $x 2] enc4 $self $fid enc4 $self $newfid enc2 $self [llength $names] foreach n $names { encS $self $n } } proc 9p::marshal::decTwalk {self} { set fid [dec4 $self] set newfid [dec4 $self] set l [dec2 $self] set names {} set i 0 while {$i < $l} { lappend names [decS $self] incr i } return [list $fid $newfid $names] } proc 9p::marshal::encRwalk {self qids} { enc2 $self [llength $qids] foreach q $qids { encQ $self $q } } proc 9p::marshal::decRwalk {self} { debug "_decRwalk $self" set l [dec2 $self] set r {} set i 0 debug "_decRwalk $self l=$l" while {$i < $l} { lappend r [decQ $self] incr i } debug "_decRwalk $self l=$l" return $r } proc 9p::marshal::encDir {self x} { debug "encDir $self ($x)" set nself [set self]dir setBuf $nself "" enc2 $nself [lindex $x 0] ;# type enc4 $nself [lindex $x 1] ;# dev encQ $nself [lindex $x 2] ;# qid enc4 $nself [lindex $x 3] ;#mode enc4 $nself [lindex $x 4] ;# atime enc4 $nself [lindex $x 5] ;# mtime enc8 $nself [lindex $x 6] ;# ln encS $nself [lindex $x 7] ;# name encS $nself [lindex $x 8] ;# uid encS $nself [lindex $x 9] ;# gid encS $nself [lindex $x 10] ;# muid encS $self [getBuf $nself] delBuf $nself } proc 9p::marshal::encStat {self l} { debug "_encStat $self ($l)" set nself [set self]stat setBuf $nself "" foreach x $l { encDir $nself $x } encS $self [getBuf $nself] delBuf $nself } proc 9p::marshal::decodeDir {self} { lappend r [dec2 $self] ;# type lappend r [dec4 $self] ;# dev lappend r [decQ $self] ;# qid lappend r [dec4 $self] ;#mode lappend r [dec4 $self] ;# atime lappend r [dec4 $self] ;# mtime lappend r [dec8 $self] ;# ln lappend r [decS $self] ;# name lappend r [decS $self] ;# uid lappend r [decS $self] ;# gid lappend r [decS $self] ;# muid return $r } proc 9p::marshal::decodeDirs {self s} { set nself [set self]dirs setBuf $nself $s set r {} while {[lenBuf $nself] > 0} { set dstr [decS $nself] set nnself [set nself]dir setBuf $nnself $dstr lappend r [decodeDir $nnself] delBuf $nnself } delBuf $nself return $r } proc 9p::marshal::decStat {self} { set s [decS $self] set r [decodeDirs $self $s] return $r } proc 9p::marshal::checkType {t} { variable fmtName if {![info exists fmtName($t)]} { error "invalid message type $t" } } proc 9p::marshal::checkResid {self} { set n [lenBuf $self] if {$n > 0} { binary scan [getBuf $self $n] h* X set Xs [string join $X ""] error "Extra information in message: $Xs" } } proc 9p::marshal::sread {f l} { set x [read $f $l] #puts stderr "9p::marshal::sread read [::9p::_dump $x] of $l" while {[string length $x] < $l} { set b [read $f [expr $l - [string length $x]]] #puts stderr "9p::marshal::sread read [::9p::_dump $b] of $l" if {[string length $b] == 0} { error "Client EOF" } append x $b } # puts stderr "9p::marshal::sread read done" return $x } proc 9p::marshal::swrite {f buf} { if {[catch { puts -nonewline $f $buf flush $f } msg]} { error "short write: $msg" } } proc 9p::marshal::send {self type tag arglist} { variable msgEncodes upvar #0 G_[set self](verbose) verbose upvar #0 C_[set self](srvfd) srvfd setBuf $self "" checkType $type enc1 $self $type enc2 $self $tag applyFuncs $self $msgEncodes($type) $arglist set l [lenBuf $self] set ss [getBuf $self] setBuf $self "" enc4 $self [expr $l + 4] encX $self $ss if {$verbose} { puts "send $type $tag $arglist" } swrite $srvfd [getBuf $self] } proc 9p::marshal::recv {self} { variable MAXSIZE upvar #0 G_[set self](verbose) verbose upvar #0 C_[set self](srvfd) srvfd variable msgDecodes setBuf $self [sread $srvfd 4] set size [dec4 $self] if {$size > $MAXSIZE || $size < 4} { error "Bad message size: $size" } setBuf $self [sread $srvfd [expr $size - 4]] set type [dec1 $self] set tag [dec2 $self] checkType $type set rest [applyFuncs $self $msgDecodes($type)] checkResid $self if {$verbose} { puts "recv $type $tag" ;# $rest } return [list $type $tag $rest] } proc 9p::proto::rpc {self type args} { variable ::9p::NOTAG variable cmdName variable Tversion variable Rerror upvar #0 G_[set self](verbose) verbose set tag 1 if {$type == $Tversion} { set tag [expr int($NOTAG)] } if {$verbose} { puts "$cmdName($type) $tag $args" } ::9p::marshal::send $self $type $tag $args set resp [::9p::marshal::recv $self] set rtype [lindex $resp 0] set rtag [lindex $resp 1] set vals [lindex $resp 2] if {$verbose} { puts "$cmdName($rtype) $rtag" ;# $vals } if {$rtag != $tag} { error "invalid tag received" } if {$rtype == $Rerror} { error "RpcError $vals" } if {$rtype != [expr $type + 1]} { error "incorrect reply from server: [list $rtype $rtag $vals]" } debug "rpc $self $type $args -> $vals" return $vals } proc 9p::proto::version {self msize version} { variable Tversion return [rpc $self $Tversion $msize $version] } proc 9p::proto::auth {self fid uname aname} { variable Tauth return [rpc $self $Tauth $fid $uname $aname] } proc 9p::proto::attach {self fid afid uname aname} { variable Tattach return [rpc $self $Tattach $fid $afid $uname $aname] } proc 9p::proto::walk {self fid newfid wnames} { variable Twalk return [rpc $self $Twalk [list $fid $newfid $wnames]] } proc 9p::proto::open {self fid mode} { variable Topen return [rpc $self $Topen $fid $mode] } proc 9p::proto::create {self fid name perm mode} { variable Tcreate return [rpc $self $Tcreate $fid $name $perm $mode] } proc 9p::proto::read {self fid off count} { variable Tread return [rpc $self $Tread $fid $off $count] } proc 9p::proto::write {self fid off data} { variable Twrite return [rpc $self $Twrite $fid $off $data] } proc 9p::proto::clunk {self fid} { variable Tclunk return [rpc $self $Tclunk $fid] } proc 9p::proto::remove {self fid} { variable Tremove return [rpc $self $Tremove $fid] } proc 9p::proto::stat {self fid} { variable Tstat return [rpc $self $Tstat $fid] } proc 9p::proto::wstat {self fid stats} { variable Twstat return [rpc $self $Twstat $fid $stats] } proc 9p::chan {handle fid cmd chan args} { debug "9p::chan $handle $fid $cmd $chan $args" switch -exact -- $cmd { initialize { return [list initialize finalize watch read write seek] } finalize { ::9p::clunk $handle $fid } watch { } read { set count [lindex $args 0] return [::9p::read $handle $fid $count] } write { set data [lindex $args 0] return [::9p::write $handle $fid $data] } seek { set off [lindex $args 0] set mode [lindex $args 1] set pos [::9p::seek $handle $fid $off $mode] } } } namespace eval 9p { variable selfnr 0 } proc 9p::mount {fd user {alist {}}} { variable VERSION variable NOFID variable selfnr set self "v9p[set selfnr]" incr selfnr upvar #0 C_[set self](CWD) CWD upvar #0 C_[set self](ROOT) ROOT upvar #0 C_[set self](AFID) AFID upvar #0 C_[set self](recycled) recycled upvar #0 C_[set self](nextF) nextF upvar #0 C_[set self](srvfd) srvfd upvar #0 G_[set self](verbose) verbose set authsrv [lindex $alist 0] set passwd [lindex $alist 1] set AFID 10 set ROOT 11 set nextF 12 set recycled {} set verbose 0 set srvfd $fd set maxbuf_vers [proto::version $self [expr 16*1024] $VERSION] debug "maxbuf_vers $maxbuf_vers" set maxbuf [lindex $maxbuf_vers 0] set vers [lindex $maxbuf_vers 1] if {[string compare $vers $VERSION] != 0} { error "version mismatch: $vers" } set afid $AFID if {[catch {proto::auth $self $afid $user ""} err]} { puts stderr "main proto::auth : $err" set afid $NOFID } else { set needauth 1 } if {$afid != $NOFID} { if {$passwd == {} && $authsrv == {}} { error "oops, missing authsrv and password" } elseif {$passwd == {}} { error "oops, missing password" } elseif {$authsrv == {}} { error "oops, missing authsrv" } else { puts "authenticating $user at $authsrv" ;# XXX only if verbose? } ::p9sk1::clientAuth $self $afid $user [::p9sk1::makeKey $passwd] $authsrv } proto::attach $self $ROOT $afid $user "" if {$afid != $NOFID} { proto::clunk $self $afid } return $self } proc 9p::unmount {self} { upvar #0 C_[set self](srvfd) srvfd # catch {close $srvfd} } proc 9p::qidisdir {qid} { set type [lindex $qid 0] set isdir [expr $type & $::9p::mode::QDIR] return $isdir } proc 9p::isdir {self F} { upvar #0 C_[set self]_[set F](qid) qid if {![info exists qid]} { error "no mapping fid->qid" } return [9p::qidisdir $qid] } proc 9p::newfid {self} { upvar #0 C_[set self](recycled) recycled upvar #0 C_[set self](nextF) nextF if {[llength $recycled] > 0} { set F [lindex $recycled 0] set recycled [lrange $recycled 1 end] } else { set F $nextF incr nextF } return $F } proc 9p::walk {self {pstr {}}} { upvar #0 C_[set self](ROOT) ROOT set root $ROOT set F [newfid $self] if {$pstr == {}} { set path {} } else { set path [split $pstr /] if {[string compare [lindex $path 0] ""] == 0} { set root $ROOT set path [lrange $path 1 end] } set path [_lfilter $path ""] } if {[catch {proto::walk $self $root $F $path} w]} { # puts "error: $w" return } upvar #0 C_[set self]_[set F](qid) qid set qid [lindex $w end] if {[llength $w] < [llength $path]} { # puts "$pstr: not found" return } debug "walk $self ($pstr): $w" return $F } proc 9p::afidopen {self F} { upvar #0 C_[set self]_[set F](pos) pos set pos 0 return } # Modes taken from ::9p::mode proc 9p::open {self F mode} { upvar #0 C_[set self]_[set F](pos) pos set pos 0 set r [proto::open $self $F $mode] debug "open $self $F $mode -> $r" return $r } proc 9p::create {self F name perm mode} { # self dirfid name perm mode upvar #0 C_[set self]_[set F](pos) pos set pos 0 if {[catch {proto::create $self $F $name $perm $mode} r]} { # puts "error: $r" return } debug "create $self $F $name $perm $mode -> $r" upvar #0 C_[set self]_[set F](qid) qid set qid [lindex $r 0] return $r } proc 9p::read {self F l} { upvar #0 C_[set self]_[set F](pos) pos debug "read $self $l" set buf [proto::read $self $F $pos $l] incr pos [string length $buf] debug "read $self $l -> done" return $buf } proc 9p::write {self F data} { upvar #0 C_[set self]_[set F](pos) pos debug "write $self" set l [proto::write $self $F $pos $data] incr pos $l debug "write $self -> done" return $l } proc 9p::stat {self F} { return [lindex [proto::stat $self $F] 0] } proc 9p::wstat {self F stats} { proto::wstat $self $F [list $stats] } proc 9p::clunk {self F} { upvar #0 C_[set self](recycled) recycled upvar #0 C_[set self]_[set F](qid) qid proto::clunk $self $F lappend recycled $F unset qid } # remove is like clunk with removal of file as side-effect proc 9p::remove {self F} { upvar #0 C_[set self](recycled) recycled upvar #0 C_[set self]_[set F](qid) qid proto::remove $self $F lappend recycled $F unset qid } proc 9p::seek {self F n mode} { upvar #0 C_[set self]_[set F](pos) pos upvar #0 C_[set self]_[set F](stat) stat if {[9p::isdir $self $F]} { error "cannot seek in directory" } set npos $pos switch -- $mode { start { set npos $n } current { incr npos $n } end { set stat [lindex [proto::stat $self $F] 0] set sz [lindex $stat 6] set npos $sz incr npos $n } default { error "9p::seek: unknown mode: $mode" } } if {$npos < 0} { # error "seek pos becomes negative: $npos" error "invalid argument" } set pos $npos return $pos } proc 9p::mode::rwx {mode s} { set bits [list "---" "--x" "-w-" "-wx" "r--" "r-x" "rw-" "rwx"] return [lindex $bits [expr ($mode >> $s) & 7]] } proc 9p::mode::perm {mode} { variable DMDIR set d "-" if {[expr $mode & $DMDIR]} { set d "d" } return "[set d][rwx $mode 6][rwx $mode 3][rwx $mode 0]" } proc 9p::mode::filetype {mode} { variable DMDIR if {[expr $mode & $DMDIR]} { return "directory" } else { return "file" } } proc 9p::decodeDirs {self s} { return [9p::marshal::decodeDirs $self $s] } # ------------------------ namespace eval vfs::9p { variable natmode variable chanmode set natmode() $::9p::mode::OREAD set natmode(r) $::9p::mode::OREAD set natmode(r+) $::9p::mode::ORDWR set natmode(w) [expr $::9p::mode::OWRITE | $::9p::mode::OTRUNC] set natmode(w+) [expr $::9p::mode::ORDWR | $::9p::mode::OTRUNC] set natmode(a) $::9p::mode::OWRITE set natmode(a+) $::9p::mode::ORDWR set chanmode() read set chanmode(r) read set chanmode(r+) [list read write] set chanmode(w) write set chanmode(w+) [list read write] set chanmode(a) write set chanmode(a+) [list read write] } proc vfs::9p::Mount {fd user local args} { vfs::log "vfs::9p::Mount: attempt to mount $fd $user at $local" set handle [::9p::mount $fd $user $args] vfs::log "9p $fd $user mounted at $local : $handle" 9p::verbose $handle $::debug vfs::filesystem mount $local [list vfs::9p::handler $handle] vfs::RegisterMount $local [list ::vfs::9p::Unmount $handle] return $handle } proc vfs::9p::Unmount {handle local} { vfs::filesystem unmount $local ::9p::unmount $handle } proc vfs::9p::handler {handle cmd root relative actualpath args} { vfs::log "vfs::9p::handler $handle $cmd $root $relative $actualpath [list $args]" if {$cmd == "matchindirectory"} { eval [list vfs::9p::$cmd $handle $relative $actualpath] $args } else { eval [list vfs::9p::$cmd $handle $relative] $args } } proc vfs::9p::stat {handle name} { vfs::log "vfs::9p::stat $handle $name" set fid [::9p::walk $handle $name] if {$fid == {}} { vfs::log "vfs::9p::stat $handle $name : ENOENT" ::vfs::9p::posixerror [::vfs::posixError ENOENT] } set stat [::9p::stat $handle $fid] ::9p::clunk $handle $fid set t [lindex $stat 0] set d [lindex $stat 1] set q [lindex $stat 2] set m [lindex $stat 3] set at [lindex $stat 4] set mt [lindex $stat 5] set l [lindex $stat 6] set name [lindex $stat 7] set u [lindex $stat 8] set g [lindex $stat 9] set mod [lindex $stat 10] lappend res type [9p::mode::filetype $m] lappend res ino [lindex $q 2] lappend res dev -1 lappend res uid -1 lappend res gid -1 lappend res nlink 1 lappend res depth 0 lappend res atime $at lappend res ctime $mt lappend res mtime $mt lappend res mode [expr $m & 0x01ff] lappend res size [expr $l & 0xffffffff] ;# XXX vfs::log "vfs::9p::stat $handle $name : ($stat) ($res) " return $res } proc vfs::9p::access {handle name mode} { vfs::log "vfs::9p::access $handle $name $mode" if {$name == ""} { vfs::log "vfs::9p::access $handle $name $mode -> 1" return 1 } set fid [::9p::walk $handle $name] if {$fid == {}} { vfs::log "vfs::9p::access $handle $name $mode -> ENOENT" ::vfs::9p::posixerror [::vfs::posixError ENOENT] } ::9p::clunk $handle $fid vfs::log "vfs::9p::access $handle $name $mode -> 1" return 1 } proc vfs::9p::createdirectory {handle name} { vfs::log "vfs::9p::createdirectory $handle $name" set dname [file dirname $name] set fname [file tail $name] set fid [::9p::walk $handle $dname] if {$fid == {}} { ::vfs::9p::posixerror [::vfs::posixError ENOENT] } set qid [::9p::create $handle $fid $fname [expr $::9p::mode::DMDIR | 0777] 0] if {$qid == {}} { ::vfs::9p::posixerror [::vfs::posixError EACCES] } ::9p::clunk $handle $fid } proc vfs::9p::removedirectory {handle name recursive} { vfs::log "vfs::9p::removedirectory $handle $name $recursive" set fid [::9p::walk $handle $name] if {$fid == {}} { ::vfs::9p::posixerror [::vfs::posixError ENOENT] } if {[::9p::isdir $handle $fid]} { 9p::open $handle $fid $::9p::mode::OREAD while {1} { set buf [::9p::read $handle $fid 4096] if {[string length $buf] <= 0} { break } foreach stat [::9p::decodeDirs $handle $buf] { if {! $recursive} { ::vfs::9p::posixerror [::vfs::posixError EEXIST] } set sname [lindex $stat 7] vfs::9p::removedirectory $handle [file join $name $sname] $recursive } } } ::9p::remove $handle $fid } proc vfs::9p::deletefile {handle name} { vfs::log "vfs::9p::deletefile $handle $name" set fid [::9p::walk $handle $name] if {$fid == {}} { ::vfs::9p::posixerror [::vfs::posixError ENOENT] } ::9p::remove $handle $fid } # XXX usually we will not be allowed to set actime proc vfs::9p::utime {handle name actime mtime} { vfs::log "vfs::9p::utime $handle $name $actime $mtime" set fid [::9p::walk $handle $name] if {$fid == {}} { ::vfs::9p::posixerror [::vfs::posixError ENOENT] } set stats {} # supply 'don't touch' values # (as discussed in plan 9 stat(5) manual page) # except for atime and mtime lappend stats 0xffff ;# type 2 lappend stats 0xffffffff ;# dev 4 lappend stats { 0xff 0xffffffff 0xffffffffffffffff};# qid lappend stats 0xffffffff ;# mode 4 lappend stats $actime ;# atime 4 lappend stats $mtime ;# mtime 4 lappend stats 0xffffffffffffffff ;# ln 8 lappend stats "" ;# name lappend stats "" ;# uid lappend stats "" ;# gid lappend stats "" ;# muid 9p::wstat $handle $fid $stats 9p::clunk $handle $fid } proc vfs::9p::open {handle name mode perm} { vfs::log "vfs::9p::open $handle $name $mode $perm" variable natmode variable chanmode # puts stderr "vfs::9p::open $handle $name $mode $perm" # 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. set nmode $natmode($mode) set cmode $chanmode($mode) switch -exact -- $mode { "" - "r" { set fid [::9p::walk $handle $name] if {$fid == {}} { ::vfs::9p::posixerror [::vfs::posixError ENOENT] } if {[catch {::9p::open $handle $fid $nmode} msg]} { ::vfs::9p::posixerror [::vfs::posixError EACCES] } return [chan create $cmode [list ::9p::chan $handle $fid]] } "r+" { set fid [::9p::walk $handle $name] if {$fid == {}} { ::vfs::9p::posixerror [::vfs::posixError ENOENT] } if {[::9p::isdir $handle $fid]} { ::vfs::9p::posixerror [::vfs::posixError EISDIR] } if {[catch {::9p::open $handle $fid $nmode} msg]} { ::vfs::9p::posixerror [::vfs::posixError EACCES] } return [chan create $cmode [list ::9p::chan $handle $fid]] } "a" - "a+" { set fid [::9p::walk $handle $name] if {$fid == {}} { # suppress walk 'not found' error message? set dname [file dirname $name] set fname [file tail $name] set fid [::9p::walk $handle $dname] if {$fid == {}} { ::vfs::9p::posixerror [::vfs::posixError ENOENT] } if {![::9p::isdir $handle $fid]} { ::vfs::9p::posixerror [::vfs::posixError ENOTDIR] } set qid [::9p::create $handle $fid $fname $perm $nmode] if {$qid == {}} { ::vfs::9p::posixerror [::vfs::posixError EACCES] } } else { if {[::9p::isdir $handle $fid]} { ::vfs::9p::posixerror [::vfs::posixError EISDIR] } if {[catch {::9p::open $handle $fid $nmode} msg]} { ::vfs::9p::posixerror [::vfs::posixError EACCES] } } ::9p::seek $handle $fid 0 end return [chan create $cmode [list ::9p::chan $handle $fid]] } "w" - "w+" { set fid [::9p::walk $handle $name] if {$fid == {}} { # suppress walk 'not found' error message? set dname [file dirname $name] set fname [file tail $name] set fid [::9p::walk $handle $dname] if {$fid == {}} { ::vfs::9p::posixerror [::vfs::posixError ENOENT] } if {![::9p::isdir $handle $fid]} { ::vfs::9p::posixerror [::vfs::posixError ENOTDIR] } set qid [::9p::create $handle $fid $fname $perm $nmode] if {$qid == {}} { ::vfs::9p::posixerror [::vfs::posixError EACCES] } } else { if {[::9p::isdir $handle $fid]} { ::vfs::9p::posixerror [::vfs::posixError EISDIR] } if {[catch {::9p::open $handle $fid $nmode} msg]} { ::vfs::9p::posixerror [::vfs::posixError EACCES] } } return [chan create $cmode [list ::9p::chan $handle $fid]] } default { return -code error "illegal access mode \"$mode\"" } } } proc vfs::9p::doesmatch {isdir types perm} { if {$isdir} { if {![::vfs::matchDirectories $types]} { return 0 } } else { if {![::vfs::matchFiles $types]} { return 0 } } return 1 } # it seems that perm is not set by tclvfs package proc vfs::9p::matchindirectory {handle relative actualpath pattern types {perm {}} {mac {}}} { vfs::log "vfs::9p::matchindirectory $handle \"$relative\" $actualpath ($pattern) ($types) ($perm) $mac" set res [list] set fid [::9p::walk $handle $relative] if {$fid == {}} { ::vfs::9p::posixerror [::vfs::posixError ENOENT] } if {[string length $pattern] > 0} { if {![::9p::isdir $handle $fid]} { ::vfs::9p::posixerror [::vfs::posixError ENOTDIR] } 9p::open $handle $fid $::9p::mode::OREAD while {1} { set buf [::9p::read $handle $fid 4096] if {[string length $buf] <= 0} { break } foreach stat [::9p::decodeDirs $handle $buf] { set name [lindex $stat 7] set qid [lindex $stat 2] if {[doesmatch [::9p::qidisdir $qid] $types $perm] && [string match $pattern $name]} { lappend res [file join $actualpath $name] } } } } else { # single file if {[doesmatch [::9p::isdir $handle $fid] $types $perm]} { lappend res $actualpath } } ::9p::clunk $handle $fid return $res } proc vfs::9p::fileattributes {handle path args} { vfs::log "vfs::9p::fileattributes $handle $path $args" switch -- [llength $args] { 0 { # list strings return [list] } 1 { # get value set index [lindex $args 0] } 2 { # set value set index [lindex $args 0] set val [lindex $args 1] error "read-only" } } } proc vfs::9p::posixerror {code} { # Seems we need a special case for EEXIST in removedirectory if {$code == [::vfs::posixError EEXIST]} { error $code } else { vfs::filesystem posixerror $code } } # ------------------------