/*
 * Copyright (C) 2003  Serge van den Boom
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 * Nota bene: later versions of the GNU General Public License do not apply
 * to this program.
 *
 * 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-1307  USA
 *
 */

// The GPDir structures and functions are used for caching only.

#ifdef __svr4__
#	define _POSIX_PTHREAD_SEMANTICS
			// For the POSIX variant of readdir_r()
#endif

#include "./stdio.h"

#ifdef WIN32
#	include <io.h>
#else
#	include <sys/stat.h>
#	include <unistd.h>
#	define _POSIX_THREAD_SAFEFUNCTIONS
		// for readdir_r
#	include <dirent.h>
#endif
#include <stdio.h>
#include <sys/types.h>
#include <errno.h>
#include <assert.h>
#include <fcntl.h>
#include <ctype.h>

#include "../uioport.h"
#include "../paths.h"
#include "../mem.h"
#include "../physical.h"
#ifdef uio_MEM_DEBUG
#	include "../memdebug.h"
#endif

static inline uio_GPFile *stdio_addFile(uio_GPDir *gPDir,
		const char *fileName);
static inline uio_GPDir *stdio_addDir(uio_GPDir *gPDir, const char *dirName);
static char *stdio_getPath(uio_GPDir *gPDir);
static stdio_GPDirData *stdio_GPDirData_new(char *name, char *cachedPath,
		uio_GPDir *upDir);
static void stdio_GPDirData_delete(stdio_GPDirData *gPDirData);
static inline stdio_GPDirData *stdio_GPDirData_alloc(void);
static inline void stdio_GPDirData_free(stdio_GPDirData *gPDirData);
static inline stdio_EntriesIterator *stdio_EntriesIterator_alloc(void);
static inline void stdio_EntriesIterator_free(
		stdio_EntriesIterator *iterator);

uio_FileSystemHandler stdio_fileSystemHandler = {
	/* .init    = */  NULL,
	/* .unInit  = */  NULL,
	/* .cleanup = */  NULL,

	/* .mount  = */  stdio_mount,
	/* .umount = */  uio_GPRoot_umount,

	/* .close  = */  stdio_close,
	/* .fstat  = */  stdio_fstat,
	/* .stat   = */  stdio_stat,
	/* .mkdir  = */  stdio_mkdir,
	/* .open   = */  stdio_open,
	/* .read   = */  stdio_read,
	/* .rename = */  stdio_rename,
	/* .rmdir  = */  stdio_rmdir,
	/* .seek   = */  stdio_seek,
	/* .write  = */  stdio_write,
	/* .unlink = */  stdio_unlink,

	/* .openEntries  = */  stdio_openEntries,
	/* .readEntries  = */  stdio_readEntries,
	/* .closeEntries = */  stdio_closeEntries,

	/* .getPDirEntryHandle     = */  stdio_getPDirEntryHandle,
	/* .deletePRootExtra       = */  uio_GPRoot_delete,
	/* .deletePDirHandleExtra  = */  uio_GPDirHandle_delete,
	/* .deletePFileHandleExtra = */  uio_GPFileHandle_delete,
};

uio_GPRoot_Operations stdio_GPRootOperations = {
	/* .fillGPDir         = */  NULL,
	/* .deleteGPRootExtra = */  NULL,
	/* .deleteGPDirExtra  = */  stdio_GPDirData_delete,
	/* .deleteGPFileExtra = */  NULL,
};


void
stdio_close(uio_Handle *handle) {
	int fd;
	int result;
	
	fd = handle->native->fd;
	uio_free(handle->native);
	
	while (1) {
		result = close(fd);
		if (result == 0)
			break;
		if (errno != EINTR) {
			fprintf(stderr, "Warning: Error while closing socket: %s\n",
					strerror(errno));
			break;
		}
	}
}

int
stdio_fstat(uio_Handle *handle, struct stat *statBuf) {
	return fstat(handle->native->fd, statBuf);
}

