/* * 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 2015 Nexenta Systems, Inc. All rights reserved. */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../lib/sharetab.h" #include "hashset.h" #include "mountd.h" static char RMTAB[] = "/etc/rmtab"; static FILE *rmtabf = NULL; /* * There is nothing magic about the value selected here. Too low, * and mountd might spend too much time rewriting the rmtab file. * Too high, it won't do it frequently enough. */ static int rmtab_del_thresh = 250; #define RMTAB_TOOMANY_DELETED() \ ((rmtab_deleted > rmtab_del_thresh) && (rmtab_deleted > rmtab_inuse)) /* * mountd's version of a "struct mountlist". It is the same except * for the added ml_pos field. */ struct mntentry { char *m_host; char *m_path; long m_pos; }; static HASHSET mntlist; static int mntentry_equal(const void *, const void *); static uint32_t mntentry_hash(const void *); static int mntlist_contains(char *, char *); static void rmtab_delete(long); static long rmtab_insert(char *, char *); static void rmtab_rewrite(void); static void rmtab_parse(char *buf); static bool_t xdr_mntlistencode(XDR * xdrs, HASHSET * mntlist); #define exstrdup(s) \ strcpy(exmalloc(strlen(s)+1), s) static int rmtab_inuse; static int rmtab_deleted; static rwlock_t rmtab_lock; /* lock to protect rmtab list */ /* * Check whether the given client/path combination * already appears in the mount list. */ static int mntlist_contains(char *host, char *path) { struct mntentry m; m.m_host = host; m.m_path = path; return (h_get(mntlist, &m) != NULL); } /* * Add an entry to the mount list. * First check whether it's there already - the client * may have crashed and be rebooting. */ static void mntlist_insert(char *host, char *path) { if (!mntlist_contains(host, path)) { struct mntentry *m; m = exmalloc(sizeof (struct mntentry)); m->m_host = exstrdup(host); m->m_path = exstrdup(path); m->m_pos = rmtab_insert(host, path); (void) h_put(mntlist, m); } } void mntlist_new(char *host, char *path) { (void) rw_wrlock(&rmtab_lock); mntlist_insert(host, path); (void) rw_unlock(&rmtab_lock); } /* * Delete an entry from the mount list. */ void mntlist_delete(char *host, char *path) { struct mntentry *m, mm; mm.m_host = host; mm.m_path = path; (void) rw_wrlock(&rmtab_lock); if ((m = (struct mntentry *)h_get(mntlist, &mm)) != NULL) { rmtab_delete(m->m_pos); (void) h_delete(mntlist, m); free(m->m_path); free(m->m_host); free(m); if (RMTAB_TOOMANY_DELETED()) rmtab_rewrite(); } (void) rw_unlock(&rmtab_lock); } /* * Delete all entries for a host from the mount list */ void mntlist_delete_all(char *host) { HASHSET_ITERATOR iterator; struct mntentry *m; (void) rw_wrlock(&rmtab_lock); iterator = h_iterator(mntlist); while ((m = (struct mntentry *)h_next(iterator)) != NULL) { if (strcasecmp(m->m_host, host)) continue; rmtab_delete(m->m_pos); (void) h_delete(mntlist, m); free(m->m_path); free(m->m_host); free(m); } if (RMTAB_TOOMANY_DELETED()) rmtab_rewrite(); (void) rw_unlock(&rmtab_lock); if (iterator != NULL) free(iterator); } /* * Equivalent to xdr_mountlist from librpcsvc but for HASHSET * rather that for a linked list. It is used only to encode data * from HASHSET before sending it over the wire. */ static bool_t xdr_mntlistencode(XDR *xdrs, HASHSET *mntlist) { HASHSET_ITERATOR iterator = h_iterator(*mntlist); for (;;) { struct mntentry *m = (struct mntentry *)h_next(iterator); bool_t more_data = (m != NULL); if (!xdr_bool(xdrs, &more_data)) { if (iterator != NULL) free(iterator); return (FALSE); } if (!more_data) break; if ((!xdr_name(xdrs, &m->m_host)) || (!xdr_dirpath(xdrs, &m->m_path))) { if (iterator != NULL) free(iterator); return (FALSE); } } if (iterator != NULL) free(iterator); return (TRUE); } void mntlist_send(SVCXPRT *transp) { (void) rw_rdlock(&rmtab_lock); errno = 0; if (!svc_sendreply(transp, xdr_mntlistencode, (char *)&mntlist)) log_cant_reply(transp); (void) rw_unlock(&rmtab_lock); } /* * Compute a 32 bit hash value for an mntlist entry. */ /* * The string hashing algorithm is from the "Dragon Book" -- * "Compilers: Principles, Tools & Techniques", by Aho, Sethi, Ullman * * And is modified for this application from usr/src/uts/common/os/modhash.c */ static uint_t mntentry_str_hash(char *s, uint_t hash) { uint_t g; for (; *s != '\0'; s++) { hash = (hash << 4) + *s; if ((g = (hash & 0xf0000000)) != 0) { hash ^= (g >> 24); hash ^= g; } } return (hash); } static uint32_t mntentry_hash(const void *p) { struct mntentry *m = (struct mntentry *)p; uint_t hash; hash = mntentry_str_hash(m->m_host, 0); hash = mntentry_str_hash(m->m_path, hash); return (hash); } /* * Compare mntlist entries. * The comparison ignores a value of m_pos. */ static int mntentry_equal(const void *p1, const void *p2) { struct mntentry *m1 = (struct mntentry *)p1; struct mntentry *m2 = (struct mntentry *)p2; return ((strcasecmp(m1->m_host, m2->m_host) || strcmp(m1->m_path, m2->m_path)) ? 0 : 1); } /* * Rewrite /etc/rmtab with a current content of mntlist. */ static void rmtab_rewrite() { if (rmtabf) (void) fclose(rmtabf); /* Rewrite the file. */ if ((rmtabf = fopen(RMTAB, "w+")) != NULL) { HASHSET_ITERATOR iterator; struct mntentry *m; (void) fchmod(fileno(rmtabf), (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)); rmtab_inuse = rmtab_deleted = 0; iterator = h_iterator(mntlist); while ((m = (struct mntentry *)h_next(iterator)) != NULL) m->m_pos = rmtab_insert(m->m_host, m->m_path); if (iterator != NULL) free(iterator); } } /* * Parse the content of /etc/rmtab and insert the entries into mntlist. * The buffer s should be ended with a NUL char. */ static void rmtab_parse(char *s) { char *host; char *path; char *tmp; struct in6_addr ipv6addr; host_part: if (*s == '#') goto skip_rest; host = s; for (;;) { switch (*s++) { case '\0': return; case '\n': goto host_part; case ':': s[-1] = '\0'; goto path_part; case '[': tmp = strchr(s, ']'); if (tmp) { *tmp = '\0'; if (inet_pton(AF_INET6, s, &ipv6addr) > 0) { host = s; s = ++tmp; } else *tmp = ']'; } /* FALLTHROUGH */ default: continue; } } path_part: path = s; for (;;) { switch (*s++) { case '\n': s[-1] = '\0'; if (*host && *path) mntlist_insert(host, path); goto host_part; case '\0': if (*host && *path) mntlist_insert(host, path); return; default: continue; } } skip_rest: for (;;) { switch (*++s) { case '\n': goto host_part; case '\0': return; default: continue; } } } /* * Read in contents of rmtab. * Call rmtab_parse to parse the file and store entries in mntlist. * Rewrites the file to get rid of unused entries. */ #define RMTAB_LOADLEN (16*2024) /* Max bytes to read at a time */ void rmtab_load() { FILE *fp; (void) rwlock_init(&rmtab_lock, USYNC_THREAD, NULL); /* * Don't need to lock the list at this point * because there's only a single thread running. */ mntlist = h_create(mntentry_hash, mntentry_equal, 101, 0.75); if ((fp = fopen(RMTAB, "r")) != NULL) { char buf[RMTAB_LOADLEN+1]; size_t len; /* * Read at most RMTAB_LOADLEN bytes from /etc/rmtab. * - if fread returns RMTAB_LOADLEN we can be in the middle * of a line so change the last newline character into NUL * and seek back to the next character after newline. * - otherwise set NUL behind the last character read. */ while ((len = fread(buf, 1, RMTAB_LOADLEN, fp)) > 0) { if (len == RMTAB_LOADLEN) { int i; for (i = 1; i < len; i++) { if (buf[len-i] == '\n') { buf[len-i] = '\0'; (void) fseek(fp, -i + 1, SEEK_CUR); goto parse; } } } /* Put a NUL character at the end of buffer */ buf[len] = '\0'; parse: rmtab_parse(buf); } (void) fclose(fp); } rmtab_rewrite(); } /* * Write an entry at the current location in rmtab * for the given client and path. * * Returns the starting position of the entry * or -1 if there was an error. */ long rmtab_insert(char *host, char *path) { long pos; struct in6_addr ipv6addr; if (rmtabf == NULL || fseek(rmtabf, 0L, 2) == -1) { return (-1); } pos = ftell(rmtabf); /* * Check if host is an IPv6 literal */ if (inet_pton(AF_INET6, host, &ipv6addr) > 0) { if (fprintf(rmtabf, "[%s]:%s\n", host, path) == EOF) { return (-1); } } else { if (fprintf(rmtabf, "%s:%s\n", host, path) == EOF) { return (-1); } } if (fflush(rmtabf) == EOF) { return (-1); } rmtab_inuse++; return (pos); } /* * Mark as unused the rmtab entry at the given offset in the file. */ void rmtab_delete(long pos) { if (rmtabf != NULL && pos != -1 && fseek(rmtabf, pos, 0) == 0) { (void) fprintf(rmtabf, "#"); (void) fflush(rmtabf); rmtab_inuse--; rmtab_deleted++; } }