// SPDX-License-Identifier: LGPL-3.0-or-later
/*
 * Copyright IBM Corporation, 2012
 *  Contributor: Frank Filz <ffilzlnx@mindspring.com>
 *
 *
 * This software is a server that implements the NFS protocol.
 *
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
 *
 *
 */

#include <fcntl.h>
#include <cephfs/libcephfs.h>
#include <pthread.h>
#include <stdlib.h>
#include <assert.h>
#include <libgen.h>
#include "multilock.h"
#include "../../include/gsh_list.h"

/* command line syntax */

char options[] = "c:qdx:s:n:p:h?C:";
char usage[] =
	"Usage: ml_cephfs_client -C ceph-config -s server -p port -n name [-q] [-d] [-c path]\n"
	"       ml_cephfs_client -C ceph-config -x script [-q] [-d] [-c path]\n"
	"       ml_cephfs_client -C ceph-config [-q] [-d] [-c path]\n"
	"\n"
	"  ml_cephfs_client may be run in three modes\n"
	"  - In the first mode, the client will be driven by a console.\n"
	"  - In the second mode, the client is driven by a script.\n"
	"  - In the third mode, the client interractive.\n"
	"\n"
	"  -s server      - specify the console's hostname or IP address\n"
	"  -p port        - specify the console's port number\n"
	"  -n name        - specify the client's name\n"
	"  -x script      - specify the name of a script to execute\n"
	"  -q             - specify quiet mode\n"
	"  -d             - specify dup errors mode (errors are sent to stdout and stderr)\n"
	"  -c path        - chdir\n"
	"  -C ceph-config - path to the ceph config file\n";

#define NUM_WORKER 4
#define POLL_DELAY 10

enum thread_type {
	THREAD_NONE,
	THREAD_MAIN,
	THREAD_WORKER,
	THREAD_POLL,
	THREAD_CANCEL
};

struct work_item {
	struct glist_head queue;
	struct glist_head fno_work;
	struct response resp;
	enum thread_type work_owner;
	pthread_t work_thread;
	time_t next_poll;
};

char server[MAXSTR];
char name[MAXSTR];
char portstr[MAXSTR];
int port;
char line[MAXXFER];
long alarmtag;
Inode *inodes[MAXFPOS + 1];
Fh *filehandles[MAXFPOS + 1];
enum lock_mode lock_mode[MAXFPOS + 1];
char ceph_config[PATH_MAX];
char ceph_path[PATH_MAX] = "/";

/* Global variables to manage work list */
struct glist_head fno_work[MAXFPOS + 1];
struct glist_head work_queue = GLIST_HEAD_INIT(work_queue);
struct glist_head poll_queue = GLIST_HEAD_INIT(poll_queue);
pthread_t threads[NUM_WORKER + 1];
pthread_mutex_t work_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t work_cond = PTHREAD_COND_INITIALIZER;

enum thread_type a_worker = THREAD_WORKER;
enum thread_type a_poller = THREAD_POLL;

/* The CephFS mount */
struct ceph_mount_info *cmount;

/* Default mount UserPerm */
UserPerm *cephperms;

void openserver(void)
{
	struct addrinfo *addr;
	int rc;
	struct addrinfo hint;
	int sock;
	struct response resp;

	if (!quiet)
		fprintf(stdout, "server=%s port=%d name=%s\n", server, port,
			name);

	memset(&hint, 0, sizeof(hint));
	hint.ai_family = AF_INET;
	hint.ai_socktype = SOCK_STREAM;

	rc = getaddrinfo(server, portstr, &hint, &addr);

	if (rc != 0)
		fatal("getaddrinfo error %d \"%s\"\n", rc, gai_strerror(rc));

	rc = socket(addr[0].ai_family, SOCK_STREAM, 0);

	if (rc == -1)
		fatal("socket failed with ERRNO %d \"%s\"\n", errno,
		      strerror(errno));

	sock = rc;

	rc = connect(sock, addr[0].ai_addr, addr[0].ai_addrlen);

	if (rc == -1)
		fatal("connect failed with ERRNO %d \"%s\"\n", errno,
		      strerror(errno));

	input = fdopen(sock, "r");

	if (input == NULL)
		fatal("Could not create input stream from socket ERROr %d \"%s\"\n",
		      errno, strerror(errno));

	output = fdopen(sock, "w");

	if (output == NULL)
		fatal("Could not create output stream from socket ERROr %d \"%s\"\n",
		      errno, strerror(errno));

	if (!quiet)
		fprintf(stdout, "connected to server %s:%d\n", server, port);

	resp.r_cmd = CMD_HELLO;
	resp.r_status = STATUS_OK;
	resp.r_tag = 0;
	array_strcpy(resp.r_data, name);
	respond(&resp);
}