int
stdio_stat(uio_PDirHandle *pDirHandle, const char *name,
		struct stat *statBuf) {
	char *path;
	int result;

	path = joinPaths(stdio_getPath(pDirHandle->extra), name);
	if (path == NULL) {
		// errno is set
		return -1;
	}

	result = stat(path, statBuf);
	if (result == -1) {
		int savedErrno = errno;
		uio_free(path);
		errno = savedErrno;
		return -1;
	}

	uio_free(path);
	return result;
}

uio_PDirHandle *
stdio_mkdir(uio_PDirHandle *pDirHandle, const char *name, mode_t mode) {
	char *path;
	uio_GPDir *newGPDir;
	
	path = joinPaths(stdio_getPath(pDirHandle->extra), name);
	if (path == NULL) {
		// errno is set
		return NULL;
	}

	if (MKDIR(path, mode) == -1) {
		int savedErrno = errno;
		uio_free(path);
		errno = savedErrno;
		return NULL;
	}
	uio_free(path);

	newGPDir = stdio_addDir(pDirHandle->extra, name);
	uio_GPDir_ref(newGPDir);
	return uio_PDirHandle_new(pDirHandle->pRoot, newGPDir);
}

/*
 * Function name: stdio_open
 * Description:   open a file from a normal stdio environment
 * Arguments:     gPDir - the dir where to open the file
 *                file - the name of the file to open
 *                flags - flags, as to stdio open()
 *                mode - mode, as to stdio open()
 * Returns:       handle, for use in functions accessing the opened file.
 *                If failed, errno is set and handle is -1.
 */
uio_Handle *
stdio_open(uio_PDirHandle *pDirHandle, const char *file, int flags,
		mode_t mode) {
	stdio_Handle *handle;
	char *path;
	int fd;
	
	path = joinPaths(stdio_getPath(pDirHandle->extra), file);
	if (path == NULL) {
		// errno is set
		return NULL;
	}

	fd = open(path, flags, mode);
	if (fd == -1) {
		int save_errno;
		
		save_errno = errno;
		uio_free(path);
		errno = save_errno;
		return NULL;
	}
	uio_free(path);

#if 0
	if (flags & O_CREAT) {
		if (uio_GPDir_getGPDirEntry(pDirHandle->extra, file) == NULL)
			stdio_addFile(pDirHandle->extra, file);
	}
#endif

	handle = uio_malloc(sizeof (stdio_Handle));
	handle->fd = fd;

	return uio_Handle_new(pDirHandle->pRoot, handle, flags);
}

ssize_t
stdio_read(uio_Handle *handle, void *buf, size_t count) {
	return read(handle->native->fd, buf, count);
}

int
stdio_rename(uio_PDirHandle *oldPDirHandle, const char *oldName,
		uio_PDirHandle *newPDirHandle, const char *newName) {
	char *newPath, *oldPath;
	int result;

	oldPath = joinPaths(stdio_getPath(oldPDirHandle->extra), oldName);
	if (oldPath == NULL) {
		// errno is set
		return -1;
	}
	
	newPath = joinPaths(stdio_getPath(newPDirHandle->extra), newName);
	if (newPath == NULL) {
		// errno is set
		uio_free(oldPath);
		return -1;
	}
	
	result = rename(oldPath, newPath);
	if (result == -1) {
		int savedErrno = errno;
		uio_free(oldPath);
		uio_free(newPath);
		errno = savedErrno;
		return -1;
	}

	uio_free(oldPath);
	uio_free(newPath);

	{
		// update the GPDir structure
		uio_GPDirEntry *entry;

		// TODO: add locking
		entry = uio_GPDir_getGPDirEntry(oldPDirHandle->extra, oldName);
		if (entry != NULL) {
			uio_GPDirEntries_remove(oldPDirHandle->extra->entries, oldName);
			uio_GPDirEntries_add(newPDirHandle->extra->entries, newName,
					entry);
		}
	}
		
	return result;
}

