/* $Id: e2_fs_mount.c 2064 2010-03-12 13:15:36Z tpgww $

Copyright (C) 2007-2009 <tooar@emelfm2.net>
Copyright (C) 2004-2007 Florian Zaehringer <flo.zaehringer@web.de>
Copyright (C) 1999-2004 Bill Wilson <bill@gkrellm.net>

This file is part of emelFM2.
emelFM2 is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.

emelFM2 is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with emelFM2; see the file GPL. If not, see http://www.gnu.org/licenses.
*/

/**
@file src/filesystem/e2_fs_mount.c
@brief filesystem mountpoint related functions

Functions dealing with fstab / mtab have in part been sourced from gkrellm
(http://gkrellm.net) written by Bill Wilson.
*/

#include "emelfm2.h"

#ifdef E2_FS_MOUNTABLE

#include <string.h>
#include <unistd.h>

//some file-system identifiers (from <linux/magic.h>, which isn't generally available)
#ifndef MSDOS_SUPER_MAGIC
# define MSDOS_SUPER_MAGIC 0x4d44
#endif
#ifndef SMB_SUPER_MAGIC
# define SMB_SUPER_MAGIC 0x517B
#endif
#ifndef USBDEVICE_SUPER_MAGIC
# define USBDEVICE_SUPER_MAGIC 0x9fa2
#endif

//in general, we support [u]mount completion
#define E2_FS_MOUNTABLE
// OSX 10.4 ("Tiger") automounts all devices. Unlike its predecessors, it uses statvfs()
#if defined(darwin)
#include <sys/types.h>
#include <sys/param.h>
#ifdef HAVE_SYS_STATVFS_H
#undef E2_FS_MOUNTABLE
#endif
#endif
//other exclusions go here ...

# if defined(__linux__)
#  include <mntent.h>
#  include <fstab.h>

# elif defined(__E2BSD__)	//maybe this should just be defined(__FreeBSD__) || defined(__OpenBSD__)
#  include <sys/param.h>
#  include <sys/mount.h>
#  include <fstab.h>

# elif defined(__solaris__) || defined(sco)
//CHECKME sco ?? svr5 in general ??
#  include <sys/mnttab.h>
#  include <vfstab.h>

# else
#  include <mntent.h>
#  include <fstab.h>

/*Some systems use statfs() to provide information about mounted
file systems, other systems use statvfs(). The header files used with
these functions vary between systems.
e.g.
HPUX 	  statfs() with sys/vfs.h
SunOS   statfs() with sys/vfs.h
Solaris  statvfs() with sys/statvfs.h */

#  include <sys/types.h>
#  include <sys/param.h>
#  ifdef HAVE_SYS_STATVFS_H
#   include <sys/statvfs.h>
#  endif
#  ifdef HAVE_SYS_VFS_H
#   include <sys/vfs.h>
#  endif

/*#ifdef HAVE_SYS_STATVFS_H
#define STATFS statvfs
#else
#define STATFS statfs
#endif

...
     struct STATFS sfs;
...
     rc = STATFS(filename, &sfs);
*/
# endif	//which os-specifis includes

//test which mounted devices should be included in the list
//of unmountable ones
//CHECKME does this need to be os-specific ? if so, how ?
# if defined(__linux__) || defined(__E2BSD__)
#  define E2_TEST_MOUNTED_DEVICE \
		if (   strcmp (dev, "none") \
			&& strcmp (dev, "/dev") \
			&& strcmp (type, "proc") \
			&& strcmp (type, "sysfs") \
			&& strcmp (type, "fuse") \
			&& strcmp (dir, G_DIR_SEPARATOR_S)) /*no need to support umounting '/' */ \

# else
#  define E2_TEST_MOUNTED_DEVICE \
		if (   strcmp (dev, "none") \
			&& strcmp (dev, "/dev") \
			&& strcmp (type, "sysfs") \
			&& strcmp (type, "proc") \
			&& strcmp (dir, G_DIR_SEPARATOR_S)) /*no need to support umounting '/' */ \

# endif
/*	these should be handled by dev="none"
			&& strcmp (type, "devpts")
			&& strcmp (type, "proc")
			&& strcmp (type, "usbdevfs")
			&& strcmp (type, "usbfs")
			&& strcmp (type, "sysfs")
			&& strstr (dir, "/dev/") */
/* permission-checking disabled pending doing it properly, if not forever
			gchar *opt = mounted->mnt_opts;
			if (!permcheck || _e2_complete_mount_permission (dir, opt)) */
/*for bsd's, which mount options are relevant ? how do we check them ?
		mounted[i].f_flags	//mount flags
		mounted[i].f_owner	//owner
*/
/* there may be things blocking an unmount eg monitoring with dnotify */

//finesse mounted device name before inclusion in the list of
//unmountable devices
# define E2_PROCESS_MOUNTED_DEVICE
//strip any trailing / from the mountpoint name
//			s = strrchr (dir, G_DIR_SEPARATOR);
//			if (s != NULL && s != dir && *(s+1) == '\0')
//				*s = '\0';

//test which unmounted devices should be included in the list
//of mountable ones
//CHECKME does this need to be os-specific ? if so, how ?
# define E2_TEST_UNMOUNTED_DEVICE \
		if (   strcmp (dev, "none") \
			&& strcmp (type, "swap") \
			&& strcmp (type, "proc") \
			&& strcmp (type, "sysfs") \
			&& strcmp (type, "ignore") \
			&& strcmp (dir, G_DIR_SEPARATOR_S)	/*no need to support mounting '/' */ \
			&& type[0] != '\0' )
