/*
 *  commands.c
 *
 *  Copyright (C) 2006 Alex deVries
 *
 */

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <utime.h>
#include <stdlib.h>
#include <getopt.h>
#include <sys/un.h>
#include <unistd.h>
#include <sys/time.h>
#include <stdarg.h>
#include <getopt.h>
#include <signal.h>

#include "afpfs-ng/afp.h"
#include "afpfs-ng/dsi.h"
#include "afp_server.h"
#include "afpfs-ng/utils.h"
#include "daemon.h"
#include "afpfs-ng/uams_def.h"
#include "afpfs-ng/codepage.h"
#include "afpfs-ng/libafpclient.h"
#include "afpfs-ng/map_def.h"
#include "fuse_int.h"
#include "fuse_error.h"
#include "fuse_internal.h"

#ifdef __linux
#define FUSE_DEVICE "/dev/fuse"
#else
#define FUSE_DEVICE "/dev/fuse0"
#endif


static int fuse_log_method=LOG_METHOD_SYSLOG;

void trigger_exit(void);

static struct fuse_client * client_base = NULL;

struct afp_volume * global_volume;

static int volopen(struct fuse_client * c, struct afp_volume * volume);
static int process_command(struct fuse_client * c);
static struct afp_volume * mount_volume(struct fuse_client * c,
	struct afp_server * server, char * volname, char * volpassword) ;

void fuse_set_log_method(int new_method)
{
	fuse_log_method=new_method;
}


static int remove_client(struct fuse_client * toremove) 
{
	struct fuse_client * c, * prev=NULL;

	for (c=client_base;c;c=c->next) {
		if (c==toremove) {
			if (!prev) client_base=NULL;
			else prev->next=toremove->next;
			free(toremove);
			toremove=NULL;
			return 0;
		}
		prev=c;
	}
	return -1;
}

static int fuse_add_client(int fd) 
{
	struct fuse_client * c, *newc;

	if ((newc=malloc(sizeof(*newc)))==NULL) goto error;


	memset(newc,0,sizeof(*newc));
	newc->fd=fd;
	newc->next=NULL;
	if (client_base==NULL) client_base=newc;
	else {
		for (c=client_base;c->next;c=c->next);
		c->next=newc;

	}
	return 0;
error:
	return -1;
}

static int fuse_process_client_fds(fd_set * set, int max_fd)
{

	struct fuse_client * c;

	for (c=client_base;c;c=c->next) {
		if (FD_ISSET(c->fd,set)) {
			if (process_command(c)<0) return -1;
			return 1;
		}
	}
	return 0;

}

static int fuse_scan_extra_fds(int command_fd, fd_set *set, int * max_fd)
{

	struct sockaddr_un new_addr;
	socklen_t new_len = sizeof(struct sockaddr_un);
	int new_fd;



	if (FD_ISSET(command_fd,set)) {
		new_fd=accept(command_fd,(struct sockaddr *) &new_addr,&new_len);
		if (new_fd>=0) {
			fuse_add_client(new_fd);
			FD_SET(new_fd,set);
			if ((new_fd+1) > *max_fd) *max_fd=new_fd+1;
		}
	}

	switch (fuse_process_client_fds(set,*max_fd)) {
	case -1:
		{
			int i;
			FD_CLR(new_fd,set);
			for (i=*max_fd;i>=0;i--)
				if (FD_ISSET(i,set)) {
					*max_fd=i;
					break;
				}
		}

		(*max_fd)++;
		close(new_fd);
		goto out;
	case 1:
		goto out;
	}
	/* unknown fd */
	sleep(10);

	return 0;

out:
	return 1;
}

static void fuse_log_for_client(void * priv,
	enum loglevels loglevel, int logtype, const char *message) {
	int len = 0;
	struct fuse_client * c = priv;

	if (c) {
		len = strlen(c->client_string);
		snprintf(c->client_string+len,
			MAX_CLIENT_RESPONSE-len,
			"%s", message);
	} else {

		if (fuse_log_method & LOG_METHOD_SYSLOG)
			syslog(LOG_INFO, "%s", message);
		if (fuse_log_method & LOG_METHOD_STDOUT)
			printf("%s",message);
	}

}

