/*
 * Guess the current network location
 *
 * Copyright (C) 2003--2010  Enrico Zini <enrico@debian.org>
 * Mostly rewritten by Enrico Zini on May 2003
 * Originally based on laptop-netconf.c by Matt Kern <matt@debian.org>
 * That was in turn based on divine.c by Felix von Leitner <felix@fefe.de>
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#define APPNAME PACKAGE
#else
#warning No config.h found: using fallback values
#define APPNAME __FILE__
#define VERSION "unknown"
#endif

#include "scanner/scan.h"
#include "options.h"
#include "runner/fake.h"
#include "runner/main.h"
#include "util/output.h"
#include "util/starter.h"
#include "scanner/linkbeat.h"

#include "IFace.h"

#include <wibble/sys/mutex.h>
#include <wibble/sys/thread.h>

#include <stdio.h>
#include <ctype.h>
#include <errno.h>	/* errno */

#include <string.h>     // memcpy
#include <sys/types.h>  // socket, getpwuid, geteuid
#include <sys/socket.h> // socket
#include <sys/ioctl.h>  // ioctl
#include <net/if.h>
#include <unistd.h>     // close, geteuid
#include <pwd.h>		// getpwuid

#include <set>
#include <vector>

using namespace std;
using namespace wibble::sys;
using namespace scanner;
using namespace util;

/*
bool detectIfupdown()
{
	pid_t ppid = getppid();
	string exe = "/proc/" + fmt(ppid) + "/exe";
	char buf[15];
	int size;
	if ((size = readlink(exe.c_str(), buf, 15)) == -1)
	{
		if (debug)
			fprintf(stderr, "Readlink of %.*s failed\n", PFSTR(exe));
		return false;
	}
	if (debug)
		fprintf(stderr, "Readlink of %.*s gave \"%.*s\"\n", PFSTR(exe), size, buf);
	return strncmp(buf, "/sbin/ifup", size) == 0;
}
*/

int main (int argc, const char *argv[])
{
	// Install the handler for unexpected exceptions
	wibble::exception::InstallUnexpected installUnexpected;

	// Access the interface
	try {
		options.init(argc, argv);
    
		debug("Guessnet " VERSION " starting...\n");

		IFace iface(options.iface);

		LinkBeat::configure(&iface);

		// After the interface is up, we need another try run to be able to
		// shut it down, if needed, in case of problems
		bool iface_was_down;
		if_params saved_iface_cfg;
		try {
			vector<const Scan*> scans = options.scans;
			verbose("%d candidate profiles\n", scans.size());

			// FIXME: is this needed?
			ScanBag::get().add(new DefaultScan(options.defprof));
			debug("Added \"default\" test %s\n", options.defprof.c_str());

      // if requested to do so, sleep for a while before starting our
      // work. may be needed in order to let the interface settle down
      // before bringing it up
      if (options.initdelay) {
        debug("Sleeping lazyly...\n");
        sleep(options.initdelay);
      }

			/* Check if we have to bring up the interface; if yes, do it */
			//iface_was_down = iface_init(Environment::get().iface(), op_init_time);
			iface.update();
			iface_was_down = !iface.up();
			if (iface_was_down)
			{
				verbose("Interface %s was down: initializing for broadcast\n", iface.name().c_str());
				saved_iface_cfg = iface.initBroadcast(options.init_timeout);
			}

			// Let the signals be caught by some other process
			sigset_t sigs, oldsigs;
			sigfillset(&sigs);
			sigdelset(&sigs, SIGFPE);
			sigdelset(&sigs, SIGILL);
			sigdelset(&sigs, SIGSEGV);
			sigdelset(&sigs, SIGBUS);
			sigdelset(&sigs, SIGABRT);
			sigdelset(&sigs, SIGIOT);
			sigdelset(&sigs, SIGTRAP);
			sigdelset(&sigs, SIGSYS);
			// Don't block the termination signals: we need them
			sigdelset(&sigs, SIGTERM);
			sigdelset(&sigs, SIGINT);
			sigdelset(&sigs, SIGQUIT);
			pthread_sigmask(SIG_BLOCK, &sigs, &oldsigs);

			// Scanning methods
			runner::Main scanner(iface);
			//runner::Fake scanner;
			debug("Initialized test subsystems\n");

			// Start the tests
			Starter::get().start();
			debug("Started tests\n");

			// Wait for the test results
			string profile;
			unsigned scanCount = ScanBag::get().getScans().size();
			if (scanCount > 1)
			{
				debug("%d candidates\n", scanCount);
				profile = scanner.getResult(options.timeout * 1000);
			} else {
				warning("No candidates provided: skipping detection\n");
			}

			// Shutdown the tests
			scanner.shutdown();

			// We've shutdown the threads: restore original signals
			pthread_sigmask(SIG_SETMASK, &oldsigs, &sigs);

			// Output the profile name we found
			if (profile.size())
			{
				output("%s\n", profile.c_str());
			} else {
				output("%s\n", options.defprof.c_str());
			}
		} catch (std::exception& e) {
			error("%s\n", e.what());
			if (geteuid() != 0)
			{
				struct passwd *p = getpwuid(geteuid());
				error("You can try invoking guessnet as user root instead of %s\n", p->pw_name);
			}
			return 1;
		}

		/* Bring down the interface if we need it */
		if (iface_was_down)
			iface.setConfiguration(saved_iface_cfg);

	} catch (std::exception& e) {
		error("%s\n", e.what());
		if (geteuid() != 0)
		{
			struct passwd *p = getpwuid(geteuid());
			error("You can try invoking guessnet as user root instead of %s\n", p->pw_name);
		}
		return 1;
	}

	return 0;
}

// vim:set ts=4 sw=4:
