/*Copyright (c) Edward Rosten 2005

See file LICENSE for the license 

Set tab stops to 4, if you wish. 
*/

#include <map>
#include <list>
#include <string>
#include <iostream>
#include <sstream>
#include <fstream>
#include <iomanip>
#include <vector>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>

#include <unistd.h>
#include <fcntl.h>
#include <signal.h>

#include <linux/time.h>
#include <linux/coda.h>

#include <gnokii.h>

using namespace std;


//Default values
#define CODA_DEV "/dev/cfs0"
#define TMP_PERM 0600
#define FILE_PERM 0666
#define DIR_PERM 0777
#define CLOSED_FILE_TIMEOUT 600	//After 10 minutes, closed files become stale

#define DEBUG

#if defined  DEBUG || defined DEBUG_OPTIONAL
	#ifdef DEBUG_OPTIONAL
		#define TDEBUG(X) do{if(config.debug)cerr << "Debug (" << __FUNCTION__ << "): " << X << endl;}while(0)
		#define TDEBUGVAR(X) do{if(config.debug)TDEBUG(#X << " = " << X);}while(0)
	#else
		#define TDEBUG(X) do{cerr << "Debug (" << __FUNCTION__ << "): " << X << endl;}while(0)
		#define TDEBUGVAR(X) do{TDEBUG(#X << " = " << X);}while(0)
	#endif
#else
	#define TDEBUG(X) 
	#define TDEBUGVAR(X)
#endif

/******************************************************************************
*
*		Function prototypes, etc
*
*******************************************************************************/

void rmtemp();
static void interrupted(int sig);


string tempfile();


/* Compare function required for stroing in std::map */
struct fid_compare
{
	bool operator()(const struct CodaFid& f1, const struct CodaFid& f2)
	{
		return f1.opaque[0] < f2.opaque[0];
	}
};

void do_options(int argc, char** argv);
void check_error_f(int e, int line);
#define check_error(X) check_error_f(X, __LINE__)


char* access_flags(int flags);
char* open_flags(int flags);
int coda_flags_to_unix_flags(int c);
struct CodaFid gimme_a_fid();


string convert_dir_listing_to_local_file(const vector<string>& listing);

//Gnokii I/O functions
bool is_remote_file_a_file(string fullname);
int get_attributes(CodaFid fid);
int deletefile(string filename);
int putfile(string phone, string real);
int replacefile(string phone, string real);
string fetch_a_file(string fullname);
string convert_remote_dir_to_local_file(string fullname);
static void busterminate(void);
static void businit(void);

/******************************************************************************
*
*		Global data
*
*******************************************************************************/

//Gnokii data
static struct gn_statemachine state;
static gn_data data;
static char *lockfile = NULL;

//Runtime configurable data
struct
{
	bool debug;
	long file_perm, dir_perm, closed_file_timeout;
	string coda_device;
} config;


map<CodaFid, string, fid_compare> phone_files;		//FID in, filename on telephone out
map<CodaFid, bool, fid_compare> is_dir;				//FID in, directoryp out
map<CodaFid, string, fid_compare> local;			//FID in, local filename out
map<CodaFid, coda_vattr, fid_compare> attributes;	//FID in, attributes out
map<CodaFid, int, fid_compare> open_count;			//FID in, number of times file is open
map<CodaFid, time_t, fid_compare> time_of_last_close;	//When was I first opened?

//Default attributes for an ordinary file
static struct coda_vattr default_fatts=
{
	/*.va_type = */C_VREG,
	/*.va_mode =*/ FILE_PERM,
	/*.va_nlink =*/ 1,
	/*.va_uid =*/ getuid(), //Root...
	/*.va_gid =*/ getgid(), 
	/*.va_fileid =*/ 0, //request.coda_getattr.VFid.opaque[0],
	/*.va_size =*/ 0,
	/*.va_blocksize =*/ 1,
	/*.va_atime =*/ {0,0},
	/*.va_mtime =*/ {0,0},
	/*.va_ctime =*/ {0,0},
	/*.va_gen =*/ 0 ,
	/*.va_flags =*/ 0,
	/*.va_rdev =*/  0,
	/*.va_bytes =*/ 0,
	/*.va_filerev =*/ 0
};

