/**
 * @file opd_parse_proc.c
 * Parsing of /proc/#pid
 *
 * @remark Copyright 2002 OProfile authors
 * @remark Read the file COPYING
 *
 * @author John Levon
 * @author Philippe Elie
 */

#include "op_libiberty.h"

#include "opd_parse_proc.h"
#include "opd_proc.h"
#include "opd_mapping.h"
#include "opd_image.h"
#include "opd_printf.h"

#include "op_file.h"
#include "op_fileio.h"

#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/**
 * opd_add_ascii_map - parse an ASCII map string for a process
 * @param proc  process to add map to
 * @param line  0-terminated ASCII string
 * @param image_name the binary application name
 *
 * Attempt to parse the string @line for map information
 * and add the info to the process @proc. Returns %1
 * on success, %0 otherwise.
 *
 * The parsing is based on Linux 2.4 format, which looks like this :
 *
 * 4001e000-400fc000 r-xp 00000000 03:04 31011      /lib/libc-2.1.2.so
 */
/* FIXME: handle (deleted) */
static int opd_add_ascii_map(struct opd_proc * proc, char const * line,
			     char * const image_name)
{
	unsigned long offset, start, end;
	struct opd_image * image;
	char const * cp = line;

	/* skip to protection field */
	while (*cp && *cp != ' ')
		cp++;

	/* handle rwx */
	if (!*cp || (!*(++cp)) || (!*(++cp)) || (*(++cp) != 'x'))
		return 0;

	/* get start and end from "40000000-4001f000" */
	if (sscanf(line, "%lx-%lx", &start, &end) != 2)
		return 0;

	/* "p " */
	cp += 2;

	/* read offset */
	if (sscanf(cp, "%lx", &offset) != 1)
		return 0;

	while (*cp && *cp != '/')
		cp++;

	if (!*cp)
		return 0;

	image = opd_get_image(cp, image_name, 0, proc->tid, proc->tgid);
	if (!image)
		return 0;

	opd_add_mapping(proc, image, start, offset, end);

	return 1;
}


/**
 * opd_get_ascii_maps - read all maps for a process
 * @param proc  process to work on
 *
 * Read the /proc/<pid>/maps file and add all
 * mapping information found to the process @proc.
 */
static void opd_get_ascii_maps(struct opd_proc * proc)
{
	FILE * fp;
	char mapsfile[20] = "/proc/";
	char * line;
	char exe_name[20];
	char * image_name;
	struct list_head * pos;

	snprintf(mapsfile + 6, 6, "%hu", proc->tid);

	strcpy(exe_name, mapsfile);

	strcat(mapsfile, "/maps");

	fp = op_try_open_file(mapsfile, "r");
	if (!fp)
		return;

	strcat(exe_name, "/exe");
	image_name = xmalloc(PATH_MAX);
	if (!realpath(exe_name, image_name))
		/* kernel thread are invalid symlink */
		strcpy(image_name, exe_name);

	verbprintf(vmisc, "image name %s for pid %u %u\n", image_name, proc->tid, proc->tgid);

	while (1) {
		line = op_get_line(fp);
		if (!line)
			break;

		opd_add_ascii_map(proc, line, image_name);
		free(line);
	}

	/* dae assume than the first map added is the primary image name, this
	 * is always true at exec time but not for /proc/pid so restore
	 * the primary image name
	 */
	list_for_each(pos, &proc->maps) {
		struct opd_map * map = list_entry(pos, struct opd_map, next);
		if (!strcmp(map->image->name, image_name)) {
			if (pos != proc->maps.next) {
				fprintf(stderr, "swap map for image %s from %s to %s\n", image_name, proc->name, map->image->name);
				free((char *)proc->name);
				proc->name = xstrdup(map->image->name);
			}
			break;
		}
	}

	if (list_empty(&proc->maps)) {
		/* we always need a valid proc->maps[0], we artificially give
		 * a map of length zero so on no samples will never go to this
		 * map. This is used only with --separate-samples and kernel
		 * thread when adding vmlinux and module maps to proc->maps[]
		 */
		/* FIXME: use the first field of /proc/pid/status as proc name
		 * for now we use /proc/%pid/exe as name */
		struct opd_image * image = opd_get_image(image_name,
                                       image_name, 0, proc->tid, proc->tgid);
		if (image)
			opd_add_mapping(proc, image, 0, 0, 0);
	}

	if (image_name)
		free(image_name);

	op_close_file(fp);
}


static u32 read_tgid(u32 tid)
{
	char status_file[30] = "/proc/";
	char * line;
	FILE * fp;
	u32 tgid;

	snprintf(status_file + 6, 6, "%hu", tid);

	strcat(status_file, "/status");

	fp = op_try_open_file(status_file, "r");
	if (!fp)
		return 0;

	while (1) {
		line = op_get_line(fp);
		if (!line)
			break;

		if (sscanf(line, "Tgid: %u", &tgid) == 1) {
			free(line);
			op_close_file(fp);
			return tgid;
		}
		free(line);
	}

	op_close_file(fp);

	return 0;
}


void opd_get_ascii_procs(void)
{
	DIR * dir;
	struct dirent * dirent;
	struct opd_proc * proc;
	u32 pid;

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

	while ((dirent = readdir(dir))) {
		if (sscanf(dirent->d_name, "%u", &pid) == 1) {
			u32 tgid = read_tgid(pid);
			verbprintf(vmisc, "ASCII added %u %u\n", pid, tgid);
			proc = opd_get_proc(pid, tgid);
			if (!proc)
				proc = opd_new_proc(pid, tgid);
			opd_get_ascii_maps(proc);
		}
	}

	closedir(dir);
}