/* 
 * Copyright (C) 1999-2004 Joachim Wieland <joe@mcknight.de>
 * 
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 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
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place - Suite 330, Boston, MA 02111, USA.
 */

#include "jftpgw.h"
#include <sys/stat.h>
#include <fcntl.h>
#include <utime.h>

#define INFO_SUFFIX ".info"

extern struct hostent_list* hostcache;

/* only the user should be able to read/write the cache */
int cache_perms = S_IRWXU;

int cache_available(struct cache_filestruct);
int cache_readinfo(struct cache_filestruct*);
int cache_writeinfo(struct cache_filestruct);
int cache_want(struct cache_filestruct);
char* cache_qualifypath(const struct cache_filestruct);
char* cache_qualifyfile(const struct cache_filestruct);
char* cache_qualifyinfo(const struct cache_filestruct);
int recursive_mkdir(const char* pathname, int perms);


struct cache_filestruct cache_gather_info(const char* filename,
						struct clientinfo* clntinfo) {
	char* pwd;
	char* complete_fname;
	size_t size;
	struct cache_filestruct cfs;

	if (filename[0] != '/') {
		/* get the directory */
		pwd = getftpwd(clntinfo);
	} else {
		pwd = strdup("");
		enough_mem(pwd);
	}
	size = strlen(filename) + 1 + strlen(pwd) + 1;
	complete_fname = (char*) malloc(size);
	enough_mem(complete_fname);
	complete_fname = rel2abs(filename, pwd, complete_fname, size);
	if ((char*) 0 == complete_fname) {
		jlog(4, "Error in rel2abs: filename: %s, path: %s",
				filename, pwd);
	}
	free(pwd);
	cfs.filepath = extract_path(complete_fname);
	cfs.filename = extract_file(complete_fname);
	cfs.size = getftpsize(complete_fname, clntinfo);
	cfs.date = getftpmdtm(complete_fname, clntinfo);
	free(complete_fname);
	/* filename is not free()ed, it points inside args and thus inside
	 * buffer in cmds.c */

	cfs.user = clntinfo->user;
	cfs.host = clntinfo->destination;
	cfs.port = clntinfo->destinationport;

	return cfs;
}

int cache_available(struct cache_filestruct cfs) {
	char* fname = cache_qualifyfile(cfs);
	struct cache_filestruct cfs_info;
	struct stat st;

	if (stat(fname, &st) < 0) {
		if (errno == ENOENT) {
			return CACHE_NOTAVL_EXIST;
		}
		/* other error */
		jlog(2, "Could not stat %s: %s",
				fname, strerror(errno));
		return -1;
	}

	if (cfs.size != st.st_size) {
		/* the new file seems to differ in size -> delete our copy
		 * */
		jlog(8, "cache copy differs in size");
		cache_delete(cfs, 1);
		return CACHE_NOTAVL_SIZE;
	}

	if (cfs.date != st.st_mtime) {
		/* the new file seems to differ in the date -> delete our
		 * copy */
		jlog(8, "cache copy differs in the date");
		cache_delete(cfs, 1);
		return CACHE_NOTAVL_DATE;
	}

	cfs_info = cfs;
	cache_readinfo(&cfs_info);

	if (cfs.checksum != cfs_info.checksum) {
		/* the new file seems to differ in the checksum -> delete
		 * our copy */
		cache_delete(cfs, 1);
		return CACHE_NOTAVL_CHECKSUM;
	}

	return CACHE_AVAILABLE;
}


int cache_add(struct cache_filestruct cfs) {

	struct stat st;
	char* fname = cache_qualifyfile(cfs);
	struct utimbuf ut;

	/* okay, we're sure the path exists, let's try to create a file */

	/* compare size, set date... */

	if (stat(fname, &st) < 0) {
		jlog(2, "Could not stat %s: %s",
				fname, strerror(errno));
		return -1;
	}
	if (st.st_size != cfs.size) {
		jlog(6, "Had to delete %s again. Size did not match", fname);
		cache_delete(cfs, 1);
	}

	/* set the date */
	ut.actime = ut.modtime = cfs.date;
	if (utime(fname, &ut) < 0) {
		jlog(6, "Could net set date/time information to %s: %s",
				fname, strerror(errno));
	}

	return cache_writeinfo(cfs);

}

int cache_writefd(struct cache_filestruct cfs) {
/*
	Add a file to the cache.

	The directory structure is like

	<cache_prefix> / <user>@<host>:<port> / <filepath> / <filename>
*/
	char* path;
	char* filefn;
	int fd;

	if (!cache_want(cfs)) {
		return -1;
	}

	/* just delete for sure, don't care about the return value */
	cache_delete(cfs, 0);

	path = cache_qualifypath(cfs);
	if (recursive_mkdir(path, cache_perms) < 0 && errno != EEXIST) {
		jlog(2, "Could not create directory %s: %s",
			path, strerror(errno));
		return -1;
	}

	filefn = cache_qualifyfile(cfs);
	fd = creat(filefn, cache_perms);
	if (fd < 0) {
		jlog(2, "Could not create data file %s in cache: %s",
				filefn, strerror(errno));
	}
	return fd;
}

int cache_delete(struct cache_filestruct cfs, int warn) {
	char* infofile, *datafile;
	int err = 0;

	infofile = cache_qualifyinfo(cfs);
	datafile = cache_qualifyfile(cfs);

	/*if (unlink(infofile) < 0) {
		jlog(2, "Could not unlink file %s: %s",
				infofile, strerror(errno));
		* do not return immediately, try to delete the other entry,
		 * too *
		err = -1;
	}*/
	if (unlink(datafile) < 0 && warn) {
		jlog(2, "Could not unlink file %s: %s",
				datafile, strerror(errno));
		err = -1;
	}
	return err;
}