struct start_fuse_thread_arg {
	struct afp_volume * volume;
	struct fuse_client * client;
	int wait;
	int fuse_result;
	int fuse_errno;
	int changeuid;
};

static void * start_fuse_thread(void * other) 
{
	int fuseargc=0;
	const char *fuseargv[200];
#define mountstring_len (AFP_SERVER_NAME_LEN+1+AFP_VOLUME_NAME_LEN+1)
	char mountstring[mountstring_len];
	struct start_fuse_thread_arg * arg = other;
	struct afp_volume * volume = arg->volume;
	struct fuse_client * c = arg->client;
	struct afp_server * server = volume->server;

	/* Check to see if we have permissions to access the mountpoint */

	snprintf(mountstring,mountstring_len,"%s:%s",
		server->server_name_printable,
			volume->volume_name_printable);
	fuseargc=0;
	fuseargv[0]=mountstring;
	fuseargc++;
	fuseargv[1]=volume->mountpoint;
	fuseargc++;
	if (get_debug_mode()) {
		fuseargv[fuseargc]="-d";
		fuseargc++;
	} else {
		fuseargv[fuseargc]="-f";
		fuseargc++;
	}
	
	if (arg->changeuid) {
		fuseargv[fuseargc]="-o";
		fuseargc++;
		fuseargv[fuseargc]="allow_other";
		fuseargc++;
	}


/* #ifdef USE_SINGLE_THREAD */
	fuseargv[fuseargc]="-s";
	fuseargc++;
/*
#endif
*/
	global_volume=volume; 

	arg->fuse_result= 
		afp_register_fuse(fuseargc, (char **) fuseargv,volume);

	arg->fuse_errno=errno;

	arg->wait=0;
	pthread_cond_signal(&volume->startup_condition_cond);

	log_for_client((void *) c,AFPFSD,LOG_WARNING,
		"Unmounting volume %s from %s\n",
		volume->volume_name_printable,
                volume->mountpoint);

	return NULL;
}

static int volopen(struct fuse_client * c, struct afp_volume * volume)
{
	char mesg[1024];
	unsigned int l = 0;	
	memset(mesg,0,1024);
	int rc=afp_connect_volume(volume,volume->server,mesg,&l,1024);

	log_for_client((void *) c,AFPFSD,LOG_ERR,mesg);

	return rc;

}


static unsigned char process_suspend(struct fuse_client * c)
{
	struct afp_server_suspend_request * req =(void *)c->incoming_string+1;
	struct afp_server * s;

	/* Find the server */
	if ((s=find_server_by_name(req->server_name))==NULL) {
		log_for_client((void *) c,AFPFSD,LOG_ERR,
			"%s is an unknown server\n",req->server_name);
		return AFP_SERVER_RESULT_ERROR;
	}

	if (afp_zzzzz(s)) 
		return AFP_SERVER_RESULT_ERROR;

	loop_disconnect(s);
	
	s->connect_state=SERVER_STATE_DISCONNECTED;
	log_for_client((void *) c,AFPFSD,LOG_NOTICE,
		"Disconnected from %s\n",req->server_name);
	return AFP_SERVER_RESULT_OKAY;
}


static int afp_server_reconnect_loud(struct fuse_client * c, struct afp_server * s) 
{
	char mesg[1024];
	unsigned int l = 2040;
	int rc;

	rc=afp_server_reconnect(s,mesg,&l,l);

	if (rc) 
                log_for_client((void *) c,AFPFSD,LOG_ERR,
                        "%s",mesg);
	return rc;


}


