/***************************************************************************
 * CVSID: $Id$
 *
 * hal-storage-mount.c : Mount wrapper
 *
 * Copyright (C) 2006 David Zeuthen, <david@fubar.dk>
 *
 * This program 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 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 **************************************************************************/


#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <glib.h>
#include <glib/gstdio.h>
#ifdef __FreeBSD__
#include <fstab.h>
#include <sys/param.h>
#include <sys/ucred.h>
#include <sys/mount.h>
#include <limits.h>
#include <pwd.h>
#elif sun
#include <sys/mnttab.h>
#include <sys/vfstab.h>
#else
#include <mntent.h>
#endif
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <syslog.h>

#include <libhal.h>
#include <libhal-storage.h>

#include "hal-storage-shared.h"

#ifdef __FreeBSD__
#define MOUNT		"/sbin/mount"
#define MOUNT_OPTIONS	"noexec,nosuid"
#define MOUNT_TYPE_OPT	"-t"
#elif sun
#define MOUNT		"/sbin/mount"
#define MOUNT_OPTIONS	"noexec,nosuid"
#define MOUNT_TYPE_OPT	"-F"
#else
#define MOUNT		"/bin/mount"
#define MOUNT_OPTIONS	"nosuid,nodev"
#define MOUNT_TYPE_OPT	"-t"
#endif

static void
usage (void)
{
	fprintf (stderr, "This program should only be started by hald.\n");
	exit (1);
}

static void
permission_denied_volume_ignore (const char *device)
{
	fprintf (stderr, "org.freedesktop.Hal.Device.Volume.PermissionDenied\n");
	fprintf (stderr, "Device has %s volume.ignore set to TRUE. Refusing to mount.\n", device);
	exit (1);
}

static void
permission_denied_etc_fstab (const char *device)
{
	fprintf (stderr, "org.freedesktop.Hal.Device.Volume.PermissionDenied\n");
	fprintf (stderr, "Device %s is listed in /etc/fstab. Refusing to mount.\n", device);
	exit (1);
}

static void
already_mounted (const char *device)
{
	fprintf (stderr, "org.freedesktop.Hal.Device.Volume.AlreadyMounted\n");
	fprintf (stderr, "Device %s is already mounted.\n", device);
	exit (1);
}

static void
invalid_mount_option (const char *option, const char *uid)
{
	fprintf (stderr, "org.freedesktop.Hal.Device.Volume.InvalidMountOption\n");
	fprintf (stderr, "The option '%s' is not allowed for uid=%s\n", option, uid);
	exit (1);
}

static void
unknown_filesystem (const char *filesystem)
{
	fprintf (stderr, "org.freedesktop.Hal.Device.Volume.UnknownFilesystemType\n");
	fprintf (stderr, "Unknown file system '%s'\n", filesystem);
	exit (1);
}

static void
invalid_mount_point (const char *mount_point)
{
	fprintf (stderr, "org.freedesktop.Hal.Device.Volume.InvalidMountpoint\n");
	fprintf (stderr, "The mount point '%s' is invalid\n", mount_point);
	exit (1);
}

static void
mount_point_not_available (const char *mount_point)
{
	fprintf (stderr, "org.freedesktop.Hal.Device.Volume.MountPointNotAvailable\n");
	fprintf (stderr, "The mount point '%s' is already occupied\n", mount_point);
	exit (1);
}


static void
cannot_remount (const char *device)
{
	fprintf (stderr, "org.freedesktop.Hal.Device.Volume.CannotRemount\n");
	fprintf (stderr, "%s not mounted already\n", device);
	exit (1);
}

#ifdef HAVE_POLKIT
static void
permission_denied_action (const char *action, const char *result)
{
	fprintf (stderr, "org.freedesktop.Hal.Device.PermissionDeniedByPolicy\n");
	fprintf (stderr, "%s %s <-- (action, result)\n", action, result);
	exit (1);
}
#endif

