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
my_dbus_error_free(DBusError * error)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
sysevent_init()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
sysevent_fini()76 sysevent_fini ()
77 {
78 	if (shp != NULL) {
79 		sysevent_unbind_handle (shp);
80 		shp = NULL;
81 	}
82 }
83 
84 static void
sysevent_dev_handler(sysevent_t * ev)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
force_unmount(LibHalContext * ctx,const char * udi)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
unmount_childs(LibHalContext * ctx,const char * udi)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
is_mounted(const char * device_file)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
close_device(int * fd)262 close_device (int *fd)
263 {
264 	if (*fd > 0) {
265 		close (*fd);
266 		*fd = -1;
267 	}
268 }
269 
270 void
drop_privileges()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
main(int argc,char * argv[])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