bool do_fork(struct response *resp, bool use_server)
{
	pid_t forked;

	if (!use_server) {
		fprintf_stderr("FORK may only be used in server mode\n");
		return false;
	}

	forked = fork();

	if (forked < 0) {
		/* Error */
		resp->r_status = STATUS_OK;
		resp->r_errno = errno;

		if (!quiet)
			fprintf(stdout, "fork failed %d (%s)\n",
				(int)resp->r_errno, strerror(resp->r_errno));

		return true;
	} else if (forked == 0) {
		/* Parent sends a FORK response */
		array_strcpy(resp->r_data, name);
		resp->r_status = STATUS_OK;
		if (!quiet)
			fprintf(stdout, "fork succeeded\n");
		return true;
	}

	if (!quiet)
		fprintf(stdout, "forked\n");

	/* This is the forked process */

	/* First close the old output. */
	fclose(output);

	/* Setup the new client name. */
	array_strcpy(name, resp->r_data);

	/* Then open a new connection to the server. */
	openserver();

	/* Response already sent. */
	return false;
}

void command(void)
{
}

void sighandler(int sig)
{
	struct response resp;

	switch (sig) {
	case SIGALRM:
		resp.r_cmd = CMD_ALARM;
		resp.r_tag = alarmtag;
		resp.r_status = STATUS_COMPLETED;
		alarmtag = 0;
		respond(&resp);
		break;

	case SIGPIPE:
	case SIGIO:
		break;
	}
}

void do_alarm(struct response *resp)
{
	unsigned int remain;

	remain = alarm(resp->r_secs);

	if (remain != 0) {
		struct response resp2;

		resp2.r_cmd = CMD_ALARM;
		resp2.r_tag = alarmtag;
		resp2.r_secs = remain;
		resp2.r_status = STATUS_CANCELED;
		respond(&resp2);
	}

	if (resp->r_secs != 0)
		alarmtag = resp->r_tag;
	else
		alarmtag = 0;

	resp->r_status = STATUS_OK;
}

/* Hack for backwards compatibility */
#ifndef AT_STATX_DONT_SYNC
#define AT_STATX_DONT_SYNC AT_NO_ATTR_SYNC
#endif

void do_open(struct response *resp)
{
	int rc;
	Fh *filehandle = NULL;
	Inode *inode = NULL, *parent = NULL;
	struct ceph_statx stx;

	if (filehandles[resp->r_fpos] != NULL) {
		resp->r_status = STATUS_ERRNO;
		resp->r_errno = EINVAL;
		array_strcpy(errdetail, "fpos in use");
		array_sprintf(badtoken, "%ld", resp->r_fpos);
		return;
	}

	if ((resp->r_flags & O_CREAT) != 0) {
		char fullpath[PATH_MAX];
		char *name;
		char *path;

		array_strcpy(fullpath, resp->r_data);

		name = basename(fullpath);
		path = dirname(fullpath);
		fprintf_stderr("path = '%s' name = '%s'\n", path, name);

		rc = ceph_ll_walk(cmount, path, &parent, &stx, 0,
				  AT_STATX_DONT_SYNC, cephperms);

		if (rc >= 0) {
			rc = ceph_ll_create(cmount, parent, name, resp->r_mode,
					    resp->r_flags, &inode, &filehandle,
					    &stx, 0, AT_STATX_DONT_SYNC,
					    cephperms);

			/* Release the parent directory. */
			ceph_ll_put(cmount, parent);

			if (rc < 0)
				array_strcpy(errdetail, "ceph_ll_create");
		} else {
			array_sprintf(errdetail, "ceph_ll_walk %s", path);
		}
	} else {
		rc = ceph_ll_walk(cmount, resp->r_data, &inode, &stx, 0,
				  AT_STATX_DONT_SYNC, cephperms);

		if (rc >= 0) {
			rc = ceph_ll_open(cmount, inode, resp->r_flags,
					  &filehandle, cephperms);

			if (rc < 0)
				array_strcpy(errdetail, "ceph_ll_open");
		} else {
			array_strcpy(errdetail, "ceph_ll_walk");
		}
	}

	if (rc < 0) {
		resp->r_status = STATUS_ERRNO;
		resp->r_errno = -rc;
		array_strcpy(badtoken, resp->r_data);
		return;
	}

	inodes[resp->r_fpos] = inode;
	filehandles[resp->r_fpos] = filehandle;
	lock_mode[resp->r_fpos] = (enum lock_mode)resp->r_lock_type;
	resp->r_fno = resp->r_fpos;
	resp->r_status = STATUS_OK;
}

