#!/usr/bin/perl -w
#
# Copyright (C)  2002 Patrice Dumas <dumas@centre-cired.fr>
#
# 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::Long;
use File::Copy;

sub usage()
{
	print STDERR "Usage: $0 [--template file][--from-dir dir]\n";
	print STDERR "    [--directory dir][--tmpl-file][-l user][--hostname hostname]\n";
	print STDERR "    [--accept-id id:id...] [server]:[mountprog]...\n";
	exit 1;
}

sub make_config_file($$$$$$$$$);

my $from_dir = SNFS::ETC_PATH;
my $debug = 0;
my $keep = 0;
my ($do_tmpl, $user, $directory, $conf_dir, $name, $uid, $var, $user_dir);
my ($fstab_mountprog, $user_mount, $conf_file, $found_host, $dir, $host, 
	$mountprog, $accept_id, $template, $hostname, $conf_hostname,
	$found_file,
	$new_snfs_mountprog);
my $conflict = 0;
my $no_host = 0;
my $verbose = 0;

my $result = GetOptions ('tmpl-file' => \$do_tmpl, 'template=s' => \$template,
	'from-dir=s' => \$from_dir, 'directory=s' => \$directory, 'l=s'  => \$user,
	'hostname=s' => \$hostname, 'accept-id=s' => \$accept_id, 
	'debug' => \$debug, 'keep' => \$keep, 'verbose' => \$verbose );

if ($debug)
{
	$verbose = 1;
}