/* borrowed from gtk/gtkfilesystemunix.c in GTK+ on 02/23/2006 */
static void
canonicalize_filename (gchar *filename)
{
	gchar *p, *q;
	gboolean last_was_slash = FALSE;
	
	p = filename;
	q = filename;
	
	while (*p)
	{
		if (*p == G_DIR_SEPARATOR)
		{
			if (!last_was_slash)
				*q++ = G_DIR_SEPARATOR;
			
			last_was_slash = TRUE;
		}
		else
		{
			if (last_was_slash && *p == '.')
			{
				if (*(p + 1) == G_DIR_SEPARATOR ||
				    *(p + 1) == '\0')
				{
					if (*(p + 1) == '\0')
						break;
					
					p += 1;
				}
				else if (*(p + 1) == '.' &&
					 (*(p + 2) == G_DIR_SEPARATOR ||
					  *(p + 2) == '\0'))
				{
					if (q > filename + 1)
					{
						q--;
						while (q > filename + 1 &&
						       *(q - 1) != G_DIR_SEPARATOR)
							q--;
					}
					
					if (*(p + 2) == '\0')
						break;
					
					p += 2;
				}
				else
				{
					*q++ = *p;
					last_was_slash = FALSE;
				}
			}
			else
			{
				*q++ = *p;
				last_was_slash = FALSE;
			}
		}
		
		p++;
	}
	
	if (q > filename + 1 && *(q - 1) == G_DIR_SEPARATOR)
		q--;
	
	*q = '\0';
}

static char *
resolve_symlink (const char *file)
{
	GError *error;
	char *dir;
	char *link;
	char *f;
	char *f1;

	f = g_strdup (file);

	while (g_file_test (f, G_FILE_TEST_IS_SYMLINK)) {
		link = g_file_read_link (f, &error);
		if (link == NULL) {
			g_warning ("Cannot resolve symlink %s: %s", f, error->message);
			g_error_free (error);
			g_free (f);
			f = NULL;
			goto out;
		}
		
		dir = g_path_get_dirname (f);
		f1 = g_strdup_printf ("%s/%s", dir, link);
		g_free (dir);
		g_free (link);
		g_free (f);
		f = f1;
	}

out:
	if (f != NULL)
		canonicalize_filename (f);
	return f;
}

static LibHalVolume *
volume_findby (LibHalContext *hal_ctx, const char *property, const char *value)
{
	int i;
	char **hal_udis;
	int num_hal_udis;
	LibHalVolume *result = NULL;
	char *found_udi = NULL;
	DBusError error;

	dbus_error_init (&error);
	if ((hal_udis = libhal_manager_find_device_string_match (hal_ctx, property, 
								 value, &num_hal_udis, &error)) == NULL) {
		if (dbus_error_is_set (&error)) {
			dbus_error_free (&error);
		}
		goto out;
	}
	for (i = 0; i < num_hal_udis; i++) {
		char *udi;
		udi = hal_udis[i];
		if (libhal_device_query_capability (hal_ctx, udi, "volume", &error)) {
			found_udi = strdup (udi);
			break;
		}
	}

	libhal_free_string_array (hal_udis);

	if (found_udi != NULL)
		result = libhal_volume_from_udi (hal_ctx, found_udi);

	free (found_udi);
out:
	return result;
}

static void
bailout_if_in_fstab (LibHalContext *hal_ctx, const char *device, const char *label, const char *uuid)
{
	gpointer handle;
	char *entry;
	char *_mount_point;

	printf (" label '%s'  uuid '%s'\n", label ? label : "" , uuid ? uuid : "");

	/* check if /etc/fstab mentions this device... (with symlinks etc) */
	if (! fstab_open (&handle)) {
		printf ("cannot open /etc/fstab\n");
		unknown_error ("Cannot open /etc/fstab");		
	}
	while ((entry = fstab_next (handle, &_mount_point)) != NULL) {
		char *resolved;

#ifdef DEBUG
		printf ("Looking at /etc/fstab entry '%s'\n", entry);
#endif
		if (label != NULL && g_str_has_prefix (entry, "LABEL=")) {
			if (strcmp (entry + 6, label) == 0) {
				gboolean skip_fstab_entry;

				skip_fstab_entry = FALSE;

				/* (heck, we also do the stuff below in gnome-mount) */

				/* OK, so what's if someone attaches an external disk with the label '/' and
				 * /etc/fstab has
				 *
				 *    LABEL=/    /    ext3    defaults    1 1
				 *
				 * in /etc/fstab as most Red Hat systems do? Bugger, this is a very common use
				 * case; suppose that you take the disk from your Fedora server and attaches it
				 * to your laptop. Bingo, you now have two disks with the label '/'. One must
				 * seriously wonder if using things like LABEL=/ for / is a good idea; just
				 * what happens if you boot in this configuration? (answer: the initrd gets
				 * it wrong most of the time.. sigh)
				 *
				 * To work around this, check if the listed entry in /etc/fstab is already mounted,
				 * if it is, then check if it's the same device_file as the given one...
				 */

				/* see if a volume is mounted at this mount point  */
				if (_mount_point != NULL) {
					LibHalVolume *mounted_vol;

					mounted_vol = volume_findby (hal_ctx, "volume.mount_point", _mount_point);
					if (mounted_vol != NULL) {
						const char *mounted_vol_device_file;

						mounted_vol_device_file = libhal_volume_get_device_file (mounted_vol);
						/* no need to resolve symlinks, hal uses the canonical device file */
						if (mounted_vol_device_file != NULL &&
						    strcmp (mounted_vol_device_file, device) !=0) {
#ifdef DEBUG
							printf ("Wanting to mount %s that has label %s, but /etc/fstab says LABEL=%s is to be mounted at mount point '%s'. However %s (that also has label %s), is already mounted at said mount point. So, skipping said /etc/fstab entry.\n", 
								   device, label, label, _mount_point, mounted_vol_device_file, _mount_point);
#endif
							skip_fstab_entry = TRUE;
						}
						libhal_volume_free (mounted_vol);
					}
				}
				
				if (!skip_fstab_entry) {
					printf ("%s found in /etc/fstab. Not mounting.\n", entry);
					permission_denied_etc_fstab (device);
				}
			}
		} else if (uuid != NULL && g_str_has_prefix (entry, "UUID=")) {
			if (strcmp (entry + 5, uuid) == 0) {
				printf ("%s found in /etc/fstab. Not mounting.\n", entry);
				permission_denied_etc_fstab (device);
			}
		} else {

			resolved = resolve_symlink (entry);
#ifdef DEBUG
			printf ("/etc/fstab: device %s -> %s \n", entry, resolved);
#endif
			if (strcmp (device, resolved) == 0) {
				printf ("%s (-> %s) found in /etc/fstab. Not mounting.\n", entry, resolved);
				permission_denied_etc_fstab (device);
			}

			g_free (resolved);
		}
	}
	fstab_close (handle);
}

