/**
 * @file oprof_start_util.cpp
 * Miscellaneous helpers for the GUI start
 *
 * @remark Copyright 2002 OProfile authors
 * @remark Read the file COPYING
 *
 * @author Philippe Elie
 * @author John Levon
 */

#include <dirent.h>
#include <unistd.h>
#include <glob.h>

#include <cerrno>
#include <vector>
#include <cmath>
#include <sstream>
#include <iostream>
#include <fstream>
#include <cstdlib>

#include <qfiledialog.h>
#include <qmessagebox.h>

#include "op_file.h"
#include "file_manip.h"
#include "child_reader.h"
#include "op_libiberty.h"

#include "oprof_start.h"
#include "oprof_start_util.h"

using namespace std;

namespace {

// return the ~ expansion suffixed with a '/'
string const get_config_dir()
{
	return "/root";
}

string daemon_pid;

} // namespace anon

daemon_status::daemon_status()
	: running(false),
	  nr_interrupts(0)
{
	int HZ;
	if (!daemon_pid.empty()) {
		string proc_filename = string("/proc/") + daemon_pid + "/exe";
		string const exec = op_realpath(proc_filename);
		if (exec == proc_filename)
			daemon_pid.erase();
		else
			running = true;
	}

	if (daemon_pid.empty()) {
		DIR * dir;
		struct dirent * dirent;

		if (!(dir = opendir("/proc"))) {
			perror("oprofiled: /proc directory could not be opened. ");
			exit(EXIT_FAILURE);
		}

		while ((dirent = readdir(dir))) {
			string const exec =
				op_realpath(string("/proc/")
				               + dirent->d_name + "/exe");
			string const name = op_basename(exec);
			if (name != "oprofiled")
				continue;

			daemon_pid = dirent->d_name;
			running = true;
		}

		closedir(dir);
	}

	HZ = sysconf(_SC_CLK_TCK);
	if (HZ == -1) {
		perror("oprofiled: Unable to determine clock ticks per second. ");
		exit(EXIT_FAILURE);
	}

	if (daemon_pid.empty())
		return;

	nr_interrupts = 0;

	switch (op_get_interface()) {
	case OP_INTERFACE_24:
		{
			ifstream ifs3("/proc/sys/dev/oprofile/nr_interrupts");
			if (ifs3)
				ifs3 >> nr_interrupts;
		}
		break;
	case OP_INTERFACE_26:
		{
			static unsigned int old_sum_interrupts;
			unsigned int sum_interrupts = 0;
			glob_t file_names;

			file_names.gl_offs = 0;
			glob("/dev/oprofile/stats/cpu*/sample_received",
			     GLOB_DOOFFS, NULL, &file_names);

			for (size_t i = 0; i < file_names.gl_pathc; ++i) {
				ifstream ifs3(file_names.gl_pathv[i]);
				if (ifs3) {
					unsigned int file_interrupts;
					ifs3 >> file_interrupts;
					sum_interrupts += file_interrupts;
				}
			}
			if (old_sum_interrupts > sum_interrupts)
				// occur if we stop/restart daemon.
				old_sum_interrupts = 0;
			nr_interrupts = sum_interrupts - old_sum_interrupts;
			old_sum_interrupts = sum_interrupts;
			globfree(&file_names);
		}
		break;
	default:
		break;
	}
}


/**
 * get_config_filename - get absolute filename of file in user $HOME
 * @param filename  the relative filename
 *
 * Get the absolute path of a file in a user's home directory.
 */
string const get_config_filename(string const & filename)
{
	return get_config_dir() + "/" + filename;
}


/**
 * check_and_create_config_dir - make sure config dir is accessible
 *
 * Returns %true if the dir is accessible.
 */
bool check_and_create_config_dir()
{
	string dir = get_config_filename(".oprofile");

	char * name = xstrdup(dir.c_str());

	if (create_dir(name)) {
		ostringstream out;
		out << "unable to create " << dir << " directory ";
		out << "cause: " << strerror(errno);
		QMessageBox::warning(0, 0, out.str().c_str());

		free(name);

		return false;
	}

	free(name);
	return true;
}


