/*
**	makelink.c - create filesystem links
**	$VER: makelink.c 42.1 (10.2.96)
**	Copyright © 1996 Michal Letowski
**
**	42.1 (10.2.96) - initial version
*/

#define __USE_SYSBASE

#include <exec/types.h>
#include <exec/execbase.h>
#include <exec/libraries.h>
#include <dos/dos.h>
#include <dos/dosextens.h>
#include <dos/rdargs.h>
#include <support/types.h>
#include <support/dos.h>

#include <string.h>

#include <proto/exec.h>
#include <proto/dos.h>

#include "makelink.rev.h"


/*
**	Constants
*/
#define DOS_NAME			"dos.library"
#define DOS_VERN			37L

#define TEMPLATE			"FROM/A,TO/A,HARD/S,SOFT/S,FORCE/S"

#define TEST_ERROR		-1L
#define TEST_FILE			0L
#define TEST_DIR			1L


/*
**	Private structures
*/
struct Options
{
	STRPTR opt_From;															/* FROM/A */
	STRPTR opt_To;																/* TO/A */
	LBOOL  opt_Hard;															/* HARD/S */
	LBOOL  opt_Soft;															/* SOFT/S */
	LBOOL  opt_Force;															/* FORCE/S */
};	/* Options */


/*
**	Global data
*/
STATIC CONST TEXT VersionString[]=
	VERSION(PROG_NAME,PROG_VERSION,PROG_REVISION,PROG_DATE);


/*
**	Private functions prototypes
*/
STATIC LONG TestLock(struct Options *opts, BPTR lock, struct Library *DOSBase);


/*
**	Public functions
*/
/****** C/MakeLink **********************************************************
*
*   NAME
*       MakeLink -- create filesystem links. (V42)
*
*   SYNOPSIS
*       MakeLink Source Target [HARD] [SOFT] [FORCE]
*
*   TEMPLATE
*       MakeLink "FROM/A,TO/A,HARD/S,SOFT/S,FORCE/S"
*
*   FUNCTION
*       MakeLink creates links to other files or directories. A link is a
*       directory entry that points to other file or directory. There are
*       two kinds of links:
*           hard links - this link points to an already existing file or
*                        directory located on the same partition;
*           soft links - such links may point to any objects (including
*                        devices, volumes, and partitions) and as such are
*                        more flexible.
*       MakeLink creates a Source name link that points to Target. HARD
*       options (default) indicates that it is a hard link. SOFT option
*       indicates a soft link. You can't specify both HARD and SOFT. When
*       creating hard links to directories FORCE option must be given.
*       MakeLink detects circular (i.e. to parent directories) dependencies
*       during creation of hard links and will not create such links.
*
*   INPUTS
*       Source - name of symbolic link to create.
*       Target - name of link target. In case of hard links must be an
*                existing directory or file name.
*       HARD   - indicates hard link (default).
*       SOFT   - indicates soft link.
*       FORCE  - this keyword must appears during creation of hard links to
*                directories, ignored for soft links.
*
*   RESULT
*       RETURN_FAIL  - if 'dos.library' couldn't be opened or command line
*                      arguments couldn't be processed or both HARD and SOFT
*                      options appeared on command line.
*       RETURN_ERROR - circular dependency was detected (ERROR_OBJECT_LINKED),
*                      link couldn't be created or some other error.
*       RETURN_OK    - if a link was created.
*
*   EXAMPLE
*       MakeLink SYS:Utilities/More C:PPMore
*           ; Creates SYS:Utilities/More file that in reality is the C:PPMore.
*       MakeLink DF0.link DF0: SOFT
*           ; Creates DF0.link pseudo-directory which points to a disk in
*           ; DF0: drive.
*       MakeLink Work:Music/Lyrics Work:Docs/Lyrics FORCE
*           ; Creates Work:Music/Lyrics directory which points to
*           ; Work:Docs/Lyrics
*
*   NOTES
*       Links (especially soft) are not fully supported in current AmigaDOS
*       releases. This is why soft links - even pointing to files - will
*       look like directories to most of the programs. Creating soft links
*       to handlers (like SPEAK:) may yield very strange results.
*       MakeLink is pure and can be made resident.
*
*   BUGS
*       In FastFileSystem before version 40 when referencing a soft link it
*       must be the last part of the path. There is also a bug regarding
*       removing the target of the hard link (in FastFileSystem, too).
*
*   SEE ALSO
*       dos.library/MakeLink().
*
*****************************************************************************
*
*/
LONG MakeLinkCom(VOID)
{
	struct ExecBase *SysBase=INITSYSBASE;
	struct Library *DOSBase;

	struct Options Opts;
	struct RDArgs *Args;
	BPTR DstLock;
	LONG LR,RC=RETURN_FAIL;
	
	/* Open DOS */
	unless(DOSBase=OpenLibrary(DOS_NAME,DOS_VERN))
		throw2(SetResult2(ERROR_INVALID_RESIDENT_LIBRARY),	NO_DOS);

	/* Read arguments */
	clear(&Opts);																	/* Clear out buffer */
	unless(Args=ReadArgs(TEMPLATE,(LONG *)&Opts,NULL))
		throw2(PrintFault(IoErr(),PROG_NAME),								NO_ARGS);
	if(Opts.opt_Hard && Opts.opt_Soft)						/* Check options */
		throw2(CauseIoErr(ERROR_TOO_MANY_ARGS,PROG_NAME),		BAD_ARGS);

	RC=RETURN_ERROR;															/* Working a bit... */

	/* Do the job */
	if(Opts.opt_Soft)															/* Softlink */
		if(MakeLink(Opts.opt_From,(LONG)Opts.opt_To,LINK_SOFT))
			RC=RETURN_OK;
		else
			PrintFault(IoErr(),Opts.opt_From);
	else																					/* Hardlink (by default!) */
		if(DstLock=Lock(Opts.opt_To,SHARED_LOCK))		/* Try to lock destination */
		{																						/* Locking successfull */
			LR=TestLock(&Opts,DstLock,DOSBase);				/* Test for circular locks */
			if(LR>0 && !Opts.opt_Force)
				PutStr("Links to directories require use of the FORCE keyword\n");
			elif(LR>=0)
				if(MakeLink(Opts.opt_From,DstLock,LINK_HARD))
					RC=RETURN_OK;													/* Link created - success */
				else																		/* Creation unsuccessfull */
					PrintFault(IoErr(),Opts.opt_From);		/* Print error message */
			UnLock(DstLock);													/* Remove lock */
		}
		else																				/* No lock on destination */
			PrintFault(IoErr(),Opts.opt_To);					/* Inform user */
			
	/* Exceptions */
	catch(BAD_ARGS,	);
	catch(NO_ARGS,	FreeArgs(Args));
	catch(NO_DOS,		CloseLibrary(DOSBase));
	return(RC);
}	/* MakeLinkCom */


