#!/usr/bin/python

"""
This is now the kind-of second version of ethphoenix.py
NOW WITH MII-TOOL RECOGNITION

yay!

Basic "important" notes:
This script has been designed to bring up stopped ethernet interfaces.  It
assumes the interface is working, and has been accidentally stopped.

If run as a cron job (or persistent thread???, maybe in further development)
it will regularly check if known "eth" interfaces are up, if not it will try
to bring them back up.  "eth" interfaces become known when the script is run.

It is mostly useful if you are running the script as a regular cron job, and
have accidentally stopped your interface while being remotely logged into it.
I've done this twice, but luckily the machine was on the other side of the room.
I can see this may be an issue if the machine was interstate or something.

The script will bring all interfaces back to the last known maximal state.
This means is that if you created an "eth" interface (virtual or otherwise) and
the script runs, it picks up that interface and will re-load it on each running
of the script.  If you wish to remove an interface and it's IP, you need to
re-set the script.  This can be done by running the script with a command-line
argument of "reset".


More notes:
This script needs to be able to read and write to the directory it is in.  It
saves it's current status to file, and thus will use last known details even
after full power-cycle of the computer.

The script does not check to see if an IP on one interface is the same as on
another.  It lets ifconfig deal with that (usually ifconfig won't let you put
one IP on two "eth" interfaces).

If this program gets some acceptance, I may look at turning it into a
fully-fledged threaded object-oriented program, but for now "live with it".

This sounds like a plug, but it's just where I got stuff from.  Reference
material for this script was the "Visual Quickstart Guide - Python" written by
Chris Fehily, from Peachpit Press 2002.

Usage:
./ethphoenix.py

On first run, the script will save the current setup.

On second run, the script will compare the current eth status to the saved copy.
If the eth setup is different, the script will return the eth status(es) to last
known maximal state.

Usage 2:
./ethphoenix.py reset

When the above command is run, the script will delete the saved copy of eth
status, and then continue through a "first run" scenario.  This is useful for
re-setting the stored eth status. (if you want to get rid of an eth interface).

The program has very few comments. I'll fix that later...  Also, the code's
messy, but all you need to know is that it works.

Created by Bradley van Ree 20040528
Revised by Bradley van Ree 20040530
"""

from os import popen3
from os import getcwd
from os import remove
from os import listdir
from sys import argv

import re
import cPickle
import psyco

def PrintDict( dict ):
	"""Accepts a dictionary, and prints it's key/value pairs."""
	keys = dict.keys()
	keys.sort()
	for key in keys:
		print key, ":", dict[key]

def OsPop( com ):
	"""Runs commands in the operating system (*nix)"""
        comIn, comOut, comErr = popen3( com )
        comIn.close()
        comErr.close()
	
	osOutput = comOut.read()
	comOut.close()

	return osOutput

def SaveMap( dict, fil ):
	"""Saves a copy of the eth to IP mapping to a file"""
	file = open( fil, "w" )
	cPickle.dump( dict, file)
	file.close()

def LoadMap( fil ):
	"""Loads a copy of the last known eth to IP mapping from a file."""
	file = open( fil, "r" )
	target = cPickle.load( file )
	file.close()
	return target

def UpdateMapDictMem( com1, com2, MapDictMemory):
	"""This function updates the Mapping dictionary in memory."""
	
	osStuff1 = OsPop( com1 )
	osStuff2 = OsPop( com2 )
	
	MapDictMemory = EthMap( osStuff1, MapDictMemory )
	MapDictMemory = MiiMap( osStuff2, MapDictMemory )

	return MapDictMemory

def EthMap( var, MapDictMemory ):
	"""Collects data from output of the ifconfig command and compiles it into a dictionary"""

	regex = re.compile( "^eth.+?\n.+?addr.+?\s", re.M )
	search = regex.findall( var )

	for i in range( len( search ) ):
		searchSplit = search[i].split()
		MapDictMemory[ searchSplit[0] ] = [ searchSplit[6][5:], "" ]
	

def MiiMap( var, MapDictMemory ):
	"""Collects data from output of the mii-tool command and compiles it into a dictionary"""

	for i in range( len( var ) ):
		MapDictMemory[ var[2:-1] ] = [ "" , var[ 2 ] ]
	

def RunFirst( com1, com2, MapDictMemory , fil ):
	"""This is a function that is run "first time only".  Or at least, each time it can't find a file it knows about in the same directory"""
	
	osStuff1 = OsPop( com1 )
	osStuff2 = OsPop( com2 )
	
	Map1 = EthMap( osStuff1, MapDictMemory )
	Map2 = MiiMap( osStuff2, MapDictMemory )
	
	SaveMap( MapDictMemory, fil )

def EthPoll( com, MapDictMemory ):
	"""This function is used to collect current "eth" interface info."""
	pass

def MiiPoll( com, MapDictMemory ):
	"""This function is used to collect the current mii-tool status info """
	
	pass

def FindEthInequality( dict1, dict2, fil ):
	"""Find the inequality between the dictionaries, and act upon it."""
	dropped = []
	gained = []
	keys1 = dict1.keys()
	keys2 = dict2.keys()

	keys1.sort()
	keys2.sort()

	for i in range( len( keys1 ) ):
		if ( dict2.has_key( keys1[i] ) == 0):
			dropped.append( keys1[i] )
		
	for i in range( len( keys2 ) ):
		if ( dict1.has_key( keys2[i] ) == 0 ):
			gained.append( keys2[i] )

	if ( len( dropped ) > 0 ):
		for i in range( len( dropped ) ):
			command = "ifconfig %s %s up" % ( dropped[i], dict1[ dropped[i] ] )
			OsPop( command )
		
	if ( len( gained ) > 0 ):
		SaveMap( dict2, fil )


# Begin program here
if __name__ == '__main__':
	
	psyco.full()
	
	#command1 = "netstat -ie"  # this has the same output as "ifconfig"
	command1 = "ifconfig"  # this has the same output as "netstat -ie"
	command2 = "mii-tool"
	filename = "MapDict.obj" # The "database" of all eth settings used.
	marker = 0
	MapDictMem = {} # the Mapping dictionary in memory
	
	if ( len( argv ) > 1 ):
		if ( argv[1] == "reset" ):
			remove( filename )
	
	dir = listdir( getcwd() )
	
	for i in range( len( dir ) ):
		if ( dir[i] == filename ):
			marker = 1
			
	if ( marker == 0 ):
		RunFirst( command1, command2, MapDictMem, filename )
	else:
		storedMap = LoadMap( filename )
		sampledMap = UpdateMapDictMem( command1, command2, MapDictMem )
		#sampledMap = EthPoll( command1, MapDictMem )

		if ( storedMap != sampledMap ):
			interface = FindEthInequality( storedMap, sampledMap, filename )