/*
 * _read_symdir(), _write_symdir(), _free_symdir(): routines for
 * reading/writing the special directories used for symbolic links.
 * If symbolic links were not set active in UNIXMODE, then theses
 * routines are no-ops.
 *
 * Written by Eric R. Smith and placed in the public domain. Use
 * at your own risk.
 */

#include <stdio.h>
#include <osbind.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include "symdir.h"

#ifndef NAME_MAX
#  include <limits.h>
#endif

#ifndef _LIB_NAME_MAX
#  define _LIB_NAME_MAX NAME_MAX
#endif

/*
 * utility routine to provide buffered I/O for _read_symdir()
 */

#define ResetFgetc() Fgetc(-1)

static int Fgetc(fd)
	int fd;
{
	static unsigned char buf[BUFSIZ], *pos;
	static int siz = 0;

	if (fd < 0) {
		siz = 0; pos = buf; return 0;
	}

	if (siz == 0) {
		siz = Fread(fd, BUFSIZ, buf);
		pos = buf;
		if (siz <= 0) {
			siz = 0; pos = buf; return -1;
		}
	}
	siz--;
	return *pos++;
}


/*
 * Routines for keeping a cache of recently used symbolic directories.
 * The last 8 directories accessed are kept cached; this really helps
 * the directory searches in _unx2dos, but does eat some memory.
 *
 * entries are added to the cache by _free_symdir, and removed if they
 * fall off the end, or if they're retrieved by a subsequent _read_symdir.
 *
 * IMPLICIT ASSUMPTION: the same directory is never open more than once.
 */

#define CACHESIZE 8

static SYMDIR *in_cache;

static
SYMDIR *_cache_lookup(pth)
	const char *pth;
{
	SYMDIR **prev = &in_cache, *cur = *prev;

	while (cur) {
		if (!strcmp(cur->s_pth, pth)) {
/* remove from cache */
			*prev = cur->s_nxt;
			cur->s_nxt = 0;
			return cur;
		}
		prev = &cur->s_nxt; cur = *prev;
	}
	return 0;
}

static
void
_cache_add(cur)
	SYMDIR *cur;
{
	SYMDIR *nxt = 0;
	SYMENTRY *dir, *old;

	int count = 0;

	cur->s_nxt = in_cache;
	in_cache = cur;
	while (cur) {
		nxt = cur->s_nxt;
		++count;
		if (count == CACHESIZE)
			cur->s_nxt = 0;
		else if (count > CACHESIZE) {
			dir = cur->s_dir;
			while (dir) {
				old = dir;
				dir = dir->next;
				free(old);
			}
			free(cur->s_pth);
			free(cur);
		}
		cur = nxt;
	}
}

/*
 * free a symbolic directory. Note that _unx2dos is expecting the entries
 * to be accessible, i.e. it knows that we're caching at least 1 directory.
 * If this ever changes, change _unx2dos.
 */

void _free_symdir(dir)
	SYMDIR *dir;
{
	if (dir)
		_cache_add(dir);
}

/*
 * read in the symbolic directory corresponding to "path".
 * the symdir must be removed from the cache, since it may
 * be modified and written back via _write_symdir
 */

SYMDIR *_read_symdir(path)
	char *path;
{
	char dirname[FILENAME_MAX], tmp[2*FILENAME_MAX], *p;
	int fd, c;
	SYMENTRY *old, *new;
	SYMDIR *dir;

/* check that symbolic links are active */
	if (!_lOK) {
		errno = EINVAL;
		return NULL;
	}

	strcpy(dirname, path);
	strcat(dirname, "\\");
	strcat(dirname, _lDIR);

	if (dir = _cache_lookup(dirname))
		return dir;

	ResetFgetc();
	fd = Fopen(dirname, 0);
	if (fd < 0 && fd != -ENOENT) {
		errno = -fd;
		return NULL;
	}

	dir = (SYMDIR *)malloc(sizeof(SYMDIR)+strlen(tmp)+1);
	if (dir == NULL) {
		errno = ENOMEM;
		return dir;
	}
	dir->s_pth = strdup(dirname);
	if (!dir->s_pth)
		return NULL;
	dir->s_nxt = 0;

	old = NULL;
	if (fd == -ENOENT) goto done_directory;

	p = tmp;
	while ( (c = Fgetc(fd)) >= 0 ) {
		if (c == '\r') continue;
		if (c == '\n') {
			*p++ = 0;
			new= (SYMENTRY *)malloc(sizeof(SYMENTRY)+strlen(tmp)+1);
			if (new == 0) break;
			strcpy(new->linkname, tmp);
			new->linkto = new->linkname;
			for (p = new->linkname; *p; p++) {
				if (*p == '\t') {
					*p++ = 0;
					new->linkto = p;
					break;
				}
			}
/*
 * Now we should check for any further fields, such as "flags"
 * it is very important that we save *all* the characters that were given
 * in the flags field, as well as setting the bits we understand.
 * That way, programs remain compatible with future versions of the
 * standard.
 */
			for (;*p;p++) {
				if (*p == '\t') {
					*p++ = 0;
					break;
				}
			}
			new->cflags = p; /* save pointer to flag characters */
			new->flags = 0;
			for (;*p;p++) {
				if (*p == 'A')
					new->flags |= SD_AUTO;
			}
			new->next = old;
			old = new;
			p = tmp;
		}
		else
			*p++ = c;
	}
	(void)Fclose(fd);

done_directory:
	dir->s_dir = old;
	return dir;
}