int
stdio_rmdir(uio_PDirHandle *pDirHandle, const char *name) {
	char *path;
	int result;

	path = joinPaths(stdio_getPath(pDirHandle->extra), name);
	if (path == NULL) {
		// errno is set
		return -1;
	}
	
	result = rmdir(path);
	if (result == -1) {
		int savedErrno = errno;
		uio_free(path);
		errno = savedErrno;
		return -1;
	}

	uio_free(path);

	uio_GPDir_removeSubDir(pDirHandle->extra, name);

	return result;
}

off_t
stdio_seek(uio_Handle *handle, off_t offset, int whence) {
	return lseek(handle->native->fd, offset, whence);
}

ssize_t
stdio_write(uio_Handle *handle, const void *buf, size_t count) {
	return write(handle->native->fd, buf, count);
}

int
stdio_unlink(uio_PDirHandle *pDirHandle, const char *name) {
	char *path;
	int result;

	path = joinPaths(stdio_getPath(pDirHandle->extra), name);
	if (path == NULL) {
		// errno is set
		return -1;
	}
	
	result = unlink(path);
	if (result == -1) {
		int savedErrno = errno;
		uio_free(path);
		errno = savedErrno;
		return -1;
	}

	uio_free(path);

	uio_GPDir_removeFile(pDirHandle->extra, name);

	return result;
}

uio_PDirEntryHandle *
stdio_getPDirEntryHandle(const uio_PDirHandle *pDirHandle, const char *name) {
	uio_PDirEntryHandle *result;
	const char *pathUpTo;
	char *path;
	struct stat statBuf;
#ifdef WIN32
	char driveName[3];
#endif

#ifdef WIN32
	if (pDirHandle->extra->extra->upDir == NULL) {
		// Top dir. Contains only drive letters and UNC \\server\share
		// parts.
		if (isDriveLetter(name[0]) && name[1] == ':' && name[2] == '\0') {
			driveName[0] = tolower(name[0]);
			driveName[1] = ':';
			driveName[2] = '\0';
			name = driveName;
		} else {
			size_t uncLen;

			uncLen = uio_skipUNCServerShare(name);
			if (name[uncLen] != '\0') {
				// 'name' contains neither a drive letter, nor the
				// first part of a UNC path.
				return NULL;
			}
		}
	}
#endif
	
	result = uio_GPDir_getPDirEntryHandle(pDirHandle, name);
	if (result != NULL)
		return result;

#ifdef WIN32
	if (pDirHandle->extra->extra->upDir == NULL) {
		// Need to create a 'directory' for the drive letter or UNC
		// "\\server\share" part.
		// It's no problem if we happen to create a dir for a non-existing
		// drive. It should just produce an empty dir.
		uio_GPDir *gPDir;
		
		gPDir = stdio_addDir(pDirHandle->extra, name);
		uio_GPDir_ref(gPDir);
		return (uio_PDirEntryHandle *) uio_PDirHandle_new(
				pDirHandle->pRoot, gPDir);
	}
#endif
	
	pathUpTo = stdio_getPath(pDirHandle->extra);
	if (pathUpTo == NULL) {
		// errno is set
		return NULL;
	}
	path = joinPaths(pathUpTo, name);
	if (path == NULL) {
		// errno is set
		return NULL;
	}

	if (stat(path, &statBuf) == -1) {
		uio_free(path);
		return NULL;
	}
	uio_free(path);

	if (S_ISREG(statBuf.st_mode)) {
		uio_GPFile *gPFile;
		
		gPFile = stdio_addFile(pDirHandle->extra, name);
		uio_GPFile_ref(gPFile);
		return (uio_PDirEntryHandle *) uio_PFileHandle_new(
				pDirHandle->pRoot, gPFile);
	} else if (S_ISDIR(statBuf.st_mode)) {
		uio_GPDir *gPDir;
		
		gPDir = stdio_addDir(pDirHandle->extra, name);
		uio_GPDir_ref(gPDir);
		return (uio_PDirEntryHandle *) uio_PDirHandle_new(
				pDirHandle->pRoot, gPDir);
	} else {
#ifdef DEBUG
		fprintf(stderr, "Warning: Attempt to access '%s' from '%s', "
				"which is not a regular file, nor a directory.\n", name,
				pathUpTo);
#endif
		return NULL;
	}
}

