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 2006 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  * routines in this module are meant to be called by other libvolmgt
30  * routines only
31  */
32 
33 #include	<stdio.h>
34 #include	<string.h>
35 #include	<dirent.h>
36 #include	<fcntl.h>
37 #include	<string.h>
38 #ifdef	DEBUG
39 #include	<errno.h>
40 #endif
41 #include	<libintl.h>
42 #include	<limits.h>
43 #include	<unistd.h>
44 #include	<stdlib.h>
45 #include	<volmgt.h>
46 #include	<sys/types.h>
47 #include	<sys/mkdev.h>
48 #include	<sys/stat.h>
49 #include	<sys/dkio.h>
50 #include	<sys/param.h>
51 #include	<sys/wait.h>
52 #include	<sys/mnttab.h>
53 #include	"volmgt_private.h"
54 
55 
56 #define	NULL_PATH		"/dev/null"
57 
58 
59 
60 /*
61  * This is an ON Consolidation Private interface.
62  *
63  * Is the specified path mounted?
64  *
65  * This function is really inadequate for ejection testing.  For example,
66  * I could have /dev/fd0a mounted and eject /dev/fd0c, and it would be
67  * ejected.  There needs to be some better way to make this check, although
68  * short of looking up the mounted dev_t in the kernel mount table and
69  * building in all kinds of knowledge into this function,  I'm not sure
70  * how to do it.
71  */
72 int
73 _dev_mounted(char *path)
74 {
75 	static int	vol_getmntdev(FILE *, struct mnttab *, dev_t,
76 			    struct dk_cinfo *);
77 	int		fd = -1;
78 	struct dk_cinfo	info;
79 	static FILE 	*fp = NULL;		/* mnttab file pointer */
80 	struct mnttab	mnt;			/* set bug not used */
81 	char		*cn = NULL;		/* char spcl pathname */
82 	struct stat64	sb;
83 	int		ret_val = 0;
84 
85 
86 	/* ensure we have the block spcl pathname */
87 	if ((cn = (char *)volmgt_getfullrawname(path)) == NULL) {
88 		goto dun;
89 	}
90 
91 	if ((fp = fopen(MNTTAB, "rF")) == NULL) {
92 		/* mtab is gone... let him go */
93 		goto dun;
94 	}
95 
96 	if ((fd = open(cn, O_RDONLY|O_NDELAY)) < 0) {
97 		goto dun;
98 	}
99 
100 	if (fstat64(fd, &sb) < 0) {
101 		goto dun;
102 	}
103 
104 	if (ioctl(fd, DKIOCINFO, &info) != 0) {
105 		goto dun;
106 	}
107 
108 	if (vol_getmntdev(fp, &mnt, sb.st_rdev, &info) != 0) {
109 		ret_val = 1;			/* match found! */
110 	}
111 
112 dun:
113 	if (cn != NULL) {
114 		free(cn);
115 	}
116 	if (fp != NULL) {
117 		(void) fclose(fp);
118 	}
119 	if (fd >= 0) {
120 		(void) close(fd);
121 	}
122 	return (ret_val);
123 }
124 
125 
126 /*
127  * This is an ON Consolidation Private interface.
128  *
129  * Forks off rmmount and (in essence) returns the result
130  *
131  * a return value of 0 (FALSE) means failure, non-zero (TRUE) means success
132  */
133 int
134 _dev_unmount(char *path)
135 {
136 	static int	call_unmount_prog(int, int, char *, int, char *,
137 			    char *);
138 	static int	get_media_info(char *, char **, int *, char **);
139 	char		*bn = NULL;		/* block name */
140 	char		*mtype = NULL;		/* media type */
141 	char		*spcl = NULL;		/* special dev. path */
142 	char		*spcl_failed = NULL;	/* spcl that failed */
143 	int		ret_val = FALSE;	/* what we return */
144 	char		*vr;			/* volmgt root dir */
145 	int		media_info_gotten = 0;
146 	int		mnum = 0;
147 	int		volume_is_not_managed;
148 	char		*pathbuf, *absname;
149 
150 
151 	if ((bn = (char *)volmgt_getfullblkname(path)) == NULL) {
152 		goto dun;
153 	}
154 
155 	if ((pathbuf = malloc(PATH_MAX+1)) == NULL)
156 		goto dun;
157 
158 	absname = bn;
159 	if (realpath(bn, pathbuf) != NULL)
160 		absname = pathbuf;
161 
162 	volume_is_not_managed = !volmgt_running() ||
163 		(!volmgt_ownspath(absname) && volmgt_symname(bn) == NULL);
164 
165 	free(pathbuf);
166 
167 	/* decide of we should use rmmount to unmount the media */
168 	if (!volume_is_not_managed) {
169 		int		use_rmm = FALSE;	/* use rmmount??  */
170 
171 		/* at least volmgt is running */
172 		vr = (char *)volmgt_root();
173 		if (strncmp(bn, vr, strlen(vr)) == 0) {
174 			/* the block path is rooted in /vol */
175 			use_rmm = TRUE;
176 		}
177 
178 		/* try to get info about media */
179 		media_info_gotten = get_media_info(bn, &mtype, &mnum, &spcl);
180 
181 		ret_val = call_unmount_prog(media_info_gotten, use_rmm, mtype,
182 		    mnum, spcl, bn);
183 
184 	} else {
185 
186 		/* volmgt is *not* running */
187 
188 		if (get_media_info(bn, &mtype, &mnum, &spcl)) {
189 
190 			/*
191 			 * volmgt is off and get_media_info() has returned
192 			 * info on the media -- soo (this is kinda' a hack)
193 			 * ... we iterate, looking for multiple slices
194 			 * of (say) a floppy being mounted
195 			 *
196 			 * note: if an unmount fails we don't want to try
197 			 * to unmount the same device on the next try, so
198 			 * we try to watch for that
199 			 */
200 
201 			do {
202 				/*
203 				 * don't call the unmount program is we're just
204 				 * trying to unmount the same device that
205 				 * failed last time -- if that's the case,
206 				 * then bail
207 				 */
208 				if (spcl_failed != NULL) {
209 					if (strcmp(spcl, spcl_failed) == 0) {
210 						break;
211 					}
212 				}
213 				ret_val = call_unmount_prog(TRUE, FALSE,
214 				    mtype, mnum, spcl, bn);
215 
216 				if (!ret_val) {
217 					/* save spcl device name that failed */
218 					spcl_failed = strdup(spcl);
219 				} else {
220 					/*
221 					 * unmount succeeded, so clean up
222 					 */
223 					if (spcl_failed != NULL) {
224 						free(spcl_failed);
225 						spcl_failed = NULL;
226 					}
227 				}
228 
229 			} while (get_media_info(bn, &mtype, &mnum, &spcl));
230 
231 		} else {
232 
233 			/* just do the unmmount cycle once */
234 			ret_val = call_unmount_prog(FALSE, FALSE, NULL, 0,
235 			    NULL, bn);
236 		}
237 
238 	}
239 
240 	if (mtype != NULL) {
241 		free(mtype);
242 	}
243 	if (spcl != NULL) {
244 		free(spcl);
245 	}
246 	if (spcl_failed != NULL) {
247 		free(spcl_failed);
248 	}
249 	if (bn != NULL) {
250 		free(bn);
251 	}
252 
253 dun:
254 
255 	return (ret_val);
256 }
257 
258 
259 /*
260  * find a mnttab entry that has the same dev as the supplied dev,
261  *  returning it and a non-zero value if found, else returning 0
262  *
263  * this is just like getmntany(), except that it scans based on st_rdev,
264  * and it even finds different slices on the same device/unit (thanx to
265  * code copied from format.c)
266  */
267 static int
268 vol_getmntdev(FILE *fp, struct mnttab *mp, dev_t dev, struct dk_cinfo *ip)
269 {
270 	int		fd;		/* dev-in-question fd */
271 	struct stat64	sb;		/* dev-in-question stat struct */
272 	int		ret_val = 0;	/* default value: no match found */
273 	char		*cn;		/* char pathname */
274 	struct dk_cinfo	dkinfo;		/* for testing for slices */
275 
276 
277 #ifdef	DEBUG
278 	denter(
279 	    "vol_getmntdev: entering for %d.%d, ctype/cnum/unit = %d/%d/%d\n",
280 	    (int)major(dev), (int)minor(dev), ip->dki_ctype, ip->dki_cnum,
281 	    ip->dki_unit);
282 #endif
283 
284 	/* reset the mnttab -- just in case */
285 	rewind(fp);
286 
287 	/* scan each entry in mnttab */
288 	while (getmntent(fp, mp) == 0) {
289 
290 		/* don't even try unless it's a local pathname */
291 		if (mp->mnt_special[0] != '/') {
292 			continue;
293 		}
294 
295 		/* get char pathname */
296 		if ((cn = volmgt_getfullrawname(mp->mnt_special)) == NULL) {
297 			continue;
298 		}
299 		if (cn[0] == NULLC) {
300 			free(cn);
301 			continue;	/* couldn't get raw name */
302 		}
303 
304 		/* open the device */
305 		if ((fd = open(cn, O_RDONLY|O_NDELAY)) < 0) {
306 			/* if we can't open it *assume* it's not a match */
307 			free(cn);
308 			continue;
309 		}
310 
311 		/* stat the device */
312 		if (fstat64(fd, &sb) < 0) {
313 			free(cn);
314 			(void) close(fd);
315 			continue;	/* ain't there: can't be a match */
316 		}
317 
318 		/* ensure we have a spcl device (a double check) */
319 		if (!S_ISBLK(sb.st_mode) && !S_ISCHR(sb.st_mode)) {
320 			free(cn);
321 			(void) close(fd);
322 			continue;
323 		}
324 
325 		/* (almost) finally -- check the dev_t for equality */
326 		if (sb.st_rdev == dev) {
327 			ret_val = 1;		/* match found! */
328 			free(cn);
329 			(void) close(fd);
330 			break;
331 		}
332 
333 		/*
334 		 * check that the major numbers match, since if they
335 		 * don't then there's no reason to use the DKIOCINFO
336 		 * ioctl to see if we have to major/minor pairs that
337 		 * really point to the same device
338 		 */
339 		if (major(sb.st_rdev) != major(dev)) {
340 			/* no use continuing, since major devs are different */
341 			free(cn);
342 			(void) close(fd);
343 			continue;
344 		}
345 
346 		/* one last check -- for diff. slices of the same dev/unit */
347 		if (ioctl(fd, DKIOCINFO, &dkinfo) < 0) {
348 			free(cn);
349 			(void) close(fd);
350 			continue;
351 		}
352 
353 		free(cn);		/* all done with raw pathname */
354 		(void) close(fd);	/* all done with file descriptor */
355 
356 		/* if ctrler type/number and unit match, it's a match */
357 		if ((ip->dki_ctype == dkinfo.dki_ctype) &&
358 		    (ip->dki_cnum == dkinfo.dki_cnum) &&
359 		    (ip->dki_unit == dkinfo.dki_unit)) {
360 			/*
361 			 * even though minor numbers differ we have a
362 			 * match
363 			 */
364 			ret_val = 1;
365 			break;
366 		}
367 
368 		/* go around again */
369 	}
370 
371 	return (ret_val);
372 }
373 
374 
375 char *
376 vol_basename(char *path)
377 {
378 	char	*cp;
379 
380 
381 	/* check for the degenerate case */
382 	if (strcmp(path, "/") == 0) {
383 		return (path);
384 	}
385 
386 	/* look for the last slash in the name */
387 	if ((cp = strrchr(path, '/')) == NULL) {
388 		/* no slash */
389 		return (path);
390 	}
391 
392 	/* ensure something is after the slash */
393 	if (*++cp != NULLC) {
394 		return (cp);
395 	}
396 
397 	/* a name that ends in slash -- back up until previous slash */
398 	while (cp != path) {
399 		if (*--cp == '/') {
400 			return (--cp);
401 		}
402 	}
403 
404 	/* the only slash is the end of the name */
405 	return (path);
406 }
407 
408 
409 static int
410 get_media_info(char *path, char **mtypep, int *mnump, char **spclp)
411 {
412 	static int	vol_getmntdev(FILE *, struct mnttab *, dev_t,
413 			    struct dk_cinfo *);
414 	FILE		*fp = NULL;
415 	int		fd = -1;
416 	char		*cn = NULL;		/* char spcl pathname */
417 	struct stat64	sb;
418 	struct dk_cinfo	info;
419 	struct mnttab	mnt;
420 	int		ret_val = FALSE;
421 
422 	if ((fp = fopen(MNTTAB, "rF")) == NULL) {
423 		/* mtab is gone... let him go */
424 		goto dun;
425 	}
426 
427 	/* get char spcl pathname */
428 	if ((cn = volmgt_getfullrawname(path)) == NULL) {
429 		goto dun;
430 	}
431 	if (cn[0] == NULLC) {
432 		goto dun;
433 	}
434 
435 	if ((fd = open(cn, O_RDONLY|O_NDELAY)) < 0) {
436 		goto dun;
437 	}
438 
439 	if (fstat64(fd, &sb) < 0) {
440 		goto dun;
441 	}
442 
443 	if (ioctl(fd, DKIOCINFO, &info) != 0) {
444 		goto dun;
445 	}
446 
447 	/* if we found the entry then disect it */
448 	if (vol_getmntdev(fp, &mnt, sb.st_rdev, &info) != 0) {
449 		char		*cp;
450 		char		*mtype;
451 		char		*mnt_dir;
452 		int		mtype_len;
453 		DIR		*dirp = NULL;
454 		struct dirent64	*dp;
455 		char		*volname;
456 
457 
458 		/* return the spcl device name found */
459 		*spclp = strdup(mnt.mnt_special);
460 
461 		/*
462 		 * try to get the media type (e.g. "floppy") from the mount
463 		 * point (e.g. "/floppy/NAME") if vold is running
464 		 */
465 
466 		if (!volmgt_running() ||
467 		    (!volmgt_ownspath(*spclp) &&
468 			volmgt_symname(*spclp) == NULL)) {
469 			ret_val = TRUE;		/* success (if limited) */
470 			goto dun;
471 		}
472 
473 		/* get the first part of the mount point (e.g. "floppy") */
474 		cp = mnt.mnt_mountp;
475 		if (*cp++ != '/') {
476 			goto dun;
477 		}
478 		mtype = cp;
479 		if ((cp = strchr(mtype, '/')) == NULL) {
480 			goto dun;
481 		}
482 		*cp++ = NULLC;
483 		mnt_dir = mnt.mnt_mountp;	/* save dir path */
484 
485 		/* get the volume name (e.g. "unnamed_floppy") */
486 		volname = cp;
487 
488 		/* scan for the symlink that points to our volname */
489 		if ((dirp = opendir(mnt_dir)) == NULL) {
490 			goto dun;
491 		}
492 		mtype_len = strlen(mtype);
493 		while ((dp = readdir64(dirp)) != NULL) {
494 			char		lpath[2 * (MAXNAMELEN+1)];
495 			char		linkbuf[MAXPATHLEN+4];
496 			int		lb_len;
497 			struct stat64	sb;
498 
499 
500 			if (strncmp(dp->d_name, mtype, mtype_len) != 0) {
501 				continue;	/* not even close */
502 			}
503 
504 			(void) sprintf(lpath, "%s/%s", mnt_dir,
505 			    dp->d_name);
506 			if (lstat64(lpath, &sb) < 0) {
507 				continue;	/* what? */
508 			}
509 			if (!S_ISLNK(sb.st_mode)) {
510 				continue;	/* not our baby */
511 			}
512 			if ((lb_len = readlink(lpath, linkbuf,
513 			    sizeof (linkbuf))) < 0) {
514 				continue;
515 			}
516 			linkbuf[lb_len] = NULLC; /* null terminate */
517 			if ((cp = vol_basename(linkbuf)) == NULL) {
518 				continue;
519 			}
520 			/* now we have the name! */
521 			if (strcmp(cp, volname) == 0) {
522 				/* found it !! */
523 				if (sscanf(dp->d_name + mtype_len, "%d",
524 				    mnump) == 1) {
525 					*mtypep = strdup(mtype);
526 					ret_val = TRUE;
527 				}
528 				break;
529 			}
530 		}
531 		(void) closedir(dirp);
532 	}
533 
534 dun:
535 	if (fp != NULL) {
536 		(void) fclose(fp);
537 	}
538 	if (fd >= 0) {
539 		(void) close(fd);
540 	}
541 	if (cn != NULL) {
542 		free(cn);
543 	}
544 #ifdef	DEBUG
545 	if (ret_val) {
546 		dexit("get_media_info: returning mtype=%s, mnum=%d, spcl=%s\n",
547 		    *mtypep == NULL ? "<null ptr>" : *mtypep,
548 		    *mnump,
549 		    *spclp == NULL ? "<null ptr>" : *spclp);
550 	} else {
551 		dexit("get_media_info: FAILED\n");
552 	}
553 #endif
554 	return (ret_val);
555 }
556 
557 
558 /*
559  * call the appropriate unmount program, returning its success (TRUE)
560  * or failure (FALSE)
561  */
562 static int
563 call_unmount_prog(int mi_gotten, int use_rmm, char *mtype, int mnum,
564     char *spcl, char *bn)
565 {
566 	pid_t		pid;			/* forked proc's pid */
567 	int		ret_val = FALSE;
568 	const char	*etc_umount = "/etc/umount";
569 	const char	*rmm = "/usr/sbin/rmmount";
570 	int		rval;			/* proc's return value */
571 
572 
573 #ifdef	DEBUG
574 	denter(
575 	"call_unmount_prog(%s, %s, \"%s\", %d, \"%s\", \"%s\"): entering\n",
576 	    mi_gotten ? "TRUE" : "FALSE", use_rmm ? "TRUE" : "FALSE",
577 	    mtype ? mtype : "<null ptr>", mnum, spcl ? spcl : "<null ptr>",
578 	    bn);
579 #endif
580 	/* create a child to unmount the path */
581 	if ((pid = fork()) < 0) {
582 		goto dun;
583 	}
584 
585 	if (pid == 0) {
586 		/* the child */
587 #ifndef	DEBUG
588 		int		xfd;
589 #endif
590 		char		env_buf[MAXPATHLEN];
591 
592 #ifndef	DEBUG
593 		/* get rid of those nasty err messages */
594 		if ((xfd = open(NULL_PATH, O_RDWR)) >= 0) {
595 			(void) dup2(xfd, fileno(stdin));
596 			(void) dup2(xfd, fileno(stdout));
597 			(void) dup2(xfd, fileno(stderr));
598 		}
599 #endif
600 
601 		if (use_rmm) {
602 			/* set up environment vars */
603 			(void) putenv("VOLUME_ACTION=eject");
604 			(void) putenv(strdup(env_buf));
605 			if (mi_gotten) {
606 				(void) sprintf(env_buf,
607 				    "VOLUME_MEDIATYPE=%s", mtype);
608 				(void) putenv(strdup(env_buf));
609 				(void) sprintf(env_buf, "VOLUME_SYMDEV=%s%d",
610 				    mtype, mnum);
611 				(void) putenv(strdup(env_buf));
612 				(void) sprintf(env_buf, "VOLUME_PATH=%s",
613 				    spcl);
614 				(void) putenv(strdup(env_buf));
615 				(void) sprintf(env_buf, "VOLUME_NAME=%s",
616 				    vol_basename(spcl));
617 				(void) putenv(strdup(env_buf));
618 			} else {
619 				(void) sprintf(env_buf, "VOLUME_PATH=%s", bn);
620 				(void) putenv(strdup(env_buf));
621 				(void) sprintf(env_buf, "VOLUME_NAME=%s",
622 				    vol_basename(bn));
623 				(void) putenv(strdup(env_buf));
624 			}
625 #ifdef	DEBUG
626 			dprintf("call_unmount_prog: calling \"%s -D\"\n", rmm);
627 			(void) execl(rmm, rmm, "-D", NULL);
628 #else
629 			(void) execl(rmm, rmm, NULL);
630 #endif
631 		} else {
632 #ifdef	DEBUG
633 			dprintf("call_unmount_prog: calling \"%s %s\"\n",
634 			    etc_umount, mi_gotten ? spcl : bn);
635 #endif
636 			(void) execl(etc_umount, etc_umount,
637 			    mi_gotten ? spcl : bn,
638 			    NULL);
639 		}
640 		exit(-1);
641 		/*NOTREACHED*/
642 	}
643 
644 	/* wait for the umount command to exit */
645 	if (waitpid(pid, &rval, 0) == pid) {
646 		if (WIFEXITED(rval)) {
647 			if (WEXITSTATUS(rval) == 0) {
648 				ret_val = TRUE;	/* success */
649 			}
650 		}
651 	}
652 
653 dun:
654 	return (ret_val);
655 }
656