blob: 5e4f10584b05611faec891a9c8875a5c6c3d6b38 [file] [log] [blame]
/*
* support/export/v4clients.c
*
* Montior clients appearing in, and disappearing from, /proc/fs/nfsd/clients
* and log relevant information.
*/
#include <unistd.h>
#include <stdlib.h>
#include <sys/inotify.h>
#include <errno.h>
#include "export.h"
#include "version.h"
/* search.h declares 'struct entry' and nfs_prot.h
* does too. Easiest fix is to trick search.h into
* calling its struct "struct Entry".
*/
#define entry Entry
#include <search.h>
#undef entry
static int clients_fd = -1;
void v4clients_init(void)
{
if (linux_version_code() < MAKE_VERSION(5, 3, 0))
return;
if (clients_fd >= 0)
return;
clients_fd = inotify_init1(IN_NONBLOCK);
if (clients_fd < 0) {
xlog_err("Unable to initialise v4clients watcher: %s\n",
strerror(errno));
return;
}
if (inotify_add_watch(clients_fd, "/proc/fs/nfsd/clients",
IN_CREATE | IN_DELETE) < 0) {
xlog_err("Unable to watch /proc/fs/nfsd/clients: %s\n",
strerror(errno));
close(clients_fd);
clients_fd = -1;
return;
}
}
void v4clients_set_fds(fd_set *fdset)
{
if (clients_fd >= 0)
FD_SET(clients_fd, fdset);
}
static void *tree_root;
static int have_unconfirmed;
struct ent {
unsigned long num;
char *clientid;
char *addr;
int vers;
int unconfirmed;
int wid;
};
static int ent_cmp(const void *av, const void *bv)
{
const struct ent *a = av;
const struct ent *b = bv;
if (a->num < b->num)
return -1;
if (a->num > b->num)
return 1;
return 0;
}
static void free_ent(struct ent *ent)
{
free(ent->clientid);
free(ent->addr);
free(ent);
}
static char *dup_line(char *line)
{
char *ret;
char *e = strchr(line, '\n');
if (!e)
e = line + strlen(line);
ret = malloc(e - line + 1);
if (ret) {
memcpy(ret, line, e - line);
ret[e-line] = 0;
}
return ret;
}
static void read_info(struct ent *key)
{
char buf[2048];
char *path;
int was_unconfirmed = key->unconfirmed;
FILE *f;
if (asprintf(&path, "/proc/fs/nfsd/clients/%lu/info", key->num) < 0)
return;
f = fopen(path, "r");
if (!f) {
free(path);
return;
}
if (key->wid < 0)
key->wid = inotify_add_watch(clients_fd, path, IN_MODIFY);
while (fgets(buf, sizeof(buf), f)) {
if (strncmp(buf, "clientid: ", 10) == 0) {
free(key->clientid);
key->clientid = dup_line(buf+10);
}
if (strncmp(buf, "address: ", 9) == 0) {
free(key->addr);
key->addr = dup_line(buf+9);
}
if (strncmp(buf, "minor version: ", 15) == 0)
key->vers = atoi(buf+15);
if (strncmp(buf, "status: ", 8) == 0 &&
strstr(buf, " unconfirmed") != NULL) {
key->unconfirmed = 1;
have_unconfirmed = 1;
}
if (strncmp(buf, "status: ", 8) == 0 &&
strstr(buf, " confirmed") != NULL)
key->unconfirmed = 0;
}
fclose(f);
free(path);
if (was_unconfirmed && !key->unconfirmed)
xlog(L_NOTICE, "v4.%d client attached: %s from %s",
key->vers, key->clientid ?: "-none-",
key->addr ?: "-none-");
if (!key->unconfirmed && key->wid >= 0) {
inotify_rm_watch(clients_fd, key->wid);
key->wid = -1;
}
}
static void add_id(int id)
{
struct ent **ent;
struct ent *key;
key = calloc(1, sizeof(*key));
if (!key) {
return;
}
key->num = id;
key->wid = -1;
ent = tsearch(key, &tree_root, ent_cmp);
if (!ent || *ent != key)
/* Already existed, or insertion failed */
free_ent(key);
else
read_info(key);
}
static void del_id(unsigned long id)
{
struct ent key = {.num = id};
struct ent **e, *ent;
e = tfind(&key, &tree_root, ent_cmp);
if (!e || !*e)
return;
ent = *e;
tdelete(ent, &tree_root, ent_cmp);
if (!ent->unconfirmed)
xlog(L_NOTICE, "v4.%d client detached: %s from %s",
ent->vers, ent->clientid, ent->addr);
if (ent->wid >= 0)
inotify_rm_watch(clients_fd, ent->wid);
free_ent(ent);
}
static void check_id(unsigned long id)
{
struct ent key = {.num = id};
struct ent **e, *ent;
e = tfind(&key, &tree_root, ent_cmp);
if (!e || !*e)
return;
ent = *e;
if (ent->unconfirmed)
read_info(ent);
}
int v4clients_process(fd_set *fdset)
{
char buf[4096] __attribute__((aligned(__alignof__(struct inotify_event))));
const struct inotify_event *ev;
ssize_t len;
char *ptr;
if (clients_fd < 0 ||
!FD_ISSET(clients_fd, fdset))
return 0;
while ((len = read(clients_fd, buf, sizeof(buf))) > 0) {
for (ptr = buf; ptr < buf + len;
ptr += sizeof(struct inotify_event) + ev->len) {
int id;
ev = (const struct inotify_event *)ptr;
id = atoi(ev->name);
if (id <= 0)
continue;
if (ev->mask & IN_CREATE)
add_id(id);
if (ev->mask & IN_DELETE)
del_id(id);
if (ev->mask & IN_MODIFY)
check_id(id);
}
}
return 1;
}