//Default attributes for a directory
static struct coda_vattr default_vatts=
{
	/*.va_type = */C_VDIR,
	/*.va_mode =*/ DIR_PERM,
	/*.va_nlink =*/ 1,
	/*.va_uid =*/ getuid(), //Root...
	/*.va_gid =*/ getgid(), 
	/*.va_fileid =*/ 0, //request.coda_getattr.VFid.opaque[0],
	/*.va_size =*/ 0,
	/*.va_blocksize =*/ 1,
	/*.va_atime =*/ {0,0},
	/*.va_mtime =*/ {0,0},
	/*.va_ctime =*/ {0,0},
	/*.va_gen =*/ 0 ,
	/*.va_flags =*/ 0,
	/*.va_rdev =*/  0,
	/*.va_bytes =*/ 0,
	/*.va_filerev =*/ 0
};

/********************************************************************************/

int main(int argc, char** argv)
{
	do_options(argc, argv);

	int fd, e, size;
	bool bus_inited=0;

	char inbuf[VC_MAXMSGSIZE];
	union inputArgs& request=*(union inputArgs*)inbuf;
	union outputArgs reply;

	fd = open(config.coda_device.c_str(), O_RDWR);
	check_error(fd);

	TDEBUG("sizeof(request) = " <<  sizeof(request));
	TDEBUG("Starting loop");

	while(1)
	{
		memset(&request, 0, sizeof(request));
		memset(&reply, 0, sizeof(reply));
		
		TDEBUG("Reading");
		e = read(fd, &request, VC_MAXMSGSIZE); //-1? who knows why
		check_error(e);
		if(e==0)
		{
			TDEBUG("Zero size read!!!");
			continue;
		}
		
		TDEBUG("read " << e << " bytes");
		TDEBUG("opcode = " << request.ih.opcode);
		
		reply.oh.opcode = request.ih.opcode;
		reply.oh.unique = request.ih.unique;
		reply.oh.result = 0;
		struct CodaFid VFid;
		
		size = sizeof(reply.oh);	
	
		switch(request.ih.opcode)
		{
			case CODA_ROOT:

				if(!bus_inited)
				{
					businit();
					bus_inited=true;
				}

				TDEBUG("Command = root");
				reply.coda_root.VFid = gimme_a_fid();
				size = sizeof(reply.coda_root);
				VFid = reply.coda_root.VFid;

				//Build attributes bu hand, since A:\ does not exist.
				phone_files[VFid] = "A:";
				is_dir[VFid] = true;
				attributes[VFid] = default_vatts;

				break;

			case CODA_GETATTR:
				//This is simple: getattr requires a VFID.
				//The only way to get a VFID is through lookup
				//Lookup fetches the attributes.
				VFid = request.coda_getattr.VFid;
				TDEBUG("Command = GetAttr");
				TDEBUG("Attribute of: " <<  phone_files[VFid]);

				reply.coda_getattr.attr = attributes[VFid];

				size = sizeof(coda_getattr_out);
				break;

			case CODA_SETATTR:
				TDEBUG("Command = Setattr");
				TDEBUG("We can't honour this request, but we pretend that we do.");
				break;

			case CODA_ACCESS: 
				//Test for accessability. The answer is yes.
				TDEBUG("Command = Access");
				TDEBUG("Accessing: " <<  phone_files[request.coda_access.VFid]);
				TDEBUG("Flags: " <<  access_flags(request.coda_access.flags));

				size = sizeof(reply.oh);
				reply.oh.result = 0;
				break;

			case CODA_OPEN_BY_FD:
				//We get a VFID (as usual) and reply with a standard file descriptor.
				//The kernel sorts out the rest. We no longer have to worry about closing it.
				VFid = request.coda_open_by_fd.VFid;

				TDEBUG("Command = Open by FD (whatever that means)");
				TDEBUG("Opening file on phone: " << phone_files[VFid]);
				TDEBUG("File is " << (is_dir[VFid]?"": "not ") << "a directory.");
				TDEBUG("Flags: " << open_flags(request.coda_open_by_fd.flags));
			
				size = sizeof(reply.coda_open_by_fd);
				if(is_dir[VFid])
				{
					//Directories are simple.
					string name;
					name = convert_remote_dir_to_local_file(phone_files[VFid]);

					reply.coda_open_by_fd.fd = open(name.c_str(), O_RDONLY);

					if(reply.coda_open_by_fd.fd == -1)
					{
						reply.oh.result = errno;
						break;
					}
				}
				else
				{
					TDEBUG("File is already open " << open_count[VFid] << " time(s)");

					if(open_count[VFid] == 0)
					{
						//If we're opening the file, then we need a local copy
						//first check to see if there is an old local copy
						//I do this because I like using xv. xv opens all the files
						//first, then closes them all and then opens them one
						//by one. Which is slow if you fetch the file every time.
						//This also speeds up image viewing (with your favourite
						//viewer) in general.
						if(local.find(VFid) != local.end())
						{
							TDEBUG("An old local file exists");
							TDEBUGVAR(time_of_last_close[VFid]);
							TDEBUG("Current time = " << time(0));
							TDEBUG("Time since last close = " << time(0) - time_of_last_close[VFid]);
							TDEBUGVAR(config.closed_file_timeout);

							if(time(0) - time_of_last_close[VFid] < config.closed_file_timeout)
							{
								TDEBUG("Using old file.");
								goto use_old;
							}
						}
						
						if(! (request.coda_open_by_fd.flags & C_O_TRUNC))
						{
							TDEBUG("Opening real file non destructively.");

							if(!is_remote_file_a_file(phone_files[VFid]))
							{
								//It can't be a directory because of the preceding logic :-)
								reply.oh.result = ENOENT;
								break;
							}
							
							local[VFid] = fetch_a_file(phone_files[VFid]);
							if(local[VFid] == "")
							{
								//I can't get lookup to respond with this properly yet.
								//So, assume any error is an ENOENT :-/
								reply.oh.result = ENOENT;
								break;
							}
						}
						else
						{	
							//Truncate the file on the phone
							TDEBUG("Destroying remote file, so we don't need to fetch it");
							replacefile(phone_files[VFid], "/dev/null");
							local[VFid] = tempfile();
						}

						use_old:;
					}
						
					//Attempt to open, and reply with the file descriptor
					//We may need to create it if this is the first instance of it.
					reply.coda_open_by_fd.fd = open(local[VFid].c_str(), O_CREAT | coda_flags_to_unix_flags(request.coda_open_by_fd.flags), TMP_PERM);
					if(reply.coda_open_by_fd.fd == -1)
					{
						reply.oh.result = errno;
						break;
					}

					//Update the reference count
					open_count[VFid]++;
				}
				break;
				
			case CODA_STORE:
				//This is called when a process with write access on the file closes the file.
				//Here we upload it. I could do an optimization, whereby the last process on the
				//phone closing the file commits it to the phone (as opposed to all processes), but
				//it's a phone, not a high performance mass storage device.
				VFid = request.coda_store.VFid;
				TDEBUG("Command = store");
				TDEBUG("Storing file to: " << phone_files[VFid]);
				TDEBUG("Storing data from: " << local[VFid]);

				//Overwrite the file on the phone:
				e = replacefile(phone_files[VFid], local[VFid]);


				if(e == -1)
					reply.oh.result = 1;

				break;
			
			case CODA_LOOKUP:
				//we're given a name. Now assign a FID
				{
					char* name =  inbuf + request.coda_lookup.name;
					string fullname = phone_files[request.coda_lookup.VFid] + "\\" + name;
					TDEBUG("Command = Lookup");
					TDEBUG("VFID translates to: " << phone_files[request.coda_lookup.VFid]);
					TDEBUG("Flags: 0x" << setbase(16) <<  request.coda_lookup.flags << setbase(10));
					TDEBUG("Name: " <<  name);
					TDEBUG("Full remote name: " <<  fullname.c_str());

					VFid = gimme_a_fid();
					phone_files[VFid] = fullname;
					int e = get_attributes(VFid);

					TDEBUG("Attributes returned: " << e);
				
					if(e != -1)
						TDEBUG("File is " << (is_dir[VFid]?"": "not ") << "a directory.");
					else
						TDEBUG("File does not exist.");
					
					reply.coda_lookup.VFid = VFid;
					
					if(e == -1)
					{
						reply.coda_lookup.oh.result = ENOENT;
						break;
					}
					if(is_dir[VFid])
						reply.coda_lookup.vtype = C_VDIR;
					else
						reply.coda_lookup.vtype = C_VREG;

					size = sizeof(reply.coda_lookup);

				}
				break;
			
			case CODA_REMOVE:
			{
				string name = inbuf + request.coda_remove.name;
				TDEBUG("Commend = remove");
				TDEBUG("VFID translates to: " << phone_files[request.coda_remove.VFid]);
				TDEBUG("Name is given as:" << name);
				
				string fullname = phone_files[request.coda_remove.VFid] + "\\" + name;
				TDEBUG("Full file name:" << fullname);

				if(deletefile(fullname) == -1)
					reply.oh.result = EPERM;
				break;
			}
				
			case CODA_RELEASE:
				TDEBUG("Command = Release");
				VFid = request.coda_open_by_fd.VFid;
				TDEBUG("Releasing " << phone_files[VFid]);

				if(!is_dir[VFid])
				{
					open_count[VFid] --;
					TDEBUG("Reference count is now " << open_count[VFid]);
					TDEBUG("Current time = " << time(0));
					time_of_last_close[VFid] = time(0);
				}
				else
					TDEBUG("Nothing to do for a directory.");
				
				reply.oh.result = 0;
				break;

			case CODA_CREATE:
			{
				TDEBUG("Command = create");
				string name = inbuf + request.coda_create.name;
				TDEBUG("VFID translates to: " << phone_files[request.coda_create.VFid]);
				TDEBUG("Name is given as:" << name);
				TDEBUG("Name:" << request.coda_create.name);
				
				string fullname = phone_files[request.coda_create.VFid] + "\\" + name;
				TDEBUG("Full file name:" << fullname);
					
				//Create the remote file
				int e = putfile(fullname, "/dev/null");
				reply.oh.result = e;

				if(!e)
				{
					TDEBUG("Creating remote file...");
					VFid = gimme_a_fid();
					
					//Fill in attributes by hand
					phone_files[VFid] = fullname;
					is_dir[VFid] = 0;
					attributes[VFid] = default_fatts;

					//Now attempt to get the attributes from the phone
					//Do we have the correct time/date, etc
					int e = get_attributes(VFid);
					if(e)
						cerr << "Warning: geting attributes from a new file failed!\n";


					reply.coda_create.attr = attributes[VFid];

					reply.coda_create.VFid = VFid;

					size = sizeof(reply.coda_create);
				}
				else
					TDEBUG("Failed to create remote file.");

				break;
			}
				
			
			default:
				TDEBUG("Erk!!!");
				reply.oh.result = EPERM;
		}

		if(size > 0)
		{
			TDEBUG("Replying with a write of size " <<  size);
			TDEBUG("Reply code is: " << reply.oh.result << " (" << strerror(reply.oh.result) << ")");
			e = write(fd, &reply, size);
			check_error(e);
		}
		
		TDEBUG("Processing completed\n\n\n");	
		
	}
}






