/**
 * @file oparchive.cpp
 * Implement oparchive utility
 *
 * @remark Copyright 2003, 2004 OProfile authors
 * @remark Read the file COPYING
 *
 * @author Will Cohen
 * @author John Levon
 * @author Philippe Elie
 */

#include <cstdlib>

#include <iostream>
#include <fstream>
#include <cstdlib>

#include <errno.h>
#include <string.h>
#include <dirent.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "op_file.h"
#include "op_bfd.h"
#include "op_config.h"
#include "oparchive_options.h"
#include "file_manip.h"
#include "cverb.h"
#include "image_errors.h"
#include "string_manip.h"
#include "locate_images.h"

using namespace std;

namespace {


void copy_one_file(image_error err, string const & source, string const & dest)
{
	if (!op_file_readable(source))
		return;

	if (options::list_files) {
		cout << source << endl;
		return;
	}

	if (!copy_file(source, dest) && err == image_ok) {
		cerr << "can't copy from " << source << " to " << dest
		     << " cause: " << strerror(errno) << endl;
	}
}

void copy_stats(string const & session_samples_dir,
		string const & archive_path)
{
	DIR * dir;
	struct dirent * dirent;
	string stats_path;
	
	stats_path = session_samples_dir + "stats/";

	if (!(dir = opendir(stats_path.c_str()))) {
		return;
	}

	string sample_base_dir = session_samples_dir.substr(archive_path.size());
	string archive_stats = options::outdirectory + sample_base_dir + "stats/";
	string archive_stats_path = archive_stats + "event_lost_overflow";
	if (!options::list_files &&
	    create_path(archive_stats_path.c_str())) {
		cerr << "Unable to create directory for "
		     <<	archive_stats << "." << endl;
		exit (EXIT_FAILURE);
	}

	copy_one_file(image_ok, stats_path + "/event_lost_overflow", archive_stats_path);

	while ((dirent = readdir(dir))) {
		int cpu_nr;
		string path;
		if (sscanf(dirent->d_name, "cpu%d", &cpu_nr) != 1)
			continue;
		path = string(dirent->d_name) + "/" + "sample_lost_overflow";
		archive_stats_path = archive_stats + path;
		if (!options::list_files &&
		    create_path(archive_stats_path.c_str())) {
			cerr << "Unable to create directory for "
			     <<	archive_stats_path << "." << endl;
			exit (EXIT_FAILURE);
		}
		copy_one_file(image_ok, stats_path + path, archive_stats_path);

	}
	closedir(dir);

}

int oparchive(options::spec const & spec)
{
	handle_options(spec);

	string archive_path = classes.extra_found_images.get_archive_path();

	/* Check to see if directory can be created */
	if (!options::list_files && create_path(options::outdirectory.c_str())) {
		cerr << "Unable to create directory for " 
		     <<	options::outdirectory << "." << endl;
		exit (EXIT_FAILURE);
	}

	/* copy over each of the executables and the debuginfo files */
	list<inverted_profile> iprofiles = invert_profiles(classes);

	report_image_errors(iprofiles, classes.extra_found_images);

	list<inverted_profile>::iterator it = iprofiles.begin();
	list<inverted_profile>::iterator const end = iprofiles.end();

	cverb << vdebug << "(exe_names)" << endl << endl;
	for (; it != end; ++it) {

		string exe_name = it->image;
		image_error error;
		string real_exe_name =
			classes.extra_found_images.find_image_path(it->image,
						  error, true);

		if (error == image_ok)
			exe_name = classes.extra_found_images.strip_path_prefix(real_exe_name);

		// output name must be identical to the original name, when
		// using this archive the used fixup will be identical e.g.:
		// oparchive -p /lib/modules/2.6.42/kernel -o tmp;
		// opreport  -p /lib/modules/2.6.42/kernel { archive:tmp }
		string exe_archive_file = options::outdirectory + exe_name;

		// FIXME: hacky
		if (it->error == image_not_found && is_prefix(exe_name, "anon "))
			continue;

		cverb << vdebug << real_exe_name << endl;
		/* Create directory for executable file. */
		if (!options::list_files &&
			create_path(exe_archive_file.c_str())) {
			cerr << "Unable to create directory for "
			     << exe_archive_file << "." << endl;
			exit (EXIT_FAILURE);
		}

		/* Copy actual executable files */
		copy_one_file(it->error, real_exe_name, exe_archive_file);

		/* If there are any debuginfo files, copy them over.
		 * Need to copy the debug info file to somewhere we'll
		 * find it - executable location + "/.debug"
		 * to avoid overwriting files with the same name. The
		 * /usr/lib/debug search path is not going to work.
		 */
		bfd * ibfd = open_bfd(real_exe_name);
		if (ibfd) {
			string dirname = op_dirname(real_exe_name);
			string debug_filename;
			if (find_separate_debug_file(ibfd, real_exe_name,
				debug_filename, classes.extra_found_images)) {
				/* found something copy it over */
				string dest_debug_dir = options::outdirectory +
					dirname + "/.debug/";
				if (!options::list_files &&
				    create_dir(dest_debug_dir.c_str())) {
					cerr << "Unable to create directory: "
					<< dest_debug_dir << "." << endl;
					exit (EXIT_FAILURE);
				}

				string dest_debug = dest_debug_dir +
					op_basename(debug_filename);
				copy_one_file(image_ok, debug_filename, dest_debug);
			}
			bfd_close(ibfd);
		}
	}

	/* copy over each of the sample files */
	list<string>::iterator sit = sample_files.begin();
	list<string>::iterator const send = sample_files.end();

	string a_sample_file = *sit;
	int offset = a_sample_file.find('{');
	string base_samples_dir = a_sample_file.substr(0, offset);
	copy_stats(base_samples_dir, archive_path);

	cverb << vdebug << "(sample_names)" << endl << endl;

	for (; sit != send; ++sit) {
		string sample_name = *sit;
		/* Get rid of the the archive_path from the name */
		string sample_base = sample_name.substr(archive_path.size());
		string sample_archive_file = options::outdirectory + sample_base;
		
		cverb << vdebug << sample_name << endl;
		cverb << vdebug << " destp " << sample_archive_file << endl;
		if (!options::list_files &&
			create_path(sample_archive_file.c_str())) {
			cerr << "Unable to create directory for "
			     <<	sample_archive_file << "." << endl;
			exit (EXIT_FAILURE);
		}

		/* Copy over actual sample file. */
		copy_one_file(image_ok, sample_name, sample_archive_file);
	}

	/* copy over the <session-dir>/abi file if it exists */
	string abi_name = string(op_session_dir) + "/abi";
	copy_one_file(image_ok, archive_path + abi_name,
	              options::outdirectory + abi_name);

	/* copy over the <session-dir>/samples/oprofiled.log file */
	string log_name = string(op_samples_dir) + "/oprofiled.log";
	copy_one_file(image_ok, archive_path + log_name,
	              options::outdirectory + log_name);

	return 0;
}

}  // anonymous namespace


int main(int argc, char const * argv[])
{
	return run_pp_tool(argc, argv, oparchive);
}