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 /*
30  *	Name:		getpathbylabel.c
31  *
32  *	Description:	Returns the global zone pathname corresponding
33  *			to the specified label. The pathname does
34  *			not need to match an existing file system object.
35  *
36  */
37 #include <stdio.h>
38 #include <string.h>
39 #include <unistd.h>
40 #include <errno.h>
41 #include <sys/types.h>
42 #include <tsol/label.h>
43 #include <stdlib.h>
44 #include <zone.h>
45 #include <sys/mntent.h>
46 #include <sys/mnttab.h>
47 #include <stdarg.h>
48 
49 /*
50  * This structure is used to chain mntent structures into a list
51  * and to cache stat information for each member of the list.
52  */
53 struct mntlist {
54 	struct mnttab	*mntl_mnt;
55 	struct mntlist	*mntl_next;
56 };
57 
58 
59 /*
60  * Return a pointer to the trailing suffix of full that follows the prefix
61  * given by pref.  If pref isn't a prefix of full, return NULL.  Apply
62  * pathname semantics to the prefix test, so that pref must match at a
63  * component boundary.
64  */
65 static char *
66 pathsuffix(char *full, char *pref)
67 {
68 	int preflen;
69 
70 	if (full == NULL || pref == NULL)
71 		return (NULL);
72 
73 	preflen = strlen(pref);
74 	if (strncmp(pref, full, preflen) != 0)
75 		return (NULL);
76 
77 	/*
78 	 * pref is a substring of full.  To be a subpath, it cannot cover a
79 	 * partial component of full.  The last clause of the test handles the
80 	 * special case of the root.
81 	 */
82 	if (full[preflen] != '\0' && full[preflen] != '/' && preflen > 1)
83 		return (NULL);
84 
85 	if (preflen == 1 && full[0] == '/')
86 		return (full);
87 	else
88 		return (full + preflen);
89 }
90 
91 /*
92  * Return zero iff the path named by sub is a leading subpath
93  * of the path named by full.
94  *
95  * Treat null paths as matching nothing.
96  */
97 static int
98 subpath(char *full, char *sub)
99 {
100 	return (pathsuffix(full, sub) == NULL);
101 }
102 
103 static void
104 tsol_mnt_free(struct mnttab *mnt)
105 {
106 	if (mnt->mnt_special)
107 		free(mnt->mnt_special);
108 	if (mnt->mnt_mountp)
109 		free(mnt->mnt_mountp);
110 	if (mnt->mnt_fstype)
111 		free(mnt->mnt_fstype);
112 	if (mnt->mnt_mntopts)
113 		free(mnt->mnt_mntopts);
114 	free(mnt);
115 }
116 
117 static void
118 tsol_mlist_free(struct mntlist *mlist)
119 {
120 	struct mntlist *mlp;
121 
122 	for (mlp = mlist; mlp; mlp = mlp->mntl_next) {
123 		struct mnttab *mnt = mlp->mntl_mnt;
124 
125 		if (mnt)
126 			tsol_mnt_free(mnt);
127 		free(mlp);
128 	}
129 }
130 
131 static struct mnttab *
132 mntdup(struct mnttab *mnt)
133 {
134 	struct mnttab *new;
135 
136 	new = (struct mnttab *)malloc(sizeof (*new));
137 	if (new == NULL)
138 		return (NULL);
139 
140 	new->mnt_special = NULL;
141 	new->mnt_mountp = NULL;
142 	new->mnt_fstype = NULL;
143 	new->mnt_mntopts = NULL;
144 
145 	new->mnt_special = strdup(mnt->mnt_special);
146 	if (new->mnt_special == NULL) {
147 		tsol_mnt_free(new);
148 		return (NULL);
149 	}
150 	new->mnt_mountp = strdup(mnt->mnt_mountp);
151 	if (new->mnt_mountp == NULL) {
152 		tsol_mnt_free(new);
153 		return (NULL);
154 	}
155 	new->mnt_fstype = strdup(mnt->mnt_fstype);
156 	if (new->mnt_fstype == NULL) {
157 		tsol_mnt_free(new);
158 		return (NULL);
159 	}
160 	new->mnt_mntopts = strdup(mnt->mnt_mntopts);
161 	if (new->mnt_mntopts == NULL) {
162 		tsol_mnt_free(new);
163 		return (NULL);
164 	}
165 	return (new);
166 }
167 
168 static struct mntlist *
169 tsol_mkmntlist(void)
170 {
171 	FILE *mounted;
172 	struct mntlist *mntl;
173 	struct mntlist *mntst = NULL;
174 	struct mnttab mnt;
175 
176 	if ((mounted = fopen(MNTTAB, "rF")) == NULL) {
177 		perror(MNTTAB);
178 		return (NULL);
179 	}
180 	resetmnttab(mounted);
181 	while (getmntent(mounted, &mnt) == NULL) {
182 		mntl = (struct mntlist *)malloc(sizeof (*mntl));
183 		if (mntl == NULL) {
184 			tsol_mlist_free(mntst);
185 			mntst = NULL;
186 			break;
187 		}
188 		mntl->mntl_mnt = mntdup((struct mnttab *)(&mnt));
189 		if (mntl->mntl_mnt == NULL) {
190 			tsol_mlist_free(mntst);
191 			mntst = NULL;
192 			break;
193 		}
194 		mntl->mntl_next = mntst;
195 		mntst = mntl;
196 	}
197 	(void) fclose(mounted);
198 	return (mntst);
199 }
200 
201 /*
202  * This function attempts to convert local zone NFS mounted pathnames
203  * into equivalent global zone NFS mounted pathnames. At present
204  * it only works for automounted filesystems. It depends on the
205  * assumption that both the local and global zone automounters
206  * share the same nameservices. It also assumes that any automount
207  * map used by a local zone is available to the global zone automounter.
208  *
209  * The algorithm used consists of three phases.
210  *
211  * 1. The local zone's mnttab is searched to find the automount map
212  *    with the closest matching mountpath.
213  *
214  * 2. The matching autmount map name is looked up in the global zone's
215  *    mnttab to determine the path where it should be mounted in the
216  *    global zone.
217  *
218  * 3. A pathname covered by an appropiate autofs trigger mount in
219  *    the global zone is generated as the resolved pathname
220  *
221  * Among the things that can go wrong is that global zone doesn't have
222  * a matching automount map or the mount was not done via the automounter.
223  * Either of these cases return a NULL path.
224  */
225 #define	ZONE_OPT "zone="
226 static int
227 getnfspathbyautofs(struct mntlist *mlist, zoneid_t zoneid,
228     struct mnttab *autofs_mnt, char *globalpath, char *zonepath, int global_len)
229 {
230 	struct mntlist *mlp;
231 	char zonematch[ZONENAME_MAX + 20];
232 	char zonename[ZONENAME_MAX];
233 	int  longestmatch;
234 	struct	mnttab	*mountmatch;
235 
236 	if (autofs_mnt) {
237 		mountmatch = autofs_mnt;
238 		longestmatch = strlen(mountmatch->mnt_mountp);
239 	} else {
240 		/*
241 		 * First we need to get the zonename to look for
242 		 */
243 		if (zone_getattr(zoneid, ZONE_ATTR_NAME, zonename,
244 		    ZONENAME_MAX) == -1) {
245 			return (0);
246 		}
247 
248 		(void) strncpy(zonematch, ZONE_OPT, sizeof (zonematch));
249 		(void) strlcat(zonematch, zonename, sizeof (zonematch));
250 
251 		/*
252 		 * Find the best match for an automount map that
253 		 * corresponds to the local zone's pathname
254 		 */
255 		longestmatch = 0;
256 		for (mlp = mlist; mlp; mlp = mlp->mntl_next) {
257 			struct mnttab *mnt = mlp->mntl_mnt;
258 			int	len;
259 			int	matchfound;
260 			char	*token;
261 			char	*lasts;
262 			char	mntopts[MAXPATHLEN];
263 
264 			if (subpath(globalpath, mnt->mnt_mountp) != 0)
265 				continue;
266 			if (strcmp(mnt->mnt_fstype, MNTTYPE_AUTOFS))
267 				continue;
268 
269 			matchfound = 0;
270 			(void) strncpy(mntopts, mnt->mnt_mntopts, MAXPATHLEN);
271 			if ((token = strtok_r(mntopts, ",", &lasts)) != NULL) {
272 				if (strcmp(token, zonematch) == 0) {
273 					matchfound = 1;
274 				} else while ((token = strtok_r(NULL, ",",
275 				    &lasts)) != NULL) {
276 					if (strcmp(token, zonematch) == 0) {
277 						matchfound = 1;
278 						break;
279 					}
280 				}
281 			}
282 			if (matchfound) {
283 				len = strlen(mnt->mnt_mountp);
284 				if (len > longestmatch) {
285 					mountmatch = mnt;
286 					longestmatch = len;
287 				}
288 			}
289 		}
290 	}
291 	if (longestmatch == 0) {
292 		return (0);
293 	} else {
294 		/*
295 		 * Now we may have found the corresponding autofs mount
296 		 * Try to find the matching global zone autofs entry
297 		 */
298 
299 		for (mlp = mlist; mlp; mlp = mlp->mntl_next) {
300 			char p[MAXPATHLEN];
301 			size_t zp_len;
302 			size_t mp_len;
303 
304 			struct mnttab *mnt = mlp->mntl_mnt;
305 
306 			if (strcmp(mountmatch->mnt_special,
307 			    mnt->mnt_special) != 0)
308 				continue;
309 			if (strcmp(mnt->mnt_fstype, MNTTYPE_AUTOFS))
310 				continue;
311 			if (strstr(mnt->mnt_mntopts, ZONE_OPT) != NULL)
312 				continue;
313 			/*
314 			 * OK, we have a matching global zone automap
315 			 * so adjust the path for the global zone.
316 			 */
317 			zp_len = strlen(zonepath);
318 			mp_len = strlen(mnt->mnt_mountp);
319 			(void) strncpy(p, globalpath + zp_len, MAXPATHLEN);
320 			/*
321 			 * If both global zone and zone-relative
322 			 * mountpoint match, just use the same pathname
323 			 */
324 			if (strncmp(mnt->mnt_mountp, p, mp_len) == 0) {
325 				(void) strncpy(globalpath, p, global_len);
326 				return (1);
327 			} else {
328 				(void) strncpy(p, globalpath, MAXPATHLEN);
329 				(void) strncpy(globalpath, mnt->mnt_mountp,
330 				    global_len);
331 				(void) strlcat(globalpath,
332 				    p + strlen(mountmatch->mnt_mountp),
333 				    global_len);
334 				return (1);
335 			}
336 		}
337 		return (0);
338 	}
339 }
340 
341 	/*
342 	 * Find the pathname for the entry in mlist that corresponds to the
343 	 * file named by path (i.e., that names a mount table entry for the
344 	 * file system in which path lies).
345 	 *
346 	 * Return 0 is there an error.
347 	 */
348 	static int
349 	getglobalpath(const char *path, zoneid_t zoneid, struct mntlist *mlist,
350 	    char *globalpath)
351 	{
352 		struct mntlist *mlp;
353 		char		lofspath[MAXPATHLEN];
354 		char		zonepath[MAXPATHLEN];
355 		int		longestmatch;
356 		struct	mnttab	*mountmatch;
357 
358 		if (zoneid != GLOBAL_ZONEID) {
359 			char	*prefix;
360 
361 			if ((prefix = getzonerootbyid(zoneid)) == NULL) {
362 				return (0);
363 			}
364 			(void) strncpy(zonepath, prefix, MAXPATHLEN);
365 			(void) strlcpy(globalpath, prefix, MAXPATHLEN);
366 			(void) strlcat(globalpath, path, MAXPATHLEN);
367 			free(prefix);
368 		} else {
369 			(void) strlcpy(globalpath, path, MAXPATHLEN);
370 		}
371 
372 		for (;;) {
373 			longestmatch = 0;
374 			for (mlp = mlist; mlp; mlp = mlp->mntl_next) {
375 				struct mnttab *mnt = mlp->mntl_mnt;
376 				int	len;
377 
378 			if (subpath(globalpath, mnt->mnt_mountp) != 0)
379 				continue;
380 			len = strlen(mnt->mnt_mountp);
381 			if (len > longestmatch) {
382 				mountmatch = mnt;
383 				longestmatch = len;
384 			}
385 		}
386 		/*
387 		 * Handle interesting mounts.
388 		 */
389 		if ((strcmp(mountmatch->mnt_fstype, MNTTYPE_NFS) == 0) ||
390 		    (strcmp(mountmatch->mnt_fstype, MNTTYPE_AUTOFS) == 0)) {
391 			if (zoneid > GLOBAL_ZONEID) {
392 				struct mnttab *m = NULL;
393 
394 				if (strcmp(mountmatch->mnt_fstype,
395 				    MNTTYPE_AUTOFS) == 0)
396 					m = mountmatch;
397 				if (getnfspathbyautofs(mlist, zoneid, m,
398 				    globalpath, zonepath, MAXPATHLEN) == 0) {
399 					return (0);
400 				}
401 			}
402 			break;
403 		} else if (strcmp(mountmatch->mnt_fstype, MNTTYPE_LOFS) == 0) {
404 			/*
405 			 * count up what's left
406 			 */
407 			int	remainder;
408 
409 			remainder = strlen(globalpath) - longestmatch;
410 			if (remainder > 0) {
411 				path = pathsuffix(globalpath,
412 				    mountmatch->mnt_mountp);
413 				(void) strlcpy(lofspath, path, MAXPATHLEN);
414 			}
415 			(void) strlcpy(globalpath, mountmatch->mnt_special,
416 			    MAXPATHLEN);
417 			if (remainder > 0) {
418 				(void) strlcat(globalpath, lofspath,
419 				    MAXPATHLEN);
420 			}
421 		} else {
422 			if ((zoneid > GLOBAL_ZONEID) &&
423 			    (strncmp(path, "/home/", strlen("/home/")) == 0)) {
424 				char zonename[ZONENAME_MAX];
425 
426 				/*
427 				 * If this is a cross-zone reference to
428 				 * a home directory, it must be corrected.
429 				 * We should only get here if the zone's
430 				 * automounter hasn't yet mounted its
431 				 * autofs trigger on /home.
432 				 *
433 				 * Since it is likely to do so in the
434 				 * future, we will assume that the global
435 				 * zone already has an equivalent autofs
436 				 * mount established. By convention,
437 				 * this should be mounted at the
438 				 * /zone/<zonename>
439 				 */
440 
441 				if (zone_getattr(zoneid, ZONE_ATTR_NAME,
442 				    zonename, ZONENAME_MAX) == -1) {
443 					return (0);
444 				} else {
445 					(void) snprintf(globalpath, MAXPATHLEN,
446 					    "/zone/%s%s", zonename, path);
447 				}
448 			}
449 			break;
450 		}
451 	}
452 	return (1);
453 }
454 
455 
456 /*
457  * This function is only useful for global zone callers
458  * It uses the global zone mnttab to translate local zone pathnames
459  * into global zone pathnames.
460  */
461 char *
462 getpathbylabel(const char *path_name, char *resolved_path, size_t bufsize,
463     const bslabel_t *sl) {
464 	char		ret_path[MAXPATHLEN];	/* pathname to return */
465 	zoneid_t	zoneid;
466 	struct mntlist *mlist;
467 
468 	if (getzoneid() != GLOBAL_ZONEID) {
469 		errno = EINVAL;
470 		return (NULL);
471 	}
472 
473 	if (path_name[0] != '/') {		/* need absolute pathname */
474 		errno = EINVAL;
475 		return (NULL);
476 	}
477 
478 	if (resolved_path == NULL) {
479 		errno = EINVAL;
480 		return (NULL);
481 	}
482 
483 	if ((zoneid = getzoneidbylabel(sl)) == -1)
484 		return (NULL);
485 
486 	/*
487 	 * Construct the list of mounted file systems.
488 	 */
489 
490 	if ((mlist = tsol_mkmntlist()) == NULL) {
491 		return (NULL);
492 	}
493 	if (getglobalpath(path_name, zoneid, mlist, ret_path) == 0) {
494 		tsol_mlist_free(mlist);
495 		return (NULL);
496 	}
497 	tsol_mlist_free(mlist);
498 	if (strlen(ret_path) >= bufsize) {
499 		errno = EFAULT;
500 		return (NULL);
501 	}
502 	return (strcpy(resolved_path, ret_path));
503 } /* end getpathbylabel() */
504