/* GNOKII library deinitialisztion copied from gnokii.c */

static void busterminate(void)
{
	TDEBUG("Terminating bus");
	gn_sm_functions(GN_OP_Terminate, NULL, &state);
	if (lockfile) gn_device_unlock(lockfile);
}


/* Shutdown cleanly, shudtown functions registered with atexit */

static void interrupted(int sig)
{
	signal(sig, SIG_IGN);
	//We could upload all currently open files. We won't, though.
	//If you want to keep your files, then close them first.
	exit(0);
}


/* GNOKII library initialisation copied from gnokii.c */

static void businit(void)
{
	gn_error error;
	char *aux;

	TDEBUG("Read config file");
	/* Read config file */
	if (gn_cfg_read_default() < 0) 
	{
		cerr << "Failed to read config.\n";
		exit(1);
	}
	if (!gn_cfg_phone_load("", &state)) exit(1);

	signal(SIGINT, interrupted);
	gn_data_clear(&data);

	TDEBUG("Bus initialization");

	aux = gn_cfg_get(gn_cfg_info, "global", "use_locking");
	/* Defaults to 'no' */
	if (aux && !strcmp(aux, "yes")) {
		lockfile = gn_device_lock(state.config.port_device);
		if (lockfile == NULL) 
		{
			cerr << "Lock file error. Exiting.\n";
			exit(1);
		}
	}

	/* register cleanup function */
	atexit(busterminate);
	/* signal(SIGINT, bussignal); */

	/* Initialise the code for the GSM interface. */
	error = gn_gsm_initialise(&state);
	if (error != GN_ERR_NONE) {
		cerr << "Telephone interface init failed: " << gn_error_print(error) << endl;
		exit(2);
	}
}

