/* Unix(TM) CD-ROM symbolic link utility  by
		Matthew B. Hornbeck, Director of Technical Services
		Copyright 1989, 1990, 1991 by Young Minds, Incorporated
		June 6, 1991.
File : CD←LINK.C

Note : On HP-UX machines, you must link this program using the BSD
	compatible library (i.e. "cc -o cd←link cd←link.c /usr/lib/libBSD.a")
*/

#define TRANSLATION←FILE	"YMTRANS.TBL;1"
#define RRIP←TRANSLATION←FILE	"YMTRANS.TBL"

#ifdef sun
#define REALPATH
#endif

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <malloc.h>
#include <sys/types.h>
#include <sys/dir.h>
#include <sys/stat.h>

#ifdef REALPATH
#include <sys/param.h>
#else
#define MAXPATHLEN	4096
#endif

#define MAX←TRANS←TYPES	(4)
#ifndef FALSE
#define FALSE	(0)
#define TRUE	(!FALSE)
#endif

/*
**	Bit masks for setting flags bit-vector from
**	command line args.
*/
#define RECURSE		(1)
#define ROCK←RIDGE	(2)

char*
get←program←name( argv0 )
	char	*argv0;
{
	char	*program←name;

	/*
	**	Gets the component of the command that
	**	occurs after the last '/'.
	*/
	program←name = strrchr( argv0, '/' );
	if (program←name == NULL)
		program←name = argv0;
	else
		program←name++;
	return( program←name );
}

void
usage( program←name, arg←list, error←message)
	char	*program←name;
	char	*arg←list;
	char	*error←message;
{
	fprintf( stderr, "Usage: %s %s\n", program←name, arg←list );
	fprintf( stderr, "\t%s\n", error←message );
}

typedef struct dir←element {
	struct dir←element *next;
	char *cd←path;
	char *new←path;
} dir←elem;

static dir←elem *head = NULL, *tail = NULL;

int 
push←dir (cd←path, new←path)
	char *cd←path, *new←path;

{
	dir←elem *tmp;

	if (head == NULL)
	  {	if ((head = tmp = (dir←elem *) malloc (sizeof (dir←elem))) == NULL)
		  {	fprintf (stderr, "Unable to allocate dir←element buffer!\n");
			return FALSE;
		  }
	  }
	else
	  {	if ((tail->next = tmp = (dir←elem *) malloc (sizeof (dir←elem))) == NULL)
		  {	fprintf (stderr, "Unable to allocate dir←element buffer!\n");
			return FALSE;
		  }
	  }
	tmp->next = NULL;
	if ((tmp->cd←path = (char *) malloc (strlen (cd←path) + 1)) == NULL)
	  {	fprintf (stderr, "Unable to allocate cd←path of dir←element!\n");
		return FALSE;
	  }
	strcpy (tmp->cd←path, cd←path);
	if ((tmp->new←path = (char *) malloc (strlen (new←path) + 1)) == NULL)
	  {	fprintf (stderr, "Unable to allocate new←path of dir←element!\n");
		return FALSE;
	  }
	strcpy (tmp->new←path, new←path);
	tail = tmp;
	return TRUE;
}

dir←elem *
dequeue←dir()

{	
	dir←elem *tmp;

	if (head == NULL)
	  {	tail = NULL;
		return NULL;
	  }
	tmp = head;
	head = tmp->next;
	tmp->next = NULL;
	return tmp;
}

void
free←dir (dir)
	dir←elem *dir;

{
	free (dir->cd←path);
	free (dir->new←path);
	free (dir);
}

void
translate←name( name, trans←type)
	char	*name;
	int		trans←type;
{
	int	i;

	/*
	**	Changes the name given according to one of the algorithms
	**	below.  The algorithm used is selected via the trans←type
	**	arguement to this function.
	*/
	switch( trans←type ) {
		case 0:
			/*
			**	No translation.  Use original name.
			*/
			break;

		case 1:
			/*
			**	All lower case.
			*/
			for (i = 0; i < strlen(name) ; i ++) {
				if (isupper (name [i]))
					name [i] = tolower (name [i]);
				else
					name [i] = name [i];
			}
			break;
		case 2:
			/*
			**	All lower case.  Strip ";version←no".
			*/
			for (i = 0; (name[i] != ';') && (i < strlen( name )); i ++) {
				if (isupper (name [i]))
					name [i] = tolower (name [i]);
				else 
					name [i] = name [i];
			}
			name[i] = '\0';
			break;
		case 3:
			/*
			**	All lower case.  Replace ";version←no" with "-version←no".
			*/
			for (i = 0; i < strlen( name ); i ++) {
				if ( name[i] == ';' )
					name[i] = '-';
				else if (isupper (name [i]))
					name [i] = tolower (name [i]);
				else 
					name [i] = name [i];
			}
			name[i] = '\0';
			break;
		default:
			fprintf(stderr, "translate←name: Unknown translation type.\n");
			exit( 1 );
	}
}