uio_PRoot *
stdio_mount(uio_Handle *handle, int flags) {
	uio_PRoot *result;
	stdio_GPDirData *extra;
	
	assert (handle == NULL);
	extra = stdio_GPDirData_new(
			uio_strdup("") /* name */,
#ifdef WIN32
			// In MS Windows, full paths start with a drive letter.
			uio_strdup("") /* cached path */,
#else
			uio_strdup("/") /* cached path */,
#endif
			NULL /* parent dir */);

	result = uio_GPRoot_makePRoot(
			uio_getFileSystemHandler(uio_FSTYPE_STDIO), flags,
			&stdio_GPRootOperations, NULL, uio_GPRoot_PERSISTENT,
			handle, extra, 0);

	uio_GPDir_setComplete(result->rootDir->extra, true);

	return result;
}

#ifdef WIN32
stdio_EntriesIterator *
stdio_openEntries(uio_PDirHandle *pDirHandle) {
	const char *dirPath;
	char path[PATH_MAX];
	char *pathEnd;
	size_t dirPathLen;
	stdio_EntriesIterator *iterator;

//	uio_GPDir_access(pDirHandle->extra);

	dirPath = stdio_getPath(pDirHandle->extra);
	if (dirPath == NULL) {
		// errno is set
		return NULL;
	}

	dirPathLen = strlen(dirPath);
	if (dirPathLen > PATH_MAX - 3) {
		// dirPath ++ '/' ++ '*' ++ '\0'
		errno = ENAMETOOLONG;
		return NULL;
	}
	memcpy(path, dirPath, dirPathLen);
	pathEnd = path + dirPathLen;
	pathEnd[0] = '/';
	pathEnd[1] = '*';
	pathEnd[2] = '\0';
	iterator = stdio_EntriesIterator_new(0);
	iterator->dirHandle = _findfirst(path, &iterator->findData);
	if (iterator->dirHandle == 1) {
		if (errno != ENOENT) {
			stdio_EntriesIterator_delete(iterator);
			return NULL;
		}
		iterator->status = 1;
	} else
		iterator->status = 0;
	return iterator;
}
#endif

#ifndef WIN32
stdio_EntriesIterator *
stdio_openEntries(uio_PDirHandle *pDirHandle) {
	const char *dirPath;
	DIR *dirHandle;
	stdio_EntriesIterator *result;

//	uio_GPDir_access(pDirHandle->extra);

	dirPath = stdio_getPath(pDirHandle->extra);
	if (dirPath == NULL) {
		// errno is set
		return NULL;
	}

	dirHandle = opendir(dirPath);
	if (dirHandle == NULL) {
		// errno is set;
		return NULL;
	}
	
	result = stdio_EntriesIterator_new(dirHandle);
	result->status = readdir_r(dirHandle, result->direntBuffer,
			&result->entry);
#ifndef WIN32
#	ifdef DEBUG
	if (result->status != 0) {
		fprintf(stderr, "Warning: readdir_r() failed: %s\n",
				strerror(result->status));
	}
#	endif
#endif
	return result;
}
#endif

