#!/usr/bin/perl -w
require 5;
use File::stat;
use Fcntl;
$0=~s!.*/!!;

BEGIN {
	open(ERRORS, ">&STDERR");
	open(STDERR, ">/dev/null");
	unless (eval "import Fcntl ':mode'; 1")
	{
		*{"main::S_IFMT"} = sub { 0170000 };
		*{"main::S_IFSOCK"} = sub { 0140000 };
		*{"main::S_IFLNK"} = sub { 0120000 };
		*{"main::S_IFREG"} = sub { 0100000 };
		*{"main::S_IFBLK"} = sub { 0060000 };
		*{"main::S_IFDIR"} = sub { 0040000 };
		*{"main::S_IFCHR"} = sub { 0020000 };
		*{"main::S_IFIFO"} = sub { 0010000 };
	}
	open(STDERR, ">&ERRORS");
	close(ERRORS);
}

use strict;

$|=1;

my $packedintlen=length(pack("i", 0));
my $hostname=`hostname`;
chomp($hostname);
my ($instream, $outstream)=(*STDIN, *STDOUT);
senddataitem("ftserver on $hostname ready");
my $cwd=`pwd`;
chomp($cwd);
senddataitem($cwd);
my $cmd;

while($cmd=recvdataitem())
{
    if ($cmd eq "close") {
	exit(0);
    }
    if ($cmd eq "ls") {
	do_ls();
    } elsif ($cmd eq "dir") {
	do_dir();
    } elsif ($cmd eq "cd") {
	do_cd();
    } elsif ($cmd eq "fileexists") {
	do_fileexists();
    } elsif ($cmd eq "get") {
	do_get();
    } elsif ($cmd eq "put") {
	do_put();
    } else {
	senddataitem("ERROR");
	senddataitem("Command not understood");
    }
}

sub recvdataitem
{
    my $packedlen;
    my ($bytes, $error)=dosysread($instream, $packedlen, $packedintlen);
    die("$0: Read error in recvdataitem\n") if $error;
    die("$0: Unexpected EOF\n") if $bytes!=$packedintlen;
    my $len=unpack("i", $packedlen);
    my $data;
    ($bytes, $error)=dosysread($instream, $data, $len);
    die("$0: Read error in recvdataitem\n") if $error;
    die("$0: Unexpected EOF\n") if $bytes!=$len;
    return $data;
}

sub senddataitem
{
    my $data=shift;
    my $len=length($data);
    my $packedlen=pack("i", $len);
    my ($bytes, $error)=dosyswrite($outstream, $packedlen, $packedintlen);
    die("$0: Write error in senddataitem\n") if $error;
    ($bytes, $error)=dosyswrite($outstream, $data, $len);
    die("$0: Write error in senddataitem\n") if $error;
}

sub dosysread
{
    my ($fh, $data, $len)=@_;
    my $bytesread=0;

    $data="";
    while ($len)
    {
	my $thisdata;
	my $bytes=sysread($fh, $thisdata, $len);
	unless (defined($bytes))
	{
	    my $error="$!";
	    warn("$0: read: $error\n");
	    return ($bytesread, $error);
	}
	last if $bytes==0;
	$data.=$thisdata;
	$len-=$bytes;
	$bytesread+=$bytes;
    }
    $_[1]=$data;
    return ($bytesread, undef);
}

sub dosyswrite
{
    my ($fh, $data, $len)=@_;
    my $byteswritten=0;

    while ($len)
    {
	my $bytes=syswrite($fh, $data, $len);
	unless (defined($bytes))
	{
	    my $error="$!";
	    warn("$0: write: $error\n");
	    return ($byteswritten, $error);
	}
	substr($data, 0, $bytes, "");
	$len-=$bytes;
	$byteswritten+=$bytes;
    }
    return $byteswritten;
}