void do_write(struct response *resp)
{
	long long rc;

	if (resp->r_fpos != 0 && filehandles[resp->r_fpos] == NULL) {
		resp->r_status = STATUS_ERRNO;
		resp->r_errno = EBADF;
		array_strcpy(errdetail, "Invalid file number");
		array_sprintf(badtoken, "%ld", resp->r_fpos);
		return;
	}

	rc = ceph_ll_write(cmount, filehandles[resp->r_fpos], -1,
			   resp->r_length, resp->r_data);

	if (rc < 0) {
		resp->r_status = STATUS_ERRNO;
		resp->r_errno = -rc;
		array_strcpy(errdetail, "Write failed");
		array_sprintf(badtoken, "%lld", resp->r_length);
		return;
	}

	if (rc != resp->r_length) {
		resp->r_status = STATUS_ERRNO;
		resp->r_errno = EIO;
		array_strcpy(errdetail, "Short write");
		array_sprintf(badtoken, "%lld", rc);
		return;
	}

	resp->r_status = STATUS_OK;
}

void do_read(struct response *resp)
{
	long long rc;

	if (resp->r_fpos != 0 && filehandles[resp->r_fpos] == NULL) {
		resp->r_status = STATUS_ERRNO;
		resp->r_errno = EBADF;
		array_strcpy(errdetail, "Invalid file number");
		array_sprintf(badtoken, "%ld", resp->r_fpos);
		return;
	}

	if (resp->r_length > MAXSTR)
		resp->r_length = MAXSTR;

	rc = ceph_ll_read(cmount, filehandles[resp->r_fpos], -1, resp->r_length,
			  resp->r_data);

	if (rc < 0) {
		resp->r_status = STATUS_ERRNO;
		resp->r_errno = -rc;
		array_strcpy(errdetail, "Read failed");
		array_sprintf(badtoken, "%lld", resp->r_length);
		return;
	}

	resp->r_data[rc] = '\0';
	resp->r_length = strlen(resp->r_data);
	resp->r_status = STATUS_OK;
}

void do_seek(struct response *resp)
{
	int rc;

	if (resp->r_fpos != 0 && filehandles[resp->r_fpos] == NULL) {
		resp->r_status = STATUS_ERRNO;
		resp->r_errno = EBADF;
		array_strcpy(errdetail, "Invalid file number");
		array_sprintf(badtoken, "%ld", resp->r_fpos);
		return;
	}

	rc = ceph_ll_lseek(cmount, filehandles[resp->r_fpos], resp->r_start,
			   SEEK_SET);

	if (rc < 0) {
		resp->r_status = STATUS_ERRNO;
		resp->r_errno = -rc;
		array_strcpy(errdetail, "Seek failed");
		array_sprintf(badtoken, "%lld", resp->r_start);
		return;
	}

	resp->r_status = STATUS_OK;
}

void free_work(struct work_item *work)
{
	/* Make sure the item isn't on any queues */
	glist_del(&work->fno_work);
	glist_del(&work->queue);

	free(work);
}

void cancel_work_item(struct work_item *work)
{
	switch (work->work_owner) {
	case THREAD_NONE:
	case THREAD_MAIN:
	case THREAD_CANCEL:
		/* nothing special to do */
		break;

	case THREAD_WORKER:
	case THREAD_POLL:
		/* Mark the work item to be canceled */
		work->work_owner = THREAD_CANCEL;

		pthread_kill(work->work_thread, SIGIO);

		/* Wait for thread to be done or cancel the work */
		while (work->work_owner != THREAD_NONE)
			pthread_cond_wait(&work_cond, &work_mutex);
	}

	/* Done with the work item, free the memory. */
	free_work(work);
}

static inline long long lock_end(struct response *req)
{
	if (req->r_length == 0)
		return LLONG_MAX;

	return req->r_start + req->r_length;
}

void cancel_work(struct response *req)
{
	struct glist_head cancel_work = GLIST_HEAD_INIT(cancel_work);
	struct glist_head *glist;
	struct work_item *work;
	bool start_over = true;

	pthread_mutex_lock(&work_mutex);

	while (start_over) {
		start_over = false;

		glist_for_each(glist, fno_work + req->r_fpos) {
			work = glist_entry(glist, struct work_item, fno_work);
			if (work->resp.r_start >= req->r_start &&
			    lock_end(&work->resp) <= lock_end(req)) {
				/* Do something */
				cancel_work_item(work);

				/* List may be messed up */
				start_over = true;
				break;
			}
		}
	}

	pthread_mutex_unlock(&work_mutex);
}

/* Must only be called from main thread...*/
int schedule_work(struct response *resp)
{
	struct work_item *work = calloc(1, sizeof(*work));

	if (work == NULL) {
		errno = ENOMEM;
		return -1;
	}

	pthread_mutex_lock(&work_mutex);

	work->resp = *resp;

	work->work_owner = THREAD_NONE;
	glist_add_tail(&work_queue, &work->queue);
	glist_add_tail(fno_work + resp->r_fpos, &work->fno_work);

	/* Signal to the worker and polling threads there is new work */
	pthread_cond_broadcast(&work_cond);

	pthread_mutex_unlock(&work_mutex);

	return 0;
}