/**
 * format - re-format a string
 * @param orig  string to format
 * @param maxlen  width of line
 *
 * Re-formats a string to fit into a certain width,
 * breaking lines at spaces between words.
 *
 * Returns the formatted string
 */
string const format(string const & orig, uint const maxlen)
{
	string text(orig);

	istringstream ss(text);
	vector<string> lines;

	string oline;
	string line;

	while (getline(ss, oline)) {
		if (line.size() + oline.size() < maxlen) {
			lines.push_back(line + oline);
			line.erase();
		} else {
			lines.push_back(line);
			line.erase();
			string s;
			string word;
			istringstream oss(oline);
			while (oss >> word) {
				if (line.size() + word.size() > maxlen) {
					lines.push_back(line);
					line.erase();
				}
				line += word + " ";
			}
		}
	}

	if (line.size())
		lines.push_back(line);

	string ret;

	for(vector<string>::const_iterator it = lines.begin(); it != lines.end(); ++it)
		ret += *it + "\n";

	return ret;
}


/**
 * do_exec_command - execute a command
 * @param cmd  command name
 * @param args  arguments to command
 *
 * Execute a command synchronously. An error message is shown
 * if the command returns a non-zero status, which is also returned.
 *
 * The arguments are verified and will refuse to execute if they contain
 * shell metacharacters.
 */
int do_exec_command(string const & cmd, vector<string> const & args)
{
	ostringstream err;
	bool ok = true;

	// verify arguments
	for (vector<string>::const_iterator cit = args.begin();
		cit != args.end(); ++cit) {
		if (verify_argument(*cit))
			continue;

		QMessageBox::warning(0, 0,
			string(
			"Could not execute: Argument \"" + *cit +
			"\" contains shell metacharacters.\n").c_str());
		return EINVAL;
	}

	child_reader reader(cmd, args);
	if (reader.error())
		ok = false;

	if (ok)
		reader.get_data(cout, err);

	int ret = reader.terminate_process();
	if (ret) {
		string error = reader.error_str() + "\n";
		error += "Failed: \n" + err.str() + "\n";
		string cmdline = cmd;
		for (vector<string>::const_iterator cit = args.begin();
		     cit != args.end(); ++cit) {
			cmdline += " " + *cit + " ";
		}
		error += "\n\nCommand was :\n\n" + cmdline + "\n";

		QMessageBox::warning(0, 0, format(error, 50).c_str());
	}

	return ret;
}


/**
 * do_open_file_or_dir - open file/directory
 * @param base_dir  directory to start at
 * @param dir_only  directory or filename to select
 *
 * Select a file or directory. The selection is returned;
 * an empty string if the selection was cancelled.
 */
string const do_open_file_or_dir(string const & base_dir, bool dir_only)
{
	QString result;

	if (dir_only) {
		result = QFileDialog::getExistingDirectory(base_dir.c_str(), 0,
			"open_file_or_dir", "Get directory name", true);
	} else {
		result = QFileDialog::getOpenFileName(base_dir.c_str(), 0, 0,
			"open_file_or_dir", "Get filename");
	}

	if (result.isNull())
		return string();
	else
		return result.latin1();
}

/**
 * verify_argument - check string for potentially dangerous characters
 *
 * This function returns false if the string contains dangerous shell
 * metacharacters.
 *
 * WWW Security FAQ dangerous chars:
 *
 * & ; ` ' \ " | * ? ~ < > ^ ( ) [ ] { } $ \n \r
 *
 * David Wheeler: ! #
 *
 * We allow '-' because we disallow whitespace. We allow ':' and '='
 */
bool verify_argument(string const & str)
{
	if (str.find_first_not_of(
		"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
		"abcdefghijklmnopqrstuvwxyz0123456789_:=-+%,./")
		!= string::npos)
		return false;
	return true;
}