/* @(#)name.c	1.28 04/03/05 joerg */
#ifndef lint
static	char sccsid[] =
	"@(#)name.c	1.28 04/03/05 joerg";

#endif
/*
 * File name.c - map full Unix file names to unique 8.3 names that
 * would be valid on DOS.
 *
 *
 * Written by Eric Youngdale (1993).
 * Almost totally rewritten by J. Schilling (2000).
 *
 * Copyright 1993 Yggdrasil Computing, Incorporated
 * Copyright (c) 1999,2000 J. Schilling
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <mconfig.h>
#include "mkisofs.h"
#include <standard.h>
#include <schily.h>
#include <ctype.h>

void	iso9660_check		__PR((struct iso_directory_record *idr,	struct directory_entry *ndr));
int	iso9660_file_length	__PR((const char *name,
					struct directory_entry *sresult,
					int dirflag));

void
iso9660_check(idr, ndr)
	struct iso_directory_record *idr;
	struct directory_entry *ndr;
{
	int	nlen;
	char	schar;
	char	*p;
	char	*np;

	nlen = idr->name_len[0];
	schar = idr->name[nlen];

	if (nlen == 1 && (idr->name[0] == '\0' || idr->name[0] == '\001'))
		return;

	idr->name[nlen] = '\0';		/* Make it null terminated */
	if ((p = strrchr(idr->name, ';')) != NULL) {
		*p = '\0';		/* Strip off old version # */
	}
	iso9660_file_length(idr->name, ndr,
				(idr->flags[0] & ISO_DIRECTORY) != 0);

	if ((np = strrchr(ndr->isorec.name, ';')) != NULL) {
		*np = '\0';		/* Strip off new version # */
	}
	if (strcmp(idr->name, ndr->isorec.name)) {
		if (p)
			*p = ';';	/* Restore old version # */
		if (np)
			*np = ';';	/* Restore new version # */
		errmsgno(EX_BAD,
			"Old session has illegal name '%.*s' length %d\n",
			idr->name_len[0],
			idr->name,
			idr->name_len[0]);
		errmsgno(EX_BAD,
			"New session will use    name '%s'\n",
			ndr->isorec.name);
	}
	if (p)
		*p = ';';		/* Restore old version # */
	if (np)
		*np = ';';		/* Restore new version # */
	idr->name[nlen] = schar;	/* Restore old iso record*/
}

/*
 * Function:	iso9660_file_length
 *
 * Purpose:	Map file name to 8.3 format, return length
 *		of result.
 *
 * Arguments:	name	file name we need to map.
 *		sresult	directory entry structure to contain mapped name.
 *		dirflag	flag indicating whether this is a directory or not.
 *
 * Note:	name being const * is a bug introduced by Eric but hard to
 *		fix without going through the whole source.
 */