bool do_lock(struct response *resp, enum thread_type thread_type)
{
	int rc;
	struct flock lock;
	bool retry = false;
	uint64_t owner;
	bool sleep = false;

	if (resp->r_fpos != 0 && filehandles[resp->r_fpos] == NULL) {
		resp->r_status = STATUS_ERRNO;
		resp->r_errno = EBADF;
		array_strcpy(errdetail, "Invalid file number");
		array_sprintf(badtoken, "%ld", resp->r_fpos);
		return true;
	}

	switch (lock_mode[resp->r_fpos]) {
	case LOCK_MODE_POSIX:
		if (resp->r_cmd == CMD_LOCKW) {
			if (thread_type != THREAD_WORKER) {
				retry = true;
				sleep = false;
			} else {
				sleep = true;
			}
		} else {
			sleep = false;
		}
		owner = getpid();
		break;

	case LOCK_MODE_OFD:
		if (resp->r_cmd == CMD_LOCKW) {
			if (thread_type != THREAD_WORKER) {
				retry = true;
			} else {
				sleep = true;
			}
		} else {
			sleep = false;
		}
		owner = resp->r_fpos;
		break;
	}

	lock.l_whence = SEEK_SET;
	lock.l_type = resp->r_lock_type;
	lock.l_start = resp->r_start;
	lock.l_len = resp->r_length;
	lock.l_pid = 0;

	rc = ceph_ll_setlk(cmount, filehandles[resp->r_fpos], &lock, owner,
			   sleep);

	if (rc == -EAGAIN && retry && thread_type == THREAD_MAIN) {
		/* We need to schedule OFD blocked lock */
		rc = schedule_work(resp);

		/* Check for scheduling success */
		if (rc >= 0)
			return false;
	}

	if (rc < 0) {
		if (rc == -EAGAIN) {
			if (retry) {
				/* Let caller know we didn't complete */
				return false;
			}
			resp->r_status = STATUS_DENIED;
		} else if (rc == -EINTR) {
			resp->r_status = STATUS_CANCELED;
		} else if (rc == -EDEADLK) {
			resp->r_status = STATUS_DEADLOCK;
		} else {
			resp->r_status = STATUS_ERRNO;
			resp->r_errno = -rc;
			array_strcpy(errdetail, "Lock failed");
			array_sprintf(badtoken, "%s %lld %lld",
				      str_lock_type(lock.l_type), resp->r_start,
				      resp->r_length);
		}
	} else
		resp->r_status = STATUS_GRANTED;

	return true;
}

void do_hop(struct response *resp)
{
	int rc;
	int pos;
	struct flock lock;
	uint64_t owner;

	if (resp->r_fpos != 0 && filehandles[resp->r_fpos] == NULL) {
		resp->r_status = STATUS_ERRNO;
		resp->r_errno = EBADF;
		array_strcpy(errdetail, "Invalid file number");
		array_sprintf(badtoken, "%ld", resp->r_fpos);
		return;
	}

	switch (lock_mode[resp->r_fpos]) {
	case LOCK_MODE_POSIX:
		owner = getpid();
		break;

	case LOCK_MODE_OFD:
		owner = resp->r_fpos;
		break;
	}

	for (pos = resp->r_start; pos < resp->r_start + resp->r_length; pos++) {
		if ((pos == 0) || (pos == (resp->r_start + resp->r_length - 1)))
			lock.l_start = pos;
		else if ((pos & 1) == 0)
			lock.l_start = pos - 1;
		else
			lock.l_start = pos + 1;

		lock.l_whence = SEEK_SET;
		lock.l_type = resp->r_lock_type;
		lock.l_len = 1;
		lock.l_pid = 0;

		rc = ceph_ll_setlk(cmount, filehandles[resp->r_fpos], &lock,
				   owner, false);

		if (rc < 0) {
			if (rc == -EAGAIN) {
				resp->r_start = lock.l_start;
				resp->r_length = 1;
				resp->r_status = STATUS_DENIED;
				break;
			} else {
				resp->r_status = STATUS_ERRNO;
				resp->r_errno = -rc;
				array_strcpy(errdetail, "Hop failed");
				array_sprintf(badtoken, "%s %ld",
					      str_lock_type(resp->r_lock_type),
					      lock.l_start);
				break;
			}
		} else
			resp->r_status = STATUS_GRANTED;
	}

	if (resp->r_status != STATUS_GRANTED) {
		lock.l_whence = SEEK_SET;
		lock.l_type = F_UNLCK;
		lock.l_start = resp->r_start;
		lock.l_len = resp->r_length;
		lock.l_pid = 0;

		rc = ceph_ll_setlk(cmount, filehandles[resp->r_fpos], &lock,
				   owner, false);

		if (rc < 0) {
			resp->r_status = STATUS_ERRNO;
			resp->r_errno = -rc;
			array_strcpy(errdetail, "Hop Unlock failed");
			array_sprintf(badtoken, "%lld %lld", resp->r_start,
				      resp->r_length);
		}
	}
}

