/*
* 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
* 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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <syslog.h>
#include <netdb.h>
#include <dirent.h>
#include <rpcsvc/sm_inter.h>
#include <rpcsvc/nsm_addr.h>
#include <errno.h>
#include <memory.h>
#include <signal.h>
#include <synch.h>
#include <thread.h>
#include <limits.h>
#include <strings.h>
#include "sm_statd.h"
int LOCAL_STATE;
int need_alloc);
static int statd_call_statd(char *name);
static void *thr_statd_init(void);
static void *sm_try(void);
static void *thr_call_statd(void *);
/*
* all entries to notify its own failure
*/
void
statd_init(void)
{
int i, tmp_state;
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.
*/
exit(1);
} else
}
if (debug >= 2)
(void) printf("empty file\n");
LOCAL_STATE = 0;
}
/*
* Scan alternate paths for largest "state" number
*/
for (i = 0; i < pathix; i++) {
if (debug)
"can't open %s: %m",
continue;
} else
}
if (debug)
"statd: %s: file empty\n", state_file);
continue;
}
if (tmp_state > LOCAL_STATE) {
if (debug)
(void) printf("Update LOCAL STATE: %d\n",
}
}
/* 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 */
exit(1);
}
exit(1);
}
for (i = 0; i < pathix; i++) {
"can't open %s: %m", state_file);
continue;
} else
}
"statd: %s: fsync failed\n", state_file);
exit(1);
}
}
if (debug)
exit(1);
}
}
exit(1);
}
}
/* get all entries in CURRENT into BACKUP */
exit(1);
}
/* rename all entries from CURRENT to BACKUP */
}
}
/* Contact hosts' statd */
THR_DETACHED, NULL)) {
"statd: unable to create thread for thr_statd_init\n");
exit(1);
}
}
/*
* Work thread which contacts hosts' statd.
*/
static void *
thr_statd_init(void)
{
int num_threads;
int num_join;
int i;
char *name;
/* Go thru backup directory and contact hosts */
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;
/*
* 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.
*/
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) {
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.
*/
"statd: unable to allocate space for name %s\n",
continue;
}
/* Create a thread to do a statd_call_statd for name */
"statd: unable to create thr_call_statd() "
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).
*/
num_threads = 0;
continue;
}
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",
}
/*
* If the number of threads exceeds the maximum, wait
* for some fraction of them to finish before
* continuing.
*/
if (num_threads > MAX_THR) {
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.
*/
"statd: unable to allocate space for name %s\n",
continue;
}
/* Create a thread to do a statd_call_statd for name */
"statd: unable to create thr_call_statd() "
continue;
}
num_threads++;
}
/*
* 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);
}
/*
* being monitored and already should be in alternate paths as part
* of insert_mon().
*/
for (i = 0; i < pathix; i++) {
buf);
else
} else
}
/*
* Reset the die and in_crash variables.
*/
die = 0;
in_crash = 0;
if (debug)
(void) printf("Creating thread for sm_try\n");
/* Continue to notify statd on hosts that were unreachable. */
NULL))
"statd: unable to create thread for sm_try().\n");
thr_exit((void *) 0);
#ifdef lint
return (0);
#endif
}
/*
* Work thread to make call to statd_call_statd.
*/
void *
{
/*
* 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;
/*
* 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
*/
if (debug) {
(void) printf(
"statd call failed, inserting %s in recov_q\n", name);
}
/*
* 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.
*/
if (is_symlink(path)) {
if (n <= 0) {
if (debug >= 2) {
(void) printf(
"thr_call_statd: can't read "
"link %s\n", path);
}
} else {
rname[n] = '\0';
MAXPATHLEN) {
} else if (debug) {
printf("thr_call_statd: path over"
"maxpathlen!\n");
}
}
}
if (debug)
} 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.
*/
}
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
{
char *name_or_addr;
int i;
int rc;
if (debug)
/*
* If it looks like an ASCII <address family>.<address> 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().
*/
} 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",
}
tottimeout.tv_usec = 0;
&tottimeout)) == NULL) {
return (-1);
}
/* Perform notification to client */
rc = 0;
if (debug) {
(void) printf("clnt_stat=%s(%d)\n",
}
if (clnt_stat != (int)RPC_SUCCESS) {
"statd: cannot talk to statd at %s, %s(%d)\n",
rc = -1;
}
/*
* Wait until the host_name is populated.
*/
(void) mutex_lock(&merges_lock);
while (in_merges)
(void) mutex_unlock(&merges_lock);
/* For HA systems and multi-homed hosts */
for (i = 0; i < addrix; i++) {
if (debug)
if (clnt_stat != (int)RPC_SUCCESS) {
"statd: cannot talk to statd at %s, %s(%d)\n",
rc = -1;
}
}
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
* lock ensures to finish this code before an sm_crash is started. Die
* variable will signal it.
*/
void *
sm_try(void)
{
int delay = 0;
if (mutex_trylock(&sm_trylock))
goto out;
while (!die) {
/*
* Wait until signalled to wakeup or time expired.
* If signalled to be awoken, then a crash has occurred
* or otherwise time expired.
*/
break;
}
/* Exit loop if queue is empty */
break;
/* remove name from BACKUP */
/* remove entry from recovery_q */
} else {
/*
* Print message only once since unreachable
* host can be contacted forever.
*/
if (delay == 0)
"statd: host %s is not "
}
}
/*
* 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;
}
out:
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 *
{
char *new;
return (NULL);
} else {
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 *
{
return (NULL);
/* Allocate name when needed which is only when adding to record_t */
if (need_alloc) {
return (NULL);
}
} else
if (debug) {
(void) printf("insert_name: inserted %s at %p\n",
}
return (new);
}
/*
* Deletes name from specified list (namepp).
*/
static void
{
else
return;
}
}
}
/*
* Finds name from specified list (namep).
*/
static name_entry *
{
return (nl);
}
}
return (NULL);
}
/*
* Creates a file.
*/
int
{
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.
*/
return (1);
}
}
if (debug >= 2)
return (1);
}
return (0);
}
/*
* Deletes the file specified by name.
*/
void
{
if (debug >= 2)
}
}
/*
* Return 1 if file is a symlink, else 0.
*/
int
{
int error;
do {
if (error == 0) {
}
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
{
int n;
if (is_symlink(from)) {
/*
* Dig out the name of the regular file the link points to.
*/
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.
*/
return (1);
}
} else {
/*
* Do what we've always done to move regular files.
*/
if (create_file(to) != 0) {
return (1);
}
}
/*
* Remove the old file if we've created the new one.
*/
return (1);
}
return (0);
}
/*
* Create a symbolic link named `lname' to regular file `rname'.
* Both files should be in directory `todir'.
*/
int
{
int error;
/*
* Form the full pathname of the link.
*/
/*
* Now make the new symlink ...
*/
if (debug >= 2) {
(void) printf("create_symlink: can't link "
}
return (1);
}
}
if (debug) {
(void) printf("link %s/%s -> %s already exists\n",
} else {
(void) printf("created link %s/%s -> %s\n",
}
}
return (0);
}
/*
* remove the name from the specified directory
* op = 0: CURRENT
* op = 1: BACKUP
*/
static void
{
int i;
char *alt_dir;
char *queue;
if (op == 0) {
} else {
}
/*
* 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++) {
}
}
}
/*
* dir1, depending on whether dir2 is NULL.
*/
static void
{
int n, error;
3 > MAXPATHLEN) {
"statd: pathname too long: %s/%s/%s\n",
else
"statd: pathname too long: %s/%s\n",
return;
}
}
/*
* 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)) {
if (n > 0) {
rname[n] = '\0';
return;
}
if (n == 1) {
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.
*/
"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.
*/
}
/*
* 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
{
int cnt = 0;
int n;
dir);
return (-1);
}
continue;
}
if (is_symlink(lpath)) {
/*
* Fetch the name of the file the symlink refers to.
*/
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.
*/
cnt++;
}
}
}
if (debug) {
}
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
* 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
{
int i;
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 ..
*/
return;
return;
}
if (debug) {
if (op == 1)
(void) printf("inserting %s at hash %d,\n",
else
}
int path_len;
(name_entry *) NULL)
/* make an entry in current directory */
if (path_len > MAXPATHLEN) {
"statd: pathname too long: %s/%s\n",
return;
}
(void) create_file(path);
if (debug) {
(void) printf("After insert_name\n");
}
/* make an entry in alternate paths */
for (i = 0; i < pathix; i++) {
if (path_len > MAXPATHLEN) {
continue;
}
(void) create_file(path);
}
return;
}
} else { /* delete */
return;
}
/* remove this entry from current directory */
remove_name(name, 0, 0);
} else
if (debug) {
(void) printf("After delete_name \n");
}
}
}
/*
* This routine adds a symlink in the form of an ASCII dotted quad
* IP address 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
{
int i;
int path_len;
char *famstr;
char *addr6;
return;
return;
} else
return;
if (debug) {
(void) printf("record_addr: addr= %x\n",
}
"record_addr: illegal IP address %x\n",
return;
}
}
/* convert address to ASCII */
"record_addr: unsupported address family %d\n",
family);
return;
}
switch (family) {
case AF_INET:
break;
case AF_INET6:
break;
default:
if (debug) {
(void) printf(
"record_addr: family2string supports unknown "
}
return;
}
if (debug) {
}
/*
* Make the symlink in CURRENT. The `name' file should have
* been created previously by record_name().
*/
/*
* Similarly for alternate paths.
*/
for (i = 0; i < pathix; i++) {
if (path_len > MAXPATHLEN) {
continue;
}
}
}
/*
* SM_CRASH - simulate a crash of statd.
*/
void
sm_crash(void)
{
int k;
for (k = 0; k < MAX_HASHSIZE; k++) {
continue;
} else {
}
}
}
/* Clean up entries in record table */
for (k = 0; k < MAX_HASHSIZE; k++) {
(name_entry *) NULL) {
continue;
} else {
}
}
}
/* Clean up entries in recovery table */
}
}
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++) {
}
}
/*
* 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 *
{
char *rc;
switch (family) {
case AF_INET:
break;
case AF_INET6:
break;
default:
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
{
unsigned int hash;
if (!debug)
return;
if (flag) {
(void) printf("*****record_q: ");
}
} else {
(void) printf("*****recovery_q: ");
}
}
(void) printf("\n");
}