/* Check standard UNIX errors */
void check_error_f(int e, int line)
{
	if(e == -1)
	{
		cerr << "Error: " << strerror(errno) << " on line " <<  line << endl;
		exit(1);
	}
}

/* Convert CODA bitfields in to human readable strings */
char* access_flags(int flags)
{
	static char buf[1024];
	buf[1023] = 0;

	snprintf(buf, 1023, "%s %s %s %s %s", 
				flags & C_A_C_OK ? "write_on_create":  "",
				flags & C_A_R_OK ? "read":  "",
				flags & C_A_W_OK ? "write":  "",
				flags & C_A_X_OK ? "execute":  "",
				flags & C_A_F_OK ? "existence":  "");
	
	return buf;
}


/* Convert CODA bitfields in to human readable strings */
char* open_flags(int flags)
{
	static char buf[1024];
	buf[1023] = 0;

	snprintf(buf, 1023, "%s %s %s %s %s", 
				flags & C_O_READ ? "read" : "",
				flags & C_O_WRITE ? "write" : "",
				flags & C_O_TRUNC ? "truncate" : "",
				flags & C_O_EXCL ? "exclusive" : "",
				flags & C_O_CREAT ? "creat(e)" : "");
	
	return buf;
}

int coda_flags_to_unix_flags(int c)
{
	int o=0;
	
	if(c & C_O_READ && c & C_O_WRITE)
		o = O_RDWR;
	else if(c & C_O_READ)
		o = O_RDONLY;
	else if(c & C_O_WRITE)
		o = O_WRONLY;

	if(c & C_O_TRUNC)
		o |= O_TRUNC;
	
	return o;
}