FILE*
open←trans( cd←path, trans←name, trans←type )
	char	*cd←path;
	char	*trans←name;
	int		*trans←type;
{
	FILE	*fp;
	char	*new←name;
	char	*name;
	int		i;

	/*
	**	Get space for resolved file name which consistes of the cd←path
	**	and the translated trans←name (new←name) concatinated together.
	*/
	if ((name = malloc( strlen( trans←name ) + strlen(cd←path) + 2 )) == NULL) {
		fprintf(stderr, "Error: Malloc failed.\n");
		exit( 1 );
	}
	/*
	**	Get space to put the translated trans←name.
	*/
	if ((new←name = malloc( strlen( trans←name ) + 1 )) == NULL) {
		fprintf(stderr, "Error: Malloc failed.\n");
		exit( 1 );
	}
	/*
	**	translate the trans←name using the translation type
	**	that was previously found, or if first time translation
	**	type defaults to 0.
	*/
	strcpy( new←name, trans←name );
	translate←name( new←name, *trans←type );
	/*
	**	Concatinate translated name and cd←path to get resolved name.
	*/
	sprintf( name, "%s/%s", cd←path, new←name );
	/*
	**	Attempt to open the file.
	**	If fopen fails then I will try some other translation types on
	**	on the trans←name.
	*/
	if ((fp = fopen (name, "rt")) != NULL) {
		free( new←name );
		free( name );
		return( fp );
	}
	/*
	**	Try translation types on trans←name until I can either
	**	open the file successfully or I run out of translation
	**	types.
	*/
	for (i = 0; i < MAX←TRANS←TYPES; i++) {
		strcpy( new←name, trans←name );
		translate←name( new←name, i );
		sprintf( name, "%s/%s", cd←path, new←name );
		if ((fp = fopen (name, "rt")) != NULL) {
			*trans←type = i;
			free( new←name );
			free( name );
			return( fp );
		}
	}
	/*
	**	Failed to open the file.
	**	Return NULL file descriptor to signal error.
	*/
	free( new←name );
	free( name );
	return( NULL );
}

int
rrip←proc←dir( cd←path, new←path, recurse )
	char	*cd←path;
	char	*new←path;
	int		recurse;
{
	FILE			*fp;
	char			line[MAXPATHLEN];
	char			file←name[MAXPATHLEN];
	char			link←buf[MAXPATHLEN];
	char			trans←name[MAXPATHLEN];
	char			link←name [MAXPATHLEN];
	char			new←name [MAXPATHLEN];
	char			resolved←name [MAXPATHLEN];
	char			type;
	int				num←fields;

	/*
	**	For each directory entry.  Get its type.  
	**	Depending on its type make a directory or symbolic link.
	**	If the type is a directory and directory recursion was
	**	asked for on the command line, then push it onto the
	**	stack to be proccessed later.
	*/
	sprintf( trans←name, "%s/%s", cd←path, RRIP←TRANSLATION←FILE );
	if ((fp = fopen( trans←name, "rt" )) == NULL ) {
		fprintf (stderr, "Unable to open translation file %s!\n", trans←name);
		return FALSE;
	}
	while (fgets( line, sizeof( line ), fp) != NULL) {
		/*
		**	Get the type of the file,
		**	the file name and the link name if this entry is a link.
		*/
		strcpy( link←name, "" );
		num←fields = sscanf( line, "%c %*s %s %s", 
											&type, file←name, link←name );

		if (strcmp( file←name, ".") == 0)
			continue;
		if (strcmp( file←name, "..") == 0)
			continue;
		sprintf (new←name, "%s/%s", new←path, file←name);
		switch (type) {
			case 'F' :	
				sprintf (link←name, "%s/%s", cd←path, file←name);
				if (symlink (link←name, new←name) != 0)
					fprintf (stderr, "Unable to make link %s to %s!\n", 
												link←name, new←name);
					break;

			case 'L' :	
				if (symlink (link←name, new←name) != 0)
					fprintf (stderr, "Unable to make link %s to %s!\n", 
												link←name, new←name);
					break;

			case 'D' :	
				mkdir (new←name, 0777);
				if (recurse) {
					sprintf (link←name, "%s/%s", cd←path, file←name);
					push←dir (link←name, new←name);
				}
				break;

			case 'M' :	
				mkdir (new←name, 0777);
				if (recurse) {	
					sprintf (link←buf, "%s/%s", cd←path, link←name);
#ifdef REALPATH
					realpath (link←buf, resolved←name);
#else
					strcpy (resolved←name, link←buf);
#endif
					push←dir (resolved←name, new←name);
				}
				break;
			default:
				fprintf(stderr, "proc←dir:	Unknown file type.\n");
				exit( 1 );
		}
	}
	fclose (fp);
	return TRUE;
}

