xref: /illumos-gate/usr/src/cmd/hal/addons/storage/addon-storage.c (revision 2983dda76a6d296fdb560c88114fe41caad1b84f)
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