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