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