void do_unhop(struct response *resp)
{
	int rc;
	int pos;
	struct flock lock;
	uint64_t owner;

	if (resp->r_fpos != 0 && filehandles[resp->r_fpos] == NULL) {
		resp->r_status = STATUS_ERRNO;
		resp->r_errno = EBADF;
		array_strcpy(errdetail, "Invalid file number");
		array_sprintf(badtoken, "%ld", resp->r_fpos);
		return;
	}

	switch (lock_mode[resp->r_fpos]) {
	case LOCK_MODE_POSIX:
		owner = getpid();
		break;

	case LOCK_MODE_OFD:
		owner = resp->r_fpos;
		break;
	}

	for (pos = resp->r_start; pos < resp->r_start + resp->r_length; pos++) {
		if ((pos == 0) || (pos == (resp->r_start + resp->r_length - 1)))
			lock.l_start = pos;
		else if ((pos & 1) == 0)
			lock.l_start = pos - 1;
		else
			lock.l_start = pos + 1;

		lock.l_whence = SEEK_SET;
		lock.l_type = resp->r_lock_type;
		lock.l_len = 1;
		lock.l_pid = 0;

		rc = ceph_ll_setlk(cmount, filehandles[resp->r_fpos], &lock,
				   owner, false);

		if (rc < 0) {
			resp->r_status = STATUS_ERRNO;
			resp->r_errno = -rc;
			array_strcpy(errdetail, "Unhop failed");
			array_sprintf(badtoken, "%s %ld",
				      str_lock_type(resp->r_lock_type),
				      lock.l_start);
			break;
		} else
			resp->r_status = STATUS_GRANTED;
	}

	if (resp->r_status != STATUS_GRANTED) {
		lock.l_whence = SEEK_SET;
		lock.l_type = F_UNLCK;
		lock.l_start = resp->r_start;
		lock.l_len = resp->r_length;
		lock.l_pid = 0;

		rc = ceph_ll_setlk(cmount, filehandles[resp->r_fpos], &lock,
				   owner, false);

		if (rc < 0) {
			resp->r_status = STATUS_ERRNO;
			resp->r_errno = -rc;
			array_strcpy(errdetail, "Unhop Unlock failed");
			array_sprintf(badtoken, "%lld %lld", resp->r_start,
				      resp->r_length);
		}
	}
}

void do_unlock(struct response *resp)
{
	int rc;
	struct flock lock;
	uint64_t owner;

	if (resp->r_fpos != 0 && filehandles[resp->r_fpos] == NULL) {
		resp->r_status = STATUS_ERRNO;
		resp->r_errno = EBADF;
		array_strcpy(errdetail, "Invalid file number");
		array_sprintf(badtoken, "%ld", resp->r_fpos);
		return;
	}

	/* If this fpos has a blocking lock, cancel it. */
	cancel_work(resp);

	switch (lock_mode[resp->r_fpos]) {
	case LOCK_MODE_POSIX:
		owner = getpid();
		break;

	case LOCK_MODE_OFD:
		owner = resp->r_fpos;
		break;
	}

	lock.l_whence = SEEK_SET;
	lock.l_type = resp->r_lock_type;
	lock.l_start = resp->r_start;
	lock.l_len = resp->r_length;
	lock.l_pid = 0;

	rc = ceph_ll_setlk(cmount, filehandles[resp->r_fpos], &lock, owner,
			   false);

	if (rc < 0) {
		resp->r_status = STATUS_ERRNO;
		resp->r_errno = -rc;
		array_strcpy(errdetail, "Unlock failed");
		array_sprintf(badtoken, "%lld %lld", resp->r_start,
			      resp->r_length);
		return;
	}

	resp->r_status = STATUS_GRANTED;
}