if ($#ARGV < 0)
{
	usage;
}

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

($conf_hostname, $conf_dir, $var, $var, $name, $uid, $var, $user_dir) 
	= @conf_infos;

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

if (($< != 0) and (defined ($user)))
{
	print STDERR "$0: -l option only as uid 0";
}

if (!defined($hostname))
{
	$hostname = $conf_hostname;
	if (!defined($hostname))
	{
		die "Could not determine hostname";
	}
}

if (!defined($accept_id))
{
	if ($uid == 0)
	{
		$accept_id = '*:*';
	}
	elsif (defined($user) and defined($name))
	{
		$accept_id = "0:0";
	}
	elsif (defined($name))
	{
	# 0 should be sufficient to have it work, but if we don't set 0:0 
	# root can't read user's dir, although he still can su
		$accept_id = "0:0";
	}
	else
	#sanity check
	{
		if ($debug)
		{
			print STDERR "bug : uid != 0, but name undef\n";
		}
	}
}

if (!defined($directory))
{
	if (defined($conf_dir))
	{
		$directory = $conf_dir;
	}
	else 
	{
		$directory = SNFS::ETC_PATH;
	}
}
	
if (($< == 0) and (!defined($user)) and (!defined($do_tmpl)))
{
	$do_tmpl = 1;
}

if (! -d $from_dir)
{
	die "$from_dir not a valid directory";
}

my $from_file;
if (defined($template))
{
	$from_file = $from_dir . '/' . $template;
	if (! -f $from_file)
	{
		die "$from_file not a valid file";
	}
}
elsif ($uid == 0)
{
	$template = SNFS::TEMPLATE;
	$from_file = $from_dir . '/' . $template;
	if (! -f $from_file)
	{
		die "$from_file not a valid file";
	}
}

if (! -d $directory)
{
	die "$directory not a valid directory";
}

my @conf_dirs = ($directory);
if (defined($user_dir))
{
	push @conf_dirs, $user_dir;
}

#if there is only one specification, echo the result
my $echo_result = 0;
if ($#ARGV == 0)
{
	$echo_result = 1;
}

while (@ARGV)
{
	my $new_snfs_mountprog;
	my $spec = shift @ARGV;
	$host = undef;
	$found_host = undef;
	$mountprog = undef;
	$found_file = undef;
	$conf_file = undef;
	$dir = undef;
	if (!defined($template))
	{
		$from_file = undef;
	}
	($host, $mountprog) = split /:/, $spec;
	if (!$host)
	{
		undef $host;
	}
	elsif ($host !~ /^[\w\-.]+$/)
	{
		if ($debug)
		{
			print STDERR "bad host $host, ignoring\n";
		}
		next;
	}
	
	if (!$mountprog)
	{
		undef $mountprog;
	}
	elsif ($mountprog !~ /^\d+$/)
	{
		if ($debug)
		{
			print STDERR "bad mountprog $mountprog, ignoring\n";
		}
		next;
	}
	
	if (!defined($host) and !defined($mountprog))
	{
		if ($debug)
		{
			print STDERR "No host nor mountprog specified\n";
		}
		$no_host = 1;
		next;
	}
	
	if (defined($host) and (!defined($mountprog)))
	{
		my @possible_mountprogs =
			SNFS::get_host_all_mountprogs_in_dirs($host, $verbose, @conf_dirs);
		if (@possible_mountprogs)
		{
			$mountprog = $possible_mountprogs[0];
		}
		elsif ($uid != 0)
		{
			$mountprog = SNFS::get_user_mount(SNFS::get_host_all_mountprogs_in_dirs($host, $verbose));
		}
		if (!defined($mountprog))
		# We couldn't find an allready used mountprog. We find one
		# which doesn't conflict with the existing ones.
		{
			# find all the mountprogs.
			# mountprogs in the /etc/snfs directory
			my @snfs_mountprogs = SNFS::get_dir_mountprogs(SNFS::ETC_PATH);
			if ($debug)
			{
				print STDERR "debug: snfs_mountprogs: @snfs_mountprogs\n";
			}
		
			my @all_mountprogs = @snfs_mountprogs;
			
			#add the mountprogs in conf_dir and directory
			push @all_mountprogs, SNFS::get_dir_mountprogs($conf_dir);
			push @all_mountprogs, SNFS::get_dir_mountprogs($directory);
			
			# if the user directory is known, add the mountprogs from that dir
			if ($user_dir)
			{
				push @all_mountprogs, SNFS::get_dir_mountprogs($user_dir);
			}
	
			#Add the mountprogs in snfs dir subdirectories
			unless (opendir DIR, SNFS::ETC_PATH)
			{
				print STDERR "$0: @{[SNFS::ETC_PATH]}: $!\n";
				exit 1;
			}
		
			my @dirs = grep { ! m/^\./ && -d SNFS::ETC_PATH . '/' . $_ } readdir DIR;
			foreach my $snfs_subdir (@dirs)
			{
				push @all_mountprogs, 
					SNFS::get_dir_mountprogs(SNFS::ETC_PATH . '/' . $snfs_subdir);
			}
			if ($debug)
			{
				print STDERR "debug: all_mountprogs: @all_mountprogs\n";
			}

			# if the directory isn't etc/snfs find a mountprog for host in 
			# etc/snfs. If not found find a free one. Make a config file
			# for that mountprog later (if super user)
			
			if ($directory ne SNFS::ETC_PATH)
			{
				my $snfs_mountprog;
				my @snfs_mountprogs = SNFS::get_host_all_mountprogs($host, $verbose);
				if (!@snfs_mountprogs)
				{
					$snfs_mountprog = SNFS::get_free_mountprog(undef, @all_mountprogs);
					push @all_mountprogs, $snfs_mountprog;
					$new_snfs_mountprog = $snfs_mountprog;
				}
				else
				{
					$snfs_mountprog = $snfs_mountprogs[0];
				}
				$mountprog = SNFS::get_free_mountprog($uid + $snfs_mountprog,
					@all_mountprogs);
			}
			else
			{
				$mountprog = SNFS::get_free_mountprog(undef, @all_mountprogs);
			}
			if ($debug)
			{
				print STDERR "New mountprog for $host: $mountprog\n";
			}
		}
	}
	elsif (defined($mountprog) and (!defined($host)))
	{
		$host = SNFS::get_host_in_dirs($mountprog, $verbose, @conf_dirs);
		if (!defined($host))
		{
			my $etc_host = SNFS::get_host($mountprog, undef, $verbose);
			my $user_mountprog = SNFS::get_user_mount(($mountprog));
			if (defined($etc_host) and (($< == 0) or !$keep
				or (defined($user_mountprog) and  
				($mountprog == $user_mountprog))))
			{
				$host = $etc_host;
			}
			if ((defined($etc_host)) and (!defined($host)) and $verbose)
			{
				print STDERR "$etc_host found for $mountprog in etc dir, not user mountable\n";
			}
		}

		if (!defined($host))
		{
			$no_host = 1;
			if ($verbose)
			{
				print STDERR "No host found for $mountprog\n";
			}
			next;
		}
	}
		
	if ($debug)
	{
		print STDERR "host $host, mountprog $mountprog\n";
	}
	if (!defined($host) or !$host or !defined($mountprog))
	{
		print STDERR "bug: host or mountprog undef\n";
		exit 1;
	}
	
	($dir, $found_file, $found_host) = SNFS::get_host_in_dirs($mountprog, $verbose, @conf_dirs);

	if ($debug and defined($found_host) and (! defined ($found_file)))
	{
		print STDERR "(bug) found_host ($found_host) defined but found_file not defined\n";
	}
	if (defined($found_host) and !defined($dir))
	{
		$dir = SNFS::ETC_PATH;
	} 
	
	if ((defined($found_host)) and ($found_host ne $host))
	{
		if ($verbose)
		{
			print STDERR "$found_host in $found_file (in $dir) forwarding $mountprog conflicting with $host\n";
			$conflict = 1;
		}
		next if $keep;
	}
	elsif (defined($found_host))
	{
		if ($debug or ($keep and $verbose))
		{
			print STDERR "config file $found_file for $host:$mountprog existing in $dir\n";
		}
		next if ($keep and defined(SNFS::get_host ($mountprog, $directory)));
	}
	
	if (!defined($template))
	{
		if (defined($found_file))
		{
			if (-f $from_dir . '/' . $found_file . ".tmpl")
			{
				$from_file = $from_dir . '/' . $found_file . ".tmpl";
			}
		}
		elsif (-f $from_dir .  '/' . $host . ".tmpl")
		{
			$from_file = $from_dir . '/' . $host . ".tmpl";
		}

		if (!defined($from_file))
		{
			$from_file = $from_dir . '/' . SNFS::TEMPLATE;
		}
	}

	if (! -f $from_file)
	{
		if ($verbose)
		{
			print STDERR "No from_file";
		}
		next;
	}
	unless ($conf_file = make_config_file($from_file, $directory, $found_file, 
		$host, $mountprog, $hostname, $accept_id, 0, $debug))
	{
		if ($verbose)
		{
			print STDERR "error with make_config_file($from_file, $directory, file, $host, $mountprog)\n";
		}
		next;
	}
	
	if (defined($new_snfs_mountprog) and ($< == 0))
	{
		make_config_file($from_file, SNFS::ETC_PATH, $found_file, 
			$host, $new_snfs_mountprog, "", "", 1, $debug);
		make_config_file($from_file, SNFS::ETC_PATH, $found_file, $host,
			$new_snfs_mountprog, $hostname, "*:*", 0, $debug);
	}
	$new_snfs_mountprog = undef;

	if ($do_tmpl)
	{
		make_config_file($from_file, $directory, $found_file, $host,
			$mountprog, "", "", 1, $debug);
	}
}

if ($echo_result)
{
	if ($no_host)
	{
		exit SNFS::NO_HOST;
	}
	if ($conflict)
	{
		exit SNFS::CONFIG_CONFLICT;
	}
	my $file = $conf_file;
	if (!defined($file) and defined($found_file))
	{
		$file = $found_file;
	}

	print "$host:$mountprog:$directory" . "/$file\n";
	exit 0;
}

exit (0 + $conflict * SNFS::CONFIG_CONFLICT + $no_host * SNFS::NO_HOST);

sub make_config_file($$$$$$$$$)
{
	my $from_file = shift;
	my $directory = shift;
	my $file = shift;
	my $host = shift;
	my $mountprog = shift;
	my $hostname = shift;
	my $accept_id = shift;
	my $do_tmpl = shift;
	my $debug = shift;

	if (!defined($from_file) or (! -f $from_file) or (! defined($directory))
		or (! -d $directory) or (!defined($host)) or ($host !~ /[\w\-.]+/)
		or (!defined($mountprog)) or ($mountprog !~ /\d+/))
	{
		if ($debug)
		{
			print STDERR "bad arg for make_config_file\n";
		}
		return undef;
	}
	my $nfsprog = $mountprog + SNFS::PROG_OFFSET;
	
	unless (opendir DIR, $directory)
	{
		if ($debug)
		{
			print STDERR "$0: make_config_file $directory: $!\n";
		}
		return undef;
	}
	my @file = grep
		{
			! m/^\./ &&
			! m/\.(bak|orig|old|dpkg-old|rpmnew|rpmsave|pid)$/ &&
			! m/~$/ &&
			! m/^#.*#$/ &&
			-f $directory . '/' . $_
		}
		readdir DIR;
	closedir DIR;

	if (!defined($file))
	{
		my $suffix = "";
		if ($do_tmpl)
		{
			$suffix = ".tmpl";
		}
		if (grep { $_ eq $host . $suffix } @file)
		{
			$file = "$host.$mountprog" . $suffix;
		}
		else 
		{
			$file = $host . $suffix;
		}
	}
	else
	{
		if ($do_tmpl)
		{
			$file = $file . ".tmpl";
		}
	}

	if (grep { $_ eq $file } @file)
	{
		if ($debug)
		{
			print STDERR "deleting " . $directory.'/'.$file . "\n";
		}
		unlink ($directory . '/' . $file);
	}

	my ($from_fh, $to_fh);
	
	unless ($from_fh = new FileHandle $from_file, 'r')
	{
		if ($debug)
		{
			print STDERR "$0: (make_conf_file) $from_fh: $!\n";
		}
		return undef;
	}

	unless ($to_fh = new FileHandle $directory . '/' . $file, 'w')
	{
		if ($debug)
		{
			print STDERR "$0: (make_conf_file) $to_fh: $!\n";
		}
		return undef;
	}

	if ($debug)
	{
		print STDERR "reading from $from_file, to " .$directory .'/'. $file."\n";
		if ($do_tmpl)
		{
			print STDERR "with do_tmpl, host $host\n";
		}
		else
		{
			print STDERR "with host $host, mountprog $mountprog, " .
			"hostname $hostname, accept_id $accept_id\n";
		}
	}
	while (<$from_fh>)
	{
		unless ($do_tmpl)
		{
			s/MOUNTPROG/$mountprog/g;
			s/NFSPROG/$nfsprog/g;
			s/ACCEPTEDID/$accept_id/g;
			s/LOCALHOSTNAME/$hostname/g;
		}
		s/REMOTE/$host/g;
		$to_fh->print($_);
	}
	return $file;
}

