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 } 450