void do_test(struct response *resp)
{
	int rc;
	struct flock lock;
	uint64_t owner;

	if (resp->r_fpos != 0 && filehandles[resp->r_fpos] == NULL) {
		resp->r_status = STATUS_ERRNO;
		resp->r_errno = EBADF;
		array_strcpy(errdetail, "Invalid file number");
		array_sprintf(badtoken, "%ld", resp->r_fpos);
		return;
	}

	switch (lock_mode[resp->r_fpos]) {
	case LOCK_MODE_POSIX:
		owner = getpid();
		break;

	case LOCK_MODE_OFD:
		owner = resp->r_fpos;
		break;
	}

	lock.l_whence = SEEK_SET;
	lock.l_type = resp->r_lock_type;
	lock.l_start = resp->r_start;
	lock.l_len = resp->r_length;
	lock.l_pid = 0;

	fprintf(stdout, "TEST lock type %s\n", str_lock_type(lock.l_type));

	rc = ceph_ll_getlk(cmount, filehandles[resp->r_fpos], &lock, owner);

	if (rc < 0) {
		resp->r_status = STATUS_ERRNO;
		resp->r_errno = -rc;
		array_strcpy(errdetail, "Test failed");
		array_sprintf(badtoken, "%s %lld %lld",
			      str_lock_type(lock.l_type), resp->r_start,
			      resp->r_length);
		return;
	}

	if (lock.l_type == F_UNLCK) {
		fprintf(stdout, "GRANTED TEST lock type %s\n",
			str_lock_type(lock.l_type));
		resp->r_status = STATUS_GRANTED;
	} else {
		resp->r_lock_type = lock.l_type;
		resp->r_pid = lock.l_pid;
		resp->r_start = lock.l_start;
		resp->r_length = lock.l_len;
		resp->r_status = STATUS_CONFLICT;
	}
}

void do_close(struct response *resp)
{
	int rc;

	if (resp->r_fpos != 0 && filehandles[resp->r_fpos] == NULL) {
		resp->r_status = STATUS_ERRNO;
		resp->r_errno = EBADF;
		array_strcpy(errdetail, "Invalid file number");
		array_sprintf(badtoken, "%ld", resp->r_fpos);
		return;
	}

	rc = ceph_ll_close(cmount, filehandles[resp->r_fpos]);

	ceph_ll_put(cmount, inodes[resp->r_fpos]);

	filehandles[resp->r_fpos] = NULL;
	inodes[resp->r_fpos] = NULL;

	if (rc < 0) {
		resp->r_status = STATUS_ERRNO;
		resp->r_errno = -rc;
		array_strcpy(errdetail, "Close failed");
		array_sprintf(badtoken, "%ld", resp->r_fpos);
		return;
	}

	resp->r_status = STATUS_OK;
}

struct test_list {
	struct test_list *tl_next;
	long long tl_start;
	long long tl_end;
};

struct test_list *tl_head;
struct test_list *tl_tail;

void remove_test_list_head(void)
{
	struct test_list *item = tl_head;

	if (item == NULL)
		fatal("Corruption in test list\n");

	tl_head = item->tl_next;

	if (tl_tail == item)
		tl_tail = NULL;

	free(item);
}

void make_test_item(long long start, long long end)
{
	struct test_list *item = malloc(sizeof(*item));

	if (item == NULL)
		fatal("Could not allocate test list item\n");

	item->tl_next = NULL;
	item->tl_start = start;
	item->tl_end = end;

	if (tl_head == NULL)
		tl_head = item;

	if (tl_tail != NULL)
		tl_tail->tl_next = item;

	tl_tail = item;
}

int list_locks(long long start, long long end, struct response *resp)
{
	long long conf_end;
	struct flock lock;
	int rc;

	lock.l_whence = SEEK_SET;
	lock.l_type = F_WRLCK;
	lock.l_start = start;
	lock.l_pid = 0;

	if (end == INT64_MAX)
		lock.l_len = 0;
	else
		lock.l_len = end - start;

	rc = ceph_ll_getlk(cmount, filehandles[resp->r_fpos], &lock, 0);

	if (rc < 0) {
		resp->r_status = STATUS_ERRNO;
		resp->r_errno = -rc;
		array_strcpy(errdetail, "Test failed");
		array_sprintf(badtoken, "%s %lld %lld",
			      str_lock_type(lock.l_type), resp->r_start,
			      resp->r_length);
		respond(resp);
		return false;
	}

	/* Our test succeeded */
	if (lock.l_type == F_UNLCK)
		return false;

	resp->r_status = STATUS_CONFLICT;
	resp->r_lock_type = lock.l_type;
	resp->r_pid = lock.l_pid;
	resp->r_start = lock.l_start;
	resp->r_length = lock.l_len;

	respond(resp);

	conf_end = lock_end(resp);

	if (lock.l_start > start)
		make_test_item(start, lock.l_start);

	if (conf_end < end)
		make_test_item(conf_end, end);

	return true;
}

void do_list(struct response *resp)
{
	long long start = resp->r_start;
	long long length = resp->r_length;
	int conflict = false;

	if (resp->r_fpos != 0 && filehandles[resp->r_fpos] == NULL) {
		resp->r_status = STATUS_ERRNO;
		resp->r_errno = EBADF;
		array_strcpy(errdetail, "Invalid file number");
		array_sprintf(badtoken, "%ld", resp->r_fpos);
		return;
	}

	resp->r_lock_type = F_WRLCK;

	make_test_item(start, lock_end(resp));

	while (tl_head != NULL) {
		conflict |=
			list_locks(tl_head->tl_start, tl_head->tl_end, resp);
		remove_test_list_head();
	}

	if (conflict)
		resp->r_status = STATUS_DENIED;
	else
		resp->r_status = STATUS_AVAILABLE;

	resp->r_lock_type = F_WRLCK;
	resp->r_start = start;
	resp->r_length = length;
}

