/* unlink.c: by ERS. This routine is in the public domain */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <osbind.h>
#include <unixlib.h>
#include <support.h>
#include <fcntl.h>
#include <string.h>
#include "symdir.h"
#include "lib.h"

#define error(x) ((errno = (x)), -1)
__EXTERN char *strdup __PROTO((const char *s));

/* remove provided for ansi compatibility */
#ifndef __GNUC__
int remove(filename)
	const char *filename;
{
	return unlink(filename);
}
#endif

static struct delete {
	char *fname;
	struct delete *next;
} *D = (struct delete *)NULL;

static void
do_deletes()
{
	struct delete *d;

	d = D;
	while (d) {
	    if(d->fname) {
		(void)Fdelete(d->fname);
		free(d->fname);
	    }
	    d = d->next;
	}
}

#ifdef __GNUC__
asm(".stabs \"_remove\",5,0,0,_unlink"); /* dept of clean tricks */
#endif
int
unlink(filename)
	const char * filename;
{
	char name[FILENAME_MAX];
	char *s;
	int r, fd;
	struct delete *d;
	int nameinfo;
	SYMDIR *dir = 0;
	SYMENTRY *ent = 0, *old;
/*
 * _unx2dos returns _NM_LINK if "filename" was a symbolic link,
 * in which case __link_path[] is the path to the directory containing
 * the link, and __link_name[] is the name of the link.
 */
	nameinfo = _unx2dos(filename, name);

	if (nameinfo == _NM_LINK) {		/* a symbolic link */
		if (!(dir = _read_symdir(__link_path)))
			return -1;
		old = 0; ent = dir->s_dir;
		while (ent) {
			if (!strcmp(ent->linkname, __link_name))
				break;
			old = ent; ent = ent->next;
		}
	}
	else if (_lOK && nameinfo == _NM_CHANGE) {
/* if symlinks are active, insist that the correct file name be given */
		errno = ENOENT;
		return -1;
	}
/*
 * unlink the real file, if no symbolic link was found (ent == 0)
 * or if the link was an automatic link. Unix allows open files
 * to be unlinked; TOS does not, and to get around this we check
 * the names of all the open files; if the one we want is found,
 * we arrange to unlink it on exit.
 */

	if (!ent || (ent->flags & SD_AUTO)) {
		for (fd = 0; fd < __NHANDLES; fd++) {
		    if ((s = __open_stat[fd].filename) && !strcmp(s, name)) {
	/* D == 0 means the atexit routine was not installed yet */
			if (!D && atexit(do_deletes)) {
				_free_symdir(dir);
				return error(ENOMEM);
			}
			d = (struct delete *)malloc(sizeof(struct delete));
			if (!d) {
				_free_symdir(dir);
				return error(ENOMEM);
			}
	/* hook the new "delete" structure into the chain */
			d->fname = strdup(name);
			d->next = D; D = d;
			if(!(d->fname)) {
			    /* note we link it in and then check, as
			       we cannot undo the atexit(), (nor do we
			       want to because there maybe others on the
			       chain already). in do_deletes we'll
			       just skip the null name entries */
			    _free_symdir(dir);
			    return error(ENOMEM);
			}
			
	/* if there was an automatic link, delete it now */
			if (ent)
				goto unlink_sym;
			return 0;
		    }
		}
		r = (int)Fdelete(name);
	}
/*
 * watch out for delete errors; if we have an auto link to a write
 * protected file, preserve the link; but if the auto link points
 * to a non-existent file, delete it
 */
	if ( ent && r == -EACCESS) {
		_free_symdir(dir);
		return error(-r);
	}
/*
 * having gotten this far, all that's left is to unlink the symbolic
 * link itself. "old" was set to the previous directory entry; we
 * remove "ent" from the chain, then rewrite the directory.
 */

unlink_sym:

	if (ent) {
		if (old)
			old->next = ent->next;
		else
			dir->s_dir = ent->next;
		r = _write_symdir(__link_path,dir);
		_free_symdir(dir);
	}

	if (r < 0) {
			return error(-r);
	}
	return 0;
}
