/*
 * Copyright (c) 2003, 2004, 2005 Nokia
 * Author: Timo Savola <tsavola@movial.fi>
 *
 * This program is licensed under GPL (see COPYING for details)
 */

#include "mount.h"
#include "common.h"

#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/param.h>

mount_info_t *mntinfo_alloc(void)
{
	return calloc(1, sizeof (mount_info_t));
}

void mntinfo_free(mount_info_t *mi)
{
	if (mi->opts)
		free(mi->opts);

	if (mi->device)
		free(mi->device);

	if (mi->point)
		free(mi->point);

	free(mi);
}

int mntinfo_copy(mount_info_t *dest, mount_info_t *src)
{
	memset(dest, 0, sizeof (mount_info_t));

	dest->type = src->type;

	dest->point = strdup(src->point);
	if (!dest->point)
		goto _err;

	dest->device = strdup(src->device);
	if (!dest->device)
		goto _err;

	if (src->opts) {
		dest->opts = strdup(src->opts);
		if (!dest->opts)
			goto _err;
	}

	dest->device_dev = src->device_dev;

	return 0;

_err:
	if (dest->device)
		free(dest->device);

	if (dest->point)
		free(dest->point);

	oom_error();
	return -1;
}

/**
 * Returns a newly allocated mount_info_t and sets its fields according
 * to the input string.
 */
mount_info_t *mntinfo_parse(const char *input)
{
	mount_info_t *mi;
	char *buf = NULL, *type, *device, *point, *opts;

	mi = mntinfo_alloc();
	if (!mi) {
		oom_error();
		return NULL;
	}

	buf = strdup(input);
	if (!buf) {
		oom_error();
		goto _err_buf;
	}

	split_string(buf, &type, &device, &point, &opts, NULL);

	if (!type || !device || !point) {
		error("Invalid mount entry: %s", input);
		goto _err;
	}

	if (strcmp(type, MTYPE_NFS_S) == 0) {
		mi->type = MTYPE_NFS;
	} else if (strcmp(type, MTYPE_BIND_S) == 0) {
		mi->type = MTYPE_BIND;
	} else if (strcmp(type, MTYPE_SSH_S) == 0) {
		mi->type = MTYPE_SSH;
	} else {
		error("Unknown mount type: %s", type);
		goto _err;
	}

	mi->device = strdup(device);
	if (!mi->device) {
		oom_error();
		goto _err;
	}

	mi->point = strdup(point);
	if (!mi->point) {
		oom_error();
		goto _err;
	}

	if (opts) {
		mi->opts = strdup(opts);
		if (!mi->opts) {
			oom_error();
			goto _err;
		}
	}

	free(buf);

	return mi;

_err:
	free(buf);

_err_buf:
	mntinfo_free(mi);
	return NULL;
}

/*
 * Parse IP address (addr_p) and path (path_p) from NFS mount string (fs).
 * NFS mount string should be of the form "<host>:<path>".
 * Returns -1 on error; 0 otherwise.
 */
static int resolve_nfs(const char *fs, uint32_t *addr_p, char **path_p)
{
	char host[MAXHOSTNAMELEN], *path;
	uint32_t addr;

	path = strchr(fs, ':');
	if (!path || path[1] == '\0') {
		error("Invalid NFS filesystem: %s", fs);
		return -1;
	}

	memset(host, '\0', sizeof (host));
	strncpy(host, fs, MIN(path - fs, sizeof (host) - 1));

	addr = resolve(host);
	if (!addr)
		return -1;

	*addr_p = addr;
	*path_p = path + 1;

	return 0;
}

/*
 * Check if NFS filesystem is not originating from this host, and try to
 * find the local mount point if it's originating from a remote host.
 */
static char *get_local_path(uint32_t r_addr, char *r_path)
{
	FILE *file;
	char buf[1024];
	struct { char nfs_path[PATH_MAX], *path; } sel;

	sel.path = NULL;

	file = fopen(MOUNTS_FILE, "r");
	if (!file) {
		error("Can't open " MOUNTS_FILE);
		return NULL;
	}

	while (1) {
		char *device, *point, *type, *nfs_path;
		uint32_t nfs_addr;

		if (read_line(file, buf, sizeof (buf)) < 0)
			break;

		split_string(buf, &device, &point, &type, NULL);
		if (!device || !point || !type || !strchr(device,':') || strcmp(type, "nfs") != 0)
			continue;

		if (resolve_nfs(device, &nfs_addr, &nfs_path) < 0)
			goto _err;

		if (r_addr != nfs_addr)
			continue;

		if (strncmp(r_path, nfs_path, strlen(nfs_path)) != 0)
			continue;

		/* addr and nfs_path match */

		if (sel.path) {
			if (strlen(sel.nfs_path) < strlen(nfs_path))
				continue;

			free(sel.path);
		}

		sel.path = strdup(point);
		if (!sel.path) {
			oom_error();
			goto _err;
		}

		memset(sel.nfs_path, '\0', sizeof (sel.nfs_path));
		strncpy(sel.nfs_path, nfs_path, sizeof (sel.nfs_path) - 1);
	}

	fclose(file);

	if (sel.path)
		return sel.path;
	else
		return r_path;

_err:
	if (sel.path)
		free(sel.path);

	fclose(file);

	return NULL;
}

static char *scratchbox_path_alias(const char *const path)
{
	const char *const scratchbox = "/scratchbox/";
	const size_t scratchbox_len = strlen(scratchbox);

	char *alias, *renamed;
	size_t alias_len, renamed_len;

	alias = getenv("_SBOX_DIR");
	if (!alias)
		return NULL;

	alias_len = strlen(alias);
	if (alias_len <= 1 || strncmp(path, alias, alias_len) != 0)
		return NULL;

	renamed_len = strlen(path) - alias_len + scratchbox_len + 1;
	renamed = malloc(renamed_len);
	if (!renamed) {
		oom_error();
		return NULL;
	}

	strcpy(renamed, scratchbox);
	strcat(renamed, path + alias_len);

	return renamed;
}

/**
 * Fills in the device_dev field of mount_info_t.
 */
int mntinfo_stat_device(mount_info_t *mi)
{
	char *nfs_path = NULL, *path, *renamed_path;
	uint32_t nfs_addr;
	struct stat buf;
	int rc;

	if (mi->type == MTYPE_NFS) {
		if (resolve_nfs(mi->device, &nfs_addr, &nfs_path) < 0)
			return -1;

		path = get_local_path(nfs_addr, nfs_path);
		if (!path)
			return -1;

		renamed_path = scratchbox_path_alias(path);
		if (renamed_path) {
			if (path != nfs_path)
				free(path);

			path = renamed_path;
		}
	} else {
		path = strchr(mi->device, ':');
		if (!path)
			return -1;
		path++;
	}

	rc = stat(path, &buf);
	if (rc >= 0)
		mi->device_dev = buf.st_dev;
	else
		error("Can't stat %s", path);

	if (nfs_path && path != nfs_path)
		free(path);

	return rc;
}

/**
 * Bubble sort based on mount point string.
 */
void mntinfo_sort_vec(mount_info_t **vec)
{
	mount_info_t **p, *tmp;
	bool_t touched;

	if (!vec[0] || !vec[1])
		return;

	do {
		touched = FALSE;

		for (p = vec; p[1]; ++p)
			if (strcmp(p[0]->point, p[1]->point) > 0) {
				tmp = p[0];
				p[0] = p[1];
				p[1] = tmp;

				touched = TRUE;
			}

	} while (touched);
}