static gboolean
device_is_mounted (const char *device, char **mount_point)
{
	gpointer handle;
	char *entry;
	gboolean ret;

	ret = FALSE;

	/* check if /proc/mounts mentions this device... (with symlinks etc) */
	if (! mtab_open (&handle)) {
		printf ("cannot open mount list\n");
		unknown_error ("Cannot open /etc/mtab or equivalent");		
	}
	while (((entry = mtab_next (handle, mount_point)) != NULL) && (ret == FALSE)) {
		char *resolved;

		resolved = resolve_symlink (entry);
#ifdef DEBUG
		printf ("/proc/mounts: device %s -> %s \n", entry, resolved);
#endif
		if (strcmp (device, resolved) == 0) {
			printf ("%s (-> %s) found in mount list. Not mounting.\n", entry, resolved);
			ret = TRUE;
		}

		g_free (resolved);
	}
	mtab_close (handle);
	return ret;
}

/* maps volume_id fs types to the appropriate -t mount option */
static const char *
map_fstype (const char *fstype)
{
#ifdef __FreeBSD__
	if (! strcmp (fstype, "iso9660"))
		return "cd9660";
	else if (! strcmp (fstype, "ext2"))
		return "ext2fs";
	else if (! strcmp (fstype, "ext3"))
		return "ext2fs";
	else if (! strcmp (fstype, "vfat"))
		return "msdosfs";
#elif sun
	if (! strcmp (fstype, "iso9660"))
		return "hsfs";
	else if (! strcmp (fstype, "vfat"))
		return "pcfs";
#endif

	return fstype;
}