static unsigned char process_resume(struct fuse_client * c)
{
	struct afp_server_resume_request * req =(void *) c->incoming_string+1;
	struct afp_server * s;

	/* Find the server */
	if ((s=find_server_by_name(req->server_name))==NULL) {
		log_for_client((void *) c,AFPFSD,LOG_ERR,
			"%s is an unknown server\n",req->server_name);
		return AFP_SERVER_RESULT_ERROR;
	}

	if (afp_server_reconnect_loud(c,s)) 
	{
		log_for_client((void *) c,AFPFSD,LOG_ERR,
			"Unable to reconnect to %s\n",req->server_name);
		return AFP_SERVER_RESULT_ERROR;
	}
	log_for_client((void *) c,AFPFSD,LOG_NOTICE,
		"Resumed connection to %s\n",req->server_name);

	return AFP_SERVER_RESULT_OKAY;
	
}

static unsigned char process_unmount(struct fuse_client * c)
{
	struct afp_server_unmount_request * req;
	struct afp_server * s;
	struct afp_volume * v;
	int j=0;

	req=(void *) c->incoming_string+1;

	for (s=get_server_base();s;s=s->next) {
		for (j=0;j<s->num_volumes;j++) {
			v=&s->volumes[j];
			if (strcmp(v->mountpoint,req->mountpoint)==0) {
				goto found;
			}

		}
	}
	goto notfound;
found:
	if (v->mounted != AFP_VOLUME_MOUNTED ) {
		log_for_client((void *) c,AFPFSD,LOG_NOTICE,
			"%s was not mounted\n",v->mountpoint);
		return AFP_SERVER_RESULT_ERROR;
	}

	afp_unmount_volume(v);

	return AFP_SERVER_RESULT_OKAY;
notfound:
	log_for_client((void *)c,AFPFSD,LOG_WARNING,
		"afpfs-ng doesn't have anything mounted on %s.\n",req->mountpoint);
	return AFP_SERVER_RESULT_ERROR;


}

static unsigned char process_ping(struct fuse_client * c)
{
	log_for_client((void *)c,AFPFSD,LOG_INFO,
		"Ping!\n");
	return AFP_SERVER_RESULT_OKAY;
}

static unsigned char process_exit(struct fuse_client * c)
{
	log_for_client((void *)c,AFPFSD,LOG_INFO,
		"Exiting\n");
	trigger_exit();
	return AFP_SERVER_RESULT_OKAY;
}

static unsigned char process_status(struct fuse_client * c)
{
	struct afp_server * s;

	char text[40960];
	int len=40960;

	if ((c->incoming_size + 1)< sizeof(struct afp_server_status_request)) 
		return AFP_SERVER_RESULT_ERROR;

	afp_status_header(text,&len);

	log_for_client((void *)c,AFPFSD,LOG_INFO,text);

	s=get_server_base();

	for (s=get_server_base();s;s=s->next) {
		afp_status_server(s,text,&len);
		log_for_client((void *)c,AFPFSD,LOG_DEBUG,text);
	}

	return AFP_SERVER_RESULT_OKAY;

}