/*
//			&& g_file_test (dir, G_FILE_TEST_IS_DIR) don't bother checking for this (slow)
    these should be handled by dev="none"
			&& strcmp (type, "devpts")
			&& strcmp (type, "proc")
			&& strcmp (type, "sysfs")
			&& strcmp (type, "usbdevfs")
			&& strcmp (type, "usbfs")
			&& strstr (dir, "/dev/") == NULL
CHECKME sometimes, test may need to include
	strncmp (dev, "/dev", 4)
	realpath (dev, realdev) != NULL
	gchar realdev[PATH_MAX];
*/
/* permission-checking disabled pending doing it properly, if not forever
			gchar *opt = fs.vfs_mntopts;
			if (!permcheck || _e2_complete_mount_permission (dir, opt)) */

//finesse unmounted device name before inclusion in the list of
//mountable devices
# define E2_PROCESS_UNMOUNTED_DEVICE
			//strip any trailing / from the mountpoint name
//			s = strrchr (dir, G_DIR_SEPARATOR);
//			if (s != NULL && s != dir && *(s+1) == '\0')
//				*s = '\0';

//guint uid;
//time_t fstab_mtime;

/**
@brief check whether @a localpath is a filesystem mountpoint

@param localpath localised absolute path string

@return TRUE if @a localpath is a mountpoint
*/
gboolean e2_fs_mount_is_mountpoint (VPATH *localpath)
{
	struct stat sb;
#ifdef E2_VFS
	if (localpath->spacedata != NULL)
		return FALSE;	//mountpoints are always local
#endif
	//root path is not detected by mountpoints lister
	if (!strcmp (VPSTR (localpath), G_DIR_SEPARATOR_S))
		return TRUE;

	if (e2_fs_lstat (localpath, &sb E2_ERR_NONE()) || !S_ISDIR (sb.st_mode))
		return FALSE;

	gboolean matched = FALSE;
	gchar *utf = F_FILENAME_FROM_LOCALE (VPSTR(localpath));
	GList *points = e2_fs_mount_get_mounts_list ();
	if (points != NULL)
	{
		matched = (g_list_find_custom (points, utf, (GCompareFunc) e2_list_strcmp) != NULL);
		e2_list_free_with_data (&points);
	}
#if defined(__linux__) || defined(__FreeBSD__)
	if (!matched)
	{
		points = e2_fs_mount_get_fusemounts_list ();
		if (points != NULL)
		{
			matched = (g_list_find_custom (points, utf, (GCompareFunc) e2_list_strcmp) != NULL);
			e2_list_free_with_data (&points);
		}
	}
#endif
	F_FREE (utf, VPSTR(localpath));
	return matched;
}
#ifdef E2_HAL
/**
@brief find mountpoint dir whose device is @a device
@param device localised string with device of mountpoint to be found

@return newly allocated string with mountpoint, or NULL upon error or no match
*/
gchar *e2_fs_mount_get_mountpoint_for_device (const gchar *device)
{
	//FIXME convert this func to check & get any related string(s):
	//	device, mountpoint, mount options
	gchar *mountpath = NULL;
	//CHECKME lock data file while accessing it ?? re-entrant forms of functions ?
#if defined(__E2BSD__) || defined(__linux__)
    struct fstab *fs;

	if (!setfsent ())
		return NULL;

	while ((fs = getfsent ()) != NULL)
	{
		if (strcmp (fs->fs_spec, device) == 0)
		{
			mountpath = g_strdup (fs->fs_file);
			break;
		}
    }
    endfsent ();
#elif defined(__solaris__) || defined(sco)
	//CHECKME sco
    struct vfstab fs;
	E2_FILE *f;

	if ((f = e2_fs_open_stream ("/etc/vfstab", "r")) == NULL)
		return NULL;

	while (getvfsent (f, &fs) == 0)
	{
		if (strcmp (fs.vfs_special, device) == 0)
		{
			mountpath = g_strdup (fs.vfs_mountp);
			break;
		}
	}
	e2_fs_close_stream (f);
#elif defined(hpux)
    struct mntent *mountable;
	E2_FILE *f;

    //handle PFS if necessary
    const gchar *fstab = (access("/etc/pfs_fstab", F_OK)) ?
		"/etc/fstab" : "/etc/pfs_fstab";

	if ((f = setmntent (fstab, "r")) == NULL)
		return NULL;

	while ((mountable = getmntent (f)) != NULL)
	{
		if (strcmp (mountable->mnt_fsname, device) == 0)
		{
			mountpath = g_strdup (mounted->mnt_dir);
			break;
		}
	}
	endmntent (f);
#elif defined(__svr4__)
	struct mnttab mountable;
	E2_FILE *f;

	if ((f = e2_fs_open_stream ("/etc/fstab", "r")) == NULL)
		return NULL;

	while (getmntent (f, &mountable) == 0)
	{
		if (strcmp (mountable.mnt_special, device) == 0)
		{
			mountpath = g_strdup (mountable.mnt_mountp);
			break;
		}
	}
	e2_fs_close_stream (f);
#else
	struct mntent *mountable;
	E2_FILE *f;

	if ((f = setmntent ("/etc/fstab", "r" )) == NULL)
		return NULL;

	while ((mountable = getmntent (f)) != NULL)	//data is not thread- or process-safe
	{
		if (strcmp (mountable->mnt_fsname, device) == 0)
		{
			mountpath = g_strdup (mountable->mnt_dir);
			break;
		}
	}
	endmntent (f);
#endif	//which OS
	return mountpath;
}
#endif //def E2_HAL
/**
@brief add @a dir to list @a mounts

The string is copied, with conversion to utf-8 if need be

@param dir localised string with path of mountpoint to be added
@param points store of pointer to the list to be updated

@return
*/
static void _e2_fs_mount_add_to_list (gchar *dir, GList **points)
{
	gchar *utf = D_FILENAME_FROM_LOCALE (dir);
	*points = g_list_append (*points, utf);
}
/**
@brief check whether @a localpath is a fuse mountpoint

@param localpath localised absolute path string

@return TRUE if @a localpath is a fuse mountpoint
*/
gboolean e2_fs_mount_is_fusepoint (VPATH *localpath)
{
#if defined(__linux__) || defined(__FreeBSD__)
# ifdef E2_VFS
	if (localpath->spacedata != NULL)
		return FALSE;	//mountpoints are always local
# endif
	gboolean retval;
#ifdef __linux__
	struct mntent *mounted;
	E2_FILE *f;
/* When the linux proc filesystem is mounted, the files /etc/mtab and /proc/mounts
	have very similar contents. The former has somewhat more information (CHECKME),
	such as the mount options used, but is not necessarily up-to-date */
	if ((f = setmntent ("/proc/mounts", "r")) == NULL
		&& (f = setmntent ("/etc/mtab", "r")) == NULL)
			return FALSE;

	retval = FALSE;
	while ((mounted = getmntent (f)) != NULL)
	{
		if (!strcmp (mounted->mnt_type, "fuse")
			&& g_str_has_prefix (VPCSTR (localpath), mounted->mnt_dir))
		{
			retval = TRUE;
			break;
		}
	}
	endmntent (f);
#elif defined(__FreeBSD__)
	retval = FALSE;
	struct statfs *mounted;
	//NOTE: data provided by this is not process- or thread-safe
	gint i, count = getmntinfo (&mounted, MNT_NOWAIT);

	for (i=0; i<count; i++)
	{
		if (!strcmp (mounted[i].f_fstypename, "fuse")
			&& g_str_has_prefix (VPCSTR (localpath), mounted[i].f_mntonname))
		{
			retval = TRUE;
			break;
		}
	}
#endif
	return retval;
#else
	return FALSE;
#endif
}
#if defined(__linux__) || defined(__FreeBSD__)
/**
@brief create list of fuse mountpoints
The list is created by the appropriate os-specifc protocol.
Mountpoints are stored as utf8 strings

@return list of utf-8 mountpoints, or NULL
*/
GList *e2_fs_mount_get_fusemounts_list (void)
{
	GList *mounts_list = NULL;
	//FIXME lock the data source while accessing it
#if defined(__linux__)
	struct mntent *mounted;
	E2_FILE *f;
/* When the linux proc filesystem is mounted, the files /etc/mtab and /proc/mounts
	have very similar contents. The former has somewhat more information (CHECKME),
	such as the mount options used, but is not necessarily up-to-date */
	if ((f = setmntent ("/proc/mounts", "r")) == NULL)
		if ((f = setmntent ("/etc/mtab", "r")) == NULL)
			return NULL;	//FIXME report an error

	while ((mounted = getmntent (f)) != NULL)
	{
		if (!strcmp (mounted->mnt_type, "fuse"))
			_e2_fs_mount_add_to_list (mounted->mnt_dir, &mounts_list);
	}
	endmntent (f);
#elif defined(__FreeBSD__)
	struct statfs *mounted;

	//NOTE: data provided by this is not process- or thread-safe
	gint i, count = getmntinfo (&mounted, MNT_NOWAIT);

	for (i=0; i<count; i++)
	{
		if (!strcmp (mounted[i].f_fstypename, "fuse"))
			_e2_fs_mount_add_to_list (mounted[i].f_mntonname, &mounts_list);
	}
#endif
	return mounts_list;
}
#endif //which OS
/* *
@brief replace escaped character codes in @a buf
@param buf buffer with string from fs/mtab, presumably locale-encoded
@return
*/
/* UNUSED
static void _e2_fs_mount_fix_fstab_name (gchar *buf)
{
	gchar *rp, *wp;

	if (buf[0] == '\0')
		return;
	rp = buf;
	wp = buf;
	do	// This loop same as in libc6 getmntent()
		if (rp[0] == '\\' && rp[1] == '0' && rp[2] == '4' && rp[3] == '0')
		{
			*wp++ = ' ';		// \040 is a SPACE
			rp += 3;
		}
		else if (rp[0] == '\\' && rp[1] == '0' && rp[2] == '1' && rp[3] == '2')
		{
			*wp++ = '\t';		// \012 is a TAB
			rp += 3;
		}
		else if (rp[0] == '\\' && rp[1] == '\\')
		{
			*wp++ = '\\';		// \\ is a \
			rp += 1;
		}
		else
			*wp++ = *rp;
	while (*rp++ != '\0');
} */
/* *
@brief check whether current user is permitted to [u]mount mountpoint @a device
@param device mountpoint path-string, presumably locale-encoded
@param options is a string with the (4th) parameters string from a line in fs/mtab
@return TRUE if permitted
*/
/* UNUSED FIXME, if this is used, needs to account for sudo & group membership etc
 and work properly for all os's
static gboolean _e2_fs_mount_permission (gchar* device, gchar *options)
{
	if (uid == 0 	//root can always do it
		|| strstr (options, "dev,") != NULL //CHECKME _I(
		|| (strstr (options, "user") != NULL && strstr (options, "nouser") == NULL)) //CHECKME _I(
		return TRUE;
#if defined (__linux__)
	struct stat my_stat;
	//no need to localise *device before stat
	return (e2_fs_stat (device, &my_stat E2_ERR_NONE()) == 0
		&& my_stat.st_uid == uid
		&& strstr (options, "owner") != NULL //CHECKME _I(
	   );
#else
	return FALSE;
#endif
} */

