fixed problems with show/channel
[spider.git] / perl / DXCommandmode.pm
1 #!/usr/bin/perl
2 #
3 # This module impliments the user facing command mode for a dx cluster
4 #
5 # Copyright (c) 1998 Dirk Koopman G1TLH
6 #
7 # $Id$
8
9
10 package DXCommandmode;
11
12 @ISA = qw(DXChannel);
13
14 use DXUtil;
15 use DXChannel;
16 use DXUser;
17 use DXVars;
18 use DXDebug;
19
20 use strict;
21 use vars qw( %Cache $last_dir_mtime @cmd);
22
23 $last_dir_mtime = 0;          # the last time one of the cmd dirs was modified
24 @cmd = undef;                 # a list of commands+path pairs (in alphabetical order)
25
26 # this is how a a connection starts, you get a hello message and the motd with
27 # possibly some other messages asking you to set various things up if you are
28 # new (or nearly new and slacking) user.
29
30 sub start
31
32   my ($self, $line) = @_;
33   my $user = $self->{user};
34   my $call = $self->{call};
35   my $name = $user->{name};
36
37   $self->{name} = $name ? $name : $call;
38   $self->msg('l2',$self->{name});
39   $self->send_file($main::motd) if (-e $main::motd);
40   $self->msg('pr', $call);
41   $self->state('prompt');                  # a bit of room for further expansion, passwords etc
42   $self->{priv} = $user->priv;
43   $self->{priv} = 0 if $line =~ /^(ax|te)/;     # set the connection priv to 0 - can be upgraded later
44   $self->{consort} = $line;                # save the connection type
45   $self->sort('U');                        # set the channel type
46 }
47
48 #
49 # This is the normal command prompt driver
50 #
51 sub normal
52 {
53   my $self = shift;
54   my $user = $self->{user};
55   my $call = $self->{call};
56   my $cmdline = shift; 
57
58   # strip out //
59   $cmdline =~ s|//|/|og;
60   
61   # split the command line up into parts, the first part is the command
62   my ($cmd, $args) = $cmdline =~ /^([\w\/]+)\s*(.*)/o;
63
64   if ($cmd) {
65
66     # first expand out the entry to a command
67     $cmd = search($cmd);
68
69     my @ans = $self->eval_file($main::localcmd, $cmd, $args);
70         @ans = $self->eval_file($main::cmd, $cmd, $args) if !$ans[0];
71         if ($ans[0]) {
72       shift @ans;
73           $self->send(@ans) if @ans > 0;
74         } else {
75       shift @ans;
76           if (@ans > 0) {
77             $self->msg('e2', @ans);
78           } else {
79         $self->msg('e1');
80           }
81         }
82   } else {
83     $self->msg('e1');
84   }
85   
86   # send a prompt only if we are in a prompt state
87   $self->prompt() if $self->{state} =~ /^prompt/o;
88 }
89
90 #
91 # This is called from inside the main cluster processing loop and is used
92 # for despatching commands that are doing some long processing job
93 #
94 sub process
95 {
96   my $t = time;
97   my @chan = DXChannel->get_all();
98   my $chan;
99   
100   foreach $chan (@chan) {
101     next if $chan->sort ne 'U';  
102
103     # send a prompt if no activity out on this channel
104     if ($t >= $chan->t + $main::user_interval) {
105       $chan->prompt() if $chan->{state} =~ /^prompt/o;
106           $chan->t($t);
107         }
108   }
109 }
110
111 #
112 # finish up a user context
113 #
114 sub finish
115 {
116
117 }
118
119 #
120 # short cut to output a prompt
121 #
122
123 sub prompt
124 {
125   my $self = shift;
126   my $call = $self->{call};
127   DXChannel::msg($self, 'pr', $call);
128 }
129
130 #
131 # search for the command in the cache of short->long form commands
132 #
133
134 sub search
135 {
136   my $short_cmd = shift;
137   return $short_cmd;    # just return it for now
138 }  
139
140 #
141 # the persistant execution of things from the command directories
142 #
143 #
144 # This allows perl programs to call functions dynamically
145
146 # This has been nicked directly from the perlembed pages
147 #
148
149 #require Devel::Symdump;  
150
151 sub valid_package_name {
152   my($string) = @_;
153   $string =~ s/([^A-Za-z0-9\/])/sprintf("_%2x",unpack("C",$1))/eg;
154   
155   #second pass only for words starting with a digit
156   $string =~ s|/(\d)|sprintf("/_%2x",unpack("C",$1))|eg;
157         
158   #Dress it up as a real package name
159   $string =~ s|/|_|g;
160   return "Emb_" . $string;
161 }
162
163 #borrowed from Safe.pm
164 sub delete_package {
165   my $pkg = shift;
166   my ($stem, $leaf);
167         
168   no strict 'refs';
169   $pkg = "DXChannel::$pkg\::";    # expand to full symbol table name
170   ($stem, $leaf) = $pkg =~ m/(.*::)(\w+::)$/;
171         
172   my $stem_symtab = *{$stem}{HASH};
173         
174   delete $stem_symtab->{$leaf};
175 }
176
177 sub eval_file {
178   my $self = shift;
179   my $path = shift;
180   my $cmdname = shift;
181   my $package = valid_package_name($cmdname);
182   my $filename = "$path/$cmdname.pl";
183   my $mtime = -M $filename;
184   
185   # return if we can't find it
186   return (0, DXM::msg('e1')) if !defined $mtime;
187   
188   if(defined $Cache{$package}{mtime} && $Cache{$package}{mtime } <= $mtime) {
189     #we have compiled this subroutine already,
190         #it has not been updated on disk, nothing left to do
191         #print STDERR "already compiled $package->handler\n";
192         ;
193   } else {
194         local *FH;
195         if (!open FH, $filename) {
196           return (0, "Syserr: can't open '$filename' $!"); 
197         };
198         local($/) = undef;
199         my $sub = <FH>;
200         close FH;
201                 
202     #wrap the code into a subroutine inside our unique package
203         my $eval = qq{package DXChannel; sub $package { $sub; }};
204         if (isdbg('eval')) {
205           my @list = split /\n/, $eval;
206           my $line;
207           foreach (@list) {
208             dbg('eval', $_, "\n");
209           }
210         }
211         #print "eval $eval\n";
212         {
213           #hide our variables within this block
214           my($filename,$mtime,$package,$sub);
215           eval $eval;
216         }
217         if ($@) {
218           delete_package($package);
219           return (0, "Syserr: Eval err $@ on $package");
220         }
221                 
222         #cache it unless we're cleaning out each time
223         $Cache{$package}{mtime} = $mtime;
224   }
225   
226   my @r;
227   my $c = qq{ \@r = \$self->$package(\@_); };
228   dbg('eval', "cluster cmd = $c\n");
229   eval  $c; ;
230   if ($@) {
231     delete_package($package);
232         return (0, "Syserr: Eval err $@ on cached $package");
233   }
234
235   #take a look if you want
236   #print Devel::Symdump->rnew($package)->as_string, $/;
237   return @r;
238 }
239
240 1;
241 __END__