hal-storage-shared.c revision 7b1f4223d0637b428ed4e46b5bc295f7a949e670
/***************************************************************************
* CVSID: $Id: hal-storage-mount.c,v 1.7 2006/06/21 00:44:03 david Exp $
*
* 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 <fcntl.h>
#include <sys/mnttab.h>
#include <sys/vfstab.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <bsm/adt.h>
#include <bsm/adt_event.h>
#else
#include <mntent.h>
#endif
#include <sys/types.h>
#include <unistd.h>
#include <sys/file.h>
#include <errno.h>
#include <syslog.h>
#include "hal-storage-shared.h"
#ifdef __FreeBSD__
struct mtab_handle
{
struct statfs *mounts;
int n_mounts;
int iter;
};
#endif
gboolean
mtab_open (gpointer *handle)
{
#ifdef __FreeBSD__
struct mtab_handle *mtab;
mtab = g_new0 (struct mtab_handle, 1);
mtab->n_mounts = getmntinfo (&mtab->mounts, MNT_NOWAIT);
if (mtab->n_mounts == 0) {
g_free (mtab);
return FALSE;
}
*handle = mtab;
return TRUE;
#elif sun
*handle = fopen (MNTTAB, "r");
return *handle != NULL;
#else
*handle = fopen ("/proc/mounts", "r");
return *handle != NULL;
#endif
}
char *
mtab_next (gpointer handle, char **mount_point)
{
#ifdef __FreeBSD__
struct mtab_handle *mtab = handle;
if (mtab->iter < mtab->n_mounts)
return mtab->mounts[mtab->iter++].f_mntfromname;
else
return NULL;
#error TODO: set *mount_point to g_strdup()-ed value if mount_point!=NULL
#elif sun
static struct mnttab mnt;
if (getmntent (handle, &mnt) == 0) {
if (mount_point != NULL) {
*mount_point = g_strdup (mnt.mnt_mountp);
}
return mnt.mnt_special;
} else {
return NULL;
}
#else
struct mntent *mnt;
mnt = getmntent (handle);
if (mnt != NULL) {
if (mount_point != NULL) {
*mount_point = g_strdup (mnt->mnt_dir);
}
return mnt->mnt_fsname;
} else {
return NULL;
}
#endif
}
void
mtab_close (gpointer handle)
{
#ifdef __FreeBSD__
g_free (handle);
#else
fclose (handle);
#endif
}
gboolean
fstab_open (gpointer *handle)
{
#ifdef __FreeBSD__
return setfsent () == 1;
#elif sun
*handle = fopen (VFSTAB, "r");
return *handle != NULL;
#else
*handle = fopen ("/etc/fstab", "r");
return *handle != NULL;
#endif
}
char *
fstab_next (gpointer handle, char **mount_point)
{
#ifdef __FreeBSD__
struct fstab *fstab;
fstab = getfsent ();
/* TODO: fill out mount_point */
if (mount_point != NULL && fstab != NULL) {
*mount_point = fstab->fs_file;
}
return fstab ? fstab->fs_spec : NULL;
#elif sun
static struct vfstab v;
return getvfsent (handle, &v) == 0 ? v.vfs_special : NULL;
#else
struct mntent *mnt;
mnt = getmntent (handle);
if (mount_point != NULL && mnt != NULL) {
*mount_point = mnt->mnt_dir;
}
return mnt ? mnt->mnt_fsname : NULL;
#endif
}
void
fstab_close (gpointer handle)
{
#ifdef __FreeBSD__
endfsent ();
#else
fclose (handle);
#endif
}
#ifdef __FreeBSD__
#define UMOUNT "/sbin/umount"
#elif sun
#define UMOUNT "/sbin/umount"
#else
#define UMOUNT "/bin/umount"
#endif
void
unknown_error (const char *detail)
{
fprintf (stderr, "org.freedesktop.Hal.Device.Volume.UnknownFailure\n");
fprintf (stderr, "%s\n", detail);
exit (1);
}
static void
device_busy (const char *detail)
{
fprintf (stderr, "org.freedesktop.Hal.Device.Volume.Busy\n");
fprintf (stderr, "%s\n", detail);
exit (1);
}
static void
not_mounted (const char *detail)
{
fprintf (stderr, "org.freedesktop.Hal.Device.Volume.NotMounted\n");
fprintf (stderr, "%s\n", detail);
exit (1);
}
static void
not_mounted_by_hal (const char *detail)
{
fprintf (stderr, "org.freedesktop.Hal.Device.Volume.NotMountedByHal\n");
fprintf (stderr, "%s\n", detail);
exit (1);
}
static void
permission_denied_privilege (const char *privilege, const char *uid)
{
fprintf (stderr, "org.freedesktop.Hal.Device.PermissionDeniedByPolicy\n");
fprintf (stderr, "%s refused uid %s\n", privilege, uid);
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);
}
void
handle_unmount (LibHalContext *hal_ctx,
#ifdef HAVE_POLKIT
LibPolKitContext *pol_ctx,
#endif
const char *udi,
LibHalVolume *volume, LibHalDrive *drive, const char *device,
const char *invoked_by_uid, const char *invoked_by_syscon_name,
gboolean option_lazy, gboolean option_force,
DBusConnection *system_bus)
{
int i, j;
DBusError error;
GError *err = NULL;
char *sout = NULL;
char *serr = NULL;
int exit_status;
char *args[10];
int na;
FILE *hal_mtab_orig;
int hal_mtab_orig_len;
int num_read;
char *hal_mtab_buf;
char **lines;
char *mount_point_to_unmount;
gboolean mounted_by_other_uid;
FILE *hal_mtab_new;
#ifdef sun
adt_export_data_t *adt_data;
size_t adt_data_size;
#endif
#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)) {
LIBHAL_FREE_DBUS_ERROR (&error);
}
/*
* When device allocation is enabled (bsmconv or TX), we
* set volume.ignore on all volumes, but still want
* Mount() to succeed when called from the euid=0
* device allocation program.
*/
if (atol (invoked_by_uid) != 0) {
permission_denied_volume_ignore (device);
}
}
if (!libhal_volume_is_mounted (volume)) {
not_mounted ("According to HAL, the volume is not mounted");
}
}
/* check hal's mtab file to verify the device to unmount is actually mounted by hal */
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);
#ifdef DEBUG
printf ("hal_mtab = '%s'\n", hal_mtab_buf);
#endif
lines = g_strsplit (hal_mtab_buf, "\n", 0);
g_free (hal_mtab_buf);
mount_point_to_unmount = NULL;
mounted_by_other_uid = TRUE;
/* find the entry we're going to unmount */
for (i = 0; lines[i] != NULL; i++) {
char **line_elements;
char *special, *dosp;
struct stat st;
#ifdef DEBUG
printf (" line = '%s'\n", lines[i]);
#endif
if ((lines[i])[0] == '#')
continue;
line_elements = g_strsplit (lines[i], "\t", 6);
if (g_strv_length (line_elements) == 6) {
#ifdef DEBUG
printf (" devfile = '%s'\n", line_elements[0]);
printf (" uid = '%s'\n", line_elements[1]);
printf (" session id = '%s'\n", line_elements[2]);
printf (" fs = '%s'\n", line_elements[3]);
printf (" options = '%s'\n", line_elements[4]);
printf (" mount_point = '%s'\n", line_elements[5]);
#endif
if (strcmp (line_elements[0], device) == 0) {
char *line_to_free;
if (strcmp (line_elements[1], invoked_by_uid) == 0) {
mounted_by_other_uid = FALSE;
}
#ifdef sun
if (stat("/dev/vt/console_user", &st) == 0 &&
st.st_uid == atoi (invoked_by_uid)) {
/*
* Owner is allowed to take over. Before we have real
* ownership in HAL, assume it's the console owner.
*/
mounted_by_other_uid = FALSE;
}
#endif /* sun */
mount_point_to_unmount = g_strdup (line_elements[5]);
line_to_free = lines[i];
for (j = i; lines[j] != NULL; j++) {
lines[j] = lines[j+1];
}
lines[j] = NULL;
g_free (line_to_free);
g_strfreev (line_elements);
goto line_found;
}
}
g_strfreev (line_elements);
}
line_found:
if (mount_point_to_unmount == NULL) {
not_mounted_by_hal ("Device to unmount is not in /media/.hal-mtab so it is not mounted by HAL");
}
/* bail out, unless if we got the "hal-storage-can-unmount-volumes-mounted-by-others" privilege only
* if mounted_by_other_uid==TRUE
*
* We allow uid 0 to actually ensure that Unmount(options=["lazy"], "/dev/blah") works from addon-storage.
*/
if ((strcmp (invoked_by_uid, "0") != 0) && mounted_by_other_uid) {
/* TODO: actually check for privilege "hal-storage-can-unmount-volumes-mounted-by-others" */
permission_denied_privilege ("hal-storage-can-unmount-volumes-mounted-by-others", invoked_by_uid);
}
/* create new .hal-mtab~ file without the entry we're going to unmount */
hal_mtab_new = fopen ("/media/.hal-mtab~", "w");
if (hal_mtab_new == NULL) {
unknown_error ("Cannot create /media/.hal-mtab~");
}
for (i = 0; lines[i] != NULL; i++) {
if (i > 0) {
char anewl[2] = "\n\0";
if (fwrite (anewl, 1, 1, hal_mtab_new) != 1) {
unknown_error ("Cannot write to /media/.hal-mtab~");
}
}
if (fwrite (lines[i], 1, strlen (lines[i]), hal_mtab_new) != strlen (lines[i])) {
unknown_error ("Cannot write to /media/.hal-mtab~");
}
}
fclose (hal_mtab_new);
g_strfreev (lines);
/* construct arguments to /bin/umount */
na = 0;
args[na++] = UMOUNT;
if (option_lazy)
args[na++] = "-l";
if (option_force)
args[na++] = "-f";
args[na++] = (char *) device;
args[na++] = NULL;
#ifdef DEBUG
printf ("will umount %s (mounted at '%s'), mounted_by_other_uid=%d\n",
device, mount_point_to_unmount, mounted_by_other_uid);
#endif
/* invoke /bin/umount */
if (!g_spawn_sync ("/",
args,
NULL,
0,
NULL,
NULL,
&sout,
&serr,
&exit_status,
&err)) {
printf ("Cannot execute %s\n", UMOUNT);
unlink ("/media/.hal-mtab~");
unknown_error ("Cannot spawn " UMOUNT);
}
/* check if unmount was succesful */
if (exit_status != 0) {
printf ("%s error %d, stdout='%s', stderr='%s'\n", UMOUNT, exit_status, sout, serr);
if (strstr (serr, "device is busy") != NULL) {
unlink ("/media/.hal-mtab~");
device_busy (serr);
} else {
unlink ("/media/.hal-mtab~");
unknown_error (serr);
}
}
#ifdef sun
if ((adt_data = get_audit_export_data (system_bus,
invoked_by_syscon_name, &adt_data_size)) != NULL) {
audit_volume (adt_data, ADT_detach, WEXITSTATUS(exit_status),
"solaris.device.mount.removable",
mount_point_to_unmount, device, NULL);
free (adt_data);
}
#endif
/* unmount was succesful, remove directory we created in Mount() */
#ifdef sun
if (strncmp (mount_point_to_unmount, "/media/", 7) == 0)
#endif
if (g_rmdir (mount_point_to_unmount) != 0) {
unlink ("/media/.hal-mtab~");
unknown_error ("Cannot remove directory");
}
/* set new .hal-mtab file */
if (rename ("/media/.hal-mtab~", "/media/.hal-mtab") != 0) {
unlink ("/media/.hal-mtab~");
unknown_error ("Cannot rename /media/.hal-mtab~ to /media/.hal-mtab");
}
#ifdef DEBUG
printf ("done unmounting\n");
#endif
openlog ("hald", 0, LOG_DAEMON);
syslog (LOG_INFO, "unmounted %s from '%s' on behalf of uid %s", device, mount_point_to_unmount, invoked_by_uid);
closelog ();
g_free (sout);
g_free (serr);
g_free (mount_point_to_unmount);
}
#define EJECT "/usr/bin/eject"
void
handle_eject (LibHalContext *hal_ctx,
#ifdef HAVE_POLKIT
LibPolKitContext *pol_ctx,
#endif
const char *udi,
LibHalDrive *drive, const char *device,
const char *invoked_by_uid, const char *invoked_by_syscon_name,
gboolean closetray, DBusConnection *system_bus)
{
GError *err = NULL;
char *sout = NULL;
char *serr = NULL;
int exit_status;
char *args[10];
int na;
#ifdef sun
adt_export_data_t *adt_data;
size_t adt_data_size;
#endif
/* TODO: should we require privileges here? */
#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
/* construct arguments to EJECT (e.g. /usr/bin/eject) */
na = 0;
args[na++] = EJECT;
if (closetray) {
args[na++] = "-t";
}
args[na++] = (char *) device;
args[na++] = NULL;
#ifdef sun
putenv("EJECT_DIRECT=1");
#endif
#ifdef DEBUG
printf ("will eject %s\n", device);
#endif
/* invoke eject command */
if (!g_spawn_sync ("/",
args,
NULL,
0,
NULL,
NULL,
&sout,
&serr,
&exit_status,
&err)) {
printf ("Cannot execute %s\n", EJECT);
unknown_error ("Cannot spawn " EJECT);
}
#ifdef sun
/*
* Solaris eject returns 4 for manually ejectable media like floppy.
* Consider it success.
*/
if (WEXITSTATUS(exit_status) == 4) {
exit_status = 0;
}
if ((adt_data = get_audit_export_data (system_bus,
invoked_by_syscon_name, &adt_data_size)) != NULL) {
audit_volume (adt_data, ADT_remove, WEXITSTATUS(exit_status),
"solaris.device.mount.removable", NULL, device, NULL);
free (adt_data);
}
#endif /* sun */
/* check if eject was succesful */
if (exit_status != 0) {
printf ("%s error %d, stdout='%s', stderr='%s'\n", EJECT, exit_status, sout, serr);
unknown_error (serr);
}
/* eject was succesful... */
#ifdef DEBUG
printf ("done ejecting\n");
#endif
g_free (sout);
g_free (serr);
}
static int lock_mtab_fd = -1;
gboolean
lock_hal_mtab (void)
{
if (lock_mtab_fd >= 0)
return TRUE;
printf ("%d: XYA attempting to get lock on /media/.hal-mtab-lock\n", getpid ());
lock_mtab_fd = open ("/media/.hal-mtab-lock", O_CREAT | O_RDWR);
if (lock_mtab_fd < 0)
return FALSE;
tryagain:
#if sun
if (lockf (lock_mtab_fd, F_LOCK, 0) != 0) {
#else
if (flock (lock_mtab_fd, LOCK_EX) != 0) {
#endif
if (errno == EINTR)
goto tryagain;
return FALSE;
}
printf ("%d: XYA got lock on /media/.hal-mtab-lock\n", getpid ());
return TRUE;
}
void
unlock_hal_mtab (void)
{
#if sun
lockf (lock_mtab_fd, F_ULOCK, 0);
#else
flock (lock_mtab_fd, LOCK_UN);
#endif
close (lock_mtab_fd);
lock_mtab_fd = -1;
printf ("%d: XYA released lock on /media/.hal-mtab-lock\n", getpid ());
}
#if sun
/* map PolicyKit privilege to RBAC authorization */
char *
auth_from_privilege(const char *privilege)
{
char *authname;
int i;
if (strcmp (privilege, "hal-storage-removable-mount") == 0) {
authname = g_strdup ("solaris.device.mount.removable");
} else if (strcmp (privilege, "hal-storage-removable-mount-all-options") == 0) {
authname = g_strdup ("solaris.device.mount.alloptions.removable");
} else if (strcmp (privilege, "hal-storage-fixed-mount") == 0) {
authname = g_strdup ("solaris.device.mount.fixed");
} else if (strcmp (privilege, "hal-storage-fixed-mount-all-options") == 0) {
authname = g_strdup ("solaris.device.mount.alloptions.fixed");
} else {
/* replace '-' with '.' */
authname = g_strdup (privilege);
for (i = 0; i < strlen (authname); i++) {
if (authname[i] == '-') {
authname[i] = '.';
}
}
}
return (authname);
}
void
audit_volume(const adt_export_data_t *imported_state, au_event_t event_id,
int result, const char *auth_used, const char *mount_point,
const char *device, const char *options)
{
adt_session_data_t *ah;
adt_event_data_t *event;
if (adt_start_session(&ah, imported_state, 0) != 0) {
printf ("adt_start_session failed %d\n", errno);
return;
}
if ((event = adt_alloc_event(ah, event_id)) == NULL) {
printf ("adt_alloc_event(ADT_attach)\n", errno);
return;
}
switch (event_id) {
case ADT_attach:
event->adt_attach.auth_used = (char *)auth_used;
event->adt_attach.mount_point = (char *)mount_point;
event->adt_attach.device = (char *)device;
event->adt_attach.options = (char *)options;
break;
case ADT_detach:
event->adt_detach.auth_used = (char *)auth_used;
event->adt_detach.mount_point = (char *)mount_point;
event->adt_detach.device = (char *)device;
event->adt_detach.options = (char *)options;
break;
case ADT_remove:
event->adt_remove.auth_used = (char *)auth_used;
event->adt_remove.mount_point = (char *)mount_point;
event->adt_remove.device = (char *)device;
break;
default:
goto out;
}
if (result == 0) {
if (adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS) != 0) {
printf ("adt_put_event(%d, success)\n", event_id);
}
} else {
if (adt_put_event(event, ADT_FAILURE, result) != 0) {
printf ("adt_put_event(%d, failure)\n", event_id);
}
}
out:
adt_free_event(event);
(void) adt_end_session(ah);
}
#endif /* sun */