#! /usr/bin/perl

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
#
# 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.
#
# This code was originally developed by Vyatta, Inc.
# Portions created by Vyatta are Copyright (C) 2010 Vyatta, Inc.
# All Rights Reserved.

use strict;
use lib "/opt/vyatta/share/perl5/";
use XorpConfigParser;
use Vyatta::Config;
use Sys::Syslog qw(:standard :macros);
use Fcntl qw(:flock);

my $BOOTFILE    = "/opt/vyatta/etc/config/config.boot";
my $UDEVDIR     = "/dev/.udev";

my $VYATTAUDEV	= $UDEVDIR . "/vyatta";
my $LOCKFILE	= $UDEVDIR . "/.vyatta-lock";
my $VYATTACFG   = "/opt/vyatta/config/active";

# Check if interface name is free to use
sub is_available {
    my ($interfaces, $ifname) = @_;

    my $count = grep { $_ eq $ifname } values %$interfaces;
    return ($count == 0);
}

# Find next available interface name
sub find_available {
    my ($interfaces, $ifprefix) = @_;
    $ifprefix =~ s/\d+$//;

    for (my $id = 0; ; $id++) {
	my $ifname = sprintf("%s%d", $ifprefix, $id);

	# is it in Vyatta config?
	return $ifname if (is_available($interfaces, $ifname));
    }
}

# Find the hardware id in the parsed config node for interface
sub get_hwid_from_children {
    my $children = shift;

    foreach my $attr (@$children) {
	next unless ($attr->{'name'} =~ /^hw-id ([0-9a-f:]+)/);
	return $1;
    }

    return;	# not found
}


# Leave file for vyatta_interface_rescan
sub leave_rescan_hint {
    my ($ifname, $hwaddr) = @_;
    my $name = "$VYATTAUDEV/$ifname";

    mkdir($VYATTAUDEV);
    open (my $f, '>', $name)
	or die "Can't create $name : $!";

    print {$f} "$hwaddr\n";
    close $f;
    return 1;
}

# Determine network name to use based on Vyatta config during boot
sub coldplug {
    my ($ifname, $hwaddr) = @_;
    my $xcp = new XorpConfigParser();
    $xcp->parse($BOOTFILE);

    my $interfaces = { };
    my $inode = $xcp->get_node(['interfaces']);
    if ($inode) {
	foreach my $child (@{$inode->{'children'}}) {
	    next unless ($child->{'name'} =~ /^ethernet (.*)|^wireless (.*)/);

	    my $intf = $1;
	    my $hwid = get_hwid_from_children($child->{'children'});
	    next unless $hwid;

	    # TBD this could be a hash with name and path?
	    $interfaces->{$hwid} = $intf;
	}
    }

    # is name already in config file
    my $newname = $interfaces->{$hwaddr};
    return $newname if ($newname);

    # add already assigned names
    if (opendir(my $dir, $VYATTAUDEV)) {
	foreach my $intf (grep { ! /^\./ } readdir($dir)) {
	    if (open (my $f, '<', "$VYATTAUDEV/$intf")) {
		my $hwid = <$f>;
		close $f;
		chomp $hwid;

		$interfaces->{$hwid} = $intf;
	    }
	}
    }

    # Does biosdevname have a suggestion?
    my $biosname = `/sbin/biosdevname -i $ifname`;
    chomp $biosname;
    if ($biosname ne '') {
	$newname = $biosname;
    } else {
	$newname = $ifname;
    } 

    $newname = find_available($interfaces, $newname)
	unless (is_available($interfaces, $newname));

    leave_rescan_hint($newname, $hwaddr);

    return $newname;
}

# Determine name from active config
sub hotplug {
    my ($ifname, $hwaddr) = @_;

    openlog("vyatta-net-name", "", LOG_DAEMON);

    # Parse active config
    my $cfg = new Vyatta::Config;
    $cfg->setLevel('interfaces');

    my $interfaces = {};
    foreach my $type ($cfg->listOrigNodes()) {
	next unless ($type eq 'ethernet') || ($type eq 'wireless');
	foreach my $intf ($cfg->listOrigNodes($type)) {
	    my $hwid = $cfg->returnOrigValue("$type $intf hw-id");
	    next unless $hwid;
	    # TBD this could be a hash with name and path?
	    $interfaces->{$hwid} = $intf;
	}
    }

    my $newname = $interfaces->{$hwaddr};
    return $newname if ($newname);

    # Does biosdevname have a suggestion?
    my $biosname = `/sbin/biosdevname -i $ifname`;
    chomp $biosname;
    syslog(LOG_DEBUG, "biosdevname for '%s' => '%s'", $ifname, $biosname);

    if ($biosname && ($biosname ne '')) {
	$newname = $biosname;
    } else {
	$newname = $ifname;
    } 

    $newname = find_available($interfaces, $ifname)
	unless is_available($interfaces, $biosname);

    syslog(LOG_INFO, "new name for '%s' is '%s'", $ifname, $newname);

    return $newname;
}

my $LOCKF;
sub lock_file {
    open ($LOCKF, '>', $LOCKFILE)
	or die "Can't open $LOCKFILE : $!";

    flock ($LOCKF, LOCK_EX)
	or die "Can't lock $LOCKFILE : $!";

}

sub unlock_file {
    close $LOCKF;
    $LOCKF = undef;
}

# This script is called from udev with two arguments
# it outputs the new name (if any) to stdout
if ($#ARGV != 1) {
    die "vyatta_net_name called with wrong args(%d) : %s",
	   $#ARGV, join(' ', @ARGV);
}

my $ifname = $ARGV[0];
my $hwaddr = $ARGV[1];

lock_file;
my $newname;
if ( -d $VYATTACFG ) {
    $newname = hotplug($ifname, $hwaddr);
} else {
    $newname = coldplug($ifname, $hwaddr);
}
unlock_file;

print "$newname\n" if ($newname);

exit 0;