xref: /illumos-gate/usr/src/cmd/rmvolmgr/vold.c (revision 581cede61ac9c14d8d4ea452562a567189eead78)
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 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 /*
29  * Vold compatibility for rmvolmgr: emulate old commands as well as
30  * action_filemgr.so to notify legacy apps via /tmp/.removable pipes.
31  * A lot of this code is copied verbatim from vold sources.
32  *
33  * Here's the original description of action_filemgr.so:
34  *
35  * action_filemgr.so - filemgr interface routines for rmmount
36  *
37  * This shared object allows rmmount to communicate with filemgr.
38  * This is done by communicating over a named pipe that filemgr
39  * creates in directory NOTIFY_DIR.  The name of the pipe must
40  * begin with NOTIFY_NAME.  This source file contains #define
41  * compiler directives set the values of NOTIFY_DIR and NOTIFY_NAME.
42  *
43  * After a partition on a medium has been mounted as a result of
44  * either insertion or remounting of the medium, the action()
45  * method creates a file named with the symbolic name of the
46  * device in which the medium is inserted and the partition name
47  * (e.g. "jaz0-s2") in NOTIFY_DIR.  The file consists of one text
48  * line containing a string naming the mount point of the partition,
49  * a string giving the raw device path to the partition, and a
50  * string naming the file system type on the partition.  The action()
51  * method then sends a single character ('i' for insertion, 'r' for
52  * remounting) through the named pipe NOTIFY_NAME to tell filemgr to
53  * look for new files in NOTIFY_DIR.
54  *
55  * If a medium containing no mountable partitions is inserted
56  * or remounted in a device, the action() method creates a file
57  * named with the symbolic name of the device in NOTIFY_DIR.
58  * The file consists of one text line containing a string
59  * giving the symbolic name of the device and a string naming
60  * the reason that the medium couldn't be mounted.  The action
61  * method then sends either an 'i' or an 'r' through the named
62  * pipe to tell filemgr to look for new files in NOTIFY_DIR.
63  *
64  * When a medium is ejected or unmounted, the action() method
65  * removes the files that were created in NOTIFY_DIR when the medium
66  * was inserted or remounted and sends a single character ('e' for
67  * ejection, 'u' for unmounting) through the named pipe.
68  *
69  * The following environment variables must be set before calling action():
70  *
71  *	VOLUME_ACTION		action that occurred (e.g. "insert", "eject")
72  *	VOLUME_SYMDEV		symbolic name (e.g. "cdrom0", "floppy1")
73  *	VOLUME_NAME		volume name (e.g. "unnamed_cdrom", "s2")
74  */
75 
76 
77 #include <stdio.h>
78 #include <stdlib.h>
79 #include <unistd.h>
80 #include <fcntl.h>
81 #include <string.h>
82 #include <strings.h>
83 #include <dirent.h>
84 #include <signal.h>
85 #include <errno.h>
86 #include <libintl.h>
87 #include <zone.h>
88 #include <pwd.h>
89 #include <sys/types.h>
90 #include <sys/stat.h>
91 #include <sys/dkio.h>
92 #include <sys/cdio.h>
93 #include <sys/vtoc.h>
94 #include <sys/param.h>
95 #include <sys/wait.h>
96 #include <libcontract.h>
97 #include <sys/contract/process.h>
98 #include <sys/ctfs.h>
99 #include <tsol/label.h>
100 
101 #include "vold.h"
102 #include "rmm_common.h"
103 
104 int		rmm_debug = 0;
105 boolean_t	rmm_vold_actions_enabled = B_FALSE;
106 boolean_t	rmm_vold_mountpoints_enabled = B_FALSE;
107 
108 static char	*prog_name = NULL;
109 static pid_t	prog_pid = 0;
110 static int	system_labeled = 0;
111 static uid_t	mnt_uid = (uid_t)-1;
112 static gid_t	mnt_gid = (gid_t)-1;
113 static zoneid_t	mnt_zoneid = -1;
114 static char	mnt_zoneroot[MAXPATHLEN];
115 static char	mnt_userdir[MAXPATHLEN];
116 
117 /*
118  * Private attribute types and attributes.
119  */
120 static const char notify_characters[] = {
121 	'e',
122 	'i',
123 	'r',
124 	'u'
125 };
126 
127 static const char *result_strings[] = {
128 	"FALSE",
129 	"TRUE"
130 };
131 
132 #define	NOTIFY_DIR	"/tmp/.removable"	/* dir where filemgr looks */
133 #define	NOTIFY_NAME	"notify"		/* named pipe to talk over */
134 
135 static void	volrmmount_usage();
136 static void	volcheck_usage();
137 static int	vold_action(struct action_arg *aap);
138 static void	vold_update_mountpoints(struct action_arg *aap);
139 static char	*not_mountable(struct action_arg *aa);
140 static int	create_one_notify_file(char *fstype,
141 				char *mount_point,
142 				char *notify_file,
143 				char *raw_partitionp,
144 				char *reason,
145 				char *symdev);
146 static int	create_notify_files(struct action_arg **aa);
147 static boolean_t notify_clients(action_t action, int do_notify);
148 static void	popdir(int fd);
149 static int	pushdir(const char *dir);
150 static boolean_t remove_notify_files(struct action_arg **aa);
151 
152 /*
153  * should be called once from main()
154  */
155 /* ARGSUSED */
156 void
157 vold_init(int argc, char **argv)
158 {
159 	system_labeled = is_system_labeled();
160 }
161 
162 /*
163  * Old version of rmmount(1M)
164  */
165 /* ARGSUSED */
166 int
167 vold_rmmount(int argc, char **argv)
168 {
169 	char		*volume_action;
170 	char		*volume_mediatype;
171 	char		*volume_mount_mode;
172 	char		*volume_name;
173 	char		*volume_path;
174 	char		*volume_pcfs_id;
175 	char		*volume_symdev;
176 	char		*volume_zonename;
177 	char		*volume_user;
178 	action_t	action;
179 	char		mountpoint[MAXPATHLEN];
180 	char		*zonemountpoint;
181 	char		*arg_mountpoint = NULL;
182 	LibHalContext	*hal_ctx;
183 	DBusError	error;
184 	rmm_error_t	rmm_error;
185 	int		ret;
186 
187 	prog_name = argv[0];
188 	prog_pid = getpid();
189 
190 	mnt_zoneroot[0] = '\0';
191 	mnt_userdir[0] = '\0';
192 
193 	volume_action = getenv("VOLUME_ACTION");
194 	volume_mediatype = getenv("VOLUME_MEDIATYPE");
195 	volume_mount_mode = getenv("VOLUME_MOUNT_MODE");
196 	volume_name = getenv("VOLUME_NAME");
197 	volume_path = getenv("VOLUME_PATH");
198 	volume_pcfs_id = getenv("VOLUME_PCFS_ID");
199 	volume_symdev = getenv("VOLUME_SYMDEV");
200 
201 	if (system_labeled) {
202 		volume_zonename = getenv("VOLUME_ZONE_NAME");
203 		volume_user = getenv("VOLUME_USER");
204 	}
205 	if (volume_action == NULL) {
206 		dprintf("%s(%ld): VOLUME_ACTION was null!!\n",
207 		    prog_name, prog_pid);
208 		return (-1);
209 	}
210 	if (volume_mediatype == NULL) {
211 		dprintf("%s(%ld): VOLUME_MEDIATYPE was null!!\n",
212 		    prog_name, prog_pid);
213 		return (-1);
214 	}
215 	if (volume_mount_mode == NULL) {
216 		volume_mount_mode = "rw";
217 	}
218 	if (volume_name == NULL) {
219 		dprintf("%s(%ld): VOLUME_NAME was null!!\n",
220 		    prog_name, prog_pid);
221 		return (-1);
222 	}
223 	if (volume_path == NULL) {
224 		dprintf("%s(%ld): VOLUME_PATH was null!!\n",
225 		    prog_name, prog_pid);
226 		return (-1);
227 	}
228 	if (volume_pcfs_id == NULL) {
229 		volume_pcfs_id = "";
230 	}
231 	if (volume_symdev == NULL) {
232 		dprintf("%s(%ld): VOLUME_SYMDEV was null!!\n",
233 		    prog_name, prog_pid);
234 		return (-1);
235 	}
236 
237 	if (system_labeled) {
238 		if (volume_zonename != NULL &&
239 		    strcmp(volume_zonename, GLOBAL_ZONENAME) != 0) {
240 			if ((mnt_zoneid =
241 			    getzoneidbyname(volume_zonename)) != -1) {
242 				if (zone_getattr(mnt_zoneid, ZONE_ATTR_ROOT,
243 				    mnt_zoneroot, MAXPATHLEN) == -1) {
244 					dprintf("%s(%ld): NO ZONEPATH!!\n",
245 					    prog_name, prog_pid);
246 					return (-1);
247 				}
248 			}
249 		} else {
250 			mnt_zoneid = GLOBAL_ZONEID;
251 			mnt_zoneroot[0] = '\0';
252 		}
253 		if (volume_user != NULL) {
254 			struct passwd	 *pw;
255 
256 			if ((pw = getpwnam(volume_user)) == NULL) {
257 				dprintf("%s(%ld) %s\n", prog_name, prog_pid,
258 				    ": VOLUME_USER was not a valid user!");
259 				return (-1);
260 			}
261 			mnt_uid = pw->pw_uid;
262 			mnt_gid = pw->pw_gid;
263 
264 			if (snprintf(mnt_userdir, sizeof (mnt_userdir),
265 			    "/%s-%s", volume_user, volume_symdev) >=
266 			    sizeof (mnt_userdir))
267 				return (-1);
268 		} else {
269 			mnt_uid = 0;
270 			mnt_userdir[0] = '\0';
271 		}
272 
273 		rmm_vold_mountpoints_enabled = B_FALSE;
274 		rmm_vold_actions_enabled = B_TRUE;
275 	} else {
276 		rmm_vold_mountpoints_enabled = B_TRUE;
277 		rmm_vold_actions_enabled = B_TRUE;
278 	}
279 
280 	if ((hal_ctx = rmm_hal_init(0, 0, 0, &error, &rmm_error)) == NULL) {
281 		rmm_dbus_error_free(&error);
282 
283 		/* if HAL's not running, must be root */
284 		if (geteuid() != 0) {
285 			(void) fprintf(stderr,
286 			    gettext("%s(%ld) error: must be root to execute\n"),
287 			    prog_name, prog_pid);
288 			return (-1);
289 		}
290 	}
291 
292 	if (strcmp(volume_action, "eject") == 0) {
293 		action = EJECT;
294 	} else if (strcmp(volume_action, "insert") == 0) {
295 		action = INSERT;
296 
297 		if (system_labeled) {
298 			/*
299 			 * create mount point
300 			 */
301 			if (strlen(mnt_userdir) > 0) {
302 				if (snprintf(mountpoint, MAXPATHLEN,
303 				    "%s/%s%s", mnt_zoneroot, volume_mediatype,
304 				    mnt_userdir) > MAXPATHLEN) {
305 					return (-1);
306 
307 				}
308 				(void) makepath(mountpoint, 0700);
309 				(void) chown(mountpoint, mnt_uid, mnt_gid);
310 				/*
311 				 * set the top level directory bits to 0755
312 				 * so user can access it.
313 				 */
314 				if (snprintf(mountpoint, MAXPATHLEN,
315 				    "%s/%s", mnt_zoneroot,
316 				    volume_mediatype) <= MAXPATHLEN) {
317 					(void) chmod(mountpoint, 0755);
318 				}
319 			}
320 			if (snprintf(mountpoint, MAXPATHLEN,
321 			    "%s/%s%s/%s", mnt_zoneroot, volume_mediatype,
322 			    mnt_userdir, volume_name) > MAXPATHLEN) {
323 				(void) fprintf(stderr,
324 				    gettext("%s(%ld) error: path too long\n"),
325 				    prog_name, prog_pid);
326 				return (-1);
327 			}
328 
329 			/* make our mountpoint */
330 			(void) makepath(mountpoint, 0755);
331 
332 			arg_mountpoint = mountpoint;
333 		}
334 	} else if (strcmp(volume_action, "remount") == 0) {
335 		action = REMOUNT;
336 	} else if (strcmp(volume_action, "unmount") == 0) {
337 		action = UNMOUNT;
338 	}
339 
340 	ret = rmm_action(hal_ctx, volume_symdev, action, 0, 0, 0,
341 	    arg_mountpoint) ? 0 : 1;
342 
343 	if (hal_ctx != NULL) {
344 		rmm_hal_fini(hal_ctx);
345 	}
346 
347 	return (ret);
348 }
349 
350 
351 /*
352  * this should be called after rmm_hal_{mount,unmount,eject}
353  */
354 int
355 vold_postprocess(LibHalContext *hal_ctx, const char *udi,
356     struct action_arg *aap)
357 {
358 	int	ret = 0;
359 
360 	/* valid mountpoint required */
361 	if ((aap->aa_action == INSERT) || (aap->aa_action == REMOUNT)) {
362 		rmm_volume_aa_update_mountpoint(hal_ctx, udi, aap);
363 		if ((aap->aa_mountpoint == NULL) ||
364 		    (strlen(aap->aa_mountpoint) == 0)) {
365 			return (1);
366 		}
367 	}
368 
369 	if (rmm_vold_mountpoints_enabled) {
370 		vold_update_mountpoints(aap);
371 	}
372 	if (rmm_vold_actions_enabled) {
373 		ret = vold_action(aap);
374 	}
375 
376 	return (ret);
377 }
378 
379 /*
380  * update legacy symlinks
381  *
382  * For cdrom:
383  *
384  *	/cdrom/<name> -> original mountpoint
385  *	/cdrom/cdrom0 -> ./<name>
386  *	/cdrom/cdrom -> cdrom0  (only for cdrom0)
387  *
388  * If it's a slice or partition, /cdrom/<name> becomes a directory:
389  *
390  *	/cdrom/<name>/s0
391  *
392  * Same for rmdisk and floppy.
393  *
394  * On labeled system (Trusted Solaris), links are in a user directory.
395  */
396 static void
397 vold_update_mountpoints(struct action_arg *aap)
398 {
399 	boolean_t	is_partition;
400 	char		part_dir[2 * MAXNAMELEN];
401 	char		symname_mp[2 * MAXNAMELEN];
402 	char		symcontents_mp[MAXNAMELEN];
403 	char		symname[2 * MAXNAMELEN];
404 	char		symcontents[MAXNAMELEN];
405 
406 	is_partition = (aap->aa_partname != NULL);
407 
408 	if (!system_labeled) {
409 		if (!is_partition) {
410 			/* /cdrom/<name> -> original mountpoint */
411 			(void) snprintf(symcontents_mp, sizeof (symcontents_mp),
412 			    "%s", aap->aa_mountpoint);
413 			(void) snprintf(symname_mp, sizeof (symname_mp),
414 			    "/%s/%s", aap->aa_media, aap->aa_name);
415 		} else {
416 			/* /cdrom/<name>/slice -> original mountpoint */
417 			(void) snprintf(part_dir, sizeof (part_dir),
418 			    "/%s/%s", aap->aa_media, aap->aa_name);
419 			(void) snprintf(symcontents_mp, sizeof (symcontents_mp),
420 			    "%s", aap->aa_mountpoint);
421 			(void) snprintf(symname_mp, sizeof (symname_mp),
422 			    "/%s/%s/%s", aap->aa_media, aap->aa_name,
423 			    aap->aa_partname);
424 
425 		}
426 		/* /cdrom/cdrom0 -> ./<name> */
427 		(void) snprintf(symcontents, sizeof (symcontents),
428 		    "./%s", aap->aa_name);
429 		(void) snprintf(symname, sizeof (symname),
430 		    "/%s/%s", aap->aa_media, aap->aa_symdev);
431 	} else {
432 		if (!is_partition) {
433 			/* /cdrom/<user>/<name> -> original mountpoint */
434 			(void) snprintf(symcontents_mp, sizeof (symcontents_mp),
435 			    "%s", aap->aa_mountpoint);
436 			(void) snprintf(symname_mp, sizeof (symname_mp),
437 			    "%s/%s/%s", mnt_zoneroot, aap->aa_media,
438 			    aap->aa_symdev);
439 		} else {
440 			/* /cdrom/<user>/<name>/slice -> original mountpoint */
441 			(void) snprintf(symcontents_mp, sizeof (symcontents_mp),
442 			    "%s", aap->aa_mountpoint);
443 			(void) snprintf(symname_mp, sizeof (symname_mp),
444 			    "%s/%s/%s", mnt_zoneroot, aap->aa_media,
445 			    aap->aa_symdev, aap->aa_partname);
446 		}
447 
448 		/* /cdrom/<user>/cdrom0 -> ./<user>/<name> */
449 		(void) snprintf(symcontents, sizeof (symcontents),
450 		    ".%s/%s", mnt_userdir, aap->aa_name);
451 		(void) snprintf(symname, sizeof (symname), "%s/%s/%s",
452 		    mnt_zoneroot, aap->aa_media, aap->aa_symdev);
453 	}
454 
455 	(void) unlink(symname);
456 	(void) unlink(symname_mp);
457 	if (is_partition) {
458 		(void) rmdir(part_dir);
459 	}
460 
461 	if ((aap->aa_action == INSERT) || (aap->aa_action == REMOUNT)) {
462 		(void) mkdir(aap->aa_media, 0755);
463 		if (is_partition) {
464 			(void) mkdir(part_dir, 0755);
465 		}
466 		(void) symlink(symcontents_mp, symname_mp);
467 		(void) symlink(symcontents, symname);
468 	}
469 }
470 
471 
472 static int
473 vold_action(struct action_arg *aap)
474 {
475 	action_t	action;
476 	int		result;
477 	int		do_notify = FALSE;
478 	action_t	notify_act = EJECT;
479 	struct action_arg *aa[2];
480 	struct action_arg a1;
481 
482 	dprintf("%s[%d]: entering action()\n", __FILE__, __LINE__);
483 
484 	/*
485 	 * on Trusted Extensions, actions are executed in the user's zone
486 	 */
487 	if (mnt_zoneid > GLOBAL_ZONEID) {
488 		pid_t	pid;
489 		int	status;
490 		int	ifx;
491 		int	tmpl_fd;
492 		int	err = 0;
493 
494 		tmpl_fd = open64(CTFS_ROOT "/process/template",
495 		    O_RDWR);
496 		if (tmpl_fd == -1)
497 			return (1);
498 
499 		/*
500 		 * Deliver no events, don't inherit,
501 		 * and allow it to be orphaned.
502 		 */
503 		err |= ct_tmpl_set_critical(tmpl_fd, 0);
504 		err |= ct_tmpl_set_informative(tmpl_fd, 0);
505 		err |= ct_pr_tmpl_set_fatal(tmpl_fd,
506 		    CT_PR_EV_HWERR);
507 		err |= ct_pr_tmpl_set_param(tmpl_fd,
508 		    CT_PR_PGRPONLY |
509 		    CT_PR_REGENT);
510 		if (err || ct_tmpl_activate(tmpl_fd)) {
511 			(void) close(tmpl_fd);
512 			return (1);
513 		}
514 		switch (pid = fork1()) {
515 		case 0:
516 			(void) ct_tmpl_clear(tmpl_fd);
517 			for (ifx = 0; ifx < _NFILE; ifx++)
518 				(void) close(ifx);
519 
520 			if (zone_enter(mnt_zoneid) == -1)
521 				_exit(0);
522 
523 			/* entered zone, proceed to action */
524 			break;
525 		case -1:
526 			dprintf("fork1 failed \n ");
527 			return (1);
528 		default :
529 			(void) ct_tmpl_clear(tmpl_fd);
530 			(void) close(tmpl_fd);
531 			if (waitpid(pid, &status, 0) < 0) {
532 				dprintf("%s(%ld): waitpid() "
533 				    "failed (errno %d) \n",
534 				    prog_name, prog_pid, errno);
535 				return (1);
536 			}
537 		    }
538 
539 	}
540 
541 	/* only support one action at a time XXX */
542 	a1.aa_path = NULL;
543 	aa[0] = aap;
544 	aa[1] = &a1;
545 
546 	action = aa[0]->aa_action;
547 
548 	if (action == CLEAR_MOUNTS) {
549 		/*
550 		 * Remove the notifications files, but don't
551 		 * notify the client.  The "clear_mounts" action
552 		 * simply clears all existing mounts of a medium's
553 		 * partitions after a medium has been repartitioned.
554 		 * Then vold builds a new file system that reflects
555 		 * the medium's new partition structure and mounts
556 		 * the new partitions by calling rmmount, and therefore
557 		 * action(), with the VOLUME_ACTION environment variable
558 		 * set to "remount".
559 		 */
560 		result = remove_notify_files(aa);
561 		result = TRUE;
562 	} else if (action == EJECT) {
563 		result = remove_notify_files(aa);
564 		if (result == TRUE) {
565 			do_notify = TRUE;
566 			notify_act = EJECT;
567 		}
568 	} else if (action = INSERT) {
569 		result = create_notify_files(aa);
570 		if (result == TRUE) {
571 			do_notify = TRUE;
572 			notify_act = INSERT;
573 		}
574 	} else if (action == REMOUNT) {
575 		result = create_notify_files(aa);
576 		if (result == TRUE) {
577 			do_notify = TRUE;
578 			notify_act = REMOUNT;
579 		}
580 	} else if (action == UNMOUNT) {
581 		result = remove_notify_files(aa);
582 		if (result == TRUE) {
583 			do_notify = TRUE;
584 			notify_act = UNMOUNT;
585 		}
586 	} else {
587 		dprintf("%s[%d]: action(): invalid action: %s\n",
588 			__FILE__, __LINE__, action);
589 		result = FALSE;
590 	}
591 
592 	if (result == TRUE) {
593 		result = notify_clients(notify_act, do_notify);
594 	}
595 
596 	dprintf("%s[%d]: leaving action(), result = %s\n",
597 		__FILE__, __LINE__, result_strings[result]);
598 
599 	if (mnt_zoneid > GLOBAL_ZONEID) {
600 		/* exit forked local zone process */
601 		_exit(0);
602 	}
603 
604 	if (result == TRUE) {
605 		/*
606 		 * File Manager is running. return 0.
607 		 * see man page rmmount.conf(4).
608 		 */
609 		return (0);
610 	} else {
611 		return (1);
612 	}
613 }
614 
615 
616 /*
617  * Returns NULL if a medium or partition is mountable
618  * and a string stating the reason the medium or partition
619  * can't be mounted if the medium or partition isn't mountable.
620  *
621  * If the volume_name of the medium or partition is one of the
622  * following, the medium or partition isn't mountable.
623  *
624  * unlabeled_<media_type>
625  * unknown_format
626  * password_protected
627  */
628 /* ARGSUSED */
629 static char *
630 not_mountable(struct action_arg *aa)
631 {
632 	return (NULL);
633 }
634 
635 static int
636 create_notify_files(struct action_arg **aa)
637 {
638 	int	ai;
639 	char	*fstype;
640 	char	*mount_point;
641 	char	notify_file[64];
642 	char	*raw_partitionp;
643 	char	*reason; /* Why the medium wasn't mounted */
644 	int	result;
645 	char	*symdev;
646 
647 	dprintf("%s[%d]: entering create_notify_files()\n", __FILE__, __LINE__);
648 
649 	ai = 0;
650 	result = FALSE;
651 	symdev = aa[ai]->aa_symdev;
652 	while ((aa[ai] != NULL) && (aa[ai]->aa_path != NULL)) {
653 		if (aa[ai]->aa_mountpoint != NULL) {
654 			if (aa[ai]->aa_type) {
655 				fstype = aa[ai]->aa_type;
656 			} else {
657 				fstype = "unknown";
658 			}
659 			mount_point = aa[ai]->aa_mountpoint;
660 			if (aa[ai]->aa_partname != NULL) {
661 				/*
662 				 * Is aa_partname ever NULL?
663 				 * When time permits, check.
664 				 * If it is, the action taken
665 				 * in the else clause could produce
666 				 * file name conflicts.
667 				 */
668 				sprintf(notify_file, "%s-%s", symdev,
669 						aa[ai]->aa_partname);
670 			} else {
671 				sprintf(notify_file, "%s-0", symdev);
672 			}
673 			reason = NULL;
674 		} else {
675 			/*
676 			 * The partition isn't mounted.
677 			 */
678 			fstype = "none";
679 			mount_point = "none";
680 			reason = not_mountable(aa[ai]);
681 			if (reason != NULL) {
682 				sprintf(notify_file, "%s-0", symdev);
683 			} else {
684 				/*
685 				 * Either the partition is a backup slice, or
686 				 * rmmount tried to mount the partition, but
687 				 * idenf_fs couldn't identify the file system
688 				 * type; that can occur when rmmount is
689 				 * trying to mount all the slices in a Solaris
690 				 * VTOC, and one or more partitions don't have
691 				 * file systems in them.
692 				 */
693 				if (aa[0]->aa_partname != NULL) {
694 					/*
695 					 * Is aa_partname ever NULL?
696 					 * When time permits, check.
697 					 * If it is, the action taken
698 					 * in the else clause could produce
699 					 * file name conflicts.
700 					 */
701 					sprintf(notify_file, "%s-%s", symdev,
702 							aa[0]->aa_partname);
703 				} else {
704 					sprintf(notify_file, "%s-0", symdev);
705 				}
706 				if ((aa[0]->aa_type != NULL) &&
707 					(strcmp(aa[0]->aa_type, "backup_slice")
708 						== 0)) {
709 					reason = "backup_slice";
710 				} else {
711 					reason = "unformatted_media";
712 				}
713 				/*
714 				 * "unformatted_media" should be
715 				 * changed to "unformmated_medium" for
716 				 * grammatical correctness, but
717 				 * "unformatted_media" is now specified
718 				 * in the interface to filemgr, so the
719 				 * change can't be made without the
720 				 * approval of the CDE group.
721 				 */
722 			}
723 		}
724 		raw_partitionp = aa[0]->aa_rawpath;
725 		result = create_one_notify_file(fstype,
726 				mount_point,
727 				notify_file,
728 				raw_partitionp,
729 				reason,
730 				symdev);
731 		ai++;
732 	}
733 	dprintf("%s[%d]: leaving create_notify_files(), result = %s\n",
734 		__FILE__, __LINE__, result_strings[result]);
735 	return (result);
736 }
737 
738 static int
739 create_one_notify_file(char *fstype,
740 	char *mount_point,
741 	char *notify_file,
742 	char *raw_partitionp,
743 	char *reason,
744 	char *symdev)
745 {
746 	/*
747 	 * For a mounted partition, create a notification file
748 	 * indicating the mount point,  the raw device pathname
749 	 * of the partition, and the partition's file system
750 	 * type.  For an unmounted partition, create a
751 	 * notification file containing the reason that the
752 	 * partition wasn't mounted and the raw device pathname
753 	 * of the partition.
754 	 *
755 	 * Create the file as root in a world-writable
756 	 * directory that resides in a world-writable directory.
757 	 *
758 	 * Handle two possible race conditions that could
759 	 * allow security breaches.
760 	 */
761 
762 	int	current_working_dir_fd;
763 	int	file_descriptor;
764 	FILE	*filep;
765 	int	result;
766 
767 	dprintf("%s[%d]:Entering create_one_notify_file()\n",
768 		__FILE__, __LINE__);
769 	dprintf("\tcreate_one_notify_file(): fstype = %s\n", fstype);
770 	dprintf("\tcreate_one_notify_file(): mount_point = %s\n", mount_point);
771 	dprintf("\tcreate_one_notify_file(): notify_file = %s\n", notify_file);
772 	dprintf("\tcreate_one_notify_file(): raw_partitionp = %s\n",
773 		raw_partitionp);
774 	if (reason != NULL) {
775 		dprintf("\tcreate_one_notify_file(): reason = %s\n", reason);
776 	} else {
777 		dprintf("\tcreate_one_notify_file(): reason = NULL\n");
778 	}
779 	dprintf("\tcreate_one_notify_file(): symdev = %s\n", symdev);
780 
781 	result = TRUE;
782 	/*
783 	 * Handle Race Condition One:
784 	 *
785 	 *   If NOTIFY_DIR exists, make sure it is not a symlink.
786 	 *   if it is, remove it and try to create it.  Check
787 	 *   again to make sure NOTIFY_DIR isn't a symlink.
788 	 *   If it is, remove it and return without creating
789 	 *   a notification file.  The condition can only occur if
790 	 *   someone is trying to break into the system by running
791 	 *   a program that repeatedly creates NOTIFY_DIR as a
792 	 *   symlink.  If NOTIFY_DIR exists and isn't a symlink,
793 	 *   change the working directory to NOTIFY_DIR.
794 	 */
795 	current_working_dir_fd = pushdir(NOTIFY_DIR);
796 	if (current_working_dir_fd < 0) {
797 		(void) makepath(NOTIFY_DIR, 0777);
798 		current_working_dir_fd = pushdir(NOTIFY_DIR);
799 		if (current_working_dir_fd < 0) {
800 			result = FALSE;
801 		}
802 	}
803 	/*
804 	 * Handle Race Condition Two:
805 	 *
806 	 * Create the notification file in NOTIFY_DIR.
807 	 * Remove any files with the same name that may already be
808 	 * there, using remove(), as it safely removes directories.
809 	 * Then open the file O_CREAT|O_EXCL, which doesn't follow
810 	 * symlinks and requires that the file not exist already,
811 	 * so the new file actually resides in the current working
812 	 * directory.  Create the file with access mode 644, which
813 	 * renders it unusable by anyone trying to break into the
814 	 * system.
815 	 */
816 	if (result == TRUE) {
817 		/*
818 		 * The current working directory is now NOTIFY_DIR.
819 		 */
820 		(void) remove(notify_file);
821 		file_descriptor =
822 			open(notify_file, O_CREAT|O_EXCL|O_WRONLY, 0644);
823 		if (file_descriptor < 0) {
824 			dprintf("%s[%d]: can't create %s/%s; %m\n",
825 				__FILE__, __LINE__, NOTIFY_DIR, notify_file);
826 			result = FALSE;
827 		} else {
828 			filep = fdopen(file_descriptor, "w");
829 			if (filep != NULL) {
830 				if (reason == NULL) {
831 					(void) fprintf(filep, "%s %s %s",
832 						mount_point,
833 						raw_partitionp,
834 						fstype);
835 					(void) fclose(filep);
836 				dprintf("%s[%d]: Just wrote %s %s %s to %s\n",
837 						__FILE__,
838 						__LINE__,
839 						mount_point,
840 						raw_partitionp,
841 						fstype,
842 						notify_file);
843 				} else {
844 					(void) fprintf(filep, "%s %s",
845 						reason, raw_partitionp);
846 					(void) fclose(filep);
847 				dprintf("%s[%d]: Just wrote %s %s to %s\n",
848 						__FILE__,
849 						__LINE__,
850 						reason,
851 						raw_partitionp,
852 						notify_file);
853 				}
854 			} else {
855 				dprintf("%s[%d]: can't write %s/%s; %m\n",
856 					__FILE__, __LINE__,
857 					NOTIFY_DIR, notify_file);
858 				(void) close(file_descriptor);
859 				result = FALSE;
860 			}
861 		}
862 		popdir(current_working_dir_fd);
863 	}
864 	dprintf("%s[%d]: leaving create_one_notify_file, result = %s\n",
865 		__FILE__, __LINE__, result_strings[result]);
866 	return (result);
867 }
868 
869 static boolean_t
870 notify_clients(action_t action, int do_notify)
871 {
872 	/*
873 	 * Notify interested applications of changes in the state
874 	 * of removable media.  Interested applications are those
875 	 * that create a named pipe in NOTIFY_DIR with a name that
876 	 * begins with "notify".  Open the pipe and write a
877 	 * character through it that indicates the type of state
878 	 * change = 'e' for ejections, 'i' for insertions, 'r'
879 	 * for remounts of the file systems on repartitioned media,
880 	 * and 'u' for unmounts of file systems.
881 	 */
882 
883 	int		current_working_dir_fd;
884 	DIR		*dirp;
885 	struct dirent	*dir_entryp;
886 	size_t		len;
887 	int		fd;
888 	char		namebuf[MAXPATHLEN];
889 	char		notify_character;
890 	void		(*old_signal_handler)();
891 	int		result;
892 	struct stat	sb;
893 
894 	dprintf("%s[%d]: entering notify_clients()\n", __FILE__, __LINE__);
895 
896 	result = TRUE;
897 	/*
898 	 * Use relative pathnames after changing the
899 	 * working directory to the notification directory.
900 	 * Check to make sure that each "notify" file is a
901 	 * named pipe to make sure that it hasn't changed
902 	 * its file type, which could mean that someone is
903 	 * trying to use "notify" files to break into the
904 	 * system.
905 	 */
906 	if ((current_working_dir_fd = pushdir(NOTIFY_DIR)) < 0) {
907 		result = FALSE;
908 	}
909 	if (result == TRUE) {
910 		dirp = opendir(".");
911 		if (dirp == NULL) {
912 			dprintf("%s[%d]:opendir failed on '.'; %m\n",
913 				__FILE__, __LINE__);
914 			popdir(current_working_dir_fd);
915 			result = FALSE;
916 		}
917 	}
918 	if (result == TRUE) {
919 		/*
920 		 * Read through the directory and write a notify
921 		 * character to all files whose names start with "notify".
922 		 */
923 		result = FALSE;
924 		old_signal_handler = signal(SIGPIPE, SIG_IGN);
925 		len = strlen(NOTIFY_NAME);
926 		while (dir_entryp = readdir(dirp)) {
927 			if (strncmp(dir_entryp->d_name, NOTIFY_NAME, len)
928 			    != 0) {
929 				continue;
930 			}
931 			result = TRUE;
932 			if (do_notify != TRUE) {
933 				continue;
934 			}
935 			(void) sprintf(namebuf, "%s/%s",
936 				NOTIFY_DIR, dir_entryp->d_name);
937 			if ((fd = open(namebuf, O_WRONLY|O_NDELAY)) < 0) {
938 				dprintf("%s[%d]: open failed for %s; %m\n",
939 				    __FILE__, __LINE__, namebuf);
940 				continue;
941 			}
942 			/*
943 			 * Check to be sure that the entry is a named pipe.
944 			 * That closes a small security hole that could
945 			 * enable unauthorized access to the system root.
946 			 */
947 			if ((fstat(fd, &sb) < 0) || (!S_ISFIFO(sb.st_mode))) {
948 				dprintf("%s[%d]: %s isn't a named pipe\n",
949 					__FILE__, __LINE__, namebuf);
950 
951 				(void) close(fd);
952 				continue;
953 			}
954 			notify_character = notify_characters[action];
955 			if (write(fd, &notify_character, 1) < 0) {
956 				dprintf("%s[%d]: write failed for %s; %m\n",
957 				    __FILE__, __LINE__, namebuf);
958 				(void) close(fd);
959 				continue;
960 			}
961 			(void) close(fd);
962 		}
963 		(void) closedir(dirp);
964 		(void) signal(SIGPIPE, old_signal_handler);
965 		popdir(current_working_dir_fd);
966 	}
967 	dprintf("%s[%d]: leaving notify_clients(), result = %s\n",
968 		__FILE__, __LINE__, result_strings[result]);
969 	return (result);
970 }
971 
972 static void
973 popdir(int fd)
974 {
975 	/*
976 	 * Change the current working directory to the directory
977 	 * specified by fd and close the fd.  Exit the program
978 	 * on failure.
979 	 */
980 	if (fchdir(fd) < 0) {
981 		dprintf("%s[%d]: popdir() failed\n", __FILE__, __LINE__);
982 		exit(1);
983 	}
984 	(void) close(fd);
985 }
986 
987 static int
988 pushdir(const char *dir)
989 {
990 	/*
991 	 * Change the current working directory to dir and
992 	 * return a file descriptor for the old working
993 	 * directory.
994 	 *
995 	 * Exception handling:
996 	 *
997 	 * If dir doesn't exist, leave the current working
998 	 * directory the same and return -1.
999 	 *
1000 	 * If dir isn't a directory, remove it, leave the
1001 	 * current working directory the same, and return -1.
1002 	 *
1003 	 * If open() fails on the current working directory
1004 	 * or the chdir operation fails on dir, leave the
1005 	 * current working directory the same and return -1.
1006 	 */
1007 
1008 	int		current_working_dir_fd;
1009 	struct stat	stat_buf;
1010 
1011 	if (lstat(dir, &stat_buf) < 0) {
1012 		dprintf("%s[%d]: push_dir_and_check(): %s does not exist\n",
1013 			__FILE__, __LINE__, dir);
1014 		return (-1);
1015 	}
1016 
1017 	if (!(S_ISDIR(stat_buf.st_mode))) {
1018 		dprintf("%s[%d]: push_dir_and_check(): %s not a directory.\n",
1019 			__FILE__, __LINE__, dir);
1020 		(void) remove(dir);
1021 		return (-1);
1022 	}
1023 	if ((current_working_dir_fd = open(".", O_RDONLY)) < 0) {
1024 		dprintf("%s[%d]: push_dir_and_check(): can't open %s.\n",
1025 			__FILE__, __LINE__, dir);
1026 		return (-1);
1027 	}
1028 	if (chdir(dir) < 0) {
1029 		(void) close(current_working_dir_fd);
1030 		dprintf("%s[%d]: push_dir_and_check(): can't chdir() to %s.\n",
1031 			__FILE__, __LINE__, dir);
1032 		return (-1);
1033 	}
1034 	return (current_working_dir_fd);
1035 }
1036 
1037 static boolean_t
1038 remove_notify_files(struct action_arg **aa)
1039 {
1040 	int	ai;
1041 	int	current_working_dir_fd;
1042 	char	notify_file[64];
1043 	int	result;
1044 	char	*symdev;
1045 
1046 	dprintf("%s[%d]: entering remove_notify_files()\n", __FILE__, __LINE__);
1047 
1048 	ai = 0;
1049 	result = TRUE;
1050 	symdev = aa[ai]->aa_symdev;
1051 	while ((result == TRUE) &&
1052 		(aa[ai] != NULL) &&
1053 		(aa[ai]->aa_path != NULL)) {
1054 
1055 		if (not_mountable(aa[ai])) {
1056 			sprintf(notify_file, "%s-0", symdev);
1057 		} else if (aa[ai]->aa_partname != NULL) {
1058 			/*
1059 			 * Is aa_partname ever NULL?
1060 			 * When time permits, check.
1061 			 * If it is, the action taken
1062 			 * in the else clause could produce
1063 			 * file name conflicts.
1064 			 */
1065 			sprintf(notify_file, "%s-%s",
1066 				symdev, aa[0]->aa_partname);
1067 		} else {
1068 			sprintf(notify_file, "%s-0", symdev);
1069 		}
1070 
1071 		current_working_dir_fd = pushdir(NOTIFY_DIR);
1072 		if (current_working_dir_fd < 0) {
1073 			result = FALSE;
1074 		}
1075 		if ((result == TRUE) && (remove(notify_file) < 0)) {
1076 			dprintf("%s[%d]: remove %s/%s; %m\n",
1077 				__FILE__, __LINE__, NOTIFY_DIR, notify_file);
1078 			result = FALSE;
1079 		}
1080 		if (current_working_dir_fd != -1) {
1081 			popdir(current_working_dir_fd);
1082 		}
1083 		ai++;
1084 	}
1085 	dprintf("%s[%d]: leaving remove_notify_files(), result = %s\n",
1086 		__FILE__, __LINE__, result_strings[result]);
1087 
1088 	return (result);
1089 }
1090