/* Generate a unique FID :-) */
struct CodaFid gimme_a_fid()
{
	static int id = 0;

	struct CodaFid	a_fid = 
	{
		{id++,0xfffffff,0x01234567,0xfffffff }
	};

	return a_fid;
}

/*******************************************************************************
Code for dealing with temp files (including cleanup) */

list<string> tempfiles;

string tempfile()
{
	static string root;
	static int num=0;
	if(num == 0)
	{
		if(getenv("TMPDIR") == NULL)
			root = "/tmp/";
		else
			root = getenv("TMPDIR") + string("/");

		atexit(rmtemp);
	}

	ostringstream buf;

	buf << root << "tmp-" << getpid() << "-" << num++;

	tempfiles.push_back(buf.str());

	return buf.str();
}

void rmtemp()
{
	TDEBUG("Removing temp files...");
	for(list<string>::iterator i=tempfiles.begin(); i != tempfiles.end(); i++)
		unlink(i->c_str());
}


bool is_remote_file_a_file(string fullname)
{
	//OK, how's this for a b0rk3d up hack?
	//	getfilelist A:\an_empty_dir\*
	//returns a list of files, length 0.
	//But:
	// getfilelist A:\a_regular_file\*
	//returns a list of files with a single entry of length 0.
	// hee, hee, hee

	//This function returns 0 for a directory or ENOENT, 1 for a file.

 	gn_file_list fi;
	gn_error error;

	fullname += "\\*";

	TDEBUG("Attempting to get a directory listing for " << fullname);

	gn_data_clear(&data);
	data.file_list = &fi;

	memset(&fi, 0, sizeof(fi));
	snprintf(fi.path, sizeof(fi.path)-1, "%s", fullname.c_str());

	if ((error = gn_sm_functions(GN_OP_GetFileList, &data, &state)) != GN_ERR_NONE)
	{
		TDEBUG("Failed to get info for " << fullname << ":"  <<  gn_error_print(error));
		//An error means it is not there, so it is not a file.
		return false;
	}

	TDEBUGVAR(fi.file_count);

	if(fi.file_count != 1) 
		return false;

	TDEBUGVAR(strlen(fi.files[0]->name));

	if(strlen(fi.files[0]->name) == 0)
		return true;
	else
		return false;
}