/*
 * write a symbolic directory out onto a path
 */

int _write_symdir(path, dir)
	char *path;
	SYMDIR *dir;
{
	SYMENTRY *new;
	int fd, r;
	char dirname[FILENAME_MAX];

/* check to see that symbolic links are OK */
	if (!_lOK)
		return -EINVAL;

	strcpy(dirname, path);
	strcat(dirname, "\\");
	strcat(dirname, _lDIR);

	if (dir->s_dir == NULL) {
		(void)Fdelete(dirname);
		return 0;
	}

	fd = Fcreate(dirname, 0);
	if (fd < 0) {
		return fd;
	}

	for (new = dir->s_dir; new; new = new->next) {
		(void)Fwrite(fd, strlen(new->linkname), new->linkname);
		(void)Fwrite(fd, 1L, "\t");
		(void)Fwrite(fd, strlen(new->linkto), new->linkto);
		if (new->cflags[0]) {
			(void)Fwrite(fd, 1L, "\t");
			(void)Fwrite(fd, strlen(new->cflags), new->cflags);
		}
		r = Fwrite(fd, 1L, "\n");
		if (r <= 0) {
			(void)Fclose(fd);
			return r;
		}
	}

	(void)Fclose(fd);
	return 0;
}

/*
 * _symdir_lookup, _make_symlink: utility routines that are needed in
 * various places
 */

/*
 * return the symbolic directory entry for "name", or NULL if it is not
 * found
 */

SYMENTRY *
_symdir_lookup(dir, name)
	SYMDIR *dir;
	const char *name;
{
	SYMENTRY *ent;

	if (!dir) return 0;
	for (ent = dir->s_dir; ent; ent = ent->next) {
		if (!strcmp(ent->linkname, name))
			return ent;
	}
	return 0;
}

/*
 * _make_autolink(dosname, source): make an "automatic" symbolic link,
 * with name "source", to the file whose full canonical dos pathname is in
 * "dosname". Returns 1 if link made, 0 if not. It checks the _lAUTO
 * flag, so parent functions don't have to. Parents are responsible
 * for dealing with any conflicts with existing files.
 */

int _make_autolink(dosname, source)
	char *dosname, *source;
{
	char path[FILENAME_MAX], oldname[_LIB_NAME_MAX];
	char *s, *p;
	SYMDIR *dir;
	SYMENTRY *d;

	if (!_lAUTO)
		return 0;

	strcpy(path, dosname);
	if ((p = strrchr(path, '\\'))) {
		*p++ = 0;
		_dos2unx(p, oldname);
	} else {
		_dos2unx(path, oldname);
		path[0] = 0;
	}

	if (!(dir = _read_symdir(path)))
		return 0;
	d = (SYMENTRY *)
		malloc(sizeof(SYMENTRY)+strlen(source)+strlen(oldname)+4);
	if (d == 0) {
		errno = ENOMEM;
		return -1;
		_free_symdir(dir);
	}
/*
 * now set up the fields; remember to set the character flags (cflags) as well!
 */
	strcpy(d->linkname, source);
	for (s = d->linkname; *s; s++)
		;
	++s;
	d->linkto = s;
	strcpy(s, oldname);
	for (; *s; s++)
		;
	++s;
	strcpy(s, "A");		/* AUTO flag */
	d->cflags = s;
	d->flags = SD_AUTO;
	d->next = dir->s_dir;
	dir->s_dir = d;
	_write_symdir(path, dir);
	return 1;
}

static
void
_del_dir(cur)
	SYMDIR *cur;
{
	SYMENTRY *dir, *old;

	dir = cur->s_dir;
	while (dir) {
		old = dir;
		dir = dir->next;
		free(old);
	}
	free(cur->s_pth);
	free(cur);
}

void
_del_symdir_cache()
{
	SYMDIR *cur = in_cache, *nxt = 0;

	while (cur) {
		nxt = cur->s_nxt;
		_del_dir(cur);
		cur = nxt;
	}
	in_cache = 0;
}
