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
58static 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 */
73int
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
111dun:
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
125static int	call_unmount_prog(int, int, char *, int, char *,
126			    char *);
127static 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 */
135int
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
252dun:
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 */
266static int
267vol_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
374char *
375vol_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
407static int	vol_getmntdev(FILE *, struct mnttab *, dev_t,
408			    struct dk_cinfo *);
409
410static int
411get_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
533dun:
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 */
561static int
562call_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
652dun:
653	return (ret_val);
654}
655