#include "server_config.h"
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <glib.h>
#ifdef __FreeBSD__
#include <sys/param.h>
#include <sys/ucred.h>
#include <sys/mount.h>
#endif
#include "gam_error.h"
#include "gam_fs.h"

#define DEFAULT_POLL_TIMEOUT 0

typedef struct _gam_fs_properties {
	char * 		fsname;
	gam_fs_mon_type mon_type;
	int		poll_timeout;
} gam_fs_properties;

typedef struct _gam_fs {
	char *path;
	char *fsname;
	guint64 flags;
} gam_fs;

static gboolean initialized = FALSE;
#ifdef __FreeBSD__
static gboolean initializing = FALSE;
#endif
static GList *filesystems = NULL;
static GList *fs_props = NULL;
static struct stat mtab_sbuf;

static void
gam_fs_free_filesystems (void)
{
	GList *iterator = NULL;
	gam_fs *fs = NULL;

	iterator = filesystems;

	while (iterator) 
	{
		fs = iterator->data;

		iterator = g_list_next (iterator);

		filesystems = g_list_remove (filesystems, fs);

		g_free (fs->path);
		g_free (fs->fsname);
		g_free (fs);
	}
}


static const gam_fs *
gam_fs_find_fs (const char *path)
{
	GList *iterator = NULL;
	gam_fs *fs = NULL;

	gam_fs_init ();

	iterator = filesystems;

	while (iterator)
	{
		fs = iterator->data;

		if (g_str_has_prefix (path, fs->path)) {
			return fs;
		}
		iterator = g_list_next (iterator);
	}

	return NULL;
}

static const gam_fs_properties *
gam_fs_find_fs_props (const char *path)
{
	const gam_fs *fs = NULL;
	gam_fs_properties *props = NULL;
	GList *iterator = NULL;

	fs = gam_fs_find_fs (path);
	if (!fs)
		return NULL;

	iterator = fs_props;

	while (iterator) 
	{
		props = iterator->data;

		if (!strcmp (props->fsname, fs->fsname)) {
			return props;
		}
		iterator = g_list_next (iterator);
	}

	return NULL;
}


static gint
gam_fs_filesystem_sort_cb (gconstpointer a, gconstpointer b)
{
	const gam_fs *fsa = a;
	const gam_fs *fsb = b;

	return strlen(fsb->path) - strlen (fsa->path);
}

#if defined(__linux__) || defined(__CYGWIN__)
static void
gam_fs_scan_mtab (void)
{
	gchar *contents, **lines, *line, **words;
	gsize len;
	GList *new_filesystems = NULL;
	gam_fs *fs = NULL;
	int i;

	g_file_get_contents ("/etc/mtab", &contents, &len, NULL);
	if (contents == NULL)
		return;

	lines = g_strsplit (contents, "\n", 0);
	if (lines != NULL)
	{
		for (i = 0; lines[i] != NULL; i++)
		{
			line = lines[i];

			if (line[0] == '\0')
				continue;

			words = g_strsplit (line, " ", 0);

			if (words == NULL)
				continue;

			if (words[0] == NULL || words[1] == NULL || words[2] == NULL) 
			{
				g_strfreev (words);
				continue;
			}
			if (words[1][0] == '\0' || words[2][0] == '\0')
			{
				g_strfreev (words);
				continue;
			}

			fs = g_new0 (gam_fs, 1);
			fs->path = g_strdup (words[1]);
			fs->fsname = g_strdup (words[2]);

			g_strfreev (words);

			new_filesystems = g_list_prepend (new_filesystems, fs);
		}
		g_strfreev (lines);
	}
	g_free (contents);

	/* Replace the old file systems list with the new one */
	gam_fs_free_filesystems ();
	filesystems = g_list_sort (new_filesystems, gam_fs_filesystem_sort_cb);
}
#endif

#ifdef __FreeBSD__
static void
gam_fs_getmntinfo (void)
{
	struct statfs *stat;
	GList *new_filesystems = NULL;
	gam_fs *fs = NULL;
        int i, n;

	n = getmntinfo(&stat, MNT_NOWAIT);
	if (n == -1)
		return;

	for (i = 0; i < n; i++)
	{
		fs = g_new0 (gam_fs, 1);
		fs->path = g_strdup (stat[i].f_mntonname);
		fs->fsname = g_strdup (stat[i].f_fstypename);
		fs->flags = stat[i].f_flags;

		new_filesystems = g_list_prepend (new_filesystems, fs);
	}

        /* Replace the old file systems list with the new one */
        gam_fs_free_filesystems ();
        filesystems = g_list_sort (new_filesystems, gam_fs_filesystem_sort_cb);
}
#endif