static void
handle_mount (LibHalContext *hal_ctx, 
	      const char *udi,
	      LibHalVolume *volume, LibHalDrive *drive, const char *device, 
	      const char *invoked_by_uid, const char *invoked_by_syscon_name)
{
	int i, j;
	DBusError error;
	char mount_point[256];
	char mount_fstype[256];
	char mount_options[1024];
	char **allowed_options;
	char **given_options;
	gboolean wants_to_change_uid;
	char *mount_dir;
	GError *err = NULL;
	char *sout = NULL;
	char *serr = NULL;
	char *mount_option_commasep = NULL;
	char *mount_do_fstype = NULL;
	int exit_status;
	char *args[10];
	int na;
	GString *mount_option_str;
	gboolean pol_is_fixed;
	gboolean pol_change_uid;
	char *action;
	gboolean is_remount;
	gboolean explicit_mount_point_given;
	gboolean found_alternative_fstype = FALSE;
	const char *end;
#ifdef __FreeBSD__
	struct passwd *pw;
	uid_t calling_uid;
	gid_t calling_gid;
#endif
	const char *label;
	const char *uuid;

#ifdef DEBUG
	printf ("device                           = %s\n", device);
	printf ("invoked by uid                   = %s\n", invoked_by_uid);
	printf ("invoked by system bus connection = %s\n", invoked_by_syscon_name);
#endif

	if (volume != NULL) {		
		dbus_error_init (&error);
		if (libhal_device_get_property_bool (hal_ctx, udi, "volume.ignore", &error) || 
		    dbus_error_is_set (&error)) {
			if (dbus_error_is_set (&error)) {
				dbus_error_free (&error);
			}
			permission_denied_volume_ignore (device);
		}

		label = libhal_volume_get_label (volume);
		uuid = libhal_volume_get_uuid (volume);
	} else {
		label = NULL;
		uuid = NULL;
	}

	bailout_if_in_fstab (hal_ctx, device, label, uuid);

        bailout_if_drive_is_locked (hal_ctx, drive, invoked_by_syscon_name);

	/* TODO: sanity check that what hal exports is correct (cf. Martin Pitt's email) */

	/* read from stdin */
	if (strlen (fgets (mount_point, sizeof (mount_point), stdin)) > 0)
		mount_point   [strlen (mount_point)   - 1] = '\0';
	if (strlen (fgets (mount_fstype, sizeof (mount_fstype), stdin)) > 0)
		mount_fstype  [strlen (mount_fstype)  - 1] = '\0';
	if (strlen (fgets (mount_options, sizeof (mount_options), stdin)) > 0)
		mount_options [strlen (mount_options) - 1] = '\0';
	/* validate that input from stdin is UTF-8 */
	if (!g_utf8_validate (mount_point, -1, &end))
		unknown_error ("Error validating mount_point as UTF-8");
	if (!g_utf8_validate (mount_fstype, -1, &end))
		unknown_error ("Error validating mount_fstype as UTF-8");
	if (!g_utf8_validate (mount_options, -1, &end))
		unknown_error ("Error validating mount_options as UTF-8");

	for (i = 0; mount_point[i] != '\0'; i++) {
		if (mount_point[i] == '\n' ||
		    mount_point[i] == G_DIR_SEPARATOR) {
			unknown_error ("mount_point cannot contain the following characters: newline, G_DIR_SEPARATOR (usually /)");
		}
	}

#ifdef DEBUG
	printf ("mount_point    = '%s'\n", mount_point);
	printf ("mount_fstype   = '%s'\n", mount_fstype);
	printf ("mount_options  = '%s'\n", mount_options);
#endif

	/* delete any trailing whitespace options from splitting the string */
	given_options = g_strsplit (mount_options, "\t", 0);
	for (i = g_strv_length (given_options) - 1; i >= 0; --i) {
		if (strlen (given_options[i]) > 0)
			break;
		given_options[i] = NULL;
	}

	/* is option 'remount' included? */
	is_remount = FALSE;
	for (i = 0; i < (int) g_strv_length (given_options); i++) {
		if (strcmp (given_options[i], "remount") == 0) {
			is_remount = TRUE;
		}
	}

	mount_dir = NULL;
	if (is_remount) {
		if (volume != NULL) {
			if (!libhal_volume_is_mounted (volume)) {
				cannot_remount (device);
			}
			mount_dir = g_strdup (libhal_volume_get_mount_point (volume));
		} else {
			if (!device_is_mounted (device, &mount_dir)) {
				cannot_remount (device);
			}
		}

		if (mount_dir == NULL) {
			unknown_error ("Cannot get mount_dir for remount even though volume is mounted!");
		}

	} else {
		if (volume != NULL) {
			if (libhal_volume_is_mounted (volume)) {
				already_mounted (device);
			}
		} else {
			if (device_is_mounted (device, NULL)) {
				already_mounted (device);
			}
		}
	}

	if (!is_remount) {
		/* figure out mount point if no mount point is given... */
		explicit_mount_point_given = FALSE;
		if (strlen (mount_point) == 0) {
			char *p;
			
			if (label != NULL) {
				/* best - use label */
				g_strlcpy (mount_point, label, sizeof (mount_point));
				
				/* TODO: use drive type */
				
			} else {
				/* fallback - use "disk" */
				g_snprintf (mount_point, sizeof (mount_point), "%s", "disk");
			}
			
			/* sanitize computed mount point name, e.g. replace invalid chars with '-' */
			p = mount_point;
			while (TRUE) {
				p = g_utf8_strchr (mount_point, -1, G_DIR_SEPARATOR);
				if (p == NULL)
					break;
				*p = '-';
			};
			
		} else {
			explicit_mount_point_given = TRUE;
		}

		/* check mount point name - only forbid separators */
		if (g_utf8_strchr (mount_point, -1, G_DIR_SEPARATOR) != NULL) {
			printf ("'%s' is an invalid mount point\n", mount_point);
			invalid_mount_point (mount_point);
		}
		
		/* check if mount point is available - append number to mount point */
		i = 0;
		mount_dir = NULL;
		while (TRUE) {
			g_free (mount_dir);
			if (i == 0)
				mount_dir = g_strdup_printf ("/media/%s", mount_point);
			else
				mount_dir = g_strdup_printf ("/media/%s-%d", mount_point, i);
			
#ifdef DEBUG
			printf ("trying dir %s\n", mount_dir);
#endif
			
			if (!g_file_test (mount_dir, G_FILE_TEST_EXISTS)) {
				break;
			}
			
			if (explicit_mount_point_given) {
				mount_point_not_available (mount_dir);
			}
			
			i++;
		}
	}	

	/* construct arguments to mount */
	na = 0;
	
	args[na++] = MOUNT;

	if (strlen (mount_fstype) > 0) {
		mount_do_fstype = (char *) map_fstype (mount_fstype);
		if (volume && strcmp(mount_do_fstype, mount_fstype) == 0) {
			/* there was nothing mapped and we have a volume */
			const char *_fstype;

			_fstype = libhal_volume_get_fstype (volume);
			/* check if the given fstype differs from the volume.fstype */
			if (_fstype && (strcmp(_fstype, mount_fstype) != 0)) {
				/* the fstype differs from the give fstype mount options, check if it's allowed as alternative */
				char **alternative;

				dbus_error_init (&error);
			        alternative = libhal_device_get_property_strlist (hal_ctx, udi, "volume.fstype.alternative", &error);
			        if (dbus_error_is_set (&error)) {
			                unknown_error ("Cannot get volume.fstype.alternative");
			                dbus_error_free (&error);
        			} else if (alternative != NULL) {
					for (i = 0; alternative[i] != NULL; i++) {
#ifdef DEBUG
						printf ("check volume.fstype.alternative[%d]='%s' for given fstype '%s'\n", i, alternative[i], mount_fstype);
#endif
						if (strcmp(alternative[i], mount_fstype) == 0) {
							printf ("found_alternative_fstype = TRUE\n");
							found_alternative_fstype = TRUE;
							break;
						}
					}
					libhal_free_string_array(alternative);
				}
				if (!found_alternative_fstype) {
					/* We have a given fstype option, which isn't allowed as 
					   as alternative fstype.
					   TODO: add error message and handling or exit/refuse mount
					*/
				}

			}
		}

	} else if (volume == NULL) {
		/* non-pollable drive; force auto */
		mount_do_fstype = "auto";
	} else if (libhal_volume_get_fstype (volume) != NULL && strlen (libhal_volume_get_fstype (volume)) > 0) {
		mount_do_fstype = (char *) map_fstype (libhal_volume_get_fstype (volume));
	} else {
		mount_do_fstype = "auto";
	}

	/* check the mount options */
	dbus_error_init (&error);
	
	if (found_alternative_fstype) {
		char key[128];

		sprintf(key, "volume.mount.%s.valid_options", mount_do_fstype);		
#ifdef DEBUG
		printf ("found alternative fstype='mount_do_fstype' checking now '%s'", key);
#endif
		allowed_options = libhal_device_get_property_strlist (hal_ctx, udi, key, &error);
	} else {
		allowed_options = libhal_device_get_property_strlist (hal_ctx, udi, "volume.mount.valid_options", &error);
	}
	if (dbus_error_is_set (&error)) {
		unknown_error ("Cannot get volume.mount.valid_options");
		dbus_error_free (&error);
	}

#ifdef DEBUG
	for (i = 0; given_options[i] != NULL; i++)
		printf ("given_options[%d] = '%s'\n", i, given_options[i]);
	for (i = 0; allowed_options[i] != NULL; i++)
		printf ("allowed_options[%d] = '%s'\n", i, allowed_options[i]);
#endif

	wants_to_change_uid = FALSE;

	/* check mount options */
	for (i = 0; given_options[i] != NULL; i++) {
		char *given = given_options[i];

		for (j = 0; allowed_options[j] != NULL; j++) {
			char *allow = allowed_options[j];
			int allow_len = strlen (allow);

			if (strcmp (given, allow) == 0) {
				goto option_ok;
			}

			if ((allow[allow_len - 1] == '=') && 
			    (strncmp (given, allow, allow_len) == 0) &&
			    (int) strlen (given) > allow_len) {

				/* option matched allowed ending in '=', e.g.
				 * given == "umask=foobar" and allowed == "umask="
				 */
				if (strcmp (allow, "uid=") == 0) {
					uid_t uid;
					char *endp;
					/* check for uid=, it requires special handling */
					uid = (uid_t) strtol (given + allow_len, &endp, 10);
					if (*endp != '\0') {
						printf ("'%s' is not a number?\n", given);
						unknown_error ("option uid is malformed");
					}
#ifdef DEBUG
					printf ("%s with uid %u\n", allow, uid);
#endif
					wants_to_change_uid = TRUE;

					goto option_ok;
				} else {

					goto option_ok;
				}
			}
		}

		/* apparently option was not ok */
		invalid_mount_option (given, invoked_by_uid);

	option_ok:
		;
	}

	/* Check privilege */
	pol_is_fixed = TRUE;
	if (libhal_drive_is_hotpluggable (drive) || libhal_drive_uses_removable_media (drive))
		pol_is_fixed = FALSE;

	pol_change_uid = FALSE;
	/* don't consider uid= on non-pollable drives for the purpose of policy 
	 * (since these drives normally use vfat)
	 */
	if (volume != NULL) {
		/* don't consider uid= on vfat, iso9660, hfs and udf change-uid for the purpose of policy
		 * (since these doesn't contain uid/gid bits) 
		 */
		if (strcmp (libhal_volume_get_fstype (volume), "vfat") != 0 &&
		    strcmp (libhal_volume_get_fstype (volume), "ntfs") != 0 &&
		    strcmp (libhal_volume_get_fstype (volume), "ntfs-3g") != 0 &&
		    strcmp (libhal_volume_get_fstype (volume), "iso9660") != 0 &&
		    strcmp (libhal_volume_get_fstype (volume), "hfs") != 0 &&
		    strcmp (libhal_volume_get_fstype (volume), "udf") != 0) {
			pol_change_uid = wants_to_change_uid;
		}
	}

	args[na++] = MOUNT_TYPE_OPT;
	args[na++] = mount_do_fstype;

	args[na++] = "-o";
#ifdef HAVE_UMOUNT_HAL
	mount_option_str = g_string_new (MOUNT_OPTIONS ",uhelper=hal");
#else
	mount_option_str = g_string_new (MOUNT_OPTIONS);
#endif
	for (i = 0; given_options[i] != NULL; i++) {
		g_string_append (mount_option_str, ",");
		g_string_append (mount_option_str, given_options[i]);
	}
	mount_option_commasep = g_string_free (mount_option_str, FALSE); /* leak! */
	args[na++] = mount_option_commasep;
	args[na++] = (char *) device;
	args[na++] = mount_dir;
	args[na++] = NULL;

	if (pol_is_fixed) {
		if (pol_change_uid) {
			action = NULL; /* "hal-storage-mount-fixed-extra-options"; TODO: rethink */
		} else {
			action = "org.freedesktop.hal.storage.mount-fixed";
		}
	} else {
		if (pol_change_uid) {
			action = NULL; /* "hal-storage-mount-removable-extra-options"; TODO: rethink "extra-options" */
		} else {
			action = "org.freedesktop.hal.storage.mount-removable";
		}
	}
        if (action == NULL) {
                unknown_error ("TODO: have to rethink extra options");
        }

#ifdef DEBUG
	printf ("using action %s for uid %s, system_bus_connection %s\n", action, invoked_by_uid, 
		invoked_by_syscon_name);
#endif

#ifdef HAVE_POLKIT
        if (invoked_by_syscon_name != NULL) {
                char *polkit_result;

                dbus_error_init (&error);
                polkit_result = libhal_device_is_caller_privileged (hal_ctx,
                                                                    udi,
                                                                    action,
                                                                    invoked_by_syscon_name,
                                                                    &error);
                if (polkit_result == NULL){
                        unknown_error ("IsCallerPrivileged() failed");
                }
                if (strcmp (polkit_result, "yes") != 0) {
                        permission_denied_action (action, polkit_result);
                }
                libhal_free_string (polkit_result);
        }
#endif

#ifdef DEBUG
	printf ("passed privilege\n");
#endif

	if (!is_remount) {
		/* create directory */
		if (g_mkdir (mount_dir, 0700) != 0) {
			printf ("Cannot create '%s'\n", mount_dir);
			unknown_error ("Cannot create mount directory");
		}
		
#ifdef __FreeBSD__
		calling_uid = (uid_t) strtol (invoked_by_uid, (char **) NULL, 10);
		pw = getpwuid (calling_uid);
		if (pw != NULL) {
			calling_gid = pw->pw_gid;
		} else {
			calling_gid = 0;
		}
		if (chown (mount_dir, calling_uid, calling_gid) != 0) {
			printf ("Cannot chown '%s' to uid: %d, gid: %d\n", mount_dir,
				calling_uid, calling_gid);
			g_rmdir (mount_dir);
			unknown_error ("Failed to chown mount directory");
		}
#endif
	}


	/* TODO FIXME XXX HACK: OK, so we should rewrite the options in /media/.hal-mtab .. 
	 *                      but it doesn't really matter much at this point */
	if (!is_remount) {
		FILE *hal_mtab;
		FILE *hal_mtab_orig;
		int hal_mtab_orig_len;
		int num_read;
		char *hal_mtab_buf;
		char *hal_mtab_buf_old;
		
		/* Maintain a list in /media/.hal-mtab with entries of the following format
		 *
		 *  <device_file>\t<uid>\t<session-id>\t<fstype>\t<options_sep_by_comma>\t<mount point>\n
		 *
		 * where session-id currently is unused and thus set to 0.
		 *
		 * Example:
		 *
		 *  /dev/sda2	500	0	hfsplus	noexec,nosuid,nodev	/media/Macintosh HD
		 *  /dev/sda4	500	0	ntfs	noexec,nosuid,nodev,umask=222	/media/Windows
		 *  /dev/sdb1	500	0	vfat	noexec,nosuid,nodev,shortname=winnt,uid=500	/media/davidz
		 */
		
		
		if (g_file_test ("/media/.hal-mtab", G_FILE_TEST_EXISTS)) {
			hal_mtab_orig = fopen ("/media/.hal-mtab", "r");
			if (hal_mtab_orig == NULL) {
				unknown_error ("Cannot open /media/.hal-mtab");
			}
			if (fseek (hal_mtab_orig, 0L, SEEK_END) != 0) {
				unknown_error ("Cannot seek to end of /media/.hal-mtab");
			}
			hal_mtab_orig_len = ftell (hal_mtab_orig);
			if (hal_mtab_orig_len < 0) {
				unknown_error ("Cannot determine size of /media/.hal-mtab");
			}
			rewind (hal_mtab_orig);
			hal_mtab_buf = g_new0 (char, hal_mtab_orig_len + 1);
			num_read = fread (hal_mtab_buf, 1, hal_mtab_orig_len, hal_mtab_orig);
			if (num_read != hal_mtab_orig_len) {
				unknown_error ("Cannot read from /media/.hal-mtab");
			}
			fclose (hal_mtab_orig);
		} else {
			hal_mtab_buf = g_strdup ("");
		}
		
#ifdef DEBUG
		printf ("%d: XYA creating /media/.hal-mtab~\n", getpid ());
#endif
		hal_mtab = fopen ("/media/.hal-mtab~", "w");
		if (hal_mtab == NULL) {
			unknown_error ("Cannot create /media/.hal-mtab~");
		}
		hal_mtab_buf_old = hal_mtab_buf;
		hal_mtab_buf = g_strdup_printf ("%s%s\t%s\t0\t%s\t%s\t%s\n", 
						hal_mtab_buf_old,
						device, invoked_by_uid, mount_do_fstype, 
						mount_option_commasep, mount_dir);
		g_free (hal_mtab_buf_old);
		if (hal_mtab_buf_old == NULL) {
			unknown_error ("Out of memory appending to /media/.hal-mtab~");
		}
		if (fwrite (hal_mtab_buf, 1, strlen (hal_mtab_buf), hal_mtab) != strlen (hal_mtab_buf)) {
			unknown_error ("Cannot write to /media/.hal-mtab~");
		}
		fclose (hal_mtab);
		g_free (hal_mtab_buf);
#ifdef DEBUG
		printf ("%d: XYA closing /media/.hal-mtab~\n", getpid ());
#endif
	} /* !is_remount */
		
	/* now try to mount */
	if (!g_spawn_sync ("/",
			   args,
			   NULL,
			   0,
			   NULL,
			   NULL,
			   &sout,
			   &serr,
			   &exit_status,
			   &err)) {
		printf ("Cannot execute %s\n", MOUNT);
		g_rmdir (mount_dir);
		unlink ("/media/.hal-mtab~");
		unknown_error ("Cannot spawn " MOUNT);
	}


	if (exit_status != 0) {
		char errstr[]  = "mount: unknown filesystem type";

		printf ("%s error %d, stdout='%s', stderr='%s'\n", MOUNT, exit_status, sout, serr);

		if (!is_remount) {
			g_rmdir (mount_dir);
			unlink ("/media/.hal-mtab~");
		}

		if (strncmp (errstr, serr, sizeof (errstr) - 1) == 0) {
			unknown_filesystem (strlen (mount_fstype) > 0 ? 
					    mount_fstype : 
					    (volume != NULL ? libhal_volume_get_fstype (volume) : "") );
		} else {
			int n;
			for (n = 0; serr[n] != '\0'; n++) {
				if (serr[n] == '\n') {
					serr[n] = ' ';
				}
			}
			unknown_error (serr);
		}
	}

	if (!is_remount) {
		if (rename ("/media/.hal-mtab~", "/media/.hal-mtab") != 0) {
			printf ("rename(2) failed, errno=%d -> '%s'\n", errno, strerror (errno));
			unlink ("/media/.hal-mtab~");
#ifdef DEBUG
			printf ("%d: XYA failed renaming /media/.hal-mtab~ to /media/.hal-mtab\n", getpid ());
#endif
			unknown_error ("Cannot rename /media/.hal-mtab~ to /media/.hal-mtab");
		}
#ifdef DEBUG
		printf ("%d: XYA done renaming /media/.hal-mtab~ to /media/.hal-mtab\n", getpid ());
#endif
	}

	openlog ("hald", 0, LOG_DAEMON);
	if (is_remount) {
		syslog (LOG_INFO, "remounted %s at '%s' on behalf of uid %s", device, mount_dir, invoked_by_uid);
	} else {
		syslog (LOG_INFO, "mounted %s on behalf of uid %s", device, invoked_by_uid);
	}
	closelog ();

	g_free (sout);
	g_free (serr);
	g_free (mount_dir);
	libhal_free_string_array (allowed_options);
	g_strfreev (given_options);
}


