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