# # 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 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright 2018 Nexenta Systems, Inc. All rights reserved. # # cmd/fs.d/nfs/Makefile # # cmd/fs.d/nfs is the directory of all nfs specific commands # whose executable reside in $(INSDIR1) and $(INSDIR2). # include $(SRC)/Makefile.master SUBDIR1= exportfs nfsd rquotad \ statd nfsstat mountd dfshares \ nfsfind nfs4cbd share tests dtrace # These do "make catalog" SUBDIR2= clear_locks lockd umount showmount \ mount dfmounts nfslog nfsmapid \ nfsref rp_basic SUBDIR3= etc svc SUBDIRS= $(SUBDIR1) $(SUBDIR2) $(SUBDIR3) # for messaging catalog files # # Hammerhead: GNU Make % substitution only replaces first %; use foreach. POFILES= $(foreach d,$(SUBDIR2),$(d)/$(d).po) POFILE= nfs.po all: TARGET= all install: TARGET= install clean: TARGET= clean clobber: TARGET= clobber catalog: TARGET= catalog .KEEP_STATE: .PARALLEL: $(SUBDIRS) all install clean clobber: $(SUBDIRS) catalog: $(SUBDIR2) $(RM) $(POFILE) cat $(POFILES) > $(POFILE) $(SUBDIRS): FRC @cd $@; pwd; $(MAKE) $(TARGET) FRC: # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (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 2004 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. PROG= clear_locks include ../../../Makefile.cmd LDLIBS += -lrpcsvc -lnsl # # Message catalog # POFILE= clear_locks.po # # message catalog # catalog: $(POFILE) $(POFILE): clear_locks.c $(RM) $@ $(COMPILE.cpp) clear_locks.c > $(POFILE).i $(XGETTEXT) $(XGETFLAGS) $(POFILE).i sed "/^domain/d" messages.po > $@ $(RM) messages.po $(POFILE).i CSTD= $(CSTD_GNU99) CFLAGS += $(CCVERBOSE) # not linted SMATCH=off .KEEP_STATE: all: $(PROG) install: all $(ROOTUSRSBINPROG) clean: include ../../../Makefile.targ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright 2023 MNX Cloud, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern char *optarg; extern int optind; extern int _nfssys(enum nfssys_op, void *); static int share_zap(char *, char *); /* * Clear locks and v4 related state held by * 'client'. */ static int nfs4_clr_state(char *client) { int he_error; char he_buf[NSS_BUFLEN_HOSTS]; struct hostent host_ent, *he; char **ap; struct nfs4clrst_args arg; if ((he = gethostbyname_r(client, &host_ent, he_buf, sizeof (he_buf), &he_error)) == NULL) { (void) fprintf(stderr, gettext("client name '%s' can not be resolved\n"), client); return (1); } if (he_error) { perror("gethostbyname"); return (1); } /* * The NFS4 clear state interface is * versioned in case we need to pass * more information in the future. */ arg.vers = NFS4_CLRST_VERSION; arg.addr_type = he->h_addrtype; /* * Iterate over IP Addresses clear * state for each. */ for (ap = he->h_addr_list; *ap; ap++) { arg.ap = *ap; if (_nfssys(NFS4_CLR_STATE, &arg) != 0) { perror("nfssys(NFS4_CLR_STATE)"); return (1); } } return (0); } int main(int argc, char *argv[]) { int i, c, ret; int sflag = 0; int errflg = 0; char myhostname[MAXHOSTNAMELEN]; if (geteuid() != (uid_t)0) { (void) fprintf(stderr, gettext("clear_locks: must be root\n")); exit(1); } (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif (void) textdomain(TEXT_DOMAIN); /* * Get the official hostname for this host */ sysinfo(SI_HOSTNAME, myhostname, sizeof (myhostname)); while ((c = getopt(argc, argv, "s")) != EOF) { switch (c) { case 's': sflag++; break; case '?': errflg++; } } i = argc - optind; if (errflg || i != 1) { (void) fprintf(stderr, gettext("Usage: clear_locks [-s] hostname\n")); exit(2); } if (sflag) { (void) fprintf(stdout, gettext( "Clearing locks held for NFS client %s on server %s\n"), myhostname, argv[optind]); ret = share_zap(myhostname, argv[optind]); } else { (void) fprintf(stdout, gettext( "Clearing locks held for NFS client %s on server %s\n"), argv[optind], myhostname); ret = share_zap(argv[optind], myhostname); ret += nfs4_clr_state(argv[optind]); } return (ret); } /* * Request that host 'server' free all locks held by * host 'client'. */ static int share_zap(char *client, char *server) { struct nlm_notify notify; enum clnt_stat rslt; notify.state = 0; notify.name = client; rslt = rpc_call(server, NLM_PROG, NLM_VERSX, NLM_FREE_ALL, xdr_nlm_notify, (char *)¬ify, xdr_void, 0, NULL); if (rslt != RPC_SUCCESS) { clnt_perrno(rslt); return (3); } (void) fprintf(stderr, gettext("clear of locks held for %s on %s returned success\n"), client, server); return (0); } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (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) 1989 by Sun Microsystems, Inc. # # Copyright (c) 2018, Joyent, Inc. FSTYPE= nfs LIBPROG= dfmounts ATTMK= $(LIBPROG) include ../../Makefile.fstype OBJS= $(LIBPROG).o clnt_subr.o SRCS= $(LIBPROG).c ../lib/clnt_subr.c # # Message catalog # POFILES= $(OBJS:%.o=%.po) POFILE= dfmounts.po LDLIBS += -lrpcsvc -lnsl CPPFLAGS += -I../lib $(LIBPROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) clnt_subr.o: ../lib/clnt_subr.c $(COMPILE.c) ../lib/clnt_subr.c # # message catalog # catalog: $(POFILE) $(POFILE): $(SRCS) $(RM) $@ $(COMPILE.cpp) $(SRCS) > $(POFILE).i $(XGETTEXT) $(XGETFLAGS) $(POFILE).i sed "/^domain/d" messages.po > $@ $(RM) messages.po $(POFILE).i clean: $(RM) $(OBJS) /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California * All Rights Reserved * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ /* * nfs dfmounts */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int hflg; static void pr_mounts(char *); static void freemntlist(struct mountbody *); static int sortpath(const void *, const void *); static void usage(void); int main(int argc, char *argv[]) { char hostbuf[256]; extern int optind; int i, c; (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif (void) textdomain(TEXT_DOMAIN); while ((c = getopt(argc, argv, "h")) != EOF) { switch (c) { case 'h': hflg++; break; default: usage(); exit(1); } } if (optind < argc) { for (i = optind; i < argc; i++) pr_mounts(argv[i]); } else { if (gethostname(hostbuf, sizeof (hostbuf)) < 0) { perror("nfs dfmounts: gethostname"); exit(1); } pr_mounts(hostbuf); } return (0); } #define NTABLEENTRIES 2048 static struct mountbody *table[NTABLEENTRIES]; static struct timeval rpc_totout_new = {15, 0}; /* * Print the filesystems on "host" that are currently mounted by a client. */ static void pr_mounts(char *host) { CLIENT *cl; struct mountbody *ml = NULL; struct mountbody **tb, **endtb; enum clnt_stat err; char *lastpath; char *lastclient; int tail = 0; struct timeval tout, rpc_totout_old; (void) __rpc_control(CLCR_GET_RPCB_TIMEOUT, &rpc_totout_old); (void) __rpc_control(CLCR_SET_RPCB_TIMEOUT, &rpc_totout_new); cl = mountprog_client_create(host, &rpc_totout_old); if (cl == NULL) { return; } (void) __rpc_control(CLCR_SET_RPCB_TIMEOUT, &rpc_totout_old); tout.tv_sec = 10; tout.tv_usec = 0; err = clnt_call(cl, MOUNTPROC_DUMP, xdr_void, 0, xdr_mountlist, (caddr_t)&ml, tout); if (err != 0) { pr_err("%s\n", clnt_sperrno(err)); clnt_destroy(cl); return; } if (ml == NULL) return; /* no mounts */ if (!hflg) { printf("%-8s %10s %-24s %s", gettext("RESOURCE"), gettext("SERVER"), gettext("PATHNAME"), gettext("CLIENTS")); hflg++; } /* * Create an array describing the mounts, so that we can sort them. */ tb = table; for (; ml != NULL && tb < &table[NTABLEENTRIES]; ml = ml->ml_next) *tb++ = ml; if (ml != NULL && tb == &table[NTABLEENTRIES]) pr_err(gettext("table overflow: only %d entries shown\n"), NTABLEENTRIES); endtb = tb; qsort(table, endtb - table, sizeof (struct mountbody *), sortpath); /* * Print out the sorted array. Group entries for the same * filesystem together, and ignore duplicate entries. */ lastpath = ""; lastclient = ""; for (tb = table; tb < endtb; tb++) { if (*((*tb)->ml_directory) == '\0' || *((*tb)->ml_hostname) == '\0') continue; if (strcmp(lastpath, (*tb)->ml_directory) == 0) { if (strcmp(lastclient, (*tb)->ml_hostname) == 0) { continue; /* ignore duplicate */ } } else { printf("\n%-8s %10s %-24s ", " -", host, (*tb)->ml_directory); lastpath = (*tb)->ml_directory; tail = 0; } if (tail++) printf(","); printf("%s", (*tb)->ml_hostname); lastclient = (*tb)->ml_hostname; } printf("\n"); freemntlist(ml); clnt_destroy(cl); } static void freemntlist(struct mountbody *ml) { struct mountbody *old; while (ml) { if (ml->ml_hostname) free(ml->ml_hostname); if (ml->ml_directory) free(ml->ml_directory); old = ml; ml = ml->ml_next; free(old); } } /* * Compare two structs for mounted filesystems. The primary sort key is * the name of the exported filesystem. There is also a secondary sort on * the name of the client, so that duplicate entries (same path and * hostname) will sort together. * * Returns < 0 if the first entry sorts before the second entry, 0 if they * sort the same, and > 0 if the first entry sorts after the second entry. */ static int sortpath(const void *a, const void *b) { const struct mountbody **m1, **m2; int result; m1 = (const struct mountbody **)a; m2 = (const struct mountbody **)b; result = strcmp((*m1)->ml_directory, (*m2)->ml_directory); if (result == 0) { result = strcmp((*m1)->ml_hostname, (*m2)->ml_hostname); } return (result); } static void usage(void) { (void) fprintf(stderr, gettext("Usage: dfmounts [-h] [host ...]\n")); } void pr_err(char *fmt, ...) { va_list ap; va_start(ap, fmt); (void) fprintf(stderr, "nfs dfmounts: "); (void) vfprintf(stderr, fmt, ap); va_end(ap); } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (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) 1989 by Sun Microsystems, Inc. # # Copyright (c) 2018, Joyent, Inc. FSTYPE= nfs LIBPROG= dfshares ATTMK= $(LIBPROG) include ../../Makefile.fstype OBJS= $(LIBPROG).o clnt_subr.o SRCS= $(OBJS:%.o=%.c) LDLIBS += -lrpcsvc -lnsl CPPFLAGS += -I../lib clnt_subr.o: ../lib/clnt_subr.c $(COMPILE.c) ../lib/clnt_subr.c $(LIBPROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) clean: $(RM) $(OBJS) /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California * All Rights Reserved * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ /* * nfs dfshares */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include int hflg; void pr_exports(char *); void free_ex(struct exportnode *); void usage(void); int main(int argc, char *argv[]) { char hostbuf[256]; int i, c; while ((c = getopt(argc, argv, "h")) != EOF) { switch (c) { case 'h': hflg++; break; default: usage(); exit(1); } } if (optind < argc) { for (i = optind; i < argc; i++) pr_exports(argv[i]); } else { if (gethostname(hostbuf, sizeof (hostbuf)) < 0) { pr_err("gethostname: %s\n", strerror(errno)); exit(1); } pr_exports(hostbuf); } return (0); } struct timeval rpc_totout_new = {15, 0}; void pr_exports(char *host) { CLIENT *cl; struct exportnode *ex = NULL; enum clnt_stat err; struct timeval tout, rpc_totout_old; (void) __rpc_control(CLCR_GET_RPCB_TIMEOUT, &rpc_totout_old); (void) __rpc_control(CLCR_SET_RPCB_TIMEOUT, &rpc_totout_new); cl = mountprog_client_create(host, &rpc_totout_old); if (cl == NULL) { exit(1); } (void) __rpc_control(CLCR_SET_RPCB_TIMEOUT, &rpc_totout_old); tout.tv_sec = 10; tout.tv_usec = 0; err = clnt_call(cl, MOUNTPROC_EXPORT, xdr_void, 0, xdr_exports, (caddr_t)&ex, tout); if (err != 0) { pr_err("%s\n", clnt_sperrno(err)); clnt_destroy(cl); exit(1); } if (ex == NULL) { clnt_destroy(cl); exit(1); } if (!hflg) { printf("%-35s %12s %-8s %s\n", "RESOURCE", "SERVER", "ACCESS", "TRANSPORT"); hflg++; } while (ex) { printf("%10s:%-24s %12s %-8s %s\n", host, ex->ex_dir, host, " -", " -"); ex = ex->ex_next; } free_ex(ex); clnt_destroy(cl); } void free_ex(struct exportnode *ex) { struct groupnode *gr, *tmpgr; struct exportnode *tmpex; while (ex) { free(ex->ex_dir); gr = ex->ex_groups; while (gr) { tmpgr = gr->gr_next; free(gr); gr = tmpgr; } tmpex = ex; ex = ex->ex_next; free(tmpex); } } void usage(void) { (void) fprintf(stderr, "Usage: dfshares [-h] [host ...]\n"); } void pr_err(char *fmt, ...) { va_list ap; va_start(ap, fmt); (void) fprintf(stderr, "nfs dfshares: "); (void) vfprintf(stderr, fmt, ap); va_end(ap); } # # This file and its contents are supplied under the terms of the # Common Development and Distribution License ("CDDL"), version 1.0. # You may only use this file in accordance with the terms of version # 1.0 of the CDDL. # # A full copy of the text of the CDDL should have accompanied this # source. A copy of the CDDL is also available via the Internet at # http://www.illumos.org/license/CDDL. # # # Copyright 2018 Nexenta Systems, Inc. All rights reserved. # SRCS=nfs-trace.d nfs-time.d include $(SRC)/cmd/Makefile.cmd ROOTNFSDTRACEDIR = $(ROOTLIB)/nfs/dtrace ROOTNFSDTRACEFILE = $(SRCS:%=$(ROOTNFSDTRACEDIR)/%) $(ROOTNFSDTRACEFILE): FILEMODE = 0555 $(ROOTNFSDTRACEDIR): $(INS.dir) $(ROOTNFSDTRACEDIR)/%: % $(INS.file) all: clean: include $(SRC)/cmd/Makefile.targ install: all $(ROOTNFSDTRACEDIR) .WAIT $(ROOTNFSDTRACEFILE) #!/usr/sbin/dtrace -s /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2018 Nexenta Systems, Inc. All rights reserved. */ /* * Quantize the time spent in each NFSv3 andf NFSv4 operation, * optionally for a specified client, share and zone. * * Usage: nfs-time.d [|all [|all] []]] * * example: nfs_time.d 192.168.123.1 /mypool/fs1 0 * * It is valid to specify or as "all" * to quantize data for all clients and/or all shares. * Omitting will quantize data for all zones. */ #pragma D option flowindent #pragma D option defaultargs dtrace:::BEGIN { all_clients = (($$1 == NULL) || ($$1 == "all")) ? 1 : 0; all_shares = (($$2 == NULL) || ($$2 == "all")) ? 1 : 0; all_zones = ($$3 == NULL) ? 1 : 0; client = $$1; share = $$2; zoneid = $3; printf("%Y - client=%s share=%s zone=%s)\n", walltimestamp, (all_clients) ? "all" : client, (all_shares) ? "all" : share, (all_zones) ? "all" : $$3); } nfsv3:::op-*-start, nfsv4:::op-*-start { self->ts[probefunc] = timestamp; } nfsv3:::op-*-done, nfsv4:::op-*-done / ((all_clients) || (args[0]->ci_remote == client)) && ((all_shares) || (args[1]->noi_shrpath == share)) && ((all_zones) || (args[1]->noi_zoneid == zoneid)) / { elapsed = (timestamp - self->ts[probefunc]); @q[probefunc]=quantize(elapsed); } tick-5s { printa(@q); /* * uncomment "clear" to quantize per 5s interval * rather than cumulative for duration of script. * clear(@q); */ } dtrace:::END { } #!/usr/sbin/dtrace -s /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2018 Nexenta Systems, Inc. All rights reserved. */ /* * Print input and output values for each NFSv3 andf NFSv4 operation, * optionally for a specified client, share and zone. * * Usage: nfs-trace.d [|all [|all] []]] * * example: nfs_trace.d 192.168.123.1 /mypool/fs1 0 * * It is valid to specify or as "all" * to quantize data for all clients and/or all shares. * Omitting will quantize data for all zones. */ /* * Unfortunately, trying to write this script using wildcards, for example: * nfsv3:::op-*-start {} * nfsv3:::op-*-done {} * prints the operation-specific args[2] structure as the incorrect type. * Until this is resolved it is necessary to explicitly list each operation. * * See nfs-time.d for an example of using the wildcard format when there are * no operation-specific args (args[2]) being traced. */ #pragma D option flowindent #pragma D option defaultargs dtrace:::BEGIN { all_clients = (($$1 == NULL) || ($$1 == "all")) ? 1 : 0; all_shares = (($$2 == NULL) || ($$2 == "all")) ? 1 : 0; all_zones = ($$3 == NULL) ? 1 : 0; client = $$1; share = $$2; zoneid = $3; printf("%Y - client=%s share=%s zone=%s)\n", walltimestamp, (all_clients) ? "all" : client, (all_shares) ? "all" : share, (all_zones) ? "all" : $$3); } nfsv3:::op-getattr-start, nfsv3:::op-setattr-start, nfsv3:::op-lookup-start, nfsv3:::op-access-start, nfsv3:::op-commit-start, nfsv3:::op-create-start, nfsv3:::op-fsinfo-start, nfsv3:::op-fsstat-start, nfsv3:::op-link-start, nfsv3:::op-mkdir-start, nfsv3:::op-mknod-start, nfsv3:::op-pathconf-start, nfsv3:::op-read-start, nfsv3:::op-readdir-start, nfsv3:::op-readdirplus-start, nfsv3:::op-readlink-start, nfsv3:::op-remove-start, nfsv3:::op-rename-start, nfsv3:::op-rmdir-start, nfsv3:::op-symlink-start, nfsv3:::op-write-start / ((all_clients) || (args[0]->ci_remote == client)) && ((all_shares) || (args[1]->noi_shrpath == share)) && ((all_zones) || (args[1]->noi_zoneid == zoneid)) / { printf("\n"); print(*args[0]); printf("\n"); print(*args[1]); printf("\n"); print(*args[2]); printf("\n"); } nfsv3:::op-getattr-done, nfsv3:::op-setattr-done, nfsv3:::op-lookup-done, nfsv3:::op-access-done, nfsv3:::op-commit-done, nfsv3:::op-create-done, nfsv3:::op-fsinfo-done, nfsv3:::op-fsstat-done, nfsv3:::op-link-done, nfsv3:::op-mkdir-done, nfsv3:::op-mknod-done, nfsv3:::op-pathconf-done, nfsv3:::op-read-done, nfsv3:::op-readdir-done, nfsv3:::op-readdirplus-done, nfsv3:::op-readlink-done, nfsv3:::op-remove-done, nfsv3:::op-rename-done, nfsv3:::op-rmdir-done, nfsv3:::op-symlink-done, nfsv3:::op-write-done / ((all_clients) || (args[0]->ci_remote == client)) && ((all_shares) || (args[1]->noi_shrpath == share)) && ((all_zones) || (args[1]->noi_zoneid == zoneid)) / { /* printf("\n"); print(*args[0]); printf("\n"); print(*args[1]); */ printf("\n"); print(*args[2]); printf("\n"); } nfsv4:::op-access-start, nfsv4:::op-close-start, nfsv4:::op-commit-start, nfsv4:::op-create-start, nfsv4:::op-delegpurge-start, nfsv4:::op-delegreturn-start, nfsv4:::op-getattr-start, nfsv4:::op-link-start, nfsv4:::op-lock-start, nfsv4:::op-lockt-start, nfsv4:::op-locku-start, nfsv4:::op-lookup-start, nfsv4:::op-nverify-start, nfsv4:::op-open-start, nfsv4:::op-open-confirm-start, nfsv4:::op-open-downgrade-start, nfsv4:::op-openattr-start, nfsv4:::op-putfh-start, nfsv4:::op-read-start, nfsv4:::op-readdir-start, nfsv4:::op-release-lockowner-start, nfsv4:::op-remove-start, nfsv4:::op-rename-start, nfsv4:::op-renew-start, nfsv4:::op-secinfo-start, nfsv4:::op-setattr-start, nfsv4:::op-setclientid-start, nfsv4:::op-setclientid-confirm-start, nfsv4:::op-verify-start, nfsv4:::op-write-start / ((all_clients) || (args[0]->ci_remote == client)) && ((all_shares) || (args[1]->noi_shrpath == share)) && ((all_zones) || (args[1]->noi_zoneid == zoneid)) / { printf("\n"); print(*args[0]); printf("\n"); print(*args[1]); printf("\n"); print(*args[2]); printf("\n"); } /* These operations do not have args[2] */ nfsv4:::op-getfh-start, nfsv4:::op-lookupp-start, nfsv4:::op-putpubfh-start, nfsv4:::op-putrootfh-start, nfsv4:::op-readlink-start, nfsv4:::op-restorefh-start, nfsv4:::op-savefh-start / ((all_clients) || (args[0]->ci_remote == client)) && ((all_shares) || (args[1]->noi_shrpath == share)) && ((all_zones) || (args[1]->noi_zoneid == zoneid)) / { printf("\n"); print(*args[0]); printf("\n"); print(*args[1]); printf("\n"); } nfsv4:::op-access-done, nfsv4:::op-close-done, nfsv4:::op-commit-done, nfsv4:::op-create-done, nfsv4:::op-delegpurge-done, nfsv4:::op-delegreturn-done, nfsv4:::op-getattr-done, nfsv4:::op-getfh-done, nfsv4:::op-link-done, nfsv4:::op-lock-done, nfsv4:::op-lockt-done, nfsv4:::op-locku-done, nfsv4:::op-lookup-done, nfsv4:::op-lookupp-done, nfsv4:::op-nverify-done, nfsv4:::op-open-done, nfsv4:::op-open-confirm-done, nfsv4:::op-open-downgrade-done, nfsv4:::op-openattr-done, nfsv4:::op-putfh-done, nfsv4:::op-putpubfh-done, nfsv4:::op-putrootfh-done, nfsv4:::op-read-done, nfsv4:::op-readdir-done, nfsv4:::op-readlink-done, nfsv4:::op-release-lockowner-done, nfsv4:::op-remove-done, nfsv4:::op-rename-done, nfsv4:::op-renew-done, nfsv4:::op-restorefh-done, nfsv4:::op-savefh-done, nfsv4:::op-secinfo-done, nfsv4:::op-setattr-done, nfsv4:::op-setclientid-done, nfsv4:::op-setclientid-confirm-done, nfsv4:::op-verify-done, nfsv4:::op-write-done / ((all_clients) || (args[0]->ci_remote == client)) && ((all_shares) || (args[1]->noi_shrpath == share)) && ((all_zones) || (args[1]->noi_zoneid == zoneid)) / { /* printf("\n"); print(*args[0]); printf("\n"); print(*args[1]); */ printf("\n"); print(*args[2]); printf("\n"); } dtrace:::END { } # # 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 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # DEFAULTFILES = nfs.dfl include ../../../Makefile.cmd ETCNFS= $(ROOTETC)/nfs VARNFS= $(ROOT)/var/nfs TXTS= nfssec.conf NFSTXTS= nfslog.conf V4SSDIR= $(VARNFS)/v4_state $(VARNFS)/v4_oldstate IETCFILES= $(TXTS:%=$(ROOTETC)/%) INFSETCFILES= $(NFSTXTS:%=$(ROOTETC)/nfs/%) FILEMODE= 0644 all: $(TXTS) $(NFSTXTS) install: all $(IETCFILES) $(ETCNFS) $(INFSETCFILES) $(VARNFS) $(V4SSDIR) \ $(IDEFFILES) $(ROOTETCDEFAULTFILES) $(ROOTETC)/%: % $(INS.file) $(ROOTETC)/nfs/%: % $(INS.file) $(ETCNFS): $(INS.dir) $(VARNFS): $(INS.dir) $(V4SSDIR): $(INS.dir) .KEEP_STATE: clean clobber: # # 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) 2010, Oracle and/or its affiliates. All rights reserved. # # # Moved to SMF. Use sharectl(8) to manage NFS properties. # # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (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 # #ident "%Z%%M% %I% %E% SMI" # # Copyright (c) 1999 by Sun Microsystems, Inc. # All rights reserved. # # NFS server log configuration file. # # [ defaultdir= ] \ # [ log= ] [ fhtable= ] \ # [ buffer= ] [ logformat=basic|extended ] # global defaultdir=/var/nfs \ log=nfslog fhtable=fhtable buffer=nfslog_workbuffer # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (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 2001 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # #ident "%Z%%M% %I% %E% SMI" # # The NFS Security Service Configuration File. # # Each entry is of the form: # # \ # # # # The "-" in signifies that this is not a GSS mechanism. # A string entry in is required for using RPCSEC_GSS # services. and are optional. # White space is not an acceptable value. # # default security mode is defined at the end. It should be one of # the flavor numbers defined above it. # none 0 - - - # AUTH_NONE sys 1 - - - # AUTH_SYS dh 3 - - - # AUTH_DH # # Uncomment the following lines to use Kerberos V5 with NFS # #krb5 390003 kerberos_v5 default - # RPCSEC_GSS #krb5i 390004 kerberos_v5 default integrity # RPCSEC_GSS #krb5p 390005 kerberos_v5 default privacy # RPCSEC_GSS default 1 - - - # default is AUTH_SYS # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (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) 1989 by Sun Microsystems, Inc. # # cmd/fs.d/nfs/exportfs/Makefile PROG= exportfs SRCS= exportfs.sh include ../../../Makefile.cmd all: $(PROG) install: all $(ROOTUSRSBINPROG) clean clobber: $(RM) $(PROG) #!/sbin/sh # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (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) 1984, 1986, 1987, 1988, 1989 AT&T # All Rights Reserved #ident "%Z%%M% %I% %E% SMI" #!/bin/sh # # exportfs: compatibility script for SunOs command. # USAGE="Usage: exportfs [-aviu] [-o options] directory" DFSTAB=/etc/dfs/dfstab OPTS="rw" # # Translate from exportfs opts to share opts # fixopts() { IFS=, ; set - $OPTS ; IFS=" " for i do case $i in *access=* ) eval $i ;; esac ; done if [ ! "$access" ] ; then return ; fi OPTS="" for i do case $i in rw=* ) OPTS="$OPTS$i," ;; ro | rw ) OPTS="${OPTS}$i=$access," ; ropt="true" ;; access=* ) ;; * ) OPTS="$OPTS$i," ;; esac done if [ ! "$ropt" ] ; then OPTS="ro=$access,$OPTS" ; fi OPTS=`echo $OPTS | sed 's/,$//'` } bad() { echo $USAGE >&2 exit 1 } PATH=/usr/sbin:/usr/bin:$PATH export PATH if set -- `getopt aviuo: $*` ; then : ; else bad ; fi for i in $* do case $i in -a ) aflg="true" ; shift ;; # share all nfs -v ) vflg="true" ; shift ;; # verbose -i ) iflg="true" ; shift ;; # ignore dfstab opts -u ) uflg="true" ; shift ;; # unshare -o ) oflg="true" ; OPTS=$2 ; shift 2 ;; # option string -- ) shift ; break ;; esac done if [ $aflg ] ; then if [ "$DIR" -o "$iflg" -o "$oflg" ] ; then bad ; fi if [ $uflg ] ; then if [ $vflg ] ; then echo unshareall -F nfs ; fi /usr/sbin/unshareall -F nfs else if [ $vflg ] ; then echo shareall -F nfs ; fi /usr/sbin/shareall -F nfs fi exit $? fi case $# in 0 ) if [ "$iflg" -o "$uflg" -o "$oflg" ] ; then bad ; fi if [ "$vflg" ] ; then echo share -F nfs ; fi /usr/sbin/share -F nfs exit $? ;; 1 ) DIR=$1 ;; * ) bad ;; esac if [ $uflg ] ; then if [ "$iflg" -o "$oflg" ] ; then bad ; fi if [ $vflg ] ; then echo unshare -F nfs $DIR ; fi /usr/sbin/unshare -F nfs $DIR exit $? fi if [ $iflg ] ; then fixopts if [ $vflg ] ; then echo share -F nfs -o $OPTS $DIR ; fi /usr/sbin/share -F nfs -o $OPTS $DIR else CMD=`grep $DIR'[ ]*$' $DFSTAB` if [ "$CMD" = "" ] ; then echo "exportfs: no entry for $DIR in $DFSTAB" >&2 exit 1 fi if [ $oflg ] ; then echo "exportfs: supplied options ignored" >&2 vflg="true" fi if [ $vflg ] ; then echo $CMD ; fi eval $CMD fi exit $? /* * 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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include "clnt_subr.h" /* * Common code to create client of MOUNTPROG protocol. * Used by dfmounts, dfshares, showmount. */ CLIENT * mountprog_client_create(const char *host, struct timeval *tv) { rpcvers_t versnum; CLIENT *cl; /* * First try circuit, then drop back to datagram if * circuit is unavailable (an old version of mountd perhaps) * Using circuit is preferred because it can handle * arbitrarily long export lists. */ cl = clnt_create_vers(host, MOUNTPROG, &versnum, MOUNTVERS, MOUNTVERS3, "circuit_n"); if (cl == NULL) { if (rpc_createerr.cf_stat == RPC_PROGNOTREGISTERED) cl = clnt_create_vers(host, MOUNTPROG, &versnum, MOUNTVERS, MOUNTVERS3, "datagram_n"); if (cl == NULL) { pr_err(gettext("can't contact server: %s\n"), clnt_spcreateerror(host)); (void) __rpc_control(CLCR_SET_RPCB_TIMEOUT, tv); } } return (cl); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2023 RackTop Systems, Inc. */ #ifndef _CLNT_SUBR_H #define _CLNT_SUBR_H #include #include #ifdef __cplusplus extern "C" { #endif /* * nfs client library routines */ extern CLIENT *mountprog_client_create(const char *, struct timeval *); extern void pr_err(char *, ...); #ifdef __cplusplus } #endif #endif /* _CLNT_SUBR_H */ /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* LINTLIBRARY */ /* PROTOLIB1 */ /* NFS server */ #include #include #include #include #include #include #include #include #include #include #include #include /* * The parent never returns from this function. It sits * and waits for the child to send status on whether it * loaded or not. * * We do not close down the standard file descriptors until * we know the child is going to run. */ int daemonize_init(void) { int status, pfds[2]; sigset_t set, oset; pid_t pid; /* * Block all signals prior to the fork and leave them blocked in the * parent so we don't get in a situation where the parent gets SIGINT * and returns non-zero exit status and the child is actually running. * In the child, restore the signal mask once we've done our setsid(). */ (void) sigfillset(&set); (void) sigdelset(&set, SIGABRT); (void) sigprocmask(SIG_BLOCK, &set, &oset); /* * We need to do this before we open the pipe - it makes things * easier in the long run. */ closefrom(STDERR_FILENO + 1); if (pipe(pfds) == -1) { fprintf(stderr, "failed to create pipe for daemonize"); exit(SMF_EXIT_ERR_FATAL); } if ((pid = fork()) == -1) { fprintf(stderr, "failed to fork into background"); exit(SMF_EXIT_ERR_FATAL); } if (pid != 0) { (void) close(pfds[1]); if (read(pfds[0], &status, sizeof (status)) == sizeof (status)) exit(status); if (waitpid(pid, &status, 0) == pid && WIFEXITED(status)) exit(WEXITSTATUS(status)); exit(SMF_EXIT_ERR_FATAL); } /* * All child from here on... */ (void) setsid(); (void) sigprocmask(SIG_SETMASK, &oset, NULL); (void) close(pfds[0]); return (pfds[1]); } /* * We are only a daemon if the file descriptor is valid. */ void daemonize_fini(int fd) { int status = 0; if (fd != -1) { (void) write(fd, &status, sizeof (status)); (void) close(fd); if ((fd = open("/dev/null", O_RDWR)) >= 0) { (void) fcntl(fd, F_DUP2FD, STDIN_FILENO); (void) fcntl(fd, F_DUP2FD, STDOUT_FILENO); (void) fcntl(fd, F_DUP2FD, STDERR_FILENO); (void) close(fd); } } } /* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Helper routines for nfsmapid and autod daemon * to translate hostname to IP address and Netinfo. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nfs_resolve.h" void free_knconf(struct knetconfig *k) { if (k == NULL) return; if (k->knc_protofmly) free(k->knc_protofmly); if (k->knc_proto) free(k->knc_proto); free(k); } struct knetconfig * get_knconf(struct netconfig *nconf) { struct stat stbuf; struct knetconfig *k = NULL; int len; if (stat(nconf->nc_device, &stbuf) < 0) { syslog(LOG_ERR, "get_knconf: stat %s: %m", nconf->nc_device); return (NULL); } k = (struct knetconfig *)malloc(sizeof (*k)); if (k == NULL) goto nomem; k->knc_semantics = nconf->nc_semantics; len = strlen(nconf->nc_protofmly); if (len <= 0) goto err_out; k->knc_protofmly = malloc(KNC_STRSIZE); if (k->knc_protofmly == NULL) goto nomem; bzero(k->knc_protofmly, KNC_STRSIZE); bcopy(nconf->nc_protofmly, k->knc_protofmly, len); len = strlen(nconf->nc_proto); if (len <= 0) goto err_out; k->knc_proto = malloc(KNC_STRSIZE); if (k->knc_proto == NULL) goto nomem; bzero(k->knc_proto, KNC_STRSIZE); bcopy(nconf->nc_proto, k->knc_proto, len); k->knc_rdev = stbuf.st_rdev; return (k); nomem: syslog(LOG_ERR, "get_knconf: no memory"); err_out: if (k != NULL) (void) free_knconf(k); return (NULL); } /* * Get the information needed for an NFSv4.x referral. This * information includes the netbuf, netname and knconfig. */ struct nfs_fsl_info * get_nfs4ref_info(char *host, int port, int nfsver) { char netname[MAXNETNAMELEN + 1]; enum clnt_stat cstat; struct nfs_fsl_info *fsl_retp = NULL; struct netconfig *netconf = NULL; char *nametemp, *namex = NULL; struct netbuf *nb = NULL; NCONF_HANDLE *nc = NULL; fsl_retp = calloc(1, sizeof (struct nfs_fsl_info)); if (fsl_retp == NULL) { syslog(LOG_ERR, "get_nfs4ref_info: no memory\n"); return (NULL); } nametemp = malloc(MAXNETNAMELEN + 1); if (nametemp == NULL) { free(fsl_retp); return (NULL); } host2netname(nametemp, host, NULL); namex = calloc(1, strlen(nametemp) + 1); if (namex == NULL) { free(nametemp); free(fsl_retp); return (NULL); } strncpy(namex, nametemp, strlen(nametemp)); free(nametemp); fsl_retp->netname = namex; fsl_retp->netnm_len = strlen(namex) + 1; fsl_retp->addr = resolve_netconf(host, NFS_PROGRAM, nfsver, &netconf, port, NULL, NULL, TRUE, NULL, &cstat); if (netconf == NULL || fsl_retp->addr == NULL) goto done; fsl_retp->knconf = get_knconf(netconf); if (fsl_retp->knconf == NULL) goto done; fsl_retp->knconf_len = (sizeof (struct knetconfig) + (KNC_STRSIZE * 2)); fsl_retp->netbuf_len = (sizeof (struct netbuf) + fsl_retp->addr->maxlen); return (fsl_retp); done: free_nfs4ref_info(fsl_retp); return (NULL); } void free_nfs4ref_info(struct nfs_fsl_info *fsl_retp) { if (fsl_retp == NULL) return; free_knconf(fsl_retp->knconf); free(fsl_retp->netname); if (fsl_retp->addr != NULL) { free(fsl_retp->addr->buf); free(fsl_retp->addr); } free(fsl_retp); } void cleanup_tli_parms(struct t_bind *tbind, int fd) { if (tbind != NULL) { t_free((char *)tbind, T_BIND); tbind = NULL; } if (fd >= 0) (void) t_close(fd); fd = -1; } struct netbuf * resolve_netconf(char *host, rpcprog_t prog, rpcvers_t nfsver, struct netconfig **netconf, ushort_t port, struct t_info *tinfo, caddr_t *fhp, bool_t direct_to_server, char *fspath, enum clnt_stat *cstatp) { NCONF_HANDLE *nc; struct netconfig *nconf = NULL; int nthtry = FIRST_TRY; struct netbuf *nb; enum clnt_stat cstat; nc = setnetpath(); if (nc == NULL) goto done; retry: while (nconf = getnetpath(nc)) { if (nconf->nc_flag & NC_VISIBLE) { if (nthtry == FIRST_TRY) { if ((nconf->nc_semantics == NC_TPI_COTS_ORD) || (nconf->nc_semantics == NC_TPI_COTS)) { if (port == 0) break; if ((strcmp(nconf->nc_protofmly, NC_INET) == 0 || strcmp(nconf->nc_protofmly, NC_INET6) == 0) && (strcmp(nconf->nc_proto, NC_TCP) == 0)) break; } } if (nthtry == SECOND_TRY) { if (nconf->nc_semantics == NC_TPI_CLTS) { if (port == 0) break; if ((strcmp(nconf->nc_protofmly, NC_INET) == 0 || strcmp(nconf->nc_protofmly, NC_INET6) == 0) && (strcmp(nconf->nc_proto, NC_UDP) == 0)) break; } } } } /* while */ if (nconf == NULL) { if (++nthtry <= MNT_PREF_LISTLEN) { endnetpath(nc); if ((nc = setnetpath()) == NULL) goto done; goto retry; } else return (NULL); } else { nb = get_server_addr(host, NFS_PROGRAM, nfsver, nconf, port, NULL, NULL, TRUE, NULL, &cstat); if (cstat != RPC_SUCCESS) goto retry; } done: *netconf = nconf; *cstatp = cstat; if (nc) endnetpath(nc); return (nb); } int setup_nb_parms(struct netconfig *nconf, struct t_bind *tbind, struct t_info *tinfo, char *hostname, int fd, bool_t direct_to_server, ushort_t port, rpcprog_t prog, rpcvers_t vers, bool_t file_handle) { if (nconf == NULL) { return (-1); } if (direct_to_server == TRUE) { struct nd_hostserv hs; struct nd_addrlist *retaddrs; hs.h_host = hostname; if (port == 0) hs.h_serv = "nfs"; else hs.h_serv = NULL; if (netdir_getbyname(nconf, &hs, &retaddrs) != ND_OK) { return (-1); } memcpy(tbind->addr.buf, retaddrs->n_addrs->buf, retaddrs->n_addrs->len); tbind->addr.len = retaddrs->n_addrs->len; tbind->addr.maxlen = retaddrs->n_addrs->maxlen; netdir_free((void *)retaddrs, ND_ADDRLIST); if (port) { /* LINTED pointer alignment */ if (strcmp(nconf->nc_protofmly, NC_INET) == 0) ((struct sockaddr_in *) tbind->addr.buf)->sin_port = htons((ushort_t)port); else if (strcmp(nconf->nc_protofmly, NC_INET6) == 0) ((struct sockaddr_in6 *) tbind->addr.buf)->sin6_port = htons((ushort_t)port); } if (file_handle) { if (netdir_options(nconf, ND_SET_RESERVEDPORT, fd, NULL) == -1) return (-1); } } else if (!file_handle) { if (port) { /* LINTED pointer alignment */ if (strcmp(nconf->nc_protofmly, NC_INET) == 0) ((struct sockaddr_in *) tbind->addr.buf)->sin_port = htons((ushort_t)port); else if (strcmp(nconf->nc_protofmly, NC_INET6) == 0) ((struct sockaddr_in6 *) tbind->addr.buf)->sin6_port = htons((ushort_t)port); } } else { return (-1); } return (1); } /* * Sets up TLI interface and finds the address withe netdir_getbyname(). * returns the address returned from the call. * Caller frees up the memory allocated here. */ struct netbuf * get_server_addr(char *hostname, rpcprog_t prog, rpcvers_t vers, struct netconfig *nconf, ushort_t port, struct t_info *tinfo, caddr_t *fhp, bool_t direct_to_server, char *fspath, enum clnt_stat *cstat) { int fd = -1; struct t_bind *tbind = NULL; enum clnt_stat cs = RPC_SYSTEMERROR; struct netbuf *nb = NULL; int ret = -1; if (prog == NFS_PROGRAM && vers == NFS_V4) if (strncasecmp(nconf->nc_proto, NC_UDP, strlen(NC_UDP)) == 0) goto done; if ((fd = t_open(nconf->nc_device, O_RDWR, tinfo)) < 0) goto done; if ((tbind = (struct t_bind *)t_alloc(fd, T_BIND, T_ADDR)) == NULL) goto done; if (setup_nb_parms(nconf, tbind, tinfo, hostname, fd, direct_to_server, port, prog, vers, 0) < 0) goto done; nb = (struct netbuf *)malloc(sizeof (struct netbuf)); if (nb == NULL) { syslog(LOG_ERR, "no memory\n"); goto done; } nb->buf = (char *)malloc(tbind->addr.maxlen); if (nb->buf == NULL) { syslog(LOG_ERR, "no memory\n"); free(nb); nb = NULL; goto done; } (void) memcpy(nb->buf, tbind->addr.buf, tbind->addr.len); nb->len = tbind->addr.len; nb->maxlen = tbind->addr.maxlen; cs = RPC_SUCCESS; done: *cstat = cs; cleanup_tli_parms(tbind, fd); return (nb); } /* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _NFS_RESOLVE_H #define _NFS_RESOLVE_H /* number of transports to try */ #define MNT_PREF_LISTLEN 2 #define FIRST_TRY 1 #define SECOND_TRY 2 extern struct knetconfig *get_knconf(struct netconfig *); extern void free_knconf(struct knetconfig *); extern bool_t xdr_nfs_fsl_info(XDR *, struct nfs_fsl_info *); extern struct netconfig *get_netconfig(NCONF_HANDLE *, ushort_t, char *); extern int setup_nb_parms(struct netconfig *, struct t_bind *, struct t_info *, char *, int, bool_t, ushort_t, rpcprog_t, rpcvers_t, int); extern void cleanup_tli_parms(struct t_bind *, int); extern struct nfs_fsl_info *get_nfs4ref_info(char *, int, int); extern void free_nfs4ref_info(struct nfs_fsl_info *); extern struct netbuf *get_server_addr(char *, rpcprog_t, rpcvers_t, struct netconfig *, ushort_t, struct t_info *, caddr_t *, bool_t, char *, enum clnt_stat *); extern struct netbuf *resolve_netconf(char *, rpcprog_t, rpcvers_t, struct netconfig **, ushort_t, struct t_info *, caddr_t *, bool_t, char *, enum clnt_stat *); #endif /* _NFS_RESOLVE_H */ /* * 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 */ /* LINTLIBRARY */ /* * Copyright 2015 Nexenta Systems, Inc. All rights reserved. */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * nfs security related library routines. * * Some of the routines in this file are adopted from * lib/libnsl/netselect/netselect.c and are modified to be * used for accessing /etc/nfssec.conf. */ /* SVr4.0 1.18 */ #include #include #include #include #include #include #include #include #include #ifdef WNFS_SEC_NEGO #include "webnfs.h" #endif #define GETBYNAME 1 #define GETBYNUM 2 /* * mapping for /etc/nfssec.conf */ struct sc_data { char *string; int value; }; static struct sc_data sc_service[] = { "default", rpc_gss_svc_default, "-", rpc_gss_svc_none, "none", rpc_gss_svc_none, "integrity", rpc_gss_svc_integrity, "privacy", rpc_gss_svc_privacy, NULL, SC_FAILURE }; static mutex_t matching_lock = DEFAULTMUTEX; static char *gettoken(char *, int); extern int atoi(const char *str); extern bool_t rpc_gss_get_principal_name(rpc_gss_principal_t *, char *, char *, char *, char *); extern bool_t rpc_gss_mech_to_oid(char *, rpc_gss_OID *); extern bool_t rpc_gss_qop_to_num(char *, char *, uint_t *); /* * blank() returns true if the line is a blank line, 0 otherwise */ static int blank(char *cp) { while (*cp && isspace(*cp)) { cp++; } return (*cp == '\0'); } /* * comment() returns true if the line is a comment, 0 otherwise. */ static int comment(char *cp) { while (*cp && isspace(*cp)) { cp++; } return (*cp == '#'); } /* * getvalue() searches for the given string in the given array, * and returns the integer value associated with the string. */ static unsigned long getvalue(char *cp, struct sc_data sc_data[]) { int i; /* used to index through the given struct sc_data array */ for (i = 0; sc_data[i].string; i++) { if (strcmp(sc_data[i].string, cp) == 0) { break; } } return (sc_data[i].value); } /* * shift1left() moves all characters in the string over 1 to * the left. */ static void shift1left(char *p) { for (; *p; p++) *p = *(p + 1); } /* * gettoken() behaves much like strtok(), except that * it knows about escaped space characters (i.e., space characters * preceeded by a '\' are taken literally). * * XXX We should make this MT-hot by making it more like strtok_r(). */ static char * gettoken(char *cp, int skip) { static char *savep; /* the place where we left off */ register char *p; /* the beginning of the new token */ register char *retp; /* the token to be returned */ /* Determine if first or subsequent call */ p = (cp == NULL)? savep: cp; /* Return if no tokens remain. */ if (p == 0) { return (NULL); } while (isspace(*p)) p++; if (*p == '\0') { return (NULL); } /* * Save the location of the token and then skip past it */ retp = p; while (*p) { if (isspace(*p)) { if (skip == TRUE) { shift1left(p); continue; } else break; } /* * Only process the escape of the space separator; * since the token may contain other separators, * let the other routines handle the escape of * specific characters in the token. */ if (*p == '\\' && *(p + 1) != '\n' && isspace(*(p + 1))) { shift1left(p); } p++; } if (*p == '\0') { savep = 0; /* indicate this is last token */ } else { *p = '\0'; savep = ++p; } return (retp); } /* * matchname() parses a line of the /etc/nfssec.conf file * and match the sc_name with the given name. * If there is a match, it fills the information into the given * pointer of the seconfig_t structure. * * Returns TRUE if a match is found. */ static bool_t matchname(char *line, char *name, seconfig_t *secp) { char *tok1, *tok2; /* holds a token from the line */ char *secname, *gss_mech, *gss_qop; /* pointer to a secmode name */ if ((secname = gettoken(line, FALSE)) == NULL) { /* bad line */ return (FALSE); } if (strcmp(secname, name) != 0) { return (FALSE); } tok1 = tok2 = NULL; if (((tok1 = gettoken(NULL, FALSE)) == NULL) || ((gss_mech = gettoken(NULL, FALSE)) == NULL) || ((gss_qop = gettoken(NULL, FALSE)) == NULL) || ((tok2 = gettoken(NULL, FALSE)) == NULL) || ((secp->sc_service = getvalue(tok2, sc_service)) == SC_FAILURE)) { return (FALSE); } secp->sc_nfsnum = atoi(tok1); (void) strcpy(secp->sc_name, secname); (void) strcpy(secp->sc_gss_mech, gss_mech); secp->sc_gss_mech_type = NULL; if (secp->sc_gss_mech[0] != '-') { if (!rpc_gss_mech_to_oid(gss_mech, &secp->sc_gss_mech_type) || !rpc_gss_qop_to_num(gss_qop, gss_mech, &secp->sc_qop)) { return (FALSE); } } return (TRUE); } /* * matchnum() parses a line of the /etc/nfssec.conf file * and match the sc_nfsnum with the given number. * If it is a match, it fills the information in the given pointer * of the seconfig_t structure. * * Returns TRUE if a match is found. */ static bool_t matchnum(char *line, int num, seconfig_t *secp) { char *tok1, *tok2; /* holds a token from the line */ char *secname, *gss_mech, *gss_qop; /* pointer to a secmode name */ if ((secname = gettoken(line, FALSE)) == NULL) { /* bad line */ return (FALSE); } tok1 = tok2 = NULL; if ((tok1 = gettoken(NULL, FALSE)) == NULL) { /* bad line */ return (FALSE); } if ((secp->sc_nfsnum = atoi(tok1)) != num) { return (FALSE); } if (((gss_mech = gettoken(NULL, FALSE)) == NULL) || ((gss_qop = gettoken(NULL, FALSE)) == NULL) || ((tok2 = gettoken(NULL, FALSE)) == NULL) || ((secp->sc_service = getvalue(tok2, sc_service)) == SC_FAILURE)) { return (FALSE); } (void) strcpy(secp->sc_name, secname); (void) strcpy(secp->sc_gss_mech, gss_mech); if (secp->sc_gss_mech[0] != '-') { if (!rpc_gss_mech_to_oid(gss_mech, &secp->sc_gss_mech_type) || !rpc_gss_qop_to_num(gss_qop, gss_mech, &secp->sc_qop)) { return (FALSE); } } return (TRUE); } /* * Fill in the RPC Protocol security flavor number * into the sc_rpcnum of seconfig_t structure. * * Mainly to map NFS secmod number to RPCSEC_GSS if * a mechanism name is specified. */ static void get_rpcnum(seconfig_t *secp) { if (secp->sc_gss_mech[0] != '-') { secp->sc_rpcnum = RPCSEC_GSS; } else { secp->sc_rpcnum = secp->sc_nfsnum; } } /* * Parse a given hostname (nodename[.domain@realm]) to * instant name (nodename[.domain]) and realm. * * Assuming user has allocated the space for inst and realm. */ static int parsehostname(char *hostname, char *inst, char *realm) { char *h, *r; if (!hostname) return (0); h = (char *)strdup(hostname); if (!h) { syslog(LOG_ERR, "parsehostname: no memory\n"); return (0); } r = (char *)strchr(h, '@'); if (!r) { (void) strcpy(inst, h); (void) strcpy(realm, ""); } else { *r++ = '\0'; (void) strcpy(inst, h); (void) strcpy(realm, r); } free(h); return (1); } /* * Get the name corresponding to a qop num. */ char * nfs_get_qop_name(seconfig_t *entryp) { char *tok; /* holds a token from the line */ char *secname, *gss_qop = NULL; /* pointer to a secmode name */ char line[BUFSIZ]; /* holds each line of NFSSEC_CONF */ FILE *fp; /* file stream for NFSSEC_CONF */ (void) mutex_lock(&matching_lock); if ((fp = fopen(NFSSEC_CONF, "r")) == NULL) { (void) mutex_unlock(&matching_lock); return (NULL); } while (fgets(line, BUFSIZ, fp)) { if (!(blank(line) || comment(line))) { if ((secname = gettoken(line, FALSE)) == NULL) { /* bad line */ continue; } if (strcmp(secname, entryp->sc_name) == 0) { tok = NULL; if ((tok = gettoken(NULL, FALSE)) == NULL) { /* bad line */ goto err; } if (atoi(tok) != entryp->sc_nfsnum) goto err; if ((gettoken(NULL, FALSE) == NULL) || ((gss_qop = gettoken(NULL, FALSE)) == NULL)) { goto err; } break; } } } err: (void) fclose(fp); (void) mutex_unlock(&matching_lock); return (gss_qop); } /* * This routine creates an auth handle assocaited with the * negotiated security flavor contained in nfs_sec. The auth * handle will be used in the next LOOKUP request to fetch * the filehandle. */ AUTH * nfs_create_ah(CLIENT *cl, char *hostname, seconfig_t *nfs_sec) { char netname[MAXNETNAMELEN+1]; char svc_name[MAXNETNAMELEN+1]; char *gss_qop; static int window = 60; if (nfs_sec == NULL) goto err; switch (nfs_sec->sc_rpcnum) { case AUTH_UNIX: case AUTH_NONE: return (NULL); case AUTH_DES: if (!host2netname(netname, hostname, NULL)) goto err; return (authdes_seccreate(netname, window, hostname, NULL)); case RPCSEC_GSS: if (cl == NULL) goto err; if (nfs_sec->sc_gss_mech_type == NULL) { syslog(LOG_ERR, "nfs_create_ah: need mechanism information\n"); goto err; } /* * RPCSEC_GSS service names are of the form svc@host.dom */ (void) sprintf(svc_name, "nfs@%s", hostname); gss_qop = nfs_get_qop_name(nfs_sec); if (gss_qop == NULL) goto err; return (rpc_gss_seccreate(cl, svc_name, nfs_sec->sc_gss_mech, nfs_sec->sc_service, gss_qop, NULL, NULL)); default: syslog(LOG_ERR, "nfs_create_ah: unknown flavor\n"); return (NULL); } err: syslog(LOG_ERR, "nfs_create_ah: failed to make auth handle\n"); return (NULL); } #ifdef WNFS_SEC_NEGO /* * This routine negotiates sec flavors with server and returns: * SNEGO_SUCCESS: successful; sec flavors are * returned in snego, * SNEGO_DEF_VALID: default sec flavor valid; no need * to negotiate flavors, * SNEGO_ARRAY_TOO_SMALL: array too small, * SNEGO_FAILURE: failure */ /* * The following depicts how sec flavors are placed in an * overloaded V2 fhandle: * * Note that the first four octets contain the length octet, * the status octet, and two padded octets to make them XDR * four-octet aligned. * * 1 2 3 4 32 * +---+---+---+---+---+---+---+---+ +---+---+---+---+ +---+ * | l | s | | | sec_1 |...| sec_n |...| | * +---+---+---+---+---+---+---+---+ +---+---+---+---+ +---+ * * where * * the status octet s indicates whether there are more security * flavors(1 means yes, 0 means no) that require the client to * perform another 0x81 LOOKUP to get them, * * the length octet l is the length describing the number of * valid octets that follow. (l = 4 * n, where n is the number * * The following depicts how sec flavors are placed in an * overloaded V3 fhandle: * * 1 4 * +--+--+--+--+ * | len | * +--+--+--+--+ * up to 64 * +--+--+--+--+--+--+--+--+--+--+--+--+ +--+--+--+--+ * |s | | | | sec_1 | sec_2 | ... | sec_n | * +--+--+--+--+--+--+--+--+--+--+--+--+ +--+--+--+--+ * * len = 4 * (n+1), where n is the number of security flavors * sent in the current overloaded filehandle. * * the status octet s indicates whether there are more security * mechanisms(1 means yes, 0 means no) that require the client * to perform another 0x81 LOOKUP to get them. * * Three octets are padded after the status octet. */ enum snego_stat nfs_sec_nego(rpcprog_t vers, CLIENT *clnt, char *fspath, struct snego_t *snego) { enum clnt_stat rpc_stat; static int MAX_V2_CNT = (WNL_FHSIZE/sizeof (int)) - 1; static int MAX_V3_CNT = (WNL3_FHSIZE/sizeof (int)) - 1; static struct timeval TIMEOUT = { 25, 0 }; int status; if (clnt == NULL || fspath == NULL || snego == NULL) return (SNEGO_FAILURE); if (vers == WNL_V2) { wnl_diropargs arg; wnl_diropres clnt_res; memset((char *)&arg.dir, 0, sizeof (wnl_fh)); arg.name = fspath; memset((char *)&clnt_res, 0, sizeof (clnt_res)); rpc_stat = clnt_call(clnt, WNLPROC_LOOKUP, (xdrproc_t)xdr_wnl_diropargs, (caddr_t)&arg, (xdrproc_t)xdr_wnl_diropres, (caddr_t)&clnt_res, TIMEOUT); if (rpc_stat == RPC_SUCCESS && clnt_res.status == WNL_OK) return (SNEGO_DEF_VALID); if (rpc_stat != RPC_AUTHERROR) return (SNEGO_FAILURE); { struct rpc_err e; wnl_diropres res; char *p; int tot = 0; CLNT_GETERR(clnt, &e); if (e.re_why != AUTH_TOOWEAK) return (SNEGO_FAILURE); if ((p = malloc(strlen(fspath)+3)) == NULL) { syslog(LOG_ERR, "no memory\n"); return (SNEGO_FAILURE); } /* * Do an x81 LOOKUP */ p[0] = (char)WNL_SEC_NEGO; strcpy(&p[2], fspath); do { p[1] = (char)(1+snego->cnt); /* sec index */ arg.name = p; memset((char *)&res, 0, sizeof (wnl_diropres)); if (wnlproc_lookup_2(&arg, &res, clnt) != RPC_SUCCESS || res.status != WNL_OK) { free(p); return (SNEGO_FAILURE); } /* * retrieve flavors from filehandle: * 1st byte: length * 2nd byte: status * 3rd & 4th: pad * 5th and after: sec flavors. */ { char *c = (char *)&res.wnl_diropres_u. wnl_diropres.file; int ii; int cnt = ((int)*c)/sizeof (uint_t); /* LINTED pointer alignment */ int *ip = (int *)(c+sizeof (int)); tot += cnt; if (tot >= MAX_FLAVORS) { free(p); return (SNEGO_ARRAY_TOO_SMALL); } status = (int)*(c+1); if (cnt > MAX_V2_CNT || cnt < 0) { free(p); return (SNEGO_FAILURE); } for (ii = 0; ii < cnt; ii++) snego->array[snego->cnt+ii] = ntohl(*(ip+ii)); snego->cnt += cnt; } } while (status); free(p); return (SNEGO_SUCCESS); } } else if (vers == WNL_V3) { WNL_LOOKUP3args arg; WNL_LOOKUP3res clnt_res; memset((char *)&arg.what.dir, 0, sizeof (wnl_fh3)); arg.what.name = fspath; arg.what.dir.data.data_len = 0; arg.what.dir.data.data_val = 0; memset((char *)&clnt_res, 0, sizeof (clnt_res)); rpc_stat = clnt_call(clnt, WNLPROC3_LOOKUP, (xdrproc_t)xdr_WNL_LOOKUP3args, (caddr_t)&arg, (xdrproc_t)xdr_WNL_LOOKUP3res, (caddr_t)&clnt_res, TIMEOUT); if (rpc_stat == RPC_SUCCESS && clnt_res.status == WNL3_OK) return (SNEGO_DEF_VALID); if (rpc_stat != RPC_AUTHERROR) return (SNEGO_FAILURE); { struct rpc_err e; WNL_LOOKUP3res res; char *p; int tot = 0; CLNT_GETERR(clnt, &e); if (e.re_why != AUTH_TOOWEAK) return (SNEGO_FAILURE); if ((p = malloc(strlen(fspath)+3)) == NULL) { syslog(LOG_ERR, "no memory\n"); return (SNEGO_FAILURE); } /* * Do an x81 LOOKUP */ p[0] = (char)WNL_SEC_NEGO; strcpy(&p[2], fspath); do { p[1] = (char)(1+snego->cnt); /* sec index */ arg.what.name = p; memset((char *)&res, 0, sizeof (WNL_LOOKUP3res)); if (wnlproc3_lookup_3(&arg, &res, clnt) != RPC_SUCCESS || res.status != WNL3_OK) { free(p); return (SNEGO_FAILURE); } /* * retrieve flavors from filehandle: * * 1st byte: status * 2nd thru 4th: pad * 5th and after: sec flavors. */ { char *c = res.WNL_LOOKUP3res_u. res_ok.object.data.data_val; int ii; int len = res.WNL_LOOKUP3res_u.res_ok. object.data.data_len; int cnt; /* LINTED pointer alignment */ int *ip = (int *)(c+sizeof (int)); cnt = len/sizeof (uint_t) - 1; tot += cnt; if (tot >= MAX_FLAVORS) { free(p); return (SNEGO_ARRAY_TOO_SMALL); } status = (int)(*c); if (cnt > MAX_V3_CNT || cnt < 0) { free(p); return (SNEGO_FAILURE); } for (ii = 0; ii < cnt; ii++) snego->array[snego->cnt+ii] = ntohl(*(ip+ii)); snego->cnt += cnt; } } while (status); free(p); return (SNEGO_SUCCESS); } } return (SNEGO_FAILURE); } #endif /* * Get seconfig from /etc/nfssec.conf by name or by number or * by descriptior. */ /* ARGSUSED */ static int get_seconfig(int whichway, char *name, int num, rpc_gss_service_t service, seconfig_t *entryp) { char line[BUFSIZ]; /* holds each line of NFSSEC_CONF */ FILE *fp; /* file stream for NFSSEC_CONF */ if ((whichway == GETBYNAME) && (name == NULL)) return (SC_NOTFOUND); (void) mutex_lock(&matching_lock); if ((fp = fopen(NFSSEC_CONF, "r")) == NULL) { (void) mutex_unlock(&matching_lock); return (SC_OPENFAIL); } while (fgets(line, BUFSIZ, fp)) { if (!(blank(line) || comment(line))) { switch (whichway) { case GETBYNAME: if (matchname(line, name, entryp)) { goto found; } break; case GETBYNUM: if (matchnum(line, num, entryp)) { goto found; } break; default: break; } } } (void) fclose(fp); (void) mutex_unlock(&matching_lock); return (SC_NOTFOUND); found: (void) fclose(fp); (void) mutex_unlock(&matching_lock); (void) get_rpcnum(entryp); return (SC_NOERROR); } /* * NFS project private API. * Get a seconfig entry from /etc/nfssec.conf by nfs specific sec name, * e.g. des, krb5p, etc. */ int nfs_getseconfig_byname(char *secmode_name, seconfig_t *entryp) { if (!entryp) return (SC_NOMEM); return (get_seconfig(GETBYNAME, secmode_name, 0, rpc_gss_svc_none, entryp)); } /* * NFS project private API. * * Get a seconfig entry from /etc/nfssec.conf by nfs specific sec number, * e.g. AUTH_DES, AUTH_KRB5_P, etc. */ int nfs_getseconfig_bynumber(int nfs_secnum, seconfig_t *entryp) { if (!entryp) return (SC_NOMEM); return (get_seconfig(GETBYNUM, NULL, nfs_secnum, rpc_gss_svc_none, entryp)); } /* * NFS project private API. * * Get a seconfig_t entry used as the default for NFS operations. * The default flavor entry is defined in /etc/nfssec.conf. * * Assume user has allocate spaces for secp. */ int nfs_getseconfig_default(seconfig_t *secp) { if (secp == NULL) return (SC_NOMEM); return (nfs_getseconfig_byname("default", secp)); } /* * NFS project private API. * * Free an sec_data structure. * Free the parts that nfs_clnt_secdata allocates. */ void nfs_free_secdata(sec_data_t *secdata) { dh_k4_clntdata_t *dkdata; gss_clntdata_t *gdata; if (!secdata) return; switch (secdata->rpcflavor) { case AUTH_UNIX: case AUTH_NONE: break; case AUTH_DES: /* LINTED pointer alignment */ dkdata = (dh_k4_clntdata_t *)secdata->data; if (dkdata) { if (dkdata->netname) free(dkdata->netname); if (dkdata->syncaddr.buf) free(dkdata->syncaddr.buf); free(dkdata); } break; case RPCSEC_GSS: /* LINTED pointer alignment */ gdata = (gss_clntdata_t *)secdata->data; if (gdata) { if (gdata->mechanism.elements) free(gdata->mechanism.elements); free(gdata); } break; default: break; } free(secdata); } /* * Make an client side sec_data structure and fill in appropriate value * based on its rpc security flavor. * * It is caller's responsibility to allocate space for seconfig_t, * and this routine will allocate space for the sec_data structure * and related data field. * * Return the sec_data_t on success. * If fail, return NULL pointer. */ sec_data_t * nfs_clnt_secdata(seconfig_t *secp, char *hostname, struct knetconfig *knconf, struct netbuf *syncaddr, int flags) { char netname[MAXNETNAMELEN+1]; sec_data_t *secdata; dh_k4_clntdata_t *dkdata; gss_clntdata_t *gdata; secdata = malloc(sizeof (sec_data_t)); if (!secdata) { syslog(LOG_ERR, "nfs_clnt_secdata: no memory\n"); return (NULL); } (void) memset(secdata, 0, sizeof (sec_data_t)); secdata->secmod = secp->sc_nfsnum; secdata->rpcflavor = secp->sc_rpcnum; secdata->uid = secp->sc_uid; secdata->flags = flags; /* * Now, fill in the information for client side secdata : * * For AUTH_UNIX, AUTH_DES * hostname can be in the form of * nodename or * nodename.domain * * For RPCSEC_GSS security flavor * hostname can be in the form of * nodename or * nodename.domain or * nodename@realm (realm can be the same as the domain) or * nodename.domain@realm */ switch (secp->sc_rpcnum) { case AUTH_UNIX: case AUTH_NONE: secdata->data = NULL; break; case AUTH_DES: /* * If hostname is in the format of host.nisdomain * the netname will be constructed with * this nisdomain name rather than the default * domain of the machine. */ if (!host2netname(netname, hostname, NULL)) { syslog(LOG_ERR, "host2netname: %s: unknown\n", hostname); goto err_out; } dkdata = malloc(sizeof (dh_k4_clntdata_t)); if (!dkdata) { syslog(LOG_ERR, "nfs_clnt_secdata: no memory\n"); goto err_out; } (void) memset((char *)dkdata, 0, sizeof (dh_k4_clntdata_t)); if ((dkdata->netname = strdup(netname)) == NULL) { syslog(LOG_ERR, "nfs_clnt_secdata: no memory\n"); goto err_out; } dkdata->netnamelen = strlen(netname); dkdata->knconf = knconf; dkdata->syncaddr = *syncaddr; dkdata->syncaddr.buf = malloc(syncaddr->len); if (dkdata->syncaddr.buf == NULL) { syslog(LOG_ERR, "nfs_clnt_secdata: no memory\n"); goto err_out; } (void) memcpy(dkdata->syncaddr.buf, syncaddr->buf, syncaddr->len); secdata->data = (caddr_t)dkdata; break; case RPCSEC_GSS: if (secp->sc_gss_mech_type == NULL) { syslog(LOG_ERR, "nfs_clnt_secdata: need mechanism information\n"); goto err_out; } gdata = malloc(sizeof (gss_clntdata_t)); if (!gdata) { syslog(LOG_ERR, "nfs_clnt_secdata: no memory\n"); goto err_out; } (void) strcpy(gdata->uname, "nfs"); if (!parsehostname(hostname, gdata->inst, gdata->realm)) { syslog(LOG_ERR, "nfs_clnt_secdata: bad host name\n"); goto err_out; } gdata->mechanism.length = secp->sc_gss_mech_type->length; if (!(gdata->mechanism.elements = malloc(secp->sc_gss_mech_type->length))) { syslog(LOG_ERR, "nfs_clnt_secdata: no memory\n"); goto err_out; } (void) memcpy(gdata->mechanism.elements, secp->sc_gss_mech_type->elements, secp->sc_gss_mech_type->length); gdata->qop = secp->sc_qop; gdata->service = secp->sc_service; secdata->data = (caddr_t)gdata; break; default: syslog(LOG_ERR, "nfs_clnt_secdata: unknown flavor\n"); goto err_out; } return (secdata); err_out: free(secdata); return (NULL); } /* * nfs_get_root_principal() maps a host name to its principal name * based on the given security information. * * input : seconfig - security configuration information * host - the host name which could be in the following forms: * node * node.namedomain * node@secdomain (e.g. kerberos realm is a secdomain) * node.namedomain@secdomain * output : rootname_p - address of the principal name for the host * * Currently, this routine is only used by share program. * */ bool_t nfs_get_root_principal(seconfig_t *seconfig, char *host, caddr_t *rootname_p) { char netname[MAXNETNAMELEN+1], node[MAX_NAME_LEN]; char secdomain[MAX_NAME_LEN]; rpc_gss_principal_t gssname; switch (seconfig->sc_rpcnum) { case AUTH_DES: if (!host2netname(netname, host, NULL)) { syslog(LOG_ERR, "nfs_get_root_principal: unknown host: %s\n", host); return (FALSE); } *rootname_p = strdup(netname); if (!*rootname_p) { syslog(LOG_ERR, "nfs_get_root_principal: no memory\n"); return (FALSE); } break; case RPCSEC_GSS: if (!parsehostname(host, node, secdomain)) { syslog(LOG_ERR, "nfs_get_root_principal: bad host name\n"); return (FALSE); } if (!rpc_gss_get_principal_name(&gssname, seconfig->sc_gss_mech, "root", node, secdomain)) { syslog(LOG_ERR, "nfs_get_root_principal: can not get principal name : %s\n", host); return (FALSE); } *rootname_p = (caddr_t)gssname; break; default: return (FALSE); } return (TRUE); } /* * SYSLOG SC_* errors. */ int nfs_syslog_scerr(int scerror, char msg[]) { switch (scerror) { case SC_NOMEM : (void) sprintf(msg, "%s : no memory", NFSSEC_CONF); return (0); case SC_OPENFAIL : (void) sprintf(msg, "can not open %s", NFSSEC_CONF); return (0); case SC_NOTFOUND : (void) sprintf(msg, "has no entry in %s", NFSSEC_CONF); return (0); case SC_BADENTRIES : (void) sprintf(msg, "bad entry in %s", NFSSEC_CONF); return (0); default: msg[0] = '\0'; return (-1); } } /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include "nfs_subr.h" #include #include #include #include "smfcfg.h" #include extern int _nfssys(enum nfssys_op, void *); /* * This function is added to detect compatibility problem with SunOS4.x. * The compatibility problem exists when fshost cannot decode the request * arguments for NLM_GRANTED procedure. * Only in this case we use local locking. * In any other case we use fshost's lockd for remote file locking. * Return value: 1 if we should use local locking, 0 if not. */ int remote_lock(char *fshost, caddr_t fh) { nlm_testargs rlm_args; nlm_res rlm_res; struct timeval timeout = { 5, 0}; CLIENT *cl; enum clnt_stat rpc_stat; struct utsname myid; (void) memset((char *)&rlm_args, 0, sizeof (nlm_testargs)); (void) memset((char *)&rlm_res, 0, sizeof (nlm_res)); /* * Assign the hostname and the file handle for the * NLM_GRANTED request below. If for some reason the uname call fails, * list the server as the caller so that caller_name has some * reasonable value. */ if (uname(&myid) == -1) { rlm_args.alock.caller_name = fshost; } else { rlm_args.alock.caller_name = myid.nodename; } rlm_args.alock.fh.n_len = sizeof (fhandle_t); rlm_args.alock.fh.n_bytes = fh; cl = clnt_create(fshost, NLM_PROG, NLM_VERS, "datagram_v"); if (cl == NULL) return (0); rpc_stat = clnt_call(cl, NLM_GRANTED, xdr_nlm_testargs, (caddr_t)&rlm_args, xdr_nlm_res, (caddr_t)&rlm_res, timeout); clnt_destroy(cl); return (rpc_stat == RPC_CANTDECODEARGS); } #define fromhex(c) ((c >= '0' && c <= '9') ? (c - '0') : \ ((c >= 'A' && c <= 'F') ? (c - 'A' + 10) :\ ((c >= 'a' && c <= 'f') ? (c - 'a' + 10) : 0))) /* * The implementation of URLparse guarantees that the final string will * fit in the original one. Replaces '%' occurrences followed by 2 characters * with its corresponding hexadecimal character. */ void URLparse(char *str) { char *p, *q; p = q = str; while (*p) { *q = *p; if (*p++ == '%') { if (*p) { *q = fromhex(*p) * 16; p++; if (*p) { *q += fromhex(*p); p++; } } } q++; } *q = '\0'; } /* * Convert from URL syntax to host:path syntax. */ int convert_special(char **specialp, char *host, char *oldpath, char *newpath, char *cur_special) { char *url; char *newspec; char *p; char *p1, *p2; /* * Rebuild the URL. This is necessary because parse replica * assumes that nfs: is the host name. */ url = malloc(strlen("nfs:") + strlen(oldpath) + 1); if (url == NULL) return (-1); strcpy(url, "nfs:"); strcat(url, oldpath); /* * If we haven't done any conversion yet, allocate a buffer for it. */ if (*specialp == NULL) { newspec = *specialp = strdup(cur_special); if (newspec == NULL) { free(url); return (-1); } } else { newspec = *specialp; } /* * Now find the first occurence of the URL in the special string. */ p = strstr(newspec, url); if (p == NULL) { free(url); return (-1); } p1 = p; p2 = host; /* * Overwrite the URL in the special. * * Begin with the host name. */ for (;;) { /* * Sine URL's take more room than host:path, there is * no way we should hit a null byte in the original special. */ if (*p1 == '\0') { free(url); free(*specialp); *specialp = NULL; return (-1); } if (*p2 == '\0') { break; } *p1 = *p2; p1++; p2++; } /* * Add the : separator. */ *p1 = ':'; p1++; /* * Now over write into special the path portion of host:path in */ p2 = newpath; for (;;) { if (*p1 == '\0') { free(url); free(*specialp); *specialp = NULL; return (-1); } if (*p2 == '\0') { break; } *p1 = *p2; p1++; p2++; } /* * Now shift the rest of original special into the gap created * by replacing nfs://host[:port]/path with host:path. */ p2 = p + strlen(url); for (;;) { if (*p1 == '\0') { free(url); free(*specialp); *specialp = NULL; return (-1); } if (*p2 == '\0') { break; } *p1 = *p2; p1++; p2++; } *p1 = '\0'; free(url); return (0); } #define AUTOFS_MOUNT_TIMEOUT 600 /* default min time mount will */ void set_nfsv4_ephemeral_mount_to(void) { char valbuf[6]; int bufsz = sizeof (valbuf); uint_t mount_to = AUTOFS_MOUNT_TIMEOUT; /* * Get the value from SMF */ if (autofs_smf_get_prop("timeout", valbuf, DEFAULT_INSTANCE, SCF_TYPE_INTEGER, AUTOMOUNTD, &bufsz) == SA_OK) { const char *errstr; uint_t val = strtonum(valbuf, 0, UINT_MAX, &errstr); if (errstr == NULL) mount_to = val; } (void) _nfssys(NFS4_EPHEMERAL_MOUNT_TO, &mount_to); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 */ /* * nfs_subr.h * * Copyright (c) 1996 Sun Microsystems Inc * All Rights Reserved. */ #ifndef _NFS_SUBR_H #define _NFS_SUBR_H #include #ifdef __cplusplus extern "C" { #endif /* * nfs library routines */ extern int remote_lock(char *, caddr_t); extern void URLparse(char *); extern int convert_special(char **, char *, char *, char *, char *); #ifdef __cplusplus } #endif #endif /* _NFS_SUBR_H */ /* * 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) 1996, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012 by Delphix. All rights reserved. * Copyright 2014 Nexenta Systems, Inc. All rights reserved. * Copyright 2014 Gary Mills */ /* * nfs_tbind.c, common part for nfsd and lockd. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nfs_tbind.h" #include #include #include #include #include #include #include /* * Determine valid semantics for most applications. */ #define OK_TPI_TYPE(_nconf) \ (_nconf->nc_semantics == NC_TPI_CLTS || \ _nconf->nc_semantics == NC_TPI_COTS || \ _nconf->nc_semantics == NC_TPI_COTS_ORD) #define BE32_TO_U32(a) \ ((((ulong_t)((uchar_t *)a)[0] & 0xFF) << (ulong_t)24) | \ (((ulong_t)((uchar_t *)a)[1] & 0xFF) << (ulong_t)16) | \ (((ulong_t)((uchar_t *)a)[2] & 0xFF) << (ulong_t)8) | \ ((ulong_t)((uchar_t *)a)[3] & 0xFF)) /* * Number of elements to add to the poll array on each allocation. */ #define POLL_ARRAY_INC_SIZE 64 /* * Number of file descriptors by which the process soft limit may be * increased on each call to nofile_increase(0). */ #define NOFILE_INC_SIZE 64 /* * Default TCP send and receive buffer size of NFS server. */ #define NFSD_TCP_BUFSZ (1024*1024) struct conn_ind { struct conn_ind *conn_next; struct conn_ind *conn_prev; struct t_call *conn_call; }; struct conn_entry { bool_t closing; struct netconfig nc; }; /* * this file contains transport routines common to nfsd and lockd */ static int nofile_increase(int); static int reuseaddr(int); static int recvucred(int); static int anonmlp(int); static void add_to_poll_list(int, struct netconfig *); static char *serv_name_to_port_name(char *); static int bind_to_proto(char *, char *, struct netbuf **, struct netconfig **); static int bind_to_provider(char *, char *, struct netbuf **, struct netconfig **); static void conn_close_oldest(void); static boolean_t conn_get(int, struct netconfig *, struct conn_ind **); static void cots_listen_event(int, int); static int discon_get(int, struct netconfig *, struct conn_ind **); static int do_poll_clts_action(int, int); static int do_poll_cots_action(int, int); static void remove_from_poll_list(int); static int set_addrmask(int, struct netconfig *, struct netbuf *); static int is_listen_fd_index(int); static struct pollfd *poll_array; static struct conn_entry *conn_polled; static int num_conns; /* Current number of connections */ int (*Mysvc4)(int, struct netbuf *, struct netconfig *, int, struct netbuf *); static int setopt(int fd, int level, int name, int value); static int get_opt(int fd, int level, int name); static void nfslib_set_sockbuf(int fd); /* * Called to create and prepare a transport descriptor for in-kernel * RPC service. * Returns -1 on failure and a valid descriptor on success. */ int nfslib_transport_open(struct netconfig *nconf) { int fd; struct strioctl strioc; if ((nconf == (struct netconfig *)NULL) || (nconf->nc_device == (char *)NULL)) { syslog(LOG_ERR, "no netconfig device"); return (-1); } /* * Open the transport device. */ fd = t_open(nconf->nc_device, O_RDWR, (struct t_info *)NULL); if (fd == -1) { if (t_errno == TSYSERR && errno == EMFILE && (nofile_increase(0) == 0)) { /* Try again with a higher NOFILE limit. */ fd = t_open(nconf->nc_device, O_RDWR, (struct t_info *)NULL); } if (fd == -1) { syslog(LOG_ERR, "t_open %s failed: t_errno %d, %m", nconf->nc_device, t_errno); return (-1); } } /* * Pop timod because the RPC module must be as close as possible * to the transport. */ if (ioctl(fd, I_POP, 0) < 0) { syslog(LOG_ERR, "I_POP of timod failed: %m"); (void) t_close(fd); return (-1); } /* * Common code for CLTS and COTS transports */ if (ioctl(fd, I_PUSH, "rpcmod") < 0) { syslog(LOG_ERR, "I_PUSH of rpcmod failed: %m"); (void) t_close(fd); return (-1); } strioc.ic_cmd = RPC_SERVER; strioc.ic_dp = (char *)0; strioc.ic_len = 0; strioc.ic_timout = -1; /* Tell rpcmod to act like a server stream. */ if (ioctl(fd, I_STR, &strioc) < 0) { syslog(LOG_ERR, "rpcmod set-up ioctl failed: %m"); (void) t_close(fd); return (-1); } /* * Re-push timod so that we will still be doing TLI * operations on the descriptor. */ if (ioctl(fd, I_PUSH, "timod") < 0) { syslog(LOG_ERR, "I_PUSH of timod failed: %m"); (void) t_close(fd); return (-1); } /* * Enable options of returning the ip's for udp. */ if (strcmp(nconf->nc_netid, "udp6") == 0) __rpc_tli_set_options(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, 1); else if (strcmp(nconf->nc_netid, "udp") == 0) __rpc_tli_set_options(fd, IPPROTO_IP, IP_RECVDSTADDR, 1); return (fd); } static int nofile_increase(int limit) { struct rlimit rl; if (getrlimit(RLIMIT_NOFILE, &rl) == -1) { syslog(LOG_ERR, "getrlimit of NOFILE failed: %m"); return (-1); } if (limit > 0) rl.rlim_cur = limit; else rl.rlim_cur += NOFILE_INC_SIZE; if (rl.rlim_cur > rl.rlim_max && rl.rlim_max != RLIM_INFINITY) rl.rlim_max = rl.rlim_cur; if (setrlimit(RLIMIT_NOFILE, &rl) == -1) { syslog(LOG_ERR, "setrlimit of NOFILE to %d failed: %m", rl.rlim_cur); return (-1); } return (0); } static void nfslib_set_sockbuf(int fd) { int curval, val; val = NFSD_TCP_BUFSZ; curval = get_opt(fd, SOL_SOCKET, SO_SNDBUF); syslog(LOG_DEBUG, "Current SO_SNDBUF value is %d", curval); if ((curval != -1) && (curval < val)) { syslog(LOG_DEBUG, "Set SO_SNDBUF option to %d", val); if (setopt(fd, SOL_SOCKET, SO_SNDBUF, val) < 0) { syslog(LOG_ERR, "couldn't set SO_SNDBUF to %d - t_errno = %d", val, t_errno); syslog(LOG_ERR, "Check and increase system-wide tcp_max_buf"); } } curval = get_opt(fd, SOL_SOCKET, SO_RCVBUF); syslog(LOG_DEBUG, "Current SO_RCVBUF value is %d", curval); if ((curval != -1) && (curval < val)) { syslog(LOG_DEBUG, "Set SO_RCVBUF option to %d", val); if (setopt(fd, SOL_SOCKET, SO_RCVBUF, val) < 0) { syslog(LOG_ERR, "couldn't set SO_RCVBUF to %d - t_errno = %d", val, t_errno); syslog(LOG_ERR, "Check and increase system-wide tcp_max_buf"); } } } int nfslib_bindit(struct netconfig *nconf, struct netbuf **addr, struct nd_hostserv *hs, int backlog) { int fd; struct t_bind *ntb; struct t_bind tb; struct nd_addrlist *addrlist; struct t_optmgmt req, resp; struct opthdr *opt; char reqbuf[128]; bool_t use_any = FALSE; bool_t gzone = TRUE; if ((fd = nfslib_transport_open(nconf)) == -1) { syslog(LOG_ERR, "cannot establish transport service over %s", nconf->nc_device); return (-1); } addrlist = (struct nd_addrlist *)NULL; /* nfs4_callback service does not used a fieed port number */ if (strcmp(hs->h_serv, "nfs4_callback") == 0) { tb.addr.maxlen = 0; tb.addr.len = 0; tb.addr.buf = 0; use_any = TRUE; gzone = (getzoneid() == GLOBAL_ZONEID); } else if (netdir_getbyname(nconf, hs, &addrlist) != 0) { syslog(LOG_ERR, "Cannot get address for transport %s host %s service %s", nconf->nc_netid, hs->h_host, hs->h_serv); (void) t_close(fd); return (-1); } if (strcmp(nconf->nc_proto, "tcp") == 0) { /* * If we're running over TCP, then set the * SO_REUSEADDR option so that we can bind * to our preferred address even if previously * left connections exist in FIN_WAIT states. * This is somewhat bogus, but otherwise you have * to wait 2 minutes to restart after killing it. */ if (reuseaddr(fd) == -1) { syslog(LOG_WARNING, "couldn't set SO_REUSEADDR option on transport"); } } else if (strcmp(nconf->nc_proto, "udp") == 0) { /* * In order to run MLP on UDP, we need to handle creds. */ if (recvucred(fd) == -1) { syslog(LOG_WARNING, "couldn't set SO_RECVUCRED option on transport"); } } /* * Make non global zone nfs4_callback port MLP */ if (use_any && is_system_labeled() && !gzone) { if (anonmlp(fd) == -1) { /* * failing to set this option means nfs4_callback * could fail silently later. So fail it with * with an error message now. */ syslog(LOG_ERR, "couldn't set SO_ANON_MLP option on transport"); (void) t_close(fd); return (-1); } } if (nconf->nc_semantics == NC_TPI_CLTS) tb.qlen = 0; else tb.qlen = backlog; /* LINTED pointer alignment */ ntb = (struct t_bind *)t_alloc(fd, T_BIND, T_ALL); if (ntb == (struct t_bind *)NULL) { syslog(LOG_ERR, "t_alloc failed: t_errno %d, %m", t_errno); (void) t_close(fd); netdir_free((void *)addrlist, ND_ADDRLIST); return (-1); } /* * XXX - what about the space tb->addr.buf points to? This should * be either a memcpy() to/from the buf fields, or t_alloc(fd,T_BIND,) * should't be called with T_ALL. */ if (addrlist) tb.addr = *(addrlist->n_addrs); /* structure copy */ if (t_bind(fd, &tb, ntb) == -1) { syslog(LOG_ERR, "t_bind failed: t_errno %d, %m", t_errno); (void) t_free((char *)ntb, T_BIND); netdir_free((void *)addrlist, ND_ADDRLIST); (void) t_close(fd); return (-1); } /* make sure we bound to the right address */ if (use_any == FALSE && (tb.addr.len != ntb->addr.len || memcmp(tb.addr.buf, ntb->addr.buf, tb.addr.len) != 0)) { syslog(LOG_ERR, "t_bind to wrong address"); (void) t_free((char *)ntb, T_BIND); netdir_free((void *)addrlist, ND_ADDRLIST); (void) t_close(fd); return (-1); } /* * Call nfs4svc_setport so that the kernel can be * informed what port number the daemon is listing * for incoming connection requests. */ if ((nconf->nc_semantics == NC_TPI_COTS || nconf->nc_semantics == NC_TPI_COTS_ORD) && Mysvc4 != NULL) (*Mysvc4)(fd, NULL, nconf, NFS4_SETPORT, &ntb->addr); *addr = &ntb->addr; netdir_free((void *)addrlist, ND_ADDRLIST); if (strcmp(nconf->nc_proto, "tcp") == 0) { /* * Disable the Nagle algorithm on TCP connections. * Connections accepted from this listener will * inherit the listener options. */ /* LINTED pointer alignment */ opt = (struct opthdr *)reqbuf; opt->level = IPPROTO_TCP; opt->name = TCP_NODELAY; opt->len = sizeof (int); /* LINTED pointer alignment */ *(int *)((char *)opt + sizeof (*opt)) = 1; req.flags = T_NEGOTIATE; req.opt.len = sizeof (*opt) + opt->len; req.opt.buf = (char *)opt; resp.flags = 0; resp.opt.buf = reqbuf; resp.opt.maxlen = sizeof (reqbuf); if (t_optmgmt(fd, &req, &resp) < 0 || resp.flags != T_SUCCESS) { syslog(LOG_ERR, "couldn't set NODELAY option for proto %s: t_errno = %d, %m", nconf->nc_proto, t_errno); } nfslib_set_sockbuf(fd); } return (fd); } static int get_opt(int fd, int level, int name) { struct t_optmgmt req, res; struct { struct opthdr opt; int value; } reqbuf; reqbuf.opt.level = level; reqbuf.opt.name = name; reqbuf.opt.len = sizeof (int); reqbuf.value = 0; req.flags = T_CURRENT; req.opt.len = sizeof (reqbuf); req.opt.buf = (char *)&reqbuf; res.flags = 0; res.opt.buf = (char *)&reqbuf; res.opt.maxlen = sizeof (reqbuf); if (t_optmgmt(fd, &req, &res) < 0 || res.flags != T_SUCCESS) { t_error("t_optmgmt"); return (-1); } return (reqbuf.value); } static int setopt(int fd, int level, int name, int value) { struct t_optmgmt req, resp; struct { struct opthdr opt; int value; } reqbuf; reqbuf.opt.level = level; reqbuf.opt.name = name; reqbuf.opt.len = sizeof (int); reqbuf.value = value; req.flags = T_NEGOTIATE; req.opt.len = sizeof (reqbuf); req.opt.buf = (char *)&reqbuf; resp.flags = 0; resp.opt.buf = (char *)&reqbuf; resp.opt.maxlen = sizeof (reqbuf); if (t_optmgmt(fd, &req, &resp) < 0 || resp.flags != T_SUCCESS) { t_error("t_optmgmt"); return (-1); } return (0); } static int reuseaddr(int fd) { return (setopt(fd, SOL_SOCKET, SO_REUSEADDR, 1)); } static int recvucred(int fd) { return (setopt(fd, SOL_SOCKET, SO_RECVUCRED, 1)); } static int anonmlp(int fd) { return (setopt(fd, SOL_SOCKET, SO_ANON_MLP, 1)); } void nfslib_log_tli_error(char *tli_name, int fd, struct netconfig *nconf) { int error; /* * Save the error code across syslog(), just in case syslog() * gets its own error and, therefore, overwrites errno. */ error = errno; if (t_errno == TSYSERR) { syslog(LOG_ERR, "%s(file descriptor %d/transport %s) %m", tli_name, fd, nconf->nc_proto); } else { syslog(LOG_ERR, "%s(file descriptor %d/transport %s) TLI error %d", tli_name, fd, nconf->nc_proto, t_errno); } errno = error; } /* * Called to set up service over a particular transport. */ void do_one(char *provider, NETSELDECL(proto), struct protob *protobp0, int (*svc)(int, struct netbuf, struct netconfig *)) { register int sock; struct protob *protobp; struct netbuf *retaddr; struct netconfig *retnconf; struct netbuf addrmask; int vers; int err; int l; if (provider) sock = bind_to_provider(provider, protobp0->serv, &retaddr, &retnconf); else sock = bind_to_proto(proto, protobp0->serv, &retaddr, &retnconf); if (sock == -1) { (void) syslog(LOG_ERR, "Cannot establish %s service over %s: transport setup problem.", protobp0->serv, provider ? provider : proto); return; } if (set_addrmask(sock, retnconf, &addrmask) < 0) { (void) syslog(LOG_ERR, "Cannot set address mask for %s", retnconf->nc_netid); return; } /* * Register all versions of the programs in the protocol block list. */ l = strlen(NC_UDP); for (protobp = protobp0; protobp; protobp = protobp->next) { for (vers = protobp->versmin; vers <= protobp->versmax; vers++) { if ((protobp->program == NFS_PROGRAM || protobp->program == NFS_ACL_PROGRAM) && vers == NFS_V4 && strncasecmp(retnconf->nc_proto, NC_UDP, l) == 0) continue; (void) rpcb_unset(protobp->program, vers, retnconf); (void) rpcb_set(protobp->program, vers, retnconf, retaddr); } } /* * Register services with CLTS semantics right now. * Note: services with COTS/COTS_ORD semantics will be * registered later from cots_listen_event function. */ if (retnconf->nc_semantics == NC_TPI_CLTS) { /* Don't drop core if supporting module(s) aren't loaded. */ (void) signal(SIGSYS, SIG_IGN); /* * svc() doesn't block, it returns success or failure. */ if (svc == NULL && Mysvc4 != NULL) err = (*Mysvc4)(sock, &addrmask, retnconf, NFS4_SETPORT|NFS4_KRPC_START, retaddr); else err = (*svc)(sock, addrmask, retnconf); if (err < 0) { (void) syslog(LOG_ERR, "Cannot establish %s service over : %m. Exiting", protobp0->serv, sock, retnconf->nc_proto); exit(1); } } free(addrmask.buf); /* * We successfully set up the server over this transport. * Add this descriptor to the one being polled on. */ add_to_poll_list(sock, retnconf); } /* * Set up the NFS service over all the available transports. * Returns -1 for failure, 0 for success. */ int do_all(struct protob *protobp, int (*svc)(int, struct netbuf, struct netconfig *)) { struct netconfig *nconf; NCONF_HANDLE *nc; int l; if ((nc = setnetconfig()) == (NCONF_HANDLE *)NULL) { syslog(LOG_ERR, "setnetconfig failed: %m"); return (-1); } l = strlen(NC_UDP); while (nconf = getnetconfig(nc)) { if ((nconf->nc_flag & NC_VISIBLE) && strcmp(nconf->nc_protofmly, NC_LOOPBACK) != 0 && OK_TPI_TYPE(nconf) && (protobp->program != NFS4_CALLBACK || strncasecmp(nconf->nc_proto, NC_UDP, l) != 0)) do_one(nconf->nc_device, nconf->nc_proto, protobp, svc); } (void) endnetconfig(nc); return (0); } /* * poll on the open transport descriptors for events and errors. */ void poll_for_action(void) { int nfds; int i; /* * Keep polling until all transports have been closed. When this * happens, we return. */ while ((int)num_fds > 0) { nfds = poll(poll_array, num_fds, INFTIM); switch (nfds) { case 0: continue; case -1: /* * Some errors from poll could be * due to temporary conditions, and we try to * be robust in the face of them. Other * errors (should never happen in theory) * are fatal (eg. EINVAL, EFAULT). */ switch (errno) { case EINTR: continue; case EAGAIN: case ENOMEM: (void) sleep(10); continue; default: (void) syslog(LOG_ERR, "poll failed: %m. Exiting"); exit(1); } default: break; } /* * Go through the poll list looking for events. */ for (i = 0; i < num_fds && nfds > 0; i++) { if (poll_array[i].revents) { nfds--; /* * We have a message, so try to read it. * Record the error return in errno, * so that syslog(LOG_ERR, "...%m") * dumps the corresponding error string. */ if (conn_polled[i].nc.nc_semantics == NC_TPI_CLTS) { errno = do_poll_clts_action( poll_array[i].fd, i); } else { errno = do_poll_cots_action( poll_array[i].fd, i); } if (errno == 0) continue; /* * Most returned error codes mean that there is * fatal condition which we can only deal with * by closing the transport. */ if (errno != EAGAIN && errno != ENOMEM) { (void) syslog(LOG_ERR, "Error (%m) reading descriptor %d/transport %s. Closing it.", poll_array[i].fd, conn_polled[i].nc.nc_proto); (void) t_close(poll_array[i].fd); remove_from_poll_list(poll_array[i].fd); } else if (errno == ENOMEM) (void) sleep(5); } } } (void) syslog(LOG_ERR, "All transports have been closed with errors. Exiting."); } /* * Allocate poll/transport array entries for this descriptor. */ static void add_to_poll_list(int fd, struct netconfig *nconf) { static int poll_array_size = 0; /* * If the arrays are full, allocate new ones. */ if (num_fds == poll_array_size) { struct pollfd *tpa; struct conn_entry *tnp; if (poll_array_size != 0) { tpa = poll_array; tnp = conn_polled; } else tpa = (struct pollfd *)0; poll_array_size += POLL_ARRAY_INC_SIZE; /* * Allocate new arrays. */ poll_array = (struct pollfd *) malloc(poll_array_size * sizeof (struct pollfd) + 256); conn_polled = (struct conn_entry *) malloc(poll_array_size * sizeof (struct conn_entry) + 256); if (poll_array == (struct pollfd *)NULL || conn_polled == (struct conn_entry *)NULL) { syslog(LOG_ERR, "malloc failed for poll array"); exit(1); } /* * Copy the data of the old ones into new arrays, and * free the old ones. */ if (tpa) { (void) memcpy((void *)poll_array, (void *)tpa, num_fds * sizeof (struct pollfd)); (void) memcpy((void *)conn_polled, (void *)tnp, num_fds * sizeof (struct conn_entry)); free((void *)tpa); free((void *)tnp); } } /* * Set the descriptor and event list. All possible events are * polled for. */ poll_array[num_fds].fd = fd; poll_array[num_fds].events = POLLIN|POLLRDNORM|POLLRDBAND|POLLPRI; /* * Copy the transport data over too. */ conn_polled[num_fds].nc = *nconf; conn_polled[num_fds].closing = 0; /* * Set the descriptor to non-blocking. Avoids a race * between data arriving on the stream and then having it * flushed before we can read it. */ if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { (void) syslog(LOG_ERR, "fcntl(file desc. %d/transport %s, F_SETFL, O_NONBLOCK): %m. Exiting", num_fds, nconf->nc_proto); exit(1); } /* * Count this descriptor. */ ++num_fds; } static void remove_from_poll_list(int fd) { int i; int num_to_copy; for (i = 0; i < num_fds; i++) { if (poll_array[i].fd == fd) { --num_fds; num_to_copy = num_fds - i; (void) memcpy((void *)&poll_array[i], (void *)&poll_array[i+1], num_to_copy * sizeof (struct pollfd)); (void) memset((void *)&poll_array[num_fds], 0, sizeof (struct pollfd)); (void) memcpy((void *)&conn_polled[i], (void *)&conn_polled[i+1], num_to_copy * sizeof (struct conn_entry)); (void) memset((void *)&conn_polled[num_fds], 0, sizeof (struct conn_entry)); return; } } syslog(LOG_ERR, "attempt to remove nonexistent fd from poll list"); } /* * Called to read and interpret the event on a connectionless descriptor. * Returns 0 if successful, or a UNIX error code if failure. */ static int do_poll_clts_action(int fd, int conn_index) { int error; int ret; int flags; struct netconfig *nconf = &conn_polled[conn_index].nc; static struct t_unitdata *unitdata = NULL; static struct t_uderr *uderr = NULL; static int oldfd = -1; struct nd_hostservlist *host = NULL; struct strbuf ctl[1], data[1]; /* * We just need to have some space to consume the * message in the event we can't use the TLI interface to do the * job. * * We flush the message using getmsg(). For the control part * we allocate enough for any TPI header plus 32 bytes for address * and options. For the data part, there is nothing magic about * the size of the array, but 256 bytes is probably better than * 1 byte, and we don't expect any data portion anyway. * * If the array sizes are too small, we handle this because getmsg() * (called to consume the message) will return MOREDATA|MORECTL. * Thus we just call getmsg() until it's read the message. */ char ctlbuf[sizeof (union T_primitives) + 32]; char databuf[256]; /* * If this is the same descriptor as the last time * do_poll_clts_action was called, we can save some * de-allocation and allocation. */ if (oldfd != fd) { oldfd = fd; if (unitdata) { (void) t_free((char *)unitdata, T_UNITDATA); unitdata = NULL; } if (uderr) { (void) t_free((char *)uderr, T_UDERROR); uderr = NULL; } } /* * Allocate a unitdata structure for receiving the event. */ if (unitdata == NULL) { /* LINTED pointer alignment */ unitdata = (struct t_unitdata *)t_alloc(fd, T_UNITDATA, T_ALL); if (unitdata == NULL) { if (t_errno == TSYSERR) { /* * Save the error code across * syslog(), just in case * syslog() gets its own error * and therefore overwrites errno. */ error = errno; (void) syslog(LOG_ERR, "t_alloc(file descriptor %d/transport %s, T_UNITDATA) failed: %m", fd, nconf->nc_proto); return (error); } (void) syslog(LOG_ERR, "t_alloc(file descriptor %d/transport %s, T_UNITDATA) failed TLI error %d", fd, nconf->nc_proto, t_errno); goto flush_it; } } try_again: flags = 0; /* * The idea is we wait for T_UNITDATA_IND's. Of course, * we don't get any, because rpcmod filters them out. * However, we need to call t_rcvudata() to let TLI * tell us we have a T_UDERROR_IND. * * algorithm is: * t_rcvudata(), expecting TLOOK. * t_look(), expecting T_UDERR. * t_rcvuderr(), expecting success (0). * expand destination address into ASCII, * and dump it. */ ret = t_rcvudata(fd, unitdata, &flags); if (ret == 0 || t_errno == TBUFOVFLW) { (void) syslog(LOG_WARNING, "t_rcvudata(file descriptor %d/transport %s) got unexpected data, %d bytes", fd, nconf->nc_proto, unitdata->udata.len); /* * Even though we don't expect any data, in case we do, * keep reading until there is no more. */ if (flags & T_MORE) goto try_again; return (0); } switch (t_errno) { case TNODATA: return (0); case TSYSERR: /* * System errors are returned to caller. * Save the error code across * syslog(), just in case * syslog() gets its own error * and therefore overwrites errno. */ error = errno; (void) syslog(LOG_ERR, "t_rcvudata(file descriptor %d/transport %s) %m", fd, nconf->nc_proto); return (error); case TLOOK: break; default: (void) syslog(LOG_ERR, "t_rcvudata(file descriptor %d/transport %s) TLI error %d", fd, nconf->nc_proto, t_errno); goto flush_it; } ret = t_look(fd); switch (ret) { case 0: return (0); case -1: /* * System errors are returned to caller. */ if (t_errno == TSYSERR) { /* * Save the error code across * syslog(), just in case * syslog() gets its own error * and therefore overwrites errno. */ error = errno; (void) syslog(LOG_ERR, "t_look(file descriptor %d/transport %s) %m", fd, nconf->nc_proto); return (error); } (void) syslog(LOG_ERR, "t_look(file descriptor %d/transport %s) TLI error %d", fd, nconf->nc_proto, t_errno); goto flush_it; case T_UDERR: break; default: (void) syslog(LOG_WARNING, "t_look(file descriptor %d/transport %s) returned %d not T_UDERR (%d)", fd, nconf->nc_proto, ret, T_UDERR); } if (uderr == NULL) { /* LINTED pointer alignment */ uderr = (struct t_uderr *)t_alloc(fd, T_UDERROR, T_ALL); if (uderr == NULL) { if (t_errno == TSYSERR) { /* * Save the error code across * syslog(), just in case * syslog() gets its own error * and therefore overwrites errno. */ error = errno; (void) syslog(LOG_ERR, "t_alloc(file descriptor %d/transport %s, T_UDERROR) failed: %m", fd, nconf->nc_proto); return (error); } (void) syslog(LOG_ERR, "t_alloc(file descriptor %d/transport %s, T_UDERROR) failed TLI error: %d", fd, nconf->nc_proto, t_errno); goto flush_it; } } ret = t_rcvuderr(fd, uderr); if (ret == 0) { /* * Save the datagram error in errno, so that the * %m argument to syslog picks up the error string. */ errno = uderr->error; /* * Log the datagram error, then log the host that * probably triggerred. Cannot log both in the * same transaction because of packet size limitations * in /dev/log. */ (void) syslog((errno == ECONNREFUSED) ? LOG_DEBUG : LOG_WARNING, "NFS response over generated error: %m", fd, nconf->nc_proto); /* * Try to map the client's address back to a * name. */ ret = netdir_getbyaddr(nconf, &host, &uderr->addr); if (ret != -1 && host && host->h_cnt > 0 && host->h_hostservs) { (void) syslog((errno == ECONNREFUSED) ? LOG_DEBUG : LOG_WARNING, "Bad NFS response was sent to client with host name: %s; service port: %s", host->h_hostservs->h_host, host->h_hostservs->h_serv); } else { int i, j; char *buf; char *hex = "0123456789abcdef"; /* * Mapping failed, print the whole thing * in ASCII hex. */ buf = (char *)malloc(uderr->addr.len * 2 + 1); for (i = 0, j = 0; i < uderr->addr.len; i++, j += 2) { buf[j] = hex[((uderr->addr.buf[i]) >> 4) & 0xf]; buf[j+1] = hex[uderr->addr.buf[i] & 0xf]; } buf[j] = '\0'; (void) syslog((errno == ECONNREFUSED) ? LOG_DEBUG : LOG_WARNING, "Bad NFS response was sent to client with transport address: 0x%s", buf); free((void *)buf); } if (ret == 0 && host != NULL) netdir_free((void *)host, ND_HOSTSERVLIST); return (0); } switch (t_errno) { case TNOUDERR: goto flush_it; case TSYSERR: /* * System errors are returned to caller. * Save the error code across * syslog(), just in case * syslog() gets its own error * and therefore overwrites errno. */ error = errno; (void) syslog(LOG_ERR, "t_rcvuderr(file descriptor %d/transport %s) %m", fd, nconf->nc_proto); return (error); default: (void) syslog(LOG_ERR, "t_rcvuderr(file descriptor %d/transport %s) TLI error %d", fd, nconf->nc_proto, t_errno); goto flush_it; } flush_it: /* * If we get here, then we could not cope with whatever message * we attempted to read, so flush it. If we did read a message, * and one isn't present, that is all right, because fd is in * nonblocking mode. */ (void) syslog(LOG_ERR, "Flushing one input message from ", fd, nconf->nc_proto); /* * Read and discard the message. Do this this until there is * no more control/data in the message or until we get an error. */ do { ctl->maxlen = sizeof (ctlbuf); ctl->buf = ctlbuf; data->maxlen = sizeof (databuf); data->buf = databuf; flags = 0; ret = getmsg(fd, ctl, data, &flags); if (ret == -1) return (errno); } while (ret != 0); return (0); } static void conn_close_oldest(void) { int fd; int i1; /* * Find the oldest connection that is not already in the * process of shutting down. */ for (i1 = end_listen_fds; /* no conditional expression */; i1++) { if (i1 >= num_fds) return; if (conn_polled[i1].closing == 0) break; } #ifdef DEBUG printf("too many connections (%d), releasing oldest (%d)\n", num_conns, poll_array[i1].fd); #else syslog(LOG_WARNING, "too many connections (%d), releasing oldest (%d)", num_conns, poll_array[i1].fd); #endif fd = poll_array[i1].fd; if (conn_polled[i1].nc.nc_semantics == NC_TPI_COTS) { /* * For politeness, send a T_DISCON_REQ to the transport * provider. We close the stream anyway. */ (void) t_snddis(fd, (struct t_call *)0); num_conns--; remove_from_poll_list(fd); (void) t_close(fd); } else { /* * For orderly release, we do not close the stream * until the T_ORDREL_IND arrives to complete * the handshake. */ if (t_sndrel(fd) == 0) conn_polled[i1].closing = 1; } } static boolean_t conn_get(int fd, struct netconfig *nconf, struct conn_ind **connp) { struct conn_ind *conn; struct conn_ind *next_conn; conn = (struct conn_ind *)malloc(sizeof (*conn)); if (conn == NULL) { syslog(LOG_ERR, "malloc for listen indication failed"); return (FALSE); } /* LINTED pointer alignment */ conn->conn_call = (struct t_call *)t_alloc(fd, T_CALL, T_ALL); if (conn->conn_call == NULL) { free((char *)conn); nfslib_log_tli_error("t_alloc", fd, nconf); return (FALSE); } if (t_listen(fd, conn->conn_call) == -1) { nfslib_log_tli_error("t_listen", fd, nconf); (void) t_free((char *)conn->conn_call, T_CALL); free((char *)conn); return (FALSE); } if (conn->conn_call->udata.len > 0) { syslog(LOG_WARNING, "rejecting inbound connection(%s) with %d bytes of connect data", nconf->nc_proto, conn->conn_call->udata.len); conn->conn_call->udata.len = 0; (void) t_snddis(fd, conn->conn_call); (void) t_free((char *)conn->conn_call, T_CALL); free((char *)conn); return (FALSE); } if ((next_conn = *connp) != NULL) { next_conn->conn_prev->conn_next = conn; conn->conn_next = next_conn; conn->conn_prev = next_conn->conn_prev; next_conn->conn_prev = conn; } else { conn->conn_next = conn; conn->conn_prev = conn; *connp = conn; } return (TRUE); } static int discon_get(int fd, struct netconfig *nconf, struct conn_ind **connp) { struct conn_ind *conn; struct t_discon discon; discon.udata.buf = (char *)0; discon.udata.maxlen = 0; if (t_rcvdis(fd, &discon) == -1) { nfslib_log_tli_error("t_rcvdis", fd, nconf); return (-1); } conn = *connp; if (conn == NULL) return (0); do { if (conn->conn_call->sequence == discon.sequence) { if (conn->conn_next == conn) *connp = (struct conn_ind *)0; else { if (conn == *connp) { *connp = conn->conn_next; } conn->conn_next->conn_prev = conn->conn_prev; conn->conn_prev->conn_next = conn->conn_next; } free((char *)conn); break; } conn = conn->conn_next; } while (conn != *connp); return (0); } static void cots_listen_event(int fd, int conn_index) { struct t_call *call; struct conn_ind *conn; struct conn_ind *conn_head; int event; struct netconfig *nconf = &conn_polled[conn_index].nc; int new_fd; struct netbuf addrmask; int ret = 0; char *clnt; char *clnt_uaddr = NULL; struct nd_hostservlist *clnt_serv = NULL; conn_head = NULL; (void) conn_get(fd, nconf, &conn_head); while ((conn = conn_head) != NULL) { conn_head = conn->conn_next; if (conn_head == conn) conn_head = NULL; else { conn_head->conn_prev = conn->conn_prev; conn->conn_prev->conn_next = conn_head; } call = conn->conn_call; free(conn); /* * If we have already accepted the maximum number of * connections allowed on the command line, then drop * the oldest connection (for any protocol) before * accepting the new connection. Unless explicitly * set on the command line, max_conns_allowed is -1. */ if (max_conns_allowed != -1 && num_conns >= max_conns_allowed) conn_close_oldest(); /* * Create a new transport endpoint for the same proto as * the listener. */ new_fd = nfslib_transport_open(nconf); if (new_fd == -1) { call->udata.len = 0; (void) t_snddis(fd, call); (void) t_free((char *)call, T_CALL); syslog(LOG_ERR, "Cannot establish transport over %s", nconf->nc_device); continue; } /* Bind to a generic address/port for the accepting stream. */ if (t_bind(new_fd, NULL, NULL) == -1) { nfslib_log_tli_error("t_bind", new_fd, nconf); call->udata.len = 0; (void) t_snddis(fd, call); (void) t_free((char *)call, T_CALL); (void) t_close(new_fd); continue; } while (t_accept(fd, new_fd, call) == -1) { if (t_errno != TLOOK) { #ifdef DEBUG nfslib_log_tli_error("t_accept", fd, nconf); #endif call->udata.len = 0; (void) t_snddis(fd, call); (void) t_free((char *)call, T_CALL); (void) t_close(new_fd); goto do_next_conn; } while (event = t_look(fd)) { switch (event) { case T_LISTEN: #ifdef DEBUG printf( "cots_listen_event(%s): T_LISTEN during accept processing\n", nconf->nc_proto); #endif (void) conn_get(fd, nconf, &conn_head); continue; case T_DISCONNECT: #ifdef DEBUG printf( "cots_listen_event(%s): T_DISCONNECT during accept processing\n", nconf->nc_proto); #endif (void) discon_get(fd, nconf, &conn_head); continue; default: syslog(LOG_ERR, "unexpected event 0x%x during accept processing (%s)", event, nconf->nc_proto); call->udata.len = 0; (void) t_snddis(fd, call); (void) t_free((char *)call, T_CALL); (void) t_close(new_fd); goto do_next_conn; } } } if (set_addrmask(new_fd, nconf, &addrmask) < 0) { (void) syslog(LOG_ERR, "Cannot set address mask for %s", nconf->nc_netid); (void) t_snddis(new_fd, NULL); (void) t_free((char *)call, T_CALL); (void) t_close(new_fd); continue; } /* Tell kRPC about the new stream. */ if (Mysvc4 != NULL) ret = (*Mysvc4)(new_fd, &addrmask, nconf, NFS4_KRPC_START, &call->addr); else ret = (*Mysvc)(new_fd, addrmask, nconf); if (ret < 0) { if (errno != ENOTCONN) { syslog(LOG_ERR, "unable to register new connection: %m"); } else { /* * This is the only error that could be * caused by the client, so who was it? */ if (netdir_getbyaddr(nconf, &clnt_serv, &(call->addr)) == ND_OK && clnt_serv->h_cnt > 0) clnt = clnt_serv->h_hostservs->h_host; else clnt = clnt_uaddr = taddr2uaddr(nconf, &(call->addr)); /* * If we don't know who the client was, * remain silent. */ if (clnt) syslog(LOG_ERR, "unable to register new connection: client %s has dropped connection", clnt); if (clnt_serv) { netdir_free(clnt_serv, ND_HOSTSERVLIST); clnt_serv = NULL; } if (clnt_uaddr) { free(clnt_uaddr); clnt_uaddr = NULL; } } free(addrmask.buf); (void) t_snddis(new_fd, NULL); (void) t_free((char *)call, T_CALL); (void) t_close(new_fd); goto do_next_conn; } free(addrmask.buf); (void) t_free((char *)call, T_CALL); /* * Poll on the new descriptor so that we get disconnect * and orderly release indications. */ num_conns++; add_to_poll_list(new_fd, nconf); /* Reset nconf in case it has been moved. */ nconf = &conn_polled[conn_index].nc; do_next_conn:; } } static int do_poll_cots_action(int fd, int conn_index) { char buf[256]; int event; int i1; int flags; struct conn_entry *connent = &conn_polled[conn_index]; struct netconfig *nconf = &(connent->nc); const char *errorstr; while (event = t_look(fd)) { switch (event) { case T_LISTEN: #ifdef DEBUG printf("do_poll_cots_action(%s,%d): T_LISTEN event\n", nconf->nc_proto, fd); #endif cots_listen_event(fd, conn_index); break; case T_DATA: #ifdef DEBUG printf("do_poll_cots_action(%d,%s): T_DATA event\n", fd, nconf->nc_proto); #endif /* * Receive a private notification from CONS rpcmod. */ i1 = t_rcv(fd, buf, sizeof (buf), &flags); if (i1 == -1) { syslog(LOG_ERR, "t_rcv failed"); break; } if (i1 < sizeof (int)) break; i1 = BE32_TO_U32(buf); if (i1 == 1 || i1 == 2) { /* * This connection has been idle for too long, * so release it as politely as we can. If we * have already initiated an orderly release * and we get notified that the stream is * still idle, pull the plug. This prevents * hung connections from continuing to consume * resources. */ #ifdef DEBUG printf("do_poll_cots_action(%s,%d): ", nconf->nc_proto, fd); printf("initiating orderly release of idle connection\n"); #endif if (nconf->nc_semantics == NC_TPI_COTS || connent->closing != 0) { (void) t_snddis(fd, (struct t_call *)0); goto fdclose; } /* * For NC_TPI_COTS_ORD, the stream is closed * and removed from the poll list when the * T_ORDREL is received from the provider. We * don't wait for it here because it may take * a while for the transport to shut down. */ if (t_sndrel(fd) == -1) { syslog(LOG_ERR, "unable to send orderly release %m"); } connent->closing = 1; } else syslog(LOG_ERR, "unexpected event from CONS rpcmod %d", i1); break; case T_ORDREL: #ifdef DEBUG printf("do_poll_cots_action(%s,%d): T_ORDREL event\n", nconf->nc_proto, fd); #endif /* Perform an orderly release. */ if (t_rcvrel(fd) == 0) { /* T_ORDREL on listen fd's should be ignored */ if (!is_listen_fd_index(conn_index)) { (void) t_sndrel(fd); goto fdclose; } break; } else if (t_errno == TLOOK) { break; } else { nfslib_log_tli_error("t_rcvrel", fd, nconf); /* * check to make sure we do not close * listen fd */ if (is_listen_fd_index(conn_index)) break; else goto fdclose; } case T_DISCONNECT: #ifdef DEBUG printf("do_poll_cots_action(%s,%d): T_DISCONNECT event\n", nconf->nc_proto, fd); #endif if (t_rcvdis(fd, (struct t_discon *)NULL) == -1) nfslib_log_tli_error("t_rcvdis", fd, nconf); /* * T_DISCONNECT on listen fd's should be ignored. */ if (is_listen_fd_index(conn_index)) break; else goto fdclose; default: if (t_errno == TSYSERR) { if ((errorstr = strerror(errno)) == NULL) { (void) sprintf(buf, "Unknown error num %d", errno); errorstr = (const char *) buf; } } else if (event == -1) errorstr = t_strerror(t_errno); else errorstr = ""; syslog(LOG_ERR, "unexpected TLI event (0x%x) on " "connection-oriented transport(%s,%d):%s", event, nconf->nc_proto, fd, errorstr); fdclose: num_conns--; remove_from_poll_list(fd); (void) t_close(fd); return (0); } } return (0); } static char * serv_name_to_port_name(char *name) { /* * Map service names (used primarily in logging) to * RPC port names (used by netdir_*() routines). */ if (strcmp(name, "NFS") == 0) { return ("nfs"); } else if (strcmp(name, "NLM") == 0) { return ("lockd"); } else if (strcmp(name, "NFS4_CALLBACK") == 0) { return ("nfs4_callback"); } return ("unrecognized"); } static int bind_to_provider(char *provider, char *serv, struct netbuf **addr, struct netconfig **retnconf) { struct netconfig *nconf; NCONF_HANDLE *nc; struct nd_hostserv hs; hs.h_host = HOST_SELF; hs.h_serv = serv_name_to_port_name(serv); if ((nc = setnetconfig()) == (NCONF_HANDLE *)NULL) { syslog(LOG_ERR, "setnetconfig failed: %m"); return (-1); } while (nconf = getnetconfig(nc)) { if (OK_TPI_TYPE(nconf) && strcmp(nconf->nc_device, provider) == 0) { *retnconf = nconf; return (nfslib_bindit(nconf, addr, &hs, listen_backlog)); } } (void) endnetconfig(nc); syslog(LOG_ERR, "couldn't find netconfig entry for provider %s", provider); return (-1); } static int bind_to_proto(NETSELDECL(proto), char *serv, struct netbuf **addr, struct netconfig **retnconf) { struct netconfig *nconf; NCONF_HANDLE *nc = NULL; struct nd_hostserv hs; hs.h_host = HOST_SELF; hs.h_serv = serv_name_to_port_name(serv); if ((nc = setnetconfig()) == (NCONF_HANDLE *)NULL) { syslog(LOG_ERR, "setnetconfig failed: %m"); return (-1); } while (nconf = getnetconfig(nc)) { if (OK_TPI_TYPE(nconf) && NETSELEQ(nconf->nc_proto, proto)) { *retnconf = nconf; return (nfslib_bindit(nconf, addr, &hs, listen_backlog)); } } (void) endnetconfig(nc); syslog(LOG_ERR, "couldn't find netconfig entry for protocol %s", proto); return (-1); } #include /* * Create an address mask appropriate for the transport. * The mask is used to obtain the host-specific part of * a network address when comparing addresses. * For an internet address the host-specific part is just * the 32 bit IP address and this part of the mask is set * to all-ones. The port number part of the mask is zeroes. */ static int set_addrmask(int fd, struct netconfig *nconf, struct netbuf *mask) { struct t_info info; /* * Find the size of the address we need to mask. */ if (t_getinfo(fd, &info) < 0) { t_error("t_getinfo"); return (-1); } mask->len = mask->maxlen = info.addr; if (info.addr <= 0) { /* * loopback devices have infinite addr size * (it is identified by -1 in addr field of t_info structure), * so don't build the netmask for them. It's a special case * that should be handled properly. */ if ((info.addr == -1) && (0 == strcmp(nconf->nc_protofmly, NC_LOOPBACK))) { memset(mask, 0, sizeof (*mask)); return (0); } syslog(LOG_ERR, "set_addrmask: address size: %ld", info.addr); return (-1); } mask->buf = (char *)malloc(mask->len); if (mask->buf == NULL) { syslog(LOG_ERR, "set_addrmask: no memory"); return (-1); } (void) memset(mask->buf, 0, mask->len); /* reset all mask bits */ if (strcmp(nconf->nc_protofmly, NC_INET) == 0) { /* * Set the mask so that the port is ignored. */ /* LINTED pointer alignment */ ((struct sockaddr_in *)mask->buf)->sin_addr.s_addr = (in_addr_t)~0; /* LINTED pointer alignment */ ((struct sockaddr_in *)mask->buf)->sin_family = (ushort_t)~0; } else if (strcmp(nconf->nc_protofmly, NC_INET6) == 0) { /* LINTED pointer alignment */ (void) memset(&((struct sockaddr_in6 *)mask->buf)->sin6_addr, (uchar_t)~0, sizeof (struct in6_addr)); /* LINTED pointer alignment */ ((struct sockaddr_in6 *)mask->buf)->sin6_family = (ushort_t)~0; } else { /* * Set all mask bits. */ (void) memset(mask->buf, 0xFF, mask->len); } return (0); } /* * For listen fd's index is always less than end_listen_fds. * end_listen_fds is defined externally in the daemon that uses this library. * It's value is equal to the number of open file descriptors after the * last listen end point was opened but before any connection was accepted. */ static int is_listen_fd_index(int index) { return (index < end_listen_fds); } /* * 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) 1996, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * nfs_tbind.h, common code for nfsd and lockd */ #ifndef _NFS_TBIND_H #define _NFS_TBIND_H #include #include #ifdef __cplusplus extern "C" { #endif /* * Globals which should be initialised by daemon main(). */ extern size_t end_listen_fds; extern size_t num_fds; extern int listen_backlog; extern int (*Mysvc)(int, struct netbuf, struct netconfig *); extern int (*Mysvc4)(int, struct netbuf *, struct netconfig *, int, struct netbuf *); extern int max_conns_allowed; /* * RPC protocol block. Useful for passing registration information. */ struct protob { char *serv; /* ASCII service name, e.g. "NFS" */ int versmin; /* minimum version no. to be registered */ int versmax; /* maximum version no. to be registered */ int program; /* program no. to be registered */ struct protob *next; /* next entry on list */ }; /* * Declarations for protocol types and comparison. */ #define NETSELDECL(x) char *x #define NETSELPDECL(x) char **x #define NETSELEQ(x, y) (strcmp((x), (y)) == 0) /* * nfs library routines */ extern int nfslib_transport_open(struct netconfig *); extern int nfslib_bindit(struct netconfig *, struct netbuf **, struct nd_hostserv *, int); extern void nfslib_log_tli_error(char *, int, struct netconfig *); extern int do_all(struct protob *, int (*)(int, struct netbuf, struct netconfig *)); extern void do_one(char *, char *, struct protob *, int (*)(int, struct netbuf, struct netconfig *)); extern void poll_for_action(void); #ifdef __cplusplus } #endif #endif /* _NFS_TBIND_H */ /* * 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) 1997, 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nfslog_config.h" #define ERROR_BUFSZ 100 /* * This flag controls where error messages go. * Zero means that messages go to stderr. * Non-zero means that messages go to syslog. */ boolean_t nfsl_errs_to_syslog; /* * Pointer to the global entry in the list */ static nfsl_config_t *global = NULL; /* * Pointer to the raw global entry in the list, this is the * global entry without the expanded paths. This is used to * complete configurations. */ static nfsl_config_t *global_raw = NULL; /* * Last modification time to config file. */ static timestruc_t config_last_modification = { 0 }; /* * Whitespace characters to delimit fields in a line. */ static const char *whitespace = " \t"; static int getconfiglist(nfsl_config_t **, boolean_t); static nfsl_config_t *create_config(char *, char *, char *, char *, char *, char *, int, boolean_t, int *); static nfsl_config_t *create_global_raw(int *); static int update_config(nfsl_config_t *, char *, char *, char *, char *, char *, char *, int, boolean_t, boolean_t); static int update_field(char **, char *, char *, boolean_t *); static nfsl_config_t *findconfig(nfsl_config_t **, char *, boolean_t, nfsl_config_t **); static nfsl_config_t *getlastconfig(nfsl_config_t *); static void complete_with_global(char **, char **, char **, char **, char **, int *); #ifdef DEBUG static void remove_config(nfsl_config_t **, nfsl_config_t *, nfsl_config_t **); void nfsl_printconfig(nfsl_config_t *); #endif /* DEBUG */ static char *gataline(FILE *, char *, char *, int); static int get_info(char *, char **, char **, char **, char **, char **, char **, int *); static void free_config(nfsl_config_t *); static int is_legal_tag(char *); static boolean_t is_complete_config(char *, char *, char *, char *); /* * Read the configuration file and create a list of configuration * parameters. Returns zero for success or an errno value. * The caller is responsible for freeing the returned configlist by calling * nfsl_freeconfig_list(). * * If the configuration file does not exist, *listpp points to a config entry * containing the hardwired defaults. */ int nfsl_getconfig_list(nfsl_config_t **listpp) { int error = 0; char *locale; /* * Set the locale correctly so that we can correctly identify * alphabetic characters. */ if ((locale = getenv("LC_ALL")) != NULL) (void) setlocale(LC_ALL, locale); else if ((locale = getenv("LC_CTYPE")) != NULL) (void) setlocale(LC_CTYPE, locale); else if ((locale = getenv("LANG")) != NULL) (void) setlocale(LC_CTYPE, locale); /* * Allocate 'global_raw' structure, its contents are * indirectly allocated by create_config(). */ assert(global_raw == NULL); global_raw = create_global_raw(&error); if (global_raw == NULL) return (error); /* * Build global entry with hardwired defaults first. */ assert(global == NULL); global = create_config(DEFAULTTAG, DEFAULTDIR, BUFFERPATH, NULL, FHPATH, LOGPATH, TRANSLOG_BASIC, B_TRUE, &error); *listpp = global; if (global == NULL) { free_config(global_raw); return (error); } error = getconfiglist(listpp, B_FALSE); if (error != 0) { nfsl_freeconfig_list(listpp); } else { assert(global != NULL); /* * The global entry was replaced with the one in the file, * clear the UPDATED flag */ global->nc_flags &= ~NC_UPDATED; } return (error); } /* * Allocates memory for the 'global_raw' structure. * The actual allocation of values for its components happens in * update_config(). */ static nfsl_config_t * create_global_raw(int *error) { nfsl_config_t *p; *error = 0; p = calloc(1, sizeof (*p)); if (p == NULL) *error = ENOMEM; return (p); } /* * Checks if the the configuration file has been modified since we last * read it, if not simply returns, otherwise it re-reads it adding new * configuration entries. Note that existing entries that no longer * exist in the configuration file are not removed. Existing entries * that are modified in the configuration file are updated in the list * as well. * if 'updated' is defined then it is set to TRUE if the list was modified. * * Note that if an error occurs, the list may be corrupted. * It is the responsibility of the caller to free the list. * If the configuration file does not exist, we simply return the list * that we previously had, log a message and return success. */ int nfsl_checkconfig_list(nfsl_config_t **listpp, boolean_t *updated) { struct stat st; int error = 0; if (updated != NULL) *updated = B_FALSE; if (stat(NFSL_CONFIG_FILE_PATH, &st) == -1) { error = errno; if (nfsl_errs_to_syslog) { syslog(LOG_ERR, gettext( "Can't stat %s - %s"), NFSL_CONFIG_FILE_PATH, strerror(error)); } else { (void) fprintf(stderr, gettext( "Can't stat %s - %s\n"), NFSL_CONFIG_FILE_PATH, strerror(error)); } return (0); } if (config_last_modification.tv_sec == st.st_mtim.tv_sec && config_last_modification.tv_nsec == st.st_mtim.tv_nsec) return (0); if (updated != NULL) *updated = B_TRUE; return (getconfiglist(listpp, B_TRUE)); } /* * Does the real work. Reads the configuration file and creates the * list of entries. Assumes that *listpp contains at least one entry. * The caller is responsible for freeing any config entries added to * the list whether this routine returns an error or not. * * Returns 0 on success and updates the '*listpp' config list, * Returns non-zero error value otherwise. */ static int getconfiglist(nfsl_config_t **listpp, boolean_t updating) { FILE *fp; int error = 0; nfsl_config_t *listp = NULL, *tail = NULL; char linebuf[MAX_LINESZ]; char errorbuf[ERROR_BUFSZ]; char *tag, *defaultdir, *bufferpath, *rpclogpath, *fhpath, *logpath; int logformat; flock_t flock; struct stat st; fp = fopen(NFSL_CONFIG_FILE_PATH, "r"); if (fp == NULL) { if (updating) { (void) sprintf(errorbuf, "Can't open %s", NFSL_CONFIG_FILE_PATH); } else { (void) sprintf(errorbuf, "Can't open %s - using hardwired defaults", NFSL_CONFIG_FILE_PATH); } /* * Use hardwired config. */ if (nfsl_errs_to_syslog) syslog(LOG_ERR, gettext("%s"), errorbuf); else (void) fprintf(stderr, gettext("%s\n"), errorbuf); return (0); } (void) memset((void *) &flock, 0, sizeof (flock)); flock.l_type = F_RDLCK; if (fcntl(fileno(fp), F_SETLKW, &flock) == -1) { error = errno; if (nfsl_errs_to_syslog) { syslog(LOG_ERR, gettext( "Can't lock %s - %s"), NFSL_CONFIG_FILE_PATH, strerror(error)); } else { (void) fprintf(stderr, gettext( "Can't lock %s - %s\n"), NFSL_CONFIG_FILE_PATH, strerror(error)); } goto done; } assert (*listpp != NULL); tail = getlastconfig(*listpp); while (gataline(fp, NFSL_CONFIG_FILE_PATH, linebuf, sizeof (linebuf))) { if (linebuf[0] == '\0') { /* * ignore lines that exceed max size */ continue; } error = get_info(linebuf, &tag, &defaultdir, &bufferpath, &rpclogpath, &fhpath, &logpath, &logformat); if (error != 0) break; listp = findconfig(listpp, tag, B_FALSE, &tail); if (listp != NULL) { /* * An entry with the same tag name exists, * update the fields that changed. */ error = update_config(listp, tag, defaultdir, bufferpath, rpclogpath, fhpath, logpath, logformat, B_TRUE, B_TRUE); if (error) break; } else { /* * New entry, create it. */ listp = create_config(tag, defaultdir, bufferpath, rpclogpath, fhpath, logpath, logformat, B_TRUE, &error); if (listp == NULL) break; if (*listpp == NULL) *listpp = listp; else tail->nc_next = listp; tail = listp; } assert(global != NULL); } if (error == 0) { /* * Get mtime while we have file locked */ error = fstat(fileno(fp), &st); if (error != 0) { error = errno; if (nfsl_errs_to_syslog) { syslog(LOG_ERR, gettext( "Can't stat %s - %s"), NFSL_CONFIG_FILE_PATH, strerror(error)); } else { (void) fprintf(stderr, gettext( "Can't stat %s - %s\n"), NFSL_CONFIG_FILE_PATH, strerror(error)); } } config_last_modification = st.st_mtim; } done: (void) fclose(fp); return (error); } /* * Creates the config structure with the values specified by the * parameters. If defaultdir has been specified, all relative paths * are prepended with this defaultdir. * If 'complete' is set then this must represent a complete config entry * as specified by is_complete_config(), otherwise no work is perfomed, and * NULL is returned. * * Returns the newly created config structure on success. * Returns NULL on failure and sets error to the appropriate error. */ static nfsl_config_t * create_config( char *tag, char *defaultdir, char *bufferpath, char *rpclogpath, char *fhpath, char *logpath, int logformat, boolean_t complete, int *error) { nfsl_config_t *config; config = calloc(1, sizeof (*config)); if (config == NULL) { *error = ENOMEM; return (NULL); } *error = update_config(config, tag, defaultdir, bufferpath, rpclogpath, fhpath, logpath, logformat, complete, B_TRUE); if (*error) { free(config); return (NULL); } config->nc_flags &= ~NC_UPDATED; /* This is a new entry */ return (config); } /* * Updates the configuration entry with the new information provided, * sets NC_UPDATED to indicate so. The entry is left untouched if all * the fields are the same (except for 'nc_rpccookie', 'nc_transcookie' * and 'nc_next'). * Prepends each path component with 'defauldir' if 'prepend' is set. * * Returns 0 on success, error otherwise. * On error, the config entry is left in an inconsistent state. * The only thing the caller can really do with it is free it. */ static int update_config( nfsl_config_t *config, char *tag, char *defaultdir, char *bufferpath, char *rpclogpath, char *fhpath, char *logpath, int logformat, boolean_t complete, boolean_t prepend) { boolean_t updated, config_updated = B_FALSE; int error = 0; if (complete && !is_complete_config(tag, bufferpath, fhpath, logpath)) { /* * Not a complete entry */ if (nfsl_errs_to_syslog) { syslog(LOG_ERR, gettext( "update_config: \"%s\" not a complete " "config entry."), tag); } else { (void) fprintf(stderr, gettext( "update_config: \"%s\" not a complete " "config entry.\n"), tag); } return (EINVAL); } assert(tag != NULL); if (config->nc_name == NULL) { /* * New entry */ if ((config->nc_name = strdup(tag)) == NULL) { error = ENOMEM; goto errout; } } else { assert(strcmp(config->nc_name, tag) == 0); } error = update_field( &config->nc_defaultdir, defaultdir, NULL, &updated); if (error != 0) goto errout; if (!prepend) { /* * Do not prepend default directory. */ defaultdir = NULL; } config_updated |= updated; error = update_field( &config->nc_bufferpath, bufferpath, defaultdir, &updated); if (error != 0) goto errout; config_updated |= updated; error = update_field( &config->nc_rpclogpath, rpclogpath, defaultdir, &updated); if (error != 0) goto errout; config_updated |= updated; error = update_field( &config->nc_fhpath, fhpath, defaultdir, &updated); if (error != 0) goto errout; config_updated |= updated; error = update_field( &config->nc_logpath, logpath, defaultdir, &updated); if (error != 0) goto errout; config_updated |= updated; updated = (config->nc_logformat != logformat); if (updated) config->nc_logformat = logformat; config_updated |= updated; if (config_updated) config->nc_flags |= NC_UPDATED; if (strcmp(tag, DEFAULTTAG) == 0) { /* * Have the default global config point to this entry. */ global = config; /* * Update the global_raw configuration entry. * Make sure no expanding of paths occurs. */ error = update_config(global_raw, DEFAULTRAWTAG, defaultdir, bufferpath, rpclogpath, fhpath, logpath, logformat, complete, B_FALSE); if (error != 0) goto errout; } return (error); errout: if (nfsl_errs_to_syslog) { syslog(LOG_ERR, gettext( "update_config: Can't process \"%s\" config entry: %s"), tag, strerror(error)); } else { (void) fprintf(stderr, gettext( "update_config: Can't process \"%s\" config entry: %s\n"), tag, strerror(error)); } return (error); } /* * Prepends 'prependir' to 'new' if 'prependir' is defined. * Compares the value of '*old' with 'new', if it has changed, * then sets whatever 'old' references equal to 'new'. * Returns 0 on success, error otherwise. * Sets '*updated' to B_TRUE if field was modified. * The value of '*updated' is undefined on error. */ static int update_field( char **old, /* pointer to config field */ char *new, /* updated value */ char *prependdir, /* prepend this directory to new */ boolean_t *updated) /* field was modified */ { char *tmp_new = NULL; int need_update = 0; if (new != NULL) { if (prependdir != NULL && new[0] != '/') { tmp_new = malloc(strlen(prependdir) + strlen(new) + 2); if (tmp_new == NULL) return (ENOMEM); (void) sprintf(tmp_new, "%s/%s", prependdir, new); } else { if ((tmp_new = strdup(new)) == NULL) return (ENOMEM); } } if (tmp_new != NULL) { if (*old == NULL) need_update++; else if (strcmp(tmp_new, *old) != 0) { free(*old); need_update++; } if (need_update) *old = tmp_new; } else if (*old != NULL) { need_update++; free(*old); *old = NULL; } *updated = need_update != 0; return (0); } #ifdef DEBUG /* * Removes and frees the 'config' entry from the list * pointed to by '*listpp'. * No error is reported if the entry does not exist. * Updates '*tail' to point to the last item in the list. */ static void remove_config( nfsl_config_t **listpp, nfsl_config_t *config, nfsl_config_t **tail) { nfsl_config_t *p, *prev; prev = *listpp; for (p = *listpp; p != NULL; p = p->nc_next) { if (p == config) { if (p == prev) { /* * first element of the list */ *listpp = prev->nc_next; } else prev->nc_next = p->nc_next; free_config(p); break; } prev = p; } /* * Find tail of the list. */ for (*tail = prev; (*tail)->nc_next != NULL; *tail = (*tail)->nc_next) ; } #endif /* DEBUG */ static void free_config(nfsl_config_t *config) { if (config == NULL) return; if (config->nc_name) free(config->nc_name); if (config->nc_defaultdir) free(config->nc_defaultdir); if (config->nc_bufferpath) free(config->nc_bufferpath); if (config->nc_rpclogpath) free(config->nc_rpclogpath); if (config->nc_fhpath) free(config->nc_fhpath); if (config->nc_logpath) free(config->nc_logpath); if (config == global) global = NULL; if (config == global_raw) global_raw = NULL; free(config); } void nfsl_freeconfig_list(nfsl_config_t **listpp) { nfsl_config_t *next; if (*listpp == NULL) return; do { next = (*listpp)->nc_next; free_config(*listpp); *listpp = next; } while (*listpp); free_config(global_raw); } /* * Returns a pointer to the first instance of 'tag' in the list. * If 'remove' is true, then the entry is removed from the list and * a pointer to it is returned. * If '*tail' is not NULL, then it will point to the last element of * the list. Note that this function assumes that *tail already * points at the last element of the list. * Returns NULL if the entry does not exist. */ static nfsl_config_t * findconfig( nfsl_config_t **listpp, char *tag, boolean_t remove, nfsl_config_t **tail) { nfsl_config_t *p, *prev; prev = *listpp; for (p = *listpp; p != NULL; p = p->nc_next) { if (strcmp(p->nc_name, tag) == 0) { if (remove) { if (p == prev) { /* * first element of the list */ *listpp = prev->nc_next; } else prev->nc_next = p->nc_next; if (tail != NULL && p == *tail) { /* * Only update *tail if we removed * the last element of the list, and we * requested *tail to be updated. */ *tail = prev; } } return (p); } prev = p; } return (NULL); } static nfsl_config_t * getlastconfig(nfsl_config_t *listp) { nfsl_config_t *lastp = NULL; for (; listp != NULL; listp = listp->nc_next) lastp = listp; return (lastp); } /* * Returns a pointer to the first instance of 'tag' in the list. * Returns NULL if the entry does not exist. * Sets 'error' if the update of the list failed if necessary, and * returns NULL. */ nfsl_config_t * nfsl_findconfig(nfsl_config_t *listp, char *tag, int *error) { nfsl_config_t *config; boolean_t updated; *error = 0; config = findconfig(&listp, tag, B_FALSE, (nfsl_config_t **)NULL); if (config == NULL) { /* * Rebuild our list if the file has changed. */ *error = nfsl_checkconfig_list(&listp, &updated); if (*error != 0) { /* * List may be corrupted, notify caller. */ return (NULL); } if (updated) { /* * Search for tag again. */ config = findconfig(&listp, tag, B_FALSE, (nfsl_config_t **)NULL); } } return (config); } /* * Use the raw global values if any of the parameters is not defined. */ static void complete_with_global( char **defaultdir, char **bufferpath, char **rpclogpath, char **fhpath, char **logpath, int *logformat) { if (*defaultdir == NULL) *defaultdir = global_raw->nc_defaultdir; if (*bufferpath == NULL) *bufferpath = global_raw->nc_bufferpath; if (*rpclogpath == NULL) *rpclogpath = global_raw->nc_rpclogpath; if (*fhpath == NULL) *fhpath = global_raw->nc_fhpath; if (*logpath == NULL) *logpath = global_raw->nc_logpath; if (*logformat == 0) *logformat = global_raw->nc_logformat; } /* * Parses 'linebuf'. Returns 0 if a valid tag is found, otherwise non-zero. * Unknown tokens are silently ignored. * It is the responsibility of the caller to make a copy of the non-NULL * parameters if they need to be used before linebuf is freed. */ static int get_info( char *linebuf, char **tag, char **defaultdir, char **bufferpath, char **rpclogpath, char **fhpath, char **logpath, int *logformat) { char *tok; char *tmp; /* tag */ *tag = NULL; tok = strtok(linebuf, whitespace); if (tok == NULL) goto badtag; if (!is_legal_tag(tok)) goto badtag; *tag = tok; *defaultdir = *bufferpath = *rpclogpath = NULL; *fhpath = *logpath = NULL; *logformat = 0; while ((tok = strtok(NULL, whitespace)) != NULL) { if (strncmp(tok, "defaultdir=", strlen("defaultdir=")) == 0) { *defaultdir = tok + strlen("defaultdir="); } else if (strncmp(tok, "buffer=", strlen("buffer=")) == 0) { *bufferpath = tok + strlen("buffer="); } else if (strncmp(tok, "rpclog=", strlen("rpclog=")) == 0) { *rpclogpath = tok + strlen("rpclog="); } else if (strncmp(tok, "fhtable=", strlen("fhtable=")) == 0) { *fhpath = tok + strlen("fhtable="); } else if (strncmp(tok, "log=", strlen("log=")) == 0) { *logpath = tok + strlen("log="); } else if (strncmp(tok, "logformat=", strlen("logformat=")) == 0) { tmp = tok + strlen("logformat="); if (strncmp(tmp, "extended", strlen("extended")) == 0) { *logformat = TRANSLOG_EXTENDED; } else { /* * Use transaction log basic format if * 'extended' was not specified. */ *logformat = TRANSLOG_BASIC; } } } if (strcmp(*tag, DEFAULTTAG) != 0) { /* * Use global values for fields not specified if * this tag is not the global tag. */ complete_with_global(defaultdir, bufferpath, rpclogpath, fhpath, logpath, logformat); } return (0); badtag: if (nfsl_errs_to_syslog) { syslog(LOG_ERR, gettext( "Bad tag found in config file.")); } else { (void) fprintf(stderr, gettext( "Bad tag found in config file.\n")); } return (-1); } /* * Returns True if we have all the elements of a complete configuration * entry. A complete configuration has tag, bufferpath, fhpath and logpath * defined to non-zero strings. */ static boolean_t is_complete_config( char *tag, char *bufferpath, char *fhpath, char *logpath) { assert(tag != NULL); assert(strlen(tag) > 0); if ((bufferpath != NULL && strlen(bufferpath) > 0) && (fhpath != NULL && strlen(fhpath) > 0) && (logpath != NULL && strlen(logpath) > 0)) return (B_TRUE); return (B_FALSE); } #ifdef DEBUG /* * Prints the configuration entry to stdout. */ void nfsl_printconfig(nfsl_config_t *config) { if (config->nc_name) (void) printf("tag=%s\t", config->nc_name); if (config->nc_defaultdir) (void) printf("defaultdir=%s\t", config->nc_defaultdir); if (config->nc_logpath) (void) printf("logpath=%s\t", config->nc_logpath); if (config->nc_fhpath) (void) printf("fhpath=%s\t", config->nc_fhpath); if (config->nc_bufferpath) (void) printf("bufpath=%s\t", config->nc_bufferpath); if (config->nc_rpclogpath) (void) printf("rpclogpath=%s\t", config->nc_rpclogpath); if (config->nc_logformat == TRANSLOG_BASIC) (void) printf("logformat=basic"); else if (config->nc_logformat == TRANSLOG_EXTENDED) (void) printf("logformat=extended"); else (void) printf("config->nc_logformat=UNKNOWN"); if (config->nc_flags & NC_UPDATED) (void) printf("\tflags=NC_UPDATED"); (void) printf("\n"); } /* * Prints the configuration list to stdout. */ void nfsl_printconfig_list(nfsl_config_t *listp) { for (; listp != NULL; listp = listp->nc_next) { nfsl_printconfig(listp); (void) printf("\n"); } } #endif /* DEBUG */ /* * Returns non-zero if the given string is allowable for a tag, zero if * not. */ static int is_legal_tag(char *tag) { int i; int len; if (tag == NULL) return (0); len = strlen(tag); if (len == 0) return (0); for (i = 0; i < len; i++) { char c; c = tag[i]; if (!(isalnum((unsigned char)c) || c == '_')) return (0); } return (1); } /* * gataline attempts to get a line from the configuration file, * upto LINESZ. A line in the file is a concatenation of lines if the * continuation symbol '\' is used at the end of the line. Returns * line on success, a NULL on EOF, and an empty string on lines > linesz. */ static char * gataline(FILE *fp, char *path, char *line, int linesz) { char *p = line; int len; int excess = 0; *p = '\0'; for (;;) { if (fgets(p, linesz - (p-line), fp) == NULL) { return (*line ? line : NULL); /* EOF */ } len = strlen(line); if (len <= 0) { p = line; continue; } p = &line[len - 1]; /* * Is input line too long? */ if (*p != '\n') { excess = 1; /* * Perhaps last char read was '\'. Reinsert it * into the stream to ease the parsing when we * read the rest of the line to discard. */ (void) ungetc(*p, fp); break; } trim: /* trim trailing white space */ while (p >= line && isspace(*(uchar_t *)p)) *p-- = '\0'; if (p < line) { /* empty line */ p = line; continue; } if (*p == '\\') { /* continuation */ *p = '\0'; continue; } /* * Ignore comments. Comments start with '#' * which must be preceded by a whitespace, unless * '#' is the first character in the line. */ p = line; while ((p = strchr(p, '#')) != NULL) { if (p == line || isspace(*(p-1))) { *p-- = '\0'; goto trim; } p++; } break; } if (excess) { int c; /* * discard rest of line and return an empty string. * done to set the stream to the correct place when * we are done with this line. */ while ((c = getc(fp)) != EOF) { *p = c; if (*p == '\n') /* end of the long line */ break; else if (*p == '\\') { /* continuation */ if (getc(fp) == EOF) /* ignore next char */ break; } } if (nfsl_errs_to_syslog) { syslog(LOG_ERR, gettext( "%s: line too long - ignored (max %d chars)"), path, linesz-1); } else { (void) fprintf(stderr, gettext( "%s: line too long - ignored (max %d chars)\n"), path, linesz-1); } *line = '\0'; } return (line); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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) 1999 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _NFS_NFSLOG_CONFIG_H #define _NFS_NFSLOG_CONFIG_H /* * Internal configuration file API for NFS logging. * * Warning: This code is likely to change drastically in future releases. */ #ifdef __cplusplus extern "C" { #endif #ifndef LINTHAPPY #define LINTHAPPY #endif #define MAX_LINESZ 4096 #define NFSL_CONFIG_FILE_PATH "/etc/nfs/nfslog.conf" #define DEFAULTTAG "global" #define DEFAULTRAWTAG "global-raw" #define DEFAULTDIR "/var/nfs" #define BUFFERPATH "nfslog_workbuffer" #define FHPATH "fhtable" #define LOGPATH "nfslog" enum translog_format { TRANSLOG_BASIC, TRANSLOG_EXTENDED }; /* * This struct is used to get or set the logging state for a filesystem. * Using a single struct like this is okay for for releases where it's * private, but it's questionable as a * public API, because of extensibility and binary compatibility issues. * * Relative paths are interpreted relative to the root of the exported * directory tree. */ typedef struct nfsl_config { uint_t nc_flags; char *nc_name; /* tag or "global" */ char *nc_defaultdir; char *nc_logpath; char *nc_fhpath; char *nc_bufferpath; char *nc_rpclogpath; enum translog_format nc_logformat; void *nc_elfcookie; /* for rpclogfile processing */ void *nc_transcookie; /* for logfile processing */ struct nfsl_config *nc_next; } nfsl_config_t; #define NC_UPDATED 0x001 /* set when an existing entry is */ /* modified after detecting changes */ /* in the configuration file. */ /* Not set on creation of entry. */ #define NC_NOTAG_PRINTED 0x002 /* 'missing tag' syslogged */ extern boolean_t nfsl_errs_to_syslog; extern int nfsl_getconfig_list(nfsl_config_t **listpp); extern int nfsl_checkconfig_list(nfsl_config_t **listpp, boolean_t *); extern void nfsl_freeconfig_list(nfsl_config_t **listpp); extern nfsl_config_t *nfsl_findconfig(nfsl_config_t *, char *, int *); #ifndef LINTHAPPY extern void nfsl_printconfig_list(nfsl_config_t *config); #endif #ifdef __cplusplus } #endif #endif /* _NFS_NFSLOG_CONFIG_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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) 1999 by Sun Microsystems, Inc. * All rights reserved. */ /* * Manipulates the nfslogtab */ #ifndef _REENTRANT #define _REENTRANT #endif #include #include #include #include #include #include #include #include #include #include #include "nfslogtab.h" #ifndef LINTHAPPY #define LINTHAPPY #endif static void logtab_ent_list_free(struct logtab_ent_list *); /* * Retrieves the next entry from nfslogtab. * Assumes the file is locked. * '*lepp' points to the new entry if successful. * Returns: * > 0 valid entry * = 0 end of file * < 0 error */ int logtab_getent(FILE *fd, struct logtab_ent **lepp) { char line[MAXBUFSIZE + 1]; char *p; char *lasts, *tmp; char *w = " \t"; struct logtab_ent *lep = NULL; int error = 0; if ((lep = (struct logtab_ent *)malloc(sizeof (*lep))) == NULL) { return (-1); } (void) memset((char *)lep, 0, sizeof (*lep)); if ((p = fgets(line, MAXBUFSIZE, fd)) == NULL) { error = 0; goto errout; } line[strlen(line) - 1] = '\0'; tmp = (char *)strtok_r(p, w, &lasts); if (tmp == NULL) { error = -1; goto errout; } if ((lep->le_buffer = strdup(tmp)) == NULL) { error = -1; goto errout; } tmp = (char *)strtok_r(NULL, w, &lasts); if (tmp == NULL) { error = -1; goto errout; } if ((lep->le_path = strdup(tmp)) == NULL) { error = -1; goto errout; } tmp = (char *)strtok_r(NULL, w, &lasts); if (tmp == NULL) { error = -1; goto errout; } if ((lep->le_tag = strdup(tmp)) == NULL) { error = -1; goto errout; } tmp = (char *)strtok_r(NULL, w, &lasts); if (tmp == NULL) { error = -1; goto errout; } lep->le_state = atoi(tmp); *lepp = lep; return (1); errout: logtab_ent_free(lep); return (error); } /* * Append an entry to the logtab file. */ int logtab_putent(FILE *fd, struct logtab_ent *lep) { int r; if (fseek(fd, 0L, SEEK_END) < 0) return (errno); r = fprintf(fd, "%s\t%s\t%s\t%d\n", lep->le_buffer, lep->le_path, lep->le_tag, lep->le_state); return (r); } #ifndef LINTHAPPY /* * Searches the nfslogtab file looking for the next entry which matches * the search criteria. The search is continued at the current position * in the nfslogtab file. * If 'buffer' != NULL, then buffer is matched. * If 'path' != NULL, then path is matched. * If 'tag' != NULL, then tag is matched. * If 'state' != -1, then state is matched. * 'buffer', 'path' and 'tag' can all be non-NULL, which means the entry must * satisfy all requirements. * * Returns 0 on success, ENOENT otherwise. * If found, '*lepp' points to the matching entry, otherwise '*lepp' is * undefined. */ static int logtab_findent(FILE *fd, char *buffer, char *path, char *tag, int state, struct logtab_ent **lepp) { boolean_t found = B_FALSE; while (!found && (logtab_getent(fd, lepp) > 0)) { found = B_TRUE; if (buffer != NULL) found = strcmp(buffer, (*lepp)->le_buffer) == 0; if (path != NULL) found = found && (strcmp(path, (*lepp)->le_path) == 0); if (tag != NULL) found = found && (strcmp(tag, (*lepp)->le_tag) == 0); if (state != -1) found = found && (state == (*lepp)->le_state); if (!found) logtab_ent_free(*lepp); } return (found ? 0 : ENOENT); } #endif /* * Remove all entries which match the search criteria. * If 'buffer' != NULL, then buffer is matched. * If 'path' != NULL, then path is matched. * If 'tag' != NULL, then tag is matched. * If 'state' != -1, then state is matched. * 'buffer', 'path' and 'tag' can all be non-NULL, which means the entry must * satisfy all requirements. * The file is assumed to be locked. * Read the entries into a linked list of logtab_ent structures * minus the entries to be removed, then truncate the nfslogtab * file and write it back to the file from the linked list. * * On success returns 0, -1 otherwise. * Entry not found is treated as success since it was going to be removed * anyway. */ int logtab_rement(FILE *fd, char *buffer, char *path, char *tag, int state) { struct logtab_ent_list *head = NULL, *tail = NULL, *tmpl; struct logtab_ent *lep; int remcnt = 0; /* remove count */ int error = 0; boolean_t found; rewind(fd); while ((error = logtab_getent(fd, &lep)) > 0) { found = B_TRUE; if (buffer != NULL) found = strcmp(buffer, lep->le_buffer) == 0; if (path != NULL) found = found && (strcmp(path, lep->le_path) == 0); if (tag != NULL) found = found && (strcmp(tag, lep->le_tag) == 0); if (state != -1) found = found && (state == lep->le_state); if (found) { remcnt++; logtab_ent_free(lep); } else { tmpl = (struct logtab_ent_list *) malloc(sizeof (struct logtab_ent)); if (tmpl == NULL) { error = ENOENT; break; } tmpl->lel_le = lep; tmpl->lel_next = NULL; if (head == NULL) { /* * empty list */ head = tail = tmpl; } else { /* * Add to the end of the list and remember * the new last element. */ tail->lel_next = tmpl; tail = tmpl; /* remember the last element */ } } } if (error) goto deallocate; if (remcnt == 0) { /* * Entry not found, nothing to do */ goto deallocate; } if (ftruncate(fileno(fd), 0) < 0) { error = -1; goto deallocate; } for (tmpl = head; tmpl != NULL; tmpl = tmpl->lel_next) (void) logtab_putent(fd, tmpl->lel_le); deallocate: logtab_ent_list_free(head); return (error); } /* * Deactivate all entries matching search criteria. * If 'buffer' != NULL then match buffer. * If 'path' != NULL then match path. * If 'tag' != NULL then match tag. * Note that 'buffer', 'path' and 'tag' can al be non-null at the same time. * * Rewrites the nfslogtab file with the updated state for each entry. * Assumes the nfslogtab file has been locked for writing. * Returns 0 on success, -1 on failure. */ int logtab_deactivate(FILE *fd, char *buffer, char *path, char *tag) { struct logtab_ent_list *lelp, *head = NULL, *tail = NULL; struct logtab_ent *lep; boolean_t found; int error = 0; int count = 0; rewind(fd); while ((error = logtab_getent(fd, &lep)) > 0) { found = B_TRUE; if (buffer != NULL) found = strcmp(buffer, lep->le_buffer) == 0; if (path != NULL) found = found && (strcmp(path, lep->le_path) == 0); if (tag != NULL) found = found && (strcmp(tag, lep->le_tag) == 0); if (found && (lep->le_state == LES_ACTIVE)) { count++; lep->le_state = LES_INACTIVE; } lelp = (struct logtab_ent_list *) malloc(sizeof (struct logtab_ent)); if (lelp == NULL) { error = ENOENT; break; } lelp->lel_le = lep; lelp->lel_next = NULL; if (head == NULL) { /* * empty list */ head = tail = lelp; } else { /* * Add to the end of the list and remember * the new last element. */ tail->lel_next = lelp; tail = lelp; /* remember the last element */ } } if (error) goto deallocate; if (count == 0) { /* * done */ error = 0; goto deallocate; } if (ftruncate(fileno(fd), 0) < 0) { error = -1; goto deallocate; } for (lelp = head; lelp != NULL; lelp = lelp->lel_next) (void) logtab_putent(fd, lelp->lel_le); deallocate: logtab_ent_list_free(head); return (error); } /* * Deactivates all entries if nfslogtab exists and is older than boot time * This will only happen the first time it is called. * Assumes 'fd' has been locked by the caller. * Returns 0 on success, otherwise -1. */ int logtab_deactivate_after_boot(FILE *fd) { struct stat st; struct utmpx *utmpxp; int error = 0; if ((fstat(fileno(fd), &st) == 0) && ((utmpxp = getutxent()) != NULL) && (utmpxp->ut_xtime > st.st_mtime)) { if (logtab_deactivate(fd, NULL, NULL, NULL)) error = -1; } return (error); } void logtab_ent_free(struct logtab_ent *lep) { if (lep->le_buffer) free(lep->le_buffer); if (lep->le_path) free(lep->le_path); if (lep->le_tag) free(lep->le_tag); free(lep); } static void logtab_ent_list_free(struct logtab_ent_list *head) { struct logtab_ent_list *lelp, *next; if (head == NULL) return; for (lelp = head; lelp != NULL; lelp = next) { if (lelp->lel_le != NULL) logtab_ent_free(lelp->lel_le); next = lelp->lel_next; free(lelp); } } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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) 1999, 2001 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _NFSLOGTAB_H #define _NFSLOGTAB_H /* * Defines logtab */ #ifdef __cplusplus extern "C" { #endif #include #define NFSLOGTAB "/etc/nfs/nfslogtab" #define MAXBUFSIZE 65536 #define LES_INACTIVE 0 /* entry is inactive */ #define LES_ACTIVE 1 /* entry is active */ struct logtab_ent { char *le_buffer; char *le_path; char *le_tag; int le_state; }; struct logtab_ent_list { struct logtab_ent *lel_le; struct logtab_ent_list *lel_next; }; int logtab_getent(FILE *, struct logtab_ent **); int logtab_putent(FILE *, struct logtab_ent *); int logtab_rement(FILE *, char *, char *, char *, int); void logtab_ent_free(struct logtab_ent *lep); int logtab_deactivate(FILE *, char *, char *, char *); int logtab_deactivate_after_boot(FILE *); #ifdef __cplusplus } #endif #endif /* _NFSLOGTAB_H */ /* * 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 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 /* * str_to_utf8 - converts a null-terminated C string to a utf8 string */ utf8string * str_to_utf8(char *nm, utf8string *str) { int len; if (str == NULL) return (NULL); if (nm == NULL || *nm == '\0') { str->utf8string_len = 0; str->utf8string_val = NULL; return (NULL); } len = strlen(nm); str->utf8string_val = malloc(len); if (str->utf8string_val == NULL) { str->utf8string_len = 0; return (NULL); } str->utf8string_len = len; bcopy(nm, str->utf8string_val, len); return (str); } /* * Converts a utf8 string to a C string. * kmem_allocs a new string if not supplied */ char * utf8_to_str(utf8string *str, uint_t *lenp, char *s) { char *sp; char *u8p; int len; int i; if (str == NULL) return (NULL); u8p = str->utf8string_val; len = str->utf8string_len; if (len <= 0 || u8p == NULL) { if (s) *s = '\0'; return (NULL); } sp = s; if (sp == NULL) sp = malloc(len + 1); if (sp == NULL) return (NULL); /* * At least check for embedded nulls */ for (i = 0; i < len; i++) { sp[i] = u8p[i]; if (u8p[i] == '\0') { if (s == NULL) free(sp); return (NULL); } } sp[len] = '\0'; *lenp = len + 1; return (sp); } void print_referral_summary(fs_locations4 *fsl) { int i, j; uint_t l; char *s; fs_location4 *fs; if (fsl == NULL) { printf("NULL\n"); return; } for (i = 0; i < fsl->locations.locations_len; i++) { if (i > 0) printf("\n"); fs = &fsl->locations.locations_val[i]; for (j = 0; j < fs->server.server_len; j++) { s = utf8_to_str(&fs->server.server_val[j], &l, NULL); if (j > 0) printf(","); printf("%s", s ? s : ""); if (s) free(s); } printf(":"); for (j = 0; j < fs->rootpath.pathname4_len; j++) { s = utf8_to_str(&fs->rootpath.pathname4_val[j], &l, NULL); printf("/%s", s ? s : ""); if (s) free(s); } if (fs->rootpath.pathname4_len == 0) printf("/"); } printf("\n"); } /* * There is a kernel copy of this routine in nfs4_srv.c. * Changes should be kept in sync. */ static int nfs4_create_components(char *path, component4 *comp4) { int slen, plen, ncomp; char *ori_path, *nxtc, buf[MAXNAMELEN]; if (path == NULL) return (0); plen = strlen(path) + 1; /* include the terminator */ ori_path = path; ncomp = 0; /* count number of components in the path */ for (nxtc = path; nxtc < ori_path + plen; nxtc++) { if (*nxtc == '/' || *nxtc == '\0' || *nxtc == '\n') { if ((slen = nxtc - path) == 0) { path = nxtc + 1; continue; } if (comp4 != NULL) { bcopy(path, buf, slen); buf[slen] = '\0'; if (str_to_utf8(buf, &comp4[ncomp]) == NULL) return (0); } ncomp++; /* 1 valid component */ path = nxtc + 1; } if (*nxtc == '\0' || *nxtc == '\n') break; } return (ncomp); } /* * There is a kernel copy of this routine in nfs4_srv.c. * Changes should be kept in sync. */ int make_pathname4(char *path, pathname4 *pathname) { int ncomp; component4 *comp4; if (pathname == NULL) return (0); if (path == NULL) { pathname->pathname4_val = NULL; pathname->pathname4_len = 0; return (0); } /* count number of components to alloc buffer */ if ((ncomp = nfs4_create_components(path, NULL)) == 0) { pathname->pathname4_val = NULL; pathname->pathname4_len = 0; return (0); } comp4 = calloc(ncomp * sizeof (component4), 1); if (comp4 == NULL) { pathname->pathname4_val = NULL; pathname->pathname4_len = 0; return (0); } /* copy components into allocated buffer */ ncomp = nfs4_create_components(path, comp4); pathname->pathname4_val = comp4; pathname->pathname4_len = ncomp; return (ncomp); } bool_t xdr_component4(register XDR *xdrs, component4 *objp) { if (!xdr_utf8string(xdrs, objp)) return (FALSE); return (TRUE); } bool_t xdr_utf8string(register XDR *xdrs, utf8string *objp) { if (xdrs->x_op != XDR_FREE) return (xdr_bytes(xdrs, (char **)&objp->utf8string_val, (uint_t *)&objp->utf8string_len, NFS4_MAX_UTF8STRING)); return (TRUE); } bool_t xdr_pathname4(register XDR *xdrs, pathname4 *objp) { if (!xdr_array(xdrs, (char **)&objp->pathname4_val, (uint_t *)&objp->pathname4_len, NFS4_MAX_PATHNAME4, sizeof (component4), (xdrproc_t)xdr_component4)) return (FALSE); return (TRUE); } bool_t xdr_fs_location4(register XDR *xdrs, fs_location4 *objp) { if (xdrs->x_op == XDR_DECODE) { objp->server.server_val = NULL; objp->rootpath.pathname4_val = NULL; } if (!xdr_array(xdrs, (char **)&objp->server.server_val, (uint_t *)&objp->server.server_len, ~0, sizeof (utf8string), (xdrproc_t)xdr_utf8string)) return (FALSE); if (!xdr_pathname4(xdrs, &objp->rootpath)) return (FALSE); return (TRUE); } bool_t xdr_fs_locations4(register XDR *xdrs, fs_locations4 *objp) { if (xdrs->x_op == XDR_DECODE) { objp->fs_root.pathname4_len = 0; objp->fs_root.pathname4_val = NULL; objp->locations.locations_val = NULL; } if (!xdr_pathname4(xdrs, &objp->fs_root)) return (FALSE); if (!xdr_array(xdrs, (char **)&objp->locations.locations_val, (uint_t *)&objp->locations.locations_len, ~0, sizeof (fs_location4), (xdrproc_t)xdr_fs_location4)) return (FALSE); return (TRUE); } /* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _REF_SUBR_H #define _REF_SUBR_H #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include #include #include #include extern utf8string *str_to_utf8(char *, utf8string *); extern char *utf8_to_str(utf8string *, uint_t *, char *); extern void print_referral_summary(fs_locations4 *); extern int make_pathname4(char *, pathname4 *); extern bool_t xdr_component4(register XDR *, component4 *); extern bool_t xdr_utf8string(register XDR *, utf8string *); extern bool_t xdr_pathname4(register XDR *, pathname4 *); extern bool_t xdr_fs_location4(register XDR *, fs_location4 *); extern bool_t xdr_fs_locations4(register XDR *, fs_locations4 *); #ifdef __cplusplus } #endif #endif /* _REF_SUBR_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 */ /* * replica.c * * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Parse replicated server lists of the form: * * host1:/path1,host2,host3,host4:/path2,host5:/path3 * * into an array containing its constituent parts: * * host1 /path1 * host2 /path2 * host3 /path2 * host4 /path2 * host5 /path3 * where a server could also be represented in form of literal address * and in case it is an IPv6 literal address it will be enclosed in * square brackets [IPv6 Literal address] * Problems indicated by null return; they will be memory allocation * errors worthy of an error message unless count == -1, which means * a parse error. */ #include #include #include #include #include #include #include "replica.h" void free_replica(struct replica *list, int count) { int i; for (i = 0; i < count; i++) { if (list[i].host) free(list[i].host); if (list[i].path) free(list[i].path); } free(list); } struct replica * parse_replica(char *special, int *count) { struct replica *list = NULL; char *root, *special2; char *proot, *x, *y; int scount, v6addr, i; int found_colon = 0; *count = 0; scount = 0; v6addr = 0; root = special2 = strdup(special); proot = root; while (root) { switch (*root) { case '[': if ((root != special2) && (*(root -1) != ',')) { root++; break; } y = strchr(root, ']'); if (!y) { root++; break; } if ((*(y + 1) != ',') && (*(y + 1) != ':')) { root = y + 1; break; } /* * Found a v6 Literal Address, so set "v6addr" * and grab the address and store it in the list * under "host part". */ proot = root + 1; root = y + 1; v6addr = 1; if ((list = realloc(list, (*count + 1) * sizeof (struct replica))) == NULL) goto bad; bzero(&list[(*count)++], sizeof (struct replica)); *y = '\0'; list[*count-1].host = strdup(proot); if (!list[*count-1].host) goto bad; break; case ':': *root = '\0'; x = root + 1; /* * Find comma (if present), which bounds the path. * The comma implies that the user is trying to * specify failover syntax if another colon follows. */ if (((y = strchr(x, ',')) != NULL) && (strchr((y + 1), ':'))) { root = y + 1; *y = '\0'; } else { found_colon = 1; root = NULL; } /* * If "v6addr" is set, unset it, and since the "host * part" is already taken care of, skip to the "path * path" part. */ if (v6addr == 1) v6addr = 0; else { if ((list = realloc(list, (*count + 1) * sizeof (struct replica))) == NULL) goto bad; bzero(&list[(*count)++], sizeof (struct replica)); list[*count-1].host = strdup(proot); if (!list[*count-1].host) goto bad; proot = root; } for (i = scount; i < *count; i++) { list[i].path = strdup(x); if (!list[i].path) goto bad; } scount = i; proot = root; if (y) *y = ','; break; case ',': /* * If "v6addr" is set, unset it and continue * else grab the address and store it in the list * under "host part". */ if (v6addr == 1) { v6addr = 0; proot = ++root; } else { *root = '\0'; root++; if ((list = realloc(list, (*count + 1) * sizeof (struct replica))) == NULL) goto bad; bzero(&list[(*count)++], sizeof (struct replica)); list[*count-1].host = strdup(proot); if (!list[*count-1].host) goto bad; proot = root; *(root - 1) = ','; } break; default: if (*root == '\0') root = NULL; else root++; } } if (found_colon) { free(special2); return (list); } bad: if (list) free_replica(list, *count); if (!found_colon) *count = -1; free(special2); return (NULL); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 */ /* * replica.h * * Copyright (c) 1996 Sun Microsystems Inc * All Rights Reserved. */ #ifndef _REPLICA_H #define _REPLICA_H #ifdef __cplusplus extern "C" { #endif /* * Used to hold results of parsing replica lists for failover */ struct replica { char *host; char *path; }; struct replica *parse_replica(char *, int *); void free_replica(struct replica *, int); #ifdef __cplusplus } #endif #endif /* _REPLICA_H */ /* * 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 */ /* * selfcheck.c * Copyright 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include int self_check(char *hostname) { int s, res = 0; struct sioc_addrreq areq; struct hostent *hostinfo; int family; int flags; int error_num; char **hostptr; struct sockaddr_in6 ipv6addr; family = AF_INET6; /* * We cannot specify AI_DEFAULT since it includes AI_ADDRCONFIG. * Localhost name resolution will fail if no IP interfaces other than * loopback are plumbed and AI_ADDRCONFIG is specified, and this * causes localhost mounts to fail. */ flags = AI_V4MAPPED; if ((s = socket(family, SOCK_DGRAM, 0)) < 0) { syslog(LOG_ERR, "self_check: socket: %m"); return (0); } if ((hostinfo = getipnodebyname(hostname, family, flags, &error_num)) == NULL) { if (error_num == TRY_AGAIN) syslog(LOG_DEBUG, "self_check: unknown host: %s (try again later)\n", hostname); else syslog(LOG_DEBUG, "self_check: unknown host: %s\n", hostname); (void) close(s); return (0); } for (hostptr = hostinfo->h_addr_list; *hostptr; hostptr++) { bzero(&ipv6addr, sizeof (ipv6addr)); ipv6addr.sin6_family = AF_INET6; ipv6addr.sin6_addr = *((struct in6_addr *)(*hostptr)); memcpy(&areq.sa_addr, (void *)&ipv6addr, sizeof (ipv6addr)); areq.sa_res = -1; (void) ioctl(s, SIOCTMYADDR, (caddr_t)&areq); if (areq.sa_res == 1) { res = 1; break; } } freehostent(hostinfo); (void) close(s); return (res); } #define MAXIFS 32 /* * create an ifconf structure that represents all the interfaces * configured for this host. Two buffers are allcated here: * lifc - the ifconf structure returned * lifc->lifc_buf - the list of ifreq structures * Both of the buffers must be freed by the calling routine. * A NULL pointer is returned upon failure. In this case any * data that was allocated before the failure has already been * freed. */ struct lifconf * getmyaddrs(void) { int sock; struct lifnum lifn; int numifs; char *buf; struct lifconf *lifc; if ((sock = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) { syslog(LOG_ERR, "statd:getmyaddrs socket: %m"); return ((struct lifconf *)NULL); } lifn.lifn_family = AF_UNSPEC; lifn.lifn_flags = 0; if (ioctl(sock, SIOCGLIFNUM, (char *)&lifn) < 0) { syslog(LOG_ERR, "statd:getmyaddrs, get number of interfaces, error: %m"); numifs = MAXIFS; } numifs = lifn.lifn_count; lifc = (struct lifconf *)malloc(sizeof (struct lifconf)); if (lifc == NULL) { syslog(LOG_ERR, "statd:getmyaddrs, malloc for lifconf failed: %m"); (void) close(sock); return ((struct lifconf *)NULL); } buf = (char *)malloc(numifs * sizeof (struct lifreq)); if (buf == NULL) { syslog(LOG_ERR, "statd:getmyaddrs, malloc for lifreq failed: %m"); (void) close(sock); free(lifc); return ((struct lifconf *)NULL); } lifc->lifc_family = AF_UNSPEC; lifc->lifc_flags = 0; lifc->lifc_buf = buf; lifc->lifc_len = numifs * sizeof (struct lifreq); if (ioctl(sock, SIOCGLIFCONF, (char *)lifc) < 0) { syslog(LOG_ERR, "statd:getmyaddrs, SIOCGLIFCONF, error: %m"); (void) close(sock); free(buf); free(lifc); return ((struct lifconf *)NULL); } (void) close(sock); return (lifc); } int Is_ipv6present(void) { int sock; struct lifnum lifn; sock = socket(AF_INET6, SOCK_DGRAM, 0); if (sock < 0) return (0); lifn.lifn_family = AF_INET6; lifn.lifn_flags = 0; if (ioctl(sock, SIOCGLIFNUM, (char *)&lifn) < 0) { close(sock); return (0); } close(sock); if (lifn.lifn_count == 0) return (0); return (1); } /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Portions of this source code were derived from Berkeley 4.3 BSD * under license from the Regents of the University of California. */ #include #include #include #include #include #include "sharetab.h" /* * Get an entry from the share table. * There should be at least 4 fields: * * pathname resource fstype options [ description ] * * A fifth field (description) is optional. * * Returns: * > 1 valid entry * = 0 end of file * < 0 error */ int getshare(FILE *fd, share_t **shp) { static char *line = NULL; static share_t *sh = NULL; char *p; char *lasts; char *w = " \t"; if (line == NULL) { line = (char *)malloc(MAXBUFSIZE+1); if (line == NULL) return (-1); } if (sh == NULL) { sh = (share_t *)malloc(sizeof (*sh)); if (sh == NULL) return (-1); } p = fgets(line, MAXBUFSIZE, fd); if (p == NULL) return (0); line[strlen(line) - 1] = '\0'; sh->sh_path = (char *)strtok_r(p, w, &lasts); if (sh->sh_path == NULL) return (-3); sh->sh_res = (char *)strtok_r(NULL, w, &lasts); if (sh->sh_res == NULL) return (-3); sh->sh_fstype = (char *)strtok_r(NULL, w, &lasts); if (sh->sh_fstype == NULL) return (-3); sh->sh_opts = (char *)strtok_r(NULL, w, &lasts); if (sh->sh_opts == NULL) return (-3); sh->sh_descr = (char *)strtok_r(NULL, "", &lasts); if (sh->sh_descr == NULL) sh->sh_descr = ""; *shp = sh; return (1); } share_t * sharedup(share_t *sh) { share_t *nsh; nsh = (share_t *)calloc(1, sizeof (*nsh)); if (nsh == NULL) return (NULL); if (sh->sh_path) { nsh->sh_path = strdup(sh->sh_path); if (nsh->sh_path == NULL) goto alloc_failed; } if (sh->sh_res) { nsh->sh_res = strdup(sh->sh_res); if (nsh->sh_res == NULL) goto alloc_failed; } if (sh->sh_fstype) { nsh->sh_fstype = strdup(sh->sh_fstype); if (nsh->sh_fstype == NULL) goto alloc_failed; } if (sh->sh_opts) { nsh->sh_opts = strdup(sh->sh_opts); if (nsh->sh_opts == NULL) goto alloc_failed; } if (sh->sh_descr) { nsh->sh_descr = strdup(sh->sh_descr); if (nsh->sh_descr == NULL) goto alloc_failed; } return (nsh); alloc_failed: sharefree(nsh); return (NULL); } void sharefree(share_t *sh) { if (sh->sh_path != NULL) free(sh->sh_path); if (sh->sh_res != NULL) free(sh->sh_res); if (sh->sh_fstype != NULL) free(sh->sh_fstype); if (sh->sh_opts != NULL) free(sh->sh_opts); if (sh->sh_descr != NULL) free(sh->sh_descr); free(sh); } /* * Return the value after "=" for option "opt" * in option string "optlist". Caller must * free returned value. */ char * getshareopt(char *optlist, char *opt) { char *p, *pe; char *b; char *bb; char *lasts; char *val = NULL; b = bb = strdup(optlist); if (b == NULL) return (NULL); while ((p = strtok_r(b, ",", &lasts)) != NULL) { b = NULL; if ((pe = strchr(p, '=')) != NULL) { *pe = '\0'; if (strcmp(opt, p) == 0) { val = strdup(pe + 1); goto done; } } if (strcmp(opt, p) == 0) { val = strdup(""); goto done; } } done: free(bb); return (val); } /* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright 2014 Nexenta Systems, Inc. All rights reserved. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Portions of this source code were derived from Berkeley 4.3 BSD * under license from the Regents of the University of California. */ /* * Note: must be included before this file. */ #ifndef _SHARETAB_H #define _SHARETAB_H #ifdef __cplusplus extern "C" { #endif #define SHOPT_RO "ro" #define SHOPT_RW "rw" #define SHOPT_NONE "none" #define SHOPT_ROOT_MAPPING "root_mapping" #define SHOPT_SEC "sec" #define SHOPT_SECURE "secure" #define SHOPT_ROOT "root" #define SHOPT_ANON "anon" #define SHOPT_WINDOW "window" #define SHOPT_NOSUB "nosub" #define SHOPT_NOSUID "nosuid" #define SHOPT_ACLOK "aclok" #define SHOPT_PUBLIC "public" #define SHOPT_INDEX "index" #define SHOPT_LOG "log" #define SHOPT_NOACLFAB "noaclfab" #define SHOPT_UIDMAP "uidmap" #define SHOPT_GIDMAP "gidmap" #define SHOPT_NOHIDE "nohide" /* XXX The following are added for testing volatile fh's purposes only */ #ifdef VOLATILE_FH_TEST #define SHOPT_VOLFH "volfh" #endif /* VOLATILE_FH_TEST */ int getshare(FILE *, share_t **); char *getshareopt(char *, char *); share_t *sharedup(share_t *); void sharefree(share_t *); #ifdef __cplusplus } #endif #endif /* !_SHARETAB_H */ /* * 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 (c) 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2023 Oxide Computer Company */ #include #include #include #include #include #include #include #include "smfcfg.h" /* * NFS version strings translation table to numeric form. */ static struct str_val { const char *str; uint32_t val; } nfs_versions[] = { { "2", NFS_VERS_2 }, { "3", NFS_VERS_3 }, { "4", NFS_VERS_4 }, { "4.0", NFS_VERS_4 }, { "4.1", NFS_VERS_4_1 }, { "4.2", NFS_VERS_4_2 } }; /* * Translate NFS version string to numeric form. * Returns NFS_VERS_... value or zero for invalid version string. */ uint32_t nfs_convert_version_str(const char *version) { uint32_t v = 0; for (size_t i = 0; i < ARRAY_SIZE(nfs_versions); i++) { if (strcmp(version, nfs_versions[i].str) == 0) { v = nfs_versions[i].val; break; } } return (v); } fs_smfhandle_t * fs_smf_init(const char *fmri, const char *instance) { fs_smfhandle_t *handle = NULL; char *svcname, srv[MAXPATHLEN]; /* * svc name is of the form svc://network/fs/server:instance1 * FMRI portion is /network/fs/server */ (void) snprintf(srv, MAXPATHLEN, "%s", fmri + strlen("svc:/")); svcname = strrchr(srv, ':'); if (svcname != NULL) *svcname = '\0'; svcname = srv; handle = calloc(1, sizeof (fs_smfhandle_t)); if (handle != NULL) { handle->fs_handle = scf_handle_create(SCF_VERSION); if (handle->fs_handle == NULL) goto out; if (scf_handle_bind(handle->fs_handle) != 0) goto out; handle->fs_service = scf_service_create(handle->fs_handle); handle->fs_scope = scf_scope_create(handle->fs_handle); if (scf_handle_get_local_scope(handle->fs_handle, handle->fs_scope) != 0) goto out; if (scf_scope_get_service(handle->fs_scope, svcname, handle->fs_service) != SCF_SUCCESS) { goto out; } handle->fs_pg = scf_pg_create(handle->fs_handle); handle->fs_instance = scf_instance_create(handle->fs_handle); handle->fs_property = scf_property_create(handle->fs_handle); handle->fs_value = scf_value_create(handle->fs_handle); } else { fprintf(stderr, gettext("Cannot access SMF repository: %s\n"), fmri); } return (handle); out: fs_smf_fini(handle); if (scf_error() != SCF_ERROR_NOT_FOUND) { fprintf(stderr, gettext("SMF Initialization problem(%s): %s\n"), fmri, scf_strerror(scf_error())); } return (NULL); } void fs_smf_fini(fs_smfhandle_t *handle) { if (handle != NULL) { scf_scope_destroy(handle->fs_scope); scf_instance_destroy(handle->fs_instance); scf_service_destroy(handle->fs_service); scf_pg_destroy(handle->fs_pg); scf_property_destroy(handle->fs_property); scf_value_destroy(handle->fs_value); if (handle->fs_handle != NULL) { (void) scf_handle_unbind(handle->fs_handle); scf_handle_destroy(handle->fs_handle); } free(handle); } } int fs_smf_set_prop(smf_fstype_t fstype, char *prop_name, char *valbuf, char *instance, scf_type_t sctype, char *fmri) { fs_smfhandle_t *phandle = NULL; scf_handle_t *handle; scf_propertygroup_t *pg; scf_property_t *prop; scf_transaction_t *tran = NULL; scf_transaction_entry_t *entry = NULL; scf_instance_t *inst; scf_value_t *val; int valint; int ret = 0; char *p = NULL; char *svcname, srv[MAXPATHLEN]; const char *pgname; /* * The SVC names we are using currently are already * appended by default. Fix this for instances project. */ (void) snprintf(srv, MAXPATHLEN, "%s", fmri); p = strstr(fmri, ":default"); if (p == NULL) { (void) strcat(srv, ":"); if (instance == NULL) instance = "default"; if (strlen(srv) + strlen(instance) > MAXPATHLEN) goto out; (void) strncat(srv, instance, strlen(instance)); } svcname = srv; phandle = fs_smf_init(fmri, instance); if (phandle == NULL) { return (SMF_SYSTEM_ERR); } handle = phandle->fs_handle; pg = phandle->fs_pg; prop = phandle->fs_property; inst = phandle->fs_instance; val = phandle->fs_value; tran = scf_transaction_create(handle); entry = scf_entry_create(handle); if (handle == NULL || pg == NULL || prop == NULL || val == NULL|| tran == NULL || entry == NULL || inst == NULL) { ret = SMF_SYSTEM_ERR; goto out; } if (scf_handle_decode_fmri(handle, svcname, phandle->fs_scope, phandle->fs_service, inst, NULL, NULL, 0) != 0) { ret = scf_error(); goto out; } if (fstype == AUTOFS_SMF) pgname = AUTOFS_PROPS_PGNAME; else pgname = NFS_PROPS_PGNAME; if (scf_instance_get_pg(inst, pgname, pg) != -1) { uint8_t vint; if (scf_transaction_start(tran, pg) == -1) { ret = scf_error(); goto out; } switch (sctype) { case SCF_TYPE_INTEGER: errno = 0; valint = strtoul(valbuf, NULL, 0); if (errno != 0) { ret = SMF_SYSTEM_ERR; goto out; } if (scf_transaction_property_change(tran, entry, prop_name, SCF_TYPE_INTEGER) == 0) { scf_value_set_integer(val, valint); if (scf_entry_add_value(entry, val) < 0) { ret = scf_error(); goto out; } } break; case SCF_TYPE_ASTRING: if (scf_transaction_property_change(tran, entry, prop_name, SCF_TYPE_ASTRING) == 0) { if (scf_value_set_astring(val, valbuf) == 0) { if (scf_entry_add_value(entry, val) != 0) { ret = scf_error(); goto out; } } else ret = SMF_SYSTEM_ERR; } else ret = SMF_SYSTEM_ERR; break; case SCF_TYPE_BOOLEAN: if (strcmp(valbuf, "1") == 0) { vint = 1; } else if (strcmp(valbuf, "0") == 0) { vint = 0; } else { ret = SMF_SYSTEM_ERR; break; } if (scf_transaction_property_change(tran, entry, prop_name, SCF_TYPE_BOOLEAN) == 0) { scf_value_set_boolean(val, (uint8_t)vint); if (scf_entry_add_value(entry, val) != 0) { ret = scf_error(); goto out; } } else { ret = SMF_SYSTEM_ERR; } break; default: break; } if (ret != SMF_SYSTEM_ERR) (void) scf_transaction_commit(tran); } out: if (tran != NULL) scf_transaction_destroy(tran); if (entry != NULL) scf_entry_destroy(entry); fs_smf_fini(phandle); return (ret); } int fs_smf_get_prop(smf_fstype_t fstype, char *prop_name, char *cbuf, char *instance, scf_type_t sctype, char *fmri, int *bufsz) { fs_smfhandle_t *phandle = NULL; scf_handle_t *handle; scf_propertygroup_t *pg; scf_property_t *prop; scf_value_t *val; scf_instance_t *inst; int ret = 0, len = 0, length; int64_t valint = 0; char srv[MAXPATHLEN], *p, *svcname; const char *pgname; uint8_t bval; /* * The SVC names we are using currently are already * appended by default. Fix this for instances project. */ (void) snprintf(srv, MAXPATHLEN, "%s", fmri); p = strstr(fmri, ":default"); if (p == NULL) { (void) strcat(srv, ":"); if (instance == NULL) instance = "default"; if (strlen(srv) + strlen(instance) > MAXPATHLEN) goto out; (void) strncat(srv, instance, strlen(instance)); } svcname = srv; phandle = fs_smf_init(fmri, instance); if (phandle == NULL) return (SMF_SYSTEM_ERR); handle = phandle->fs_handle; pg = phandle->fs_pg; inst = phandle->fs_instance; prop = phandle->fs_property; val = phandle->fs_value; if (handle == NULL || pg == NULL || prop == NULL || val == NULL || inst == NULL) { return (SMF_SYSTEM_ERR); } if (scf_handle_decode_fmri(handle, svcname, phandle->fs_scope, phandle->fs_service, inst, NULL, NULL, 0) != 0) { ret = scf_error(); goto out; } if (fstype == AUTOFS_SMF) pgname = AUTOFS_PROPS_PGNAME; else pgname = NFS_PROPS_PGNAME; if (scf_instance_get_pg(inst, pgname, pg) != -1) { if (scf_pg_get_property(pg, prop_name, prop) != SCF_SUCCESS) { ret = scf_error(); goto out; } if (scf_property_get_value(prop, val) != SCF_SUCCESS) { ret = scf_error(); goto out; } switch (sctype) { case SCF_TYPE_ASTRING: len = scf_value_get_astring(val, cbuf, *bufsz); if (len < 0 || len > *bufsz) { ret = scf_error(); goto out; } ret = 0; *bufsz = len; break; case SCF_TYPE_INTEGER: if (scf_value_get_integer(val, &valint) != 0) { ret = scf_error(); goto out; } length = snprintf(cbuf, *bufsz, "%lld", valint); if (length < 0 || length > *bufsz) { ret = SA_BAD_VALUE; goto out; } ret = 0; break; case SCF_TYPE_BOOLEAN: if (scf_value_get_boolean(val, &bval) != 0) { ret = scf_error(); goto out; } if (bval == 1) { length = snprintf(cbuf, *bufsz, "%s", "true"); } else { length = snprintf(cbuf, *bufsz, "%s", "false"); } if (length < 0 || length > *bufsz) { ret = SA_BAD_VALUE; goto out; } break; default: break; } } else { ret = scf_error(); } if ((ret != 0) && scf_error() != SCF_ERROR_NONE) fprintf(stdout, gettext("%s\n"), scf_strerror(ret)); out: fs_smf_fini(phandle); return (ret); } int nfs_smf_get_prop(char *prop_name, char *propbuf, char *instance, scf_type_t sctype, char *svc_name, int *bufsz) { return (fs_smf_get_prop(NFS_SMF, prop_name, propbuf, instance, sctype, svc_name, bufsz)); } /* Get an integer (base 10) property */ int nfs_smf_get_iprop(char *prop_name, int *rvp, char *instance, scf_type_t sctype, char *svc_name) { char propbuf[32]; int bufsz, rc, val; bufsz = sizeof (propbuf); rc = fs_smf_get_prop(NFS_SMF, prop_name, propbuf, instance, sctype, svc_name, &bufsz); if (rc != SA_OK) return (rc); errno = 0; val = strtol(propbuf, NULL, 10); if (errno != 0) return (SA_BAD_VALUE); *rvp = val; return (SA_OK); } int nfs_smf_set_prop(char *prop_name, char *value, char *instance, scf_type_t type, char *svc_name) { return (fs_smf_set_prop(NFS_SMF, prop_name, value, instance, type, svc_name)); } int autofs_smf_set_prop(char *prop_name, char *value, char *instance, scf_type_t type, char *svc_name) { return (fs_smf_set_prop(AUTOFS_SMF, prop_name, value, instance, type, svc_name)); } int autofs_smf_get_prop(char *prop_name, char *propbuf, char *instance, scf_type_t sctype, char *svc_name, int *bufsz) { return (fs_smf_get_prop(AUTOFS_SMF, prop_name, propbuf, instance, sctype, svc_name, bufsz)); } boolean_t string_to_boolean(const char *str) { if (strcasecmp(str, "true") == 0 || atoi(str) == 1 || strcasecmp(str, "on") == 0 || strcasecmp(str, "yes") == 0) { return (B_TRUE); } else return (B_FALSE); } /* * upgrade server_versmin and server_versmax from int to string. * This is needed to allow to specify version as major.minor. */ static void nfs_upgrade_server_vers(const char *fmri) { fs_smfhandle_t *phandle; scf_handle_t *handle; scf_propertygroup_t *pg; scf_instance_t *inst; scf_value_t *vmin = NULL, *vmax = NULL; scf_transaction_t *tran = NULL; scf_transaction_entry_t *emin = NULL, *emax = NULL; char versmax[32]; char versmin[32]; int bufsz; /* * Read old integer values, stop in case of error - apparently * the upgrade is already done. */ bufsz = sizeof (versmax); if (nfs_smf_get_prop("server_versmax", versmax, DEFAULT_INSTANCE, SCF_TYPE_INTEGER, (char *)fmri, &bufsz) != SA_OK) { return; } bufsz = sizeof (versmin); if (nfs_smf_get_prop("server_versmin", versmin, DEFAULT_INSTANCE, SCF_TYPE_INTEGER, (char *)fmri, &bufsz) != SA_OK) { return; } /* Write back as SCF_TYPE_ASTRING */ phandle = fs_smf_init(fmri, NULL); if (phandle == NULL) return; handle = phandle->fs_handle; if (handle == NULL) goto done; pg = phandle->fs_pg; inst = phandle->fs_instance; tran = scf_transaction_create(handle); vmin = scf_value_create(handle); vmax = scf_value_create(handle); emin = scf_entry_create(handle); emax = scf_entry_create(handle); if (pg == NULL || inst == NULL || tran == NULL || emin == NULL || emax == NULL || vmin == NULL || vmax == NULL) { goto done; } if (scf_handle_decode_fmri(handle, (char *)fmri, phandle->fs_scope, phandle->fs_service, inst, NULL, NULL, 0) != 0) { goto done; } if (scf_instance_get_pg(inst, NFS_PROPS_PGNAME, pg) == -1) goto done; if (scf_pg_update(pg) == -1) goto done; if (scf_transaction_start(tran, pg) == -1) goto done; if (scf_transaction_property_change_type(tran, emax, "server_versmax", SCF_TYPE_ASTRING) != 0) { goto done; } if (scf_value_set_astring(vmax, versmax) == 0) { if (scf_entry_add_value(emax, vmax) != 0) goto done; } else { goto done; } if (scf_transaction_property_change_type(tran, emin, "server_versmin", SCF_TYPE_ASTRING) != 0) { goto done; } if (scf_value_set_astring(vmin, versmin) == 0) { if (scf_entry_add_value(emin, vmin) != 0) goto done; } else { goto done; } (void) scf_transaction_commit(tran); done: if (tran != NULL) scf_transaction_destroy(tran); if (emin != NULL) scf_entry_destroy(emin); if (emax != NULL) scf_entry_destroy(emax); if (vmin != NULL) scf_value_destroy(vmin); if (vmax != NULL) scf_value_destroy(vmax); fs_smf_fini(phandle); } void nfs_config_upgrade(const char *svc_name) { if (strcmp(svc_name, NFSD) == 0) { nfs_upgrade_server_vers(svc_name); } } /* * 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) 2010, Oracle and/or its affiliates. All rights reserved. */ #ifndef _SMFCFG_H #define _SMFCFG_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif typedef enum { NFS_SMF = 1, AUTOFS_SMF } smf_fstype_t; typedef struct fs_smfhandle { scf_handle_t *fs_handle; scf_service_t *fs_service; scf_scope_t *fs_scope; scf_instance_t *fs_instance; scf_propertygroup_t *fs_pg; scf_property_t *fs_property; scf_value_t *fs_value; } fs_smfhandle_t; #define DEFAULT_INSTANCE "default" /* * NFS Property Group names. */ #define SMF_PG_NFSPROPS ((const char *)"com.oracle.nfs,props") #define NFS_PROPS_PGNAME ((const char *)"nfs-props") #define SVC_NFS_CLIENT "svc:/network/nfs/client" /* * AUTOFS Property Group Names. */ #define SMF_PG_AUTOFS ((const char *)"com.oracle.autofs,props") #define AUTOFS_PROPS_PGNAME ((const char *)"autofs-props") #define AUTOFS_FMRI "svc:/system/filesystem/autofs" #define AUTOFS_DEFAULT_FMRI "svc:/system/filesystem/autofs:default" #define MAXDIGITS 32 /* * ERRORS */ #define SMF_OK 0 #define SMF_SYSTEM_ERR -1 #define STATE_INITIALIZING 1 #define SMF_NO_PERMISSION 2 #define SMF_NO_PGTYPE 3 extern uint32_t nfs_convert_version_str(const char *); extern void nfs_config_upgrade(const char *); extern int nfs_smf_get_iprop(char *, int *, char *, scf_type_t, char *); extern int nfs_smf_get_prop(char *, char *, char *, scf_type_t, char *, int *); extern int fs_smf_get_prop(smf_fstype_t, char *, char *, char *, scf_type_t, char *, int *); extern int nfs_smf_set_prop(char *, char *, char *, scf_type_t, char *); extern int fs_smf_set_prop(smf_fstype_t, char *, char *, char *, scf_type_t, char *); extern int autofs_smf_set_prop(char *, char *, char *, scf_type_t, char *); extern int autofs_smf_get_prop(char *, char *, char *, scf_type_t, char *, int *); extern void fs_smf_fini(fs_smfhandle_t *); extern boolean_t string_to_boolean(const char *); #ifdef __cplusplus } #endif #endif /* _SMFCFG_H */ /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include "thrpool.h" extern int _nfssys(int, void *); /* * Thread to call into the kernel and do work on behalf of NFS. */ static void * svcstart(void *arg) { int id = (int)arg; /* * Create a kernel worker thread to service * new incoming requests on a pool. */ _nfssys(SVCPOOL_RUN, &id); /* * Returned from the kernel, this thread's work is done, * and it should exit. For new incoming requests, * svcblock() will spawn another worker thread by * calling svcstart() again. */ thr_exit(NULL); return (NULL); } static void * svc_rdma_creator(void *arg) { struct rdma_svc_args *rsap = (struct rdma_svc_args *)arg; if (_nfssys(RDMA_SVC_INIT, rsap) < 0) { if (errno != ENODEV) { (void) syslog(LOG_INFO, "RDMA transport startup " "failed with %m"); } } free(rsap); thr_exit(NULL); return (NULL); } /* * User-space "creator" thread. This thread blocks in the kernel * until new worker threads need to be created for the service * pool. On return to userspace, if there is no error, create a * new thread for the service pool. */ static void * svcblock(void *arg) { int id = (int)arg; /* CONSTCOND */ while (1) { thread_t tid; /* * Call into the kernel, and hang out there * until a thread needs to be created. */ if (_nfssys(SVCPOOL_WAIT, &id) < 0) { if (errno == ECANCELED || errno == EINTR || errno == EBUSY) /* * If we get back ECANCELED or EINTR, * the service pool is exiting, and we * may as well clean up this thread. If * EBUSY is returned, there's already a * thread looping on this pool, so we * should give up. */ break; else continue; } /* * User portion of the thread does no real work since * the svcpool threads actually spend their entire * lives in the kernel. So, user portion of the thread * should have the smallest stack possible. */ (void) thr_create(NULL, THR_MIN_STACK, svcstart, (void *)id, THR_BOUND | THR_DETACHED, &tid); } thr_exit(NULL); return (NULL); } void svcsetprio(void) { pcinfo_t pcinfo; pri_t maxupri; /* * By default, all threads should be part of the FX scheduler * class. As nfsd/lockd server threads used to be part of the * kernel, they're used to being scheduled in the SYS class. * Userland threads shouldn't be in SYS, but they can be given a * higher priority by default. This change still renders nfsd/lockd * managable by an admin by utilizing commands to change scheduling * manually, or by using resource management tools such as pools * to associate them with a different scheduling class and segregate * the workload. * * We set the threads' priority to the upper bound for priorities * in FX. This should be 60, but since the desired action is to * make nfsd/lockd more important than TS threads, we bow to the * system's knowledge rather than setting it manually. Furthermore, * since the SYS class doesn't timeslice, use an "infinite" quantum. * If anything fails, just log the failure and let the daemon * default to TS. * * The change of scheduling class is expected to fail in a non-global * zone, so we avoid worrying the zone administrator unnecessarily. */ (void) strcpy(pcinfo.pc_clname, "FX"); if (priocntl(0, 0, PC_GETCID, (caddr_t)&pcinfo) != -1) { maxupri = ((fxinfo_t *)pcinfo.pc_clinfo)->fx_maxupri; if (priocntl(P_LWPID, P_MYID, PC_SETXPARMS, "FX", FX_KY_UPRILIM, maxupri, FX_KY_UPRI, maxupri, FX_KY_TQNSECS, FX_TQINF, NULL) != 0 && getzoneid() == GLOBAL_ZONEID) (void) syslog(LOG_ERR, "Unable to use FX scheduler: " "%m. Using system default scheduler."); } else (void) syslog(LOG_ERR, "Unable to determine parameters " "for FX scheduler. Using system default scheduler."); } int svcrdma(int id, int versmin, int versmax, int delegation) { thread_t tid; struct rdma_svc_args *rsa; rsa = (struct rdma_svc_args *)malloc(sizeof (struct rdma_svc_args)); rsa->poolid = (uint32_t)id; rsa->netid = NULL; rsa->nfs_versmin = versmin; rsa->nfs_versmax = versmax; rsa->delegation = delegation; /* * Create a thread to handle RDMA start and stop. */ if (thr_create(NULL, THR_MIN_STACK * 2, svc_rdma_creator, (void *)rsa, THR_BOUND | THR_DETACHED, &tid)) return (1); return (0); } int svcwait(int id) { thread_t tid; /* * Create a bound thread to wait for kernel LWPs that * need to be created. This thread also has little need * of stackspace, so should be created with that in mind. */ if (thr_create(NULL, THR_MIN_STACK * 2, svcblock, (void *)id, THR_BOUND | THR_DETACHED, &tid)) return (1); return (0); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _THRPOOL_H #define _THRPOOL_H #ifdef __cplusplus extern "C" { #endif int svcwait(int id); void svcsetprio(void); int svcrdma(int, int, int, int); #ifdef __cplusplus } #endif #endif /* _THRPOOL_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 1996-1999, 2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ const WNL_PORT = 2049; const WNL_MAXDATA = 8192; const WNL_MAXNAMLEN = 255; const WNL_FHSIZE = 32; const WNL_FIFO_DEV = -1; /* size kludge for named pipes */ /* * Indicator for native path semantics. */ const WNL_NATIVEPATH = 0x80; /* * Indicator for security negotiation. */ const WNL_SEC_NEGO = 0x81; /* * File types */ const WNLMODE_FMT = 0170000; /* type of file */ const WNLMODE_DIR = 0040000; /* directory */ const WNLMODE_CHR = 0020000; /* character special */ const WNLMODE_BLK = 0060000; /* block special */ const WNLMODE_REG = 0100000; /* regular */ const WNLMODE_LNK = 0120000; /* symbolic link */ const WNLMODE_SOCK = 0140000; /* socket */ const WNLMODE_FIFO = 0010000; /* fifo */ /* * Error status */ enum wnl_stat { WNL_OK= 0, /* no error */ WNLERR_PERM=1, /* Not owner */ WNLERR_NOENT=2, /* No such file or directory */ WNLERR_IO=5, /* I/O error */ WNLERR_NXIO=6, /* No such device or address */ WNLERR_ACCES=13, /* Permission denied */ WNLERR_EXIST=17, /* File exists */ WNLERR_XDEV=18, /* Cross-device link */ WNLERR_NODEV=19, /* No such device */ WNLERR_NOTDIR=20, /* Not a directory*/ WNLERR_ISDIR=21, /* Is a directory */ WNLERR_INVAL=22, /* Invalid argument */ WNLERR_FBIG=27, /* File too large */ WNLERR_NOSPC=28, /* No space left on device */ WNLERR_ROFS=30, /* Read-only file system */ WNLERR_OPNOTSUPP=45, /* Operation not supported */ WNLERR_NAMETOOLONG=63, /* File name too long */ WNLERR_NOTEMPTY=66, /* Directory not empty */ WNLERR_DQUOT=69, /* Disc quota exceeded */ WNLERR_STALE=70, /* Stale WNL file handle */ WNLERR_REMOTE=71, /* Object is remote */ WNLERR_WFLUSH=72 /* write cache flushed */ }; /* * File types */ enum wnl_ftype { WNL_NON = 0, /* non-file */ WNL_REG = 1, /* regular file */ WNL_DIR = 2, /* directory */ WNL_BLK = 3, /* block special */ WNL_CHR = 4, /* character special */ WNL_LNK = 5, /* symbolic link */ WNL_SOCK = 6, /* unix domain sockets */ WNL_BAD = 7, /* unused */ WNL_FIFO = 8 /* named pipe */ }; /* * File access handle */ struct wnl_fh { opaque data[WNL_FHSIZE]; }; /* * Timeval */ struct wnl_time { unsigned seconds; unsigned useconds; }; /* * File attributes */ struct wnl_fattr { wnl_ftype type; /* file type */ unsigned mode; /* protection mode bits */ unsigned nlink; /* # hard links */ unsigned uid; /* owner user id */ unsigned gid; /* owner group id */ unsigned size; /* file size in bytes */ unsigned blocksize; /* prefered block size */ unsigned rdev; /* special device # */ unsigned blocks; /* Kb of disk used by file */ unsigned fsid; /* device # */ unsigned fileid; /* inode # */ wnl_time atime; /* time of last access */ wnl_time mtime; /* time of last modification */ wnl_time ctime; /* time of last change */ }; typedef string wnl_filename; /* * Arguments for directory operations */ struct wnl_diropargs { wnl_fh dir; /* directory file handle */ wnl_filename name; /* name (up to WNL_MAXNAMLEN bytes) */ }; struct wnl_diropokres { wnl_fh file; wnl_fattr attributes; }; /* * Results from directory operation */ union wnl_diropres switch (wnl_stat status) { case WNL_OK: wnl_diropokres wnl_diropres; default: void; }; /* * Version 3 declarations and definitions. */ /* * Sizes */ const WNL3_FHSIZE = 64; /* * Basic data types */ typedef unsigned hyper wnl_uint64; typedef hyper wnl_int64; typedef unsigned int wnl_uint32; typedef string wnl_filename3<>; typedef wnl_uint64 wnl_fileid3; typedef wnl_uint32 wnl_uid3; typedef wnl_uint32 wnl_gid3; typedef wnl_uint64 wnl_size3; typedef wnl_uint32 wnl_mode3; /* * Error status */ enum wnl_stat3 { WNL3_OK = 0, WNL3ERR_PERM = 1, WNL3ERR_NOENT = 2, WNL3ERR_IO = 5, WNL3ERR_NXIO = 6, WNL3ERR_ACCES = 13, WNL3ERR_EXIST = 17, WNL3ERR_XDEV = 18, WNL3ERR_NODEV = 19, WNL3ERR_NOTDIR = 20, WNL3ERR_ISDIR = 21, WNL3ERR_INVAL = 22, WNL3ERR_FBIG = 27, WNL3ERR_NOSPC = 28, WNL3ERR_ROFS = 30, WNL3ERR_MLINK = 31, WNL3ERR_NAMETOOLONG = 63, WNL3ERR_NOTEMPTY = 66, WNL3ERR_DQUOT = 69, WNL3ERR_STALE = 70, WNL3ERR_REMOTE = 71, WNL3ERR_BADHANDLE = 10001, WNL3ERR_NOT_SYNC = 10002, WNL3ERR_BAD_COOKIE = 10003, WNL3ERR_NOTSUPP = 10004, WNL3ERR_TOOSMALL = 10005, WNL3ERR_SERVERFAULT = 10006, WNL3ERR_BADTYPE = 10007, WNL3ERR_JUKEBOX = 10008 }; /* * File types */ enum wnl_ftype3 { WNL_3REG = 1, WNL_3DIR = 2, WNL_3BLK = 3, WNL_3CHR = 4, WNL_3LNK = 5, WNL_3SOCK = 6, WNL_3FIFO = 7 }; struct wnl_specdata3 { wnl_uint32 specdata1; wnl_uint32 specdata2; }; /* * File access handle */ struct wnl_fh3 { opaque data; }; /* * Timeval */ struct wnl_time3 { wnl_uint32 seconds; wnl_uint32 nseconds; }; /* * File attributes */ struct wnl_fattr3 { wnl_ftype3 type; wnl_mode3 mode; wnl_uint32 nlink; wnl_uid3 uid; wnl_gid3 gid; wnl_size3 size; wnl_size3 used; wnl_specdata3 rdev; wnl_uint64 fsid; wnl_fileid3 fileid; wnl_time3 atime; wnl_time3 mtime; wnl_time3 ctime; }; /* * File attributes */ union wnl_post_op_attr switch (bool attributes_follow) { case TRUE: wnl_fattr3 attributes; case FALSE: void; }; union wln_post_op_fh3 switch (bool handle_follows) { case TRUE: wnl_fh3 handle; case FALSE: void; }; struct wnl_diropargs3 { wnl_fh3 dir; wnl_filename3 name; }; /* * LOOKUP: Lookup wnl_filename */ struct WNL_LOOKUP3args { wnl_diropargs3 what; }; struct WNL_LOOKUP3resok { wnl_fh3 object; wnl_post_op_attr obj_attributes; wnl_post_op_attr dir_attributes; }; struct WNL_LOOKUP3resfail { wnl_post_op_attr dir_attributes; }; union WNL_LOOKUP3res switch (wnl_stat3 status) { case WNL3_OK: WNL_LOOKUP3resok res_ok; default: WNL_LOOKUP3resfail res_fail; }; const MAX_FLAVORS = 128; struct snego_t { int cnt; int array[MAX_FLAVORS]; }; enum snego_stat { /* default flavor invalid and a flavor has been negotiated */ SNEGO_SUCCESS = 0, /* default flavor valid, no need to negotiate flavors */ SNEGO_DEF_VALID = 1, /* array size too small */ SNEGO_ARRAY_TOO_SMALL = 2, SNEGO_FAILURE = 3 }; /* * Remote file service routines */ program WNL_PROGRAM { version WNL_V2 { void WNLPROC_NULL(void) = 0; wnl_diropres WNLPROC_LOOKUP(wnl_diropargs) = 4; } = 2; version WNL_V3 { void WNLPROC3_NULL(void) = 0; WNL_LOOKUP3res WNLPROC3_LOOKUP(WNL_LOOKUP3args) = 3; } = 3; version WNL_V4 { void WNLPROC4_NULL(void) = 0; } = 4; } = 100003; # # 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) 1990, 2010, Oracle and/or its affiliates. All rights reserved. # Copyright (c) 2012 by Delphix. All rights reserved. # Copyright (c) 2018, Joyent, Inc. # FSTYPE = nfs TYPEPROG = lockd ATTMK = $(TYPEPROG) include ../../Makefile.fstype LOCAL = lockd.o OBJS = $(LOCAL) daemon.o nfs_tbind.o smfcfg.o thrpool.o POFILE = lockd.po SRCS = $(LOCAL:%.o=%.c) ../lib/daemon.c ../lib/nfs_tbind.c \ ../lib/smfcfg.c ../lib/thrpool.c LDLIBS += -lnsl -lscf CPPFLAGS += -I../lib CERRWARN += -Wno-parentheses CERRWARN += -Wno-switch CERRWARN += -Wno-unused-variable CERRWARN += $(CNOWARN_UNINIT) # Hammerhead: Suppress overflow/pointer cast warnings in legacy NFS code CERRWARN += -Wno-overflow CERRWARN += -Wno-pointer-to-int-cast CERRWARN += -Wno-int-to-pointer-cast # not linted SMATCH=off $(TYPEPROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) lockd.o: lockd.c $(COMPILE.c) lockd.c nfs_tbind.o: ../lib/nfs_tbind.c $(COMPILE.c) ../lib/nfs_tbind.c thrpool.o: ../lib/thrpool.c $(COMPILE.c) ../lib/thrpool.c daemon.o: ../lib/daemon.c $(COMPILE.c) ../lib/daemon.c smfcfg.o: ../lib/smfcfg.c $(COMPILE.c) ../lib/smfcfg.c # # message catalog # catalog: $(POFILE) $(POFILE): $(SRCS) $(RM) $@ $(COMPILE.cpp) $(SRCS) > $(POFILE).i $(XGETTEXT) $(XGETFLAGS) $(POFILE).i sed "/^domain/d" messages.po > $@ $(RM) $(POFILE).i messages.po clean: $(RM) $(OBJS) $(DOBJ) /* * 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) 1989, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012 by Delphix. All rights reserved. * Copyright 2014 Nexenta Systems, Inc. All rights reserved. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California * All Rights Reserved * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ /* LINTLIBRARY */ /* PROTOLIB1 */ /* * NLM server * * Most of this copied from ../nfsd/nfsd.c * and then s:NFS:NLM: applied, etc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nfs_tbind.h" #include "thrpool.h" #include "smfcfg.h" /* Option defaults. See nfssys.h */ struct lm_svc_args lmargs = { .version = LM_SVC_CUR_VERS, /* fd, n_fmly, n_proto, n_rdev (below) */ .debug = 0, .timout = 5 * 60, .grace = 90, .retransmittimeout = 5 }; int max_servers = 256; #define RET_OK 0 /* return code for no error */ #define RET_ERR 33 /* return code for error(s) */ static int nlmsvc(int fd, struct netbuf addrmask, struct netconfig *nconf); static int nlmsvcpool(int max_servers); static void usage(void); extern int _nfssys(int, void *); static void sigterm_handler(int); static void shutdown_lockd(void); extern int daemonize_init(void); extern void daemonize_fini(int fd); static char *MyName; /* * We want to bind to these TLI providers, and in this order, * because the kernel NLM needs the loopback first for its * initialization. (It uses it to talk to statd.) */ static NETSELDECL(defaultproviders)[] = { "/dev/ticotsord", "/dev/tcp", "/dev/udp", "/dev/tcp6", "/dev/udp6", NULL }; /* * The following are all globals used by routines in nfs_tbind.c. */ size_t end_listen_fds; /* used by conn_close_oldest() */ size_t num_fds = 0; /* used by multiple routines */ int listen_backlog = 32; /* used by bind_to_{provider,proto}() */ int (*Mysvc)(int, struct netbuf, struct netconfig *) = nlmsvc; /* used by cots_listen_event() */ int max_conns_allowed = -1; /* used by cots_listen_event() */ int main(int ac, char *av[]) { char *propname = NULL; char *dir = "/"; char *provider = (char *)NULL; struct protob *protobp; NETSELPDECL(providerp); sigset_t sgset; int i, c, pid, ret, val; int pipe_fd = -1; struct sigaction act; MyName = *av; /* * Initializations that require more privileges than we need to run. */ (void) _create_daemon_lock(LOCKD, DAEMON_UID, DAEMON_GID); svcsetprio(); if (__init_daemon_priv(PU_RESETGROUPS|PU_CLEARLIMITSET, DAEMON_UID, DAEMON_GID, PRIV_SYS_NFS, NULL) == -1) { (void) fprintf(stderr, "%s should be run with" " sufficient privileges\n", av[0]); exit(1); } (void) enable_extended_FILE_stdio(-1, -1); /* * Read in the values from SMF first before we check * command line options so the options override SMF values. */ /* How long to wait for clients to re-establish locks. */ propname = "grace_period"; /* also -g */ ret = nfs_smf_get_iprop(propname, &val, DEFAULT_INSTANCE, SCF_TYPE_INTEGER, LOCKD); if (ret == SA_OK) { if (val <= 0) fprintf(stderr, gettext( "Invalid %s from SMF"), propname); else lmargs.grace = val; } else { syslog(LOG_ERR, "Reading of %s from SMF failed, using default " "value", propname); } propname = "lockd_listen_backlog"; /* also -l */ ret = nfs_smf_get_iprop(propname, &val, DEFAULT_INSTANCE, SCF_TYPE_INTEGER, LOCKD); if (ret == SA_OK) { if (val <= 0) fprintf(stderr, gettext( "Invalid %s from SMF"), propname); else listen_backlog = val; } else { syslog(LOG_ERR, "Reading of %s from SMF failed, using default " "value", propname); } propname = "lockd_servers"; /* also argv[1] */ ret = nfs_smf_get_iprop(propname, &val, DEFAULT_INSTANCE, SCF_TYPE_INTEGER, LOCKD); if (ret == SA_OK) { if (val <= 0) fprintf(stderr, gettext( "Invalid %s from SMF"), propname); else max_servers = val; } else { syslog(LOG_ERR, "Reading of %s from SMF failed, using default " "value", propname); } propname = "lockd_retransmit_timeout"; /* also -t */ ret = nfs_smf_get_iprop(propname, &val, DEFAULT_INSTANCE, SCF_TYPE_INTEGER, LOCKD); if (ret == SA_OK) { if (val <= 0) fprintf(stderr, gettext( "Invalid %s from SMF"), propname); else lmargs.retransmittimeout = val; } else { syslog(LOG_ERR, "Reading of %s from SMF failed, using default " "value", propname); } while ((c = getopt(ac, av, "c:d:g:l:t:")) != EOF) switch (c) { case 'c': /* max_connections */ if ((val = atoi(optarg)) <= 0) goto badval; max_conns_allowed = val; break; case 'd': /* debug */ lmargs.debug = atoi(optarg); break; case 'g': /* grace_period */ if ((val = atoi(optarg)) <= 0) goto badval; lmargs.grace = val; break; case 'l': /* listen_backlog */ if ((val = atoi(optarg)) <= 0) goto badval; listen_backlog = val; break; case 't': /* retrans_timeout */ if ((val = atoi(optarg)) <= 0) goto badval; lmargs.retransmittimeout = val; break; badval: fprintf(stderr, gettext( "Invalid -%c option value"), c); /* FALLTHROUGH */ default: usage(); /* NOTREACHED */ } /* * If there is exactly one more argument, it is the number of * servers. */ if (optind < ac) { val = atoi(av[optind]); if (val <= 0) { fprintf(stderr, gettext( "Invalid max_servers argument")); usage(); } max_servers = val; optind++; } /* * If there are two or more arguments, then this is a usage error. */ if (optind != ac) usage(); if (lmargs.debug) { printf("%s: debug= %d, conn_idle_timout= %d," " grace_period= %d, listen_backlog= %d," " max_connections= %d, max_servers= %d," " retrans_timeout= %d\n", MyName, lmargs.debug, lmargs.timout, lmargs.grace, listen_backlog, max_conns_allowed, max_servers, lmargs.retransmittimeout); } /* * Set current dir to server root */ if (chdir(dir) < 0) { (void) fprintf(stderr, "%s: ", MyName); perror(dir); exit(1); } /* Daemonize, if not debug. */ if (lmargs.debug == 0) pipe_fd = daemonize_init(); openlog(MyName, LOG_PID | LOG_NDELAY, LOG_DAEMON); /* * establish our lock on the lock file and write our pid to it. * exit if some other process holds the lock, or if there's any * error in writing/locking the file. */ pid = _enter_daemon_lock(LOCKD); switch (pid) { case 0: break; case -1: fprintf(stderr, "error locking for %s: %s", LOCKD, strerror(errno)); exit(2); default: /* daemon was already running */ exit(0); } /* * Block all signals till we spawn other * threads. */ (void) sigfillset(&sgset); (void) thr_sigsetmask(SIG_BLOCK, &sgset, NULL); /* Unregister any previous versions. */ for (i = NLM_VERS; i < NLM4_VERS; i++) { svc_unreg(NLM_PROG, i); } /* * Set up kernel RPC thread pool for the NLM server. */ if (nlmsvcpool(max_servers)) { fprintf(stderr, "Can't set up kernel NLM service: %s. Exiting", strerror(errno)); exit(1); } /* * Set up blocked thread to do LWP creation on behalf of the kernel. */ if (svcwait(NLM_SVCPOOL_ID)) { fprintf(stderr, "Can't set up NLM pool creator: %s. Exiting", strerror(errno)); exit(1); } /* * Install atexit and sigterm handlers */ act.sa_handler = sigterm_handler; act.sa_flags = 0; (void) sigaction(SIGTERM, &act, NULL); (void) atexit(shutdown_lockd); /* * Now open up for signal delivery */ (void) thr_sigsetmask(SIG_UNBLOCK, &sgset, NULL); /* * Build a protocol block list for registration. */ protobp = (struct protob *)malloc(sizeof (struct protob)); protobp->serv = "NLM"; protobp->versmin = NLM_VERS; protobp->versmax = NLM4_VERS; protobp->program = NLM_PROG; protobp->next = (struct protob *)NULL; for (providerp = defaultproviders; *providerp != NULL; providerp++) { provider = *providerp; do_one(provider, NULL, protobp, nlmsvc); } free(protobp); if (num_fds == 0) { fprintf(stderr, "Could not start NLM service for any protocol." " Exiting"); exit(1); } end_listen_fds = num_fds; /* * lockd is up and running as far as we are concerned. */ if (lmargs.debug == 0) daemonize_fini(pipe_fd); /* * Get rid of unneeded privileges. */ __fini_daemon_priv(PRIV_PROC_FORK, PRIV_PROC_EXEC, PRIV_PROC_SESSION, PRIV_FILE_LINK_ANY, PRIV_PROC_INFO, (char *)NULL); /* * Poll for non-data control events on the transport descriptors. */ poll_for_action(); /* * If we get here, something failed in poll_for_action(). */ return (1); } static int nlmsvcpool(int maxservers) { struct svcpool_args npa; npa.id = NLM_SVCPOOL_ID; npa.maxthreads = maxservers; npa.redline = 0; npa.qsize = 0; npa.timeout = 0; npa.stksize = 0; npa.max_same_xprt = 0; return (_nfssys(SVCPOOL_CREATE, &npa)); } static int ncfmly_to_lmfmly(const char *ncfmly) { if (0 == strcmp(ncfmly, NC_INET)) return (LM_INET); if (0 == strcmp(ncfmly, NC_INET6)) return (LM_INET6); if (0 == strcmp(ncfmly, NC_LOOPBACK)) return (LM_LOOPBACK); return (-1); } static int nctype_to_lmprot(uint_t semantics) { switch (semantics) { case NC_TPI_CLTS: return (LM_UDP); case NC_TPI_COTS_ORD: return (LM_TCP); } return (-1); } static dev_t ncdev_to_rdev(const char *ncdev) { struct stat st; if (stat(ncdev, &st) < 0) return (NODEV); return (st.st_rdev); } static void sigterm_handler(int signal __unused) { /* to call atexit handler */ exit(0); } static void shutdown_lockd(void) { (void) _nfssys(KILL_LOCKMGR, NULL); } /* * Establish NLM service thread. */ static int nlmsvc(int fd, struct netbuf addrmask, struct netconfig *nconf) { struct lm_svc_args lma; lma = lmargs; /* init by struct copy */ /* * The kernel code needs to reconstruct a complete * knetconfig from n_fmly, n_proto. We use these * two fields to convey the family and semantics. */ lma.fd = fd; lma.n_fmly = ncfmly_to_lmfmly(nconf->nc_protofmly); lma.n_proto = nctype_to_lmprot(nconf->nc_semantics); lma.n_rdev = ncdev_to_rdev(nconf->nc_device); return (_nfssys(LM_SVC, &lma)); } static void usage(void) { (void) fprintf(stderr, gettext( "usage: %s [options] [max_servers]\n"), MyName); (void) fprintf(stderr, gettext( "options: (see SMF property descriptions)\n")); /* Note: don't translate these */ (void) fprintf(stderr, "\t-c max_connections\n"); (void) fprintf(stderr, "\t-d debug_level\n"); (void) fprintf(stderr, "\t-g grace_period\n"); (void) fprintf(stderr, "\t-l listen_backlog\n"); (void) fprintf(stderr, "\t-t retransmit_timeout\n"); exit(1); } # # 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) 1990, 2010, Oracle and/or its affiliates. All rights reserved. # Copyright 2018, Joyent, Inc. All rights reserved. # # cmd/fs.d/nfs/mount/Makefile FSTYPE= nfs LIBPROG= mount ROOTFS_PROG= $(LIBPROG) # duplicate ROOTLIBFSTYPE value needed for installation rule # we must define this before including Makefile.fstype ROOTLIBFSTYPE = $(ROOT)/usr/lib/fs/$(FSTYPE) $(ROOTLIBFSTYPE)/%: $(ROOTLIBFSTYPE) % $(RM) $@; $(SYMLINK) ../../../../etc/fs/$(FSTYPE)/$(LIBPROG) $@ include ../../Makefile.fstype COMMON= $(FSLIB) nfs_sec.o replica.o nfs_subr.o selfcheck.o smfcfg.o OBJS= $(LIBPROG).o $(COMMON) webnfs_client.o webnfs_xdr.o SRCS= $(LIBPROG).c $(FSLIBSRC) ../lib/nfs_sec.c ../lib/replica.c \ ../lib/nfs_subr.c webnfs_xdr.c webnfs_client.c ../lib/selfcheck.c \ ../lib/smfcfg.c UNCHECKED_HDRS= webnfs.h CERRWARN += -Wno-parentheses CERRWARN += -Wno-switch CERRWARN += -Wno-unused-variable CERRWARN += $(CNOWARN_UNINIT) CERRWARN += -Wno-address CERRWARN += -Wno-unused-function # unknown type for func SMATCH=off # # Message catalog # POFILE= mount.po LDLIBS += -lrpcsvc -lnsl -lsocket -lscf CPPFLAGS += -I. -I../.. -I../lib CFLAGS += $(CCVERBOSE) nfs_sec.o : CPPFLAGS += -DWNFS_SEC_NEGO ROOTETCPROG = $(LIBPROG:%=$(ROOTETCFSTYPE)/%) CLOBBERFILES += $(LIBPROG) .KEEP_STATE: all: $(ROOTFS_PROG) $(LIBPROG): webnfs.h $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) nfs_sec.o: ../lib/nfs_sec.c $(COMPILE.c) ../lib/nfs_sec.c replica.o: ../lib/replica.c $(COMPILE.c) ../lib/replica.c nfs_subr.o: ../lib/nfs_subr.c $(COMPILE.c) ../lib/nfs_subr.c selfcheck.o: ../lib/selfcheck.c $(COMPILE.c) ../lib/selfcheck.c nfs_tbind.o: ../lib/nfs_tbind.c $(COMPILE.c) ../lib/nfs_tbind.c smfcfg.o: ../lib/smfcfg.c $(COMPILE.c) ../lib/smfcfg.c # Hammerhead: pre-generated (rpcgen + GNU cpp truncation bug) # webnfs.h, webnfs_xdr.c, webnfs_client.c are now committed source files. webnfs.x: ../lib/webnfs.x $(RM) webnfs.x cp ../lib/webnfs.x . # # message catalog # catalog: $(POFILE) $(POFILE): $(SRCS) webnfs.h $(RM) $@ $(COMPILE.cpp) $(SRCS) > $(POFILE).i $(XGETTEXT) $(XGETFLAGS) $(POFILE).i sed "/^domain/d" messages.po > $@ $(RM) $(POFILE).i messages.po install: $(ROOTETCPROG) clean: $(RM) $(OBJS) webnfs.x @# Hammerhead: do not delete pre-generated webnfs.h webnfs_xdr.c webnfs_client.c /* * 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) 1989, 2010, Oracle and/or its affiliates. All rights reserved. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California * All Rights Reserved * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ /* * nfs mount */ #define NFSCLIENT #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "replica.h" #include #include #include #include #include #include "nfs_subr.h" #include "webnfs.h" #include #include #include #include #include "smfcfg.h" #include extern int _nfssys(enum nfssys_op, void *); #ifndef NFS_VERSMAX #define NFS_VERSMAX 4 #endif #ifndef NFS_VERSMIN #define NFS_VERSMIN 2 #endif #define RET_OK 0 #define RET_RETRY 32 #define RET_ERR 33 #define RET_MNTERR 1000 #define ERR_PROTO_NONE 0 #define ERR_PROTO_INVALID 901 #define ERR_PROTO_UNSUPP 902 #define ERR_NETPATH 903 #define ERR_NOHOST 904 #define ERR_RPCERROR 905 typedef struct err_ret { int error_type; int error_value; } err_ret_t; #define SET_ERR_RET(errst, etype, eval) \ if (errst) { \ (errst)->error_type = etype; \ (errst)->error_value = eval; \ } /* number of transports to try */ #define MNT_PREF_LISTLEN 2 #define FIRST_TRY 1 #define SECOND_TRY 2 #define BIGRETRY 10000 /* maximum length of RPC header for NFS messages */ #define NFS_RPC_HDR 432 #define NFS_ARGS_EXTB_secdata(args, secdata) \ { (args)->nfs_args_ext = NFS_ARGS_EXTB, \ (args)->nfs_ext_u.nfs_extB.secdata = secdata; } extern int __clnt_bindresvport(CLIENT *); extern char *nfs_get_qop_name(); extern AUTH * nfs_create_ah(); extern enum snego_stat nfs_sec_nego(); static void usage(void); static int retry(struct mnttab *, int); static int set_args(int *, struct nfs_args *, char *, struct mnttab *); static int get_fh_via_pub(struct nfs_args *, char *, char *, bool_t, bool_t, int *, struct netconfig **, ushort_t); static int get_fh(struct nfs_args *, char *, char *, int *, bool_t, struct netconfig **, ushort_t); static int make_secure(struct nfs_args *, char *, struct netconfig *, bool_t, rpcvers_t); static int mount_nfs(struct mnttab *, int, err_ret_t *); static int getaddr_nfs(struct nfs_args *, char *, struct netconfig **, bool_t, char *, ushort_t, err_ret_t *, bool_t); static void pr_err(const char *fmt, ...); static void usage(void); static struct netbuf *get_addr(char *, rpcprog_t, rpcvers_t, struct netconfig **, char *, ushort_t, struct t_info *, caddr_t *, bool_t, char *, err_ret_t *); static struct netbuf *get_the_addr(char *, rpcprog_t, rpcvers_t, struct netconfig *, ushort_t, struct t_info *, caddr_t *, bool_t, char *, err_ret_t *); extern int self_check(char *); static void read_default(void); static char typename[64]; static int bg = 0; static int backgrounded = 0; static int posix = 0; static int retries = BIGRETRY; static ushort_t nfs_port = 0; static char *nfs_proto = NULL; static int mflg = 0; static int Oflg = 0; /* Overlay mounts */ static int qflg = 0; /* quiet - don't print warnings on bad options */ static char *fstype = MNTTYPE_NFS; static seconfig_t nfs_sec; static int sec_opt = 0; /* any security option ? */ static bool_t snego_done; static void sigusr1(int); extern void set_nfsv4_ephemeral_mount_to(void); /* * list of support services needed */ static char *service_list[] = { STATD, LOCKD, NULL }; static char *service_list_v4[] = { STATD, LOCKD, NFS4CBD, NFSMAPID, NULL }; /* * These two variables control the NFS version number to be used. * * nfsvers defaults to 0 which means to use the highest number that * both the client and the server support. It can also be set to * a particular value, either 2, 3, or 4 to indicate the version * number of choice. If the server (or the client) do not support * the version indicated, then the mount attempt will be failed. * * nfsvers_to_use is the actual version number found to use. It * is determined in get_fh by pinging the various versions of the * NFS service on the server to see which responds positively. * * nfsretry_vers is the version number set when we retry the mount * command with the version decremented from nfsvers_to_use. * nfsretry_vers is set from nfsvers_to_use when we retry the mount * for errors other than RPC errors; it helps un know why we are * retrying. It is an indication that the retry is due to * non-RPC errors. */ static rpcvers_t nfsvers = 0; static rpcvers_t nfsvers_to_use = 0; static rpcvers_t nfsretry_vers = 0; /* * There are the defaults (range) for the client when determining * which NFS version to use when probing the server (see above). * These will only be used when the vers mount option is not used and * these may be reset if NFS SMF is configured to do so. */ static rpcvers_t vers_max_default = NFS_VERSMAX_DEFAULT; static rpcvers_t vers_min_default = NFS_VERSMIN_DEFAULT; /* * This variable controls whether to try the public file handle. */ static bool_t public_opt; int main(int argc, char *argv[]) { struct mnttab mnt; extern char *optarg; extern int optind; char optbuf[MAX_MNTOPT_STR]; int ro = 0; int r; int c; char *myname; err_ret_t retry_error; (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif (void) textdomain(TEXT_DOMAIN); myname = strrchr(argv[0], '/'); myname = myname ? myname + 1 : argv[0]; (void) snprintf(typename, sizeof (typename), "%s %s", MNTTYPE_NFS, myname); argv[0] = typename; mnt.mnt_mntopts = optbuf; (void) strcpy(optbuf, "rw"); /* * Set options */ while ((c = getopt(argc, argv, "ro:mOq")) != EOF) { switch (c) { case 'r': ro++; break; case 'o': if (strlen(optarg) >= MAX_MNTOPT_STR) { pr_err(gettext("option string too long")); return (RET_ERR); } (void) strcpy(mnt.mnt_mntopts, optarg); #ifdef LATER /* XXX */ if (strstr(optarg, MNTOPT_REMOUNT)) { /* * If remount is specified, only rw is allowed. */ if ((strcmp(optarg, MNTOPT_REMOUNT) != 0) && (strcmp(optarg, "remount,rw") != 0) && (strcmp(optarg, "rw,remount") != 0)) { pr_err(gettext("Invalid options\n")); exit(RET_ERR); } } #endif /* LATER */ /* XXX */ break; case 'm': mflg++; break; case 'O': Oflg++; break; case 'q': qflg++; break; default: usage(); exit(RET_ERR); } } if (argc - optind != 2) { usage(); exit(RET_ERR); } mnt.mnt_special = argv[optind]; mnt.mnt_mountp = argv[optind+1]; if (!priv_ineffect(PRIV_SYS_MOUNT) || !priv_ineffect(PRIV_NET_PRIVADDR)) { pr_err(gettext("insufficient privileges\n")); exit(RET_ERR); } /* * On a labeled system, allow read-down nfs mounts if privileged * (PRIV_NET_MAC_AWARE) to do so. Otherwise, ignore the error * and "mount equal label only" behavior will result. */ if (is_system_labeled()) (void) setpflags(NET_MAC_AWARE, 1); /* * Read the NFS SMF defaults to see if the min/max versions have * been set and therefore would override the encoded defaults. * Then check to make sure that if they were set that the * values are reasonable. */ read_default(); if (vers_min_default > vers_max_default || vers_min_default < NFS_VERSMIN || vers_max_default > NFS_VERSMAX) { pr_err("%s\n%s %s\n", gettext("Incorrect configuration of client\'s"), gettext("client_versmin or client_versmax"), gettext("is either out of range or overlaps.")); } SET_ERR_RET(&retry_error, ERR_PROTO_NONE, 0); r = mount_nfs(&mnt, ro, &retry_error); if (r == RET_RETRY && retries) { /* * Check the error code from the last mount attempt if it was * an RPC error, then retry as is. Otherwise we retry with the * nfsretry_vers set. It is set by decrementing nfsvers_to_use. * If we are retrying with nfsretry_vers then we don't print any * retry messages, since we are not retrying due to an RPC * error. */ if (retry_error.error_type) { if (retry_error.error_type != ERR_RPCERROR) { nfsretry_vers = nfsvers_to_use = nfsvers_to_use - 1; if (nfsretry_vers < NFS_VERSMIN) return (r); } } r = retry(&mnt, ro); } /* * exit(r); */ return (r); } static void pr_err(const char *fmt, ...) { va_list ap; va_start(ap, fmt); if (backgrounded != 0) { (void) vsyslog(LOG_ERR, fmt, ap); } else { (void) fprintf(stderr, "%s: ", typename); (void) vfprintf(stderr, fmt, ap); (void) fflush(stderr); } va_end(ap); } static void usage() { (void) fprintf(stderr, gettext("Usage: nfs mount [-r] [-o opts] [server:]path dir\n")); exit(RET_ERR); } static int mount_nfs(struct mnttab *mntp, int ro, err_ret_t *retry_error) { struct nfs_args *args = NULL, *argp = NULL, *prev_argp = NULL; struct netconfig *nconf = NULL; struct replica *list = NULL; int mntflags = 0; int i, r, n; int oldvers = 0, vers = 0; int last_error = RET_OK; int replicated = 0; char *p; bool_t url; bool_t use_pubfh; char *special = NULL; char *oldpath = NULL; char *newpath = NULL; char *service; pid_t pi; struct flock f; char *saveopts = NULL; char **sl = NULL; mntp->mnt_fstype = MNTTYPE_NFS; if (ro) { mntflags |= MS_RDONLY; /* convert "rw"->"ro" */ if (p = strstr(mntp->mnt_mntopts, "rw")) { if (*(p+2) == ',' || *(p+2) == '\0') *(p+1) = 'o'; } } if (Oflg) mntflags |= MS_OVERLAY; list = parse_replica(mntp->mnt_special, &n); if (list == NULL) { if (n < 0) pr_err(gettext("nfs file system; use [host:]path\n")); else pr_err(gettext("no memory\n")); return (RET_ERR); } replicated = (n > 1); /* * There are some free() calls at the bottom of this loop, so be * careful about adding continue statements. */ for (i = 0; i < n; i++) { char *path; char *host; ushort_t port; argp = (struct nfs_args *)malloc(sizeof (*argp)); if (argp == NULL) { pr_err(gettext("no memory\n")); last_error = RET_ERR; goto out; } memset(argp, 0, sizeof (*argp)); memset(&nfs_sec, 0, sizeof (nfs_sec)); sec_opt = 0; use_pubfh = FALSE; url = FALSE; port = 0; snego_done = FALSE; /* * Looking for resources of the form * nfs://server_host[:port_number]/path_name */ if (strcmp(list[i].host, "nfs") == 0 && strncmp(list[i].path, "//", 2) == 0) { char *sport, *cb; url = TRUE; oldpath = strdup(list[i].path); if (oldpath == NULL) { pr_err(gettext("memory allocation failure\n")); last_error = RET_ERR; goto out; } host = list[i].path+2; path = strchr(host, '/'); if (path == NULL) { pr_err(gettext( "illegal nfs url syntax\n")); last_error = RET_ERR; goto out; } *path = '\0'; if (*host == '[') { cb = strchr(host, ']'); if (cb == NULL) { pr_err(gettext( "illegal nfs url syntax\n")); last_error = RET_ERR; goto out; } else { *cb = '\0'; host++; cb++; if (*cb == ':') port = htons((ushort_t) atoi(cb+1)); } } else { sport = strchr(host, ':'); if (sport != NULL && sport < path) { *sport = '\0'; port = htons((ushort_t)atoi(sport+1)); } } path++; if (*path == '\0') path = "."; } else { host = list[i].host; path = list[i].path; } if (r = set_args(&mntflags, argp, host, mntp)) { last_error = r; goto out; } if (public_opt == TRUE) use_pubfh = TRUE; if (port == 0) { port = nfs_port; } else if (nfs_port != 0 && nfs_port != port) { pr_err(gettext( "port (%u) in nfs URL not the same" " as port (%u) in port option\n"), (unsigned int)ntohs(port), (unsigned int)ntohs(nfs_port)); last_error = RET_ERR; goto out; } if (replicated && !(mntflags & MS_RDONLY)) { pr_err(gettext( "replicated mounts must be read-only\n")); last_error = RET_ERR; goto out; } if (replicated && (argp->flags & NFSMNT_SOFT)) { pr_err(gettext( "replicated mounts must not be soft\n")); last_error = RET_ERR; goto out; } oldvers = vers; nconf = NULL; r = RET_ERR; /* * If -o public was specified, and/or a URL was specified, * then try the public file handle method. */ if ((use_pubfh == TRUE) || (url == TRUE)) { r = get_fh_via_pub(argp, host, path, url, use_pubfh, &vers, &nconf, port); if (r != RET_OK) { /* * If -o public was specified, then return the * error now. */ if (use_pubfh == TRUE) { last_error = r; goto out; } } else use_pubfh = TRUE; argp->flags |= NFSMNT_PUBLIC; } if ((r != RET_OK) || (vers == NFS_V4)) { bool_t loud_on_mnt_err; /* * This can happen if -o public is not specified, * special is a URL, and server doesn't support * public file handle. */ if (url) { URLparse(path); } /* * If the path portion of the URL didn't have * a leading / then there is good possibility * that a mount without a leading slash will * fail. */ if (url == TRUE && *path != '/') loud_on_mnt_err = FALSE; else loud_on_mnt_err = TRUE; r = get_fh(argp, host, path, &vers, loud_on_mnt_err, &nconf, port); if (r != RET_OK) { /* * If there was no leading / and the path was * derived from a URL, then try again * with a leading /. */ if ((r == RET_MNTERR) && (loud_on_mnt_err == FALSE)) { newpath = malloc(strlen(path)+2); if (newpath == NULL) { pr_err(gettext("memory " "allocation failure\n")); last_error = RET_ERR; goto out; } strcpy(newpath, "/"); strcat(newpath, path); r = get_fh(argp, host, newpath, &vers, TRUE, &nconf, port); if (r == RET_OK) path = newpath; } /* * map exit code back to RET_ERR. */ if (r == RET_MNTERR) r = RET_ERR; if (r != RET_OK) { if (replicated) { if (argp->fh) free(argp->fh); if (argp->pathconf) free(argp->pathconf); free(argp); goto cont; } last_error = r; goto out; } } } if (oldvers && vers != oldvers) { pr_err( gettext("replicas must have the same version\n")); last_error = RET_ERR; goto out; } /* * decide whether to use remote host's * lockd or do local locking */ if (!(argp->flags & NFSMNT_LLOCK) && vers == NFS_VERSION && remote_lock(host, argp->fh)) { (void) fprintf(stderr, gettext( "WARNING: No network locking on %s:%s:"), host, path); (void) fprintf(stderr, gettext( " contact admin to install server change\n")); argp->flags |= NFSMNT_LLOCK; } if (self_check(host)) argp->flags |= NFSMNT_LOOPBACK; if (use_pubfh == FALSE) { /* * Call to get_fh() above may have obtained the * netconfig info and NULL proc'd the server. * This would be the case with v4 */ if (!(argp->flags & NFSMNT_KNCONF)) { nconf = NULL; if (r = getaddr_nfs(argp, host, &nconf, FALSE, path, port, retry_error, TRUE)) { last_error = r; goto out; } } } if (make_secure(argp, host, nconf, use_pubfh, vers) < 0) { last_error = RET_ERR; goto out; } if ((url == TRUE) && (use_pubfh == FALSE)) { /* * Convert the special from * nfs://host/path * to * host:path */ if (convert_special(&special, host, oldpath, path, mntp->mnt_special) == -1) { (void) fprintf(stderr, gettext( "could not convert URL nfs:%s to %s:%s\n"), oldpath, host, path); last_error = RET_ERR; goto out; } else { mntp->mnt_special = special; } } if (prev_argp == NULL) args = argp; else prev_argp->nfs_ext_u.nfs_extB.next = argp; prev_argp = argp; cont: if (oldpath != NULL) { free(oldpath); oldpath = NULL; } if (newpath != NULL) { free(newpath); newpath = NULL; } } argp = NULL; if (args == NULL) { last_error = RET_RETRY; goto out; } /* Determine which services are appropriate for the NFS version */ if (strcmp(fstype, MNTTYPE_NFS4) == 0) sl = service_list_v4; else sl = service_list; /* * enable services as needed. */ _check_services(sl); mntflags |= MS_DATA | MS_OPTIONSTR; if (mflg) mntflags |= MS_NOMNTTAB; if (!qflg) saveopts = strdup(mntp->mnt_mntopts); /* * And make sure that we have the ephemeral mount_to * set for this zone. */ set_nfsv4_ephemeral_mount_to(); if (mount(mntp->mnt_special, mntp->mnt_mountp, mntflags, fstype, args, sizeof (*args), mntp->mnt_mntopts, MAX_MNTOPT_STR) < 0) { if (errno != ENOENT) { pr_err(gettext("mount: %s: %s\n"), mntp->mnt_mountp, strerror(errno)); } else { struct stat sb; if (stat(mntp->mnt_mountp, &sb) < 0 && errno == ENOENT) pr_err(gettext("mount: %s: %s\n"), mntp->mnt_mountp, strerror(ENOENT)); else pr_err("%s: %s\n", mntp->mnt_special, strerror(ENOENT)); } last_error = RET_ERR; goto out; } if (!qflg && saveopts != NULL) { cmp_requested_to_actual_options(saveopts, mntp->mnt_mntopts, mntp->mnt_special, mntp->mnt_mountp); } out: if (saveopts != NULL) free(saveopts); if (special != NULL) free(special); if (oldpath != NULL) free(oldpath); if (newpath != NULL) free(newpath); free_replica(list, n); if (argp != NULL) { /* * If we had a new entry which was not added to the * list yet, then add it now that it can be freed. */ if (prev_argp == NULL) args = argp; else prev_argp->nfs_ext_u.nfs_extB.next = argp; } argp = args; while (argp != NULL) { if (argp->fh) free(argp->fh); if (argp->pathconf) free(argp->pathconf); if (argp->knconf) free(argp->knconf); if (argp->addr) { free(argp->addr->buf); free(argp->addr); } nfs_free_secdata(argp->nfs_ext_u.nfs_extB.secdata); if (argp->syncaddr) { free(argp->syncaddr->buf); free(argp->syncaddr); } if (argp->netname) free(argp->netname); prev_argp = argp; argp = argp->nfs_ext_u.nfs_extB.next; free(prev_argp); } return (last_error); } /* * These options are duplicated in uts/common/fs/nfs/nfs_dlinet.c * Changes must be made to both lists. */ static char *optlist[] = { #define OPT_RO 0 MNTOPT_RO, #define OPT_RW 1 MNTOPT_RW, #define OPT_QUOTA 2 MNTOPT_QUOTA, #define OPT_NOQUOTA 3 MNTOPT_NOQUOTA, #define OPT_SOFT 4 MNTOPT_SOFT, #define OPT_HARD 5 MNTOPT_HARD, #define OPT_SUID 6 MNTOPT_SUID, #define OPT_NOSUID 7 MNTOPT_NOSUID, #define OPT_GRPID 8 MNTOPT_GRPID, #define OPT_REMOUNT 9 MNTOPT_REMOUNT, #define OPT_NOSUB 10 MNTOPT_NOSUB, #define OPT_INTR 11 MNTOPT_INTR, #define OPT_NOINTR 12 MNTOPT_NOINTR, #define OPT_PORT 13 MNTOPT_PORT, #define OPT_SECURE 14 MNTOPT_SECURE, #define OPT_RSIZE 15 MNTOPT_RSIZE, #define OPT_WSIZE 16 MNTOPT_WSIZE, #define OPT_TIMEO 17 MNTOPT_TIMEO, #define OPT_RETRANS 18 MNTOPT_RETRANS, #define OPT_ACTIMEO 19 MNTOPT_ACTIMEO, #define OPT_ACREGMIN 20 MNTOPT_ACREGMIN, #define OPT_ACREGMAX 21 MNTOPT_ACREGMAX, #define OPT_ACDIRMIN 22 MNTOPT_ACDIRMIN, #define OPT_ACDIRMAX 23 MNTOPT_ACDIRMAX, #define OPT_BG 24 MNTOPT_BG, #define OPT_FG 25 MNTOPT_FG, #define OPT_RETRY 26 MNTOPT_RETRY, #define OPT_NOAC 27 MNTOPT_NOAC, #define OPT_NOCTO 28 MNTOPT_NOCTO, #define OPT_LLOCK 29 MNTOPT_LLOCK, #define OPT_POSIX 30 MNTOPT_POSIX, #define OPT_VERS 31 MNTOPT_VERS, #define OPT_PROTO 32 MNTOPT_PROTO, #define OPT_SEMISOFT 33 MNTOPT_SEMISOFT, #define OPT_NOPRINT 34 MNTOPT_NOPRINT, #define OPT_SEC 35 MNTOPT_SEC, #define OPT_LARGEFILES 36 MNTOPT_LARGEFILES, #define OPT_NOLARGEFILES 37 MNTOPT_NOLARGEFILES, #define OPT_PUBLIC 38 MNTOPT_PUBLIC, #define OPT_DIRECTIO 39 MNTOPT_FORCEDIRECTIO, #define OPT_NODIRECTIO 40 MNTOPT_NOFORCEDIRECTIO, #define OPT_XATTR 41 MNTOPT_XATTR, #define OPT_NOXATTR 42 MNTOPT_NOXATTR, #define OPT_DEVICES 43 MNTOPT_DEVICES, #define OPT_NODEVICES 44 MNTOPT_NODEVICES, #define OPT_SETUID 45 MNTOPT_SETUID, #define OPT_NOSETUID 46 MNTOPT_NOSETUID, #define OPT_EXEC 47 MNTOPT_EXEC, #define OPT_NOEXEC 48 MNTOPT_NOEXEC, NULL }; static int convert_int(int *val, char *str) { long lval; if (str == NULL || !isdigit(*str)) return (-1); lval = strtol(str, &str, 10); if (*str != '\0' || lval > INT_MAX) return (-2); *val = (int)lval; return (0); } static int set_args(int *mntflags, struct nfs_args *args, char *fshost, struct mnttab *mnt) { char *saveopt, *optstr, *opts, *newopts, *val; int num; int largefiles = 0; int invalid = 0; int attrpref = 0; int optlen; args->flags = NFSMNT_INT; /* default is "intr" */ args->flags |= NFSMNT_HOSTNAME; args->flags |= NFSMNT_NEWARGS; /* using extented nfs_args structure */ args->hostname = fshost; optstr = opts = strdup(mnt->mnt_mntopts); /* sizeof (MNTOPT_XXX) includes one extra byte we may need for "," */ optlen = strlen(mnt->mnt_mntopts) + sizeof (MNTOPT_XATTR) + 1; if (optlen > MAX_MNTOPT_STR) { pr_err(gettext("option string too long")); return (RET_ERR); } newopts = malloc(optlen); if (opts == NULL || newopts == NULL) { pr_err(gettext("no memory")); if (opts) free(opts); if (newopts) free(newopts); return (RET_ERR); } newopts[0] = '\0'; while (*opts) { invalid = 0; saveopt = opts; switch (getsubopt(&opts, optlist, &val)) { case OPT_RO: *mntflags |= MS_RDONLY; break; case OPT_RW: *mntflags &= ~(MS_RDONLY); break; case OPT_QUOTA: case OPT_NOQUOTA: break; case OPT_SOFT: args->flags |= NFSMNT_SOFT; args->flags &= ~(NFSMNT_SEMISOFT); break; case OPT_SEMISOFT: args->flags |= NFSMNT_SOFT; args->flags |= NFSMNT_SEMISOFT; break; case OPT_HARD: args->flags &= ~(NFSMNT_SOFT); args->flags &= ~(NFSMNT_SEMISOFT); break; case OPT_SUID: *mntflags &= ~(MS_NOSUID); break; case OPT_NOSUID: *mntflags |= MS_NOSUID; break; case OPT_GRPID: args->flags |= NFSMNT_GRPID; break; case OPT_REMOUNT: *mntflags |= MS_REMOUNT; break; case OPT_INTR: args->flags |= NFSMNT_INT; break; case OPT_NOINTR: args->flags &= ~(NFSMNT_INT); break; case OPT_NOAC: args->flags |= NFSMNT_NOAC; break; case OPT_PORT: if (convert_int(&num, val) != 0) goto badopt; nfs_port = htons((ushort_t)num); break; case OPT_SECURE: if (nfs_getseconfig_byname("dh", &nfs_sec)) { pr_err(gettext("can not get \"dh\" from %s\n"), NFSSEC_CONF); goto badopt; } sec_opt++; break; case OPT_NOCTO: args->flags |= NFSMNT_NOCTO; break; case OPT_RSIZE: if (convert_int(&args->rsize, val) != 0) goto badopt; args->flags |= NFSMNT_RSIZE; break; case OPT_WSIZE: if (convert_int(&args->wsize, val) != 0) goto badopt; args->flags |= NFSMNT_WSIZE; break; case OPT_TIMEO: if (convert_int(&args->timeo, val) != 0) goto badopt; args->flags |= NFSMNT_TIMEO; break; case OPT_RETRANS: if (convert_int(&args->retrans, val) != 0) goto badopt; args->flags |= NFSMNT_RETRANS; break; case OPT_ACTIMEO: if (convert_int(&args->acregmax, val) != 0) goto badopt; args->acdirmin = args->acregmin = args->acdirmax = args->acregmax; args->flags |= NFSMNT_ACDIRMAX; args->flags |= NFSMNT_ACREGMAX; args->flags |= NFSMNT_ACDIRMIN; args->flags |= NFSMNT_ACREGMIN; break; case OPT_ACREGMIN: if (convert_int(&args->acregmin, val) != 0) goto badopt; args->flags |= NFSMNT_ACREGMIN; break; case OPT_ACREGMAX: if (convert_int(&args->acregmax, val) != 0) goto badopt; args->flags |= NFSMNT_ACREGMAX; break; case OPT_ACDIRMIN: if (convert_int(&args->acdirmin, val) != 0) goto badopt; args->flags |= NFSMNT_ACDIRMIN; break; case OPT_ACDIRMAX: if (convert_int(&args->acdirmax, val) != 0) goto badopt; args->flags |= NFSMNT_ACDIRMAX; break; case OPT_BG: bg++; break; case OPT_FG: bg = 0; break; case OPT_RETRY: if (convert_int(&retries, val) != 0) goto badopt; break; case OPT_LLOCK: args->flags |= NFSMNT_LLOCK; break; case OPT_POSIX: posix = 1; break; case OPT_VERS: if (convert_int(&num, val) != 0) goto badopt; nfsvers = (rpcvers_t)num; break; case OPT_PROTO: if (val == NULL) goto badopt; nfs_proto = (char *)malloc(strlen(val)+1); if (!nfs_proto) { pr_err(gettext("no memory")); return (RET_ERR); } (void) strncpy(nfs_proto, val, strlen(val)+1); break; case OPT_NOPRINT: args->flags |= NFSMNT_NOPRINT; break; case OPT_LARGEFILES: largefiles = 1; break; case OPT_NOLARGEFILES: pr_err(gettext("NFS can't support \"nolargefiles\"\n")); free(optstr); return (RET_ERR); case OPT_SEC: if (val == NULL) { pr_err(gettext( "\"sec\" option requires argument\n")); return (RET_ERR); } if (nfs_getseconfig_byname(val, &nfs_sec)) { pr_err(gettext("can not get \"%s\" from %s\n"), val, NFSSEC_CONF); return (RET_ERR); } sec_opt++; break; case OPT_PUBLIC: public_opt = TRUE; break; case OPT_DIRECTIO: args->flags |= NFSMNT_DIRECTIO; break; case OPT_NODIRECTIO: args->flags &= ~(NFSMNT_DIRECTIO); break; case OPT_XATTR: case OPT_NOXATTR: /* * VFS options; just need to get them into the * new mount option string and note we've seen them */ attrpref = 1; break; default: /* * Note that this could be a valid OPT_* option so * we can't use "val" but need to use "saveopt". */ if (fsisstdopt(saveopt)) break; invalid = 1; if (!qflg) (void) fprintf(stderr, gettext( "mount: %s on %s - WARNING unknown option" " \"%s\"\n"), mnt->mnt_special, mnt->mnt_mountp, saveopt); break; } if (!invalid) { if (newopts[0]) strcat(newopts, ","); strcat(newopts, saveopt); } } /* Default is to turn extended attrs on */ if (!attrpref) { if (newopts[0]) strcat(newopts, ","); strcat(newopts, MNTOPT_XATTR); } strcpy(mnt->mnt_mntopts, newopts); free(newopts); free(optstr); /* ensure that only one secure mode is requested */ if (sec_opt > 1) { pr_err(gettext("Security options conflict\n")); return (RET_ERR); } /* ensure that the user isn't trying to get large files over V2 */ if (nfsvers == NFS_VERSION && largefiles) { pr_err(gettext("NFS V2 can't support \"largefiles\"\n")); return (RET_ERR); } if (nfsvers == NFS_V4 && nfs_proto != NULL && strncasecmp(nfs_proto, NC_UDP, strlen(NC_UDP)) == 0) { pr_err(gettext("NFS V4 does not support %s\n"), nfs_proto); return (RET_ERR); } return (RET_OK); badopt: pr_err(gettext("invalid option: \"%s\"\n"), saveopt); free(optstr); return (RET_ERR); } static int make_secure(struct nfs_args *args, char *hostname, struct netconfig *nconf, bool_t use_pubfh, rpcvers_t vers) { sec_data_t *secdata; int flags; struct netbuf *syncaddr = NULL; struct nd_addrlist *retaddrs = NULL; char netname[MAXNETNAMELEN+1]; /* * check to see if any secure mode is requested. * if not, use default security mode. */ if (!snego_done && !sec_opt) { /* * Get default security mode. * AUTH_UNIX has been the default choice for a long time. * The better NFS security service becomes, the better chance * we will set stronger security service as the default NFS * security mode. */ if (nfs_getseconfig_default(&nfs_sec)) { pr_err(gettext("error getting default" " security entry\n")); return (-1); } args->flags |= NFSMNT_SECDEFAULT; } /* * Get the network address for the time service on the server. * If an RPC based time service is not available then try the * IP time service. * * This is for AUTH_DH processing. We will also pass down syncaddr * and netname for NFS V4 even if AUTH_DH is not requested right now. * NFS V4 does security negotiation in the kernel via SECINFO. * These information might be needed later in the kernel. * * Eventurally, we want to move this code to nfs_clnt_secdata() * when autod_nfs.c and mount.c can share the same get_the_addr() * routine. */ flags = 0; syncaddr = NULL; if (nfs_sec.sc_rpcnum == AUTH_DH || vers == NFS_V4) { /* * If using the public fh or nfsv4, we will not contact the * remote RPCBINDer, since it is possibly behind a firewall. */ if (use_pubfh == FALSE && vers != NFS_V4) syncaddr = get_the_addr(hostname, RPCBPROG, RPCBVERS, nconf, 0, NULL, NULL, FALSE, NULL, NULL); if (syncaddr != NULL) { /* for flags in sec_data */ flags |= AUTH_F_RPCTIMESYNC; } else { struct nd_hostserv hs; int error; hs.h_host = hostname; hs.h_serv = "timserver"; error = netdir_getbyname(nconf, &hs, &retaddrs); if (error != ND_OK && (nfs_sec.sc_rpcnum == AUTH_DH)) { pr_err(gettext("%s: secure: no time service\n"), hostname); return (-1); } if (error == ND_OK) syncaddr = retaddrs->n_addrs; /* * For NFS_V4 if AUTH_DH is negotiated later in the * kernel thru SECINFO, it will need syncaddr * and netname data. */ if (vers == NFS_V4 && syncaddr && host2netname(netname, hostname, NULL)) { args->syncaddr = malloc(sizeof (struct netbuf)); args->syncaddr->buf = malloc(syncaddr->len); (void) memcpy(args->syncaddr->buf, syncaddr->buf, syncaddr->len); args->syncaddr->len = syncaddr->len; args->syncaddr->maxlen = syncaddr->maxlen; args->netname = strdup(netname); args->flags |= NFSMNT_SECURE; } } } /* * For the initial chosen flavor (any flavor defined in nfssec.conf), * the data will be stored in the sec_data structure via * nfs_clnt_secdata() and be passed to the kernel via nfs_args_* * extended data structure. */ if (!(secdata = nfs_clnt_secdata(&nfs_sec, hostname, args->knconf, syncaddr, flags))) { pr_err(gettext("errors constructing security related data\n")); if (flags & AUTH_F_RPCTIMESYNC) { free(syncaddr->buf); free(syncaddr); } else if (retaddrs) netdir_free((void *)retaddrs, ND_ADDRLIST); return (-1); } NFS_ARGS_EXTB_secdata(args, secdata); if (flags & AUTH_F_RPCTIMESYNC) { free(syncaddr->buf); free(syncaddr); } else if (retaddrs) netdir_free((void *)retaddrs, ND_ADDRLIST); return (0); } /* * Get the network address on "hostname" for program "prog" * with version "vers" by using the nconf configuration data * passed in. * * If the address of a netconfig pointer is null then * information is not sufficient and no netbuf will be returned. * * Finally, ping the null procedure of that service. * * A similar routine is also defined in ../../autofs/autod_nfs.c. * This is a potential routine to move to ../lib for common usage. */ /* Hammerhead: Use rpcprog_t/rpcvers_t to match forward declaration */ static struct netbuf * get_the_addr(char *hostname, rpcprog_t prog, rpcvers_t vers, struct netconfig *nconf, ushort_t port, struct t_info *tinfo, caddr_t *fhp, bool_t get_pubfh, char *fspath, err_ret_t *error) { struct netbuf *nb = NULL; struct t_bind *tbind = NULL; CLIENT *cl = NULL; struct timeval tv; int fd = -1; AUTH *ah = NULL; AUTH *new_ah = NULL; struct snego_t snego; if (nconf == NULL) return (NULL); if ((fd = t_open(nconf->nc_device, O_RDWR, tinfo)) == -1) goto done; /* LINTED pointer alignment */ if ((tbind = (struct t_bind *)t_alloc(fd, T_BIND, T_ADDR)) == NULL) goto done; /* * In the case of public filehandle usage or NFSv4 we want to * avoid use of the rpcbind/portmap protocol */ if ((get_pubfh == TRUE) || (vers == NFS_V4)) { struct nd_hostserv hs; struct nd_addrlist *retaddrs; int retval; hs.h_host = hostname; /* NFS where vers==4 does not support UDP */ if (vers == NFS_V4 && strncasecmp(nconf->nc_proto, NC_UDP, strlen(NC_UDP)) == 0) { SET_ERR_RET(error, ERR_PROTO_UNSUPP, 0); goto done; } if (port == 0) hs.h_serv = "nfs"; else hs.h_serv = NULL; if ((retval = netdir_getbyname(nconf, &hs, &retaddrs)) != ND_OK) { /* * Carefully set the error value here. Want to signify * that the error was an unknown host. */ if (retval == ND_NOHOST) { SET_ERR_RET(error, ERR_NOHOST, retval); } goto done; } memcpy(tbind->addr.buf, retaddrs->n_addrs->buf, retaddrs->n_addrs->len); tbind->addr.len = retaddrs->n_addrs->len; netdir_free((void *)retaddrs, ND_ADDRLIST); (void) netdir_options(nconf, ND_SET_RESERVEDPORT, fd, NULL); } else { if (rpcb_getaddr(prog, vers, nconf, &tbind->addr, hostname) == FALSE) { goto done; } } if (port) { /* LINTED pointer alignment */ if (strcmp(nconf->nc_protofmly, NC_INET) == 0) ((struct sockaddr_in *)tbind->addr.buf)->sin_port = port; else if (strcmp(nconf->nc_protofmly, NC_INET6) == 0) ((struct sockaddr_in6 *)tbind->addr.buf)->sin6_port = port; } cl = clnt_tli_create(fd, nconf, &tbind->addr, prog, vers, 0, 0); if (cl == NULL) { /* * clnt_tli_create() returns either RPC_SYSTEMERROR, * RPC_UNKNOWNPROTO or RPC_TLIERROR. The RPC_TLIERROR translates * to "Misc. TLI error". This is not too helpful. Most likely * the connection to the remote server timed out, so this * error is at least less perplexing. * See: usr/src/cmd/rpcinfo/rpcinfo.c */ if (rpc_createerr.cf_stat == RPC_TLIERROR) { SET_ERR_RET(error, ERR_RPCERROR, RPC_PMAPFAILURE); } else { SET_ERR_RET(error, ERR_RPCERROR, rpc_createerr.cf_stat); } goto done; } ah = authsys_create_default(); if (ah != NULL) cl->cl_auth = ah; tv.tv_sec = 5; tv.tv_usec = 0; (void) clnt_control(cl, CLSET_TIMEOUT, (char *)&tv); if ((get_pubfh == TRUE) && (vers != NFS_V4)) { enum snego_stat sec; if (!snego_done) { /* * negotiate sec flavor. */ snego.cnt = 0; if ((sec = nfs_sec_nego(vers, cl, fspath, &snego)) == SNEGO_SUCCESS) { int jj; /* * check if server supports the one * specified in the sec= option. */ if (sec_opt) { for (jj = 0; jj < snego.cnt; jj++) { if (snego.array[jj] == nfs_sec.sc_nfsnum) { snego_done = TRUE; break; } } } /* * find a common sec flavor */ if (!snego_done) { if (sec_opt) { pr_err(gettext( "Server does not support" " the security flavor" " specified.\n")); } for (jj = 0; jj < snego.cnt; jj++) { if (!nfs_getseconfig_bynumber( snego.array[jj], &nfs_sec)) { snego_done = TRUE; #define EMSG80SUX "Security flavor %d was negotiated and will be used.\n" if (sec_opt) pr_err(gettext( EMSG80SUX), nfs_sec. sc_nfsnum); break; } } } if (!snego_done) return (NULL); /* * Now that the flavor has been * negotiated, get the fh. * * First, create an auth handle using the * negotiated sec flavor in the next lookup to * fetch the filehandle. */ new_ah = nfs_create_ah(cl, hostname, &nfs_sec); if (new_ah == NULL) goto done; cl->cl_auth = new_ah; } else if (sec == SNEGO_ARRAY_TOO_SMALL || sec == SNEGO_FAILURE) { goto done; } /* * Note that if sec == SNEGO_DEF_VALID * default sec flavor is acceptable. * Use it to get the filehandle. */ } if (vers == NFS_VERSION) { wnl_diropargs arg; wnl_diropres res; memset((char *)&arg.dir, 0, sizeof (wnl_fh)); arg.name = fspath; memset((char *)&res, 0, sizeof (wnl_diropres)); if (wnlproc_lookup_2(&arg, &res, cl) != RPC_SUCCESS || res.status != WNL_OK) goto done; *fhp = malloc(sizeof (wnl_fh)); if (*fhp == NULL) { pr_err(gettext("no memory\n")); goto done; } memcpy((char *)*fhp, (char *)&res.wnl_diropres_u.wnl_diropres.file, sizeof (wnl_fh)); } else { WNL_LOOKUP3args arg; WNL_LOOKUP3res res; nfs_fh3 *fh3p; memset((char *)&arg.what.dir, 0, sizeof (wnl_fh3)); arg.what.name = fspath; memset((char *)&res, 0, sizeof (WNL_LOOKUP3res)); if (wnlproc3_lookup_3(&arg, &res, cl) != RPC_SUCCESS || res.status != WNL3_OK) goto done; fh3p = (nfs_fh3 *)malloc(sizeof (*fh3p)); if (fh3p == NULL) { pr_err(gettext("no memory\n")); goto done; } fh3p->fh3_length = res.WNL_LOOKUP3res_u.res_ok.object.data.data_len; memcpy(fh3p->fh3_u.data, res.WNL_LOOKUP3res_u.res_ok.object.data.data_val, fh3p->fh3_length); *fhp = (caddr_t)fh3p; } } else { struct rpc_err r_err; enum clnt_stat rc; /* * NULL procedures need not have an argument or * result param. */ if (vers == NFS_VERSION) rc = wnlproc_null_2(NULL, NULL, cl); else if (vers == NFS_V3) rc = wnlproc3_null_3(NULL, NULL, cl); else rc = wnlproc4_null_4(NULL, NULL, cl); if (rc != RPC_SUCCESS) { clnt_geterr(cl, &r_err); if (strcmp(nconf->nc_protofmly, NC_LOOPBACK) == 0) { switch (r_err.re_status) { case RPC_TLIERROR: case RPC_CANTRECV: case RPC_CANTSEND: r_err.re_status = RPC_PROGVERSMISMATCH; } } SET_ERR_RET(error, ERR_RPCERROR, r_err.re_status); goto done; } } /* * Make a copy of the netbuf to return */ nb = (struct netbuf *)malloc(sizeof (*nb)); if (nb == NULL) { pr_err(gettext("no memory\n")); goto done; } *nb = tbind->addr; nb->buf = (char *)malloc(nb->maxlen); if (nb->buf == NULL) { pr_err(gettext("no memory\n")); free(nb); nb = NULL; goto done; } (void) memcpy(nb->buf, tbind->addr.buf, tbind->addr.len); done: if (cl) { if (ah != NULL) { if (new_ah != NULL) AUTH_DESTROY(ah); AUTH_DESTROY(cl->cl_auth); cl->cl_auth = NULL; } clnt_destroy(cl); cl = NULL; } if (tbind) { t_free((char *)tbind, T_BIND); tbind = NULL; } if (fd >= 0) (void) t_close(fd); return (nb); } static int check_nconf(struct netconfig *nconf, int nthtry, int *valid_proto) { int try_test = 0; int valid_family; char *proto = NULL; if (nthtry == FIRST_TRY) { try_test = ((nconf->nc_semantics == NC_TPI_COTS_ORD) || (nconf->nc_semantics == NC_TPI_COTS)); proto = NC_TCP; } else if (nthtry == SECOND_TRY) { try_test = (nconf->nc_semantics == NC_TPI_CLTS); proto = NC_UDP; } if (proto && (strcmp(nconf->nc_protofmly, NC_INET) == 0 || strcmp(nconf->nc_protofmly, NC_INET6) == 0) && (strcmp(nconf->nc_proto, proto) == 0)) *valid_proto = TRUE; else *valid_proto = FALSE; return (try_test); } /* * Get a network address on "hostname" for program "prog" * with version "vers". If the port number is specified (non zero) * then try for a TCP/UDP transport and set the port number of the * resulting IP address. * * If the address of a netconfig pointer was passed and * if it's not null, use it as the netconfig otherwise * assign the address of the netconfig that was used to * establish contact with the service. * * A similar routine is also defined in ../../autofs/autod_nfs.c. * This is a potential routine to move to ../lib for common usage. * * "error" refers to a more descriptive term when get_addr fails * and returns NULL: ERR_PROTO_NONE if no error introduced by * -o proto option, ERR_NETPATH if error found in NETPATH * environment variable, ERR_PROTO_INVALID if an unrecognized * protocol is specified by user, and ERR_PROTO_UNSUPP for a * recognized but invalid protocol (eg. ticlts, ticots, etc.). * "error" is ignored if get_addr returns non-NULL result. * */ /* Hammerhead: Use rpcprog_t/rpcvers_t to match forward declaration */ static struct netbuf * get_addr(char *hostname, rpcprog_t prog, rpcvers_t vers, struct netconfig **nconfp, char *proto, ushort_t port, struct t_info *tinfo, caddr_t *fhp, bool_t get_pubfh, char *fspath, err_ret_t *error) { struct netbuf *nb = NULL; struct netconfig *nconf = NULL; NCONF_HANDLE *nc = NULL; int nthtry = FIRST_TRY; err_ret_t errsave_nohost, errsave_rpcerr; SET_ERR_RET(&errsave_nohost, ERR_PROTO_NONE, 0); SET_ERR_RET(&errsave_rpcerr, ERR_PROTO_NONE, 0); SET_ERR_RET(error, ERR_PROTO_NONE, 0); if (nconfp && *nconfp) return (get_the_addr(hostname, prog, vers, *nconfp, port, tinfo, fhp, get_pubfh, fspath, error)); /* * No nconf passed in. * * Try to get a nconf from /etc/netconfig filtered by * the NETPATH environment variable. * First search for COTS, second for CLTS unless proto * is specified. When we retry, we reset the * netconfig list so that we would search the whole list * all over again. */ if ((nc = setnetpath()) == NULL) { /* should only return an error if problems with NETPATH */ /* In which case you are hosed */ SET_ERR_RET(error, ERR_NETPATH, 0); goto done; } /* * If proto is specified, then only search for the match, * otherwise try COTS first, if failed, try CLTS. */ if (proto) { /* no matching proto name */ SET_ERR_RET(error, ERR_PROTO_INVALID, 0); while (nconf = getnetpath(nc)) { if (strcmp(nconf->nc_netid, proto)) continue; /* may be unsupported */ SET_ERR_RET(error, ERR_PROTO_UNSUPP, 0); if ((port != 0) && ((strcmp(nconf->nc_protofmly, NC_INET) == 0 || strcmp(nconf->nc_protofmly, NC_INET6) == 0) && (strcmp(nconf->nc_proto, NC_TCP) != 0 && strcmp(nconf->nc_proto, NC_UDP) != 0))) { continue; } else { nb = get_the_addr(hostname, prog, vers, nconf, port, tinfo, fhp, get_pubfh, fspath, error); if (nb != NULL) break; /* nb is NULL - deal with errors */ if (error) { if (error->error_type == ERR_NOHOST) SET_ERR_RET(&errsave_nohost, error->error_type, error->error_value); if (error->error_type == ERR_RPCERROR) SET_ERR_RET(&errsave_rpcerr, error->error_type, error->error_value); } /* * continue with same protocol * selection */ continue; } } /* end of while */ if (nconf == NULL) goto done; if ((nb = get_the_addr(hostname, prog, vers, nconf, port, tinfo, fhp, get_pubfh, fspath, error)) == NULL) goto done; } else { retry: SET_ERR_RET(error, ERR_NETPATH, 0); while (nconf = getnetpath(nc)) { SET_ERR_RET(error, ERR_PROTO_NONE, 0); if (nconf->nc_flag & NC_VISIBLE) { int valid_proto; if (check_nconf(nconf, nthtry, &valid_proto)) { if (port == 0) break; if (valid_proto == TRUE) break; } } } /* while */ if (nconf == NULL) { if (++nthtry <= MNT_PREF_LISTLEN) { endnetpath(nc); if ((nc = setnetpath()) == NULL) goto done; goto retry; } else goto done; } else { if ((nb = get_the_addr(hostname, prog, vers, nconf, port, tinfo, fhp, get_pubfh, fspath, error)) == NULL) { /* nb is NULL - deal with errors */ if (error) { if (error->error_type == ERR_NOHOST) SET_ERR_RET(&errsave_nohost, error->error_type, error->error_value); if (error->error_type == ERR_RPCERROR) SET_ERR_RET(&errsave_rpcerr, error->error_type, error->error_value); } /* * Continue the same search path in the * netconfig db until no more matched * nconf (nconf == NULL). */ goto retry; } } } SET_ERR_RET(error, ERR_PROTO_NONE, 0); /* * Got nconf and nb. Now dup the netconfig structure (nconf) * and return it thru nconfp. */ *nconfp = getnetconfigent(nconf->nc_netid); if (*nconfp == NULL) { syslog(LOG_ERR, "no memory\n"); free(nb); nb = NULL; } done: if (nc) endnetpath(nc); if (nb == NULL) { /* * Check the saved errors. The RPC error has * * precedence over the no host error. */ if (errsave_nohost.error_type != ERR_PROTO_NONE) SET_ERR_RET(error, errsave_nohost.error_type, errsave_nohost.error_value); if (errsave_rpcerr.error_type != ERR_PROTO_NONE) SET_ERR_RET(error, errsave_rpcerr.error_type, errsave_rpcerr.error_value); } return (nb); } /* * Get a file handle usinging multi-component lookup with the public * file handle. */ static int get_fh_via_pub(struct nfs_args *args, char *fshost, char *fspath, bool_t url, bool_t loud, int *versp, struct netconfig **nconfp, ushort_t port) { uint_t vers_min; uint_t vers_max; int r; char *path; if (nfsvers != 0) { vers_max = vers_min = nfsvers; } else { vers_max = vers_max_default; vers_min = vers_min_default; } if (url == FALSE) { path = malloc(strlen(fspath) + 2); if (path == NULL) { if (loud == TRUE) pr_err(gettext("no memory\n")); return (RET_ERR); } path[0] = (char)WNL_NATIVEPATH; (void) strcpy(&path[1], fspath); } else { path = fspath; } for (nfsvers_to_use = vers_max; nfsvers_to_use >= vers_min; nfsvers_to_use--) { /* * getaddr_nfs will also fill in the fh for us. */ r = getaddr_nfs(args, fshost, nconfp, TRUE, path, port, NULL, FALSE); if (r == RET_OK) { /* * Since we are using the public fh, and NLM is * not firewall friendly, use local locking. * Not the case for v4. */ *versp = nfsvers_to_use; switch (nfsvers_to_use) { case NFS_V4: fstype = MNTTYPE_NFS4; break; case NFS_V3: fstype = MNTTYPE_NFS3; /* FALLTHROUGH */ default: args->flags |= NFSMNT_LLOCK; break; } if (fspath != path) free(path); return (r); } } if (fspath != path) free(path); if (loud == TRUE) { pr_err(gettext("Could not use public filehandle in request to" " server %s\n"), fshost); } return (r); } /* * get fhandle of remote path from server's mountd */ static int get_fh(struct nfs_args *args, char *fshost, char *fspath, int *versp, bool_t loud_on_mnt_err, struct netconfig **nconfp, ushort_t port) { static struct fhstatus fhs; static struct mountres3 mountres3; static struct pathcnf p; nfs_fh3 *fh3p; struct timeval timeout = { 25, 0}; CLIENT *cl; enum clnt_stat rpc_stat; rpcvers_t outvers = 0; rpcvers_t vers_to_try; rpcvers_t vers_min; static int printed = 0; int count, i, *auths; char *msg; switch (nfsvers) { case 2: /* version 2 specified try that only */ vers_to_try = MOUNTVERS_POSIX; vers_min = MOUNTVERS; break; case 3: /* version 3 specified try that only */ vers_to_try = MOUNTVERS3; vers_min = MOUNTVERS3; break; case 4: /* version 4 specified try that only */ /* * This assignment is in the wrong version sequence. * The above are MOUNT program and this is NFS * program. However, it happens to work out since the * two don't collide for NFSv4. */ vers_to_try = NFS_V4; vers_min = NFS_V4; break; default: /* no version specified, start with default */ /* * If the retry version is set, use that. This will * be set if the last mount attempt returned any other * besides an RPC error. */ if (nfsretry_vers) vers_to_try = nfsretry_vers; else { vers_to_try = vers_max_default; vers_min = vers_min_default; } break; } /* * In the case of version 4, just NULL proc the server since * there is no MOUNT program. If this fails, then decrease * vers_to_try and continue on with regular MOUNT program * processing. */ if (vers_to_try == NFS_V4) { int savevers = nfsvers_to_use; err_ret_t error; int retval; SET_ERR_RET(&error, ERR_PROTO_NONE, 0); /* Let's hope for the best */ nfsvers_to_use = NFS_V4; retval = getaddr_nfs(args, fshost, nconfp, FALSE, fspath, port, &error, vers_min == NFS_V4); if (retval == RET_OK) { *versp = nfsvers_to_use = NFS_V4; fstype = MNTTYPE_NFS4; args->fh = strdup(fspath); if (args->fh == NULL) { pr_err(gettext("no memory\n")); *versp = nfsvers_to_use = savevers; return (RET_ERR); } return (RET_OK); } nfsvers_to_use = savevers; vers_to_try--; /* If no more versions to try, let the user know. */ if (vers_to_try < vers_min) return (retval); /* * If we are here, there are more versions to try but * there has been an error of some sort. If it is not * an RPC error (e.g. host unknown), we just stop and * return the error since the other versions would see * the same error as well. */ if (retval == RET_ERR && error.error_type != ERR_RPCERROR) return (retval); } while ((cl = clnt_create_vers(fshost, MOUNTPROG, &outvers, vers_min, vers_to_try, "datagram_v")) == NULL) { if (rpc_createerr.cf_stat == RPC_UNKNOWNHOST) { pr_err(gettext("%s: %s\n"), fshost, clnt_spcreateerror("")); return (RET_ERR); } /* * We don't want to downgrade version on lost packets */ if ((rpc_createerr.cf_stat == RPC_TIMEDOUT) || (rpc_createerr.cf_stat == RPC_PMAPFAILURE)) { pr_err(gettext("%s: %s\n"), fshost, clnt_spcreateerror("")); return (RET_RETRY); } /* * back off and try the previous version - patch to the * problem of version numbers not being contigous and * clnt_create_vers failing (SunOS4.1 clients & SGI servers) * The problem happens with most non-Sun servers who * don't support mountd protocol #2. So, in case the * call fails, we re-try the call anyway. */ vers_to_try--; if (vers_to_try < vers_min) { if (rpc_createerr.cf_stat == RPC_PROGVERSMISMATCH) { if (nfsvers == 0) { pr_err(gettext( "%s:%s: no applicable versions of NFS supported\n"), fshost, fspath); } else { pr_err(gettext( "%s:%s: NFS Version %d not supported\n"), fshost, fspath, nfsvers); } return (RET_ERR); } if (!printed) { pr_err(gettext("%s: %s\n"), fshost, clnt_spcreateerror("")); printed = 1; } return (RET_RETRY); } } if (posix && outvers < MOUNTVERS_POSIX) { pr_err(gettext("%s: %s: no pathconf info\n"), fshost, clnt_sperror(cl, "")); clnt_destroy(cl); return (RET_ERR); } if (__clnt_bindresvport(cl) < 0) { pr_err(gettext("Couldn't bind to reserved port\n")); clnt_destroy(cl); return (RET_RETRY); } if ((cl->cl_auth = authsys_create_default()) == NULL) { pr_err( gettext("Couldn't create default authentication handle\n")); clnt_destroy(cl); return (RET_RETRY); } switch (outvers) { case MOUNTVERS: case MOUNTVERS_POSIX: *versp = nfsvers_to_use = NFS_VERSION; rpc_stat = clnt_call(cl, MOUNTPROC_MNT, xdr_dirpath, (caddr_t)&fspath, xdr_fhstatus, (caddr_t)&fhs, timeout); if (rpc_stat != RPC_SUCCESS) { pr_err(gettext("%s:%s: server not responding %s\n"), fshost, fspath, clnt_sperror(cl, "")); clnt_destroy(cl); return (RET_RETRY); } if ((errno = fhs.fhs_status) != MNT_OK) { if (loud_on_mnt_err) { if (errno == EACCES) { pr_err(gettext( "%s:%s: access denied\n"), fshost, fspath); } else { pr_err(gettext("%s:%s: %s\n"), fshost, fspath, errno >= 0 ? strerror(errno) : "invalid error " "returned by server"); } } clnt_destroy(cl); return (RET_MNTERR); } args->fh = malloc(sizeof (fhs.fhstatus_u.fhs_fhandle)); if (args->fh == NULL) { pr_err(gettext("no memory\n")); return (RET_ERR); } memcpy((caddr_t)args->fh, (caddr_t)&fhs.fhstatus_u.fhs_fhandle, sizeof (fhs.fhstatus_u.fhs_fhandle)); if (!errno && posix) { rpc_stat = clnt_call(cl, MOUNTPROC_PATHCONF, xdr_dirpath, (caddr_t)&fspath, xdr_ppathcnf, (caddr_t)&p, timeout); if (rpc_stat != RPC_SUCCESS) { pr_err(gettext( "%s:%s: server not responding %s\n"), fshost, fspath, clnt_sperror(cl, "")); free(args->fh); clnt_destroy(cl); return (RET_RETRY); } if (_PC_ISSET(_PC_ERROR, p.pc_mask)) { pr_err(gettext( "%s:%s: no pathconf info\n"), fshost, fspath); free(args->fh); clnt_destroy(cl); return (RET_ERR); } args->flags |= NFSMNT_POSIX; args->pathconf = malloc(sizeof (p)); if (args->pathconf == NULL) { pr_err(gettext("no memory\n")); free(args->fh); clnt_destroy(cl); return (RET_ERR); } memcpy((caddr_t)args->pathconf, (caddr_t)&p, sizeof (p)); } break; case MOUNTVERS3: *versp = nfsvers_to_use = NFS_V3; rpc_stat = clnt_call(cl, MOUNTPROC_MNT, xdr_dirpath, (caddr_t)&fspath, xdr_mountres3, (caddr_t)&mountres3, timeout); if (rpc_stat != RPC_SUCCESS) { pr_err(gettext("%s:%s: server not responding %s\n"), fshost, fspath, clnt_sperror(cl, "")); clnt_destroy(cl); return (RET_RETRY); } /* * Assume here that most of the MNT3ERR_* * codes map into E* errors. */ if ((errno = mountres3.fhs_status) != MNT_OK) { if (loud_on_mnt_err) { switch (errno) { case MNT3ERR_NAMETOOLONG: msg = "path name is too long"; break; case MNT3ERR_NOTSUPP: msg = "operation not supported"; break; case MNT3ERR_SERVERFAULT: msg = "server fault"; break; default: if (errno >= 0) msg = strerror(errno); else msg = "invalid error returned " "by server"; break; } pr_err(gettext("%s:%s: %s\n"), fshost, fspath, msg); } clnt_destroy(cl); return (RET_MNTERR); } fh3p = (nfs_fh3 *)malloc(sizeof (*fh3p)); if (fh3p == NULL) { pr_err(gettext("no memory\n")); return (RET_ERR); } fh3p->fh3_length = mountres3.mountres3_u.mountinfo.fhandle.fhandle3_len; (void) memcpy(fh3p->fh3_u.data, mountres3.mountres3_u.mountinfo.fhandle.fhandle3_val, fh3p->fh3_length); args->fh = (caddr_t)fh3p; fstype = MNTTYPE_NFS3; /* * Check the security flavor to be used. * * If "secure" or "sec=flavor" is a mount * option, check if the server supports the "flavor". * If the server does not support the flavor, return * error. * * If no mount option is given then look for default auth * (default auth entry in /etc/nfssec.conf) in the auth list * returned from server. If default auth not found, then use * the first supported security flavor (by the client) in the * auth list returned from the server. * */ auths = mountres3.mountres3_u.mountinfo.auth_flavors .auth_flavors_val; count = mountres3.mountres3_u.mountinfo.auth_flavors .auth_flavors_len; if (count <= 0) { pr_err(gettext( "server %s did not return any security mode\n"), fshost); clnt_destroy(cl); return (RET_ERR); } if (sec_opt) { for (i = 0; i < count; i++) { if (auths[i] == nfs_sec.sc_nfsnum) break; } if (i == count) goto autherr; } else { seconfig_t default_sec; /* * Get client configured default auth. */ nfs_sec.sc_nfsnum = -1; default_sec.sc_nfsnum = -1; (void) nfs_getseconfig_default(&default_sec); /* * Look for clients default auth in servers list. */ if (default_sec.sc_nfsnum != -1) { for (i = 0; i < count; i++) { if (auths[i] == default_sec.sc_nfsnum) { sec_opt++; nfs_sec = default_sec; break; } } } /* * Could not find clients default auth in servers list. * Pick the first auth from servers list that is * also supported on the client. */ if (nfs_sec.sc_nfsnum == -1) { for (i = 0; i < count; i++) { if (!nfs_getseconfig_bynumber(auths[i], &nfs_sec)) { sec_opt++; break; } } } if (i == count) goto autherr; } break; default: pr_err(gettext("%s:%s: Unknown MOUNT version %d\n"), fshost, fspath, outvers); clnt_destroy(cl); return (RET_ERR); } clnt_destroy(cl); return (RET_OK); autherr: pr_err(gettext( "security mode does not match the server exporting %s:%s\n"), fshost, fspath); clnt_destroy(cl); return (RET_ERR); } /* * Fill in the address for the server's NFS service and * fill in a knetconfig structure for the transport that * the service is available on. */ static int getaddr_nfs(struct nfs_args *args, char *fshost, struct netconfig **nconfp, bool_t get_pubfh, char *fspath, ushort_t port, err_ret_t *error, bool_t print_rpcerror) { struct stat sb; struct netconfig *nconf; struct knetconfig *knconfp; static int printed = 0; struct t_info tinfo; err_ret_t addr_error; SET_ERR_RET(error, ERR_PROTO_NONE, 0); SET_ERR_RET(&addr_error, ERR_PROTO_NONE, 0); if (nfs_proto) { /* * If a proto is specified and its rdma try this. The kernel * will later do the reachablity test and fail form there * if rdma transport is not available to kernel rpc */ if (strcmp(nfs_proto, "rdma") == 0) { args->addr = get_addr(fshost, NFS_PROGRAM, nfsvers_to_use, nconfp, NULL, port, &tinfo, &args->fh, get_pubfh, fspath, &addr_error); args->flags |= NFSMNT_DORDMA; } else { args->addr = get_addr(fshost, NFS_PROGRAM, nfsvers_to_use, nconfp, nfs_proto, port, &tinfo, &args->fh, get_pubfh, fspath, &addr_error); } } else { args->addr = get_addr(fshost, NFS_PROGRAM, nfsvers_to_use, nconfp, nfs_proto, port, &tinfo, &args->fh, get_pubfh, fspath, &addr_error); /* * If no proto is specified set this flag. * Kernel mount code will try to use RDMA if its on the * system, otherwise it will keep on using the protocol * selected here, through the above get_addr call. */ if (nfs_proto == NULL) args->flags |= NFSMNT_TRYRDMA; } if (args->addr == NULL) { /* * We could have failed because the server had no public * file handle support. So don't print a message and don't * retry. */ if (get_pubfh == TRUE) return (RET_ERR); if (!printed) { switch (addr_error.error_type) { case 0: printed = 1; break; case ERR_RPCERROR: if (!print_rpcerror) /* no error print at this time */ break; pr_err(gettext("%s NFS service not" " available %s\n"), fshost, clnt_sperrno(addr_error.error_value)); printed = 1; break; case ERR_NETPATH: pr_err(gettext("%s: Error in NETPATH.\n"), fshost); printed = 1; break; case ERR_PROTO_INVALID: pr_err(gettext("%s: NFS service does not" " recognize protocol: %s.\n"), fshost, nfs_proto); printed = 1; break; case ERR_PROTO_UNSUPP: if (nfsvers || nfsvers_to_use == NFS_VERSMIN) { /* * Don't set "printed" here. Since we * have to keep checking here till we * exhaust transport errors on all vers. * * Print this message if: * 1. After we have tried all versions * of NFS and none support the asked * transport. * * 2. If a version is specified and it * does'nt support the asked * transport. * * Otherwise we decrement the version * and retry below. */ pr_err(gettext("%s: NFS service does" " not support protocol: %s.\n"), fshost, nfs_proto); } break; case ERR_NOHOST: pr_err("%s: %s\n", fshost, "Unknown host"); printed = 1; break; default: /* case ERR_PROTO_NONE falls through */ pr_err(gettext("%s: NFS service not responding" "\n"), fshost); printed = 1; break; } } SET_ERR_RET(error, addr_error.error_type, addr_error.error_value); if (addr_error.error_type == ERR_PROTO_NONE) return (RET_RETRY); else if (addr_error.error_type == ERR_RPCERROR && !IS_UNRECOVERABLE_RPC(addr_error.error_value)) { return (RET_RETRY); } else if (nfsvers == 0 && addr_error.error_type == ERR_PROTO_UNSUPP && nfsvers_to_use != NFS_VERSMIN) { /* * If no version is specified, and the error is due * to an unsupported transport, then decrement the * version and retry. */ return (RET_RETRY); } else return (RET_ERR); } nconf = *nconfp; if (stat(nconf->nc_device, &sb) < 0) { pr_err(gettext("getaddr_nfs: couldn't stat: %s: %s\n"), nconf->nc_device, strerror(errno)); return (RET_ERR); } knconfp = (struct knetconfig *)malloc(sizeof (*knconfp)); if (!knconfp) { pr_err(gettext("no memory\n")); return (RET_ERR); } knconfp->knc_semantics = nconf->nc_semantics; knconfp->knc_protofmly = nconf->nc_protofmly; knconfp->knc_proto = nconf->nc_proto; knconfp->knc_rdev = sb.st_rdev; /* make sure we don't overload the transport */ if (tinfo.tsdu > 0 && tinfo.tsdu < NFS_MAXDATA + NFS_RPC_HDR) { args->flags |= (NFSMNT_RSIZE | NFSMNT_WSIZE); if (args->rsize == 0 || args->rsize > tinfo.tsdu - NFS_RPC_HDR) args->rsize = tinfo.tsdu - NFS_RPC_HDR; if (args->wsize == 0 || args->wsize > tinfo.tsdu - NFS_RPC_HDR) args->wsize = tinfo.tsdu - NFS_RPC_HDR; } args->flags |= NFSMNT_KNCONF; args->knconf = knconfp; return (RET_OK); } static int retry(struct mnttab *mntp, int ro) { int delay = 5; int count = retries; int r; /* * Please see comments on nfsretry_vers in the beginning of this file * and in main() routine. */ if (bg) { if (fork() > 0) return (RET_OK); backgrounded = 1; pr_err(gettext("backgrounding: %s\n"), mntp->mnt_mountp); } else { if (!nfsretry_vers) pr_err(gettext("retrying: %s\n"), mntp->mnt_mountp); } while (count--) { if ((r = mount_nfs(mntp, ro, NULL)) == RET_OK) { pr_err(gettext("%s: mounted OK\n"), mntp->mnt_mountp); return (RET_OK); } if (r != RET_RETRY) break; if (count > 0) { (void) sleep(delay); delay *= 2; if (delay > 120) delay = 120; } } if (!nfsretry_vers) pr_err(gettext("giving up on: %s\n"), mntp->mnt_mountp); return (RET_ERR); } /* * Read the NFS SMF Parameters to determine if the * client has been configured for a new min/max for the NFS version to * use. */ static void read_default(void) { char value[4]; int errno; int tmp = 0, bufsz = 0, ret = 0; /* Maximum number of bytes expected. */ bufsz = 4; ret = nfs_smf_get_prop("client_versmin", value, DEFAULT_INSTANCE, SCF_TYPE_INTEGER, SVC_NFS_CLIENT, &bufsz); if (ret == SA_OK) { errno = 0; tmp = strtol(value, (char **)NULL, 10); if (errno == 0) { vers_min_default = tmp; } } /* Maximum number of bytes expected. */ bufsz = 4; ret = nfs_smf_get_prop("client_versmax", value, DEFAULT_INSTANCE, SCF_TYPE_INTEGER, SVC_NFS_CLIENT, &bufsz); if (ret == SA_OK) { errno = 0; tmp = strtol(value, (char **)NULL, 10); if (errno == 0) { vers_max_default = tmp; } } } static void sigusr1(int s) { } /* * Please do not edit this file. * It was generated using rpcgen. */ #ifndef _NFSMOUNT_WEBNFS_H_RPCGEN #define _NFSMOUNT_WEBNFS_H_RPCGEN #include #ifndef _KERNEL #include #include #endif /* !_KERNEL */ #ifdef __cplusplus extern "C" { #endif #define WNL_PORT 2049 #define WNL_MAXDATA 8192 #define WNL_MAXNAMLEN 255 #define WNL_FHSIZE 32 #define WNL_FIFO_DEV -1 #define WNL_NATIVEPATH 0x80 #define WNL_SEC_NEGO 0x81 #define WNLMODE_FMT 0170000 #define WNLMODE_DIR 0040000 #define WNLMODE_CHR 0020000 #define WNLMODE_BLK 0060000 #define WNLMODE_REG 0100000 #define WNLMODE_LNK 0120000 #define WNLMODE_SOCK 0140000 #define WNLMODE_FIFO 0010000 enum wnl_stat { WNL_OK = 0, WNLERR_PERM = 1, WNLERR_NOENT = 2, WNLERR_IO = 5, WNLERR_NXIO = 6, WNLERR_ACCES = 13, WNLERR_EXIST = 17, WNLERR_XDEV = 18, WNLERR_NODEV = 19, WNLERR_NOTDIR = 20, WNLERR_ISDIR = 21, WNLERR_INVAL = 22, WNLERR_FBIG = 27, WNLERR_NOSPC = 28, WNLERR_ROFS = 30, WNLERR_OPNOTSUPP = 45, WNLERR_NAMETOOLONG = 63, WNLERR_NOTEMPTY = 66, WNLERR_DQUOT = 69, WNLERR_STALE = 70, WNLERR_REMOTE = 71, WNLERR_WFLUSH = 72 }; typedef enum wnl_stat wnl_stat; enum wnl_ftype { WNL_NON = 0, WNL_REG = 1, WNL_DIR = 2, WNL_BLK = 3, WNL_CHR = 4, WNL_LNK = 5, WNL_SOCK = 6, WNL_BAD = 7, WNL_FIFO = 8 }; typedef enum wnl_ftype wnl_ftype; struct wnl_fh { char data[WNL_FHSIZE]; }; typedef struct wnl_fh wnl_fh; struct wnl_time { u_int seconds; u_int useconds; }; typedef struct wnl_time wnl_time; struct wnl_fattr { wnl_ftype type; u_int mode; u_int nlink; u_int uid; u_int gid; u_int size; u_int blocksize; u_int rdev; u_int blocks; u_int fsid; u_int fileid; wnl_time atime; wnl_time mtime; wnl_time ctime; }; typedef struct wnl_fattr wnl_fattr; typedef char *wnl_filename; struct wnl_diropargs { wnl_fh dir; wnl_filename name; }; typedef struct wnl_diropargs wnl_diropargs; struct wnl_diropokres { wnl_fh file; wnl_fattr attributes; }; typedef struct wnl_diropokres wnl_diropokres; struct wnl_diropres { wnl_stat status; union { wnl_diropokres wnl_diropres; } wnl_diropres_u; }; typedef struct wnl_diropres wnl_diropres; #define WNL3_FHSIZE 64 typedef u_longlong_t wnl_uint64; typedef longlong_t wnl_int64; typedef u_int wnl_uint32; typedef char *wnl_filename3; typedef wnl_uint64 wnl_fileid3; typedef wnl_uint32 wnl_uid3; typedef wnl_uint32 wnl_gid3; typedef wnl_uint64 wnl_size3; typedef wnl_uint32 wnl_mode3; enum wnl_stat3 { WNL3_OK = 0, WNL3ERR_PERM = 1, WNL3ERR_NOENT = 2, WNL3ERR_IO = 5, WNL3ERR_NXIO = 6, WNL3ERR_ACCES = 13, WNL3ERR_EXIST = 17, WNL3ERR_XDEV = 18, WNL3ERR_NODEV = 19, WNL3ERR_NOTDIR = 20, WNL3ERR_ISDIR = 21, WNL3ERR_INVAL = 22, WNL3ERR_FBIG = 27, WNL3ERR_NOSPC = 28, WNL3ERR_ROFS = 30, WNL3ERR_MLINK = 31, WNL3ERR_NAMETOOLONG = 63, WNL3ERR_NOTEMPTY = 66, WNL3ERR_DQUOT = 69, WNL3ERR_STALE = 70, WNL3ERR_REMOTE = 71, WNL3ERR_BADHANDLE = 10001, WNL3ERR_NOT_SYNC = 10002, WNL3ERR_BAD_COOKIE = 10003, WNL3ERR_NOTSUPP = 10004, WNL3ERR_TOOSMALL = 10005, WNL3ERR_SERVERFAULT = 10006, WNL3ERR_BADTYPE = 10007, WNL3ERR_JUKEBOX = 10008 }; typedef enum wnl_stat3 wnl_stat3; enum wnl_ftype3 { WNL_3REG = 1, WNL_3DIR = 2, WNL_3BLK = 3, WNL_3CHR = 4, WNL_3LNK = 5, WNL_3SOCK = 6, WNL_3FIFO = 7 }; typedef enum wnl_ftype3 wnl_ftype3; struct wnl_specdata3 { wnl_uint32 specdata1; wnl_uint32 specdata2; }; typedef struct wnl_specdata3 wnl_specdata3; struct wnl_fh3 { struct { u_int data_len; char *data_val; } data; }; typedef struct wnl_fh3 wnl_fh3; struct wnl_time3 { wnl_uint32 seconds; wnl_uint32 nseconds; }; typedef struct wnl_time3 wnl_time3; struct wnl_fattr3 { wnl_ftype3 type; wnl_mode3 mode; wnl_uint32 nlink; wnl_uid3 uid; wnl_gid3 gid; wnl_size3 size; wnl_size3 used; wnl_specdata3 rdev; wnl_uint64 fsid; wnl_fileid3 fileid; wnl_time3 atime; wnl_time3 mtime; wnl_time3 ctime; }; typedef struct wnl_fattr3 wnl_fattr3; struct wnl_post_op_attr { bool_t attributes_follow; union { wnl_fattr3 attributes; } wnl_post_op_attr_u; }; typedef struct wnl_post_op_attr wnl_post_op_attr; struct wln_post_op_fh3 { bool_t handle_follows; union { wnl_fh3 handle; } wln_post_op_fh3_u; }; typedef struct wln_post_op_fh3 wln_post_op_fh3; struct wnl_diropargs3 { wnl_fh3 dir; wnl_filename3 name; }; typedef struct wnl_diropargs3 wnl_diropargs3; struct WNL_LOOKUP3args { wnl_diropargs3 what; }; typedef struct WNL_LOOKUP3args WNL_LOOKUP3args; struct WNL_LOOKUP3resok { wnl_fh3 object; wnl_post_op_attr obj_attributes; wnl_post_op_attr dir_attributes; }; typedef struct WNL_LOOKUP3resok WNL_LOOKUP3resok; struct WNL_LOOKUP3resfail { wnl_post_op_attr dir_attributes; }; typedef struct WNL_LOOKUP3resfail WNL_LOOKUP3resfail; struct WNL_LOOKUP3res { wnl_stat3 status; union { WNL_LOOKUP3resok res_ok; WNL_LOOKUP3resfail res_fail; } WNL_LOOKUP3res_u; }; typedef struct WNL_LOOKUP3res WNL_LOOKUP3res; #define MAX_FLAVORS 128 struct snego_t { int cnt; int array[MAX_FLAVORS]; }; typedef struct snego_t snego_t; enum snego_stat { SNEGO_SUCCESS = 0, SNEGO_DEF_VALID = 1, SNEGO_ARRAY_TOO_SMALL = 2, SNEGO_FAILURE = 3 }; typedef enum snego_stat snego_stat; #define WNL_PROGRAM 100003 #define WNL_V2 2 #if defined(__STDC__) || defined(__cplusplus) #define WNLPROC_NULL 0 extern enum clnt_stat wnlproc_null_2(void *, void *, CLIENT *); extern bool_t wnlproc_null_2_svc(void *, void *, struct svc_req *); #define WNLPROC_LOOKUP 4 extern enum clnt_stat wnlproc_lookup_2(wnl_diropargs *, wnl_diropres *, CLIENT *); extern bool_t wnlproc_lookup_2_svc(wnl_diropargs *, wnl_diropres *, struct svc_req *); extern int wnl_program_2_freeresult(SVCXPRT *, xdrproc_t, caddr_t); #else /* K&R C */ #define WNLPROC_NULL 0 extern enum clnt_stat wnlproc_null_2(); extern bool_t wnlproc_null_2_svc(); #define WNLPROC_LOOKUP 4 extern enum clnt_stat wnlproc_lookup_2(); extern bool_t wnlproc_lookup_2_svc(); extern int wnl_program_2_freeresult(); #endif /* K&R C */ #define WNL_V3 3 #if defined(__STDC__) || defined(__cplusplus) #define WNLPROC3_NULL 0 extern enum clnt_stat wnlproc3_null_3(void *, void *, CLIENT *); extern bool_t wnlproc3_null_3_svc(void *, void *, struct svc_req *); #define WNLPROC3_LOOKUP 3 extern enum clnt_stat wnlproc3_lookup_3(WNL_LOOKUP3args *, WNL_LOOKUP3res *, CLIENT *); extern bool_t wnlproc3_lookup_3_svc(WNL_LOOKUP3args *, WNL_LOOKUP3res *, struct svc_req *); extern int wnl_program_3_freeresult(SVCXPRT *, xdrproc_t, caddr_t); #else /* K&R C */ #define WNLPROC3_NULL 0 extern enum clnt_stat wnlproc3_null_3(); extern bool_t wnlproc3_null_3_svc(); #define WNLPROC3_LOOKUP 3 extern enum clnt_stat wnlproc3_lookup_3(); extern bool_t wnlproc3_lookup_3_svc(); extern int wnl_program_3_freeresult(); #endif /* K&R C */ #define WNL_V4 4 #if defined(__STDC__) || defined(__cplusplus) #define WNLPROC4_NULL 0 extern enum clnt_stat wnlproc4_null_4(void *, void *, CLIENT *); extern bool_t wnlproc4_null_4_svc(void *, void *, struct svc_req *); extern int wnl_program_4_freeresult(SVCXPRT *, xdrproc_t, caddr_t); #else /* K&R C */ #define WNLPROC4_NULL 0 extern enum clnt_stat wnlproc4_null_4(); extern bool_t wnlproc4_null_4_svc(); extern int wnl_program_4_freeresult(); #endif /* K&R C */ /* the xdr functions */ #if defined(__STDC__) || defined(__cplusplus) extern bool_t xdr_wnl_stat(XDR *, wnl_stat*); extern bool_t xdr_wnl_ftype(XDR *, wnl_ftype*); extern bool_t xdr_wnl_fh(XDR *, wnl_fh*); extern bool_t xdr_wnl_time(XDR *, wnl_time*); extern bool_t xdr_wnl_fattr(XDR *, wnl_fattr*); extern bool_t xdr_wnl_filename(XDR *, wnl_filename*); extern bool_t xdr_wnl_diropargs(XDR *, wnl_diropargs*); extern bool_t xdr_wnl_diropokres(XDR *, wnl_diropokres*); extern bool_t xdr_wnl_diropres(XDR *, wnl_diropres*); extern bool_t xdr_wnl_uint64(XDR *, wnl_uint64*); extern bool_t xdr_wnl_int64(XDR *, wnl_int64*); extern bool_t xdr_wnl_uint32(XDR *, wnl_uint32*); extern bool_t xdr_wnl_filename3(XDR *, wnl_filename3*); extern bool_t xdr_wnl_fileid3(XDR *, wnl_fileid3*); extern bool_t xdr_wnl_uid3(XDR *, wnl_uid3*); extern bool_t xdr_wnl_gid3(XDR *, wnl_gid3*); extern bool_t xdr_wnl_size3(XDR *, wnl_size3*); extern bool_t xdr_wnl_mode3(XDR *, wnl_mode3*); extern bool_t xdr_wnl_stat3(XDR *, wnl_stat3*); extern bool_t xdr_wnl_ftype3(XDR *, wnl_ftype3*); extern bool_t xdr_wnl_specdata3(XDR *, wnl_specdata3*); extern bool_t xdr_wnl_fh3(XDR *, wnl_fh3*); extern bool_t xdr_wnl_time3(XDR *, wnl_time3*); extern bool_t xdr_wnl_fattr3(XDR *, wnl_fattr3*); extern bool_t xdr_wnl_post_op_attr(XDR *, wnl_post_op_attr*); extern bool_t xdr_wln_post_op_fh3(XDR *, wln_post_op_fh3*); extern bool_t xdr_wnl_diropargs3(XDR *, wnl_diropargs3*); extern bool_t xdr_WNL_LOOKUP3args(XDR *, WNL_LOOKUP3args*); extern bool_t xdr_WNL_LOOKUP3resok(XDR *, WNL_LOOKUP3resok*); extern bool_t xdr_WNL_LOOKUP3resfail(XDR *, WNL_LOOKUP3resfail*); extern bool_t xdr_WNL_LOOKUP3res(XDR *, WNL_LOOKUP3res*); extern bool_t xdr_snego_t(XDR *, snego_t*); extern bool_t xdr_snego_stat(XDR *, snego_stat*); #else /* K&R C */ extern bool_t xdr_wnl_stat(); extern bool_t xdr_wnl_ftype(); extern bool_t xdr_wnl_fh(); extern bool_t xdr_wnl_time(); extern bool_t xdr_wnl_fattr(); extern bool_t xdr_wnl_filename(); extern bool_t xdr_wnl_diropargs(); extern bool_t xdr_wnl_diropokres(); extern bool_t xdr_wnl_diropres(); extern bool_t xdr_wnl_uint64(); extern bool_t xdr_wnl_int64(); extern bool_t xdr_wnl_uint32(); extern bool_t xdr_wnl_filename3(); extern bool_t xdr_wnl_fileid3(); extern bool_t xdr_wnl_uid3(); extern bool_t xdr_wnl_gid3(); extern bool_t xdr_wnl_size3(); extern bool_t xdr_wnl_mode3(); extern bool_t xdr_wnl_stat3(); extern bool_t xdr_wnl_ftype3(); extern bool_t xdr_wnl_specdata3(); extern bool_t xdr_wnl_fh3(); extern bool_t xdr_wnl_time3(); extern bool_t xdr_wnl_fattr3(); extern bool_t xdr_wnl_post_op_attr(); extern bool_t xdr_wln_post_op_fh3(); extern bool_t xdr_wnl_diropargs3(); extern bool_t xdr_WNL_LOOKUP3args(); extern bool_t xdr_WNL_LOOKUP3resok(); extern bool_t xdr_WNL_LOOKUP3resfail(); extern bool_t xdr_WNL_LOOKUP3res(); extern bool_t xdr_snego_t(); extern bool_t xdr_snego_stat(); #endif /* K&R C */ #ifdef __cplusplus } #endif #endif /* !_NFSMOUNT_WEBNFS_H_RPCGEN */ /* * Please do not edit this file. * It was generated using rpcgen. */ #include /* for memset */ #include "webnfs.h" #ifndef _KERNEL #include #include /* getenv, exit */ #endif /* !_KERNEL */ /* Default timeout can be changed using clnt_control() */ static struct timeval TIMEOUT = { 25, 0 }; enum clnt_stat wnlproc_null_2(void *argp, void *clnt_res, CLIENT *clnt) { return (clnt_call(clnt, WNLPROC_NULL, (xdrproc_t)xdr_void, (caddr_t)argp, (xdrproc_t)xdr_void, (caddr_t)clnt_res, TIMEOUT)); } enum clnt_stat wnlproc_lookup_2(wnl_diropargs *argp, wnl_diropres *clnt_res, CLIENT *clnt) { return (clnt_call(clnt, WNLPROC_LOOKUP, (xdrproc_t)xdr_wnl_diropargs, (caddr_t)argp, (xdrproc_t)xdr_wnl_diropres, (caddr_t)clnt_res, TIMEOUT)); } enum clnt_stat wnlproc3_null_3(void *argp, void *clnt_res, CLIENT *clnt) { return (clnt_call(clnt, WNLPROC3_NULL, (xdrproc_t)xdr_void, (caddr_t)argp, (xdrproc_t)xdr_void, (caddr_t)clnt_res, TIMEOUT)); } enum clnt_stat wnlproc3_lookup_3(WNL_LOOKUP3args *argp, WNL_LOOKUP3res *clnt_res, CLIENT *clnt) { return (clnt_call(clnt, WNLPROC3_LOOKUP, (xdrproc_t)xdr_WNL_LOOKUP3args, (caddr_t)argp, (xdrproc_t)xdr_WNL_LOOKUP3res, (caddr_t)clnt_res, TIMEOUT)); } enum clnt_stat wnlproc4_null_4(void *argp, void *clnt_res, CLIENT *clnt) { return (clnt_call(clnt, WNLPROC4_NULL, (xdrproc_t)xdr_void, (caddr_t)argp, (xdrproc_t)xdr_void, (caddr_t)clnt_res, TIMEOUT)); } /* * Please do not edit this file. * It was generated using rpcgen. */ #include "webnfs.h" #ifndef _KERNEL #include #endif /* !_KERNEL */ bool_t xdr_wnl_stat(XDR *xdrs, wnl_stat *objp) { rpc_inline_t *buf __unused; if (!xdr_enum(xdrs, (enum_t *)objp)) return (FALSE); return (TRUE); } bool_t xdr_wnl_ftype(XDR *xdrs, wnl_ftype *objp) { rpc_inline_t *buf __unused; if (!xdr_enum(xdrs, (enum_t *)objp)) return (FALSE); return (TRUE); } bool_t xdr_wnl_fh(XDR *xdrs, wnl_fh *objp) { rpc_inline_t *buf __unused; if (!xdr_opaque(xdrs, objp->data, WNL_FHSIZE)) return (FALSE); return (TRUE); } bool_t xdr_wnl_time(XDR *xdrs, wnl_time *objp) { rpc_inline_t *buf __unused; if (!xdr_u_int(xdrs, &objp->seconds)) return (FALSE); if (!xdr_u_int(xdrs, &objp->useconds)) return (FALSE); return (TRUE); } bool_t xdr_wnl_fattr(XDR *xdrs, wnl_fattr *objp) { rpc_inline_t *buf __unused; if (xdrs->x_op == XDR_ENCODE) { if (!xdr_wnl_ftype(xdrs, &objp->type)) return (FALSE); buf = XDR_INLINE(xdrs, 10 * BYTES_PER_XDR_UNIT); if (buf == NULL) { if (!xdr_u_int(xdrs, &objp->mode)) return (FALSE); if (!xdr_u_int(xdrs, &objp->nlink)) return (FALSE); if (!xdr_u_int(xdrs, &objp->uid)) return (FALSE); if (!xdr_u_int(xdrs, &objp->gid)) return (FALSE); if (!xdr_u_int(xdrs, &objp->size)) return (FALSE); if (!xdr_u_int(xdrs, &objp->blocksize)) return (FALSE); if (!xdr_u_int(xdrs, &objp->rdev)) return (FALSE); if (!xdr_u_int(xdrs, &objp->blocks)) return (FALSE); if (!xdr_u_int(xdrs, &objp->fsid)) return (FALSE); if (!xdr_u_int(xdrs, &objp->fileid)) return (FALSE); } else { #if defined(_LP64) || defined(_KERNEL) IXDR_PUT_U_INT32(buf, objp->mode); IXDR_PUT_U_INT32(buf, objp->nlink); IXDR_PUT_U_INT32(buf, objp->uid); IXDR_PUT_U_INT32(buf, objp->gid); IXDR_PUT_U_INT32(buf, objp->size); IXDR_PUT_U_INT32(buf, objp->blocksize); IXDR_PUT_U_INT32(buf, objp->rdev); IXDR_PUT_U_INT32(buf, objp->blocks); IXDR_PUT_U_INT32(buf, objp->fsid); IXDR_PUT_U_INT32(buf, objp->fileid); #else IXDR_PUT_U_LONG(buf, objp->mode); IXDR_PUT_U_LONG(buf, objp->nlink); IXDR_PUT_U_LONG(buf, objp->uid); IXDR_PUT_U_LONG(buf, objp->gid); IXDR_PUT_U_LONG(buf, objp->size); IXDR_PUT_U_LONG(buf, objp->blocksize); IXDR_PUT_U_LONG(buf, objp->rdev); IXDR_PUT_U_LONG(buf, objp->blocks); IXDR_PUT_U_LONG(buf, objp->fsid); IXDR_PUT_U_LONG(buf, objp->fileid); #endif } if (!xdr_wnl_time(xdrs, &objp->atime)) return (FALSE); if (!xdr_wnl_time(xdrs, &objp->mtime)) return (FALSE); if (!xdr_wnl_time(xdrs, &objp->ctime)) return (FALSE); return (TRUE); } else if (xdrs->x_op == XDR_DECODE) { if (!xdr_wnl_ftype(xdrs, &objp->type)) return (FALSE); buf = XDR_INLINE(xdrs, 10 * BYTES_PER_XDR_UNIT); if (buf == NULL) { if (!xdr_u_int(xdrs, &objp->mode)) return (FALSE); if (!xdr_u_int(xdrs, &objp->nlink)) return (FALSE); if (!xdr_u_int(xdrs, &objp->uid)) return (FALSE); if (!xdr_u_int(xdrs, &objp->gid)) return (FALSE); if (!xdr_u_int(xdrs, &objp->size)) return (FALSE); if (!xdr_u_int(xdrs, &objp->blocksize)) return (FALSE); if (!xdr_u_int(xdrs, &objp->rdev)) return (FALSE); if (!xdr_u_int(xdrs, &objp->blocks)) return (FALSE); if (!xdr_u_int(xdrs, &objp->fsid)) return (FALSE); if (!xdr_u_int(xdrs, &objp->fileid)) return (FALSE); } else { #if defined(_LP64) || defined(_KERNEL) objp->mode = IXDR_GET_U_INT32(buf); objp->nlink = IXDR_GET_U_INT32(buf); objp->uid = IXDR_GET_U_INT32(buf); objp->gid = IXDR_GET_U_INT32(buf); objp->size = IXDR_GET_U_INT32(buf); objp->blocksize = IXDR_GET_U_INT32(buf); objp->rdev = IXDR_GET_U_INT32(buf); objp->blocks = IXDR_GET_U_INT32(buf); objp->fsid = IXDR_GET_U_INT32(buf); objp->fileid = IXDR_GET_U_INT32(buf); #else objp->mode = IXDR_GET_U_LONG(buf); objp->nlink = IXDR_GET_U_LONG(buf); objp->uid = IXDR_GET_U_LONG(buf); objp->gid = IXDR_GET_U_LONG(buf); objp->size = IXDR_GET_U_LONG(buf); objp->blocksize = IXDR_GET_U_LONG(buf); objp->rdev = IXDR_GET_U_LONG(buf); objp->blocks = IXDR_GET_U_LONG(buf); objp->fsid = IXDR_GET_U_LONG(buf); objp->fileid = IXDR_GET_U_LONG(buf); #endif } if (!xdr_wnl_time(xdrs, &objp->atime)) return (FALSE); if (!xdr_wnl_time(xdrs, &objp->mtime)) return (FALSE); if (!xdr_wnl_time(xdrs, &objp->ctime)) return (FALSE); return (TRUE); } if (!xdr_wnl_ftype(xdrs, &objp->type)) return (FALSE); if (!xdr_u_int(xdrs, &objp->mode)) return (FALSE); if (!xdr_u_int(xdrs, &objp->nlink)) return (FALSE); if (!xdr_u_int(xdrs, &objp->uid)) return (FALSE); if (!xdr_u_int(xdrs, &objp->gid)) return (FALSE); if (!xdr_u_int(xdrs, &objp->size)) return (FALSE); if (!xdr_u_int(xdrs, &objp->blocksize)) return (FALSE); if (!xdr_u_int(xdrs, &objp->rdev)) return (FALSE); if (!xdr_u_int(xdrs, &objp->blocks)) return (FALSE); if (!xdr_u_int(xdrs, &objp->fsid)) return (FALSE); if (!xdr_u_int(xdrs, &objp->fileid)) return (FALSE); if (!xdr_wnl_time(xdrs, &objp->atime)) return (FALSE); if (!xdr_wnl_time(xdrs, &objp->mtime)) return (FALSE); if (!xdr_wnl_time(xdrs, &objp->ctime)) return (FALSE); return (TRUE); } bool_t xdr_wnl_filename(XDR *xdrs, wnl_filename *objp) { rpc_inline_t *buf __unused; if (!xdr_string(xdrs, objp, WNL_MAXNAMLEN)) return (FALSE); return (TRUE); } bool_t xdr_wnl_diropargs(XDR *xdrs, wnl_diropargs *objp) { rpc_inline_t *buf __unused; if (!xdr_wnl_fh(xdrs, &objp->dir)) return (FALSE); if (!xdr_wnl_filename(xdrs, &objp->name)) return (FALSE); return (TRUE); } bool_t xdr_wnl_diropokres(XDR *xdrs, wnl_diropokres *objp) { rpc_inline_t *buf __unused; if (!xdr_wnl_fh(xdrs, &objp->file)) return (FALSE); if (!xdr_wnl_fattr(xdrs, &objp->attributes)) return (FALSE); return (TRUE); } bool_t xdr_wnl_diropres(XDR *xdrs, wnl_diropres *objp) { rpc_inline_t *buf __unused; if (!xdr_wnl_stat(xdrs, &objp->status)) return (FALSE); switch (objp->status) { case WNL_OK: if (!xdr_wnl_diropokres(xdrs, &objp->wnl_diropres_u.wnl_diropres)) return (FALSE); break; default: break; } return (TRUE); } bool_t xdr_wnl_uint64(XDR *xdrs, wnl_uint64 *objp) { rpc_inline_t *buf __unused; if (!xdr_u_longlong_t(xdrs, objp)) return (FALSE); return (TRUE); } bool_t xdr_wnl_int64(XDR *xdrs, wnl_int64 *objp) { rpc_inline_t *buf __unused; if (!xdr_longlong_t(xdrs, objp)) return (FALSE); return (TRUE); } bool_t xdr_wnl_uint32(XDR *xdrs, wnl_uint32 *objp) { rpc_inline_t *buf __unused; if (!xdr_u_int(xdrs, objp)) return (FALSE); return (TRUE); } bool_t xdr_wnl_filename3(XDR *xdrs, wnl_filename3 *objp) { rpc_inline_t *buf __unused; if (!xdr_string(xdrs, objp, ~0)) return (FALSE); return (TRUE); } bool_t xdr_wnl_fileid3(XDR *xdrs, wnl_fileid3 *objp) { rpc_inline_t *buf __unused; if (!xdr_wnl_uint64(xdrs, objp)) return (FALSE); return (TRUE); } bool_t xdr_wnl_uid3(XDR *xdrs, wnl_uid3 *objp) { rpc_inline_t *buf __unused; if (!xdr_wnl_uint32(xdrs, objp)) return (FALSE); return (TRUE); } bool_t xdr_wnl_gid3(XDR *xdrs, wnl_gid3 *objp) { rpc_inline_t *buf __unused; if (!xdr_wnl_uint32(xdrs, objp)) return (FALSE); return (TRUE); } bool_t xdr_wnl_size3(XDR *xdrs, wnl_size3 *objp) { rpc_inline_t *buf __unused; if (!xdr_wnl_uint64(xdrs, objp)) return (FALSE); return (TRUE); } bool_t xdr_wnl_mode3(XDR *xdrs, wnl_mode3 *objp) { rpc_inline_t *buf __unused; if (!xdr_wnl_uint32(xdrs, objp)) return (FALSE); return (TRUE); } bool_t xdr_wnl_stat3(XDR *xdrs, wnl_stat3 *objp) { rpc_inline_t *buf __unused; if (!xdr_enum(xdrs, (enum_t *)objp)) return (FALSE); return (TRUE); } bool_t xdr_wnl_ftype3(XDR *xdrs, wnl_ftype3 *objp) { rpc_inline_t *buf __unused; if (!xdr_enum(xdrs, (enum_t *)objp)) return (FALSE); return (TRUE); } bool_t xdr_wnl_specdata3(XDR *xdrs, wnl_specdata3 *objp) { rpc_inline_t *buf __unused; if (!xdr_wnl_uint32(xdrs, &objp->specdata1)) return (FALSE); if (!xdr_wnl_uint32(xdrs, &objp->specdata2)) return (FALSE); return (TRUE); } bool_t xdr_wnl_fh3(XDR *xdrs, wnl_fh3 *objp) { rpc_inline_t *buf __unused; if (!xdr_bytes(xdrs, (char **)&objp->data.data_val, (u_int *) &objp->data.data_len, WNL3_FHSIZE)) return (FALSE); return (TRUE); } bool_t xdr_wnl_time3(XDR *xdrs, wnl_time3 *objp) { rpc_inline_t *buf __unused; if (!xdr_wnl_uint32(xdrs, &objp->seconds)) return (FALSE); if (!xdr_wnl_uint32(xdrs, &objp->nseconds)) return (FALSE); return (TRUE); } bool_t xdr_wnl_fattr3(XDR *xdrs, wnl_fattr3 *objp) { rpc_inline_t *buf __unused; if (!xdr_wnl_ftype3(xdrs, &objp->type)) return (FALSE); if (!xdr_wnl_mode3(xdrs, &objp->mode)) return (FALSE); if (!xdr_wnl_uint32(xdrs, &objp->nlink)) return (FALSE); if (!xdr_wnl_uid3(xdrs, &objp->uid)) return (FALSE); if (!xdr_wnl_gid3(xdrs, &objp->gid)) return (FALSE); if (!xdr_wnl_size3(xdrs, &objp->size)) return (FALSE); if (!xdr_wnl_size3(xdrs, &objp->used)) return (FALSE); if (!xdr_wnl_specdata3(xdrs, &objp->rdev)) return (FALSE); if (!xdr_wnl_uint64(xdrs, &objp->fsid)) return (FALSE); if (!xdr_wnl_fileid3(xdrs, &objp->fileid)) return (FALSE); if (!xdr_wnl_time3(xdrs, &objp->atime)) return (FALSE); if (!xdr_wnl_time3(xdrs, &objp->mtime)) return (FALSE); if (!xdr_wnl_time3(xdrs, &objp->ctime)) return (FALSE); return (TRUE); } bool_t xdr_wnl_post_op_attr(XDR *xdrs, wnl_post_op_attr *objp) { rpc_inline_t *buf __unused; if (!xdr_bool(xdrs, &objp->attributes_follow)) return (FALSE); switch (objp->attributes_follow) { case TRUE: if (!xdr_wnl_fattr3(xdrs, &objp->wnl_post_op_attr_u.attributes)) return (FALSE); break; case FALSE: break; default: return (FALSE); } return (TRUE); } bool_t xdr_wln_post_op_fh3(XDR *xdrs, wln_post_op_fh3 *objp) { rpc_inline_t *buf __unused; if (!xdr_bool(xdrs, &objp->handle_follows)) return (FALSE); switch (objp->handle_follows) { case TRUE: if (!xdr_wnl_fh3(xdrs, &objp->wln_post_op_fh3_u.handle)) return (FALSE); break; case FALSE: break; default: return (FALSE); } return (TRUE); } bool_t xdr_wnl_diropargs3(XDR *xdrs, wnl_diropargs3 *objp) { rpc_inline_t *buf __unused; if (!xdr_wnl_fh3(xdrs, &objp->dir)) return (FALSE); if (!xdr_wnl_filename3(xdrs, &objp->name)) return (FALSE); return (TRUE); } bool_t xdr_WNL_LOOKUP3args(XDR *xdrs, WNL_LOOKUP3args *objp) { rpc_inline_t *buf __unused; if (!xdr_wnl_diropargs3(xdrs, &objp->what)) return (FALSE); return (TRUE); } bool_t xdr_WNL_LOOKUP3resok(XDR *xdrs, WNL_LOOKUP3resok *objp) { rpc_inline_t *buf __unused; if (!xdr_wnl_fh3(xdrs, &objp->object)) return (FALSE); if (!xdr_wnl_post_op_attr(xdrs, &objp->obj_attributes)) return (FALSE); if (!xdr_wnl_post_op_attr(xdrs, &objp->dir_attributes)) return (FALSE); return (TRUE); } bool_t xdr_WNL_LOOKUP3resfail(XDR *xdrs, WNL_LOOKUP3resfail *objp) { rpc_inline_t *buf __unused; if (!xdr_wnl_post_op_attr(xdrs, &objp->dir_attributes)) return (FALSE); return (TRUE); } bool_t xdr_WNL_LOOKUP3res(XDR *xdrs, WNL_LOOKUP3res *objp) { rpc_inline_t *buf __unused; if (!xdr_wnl_stat3(xdrs, &objp->status)) return (FALSE); switch (objp->status) { case WNL3_OK: if (!xdr_WNL_LOOKUP3resok(xdrs, &objp->WNL_LOOKUP3res_u.res_ok)) return (FALSE); break; default: if (!xdr_WNL_LOOKUP3resfail(xdrs, &objp->WNL_LOOKUP3res_u.res_fail)) return (FALSE); break; } return (TRUE); } bool_t xdr_snego_t(XDR *xdrs, snego_t *objp) { rpc_inline_t *buf __unused; int i; if (xdrs->x_op == XDR_ENCODE) { buf = XDR_INLINE(xdrs, (1 + (MAX_FLAVORS)) * BYTES_PER_XDR_UNIT); if (buf == NULL) { if (!xdr_int(xdrs, &objp->cnt)) return (FALSE); if (!xdr_vector(xdrs, (char *)objp->array, MAX_FLAVORS, sizeof (int), (xdrproc_t)xdr_int)) return (FALSE); } else { #if defined(_LP64) || defined(_KERNEL) IXDR_PUT_INT32(buf, objp->cnt); { int *genp; for (i = 0, genp = objp->array; i < MAX_FLAVORS; i++) { IXDR_PUT_INT32(buf, *genp++); } } #else IXDR_PUT_LONG(buf, objp->cnt); { int *genp; for (i = 0, genp = objp->array; i < MAX_FLAVORS; i++) { IXDR_PUT_LONG(buf, *genp++); } } #endif } return (TRUE); } else if (xdrs->x_op == XDR_DECODE) { buf = XDR_INLINE(xdrs, (1 + (MAX_FLAVORS)) * BYTES_PER_XDR_UNIT); if (buf == NULL) { if (!xdr_int(xdrs, &objp->cnt)) return (FALSE); if (!xdr_vector(xdrs, (char *)objp->array, MAX_FLAVORS, sizeof (int), (xdrproc_t)xdr_int)) return (FALSE); } else { #if defined(_LP64) || defined(_KERNEL) objp->cnt = IXDR_GET_INT32(buf); { int *genp; for (i = 0, genp = objp->array; i < MAX_FLAVORS; i++) { *genp++ = IXDR_GET_INT32(buf); } } #else objp->cnt = IXDR_GET_LONG(buf); { int *genp; for (i = 0, genp = objp->array; i < MAX_FLAVORS; i++) { *genp++ = IXDR_GET_LONG(buf); } } #endif } return (TRUE); } if (!xdr_int(xdrs, &objp->cnt)) return (FALSE); if (!xdr_vector(xdrs, (char *)objp->array, MAX_FLAVORS, sizeof (int), (xdrproc_t)xdr_int)) return (FALSE); return (TRUE); } bool_t xdr_snego_stat(XDR *xdrs, snego_stat *objp) { rpc_inline_t *buf __unused; if (!xdr_enum(xdrs, (enum_t *)objp)) return (FALSE); return (TRUE); } # # 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 (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved. # # Copyright (c) 2018, Joyent, Inc. # Copyright 2022 RackTop Systems. FSTYPE = nfs TYPEPROG = mountd ATTMK = $(TYPEPROG) include ../../Makefile.fstype COMMON = nfs_sec.o sharetab.o daemon.o smfcfg.o LOCAL = mountd.o netgroup.o rmtab.o nfsauth.o \ nfsauth_xdr.o exportlist.o hashset.o nfs_cmd.o OBJS = $(FSLIB) $(LOCAL) $(COMMON) SRCS = $(LOCAL:%.o=%.c) $(FSLIBSRC) ../lib/nfs_sec.c \ ../lib/sharetab.c ../lib/daemon.c ../lib/smfcfg.c DSRC = mountd_dt.d DOBJ = $(DSRC:%.d=%.o) LDLIBS += -lrpcsvc -lnsl -lbsm -lsocket -linetutil -ltsnet -ltsol LDLIBS += -lnvpair -lscf -lumem CPPFLAGS += -D_REENTRANT -I../lib CERRWARN += $(CNOWARN_UNINIT) # unreachable code in mountd.c is to please the C compiler. mountd.o : SMOFF += check_unreachable $(TYPEPROG): $(OBJS) $(COMPILE.d) -s $(DSRC) -o $(DOBJ) $(OBJS) $(LINK.c) -o $@ $(DOBJ) $(OBJS) $(LDLIBS) $(POST_PROCESS) nfs_sec.o: ../lib/nfs_sec.c $(COMPILE.c) ../lib/nfs_sec.c sharetab.o: ../lib/sharetab.c $(COMPILE.c) ../lib/sharetab.c daemon.o: ../lib/daemon.c $(COMPILE.c) ../lib/daemon.c smfcfg.o: ../lib/smfcfg.c $(COMPILE.c) ../lib/smfcfg.c clean: $(RM) $(OBJS) $(DOBJ) /* * 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 2007 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 "../lib/sharetab.h" #include "mountd.h" static void freeexports(struct exportnode *); static struct groupnode **newgroup(char *, struct groupnode **); static struct exportnode **newexport(char *, struct groupnode *, struct exportnode **); static char *optlist[] = { #define OPT_RO 0 SHOPT_RO, #define OPT_RW 1 SHOPT_RW, NULL }; /* * Send current export list to a client */ void export(struct svc_req *rqstp) { SVCXPRT *transp; struct exportnode *exportlist; struct exportnode **tail; struct groupnode *groups; struct groupnode **grtail; struct share *sh; struct sh_list *shp; char *gr, *p, *opts, *val, *lasts; int export_to_everyone; transp = rqstp->rq_xprt; if (!svc_getargs(transp, xdr_void, NULL)) { svcerr_decode(transp); return; } check_sharetab(); exportlist = NULL; tail = &exportlist; (void) rw_rdlock(&sharetab_lock); for (shp = share_list; shp; shp = shp->shl_next) { groups = NULL; grtail = &groups; sh = shp->shl_sh; /* * Check for "ro" or "rw" list without argument values. This * indicates export to everyone. Unfortunately, SunOS 4.x * automounter uses this, and it is indicated indirectly with * 'showmount -e'. * * If export_to_everyone is 1, then groups should be NULL to * indicate export to everyone. */ opts = strdup(sh->sh_opts); p = opts; export_to_everyone = 0; while (*p) { switch (getsubopt(&p, optlist, &val)) { case OPT_RO: case OPT_RW: if (val == NULL) export_to_everyone = 1; break; } } free(opts); if (export_to_everyone == 0) { opts = strdup(sh->sh_opts); p = opts; /* * Just concatenate all the hostnames/groups * from the "ro" and "rw" lists for each flavor. * This list is rather meaningless now, but * that's what the protocol demands. */ while (*p) { switch (getsubopt(&p, optlist, &val)) { case OPT_RO: case OPT_RW: while ((gr = strtok_r(val, ":", &lasts)) != NULL) { val = NULL; grtail = newgroup(gr, grtail); } break; } } free(opts); } tail = newexport(sh->sh_path, groups, tail); } (void) rw_unlock(&sharetab_lock); errno = 0; if (!svc_sendreply(transp, xdr_exports, (char *)&exportlist)) log_cant_reply(transp); freeexports(exportlist); } static void freeexports(struct exportnode *ex) { struct groupnode *groups, *tmpgroups; struct exportnode *tmpex; while (ex) { groups = ex->ex_groups; while (groups) { tmpgroups = groups->gr_next; free(groups->gr_name); free(groups); groups = tmpgroups; } tmpex = ex->ex_next; free(ex->ex_dir); free(ex); ex = tmpex; } } static struct groupnode ** newgroup(char *grname, struct groupnode **tail) { struct groupnode *new; char *newname; new = exmalloc(sizeof (*new)); newname = exmalloc(strlen(grname) + 1); (void) strcpy(newname, grname); new->gr_name = newname; new->gr_next = NULL; *tail = new; return (&new->gr_next); } static struct exportnode ** newexport(char *grname, struct groupnode *grplist, struct exportnode **tail) { struct exportnode *new; char *newname; new = exmalloc(sizeof (*new)); newname = exmalloc(strlen(grname) + 1); (void) strcpy(newname, grname); new->ex_dir = newname; new->ex_groups = grplist; new->ex_next = NULL; *tail = new; return (&new->ex_next); } /* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include "hashset.h" #include "mountd.h" #include /* * HASHSET is hash table managing pointers to a set of keys * (set is a collection without duplicates). The public interface * of the HASHSET is similar to the java.util.Set interface. * Unlike the libc `hsearch' based hash table, this implementation * does allow multiple instances of HASHSET within a single application, * and the HASHSET_ITERATOR allows to iterate through the entire set * using h_next(). * * HASHSET does not store actual keys but only pointers to keys. Hence the * data remains intact when HASHSET grows (resizes itself). HASHSET accesses * the actual key data only through the hash and equal functions given * as arguments to h_create. * * Hash collisions are resolved with linked lists. */ typedef struct HashSetEntry { uint_t e_hash; /* Hash value */ const void *e_key; /* Pointer to a key */ struct HashSetEntry *e_next; } ENTRY; struct HashSet { ENTRY **h_table; /* Pointer to an array of ENTRY'ies */ uint_t h_tableSize; /* Size of the array */ uint_t h_count; /* Current count */ uint_t h_threshold; /* loadFactor threshold */ float h_loadFactor; /* Current loadFactor (h_count/h_tableSize( */ uint_t (*h_hash) (const void *); int (*h_equal) (const void *, const void *); }; struct HashSetIterator { HASHSET i_h; uint_t i_indx; ENTRY *i_e; uint_t i_coll; }; static void rehash(HASHSET h); #define DEFAULT_INITIALCAPACITY 1 #define DEFAULT_LOADFACTOR 0.75 /* * Create a HASHSET * - HASHSET is a hash table of pointers to keys * - duplicate keys are not allowed * - the HASHSET is opaque and can be accessed only through the h_ functions * - two keys k1 and k2 are considered equal if the result of equal(k1, k2) * is non-zero * - the function hash(key) is used to compute hash values for keys; if * keys are "equal" the values returned by the hash function must be * identical. */ HASHSET h_create(uint_t (*hash) (const void *), int (*equal) (const void *, const void *), uint_t initialCapacity, float loadFactor) { HASHSET h; if (initialCapacity == 0) initialCapacity = DEFAULT_INITIALCAPACITY; if (loadFactor < 0.0) loadFactor = DEFAULT_LOADFACTOR; h = exmalloc(sizeof (*h)); if (h == NULL) return (NULL); h->h_table = exmalloc(initialCapacity * sizeof (ENTRY *)); (void) memset(h->h_table, 0, initialCapacity * sizeof (ENTRY *)); if (h->h_table == NULL) { free(h); return (NULL); } h->h_tableSize = initialCapacity; h->h_hash = hash; h->h_equal = equal; h->h_loadFactor = loadFactor; h->h_threshold = (uint_t)(initialCapacity * loadFactor); h->h_count = 0; return (h); } /* * Return a pointer to a matching key */ const void * h_get(const HASHSET h, void *key) { uint_t hash = h->h_hash(key); uint_t i = hash % h->h_tableSize; ENTRY *e; for (e = h->h_table[i]; e; e = e->e_next) if (e->e_hash == hash && h->h_equal(e->e_key, key)) return (e->e_key); return (NULL); } /* * Rehash (grow) HASHSET * - called when loadFactor exceeds threshold * - new size is 2*old_size+1 */ static void rehash(HASHSET h) { uint_t i = h->h_tableSize; uint_t newtabSize = 2 * i + 1; ENTRY **newtab = exmalloc(newtabSize * sizeof (ENTRY *)); (void) memset(newtab, 0, newtabSize * sizeof (ENTRY *)); while (i--) { ENTRY *e, *next; for (e = h->h_table[i]; e; e = next) { uint_t k = e->e_hash % newtabSize; next = (ENTRY *) e->e_next; e->e_next = (ENTRY *) newtab[k]; newtab[k] = e; } } h->h_threshold = (uint_t)(newtabSize * h->h_loadFactor); h->h_tableSize = newtabSize; free(h->h_table); h->h_table = newtab; } /* * Store a key into a HASHSET * - if there is already an "equal" key then the new key will not * be stored and the function returns a pointer to an existing key * - otherwise the function stores pointer to the new key and return NULL */ const void * h_put(HASHSET h, const void *key) { uint_t hash = h->h_hash(key); uint_t indx = hash % h->h_tableSize; ENTRY *e; for (e = h->h_table[indx]; e; e = e->e_next) if (e->e_hash == hash && h->h_equal(e->e_key, key)) return (key); if (h->h_count >= h->h_threshold) { rehash(h); indx = hash % h->h_tableSize; } e = exmalloc(sizeof (ENTRY)); e->e_hash = hash; e->e_key = (void *) key; e->e_next = h->h_table[indx]; h->h_table[indx] = e; h->h_count++; DTRACE_PROBE2(mountd, hashset, h->h_count, h->h_loadFactor); return (NULL); } /* * Delete a key * - if there is no "equal" key in the HASHSET the fuction returns NULL * - otherwise the function returns a pointer to the deleted key */ const void * h_delete(HASHSET h, const void *key) { uint_t hash = h->h_hash(key); uint_t indx = hash % h->h_tableSize; ENTRY *e, *prev; for (e = h->h_table[indx], prev = NULL; e; prev = e, e = e->e_next) { if (e->e_hash == hash && h->h_equal(e->e_key, key)) { key = e->e_key; if (prev) prev->e_next = e->e_next; else h->h_table[indx] = e->e_next; free(e); return (key); } } return (NULL); } /* * Return an opaque HASHSET_ITERATOR (to be used in h_next()) */ HASHSET_ITERATOR h_iterator(HASHSET h) { HASHSET_ITERATOR i = exmalloc(sizeof (*i)); i->i_h = h; i->i_indx = h->h_tableSize; i->i_e = NULL; i->i_coll = 0; return (i); } /* * Return a pointer to a next key */ const void * h_next(HASHSET_ITERATOR i) { const void *key; while (i->i_e == NULL) { if (i->i_indx-- == 0) return (NULL); i->i_e = i->i_h->h_table[i->i_indx]; } key = i->i_e->e_key; i->i_e = i->i_e->e_next; if (i->i_e) i->i_coll++; return (key); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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) 1999, by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _HASHSET_H #define _HASHSET_H #include #ifdef __cplusplus extern "C" { #endif typedef struct HashSet *HASHSET; typedef struct HashSetIterator *HASHSET_ITERATOR; extern HASHSET h_create(uint_t (*hash) (const void *), int (*equal) (const void *, const void *), uint_t initialCapacity, float loadFactor); extern const void *h_get(const HASHSET h, void *key); extern const void *h_put(HASHSET h, const void *key); extern const void *h_delete(HASHSET h, const void *key); extern HASHSET_ITERATOR h_iterator(HASHSET h); extern const void *h_next(HASHSET_ITERATOR i); #ifdef __cplusplus } #endif #endif /* _HASHSET_H */ /* * 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) 1989, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012, 2016 by Delphix. All rights reserved. * Copyright 2016 Nexenta Systems, Inc. All rights reserved. * Copyright 2022 RackTop Systems. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Portions of this source code were derived from Berkeley 4.3 BSD * under license from the Regents of the University of California. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../../fslib.h" #include #include #include "../lib/sharetab.h" #include "mountd.h" #include #include #include #include #include #include #include #include #include "smfcfg.h" #include #include #include #include #include extern int daemonize_init(void); extern void daemonize_fini(int); extern int _nfssys(int, void *); struct sh_list *share_list; rwlock_t sharetab_lock; /* lock to protect the cached sharetab */ static mutex_t mnttab_lock; /* prevent concurrent mnttab readers */ static mutex_t logging_queue_lock; static cond_t logging_queue_cv; static share_t *find_lofsentry(char *, int *); static int getclientsflavors_old(share_t *, struct cln *, int *); static int getclientsflavors_new(share_t *, struct cln *, int *); static int check_client_old(share_t *, struct cln *, int, uid_t, gid_t, uint_t, gid_t *, uid_t *, gid_t *, uint_t *, gid_t **); static int check_client_new(share_t *, struct cln *, int, uid_t, gid_t, uint_t, gid_t *, uid_t *, gid_t *i, uint_t *, gid_t **); static void mnt(struct svc_req *, SVCXPRT *); static void mnt_pathconf(struct svc_req *); static int mount(struct svc_req *r); static void sh_free(struct sh_list *); static void umount(struct svc_req *); static void umountall(struct svc_req *); static int newopts(char *); static tsol_tpent_t *get_client_template(struct sockaddr *); static int debug; static int verbose; static int rejecting; static int mount_vers_min = MOUNTVERS; static int mount_vers_max = MOUNTVERS3; static int mountd_port = 0; static boolean_t mountd_remote_dump = B_FALSE; extern void nfscmd_func(void *, char *, size_t, door_desc_t *, uint_t); thread_t nfsauth_thread; thread_t cmd_thread; thread_t logging_thread; typedef struct logging_data { char *ld_host; char *ld_path; char *ld_rpath; int ld_status; char *ld_netid; struct netbuf *ld_nb; struct logging_data *ld_next; } logging_data; static logging_data *logging_head = NULL; static logging_data *logging_tail = NULL; /* * Our copy of some system variables obtained using sysconf(3c) */ static long ngroups_max; /* _SC_NGROUPS_MAX */ static long pw_size; /* _SC_GETPW_R_SIZE_MAX */ /* Cached address info for this host. */ static struct addrinfo *host_ai = NULL; static void * nfsauth_svc(void *arg __unused) { int doorfd = -1; uint_t darg; #ifdef DEBUG int dfd; #endif if ((doorfd = door_create(nfsauth_func, NULL, DOOR_REFUSE_DESC | DOOR_NO_CANCEL)) == -1) { syslog(LOG_ERR, "Unable to create door: %m\n"); exit(10); } #ifdef DEBUG /* * Create a file system path for the door */ if ((dfd = open(MOUNTD_DOOR, O_RDWR|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) == -1) { syslog(LOG_ERR, "Unable to open %s: %m\n", MOUNTD_DOOR); (void) close(doorfd); exit(11); } /* * Clean up any stale namespace associations */ (void) fdetach(MOUNTD_DOOR); /* * Register in namespace to pass to the kernel to door_ki_open */ if (fattach(doorfd, MOUNTD_DOOR) == -1) { syslog(LOG_ERR, "Unable to fattach door: %m\n"); (void) close(dfd); (void) close(doorfd); exit(12); } (void) close(dfd); #endif /* * Must pass the doorfd down to the kernel. */ darg = doorfd; (void) _nfssys(MOUNTD_ARGS, &darg); /* * Wait for incoming calls */ /*CONSTCOND*/ for (;;) (void) pause(); /*NOTREACHED*/ syslog(LOG_ERR, gettext("Door server exited")); return (NULL); } /* * NFS command service thread code for setup and handling of the * nfs_cmd requests for character set conversion and other future * events. */ static void * cmd_svc(void *arg) { int doorfd = -1; uint_t darg; if ((doorfd = door_create(nfscmd_func, NULL, DOOR_REFUSE_DESC | DOOR_NO_CANCEL)) == -1) { syslog(LOG_ERR, "Unable to create cmd door: %m\n"); exit(10); } /* * Must pass the doorfd down to the kernel. */ darg = doorfd; (void) _nfssys(NFSCMD_ARGS, &darg); /* * Wait for incoming calls */ /*CONSTCOND*/ for (;;) (void) pause(); /*NOTREACHED*/ syslog(LOG_ERR, gettext("Cmd door server exited")); return (NULL); } static void free_logging_data(logging_data *lq) { if (lq != NULL) { free(lq->ld_host); free(lq->ld_netid); if (lq->ld_nb != NULL) { free(lq->ld_nb->buf); free(lq->ld_nb); } free(lq->ld_path); free(lq->ld_rpath); free(lq); } } static logging_data * remove_head_of_queue(void) { logging_data *lq; /* * Pull it off the queue. */ lq = logging_head; if (lq) { logging_head = lq->ld_next; /* * Drained it. */ if (logging_head == NULL) { logging_tail = NULL; } } return (lq); } static void do_logging_queue(logging_data *lq) { int cleared = 0; char *host; while (lq) { struct cln cln; if (lq->ld_host == NULL) { DTRACE_PROBE(mountd, name_by_lazy); cln_init_lazy(&cln, lq->ld_netid, lq->ld_nb); host = cln_gethost(&cln); } else host = lq->ld_host; audit_mountd_mount(host, lq->ld_path, lq->ld_status); /* BSM */ /* add entry to mount list */ if (lq->ld_rpath) mntlist_new(host, lq->ld_rpath); if (lq->ld_host == NULL) cln_fini(&cln); free_logging_data(lq); cleared++; (void) mutex_lock(&logging_queue_lock); lq = remove_head_of_queue(); (void) mutex_unlock(&logging_queue_lock); } DTRACE_PROBE1(mountd, logging_cleared, cleared); } static void * logging_svc(void *arg __unused) { logging_data *lq; for (;;) { (void) mutex_lock(&logging_queue_lock); while (logging_head == NULL) { (void) cond_wait(&logging_queue_cv, &logging_queue_lock); } lq = remove_head_of_queue(); (void) mutex_unlock(&logging_queue_lock); do_logging_queue(lq); } /*NOTREACHED*/ syslog(LOG_ERR, gettext("Logging server exited")); return (NULL); } /* * This function is called for each configured network type to * bind and register our RPC service programs. * * On TCP or UDP, we may want to bind MOUNTPROG on a specific port * (when mountd_port is specified) in which case we'll use the * variant of svc_tp_create() that lets us pass a bind address. */ static void md_svc_tp_create(struct netconfig *nconf) { char port_str[8]; struct nd_hostserv hs; struct nd_addrlist *al = NULL; SVCXPRT *xprt = NULL; rpcvers_t vers; vers = mount_vers_max; /* * If mountd_port is set and this is an inet transport, * bind this service on the specified port. The TLI way * to create such a bind address is netdir_getbyname() * with the special "host" HOST_SELF_BIND. This builds * an all-zeros IP address with the specified port. */ if (mountd_port != 0 && (strcmp(nconf->nc_protofmly, NC_INET) == 0 || strcmp(nconf->nc_protofmly, NC_INET6) == 0)) { int err; (void) snprintf(port_str, sizeof (port_str), "%u", (unsigned short)mountd_port); hs.h_host = HOST_SELF_BIND; hs.h_serv = port_str; err = netdir_getbyname((struct netconfig *)nconf, &hs, &al); if (err == 0 && al != NULL) { xprt = svc_tp_create_addr(mnt, MOUNTPROG, vers, nconf, al->n_addrs); netdir_free(al, ND_ADDRLIST); } if (xprt == NULL) { syslog(LOG_ERR, "mountd: unable to create " "(MOUNTD,%d) on transport %s (port %d)", vers, nconf->nc_netid, mountd_port); } /* fall-back to default bind */ } if (xprt == NULL) { /* * Had mountd_port=0, or non-inet transport, * or the bind to a specific port failed. * Do a default bind. */ xprt = svc_tp_create(mnt, MOUNTPROG, vers, nconf); } if (xprt == NULL) { syslog(LOG_ERR, "mountd: unable to create " "(MOUNTD,%d) on transport %s", vers, nconf->nc_netid); return; } /* * Register additional versions on this transport. */ while (--vers >= mount_vers_min) { if (!svc_reg(xprt, MOUNTPROG, vers, mnt, nconf)) { (void) syslog(LOG_ERR, "mountd: " "failed to register vers %d on %s", vers, nconf->nc_netid); } } } int main(int argc, char *argv[]) { int pid; int c; int rpc_svc_fdunlim = 1; int rpc_svc_mode = RPC_SVC_MT_AUTO; int maxrecsz = RPC_MAXDATASIZE; bool_t exclbind = TRUE; bool_t can_do_mlp; long thr_flags = (THR_NEW_LWP|THR_DAEMON); char defval[5]; int ret, bufsz; struct rlimit rl; int listen_backlog = 0; int max_threads = 0; int tmp; struct netconfig *nconf; NCONF_HANDLE *nc; const char *errstr; int pipe_fd = -1; char hostbuf[256]; /* * Mountd requires uid 0 for: * /etc/rmtab updates (we could chown it to daemon) * /etc/dfs/dfstab reading (it wants to lock out share which * doesn't do any locking before first truncate; * NFS share does; should use fcntl locking instead) * Needed privileges: * auditing * nfs syscall * file dac search (so it can stat all files) * Optional privileges: * MLP */ can_do_mlp = priv_ineffect(PRIV_NET_BINDMLP); if (__init_daemon_priv(PU_RESETGROUPS|PU_CLEARLIMITSET, -1, -1, PRIV_SYS_NFS, PRIV_PROC_AUDIT, PRIV_FILE_DAC_SEARCH, PRIV_NET_PRIVADDR, can_do_mlp ? PRIV_NET_BINDMLP : NULL, NULL) == -1) { (void) fprintf(stderr, "%s: must be run with sufficient privileges\n", argv[0]); exit(1); } if (getrlimit(RLIMIT_NOFILE, &rl) != 0) { syslog(LOG_ERR, "getrlimit failed"); } else { rl.rlim_cur = rl.rlim_max; if (setrlimit(RLIMIT_NOFILE, &rl) != 0) syslog(LOG_ERR, "setrlimit failed"); } (void) enable_extended_FILE_stdio(-1, -1); /* Upgrade SMF settings, if necessary. */ nfs_config_upgrade(NFSD); ret = nfs_smf_get_iprop("mountd_max_threads", &max_threads, DEFAULT_INSTANCE, SCF_TYPE_INTEGER, NFSD); if (ret != SA_OK) { syslog(LOG_ERR, "Reading of mountd_max_threads from SMF " "failed, using default value"); } ret = nfs_smf_get_iprop("mountd_port", &mountd_port, DEFAULT_INSTANCE, SCF_TYPE_INTEGER, NFSD); if (ret != SA_OK) { syslog(LOG_ERR, "Reading of mountd_port from SMF " "failed, using default value"); } while ((c = getopt(argc, argv, "dvrm:p:")) != EOF) { switch (c) { case 'd': debug++; break; case 'v': verbose++; break; case 'r': rejecting = 1; break; case 'm': tmp = strtonum(optarg, 1, INT_MAX, &errstr); if (errstr != NULL) { (void) fprintf(stderr, "%s: invalid " "max_threads option: %s, using defaults\n", argv[0], errstr); break; } max_threads = tmp; break; case 'p': tmp = strtonum(optarg, 1, UINT16_MAX, &errstr); if (errstr != NULL) { (void) fprintf(stderr, "%s: invalid port " "number: %s\n", argv[0], errstr); break; } mountd_port = tmp; break; default: fprintf(stderr, "usage: mountd [-v] [-r]\n"); exit(1); } } /* * One might be tempted to use the NFS configuration values * server_versmin, server_versmax to constrain the range of * mountd versions supported here. However, older clients * use mountd V1 for showmount, so just leave all mountd * versions enabled here. (mount_vers_min, mount_vers_max) */ ret = nfs_smf_get_iprop("mountd_listen_backlog", &listen_backlog, DEFAULT_INSTANCE, SCF_TYPE_INTEGER, NFSD); if (ret != SA_OK) { syslog(LOG_ERR, "Reading of mountd_listen_backlog from SMF " "failed, using default value"); } bufsz = sizeof (defval); ret = nfs_smf_get_prop("mountd_remote_dump", defval, DEFAULT_INSTANCE, SCF_TYPE_BOOLEAN, NFSD, &bufsz); if (ret == SA_OK) { mountd_remote_dump = string_to_boolean(defval); } if (!mountd_remote_dump) { /* Cache host address list */ if (gethostname(hostbuf, sizeof (hostbuf)) < 0) { syslog(LOG_ERR, "gethostname() failed"); exit(1); } if (getaddrinfo(hostbuf, NULL, NULL, &host_ai) != 0) { syslog(LOG_ERR, "getaddrinfo() failed"); exit(1); } } /* * Sanity check versions, * even though we may get versions > MOUNTVERS3, we still need * to start nfsauth service, so continue on regardless of values. */ if (mount_vers_max > MOUNTVERS3) mount_vers_max = MOUNTVERS3; if (mount_vers_min > mount_vers_max) { fprintf(stderr, "server_versmin > server_versmax\n"); mount_vers_max = mount_vers_min; } (void) setlocale(LC_ALL, ""); (void) rwlock_init(&sharetab_lock, USYNC_THREAD, NULL); (void) mutex_init(&mnttab_lock, USYNC_THREAD, NULL); (void) mutex_init(&logging_queue_lock, USYNC_THREAD, NULL); (void) cond_init(&logging_queue_cv, USYNC_THREAD, NULL); netgroup_init(); #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif (void) textdomain(TEXT_DOMAIN); /* Don't drop core if the NFS module isn't loaded. */ (void) signal(SIGSYS, SIG_IGN); if (!debug) pipe_fd = daemonize_init(); /* * If we coredump it'll be in /core */ if (chdir("/") < 0) fprintf(stderr, "chdir /: %s\n", strerror(errno)); if (!debug) openlog("mountd", LOG_PID, LOG_DAEMON); /* * establish our lock on the lock file and write our pid to it. * exit if some other process holds the lock, or if there's any * error in writing/locking the file. */ pid = _enter_daemon_lock(MOUNTD); switch (pid) { case 0: break; case -1: fprintf(stderr, "error locking for %s: %s\n", MOUNTD, strerror(errno)); exit(2); default: /* daemon was already running */ exit(0); } audit_mountd_setup(); /* BSM */ /* * Get required system variables */ if ((ngroups_max = sysconf(_SC_NGROUPS_MAX)) == -1) { syslog(LOG_ERR, "Unable to get _SC_NGROUPS_MAX"); exit(1); } if ((pw_size = sysconf(_SC_GETPW_R_SIZE_MAX)) == -1) { syslog(LOG_ERR, "Unable to get _SC_GETPW_R_SIZE_MAX"); exit(1); } /* * Set number of file descriptors to unlimited */ if (!rpc_control(RPC_SVC_USE_POLLFD, &rpc_svc_fdunlim)) { syslog(LOG_INFO, "unable to set number of FDs to unlimited"); } /* * Tell RPC that we want automatic thread mode. * A new thread will be spawned for each request. */ if (!rpc_control(RPC_SVC_MTMODE_SET, &rpc_svc_mode)) { fprintf(stderr, "unable to set automatic MT mode\n"); exit(1); } /* * Enable non-blocking mode and maximum record size checks for * connection oriented transports. */ if (!rpc_control(RPC_SVC_CONNMAXREC_SET, &maxrecsz)) { fprintf(stderr, "unable to set RPC max record size\n"); } /* * Prevent our non-priv udp and tcp ports bound w/wildcard addr * from being hijacked by a bind to a more specific addr. */ if (!rpc_control(__RPC_SVC_EXCLBIND_SET, &exclbind)) { fprintf(stderr, "warning: unable to set udp/tcp EXCLBIND\n"); } /* * Set the maximum number of outstanding connection * indications (listen backlog) to the value specified. */ if (listen_backlog > 0 && !rpc_control(__RPC_SVC_LSTNBKLOG_SET, &listen_backlog)) { fprintf(stderr, "unable to set listen backlog\n"); exit(1); } /* * If max_threads was specified, then set the * maximum number of threads to the value specified. */ if (max_threads > 0 && !rpc_control(RPC_SVC_THRMAX_SET, &max_threads)) { fprintf(stderr, "unable to set max_threads\n"); exit(1); } if (mountd_port < 0 || mountd_port > UINT16_MAX) { fprintf(stderr, "unable to use specified port\n"); exit(1); } /* * Make sure to unregister any previous versions in case the * user is reconfiguring the server in interesting ways. */ svc_unreg(MOUNTPROG, MOUNTVERS); svc_unreg(MOUNTPROG, MOUNTVERS_POSIX); svc_unreg(MOUNTPROG, MOUNTVERS3); /* * Create the nfsauth thread with same signal disposition * as the main thread. We need to create a separate thread * since mountd() will be both an RPC server (for remote * traffic) _and_ a doors server (for kernel upcalls). */ if (thr_create(NULL, 0, nfsauth_svc, 0, thr_flags, &nfsauth_thread)) { fprintf(stderr, gettext("Failed to create NFSAUTH svc thread\n")); exit(2); } /* * Create the cmd service thread with same signal disposition * as the main thread. We need to create a separate thread * since mountd() will be both an RPC server (for remote * traffic) _and_ a doors server (for kernel upcalls). */ if (thr_create(NULL, 0, cmd_svc, 0, thr_flags, &cmd_thread)) { syslog(LOG_ERR, gettext("Failed to create CMD svc thread")); exit(2); } /* * Create an additional thread to service the rmtab and * audit_mountd_mount logging for mount requests. Use the same * signal disposition as the main thread. We create * a separate thread to allow the mount request threads to * clear as soon as possible. */ if (thr_create(NULL, 0, logging_svc, 0, thr_flags, &logging_thread)) { syslog(LOG_ERR, gettext("Failed to create LOGGING svc thread")); exit(2); } /* * Enumerate network transports and create service listeners * as appropriate for each. */ if ((nc = setnetconfig()) == NULL) { syslog(LOG_ERR, "setnetconfig failed: %m"); return (-1); } while ((nconf = getnetconfig(nc)) != NULL) { /* * Skip things like tpi_raw, invisible... */ if ((nconf->nc_flag & NC_VISIBLE) == 0) continue; if (nconf->nc_semantics != NC_TPI_CLTS && nconf->nc_semantics != NC_TPI_COTS && nconf->nc_semantics != NC_TPI_COTS_ORD) continue; md_svc_tp_create(nconf); } (void) endnetconfig(nc); /* * Start serving */ rmtab_load(); daemonize_fini(pipe_fd); /* Get rid of the most dangerous basic privileges. */ __fini_daemon_priv(PRIV_PROC_EXEC, PRIV_PROC_INFO, PRIV_PROC_SESSION, (char *)NULL); svc_run(); syslog(LOG_ERR, "Error: svc_run shouldn't have returned"); abort(); /* NOTREACHED */ return (0); } /* * copied from usr/src/uts/common/klm/nlm_impl.c */ static bool_t caller_is_local(SVCXPRT *transp) { struct addrinfo *a; char *netid; struct netbuf *rtaddr; struct sockaddr_storage addr; bool_t rv = FALSE; netid = transp->xp_netid; rtaddr = svc_getrpccaller(transp); if (netid == NULL) return (FALSE); if (strcmp(netid, "ticlts") == 0 || strcmp(netid, "ticotsord") == 0) return (TRUE); if (strcmp(netid, "tcp") == 0 || strcmp(netid, "udp") == 0) { struct sockaddr_in *sin = (void *)rtaddr->buf; if (sin->sin_addr.s_addr == htonl(INADDR_LOOPBACK)) return (TRUE); memmove(&addr, sin, sizeof (*sin)); } if (strcmp(netid, "tcp6") == 0 || strcmp(netid, "udp6") == 0) { struct sockaddr_in6 *sin6 = (void *)rtaddr->buf; if (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr)) return (TRUE); memmove(&addr, sin6, sizeof (*sin6)); } for (a = host_ai; a != NULL; a = a->ai_next) { if (sockaddrcmp(&addr, (struct sockaddr_storage *)a->ai_addr)) { rv = TRUE; break; } } return (rv); } /* * Server procedure switch routine */ void mnt(struct svc_req *rqstp, SVCXPRT *transp) { switch (rqstp->rq_proc) { case NULLPROC: errno = 0; if (!svc_sendreply(transp, xdr_void, (char *)0)) log_cant_reply(transp); return; case MOUNTPROC_MNT: (void) mount(rqstp); return; case MOUNTPROC_DUMP: if (mountd_remote_dump || caller_is_local(transp)) mntlist_send(transp); else svcerr_noproc(transp); return; case MOUNTPROC_UMNT: umount(rqstp); return; case MOUNTPROC_UMNTALL: umountall(rqstp); return; case MOUNTPROC_EXPORT: case MOUNTPROC_EXPORTALL: export(rqstp); return; case MOUNTPROC_PATHCONF: if (rqstp->rq_vers == MOUNTVERS_POSIX) mnt_pathconf(rqstp); else svcerr_noproc(transp); return; default: svcerr_noproc(transp); return; } } void log_cant_reply_cln(struct cln *cln) { int saverrno; char *host; saverrno = errno; /* save error code */ host = cln_gethost(cln); if (host == NULL) return; errno = saverrno; if (errno == 0) syslog(LOG_ERR, "couldn't send reply to %s", host); else syslog(LOG_ERR, "couldn't send reply to %s: %m", host); } void log_cant_reply(SVCXPRT *transp) { int saverrno; struct cln cln; saverrno = errno; /* save error code */ cln_init(&cln, transp); errno = saverrno; log_cant_reply_cln(&cln); cln_fini(&cln); } /* * Answer pathconf questions for the mount point fs */ static void mnt_pathconf(struct svc_req *rqstp) { SVCXPRT *transp; struct pathcnf p; char *path, rpath[MAXPATHLEN]; struct stat st; transp = rqstp->rq_xprt; path = NULL; (void) memset((caddr_t)&p, 0, sizeof (p)); if (!svc_getargs(transp, xdr_dirpath, (caddr_t)&path)) { svcerr_decode(transp); return; } if (lstat(path, &st) < 0) { _PC_SET(_PC_ERROR, p.pc_mask); goto done; } /* * Get a path without symbolic links. */ if (realpath(path, rpath) == NULL) { syslog(LOG_DEBUG, "mount request: realpath failed on %s: %m", path); _PC_SET(_PC_ERROR, p.pc_mask); goto done; } (void) memset((caddr_t)&p, 0, sizeof (p)); /* * can't ask about devices over NFS */ _PC_SET(_PC_MAX_CANON, p.pc_mask); _PC_SET(_PC_MAX_INPUT, p.pc_mask); _PC_SET(_PC_PIPE_BUF, p.pc_mask); _PC_SET(_PC_VDISABLE, p.pc_mask); errno = 0; p.pc_link_max = pathconf(rpath, _PC_LINK_MAX); if (errno) _PC_SET(_PC_LINK_MAX, p.pc_mask); p.pc_name_max = pathconf(rpath, _PC_NAME_MAX); if (errno) _PC_SET(_PC_NAME_MAX, p.pc_mask); p.pc_path_max = pathconf(rpath, _PC_PATH_MAX); if (errno) _PC_SET(_PC_PATH_MAX, p.pc_mask); if (pathconf(rpath, _PC_NO_TRUNC) == 1) _PC_SET(_PC_NO_TRUNC, p.pc_mask); if (pathconf(rpath, _PC_CHOWN_RESTRICTED) == 1) _PC_SET(_PC_CHOWN_RESTRICTED, p.pc_mask); done: errno = 0; if (!svc_sendreply(transp, xdr_ppathcnf, (char *)&p)) log_cant_reply(transp); if (path != NULL) (void) svc_freeargs(transp, xdr_dirpath, (caddr_t)&path); } /* * If the rootmount (export) option is specified, the all mount requests for * subdirectories return EACCES. */ static int checkrootmount(share_t *sh, char *rpath) { char *val; if ((val = getshareopt(sh->sh_opts, SHOPT_NOSUB)) != NULL) { free(val); if (strcmp(sh->sh_path, rpath) != 0) return (0); else return (1); } else return (1); } #define MAX_FLAVORS 128 /* * Return only EACCES if client does not have access * to this directory. * "If the server exports only /a/b, an attempt to * mount a/b/c will fail with ENOENT if the directory * does not exist"... However, if the client * does not have access to /a/b, an attacker can * determine whether the directory exists. * This routine checks either existence of the file or * existence of the file name entry in the mount table. * If the file exists and there is no file name entry, * the error returned should be EACCES. * If the file does not exist, it must be determined * whether the client has access to a parent * directory. If the client has access to a parent * directory, the error returned should be ENOENT, * otherwise EACCES. */ static int mount_enoent_error(struct cln *cln, char *path, char *rpath, int *flavor_list) { char *checkpath, *dp; share_t *sh = NULL; int realpath_error = ENOENT, reply_error = EACCES, lofs_tried = 0; int flavor_count; checkpath = strdup(path); if (checkpath == NULL) { syslog(LOG_ERR, "mount_enoent: no memory"); return (EACCES); } /* CONSTCOND */ while (1) { if (sh) { sharefree(sh); sh = NULL; } if ((sh = findentry(rpath)) == NULL && (sh = find_lofsentry(rpath, &lofs_tried)) == NULL) { /* * There is no file name entry. * If the file (with symbolic links resolved) exists, * the error returned should be EACCES. */ if (realpath_error == 0) break; } else if (checkrootmount(sh, rpath) == 0) { /* * This is a "nosub" only export, in which case, * mounting subdirectories isn't allowed. * If the file (with symbolic links resolved) exists, * the error returned should be EACCES. */ if (realpath_error == 0) break; } else { /* * Check permissions in mount table. */ if (newopts(sh->sh_opts)) flavor_count = getclientsflavors_new(sh, cln, flavor_list); else flavor_count = getclientsflavors_old(sh, cln, flavor_list); if (flavor_count != 0) { /* * Found entry in table and * client has correct permissions. */ reply_error = ENOENT; break; } } /* * Check all parent directories. */ dp = strrchr(checkpath, '/'); if (dp == NULL) break; *dp = '\0'; if (strlen(checkpath) == 0) break; /* * Get the real path (no symbolic links in it) */ if (realpath(checkpath, rpath) == NULL) { if (errno != ENOENT) break; } else { realpath_error = 0; } } if (sh) sharefree(sh); free(checkpath); return (reply_error); } /* * We need to inform the caller whether or not we were * able to add a node to the queue. If we are not, then * it is up to the caller to go ahead and log the data. */ static int enqueue_logging_data(char *host, SVCXPRT *transp, char *path, char *rpath, int status, int error) { logging_data *lq; struct netbuf *nb; lq = (logging_data *)calloc(1, sizeof (logging_data)); if (lq == NULL) goto cleanup; /* * We might not yet have the host... */ if (host) { DTRACE_PROBE1(mountd, log_host, host); lq->ld_host = strdup(host); if (lq->ld_host == NULL) goto cleanup; } else { DTRACE_PROBE(mountd, log_no_host); lq->ld_netid = strdup(transp->xp_netid); if (lq->ld_netid == NULL) goto cleanup; lq->ld_nb = calloc(1, sizeof (struct netbuf)); if (lq->ld_nb == NULL) goto cleanup; nb = svc_getrpccaller(transp); if (nb == NULL) { DTRACE_PROBE(mountd, e__nb__enqueue); goto cleanup; } DTRACE_PROBE(mountd, nb_set_enqueue); lq->ld_nb->maxlen = nb->maxlen; lq->ld_nb->len = nb->len; lq->ld_nb->buf = malloc(lq->ld_nb->len); if (lq->ld_nb->buf == NULL) goto cleanup; bcopy(nb->buf, lq->ld_nb->buf, lq->ld_nb->len); } lq->ld_path = strdup(path); if (lq->ld_path == NULL) goto cleanup; if (!error) { lq->ld_rpath = strdup(rpath); if (lq->ld_rpath == NULL) goto cleanup; } lq->ld_status = status; /* * Add to the tail of the logging queue. */ (void) mutex_lock(&logging_queue_lock); if (logging_tail == NULL) { logging_tail = logging_head = lq; } else { logging_tail->ld_next = lq; logging_tail = lq; } (void) cond_signal(&logging_queue_cv); (void) mutex_unlock(&logging_queue_lock); return (TRUE); cleanup: free_logging_data(lq); return (FALSE); } #define CLN_CLNAMES (1 << 0) #define CLN_HOST (1 << 1) static void cln_init_common(struct cln *cln, SVCXPRT *transp, char *netid, struct netbuf *nbuf) { if ((cln->transp = transp) != NULL) { assert(netid == NULL && nbuf == NULL); cln->netid = transp->xp_netid; cln->nbuf = svc_getrpccaller(transp); } else { cln->netid = netid; cln->nbuf = nbuf; } cln->nconf = NULL; cln->clnames = NULL; cln->host = NULL; cln->flags = 0; } void cln_init(struct cln *cln, SVCXPRT *transp) { cln_init_common(cln, transp, NULL, NULL); } void cln_init_lazy(struct cln *cln, char *netid, struct netbuf *nbuf) { cln_init_common(cln, NULL, netid, nbuf); } void cln_fini(struct cln *cln) { if (cln->nconf != NULL) freenetconfigent(cln->nconf); if (cln->clnames != NULL) netdir_free(cln->clnames, ND_HOSTSERVLIST); free(cln->host); } struct netbuf * cln_getnbuf(struct cln *cln) { return (cln->nbuf); } struct nd_hostservlist * cln_getclientsnames(struct cln *cln) { if ((cln->flags & CLN_CLNAMES) == 0) { /* * nconf is not needed if we do not have nbuf (see * cln_gethost() too), so we check for nbuf and in a case it is * NULL we do not try to get nconf. */ if (cln->netid != NULL && cln->nbuf != NULL) { cln->nconf = getnetconfigent(cln->netid); if (cln->nconf == NULL) syslog(LOG_ERR, "%s: getnetconfigent failed", cln->netid); } if (cln->nconf != NULL && cln->nbuf != NULL) (void) __netdir_getbyaddr_nosrv(cln->nconf, &cln->clnames, cln->nbuf); cln->flags |= CLN_CLNAMES; } return (cln->clnames); } /* * Return B_TRUE if the host is already available at no cost */ boolean_t cln_havehost(struct cln *cln) { return ((cln->flags & (CLN_CLNAMES | CLN_HOST)) != 0); } char * cln_gethost(struct cln *cln) { if (cln_getclientsnames(cln) != NULL) return (cln->clnames->h_hostservs[0].h_host); if ((cln->flags & CLN_HOST) == 0) { if (cln->nconf == NULL || cln->nbuf == NULL) { cln->host = strdup("(anon)"); } else { char host[MAXIPADDRLEN]; if (strcmp(cln->nconf->nc_protofmly, NC_INET) == 0) { struct sockaddr_in *sa; /* LINTED pointer alignment */ sa = (struct sockaddr_in *)(cln->nbuf->buf); (void) inet_ntoa_r(sa->sin_addr, host); cln->host = strdup(host); } else if (strcmp(cln->nconf->nc_protofmly, NC_INET6) == 0) { struct sockaddr_in6 *sa; /* LINTED pointer alignment */ sa = (struct sockaddr_in6 *)(cln->nbuf->buf); (void) inet_ntop(AF_INET6, sa->sin6_addr.s6_addr, host, INET6_ADDRSTRLEN); cln->host = strdup(host); } else { syslog(LOG_ERR, gettext("Client's address is " "neither IPv4 nor IPv6")); cln->host = strdup("(anon)"); } } cln->flags |= CLN_HOST; } return (cln->host); } /* * Check mount requests, add to mounted list if ok */ static int mount(struct svc_req *rqstp) { SVCXPRT *transp; int version, vers; struct fhstatus fhs; struct mountres3 mountres3; char fh[FHSIZE3]; int len = FHSIZE3; char *path, rpath[MAXPATHLEN]; share_t *sh = NULL; struct cln cln; char *host = NULL; int error = 0, lofs_tried = 0, enqueued; int flavor_list[MAX_FLAVORS]; int flavor_count; ucred_t *uc = NULL; int audit_status; transp = rqstp->rq_xprt; version = rqstp->rq_vers; path = NULL; if (!svc_getargs(transp, xdr_dirpath, (caddr_t)&path)) { svcerr_decode(transp); return (EACCES); } cln_init(&cln, transp); /* * Put off getting the name for the client until we * need it. This is a performance gain. If we are logging, * then we don't care about performance and might as well * get the host name now in case we need to spit out an * error message. */ if (verbose) { DTRACE_PROBE(mountd, name_by_verbose); if ((host = cln_gethost(&cln)) == NULL) { /* * We failed to get a name for the client, even * 'anon', probably because we ran out of memory. * In this situation it doesn't make sense to * allow the mount to succeed. */ error = EACCES; goto reply; } } /* * If the version being used is less than the minimum version, * the filehandle translation should not be provided to the * client. */ if (rejecting || version < mount_vers_min) { if (verbose) syslog(LOG_NOTICE, "Rejected mount: %s for %s", host, path); error = EACCES; goto reply; } /* * Trusted Extension doesn't support nfsv2. nfsv2 client * uses MOUNT protocol v1 and v2. To prevent circumventing * TX label policy via using nfsv2 client, reject a mount * request with version less than 3 and log an error. */ if (is_system_labeled()) { if (version < 3) { if (verbose) syslog(LOG_ERR, "Rejected mount: TX doesn't support NFSv2"); error = EACCES; goto reply; } } /* * Get the real path (no symbolic links in it) */ if (realpath(path, rpath) == NULL) { error = errno; if (verbose) syslog(LOG_ERR, "mount request: realpath: %s: %m", path); if (error == ENOENT) error = mount_enoent_error(&cln, path, rpath, flavor_list); goto reply; } if ((sh = findentry(rpath)) == NULL && (sh = find_lofsentry(rpath, &lofs_tried)) == NULL) { error = EACCES; goto reply; } /* * Check if this is a "nosub" only export, in which case, mounting * subdirectories isn't allowed. Bug 1184573. */ if (checkrootmount(sh, rpath) == 0) { error = EACCES; goto reply; } if (newopts(sh->sh_opts)) flavor_count = getclientsflavors_new(sh, &cln, flavor_list); else flavor_count = getclientsflavors_old(sh, &cln, flavor_list); if (flavor_count == 0) { error = EACCES; goto reply; } /* * Check MAC policy here. The server side policy should be * consistent with client side mount policy, i.e. * - we disallow an admin_low unlabeled client to mount * - we disallow mount from a lower labeled client. */ if (is_system_labeled()) { m_label_t *clabel = NULL; m_label_t *slabel = NULL; m_label_t admin_low; if (svc_getcallerucred(rqstp->rq_xprt, &uc) != 0) { syslog(LOG_ERR, "mount request: Failed to get caller's ucred : %m"); error = EACCES; goto reply; } if ((clabel = ucred_getlabel(uc)) == NULL) { syslog(LOG_ERR, "mount request: can't get client label from ucred"); error = EACCES; goto reply; } bsllow(&admin_low); if (blequal(&admin_low, clabel)) { struct sockaddr *ca; tsol_tpent_t *tp; ca = (struct sockaddr *)(void *)svc_getrpccaller( rqstp->rq_xprt)->buf; if (ca == NULL) { error = EACCES; goto reply; } /* * get trusted network template associated * with the client. */ tp = get_client_template(ca); if (tp == NULL || tp->host_type != SUN_CIPSO) { if (tp != NULL) tsol_freetpent(tp); error = EACCES; goto reply; } tsol_freetpent(tp); } else { if ((slabel = m_label_alloc(MAC_LABEL)) == NULL) { error = EACCES; goto reply; } if (getlabel(rpath, slabel) != 0) { m_label_free(slabel); error = EACCES; goto reply; } if (!bldominates(clabel, slabel)) { m_label_free(slabel); error = EACCES; goto reply; } m_label_free(slabel); } } /* * Now get the filehandle. * * NFS V2 clients get a 32 byte filehandle. * NFS V3 clients get a 32 or 64 byte filehandle, depending on * the embedded FIDs. */ vers = (version == MOUNTVERS3) ? NFS_V3 : NFS_VERSION; /* LINTED pointer alignment */ while (nfs_getfh(rpath, vers, &len, fh) < 0) { if (errno == EINVAL && (sh = find_lofsentry(rpath, &lofs_tried)) != NULL) { errno = 0; continue; } error = errno == EINVAL ? EACCES : errno; syslog(LOG_DEBUG, "mount request: getfh failed on %s: %m", path); break; } if (version == MOUNTVERS3) { mountres3.mountres3_u.mountinfo.fhandle.fhandle3_len = len; mountres3.mountres3_u.mountinfo.fhandle.fhandle3_val = fh; } else { bcopy(fh, &fhs.fhstatus_u.fhs_fhandle, NFS_FHSIZE); } reply: if (uc != NULL) ucred_free(uc); switch (version) { case MOUNTVERS: case MOUNTVERS_POSIX: if (error == EINVAL) fhs.fhs_status = NFSERR_ACCES; else if (error == EREMOTE) fhs.fhs_status = NFSERR_REMOTE; else fhs.fhs_status = error; if (!svc_sendreply(transp, xdr_fhstatus, (char *)&fhs)) log_cant_reply_cln(&cln); audit_status = fhs.fhs_status; break; case MOUNTVERS3: if (!error) { mountres3.mountres3_u.mountinfo.auth_flavors.auth_flavors_val = flavor_list; mountres3.mountres3_u.mountinfo.auth_flavors.auth_flavors_len = flavor_count; } else if (error == ENAMETOOLONG) error = MNT3ERR_NAMETOOLONG; mountres3.fhs_status = error; if (!svc_sendreply(transp, xdr_mountres3, (char *)&mountres3)) log_cant_reply_cln(&cln); audit_status = mountres3.fhs_status; break; } if (cln_havehost(&cln)) host = cln_gethost(&cln); if (verbose) syslog(LOG_NOTICE, "MOUNT: %s %s %s", (host == NULL) ? "unknown host" : host, error ? "denied" : "mounted", path); /* * If we can not create a queue entry, go ahead and do it * in the context of this thread. */ enqueued = enqueue_logging_data(host, transp, path, rpath, audit_status, error); if (enqueued == FALSE) { if (host == NULL) { DTRACE_PROBE(mountd, name_by_in_thread); host = cln_gethost(&cln); } DTRACE_PROBE(mountd, logged_in_thread); audit_mountd_mount(host, path, audit_status); /* BSM */ if (!error) mntlist_new(host, rpath); /* add entry to mount list */ } if (path != NULL) (void) svc_freeargs(transp, xdr_dirpath, (caddr_t)&path); if (sh) sharefree(sh); cln_fini(&cln); return (error); } /* * Determine whether two paths are within the same file system. * Returns nonzero (true) if paths are the same, zero (false) if * they are different. If an error occurs, return false. * * Use the actual FSID if it's available (via getattrat()); otherwise, * fall back on st_dev. * * With ZFS snapshots, st_dev differs from the regular file system * versus the snapshot. But the fsid is the same throughout. Thus * the fsid is a better test. */ static int same_file_system(const char *path1, const char *path2) { uint64_t fsid1, fsid2; struct stat64 st1, st2; nvlist_t *nvl1 = NULL; nvlist_t *nvl2 = NULL; if ((getattrat(AT_FDCWD, XATTR_VIEW_READONLY, path1, &nvl1) == 0) && (getattrat(AT_FDCWD, XATTR_VIEW_READONLY, path2, &nvl2) == 0) && (nvlist_lookup_uint64(nvl1, A_FSID, &fsid1) == 0) && (nvlist_lookup_uint64(nvl2, A_FSID, &fsid2) == 0)) { nvlist_free(nvl1); nvlist_free(nvl2); /* * We have found fsid's for both paths. */ if (fsid1 == fsid2) return (B_TRUE); return (B_FALSE); } nvlist_free(nvl1); nvlist_free(nvl2); /* * We were unable to find fsid's for at least one of the paths. * fall back on st_dev. */ if (stat64(path1, &st1) < 0) { syslog(LOG_NOTICE, "%s: %m", path1); return (B_FALSE); } if (stat64(path2, &st2) < 0) { syslog(LOG_NOTICE, "%s: %m", path2); return (B_FALSE); } if (st1.st_dev == st2.st_dev) return (B_TRUE); return (B_FALSE); } share_t * findentry(char *path) { share_t *sh = NULL; struct sh_list *shp; char *p1, *p2; check_sharetab(); (void) rw_rdlock(&sharetab_lock); for (shp = share_list; shp; shp = shp->shl_next) { sh = shp->shl_sh; for (p1 = sh->sh_path, p2 = path; *p1 == *p2; p1++, p2++) if (*p1 == '\0') goto done; /* exact match */ /* * Now compare the pathnames for three cases: * * Parent: /export/foo (no trailing slash on parent) * Child: /export/foo/bar * * Parent: /export/foo/ (trailing slash on parent) * Child: /export/foo/bar * * Parent: /export/foo/ (no trailing slash on child) * Child: /export/foo */ if ((*p1 == '\0' && *p2 == '/') || (*p1 == '\0' && *(p1-1) == '/') || (*p2 == '\0' && *p1 == '/' && *(p1+1) == '\0')) { /* * We have a subdirectory. Test whether the * subdirectory is in the same file system. */ if (same_file_system(path, sh->sh_path)) goto done; } } done: sh = shp ? sharedup(sh) : NULL; (void) rw_unlock(&sharetab_lock); return (sh); } static int is_substring(char **mntp, char **path) { char *p1 = *mntp, *p2 = *path; if (*p1 == '\0' && *p2 == '\0') /* exact match */ return (1); else if (*p1 == '\0' && *p2 == '/') return (1); else if (*p1 == '\0' && *(p1-1) == '/') { *path = --p2; /* we need the slash in p2 */ return (1); } else if (*p2 == '\0') { while (*p1 == '/') p1++; if (*p1 == '\0') /* exact match */ return (1); } return (0); } /* * find_lofsentry() searches for the real path which this requested LOFS path * (rpath) shadows. If found, it will return the sharetab entry of * the real path that corresponds to the LOFS path. * We first search mnttab to see if the requested path is an automounted * path. If it is an automounted path, it will trigger the mount by stat()ing * the requested path. Note that it is important to check that this path is * actually an automounted path, otherwise we would stat() a path which may * turn out to be NFS and block indefinitely on a dead server. The automounter * times-out if the server is dead, so there's no risk of hanging this * thread waiting for stat(). * After the mount has been triggered (if necessary), we look for a * mountpoint of type LOFS (by searching /etc/mnttab again) which * is a substring of the rpath. If found, we construct a new path by * concatenating the mnt_special and the remaining of rpath, call findentry() * to make sure the 'real path' is shared. */ static share_t * find_lofsentry(char *rpath, int *done_flag) { struct stat r_stbuf; mntlist_t *ml, *mntl, *mntpnt = NULL; share_t *retcode = NULL; char tmp_path[MAXPATHLEN]; int mntpnt_len = 0, tmp; char *p1, *p2; if ((*done_flag)++) return (retcode); /* * While fsgetmntlist() uses lockf() to * lock the mnttab before reading it in, * the lock ignores threads in the same process. * Read in the mnttab with the protection of a mutex. */ (void) mutex_lock(&mnttab_lock); mntl = fsgetmntlist(); (void) mutex_unlock(&mnttab_lock); /* * Obtain the mountpoint for the requested path. */ for (ml = mntl; ml; ml = ml->mntl_next) { for (p1 = ml->mntl_mnt->mnt_mountp, p2 = rpath; *p1 == *p2 && *p1; p1++, p2++) ; if (is_substring(&p1, &p2) && (tmp = strlen(ml->mntl_mnt->mnt_mountp)) >= mntpnt_len) { mntpnt = ml; mntpnt_len = tmp; } } /* * If the path needs to be autoFS mounted, trigger the mount by * stat()ing it. This is determined by checking whether the * mountpoint we just found is of type autofs. */ if (mntpnt != NULL && strcmp(mntpnt->mntl_mnt->mnt_fstype, "autofs") == 0) { /* * The requested path is a substring of an autoFS filesystem. * Trigger the mount. */ if (stat(rpath, &r_stbuf) < 0) { if (verbose) syslog(LOG_NOTICE, "%s: %m", rpath); goto done; } if ((r_stbuf.st_mode & S_IFMT) == S_IFDIR) { /* * The requested path is a directory, stat(2) it * again with a trailing '.' to force the autoFS * module to trigger the mount of indirect * automount entries, such as /net/jurassic/. */ if (strlen(rpath) + 2 > MAXPATHLEN) { if (verbose) { syslog(LOG_NOTICE, "%s/.: exceeds MAXPATHLEN %d", rpath, MAXPATHLEN); } goto done; } (void) strcpy(tmp_path, rpath); (void) strcat(tmp_path, "/."); if (stat(tmp_path, &r_stbuf) < 0) { if (verbose) syslog(LOG_NOTICE, "%s: %m", tmp_path); goto done; } } /* * The mount has been triggered, re-read mnttab to pick up * the changes made by autoFS. */ fsfreemntlist(mntl); (void) mutex_lock(&mnttab_lock); mntl = fsgetmntlist(); (void) mutex_unlock(&mnttab_lock); } /* * The autoFS mountpoint has been triggered if necessary, * now search mnttab again to determine if the requested path * is an LOFS mount of a shared path. */ mntpnt_len = 0; for (ml = mntl; ml; ml = ml->mntl_next) { if (strcmp(ml->mntl_mnt->mnt_fstype, "lofs")) continue; for (p1 = ml->mntl_mnt->mnt_mountp, p2 = rpath; *p1 == *p2 && *p1; p1++, p2++) ; if (is_substring(&p1, &p2) && ((tmp = strlen(ml->mntl_mnt->mnt_mountp)) >= mntpnt_len)) { mntpnt_len = tmp; if ((strlen(ml->mntl_mnt->mnt_special) + strlen(p2)) > MAXPATHLEN) { if (verbose) { syslog(LOG_NOTICE, "%s%s: exceeds %d", ml->mntl_mnt->mnt_special, p2, MAXPATHLEN); } if (retcode) sharefree(retcode); retcode = NULL; goto done; } (void) strcpy(tmp_path, ml->mntl_mnt->mnt_special); (void) strcat(tmp_path, p2); if (retcode) sharefree(retcode); retcode = findentry(tmp_path); } } if (retcode) { assert(strlen(tmp_path) > 0); (void) strcpy(rpath, tmp_path); } done: fsfreemntlist(mntl); return (retcode); } /* * Determine whether an access list grants rights to a particular host. * We match on aliases of the hostname as well as on the canonical name. * Names in the access list may be either hosts or netgroups; they're * not distinguished syntactically. We check for hosts first because * it's cheaper, then try netgroups. * * Return values: * 1 - access is granted * 0 - access is denied * -1 - an error occured */ int in_access_list(struct cln *cln, char *access_list) /* N.B. we clobber this "input" parameter */ { char addr[INET_ADDRSTRLEN]; char buff[256]; int nentries = 0; char *cstr = access_list; char *gr = access_list; int i; int response; int ret; struct netbuf *pnb; struct nd_hostservlist *clnames = NULL; /* If no access list - then it's unrestricted */ if (access_list == NULL || *access_list == '\0') return (1); if ((pnb = cln_getnbuf(cln)) == NULL) return (-1); for (;;) { if ((cstr = strpbrk(cstr, "[:")) != NULL) { if (*cstr == ':') { *cstr = '\0'; } else { assert(*cstr == '['); cstr = strchr(cstr + 1, ']'); if (cstr == NULL) return (-1); cstr++; continue; } } /* * If the list name has a '-' prepended then a match of * the following name implies failure instead of success. */ if (*gr == '-') { response = 0; gr++; } else { response = 1; } /* * First check if we have '@' entry, as it doesn't * require client hostname. */ if (*gr == '@') { gr++; /* Netname support */ if (!isdigit(*gr) && *gr != '[') { struct netent n, *np; if ((np = getnetbyname_r(gr, &n, buff, sizeof (buff))) != NULL && np->n_net != 0) { while ((np->n_net & 0xFF000000u) == 0) np->n_net <<= 8; np->n_net = htonl(np->n_net); if (inet_ntop(AF_INET, &np->n_net, addr, INET_ADDRSTRLEN) == NULL) break; ret = inet_matchaddr(pnb->buf, addr); if (ret == -1) { if (errno == EINVAL) { syslog(LOG_WARNING, "invalid access " "list entry: %s", addr); } return (-1); } else if (ret == 1) { return (response); } } } else { ret = inet_matchaddr(pnb->buf, gr); if (ret == -1) { if (errno == EINVAL) { syslog(LOG_WARNING, "invalid access list " "entry: %s", gr); } return (-1); } else if (ret == 1) { return (response); } } goto next; } /* * No other checks can be performed if client address * can't be resolved. */ if ((clnames = cln_getclientsnames(cln)) == NULL) goto next; /* Otherwise loop through all client hostname aliases */ for (i = 0; i < clnames->h_cnt; i++) { char *host = clnames->h_hostservs[i].h_host; /* * If the list name begins with a dot then * do a domain name suffix comparison. * A single dot matches any name with no * suffix. */ if (*gr == '.') { if (*(gr + 1) == '\0') { /* single dot */ if (strchr(host, '.') == NULL) return (response); } else { int off = strlen(host) - strlen(gr); if (off > 0 && strcasecmp(host + off, gr) == 0) { return (response); } } } else { /* Just do a hostname match */ if (strcasecmp(gr, host) == 0) return (response); } } nentries++; next: if (cstr == NULL) break; gr = ++cstr; } if (clnames == NULL) return (0); return (netgroup_check(clnames, access_list, nentries)); } static char *optlist[] = { #define OPT_RO 0 SHOPT_RO, #define OPT_RW 1 SHOPT_RW, #define OPT_ROOT 2 SHOPT_ROOT, #define OPT_SECURE 3 SHOPT_SECURE, #define OPT_ANON 4 SHOPT_ANON, #define OPT_WINDOW 5 SHOPT_WINDOW, #define OPT_NOSUID 6 SHOPT_NOSUID, #define OPT_ACLOK 7 SHOPT_ACLOK, #define OPT_SEC 8 SHOPT_SEC, #define OPT_NONE 9 SHOPT_NONE, #define OPT_UIDMAP 10 SHOPT_UIDMAP, #define OPT_GIDMAP 11 SHOPT_GIDMAP, NULL }; static int map_flavor(char *str) { seconfig_t sec; if (nfs_getseconfig_byname(str, &sec)) return (-1); return (sec.sc_nfsnum); } /* * If the option string contains a "sec=" * option, then use new option syntax. */ static int newopts(char *opts) { char *head, *p, *val; if (!opts || *opts == '\0') return (0); head = strdup(opts); if (head == NULL) { syslog(LOG_ERR, "opts: no memory"); return (0); } p = head; while (*p) { if (getsubopt(&p, optlist, &val) == OPT_SEC) { free(head); return (1); } } free(head); return (0); } /* * Given an export and the clients hostname(s) * determine the security flavors that this * client is permitted to use. * * This routine is called only for "old" syntax, i.e. * only one security flavor is allowed. So we need * to determine two things: the particular flavor, * and whether the client is allowed to use this * flavor, i.e. is in the access list. * * Note that if there is no access list, then the * default is that access is granted. */ static int getclientsflavors_old(share_t *sh, struct cln *cln, int *flavors) { char *opts, *p, *val; boolean_t ok = B_FALSE; int defaultaccess = 1; boolean_t reject = B_FALSE; opts = strdup(sh->sh_opts); if (opts == NULL) { syslog(LOG_ERR, "getclientsflavors: no memory"); return (0); } flavors[0] = AUTH_SYS; p = opts; while (*p) { switch (getsubopt(&p, optlist, &val)) { case OPT_SECURE: flavors[0] = AUTH_DES; break; case OPT_RO: case OPT_RW: defaultaccess = 0; if (in_access_list(cln, val) > 0) ok = B_TRUE; break; case OPT_NONE: defaultaccess = 0; if (in_access_list(cln, val) > 0) reject = B_TRUE; } } free(opts); /* none takes precedence over everything else */ if (reject) ok = B_FALSE; return (defaultaccess || ok); } /* * Given an export and the clients hostname(s) * determine the security flavors that this * client is permitted to use. * * This is somewhat more complicated than the "old" * routine because the options may contain multiple * security flavors (sec=) each with its own access * lists. So a client could be granted access based * on a number of security flavors. Note that the * type of access might not always be the same, the * client may get readonly access with one flavor * and readwrite with another, however the client * is not told this detail, it gets only the list * of flavors, and only if the client is using * version 3 of the mount protocol. */ static int getclientsflavors_new(share_t *sh, struct cln *cln, int *flavors) { char *opts, *p, *val; char *lasts; char *f; boolean_t defaultaccess = B_TRUE; /* default access is rw */ boolean_t access_ok = B_FALSE; int count, c; boolean_t reject = B_FALSE; opts = strdup(sh->sh_opts); if (opts == NULL) { syslog(LOG_ERR, "getclientsflavors: no memory"); return (0); } p = opts; count = c = 0; while (*p) { switch (getsubopt(&p, optlist, &val)) { case OPT_SEC: if (reject) access_ok = B_FALSE; /* * Before a new sec=xxx option, check if we need * to move the c index back to the previous count. */ if (!defaultaccess && !access_ok) { c = count; } /* get all the sec=f1[:f2] flavors */ while ((f = strtok_r(val, ":", &lasts)) != NULL) { flavors[c++] = map_flavor(f); val = NULL; } /* for a new sec=xxx option, default is rw access */ defaultaccess = B_TRUE; access_ok = B_FALSE; reject = B_FALSE; break; case OPT_RO: case OPT_RW: defaultaccess = B_FALSE; if (in_access_list(cln, val) > 0) access_ok = B_TRUE; break; case OPT_NONE: defaultaccess = B_FALSE; if (in_access_list(cln, val) > 0) reject = B_TRUE; /* none overides rw/ro */ break; } } if (reject) access_ok = B_FALSE; if (!defaultaccess && !access_ok) c = count; free(opts); return (c); } /* * This is a tricky piece of code that parses the * share options looking for a match on the auth * flavor that the client is using. If it finds * a match, then the client is given ro, rw, or * no access depending whether it is in the access * list. There is a special case for "secure" * flavor. Other flavors are values of the new "sec=" option. */ int check_client(share_t *sh, struct cln *cln, int flavor, uid_t clnt_uid, gid_t clnt_gid, uint_t clnt_ngids, gid_t *clnt_gids, uid_t *srv_uid, gid_t *srv_gid, uint_t *srv_ngids, gid_t **srv_gids) { if (newopts(sh->sh_opts)) return (check_client_new(sh, cln, flavor, clnt_uid, clnt_gid, clnt_ngids, clnt_gids, srv_uid, srv_gid, srv_ngids, srv_gids)); else return (check_client_old(sh, cln, flavor, clnt_uid, clnt_gid, clnt_ngids, clnt_gids, srv_uid, srv_gid, srv_ngids, srv_gids)); } extern int _getgroupsbymember(const char *, gid_t[], int, int); /* * Get supplemental groups for uid */ static int getusergroups(uid_t uid, uint_t *ngrps, gid_t **grps) { struct passwd pwd; char *pwbuf = alloca(pw_size); gid_t *tmpgrps = alloca(ngroups_max * sizeof (gid_t)); int tmpngrps; if (getpwuid_r(uid, &pwd, pwbuf, pw_size) == NULL) return (-1); tmpgrps[0] = pwd.pw_gid; tmpngrps = _getgroupsbymember(pwd.pw_name, tmpgrps, ngroups_max, 1); if (tmpngrps <= 0) { syslog(LOG_WARNING, "getusergroups(): Unable to get groups for user %s", pwd.pw_name); return (-1); } *grps = malloc(tmpngrps * sizeof (gid_t)); if (*grps == NULL) { syslog(LOG_ERR, "getusergroups(): Memory allocation failed: %m"); return (-1); } *ngrps = tmpngrps; (void) memcpy(*grps, tmpgrps, tmpngrps * sizeof (gid_t)); return (0); } /* * is_a_number(number) * * is the string a number in one of the forms we want to use? */ static int is_a_number(char *number) { int ret = 1; int hex = 0; if (strncmp(number, "0x", 2) == 0) { number += 2; hex = 1; } else if (*number == '-') { number++; /* skip the minus */ } while (ret == 1 && *number != '\0') { if (hex) { ret = isxdigit(*number++); } else { ret = isdigit(*number++); } } return (ret); } static boolean_t get_uid(char *value, uid_t *uid) { if (!is_a_number(value)) { struct passwd *pw; /* * in this case it would have to be a * user name */ pw = getpwnam(value); if (pw == NULL) return (B_FALSE); *uid = pw->pw_uid; endpwent(); } else { uint64_t intval; intval = strtoull(value, NULL, 0); if (intval > UID_MAX && intval != -1) return (B_FALSE); *uid = (uid_t)intval; } return (B_TRUE); } static boolean_t get_gid(char *value, gid_t *gid) { if (!is_a_number(value)) { struct group *gr; /* * in this case it would have to be a * group name */ gr = getgrnam(value); if (gr == NULL) return (B_FALSE); *gid = gr->gr_gid; endgrent(); } else { uint64_t intval; intval = strtoull(value, NULL, 0); if (intval > UID_MAX && intval != -1) return (B_FALSE); *gid = (gid_t)intval; } return (B_TRUE); } static int check_client_old(share_t *sh, struct cln *cln, int flavor, uid_t clnt_uid, gid_t clnt_gid, uint_t clnt_ngids, gid_t *clnt_gids, uid_t *srv_uid, gid_t *srv_gid, uint_t *srv_ngids, gid_t **srv_gids) { char *opts, *p, *val; int match; /* Set when a flavor is matched */ int perm = 0; /* Set when "ro", "rw" or "root" is matched */ int list = 0; /* Set when "ro", "rw" is found */ int ro_val = 0; /* Set if ro option is 'ro=' */ int rw_val = 0; /* Set if rw option is 'rw=' */ boolean_t map_deny = B_FALSE; opts = strdup(sh->sh_opts); if (opts == NULL) { syslog(LOG_ERR, "check_client: no memory"); return (0); } /* * If client provided 16 supplemental groups with AUTH_SYS, lookup * locally for all of them */ if (flavor == AUTH_SYS && clnt_ngids == NGRPS && ngroups_max > NGRPS) if (getusergroups(clnt_uid, srv_ngids, srv_gids) == 0) perm |= NFSAUTH_GROUPS; p = opts; match = AUTH_UNIX; while (*p) { switch (getsubopt(&p, optlist, &val)) { case OPT_SECURE: match = AUTH_DES; if (perm & NFSAUTH_GROUPS) { free(*srv_gids); *srv_ngids = 0; *srv_gids = NULL; perm &= ~NFSAUTH_GROUPS; } break; case OPT_RO: list++; if (val != NULL) ro_val++; if (in_access_list(cln, val) > 0) perm |= NFSAUTH_RO; break; case OPT_RW: list++; if (val != NULL) rw_val++; if (in_access_list(cln, val) > 0) perm |= NFSAUTH_RW; break; case OPT_ROOT: /* * Check if the client is in * the root list. Only valid * for AUTH_SYS. */ if (flavor != AUTH_SYS) break; if (val == NULL || *val == '\0') break; if (clnt_uid != 0) break; if (in_access_list(cln, val) > 0) { perm |= NFSAUTH_ROOT; perm |= NFSAUTH_UIDMAP | NFSAUTH_GIDMAP; map_deny = B_FALSE; if (perm & NFSAUTH_GROUPS) { free(*srv_gids); *srv_ngids = 0; *srv_gids = NULL; perm &= ~NFSAUTH_GROUPS; } } break; case OPT_NONE: /* * Check if the client should have no access * to this share at all. This option behaves * more like "root" than either "rw" or "ro". */ if (in_access_list(cln, val) > 0) perm |= NFSAUTH_DENIED; break; case OPT_UIDMAP: { char *c; char *n; /* * The uidmap is supported for AUTH_SYS only. */ if (flavor != AUTH_SYS) break; if (perm & NFSAUTH_UIDMAP || map_deny) break; for (c = val; c != NULL; c = n) { char *s; char *al; uid_t srv; n = strchr(c, '~'); if (n != NULL) *n++ = '\0'; s = strchr(c, ':'); if (s != NULL) { *s++ = '\0'; al = strchr(s, ':'); if (al != NULL) *al++ = '\0'; } if (s == NULL || al == NULL) continue; if (*c == '\0') { if (clnt_uid != (uid_t)-1) continue; } else if (strcmp(c, "*") != 0) { uid_t clnt; if (!get_uid(c, &clnt)) continue; if (clnt_uid != clnt) continue; } if (*s == '\0') srv = UID_NOBODY; else if (!get_uid(s, &srv)) continue; else if (srv == (uid_t)-1) { map_deny = B_TRUE; break; } if (in_access_list(cln, al) > 0) { *srv_uid = srv; perm |= NFSAUTH_UIDMAP; if (perm & NFSAUTH_GROUPS) { free(*srv_gids); *srv_ngids = 0; *srv_gids = NULL; perm &= ~NFSAUTH_GROUPS; } break; } } break; } case OPT_GIDMAP: { char *c; char *n; /* * The gidmap is supported for AUTH_SYS only. */ if (flavor != AUTH_SYS) break; if (perm & NFSAUTH_GIDMAP || map_deny) break; for (c = val; c != NULL; c = n) { char *s; char *al; gid_t srv; n = strchr(c, '~'); if (n != NULL) *n++ = '\0'; s = strchr(c, ':'); if (s != NULL) { *s++ = '\0'; al = strchr(s, ':'); if (al != NULL) *al++ = '\0'; } if (s == NULL || al == NULL) break; if (*c == '\0') { if (clnt_gid != (gid_t)-1) continue; } else if (strcmp(c, "*") != 0) { gid_t clnt; if (!get_gid(c, &clnt)) continue; if (clnt_gid != clnt) continue; } if (*s == '\0') srv = UID_NOBODY; else if (!get_gid(s, &srv)) continue; else if (srv == (gid_t)-1) { map_deny = B_TRUE; break; } if (in_access_list(cln, al) > 0) { *srv_gid = srv; perm |= NFSAUTH_GIDMAP; if (perm & NFSAUTH_GROUPS) { free(*srv_gids); *srv_ngids = 0; *srv_gids = NULL; perm &= ~NFSAUTH_GROUPS; } break; } } break; } default: break; } } free(opts); if (perm & NFSAUTH_ROOT) { *srv_uid = 0; *srv_gid = 0; } if (map_deny) perm |= NFSAUTH_DENIED; if (!(perm & NFSAUTH_UIDMAP)) *srv_uid = clnt_uid; if (!(perm & NFSAUTH_GIDMAP)) *srv_gid = clnt_gid; if (flavor != match || perm & NFSAUTH_DENIED) return (NFSAUTH_DENIED); if (list) { /* * If the client doesn't match an "ro" or "rw" * list then set no access. */ if ((perm & (NFSAUTH_RO | NFSAUTH_RW)) == 0) perm |= NFSAUTH_DENIED; } else { /* * The client matched a flavor entry that * has no explicit "rw" or "ro" determination. * Default it to "rw". */ perm |= NFSAUTH_RW; } /* * The client may show up in both ro= and rw= * lists. If so, then turn off the RO access * bit leaving RW access. */ if (perm & NFSAUTH_RO && perm & NFSAUTH_RW) { /* * Logically cover all permutations of rw=,ro=. * In the case where, rw,ro= we would like * to remove RW access for the host. In all other cases * RW wins the precedence battle. */ if (!rw_val && ro_val) { perm &= ~(NFSAUTH_RW); } else { perm &= ~(NFSAUTH_RO); } } return (perm); } /* * Check if the client has access by using a flavor different from * the given "flavor". If "flavor" is not in the flavor list, * return TRUE to indicate that this "flavor" is a wrong sec. */ static bool_t is_wrongsec(share_t *sh, struct cln *cln, int flavor) { int flavor_list[MAX_FLAVORS]; int flavor_count, i; /* get the flavor list that the client has access with */ flavor_count = getclientsflavors_new(sh, cln, flavor_list); if (flavor_count == 0) return (FALSE); /* * Check if the given "flavor" is in the flavor_list. */ for (i = 0; i < flavor_count; i++) { if (flavor == flavor_list[i]) return (FALSE); } /* * If "flavor" is not in the flavor_list, return TRUE to indicate * that the client should have access by using a security flavor * different from this "flavor". */ return (TRUE); } /* * Given an export and the client's hostname, we * check the security options to see whether the * client is allowed to use the given security flavor. * * The strategy is to proceed through the options looking * for a flavor match, then pay attention to the ro, rw, * and root options. * * Note that an entry may list several flavors in a * single entry, e.g. * * sec=krb5,rw=clnt1:clnt2,ro,sec=sys,ro * */ static int check_client_new(share_t *sh, struct cln *cln, int flavor, uid_t clnt_uid, gid_t clnt_gid, uint_t clnt_ngids, gid_t *clnt_gids, uid_t *srv_uid, gid_t *srv_gid, uint_t *srv_ngids, gid_t **srv_gids) { char *opts, *p, *val; char *lasts; char *f; int match = 0; /* Set when a flavor is matched */ int perm = 0; /* Set when "ro", "rw" or "root" is matched */ int list = 0; /* Set when "ro", "rw" is found */ int ro_val = 0; /* Set if ro option is 'ro=' */ int rw_val = 0; /* Set if rw option is 'rw=' */ boolean_t map_deny = B_FALSE; opts = strdup(sh->sh_opts); if (opts == NULL) { syslog(LOG_ERR, "check_client: no memory"); return (0); } /* * If client provided 16 supplemental groups with AUTH_SYS, lookup * locally for all of them */ if (flavor == AUTH_SYS && clnt_ngids == NGRPS && ngroups_max > NGRPS) if (getusergroups(clnt_uid, srv_ngids, srv_gids) == 0) perm |= NFSAUTH_GROUPS; p = opts; while (*p) { switch (getsubopt(&p, optlist, &val)) { case OPT_SEC: if (match) goto done; while ((f = strtok_r(val, ":", &lasts)) != NULL) { if (flavor == map_flavor(f)) { match = 1; break; } val = NULL; } break; case OPT_RO: if (!match) break; list++; if (val != NULL) ro_val++; if (in_access_list(cln, val) > 0) perm |= NFSAUTH_RO; break; case OPT_RW: if (!match) break; list++; if (val != NULL) rw_val++; if (in_access_list(cln, val) > 0) perm |= NFSAUTH_RW; break; case OPT_ROOT: /* * Check if the client is in * the root list. Only valid * for AUTH_SYS. */ if (flavor != AUTH_SYS) break; if (!match) break; if (val == NULL || *val == '\0') break; if (clnt_uid != 0) break; if (in_access_list(cln, val) > 0) { perm |= NFSAUTH_ROOT; perm |= NFSAUTH_UIDMAP | NFSAUTH_GIDMAP; map_deny = B_FALSE; if (perm & NFSAUTH_GROUPS) { free(*srv_gids); *srv_gids = NULL; *srv_ngids = 0; perm &= ~NFSAUTH_GROUPS; } } break; case OPT_NONE: /* * Check if the client should have no access * to this share at all. This option behaves * more like "root" than either "rw" or "ro". */ if (in_access_list(cln, val) > 0) perm |= NFSAUTH_DENIED; break; case OPT_UIDMAP: { char *c; char *n; /* * The uidmap is supported for AUTH_SYS only. */ if (flavor != AUTH_SYS) break; if (!match || perm & NFSAUTH_UIDMAP || map_deny) break; for (c = val; c != NULL; c = n) { char *s; char *al; uid_t srv; n = strchr(c, '~'); if (n != NULL) *n++ = '\0'; s = strchr(c, ':'); if (s != NULL) { *s++ = '\0'; al = strchr(s, ':'); if (al != NULL) *al++ = '\0'; } if (s == NULL || al == NULL) continue; if (*c == '\0') { if (clnt_uid != (uid_t)-1) continue; } else if (strcmp(c, "*") != 0) { uid_t clnt; if (!get_uid(c, &clnt)) continue; if (clnt_uid != clnt) continue; } if (*s == '\0') srv = UID_NOBODY; else if (!get_uid(s, &srv)) continue; else if (srv == (uid_t)-1) { map_deny = B_TRUE; break; } if (in_access_list(cln, al) > 0) { *srv_uid = srv; perm |= NFSAUTH_UIDMAP; if (perm & NFSAUTH_GROUPS) { free(*srv_gids); *srv_gids = NULL; *srv_ngids = 0; perm &= ~NFSAUTH_GROUPS; } break; } } break; } case OPT_GIDMAP: { char *c; char *n; /* * The gidmap is supported for AUTH_SYS only. */ if (flavor != AUTH_SYS) break; if (!match || perm & NFSAUTH_GIDMAP || map_deny) break; for (c = val; c != NULL; c = n) { char *s; char *al; gid_t srv; n = strchr(c, '~'); if (n != NULL) *n++ = '\0'; s = strchr(c, ':'); if (s != NULL) { *s++ = '\0'; al = strchr(s, ':'); if (al != NULL) *al++ = '\0'; } if (s == NULL || al == NULL) break; if (*c == '\0') { if (clnt_gid != (gid_t)-1) continue; } else if (strcmp(c, "*") != 0) { gid_t clnt; if (!get_gid(c, &clnt)) continue; if (clnt_gid != clnt) continue; } if (*s == '\0') srv = UID_NOBODY; else if (!get_gid(s, &srv)) continue; else if (srv == (gid_t)-1) { map_deny = B_TRUE; break; } if (in_access_list(cln, al) > 0) { *srv_gid = srv; perm |= NFSAUTH_GIDMAP; if (perm & NFSAUTH_GROUPS) { free(*srv_gids); *srv_gids = NULL; *srv_ngids = 0; perm &= ~NFSAUTH_GROUPS; } break; } } break; } default: break; } } done: if (perm & NFSAUTH_ROOT) { *srv_uid = 0; *srv_gid = 0; } if (map_deny) perm |= NFSAUTH_DENIED; if (!(perm & NFSAUTH_UIDMAP)) *srv_uid = clnt_uid; if (!(perm & NFSAUTH_GIDMAP)) *srv_gid = clnt_gid; /* * If no match then set the perm accordingly */ if (!match || perm & NFSAUTH_DENIED) { free(opts); return (NFSAUTH_DENIED); } if (list) { /* * If the client doesn't match an "ro" or "rw" list then * check if it may have access by using a different flavor. * If so, return NFSAUTH_WRONGSEC. * If not, return NFSAUTH_DENIED. */ if ((perm & (NFSAUTH_RO | NFSAUTH_RW)) == 0) { if (is_wrongsec(sh, cln, flavor)) perm |= NFSAUTH_WRONGSEC; else perm |= NFSAUTH_DENIED; } } else { /* * The client matched a flavor entry that * has no explicit "rw" or "ro" determination. * Make sure it defaults to "rw". */ perm |= NFSAUTH_RW; } /* * The client may show up in both ro= and rw= * lists. If so, then turn off the RO access * bit leaving RW access. */ if (perm & NFSAUTH_RO && perm & NFSAUTH_RW) { /* * Logically cover all permutations of rw=,ro=. * In the case where, rw,ro= we would like * to remove RW access for the host. In all other cases * RW wins the precedence battle. */ if (!rw_val && ro_val) { perm &= ~(NFSAUTH_RW); } else { perm &= ~(NFSAUTH_RO); } } free(opts); return (perm); } void check_sharetab() { FILE *f; struct stat st; static timestruc_t last_sharetab_time; timestruc_t prev_sharetab_time; share_t *sh; struct sh_list *shp, *shp_prev; int res, c = 0; /* * read in /etc/dfs/sharetab if it has changed */ if (stat(SHARETAB, &st) != 0) { syslog(LOG_ERR, "Cannot stat %s: %m", SHARETAB); return; } if (st.st_mtim.tv_sec == last_sharetab_time.tv_sec && st.st_mtim.tv_nsec == last_sharetab_time.tv_nsec) { /* * No change. */ return; } /* * Remember the mod time, then after getting the * write lock check again. If another thread * already did the update, then there's no * work to do. */ prev_sharetab_time = last_sharetab_time; (void) rw_wrlock(&sharetab_lock); if (prev_sharetab_time.tv_sec != last_sharetab_time.tv_sec || prev_sharetab_time.tv_nsec != last_sharetab_time.tv_nsec) { (void) rw_unlock(&sharetab_lock); return; } /* * Note that since the sharetab is now in memory * and a snapshot is taken, we no longer have to * lock the file. */ f = fopen(SHARETAB, "r"); if (f == NULL) { syslog(LOG_ERR, "Cannot open %s: %m", SHARETAB); (void) rw_unlock(&sharetab_lock); return; } /* * Once we are sure /etc/dfs/sharetab has been * modified, flush netgroup cache entries. */ netgrp_cache_flush(); sh_free(share_list); /* free old list */ share_list = NULL; while ((res = getshare(f, &sh)) > 0) { c++; if (strcmp(sh->sh_fstype, "nfs") != 0) continue; shp = malloc(sizeof (*shp)); if (shp == NULL) goto alloc_failed; if (share_list == NULL) share_list = shp; else /* LINTED not used before set */ shp_prev->shl_next = shp; shp_prev = shp; shp->shl_next = NULL; shp->shl_sh = sharedup(sh); if (shp->shl_sh == NULL) goto alloc_failed; } if (res < 0) syslog(LOG_ERR, "%s: invalid at line %d\n", SHARETAB, c + 1); if (stat(SHARETAB, &st) != 0) { syslog(LOG_ERR, "Cannot stat %s: %m", SHARETAB); (void) fclose(f); (void) rw_unlock(&sharetab_lock); return; } last_sharetab_time = st.st_mtim; (void) fclose(f); (void) rw_unlock(&sharetab_lock); return; alloc_failed: syslog(LOG_ERR, "check_sharetab: no memory"); sh_free(share_list); share_list = NULL; (void) fclose(f); (void) rw_unlock(&sharetab_lock); } static void sh_free(struct sh_list *shp) { struct sh_list *next; while (shp) { sharefree(shp->shl_sh); next = shp->shl_next; free(shp); shp = next; } } /* * Remove an entry from mounted list */ static void umount(struct svc_req *rqstp) { char *host, *path, *remove_path; char rpath[MAXPATHLEN]; SVCXPRT *transp; struct cln cln; transp = rqstp->rq_xprt; path = NULL; if (!svc_getargs(transp, xdr_dirpath, (caddr_t)&path)) { svcerr_decode(transp); return; } cln_init(&cln, transp); errno = 0; if (!svc_sendreply(transp, xdr_void, (char *)NULL)) log_cant_reply_cln(&cln); host = cln_gethost(&cln); if (host == NULL) { /* * Without the hostname we can't do audit or delete * this host from the mount entries. */ (void) svc_freeargs(transp, xdr_dirpath, (caddr_t)&path); return; } if (verbose) syslog(LOG_NOTICE, "UNMOUNT: %s unmounted %s", host, path); audit_mountd_umount(host, path); remove_path = rpath; /* assume we will use the cannonical path */ if (realpath(path, rpath) == NULL) { if (verbose) syslog(LOG_WARNING, "UNMOUNT: realpath: %s: %m ", path); remove_path = path; /* use path provided instead */ } mntlist_delete(host, remove_path); /* remove from mount list */ cln_fini(&cln); (void) svc_freeargs(transp, xdr_dirpath, (caddr_t)&path); } /* * Remove all entries for one machine from mounted list */ static void umountall(struct svc_req *rqstp) { SVCXPRT *transp; char *host; struct cln cln; transp = rqstp->rq_xprt; if (!svc_getargs(transp, xdr_void, NULL)) { svcerr_decode(transp); return; } /* * We assume that this call is asynchronous and made via rpcbind * callit routine. Therefore return control immediately. The error * causes rpcbind to remain silent, as opposed to every machine * on the net blasting the requester with a response. */ svcerr_systemerr(transp); cln_init(&cln, transp); host = cln_gethost(&cln); if (host == NULL) { /* Can't do anything without the name of the client */ return; } /* * Remove all hosts entries from mount list */ mntlist_delete_all(host); if (verbose) syslog(LOG_NOTICE, "UNMOUNTALL: from %s", host); cln_fini(&cln); } void * exmalloc(size_t size) { void *ret; if ((ret = malloc(size)) == NULL) { syslog(LOG_ERR, "Out of memory"); exit(1); } return (ret); } static tsol_tpent_t * get_client_template(struct sockaddr *sock) { in_addr_t v4client; in6_addr_t v6client; char v4_addr[INET_ADDRSTRLEN]; char v6_addr[INET6_ADDRSTRLEN]; tsol_rhent_t *rh; tsol_tpent_t *tp; switch (sock->sa_family) { case AF_INET: v4client = ((struct sockaddr_in *)(void *)sock)-> sin_addr.s_addr; if (inet_ntop(AF_INET, &v4client, v4_addr, INET_ADDRSTRLEN) == NULL) return (NULL); rh = tsol_getrhbyaddr(v4_addr, sizeof (v4_addr), AF_INET); if (rh == NULL) return (NULL); tp = tsol_gettpbyname(rh->rh_template); tsol_freerhent(rh); return (tp); break; case AF_INET6: v6client = ((struct sockaddr_in6 *)(void *)sock)->sin6_addr; if (inet_ntop(AF_INET6, &v6client, v6_addr, INET6_ADDRSTRLEN) == NULL) return (NULL); rh = tsol_getrhbyaddr(v6_addr, sizeof (v6_addr), AF_INET6); if (rh == NULL) return (NULL); tp = tsol_gettpbyname(rh->rh_template); tsol_freerhent(rh); return (tp); break; default: return (NULL); } } /* * 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 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _MOUNTD_H #define _MOUNTD_H #include #include #ifdef __cplusplus extern "C" { #endif #define MAXIPADDRLEN 512 struct nd_hostservlist; extern void rmtab_load(void); extern void mntlist_send(SVCXPRT *transp); extern void mntlist_new(char *host, char *path); extern void mntlist_delete(char *host, char *path); extern void mntlist_delete_all(char *host); extern void netgroup_init(void); extern int netgroup_check(struct nd_hostservlist *, char *, int); extern void netgrp_cache_flush(void); extern void export(struct svc_req *); extern void nfsauth_func(void *, char *, size_t, door_desc_t *, uint_t); extern char *inet_ntoa_r(struct in_addr, char *); extern int nfs_getfh(char *, int, int *, char *); extern void nfsauth_prog(struct svc_req *, SVCXPRT *); extern struct sh_list *share_list; extern rwlock_t sharetab_lock; extern void check_sharetab(void); struct cln; extern void log_cant_reply(SVCXPRT *); extern void log_cant_reply_cln(struct cln *); extern void *exmalloc(size_t); extern struct share *findentry(char *); extern int check_client(struct share *, struct cln *, int, uid_t, gid_t, uint_t, gid_t *, uid_t *, gid_t *, uint_t *, gid_t **); extern int in_access_list(struct cln *, char *); struct cln { SVCXPRT *transp; char *netid; struct netconfig *nconf; struct netbuf *nbuf; struct nd_hostservlist *clnames; char *host; int flags; }; extern void cln_init(struct cln *, SVCXPRT *); extern void cln_init_lazy(struct cln *, char *, struct netbuf *); extern void cln_fini(struct cln *); extern struct netbuf *cln_getnbuf(struct cln *); extern struct nd_hostservlist *cln_getclientsnames(struct cln *); extern boolean_t cln_havehost(struct cln *); extern char *cln_gethost(struct cln *); /* * These functions are defined here due to the fact * that we can not find the proper header file to * include. These functions are, at present, not * listed in any other header files. */ /* * These three functions are hidden functions in the * bsm libraries (libbsm). */ extern void audit_mountd_setup(void); extern void audit_mountd_mount(char *, char *, int); extern void audit_mountd_umount(char *, char *); /* * This appears to be a hidden function in libc. * Private interface to nss_search(). * Accepts N strings rather than 1. */ extern int __multi_innetgr(); #ifdef __cplusplus } #endif #endif /* _MOUNTD_H */ /* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ provider mountd { probe hashset(int, float); probe e__nb__enqueue(void); probe nb_set_enqueue(void); probe log_host(char *); probe log_no_host(void); probe logging_cleared(int); probe logged_in_thread(void); probe name_by_verbose(void); probe name_by_in_thread(void); probe name_by_lazy(void); probe name_by_addrlist(void); probe name_by_netgroup(void); }; #pragma D attributes Private/Private/Common provider mountd provider #pragma D attributes Private/Private/Common provider mountd module #pragma D attributes Private/Private/Common provider mountd function #pragma D attributes Private/Private/Common provider mountd name #pragma D attributes Private/Private/Common provider mountd args /* * 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 2007 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 "mountd.h" struct cache_entry { char *cache_host; time_t cache_time; int cache_belong; char **cache_grl; int cache_grc; struct cache_entry *cache_next; }; static struct cache_entry *cache_head; #define VALID_TIME 60 /* seconds */ static rwlock_t cache_lock; /* protect the cache chain */ static void cache_free(struct cache_entry *entry); static int cache_check(char *host, char **grl, int grc, int *belong); static void cache_enter(char *host, char **grl, int grc, int belong); void netgroup_init() { (void) rwlock_init(&cache_lock, USYNC_THREAD, NULL); } /* * Check whether any of the hostnames in clnames are * members (or non-members) of the netgroups in glist. * Since the innetgr lookup is rather expensive, the * result is cached. The cached entry is valid only * for VALID_TIME seconds. This works well because * typically these lookups occur in clusters when * a client is mounting. * * Note that this routine establishes a host membership * in a list of netgroups - we've no idea just which * netgroup in the list it is a member of. * * glist is a character array containing grc strings * representing netgroup names (optionally prefixed * with '-'). Each string is ended with '\0' and * followed immediately by the next string. */ int netgroup_check(struct nd_hostservlist *clnames, char *glist, int grc) { char **grl; char *gr; int nhosts = clnames->h_cnt; char *host0, *host; int i, j, n; int response; int belong = 0; static char *domain; if (domain == NULL) { int ssize; domain = exmalloc(SYS_NMLN); ssize = sysinfo(SI_SRPC_DOMAIN, domain, SYS_NMLN); if (ssize > SYS_NMLN) { free(domain); domain = exmalloc(ssize); ssize = sysinfo(SI_SRPC_DOMAIN, domain, ssize); } /* Check for error in syscall or NULL domain name */ if (ssize <= 1) { syslog(LOG_ERR, "No default domain set"); return (0); } } grl = calloc(grc, sizeof (char *)); if (grl == NULL) return (0); for (i = 0, gr = glist; i < grc && !belong; ) { /* * If the netgroup name has a '-' prepended * then a match of this name implies a failure * instead of success. */ response = (*gr != '-') ? 1 : 0; /* * Subsequent names with or without a '-' (but no mix) * can be grouped together for a single check. */ for (n = 0; i < grc; i++, n++, gr += strlen(gr) + 1) { if ((response && *gr == '-') || (!response && *gr != '-')) break; grl[n] = response ? gr : gr + 1; } host0 = clnames->h_hostservs[0].h_host; /* * If not in cache check the netgroup for each * of the hosts names (usually just one). * Enter the result into the cache. */ if (!cache_check(host0, grl, n, &belong)) { for (j = 0; j < nhosts && !belong; j++) { host = clnames->h_hostservs[j].h_host; if (__multi_innetgr(n, grl, 1, &host, 0, NULL, 1, &domain)) belong = 1; } cache_enter(host0, grl, n, belong); } } free(grl); return (belong ? response : 0); } /* * Free a cache entry and all entries * further down the chain since they * will also be expired. */ static void cache_free(struct cache_entry *entry) { struct cache_entry *ce, *next; int i; for (ce = entry; ce; ce = next) { if (ce->cache_host) free(ce->cache_host); for (i = 0; i < ce->cache_grc; i++) if (ce->cache_grl[i]) free(ce->cache_grl[i]); if (ce->cache_grl) free(ce->cache_grl); next = ce->cache_next; free(ce); } } /* * Search the entries in the cache chain looking * for an entry with a matching hostname and group * list. If a match is found then return the "belong" * value which may be 1 or 0 depending on whether the * client is a member of the list or not. This is * both a positive and negative cache. * * Cache entries have a validity of VALID_TIME seconds. * If we find an expired entry then blow away the entry * and the rest of the chain since entries further down * the chain will be expired too because we always add * new entries to the head of the chain. */ static int cache_check(char *host, char **grl, int grc, int *belong) { struct cache_entry *ce, *prev; time_t timenow = time(NULL); int i; (void) rw_rdlock(&cache_lock); for (ce = cache_head; ce; ce = ce->cache_next) { /* * If we find a stale entry, there can't * be any valid entries from here on. * Acquire a write lock, search the chain again * and delete the stale entry and all following * entries. */ if (timenow > ce->cache_time) { (void) rw_unlock(&cache_lock); (void) rw_wrlock(&cache_lock); for (prev = NULL, ce = cache_head; ce; prev = ce, ce = ce->cache_next) if (timenow > ce->cache_time) break; if (ce != NULL) { if (prev) prev->cache_next = NULL; else cache_head = NULL; cache_free(ce); } (void) rw_unlock(&cache_lock); return (0); } if (ce->cache_grc != grc) continue; /* no match */ if (strcasecmp(host, ce->cache_host) != 0) continue; /* no match */ for (i = 0; i < grc; i++) if (strcasecmp(ce->cache_grl[i], grl[i]) != 0) break; /* no match */ if (i < grc) continue; *belong = ce->cache_belong; (void) rw_unlock(&cache_lock); return (1); } (void) rw_unlock(&cache_lock); return (0); } /* * Put a new entry in the cache chain by * prepending it to the front. * If there isn't enough memory then just give up. */ static void cache_enter(char *host, char **grl, int grc, int belong) { struct cache_entry *entry; int i; entry = malloc(sizeof (*entry)); if (entry == NULL) return; (void) memset((caddr_t)entry, 0, sizeof (*entry)); entry->cache_host = strdup(host); if (entry->cache_host == NULL) { cache_free(entry); return; } entry->cache_time = time(NULL) + VALID_TIME; entry->cache_belong = belong; entry->cache_grl = malloc(grc * sizeof (char *)); if (entry->cache_grl == NULL) { cache_free(entry); return; } for (i = 0; i < grc; i++) { entry->cache_grl[i] = strdup(grl[i]); if (entry->cache_grl[i] == NULL) { entry->cache_grc = i; cache_free(entry); return; } } entry->cache_grc = grc; (void) rw_wrlock(&cache_lock); entry->cache_next = cache_head; cache_head = entry; (void) rw_unlock(&cache_lock); } /* * Full cache flush */ void netgrp_cache_flush(void) { (void) rw_wrlock(&cache_lock); cache_free(cache_head); cache_head = NULL; (void) rw_unlock(&cache_lock); } /* * 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 (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../lib/sharetab.h" #include "mountd.h" /* * The following codesets must match what is in libshare_nfs.c until we can * request them from the kernel. */ char *charopts[] = { "euc-cn", "euc-jp", "euc-jpms", "euc-kr", "euc-tw", "iso8859-1", "iso8859-2", "iso8859-5", "iso8859-6", "iso8859-7", "iso8859-8", "iso8859-9", "iso8859-13", "iso8859-15", "koi8-r", NULL }; /* * nfscmd_err(dp, args, err) * Return an error for the door call. */ static void nfscmd_err(door_desc_t *dp, nfscmd_arg_t *args, int err) { nfscmd_res_t res; res.version = NFSCMD_VERS_1; res.cmd = NFSCMD_ERROR; res.error = err; (void) door_return((char *)&res, sizeof (nfscmd_res_t), NULL, 0); (void) door_return(NULL, 0, NULL, 0); /* NOTREACHED */ } /* * charmap_search(netbuf, opts) * * Check to see if the address in the netbuf is found in * a character map spec in the opts option string. Returns the charset * name if found. */ static char * charmap_search(struct netbuf *nbuf, char *opts) { char *copts; char *next; char *name; char *result = NULL; char *netid; struct sockaddr *sa; struct cln cln; sa = (struct sockaddr *)nbuf->buf; switch (sa->sa_family) { case AF_INET: netid = "tcp"; break; case AF_INET6: netid = "tcp6"; break; default: return (NULL); } copts = strdup(opts); if (copts == NULL) return (NULL); cln_init_lazy(&cln, netid, nbuf); next = copts; while (*next != '\0') { char *val; name = next; if (getsubopt(&next, charopts, &val) >= 0) { char *cp; /* * name will have the whole opt and val the value. Set * the '=' to '\0' and we have the charmap in name and * the access list in val. */ cp = strchr(name, '='); if (cp != NULL) *cp = '\0'; if (in_access_list(&cln, val) > 0) { result = name; break; } } } if (result != NULL) result = strdup(result); cln_fini(&cln); free(copts); return (result); } /* * nfscmd_charmap_lookup(door, args) * * Check to see if there is a translation requested for the path * specified in the request. If there is, return the charset name. */ static void nfscmd_charmap_lookup(door_desc_t *dp, nfscmd_arg_t *args) { nfscmd_res_t res; struct netbuf nb; struct sockaddr sa; struct share *sh = NULL; char *name; memset(&res, '\0', sizeof (res)); res.version = NFSCMD_VERS_1; res.cmd = NFSCMD_CHARMAP_LOOKUP; sh = findentry(args->arg.charmap.path); if (sh != NULL) { nb.len = nb.maxlen = sizeof (struct sockaddr); nb.buf = (char *)&sa; sa = args->arg.charmap.addr; name = charmap_search(&nb, sh->sh_opts); if (name != NULL) { (void) strcpy(res.result.charmap.codeset, name); res.result.charmap.apply = B_TRUE; res.error = NFSCMD_ERR_SUCCESS; free(name); } else { res.result.charmap.apply = B_FALSE; res.error = NFSCMD_ERR_NOTFOUND; } sharefree(sh); } else { res.error = NFSCMD_ERR_NOTFOUND; } (void) door_return((char *)&res, sizeof (nfscmd_res_t), NULL, 0); (void) door_return(NULL, 0, NULL, 0); /* NOTREACHED */ } /* * nfscmd_ver_1(door, args, size) * * Version 1 of the door command processor for nfs cmds. */ static void nfscmd_vers_1(door_desc_t *dp, nfscmd_arg_t *args, size_t size) { switch (args->cmd) { case NFSCMD_CHARMAP_LOOKUP: nfscmd_charmap_lookup(dp, args); break; default: nfscmd_err(dp, args, NFSCMD_ERR_BADCMD); break; } } /* * nfscmd_func(cookie, dataptr, size, door, ndesc) * * The function called by the door thread for processing * nfscmd type commands. */ void nfscmd_func(void *cookie, char *dataptr, size_t arg_size, door_desc_t *dp, uint_t n_desc) { nfscmd_arg_t *args; args = (nfscmd_arg_t *)dataptr; switch (args->version) { case NFSCMD_VERS_1: nfscmd_vers_1(dp, args, arg_size); break; default: syslog(LOG_ERR, gettext("Invalid nfscmd version")); break; } (void) door_return((caddr_t)args, sizeof (nfscmd_res_t), NULL, 0); (void) door_return(NULL, 0, NULL, 0); /* NOTREACHED */ } /* * 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 "mountd.h" static void nfsauth_access(auth_req *argp, auth_res *result) { struct netbuf nbuf; struct share *sh; struct cln cln; result->auth_perm = NFSAUTH_DENIED; nbuf.len = argp->req_client.n_len; nbuf.buf = argp->req_client.n_bytes; if (nbuf.len == 0 || nbuf.buf == NULL) return; /* * Find the export */ sh = findentry(argp->req_path); if (sh == NULL) { syslog(LOG_ERR, "%s not exported", argp->req_path); return; } cln_init_lazy(&cln, argp->req_netid, &nbuf); result->auth_perm = check_client(sh, &cln, argp->req_flavor, argp->req_clnt_uid, argp->req_clnt_gid, argp->req_clnt_gids.len, argp->req_clnt_gids.val, &result->auth_srv_uid, &result->auth_srv_gid, &result->auth_srv_gids.len, &result->auth_srv_gids.val); sharefree(sh); if (result->auth_perm == NFSAUTH_DENIED) { char *host = cln_gethost(&cln); if (host != NULL) syslog(LOG_ERR, "%s denied access to %s", host, argp->req_path); } cln_fini(&cln); } void nfsauth_func(void *cookie, char *dataptr, size_t arg_size, door_desc_t *dp, uint_t n_desc) { nfsauth_arg_t *ap; nfsauth_res_t res = {0}; XDR xdrs_a; XDR xdrs_r; size_t rbsz; caddr_t rbuf; varg_t varg = {0}; /* * Decode the inbound door data, so we can look at the cmd. */ xdrmem_create(&xdrs_a, dataptr, arg_size, XDR_DECODE); if (!xdr_varg(&xdrs_a, &varg)) { /* * If the arguments can't be decoded, bail. */ if (varg.vers == V_ERROR) syslog(LOG_ERR, gettext("Arg version mismatch")); res.stat = NFSAUTH_DR_DECERR; goto encres; } /* * Now set the args pointer to the proper version of the args */ switch (varg.vers) { case V_PROTO: ap = &varg.arg_u.arg; break; /* Additional arguments versions go here */ default: syslog(LOG_ERR, gettext("Invalid args version")); res.stat = NFSAUTH_DR_DECERR; goto encres; } /* * Call the specified cmd */ switch (ap->cmd) { case NFSAUTH_ACCESS: nfsauth_access(&ap->areq, &res.ares); res.stat = NFSAUTH_DR_OKAY; break; default: res.stat = NFSAUTH_DR_BADCMD; break; } encres: /* * Free space used to decode the args */ xdr_free(xdr_varg, (char *)&varg); xdr_destroy(&xdrs_a); /* * Encode the results before passing thru door. */ rbsz = xdr_sizeof(xdr_nfsauth_res, &res); if (rbsz == 0) goto failed; rbuf = alloca(rbsz); xdrmem_create(&xdrs_r, rbuf, rbsz, XDR_ENCODE); if (!xdr_nfsauth_res(&xdrs_r, &res)) { xdr_destroy(&xdrs_r); failed: xdr_free(xdr_nfsauth_res, (char *)&res); /* * return only the status code */ res.stat = NFSAUTH_DR_EFAIL; rbsz = sizeof (uint_t); rbuf = (caddr_t)&res.stat; goto out; } xdr_destroy(&xdrs_r); xdr_free(xdr_nfsauth_res, (char *)&res); out: (void) door_return((char *)rbuf, rbsz, NULL, 0); (void) door_return(NULL, 0, NULL, 0); /* NOTREACHED */ } /* * 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 2014 Nexenta Systems, Inc. All rights reserved. */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include bool_t xdr_varg(XDR *xdrs, varg_t *vap) { if (!xdr_u_int(xdrs, &vap->vers)) return (FALSE); switch (vap->vers) { case V_PROTO: if (!xdr_nfsauth_arg(xdrs, &vap->arg_u.arg)) return (FALSE); break; /* Additional versions of the args go here */ default: vap->vers = V_ERROR; return (FALSE); /* NOTREACHED */ } return (TRUE); } bool_t xdr_nfsauth_arg(XDR *xdrs, nfsauth_arg_t *argp) { if (!xdr_u_int(xdrs, &argp->cmd)) return (FALSE); if (!xdr_netobj(xdrs, &argp->areq.req_client)) return (FALSE); if (!xdr_string(xdrs, &argp->areq.req_netid, ~0)) return (FALSE); if (!xdr_string(xdrs, &argp->areq.req_path, A_MAXPATH)) return (FALSE); if (!xdr_int(xdrs, &argp->areq.req_flavor)) return (FALSE); if (!xdr_uid_t(xdrs, &argp->areq.req_clnt_uid)) return (FALSE); if (!xdr_gid_t(xdrs, &argp->areq.req_clnt_gid)) return (FALSE); if (!xdr_array(xdrs, (caddr_t *)&argp->areq.req_clnt_gids.val, &argp->areq.req_clnt_gids.len, NGROUPS_UMAX, (uint_t)sizeof (gid_t), xdr_gid_t)) return (FALSE); return (TRUE); } bool_t xdr_nfsauth_res(XDR *xdrs, nfsauth_res_t *argp) { if (!xdr_u_int(xdrs, &argp->stat)) return (FALSE); if (!xdr_int(xdrs, &argp->ares.auth_perm)) return (FALSE); if (!xdr_uid_t(xdrs, &argp->ares.auth_srv_uid)) return (FALSE); if (!xdr_gid_t(xdrs, &argp->ares.auth_srv_gid)) return (FALSE); if (!xdr_array(xdrs, (caddr_t *)&argp->ares.auth_srv_gids.val, &argp->ares.auth_srv_gids.len, NGROUPS_UMAX, (uint_t)sizeof (gid_t), xdr_gid_t)) return (FALSE); return (TRUE); } /* * 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++; } } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (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 2004 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. FSTYPE= nfs TYPEPROG= nfs4cbd include ../../Makefile.fstype OBJS= $(TYPEPROG).o nfs_tbind.o thrpool.o SRCS= $(TYPEPROG).c ../../fslib.c ../lib/nfs_tbind.c ../lib/thrpool.c LDLIBS += -lnsl CPPFLAGS += -I. -I../.. -I../lib CFLAGS += $(CCVERBOSE) CERRWARN += -Wno-unused-variable CERRWARN += -Wno-parentheses CERRWARN += -Wno-extra # Hammerhead: thrpool.c uses int-to-pointer and pointer-to-int casts CERRWARN += -Wno-pointer-to-int-cast CERRWARN += -Wno-int-to-pointer-cast # not linted SMATCH=off .KEEP_STATE: all: $(TYPEPROG) $(TYPEPROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) nfs_tbind.o: ../lib/nfs_tbind.c $(COMPILE.c) ../lib/nfs_tbind.c thrpool.o: ../lib/thrpool.c $(COMPILE.c) ../lib/thrpool.c install: $(TYPEPROG) clean: $(RM) $(OBJS) /* * 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) 2003, 2010, Oracle and/or its affiliates. All rights reserved. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Portions of this source code were derived from Berkeley 4.3 BSD * under license from the Regents of the University of California. */ /* * This module provides the user level support for the NFSv4 * callback program. It is modeled after nfsd. When a nfsv4 * mount occurs, the mount command forks and the child runs * start_nfs4_callback. If this is the first mount, then the * process will hang around listening for incoming connection * requests from the nfsv4 server. * * For connection-less protocols, the krpc is started immediately. * For connection oriented protocols, the kernel module is informed * of netid and universal address that it can give this * information to the server during setclientid. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nfs_tbind.h" #include "thrpool.h" #include #include #include #include #include #include static int nfs4svc(int, struct netbuf *, struct netconfig *, int, struct netbuf *); extern int _nfssys(int, void *); static char *MyName; /* * The following are all globals used by routines in nfs_tbind.c. */ size_t end_listen_fds; /* used by conn_close_oldest() */ size_t num_fds = 0; /* used by multiple routines */ int listen_backlog = 32; /* used by bind_to_{provider,proto}() */ int num_servers; /* used by cots_listen_event() */ int (*Mysvc)(int, struct netbuf, struct netconfig *) = NULL; /* used by cots_listen_event() */ int max_conns_allowed = -1; /* used by cots_listen_event() */ int main(int argc, char *argv[]) { int pid; int i; struct protob *protobp; struct flock f; pid_t pi; struct svcpool_args cb_svcpool; MyName = "nfs4cbd"; Mysvc4 = nfs4svc; #ifndef DEBUG /* * Close existing file descriptors, open "/dev/null" as * standard input, output, and error, and detach from * controlling terminal. */ closefrom(0); (void) open("/dev/null", O_RDONLY); (void) open("/dev/null", O_WRONLY); (void) dup(1); (void) setsid(); #endif /* * create a child to continue our work * Parent's exit will tell mount command we're ready to go */ if ((pi = fork()) > 0) { exit(0); } if (pi == -1) { (void) syslog(LOG_ERR, "Could not start NFS4_CALLBACK service"); exit(1); } (void) _create_daemon_lock(NFS4CBD, DAEMON_UID, DAEMON_GID); svcsetprio(); if (__init_daemon_priv(PU_RESETGROUPS|PU_CLEARLIMITSET, DAEMON_UID, DAEMON_GID, PRIV_SYS_NFS, (char *)NULL) == -1) { (void) fprintf(stderr, "%s must be run with sufficient" " privileges\n", argv[0]); exit(1); } /* Basic privileges we don't need, remove from E/P. */ __fini_daemon_priv(PRIV_PROC_EXEC, PRIV_PROC_FORK, PRIV_FILE_LINK_ANY, PRIV_PROC_SESSION, PRIV_PROC_INFO, (char *)NULL); /* * establish our lock on the lock file and write our pid to it. * exit if some other process holds the lock, or if there's any * error in writing/locking the file. */ pid = _enter_daemon_lock(NFS4CBD); switch (pid) { case 0: break; case -1: syslog(LOG_ERR, "error locking for %s: %s", NFS4CBD, strerror(errno)); exit(2); default: /* daemon was already running */ exit(0); } openlog(MyName, LOG_PID | LOG_NDELAY, LOG_DAEMON); cb_svcpool.id = NFS_CB_SVCPOOL_ID; cb_svcpool.maxthreads = 0; cb_svcpool.redline = 0; cb_svcpool.qsize = 0; cb_svcpool.timeout = 0; cb_svcpool.stksize = 0; cb_svcpool.max_same_xprt = 0; /* create a SVC_POOL for the nfsv4 callback deamon */ if (_nfssys(SVCPOOL_CREATE, &cb_svcpool)) { (void) syslog(LOG_ERR, "can't setup NFS_CB SVCPOOL: Exiting"); exit(1); } /* * Set up blocked thread to do LWP creation on behalf of the kernel. */ if (svcwait(NFS_CB_SVCPOOL_ID)) { (void) syslog(LOG_ERR, "Can't set up NFS_CB LWP creator: Exiting"); exit(1); } /* * Build a protocol block list for registration. */ protobp = (struct protob *)malloc(sizeof (struct protob)); protobp->serv = "NFS4_CALLBACK"; protobp->versmin = NFS_CB; protobp->versmax = NFS_CB; protobp->program = NFS4_CALLBACK; protobp->next = NULL; if (do_all(protobp, NULL) == -1) { exit(1); } free(protobp); if (num_fds == 0) { (void) syslog(LOG_ERR, "Could not start NFS4_CALLBACK service for any protocol"); exit(1); } end_listen_fds = num_fds; /* * Poll for non-data control events on the transport descriptors. */ poll_for_action(); /* * If we get here, something failed in poll_for_action(). */ return (1); } char * get_uaddr(int fd, struct netconfig *nconf, struct netbuf *nb) { struct nfs_svc_args nsa; char *ua, *ua2, *mua = NULL; char me[MAXHOSTNAMELEN]; struct nd_addrlist *nas; struct nd_hostserv hs; struct nd_mergearg ma; ua = taddr2uaddr(nconf, nb); if (ua == NULL) { #ifdef DEBUG fprintf(stderr, "taddr2uaddr failed for netid %s\n", nconf->nc_netid); #endif return (NULL); } gethostname(me, MAXHOSTNAMELEN); hs.h_host = me; hs.h_serv = "nfs"; if (netdir_getbyname(nconf, &hs, &nas)) { #ifdef DEBUG netdir_perror("netdir_getbyname"); #endif return (NULL); } ua2 = taddr2uaddr(nconf, nas->n_addrs); if (ua2 == NULL) { #ifdef DEBUG fprintf(stderr, "taddr2uaddr failed for netid %s.\n", nconf->nc_netid); #endif return (NULL); } ma.s_uaddr = ua; ma.c_uaddr = ua2; ma.m_uaddr = NULL; if (netdir_options(nconf, ND_MERGEADDR, 0, (char *)&ma)) { #ifdef DEBUG netdir_perror("netdir_options"); #endif return (NULL); } mua = ma.m_uaddr; return (mua); } /* * Establish NFS4 callback service thread. */ static int nfs4svc(int fd, struct netbuf *addrmask, struct netconfig *nconf, int cmd, struct netbuf *addr) { struct nfs4_svc_args nsa; char *ua; int error; ua = get_uaddr(fd, nconf, addr); if (ua == NULL) { syslog(LOG_NOTICE, "nfsv4 cannot determine local hostname " "binding for transport %s - delegations will not be " "available on this transport\n", nconf->nc_netid); return (0); } #ifdef DEBUG if (cmd & NFS4_KRPC_START) fprintf(stderr, "nfs4cbd: starting callback rpc on %s %s\n", nconf->nc_netid, ua); else fprintf(stderr, "nfs4cbd: listening on %s %s\n", nconf->nc_netid, ua); #endif nsa.fd = fd; nsa.cmd = cmd; nsa.netid = nconf->nc_netid; if (addrmask) nsa.addrmask = *addrmask; else bzero(&nsa.addrmask, sizeof (struct netbuf)); nsa.addr = ua; nsa.protofmly = nconf->nc_protofmly; nsa.proto = nconf->nc_proto; if ((error = _nfssys(NFS4_SVC, &nsa)) != 0) syslog(LOG_ERR, "nfssys NFS4_SVC failed\n"); return (error); } # # 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) 1990, 2010, Oracle and/or its affiliates. All rights reserved. # # Copyright (c) 2018, Joyent, Inc. FSTYPE= nfs TYPEPROG= nfsd ATTMK= $(TYPEPROG) include ../../Makefile.fstype LDLIBS += -lnsl -lnvpair -lscf LOCAL= nfsd.o OBJS= $(LOCAL) nfs_tbind.o thrpool.o daemon.o smfcfg.o SRCS= $(LOCAL:%.o=%.c) ../lib/nfs_tbind.c ../lib/thrpool.c ../lib/daemon.c \ ../lib/smfcfg.c CPPFLAGS += -I../lib CERRWARN += -Wno-parentheses CERRWARN += -Wno-unused-variable CERRWARN += -Wno-switch CERRWARN += -Wno-extra # Hammerhead: Suppress overflow/pointer cast warnings in legacy NFS code CERRWARN += -Wno-overflow CERRWARN += -Wno-pointer-to-int-cast CERRWARN += -Wno-int-to-pointer-cast # not linted SMATCH=off $(TYPEPROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) nfs_tbind.o: ../lib/nfs_tbind.c $(COMPILE.c) ../lib/nfs_tbind.c thrpool.o: ../lib/thrpool.c $(COMPILE.c) ../lib/thrpool.c daemon.o: ../lib/daemon.c $(COMPILE.c) ../lib/daemon.c smfcfg.o: ../lib/smfcfg.c $(COMPILE.c) ../lib/smfcfg.c clean: $(RM) $(OBJS) /* * 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) 1989, 2010, Oracle and/or its affiliates. All rights reserved. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California * All Rights Reserved * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ /* NFS server */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nfs_tbind.h" #include "thrpool.h" #include "smfcfg.h" /* quiesce requests will be ignored if nfs_server_vers_max < QUIESCE_VERSMIN */ #define QUIESCE_VERSMIN 4 /* DSS: distributed stable storage */ #define DSS_VERSMIN 4 static int nfssvc(int, struct netbuf, struct netconfig *); static int nfssvcpool(int maxservers); static int dss_init(uint_t npaths, char **pathnames); static void dss_mkleafdirs(uint_t npaths, char **pathnames); static void dss_mkleafdir(char *dir, char *leaf, char *path); static void usage(void); int qstrcmp(const void *s1, const void *s2); extern int _nfssys(int, void *); extern int daemonize_init(void); extern void daemonize_fini(int fd); /* signal handlers */ static void sigflush(int); static void quiesce(int); static char *MyName; static NETSELDECL(defaultproviders)[] = { "/dev/tcp6", "/dev/tcp", "/dev/udp", "/dev/udp6", NULL }; /* * The following are all globals used by routines in nfs_tbind.c. */ size_t end_listen_fds; /* used by conn_close_oldest() */ size_t num_fds = 0; /* used by multiple routines */ int listen_backlog = 32; /* used by bind_to_{provider,proto}() */ int num_servers; /* used by cots_listen_event() */ int (*Mysvc)(int, struct netbuf, struct netconfig *) = nfssvc; /* used by cots_listen_event() */ int max_conns_allowed = -1; /* used by cots_listen_event() */ /* * Keep track of min/max versions of NFS protocol to be started. * Start with the defaults (min == 2, max == 4). * Used NFS_VERS_... and should be analyzed with NFS_PROT_VERSION * macros. */ uint32_t nfs_server_vers_min = NFS_SRV_VERS_MIN; uint32_t nfs_server_vers_max = NFS_SRV_VERS_MAX; /* * Set the default for server delegation enablement and set per * /etc/default/nfs configuration (if present). */ int nfs_server_delegation = NFS_SERVER_DELEGATION_DEFAULT; int main(int ac, char *av[]) { char *dir = "/"; int allflag = 0; int df_allflag = 0; int opt_cnt = 0; int maxservers = 1024; /* zero allows inifinte number of threads */ int maxservers_set = 0; int logmaxservers = 0; int pid; int i; char *provider = NULL; char *df_provider = NULL; struct protob *protobp0, *protobp; NETSELDECL(proto) = NULL; NETSELDECL(df_proto) = NULL; NETSELPDECL(providerp); char *defval; boolean_t can_do_mlp; uint_t dss_npaths = 0; char **dss_pathnames = NULL; sigset_t sgset; char name[PATH_MAX], value[PATH_MAX]; int ret, bufsz; int pipe_fd = -1; const char *errstr; MyName = *av; /* * Initializations that require more privileges than we need to run. */ (void) _create_daemon_lock(NFSD, DAEMON_UID, DAEMON_GID); svcsetprio(); can_do_mlp = priv_ineffect(PRIV_NET_BINDMLP); if (__init_daemon_priv(PU_RESETGROUPS|PU_CLEARLIMITSET, DAEMON_UID, DAEMON_GID, PRIV_SYS_NFS, can_do_mlp ? PRIV_NET_BINDMLP : NULL, NULL) == -1) { (void) fprintf(stderr, "%s should be run with" " sufficient privileges\n", av[0]); exit(1); } (void) enable_extended_FILE_stdio(-1, -1); /* Upgrade SMF settings, if necessary. */ nfs_config_upgrade(NFSD); /* * Read in the values from SMF first before we check * command line options so the options override SMF values. */ bufsz = PATH_MAX; ret = nfs_smf_get_prop("max_connections", value, DEFAULT_INSTANCE, SCF_TYPE_INTEGER, NFSD, &bufsz); if (ret == SA_OK) { max_conns_allowed = strtonum(value, -1, INT32_MAX, &errstr); if (errstr != NULL) max_conns_allowed = -1; } bufsz = PATH_MAX; ret = nfs_smf_get_prop("listen_backlog", value, DEFAULT_INSTANCE, SCF_TYPE_INTEGER, NFSD, &bufsz); if (ret == SA_OK) { listen_backlog = strtonum(value, 0, INT32_MAX, &errstr); if (errstr != NULL) { listen_backlog = 32; } } bufsz = PATH_MAX; ret = nfs_smf_get_prop("protocol", value, DEFAULT_INSTANCE, SCF_TYPE_ASTRING, NFSD, &bufsz); if ((ret == SA_OK) && strlen(value) > 0) { df_proto = strdup(value); opt_cnt++; if (strncasecmp("ALL", value, 3) == 0) { free(df_proto); df_proto = NULL; df_allflag = 1; } } bufsz = PATH_MAX; ret = nfs_smf_get_prop("device", value, DEFAULT_INSTANCE, SCF_TYPE_ASTRING, NFSD, &bufsz); if ((ret == SA_OK) && strlen(value) > 0) { df_provider = strdup(value); opt_cnt++; } bufsz = PATH_MAX; ret = nfs_smf_get_prop("servers", value, DEFAULT_INSTANCE, SCF_TYPE_INTEGER, NFSD, &bufsz); if (ret == SA_OK) { maxservers = strtonum(value, 1, INT32_MAX, &errstr); if (errstr != NULL) maxservers = 1024; else maxservers_set = 1; } bufsz = PATH_MAX; ret = nfs_smf_get_prop("server_versmin", value, DEFAULT_INSTANCE, SCF_TYPE_ASTRING, NFSD, &bufsz); if (ret == SA_OK) { ret = nfs_convert_version_str(value); if (ret == 0) { (void) fprintf(stderr, "invalid server_versmin: %s\n", value); } else { nfs_server_vers_min = ret; } } bufsz = PATH_MAX; ret = nfs_smf_get_prop("server_versmax", value, DEFAULT_INSTANCE, SCF_TYPE_ASTRING, NFSD, &bufsz); if (ret == SA_OK) { ret = nfs_convert_version_str(value); if (ret == 0) { (void) fprintf(stderr, "invalid server_versmax: %s\n", value); } else { nfs_server_vers_max = ret; } } bufsz = PATH_MAX; ret = nfs_smf_get_prop("server_delegation", value, DEFAULT_INSTANCE, SCF_TYPE_ASTRING, NFSD, &bufsz); if (ret == SA_OK) if (strncasecmp(value, "off", 3) == 0) nfs_server_delegation = FALSE; /* * Conflict options error messages. */ if (opt_cnt > 1) { (void) fprintf(stderr, "\nConflicting options, only one of " "the following options can be specified\n" "in SMF:\n" "\tprotocol=ALL\n" "\tprotocol=protocol\n" "\tdevice=devicename\n\n"); usage(); } opt_cnt = 0; while ((i = getopt(ac, av, "ac:p:s:t:l:")) != EOF) { switch (i) { case 'a': free(df_proto); df_proto = NULL; free(df_provider); df_provider = NULL; allflag = 1; opt_cnt++; break; case 'c': max_conns_allowed = atoi(optarg); break; case 'p': proto = optarg; df_allflag = 0; opt_cnt++; break; /* * DSS: NFSv4 distributed stable storage. * * This is a Contracted Project Private interface, for * the sole use of Sun Cluster HA-NFS. See PSARC/2006/313. */ case 's': if (strlen(optarg) < MAXPATHLEN) { /* first "-s" option encountered? */ if (dss_pathnames == NULL) { /* * Allocate maximum possible space * required given cmdline arg count; * "-s " consumes two args. */ size_t sz = (ac / 2) * sizeof (char *); dss_pathnames = (char **)malloc(sz); if (dss_pathnames == NULL) { (void) fprintf(stderr, "%s: " "dss paths malloc failed\n", av[0]); exit(1); } (void) memset(dss_pathnames, 0, sz); } dss_pathnames[dss_npaths] = optarg; dss_npaths++; } else { (void) fprintf(stderr, "%s: -s pathname too long.\n", av[0]); } break; case 't': provider = optarg; df_allflag = 0; opt_cnt++; break; case 'l': listen_backlog = atoi(optarg); break; case '?': usage(); /* NOTREACHED */ } } allflag = df_allflag; if (proto == NULL) proto = df_proto; if (provider == NULL) provider = df_provider; /* * Conflict options error messages. */ if (opt_cnt > 1) { (void) fprintf(stderr, "\nConflicting options, only one of " "the following options can be specified\n" "on the command line:\n" "\t-a\n" "\t-p protocol\n" "\t-t transport\n\n"); usage(); } if (proto != NULL && strncasecmp(proto, NC_UDP, strlen(NC_UDP)) == 0) { if (NFS_PROT_VERSION(nfs_server_vers_max) == NFS_V4) { if (NFS_PROT_VERSION(nfs_server_vers_min) == NFS_V4) { fprintf(stderr, "NFS version 4 is not supported " "with the UDP protocol. Exiting\n"); exit(3); } else { fprintf(stderr, "NFS version 4 is not supported " "with the UDP protocol.\n"); } } } /* * If there is exactly one more argument, it is the number of * servers. */ if (optind == ac - 1) { maxservers = atoi(av[optind]); maxservers_set = 1; } /* * If there are two or more arguments, then this is a usage error. */ else if (optind < ac - 1) usage(); /* * Check the ranges for min/max version specified */ else if ((nfs_server_vers_min > nfs_server_vers_max) || (nfs_server_vers_min < NFS_SRV_VERS_MIN) || (nfs_server_vers_max > NFS_SRV_VERS_MAX)) usage(); /* * There are no additional arguments, and we haven't set maxservers * explicitly via the config file, we use a default number of * servers. We will log this. */ else if (maxservers_set == 0) logmaxservers = 1; /* * Basic Sanity checks on options * * max_conns_allowed must be positive, except for the special * value of -1 which is used internally to mean unlimited, -1 isn't * documented but we allow it anyway. * * maxservers must be positive * listen_backlog must be positive or zero */ if (((max_conns_allowed != -1) && (max_conns_allowed <= 0)) || (listen_backlog < 0) || (maxservers <= 0)) { usage(); } /* * Set current dir to server root */ if (chdir(dir) < 0) { (void) fprintf(stderr, "%s: ", MyName); perror(dir); exit(1); } #ifndef DEBUG pipe_fd = daemonize_init(); #endif openlog(MyName, LOG_PID | LOG_NDELAY, LOG_DAEMON); /* * establish our lock on the lock file and write our pid to it. * exit if some other process holds the lock, or if there's any * error in writing/locking the file. */ pid = _enter_daemon_lock(NFSD); switch (pid) { case 0: break; case -1: fprintf(stderr, "error locking for %s: %s\n", NFSD, strerror(errno)); exit(2); default: /* daemon was already running */ exit(0); } /* * If we've been given a list of paths to be used for distributed * stable storage, and provided we're going to run a version * that supports it, setup the DSS paths. */ if (dss_pathnames != NULL && NFS_PROT_VERSION(nfs_server_vers_max) >= DSS_VERSMIN) { if (dss_init(dss_npaths, dss_pathnames) != 0) { fprintf(stderr, "%s", "dss_init failed. Exiting.\n"); exit(1); } } /* * Block all signals till we spawn other * threads. */ (void) sigfillset(&sgset); (void) thr_sigsetmask(SIG_BLOCK, &sgset, NULL); if (logmaxservers) { fprintf(stderr, "Number of servers not specified. Using default of %d.\n", maxservers); } /* * Make sure to unregister any previous versions in case the * user is reconfiguring the server in interesting ways. */ svc_unreg(NFS_PROGRAM, NFS_VERSION); svc_unreg(NFS_PROGRAM, NFS_V3); svc_unreg(NFS_PROGRAM, NFS_V4); svc_unreg(NFS_ACL_PROGRAM, NFS_ACL_V2); svc_unreg(NFS_ACL_PROGRAM, NFS_ACL_V3); /* * Set up kernel RPC thread pool for the NFS server. */ if (nfssvcpool(maxservers)) { fprintf(stderr, "Can't set up kernel NFS service: %s. " "Exiting.\n", strerror(errno)); exit(1); } /* * Set up blocked thread to do LWP creation on behalf of the kernel. */ if (svcwait(NFS_SVCPOOL_ID)) { fprintf(stderr, "Can't set up NFS pool creator: %s. Exiting.\n", strerror(errno)); exit(1); } /* * RDMA start and stop thread. * Per pool RDMA listener creation and * destructor thread. * * start rdma services and block in the kernel. * (only if proto or provider is not set to TCP or UDP) */ if ((proto == NULL) && (provider == NULL)) { if (svcrdma(NFS_SVCPOOL_ID, nfs_server_vers_min, nfs_server_vers_max, nfs_server_delegation)) { fprintf(stderr, "Can't set up RDMA creator thread : %s\n", strerror(errno)); } } /* * Now open up for signal delivery */ (void) thr_sigsetmask(SIG_UNBLOCK, &sgset, NULL); sigset(SIGTERM, sigflush); sigset(SIGUSR1, quiesce); /* * Build a protocol block list for registration. * In protocol list we have first block for NFS and second * block for NFS_ACL - which is needed up to v3, as support * for ACL is included in NFS protocol since v4. */ protobp0 = protobp = (struct protob *)malloc(sizeof (struct protob)); protobp->serv = "NFS"; protobp->versmin = NFS_PROT_VERSION(nfs_server_vers_min); protobp->versmax = NFS_PROT_VERSION(nfs_server_vers_max); protobp->program = NFS_PROGRAM; protobp->next = (struct protob *)malloc(sizeof (struct protob)); protobp = protobp->next; protobp->serv = "NFS_ACL"; /* not used */ protobp->versmin = NFS_PROT_VERSION(nfs_server_vers_min); /* XXX - this needs work to get the version just right */ protobp->versmax = MIN(NFS_PROT_VERSION(nfs_server_vers_max), NFS_ACL_V3); protobp->program = NFS_ACL_PROGRAM; protobp->next = NULL; if (allflag) { if (do_all(protobp0, nfssvc) == -1) { fprintf(stderr, "setnetconfig failed : %s\n", strerror(errno)); exit(1); } } else if (proto) { /* there's more than one match for the same protocol */ struct netconfig *nconf; NCONF_HANDLE *nc; bool_t protoFound = FALSE; if ((nc = setnetconfig()) == (NCONF_HANDLE *) NULL) { fprintf(stderr, "setnetconfig failed : %s\n", strerror(errno)); goto done; } while (nconf = getnetconfig(nc)) { if (strcmp(nconf->nc_proto, proto) == 0) { protoFound = TRUE; do_one(nconf->nc_device, NULL, protobp0, nfssvc); } } (void) endnetconfig(nc); if (protoFound == FALSE) { fprintf(stderr, "couldn't find netconfig entry for protocol %s\n", proto); } } else if (provider) do_one(provider, proto, protobp0, nfssvc); else { for (providerp = defaultproviders; *providerp != NULL; providerp++) { provider = *providerp; do_one(provider, NULL, protobp0, nfssvc); } } done: free(protobp); free(protobp0); if (num_fds == 0) { fprintf(stderr, "Could not start NFS service for any protocol." " Exiting.\n"); exit(1); } end_listen_fds = num_fds; /* * nfsd is up and running as far as we are concerned. */ daemonize_fini(pipe_fd); /* * Get rid of unneeded privileges. */ __fini_daemon_priv(PRIV_PROC_FORK, PRIV_PROC_EXEC, PRIV_PROC_SESSION, PRIV_FILE_LINK_ANY, PRIV_PROC_INFO, (char *)NULL); /* * Poll for non-data control events on the transport descriptors. */ poll_for_action(); /* * If we get here, something failed in poll_for_action(). */ return (1); } static int nfssvcpool(int maxservers) { struct svcpool_args npa; npa.id = NFS_SVCPOOL_ID; npa.maxthreads = maxservers; npa.redline = 0; npa.qsize = 0; npa.timeout = 0; npa.stksize = 0; npa.max_same_xprt = 0; return (_nfssys(SVCPOOL_CREATE, &npa)); } /* * Establish NFS service thread. */ static int nfssvc(int fd, struct netbuf addrmask, struct netconfig *nconf) { struct nfs_svc_args nsa; nsa.fd = fd; nsa.netid = nconf->nc_netid; nsa.addrmask = addrmask; if (strncasecmp(nconf->nc_proto, NC_UDP, strlen(NC_UDP)) == 0) { nsa.nfs_versmax = MIN(nfs_server_vers_max, NFS_VERS_3); nsa.nfs_versmin = nfs_server_vers_min; /* * If no version left, silently do nothing, previous * checks will have assured at least TCP is available. */ if (nsa.nfs_versmin > nsa.nfs_versmax) return (0); } else { nsa.nfs_versmax = nfs_server_vers_max; nsa.nfs_versmin = nfs_server_vers_min; } nsa.delegation = nfs_server_delegation; return (_nfssys(NFS_SVC, &nsa)); } static void usage(void) { (void) fprintf(stderr, "usage: %s [ -a ] [ -c max_conns ] [ -p protocol ] [ -t transport ] ", MyName); (void) fprintf(stderr, "\n[ -l listen_backlog ] [ nservers ]\n"); (void) fprintf(stderr, "\twhere -a causes to be started on each appropriate transport,\n"); (void) fprintf(stderr, "\tmax_conns is the maximum number of concurrent connections allowed,\n"); (void) fprintf(stderr, "\t\tand max_conns must be a decimal number"); (void) fprintf(stderr, "> zero,\n"); (void) fprintf(stderr, "\tprotocol is a protocol identifier,\n"); (void) fprintf(stderr, "\ttransport is a transport provider name (i.e. device),\n"); (void) fprintf(stderr, "\tlisten_backlog is the TCP listen backlog,\n"); (void) fprintf(stderr, "\tand must be a decimal number > zero.\n"); exit(1); } /* * Issue nfssys system call to flush all logging buffers asynchronously. * * NOTICE: It is extremely important to flush NFS logging buffers when * nfsd exits. When the system is halted or rebooted nfslogd * may not have an opportunity to flush the buffers. */ static void nfsl_flush() { struct nfsl_flush_args nfa; memset((void *)&nfa, 0, sizeof (nfa)); nfa.version = NFSL_FLUSH_ARGS_VERS; nfa.directive = NFSL_ALL; /* flush all asynchronously */ if (_nfssys(LOG_FLUSH, &nfa) < 0) syslog(LOG_ERR, "_nfssys(LOG_FLUSH) failed: %s\n", strerror(errno)); } /* * SIGTERM handler. * Flush logging buffers and exit. */ static void sigflush(int sig) { nfsl_flush(); _exit(0); } /* * SIGUSR1 handler. * * Request that server quiesce, then (nfsd) exit. For subsequent warm start. * * This is a Contracted Project Private interface, for the sole use * of Sun Cluster HA-NFS. See PSARC/2004/497. * * Equivalent to SIGTERM handler if nfs_server_vers_max < QUIESCE_VERSMIN. */ static void quiesce(int sig) { int error; int id = NFS_SVCPOOL_ID; if (NFS_PROT_VERSION(nfs_server_vers_max) >= QUIESCE_VERSMIN) { /* Request server quiesce at next shutdown */ error = _nfssys(NFS4_SVC_REQUEST_QUIESCE, &id); /* * ENOENT is returned if there is no matching SVC pool * for the id. Possibly because the pool is not yet setup. * In this case, just exit as if no error. For all other errors, * just return and allow caller to retry. */ if (error && errno != ENOENT) { syslog(LOG_ERR, "_nfssys(NFS4_SVC_REQUEST_QUIESCE) failed: %s", strerror(errno)); return; } } /* Flush logging buffers */ nfsl_flush(); _exit(0); } /* * DSS: distributed stable storage. * Create leaf directories as required, keeping an eye on path * lengths. Calls exit(1) on failure. * The pathnames passed in must already exist, and must be writeable by nfsd. * Note: the leaf directories under NFS4_VAR_DIR are not created here; * they're created at pkg install. */ static void dss_mkleafdirs(uint_t npaths, char **pathnames) { int i; char *tmppath = NULL; /* * Create the temporary storage used by dss_mkleafdir() here, * rather than in that function, so that it only needs to be * done once, rather than once for each call. Too big to put * on the function's stack. */ tmppath = (char *)malloc(MAXPATHLEN); if (tmppath == NULL) { syslog(LOG_ERR, "tmppath malloc failed. Exiting"); exit(1); } for (i = 0; i < npaths; i++) { char *p = pathnames[i]; dss_mkleafdir(p, NFS4_DSS_STATE_LEAF, tmppath); dss_mkleafdir(p, NFS4_DSS_OLDSTATE_LEAF, tmppath); } free(tmppath); } /* * Create "leaf" in "dir" (which must already exist). * leaf: should start with a '/' */ static void dss_mkleafdir(char *dir, char *leaf, char *tmppath) { /* MAXPATHLEN includes the terminating NUL */ if (strlen(dir) + strlen(leaf) > MAXPATHLEN - 1) { fprintf(stderr, "stable storage path too long: %s%s. " "Exiting.\n", dir, leaf); exit(1); } (void) snprintf(tmppath, MAXPATHLEN, "%s/%s", dir, leaf); /* the directory may already exist: that's OK */ if (mkdir(tmppath, NFS4_DSS_DIR_MODE) == -1 && errno != EEXIST) { fprintf(stderr, "error creating stable storage directory: " "%s: %s. Exiting.\n", strerror(errno), tmppath); exit(1); } } /* * Create the storage dirs, and pass the path list to the kernel. * This requires the nfssrv module to be loaded; the _nfssys() syscall * will fail ENOTSUP if it is not. * Use libnvpair(3LIB) to pass the data to the kernel. */ static int dss_init(uint_t npaths, char **pathnames) { int i, j, nskipped, error; char *bufp; uint32_t bufsize; size_t buflen; nvlist_t *nvl; if (npaths > 1) { /* * We need to remove duplicate paths; this might be user error * in the general case, but HA-NFSv4 can also cause this. * Sort the pathnames array, and NULL out duplicates, * then write the non-NULL entries to a new array. * Sorting will also allow the kernel to optimise its searches. */ qsort(pathnames, npaths, sizeof (char *), qstrcmp); /* now NULL out any duplicates */ i = 0; j = 1; nskipped = 0; while (j < npaths) { if (strcmp(pathnames[i], pathnames[j]) == 0) { pathnames[j] = NULL; j++; nskipped++; continue; } /* skip i over any of its NULLed duplicates */ i = j++; } /* finally, write the non-NULL entries to a new array */ if (nskipped > 0) { int nreal; size_t sz; char **tmp_pathnames; nreal = npaths - nskipped; sz = nreal * sizeof (char *); tmp_pathnames = (char **)malloc(sz); if (tmp_pathnames == NULL) { fprintf(stderr, "tmp_pathnames malloc " "failed\n"); exit(1); } for (i = 0, j = 0; i < npaths; i++) if (pathnames[i] != NULL) tmp_pathnames[j++] = pathnames[i]; free(pathnames); pathnames = tmp_pathnames; npaths = nreal; } } /* Create directories to store the distributed state files */ dss_mkleafdirs(npaths, pathnames); /* Create the name-value pair list */ error = nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0); if (error) { fprintf(stderr, "nvlist_alloc failed: %s\n", strerror(errno)); return (1); } /* Add the pathnames array as a single name-value pair */ error = nvlist_add_string_array(nvl, NFS4_DSS_NVPAIR_NAME, pathnames, npaths); if (error) { fprintf(stderr, "nvlist_add_string_array failed: %s\n", strerror(errno)); nvlist_free(nvl); return (1); } /* * Pack list into contiguous memory, for passing to kernel. * nvlist_pack() will allocate the memory for the buffer, * which we should free() when no longer needed. * NV_ENCODE_XDR for safety across ILP32/LP64 kernel boundary. */ bufp = NULL; error = nvlist_pack(nvl, &bufp, &buflen, NV_ENCODE_XDR, 0); if (error) { fprintf(stderr, "nvlist_pack failed: %s\n", strerror(errno)); nvlist_free(nvl); return (1); } /* Now we have the packed buffer, we no longer need the list */ nvlist_free(nvl); /* * Let the kernel know in advance how big the buffer is. * NOTE: we cannot just pass buflen, since size_t is a long, and * thus a different size between ILP32 userland and LP64 kernel. * Use an int for the transfer, since that should be big enough; * this is a no-op at the moment, here, since nfsd is 32-bit, but * that could change. */ bufsize = (uint32_t)buflen; error = _nfssys(NFS4_DSS_SETPATHS_SIZE, &bufsize); if (error) { fprintf(stderr, "_nfssys(NFS4_DSS_SETPATHS_SIZE) failed: %s\n", strerror(errno)); free(bufp); return (1); } /* Pass the packed buffer to the kernel */ error = _nfssys(NFS4_DSS_SETPATHS, bufp); if (error) { fprintf(stderr, "_nfssys(NFS4_DSS_SETPATHS) failed: %s\n", strerror(errno)); free(bufp); return (1); } /* * The kernel has now unpacked the buffer and extracted the * pathnames array, we no longer need the buffer. */ free(bufp); return (0); } /* * Quick sort string compare routine, for qsort. * Needed to make arg types correct. */ int qstrcmp(const void *p1, const void *p2) { char *s1 = *((char **)p1); char *s2 = *((char **)p2); return (strcmp(s1, s2)); } # # 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 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # cmd/fs.d/nfs/nfsfind/Makefile FSTYPE= nfs LIBPROG= nfsfind SRCS= nfsfind.sh include ../../Makefile.fstype #!/bin/sh # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (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) 1993 by Sun Microsystems, Inc. #ident "%Z%%M% %I% %E% SMI" # # Check shared NFS filesystems for .nfs* files that # are more than a week old. # # These files are created by NFS clients when an open file # is removed. To preserve some semblance of Unix semantics # the client renames the file to a unique name so that the # file appears to have been removed from the directory, but # is still usable by the process that has the file open. if [ ! -s /etc/dfs/sharetab ]; then exit ; fi # Get all NFS filesystems exported with read-write permission. DIRS=`/bin/nawk '($3 != "nfs") { next } ($4 ~ /^rw$|^rw,|^rw=|,rw,|,rw=|,rw$/) { print $1; next } ($4 !~ /^ro$|^ro,|^ro=|,ro,|,ro=|,ro$/) { print $1 }' /etc/dfs/sharetab` for dir in $DIRS do find $dir -type f -name .nfs\* -mtime +7 -mount -exec rm -f {} \; done # # 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 2006 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. FSTYPE= nfs TYPEPROG= nfslogd ATTMK= $(TYPEPROG) DEFAULTFILES= nfslogd.dfl include ../../Makefile.fstype COMMON= nfslog_config.o nfslogtab.o LOCAL= process_buffer.o fhtab.o nfslogd.o nfslog_elf.o \ nfslog_trans.o nfslog_ipaddr.o readbuf.o dbtab.o \ nfs_log_xdr.o buffer_list.o OBJS= $(LOCAL) $(COMMON) SRCS= $(LOCAL:%.o=%.c) $(COMMON:%.o=../lib/%.c) LDLIBS += -lsocket -lnsl CFLAGS += $(CCVERBOSE) CERRWARN += -Wno-parentheses CERRWARN += $(CNOWARN_UNINIT) CERRWARN += -Wno-switch CERRWARN += -Wno-type-limits # not linted SMATCH=off CPPFLAGS += -D_FILE_OFFSET_BITS=64 # # Message catalog # POFILE= nfslog.po catalog: $(POFILE) $(POFILE): $(SRCS) $(RM) $@ $(COMPILE.cpp) $(SRCS) > $(POFILE).i $(XGETTEXT) $(XGETFLAGS) $(POFILE).i sed "/^domain/d" messages.po > $@ $(RM) messages.po $(POFILE).i $(TYPEPROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) install: all $(ROOTETCDEFAULTFILES) nfslog_config.o: ../lib/nfslog_config.c $(COMPILE.c) ../lib/nfslog_config.c nfslogtab.o: ../lib/nfslogtab.c $(COMPILE.c) ../lib/nfslogtab.c clean: $(RM) $(OBJS) /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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) 1999 by Sun Microsystems, Inc. * All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include "nfslogd.h" #include "../lib/nfslogtab.h" #include "buffer_list.h" static int buildbuffer_list(struct buffer_ent **, timestruc_t *); static void free_buffer_ent(struct buffer_ent *); static struct buffer_ent *findbuffer(struct buffer_ent *, char *); static void free_sharepnt_list(struct sharepnt_ent *); static void free_sharepnt_ent(struct sharepnt_ent *); #ifdef DEBUG static void print_sharepnt_list(struct sharepnt_ent *); #endif static struct sharepnt_ent *findsharepnt(struct sharepnt_ent *, char *, struct sharepnt_ent **); /* * Builds the buffer list from NFSLOGTAB and returns it in *listpp. * Returns 0 on success, non-zero error code otherwise. */ int getbuffer_list(struct buffer_ent **listpp, timestruc_t *lu) { *listpp = NULL; return (buildbuffer_list(listpp, lu)); } /* * If NFSLOGTAB has not been modified since the last time we read it, * it simply returns the same buffer list, otherwise it re-reads NFSLOGTAB * and rebuilds the list. * No NFSLOGTAB is not treated as an error. * Returns 0 on success, non-zero error code otherwise */ int checkbuffer_list(struct buffer_ent **listpp, timestruc_t *lu) { struct stat st; int error = 0; if (stat(NFSLOGTAB, &st) == -1) { error = errno; if (error != ENOENT) { syslog(LOG_ERR, gettext("Can't stat %s - %s"), NFSLOGTAB, strerror(error)); error = 0; } return (error); } if (lu->tv_sec == st.st_mtim.tv_sec && lu->tv_nsec == st.st_mtim.tv_nsec) return (0); free_buffer_list(listpp); /* free existing list first */ return (buildbuffer_list(listpp, lu)); } /* * Does the actual work of reading NFSLOGTAB, and building the * buffer list. If *be_head already contains entries, it will * update the list with new information. * Returns 0 on success, non-zero error code otherwise. */ static int buildbuffer_list(struct buffer_ent **be_head, timestruc_t *lu) { FILE *fd; struct buffer_ent *be_tail = NULL, *bep; struct sharepnt_ent *se_tail = NULL, *sep; struct logtab_ent *lep; struct stat st; int error = 0, res; if ((fd = fopen(NFSLOGTAB, "r+")) == NULL) { error = errno; if (error != ENOENT) { syslog(LOG_ERR, gettext("%s - %s\n"), NFSLOGTAB, strerror(error)); error = 0; } return (error); } if (lockf(fileno(fd), F_LOCK, 0L) < 0) { error = errno; syslog(LOG_ERR, gettext("cannot lock %s - %s\n"), NFSLOGTAB, strerror(error)); (void) fclose(fd); return (error); } assert(*be_head == NULL); while ((res = logtab_getent(fd, &lep)) > 0) { if (bep = findbuffer(*be_head, lep->le_buffer)) { /* * Add sharepnt to buffer list */ if (sep = findsharepnt(bep->be_sharepnt, lep->le_path, &se_tail)) { /* * Sharepoint already in list, * update its state. */ sep->se_state = lep->le_state; } else { /* * Need to add to sharepoint list */ sep = (struct sharepnt_ent *) malloc(sizeof (*sep)); if (sep == NULL) { error = ENOMEM; goto errout; } (void) memset(sep, 0, sizeof (*sep)); sep->se_name = strdup(lep->le_path); if (sep->se_name == NULL) { error = ENOMEM; goto errout; } sep->se_state = lep->le_state; assert(se_tail != NULL); assert(se_tail->se_next == NULL); se_tail->se_next = sep; } } else { /* * Add new buffer to list */ bep = (struct buffer_ent *)malloc(sizeof (*bep)); if (bep == NULL) { error = ENOMEM; goto errout; } (void) memset(bep, 0, sizeof (*bep)); bep->be_name = strdup(lep->le_buffer); if (bep->be_name == NULL) { error = ENOMEM; goto errout; } if (*be_head == NULL) *be_head = bep; else be_tail->be_next = bep; be_tail = bep; bep->be_sharepnt = (struct sharepnt_ent *) malloc(sizeof (*(bep->be_sharepnt))); (void) memset(bep->be_sharepnt, 0, sizeof (*(bep->be_sharepnt))); if (bep->be_sharepnt == NULL) { error = ENOMEM; goto errout; } bep->be_sharepnt->se_name = strdup(lep->le_path); if (bep->be_sharepnt->se_name == NULL) { error = ENOMEM; goto errout; } bep->be_sharepnt->se_state = lep->le_state; } } if (res < 0) { error = EIO; goto errout; } /* * Get modification time while we have the file locked. */ if (lu) { if ((error = fstat(fileno(fd), &st)) == -1) { syslog(LOG_ERR, gettext("Can't stat %s"), NFSLOGTAB); goto errout; } *lu = st.st_mtim; } (void) fclose(fd); return (error); errout: (void) fclose(fd); if (lep) logtab_ent_free(lep); free_buffer_list(be_head); assert(*be_head == NULL); syslog(LOG_ERR, gettext("cannot read %s: %s\n"), NFSLOGTAB, strerror(error)); return (error); } /* * Removes the entry from the buffer list and frees it. */ void remove_buffer_ent(struct buffer_ent **be_listpp, struct buffer_ent *bep) { struct buffer_ent *p, *prev; for (p = prev = *be_listpp; p != NULL; p = p->be_next) { if (p == bep) { if (p == *be_listpp) *be_listpp = (*be_listpp)->be_next; else prev->be_next = bep->be_next; free_buffer_ent(bep); break; } prev = p; } } /* * Frees the buffer list. */ void free_buffer_list(struct buffer_ent **be_listpp) { struct buffer_ent *bep, *nextp; for (bep = *be_listpp; bep != NULL; bep = nextp) { nextp = bep->be_next; free_buffer_ent(bep); } *be_listpp = NULL; } static void free_buffer_ent(struct buffer_ent *bep) { assert(bep != NULL); if (debug) (void) printf("freeing %s\n", bep->be_name); if (bep->be_name != NULL) free(bep->be_name); if (bep->be_sharepnt != NULL) free_sharepnt_list(bep->be_sharepnt); free(bep); } static void free_sharepnt_list(struct sharepnt_ent *sep_listp) { struct sharepnt_ent *nextp; for (; sep_listp != NULL; sep_listp = nextp) { nextp = sep_listp->se_next; free_sharepnt_ent(sep_listp); } free(sep_listp); } /* * Removes the entry from the sharepnt list and frees it. */ void remove_sharepnt_ent(struct sharepnt_ent **se_listpp, struct sharepnt_ent *sep) { struct sharepnt_ent *p, *prev; for (p = prev = *se_listpp; p != NULL; p = p->se_next) { if (p == sep) { if (p == *se_listpp) *se_listpp = (*se_listpp)->se_next; else prev->se_next = sep->se_next; free_sharepnt_ent(sep); break; } prev = p; } } static void free_sharepnt_ent(struct sharepnt_ent *sep) { assert(sep != NULL); if (debug) (void) printf("freeing %s\n", sep->se_name); if (sep->se_name != NULL) free(sep->se_name); free(sep); } #ifdef DEBUG void printbuffer_list(struct buffer_ent *bep) { for (; bep != NULL; bep = bep->be_next) { (void) printf("%s\n", bep->be_name); if (bep->be_sharepnt != NULL) print_sharepnt_list(bep->be_sharepnt); } } static void print_sharepnt_list(struct sharepnt_ent *sep) { for (; sep != NULL; sep = sep->se_next) (void) printf("\t(%d) %s\n", sep->se_state, sep->se_name); } #endif /* * Returns a pointer to the buffer matching 'name', NULL otherwise. */ static struct buffer_ent * findbuffer(struct buffer_ent *bep, char *name) { for (; bep != NULL; bep = bep->be_next) { if (strcmp(bep->be_name, name) == 0) return (bep); } return (NULL); } /* * Returns a pointer the sharepoint entry matching 'name'. * Otherwise, it sets '*se_tail' to the last element of the list * to make insertion of new element easier, and returns NULL. */ static struct sharepnt_ent * findsharepnt( struct sharepnt_ent *sep, char *name, struct sharepnt_ent **se_tail) { struct sharepnt_ent *tail; for (; sep != NULL; sep = sep->se_next) { if (strcmp(sep->se_name, name) == 0) return (sep); tail = sep; } *se_tail = tail; return (NULL); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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) 1999 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _BUFFER_LIST_H #define _BUFFER_LIST_H #ifdef __cplusplus extern "C" { #endif #include /* * List of work buffers that need to be processed */ struct buffer_ent { uint_t be_flags; int be_error; /* last error reported */ char *be_name; /* work buffer path */ time_t be_lastprocessed; /* last time processed */ struct sharepnt_ent *be_sharepnt; /* share point list */ struct buffer_ent *be_next; }; /* * List of share points that refer to a given work buffer */ struct sharepnt_ent { uint_t se_flags; char *se_name; /* share point path */ int se_state; /* active or inactive? */ struct sharepnt_ent *se_next; }; #define SE_INTABLE 0x01 /* entry still in file table */ extern int getbuffer_list(struct buffer_ent **, timestruc_t *); extern int checkbuffer_list(struct buffer_ent **, timestruc_t *); extern void free_buffer_list(struct buffer_ent **); extern void remove_buffer_ent(struct buffer_ent **, struct buffer_ent *); extern void free_buffer_list(struct buffer_ent **); extern void remove_sharepnt_ent(struct sharepnt_ent **, struct sharepnt_ent *); #ifdef DEBUG extern void printbuffer_list(struct buffer_ent *); #endif #ifdef __cplusplus } #endif #endif /* _BUFFER_LIST_H */ /* * 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 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Code to maintain the runtime and on-disk filehandle mapping table for * nfslog. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fhtab.h" #include "nfslogd.h" #define ROUNDUP32(val) (((val) + 3) & ~3) /* * It is important that this string not match the length of the * file handle key length NFS_FHMAXDATA */ #define DB_VERSION_STRING "NFSLOG_DB_VERSION" #define DB_VERSION "1" #define MAX_PRUNE_REC_CNT 100000 fhandle_t public_fh = { 0 }; struct db_list { fsid_t fsid; /* filesystem fsid */ char *path; /* dbm filepair path */ DBM *db; /* open dbm database */ bool_t getall; /* TRUE if all dbm for prefix open */ struct db_list *next; /* next db */ }; static struct db_list *db_fs_list = NULL; static char err_str[] = "DB I/O error has occurred"; struct link_keys { fh_secondary_key lnkey; int lnsize; struct link_keys *next; }; extern int debug; extern time_t mapping_update_interval; extern time_t prune_timeout; static int fill_link_key(char *linkkey, fhandle_t *dfh, char *name); static struct db_list *db_get_db(char *fhpath, fsid_t *fsid, int *errorp, int create_flag); static struct db_list *db_get_all_databases(char *fhpath, bool_t getall); static void debug_print_fhlist(FILE *fp, fhlist_ent *fhrecp); static void debug_print_linkinfo(FILE *fp, linkinfo_ent *fhrecp); static void debug_print_key(FILE *fp, char *str1, char *str2, char *key, int ksize); static void debug_print_key_and_data(FILE *fp, char *str1, char *str2, char *key, int ksize, char *data, int dsize); static int store_record(struct db_list *dbp, void *keyaddr, int keysize, void *dataaddr, int datasize, char *str); static void *fetch_record(struct db_list *dbp, void *keyaddr, int keysize, void *dataaddr, int *errorp, char *str); static int delete_record(struct db_list *dbp, void *keyaddr, int keysize, char *str); static int db_update_fhrec(struct db_list *dbp, void *keyaddr, int keysize, fhlist_ent *fhrecp, char *str); static int db_update_linkinfo(struct db_list *dbp, void *keyaddr, int keysize, linkinfo_ent *linkp, char *str); static fhlist_ent *create_primary_struct(struct db_list *dbp, fhandle_t *dfh, char *name, fhandle_t *fh, uint_t flags, fhlist_ent *fhrecp, int *errorp); static fhlist_ent *db_add_primary(struct db_list *dbp, fhandle_t *dfh, char *name, fhandle_t *fh, uint_t flags, fhlist_ent *fhrecp, int *errorp); static linkinfo_ent *get_next_link(struct db_list *dbp, char *linkkey, int *linksizep, linkinfo_ent *linkp, void **cookiep, int *errorp, char *msg); static void free_link_cookies(void *cookie); static void add_mc_path(struct db_list *dbp, fhandle_t *dfh, char *name, fhlist_ent *fhrecp, linkinfo_ent *linkp, int *errorp); static linkinfo_ent *create_link_struct(struct db_list *dbp, fhandle_t *dfh, char *name, fhlist_ent *fhrecp, int *errorp); static int db_add_secondary(struct db_list *dbp, fhandle_t *dfh, char *name, fhandle_t *fh, fhlist_ent *fhrecp); static linkinfo_ent *update_next_link(struct db_list *dbp, char *nextkey, int nextsize, char *prevkey, int prevsize, int *errorp); static int update_prev_link(struct db_list *dbp, char *nextkey, int nextsize, char *prevkey, int prevsize); static linkinfo_ent *update_linked_list(struct db_list *dbp, char *nextkey, int nextsize, char *prevkey, int prevsize, int *errorp); static int db_update_primary_new_head(struct db_list *dbp, linkinfo_ent *dellinkp, linkinfo_ent *nextlinkp, fhlist_ent *fhrecp); static int delete_link_by_key(struct db_list *dbp, char *linkkey, int *linksizep, int *errorp, char *errstr); static int delete_link(struct db_list *dbp, fhandle_t *dfh, char *name, char *nextlinkkey, int *nextlinksizep, int *errorp, char *errstr); /* * The following functions do the actual database I/O. Currently use DBM. */ /* * The "db_*" functions are functions that access the database using * database-specific calls. Currently the only database supported is * dbm. Because of the limitations of this database, in particular when * it comes to manipulating records with the same key, or using multiple keys, * the following design decisions have been made: * * Each file system has a separate dbm file, which are kept open as * accessed, listed in a linked list. * Two possible access mode are available for each file - either by * file handle, or by directory file handle and name. Since * dbm does not allow multiple keys, we will have a primary * and secondary key for each file/link. * The primary key is the pair (inode,gen) which can be obtained * from the file handle. This points to a record with * the full file handle and the secondary key (dfh-key,name) * for one of the links. * The secondary key is the pair (dfh-key,name) where dfh-key is * the primary key for the directory and the name is the * link name. It points to a record that contains the primary * key for the file and to the previous and next hard link * found for this file (if they exist). * * Summary of operations: * Adding a new file: Create the primary record and secondary (link) * record and add both to the database. The link record * would have prev and next links set to NULL. * * Adding a link to a file in the database: Add the link record, * to the head of the links list (i.e. prev = NULL, next = * secondary key recorded in the primary record). Update * the primary record to point to the new link, and the * secondary record for the old head of list to point to new. * * Deleting a file: Delete the link record. If it is the last link * then mark the primary record as deleted but don't delete * that one from the database (in case some clients still * hold the file handle). If there are other links, and the * deleted link is the head of the list (in the primary * record), update the primary record with the new head. * * Renaming a file: Add the new link and then delete the old one. * * Lookup by file handle (read, write, lookup, etc.) - fetch primary rec. * Lookup by dir info (delete, link, rename) - fetch secondary rec. * * XXX NOTE: The code is written single-threaded. To make it multi- * threaded, the following considerations must be made: * 1. Changes/access to the db list must be atomic. * 2. Changes/access for a specific file handle must be atomic * (example: deleting a link may affect up to 4 separate database * entries: the deleted link, the prev and next links if exist, * and the filehandle entry, if it points to the deleted link - * these changes must be atomic). */ /* * Create a link key given directory fh and name */ static int fill_link_key(char *linkkey, fhandle_t *dfh, char *name) { int linksize, linksize32; (void) memcpy(linkkey, &dfh->fh_data, dfh->fh_len); (void) strcpy(&linkkey[dfh->fh_len], name); linksize = dfh->fh_len + strlen(name) + 1; linksize32 = ROUNDUP32(linksize); if (linksize32 > linksize) bzero(&linkkey[linksize], linksize32 - linksize); return (linksize32); } /* * db_get_db - gets the database for the filesystem, or creates one * if none exists. Return the pointer for the database in *dbpp if success. * Return 0 for success, error code otherwise. */ static struct db_list * db_get_db(char *fhpath, fsid_t *fsid, int *errorp, int create_flag) { struct db_list *p, *newp; char fsidstr[30]; datum key, data; *errorp = 0; for (p = db_fs_list; (p != NULL) && memcmp(&p->fsid, fsid, sizeof (*fsid)); p = p->next); if (p != NULL) { /* Found it */ return (p); } /* Create it */ if ((newp = calloc(1, sizeof (*newp))) == NULL) { *errorp = errno; syslog(LOG_ERR, gettext( "db_get_db: malloc db failed: Error %s"), strerror(*errorp)); return (NULL); } (void) sprintf(fsidstr, "%08x%08x", fsid->val[0], fsid->val[1]); if ((newp->path = malloc(strlen(fhpath) + 2 + strlen(fsidstr))) == NULL) { *errorp = errno; syslog(LOG_ERR, gettext( "db_get_db: malloc dbpath failed: Error %s"), strerror(*errorp)); goto err_exit; } (void) sprintf(newp->path, "%s.%s", fhpath, fsidstr); /* * The open mode is masked by UMASK. */ if ((newp->db = dbm_open(newp->path, create_flag | O_RDWR, 0666)) == NULL) { *errorp = errno; syslog(LOG_ERR, gettext( "db_get_db: dbm_open db '%s' failed: Error %s"), newp->path, strerror(*errorp)); if (*errorp == 0) /* should not happen but may */ *errorp = -1; goto err_exit; } /* * Add the version identifier (have to check first in the * case the db exists) */ key.dptr = DB_VERSION_STRING; key.dsize = strlen(DB_VERSION_STRING); data = dbm_fetch(newp->db, key); if (data.dptr == NULL) { data.dptr = DB_VERSION; data.dsize = strlen(DB_VERSION); (void) dbm_store(newp->db, key, data, DBM_INSERT); } (void) memcpy(&newp->fsid, fsid, sizeof (*fsid)); newp->next = db_fs_list; db_fs_list = newp; if (debug > 1) { (void) printf("db_get_db: db %s opened\n", newp->path); } return (newp); err_exit: if (newp != NULL) { if (newp->db != NULL) { dbm_close(newp->db); } if (newp->path != NULL) { free(newp->path); } free(newp); } return (NULL); } /* * db_get_all_databases - gets the database for any filesystem. This is used * when any database will do - typically to retrieve the path for the * public filesystem. If any database is open - return the first one, * otherwise, search for it using fhpath. If getall is TRUE, open all * matching databases, and mark them (to indicate that all such were opened). * Return the pointer for a matching database if success. */ static struct db_list * db_get_all_databases(char *fhpath, bool_t getall) { char *dirptr, *fhdir, *fhpathname; int len, error; DIR *dirp; struct dirent *dp; fsid_t fsid; struct db_list *dbp, *ret_dbp; for (dbp = db_fs_list; dbp != NULL; dbp = dbp->next) { if (strncmp(fhpath, dbp->path, strlen(fhpath)) == 0) break; } if (dbp != NULL) { /* * if one database for that prefix is open, and either only * one is needed, or already opened all such databases, * return here without exhaustive search */ if (!getall || dbp->getall) return (dbp); } if ((fhdir = strdup(fhpath)) == NULL) { syslog(LOG_ERR, gettext( "db_get_all_databases: strdup '%s' Error '%s*'"), fhpath, strerror(errno)); return (NULL); } fhpathname = NULL; ret_dbp = NULL; if ((dirptr = strrchr(fhdir, '/')) == NULL) { /* no directory */ goto exit; } if ((fhpathname = strdup(&dirptr[1])) == NULL) { syslog(LOG_ERR, gettext( "db_get_all_databases: strdup '%s' Error '%s*'"), &dirptr[1], strerror(errno)); goto exit; } /* Terminate fhdir string at last '/' */ dirptr[1] = '\0'; /* Search the directory */ if (debug > 2) { (void) printf("db_get_all_databases: search '%s' for '%s*'\n", fhdir, fhpathname); } if ((dirp = opendir(fhdir)) == NULL) { syslog(LOG_ERR, gettext( "db_get_all_databases: opendir '%s' Error '%s*'"), fhdir, strerror(errno)); goto exit; } len = strlen(fhpathname); while ((dp = readdir(dirp)) != NULL) { if (strncmp(fhpathname, dp->d_name, len) == 0) { dirptr = &dp->d_name[len + 1]; if (*(dirptr - 1) != '.') { continue; } (void) sscanf(dirptr, "%08lx%08lx", (ulong_t *)&fsid.val[0], (ulong_t *)&fsid.val[1]); dbp = db_get_db(fhpath, &fsid, &error, 0); if (dbp != NULL) { ret_dbp = dbp; if (!getall) break; dbp->getall = TRUE; } } } (void) closedir(dirp); exit: if (fhpathname != NULL) free(fhpathname); if (fhdir != NULL) free(fhdir); return (ret_dbp); } static void debug_print_key(FILE *fp, char *str1, char *str2, char *key, int ksize) { (void) fprintf(fp, "%s: %s key (%d) ", str1, str2, ksize); debug_opaque_print(fp, key, ksize); /* may be inode,name - try to print the fields */ if (ksize >= NFS_FHMAXDATA) { (void) fprintf(fp, ": inode "); debug_opaque_print(fp, &key[2], sizeof (int)); (void) fprintf(fp, ", gen "); debug_opaque_print(fp, &key[2 + sizeof (int)], sizeof (int)); if (ksize > NFS_FHMAXDATA) { (void) fprintf(fp, ", name '%s'", &key[NFS_FHMAXDATA]); } } (void) fprintf(fp, "\n"); } static void debug_print_linkinfo(FILE *fp, linkinfo_ent *linkp) { if (linkp == NULL) return; (void) fprintf(fp, "linkinfo:\ndfh: "); debug_opaque_print(fp, (void *)&linkp->dfh, sizeof (linkp->dfh)); (void) fprintf(fp, "\nname: '%s'", LN_NAME(linkp)); (void) fprintf(fp, "\nmtime 0x%x, atime 0x%x, flags 0x%x, reclen %d\n", linkp->mtime, linkp->atime, linkp->flags, linkp->reclen); (void) fprintf(fp, "offsets: fhkey %d, name %d, next %d, prev %d\n", linkp->fhkey_offset, linkp->name_offset, linkp->next_offset, linkp->prev_offset); debug_print_key(fp, "fhkey", "", LN_FHKEY(linkp), LN_FHKEY_LEN(linkp)); debug_print_key(fp, "next", "", LN_NEXT(linkp), LN_NEXT_LEN(linkp)); debug_print_key(fp, "prev", "", LN_PREV(linkp), LN_PREV_LEN(linkp)); } static void debug_print_fhlist(FILE *fp, fhlist_ent *fhrecp) { if (fhrecp == NULL) return; (void) fprintf(fp, "fhrec:\nfh: "); debug_opaque_print(fp, (void *)&fhrecp->fh, sizeof (fhrecp->fh)); (void) fprintf(fp, "name '%s', dfh: ", fhrecp->name); debug_opaque_print(fp, (void *)&fhrecp->dfh, sizeof (fhrecp->dfh)); (void) fprintf(fp, "\nmtime 0x%x, atime 0x%x, flags 0x%x, reclen %d\n", fhrecp->mtime, fhrecp->atime, fhrecp->flags, fhrecp->reclen); } static void debug_print_key_and_data(FILE *fp, char *str1, char *str2, char *key, int ksize, char *data, int dsize) { debug_print_key(fp, str1, str2, key, ksize); (void) fprintf(fp, " ==> (%p,%d)\n", (void *)data, dsize); if (ksize > NFS_FHMAXDATA) { linkinfo_ent inf; /* probably a link struct */ (void) memcpy(&inf, data, sizeof (linkinfo_ent)); debug_print_linkinfo(fp, &inf); } else if (ksize == NFS_FHMAXDATA) { fhlist_ent inf; /* probably an fhlist struct */ (void) memcpy(&inf, data, sizeof (linkinfo_ent)); debug_print_fhlist(fp, &inf); } else { /* don't know... */ debug_opaque_print(fp, data, dsize); } } /* * store_record - store the record in the database and return 0 for success * or error code otherwise. */ static int store_record(struct db_list *dbp, void *keyaddr, int keysize, void *dataaddr, int datasize, char *str) { datum key, data; int error; char *errfmt = "store_record: dbm_store failed, Error: %s\n"; char *err; errno = 0; key.dptr = keyaddr; key.dsize = keysize; data.dptr = dataaddr; data.dsize = datasize; if (debug > 2) { debug_print_key_and_data(stdout, str, "dbm_store:\n ", key.dptr, key.dsize, data.dptr, data.dsize); } if (dbm_store(dbp->db, key, data, DBM_REPLACE) < 0) { /* Could not store */ error = dbm_error(dbp->db); dbm_clearerr(dbp->db); if (error) { if (errno) err = strerror(errno); else { err = err_str; errno = EIO; } } else { /* should not happen but sometimes does */ err = err_str; errno = -1; } if (debug) { debug_print_key(stderr, str, "store_record:" "dbm_store:\n", key.dptr, key.dsize); (void) fprintf(stderr, errfmt, err); } else syslog(LOG_ERR, gettext(errfmt), err); return (errno); } return (0); } /* * fetch_record - fetch the record from the database and return 0 for success * and errno for failure. * dataaddr is an optional valid address for the result. If dataaddr * is non-null, then that memory is already alloc'd. Else, alloc it, and * the caller must free the returned struct when done. */ static void * fetch_record(struct db_list *dbp, void *keyaddr, int keysize, void *dataaddr, int *errorp, char *str) { datum key, data; char *errfmt = "fetch_record: dbm_fetch failed, Error: %s\n"; char *err; errno = 0; *errorp = 0; key.dptr = keyaddr; key.dsize = keysize; data = dbm_fetch(dbp->db, key); if (data.dptr == NULL) { /* see if there is a database error */ if (dbm_error(dbp->db)) { /* clear and report the database error */ dbm_clearerr(dbp->db); *errorp = EIO; err = strerror(*errorp); syslog(LOG_ERR, gettext(errfmt), err); } else { /* primary record not in database */ *errorp = ENOENT; } if (debug > 3) { err = strerror(*errorp); debug_print_key(stderr, str, "fetch_record:" "dbm_fetch:\n", key.dptr, key.dsize); (void) fprintf(stderr, errfmt, err); } return (NULL); } /* copy to local struct because dbm may return non-aligned pointers */ if ((dataaddr == NULL) && ((dataaddr = malloc(data.dsize)) == NULL)) { *errorp = errno; syslog(LOG_ERR, gettext( "%s: dbm_fetch - malloc %ld: Error %s"), str, data.dsize, strerror(*errorp)); return (NULL); } (void) memcpy(dataaddr, data.dptr, data.dsize); if (debug > 3) { debug_print_key_and_data(stdout, str, "fetch_record:" "dbm_fetch:\n", key.dptr, key.dsize, dataaddr, data.dsize); } *errorp = 0; return (dataaddr); } /* * delete_record - delete the record from the database and return 0 for success * or error code for failure. */ static int delete_record(struct db_list *dbp, void *keyaddr, int keysize, char *str) { datum key; int error = 0; char *errfmt = "delete_record: dbm_delete failed, Error: %s\n"; char *err; errno = 0; key.dptr = keyaddr; key.dsize = keysize; if (debug > 2) { debug_print_key(stdout, str, "delete_record:" "dbm_delete:\n", key.dptr, key.dsize); } if (dbm_delete(dbp->db, key) < 0) { error = dbm_error(dbp->db); dbm_clearerr(dbp->db); if (error) { if (errno) err = strerror(errno); else { err = err_str; errno = EIO; } } else { /* should not happen but sometimes does */ err = err_str; errno = -1; } if (debug) { debug_print_key(stderr, str, "delete_record:" "dbm_delete:\n", key.dptr, key.dsize); (void) fprintf(stderr, errfmt, err); } else syslog(LOG_ERR, gettext(errfmt), err); } return (errno); } /* * db_update_fhrec - puts fhrec in db with updated atime if more than * mapping_update_interval seconds passed. Return 0 if success, error otherwise. */ static int db_update_fhrec(struct db_list *dbp, void *keyaddr, int keysize, fhlist_ent *fhrecp, char *str) { time_t cur_time = time(0); if (difftime(cur_time, fhrecp->atime) >= mapping_update_interval) { fhrecp->atime = cur_time; return (store_record(dbp, keyaddr, keysize, fhrecp, fhrecp->reclen, str)); } return (0); } /* * db_update_linkinfo - puts linkinfo in db with updated atime if more than * mapping_update_interval seconds passed. Return 0 if success, error otherwise. */ static int db_update_linkinfo(struct db_list *dbp, void *keyaddr, int keysize, linkinfo_ent *linkp, char *str) { time_t cur_time = time(0); if (difftime(cur_time, linkp->atime) >= mapping_update_interval) { linkp->atime = cur_time; return (store_record(dbp, keyaddr, keysize, linkp, linkp->reclen, str)); } return (0); } /* * create_primary_struct - add primary record to the database. * Database must be open when this function is called. * If success, return the added database entry. fhrecp may be used to * provide an existing memory area, else malloc it. If failed, *errorp * contains the error code and return NULL. */ static fhlist_ent * create_primary_struct(struct db_list *dbp, fhandle_t *dfh, char *name, fhandle_t *fh, uint_t flags, fhlist_ent *fhrecp, int *errorp) { int reclen, reclen1; fhlist_ent *new_fhrecp = fhrecp; reclen1 = offsetof(fhlist_ent, name) + strlen(name) + 1; reclen = ROUNDUP32(reclen1); if (fhrecp == NULL) { /* allocated the memory */ if ((new_fhrecp = malloc(reclen)) == NULL) { *errorp = errno; syslog(LOG_ERR, gettext( "create_primary_struct: malloc %d Error %s"), reclen, strerror(*errorp)); return (NULL); } } /* Fill in the fields */ (void) memcpy(&new_fhrecp->fh, fh, sizeof (*fh)); (void) memcpy(&new_fhrecp->dfh, dfh, sizeof (*dfh)); new_fhrecp->flags = flags; if (dfh == &public_fh) new_fhrecp->flags |= PUBLIC_PATH; else new_fhrecp->flags &= ~PUBLIC_PATH; new_fhrecp->mtime = time(0); new_fhrecp->atime = new_fhrecp->mtime; (void) strcpy(new_fhrecp->name, name); if (reclen1 < reclen) { bzero((char *)((uintptr_t)new_fhrecp + reclen1), reclen - reclen1); } new_fhrecp->reclen = reclen; *errorp = store_record(dbp, &fh->fh_data, fh->fh_len, new_fhrecp, new_fhrecp->reclen, "create_primary_struct"); if (*errorp != 0) { /* Could not store */ if (fhrecp == NULL) /* caller did not supply pointer */ free(new_fhrecp); return (NULL); } return (new_fhrecp); } /* * db_add_primary - add primary record to the database. * If record already in and live, return it (even if for a different link). * If in database but marked deleted, replace it. If not in database, add it. * Database must be open when this function is called. * If success, return the added database entry. fhrecp may be used to * provide an existing memory area, else malloc it. If failed, *errorp * contains the error code and return NULL. */ static fhlist_ent * db_add_primary(struct db_list *dbp, fhandle_t *dfh, char *name, fhandle_t *fh, uint_t flags, fhlist_ent *fhrecp, int *errorp) { fhlist_ent *new_fhrecp; fh_primary_key fhkey; if (debug > 2) (void) printf("db_add_primary entered: name '%s'\n", name); bcopy(&fh->fh_data, fhkey, fh->fh_len); new_fhrecp = fetch_record(dbp, fhkey, fh->fh_len, (void *)fhrecp, errorp, "db_add_primary"); if (new_fhrecp != NULL) { /* primary record is in the database */ /* Update atime if needed */ *errorp = db_update_fhrec(dbp, fhkey, fh->fh_len, new_fhrecp, "db_add_primary put fhrec"); if (debug > 2) (void) printf("db_add_primary exits(2): name '%s'\n", name); return (new_fhrecp); } /* primary record not in database - create it */ new_fhrecp = create_primary_struct(dbp, dfh, name, fh, flags, fhrecp, errorp); if (new_fhrecp == NULL) { /* Could not store */ if (debug > 2) (void) printf( "db_add_primary exits(1): name '%s' Error %s\n", name, ((*errorp >= 0) ? strerror(*errorp) : "Unknown")); return (NULL); } if (debug > 2) (void) printf("db_add_primary exits(0): name '%s'\n", name); return (new_fhrecp); } /* * get_next_link - get and check the next link in the chain. * Re-use space if linkp param non-null. Also set *linkkey and *linksizep * to values for next link (*linksizep set to 0 if last link). * cookie is used to detect corrupted link entries XXXXXXX * Return the link pointer or NULL if none. */ static linkinfo_ent * get_next_link(struct db_list *dbp, char *linkkey, int *linksizep, linkinfo_ent *linkp, void **cookiep, int *errorp, char *msg) { int linksize, nextsize; char *nextkey; linkinfo_ent *new_linkp = linkp; struct link_keys *lnp; linksize = *linksizep; if (linksize == 0) return (NULL); *linksizep = 0; new_linkp = fetch_record(dbp, linkkey, linksize, (void *)linkp, errorp, msg); if (new_linkp == NULL) return (NULL); /* Set linkkey to point to next record */ nextsize = LN_NEXT_LEN(new_linkp); if (nextsize == 0) return (new_linkp); /* Add this key to the cookie list */ if ((lnp = malloc(sizeof (struct link_keys))) == NULL) { syslog(LOG_ERR, gettext("get_next_key: malloc error %s\n"), strerror(errno)); if ((new_linkp != NULL) && (linkp == NULL)) free(new_linkp); return (NULL); } (void) memcpy(lnp->lnkey, linkkey, linksize); lnp->lnsize = linksize; lnp->next = *(struct link_keys **)cookiep; *cookiep = (void *)lnp; /* Make sure record does not point to itself or other internal loops */ nextkey = LN_NEXT(new_linkp); for (; lnp != NULL; lnp = lnp->next) { if ((nextsize == lnp->lnsize) && (memcmp( lnp->lnkey, nextkey, nextsize) == 0)) { /* * XXX This entry's next pointer points to * itself. This is only a work-around, remove * this check once bug 4203186 is fixed. */ if (debug) { (void) fprintf(stderr, "%s: get_next_link: last record invalid.\n", msg); debug_print_key_and_data(stderr, msg, "invalid rec:\n ", linkkey, linksize, (char *)new_linkp, new_linkp->reclen); } /* Return as if this is the last link */ return (new_linkp); } } (void) memcpy(linkkey, nextkey, nextsize); *linksizep = nextsize; return (new_linkp); } /* * free_link_cookies - free the cookie list */ static void free_link_cookies(void *cookie) { struct link_keys *dellnp, *lnp; lnp = (struct link_keys *)cookie; while (lnp != NULL) { dellnp = lnp; lnp = lnp->next; free(dellnp); } } /* * add_mc_path - add a mc link to a file that has other links. Add it at end * of linked list. Called when it's known there are other links. */ static void add_mc_path(struct db_list *dbp, fhandle_t *dfh, char *name, fhlist_ent *fhrecp, linkinfo_ent *linkp, int *errorp) { fh_secondary_key linkkey; int linksize, len; linkinfo_ent lastlink, *lastlinkp; void *cookie; linksize = fill_link_key(linkkey, &fhrecp->dfh, fhrecp->name); cookie = NULL; do { lastlinkp = get_next_link(dbp, linkkey, &linksize, &lastlink, &cookie, errorp, "add_mc_path"); } while (linksize > 0); free_link_cookies(cookie); /* reached end of list */ if (lastlinkp == NULL) { /* nothing to do */ if (debug > 1) { (void) fprintf(stderr, "add_mc_path link is null\n"); } return; } /* Add new link after last link */ /* * next - link key for the next in the list - add at end so null. * prev - link key for the previous link in the list. */ linkp->prev_offset = linkp->next_offset; /* aligned */ linksize = fill_link_key(LN_PREV(linkp), &lastlinkp->dfh, LN_NAME(lastlinkp)); linkp->reclen = linkp->prev_offset + linksize; /* aligned */ /* Add the link information to the database */ linksize = fill_link_key(linkkey, dfh, name); *errorp = store_record(dbp, linkkey, linksize, linkp, linkp->reclen, "add_mc_path"); if (*errorp != 0) return; /* Now update previous last link to point forward to new link */ /* Copy prev link out since it's going to be overwritten */ linksize = LN_PREV_LEN(lastlinkp); (void) memcpy(linkkey, LN_PREV(lastlinkp), linksize); /* Update previous last link to point to new one */ len = fill_link_key(LN_NEXT(lastlinkp), dfh, name); lastlinkp->prev_offset = lastlinkp->next_offset + len; /* aligned */ (void) memcpy(LN_PREV(lastlinkp), linkkey, linksize); lastlinkp->reclen = lastlinkp->prev_offset + linksize; /* Update the link information to the database */ linksize = fill_link_key(linkkey, &lastlinkp->dfh, LN_NAME(lastlinkp)); *errorp = store_record(dbp, linkkey, linksize, lastlinkp, lastlinkp->reclen, "add_mc_path prev"); } /* * create_link_struct - create the secondary struct. * (dfh,name) is the secondary key, fhrec is the primary record for the file * and linkpp is a place holder for the record (could be null). * Insert the record to the database. * Return 0 if success, error otherwise. */ static linkinfo_ent * create_link_struct(struct db_list *dbp, fhandle_t *dfh, char *name, fhlist_ent *fhrecp, int *errorp) { fh_secondary_key linkkey; int len, linksize; linkinfo_ent *linkp; if ((linkp = malloc(sizeof (linkinfo_ent))) == NULL) { *errorp = errno; syslog(LOG_ERR, gettext( "create_link_struct: malloc failed: Error %s"), strerror(*errorp)); return (NULL); } if (dfh == &public_fh) linkp->flags |= PUBLIC_PATH; else linkp->flags &= ~PUBLIC_PATH; (void) memcpy(&linkp->dfh, dfh, sizeof (*dfh)); linkp->mtime = time(0); linkp->atime = linkp->mtime; /* Calculate offsets of variable fields */ /* fhkey - primary key (inode/gen) */ /* name - component name (in directory dfh) */ linkp->fhkey_offset = ROUNDUP32(offsetof(linkinfo_ent, varbuf)); len = fill_link_key(LN_FHKEY(linkp), &fhrecp->fh, name); linkp->name_offset = linkp->fhkey_offset + fhrecp->fh.fh_len; linkp->next_offset = linkp->fhkey_offset + len; /* aligned */ /* * next - link key for the next link in the list - NULL if it's * the first link. If this is the public fs, only one link allowed. * Avoid setting a multi-component path as primary path, * unless no choice. */ len = 0; if (memcmp(&fhrecp->dfh, dfh, sizeof (*dfh)) || strcmp(fhrecp->name, name)) { /* different link than the one that's in the record */ if (dfh == &public_fh) { /* parent is public fh - either multi-comp or root */ if (memcmp(&fhrecp->fh, &public_fh, sizeof (public_fh))) { /* multi-comp path */ add_mc_path(dbp, dfh, name, fhrecp, linkp, errorp); if (*errorp != 0) { free(linkp); return (NULL); } return (linkp); } } else { /* new link to a file with a different one already */ len = fill_link_key(LN_NEXT(linkp), &fhrecp->dfh, fhrecp->name); } } /* * prev - link key for the previous link in the list - since we * always insert at the front of the list, it's always initially NULL. */ linkp->prev_offset = linkp->next_offset + len; /* aligned */ linkp->reclen = linkp->prev_offset; /* Add the link information to the database */ linksize = fill_link_key(linkkey, dfh, name); *errorp = store_record(dbp, linkkey, linksize, linkp, linkp->reclen, "create_link_struct"); if (*errorp != 0) { free(linkp); return (NULL); } return (linkp); } /* * db_add_secondary - add secondary record to the database (for the directory * information). * Assumes this is a new link, not yet in the database, and that the primary * record is already in. * If fhrecp is non-null, then fhrecp is the primary record. * Database must be open when this function is called. * Return 0 if success, error code otherwise. */ static int db_add_secondary(struct db_list *dbp, fhandle_t *dfh, char *name, fhandle_t *fh, fhlist_ent *fhrecp) { int nextsize, len, error; linkinfo_ent nextlink, *newlinkp, *nextlinkp; uint_t fhflags; char *nextaddr; fhlist_ent *new_fhrecp = fhrecp; fh_primary_key fhkey; if (debug > 2) (void) printf("db_add_secondary entered: name '%s'\n", name); bcopy(&fh->fh_data, fhkey, fh->fh_len); if (fhrecp == NULL) { /* Fetch the primary record */ new_fhrecp = fetch_record(dbp, fhkey, fh->fh_len, NULL, &error, "db_add_secondary primary"); if (new_fhrecp == NULL) { return (error); } } /* Update fhrec atime if needed */ error = db_update_fhrec(dbp, fhkey, fh->fh_len, new_fhrecp, "db_add_secondary primary"); fhflags = new_fhrecp->flags; /* now create and insert the secondary record */ newlinkp = create_link_struct(dbp, dfh, name, new_fhrecp, &error); if (fhrecp == NULL) { free(new_fhrecp); new_fhrecp = NULL; } if (newlinkp == NULL) { if (debug > 2) (void) printf("create_link_struct '%s' Error %s\n", name, ((error >= 0) ? strerror(error) : "Unknown")); return (error); } nextsize = LN_NEXT_LEN(newlinkp); if (nextsize == 0) { /* No next - can exit now */ if (debug > 2) (void) printf("db_add_secondary: no next link\n"); free(newlinkp); return (0); } /* * Update the linked list to point to new head: replace head of * list in the primary record, then update previous secondary record * to point to new head */ new_fhrecp = create_primary_struct(dbp, dfh, name, fh, fhflags, new_fhrecp, &error); if (new_fhrecp == NULL) { if (debug > 2) (void) printf( "db_add_secondary: replace primary failed\n"); free(newlinkp); return (error); } else if (fhrecp == NULL) { free(new_fhrecp); } /* * newlink is the new head of the list, with its "next" pointing to * the old head, and its "prev" pointing to NULL. We now need to * modify the "next" entry to have its "prev" point to the new entry. */ nextaddr = LN_NEXT(newlinkp); if (debug > 2) { debug_print_key(stderr, "db_add_secondary", "next key\n ", nextaddr, nextsize); } /* Get the next link entry from the database */ nextlinkp = fetch_record(dbp, nextaddr, nextsize, (void *)&nextlink, &error, "db_add_secondary next link"); if (nextlinkp == NULL) { if (debug > 2) (void) printf( "db_add_secondary: fetch next link failed\n"); free(newlinkp); return (error); } /* * since the "prev" field is the only field to be changed, and it's * the last in the link record, we only need to modify it (and reclen). * Re-use link to update the next record. */ len = fill_link_key(LN_PREV(nextlinkp), dfh, name); nextlinkp->reclen = nextlinkp->prev_offset + len; error = store_record(dbp, nextaddr, nextsize, nextlinkp, nextlinkp->reclen, "db_add_secondary"); if (debug > 2) (void) printf( "db_add_secondary exits(%d): name '%s'\n", error, name); free(newlinkp); return (error); } /* * Update the next link to point to the new prev. * Return 0 for success, error code otherwise. * If successful, and nextlinkpp is non-null, * *nextlinkpp contains the record for the next link, since * we may will it if the primary record should be updated. */ static linkinfo_ent * update_next_link(struct db_list *dbp, char *nextkey, int nextsize, char *prevkey, int prevsize, int *errorp) { linkinfo_ent *nextlinkp, *linkp1; if ((nextlinkp = malloc(sizeof (linkinfo_ent))) == NULL) { *errorp = errno; syslog(LOG_ERR, gettext( "update_next_link: malloc next Error %s"), strerror(*errorp)); return (NULL); } linkp1 = nextlinkp; nextlinkp = fetch_record(dbp, nextkey, nextsize, nextlinkp, errorp, "update next"); /* if there is no next record - ok */ if (nextlinkp == NULL) { /* Return no error */ *errorp = 0; free(linkp1); return (NULL); } /* Set its prev to the prev of the deleted record */ nextlinkp->reclen = ROUNDUP32(nextlinkp->reclen - LN_PREV_LEN(nextlinkp) + prevsize); /* Change the len and set prev */ if (prevsize > 0) { (void) memcpy(LN_PREV(nextlinkp), prevkey, prevsize); } /* No other changes needed because prev is last field */ *errorp = store_record(dbp, nextkey, nextsize, nextlinkp, nextlinkp->reclen, "update_next"); if (*errorp != 0) { free(nextlinkp); nextlinkp = NULL; } return (nextlinkp); } /* * Update the prev link to point to the new next. * Return 0 for success, error code otherwise. */ static int update_prev_link(struct db_list *dbp, char *nextkey, int nextsize, char *prevkey, int prevsize) { linkinfo_ent prevlink, *prevlinkp; int diff, error; /* Update its next to the given one */ prevlinkp = fetch_record(dbp, prevkey, prevsize, &prevlink, &error, "update prev"); /* if error there is no next record - ok */ if (prevlinkp == NULL) { return (0); } diff = nextsize - LN_NEXT_LEN(prevlinkp); prevlinkp->reclen = ROUNDUP32(prevlinkp->reclen + diff); /* Change the len and set next - may push prev */ if (diff != 0) { char *ptr = LN_PREV(prevlinkp); prevlinkp->prev_offset += diff; (void) memcpy(LN_PREV(prevlinkp), ptr, LN_PREV_LEN(prevlinkp)); } if (nextsize > 0) { (void) memcpy(LN_NEXT(prevlinkp), nextkey, nextsize); } /* Store updated record */ error = store_record(dbp, prevkey, prevsize, prevlinkp, prevlinkp->reclen, "update_prev"); return (error); } /* * update_linked_list - update the next link to point back to prev, and vice * versa. Normally called by delete_link to drop the deleted link from the * linked list of hard links for the file. next and prev are the keys of next * and previous links for the deleted link in the list (could be NULL). * Return 0 for success, error code otherwise. * If successful, and nextlinkpp is non-null, * return the record for the next link, since * if the primary record should be updated we'll need it. In this case, * actually allocate the space for it because we can't tell otherwise. */ static linkinfo_ent * update_linked_list(struct db_list *dbp, char *nextkey, int nextsize, char *prevkey, int prevsize, int *errorp) { linkinfo_ent *nextlinkp = NULL; *errorp = 0; if (nextsize > 0) { nextlinkp = update_next_link(dbp, nextkey, nextsize, prevkey, prevsize, errorp); if (nextlinkp == NULL) { /* not an error if no next link */ if (*errorp != 0) { if (debug > 1) { (void) fprintf(stderr, "update_next_link Error %s\n", ((*errorp >= 0) ? strerror(*errorp) : "Unknown")); } return (NULL); } } } if (prevsize > 0) { *errorp = update_prev_link(dbp, nextkey, nextsize, prevkey, prevsize); if (*errorp != 0) { if (debug > 1) { (void) fprintf(stderr, "update_prev_link Error %s\n", ((*errorp >= 0) ? strerror(*errorp) : "Unknown")); } if (nextlinkp != NULL) free(nextlinkp); nextlinkp = NULL; } } return (nextlinkp); } /* * db_update_primary_new_head - Update a primary record that the head of * the list is deleted. Similar to db_add_primary, but the primary record * must exist, and is always replaced with one pointing to the new link, * unless it does not point to the deleted link. If the link we deleted * was the last link, the delete the primary record as well. * Return 0 for success, error code otherwise. */ static int db_update_primary_new_head(struct db_list *dbp, linkinfo_ent *dellinkp, linkinfo_ent *nextlinkp, fhlist_ent *fhrecp) { int error; char *name, *next_name; fhandle_t *dfh; fh_primary_key fhkey; dfh = &dellinkp->dfh; name = LN_NAME(dellinkp); /* If the deleted link was not the head of the list, we are done */ if (memcmp(&fhrecp->dfh, dfh, sizeof (*dfh)) || strcmp(fhrecp->name, name)) { /* should never be here... */ if (debug > 1) { (void) fprintf(stderr, "db_update_primary_new_head: primary " "is for [%s,", name); debug_opaque_print(stderr, (void *)dfh, sizeof (*dfh)); (void) fprintf(stderr, "], not [%s,", fhrecp->name); debug_opaque_print(stderr, (void *)&fhrecp->dfh, sizeof (fhrecp->dfh)); (void) fprintf(stderr, "]\n"); } return (0); /* not head of list so done */ } /* Set the head to nextkey if exists. Otherwise, mark file as deleted */ bcopy(&fhrecp->fh.fh_data, fhkey, fhrecp->fh.fh_len); if (nextlinkp == NULL) { /* last link */ /* remove primary record from database */ (void) delete_record(dbp, fhkey, fhrecp->fh.fh_len, "db_update_primary_new_head: fh delete"); return (0); } else { /* * There are still "live" links, so update the primary record. */ next_name = LN_NAME(nextlinkp); fhrecp->reclen = ROUNDUP32(offsetof(fhlist_ent, name) + strlen(next_name) + 1); /* Replace link data with the info for the next link */ (void) memcpy(&fhrecp->dfh, &nextlinkp->dfh, sizeof (nextlinkp->dfh)); (void) strcpy(fhrecp->name, next_name); } /* not last link */ fhrecp->mtime = time(0); fhrecp->atime = fhrecp->mtime; error = store_record(dbp, fhkey, fhrecp->fh.fh_len, fhrecp, fhrecp->reclen, "db_update_primary_new_head: fh"); return (error); } /* * Exported functions */ /* * db_add - add record to the database. If dfh, fh and name are all here, * add both primary and secondary records. If fh is not available, don't * add anything... * Assumes this is a new file, not yet in the database and that the record * for fh is already in. * Return 0 for success, error code otherwise. */ int db_add(char *fhpath, fhandle_t *dfh, char *name, fhandle_t *fh, uint_t flags) { struct db_list *dbp = NULL; fhlist_ent fhrec, *fhrecp; int error = 0; if (fh == NULL) { /* nothing to add */ return (EINVAL); } if (fh == &public_fh) { dbp = db_get_all_databases(fhpath, FALSE); } else { dbp = db_get_db(fhpath, &fh->fh_fsid, &error, O_CREAT); } for (; dbp != NULL; dbp = ((fh != &public_fh) ? NULL : dbp->next)) { if (debug > 3) { (void) printf("db_add: name '%s', db '%s'\n", name, dbp->path); } fhrecp = db_add_primary(dbp, dfh, name, fh, flags, &fhrec, &error); if (fhrecp == NULL) { continue; } if ((dfh == NULL) || (name == NULL)) { /* Can't add link information */ syslog(LOG_ERR, gettext( "db_add: dfh %p, name %p - invalid"), (void *)dfh, (void *)name); error = EINVAL; continue; } if (fh == &public_fh) { while ((fhrecp != NULL) && strcmp(name, fhrecp->name)) { /* Replace the public fh rather than add link */ error = db_delete_link(fhpath, dfh, fhrecp->name); fhrecp = db_add_primary(dbp, dfh, name, fh, flags, &fhrec, &error); } if (fhrecp == NULL) { continue; } } error = db_add_secondary(dbp, dfh, name, fh, fhrecp); if (fhrecp != &fhrec) { free(fhrecp); } } return (error); } /* * db_lookup - search the database for the file identified by fh. * Return the entry in *fhrecpp if found, or NULL with error set otherwise. */ fhlist_ent * db_lookup(char *fhpath, fhandle_t *fh, fhlist_ent *fhrecp, int *errorp) { struct db_list *dbp; fh_primary_key fhkey; if ((fhpath == NULL) || (fh == NULL) || (errorp == NULL)) { if (errorp != NULL) *errorp = EINVAL; return (NULL); } *errorp = 0; if (fh == &public_fh) { dbp = db_get_all_databases(fhpath, FALSE); } else { dbp = db_get_db(fhpath, &fh->fh_fsid, errorp, O_CREAT); } if (dbp == NULL) { /* Could not get or create database */ return (NULL); } bcopy(&fh->fh_data, fhkey, fh->fh_len); fhrecp = fetch_record(dbp, fhkey, fh->fh_len, fhrecp, errorp, "db_lookup"); /* Update fhrec atime if needed */ if (fhrecp != NULL) { *errorp = db_update_fhrec(dbp, fhkey, fh->fh_len, fhrecp, "db_lookup"); } return (fhrecp); } /* * db_lookup_link - search the database for the file identified by (dfh,name). * If the link was found, use it to search for the primary record. * Return 0 and set the entry in *fhrecpp if found, return error otherwise. */ fhlist_ent * db_lookup_link(char *fhpath, fhandle_t *dfh, char *name, fhlist_ent *fhrecp, int *errorp) { struct db_list *dbp; fh_secondary_key linkkey; linkinfo_ent *linkp; int linksize, fhkeysize; char *fhkey; if ((fhpath == NULL) || (dfh == NULL) || (name == NULL) || (errorp == NULL)) { if (errorp != NULL) *errorp = EINVAL; return (NULL); } *errorp = 0; if (dfh == &public_fh) { dbp = db_get_all_databases(fhpath, FALSE); } else { dbp = db_get_db(fhpath, &dfh->fh_fsid, errorp, O_CREAT); } if (dbp == NULL) { /* Could not get or create database */ return (NULL); } /* Get the link record */ linksize = fill_link_key(linkkey, dfh, name); linkp = fetch_record(dbp, linkkey, linksize, NULL, errorp, "db_lookup_link link"); if (linkp != NULL) { /* Now use link to search for fh entry */ fhkeysize = LN_FHKEY_LEN(linkp); fhkey = LN_FHKEY(linkp); fhrecp = fetch_record(dbp, fhkey, fhkeysize, (void *)fhrecp, errorp, "db_lookup_link fh"); /* Update fhrec atime if needed */ if (fhrecp != NULL) { *errorp = db_update_fhrec(dbp, fhkey, fhkeysize, fhrecp, "db_lookup_link fhrec"); } /* Update link atime if needed */ *errorp = db_update_linkinfo(dbp, linkkey, linksize, linkp, "db_lookup_link link"); free(linkp); } else { fhrecp = NULL; } return (fhrecp); } /* * delete_link - delete the requested link from the database. If it's the * last link in the database for that file then remove the primary record * as well. *errorp contains the returned error code. * Return ENOENT if link not in database and 0 otherwise. */ static int delete_link_by_key(struct db_list *dbp, char *linkkey, int *linksizep, int *errorp, char *errstr) { int nextsize, prevsize, fhkeysize, linksize; char *nextkey, *prevkey, *fhkey; linkinfo_ent *dellinkp, *nextlinkp; fhlist_ent *fhrecp, fhrec; *errorp = 0; linksize = *linksizep; /* Get the link record */ dellinkp = fetch_record(dbp, linkkey, linksize, NULL, errorp, errstr); if (dellinkp == NULL) { /* * Link not in database. */ if (debug > 2) { debug_print_key(stderr, errstr, "link not in database\n", linkkey, linksize); } *linksizep = 0; return (ENOENT); } /* * Possibilities: * 1. Normal case - only one link to delete: the link next and * prev should be NULL, and fhrec's name/dfh are same * as the link. Remove the link and fhrec. * 2. Multiple hard links, and the deleted link is the head of * the list. Remove the link and replace the link key in * the primary record to point to the new head. * 3. Multiple hard links, and the deleted link is not the * head of the list (not the same as in fhrec) - just * delete the link and update the previous and next records * in the links linked list. */ /* Get next and prev keys for linked list updates */ nextsize = LN_NEXT_LEN(dellinkp); nextkey = ((nextsize > 0) ? LN_NEXT(dellinkp) : NULL); prevsize = LN_PREV_LEN(dellinkp); prevkey = ((prevsize > 0) ? LN_PREV(dellinkp) : NULL); /* Update the linked list for the file */ nextlinkp = update_linked_list(dbp, nextkey, nextsize, prevkey, prevsize, errorp); if ((nextlinkp == NULL) && (*errorp != 0)) { free(dellinkp); *linksizep = 0; return (0); } /* Delete link record */ *errorp = delete_record(dbp, linkkey, linksize, errstr); /* Get the primary key */ fhkeysize = LN_FHKEY_LEN(dellinkp); fhkey = LN_FHKEY(dellinkp); fhrecp = fetch_record(dbp, fhkey, fhkeysize, &fhrec, errorp, errstr); if (fhrecp == NULL) { /* Should never happen */ if (debug > 1) { debug_print_key(stderr, errstr, "fetch primary for ", linkkey, linksize); (void) fprintf(stderr, " Error %s\n", ((*errorp >= 0) ? strerror(*errorp) : "Unknown")); } } else if ((*errorp == 0) && (prevsize <= 0)) { /* This is the head of the list update primary record */ *errorp = db_update_primary_new_head(dbp, dellinkp, nextlinkp, fhrecp); } else { /* Update fhrec atime if needed */ *errorp = db_update_fhrec(dbp, fhkey, fhkeysize, fhrecp, errstr); } *linksizep = nextsize; if (nextsize > 0) (void) memcpy(linkkey, nextkey, nextsize); if (nextlinkp != NULL) free(nextlinkp); free(dellinkp); return (0); } /* * delete_link - delete the requested link from the database. If it's the * last link in the database for that file then remove the primary record * as well. If nextlinkkey/sizep are non-null, copy the key and key size of * the next link in the chain into them (this would save a dbm_fetch op). * Return ENOENT if link not in database and 0 otherwise, with *errorp * containing the returned error if any from the delete_link ops. */ static int delete_link(struct db_list *dbp, fhandle_t *dfh, char *name, char *nextlinkkey, int *nextlinksizep, int *errorp, char *errstr) { int linkerr; *errorp = 0; if ((nextlinkkey != NULL) && (nextlinksizep != NULL)) { *nextlinksizep = fill_link_key(nextlinkkey, dfh, name); linkerr = delete_link_by_key(dbp, nextlinkkey, nextlinksizep, errorp, errstr); } else { int linksize; fh_secondary_key linkkey; linksize = fill_link_key(linkkey, dfh, name); linkerr = delete_link_by_key(dbp, linkkey, &linksize, errorp, errstr); } return (linkerr); } /* * db_delete_link - search the database for the file system for link. * Delete the link from the database. If this is the "primary" link, * set the primary record for the next link. If it's the last one, * delete the primary record. * Return 0 for success, error code otherwise. */ int db_delete_link(char *fhpath, fhandle_t *dfh, char *name) { struct db_list *dbp; int error = 0; if ((fhpath == NULL) || (dfh == NULL) || (name == NULL)) { return (EINVAL); } if (dfh == &public_fh) { dbp = db_get_all_databases(fhpath, TRUE); } else { dbp = db_get_db(fhpath, &dfh->fh_fsid, &error, O_CREAT); } for (; dbp != NULL; dbp = ((dfh == &public_fh) ? dbp->next : NULL)) { (void) delete_link(dbp, dfh, name, NULL, NULL, &error, "db_delete_link link"); } return (error); } #ifdef DEBUG /* * db_delete - Deletes the fhrec corresponding to the fh. Use only * for repairing the fhtable, not for normal handling. * Return 0 for success, error code otherwise. */ int db_delete(char *fhpath, fhandle_t *fh) { struct db_list *dbp; int error = 0; if ((fhpath == NULL) || (fh == NULL)) { return (EINVAL); } if (fh == &public_fh) { dbp = db_get_all_databases(fhpath, TRUE); } else { dbp = db_get_db(fhpath, &fh->fh_fsid, &error, O_CREAT); } for (; dbp != NULL; dbp = ((fh == &public_fh) ? dbp->next : NULL)) { /* Get the link record */ (void) delete_record(dbp, &fh->fh_data, fh->fh_len, "db_delete: fh delete"); } return (error); } #endif /* DEBUG */ /* * db_rename_link - search the database for the file system for link. * Add the new link and delete the old link from the database. * Return 0 for success, error code otherwise. */ int db_rename_link(char *fhpath, fhandle_t *from_dfh, char *from_name, fhandle_t *to_dfh, char *to_name) { int error; struct db_list *dbp; fhlist_ent fhrec, *fhrecp; if ((fhpath == NULL) || (from_dfh == NULL) || (from_name == NULL) || (to_dfh == NULL) || (to_name == NULL)) { return (EINVAL); } if (from_dfh == &public_fh) { dbp = db_get_all_databases(fhpath, FALSE); } else { dbp = db_get_db(fhpath, &from_dfh->fh_fsid, &error, O_CREAT); } for (; dbp != NULL; dbp = ((from_dfh != &public_fh) ? NULL : dbp->next)) { /* find existing link */ fhrecp = db_lookup_link(fhpath, from_dfh, from_name, &fhrec, &error); if (fhrecp == NULL) { /* Could not find the link */ continue; } /* Delete the old link (if last primary record not deleted) */ error = db_delete_link(fhpath, from_dfh, from_name); if (error == 0) { error = db_add(fhpath, to_dfh, to_name, &fhrecp->fh, fhrecp->flags); } } return (error); } /* * db_print_all_keys: prints all keys for a given filesystem. If fsidp is * NULL, print for all filesystems covered by fhpath. */ void db_print_all_keys(char *fhpath, fsid_t *fsidp, FILE *fp) { struct db_list *dbp; datum key; int error, len; char strkey[NFS_FHMAXDATA + MAXNAMELEN]; db_record rec; void *ptr; if ((fhpath == NULL) || ((fsidp != NULL) && (fsidp == &public_fh.fh_fsid))) return; if (fsidp == NULL) { (void) db_get_all_databases(fhpath, TRUE); dbp = db_fs_list; } else { dbp = db_get_db(fhpath, fsidp, &error, 0); } if (dbp == NULL) { /* Could not get or create database */ return; } len = strlen(fhpath); for (; dbp != NULL; dbp = ((fsidp != NULL) ? NULL : dbp->next)) { if (strncmp(fhpath, dbp->path, len)) continue; (void) fprintf(fp, "\nStart print database for fsid 0x%x 0x%x\n", dbp->fsid.val[0], dbp->fsid.val[1]); (void) fprintf(fp, "=============================\n"); for (key = dbm_firstkey(dbp->db); key.dptr != NULL; key = dbm_nextkey(dbp->db)) { (void) memcpy(strkey, key.dptr, key.dsize); debug_print_key(fp, "", "", strkey, key.dsize); if (debug < 2) continue; ptr = fetch_record(dbp, key.dptr, key.dsize, (void *)&rec, &error, "db_prt_keys"); if (ptr == NULL) continue; if (key.dsize == NFS_FHMAXDATA) { /* fhrec */ debug_print_fhlist(fp, &rec.fhlist_rec); } else if (key.dsize > NFS_FHMAXDATA) { /* linkinfo */ debug_print_linkinfo(fp, &rec.link_rec); } (void) fprintf(fp, "-----------------------------\n"); } (void) fprintf(fp, "End print database for fsid 0x%x 0x%x\n", dbp->fsid.val[0], dbp->fsid.val[1]); } } void debug_opaque_print(FILE *fp, void *buf, int size) { int bufoffset = 0; char debug_str[200]; if ((buf == NULL) || (size <= 0)) return; nfslog_opaque_print_buf(buf, size, debug_str, &bufoffset, 200); (void) fprintf(fp, debug_str); } /* * links_timedout() takes a primary records and searches all of its * links to see if they all have access times that are older than * the 'prune_timeout' value. TRUE if all links are old and FALSE * if there is just one link that has an access time which is recent. */ static int links_timedout(struct db_list *pdb, fhlist_ent *pfe, time_t ts) { fh_secondary_key linkkey; linkinfo_ent *linkp, link_st; int error; int linksize; void *cookie; /* Get the link record */ linksize = fill_link_key(linkkey, &pfe->dfh, pfe->name); cookie = NULL; do { linkp = get_next_link(pdb, linkkey, &linksize, &link_st, &cookie, &error, "links_timedout"); if ((linkp != NULL) && (difftime(ts, linkp->atime) <= prune_timeout)) { /* update primary record to have an uptodate time */ pfe = fetch_record(pdb, (void *)&pfe->fh.fh_data, pfe->fh.fh_len, NULL, &error, "links_timedout"); if (pfe == NULL) { syslog(LOG_ERR, gettext( "links_timedout: fetch fhrec error %s\n"), strerror(error)); } else { if (difftime(pfe->atime, linkp->atime) < 0) { /* update fhrec atime */ pfe->atime = linkp->atime; (void) store_record(pdb, (void *)&pfe->fh.fh_data, pfe->fh.fh_len, pfe, pfe->reclen, "links_timedout"); } free(pfe); } free_link_cookies(cookie); return (FALSE); } } while (linksize > 0); free_link_cookies(cookie); return (TRUE); } /* * prune_dbs() will search all of the open databases looking for records * that have not been accessed in the last 'prune_timeout' seconds. * This search is done on the primary records and a list of potential * timeout candidates is built. The reason for doing this is to not * disturb the underlying dbm_firstkey()/dbm_nextkey() sequence; we * want to search all of the records in the database. * Once we have our candidate list built, we examine each of those * item's links to check if the links have been accessed within the * 'prune_timeout' seconds. If neither the primary nor any its links * have been accessed, then all of those records are removed/deleted * from the database. */ int prune_dbs(char *fhpath) { struct db_list *pdb; datum key; db_record *ptr; struct fhlist_ent *pfe; int error, linkerr, linksize; time_t cur_time = time(0); fh_secondary_key linkkey; struct thelist { struct thelist *next; db_record *ptr; } thelist, *ptl; int cnt = 0; if (fhpath != NULL) (void) db_get_all_databases(fhpath, TRUE); thelist.next = NULL; /* * Search each of the open databases */ for (pdb = db_fs_list; pdb; pdb = pdb->next) { do { /* Check each record in the database */ for (key = dbm_firstkey(pdb->db); key.dptr != NULL; key = dbm_nextkey(pdb->db)) { /* We're only interested in primary records */ if (key.dsize != NFS_FHMAXDATA) continue; /* probably a link record */ ptr = fetch_record(pdb, key.dptr, key.dsize, NULL, &error, "dump_db"); if (ptr == NULL) continue; /* * If this record is a primary record and it is * not an export point or a public file handle path, * check it for a ancient access time. */ if ((ptr->fhlist_rec.flags & (EXPORT_POINT | PUBLIC_PATH)) || (difftime(cur_time, ptr->fhlist_rec.atime) <= prune_timeout)) { /* Keep this record in the database */ free(ptr); } else { /* Found one? Save off info about it */ ptl = malloc(sizeof (struct thelist)); if (ptl == NULL) { syslog(LOG_ERR, gettext( "prune_dbs: malloc failed, error %s\n"), strerror(errno)); break; } ptl->ptr = ptr; ptl->next = thelist.next; thelist.next = ptl; cnt++; /* count how many records allocated */ if (cnt > MAX_PRUNE_REC_CNT) { /* Limit number of records malloc'd */ if (debug) (void) fprintf(stderr, "prune_dbs: halt search - too many records\n"); break; } } } /* * Take the saved records and check their links to make * sure that they have not been accessed as well. */ for (ptl = thelist.next; ptl; ptl = thelist.next) { thelist.next = ptl->next; /* Everything timed out? */ pfe = &(ptl->ptr->fhlist_rec); if (links_timedout(pdb, pfe, cur_time)) { /* * Iterate until we run out of links. * We have to do this since there can be * multiple links to a primary record and * we need to delete one at a time. */ /* Delete the link and get the next */ linkerr = delete_link(pdb, &pfe->dfh, pfe->name, linkkey, &linksize, &error, "dump_db"); while ((linksize > 0) && !(error || linkerr)) { /* Delete the link and get the next */ linkerr = delete_link_by_key(pdb, linkkey, &linksize, &error, "dump_db"); if (error || linkerr) { break; } } if (linkerr) { /* link not in database, primary is */ /* Should never happen */ if (debug > 1) { (void) fprintf(stderr, "prune_dbs: Error primary exists "); debug_opaque_print(stderr, (void *)&pfe->fh, sizeof (pfe->fh)); (void) fprintf(stderr, "\n"); } if (debug) syslog(LOG_ERR, gettext( "prune_dbs: Error primary exists\n")); (void) delete_record(pdb, &pfe->fh.fh_data, pfe->fh.fh_len, "prune_dbs: fh delete"); } } /* Make sure to free the pointers used in the list */ free(ptl->ptr); free(ptl); cnt--; } thelist.next = NULL; } while (key.dptr != NULL); } return (0); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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) 1999 by Sun Microsystems, Inc. * All rights reserved. */ /* * Code to maintain the runtime and on-disk filehandle mapping table for * nfslog. */ #include #include #include #include #include #include #include #include #include #include #include #include "fhtab.h" #include "nfslogd.h" #define ROUNDUP32(val) (((val) + 3) & ~3) #define IS_DOT_FILENAME(name) \ ((strcmp(name, ".") == 0) || (strcmp(name, "..") == 0)) #define PRINT_LINK_DATA(fp, func, dfh, name, str) \ (void) fprintf(fp, "%s: name '%s', dfh ", \ func, (((name) != NULL) ? name : "")); \ debug_opaque_print(fp, dfh, sizeof (*(dfh))); \ (void) fprintf(fp, "%s\n", str); #define PRINT_FULL_DATA(fp, func, dfh, fh, name, str) \ (void) fprintf(fp, "%s: name '%s', dfh ", \ func, (((name) != NULL) ? name : "")); \ debug_opaque_print(fp, dfh, sizeof (*(dfh))); \ if ((fh) != NULL) { \ (void) fprintf(fp, ", fh "); \ debug_opaque_print(fp, fh, sizeof (*(fh))); \ } \ (void) fprintf(fp, "%s\n", str); /* * export handle cache */ struct export_handle_cache { fhandle_t fh; char *name; struct export_handle_cache *next; }; static struct export_handle_cache *exp_handle_cache = NULL; extern bool_t nfsl_prin_fh; static int fh_add(char *, fhandle_t *, fhandle_t *, char *); static char *get_export_path(fhandle_t *, char *); static void sprint_fid(char *, uint_t, const fhandle_t *); static void fh_print_all_keys(char *fhpath, fhandle_t *fh); static int fh_compare(fhandle_t *fh1, fhandle_t *fh2); static fhlist_ent *fh_lookup(char *fhpath, fhandle_t *fh, fhlist_ent *fhrecp, int *errorp); static int fh_remove_mc_link(char *fhpath, fhandle_t *dfh, char *name, char **pathp); static int fh_remove(char *fhpath, fhandle_t *dfh, char *name, char **pathp); static int fh_rename(char *fhpath, fhandle_t *from_dfh, char *from_name, char **from_pathp, fhandle_t *to_dfh, char *to_name); static fhlist_ent *fh_lookup_link(char *fhpath, fhandle_t *dfh, fhandle_t *fh, char *name, fhlist_ent *fhrecp, int *errorp); static struct nfsl_fh_proc_disp *nfslog_find_fh_dispatch( nfslog_request_record *); static struct export_handle_cache *find_fh_in_export_cache(fhandle_t *fh); static void add_fh_to_export_cache(fhandle_t *fh, char *path); static char *update_export_point(char *fhpath, fhandle_t *fh, char *path); static char *fh_print_absolute(char *fhpath, fhandle_t *fh, char *name); static void nfslog_null_fhargs(caddr_t *nfsl_args, caddr_t *nfsl_res, char *fhpath, char **pathp1, char **pathp2); static void nfslog_LOOKUP_calc(fhandle_t *dfh, char *name, fhandle_t *fh, char *fhpath, char **pathp1, char **pathp2, char *str); /* * NFS VERSION 2 */ /* * Functions for updating the fhtable for fhtoppath and for returning * the absolute pathname */ static void nfslog_GETATTR2_fhargs(fhandle_t *, nfsstat *, char *fhpath, char **, char **); static void nfslog_SETATTR2_fhargs(nfslog_setattrargs *, nfsstat *, char *, char **, char **); static void nfslog_LOOKUP2_fhargs(nfslog_diropargs *, nfslog_diropres *, char *, char **, char **); static void nfslog_READLINK2_fhargs(fhandle_t *, nfslog_rdlnres *, char *, char **, char **); static void nfslog_READ2_fhargs(nfslog_nfsreadargs *, nfslog_rdresult *, char *, char **, char **); static void nfslog_WRITE2_fhargs(nfslog_writeargs *, nfslog_writeresult *, char *, char **, char **); static void nfslog_CREATE2_fhargs(nfslog_createargs *, nfslog_diropres*, char *, char **, char **); static void nfslog_REMOVE2_fhargs(nfslog_diropargs *, nfsstat *, char *, char **, char **); static void nfslog_RENAME2_fhargs(nfslog_rnmargs *, nfsstat *, char *, char **, char **); static void nfslog_LINK2_fhargs(nfslog_linkargs *, nfsstat *, char *, char **, char **); static void nfslog_SYMLINK2_fhargs(nfslog_symlinkargs *, nfsstat *, char *, char **, char **); static void nfslog_READDIR2_fhargs(nfslog_rddirargs *, nfslog_rddirres *, char *, char **, char **); static void nfslog_STATFS2_fhargs(fhandle_t *, nfsstat *, char *, char **, char **); /* * NFS VERSION 3 * * Functions for updating the fhtable for fhtoppath */ static void nfslog_GETATTR3_fhargs(nfs_fh3 *, nfsstat3 *, char *, char **, char **); static void nfslog_SETATTR3_fhargs(nfslog_SETATTR3args *, nfsstat3 *, char *, char **, char **); static void nfslog_LOOKUP3_fhargs(nfslog_diropargs3 *, nfslog_LOOKUP3res *, char *, char **, char **); static void nfslog_ACCESS3_fhargs(nfs_fh3 *, nfsstat3 *, char *, char **, char **); static void nfslog_READLINK3_fhargs(nfs_fh3 *, nfslog_READLINK3res *, char *, char **, char **); static void nfslog_READ3_fhargs(nfslog_READ3args *, nfslog_READ3res *, char *, char **, char **); static void nfslog_WRITE3_fhargs(nfslog_WRITE3args *, nfslog_WRITE3res *, char *, char **, char **); static void nfslog_CREATE3_fhargs(nfslog_CREATE3args *, nfslog_CREATE3res *, char *, char **, char **); static void nfslog_MKDIR3_fhargs(nfslog_MKDIR3args *, nfslog_MKDIR3res *, char *, char **, char **); static void nfslog_SYMLINK3_fhargs(nfslog_SYMLINK3args *, nfslog_SYMLINK3res *, char *, char **, char **); static void nfslog_MKNOD3_fhargs(nfslog_MKNOD3args *, nfslog_MKNOD3res *, char *, char **, char **); static void nfslog_REMOVE3_fhargs(nfslog_REMOVE3args *, nfsstat3 *, char *, char **, char **); static void nfslog_RMDIR3_fhargs(nfslog_RMDIR3args *, nfsstat3 *, char *, char **, char **); static void nfslog_RENAME3_fhargs(nfslog_RENAME3args *, nfsstat3 *, char *, char **, char **); static void nfslog_LINK3_fhargs(nfslog_LINK3args *, nfsstat3 *, char *, char **, char **); static void nfslog_READDIR3_fhargs(nfs_fh3 *, nfsstat3 *, char *, char **, char **); static void nfslog_READDIRPLUS3_fhargs(nfslog_READDIRPLUS3args *, nfslog_READDIRPLUS3res *, char *, char **, char **); static void nfslog_FSSTAT3_fhargs(nfs_fh3 *, nfsstat3 *, char *, char **, char **); static void nfslog_FSINFO3_fhargs(nfs_fh3 *, nfsstat3 *, char *, char **, char **); static void nfslog_PATHCONF3_fhargs(nfs_fh3 *, nfsstat3 *, char *, char **, char **); static void nfslog_COMMIT3_fhargs(nfslog_COMMIT3args *, nfsstat3 *, char *, char **, char **); /* * NFSLOG VERSION 1 * * Functions for updating the fhtable for fhtoppath */ static void nfslog_SHARE_fhargs(nfslog_sharefsargs *, nfslog_sharefsres *, char *, char **, char **); static void nfslog_UNSHARE_fhargs(nfslog_sharefsargs *, nfslog_sharefsres *, char *, char **, char **); static void nfslog_GETFH_fhargs(nfslog_getfhargs *, nfsstat *, char *, char **, char **); /* * Define the actions taken per prog/vers/proc: * * In some cases, the nl types are the same as the nfs types and a simple * bcopy should suffice. Rather that define tens of identical procedures, * simply define these to bcopy. Similarly this takes care of different * procs that use same parameter struct. */ static struct nfsl_fh_proc_disp nfsl_fh_proc_v2[] = { /* * NFS VERSION 2 */ /* RFS_NULL = 0 */ {nfslog_null_fhargs, xdr_void, xdr_void, 0, 0}, /* RFS_GETATTR = 1 */ {nfslog_GETATTR2_fhargs, xdr_fhandle, xdr_nfsstat, sizeof (fhandle_t), sizeof (nfsstat)}, /* RFS_SETATTR = 2 */ {nfslog_SETATTR2_fhargs, xdr_nfslog_setattrargs, xdr_nfsstat, sizeof (nfslog_setattrargs), sizeof (nfsstat)}, /* RFS_ROOT = 3 *** NO LONGER SUPPORTED *** */ {nfslog_null_fhargs, xdr_void, xdr_void, 0, 0}, /* RFS_LOOKUP = 4 */ {nfslog_LOOKUP2_fhargs, xdr_nfslog_diropargs, xdr_nfslog_diropres, sizeof (nfslog_diropargs), sizeof (nfslog_diropres)}, /* RFS_READLINK = 5 */ {nfslog_READLINK2_fhargs, xdr_fhandle, xdr_nfslog_rdlnres, sizeof (fhandle_t), sizeof (nfslog_rdlnres)}, /* RFS_READ = 6 */ {nfslog_READ2_fhargs, xdr_nfslog_nfsreadargs, xdr_nfslog_rdresult, sizeof (nfslog_nfsreadargs), sizeof (nfslog_rdresult)}, /* RFS_WRITECACHE = 7 *** NO LONGER SUPPORTED *** */ {nfslog_null_fhargs, xdr_void, xdr_void, 0, 0}, /* RFS_WRITE = 8 */ {nfslog_WRITE2_fhargs, xdr_nfslog_writeargs, xdr_nfslog_writeresult, sizeof (nfslog_writeargs), sizeof (nfslog_writeresult)}, /* RFS_CREATE = 9 */ {nfslog_CREATE2_fhargs, xdr_nfslog_createargs, xdr_nfslog_diropres, sizeof (nfslog_createargs), sizeof (nfslog_diropres)}, /* RFS_REMOVE = 10 */ {nfslog_REMOVE2_fhargs, xdr_nfslog_diropargs, xdr_nfsstat, sizeof (nfslog_diropargs), sizeof (nfsstat)}, /* RFS_RENAME = 11 */ {nfslog_RENAME2_fhargs, xdr_nfslog_rnmargs, xdr_nfsstat, sizeof (nfslog_rnmargs), sizeof (nfsstat)}, /* RFS_LINK = 12 */ {nfslog_LINK2_fhargs, xdr_nfslog_linkargs, xdr_nfsstat, sizeof (nfslog_linkargs), sizeof (nfsstat)}, /* RFS_SYMLINK = 13 */ {nfslog_SYMLINK2_fhargs, xdr_nfslog_symlinkargs, xdr_nfsstat, sizeof (nfslog_symlinkargs), sizeof (nfsstat)}, /* RFS_MKDIR = 14 */ {nfslog_CREATE2_fhargs, xdr_nfslog_createargs, xdr_nfslog_diropres, sizeof (nfslog_createargs), sizeof (nfslog_diropres)}, /* RFS_RMDIR = 15 */ {nfslog_REMOVE2_fhargs, xdr_nfslog_diropargs, xdr_nfsstat, sizeof (nfslog_diropargs), sizeof (nfsstat)}, /* RFS_READDIR = 16 */ {nfslog_READDIR2_fhargs, xdr_nfslog_rddirargs, xdr_nfslog_rddirres, sizeof (nfslog_rddirargs), sizeof (nfslog_rddirres)}, /* RFS_STATFS = 17 */ {nfslog_STATFS2_fhargs, xdr_fhandle, xdr_nfsstat, sizeof (fhandle_t), sizeof (nfsstat)}, }; /* * NFS VERSION 3 */ static struct nfsl_fh_proc_disp nfsl_fh_proc_v3[] = { /* RFS_NULL = 0 */ {nfslog_null_fhargs, xdr_void, xdr_void, 0, 0}, /* RFS3_GETATTR = 1 */ {nfslog_GETATTR3_fhargs, xdr_nfs_fh3, xdr_nfsstat3, sizeof (nfs_fh3), sizeof (nfsstat3)}, /* RFS3_SETATTR = 2 */ {nfslog_SETATTR3_fhargs, xdr_nfslog_SETATTR3args, xdr_nfsstat3, sizeof (nfslog_SETATTR3args), sizeof (nfsstat3)}, /* RFS3_LOOKUP = 3 */ {nfslog_LOOKUP3_fhargs, xdr_nfslog_diropargs3, xdr_nfslog_LOOKUP3res, sizeof (nfslog_diropargs3), sizeof (nfslog_LOOKUP3res)}, /* RFS3_ACCESS = 4 */ {nfslog_ACCESS3_fhargs, xdr_nfs_fh3, xdr_nfsstat3, sizeof (nfs_fh3), sizeof (nfsstat3)}, /* RFS3_READLINK = 5 */ {nfslog_READLINK3_fhargs, xdr_nfs_fh3, xdr_nfslog_READLINK3res, sizeof (nfs_fh3), sizeof (nfslog_READLINK3res)}, /* RFS3_READ = 6 */ {nfslog_READ3_fhargs, xdr_nfslog_READ3args, xdr_nfslog_READ3res, sizeof (nfslog_READ3args), sizeof (nfslog_READ3res)}, /* RFS3_WRITE = 7 */ {nfslog_WRITE3_fhargs, xdr_nfslog_WRITE3args, xdr_nfslog_WRITE3res, sizeof (nfslog_WRITE3args), sizeof (nfslog_WRITE3res)}, /* RFS3_CREATE = 8 */ {nfslog_CREATE3_fhargs, xdr_nfslog_CREATE3args, xdr_nfslog_CREATE3res, sizeof (nfslog_CREATE3args), sizeof (nfslog_CREATE3res)}, /* RFS3_MKDIR = 9 */ {nfslog_MKDIR3_fhargs, xdr_nfslog_MKDIR3args, xdr_nfslog_MKDIR3res, sizeof (nfslog_MKDIR3args), sizeof (nfslog_MKDIR3res)}, /* RFS3_SYMLINK = 10 */ {nfslog_SYMLINK3_fhargs, xdr_nfslog_SYMLINK3args, xdr_nfslog_SYMLINK3res, sizeof (nfslog_SYMLINK3args), sizeof (nfslog_SYMLINK3res)}, /* RFS3_MKNOD = 11 */ {nfslog_MKNOD3_fhargs, xdr_nfslog_MKNOD3args, xdr_nfslog_MKNOD3res, sizeof (nfslog_MKNOD3args), sizeof (nfslog_MKNOD3res)}, /* RFS3_REMOVE = 12 */ {nfslog_REMOVE3_fhargs, xdr_nfslog_REMOVE3args, xdr_nfsstat3, sizeof (nfslog_REMOVE3args), sizeof (nfsstat3)}, /* RFS3_RMDIR = 13 */ {nfslog_RMDIR3_fhargs, xdr_nfslog_RMDIR3args, xdr_nfsstat3, sizeof (nfslog_RMDIR3args), sizeof (nfsstat3)}, /* RFS3_RENAME = 14 */ {nfslog_RENAME3_fhargs, xdr_nfslog_RENAME3args, xdr_nfsstat3, sizeof (nfslog_RENAME3args), sizeof (nfsstat3)}, /* RFS3_LINK = 15 */ {nfslog_LINK3_fhargs, xdr_nfslog_LINK3args, xdr_nfsstat3, sizeof (nfslog_LINK3args), sizeof (nfsstat3)}, /* RFS3_READDIR = 16 */ {nfslog_READDIR3_fhargs, xdr_nfs_fh3, xdr_nfsstat3, sizeof (nfs_fh3), sizeof (nfsstat3)}, /* RFS3_READDIRPLUS = 17 */ {nfslog_READDIRPLUS3_fhargs, xdr_nfslog_READDIRPLUS3args, xdr_nfslog_READDIRPLUS3res, sizeof (nfslog_READDIRPLUS3args), sizeof (nfslog_READDIRPLUS3res)}, /* RFS3_FSSTAT = 18 */ {nfslog_FSSTAT3_fhargs, xdr_nfs_fh3, xdr_nfsstat3, sizeof (nfs_fh3), sizeof (nfsstat3)}, /* RFS3_FSINFO = 19 */ {nfslog_FSINFO3_fhargs, xdr_nfs_fh3, xdr_nfsstat3, sizeof (nfs_fh3), sizeof (nfsstat3)}, /* RFS3_PATHCONF = 20 */ {nfslog_PATHCONF3_fhargs, xdr_nfs_fh3, xdr_nfsstat3, sizeof (nfs_fh3), sizeof (nfsstat3)}, /* RFS3_COMMIT = 21 */ {nfslog_COMMIT3_fhargs, xdr_nfslog_COMMIT3args, xdr_nfsstat3, sizeof (nfslog_COMMIT3args), sizeof (nfsstat3)}, }; /* * NFSLOG VERSION 1 */ static struct nfsl_fh_proc_disp nfsl_log_fh_proc_v1[] = { /* NFSLOG_NULL = 0 */ {nfslog_null_fhargs, xdr_void, xdr_void, 0, 0}, /* NFSLOG_SHARE = 1 */ {nfslog_SHARE_fhargs, xdr_nfslog_sharefsargs, xdr_nfslog_sharefsres, sizeof (nfslog_sharefsargs), sizeof (nfslog_sharefsres)}, /* NFSLOG_UNSHARE = 2 */ {nfslog_UNSHARE_fhargs, xdr_nfslog_sharefsargs, xdr_nfslog_sharefsres, sizeof (nfslog_sharefsargs), sizeof (nfslog_sharefsres)}, /* NFSLOG_LOOKUP3 = 3 */ {nfslog_LOOKUP3_fhargs, xdr_nfslog_diropargs3, xdr_nfslog_LOOKUP3res, sizeof (nfslog_diropargs3), sizeof (nfslog_LOOKUP3res)}, /* NFSLOG_GETFH = 4 */ {nfslog_GETFH_fhargs, xdr_nfslog_getfhargs, xdr_nfsstat, sizeof (nfslog_getfhargs), sizeof (nfsstat)}, }; static struct nfsl_fh_vers_disp nfsl_fh_vers_disptable[] = { {sizeof (nfsl_fh_proc_v2) / sizeof (nfsl_fh_proc_v2[0]), nfsl_fh_proc_v2}, {sizeof (nfsl_fh_proc_v3) / sizeof (nfsl_fh_proc_v3[0]), nfsl_fh_proc_v3}, }; static struct nfsl_fh_vers_disp nfsl_log_fh_vers_disptable[] = { {sizeof (nfsl_log_fh_proc_v1) / sizeof (nfsl_log_fh_proc_v1[0]), nfsl_log_fh_proc_v1}, }; static struct nfsl_fh_prog_disp nfsl_fh_dispatch_table[] = { {NFS_PROGRAM, NFS_VERSMIN, sizeof (nfsl_fh_vers_disptable) / sizeof (nfsl_fh_vers_disptable[0]), nfsl_fh_vers_disptable}, {NFSLOG_PROGRAM, NFSLOG_VERSMIN, sizeof (nfsl_log_fh_vers_disptable) / sizeof (nfsl_log_fh_vers_disptable[0]), nfsl_log_fh_vers_disptable}, }; static int nfsl_fh_dispatch_table_arglen = sizeof (nfsl_fh_dispatch_table) / sizeof (nfsl_fh_dispatch_table[0]); extern int debug; /* * print the fid into the given string as a series of hex digits. * XXX Ideally, we'd like to just convert the filehandle into an i-number, * but the fid encoding is a little tricky (see nfs_fhtovp() and * ufs_vget()) and may be private to UFS. */ static void sprint_fid(char *buf, uint_t buflen, const fhandle_t *fh) { int i; uchar_t byte; uint_t fhlen; /* * If the filehandle somehow got corrupted, only print the part * that makes sense. */ if (fh->fh_len > NFS_FHMAXDATA) fhlen = NFS_FHMAXDATA; else fhlen = fh->fh_len; assert(2 * fhlen < buflen); for (i = 0; i < fhlen; i++) { byte = fh->fh_data[i]; (void) sprintf(buf + 2 * i, "%02x", byte); } } static void fh_print_all_keys(char *fhpath, fhandle_t *fh) { if ((fhpath == NULL) || (fh == NULL) || (debug <= 1)) return; (void) printf("\nBegin all database keys\n"); db_print_all_keys(fhpath, &fh->fh_fsid, stdout); (void) printf("\nEnd all database keys\n"); } #define FH_ADD(path, dfh, fh, name) \ fh_add(path, dfh, fh, name) /* * Add the filehandle "fh", which has the name "name" and lives in * directory "dfh", to the table "fhlist". "fhlist" will be updated if the * entry is added to the front of the list. * Return 0 for success, error code otherwise. */ static int fh_add(char *fhpath, fhandle_t *dfh, fhandle_t *fh, char *name) { uint_t flags = 0; int error; if (IS_DOT_FILENAME(name)) { /* we don't insert these to the database but not an error */ if (debug > 3) { PRINT_FULL_DATA(stdout, "fh_add", dfh, fh, name, " - no dot files") } return (0); } if (dfh && (memcmp(fh, dfh, NFS_FHSIZE) == 0)) { flags |= EXPORT_POINT; } /* Add to database */ error = db_add(fhpath, dfh, name, fh, flags); if (debug > 1) { if (error != 0) { (void) printf("db_add error %s:\n", ((error >= 0) ? strerror(error) : "Unknown")); PRINT_FULL_DATA(stdout, "fh_add", dfh, fh, name, "") } else if (debug > 2) { PRINT_FULL_DATA(stdout, "fh_add", dfh, fh, name, "") } } return (error); } /* * fh_compare returns 0 if the file handles match, error code otherwise */ static int fh_compare(fhandle_t *fh1, fhandle_t *fh2) { if (memcmp(fh1, fh2, NFS_FHSIZE)) return (errno); else return (0); } /* * Try to find the filehandle "fh" in the table. Returns 0 and the * corresponding table entry if found, error otherwise. * If successfull and fhrecpp is non-null then *fhrecpp points to the * returned record. If *fhrecpp was initially null, that record had * been malloc'd and must be freed by caller. */ static fhlist_ent * fh_lookup(char *fhpath, fhandle_t *fh, fhlist_ent *fhrecp, int *errorp) { if (debug > 3) { (void) printf("fh_lookup: fh "); debug_opaque_print(stdout, fh, sizeof (*fh)); (void) printf("\n"); } return (db_lookup(fhpath, fh, fhrecp, errorp)); } /* * Remove the mc link if exists when removing a regular link. * Return 0 for success, error code otherwise. */ static int fh_remove_mc_link(char *fhpath, fhandle_t *dfh, char *name, char **pathp) { int error; char *str, *str1; /* Delete the multi-component path if exists */ if ((pathp == NULL) || (*pathp == NULL)) { str = nfslog_get_path(dfh, name, fhpath, "remove_mc_link"); str1 = str; } else { str = *pathp; str1 = NULL; } error = db_delete_link(fhpath, &public_fh, str); if (str1 != NULL) free(str1); return (error); } /* * Remove the link entry from the fh table. * Return 0 for success, error code otherwise. */ static int fh_remove(char *fhpath, fhandle_t *dfh, char *name, char **pathp) { /* * disconnect element from list * * Remove the link entry for the file. Remove fh entry if last link. */ if (IS_DOT_FILENAME(name)) { /* we don't insert these to the database but not an error */ if (debug > 2) { PRINT_LINK_DATA(stdout, "fh_remove", dfh, name, " - no dot files") } return (0); } if (debug > 2) { PRINT_LINK_DATA(stdout, "fh_remove", dfh, name, "") } /* Delete the multi-component path if exists */ (void) fh_remove_mc_link(fhpath, dfh, name, pathp); return (db_delete_link(fhpath, dfh, name)); } /* * fh_rename - renames a link in the database (adds the new one if from link * did not exist). * Return 0 for success, error code otherwise. */ static int fh_rename(char *fhpath, fhandle_t *from_dfh, char *from_name, char **from_pathp, fhandle_t *to_dfh, char *to_name) { if (debug > 2) { PRINT_LINK_DATA(stdout, "fh_rename: from:", from_dfh, from_name, "") PRINT_LINK_DATA(stdout, "fh_rename: to :", to_dfh, to_name, "") } /* * if any of these are dot files (should not happen), the rename * becomes a "delete" or "add" operation because the dot files * don't get in the database */ if (IS_DOT_FILENAME(to_name)) { /* it is just a delete op */ if (debug > 2) { (void) printf("to: no dot files\nDelete from: '%s'\n", from_name); } return (fh_remove(fhpath, from_dfh, from_name, from_pathp)); } else if (IS_DOT_FILENAME(from_name)) { /* we don't insert these to the database */ if (debug > 2) { (void) printf("rename - from: no dot files\n"); } /* can't insert the target, because don't have a handle */ return (EINVAL); } /* Delete the multi-component path if exists */ (void) fh_remove_mc_link(fhpath, from_dfh, from_name, from_pathp); return (db_rename_link(fhpath, from_dfh, from_name, to_dfh, to_name)); } /* * fh_lookup_link - search the fhtable for the link defined by (dfh,name,fh). * Return 0 and set *fhrecpp to the fhlist item corresponding to it if found, * or error if not found. * Possible configurations: * 1. dfh, fh, name are all non-null: Only exact match accepted. * 2. dfh,name non-null, fh null: return first match found. * 3. fh,name non-null, dfh null: return first match found. * 3. fh non-null, dfh, name null: return first match found. * If successfull and fhrecpp is non-null then *fhrecpp points to the * returned record. If *fhrecpp was initially null, that record had * been malloc'd and must be freed by caller. */ static fhlist_ent * fh_lookup_link(char *fhpath, fhandle_t *dfh, fhandle_t *fh, char *name, fhlist_ent *fhrecp, int *errorp) { fhlist_ent *in_fhrecp = fhrecp; if ((name != NULL) && IS_DOT_FILENAME(name)) { /* we don't insert these to the database but not an error */ if (debug > 2) { PRINT_FULL_DATA(stdout, "fh_lookup_link", dfh, fh, name, " - no dot files\n") } *errorp = 0; return (NULL); } if (debug > 3) { PRINT_FULL_DATA(stdout, "fh_lookup_link", dfh, fh, name, "") } /* Add to database */ if (fh != NULL) { fhrecp = db_lookup(fhpath, fh, fhrecp, errorp); if (fhrecp == NULL) { if (debug > 3) (void) printf("fh_lookup_link: fh not found\n"); return (NULL); } /* Check if name and dfh match, if not search link */ if (((dfh == NULL) || !fh_compare(dfh, &fhrecp->dfh)) && ((name == NULL) || (strcmp(name, fhrecp->name) == 0))) { /* found it */ goto exit; } /* Found the primary record, but it's a different link */ if (debug == 3) { /* Only log if >2 but already printed */ PRINT_FULL_DATA(stdout, "fh_lookup_link", dfh, fh, name, "") } if (debug > 2) { PRINT_LINK_DATA(stdout, "Different primary link", &fhrecp->dfh, fhrecp->name, "") } /* can now free the record unless it was supplied by caller */ if (fhrecp != in_fhrecp) { free(fhrecp); fhrecp = NULL; } } /* If here, we must search by link */ if ((dfh == NULL) || (name == NULL)) { if (debug > 2) (void) printf("fh_lookup_link: invalid params\n"); *errorp = EINVAL; return (NULL); } fhrecp = db_lookup_link(fhpath, dfh, name, fhrecp, errorp); if (fhrecp == NULL) { if (debug > 3) (void) printf("fh_lookup_link: link not found: %s\n", ((*errorp >= 0) ? strerror(*errorp) : "Unknown")); return (NULL); } /* If all args supplied, check if an exact match */ if ((fh != NULL) && fh_compare(fh, &fhrecp->fh)) { if (debug > 2) { PRINT_FULL_DATA(stderr, "fh_lookup_link", dfh, fh, name, "") PRINT_LINK_DATA(stderr, "Different primary link", &fhrecp->dfh, fhrecp->name, "") } if (fhrecp != in_fhrecp) free(fhrecp); *errorp = EINVAL; return (NULL); } exit: if (debug > 3) (void) printf("lookup: found '%s' in fhtable\n", name); *errorp = 0; return (fhrecp); } /* * Export handle cache is maintained if we see an export handle that either * cannot have the path for it determined, or we failed store it. * Usually the path of an export handle can be identified in the NFSLOGTAB * and since every path for that filesystem will be affected, it's worth * caching the ones we had problem identifying. */ /* * find_fh_in_export_cache - given an export fh, find it in the cache and * return the handle */ static struct export_handle_cache * find_fh_in_export_cache(fhandle_t *fh) { struct export_handle_cache *p; for (p = exp_handle_cache; p != NULL; p = p->next) { if (memcmp(fh, &p->fh, sizeof (*fh)) == 0) break; } return (p); } static void add_fh_to_export_cache(fhandle_t *fh, char *path) { struct export_handle_cache *new; if ((new = malloc(sizeof (*new))) == NULL) { syslog(LOG_ERR, gettext( "add_fh_to_export_cache: alloc new for '%s' Error %s\n"), ((path != NULL) ? path : ""), strerror(errno)); return; } if (path != NULL) { if ((new->name = malloc(strlen(path) + 1)) == NULL) { syslog(LOG_ERR, gettext( "add_fh_to_export_cache: alloc '%s'" " Error %s\n"), path, strerror(errno)); free(new); return; } (void) strcpy(new->name, path); } else { new->name = NULL; } (void) memcpy(&new->fh, fh, sizeof (*fh)); new->next = exp_handle_cache; exp_handle_cache = new; } /* * update_export_point - called when the path for fh cannot be determined. * In the past it called get_export_path() to get the name of the * export point given a filehandle. This was a hack, since there's no * reason why the filehandle should be lost. * * If a match is found, insert the path to the database. * Return the inserted fhrecp is found, * and NULL if not. If it is an exported fs but not in the list, log a * error. * If input fhrecp is non-null, it is a valid address for result, * otherwise malloc it. */ static char * update_export_point(char *fhpath, fhandle_t *fh, char *path) { struct export_handle_cache *p; if ((fh == NULL) || memcmp(&fh->fh_data, &fh->fh_xdata, fh->fh_len)) { /* either null fh or not the root of a shared directory */ return (NULL); } /* Did we already see (and fail) this one? */ if ((p = find_fh_in_export_cache(fh)) != NULL) { /* Found it! */ if (debug > 2) { PRINT_LINK_DATA(stdout, "update_export_point", fh, ((p->name != NULL) ? p->name : ""), ""); } if (p->name == NULL) return (NULL); /* * We should not normally be here - only add to cache if * fh_add failed. */ if ((path == NULL) && ((path = malloc(strlen(p->name) + 1)) == NULL)) { syslog(LOG_ERR, gettext( "update_export_point: malloc '%s' Error %s"), p->name, strerror(errno)); return (NULL); } (void) strcpy(path, p->name); return (path); } if ((path = get_export_path(fh, path)) == NULL) { add_fh_to_export_cache(fh, NULL); return (NULL); } /* Found it! */ if (debug > 2) { PRINT_LINK_DATA(stdout, "update_export_point", fh, path, "") } if (FH_ADD(fhpath, fh, fh, path)) { /* cache this handle so we don't repeat the search */ add_fh_to_export_cache(fh, path); } return (path); } /* * HACK!!! To get rid of get_export_path() use */ /* ARGSUSED */ static char * get_export_path(fhandle_t *fh, char *path) { return (NULL); } /* * Return the absolute pathname for the filehandle "fh", using the mapping * table "fhlist". The caller must free the return string. * name is an optional dir component name, to be appended at the end * (if name is non-null, the function assumes the fh is the parent directory) * * Note: The original code was recursive, which was much more elegant but * ran out of stack... */ static char * fh_print_absolute(char *fhpath, fhandle_t *fh, char *name) { char *str, *rootname, parent[MAXPATHLEN]; int i, j, k, len, error; fhlist_ent fhrec, *fhrecp; fhandle_t prevfh; int namelen; if (debug > 3) (void) printf("fh_print_absolute: input name '%s'\n", ((name != NULL) ? name : "")); /* If name starts with '/' we are done */ if ((name != NULL) && (name[0] == '/')) { if ((str = strdup(name)) == NULL) { syslog(LOG_ERR, gettext( "fh_print_absolute: strdup '%s' error %s\n"), name, strerror(errno)); } return (str); } namelen = ((name != NULL) ? strlen(name) + 2 : 0); parent[0] = '\0'; /* remember the last filehandle we've seen */ (void) memcpy((void *) &prevfh, (void *) fh, sizeof (*fh)); fh = &prevfh; /* dump all names in reverse order */ while ((fhrecp = fh_lookup(fhpath, fh, &fhrec, &error)) != NULL && !(fhrecp->flags & (EXPORT_POINT | PUBLIC_PATH))) { if (debug > 3) { (void) printf("fh_print_absolute: name '%s'%s\n", fhrecp->name, ((fhrecp->flags & EXPORT_POINT) ? "root" : "")); } if (memcmp(&prevfh, &fhrecp->dfh, sizeof (*fh)) == 0) { /* dfh == prevfh but not an export point */ if (debug > 1) { (void) printf( "fh_print_absolute: fhrec loop:\n"); debug_opaque_print(stdout, fhrecp, fhrecp->reclen); } break; } (void) strcat(parent, "/"); (void) strcat(parent, fhrecp->name); /* remember the last filehandle we've seen */ (void) memcpy(&prevfh, &fhrecp->dfh, sizeof (fhrecp->dfh)); } assert(fh == &prevfh); if (fhrecp != NULL) { rootname = fhrecp->name; } else { /* Check if export point, just in case... */ /* There should be enough room in parent, leave the '\0' */ rootname = update_export_point( fhpath, fh, &parent[strlen(parent) + 1]); } /* Now need to reverse the order */ if (rootname != NULL) { /* *fhrecp is the export point */ len = strlen(rootname) + 2; } else { len = 2 * (NFS_FHMAXDATA + fh->fh_len); /* fid instead */ } len = ROUNDUP32(len + namelen + strlen(parent)); if ((str = malloc(len)) == NULL) { syslog(LOG_ERR, gettext( "fh_print_absolute: malloc %d error %s\n"), len, strerror(errno)); return (NULL); } /* first put the export point path in */ if (rootname != NULL) { /* *fhrecp is the export point */ (void) strcpy(str, rootname); } else { sprint_fid(str, len, fh); } for (k = strlen(str), i = strlen(parent); (k < len) && (i >= 0); i--) { for (j = i; (j >= 0) && (parent[j] != '/'); j--); if (j < 0) break; (void) strcpy(&str[k], &parent[j]); k += strlen(&str[k]); parent[j] = '\0'; } if ((name != NULL) && ((k + namelen) <= len)) { str[k] = '/'; (void) strcpy(&str[k + 1], name); } if (debug > 3) (void) printf("fh_print_absolute: path '%s'\n", str); return (str); } /* * nfslog_find_fh_dispatch - get the dispatch struct for this request */ static struct nfsl_fh_proc_disp * nfslog_find_fh_dispatch(nfslog_request_record *logrec) { nfslog_record_header *logrechdr = &logrec->re_header; struct nfsl_fh_prog_disp *progtable; /* prog struct */ struct nfsl_fh_vers_disp *verstable; /* version struct */ int i, vers; /* Find prog element - search because can't use prog as array index */ for (i = 0; (i < nfsl_fh_dispatch_table_arglen) && (logrechdr->rh_prognum != nfsl_fh_dispatch_table[i].nfsl_dis_prog); i++); if (i >= nfsl_fh_dispatch_table_arglen) { /* program not logged */ /* not an error */ return (NULL); } progtable = &nfsl_fh_dispatch_table[i]; /* Find vers element - no validity check - if here it's valid vers */ vers = logrechdr->rh_version - progtable->nfsl_dis_versmin; verstable = &progtable->nfsl_dis_vers_table[vers]; /* Find proc element - no validity check - if here it's valid proc */ return (&verstable->nfsl_dis_proc_table[logrechdr->rh_procnum]); } /* ARGSUSED */ static void nfslog_null_fhargs(caddr_t *nfsl_args, caddr_t *nfsl_res, char *fhpath, char **pathp1, char **pathp2) { *pathp1 = NULL; *pathp2 = NULL; } /* * nfslog_LOOKUP_calc - called by both lookup3 and lookup2. Handles the * mclookup as well as normal lookups. */ /* ARGSUSED */ static void nfslog_LOOKUP_calc(fhandle_t *dfh, char *name, fhandle_t *fh, char *fhpath, char **pathp1, char **pathp2, char *str) { int error; fhlist_ent fhrec; char *name1 = NULL; if (fh == &public_fh) { /* a fake lookup to inform us of the public fs path */ if (error = FH_ADD(fhpath, fh, fh, name)) { syslog(LOG_ERR, gettext( "%s: Add Public fs '%s' failed: %s\n"), str, name, ((error >= 0) ? strerror(error) : "Unknown")); } if (pathp1 != NULL) { *pathp1 = nfslog_get_path(dfh, NULL, fhpath, str); *pathp2 = NULL; } return; } if (pathp1 != NULL) { *pathp1 = nfslog_get_path(dfh, name, fhpath, str); *pathp2 = NULL; } /* If public fh mclookup, then insert complete path */ if (dfh == &public_fh) { if (pathp1 != NULL) { name = *pathp1; } else { name = nfslog_get_path(dfh, name, fhpath, str); name1 = name; } } if (fh_lookup_link(fhpath, dfh, fh, name, &fhrec, &error) != NULL) { /* link already in table */ if (name1 != NULL) free(name1); return; } /* A new link so add it */ if (error = FH_ADD(fhpath, dfh, fh, name)) { syslog(LOG_ERR, gettext( "%s: Add fh for '%s' failed: %s\n"), str, name, ((error >= 0) ? strerror(error) : "Unknown")); } if (name1 != NULL) free(name1); } /* * NFS VERSION 2 */ /* Functions for updating the fhtable for fhtoppath */ /* * nfslog_GETATTR2_fhargs - updates path1 but no fhtable changes */ /* ARGSUSED */ static void nfslog_GETATTR2_fhargs(fhandle_t *args, nfsstat *res, char *fhpath, char **pathp1, char **pathp2) { if (debug > 2) { (void) printf("=============\nGETATTR2: fh "); debug_opaque_print(stdout, args, sizeof (*args)); (void) printf("\n"); } if (pathp1 != NULL) { *pathp1 = nfslog_get_path(NFSLOG_GET_FHANDLE2(args), NULL, fhpath, "getattr2"); *pathp2 = NULL; } } /* * nfslog_SETATTR2_fhargs - updates path1 but no fhtable changes */ /* ARGSUSED */ static void nfslog_SETATTR2_fhargs(nfslog_setattrargs *args, nfsstat *res, char *fhpath, char **pathp1, char **pathp2) { if (debug > 2) { (void) printf("=============\nSETATTR2: fh "); debug_opaque_print(stdout, &args->saa_fh, sizeof (args->saa_fh)); (void) printf("\n"); } if (pathp1 != NULL) { *pathp1 = nfslog_get_path(NFSLOG_GET_FHANDLE2(&args->saa_fh), NULL, fhpath, "setattr2"); *pathp2 = NULL; } } /* * nfslog_LOOKUP2_fhargs - search the table to ensure we have not added this * one already. Note that if the response status was anything but okay, * there is no fh to check... */ /* ARGSUSED */ static void nfslog_LOOKUP2_fhargs(nfslog_diropargs *args, nfslog_diropres *res, char *fhpath, char **pathp1, char **pathp2) { char *name; fhandle_t *dfh, *fh; dfh = &args->da_fhandle; name = args->da_name; if (debug > 2) { if (res->dr_status == NFS_OK) fh = &res->nfslog_diropres_u.dr_ok.drok_fhandle; else fh = NULL; PRINT_FULL_DATA(stdout, "=============\nLOOKUP2", dfh, fh, name, "") if (res->dr_status != NFS_OK) (void) printf("status %d\n", res->dr_status); } dfh = NFSLOG_GET_FHANDLE2(dfh); if ((dfh == &public_fh) && (name[0] == '\x80')) { /* special mclookup */ name = &name[1]; } if (res->dr_status != NFS_OK) { if (pathp1 != NULL) { *pathp1 = nfslog_get_path(dfh, name, fhpath, "lookup2"); *pathp2 = NULL; } return; } fh = NFSLOG_GET_FHANDLE2(&res->nfslog_diropres_u.dr_ok.drok_fhandle); nfslog_LOOKUP_calc(dfh, name, fh, fhpath, pathp1, pathp2, "Lookup2"); } /* * nfslog_READLINK2_fhargs - updates path1 but no fhtable changes */ /* ARGSUSED */ static void nfslog_READLINK2_fhargs(fhandle_t *args, nfslog_rdlnres *res, char *fhpath, char **pathp1, char **pathp2) { if (debug > 2) { (void) printf("=============\nREADLINK2: fh "); debug_opaque_print(stdout, args, sizeof (*args)); (void) printf("\n"); } if (pathp1 != NULL) { *pathp1 = nfslog_get_path(NFSLOG_GET_FHANDLE2(args), NULL, fhpath, "readlink2"); *pathp2 = NULL; } } /* * nfslog_READ2_fhargs - updates path1 but no fhtable changes */ /* ARGSUSED */ static void nfslog_READ2_fhargs(nfslog_nfsreadargs *args, nfslog_rdresult *res, char *fhpath, char **pathp1, char **pathp2) { if (debug > 2) { (void) printf("=============\nREAD2: fh "); debug_opaque_print(stdout, &args->ra_fhandle, sizeof (args->ra_fhandle)); (void) printf("\n"); } if (pathp1 != NULL) { *pathp1 = nfslog_get_path( NFSLOG_GET_FHANDLE2(&args->ra_fhandle), NULL, fhpath, "read2"); *pathp2 = NULL; } } /* * nfslog_WRITE2_fhargs - updates path1 but no fhtable changes */ /* ARGSUSED */ static void nfslog_WRITE2_fhargs(nfslog_writeargs *args, nfslog_writeresult *res, char *fhpath, char **pathp1, char **pathp2) { if (debug > 2) { (void) printf("=============\nWRITE2: fh "); debug_opaque_print(stdout, &args->waargs_fhandle, sizeof (args->waargs_fhandle)); (void) printf("\n"); } if (pathp1 != NULL) { *pathp1 = nfslog_get_path( NFSLOG_GET_FHANDLE2(&args->waargs_fhandle), NULL, fhpath, "write2"); *pathp2 = NULL; } } /* * nfslog_CREATE2_fhargs - if the operation succeeded, we are sure there can * be no such link in the fhtable, so just add it. */ /* ARGSUSED */ static void nfslog_CREATE2_fhargs(nfslog_createargs *args, nfslog_diropres *res, char *fhpath, char **pathp1, char **pathp2) { char *name; fhandle_t *dfh, *fh; int error; name = args->ca_da.da_name; dfh = &args->ca_da.da_fhandle; if (debug > 2) { if (res->dr_status == NFS_OK) fh = &res->nfslog_diropres_u.dr_ok.drok_fhandle; else fh = NULL; PRINT_FULL_DATA(stdout, "=============\nCREATE2", dfh, fh, name, "") if (res->dr_status != NFS_OK) (void) printf("status %d\n", res->dr_status); } dfh = NFSLOG_GET_FHANDLE2(dfh); if (pathp1 != NULL) { *pathp1 = nfslog_get_path(dfh, name, fhpath, "create2"); *pathp2 = NULL; } if (res->dr_status != NFS_OK) /* no returned fh so nothing to add */ return; /* A new file handle so add it */ fh = NFSLOG_GET_FHANDLE2(&res->nfslog_diropres_u.dr_ok.drok_fhandle); if (error = FH_ADD(fhpath, dfh, fh, name)) { syslog(LOG_ERR, gettext( "Create2: Add fh for '%s' failed: %s\n"), name, ((error >= 0) ? strerror(error) : "Unknown")); } } /* * nfslog_REMOVE2_fhargs - if the operation succeeded, remove the link from * the fhtable. */ /* ARGSUSED */ static void nfslog_REMOVE2_fhargs(nfslog_diropargs *args, nfsstat *res, char *fhpath, char **pathp1, char **pathp2) { char *name; fhandle_t *dfh; int error; name = args->da_name; dfh = &args->da_fhandle; if (debug > 2) { PRINT_LINK_DATA(stdout, "=============\nREMOVE2", dfh, name, "") if (*res != NFS_OK) (void) printf("status %d\n", *res); } dfh = NFSLOG_GET_FHANDLE2(dfh); if (pathp1 != NULL) { *pathp1 = nfslog_get_path(dfh, name, fhpath, "remove2"); *pathp2 = NULL; } if (*res != NFS_OK) /* remove failed so nothing to update */ return; if (error = fh_remove(fhpath, dfh, name, pathp1)) { syslog(LOG_ERR, gettext("Remove2: '%s' failed: %s\n"), name, ((error >= 0) ? strerror(error) : "Unknown")); } } /* * nfsl_RENAME2_fhargs - updates the dfh and name fields for the given fh * to change them to the new name. */ /* ARGSUSED */ static void nfslog_RENAME2_fhargs(nfslog_rnmargs *args, nfsstat *res, char *fhpath, char **pathp1, char **pathp2) { char *from_name, *to_name; fhandle_t *from_dfh, *to_dfh; int error; from_name = args->rna_from.da_name; from_dfh = &args->rna_from.da_fhandle; to_name = args->rna_to.da_name; to_dfh = &args->rna_to.da_fhandle; if (debug > 2) { PRINT_LINK_DATA(stdout, "=============\nRENAME2: from", from_dfh, from_name, "") PRINT_LINK_DATA(stdout, "RENAME2: to ", to_dfh, to_name, "") if (*res != NFS_OK) (void) printf("status %d\n", *res); } from_dfh = NFSLOG_GET_FHANDLE2(from_dfh); to_dfh = NFSLOG_GET_FHANDLE2(to_dfh); if (pathp1 != NULL) { *pathp1 = nfslog_get_path(from_dfh, from_name, fhpath, "rename2 from"); *pathp2 = nfslog_get_path(to_dfh, to_name, fhpath, "rename2 to"); } if (*res != NFS_OK) /* rename failed so nothing to update */ return; /* Rename the link in the database */ if (error = fh_rename(fhpath, from_dfh, from_name, pathp1, to_dfh, to_name)) { syslog(LOG_ERR, gettext( "Rename2: Update from '%s' to '%s' failed: %s\n"), from_name, to_name, ((error >= 0) ? strerror(error) : "Unknown")); } } /* * nfslog_LINK2_fhargs - adds link name and fh to fhlist. Note that as a * result we may have more than one name for an fh. */ /* ARGSUSED */ static void nfslog_LINK2_fhargs(nfslog_linkargs *args, nfsstat *res, char *fhpath, char **pathp1, char **pathp2) { char *name; fhandle_t *dfh, *fh; int error; fh = &args->la_from; name = args->la_to.da_name; dfh = &args->la_to.da_fhandle; if (debug > 2) { PRINT_FULL_DATA(stdout, "=============\nLINK2", dfh, fh, name, "") if (*res != NFS_OK) (void) printf("status %d\n", *res); } dfh = NFSLOG_GET_FHANDLE2(dfh); fh = NFSLOG_GET_FHANDLE2(fh); if (pathp1 != NULL) { *pathp1 = nfslog_get_path(fh, NULL, fhpath, "link2 from"); *pathp2 = nfslog_get_path(dfh, name, fhpath, "link2 to"); } if (*res != NFS_OK) /* no returned fh so nothing to add */ return; /* A new link so add it, have fh_add find the link count */ if (error = FH_ADD(fhpath, dfh, fh, name)) { syslog(LOG_ERR, gettext( "Link2: Add fh for '%s' failed: %s\n"), name, ((error >= 0) ? strerror(error) : "Unknown")); } } /* * nfslog_SYMLINK2_fhargs - adds symlink name and fh to fhlist if fh returned. */ /* ARGSUSED */ static void nfslog_SYMLINK2_fhargs(nfslog_symlinkargs *args, nfsstat *res, char *fhpath, char **pathp1, char **pathp2) { char *name; fhandle_t *dfh; name = args->sla_from.da_name; dfh = &args->sla_from.da_fhandle; if (debug > 2) { PRINT_LINK_DATA(stdout, "=============\nSYMLINK2", dfh, name, "") } dfh = NFSLOG_GET_FHANDLE2(dfh); if (pathp1 != NULL) { *pathp1 = nfslog_get_path(dfh, name, fhpath, "symlink2"); *pathp2 = NULL; } } /* * nfslog_READDIR2_fhargs - updates path1 but no fhtable changes */ /* ARGSUSED */ static void nfslog_READDIR2_fhargs(nfslog_rddirargs *args, nfslog_rddirres *res, char *fhpath, char **pathp1, char **pathp2) { if (debug > 2) { (void) printf("=============\nREADDIR2: fh "); debug_opaque_print(stdout, &args->rda_fh, sizeof (args->rda_fh)); (void) printf("\n"); } if (pathp1 != NULL) { *pathp1 = nfslog_get_path(NFSLOG_GET_FHANDLE2(&args->rda_fh), NULL, fhpath, "readdir2"); *pathp2 = NULL; } } /* * nfslog_STATFS2_fhargs - updates path1 but no fhtable changes */ /* ARGSUSED */ static void nfslog_STATFS2_fhargs(fhandle_t *args, nfsstat *res, char *fhpath, char **pathp1, char **pathp2) { if (debug > 2) { (void) printf("=============\nSTATFS2: fh "); debug_opaque_print(stdout, args, sizeof (*args)); (void) printf("\n"); } if (pathp1 != NULL) { *pathp1 = nfslog_get_path(NFSLOG_GET_FHANDLE2(args), NULL, fhpath, "statfs2"); *pathp2 = NULL; } } /* * NFS VERSION 3 */ /* Functions for updating the fhtable for fhtoppath */ /* * nfslog_GETATTR3_fhargs - updates path1 but no fhtable changes */ /* ARGSUSED */ static void nfslog_GETATTR3_fhargs(nfs_fh3 *args, nfsstat3 *res, char *fhpath, char **pathp1, char **pathp2) { if (debug > 2) { (void) printf("=============\nGETATTR3: fh "); debug_opaque_print(stdout, args, sizeof (*args)); (void) printf("\n"); } if (pathp1 != NULL) { *pathp1 = nfslog_get_path(NFSLOG_GET_FHANDLE3(args), NULL, fhpath, "getattr3"); *pathp2 = NULL; } } /* * nfslog_SETATTR3_fhargs - updates path1 but no fhtable changes */ /* ARGSUSED */ static void nfslog_SETATTR3_fhargs(nfslog_SETATTR3args *args, nfsstat3 *res, char *fhpath, char **pathp1, char **pathp2) { if (debug > 2) { (void) printf("=============\nSETATTR3: fh "); debug_opaque_print(stdout, &args->object, sizeof (args->object)); (void) printf("\n"); } if (pathp1 != NULL) { *pathp1 = nfslog_get_path(NFSLOG_GET_FHANDLE3(&args->object), NULL, fhpath, "setattr3"); *pathp2 = NULL; } } /* * nfslog_LOOKUP3_fhargs - search the table to ensure we have not added this * one already. Note that if the response status was anything but okay, * there is no fh to check... */ /* ARGSUSED */ static void nfslog_LOOKUP3_fhargs(nfslog_diropargs3 *args, nfslog_LOOKUP3res *res, char *fhpath, char **pathp1, char **pathp2) { char *name; fhandle_t *dfh, *fh; name = args->name; dfh = NFSLOG_GET_FHANDLE3(&args->dir); if (debug > 2) { if (res->status == NFS3_OK) fh = NFSLOG_GET_FHANDLE3( &res->nfslog_LOOKUP3res_u.object); else fh = NULL; PRINT_FULL_DATA(stdout, "=============\nLOOKUP3", dfh, fh, name, "") if (res->status != NFS3_OK) (void) printf("status %d\n", res->status); } if ((dfh == &public_fh) && (name[0] == '\x80')) { /* special mclookup */ name = &name[1]; } if (res->status != NFS3_OK) { if (pathp1 != NULL) { *pathp1 = nfslog_get_path(dfh, name, fhpath, "lookup3"); *pathp2 = NULL; } return; } fh = NFSLOG_GET_FHANDLE3(&res->nfslog_LOOKUP3res_u.object); nfslog_LOOKUP_calc(dfh, name, fh, fhpath, pathp1, pathp2, "Lookup3"); } /* * nfslog_ACCESS3_fhargs - updates path1 but no fhtable changes */ /* ARGSUSED */ static void nfslog_ACCESS3_fhargs(nfs_fh3 *args, nfsstat3 *res, char *fhpath, char **pathp1, char **pathp2) { if (debug > 2) { (void) printf("=============\nACCESS3: fh "); debug_opaque_print(stdout, args, sizeof (*args)); (void) printf("\n"); } if (pathp1 != NULL) { *pathp1 = nfslog_get_path(NFSLOG_GET_FHANDLE3(args), NULL, fhpath, "access3"); *pathp2 = NULL; } } /* * nfslog_READLINK3_fhargs - updates path1 but no fhtable changes */ /* ARGSUSED */ static void nfslog_READLINK3_fhargs(nfs_fh3 *args, nfslog_READLINK3res *res, char *fhpath, char **pathp1, char **pathp2) { if (debug > 2) { (void) printf("=============\nREADLINK3: fh "); debug_opaque_print(stdout, args, sizeof (*args)); (void) printf("\n"); } if (pathp1 != NULL) { *pathp1 = nfslog_get_path(NFSLOG_GET_FHANDLE3(args), NULL, fhpath, "readlink3"); *pathp2 = NULL; } } /* * nfslog_READ3_fhargs - updates path1 but no fhtable changes */ /* ARGSUSED */ static void nfslog_READ3_fhargs(nfslog_READ3args *args, nfslog_READ3res *res, char *fhpath, char **pathp1, char **pathp2) { if (debug > 2) { (void) printf("=============\nREAD3: fh "); debug_opaque_print(stdout, &args->file, sizeof (args->file)); (void) printf("\n"); } if (pathp1 != NULL) { *pathp1 = nfslog_get_path(NFSLOG_GET_FHANDLE3(&args->file), NULL, fhpath, "read3"); *pathp2 = NULL; } } /* * nfslog_WRITE3_fhargs - updates path1 but no fhtable changes */ /* ARGSUSED */ static void nfslog_WRITE3_fhargs(nfslog_WRITE3args *args, nfslog_WRITE3res *res, char *fhpath, char **pathp1, char **pathp2) { if (debug > 2) { (void) printf("=============\nWRITE3: fh "); debug_opaque_print(stdout, &args->file, sizeof (args->file)); (void) printf("\n"); } if (pathp1 != NULL) { *pathp1 = nfslog_get_path(NFSLOG_GET_FHANDLE3(&args->file), NULL, fhpath, "write3"); *pathp2 = NULL; } } /* * nfslog_CREATE3_fhargs - if the operation succeeded, we are sure there can * be no such link in the fhtable, so just add it. */ /* ARGSUSED */ static void nfslog_CREATE3_fhargs(nfslog_CREATE3args *args, nfslog_CREATE3res *res, char *fhpath, char **pathp1, char **pathp2) { char *name; fhandle_t *dfh, *fh; int error; name = args->where.name; dfh = NFSLOG_GET_FHANDLE3(&args->where.dir); if (debug > 2) { if (res->status == NFS3_OK) fh = NFSLOG_GET_FHANDLE3( &res->nfslog_CREATE3res_u.ok.obj.handle); else fh = NULL; PRINT_FULL_DATA(stdout, "=============\nCREATE3", dfh, fh, name, "") if (res->status != NFS3_OK) (void) printf("status %d\n", res->status); } if (pathp1 != NULL) { *pathp1 = nfslog_get_path(dfh, name, fhpath, "create3"); *pathp2 = NULL; } if ((res->status != NFS3_OK) || !res->nfslog_CREATE3res_u.ok.obj.handle_follows) /* no returned fh so nothing to add */ return; /* A new file handle so add it */ fh = NFSLOG_GET_FHANDLE3(&res->nfslog_CREATE3res_u.ok.obj.handle); if (error = FH_ADD(fhpath, dfh, fh, name)) { syslog(LOG_ERR, gettext( "Create3: Add fh for '%s' failed: %s\n"), name, ((error >= 0) ? strerror(error) : "Unknown")); } } /* * nfslog_MKDIR3_fhargs - if the operation succeeded, we are sure there can * be no such link in the fhtable, so just add it. */ /* ARGSUSED */ static void nfslog_MKDIR3_fhargs(nfslog_MKDIR3args *args, nfslog_MKDIR3res *res, char *fhpath, char **pathp1, char **pathp2) { char *name; fhandle_t *dfh, *fh; int error; name = args->where.name; dfh = NFSLOG_GET_FHANDLE3(&args->where.dir); if (debug > 2) { if (res->status == NFS3_OK) fh = NFSLOG_GET_FHANDLE3( &res->nfslog_MKDIR3res_u.obj.handle); else fh = NULL; PRINT_FULL_DATA(stdout, "=============\nMKDIR3", dfh, fh, name, "") if (res->status != NFS3_OK) (void) printf("status %d\n", res->status); } if (pathp1 != NULL) { *pathp1 = nfslog_get_path(dfh, name, fhpath, "mkdir3"); *pathp2 = NULL; } if ((res->status != NFS3_OK) || !res->nfslog_MKDIR3res_u.obj.handle_follows) /* no returned fh so nothing to add */ return; /* A new file handle so add it */ fh = NFSLOG_GET_FHANDLE3(&res->nfslog_MKDIR3res_u.obj.handle); if (error = FH_ADD(fhpath, dfh, fh, name)) { syslog(LOG_ERR, gettext( "Mkdir3: Add fh for '%s' failed: %s\n"), name, ((error >= 0) ? strerror(error) : "Unknown")); } } /* * nfslog_REMOVE3_fhargs - if the operation succeeded, remove the link from * the fhtable. */ /* ARGSUSED */ static void nfslog_REMOVE3_fhargs(nfslog_REMOVE3args *args, nfsstat3 *res, char *fhpath, char **pathp1, char **pathp2) { char *name; fhandle_t *dfh; int error; name = args->object.name; dfh = NFSLOG_GET_FHANDLE3(&args->object.dir); if (debug > 2) { PRINT_LINK_DATA(stdout, "=============\nREMOVE3", dfh, name, "") if (*res != NFS3_OK) (void) printf("status %d\n", *res); } if (pathp1 != NULL) { *pathp1 = nfslog_get_path(dfh, name, fhpath, "remove3"); *pathp2 = NULL; } if (*res != NFS3_OK) /* remove failed so nothing to update */ return; if (error = fh_remove(fhpath, dfh, name, pathp1)) { syslog(LOG_ERR, gettext("Remove3: '%s' failed: %s\n"), name, ((error >= 0) ? strerror(error) : "Unknown")); } } /* * nfslog_RMDIR3_fhargs - if the operation succeeded, remove the link from * the fhtable. */ /* ARGSUSED */ static void nfslog_RMDIR3_fhargs(nfslog_RMDIR3args *args, nfsstat3 *res, char *fhpath, char **pathp1, char **pathp2) { char *name; fhandle_t *dfh; int error; name = args->object.name; dfh = NFSLOG_GET_FHANDLE3(&args->object.dir); if (debug > 2) { PRINT_LINK_DATA(stdout, "=============\nRMDIR3", dfh, name, "") if (*res != NFS3_OK) (void) printf("status %d\n", *res); } if (pathp1 != NULL) { *pathp1 = nfslog_get_path(dfh, name, fhpath, "rmdir3"); *pathp2 = NULL; } if (*res != NFS3_OK) /* rmdir failed so nothing to update */ return; if (error = fh_remove(fhpath, dfh, name, pathp1)) { syslog(LOG_ERR, gettext("Rmdir3: '%s' failed: %s\n"), name, ((error >= 0) ? strerror(error) : "Unknown")); } } /* * nfslog_RENAME3_fhargs - if the operation succeeded, update the existing * fhtable entry to point to new dir and name. */ /* ARGSUSED */ static void nfslog_RENAME3_fhargs(nfslog_RENAME3args *args, nfsstat3 *res, char *fhpath, char **pathp1, char **pathp2) { char *from_name, *to_name; fhandle_t *from_dfh, *to_dfh; int error; from_name = args->from.name; from_dfh = NFSLOG_GET_FHANDLE3(&args->from.dir); to_name = args->to.name; to_dfh = NFSLOG_GET_FHANDLE3(&args->to.dir); if (debug > 2) { PRINT_LINK_DATA(stdout, "=============\nRENAME3: from", from_dfh, from_name, "") PRINT_LINK_DATA(stdout, "=============\nRENAME3: to ", to_dfh, to_name, "") if (*res != NFS3_OK) (void) printf("status %d\n", *res); } if (pathp1 != NULL) { *pathp1 = nfslog_get_path(from_dfh, from_name, fhpath, "rename3 from"); *pathp2 = nfslog_get_path(to_dfh, to_name, fhpath, "rename3 to"); } if (*res != NFS3_OK) /* rename failed so nothing to update */ return; if (error = fh_rename(fhpath, from_dfh, from_name, pathp1, to_dfh, to_name)) { syslog(LOG_ERR, gettext( "Rename3: Update from '%s' to '%s' failed: %s\n"), from_name, to_name, ((error >= 0) ? strerror(error) : "Unknown")); } } /* * nfslog_LINK3_fhargs - if the operation succeeded, we are sure there can * be no such link in the fhtable, so just add it. */ /* ARGSUSED */ static void nfslog_LINK3_fhargs(nfslog_LINK3args *args, nfsstat3 *res, char *fhpath, char **pathp1, char **pathp2) { char *name; fhandle_t *dfh, *fh; int error; fh = NFSLOG_GET_FHANDLE3(&args->file); name = args->link.name; dfh = NFSLOG_GET_FHANDLE3(&args->link.dir); if (debug > 2) { PRINT_FULL_DATA(stdout, "=============\nLINK3", dfh, fh, name, "") if (*res != NFS3_OK) (void) printf("status %d\n", *res); } if (pathp1 != NULL) { *pathp1 = nfslog_get_path(fh, NULL, fhpath, "link3 from"); *pathp2 = nfslog_get_path(dfh, name, fhpath, "link3 to"); } if (*res != NFS3_OK) /* link failed so nothing to add */ return; /* A new link so add it, have fh_add find link count */ if (error = FH_ADD(fhpath, dfh, fh, name)) { syslog(LOG_ERR, gettext( "Link3: Add fh for '%s' failed: %s\n"), name, ((error >= 0) ? strerror(error) : "Unknown")); } } /* * nfslog_MKNOD3_fhargs - if the operation succeeded, we are sure there can * be no such link in the fhtable, so just add it. */ /* ARGSUSED */ static void nfslog_MKNOD3_fhargs(nfslog_MKNOD3args *args, nfslog_MKNOD3res *res, char *fhpath, char **pathp1, char **pathp2) { char *name; fhandle_t *dfh, *fh; int error; name = args->where.name; dfh = NFSLOG_GET_FHANDLE3(&args->where.dir); if (debug > 2) { if (res->status == NFS3_OK) fh = NFSLOG_GET_FHANDLE3( &res->nfslog_MKNOD3res_u.obj.handle); else fh = NULL; PRINT_FULL_DATA(stdout, "=============\nMKNOD3", dfh, fh, name, "") if (res->status != NFS3_OK) (void) printf("status %d\n", res->status); } if (pathp1 != NULL) { *pathp1 = nfslog_get_path(dfh, name, fhpath, "mknod3"); *pathp2 = NULL; } if ((res->status != NFS3_OK) || !res->nfslog_MKNOD3res_u.obj.handle_follows) /* no returned fh so nothing to add */ return; /* A new file handle so add it */ fh = NFSLOG_GET_FHANDLE3(&res->nfslog_MKNOD3res_u.obj.handle); if (error = FH_ADD(fhpath, dfh, fh, name)) { syslog(LOG_ERR, gettext("Mknod3: Add fh for '%s' failed: %s\n"), name, ((error >= 0) ? strerror(error) : "Unknown")); } } /* * nfslog_SYMLINK3_fhargs - if the operation succeeded, we are sure there can * be no such link in the fhtable, so just add it. */ /* ARGSUSED */ static void nfslog_SYMLINK3_fhargs(nfslog_SYMLINK3args *args, nfslog_SYMLINK3res *res, char *fhpath, char **pathp1, char **pathp2) { char *name; fhandle_t *dfh, *fh; int error; name = args->where.name; dfh = NFSLOG_GET_FHANDLE3(&args->where.dir); if (debug > 2) { if (res->status == NFS3_OK) fh = NFSLOG_GET_FHANDLE3( &res->nfslog_SYMLINK3res_u.obj.handle); else fh = NULL; PRINT_FULL_DATA(stdout, "=============\nSYMLINK3", dfh, fh, name, "") if (res->status != NFS3_OK) (void) printf("status %d\n", res->status); } if (pathp1 != NULL) { *pathp1 = nfslog_get_path(dfh, name, fhpath, "symlink3"); *pathp2 = NULL; } if ((res->status != NFS3_OK) || !res->nfslog_SYMLINK3res_u.obj.handle_follows) /* no returned fh so nothing to add */ return; /* A new file handle so add it */ fh = NFSLOG_GET_FHANDLE3(&res->nfslog_SYMLINK3res_u.obj.handle); if (error = FH_ADD(fhpath, dfh, fh, name)) { syslog(LOG_ERR, gettext( "Symlink3: Add fh for '%s' failed: %s\n"), name, ((error >= 0) ? strerror(error) : "Unknown")); } } /* * nfslog_READDIR3_fhargs - updates path1 but no fhtable changes */ /* ARGSUSED */ static void nfslog_READDIR3_fhargs(nfs_fh3 *args, nfsstat3 *res, char *fhpath, char **pathp1, char **pathp2) { if (debug > 2) { (void) printf("=============\nREADDIR3: fh "); debug_opaque_print(stdout, args, sizeof (*args)); (void) printf("\n"); } if (pathp1 != NULL) { *pathp1 = nfslog_get_path(NFSLOG_GET_FHANDLE3(args), NULL, fhpath, "readdir3"); *pathp2 = NULL; } } /* * nfslog_READDIRPLUS3_fhargs - updates path1 but no fhtable changes */ /* ARGSUSED */ static void nfslog_READDIRPLUS3_fhargs(nfslog_READDIRPLUS3args *args, nfslog_READDIRPLUS3res *res, char *fhpath, char **pathp1, char **pathp2) { char *name; fhandle_t *dfh, *fh; nfslog_entryplus3 *ep; if (debug > 2) { (void) printf("=============\nREADDIRPLUS3: fh "); debug_opaque_print(stdout, &args->dir, sizeof (args->dir)); (void) printf("\n"); } if (pathp1 != NULL) { *pathp1 = nfslog_get_path(NFSLOG_GET_FHANDLE3(&args->dir), NULL, fhpath, "readdirplus3"); *pathp2 = NULL; } if (res->status == NFS3_OK) { dfh = NFSLOG_GET_FHANDLE3(&args->dir); /* * Loop through the fh/name pair and add them * to the mappings. */ for (ep = res->nfslog_READDIRPLUS3res_u.ok.reply.entries; ep != NULL; ep = ep->nextentry) { name = ep->name; fh = NFSLOG_GET_FHANDLE3(&ep->name_handle.handle); nfslog_LOOKUP_calc(dfh, name, fh, fhpath, NULL, NULL, "ReaddirPlus3"); } } } /* * nfslog_FSSTAT3_fhargs - updates path1 but no fhtable changes */ /* ARGSUSED */ static void nfslog_FSSTAT3_fhargs(nfs_fh3 *args, nfsstat3 *res, char *fhpath, char **pathp1, char **pathp2) { if (debug > 2) { (void) printf("=============\nFSSTAT3: fh "); debug_opaque_print(stdout, args, sizeof (*args)); (void) printf("\n"); } if (pathp1 != NULL) { *pathp1 = nfslog_get_path(NFSLOG_GET_FHANDLE3(args), NULL, fhpath, "fsstat3"); *pathp2 = NULL; } } /* * nfslog_FSINFO3_fhargs - updates path1 but no fhtable changes */ /* ARGSUSED */ static void nfslog_FSINFO3_fhargs(nfs_fh3 *args, nfsstat3 *res, char *fhpath, char **pathp1, char **pathp2) { if (debug > 2) { (void) printf("=============\nFSINFO3: fh "); debug_opaque_print(stdout, args, sizeof (*args)); (void) printf("\n"); } if (pathp1 != NULL) { *pathp1 = nfslog_get_path(NFSLOG_GET_FHANDLE3(args), NULL, fhpath, "fsinfo3"); *pathp2 = NULL; } } /* * nfslog_PATHCONF3_fhargs - updates path1 but no fhtable changes */ /* ARGSUSED */ static void nfslog_PATHCONF3_fhargs(nfs_fh3 *args, nfsstat3 *res, char *fhpath, char **pathp1, char **pathp2) { if (debug > 2) { (void) printf("=============\nPATHCONF3: fh "); debug_opaque_print(stdout, args, sizeof (*args)); (void) printf("\n"); } if (pathp1 != NULL) { *pathp1 = nfslog_get_path(NFSLOG_GET_FHANDLE3(args), NULL, fhpath, "pathconf3"); *pathp2 = NULL; } } /* * nfslog_COMMIT3_fhargs - updates path1 but no fhtable changes */ /* ARGSUSED */ static void nfslog_COMMIT3_fhargs(nfslog_COMMIT3args *args, nfsstat3 *res, char *fhpath, char **pathp1, char **pathp2) { if (debug > 2) { (void) printf("=============\nCOMMIT3: fh "); debug_opaque_print(stdout, &args->file, sizeof (args->file)); (void) printf("\n"); } if (pathp1 != NULL) { *pathp1 = nfslog_get_path(NFSLOG_GET_FHANDLE3(&args->file), NULL, fhpath, "commit3"); *pathp2 = NULL; } } /* * NFSLOG VERSION 1 */ /* * nfslog_SHARE_fhargs - adds export path and handle to fhlist */ /* ARGSUSED */ static void nfslog_SHARE_fhargs(nfslog_sharefsargs *args, nfslog_sharefsres *res, char *fhpath, char **pathp1, char **pathp2) { fhlist_ent fhrec; fhandle_t *fh; int error; if (debug > 2) { (void) printf( "=============\nSHARE: name '%s', fh ", args->sh_path); debug_opaque_print(stdout, &args->sh_fh_buf, sizeof (fhandle_t)); (void) printf("\n"); } fh = &args->sh_fh_buf; /* * This bcopy is done because the fh_data for the export/share directory * is not meaningful with respect to the database keys. Therefore, we * copy the export or fh_xdata fid to the fh_data so that a reasonable * entry will be added in the data base. */ bcopy(fh->fh_xdata, fh->fh_data, fh->fh_xlen); /* If debug print the database */ if (debug > 10) { fh_print_all_keys(fhpath, fh); } if (fh_lookup_link(fhpath, fh, fh, args->sh_path, &fhrec, &error) == NULL) { if (error = FH_ADD(fhpath, fh, fh, args->sh_path)) { syslog(LOG_ERR, gettext( "Share: Add fh for '%s' failed: %s\n"), args->sh_path, ((error >= 0) ? strerror(error) : "Unknown")); } } if (pathp1 != NULL) { *pathp1 = nfslog_get_path(fh, NULL, fhpath, "share"); *pathp2 = NULL; } } /* * nfslog_UNSHARE_fhargs - remove export path and handle from fhlist */ /* ARGSUSED */ static void nfslog_UNSHARE_fhargs(nfslog_sharefsargs *args, nfslog_sharefsres *res, char *fhpath, char **pathp1, char **pathp2) { fhandle_t *fh; int error; if (debug > 2) { (void) printf("=============\nUNSHARE: name '%s', fh ", args->sh_path); debug_opaque_print(stdout, &args->sh_fh_buf, sizeof (fhandle_t)); (void) printf("\n"); } fh = &args->sh_fh_buf; /* * This bcopy is done because the fh_data for the export/share directory * is not meaningful with respect to the database keys. Therefore, we * copy the export or fh_xdata fid to the fh_data so that a reasonable * entry will be added in the data base. */ bcopy(fh->fh_xdata, fh->fh_data, fh->fh_xlen); /* If debug print the database */ if (debug > 10) { fh_print_all_keys(fhpath, fh); } if (pathp1 != NULL) { *pathp1 = nfslog_get_path(fh, NULL, fhpath, "share"); *pathp2 = NULL; } if (error = fh_remove(fhpath, fh, args->sh_path, pathp1)) { syslog(LOG_ERR, gettext("Unshare: '%s' failed: %s\n"), args->sh_path, ((error >= 0) ? strerror(error) : "Unknown")); } } /* ARGSUSED */ static void nfslog_GETFH_fhargs(nfslog_getfhargs *args, nfsstat *res, char *fhpath, char **pathp1, char **pathp2) { fhlist_ent fhrec; fhandle_t *fh; int error; if (debug > 2) { (void) printf("=============\nGETFH3: name '%s', fh ", args->gfh_path); debug_opaque_print(stdout, &args->gfh_fh_buf, sizeof (fhandle_t)); (void) printf("\n"); } fh = &args->gfh_fh_buf; /* If debug print the database */ if (debug > 10) { fh_print_all_keys(fhpath, fh); } if (fh_lookup_link(fhpath, fh, fh, args->gfh_path, &fhrec, &error) == NULL) { if (error = FH_ADD(fhpath, fh, fh, args->gfh_path)) { syslog(LOG_ERR, gettext( "Getfh: Add fh for '%s' failed: %s\n"), args->gfh_path, ((error >= 0) ? strerror(error) : "Unknown")); } } if (pathp1 != NULL) { *pathp1 = nfslog_get_path(fh, NULL, fhpath, "getfh"); *pathp2 = NULL; } } /* * Exported function */ /* * nfslog_get_path - gets the path for this file. fh must be supplied, * name may be null. If name is supplied, fh is assumed to be a directory * filehandle, with name as its component. fhpath is the generic path for the * fhtopath table and prtstr is the name of the caller (for debug purposes). * Returns the malloc'd path. The caller must free it later. */ char * nfslog_get_path(fhandle_t *fh, char *name, char *fhpath, char *prtstr) { char *pathp = fh_print_absolute(fhpath, fh, name); if (debug > 3) { (void) printf(" %s: path '%s', fh ", prtstr, pathp); debug_opaque_print(stdout, fh, sizeof (*fh)); (void) printf("\n"); } return (pathp); } /* * nfslog_process_fh_rec - updates the fh table based on the rpc req * Return 0 for success, error otherwise. If success return the path * for the input file handle(s) if so indicated. */ int nfslog_process_fh_rec(struct nfslog_lr *lrp, char *fhpath, char **pathp1, char **pathp2, bool_t return_path) { struct nfsl_fh_proc_disp *disp; nfslog_request_record *logrec = &lrp->log_record; nfslog_record_header *logrechdr = &logrec->re_header; if ((disp = nfslog_find_fh_dispatch(logrec)) != NULL) { /* * Allocate space for the args and results and decode */ logrec->re_rpc_arg = calloc(1, disp->args_size); if (!(*disp->xdr_args)(&lrp->xdrs, logrec->re_rpc_arg)) { free(logrec->re_rpc_arg); logrec->re_rpc_arg = NULL; syslog(LOG_ERR, gettext("argument decode failed")); return (FALSE); } /* used later for free of data structures */ lrp->xdrargs = disp->xdr_args; logrec->re_rpc_res = calloc(1, disp->res_size); if (!(*disp->xdr_res)(&lrp->xdrs, logrec->re_rpc_res)) { free(logrec->re_rpc_res); logrec->re_rpc_res = NULL; syslog(LOG_ERR, gettext("results decode failed")); return (FALSE); } /* used later for free of data structures */ lrp->xdrres = disp->xdr_res; /* * Process the operation within the context of the file handle * mapping process */ if (return_path) { (*disp->nfsl_dis_args)(logrec->re_rpc_arg, logrec->re_rpc_res, fhpath, pathp1, pathp2); } else { if ((logrechdr->rh_version == NFS_VERSION && logrechdr->rh_procnum == RFS_LINK) || (logrechdr->rh_version == NFS_V3 && logrechdr->rh_procnum == NFSPROC3_LINK)) { (*disp->nfsl_dis_args)(logrec->re_rpc_arg, logrec->re_rpc_res, fhpath, pathp1, pathp2); } else { (*disp->nfsl_dis_args)(logrec->re_rpc_arg, logrec->re_rpc_res, fhpath, NULL, NULL); } } return (TRUE); } else { syslog(LOG_ERR, gettext("procedure unknown")); return (FALSE); } } /* * 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 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _FHTAB_H #define _FHTAB_H /* * Support for the fh mapping file for nfslog. */ #ifdef __cplusplus extern "C" { #endif /* * RPC dispatch table for file handles * Indexed by program, version, proc * Based on NFS dispatch table. * Differences: no xdr of args/res. */ struct nfsl_fh_proc_disp { void (*nfsl_dis_args)(); /* dispatch routine for proc */ bool_t (*xdr_args)(); /* XDR function for arguments */ bool_t (*xdr_res)(); /* XDR function for results */ int args_size; /* size of arguments struct */ int res_size; /* size of results struct */ }; struct nfsl_fh_vers_disp { int nfsl_dis_nprocs; /* number of procs */ struct nfsl_fh_proc_disp *nfsl_dis_proc_table; /* proc array */ }; struct nfsl_fh_prog_disp { int nfsl_dis_prog; /* program number */ int nfsl_dis_versmin; /* minimum version number */ int nfsl_dis_nvers; /* number of version values */ struct nfsl_fh_vers_disp *nfsl_dis_vers_table; /* versions array */ }; /* key comprised of inode/gen, currenly 8 or 10 bytes */ #define PRIMARY_KEY_LEN_MAX 16 typedef char fh_primary_key[PRIMARY_KEY_LEN_MAX]; /* link key - directory primary key plus name (upto 2 components) */ #define SECONDARY_KEY_LEN_MAX (PRIMARY_KEY_LEN_MAX + MAXPATHLEN) typedef char fh_secondary_key[SECONDARY_KEY_LEN_MAX]; /* * This is the runtime filehandle table entry. Because an fhandle_t is * used for both Version 2 and Version 3, we don't need two different types * of entries in the table. */ typedef struct fhlist_ent { fhandle_t fh; /* filehandle for this component */ time32_t mtime; /* modification time of entry */ time32_t atime; /* access time of entry */ fhandle_t dfh; /* parent filehandle for this component */ ushort_t flags; short reclen; /* length of record */ char name[MAXPATHLEN]; /* variable record */ } fhlist_ent; /* flags values */ #define EXPORT_POINT 0x01 /* if this is export point */ #define NAME_DELETED 0x02 /* is the dir info still valid for this fh? */ #define PUBLIC_PATH 0x04 /* is the dir info still valid for this fh? */ /* * Information maintained for the secondary key * Note that this is a variable length record with 4 variable size fields: * fhkey - primary key (must be there) * name - component name (must be there) * next - next link in list (could be null) * prev - previous link in list (could be null) */ #define MAX_LINK_VARBUF (3 * SECONDARY_KEY_LEN_MAX) typedef struct linkinfo_ent { fhandle_t dfh; /* directory filehandle */ time32_t mtime; /* modification time of entry */ time32_t atime; /* access time of entry */ ushort_t flags; short reclen; /* Actual record length */ short fhkey_offset; /* offset of fhkey, from head of record */ short name_offset; /* offset of name */ short next_offset; /* offset of next link key */ short prev_offset; /* offset of prev link key */ char varbuf[MAX_LINK_VARBUF]; /* max size for above */ } linkinfo_ent; /* Macros for lengths of the various fields */ #define LN_FHKEY_LEN(link) ((link)->name_offset - (link)->fhkey_offset) #define LN_NAME_LEN(link) ((link)->next_offset - (link)->name_offset) #define LN_NEXT_LEN(link) ((link)->prev_offset - (link)->next_offset) #define LN_PREV_LEN(link) ((link)->reclen - (link)->prev_offset) /* Macros for address of the various fields */ #define LN_FHKEY(link) (char *)((uintptr_t)(link) + (link)->fhkey_offset) #define LN_NAME(link) (char *)((uintptr_t)(link) + (link)->name_offset) #define LN_NEXT(link) (char *)((uintptr_t)(link) + (link)->next_offset) #define LN_PREV(link) (char *)((uintptr_t)(link) + (link)->prev_offset) /* Which record can reside in database */ typedef union { fhlist_ent fhlist_rec; linkinfo_ent link_rec; } db_record; void debug_opaque_print(FILE *, void *buf, int size); int db_add(char *fhpath, fhandle_t *dfh, char *name, fhandle_t *fh, uint_t flags); fhlist_ent *db_lookup(char *fhpath, fhandle_t *fh, fhlist_ent *fhrecp, int *errorp); fhlist_ent *db_lookup_link(char *fhpath, fhandle_t *dfh, char *name, fhlist_ent *fhrecp, int *errorp); int db_delete(char *fhpath, fhandle_t *fh); int db_delete_link(char *fhpath, fhandle_t *dfh, char *name); int db_rename_link(char *fhpath, fhandle_t *from_dfh, char *from_name, fhandle_t *to_dfh, char *to_name); void db_print_all_keys(char *fhpath, fsid_t *fsidp, FILE *fp); char *nfslog_get_path(fhandle_t *fh, char *name, char *fhpath, char *prtstr); extern fhandle_t public_fh; /* * Macro to determine which fhandle to use - input or public fh */ #define NFSLOG_GET_FHANDLE2(fh) \ (((fh)->fh_len > 0) ? fh : &public_fh) /* * Macro to determine which fhandle to use - input or public fh */ #define NFSLOG_GET_FHANDLE3(fh3) \ (((fh3)->fh3_length == sizeof (fhandle_t)) ? \ (fhandle_t *)&(fh3)->fh3_u.data : &public_fh) #ifdef __cplusplus } #endif #endif /* _FHTAB_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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) 1999 by Sun Microsystems, Inc. * All rights reserved. */ #include #include static bool_t xdr_timestruc32_t(XDR *, timestruc32_t *); static bool_t xdr_nfs2_timeval(XDR *, nfs2_timeval *); static bool_t xdr_ftype3(XDR *, ftype3 *); static bool_t xdr_stable_how(XDR *, stable_how *); static bool_t xdr_createmode3(XDR *, createmode3 *); static bool_t xdr_size3(XDR *, size3 *); static bool_t xdr_count3(XDR *, count3 *); static bool_t xdr_set_size3(XDR *, set_size3 *); static bool_t xdr_offset3(XDR *, offset3 *); static bool_t xdr_post_op_fh3(XDR *, post_op_fh3 *); static bool_t xdr_nfsreadargs(XDR *, struct nfsreadargs *); static bool_t xdr_nfslog_record_header(XDR *, nfslog_record_header *); static bool_t xdr_nfslog_drok(XDR *, nfslog_drok *); static bool_t xdr_nfslog_rrok(XDR *, nfslog_rrok *); static bool_t xdr_nfslog_sattr(XDR *, nfslog_sattr *); static bool_t xdr_nfslog_rdok(XDR *, nfslog_rdok *); static bool_t xdr_nfslog_createhow3(XDR *, nfslog_createhow3 *); static bool_t xdr_nfslog_CREATE3resok(XDR *, nfslog_CREATE3resok *); static bool_t xdr_nfslog_READ3resok(XDR *, nfslog_READ3resok *); static bool_t xdr_nfslog_WRITE3resok(XDR *, nfslog_WRITE3resok *); static bool_t xdr_nfslog_entryplus3(XDR *, nfslog_entryplus3 *); static bool_t xdr_nfslog_dirlistplus3(XDR *, nfslog_dirlistplus3 *); static bool_t xdr_nfslog_READDIRPLUS3resok(XDR *, nfslog_READDIRPLUS3resok *); static bool_t xdr_timestruc32_t(XDR *xdrs, timestruc32_t *objp) { if (!xdr_int(xdrs, &objp->tv_sec)) return (FALSE); if (!xdr_int(xdrs, &objp->tv_nsec)) return (FALSE); return (TRUE); } static bool_t xdr_nfs2_timeval(XDR *xdrs, nfs2_timeval *objp) { if (!xdr_u_int(xdrs, &objp->tv_sec)) return (FALSE); if (!xdr_u_int(xdrs, &objp->tv_usec)) return (FALSE); return (TRUE); } bool_t xdr_nfsstat(XDR *xdrs, nfsstat *objp) { if (!xdr_enum(xdrs, (enum_t *)objp)) return (FALSE); return (TRUE); } bool_t xdr_uint64(XDR *xdrs, uint64 *objp) { return (xdr_u_longlong_t(xdrs, objp)); } bool_t xdr_uint32(XDR *xdrs, uint32 *objp) { return (xdr_u_int(xdrs, objp)); } static bool_t xdr_ftype3(XDR *xdrs, ftype3 *objp) { return (xdr_enum(xdrs, (enum_t *)objp)); } static bool_t xdr_stable_how(XDR *xdrs, stable_how *objp) { return (xdr_enum(xdrs, (enum_t *)objp)); } static bool_t xdr_createmode3(XDR *xdrs, createmode3 *objp) { return (xdr_enum(xdrs, (enum_t *)objp)); } static bool_t xdr_size3(XDR *xdrs, size3 *objp) { return (xdr_uint64(xdrs, objp)); } static bool_t xdr_count3(XDR *xdrs, count3 *objp) { return (xdr_uint32(xdrs, objp)); } static bool_t xdr_set_size3(XDR *xdrs, set_size3 *objp) { if (!xdr_bool(xdrs, &objp->set_it)) return (FALSE); switch (objp->set_it) { case TRUE: if (!xdr_size3(xdrs, &objp->size)) return (FALSE); break; } return (TRUE); } static bool_t xdr_offset3(XDR *xdrs, offset3 *objp) { return (xdr_uint64(xdrs, objp)); } bool_t xdr_fhandle(XDR *xdrs, fhandle_t *fh) { if (xdrs->x_op == XDR_FREE) return (TRUE); return (xdr_opaque(xdrs, (caddr_t)fh, NFS_FHSIZE)); } bool_t xdr_nfs_fh3(XDR *xdrs, nfs_fh3 *objp) { if (!xdr_u_int(xdrs, &objp->fh3_length)) return (FALSE); if (objp->fh3_length > NFS3_FHSIZE) return (FALSE); if (xdrs->x_op == XDR_DECODE || xdrs->x_op == XDR_ENCODE) return (xdr_opaque(xdrs, objp->fh3_u.data, objp->fh3_length)); if (xdrs->x_op == XDR_FREE) return (TRUE); return (FALSE); } static bool_t xdr_post_op_fh3(XDR *xdrs, post_op_fh3 *objp) { if (!xdr_bool(xdrs, &objp->handle_follows)) return (FALSE); switch (objp->handle_follows) { case TRUE: if (!xdr_nfs_fh3(xdrs, &objp->handle)) return (FALSE); break; case FALSE: break; default: return (FALSE); } return (TRUE); } bool_t xdr_nfsstat3(XDR *xdrs, nfsstat3 *objp) { return (xdr_enum(xdrs, (enum_t *)objp)); } static bool_t xdr_nfsreadargs(XDR *xdrs, struct nfsreadargs *ra) { if (xdr_fhandle(xdrs, &ra->ra_fhandle) && xdr_u_int(xdrs, &ra->ra_offset) && xdr_u_int(xdrs, &ra->ra_count) && xdr_u_int(xdrs, &ra->ra_totcount)) { return (TRUE); } return (FALSE); } bool_t xdr_nfslog_buffer_header(XDR *xdrs, nfslog_buffer_header *objp) { if (!xdr_u_int(xdrs, &objp->bh_length)) return (FALSE); if (!xdr_rpcvers(xdrs, &objp->bh_version)) return (FALSE); if (objp->bh_version > 1) { if (!xdr_u_longlong_t(xdrs, &objp->bh_offset)) return (FALSE); if (!xdr_u_int(xdrs, &objp->bh_flags)) return (FALSE); } else { uint_t bh_offset; if (!xdr_u_int(xdrs, &objp->bh_flags)) return (FALSE); if (xdrs->x_op == XDR_ENCODE) bh_offset = (uint_t)objp->bh_offset; if (!xdr_u_int(xdrs, &bh_offset)) return (FALSE); if (xdrs->x_op == XDR_DECODE) objp->bh_offset = (u_offset_t)bh_offset; } if (!xdr_timestruc32_t(xdrs, &objp->bh_timestamp)) return (FALSE); return (TRUE); } static bool_t xdr_nfslog_record_header(XDR *xdrs, nfslog_record_header *objp) { if (!xdr_u_int(xdrs, &objp->rh_reclen)) return (FALSE); if (!xdr_u_int(xdrs, &objp->rh_rec_id)) return (FALSE); if (!xdr_rpcprog(xdrs, &objp->rh_prognum)) return (FALSE); if (!xdr_rpcproc(xdrs, &objp->rh_procnum)) return (FALSE); if (!xdr_rpcvers(xdrs, &objp->rh_version)) return (FALSE); if (!xdr_u_int(xdrs, &objp->rh_auth_flavor)) return (FALSE); if (!xdr_timestruc32_t(xdrs, &objp->rh_timestamp)) return (FALSE); if (!xdr_uid_t(xdrs, &objp->rh_uid)) return (FALSE); if (!xdr_gid_t(xdrs, &objp->rh_gid)) return (FALSE); return (TRUE); } bool_t xdr_nfslog_request_record(XDR *xdrs, nfslog_request_record *objp) { if (!xdr_nfslog_record_header(xdrs, &objp->re_header)) return (FALSE); if (!xdr_string(xdrs, &objp->re_principal_name, ~0)) return (FALSE); if (!xdr_string(xdrs, &objp->re_netid, ~0)) return (FALSE); if (!xdr_string(xdrs, &objp->re_tag, ~0)) return (FALSE); if (!xdr_netbuf(xdrs, &objp->re_ipaddr)) return (FALSE); return (TRUE); } bool_t xdr_nfslog_sharefsargs(XDR *xdrs, nfslog_sharefsargs *objp) { if (!xdr_int(xdrs, &objp->sh_flags)) return (FALSE); if (!xdr_u_int(xdrs, &objp->sh_anon)) return (FALSE); if (!xdr_string(xdrs, &objp->sh_path, ~0)) return (FALSE); if (!xdr_fhandle(xdrs, &objp->sh_fh_buf)) return (FALSE); return (TRUE); } bool_t xdr_nfslog_sharefsres(XDR *xdrs, nfslog_sharefsres *objp) { if (!xdr_nfsstat(xdrs, objp)) return (FALSE); return (TRUE); } bool_t xdr_nfslog_getfhargs(XDR *xdrs, nfslog_getfhargs *objp) { if (!xdr_fhandle(xdrs, &objp->gfh_fh_buf)) return (FALSE); if (!xdr_string(xdrs, &objp->gfh_path, ~0)) return (FALSE); return (TRUE); } bool_t xdr_nfslog_diropargs(XDR *xdrs, nfslog_diropargs *objp) { if (!xdr_fhandle(xdrs, &objp->da_fhandle)) return (FALSE); if (!xdr_string(xdrs, &objp->da_name, ~0)) return (FALSE); return (TRUE); } static bool_t xdr_nfslog_drok(XDR *xdrs, nfslog_drok *objp) { if (!xdr_fhandle(xdrs, &objp->drok_fhandle)) return (FALSE); return (TRUE); } bool_t xdr_nfslog_diropres(XDR *xdrs, nfslog_diropres *objp) { if (!xdr_nfsstat(xdrs, &objp->dr_status)) return (FALSE); switch (objp->dr_status) { case NFS_OK: if (!xdr_nfslog_drok(xdrs, &objp->nfslog_diropres_u.dr_ok)) return (FALSE); break; } return (TRUE); } bool_t xdr_nfslog_nfsreadargs(XDR *xdrs, nfslog_nfsreadargs *objp) { if (!xdr_nfsreadargs(xdrs, objp)) return (FALSE); return (TRUE); } static bool_t xdr_nfslog_rrok(XDR *xdrs, nfslog_rrok *objp) { if (!xdr_u_int(xdrs, &objp->filesize)) return (FALSE); if (!xdr_u_int(xdrs, &objp->rrok_count)) return (FALSE); return (TRUE); } bool_t xdr_nfslog_rdresult(XDR *xdrs, nfslog_rdresult *objp) { if (!xdr_nfsstat(xdrs, &objp->r_status)) return (FALSE); switch (objp->r_status) { case NFS_OK: if (!xdr_nfslog_rrok(xdrs, &objp->nfslog_rdresult_u.r_ok)) return (FALSE); break; } return (TRUE); } bool_t xdr_nfslog_writeargs(XDR *xdrs, nfslog_writeargs *objp) { if (!xdr_fhandle(xdrs, &objp->waargs_fhandle)) return (FALSE); if (!xdr_u_int(xdrs, &objp->waargs_begoff)) return (FALSE); if (!xdr_u_int(xdrs, &objp->waargs_offset)) return (FALSE); if (!xdr_u_int(xdrs, &objp->waargs_totcount)) return (FALSE); if (!xdr_u_int(xdrs, &objp->waargs_count)) return (FALSE); return (TRUE); } bool_t xdr_nfslog_writeresult(XDR *xdrs, nfslog_writeresult *objp) { if (!xdr_nfsstat(xdrs, &objp->wr_status)) return (FALSE); switch (objp->wr_status) { case NFS_OK: if (!xdr_u_int(xdrs, &objp->nfslog_writeresult_u.wr_size)) return (FALSE); break; } return (TRUE); } static bool_t xdr_nfslog_sattr(XDR *xdrs, nfslog_sattr *objp) { if (!xdr_u_int(xdrs, &objp->sa_mode)) return (FALSE); if (!xdr_u_int(xdrs, &objp->sa_uid)) return (FALSE); if (!xdr_u_int(xdrs, &objp->sa_gid)) return (FALSE); if (!xdr_u_int(xdrs, &objp->sa_size)) return (FALSE); if (!xdr_nfs2_timeval(xdrs, (nfs2_timeval *)&objp->sa_atime)) return (FALSE); if (!xdr_nfs2_timeval(xdrs, (nfs2_timeval *)&objp->sa_mtime)) return (FALSE); return (TRUE); } bool_t xdr_nfslog_createargs(XDR *xdrs, nfslog_createargs *objp) { if (!xdr_nfslog_sattr(xdrs, &objp->ca_sa)) return (FALSE); if (!xdr_nfslog_diropargs(xdrs, &objp->ca_da)) return (FALSE); return (TRUE); } bool_t xdr_nfslog_setattrargs(XDR *xdrs, nfslog_setattrargs *objp) { if (!xdr_fhandle(xdrs, &objp->saa_fh)) return (FALSE); if (!xdr_nfslog_sattr(xdrs, &objp->saa_sa)) return (FALSE); return (TRUE); } bool_t xdr_nfslog_rdlnres(XDR *xdrs, nfslog_rdlnres *objp) { if (!xdr_nfsstat(xdrs, &objp->rl_status)) return (FALSE); switch (objp->rl_status) { case NFS_OK: if (!xdr_string(xdrs, &objp->nfslog_rdlnres_u.rl_ok, ~0)) return (FALSE); break; } return (TRUE); } bool_t xdr_nfslog_rnmargs(XDR *xdrs, nfslog_rnmargs *objp) { if (!xdr_nfslog_diropargs(xdrs, &objp->rna_from)) return (FALSE); if (!xdr_nfslog_diropargs(xdrs, &objp->rna_to)) return (FALSE); return (TRUE); } bool_t xdr_nfslog_linkargs(XDR *xdrs, nfslog_linkargs *objp) { if (!xdr_fhandle(xdrs, &objp->la_from)) return (FALSE); if (!xdr_nfslog_diropargs(xdrs, &objp->la_to)) return (FALSE); return (TRUE); } bool_t xdr_nfslog_symlinkargs(XDR *xdrs, nfslog_symlinkargs *objp) { if (!xdr_nfslog_diropargs(xdrs, &objp->sla_from)) return (FALSE); if (!xdr_string(xdrs, &objp->sla_tnm, ~0)) return (FALSE); if (!xdr_nfslog_sattr(xdrs, &objp->sla_sa)) return (FALSE); return (TRUE); } bool_t xdr_nfslog_rddirargs(XDR *xdrs, nfslog_rddirargs *objp) { if (!xdr_fhandle(xdrs, &objp->rda_fh)) return (FALSE); if (!xdr_u_int(xdrs, &objp->rda_offset)) return (FALSE); if (!xdr_u_int(xdrs, &objp->rda_count)) return (FALSE); return (TRUE); } static bool_t xdr_nfslog_rdok(XDR *xdrs, nfslog_rdok *objp) { if (!xdr_u_int(xdrs, &objp->rdok_offset)) return (FALSE); if (!xdr_u_int(xdrs, &objp->rdok_size)) return (FALSE); if (!xdr_bool(xdrs, &objp->rdok_eof)) return (FALSE); return (TRUE); } bool_t xdr_nfslog_rddirres(XDR *xdrs, nfslog_rddirres *objp) { if (!xdr_nfsstat(xdrs, &objp->rd_status)) return (FALSE); switch (objp->rd_status) { case NFS_OK: if (!xdr_nfslog_rdok(xdrs, &objp->nfslog_rddirres_u.rd_ok)) return (FALSE); break; } return (TRUE); } bool_t xdr_nfslog_diropargs3(XDR *xdrs, nfslog_diropargs3 *objp) { if (!xdr_nfs_fh3(xdrs, &objp->dir)) return (FALSE); if (!xdr_string(xdrs, &objp->name, ~0)) return (FALSE); return (TRUE); } bool_t xdr_nfslog_LOOKUP3res(XDR *xdrs, nfslog_LOOKUP3res *objp) { if (!xdr_nfsstat3(xdrs, &objp->status)) return (FALSE); switch (objp->status) { case NFS3_OK: if (!xdr_nfs_fh3(xdrs, &objp->nfslog_LOOKUP3res_u.object)) return (FALSE); break; } return (TRUE); } static bool_t xdr_nfslog_createhow3(XDR *xdrs, nfslog_createhow3 *objp) { if (!xdr_createmode3(xdrs, &objp->mode)) return (FALSE); switch (objp->mode) { case UNCHECKED: case GUARDED: if (!xdr_set_size3(xdrs, &objp->nfslog_createhow3_u.size)) return (FALSE); break; case EXCLUSIVE: break; default: return (FALSE); } return (TRUE); } bool_t xdr_nfslog_CREATE3args(XDR *xdrs, nfslog_CREATE3args *objp) { if (!xdr_nfslog_diropargs3(xdrs, &objp->where)) return (FALSE); if (!xdr_nfslog_createhow3(xdrs, &objp->how)) return (FALSE); return (TRUE); } static bool_t xdr_nfslog_CREATE3resok(XDR *xdrs, nfslog_CREATE3resok *objp) { if (!xdr_post_op_fh3(xdrs, &objp->obj)) return (FALSE); return (TRUE); } bool_t xdr_nfslog_CREATE3res(XDR *xdrs, nfslog_CREATE3res *objp) { if (!xdr_nfsstat3(xdrs, &objp->status)) return (FALSE); switch (objp->status) { case NFS3_OK: if (!xdr_nfslog_CREATE3resok( xdrs, &objp->nfslog_CREATE3res_u.ok)) return (FALSE); break; } return (TRUE); } bool_t xdr_nfslog_SETATTR3args(XDR *xdrs, nfslog_SETATTR3args *objp) { if (!xdr_nfs_fh3(xdrs, &objp->object)) return (FALSE); if (!xdr_set_size3(xdrs, &objp->size)) return (FALSE); return (TRUE); } bool_t xdr_nfslog_READLINK3res(XDR *xdrs, nfslog_READLINK3res *objp) { if (!xdr_nfsstat3(xdrs, &objp->status)) return (FALSE); switch (objp->status) { case NFS3_OK: if (!xdr_string(xdrs, &objp->nfslog_READLINK3res_u.data, ~0)) return (FALSE); break; } return (TRUE); } bool_t xdr_nfslog_READ3args(XDR *xdrs, nfslog_READ3args *objp) { if (!xdr_nfs_fh3(xdrs, &objp->file)) return (FALSE); if (!xdr_offset3(xdrs, &objp->offset)) return (FALSE); if (!xdr_count3(xdrs, &objp->count)) return (FALSE); return (TRUE); } static bool_t xdr_nfslog_READ3resok(XDR *xdrs, nfslog_READ3resok *objp) { if (!xdr_size3(xdrs, &objp->filesize)) return (FALSE); if (!xdr_count3(xdrs, &objp->count)) return (FALSE); if (!xdr_bool(xdrs, &objp->eof)) return (FALSE); if (!xdr_u_int(xdrs, &objp->size)) return (FALSE); return (TRUE); } bool_t xdr_nfslog_READ3res(XDR *xdrs, nfslog_READ3res *objp) { if (!xdr_nfsstat3(xdrs, &objp->status)) return (FALSE); switch (objp->status) { case NFS3_OK: if (!xdr_nfslog_READ3resok(xdrs, &objp->nfslog_READ3res_u.ok)) return (FALSE); break; } return (TRUE); } bool_t xdr_nfslog_WRITE3args(XDR *xdrs, nfslog_WRITE3args *objp) { if (!xdr_nfs_fh3(xdrs, &objp->file)) return (FALSE); if (!xdr_offset3(xdrs, &objp->offset)) return (FALSE); if (!xdr_count3(xdrs, &objp->count)) return (FALSE); if (!xdr_stable_how(xdrs, &objp->stable)) return (FALSE); return (TRUE); } static bool_t xdr_nfslog_WRITE3resok(XDR *xdrs, nfslog_WRITE3resok *objp) { if (!xdr_size3(xdrs, &objp->filesize)) return (FALSE); if (!xdr_count3(xdrs, &objp->count)) return (FALSE); if (!xdr_stable_how(xdrs, &objp->committed)) return (FALSE); return (TRUE); } bool_t xdr_nfslog_WRITE3res(XDR *xdrs, nfslog_WRITE3res *objp) { if (!xdr_nfsstat3(xdrs, &objp->status)) return (FALSE); switch (objp->status) { case NFS3_OK: if (!xdr_nfslog_WRITE3resok(xdrs, &objp->nfslog_WRITE3res_u.ok)) return (FALSE); break; } return (TRUE); } bool_t xdr_nfslog_MKDIR3args(XDR *xdrs, nfslog_MKDIR3args *objp) { if (!xdr_nfslog_diropargs3(xdrs, &objp->where)) return (FALSE); return (TRUE); } bool_t xdr_nfslog_MKDIR3res(XDR *xdrs, nfslog_MKDIR3res *objp) { if (!xdr_nfsstat3(xdrs, &objp->status)) return (FALSE); switch (objp->status) { case NFS3_OK: if (!xdr_post_op_fh3(xdrs, &objp->nfslog_MKDIR3res_u.obj)) return (FALSE); break; } return (TRUE); } bool_t xdr_nfslog_SYMLINK3args(XDR *xdrs, nfslog_SYMLINK3args *objp) { if (!xdr_nfslog_diropargs3(xdrs, &objp->where)) return (FALSE); if (!xdr_string(xdrs, &objp->symlink_data, ~0)) return (FALSE); return (TRUE); } bool_t xdr_nfslog_SYMLINK3res(XDR *xdrs, nfslog_SYMLINK3res *objp) { if (!xdr_nfsstat3(xdrs, &objp->status)) return (FALSE); switch (objp->status) { case NFS3_OK: if (!xdr_post_op_fh3(xdrs, &objp->nfslog_SYMLINK3res_u.obj)) return (FALSE); break; } return (TRUE); } bool_t xdr_nfslog_MKNOD3args(XDR *xdrs, nfslog_MKNOD3args *objp) { if (!xdr_nfslog_diropargs3(xdrs, &objp->where)) return (FALSE); if (!xdr_ftype3(xdrs, &objp->type)) return (FALSE); return (TRUE); } bool_t xdr_nfslog_MKNOD3res(XDR *xdrs, nfslog_MKNOD3res *objp) { if (!xdr_nfsstat3(xdrs, &objp->status)) return (FALSE); switch (objp->status) { case NFS3_OK: if (!xdr_post_op_fh3(xdrs, &objp->nfslog_MKNOD3res_u.obj)) return (FALSE); break; } return (TRUE); } bool_t xdr_nfslog_REMOVE3args(XDR *xdrs, nfslog_REMOVE3args *objp) { if (!xdr_nfslog_diropargs3(xdrs, &objp->object)) return (FALSE); return (TRUE); } bool_t xdr_nfslog_RMDIR3args(XDR *xdrs, nfslog_RMDIR3args *objp) { if (!xdr_nfslog_diropargs3(xdrs, &objp->object)) return (FALSE); return (TRUE); } bool_t xdr_nfslog_RENAME3args(XDR *xdrs, nfslog_RENAME3args *objp) { if (!xdr_nfslog_diropargs3(xdrs, &objp->from)) return (FALSE); if (!xdr_nfslog_diropargs3(xdrs, &objp->to)) return (FALSE); return (TRUE); } bool_t xdr_nfslog_LINK3args(XDR *xdrs, nfslog_LINK3args *objp) { if (!xdr_nfs_fh3(xdrs, &objp->file)) return (FALSE); if (!xdr_nfslog_diropargs3(xdrs, &objp->link)) return (FALSE); return (TRUE); } bool_t xdr_nfslog_READDIRPLUS3args(XDR *xdrs, nfslog_READDIRPLUS3args *objp) { if (!xdr_nfs_fh3(xdrs, &objp->dir)) return (FALSE); if (!xdr_count3(xdrs, &objp->dircount)) return (FALSE); if (!xdr_count3(xdrs, &objp->maxcount)) return (FALSE); return (TRUE); } static bool_t xdr_nfslog_entryplus3(XDR *xdrs, nfslog_entryplus3 *objp) { if (!xdr_post_op_fh3(xdrs, &objp->name_handle)) return (FALSE); if (!xdr_string(xdrs, &objp->name, ~0)) return (FALSE); if (!xdr_pointer(xdrs, (char **)&objp->nextentry, sizeof (nfslog_entryplus3), (xdrproc_t)xdr_nfslog_entryplus3)) return (FALSE); return (TRUE); } static bool_t xdr_nfslog_dirlistplus3(XDR *xdrs, nfslog_dirlistplus3 *objp) { if (!xdr_pointer(xdrs, (char **)&objp->entries, sizeof (nfslog_entryplus3), (xdrproc_t)xdr_nfslog_entryplus3)) return (FALSE); if (!xdr_bool(xdrs, &objp->eof)) return (FALSE); return (TRUE); } static bool_t xdr_nfslog_READDIRPLUS3resok(XDR *xdrs, nfslog_READDIRPLUS3resok *objp) { if (!xdr_nfslog_dirlistplus3(xdrs, &objp->reply)) return (FALSE); return (TRUE); } bool_t xdr_nfslog_READDIRPLUS3res(XDR *xdrs, nfslog_READDIRPLUS3res *objp) { if (!xdr_nfsstat3(xdrs, &objp->status)) return (FALSE); switch (objp->status) { case NFS3_OK: if (!xdr_nfslog_READDIRPLUS3resok( xdrs, &objp->nfslog_READDIRPLUS3res_u.ok)) return (FALSE); break; } return (TRUE); } bool_t xdr_nfslog_COMMIT3args(XDR *xdrs, nfslog_COMMIT3args *objp) { if (!xdr_nfs_fh3(xdrs, &objp->file)) return (FALSE); if (!xdr_offset3(xdrs, &objp->offset)) return (FALSE); if (!xdr_count3(xdrs, &objp->count)) return (FALSE); return (TRUE); } /* * 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 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * nfs log - read buffer file and print structs in user-readable form */ #define _REENTRANT #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fhtab.h" #include "nfslogd.h" static char empty_name[4] = "-"; static char ftype3_names[NF3FIFO + 1][20] = { "\"none\"", "\"file\"", "\"dir\"", "\"blk device\"", "\"chr device\"", "\"link\"", "\"socket\"", "\"fifo\"" }; #define NFSL_FTYPE3(ftype) \ ((((ftype) >= 0) && ((ftype) <= NF3FIFO)) ? \ ftype3_names[ftype] : empty_name) static char createmode3_names[EXCLUSIVE + 1][20] = { "\"unchecked", "\"guarded\"", "\"exclusive\"" }; #define NFSL_CREATEMODE3(createmode) \ ((((createmode) >= 0) && ((createmode) <= EXCLUSIVE)) ? \ createmode3_names[createmode] : empty_name) static char auth_flavor_name[RPCSEC_GSS + 1][20] = { "\"auth_null\"", "\"auth_unix\"", "\"auth_short\"", "\"auth_des\"", "\"auth_kerb\"", "\"none\"", "\"rpcsec_gss\"" }; #define NFSL_AUTH_FLAVOR_PRINT(auth_flavor) \ (((auth_flavor) <= RPCSEC_GSS) ? \ auth_flavor_name[auth_flavor] : empty_name) #define NFSL_ERR_CNT 31 /* Actual err numbers */ /* * Two arrays - one short ints containing err codes, the other the strings * (merged codes for both v2 and v3 */ static char nfsl_status_name[NFSL_ERR_CNT][30] = { "\"ok\"", "\"perm\"", "\"noent\"", "\"io\"", "\"nxio\"", "\"access\"", "\"exist\"", "\"xdev\"", "\"nodev\"", "\"notdir\"", "\"isdir\"", "\"inval\"", "\"fbig\"", "\"nospc\"", "\"rofs\"", "\"mlink\"", "\"notsupp\"", "\"nametoolong\"", "\"notempty\"", "\"dquot\"", "\"stale\"", "\"remote\"", "\"wflush\"", "\"badhandle\"", "\"not_sync\"", "\"bad_cookie\"", "\"notsupp\"", "\"toosmall\"", "\"serverfault\"", "\"badtype\"", "\"jukebox\"", }; static short nfsl_status[NFSL_ERR_CNT] = { 0, 1, 2, 5, 6, 13, 17, 18, 19, 20, 21, 22, 27, 28, 30, 31, 45, 63, 66, 69, 70, 71, 99, 10001, 10002, 10003, 10004, 10005, 10006, 10007, 10008 }; /* list of open elf files */ static struct nfsl_log_file *elf_file_list = NULL; /* Imported functions */ extern void bcopy(const void *s1, void *s2, size_t n); /* Static functions */ static void nfsl_log_file_free(struct nfsl_log_file *elfrec); static void nfsl_log_file_add(struct nfsl_log_file *elfrec, struct nfsl_log_file **elf_listp); static struct nfsl_log_file *nfsl_log_file_find(struct nfsl_log_file *elfrec, struct nfsl_log_file *elf_list); static struct nfsl_log_file *nfsl_log_file_del(struct nfsl_log_file *elfrec, struct nfsl_log_file **elf_listp); static char *nfsl_get_time(time_t tt); static char *nfsl_get_date(time_t tt); static char *nfsl_get_date_nq(time_t tt); static int nfsl_write_elfbuf(struct nfsl_log_file *elfrec); static void nfsl_ipaddr_print(struct nfsl_log_file *, struct netbuf *); static void nfsl_elf_record_header_print(struct nfsl_log_file *, nfslog_record_header *, char *, char *, struct nfsl_proc_disp *, char *); static void nfsl_elf_buffer_header_print(struct nfsl_log_file *, nfslog_buffer_header *); static struct nfsl_proc_disp *nfsl_find_elf_dispatch( nfslog_request_record *, char **); static void nfsl_elf_rpc_print(struct nfsl_log_file *, nfslog_request_record *, struct nfsl_proc_disp *, char *, char *, char *); static void nfslog_size3_print(struct nfsl_log_file *, set_size3 *); static void nfslog_null_args(struct nfsl_log_file *, caddr_t *); static void nfslog_null_res(struct nfsl_log_file *, caddr_t *); /* * NFS VERSION 2 */ /* Functions for elf print of the arguments */ static void nfslog_fhandle_print(struct nfsl_log_file *, fhandle_t *); static void nfslog_diropargs_print(struct nfsl_log_file *, nfslog_diropargs *); static void nfslog_setattrargs_print(struct nfsl_log_file *, nfslog_setattrargs *); static void nfslog_sattr_print(struct nfsl_log_file *, nfslog_sattr *); static void nfslog_nfsreadargs_print(struct nfsl_log_file *, nfslog_nfsreadargs *); static void nfslog_writeargs_print(struct nfsl_log_file *, nfslog_writeargs *); static void nfslog_writeresult_print(struct nfsl_log_file *, nfslog_writeresult *, bool_t); static void nfslog_creatargs_print(struct nfsl_log_file *, nfslog_createargs *); static void nfslog_rddirargs_print(struct nfsl_log_file *, nfslog_rddirargs *); static void nfslog_linkargs_print(struct nfsl_log_file *, nfslog_linkargs *); static void nfslog_rnmargs_print(struct nfsl_log_file *, nfslog_rnmargs *); static void nfslog_symlinkargs_print(struct nfsl_log_file *, nfslog_symlinkargs *); static void nfslog_sharefsargs_print(struct nfsl_log_file *, nfslog_sharefsargs *); static void nfslog_getfhargs_print(struct nfsl_log_file *, nfslog_getfhargs *); /* Functions for elf print of the response */ static void nfslog_nfsstat_print(struct nfsl_log_file *, enum nfsstat *, bool_t); static void nfslog_diropres_print(struct nfsl_log_file *, nfslog_diropres *, bool_t); static void nfslog_rdlnres_print(struct nfsl_log_file *, nfslog_rdlnres *, bool_t); static void nfslog_rdresult_print(struct nfsl_log_file *, nfslog_rdresult *, bool_t); static void nfslog_rddirres_print(struct nfsl_log_file *, nfslog_rddirres *, bool_t); /* * NFS VERSION 3 */ /* Functions for elf print of the arguments */ static void nfslog_fh3_print(struct nfsl_log_file *, nfs_fh3 *); static void nfslog_diropargs3_print(struct nfsl_log_file *, nfslog_diropargs3 *); static void nfslog_SETATTR3args_print(struct nfsl_log_file *, nfslog_SETATTR3args *); static void nfslog_READ3args_print(struct nfsl_log_file *, nfslog_READ3args *); static void nfslog_WRITE3args_print(struct nfsl_log_file *, nfslog_WRITE3args *); static void nfslog_CREATE3args_print(struct nfsl_log_file *, nfslog_CREATE3args *); static void nfslog_MKDIR3args_print(struct nfsl_log_file *, nfslog_MKDIR3args *); static void nfslog_SYMLINK3args_print(struct nfsl_log_file *, nfslog_SYMLINK3args *); static void nfslog_MKNOD3args_print(struct nfsl_log_file *, nfslog_MKNOD3args *); static void nfslog_REMOVE3args_print(struct nfsl_log_file *, nfslog_REMOVE3args *); static void nfslog_RMDIR3args_print(struct nfsl_log_file *, nfslog_RMDIR3args *); static void nfslog_RENAME3args_print(struct nfsl_log_file *, nfslog_RENAME3args *); static void nfslog_LINK3args_print(struct nfsl_log_file *, nfslog_LINK3args *); static void nfslog_COMMIT3args_print(struct nfsl_log_file *, nfslog_COMMIT3args *); static void nfslog_READDIRPLUS3args_print(struct nfsl_log_file *, nfslog_READDIRPLUS3args *); /* Functions for elf print of the response */ static void nfslog_nfsstat3_print(struct nfsl_log_file *, nfsstat3 *, bool_t); static void nfslog_LOOKUP3res_print(struct nfsl_log_file *, nfslog_LOOKUP3res *, bool_t); static void nfslog_READLINK3res_print(struct nfsl_log_file *, nfslog_READLINK3res *, bool_t); static void nfslog_READ3res_print(struct nfsl_log_file *, nfslog_READ3res *, bool_t); static void nfslog_WRITE3res_print(struct nfsl_log_file *, nfslog_WRITE3res *, bool_t); static void nfslog_CREATE3res_print(struct nfsl_log_file *, nfslog_CREATE3res *, bool_t); static void nfslog_MKDIR3res_print(struct nfsl_log_file *, nfslog_MKDIR3res *, bool_t); static void nfslog_SYMLINK3res_print(struct nfsl_log_file *, nfslog_SYMLINK3res *, bool_t); static void nfslog_MKNOD3res_print(struct nfsl_log_file *, nfslog_MKNOD3res *, bool_t); static void nfslog_READDIRPLUS3res_print(struct nfsl_log_file *, nfslog_READDIRPLUS3res *, bool_t); extern int debug; static bool_t nfsl_print_fh = FALSE; /* print file handles? */ #define DFLT_BUFFERSIZE 8192 #define DFLT_OVFSIZE 3072 /* Maximum logged or buffered size */ static char hostname[MAXHOSTNAMELEN]; /* name of host */ /* * Define the actions taken per prog/vers/proc: * * In some cases, the nl types are the same as the nfs types and a simple * bcopy should suffice. Rather that define tens of identical procedures, * simply define these to bcopy. Similarly this takes care of different * procs that use same parameter struct. */ static struct nfsl_proc_disp nfsl_elf_proc_v2[] = { /* * NFS VERSION 2 */ /* RFS_NULL = 0 */ {nfslog_null_args, nfslog_null_res, "\"null\""}, /* RFS_GETATTR = 1 */ {nfslog_fhandle_print, nfslog_nfsstat_print, "\"getattr\""}, /* RFS_SETATTR = 2 */ {nfslog_setattrargs_print, nfslog_nfsstat_print, "\"setattr\""}, /* RFS_ROOT = 3 *** NO LONGER SUPPORTED *** */ {nfslog_null_args, nfslog_null_res, "\"root\""}, /* RFS_LOOKUP = 4 */ {nfslog_diropargs_print, nfslog_diropres_print, "\"lookup\""}, /* RFS_READLINK = 5 */ {nfslog_fhandle_print, nfslog_rdlnres_print, "\"readlink\""}, /* RFS_READ = 6 */ {nfslog_nfsreadargs_print, nfslog_rdresult_print, "\"read\""}, /* RFS_WRITECACHE = 7 *** NO LONGER SUPPORTED *** */ {nfslog_null_args, nfslog_null_res, "\"writecache\""}, /* RFS_WRITE = 8 */ {nfslog_writeargs_print, nfslog_writeresult_print, "\"write\""}, /* RFS_CREATE = 9 */ {nfslog_creatargs_print, nfslog_diropres_print, "\"create\""}, /* RFS_REMOVE = 10 */ {nfslog_diropargs_print, nfslog_nfsstat_print, "\"remove\""}, /* RFS_RENAME = 11 */ {nfslog_rnmargs_print, nfslog_nfsstat_print, "\"rename\""}, /* RFS_LINK = 12 */ {nfslog_linkargs_print, nfslog_nfsstat_print, "\"link\""}, /* RFS_SYMLINK = 13 */ {nfslog_symlinkargs_print, nfslog_nfsstat_print, "\"symlink\""}, /* RFS_MKDIR = 14 */ {nfslog_creatargs_print, nfslog_diropres_print, "\"mkdir\""}, /* RFS_RMDIR = 15 */ {nfslog_diropargs_print, nfslog_nfsstat_print, "\"rmdir\""}, /* RFS_READDIR = 16 */ {nfslog_rddirargs_print, nfslog_rddirres_print, "\"readdir\""}, /* RFS_STATFS = 17 */ {nfslog_fhandle_print, nfslog_nfsstat_print, "\"statfs\""}, }; /* * NFS VERSION 3 */ static struct nfsl_proc_disp nfsl_elf_proc_v3[] = { /* NFSPROC3_NULL = 0 */ {nfslog_null_args, nfslog_null_res, "\"null\""}, /* NFSPROC3_GETATTR = 1 */ {nfslog_fh3_print, nfslog_nfsstat3_print, "\"getattr\""}, /* NFSPROC3_SETATTR = 2 */ {nfslog_SETATTR3args_print, nfslog_nfsstat3_print, "\"setattr\""}, /* NFSPROC3_LOOKUP = 3 */ {nfslog_diropargs3_print, nfslog_LOOKUP3res_print, "\"lookup\""}, /* NFSPROC3_ACCESS = 4 */ {nfslog_fh3_print, nfslog_nfsstat3_print, "\"access\""}, /* NFSPROC3_READLINK = 5 */ {nfslog_fh3_print, nfslog_READLINK3res_print, "\"readlink\""}, /* NFSPROC3_READ = 6 */ {nfslog_READ3args_print, nfslog_READ3res_print, "\"read\""}, /* NFSPROC3_WRITE = 7 */ {nfslog_WRITE3args_print, nfslog_WRITE3res_print, "\"write\""}, /* NFSPROC3_CREATE = 8 */ {nfslog_CREATE3args_print, nfslog_CREATE3res_print, "\"create\""}, /* NFSPROC3_MKDIR = 9 */ {nfslog_MKDIR3args_print, nfslog_MKDIR3res_print, "\"mkdir\""}, /* NFSPROC3_SYMLINK = 10 */ {nfslog_SYMLINK3args_print, nfslog_SYMLINK3res_print, "\"symlink\""}, /* NFSPROC3_MKNOD = 11 */ {nfslog_MKNOD3args_print, nfslog_MKNOD3res_print, "\"mknod\""}, /* NFSPROC3_REMOVE = 12 */ {nfslog_REMOVE3args_print, nfslog_nfsstat3_print, "\"remove\""}, /* NFSPROC3_RMDIR = 13 */ {nfslog_RMDIR3args_print, nfslog_nfsstat3_print, "\"rmdir\""}, /* NFSPROC3_RENAME = 14 */ {nfslog_RENAME3args_print, nfslog_nfsstat3_print, "\"rename\""}, /* NFSPROC3_LINK = 15 */ {nfslog_LINK3args_print, nfslog_nfsstat3_print, "\"link\""}, /* NFSPROC3_READDIR = 16 */ {nfslog_fh3_print, nfslog_nfsstat3_print, "\"readdir\""}, /* NFSPROC3_READDIRPLUS = 17 */ {nfslog_READDIRPLUS3args_print, nfslog_READDIRPLUS3res_print, "\"readdirplus\""}, /* NFSPROC3_FSSTAT = 18 */ {nfslog_fh3_print, nfslog_nfsstat3_print, "\"fsstat\""}, /* NFSPROC3_FSINFO = 19 */ {nfslog_fh3_print, nfslog_nfsstat3_print, "\"fsinfo\""}, /* NFSPROC3_PATHCONF = 20 */ {nfslog_fh3_print, nfslog_nfsstat3_print, "\"pathconf\""}, /* NFSPROC3_COMMIT = 21 */ {nfslog_COMMIT3args_print, nfslog_nfsstat3_print, "\"commit\""}, }; /* * NFSLOG VERSION 1 */ static struct nfsl_proc_disp nfsl_log_elf_proc_v1[] = { /* NFSLOG_NULL = 0 */ {nfslog_null_args, nfslog_null_res, "\"null\""}, /* NFSLOG_SHARE = 1 */ {nfslog_sharefsargs_print, nfslog_nfsstat_print, "\"log_share\""}, /* NFSLOG_UNSHARE = 2 */ {nfslog_sharefsargs_print, nfslog_nfsstat_print, "\"log_unshare\""}, /* NFSLOG_LOOKUP = 3 */ {nfslog_diropargs3_print, nfslog_LOOKUP3res_print, "\"lookup\""}, /* NFSLOG_GETFH = 4 */ {nfslog_getfhargs_print, nfslog_nfsstat_print, "\"log_getfh\""}, }; static struct nfsl_vers_disp nfsl_elf_vers_disptable[] = { {sizeof (nfsl_elf_proc_v2) / sizeof (nfsl_elf_proc_v2[0]), nfsl_elf_proc_v2}, {sizeof (nfsl_elf_proc_v3) / sizeof (nfsl_elf_proc_v3[0]), nfsl_elf_proc_v3}, }; static struct nfsl_vers_disp nfsl_log_elf_vers_disptable[] = { {sizeof (nfsl_log_elf_proc_v1) / sizeof (nfsl_log_elf_proc_v1[0]), nfsl_log_elf_proc_v1}, }; static struct nfsl_prog_disp nfsl_elf_dispatch_table[] = { {NFS_PROGRAM, NFS_VERSMIN, sizeof (nfsl_elf_vers_disptable) / sizeof (nfsl_elf_vers_disptable[0]), nfsl_elf_vers_disptable, "nfs"}, {NFSLOG_PROGRAM, NFSLOG_VERSMIN, sizeof (nfsl_log_elf_vers_disptable) / sizeof (nfsl_log_elf_vers_disptable[0]), nfsl_log_elf_vers_disptable, "nfslog"}, }; static int nfsl_elf_dispatch_table_arglen = sizeof (nfsl_elf_dispatch_table) / sizeof (nfsl_elf_dispatch_table[0]); static char * nfslog_get_status(short status) { int low, mid, high; short errstat; /* Usually status is 0... */ if (status == 0) return (nfsl_status_name[0]); low = 0; high = NFSL_ERR_CNT; mid = NFSL_ERR_CNT / 2; /* binary search for status string */ while (((errstat = nfsl_status[mid]) != status) && (low < mid) && (mid < high)) { if (errstat > status) { /* search bottom half */ high = mid; } else { /* search upper half */ low = mid; } mid = low + ((high - low) / 2); } if (errstat == status) { /* found it */ return (nfsl_status_name[mid]); } return (NULL); } /* nfsl_get_time - return string with time formatted as hh:mm:ss */ static char * nfsl_get_time(time_t tt) { static char timestr[20]; static time_t lasttime; struct tm tmst; if (tt == lasttime) return (timestr); if (localtime_r(&tt, &tmst) == NULL) { return (empty_name); } (void) sprintf(timestr, "%02d:%02d:%02d", tmst.tm_hour, tmst.tm_min, tmst.tm_sec); lasttime = tt; return (timestr); } /* nfsl_get_date - return date string formatted as "yyyy-mm-dd hh:mm:ss" */ static char * nfsl_get_date(time_t tt) { static char timestr[30]; static time_t lasttime; struct tm tmst; if (tt == lasttime) return (timestr); if (localtime_r(&tt, &tmst) == NULL) { return (empty_name); } (void) sprintf(timestr, "\"%04d-%02d-%02d %02d:%02d:%02d\"", tmst.tm_year + 1900, tmst.tm_mon + 1, tmst.tm_mday, tmst.tm_hour, tmst.tm_min, tmst.tm_sec); lasttime = tt; return (timestr); } /* * nfsl_get_date_nq - return date string formatted as yyyy-mm-dd hh:mm:ss * (no quotes) */ static char * nfsl_get_date_nq(time_t tt) { static char timestr[30]; static time_t lasttime; struct tm tmst; if (tt == lasttime) return (timestr); if (localtime_r(&tt, &tmst) == NULL) { return (empty_name); } (void) sprintf(timestr, "%04d-%02d-%02d %02d:%02d:%02d", tmst.tm_year + 1900, tmst.tm_mon + 1, tmst.tm_mday, tmst.tm_hour, tmst.tm_min, tmst.tm_sec); return (timestr); } /* write log buffer out to file */ static int nfsl_write_elfbuf(struct nfsl_log_file *elfrec) { int rc; char *elfbuf = elfrec->buf; int elfbufoffset = elfrec->bufoffset; if (debug > 1) (void) printf("nfsl_write_elfbuf: bufoffset %d\n", elfbufoffset); if (elfbufoffset <= 0) return (0); elfbuf[elfbufoffset] = '\0'; if ((rc = fputs(elfbuf, elfrec->fp)) < 0) { syslog(LOG_ERR, gettext("Write to %s failed: %s\n"), elfrec->path, strerror(errno)); return (-1); } if (rc != elfbufoffset) { syslog(LOG_ERR, gettext("Write %d bytes to %s returned %d\n"), elfbufoffset, elfrec->path, rc); return (-1); } elfrec->bufoffset = 0; return (0); } /*ARGSUSED*/ static void nfslog_null_args(struct nfsl_log_file *elfrec, caddr_t *nfsl_args) { } /*ARGSUSED*/ static void nfslog_null_res(struct nfsl_log_file *elfrec, caddr_t *nfsl_res) { } static void nfslog_fh3_print(struct nfsl_log_file *elfrec, nfs_fh3 *fh3) { if (!nfsl_print_fh) return; if (fh3->fh3_length == sizeof (fhandle_t)) { nfslog_fhandle_print(elfrec, (fhandle_t *)&fh3->fh3_u.data); } else { nfslog_opaque_print_buf(fh3->fh3_u.data, fh3->fh3_length, elfrec->buf, &elfrec->bufoffset, DFLT_BUFFERSIZE + DFLT_OVFSIZE); } } /* * NFS VERSION 2 */ /* Functions that elf print the arguments */ static void nfslog_fhandle_print(struct nfsl_log_file *elfrec, fhandle_t *args) { if (!nfsl_print_fh) return; nfslog_opaque_print_buf(args, sizeof (*args), elfrec->buf, &elfrec->bufoffset, DFLT_BUFFERSIZE + DFLT_OVFSIZE); } static void nfslog_diropargs_print(struct nfsl_log_file *elfrec, nfslog_diropargs *args) { char *elfbuf = elfrec->buf; int elfbufoffset = elfrec->bufoffset; if (nfsl_print_fh) { nfslog_fhandle_print(elfrec, &args->da_fhandle); elfbufoffset = elfrec->bufoffset; if (args->da_name != NULL) { elfbufoffset += sprintf(&elfbuf[elfbufoffset], " \"%s\"", args->da_name); } else { elfbufoffset += sprintf(&elfbuf[elfbufoffset], " %s", empty_name); } } elfrec->bufoffset = elfbufoffset; } static void nfslog_sattr_print(struct nfsl_log_file *elfrec, nfslog_sattr *args) { char *elfbuf = elfrec->buf; int elfbufoffset = elfrec->bufoffset; /* BEGIN CSTYLED */ if (args->sa_mode != (uint32_t)-1) { elfbufoffset += sprintf(&elfbuf[elfbufoffset], " \"mode=0%o\"", args->sa_mode); } if (args->sa_uid != (uint32_t)-1) { elfbufoffset += sprintf(&elfbuf[elfbufoffset], " \"uid=0x%x\"", args->sa_uid); } if (args->sa_gid != (uint32_t)-1) { elfbufoffset += sprintf(&elfbuf[elfbufoffset], " \"gid=0x%x\"", args->sa_gid); } if (args->sa_size != (uint32_t)-1) { elfbufoffset += sprintf(&elfbuf[elfbufoffset], " \"size=0x%x\"", args->sa_size); } if (args->sa_atime.tv_sec != (uint32_t)-1) { elfbufoffset += sprintf(&elfbuf[elfbufoffset], " \"atime=%s\"", nfsl_get_date_nq((time_t)args->sa_atime.tv_sec)); } if (args->sa_mtime.tv_sec != (uint32_t)-1) { elfbufoffset += sprintf(&elfbuf[elfbufoffset], " \"mtime=%s\"", nfsl_get_date_nq((time_t)args->sa_mtime.tv_sec)); } /* END CSTYLED */ elfrec->bufoffset = elfbufoffset; } static void nfslog_setattrargs_print(struct nfsl_log_file *elfrec, nfslog_setattrargs *args) { nfslog_fhandle_print(elfrec, &args->saa_fh); nfslog_sattr_print(elfrec, &args->saa_sa); } static void nfslog_nfsreadargs_print(struct nfsl_log_file *elfrec, nfslog_nfsreadargs *args) { char *elfbuf = elfrec->buf; int elfbufoffset; nfslog_fhandle_print(elfrec, &args->ra_fhandle); elfbufoffset = elfrec->bufoffset; elfbufoffset += sprintf(&elfbuf[elfbufoffset], " 0x%x", args->ra_offset); elfbufoffset += sprintf(&elfbuf[elfbufoffset], " 0x%x", args->ra_count); elfbufoffset += sprintf(&elfbuf[elfbufoffset], " 0x%x", args->ra_totcount); elfrec->bufoffset = elfbufoffset; } static void nfslog_writeargs_print(struct nfsl_log_file *elfrec, nfslog_writeargs *args) { char *elfbuf = elfrec->buf; int elfbufoffset = elfrec->bufoffset; nfslog_fhandle_print(elfrec, &args->waargs_fhandle); elfbufoffset = elfrec->bufoffset; elfbufoffset += sprintf(&elfbuf[elfbufoffset], " 0x%x", args->waargs_begoff); elfbufoffset += sprintf(&elfbuf[elfbufoffset], " 0x%x", args->waargs_offset); elfbufoffset += sprintf(&elfbuf[elfbufoffset], " 0x%x", args->waargs_totcount); elfrec->bufoffset += sprintf(&elfrec->buf[elfrec->bufoffset], " 0x%x", args->waargs_count); } static void nfslog_writeresult_print(struct nfsl_log_file *elfrec, nfslog_writeresult *res, bool_t print_status) { char *elfbuf = elfrec->buf; int elfbufoffset = elfrec->bufoffset; if (print_status) { nfslog_nfsstat_print(elfrec, &res->wr_status, print_status); } else if (res->wr_status == NFS_OK) { elfbufoffset += sprintf(&elfbuf[elfbufoffset], " 0x%x", res->nfslog_writeresult_u.wr_size); elfrec->bufoffset = elfbufoffset; } } static void nfslog_creatargs_print(struct nfsl_log_file *elfrec, nfslog_createargs *args) { nfslog_diropargs_print(elfrec, &args->ca_da); nfslog_sattr_print(elfrec, &args->ca_sa); } static void nfslog_rddirargs_print(struct nfsl_log_file *elfrec, nfslog_rddirargs *args) { char *elfbuf = elfrec->buf; int elfbufoffset; nfslog_fhandle_print(elfrec, &args->rda_fh); elfbufoffset = elfrec->bufoffset; elfbufoffset += sprintf(&elfbuf[elfbufoffset], " 0x%x", args->rda_offset); elfbufoffset += sprintf(&elfbuf[elfbufoffset], " 0x%x", args->rda_count); elfrec->bufoffset = elfbufoffset; } static void nfslog_rnmargs_print(struct nfsl_log_file *elfrec, nfslog_rnmargs *args) { nfslog_diropargs_print(elfrec, &args->rna_from); nfslog_diropargs_print(elfrec, &args->rna_to); } static void nfslog_linkargs_print(struct nfsl_log_file *elfrec, nfslog_linkargs *args) { nfslog_fhandle_print(elfrec, &args->la_from); nfslog_diropargs_print(elfrec, &args->la_to); } static void nfslog_symlinkargs_print(struct nfsl_log_file *elfrec, nfslog_symlinkargs *args) { char *elfbuf = elfrec->buf; int elfbufoffset; nfslog_diropargs_print(elfrec, &args->sla_from); elfbufoffset = elfrec->bufoffset; elfbufoffset += sprintf(&elfbuf[elfbufoffset], " \"%s\"", args->sla_tnm); elfrec->bufoffset = elfbufoffset; nfslog_sattr_print(elfrec, &args->sla_sa); } /* * SHARE/UNSHARE fs log args copy */ static void nfslog_sharefsargs_print(struct nfsl_log_file *elfrec, nfslog_sharefsargs *args) { unsigned int elfbufoffset; char *elfbuf = elfrec->buf; nfslog_fhandle_print(elfrec, &args->sh_fh_buf); elfbufoffset = elfrec->bufoffset; elfbufoffset += sprintf(&elfbuf[elfbufoffset], " 0x%x", args->sh_flags); elfbufoffset += sprintf(&elfbuf[elfbufoffset], " 0x%x", args->sh_anon); if (nfsl_print_fh) { if (args->sh_path != NULL) { elfbufoffset += sprintf(&elfbuf[elfbufoffset], " \"%s\"", args->sh_path); } else { elfbufoffset += sprintf(&elfbuf[elfbufoffset], " %s", empty_name); } } elfrec->bufoffset = elfbufoffset; } static void nfslog_getfhargs_print(struct nfsl_log_file *elfrec, nfslog_getfhargs *args) { unsigned int elfbufoffset; char *elfbuf = elfrec->buf; nfslog_fhandle_print(elfrec, &args->gfh_fh_buf); elfbufoffset = elfrec->bufoffset; if (nfsl_print_fh) { if (args->gfh_path != NULL) { elfbufoffset += sprintf(&elfbuf[elfbufoffset], " \"%s\"", args->gfh_path); } else { elfbufoffset += sprintf(&elfbuf[elfbufoffset], " %s", empty_name); } } elfrec->bufoffset = elfbufoffset; } static void nfslog_nfsstat_print(struct nfsl_log_file *elfrec, enum nfsstat *res, bool_t print_status) { if (print_status) { char *statp = nfslog_get_status((short)(*res)); if (statp != NULL) elfrec->bufoffset += sprintf(&elfrec->buf[elfrec->bufoffset], " %s", statp); else elfrec->bufoffset += sprintf(&elfrec->buf[elfrec->bufoffset], " %5d", *res); } } static void nfslog_diropres_print(struct nfsl_log_file *elfrec, nfslog_diropres *res, bool_t print_status) { if (print_status) { nfslog_nfsstat_print(elfrec, &res->dr_status, print_status); } else if (res->dr_status == NFS_OK) { nfslog_fhandle_print(elfrec, &res->nfslog_diropres_u.dr_ok.drok_fhandle); } } static void nfslog_rdlnres_print(struct nfsl_log_file *elfrec, nfslog_rdlnres *res, bool_t print_status) { if (print_status) { nfslog_nfsstat_print(elfrec, &res->rl_status, print_status); } else if (res->rl_status == NFS_OK) { elfrec->bufoffset += sprintf(&elfrec->buf[elfrec->bufoffset], " \"%s\"", res->nfslog_rdlnres_u.rl_ok); } } static void nfslog_rdresult_print(struct nfsl_log_file *elfrec, nfslog_rdresult *res, bool_t print_status) { if (print_status) { nfslog_nfsstat_print(elfrec, &res->r_status, print_status); } else if (res->r_status == NFS_OK) { elfrec->bufoffset += sprintf(&elfrec->buf[elfrec->bufoffset], " 0x%x", res->nfslog_rdresult_u.r_ok.filesize); elfrec->bufoffset += sprintf(&elfrec->buf[elfrec->bufoffset], " 0x%x", res->nfslog_rdresult_u.r_ok.rrok_count); } } static void nfslog_rddirres_print(struct nfsl_log_file *elfrec, nfslog_rddirres *res, bool_t print_status) { if (print_status) { nfslog_nfsstat_print(elfrec, &res->rd_status, print_status); } else if (res->rd_status == NFS_OK) { char *elfbuf = elfrec->buf; int elfbufoffset = elfrec->bufoffset; elfbufoffset += sprintf(&elfbuf[elfbufoffset], " 0x%x", res->nfslog_rddirres_u.rd_ok.rdok_offset); elfbufoffset += sprintf(&elfbuf[elfbufoffset], " 0x%x", res->nfslog_rddirres_u.rd_ok.rdok_size); elfbufoffset += sprintf(&elfbuf[elfbufoffset], " 0x%x", res->nfslog_rddirres_u.rd_ok.rdok_eof); elfrec->bufoffset = elfbufoffset; } } /* * NFS VERSION 3 */ static void nfslog_diropargs3_print(struct nfsl_log_file *elfrec, nfslog_diropargs3 *args) { if (nfsl_print_fh) { nfslog_fh3_print(elfrec, &args->dir); elfrec->bufoffset += sprintf(&elfrec->buf[elfrec->bufoffset], " \"%s\"", args->name); } } static void nfslog_size3_print(struct nfsl_log_file *elfrec, set_size3 *args) { char *elfbuf = elfrec->buf; int elfbufoffset = elfrec->bufoffset; if (args->set_it) { elfbufoffset += sprintf(&elfbuf[elfbufoffset], /* CSTYLED */ " \"size=0x%llx\"", args->size); } elfrec->bufoffset = elfbufoffset; } static void nfslog_SETATTR3args_print(struct nfsl_log_file *elfrec, nfslog_SETATTR3args *args) { nfslog_fh3_print(elfrec, &args->object); nfslog_size3_print(elfrec, &args->size); } static void nfslog_READ3args_print(struct nfsl_log_file *elfrec, nfslog_READ3args *args) { nfslog_fh3_print(elfrec, &args->file); elfrec->bufoffset += sprintf(&elfrec->buf[elfrec->bufoffset], " 0x%llx", args->offset); elfrec->bufoffset += sprintf(&elfrec->buf[elfrec->bufoffset], " 0x%x", args->count); } static void nfslog_WRITE3args_print(struct nfsl_log_file *elfrec, nfslog_WRITE3args *args) { char *elfbuf = elfrec->buf; int elfbufoffset; nfslog_fh3_print(elfrec, &args->file); elfbufoffset = elfrec->bufoffset; elfbufoffset += sprintf(&elfbuf[elfbufoffset], " 0x%llx", args->offset); elfbufoffset += sprintf(&elfbuf[elfbufoffset], " 0x%x", args->count); elfbufoffset += sprintf(&elfbuf[elfbufoffset], " 0x%x", args->stable); elfrec->bufoffset = elfbufoffset; } static void nfslog_CREATE3args_print(struct nfsl_log_file *elfrec, nfslog_CREATE3args *args) { nfslog_diropargs3_print(elfrec, &args->where); elfrec->bufoffset += sprintf(&elfrec->buf[elfrec->bufoffset], " %s", NFSL_CREATEMODE3(args->how.mode)); if (args->how.mode != EXCLUSIVE) { nfslog_size3_print(elfrec, &args->how.nfslog_createhow3_u.size); } } static void nfslog_MKDIR3args_print(struct nfsl_log_file *elfrec, nfslog_MKDIR3args *args) { nfslog_diropargs3_print(elfrec, &args->where); } static void nfslog_SYMLINK3args_print(struct nfsl_log_file *elfrec, nfslog_SYMLINK3args *args) { nfslog_diropargs3_print(elfrec, &args->where); elfrec->bufoffset += sprintf(&elfrec->buf[elfrec->bufoffset], " \"%s\"", args->symlink_data); } static void nfslog_MKNOD3args_print(struct nfsl_log_file *elfrec, nfslog_MKNOD3args *args) { nfslog_diropargs3_print(elfrec, &args->where); elfrec->bufoffset += sprintf(&elfrec->buf[elfrec->bufoffset], " %s", NFSL_FTYPE3(args->type)); } static void nfslog_REMOVE3args_print(struct nfsl_log_file *elfrec, nfslog_REMOVE3args *args) { nfslog_diropargs3_print(elfrec, &args->object); } static void nfslog_RMDIR3args_print(struct nfsl_log_file *elfrec, nfslog_RMDIR3args *args) { nfslog_diropargs3_print(elfrec, &args->object); } static void nfslog_RENAME3args_print(struct nfsl_log_file *elfrec, nfslog_RENAME3args *args) { nfslog_diropargs3_print(elfrec, &args->from); nfslog_diropargs3_print(elfrec, &args->to); } static void nfslog_LINK3args_print(struct nfsl_log_file *elfrec, nfslog_LINK3args *args) { nfslog_fh3_print(elfrec, &args->file); nfslog_diropargs3_print(elfrec, &args->link); } static void nfslog_READDIRPLUS3args_print(struct nfsl_log_file *elfrec, nfslog_READDIRPLUS3args *args) { nfslog_fh3_print(elfrec, &args->dir); elfrec->bufoffset += sprintf(&elfrec->buf[elfrec->bufoffset], " 0x%x", args->dircount); elfrec->bufoffset += sprintf(&elfrec->buf[elfrec->bufoffset], " 0x%x", args->maxcount); } static void nfslog_COMMIT3args_print(struct nfsl_log_file *elfrec, nfslog_COMMIT3args *args) { nfslog_fh3_print(elfrec, &args->file); elfrec->bufoffset += sprintf(&elfrec->buf[elfrec->bufoffset], " 0x%llx", args->offset); elfrec->bufoffset += sprintf(&elfrec->buf[elfrec->bufoffset], " 0x%x", args->count); } static void nfslog_nfsstat3_print(struct nfsl_log_file *elfrec, enum nfsstat3 *res, bool_t print_status) { if (print_status) { char *statp = nfslog_get_status((short)(*res)); if (statp != NULL) elfrec->bufoffset += sprintf(&elfrec->buf[elfrec->bufoffset], " %s", statp); else elfrec->bufoffset += sprintf(&elfrec->buf[elfrec->bufoffset], " %5d", *res); } } static void nfslog_LOOKUP3res_print(struct nfsl_log_file *elfrec, nfslog_LOOKUP3res *res, bool_t print_status) { if (print_status) { nfslog_nfsstat3_print(elfrec, &res->status, print_status); } else if (res->status == NFS3_OK) { nfslog_fh3_print(elfrec, &res->nfslog_LOOKUP3res_u.object); } } static void nfslog_READLINK3res_print(struct nfsl_log_file *elfrec, nfslog_READLINK3res *res, bool_t print_status) { if (print_status) { nfslog_nfsstat3_print(elfrec, &res->status, print_status); } else if (res->status == NFS3_OK) { elfrec->bufoffset += sprintf(&elfrec->buf[elfrec->bufoffset], " %s", res->nfslog_READLINK3res_u.data); } } static void nfslog_READ3res_print(struct nfsl_log_file *elfrec, nfslog_READ3res *res, bool_t print_status) { if (print_status) { nfslog_nfsstat3_print(elfrec, &res->status, print_status); } else if (res->status == NFS3_OK) { char *elfbuf = elfrec->buf; int elfbufoffset = elfrec->bufoffset; elfbufoffset += sprintf(&elfbuf[elfbufoffset], " 0x%llx", res->nfslog_READ3res_u.ok.filesize); elfbufoffset += sprintf(&elfbuf[elfbufoffset], " 0x%x", res->nfslog_READ3res_u.ok.count); elfbufoffset += sprintf(&elfbuf[elfbufoffset], " 0x%x", res->nfslog_READ3res_u.ok.eof); elfbufoffset += sprintf(&elfbuf[elfbufoffset], " 0x%x", res->nfslog_READ3res_u.ok.size); elfrec->bufoffset = elfbufoffset; } } static void nfslog_WRITE3res_print(struct nfsl_log_file *elfrec, nfslog_WRITE3res *res, bool_t print_status) { if (print_status) { nfslog_nfsstat3_print(elfrec, &res->status, print_status); } else if (res->status == NFS3_OK) { char *elfbuf = elfrec->buf; int elfbufoffset = elfrec->bufoffset; elfbufoffset += sprintf(&elfbuf[elfbufoffset], " 0x%llx", res->nfslog_WRITE3res_u.ok.filesize); elfbufoffset += sprintf(&elfbuf[elfbufoffset], " 0x%x", res->nfslog_WRITE3res_u.ok.count); elfbufoffset += sprintf(&elfrec->buf[elfbufoffset], " 0x%x", res->nfslog_WRITE3res_u.ok.committed); elfrec->bufoffset = elfbufoffset; } } static void nfslog_CREATE3res_print(struct nfsl_log_file *elfrec, nfslog_CREATE3res *res, bool_t print_status) { if (print_status) { nfslog_nfsstat3_print(elfrec, &res->status, print_status); } else if (res->status == NFS3_OK) { if (res->nfslog_CREATE3res_u.ok.obj.handle_follows) { nfslog_fh3_print(elfrec, &res->nfslog_CREATE3res_u.ok.obj.handle); } } } static void nfslog_MKDIR3res_print(struct nfsl_log_file *elfrec, nfslog_MKDIR3res *res, bool_t print_status) { if (print_status) { nfslog_nfsstat3_print(elfrec, &res->status, print_status); } else if (res->status == NFS3_OK) { if (res->nfslog_MKDIR3res_u.obj.handle_follows) { nfslog_fh3_print(elfrec, &res->nfslog_MKDIR3res_u.obj.handle); } } } static void nfslog_SYMLINK3res_print(struct nfsl_log_file *elfrec, nfslog_SYMLINK3res *res, bool_t print_status) { if (print_status) { nfslog_nfsstat3_print(elfrec, &res->status, print_status); } else if (res->status == NFS3_OK) { if (res->nfslog_SYMLINK3res_u.obj.handle_follows) { nfslog_fh3_print(elfrec, &res->nfslog_SYMLINK3res_u.obj.handle); } } } static void nfslog_MKNOD3res_print(struct nfsl_log_file *elfrec, nfslog_MKNOD3res *res, bool_t print_status) { if (print_status) { nfslog_nfsstat3_print(elfrec, &res->status, print_status); } else if (res->status == NFS3_OK) { if (res->nfslog_MKNOD3res_u.obj.handle_follows) { nfslog_fh3_print(elfrec, &res->nfslog_MKNOD3res_u.obj.handle); } } } static void nfslog_READDIRPLUS3res_print(struct nfsl_log_file *elfrec, nfslog_READDIRPLUS3res *res, bool_t print_status) { if (print_status) { nfslog_nfsstat3_print(elfrec, &res->status, print_status); } } /* * **** End of table functions for logging specific procs **** * * Hereafter are the general logging management and dispatcher. */ /* * nfsl_ipaddr_print - extracts sender ip address from transport struct * and prints it in legible form. */ static void nfsl_ipaddr_print(struct nfsl_log_file *elfrec, struct netbuf *ptr) { struct hostent *hp; extern char *inet_ntop(); int size, sin_family, error; char *elfbuf = elfrec->buf; char *addrp; int elfbufoffset = elfrec->bufoffset; if (ptr->len == 0) { elfbufoffset += sprintf(&elfbuf[elfbufoffset], " %s", empty_name); elfbufoffset += sprintf(&elfbuf[elfbufoffset], " %s", empty_name); elfrec->bufoffset = elfbufoffset; return; } elfbufoffset += sprintf(&elfbuf[elfbufoffset], " "); /* LINTED */ sin_family = ((struct sockaddr_in *)ptr->buf)->sin_family; switch (sin_family) { case (AF_INET): /* LINTED */ addrp = (char *)&((struct sockaddr_in *)ptr->buf)->sin_addr; size = sizeof (struct in_addr); break; case (AF_INET6): /* LINTED */ addrp = (char *)&((struct sockaddr_in6 *)ptr->buf)->sin6_addr; size = sizeof (struct in6_addr); break; default: /* unknown protocol: print address in hex form */ for (size = ptr->len, addrp = ptr->buf; size > 0; size--) { elfbufoffset += sprintf(&elfbuf[elfbufoffset], "%02x", *addrp); } elfbufoffset += sprintf(&elfbuf[elfbufoffset], " %s", empty_name); elfrec->bufoffset = elfbufoffset; return; } if (inet_ntop(sin_family, addrp, &elfbuf[elfbufoffset], (size_t)(DFLT_BUFFERSIZE + DFLT_OVFSIZE - elfbufoffset)) == NULL) { /* Not enough space to print - should never happen */ elfbuf[elfrec->bufoffset] = '\0'; /* just in case */ return; } /* inet_ntop copied address into elfbuf, so update offset */ elfbufoffset += strlen(&elfbuf[elfbufoffset]); /* get host name and log it as well */ hp = getipnodebyaddr(addrp, size, sin_family, &error); if (hp != NULL) { elfbufoffset += sprintf(&elfbuf[elfbufoffset], " \"%s\"", hp->h_name); } else { elfbufoffset += sprintf(&elfbuf[elfbufoffset], " %s", empty_name); } elfrec->bufoffset = elfbufoffset; } static void nfsl_elf_record_header_print(struct nfsl_log_file *elfrec, nfslog_record_header *lhp, char *principal_name, char *tag, struct nfsl_proc_disp *disp, char *progname) { struct passwd *pwp = NULL; char *elfbuf = elfrec->buf; int elfbufoffset = elfrec->bufoffset; /* * Fields: time bytes tag rpc-program rpc-version rpc-procedure * auth-flavor s-user-name s-uid uid u-name gid net-id * c-ip c-dns s-dns status rpcarg-path */ elfbufoffset += sprintf(&elfbuf[elfbufoffset], "%s", nfsl_get_time((time_t)lhp->rh_timestamp.tv_sec)); elfbufoffset += sprintf(&elfbuf[elfbufoffset], " 0x%x", lhp->rh_reclen); if ((tag != NULL) && (tag[0] != '\0')) { elfbufoffset += sprintf(&elfbuf[elfbufoffset], " \"%s\"", tag); } else { elfbufoffset += sprintf(&elfbuf[elfbufoffset], " %s", empty_name); } elfbufoffset += sprintf(&elfbuf[elfbufoffset], " %s 0x%x %s", progname, lhp->rh_version, disp->procname); elfbufoffset += sprintf(&elfbuf[elfbufoffset], " %s", NFSL_AUTH_FLAVOR_PRINT(lhp->rh_auth_flavor)); if ((principal_name != NULL) && (principal_name[0] != '\0')) { elfbufoffset += sprintf(&elfbuf[elfbufoffset], " \"%s\"", principal_name); if ((pwp = getpwnam(principal_name)) != NULL) { elfbufoffset += sprintf(&elfbuf[elfbufoffset], " 0x%lx", pwp->pw_uid); } else { elfbufoffset += sprintf(&elfbuf[elfbufoffset], " %s", empty_name); } } else { elfbufoffset += sprintf(&elfbuf[elfbufoffset], " %s", empty_name); elfbufoffset += sprintf(&elfbuf[elfbufoffset], " %s", empty_name); } elfbufoffset += sprintf(&elfbuf[elfbufoffset], " 0x%lx", lhp->rh_uid); if (((pwp = getpwuid(lhp->rh_uid)) != NULL) && (pwp->pw_name != NULL)) { elfbufoffset += sprintf(&elfbuf[elfbufoffset], " \"%s\"", pwp->pw_name); } else { elfbufoffset += sprintf(&elfbuf[elfbufoffset], " %s", empty_name); } elfbufoffset += sprintf(&elfbuf[elfbufoffset], " 0x%lx", lhp->rh_gid); elfrec->bufoffset = elfbufoffset; } static void nfsl_elf_buffer_header_print(struct nfsl_log_file *elfrec, nfslog_buffer_header *bufhdr) { int rc; struct utsname name; char *elfbuf = elfrec->buf; int elfbufoffset = elfrec->bufoffset; rc = uname(&name); elfbufoffset += sprintf(&elfbuf[elfbufoffset], "#Version %d.0\n#Software \"%s\"\n", bufhdr->bh_version, ((rc >= 0) ? name.sysname : empty_name)); elfbufoffset += sprintf(&elfbuf[elfbufoffset], "#Date %s\n", nfsl_get_date((time_t)bufhdr->bh_timestamp.tv_sec)); elfbufoffset += sprintf(&elfbuf[elfbufoffset], "#Remark %s\n", empty_name); elfbufoffset += sprintf(&elfbuf[elfbufoffset], "#Fields: time bytes tag"); elfbufoffset += sprintf(&elfbuf[elfbufoffset], " rpc-program rpc-version rpc-procedure"); elfbufoffset += sprintf(&elfbuf[elfbufoffset], " auth-flavor s-user-name s-uid uid u-name gid net-id c-ip"); elfbufoffset += sprintf(&elfbuf[elfbufoffset], " c-dns s-dns status rpcarg-path"); elfbufoffset += sprintf(&elfbuf[elfbufoffset], " rpc-arguments... rpc-response...\n"); elfrec->bufoffset = elfbufoffset; } /* * nfsl_find_elf_dispatch - get the dispatch struct for this request */ static struct nfsl_proc_disp * nfsl_find_elf_dispatch(nfslog_request_record *logrec, char **prognamep) { nfslog_record_header *logrechdr = &logrec->re_header; struct nfsl_prog_disp *progtable; /* prog struct */ struct nfsl_vers_disp *verstable; /* version struct */ int i, vers; /* Find prog element - search because can't use prog as array index */ for (i = 0; (i < nfsl_elf_dispatch_table_arglen) && (logrechdr->rh_prognum != nfsl_elf_dispatch_table[i].nfsl_dis_prog); i++); if (i >= nfsl_elf_dispatch_table_arglen) { /* program not logged */ /* not an error */ return (NULL); } progtable = &nfsl_elf_dispatch_table[i]; /* Find vers element - no validity check - if here it's valid vers */ vers = logrechdr->rh_version - progtable->nfsl_dis_versmin; verstable = &progtable->nfsl_dis_vers_table[vers]; /* Find proc element - no validity check - if here it's valid proc */ *prognamep = progtable->progname; return (&verstable->nfsl_dis_proc_table[logrechdr->rh_procnum]); } /* * nfsl_elf_rpc_print - Print the record buffer. */ static void nfsl_elf_rpc_print(struct nfsl_log_file *elfrec, nfslog_request_record *logrec, struct nfsl_proc_disp *disp, char *progname, char *path1, char *path2) { if (debug > 1) { (void) printf("%s %d %s", progname, logrec->re_header.rh_version, disp->procname); (void) printf(": '%s', '%s'\n", ((path1 != NULL) ? path1 : empty_name), ((path2 != NULL) ? path2 : empty_name)); } /* * XXXX programs using this file to get a usable record should * take "record" struct. */ /* * Print the variable fields: * principal name * netid * ip address * rpc args * rpc res * Use the displacements calculated earlier... */ /* * Fields: time bytes tag rpc-program rpc-version rpc-procedure * auth-flavor s-user-name s-uid uid u-name gid net-id c-ip * c-dns s-dns status rpcarg-path */ nfsl_elf_record_header_print(elfrec, &logrec->re_header, logrec->re_principal_name, logrec->re_tag, disp, progname); if ((logrec->re_netid != NULL) && (logrec->re_netid[0] != '\0')) { elfrec->bufoffset += sprintf(&elfrec->buf[elfrec->bufoffset], " \"%s\"", logrec->re_netid); } else { elfrec->bufoffset += sprintf(&elfrec->buf[elfrec->bufoffset], " %s", empty_name); } nfsl_ipaddr_print(elfrec, &logrec->re_ipaddr); elfrec->bufoffset += sprintf(&elfrec->buf[elfrec->bufoffset], " \"%s\"", hostname); /* Next is return status */ (*disp->nfsl_dis_res)(elfrec, logrec->re_rpc_res, TRUE); /* Next is argpath */ if (path1 != NULL) { elfrec->bufoffset += sprintf(&elfrec->buf[elfrec->bufoffset], " \"%s\"", path1); } else { elfrec->bufoffset += sprintf(&elfrec->buf[elfrec->bufoffset], " %s", empty_name); } /* * path2 is non-empty for rename/link type operations. If it is non- * empty print it here as it's a part of the args */ if (path2 != NULL) { elfrec->bufoffset += sprintf(&elfrec->buf[elfrec->bufoffset], " \"%s\"", path2); } /* Next print formatted rpc args */ (*disp->nfsl_dis_args)(elfrec, logrec->re_rpc_arg); /* Next print formatted rpc res (minus status) */ (*disp->nfsl_dis_res)(elfrec, logrec->re_rpc_res, FALSE); elfrec->bufoffset += sprintf(&elfrec->buf[elfrec->bufoffset], "\n"); } /* * nfsl_log_file_add - add a new record to the list */ static void nfsl_log_file_add(struct nfsl_log_file *elfrec, struct nfsl_log_file **elf_listp) { elfrec->next = *elf_listp; elfrec->prev = NULL; if (*elf_listp != NULL) { (*elf_listp)->prev = elfrec; } *elf_listp = elfrec; } /* * nfsl_log_file_find - finds a record in the list, given a cookie (== elfrec) * Returns the record. */ static struct nfsl_log_file * nfsl_log_file_find(struct nfsl_log_file *elfrec, struct nfsl_log_file *elf_list) { struct nfsl_log_file *rec; for (rec = elf_list; (rec != NULL) && (rec != elfrec); rec = rec->next); return (rec); } /* * nfsl_log_file_del - delete a record from the list, does not free rec. * Returns the deleted record. */ static struct nfsl_log_file * nfsl_log_file_del(struct nfsl_log_file *elfrec, struct nfsl_log_file **elf_listp) { struct nfsl_log_file *rec; if ((rec = nfsl_log_file_find(elfrec, *elf_listp)) == NULL) { return (NULL); } if (rec->prev != NULL) { rec->prev->next = rec->next; } else { *elf_listp = rec->next; } if (rec->next != NULL) { rec->next->prev = rec->prev; } return (rec); } /* * nfsl_log_file_free - frees a record */ static void nfsl_log_file_free(struct nfsl_log_file *elfrec) { if (elfrec == NULL) return; if (elfrec->path != NULL) free(elfrec->path); if (elfrec->buf != NULL) free(elfrec->buf); free(elfrec); } /* * Exported Functions */ /* * nfslog_open_elf_file - open the output elf file and mallocs needed buffers * Returns a pointer to the nfsl_log_file on success, NULL on error. * * *error contains the last error encountered on this object, It can * be used to avoid reporting the same error endlessly, by comparing * the current error to the last error. It is reset to the current error * code on return. */ void * nfslog_open_elf_file(char *elfpath, nfslog_buffer_header *bufhdr, int *error) { struct nfsl_log_file *elfrec; struct stat stat_buf; int preverror = *error; if ((elfrec = malloc(sizeof (*elfrec))) == NULL) { *error = errno; if (*error != preverror) { syslog(LOG_ERR, gettext("nfslog_open_elf_file: %s"), strerror(*error)); } return (NULL); } bzero(elfrec, sizeof (*elfrec)); elfrec->buf = (char *)malloc(DFLT_BUFFERSIZE + DFLT_OVFSIZE); if (elfrec->buf == NULL) { *error = errno; if (*error != preverror) { syslog(LOG_ERR, gettext("nfslog_open_elf_file: %s"), strerror(*error)); } nfsl_log_file_free(elfrec); return (NULL); } if ((elfrec->path = strdup(elfpath)) == NULL) { *error = errno; if (*error != preverror) { syslog(LOG_ERR, gettext("nfslog_open_elf_file: %s"), strerror(*error)); } nfsl_log_file_free(elfrec); return (NULL); } if ((elfrec->fp = fopen(elfpath, "a")) == NULL) { *error = errno; if (*error != preverror) { syslog(LOG_ERR, gettext("Cannot open '%s': %s"), elfpath, strerror(*error)); } nfsl_log_file_free(elfrec); return (NULL); } if (stat(elfpath, &stat_buf) == -1) { *error = errno; if (*error != preverror) { syslog(LOG_ERR, gettext("Cannot stat '%s': %s"), elfpath, strerror(*error)); } (void) fclose(elfrec->fp); nfsl_log_file_free(elfrec); return (NULL); } nfsl_log_file_add(elfrec, &elf_file_list); if (stat_buf.st_size == 0) { /* * Print header unto logfile */ nfsl_elf_buffer_header_print(elfrec, bufhdr); } if (hostname[0] == '\0') { (void) gethostname(hostname, MAXHOSTNAMELEN); } return (elfrec); } /* * nfslog_close_elf_file - close elffile and write out last buffer */ void nfslog_close_elf_file(void **elfcookie) { struct nfsl_log_file *elfrec; if ((*elfcookie == NULL) || ((elfrec = nfsl_log_file_del( *elfcookie, &elf_file_list)) == NULL)) { *elfcookie = NULL; return; } if (elfrec->fp != NULL) { /* Write the last output buffer to disk */ (void) nfsl_write_elfbuf(elfrec); (void) fclose(elfrec->fp); } nfsl_log_file_free(elfrec); *elfcookie = NULL; } /* * nfslog_process_elf_rec - processes the record in the buffer and outputs * to the elf log. * Return 0 for success, errno else. */ int nfslog_process_elf_rec(void *elfcookie, nfslog_request_record *logrec, char *path1, char *path2) { struct nfsl_log_file *elfrec; struct nfsl_proc_disp *disp; char *progname; if ((elfrec = nfsl_log_file_find(elfcookie, elf_file_list)) == NULL) { return (EINVAL); } /* Make sure there is room */ if (elfrec->bufoffset > DFLT_BUFFERSIZE) { if (nfsl_write_elfbuf(elfrec) < 0) { return (errno); } } if ((disp = nfsl_find_elf_dispatch(logrec, &progname)) != NULL) { nfsl_elf_rpc_print(elfrec, logrec, disp, progname, path1, path2); } return (0); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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) 1991, 1999 by Sun Microsystems, Inc. * All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Note: If making changes to this file, check also the file * cmd/cmd-inet/usr.sbin/snoop/snoop_ipaddr.c * as it has the same functions there. */ static jmp_buf nisjmp; #define MAXHASH 1024 /* must be a power of 2 */ struct hostdata { struct hostdata *h_next; char *h_hostname; int h_pktsout; int h_pktsin; }; struct hostdata4 { struct hostdata4 *h4_next; char *h4_hostname; int h4_pktsout; int h4_pktsin; struct in_addr h4_addr; }; struct hostdata6 { struct hostdata6 *h6_next; char *h6_hostname; int h6_pktsout; int h6_pktsin; struct in6_addr h6_addr; }; static struct hostdata *addhost(int, void *, char *); static struct hostdata4 *h_table4[MAXHASH]; static struct hostdata6 *h_table6[MAXHASH]; #define iphash(e) ((e) & (MAXHASH-1)) /* ARGSUSED */ static void wakeup(int n) { longjmp(nisjmp, 1); } extern char *inet_ntoa(); static struct hostdata * iplookup(ipaddr) struct in_addr *ipaddr; { register struct hostdata4 *h; struct hostent *hp = NULL; struct netent *np; int error_num; for (h = h_table4[iphash(ipaddr->s_addr)]; h; h = h->h4_next) { if (h->h4_addr.s_addr == ipaddr->s_addr) return ((struct hostdata *)h); } /* not found. Put it in */ if (ipaddr->s_addr == htonl(INADDR_BROADCAST)) return (addhost(AF_INET, ipaddr, "BROADCAST")); if (ipaddr->s_addr == htonl(INADDR_ANY)) return (addhost(AF_INET, ipaddr, "OLD-BROADCAST")); /* * Set an alarm here so we don't get held up by * an unresponsive name server. * Give it 3 sec to do its work. */ if (setjmp(nisjmp) == 0) { (void) signal(SIGALRM, wakeup); (void) alarm(3); hp = getipnodebyaddr((char *)ipaddr, sizeof (struct in_addr), AF_INET, &error_num); if (hp == NULL && inet_lnaof(*ipaddr) == 0) { np = getnetbyaddr(inet_netof(*ipaddr), AF_INET); if (np) return (addhost(AF_INET, ipaddr, np->n_name)); } (void) alarm(0); } else { hp = NULL; } return (addhost(AF_INET, ipaddr, hp ? hp->h_name : inet_ntoa(*ipaddr))); } static struct hostdata * ip6lookup(ip6addr) struct in6_addr *ip6addr; { struct hostdata6 *h; struct hostent *hp = NULL; int error_num; char addrstr[INET6_ADDRSTRLEN]; char *addname; struct hostdata *retval; for (h = h_table6[iphash(((uint32_t *)ip6addr)[3])]; h; h = h->h6_next) { if (IN6_ARE_ADDR_EQUAL(&h->h6_addr, ip6addr)) return ((struct hostdata *)h); } /* not in the hash table, put it in */ if (IN6_IS_ADDR_UNSPECIFIED(ip6addr)) return (addhost(AF_INET6, ip6addr, "UNSPECIFIED")); /* * Set an alarm here so we don't get held up by * an unresponsive name server. * Give it 3 sec to do its work. */ if (setjmp(nisjmp) == 0) { (void) signal(SIGALRM, wakeup); (void) alarm(3); hp = getipnodebyaddr(ip6addr, sizeof (struct in6_addr), AF_INET6, &error_num); (void) alarm(0); } else { hp = NULL; } if (hp != NULL) addname = hp->h_name; else { (void) inet_ntop(AF_INET6, ip6addr, addrstr, INET6_ADDRSTRLEN); addname = addrstr; } retval = addhost(AF_INET6, ip6addr, addname); freehostent(hp); return (retval); } static struct hostdata * addhost(family, ipaddr, name) int family; void *ipaddr; char *name; { register struct hostdata **hp, *n; int hashval; switch (family) { case AF_INET: n = (struct hostdata *)malloc(sizeof (struct hostdata4)); if (n == NULL) goto alloc_failed; (void) memset(n, 0, sizeof (struct hostdata4)); n->h_hostname = strdup(name); if (n->h_hostname == NULL) goto alloc_failed; ((struct hostdata4 *)n)->h4_addr = *(struct in_addr *)ipaddr; hashval = ((struct in_addr *)ipaddr)->s_addr; hp = (struct hostdata **)&h_table4[iphash(hashval)]; break; case AF_INET6: n = (struct hostdata *)malloc(sizeof (struct hostdata6)); if (n == NULL) goto alloc_failed; (void) memset(n, 0, sizeof (struct hostdata6)); n->h_hostname = strdup(name); if (n->h_hostname == NULL) goto alloc_failed; (void) memcpy(&((struct hostdata6 *)n)->h6_addr, ipaddr, sizeof (struct in6_addr)); hashval = ((int *)ipaddr)[3]; hp = (struct hostdata **)&h_table6[iphash(hashval)]; break; default: (void) fprintf(stderr, "nfslog: addhost ERROR: Unknown address family: %d", family); return (NULL); } n->h_next = *hp; *hp = n; return (n); alloc_failed: (void) fprintf(stderr, "addhost: no mem\n"); if (n != NULL) free(n); return (NULL); } char * addrtoname(void *sockp) { struct hostdata *hostp; int family = ((struct sockaddr_in *)sockp)->sin_family; switch (family) { case AF_INET: hostp = iplookup(&((struct sockaddr_in *)sockp)->sin_addr); break; case AF_INET6: hostp = ip6lookup(&((struct sockaddr_in6 *)sockp)->sin6_addr); break; default: (void) fprintf(stderr, "nfslog: ERROR: unknown address " \ "family: %d\n", family); hostp = NULL; } return ((hostp != NULL) ? hostp->h_hostname : NULL); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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) 1999 by Sun Microsystems, Inc. * All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fhtab.h" #include "nfslogd.h" /* * How long should an entry stay in the list before being forced * out and a trans log entry printed */ #define TRANS_ENTRY_TIMEOUT 60 extern char *addrtoname(void *); struct transentry { struct transentry *next; struct transentry *prev; timestruc32_t starttime; /* when did transaction start? */ timestruc32_t lastupdate; /* last operation for this entry */ #define TRANS_OPER_READ 1 #define TRANS_OPER_WRITE 2 #define TRANS_OPER_SETATTR 3 #define TRANS_OPER_REMOVE 4 #define TRANS_OPER_MKDIR 5 #define TRANS_OPER_CREATE 6 #define TRANS_OPER_RMDIR 7 #define TRANS_OPER_RENAME 8 #define TRANS_OPER_MKNOD 9 #define TRANS_OPER_LINK 10 #define TRANS_OPER_SYMLINK 11 uchar_t optype; /* read, write, ...? */ #define TRANS_DATATYPE_NA /* not applicable data type */ #define TRANS_DATATYPE_ASCII 0 /* transfer done as ascii */ #define TRANS_DATATYPE_BINARY 1 /* transfer done as binary */ uchar_t datatype; /* * Action taken by server before transfer was made -- noaction, * compressed, tar or uncompressed. */ #define TRANS_OPTION_NOACTION 0 uchar_t transoption; char *pathname; struct netbuf *pnb; uid_t uid; int nfsvers; char *netid; char *principal_name; uint64_t totalbytes; /* total operated upon in history */ union { fhandle_t fh; nfs_fh3 fh3; } fh_u; }; struct nfslog_trans_file { struct nfslog_trans_file *next; /* next file in list */ struct nfslog_trans_file *prev; /* next file in list */ int refcnt; /* number of references to this struct */ char *path; /* pathname of file */ FILE *fp; /* file pointer */ /* timestamp of the last transaction processed for this file */ timestruc32_t lasttrans_timestamp; /* 'current' time that last trans was processed */ time_t last_trans_read; uint32_t trans_to_log; /* transactions that are to be logged */ uint32_t trans_output_type; struct transentry *te_list_v3_read; struct transentry *te_list_v3_write; struct transentry *te_list_v2_read; struct transentry *te_list_v2_write; }; static struct nfslog_trans_file *trans_file_head = NULL; static void nfslog_print_trans_logentry(struct transentry *, struct nfslog_trans_file *); static struct netbuf * netbufdup(struct netbuf *pnb) { struct netbuf *pnewnb; uint32_t size; size = offsetof(struct netbuf, buf); size += pnb->len; if ((pnewnb = (struct netbuf *)malloc(sizeof (*pnewnb))) == NULL) return (NULL); if ((pnewnb->buf = malloc(pnb->len)) == NULL) { free(pnewnb); return (NULL); } pnewnb->maxlen = pnb->maxlen; pnewnb->len = pnb->len; bcopy(pnb->buf, pnewnb->buf, pnb->len); return (pnewnb); } static void freenetbuf(struct netbuf *pnb) { free(pnb->buf); free(pnb); } static struct transentry * create_te() { struct transentry *pte; if ((pte = (struct transentry *)calloc(1, sizeof (*pte))) == NULL) { /* failure message or action */ return (NULL); } pte->next = pte->prev = NULL; return (pte); } static struct transentry * insert_te( struct transentry *te_list, struct transentry *entry) { struct transentry *pte; /* * First check for any non-filehandle comparisons that may be needed. */ switch (entry->optype) { case TRANS_OPER_REMOVE: case TRANS_OPER_RENAME: for (pte = te_list->next; pte != te_list; pte = pte->next) { /* if path names match, then return */ if (strcmp(pte->pathname, entry->pathname) == 0) { return (pte); } } return (NULL); default: break; } for (pte = te_list->next; pte != te_list; pte = pte->next) { /* If the file handles match, then we have a hit */ if (entry->nfsvers == NFS_VERSION) { if (bcmp(&(pte->fh_u.fh), &(entry->fh_u.fh), sizeof (fhandle_t)) == 0) { switch (entry->optype) { case TRANS_OPER_READ: case TRANS_OPER_WRITE: if (pte->uid == entry->uid) { return (pte); } break; default: return (pte); } } } else { if (pte->fh_u.fh3.fh3_length == entry->fh_u.fh3.fh3_length && bcmp(pte->fh_u.fh3.fh3_u.data, entry->fh_u.fh3.fh3_u.data, pte->fh_u.fh3.fh3_length) == 0) switch (entry->optype) { case TRANS_OPER_READ: case TRANS_OPER_WRITE: if (pte->uid == entry->uid) { return (pte); } break; default: return (pte); } } } /* * XXX - should compare more of the information to make sure * it is a match. */ /* * other operation types do not generate an entry for * further analysis */ switch (entry->optype) { case TRANS_OPER_READ: case TRANS_OPER_WRITE: break; default: return (NULL); } insque(entry, te_list); return (NULL); /* NULL signifies insertion and no record found */ } static void remove_te(struct transentry *pte) { if (pte->next) remque(pte); if (pte->principal_name) free(pte->principal_name); if (pte->pathname) free(pte->pathname); if (pte->pnb) freenetbuf(pte->pnb); if (pte->netid) free(pte->netid); free(pte); } /* * nfslog_trans_file_free - frees a record */ static void nfslog_trans_file_free(struct nfslog_trans_file *transrec) { if (transrec == NULL) return; if (transrec->path != NULL) { if (debug) (void) printf("freeing transpath '%s'\n", transrec->path); free(transrec->path); } free(transrec); } /* * On success returns a pointer to the trans_file that matches * 'path', 'output_type' and 'transtolog'. The reference count for this * object is incremented as well. * Returns NULL if it is not in the list. */ static struct nfslog_trans_file * nfslog_trans_file_find( char *path, uint32_t output_type, uint32_t transtolog) { struct nfslog_trans_file *tfp; for (tfp = trans_file_head; tfp != NULL; tfp = tfp->next) { if ((strcmp(path, tfp->path) == 0) && (output_type == tfp->trans_output_type) && (transtolog == tfp->trans_to_log)) { if (debug) (void) printf("Found transfile '%s'\n", path); (tfp->refcnt)++; return (tfp); } } return (NULL); } /* * nfslog_close_trans_file - decrements the reference count on * this object. On last reference it closes transfile and * frees resources */ static void nfslog_close_trans_file(struct nfslog_trans_file *tf) { assert(tf != NULL); assert(tf->refcnt > 0); if (tf->refcnt > 1) { (tf->refcnt)--; return; } if (tf->fp != NULL) { (void) fsync(fileno(tf->fp)); (void) fclose(tf->fp); } /* * Disconnect from list */ tf->prev->next = tf->next; if (tf->next != NULL) tf->next->prev = tf->prev; /* * Adjust the head of the list if appropriate */ if (tf == trans_file_head) trans_file_head = tf->next; nfslog_trans_file_free(tf); } /* * nfslog_open_trans_file - open the output trans file and mallocs. * The object is then inserted at the beginning of the global * transfile list. * Returns 0 for success, error else. * * *error contains the last error encountered on this object. It can * be used to avoid reporting the same error endlessly, by comparing * the current error to the last error. It is reset to the current error * code on return. */ void * nfslog_open_trans_file( char *transpath, uint32_t output_type, uint32_t transtolog, int *error) { int preverror = *error; struct nfslog_trans_file *transrec; transrec = nfslog_trans_file_find(transpath, output_type, transtolog); if (transrec != NULL) return (transrec); if ((transrec = malloc(sizeof (*transrec))) == NULL) { *error = errno; if (*error != preverror) { syslog(LOG_ERR, gettext("nfslog_open_trans_file: %s"), strerror(*error)); } return (NULL); } bzero(transrec, sizeof (*transrec)); if ((transrec->path = strdup(transpath)) == NULL) { *error = errno; if (*error != preverror) { syslog(LOG_ERR, gettext("nfslog_open_trans_file: %s"), strerror(*error)); } nfslog_trans_file_free(transrec); return (NULL); } if ((transrec->fp = fopen(transpath, "a")) == NULL) { *error = errno; if (*error != preverror) { syslog(LOG_ERR, gettext("Cannot open '%s': %s"), transpath, strerror(*error)); } nfslog_trans_file_free(transrec); return (NULL); } transrec->te_list_v3_read = (struct transentry *)malloc(sizeof (struct transentry)); transrec->te_list_v3_write = (struct transentry *)malloc(sizeof (struct transentry)); transrec->te_list_v2_read = (struct transentry *)malloc(sizeof (struct transentry)); transrec->te_list_v2_write = (struct transentry *)malloc(sizeof (struct transentry)); if (transrec->te_list_v3_read == NULL || transrec->te_list_v3_write == NULL || transrec->te_list_v2_read == NULL || transrec->te_list_v2_write == NULL) { if (transrec->te_list_v3_read) free(transrec->te_list_v3_read); if (transrec->te_list_v3_write) free(transrec->te_list_v3_write); if (transrec->te_list_v2_read) free(transrec->te_list_v2_read); if (transrec->te_list_v2_write) free(transrec->te_list_v2_write); nfslog_close_trans_file(transrec); return (NULL); } transrec->te_list_v3_read->next = transrec->te_list_v3_read->prev = transrec->te_list_v3_read; transrec->te_list_v3_write->next = transrec->te_list_v3_write->prev = transrec->te_list_v3_write; transrec->te_list_v2_read->next = transrec->te_list_v2_read->prev = transrec->te_list_v2_read; transrec->te_list_v2_write->next = transrec->te_list_v2_write->prev = transrec->te_list_v2_write; /* * Indicate what transaction types to log */ transrec->trans_to_log = transtolog; /* * Indicate whether to print 'full' or 'basic' version * of the transactions */ transrec->trans_output_type = output_type; /* * Insert at the beginning of the list. */ transrec->next = trans_file_head; if (trans_file_head != NULL) trans_file_head->prev = transrec; trans_file_head = transrec->prev = transrec; transrec->refcnt = 1; transrec->lasttrans_timestamp.tv_sec = 0; transrec->lasttrans_timestamp.tv_nsec = 0; transrec->last_trans_read = time(0); if (debug) (void) printf("New transfile '%s'\n", transrec->path); return (transrec); } void nfslog_process_trans_timeout( struct nfslog_trans_file *tf, uint32_t force_flush) { struct transentry *pte; time_t cur_time = time(0); /* * If we have not seen a transaction on this file for * a long time, then we need to flush everything out since * we may not be getting anything else in for awhile. */ if (difftime(cur_time, tf->last_trans_read) > (2 * MAX(TRANS_ENTRY_TIMEOUT, idle_time))) force_flush = TRUE; restart1: for (pte = tf->te_list_v3_read->next; pte != tf->te_list_v3_read; pte = pte->next) { if (force_flush == TRUE || (difftime(tf->lasttrans_timestamp.tv_sec, pte->lastupdate.tv_sec) > MAX(TRANS_ENTRY_TIMEOUT, idle_time))) { nfslog_print_trans_logentry(pte, tf); remove_te(pte); goto restart1; } } restart2: for (pte = tf->te_list_v3_write->next; pte != tf->te_list_v3_write; pte = pte->next) { if (force_flush == TRUE || (difftime(tf->lasttrans_timestamp.tv_sec, pte->lastupdate.tv_sec) > MAX(TRANS_ENTRY_TIMEOUT, idle_time))) { nfslog_print_trans_logentry(pte, tf); remove_te(pte); goto restart2; } } restart3: for (pte = tf->te_list_v2_read->next; pte != tf->te_list_v2_read; pte = pte->next) { if (force_flush == TRUE || (difftime(tf->lasttrans_timestamp.tv_sec, pte->lastupdate.tv_sec) > MAX(TRANS_ENTRY_TIMEOUT, idle_time))) { nfslog_print_trans_logentry(pte, tf); remove_te(pte); goto restart3; } } restart4: for (pte = tf->te_list_v2_write->next; pte != tf->te_list_v2_write; pte = pte->next) { if (force_flush == TRUE || (difftime(tf->lasttrans_timestamp.tv_sec, pte->lastupdate.tv_sec) > MAX(TRANS_ENTRY_TIMEOUT, idle_time))) { nfslog_print_trans_logentry(pte, tf); remove_te(pte); goto restart4; } } (void) fflush(tf->fp); } /* * Flushes outstanding transactions to disk, and closes * the transaction log. */ void nfslog_close_transactions(void **transcookie) { assert(*transcookie != NULL); nfslog_process_trans_timeout( (struct nfslog_trans_file *)(*transcookie), TRUE); nfslog_close_trans_file((struct nfslog_trans_file *)(*transcookie)); *transcookie = NULL; } static struct transentry * trans_read( nfslog_request_record *logrec, struct nfslog_trans_file *tf, char *fhpath, char *path1) { struct transentry *newte; struct transentry *pte = NULL; /* LINTED */ nfslog_nfsreadargs *args = (nfslog_nfsreadargs *)logrec->re_rpc_arg; /* LINTED */ nfslog_rdresult *res = (nfslog_rdresult *)logrec->re_rpc_res; if (res->r_status != NFS_OK) return (NULL); if ((newte = create_te()) == NULL) return (NULL); if (!path1) { newte->pathname = nfslog_get_path(&args->ra_fhandle, NULL, fhpath, "trans_read"); } else { newte->pathname = strdup(path1); } /* prep the struct for insertion */ newte->starttime = logrec->re_header.rh_timestamp; newte->lastupdate = logrec->re_header.rh_timestamp; newte->optype = TRANS_OPER_READ; newte->datatype = TRANS_DATATYPE_BINARY; newte->transoption = TRANS_OPTION_NOACTION; newte->pnb = netbufdup(&(logrec->re_ipaddr)); newte->uid = logrec->re_header.rh_uid; newte->nfsvers = NFS_VERSION; newte->netid = strdup(logrec->re_netid); if (logrec->re_principal_name) newte->principal_name = strdup(logrec->re_principal_name); else newte->principal_name = NULL; newte->totalbytes = res->nfslog_rdresult_u.r_ok.rrok_count; newte->fh_u.fh = *(NFSLOG_GET_FHANDLE2(&args->ra_fhandle)); if (res->nfslog_rdresult_u.r_ok.rrok_count < res->nfslog_rdresult_u.r_ok.filesize) { if (pte = insert_te(tf->te_list_v2_read, newte)) { /* free this since entry was found (not inserted) */ remove_te(newte); pte->totalbytes += res->nfslog_rdresult_u.r_ok.rrok_count; if (pte->lastupdate.tv_sec <= logrec->re_header.rh_timestamp.tv_sec) pte->lastupdate = logrec->re_header.rh_timestamp; if (pte->totalbytes < res->nfslog_rdresult_u.r_ok.filesize) { pte = NULL; /* prevent printing of log entry */ } } } else { pte = newte; /* print a log record - complete file read */ } return (pte); } static struct transentry * trans_write( nfslog_request_record *logrec, struct nfslog_trans_file *tf, char *fhpath, char *path1) { struct transentry *newte; struct transentry *pte = NULL; /* LINTED */ nfslog_writeargs *args = (nfslog_writeargs *)logrec->re_rpc_arg; /* LINTED */ nfslog_writeresult *res = (nfslog_writeresult *)logrec->re_rpc_res; if (res->wr_status != NFS_OK) return (NULL); if ((newte = create_te()) == NULL) return (NULL); if (!path1) { newte->pathname = nfslog_get_path(&args->waargs_fhandle, NULL, fhpath, "trans_write"); } else { newte->pathname = strdup(path1); } newte->starttime = logrec->re_header.rh_timestamp; newte->lastupdate = logrec->re_header.rh_timestamp; newte->optype = TRANS_OPER_WRITE; newte->datatype = TRANS_DATATYPE_BINARY; newte->transoption = TRANS_OPTION_NOACTION; newte->pnb = netbufdup(&(logrec->re_ipaddr)); newte->uid = logrec->re_header.rh_uid; newte->nfsvers = NFS_VERSION; newte->netid = strdup(logrec->re_netid); if (logrec->re_principal_name) newte->principal_name = strdup(logrec->re_principal_name); else newte->principal_name = NULL; newte->totalbytes = args->waargs_totcount; newte->fh_u.fh = *(NFSLOG_GET_FHANDLE2(&args->waargs_fhandle)); if (pte = insert_te(tf->te_list_v2_write, newte)) { /* * if the write would have increased the total byte count * over the filesize, then generate a log entry and remove * the write record and insert the new one. */ if (pte->totalbytes + args->waargs_totcount > res->nfslog_writeresult_u.wr_size) { nfslog_print_trans_logentry(pte, tf); remove_te(pte); (void) insert_te(tf->te_list_v2_write, newte); pte = NULL; } else { /* free this since entry was found (not inserted) */ remove_te(newte); pte->totalbytes += args->waargs_totcount; if (pte->lastupdate.tv_sec <= logrec->re_header.rh_timestamp.tv_sec) { pte->lastupdate = logrec->re_header.rh_timestamp; } pte = NULL; /* prevent printing of log entry */ } } return (pte); } static struct transentry * trans_setattr( nfslog_request_record *logrec, struct nfslog_trans_file *tf, char *fhpath, char *path1) { struct transentry *newte; struct transentry *pte = NULL; /* LINTED */ nfslog_setattrargs *args = (nfslog_setattrargs *)logrec->re_rpc_arg; /* LINTED */ nfsstat *res = (nfsstat *)logrec->re_rpc_res; if (*res != NFS_OK) return (NULL); if (args->saa_sa.sa_size == (uint32_t)-1) return (NULL); /* * should check the size of the file to see if it * is being truncated below current eof. if so * a record should be generated.... XXX */ if (args->saa_sa.sa_size != 0) return (NULL); if ((newte = create_te()) == NULL) return (NULL); if (!path1) { newte->pathname = nfslog_get_path(&args->saa_fh, NULL, fhpath, "trans_setattr2"); } else { newte->pathname = strdup(path1); } newte->starttime = logrec->re_header.rh_timestamp; newte->lastupdate = logrec->re_header.rh_timestamp; newte->optype = TRANS_OPER_SETATTR; newte->datatype = TRANS_DATATYPE_BINARY; newte->transoption = TRANS_OPTION_NOACTION; newte->pnb = netbufdup(&(logrec->re_ipaddr)); newte->uid = logrec->re_header.rh_uid; newte->nfsvers = NFS_VERSION; newte->netid = strdup(logrec->re_netid); if (logrec->re_principal_name) newte->principal_name = strdup(logrec->re_principal_name); else newte->principal_name = NULL; newte->totalbytes = 0; newte->fh_u.fh = *(NFSLOG_GET_FHANDLE2(&args->saa_fh)); if (pte = insert_te(tf->te_list_v2_write, newte)) { nfslog_print_trans_logentry(pte, tf); remove_te(pte); } if (pte = insert_te(tf->te_list_v2_read, newte)) { nfslog_print_trans_logentry(pte, tf); remove_te(pte); } return (newte); } static struct transentry * trans_create( nfslog_request_record *logrec, struct nfslog_trans_file *tf, char *fhpath, char *path1) { struct transentry *newte; struct transentry *pte = NULL; /* LINTED */ nfslog_createargs *args = (nfslog_createargs *)logrec->re_rpc_arg; /* LINTED */ nfslog_diropres *res = (nfslog_diropres *)logrec->re_rpc_res; if (res->dr_status != NFS_OK) return (NULL); if ((newte = create_te()) == NULL) return (NULL); if (!path1) { newte->pathname = nfslog_get_path(&args->ca_da.da_fhandle, args->ca_da.da_name, fhpath, "trans_create2"); } else { newte->pathname = strdup(path1); } newte->starttime = logrec->re_header.rh_timestamp; newte->lastupdate = logrec->re_header.rh_timestamp; newte->optype = TRANS_OPER_CREATE; newte->datatype = TRANS_DATATYPE_BINARY; newte->transoption = TRANS_OPTION_NOACTION; newte->pnb = netbufdup(&(logrec->re_ipaddr)); newte->uid = logrec->re_header.rh_uid; newte->nfsvers = NFS_VERSION; newte->netid = strdup(logrec->re_netid); if (logrec->re_principal_name) newte->principal_name = strdup(logrec->re_principal_name); else newte->principal_name = NULL; if (args->ca_sa.sa_size == (uint32_t)-1) newte->totalbytes = 0; else newte->totalbytes = args->ca_sa.sa_size; newte->fh_u.fh = *(NFSLOG_GET_FHANDLE2( &res->nfslog_diropres_u.dr_ok.drok_fhandle)); /* * if the file is being truncated on create, we need to flush * any outstanding read/write transactions */ if (args->ca_sa.sa_size != (uint32_t)-1) { if (pte = insert_te(tf->te_list_v2_write, newte)) { nfslog_print_trans_logentry(pte, tf); remove_te(pte); } if (pte = insert_te(tf->te_list_v2_read, newte)) { nfslog_print_trans_logentry(pte, tf); remove_te(pte); } } return (newte); } static struct transentry * trans_remove( nfslog_request_record *logrec, struct nfslog_trans_file *tf, char *fhpath, char *path1) { struct transentry *newte; struct transentry *pte = NULL; /* LINTED */ nfslog_diropargs *args = (nfslog_diropargs *)logrec->re_rpc_arg; /* LINTED */ nfsstat *res = (nfsstat *)logrec->re_rpc_res; if (*res != NFS_OK) return (NULL); if ((newte = create_te()) == NULL) return (NULL); if (!path1) { char *name = args->da_name; fhandle_t *dfh = &args->da_fhandle; newte->pathname = nfslog_get_path(dfh, name, fhpath, "trans_remove2"); } else { newte->pathname = strdup(path1); } newte->starttime = logrec->re_header.rh_timestamp; newte->lastupdate = logrec->re_header.rh_timestamp; newte->optype = TRANS_OPER_REMOVE; newte->datatype = TRANS_DATATYPE_BINARY; newte->transoption = TRANS_OPTION_NOACTION; newte->pnb = netbufdup(&(logrec->re_ipaddr)); newte->uid = logrec->re_header.rh_uid; newte->nfsvers = NFS_VERSION; newte->netid = strdup(logrec->re_netid); if (logrec->re_principal_name) newte->principal_name = strdup(logrec->re_principal_name); else newte->principal_name = NULL; newte->totalbytes = 0; newte->fh_u.fh = *(NFSLOG_GET_FHANDLE2(&args->da_fhandle)); if (pte = insert_te(tf->te_list_v2_write, newte)) { nfslog_print_trans_logentry(pte, tf); remove_te(pte); } if (pte = insert_te(tf->te_list_v2_read, newte)) { nfslog_print_trans_logentry(pte, tf); remove_te(pte); } if (pte = insert_te(tf->te_list_v3_write, newte)) { nfslog_print_trans_logentry(pte, tf); remove_te(pte); } if (pte = insert_te(tf->te_list_v3_read, newte)) { nfslog_print_trans_logentry(pte, tf); remove_te(pte); } return (newte); } static struct transentry * trans_mkdir( nfslog_request_record *logrec, char *fhpath, char *path1) { struct transentry *newte; /* LINTED */ nfslog_createargs *args = (nfslog_createargs *)logrec->re_rpc_arg; /* LINTED */ nfslog_diropres *res = (nfslog_diropres *)logrec->re_rpc_res; if (res->dr_status != NFS_OK) return (NULL); if ((newte = create_te()) == NULL) return (NULL); if (!path1) { nfslog_diropargs *dargs = &args->ca_da; char *name = dargs->da_name; fhandle_t *dfh = &dargs->da_fhandle; newte->pathname = nfslog_get_path(dfh, name, fhpath, "trans_mkdir2"); } else { newte->pathname = strdup(path1); } newte->starttime = logrec->re_header.rh_timestamp; newte->lastupdate = logrec->re_header.rh_timestamp; newte->optype = TRANS_OPER_MKDIR; newte->datatype = TRANS_DATATYPE_BINARY; newte->transoption = TRANS_OPTION_NOACTION; newte->pnb = netbufdup(&(logrec->re_ipaddr)); newte->uid = logrec->re_header.rh_uid; newte->nfsvers = NFS_VERSION; newte->netid = strdup(logrec->re_netid); if (logrec->re_principal_name) newte->principal_name = strdup(logrec->re_principal_name); else newte->principal_name = NULL; newte->totalbytes = 0; newte->fh_u.fh = *(NFSLOG_GET_FHANDLE2(&args->ca_da.da_fhandle)); return (newte); } static struct transentry * trans_rmdir( nfslog_request_record *logrec, char *fhpath, char *path1) { struct transentry *newte; /* LINTED */ nfslog_diropargs *args = (nfslog_diropargs *)logrec->re_rpc_arg; /* LINTED */ nfsstat *res = (nfsstat *)logrec->re_rpc_res; if (*res != NFS_OK) return (NULL); if ((newte = create_te()) == NULL) return (NULL); if (!path1) { char *name = args->da_name; fhandle_t *dfh = &args->da_fhandle; newte->pathname = nfslog_get_path(dfh, name, fhpath, "trans_rmdir2"); } else { newte->pathname = strdup(path1); } newte->starttime = logrec->re_header.rh_timestamp; newte->lastupdate = logrec->re_header.rh_timestamp; newte->optype = TRANS_OPER_RMDIR; newte->datatype = TRANS_DATATYPE_BINARY; newte->transoption = TRANS_OPTION_NOACTION; newte->pnb = netbufdup(&(logrec->re_ipaddr)); newte->uid = logrec->re_header.rh_uid; newte->nfsvers = NFS_VERSION; newte->netid = strdup(logrec->re_netid); if (logrec->re_principal_name) newte->principal_name = strdup(logrec->re_principal_name); else newte->principal_name = NULL; newte->totalbytes = 0; newte->fh_u.fh = *(NFSLOG_GET_FHANDLE2(&args->da_fhandle)); return (newte); } static struct transentry * trans_rename( nfslog_request_record *logrec, struct nfslog_trans_file *tf, char *fhpath, char *path1, char *path2) { struct transentry *newte; struct transentry *pte = NULL; /* LINTED */ nfslog_rnmargs *args = (nfslog_rnmargs *)logrec->re_rpc_arg; /* LINTED */ nfsstat *res = (nfsstat *)logrec->re_rpc_res; char *tpath1 = NULL; char *tpath2 = NULL; if (*res != NFS_OK) return (NULL); if ((newte = create_te()) == NULL) return (NULL); if (!path1) { char *from_name, *to_name; fhandle_t *from_dfh, *to_dfh; from_name = args->rna_from.da_name; from_dfh = &args->rna_from.da_fhandle; to_name = args->rna_to.da_name; to_dfh = &args->rna_to.da_fhandle; path1 = tpath1 = nfslog_get_path(from_dfh, from_name, fhpath, "trans_rename from"); path2 = tpath2 = nfslog_get_path(to_dfh, to_name, fhpath, "trans_rename to"); } newte->pathname = path1; /* no need to strdup here */ newte->starttime = logrec->re_header.rh_timestamp; newte->lastupdate = logrec->re_header.rh_timestamp; newte->optype = TRANS_OPER_RENAME; newte->datatype = TRANS_DATATYPE_BINARY; newte->transoption = TRANS_OPTION_NOACTION; newte->pnb = netbufdup(&(logrec->re_ipaddr)); newte->uid = logrec->re_header.rh_uid; newte->nfsvers = NFS_VERSION; newte->netid = strdup(logrec->re_netid); if (logrec->re_principal_name) newte->principal_name = strdup(logrec->re_principal_name); else newte->principal_name = NULL; newte->totalbytes = 0; newte->fh_u.fh = *(NFSLOG_GET_FHANDLE2(&args->rna_from.da_fhandle)); /* switch path names for the file for renames */ if (pte = insert_te(tf->te_list_v2_write, newte)) { free(pte->pathname); pte->pathname = strdup(path2); } if (pte = insert_te(tf->te_list_v2_read, newte)) { free(pte->pathname); pte->pathname = strdup(path2); } if (pte = insert_te(tf->te_list_v3_write, newte)) { free(pte->pathname); pte->pathname = strdup(path2); } if (pte = insert_te(tf->te_list_v3_read, newte)) { free(pte->pathname); pte->pathname = strdup(path2); } newte->pathname = (char *)malloc(strlen(path1) + strlen(path2) + 3); /* check for NULL malloc */ (void) sprintf(newte->pathname, "%s->%s", path1, path2); if (tpath1) { free(tpath1); free(tpath2); } return (newte); } static struct transentry * trans_link( nfslog_request_record *logrec, char *fhpath, char *path1, char *path2) { struct transentry *newte; /* LINTED */ nfslog_linkargs *args = (nfslog_linkargs *)logrec->re_rpc_arg; /* LINTED */ nfsstat *res = (nfsstat *)logrec->re_rpc_res; char *tpath1 = NULL; char *tpath2 = NULL; if (*res != NFS_OK) return (NULL); if ((newte = create_te()) == NULL) return (NULL); if (!path1) { fhandle_t *fh = &args->la_from; char *name = args->la_to.da_name; fhandle_t *dfh = &args->la_to.da_fhandle; path1 = tpath1 = nfslog_get_path(fh, NULL, fhpath, "trans_link from"); path2 = tpath2 = nfslog_get_path(dfh, name, fhpath, "trans_link to"); } newte->starttime = logrec->re_header.rh_timestamp; newte->lastupdate = logrec->re_header.rh_timestamp; newte->optype = TRANS_OPER_LINK; newte->datatype = TRANS_DATATYPE_BINARY; newte->transoption = TRANS_OPTION_NOACTION; newte->pnb = netbufdup(&(logrec->re_ipaddr)); newte->uid = logrec->re_header.rh_uid; newte->nfsvers = NFS_VERSION; newte->netid = strdup(logrec->re_netid); if (logrec->re_principal_name) newte->principal_name = strdup(logrec->re_principal_name); else newte->principal_name = NULL; newte->totalbytes = 0; newte->fh_u.fh = *(NFSLOG_GET_FHANDLE2(&args->la_from)); newte->pathname = (char *)malloc(strlen(path1) + strlen(path2) + 3); /* check for NULL malloc */ (void) sprintf(newte->pathname, "%s->%s", path1, path2); if (tpath1) { free(tpath1); free(tpath2); } return (newte); } static struct transentry * trans_symlink( nfslog_request_record *logrec, char *fhpath, char *path1) { struct transentry *newte; /* LINTED */ nfslog_symlinkargs *args = (nfslog_symlinkargs *)logrec->re_rpc_arg; /* LINTED */ nfsstat *res = (nfsstat *)logrec->re_rpc_res; char *tpath1 = NULL; if (*res != NFS_OK) return (NULL); if ((newte = create_te()) == NULL) return (NULL); if (!path1) { char *name = args->sla_from.da_name; fhandle_t *dfh = &args->sla_from.da_fhandle; path1 = tpath1 = nfslog_get_path(dfh, name, fhpath, "trans_symlink"); } newte->starttime = logrec->re_header.rh_timestamp; newte->lastupdate = logrec->re_header.rh_timestamp; newte->optype = TRANS_OPER_SYMLINK; newte->datatype = TRANS_DATATYPE_BINARY; newte->transoption = TRANS_OPTION_NOACTION; newte->pnb = netbufdup(&(logrec->re_ipaddr)); newte->uid = logrec->re_header.rh_uid; newte->nfsvers = NFS_VERSION; newte->netid = strdup(logrec->re_netid); if (logrec->re_principal_name) newte->principal_name = strdup(logrec->re_principal_name); else newte->principal_name = NULL; newte->totalbytes = 0; newte->fh_u.fh = *(NFSLOG_GET_FHANDLE2(&args->sla_from.da_fhandle)); newte->pathname = (char *)malloc(strlen(path1) + strlen(args->sla_tnm) + 3); (void) sprintf(newte->pathname, "%s->%s", path1, args->sla_tnm); if (tpath1) free(tpath1); return (newte); } static struct transentry * trans_read3( nfslog_request_record *logrec, struct nfslog_trans_file *tf, char *fhpath, char *path1) { struct transentry *newte; struct transentry *pte = NULL; /* LINTED */ nfslog_READ3args *args = (nfslog_READ3args *)logrec->re_rpc_arg; /* LINTED */ nfslog_READ3res *res = (nfslog_READ3res *)logrec->re_rpc_res; if (res->status != NFS3_OK) return (NULL); if ((newte = create_te()) == NULL) return (NULL); if (!path1) { fhandle_t *fh = NFSLOG_GET_FHANDLE3(&args->file); newte->pathname = nfslog_get_path(fh, NULL, fhpath, "trans_read3"); } else { newte->pathname = strdup(path1); } /* prep the struct for insertion */ newte->starttime = logrec->re_header.rh_timestamp; newte->lastupdate = logrec->re_header.rh_timestamp; newte->optype = TRANS_OPER_READ; newte->datatype = TRANS_DATATYPE_BINARY; newte->transoption = TRANS_OPTION_NOACTION; newte->pnb = netbufdup(&(logrec->re_ipaddr)); newte->uid = logrec->re_header.rh_uid; newte->nfsvers = NFS_V3; newte->netid = strdup(logrec->re_netid); if (logrec->re_principal_name) newte->principal_name = strdup(logrec->re_principal_name); else newte->principal_name = NULL; newte->totalbytes = res->nfslog_READ3res_u.ok.count; newte->fh_u.fh3 = args->file; if (res->nfslog_READ3res_u.ok.count < res->nfslog_READ3res_u.ok.filesize) { if (pte = insert_te(tf->te_list_v3_read, newte)) { /* free this since entry was found (not inserted) */ remove_te(newte); pte->totalbytes += res->nfslog_READ3res_u.ok.count; if (pte->lastupdate.tv_sec <= logrec->re_header.rh_timestamp.tv_sec) pte->lastupdate = logrec->re_header.rh_timestamp; if (pte->totalbytes < res->nfslog_READ3res_u.ok.filesize) { pte = NULL; /* prevent printing of log entry */ } } } else { pte = newte; /* print a log record - complete file read */ } return (pte); } static struct transentry * trans_write3( nfslog_request_record *logrec, struct nfslog_trans_file *tf, char *fhpath, char *path1) { struct transentry *newte; struct transentry *pte = NULL; /* LINTED */ nfslog_WRITE3args *args = (nfslog_WRITE3args *)logrec->re_rpc_arg; /* LINTED */ nfslog_WRITE3res *res = (nfslog_WRITE3res *)logrec->re_rpc_res; if (res->status != NFS3_OK) return (NULL); if ((newte = create_te()) == NULL) return (NULL); if (!path1) { fhandle_t *fh = NFSLOG_GET_FHANDLE3(&args->file); newte->pathname = nfslog_get_path(fh, NULL, fhpath, "trans_write3"); } else { newte->pathname = strdup(path1); } newte->starttime = logrec->re_header.rh_timestamp; newte->lastupdate = logrec->re_header.rh_timestamp; newte->optype = TRANS_OPER_WRITE; newte->datatype = TRANS_DATATYPE_BINARY; newte->transoption = TRANS_OPTION_NOACTION; newte->pnb = netbufdup(&(logrec->re_ipaddr)); newte->uid = logrec->re_header.rh_uid; newte->nfsvers = NFS_V3; newte->netid = strdup(logrec->re_netid); if (logrec->re_principal_name) newte->principal_name = strdup(logrec->re_principal_name); else newte->principal_name = NULL; newte->totalbytes = res->nfslog_WRITE3res_u.ok.count; newte->fh_u.fh3 = args->file; if (pte = insert_te(tf->te_list_v3_write, newte)) { /* * if the write would have increased the total byte count * over the filesize, then generate a log entry and remove * the write record and insert the new one. */ if (pte->totalbytes + res->nfslog_WRITE3res_u.ok.count > res->nfslog_WRITE3res_u.ok.filesize) { nfslog_print_trans_logentry(pte, tf); remove_te(pte); (void) insert_te(tf->te_list_v3_write, newte); pte = NULL; } else { /* free this since entry was found (not inserted) */ remove_te(newte); pte->totalbytes += res->nfslog_WRITE3res_u.ok.count; if (pte->lastupdate.tv_sec <= logrec->re_header.rh_timestamp.tv_sec) { pte->lastupdate = logrec->re_header.rh_timestamp; } pte = NULL; /* prevent printing of log entry */ } } return (pte); } static struct transentry * trans_setattr3( nfslog_request_record *logrec, struct nfslog_trans_file *tf, char *fhpath, char *path1) { struct transentry *newte; struct transentry *pte = NULL; /* LINTED */ nfslog_SETATTR3args *args = (nfslog_SETATTR3args *)logrec->re_rpc_arg; /* LINTED */ nfsstat3 *res = (nfsstat3 *)logrec->re_rpc_res; if (*res != NFS3_OK) return (NULL); if (!args->size.set_it) return (NULL); /* * should check the size of the file to see if it * is being truncated below current eof. if so * a record should be generated.... XXX */ if (args->size.size != 0) return (NULL); if ((newte = create_te()) == NULL) return (NULL); if (!path1) { fhandle_t *fh = NFSLOG_GET_FHANDLE3(&args->object); newte->pathname = nfslog_get_path(fh, NULL, fhpath, "trans_setattr3"); } else { newte->pathname = strdup(path1); } newte->starttime = logrec->re_header.rh_timestamp; newte->lastupdate = logrec->re_header.rh_timestamp; newte->optype = TRANS_OPER_SETATTR; newte->datatype = TRANS_DATATYPE_BINARY; newte->transoption = TRANS_OPTION_NOACTION; newte->pnb = netbufdup(&(logrec->re_ipaddr)); newte->uid = logrec->re_header.rh_uid; newte->nfsvers = NFS_V3; newte->netid = strdup(logrec->re_netid); if (logrec->re_principal_name) newte->principal_name = strdup(logrec->re_principal_name); else newte->principal_name = NULL; newte->totalbytes = 0; newte->fh_u.fh3 = args->object; if (pte = insert_te(tf->te_list_v3_write, newte)) { nfslog_print_trans_logentry(pte, tf); remove_te(pte); } if (pte = insert_te(tf->te_list_v3_read, newte)) { nfslog_print_trans_logentry(pte, tf); remove_te(pte); } return (newte); } static struct transentry * trans_create3( nfslog_request_record *logrec, struct nfslog_trans_file *tf, char *fhpath, char *path1) { struct transentry *newte; struct transentry *pte = NULL; /* LINTED */ nfslog_CREATE3args *args = (nfslog_CREATE3args *)logrec->re_rpc_arg; /* LINTED */ nfslog_CREATE3res *res = (nfslog_CREATE3res *)logrec->re_rpc_res; if (res->status != NFS3_OK) return (NULL); if ((newte = create_te()) == NULL) return (NULL); if (!path1) { newte->pathname = nfslog_get_path(NFSLOG_GET_FHANDLE3(&args->where.dir), args->where.name, fhpath, "trans_create3"); } else { newte->pathname = strdup(path1); } newte->starttime = logrec->re_header.rh_timestamp; newte->lastupdate = logrec->re_header.rh_timestamp; newte->optype = TRANS_OPER_CREATE; newte->datatype = TRANS_DATATYPE_BINARY; newte->transoption = TRANS_OPTION_NOACTION; newte->pnb = netbufdup(&(logrec->re_ipaddr)); newte->uid = logrec->re_header.rh_uid; newte->nfsvers = NFS_V3; newte->netid = strdup(logrec->re_netid); if (logrec->re_principal_name) newte->principal_name = strdup(logrec->re_principal_name); else newte->principal_name = NULL; if (!args->how.nfslog_createhow3_u.size.set_it) newte->totalbytes = 0; else newte->totalbytes = args->how.nfslog_createhow3_u.size.size; newte->fh_u.fh3 = args->where.dir; if (args->how.nfslog_createhow3_u.size.set_it) { if (pte = insert_te(tf->te_list_v3_write, newte)) { nfslog_print_trans_logentry(pte, tf); remove_te(pte); } if (pte = insert_te(tf->te_list_v3_read, newte)) { nfslog_print_trans_logentry(pte, tf); remove_te(pte); } } return (newte); } static struct transentry * trans_remove3( nfslog_request_record *logrec, struct nfslog_trans_file *tf, char *fhpath, char *path1) { struct transentry *newte; struct transentry *pte = NULL; /* LINTED */ nfslog_REMOVE3args *args = (nfslog_REMOVE3args *)logrec->re_rpc_arg; /* LINTED */ nfsstat3 *res = (nfsstat3 *)logrec->re_rpc_res; if (*res != NFS3_OK) return (NULL); if ((newte = create_te()) == NULL) return (NULL); if (!path1) { newte->pathname = nfslog_get_path(NFSLOG_GET_FHANDLE3(&args->object.dir), args->object.name, fhpath, "trans_remove3"); } else { newte->pathname = strdup(path1); } newte->starttime = logrec->re_header.rh_timestamp; newte->lastupdate = logrec->re_header.rh_timestamp; newte->optype = TRANS_OPER_REMOVE; newte->datatype = TRANS_DATATYPE_BINARY; newte->transoption = TRANS_OPTION_NOACTION; newte->pnb = netbufdup(&(logrec->re_ipaddr)); newte->uid = logrec->re_header.rh_uid; newte->nfsvers = NFS_V3; newte->netid = strdup(logrec->re_netid); if (logrec->re_principal_name) newte->principal_name = strdup(logrec->re_principal_name); else newte->principal_name = NULL; newte->totalbytes = 0; newte->fh_u.fh3 = args->object.dir; if (pte = insert_te(tf->te_list_v3_write, newte)) { nfslog_print_trans_logentry(pte, tf); remove_te(pte); } if (pte = insert_te(tf->te_list_v3_read, newte)) { nfslog_print_trans_logentry(pte, tf); remove_te(pte); } if (pte = insert_te(tf->te_list_v2_write, newte)) { nfslog_print_trans_logentry(pte, tf); remove_te(pte); } if (pte = insert_te(tf->te_list_v2_read, newte)) { nfslog_print_trans_logentry(pte, tf); remove_te(pte); } return (newte); } static struct transentry * trans_mkdir3( nfslog_request_record *logrec, char *fhpath, char *path1) { struct transentry *newte; /* LINTED */ nfslog_MKDIR3args *args = (nfslog_MKDIR3args *)logrec->re_rpc_arg; /* LINTED */ nfslog_MKDIR3res *res = (nfslog_MKDIR3res *)logrec->re_rpc_res; if (res->status != NFS3_OK) return (NULL); if ((newte = create_te()) == NULL) return (NULL); if (!path1) { newte->pathname = nfslog_get_path(NFSLOG_GET_FHANDLE3(&args->where.dir), args->where.name, fhpath, "trans_mkdir3"); } else { newte->pathname = strdup(path1); } newte->starttime = logrec->re_header.rh_timestamp; newte->lastupdate = logrec->re_header.rh_timestamp; newte->optype = TRANS_OPER_MKDIR; newte->datatype = TRANS_DATATYPE_BINARY; newte->transoption = TRANS_OPTION_NOACTION; newte->pnb = netbufdup(&(logrec->re_ipaddr)); newte->uid = logrec->re_header.rh_uid; newte->nfsvers = NFS_V3; newte->netid = strdup(logrec->re_netid); if (logrec->re_principal_name) newte->principal_name = strdup(logrec->re_principal_name); else newte->principal_name = NULL; newte->totalbytes = 0; newte->fh_u.fh3 = args->where.dir; return (newte); } static struct transentry * trans_rmdir3( nfslog_request_record *logrec, char *fhpath, char *path1) { struct transentry *newte; /* LINTED */ nfslog_RMDIR3args *args = (nfslog_RMDIR3args *)logrec->re_rpc_arg; /* LINTED */ nfsstat3 *res = (nfsstat3 *)logrec->re_rpc_res; if (*res != NFS3_OK) return (NULL); if ((newte = create_te()) == NULL) return (NULL); if (!path1) { newte->pathname = nfslog_get_path(NFSLOG_GET_FHANDLE3(&args->object.dir), args->object.name, fhpath, "trans_rmdir3"); } else { newte->pathname = strdup(path1); } newte->starttime = logrec->re_header.rh_timestamp; newte->lastupdate = logrec->re_header.rh_timestamp; newte->optype = TRANS_OPER_RMDIR; newte->datatype = TRANS_DATATYPE_BINARY; newte->transoption = TRANS_OPTION_NOACTION; newte->pnb = netbufdup(&(logrec->re_ipaddr)); newte->uid = logrec->re_header.rh_uid; newte->nfsvers = NFS_V3; newte->netid = strdup(logrec->re_netid); if (logrec->re_principal_name) newte->principal_name = strdup(logrec->re_principal_name); else newte->principal_name = NULL; newte->totalbytes = 0; newte->fh_u.fh3 = args->object.dir; return (newte); } static struct transentry * trans_rename3( nfslog_request_record *logrec, struct nfslog_trans_file *tf, char *fhpath, char *path1, char *path2) { struct transentry *newte; struct transentry *pte = NULL; /* LINTED */ nfslog_RENAME3args *args = (nfslog_RENAME3args *)logrec->re_rpc_arg; /* LINTED */ nfsstat3 *res = (nfsstat3 *)logrec->re_rpc_res; char *tpath1 = NULL; char *tpath2 = NULL; if (*res != NFS3_OK) return (NULL); if ((newte = create_te()) == NULL) return (NULL); if (!path1) { path1 = tpath1 = nfslog_get_path(NFSLOG_GET_FHANDLE3(&args->from.dir), args->from.name, fhpath, "trans_rename3 from"); path2 = tpath2 = nfslog_get_path(NFSLOG_GET_FHANDLE3(&args->to.dir), args->to.name, fhpath, "trans_rename3 to"); } newte->pathname = path1; /* no need to strdup here */ newte->starttime = logrec->re_header.rh_timestamp; newte->lastupdate = logrec->re_header.rh_timestamp; newte->optype = TRANS_OPER_RENAME; newte->datatype = TRANS_DATATYPE_BINARY; newte->transoption = TRANS_OPTION_NOACTION; newte->pnb = netbufdup(&(logrec->re_ipaddr)); newte->uid = logrec->re_header.rh_uid; newte->nfsvers = NFS_V3; newte->netid = strdup(logrec->re_netid); if (logrec->re_principal_name) newte->principal_name = strdup(logrec->re_principal_name); else newte->principal_name = NULL; newte->totalbytes = 0; newte->fh_u.fh3 = args->from.dir; /* switch path names for the file for renames */ if (pte = insert_te(tf->te_list_v3_write, newte)) { free(pte->pathname); pte->pathname = strdup(path2); } if (pte = insert_te(tf->te_list_v3_read, newte)) { free(pte->pathname); pte->pathname = strdup(path2); } if (pte = insert_te(tf->te_list_v2_write, newte)) { free(pte->pathname); pte->pathname = strdup(path2); } if (pte = insert_te(tf->te_list_v2_read, newte)) { free(pte->pathname); pte->pathname = strdup(path2); } newte->pathname = (char *)malloc(strlen(path1) + strlen(path2) + 3); /* check for NULL malloc */ (void) sprintf(newte->pathname, "%s->%s", path1, path2); if (tpath1) { free(tpath1); free(tpath2); } return (newte); } static struct transentry * trans_mknod3( nfslog_request_record *logrec, char *fhpath, char *path1) { struct transentry *newte; /* LINTED */ nfslog_MKNOD3args *args = (nfslog_MKNOD3args *)logrec->re_rpc_arg; /* LINTED */ nfslog_MKNOD3res *res = (nfslog_MKNOD3res *)logrec->re_rpc_res; if (res->status != NFS3_OK) return (NULL); if ((newte = create_te()) == NULL) return (NULL); if (!path1) { newte->pathname = nfslog_get_path(NFSLOG_GET_FHANDLE3(&args->where.dir), args->where.name, fhpath, "trans_mknod3"); } else { newte->pathname = strdup(path1); } newte->starttime = logrec->re_header.rh_timestamp; newte->lastupdate = logrec->re_header.rh_timestamp; newte->optype = TRANS_OPER_MKNOD; newte->datatype = TRANS_DATATYPE_BINARY; newte->transoption = TRANS_OPTION_NOACTION; newte->pnb = netbufdup(&(logrec->re_ipaddr)); newte->uid = logrec->re_header.rh_uid; newte->nfsvers = NFS_V3; newte->netid = strdup(logrec->re_netid); if (logrec->re_principal_name) newte->principal_name = strdup(logrec->re_principal_name); else newte->principal_name = NULL; newte->totalbytes = 0; newte->fh_u.fh3 = args->where.dir; return (newte); } static struct transentry * trans_link3( nfslog_request_record *logrec, char *fhpath, char *path1, char *path2) { struct transentry *newte; /* LINTED */ nfslog_LINK3args *args = (nfslog_LINK3args *)logrec->re_rpc_arg; /* LINTED */ nfsstat3 *res = (nfsstat3 *)logrec->re_rpc_res; char *tpath1 = NULL; char *tpath2 = NULL; if (*res != NFS3_OK) return (NULL); if ((newte = create_te()) == NULL) return (NULL); if (!path1) { tpath1 = nfslog_get_path(NFSLOG_GET_FHANDLE3(&args->file), NULL, fhpath, "trans_link3 from"); tpath2 = nfslog_get_path(NFSLOG_GET_FHANDLE3(&args->link.dir), args->link.name, fhpath, "trans_link3 to"); path1 = tpath1; path2 = tpath2; } newte->starttime = logrec->re_header.rh_timestamp; newte->lastupdate = logrec->re_header.rh_timestamp; newte->optype = TRANS_OPER_LINK; newte->datatype = TRANS_DATATYPE_BINARY; newte->transoption = TRANS_OPTION_NOACTION; newte->pnb = netbufdup(&(logrec->re_ipaddr)); newte->uid = logrec->re_header.rh_uid; newte->nfsvers = NFS_V3; newte->netid = strdup(logrec->re_netid); if (logrec->re_principal_name) newte->principal_name = strdup(logrec->re_principal_name); else newte->principal_name = NULL; newte->totalbytes = 0; newte->fh_u.fh3 = args->file; newte->pathname = (char *)malloc(strlen(path1) + strlen(path2) + 3); /* check for NULL malloc */ (void) sprintf(newte->pathname, "%s->%s", path1, path2); if (tpath1) { free(tpath1); free(tpath2); } return (newte); } static struct transentry * trans_symlink3( nfslog_request_record *logrec, char *fhpath, char *path1) { struct transentry *newte; /* LINTED */ nfslog_SYMLINK3args *args = (nfslog_SYMLINK3args *)logrec->re_rpc_arg; /* LINTED */ nfslog_SYMLINK3res *res = (nfslog_SYMLINK3res *)logrec->re_rpc_res; char *name; if (res->status != NFS3_OK) return (NULL); if ((newte = create_te()) == NULL) return (NULL); if (path1) { name = strdup(path1); } else { name = nfslog_get_path(NFSLOG_GET_FHANDLE3(&args->where.dir), args->where.name, fhpath, "trans_symlink3"); } newte->starttime = logrec->re_header.rh_timestamp; newte->lastupdate = logrec->re_header.rh_timestamp; newte->optype = TRANS_OPER_SYMLINK; newte->datatype = TRANS_DATATYPE_BINARY; newte->transoption = TRANS_OPTION_NOACTION; newte->pnb = netbufdup(&(logrec->re_ipaddr)); newte->uid = logrec->re_header.rh_uid; newte->nfsvers = NFS_V3; newte->netid = strdup(logrec->re_netid); if (logrec->re_principal_name) newte->principal_name = strdup(logrec->re_principal_name); else newte->principal_name = NULL; newte->totalbytes = 0; newte->fh_u.fh3 = args->where.dir; newte->pathname = (char *)malloc(strlen(name) + strlen(args->symlink_data) + 3); /* check for NULL malloc */ (void) sprintf(newte->pathname, "%s->%s", name, args->symlink_data); free(name); return (newte); } /* * nfslog_process_trans_rec - processes the record in the buffer and outputs * to the trans log. * Return 0 for success, errno else. */ int nfslog_process_trans_rec(void *transcookie, nfslog_request_record *logrec, char *fhpath, char *path1, char *path2) { struct transentry *pte = NULL; struct nfslog_trans_file *tf = (struct nfslog_trans_file *)transcookie; /* ignore programs other than nfs */ if (logrec->re_header.rh_prognum != NFS_PROGRAM) return (0); /* update the timestamp for use later in the timeout sequences */ if (tf->lasttrans_timestamp.tv_sec < logrec->re_header.rh_timestamp.tv_sec) tf->lasttrans_timestamp = logrec->re_header.rh_timestamp; /* current time of this processing */ tf->last_trans_read = time(0); /* ignore anything that is not a read or write */ switch (logrec->re_header.rh_version) { case NFS_VERSION: switch (logrec->re_header.rh_procnum) { case RFS_READ: if (tf->trans_to_log & TRANSTOLOG_OPER_READ) pte = trans_read(logrec, tf, fhpath, path1); break; case RFS_WRITE: if (tf->trans_to_log & TRANSTOLOG_OPER_WRITE) pte = trans_write(logrec, tf, fhpath, path1); break; case RFS_SETATTR: if (tf->trans_to_log & TRANSTOLOG_OPER_SETATTR) pte = trans_setattr(logrec, tf, fhpath, path1); break; case RFS_REMOVE: if (tf->trans_to_log & TRANSTOLOG_OPER_REMOVE) pte = trans_remove(logrec, tf, fhpath, path1); break; case RFS_MKDIR: if (tf->trans_to_log & TRANSTOLOG_OPER_MKDIR) pte = trans_mkdir(logrec, fhpath, path1); break; case RFS_RMDIR: if (tf->trans_to_log & TRANSTOLOG_OPER_RMDIR) pte = trans_rmdir(logrec, fhpath, path1); break; case RFS_CREATE: if (tf->trans_to_log & TRANSTOLOG_OPER_CREATE) pte = trans_create(logrec, tf, fhpath, path1); break; case RFS_RENAME: if (tf->trans_to_log & TRANSTOLOG_OPER_RENAME) pte = trans_rename(logrec, tf, fhpath, path1, path2); break; case RFS_LINK: if (tf->trans_to_log & TRANSTOLOG_OPER_LINK) pte = trans_link(logrec, fhpath, path1, path2); break; case RFS_SYMLINK: if (tf->trans_to_log & TRANSTOLOG_OPER_SYMLINK) pte = trans_symlink(logrec, fhpath, path1); break; default: break; } break; case NFS_V3: switch (logrec->re_header.rh_procnum) { case NFSPROC3_READ: if (tf->trans_to_log & TRANSTOLOG_OPER_READ) pte = trans_read3(logrec, tf, fhpath, path1); break; case NFSPROC3_WRITE: if (tf->trans_to_log & TRANSTOLOG_OPER_WRITE) pte = trans_write3(logrec, tf, fhpath, path1); break; case NFSPROC3_SETATTR: if (tf->trans_to_log & TRANSTOLOG_OPER_SETATTR) pte = trans_setattr3(logrec, tf, fhpath, path1); break; case NFSPROC3_REMOVE: if (tf->trans_to_log & TRANSTOLOG_OPER_REMOVE) pte = trans_remove3(logrec, tf, fhpath, path1); break; case NFSPROC3_MKDIR: if (tf->trans_to_log & TRANSTOLOG_OPER_MKDIR) pte = trans_mkdir3(logrec, fhpath, path1); break; case NFSPROC3_RMDIR: if (tf->trans_to_log & TRANSTOLOG_OPER_RMDIR) pte = trans_rmdir3(logrec, fhpath, path1); break; case NFSPROC3_CREATE: if (tf->trans_to_log & TRANSTOLOG_OPER_CREATE) pte = trans_create3(logrec, tf, fhpath, path1); break; case NFSPROC3_RENAME: if (tf->trans_to_log & TRANSTOLOG_OPER_RENAME) pte = trans_rename3(logrec, tf, fhpath, path1, path2); break; case NFSPROC3_MKNOD: if (tf->trans_to_log & TRANSTOLOG_OPER_MKNOD) pte = trans_mknod3(logrec, fhpath, path1); break; case NFSPROC3_LINK: if (tf->trans_to_log & TRANSTOLOG_OPER_LINK) pte = trans_link3(logrec, fhpath, path1, path2); break; case NFSPROC3_SYMLINK: if (tf->trans_to_log & TRANSTOLOG_OPER_SYMLINK) pte = trans_symlink3(logrec, fhpath, path1); break; default: break; } break; default: break; } if (pte != NULL) { nfslog_print_trans_logentry(pte, tf); remove_te(pte); } return (0); } static void nfslog_print_trans_logentry(struct transentry *pte, struct nfslog_trans_file *tf) { char *remotehost; char datatype; char transoption; char *optype; char *prin; int prinid; char nfs_ident[32]; remotehost = addrtoname(pte->pnb->buf); datatype = (pte->datatype == TRANS_DATATYPE_BINARY ? 'b' : 'a'); transoption = (pte->transoption == TRANS_OPTION_NOACTION ? '_' : '?'); if (tf->trans_output_type == TRANSLOG_BASIC) { (void) strcpy(nfs_ident, "nfs"); } else { (void) strcpy(nfs_ident, (pte->nfsvers == NFS_V3 ? "nfs3-" : "nfs-")); (void) strcat(nfs_ident, pte->netid); } switch (pte->optype) { case TRANS_OPER_READ: optype = (tf->trans_output_type == TRANSLOG_EXTENDED ? "read" : "o"); break; case TRANS_OPER_WRITE: optype = (tf->trans_output_type == TRANSLOG_EXTENDED ? "write" : "i"); break; case TRANS_OPER_REMOVE: optype = (tf->trans_output_type == TRANSLOG_EXTENDED ? "remove" : "?"); break; case TRANS_OPER_MKDIR: optype = (tf->trans_output_type == TRANSLOG_EXTENDED ? "mkdir" : "?"); break; case TRANS_OPER_CREATE: optype = (tf->trans_output_type == TRANSLOG_EXTENDED ? "create" : "?"); break; case TRANS_OPER_RMDIR: optype = (tf->trans_output_type == TRANSLOG_EXTENDED ? "rmdir" : "?"); break; case TRANS_OPER_SETATTR: optype = (tf->trans_output_type == TRANSLOG_EXTENDED ? "setattr" : "?"); break; case TRANS_OPER_RENAME: optype = (tf->trans_output_type == TRANSLOG_EXTENDED ? "rename" : "?"); break; case TRANS_OPER_MKNOD: optype = (tf->trans_output_type == TRANSLOG_EXTENDED ? "mknod" : "?"); break; case TRANS_OPER_LINK: optype = (tf->trans_output_type == TRANSLOG_EXTENDED ? "link" : "?"); break; case TRANS_OPER_SYMLINK: optype = (tf->trans_output_type == TRANSLOG_EXTENDED ? "symlink" : "?"); break; default: optype = "?"; break; } if (strcmp(pte->principal_name, "") == 0) { prinid = 0; prin = "*"; } else { prinid = 1; prin = pte->principal_name; } (void) fprintf(tf->fp, "%.24s %d %s %d %s %c %c %s %c %ld %s %d %s\n", ctime((time_t *)&pte->starttime.tv_sec), pte->lastupdate.tv_sec - pte->starttime.tv_sec, remotehost, (uint32_t)pte->totalbytes, pte->pathname, datatype, transoption, optype, 'r', /* anonymous == 'a', guest == 'g', real == 'r'), */ pte->uid, nfs_ident, /* authenticated - fill in kerb/security? */ prinid, /* authenticated ? authuser : "*" */ prin); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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) 1999-2000 by Sun Microsystems, Inc. * All rights reserved. */ /* * Postprocessor for NFS server logging. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fhtab.h" #include "nfslogd.h" #include "buffer_list.h" #include "../lib/nfslog_config.h" #include "../lib/nfslogtab.h" enum pidfile_operation { PID_STARTUP, PID_SHUTDOWN }; static int nfslogtab_deactivate_after_boot(void); static int nfslogtab_remove(struct buffer_ent **, struct buffer_ent **, boolean_t); static int cycle_logs(nfsl_config_t *, int); static void enable_logcycling(void); static int process_pidfile(enum pidfile_operation); static void short_cleanup(void); static void full_cleanup(void); static void transactions_timeout(nfsl_config_t *); static void close_all_translogs(nfsl_config_t *); int cycle_log(char *, int); static boolean_t is_cycle_needed(char *, void **, boolean_t, int *); /* * Configuration information. */ int debug = 0; boolean_t test = B_FALSE; time_t mapping_update_interval = MAPPING_UPDATE_INTERVAL; /* prune_timeout measures how old a database entry must be to be pruned */ time_t prune_timeout = (SECSPERHOUR * 7 * 24); int max_logs_preserve = MAX_LOGS_PRESERVE; uint_t idle_time = IDLE_TIME; static mode_t Umask = NFSLOG_UMASK; static long cycle_frequency = CYCLE_FREQUENCY; /* prune_frequency measures how often should prune_dbs be called */ static long prune_frequency = (SECSPERHOUR * 24); static int min_size = MIN_PROCESSING_SIZE; static volatile bool_t need2cycle = FALSE; static volatile bool_t need2prune = FALSE; boolean_t keep_running = B_TRUE; boolean_t quick_cleaning = B_FALSE; /*ARGSUSED*/ int main(int argc, char **argv) { struct rlimit rl; int error = 0; char *defp; pid_t pid; timestruc_t logtab_update; time_t process_start, last_prune = time(0); time_t last_cycle = time(0); /* last time logs were cycled */ int processed, buffers_processed; struct buffer_ent *buffer_list = NULL, *bep, *next; nfsl_config_t *config_list = NULL; char *fhtable_to_prune = NULL; /* * Check to make sure user is root. */ if (geteuid() != 0) { (void) fprintf(stderr, gettext("%s must be run as root\n"), argv[0]); exit(1); } /* * Read defaults file. */ if (defopen(NFSLOG_OPTIONS_FILE) == 0) { if ((defp = defread("DEBUG=")) != NULL) { debug = atoi(defp); if (debug > 0) (void) printf("debug=%d\n", debug); } if ((defp = defread("TEST=")) != NULL) { if (strcmp(defp, "TRUE") == 0) test = B_TRUE; if (debug > 0) { if (test) (void) printf("test=TRUE\n"); else (void) printf("test=FALSE\n"); } } /* * Set Umask for log and fhtable creation. */ if ((defp = defread("UMASK=")) != NULL) { if (sscanf(defp, "%lo", &Umask) != 1) Umask = NFSLOG_UMASK; } /* * Minimum size buffer should reach before processing. */ if ((defp = defread("MIN_PROCESSING_SIZE=")) != NULL) { min_size = atoi(defp); if (debug > 0) (void) printf("min_size=%d\n", min_size); } /* * Number of seconds the daemon should * sleep waiting for more work. */ if ((defp = defread("IDLE_TIME=")) != NULL) { idle_time = (uint_t)atoi(defp); if (debug > 0) (void) printf("idle_time=%d\n", idle_time); } /* * Maximum number of logs to preserve. */ if ((defp = defread("MAX_LOGS_PRESERVE=")) != NULL) { max_logs_preserve = atoi(defp); if (debug > 0) { (void) printf("max_logs_preserve=%d\n", max_logs_preserve); } } /* * Frequency of atime updates. */ if ((defp = defread("MAPPING_UPDATE_INTERVAL=")) != NULL) { mapping_update_interval = atoi(defp); if (debug > 0) { (void) printf("mapping_update_interval=%ld\n", mapping_update_interval); } } /* * Time to remove entries */ if ((defp = defread("PRUNE_TIMEOUT=")) != NULL) { /* * Prune timeout is in hours but we want * deal with the time in seconds internally. */ prune_timeout = atoi(defp); prune_timeout *= SECSPERHOUR; if (prune_timeout < prune_frequency) prune_frequency = prune_timeout; if (debug > 0) { (void) printf("prune_timeout=%ld\n", prune_timeout); } } /* * fhtable to prune when start (for debug/test purposes) */ if ((defp = defread("PRUNE_FHTABLE=")) != NULL) { /* * Specify full pathname of fhtable to prune before * any processing is to be done */ if (fhtable_to_prune = malloc(strlen(defp) + 1)) { (void) strcpy(fhtable_to_prune, defp); if (debug > 0) { (void) printf("fhtable to prune=%s\n", fhtable_to_prune); } } else { syslog(LOG_ERR, gettext( "malloc fhtable_to_prune error %s\n"), strerror(errno)); } } /* * Log cycle frequency. */ if ((defp = defread("CYCLE_FREQUENCY=")) != NULL) { cycle_frequency = atol(defp); if (debug > 0) { (void) printf("cycle_frequency=%ld\n", cycle_frequency); } } /* * defopen of NULL closes the open defaults file. */ (void) defopen((char *)NULL); } if (Umask > ((mode_t)0777)) Umask = NFSLOG_UMASK; (void) umask(Umask); if (getrlimit(RLIMIT_FSIZE, &rl) < 0) { error = errno; (void) fprintf(stderr, gettext( "getrlimit failed error is %d - %s\n"), error, strerror(error)); exit(1); } if (min_size < 0 || min_size > rl.rlim_cur) { (void) fprintf(stderr, gettext( "MIN_PROCESSING_SIZE out of range, should be >= 0 and " "< %d. Check %s.\n"), rl.rlim_cur, NFSLOG_OPTIONS_FILE); exit(1); } if (idle_time > INT_MAX) { (void) fprintf(stderr, gettext( "IDLE_TIME out of range, should be >= 0 and " "< %d. Check %s.\n"), INT_MAX, NFSLOG_OPTIONS_FILE); exit(1); } if (max_logs_preserve < 0 || max_logs_preserve > INT_MAX) { (void) fprintf(stderr, gettext( "MAX_LOGS_PRESERVE out of range, should be >= 0 and " "< %d. Check %s.\n"), INT_MAX, NFSLOG_OPTIONS_FILE); exit(1); } if (mapping_update_interval < 0|| mapping_update_interval > INT_MAX) { (void) fprintf(stderr, gettext( "MAPPING_UPDATE_INTERVAL out of range, " "should be >= 0 and " "< %d. Check %s.\n"), INT_MAX, NFSLOG_OPTIONS_FILE); exit(1); } if (cycle_frequency < 0 || cycle_frequency > INT_MAX) { (void) fprintf(stderr, gettext( "CYCLE_FREQUENCY out of range, should be >= 0 and " "< %d. Check %s.\n"), INT_MAX, NFSLOG_OPTIONS_FILE); exit(1); } /* get value in seconds */ cycle_frequency = cycle_frequency * 60 * 60; /* * If we dump core, it will be /core */ if (chdir("/") < 0) (void) fprintf(stderr, gettext("chdir /: %s"), strerror(errno)); /* * Config errors to stderr */ nfsl_errs_to_syslog = B_FALSE; #ifndef DEBUG pid = fork(); if (pid == -1) { (void) fprintf(stderr, gettext("%s: fork failure\n"), argv[0]); exit(1); } if (pid != 0) exit(0); /* * Config errors to syslog */ nfsl_errs_to_syslog = B_TRUE; #endif /* DEBUG */ (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif (void) textdomain(TEXT_DOMAIN); /* * Check to see if nfslogd is already running. */ if (process_pidfile(PID_STARTUP) != 0) { exit(1); } (void) sigset(SIGUSR1, (void (*)(int))enable_logcycling); (void) sigset(SIGHUP, (void (*)(int))full_cleanup); (void) sigset(SIGTERM, (void(*)(int))short_cleanup); #ifndef DEBUG /* * Close existing file descriptors, open "/dev/null" as * standard input, output, and error, and detach from * controlling terminal. */ if (!debug && !test) { closefrom(0); (void) open("/dev/null", O_RDONLY); (void) open("/dev/null", O_WRONLY); (void) dup(1); } (void) setsid(); #endif /* DEBUG */ openlog(argv[0], LOG_PID, LOG_DAEMON); public_fh.fh_len = NFS_FHMAXDATA; public_fh.fh_xlen = NFS_FHMAXDATA; /* * Call once at startup to handle the nfslogtab */ if (nfslogtab_deactivate_after_boot() == -1) exit(1); /* * Get a list of buffers that need to be processed. */ if (error = getbuffer_list(&buffer_list, &logtab_update)) { syslog(LOG_ERR, gettext("Could not read %s: %s"), NFSLOGTAB, strerror(error)); goto done; } /* * Get the configuration list. */ if (error = nfsl_getconfig_list(&config_list)) { syslog(LOG_ERR, gettext( "Could not obtain configuration list: %s"), strerror(error)); goto done; } /* * loop to process the work being generated by the NFS server */ while (keep_running) { buffers_processed = 0; (void) checkbuffer_list(&buffer_list, &logtab_update); while (buffer_list == NULL) { /* * Nothing to do */ (void) sleep(idle_time); if (!keep_running) { /* * We have been interrupted and asked to * flush our transactions and exit. */ close_all_translogs(config_list); goto done; } (void) checkbuffer_list(&buffer_list, &logtab_update); } process_start = time(0); if (error = nfsl_checkconfig_list(&config_list, NULL)) { syslog(LOG_ERR, gettext( "Could not update configuration list: %s"), strerror(error)); nfsl_freeconfig_list(&config_list); goto done; } if (difftime(time(0), last_cycle) > cycle_frequency) need2cycle = TRUE; if (need2cycle) { error = cycle_logs(config_list, max_logs_preserve); if (error) { syslog(LOG_WARNING, gettext( "One or more logfiles couldn't be cycled, " "continuing regular processing")); } need2cycle = FALSE; last_cycle = time(0); } if (difftime(time(0), last_prune) > prune_frequency) need2prune = TRUE; if (need2prune || fhtable_to_prune) { error = prune_dbs(fhtable_to_prune); if (error) { syslog(LOG_WARNING, gettext( "Error in cleaning database files")); } need2prune = FALSE; last_prune = time(0); /* After the first time, use the normal procedure */ free(fhtable_to_prune); fhtable_to_prune = NULL; } for (bep = buffer_list; bep != NULL; bep = next) { next = bep->be_next; processed = 0; error = process_buffer(bep, &config_list, min_size, idle_time, &processed); if (error == 0 && processed) { if (bep->be_error) { syslog(LOG_ERR, gettext( "Buffer file '%s' " "processed successfully."), bep->be_name); } error = nfslogtab_remove(&buffer_list, &bep, B_FALSE); } else if (error == ENOENT) { syslog(LOG_ERR, gettext("Removed entry" "\t\"%s\t%s\t%d\" from %s"), bep->be_name, bep->be_sharepnt->se_name, bep->be_sharepnt->se_state, NFSLOGTAB); error = nfslogtab_remove(&buffer_list, &bep, B_TRUE); } else if (error && error != bep->be_error) { /* * An error different from what we've reported * before occured. */ syslog(LOG_ERR, gettext( "Cannot process buffer file '%s' - " "will retry on every iteration."), bep->be_name); } if (bep != NULL) bep->be_error = error; buffers_processed += processed; } transactions_timeout(config_list); if (keep_running) { uint_t process_time; /* * Sleep idle_time minus however long it took us * to process the buffers. */ process_time = (uint_t)(difftime(time(0), process_start)); if (process_time < idle_time) (void) sleep(idle_time - process_time); } } done: /* * Make sure to clean house before we exit */ close_all_translogs(config_list); free_buffer_list(&buffer_list); nfsl_freeconfig_list(&config_list); (void) process_pidfile(PID_SHUTDOWN); return (error); } static void short_cleanup(void) { if (debug) { (void) fprintf(stderr, "SIGTERM received, setting state to terminate...\n"); } quick_cleaning = B_TRUE; keep_running = B_FALSE; } static void full_cleanup(void) { if (debug) { (void) fprintf(stderr, "SIGHUP received, setting state to shutdown...\n"); } quick_cleaning = keep_running = B_FALSE; } /* * Removes nfslogtab entries matching the specified buffer_ent, * if 'inactive_only' is set, then only inactive entries are removed. * The buffer_list and sharepoint list entries are removed appropriately. * Returns 0 on success, error otherwise. */ static int nfslogtab_remove( struct buffer_ent **buffer_list, struct buffer_ent **bep, boolean_t allstates) { FILE *fd; int error = 0; struct sharepnt_ent *sep, *next; fd = fopen(NFSLOGTAB, "r+"); rewind(fd); if (fd == NULL) { error = errno; syslog(LOG_ERR, gettext("%s - %s\n"), NFSLOGTAB, strerror(error)); return (error); } if (lockf(fileno(fd), F_LOCK, 0L) < 0) { error = errno; syslog(LOG_ERR, gettext("cannot lock %s - %s\n"), NFSLOGTAB, strerror(error)); (void) fclose(fd); return (error); } for (sep = (*bep)->be_sharepnt; sep != NULL; sep = next) { next = sep->se_next; if (!allstates && sep->se_state == LES_ACTIVE) continue; if (error = logtab_rement(fd, (*bep)->be_name, sep->se_name, NULL, sep->se_state)) { syslog(LOG_ERR, gettext("cannot update %s\n"), NFSLOGTAB); error = EIO; goto errout; } remove_sharepnt_ent(&((*bep)->be_sharepnt), sep); } if ((*bep)->be_sharepnt == NULL) { /* * All sharepoints were removed from NFSLOGTAB. * Remove this buffer from our list. */ remove_buffer_ent(buffer_list, *bep); *bep = NULL; } errout: (void) fclose(fd); return (error); } /* * Deactivates entries if nfslogtab is older than the boot time. */ static int nfslogtab_deactivate_after_boot(void) { FILE *fd; int error = 0; fd = fopen(NFSLOGTAB, "r+"); if (fd == NULL) { error = errno; if (error != ENOENT) { syslog(LOG_ERR, gettext("%s: %s\n"), NFSLOGTAB, strerror(error)); return (-1); } return (0); } if (lockf(fileno(fd), F_LOCK, 0L) < 0) { error = errno; syslog(LOG_ERR, gettext("cannot lock %s: %s\n"), NFSLOGTAB, strerror(error)); (void) fclose(fd); return (-1); } if (logtab_deactivate_after_boot(fd) == -1) { syslog(LOG_ERR, gettext( "Cannot deactivate all entries in %s\n"), NFSLOGTAB); (void) fclose(fd); return (-1); } (void) fclose(fd); return (0); } /* * Enables the log file cycling flag. */ static void enable_logcycling(void) { need2cycle = TRUE; } /* * Cycle all log files that have been active since the last cycling. * This means it's not simply listed in the configuration file, but * there's information associated with it. */ static int cycle_logs(nfsl_config_t *listp, int max_logs_preserve) { nfsl_config_t *clp; void *processed_list = NULL; int error = 0, total_errors = 0; for (clp = listp; clp != NULL; clp = clp->nc_next) { error = 0; /* * Process transpath log. */ if (clp->nc_logpath) { if (is_cycle_needed(clp->nc_logpath, &processed_list, B_FALSE, &error)) { if (clp->nc_transcookie != NULL) { nfslog_close_transactions( &clp->nc_transcookie); assert(clp->nc_transcookie == NULL); } error = cycle_log(clp->nc_logpath, max_logs_preserve); } else if (error) goto errout; } total_errors += error; /* * Process elfpath log. */ if (clp->nc_rpclogpath) { if (is_cycle_needed(clp->nc_rpclogpath, &processed_list, B_FALSE, &error)) { error = cycle_log(clp->nc_rpclogpath, max_logs_preserve); } else if (error) goto errout; } total_errors += error; } errout: /* * Free the list of processed entries. */ (void) is_cycle_needed(NULL, &processed_list, B_TRUE, &error); return (total_errors); } /* * Returns TRUE if this log has not yet been cycled, FALSE otherwise. * '*head' points to the list of entries that have been processed. * If this is a new entry, it gets inserted at the beginning of the * list, and returns TRUE. * * The list is freed if 'need2free' is set, and returns FALSE. * Sets 'error' on failure, and returns FALSE. */ static boolean_t is_cycle_needed(char *path, void **list, boolean_t need2free, int *error) { struct list { char *log; struct list *next; } *head, *next, *p; head = (struct list *)(*list); if (need2free) { /* * Free the list and return */ for (p = head; p != NULL; p = next) { next = p->next; free(p); } head = NULL; return (B_FALSE); } assert(path != NULL); *error = 0; for (p = head; p != NULL; p = p->next) { /* * Have we seen this before? */ if (strcmp(p->log, path) == 0) return (B_FALSE); } /* * Add it to the list */ if ((p = (struct list *)malloc(sizeof (*p))) == NULL) { *error = ENOMEM; syslog(LOG_ERR, gettext("Cannot allocate memory.")); return (B_FALSE); } p->log = path; p->next = head; head = p; return (B_TRUE); } /* * cycle given log file. */ int cycle_log(char *filename, int max_logs_preserve) { int i; char *file_1; char *file_2; int error = 0; struct stat st; if (max_logs_preserve == 0) return (0); if (stat(filename, &st) == -1) { if (errno == ENOENT) { /* * Nothing to cycle. */ return (0); } return (errno); } file_1 = (char *)malloc(PATH_MAX); file_2 = (char *)malloc(PATH_MAX); for (i = max_logs_preserve - 2; i >= 0; i--) { (void) sprintf(file_1, "%s.%d", filename, i); (void) sprintf(file_2, "%s.%d", filename, (i + 1)); if (rename(file_1, file_2) == -1) { error = errno; if (error != ENOENT) { syslog(LOG_ERR, gettext( "cycle_log: can not rename %s to %s: %s"), file_1, file_2, strerror(error)); goto out; } } } (void) sprintf(file_1, "%s.0", filename); if (rename(filename, file_1) == -1) { error = errno; if (error != ENOENT) { syslog(LOG_ERR, gettext( "cycle_log: can not rename %s to %s: %s"), filename, file_1, strerror(error)); goto out; } } error = 0; out: free(file_1); free(file_2); return (error); } /* * If operation = PID_STARTUP then checks the nfslogd.pid file, it is opened * if it exists, read and the pid is checked for an active process. If no * active process is found, the pid of this process is written to the file, * and 0 is returned, otherwise non-zero error is returned. * * If operation = PID_SHUTDOWN then removes the nfslogd.pid file and 0 is * returned. */ static int process_pidfile(enum pidfile_operation op) { int fd, read_count; int error = 0; pid_t pid, mypid; char *PidFile = NFSLOGD_PIDFILE; int open_flags; if (op == PID_STARTUP) open_flags = O_RDWR | O_CREAT; else { assert(op == PID_SHUTDOWN); open_flags = O_RDWR; } if ((fd = open(PidFile, open_flags, 0600)) < 0) { error = errno; if (error == ENOENT && op == PID_SHUTDOWN) { /* * We were going to remove it anyway */ error = 0; goto out; } (void) fprintf(stderr, gettext( "cannot open or create pid file %s\n"), PidFile); goto out; } if (lockf(fd, F_LOCK, 0) < 0) { error = errno; (void) fprintf(stderr, gettext( "Cannot lock %s - %s\n"), PidFile, strerror(error)); goto out; } if ((read_count = read(fd, &pid, sizeof (pid))) < 0) { error = errno; (void) fprintf(stderr, gettext( "Can not read from file %s - %s\n"), PidFile, strerror(error)); } mypid = getpid(); if (op == PID_STARTUP) { if (read_count > 0) { if (kill(pid, 0) == 0) { error = EEXIST; (void) fprintf(stderr, gettext( "Terminated - nfslogd(%ld) already " "running.\n"), pid); goto out; } else if (errno != ESRCH) { error = errno; (void) fprintf(stderr, gettext( "Unexpected error returned %s\n"), strerror(error)); goto out; } } pid = mypid; /* * rewind the file to overwrite old pid */ (void) lseek(fd, 0, SEEK_SET); if (write(fd, &mypid, sizeof (mypid)) < 0) { error = errno; (void) fprintf(stderr, gettext( "Cannot update %s: %s\n"), PidFile, strerror(error)); } } else { assert(pid == mypid); if (unlink(PidFile)) { error = errno; syslog(LOG_ERR, gettext("Cannot remove %s: %s"), strerror(error)); } } out: if (fd >= 0) (void) close(fd); return (error); } /* * Forces a timeout on all open transactions. */ static void transactions_timeout(nfsl_config_t *clp) { for (; clp != NULL; clp = clp->nc_next) { if (clp->nc_transcookie != NULL) { nfslog_process_trans_timeout( (struct nfslog_trans_file *)clp->nc_transcookie, FALSE); } } } /* * Closes all transaction logs causing outstanding transactions * to be flushed to their respective log. */ static void close_all_translogs(nfsl_config_t *clp) { for (; clp != NULL; clp = clp->nc_next) { if (clp->nc_transcookie != NULL) { nfslog_close_transactions(&clp->nc_transcookie); assert(clp->nc_transcookie == NULL); } } } # #ident "%Z%%M% %I% %E% SMI" # # Copyright 2005 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (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 # # Specify the maximum number of logs to preserve. # # MAX_LOGS_PRESERVE=10 # Minimum size buffer should reach before processing. # # MIN_PROCESSING_SIZE=524288 # Number of seconds the daemon should sleep waiting for more work. # # IDLE_TIME=300 # CYCLE_FREQUENCY specifies the frequency (in hours) with which the # log buffers should be cycled. # # CYCLE_FREQUENCY=24 # Use UMASK for the creation of logs and file handle mapping tables. # # UMASK=0137 /* * 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _NFSLOGD_H #define _NFSLOGD_H #ifdef __cplusplus extern "C" { #endif #include #include #include #include "../lib/nfslog_config.h" #include "buffer_list.h" #define NFSLOGD_PIDFILE "/var/run/nfslogd.pid" #define NFSLOG_OPTIONS_FILE "/etc/default/nfslogd" #define MIN_PROCESSING_SIZE 512*1024 /* Minimum size buffer */ /* should reach before */ /* processing */ #define IDLE_TIME 300 /* Max time to wait w/o processing */ /* in seconds */ #define MAX_LOGS_PRESERVE 10 /* Number of log files to keep for */ /* cycling */ #define MAPPING_UPDATE_INTERVAL (SECSPERDAY) /* frequency of updates to */ /* dbm records in seconds */ #define CYCLE_FREQUENCY 24 /* in hours */ #define PRUNE_TIMEOUT (SECSPERHOUR * 168) #define NFSLOG_UMASK 0137 /* for creating tables and logs */ /* * RPC dispatch table for logging. Indexed by program, version, proc. * Based on NFS dispatch table, but differs in that it does not xdr * encode/decode arguments and results. */ struct nfsl_proc_disp { void (*nfsl_dis_args)(); /* prt elf nl args from rpc args */ void (*nfsl_dis_res)(); /* prt elf nl res from rpc res */ char *procname; /* string describing the proc */ }; struct nfsl_vers_disp { int nfsl_dis_nprocs; /* number of procs */ struct nfsl_proc_disp *nfsl_dis_proc_table; /* proc array */ }; struct nfsl_prog_disp { rpcprog_t nfsl_dis_prog; /* program number */ rpcvers_t nfsl_dis_versmin; /* minimum version number */ int nfsl_dis_nvers; /* number of version values */ struct nfsl_vers_disp *nfsl_dis_vers_table; /* versions array */ char *progname; /* string describing the program */ }; struct nfsl_log_file { char *path; /* pathname of file */ FILE *fp; /* file pointer */ char *buf; /* buffer where output queued before print */ int bufoffset; /* current offset in (memory) buffer */ struct nfsl_log_file *next; /* next file in list */ struct nfsl_log_file *prev; /* next file in list */ }; /* * The following four structures are used for processing the buffer file. */ struct valid_rpcs { rpcprog_t prog; rpcvers_t versmin; rpcvers_t versmax; }; /* * Simple struct for keeping track of the offset and length of * records processed from the buffer file. This is used for the logic * of rewriting the buffer header of that last record processed. * Since records within the buffer file can be 'out of order' and nfslogd * sorts those records, we need to keep track of what has been processed * and where. This record keeping is then used to decide when to rewrite * the buffer header and to decide the correct offset for that rewrite. */ struct processed_records { struct processed_records *next; struct processed_records *prev; u_offset_t start_offset; unsigned int len; unsigned int num_recs; }; struct nfslog_buf { struct nfslog_buf *next; struct nfslog_buf *prev; char *bufpath; /* buffer file name */ int fd; /* buffer file fd */ flock_t fl; /* buffer file lock */ u_offset_t filesize; /* file size */ intptr_t mmap_addr; /* address of mmap */ u_offset_t next_rec; /* address of next record */ unsigned int last_rec_id; /* last record id processed */ nfslog_buffer_header bh; /* file buffer header */ struct nfslog_lr *bh_lrp; int num_lrps; struct nfslog_lr *lrps; /* raw records - not cooked */ /* Next fields used for tracking processed records from buf file */ u_offset_t last_record_offset; /* value last written to hdr */ struct processed_records *prp; /* list of processed chunks */ int num_pr_queued; /* # of processed records */ }; struct nfslog_lr { struct nfslog_lr *next; struct nfslog_lr *prev; u_offset_t f_offset; /* offset for ondisk file */ intptr_t record; /* mmap address of record */ unsigned int recsize; /* size of this record */ caddr_t buffer; /* used if mmap fails */ XDR xdrs; nfslog_request_record log_record; /* decoded record */ bool_t (*xdrargs)(); /* xdr function for FREE */ bool_t (*xdrres)(); /* xdr function for FREE */ struct nfslog_buf *lbp; }; /* * Following defines are used as a parameter to nfslog_open_trans() * The bit mask passed to this function will determine which operations * are placed in the log. */ #define TRANSTOLOG_OPER_READ 0x00000001 #define TRANSTOLOG_OPER_WRITE 0x00000002 #define TRANSTOLOG_OPER_SETATTR 0x00000004 #define TRANSTOLOG_OPER_REMOVE 0x00000008 #define TRANSTOLOG_OPER_MKDIR 0x00000010 #define TRANSTOLOG_OPER_CREATE 0x00000020 #define TRANSTOLOG_OPER_RMDIR 0x00000040 #define TRANSTOLOG_OPER_RENAME 0x00000080 #define TRANSTOLOG_OPER_MKNOD 0x00000100 #define TRANSTOLOG_OPER_LINK 0x00000200 #define TRANSTOLOG_OPER_SYMLINK 0x00000400 #define TRANSTOLOG_OPER_READWRITE \ (TRANSTOLOG_OPER_READ | TRANSTOLOG_OPER_WRITE) #define TRANSTOLOG_ALL ((uint32_t)~0) extern int debug; extern boolean_t test; extern int max_logs_preserve; extern uint_t idle_time; extern boolean_t keep_running; extern boolean_t quick_cleaning; extern int cycle_log(char *, int); extern int prune_dbs(char *); extern int process_buffer( struct buffer_ent *, nfsl_config_t **, int, int, int *); extern struct nfslog_buf *nfslog_open_buf(char *, int *); extern void nfslog_close_buf(struct nfslog_buf *, int); extern struct nfslog_lr *nfslog_get_logrecord(struct nfslog_buf *); extern void nfslog_free_logrecord(struct nfslog_lr *, bool_t); extern int nfslog_process_fh_rec(struct nfslog_lr *, char *, char **, char **, bool_t); extern void *nfslog_open_elf_file(char *, nfslog_buffer_header *, int *); extern void nfslog_close_elf_file(void **); extern int nfslog_process_elf_rec(void *, nfslog_request_record *, char *, char *); struct nfslog_trans_file; extern void *nfslog_open_trans_file(char *, uint32_t, uint32_t, int *); extern void nfslog_process_trans_timeout(struct nfslog_trans_file *, uint32_t); extern int nfslog_process_trans_rec(void *, nfslog_request_record *, char *, char *, char *); extern void nfslog_close_transactions(void **); extern void nfslog_opaque_print_buf(void *, int, char *, int *, int); #ifdef __cplusplus } #endif #endif /* _NFSLOGD_H */ /* * 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 2007 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 "../lib/nfslog_config.h" #include "buffer_list.h" #include "nfslogd.h" extern int _nfssys(int, void *); /* * simple list used to keep track of bad tag messages syslogged. */ struct nfs_log_list { char *l_name; struct nfs_log_list *l_next; }; static void badtag_notify(char *tag); static struct nfs_log_list *badtag_list = NULL; static void cleanup_elf_state(nfsl_config_t *); static void cleanup_trans_state(nfsl_config_t *); /* * Read the contents of the 'bufferpath', process them and store the * user-readable log in 'elfpath', updating the 'fhpath' filehandle * table. * The contents of the configuration list (*config_list) may be * modified if the configuration file has been updated and we can not * find the configuration entry in the currently loaded list. * * Returns 0 on success and sets *buffer_processed to 1. * non zero error on failure and *buffer_processed set to 0. */ int process_buffer( struct buffer_ent *bep, nfsl_config_t **config_list, int min_size, int idle_time, int *buffer_processed) { struct stat st; struct nfsl_flush_args nfa; struct nfslog_buf *lbp = NULL; struct nfslog_lr *lrp; char *path1 = NULL; char *path2 = NULL; char *buffer_inprog = NULL; int buffer_inprog_len; int error = 0; nfsl_config_t *ncp = NULL, *last_good_ncp; char *bufferpath = bep->be_name; char *tag; boolean_t elf_checked = B_FALSE; boolean_t trans_checked = B_FALSE; assert(buffer_processed != NULL); assert(bufferpath != NULL); if (stat(bufferpath, &st) == -1) { error = errno; if (error == ENOENT) { error = 0; buffer_inprog_len = strlen(bufferpath) + strlen(LOG_INPROG_STRING) + 1; buffer_inprog = (char *)malloc(buffer_inprog_len); if (buffer_inprog == NULL) { syslog(LOG_ERR, gettext( "process_buffer: malloc failed")); return (ENOMEM); } (void) sprintf(buffer_inprog, "%s%s", bufferpath, LOG_INPROG_STRING); if (stat(buffer_inprog, &st) == -1) { error = errno; if (bep->be_error != error) { syslog(LOG_ERR, gettext( "Can not stat %s: %s"), buffer_inprog, strerror(error)); } free(buffer_inprog); return (error); } free(buffer_inprog); /* * Does the buffer in progress meet our minimum * processing requirements? or has it been around * longer than we're willing to wait for more * data to be logged? */ if ((st.st_size < min_size) && ((time(0) - bep->be_lastprocessed) < idle_time)) { /* * The buffer does not meet the minimum * size processing requirements, and it has not * been around longer than we're willing to * wait for more data collection. * We return now without processing it. */ return (0); } /* * Issue the LOG_FLUSH system call to flush the * buffer and process it. */ (void) memset((void *)&nfa, 0, sizeof (nfa)); nfa.version = NFSL_FLUSH_ARGS_VERS; nfa.directive = NFSL_RENAME | NFSL_SYNC; nfa.buff = bufferpath; nfa.buff_len = strlen(bufferpath) + 1; if (_nfssys(LOG_FLUSH, &nfa) < 0) { error = errno; if (bep->be_error != error) { syslog(LOG_ERR, gettext( "_nfssys(%s) failed: %s"), nfa.buff, strerror(error)); } return (error); } } else { if (bep->be_error != error) { syslog(LOG_ERR, gettext("Can not stat %s: %s"), bufferpath, strerror(error)); } return (error); } } /* * Open and lock input buffer. * Passes in the value of the last error so that it will not * print it again if it is still hitting the same error condition. */ error = bep->be_error; if ((lbp = nfslog_open_buf(bufferpath, &error)) == NULL) goto done; if ((ncp = last_good_ncp = nfsl_findconfig(*config_list, "global", &error)) == NULL) { assert(error != 0); nfsl_freeconfig_list(config_list); if (error != bep->be_error) { syslog(LOG_ERR, gettext( "Could not search config list: %s"), strerror(error)); } goto done; } assert(error == 0); while ((lrp = nfslog_get_logrecord(lbp)) != NULL && keep_running) { if (*buffer_processed == 0) (*buffer_processed)++; /* * Get the matching config entry. */ tag = lrp->log_record.re_tag; if (strcmp(tag, last_good_ncp->nc_name) != 0) { ncp = nfsl_findconfig(*config_list, tag, &error); if (error) { if (error != bep->be_error) { syslog(LOG_ERR, gettext( "Could not search config list: %s"), strerror(error)); } nfsl_freeconfig_list(config_list); goto done; } if (ncp == NULL) { badtag_notify(tag); ncp = last_good_ncp; goto skip; } last_good_ncp = ncp; } if (ncp->nc_flags & NC_UPDATED) { /* * The location of the log files may have changed, * we need to close transactions and invalidate * cookies so that the log files can be reopened * further down. */ cleanup_elf_state(ncp); cleanup_trans_state(ncp); ncp->nc_flags &= ~NC_UPDATED; /* * Force cookies to be recreated if necessary. */ elf_checked = trans_checked = B_FALSE; } /* * Open output files. */ if (ncp->nc_rpclogpath != NULL) { /* * Log rpc requests in W3C-ELF format. */ if (!elf_checked && ncp->nc_elfcookie != NULL) { /* * Make sure file still exists. * Do this once per buffer. */ if (stat(ncp->nc_rpclogpath, &st) == -1 && errno == ENOENT) { /* * The open rpclogfile has been * deleted. Get new one below. */ cleanup_elf_state(ncp); } elf_checked = B_TRUE; } if (ncp->nc_elfcookie == NULL) { error = bep->be_error; ncp->nc_elfcookie = nfslog_open_elf_file( ncp->nc_rpclogpath, &lbp->bh, &error); if (ncp->nc_elfcookie == NULL) { bep->be_error = error; goto done; } } } if (ncp->nc_logpath != NULL) { /* * Log rpc reqs in trans/ftp format. */ if (!trans_checked && ncp->nc_transcookie != NULL) { /* * Do this once per buffer. */ if (stat(ncp->nc_logpath, &st) == -1 && errno == ENOENT) { /* * The open transaction file has been * deleted. Close pending transaction * work. A new transaction log will be * opened by nfslog_open_trans_file() * below. */ cleanup_trans_state(ncp); } trans_checked = B_TRUE; } if (ncp->nc_transcookie == NULL) { int transtolog; transtolog = (ncp->nc_logformat == TRANSLOG_BASIC) ? TRANSTOLOG_OPER_READWRITE : TRANSTOLOG_ALL; error = bep->be_error; ncp->nc_transcookie = nfslog_open_trans_file( ncp->nc_logpath, ncp->nc_logformat, transtolog, &error); if (ncp->nc_transcookie == NULL) { bep->be_error = error; goto done; } } } assert(ncp->nc_fhpath != NULL); if (nfslog_process_fh_rec(lrp, ncp->nc_fhpath, &path1, &path2, ncp->nc_elfcookie != NULL)) { /* * Make sure there is room. */ if (ncp->nc_elfcookie != NULL) { (void) nfslog_process_elf_rec(ncp->nc_elfcookie, &lrp->log_record, path1, path2); } if (ncp->nc_transcookie != NULL) { (void) nfslog_process_trans_rec( ncp->nc_transcookie, &lrp->log_record, ncp->nc_fhpath, path1, path2); } } skip: if (path1 != NULL) free(path1); if (path2 != NULL) free(path2); path1 = path2 = NULL; nfslog_free_logrecord(lrp, TRUE); } /* while */ if (!error && keep_running) { /* * Keep track of when this buffer was last processed. */ bep->be_lastprocessed = time(0); if (test && *buffer_processed != 0) { /* * Save the buffer for future debugging. We do this * by following the log cycling policy, with a maximum * of 'max_logs_preserve' to save. */ if (cycle_log(bufferpath, max_logs_preserve)) { syslog(LOG_ERR, gettext( "could not save copy of buffer \"%s\""), bufferpath); } } else { /* * Remove buffer since it has been processed. */ if (unlink(bufferpath)) { error = errno; syslog(LOG_ERR, gettext( "could not unlink %s: %s"), bufferpath, strerror(error)); /* * Buffer was processed correctly. */ error = 0; } } } done: if (lbp != NULL) nfslog_close_buf(lbp, quick_cleaning); if (ncp && !quick_cleaning) cleanup_elf_state(ncp); return (error); } static void cleanup_elf_state(nfsl_config_t *ncp) { if (ncp->nc_elfcookie != NULL) { nfslog_close_elf_file(&ncp->nc_elfcookie); assert(ncp->nc_elfcookie == NULL); } } static void cleanup_trans_state(nfsl_config_t *ncp) { if (ncp->nc_transcookie != NULL) { nfslog_close_transactions(&ncp->nc_transcookie); assert(ncp->nc_transcookie == NULL); } } /* * Searches the list of previously seen bad tags. Note that this * list is never pruned. This should not be a problem since the * list of bad tags should be fairl small. New entries are inserted * at the beginning of the list assuming it will be accessed more * frequently since we have just seen it. */ static void badtag_notify(char *tag) { struct nfs_log_list *lp, *p; int error; for (p = badtag_list; p != NULL; p = p->l_next) { if (strcmp(tag, p->l_name) == 0) { /* * We've seen this before, nothing to do. */ return; } } /* * Not on the list, add it. */ syslog(LOG_ERR, gettext("tag \"%s\" not found in %s - " "ignoring records referencing such tag."), tag, NFSL_CONFIG_FILE_PATH); if ((lp = (struct nfs_log_list *)malloc(sizeof (*lp))) != NULL) { if ((lp->l_name = strdup(tag)) != NULL) { lp->l_next = badtag_list; badtag_list = lp; return; /* done */ } } if (lp->l_name != NULL) free(lp->l_name); if (lp) free(lp); error = errno; syslog(LOG_ERR, gettext( "Cannot add \"%s\" to bad tag list: %s"), tag, strerror(error)); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * nfs log - read buffer file and return structs in usable form */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nfslogd.h" #define MAX_LRS_READ_AHEAD 2048 #define MAX_RECS_TO_DELAY 32768 static int nfslog_init_buf(char *, struct nfslog_buf *, int *); static void nfslog_free_buf(struct nfslog_buf *, int); static struct nfslog_lr *nfslog_read_buffer(struct nfslog_buf *); static void free_lrp(struct nfslog_lr *); static struct nfslog_lr *remove_lrp_from_lb(struct nfslog_buf *, struct nfslog_lr *); static void insert_lrp_to_lb(struct nfslog_buf *, struct nfslog_lr *); static void nfslog_rewrite_bufheader(struct nfslog_buf *); /* * Treat the provided path name as an NFS log buffer file. * Allocate a data structure for its handling and initialize it. * *error contains the previous error condition encountered for * this object. This value can be used to avoid printing the last * error endlessly. * It will set *error appropriately after processing. */ struct nfslog_buf * nfslog_open_buf(char *bufpath, int *error) { struct nfslog_buf *lbp = NULL; if (bufpath == NULL) { *error = EINVAL; return (NULL); } if ((lbp = malloc(sizeof (struct nfslog_buf))) == NULL) { *error = ENOMEM; return (NULL); } bzero(lbp, sizeof (struct nfslog_buf)); if (nfslog_init_buf(bufpath, lbp, error)) { free(lbp); return (NULL); } return (lbp); } /* * Free the log buffer struct with all of its baggage and free the data struct */ void nfslog_close_buf(struct nfslog_buf *lbp, int close_quick) { nfslog_free_buf(lbp, close_quick); free(lbp); } /* * Set up the log buffer struct; simple things are opening and locking * the buffer file and then on to mmap()ing it for later use by the * XDR decode path. Make sure to read the buffer header before * returning so that we will be at the first true log record. * * *error contains the last error encountered on this object. It can * be used to avoid reporting the same error endlessly. It is reset * to the current error code on return. */ static int nfslog_init_buf(char *bufpath, struct nfslog_buf *lbp, int *error) { struct stat sb; int preverror = *error; lbp->next = lbp; lbp->prev = lbp; /* * set these values so that the free routine will know what to do */ lbp->mmap_addr = (intptr_t)MAP_FAILED; lbp->last_rec_id = MAXINT - 1; lbp->bh.bh_length = 0; lbp->bh_lrp = NULL; lbp->num_lrps = 0; lbp->lrps = NULL; lbp->last_record_offset = 0; lbp->prp = NULL; lbp->num_pr_queued = 0; lbp->bufpath = strdup(bufpath); if (lbp->bufpath == NULL) { *error = ENOMEM; if (preverror != *error) { syslog(LOG_ERR, gettext("Cannot strdup '%s': %s"), bufpath, strerror(*error)); } nfslog_free_buf(lbp, FALSE); return (*error); } if ((lbp->fd = open(bufpath, O_RDWR)) < 0) { *error = errno; if (preverror != *error) { syslog(LOG_ERR, gettext("Cannot open '%s': %s"), bufpath, strerror(*error)); } nfslog_free_buf(lbp, FALSE); return (*error); } /* * Lock the entire buffer file to prevent conflicting access. * We get a write lock because we want only 1 process to be * generating records from it. */ lbp->fl.l_type = F_WRLCK; lbp->fl.l_whence = SEEK_SET; /* beginning of file */ lbp->fl.l_start = (offset_t)0; lbp->fl.l_len = 0; /* entire file */ lbp->fl.l_sysid = 0; lbp->fl.l_pid = 0; if (fcntl(lbp->fd, F_SETLKW, &lbp->fl) == -1) { *error = errno; if (preverror != *error) { syslog(LOG_ERR, gettext("Cannot lock (%s): %s"), bufpath, strerror(*error)); } nfslog_free_buf(lbp, FALSE); return (*error); } if (fstat(lbp->fd, &sb)) { *error = errno; if (preverror != *error) { syslog(LOG_ERR, gettext("Cannot stat (%s): %s"), bufpath, strerror(*error)); } nfslog_free_buf(lbp, FALSE); return (*error); } lbp->filesize = sb.st_size; lbp->mmap_addr = (intptr_t)mmap(0, lbp->filesize, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_NORESERVE, lbp->fd, 0); /* This is part of the duality of the use of either mmap()|read() */ if (lbp->mmap_addr == (intptr_t)MAP_FAILED) { lbp->next_rec = 0; } else { lbp->next_rec = lbp->mmap_addr; } /* Read the header */ if ((lbp->bh_lrp = nfslog_read_buffer(lbp)) == NULL) { *error = EIO; if (preverror != *error) { syslog(LOG_ERR, gettext( "error in reading file '%s': %s"), bufpath, strerror(EIO)); } nfslog_free_buf(lbp, FALSE); return (*error); } if (!xdr_nfslog_buffer_header(&lbp->bh_lrp->xdrs, &lbp->bh)) { *error = EIO; if (preverror != *error) { syslog(LOG_ERR, gettext( "error in reading file '%s': %s"), bufpath, strerror(*error)); } nfslog_free_buf(lbp, FALSE); return (*error); } /* * Set the pointer to the next record based on the buffer header. * 'lbp->bh.bh_offset' contains the offset of where to begin * processing relative to the buffer header. */ lbp->next_rec += lbp->bh.bh_offset; /* * If we are going to be using read() for file data, then we may * have to adjust the current file pointer to take into account * a starting point other than the beginning of the file. * If mmap is being used, this is taken care of as a side effect of * setting up the value of next_rec. */ if (lbp->mmap_addr == (intptr_t)MAP_FAILED && lbp->next_rec != 0) { (void) lseek(lbp->fd, lbp->next_rec, SEEK_SET); /* This is a special case of setting the last_record_offset */ lbp->last_record_offset = lbp->next_rec; } else { lbp->last_record_offset = lbp->next_rec - lbp->mmap_addr; } return (*error = 0); } /* * Free the nfslog buffer and its associated allocations */ static void nfslog_free_buf(struct nfslog_buf *lbp, int close_quick) { XDR xdrs; int error; caddr_t buffer; struct nfslog_lr *lrp, *lrp_next; struct processed_records *prp, *tprp; /* work to free the offset records and rewrite header */ if (lbp->prp) { if (lbp->last_record_offset == lbp->prp->start_offset) { /* adjust the offset for the entire buffer */ lbp->last_record_offset = lbp->prp->start_offset + lbp->prp->len; nfslog_rewrite_bufheader(lbp); } if (close_quick) return; prp = lbp->prp; do { tprp = prp->next; free(prp); prp = tprp; } while (lbp->prp != prp); } if (close_quick) return; /* Take care of the queue log records first */ if (lbp->lrps != NULL) { lrp = lbp->lrps; do { lrp_next = lrp->next; nfslog_free_logrecord(lrp, FALSE); lrp = lrp_next; } while (lrp != lbp->lrps); lbp->lrps = NULL; } /* The buffer header was decoded and needs to be freed */ if (lbp->bh.bh_length != 0) { buffer = (lbp->bh_lrp->buffer != NULL ? lbp->bh_lrp->buffer : (caddr_t)lbp->mmap_addr); xdrmem_create(&xdrs, buffer, lbp->bh_lrp->recsize, XDR_FREE); (void) xdr_nfslog_buffer_header(&xdrs, &lbp->bh); lbp->bh.bh_length = 0; } /* get rid of the bufheader lrp */ if (lbp->bh_lrp != NULL) { free_lrp(lbp->bh_lrp); lbp->bh_lrp = NULL; } /* Clean up for mmap() usage */ if (lbp->mmap_addr != (intptr_t)MAP_FAILED) { if (munmap((void *)lbp->mmap_addr, lbp->filesize)) { error = errno; syslog(LOG_ERR, gettext("munmap failed: %s: %s"), (lbp->bufpath != NULL ? lbp->bufpath : ""), strerror(error)); } lbp->mmap_addr = (intptr_t)MAP_FAILED; } /* Finally close the buffer file */ if (lbp->fd >= 0) { lbp->fl.l_type = F_UNLCK; if (fcntl(lbp->fd, F_SETLK, &lbp->fl) == -1) { error = errno; syslog(LOG_ERR, gettext("Cannot unlock file %s: %s"), (lbp->bufpath != NULL ? lbp->bufpath : ""), strerror(error)); } (void) close(lbp->fd); lbp->fd = -1; } if (lbp->bufpath != NULL) free(lbp->bufpath); } /* * We are reading a record from the log buffer file. Since we are reading * an XDR stream, we first have to read the first integer to determine * how much to read in whole for this record. Our preference is to use * mmap() but if failed initially we will be using read(). Need to be * careful about proper initialization of the log record both from a field * perspective and for XDR decoding. */ static struct nfslog_lr * nfslog_read_buffer(struct nfslog_buf *lbp) { XDR xdrs; unsigned int record_size; struct nfslog_lr *lrp; char *sizebuf, tbuf[16]; caddr_t buffer; offset_t next_rec; lrp = (struct nfslog_lr *)malloc(sizeof (*lrp)); bzero(lrp, sizeof (*lrp)); /* Check to see if mmap worked */ if (lbp->mmap_addr == (intptr_t)MAP_FAILED) { /* * EOF or other failure; we don't try to recover, just return */ if (read(lbp->fd, tbuf, BYTES_PER_XDR_UNIT) <= 0) { free_lrp(lrp); return (NULL); } sizebuf = tbuf; } else { /* EOF check for the mmap() case */ if (lbp->filesize <= lbp->next_rec - lbp->mmap_addr) { free_lrp(lrp); return (NULL); } sizebuf = (char *)(uintptr_t)lbp->next_rec; } /* We have to XDR the first int so we know how much is in this record */ xdrmem_create(&xdrs, sizebuf, sizeof (unsigned int), XDR_DECODE); if (!xdr_u_int(&xdrs, &record_size)) { free_lrp(lrp); return (NULL); } lrp->recsize = record_size; next_rec = lbp->next_rec + lrp->recsize; if (lbp->mmap_addr == (intptr_t)MAP_FAILED) { /* * Read() case - shouldn't be used very much. * Note: The 'buffer' field is used later on * to determine which method is being used mmap()|read() */ if (lbp->filesize < next_rec) { /* partial record from buffer */ syslog(LOG_ERR, gettext( "Last partial record in work buffer %s " "discarded\n"), lbp->bufpath); free_lrp(lrp); return (NULL); } if ((lrp->buffer = malloc(lrp->recsize)) == NULL) { free_lrp(lrp); return (NULL); } bcopy(sizebuf, lrp->buffer, BYTES_PER_XDR_UNIT); if (read(lbp->fd, &lrp->buffer[BYTES_PER_XDR_UNIT], lrp->recsize - BYTES_PER_XDR_UNIT) <= 0) { free_lrp(lrp); return (NULL); } } else if (lbp->filesize < next_rec - lbp->mmap_addr) { /* partial record from buffer */ syslog(LOG_ERR, gettext( "Last partial record in work buffer %s " "discarded\n"), lbp->bufpath); free_lrp(lrp); return (NULL); } /* other initializations */ lrp->next = lrp->prev = lrp; /* Keep track of the offset at which this record was read */ if (lbp->mmap_addr == (intptr_t)MAP_FAILED) lrp->f_offset = lbp->next_rec; else lrp->f_offset = lbp->next_rec - lbp->mmap_addr; /* This is the true address of the record */ lrp->record = lbp->next_rec; lrp->xdrargs = lrp->xdrres = NULL; lrp->lbp = lbp; /* Here is the logic for mmap() vs. read() */ buffer = (lrp->buffer != NULL ? lrp->buffer : (caddr_t)lrp->record); /* Setup for the 'real' XDR decode of the entire record */ xdrmem_create(&lrp->xdrs, buffer, lrp->recsize, XDR_DECODE); /* calculate the offset for the next record */ lbp->next_rec = next_rec; return (lrp); } /* * Simple removal of the log record from the log buffer queue. * Make sure to manage the count of records queued. */ static struct nfslog_lr * remove_lrp_from_lb(struct nfslog_buf *lbp, struct nfslog_lr *lrp) { if (lbp->lrps == lrp) { if (lbp->lrps == lbp->lrps->next) { lbp->lrps = NULL; } else { lbp->lrps = lrp->next; remque(lrp); } } else { remque(lrp); } lbp->num_lrps--; return (lrp); } /* * Insert a log record struct on the log buffer struct. The log buffer * has a pointer to the head of a queue of log records that have been * read from the buffer file but have not been processed yet because * the record id did not match the sequence desired for processing. * The insertion must be in the 'correct'/sorted order which adds * to the complexity of this function. */ static void insert_lrp_to_lb(struct nfslog_buf *lbp, struct nfslog_lr *lrp) { int ins_rec_id = lrp->log_record.re_header.rh_rec_id; struct nfslog_lr *curlrp; if (lbp->lrps == NULL) { /* that was easy */ lbp->lrps = lrp; } else { /* * Does this lrp go before the first on the list? * If so, do the insertion by hand since insque is not * as flexible when queueing an element to the head of * a list. */ if (ins_rec_id < lbp->lrps->log_record.re_header.rh_rec_id) { lrp->next = lbp->lrps; lrp->prev = lbp->lrps->prev; lbp->lrps->prev->next = lrp; lbp->lrps->prev = lrp; lbp->lrps = lrp; } else { /* * Search the queue for the correct insertion point. * Be careful about the insque so that the record * ends up in the right place. */ curlrp = lbp->lrps; do { if (ins_rec_id < curlrp->next->log_record.re_header.rh_rec_id) break; curlrp = curlrp->next; } while (curlrp != lbp->lrps); if (curlrp == lbp->lrps) insque(lrp, lbp->lrps->prev); else insque(lrp, curlrp); } } /* always keep track of how many we have */ lbp->num_lrps++; } /* * We are rewriting the buffer header at the start of the log buffer * for the sole purpose of resetting the bh_offset field. This is * supposed to represent the progress that the nfslogd daemon has made * in its processing of the log buffer file. * 'lbp->last_record_offset' contains the absolute offset of the end * of the last element processed. The on-disk buffer offset is relative * to the buffer header, therefore we subtract the length of the buffer * header from the absolute offset. */ static void nfslog_rewrite_bufheader(struct nfslog_buf *lbp) { XDR xdrs; nfslog_buffer_header bh; /* size big enough for buffer header encode */ #define XBUFSIZE 128 char buffer[XBUFSIZE]; unsigned int wsize; /* * if version 1 buffer is large and the current offset cannot be * represented, then don't update the offset in the buffer. */ if (lbp->bh.bh_flags & NFSLOG_BH_OFFSET_OVERFLOW) { /* No need to update the header - offset too big */ return; } /* * build the buffer header from the original that was saved * on initialization; note that the offset is taken from the * last record processed (the last offset that represents * all records processed without any holes in the processing) */ bh = lbp->bh; /* * if version 1 buffer is large and the current offset cannot be * represented in 32 bits, then save only the last valid offset * in the buffer and mark the flags to indicate that. */ if ((bh.bh_version > 1) || (lbp->last_record_offset - bh.bh_length < UINT32_MAX)) { bh.bh_offset = lbp->last_record_offset - bh.bh_length; } else { /* don't update the offset in the buffer */ bh.bh_flags |= NFSLOG_BH_OFFSET_OVERFLOW; lbp->bh.bh_flags = bh.bh_flags; syslog(LOG_ERR, gettext( "nfslog_rewrite_bufheader: %s: offset does not fit " "in a 32 bit field\n"), lbp->bufpath); } xdrmem_create(&xdrs, buffer, XBUFSIZE, XDR_ENCODE); if (!xdr_nfslog_buffer_header(&xdrs, &bh)) { syslog(LOG_ERR, gettext( "error in re-writing buffer file %s header\n"), lbp->bufpath); return; } wsize = xdr_getpos(&xdrs); if (lbp->mmap_addr == (intptr_t)MAP_FAILED) { /* go to the beginning of the file */ (void) lseek(lbp->fd, 0, SEEK_SET); (void) write(lbp->fd, buffer, wsize); (void) lseek(lbp->fd, lbp->next_rec, SEEK_SET); (void) fsync(lbp->fd); } else { bcopy(buffer, (void *)lbp->mmap_addr, wsize); (void) msync((void *)lbp->mmap_addr, wsize, MS_SYNC); } } /* * With the provided lrp, we will take and 'insert' the range that the * record covered in the buffer file into a list of processed ranges * for the buffer file. These ranges represent the records processed * but not 'marked' in the buffer header as being processed. * This insertion process is being done for two reasons. The first is that * we do not want to pay the performance penalty of re-writing the buffer header * for each record that we process. The second reason is that the records * may be processed out of order because of the unique ids. This will occur * if the kernel has written the records to the buffer file out of order. * The read routine will 'sort' them as the records are read. * * We do not want to re-write the buffer header such that a record is * represented and being processed when it has not been. In the case * that the nfslogd daemon restarts processing and the buffer header * has been re-written improperly, some records could be skipped. * We will be taking the conservative approach and only writing buffer * header offsets when the entire offset range has been processed. */ static void nfslog_ins_last_rec_processed(struct nfslog_lr *lrp) { struct processed_records *prp, *tp; /* init the data struct as if it were the only one */ prp = malloc(sizeof (*prp)); prp->next = prp->prev = prp; prp->start_offset = lrp->f_offset; prp->len = lrp->recsize; prp->num_recs = 1; /* always add since we know we are going to insert */ lrp->lbp->num_pr_queued++; /* Is this the first one? If so, take the easy way out */ if (lrp->lbp->prp == NULL) { lrp->lbp->prp = prp; } else { /* sort on insertion... */ tp = lrp->lbp->prp; do { if (prp->start_offset < tp->start_offset) break; tp = tp->next; } while (tp != lrp->lbp->prp); /* insert where appropriate (before the one we found */ insque(prp, tp->prev); /* * special case where the insertion was done at the * head of the list */ if (tp == lrp->lbp->prp && prp->start_offset < tp->start_offset) lrp->lbp->prp = prp; /* * now that the entry is in place, we need to see if it can * be combined with the previous or following entries. * combination is done by adding to the length. */ if (prp->start_offset == (prp->prev->start_offset + prp->prev->len)) { tp = prp->prev; remque(prp); tp->len += prp->len; tp->num_recs += prp->num_recs; free(prp); prp = tp; } if (prp->next->start_offset == (prp->start_offset + prp->len)) { prp->len += prp->next->len; prp->num_recs += prp->next->num_recs; tp = prp->next; remque(tp); free(tp); } } if (lrp->lbp->num_pr_queued > MAX_RECS_TO_DELAY) { prp = lrp->lbp->prp; if (lrp->lbp->last_record_offset == prp->start_offset) { /* adjust the offset for the entire buffer */ lrp->lbp->last_record_offset = prp->start_offset + prp->len; nfslog_rewrite_bufheader(lrp->lbp); tp = prp->next; if (tp != prp) remque(prp); else tp = NULL; lrp->lbp->prp = tp; lrp->lbp->num_pr_queued -= prp->num_recs; free(prp); } } } /* * nfslog_get_logrecord is responsible for retrieving the next log record * from the buffer file. This would normally be very straightforward but there * is the added complexity of attempting to order the requests coming out of * the buffer file. The fundamental problems is that the kernel nfs logging * functionality does not guarantee that the records were written to the file * in the order that the NFS server processed them. This can cause a problem * in the fh -> pathname mapping in the case were a lookup for a file comes * later in the buffer file than other operations on the lookup's target. * The fh mapping database will not have an entry and will therefore not * be able to map the fh to a name. * * So to solve this problem, the kernel nfs logging code tags each record * with a monotonically increasing id and is guaranteed to be allocated * in the order that the requests were processed. Realize however that * this processing guarantee is essentially for one thread on one client. * This id mechanism does not order all requests since it is only the * single client/single thread case that is most concerning to us here. * * This function will do the 'sorting' of the requests as they are * read from the buffer file. The sorting needs to take into account * that some ids may be missing (operations not logged but ids allocated) * and that the id field will eventually wrap over MAXINT. * * Complexity to solve the fh -> pathname mapping issue. */ struct nfslog_lr * nfslog_get_logrecord(struct nfslog_buf *lbp) { /* figure out what the next should be if the world were perfect */ unsigned int next_rec_id = lbp->last_rec_id + 1; struct nfslog_lr *lrp = NULL; /* * First we check the queued records on the log buffer struct * to see if the one we want is there. The records are sorted * on the record id during the insertions to the queue so that * this check is easy. */ if (lbp->lrps != NULL) { /* Does the first record match ? */ if (lbp->lrps->log_record.re_header.rh_rec_id == next_rec_id) { lrp = remove_lrp_from_lb(lbp, lbp->lrps); lbp->last_rec_id = lrp->log_record.re_header.rh_rec_id; } else { /* * Here we are checking for wrap of the record id * since it is an unsigned in. The idea is that * if there is a huge span between what we expect * and what is queued then we need to flush/empty * the queued records first. */ if (next_rec_id < lbp->lrps->log_record.re_header.rh_rec_id && ((lbp->lrps->log_record.re_header.rh_rec_id - next_rec_id) > (MAXINT / 2))) { lrp = remove_lrp_from_lb(lbp, lbp->lrps); lbp->last_rec_id = lrp->log_record.re_header.rh_rec_id; } } } /* * So the first queued record didn't match (or there were no queued * records to look at). Now we go to the buffer file looking for * the expected log record based on its id. We loop looking for * a matching records and save/queue the records that don't match. * Note that we will queue a maximum number to handle the case * of a missing record id or a queue that is very confused. We don't * want to consume too much memory. */ while (lrp == NULL) { /* Have we queued too many for this buffer? */ if (lbp->num_lrps >= MAX_LRS_READ_AHEAD) { lrp = remove_lrp_from_lb(lbp, lbp->lrps); lbp->last_rec_id = lrp->log_record.re_header.rh_rec_id; break; } /* * Get a record from the buffer file. If none are available, * this is probably and EOF condition (could be a read error * as well but that is masked. :-(). No records in the * file means that we need to pull any queued records * so that we don't miss any in the processing. */ if ((lrp = nfslog_read_buffer(lbp)) == NULL) { if (lbp->lrps != NULL) { lrp = remove_lrp_from_lb(lbp, lbp->lrps); lbp->last_rec_id = lrp->log_record.re_header.rh_rec_id; } else { return (NULL); /* it was really and EOF */ } } else { /* * Just read a record from the buffer file and now we * need to XDR the record header so that we can take * a look at the record id. */ if (!xdr_nfslog_request_record(&lrp->xdrs, &lrp->log_record)) { /* Free and return EOF/NULL on error */ nfslog_free_logrecord(lrp, FALSE); return (NULL); } /* * If the new record is less than or matches the * expected record id, then we return this record */ if (lrp->log_record.re_header.rh_rec_id <= next_rec_id) { lbp->last_rec_id = lrp->log_record.re_header.rh_rec_id; } else { /* * This is not the one we were looking * for; queue it for later processing * (queueing sorts on record id) */ insert_lrp_to_lb(lbp, lrp); lrp = NULL; } } } return (lrp); } /* * Free the log record provided. * This is complex because the associated XDR streams also need to be freed * since allocation could have occured during the DECODE phase. The record * header, args and results need to be XDR_FREEd. The xdr funtions will * be provided if a free needs to be done. * * Note that caller tells us if the record being freed was processed. * If so, then the buffer header should be updated. Updating the buffer * header keeps track of where the nfslogd daemon left off in its processing * if it is unable to complete the entire file. */ void nfslog_free_logrecord(struct nfslog_lr *lrp, bool_t processing_complete) { caddr_t buffer; nfslog_request_record *reqrec; if (processing_complete) { nfslog_ins_last_rec_processed(lrp); } reqrec = &lrp->log_record; buffer = (lrp->buffer != NULL ? lrp->buffer : (caddr_t)lrp->record); xdrmem_create(&lrp->xdrs, buffer, lrp->recsize, XDR_FREE); (void) xdr_nfslog_request_record(&lrp->xdrs, reqrec); if (lrp->xdrargs != NULL && reqrec->re_rpc_arg) (*lrp->xdrargs)(&lrp->xdrs, reqrec->re_rpc_arg); if (reqrec->re_rpc_arg) free(reqrec->re_rpc_arg); if (lrp->xdrres != NULL && reqrec->re_rpc_res) (*lrp->xdrres)(&lrp->xdrs, reqrec->re_rpc_res); if (reqrec->re_rpc_res) free(reqrec->re_rpc_res); free_lrp(lrp); } static void free_lrp(struct nfslog_lr *lrp) { if (lrp->buffer != NULL) free(lrp->buffer); free(lrp); } /* * Utility function used elsewhere */ void nfslog_opaque_print_buf(void *buf, int len, char *outbuf, int *outbufoffsetp, int maxoffset) { int i, j; uint_t *ip; uchar_t *u_buf = (uchar_t *)buf; int outbufoffset = *outbufoffsetp; outbufoffset += sprintf(&outbuf[outbufoffset], " \""); if (len <= sizeof (int)) { for (j = 0; (j < len) && (outbufoffset < maxoffset); j++, u_buf++) outbufoffset += sprintf(&outbuf[outbufoffset], "%02x", *u_buf); return; } /* More than 4 bytes, print with spaces in integer offsets */ j = (int)((uintptr_t)buf % sizeof (int)); i = 0; if (j > 0) { i = sizeof (int) - j; for (; (j < sizeof (int)) && (outbufoffset < maxoffset); j++, u_buf++) outbufoffset += sprintf(&outbuf[outbufoffset], "%02x", *u_buf); } /* LINTED */ ip = (uint_t *)u_buf; for (; ((i + sizeof (int)) <= len) && (outbufoffset < maxoffset); i += sizeof (int), ip++) { outbufoffset += sprintf(&outbuf[outbufoffset], " %08x", *ip); } if (i < len) { /* Last element not int */ u_buf = (uchar_t *)ip; if (i > j) /* not first element */ outbufoffset += sprintf(&outbuf[outbufoffset], " "); for (; (i < len) && (outbufoffset < maxoffset); i++, u_buf++) { outbufoffset += sprintf(&outbuf[outbufoffset], "%02x", *u_buf); } } if (outbufoffset < maxoffset) outbufoffset += sprintf(&outbuf[outbufoffset], "\""); *outbufoffsetp = outbufoffset; } # # 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 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright 2020 Joyent, Inc. FSTYPE = nfs TYPEPROG = nfsmapid TESTPROG = nfsmapid_test ATTMK = $(TYPEPROG) include ../../Makefile.fstype LDLIBS += -L$(ROOT)/usr/lib/nfs -R/usr/lib/nfs $(TYPEPROG) : LDLIBS += -lnsl -lmapid -ldtrace -lidmap COMMON = nfs_resolve.o SRCS = nfsmapid.c ../lib/nfs_resolve.c nfsmapid_server.c DSRC = nfsmapid_dt.d DOBJ = $(DSRC:%.d=%.o) OBJS = nfsmapid.o nfsmapid_server.o $(COMMON) CPPFLAGS += -I../lib -D_POSIX_PTHREAD_SEMANTICS CERRWARN += -Wno-implicit-function-declaration CERRWARN += -Wno-unused-variable CERRWARN += -Wno-parentheses CERRWARN += $(CNOWARN_UNINIT) # not linted SMATCH=off all: $(TYPEPROG) $(TESTPROG) $(TYPEPROG): $(OBJS) $(DSRC) $(COMPILE.d) -s $(DSRC) -o $(DOBJ) $(OBJS) $(LINK.c) $(ZIGNORE) -o $@ $(DOBJ) $(OBJS) $(LDLIBS) $(POST_PROCESS) nfs_resolve.o: ../lib/nfs_resolve.c $(COMPILE.c) ../lib/nfs_resolve.c TESTSRCS = nfsmapid_test.c TESTOBJS = $(TESTSRCS:%.c=%.o) TEST_OBJS = $(TESTOBJS) $(TESTPROG): $(TEST_OBJS) $(LINK.c) -o $@ $(TEST_OBJS) $(LDLIBS) $(POST_PROCESS) POFILE = nfsmapid.po catalog: $(POFILE) $(POFILE): $(SRCS) $(RM) $@ $(COMPILE.cpp) $(SRCS) > $@.i $(XGETTEXT) $(XGETFLAGS) $@.i sed "/^domain/d" messages.po > $@ $(RM) $@.i messages.po clean: $(RM) $(OBJS) $(TESTPROG) $(TESTOBJS) $(DOBJ) $(POFILE) /* * 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 2006 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 extern struct group *_uncached_getgrgid_r(gid_t, struct group *, char *, int); extern struct group *_uncached_getgrnam_r(const char *, struct group *, char *, int); extern struct passwd *_uncached_getpwuid_r(uid_t, struct passwd *, char *, int); extern struct passwd *_uncached_getpwnam_r(const char *, struct passwd *, char *, int); /* * seconds to cache nfsmapid domain info */ #define NFSCFG_DEFAULT_DOMAIN_TMOUT (5 * 60) #define NFSMAPID_DOOR "/var/run/nfsmapid_door" extern void nfsmapid_func(void *, char *, size_t, door_desc_t *, uint_t); extern void check_domain(int); extern void idmap_kcall(int); extern void open_diag_file(void); size_t pwd_buflen = 0; size_t grp_buflen = 0; thread_t sig_thread; static char *MyName; /* * nfscfg_domain_tmout is used by nfsv4-test scripts to query * the nfsmapid daemon for the proper timeout. Don't delete ! */ time_t nfscfg_domain_tmout = NFSCFG_DEFAULT_DOMAIN_TMOUT; /* * Processing for daemonization */ static void daemonize(void) { switch (fork()) { case -1: perror("nfsmapid: can't fork"); exit(2); /* NOTREACHED */ case 0: /* child */ break; default: /* parent */ _exit(0); } if (chdir("/") < 0) syslog(LOG_ERR, gettext("chdir /: %m")); /* * Close stdin, stdout, and stderr. * Open again to redirect input+output */ (void) close(0); (void) close(1); (void) close(2); (void) open("/dev/null", O_RDONLY); (void) open("/dev/null", O_WRONLY); (void) dup(1); (void) setsid(); } /* ARGSUSED */ static void * sig_handler(void *arg) { siginfo_t si; sigset_t sigset; struct timespec tmout; int ret; tmout.tv_nsec = 0; (void) sigemptyset(&sigset); (void) sigaddset(&sigset, SIGHUP); (void) sigaddset(&sigset, SIGTERM); #ifdef DEBUG (void) sigaddset(&sigset, SIGINT); #endif /*CONSTCOND*/ while (1) { tmout.tv_sec = nfscfg_domain_tmout; if ((ret = sigtimedwait(&sigset, &si, &tmout)) != 0) { /* * EAGAIN: no signals arrived during timeout. * check/update config files and continue. */ if (ret == -1 && errno == EAGAIN) { check_domain(0); continue; } switch (si.si_signo) { case SIGHUP: check_domain(1); break; #ifdef DEBUG case SIGINT: exit(0); #endif case SIGTERM: default: exit(si.si_signo); } } } /*NOTREACHED*/ return (NULL); } /* * Thread initialization. Mask out all signals we want our * signal handler to handle for us from any other threads. */ static void thr_init(void) { sigset_t sigset; long thr_flags = (THR_NEW_LWP|THR_DAEMON|THR_SUSPENDED); /* * Before we kick off any other threads, mask out desired * signals from main thread so that any subsequent threads * don't receive said signals. */ (void) thr_sigsetmask(0, NULL, &sigset); (void) sigaddset(&sigset, SIGHUP); (void) sigaddset(&sigset, SIGTERM); #ifdef DEBUG (void) sigaddset(&sigset, SIGINT); #endif (void) thr_sigsetmask(SIG_SETMASK, &sigset, NULL); /* * Create the signal handler thread suspended ! We do things * this way at setup time to minimize the probability of * introducing any race conditions _if_ the process were to * get a SIGHUP signal while creating a new DNS query thread * in get_dns_txt_domain(). */ if (thr_create(NULL, 0, sig_handler, 0, thr_flags, &sig_thread)) { syslog(LOG_ERR, gettext("Failed to create signal handling thread")); exit(4); } } static void daemon_init(void) { struct passwd pwd; struct group grp; char *pwd_buf; char *grp_buf; /* * passwd/group reentrant interfaces limits */ pwd_buflen = (size_t)sysconf(_SC_GETPW_R_SIZE_MAX); grp_buflen = (size_t)sysconf(_SC_GETGR_R_SIZE_MAX); /* * MT initialization is done first so that if there is the * need to fire an additional thread to continue to query * DNS, that thread is started off with the main thread's * sigmask. */ thr_init(); /* * Determine nfsmapid domain. */ check_domain(0); /* * In the case of nfsmapid running diskless, it is important * to get the initial connections to the nameservices * established to prevent problems like opening a devfs * node to contact a nameservice being blocked by the * resolution of an active devfs lookup. * First issue a set*ent to "open" the databases and then * get an entry and finally lookup a bogus entry to trigger * any lazy opens. */ setpwent(); setgrent(); (void) getpwent(); (void) getgrent(); if ((pwd_buf = malloc(pwd_buflen)) == NULL) return; (void) _uncached_getpwnam_r("NF21dmvP", &pwd, pwd_buf, pwd_buflen); (void) _uncached_getpwuid_r(1181794, &pwd, pwd_buf, pwd_buflen); if ((grp_buf = realloc(pwd_buf, grp_buflen)) == NULL) { free(pwd_buf); return; } (void) _uncached_getgrnam_r("NF21dmvP", &grp, grp_buf, grp_buflen); (void) _uncached_getgrgid_r(1181794, &grp, grp_buf, grp_buflen); free(grp_buf); } static int start_svcs(void) { int doorfd = -1; #ifdef DEBUG int dfd; #endif if ((doorfd = door_create(nfsmapid_func, NULL, DOOR_REFUSE_DESC | DOOR_NO_CANCEL)) == -1) { syslog(LOG_ERR, "Unable to create door: %m\n"); return (1); } #ifdef DEBUG /* * Create a file system path for the door */ if ((dfd = open(NFSMAPID_DOOR, O_RDWR|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) == -1) { syslog(LOG_ERR, "Unable to open %s: %m\n", NFSMAPID_DOOR); (void) close(doorfd); return (1); } /* * Clean up any stale associations */ (void) fdetach(NFSMAPID_DOOR); /* * Register in namespace to pass to the kernel to door_ki_open */ if (fattach(doorfd, NFSMAPID_DOOR) == -1) { syslog(LOG_ERR, "Unable to fattach door: %m\n"); (void) close(dfd); (void) close(doorfd); return (1); } (void) close(dfd); #endif /* * Now that we're actually running, go * ahead and flush the kernel flushes * Pass door name to kernel for door_ki_open */ idmap_kcall(doorfd); /* * Wait for incoming calls */ /*CONSTCOND*/ while (1) (void) pause(); syslog(LOG_ERR, gettext("Door server exited")); return (10); } /* ARGSUSED */ int main(int argc, char **argv) { MyName = argv[0]; (void) setlocale(LC_ALL, ""); (void) textdomain(TEXT_DOMAIN); /* _check_services() framework setup */ (void) _create_daemon_lock(NFSMAPID, DAEMON_UID, DAEMON_GID); /* * Open diag file in /var/run while we've got the perms */ open_diag_file(); /* * Initialize the daemon to basic + sys_nfs */ #ifndef DEBUG if (__init_daemon_priv(PU_RESETGROUPS|PU_CLEARLIMITSET, DAEMON_UID, DAEMON_GID, PRIV_SYS_NFS, (char *)NULL) == -1) { (void) fprintf(stderr, gettext("%s PRIV_SYS_NFS privilege " "missing\n"), MyName); exit(1); } #endif /* * Take away a subset of basic, while this is not the absolute * minimum, it is important that it is unique among other * daemons to insure that we get a unique cred that will * result in a unique open_owner. If not, we run the risk * of a diskless client deadlocking with a thread holding * the open_owner seqid lock while upcalling the daemon. * XXX This restriction will go away once we stop holding * XXX open_owner lock across rfscalls! */ (void) priv_set(PRIV_OFF, PRIV_PERMITTED, PRIV_FILE_LINK_ANY, PRIV_PROC_SESSION, (char *)NULL); #ifndef DEBUG daemonize(); switch (_enter_daemon_lock(NFSMAPID)) { case 0: break; case -1: syslog(LOG_ERR, "error locking for %s: %s", NFSMAPID, strerror(errno)); exit(3); default: /* daemon was already running */ exit(0); } #endif openlog(MyName, LOG_PID | LOG_NDELAY, LOG_DAEMON); /* Initialize daemon subsystems */ daemon_init(); /* start services */ return (start_svcs()); } /* * 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 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ provider nfsmapid { probe daemon__domain(string); }; #pragma D attributes Private/Private/Common provider nfsmapid provider #pragma D attributes Private/Private/Common provider nfsmapid module #pragma D attributes Private/Private/Common provider nfsmapid function #pragma D attributes Private/Private/Common provider nfsmapid name #pragma D attributes Private/Private/Common provider nfsmapid args /* * 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) 2009, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * Door server routines for nfsmapid daemon * Translate NFSv4 users and groups between numeric and string values */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nfs_resolve.h" #define UID_MAX_STR_LEN 11 /* Digits in UID_MAX + 1 */ #define DIAG_FILE "/var/run/nfs4_domain" /* * idmap_kcall() takes a door descriptor as it's argument when we * need to (re)establish the in-kernel door handles. When we only * want to flush the id kernel caches, we don't redo the door setup. */ #define FLUSH_KCACHES_ONLY (int)-1 FILE *n4_fp; int n4_fd; extern size_t pwd_buflen; extern size_t grp_buflen; extern thread_t sig_thread; /* * Prototypes */ extern void check_domain(int); extern void idmap_kcall(int); extern int _nfssys(int, void *); extern int valid_domain(const char *); extern int validate_id_str(const char *); extern int extract_domain(char *, char **, char **); extern void update_diag_file(char *); extern void *cb_update_domain(void *); extern int cur_domain_null(void); void nfsmapid_str_uid(struct mapid_arg *argp, size_t arg_size) { struct mapid_res result; struct passwd pwd; struct passwd *pwd_ptr; int pwd_rc; char *pwd_buf; char *user; char *domain; idmap_stat rc; if (argp->u_arg.len <= 0 || arg_size < MAPID_ARG_LEN(argp->u_arg.len)) { result.status = NFSMAPID_INVALID; result.u_res.uid = UID_NOBODY; goto done; } if (!extract_domain(argp->str, &user, &domain)) { unsigned long id; /* * Invalid "user@domain" string. Still, the user * part might be an encoded uid, so do a final check. * Remember, domain part of string was not set since * not a valid string. */ if (!validate_id_str(user)) { result.status = NFSMAPID_UNMAPPABLE; result.u_res.uid = UID_NOBODY; goto done; } errno = 0; id = strtoul(user, (char **)NULL, 10); /* * We don't accept ephemeral ids from the wire. */ if (errno || id > UID_MAX) { result.status = NFSMAPID_UNMAPPABLE; result.u_res.uid = UID_NOBODY; goto done; } result.u_res.uid = (uid_t)id; result.status = NFSMAPID_NUMSTR; goto done; } /* * String properly constructed. Now we check for domain and * group validity. */ if (!cur_domain_null() && !valid_domain(domain)) { /* * If the domain part of the string does not * match the NFS domain, try to map it using * idmap service. */ rc = idmap_getuidbywinname(user, domain, 0, &result.u_res.uid); if (rc != IDMAP_SUCCESS) { result.status = NFSMAPID_BADDOMAIN; result.u_res.uid = UID_NOBODY; goto done; } result.status = NFSMAPID_OK; goto done; } if ((pwd_buf = malloc(pwd_buflen)) == NULL || (pwd_rc = getpwnam_r(user, &pwd, pwd_buf, pwd_buflen, &pwd_ptr)) != 0 || pwd_ptr == NULL) { if (pwd_buf == NULL || pwd_rc != 0) result.status = NFSMAPID_INTERNAL; else { /* * Not a valid user */ result.status = NFSMAPID_NOTFOUND; free(pwd_buf); } result.u_res.uid = UID_NOBODY; goto done; } /* * Valid user entry */ result.u_res.uid = pwd.pw_uid; result.status = NFSMAPID_OK; free(pwd_buf); done: (void) door_return((char *)&result, sizeof (struct mapid_res), NULL, 0); } /* ARGSUSED1 */ void nfsmapid_uid_str(struct mapid_arg *argp, size_t arg_size) { struct mapid_res result; struct mapid_res *resp; struct passwd pwd; struct passwd *pwd_ptr; char *pwd_buf = NULL; char *idmap_buf = NULL; uid_t uid = argp->u_arg.uid; size_t uid_str_len; char *pw_str; size_t pw_str_len; char *at_str; size_t at_str_len; char dom_str[DNAMEMAX]; size_t dom_str_len; idmap_stat rc; if (uid == (uid_t)-1) { /* * Sentinel uid is not a valid id */ resp = &result; resp->status = NFSMAPID_BADID; resp->u_res.len = 0; goto done; } /* * Make local copy of domain for further manipuation * NOTE: mapid_get_domain() returns a ptr to TSD. */ if (cur_domain_null()) { dom_str_len = 0; dom_str[0] = '\0'; } else { dom_str_len = strlcpy(dom_str, mapid_get_domain(), DNAMEMAX); } /* * If uid is ephemeral then resolve it using idmap service */ if (uid > UID_MAX) { rc = idmap_getwinnamebyuid(uid, 0, &idmap_buf, NULL); if (rc != IDMAP_SUCCESS) { /* * We don't put stringified ephemeral uids on * the wire. */ resp = &result; resp->status = NFSMAPID_UNMAPPABLE; resp->u_res.len = 0; goto done; } /* * idmap_buf is already in the desired form i.e. name@domain */ pw_str = idmap_buf; pw_str_len = strlen(pw_str); at_str_len = dom_str_len = 0; at_str = ""; dom_str[0] = '\0'; goto gen_result; } /* * Handling non-ephemeral uids * * We want to encode the uid into a literal string... : * * - upon failure to allocate space from the heap * - if there is no current domain configured * - if there is no such uid in the passwd DB's */ if ((pwd_buf = malloc(pwd_buflen)) == NULL || dom_str_len == 0 || getpwuid_r(uid, &pwd, pwd_buf, pwd_buflen, &pwd_ptr) != 0 || pwd_ptr == NULL) { /* * If we could not allocate from the heap, try * allocating from the stack as a last resort. */ if (pwd_buf == NULL && (pwd_buf = alloca(MAPID_RES_LEN(UID_MAX_STR_LEN))) == NULL) { resp = &result; resp->status = NFSMAPID_INTERNAL; resp->u_res.len = 0; goto done; } /* * Constructing literal string without '@' so that * we'll know that it's not a user, but rather a * uid encoded string. */ pw_str = pwd_buf; (void) sprintf(pw_str, "%u", uid); pw_str_len = strlen(pw_str); at_str_len = dom_str_len = 0; at_str = ""; dom_str[0] = '\0'; } else { /* * Otherwise, we construct the "user@domain" string if * it's not already in that form. */ pw_str = pwd.pw_name; pw_str_len = strlen(pw_str); if (strchr(pw_str, '@') == NULL) { at_str = "@"; at_str_len = 1; } else { at_str_len = dom_str_len = 0; at_str = ""; dom_str[0] = '\0'; } } gen_result: uid_str_len = pw_str_len + at_str_len + dom_str_len; if ((resp = alloca(MAPID_RES_LEN(uid_str_len))) == NULL) { resp = &result; resp->status = NFSMAPID_INTERNAL; resp->u_res.len = 0; goto done; } /* LINTED format argument to sprintf */ (void) sprintf(resp->str, "%s%s%s", pw_str, at_str, dom_str); resp->u_res.len = uid_str_len; if (pwd_buf) free(pwd_buf); if (idmap_buf) idmap_free(idmap_buf); resp->status = NFSMAPID_OK; done: /* * There is a chance that the door_return will fail because the * resulting string is too large, try to indicate that if possible */ if (door_return((char *)resp, MAPID_RES_LEN(resp->u_res.len), NULL, 0) == -1) { resp->status = NFSMAPID_INTERNAL; resp->u_res.len = 0; (void) door_return((char *)&result, sizeof (struct mapid_res), NULL, 0); } } void nfsmapid_str_gid(struct mapid_arg *argp, size_t arg_size) { struct mapid_res result; struct group grp; struct group *grp_ptr; int grp_rc; char *grp_buf; char *group; char *domain; idmap_stat rc; if (argp->u_arg.len <= 0 || arg_size < MAPID_ARG_LEN(argp->u_arg.len)) { result.status = NFSMAPID_INVALID; result.u_res.gid = GID_NOBODY; goto done; } if (!extract_domain(argp->str, &group, &domain)) { unsigned long id; /* * Invalid "group@domain" string. Still, the * group part might be an encoded gid, so do a * final check. Remember, domain part of string * was not set since not a valid string. */ if (!validate_id_str(group)) { result.status = NFSMAPID_UNMAPPABLE; result.u_res.gid = GID_NOBODY; goto done; } errno = 0; id = strtoul(group, (char **)NULL, 10); /* * We don't accept ephemeral ids from the wire. */ if (errno || id > UID_MAX) { result.status = NFSMAPID_UNMAPPABLE; result.u_res.gid = GID_NOBODY; goto done; } result.u_res.gid = (gid_t)id; result.status = NFSMAPID_NUMSTR; goto done; } /* * String properly constructed. Now we check for domain and * group validity. */ if (!cur_domain_null() && !valid_domain(domain)) { /* * If the domain part of the string does not * match the NFS domain, try to map it using * idmap service. */ rc = idmap_getgidbywinname(group, domain, 0, &result.u_res.gid); if (rc != IDMAP_SUCCESS) { result.status = NFSMAPID_BADDOMAIN; result.u_res.gid = GID_NOBODY; goto done; } result.status = NFSMAPID_OK; goto done; } if ((grp_buf = malloc(grp_buflen)) == NULL || (grp_rc = getgrnam_r(group, &grp, grp_buf, grp_buflen, &grp_ptr)) != 0 || grp_ptr == NULL) { if (grp_buf == NULL || grp_rc != 0) result.status = NFSMAPID_INTERNAL; else { /* * Not a valid group */ result.status = NFSMAPID_NOTFOUND; free(grp_buf); } result.u_res.gid = GID_NOBODY; goto done; } /* * Valid group entry */ result.status = NFSMAPID_OK; result.u_res.gid = grp.gr_gid; free(grp_buf); done: (void) door_return((char *)&result, sizeof (struct mapid_res), NULL, 0); } /* ARGSUSED1 */ void nfsmapid_gid_str(struct mapid_arg *argp, size_t arg_size) { struct mapid_res result; struct mapid_res *resp; struct group grp; struct group *grp_ptr; char *grp_buf = NULL; char *idmap_buf = NULL; idmap_stat rc; gid_t gid = argp->u_arg.gid; size_t gid_str_len; char *gr_str; size_t gr_str_len; char *at_str; size_t at_str_len; char dom_str[DNAMEMAX]; size_t dom_str_len; if (gid == (gid_t)-1) { /* * Sentinel gid is not a valid id */ resp = &result; resp->status = NFSMAPID_BADID; resp->u_res.len = 0; goto done; } /* * Make local copy of domain for further manipuation * NOTE: mapid_get_domain() returns a ptr to TSD. */ if (cur_domain_null()) { dom_str_len = 0; dom_str[0] = '\0'; } else { dom_str_len = strlen(mapid_get_domain()); bcopy(mapid_get_domain(), dom_str, dom_str_len); dom_str[dom_str_len] = '\0'; } /* * If gid is ephemeral then resolve it using idmap service */ if (gid > UID_MAX) { rc = idmap_getwinnamebygid(gid, 0, &idmap_buf, NULL); if (rc != IDMAP_SUCCESS) { /* * We don't put stringified ephemeral gids on * the wire. */ resp = &result; resp->status = NFSMAPID_UNMAPPABLE; resp->u_res.len = 0; goto done; } /* * idmap_buf is already in the desired form i.e. name@domain */ gr_str = idmap_buf; gr_str_len = strlen(gr_str); at_str_len = dom_str_len = 0; at_str = ""; dom_str[0] = '\0'; goto gen_result; } /* * Handling non-ephemeral gids * * We want to encode the gid into a literal string... : * * - upon failure to allocate space from the heap * - if there is no current domain configured * - if there is no such gid in the group DB's */ if ((grp_buf = malloc(grp_buflen)) == NULL || dom_str_len == 0 || getgrgid_r(gid, &grp, grp_buf, grp_buflen, &grp_ptr) != 0 || grp_ptr == NULL) { /* * If we could not allocate from the heap, try * allocating from the stack as a last resort. */ if (grp_buf == NULL && (grp_buf = alloca(MAPID_RES_LEN(UID_MAX_STR_LEN))) == NULL) { resp = &result; resp->status = NFSMAPID_INTERNAL; resp->u_res.len = 0; goto done; } /* * Constructing literal string without '@' so that * we'll know that it's not a group, but rather a * gid encoded string. */ gr_str = grp_buf; (void) sprintf(gr_str, "%u", gid); gr_str_len = strlen(gr_str); at_str_len = dom_str_len = 0; at_str = ""; dom_str[0] = '\0'; } else { /* * Otherwise, we construct the "group@domain" string if * it's not already in that form. */ gr_str = grp.gr_name; gr_str_len = strlen(gr_str); if (strchr(gr_str, '@') == NULL) { at_str = "@"; at_str_len = 1; } else { at_str_len = dom_str_len = 0; at_str = ""; dom_str[0] = '\0'; } } gen_result: gid_str_len = gr_str_len + at_str_len + dom_str_len; if ((resp = alloca(MAPID_RES_LEN(gid_str_len))) == NULL) { resp = &result; resp->status = NFSMAPID_INTERNAL; resp->u_res.len = 0; goto done; } /* LINTED format argument to sprintf */ (void) sprintf(resp->str, "%s%s%s", gr_str, at_str, dom_str); resp->u_res.len = gid_str_len; if (grp_buf) free(grp_buf); if (idmap_buf) idmap_free(idmap_buf); resp->status = NFSMAPID_OK; done: /* * There is a chance that the door_return will fail because the * resulting string is too large, try to indicate that if possible */ if (door_return((char *)resp, MAPID_RES_LEN(resp->u_res.len), NULL, 0) == -1) { resp->status = NFSMAPID_INTERNAL; resp->u_res.len = 0; (void) door_return((char *)&result, sizeof (struct mapid_res), NULL, 0); } } void nfsmapid_server_netinfo(refd_door_args_t *referral_args, size_t arg_size) { char *res; int res_size; int error; int srsz = 0; char host[MAXHOSTNAMELEN]; utf8string *nfsfsloc_args; refd_door_res_t *door_res; refd_door_res_t failed_res; struct nfs_fsl_info *nfs_fsloc_res; if (arg_size < sizeof (refd_door_args_t)) { failed_res.res_status = EINVAL; res = (char *)&failed_res; res_size = sizeof (refd_door_res_t); syslog(LOG_ERR, "nfsmapid_server_netinfo failed: Invalid data\n"); goto send_response; } if (decode_args(xdr_utf8string, (refd_door_args_t *)referral_args, (caddr_t *)&nfsfsloc_args, sizeof (utf8string))) { syslog(LOG_ERR, "cannot allocate memory"); failed_res.res_status = ENOMEM; failed_res.xdr_len = 0; res = (caddr_t)&failed_res; res_size = sizeof (refd_door_res_t); goto send_response; } if (nfsfsloc_args->utf8string_len >= MAXHOSTNAMELEN) { syslog(LOG_ERR, "argument too large"); failed_res.res_status = EOVERFLOW; failed_res.xdr_len = 0; res = (caddr_t)&failed_res; res_size = sizeof (refd_door_res_t); goto send_response; } snprintf(host, nfsfsloc_args->utf8string_len + 1, "%s", nfsfsloc_args->utf8string_val); nfs_fsloc_res = get_nfs4ref_info(host, NFS_PORT, NFS_V4); xdr_free(xdr_utf8string, (char *)&nfsfsloc_args); if (nfs_fsloc_res) { error = 0; error = encode_res(xdr_nfs_fsl_info, &door_res, (caddr_t)nfs_fsloc_res, &res_size); free_nfs4ref_info(nfs_fsloc_res); if (error != 0) { syslog(LOG_ERR, "error allocating fs_locations " "results buffer"); failed_res.res_status = error; failed_res.xdr_len = srsz; res = (caddr_t)&failed_res; res_size = sizeof (refd_door_res_t); } else { door_res->res_status = 0; res = (caddr_t)door_res; } } else { failed_res.res_status = EINVAL; failed_res.xdr_len = 0; res = (caddr_t)&failed_res; res_size = sizeof (refd_door_res_t); } send_response: srsz = res_size; errno = 0; error = door_return(res, res_size, NULL, 0); if (errno == E2BIG) { failed_res.res_status = EOVERFLOW; failed_res.xdr_len = srsz; res = (caddr_t)&failed_res; res_size = sizeof (refd_door_res_t); } else { res = NULL; res_size = 0; } door_return(res, res_size, NULL, 0); } /* ARGSUSED */ void nfsmapid_func(void *cookie, char *argp, size_t arg_size, door_desc_t *dp, uint_t n_desc) { struct mapid_arg *mapargp; struct mapid_res mapres; refd_door_args_t *referral_args; /* * Make sure we have a valid argument */ if (arg_size < sizeof (struct mapid_arg)) { mapres.status = NFSMAPID_INVALID; mapres.u_res.len = 0; (void) door_return((char *)&mapres, sizeof (struct mapid_res), NULL, 0); return; } /* LINTED pointer cast */ mapargp = (struct mapid_arg *)argp; referral_args = (refd_door_args_t *)argp; switch (mapargp->cmd) { case NFSMAPID_STR_UID: nfsmapid_str_uid(mapargp, arg_size); return; case NFSMAPID_UID_STR: nfsmapid_uid_str(mapargp, arg_size); return; case NFSMAPID_STR_GID: nfsmapid_str_gid(mapargp, arg_size); return; case NFSMAPID_GID_STR: nfsmapid_gid_str(mapargp, arg_size); return; case NFSMAPID_SRV_NETINFO: nfsmapid_server_netinfo(referral_args, arg_size); default: break; } mapres.status = NFSMAPID_INVALID; mapres.u_res.len = 0; (void) door_return((char *)&mapres, sizeof (struct mapid_res), NULL, 0); } /* * mapid_get_domain() always returns a ptr to TSD, so the * check for a NULL domain is not a simple comparison with * NULL but we need to check the contents of the TSD data. */ int cur_domain_null(void) { char *p; if ((p = mapid_get_domain()) == NULL) return (1); return (p[0] == '\0'); } int extract_domain(char *cp, char **upp, char **dpp) { /* * Caller must insure that the string is valid */ *upp = cp; if ((*dpp = strchr(cp, '@')) == NULL) return (0); *(*dpp)++ = '\0'; return (1); } int valid_domain(const char *dom) { const char *whoami = "valid_domain"; if (!mapid_stdchk_domain(dom)) { syslog(LOG_ERR, gettext("%s: Invalid inbound domain name %s."), whoami, dom); return (0); } /* * NOTE: mapid_get_domain() returns a ptr to TSD. */ return (strcasecmp(dom, mapid_get_domain()) == 0); } int validate_id_str(const char *id) { while (*id) { if (!isdigit(*id++)) return (0); } return (1); } void idmap_kcall(int door_id) { struct nfsidmap_args args; if (door_id >= 0) { args.state = 1; args.did = door_id; } else { args.state = 0; args.did = 0; } (void) _nfssys(NFS_IDMAP, &args); } /* * Get the current NFS domain. * * If nfsmapid_domain is set in NFS SMF, then it is the NFS domain; * otherwise, the DNS domain is used. */ void check_domain(int sighup) { const char *whoami = "check_domain"; static int setup_done = 0; static cb_t cb; /* * Construct the arguments to be passed to libmapid interface * If called in response to a SIGHUP, reset any cached DNS TXT * RR state. */ cb.fcn = cb_update_domain; cb.signal = sighup; mapid_reeval_domain(&cb); /* * Restart the signal handler thread if we're still setting up */ if (!setup_done) { setup_done = 1; if (thr_continue(sig_thread)) { syslog(LOG_ERR, gettext("%s: Fatal error: signal " "handler thread could not be restarted."), whoami); exit(6); } } } /* * Need to be able to open the DIAG_FILE before nfsmapid(8) * releases it's root priviledges. The DIAG_FILE then remains * open for the duration of this nfsmapid instance via n4_fd. */ void open_diag_file() { static int msg_done = 0; if ((n4_fp = fopen(DIAG_FILE, "w+")) != NULL) { n4_fd = fileno(n4_fp); return; } if (msg_done) return; syslog(LOG_ERR, "Failed to create %s. Enable syslog " "daemon.debug for more info", DIAG_FILE); msg_done = 1; } /* * When a new domain name is configured, save to DIAG_FILE * and log to syslog, with LOG_DEBUG level (if configured). */ void update_diag_file(char *new) { char buf[DNAMEMAX]; ssize_t n; size_t len; (void) lseek(n4_fd, (off_t)0, SEEK_SET); (void) ftruncate(n4_fd, 0); (void) snprintf(buf, DNAMEMAX, "%s\n", new); len = strlen(buf); n = write(n4_fd, buf, len); if (n < 0 || n < len) syslog(LOG_DEBUG, "Could not write %s to diag file", new); (void) fsync(n4_fd); syslog(LOG_DEBUG, "nfsmapid domain = %s", new); } /* * Callback function for libmapid. This will be called * by the lib, everytime the nfsmapid(8) domain changes. */ void * cb_update_domain(void *arg) { char *new_dname = (char *)arg; DTRACE_PROBE1(nfsmapid, daemon__domain, new_dname); update_diag_file(new_dname); idmap_kcall(FLUSH_KCACHES_ONLY); return (NULL); } bool_t xdr_utf8string(XDR *xdrs, utf8string *objp) { if (xdrs->x_op != XDR_FREE) return (xdr_bytes(xdrs, (char **)&objp->utf8string_val, (uint_t *)&objp->utf8string_len, NFS4_MAX_UTF8STRING)); return (TRUE); } int decode_args(xdrproc_t xdrfunc, refd_door_args_t *argp, caddr_t *xdrargs, int size) { XDR xdrs; caddr_t tmpargs = (caddr_t)&((refd_door_args_t *)argp)->xdr_arg; size_t arg_size = ((refd_door_args_t *)argp)->xdr_len; xdrmem_create(&xdrs, tmpargs, arg_size, XDR_DECODE); *xdrargs = calloc(1, size); if (*xdrargs == NULL) { syslog(LOG_ERR, "error allocating arguments buffer"); return (ENOMEM); } if (!(*xdrfunc)(&xdrs, *xdrargs)) { free(*xdrargs); *xdrargs = NULL; syslog(LOG_ERR, "error decoding arguments"); return (EINVAL); } return (0); } int encode_res( xdrproc_t xdrfunc, refd_door_res_t **results, caddr_t resp, int *size) { XDR xdrs; *size = xdr_sizeof((*xdrfunc), resp); *results = malloc(sizeof (refd_door_res_t) + *size); if (*results == NULL) { return (ENOMEM); } (*results)->xdr_len = *size; *size = sizeof (refd_door_res_t) + (*results)->xdr_len; xdrmem_create(&xdrs, (caddr_t)((*results)->xdr_res), (*results)->xdr_len, XDR_ENCODE); if (!(*xdrfunc)(&xdrs, resp)) { (*results)->res_status = EINVAL; syslog(LOG_ERR, "error encoding results"); return ((*results)->res_status); } (*results)->res_status = 0; return ((*results)->res_status); } bool_t xdr_knetconfig(XDR *xdrs, struct knetconfig *objp) { rpc_inline_t *buf; int i; u_longlong_t dev64; #if !defined(_LP64) uint32_t major, minor; #endif if (!xdr_u_int(xdrs, &objp->knc_semantics)) return (FALSE); if (!xdr_opaque(xdrs, objp->knc_protofmly, KNC_STRSIZE)) return (FALSE); if (!xdr_opaque(xdrs, objp->knc_proto, KNC_STRSIZE)) return (FALSE); /* * For interoperability between 32-bit daemon and 64-bit kernel, * we always treat dev_t as 64-bit number and do the expanding * or compression of dev_t as needed. * We have to hand craft the conversion since there is no available * function in ddi.c. Besides ddi.c is available only in the kernel * and we want to keep both user and kernel of xdr_knetconfig() the * same for consistency. */ if (xdrs->x_op == XDR_ENCODE) { #if defined(_LP64) dev64 = objp->knc_rdev; #else major = (objp->knc_rdev >> NBITSMINOR32) & MAXMAJ32; minor = objp->knc_rdev & MAXMIN32; dev64 = (((unsigned long long)major) << NBITSMINOR64) | minor; #endif if (!xdr_u_longlong_t(xdrs, &dev64)) return (FALSE); } if (xdrs->x_op == XDR_DECODE) { #if defined(_LP64) if (!xdr_u_longlong_t(xdrs, (u_longlong_t *)&objp->knc_rdev)) return (FALSE); #else if (!xdr_u_longlong_t(xdrs, &dev64)) return (FALSE); major = (dev64 >> NBITSMINOR64) & L_MAXMAJ32; minor = dev64 & L_MAXMIN32; objp->knc_rdev = (major << L_BITSMINOR32) | minor; #endif } if (xdrs->x_op == XDR_ENCODE) { buf = XDR_INLINE(xdrs, (8) * BYTES_PER_XDR_UNIT); if (buf == NULL) { if (!xdr_vector(xdrs, (char *)objp->knc_unused, 8, sizeof (uint_t), (xdrproc_t)xdr_u_int)) return (FALSE); } else { uint_t *genp; for (i = 0, genp = objp->knc_unused; i < 8; i++) { #if defined(_LP64) || defined(_KERNEL) IXDR_PUT_U_INT32(buf, *genp++); #else IXDR_PUT_U_LONG(buf, *genp++); #endif } } return (TRUE); } else if (xdrs->x_op == XDR_DECODE) { buf = XDR_INLINE(xdrs, (8) * BYTES_PER_XDR_UNIT); if (buf == NULL) { if (!xdr_vector(xdrs, (char *)objp->knc_unused, 8, sizeof (uint_t), (xdrproc_t)xdr_u_int)) return (FALSE); } else { uint_t *genp; for (i = 0, genp = objp->knc_unused; i < 8; i++) { #if defined(_LP64) || defined(_KERNEL) *genp++ = IXDR_GET_U_INT32(buf); #else *genp++ = IXDR_GET_U_LONG(buf); #endif } } return (TRUE); } if (!xdr_vector(xdrs, (char *)objp->knc_unused, 8, sizeof (uint_t), (xdrproc_t)xdr_u_int)) return (FALSE); return (TRUE); } /* * used by NFSv4 referrals to get info needed for NFSv4 referral mount. */ bool_t xdr_nfs_fsl_info(XDR *xdrs, struct nfs_fsl_info *objp) { if (!xdr_u_int(xdrs, &objp->netbuf_len)) return (FALSE); if (!xdr_u_int(xdrs, &objp->netnm_len)) return (FALSE); if (!xdr_u_int(xdrs, &objp->knconf_len)) return (FALSE); if (!xdr_string(xdrs, &objp->netname, ~0)) return (FALSE); if (!xdr_pointer(xdrs, (char **)&objp->addr, objp->netbuf_len, (xdrproc_t)xdr_netbuf)) return (FALSE); if (!xdr_pointer(xdrs, (char **)&objp->knconf, objp->knconf_len, (xdrproc_t)xdr_knetconfig)) return (FALSE); return (TRUE); } /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Test nfsmapid. This program is not shipped on the binary release. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static char nobody_str[] = "nobody"; static int nfs_idmap_str_uid(utf8string *, uid_t *); static int nfs_idmap_uid_str(uid_t, utf8string *); static int nfs_idmap_str_gid(utf8string *, gid_t *); static int nfs_idmap_gid_str(gid_t, utf8string *); static void usage() { fprintf(stderr, gettext( "\nUsage:\tstr2uid string\n" "\tstr2gid string\n" "\tuid2str uid\n" "\tgid2str gid\n" "\techo string\n" "\texit|quit\n")); } static int read_line(char *buf, int size) { int len; /* read the next line. If cntl-d, return with zero char count */ printf(gettext("\n> ")); if (fgets(buf, size, stdin) == NULL) return (0); len = strlen(buf); buf[--len] = '\0'; return (len); } static int parse_input_line(char *input_line, int *argc, char ***argv) { const char nil = '\0'; char *chptr; int chr_cnt; int arg_cnt = 0; int ch_was_space = 1; int ch_is_space; chr_cnt = strlen(input_line); /* Count the arguments in the input_line string */ *argc = 1; for (chptr = &input_line[0]; *chptr != nil; chptr++) { ch_is_space = isspace(*chptr); if (ch_is_space && !ch_was_space) { (*argc)++; } ch_was_space = ch_is_space; } if (ch_was_space) { (*argc)--; } /* minus trailing spaces */ /* Now that we know how many args calloc the argv array */ *argv = calloc((*argc)+1, sizeof (char *)); chptr = (char *)(&input_line[0]); for (ch_was_space = 1; *chptr != nil; chptr++) { ch_is_space = isspace(*chptr); if (ch_is_space) { *chptr = nil; /* replace each space with nil */ } else if (ch_was_space) { /* begining of word? */ (*argv)[arg_cnt++] = chptr; /* new argument ? */ } ch_was_space = ch_is_space; } return (chr_cnt); } char * mapstat(int stat) { switch (stat) { case NFSMAPID_OK: return ("NFSMAPID_OK"); case NFSMAPID_NUMSTR: return ("NFSMAPID_NUMSTR"); case NFSMAPID_UNMAPPABLE: return ("NFSMAPID_UNMAPPABLE"); case NFSMAPID_INVALID: return ("NFSMAPID_INVALID"); case NFSMAPID_INTERNAL: return ("NFSMAPID_INTERNAL"); case NFSMAPID_BADDOMAIN: return ("NFSMAPID_BADDOMAIN"); case NFSMAPID_BADID: return ("NFSMAPID_BADID"); case NFSMAPID_NOTFOUND: return ("NFSMAPID_NOTFOUND"); case EINVAL: return ("EINVAL"); case ECOMM: return ("ECOMM"); case ENOMEM: return ("ENOMEM"); default: printf(" unknown error %d ", stat); return ("..."); } } int do_test(char *input_buf) { int argc, seal_argc; char **argv, **argv_array; char *cmd; int i, bufsize = 512; char str_buf[512]; utf8string str; uid_t uid; gid_t gid; int stat; argv = 0; if (parse_input_line(input_buf, &argc, &argv) == 0) { printf(gettext("\n")); return (1); } /* * remember argv_array address, which is memory calloc'd by * parse_input_line, so it can be free'd at the end of the loop. */ argv_array = argv; if (argc < 1) { usage(); free(argv_array); return (0); } cmd = argv[0]; if (strcmp(cmd, "str2uid") == 0) { if (argc < 2) { usage(); free(argv_array); return (0); } str.utf8string_val = argv[1]; str.utf8string_len = strlen(argv[1]); stat = nfs_idmap_str_uid(&str, &uid); printf(gettext("%u stat=%s \n"), uid, mapstat(stat)); } else if (strcmp(cmd, "str2gid") == 0) { if (argc < 2) { usage(); free(argv_array); return (0); } str.utf8string_val = argv[1]; str.utf8string_len = strlen(argv[1]); stat = nfs_idmap_str_gid(&str, &gid); printf(gettext("%u stat=%s \n"), gid, mapstat(stat)); } else if (strcmp(cmd, "uid2str") == 0) { if (argc < 2) { usage(); free(argv_array); return (0); } uid = atoi(argv[1]); bzero(str_buf, bufsize); str.utf8string_val = str_buf; stat = nfs_idmap_uid_str(uid, &str); printf(gettext("%s stat=%s\n"), str.utf8string_val, mapstat(stat)); } else if (strcmp(cmd, "gid2str") == 0) { if (argc < 2) { usage(); free(argv_array); return (0); } gid = atoi(argv[1]); bzero(str_buf, bufsize); str.utf8string_val = str_buf; stat = nfs_idmap_gid_str(gid, &str); printf(gettext("%s stat=%s\n"), str.utf8string_val, mapstat(stat)); } else if (strcmp(cmd, "echo") == 0) { for (i = 1; i < argc; i++) printf("%s ", argv[i]); printf("\n"); } else if (strcmp(cmd, "exit") == 0 || strcmp(cmd, "quit") == 0) { printf(gettext("\n")); free(argv_array); return (1); } else usage(); /* free argv array */ free(argv_array); return (0); } int main(int argc, char **argv) { char buf[512]; int len, ret; (void) setlocale(LC_ALL, ""); #ifndef TEXT_DOMAIN #define TEXT_DOMAIN "" #endif (void) textdomain(TEXT_DOMAIN); usage(); /* * Loop, repeatedly calling parse_input_line() to get the * next line and parse it into argc and argv. Act on the * arguements found on the line. */ do { len = read_line(buf, 512); if (len) ret = do_test(buf); } while (!ret); return (0); } #define NFSMAPID_DOOR "/var/run/nfsmapid_door" /* * Gen the door handle for connecting to the nfsmapid process. * Keep the door cached. This call may be made quite often. */ int nfs_idmap_doorget() { static int doorfd = -1; if (doorfd != -1) return (doorfd); if ((doorfd = open(NFSMAPID_DOOR, O_RDWR)) == -1) { perror(NFSMAPID_DOOR); exit(1); } return (doorfd); } /* * Convert a user utf-8 string identifier into its local uid. */ int nfs_idmap_str_uid(utf8string *u8s, uid_t *uid) { struct mapid_arg *mapargp; struct mapid_res mapres; struct mapid_res *mapresp = &mapres; struct mapid_res *resp = mapresp; door_arg_t door_args; int doorfd; int error = 0; static int msg_done = 0; if (!u8s || !u8s->utf8string_val || !u8s->utf8string_len || (u8s->utf8string_val[0] == '\0')) { error = EINVAL; goto s2u_done; } if (bcmp(u8s->utf8string_val, "nobody", 6) == 0) { /* * If "nobody", just short circuit and bail */ *uid = UID_NOBODY; goto s2u_done; } if ((mapargp = malloc(MAPID_ARG_LEN(u8s->utf8string_len))) == NULL) { (void) fprintf(stderr, "Unable to malloc %d bytes\n", MAPID_ARG_LEN(u8s->utf8string_len)); error = ENOMEM; goto s2u_done; } mapargp->cmd = NFSMAPID_STR_UID; mapargp->u_arg.len = u8s->utf8string_len; (void) bcopy(u8s->utf8string_val, mapargp->str, mapargp->u_arg.len); mapargp->str[mapargp->u_arg.len] = '\0'; door_args.data_ptr = (char *)mapargp; door_args.data_size = MAPID_ARG_LEN(mapargp->u_arg.len); door_args.desc_ptr = NULL; door_args.desc_num = 0; door_args.rbuf = (char *)mapresp; door_args.rsize = sizeof (struct mapid_res); /* * call to the nfsmapid daemon */ if ((doorfd = nfs_idmap_doorget()) == -1) { if (!msg_done) { fprintf(stderr, "nfs_idmap_str_uid: Can't communicate" " with mapping daemon nfsmapid\n"); msg_done = 1; } error = ECOMM; free(mapargp); goto s2u_done; } if (door_call(doorfd, &door_args) == -1) { perror("door_call failed"); error = EINVAL; free(mapargp); goto s2u_done; } free(mapargp); resp = (struct mapid_res *)door_args.rbuf; switch (resp->status) { case NFSMAPID_OK: *uid = resp->u_res.uid; break; case NFSMAPID_NUMSTR: *uid = resp->u_res.uid; error = resp->status; goto out; default: case NFSMAPID_UNMAPPABLE: case NFSMAPID_INVALID: case NFSMAPID_INTERNAL: case NFSMAPID_BADDOMAIN: case NFSMAPID_BADID: case NFSMAPID_NOTFOUND: error = resp->status; goto s2u_done; } s2u_done: if (error) *uid = UID_NOBODY; out: if (resp != mapresp) munmap(door_args.rbuf, door_args.rsize); return (error); } /* * Convert a uid into its utf-8 string representation. */ int nfs_idmap_uid_str(uid_t uid, /* uid to map */ utf8string *u8s) /* resulting utf-8 string for uid */ { struct mapid_arg maparg; struct mapid_res mapres; struct mapid_res *mapresp = &mapres; struct mapid_res *resp = mapresp; door_arg_t door_args; int doorfd; int error = 0; static int msg_done = 0; if (uid == UID_NOBODY) { u8s->utf8string_len = strlen("nobody"); u8s->utf8string_val = nobody_str; goto u2s_done; } /* * Daemon call... */ maparg.cmd = NFSMAPID_UID_STR; maparg.u_arg.uid = uid; door_args.data_ptr = (char *)&maparg; door_args.data_size = sizeof (struct mapid_arg); door_args.desc_ptr = NULL; door_args.desc_num = 0; door_args.rbuf = (char *)mapresp; door_args.rsize = sizeof (struct mapid_res); if ((doorfd = nfs_idmap_doorget()) == -1) { if (!msg_done) { fprintf(stderr, "nfs_idmap_uid_str: Can't " "communicate with mapping daemon nfsmapid\n"); msg_done = 1; } error = ECOMM; goto u2s_done; } if (door_call(doorfd, &door_args) == -1) { perror("door_call failed"); error = EINVAL; goto u2s_done; } resp = (struct mapid_res *)door_args.rbuf; if (resp->status != NFSMAPID_OK) { error = resp->status; goto u2s_done; } if (resp->u_res.len != strlen(resp->str)) { (void) fprintf(stderr, "Incorrect length %d expected %d\n", resp->u_res.len, strlen(resp->str)); error = NFSMAPID_INVALID; goto u2s_done; } u8s->utf8string_len = resp->u_res.len; bcopy(resp->str, u8s->utf8string_val, u8s->utf8string_len); u2s_done: if (resp != mapresp) munmap(door_args.rbuf, door_args.rsize); return (error); } /* * Convert a group utf-8 string identifier into its local gid. */ int nfs_idmap_str_gid(utf8string *u8s, gid_t *gid) { struct mapid_arg *mapargp; struct mapid_res mapres; struct mapid_res *mapresp = &mapres; struct mapid_res *resp = mapresp; door_arg_t door_args; int doorfd; int error = 0; static int msg_done = 0; if (!u8s || !u8s->utf8string_val || !u8s->utf8string_len || (u8s->utf8string_val[0] == '\0')) { error = EINVAL; goto s2g_done; } if (bcmp(u8s->utf8string_val, "nobody", 6) == 0) { /* * If "nobody", just short circuit and bail */ *gid = GID_NOBODY; goto s2g_done; } if ((mapargp = malloc(MAPID_ARG_LEN(u8s->utf8string_len))) == NULL) { (void) fprintf(stderr, "Unable to malloc %d bytes\n", MAPID_ARG_LEN(u8s->utf8string_len)); error = ENOMEM; goto s2g_done; } mapargp->cmd = NFSMAPID_STR_GID; mapargp->u_arg.len = u8s->utf8string_len; (void) bcopy(u8s->utf8string_val, mapargp->str, mapargp->u_arg.len); mapargp->str[mapargp->u_arg.len] = '\0'; door_args.data_ptr = (char *)mapargp; door_args.data_size = MAPID_ARG_LEN(mapargp->u_arg.len); door_args.desc_ptr = NULL; door_args.desc_num = 0; door_args.rbuf = (char *)mapresp; door_args.rsize = sizeof (struct mapid_res); /* * call to the nfsmapid daemon */ if ((doorfd = nfs_idmap_doorget()) == -1) { if (!msg_done) { fprintf(stderr, "nfs_idmap_str_uid: Can't communicate" " with mapping daemon nfsmapid\n"); msg_done = 1; } error = ECOMM; free(mapargp); goto s2g_done; } if (door_call(doorfd, &door_args) == -1) { perror("door_call failed"); error = EINVAL; free(mapargp); goto s2g_done; } free(mapargp); resp = (struct mapid_res *)door_args.rbuf; switch (resp->status) { case NFSMAPID_OK: *gid = resp->u_res.gid; break; case NFSMAPID_NUMSTR: *gid = resp->u_res.gid; error = resp->status; goto out; default: case NFSMAPID_UNMAPPABLE: case NFSMAPID_INVALID: case NFSMAPID_INTERNAL: case NFSMAPID_BADDOMAIN: case NFSMAPID_BADID: case NFSMAPID_NOTFOUND: error = resp->status; goto s2g_done; } s2g_done: if (error) *gid = GID_NOBODY; out: if (resp != mapresp) munmap(door_args.rbuf, door_args.rsize); return (error); } /* * Convert a gid into its utf-8 string representation. */ int nfs_idmap_gid_str(gid_t gid, /* gid to map */ utf8string *g8s) /* resulting utf-8 string for gid */ { struct mapid_arg maparg; struct mapid_res mapres; struct mapid_res *mapresp = &mapres; struct mapid_res *resp = mapresp; door_arg_t door_args; int error = 0; int doorfd; static int msg_done = 0; if (gid == GID_NOBODY) { g8s->utf8string_len = strlen("nobody"); g8s->utf8string_val = nobody_str; goto g2s_done; } /* * Daemon call... */ maparg.cmd = NFSMAPID_GID_STR; maparg.u_arg.gid = gid; door_args.data_ptr = (char *)&maparg; door_args.data_size = sizeof (struct mapid_arg); door_args.desc_ptr = NULL; door_args.desc_num = 0; door_args.rbuf = (char *)mapresp; door_args.rsize = sizeof (struct mapid_res); if ((doorfd = nfs_idmap_doorget()) == -1) { if (!msg_done) { fprintf(stderr, "nfs_idmap_uid_str: Can't " "communicate with mapping daemon nfsmapid\n"); msg_done = 1; } error = ECOMM; goto g2s_done; } if (door_call(doorfd, &door_args) == -1) { perror("door_call failed"); error = EINVAL; goto g2s_done; } resp = (struct mapid_res *)door_args.rbuf; if (resp->status != NFSMAPID_OK) { error = resp->status; goto g2s_done; } if (resp->u_res.len != strlen(resp->str)) { (void) fprintf(stderr, "Incorrect length %d expected %d\n", resp->u_res.len, strlen(resp->str)); error = NFSMAPID_INVALID; goto g2s_done; } g8s->utf8string_len = resp->u_res.len; bcopy(resp->str, g8s->utf8string_val, g8s->utf8string_len); g2s_done: if (resp != mapresp) munmap(door_args.rbuf, door_args.rsize); return (error); } # # 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 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. PROG= nfsref POFILE= nfsref.po include ../../../Makefile.cmd CFLAGS += $(CCVERBOSE) -I../lib OBJS= nfsref.o ref_subr.o SRCS= nfsref.c CSTD= $(CSTD_GNU99) CERRWARN += -Wno-unused-variable # not linted SMATCH=off LDLIBS += -lreparse -lnvpair -lnsl -lumem $(PROG): $(OBJS) $(LINK.c) -o $@ $(LDLIBS) $(OBJS) $(POST_PROCESS) FILEMODE= 0555 .KEEP_STATE: all: $(PROG) ref_subr.o: ../lib/ref_subr.c $(COMPILE.c) ../lib/ref_subr.c install: all $(ROOTUSRSBINPROG) catalog: $(POFILE) $(POFILE): $(SRCS) $(RM) $@ $(COMPILE.cpp) $(SRCS) > $(POFILE).i $(XGETTEXT) $(XGETFLAGS) $(POFILE).i sed "/^domain/d" messages.po > $@ $(RM) $(POFILE).i messages.po clean: $(RM) $(OBJS) include ../../../Makefile.targ /* * 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 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 "ref_subr.h" #ifndef TEXT_DOMAIN #define TEXT_DOMAIN "SUNW_OST_OSCMD" #endif /* TEXT_DOMAIN */ extern int errno; void usage() { fprintf(stderr, gettext("Usage:\n")); fprintf(stderr, gettext("\tnfsref [-t type] add path location [location ...]\n")); fprintf(stderr, gettext("\tnfsref [-t type] remove path\n")); fprintf(stderr, gettext("\tnfsref [-t type] lookup path\n")); } /* * Copy a string from source to destination, escaping space * with a backslash and escaping the escape character as well. */ int add_escape(char *src, char *dest, int limit) { char *sp, *dp; int destlen = 0; sp = src; dp = dest; while (*sp && destlen < limit) { if (*sp == '\\') { *dp++ = '\\'; *dp++ = '\\'; destlen++; } else if (*sp == ' ') { *dp++ = '\\'; *dp++ = ' '; destlen++; } else *dp++ = *sp; destlen++; sp++; } if (limit <= 0) return (-1); return (destlen); } int addref(char *sl_path, char *svc_type, int optind, int argc, char *argv[]) { int err, fd, i, len, oldlen, notfound = 0; char *text, *location; nvlist_t *nvl = NULL; char buf[SYMLINK_MAX]; struct stat sbuf; /* Get an nvlist */ nvl = reparse_init(); if (nvl == NULL) return (ENOMEM); /* Get the reparse point data, if the RP exists */ err = readlink(sl_path, buf, SYMLINK_MAX); if (err == -1) { if (errno == ENOENT) { notfound = 1; err = 0; } else { reparse_free(nvl); return (errno); } } else { buf[err] = '\0'; } /* Get any data into nvlist */ if (notfound == 0) err = reparse_parse(buf, nvl); if (err != 0) { reparse_free(nvl); return (err); } /* * Accumulate multiple locations on the command line into 'buf' */ oldlen = len = 0; location = NULL; for (i = optind; i < argc; i++) { bzero(buf, sizeof (buf)); len += add_escape(argv[i], buf, SYMLINK_MAX) + 2; location = realloc(location, len); location[oldlen] = '\0'; oldlen = len; strlcat(location, buf, len); strlcat(location, " ", len); } location[len - 2] = '\0'; /* Add to the list */ err = reparse_add(nvl, svc_type, location); if (err) { reparse_free(nvl); return (err); } /* Get the new or modified symlink contents */ err = reparse_unparse(nvl, &text); reparse_free(nvl); if (err) return (err); /* Delete first if found */ if (notfound == 0) { err = reparse_delete(sl_path); if (err) { free(text); return (err); } } /* Finally, write out the reparse point */ err = reparse_create(sl_path, text); free(text); if (err) return (err); err = lstat(sl_path, &sbuf); if (err == 0 && strcasecmp(sbuf.st_fstype, "ZFS") != 0) printf(gettext( "Warning: referrals do not work on this filesystem\n")); if (notfound) printf(gettext("Created reparse point %s\n"), sl_path); else printf(gettext("Added to reparse point %s\n"), sl_path); return (0); } int delref(char *sl_path, char *svc_type) { char *cp; char *svc_data; int err; nvlist_t *nvl; nvpair_t *curr; char buf[SYMLINK_MAX]; int fd, fd2; FILE *fp, *fp2; char uuid[UUID_PRINTABLE_STRING_LENGTH], path[256], loc[2048]; /* Get an nvlist */ if (!(nvl = reparse_init())) return (ENOMEM); /* Get the symlink data (should be there) */ err = readlink(sl_path, buf, SYMLINK_MAX); if (err == -1) { reparse_free(nvl); return (errno); } buf[err] = '\0'; /* Get the records into the nvlist */ err = reparse_parse(buf, nvl); if (err) { reparse_free(nvl); return (err); } /* Remove from nvlist */ err = reparse_remove(nvl, svc_type); if (err) { reparse_free(nvl); return (err); } /* Any list entries left? If so, turn nvlist back to string. */ curr = nvlist_next_nvpair(nvl, NULL); if (curr != NULL) { err = reparse_unparse(nvl, &cp); reparse_free(nvl); if (err) return (err); } else { reparse_free(nvl); cp = NULL; } /* Finally, delete and perhaps recreate the reparse point */ err = reparse_delete(sl_path); if (err) { free(cp); return (err); } if (cp != NULL) { err = reparse_create(sl_path, cp); free(cp); if (err) return (err); } printf(gettext("Removed svc_type '%s' from %s\n"), svc_type, sl_path); return (err); } int lookup(char *sl_path, char *svc_type, int type_set) { int err; size_t bufsize; char buf[1024]; char *type, *svc_data; nvlist_t *nvl; nvpair_t *curr; fs_locations4 fsl; XDR xdr; if (!(nvl = reparse_init())) return (-1); /* Get reparse point data */ err = readlink(sl_path, buf, SYMLINK_MAX); if (err == -1) return (errno); buf[err] = '\0'; /* Parse it to an nvlist */ err = reparse_parse(buf, nvl); if (err) { reparse_free(nvl); return (err); } /* Look for entries of the requested service type */ curr = NULL; while ((curr = nvlist_next_nvpair(nvl, curr)) != NULL) { type = nvpair_name(curr); if (type_set && strcasecmp(type, svc_type) == 0) break; if (!type_set && strncasecmp(type, "nfs", 3) == 0) break; } if (curr == NULL) { reparse_free(nvl); return (ENOENT); } /* Get the service data and look it up */ nvpair_value_string(curr, &svc_data); bufsize = sizeof (buf); err = reparse_deref(type, svc_data, buf, &bufsize); reparse_free(nvl); if (err) return (err); xdrmem_create(&xdr, buf, bufsize, XDR_DECODE); err = xdr_fs_locations4(&xdr, &fsl); XDR_DESTROY(&xdr); if (err != TRUE) return (ENOENT); printf(gettext("%s points to: "), sl_path); print_referral_summary(&fsl); return (0); } extern char *optarg; extern int optind, optopt; int main(int argc, char *argv[]) { char *command, *sl_path, *svc_type; int c, type_set, err; (void) setlocale(LC_ALL, ""); (void) textdomain(TEXT_DOMAIN); svc_type = "nfs-basic"; /* Default from SMF some day */ type_set = 0; /* Lookup any nfs type */ /* Look for options (just the service type now) */ while ((c = getopt(argc, argv, "t:")) != -1) { switch (c) { case 't': svc_type = optarg; type_set = 1; break; default: usage(); exit(1); } } /* Make sure there's at least a command and one argument */ if (optind + 1 >= argc) { usage(); exit(1); } err = rp_plugin_init(); switch (err) { case RP_OK: break; case RP_NO_PLUGIN_DIR: fprintf(stderr, gettext("Warning: no plugin directory, continuing...\n")); break; case RP_NO_PLUGIN: fprintf(stderr, gettext("Warning: no plugin found, continuing...\n")); break; case RP_NO_MEMORY: fprintf(stderr, gettext("rp_plugin_init failed, no memory\n")); exit(0); default: fprintf(stderr, gettext("rp_plugin_init failed, error %d\n"), err); exit(0); } command = argv[optind++]; sl_path = argv[optind++]; if (strcmp(command, "add") == 0) { if (optind >= argc) { usage(); exit(1); } err = addref(sl_path, svc_type, optind, argc, argv); } else if (strcmp(command, "remove") == 0) { err = delref(sl_path, svc_type); } else if (strcmp(command, "lookup") == 0) { err = lookup(sl_path, svc_type, type_set); } else { usage(); exit(1); } if (err != 0) fprintf(stderr, gettext("Command %s failed: %s\n"), command, strerror(err)); return (err); } # # 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 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. # Copyright 2025 Hans Rosenfeld PROG= nfsstat include ../../../Makefile.cmd CFLAGS += $(CCVERBOSE) COMMON= nfs_sec.o OBJS= nfsstat.o $(COMMON) SRCS= nfsstat.c ../lib/nfs_sec.c CLEANFILES = $(OBJS) include $(SRC)/cmd/stat/Makefile.stat CSTD= $(CSTD_GNU99) CERRWARN += -Wno-parentheses # not linted SMATCH=off LDLIBS += -lkstat -lnsl $(PROG): $(OBJS) $(LINK.c) -o $@ $(LDLIBS) $(OBJS) $(POST_PROCESS) FILEMODE= 0555 .KEEP_STATE: all: $(PROG) nfs_sec.o: ../lib/nfs_sec.c $(COMPILE.c) ../lib/nfs_sec.c $(POST_PROCESS_O) install: all $(ROOTPROG) clean: $(RM) $(CLEANFILES) include ../../../Makefile.targ /* * 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 */ /* LINTLIBRARY */ /* PROTOLIB1 */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright 2015 Nexenta Systems, Inc. All rights reserved. */ /* * nfsstat: Network File System statistics * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "statcommon.h" static kstat_ctl_t *kc = NULL; /* libkstat cookie */ static kstat_t *rpc_clts_client_kstat, *rpc_clts_server_kstat; static kstat_t *rpc_cots_client_kstat, *rpc_cots_server_kstat; static kstat_t *rpc_rdma_client_kstat, *rpc_rdma_server_kstat; static kstat_t *nfs_client_kstat, *nfs_server_v2_kstat, *nfs_server_v3_kstat; static kstat_t *nfs4_client_kstat, *nfs_server_v4_kstat; static kstat_t *rfsproccnt_v2_kstat, *rfsproccnt_v3_kstat, *rfsproccnt_v4_kstat; static kstat_t *rfsreqcnt_v2_kstat, *rfsreqcnt_v3_kstat, *rfsreqcnt_v4_kstat; static kstat_t *aclproccnt_v2_kstat, *aclproccnt_v3_kstat; static kstat_t *aclreqcnt_v2_kstat, *aclreqcnt_v3_kstat; static kstat_t *ksum_kstat; static void handle_sig(int); static int getstats_rpc(void); static int getstats_nfs(void); static int getstats_rfsproc(int); static int getstats_rfsreq(int); static int getstats_aclproc(void); static int getstats_aclreq(void); static void putstats(void); static void setup(void); static void cr_print(int); static void sr_print(int); static void cn_print(int, int); static void sn_print(int, int); static void ca_print(int, int); static void sa_print(int, int); static void req_print(kstat_t *, kstat_t *, int, int, int); static void req_print_v4(kstat_t *, kstat_t *, int, int); static void stat_print(const char *, kstat_t *, kstat_t *, int, int); static void nfsstat_kstat_sum(kstat_t *, kstat_t *, kstat_t *); static void stats_timer(int); static void safe_zalloc(void **, uint_t, int); static int safe_strtoi(char const *, char *); static void nfsstat_kstat_copy(kstat_t *, kstat_t *, int); static kid_t safe_kstat_read(kstat_ctl_t *, kstat_t *, void *); static kid_t safe_kstat_write(kstat_ctl_t *, kstat_t *, void *); static void usage(void); static void mi_print(void); static int ignore(char *); static int interval; /* interval between stats */ static int count; /* number of iterations the stat is printed */ #define MAX_COLUMNS 80 #define MAX_PATHS 50 /* max paths that can be taken by -m */ /* * MI4_MIRRORMOUNT is canonically defined in nfs4_clnt.h, but we cannot * include that file here. Same with MI4_REFERRAL. */ #define MI4_MIRRORMOUNT 0x4000 #define MI4_REFERRAL 0x8000 #define NFS_V4 4 static int req_width(kstat_t *, int); static int stat_width(kstat_t *, int); static char *path [MAX_PATHS] = {NULL}; /* array to store the multiple paths */ /* * Struct holds the previous kstat values so * we can compute deltas when using the -i flag */ typedef struct old_kstat { kstat_t kst; int tot; } old_kstat_t; static old_kstat_t old_rpc_clts_client_kstat, old_rpc_clts_server_kstat; static old_kstat_t old_rpc_cots_client_kstat, old_rpc_cots_server_kstat; static old_kstat_t old_rpc_rdma_client_kstat, old_rpc_rdma_server_kstat; static old_kstat_t old_nfs_client_kstat, old_nfs_server_v2_kstat; static old_kstat_t old_nfs_server_v3_kstat, old_ksum_kstat; static old_kstat_t old_nfs4_client_kstat, old_nfs_server_v4_kstat; static old_kstat_t old_rfsproccnt_v2_kstat, old_rfsproccnt_v3_kstat; static old_kstat_t old_rfsproccnt_v4_kstat, old_rfsreqcnt_v2_kstat; static old_kstat_t old_rfsreqcnt_v3_kstat, old_rfsreqcnt_v4_kstat; static old_kstat_t old_aclproccnt_v2_kstat, old_aclproccnt_v3_kstat; static old_kstat_t old_aclreqcnt_v2_kstat, old_aclreqcnt_v3_kstat; static uint_t timestamp_fmt = NODATE; #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it isn't */ #endif int main(int argc, char *argv[]) { int c, go_forever, j; int cflag = 0; /* client stats */ int sflag = 0; /* server stats */ int nflag = 0; /* nfs stats */ int rflag = 0; /* rpc stats */ int mflag = 0; /* mount table stats */ int aflag = 0; /* print acl statistics */ int vflag = 0; /* version specified, 0 specifies all */ int zflag = 0; /* zero stats after printing */ char *split_line = "*******************************************" "*************************************"; interval = 0; count = 0; go_forever = 0; (void) setlocale(LC_ALL, ""); (void) textdomain(TEXT_DOMAIN); while ((c = getopt(argc, argv, "cnrsmzav:T:")) != EOF) { switch (c) { case 'c': cflag++; break; case 'n': nflag++; break; case 'r': rflag++; break; case 's': sflag++; break; case 'm': mflag++; break; case 'z': if (geteuid()) fail(0, "Must be root for z flag\n"); zflag++; break; case 'a': aflag++; break; case 'v': vflag = atoi(optarg); if ((vflag < 2) || (vflag > 4)) fail(0, "Invalid version number\n"); break; case 'T': if (optarg) { if (*optarg == 'u') timestamp_fmt = UDATE; else if (*optarg == 'd') timestamp_fmt = DDATE; else usage(); } else { usage(); } break; case '?': default: usage(); } } if (((argc - optind) > 0) && !mflag) { interval = safe_strtoi(argv[optind], "invalid interval"); if (interval < 1) fail(0, "invalid interval\n"); optind++; if ((argc - optind) > 0) { count = safe_strtoi(argv[optind], "invalid count"); if (count <= 0) fail(0, "invalid count\n"); } optind++; if ((argc - optind) > 0) usage(); /* * no count number was set, so we will loop infinitely * at interval specified */ if (!count) go_forever = 1; stats_timer(interval); } else if (mflag) { if (cflag || rflag || sflag || zflag || nflag || aflag || vflag) fail(0, "The -m flag may not be used with any other flags"); for (j = 0; (argc - optind > 0) && (j < (MAX_PATHS - 1)); j++) { path[j] = argv[optind]; if (*path[j] != '/') fail(0, "Please fully qualify your pathname " "with a leading '/'"); optind++; } path[j] = NULL; if (argc - optind > 0) fprintf(stderr, "Only the first 50 paths " "will be searched for\n"); } setup(); do { if (mflag) { mi_print(); } else { if (timestamp_fmt != NODATE) print_timestamp(timestamp_fmt); if (sflag && (rpc_clts_server_kstat == NULL || nfs_server_v4_kstat == NULL)) { fprintf(stderr, "nfsstat: kernel is not configured with " "the server nfs and rpc code.\n"); } /* if s and nothing else, all 3 prints are called */ if (sflag || (!sflag && !cflag)) { if (rflag || (!rflag && !nflag && !aflag)) sr_print(zflag); if (nflag || (!rflag && !nflag && !aflag)) sn_print(zflag, vflag); if (aflag || (!rflag && !nflag && !aflag)) sa_print(zflag, vflag); } if (cflag && (rpc_clts_client_kstat == NULL || nfs_client_kstat == NULL)) { fprintf(stderr, "nfsstat: kernel is not configured with" " the client nfs and rpc code.\n"); } if (cflag || (!sflag && !cflag)) { if (rflag || (!rflag && !nflag && !aflag)) cr_print(zflag); if (nflag || (!rflag && !nflag && !aflag)) cn_print(zflag, vflag); if (aflag || (!rflag && !nflag && !aflag)) ca_print(zflag, vflag); } } if (zflag) putstats(); if (interval) printf("%s\n", split_line); if (interval > 0) (void) pause(); } while ((--count > 0) || go_forever); kstat_close(kc); free(ksum_kstat); return (0); } static int getstats_rpc(void) { int field_width = 0; if (rpc_clts_client_kstat != NULL) { safe_kstat_read(kc, rpc_clts_client_kstat, NULL); field_width = stat_width(rpc_clts_client_kstat, field_width); } if (rpc_cots_client_kstat != NULL) { safe_kstat_read(kc, rpc_cots_client_kstat, NULL); field_width = stat_width(rpc_cots_client_kstat, field_width); } if (rpc_rdma_client_kstat != NULL) { safe_kstat_read(kc, rpc_rdma_client_kstat, NULL); field_width = stat_width(rpc_rdma_client_kstat, field_width); } if (rpc_clts_server_kstat != NULL) { safe_kstat_read(kc, rpc_clts_server_kstat, NULL); field_width = stat_width(rpc_clts_server_kstat, field_width); } if (rpc_cots_server_kstat != NULL) { safe_kstat_read(kc, rpc_cots_server_kstat, NULL); field_width = stat_width(rpc_cots_server_kstat, field_width); } if (rpc_rdma_server_kstat != NULL) { safe_kstat_read(kc, rpc_rdma_server_kstat, NULL); field_width = stat_width(rpc_rdma_server_kstat, field_width); } return (field_width); } static int getstats_nfs(void) { int field_width = 0; if (nfs_client_kstat != NULL) { safe_kstat_read(kc, nfs_client_kstat, NULL); field_width = stat_width(nfs_client_kstat, field_width); } if (nfs4_client_kstat != NULL) { safe_kstat_read(kc, nfs4_client_kstat, NULL); field_width = stat_width(nfs4_client_kstat, field_width); } if (nfs_server_v2_kstat != NULL) { safe_kstat_read(kc, nfs_server_v2_kstat, NULL); field_width = stat_width(nfs_server_v2_kstat, field_width); } if (nfs_server_v3_kstat != NULL) { safe_kstat_read(kc, nfs_server_v3_kstat, NULL); field_width = stat_width(nfs_server_v3_kstat, field_width); } if (nfs_server_v4_kstat != NULL) { safe_kstat_read(kc, nfs_server_v4_kstat, NULL); field_width = stat_width(nfs_server_v4_kstat, field_width); } return (field_width); } static int getstats_rfsproc(int ver) { int field_width = 0; if ((ver == 2) && (rfsproccnt_v2_kstat != NULL)) { safe_kstat_read(kc, rfsproccnt_v2_kstat, NULL); field_width = req_width(rfsproccnt_v2_kstat, field_width); } if ((ver == 3) && (rfsproccnt_v3_kstat != NULL)) { safe_kstat_read(kc, rfsproccnt_v3_kstat, NULL); field_width = req_width(rfsproccnt_v3_kstat, field_width); } if ((ver == 4) && (rfsproccnt_v4_kstat != NULL)) { safe_kstat_read(kc, rfsproccnt_v4_kstat, NULL); field_width = req_width(rfsproccnt_v4_kstat, field_width); } return (field_width); } static int getstats_rfsreq(int ver) { int field_width = 0; if ((ver == 2) && (rfsreqcnt_v2_kstat != NULL)) { safe_kstat_read(kc, rfsreqcnt_v2_kstat, NULL); field_width = req_width(rfsreqcnt_v2_kstat, field_width); } if ((ver == 3) && (rfsreqcnt_v3_kstat != NULL)) { safe_kstat_read(kc, rfsreqcnt_v3_kstat, NULL); field_width = req_width(rfsreqcnt_v3_kstat, field_width); } if ((ver == 4) && (rfsreqcnt_v4_kstat != NULL)) { safe_kstat_read(kc, rfsreqcnt_v4_kstat, NULL); field_width = req_width(rfsreqcnt_v4_kstat, field_width); } return (field_width); } static int getstats_aclproc(void) { int field_width = 0; if (aclproccnt_v2_kstat != NULL) { safe_kstat_read(kc, aclproccnt_v2_kstat, NULL); field_width = req_width(aclproccnt_v2_kstat, field_width); } if (aclproccnt_v3_kstat != NULL) { safe_kstat_read(kc, aclproccnt_v3_kstat, NULL); field_width = req_width(aclproccnt_v3_kstat, field_width); } return (field_width); } static int getstats_aclreq(void) { int field_width = 0; if (aclreqcnt_v2_kstat != NULL) { safe_kstat_read(kc, aclreqcnt_v2_kstat, NULL); field_width = req_width(aclreqcnt_v2_kstat, field_width); } if (aclreqcnt_v3_kstat != NULL) { safe_kstat_read(kc, aclreqcnt_v3_kstat, NULL); field_width = req_width(aclreqcnt_v3_kstat, field_width); } return (field_width); } static void putstats(void) { if (rpc_clts_client_kstat != NULL) safe_kstat_write(kc, rpc_clts_client_kstat, NULL); if (rpc_cots_client_kstat != NULL) safe_kstat_write(kc, rpc_cots_client_kstat, NULL); if (rpc_rdma_client_kstat != NULL) safe_kstat_write(kc, rpc_rdma_client_kstat, NULL); if (nfs_client_kstat != NULL) safe_kstat_write(kc, nfs_client_kstat, NULL); if (nfs4_client_kstat != NULL) safe_kstat_write(kc, nfs4_client_kstat, NULL); if (rpc_clts_server_kstat != NULL) safe_kstat_write(kc, rpc_clts_server_kstat, NULL); if (rpc_cots_server_kstat != NULL) safe_kstat_write(kc, rpc_cots_server_kstat, NULL); if (rpc_rdma_server_kstat != NULL) safe_kstat_write(kc, rpc_rdma_server_kstat, NULL); if (nfs_server_v2_kstat != NULL) safe_kstat_write(kc, nfs_server_v2_kstat, NULL); if (nfs_server_v3_kstat != NULL) safe_kstat_write(kc, nfs_server_v3_kstat, NULL); if (nfs_server_v4_kstat != NULL) safe_kstat_write(kc, nfs_server_v4_kstat, NULL); if (rfsproccnt_v2_kstat != NULL) safe_kstat_write(kc, rfsproccnt_v2_kstat, NULL); if (rfsproccnt_v3_kstat != NULL) safe_kstat_write(kc, rfsproccnt_v3_kstat, NULL); if (rfsproccnt_v4_kstat != NULL) safe_kstat_write(kc, rfsproccnt_v4_kstat, NULL); if (rfsreqcnt_v2_kstat != NULL) safe_kstat_write(kc, rfsreqcnt_v2_kstat, NULL); if (rfsreqcnt_v3_kstat != NULL) safe_kstat_write(kc, rfsreqcnt_v3_kstat, NULL); if (rfsreqcnt_v4_kstat != NULL) safe_kstat_write(kc, rfsreqcnt_v4_kstat, NULL); if (aclproccnt_v2_kstat != NULL) safe_kstat_write(kc, aclproccnt_v2_kstat, NULL); if (aclproccnt_v3_kstat != NULL) safe_kstat_write(kc, aclproccnt_v3_kstat, NULL); if (aclreqcnt_v2_kstat != NULL) safe_kstat_write(kc, aclreqcnt_v2_kstat, NULL); if (aclreqcnt_v3_kstat != NULL) safe_kstat_write(kc, aclreqcnt_v3_kstat, NULL); } static void setup(void) { if ((kc = kstat_open()) == NULL) fail(1, "kstat_open(): can't open /dev/kstat"); /* alloc space for our temporary kstat */ safe_zalloc((void **)&ksum_kstat, sizeof (kstat_t), 0); rpc_clts_client_kstat = kstat_lookup(kc, "unix", 0, "rpc_clts_client"); rpc_clts_server_kstat = kstat_lookup(kc, "unix", 0, "rpc_clts_server"); rpc_cots_client_kstat = kstat_lookup(kc, "unix", 0, "rpc_cots_client"); rpc_cots_server_kstat = kstat_lookup(kc, "unix", 0, "rpc_cots_server"); rpc_rdma_client_kstat = kstat_lookup(kc, "unix", 0, "rpc_rdma_client"); rpc_rdma_server_kstat = kstat_lookup(kc, "unix", 0, "rpc_rdma_server"); nfs_client_kstat = kstat_lookup(kc, "nfs", 0, "nfs_client"); nfs4_client_kstat = kstat_lookup(kc, "nfs", 0, "nfs4_client"); nfs_server_v2_kstat = kstat_lookup(kc, "nfs", 2, "nfs_server"); nfs_server_v3_kstat = kstat_lookup(kc, "nfs", 3, "nfs_server"); nfs_server_v4_kstat = kstat_lookup(kc, "nfs", 4, "nfs_server"); rfsproccnt_v2_kstat = kstat_lookup(kc, "nfs", 0, "rfsproccnt_v2"); rfsproccnt_v3_kstat = kstat_lookup(kc, "nfs", 0, "rfsproccnt_v3"); rfsproccnt_v4_kstat = kstat_lookup(kc, "nfs", 0, "rfsproccnt_v4"); rfsreqcnt_v2_kstat = kstat_lookup(kc, "nfs", 0, "rfsreqcnt_v2"); rfsreqcnt_v3_kstat = kstat_lookup(kc, "nfs", 0, "rfsreqcnt_v3"); rfsreqcnt_v4_kstat = kstat_lookup(kc, "nfs", 0, "rfsreqcnt_v4"); aclproccnt_v2_kstat = kstat_lookup(kc, "nfs_acl", 0, "aclproccnt_v2"); aclproccnt_v3_kstat = kstat_lookup(kc, "nfs_acl", 0, "aclproccnt_v3"); aclreqcnt_v2_kstat = kstat_lookup(kc, "nfs_acl", 0, "aclreqcnt_v2"); aclreqcnt_v3_kstat = kstat_lookup(kc, "nfs_acl", 0, "aclreqcnt_v3"); if (rpc_clts_client_kstat == NULL && rpc_cots_server_kstat == NULL && rfsproccnt_v2_kstat == NULL && rfsreqcnt_v3_kstat == NULL) fail(0, "Multiple kstat lookups failed." "Your kernel module may not be loaded\n"); } static int req_width(kstat_t *req, int field_width) { int i, nreq, per, len; char fixlen[128]; kstat_named_t *knp; uint64_t tot; tot = 0; knp = KSTAT_NAMED_PTR(req); for (i = 0; i < req->ks_ndata; i++) tot += knp[i].value.ui64; knp = kstat_data_lookup(req, "null"); nreq = req->ks_ndata - (knp - KSTAT_NAMED_PTR(req)); for (i = 0; i < nreq; i++) { len = strlen(knp[i].name) + 1; if (field_width < len) field_width = len; if (tot) per = (int)(knp[i].value.ui64 * 100 / tot); else per = 0; (void) sprintf(fixlen, "%" PRIu64 " %d%%", knp[i].value.ui64, per); len = strlen(fixlen) + 1; if (field_width < len) field_width = len; } return (field_width); } static int stat_width(kstat_t *req, int field_width) { int i, nreq, len; char fixlen[128]; kstat_named_t *knp; knp = KSTAT_NAMED_PTR(req); nreq = req->ks_ndata; for (i = 0; i < nreq; i++) { len = strlen(knp[i].name) + 1; if (field_width < len) field_width = len; (void) sprintf(fixlen, "%" PRIu64, knp[i].value.ui64); len = strlen(fixlen) + 1; if (field_width < len) field_width = len; } return (field_width); } static void cr_print(int zflag) { int field_width; field_width = getstats_rpc(); if (field_width == 0) return; stat_print("\nClient rpc:\nConnection oriented:", rpc_cots_client_kstat, &old_rpc_cots_client_kstat.kst, field_width, zflag); stat_print("Connectionless:", rpc_clts_client_kstat, &old_rpc_clts_client_kstat.kst, field_width, zflag); stat_print("RDMA based:", rpc_rdma_client_kstat, &old_rpc_rdma_client_kstat.kst, field_width, zflag); } static void sr_print(int zflag) { int field_width; field_width = getstats_rpc(); if (field_width == 0) return; stat_print("\nServer rpc:\nConnection oriented:", rpc_cots_server_kstat, &old_rpc_cots_server_kstat.kst, field_width, zflag); stat_print("Connectionless:", rpc_clts_server_kstat, &old_rpc_clts_server_kstat.kst, field_width, zflag); stat_print("RDMA based:", rpc_rdma_server_kstat, &old_rpc_rdma_server_kstat.kst, field_width, zflag); } static void cn_print(int zflag, int vflag) { int field_width; field_width = getstats_nfs(); if (field_width == 0) return; if (vflag == 0) { nfsstat_kstat_sum(nfs_client_kstat, nfs4_client_kstat, ksum_kstat); stat_print("\nClient nfs:", ksum_kstat, &old_ksum_kstat.kst, field_width, zflag); } if (vflag == 2 || vflag == 3) { stat_print("\nClient nfs:", nfs_client_kstat, &old_nfs_client_kstat.kst, field_width, zflag); } if (vflag == 4) { stat_print("\nClient nfs:", nfs4_client_kstat, &old_nfs4_client_kstat.kst, field_width, zflag); } if (vflag == 2 || vflag == 0) { field_width = getstats_rfsreq(2); req_print(rfsreqcnt_v2_kstat, &old_rfsreqcnt_v2_kstat.kst, 2, field_width, zflag); } if (vflag == 3 || vflag == 0) { field_width = getstats_rfsreq(3); req_print(rfsreqcnt_v3_kstat, &old_rfsreqcnt_v3_kstat.kst, 3, field_width, zflag); } if (vflag == 4 || vflag == 0) { field_width = getstats_rfsreq(4); req_print_v4(rfsreqcnt_v4_kstat, &old_rfsreqcnt_v4_kstat.kst, field_width, zflag); } } static void sn_print(int zflag, int vflag) { int field_width; field_width = getstats_nfs(); if (field_width == 0) return; if (vflag == 2 || vflag == 0) { stat_print("\nServer NFSv2:", nfs_server_v2_kstat, &old_nfs_server_v2_kstat.kst, field_width, zflag); } if (vflag == 3 || vflag == 0) { stat_print("\nServer NFSv3:", nfs_server_v3_kstat, &old_nfs_server_v3_kstat.kst, field_width, zflag); } if (vflag == 4 || vflag == 0) { stat_print("\nServer NFSv4:", nfs_server_v4_kstat, &old_nfs_server_v4_kstat.kst, field_width, zflag); } if (vflag == 2 || vflag == 0) { field_width = getstats_rfsproc(2); req_print(rfsproccnt_v2_kstat, &old_rfsproccnt_v2_kstat.kst, 2, field_width, zflag); } if (vflag == 3 || vflag == 0) { field_width = getstats_rfsproc(3); req_print(rfsproccnt_v3_kstat, &old_rfsproccnt_v3_kstat.kst, 3, field_width, zflag); } if (vflag == 4 || vflag == 0) { field_width = getstats_rfsproc(4); req_print_v4(rfsproccnt_v4_kstat, &old_rfsproccnt_v4_kstat.kst, field_width, zflag); } } static void ca_print(int zflag, int vflag) { int field_width; field_width = getstats_aclreq(); if (field_width == 0) return; printf("\nClient nfs_acl:\n"); if (vflag == 2 || vflag == 0) { req_print(aclreqcnt_v2_kstat, &old_aclreqcnt_v2_kstat.kst, 2, field_width, zflag); } if (vflag == 3 || vflag == 0) { req_print(aclreqcnt_v3_kstat, &old_aclreqcnt_v3_kstat.kst, 3, field_width, zflag); } } static void sa_print(int zflag, int vflag) { int field_width; field_width = getstats_aclproc(); if (field_width == 0) return; printf("\nServer nfs_acl:\n"); if (vflag == 2 || vflag == 0) { req_print(aclproccnt_v2_kstat, &old_aclproccnt_v2_kstat.kst, 2, field_width, zflag); } if (vflag == 3 || vflag == 0) { req_print(aclproccnt_v3_kstat, &old_aclproccnt_v3_kstat.kst, 3, field_width, zflag); } } #define MIN(a, b) ((a) < (b) ? (a) : (b)) static void req_print(kstat_t *req, kstat_t *req_old, int ver, int field_width, int zflag) { int i, j, nreq, per, ncolumns; uint64_t tot, old_tot; char fixlen[128]; kstat_named_t *knp; kstat_named_t *kptr; kstat_named_t *knp_old; if (req == NULL) return; if (field_width == 0) return; ncolumns = (MAX_COLUMNS -1)/field_width; knp = kstat_data_lookup(req, "null"); knp_old = KSTAT_NAMED_PTR(req_old); kptr = KSTAT_NAMED_PTR(req); nreq = req->ks_ndata - (knp - KSTAT_NAMED_PTR(req)); tot = 0; old_tot = 0; if (knp_old == NULL) { old_tot = 0; } for (i = 0; i < req->ks_ndata; i++) tot += kptr[i].value.ui64; if (interval && knp_old != NULL) { for (i = 0; i < req_old->ks_ndata; i++) old_tot += knp_old[i].value.ui64; tot -= old_tot; } printf("Version %d: (%" PRIu64 " calls)\n", ver, tot); for (i = 0; i < nreq; i += ncolumns) { for (j = i; j < MIN(i + ncolumns, nreq); j++) { printf("%-*s", field_width, knp[j].name); } printf("\n"); for (j = i; j < MIN(i + ncolumns, nreq); j++) { if (tot && interval && knp_old != NULL) per = (int)((knp[j].value.ui64 - knp_old[j].value.ui64) * 100 / tot); else if (tot) per = (int)(knp[j].value.ui64 * 100 / tot); else per = 0; (void) sprintf(fixlen, "%" PRIu64 " %d%% ", ((interval && knp_old != NULL) ? (knp[j].value.ui64 - knp_old[j].value.ui64) : knp[j].value.ui64), per); printf("%-*s", field_width, fixlen); } printf("\n"); } if (zflag) { for (i = 0; i < req->ks_ndata; i++) knp[i].value.ui64 = 0; } if (knp_old != NULL) nfsstat_kstat_copy(req, req_old, 1); else nfsstat_kstat_copy(req, req_old, 0); } /* * Separate version of the req_print() to deal with V4 and its use of * procedures and operations. It looks odd to have the counts for * both of those lumped into the same set of statistics so this * function (copy of req_print() does the separation and titles). */ #define COUNT 2 static void req_print_v4(kstat_t *req, kstat_t *req_old, int field_width, int zflag) { int i, j, nreq, per, ncolumns; uint64_t tot, tot_ops, old_tot, old_tot_ops; char fixlen[128]; kstat_named_t *kptr; kstat_named_t *knp; kstat_named_t *kptr_old; if (req == NULL) return; if (field_width == 0) return; ncolumns = (MAX_COLUMNS)/field_width; kptr = KSTAT_NAMED_PTR(req); kptr_old = KSTAT_NAMED_PTR(req_old); if (kptr_old == NULL) { old_tot_ops = 0; old_tot = 0; } else { old_tot = kptr_old[0].value.ui64 + kptr_old[1].value.ui64; for (i = 2, old_tot_ops = 0; i < req_old->ks_ndata; i++) old_tot_ops += kptr_old[i].value.ui64; } /* Count the number of operations sent */ for (i = 2, tot_ops = 0; i < req->ks_ndata; i++) tot_ops += kptr[i].value.ui64; /* For v4 NULL/COMPOUND are the only procedures */ tot = kptr[0].value.ui64 + kptr[1].value.ui64; if (interval) { tot -= old_tot; tot_ops -= old_tot_ops; } printf("Version 4: (%" PRIu64 " calls)\n", tot); knp = kstat_data_lookup(req, "null"); nreq = req->ks_ndata - (knp - KSTAT_NAMED_PTR(req)); for (i = 0; i < COUNT; i += ncolumns) { for (j = i; j < MIN(i + ncolumns, 2); j++) { printf("%-*s", field_width, knp[j].name); } printf("\n"); for (j = i; j < MIN(i + ncolumns, 2); j++) { if (tot && interval && kptr_old != NULL) per = (int)((knp[j].value.ui64 - kptr_old[j].value.ui64) * 100 / tot); else if (tot) per = (int)(knp[j].value.ui64 * 100 / tot); else per = 0; (void) sprintf(fixlen, "%" PRIu64 " %d%% ", ((interval && kptr_old != NULL) ? (knp[j].value.ui64 - kptr_old[j].value.ui64) : knp[j].value.ui64), per); printf("%-*s", field_width, fixlen); } printf("\n"); } printf("Version 4: (%" PRIu64 " operations)\n", tot_ops); for (i = 2; i < nreq; i += ncolumns) { for (j = i; j < MIN(i + ncolumns, nreq); j++) { printf("%-*s", field_width, knp[j].name); } printf("\n"); for (j = i; j < MIN(i + ncolumns, nreq); j++) { if (tot_ops && interval && kptr_old != NULL) per = (int)((knp[j].value.ui64 - kptr_old[j].value.ui64) * 100 / tot_ops); else if (tot_ops) per = (int)(knp[j].value.ui64 * 100 / tot_ops); else per = 0; (void) sprintf(fixlen, "%" PRIu64 " %d%% ", ((interval && kptr_old != NULL) ? (knp[j].value.ui64 - kptr_old[j].value.ui64) : knp[j].value.ui64), per); printf("%-*s", field_width, fixlen); } printf("\n"); } if (zflag) { for (i = 0; i < req->ks_ndata; i++) kptr[i].value.ui64 = 0; } if (kptr_old != NULL) nfsstat_kstat_copy(req, req_old, 1); else nfsstat_kstat_copy(req, req_old, 0); } static void stat_print(const char *title_string, kstat_t *req, kstat_t *req_old, int field_width, int zflag) { int i, j, nreq, ncolumns; char fixlen[128]; kstat_named_t *knp; kstat_named_t *knp_old; if (req == NULL) return; if (field_width == 0) return; printf("%s\n", title_string); ncolumns = (MAX_COLUMNS -1)/field_width; /* MEANS knp = (kstat_named_t *)req->ks_data */ knp = KSTAT_NAMED_PTR(req); nreq = req->ks_ndata; knp_old = KSTAT_NAMED_PTR(req_old); for (i = 0; i < nreq; i += ncolumns) { /* prints out the titles of the columns */ for (j = i; j < MIN(i + ncolumns, nreq); j++) { printf("%-*s", field_width, knp[j].name); } printf("\n"); /* prints out the stat numbers */ for (j = i; j < MIN(i + ncolumns, nreq); j++) { (void) sprintf(fixlen, "%" PRIu64 " ", (interval && knp_old != NULL) ? (knp[j].value.ui64 - knp_old[j].value.ui64) : knp[j].value.ui64); printf("%-*s", field_width, fixlen); } printf("\n"); } if (zflag) { for (i = 0; i < req->ks_ndata; i++) knp[i].value.ui64 = 0; } if (knp_old != NULL) nfsstat_kstat_copy(req, req_old, 1); else nfsstat_kstat_copy(req, req_old, 0); } static void nfsstat_kstat_sum(kstat_t *kstat1, kstat_t *kstat2, kstat_t *sum) { int i; kstat_named_t *knp1, *knp2, *knpsum; if (kstat1 == NULL || kstat2 == NULL) return; knp1 = KSTAT_NAMED_PTR(kstat1); knp2 = KSTAT_NAMED_PTR(kstat2); if (sum->ks_data == NULL) nfsstat_kstat_copy(kstat1, sum, 0); knpsum = KSTAT_NAMED_PTR(sum); for (i = 0; i < (kstat1->ks_ndata); i++) knpsum[i].value.ui64 = knp1[i].value.ui64 + knp2[i].value.ui64; } /* * my_dir and my_path could be pointers */ struct myrec { ulong_t my_fsid; char my_dir[MAXPATHLEN]; char *my_path; char *ig_path; struct myrec *next; }; /* * Print the mount table info */ static void mi_print(void) { FILE *mt; struct extmnttab m; struct myrec *list, *mrp, *pmrp; char *flavor; int ignored = 0; seconfig_t nfs_sec; kstat_t *ksp; struct mntinfo_kstat mik; int transport_flag = 0; int path_count; int found; char *timer_name[] = { "Lookups", "Reads", "Writes", "All" }; mt = fopen(MNTTAB, "r"); if (mt == NULL) { perror(MNTTAB); exit(0); } list = NULL; resetmnttab(mt); while (getextmntent(mt, &m, sizeof (struct extmnttab)) == 0) { /* ignore non "nfs" and save the "ignore" entries */ if (strcmp(m.mnt_fstype, MNTTYPE_NFS) != 0) continue; /* * Check to see here if user gave a path(s) to * only show the mount point they wanted * Iterate through the list of paths the user gave and see * if any of them match our current nfs mount */ if (path[0] != NULL) { found = 0; for (path_count = 0; path[path_count] != NULL; path_count++) { if (strcmp(path[path_count], m.mnt_mountp) == 0) { found = 1; break; } } if (!found) continue; } if ((mrp = malloc(sizeof (struct myrec))) == 0) { fprintf(stderr, "nfsstat: not enough memory\n"); exit(1); } mrp->my_fsid = makedev(m.mnt_major, m.mnt_minor); if (ignore(m.mnt_mntopts)) { /* * ignored entries cannot be ignored for this * option. We have to display the info for this * nfs mount. The ignore is an indication * that the actual mount point is different and * something is in between the nfs mount. * So save the mount point now */ if ((mrp->ig_path = malloc( strlen(m.mnt_mountp) + 1)) == 0) { fprintf(stderr, "nfsstat: not enough memory\n"); exit(1); } (void) strcpy(mrp->ig_path, m.mnt_mountp); ignored++; } else { mrp->ig_path = 0; (void) strcpy(mrp->my_dir, m.mnt_mountp); } if ((mrp->my_path = strdup(m.mnt_special)) == NULL) { fprintf(stderr, "nfsstat: not enough memory\n"); exit(1); } mrp->next = list; list = mrp; } (void) fclose(mt); if (ignored) { /* * Now ignored entries which do not have * the my_dir initialized are really ignored; This never * happens unless the mnttab is corrupted. */ for (pmrp = 0, mrp = list; mrp; mrp = mrp->next) { if (mrp->ig_path == 0) pmrp = mrp; else if (pmrp) pmrp->next = mrp->next; else list = mrp->next; } } for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) { int i; if (ksp->ks_type != KSTAT_TYPE_RAW) continue; if (strcmp(ksp->ks_module, "nfs") != 0) continue; if (strcmp(ksp->ks_name, "mntinfo") != 0) continue; for (mrp = list; mrp; mrp = mrp->next) { if ((mrp->my_fsid & MAXMIN) == ksp->ks_instance) break; } if (mrp == 0) continue; if (safe_kstat_read(kc, ksp, &mik) == -1) continue; printf("%s from %s\n", mrp->my_dir, mrp->my_path); /* * for printing rdma transport and provider string. * This way we avoid modifying the kernel mntinfo_kstat * struct for protofmly. */ if (strcmp(mik.mik_proto, "ibtf") == 0) { printf(" Flags: vers=%u,proto=rdma", mik.mik_vers); transport_flag = 1; } else { printf(" Flags: vers=%u,proto=%s", mik.mik_vers, mik.mik_proto); transport_flag = 0; } /* * get the secmode name from /etc/nfssec.conf. */ if (!nfs_getseconfig_bynumber(mik.mik_secmod, &nfs_sec)) { flavor = nfs_sec.sc_name; } else flavor = NULL; if (flavor != NULL) printf(",sec=%s", flavor); else printf(",sec#=%d", mik.mik_secmod); printf(",%s", (mik.mik_flags & MI_HARD) ? "hard" : "soft"); if (mik.mik_flags & MI_PRINTED) printf(",printed"); printf(",%s", (mik.mik_flags & MI_INT) ? "intr" : "nointr"); if (mik.mik_flags & MI_DOWN) printf(",down"); if (mik.mik_flags & MI_NOAC) printf(",noac"); if (mik.mik_flags & MI_NOCTO) printf(",nocto"); if (mik.mik_flags & MI_DYNAMIC) printf(",dynamic"); if (mik.mik_flags & MI_LLOCK) printf(",llock"); if (mik.mik_flags & MI_GRPID) printf(",grpid"); if (mik.mik_flags & MI_RPCTIMESYNC) printf(",rpctimesync"); if (mik.mik_flags & MI_LINK) printf(",link"); if (mik.mik_flags & MI_SYMLINK) printf(",symlink"); if (mik.mik_vers < NFS_V4 && mik.mik_flags & MI_READDIRONLY) printf(",readdironly"); if (mik.mik_flags & MI_ACL) printf(",acl"); if (mik.mik_flags & MI_DIRECTIO) printf(",forcedirectio"); if (mik.mik_vers >= NFS_V4) { if (mik.mik_flags & MI4_MIRRORMOUNT) printf(",mirrormount"); if (mik.mik_flags & MI4_REFERRAL) printf(",referral"); } printf(",rsize=%d,wsize=%d,retrans=%d,timeo=%d", mik.mik_curread, mik.mik_curwrite, mik.mik_retrans, mik.mik_timeo); printf("\n"); printf(" Attr cache: acregmin=%d,acregmax=%d" ",acdirmin=%d,acdirmax=%d\n", mik.mik_acregmin, mik.mik_acregmax, mik.mik_acdirmin, mik.mik_acdirmax); if (transport_flag) { printf(" Transport: proto=rdma, plugin=%s\n", mik.mik_proto); } #define srtt_to_ms(x) x, (x * 2 + x / 2) #define dev_to_ms(x) x, (x * 5) for (i = 0; i < NFS_CALLTYPES + 1; i++) { int j; j = (i == NFS_CALLTYPES ? i - 1 : i); if (mik.mik_timers[j].srtt || mik.mik_timers[j].rtxcur) { printf(" %s: srtt=%d (%dms), " "dev=%d (%dms), cur=%u (%ums)\n", timer_name[i], srtt_to_ms(mik.mik_timers[i].srtt), dev_to_ms(mik.mik_timers[i].deviate), mik.mik_timers[i].rtxcur, mik.mik_timers[i].rtxcur * 20); } } if (strchr(mrp->my_path, ',')) printf( " Failover: noresponse=%d,failover=%d," "remap=%d,currserver=%s\n", mik.mik_noresponse, mik.mik_failover, mik.mik_remap, mik.mik_curserver); printf("\n"); } } static char *mntopts[] = { MNTOPT_IGNORE, MNTOPT_DEV, NULL }; #define IGNORE 0 #define DEV 1 /* * Return 1 if "ignore" appears in the options string */ static int ignore(char *opts) { char *value; char *s; if (opts == NULL) return (0); s = strdup(opts); if (s == NULL) return (0); opts = s; while (*opts != '\0') { if (getsubopt(&opts, mntopts, &value) == IGNORE) { free(s); return (1); } } free(s); return (0); } void usage(void) { fprintf(stderr, "Usage: nfsstat [-cnrsza [-v version] " "[-T d|u] [interval [count]]\n"); fprintf(stderr, "Usage: nfsstat -m [pathname..]\n"); exit(1); } void fail(int do_perror, char *message, ...) { va_list args; va_start(args, message); fprintf(stderr, "nfsstat: "); vfprintf(stderr, message, args); va_end(args); if (do_perror) fprintf(stderr, ": %s", strerror(errno)); fprintf(stderr, "\n"); exit(1); } kid_t safe_kstat_read(kstat_ctl_t *kc, kstat_t *ksp, void *data) { kid_t kstat_chain_id = kstat_read(kc, ksp, data); if (kstat_chain_id == -1) fail(1, "kstat_read(%x, '%s') failed", kc, ksp->ks_name); return (kstat_chain_id); } kid_t safe_kstat_write(kstat_ctl_t *kc, kstat_t *ksp, void *data) { kid_t kstat_chain_id = 0; if (ksp->ks_data != NULL) { kstat_chain_id = kstat_write(kc, ksp, data); if (kstat_chain_id == -1) fail(1, "kstat_write(%x, '%s') failed", kc, ksp->ks_name); } return (kstat_chain_id); } void stats_timer(int interval) { timer_t t_id; itimerspec_t time_struct; struct sigevent sig_struct; struct sigaction act; bzero(&sig_struct, sizeof (struct sigevent)); bzero(&act, sizeof (struct sigaction)); /* Create timer */ sig_struct.sigev_notify = SIGEV_SIGNAL; sig_struct.sigev_signo = SIGUSR1; sig_struct.sigev_value.sival_int = 0; if (timer_create(CLOCK_REALTIME, &sig_struct, &t_id) != 0) { fail(1, "Timer creation failed"); } act.sa_handler = handle_sig; if (sigaction(SIGUSR1, &act, NULL) != 0) { fail(1, "Could not set up signal handler"); } time_struct.it_value.tv_sec = interval; time_struct.it_value.tv_nsec = 0; time_struct.it_interval.tv_sec = interval; time_struct.it_interval.tv_nsec = 0; /* Arm timer */ if ((timer_settime(t_id, 0, &time_struct, NULL)) != 0) { fail(1, "Setting timer failed"); } } void handle_sig(int x) { } static void nfsstat_kstat_copy(kstat_t *src, kstat_t *dst, int fr) { if (fr) free(dst->ks_data); *dst = *src; if (src->ks_data != NULL) { safe_zalloc(&dst->ks_data, src->ks_data_size, 0); (void) memcpy(dst->ks_data, src->ks_data, src->ks_data_size); } else { dst->ks_data = NULL; dst->ks_data_size = 0; } } /* * "Safe" allocators - if we return we're guaranteed to have the desired space * allocated and zero-filled. We exit via fail if we can't get the space. */ void safe_zalloc(void **ptr, uint_t size, int free_first) { if (ptr == NULL) fail(1, "invalid pointer"); if (free_first && *ptr != NULL) free(*ptr); if ((*ptr = (void *)malloc(size)) == NULL) fail(1, "malloc failed"); (void) memset(*ptr, 0, size); } static int safe_strtoi(char const *val, char *errmsg) { char *end; long tmp; errno = 0; tmp = strtol(val, &end, 10); if (*end != '\0' || errno) fail(0, "%s %s", errmsg, val); return ((int)tmp); } # # 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 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # include $(SRC)/lib/Makefile.lib # Hammerhead: amd64-only SUBDIRS = $(MACH64) MSGFILES = libnfs_basic.c POFILE = rp_basic.po all : TARGET= all clean : TARGET= clean clobber : TARGET= clobber install : TARGET= install .KEEP_STATE: all clean clobber install: $(SUBDIRS) catalog: $(POFILE) $(POFILE): $(MSGFILES) $(BUILDPO.msgfiles) _msg: $(MSGDOMAINPOFILE) $(SUBDIRS): FRC @cd $@; pwd; $(MAKE) $(TARGET) FRC: include $(SRC)/lib/Makefile.targ include $(SRC)/Makefile.msg.targ # # 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 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. LIBRARY = libnfs_basic.a VERS = .1 LIBOBJS = libnfs_basic.o COMMON = ref_subr.o OBJECTS = $(LIBOBJS) $(COMMON) include $(SRC)/lib/Makefile.lib ROOTLIBDIR = $(ROOT)/usr/lib/reparse # Hammerhead: 64-bit only - flatten reparse library path ROOTLIBDIR64 = $(ROOT)/usr/lib/reparse LIBSRCS = $(LIBOBJS:%.o=$(SRCDIR)/%.c) LIBS = $(DYNLIB) LDLIBS += -lc -lnsl CSTD = $(CSTD_GNU99) CFLAGS += $(CCVERBOSE) CPPFLAGS += -D_REENTRANT -I$(SRC)/cmd/fs.d/nfs/lib # not linted SMATCH=off ZGUIDANCE= -Wl,-zguidance=noasserts .KEEP_STATE: all: $(LIBS) install: $(ROOTLIBDIR) $(ROOTLIBDIR64) all pics/ref_subr.o: ../../lib/ref_subr.c $(COMPILE.c) -o pics/ref_subr.o ../../lib/ref_subr.c $(POST_PROCESS_O) $(ROOTLIBDIR): $(INS.dir) $(ROOTLIBDIR64): $(INS.dir) include $(SRC)/lib/Makefile.targ # # 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 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # include ../Makefile.com include $(SRC)/lib/Makefile.lib.64 install: all $(ROOTLIBS64) $(ROOTLINKS64) /* * 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 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 "ref_subr.h" extern int errno; #define SERVICE_TYPE "nfs-basic" char *nfs_basic_service_type(void); boolean_t nfs_basic_supports_svc(const char *); int nfs_basic_deref(const char *, const char *, char *, size_t *); int nfs_basic_form(const char *, const char *, char *, size_t *); struct rp_plugin_ops rp_plugin_ops = { RP_PLUGIN_V1, NULL, /* rpo_init */ NULL, /* rpo_fini */ nfs_basic_service_type, nfs_basic_supports_svc, nfs_basic_form, nfs_basic_deref }; /* * What service type does this module support? */ char * nfs_basic_service_type() { return (SERVICE_TYPE); } /* * Does this module support a particular service type? */ boolean_t nfs_basic_supports_svc(const char *svc_type) { if (!svc_type) return (0); return (!strncasecmp(svc_type, SERVICE_TYPE, strlen(SERVICE_TYPE))); } /* * Take a string with a set of locations like this: * host1:/path1 host2:/path2 host3:/path3 * and convert it to an fs_locations4 for the deref routine. */ static fs_locations4 * get_fs_locations(char *buf) { fs_locations4 *result = NULL; fs_location4 *fsl_array; int i, gothost; int fsl_count = 0, escape = 0, delimiter = 0; int len; char *p, *sp, *dp, buf2[SYMLINK_MAX]; if (buf == NULL) return (NULL); #ifdef DEBUG printf("get_fs_locations: input %s\n", buf); #endif /* * Count fs_location entries by counting spaces. * Remember that escaped spaces ("\ ") may exist. * We mark the location boundaries with null bytes. * Variable use: * escape - set if we have found a backspace, * part of either "\ " or "\\" * delimiter - set if we have found a space and * used to skip multiple spaces */ for (sp = buf; sp && *sp; sp++) { if (*sp == '\\') { escape = 1; delimiter = 0; continue; } if (*sp == ' ') { if (delimiter == 1) continue; if (escape == 0) { delimiter = 1; fsl_count++; *sp = '\0'; } else escape = 0; } else delimiter = 0; } len = sp - buf; sp--; if (escape == 0 && *sp != '\0') fsl_count++; #ifdef DEBUG printf("get_fs_locations: fsl_count %d\n", fsl_count); #endif if (fsl_count == 0) goto out; /* Alloc space for everything */ result = calloc(1, sizeof (fs_locations4)); if (result == NULL) goto out; fsl_array = calloc(fsl_count, sizeof (fs_location4)); if (fsl_array == NULL) { free(result); result = NULL; goto out; } result->locations.locations_len = fsl_count; result->locations.locations_val = fsl_array; result->fs_root.pathname4_len = 0; result->fs_root.pathname4_val = NULL; /* * Copy input, removing escapes from host:/path/to/my\ files */ sp = buf; dp = buf2; bzero(buf2, sizeof (buf2)); i = gothost = 0; while ((sp && *sp && (sp - buf < len)) || gothost) { if (!gothost) { /* Drop leading spaces */ if (*sp == ' ') { sp++; continue; } /* Look for the rightmost colon for host */ p = strrchr(sp, ':'); if (!p) { #ifdef DEBUG printf("get_fs_locations: skipping %s\n", sp); #endif fsl_count--; sp += strlen(sp) + 1; } else { bcopy(sp, dp, p - sp); sp = p + 1; #ifdef DEBUG printf("get_fs_locations: host %s\n", buf2); #endif fsl_array[i].server.server_len = 1; fsl_array[i].server.server_val = malloc(sizeof (utf8string)); if (fsl_array[i].server.server_val == NULL) { int j; free(result); result = NULL; for (j = 0; j < i; j++) free(fsl_array[j]. server.server_val); free(fsl_array); goto out; } str_to_utf8(buf2, fsl_array[i].server.server_val); gothost = 1; dp = buf2; bzero(buf2, sizeof (buf2)); } continue; } /* End of string should mean a pathname */ if (*sp == '\0' && gothost) { #ifdef DEBUG printf("get_fs_locations: path %s\n", buf2); #endif (void) make_pathname4(buf2, &fsl_array[i].rootpath); i++; gothost = 0; dp = buf2; bzero(buf2, sizeof (buf2)); if (sp - buf < len) sp++; continue; } /* Skip a single escape character */ if (*sp == '\\') sp++; /* Plain char, just copy it */ *dp++ = *sp++; } /* * If we're still expecting a path name, we don't have a * server:/path pair and should discard the server and * note that we got fewer locations than expected. */ if (gothost) { fsl_count--; free(fsl_array[i].server.server_val); fsl_array[i].server.server_val = NULL; fsl_array[i].server.server_len = 0; } /* * If we have zero entries, we never got a whole server:/path * pair, and so cannot have anything else allocated. */ if (fsl_count <= 0) { free(result); free(fsl_array); return (NULL); } /* * Make sure we reflect the right number of locations. */ if (fsl_count < result->locations.locations_len) result->locations.locations_len = fsl_count; out: return (result); } /* * Deref function for nfs-basic service type returns an fs_locations4. */ int nfs_basic_deref(const char *svc_type, const char *svc_data, char *buf, size_t *bufsz) { int slen, err; fs_locations4 *fsl; XDR xdr; if ((!svc_type) || (!svc_data) || (!buf) || (!bufsz) || (*bufsz == 0)) return (EINVAL); if (strcasecmp(svc_type, SERVICE_TYPE)) return (ENOTSUP); fsl = get_fs_locations((char *)svc_data); if (fsl == NULL) return (ENOENT); #ifdef DEBUG printf("nfs_basic_deref: past get_fs_locations()\n"); #endif slen = xdr_sizeof(xdr_fs_locations4, (void *)fsl); if (slen > *bufsz) { *bufsz = slen; xdr_free(xdr_fs_locations4, (char *)fsl); return (EOVERFLOW); } #ifdef DEBUG printf("nfs_basic_deref: past buffer check\n"); print_referral_summary(fsl); #endif xdrmem_create(&xdr, buf, *bufsz, XDR_ENCODE); err = xdr_fs_locations4(&xdr, fsl); XDR_DESTROY(&xdr); xdr_free(xdr_fs_locations4, (char *)fsl); if (err != TRUE) return (EINVAL); *bufsz = slen; #ifdef DEBUG printf("nfs_basic_deref: past xdr_fs_locations4() and done\n"); #endif return (0); } /* * Form function for nfs-basic service type. */ int nfs_basic_form(const char *svc_type, const char *svc_data, char *buf, size_t *bufsz) { int slen; if ((!svc_type) || (!svc_data) || (!buf) || (*bufsz == 0)) return (EINVAL); if (strcmp(svc_type, SERVICE_TYPE)) return (ENOTSUP); slen = strlen(svc_data) + 1; if (slen > *bufsz) { *bufsz = slen; return (EOVERFLOW); } *bufsz = slen; strncpy(buf, svc_data, slen); return (0); } # # 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) 2009, 2010, Oracle and/or its affiliates. All rights reserved. # # # MAPFILE HEADER START # # WARNING: STOP NOW. DO NOT MODIFY THIS FILE. # Object versioning must comply with the rules detailed in # # usr/src/lib/README.mapfiles # # You should not be making modifications here until you've read the most current # copy of that file. If you need help, contact a gatekeeper for guidance. # # MAPFILE HEADER END # $mapfile_version 2 SYMBOL_VERSION SUNWprivate_1.1 { global: rp_plugin_ops; local: *; }; # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (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) 1989 by Sun Microsystems, Inc. # # Copyright (c) 2018, Joyent, Inc. FSTYPE= nfs TYPEPROG= rquotad ATTMK= $(TYPEPROG) include ../../Makefile.fstype OBJS= rpc.rquotad.o rquota_xdr.o SRCS= $(OBJS:%.o=%.c) XFILE= $(ROOT)/usr/include/rpcsvc/rquota.x CPPFLAGS += -I$(SRC)/head/rpcsvc -D_LARGEFILE64_SOURCE CERRWARN += -Wno-implicit-function-declaration CERRWARN += -Wno-unused-variable # Hammerhead: -ldl for dlopen/dlsym of quota modules LDLIBS += -lnsl -ldl # unknown type for func SMATCH=off $(TYPEPROG): $(OBJS) $(LINK.c) -o $@ $(LDLIBS) $(OBJS) $(POST_PROCESS) clean: $(RM) $(OBJS) @# Hammerhead: do not delete pre-generated rquota_xdr.c # Hammerhead: pre-generated (rpcgen + GNU cpp truncation bug) # rquota_xdr.c: $(XFILE) # $(RPCGEN) -c $(XFILE) -o $@ /* * 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 2017 Joyent Inc * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef notdef #include #endif #include #include #include #include #include #include #include #include #include #include #define QFNAME "quotas" /* name of quota file */ #define RPCSVC_CLOSEDOWN 120 /* 2 minutes */ struct fsquot { char *fsq_fstype; struct fsquot *fsq_next; char *fsq_dir; char *fsq_devname; dev_t fsq_dev; }; struct fsquot *fsqlist = NULL; typedef struct authunix_parms *authp; static int request_pending; /* Request in progress ? */ void closedown(); void dispatch(); struct fsquot *findfsq(); void freefs(); int getdiskquota(); void getquota(); int hasquota(); void log_cant_reply(); void setupfs(); static void zexit(int) __NORETURN; static libzfs_handle_t *(*_libzfs_init)(void); static void (*_libzfs_fini)(libzfs_handle_t *); static zfs_handle_t *(*_zfs_open)(libzfs_handle_t *, const char *, int); static void (*_zfs_close)(zfs_handle_t *); static int (*_zfs_prop_get_userquota_int)(zfs_handle_t *, const char *, uint64_t *); static libzfs_handle_t *g_zfs = NULL; /* * Dynamically check for libzfs, in case the user hasn't installed the SUNWzfs * packages. 'rquotad' supports zfs as an option. */ static void load_libzfs(void) { void *hdl; if (g_zfs != NULL) return; if ((hdl = dlopen("libzfs.so", RTLD_LAZY)) != NULL) { _libzfs_init = (libzfs_handle_t *(*)(void))dlsym(hdl, "libzfs_init"); _libzfs_fini = (void (*)())dlsym(hdl, "libzfs_fini"); _zfs_open = (zfs_handle_t *(*)())dlsym(hdl, "zfs_open"); _zfs_close = (void (*)())dlsym(hdl, "zfs_close"); _zfs_prop_get_userquota_int = (int (*)()) dlsym(hdl, "zfs_prop_get_userquota_int"); if (_libzfs_init && _libzfs_fini && _zfs_open && _zfs_close && _zfs_prop_get_userquota_int) g_zfs = _libzfs_init(); } } /*ARGSUSED*/ int main(int argc, char *argv[]) { register SVCXPRT *transp; load_libzfs(); /* * If stdin looks like a TLI endpoint, we assume * that we were started by a port monitor. If * t_getstate fails with TBADF, this is not a * TLI endpoint. */ if (t_getstate(0) != -1 || t_errno != TBADF) { char *netid; struct netconfig *nconf = NULL; openlog("rquotad", LOG_PID, LOG_DAEMON); if ((netid = getenv("NLSPROVIDER")) == NULL) { struct t_info tinfo; if (t_sync(0) == -1) { syslog(LOG_ERR, "could not do t_sync"); zexit(1); } if (t_getinfo(0, &tinfo) == -1) { syslog(LOG_ERR, "t_getinfo failed"); zexit(1); } if (tinfo.servtype == T_CLTS) { if (tinfo.addr == INET_ADDRSTRLEN) netid = "udp"; else netid = "udp6"; } else { syslog(LOG_ERR, "wrong transport"); zexit(1); } } if ((nconf = getnetconfigent(netid)) == NULL) { syslog(LOG_ERR, "cannot get transport info"); } if ((transp = svc_tli_create(0, nconf, NULL, 0, 0)) == NULL) { syslog(LOG_ERR, "cannot create server handle"); zexit(1); } if (nconf) freenetconfigent(nconf); if (!svc_reg(transp, RQUOTAPROG, RQUOTAVERS, dispatch, 0)) { syslog(LOG_ERR, "unable to register (RQUOTAPROG, RQUOTAVERS)."); zexit(1); } (void) sigset(SIGALRM, (void(*)(int)) closedown); (void) alarm(RPCSVC_CLOSEDOWN); svc_run(); zexit(1); /* NOTREACHED */ } /* * Started from a shell - fork the daemon. */ switch (fork()) { case 0: /* child */ break; case -1: perror("rquotad: can't fork"); zexit(1); default: /* parent */ zexit(0); } /* * Close existing file descriptors, open "/dev/null" as * standard input, output, and error, and detach from * controlling terminal. */ closefrom(0); (void) open("/dev/null", O_RDONLY); (void) open("/dev/null", O_WRONLY); (void) dup(1); (void) setsid(); openlog("rquotad", LOG_PID, LOG_DAEMON); /* * Create datagram service */ if (svc_create(dispatch, RQUOTAPROG, RQUOTAVERS, "datagram_v") == 0) { syslog(LOG_ERR, "couldn't register datagram_v service"); zexit(1); } /* * Start serving */ svc_run(); syslog(LOG_ERR, "Error: svc_run shouldn't have returned"); return (1); } void dispatch(rqstp, transp) register struct svc_req *rqstp; register SVCXPRT *transp; { request_pending = 1; switch (rqstp->rq_proc) { case NULLPROC: errno = 0; if (!svc_sendreply(transp, xdr_void, 0)) log_cant_reply(transp); break; case RQUOTAPROC_GETQUOTA: case RQUOTAPROC_GETACTIVEQUOTA: getquota(rqstp, transp); break; default: svcerr_noproc(transp); break; } request_pending = 0; } void closedown() { if (!request_pending) { int i, openfd; struct t_info tinfo; if (!t_getinfo(0, &tinfo) && (tinfo.servtype == T_CLTS)) zexit(0); for (i = 0, openfd = 0; i < svc_max_pollfd && openfd < 2; i++) { if (svc_pollfd[i].fd >= 0) openfd++; } if (openfd <= 1) zexit(0); } (void) alarm(RPCSVC_CLOSEDOWN); } static int getzfsquota(uid_t user, char *dataset, struct dqblk *zq) { zfs_handle_t *zhp = NULL; char propname[ZFS_MAXPROPLEN]; uint64_t userquota, userused; if (g_zfs == NULL) return (1); if ((zhp = _zfs_open(g_zfs, dataset, ZFS_TYPE_DATASET)) == NULL) { syslog(LOG_ERR, "can not open zfs dataset %s", dataset); return (1); } (void) snprintf(propname, sizeof (propname), "userquota@%u", user); if (_zfs_prop_get_userquota_int(zhp, propname, &userquota) != 0) { _zfs_close(zhp); return (1); } (void) snprintf(propname, sizeof (propname), "userused@%u", user); if (_zfs_prop_get_userquota_int(zhp, propname, &userused) != 0) { _zfs_close(zhp); return (1); } zq->dqb_bhardlimit = userquota / DEV_BSIZE; zq->dqb_bsoftlimit = userquota / DEV_BSIZE; zq->dqb_curblocks = userused / DEV_BSIZE; _zfs_close(zhp); return (0); } void getquota(rqstp, transp) register struct svc_req *rqstp; register SVCXPRT *transp; { struct getquota_args gqa; struct getquota_rslt gqr; struct dqblk dqblk; struct fsquot *fsqp; struct timeval tv; bool_t qactive; gqa.gqa_pathp = NULL; /* let xdr allocate the storage */ if (!svc_getargs(transp, xdr_getquota_args, (caddr_t)&gqa)) { svcerr_decode(transp); return; } /* * This authentication is really bogus with the current rpc * authentication scheme. One day we will have something for real. */ CTASSERT(sizeof (authp) <= RQCRED_SIZE); if (rqstp->rq_cred.oa_flavor != AUTH_UNIX || (((authp) rqstp->rq_clntcred)->aup_uid != 0 && ((authp) rqstp->rq_clntcred)->aup_uid != (uid_t)gqa.gqa_uid)) { gqr.status = Q_EPERM; goto sendreply; } fsqp = findfsq(gqa.gqa_pathp); if (fsqp == NULL) { gqr.status = Q_NOQUOTA; goto sendreply; } bzero(&dqblk, sizeof (dqblk)); if (strcmp(fsqp->fsq_fstype, MNTTYPE_ZFS) == 0) { if (getzfsquota(gqa.gqa_uid, fsqp->fsq_devname, &dqblk)) { gqr.status = Q_NOQUOTA; goto sendreply; } qactive = TRUE; } else { if (quotactl(Q_GETQUOTA, fsqp->fsq_dir, (uid_t)gqa.gqa_uid, &dqblk) != 0) { qactive = FALSE; if ((errno == ENOENT) || (rqstp->rq_proc != RQUOTAPROC_GETQUOTA)) { gqr.status = Q_NOQUOTA; goto sendreply; } /* * If there is no quotas file, don't bother to sync it. */ if (errno != ENOENT) { if (quotactl(Q_ALLSYNC, fsqp->fsq_dir, (uid_t)gqa.gqa_uid, &dqblk) < 0 && errno == EINVAL) syslog(LOG_WARNING, "Quotas are not compiled " "into this kernel"); if (getdiskquota(fsqp, (uid_t)gqa.gqa_uid, &dqblk) == 0) { gqr.status = Q_NOQUOTA; goto sendreply; } } } else { qactive = TRUE; } /* * We send the remaining time instead of the absolute time * because clock skew between machines should be much greater * than rpc delay. */ #define gqrslt getquota_rslt_u.gqr_rquota gettimeofday(&tv, NULL); gqr.gqrslt.rq_btimeleft = dqblk.dqb_btimelimit - tv.tv_sec; gqr.gqrslt.rq_ftimeleft = dqblk.dqb_ftimelimit - tv.tv_sec; } gqr.status = Q_OK; gqr.gqrslt.rq_active = qactive; gqr.gqrslt.rq_bsize = DEV_BSIZE; gqr.gqrslt.rq_bhardlimit = dqblk.dqb_bhardlimit; gqr.gqrslt.rq_bsoftlimit = dqblk.dqb_bsoftlimit; gqr.gqrslt.rq_curblocks = dqblk.dqb_curblocks; gqr.gqrslt.rq_fhardlimit = dqblk.dqb_fhardlimit; gqr.gqrslt.rq_fsoftlimit = dqblk.dqb_fsoftlimit; gqr.gqrslt.rq_curfiles = dqblk.dqb_curfiles; sendreply: errno = 0; if (!svc_sendreply(transp, xdr_getquota_rslt, (caddr_t)&gqr)) log_cant_reply(transp); } int quotactl(int cmd, char *mountp, uid_t uid, struct dqblk *dqp) { int fd; int status; struct quotctl quota; char mountpoint[256]; FILE *fstab; struct mnttab mntp; if ((mountp == NULL) && (cmd == Q_ALLSYNC)) { /* * Find the mount point of any ufs file system. this is * because the ioctl that implements the quotactl call has * to go to a real file, and not to the block device. */ if ((fstab = fopen(MNTTAB, "r")) == NULL) { syslog(LOG_ERR, "can not open %s: %m ", MNTTAB); return (-1); } fd = -1; while ((status = getmntent(fstab, &mntp)) == 0) { if (strcmp(mntp.mnt_fstype, MNTTYPE_UFS) != 0 || !(hasmntopt(&mntp, MNTOPT_RQ) || hasmntopt(&mntp, MNTOPT_QUOTA))) continue; (void) strlcpy(mountpoint, mntp.mnt_mountp, sizeof (mountpoint)); strcat(mountpoint, "/quotas"); if ((fd = open64(mountpoint, O_RDWR)) >= 0) break; } fclose(fstab); if (fd == -1) { errno = ENOENT; return (-1); } } else { if (mountp == NULL || mountp[0] == '\0') { errno = ENOENT; return (-1); } (void) strlcpy(mountpoint, mountp, sizeof (mountpoint)); strcat(mountpoint, "/quotas"); if ((fd = open64(mountpoint, O_RDONLY)) < 0) { errno = ENOENT; syslog(LOG_ERR, "can not open %s: %m ", mountpoint); return (-1); } } quota.op = cmd; quota.uid = uid; quota.addr = (caddr_t)dqp; status = ioctl(fd, Q_QUOTACTL, "a); close(fd); return (status); } /* * Return the quota information for the given path. Returns NULL if none * was found. */ struct fsquot * findfsq(char *dir) { struct stat sb; struct fsquot *fsqp; static time_t lastmtime = 0; /* mount table's previous mtime */ /* * If we've never looked at the mount table, or it has changed * since the last time, rebuild the list of quota'd file systems * and remember the current mod time for the mount table. */ if (stat(MNTTAB, &sb) < 0) { syslog(LOG_ERR, "can't stat %s: %m", MNTTAB); return (NULL); } if (lastmtime == 0 || sb.st_mtime != lastmtime) { freefs(); setupfs(); lastmtime = sb.st_mtime; } /* * Try to find the given path in the list of file systems with * quotas. */ if (fsqlist == NULL) return (NULL); if (stat(dir, &sb) < 0) return (NULL); for (fsqp = fsqlist; fsqp != NULL; fsqp = fsqp->fsq_next) { if (sb.st_dev == fsqp->fsq_dev) return (fsqp); } return (NULL); } static void setup_zfs(struct mnttab *mp) { struct fsquot *fsqp; struct stat sb; if (stat(mp->mnt_mountp, &sb) < 0) return; fsqp = malloc(sizeof (struct fsquot)); if (fsqp == NULL) { syslog(LOG_ERR, "out of memory"); zexit(1); } fsqp->fsq_dir = strdup(mp->mnt_mountp); fsqp->fsq_devname = strdup(mp->mnt_special); if (fsqp->fsq_dir == NULL || fsqp->fsq_devname == NULL) { syslog(LOG_ERR, "out of memory"); zexit(1); } fsqp->fsq_fstype = MNTTYPE_ZFS; fsqp->fsq_dev = sb.st_dev; fsqp->fsq_next = fsqlist; fsqlist = fsqp; } void setupfs() { struct fsquot *fsqp; FILE *mt; struct mnttab m; struct stat sb; char qfilename[MAXPATHLEN]; mt = fopen(MNTTAB, "r"); if (mt == NULL) { syslog(LOG_ERR, "can't read %s: %m", MNTTAB); return; } while (getmntent(mt, &m) == 0) { if (strcmp(m.mnt_fstype, MNTTYPE_ZFS) == 0) { setup_zfs(&m); continue; } if (strcmp(m.mnt_fstype, MNTTYPE_UFS) != 0) continue; if (!hasquota(m.mnt_mntopts)) { snprintf(qfilename, sizeof (qfilename), "%s/%s", m.mnt_mountp, QFNAME); if (access(qfilename, F_OK) < 0) continue; } if (stat(m.mnt_special, &sb) < 0 || (sb.st_mode & S_IFMT) != S_IFBLK) continue; fsqp = malloc(sizeof (struct fsquot)); if (fsqp == NULL) { syslog(LOG_ERR, "out of memory"); zexit(1); } fsqp->fsq_dir = strdup(m.mnt_mountp); fsqp->fsq_devname = strdup(m.mnt_special); if (fsqp->fsq_dir == NULL || fsqp->fsq_devname == NULL) { syslog(LOG_ERR, "out of memory"); zexit(1); } fsqp->fsq_fstype = MNTTYPE_UFS; fsqp->fsq_dev = sb.st_rdev; fsqp->fsq_next = fsqlist; fsqlist = fsqp; } (void) fclose(mt); } /* * Free the memory used by the current list of quota'd file systems. Nulls * out the list. */ void freefs() { register struct fsquot *fsqp; while ((fsqp = fsqlist) != NULL) { fsqlist = fsqp->fsq_next; free(fsqp->fsq_dir); free(fsqp->fsq_devname); free(fsqp); } } int getdiskquota(fsqp, uid, dqp) struct fsquot *fsqp; uid_t uid; struct dqblk *dqp; { int fd; char qfilename[MAXPATHLEN]; snprintf(qfilename, sizeof (qfilename), "%s/%s", fsqp->fsq_dir, QFNAME); if ((fd = open64(qfilename, O_RDONLY)) < 0) return (0); (void) llseek(fd, (offset_t)dqoff(uid), L_SET); if (read(fd, dqp, sizeof (struct dqblk)) != sizeof (struct dqblk)) { close(fd); return (0); } close(fd); if (dqp->dqb_bhardlimit == 0 && dqp->dqb_bsoftlimit == 0 && dqp->dqb_fhardlimit == 0 && dqp->dqb_fsoftlimit == 0) { return (0); } return (1); } /* * Get the client's hostname from the transport handle * If the name is not available then return "(anon)". */ struct nd_hostservlist * getclientsnames(transp) SVCXPRT *transp; { struct netbuf *nbuf; struct netconfig *nconf; static struct nd_hostservlist *serv; static struct nd_hostservlist anon_hsl; static struct nd_hostserv anon_hs; static char anon_hname[] = "(anon)"; static char anon_sname[] = ""; /* Set up anonymous client */ anon_hs.h_host = anon_hname; anon_hs.h_serv = anon_sname; anon_hsl.h_cnt = 1; anon_hsl.h_hostservs = &anon_hs; if (serv) { netdir_free((char *)serv, ND_HOSTSERVLIST); serv = NULL; } nconf = getnetconfigent(transp->xp_netid); if (nconf == NULL) { syslog(LOG_ERR, "%s: getnetconfigent failed", transp->xp_netid); return (&anon_hsl); } nbuf = svc_getrpccaller(transp); if (nbuf == NULL) { freenetconfigent(nconf); return (&anon_hsl); } if (netdir_getbyaddr(nconf, &serv, nbuf)) { freenetconfigent(nconf); return (&anon_hsl); } freenetconfigent(nconf); return (serv); } void log_cant_reply(transp) SVCXPRT *transp; { int saverrno; struct nd_hostservlist *clnames; register char *name; saverrno = errno; /* save error code */ clnames = getclientsnames(transp); if (clnames == NULL) return; name = clnames->h_hostservs->h_host; errno = saverrno; if (errno == 0) syslog(LOG_ERR, "couldn't send reply to %s", name); else syslog(LOG_ERR, "couldn't send reply to %s: %m", name); } char *mntopts[] = { MNTOPT_QUOTA, NULL }; #define QUOTA 0 /* * Return 1 if "quota" appears in the options string */ int hasquota(opts) char *opts; { char *value; if (opts == NULL) return (0); while (*opts != '\0') { if (getsubopt(&opts, mntopts, &value) == QUOTA) return (1); } return (0); } static void zexit(int n) { if (g_zfs != NULL) _libzfs_fini(g_zfs); exit(n); } /* * Please do not edit this file. * It was generated using rpcgen. */ #include "rquota.h" #ifndef _KERNEL #include #endif /* !_KERNEL */ bool_t xdr_getquota_args(xdrs, objp) XDR *xdrs; getquota_args *objp; { rpc_inline_t *buf __unused; if (!xdr_string(xdrs, &objp->gqa_pathp, RQ_PATHLEN)) return (FALSE); if (!xdr_int32_t(xdrs, &objp->gqa_uid)) return (FALSE); return (TRUE); } bool_t xdr_rquota(xdrs, objp) XDR *xdrs; rquota *objp; { rpc_inline_t *buf __unused; if (!xdr_int32_t(xdrs, &objp->rq_bsize)) return (FALSE); if (!xdr_bool(xdrs, &objp->rq_active)) return (FALSE); if (!xdr_uint32_t(xdrs, &objp->rq_bhardlimit)) return (FALSE); if (!xdr_uint32_t(xdrs, &objp->rq_bsoftlimit)) return (FALSE); if (!xdr_uint32_t(xdrs, &objp->rq_curblocks)) return (FALSE); if (!xdr_uint32_t(xdrs, &objp->rq_fhardlimit)) return (FALSE); if (!xdr_uint32_t(xdrs, &objp->rq_fsoftlimit)) return (FALSE); if (!xdr_uint32_t(xdrs, &objp->rq_curfiles)) return (FALSE); if (!xdr_uint32_t(xdrs, &objp->rq_btimeleft)) return (FALSE); if (!xdr_uint32_t(xdrs, &objp->rq_ftimeleft)) return (FALSE); return (TRUE); } bool_t xdr_gqr_status(xdrs, objp) XDR *xdrs; gqr_status *objp; { rpc_inline_t *buf __unused; if (!xdr_enum(xdrs, (enum_t *)objp)) return (FALSE); return (TRUE); } bool_t xdr_getquota_rslt(xdrs, objp) XDR *xdrs; getquota_rslt *objp; { rpc_inline_t *buf __unused; if (!xdr_gqr_status(xdrs, &objp->status)) return (FALSE); switch (objp->status) { case Q_OK: if (!xdr_rquota(xdrs, &objp->getquota_rslt_u.gqr_rquota)) return (FALSE); break; case Q_NOQUOTA: break; case Q_EPERM: break; default: return (FALSE); } return (TRUE); } # # 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 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # FSTYPE= nfs OTHERINSTALL= $(ROOTETC)/dfs/fstypes $(ROOTETC)/dfs/sharetab SHARETAB= sharetab include ../../Makefile.fstype $(ROOTETC)/dfs/fstypes : FILEMODE= 644 $(ROOTETC)/dfs/sharetab : FILEMODE= 444 $(SHARETAB): touch $(SHARETAB) clean: $(RM) $(SHARETAB) $(ROOTETC)/dfs/%: % $(INS.file) nfs NFS Utilities autofs AUTOFS Utilities smbfs CIFS Utilities # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (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) 1989 by Sun Microsystems, Inc. # # Copyright (c) 2018, Joyent, Inc. FSTYPE= nfs LIBPROG= showmount ATTMK= $(LIBPROG) OTHERINSTALL= $(ROOTUSRSBIN)/$(LIBPROG) LINKVALUE= ../lib/fs/$(FSTYPE)/$(LIBPROG) include ../../Makefile.fstype OBJS= $(LIBPROG).o clnt_subr.o SRCS= $(LIBPROG).c ../lib/clnt_subr.c # # Message catalog # POFILE= showmount.po LDLIBS += -lrpcsvc -lnsl CPPFLAGS += -I../lib $(LIBPROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) $(ROOTUSRSBIN)/$(LIBPROG): $(RM) $@; $(SYMLINK) $(LINKVALUE) $@ clnt_subr.o: ../lib/clnt_subr.c $(COMPILE.c) ../lib/clnt_subr.c # # message catalog # catalog: $(POFILE) $(POFILE): $(SRCS) $(RM) $@ $(COMPILE.cpp) $(SRCS) > $(POFILE).i $(XGETTEXT) $(XGETFLAGS) $(POFILE).i sed "/^domain/d" messages.po > $@ $(RM) messages.po $(POFILE).i clean: $(RM) $(OBJS) /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California * All Rights Reserved * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ /* * showmount */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include int sorthost(const void *, const void *); int sortpath(const void *, const void *); void printex(CLIENT *, char *); void usage(void); /* * Dynamically-sized array of pointers to mountlist entries. Each element * points into the linked list returned by the RPC call. We use an array * so that we can conveniently sort the entries. */ static struct mountbody **table; struct timeval rpc_totout_new = {15, 0}; int main(int argc, char *argv[]) { int aflg = 0, dflg = 0, eflg = 0; int err; struct mountbody *result_list = NULL; struct mountbody *ml = NULL; struct mountbody **tb; /* pointer into table */ char *host, hostbuf[256]; char *last; CLIENT *cl; int c; struct timeval tout, rpc_totout_old; int numentries; (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif (void) textdomain(TEXT_DOMAIN); while ((c = getopt(argc, argv, "ade")) != EOF) { switch (c) { case 'a': aflg++; break; case 'd': dflg++; break; case 'e': eflg++; break; default: usage(); exit(1); } } switch (argc - optind) { case 0: /* no args */ if (gethostname(hostbuf, sizeof (hostbuf)) < 0) { pr_err("gethostname: %s\n", strerror(errno)); exit(1); } host = hostbuf; break; case 1: /* one arg */ host = argv[optind]; break; default: /* too many args */ usage(); exit(1); } (void) __rpc_control(CLCR_GET_RPCB_TIMEOUT, &rpc_totout_old); (void) __rpc_control(CLCR_SET_RPCB_TIMEOUT, &rpc_totout_new); cl = mountprog_client_create(host, &rpc_totout_old); if (cl == NULL) { exit(1); } (void) __rpc_control(CLCR_SET_RPCB_TIMEOUT, &rpc_totout_old); if (eflg) { printex(cl, host); if (aflg + dflg == 0) { exit(0); } } tout.tv_sec = 10; tout.tv_usec = 0; err = clnt_call(cl, MOUNTPROC_DUMP, xdr_void, 0, xdr_mountlist, (caddr_t)&result_list, tout); if (err != 0) { pr_err("%s\n", clnt_sperrno(err)); exit(1); } /* * Count the number of entries in the list. If the list is empty, * quit now. */ numentries = 0; for (ml = result_list; ml != NULL; ml = ml->ml_next) numentries++; if (numentries == 0) exit(0); /* * Allocate memory for the array and initialize the array. */ table = calloc(numentries, sizeof (struct mountbody *)); if (table == NULL) { pr_err(gettext("not enough memory for %d entries\n"), numentries); exit(1); } for (ml = result_list, tb = &table[0]; ml != NULL; ml = ml->ml_next, tb++) { *tb = ml; } /* * Sort the entries and print the results. */ if (dflg) qsort(table, numentries, sizeof (struct mountbody *), sortpath); else qsort(table, numentries, sizeof (struct mountbody *), sorthost); if (aflg) { for (tb = table; tb < table + numentries; tb++) printf("%s:%s\n", (*tb)->ml_hostname, (*tb)->ml_directory); } else if (dflg) { last = ""; for (tb = table; tb < table + numentries; tb++) { if (strcmp(last, (*tb)->ml_directory)) printf("%s\n", (*tb)->ml_directory); last = (*tb)->ml_directory; } } else { last = ""; for (tb = table; tb < table + numentries; tb++) { if (strcmp(last, (*tb)->ml_hostname)) printf("%s\n", (*tb)->ml_hostname); last = (*tb)->ml_hostname; } } return (0); } int sorthost(const void *_a, const void *_b) { struct mountbody **a = (struct mountbody **)_a; struct mountbody **b = (struct mountbody **)_b; return (strcmp((*a)->ml_hostname, (*b)->ml_hostname)); } int sortpath(const void *_a, const void *_b) { struct mountbody **a = (struct mountbody **)_a; struct mountbody **b = (struct mountbody **)_b; return (strcmp((*a)->ml_directory, (*b)->ml_directory)); } void usage(void) { (void) fprintf(stderr, gettext("Usage: showmount [-a] [-d] [-e] [host]\n")); } void pr_err(char *fmt, ...) { va_list ap; va_start(ap, fmt); (void) fprintf(stderr, "showmount: "); (void) vfprintf(stderr, fmt, ap); va_end(ap); } void printex(CLIENT *cl, char *host) { struct exportnode *ex = NULL; struct exportnode *e; struct groupnode *gr; enum clnt_stat err; int max; struct timeval tout; tout.tv_sec = 10; tout.tv_usec = 0; err = clnt_call(cl, MOUNTPROC_EXPORT, xdr_void, 0, xdr_exports, (caddr_t)&ex, tout); if (err != 0) { pr_err("%s\n", clnt_sperrno(err)); exit(1); } if (ex == NULL) { printf(gettext("no exported file systems for %s\n"), host); } else { printf(gettext("export list for %s:\n"), host); } max = 0; for (e = ex; e != NULL; e = e->ex_next) { if (strlen(e->ex_dir) > max) { max = strlen(e->ex_dir); } } while (ex) { printf("%-*s ", max, ex->ex_dir); gr = ex->ex_groups; if (gr == NULL) { printf(gettext("(everyone)")); } while (gr) { printf("%s", gr->gr_name); gr = gr->gr_next; if (gr) { printf(","); } } printf("\n"); ex = ex->ex_next; } } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (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 1990-2003 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2016 by Delphix. All rights reserved. # # Copyright (c) 2018, Joyent, Inc. FSTYPE= nfs TYPEPROG= statd ATTMK= $(TYPEPROG) include ../../Makefile.fstype CPPFLAGS += -D_REENTRANT -DSUN_THREADS CERRWARN += -Wno-switch CERRWARN += -Wno-parentheses # Hammerhead: Suppress pointer/int cast warnings in legacy statd code CERRWARN += -Wno-pointer-to-int-cast # not linted SMATCH=off LOCAL= sm_svc.o sm_proc.o sm_statd.o OBJS= $(LOCAL) selfcheck.o daemon.o smfcfg.o SRCS= $(LOCAL:%.o=%.c) ../lib/selfcheck.c ../lib/daemon.c \ ../lib/smfcfg.c LDLIBS += -lsocket -lrpcsvc -lnsl -lscf CPPFLAGS += -I../lib $(TYPEPROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) $(LOCK_LINT) selfcheck.o: ../lib/selfcheck.c $(COMPILE.c) ../lib/selfcheck.c daemon.o: ../lib/daemon.c $(COMPILE.c) ../lib/daemon.c smfcfg.o: ../lib/smfcfg.c $(COMPILE.c) ../lib/smfcfg.c clean: $(RM) $(OBJS) $(TYPEPROG) /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright (c) 2012 by Delphix. All rights reserved. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California * All Rights Reserved * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sm_statd.h" static int local_state; /* fake local sm state */ /* client name-to-address translation table */ static name_addr_entry_t *name_addr = NULL; #define LOGHOST "loghost" static void delete_mon(char *mon_name, my_id *my_idp); static void insert_mon(mon *monp); static void pr_mon(char *); static int statd_call_lockd(mon *monp, int state); static int hostname_eq(char *host1, char *host2); static char *get_system_id(char *hostname); static void add_aliases(struct hostent *phost); static void *thr_send_notice(void *); static void delete_onemon(char *mon_name, my_id *my_idp, mon_entry **monitor_q); static void send_notice(char *mon_name, int state); static void add_to_host_array(char *host); static int in_host_array(char *host); static void pr_name_addr(name_addr_entry_t *name_addr); extern int self_check(char *hostname); extern struct lifconf *getmyaddrs(void); /* ARGSUSED */ void sm_stat_svc(void *arg1, void *arg2) { sm_name *namep = arg1; sm_stat_res *resp = arg2; if (debug) (void) printf("proc sm_stat: mon_name = %s\n", namep->mon_name); resp->res_stat = stat_succ; resp->state = LOCAL_STATE; } /* ARGSUSED */ void sm_mon_svc(void *arg1, void *arg2) { mon *monp = arg1; sm_stat_res *resp = arg2; mon_id *monidp; monidp = &monp->mon_id; rw_rdlock(&thr_rwlock); if (debug) { (void) printf("proc sm_mon: mon_name = %s, id = %d\n", monidp->mon_name, *((int *)monp->priv)); pr_mon(monp->mon_id.mon_name); } /* only monitor other hosts */ if (self_check(monp->mon_id.mon_name) == 0) { /* store monitor request into monitor_q */ insert_mon(monp); } pr_mon(monp->mon_id.mon_name); resp->res_stat = stat_succ; resp->state = local_state; rw_unlock(&thr_rwlock); } /* ARGSUSED */ void sm_unmon_svc(void *arg1, void *arg2) { mon_id *monidp = arg1; sm_stat *resp = arg2; rw_rdlock(&thr_rwlock); if (debug) { (void) printf( "proc sm_unmon: mon_name = %s, [%s, %d, %d, %d]\n", monidp->mon_name, monidp->my_id.my_name, monidp->my_id.my_prog, monidp->my_id.my_vers, monidp->my_id.my_proc); pr_mon(monidp->mon_name); } delete_mon(monidp->mon_name, &monidp->my_id); pr_mon(monidp->mon_name); resp->state = local_state; rw_unlock(&thr_rwlock); } /* ARGSUSED */ void sm_unmon_all_svc(void *arg1, void *arg2) { my_id *myidp = arg1; sm_stat *resp = arg2; rw_rdlock(&thr_rwlock); if (debug) (void) printf("proc sm_unmon_all: [%s, %d, %d, %d]\n", myidp->my_name, myidp->my_prog, myidp->my_vers, myidp->my_proc); delete_mon(NULL, myidp); pr_mon(NULL); resp->state = local_state; rw_unlock(&thr_rwlock); } /* * Notifies lockd specified by name that state has changed for this server. */ void sm_notify_svc(void *arg, void *arg1 __unused) { stat_chge *ntfp = arg; rw_rdlock(&thr_rwlock); if (debug) (void) printf("sm_notify: %s state =%d\n", ntfp->mon_name, ntfp->state); send_notice(ntfp->mon_name, ntfp->state); rw_unlock(&thr_rwlock); } /* ARGSUSED */ void sm_simu_crash_svc(void *myidp, void *arg __unused) { int i; struct mon_entry *monitor_q; int found = 0; if (debug) (void) printf("proc sm_simu_crash\n"); /* Only one crash should be running at a time. */ mutex_lock(&crash_lock); if (in_crash != 0) { mutex_unlock(&crash_lock); return; } in_crash = 1; mutex_unlock(&crash_lock); for (i = 0; i < MAX_HASHSIZE; i++) { mutex_lock(&mon_table[i].lock); monitor_q = mon_table[i].sm_monhdp; if (monitor_q != NULL) { mutex_unlock(&mon_table[i].lock); found = 1; break; } mutex_unlock(&mon_table[i].lock); } /* * If there are entries found in the monitor table, * initiate a crash, else zero out the in_crash variable. */ if (found) { mutex_lock(&crash_lock); die = 1; /* Signal sm_try() thread if sleeping. */ cond_signal(&retrywait); mutex_unlock(&crash_lock); rw_wrlock(&thr_rwlock); sm_crash(); rw_unlock(&thr_rwlock); } else { mutex_lock(&crash_lock); in_crash = 0; mutex_unlock(&crash_lock); } } /* ARGSUSED */ void nsmaddrproc1_reg(void *arg1, void *arg2) { reg1args *regargs = arg1; reg1res *regresp = arg2; nsm_addr_res status; name_addr_entry_t *entry; char *tmp_n_bytes; addr_entry_t *addr; rw_rdlock(&thr_rwlock); if (debug) { int i; (void) printf("nap1_reg: fam= %d, name= %s, len= %d\n", regargs->family, regargs->name, regargs->address.n_len); (void) printf("address is: "); for (i = 0; i < regargs->address.n_len; i++) { (void) printf("%d.", (unsigned char)regargs->address.n_bytes[i]); } (void) printf("\n"); } /* * Locate the entry with the name in the NSM_ADDR_REG request if * it exists. If it doesn't, create a new entry to hold this name. * The first time through this code, name_addr starts out as NULL. */ mutex_lock(&name_addrlock); for (entry = name_addr; entry; entry = entry->next) { if (strcmp(regargs->name, entry->name) == 0) { if (debug) { (void) printf("nap1_reg: matched name %s\n", entry->name); } break; } } if (entry == NULL) { entry = (name_addr_entry_t *)malloc(sizeof (*entry)); if (entry == NULL) { if (debug) { (void) printf( "nsmaddrproc1_reg: no memory for entry\n"); } status = nsm_addr_fail; goto done; } entry->name = strdup(regargs->name); if (entry->name == NULL) { if (debug) { (void) printf( "nsmaddrproc1_reg: no memory for name\n"); } free(entry); status = nsm_addr_fail; goto done; } entry->addresses = NULL; /* * Link the new entry onto the *head* of the name_addr * table. * * Note: there is code below in the address maintenance * section that assumes this behavior. */ entry->next = name_addr; name_addr = entry; } /* * Try to match the address in the request; if it doesn't match, * add it to the entry's address list. */ for (addr = entry->addresses; addr; addr = addr->next) { if (addr->family == (sa_family_t)regargs->family && addr->ah.n_len == regargs->address.n_len && memcmp(addr->ah.n_bytes, regargs->address.n_bytes, addr->ah.n_len) == 0) { if (debug) { int i; (void) printf("nap1_reg: matched addr "); for (i = 0; i < addr->ah.n_len; i++) { (void) printf("%d.", (unsigned char)addr->ah.n_bytes[i]); } (void) printf(" family %d for name %s\n", addr->family, entry->name); } break; } } if (addr == NULL) { addr = (addr_entry_t *)malloc(sizeof (*addr)); tmp_n_bytes = (char *)malloc(regargs->address.n_len); if (addr == NULL || tmp_n_bytes == NULL) { if (debug) { (void) printf("nap1_reg: no memory for addr\n"); } /* * If this name entry was just newly made in the * table, back it out now that we can't register * an address with it anyway. * * Note: we are making an assumption about how * names are added to (the head of) name_addr here. */ if (entry == name_addr && entry->addresses == NULL) { name_addr = name_addr->next; free(entry->name); free(entry); if (tmp_n_bytes) free(tmp_n_bytes); if (addr) free(addr); status = nsm_addr_fail; goto done; } } /* * Note: this check for address family assumes that we * will get something different here someday for * other supported address types, such as IPv6. */ addr->ah.n_len = regargs->address.n_len; addr->ah.n_bytes = tmp_n_bytes; addr->family = regargs->family; if (debug) { if ((addr->family != AF_INET) && (addr->family != AF_INET6)) { (void) printf( "nap1_reg: unknown addr family %d\n", addr->family); } } (void) memcpy(addr->ah.n_bytes, regargs->address.n_bytes, addr->ah.n_len); addr->next = entry->addresses; entry->addresses = addr; } status = nsm_addr_succ; done: regresp->status = status; if (debug) { pr_name_addr(name_addr); } mutex_unlock(&name_addrlock); rw_unlock(&thr_rwlock); } /* * Insert an entry into the monitor_q. Space for the entry is allocated * here. It is then filled in from the information passed in. */ static void insert_mon(mon *monp) { mon_entry *new, *found; my_id *my_idp, *nl_idp; mon_entry *monitor_q; unsigned int hash; name_addr_entry_t *entry; addr_entry_t *addr; /* Allocate entry for new */ if ((new = (mon_entry *) malloc(sizeof (mon_entry))) == 0) { syslog(LOG_ERR, "statd: insert_mon: malloc error on mon %s (id=%d)\n", monp->mon_id.mon_name, *((int *)monp->priv)); return; } /* Initialize and copy contents of monp to new */ (void) memset(new, 0, sizeof (mon_entry)); (void) memcpy(&new->id, monp, sizeof (mon)); /* Allocate entry for new mon_name */ if ((new->id.mon_id.mon_name = strdup(monp->mon_id.mon_name)) == 0) { syslog(LOG_ERR, "statd: insert_mon: malloc error on mon %s (id=%d)\n", monp->mon_id.mon_name, *((int *)monp->priv)); free(new); return; } /* Allocate entry for new my_name */ if ((new->id.mon_id.my_id.my_name = strdup(monp->mon_id.my_id.my_name)) == 0) { syslog(LOG_ERR, "statd: insert_mon: malloc error on mon %s (id=%d)\n", monp->mon_id.mon_name, *((int *)monp->priv)); free(new->id.mon_id.mon_name); free(new); return; } if (debug) (void) printf("add_mon(%x) %s (id=%d)\n", (int)new, new->id.mon_id.mon_name, *((int *)new->id.priv)); /* * Record the name, and all addresses which have been registered * for this name, in the filesystem name space. */ record_name(new->id.mon_id.mon_name, 1); if (regfiles_only == 0) { mutex_lock(&name_addrlock); for (entry = name_addr; entry; entry = entry->next) { if (strcmp(new->id.mon_id.mon_name, entry->name) != 0) { continue; } for (addr = entry->addresses; addr; addr = addr->next) { record_addr(new->id.mon_id.mon_name, addr->family, &addr->ah); } break; } mutex_unlock(&name_addrlock); } SMHASH(new->id.mon_id.mon_name, hash); mutex_lock(&mon_table[hash].lock); monitor_q = mon_table[hash].sm_monhdp; /* If mon_table hash list is empty. */ if (monitor_q == NULL) { if (debug) (void) printf("\nAdding to monitor_q hash %d\n", hash); new->nxt = new->prev = NULL; mon_table[hash].sm_monhdp = new; mutex_unlock(&mon_table[hash].lock); return; } else { found = 0; my_idp = &new->id.mon_id.my_id; while (monitor_q != NULL) { /* * This list is searched sequentially for the * tuple (hostname, prog, vers, proc). The tuples * are inserted in the beginning of the monitor_q, * if the hostname is not already present in the list. * If the hostname is found in the list, the incoming * tuple is inserted just after all the tuples with the * same hostname. However, if the tuple matches exactly * with an entry in the list, space allocated for the * new entry is released and nothing is inserted in the * list. */ if (str_cmp_unqual_hostname( monitor_q->id.mon_id.mon_name, new->id.mon_id.mon_name) == 0) { /* found */ nl_idp = &monitor_q->id.mon_id.my_id; if ((str_cmp_unqual_hostname(my_idp->my_name, nl_idp->my_name) == 0) && my_idp->my_prog == nl_idp->my_prog && my_idp->my_vers == nl_idp->my_vers && my_idp->my_proc == nl_idp->my_proc) { /* * already exists an identical one, * release the space allocated for the * mon_entry */ free(new->id.mon_id.mon_name); free(new->id.mon_id.my_id.my_name); free(new); mutex_unlock(&mon_table[hash].lock); return; } else { /* * mark the last callback that is * not matching; new is inserted * after this */ found = monitor_q; } } else if (found) break; monitor_q = monitor_q->nxt; } if (found) { /* * insert just after the entry having matching tuple. */ new->nxt = found->nxt; new->prev = found; if (found->nxt != NULL) found->nxt->prev = new; found->nxt = new; } else { /* * not found, insert in front of list. */ new->nxt = mon_table[hash].sm_monhdp; new->prev = (mon_entry *) NULL; if (new->nxt != (mon_entry *) NULL) new->nxt->prev = new; mon_table[hash].sm_monhdp = new; } mutex_unlock(&mon_table[hash].lock); return; } } /* * Deletes a specific monitor name or deletes all monitors with same id * in hash table. */ static void delete_mon(char *mon_name, my_id *my_idp) { unsigned int hash; if (mon_name != NULL) { record_name(mon_name, 0); SMHASH(mon_name, hash); mutex_lock(&mon_table[hash].lock); delete_onemon(mon_name, my_idp, &mon_table[hash].sm_monhdp); mutex_unlock(&mon_table[hash].lock); } else { for (hash = 0; hash < MAX_HASHSIZE; hash++) { mutex_lock(&mon_table[hash].lock); delete_onemon(mon_name, my_idp, &mon_table[hash].sm_monhdp); mutex_unlock(&mon_table[hash].lock); } } } /* * Deletes a monitor in list. * IF mon_name is NULL, delete all mon_names that have the same id, * else delete specific monitor. */ void delete_onemon(char *mon_name, my_id *my_idp, mon_entry **monitor_q) { mon_entry *next, *nl; my_id *nl_idp; next = *monitor_q; while ((nl = next) != NULL) { next = next->nxt; if (mon_name == NULL || (mon_name != NULL && str_cmp_unqual_hostname(nl->id.mon_id.mon_name, mon_name) == 0)) { nl_idp = &nl->id.mon_id.my_id; if ((str_cmp_unqual_hostname(my_idp->my_name, nl_idp->my_name) == 0) && my_idp->my_prog == nl_idp->my_prog && my_idp->my_vers == nl_idp->my_vers && my_idp->my_proc == nl_idp->my_proc) { /* found */ if (debug) (void) printf("delete_mon(%x): %s\n", (int)nl, mon_name ? mon_name : ""); /* * Remove the monitor name from the * record_q, if id matches. */ record_name(nl->id.mon_id.mon_name, 0); /* if nl is not the first entry on list */ if (nl->prev != NULL) nl->prev->nxt = nl->nxt; else { *monitor_q = nl->nxt; } if (nl->nxt != NULL) nl->nxt->prev = nl->prev; free(nl->id.mon_id.mon_name); free(nl_idp->my_name); free(nl); } } /* end of if mon */ } } /* * Notify lockd of host specified by mon_name that the specified state * has changed. */ static void send_notice(char *mon_name, int state) { struct mon_entry *next; mon_entry *monitor_q; unsigned int hash; moninfo_t *minfop; mon *monp; SMHASH(mon_name, hash); mutex_lock(&mon_table[hash].lock); monitor_q = mon_table[hash].sm_monhdp; next = monitor_q; while (next != NULL) { if (hostname_eq(next->id.mon_id.mon_name, mon_name)) { monp = &next->id; /* * Prepare the minfop structure to pass to * thr_create(). This structure is a copy of * mon info and state. */ if ((minfop = (moninfo_t *)xmalloc(sizeof (moninfo_t))) != NULL) { (void) memcpy(&minfop->id, monp, sizeof (mon)); /* Allocate entry for mon_name */ if ((minfop->id.mon_id.mon_name = strdup(monp->mon_id.mon_name)) == 0) { syslog(LOG_ERR, "statd: send_notice: " "malloc error on mon %s (id=%d)\n", monp->mon_id.mon_name, *((int *)monp->priv)); free(minfop); continue; } /* Allocate entry for my_name */ if ((minfop->id.mon_id.my_id.my_name = strdup(monp->mon_id.my_id.my_name)) == 0) { syslog(LOG_ERR, "statd: send_notice: " "malloc error on mon %s (id=%d)\n", monp->mon_id.mon_name, *((int *)monp->priv)); free(minfop->id.mon_id.mon_name); free(minfop); continue; } minfop->state = state; /* * Create detached threads to process each host * to notify. If error, print out msg, free * resources and continue. */ if (thr_create(NULL, 0, thr_send_notice, minfop, THR_DETACHED, NULL)) { syslog(LOG_ERR, "statd: unable to " "create thread to send_notice to " "%s.\n", mon_name); free(minfop->id.mon_id.mon_name); free(minfop->id.mon_id.my_id.my_name); free(minfop); continue; } } } next = next->nxt; } mutex_unlock(&mon_table[hash].lock); } /* * Work thread created to do the actual statd_call_lockd */ static void * thr_send_notice(void *arg) { moninfo_t *minfop; minfop = (moninfo_t *)arg; if (statd_call_lockd(&minfop->id, minfop->state) == -1) { if (debug && minfop->id.mon_id.mon_name) (void) printf("problem with notifying %s failure, " "give up\n", minfop->id.mon_id.mon_name); } else { if (debug) (void) printf("send_notice: %s, %d notified.\n", minfop->id.mon_id.mon_name, minfop->state); } free(minfop->id.mon_id.mon_name); free(minfop->id.mon_id.my_id.my_name); free(minfop); thr_exit((void *) 0); #ifdef lint /*NOTREACHED*/ return ((void *)0); #endif } /* * Contact lockd specified by monp. */ static int statd_call_lockd(mon *monp, int state) { enum clnt_stat clnt_stat; struct timeval tottimeout; struct sm_status stat; my_id *my_idp; char *mon_name; int i; int rc = 0; CLIENT *clnt; mon_name = monp->mon_id.mon_name; my_idp = &monp->mon_id.my_id; (void) memset(&stat, 0, sizeof (stat)); stat.mon_name = mon_name; stat.state = state; for (i = 0; i < 16; i++) { stat.priv[i] = monp->priv[i]; } if (debug) (void) printf("statd_call_lockd: %s state = %d\n", stat.mon_name, stat.state); tottimeout.tv_sec = SM_RPC_TIMEOUT; tottimeout.tv_usec = 0; clnt = create_client(my_idp->my_name, my_idp->my_prog, my_idp->my_vers, "ticotsord", &tottimeout); if (clnt == NULL) { return (-1); } clnt_stat = clnt_call(clnt, my_idp->my_proc, xdr_sm_status, (char *)&stat, xdr_void, NULL, tottimeout); if (debug) { (void) printf("clnt_stat=%s(%d)\n", clnt_sperrno(clnt_stat), clnt_stat); } if (clnt_stat != (int)RPC_SUCCESS) { syslog(LOG_WARNING, "statd: cannot talk to lockd at %s, %s(%d)\n", my_idp->my_name, clnt_sperrno(clnt_stat), clnt_stat); rc = -1; } clnt_destroy(clnt); return (rc); } /* * Client handle created. */ CLIENT * create_client(char *host, int prognum, int versnum, char *netid, struct timeval *utimeout) { int fd; struct timeval timeout; CLIENT *client; struct t_info tinfo; if (netid == NULL) { client = clnt_create_timed(host, prognum, versnum, "netpath", utimeout); } else { struct netconfig *nconf; nconf = getnetconfigent(netid); if (nconf == NULL) { return (NULL); } client = clnt_tp_create_timed(host, prognum, versnum, nconf, utimeout); freenetconfigent(nconf); } if (client == NULL) { return (NULL); } (void) CLNT_CONTROL(client, CLGET_FD, (caddr_t)&fd); if (t_getinfo(fd, &tinfo) != -1) { if (tinfo.servtype == T_CLTS) { /* * Set time outs for connectionless case */ timeout.tv_usec = 0; timeout.tv_sec = SM_CLTS_TIMEOUT; (void) CLNT_CONTROL(client, CLSET_RETRY_TIMEOUT, (caddr_t)&timeout); } } else return (NULL); return (client); } /* * ONLY for debugging. * Debug messages which prints out the monitor table information. * If name is specified, just print out the hash list corresponding * to name, otherwise print out the entire monitor table. */ static void pr_mon(char *name) { mon_entry *nl; int hash; if (!debug) return; /* print all */ if (name == NULL) { for (hash = 0; hash < MAX_HASHSIZE; hash++) { mutex_lock(&mon_table[hash].lock); nl = mon_table[hash].sm_monhdp; if (nl == NULL) { (void) printf( "*****monitor_q = NULL hash %d\n", hash); mutex_unlock(&mon_table[hash].lock); continue; } (void) printf("*****monitor_q:\n "); while (nl != NULL) { (void) printf("%s:(%x), ", nl->id.mon_id.mon_name, (int)nl); nl = nl->nxt; } mutex_unlock(&mon_table[hash].lock); (void) printf("\n"); } } else { /* print one hash list */ SMHASH(name, hash); mutex_lock(&mon_table[hash].lock); nl = mon_table[hash].sm_monhdp; if (nl == NULL) { (void) printf("*****monitor_q = NULL hash %d\n", hash); } else { (void) printf("*****monitor_q:\n "); while (nl != NULL) { (void) printf("%s:(%x), ", nl->id.mon_id.mon_name, (int)nl); nl = nl->nxt; } (void) printf("\n"); } mutex_unlock(&mon_table[hash].lock); } } /* * Only for debugging. * Dump the host name-to-address translation table passed in `name_addr'. */ static void pr_name_addr(name_addr_entry_t *name_addr) { name_addr_entry_t *entry; addr_entry_t *addr; struct in_addr ipv4_addr; char *ipv6_addr; char abuf[INET6_ADDRSTRLEN]; assert(MUTEX_HELD(&name_addrlock)); (void) printf("name-to-address translation table:\n"); for (entry = name_addr; entry != NULL; entry = entry->next) { (void) printf("\t%s: ", (entry->name ? entry->name : "(null)")); for (addr = entry->addresses; addr; addr = addr->next) { switch (addr->family) { case AF_INET: ipv4_addr = *(struct in_addr *)addr->ah.n_bytes; (void) printf(" %s (fam %d)", inet_ntoa(ipv4_addr), addr->family); break; case AF_INET6: ipv6_addr = (char *)addr->ah.n_bytes; (void) printf(" %s (fam %d)", inet_ntop(addr->family, ipv6_addr, abuf, sizeof (abuf)), addr->family); break; default: return; } } printf("\n"); } } /* * First, try to compare the hostnames as strings. If the hostnames does not * match we might deal with the hostname aliases. In this case two different * aliases for the same machine don't match each other when using strcmp. To * deal with this, the hostnames must be translated into some sort of universal * identifier. These identifiers can be compared. Universal network addresses * are currently used for this identifier because it is general and easy to do. * Other schemes are possible and this routine could be converted if required. * * If it can't find an address for some reason, 0 is returned. */ static int hostname_eq(char *host1, char *host2) { char *sysid1; char *sysid2; int rv; /* Compare hostnames as strings */ if (host1 != NULL && host2 != NULL && strcmp(host1, host2) == 0) return (1); /* Try harder if hostnames do not match */ sysid1 = get_system_id(host1); sysid2 = get_system_id(host2); if ((sysid1 == NULL) || (sysid2 == NULL)) rv = 0; else rv = (strcmp(sysid1, sysid2) == 0); free(sysid1); free(sysid2); return (rv); } /* * Convert a hostname character string into its network address. * A network address is found by searching through all the entries * in /etc/netconfig and doing a netdir_getbyname() for each inet * entry found. The netbuf structure returned is converted into * a universal address format. * * If a NULL hostname is given, then the name of the current host * is used. If the hostname doesn't map to an address, a NULL * pointer is returned. * * N.B. the character string returned is allocated in taddr2uaddr() * and should be freed by the caller using free(). */ static char * get_system_id(char *hostname) { void *hp; struct netconfig *ncp; struct nd_hostserv service; struct nd_addrlist *addrs; char *uaddr; int rv; if (hostname == NULL) service.h_host = HOST_SELF; else service.h_host = hostname; service.h_serv = NULL; hp = setnetconfig(); if (hp == (void *) NULL) { return (NULL); } while ((ncp = getnetconfig(hp)) != NULL) { if ((strcmp(ncp->nc_protofmly, NC_INET) == 0) || (strcmp(ncp->nc_protofmly, NC_INET6) == 0)) { addrs = NULL; rv = netdir_getbyname(ncp, &service, &addrs); if (rv != 0) { continue; } if (addrs) { uaddr = taddr2uaddr(ncp, addrs->n_addrs); netdir_free(addrs, ND_ADDRLIST); endnetconfig(hp); return (uaddr); } } else continue; } endnetconfig(hp); return (NULL); } void merge_hosts(void) { struct lifconf *lifc = NULL; int sock = -1; struct lifreq *lifrp; struct lifreq lifr; int n; struct sockaddr_in *sin; struct sockaddr_in6 *sin6; struct sockaddr_storage *sa; int af; struct hostent *phost; char *addr; size_t alen; int errnum; /* * This function will enumerate all the interfaces for * this platform, then get the hostent for each i/f. * With the hostent structure, we can get all of the * aliases for the i/f. Then we'll merge all the aliases * with the existing host_name[] list to come up with * all of the known names for each interface. This solves * the problem of a multi-homed host not knowing which * name to publish when statd is started. All the aliases * will be stored in the array, host_name. * * NOTE: Even though we will use all of the aliases we * can get from the i/f hostent, the receiving statd * will still need to handle aliases with hostname_eq. * This is because the sender's aliases may not match * those of the receiver. */ lifc = getmyaddrs(); if (lifc == NULL) { goto finish; } lifrp = lifc->lifc_req; for (n = lifc->lifc_len / sizeof (struct lifreq); n > 0; n--, lifrp++) { (void) strncpy(lifr.lifr_name, lifrp->lifr_name, sizeof (lifr.lifr_name)); af = lifrp->lifr_addr.ss_family; sock = socket(af, SOCK_DGRAM, 0); if (sock == -1) { syslog(LOG_ERR, "statd: socket failed\n"); goto finish; } /* If it's the loopback interface, ignore */ if (ioctl(sock, SIOCGLIFFLAGS, (caddr_t)&lifr) < 0) { syslog(LOG_ERR, "statd: SIOCGLIFFLAGS failed, error: %m\n"); goto finish; } if (lifr.lifr_flags & IFF_LOOPBACK) continue; if (ioctl(sock, SIOCGLIFADDR, (caddr_t)&lifr) < 0) { syslog(LOG_ERR, "statd: SIOCGLIFADDR failed, error: %m\n"); goto finish; } sa = (struct sockaddr_storage *)&(lifr.lifr_addr); if (sa->ss_family == AF_INET) { sin = (struct sockaddr_in *)&lifr.lifr_addr; addr = (char *)(&sin->sin_addr); alen = sizeof (struct in_addr); } else if (sa->ss_family == AF_INET6) { sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; addr = (char *)(&sin6->sin6_addr); alen = sizeof (struct in6_addr); } else { syslog(LOG_WARNING, "unexpected address family (%d)", sa->ss_family); continue; } phost = getipnodebyaddr(addr, alen, sa->ss_family, &errnum); if (phost) add_aliases(phost); } /* * Now, just in case we didn't get them all byaddr, * let's look by name. */ phost = getipnodebyname(hostname, AF_INET6, AI_ALL, &errnum); if (phost) add_aliases(phost); finish: if (sock != -1) (void) close(sock); if (lifc) { free(lifc->lifc_buf); free(lifc); } } /* * add_aliases traverses a hostent alias list, compares * the aliases to the contents of host_name, and if an * alias is not already present, adds it to host_name[]. */ static void add_aliases(struct hostent *phost) { char **aliases; if (!in_host_array(phost->h_name)) { add_to_host_array(phost->h_name); } if (phost->h_aliases == NULL) return; /* no aliases to register */ for (aliases = phost->h_aliases; *aliases != NULL; aliases++) { if (!in_host_array(*aliases)) { add_to_host_array(*aliases); } } } /* * in_host_array checks if the given hostname exists in the host_name * array. Returns 0 if the host doesn't exist, and 1 if it does exist */ static int in_host_array(char *host) { int i; if (debug) (void) printf("%s ", host); if ((strcmp(hostname, host) == 0) || (strcmp(LOGHOST, host) == 0)) return (1); for (i = 0; i < addrix; i++) { if (strcmp(host_name[i], host) == 0) return (1); } return (0); } /* * add_to_host_array adds a hostname to the host_name array. But if * the array is already full, then it first reallocates the array with * HOST_NAME_INCR extra elements. If the realloc fails, then it does * nothing and leaves host_name the way it was previous to the call. */ static void add_to_host_array(char *host) { void *new_block = NULL; /* Make sure we don't overrun host_name. */ if (addrix >= host_name_count) { host_name_count += HOST_NAME_INCR; new_block = realloc((void *)host_name, host_name_count * sizeof (char *)); if (new_block != NULL) host_name = new_block; else { host_name_count -= HOST_NAME_INCR; return; } } if ((host_name[addrix] = strdup(host)) != NULL) addrix++; } /* * Compares the unqualified hostnames for hosts. Returns 0 if the * names match, and 1 if the names fail to match. */ int str_cmp_unqual_hostname(char *rawname1, char *rawname2) { size_t unq_len1, unq_len2; char *domain; if (debug) { (void) printf("str_cmp_unqual: rawname1= %s, rawname2= %s\n", rawname1, rawname2); } unq_len1 = strcspn(rawname1, "."); unq_len2 = strcspn(rawname2, "."); domain = strchr(rawname1, '.'); if (domain != NULL) { if ((strncmp(rawname1, SM_ADDR_IPV4, unq_len1) == 0) || (strncmp(rawname1, SM_ADDR_IPV6, unq_len1) == 0)) return (1); } if ((unq_len1 == unq_len2) && (strncmp(rawname1, rawname2, unq_len1) == 0)) { return (0); } return (1); } /* * Compares . ASCII names for hosts. Returns * 0 if the addresses match, and 1 if the addresses fail to match. * If the args are indeed specifiers, they should look like this: * * ipv4.192.9.200.1 or ipv6.::C009:C801 */ int str_cmp_address_specifier(char *specifier1, char *specifier2) { size_t unq_len1, unq_len2; char *rawaddr1, *rawaddr2; int af1, af2, len; if (debug) { (void) printf("str_cmp_addr: specifier1= %s, specifier2= %s\n", specifier1, specifier2); } /* * Verify that: * 1. The family tokens match; * 2. The IP addresses following the `.' are legal; and * 3. These addresses match. */ unq_len1 = strcspn(specifier1, "."); unq_len2 = strcspn(specifier2, "."); rawaddr1 = strchr(specifier1, '.'); rawaddr2 = strchr(specifier2, '.'); if (strncmp(specifier1, SM_ADDR_IPV4, unq_len1) == 0) { af1 = AF_INET; len = 4; } else if (strncmp(specifier1, SM_ADDR_IPV6, unq_len1) == 0) { af1 = AF_INET6; len = 16; } else return (1); if (strncmp(specifier2, SM_ADDR_IPV4, unq_len2) == 0) af2 = AF_INET; else if (strncmp(specifier2, SM_ADDR_IPV6, unq_len2) == 0) af2 = AF_INET6; else return (1); if (af1 != af2) return (1); if (rawaddr1 != NULL && rawaddr2 != NULL) { char dst1[16]; char dst2[16]; ++rawaddr1; ++rawaddr2; if (inet_pton(af1, rawaddr1, dst1) == 1 && inet_pton(af2, rawaddr1, dst2) == 1 && memcmp(dst1, dst2, len) == 0) { return (0); } } return (1); } /* * Add IP address strings to the host_name list. */ void merge_ips(void) { struct ifaddrs *ifap, *cifap; int error; error = getifaddrs(&ifap); if (error) { syslog(LOG_WARNING, "getifaddrs error: '%s'", strerror(errno)); return; } for (cifap = ifap; cifap != NULL; cifap = cifap->ifa_next) { struct sockaddr *sa = cifap->ifa_addr; char addr_str[INET6_ADDRSTRLEN]; void *addr = NULL; switch (sa->sa_family) { case AF_INET: { struct sockaddr_in *sin = (struct sockaddr_in *)sa; /* Skip loopback addresses. */ if (sin->sin_addr.s_addr == htonl(INADDR_LOOPBACK)) { continue; } addr = &sin->sin_addr; break; } case AF_INET6: { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa; /* Skip loopback addresses. */ if (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr)) { continue; } addr = &sin6->sin6_addr; break; } case AF_LINK: continue; default: syslog(LOG_WARNING, "Unknown address family %d for " "interface %s", sa->sa_family, cifap->ifa_name); continue; } if (inet_ntop(sa->sa_family, addr, addr_str, sizeof (addr_str)) == NULL) { syslog(LOG_WARNING, "Failed to convert address into " "string representation for interface '%s' " "address family %d", cifap->ifa_name, sa->sa_family); continue; } if (!in_host_array(addr_str)) { add_to_host_array(addr_str); } } freeifaddrs(ifap); } /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California * All Rights Reserved * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ /* * Copyright (c) 2012 by Delphix. All rights reserved. */ /* * sm_statd.c consists of routines used for the intermediate * statd implementation(3.2 rpc.statd); * it creates an entry in "current" directory for each site that it monitors; * after crash and recovery, it moves all entries in "current" * to "backup" directory, and notifies the corresponding statd of its recovery. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sm_statd.h" int LOCAL_STATE; sm_hash_t mon_table[MAX_HASHSIZE]; static sm_hash_t record_table[MAX_HASHSIZE]; static sm_hash_t recov_q; static name_entry *find_name(name_entry **namepp, char *name); static name_entry *insert_name(name_entry **namepp, char *name, int need_alloc); static void delete_name(name_entry **namepp, char *name); static void remove_name(char *name, int op, int startup); static int statd_call_statd(char *name); static void pr_name(char *name, int flag); static void *thr_statd_init(void *); static void *sm_try(void *); static void *thr_call_statd(void *); static void remove_single_name(char *name, char *dir1, char *dir2); static int move_file(char *fromdir, char *file, char *todir); static int count_symlinks(char *dir, char *name, int *count); static char *family2string(sa_family_t family); /* * called when statd first comes up; it searches /etc/sm to gather * all entries to notify its own failure */ void statd_init(void) { struct dirent *dirp; DIR *dp; FILE *fp, *fp_tmp; int i, tmp_state; char state_file[MAXPATHLEN+SM_MAXPATHLEN]; if (debug) (void) printf("enter statd_init\n"); /* * First try to open the file. If that fails, try to create it. * If that fails, give up. */ if ((fp = fopen(STATE, "r+")) == NULL) { if ((fp = fopen(STATE, "w+")) == NULL) { syslog(LOG_ERR, "can't open %s: %m", STATE); exit(1); } else (void) chmod(STATE, 0644); } if ((fscanf(fp, "%d", &LOCAL_STATE)) == EOF) { if (debug >= 2) (void) printf("empty file\n"); LOCAL_STATE = 0; } /* * Scan alternate paths for largest "state" number */ for (i = 0; i < pathix; i++) { (void) sprintf(state_file, "%s/statmon/state", path_name[i]); if ((fp_tmp = fopen(state_file, "r+")) == NULL) { if ((fp_tmp = fopen(state_file, "w+")) == NULL) { if (debug) syslog(LOG_ERR, "can't open %s: %m", state_file); continue; } else (void) chmod(state_file, 0644); } if ((fscanf(fp_tmp, "%d", &tmp_state)) == EOF) { if (debug) syslog(LOG_ERR, "statd: %s: file empty\n", state_file); (void) fclose(fp_tmp); continue; } if (tmp_state > LOCAL_STATE) { LOCAL_STATE = tmp_state; if (debug) (void) printf("Update LOCAL STATE: %d\n", tmp_state); } (void) fclose(fp_tmp); } LOCAL_STATE = ((LOCAL_STATE%2) == 0) ? LOCAL_STATE+1 : LOCAL_STATE+2; /* IF local state overflows, reset to value 1 */ if (LOCAL_STATE < 0) { LOCAL_STATE = 1; } /* Copy the LOCAL_STATE value back to all stat files */ if (fseek(fp, 0, 0) == -1) { syslog(LOG_ERR, "statd: fseek failed\n"); exit(1); } (void) fprintf(fp, "%-10d", LOCAL_STATE); (void) fflush(fp); if (fsync(fileno(fp)) == -1) { syslog(LOG_ERR, "statd: fsync failed\n"); exit(1); } (void) fclose(fp); for (i = 0; i < pathix; i++) { (void) sprintf(state_file, "%s/statmon/state", path_name[i]); if ((fp_tmp = fopen(state_file, "r+")) == NULL) { if ((fp_tmp = fopen(state_file, "w+")) == NULL) { syslog(LOG_ERR, "can't open %s: %m", state_file); continue; } else (void) chmod(state_file, 0644); } (void) fprintf(fp_tmp, "%-10d", LOCAL_STATE); (void) fflush(fp_tmp); if (fsync(fileno(fp_tmp)) == -1) { syslog(LOG_ERR, "statd: %s: fsync failed\n", state_file); (void) fclose(fp_tmp); exit(1); } (void) fclose(fp_tmp); } if (debug) (void) printf("local state = %d\n", LOCAL_STATE); if ((mkdir(CURRENT, SM_DIRECTORY_MODE)) == -1) { if (errno != EEXIST) { syslog(LOG_ERR, "statd: mkdir current, error %m\n"); exit(1); } } if ((mkdir(BACKUP, SM_DIRECTORY_MODE)) == -1) { if (errno != EEXIST) { syslog(LOG_ERR, "statd: mkdir backup, error %m\n"); exit(1); } } /* get all entries in CURRENT into BACKUP */ if ((dp = opendir(CURRENT)) == NULL) { syslog(LOG_ERR, "statd: open current directory, error %m\n"); exit(1); } while ((dirp = readdir(dp)) != NULL) { if (strcmp(dirp->d_name, ".") != 0 && strcmp(dirp->d_name, "..") != 0) { /* rename all entries from CURRENT to BACKUP */ (void) move_file(CURRENT, dirp->d_name, BACKUP); } } (void) closedir(dp); /* Contact hosts' statd */ if (thr_create(NULL, 0, thr_statd_init, NULL, THR_DETACHED, NULL)) { syslog(LOG_ERR, "statd: unable to create thread for thr_statd_init\n"); exit(1); } } /* * Work thread which contacts hosts' statd. */ static void * thr_statd_init(void *arg __unused) { struct dirent *dirp; DIR *dp; int num_threads; int num_join; int i; char *name; char buf[MAXPATHLEN+SM_MAXPATHLEN]; /* Go thru backup directory and contact hosts */ if ((dp = opendir(BACKUP)) == NULL) { syslog(LOG_ERR, "statd: open backup directory, error %m\n"); exit(1); } /* * Create "UNDETACHED" threads for each symlink and (unlinked) * regular file in backup directory to initiate statd_call_statd. * NOTE: These threads are the only undetached threads in this * program and thus, the thread id is not needed to join the threads. */ num_threads = 0; while ((dirp = readdir(dp)) != NULL) { /* * If host file is not a symlink, don't bother to * spawn a thread for it. If any link(s) refer to * it, the host will be contacted using the link(s). * If not, we'll deal with it during the legacy pass. */ (void) sprintf(buf, "%s/%s", BACKUP, dirp->d_name); if (is_symlink(buf) == 0) { continue; } /* * If the num_threads has exceeded, wait until * a certain amount of threads have finished. * Currently, 10% of threads created should be joined. */ if (num_threads > MAX_THR) { num_join = num_threads/PERCENT_MINJOIN; for (i = 0; i < num_join; i++) thr_join(0, 0, 0); num_threads -= num_join; } /* * If can't alloc name then print error msg and * continue to next item on list. */ name = strdup(dirp->d_name); if (name == NULL) { syslog(LOG_ERR, "statd: unable to allocate space for name %s\n", dirp->d_name); continue; } /* Create a thread to do a statd_call_statd for name */ if (thr_create(NULL, 0, thr_call_statd, name, 0, NULL)) { syslog(LOG_ERR, "statd: unable to create thr_call_statd() " "for name %s.\n", dirp->d_name); free(name); continue; } num_threads++; } /* * Join the other threads created above before processing the * legacies. This allows all symlinks and the regular files * to which they correspond to be processed and deleted. */ for (i = 0; i < num_threads; i++) { thr_join(0, 0, 0); } /* * The second pass checks for `legacies': regular files which * never had symlinks pointing to them at all, just like in the * good old (pre-1184192 fix) days. Once a machine has cleaned * up its legacies they should only reoccur due to catastrophes * (e.g., severed symlinks). */ rewinddir(dp); num_threads = 0; while ((dirp = readdir(dp)) != NULL) { if (strcmp(dirp->d_name, ".") == 0 || strcmp(dirp->d_name, "..") == 0) { continue; } (void) sprintf(buf, "%s/%s", BACKUP, dirp->d_name); if (is_symlink(buf)) { /* * We probably couldn't reach this host and it's * been put on the recovery queue for retry. * Skip it and keep looking for regular files. */ continue; } if (debug) { (void) printf("thr_statd_init: legacy %s\n", dirp->d_name); } /* * If the number of threads exceeds the maximum, wait * for some fraction of them to finish before * continuing. */ if (num_threads > MAX_THR) { num_join = num_threads/PERCENT_MINJOIN; for (i = 0; i < num_join; i++) thr_join(0, 0, 0); num_threads -= num_join; } /* * If can't alloc name then print error msg and * continue to next item on list. */ name = strdup(dirp->d_name); if (name == NULL) { syslog(LOG_ERR, "statd: unable to allocate space for name %s\n", dirp->d_name); continue; } /* Create a thread to do a statd_call_statd for name */ if (thr_create(NULL, 0, thr_call_statd, name, 0, NULL)) { syslog(LOG_ERR, "statd: unable to create thr_call_statd() " "for name %s.\n", dirp->d_name); free(name); continue; } num_threads++; } (void) closedir(dp); /* * Join the other threads created above before creating thread * to process items in recovery table. */ for (i = 0; i < num_threads; i++) { thr_join(0, 0, 0); } /* * Need to only copy /var/statmon/sm.bak to alternate paths, since * the only hosts in /var/statmon/sm should be the ones currently * being monitored and already should be in alternate paths as part * of insert_mon(). */ for (i = 0; i < pathix; i++) { (void) sprintf(buf, "%s/statmon/sm.bak", path_name[i]); if ((mkdir(buf, SM_DIRECTORY_MODE)) == -1) { if (errno != EEXIST) syslog(LOG_ERR, "statd: mkdir %s error %m\n", buf); else copydir_from_to(BACKUP, buf); } else copydir_from_to(BACKUP, buf); } /* * Reset the die and in_crash variables. */ mutex_lock(&crash_lock); die = 0; in_crash = 0; mutex_unlock(&crash_lock); if (debug) (void) printf("Creating thread for sm_try\n"); /* Continue to notify statd on hosts that were unreachable. */ if (thr_create(NULL, 0, sm_try, NULL, THR_DETACHED, NULL)) syslog(LOG_ERR, "statd: unable to create thread for sm_try().\n"); thr_exit(NULL); #ifdef lint return (0); #endif } /* * Work thread to make call to statd_call_statd. */ void * thr_call_statd(void *namep) { char *name = (char *)namep; /* * If statd of name is unreachable, add name to recovery table * otherwise if statd_call_statd was successful, remove from backup. */ if (statd_call_statd(name) != 0) { int n; char *tail; char path[MAXPATHLEN]; /* * since we are constructing this pathname below we add * another space for the terminating NULL so we don't * overflow our buffer when we do the readlink */ char rname[MAXNAMELEN + 1]; if (debug) { (void) printf( "statd call failed, inserting %s in recov_q\n", name); } mutex_lock(&recov_q.lock); (void) insert_name(&recov_q.sm_recovhdp, name, 0); mutex_unlock(&recov_q.lock); /* * If we queued a symlink name in the recovery queue, * we now clean up the regular file to which it referred. * This may leave a severed symlink if multiple links * referred to one regular file; this is unaesthetic but * it works. The big benefit is that it prevents us * from recovering the same host twice (as symlink and * as regular file) needlessly, usually on separate reboots. */ (void) strcpy(path, BACKUP); (void) strcat(path, "/"); (void) strcat(path, name); if (is_symlink(path)) { n = readlink(path, rname, MAXNAMELEN); if (n <= 0) { if (debug >= 2) { (void) printf( "thr_call_statd: can't read " "link %s\n", path); } } else { rname[n] = '\0'; tail = strrchr(path, '/') + 1; if ((strlen(BACKUP) + strlen(rname) + 2) <= MAXPATHLEN) { (void) strcpy(tail, rname); delete_file(path); } else if (debug) { printf("thr_call_statd: path over" "maxpathlen!\n"); } } } if (debug) pr_name(name, 0); } else { /* * If `name' is an IP address symlink to a name file, * remove it now. If it is the last such symlink, * remove the name file as well. Regular files with * no symlinks to them are assumed to be legacies and * are removed as well. */ remove_name(name, 1, 1); free(name); } thr_exit((void *) 0); #ifdef lint return (0); #endif } /* * Notifies the statd of host specified by name to indicate that * state has changed for this server. */ static int statd_call_statd(char *name) { enum clnt_stat clnt_stat; struct timeval tottimeout; CLIENT *clnt; char *name_or_addr; stat_chge ntf; int i; int rc; size_t unq_len; ntf.mon_name = hostname; ntf.state = LOCAL_STATE; if (debug) (void) printf("statd_call_statd at %s\n", name); /* * If it looks like an ASCII
.
specifier, * strip off the family - we just want the address when obtaining * a client handle. * If it's anything else, just pass it on to create_client(). */ unq_len = strcspn(name, "."); if ((strncmp(name, SM_ADDR_IPV4, unq_len) == 0) || (strncmp(name, SM_ADDR_IPV6, unq_len) == 0)) { name_or_addr = strchr(name, '.') + 1; } else { name_or_addr = name; } /* * NOTE: We depend here upon the fact that the RPC client code * allows us to use ASCII dotted quad `names', i.e. "192.9.200.1". * This may change in a future release. */ if (debug) { (void) printf("statd_call_statd: calling create_client(%s)\n", name_or_addr); } tottimeout.tv_sec = SM_RPC_TIMEOUT; tottimeout.tv_usec = 0; if ((clnt = create_client(name_or_addr, SM_PROG, SM_VERS, NULL, &tottimeout)) == NULL) { return (-1); } /* Perform notification to client */ rc = 0; clnt_stat = clnt_call(clnt, SM_NOTIFY, xdr_stat_chge, (char *)&ntf, xdr_void, NULL, tottimeout); if (debug) { (void) printf("clnt_stat=%s(%d)\n", clnt_sperrno(clnt_stat), clnt_stat); } if (clnt_stat != (int)RPC_SUCCESS) { syslog(LOG_WARNING, "statd: cannot talk to statd at %s, %s(%d)\n", name_or_addr, clnt_sperrno(clnt_stat), clnt_stat); rc = -1; } /* * Wait until the host_name is populated. */ (void) mutex_lock(&merges_lock); while (in_merges) (void) cond_wait(&merges_cond, &merges_lock); (void) mutex_unlock(&merges_lock); /* For HA systems and multi-homed hosts */ ntf.state = LOCAL_STATE; for (i = 0; i < addrix; i++) { ntf.mon_name = host_name[i]; if (debug) (void) printf("statd_call_statd at %s\n", name_or_addr); clnt_stat = clnt_call(clnt, SM_NOTIFY, xdr_stat_chge, (char *)&ntf, xdr_void, NULL, tottimeout); if (clnt_stat != (int)RPC_SUCCESS) { syslog(LOG_WARNING, "statd: cannot talk to statd at %s, %s(%d)\n", name_or_addr, clnt_sperrno(clnt_stat), clnt_stat); rc = -1; } } clnt_destroy(clnt); return (rc); } /* * Continues to contact hosts in recovery table that were unreachable. * NOTE: There should only be one sm_try thread executing and * thus locks are not needed for recovery table. Die is only cleared * after all the hosts has at least been contacted once. The reader/writer * lock ensures to finish this code before an sm_crash is started. Die * variable will signal it. */ void * sm_try(void *arg __unused) { name_entry *nl, *next; timestruc_t wtime; int delay = 0; rw_rdlock(&thr_rwlock); if (mutex_trylock(&sm_trylock)) goto out; mutex_lock(&crash_lock); while (!die) { wtime.tv_sec = delay; wtime.tv_nsec = 0; /* * Wait until signalled to wakeup or time expired. * If signalled to be awoken, then a crash has occurred * or otherwise time expired. */ if (cond_reltimedwait(&retrywait, &crash_lock, &wtime) == 0) { break; } /* Exit loop if queue is empty */ if ((next = recov_q.sm_recovhdp) == NULL) break; mutex_unlock(&crash_lock); while (((nl = next) != NULL) && (!die)) { next = next->nxt; if (statd_call_statd(nl->name) == 0) { /* remove name from BACKUP */ remove_name(nl->name, 1, 0); mutex_lock(&recov_q.lock); /* remove entry from recovery_q */ delete_name(&recov_q.sm_recovhdp, nl->name); mutex_unlock(&recov_q.lock); } else { /* * Print message only once since unreachable * host can be contacted forever. */ if (delay == 0) syslog(LOG_WARNING, "statd: host %s is not " "responding\n", nl->name); } } /* * Increment the amount of delay before restarting again. * The amount of delay should not exceed the MAX_DELAYTIME. */ if (delay <= MAX_DELAYTIME) delay += INC_DELAYTIME; mutex_lock(&crash_lock); } mutex_unlock(&crash_lock); mutex_unlock(&sm_trylock); out: rw_unlock(&thr_rwlock); if (debug) (void) printf("EXITING sm_try\n"); thr_exit((void *) 0); #ifdef lint return (0); #endif } /* * Malloc's space and returns the ptr to malloc'ed space. NULL if unsuccessful. */ char * xmalloc(unsigned len) { char *new; if ((new = malloc(len)) == 0) { syslog(LOG_ERR, "statd: malloc, error %m\n"); return (NULL); } else { (void) memset(new, 0, len); return (new); } } /* * the following two routines are very similar to * insert_mon and delete_mon in sm_proc.c, except the structture * is different */ static name_entry * insert_name(name_entry **namepp, char *name, int need_alloc) { name_entry *new; new = (name_entry *)xmalloc(sizeof (name_entry)); if (new == (name_entry *) NULL) return (NULL); /* Allocate name when needed which is only when adding to record_t */ if (need_alloc) { if ((new->name = strdup(name)) == NULL) { syslog(LOG_ERR, "statd: strdup, error %m\n"); free(new); return (NULL); } } else new->name = name; new->nxt = *namepp; if (new->nxt != NULL) new->nxt->prev = new; new->prev = (name_entry *) NULL; *namepp = new; if (debug) { (void) printf("insert_name: inserted %s at %p\n", name, (void *)namepp); } return (new); } /* * Deletes name from specified list (namepp). */ static void delete_name(name_entry **namepp, char *name) { name_entry *nl; nl = *namepp; while (nl != NULL) { if (str_cmp_address_specifier(nl->name, name) == 0 || str_cmp_unqual_hostname(nl->name, name) == 0) { if (nl->prev != NULL) nl->prev->nxt = nl->nxt; else *namepp = nl->nxt; if (nl->nxt != NULL) nl->nxt->prev = nl->prev; free(nl->name); free(nl); return; } nl = nl->nxt; } } /* * Finds name from specified list (namep). */ static name_entry * find_name(name_entry **namep, char *name) { name_entry *nl; nl = *namep; while (nl != NULL) { if (str_cmp_unqual_hostname(nl->name, name) == 0) { return (nl); } nl = nl->nxt; } return (NULL); } /* * Creates a file. */ int create_file(char *name) { int fd; /* * The file might already exist. If it does, we ask for only write * permission, since that's all the file was created with. */ if ((fd = open(name, O_CREAT | O_WRONLY, S_IWUSR)) == -1) { if (errno != EEXIST) { syslog(LOG_ERR, "can't open %s: %m", name); return (1); } } if (debug >= 2) (void) printf("%s is created\n", name); if (close(fd)) { syslog(LOG_ERR, "statd: close, error %m\n"); return (1); } return (0); } /* * Deletes the file specified by name. */ void delete_file(char *name) { if (debug >= 2) (void) printf("Remove monitor entry %s\n", name); if (unlink(name) == -1) { if (errno != ENOENT) syslog(LOG_ERR, "statd: unlink of %s, error %m", name); } } /* * Return 1 if file is a symlink, else 0. */ int is_symlink(char *file) { int error; struct stat lbuf; do { bzero((caddr_t)&lbuf, sizeof (lbuf)); error = lstat(file, &lbuf); } while (error == EINTR); if (error == 0) { return ((lbuf.st_mode & S_IFMT) == S_IFLNK); } return (0); } /* * Moves the file specified by `from' to `to' only if the * new file is guaranteed to be created (which is presumably * why we don't just do a rename(2)). If `from' is a * symlink, the destination file will be a similar symlink * in the directory of `to'. * * Returns 0 for success, 1 for failure. */ static int move_file(char *fromdir, char *file, char *todir) { int n; char rname[MAXNAMELEN + 1]; /* +1 for the terminating NULL */ char from[MAXPATHLEN]; char to[MAXPATHLEN]; (void) strcpy(from, fromdir); (void) strcat(from, "/"); (void) strcat(from, file); if (is_symlink(from)) { /* * Dig out the name of the regular file the link points to. */ n = readlink(from, rname, MAXNAMELEN); if (n <= 0) { if (debug >= 2) { (void) printf("move_file: can't read link %s\n", from); } return (1); } rname[n] = '\0'; /* * Create the link. */ if (create_symlink(todir, rname, file) != 0) { return (1); } } else { /* * Do what we've always done to move regular files. */ (void) strcpy(to, todir); (void) strcat(to, "/"); (void) strcat(to, file); if (create_file(to) != 0) { return (1); } } /* * Remove the old file if we've created the new one. */ if (unlink(from) < 0) { syslog(LOG_ERR, "move_file: unlink of %s, error %m", from); return (1); } return (0); } /* * Create a symbolic link named `lname' to regular file `rname'. * Both files should be in directory `todir'. */ int create_symlink(char *todir, char *rname, char *lname) { int error = 0; char lpath[MAXPATHLEN]; /* * Form the full pathname of the link. */ (void) strcpy(lpath, todir); (void) strcat(lpath, "/"); (void) strcat(lpath, lname); /* * Now make the new symlink ... */ if (symlink(rname, lpath) < 0) { error = errno; if (error != 0 && error != EEXIST) { if (debug >= 2) { (void) printf("create_symlink: can't link " "%s/%s -> %s\n", todir, lname, rname); } return (1); } } if (debug) { if (error == EEXIST) { (void) printf("link %s/%s -> %s already exists\n", todir, lname, rname); } else { (void) printf("created link %s/%s -> %s\n", todir, lname, rname); } } return (0); } /* * remove the name from the specified directory * op = 0: CURRENT * op = 1: BACKUP */ static void remove_name(char *name, int op, int startup) { int i; char *alt_dir; char *queue; if (op == 0) { alt_dir = "statmon/sm"; queue = CURRENT; } else { alt_dir = "statmon/sm.bak"; queue = BACKUP; } remove_single_name(name, queue, NULL); /* * At startup, entries have not yet been copied to alternate * directories and thus do not need to be removed. */ if (startup == 0) { for (i = 0; i < pathix; i++) { remove_single_name(name, path_name[i], alt_dir); } } } /* * Remove the name from the specified directory, which is dir1/dir2 or * dir1, depending on whether dir2 is NULL. */ static void remove_single_name(char *name, char *dir1, char *dir2) { int n, error; char path[MAXPATHLEN+MAXNAMELEN+SM_MAXPATHLEN]; /* why > MAXPATHLEN? */ char dirpath[MAXPATHLEN]; char rname[MAXNAMELEN + 1]; /* +1 for NULL term */ if (strlen(name) + strlen(dir1) + (dir2 != NULL ? strlen(dir2) : 0) + 3 > MAXPATHLEN) { if (dir2 != NULL) syslog(LOG_ERR, "statd: pathname too long: %s/%s/%s\n", dir1, dir2, name); else syslog(LOG_ERR, "statd: pathname too long: %s/%s\n", dir1, name); return; } (void) strcpy(path, dir1); (void) strcat(path, "/"); if (dir2 != NULL) { (void) strcat(path, dir2); (void) strcat(path, "/"); } (void) strcpy(dirpath, path); /* save here - we may need it shortly */ (void) strcat(path, name); /* * Despite the name of this routine :-@), `path' may be a symlink * to a regular file. If it is, and if that file has no other * links to it, we must remove it now as well. */ if (is_symlink(path)) { n = readlink(path, rname, MAXNAMELEN); if (n > 0) { rname[n] = '\0'; if (count_symlinks(dirpath, rname, &n) < 0) { return; } if (n == 1) { (void) strcat(dirpath, rname); error = unlink(dirpath); if (debug >= 2) { if (error < 0) { (void) printf( "remove_name: can't " "unlink %s\n", dirpath); } else { (void) printf( "remove_name: unlinked ", "%s\n", dirpath); } } } } else { /* * Policy: if we can't read the symlink, leave it * here for analysis by the system administrator. */ syslog(LOG_ERR, "statd: can't read link %s: %m\n", path); } } /* * If it's a regular file, we can assume all symlinks and the * files to which they refer have been processed already - just * fall through to here to remove it. */ delete_file(path); } /* * Count the number of symlinks in `dir' which point to `name' (also in dir). * Passes back symlink count in `count'. * Returns 0 for success, < 0 for failure. */ static int count_symlinks(char *dir, char *name, int *count) { int cnt = 0; int n; DIR *dp; struct dirent *dirp; char lpath[MAXPATHLEN]; char rname[MAXNAMELEN + 1]; /* +1 for term NULL */ if ((dp = opendir(dir)) == NULL) { syslog(LOG_ERR, "count_symlinks: open %s dir, error %m\n", dir); return (-1); } while ((dirp = readdir(dp)) != NULL) { if (strcmp(dirp->d_name, ".") == 0 || strcmp(dirp->d_name, "..") == 0) { continue; } (void) sprintf(lpath, "%s%s", dir, dirp->d_name); if (is_symlink(lpath)) { /* * Fetch the name of the file the symlink refers to. */ n = readlink(lpath, rname, MAXNAMELEN); if (n <= 0) { if (debug >= 2) { (void) printf( "count_symlinks: can't read link " "%s\n", lpath); } continue; } rname[n] = '\0'; /* * If `rname' matches `name', bump the count. There * may well be multiple symlinks to the same name, so * we must continue to process the entire directory. */ if (strcmp(rname, name) == 0) { cnt++; } } } (void) closedir(dp); if (debug) { (void) printf("count_symlinks: found %d symlinks\n", cnt); } *count = cnt; return (0); } /* * Manage the cache of hostnames. An entry for each host that has recently * locked a file is kept. There is an in-ram table (record_table) and an empty * file in the file system name space (/var/statmon/sm/). This * routine adds (deletes) the name to (from) the in-ram table and the entry * to (from) the file system name space. * * If op == 1 then the name is added to the queue otherwise the name is * deleted. */ void record_name(char *name, int op) { name_entry *nl; int i; char path[MAXPATHLEN+MAXNAMELEN+SM_MAXPATHLEN]; name_entry **record_q; unsigned int hash; /* * These names are supposed to be just host names, not paths or * other arbitrary files. * manipulating the empty pathname unlinks CURRENT, * manipulating files with '/' would allow you to create and unlink * files all over the system; LOG_AUTH, it's a security thing. * Don't remove the directories . and .. */ if (name == NULL) return; if (name[0] == '\0' || strchr(name, '/') != NULL || strcmp(name, ".") == 0 || strcmp(name, "..") == 0) { syslog(LOG_ERR|LOG_AUTH, "statd: attempt to %s \"%s/%s\"", op == 1 ? "create" : "remove", CURRENT, name); return; } SMHASH(name, hash); if (debug) { if (op == 1) (void) printf("inserting %s at hash %d,\n", name, hash); else (void) printf("deleting %s at hash %d\n", name, hash); pr_name(name, 1); } if (op == 1) { /* insert */ mutex_lock(&record_table[hash].lock); record_q = &record_table[hash].sm_rechdp; if ((nl = find_name(record_q, name)) == NULL) { int path_len; if ((nl = insert_name(record_q, name, 1)) != (name_entry *) NULL) nl->count++; mutex_unlock(&record_table[hash].lock); /* make an entry in current directory */ path_len = strlen(CURRENT) + strlen(name) + 2; if (path_len > MAXPATHLEN) { syslog(LOG_ERR, "statd: pathname too long: %s/%s\n", CURRENT, name); return; } (void) strcpy(path, CURRENT); (void) strcat(path, "/"); (void) strcat(path, name); (void) create_file(path); if (debug) { (void) printf("After insert_name\n"); pr_name(name, 1); } /* make an entry in alternate paths */ for (i = 0; i < pathix; i++) { path_len = strlen(path_name[i]) + strlen("/statmon/sm/") + strlen(name) + 1; if (path_len > MAXPATHLEN) { syslog(LOG_ERR, "statd: pathname too " "long: %s/statmon/sm/%s\n", path_name[i], name); continue; } (void) strcpy(path, path_name[i]); (void) strcat(path, "/statmon/sm/"); (void) strcat(path, name); (void) create_file(path); } return; } nl->count++; mutex_unlock(&record_table[hash].lock); } else { /* delete */ mutex_lock(&record_table[hash].lock); record_q = &record_table[hash].sm_rechdp; if ((nl = find_name(record_q, name)) == NULL) { mutex_unlock(&record_table[hash].lock); return; } nl->count--; if (nl->count == 0) { delete_name(record_q, name); mutex_unlock(&record_table[hash].lock); /* remove this entry from current directory */ remove_name(name, 0, 0); } else mutex_unlock(&record_table[hash].lock); if (debug) { (void) printf("After delete_name \n"); pr_name(name, 1); } } } /* * This routine adds a symlink in the form of an IP address in * text string format that is linked to the name already recorded in the * filesystem name space by record_name(). Enough information is * (hopefully) provided to support other address types in the future. * The purpose of this is to cache enough information to contact * hosts in other domains during server crash recovery (see bugid * 1184192). * * The worst failure mode here is that the symlink is not made, and * statd falls back to the old buggy behavior. */ void record_addr(char *name, sa_family_t family, struct netobj *ah) { int i; int path_len; char *famstr; struct in_addr addr = { 0 }; char *addr6 = NULL; char ascii_addr[MAXNAMELEN]; char path[MAXPATHLEN]; if (family == AF_INET) { if (ah->n_len != sizeof (struct in_addr)) return; addr = *(struct in_addr *)ah->n_bytes; } else if (family == AF_INET6) { if (ah->n_len != sizeof (struct in6_addr)) return; addr6 = (char *)ah->n_bytes; } else return; if (debug) { if (family == AF_INET) (void) printf("record_addr: addr= %x\n", addr.s_addr); else if (family == AF_INET6) (void) printf("record_addr: addr= %x\n", ((struct in6_addr *)addr6)->s6_addr); } if (family == AF_INET) { if ((ntohl(addr.s_addr) & 0xff000000) == 0) { syslog(LOG_DEBUG, "record_addr: illegal IP address %x\n", addr.s_addr); return; } } /* convert address to ASCII */ famstr = family2string(family); if (famstr == NULL) { syslog(LOG_DEBUG, "record_addr: unsupported address family %d\n", family); return; } switch (family) { char abuf[INET6_ADDRSTRLEN]; case AF_INET: (void) sprintf(ascii_addr, "%s.%s", famstr, inet_ntoa(addr)); break; case AF_INET6: (void) sprintf(ascii_addr, "%s.%s", famstr, inet_ntop(family, addr6, abuf, sizeof (abuf))); break; default: if (debug) { (void) printf( "record_addr: family2string supports unknown " "family %d (%s)\n", family, famstr); } free(famstr); return; } if (debug) { (void) printf("record_addr: ascii_addr= %s\n", ascii_addr); } free(famstr); /* * Make the symlink in CURRENT. The `name' file should have * been created previously by record_name(). */ (void) create_symlink(CURRENT, name, ascii_addr); /* * Similarly for alternate paths. */ for (i = 0; i < pathix; i++) { path_len = strlen(path_name[i]) + strlen("/statmon/sm/") + strlen(name) + 1; if (path_len > MAXPATHLEN) { syslog(LOG_ERR, "statd: pathname too long: %s/statmon/sm/%s\n", path_name[i], name); continue; } (void) strcpy(path, path_name[i]); (void) strcat(path, "/statmon/sm"); (void) create_symlink(path, name, ascii_addr); } } /* * SM_CRASH - simulate a crash of statd. */ void sm_crash(void) { name_entry *nl, *next; mon_entry *nl_monp, *mon_next; int k; my_id *nl_idp; for (k = 0; k < MAX_HASHSIZE; k++) { mutex_lock(&mon_table[k].lock); if ((mon_next = mon_table[k].sm_monhdp) == (mon_entry *) NULL) { mutex_unlock(&mon_table[k].lock); continue; } else { while ((nl_monp = mon_next) != NULL) { mon_next = mon_next->nxt; nl_idp = &nl_monp->id.mon_id.my_id; free(nl_monp->id.mon_id.mon_name); free(nl_idp->my_name); free(nl_monp); } mon_table[k].sm_monhdp = NULL; } mutex_unlock(&mon_table[k].lock); } /* Clean up entries in record table */ for (k = 0; k < MAX_HASHSIZE; k++) { mutex_lock(&record_table[k].lock); if ((next = record_table[k].sm_rechdp) == (name_entry *) NULL) { mutex_unlock(&record_table[k].lock); continue; } else { while ((nl = next) != NULL) { next = next->nxt; free(nl->name); free(nl); } record_table[k].sm_rechdp = NULL; } mutex_unlock(&record_table[k].lock); } /* Clean up entries in recovery table */ mutex_lock(&recov_q.lock); if ((next = recov_q.sm_recovhdp) != NULL) { while ((nl = next) != NULL) { next = next->nxt; free(nl->name); free(nl); } recov_q.sm_recovhdp = NULL; } mutex_unlock(&recov_q.lock); statd_init(); } /* * Initialize the hash tables: mon_table, record_table, recov_q and * locks. */ void sm_inithash(void) { int k; if (debug) (void) printf("Initializing hash tables\n"); for (k = 0; k < MAX_HASHSIZE; k++) { mon_table[k].sm_monhdp = NULL; record_table[k].sm_rechdp = NULL; mutex_init(&mon_table[k].lock, USYNC_THREAD, NULL); mutex_init(&record_table[k].lock, USYNC_THREAD, NULL); } mutex_init(&recov_q.lock, USYNC_THREAD, NULL); recov_q.sm_recovhdp = NULL; } /* * Maps a socket address family to a name string, or NULL if the family * is not supported by statd. * Caller is responsible for freeing storage used by result string, if any. */ static char * family2string(sa_family_t family) { char *rc; switch (family) { case AF_INET: rc = strdup(SM_ADDR_IPV4); break; case AF_INET6: rc = strdup(SM_ADDR_IPV6); break; default: rc = NULL; break; } return (rc); } /* * Prints out list in record_table if flag is 1 otherwise * prints out each list in recov_q specified by name. */ static void pr_name(char *name, int flag) { name_entry *nl; unsigned int hash; if (!debug) return; if (flag) { SMHASH(name, hash); (void) printf("*****record_q: "); mutex_lock(&record_table[hash].lock); nl = record_table[hash].sm_rechdp; while (nl != NULL) { (void) printf("(%x), ", (int)nl); nl = nl->nxt; } mutex_unlock(&record_table[hash].lock); } else { (void) printf("*****recovery_q: "); mutex_lock(&recov_q.lock); nl = recov_q.sm_recovhdp; while (nl != NULL) { (void) printf("(%x), ", (int)nl); nl = nl->nxt; } mutex_unlock(&recov_q.lock); } (void) printf("\n"); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California * All Rights Reserved * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ /* * Copyright (c) 2012 by Delphix. All rights reserved. */ #ifndef _SM_STATD_H #define _SM_STATD_H #ifdef __cplusplus extern "C" { #endif /* Limit defines */ #define SM_DIRECTORY_MODE 00755 #define MAX_HASHSIZE 50 #define SM_RPC_TIMEOUT 15 #define PERCENT_MINJOIN 10 #define MAX_FDS 256 #define MAX_THR 25 #define INC_DELAYTIME 30 #define MAX_DELAYTIME 300 #define SM_CLTS_TIMEOUT 15 /* max strlen of /statmon/state, /statmon/sm.bak, /statmon/sm */ #define SM_MAXPATHLEN 17 /* Increment size for realloc of array host_name */ #define HOST_NAME_INCR 5 /* supported address family names in /var/statmon symlinks */ #define SM_ADDR_IPV4 "ipv4" #define SM_ADDR_IPV6 "ipv6" /* Supported for readdir_r() */ #define MAXDIRENT (sizeof (struct dirent) + _POSIX_PATH_MAX + 1) /* Structure entry for monitor table (mon_table) */ struct mon_entry { mon id; /* mon information: mon_name, my_id */ struct mon_entry *prev; /* Prev ptr to prev entry in hash */ struct mon_entry *nxt; /* Next ptr to next entry in hash */ }; typedef struct mon_entry mon_entry; /* Structure entry for record (record_table) and recovery (recov_q) tables */ struct name_entry { char *name; /* name of host */ int count; /* count of entries */ struct name_entry *prev; /* Prev ptr to prev entry in hash */ struct name_entry *nxt; /* Next ptr to next entry in hash */ }; typedef struct name_entry name_entry; /* Structure for passing arguments into thread send_notice */ typedef struct moninfo { mon id; /* Monitor information */ int state; /* Current state */ } moninfo_t; /* Structure entry for hash tables */ typedef struct sm_hash { union { struct mon_entry *mon_hdptr; /* Head ptr for mon_table */ name_entry *rec_hdptr; /* Head ptr for record_table */ name_entry *recov_hdptr; /* Head ptr for recov_q */ } smhd_t; mutex_t lock; /* Lock to protect each list head */ } sm_hash_t; #define sm_monhdp smhd_t.mon_hdptr #define sm_rechdp smhd_t.rec_hdptr #define sm_recovhdp smhd_t.recov_hdptr /* Structure entry for address list in name-to-address entry */ typedef struct addr_entry { struct addr_entry *next; struct netobj ah; sa_family_t family; } addr_entry_t; /* Structure entry for name-to-address translation table */ typedef struct name_addr_entry { struct name_addr_entry *next; char *name; struct addr_entry *addresses; } name_addr_entry_t; /* Hash tables for each of the in-cache information */ extern sm_hash_t mon_table[MAX_HASHSIZE]; /* Global variables */ extern mutex_t crash_lock; /* lock for die and crash variables */ extern int die; /* Flag to indicate that an SM_CRASH */ /* request came in & to stop threads cleanly */ extern int in_crash; /* Flag to single thread sm_crash requests. */ extern int regfiles_only; /* Flag to indicate symlink use in statmon */ extern mutex_t sm_trylock; /* Lock to single thread sm_try */ /* * The only established lock precedence here is: * * thr_rwlock > name_addrlock */ extern mutex_t name_addrlock; /* Locks all entries of name-to-addr table */ extern rwlock_t thr_rwlock; /* Reader/writer lock for requests coming in */ extern cond_t retrywait; /* Condition to wait before starting retry */ extern boolean_t in_merges; /* Flag to indicate the host_name is not */ /* populated yet */ extern mutex_t merges_lock; /* Lock for in_merges variable */ extern cond_t merges_cond; /* Condition variable for in_merges */ extern char STATE[MAXPATHLEN], CURRENT[MAXPATHLEN]; extern char BACKUP[MAXPATHLEN]; extern int LOCAL_STATE; /* * Hash functions for monitor and record hash tables. * Functions are hashed based on first 2 letters and last 2 letters of name. * If only 1 letter in name, then, hash only on 1 letter. */ #define SMHASH(name, key) { \ int l; \ key = *name; \ if ((l = strlen(name)) != 1) \ key |= ((*(name+(l-1)) << 24) | (*(name+1) << 16) | \ (*(name+(l-2)) << 8)); \ key = key % MAX_HASHSIZE; \ } extern int debug; /* Prints out debug information if set. */ extern char hostname[MAXHOSTNAMELEN]; /* * These variables will be used to store all the * alias names for the host, as well as the -a * command line hostnames. */ extern char **host_name; /* store -a opts */ extern int host_name_count; extern int addrix; /* # of -a entries */ /* * The following 2 variables are meaningful * only under a HA configuration. */ extern char **path_name; /* store -p opts */ extern int pathix; /* # of -p entries */ /* Function prototypes used in program */ extern int create_file(char *name); extern void delete_file(char *name); extern void record_name(char *name, int op); extern void sm_crash(void); extern void statd_init(void); extern void merge_hosts(void); extern void merge_ips(void); extern CLIENT *create_client(char *, int, int, char *, struct timeval *); extern char *xmalloc(unsigned); /* * RPC service functions, slightly different here than the * generated ones in sm_inter.h */ extern void nsmaddrproc1_reg(void *, void *); extern void sm_stat_svc(void *, void *); extern void sm_mon_svc(void *, void *); extern void sm_unmon_svc(void *, void *); extern void sm_unmon_all_svc(void *, void *); extern void sm_simu_crash_svc(void *, void *); extern void sm_notify_svc(void *, void *); extern void sm_inithash(void); extern void copydir_from_to(char *from_dir, char *to_dir); extern int str_cmp_unqual_hostname(char *, char *); extern void record_addr(char *name, sa_family_t family, struct netobj *ah); extern int is_symlink(char *file); extern int create_symlink(char *todir, char *rname, char *lname); extern int str_cmp_address_specifier(char *specifier1, char *specifier2); #ifdef __cplusplus } #endif #endif /* _SM_STATD_H */ /* * 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) 1989, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012, 2016 by Delphix. All rights reserved. * Copyright 2016 Nexenta Systems, Inc. All rights reserved. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California * All Rights Reserved * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "smfcfg.h" #include "sm_statd.h" #define home0 "/var/statmon" #define current0 "/var/statmon/sm" #define backup0 "/var/statmon/sm.bak" #define state0 "/var/statmon/state" #define home1 "statmon" #define current1 "statmon/sm/" #define backup1 "statmon/sm.bak/" #define state1 "statmon/state" extern int daemonize_init(void); extern void daemonize_fini(int fd); /* * User and group IDs to run as. These are hardwired, rather than looked * up at runtime, because they are very unlikely to change and because they * provide some protection against bogus changes to the passwd and group * files. */ uid_t daemon_uid = DAEMON_UID; gid_t daemon_gid = DAEMON_GID; char STATE[MAXPATHLEN], CURRENT[MAXPATHLEN], BACKUP[MAXPATHLEN]; static char statd_home[MAXPATHLEN]; int debug; int regfiles_only = 0; /* 1 => use symlinks in statmon, 0 => don't */ int statd_port = 0; char hostname[MAXHOSTNAMELEN]; /* * These variables will be used to store all the * alias names for the host, as well as the -a * command line hostnames. */ int host_name_count; char **host_name; /* store -a opts */ int addrix; /* # of -a entries */ /* * The following 2 variables are meaningful * only under a HA configuration. * The path_name array is dynamically allocated in main() during * command line argument processing for the -p options. */ char **path_name = NULL; /* store -p opts */ int pathix = 0; /* # of -p entries */ /* Global variables. Refer to sm_statd.h for description */ mutex_t crash_lock; int die; int in_crash; mutex_t sm_trylock; rwlock_t thr_rwlock; cond_t retrywait; mutex_t name_addrlock; mutex_t merges_lock; cond_t merges_cond; boolean_t in_merges; /* forward references */ static void set_statmon_owner(void); static void copy_client_names(void); static void one_statmon_owner(const char *); static int nftw_owner(const char *, const struct stat *, int, struct FTW *); /* * statd protocol * commands: * SM_STAT * returns stat_fail to caller * SM_MON * adds an entry to the monitor_q and the record_q. * This message is sent by the server lockd to the server * statd, to indicate that a new client is to be monitored. * It is also sent by the server lockd to the client statd * to indicate that a new server is to be monitored. * SM_UNMON * removes an entry from the monitor_q and the record_q * SM_UNMON_ALL * removes all entries from a particular host from the * monitor_q and the record_q. Our statd has this * disabled. * SM_SIMU_CRASH * simulate a crash. Removes everything from the * record_q and the recovery_q, then calls statd_init() * to restart things. This message is sent by the server * lockd to the server statd to have all clients notified * that they should reclaim locks. * SM_NOTIFY * Sent by statd on server to statd on client during * crash recovery. The client statd passes the info * to its lockd so it can attempt to reclaim the locks * held on the server. * * There are three main hash tables used to keep track of things. * mon_table * table that keeps track hosts statd must watch. If one of * these hosts crashes, then any locks held by that host must * be released. * record_table * used to keep track of all the hostname files stored in * the directory /var/statmon/sm. These are client hosts who * are holding or have held a lock at some point. Needed * to determine if a file needs to be created for host in * /var/statmon/sm. * recov_q * used to keep track hostnames during a recovery * * The entries are hashed based upon the name. * * There is a directory /var/statmon/sm which holds a file named * for each host that is holding (or has held) a lock. This is * used during initialization on startup, or after a simulated * crash. */ static void sm_prog_1(struct svc_req *rqstp, SVCXPRT *transp) { union { struct sm_name sm_stat_1_arg; struct mon sm_mon_1_arg; struct mon_id sm_unmon_1_arg; struct my_id sm_unmon_all_1_arg; struct stat_chge ntf_arg; struct reg1args reg1_arg; } argument; union { sm_stat_res stat_resp; sm_stat mon_resp; struct reg1res reg1_resp; } result; bool_t (*xdr_argument)(), (*xdr_result)(); void (*local)(void *, void *); /* * Dispatch according to which protocol is being used: * NSM_ADDR_PROGRAM is the private lockd address * registration protocol. * SM_PROG is the normal statd (NSM) protocol. */ if (rqstp->rq_prog == NSM_ADDR_PROGRAM) { switch (rqstp->rq_proc) { case NULLPROC: svc_sendreply(transp, xdr_void, (caddr_t)NULL); return; case NSMADDRPROC1_REG: xdr_argument = xdr_reg1args; xdr_result = xdr_reg1res; local = nsmaddrproc1_reg; break; case NSMADDRPROC1_UNREG: /* Not impl. */ default: svcerr_noproc(transp); return; } } else { /* Must be SM_PROG */ switch (rqstp->rq_proc) { case NULLPROC: svc_sendreply(transp, xdr_void, (caddr_t)NULL); return; case SM_STAT: xdr_argument = xdr_sm_name; xdr_result = xdr_sm_stat_res; local = sm_stat_svc; break; case SM_MON: xdr_argument = xdr_mon; xdr_result = xdr_sm_stat_res; local = sm_mon_svc; break; case SM_UNMON: xdr_argument = xdr_mon_id; xdr_result = xdr_sm_stat; local = sm_unmon_svc; break; case SM_UNMON_ALL: xdr_argument = xdr_my_id; xdr_result = xdr_sm_stat; local = sm_unmon_all_svc; break; case SM_SIMU_CRASH: xdr_argument = xdr_void; xdr_result = xdr_void; local = sm_simu_crash_svc; break; case SM_NOTIFY: xdr_argument = xdr_stat_chge; xdr_result = xdr_void; local = sm_notify_svc; break; default: svcerr_noproc(transp); return; } } (void) memset(&argument, 0, sizeof (argument)); if (!svc_getargs(transp, xdr_argument, (caddr_t)&argument)) { svcerr_decode(transp); return; } (void) memset(&result, 0, sizeof (result)); (*local)(&argument, &result); if (!svc_sendreply(transp, xdr_result, (caddr_t)&result)) { svcerr_systemerr(transp); } if (!svc_freeargs(transp, xdr_argument, (caddr_t)&argument)) { syslog(LOG_ERR, "statd: unable to free arguments\n"); } } /* * Remove all files under directory path_dir. */ static int remove_dir(char *path_dir) { DIR *dp; struct dirent *dirp; char tmp_path[MAXPATHLEN]; if ((dp = opendir(path_dir)) == NULL) { if (debug) syslog(LOG_ERR, "warning: open directory %s failed: %m\n", path_dir); return (1); } while ((dirp = readdir(dp)) != NULL) { if (strcmp(dirp->d_name, ".") != 0 && strcmp(dirp->d_name, "..") != 0) { if (strlen(path_dir) + strlen(dirp->d_name) +2 > MAXPATHLEN) { syslog(LOG_ERR, "statd: remove dir %s/%s " "failed. Pathname too long.\n", path_dir, dirp->d_name); continue; } (void) strcpy(tmp_path, path_dir); (void) strcat(tmp_path, "/"); (void) strcat(tmp_path, dirp->d_name); delete_file(tmp_path); } } (void) closedir(dp); return (0); } /* * Copy all files from directory `from_dir' to directory `to_dir'. * Symlinks, if any, are preserved. */ void copydir_from_to(char *from_dir, char *to_dir) { int n; DIR *dp; struct dirent *dirp; char rname[MAXNAMELEN + 1]; char path[MAXPATHLEN+MAXNAMELEN+2]; if ((dp = opendir(from_dir)) == NULL) { if (debug) syslog(LOG_ERR, "warning: open directory %s failed: %m\n", from_dir); return; } while ((dirp = readdir(dp)) != NULL) { if (strcmp(dirp->d_name, ".") == 0 || strcmp(dirp->d_name, "..") == 0) { continue; } (void) strcpy(path, from_dir); (void) strcat(path, "/"); (void) strcat(path, dirp->d_name); if (is_symlink(path)) { /* * Follow the link to get the referenced file name * and make a new link for that file in to_dir. */ n = readlink(path, rname, MAXNAMELEN); if (n <= 0) { if (debug >= 2) { (void) printf("copydir_from_to: can't " "read link %s\n", path); } continue; } rname[n] = '\0'; (void) create_symlink(to_dir, rname, dirp->d_name); } else { /* * Simply copy regular files to to_dir. */ (void) strcpy(path, to_dir); (void) strcat(path, "/"); (void) strcat(path, dirp->d_name); (void) create_file(path); } } (void) closedir(dp); } static int init_hostname(void) { struct lifnum lifn; int sock; if ((sock = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) { syslog(LOG_ERR, "statd:init_hostname, socket: %m"); return (-1); } lifn.lifn_family = AF_UNSPEC; lifn.lifn_flags = 0; if (ioctl(sock, SIOCGLIFNUM, (char *)&lifn) < 0) { syslog(LOG_ERR, "statd:init_hostname, get number of interfaces, error: %m"); close(sock); return (-1); } host_name_count = lifn.lifn_count; host_name = malloc(host_name_count * sizeof (char *)); if (host_name == NULL) { perror("statd -a can't get ip configuration\n"); close(sock); return (-1); } close(sock); return (0); } static void thr_statd_merges(void) { /* * Get other aliases from each interface. */ merge_hosts(); /* * Get all of the configured IP addresses. */ merge_ips(); /* * Notify the waiters. */ (void) mutex_lock(&merges_lock); in_merges = B_FALSE; (void) cond_broadcast(&merges_cond); (void) mutex_unlock(&merges_lock); } /* * This function is called for each configured network type to * bind and register our RPC service programs. * * On TCP or UDP, we may want to bind SM_PROG on a specific port * (when statd_port is specified) in which case we'll use the * variant of svc_tp_create() that lets us pass a bind address. */ static void sm_svc_tp_create(struct netconfig *nconf) { char port_str[8]; struct nd_hostserv hs; struct nd_addrlist *al = NULL; SVCXPRT *xprt = NULL; /* * If statd_port is set and this is an inet transport, * bind this service on the specified port. The TLI way * to create such a bind address is netdir_getbyname() * with the special "host" HOST_SELF_BIND. This builds * an all-zeros IP address with the specified port. */ if (statd_port != 0 && (strcmp(nconf->nc_protofmly, NC_INET) == 0 || strcmp(nconf->nc_protofmly, NC_INET6) == 0)) { int err; snprintf(port_str, sizeof (port_str), "%u", (unsigned short)statd_port); hs.h_host = HOST_SELF_BIND; hs.h_serv = port_str; err = netdir_getbyname((struct netconfig *)nconf, &hs, &al); if (err == 0 && al != NULL) { xprt = svc_tp_create_addr(sm_prog_1, SM_PROG, SM_VERS, nconf, al->n_addrs); netdir_free(al, ND_ADDRLIST); } if (xprt == NULL) { syslog(LOG_ERR, "statd: unable to create " "(SM_PROG, SM_VERS) on transport %s (port %d)", nconf->nc_netid, statd_port); } /* fall-back to default bind */ } if (xprt == NULL) { /* * Had statd_port=0, or non-inet transport, * or the bind to a specific port failed. * Do a default bind. */ xprt = svc_tp_create(sm_prog_1, SM_PROG, SM_VERS, nconf); } if (xprt == NULL) { syslog(LOG_ERR, "statd: unable to create " "(SM_PROG, SM_VERS) for transport %s", nconf->nc_netid); return; } /* * Also register the NSM_ADDR program on this * transport handle (same dispatch function). */ if (!svc_reg(xprt, NSM_ADDR_PROGRAM, NSM_ADDR_V1, sm_prog_1, nconf)) { syslog(LOG_ERR, "statd: failed to register " "(NSM_ADDR_PROGRAM, NSM_ADDR_V1) for " "netconfig %s", nconf->nc_netid); } } int main(int argc, char *argv[]) { int c; int ppid; extern char *optarg; int choice = 0; struct rlimit rl; int mode; int sz; int pipe_fd = -1; int ret; int connmaxrec = RPC_MAXDATASIZE; struct netconfig *nconf; NCONF_HANDLE *nc; addrix = 0; pathix = 0; (void) gethostname(hostname, MAXHOSTNAMELEN); if (init_hostname() < 0) exit(1); ret = nfs_smf_get_iprop("statd_port", &statd_port, DEFAULT_INSTANCE, SCF_TYPE_INTEGER, STATD); if (ret != SA_OK) { syslog(LOG_ERR, "Reading of statd_port from SMF " "failed, using default value"); } while ((c = getopt(argc, argv, "Dd:a:G:p:P:rU:")) != EOF) switch (c) { case 'd': (void) sscanf(optarg, "%d", &debug); break; case 'D': choice = 1; break; case 'a': if (addrix < host_name_count) { if (strcmp(hostname, optarg) != 0) { sz = strlen(optarg); if (sz < MAXHOSTNAMELEN) { host_name[addrix] = (char *)xmalloc(sz+1); if (host_name[addrix] != NULL) { (void) sscanf(optarg, "%s", host_name[addrix]); addrix++; } } else (void) fprintf(stderr, "statd: -a name of host is too long.\n"); } } else (void) fprintf(stderr, "statd: -a exceeding maximum hostnames\n"); break; case 'U': (void) sscanf(optarg, "%d", &daemon_uid); break; case 'G': (void) sscanf(optarg, "%d", &daemon_gid); break; case 'p': if (strlen(optarg) < MAXPATHLEN) { /* If the path_name array has not yet */ /* been malloc'ed, do that. The array */ /* should be big enough to hold all of the */ /* -p options we might have. An upper */ /* bound on the number of -p options is */ /* argc/2, because each -p option consumes */ /* two arguments. Here the upper bound */ /* is supposing that all the command line */ /* arguments are -p options, which would */ /* actually never be the case. */ if (path_name == NULL) { size_t sz = (argc/2) * sizeof (char *); path_name = (char **)malloc(sz); if (path_name == NULL) { (void) fprintf(stderr, "statd: malloc failed\n"); exit(1); } (void) memset(path_name, 0, sz); } path_name[pathix] = optarg; pathix++; } else { (void) fprintf(stderr, "statd: -p pathname is too long.\n"); } break; case 'P': (void) sscanf(optarg, "%d", &statd_port); if (statd_port < 1 || statd_port > UINT16_MAX) { (void) fprintf(stderr, "statd: -P port invalid.\n"); statd_port = 0; } break; case 'r': regfiles_only = 1; break; default: (void) fprintf(stderr, "statd [-d level] [-D]\n"); return (1); } if (choice == 0) { (void) strcpy(statd_home, home0); (void) strcpy(CURRENT, current0); (void) strcpy(BACKUP, backup0); (void) strcpy(STATE, state0); } else { (void) strcpy(statd_home, home1); (void) strcpy(CURRENT, current1); (void) strcpy(BACKUP, backup1); (void) strcpy(STATE, state1); } if (debug) (void) printf("debug is on, create entry: %s, %s, %s\n", CURRENT, BACKUP, STATE); if (getrlimit(RLIMIT_NOFILE, &rl)) (void) printf("statd: getrlimit failed. \n"); /* Set maxfdlimit current soft limit */ rl.rlim_cur = rl.rlim_max; if (setrlimit(RLIMIT_NOFILE, &rl) != 0) syslog(LOG_ERR, "statd: unable to set RLIMIT_NOFILE to %d\n", rl.rlim_cur); (void) enable_extended_FILE_stdio(-1, -1); if (!debug) { pipe_fd = daemonize_init(); openlog("statd", LOG_PID, LOG_DAEMON); } (void) _create_daemon_lock(STATD, daemon_uid, daemon_gid); /* * establish our lock on the lock file and write our pid to it. * exit if some other process holds the lock, or if there's any * error in writing/locking the file. */ ppid = _enter_daemon_lock(STATD); switch (ppid) { case 0: break; case -1: syslog(LOG_ERR, "error locking for %s: %s", STATD, strerror(errno)); exit(2); default: /* daemon was already running */ exit(0); } mutex_init(&merges_lock, USYNC_THREAD, NULL); cond_init(&merges_cond, USYNC_THREAD, NULL); in_merges = B_TRUE; /* * Create thr_statd_merges() thread to populate the host_name list * asynchronously. */ if (thr_create(NULL, 0, (void *(*)(void *))thr_statd_merges, NULL, THR_DETACHED, NULL) != 0) { syslog(LOG_ERR, "statd: unable to create thread for " "thr_statd_merges()."); exit(1); } /* * Set to automatic mode such that threads are automatically * created */ mode = RPC_SVC_MT_AUTO; if (!rpc_control(RPC_SVC_MTMODE_SET, &mode)) { syslog(LOG_ERR, "statd:unable to set automatic MT mode."); exit(1); } /* * Set non-blocking mode and maximum record size for * connection oriented RPC transports. */ if (!rpc_control(RPC_SVC_CONNMAXREC_SET, &connmaxrec)) { syslog(LOG_INFO, "unable to set maximum RPC record size"); } /* * Enumerate network transports and create service listeners * as appropriate for each. */ if ((nc = setnetconfig()) == NULL) { syslog(LOG_ERR, "setnetconfig failed: %m"); return (-1); } while ((nconf = getnetconfig(nc)) != NULL) { /* * Skip things like tpi_raw, invisible... */ if ((nconf->nc_flag & NC_VISIBLE) == 0) continue; if (nconf->nc_semantics != NC_TPI_CLTS && nconf->nc_semantics != NC_TPI_COTS && nconf->nc_semantics != NC_TPI_COTS_ORD) continue; sm_svc_tp_create(nconf); } (void) endnetconfig(nc); /* * Make sure /var/statmon and any alternate (-p) statmon * directories exist and are owned by daemon. Then change our uid * to daemon. The uid change is to prevent attacks against local * daemons that trust any call from a local root process. */ set_statmon_owner(); /* * * statd now runs as a daemon rather than root and can not * dump core under / because of the permission. It is * important that current working directory of statd be * changed to writable directory /var/statmon so that it * can dump the core upon the receipt of the signal. * One still need to set allow_setid_core to non-zero in * /etc/system to get the core dump. * */ if (chdir(statd_home) < 0) { syslog(LOG_ERR, "can't chdir %s: %m", statd_home); exit(1); } copy_client_names(); rwlock_init(&thr_rwlock, USYNC_THREAD, NULL); mutex_init(&crash_lock, USYNC_THREAD, NULL); mutex_init(&name_addrlock, USYNC_THREAD, NULL); cond_init(&retrywait, USYNC_THREAD, NULL); sm_inithash(); die = 0; /* * This variable is set to ensure that an sm_crash * request will not be done at the same time * when a statd_init is being done, since sm_crash * can reset some variables that statd_init will be using. */ in_crash = 1; statd_init(); /* * statd is up and running as far as we are concerned. */ daemonize_fini(pipe_fd); if (debug) (void) printf("Starting svc_run\n"); svc_run(); syslog(LOG_ERR, "statd: svc_run returned\n"); /* NOTREACHED */ thr_exit((void *)1); return (0); } /* * Make sure the ownership of the statmon directories is correct, then * change our uid to match. If the top-level directories (/var/statmon, -p * arguments) don't exist, they are created first. The sm and sm.bak * directories are not created here, but if they already exist, they are * chowned to the correct uid, along with anything else in the * directories. */ static void set_statmon_owner(void) { int i; boolean_t can_do_mlp; /* * Recursively chown/chgrp /var/statmon and the alternate paths, * creating them if necessary. */ one_statmon_owner(statd_home); for (i = 0; i < pathix; i++) { char alt_path[MAXPATHLEN]; snprintf(alt_path, MAXPATHLEN, "%s/statmon", path_name[i]); one_statmon_owner(alt_path); } can_do_mlp = priv_ineffect(PRIV_NET_BINDMLP); if (__init_daemon_priv(PU_RESETGROUPS|PU_CLEARLIMITSET, daemon_uid, daemon_gid, can_do_mlp ? PRIV_NET_BINDMLP : NULL, NULL) == -1) { syslog(LOG_ERR, "can't run unprivileged: %m"); exit(1); } __fini_daemon_priv(PRIV_PROC_EXEC, PRIV_PROC_SESSION, PRIV_FILE_LINK_ANY, PRIV_PROC_INFO, NULL); } /* * Copy client names from the alternate statmon directories into * /var/statmon. The top-level (statmon) directories should already * exist, though the sm and sm.bak directories might not. */ static void copy_client_names(void) { int i; char buf[MAXPATHLEN+SM_MAXPATHLEN]; /* * Copy all clients from alternate paths to /var/statmon/sm * Remove the files in alternate directory when copying is done. */ for (i = 0; i < pathix; i++) { /* * If the alternate directories do not exist, create it. * If they do exist, just do the copy. */ snprintf(buf, sizeof (buf), "%s/statmon/sm", path_name[i]); if ((mkdir(buf, SM_DIRECTORY_MODE)) == -1) { if (errno != EEXIST) { syslog(LOG_ERR, "can't mkdir %s: %m\n", buf); continue; } copydir_from_to(buf, CURRENT); (void) remove_dir(buf); } (void) snprintf(buf, sizeof (buf), "%s/statmon/sm.bak", path_name[i]); if ((mkdir(buf, SM_DIRECTORY_MODE)) == -1) { if (errno != EEXIST) { syslog(LOG_ERR, "can't mkdir %s: %m\n", buf); continue; } copydir_from_to(buf, BACKUP); (void) remove_dir(buf); } } } /* * Create the given directory if it doesn't already exist. Set the user * and group to daemon for the directory and anything under it. */ static void one_statmon_owner(const char *dir) { if ((mkdir(dir, SM_DIRECTORY_MODE)) == -1) { if (errno != EEXIST) { syslog(LOG_ERR, "can't mkdir %s: %m", dir); return; } } if (debug) printf("Setting owner for %s\n", dir); if (nftw(dir, nftw_owner, MAX_FDS, FTW_PHYS) != 0) { syslog(LOG_WARNING, "error setting owner for %s: %m", dir); } } /* * Set the user and group to daemon for the given file or directory. If * it's a directory, also makes sure that it is mode 755. * Generates a syslog message but does not return an error if there were * problems. */ /*ARGSUSED3*/ static int nftw_owner(const char *path, const struct stat *statp, int info, struct FTW *ftw) { if (!(info == FTW_F || info == FTW_D)) return (0); /* * Some older systems might have mode 777 directories. Fix that. */ if (info == FTW_D && (statp->st_mode & (S_IWGRP | S_IWOTH)) != 0) { mode_t newmode = (statp->st_mode & ~(S_IWGRP | S_IWOTH)) & S_IAMB; if (debug) printf("chmod %03o %s\n", newmode, path); if (chmod(path, newmode) < 0) { int error = errno; syslog(LOG_WARNING, "can't chmod %s to %03o: %m", path, newmode); if (debug) printf(" FAILED: %s\n", strerror(error)); } } /* If already owned by daemon, don't bother changing. */ if (statp->st_uid == daemon_uid && statp->st_gid == daemon_gid) return (0); if (debug) printf("lchown %s daemon:daemon\n", path); if (lchown(path, daemon_uid, daemon_gid) < 0) { int error = errno; syslog(LOG_WARNING, "can't chown %s to daemon: %m", path); if (debug) printf(" FAILED: %s\n", strerror(error)); } return (0); } # # 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 2006 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright 2012 Nexenta Systems, Inc. All rights reserved. # # MANIFEST= server.xml client.xml rquota.xml mapid.xml nlockmgr.xml \ status.xml cbd.xml nfslogd.xml SVCMETHOD= nfs-server nfs-client nlockmgr include $(SRC)/cmd/Makefile.cmd ROOTMANIFESTDIR= $(ROOTSVCNETWORKNFS) all: install: $(ROOTMANIFEST) $(ROOTSVCMETHOD) clean: $(RM) $(OBJS) check: $(CHKMANIFEST) include $(SRC)/cmd/Makefile.targ #!/sbin/sh # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (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 2004 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # Copyright 2015 Nexenta Systems, Inc. All rights reserved. # # # Start/stop client NFS service # . /lib/svc/share/smf_include.sh stop_nfsclnt() { /sbin/umountall -F nfs } case "$1" in 'start') /sbin/mountall -F nfs /sbin/swapadd ;; 'stop') stop_nfsclnt ;; *) echo "Usage: $0 { start | stop }" exit 1 ;; esac exit $SMF_EXIT_OK #!/sbin/sh # # 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) 2009, 2010, Oracle and/or its affiliates. All rights reserved. # Copyright 2016 Hans Rosenfeld # Copyright 2018 Nexenta Systems, Inc. All rights reserved. # # Start/stop processes required for server NFS . /lib/svc/share/smf_include.sh . /lib/svc/share/ipf_include.sh zone=`smf_zonename` # # Handling a corner case here. If we were in offline state due to an # unsatisfied dependency, the ipf_method process wouldn't have generated # the ipfilter configuration. When we transition to online because the # dependency is satisfied, the start method will have to generate the # ipfilter configuration. To avoid all possible deadlock scenarios, # we restart ipfilter which will regenerate the ipfilter configuration # for the entire system. # # The ipf_method process signals that it didn't generate ipf rules by # removing the service's ipf file. Thus we only restart network/ipfilter # when the file is missing. # configure_ipfilter() { ipfile=`fmri_to_file $SMF_FMRI $IPF_SUFFIX` ip6file=`fmri_to_file $SMF_FMRI $IPF6_SUFFIX` [ -f "$ipfile" -a -f "$ip6file" ] && return 0 # # Nothing to do if: # - ipfilter isn't online # - global policy is 'custom' # - service's policy is 'use_global' # service_check_state $IPF_FMRI $SMF_ONLINE || return 0 [ "`get_global_def_policy`" = "custom" ] && return 0 [ "`get_policy $SMF_FMRI`" = "use_global" ] && return 0 svcadm restart $IPF_FMRI } case "$1" in 'start') # Share all file systems enabled for sharing. sharemgr understands # regular shares and ZFS shares and will handle both. Technically, # the shares would have been started long before getting here since # nfsd has a dependency on them. # restart stopped shares from the repository /usr/sbin/sharemgr start -P nfs -a # Options for nfsd are now set in SMF /usr/lib/nfs/mountd rc=$? if [ $rc != 0 ]; then /sbin/svcadm mark -t maintenance svc:/network/nfs/server echo "$0: mountd failed with $rc" sleep 5 & exit $SMF_EXIT_ERR_FATAL fi /usr/lib/nfs/nfsd rc=$? if [ $rc != 0 ]; then /sbin/svcadm mark -t maintenance svc:/network/nfs/server echo "$0: nfsd failed with $rc" sleep 5 & exit $SMF_EXIT_ERR_FATAL fi configure_ipfilter ;; 'refresh') /usr/sbin/sharemgr start -P nfs -a ;; 'stop') /usr/bin/pkill -x -u 0,1 -z $zone '(nfsd|mountd)' # Unshare all shared file systems using NFS /usr/sbin/sharemgr stop -P nfs -a # Kill any processes left in service contract smf_kill_contract $2 TERM 1 [ $? -ne 0 ] && exit 1 ;; 'ipfilter') # # NFS related services are RPC. nfs/server has nfsd which has # well-defined port number but mountd is an RPC daemon. # # Essentially, we generate rules for the following "services" # - nfs/server which has nfsd and mountd # - nfs/rquota # # The following services are enabled for both nfs client and # server, if nfs/client is enabled we'll treat them as client # services and simply allow incoming traffic. # - nfs/status # - nfs/nlockmgr # - nfs/cbd # NFS_FMRI="svc:/network/nfs/server:default" NFSCLI_FMRI="svc:/network/nfs/client:default" RQUOTA_FMRI="svc:/network/nfs/rquota:default" FMRI=$2 file=`fmri_to_file $FMRI $IPF_SUFFIX` file6=`fmri_to_file $FMRI $IPF6_SUFFIX` echo "# $FMRI" >$file echo "# $FMRI" >$file6 policy=`get_policy $NFS_FMRI` # # nfs/server configuration is processed in the start method. # if [ "$FMRI" = "$NFS_FMRI" ]; then service_check_state $FMRI $SMF_ONLINE if [ $? -ne 0 ]; then rm $file exit $SMF_EXIT_OK fi nfs_name=`svcprop -p $FW_CONTEXT_PG/name $FMRI 2>/dev/null` tport=`$SERVINFO -p -t -s $nfs_name 2>/dev/null` if [ -n "$tport" ]; then generate_rules $FMRI $policy "tcp" $tport $file fi tport6=`$SERVINFO -p -t6 -s $nfs_name 2>/dev/null` if [ -n "$tport6" ]; then generate_rules $FMRI $policy "tcp" $tport6 $file6 _6 fi uport=`$SERVINFO -p -u -s $nfs_name 2>/dev/null` if [ -n "$uport" ]; then generate_rules $FMRI $policy "udp" $uport $file fi uport6=`$SERVINFO -p -u6 -s $nfs_name 2>/dev/null` if [ -n "$uport6" ]; then generate_rules $FMRI $policy "udp" $uport6 $file6 _6 fi # mountd IPv6 ports are also reachable through IPv4, so include # them when generating IPv4 rules. tports=`$SERVINFO -R -p -t -s "mountd" 2>/dev/null` tports6=`$SERVINFO -R -p -t6 -s "mountd" 2>/dev/null` if [ -n "$tports" -o -n "$tports6" ]; then tports=`unique_ports $tports $tports6` for tport in $tports; do generate_rules $FMRI $policy "tcp" \ $tport $file done fi if [ -n "$tports6" ]; then for tport6 in $tports6; do generate_rules $FMRI $policy "tcp" \ $tport6 $file6 _6 done fi uports=`$SERVINFO -R -p -u -s "mountd" 2>/dev/null` uports6=`$SERVINFO -R -p -u6 -s "mountd" 2>/dev/null` if [ -n "$uports" -o -n "$uports6" ]; then uports=`unique_ports $uports $uports6` for uport in $uports; do generate_rules $FMRI $policy "udp" \ $uport $file done fi if [ -n "$uports6" ]; then for uport6 in $uports6; do generate_rules $FMRI $policy "udp" \ $uport6 $file6 _6 done fi elif [ "$FMRI" = "$RQUOTA_FMRI" ]; then iana_name=`svcprop -p inetd/name $FMRI` # rquota IPv6 ports are also reachable through IPv4, so include # them when generating IPv4 rules. tports=`$SERVINFO -R -p -t -s $iana_name 2>/dev/null` tports6=`$SERVINFO -R -p -t6 -s $iana_name 2>/dev/null` if [ -n "$tports" -o -n "$tports6" ]; then tports=`unique_ports $tports $tports6` for tport in $tports; do generate_rules $NFS_FMRI $policy "tcp" \ $tport $file done fi if [ -n "$tports6" ]; then for tport6 in $tports6; do generate_rules $NFS_FMRI $policy "tcp" \ $tport6 $file6 _6 done fi uports=`$SERVINFO -R -p -u -s $iana_name 2>/dev/null` uports6=`$SERVINFO -R -p -u6 -s $iana_name 2>/dev/null` if [ -n "$uports" -o -n "$uports6" ]; then uports=`unique_ports $uports $uports6` for uport in $uports; do generate_rules $NFS_FMRI $policy "udp" \ $uport $file done fi if [ -n "$uports6" ]; then for uport6 in $uports6; do generate_rules $NFS_FMRI $policy "udp" \ $uport6 $file6 _6 done fi else # # Handle the client services here # if service_check_state $NFSCLI_FMRI $SMF_ONLINE; then policy=none ip=any fi restarter=`svcprop -p general/restarter $FMRI 2>/dev/null` if [ "$restarter" = "$INETDFMRI" ]; then iana_name=`svcprop -p inetd/name $FMRI` isrpc=`svcprop -p inetd/isrpc $FMRI` else iana_name=`svcprop -p $FW_CONTEXT_PG/name $FMRI` isrpc=`svcprop -p $FW_CONTEXT_PG/isrpc $FMRI` fi if [ "$isrpc" = "true" ]; then tports=`$SERVINFO -R -p -t -s $iana_name 2>/dev/null` tports6=`$SERVINFO -R -p -t6 -s $iana_name 2>/dev/null` uports=`$SERVINFO -R -p -u -s $iana_name 2>/dev/null` uports6=`$SERVINFO -R -p -u6 -s $iana_name 2>/dev/null` else tports=`$SERVINFO -p -t -s $iana_name 2>/dev/null` tports6=`$SERVINFO -p -t6 -s $iana_name 2>/dev/null` uports=`$SERVINFO -p -u -s $iana_name 2>/dev/null` uports6=`$SERVINFO -p -u6 -s $iana_name 2>/dev/null` fi # IPv6 ports are also reachable through IPv4, so include # them when generating IPv4 rules. if [ -n "$tports" -o -n "$tports6" ]; then tports=`unique_ports $tports $tports6` for tport in $tports; do generate_rules $FMRI $policy "tcp" $tport $file done fi if [ -n "$tports6" ]; then for tport6 in $tports6; do generate_rules $FMRI $policy "tcp" $tport6 $file6 _6 done fi if [ -n "$uports" -o -n "$uports6" ]; then uports=`unique_ports $uports $uports6` for uport in $uports; do generate_rules $FMRI $policy "udp" $uport $file done fi if [ -n "$uports6" ]; then for uport6 in $uports6; do generate_rules $FMRI $policy "udp" $uport6 $file6 _6 done fi fi ;; *) echo "Usage: $0 { start | stop | refresh }" exit 1 ;; esac exit $SMF_EXIT_OK #!/sbin/sh # # 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 2006 Sun Microsystems, Inc. All rights reserved. # Copyright (c) 2015 by Delphix. All rights reserved. # Use is subject to license terms. # # # Start the lockd service; we are serving NFS and we need to verify # that rpcbind is accepting traffic from the network. # BIND_FMRI=svc:/network/rpc/bind do_change=false if set -- `svcprop -t -p config/local_only $BIND_FMRI`; then if [ "$2" != boolean ]; then echo "$0: config/local_only property for $BIND_FMRI has wrong "\ "type" 1>&2 elif [ "$#" -ne 3 ]; then echo "$0: config/local_only property for $BIND_FMRI has wrong "\ "number of values" 1>&2 elif [ "$3" = true ]; then do_change=true fi else # If the property is not found, we just set it. do_change=true fi if $do_change then # These will generate errors in the log. svccfg -s $BIND_FMRI setprop config/local_only = boolean: false if [ $? != 0 ]; then echo "$0: WARNING setprop failed" 1>&2 fi svcadm refresh $BIND_FMRI if [ $? != 0 ]; then echo "$0: WARNING svcadm refresh failed" 1>&2 fi fi # # We have to wait for statd to finish starting up before lockd can # start running. If statd hangs after service startup (so SMF thinks # it's done) but before it registers an rpc address, we can end up # failing in the kernel when we attempt to contact it. # until /usr/bin/rpcinfo -T tcp 127.0.0.1 status >/dev/null 2>&1 do sleep 1 done exec /usr/lib/nfs/lockd # # 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 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright 2022 Tintri by DDN, Inc. All rights reserved. # include $(SRC)/Makefile.master SUBDIRS = rpcsec_gss_conn test_svc_tp_create all: TARGET= all install: TARGET= install clean: TARGET= clean clobber: TARGET= clobber catalog: TARGET= catalog .KEEP_STATE: .PARALLEL: $(SUBDIRS) all install clean clobber: $(SUBDIRS) catalog: $(SUBDIRS) $(RM) $(POFILE) cat $(POFILES) > $(POFILE) $(SUBDIRS): FRC @cd $@; pwd; $(MAKE) $(TARGET) FRC: # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (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 2004 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. # Copyright 2022 Tintri by DDN, Inc. All rights reserved. # FSTYPE= nfs include $(SRC)/cmd/fs.d/Makefile.fstype CFLAGS += $(CCVERBOSE) SRCS= $(LIBPROG:%=%.c) # message catalog catalog: $(LIBPROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) clean: $(RM) $(OBJS) # # This file and its contents are supplied under the terms of the # Common Development and Distribution License ("CDDL"), version 1.0. # You may only use this file in accordance with the terms of version # 1.0 of the CDDL. # # A full copy of the text of the CDDL should have accompanied this # source. A copy of the CDDL is also available via the Internet at # http://www.illumos.org/license/CDDL. # # # Copyright 2022 Tintri by DDN, Inc. All rights reserved. # LIBPROG = rpcsec_gss_conn SNOOPDIR = $(SRC)/cmd/cmd-inet/usr.sbin/snoop SNOOPOBJS = nfs4_xdr.o OBJS = $(LIBPROG:%=%.o) $(SNOOPOBJS) %.o: $(SNOOPDIR)/%.c $(COMPILE.c) -o $@ $< -I$(SNOOPDIR) $(POST_PROCESS_O) include ../Makefile.com CERRWARN += -Wno-switch LDLIBS += -lnsl -lgss /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Copyright 2021 Tintri by DDN, Inc. All rights reserved. */ /* * Test connecting to an NFSv4 server with Kerberos auth. */ #include #include #include #include #include #include #include "rpcsvc/nfs4_prot.h" int nfs4_skip_bytes; int main(int argc, char *argv[]) { CLIENT *client; AUTH *auth; COMPOUND4args args; COMPOUND4res res; enum clnt_stat status; struct timeval timeout; nfs_argop4 arg[1] = {0}; char *tag = "RPCSEC_GSS test"; char *host; char *ip = NULL; char *slp = NULL; char svc_name[256]; ulong_t sleep_time = 0; rpc_gss_options_ret_t opt_ret = {0}; if (argc < 2) { fprintf(stderr, "usage: %s [-I IP] hostname \n", argv[0]); return (-1); } if (strcmp(argv[1], "-I") == 0) { if (argc < 4) { fprintf(stderr, "-I needs an arg\n"); return (-3); } ip = argv[2]; host = argv[3]; if (argc > 4) slp = argv[4]; } else { host = ip = argv[1]; if (argc > 2) slp = argv[2]; } if (slp != NULL) { errno = 0; sleep_time = strtoul(argv[2], NULL, 0); if (errno != 0) { perror("failed to convert seconds string"); return (-2); } } timeout.tv_sec = 30; timeout.tv_usec = 0; client = clnt_create(ip, NFS4_PROGRAM, NFS_V4, "tcp"); if (client == NULL) { clnt_pcreateerror("test"); fprintf(stderr, "clnt_create failed\n"); return (1); } (void) snprintf(svc_name, sizeof (svc_name), "nfs@%s", host); if ((auth = rpc_gss_seccreate(client, svc_name, "kerberos_v5", rpc_gss_svc_none, "default", NULL, &opt_ret)) == NULL) { uint32_t disp_major, disp_minor, msg_ctx = 0; gss_buffer_desc errstr = {0}; rpc_gss_error_t rpcerr = {0}; fprintf(stderr, "creating GSS ctx failed\n"); rpc_gss_get_error(&rpcerr); if (rpcerr.rpc_gss_error != 0) { fprintf(stderr, "failed with errno %d\n", rpcerr.system_error); } if (!GSS_ERROR(opt_ret.major_status)) { fprintf(stderr, "no GSS error info\n"); return (2); } do { disp_major = gss_display_status(&disp_minor, opt_ret.major_status, GSS_C_GSS_CODE, GSS_C_NULL_OID, &msg_ctx, &errstr); if (!GSS_ERROR(disp_major) && errstr.length != 0) { fprintf(stderr, "major: %s\n", errstr.value); (void) gss_release_buffer(&disp_minor, &errstr); } else { fprintf(stderr, "gss_display_status() failed with " "0x%x:0x%x", disp_major, disp_minor); } } while (!GSS_ERROR(disp_major) && msg_ctx != 0); if (opt_ret.minor_status == 0) return (2); do { disp_major = gss_display_status(&disp_minor, opt_ret.minor_status, GSS_C_MECH_CODE, GSS_C_NULL_OID, &msg_ctx, &errstr); if (!GSS_ERROR(disp_major) && errstr.length != 0) { fprintf(stderr, "minor: %s\n", errstr.value); (void) gss_release_buffer(&disp_minor, &errstr); } else { fprintf(stderr, "gss_display_status() failed with " "0x%x:0x%x", disp_major, disp_minor); } } while (!GSS_ERROR(disp_major) && msg_ctx != 0); return (2); } client->cl_auth = auth; args.minorversion = 0; args.tag.utf8string_len = strlen(tag); args.tag.utf8string_val = tag; args.argarray.argarray_len = sizeof (arg) / sizeof (nfs_argop4); args.argarray.argarray_val = arg; arg[0].argop = OP_SETCLIENTID; /* leaving arg[0].nfs_argop4_u.opsetclientid as-is */ bzero(&res, sizeof (res)); status = clnt_call(client, NFSPROC4_COMPOUND, xdr_COMPOUND4args, (caddr_t)&args, xdr_COMPOUND4res, (caddr_t)&res, timeout); if (status != RPC_SUCCESS) { clnt_perror(client, "test"); fprintf(stderr, "clnt_call failed\n"); return (2); } if (sleep_time != 0) (void) sleep(sleep_time); fprintf(stderr, "success!\n"); return (0); } # # This file and its contents are supplied under the terms of the # Common Development and Distribution License ("CDDL"), version 1.0. # You may only use this file in accordance with the terms of version # 1.0 of the CDDL. # # A full copy of the text of the CDDL should have accompanied this # source. A copy of the CDDL is also available via the Internet at # http://www.illumos.org/license/CDDL. # # # Copyright 2022 Tintri by DDN, Inc. All rights reserved. # LIBPROG= test_svc_tp_create OBJS= $(LIBPROG).o include ../Makefile.com # not linted SMATCH=off LDLIBS += -lnsl -lsocket /* * 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) 1989, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012, 2016 by Delphix. All rights reserved. * Copyright 2017 Nexenta Systems, Inc. All rights reserved. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Portions of this source code were derived from Berkeley 4.3 BSD * under license from the Regents of the University of California. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define TESTPROG 987654 uint32_t test_vers_max = 2; uint32_t test_vers_min = 1; int debug; int verbose; int testd_port; static void mysvc(struct svc_req *, SVCXPRT *); static void bind2(void); /* * This function is called for each configured network type to * bind and register our RPC service programs. * * On TCP or UDP, we want to bind TESTPROG on a specific port * (when testd_port is specified) in which case we'll use the * variant of svc_tp_create() that lets us pass a bind address. */ static void test_svc_tp_create(struct netconfig *nconf) { char port_str[8]; struct nd_hostserv hs; struct nd_addrlist *al = NULL; SVCXPRT *xprt = NULL; rpcvers_t vers; vers = test_vers_max; /* * If testd_port is set and this is an inet transport, * bind this service on the specified port. */ if (testd_port != 0 && (strcmp(nconf->nc_protofmly, NC_INET) == 0 || strcmp(nconf->nc_protofmly, NC_INET6) == 0)) { int err; snprintf(port_str, sizeof (port_str), "%u", (unsigned short)testd_port); hs.h_host = HOST_SELF_BIND; hs.h_serv = port_str; err = netdir_getbyname((struct netconfig *)nconf, &hs, &al); if (err == 0 && al != NULL) { xprt = svc_tp_create_addr(mysvc, TESTPROG, vers, nconf, al->n_addrs); netdir_free(al, ND_ADDRLIST); } if (xprt == NULL) { printf("testd: unable to create " "(TESTD,%d) on transport %s (port %d)\n", (int)vers, nconf->nc_netid, testd_port); } /* fall-back to default bind */ } if (xprt == NULL) { /* * Had testd_port=0, or non-inet transport, * or the bind to a specific port failed. * Do a default bind. */ xprt = svc_tp_create(mysvc, TESTPROG, vers, nconf); } if (xprt == NULL) { printf("testd: unable to create " "(TESTD,%d) on transport %s\n", (int)vers, nconf->nc_netid); return; } /* * Register additional versions on this transport. */ while (--vers >= test_vers_min) { if (!svc_reg(xprt, TESTPROG, vers, mysvc, nconf)) { printf("testd: " "failed to register vers %d on %s\n", (int)vers, nconf->nc_netid); } } } static void test_svc_unreg(void) { rpcvers_t vers; for (vers = test_vers_min; vers <= test_vers_max; vers++) svc_unreg(TESTPROG, vers); } int main(int argc, char *argv[]) { int c; bool_t exclbind = TRUE; int tmp; struct netconfig *nconf; NCONF_HANDLE *nc; while ((c = getopt(argc, argv, "dvp:")) != EOF) { switch (c) { case 'd': debug++; break; case 'v': verbose++; break; case 'p': (void) sscanf(optarg, "%d", &tmp); if (tmp < 1 || tmp > UINT16_MAX) { (void) fprintf(stderr, "testd: -P port invalid.\n"); return (1); } testd_port = tmp; break; default: fprintf(stderr, "usage: testd [-v] [-r]\n"); exit(1); } } (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif (void) textdomain(TEXT_DOMAIN); /* * Prevent our non-priv udp and tcp ports bound w/wildcard addr * from being hijacked by a bind to a more specific addr. */ if (!rpc_control(__RPC_SVC_EXCLBIND_SET, &exclbind)) { fprintf(stderr, "warning: unable to set udp/tcp EXCLBIND\n"); } if (testd_port < 0 || testd_port > UINT16_MAX) { fprintf(stderr, "unable to use specified port\n"); exit(1); } /* * Make sure to unregister any previous versions in case the * user is reconfiguring the server in interesting ways. */ test_svc_unreg(); /* * Enumerate network transports and create service listeners * as appropriate for each. */ if ((nc = setnetconfig()) == NULL) { perror("setnetconfig failed"); return (-1); } while ((nconf = getnetconfig(nc)) != NULL) { /* * Skip things like tpi_raw, invisible... */ if ((nconf->nc_flag & NC_VISIBLE) == 0) continue; if (nconf->nc_semantics != NC_TPI_CLTS && nconf->nc_semantics != NC_TPI_COTS && nconf->nc_semantics != NC_TPI_COTS_ORD) continue; test_svc_tp_create(nconf); } (void) endnetconfig(nc); /* * XXX: Normally would call svc_run() here, but * we just want to check our IP bindings. */ if (testd_port != 0) bind2(); if (debug) { char sysbuf[100]; snprintf(sysbuf, sizeof (sysbuf), "rpcinfo -p |grep %u", TESTPROG); printf("x %s\n", sysbuf); fflush(stdout); system(sysbuf); if (testd_port) { snprintf(sysbuf, sizeof (sysbuf), "netstat -a -f inet -P udp |grep %u", testd_port); printf("x %s\n", sysbuf); fflush(stdout); system(sysbuf); snprintf(sysbuf, sizeof (sysbuf), "netstat -a -f inet -P tcp |grep %u", testd_port); printf("x %s\n", sysbuf); fflush(stdout); system(sysbuf); } } /* cleanup */ test_svc_unreg(); printf("%s complete\n", argv[0]); return (0); } /* * Server procedure switch routine */ static void mysvc(struct svc_req *rq, SVCXPRT *xprt) { switch (rq->rq_proc) { case NULLPROC: errno = 0; (void) svc_sendreply(xprt, xdr_void, (char *)0); return; default: svcerr_noproc(xprt); return; } } struct sockaddr_in addr; /* * The actual test: Try doing a 2nd bind with a specific IP. * The exclusive wildcard bind should prvent this. */ static void bind2(void) { int ret; int sock; addr.sin_family = AF_INET; addr.sin_port = htons(testd_port); addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == -1) { fprintf(stderr, "bind2 socket fail %s\n", strerror(errno)); exit(1); } ret = bind(sock, (struct sockaddr *)&addr, sizeof (addr)); if (ret == -1) { fprintf(stderr, "bind2 bind fail %s (expected) PASS\n", strerror(errno)); close(sock); return; } printf("Oh no, bind2 worked! test FAILED\n"); close(sock); } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (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 2004 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # cmd/fs.d/nfs/umount/Makefile FSTYPE= nfs LIBPROG= umount include ../../Makefile.fstype COMMON= $(FSLIB) replica.o OBJS= $(LIBPROG).o $(COMMON) SRCS= $(LIBPROG).c $(FSLIBSRC) ../lib/replica.c CPPFLAGS += -I../.. -I../lib CFLAGS += $(CCVERBOSE) LDLIBS += -lrpcsvc -lnsl -lkstat # # Message catalog # POFILE= umount.po # # message catalog # catalog: $(POFILE) $(POFILE): $(SRCS) $(RM) $@ $(COMPILE.cpp) $(SRCS) > $(POFILE).i $(XGETTEXT) $(XGETFLAGS) $(POFILE).i sed "/^domain/d" messages.po > $@ $(RM) messages.po $(POFILE).i $(LIBPROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) clean: $(RM) $(OBJS) replica.o: ../lib/replica.c $(COMPILE.c) ../lib/replica.c /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California * All Rights Reserved * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ /* * nfs umount */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "replica.h" #define RET_OK 0 #define RET_ERR 32 #ifdef __STDC__ static void pr_err(const char *fmt, ...); #else static void pr_err(char *fmt, va_dcl); #endif static void usage(); static int nfs_unmount(char *, int); static void inform_server(char *, char *, bool_t); static struct extmnttab *mnttab_find(); extern int __clnt_bindresvport(CLIENT *); static int is_v4_mount(struct extmnttab *); static char *myname; static char typename[64]; int main(int argc, char *argv[]) { extern int optind; int c; int umnt_flag = 0; (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif (void) textdomain(TEXT_DOMAIN); myname = strrchr(argv[0], '/'); myname = myname ? myname+1 : argv[0]; (void) snprintf(typename, sizeof (typename), "nfs %s", myname); argv[0] = typename; /* * Set options */ while ((c = getopt(argc, argv, "f")) != EOF) { switch (c) { case 'f': umnt_flag |= MS_FORCE; /* forced unmount is desired */ break; default: usage(); exit(RET_ERR); } } if (argc - optind != 1) { usage(); exit(RET_ERR); } if (!priv_ineffect(PRIV_SYS_MOUNT) || !priv_ineffect(PRIV_NET_PRIVADDR)) { pr_err(gettext("insufficient privileges\n")); exit(RET_ERR); } /* * On a labeled system, a MAC flag may be needed if the mount is * read-down, so attempt to assert it but ignore errors in any case. */ if (is_system_labeled()) (void) setpflags(NET_MAC_AWARE, 1); /* * exit, really */ return (nfs_unmount(argv[optind], umnt_flag)); } static void pr_err(const char *fmt, ...) { va_list ap; va_start(ap, fmt); (void) fprintf(stderr, "%s: ", typename); (void) vfprintf(stderr, fmt, ap); (void) fflush(stderr); va_end(ap); } static void usage() { (void) fprintf(stderr, gettext("Usage: nfs umount [-f] {server:path | dir}\n")); exit(RET_ERR); } static int nfs_unmount(char *pathname, int umnt_flag) { struct extmnttab *mntp; bool_t quick = FALSE; int is_v4 = FALSE; mntp = mnttab_find(pathname); if (mntp) { pathname = mntp->mnt_mountp; } if (mntp) is_v4 = is_v4_mount(mntp); /* Forced unmount will almost always be successful */ if (umount2(pathname, umnt_flag) < 0) { if (errno == EBUSY) { pr_err(gettext("%s: is busy\n"), pathname); } else { pr_err(gettext("%s: not mounted\n"), pathname); } return (RET_ERR); } /* All done if it's v4 */ if (is_v4 == TRUE) return (RET_OK); /* Inform server quickly in case of forced unmount */ if (umnt_flag & MS_FORCE) quick = TRUE; if (mntp) { inform_server(mntp->mnt_special, mntp->mnt_mntopts, quick); } return (RET_OK); } /* * Find the mnttab entry that corresponds to "name". * We're not sure what the name represents: either * a mountpoint name, or a special name (server:/path). * Return the last entry in the file that matches. */ static struct extmnttab * mnttab_find(dirname) char *dirname; { FILE *fp; struct extmnttab mnt; struct extmnttab *res = NULL; fp = fopen(MNTTAB, "r"); if (fp == NULL) { pr_err("%s: %s\n", MNTTAB, strerror(errno)); return (NULL); } while (getextmntent(fp, &mnt, sizeof (struct extmnttab)) == 0) { if (strcmp(mnt.mnt_mountp, dirname) == 0 || strcmp(mnt.mnt_special, dirname) == 0) { if (res) fsfreemnttab(res); res = fsdupmnttab(&mnt); } } (void) fclose(fp); return (res); } /* * If quick is TRUE, it will try to inform server quickly * as possible. */ static void inform_server(char *string, char *opts, bool_t quick) { struct timeval timeout; CLIENT *cl; enum clnt_stat rpc_stat; struct replica *list; int i, n; char *p = NULL; static struct timeval create_timeout = {5, 0}; static struct timeval *timep; list = parse_replica(string, &n); if (list == NULL) { if (n < 0) pr_err(gettext("%s is not hostname:path format\n"), string); else pr_err(gettext("no memory\n")); return; } /* * If mounted with -o public, then no need to contact server * because mount protocol was not used. */ if (opts != NULL) p = strstr(opts, MNTOPT_PUBLIC); if (p != NULL) { i = strlen(MNTOPT_PUBLIC); /* * Now make sure the match of "public" isn't a substring * of another option. */ if (((p == opts) || (*(p-1) == ',')) && ((p[i] == ',') || (p[i] == '\0'))) return; } for (i = 0; i < n; i++) { int vers; /* * Skip file systems mounted using WebNFS, because mount * protocol was not used. */ if (strcmp(list[i].host, "nfs") == 0 && strncmp(list[i].path, "//", 2) == 0) continue; vers = MOUNTVERS; retry: /* * Use 5 sec. timeout if file system is forced unmounted, * otherwise use default timeout to create a client handle. * This would minimize the time to force unmount a file * system reside on a server that is down. */ timep = (quick ? &create_timeout : NULL); cl = clnt_create_timed(list[i].host, MOUNTPROG, vers, "datagram_n", timep); /* * Do not print any error messages in case of forced * unmount. */ if (cl == NULL) { if (!quick) pr_err("%s:%s %s\n", list[i].host, list[i].path, clnt_spcreateerror( "server not responding")); continue; } /* * Now it is most likely that the NFS client will be able * to contact the server since rpcbind is running on * the server. There is still a small window in which * server can be unreachable. */ if (__clnt_bindresvport(cl) < 0) { if (!quick) pr_err(gettext( "couldn't bind to reserved port\n")); clnt_destroy(cl); return; } if ((cl->cl_auth = authsys_create_default()) == NULL) { if (!quick) pr_err(gettext( "couldn't create authsys structure\n")); clnt_destroy(cl); return; } timeout.tv_usec = 0; timeout.tv_sec = 5; clnt_control(cl, CLSET_RETRY_TIMEOUT, (char *)&timeout); timeout.tv_sec = 25; rpc_stat = clnt_call(cl, MOUNTPROC_UMNT, xdr_dirpath, (char *)&list[i].path, xdr_void, (char *)NULL, timeout); AUTH_DESTROY(cl->cl_auth); clnt_destroy(cl); if (rpc_stat == RPC_PROGVERSMISMATCH && vers == MOUNTVERS) { /* * The rare case of a v3-only server */ vers = MOUNTVERS3; goto retry; } if (rpc_stat != RPC_SUCCESS) pr_err("%s\n", clnt_sperror(cl, "unmount")); } free_replica(list, n); } /* * This function's behavior is taken from nfsstat. * Trying to determine what NFS version was used for the mount. */ int is_v4_mount(struct extmnttab *mntp) { kstat_ctl_t *kc = NULL; /* libkstat cookie */ kstat_t *ksp; struct mntinfo_kstat mik; if (mntp == NULL) return (FALSE); if ((kc = kstat_open()) == NULL) return (FALSE); for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) { if (ksp->ks_type != KSTAT_TYPE_RAW) continue; if (strcmp(ksp->ks_module, "nfs") != 0) continue; if (strcmp(ksp->ks_name, "mntinfo") != 0) continue; if (mntp->mnt_minor != ksp->ks_instance) continue; if (kstat_read(kc, ksp, &mik) == -1) continue; if (mik.mik_vers == 4) return (TRUE); else return (FALSE); } return (FALSE); }