1
/***************************************************************************
4
* addon-storage.c : Poll storage devices for media changes
6
* Copyright (C) 2004 David Zeuthen, <david@fubar.dk>
8
* Licensed under the Academic Free License version 2.1
10
* This program is free software; you can redistribute it and/or modify
11
* it under the terms of the GNU General Public License as published by
12
* the Free Software Foundation; either version 2 of the License, or
13
* (at your option) any later version.
15
* This program is distributed in the hope that it will be useful,
16
* but WITHOUT ANY WARRANTY; without even the implied warranty of
17
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
* GNU General Public License for more details.
20
* You should have received a copy of the GNU General Public License
21
* along with this program; if not, write to the Free Software
22
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
24
**************************************************************************/
32
#include <linux/cdrom.h>
40
#include <sys/ioctl.h>
43
#include <dbus/dbus-glib.h>
44
#include <dbus/dbus-glib-lowlevel.h>
46
#include "libhal/libhal.h"
48
#include "../../logger.h"
49
#include "../../util_helper.h"
53
static char *device_file;
54
static int media_status;
56
static int support_media_changed;
57
static LibHalContext *ctx = NULL;
58
static DBusConnection *con = NULL;
59
static guint poll_timer = -1;
60
static GMainLoop *loop;
61
static gboolean system_is_idle = FALSE;
62
static gboolean check_lock_state = TRUE;
64
static gboolean polling_disabled = FALSE;
67
force_unmount (LibHalContext *ctx, const char *udi)
70
DBusMessage *msg = NULL;
71
DBusMessage *reply = NULL;
72
char **options = NULL;
73
unsigned int num_options = 0;
74
DBusConnection *dbus_connection;
77
dbus_connection = libhal_ctx_get_dbus_connection (ctx);
79
msg = dbus_message_new_method_call ("org.freedesktop.Hal", udi,
80
"org.freedesktop.Hal.Device.Volume",
83
HAL_ERROR (("Could not create dbus message for %s", udi));
88
options = calloc (1, sizeof (char *));
89
if (options == NULL) {
90
HAL_ERROR (("Could not allocate options array"));
97
device_file = libhal_device_get_property_string (ctx, udi, "block.device", NULL);
98
if (device_file != NULL) {
99
HAL_INFO(("forcibly attempting to lazy unmount %s as media was removed", device_file));
100
libhal_free_string (device_file);
103
if (!dbus_message_append_args (msg,
104
DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &options, num_options,
105
DBUS_TYPE_INVALID)) {
106
HAL_ERROR (("Could not append args to dbus message for %s", udi));
110
dbus_error_init (&error);
111
if (!(reply = dbus_connection_send_with_reply_and_block (dbus_connection, msg, -1, &error))) {
112
HAL_ERROR (("Unmount failed for %s: %s : %s\n", udi, error.name, error.message));
113
dbus_error_free (&error);
117
if (dbus_error_is_set (&error)) {
118
HAL_ERROR (("Unmount failed for %s\n%s : %s\n", udi, error.name, error.message));
119
dbus_error_free (&error);
123
HAL_DEBUG (("Succesfully unmounted udi '%s'", udi));
129
dbus_message_unref (msg);
131
dbus_message_unref (reply);
135
unmount_cleartext_devices (LibHalContext *ctx, const char *udi)
138
char **clear_devices;
139
int num_clear_devices;
144
/* check if the volume we back is mounted.. if it is.. unmount it */
145
dbus_error_init (&error);
146
clear_devices = libhal_manager_find_device_string_match (ctx,
147
"volume.crypto_luks.clear.backing_volume",
152
if (clear_devices != NULL && num_clear_devices > 0) {
157
for (i = 0; i < num_clear_devices; i++) {
159
clear_udi = clear_devices[i];
160
LIBHAL_FREE_DBUS_ERROR (&error);
161
if (libhal_device_get_property_bool (ctx, clear_udi, "volume.is_mounted", &error)) {
162
HAL_DEBUG (("Forcing unmount of child '%s' (crypto)", clear_udi));
163
force_unmount (ctx, clear_udi);
166
libhal_free_string_array (clear_devices);
169
LIBHAL_FREE_DBUS_ERROR (&error);
174
unmount_childs (LibHalContext *ctx, const char *udi)
180
/* need to force unmount all partitions */
181
dbus_error_init (&error);
182
if ((volumes = libhal_manager_find_device_string_match (ctx, "block.storage_device", udi, &num_volumes, &error)) != NULL) {
185
for (i = 0; i < num_volumes; i++) {
188
vol_udi = volumes[i];
189
LIBHAL_FREE_DBUS_ERROR (&error);
191
if (libhal_device_get_property_bool (ctx, vol_udi, "block.is_volume", &error)) {
192
dbus_bool_t is_crypto;
194
/* unmount all cleartext devices associated with us */
195
is_crypto = unmount_cleartext_devices (ctx, vol_udi);
197
LIBHAL_FREE_DBUS_ERROR (&error);
198
if (libhal_device_get_property_bool (ctx, vol_udi, "volume.is_mounted", &error)) {
199
HAL_DEBUG (("Forcing unmount of child '%s'", vol_udi));
200
force_unmount (ctx, vol_udi);
203
/* teardown crypto */
205
DBusMessage *msg = NULL;
206
DBusMessage *reply = NULL;
208
/* tear down mapping */
209
HAL_DEBUG (("Teardown crypto for '%s'", vol_udi));
211
msg = dbus_message_new_method_call ("org.freedesktop.Hal", vol_udi,
212
"org.freedesktop.Hal.Device.Volume.Crypto",
215
HAL_ERROR (("Could not create dbus message for %s", vol_udi));
216
goto teardown_failed;
219
LIBHAL_FREE_DBUS_ERROR (&error);
221
if (!(reply = dbus_connection_send_with_reply_and_block (
222
libhal_ctx_get_dbus_connection (ctx), msg, -1, &error)) ||
223
dbus_error_is_set (&error)) {
224
HAL_DEBUG (("Teardown failed for %s: %s : %s\n", udi, error.name, error.message));
225
dbus_error_free (&error);
230
dbus_message_unref (msg);
232
dbus_message_unref (reply);
238
libhal_free_string_array (volumes);
240
LIBHAL_FREE_DBUS_ERROR (&error);
243
/** Check if a filesystem on a special device file is mounted
245
* @param device_file Special device file, e.g. /dev/cdrom
246
* @return TRUE iff there is a filesystem system mounted
247
* on the special device file
250
is_mounted (const char *device_file)
259
if ((f = setmntent ("/etc/mtab", "r")) == NULL)
262
while (getmntent_r (f, &mnt, buf, sizeof(buf)) != NULL) {
263
if (strcmp (device_file, mnt.mnt_fsname) == 0) {
277
MEDIA_STATUS_UNKNOWN = 0,
278
MEDIA_STATUS_GOT_MEDIA = 1,
279
MEDIA_STATUS_NO_MEDIA = 2
282
static gboolean poll_for_media (gpointer user_data);
283
static gboolean poll_for_media_force (void);
285
static int interval_in_seconds = 2;
287
static gboolean is_locked_by_hal = FALSE;
288
static gboolean is_locked_via_o_excl = FALSE;
291
update_proc_title (void)
294
if (polling_disabled) {
295
hal_set_proc_title ("hald-addon-storage: no polling on %s because it is explicitly disabled", device_file);
296
} else if (is_locked_by_hal) {
297
if (is_locked_via_o_excl) {
298
hal_set_proc_title ("hald-addon-storage: no polling because %s is locked via HAL and O_EXCL", device_file);
300
hal_set_proc_title ("hald-addon-storage: no polling because %s is locked via HAL", device_file);
302
} else if (is_locked_via_o_excl) {
303
hal_set_proc_title ("hald-addon-storage: no polling because %s is locked via O_EXCL", device_file);
305
hal_set_proc_title ("hald-addon-storage: polling %s (every %d sec)", device_file, interval_in_seconds);
310
update_polling_interval (void)
312
/* TODO: ideally we want all things that do polling to do it
313
* at the same time.. such as to minimize battery
314
* usage. Suppose we want to wake up to do poll_for_media()
315
* every N seconds. Suppose M is the number of seconds since
316
* epoch. The fix is to wake up at exactly when M is divisible
317
* by N... plus some system wide offset (ideally read from
318
* /sys so kernel threads can use the same offset) to make Xen
321
* Also, the polling intervals should be powers of two to
322
* ensure that wakeup's with different intervals happen at the
323
* same time when possible.
325
* This is sorta-kinda what g_timeout_source_new_seconds()
326
* tries to achieve, not enough I think, and it's only
327
* available in glib >= 2.14. Too little, too late? *shrug*
331
interval_in_seconds = 16;
333
interval_in_seconds = 2;
336
g_source_remove (poll_timer);
338
#ifdef HAVE_GLIB_2_14
339
poll_timer = g_timeout_add_seconds (interval_in_seconds, poll_for_media, NULL);
341
poll_timer = g_timeout_add (interval_in_seconds * 1000, poll_for_media, NULL);
344
update_proc_title ();
348
poll_for_media (gpointer user_data)
350
if (check_lock_state) {
352
dbus_bool_t should_poll;
354
check_lock_state = FALSE;
356
HAL_INFO (("Checking whether device %s is locked on HAL", device_file));
357
dbus_error_init (&error);
358
if (libhal_device_is_locked_by_others (ctx, udi, "org.freedesktop.Hal.Device.Storage", &error)) {
359
HAL_INFO (("... device %s is locked on HAL", device_file));
360
is_locked_by_hal = TRUE;
361
update_proc_title ();
362
LIBHAL_FREE_DBUS_ERROR (&error);
365
HAL_INFO (("... device %s is not locked on HAL", device_file));
366
is_locked_by_hal = FALSE;
369
LIBHAL_FREE_DBUS_ERROR (&error);
371
should_poll = libhal_device_get_property_bool (ctx, udi, "storage.media_check_enabled", &error);
372
LIBHAL_FREE_DBUS_ERROR (&error);
373
polling_disabled = !should_poll;
374
update_proc_title ();
377
/* TODO: we could remove the timeout completely... */
378
if (is_locked_by_hal || polling_disabled)
381
poll_for_media_force ();
387
/* returns: whether the state changed */
389
poll_for_media_force (void)
393
int old_media_status;
397
old_media_status = media_status;
401
fd = open (device_file, O_RDONLY | O_NONBLOCK | O_EXCL);
403
if (fd < 0 && errno == EBUSY) {
404
/* this means the disc is mounted or some other app,
405
* like a cd burner, has already opened O_EXCL */
407
/* HOWEVER, when starting hald, a disc may be
408
* mounted; so check /etc/mtab to see if it
409
* actually is mounted. If it is we retry to open
412
if (!is_mounted (device_file)) {
413
if (!is_locked_via_o_excl) {
414
is_locked_via_o_excl = TRUE;
415
update_proc_title ();
417
is_locked_via_o_excl = TRUE;
422
fd = open (device_file, O_RDONLY | O_NONBLOCK);
426
HAL_ERROR (("open failed for %s: %s", device_file, strerror (errno)));
430
if (is_locked_via_o_excl) {
431
is_locked_via_o_excl = FALSE;
432
update_proc_title ();
436
/* Check if a disc is in the drive
438
* @todo Use MMC-2 API if applicable
440
drive = ioctl (fd, CDROM_DRIVE_STATUS, CDSL_CURRENT);
445
case CDS_DRIVE_NOT_READY:
449
/* some CD-ROMs report CDS_DISK_OK even with an open
450
* tray; if media check has the same value two times in
451
* a row then this seems to be the case and we must not
452
* report that there is a media in it. */
453
if (support_media_changed &&
454
ioctl (fd, CDROM_MEDIA_CHANGED, CDSL_CURRENT) &&
455
ioctl (fd, CDROM_MEDIA_CHANGED, CDSL_CURRENT)) {
462
HAL_ERROR (("CDROM_DRIVE_STATUS failed: %s\n", strerror(errno)));
469
/* check if eject button was pressed */
471
unsigned char cdb[10] = { 0x4a, 1, 0, 0, 16, 0, 0, 0, 8, 0};
472
unsigned char buffer[8];
473
struct sg_io_hdr sg_h;
476
memset(buffer, 0, sizeof(buffer));
477
memset(&sg_h, 0, sizeof(struct sg_io_hdr));
478
sg_h.interface_id = 'S';
479
sg_h.cmd_len = sizeof(cdb);
480
sg_h.dxfer_direction = SG_DXFER_FROM_DEV;
481
sg_h.dxfer_len = sizeof(buffer);
482
sg_h.dxferp = buffer;
485
retval = ioctl(fd, SG_IO, &sg_h);
486
if (retval == 0 && sg_h.status == 0 && (buffer[4] & 0x0f) == 0x01) {
489
/* emit signal from drive device object */
490
dbus_error_init (&error);
491
libhal_device_emit_condition (ctx, udi, "EjectPressed", "", &error);
492
LIBHAL_FREE_DBUS_ERROR (&error);
497
fd = open (device_file, O_RDONLY);
498
if (fd < 0 && errno == ENOMEDIUM) {
500
} else if (fd >= 0) {
504
HAL_ERROR (("open failed for %s: %s", device_file, strerror (errno)));
509
/* set correct state on startup, this avoid endless loops if there was a media in the device on startup */
510
if (media_status == MEDIA_STATUS_UNKNOWN) {
512
media_status = MEDIA_STATUS_NO_MEDIA;
514
media_status = MEDIA_STATUS_GOT_MEDIA;
517
switch (media_status) {
518
case MEDIA_STATUS_GOT_MEDIA:
522
HAL_DEBUG (("Media removal detected on %s", device_file));
523
libhal_device_set_property_bool (ctx, udi, "storage.removable.media_available", FALSE, NULL);
524
libhal_device_set_property_string (ctx, udi, "storage.partitioning_scheme", "", NULL);
527
/* attempt to unmount all childs */
528
unmount_childs (ctx, udi);
530
/* could have a fs on the main block device; do a rescan to remove it */
531
dbus_error_init (&error);
532
libhal_device_rescan (ctx, udi, &error);
533
LIBHAL_FREE_DBUS_ERROR (&error);
535
/* have to this to trigger appropriate hotplug events */
536
fd = open (device_file, O_RDONLY | O_NONBLOCK);
538
ioctl (fd, BLKRRPART);
544
case MEDIA_STATUS_NO_MEDIA:
548
HAL_DEBUG (("Media insertion detected on %s", device_file));
550
/* our probe will trigger the appropriate hotplug events */
551
libhal_device_set_property_bool (
552
ctx, udi, "storage.removable.media_available", TRUE, NULL);
554
/* could have a fs on the main block device; do a rescan to add it */
555
dbus_error_init (&error);
556
libhal_device_rescan (ctx, udi, &error);
557
LIBHAL_FREE_DBUS_ERROR (&error);
561
case MEDIA_STATUS_UNKNOWN:
566
/* update our current status */
568
media_status = MEDIA_STATUS_GOT_MEDIA;
570
media_status = MEDIA_STATUS_NO_MEDIA;
572
/*HAL_DEBUG (("polling %s; got media=%d", device_file, got_media));*/
575
return old_media_status != media_status;
580
get_system_idle_from_ck (void)
584
DBusMessage *message;
589
message = dbus_message_new_method_call ("org.freedesktop.ConsoleKit",
590
"/org/freedesktop/ConsoleKit/Manager",
591
"org.freedesktop.ConsoleKit.Manager",
592
"GetSystemIdleHint");
593
dbus_error_init (&error);
594
reply = dbus_connection_send_with_reply_and_block (con, message, -1, &error);
595
if (reply == NULL || dbus_error_is_set (&error)) {
596
HAL_ERROR (("Error doing Manager.GetSystemIdleHint on ConsoleKit: %s: %s", error.name, error.message));
597
dbus_message_unref (message);
599
dbus_message_unref (reply);
602
if (!dbus_message_get_args (reply, NULL,
603
DBUS_TYPE_BOOLEAN, &(system_is_idle),
604
DBUS_TYPE_INVALID)) {
605
HAL_ERROR (("Invalid GetSystemIdleHint reply from CK"));
608
dbus_message_unref (message);
609
dbus_message_unref (reply);
614
LIBHAL_FREE_DBUS_ERROR (&error);
617
#endif /* HAVE_CONKIT */
620
static DBusHandlerResult
621
direct_filter_function (DBusConnection *connection, DBusMessage *message, void *user_data)
623
if (dbus_message_is_method_call (message,
624
"org.freedesktop.Hal.Device.Storage.Removable",
627
dbus_bool_t call_had_sideeffect;
629
HAL_INFO (("Forcing poll for media becusse CheckForMedia() was called"));
631
call_had_sideeffect = poll_for_media_force ();
633
reply = dbus_message_new_method_return (message);
634
dbus_message_append_args (reply,
635
DBUS_TYPE_BOOLEAN, &call_had_sideeffect,
637
dbus_connection_send (connection, reply, NULL);
638
dbus_message_unref (reply);
641
return DBUS_HANDLER_RESULT_HANDLED;
644
static DBusHandlerResult
645
dbus_filter_function (DBusConnection *connection, DBusMessage *message, void *user_data)
648
gboolean system_is_idle_new;
650
if (dbus_message_is_signal (message,
651
"org.freedesktop.ConsoleKit.Manager",
652
"SystemIdleHintChanged")) {
653
if (!dbus_message_get_args (message, NULL,
654
DBUS_TYPE_BOOLEAN, &system_is_idle_new,
655
DBUS_TYPE_INVALID)) {
656
HAL_ERROR (("Invalid SystemIdleHintChanged signal from CK"));
660
if (system_is_idle_new != system_is_idle) {
661
system_is_idle = system_is_idle_new;
662
update_polling_interval ();
667
#endif /* HAVE_CONKIT */
669
/* Check, just before the next poll, whether lock state have changed;
671
* Note that we get called on at least these signals
673
* 1. CK.Manager - SystemIdleHintChanged
674
* 2. CK.Seat - ActiveSessionChanged
675
* 2. HAL.Manager - GlobalLockAcquired, GlobalLockReleased
676
* 3. HAL.Device - LockAcquired, LockReleased
678
* meaning that every time the locking situation changes, we
681
check_lock_state = TRUE;
683
return DBUS_HANDLER_RESULT_HANDLED;
687
main (int argc, char *argv[])
690
LibHalContext *ctx_direct;
691
DBusConnection *con_direct;
694
char *support_media_changed_str;
697
hal_set_proc_title_init (argc, argv);
699
/* We could drop privs if we know that the haldaemon user is
700
* to be able to access block devices...
702
/*drop_privileges (1);*/
704
if ((udi = getenv ("UDI")) == NULL)
706
if ((device_file = getenv ("HAL_PROP_BLOCK_DEVICE")) == NULL)
708
if ((bus = getenv ("HAL_PROP_STORAGE_BUS")) == NULL)
710
if ((drive_type = getenv ("HAL_PROP_STORAGE_DRIVE_TYPE")) == NULL)
715
support_media_changed_str = getenv ("HAL_PROP_STORAGE_CDROM_SUPPORT_MEDIA_CHANGED");
716
if (support_media_changed_str != NULL && strcmp (support_media_changed_str, "true") == 0)
717
support_media_changed = TRUE;
719
support_media_changed = FALSE;
721
dbus_error_init (&error);
722
con = dbus_bus_get (DBUS_BUS_SYSTEM, &error);
724
HAL_ERROR (("Cannot connect to system bus"));
727
loop = g_main_loop_new (NULL, FALSE);
728
dbus_connection_setup_with_g_main (con, NULL);
729
dbus_connection_set_exit_on_disconnect (con, 0);
731
if ((ctx_direct = libhal_ctx_init_direct (&error)) == NULL) {
732
HAL_ERROR (("Cannot connect to hald"));
735
if (!libhal_device_addon_is_ready (ctx_direct, udi, &error)) {
738
con_direct = libhal_ctx_get_dbus_connection (ctx_direct);
739
dbus_connection_setup_with_g_main (con_direct, NULL);
740
dbus_connection_set_exit_on_disconnect (con_direct, 0);
741
dbus_connection_add_filter (con_direct, direct_filter_function, NULL, NULL);
744
if ((ctx = libhal_ctx_init_direct (&error)) == NULL)
747
if (!libhal_device_addon_is_ready (ctx, udi, &error))
750
HAL_DEBUG (("**************************************************"));
751
HAL_DEBUG (("Doing addon-storage for %s (bus %s) (drive_type %s) (udi %s)", device_file, bus, drive_type, udi));
752
HAL_DEBUG (("**************************************************"));
754
if (strcmp (drive_type, "cdrom") == 0)
759
media_status = MEDIA_STATUS_UNKNOWN;
763
/* TODO: ideally we should track the sessions on the seats on
764
* which the device belongs to. But right now we don't really
765
* do multi-seat so I'm going to punt on this for now.
767
get_system_idle_from_ck ();
769
dbus_bus_add_match (con,
771
",interface='org.freedesktop.ConsoleKit.Manager'"
772
",sender='org.freedesktop.ConsoleKit'"
773
",member='SystemIdleHintChanged'",
775
dbus_bus_add_match (con,
777
",interface='org.freedesktop.ConsoleKit.Seat'"
778
",sender='org.freedesktop.ConsoleKit'"
779
",member='ActiveSessionChanged'",
783
/* this is a bit weird; but we want to listen to signals about
784
* locking from hald.. and signals are not pushed over direct
785
* connections (for a good reason).
787
dbus_bus_add_match (con,
789
",interface='org.freedesktop.Hal.Manager'"
790
",sender='org.freedesktop.Hal'",
792
dbus_bus_add_match (con,
794
",interface='org.freedesktop.Hal.Manager'"
795
",sender='org.freedesktop.Hal'",
797
str = g_strdup_printf ("type='signal'"
798
",interface='org.freedesktop.Hal.Device'"
799
",sender='org.freedesktop.Hal'"
802
dbus_bus_add_match (con,
806
dbus_connection_add_filter (con, dbus_filter_function, NULL, NULL);
808
if (!libhal_device_claim_interface (ctx,
810
"org.freedesktop.Hal.Device.Storage.Removable",
811
" <method name=\"CheckForMedia\">\n"
812
" <arg name=\"call_had_sideeffect\" direction=\"out\" type=\"b\"/>\n"
815
HAL_ERROR (("Cannot claim interface 'org.freedesktop.Hal.Device.Storage.Removable'"));
820
update_polling_interval ();
821
g_main_loop_run (loop);
824
HAL_DEBUG (("An error occured, exiting cleanly"));
826
LIBHAL_FREE_DBUS_ERROR (&error);
829
libhal_ctx_shutdown (ctx, &error);
830
LIBHAL_FREE_DBUS_ERROR (&error);
831
libhal_ctx_free (ctx);