/*
* UNFS3 cluster support
* (C) 2004, Pascal Schmidt
* see file LICENSE for license details
*/
#include "../config.h"
#ifdef WANT_CLUSTER
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <rpc/rpc.h>
#include <dirent.h>
#include <errno.h>
#include <libgen.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "../nfs.h"
#include "../daemon.h"
#include "../backend.h"
#include "cluster.h"
/* array of dirents prefixed with master file name */
static char **cluster_dirents = NULL;
/* number of dirents in above array */
static int cluster_count = -1;
/*
* check whether given pathname is in clustering path
*/
int want_cluster(const char *path)
{
char buf[NFS_MAXPATHLEN];
char *last, *next;
/* if path is too long, play it safe */
if (strlen(opt_cluster_path) + 1 > NFS_MAXPATHLEN)
return TRUE;
strcpy(buf, opt_cluster_path);
last = buf;
/* iterate over colon-seperated list */
do {
next = strchr(last, ':');
if (next)
*next = 0;
if (strstr(path, last) == path)
return TRUE;
if (next) {
last = next + 1;
if (strlen(last) == 0)
last = NULL;
} else {
last = NULL;
}
} while (last);
return FALSE;
}
/*
* get name of remote machine
*/
static char *get_host(struct in_addr remote)
{
static char buf[NFS_MAXPATHLEN];
struct hostent *entry;
char *dot;
entry = gethostbyaddr((char *) &remote, sizeof(struct in_addr), AF_INET);
if (entry) {
strcpy(buf, entry->h_name);
/* have the name string end at the first dot */
dot = strchr(buf, '.');
if (dot)
*dot = 0;
return buf;
}
return NULL;
}
/*
* check whether name is already host tagged name
*/
int is_host(const char *name)
{
return (int) (strstr(name, "$$HOST=") && name[strlen(name) - 1] == '$' &&
name[strlen(name) - 2] == '$');
}
/*
* check whether a hostname matches a dirent
*/
char *match_host(const char *hname, const char *entry)
{
char buf[NFS_MAXPATHLEN];
static char *part;
/* check for presence of hostname tag */
if (!is_host(entry))
return NULL;
part = strstr(entry, "$$HOST=");
/* copy hostname part of host tag */
memcpy(buf, part + 7, strlen(part) - 8);
buf[strlen(part) - 9] = 0;
/* exact match? */
if (strcmp(buf, hname) == 0)
return part;
/* wildcard pattern? */
if (buf[strlen(buf) - 1] != '*')
return NULL;
/* if wildcard, check for matching prefix */
buf[strlen(buf) - 1] = 0;
if (strstr(hname, buf) == hname)
return part;
return NULL;
}
/*
* better dirname providing internal buffer
*/
char *cluster_dirname(const char *path)
{
static char buf[NFS_MAXPATHLEN];
strcpy(buf, path);
return dirname(buf);
}
/*
* better basename providing internal buffer
*/
char *cluster_basename(const char *path)
{
static char buf[NFS_MAXPATHLEN];
strcpy(buf, path);
return basename(buf);
}
/*
* free dirent array
*/
void cluster_freedir(void)
{
/* only if it was really allocated before */
if (cluster_dirents) {
while (cluster_count--)
free(cluster_dirents[cluster_count]);
free(cluster_dirents);
cluster_dirents = NULL;
}
}
/*
* compare function for qsort'ing the scandir list
*/
int compar(const void *x, const void *y)
{
return strcmp(*(const char **) x, *(const char **) y);
}
/*
* reset euid/egid to specific values
*/
static void reset_ids(uid_t euid, gid_t egid)
{
if (backend_setegid(egid) || backend_seteuid(euid)) {
logmsg(LOG_EMERG, "euid/egid switching failed, aborting");
daemon_exit(CRISIS);
}
}
/*
* scan directory for filenames beginning with master name as prefix
*/
void cluster_scandir(const char *path)
{
char prefix[NFS_MAXPATHLEN];
DIR *scan;
struct dirent *entry;
char **new, *name;
uid_t euid;
gid_t egid;
strcpy(prefix, cluster_basename(path));
/*
* need to read directory as root, temporarily switch back
*/
euid = backend_geteuid();
egid = backend_getegid();
backend_setegid(0);
backend_seteuid(0);
scan = backend_opendir(cluster_dirname(path));
if (!scan) {
cluster_count = -1;
reset_ids(euid, egid);
return;
}
cluster_count = 0;
while ((entry = backend_readdir(scan))) {
if (strstr(entry->d_name, prefix) != entry->d_name &&
strcmp(entry->d_name, "$$CREATE=IP$$") != 0 &&
strcmp(entry->d_name, "$$CREATE=CLIENT$$") != 0 &&
strcmp(entry->d_name, "$$ALWAYS=IP$$") != 0 &&
strcmp(entry->d_name, "$$ALWAYS=CLIENT$$") != 0)
continue;
name = malloc(strlen(entry->d_name) + 1);
new = realloc(cluster_dirents, (cluster_count + 1) * sizeof(char *));
if (!new || !name) {
cluster_freedir();
cluster_count = -1;
free(new);
free(name);
backend_closedir(scan);
reset_ids(euid, egid);
return;
}
strcpy(name, entry->d_name);
cluster_dirents = new;
cluster_dirents[cluster_count] = name;
cluster_count++;
}
backend_closedir(scan);
reset_ids(euid, egid);
/* list needs to be sorted for cluster_lookup_lowlevel to work */
qsort(cluster_dirents, cluster_count, sizeof(char *), compar);
}
/*
* check whether master name + suffix matches with a string
*/
int match_suffix(const char *master, const char *suffix, const char *entry)
{
char obj[NFS_MAXPATHLEN];
sprintf(obj, "%s%s", master, suffix);
if (strcmp(entry, obj) == 0)
return CLU_SLAVE;
else
return FALSE;
}
/*
* create string version of a netmask
* buf: where to put string
* remote: full IP address of remote machine
* n: number of dots to keep
*/
void cluster_netmask(char *buf, const char *remote, int n)
{
int i;
sprintf(buf, "$$IP=%s", remote);
/* skip to desired dot position */
for (i = 0; i < n; i++)
buf = strchr(buf, '.') + 1;
*buf-- = 0;
/* append trailer of netmask string */
switch (n) {
case 3:
strcat(buf, "0_24$$");
break;
case 2:
strcat(buf, "0.0_16$$");
break;
case 1:
strcat(buf, "0.0.0_8$$");
break;
}
}
/*
* look up cluster name, defaulting to master name if no slave name found
*/
int cluster_lookup_lowlevel(char *path, struct svc_req *rqstp)
{
struct in_addr raddr;
char *remote, *hname, *master, *entry, *match;
char buf[NFS_MAXPATHLEN];
int i, res = CLU_MASTER;
cluster_freedir();
cluster_scandir(path);
if (cluster_count == -1)
return CLU_IO;
else if (cluster_count == 0)
return CLU_MASTER;
raddr = get_remote(rqstp); /* remote IP address */
master = cluster_basename(path); /* master file name */
remote = inet_ntoa(raddr); /* remote IP address string */
hname = get_host(raddr); /* remote hostname */
/*
* traversal in reverse alphanumerical order, so that
* IP is encountered before HOST, HOST before CLIENT,
* CLIENT before ALWAYS, and also subnets are encountered
* in the right order
*/
i = cluster_count;
while (i--) {
entry = cluster_dirents[i];
/* match specific IP address */
sprintf(buf, "$$IP=%s$$", remote);
if ((res = match_suffix(master, buf, entry)))
break;
/* always match IP file */
if ((res = match_suffix(master, "$$ALWAYS=IP$$", entry)))
break;
if (strcmp("$$ALWAYS=IP$$", entry) == 0) {
res = CLU_SLAVE;
break;
}
/* match all clients */
strcpy(buf, "$$CLIENT$$");
if ((res = match_suffix(master, buf, entry)))
break;
/* always match CLIENT file */
if ((res = match_suffix(master, "$$ALWAYS=CLIENT$$", entry)))
break;
if (strcmp("$$ALWAYS=CLIENT$$", entry) == 0) {
res = CLU_SLAVE;
break;
}
/* match 24 bit network address */
cluster_netmask(buf, remote, 3);
if ((res = match_suffix(master, buf, entry)))
break;
/* match 16 bit network address */
cluster_netmask(buf, remote, 2);
if ((res = match_suffix(master, buf, entry)))
break;
/* match 8 bit network address */
cluster_netmask(buf, remote, 1);
if ((res = match_suffix(master, buf, entry)))
break;
/* match hostname pattern */
if (!is_host(master)) {
match = match_host(hname, entry);
if (match) {
res = CLU_SLAVE;
strcpy(buf, match);
break;
}
}
}
/* append suffix if possible */
if (res == CLU_SLAVE) {
if (strlen(path) + strlen(buf) + 1 < NFS_MAXPATHLEN)
strcat(path, buf);
else
res = CLU_TOOLONG;
} else {
/* res will be 0 after above loop */
res = CLU_MASTER;
}
/*
* dirent array not freed here since cluster_create may need
* to look at it afterwards
*/
return res;
}
/*
* substitute slave filename if possible
*/
void cluster_lookup(char *path, struct svc_req *rqstp, nfsstat3 * nstat)
{
int res;
if (!opt_cluster)
return;
if (!path)
return;
if (*nstat != NFS3_OK)
return;
if (!want_cluster(path))
return;
res = strlen(path);
if (strstr(path, "$$$$") == path + res - 4) {
*(path + res - 4) = 0;
return;
}
res = cluster_lookup_lowlevel(path, rqstp);
if (res == CLU_TOOLONG)
*nstat = NFS3ERR_NAMETOOLONG;
else if (res == CLU_IO)
*nstat = NFS3ERR_IO;
}
/*
* substitute slave filename if possible, for create operations
*/
void cluster_create(char *path, struct svc_req *rqstp, nfsstat3 * nstat)
{
int i, res;
char buf[NFS_MAXPATHLEN];
char *master, *entry;
if (!opt_cluster)
return;
if (*nstat != NFS3_OK)
return;
if (!want_cluster(path))
return;
res = cluster_lookup_lowlevel(path, rqstp);
if (res == CLU_TOOLONG) {
*nstat = NFS3ERR_NAMETOOLONG;
return;
} else if (res == CLU_IO) {
*nstat = NFS3ERR_IO;
return;
} else if (res == CLU_SLAVE)
return;
master = cluster_basename(path);
/* look for create tag */
i = cluster_count;
while (i--) {
entry = cluster_dirents[i];
/* always create IP file */
sprintf(buf, "$$IP=%s$$", inet_ntoa(get_remote(rqstp)));
if ((res = match_suffix(master, "$$CREATE=IP$$", entry)) ||
(res = match_suffix(master, "$$ALWAYS=IP$$", entry)))
break;
if ((strcmp("$$CREATE=IP$$", entry) == 0) ||
(strcmp("$$ALWAYS=IP$$", entry) == 0)) {
res = CLU_SLAVE;
break;
}
/* always create CLIENT file */
sprintf(buf, "$$CLIENT$$");
if ((res = match_suffix(master, "$$CREATE=CLIENT$$", entry)) ||
(res = match_suffix(master, "$$ALWAYS=CLIENT$$", entry)))
break;
if ((strcmp("$$CREATE=CLIENT$$", entry) == 0) ||
(strcmp("$$ALWAYS=CLIENT$$", entry) == 0)) {
res = CLU_SLAVE;
break;
}
}
if (res != CLU_SLAVE)
return;
/* append suffix if possible */
if (strlen(path) + strlen(buf) + 1 < NFS_MAXPATHLEN)
strcat(path, buf);
else
*nstat = NFS3ERR_NAMETOOLONG;
}
#endif
syntax highlighted by Code2HTML, v. 0.9.1