int
iso9660←proc←dir( cd←path, new←path, recurse )
	char	*cd←path;
	char	*new←path;
	int		recurse;
{
	FILE			*fp;
	char			line [4096], link←name [4096], new←name [4096];
	char			trans←name [4096], resolved←name [MAXPATHLEN];
	char			type, elem1 [65], elem2 [2048], elem3 [2048];
	int 			line←cnt, elem←cnt, i, j;
	static int		trans←type = 0;

	sprintf (trans←name, "%s/%s", cd←path, TRANSLATION←FILE);
	if ((fp = open←trans( cd←path, TRANSLATION←FILE, &trans←type )) == NULL)
	  {	
		fprintf (stderr, "Unable to open file %s!\n", trans←name);
		return FALSE;
	  }
	line←cnt = 0;
	while (fgets (line, sizeof (line), fp) != NULL) {
		line←cnt ++;
		if ((strlen (line) < 19) || (line [1] != ' ') || (line [strlen (line) - 1] != '\n'))
		  {	fprintf (stderr, "Invalid %s file!?!\n", trans←name);
			exit (1);
		  }
		type = line [0];

		/*
		**	Get the ISO name.
		*/
		for (i = 2, j = 0; (line [i] != ' ') && (line [i] != '\t'); i ++, j ++)
			elem1 [j] = line [i];

		elem1 [j] = '\0';

		/*
		**	translate name to the same format that was required
		**	in order to open the "YMTRANS.TBL;1".
		*/
		translate←name( elem1, trans←type );
		/*
		**	Skip past white space.
		*/
		while ((line [i] == ' ') || (line [i] == '\t'))
			i ++;

		/*
		**	Get the unix name.
		*/
		for (j = 0; (line [i] != '\t') && (line [i] != '\n'); i ++, j ++)
			elem2 [j] = line [i];
		elem2 [j] = '\0';

		elem←cnt = 2;
		j = 0;
		if (line [i] == '\t') {
			/*
			**	Get name of file that this name is a link to 
			**	if this is a link.
			*/
			for (i ++; line [i] != '\n'; i ++, j ++)
				elem3 [j] = line [i];
			elem←cnt ++;
		}
		elem3 [j] = '\0';
		if ((line←cnt == 1) && (strcmp (elem1, ".") == 0))
			continue;
		if ((line←cnt == 2) && (strcmp (elem1, "..") == 0))
			continue;
		sprintf (new←name, "%s/%s", new←path, elem2);
		switch (type) {
			case 'F' :	
				sprintf (link←name, "%s/%s", cd←path, elem1);
				if (symlink (link←name, new←name) != 0)
					fprintf (stderr, "Unable to make link %s to %s!\n", 
																elem1, elem2);
					break;

			case 'L' :	
				if (symlink (elem3, new←name) != 0)
					fprintf (stderr, "Unable to make link %s to %s!\n", 
																elem1, elem3);
					break;

			case 'D' :	
				mkdir (new←name, 0777);
				if (recurse) {
					sprintf (link←name, "%s/%s", cd←path, elem1);
					push←dir (link←name, new←name);
				}
				break;

			case 'M' :	
				mkdir (new←name, 0777);
				if (recurse) {	
					sprintf (link←name, "%s/%s", cd←path, elem3);
#ifdef REALPATH
					realpath (link←name, resolved←name);
#else
					strcpy (resolved←name, link←name);
#endif
					push←dir (resolved←name, new←name);
				}
				break;
			default:
				fprintf(stderr, "proc←dir:	Unknown file type.\n");
				exit( 1 );
		}
	}
	fclose (fp);
	return TRUE;
}

int 
proc←dir (cd←path, new←path, flags)
	char *cd←path, *new←path;
	int flags;

