#!/local/bin/perl
#
# fspcli, by Nicolai Langfeldt (janl@ifi.uio.no)
# Copyleft 1992, Nicolai Langfeldt

$version="1.0";

### Programmer documentation
#
# These globals are used:
#   $dir	The working directory on the fsp server
#   $server	The fsp server in use now, decorational purposes only
#   $servern	The nickname of current server ("" if nickname wasn't used)
#   $version	fspcli version
#   $verbose	0: quiet, 1: normal, 2: debugging
#   $usage	The "Usage:" string for flaming fumblers :-)
#   $trace	FSP_TRACE set or unset
#   $tmp	Name of the tmp directory we use
#   $fspprogs   See below
#   $uncompr	See below
#   $compress	See below
#   $ping	See below
#   $rm		See below
#   $beep	Beep when file x-fers are finnished
#   $prompt	What we eval to get the prompt
#   $PG		Pager, either == $ENV{'PAGER'} or "more"
#   $ED		Editor, either == $ENV{'EDITOR'} or "vi"
#   @help	Contains the help texts
#   $help	0 or 1 if DATA read into @help
#   %hostname	Array of servers, lookup by nickname
#   %portno	Array of ports, lookup by nickname
#   %comment	Array of descriptions by nickname
#   %initdir	Initial directory on server by nickname
#   @ignoresigs Signals we want to ignore while spawned program is running
#
# These environment variables are used:
#   HOME	Where to find .fspclirc file
#   PAGER	Used for less/more/zmore. If not set more is used.
#   EDITOR	Used for message/redit. If not set vi is used.
#
# These environment variables are maintained:
#   FSP_TRACE	  (set to "yes"). Get progress info when x-fering files
#   FSP_LOCALPORT 0, unix finds local port that can be used.
#   FSP_DIR	  Same value/meaning as $dir
#   FSP_HOST	  Full name of the fsp server/host
#   FSP_PORT	  Port on the fsp server to connect to
#
# Credits:
#   Me (janl@ifi.uio.no): writing this thingy
#   Ove Ruben R Olsen (ruben@uib.no): We wrote our clients at the same time,
#     talked a lot about them on irc and read eachothers code. It's impossible
#     to keep account of who invented what feature. Hei Ruben! :)
#     This cooperation has resulted in some creeping featurism, but the
#     shells are much better now than before... :)
#   Christer Jansson (svarten@Abacus.HGS.SE): testing
#
# ############################## Configuration #############################

$fspprogs = "/hom/janl/bin/sun4";    # Placement of fsp binaries

# Actually, if you can stand to wait the time it takes to search the path
# for these progs you can leave out the path part, but the correct thing to
# do is put the path part in there.

$uncompr  = "/local/bin/uncompress"; # Full path/name of uncompress
$compress = "/local/bin/compress";   # Full parh/name of compress
$ping     = "/usr/etc/ping";	     # Full path/name of ping
$rm	  = "/bin/rm -f";	     # No questions asked rm command

# These should probably be left as is.
$verbose = 1;		
$trace = 1;		# Dictates setting of FSP_TRACE

# ############################### Initiation ###############################

@ignoresigs = ('INT','QUIT');

$server = 0;		# no server yet.
$dir = 0;		# no cwd yet
$help = 0;		# Help not read yet
$beep = 1;		# Don't beep
$prompt = '$server:$dir>'; # Default prompt. Keep the tick quotes.

# Some handy stuff:
$tmp = "/tmp/fspcli.$$";
$mtmp = "mkdir $tmp";
$ctmp = "cd $tmp";
$rtmp = "cd /;$rm $tmp";
$mctmp = "$mtmp;$ctmp";

$PG = $ENV{'PAGER'} || "more";
$ED = $ENV{'EDITOR'} || "vi";

# Read list of nicknames, servers, and ports
&readserverlist(1,"$ENV{'HOME'}/.fspclirc");