// the start of 'buf' will be filled with pointers to strings
// those strings are stored elsewhere in buf.
// The function returns the number of strings passed along, or -1 for error.
// If there are no more entries, the last pointer will be NULL.
// (this pointer counts towards the return value)
int
stdio_readEntries(stdio_EntriesIterator **iteratorPtr,
		char *buf, size_t len) {
	char *end;
	char **start;
	int num;
	const char *name;
	size_t nameLen;
	stdio_EntriesIterator *iterator;

	iterator = *iteratorPtr;

	// buf will be filled like this:
	// The start of buf will contain pointers to char *,
	// the end will contain the actual char[] that those pointers point to.
	// The start and the end will grow towards eachother.
	start = (char **) buf;
	end = buf + len;
	num = 0;
#ifdef WIN32
	for (; iterator->status == 0;
			iterator->status = _findnext(iterator->dirHandle,
			&iterator->findData))
#else
	for (; iterator->status == 0 && iterator->entry != NULL;
			iterator->status = readdir_r(iterator->dirHandle,
			iterator->direntBuffer, &iterator->entry))
#endif
	{
#ifdef WIN32
		name = iterator->findData.name;
#else
		name = iterator->entry->d_name;
#endif
		if (name[0] == '.' &&
				(name[1] == '\0' ||
				(name[1] == '.' && name[2] == '\0'))) {
			// skip directories "." and ".."
			continue;
		}
		nameLen = strlen(name) + 1;
		
		// Does this work with systems that need memory access to be
		// aligned on a certain number of bytes?
		if ((size_t) (sizeof (char *) + nameLen) >
				(size_t) (end - (char *) start)) {
			// Not enough room to fit the pointer (at the beginning) and
			// the string (at the end).
			return num;
		}
		end -= nameLen;
		memcpy(end, name, nameLen);
		*start = end;
		start++;
		num++;
	}
#ifndef WIN32
#	ifdef DEBUG
	if (iterator->status != 0) {
		fprintf(stderr, "Warning: readdir_r() failed: %s\n",
				strerror(iterator->status));
	}
#	endif
#endif
	if (sizeof (char *) > (size_t) (end - (char *) start)) {
		// not enough room to fit the NULL pointer.
		// It will have to be reported seperately the next time.
		return num;
	}
	*start = NULL;
	num++;
	return num;
}

void
stdio_closeEntries(stdio_EntriesIterator *iterator) {
#ifdef WIN32
	_findclose(iterator->dirHandle);
#else
	closedir(iterator->dirHandle);
#endif
	stdio_EntriesIterator_delete(iterator);
}

#ifdef WIN32
stdio_EntriesIterator *
stdio_EntriesIterator_new(long dirHandle) {
	stdio_EntriesIterator *result;

	result = stdio_EntriesIterator_alloc();
	result->dirHandle = dirHandle;
	return result;
}
#else
stdio_EntriesIterator *
stdio_EntriesIterator_new(DIR *dirHandle) {
	stdio_EntriesIterator *result;
	size_t bufferSize;

	result = stdio_EntriesIterator_alloc();
	result->dirHandle = dirHandle;

	// Linux's and FreeBSD's struct dirent are defined with a 
	// maximum d_name field (NAME_MAX).
	// However, POSIX doesn't require this, and in fact
	// at least QNX defines struct dirent with an empty d_name field.
	// Solaris defineds it with a d_name field of length 1.
	// This should take care of it:
	bufferSize = sizeof (struct dirent)
			- sizeof (((struct dirent *) 0)->d_name) + (NAME_MAX + 1);
			// Take the length of the dirent structure as it is defined,
			// subtract the length of the d_name field, and add the length
			// of the maximum length d_name field (NAME_MAX plus 1 for
			// the '\0').
			// XXX: Could this give problems with weird alignments?
	result->direntBuffer = uio_malloc(bufferSize);
	return result;
}
#endif

void
stdio_EntriesIterator_delete(stdio_EntriesIterator *iterator) {
#ifndef WIN32
	uio_free(iterator->direntBuffer);
#endif
	stdio_EntriesIterator_free(iterator);
}

static inline stdio_EntriesIterator *
stdio_EntriesIterator_alloc(void) {
	return uio_malloc(sizeof (stdio_EntriesIterator));
}

static inline void
stdio_EntriesIterator_free(stdio_EntriesIterator *iterator) {
	uio_free(iterator);
}

static inline uio_GPFile *
stdio_addFile(uio_GPDir *gPDir, const char *fileName) {
	uio_GPFile *file;
	
	file = uio_GPFile_new(gPDir->pRoot, NULL,
			uio_gPFileFlagsFromPRootFlags(gPDir->pRoot->flags));
	uio_GPDir_addFile(gPDir, fileName, file);
	return file;
}