static int process_mount(struct fuse_client * c)
{
	struct afp_server_mount_request * req;
	struct afp_server  * s=NULL;
	struct afp_volume * volume;
	struct afp_connection_request conn_req;
	int ret;
	struct stat lstat;

	if ((c->incoming_size-1) < sizeof(struct afp_server_mount_request)) 
		goto error;

	req=(void *) c->incoming_string+1;

	/* Todo should check the existance and perms of the mount point */

	if ((ret=access(req->mountpoint,X_OK))!=0) {
		log_for_client((void *)c,AFPFSD,LOG_DEBUG,
			"Incorrect permissions on mountpoint %s: %s\n",
			req->mountpoint, strerror(errno));

		goto error;
	}

	if (stat(FUSE_DEVICE,&lstat)) {
		printf("Could not find %s\n",FUSE_DEVICE);
		goto error;
	}

	if (access(FUSE_DEVICE,R_OK | W_OK )!=0) {
		log_for_client((void *)c, AFPFSD,LOG_NOTICE, 
			"Incorrect permissions on %s, mode of device"
			" is %o, uid/gid is %d/%d.  But your effective "
			"uid/gid is %d/%d\n", 
				FUSE_DEVICE,lstat.st_mode, lstat.st_uid, 
				lstat.st_gid, 
				geteuid(),getegid());
		goto error;
	}

	log_for_client((void *)c,AFPFSD,LOG_NOTICE,
		"Mounting %s from %s on %s\n",
		(char *) req->url.servername, 
		(char *) req->url.volumename,req->mountpoint);

	memset(&conn_req,0,sizeof(conn_req));

	conn_req.url=req->url;
	conn_req.uam_mask=req->uam_mask;

	if ((s=afp_server_full_connect(c,&conn_req))==NULL) {
		signal_main_thread();
		goto error;
	}
	
	if ((volume=mount_volume(c,s,req->url.volumename,
		req->url.volpassword))==NULL) {
		goto error;
	}

	volume->extra_flags|=req->volume_options;

	volume->mapping=req->map;
	afp_detect_mapping(volume);

	snprintf(volume->mountpoint,255, "%s", req->mountpoint);

	/* Create the new thread and block until we get an answer back */
	{
		pthread_mutex_t mutex;
		struct timespec ts;
		struct timeval tv;
		int ret;
		struct start_fuse_thread_arg arg;
		memset(&arg,0,sizeof(arg));
		arg.client = c;
		arg.volume = volume;
		arg.wait = 1;
		arg.changeuid=req->changeuid;

		gettimeofday(&tv,NULL);
		ts.tv_sec=tv.tv_sec;
		ts.tv_sec+=5;
		ts.tv_nsec=tv.tv_usec*1000;
		pthread_mutex_init(&mutex,NULL);
		pthread_cond_init(&volume->startup_condition_cond,NULL);

		/* Kickoff a thread to see how quickly it exits.  If
		 * it exits quickly, we have an error and it failed. */

		pthread_create(&volume->thread,NULL,start_fuse_thread,&arg);

		if (arg.wait) ret = pthread_cond_timedwait(
				&volume->startup_condition_cond,&mutex,&ts);

		report_fuse_errors(c);
		
		switch (arg.fuse_result) {
		case 0:
		if (volume->mounted==AFP_VOLUME_UNMOUNTED) {
			/* Try and discover why */
			switch(arg.fuse_errno) {
			case ENOENT:
				log_for_client((void *)c,AFPFSD,LOG_ERR,
					"Permission denied, maybe a problem with the fuse device or mountpoint?\n");
				break;
			default:
				log_for_client((void *)c,AFPFSD,LOG_ERR,
					"Mounting of volume %s of server %s failed.\n", 
						volume->volume_name_printable, 
						volume->server->server_name_printable);
			}
			goto error;
		} else {
			log_for_client((void *)c,AFPFSD,LOG_NOTICE,
				"Mounting of volume %s of server %s succeeded.\n", 
					volume->volume_name_printable, 
					volume->server->server_name_printable);
			return 0;
		}
		break;
		case ETIMEDOUT:
			log_for_client((void *)c,AFPFSD,LOG_NOTICE,
				"Still trying.\n");
			return 0;
			break;
		default:
			volume->mounted=AFP_VOLUME_UNMOUNTED;
			log_for_client((void *)c,AFPFSD,LOG_NOTICE,
				"Unknown error %d, %d.\n", 
				arg.fuse_result,arg.fuse_errno);
			goto error;
		}

	}
	return AFP_SERVER_RESULT_OKAY;
error:
	if ((s) && (!something_is_mounted(s))) {
		afp_server_remove(s);
	}
	signal_main_thread();
	return AFP_SERVER_RESULT_ERROR;
}


