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