/**
 * @file op_file.c
 * Useful file management helpers
 *
 * @remark Copyright 2002 OProfile authors
 * @remark Read the file COPYING
 *
 * @author John Levon
 * @author Philippe Elie
 */

#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <fnmatch.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <limits.h>

#include "op_file.h"
#include "op_libiberty.h"

int op_file_readable(char const * file)
{
	struct stat st;
	return !stat(file, &st) && S_ISREG(st.st_mode) && !access(file, R_OK);
}


time_t op_get_mtime(char const * file)
{
	struct stat st;

	if (stat(file, &st))
		return 0;

	return st.st_mtime;
}


int create_dir(char const * dir)
{
	if (mkdir(dir, 0755)) {
		/* FIXME: Does not verify existing is a dir */
		if (errno == EEXIST)
			return 0;
		return errno;
	}

	return 0;
}


int create_path(char const * path)
{
	int ret = 0;

	char * str = xstrdup(path);

	char * pos = str[0] == '/' ? str + 1 : str;

	for ( ; (pos = strchr(pos, '/')) != NULL; ++pos) {
		*pos = '\0';
		ret = create_dir(str);
		*pos = '/';
		if (ret)
			break;
	}

	free(str);
	return ret;
}


inline static int is_dot_or_dotdot(char const * name)
{
	return name[0] == '.' &&
		(name[1] == '\0' ||
		 (name[1] == '.' && name[2] == '\0'));
}


/* If non-null is returned, the caller is responsible for freeing
 * the memory allocated for the return value. */
static char * make_pathname_from_dirent(char const * basedir,
				      struct dirent * ent,
				      struct stat * st_buf)
{
	int name_len;
	char * name;
	name_len = strlen(basedir) + strlen("/") + strlen(ent->d_name) + 1;
	name = xmalloc(name_len);
	sprintf(name, "%s/%s", basedir,	ent->d_name);
	if (stat(name, st_buf) != 0)
	{
		struct stat lstat_buf;
		int err = errno;
		if (lstat(name, &lstat_buf) == 0 &&
			    S_ISLNK(lstat_buf.st_mode)) {
			// dangling symlink -- silently ignore
		} else {
			fprintf(stderr, "stat failed for %s (%s)\n",
			                name, strerror(err));
		}
		free(name);
		name = NULL;
	}
	return name;
}


int get_matching_pathnames(void * name_list, get_pathname_callback getpathname,
			   char const * base_dir, char const * filter,
			   enum recursion_type recursion)
{
/* The algorithm below depends on recursion type (of which there are 3)
 * and whether the current dirent matches the filter.  There are 6 possible
 * different behaviors, which is why we define 6 case below in the switch
 * statement of the algorithm.  Actually, when the recursion type is
 * MATCH_DIR_ONLY_RECURSION, the behavior is the same, whether or not the dir
 * entry matches the filter.  However, the behavior of the recursion types
 * NO_RECURSION and MATCH_ANY_ENTRY_RECURSION do depend on the dir entry
 * filter match, so for simplicity, we perform this match for all recursion
 * types and logically OR the match result with the  value of the passed
 * recursion_type.
 */
#define NO_MATCH 0
#define MATCH 1

	DIR * dir;
	struct dirent * ent;
	struct stat stat_buffer;
	int match;
	char * name = NULL;

	if (!(dir = opendir(base_dir)))
		return -1;
	while ((ent = readdir(dir)) != 0) {
		if (is_dot_or_dotdot(ent->d_name))
			continue;
		if (fnmatch(filter, ent->d_name, 0) == 0)
			match = 1;
		else
			match = 0;

		switch (recursion | match) {
		case NO_RECURSION + NO_MATCH:
		case MATCH_ANY_ENTRY_RECURSION + NO_MATCH:
			// nothing to do but continue the loop
			break;
		case NO_RECURSION + MATCH:
			getpathname(ent->d_name, name_list);
			break;
		case MATCH_ANY_ENTRY_RECURSION + MATCH:
			name = make_pathname_from_dirent(base_dir, ent,
						       &stat_buffer);
			if (name) {
				if (S_ISDIR(stat_buffer.st_mode)) {
					get_matching_pathnames(
						name_list, getpathname,
						name, filter, recursion);
				} else {
					getpathname(name, name_list);
				}
			}
			free(name);
			break;
		case MATCH_DIR_ONLY_RECURSION + NO_MATCH:
		case MATCH_DIR_ONLY_RECURSION + MATCH:
			name = make_pathname_from_dirent(base_dir, ent,
						       &stat_buffer);
			if (name && S_ISDIR(stat_buffer.st_mode)) {
				/* Check if full directory name contains
				 * match to the filter; if so, add it to
				 * name_list and quit; else, recurse.
				 */
				if (!fnmatch(filter, name, 0)) {
					getpathname(name, name_list);
				} else {
					get_matching_pathnames(
						name_list, getpathname,
						name, filter, recursion);
				}
			}
			free(name);
			break;
		}
	}
	closedir(dir);

	return 0;
}