// called by fillGPDir when a subdir is found
static inline uio_GPDir *
stdio_addDir(uio_GPDir *gPDir, const char *dirName) {
	uio_GPDir *subDir;

	subDir = uio_GPDir_prepareSubDir(gPDir, dirName);
	if (subDir->extra == NULL) {
		// It's a new dir, we'll need to add our own data.
		uio_GPDir_ref(gPDir);
		subDir->extra = stdio_GPDirData_new(uio_strdup(dirName),
				NULL, gPDir);
		uio_GPDir_setComplete(subDir, true);
				// fillPDir should not be called.
	}
	uio_GPDir_commitSubDir(gPDir, dirName, subDir);
	return subDir;
}

// returns a pointer to gPDir->extra->cachedPath
// pointer should not be stored, the memory it points to can be freed
// lateron. TODO: not threadsafe.
static char *
stdio_getPath(uio_GPDir *gPDir) {
	if (gPDir->extra->cachedPath == NULL) {
		char *upPath;
		size_t upPathLen, nameLen;
	
		if (gPDir->extra->upDir == NULL) {
#ifdef WIN32
			// Drive letter still needs to follow.
			gPDir->extra->cachedPath = uio_malloc(1);
			gPDir->extra->cachedPath[0] = '\0';
#else
			gPDir->extra->cachedPath = uio_malloc(2);
			gPDir->extra->cachedPath[0] = '/';
			gPDir->extra->cachedPath[1] = '\0';
#endif
			return gPDir->extra->cachedPath;
		}
		
		upPath = stdio_getPath(gPDir->extra->upDir);
		if (upPath == NULL) {
			// errno is set
			return NULL;
		}
			
#ifdef WIN32
		if (upPath[0] == '\0') {
			// The up dir is the root dir. Directly below the root dir are
			// only dirs for drive letters. No '/' needs to be attached.
			gPDir->extra->cachedPath = uio_strdup(gPDir->extra->name);
			return gPDir->extra->cachedPath;
		}
#endif
		upPathLen = strlen(upPath);
#ifndef WIN32
		if (upPath[upPathLen - 1] == '/') {
			// should only happen for "/"
			upPathLen--;
		}
#endif
		nameLen = strlen(gPDir->extra->name);
		if (upPathLen + nameLen + 1 >= PATH_MAX) {
			errno = ENAMETOOLONG;
			return NULL;
		}
		gPDir->extra->cachedPath = uio_malloc(upPathLen + nameLen + 2);
		memcpy(gPDir->extra->cachedPath, upPath, upPathLen);
		gPDir->extra->cachedPath[upPathLen] = '/';
		memcpy(gPDir->extra->cachedPath + upPathLen + 1,
				gPDir->extra->name, nameLen);
		gPDir->extra->cachedPath[upPathLen + nameLen + 1] = '\0';
	}
	return gPDir->extra->cachedPath;
}

static stdio_GPDirData *
stdio_GPDirData_new(char *name, char *cachedPath, uio_GPDir *upDir) {
	stdio_GPDirData *result;
	
	result = stdio_GPDirData_alloc();
	result->name = name;
	result->cachedPath = cachedPath;
	result->upDir = upDir;
	return result;
}

static void
stdio_GPDirData_delete(stdio_GPDirData *gPDirData) {
	if (gPDirData->upDir != NULL)
		uio_GPDir_unref(gPDirData->upDir);
	stdio_GPDirData_free(gPDirData);
}

static inline stdio_GPDirData *
stdio_GPDirData_alloc(void) {
	stdio_GPDirData *result;
	
	result = uio_malloc(sizeof (stdio_GPDirData));
#ifdef uio_MEM_DEBUG
	uio_MemDebug_debugAlloc(stdio_GPDirData, (void *) result);
#endif
	return result;
}

static inline void
stdio_GPDirData_free(stdio_GPDirData *gPDirData) {
#ifdef uio_MEM_DEBUG
	uio_MemDebug_debugFree(stdio_GPDirData, (void *) gPDirData);
#endif
	uio_free(gPDirData->name);
	if (gPDirData->cachedPath != NULL)
		uio_free(gPDirData->cachedPath);
	uio_free(gPDirData);
}