struct work_item *get_work(enum thread_type thread_type)
{
	struct work_item *work, *poll;

	while (true) {
		/* Check poll list first */
		poll = glist_first_entry(&poll_queue, struct work_item, queue);
		work = poll;

		/* If we didn't find work on the poll list or this is a polling
		 * thread and the work on the poll list isn't due for polling
		 * yet, then look for work on the work list.
		 */
		if (work == NULL || (thread_type == THREAD_POLL &&
				     work->next_poll > time(NULL))) {
			/* Check work list now */
			work = glist_first_entry(&work_queue, struct work_item,
						 queue);
		}

		/* Assign the work to ourself and remove from the queue and
		 * return to the caller.
		 */
		if (work != NULL) {
			work->work_owner = thread_type;
			work->work_thread = pthread_self();
			glist_del(&work->queue);
			return work;
		}

		/* No work, decide what kind of wait to do. */
		if (thread_type == THREAD_POLL && poll != NULL) {
			/* Since there is polling work to do, determine next
			 * time to poll and wait that long for signal.
			 */
			struct timespec twait = { poll->next_poll - time(NULL),
						  0 };

			pthread_cond_timedwait(&work_cond, &work_mutex, &twait);
		} else {
			/* Wait for signal */
			pthread_cond_wait(&work_cond, &work_mutex);
		}
	}
}

void *worker(void *t_type)
{
	struct work_item *work = NULL;
	bool complete, cancelled = false;
	enum thread_type thread_type = *((enum thread_type *)t_type);

	pthread_mutex_lock(&work_mutex);

	while (true) {
		/* Look for work */
		work = get_work(thread_type);

		pthread_mutex_unlock(&work_mutex);

		assert(work != NULL);

		/* Do the work */
		switch (work->resp.r_cmd) {
		case CMD_LOCKW:
			complete = do_lock(&work->resp, thread_type);
			break;
		default:
			work->resp.r_status = STATUS_ERRNO;
			work->resp.r_errno = EINVAL;
			complete = true;
			break;
		}

		if (complete)
			respond(&work->resp);

		pthread_mutex_lock(&work_mutex);

		if (complete) {
			/* Remember if the main thread was trying to cancel
			 * the work.
			 */
			cancelled = work->work_owner == THREAD_CANCEL;

			/* Indicate this work is complete */
			work->work_owner = THREAD_NONE;
			work->work_thread = 0;

			if (cancelled) {
				/* Signal that work has been canceled or
				 * completed.
				 */
				pthread_cond_broadcast(&work_cond);
			} else {
				/* The work is done, and may be freed, except
				 * that if the work was being cancelled by
				 * cancel_work, then the main thread is waiting
				 * for this thread to be done with the work
				 * item, and thus we can not free it,
				 * cancel_work_item will be responsible to free
				 * the work item. This assures that the request
				 * that caused the cancellation is blocked until
				 * the cancellation has completed.
				 */
				free_work(work);
			}
		} else {
			/* This can ONLY happen for a polling thread.
			 * Put this work back in the queue, with a new
			 * next polling time.
			 */
			work->work_owner = THREAD_NONE;
			work->work_thread = 0;
			work->next_poll = time(NULL) + POLL_DELAY;
			glist_add_tail(&poll_queue, &work->queue);

			/* And let worker threads know there may be
			 * more work to do.
			 */
			pthread_cond_broadcast(&work_cond);
		}
	}
}

