/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2006, 2011, Oracle and/or its affiliates. All rights reserved.
*/
/*
* Name: getpathbylabel.c
*
* Description: Returns the global zone pathname corresponding
* to the specified label. The pathname does
* not need to match an existing file system object.
*
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <tsol/label.h>
#include <stdlib.h>
#include <zone.h>
#include <sys/mntent.h>
#include <sys/mnttab.h>
#include <stdarg.h>
/*
* This structure is used to chain mntent structures into a list
* and to cache stat information for each member of the list.
*/
struct mntlist {
struct mnttab *mntl_mnt;
struct mntlist *mntl_next;
};
/*
* Return a pointer to the trailing suffix of full that follows the prefix
* given by pref. If pref isn't a prefix of full, return NULL. Apply
* pathname semantics to the prefix test, so that pref must match at a
* component boundary.
*/
static char *
pathsuffix(char *full, char *pref)
{
int preflen;
if (full == NULL || pref == NULL)
return (NULL);
preflen = strlen(pref);
if (strncmp(pref, full, preflen) != 0)
return (NULL);
/*
* pref is a substring of full. To be a subpath, it cannot cover a
* partial component of full. The last clause of the test handles the
* special case of the root.
*/
if (full[preflen] != '\0' && full[preflen] != '/' && preflen > 1)
return (NULL);
if (preflen == 1 && full[0] == '/')
return (full);
else
return (full + preflen);
}
/*
* Return zero iff the path named by sub is a leading subpath
* of the path named by full.
*
* Treat null paths as matching nothing.
*/
static int
subpath(char *full, char *sub)
{
return (pathsuffix(full, sub) == NULL);
}
static void
tsol_mnt_free(struct mnttab *mnt)
{
if (mnt->mnt_special)
free(mnt->mnt_special);
if (mnt->mnt_mountp)
free(mnt->mnt_mountp);
if (mnt->mnt_fstype)
free(mnt->mnt_fstype);
if (mnt->mnt_mntopts)
free(mnt->mnt_mntopts);
free(mnt);
}
static void
tsol_mlist_free(struct mntlist *mlist)
{
struct mntlist *mlp;
struct mntlist *oldmlp;
mlp = mlist;
while (mlp) {
struct mnttab *mnt = mlp->mntl_mnt;
if (mnt)
tsol_mnt_free(mnt);
oldmlp = mlp;
mlp = mlp->mntl_next;
free(oldmlp);
}
}
static struct mnttab *
mntdup(struct mnttab *mnt)
{
struct mnttab *new;
new = (struct mnttab *)malloc(sizeof (*new));
if (new == NULL)
return (NULL);
new->mnt_special = NULL;
new->mnt_mountp = NULL;
new->mnt_fstype = NULL;
new->mnt_mntopts = NULL;
new->mnt_special = strdup(mnt->mnt_special);
if (new->mnt_special == NULL) {
tsol_mnt_free(new);
return (NULL);
}
new->mnt_mountp = strdup(mnt->mnt_mountp);
if (new->mnt_mountp == NULL) {
tsol_mnt_free(new);
return (NULL);
}
new->mnt_fstype = strdup(mnt->mnt_fstype);
if (new->mnt_fstype == NULL) {
tsol_mnt_free(new);
return (NULL);
}
new->mnt_mntopts = strdup(mnt->mnt_mntopts);
if (new->mnt_mntopts == NULL) {
tsol_mnt_free(new);
return (NULL);
}
return (new);
}
static struct mntlist *
tsol_mkmntlist(void)
{
FILE *mounted;
struct mntlist *mntl;
struct mntlist *mntst = NULL;
struct mnttab mnt;
if ((mounted = fopen(MNTTAB, "rF")) == NULL) {
perror(MNTTAB);
return (NULL);
}
resetmnttab(mounted);
while (getmntent(mounted, &mnt) == NULL) {
mntl = (struct mntlist *)malloc(sizeof (*mntl));
if (mntl == NULL) {
tsol_mlist_free(mntst);
mntst = NULL;
break;
}
mntl->mntl_mnt = mntdup((struct mnttab *)(&mnt));
if (mntl->mntl_mnt == NULL) {
tsol_mlist_free(mntst);
free(mntl);
mntst = NULL;
break;
}
mntl->mntl_next = mntst;
mntst = mntl;
}
(void) fclose(mounted);
return (mntst);
}
/*
* This function attempts to convert local zone NFS mounted pathnames
* into equivalent global zone NFS mounted pathnames. At present
* it only works for automounted filesystems. It depends on the
* assumption that both the local and global zone automounters
* share the same nameservices. It also assumes that any automount
* map used by a local zone is available to the global zone automounter.
*
* The algorithm used consists of three phases.
*
* 1. The local zone's mnttab is searched to find the automount map
* with the closest matching mountpath.
*
* 2. The matching autmount map name is looked up in the global zone's
* mnttab to determine the path where it should be mounted in the
* global zone.
*
* 3. A pathname covered by an appropiate autofs trigger mount in
* the global zone is generated as the resolved pathname
*
* Among the things that can go wrong is that global zone doesn't have
* a matching automount map or the mount was not done via the automounter.
* Either of these cases return a NULL path.
*/
#define ZONE_OPT "zone="
static int
getnfspathbyautofs(struct mntlist *mlist, zoneid_t zoneid,
struct mnttab *autofs_mnt, char *globalpath, char *zonepath, int global_len)
{
struct mntlist *mlp;
char zonematch[ZONENAME_MAX + 20];
char zonename[ZONENAME_MAX];
int longestmatch;
struct mnttab *mountmatch;
if (autofs_mnt) {
mountmatch = autofs_mnt;
longestmatch = strlen(mountmatch->mnt_mountp);
} else {
/*
* First we need to get the zonename to look for
*/
if (zone_getattr(zoneid, ZONE_ATTR_NAME, zonename,
ZONENAME_MAX) == -1) {
return (0);
}
(void) strncpy(zonematch, ZONE_OPT, sizeof (zonematch));
(void) strlcat(zonematch, zonename, sizeof (zonematch));
/*
* Find the best match for an automount map that
* corresponds to the local zone's pathname
*/
longestmatch = 0;
for (mlp = mlist; mlp; mlp = mlp->mntl_next) {
struct mnttab *mnt = mlp->mntl_mnt;
int len;
int matchfound;
char *token;
char *lasts;
char mntopts[MAXPATHLEN];
if (subpath(globalpath, mnt->mnt_mountp) != 0)
continue;
if (strcmp(mnt->mnt_fstype, MNTTYPE_AUTOFS))
continue;
matchfound = 0;
(void) strncpy(mntopts, mnt->mnt_mntopts, MAXPATHLEN);
if ((token = strtok_r(mntopts, ",", &lasts)) != NULL) {
if (strcmp(token, zonematch) == 0) {
matchfound = 1;
} else while ((token = strtok_r(NULL, ",",
&lasts)) != NULL) {
if (strcmp(token, zonematch) == 0) {
matchfound = 1;
break;
}
}
}
if (matchfound) {
len = strlen(mnt->mnt_mountp);
if (len > longestmatch) {
mountmatch = mnt;
longestmatch = len;
}
}
}
}
if (longestmatch == 0) {
return (0);
} else {
/*
* Now we may have found the corresponding autofs mount
* Try to find the matching global zone autofs entry
*/
for (mlp = mlist; mlp; mlp = mlp->mntl_next) {
char p[MAXPATHLEN];
size_t zp_len;
size_t mp_len;
struct mnttab *mnt = mlp->mntl_mnt;
if (strcmp(mountmatch->mnt_special,
mnt->mnt_special) != 0)
continue;
if (strcmp(mnt->mnt_fstype, MNTTYPE_AUTOFS))
continue;
if (strstr(mnt->mnt_mntopts, ZONE_OPT) != NULL)
continue;
/*
* OK, we have a matching global zone automap
* so adjust the path for the global zone.
*/
zp_len = strlen(zonepath);
mp_len = strlen(mnt->mnt_mountp);
(void) strncpy(p, globalpath + zp_len, MAXPATHLEN);
/*
* If both global zone and zone-relative
* mountpoint match, just use the same pathname
*/
if (strncmp(mnt->mnt_mountp, p, mp_len) == 0) {
(void) strncpy(globalpath, p, global_len);
return (1);
} else {
(void) strncpy(p, globalpath, MAXPATHLEN);
(void) strncpy(globalpath, mnt->mnt_mountp,
global_len);
(void) strlcat(globalpath,
p + strlen(mountmatch->mnt_mountp),
global_len);
return (1);
}
}
return (0);
}
}
/*
* Find the pathname for the entry in mlist that corresponds to the
* file named by path (i.e., that names a mount table entry for the
* file system in which path lies).
*
* Return 0 is there an error.
*/
static int
getglobalpath(const char *path, zoneid_t zoneid, struct mntlist *mlist,
char *globalpath)
{
struct mntlist *mlp;
char lofspath[MAXPATHLEN];
char zonepath[MAXPATHLEN];
int longestmatch;
struct mnttab *mountmatch;
if (zoneid != GLOBAL_ZONEID) {
char *prefix;
if ((prefix = getzonerootbyid(zoneid)) == NULL) {
return (0);
}
(void) strncpy(zonepath, prefix, MAXPATHLEN);
(void) strlcpy(globalpath, prefix, MAXPATHLEN);
(void) strlcat(globalpath, path, MAXPATHLEN);
free(prefix);
} else {
(void) strlcpy(globalpath, path, MAXPATHLEN);
}
for (;;) {
longestmatch = 0;
for (mlp = mlist; mlp; mlp = mlp->mntl_next) {
struct mnttab *mnt = mlp->mntl_mnt;
int len;
if (subpath(globalpath, mnt->mnt_mountp) != 0)
continue;
len = strlen(mnt->mnt_mountp);
if (len > longestmatch) {
mountmatch = mnt;
longestmatch = len;
}
}
/*
* Handle interesting mounts.
*/
if ((strcmp(mountmatch->mnt_fstype, MNTTYPE_NFS) == 0) ||
(strcmp(mountmatch->mnt_fstype, MNTTYPE_AUTOFS) == 0)) {
if (zoneid > GLOBAL_ZONEID) {
struct mnttab *m = NULL;
if (strcmp(mountmatch->mnt_fstype,
MNTTYPE_AUTOFS) == 0)
m = mountmatch;
if (getnfspathbyautofs(mlist, zoneid, m,
globalpath, zonepath, MAXPATHLEN) == 0) {
return (0);
}
}
break;
} else if (strcmp(mountmatch->mnt_fstype, MNTTYPE_LOFS) == 0) {
/*
* count up what's left
*/
int remainder;
remainder = strlen(globalpath) - longestmatch;
if (remainder > 0) {
path = pathsuffix(globalpath,
mountmatch->mnt_mountp);
(void) strlcpy(lofspath, path, MAXPATHLEN);
}
(void) strlcpy(globalpath, mountmatch->mnt_special,
MAXPATHLEN);
if (remainder > 0) {
(void) strlcat(globalpath, lofspath,
MAXPATHLEN);
}
} else {
if ((zoneid > GLOBAL_ZONEID) &&
(strncmp(path, "/home/", strlen("/home/")) == 0)) {
char zonename[ZONENAME_MAX];
/*
* If this is a cross-zone reference to
* a home directory, it must be corrected.
* We should only get here if the zone's
* automounter hasn't yet mounted its
* autofs trigger on /home.
*
* Since it is likely to do so in the
* future, we will assume that the global
* zone already has an equivalent autofs
* mount established. By convention,
* this should be mounted at the
* /zone/<zonename>
*/
if (zone_getattr(zoneid, ZONE_ATTR_NAME,
zonename, ZONENAME_MAX) == -1) {
return (0);
} else {
(void) snprintf(globalpath, MAXPATHLEN,
"/zone/%s%s", zonename, path);
}
}
break;
}
}
return (1);
}
/*
* This function is only useful for global zone callers
* It uses the global zone mnttab to translate local zone pathnames
* into global zone pathnames.
*/
char *
getpathbylabel(const char *path_name, char *resolved_path, size_t bufsize,
const bslabel_t *sl)
{
char ret_path[MAXPATHLEN]; /* pathname to return */
zoneid_t zoneid;
struct mntlist *mlist;
if (getzoneid() != GLOBAL_ZONEID) {
errno = EINVAL;
return (NULL);
}
if (path_name[0] != '/') { /* need absolute pathname */
errno = EINVAL;
return (NULL);
}
if (resolved_path == NULL) {
errno = EINVAL;
return (NULL);
}
if ((zoneid = getzoneidbylabel(sl)) == -1)
return (NULL);
/*
* Construct the list of mounted file systems.
*/
if ((mlist = tsol_mkmntlist()) == NULL) {
return (NULL);
}
if (getglobalpath(path_name, zoneid, mlist, ret_path) == 0) {
tsol_mlist_free(mlist);
return (NULL);
}
tsol_mlist_free(mlist);
if (strlen(ret_path) >= bufsize) {
errno = EFAULT;
return (NULL);
}
return (strcpy(resolved_path, ret_path));
} /* end getpathbylabel() */