#!/usr/bin/perl
# Copyright © 2016 by Yaroslav Kolomiiets
package Plan9;
use strict;
use Symbol;
# use bigint;
use constant {
VERSION9P=> '9P2000',
MAXWELEM=> 16,
IOHDRSZ=> 24,
NOTAG=> 0xffff,
NOFID=> 0xffffffff,
# bits in Qid.type
QTDIR => 0x80, # type bit for directories
QTAPPEND => 0x40, # type bit for append only files
QTEXCL => 0x20, # type bit for exclusive use files
QTMOUNT => 0x10, # type bit for mounted channel
QTAUTH => 0x08, # type bit for authentication file
QTTMP => 0x04, # type bit for not-backed-up file
QTFILE => 0x00, # plain file
# bits in Dir.mode
DMDIR => 0x80000000, # mode bit for directories
DMAPPEND=> 0x40000000, # mode bit for append only files
DMEXCL => 0x20000000, # mode bit for exclusive use files
DMMOUNT => 0x10000000, # mode bit for mounted channel
DMAUTH => 0x08000000, # mode bit for authentication file
DMTMP => 0x04000000, # mode bit for non-backed-up files
DMREAD => 0x4, # mode bit for read permission
DMWRITE => 0x2, # mode bit for write permission
DMEXEC => 0x1, # mode bit for execute permission
# open modes
OREAD => 0, # open for read
OWRITE => 1, # write
ORDWR => 2, # read and write
OEXEC => 3, # execute, == read but check execute permission
OTRUNC => 16, # or'ed in (except for exec), truncate file first
OCEXEC => 32, # or'ed in, close on exec
ORCLOSE => 64, # or'ed in, remove on close
OEXCL => 0x1000, # or'ed in, exclusive use (create only)
};
use constant {
Tversion => 100,
Rversion => 101,
Tauth => 102,
Rauth => 103,
Tattach => 104,
Rattach => 105,
Terror => 106, # illegal
Rerror => 107,
Tflush => 108,
Rflush => 109,
Twalk => 110,
Rwalk => 111,
Topen => 112,
Ropen => 113,
Tcreate => 114,
Rcreate => 115,
Tread => 116,
Rread => 117,
Twrite => 118,
Rwrite => 119,
Tclunk => 120,
Rclunk => 121,
Tremove => 122,
Rremove => 123,
Tstat => 124,
Rstat => 125,
Twstat => 126,
Rwstat => 127,
Tmax => 128,
};
our %nulldir = (
type => 0,
dev => 0,
qid => {
type => 0,
vers => 0,
path => 0,
},
mode => 0,
atime => 0,
mtime => 0,
length => 0,
name => "",
uid => "",
gid => "",
muid => "",
);
our %syncdir = (
type => ~0 & 0xffff,
dev => ~0 & 0xffffffff,
qid => {
type => ~0 & 0xff,
vers => ~0 & 0xffffffff,
path => ~0,
},
mode => ~0 & 0xffffffff,
atime => ~0 & 0xffffffff,
mtime => ~0 & 0xffffffff,
length => ~0,
name => "",
uid => "",
gid => "",
muid => "",
);
sub packvlong($) {
local ($_) = @_;
my ($lo, $hi);
$lo = $_;
$hi = 0;
if($lo == ~0){
$hi = ~0;
}
return pack("VV", $lo, $hi);
}
sub unpackvlong($) {
local ($_) = @_;
my ($lo, $hi);
($lo, $hi) = unpack("VV", $_);
if($hi != 0){
$lo = ~0;
}
return $lo;
}
sub cmpvlong($$) {
my ($a, $b) = @_;
return $a-$b;
}
sub packqid(\%) {
local $_ = shift;
my $a;
# qid.type[1] qid.vers[4] qid.path[8]
$_->{_path} = packvlong($_->{path});
$a = pack("CVa[8]", @{$_}{'type', 'vers', '_path'});
delete $_->{_path};
return $a;
}
sub unpackqid($) {
my $m = shift;
local $_ = {};
@{$_}{'type', 'vers', 'path'} = unpack("CVa[8]", $m);
$_->{path} = unpackvlong($_->{path});
return $_;
}
sub packdir(\%) {
my $d = shift;
my $a;
$d->{_length} = packvlong($d->{length});
$d->{_qid} = packqid(%{$d->{qid}});
$a = pack("v/a*", pack("vVa[13]VVVa[8]v/a*v/a*v/a*v/a*",
@{$d}{qw(type dev _qid mode atime mtime _length name uid gid muid)}));
delete $d->{_length};
delete $d->{_qid};
return $a;
}
sub unpackdir($) {
my $m = shift;
my ($d, $dm, @d, $size);
for $dm (unpack("(v/a)*", $m)){
$d = {};
@{$d}{qw(type dev qid mode atime mtime length name uid gid muid)} =
unpack("vVa[13]VVVa[8]v/a*v/a*v/a*v/a*", $dm);
$d->{length} = unpackvlong($d->{length});
$d->{qid} = unpackqid($d->{qid});
return $d unless wantarray;
push(@d, $d);
}
return @d;
}
sub packfcall(\%) {
my ($f) = @_;
my ($size, $ap, $p, $m, $fntab, $fn);
$fntab = {
&Tversion => sub { pack("Vv/a*", @$f{'msize','version'}) },
&Rversion => sub { pack("Vv/a*", @$f{'msize','version'}) },
&Tauth => sub { pack("Vv/a*v/a*", @{$f}{'afid','uname','aname'}) },
&Rauth => sub { packqid(%{$f->{aqid}}) },
&Rerror => sub { pack("v/a*", $f->{ename}) },
&Tflush => sub { pack("v", ${$f}{oldsize}) },
&Rflush => sub { '' },
&Tattach => sub { pack("VVv/a*v/a*", @$f{qw(fid afid uname aname)}) },
&Rattach => sub { packqid(%{$f->{qid}}); },
&Twalk => sub { pack("VVv", @$f{'fid','newfid'}, 0+@{$f->{wname}}) . join('', map {pack("v/a*", $_)} @{$f->{wname}}) },
&Rwalk => sub { pack("v", 0+@{$f->{wqid}}) . join('', map {packqid(%{$_})} @{$f->{wqid}}) },
&Topen => sub { pack("VC", @$f{'fid','mode'}) },
&Ropen => sub { pack("a[13]V", @$f{'qid', 'iounit'}) },
&Tcreate => sub { pack("Vv/a*VC", @$f{'fid','name','perm','mode'}) },
&Rcreate => sub { pack("a[13]V", packqid(%{$f->{qid}}), $f->{iounit}); },
&Tread => sub { pack("Va[8]V", $f->{fid}, packvlong($f->{offset}), $f->{count}) },
&Rread => sub { pack("V/a*", $f->{data}); },
&Twrite => sub { pack("Va[8]V/a*", $f->{fid}, packvlong($f->{offset}), $f->{data}) },
&Rwrite => sub { pack("V", $f->{count}) },
&Tclunk => sub { pack("V", $f->{fid}) },
&Rclunk => sub { '' },
&Tremove => sub { pack("V", $f->{fid}) },
&Rremove => sub { '' },
&Tstat => sub { pack("V", $f->{fid}) },
&Rstat => sub { pack("v/a*", packdir(%{$f->{stat}})) },
&Twstat => sub { pack("Vv/a*", $f->{fid}, packdir(%{$f->{stat}})) },
&Rwstat => sub { '' },
};
$fn = $fntab->{$f->{type}};
if(!defined $fn){
$@ = "bad Fcall.type";
return undef;
}
$p = &$fn();
$size = 7+length($p);
$ap = pack("VCv", $size, @{$f}{'type', 'tag'});
return $ap.$p;
}
sub unpackfcall($) {
my ($ap) = @_;
my ($f, $nap, $p, $size, %qid, $fntab, $fn);
$nap = length($ap);
if($nap < 7){
$@ = "short message";
return undef;
}
$size = unpack("V", $ap);
if($size < 7 or $size > $nap){
$@ = "bad length in Fcall header";
return undef;
}
$f = {};
(undef, $f->{type}, $f->{tag}, $p) = unpack("VCva*", $ap);
$fntab = {
&Tversion => sub { @{$f}{'msize','version'} = unpack("Vv/a", $p) },
&Rversion => sub { @{$f}{'msize','version'} = unpack("Vv/a", $p) },
&Tauth => sub { @{$f}{'afid','uname','aname'} = unpack("Vv/av/a", $p) },
&Rauth => sub { ${$f}{aqid} = unpackqid($p) },
&Rerror => sub { ${$f}{ename} = unpack("v/a", $p) },
&Tflush => sub { ${$f}{oldtag} = unpack("v", $p) },
&Rflush => sub { },
&Tattach => sub { @{$f}{'fid','afid','uname','aname'} = unpack("VVv/av/a", $p) },
&Rattach => sub { ${$f}{qid} = unpackqid($p) },
&Twalk => sub {
@{$f}{'fid','newfid','nwname'} = unpack("VVv", $p);
if($f->{nwname} > MAXWELEM){
die "name too long";
}
${$f}{wname} = [];
(undef, undef, undef, @{$f->{wname}}) = unpack("VVv" . "v/a"x$f->{nwname}, $p);
},
&Rwalk => sub {
$f->{wqid} = [map {unpackqid($_)} unpack("v/(a[13])", $p)];
},
&Topen => sub { @{$f}{'fid','mode'} = unpack("VC", $p) },
&Ropen => sub {
@{$f}{'qid','iounit'} = unpack("a[13]V", $p);
$f->{qid} = unpackqid($f->{qid});
},
&Tcreate => sub { @{$f}{'fid','name','perm','mode'} = unpack("Vv/aVC", $p) },
&Rcreate => sub {
@{$f}{'qid', 'iounit'} = unpack("a[13]V", $p);
$f->{qid} = unpackqid($f->{qid});
},
&Tread => sub {
@{$f}{'fid','offset','count'} = unpack("Va[8]V", $p);
${$f}{offset} = unpackvlong(${$f}{offset});
},
&Rread => sub { @{$f}{'count','data'} = unpack("VX[V]V/a", $p) },
&Twrite => sub {
@{$f}{'fid','offset','count','data'} = unpack("Va[8]VX[V]V/a", $p);
${$f}{offset} = unpackvlong(${$f}{offset});
},
&Rwrite => sub { ${$f}{count} = unpack("V", $p) },
&Tclunk => sub { ${$f}{fid} = unpack("V", $p) },
&Rclunk => sub { },
&Tremove => sub { ${$f}{fid} = unpack("V", $p) },
&Rremove => sub { },
&Tstat => sub { ${$f}{fid} = unpack("V", $p) },
&Rstat => sub { ${$f}{stat} = unpackdir(unpack("v/a*", $p)) },
&Twstat => sub {
@{$f}{'fid','stat'} = unpack("Vv/a*", $p);
${$f}{stat} = unpackdir(${$f}{stat});
},
&Rwstat => sub { },
};
eval {
$fn = $fntab->{$f->{type}};
if(!defined $fn){
die "bad Fcall.type $f->{type}";
}
&$fn();
};
if(length($@) > 0){
return undef;
}
return $f;
}
sub readfcall(*;$) {
my $io = Symbol::qualify_to_ref(shift, caller);
my $n = shift;
my ($m, $len, $a, $b, $r);
$m = read($io, $a, 4);
if(!defined $m){
$@ = "read: $!";
return undef;
}
if($m != 4){
$@ = "short read";
return undef;
}
$len = unpack("V", $a);
if($len < 4 or $len > $n) {
$@ = "bad length in 9P2000 message header";
return undef;
}
$len -= 4;
$m = read($io, $b, $len);
if(!defined $m || $m != $len){
$@ = "read: $!";
return undef;
}
$r = unpackfcall($a.$b);
if(!defined $r){
$@ = "unpackfcall: $@";
return undef;
}
return $r;
}
sub writefcall(*;\%) {
my $io = Symbol::qualify_to_ref(shift, caller);
my $ofcall = shift;
my ($b, $n);
$b = packfcall(%$ofcall);
if(!defined $b){
$@ = "packfcall: $@";
return undef;
}
$n = syswrite($io, $b, length($b));
if(!defined $n){
$@ = "syswrite: $!";
return undef;
}
return $n;
}
require 'Plan9str.pl';
require 'Plan9fs.pl'; # see also ./Plan9fs.t
require 'Plan9srv.pl'; # see also ./Plan9srv.t
1;
|