#!/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;

sub umount ($);
sub get_mountcount ($);
sub kill_psrv_for_mountprog ($;$$$$);

sub usage()
{
	print STDERR "Usage: $0 [-v][-k][-f][-d][-l user] -r remote_host | PATH | [-e]-a\n";
	exit 1;
}

our ($opt_d, $opt_l, $opt_f, $opt_r, $opt_a, $opt_e, $opt_k, $opt_q);
$opt_d = $opt_f = $opt_a = $opt_e = $opt_k = $opt_q = 0;
my $conf_pid_res;
my $umount_status;

getopts('akqdfel:r:');
unless (($#ARGV == 0 and !defined($opt_r) and !$opt_a) or
	(($#ARGV < 0) and (defined ($opt_r) or $opt_a)))
{
	usage;
}

if (defined($opt_r) and $opt_a)
{
	unless ($opt_q)
	{
		print STDERR "$0: -a and -r incompatible\n";
	}
	exit 1;
}

if (defined($opt_l) and ($< != 0))
{
	print STDERR "$0: -l option only allowed for uid 0\n";
	exit 1;
}

if ($#ARGV == 0)
# a path is given. umount and kill corresponding psrv if possible
{ 
	my $path = shift @ARGV;
	my ($user, $mountprog);
	($user, $mountprog) = SNFS::get_mountprog ($path,SNFS::MTAB_PATH);

	if (($< != 0) and defined($mountprog))
	{
		my @user_conf = ();
		if (!defined(SNFS::get_conf(\@user_conf, undef, $opt_d)))
		{
			unless ($opt_q)
			{
				print STDERR "$0: Cannot get configuration information\n";
				exit 1;
			}
		}
		my $name = $user_conf[4];
		if (!defined($user) or (defined($user) and ($user ne $name)))
		# the path was mounted by root, or another user, cannot umount it
		{
			unless ($opt_q)
			{
				print "$path not mounted by $name\n";
			}
			exit 1;
		}
	}

	if (defined($mountprog))
	# the path is mounted
	{
		$umount_status = umount $path;
		unless ($umount_status == 0)
		{
			exit $umount_status;
		}
	}
	else
	# the path isn't mounted. try to find a tunnel
	{
		unless ($opt_q)
		{
			print STDERR "$0: Directory $path not SNFS mounted\n";
		}
		$umount_status = 1;
		$mountprog = SNFS::get_mountprog ($path,SNFS::FSTAB_PATH);
		unless (defined($mountprog))
		{
			unless ($opt_q)
			{
				print STDERR "$0: no mountprog found for $path\n";
			}
			exit 1;
		}
	}

	if ($opt_d)
	{
		print STDERR "Killing rpc_psrv forwarding $mountprog\n";
	}
	
	# now we have a mountprog, we are gonna try to kill the associated psrv.
	
	# the pid dir where the pid file lies and the conf dir where the 
	# host name associated with mountprog is taken has to be determined.
	# We make a hash associating conf_dir with pid_dir.
	
	my %conf_pid_dirs = ();
	if ($user and ($< == 0))
	# in case we are trying to kill a rpc_psrv started by a user, we  
	# first try to set $pid_dir to the user's $pid_dir.
	{
		$conf_pid_res = SNFS::get_conf_pid (\%conf_pid_dirs, $user);
	}
	else 
	{
		$conf_pid_res = SNFS::get_conf_pid (\%conf_pid_dirs, $opt_l);
	}
	if (! $conf_pid_res)
	{
		unless ($opt_q)
		{
			print STDERR "Problem getting conf_pid\n";
		}
		if ($opt_k) {exit 0;}
		else { exit $umount_status; }
	}

	foreach my $conf_dir (keys(%conf_pid_dirs))
	{
		if ($opt_d)
		{
			my $pid_dir = $conf_pid_dirs{$conf_dir};
			if (!defined ($pid_dir))
			{
				$pid_dir = SNFS::ETC_PATH;
			}
			print STDERR "killing with $mountprog, $conf_dir, $pid_dir\n";
		}
		if (kill_psrv_for_mountprog($mountprog, $opt_d, $conf_dir,
			$conf_pid_dirs{$conf_dir}))
		{
			if ($opt_k) {exit 0; }
			else { exit $umount_status; }
		}
	}

	if ($opt_d)
	{
		print STDERR "$0: @{[SNFS::RPC_PSRV_PATH]}: Could not stop\n";
	}
	if ($opt_k) { exit 1; }
	else { exit $umount_status; }
}

# -a option : umount all the path found in mtab (or fstab if -e is given)

if ($opt_a)
{
	my @snfsumount_command = ("$0");
	my @paths;
	if ($opt_e)
	{
		@paths = SNFS::get_ftab_paths(SNFS::FSTAB_PATH, 0);
		if ($< != 0)
		{
			my ($var, $name);
			my @conf_infos= ();
			if (!defined(SNFS::get_conf(\@conf_infos, undef, $opt_d)))
			{
				die "Cannot determine configuration info";
			}

			($var, $var, $var, $var, $name, $var, $var, $var)
				= @conf_infos;
			my @fstab_paths = @paths;
			@paths = ();
			foreach my $mtab_path (SNFS::get_ftab_paths (SNFS::MTAB_PATH,0))
			{
				my ($user, $mountprog);
				($user, $mountprog) = SNFS::get_mountprog($mtab_path, SNFS::MTAB_PATH);
				next if (!grep { $_ eq $mtab_path } @fstab_paths);
				next if (!defined($user) or (defined($user) and $user ne $name));
				push @paths, $mtab_path;
			}
		}		
	}
	else
	{
		@paths = SNFS::get_ftab_paths(SNFS::MTAB_PATH, 0);
		if ($< != 0)
		{
			my @mtab_paths = @paths;
			@paths = ();
			foreach my $mtab_path (@mtab_paths)
			{
				my ($user, $mountprog);
				($user, $mountprog) = SNFS::get_mountprog($mtab_path,
					SNFS::MTAB_PATH);
				push @paths, $mtab_path;
			}
		}
	}
	if ($opt_d)
	{
		print STDERR "Unmounting all the paths:\n(@paths)\n";
		push @snfsumount_command, "-d";
	}
	foreach my $path (@paths)
	{
		my @command = @snfsumount_command;
		push @command, $path;
		if ($opt_d)
		{
			print STDERR "snfsumount command @command\n";
		}
		if (system(@command) != 0)
		{
			unless ($opt_q)
			{
				print STDERR "error with snfsumount $path\n";
			}
		}
	}
	#Test whether all the path have been unmounted
	my $mounted = 0;
	foreach my $path (@paths)
	{
		my ($user, $mountprog);
		($user, $mountprog) = SNFS::get_mountprog ($path,SNFS::MTAB_PATH);
		if (defined($mountprog))
		{
			unless ($opt_q)
			{
				my $user_by = "";
				if (defined($user)) 
				{
					$user_by = "by $user ";
				}
				print STDERR "$path still mounted (${user_by}using $mountprog)\n";
			}
			$mounted++;
		}
	}
	exit $mounted;
}

# a host was given. Kill corresponding psrv if possible

my %conf_pid_dirs = ();
unless (SNFS::get_conf_pid (\%conf_pid_dirs, $opt_l))
{
	if ($opt_d)
	{
		print STDERR "Problem getting conf_pid_dirs\n";
	}
	exit 1;
}
	
my $errors = 0;
foreach my $conf_dir (keys(%conf_pid_dirs))
{
	my @mountprogs = SNFS::get_host_all_mountprogs ($opt_r, $opt_d, $conf_dir);
	foreach my $mountprog (@mountprogs)
	{
		unless (kill_psrv_for_mountprog($mountprog, $opt_d, $conf_dir,
			$conf_pid_dirs{$conf_dir}))
		{
			$errors ++;
		}
	}
}

if ($errors)
{
	if ($opt_d)
	{
		print "$errors kill psrv errors\n";
	}
	exit $errors;
}
exit 0;

sub kill_psrv_for_mountprog ($;$$$$)
{
	my $mountprog = shift;
	if (!defined($mountprog) or ($mountprog !~ /^\d+$/))
	{
		return undef;
	}
	my $debug = shift;
	if (!defined($debug))
	{
		$debug = 0;
	}
	my $conf_dir = shift;
	my $pid_dir = shift;
	my $force = shift;

	my $mountcount;
	unless (defined($mountcount = SNFS::get_mountcount $mountprog))
	{
		print STDERR "$0: Could not determine number of mounts for $mountprog\n";
		return undef;
	}
	
	if ($mountcount == 0)
	{
		if (! SNFS::kill_rpc_psrv $mountprog, $opt_d, $conf_dir, $pid_dir, $force)
		{
			return undef;
		}
		else 
		{
			return 1;
		}
	}
	
	if ($opt_d)
	{
		print STDERR "$0: $mountprog still used in $mountcount mounts\n";
	}
	return 0;
}

sub umount ($)
{
	my $path = splice @_;
	my $rc;
	my $redirection = "2>/dev/null";
	unless ($opt_q)
	{
		$redirection = "";
	}
	if ($opt_f == 0) 
	{
		$rc = system SNFS::UMOUNT_PATH . " $path $redirection";
	}
	else
	{
	    $rc = system SNFS::UMOUNT_PATH . " -f $path $redirection";
	}
	unless ($rc == 0) 
	{
		if ($opt_d)
		{
			print STDERR "$0: @{[SNFS::UMOUNT_PATH]}: Failed\n";
		}
	}

	return $rc;
}
