+static void write_fsloc(FILE *f, struct exportent *ep)
+{
+ struct servers *servers;
+
+ if (ep->e_fslocmethod == FSLOC_NONE)
+ return;
+
+ servers = replicas_lookup(ep->e_fslocmethod, ep->e_fslocdata);
+ if (!servers)
+ return;
+ qword_print(f, "fsloc");
+ qword_printint(f, servers->h_num);
+ if (servers->h_num >= 0) {
+ int i;
+ for (i=0; i<servers->h_num; i++) {
+ qword_print(f, servers->h_mp[i]->h_host);
+ qword_print(f, servers->h_mp[i]->h_path);
+ }
+ }
+ qword_printint(f, servers->h_referral);
+ release_replicas(servers);
+}
+
+static void write_secinfo(FILE *f, struct exportent *ep, int flag_mask)
+{
+ struct sec_entry *p;
+
+ for (p = ep->e_secinfo; p->flav; p++)
+ ; /* Do nothing */
+ if (p == ep->e_secinfo) {
+ /* There was no sec= option */
+ return;
+ }
+ fix_pseudoflavor_flags(ep);
+ qword_print(f, "secinfo");
+ qword_printint(f, p - ep->e_secinfo);
+ for (p = ep->e_secinfo; p->flav; p++) {
+ qword_printint(f, p->flav->fnum);
+ qword_printint(f, p->flags & flag_mask);
+ }
+
+}
+
+static int dump_to_cache(FILE *f, char *domain, char *path, struct exportent *exp)
+{
+ qword_print(f, domain);
+ qword_print(f, path);
+ if (exp) {
+ int different_fs = strcmp(path, exp->e_path) != 0;
+ int flag_mask = different_fs ? ~NFSEXP_FSID : ~0;
+
+ qword_printtimefrom(f, exp->e_ttl);
+ qword_printint(f, exp->e_flags & flag_mask);
+ qword_printint(f, exp->e_anonuid);
+ qword_printint(f, exp->e_anongid);
+ qword_printint(f, exp->e_fsid);
+ write_fsloc(f, exp);
+ write_secinfo(f, exp, flag_mask);
+ if (exp->e_uuid == NULL || different_fs) {
+ char u[16];
+ if (uuid_by_path(path, 0, 16, u)) {
+ qword_print(f, "uuid");
+ qword_printhex(f, u, 16);
+ }
+ } else {
+ char u[16];
+ get_uuid(exp->e_uuid, 16, u);
+ qword_print(f, "uuid");
+ qword_printhex(f, u, 16);
+ }
+ } else
+ qword_printtimefrom(f, DEFAULT_TTL);
+ return qword_eol(f);
+}
+
+static nfs_export *
+lookup_export(char *dom, char *path, struct addrinfo *ai)
+{
+ nfs_export *exp;
+ nfs_export *found = NULL;
+ int found_type = 0;
+ int i;
+
+ for (i=0 ; i < MCL_MAXTYPES; i++) {
+ for (exp = exportlist[i].p_head; exp; exp = exp->m_next) {
+ if (!export_matches(exp, dom, path, ai))
+ continue;
+ if (!found) {
+ found = exp;
+ found_type = i;
+ continue;
+ }
+ /* Always prefer non-V4ROOT exports */
+ if (exp->m_export.e_flags & NFSEXP_V4ROOT)
+ continue;
+ if (found->m_export.e_flags & NFSEXP_V4ROOT) {
+ found = exp;
+ found_type = i;
+ continue;
+ }
+
+ /* If one is a CROSSMOUNT, then prefer the longest path */
+ if (((found->m_export.e_flags & NFSEXP_CROSSMOUNT) ||
+ (exp->m_export.e_flags & NFSEXP_CROSSMOUNT)) &&
+ strlen(found->m_export.e_path) !=
+ strlen(exp->m_export.e_path)) {
+
+ if (strlen(exp->m_export.e_path) >
+ strlen(found->m_export.e_path)) {
+ found = exp;
+ found_type = i;
+ }
+ continue;
+
+ } else if (found_type == i && found->m_warned == 0) {
+ xlog(L_WARNING, "%s exported to both %s and %s, "
+ "arbitrarily choosing options from first",
+ path, found->m_client->m_hostname, exp->m_client->m_hostname,
+ dom);
+ found->m_warned = 1;
+ }
+ }
+ }
+ return found;
+}
+
+#ifdef HAVE_NFS_PLUGIN_H
+#include <dlfcn.h>
+#include <link.h>
+#include <nfs-plugin.h>
+
+/*
+ * Find the export entry for the parent of "pathname".
+ * Caller must not free returned exportent.
+ */
+static struct exportent *lookup_parent_export(char *dom,
+ const char *pathname, struct addrinfo *ai)
+{
+ char *parent, *slash;
+ nfs_export *result;
+
+ parent = strdup(pathname);
+ if (parent == NULL) {
+ xlog(D_GENERAL, "%s: failed to allocate parent path buffer",
+ __func__);
+ goto out_default;
+ }
+ xlog(D_CALL, "%s: pathname = '%s'", __func__, pathname);
+
+again:
+ /* shorten pathname by one component */
+ slash = strrchr(parent, '/');
+ if (slash == NULL) {
+ xlog(D_GENERAL, "%s: no slash found in pathname",
+ __func__);
+ goto out_default;
+ }
+ *slash = '\0';
+
+ if (strlen(parent) == 0) {
+ result = lookup_export(dom, "/", ai);
+ if (result == NULL) {
+ xlog(L_ERROR, "%s: no root export found.", __func__);
+ goto out_default;
+ }
+ goto out;
+ }
+
+ result = lookup_export(dom, parent, ai);
+ if (result == NULL) {
+ xlog(D_GENERAL, "%s: lookup_export(%s) found nothing",
+ __func__, parent);
+ goto again;
+ }
+
+out:
+ xlog(D_CALL, "%s: found export for %s", __func__, parent);
+ free(parent);
+ return &result->m_export;
+
+out_default:
+ free(parent);
+ return mkexportent("*", "/", "insecure");
+}
+
+/*
+ * Walk through a set of FS locations and build an e_fslocdata string.
+ * Returns true if all went to plan; otherwise, false.
+ */
+static bool locations_to_fslocdata(struct jp_ops *ops,
+ nfs_fsloc_set_t locations, char *fslocdata,
+ size_t remaining, int *ttl)
+{
+ char *server, *last_path, *rootpath, *ptr;
+ _Bool seen = false;
+
+ last_path = NULL;
+ rootpath = NULL;
+ server = NULL;
+ ptr = fslocdata;
+ *ttl = 0;
+
+ for (;;) {
+ enum jp_status status;
+ int len;
+
+ status = ops->jp_get_next_location(locations, &server,
+ &rootpath, ttl);
+ if (status == JP_EMPTY)
+ break;
+ if (status != JP_OK) {
+ xlog(D_GENERAL, "%s: failed to parse location: %s",
+ __func__, ops->jp_error(status));
+ goto out_false;
+ }
+ xlog(D_GENERAL, "%s: Location: %s:%s",
+ __func__, server, rootpath);
+
+ if (last_path && strcmp(rootpath, last_path) == 0) {
+ len = snprintf(ptr, remaining, "+%s", server);
+ if (len < 0) {
+ xlog(D_GENERAL, "%s: snprintf: %m", __func__);
+ goto out_false;
+ }
+ if ((size_t)len >= remaining) {
+ xlog(D_GENERAL, "%s: fslocdata buffer overflow", __func__);
+ goto out_false;
+ }
+ remaining -= (size_t)len;
+ ptr += len;
+ } else {
+ if (last_path == NULL)
+ len = snprintf(ptr, remaining, "%s@%s",
+ rootpath, server);
+ else
+ len = snprintf(ptr, remaining, ":%s@%s",
+ rootpath, server);
+ if (len < 0) {
+ xlog(D_GENERAL, "%s: snprintf: %m", __func__);
+ goto out_false;
+ }
+ if ((size_t)len >= remaining) {
+ xlog(D_GENERAL, "%s: fslocdata buffer overflow",
+ __func__);
+ goto out_false;
+ }
+ remaining -= (size_t)len;
+ ptr += len;
+ last_path = rootpath;
+ }
+
+ seen = true;
+ free(rootpath);
+ free(server);
+ }
+
+ xlog(D_CALL, "%s: fslocdata='%s', ttl=%d",
+ __func__, fslocdata, *ttl);
+ return seen;
+
+out_false:
+ free(rootpath);
+ free(server);
+ return false;
+}
+
+/*
+ * Duplicate the junction's parent's export options and graft in
+ * the fslocdata we constructed from the locations list.
+ */
+static struct exportent *create_junction_exportent(struct exportent *parent,
+ const char *junction, const char *fslocdata, int ttl)
+{
+ static struct exportent *eep;
+
+ eep = (struct exportent *)malloc(sizeof(*eep));
+ if (eep == NULL)
+ goto out_nomem;
+
+ dupexportent(eep, parent);
+ strcpy(eep->e_path, junction);
+ eep->e_hostname = strdup(parent->e_hostname);
+ if (eep->e_hostname == NULL) {
+ free(eep);
+ goto out_nomem;
+ }
+ free(eep->e_uuid);
+ eep->e_uuid = NULL;
+ eep->e_ttl = (unsigned int)ttl;
+
+ free(eep->e_fslocdata);
+ eep->e_fslocdata = strdup(fslocdata);
+ if (eep->e_fslocdata == NULL) {
+ free(eep->e_hostname);
+ free(eep);
+ goto out_nomem;
+ }
+ eep->e_fslocmethod = FSLOC_REFER;
+ return eep;
+
+out_nomem:
+ xlog(L_ERROR, "%s: No memory", __func__);
+ return NULL;
+}
+
+/*
+ * Walk through the set of FS locations and build an exportent.
+ * Returns pointer to an exportent if "junction" refers to a junction.
+ */
+static struct exportent *locations_to_export(struct jp_ops *ops,
+ nfs_fsloc_set_t locations, const char *junction,
+ struct exportent *parent)
+{
+ static char fslocdata[BUFSIZ];
+ int ttl;
+
+ fslocdata[0] = '\0';
+ if (!locations_to_fslocdata(ops, locations,
+ fslocdata, sizeof(fslocdata), &ttl))
+ return NULL;
+ return create_junction_exportent(parent, junction, fslocdata, ttl);
+}
+
+/*
+ * Retrieve locations information in "junction" and dump it to the
+ * kernel. Returns pointer to an exportent if "junction" refers
+ * to a junction.
+ */
+static struct exportent *invoke_junction_ops(void *handle, char *dom,
+ const char *junction, struct addrinfo *ai)
+{
+ struct exportent *parent, *exp = NULL;
+ nfs_fsloc_set_t locations;
+ enum jp_status status;
+ struct jp_ops *ops;
+ char *error;
+
+ ops = (struct jp_ops *)dlsym(handle, "nfs_junction_ops");
+ error = dlerror();
+ if (error != NULL) {
+ xlog(D_GENERAL, "%s: dlsym(jp_junction_ops): %s",
+ __func__, error);
+ return NULL;
+ }
+ if (ops->jp_api_version != JP_API_VERSION) {
+ xlog(D_GENERAL, "%s: unrecognized junction API version: %u",
+ __func__, ops->jp_api_version);
+ return NULL;
+ }
+
+ status = ops->jp_init(false);
+ if (status != JP_OK) {
+ xlog(D_GENERAL, "%s: failed to resolve %s: %s",
+ __func__, junction, ops->jp_error(status));
+ return NULL;
+ }
+
+ status = ops->jp_get_locations(junction, &locations);
+ switch (status) {
+ case JP_OK:
+ break;
+ case JP_NOTJUNCTION:
+ xlog(D_GENERAL, "%s: %s is not a junction",
+ __func__, junction);
+ goto out;
+ default:
+ xlog(L_WARNING, "Dangling junction %s: %s",
+ junction, ops->jp_error(status));
+ goto out;
+ }
+
+ parent = lookup_parent_export(dom, junction, ai);
+ if (parent == NULL)
+ goto out;
+
+ exp = locations_to_export(ops, locations, junction, parent);
+
+ ops->jp_put_locations(locations);
+
+out:
+ ops->jp_done();
+ return exp;
+}
+
+/*
+ * Load the junction plug-in, then try to resolve "pathname".
+ * Returns pointer to an initialized exportent if "junction"
+ * refers to a junction, or NULL if not.
+ */
+static struct exportent *lookup_junction(char *dom, const char *pathname,
+ struct addrinfo *ai)
+{
+ struct exportent *exp;
+ struct link_map *map;
+ void *handle;
+
+ handle = dlopen("libnfsjunct.so", RTLD_NOW);
+ if (handle == NULL) {
+ xlog(D_GENERAL, "%s: dlopen: %s", __func__, dlerror());
+ return NULL;
+ }
+
+ if (dlinfo(handle, RTLD_DI_LINKMAP, &map) == 0)
+ xlog(D_GENERAL, "%s: loaded plug-in %s",
+ __func__, map->l_name);
+
+ (void)dlerror(); /* Clear any error */
+
+ exp = invoke_junction_ops(handle, dom, pathname, ai);
+
+ /* We could leave it loaded to make junction resolution
+ * faster next time. However, if we want to replace the
+ * library, that would require restarting mountd. */
+ (void)dlclose(handle);
+ return exp;
+}
+
+static void lookup_nonexport(FILE *f, char *dom, char *path,
+ struct addrinfo *ai)
+{
+ struct exportent *eep;
+
+ eep = lookup_junction(dom, path, ai);
+ dump_to_cache(f, dom, path, eep);
+ if (eep == NULL)
+ return;
+ exportent_release(eep);
+ free(eep);
+}
+#else /* !HAVE_NFS_PLUGIN_H */
+static void lookup_nonexport(FILE *f, char *dom, char *path,
+ struct addrinfo *UNUSED(ai))
+{
+ dump_to_cache(f, dom, path, NULL);
+}
+#endif /* !HAVE_NFS_PLUGIN_H */
+
+static void nfsd_export(FILE *f)