void
gam_fs_init (void)
{
#if defined(__linux__) || defined(__CYGWIN__)
	if (initialized == FALSE)
	{
		initialized = TRUE;
		gam_fs_set ("ext3", GFS_MT_DEFAULT, 0);
		gam_fs_set ("ext2", GFS_MT_DEFAULT, 0);
		gam_fs_set ("reiser4", GFS_MT_DEFAULT, 0);
		gam_fs_set ("reiserfs", GFS_MT_DEFAULT, 0);
		gam_fs_set ("novfs", GFS_MT_POLL, 30);
		gam_fs_set ("nfs", GFS_MT_POLL, 5);
		gam_fs_set ("ntfs", GFS_MT_POLL, 5);
		gam_fs_set ("vfat", GFS_MT_POLL, 5);
		if (stat("/etc/mtab", &mtab_sbuf) != 0)
		{
			GAM_DEBUG(DEBUG_INFO, "Could not stat /etc/mtab\n");
                        return;
		}
		gam_fs_scan_mtab ();
	} else {
		struct stat sbuf;

		if (stat("/etc/mtab", &sbuf) != 0)
		{
			GAM_DEBUG(DEBUG_INFO, "Could not stat /etc/mtab\n");
                        return;
		}

		/* /etc/mtab has changed */
		if (sbuf.st_mtime != mtab_sbuf.st_mtime) {
			GAM_DEBUG(DEBUG_INFO, "Updating list of mounted filesystems\n");
			gam_fs_scan_mtab ();
		}

		mtab_sbuf = sbuf;
	}
#elif defined(__FreeBSD__)
	if (initialized == FALSE && initializing == FALSE)
	{
		GList *iterator = NULL;
		GHashTable *fs_hash = NULL;
		gam_fs *fs = NULL;

		initialized = TRUE;
		initializing = TRUE;

		gam_fs_getmntinfo ();

		iterator = filesystems;
		fs_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);

		while (iterator) {
			fs = iterator->data;

			if (!g_hash_table_lookup (fs_hash, fs->fsname)) {
				if (fs->flags & MNT_LOCAL)
					gam_fs_set (fs->fsname, GFS_MT_KERNEL, 0);
				else
					gam_fs_set (fs->fsname, GFS_MT_POLL, 5);

				g_hash_table_insert (fs_hash, g_strdup (fs->fsname), GINT_TO_POINTER (1));
			}

			iterator = g_list_next (iterator);
		}

		g_hash_table_destroy (fs_hash);
		initializing = FALSE;
	} else if (initializing == FALSE) {
		struct stat sbuf;

		if (stat ("/etc/fstab", &sbuf) != 0) {
			GAM_DEBUG(DEBUG_INFO, "Could not stat /etc/fstab\n");
			return;
		}

		if (sbuf.st_mtime != mtab_sbuf.st_mtime) {
			GAM_DEBUG(DEBUG_INFO, "Updating list of mounted filesystems\n");
			gam_fs_getmntinfo ();
		}

		mtab_sbuf = sbuf;
	}
#endif
}

gam_fs_mon_type
gam_fs_get_mon_type (const char *path)
{
	const gam_fs_properties *props = NULL;
	gam_fs_init ();

	props = gam_fs_find_fs_props (path);

#if (defined(ENABLE_INOTIFY) || defined(ENABLE_DNOTIFY) || defined(ENABLE_KQUEUE) || defined(ENABLE_HURD_MACH_NOTIFY)) && defined(USE_GAMIN_POLLER)
	if (props)
		return props->mon_type;
#elif !defined(USE_GAMIN_POLLER)
	return GFS_MT_DEFAULT;
#endif
	return GFS_MT_POLL;
}

int
gam_fs_get_poll_timeout (const char *path)
{
	const gam_fs_properties *props = NULL;
	gam_fs_init ();

	props = gam_fs_find_fs_props (path);

	if (!props)
		return DEFAULT_POLL_TIMEOUT;

	return props->poll_timeout;
}

void
gam_fs_set (const char *fsname, gam_fs_mon_type type, int poll_timeout)
{
	GList *iterator = NULL;
	gam_fs_properties *prop = NULL;

	gam_fs_init ();
	iterator = fs_props;

	while (iterator) 
	{
		prop = iterator->data;

		if (!strcmp (prop->fsname, fsname)) {
			prop->mon_type = type;
			if (poll_timeout >= 0)
				prop->poll_timeout = poll_timeout;
			return;
		}

		iterator = g_list_next (iterator);
	}

	prop = g_new0(gam_fs_properties, 1);

	prop->fsname = g_strdup (fsname);
	prop->mon_type = type;
	if (poll_timeout >= 0)
		prop->poll_timeout = poll_timeout;
	else
		prop->poll_timeout = DEFAULT_POLL_TIMEOUT;

	fs_props = g_list_prepend (fs_props, prop);
}

void
gam_fs_unset (const char *fsname)
{
	GList *iterator = NULL;
	gam_fs_properties *prop = NULL;

	gam_fs_init ();

	iterator = fs_props;

	while (iterator) 
	{
		prop = iterator->data;

		if (!strcmp (prop->fsname, fsname)) {
			fs_props = g_list_remove (fs_props, prop);
			g_free (prop->fsname);
			g_free (prop);
			return;
		}
		iterator = g_list_next (iterator);
	}
}

void
gam_fs_debug (void)
{
	GList *iterator = NULL;
	gam_fs_properties *prop = NULL;
	gam_fs *fs = NULL;

	gam_fs_init ();

	iterator = filesystems;

	GAM_DEBUG (DEBUG_INFO, "Dumping mounted file systems\n");
	while (iterator)
	{
		fs = iterator->data;
		GAM_DEBUG (DEBUG_INFO, "%s filesystem mounted at %s\n", fs->fsname, fs->path);
		iterator = g_list_next (iterator);
	}

	iterator = fs_props;
	GAM_DEBUG (DEBUG_INFO, "Dumping file system properties\n");
	while (iterator)
	{
		prop = iterator->data;
		GAM_DEBUG (DEBUG_INFO, "fstype %s monitor %s poll timeout %d\n", prop->fsname, (prop->mon_type == GFS_MT_KERNEL) ? "kernel" : (prop->mon_type == GFS_MT_POLL) ? "poll" : "none", prop->poll_timeout);
		iterator = g_list_next (iterator);
	}
}