// Given a CODA FID, get the file attributes.
int get_attributes(CodaFid fid)
{
	struct coda_vattr vatts;
	
	gn_file_list fi;
	gn_error error;

	memset(&fi, 0, sizeof(fi));
	snprintf(fi.path, sizeof(fi.path)-1, "%s", phone_files[fid].c_str());

	TDEBUG("Getting: " << phone_files[fid]);

	gn_data_clear(&data);
	data.file_list = &fi;

	TDEBUG("File count: " << fi.file_count);

	if ((error = gn_sm_functions(GN_OP_GetFileList, &data, &state)) != GN_ERR_NONE || fi.file_count ==0)
	{
		TDEBUG("Failed to get info: " <<  gn_error_print(error));
		return -1;
	}

	TDEBUG("Got attributes.");

	if(is_remote_file_a_file(phone_files[fid]))
	{
		TDEBUG("Is not a directory.");
		vatts = default_fatts;
		is_dir[fid] = false;
	}
	else
	{
		TDEBUG("Is a directory.");
		vatts = default_vatts;
		is_dir[fid] = true;
	}
	
	vatts.va_size =  fi.files[0]->file_length;

	//Figure out creation time
	struct tm time;
	memset(&time, 0, sizeof(time));	//Some extra fields on some systems?
	time.tm_sec = fi.files[0]->second;
	time.tm_min = fi.files[0]->minute;
	time.tm_hour = fi.files[0]->hour;
	time.tm_mday = fi.files[0]->day;
	time.tm_mon = fi.files[0]->month-1; //Monts start from 0
	time.tm_year = fi.files[0]->year-1900; //Years start from 1900
	time.tm_wday = 0;
	time.tm_yday = 0;
	time.tm_isdst = -1;
	
	//FIXME timezones? The phone can tell us what it thinks the time is
	//Mktime ignores the tm_wday, yday fields
	struct timespec ts=
	{	//Second,      useconds since the Epoch.
		mktime(&time), 0
	};

	vatts.va_ctime = vatts.va_mtime = vatts.va_atime = ts;

	TDEBUGVAR(mktime(&time));	
	TDEBUGVAR(fi.files[0]->name);
	TDEBUGVAR(fi.files[0]->year);
	TDEBUGVAR(fi.files[0]->month);
	TDEBUGVAR(fi.files[0]->day);
	TDEBUGVAR(fi.files[0]->hour);
	TDEBUGVAR(fi.files[0]->minute);
	TDEBUGVAR(fi.files[0]->second);
	TDEBUGVAR(fi.files[0]->file_length);
	TDEBUGVAR(fi.files[0]->togo);
	TDEBUGVAR(fi.files[0]->just_sent);
	TDEBUGVAR(fi.files[0]->name);

	
	free(fi.files[0]);
	attributes[fid] = vatts;

	return 0;
}