/*
**	Private functions
*/
/* Returns -1 if error, 0 if file or 1 if directory */
STATIC LONG TestLock(struct Options *opts, BPTR lock, struct Library *DOSBase)
{
	CHAR Buffer[MAX_PATH_LEN];

	struct FileInfoBlock *FIB;
	BPTR L1,L2;
	LONG RC=TEST_ERROR;															/* To indicate circular link */

	if(FIB=AllocDosObject(DOS_FIB,NULL))						/* Try to allocate FileInfoBlock */
	{																								/* Allocation successfull */
		if(Examine(lock,FIB))													/* Try to examine lock */
			if(FIB->fib_EntryType>=0)										/* Directory check */
			{																						/* It's a directory */
				RC=TEST_DIR;															/* Set result to 'dir' */
				strcpy(Buffer,opts->opt_From);						/* Make a copy of path */
				*PathPart(Buffer)='\0';										/* NULL-terminate path */
				if(L1=Lock(Buffer,SHARED_LOCK))						/* Try to lock FROM path */
					do
					{
						if(SameLock(L1,lock)==LOCK_SAME)			/* Compare lock */
						{																			/* Lock is the same */
							RC=TEST_ERROR;											/* Mark loop detection */
							CauseIoErr(ERROR_OBJECT_LINKED,opts->opt_To);
							L2=NULL;														/* To break loop */
						}
						else																	/* Lock is different */
							L2=ParentDir(L1);										/* Go level up */
						UnLock(L1);														/* Remove old lock */
						L1=L2;																/* Make old lock a new one */
					} while(L1);														/* While valid lock */
				else
					PrintFault(IoErr(),Buffer);							/* Print error if impossible */
			}
			else																				/* It's a file */
				RC=TEST_FILE;															/* Set result to 'file' */
		else																				/* Could not examine */
			PrintFault(IoErr(),opts->opt_To);					/* Print error message */
		FreeDosObject(DOS_FIB,FIB);									/* Free FileInfoBlock */
	}
	else																					/* Allocation unsuccessfull */
		PrintFault(IoErr(),PROG_NAME);							/* Print error message */

	return(RC);
}	/* TestLock */
