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
41static char			*udi;
42static char			*devfs_path;
43LibHalContext			*ctx = NULL;
44static sysevent_handle_t	*shp = NULL;
45
46static void	sysevent_dev_handler(sysevent_t *);
47
48static void
49my_dbus_error_free(DBusError *error)
50{
51	if (dbus_error_is_set(error)) {
52		dbus_error_free(error);
53	}
54}
55
56static void
57sysevent_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
75static void
76sysevent_fini ()
77{
78	if (shp != NULL) {
79		sysevent_unbind_handle (shp);
80		shp = NULL;
81	}
82}
83
84static void
85sysevent_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
131out:
132	nvlist_free(attr_list);
133}
134
135static void
136force_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
190out:
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
200static void
201unmount_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 */
239static dbus_bool_t
240is_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
261void
262close_device (int *fd)
263{
264	if (*fd > 0) {
265		close (*fd);
266		*fd = -1;
267	}
268}
269
270void
271drop_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
314int
315main (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
433out:
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