sub do_ls
{
    opendir(DIR, ".") || die("$0: opendir . : $!\n");
    my @files=readdir(DIR);
    closedir(DIR);
    my $files=join("\0", @files);
    senddataitem("OK");
    senddataitem($files);
}

sub do_dir
{
    opendir(DIR, ".") || die("$0: opendir . : $!\n");
    my @files=readdir(DIR);
    closedir(DIR);
    my $dirdata="";
    my $file;
    for $file (@files)
    {
	    my $stat=lstat($file);
	    $dirdata.="$file\0";
	    unless(defined($stat))
	    {
		    $dirdata.="ERROR\0$!\0";
		    next;
	    }	    
	    $dirdata.=join("\0", "OK",
	    ( $stat->dev, $stat->ino, $stat->mode, $stat->nlink,
	      lookup_uid($stat->uid), lookup_gid($stat->gid),
	      $stat->rdev, $stat->size,
	      $stat->atime, $stat->mtime, $stat->ctime,
	      $stat->blksize, $stat->blocks, ""));
    }
    senddataitem("OK");
    senddataitem($dirdata);
}

my %uid;
sub lookup_uid
{
	my $uid=shift;
	$uid{$uid}=getpwuid($uid) unless exists($uid{$uid});
	return $uid{$uid}? $uid{$uid}:"uid:".$uid;
}

my %gid;
sub lookup_gid
{
	my $gid=shift;
	$gid{$gid}=getgrgid($gid) unless exists($gid{$gid});
	return $gid{$gid}? $gid{$gid}:"gid:".$gid;
}

sub do_cd
{
    my $dir=recvdataitem();
    unless (chdir($dir))
    {
	my $error="$!";
	senddataitem("ERROR");
	senddataitem("cd: $error");
	return;
    }
    senddataitem("OK");
    $cwd=`pwd`;
    chomp($cwd);
    senddataitem($cwd);    
}

sub do_fileexists
{
    my $file=recvdataitem();
    unless (-e $file)
    {
	senddataitem("ERROR");
	senddataitem("Object does not exist");
	return;
    }
    unless (-f $file)
    {
	my $error="$!";
	senddataitem("ERROR");
	senddataitem("Object is not a file");
	return;
    }
    senddataitem("OK");
}

sub do_get
{
       my $file=recvdataitem();
       unless (open(FILE, "<$file"))
       {
	   my $error=$!;
	   senddataitem("ERROR");
	   senddataitem("$error");
	   return;
       }
       my $stat=lstat(*FILE);
       unless (defined($stat))
       {
	   my $error=$!;
	   senddataitem("ERROR");
	   senddataitem("$error");
	   return;
       }
       unless (($stat->mode & S_IFMT) == S_IFREG)
       {
	   senddataitem("ERROR");
	   senddataitem("Not a regular file");
	   return;
       }
       my $data;
       my ($bytes, $error)=dosysread(*FILE, $data, 4096);
       while ($bytes && !$error)
       {
	   senddataitem("DATA");
	   senddataitem($data);
	   ($bytes, $error)=dosysread(*FILE, $data, 4096);
       }
       close(FILE);
       senddataitem("OK");
}

sub do_put
{
       my $file=recvdataitem();
       unless (open(FILE, ">.$file.new"))
       {
	   my $error=$!;
	   senddataitem("ERROR");
	   senddataitem("$error");
	   return;
       }
       senddataitem("OK");
       my $status=recvdataitem();
       my ($bytes, $error);
       while ($status eq "DATA")
       {
	   my $data=recvdataitem();
	   ($bytes, $error)=dosyswrite(*FILE, $data, length($data))
	       if length($data) && !$error;
	   $status=recvdataitem();
       }
       close(FILE);
       if ($status ne "OK")
       {
	   unlink(".$file.new");
	   recvdataitem();
	   return;
       }
       unless (rename(".$file.new", "$file"))
       {
	   my $error=$!;
	   senddataitem("ERROR");
	   senddataitem("rename: $error");
	   return;
       }
       senddataitem("OK");
}