int
main (int argc, char *argv[])
{
	char *udi;
	char *device;
	LibHalVolume *volume;
	DBusError error;
	LibHalContext *hal_ctx = NULL;
	DBusConnection *system_bus = NULL;
	char *invoked_by_uid;
	char *invoked_by_syscon_name;

	if (!lock_hal_mtab ()) {
		unknown_error ("Cannot obtain lock on /media/.hal-mtab");
	}

	device = getenv ("HAL_PROP_BLOCK_DEVICE");
	if (device == NULL)
		usage ();

	udi = getenv ("HAL_PROP_INFO_UDI");
	if (udi == NULL)
		usage ();

	invoked_by_uid = getenv ("HAL_METHOD_INVOKED_BY_UID");

	invoked_by_syscon_name = getenv ("HAL_METHOD_INVOKED_BY_SYSTEMBUS_CONNECTION_NAME");

	dbus_error_init (&error);
	if ((hal_ctx = libhal_ctx_init_direct (&error)) == NULL) {
		printf ("Cannot connect to hald\n");
		if (dbus_error_is_set (&error)) {
			dbus_error_free (&error);
		}
		usage ();
	}

	dbus_error_init (&error);
	system_bus = dbus_bus_get (DBUS_BUS_SYSTEM, &error);
	if (system_bus == NULL) {
		printf ("Cannot connect to the system bus\n");
		if (dbus_error_is_set (&error)) {
			dbus_error_free (&error);
		}
		usage ();
	}

	volume = libhal_volume_from_udi (hal_ctx, udi);
	if (volume == NULL) {
		LibHalDrive *drive;

		drive = libhal_drive_from_udi (hal_ctx, udi);
		if (drive == NULL) {
			usage ();
		} else {
			handle_mount (hal_ctx, 
				      udi, NULL, drive, device, invoked_by_uid, 
				      invoked_by_syscon_name);
		}

	} else {
		const char *drive_udi;
		LibHalDrive *drive;

		drive_udi = libhal_volume_get_storage_device_udi (volume);
		
		if (drive_udi == NULL)
			unknown_error ("Cannot get drive_udi from volume");
		drive = libhal_drive_from_udi (hal_ctx, drive_udi);
		if (drive == NULL)
			unknown_error ("Cannot get drive from hal");
		
		handle_mount (hal_ctx, 
			      udi, volume, drive, device, invoked_by_uid, 
			      invoked_by_syscon_name);

	}

	unlock_hal_mtab ();

	return 0;
}