#if defined (E2_HAL) || defined (E2_DEVKIT)
/**
@brief get device corresponding to mounted directory @a utfpath
@param absolute path, utf8, may have trailing separator
This gets the device for the mounted point with the longest match between
the point's path and @a utfpath. Only works properly when @a utfpath is mounted.
Simple string comparisons are performed. @a utfpath may be quoted.
@return newly-allocated string, or NULL
*/
gchar *_e2_fs_get_mounted_device (const gchar *utfpath)
{
	gint local_length, current_length, longest_sofar;
	gchar *clean, *local, *thisdev, *bestdev;

//=========== OS-specific stuff =============
#if defined(__linux__)
	struct mntent *mounted;
	E2_FILE *f;
/* When the linux proc filesystem is mounted, the files /etc/mtab and /proc/mounts
	have very similar contents. The former has somewhat more information (CHECKME),
	such as the mount options used, but is not necessarily up-to-date */
	if ((f = setmntent ("/proc/mounts", "r")) == NULL
//	 && (f = setmntent ("/etc/mtab", "r")) == NULL
	)
		return NULL;

	clean = e2_utils_unquote_string (utfpath);
	if (clean == NULL)
		return NULL;
	local = F_FILENAME_TO_LOCALE (clean);
	local_length = strlen (local);	//length includes trailing /
	longest_sofar = 0;
	bestdev = NULL;

	while ((mounted = getmntent (f)) != NULL)
	{
		current_length = strlen (mounted->mnt_dir);
		if (strncmp (local, mounted->mnt_dir, current_length) == 0)
		{
			thisdev = mounted->mnt_fsname;
			if (current_length == local_length //they'll both be 1
				|| current_length == local_length - 1)	//same except for trailer
			{
				//we can stop immediately if this type matches
				g_free (bestdev);
				bestdev = g_strdup (thisdev);
				break;
			}
			else if (current_length > longest_sofar)
			{
				longest_sofar = current_length;
				g_free (bestdev);
				bestdev = g_strdup (thisdev);
			}
			else if (current_length == longest_sofar)
			{
				//FIXME decide which one is better to keep
			}
		}
	}
	endmntent (f);
#elif defined(__E2BSD__)
	struct statfs *mounted;

	clean = e2_utils_unquote_string (utfpath);
	if (clean == NULL)
		return NULL;
	local = F_FILENAME_TO_LOCALE (clean);
	local_length = strlen (local);	//length includes trailing /
	longest_sofar = 0;
	bestdev = NULL;

	//NOTE: data provided by this is not process- or thread-safe
	gint i, count = getmntinfo (&mounted, MNT_NOWAIT);

	for (i=0; i<count; i++)
	{
		current_length = strlen (mounted[i].f_mntonname);
		if (strncmp (local, mounted[i].f_mntonname, current_length) == 0)
		{
			thisdev = mounted[i].f_mntfromname;
			if (current_length == local_length //they'll both be 1
				|| current_length == local_length - 1)	//same except for trailer
			{
				//we can stop immediately if this type matches
				g_free (bestdev);
				bestdev = g_strdup (thisdev);
				break;
			}
			else if (current_length > longest_sofar)
			{
				longest_sofar = current_length;
				g_free (bestdev);
				bestdev = g_strdup (thisdev);
			}
			else if (current_length == longest_sofar)
			{
				//FIXME decide which one is better to keep
			}
		}
	}
#elif defined(__solaris__) || defined(sco)
	//CHECKME sco?
	struct mnttab mounted;
	E2_FILE *f;
	gint result;

	if ((f = e2_fs_open_stream ("/etc/mnttab", "r")) == NULL)
		return NULL;

	clean = e2_utils_unquote_string (utfpath);
	if (clean == NULL)
		return NULL;
	local = F_FILENAME_TO_LOCALE (clean);
	local_length = strlen (local);	//length includes trailing /
	longest_sofar = 0;
	bestdev = NULL;

	while ((result = getmntent (f, &mounted)) == 0)
	{
		current_length = strlen (mounted.mnt_mountp);
		if (strncmp (local, mounted.mnt_mountp, current_length) == 0)
		{
			thisdev = mounted.mnt_special;
			if (current_length == local_length //they'll both be 1
				|| current_length == local_length - 1)	//same except for trailer
			{
				//we can stop immediately if this type matches
				g_free (bestdev);
				bestdev = g_strdup (thisdev);
				break;
			}
			else if (current_length > longest_sofar)
			{
				longest_sofar = current_length;
				g_free (bestdev);
				bestdev = g_strdup (thisdev);
			}
			else if (current_length == longest_sofar)
			{
				//FIXME decide which one is better to keep
			}
		}
	}
	e2_fs_close_stream (f);
#elif defined(hpux)
	struct mntent *mounted;
	E2_FILE *f;

	const gchar *mtab = (e2_fs_access ("/etc/pfs_mtab", F_OK E2_ERR_PTR())) ?
		"/etc/mtab" : "/etc/pfs_mtab";
	if ((f = setmntent (mtab, "r")) == NULL)
		return NULL;

	clean = e2_utils_unquote_string (utfpath);
	if (clean == NULL)
		return NULL;
	local = F_FILENAME_TO_LOCALE (clean);
	local_length = strlen (local);	//length includes trailing /
	longest_sofar = 0;
	bestdev = NULL;

	while ((mounted = getmntent (f)) != NULL)
	{
		current_length = strlen (mounted->mnt_dir);
		if (strncmp (local, mounted->mnt_dir, current_length) == 0)
		{
			thisdev = mounted->mnt_fsname;
			if (current_length == local_length //they'll both be 1
				|| current_length == local_length - 1)	//same except for trailer
			{
				//we can stop immediately if this type matches
				g_free (bestdev);
				bestdev = g_strdup (thisdev);
				break;
			}
			else if (current_length > longest_sofar)
			{
				longest_sofar = current_length;
				g_free (bestdev);
				bestdev = g_strdup (thisdev);
			}
			else if (current_length == longest_sofar)
			{
				//FIXME decide which one is better to keep
			}
		}
	}
	endmntent (f);
#elif defined(__svr4__)
	struct mnttab mounted;
	E2_FILE *f;
	gint result;

	if ((f = e2_fs_open_stream ("/etc/mtab", "r") == NULL)
		return NULL;

	clean = e2_utils_unquote_string (utfpath);
	if (clean == NULL)
		return NULL;
	local = F_FILENAME_TO_LOCALE (clean);
	local_length = strlen (local);	//length includes trailing /
	longest_sofar = 0;
	bestdev = NULL;

	while ((result = getmntent (f, &mounted)) == 0)
	{
		current_length = strlen (mounted.f_mntonname);
		if (strncmp (local, mounted.f_mntonname, current_length) == 0)
		{
			thisdev = mounted.f_mntfromname;
			if (current_length == local_length //they'll both be 1
				|| current_length == local_length - 1)	//same except for trailer
			{
				//we can stop immediately if this type matches
				g_free (bestdev);
				bestdev = g_strdup (thisdev);
				break;
			}
			else if (current_length > longest_sofar)
			{
				longest_sofar = current_length;
				g_free (bestdev);
				bestdev = g_strdup (thisdev);
			}
			else if (current_length == longest_sofar)
			{
				//FIXME decide which one is better to keep
			}
		}
	}
	e2_fs_close_stream (f);
#else
//other unix-like os's do it like linux, but without /proc/mount
//CHECKME sometimes with a different filename ??
	struct mntent *mounted;
	E2_FILE *f;

	if ((f = setmntent ("/etc/mtab", "r")) == NULL)
		return NULL;

	clean = e2_utils_unquote_string (utfpath);
	if (clean == NULL)
		return NULL;
	local = F_FILENAME_TO_LOCALE (clean);
	local_length = strlen (local);	//length includes trailing /
	longest_sofar = 0;
	bestdev = NULL;

	while ((mounted = getmntent (f)) != NULL)
	{
		current_length = strlen (mounted->mnt_dir);
		if (strncmp (local, mounted->mnt_dir, current_length) == 0)
		{
			thisdev = mounted->mnt_fsname;
			if (current_length == local_length //they'll both be 1
				|| current_length == local_length - 1)	//same except for trailer
			{
				//we can stop immediately if this type matches
				g_free (bestdev);
				bestdev = g_strdup (thisdev);
				break;
			}
			else if (current_length > longest_sofar)
			{
				longest_sofar = current_length;
				g_free (bestdev);
				bestdev = g_strdup (thisdev);
			}
			else if (current_length == longest_sofar)
			{
				//FIXME decide which one is better to keep
			}
		}
	}
	endmntent (f);
#endif	//which OS
//=========================
	g_free (clean);
	F_FREE (local, clean);
	return bestdev;
}
#endif	//def (E2_HAL) || def (E2_DEVKIT)
/**
@brief create list of mounted partitions (other than "/") in the native filesystem

The list is created by the appropriate os-specifc protocol.
Root dir is exlcluded because we don't need to ever unmount that.
@return list of mounted partitions (UTF-8 strings), or NULL
*/
GList *e2_fs_mount_get_mounts_list (void)	//gboolean permcheck)
{
	GList *mounts_list = NULL;
	//FIXME lock the data source while accessing it
#if defined(__linux__)
	struct mntent *mounted;
	E2_FILE *f;
/* When the linux proc filesystem is mounted, the files /etc/mtab and /proc/mounts
	have very similar contents. The former has somewhat more information (CHECKME),
	such as the mount options used, but is not necessarily up-to-date */
	if ((f = setmntent ("/proc/mounts", "r")) == NULL)
		if ((f = setmntent ("/etc/mtab", "r")) == NULL)
			return NULL;

	while ((mounted = getmntent (f)) != NULL)
	{
		//check the mounted device meets our needs
		//need to take a copy if string is altered
		gchar *dev = mounted->mnt_fsname;
		gchar *dir = mounted->mnt_dir;
		gchar *type = mounted->mnt_type;
		E2_TEST_MOUNTED_DEVICE
		{
			E2_PROCESS_MOUNTED_DEVICE
			_e2_fs_mount_add_to_list (dir, &mounts_list);
		}
	}
	endmntent (f);
#elif defined(__E2BSD__)
	struct statfs *mounted;

	//NOTE: data provided by this is not process- or thread-safe
	gint i, count = getmntinfo (&mounted, MNT_NOWAIT);

	for (i=0; i<count; i++)
	{
		//check the mounted device meets our needs
		//need to take a copy if string is altered
		gchar *dev = mounted[i].f_mntfromname;
		gchar *dir = mounted[i].f_mntonname;
		gchar *type = mounted[i].f_fstypename;
		E2_TEST_MOUNTED_DEVICE
		{
			E2_PROCESS_MOUNTED_DEVICE
			_e2_fs_mount_add_to_list (dir, &mounts_list);
		}
	}
#elif defined(__solaris__) || defined(sco)
	//CHECKME sco?
	struct mnttab mounted;
	E2_FILE *f;

	if ((f = e2_fs_open_stream ("/etc/mnttab", "r")) == NULL)
		return NULL;

	while (getmntent (f, &mounted) == 0)
	{
		//check the mounted device meets our needs
		//need to take a copy if string is altered
		gchar *dev = mounted.mnt_special;
		gchar *dir = mounted.mnt_mountp;
//		gchar *type = mounted.mnt_fstype;
		E2_TEST_MOUNTED_DEVICE
		{
			E2_PROCESS_MOUNTED_DEVICE
			_e2_fs_mount_add_to_list (dir, &mounts_list);
		}
	}
	e2_fs_close_stream (f);
#elif defined(hpux)
	struct mntent *mounted;
	E2_FILE *f;

	const gchar *mtab = (e2_fs_access ("/etc/pfs_mtab", F_OK E2_ERR_PTR())) ?
		"/etc/mtab" : "/etc/pfs_mtab";
	if ((f = setmntent (mtab, "r")) == NULL)
		return NULL;

	while ((mounted = getmntent (f)) != NULL)
	{
		//check the mounted device meets our needs
		//need to take a copy if string is altered
		gchar *dev = mounted->mnt_fsname;
		gchar *dir = mounted->mnt_dir;
//		gchar *type = mounted->mnt_type;
		E2_TEST_MOUNTED_DEVICE
		{
			E2_PROCESS_MOUNTED_DEVICE
			_e2_fs_mount_add_to_list (dir, &mounts_list);
		}
	}
	endmntent (f);
#elif defined(__svr4__)
	struct mnttab mounted;
	E2_FILE *f;

	if ((f = e2_fs_open_stream ("/etc/mtab", "r") == NULL)
		return NULL;

	while (getmntent (f, &mounted) ==0)
	{
		gchar *dev = mounted.f_mntfromname;
		gchar *dir = mounted.f_mntonname;
//		gchar *type = mounted.f_fstypename;
		E2_TEST_MOUNTED_DEVICE
		{
			E2_PROCESS_MOUNTED_DEVICE
			_e2_fs_mount_add_to_list (dir, &mounts_list);
		}
	}
	e2_fs_close_stream (f);
#else
//other unix-like os's do it like linux, but without /proc/mount
//CHECKME sometimes with a different filename ??
	struct mntent *mounted;
	E2_FILE *f;

	if ((f = setmntent ("/etc/mtab", "r")) == NULL)
		return NULL;

	while ((mounted = getmntent (f)) != NULL)
	{
		//check the mounted device meets our needs
		//need to take a copy if string is altered
		gchar *dev = mounted->mnt_fsname;
		gchar *dir = mounted->mnt_dir;
//		gchar *type = mounted->mnt_type;
		E2_TEST_MOUNTED_DEVICE
		{
			E2_PROCESS_MOUNTED_DEVICE
			_e2_fs_mount_add_to_list (dir, &mounts_list);
		}
	}
	endmntent (f);
#endif	//which OS
	return mounts_list;
}
/**
@brief create list of mountable partitions (other than "/") in the native filesystem

The list is created by the appropriate os-specifc protocol.
Root dir is exlcluded because we don't need to ever unmount that.

@return list of partitions (utf-8 strings), or NULL
*/
GList *e2_fs_mount_get_mountable_list (void)	//gboolean permcheck)
{
	GList *fstab_list = NULL;
	//CHECKME lock data file while accessing it ??
#if defined(__E2BSD__) || defined(__linux__)
    struct fstab *fs;

	if (!setfsent ())
		return NULL;

	while ((fs = getfsent ()) != NULL)
	{
		gchar *dev = fs->fs_spec;
		gchar *dir = fs->fs_file;
		gchar *type = fs->fs_vfstype;
/*		copy strings if they need to be modified
		g_strlcpy (dev, fs->fs_spec, sizeof(dev));
		g_strlcpy (dir, fs->fs_file, sizeof(dir));
		g_strlcpy (type, fs->fs_vfstype, sizeof(type));
		_e2_complete_mount_fix_fstab_name (dev);
		_e2_complete_mount_fix_fstab_name (dir);
		_e2_complete_mount_fix_fstab_name (type); */
		E2_TEST_UNMOUNTED_DEVICE
		{
			E2_PROCESS_UNMOUNTED_DEVICE
			_e2_fs_mount_add_to_list (dir, &fstab_list);
		}
    }
    endfsent ();
#elif defined(__solaris__) || defined(sco)
	//CHECKME sco
    struct vfstab fs;
	E2_FILE *f;

	if ((f = e2_fs_open_stream ("/etc/vfstab", "r")) == NULL)
		return NULL;

	while (getvfsent (f, &fs) == 0)
	{
		gchar *dev = fs.vfs_special;
		gchar *dir = fs.vfs_mountp;
//		gchar *type = fs.vfs_fstype;
		E2_TEST_UNMOUNTED_DEVICE
		{
			E2_PROCESS_UNMOUNTED_DEVICE
			_e2_fs_mount_add_to_list (dir, &fstab_list);
		}
	}
	e2_fs_close_stream (f);
#elif defined(hpux)
    struct mntent *mountable;
	E2_FILE *f;

    //handle PFS if necessary
    const gchar *fstab = (access("/etc/pfs_fstab", F_OK)) ?
		"/etc/fstab" : "/etc/pfs_fstab";

	if ((f = setmntent (fstab, "r")) == NULL)
		return NULL;

	while ((mountable = getmntent (f)) != NULL)
	{
		gchar *dev = mountable->mnt_fsname;
		gchar *type = mountable->mnt_type;
		E2_TEST_UNMOUNTED_DEVICE
		{
			E2_PROCESS_UNMOUNTED_DEVICE
			_e2_fs_mount_add_to_list (dir, &fstab_list);
		}
	}
	endmntent (f);
#elif defined(__svr4__)
	struct mnttab mountable;
	E2_FILE *f;

	if ((f = e2_fs_open_stream ("/etc/fstab", "r")) == NULL)
		return NULL;

	while (getmntent (f, &mountable) == 0)
	{
		gchar *dev = mountable.mnt_special;
		gchar *dir = mountable.mnt_mountp;
		E2_TEST_UNMOUNTED_DEVICE
		{
			E2_PROCESS_UNMOUNTED_DEVICE
			_e2_fs_mount_add_to_list (dir, &fstab_list);
		}
	}
	e2_fs_close_stream (f);
#else
	struct mntent *mountable;
	E2_FILE *f;

	if ((f = setmntent ("/etc/fstab", "r" )) == NULL)
		return NULL;

	while ((mountable = getmntent (f)) != NULL)	//data is not thread- or process-safe
	{
		gchar *dev = mountable->mnt_fsname;
		gchar *dir = mountable->mnt_dir;
//		gchar *type = mountable->mnt_type;
//		gchar *opts = mountable->mnt_opts;
		E2_TEST_UNMOUNTED_DEVICE
		{
			E2_PROCESS_UNMOUNTED_DEVICE
			_e2_fs_mount_add_to_list (dir, &fstab_list);
		}
	}
	endmntent (f);
#endif	//which OS
	return fstab_list;
}
/**
@brief find the top dir of the device where @a localpath is
This is intended only for native dirs, so no VPATH involved
It analyses mounted-devices data, so is only meaningful when @a localpath is mounted
@param localpath absolute localised path of an item in local filesystem, may have trailing separator
@return newly allocated localised path, defaults to "/"
*/
gchar *e2_fs_mount_get_enclosing_point (const gchar *localpath)
{
	GList *mounts = e2_fs_mount_get_mounts_list ();	//list of UTF-8 paths other than /
	if (mounts != NULL)
	{
		GList *member;
		guint tlen = 0;
		const gchar *thispoint = NULL;
		gchar *converted = F_FILENAME_FROM_LOCALE (localpath);
		//assume that the longest matching mountpoint path is the one we want
		for (member = mounts; member != NULL; member = member->next)
		{
			gchar *s = (gchar *)member->data;
			if (g_str_has_prefix (converted, s))
			{
				guint len = 0;	//warning prevention
				if (thispoint == NULL || tlen <= (len = strlen (s)))
				{
					thispoint = s;
					tlen = len;
				}
			}
		}
		F_FREE (converted, localpath);
		if (thispoint != NULL)
			converted = D_FILENAME_TO_LOCALE (thispoint);
		else
			converted = g_strdup (G_DIR_SEPARATOR_S);	//NULL;
		e2_list_free_with_data (&mounts);
		return converted;
	}
	return g_strdup (G_DIR_SEPARATOR_S);	//NULL;
}
#if defined (E2_HAL) || defined (E2_DEVKIT)
/**
@brief check whether device on which @a utfpath resides is removable
This is intended only for native dirs, so no VPATH involved
It analyses mounted-devices data, so can only be used when @a utfpath is mounted
@param utfpath absolute path string, utf8, may be quoted, and/or have spaces
and/or trailing separator
@return TRUE if the dir is on a removable device
*/
gboolean e2_fs_mount_is_ejectable (const gchar *utfpath)
{
	gboolean retval;
	gchar *device = _e2_fs_get_mounted_device (utfpath);
	if (device != NULL)
	{
		retval =
# ifdef E2_HAL
		e2_hal_device_is_ejectable (device);
# else
		e2_devkit_device_is_ejectable (device);
# endif
		g_free (device);
	}
	else
		retval = FALSE;
	return retval;
}
#endif
/**
@brief check whether device on which @a utfpath resides is removable
@param utfpath absolute path string, utf8, may be quoted, and/or have spaces
and/or trailing separator
This is intended only for native dirs, so no VPATH involved
It analyses mounted-devices data, so can only be used when @a utfpath is mounted
@return TRUE if the dir is on a removable device
*/
gboolean e2_fs_mount_is_removable (const gchar *utfpath)
{
	gboolean retval;
#if defined (E2_HAL) || defined (E2_DEVKIT)
	gchar *device = _e2_fs_get_mounted_device (utfpath);
	if (device != NULL)
	{
		retval =
# ifdef E2_HAL
		e2_hal_device_is_removable (device);
# else
		e2_devkit_device_is_removable (device);
# endif
		g_free (device);
	}
	else
		retval = FALSE;
#else
	//FIXME
//=========== OS-specific stuff =============
/*#if defined(__linux__)
#elif defined(__E2BSD__)
#elif defined(__solaris__) || defined(sco)
#elif defined(hpux)
#elif defined(__svr4__)
#else
#endif	//which OS
*/
//=========================
	retval = FALSE;	//default
#endif
	return retval;
}
/**
@brief check whether the filesystem associated with @a view uses case-sensitive paths/names

MS-DOS/Windows FAT* are case-insensitive, but so-called "long" names are
case-preserving.
NTFS can be fully case sensitive or just case preserving.
Classic Mac OS was case-insensitive but case-preserving. HFS+ on Mac OS X is
probably the same. (its tag is "hfsplus") Mac HFSX is fully case senstive.
Samba servers usually exhibit case-insensitive behaviour, regardless of the
actual fs properties.
For our purposes here, probably best to treat treat -preserving as -sensitive.

@param view pointer to view data struct

@return TRUE if paths/names are case-sensitive (usually the case)
*/
gboolean e2_fs_mount_is_cased (ViewInfo *view)
{
	if (view->case_sensitive_names == E2FSCASE_UNKNOWN)
	{
#ifdef E2_VFSTMP
		if (view->case_sensitive_names == E2FSCASE_VIRTUAL)
		{
			figure it out
		}
		else
		{
#endif
			gchar *local = D_FILENAME_TO_LOCALE (view->dir); //always dup, to avoid dirchange race
//=========== OS-specific stuff =============
#if defined(__linux__)
#include <sys/vfs.h>
//#include <linux/magic.h> only in glibc-devel
			struct statfs buf;
retry:
			if (statfs (local, &buf))
			{
				if (errno == EINTR)
					goto retry;
				view->case_sensitive_names = E2FSCASE_NEVER;
			}
			else
			{
				switch (buf.f_type)
				{
					case MSDOS_SUPER_MAGIC:
					case SMB_SUPER_MAGIC:
						view->case_sensitive_names = E2FSCASE_ANY;
						break;
					default:
						view->case_sensitive_names = E2FSCASE_SENSITIVE;
						break;
				}
			}
#if defined (E2_HAL) || defined (E2_DEVKIT)
			if (e2_fs_mount_is_removable (view->dir)) //removable device
#endif
				view->case_sensitive_names |= E2FSCASE_NOCACHE;
#elif defined(__E2BSD__)
#include <sys/param.h>
#include <sys/mount.h>
			struct statfs buf;

			if (statfs (local, &buf))
			{
				view->case_sensitive_names = E2FSCASE_NEVER;
			}
			else
			{
				gchar *type = buf.f_fstypename;
				if (strcmp (type, "vfat") == 0)
					view->case_sensitive_names = E2FSCASE_ANY;
				else
					view->case_sensitive_names = E2FSCASE_SENSITIVE;
			}
#if defined (E2_HAL) || defined (E2_DEVKIT)
			if (e2_fs_mount_is_removable (view->dir)) //on removable device
#endif
				view->case_sensitive_names |= E2FSCASE_NOCACHE;
#elif defined(__solaris__) || defined(sco) || defined(hpux) || defined(__svr4__)
#include <sys/types.h>
#include <sys/statvfs.h>
			struct statvfs buf;

			if (statvfs (local, &buf))
			{
				view->case_sensitive_names = E2FSCASE_NEVER;
			}
			else
			{
				gchar *type = buf.f_basetype;
				if (strcmp (type, "vfat") == 0)
					view->case_sensitive_names = E2FSCASE_ANY;
				else
					view->case_sensitive_names = E2FSCASE_SENSITIVE;
			}
#if defined (E2_HAL) || defined (E2_DEVKIT)
			if (e2_fs_mount_is_removable (view->dir)) //on removable device
#endif
				view->case_sensitive_names |= E2FSCASE_NOCACHE;
#else
#include <sys/param.h>
#include <sys/mount.h>
			struct statfs buf;

			if (statfs (local, &buf))
			{
				view->case_sensitive_names = E2FSCASE_NEVER;
			}
			else
			{
				gchar *type = buf.f_fstypename;
				if (strcmp (type, "vfat") == 0)
					view->case_sensitive_names = E2FSCASE_ANY;
				else
					view->case_sensitive_names = E2FSCASE_SENSITIVE;
			}
#if defined (E2_HAL) || defined (E2_DEVKIT)
			if (e2_fs_mount_is_removable (view->dir)) //on removable place
#endif
				view->case_sensitive_names |= E2FSCASE_NOCACHE;
#endif	//which OS
//=========================
			g_free (local);
#ifdef E2_VFSTMP
		}
#endif
	}
	return (view->case_sensitive_names == E2FSCASE_SENSITIVE ||
			view->case_sensitive_names == E2FSCASE_SENSITIVENOW);
}

/**
@brief register mountpoint-related actions
@return
*/
void e2_fs_mount_actions_register (void)
{
	E2_Action action =
	{g_strconcat(_A(61),".",_A(27),NULL),e2_menu_create_mounts_menu,TRUE,E2_ACTION_TYPE_ITEM,E2_ACTION_EXCLUDE_MENU,NULL,NULL};
	e2_action_register (&action);
}
#endif //def E2_FS_MOUNTABLE