int main(int argc, char **argv)
{
	int opt;
	int len, rc, i;
	struct sigaction sigact;
	char *rest;
	int oflags = 0;
	int no_tag;

	/* Init the lists of work for each fno */
	for (i = 0; i <= MAXFPOS; i++)
		glist_init(fno_work + i);

	/* Start the worker and polling threads */
	for (i = 0; i <= NUM_WORKER; i++) {
		rc = pthread_create(&threads[i], NULL, worker,
				    i == 0 ? &a_poller : &a_worker);
		if (rc == -1) {
			fprintf(stderr, "pthread_create failed %s\n",
				strerror(errno));
			exit(1);
		}
	}

	/* Initialize the signal handling */
	memset(&sigact, 0, sizeof(sigact));
	sigact.sa_handler = sighandler;

	if (sigaction(SIGALRM, &sigact, NULL) == -1)
		return 1;

	if (sigaction(SIGPIPE, &sigact, NULL) == -1)
		return 1;

	if (sigaction(SIGIO, &sigact, NULL) == -1)
		return 1;

	input = stdin;
	output = stdout;

	/* now parsing options with getopt */
	while ((opt = getopt(argc, argv, options)) != EOF) {
		switch (opt) {
		case 'c':
			array_strcpy(ceph_path, optarg);
			break;

		case 'q':
			quiet = true;
			break;

		case 'd':
			duperrors = true;
			break;

		case 's':
			if (oflags > 7)
				show_usage(1, "Can not combine -x and -s\n");

			oflags |= 1;
			script = true;
			array_strcpy(server, optarg);
			break;

		case 'x':
			if ((oflags & 7) != 0)
				show_usage(1,
					   "Can not combine -x and -s/-p/-n\n");

			oflags |= 8;
			script = true;
			input = fopen(optarg, "r");

			if (input == NULL)
				fatal("Could not open %s\n", optarg);
			break;

		case 'n':
			if (oflags > 7)
				show_usage(1, "Can not combine -x and -n\n");

			oflags |= 2;
			array_strcpy(name, optarg);
			break;

		case 'p':
			if (oflags > 7)
				show_usage(1, "Can not combine -x and -p\n");

			oflags |= 4;
			array_strcpy(portstr, optarg);
			port = atoi(optarg);
			break;

		case 'C':
			array_strcpy(ceph_config, optarg);
			break;

		case '?':
		case 'h':
		default:
			/* display the help */
			show_usage(0, "Help\n");
		}
	}

	if (ceph_config[0] == '\0')
		show_usage(1, "Must specifiy -C ceph-config\n");

	if (oflags > 0 && oflags < 7)
		show_usage(1, "Must specify -s, -p, and -n together\n");

	if (oflags == 7)
		openserver();

	rc = ceph_create(&cmount, NULL);

	if (rc != 0)
		fatal("Unable to create ceph mount context\n");

	rc = ceph_conf_read_file(cmount, ceph_config);

	if (rc != 0)
		fatal("Unable to read ceph config\n");

	rc = ceph_mount(cmount, NULL);

	if (rc != 0) {
		fprintf_stderr("Unable to mount Ceph cluster %s(%d)\n",
			       strerror(-rc), rc);
		fatal("Unable to mount Ceph cluster");
	}

	cephperms = ceph_mount_perms(cmount);

	while (1) {
		len = readln(input, line, sizeof(line));
		if (len < 0) {
			if (script)
				fatal("End of file on input\n");
			else
				break;
		} else {
			struct response resp;
			bool complete = true;

			lno++;
			memset(&resp, 0, sizeof(resp));

			rest = SkipWhite(line, REQUIRES_MORE, "Invalid line");

			/* Skip totally blank line */
			if (rest == NULL)
				continue;

			if (script && !quiet)
				fprintf(stdout, "%s\n", rest);

			/* If line doesn't start with a tag, that's ok */
			no_tag = (!isdigit(*rest) && (*rest != '$') &&
				  (*rest != '-'));

			/* Parse request into response structure */
			rest = parse_request(rest, &resp, no_tag);

			if (rest == NULL) {
				resp.r_status = STATUS_ERRNO;
				resp.r_errno = errno;
			} else {
				/* Make sure default status is ok */
				resp.r_status = STATUS_OK;

				if (*rest != '\0' && *rest != '#')
					fprintf_stderr(
						"Command line not consumed, rest=\"%s\"\n",
						rest);

				switch (resp.r_cmd) {
				case CMD_OPEN:
					do_open(&resp);
					break;
				case CMD_CLOSE:
					do_close(&resp);
					break;
				case CMD_LOCKW:
					complete = do_lock(&resp, THREAD_MAIN);
					break;
				case CMD_LOCK:
					complete = do_lock(&resp, THREAD_MAIN);
					break;
				case CMD_UNLOCK:
					do_unlock(&resp);
					break;
				case CMD_TEST:
					do_test(&resp);
					break;
				case CMD_LIST:
					do_list(&resp);
					break;
				case CMD_HOP:
					do_hop(&resp);
					break;
				case CMD_UNHOP:
					do_unhop(&resp);
					break;
				case CMD_SEEK:
					do_seek(&resp);
					break;
				case CMD_READ:
					do_read(&resp);
					break;
				case CMD_WRITE:
					do_write(&resp);
					break;
				case CMD_ALARM:
					do_alarm(&resp);
					break;
				case CMD_FORK:
					complete = do_fork(&resp, oflags == 7);
					break;

				case CMD_HELLO:
				case CMD_COMMENT:
				case CMD_QUIT:
					resp.r_status = STATUS_OK;
					break;

				case NUM_COMMANDS:
					fprintf_stderr("Invalid command %s\n",
						       line);
					continue;
				}
			}

			if (complete)
				respond(&resp);

			if (resp.r_cmd == CMD_QUIT)
				exit(0);
		}
	}

	return 0;
}