//This fetched a named file from the phone and places it in a temporary file. 
//The name of the temporary file is returned.
string fetch_a_file(string fullname)
{
	//Copied from gnokii.c
  	gn_file fi;
	gn_error error;

	memset(&fi, 0, sizeof(fi));
	snprintf(fi.name, 511, "%s", fullname.c_str());

	TDEBUG("Getting file: " << fullname);

	gn_data_clear(&data);
	data.file = &fi;

	if ((error = gn_sm_functions(GN_OP_GetFile, &data, &state)) != GN_ERR_NONE)
	{
		cerr <<"Failed to get file:" << gn_error_print(error) << endl;
		return "";
	}
	
	string name = tempfile();

	int fd = open(name.c_str(), O_TRUNC | O_WRONLY | O_CREAT, TMP_PERM);
	write(fd, fi.file, fi.file_length);
	close(fd);
	
	free(fi.file);
	return name;
}


string convert_dir_listing_to_local_file(const vector<string>& listing)
{
	//Why?
	static int dirfileno = 0;

	string output_file=tempfile();
	TDEBUG("Converting listing.");
	TDEBUGVAR(output_file);

	int fd = open(output_file.c_str(), O_TRUNC | O_WRONLY | O_CREAT, TMP_PERM);
	if(fd == -1)
	{
		cerr << "Failed to create tempfile for directory listing: " << strerror(errno) << endl;
		return "";
	}

	struct venus_dirent d;
	int bytes = 0;
	memset(&d, 0xFF, sizeof(d)); //Prevent padding being filled up with zeros (end of dir marker)

	
	//First entry is ignored, but we have to have it, so write an empty entry
	d.d_fileno = dirfileno++;
	d.d_type = CDT_REG; //This is ignored
	d.d_namlen = 0;
	d.d_reclen=DIRSIZ(&d);
	write(fd, &d, d.d_reclen);
	bytes += d.d_reclen;

	for(unsigned int i=0;i<listing.size();i++)
	{
		d.d_fileno = dirfileno++;
		d.d_type = CDT_REG; 					//This is ignored

		int len = listing[i].size();
		if(len > CODA_MAXNAMLEN)
			len = CODA_MAXNAMLEN;
		d.d_namlen = len;
		
		memcpy(d.d_name, listing[i].c_str(), len);
		d.d_reclen=DIRSIZ(&d);

		write(fd, &d, d.d_reclen);
		bytes += d.d_reclen;

		TDEBUG("File name: " << listing[i]);
	}

	//End of dir
	d.d_type = 0;
	d.d_namlen = 0;
	//Magic 12? Why?
	write(fd, &d, 12); 
	
	close(fd);

	return output_file;
}

//Fetch a remote directory listing and place it in a file that coda can
//understand. The filename is returned.
string  convert_remote_dir_to_local_file(string fullname)
{
	//Make data for a coda directory if necessary
	TDEBUG("Getting listing.");
////copy from gnokii.c

 	gn_file_list fi;
	gn_error error;
	int i;

	fullname += "\\*";


	memset(&fi, 0, sizeof(fi));
	snprintf(fi.path, sizeof(fi.path)-1, "%s", fullname.c_str());

	TDEBUG("Getting: " << fullname);

	gn_data_clear(&data);
	data.file_list = &fi;

	if ((error = gn_sm_functions(GN_OP_GetFileList, &data, &state)) != GN_ERR_NONE)
	{
		cerr << "Failed to get info for " << fullname << ":"  <<  gn_error_print(error) << endl;
		return "";
	}


	TDEBUG("File count in directory is: " << fi.file_count);
	vector<string> listing;

	for(i=0;i<fi.file_count;i++)
	{
		TDEBUG("File name: " << fi.files[i]->name);
		listing.push_back(fi.files[i]->name);
		free(fi.files[i]);
	}

	return convert_dir_listing_to_local_file(listing);
}

//Delete a remote file
int deletefile(string filename)
{
	gn_file fi;
	gn_error error;

	TDEBUG("Deleteing: " << filename);

	memset(&fi, 0, sizeof(fi));
	snprintf(fi.name, 511, "%s", filename.c_str());

	gn_data_clear(&data);
	data.file = &fi;

	if ((error = gn_sm_functions(GN_OP_DeleteFile, &data, &state)) != GN_ERR_NONE)
	{
		cerr << "Failed to delete:" << gn_error_print(error) << endl;;
		return -1;
	}

	TDEBUG("Delete sucessful.");

	return 0;
}




