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