static void * process_command_thread(void * other)
{

	struct fuse_client * c = other;
	int ret=0;
	char tosend[sizeof(struct afp_server_response) + MAX_CLIENT_RESPONSE];
	struct afp_server_response response;


	switch(c->incoming_string[0]) {
	case AFP_SERVER_COMMAND_MOUNT: 
		ret=process_mount(c);
		break;
	case AFP_SERVER_COMMAND_STATUS: 
		ret=process_status(c);
		break;
	case AFP_SERVER_COMMAND_UNMOUNT: 
		ret=process_unmount(c);
		break;
	case AFP_SERVER_COMMAND_SUSPEND: 
		ret=process_suspend(c);
		break;
	case AFP_SERVER_COMMAND_RESUME: 
		ret=process_resume(c);
		break;
	case AFP_SERVER_COMMAND_PING: 
		ret=process_ping(c);
		break;
	case AFP_SERVER_COMMAND_EXIT: 
		ret=process_exit(c);
		break;
	default:
		log_for_client((void *)c,AFPFSD,LOG_ERR,"Unknown command\n");
	}
	/* Send response */
	response.result=ret;
	response.len=strlen(c->client_string);

	bcopy(&response,tosend,sizeof(response));
	bcopy(c->client_string,tosend+sizeof(response),response.len);
	ret=write(c->fd,tosend,sizeof(response)+response.len);
	if (ret<0) {
		perror("Writing");
	}

	if ((!c) || (c->fd==0)) return NULL;
	rm_fd_and_signal(c->fd);
	close(c->fd);
	remove_client(c);

	return NULL;

}
static int process_command(struct fuse_client * c)
{
	int ret;
	int fd;

	ret=read(c->fd,&c->incoming_string,AFP_CLIENT_INCOMING_BUF);

	if (ret<=0) {
		perror("reading");
		goto out;
	}
	c->incoming_size=ret;

	pthread_t thread;
	pthread_create(&thread,NULL,process_command_thread,c);
	return 0;
out:
	fd=c->fd;
	c->fd=0;
	remove_client(c);
	close(fd);
	rm_fd_and_signal(fd);
	return 0;
}


static struct afp_volume * mount_volume(struct fuse_client * c,
	struct afp_server * server, char * volname, char * volpassword) 
{
	struct afp_volume * using_volume;

	using_volume = find_volume_by_name(server,volname);

	if (!using_volume) {
		log_for_client((void *) c,AFPFSD,LOG_ERR,
			"Volume %s does not exist on server %s.\n",volname,
			server->server_name_printable);
		if (server->num_volumes) {
			char names[1024];
			afp_list_volnames(server,names,1024);
			log_for_client((void *)c,AFPFSD,LOG_ERR,
				"Choose from: %s\n",names);
		}
		goto error;
	}

	if (using_volume->mounted==AFP_VOLUME_MOUNTED) {
		log_for_client((void *)c,AFPFSD,LOG_ERR,
			"Volume %s is already mounted on %s\n",volname,
			using_volume->mountpoint);
		goto error;
	}

	if (using_volume->flags & HasPassword) {
		bcopy(volpassword,using_volume->volpassword,AFP_VOLPASS_LEN);
		if (strlen(volpassword)<1) {
			log_for_client((void *) c,AFPFSD,LOG_ERR,"Volume password needed\n");
			goto error;
		}
	}  else memset(using_volume->volpassword,0,AFP_VOLPASS_LEN);

	if (volopen(c,using_volume)) {
		log_for_client((void *) c,AFPFSD,LOG_ERR,"Could not mount volume %s\n",volname);
		goto error;
	}

	using_volume->server=server;

	return using_volume;
error:
	return NULL;
}


static struct libafpclient client = {
	.unmount_volume = fuse_unmount_volume,
	.log_for_client = fuse_log_for_client,
	.forced_ending_hook =fuse_forced_ending_hook,
	.scan_extra_fds = fuse_scan_extra_fds};

int fuse_register_afpclient(void)
{
	libafpclient_register(&client);
	return 0;
}



