addon-storage.c revision 2912:85ea316d9c18
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
40static void
41my_dbus_error_free(DBusError *error)
42{
43	if (dbus_error_is_set(error)) {
44		dbus_error_free(error);
45	}
46}
47
48static void
49force_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
103out:
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
113static void
114unmount_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 */
152static dbus_bool_t
153is_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
174void
175close_device (int *fd)
176{
177	if (*fd > 0) {
178		close (*fd);
179		*fd = -1;
180	}
181}
182
183void
184drop_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
224int
225main (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
349out:
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