xref: /illumos-gate/usr/src/cmd/rmvolmgr/rmvolmgr.c (revision b5a2d8455dfa3190fc977c4bec53e91c99012767)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 /*
27  * rmvolmgr daemon
28  */
29 
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <dirent.h>
35 #include <signal.h>
36 #include <unistd.h>
37 #include <fcntl.h>
38 #include <strings.h>
39 #include <errno.h>
40 #include <libintl.h>
41 #include <sys/syscall.h>
42 #include <libscf.h>
43 #include <priv_utils.h>
44 
45 #include <dbus/dbus.h>
46 #include <dbus/dbus-glib.h>
47 #include <dbus/dbus-glib-lowlevel.h>
48 #include <libhal.h>
49 
50 #include "rmm_common.h"
51 
52 char *progname = "rmvolmgr";
53 
54 #define	RMVOLMGR_FMRI	"svc:/system/filesystem/rmvolmgr:default"
55 
56 typedef struct managed_volume {
57 	char			*udi;
58 	boolean_t		my;
59 	struct action_arg	aa;
60 } managed_volume_t;
61 
62 static GSList		*managed_volumes;
63 
64 static GMainLoop	*mainloop;
65 static LibHalContext	*hal_ctx;
66 static int		sigexit_pipe[2];
67 static GIOChannel	*sigexit_ioch;
68 
69 static boolean_t	opt_c;	/* disable CDE compatibility */
70 static boolean_t	opt_n;	/* disable legacy mountpoint symlinks */
71 static boolean_t	opt_s;	/* system instance */
72 
73 /* SMF property "eject_button" */
74 static boolean_t	rmm_prop_eject_button = B_TRUE;
75 
76 static void	get_smf_properties();
77 static int	daemon(int nochdir, int noclose);
78 static void	rmm_device_added(LibHalContext *ctx, const char *udi);
79 static void	rmm_device_removed(LibHalContext *ctx, const char *udi);
80 static void	rmm_property_modified(LibHalContext *ctx, const char *udi,
81 		const char *key, dbus_bool_t is_removed, dbus_bool_t is_added);
82 static void	rmm_device_condition(LibHalContext *ctx, const char *udi,
83 		const char *name, const char *detail);
84 static void	rmm_mount_all();
85 static void	rmm_unmount_all();
86 static void	sigexit(int signo);
87 static gboolean	sigexit_ioch_func(GIOChannel *source, GIOCondition condition,
88 		gpointer user_data);
89 
90 static void
91 usage()
92 {
93 	(void) fprintf(stderr, gettext("\nusage: rmvolmgr [-v]\n"));
94 }
95 
96 static int
97 rmvolmgr(int argc, char **argv)
98 {
99 	const char	*opts = "chnsv";
100 	DBusError	error;
101 	boolean_t	daemonize;
102 	rmm_error_t	rmm_error;
103 	int		c;
104 
105 	while ((c = getopt(argc, argv, opts)) != EOF) {
106 		switch (c) {
107 		case 'c':
108 			opt_c = B_TRUE;
109 			break;
110 		case 'n':
111 			opt_n = B_TRUE;
112 			break;
113 		case 's':
114 			opt_s = B_TRUE;
115 			break;
116 		case 'v':
117 			rmm_debug = 1;
118 			break;
119 		case '?':
120 		case 'h':
121 			usage();
122 			return (0);
123 		default:
124 			usage();
125 			return (1);
126 		}
127 	}
128 
129 	if (opt_s) {
130 		if (geteuid() != 0) {
131 			(void) fprintf(stderr,
132 			    gettext("system instance must have euid 0\n"));
133 			return (1);
134 		}
135 
136 		get_smf_properties();
137 
138 		if (opt_c) {
139 			rmm_vold_actions_enabled = B_FALSE;
140 		}
141 		if (opt_n) {
142 			rmm_vold_mountpoints_enabled = B_FALSE;
143 		}
144 
145 
146 		/*
147 		 * Drop unused privileges. Remain root for HAL interaction
148 		 * and to create legacy symlinks.
149 		 *
150 		 * Need PRIV_FILE_DAC_WRITE to write to users'
151 		 * /tmp/.removable/notify* files.
152 		 */
153 		if (__init_daemon_priv(PU_RESETGROUPS|PU_CLEARLIMITSET,
154 		    0, 0,
155 		    rmm_vold_actions_enabled ? PRIV_FILE_DAC_WRITE : NULL,
156 		    NULL) == -1) {
157 			(void) fprintf(stderr,
158 			    gettext("failed to drop privileges"));
159 			return (1);
160 		}
161 		/* basic privileges we don't need */
162 		(void) priv_set(PRIV_OFF, PRIV_PERMITTED, PRIV_PROC_EXEC,
163 		    PRIV_PROC_INFO, PRIV_FILE_LINK_ANY, PRIV_PROC_SESSION,
164 		    NULL);
165 
166 	} else {
167 		if (opt_c) {
168 			rmm_vold_actions_enabled = B_FALSE;
169 		}
170 		if (opt_n) {
171 			rmm_vold_mountpoints_enabled = B_FALSE;
172 		}
173 	}
174 
175 	daemonize = (getenv("RMVOLMGR_NODAEMON") == NULL);
176 
177 	if (daemonize && daemon(0, 0) < 0) {
178 		dprintf("daemonizing failed: %s", strerror(errno));
179 		return (1);
180 	}
181 
182 	if (opt_s) {
183 		__fini_daemon_priv(PRIV_PROC_FORK, NULL);
184 	}
185 
186 	/*
187 	 * signal mainloop integration using pipes
188 	 */
189 	if (pipe(sigexit_pipe) != 0) {
190 		dprintf("pipe failed %s\n", strerror(errno));
191 		return (1);
192 	}
193 	sigexit_ioch = g_io_channel_unix_new(sigexit_pipe[0]);
194 	if (sigexit_ioch == NULL) {
195 		dprintf("g_io_channel_unix_new failed\n");
196 		return (1);
197 	}
198 	g_io_add_watch(sigexit_ioch, G_IO_IN, sigexit_ioch_func, NULL);
199 	signal(SIGTERM, sigexit);
200 	signal(SIGINT, sigexit);
201 	signal(SIGHUP, SIG_IGN);
202 	signal(SIGUSR1, SIG_IGN);
203 	signal(SIGUSR2, SIG_IGN);
204 
205 	if ((hal_ctx = rmm_hal_init(rmm_device_added, rmm_device_removed,
206 	    rmm_property_modified, rmm_device_condition,
207 	    &error, &rmm_error)) == NULL) {
208 		dbus_error_free(&error);
209 		return (1);
210 	}
211 
212 	/* user instance should claim devices */
213 	if (!opt_s) {
214 		if (!rmm_hal_claim_branch(hal_ctx, HAL_BRANCH_LOCAL)) {
215 			(void) fprintf(stderr,
216 			    gettext("cannot claim branch\n"));
217 			return (1);
218 		}
219 	}
220 
221 	rmm_mount_all();
222 
223 	if ((mainloop = g_main_loop_new(NULL, B_FALSE)) == NULL) {
224 		dprintf("Cannot create main loop\n");
225 		return (1);
226 	}
227 
228 	g_main_loop_run(mainloop);
229 
230 	return (0);
231 }
232 
233 static void
234 get_smf_properties()
235 {
236 	scf_simple_prop_t *prop;
237 	uint8_t *val;
238 
239 	if ((prop = scf_simple_prop_get(NULL, RMVOLMGR_FMRI,
240 	    "rmvolmgr", "legacy_mountpoints")) != NULL) {
241 		if ((val = scf_simple_prop_next_boolean(prop)) != NULL) {
242 			rmm_vold_mountpoints_enabled = (*val != 0);
243 		}
244 		scf_simple_prop_free(prop);
245 	}
246 
247 	if ((prop = scf_simple_prop_get(NULL, RMVOLMGR_FMRI,
248 	    "rmvolmgr", "cde_compatible")) != NULL) {
249 		if ((val = scf_simple_prop_next_boolean(prop)) != NULL) {
250 			rmm_vold_actions_enabled = (*val != 0);
251 		}
252 		scf_simple_prop_free(prop);
253 	}
254 
255 	if ((prop = scf_simple_prop_get(NULL, RMVOLMGR_FMRI,
256 	    "rmvolmgr", "eject_button")) != NULL) {
257 		if ((val = scf_simple_prop_next_boolean(prop)) != NULL) {
258 			rmm_prop_eject_button = (*val != 0);
259 		}
260 		scf_simple_prop_free(prop);
261 	}
262 }
263 
264 /* ARGSUSED */
265 static void
266 sigexit(int signo)
267 {
268 	dprintf("signal to exit %d\n", signo);
269 
270 	write(sigexit_pipe[1], "s", 1);
271 }
272 
273 /* ARGSUSED */
274 static gboolean
275 sigexit_ioch_func(GIOChannel *source, GIOCondition condition,
276     gpointer user_data)
277 {
278 	gchar	buf[1];
279 	gsize	bytes_read;
280 	GError	*error = NULL;
281 
282 	if (g_io_channel_read_chars(source, buf, 1, &bytes_read, &error) !=
283 	    G_IO_STATUS_NORMAL) {
284 		dprintf("g_io_channel_read_chars failed %s", error->message);
285 		g_error_free(error);
286 		return (TRUE);
287 	}
288 
289 	dprintf("signal to exit\n");
290 
291 	rmm_unmount_all();
292 
293 	g_main_loop_quit(mainloop);
294 
295 	return (TRUE);
296 }
297 
298 static managed_volume_t *
299 rmm_managed_alloc(LibHalContext *ctx, const char *udi)
300 {
301 	managed_volume_t *v;
302 
303 	if ((v = calloc(1, sizeof (managed_volume_t))) == NULL) {
304 		return (NULL);
305 	}
306 	if ((v->udi = strdup(udi)) == NULL) {
307 		free(v);
308 		return (NULL);
309 	}
310 	if (!rmm_volume_aa_from_prop(ctx, udi, NULL, &v->aa)) {
311 		free(v->udi);
312 		free(v);
313 		return (NULL);
314 	}
315 
316 	return (v);
317 }
318 
319 static void
320 rmm_managed_free(managed_volume_t *v)
321 {
322 	rmm_volume_aa_free(&v->aa);
323 	free(v->udi);
324 	free(v);
325 }
326 
327 static gint
328 rmm_managed_compare_udi(gconstpointer a, gconstpointer b)
329 {
330 	const managed_volume_t *va = a;
331 	const char *udi = b;
332 
333 	return (strcmp(va->udi, udi));
334 }
335 
336 static boolean_t
337 volume_should_mount(const char *udi)
338 {
339 	char	*storage_device = NULL;
340 	int	ret = B_FALSE;
341 
342 	if (libhal_device_get_property_bool(hal_ctx, udi,
343 	    "volume.ignore", NULL)) {
344 		goto out;
345 	}
346 
347 	/* get the backing storage device */
348 	if (!(storage_device = libhal_device_get_property_string(hal_ctx, udi,
349 	    "block.storage_device", NULL))) {
350 		dprintf("cannot get block.storage_device\n");
351 		goto out;
352 	}
353 
354 	/* we handle either removable or hotpluggable */
355 	if (!libhal_device_get_property_bool(hal_ctx, storage_device,
356 	    "storage.removable", NULL) &&
357 	    !libhal_device_get_property_bool(hal_ctx, storage_device,
358 	    "storage.hotpluggable", NULL)) {
359 		goto out;
360 	}
361 
362 	/* ignore if claimed by another volume manager */
363 	if (libhal_device_get_property_bool(hal_ctx, storage_device,
364 	    "info.claimed", NULL)) {
365 		goto out;
366 	}
367 
368 	ret = B_TRUE;
369 
370 out:
371 	libhal_free_string(storage_device);
372 	return (ret);
373 }
374 
375 static void
376 volume_added(const char *udi)
377 {
378 	GSList		*l;
379 	managed_volume_t *v;
380 
381 	dprintf("volume added %s\n", udi);
382 
383 	l = g_slist_find_custom(managed_volumes, udi, rmm_managed_compare_udi);
384 	v = (l != NULL) ? l->data : NULL;
385 
386 	if (v != NULL) {
387 		dprintf("already managed %s\n", udi);
388 		return;
389 	}
390 	if (!volume_should_mount(udi)) {
391 		dprintf("should not mount %s\n", udi);
392 		return;
393 	}
394 	if ((v = rmm_managed_alloc(hal_ctx, udi)) == NULL) {
395 		return;
396 	}
397 	if (rmm_action(hal_ctx, udi, INSERT, &v->aa, 0, 0, 0)) {
398 		v->my = B_TRUE;
399 		managed_volumes = g_slist_prepend(managed_volumes, v);
400 	} else {
401 		dprintf("rmm_action failed %s\n", udi);
402 		rmm_managed_free(v);
403 	}
404 }
405 
406 static void
407 volume_removed(const char *udi)
408 {
409 	GSList		*l;
410 	managed_volume_t *v;
411 
412 	dprintf("volume removed %s\n", udi);
413 
414 	l = g_slist_find_custom(managed_volumes, udi, rmm_managed_compare_udi);
415 	v = (l != NULL) ? l->data : NULL;
416 	if (v == NULL) {
417 		return;
418 	}
419 
420 	/* HAL will unmount, just do the vold legacy stuff */
421 	v->aa.aa_action = EJECT;
422 	(void) vold_postprocess(hal_ctx, udi, &v->aa);
423 
424 	rmm_managed_free(v);
425 	managed_volumes = g_slist_delete_link(managed_volumes, l);
426 }
427 
428 /* ARGSUSED */
429 static void
430 rmm_device_added(LibHalContext *ctx, const char *udi)
431 {
432 	if (libhal_device_query_capability(hal_ctx, udi, "volume", NULL)) {
433 		volume_added(udi);
434 	}
435 }
436 
437 /* ARGSUSED */
438 static void
439 rmm_device_removed(LibHalContext *ctx, const char *udi)
440 {
441 	if (libhal_device_query_capability(hal_ctx, udi, "volume", NULL)) {
442 		volume_removed(udi);
443 	}
444 }
445 
446 /* ARGSUSED */
447 static void
448 rmm_property_modified(LibHalContext *ctx, const char *udi, const char *key,
449     dbus_bool_t is_removed, dbus_bool_t is_added)
450 {
451 	DBusError		error;
452 	GSList			*l;
453 	managed_volume_t	*v;
454 	boolean_t		is_mounted;
455 
456 	if (strcmp(key, "volume.is_mounted") != 0) {
457 		return;
458 	}
459 	is_mounted = libhal_device_get_property_bool(hal_ctx, udi, key, NULL);
460 
461 	l = g_slist_find_custom(managed_volumes, udi, rmm_managed_compare_udi);
462 	v = (l != NULL) ? l->data : NULL;
463 
464 	if (is_mounted) {
465 		dprintf("Mounted: %s\n", udi);
466 
467 		if (v != NULL) {
468 			/* volume mounted by us is already taken care of */
469 			if (v->my) {
470 				return;
471 			}
472 		} else {
473 			if ((v = rmm_managed_alloc(ctx, udi)) == NULL) {
474 				return;
475 			}
476 			managed_volumes = g_slist_prepend(managed_volumes, v);
477 		}
478 
479 		v->aa.aa_action = INSERT;
480 		(void) vold_postprocess(hal_ctx, udi, &v->aa);
481 
482 	} else {
483 		dprintf("Unmounted: %s\n", udi);
484 
485 		if (v == NULL) {
486 			return;
487 		}
488 
489 		v->aa.aa_action = EJECT;
490 		(void) vold_postprocess(hal_ctx, udi, &v->aa);
491 
492 		rmm_managed_free(v);
493 		managed_volumes = g_slist_delete_link(managed_volumes, l);
494 	}
495 }
496 
497 static void
498 storage_eject_pressed(const char *udi)
499 {
500 	DBusError	error;
501 
502 	/* ignore if disabled via SMF or claimed by another volume manager */
503 	if (!rmm_prop_eject_button ||
504 	    libhal_device_get_property_bool(hal_ctx, udi, "info.claimed",
505 	    NULL)) {
506 		return;
507 	}
508 
509 	dbus_error_init(&error);
510 	(void) rmm_hal_eject(hal_ctx, udi, &error);
511 	rmm_dbus_error_free(&error);
512 }
513 
514 /* ARGSUSED */
515 static void
516 rmm_device_condition(LibHalContext *ctx, const char *udi,
517     const char *name, const char *detail)
518 {
519 	if ((strcmp(name, "EjectPressed") == 0) &&
520 	    libhal_device_query_capability(hal_ctx, udi, "storage", NULL)) {
521 		storage_eject_pressed(udi);
522 	}
523 }
524 
525 /*
526  * Mount all mountable volumes
527  */
528 static void
529 rmm_mount_all()
530 {
531 	DBusError	error;
532 	char		**udis = NULL;
533 	int		num_udis;
534 	int		i;
535 	managed_volume_t *v;
536 
537 	dbus_error_init(&error);
538 
539 	/* get all volumes */
540 	if ((udis = libhal_find_device_by_capability(hal_ctx, "volume",
541 	    &num_udis, &error)) == NULL) {
542 		dprintf("mount_all: no volumes found\n");
543 		goto out;
544 	}
545 
546 	for (i = 0; i < num_udis; i++) {
547 		/* skip if already mounted */
548 		if (libhal_device_get_property_bool(hal_ctx, udis[i],
549 		    "volume.is_mounted", NULL)) {
550 			dprintf("mount_all: %s already mounted\n", udis[i]);
551 			continue;
552 		}
553 		if (!volume_should_mount(udis[i])) {
554 			continue;
555 		}
556 		if ((v = rmm_managed_alloc(hal_ctx, udis[i])) == NULL) {
557 			continue;
558 		}
559 		if (rmm_action(hal_ctx, udis[i], INSERT, &v->aa, 0, 0, 0)) {
560 			v->my = B_TRUE;
561 			managed_volumes = g_slist_prepend(managed_volumes, v);
562 		} else {
563 			rmm_managed_free(v);
564 		}
565 	}
566 
567 out:
568 	if (udis != NULL) {
569 		libhal_free_string_array(udis);
570 	}
571 	rmm_dbus_error_free(&error);
572 }
573 
574 /*
575  * Mount all volumes mounted by this program
576  */
577 static void
578 rmm_unmount_all()
579 {
580 	GSList		*i;
581 	managed_volume_t *v;
582 
583 	for (i = managed_volumes; i != NULL; i = managed_volumes) {
584 		v = (managed_volume_t *)i->data;
585 
586 		if (v->my && libhal_device_get_property_bool(hal_ctx, v->udi,
587 		    "volume.is_mounted", NULL)) {
588 			(void) rmm_action(hal_ctx, v->udi, UNMOUNT,
589 			    &v->aa, 0, 0, 0);
590 		}
591 
592 		managed_volumes = g_slist_remove(managed_volumes, v);
593 		rmm_managed_free(v);
594 	}
595 }
596 
597 static int
598 daemon(int nochdir, int noclose)
599 {
600 	int fd;
601 
602 	switch (fork()) {
603 	case -1:
604 		return (-1);
605 	case 0:
606 		break;
607 	default:
608 		exit(0);
609 	}
610 
611 	if (setsid() == -1)
612 		return (-1);
613 
614 	if (!nochdir)
615 		(void) chdir("/");
616 
617 	if (!noclose) {
618 		struct stat64 st;
619 
620 		if (((fd = open("/dev/null", O_RDWR, 0)) != -1) &&
621 		    (fstat64(fd, &st) == 0)) {
622 			if (S_ISCHR(st.st_mode) != 0) {
623 				(void) dup2(fd, STDIN_FILENO);
624 				(void) dup2(fd, STDOUT_FILENO);
625 				(void) dup2(fd, STDERR_FILENO);
626 				if (fd > 2)
627 					(void) close(fd);
628 			} else {
629 				(void) close(fd);
630 				(void) __set_errno(ENODEV);
631 				return (-1);
632 			}
633 		} else {
634 			(void) close(fd);
635 			return (-1);
636 		}
637 	}
638 	return (0);
639 }
640 
641 int
642 main(int argc, char **argv)
643 {
644 	vold_init(argc, argv);
645 
646 	return (rmvolmgr(argc, argv));
647 }
648