1 /***************************************************************************
   2  *
   3  * addon-storage.c : watch removable media state changes
   4  *
   5  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
   6  * Use is subject to license terms.
   7  *
   8  * Licensed under the Academic Free License version 2.1
   9  *
  10  **************************************************************************/
  11 
  12 #ifdef HAVE_CONFIG_H
  13 #  include <config.h>
  14 #endif
  15 
  16 #include <errno.h>
  17 #include <string.h>
  18 #include <strings.h>
  19 #include <stdlib.h>
  20 #include <stdio.h>
  21 #include <sys/ioctl.h>
  22 #include <sys/types.h>
  23 #include <sys/stat.h>
  24 #include <sys/types.h>
  25 #include <sys/wait.h>
  26 #include <fcntl.h>
  27 #include <unistd.h>
  28 #include <sys/mnttab.h>
  29 #include <sys/dkio.h>
  30 #include <priv.h>
  31 #include <libsysevent.h>
  32 #include <sys/sysevent/dev.h>
  33 
  34 #include <libhal.h>
  35 
  36 #include "../../hald/logger.h"
  37 
  38 #define SLEEP_PERIOD    5
  39 
  40 static char                     *udi;
  41 static char                     *devfs_path;
  42 LibHalContext                   *ctx = NULL;
  43 static sysevent_handle_t        *shp = NULL;
  44 
  45 static void     sysevent_dev_handler(sysevent_t *);
  46 
  47 static void
  48 my_dbus_error_free(DBusError *error)
  49 {
  50         if (dbus_error_is_set(error)) {
  51                 dbus_error_free(error);
  52         }
  53 }
  54 
  55 static void
  56 sysevent_init ()
  57 {
  58         const char      *subcl[1];
  59 
  60         shp = sysevent_bind_handle (sysevent_dev_handler);
  61         if (shp == NULL) {
  62                 HAL_DEBUG (("sysevent_bind_handle failed %d", errno));
  63                 return;
  64         }
  65 
  66         subcl[0] = ESC_DEV_EJECT_REQUEST;
  67         if (sysevent_subscribe_event (shp, EC_DEV_STATUS, subcl, 1) != 0) {
  68                 HAL_INFO (("subscribe(dev_status) failed %d", errno));
  69                 sysevent_unbind_handle (shp);
  70                 return;
  71         }
  72 }
  73 
  74 static void
  75 sysevent_fini ()
  76 {
  77         if (shp != NULL) {
  78                 sysevent_unbind_handle (shp);
  79                 shp = NULL;
  80         }
  81 }
  82 
  83 static void
  84 sysevent_dev_handler (sysevent_t *ev)
  85 {
  86         char            *class;
  87         char            *subclass;
  88         nvlist_t        *attr_list;
  89         char            *phys_path, *path;
  90         char            *p;
  91         int             len;
  92         DBusError       error;
  93 
  94         if ((class = sysevent_get_class_name (ev)) == NULL)
  95                 return;
  96 
  97         if ((subclass = sysevent_get_subclass_name (ev)) == NULL)
  98                 return;
  99 
 100         if ((strcmp (class, EC_DEV_STATUS) != 0) ||
 101             (strcmp (subclass, ESC_DEV_EJECT_REQUEST) != 0))
 102                 return;
 103 
 104         if (sysevent_get_attr_list (ev, &attr_list) != 0)
 105                 return;
 106 
 107         if (nvlist_lookup_string (attr_list, DEV_PHYS_PATH, &phys_path) != 0) {
 108                 goto out;
 109         }
 110 
 111         /* see if event belongs to our LUN (ignore slice and "/devices" ) */
 112         if (strncmp (phys_path, "/devices", sizeof ("/devices") - 1) == 0)
 113                 path = phys_path + sizeof ("/devices") - 1;
 114         else
 115                 path = phys_path;
 116 
 117         if ((p = strrchr (path, ':')) == NULL)
 118                 goto out;
 119         len = (uintptr_t)p - (uintptr_t)path;
 120         if (strncmp (path, devfs_path, len) != 0)
 121                 goto out;
 122 
 123         HAL_DEBUG (("sysevent_dev_handler %s %s", subclass, phys_path));
 124 
 125         /* we got it, tell the world */
 126         dbus_error_init (&error);
 127         libhal_device_emit_condition (ctx, udi, "EjectPressed", "", &error);
 128         dbus_error_free (&error);
 129 
 130 out:
 131         nvlist_free(attr_list);
 132 }
 133 
 134 static void
 135 force_unmount (LibHalContext *ctx, const char *udi)
 136 {
 137         DBusError error;
 138         DBusMessage *msg = NULL;
 139         DBusMessage *reply = NULL;
 140         char **options = NULL;
 141         unsigned int num_options = 0;
 142         DBusConnection *dbus_connection;
 143         char *device_file;
 144 
 145         dbus_error_init (&error);
 146 
 147         dbus_connection = libhal_ctx_get_dbus_connection (ctx);
 148 
 149         msg = dbus_message_new_method_call ("org.freedesktop.Hal", udi,
 150                                             "org.freedesktop.Hal.Device.Volume",
 151                                             "Unmount");
 152         if (msg == NULL) {
 153                 HAL_DEBUG (("Could not create dbus message for %s", udi));
 154                 goto out;
 155         }
 156 
 157 
 158         options = calloc (1, sizeof (char *));
 159         if (options == NULL) {
 160                 HAL_DEBUG (("Could not allocate options array"));
 161                 goto out;
 162         }
 163 
 164         device_file = libhal_device_get_property_string (ctx, udi, "block.device", &error);
 165         if (device_file != NULL) {
 166                 libhal_free_string (device_file);
 167         }
 168         dbus_error_free (&error);
 169 
 170         if (!dbus_message_append_args (msg, 
 171                                        DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &options, num_options,
 172                                        DBUS_TYPE_INVALID)) {
 173                 HAL_DEBUG (("Could not append args to dbus message for %s", udi));
 174                 goto out;
 175         }
 176         
 177         if (!(reply = dbus_connection_send_with_reply_and_block (dbus_connection, msg, -1, &error))) {
 178                 HAL_DEBUG (("Unmount failed for %s: %s : %s\n", udi, error.name, error.message));
 179                 goto out;
 180         }
 181 
 182         if (dbus_error_is_set (&error)) {
 183                 HAL_DEBUG (("Unmount failed for %s\n%s : %s\n", udi, error.name, error.message));
 184                 goto out;
 185         }
 186 
 187         HAL_DEBUG (("Succesfully unmounted udi '%s'", udi));
 188 
 189 out:
 190         dbus_error_free (&error);
 191         if (options != NULL)
 192                 free (options);
 193         if (msg != NULL)
 194                 dbus_message_unref (msg);
 195         if (reply != NULL)
 196                 dbus_message_unref (reply);
 197 }
 198 
 199 static void 
 200 unmount_childs (LibHalContext *ctx, const char *udi)
 201 {
 202         DBusError error;
 203         int num_volumes;
 204         char **volumes;
 205 
 206         dbus_error_init (&error);
 207 
 208         /* need to force unmount all partitions */
 209         if ((volumes = libhal_manager_find_device_string_match (
 210              ctx, "block.storage_device", udi, &num_volumes, &error)) != NULL) {
 211                 dbus_error_free (&error);
 212                 int i;
 213 
 214                 for (i = 0; i < num_volumes; i++) {
 215                         char *vol_udi;
 216 
 217                         vol_udi = volumes[i];
 218                         if (libhal_device_get_property_bool (ctx, vol_udi, "block.is_volume", &error)) {
 219                                 dbus_error_free (&error);
 220                                 if (libhal_device_get_property_bool (ctx, vol_udi, "volume.is_mounted", &error)) {
 221                                         dbus_error_free (&error);
 222                                         HAL_DEBUG (("Forcing unmount of child '%s'", vol_udi));
 223                                         force_unmount (ctx, vol_udi);
 224                                 }
 225                         }
 226                 }
 227                 libhal_free_string_array (volumes);
 228         }
 229         my_dbus_error_free (&error);
 230 }
 231 
 232 /** Check if a filesystem on a special device file is mounted
 233  *
 234  *  @param  device_file         Special device file, e.g. /dev/cdrom
 235  *  @return                     TRUE iff there is a filesystem system mounted
 236  *                              on the special device file
 237  */
 238 static dbus_bool_t
 239 is_mounted (const char *device_file)
 240 {
 241         FILE *f;
 242         dbus_bool_t rc = FALSE;
 243         struct mnttab mp;
 244         struct mnttab mpref;
 245 
 246         if ((f = fopen ("/etc/mnttab", "r")) == NULL)
 247                 return rc;
 248 
 249         bzero(&mp, sizeof (mp));
 250         bzero(&mpref, sizeof (mpref));
 251         mpref.mnt_special = (char *)device_file;
 252         if (getmntany(f, &mp, &mpref) == 0) {
 253                 rc = TRUE;
 254         }
 255 
 256         fclose (f);
 257         return rc;
 258 }
 259 
 260 void
 261 close_device (int *fd)
 262 {
 263         if (*fd > 0) {
 264                 close (*fd);
 265                 *fd = -1;
 266         }
 267 }
 268 
 269 void
 270 drop_privileges ()
 271 {
 272         priv_set_t *pPrivSet = NULL;
 273         priv_set_t *lPrivSet = NULL;
 274 
 275         /*
 276          * Start with the 'basic' privilege set and then remove any
 277          * of the 'basic' privileges that will not be needed.
 278          */
 279         if ((pPrivSet = priv_str_to_set("basic", ",", NULL)) == NULL) {
 280                 return;
 281         }
 282 
 283         /* Clear privileges we will not need from the 'basic' set */
 284         (void) priv_delset(pPrivSet, PRIV_FILE_LINK_ANY);
 285         (void) priv_delset(pPrivSet, PRIV_PROC_INFO);
 286         (void) priv_delset(pPrivSet, PRIV_PROC_SESSION);
 287 
 288         /* to open logindevperm'd devices */
 289         (void) priv_addset(pPrivSet, PRIV_FILE_DAC_READ);
 290 
 291         /* to receive sysevents */
 292         (void) priv_addset(pPrivSet, PRIV_SYS_CONFIG);
 293 
 294         /* Set the permitted privilege set. */
 295         if (setppriv(PRIV_SET, PRIV_PERMITTED, pPrivSet) != 0) {
 296                 return;
 297         }
 298 
 299         /* Clear the limit set. */
 300         if ((lPrivSet = priv_allocset()) == NULL) {
 301                 return;
 302         }
 303 
 304         priv_emptyset(lPrivSet);
 305 
 306         if (setppriv(PRIV_SET, PRIV_LIMIT, lPrivSet) != 0) {
 307                 return;
 308         }
 309 
 310         priv_freeset(lPrivSet);
 311 }
 312 
 313 int 
 314 main (int argc, char *argv[])
 315 {
 316         char *device_file, *raw_device_file;
 317         DBusError error;
 318         char *bus;
 319         char *drive_type;
 320         int state, last_state;
 321         char *support_media_changed_str;
 322         int support_media_changed;
 323         int fd = -1;
 324 
 325         if ((udi = getenv ("UDI")) == NULL)
 326                 goto out;
 327         if ((device_file = getenv ("HAL_PROP_BLOCK_DEVICE")) == NULL)
 328                 goto out;
 329         if ((raw_device_file = getenv ("HAL_PROP_BLOCK_SOLARIS_RAW_DEVICE")) == NULL)
 330                 goto out;
 331         if ((bus = getenv ("HAL_PROP_STORAGE_BUS")) == NULL)
 332                 goto out;
 333         if ((drive_type = getenv ("HAL_PROP_STORAGE_DRIVE_TYPE")) == NULL)
 334                 goto out;
 335         if ((devfs_path = getenv ("HAL_PROP_SOLARIS_DEVFS_PATH")) == NULL)
 336                 goto out;
 337 
 338         drop_privileges ();
 339 
 340         setup_logger ();
 341 
 342         sysevent_init ();
 343 
 344         support_media_changed_str = getenv ("HAL_PROP_STORAGE_CDROM_SUPPORT_MEDIA_CHANGED");
 345         if (support_media_changed_str != NULL && strcmp (support_media_changed_str, "true") == 0)
 346                 support_media_changed = TRUE;
 347         else
 348                 support_media_changed = FALSE;
 349 
 350         dbus_error_init (&error);
 351 
 352         if ((ctx = libhal_ctx_init_direct (&error)) == NULL) {
 353                 goto out;
 354         }
 355         my_dbus_error_free (&error);
 356 
 357         if (!libhal_device_addon_is_ready (ctx, udi, &error)) {
 358                 goto out;
 359         }
 360         my_dbus_error_free (&error);
 361 
 362         printf ("Doing addon-storage for %s (bus %s) (drive_type %s) (udi %s)\n", device_file, bus, drive_type, udi);
 363 
 364         last_state = state = DKIO_NONE;
 365 
 366         /* Linux version of this addon attempts to re-open the device O_EXCL
 367          * every 2 seconds, trying to figure out if some other app,
 368          * like a cd burner, is using the device. Aside from questionable
 369          * value of this (apps should use HAL's locked property or/and
 370          * Solaris in_use facility), but also frequent opens/closes
 371          * keeps media constantly spun up. All this needs more thought.
 372          */
 373         for (;;) {
 374                 if (is_mounted (device_file)) {
 375                         close_device (&fd);
 376                         sleep (SLEEP_PERIOD);
 377                 } else if ((fd < 0) && ((fd = open (raw_device_file, O_RDONLY | O_NONBLOCK)) < 0)) {
 378                         HAL_DEBUG (("open failed for %s: %s", raw_device_file, strerror (errno)));
 379                         sleep (SLEEP_PERIOD);
 380                 } else {
 381                         /* Check if a disc is in the drive */
 382                         /* XXX initial call always returns inserted
 383                          * causing unnecessary rescan - optimize?
 384                          */
 385                         if (ioctl (fd, DKIOCSTATE, &state) == 0) {
 386                                 if (state == last_state) {
 387                                         HAL_DEBUG (("state has not changed %d %s", state, device_file));
 388                                         continue;
 389                                 } else {
 390                                         HAL_DEBUG (("new state %d %s", state, device_file));
 391                                 }
 392 
 393                                 switch (state) {
 394                                 case DKIO_EJECTED:
 395                                         HAL_DEBUG (("Media removal detected on %s", device_file));
 396                                         last_state = state;
 397 
 398                                         libhal_device_set_property_bool (ctx, udi, "storage.removable.media_available", FALSE, &error);
 399                                         my_dbus_error_free (&error);
 400 
 401                                         /* attempt to unmount all childs */
 402                                         unmount_childs (ctx, udi);
 403 
 404                                         /* could have a fs on the main block device; do a rescan to remove it */
 405                                         libhal_device_rescan (ctx, udi, &error);
 406                                         my_dbus_error_free (&error);
 407                                         break;
 408 
 409                                 case DKIO_INSERTED:
 410                                         HAL_DEBUG (("Media insertion detected on %s", device_file));
 411                                         last_state = state;
 412 
 413                                         libhal_device_set_property_bool (ctx, udi, "storage.removable.media_available", TRUE, &error);
 414                                         my_dbus_error_free (&error);
 415 
 416                                         /* could have a fs on the main block device; do a rescan to add it */
 417                                         libhal_device_rescan (ctx, udi, &error);
 418                                         my_dbus_error_free (&error);
 419                                         break;
 420 
 421                                 case DKIO_DEV_GONE:
 422                                         HAL_DEBUG (("Device gone detected on %s", device_file));
 423                                         last_state = state;
 424 
 425                                         unmount_childs (ctx, udi);
 426                                         close_device (&fd);
 427                                         goto out;
 428 
 429                                 case DKIO_NONE:
 430                                 default:
 431                                         break;
 432                                 }
 433                         } else {
 434                                 HAL_DEBUG (("DKIOCSTATE failed: %s\n", strerror(errno)));
 435                                 sleep (SLEEP_PERIOD);
 436                         }
 437                 }
 438         }
 439 
 440 out:
 441         sysevent_fini ();
 442         if (ctx != NULL) {
 443                 my_dbus_error_free (&error);
 444                 libhal_ctx_shutdown (ctx, &error);
 445                 libhal_ctx_free (ctx);
 446         }
 447 
 448         return 0;
 449 }