#!/usr/bin/perl -w

# Copyright (C) 25-11-2001 Fredrik Roubert <roubert@df.lth.se> 
# Modified 2001-12-21 by John Bowman <bowman@math.ualberta.ca>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#

require 5;

use strict;
use FileHandle;
use SNFS;
use Getopt::Std;
#use User::pwent;

sub mount ($;$$$);

our ($opt_d, $opt_l, $opt_r, $opt_o, $opt_m, $opt_u, $opt_g, $opt_f, 
	$opt_s, $opt_a);
$opt_d = $opt_f = $opt_a = 0;


my ($name,$uid,$gid);
my ($remote_host, $remote_path, $opt_mountprog,	$opt_nfsprog, $mountprog, 
 $fstab_mountprog, $host, $mount_options, $mount_fs, $conf_host, 
 $conf_mountprog, $host_mountprog, $file);

my ($hostname, $pid_dir, $conf_dir, $log_dir, $user_dir, $psrv_user,
	$psrv_uid, $psrv_gid);

sub usage()
{
	print STDERR "Usage: $0 [-d][-f][-l user][-m mountprog][-u uid][-g gid]\n    [-s agentfile][-r host[:rpath]][-o option,...] path | -a\n";
	exit 1;
}

# parse command line
getopts('dfal:r:o:m:u:g:s:');