int cache_readfd(struct cache_filestruct cfs) {
	char* fname = cache_qualifyfile(cfs);
	int fd;

	if (cache_available(cfs) != CACHE_AVAILABLE) {
		return -1;
	}

	fd = open(fname, O_RDONLY);
	return fd;
}

int cache_want(struct cache_filestruct cfs) {
/*
	See if we want a file to be added to the cache.
*/
	unsigned long int minsize = config_get_size("cacheminsize", 0);
	unsigned long int maxsize = config_get_size("cachemaxsize", ULONG_MAX);

	return (cfs.size <= maxsize && cfs.size >= minsize);
}

int cache_readinfo(struct cache_filestruct *cfs) {
	char* fname = cache_qualifyinfo(*cfs);
	char checksumbuf[128];
	int fdinfo;
	int i;

	return 0; /* not yet implemented */

	if (cfs->checksum) {
		free(cfs->checksum);
		cfs->checksum = (char*) 0;
	}

	fdinfo = open(fname, O_RDONLY);

	if (fdinfo < 0) {
		jlog(2, "Could not open file %s in cache: %s",
				fname, strerror(errno));
		return -1;
	}

	/* file is opened */
	i = read(fdinfo, checksumbuf, sizeof(checksumbuf) - 1);
	if (i < 0) {
		jlog(3, "Could not read info from file %s: %s",
				fname, strerror(errno));
		close(fdinfo);
		return -1;
	}

	if (i > 0 && i < sizeof(checksumbuf)) {
		checksumbuf[i] = '\0';
	}
	cfs->checksum = strdup(checksumbuf);
	close(fdinfo);
	return 0;
}


int cache_writeinfo(struct cache_filestruct cfs) {
	char* fname = cache_qualifyinfo(cfs);
	int fdinfo;
	int i;

	return 0; /* not yet implemented */

	fdinfo = creat(fname, cache_perms);
	if (fdinfo < 0) {
		jlog(2, "Could not create info file %s in cache: %s",
				fname, strerror(errno));
		return -1;
	}

	/* file is created and opened */
	/* write the info */
	i = write(fdinfo, cfs.checksum, strlen(cfs.checksum));
	i += write(fdinfo, "\n", 1);

	close(fdinfo);

	if (i == strlen(cfs.checksum) + 1) {
		return 0;
	} else {
		return -1;
	}
}


char* cache_qualifypath(const struct cache_filestruct cfs) {
	size_t size;
	static char* path;
	const char* hostname;
	unsigned long iaddr;
	const char* cache_prefix = config_get_option("cacheprefix");

	if (!cache_prefix || config_get_bool("cache") == 0) {
		return (char*) 0;
	}
	iaddr = inet_addr(cfs.host);
	if (iaddr == (unsigned long int) UINT_MAX) {
		/* cfs.host was not a valid IP */
		hostname = cfs.host;
	} else {
		/* try to look it up */
		if ( !(hostname = hostent_get_name(&hostcache, iaddr)) ) {
			hostname = cfs.host;
		}
	}

	size =  	  strlen(cache_prefix) + 1
			+ strlen(cfs.user)     + 1
			+ strlen(hostname)     + 1
			+ 20
			+ strlen(cfs.filepath) + 1
			+ 1;

	if (path) {
		path = (char*) realloc(path, size);
	} else {
		path = (char*) malloc(size);
	}
	enough_mem(path);

	snprintf(path, size, "%s/%s@%s:%d/%s",
			cache_prefix,
			cfs.user,
			hostname,
			cfs.port,
			cfs.filepath);

	return path;
}


char* cache_qualifyfile(const struct cache_filestruct cfs) {
	char* path = cache_qualifypath(cfs);
	size_t size = strlen(path) + 1 + strlen(cfs.filename) + 1;
	static char* filename;

	if (filename) {
		filename = (char*) realloc(filename, size);
	} else {
		filename = (char*) malloc(size);
	}
	enough_mem(filename);

	snprintf(filename, size, "%s/%s", path, cfs.filename);

	return filename;
}


char* cache_qualifyinfo(const struct cache_filestruct cfs) {
	char* filename = cache_qualifyfile(cfs);
	size_t size = strlen(filename) + strlen(INFO_SUFFIX) + 1;
	static char* infoname;

	if (infoname) {
		infoname = (char*) realloc(infoname, size);
	} else {
		infoname = (char*) malloc(size);
	}
	enough_mem(infoname);

	snprintf(infoname, size, "%s/%s", filename, INFO_SUFFIX);

	return infoname;
}


int recursive_mkdir(const char* pathname, int perms) {
	char* tocreate;
	int sidx = 0;
	int failed = 0;

	tocreate = (char*) malloc(strlen(pathname) + 1);
	enough_mem(tocreate);

	do {
		while(pathname[sidx] && pathname[sidx] != '/') {
			tocreate[sidx] = pathname[sidx];
			sidx++;
		}
		tocreate[sidx] = pathname[sidx];
		sidx++;
		tocreate[sidx] = '\0';

		jlog(9, "Creating %s\n", tocreate);
		/* If the directory exists, Linux returns EEXIST whereas
		 * *BSD (at least FreeBSD returns EISDIR */
		if (mkdir(tocreate, perms) < 0
				&& errno != EEXIST && errno != EISDIR) {
			failed = -1;
		}
	} while(pathname[sidx] && failed == 0);

	free(tocreate);
	return -failed;
}



syntax highlighted by Code2HTML, v. 0.9.1