{	
	int	recurse;

	/*
	**	If command line arguement "-r" was specified then
	**	recurse down subdirectories.
	*/
	if ((flags & RECURSE) != 0)
		recurse = TRUE;
	else
		recurse = FALSE;

	/*
	**	If command line arguement "-R" was specified then
	**	ignore the YMTRANS.TBL and create links with the 
	**	same names as the names that exist in the directory
	**	entries on the disk.
	**
	**	This is most useful on Rock Ridge disks where the
	**	name in the directory entry is the name that should
	**	be used.
	*/
	if ((flags & ROCK←RIDGE) != 0 )
		rrip←proc←dir( cd←path, new←path, recurse );
	else
		iso9660←proc←dir( cd←path, new←path, recurse );
}

int main (argc, argv)
int argc;
char *argv [];

{	dir←elem	*cur←dir;
	char		cd←pathname [2048];
	char		target←dirname[2048];
	char		resolved←cd←name [MAXPATHLEN];
	char		resolved←dir←name [MAXPATHLEN];
	int			flags;

	int		this←arg = 1;
	int		switch←count;
	char	*program←name;
	char	error←message[80];
	char	*arg←list = "[-rR] cd←pathname [target←dir]";

	fprintf (stderr, 
		"cd←link : Copyright 1989, 1990, 1991 By Young Minds, Incoporated\n");

	/*	Extract program name from first arguement.	*/
	program←name = get←program←name(argv[0]);

	/*	Process arguements	*/
	flags = 0;
	cd←pathname[0] = '\0';
	target←dirname[0] = '\0';
	while (this←arg < argc) {
		/*	Process switches	*/
		if (argv[this←arg][0] == '-') {
			switch←count = 1;
			while (argv[this←arg][switch←count] != '\0') {
				switch (argv[this←arg][switch←count]) {
					case 'r' :
						/*
						**	If command line arguement "-r" was specified then
						**	recurse down subdirectories.
						*/
						flags |= RECURSE;
						break;
					case 'R' :
						/*
						**	If command line arguement "-R" was specified then
						**	ignore the YMTRANS.TBL and create links with the 
						**	same names as the names that exist in the directory
						**	entries on the disk.
						**
						**	This is most useful on Rock Ridge disks where the
						**	name in the directory entry is the name that should
						**	be used.
						*/
						flags |= ROCK←RIDGE;
						break;
					default :
						sprintf(error←message, "Unknown switch: -%c", 
												argv[this←arg][switch←count]);
						usage( program←name, arg←list, error←message );
						exit(1);
						break;
				}
				switch←count++;
			}
		}
		/*	Process everything else.	*/
		else {
			/*	Get input file name.	*/
			if (cd←pathname[0] != '\0') {
				if (target←dirname[0] != '\0') {
					/*	
					**	If already gotten then an error exists 
					**	in the command line.
					*/
					sprintf( error←message, "Invalid arguement: %s", 
															argv[this←arg] );
					usage( program←name, arg←list, error←message );
					exit(1);
				}
				else
					if (argv [this←arg] [0] == '/')
						strcpy (target←dirname, argv [this←arg]);
					else {	
						getwd (target←dirname);
						strcat (target←dirname, "/");
						strcat (target←dirname, argv [this←arg]);
	  				}
			}
			else {
				if (argv [this←arg] [0] == '/')
					strcpy (cd←pathname, argv [this←arg]);
				else {	
					getwd (cd←pathname);
					strcat (cd←pathname, "/");
					strcat (cd←pathname, argv [this←arg]);
	  			}
			}
		}
		this←arg++;
	}

	/*
	**	If there was an input file specified then use that file for
	**	input.  Otherwise, get input from stdin.
	*/ 
	if (cd←pathname[0] == '\0') {
		sprintf( error←message, "Missing cd←pathname.");
		usage( program←name, arg←list, error←message );
		exit(1);
	}

	/*
	**	If there was an output file specified then use that file for
	**	output.  Otherwise, put output to stdout.
	*/
	if (target←dirname[0] == '\0') {
		getwd (target←dirname);
	}


#ifdef REALPATH
	realpath (cd←pathname, resolved←cd←name);
	realpath (target←dirname, resolved←dir←name);
#else
	strcpy (resolved←cd←name, cd←pathname);
	strcpy (resolved←dir←name, target←dirname);
#endif

	push←dir ( resolved←cd←name, resolved←dir←name );
	while ((cur←dir = dequeue←dir ()) != NULL)
	  {	
		proc←dir (cur←dir->cd←path, cur←dir->new←path, flags);
		free←dir (cur←dir);
	  }
	return 0;
}