unless ((($#ARGV == 0) and ! $opt_a) || (($#ARGV < 0) and $opt_a))
{
	usage;
}
my $path = shift @ARGV;

my @conf_infos;
if (!defined(SNFS::get_conf(\@conf_infos, $opt_l, $opt_d)))
{
	die "Cannot determine configuration info";
}

($hostname, $conf_dir, $pid_dir, $log_dir, $name, $uid, $gid, $user_dir)
	= @conf_infos;

if ($uid == 0)
{
	$opt_l = undef;
}

if ($< == 0)
{
	if (($opt_a) and (defined($opt_l) || defined($opt_r) || $opt_f))
	{
		die "-a and -l or -r or -f incompatible";
	}
	if (defined($opt_l))
	{
		# verify that the uid and gid of the user the filesystem should
		# be mounted as are the same than the one provided by -u and -g

		if (defined($opt_u))
		{
			unless ($opt_u =~ /^\d+$/)
			{
				die "Bad -u option, not a numeric uid";
			}
			if ($opt_u != $uid)
			{
				exit(2);
			}
		}
		if (defined($opt_g))
		{
			unless ($opt_g =~ /^\d+$/)
       		{
				die "Bad -g option, not a numeric gid";
			}
			if ($opt_g != $gid)
			{
				exit(2);
			}
		}
		
		$psrv_user = $name;
		$psrv_uid = $uid;
		$psrv_gid = $gid;
		$name = undef;
	}
	elsif (defined($opt_g) || defined($opt_u) || defined($opt_s))
	{
		die "-g, -u and -s options only allowed when used with a -l option";
	}
	
	if (defined($opt_r))
	{
		if ($opt_r =~ /^[\w.\-]+$/)
		{
			$remote_host = $opt_r;
			$remote_path = "";
		}
		elsif ($opt_r =~ m%^([\w.\-]+):((/[\w.\-]*)+/?)$%)
		{
			$remote_host = $1;
			$remote_path = $2;
		}
		else
		{
			die "Bad -r option";
		}
		if (defined($opt_o))
		{
			if ($opt_o =~ /mountprog=(\d+)/)
			{
				$opt_mountprog = $1;
			}
			if ($opt_o =~ /nfsprog=(\d+)/)
			{
				$opt_nfsprog = $1;
			}
		}
		if (defined($opt_m))
		{
			unless ($opt_m =~ /^\d+$/)
			{
				die "Bad -m option"
			}
			if (defined($opt_mountprog) and ($opt_m != $opt_mountprog))
			{
				die "mountprog in -o and specified by -m don't match"
			}
		}
		elsif (defined($opt_mountprog))
		{
			$opt_m = $opt_mountprog;
		}
		if ($opt_d)
		{
			print STDERR "info: remote host $remote_host, remote path $remote_path\n";
			if (defined($opt_o))
			{
				my ($have_mountprog, $have_nfsprog);
				$have_mountprog = $have_nfsprog = 0;
				if (defined($opt_mountprog))
				{
					$have_mountprog = 1;
				}
				if (defined($opt_nfsprog))
				{
					$have_nfsprog = 1;
				}
				print STDERR "info: options $opt_o, mountprog ($have_mountprog), nfsprog($have_nfsprog)\n";
			}
			if (defined($opt_m))
			{
				print STDERR "info: mountprog: $opt_m\n";
			}
			
		}
	}
	elsif (defined($opt_o) || defined($opt_m))
	{
		die "-o and -m options only allowed when used with -r";
	}
}
elsif (defined($opt_l) || defined($opt_m) || defined($opt_u) || $opt_a
	|| defined($opt_g) || defined($opt_r) || defined($opt_o) || defined($opt_s))
{
	die "-l, -m, -u, -g, -s, -a, -r, and -o options permitted only when run as uid 0 (not $<)";
}

# if -a option is given, mount all the snfs filesystems in fstab and exit
if ($opt_a)
{
	my $error = 0;
	my @snfsmount_command = ("$0");
	my @fstab_paths = SNFS::get_ftab_paths(SNFS::FSTAB_PATH, 1);
	if ($opt_d)
	{
		print STDERR "Mounting all the path found in fstab:\n(@fstab_paths)\n";
		push @snfsmount_command, "-d";
	}
	foreach my $fstab_path (@fstab_paths)
	{
		my @command = @snfsmount_command;
		push @command, $fstab_path;
		if ($opt_d)
		{
			print STDERR "snfsmount command @command\n";
		}
		if (system(@command) != 0)
		{
			$error = 1;
			if ($opt_d)
			{
				print STDERR "error with snfsmount $fstab_path\n";
			}
		}
	}
	exit ($error);
}

# get mountprog and host if available
if ($remote_path)
{
	$fstab_mountprog = SNFS::get_mountprog ($path, SNFS::FSTAB_PATH, 
		$remote_path);
}
else
{
	$fstab_mountprog = SNFS::get_mountprog ($path, SNFS::FSTAB_PATH);
}

if (defined($fstab_mountprog))
# an entry in fstab exists for this mount point
{
	$mountprog = $fstab_mountprog;
	if (defined($opt_m) and $opt_m != $fstab_mountprog)
	{
		die "mountprog in fstab $fstab_mountprog different from provided mountprog $opt_m";
	}
}

if (defined($remote_host))
{
	$host = $remote_host;
}
else
{
	$host = "";
}

if (!defined($mountprog) and defined($opt_m))
{
	$mountprog = $opt_m;
}
elsif(!defined($mountprog))
{
	$mountprog = "";
}

#prepare command line for snfshost
my $opts = "";
if ($opt_d)
{
	$opts = "$opts --debug";
}
if (defined($opt_l))
{
	$opts = "$opts -l $psrv_user";
}

my $snfshost_command = SNFS::ADDHOST_PATH;
my $snfshost = "$snfshost_command --keep $opts $host:$mountprog";

if ($opt_d)
{
	print STDERR "snfshost command: $snfshost\n";
}

$host_mountprog = `$snfshost`;
my $snfshost_exit = $? >> 8;
if (($snfshost_exit != 0) or (!defined($host_mountprog)) or !$host_mountprog)
{
	my $why = "exit code unknown ($snfshost_exit)\n";
	if ($snfshost_exit == SNFS::CONFIG_CONFLICT)
	{	
		$why = "config files conflicting\n";
	}
	elsif ($snfshost_exit == SNFS::NO_HOST)
	{
		$why = "no host found\n";
	}
	elsif ($snfshost_exit == 1)
	{
		$why = "exit code 1\n";
	}
	
	print STDERR "$0: Error with $snfshost_command: $why";
	exit 1;
}

chomp $host_mountprog;
($conf_host, $conf_mountprog,$file) = split /:/, $host_mountprog;
if (!defined($conf_host) or !$conf_host or ($host and ($conf_host ne $host)))
{
	print STDERR "$0: (bug) bad output (for $host) from $snfshost_command: $host_mountprog\n";
	exit 1;
}
elsif (!$host)
{
	$host = $conf_host;
}

if (!defined($conf_mountprog) or !$conf_mountprog 
	or ($mountprog and ($conf_mountprog ne $mountprog)))
{
        print STDERR "$0: (bug) bad output (for $mountprog) from $snfshost_command: $host_mountprog\n";
        exit 1;
}
elsif (!$mountprog)
{
        $mountprog = $conf_mountprog;
}
if (!defined($file) or !$file or (! -f $file))
{
	print STDERR "$0: (bug) bad output (for file) from $snfshost_command: $host_mountprog\n";
	exit 1;
}

#some debug info
if ($opt_d)
{
	print STDERR "info: process_uid $<, remote host $host, mountprog $mountprog\n";
	if (($< == 0) and defined ($name))
	{
		die "bug: process_uid == 0 and name defined";
	}
	if (defined($conf_dir) and defined($pid_dir))
	{
		print STDERR "info: conf dir $conf_dir, pid dir $pid_dir\n";
	}
	else 
	{
		print STDERR "info: conf_dir or pid_dir undef\n";
	}
	if (defined($opt_l) and ($< == 0))
	{
		my $dir = $user_dir;
		if (!defined($dir))
		{
			$dir = "no user dir";
		}
		print STDERR "info: psrv user $psrv_user, uid $psrv_uid, gid $psrv_gid, user dir $dir\n";
	}
	elsif (defined($name))
	{
		print STDERR "info: user $name, uid $uid\n";
		if (defined ($psrv_user))
		{
			die "bug: called by a user, but psrv_user user defined";
		}
	}
	else
	{
		if (defined($psrv_user))
		{
			die "bug: called by root not as a user, but psrv_user user defined";
		}
	}
}

unless (defined($mountprog) and defined($host))
{
	die "bug: undefined mountprog or host";
}

#now we have a $host, $mountprog and a config file. Let's start rpc_psrv
unless (SNFS::start_rpc_psrv ($mountprog, $opt_d, $opt_f, $conf_dir, $pid_dir, 
	$log_dir, $psrv_user, $psrv_uid, $psrv_gid, $opt_s))
{
	print STDERR "$0: @{[SNFS::RPC_PSRV_PATH]}: Could not start\n";
	exit 1;
}

my $mtab_mountprog = SNFS::get_mountprog  $path, SNFS::MTAB_PATH;
if (defined($mtab_mountprog))
{
	if ($opt_d)
	{
		print STDERR "$0: Directory $path is already SNFS mounted\n";
	}
	exit 0;
}

#complete the mount options, if needed
if (! defined($fstab_mountprog))
{
	if (! defined($opt_o))
	{
	        $mount_options = "mountprog=$mountprog,nfsprog=" . ($mountprog+SNFS::PROG_OFFSET);
	}
	else
	{
		$mount_options = $opt_o;
		if (!defined($opt_nfsprog))
		{
		        $mount_options .= ",nfsprog=" . ($mountprog+SNFS::PROG_OFFSET);
		}
		if (!defined($opt_mountprog))
		{
			$mount_options .= ",mountprog=$mountprog";
		}
	}
	if (!defined($hostname))
	{
		die "Couldn't determine hostname, won't do the mount";
	}
	if (!$remote_path)
	{
		$remote_path = $path;
	}
	$mount_fs = "$hostname:$remote_path";
}

if (defined($opt_l) and defined($opt_r))
{
	if ((system("mkdir", "-p", "$path") != 0) and ($opt_d))
	{
		print STDERR "$0:mkdir -p $path:$!\n";
	}
}
	
unless (mount $path, $opt_d, $mount_fs, $mount_options)
{
	print STDERR "$0: $path: Could not mount\n";
	exit 1;
}

exit 0;

sub mount ($;$$$)
{
	my $path = shift;
	if (!defined ($path))
	{
		die "bug: mount path undefined";
	}
	my $debug = shift;
	if (! defined($debug))
	{
		$debug = 0;
	}
	my $mount_fs = shift;
	my $mount_options = shift;

	my @mount_command = ('mount');
	
	if (defined($mount_fs))
	{
		push @mount_command, ('-t', 'nfs');
		if (defined ($mount_options))
		{
			push @mount_command, ('-o', $mount_options);
		}
		push @mount_command, $mount_fs;
	}
	push @mount_command, $path;

	if ($debug)
	{
		print STDERR "$0: info: mount command @mount_command\n";
	}
	
	unless (0 == system {SNFS::MOUNT_PATH} @mount_command)
	{
		print STDERR "$0: @{[SNFS::MOUNT_PATH]}: Failed\n";
		return undef;
	}

	return 1;
}
