# # 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); }