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