int
iso9660_file_length(name, sresult, dirflag)
	const char	*name;			/* Not really const !!! */
	struct directory_entry *sresult;
	int		dirflag;
{
	char		c;
	char		*cp;
	int		before_dot = 8;
	int		after_dot = 3;
	int		chars_after_dot = 0;
	int		chars_before_dot = 0;
	int		current_length = 0;
	int		extra = 0;
	int		ignore = 0;
	char		*last_dot;
	const char	*pnt;
	int		priority = 32767;
	char		*result;
	int		ochars_after_dot;
	int		ochars_before_dot;
	int		seen_dot = 0;
	int		seen_semic = 0;
	int		tildes = 0;

	result = sresult->isorec.name;

	if (sresult->priority)
		priority = sresult->priority;

	/*
	 * For the '.' entry, generate the correct record, and return 1 for
	 * the length.
	 */
	if (strcmp(name, ".") == 0) {
		*result = 0;
		return (1);
	}
	/*
	 * For the '..' entry, generate the correct record, and return 1
	 * for the length.
	 */
	if (strcmp(name, "..") == 0) {
		*result++ = 1;
		*result++ = 0;
		return (1);
	}
	/*
	 * Now scan the directory one character at a time, and figure out
	 * what to do.
	 */
	pnt = name;

	/*
	 * Find the '.' that we intend to use for the extension.
	 * Usually this is the last dot, but if we have . followed by nothing
	 * or a ~, we would consider this to be unsatisfactory, and we keep
	 * searching.
	 */
	last_dot = strrchr(pnt, '.');
	if ((last_dot != NULL) &&
	    ((last_dot[1] == '~') || (last_dot[1] == '\0'))) {
		cp = last_dot;
		*cp = '\0';
		last_dot = strrchr(pnt, '.');
		*cp = '.';
		/*
		 * If we found no better '.' back up to the last match.
		 */
		if (last_dot == NULL)
			last_dot = cp;
	}

	if (last_dot != NULL) {
		ochars_after_dot = strlen(last_dot);	/* dot counts */
		ochars_before_dot = last_dot - pnt;
	} else {
		ochars_before_dot = 128;
		ochars_after_dot = 0;
	}
	/*
	 * If we have full names, the names we generate will not work
	 * on a DOS machine, since they are not guaranteed to be 8.3.
	 * Nonetheless, in many cases this is a useful option.  We
	 * still only allow one '.' character in the name, however.
	 */
	if (full_iso9660_filenames || iso9660_level > 1) {
		before_dot = iso9660_namelen;
		after_dot = before_dot - 1;

		if (!dirflag) {
			if (ochars_after_dot > ((iso9660_namelen/2)+1)) {
				/*
				 * The minimum number of characters before
				 * the dot is 3 to allow renaming.
				 * Let us allow to have 15 characters after
				 * dot to give more rational filenames.
				 */
				before_dot = iso9660_namelen/2;
				after_dot = ochars_after_dot;
			} else {
				before_dot -= ochars_after_dot; /* dot counts */
				after_dot = ochars_after_dot;
			}
		}
	}

	while (*pnt) {
#ifdef VMS
		if (strcmp(pnt, ".DIR;1") == 0) {
			break;
		}
#endif

#ifdef		Eric_code_does_not_work
		/*
		 * XXX If we make this code active we get corrupted direcrory
		 * XXX trees with infinite loops.
		 */
		/*
		 * This character indicates a Unix style of backup file
		 * generated by some editors.  Lower the priority of the file.
		 */
		if (iso_translate && *pnt == '#') {
			priority = 1;
			pnt++;
			continue;
		}
		/*
		 * This character indicates a Unix style of backup file
		 * generated by some editors.  Lower the priority of the file.
		 */
		if (iso_translate && *pnt == '~') {
			priority = 1;
			tildes++;
			pnt++;
			continue;
		}
#endif
		/*
		 * This might come up if we had some joker already try and put
		 * iso9660 version numbers into the file names.  This would be
		 * a silly thing to do on a Unix box, but we check for it
		 * anyways.  If we see this, then we don't have to add our own
		 * version number at the end. UNLESS the ';' is part of the
		 * filename and no valid version number is following.
		 */
		if (use_fileversion && *pnt == ';' && seen_dot) {
			/*
			 * Check if a valid version number follows.
			 * The maximum valid version number is 32767.
			 */
			for (c = 1, cp = (char *)&pnt[1]; c < 6 && *cp; c++, cp++) {
				if (*cp < '0' || *cp > '9')
					break;
			}
			if (c <= 6 && *cp == '\0' && atoi(&pnt[1]) <= 32767)
				seen_semic++;
		}
		/*
		 * If we have a name with multiple '.' characters, we ignore
		 * everything after we have gotten the extension.
		 */
		if (ignore) {
			pnt++;
			continue;
		}
		if (current_length >= iso9660_namelen) {
#ifdef	nono
			/*
			 * Does not work as we may truncate before the dot.
			 */
			error("Truncating '%s' to '%.*s'.\n",
				name,
				current_length, sresult->isorec.name);
			ignore++;
#endif
			pnt++;
			continue;
		}
		/* Spin past any iso9660 version number we might have. */
		if (seen_semic) {
			if (seen_semic == 1) {
				seen_semic++;
				*result++ = ';';
			}
			if (*pnt >= '0' && *pnt <= '9') {
				*result++ = *pnt;
			}
			extra++;
			pnt++;
			continue;
		}

		if (*pnt == '.') {
			if (!allow_multidot) {
				if (strcmp(pnt, ".tar.gz") == 0)
					pnt = last_dot = ".tgz";
				if (strcmp(pnt, ".ps.gz") == 0)
					pnt = last_dot = ".psz";
			}

			if (!chars_before_dot && !allow_leading_dots) {
				/*
				 * DOS can't read files with dot first
				 */
				chars_before_dot++;
				*result++ = '_'; /* Substitute underscore */

			} else if (pnt == last_dot) {
				if (seen_dot) {
					ignore++;
					continue;
				}
				*result++ = '.';
				seen_dot++;
			} else if (allow_multidot) {
				if (chars_before_dot < before_dot) {
					chars_before_dot++;
					*result++ = '.';
				}
			} else {
				/*
				 * If this isn't the dot that we use
				 * for the extension, then change the
				 * character into a '_' instead.
				 */
				if (chars_before_dot < before_dot) {
					chars_before_dot++;
					*result++ = '_';
				}
			}
		} else {
			if ((seen_dot && (chars_after_dot < after_dot) &&
						++chars_after_dot) ||
			    (!seen_dot && (chars_before_dot < before_dot) &&
			    ++chars_before_dot)) {

				c = *pnt;
				if (c & 0x80) {
					/*
					 * We allow 8 bit chars if -iso-level
					 * is at least 4
					 *
					 * XXX We should check if the output
					 * XXX character set is a 7 Bit ASCI
					 * extension.
					 */
					if (iso9660_level >= 4) {
						c = conv_charset(c, in_nls, out_nls);
					} else {
						c = '_';
					}
				} else if (!allow_lowercase) {
					c = islower((unsigned char)c) ?
						toupper((unsigned char)c) : c;
				}
				if (relaxed_filenames) {
					/*
					 * Here we allow a more relaxed syntax.
					 */
					if (c == '/')
						c = '_';
					*result++ = c;
				} else switch (c) {
					/*
					 * Dos style filenames.
					 * We really restrict the names here.
					 */

				default:
					*result++ = c;
					break;

				/*
				 * Descriptions of DOS's 'Parse Filename'
				 * (function 29H) describes V1 and V2.0+
				 * separator and terminator characters. These
				 * characters in a DOS name make the file
				 * visible but un-manipulable (all useful
				 * operations error off.
				 */
				/* separators */
				case '+':
				case '=':
				case '%': /* not legal DOS */
						/* filename */
				case ':':
				case ';': /* already handled */
				case '.': /* already handled */
				case ',': /* already handled */
				case '\t':
				case ' ':
				/* V1 only separators */
				case '/':
				case '"':
				case '[':
				case ']':
				/* terminators */
				case '>':
				case '<':
				case '|':
				/*
				 * Other characters that are not valid ISO-9660
				 * characters.
				 */
				case '!':
/*				case '#':*/
				case '$':
				case '&':
				case '\'':
				case '(':
				case ')':
				case '*':
/*				case '-':*/
				case '?':
				case '@':
				case '\\':
				case '^':
				case '`':
				case '{':
				case '}':
/*				case '~':*/
				/*
				 * All characters below 32 (space) are not
				 * allowed too.
				 */
				case 1: case 2: case 3: case 4:
				case 5: case 6: case 7: case 8:
				/* case 9: */
				case 10: case 11: case 12:
				case 13: case 14: case 15:
				case 16: case 17: case 18:
				case 19: case 20: case 21:
				case 22: case 23: case 24:
				case 25: case 26: case 27:
				case 28: case 29: case 30:
				case 31:

					/*
					 * Hmm - what to do here? Skip? Win95
					 * looks like it substitutes '_'
					 */
					*result++ = '_';
					break;

				case '#':
				case '-':
				case '~':
					/*
					 * Check if we should allow these
					 * illegal characters used by
					 * Microsoft.
					 */
					if (iso_translate)
						*result++ = '_';
					else
						*result++ = c;
					break;
				}	/* switch (*pnt) */
			} else {	/* if (chars_{after,before}_dot) ... */
				pnt++;
				continue;
			}
		}	/* else *pnt == '.' */
		current_length++;
		pnt++;
	}	/* while (*pnt) */

	/*
	 * OK, that wraps up the scan of the name.  Now tidy up a few other
	 * things.
	 * Look for emacs style of numbered backups, like foo.c.~3~.  If we
	 * see this, convert the version number into the priority number.
	 * In case of name conflicts, this is what would end up being used as
	 * the 'extension'.
	 */
	if (tildes == 2) {
		int	prio1 = 0;

		pnt = name;
		while (*pnt && *pnt != '~') {
			pnt++;
		}
		if (*pnt) {
			pnt++;
		}
		while (*pnt && *pnt != '~') {
			prio1 = 10 * prio1 + *pnt - '0';
			pnt++;
		}
		priority = prio1;
	}
	/*
	 * If this is not a directory, force a '.' in case we haven't seen one,
	 * and add a version number if we haven't seen one of those either.
	 */
	if (!dirflag) {
		if (!seen_dot && !omit_period) {
			if (chars_before_dot >= (iso9660_namelen-1)) {
				chars_before_dot--;
				result--;
			}
			*result++ = '.';
			extra++;
		}
		if (!omit_version_number && !seen_semic) {
			*result++ = ';';
			*result++ = '1';
			extra += 2;
		}
	}
	*result++ = 0;
	sresult->priority = priority;

/*#define	DEBBUG*/
#ifdef	DEBBUG
	error("NAME: '%s'\n", sresult->isorec.name);
	error("chars_before_dot %d chars_after_dot %d seen_dot %d extra %d\n",
		chars_before_dot, chars_after_dot, seen_dot, extra);
#endif
	return (chars_before_dot + chars_after_dot + seen_dot + extra);
}
