1 /*************************************************************************** 2 * 3 * addon-storage.c : watch removable media state changes 4 * 5 * Copyright 2006 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 #pragma ident "%Z%%M% %I% %E% SMI" 13 14 #ifdef HAVE_CONFIG_H 15 # include <config.h> 16 #endif 17 18 #include <errno.h> 19 #include <string.h> 20 #include <strings.h> 21 #include <stdlib.h> 22 #include <stdio.h> 23 #include <sys/ioctl.h> 24 #include <sys/types.h> 25 #include <sys/stat.h> 26 #include <sys/types.h> 27 #include <sys/wait.h> 28 #include <fcntl.h> 29 #include <unistd.h> 30 #include <sys/mnttab.h> 31 #include <sys/dkio.h> 32 #include <priv.h> 33 34 #include <libhal.h> 35 36 #include "../../hald/logger.h" 37 38 #define SLEEP_PERIOD 5 39 40 static void 41 my_dbus_error_free(DBusError *error) 42 { 43 if (dbus_error_is_set(error)) { 44 dbus_error_free(error); 45 } 46 } 47 48 static void 49 force_unmount (LibHalContext *ctx, const char *udi) 50 { 51 DBusError error; 52 DBusMessage *msg = NULL; 53 DBusMessage *reply = NULL; 54 char **options = NULL; 55 unsigned int num_options = 0; 56 DBusConnection *dbus_connection; 57 char *device_file; 58 59 dbus_error_init (&error); 60 61 dbus_connection = libhal_ctx_get_dbus_connection (ctx); 62 63 msg = dbus_message_new_method_call ("org.freedesktop.Hal", udi, 64 "org.freedesktop.Hal.Device.Volume", 65 "Unmount"); 66 if (msg == NULL) { 67 HAL_DEBUG (("Could not create dbus message for %s", udi)); 68 goto out; 69 } 70 71 72 options = calloc (1, sizeof (char *)); 73 if (options == NULL) { 74 HAL_DEBUG (("Could not allocate options array")); 75 goto out; 76 } 77 78 device_file = libhal_device_get_property_string (ctx, udi, "block.device", &error); 79 if (device_file != NULL) { 80 libhal_free_string (device_file); 81 } 82 dbus_error_free (&error); 83 84 if (!dbus_message_append_args (msg, 85 DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &options, num_options, 86 DBUS_TYPE_INVALID)) { 87 HAL_DEBUG (("Could not append args to dbus message for %s", udi)); 88 goto out; 89 } 90 91 if (!(reply = dbus_connection_send_with_reply_and_block (dbus_connection, msg, -1, &error))) { 92 HAL_DEBUG (("Unmount failed for %s: %s : %s\n", udi, error.name, error.message)); 93 goto out; 94 } 95 96 if (dbus_error_is_set (&error)) { 97 HAL_DEBUG (("Unmount failed for %s\n%s : %s\n", udi, error.name, error.message)); 98 goto out; 99 } 100 101 HAL_DEBUG (("Succesfully unmounted udi '%s'", udi)); 102 103 out: 104 dbus_error_free (&error); 105 if (options != NULL) 106 free (options); 107 if (msg != NULL) 108 dbus_message_unref (msg); 109 if (reply != NULL) 110 dbus_message_unref (reply); 111 } 112 113 static void 114 unmount_childs (LibHalContext *ctx, const char *udi) 115 { 116 DBusError error; 117 int num_volumes; 118 char **volumes; 119 120 dbus_error_init (&error); 121 122 /* need to force unmount all partitions */ 123 if ((volumes = libhal_manager_find_device_string_match ( 124 ctx, "block.storage_device", udi, &num_volumes, &error)) != NULL) { 125 dbus_error_free (&error); 126 int i; 127 128 for (i = 0; i < num_volumes; i++) { 129 char *vol_udi; 130 131 vol_udi = volumes[i]; 132 if (libhal_device_get_property_bool (ctx, vol_udi, "block.is_volume", &error)) { 133 dbus_error_free (&error); 134 if (libhal_device_get_property_bool (ctx, vol_udi, "volume.is_mounted", &error)) { 135 dbus_error_free (&error); 136 HAL_DEBUG (("Forcing unmount of child '%s'", vol_udi)); 137 force_unmount (ctx, vol_udi); 138 } 139 } 140 } 141 libhal_free_string_array (volumes); 142 } 143 my_dbus_error_free (&error); 144 } 145 146 /** Check if a filesystem on a special device file is mounted 147 * 148 * @param device_file Special device file, e.g. /dev/cdrom 149 * @return TRUE iff there is a filesystem system mounted 150 * on the special device file 151 */ 152 static dbus_bool_t 153 is_mounted (const char *device_file) 154 { 155 FILE *f; 156 dbus_bool_t rc = FALSE; 157 struct mnttab mp; 158 struct mnttab mpref; 159 160 if ((f = fopen ("/etc/mnttab", "r")) == NULL) 161 return rc; 162 163 bzero(&mp, sizeof (mp)); 164 bzero(&mpref, sizeof (mpref)); 165 mpref.mnt_special = (char *)device_file; 166 if (getmntany(f, &mp, &mpref) == 0) { 167 rc = TRUE; 168 } 169 170 fclose (f); 171 return rc; 172 } 173 174 void 175 close_device (int *fd) 176 { 177 if (*fd > 0) { 178 close (*fd); 179 *fd = -1; 180 } 181 } 182 183 void 184 drop_privileges () 185 { 186 priv_set_t *pPrivSet = NULL; 187 priv_set_t *lPrivSet = NULL; 188 189 /* 190 * Start with the 'basic' privilege set and then remove any 191 * of the 'basic' privileges that will not be needed. 192 */ 193 if ((pPrivSet = priv_str_to_set("basic", ",", NULL)) == NULL) { 194 return; 195 } 196 197 /* Clear privileges we will not need from the 'basic' set */ 198 (void) priv_delset(pPrivSet, PRIV_FILE_LINK_ANY); 199 (void) priv_delset(pPrivSet, PRIV_PROC_INFO); 200 (void) priv_delset(pPrivSet, PRIV_PROC_SESSION); 201 202 /* to open logindevperm'd devices */ 203 (void) priv_addset(pPrivSet, PRIV_FILE_DAC_READ); 204 205 /* Set the permitted privilege set. */ 206 if (setppriv(PRIV_SET, PRIV_PERMITTED, pPrivSet) != 0) { 207 return; 208 } 209 210 /* Clear the limit set. */ 211 if ((lPrivSet = priv_allocset()) == NULL) { 212 return; 213 } 214 215 priv_emptyset(lPrivSet); 216 217 if (setppriv(PRIV_SET, PRIV_LIMIT, lPrivSet) != 0) { 218 return; 219 } 220 221 priv_freeset(lPrivSet); 222 } 223 224 int 225 main (int argc, char *argv[]) 226 { 227 char *udi; 228 char *device_file, *raw_device_file; 229 LibHalContext *ctx = NULL; 230 DBusError error; 231 char *bus; 232 char *drive_type; 233 int state, last_state; 234 char *support_media_changed_str; 235 int support_media_changed; 236 int fd = -1; 237 238 if ((udi = getenv ("UDI")) == NULL) 239 goto out; 240 if ((device_file = getenv ("HAL_PROP_BLOCK_DEVICE")) == NULL) 241 goto out; 242 if ((raw_device_file = getenv ("HAL_PROP_BLOCK_SOLARIS_RAW_DEVICE")) == NULL) 243 goto out; 244 if ((bus = getenv ("HAL_PROP_STORAGE_BUS")) == NULL) 245 goto out; 246 if ((drive_type = getenv ("HAL_PROP_STORAGE_DRIVE_TYPE")) == NULL) 247 goto out; 248 249 drop_privileges (); 250 251 setup_logger (); 252 253 support_media_changed_str = getenv ("HAL_PROP_STORAGE_CDROM_SUPPORT_MEDIA_CHANGED"); 254 if (support_media_changed_str != NULL && strcmp (support_media_changed_str, "true") == 0) 255 support_media_changed = TRUE; 256 else 257 support_media_changed = FALSE; 258 259 dbus_error_init (&error); 260 261 if ((ctx = libhal_ctx_init_direct (&error)) == NULL) { 262 goto out; 263 } 264 my_dbus_error_free (&error); 265 266 if (!libhal_device_addon_is_ready (ctx, udi, &error)) { 267 goto out; 268 } 269 my_dbus_error_free (&error); 270 271 printf ("Doing addon-storage for %s (bus %s) (drive_type %s) (udi %s)\n", device_file, bus, drive_type, udi); 272 273 last_state = state = DKIO_NONE; 274 275 /* Linux version of this addon attempts to re-open the device O_EXCL 276 * every 2 seconds, trying to figure out if some other app, 277 * like a cd burner, is using the device. Aside from questionable 278 * value of this (apps should use HAL's locked property or/and 279 * Solaris in_use facility), but also frequent opens/closes 280 * keeps media constantly spun up. All this needs more thought. 281 */ 282 for (;;) { 283 if (is_mounted (device_file)) { 284 close_device (&fd); 285 sleep (SLEEP_PERIOD); 286 } else if ((fd < 0) && ((fd = open (raw_device_file, O_RDONLY | O_NONBLOCK)) < 0)) { 287 HAL_DEBUG (("open failed for %s: %s", raw_device_file, strerror (errno))); 288 sleep (SLEEP_PERIOD); 289 } else { 290 /* Check if a disc is in the drive */ 291 /* XXX initial call always returns inserted 292 * causing unnecessary rescan - optimize? 293 */ 294 if (ioctl (fd, DKIOCSTATE, &state) == 0) { 295 if (state == last_state) { 296 HAL_DEBUG (("state has not changed %d %s", state, device_file)); 297 continue; 298 } else { 299 HAL_DEBUG (("new state %d %s", state, device_file)); 300 } 301 302 switch (state) { 303 case DKIO_EJECTED: 304 HAL_DEBUG (("Media removal detected on %s", device_file)); 305 last_state = state; 306 307 libhal_device_set_property_bool (ctx, udi, "storage.removable.media_available", FALSE, &error); 308 my_dbus_error_free (&error); 309 310 /* attempt to unmount all childs */ 311 unmount_childs (ctx, udi); 312 313 /* could have a fs on the main block device; do a rescan to remove it */ 314 libhal_device_rescan (ctx, udi, &error); 315 my_dbus_error_free (&error); 316 break; 317 318 case DKIO_INSERTED: 319 HAL_DEBUG (("Media insertion detected on %s", device_file)); 320 last_state = state; 321 322 libhal_device_set_property_bool (ctx, udi, "storage.removable.media_available", TRUE, &error); 323 my_dbus_error_free (&error); 324 325 /* could have a fs on the main block device; do a rescan to add it */ 326 libhal_device_rescan (ctx, udi, &error); 327 my_dbus_error_free (&error); 328 break; 329 330 case DKIO_DEV_GONE: 331 HAL_DEBUG (("Device gone detected on %s", device_file)); 332 last_state = state; 333 334 unmount_childs (ctx, udi); 335 close_device (&fd); 336 goto out; 337 338 case DKIO_NONE: 339 default: 340 break; 341 } 342 } else { 343 HAL_DEBUG (("DKIOCSTATE failed: %s\n", strerror(errno))); 344 sleep (SLEEP_PERIOD); 345 } 346 } 347 } 348 349 out: 350 if (ctx != NULL) { 351 my_dbus_error_free (&error); 352 libhal_ctx_shutdown (ctx, &error); 353 libhal_ctx_free (ctx); 354 } 355 356 return 0; 357 } 358