int putfile(string phone, string real)
{
	//NB this function only overwrites existing files. It does not
	//truncate then first.
	gn_file fi;
	gn_error error;

	TDEBUG("Putting file " << real << " to " << phone);

	memset(&fi, 0, sizeof(fi));
	snprintf(fi.name, 511, "%s", phone.c_str());

	gn_data_clear(&data);
	data.file = &fi;

	ifstream f;
	f.open(real.c_str());

	if (!f.good())
	{
		cerr << "Cannot open file: " << real;
		return errno;
	}

	TDEBUG("Reading file in to memory.");

	vector<unsigned char> file_data;
	unsigned char c;
	for(;;)
	{
		c = f.get();
		if(!f.good())
			break;
		file_data.push_back(c);
	}

	TDEBUG("File size = " << file_data.size());

	fi.file_length = file_data.size();
	fi.file = file_data.size() == 0 ? NULL: &file_data[0];

	if ((error = gn_sm_functions(GN_OP_PutFile, &data, &state)) != GN_ERR_NONE)
	{
		cerr << "Failed to put file:" << gn_error_print(error) << endl;
		return EAGAIN;
	}

	return 0;
}


int replacefile(string phone, string real)
{
	TDEBUG("Replacing file.");
	deletefile(phone);

	if(putfile(phone, real) == -1)
		return -1;

	return 0;
}




void do_options(int argc, char** argv)
{
	TDEBUG("Processing command line arguments.");
	opterr = 0;
	int opt;
	
	config.debug = 0;
	config.file_perm = FILE_PERM;
	config.dir_perm = DIR_PERM;
	config.coda_device = CODA_DEV;
	config.closed_file_timeout = CLOSED_FILE_TIMEOUT;

	char* opts =  "f:d:t:c:hD";
	char* prog = strrchr(argv[0], '/');
	if(!prog)
		prog = argv[0];
	else
		prog++;

	string help = 
" [-f X | -d X | -c X | -t X | -h | -d]\n"
" -f X = file permission bits (use 0nnn for octal)\n"
" -d X = directory permission bits\n"
" -c X = coda device file\n"
" -t X = Time after which closed files are flushed from the cache in seconds.\n"
" -D print very verbose debug messages (only if compiled in)\n"
" -h print this message\n\n";



	while((opt = getopt(argc, argv, opts)) != -1)
	{
		switch(opt)
		{
			case 'f':
				config.file_perm = strtol(optarg, 0, 0);
				TDEBUG("File mode is now: " << setbase(8) << config.file_perm << setbase(10));
				break;
					
			case 'd':
				config.dir_perm = strtol(optarg, 0, 0);
				TDEBUG("Directory mode is now: " << setbase(8) << config.dir_perm << setbase(10));
				break;

			case 't':
				config.closed_file_timeout = strtol(optarg, 0, 0);
				TDEBUG("Old file flushing time is now: " << config.closed_file_timeout << "s");
				break;

			case 'c':
				config.coda_device = optarg;
				TDEBUG("Coda device is now: " << config.coda_device);
				break;

			case 'D':
				config.debug = 1;
				TDEBUG("Debugging output now enabled.");
				break;	

			case 'h':
				cout << prog << help;
				exit(0);

			case '?':
			default:

				if(strchr(opts, opt) == NULL)
					cerr << prog << ": Error: unknown option -" << (char)optopt << endl;
				else
					cerr << prog << ": Error: option -" << (char)optopt << " requires a parameter." << endl;

				cerr << prog << help;
				exit(1);
		}

	}
	if(optind != argc)
	{
		cerr << prog << ": Error: bad argument: " << argv[optind] << endl
			 << prog << help;
		exit(1);
	}

	default_vatts.va_mode = config.dir_perm;
	default_fatts.va_mode = config.file_perm;
}