# Parse arguments
&parseargs(split(/\s/,$ENV{'FSPCLI'}));
&parseargs(@ARGV);
  
&output(2,'print "Fspcli version: Original IFI version $version, December 1992\n";');
&output(2,'&mysys("$fspprogs/fver -l");');

&settrace;

# ########################### Command interpreter. #########################
#
# i.e. main loop.
# It leavs something to be desired, but it works fairly well imho.
#

&output(1,'print "No server set, use \"server\" command to set.\n";')
							   unless ($server);

eval "print \"$prompt\"";

while (<STDIN>) {
  # Remove \n and blanks at start/end of command
  chop;
  s/^\s*//g;
  s/\s*$//g;

  # Get arguments from command
  $args="" unless ($args)=/^\w*\s*(.*)$/;
  $rargs=$args; 
#  print "/$args/ /$rargs/\n";
  $rargs=~s/([\*"'`\]\[])/\\$1/g; # Escape shell chars for remote use

  if (/^!(.*)/) {
    &mysys("$1"); # nothing is escaped...
  } elsif (/^prompt /) {
    $prompt=$args;
  } elsif (/^pwd$/) {
    print "$dir\n";
  } elsif (/^hinfo$/) {
    if (!$servern=="") {
      print "Server nickname: $servern\n";
      print "Description: $comment{$servern}\n";
      print "Initial directory: $initdir{$servern}\n";
    }
    print "Full name of server: $ENV{'FSP_HOST'}\n";
    print "Current directory: $ENV{'FSP_DIR'}\n";
    print "Port: $ENV{'FSP_PORT'}\n";
    print "Local port: $ENV{'FSP_LOCALPORT'}\n";
  } elsif (/^cd$/) {
    &cd("/");
  } elsif (/^cd /) {
    &cd($rargs);
  } elsif (/^ls |^ls$/) {
    &mysys("$fspprogs/flscmd $rargs");
  } elsif (/^dir |^dir$|^v |^v$/) {
    &mysys("$fspprogs/flscmd -l $rargs");
  } elsif (/^get |^mget /) {
    &signals('IGNORE');
    foreach $file (split(/\s+/,$args)) { # Loop over arguments
      if (!open(PIPE,"| $fspprogs/fgetcmd")) {
        print "Can't spawn fgetcmd\n";
	next;
      }
      print PIPE $file;
      close(PIPE);
    }
    &signals('DEFAULT');
    &beep;
  } elsif (/^put |^mput |^send /) {
    &signals('IGNORE');
    foreach $file (split(/\s+/,$args)) { # Loop over arguments
      if (!open(PIPE,"| $fspprogs/fput")) {
        print "Can't spawn fput\n";
	next;
      }
      print PIPE $file;
      close(PIPE);
    }
    &signals('DEFAULT');
    &beep;
  } elsif (/^zput /) {
    &mysys("$mtmp;cp $args $tmp;$ctmp;$compress $args;$fspprogs/fput $args;$rtmp");
  } elsif (/^zget /) {
    &mysys("$fspprogs/fgetcmd $args;$uncompr $args;");
  } elsif (/^local /) {
    $ENV{'FSP_LOCALPORT'}=$args;
  } elsif (/^server$|^server |^open$|^open /) {
    &getservername($args);
  } elsif (/^lcd |^lcd$/) {
    chdir($args);
    printf("Local directory is now: %s",`pwd`);
  } elsif (/^quit|^bye$/) {
    die "*Poof*\n"; # Anyone verbose enough to type 'bye' deservs a *poof*
  } elsif (/^help/) {
    &help($args);
  } elsif (/^cat /) {
    &traceoff;
    &mysys("$fspprogs/fcatcmd $rargs");
    &settrace;
  } elsif ((/^less |^more /)) {
    &traceoff;
    &mysys("$fspprogs/fcatcmd $rargs | $PG");
    &settrace;
  } elsif (/^zcat /) {
    &traceoff;
    &mysys("$fspprogs/fcatcmd $rargs | $uncompr");
    &settrace;
  } elsif (/^zmore |^zless /) {
    &traceoff;
    &mysys("$fspprogs/fcatcmd $rargs | $uncompr | $PG");
    &settrace;
  } elsif (/^mkdir |^md /) {
    &mysys("$fspprogs/fmkdir $args");
  } elsif (/^pro |^chmod /) {
    &mysys("$fspprogs/fprocmd $rargs");
  } elsif (/^rm |^del /) {
    &mysys("$fspprogs/frmcmd $rargs");
  } elsif (/^rmdir |^rd /) {
    &mysys("$fspprogs/frmdircmd $rargs");
  } elsif (/^msg |^touch /) {
    $args =~ s/\s+/_/;
    &mysys("$mctmp;touch $args;$fspprogs/fput $args;$rtmp");
  } elsif (/^message /) {
    $args =~ s/\s+/_/;
    &mysys("$mctmp;$ED $args;$fspprogs/fput $args;$rtmp");
  } elsif (/^redit |^edit /) {
    &mysys("$mctmp;$fspprogs/fgetcmd $args;$ED $args;$fspprogs/fput $args;$rtmp");
  } elsif (/^trace\s+on$/) {
    $trace=1;
    &settrace;
  } elsif (/^trace\s+off$/) {
    $trace=0;
    &settrace;
  } elsif (/^beep\s+on$/) {
    $beep=1;
  } elsif (/^beep\s+off$/) {
    $beep=0;
  } elsif (/^verbose$/) {
    $verbose=2;
  } elsif (/^brief$/) {
    $verbose=0;
  } elsif (/^normal$/) {
    $verbose=1;
  } elsif (/^ping$/) {
    &mysys("$ping $ENV{FSP_HOST}");
  } elsif (/^lservers$|^list$/) {
    &listserv;
  } elsif (/^ver$/) {
    &mysys("$fspprogs/fver");
    &mysys("$fspprogs/fver -l");
  } else {
    print "Unknown command or wrong # of argument(s)\n";
  }
  eval "print \"\r$prompt\"";
#  print "\r$server:$dir>";
}

&output(1,'print "\n*Poof*";');
print "\n";

# ############################### Subroutines #############################

sub cd {
  local($tmpd); # Don't want to nuke old dir before new dir is secured

  &signals('IGNORE');
  if (open(PIPE,"$fspprogs/fcdcmd @_[0] |")) {
    chop($tmpd=<PIPE>);
    if (close(PIPE)) {
      $ENV{'FSP_DIR'}=$dir=$tmpd;
    } else {
      print "Chdir command failed with error $?: $!\n";
    }
  } else {
    print "Chdir command ($fspprogs/fcdcmd) failed with error $!, code $?\n";
  }
  &signals('DEFAULT');
}


sub readserverlist {
  # Read serverlist. If called more than once it's
  # additive since the hosts are stored in associative arrays.
  local($complain,$file)=@_;
  local($nick,$server,$port,$dir,$comment);

  if (!open(SERVERS,$file)) {
    print "Can't open $file.\nDo \"help fspcli\" for help on how to make it\n"
      if ($complain);
  } else {
    while (<SERVERS>) {
      chop;
      s/#.*//; 			# remove comments
      next if (/^\s*$/);	# Ignore empty lines
      die "Error in $file on line $.\n"
        unless ($server,$port,$nick,$dir,$comment)=
	                   /^\s*(\S+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(.*)\s*$/;
      $hostname{$nick}=$server;
      $portno{$nick}=$port;
      $comment{$nick}=$comment;
      $initdir{$nick}=$dir;
    }
    close(SERVERS);
  }
}


sub getservername {
  # Get a new server name and set some globals accordingly, if no servername
  # isn't given as nick one is asked for.
  # We know two kinds of server specs, with port number, then we take server
  # name literaly, and without port number, then we use the associative
  # arrays to look the server up.

  local($nick)=@_;
  local($port,$gotserver);

  ($nick,$port)=split(/\s+/,$nick);
  if ($port) { # In that case there were two arguments
    $server=$nick;
    $dir="/";
    $gotserver=1;
    $servern="";
  } elsif ($hostname{$nick}) {
    $servern=$nick;
    $server=$hostname{$nick};
    $port=$portno{$nick};
    $dir=$initdir{$nick};
    $gotserver=1;
  } else {
    print "No such server: \"$nick\". \"list\" will print a list of known servers\n";
    $gotserver=0;
    $servern="";
  }
  if ($gotserver) {
    &output(2,'print "Server is $server, port $port\n";');
    $ENV{'FSP_HOST'}=$server;
    $ENV{'FSP_PORT'}=$port;
    $ENV{'FSP_LOCALPORT'}="0";
    $ENV{'FSP_DIR'}="/";
    &cd($dir);
    &output(2,'&mysys("$fspprogs/fver");');
  }
}


sub output {
  # Do command $command if we are at right verboseness level

  local($level,$command)=@_;
  
  eval $command if ($level<=$verbose);
}


sub parseargs {
  # Flag + server parsing

  # Have usage here where it's parsed :-)
  $usage = "Usage: fspcli [-q|-d|-v n|-nt|-t|-help] [server|full-server-name <port>]";

  while ($#_ >= 0) {
    $_ = shift;
    if (/^-q$/) { $verbose = 0; next; }
    if (/^-d$/) { $verbose = 2; next; }
    if (/^-v$/) { $verbose = shift; next; }
    if (/^-nt$/) { $trace = 0; next; }
    if (/^-t$/) { $trace = 1; next; }
    if (/^-b$/) { $beep = 1; next; }
    if (/^-p$/) { $prompt = shift; $prompt =~ s/_/ /g; next; }
    if (/^-help$/) { &arghelp; } # arghelp terninates
    if (/^-.*/) { print "$usage\n"; exit 1;}
    &getservername(join(" ",($_,@_)));
    shift;
  }
}


sub traceoff {
  # Set tracing off
  delete $ENV{'FSP_TRACE'};
}


sub traceon {
  # Set tracing off
  $ENV{'FSP_TRACE'}="";
}


sub settrace {
  # Set traceing to value according to $trace
  if ($trace) { &traceon; } else { &traceoff;  }
}


sub help {
  # Show help texts.
  local($cmd) = @_;
  local($dummy);

  $cmd='cmds' if ($cmd !~ /\w|\!/); 

  if (!$help) {
    while (<DATA>) {
      push (@help,$_);
    }
    $help=1;
  }
  
  foreach (grep(/^$cmd -/,@help)) {
    /^$cmd -(.*)/;
    print "$1\n";
  }
}


sub signals {
  local($action)=@_;

  foreach (@ignoresigs) {
    $SIG{$_} = $action;
  }
}


sub mysys {
  # My own system call, with error checking
  local($cmd)=@_;
  local($err);

  &signals('IGNORE');
  $err=system($cmd);
  &signals('DEFAULT');

  print "Error $err doing system(\"$cmd\")\n" if (($err) && ($err!=768));
}


sub arghelp {
  # Help for arguments

  print "$usage

  -q		Quiet, almost no output (verboseness=0)
  -d		Debug output (verboseness=2)
  -v n		Verboseness=n, default is 1, normal
  -nt		No trace. When tracing the put/get cmd's print how many
		  Kb have been xfered until now
  -t		Trace on, this is default
  -b		Beep when file x-fers are finnished.
  -p prompt	Set the fspcli prompt. See notes in \"help prompt\".
  -help		Display this help

You can give default options with the environment varaible FSPCLI, the
flags supplied on command line takes precedence over the environment
varaible.

fspcli has builtin help; use the \"help\" command.
First time you try fspcli do \"help fspcli\".
";
  exit 0;
}


sub listserv {
  # Print list of servers
  local($listform)="%15s  %-60s\n";
  local($server,$i);
  
  $i=0;
  while (($nick,$server)=each %hostname) {
    if (!$i) {
      printf($listform,"Server","Description");
      printf($listform,"------","-----------");
    }
    printf($listform,$nick,$comment{$nick});
    $i++;
  }
  print "Hmm, seems I know of no servers, do \"help server\" on how to get to
one anyway, and \"help fspcli\" on how to tell me of servers.\n" unless ($i);
}

sub beep {
  print "\007" if ($beep);
}

__END__
cmds -Command summary for fspcli:
cmds -
cmds -Local commands:
cmds -	!		help		lcd		local
cmds -	quit/bye/^D
cmds -
cmds -Remote commands:
cmds -	beep		brief		cat		cd
cmds -	dir/v		edit/redit	get/mget	hinfo
cmds -	less/more	list/lservers	ls		message
cmds -	mkdir/md	msg/touch	normal		ping
cmds -	pro/chmod	put/mput/send	pwd		rm/del
cmds -	rmdir/rd	server/open	trace		ver
cmds -	verbose		zcat		zmore/zless
cmds -
cmds -Other topics:
cmds -	fspcli
cmds -
cmds -Use "help <command>" to get help for a specific command.
cmds -You can interupt cd/ls/get/put/cat/rm/mkdir/rmdir/pro/msg by pressing
cmds -^C twice. Like if they use more time than you like.
v -v [flags] [filespec]
v -  Alias for 'dir', it's here just to help me, i have 'v' in my fingers.
v -  v is a gnu variant of 'ls'.
message -message <filename>
message -  Used to make a file containing something (like a message) and
message -  send it to the server: Your favourite editor is invoked to edit
message -  the given file, it is sent to the server, and then deleted from
message -  your machine. Any spaces in the filename are replaced by
message -  underline "_".
edit -edit <filename>
edit -  Used to edit a file on the remote machine. It gets the file, starts
edit -  your favourite editor, and then sends the file back. This requires
edit -  that you have delete permission in the remote directorty.
redit -See "help edit".
list -list
list -  Print a list of known servers.
lservers -See "help list"
verbose -verbose
verbose -  A lot of messages will be printed.
brief -brief
brief -  Almost no messages will be printed.
normal -normal
normal -  Some messages will be printed.
ping -ping
ping -  ping the fsp host.
! -!<shell command(s)>
! -  Shell escape.
lcd -lcd [localdir]
lcd -  Local cd, sets directory where files are gotten to or put from.
lcd -  lcd with no argument changes local dir to $HOME.
local -local <portno>
local -  Useless command that sets the port number to use localy, set it to
local -  0 and unix will find a port you can use. fspcli sets it to 0 all
local -  by itself :-) The local port number is reset to 0 by the server/open
local -  command.
help -help [command]
help -  8->
bye -See "help quit"
quit -quit
quit -  Quit fspcli
open -See "help server"
server -server
server -server <server>
server -server <full server name> <portno>
server -  Change/set server. If no argument is given then a prompt asking
server -  for a server is issued. If you give unknown server (just Return
server -  is unknown) a list of known servers is printed.
server -  The second form will print list and issue prompt if the given
server -  server is unknown.
server -  The third form allows you to give full server name and port
server -  so the server don't have to be in the server lists. The full
server -  spec can be given at the "Server:" prompt as too.
cd -cd [remotedir]
cd -  Change directory on server. If no argument is given "/" is assumed.
ls -ls [flags] [filespecs]
ls -  Do ls on server. You can do stuff like 'ls -lR >ls-lR' if you like
ls -  (but getting a ready made index is a lot better).
dir -dir [flags] [filespecs]
dir -  Alias for "ls -l"
get -get <filespec>
get -  Get files from server. Wildcards ('*' and family) is expanded on
get -  the server.
mget -See "help get".
put -put <filespec>
put -  Put files on server. Wildcards ('*' and family) is expanded localy.
mput -See "help put".
send -See "help put".
cat -cat <filespec>
cat -  cat a file from server onto screen or onto file "cat foo >bar".
zcat -zcat <file>
zcat -  Like cat except that the file is uncompressed before it's shown.
zcat -  Should only be used for one file at a time.
less -See "help more"
more -more <file>
more -  View file from server with help from your favourite pager.
more -  If no PAGER environment variable is set 'more' is used.
more -  A <filespec> can be used in place of <file>, but all files
more -  will be considered as the same file by more.
zmore -zmore <file>
zmore -  Like more except that the file is uncompressed before it's shown.
zmore -  Should only be used for one file at a time.
zless -See "help zmore"
rd -See "help rmdir"
rmdir -rmdir <filespec>
rmdir -  Remove directory(s) (from server).
md -See "help mkdir"
mkdir -mkdir <filespec>
mkdir -  Make directory on server, globbing is done localy since it's
mkdir -  meaningless to do it remotely and local globing allows easy(er)
mkdir -  directory tree duplication. Set permissions for the directory
mkdir -  with "pro" command afterwards.
pro -pro [+c|-c|+d|-d] <dir> 
pro -  get or set permissions for directories.
pro -    +c Give others permission to create new items.
pro -    -c Deny others permission to create new items.
pro -    +d Give others permission to delete old items.
pro -    -d Deny others permission to delete old items.
chmod -See "help pro"
msg -msg <message>
msg -  Make a empty file with given name on server. This is much better
msg -  than doing mkdir to send messages. Any spaces in the message are
msg -  replaced by underlines "_". Se 'message' if you want to send a
msg -  long message.
touch -See "help msg"
del -See "help rm"
rm -rm <filespec>
rm -  Remove a file from server
trace -trace <on|off>
trace -  When trace is on fsp prints how many Kb of the file being
trace -  transfered that has actually been transfered. When trace is
trace -  off it dosn't say anything.
fspcli -Invoking fspcli
fspcli -  fspcli may be given commandline arguments. For a list of those
fspcli -  do 'fspcli -help' at a unix prompt
fspcli -
fspcli -Server list
fspcli -  In you home directory you can have a file named .fspclirc . In it
fspcli -  you can store to details on the fsp servers you know. This is the
fspcli -  format:
fspcli -     "full-server-name port# nickname directory description"
fspcli -  All fields are required. Wuarchive can be described thusly:
fspcli -     "wuarchive.wustl.edu     21      wu       / Washington univ."
fspcli -  Comments are started with "#" and can appear alone on lines or on
fspcli -  ends of lines.
fspcli -
fspcli -Environment variable
fspcli -  The commandline arguments may also be put in a environment variable
fspcli -  named FSPCLI. Example: "setenv FSPCLI -q". Since this variable
fspcli -  is parsed just like if it was commandline arguments you can supply
fspcli -  a default server with it too: "setenv FSPCLI -q wu".
fspcli -  Commandline options overrule whatever you have in FSPCLI
ver -ver
ver -  Print version of fsp software here and on server
beep -beep <on|off>
beep -  Set on if fspcli should beep when file xfers are finnished.
beep -  Handy if you use screen, or have loads of windows, or are
beep -  looking other way while xfering.
pwd -pwd
pwd -  Echos your current working directory on the server.
hinfo -hinfo
hinfo -  Print all known information about the current server
prompt - prompt <prompt>
prompt -   Configure the fspcli prompt. You can put any text and these
prompt -   variables into the prompt:
prompt -     $dir    pwd on server
prompt -     $server full name of server
prompt -   The default prompt is "$server:$dir>".
prompt -   The prompt can also be set from the commandline/environment,
prompt -   when set thus the whole prompt string needs to be in one piece,
prompt -   this can be done by replacing any spaces " " with "_", fspcli
prompt -   replaces all underscores "_" are with space(es) " ".
