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