/*
 * File name.c - map full Unix file names to unique 8.3 names that
 * would be valid on DOS.
 *

   Written by Eric Youngdale (1993).

   Copyright 1993 Yggdrasil Computing, Incorporated

   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.  */

static char rcsid[] ="$Id: name.c,v 1.11 1999/03/02 03:41:26 eric Exp $";

#include "config.h"
#include "mkisofs.h"

#include <ctype.h>

extern int allow_leading_dots;
   
/*
 * 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.
 *
 * Notes:	This procedure probably needs to be rationalized somehow.
 *		New options to affect the behavior of this function
 *		would also be nice to have.
 */
int FDECL3(iso9660_file_length,
	   const char*, name, 
	   struct directory_entry *, sresult, 
	   int, dirflag)
{
  char		* c;
  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		  seen_dot	   = 0;
  int		  seen_semic	   = 0;
  int		  tildes	   = 0;

  result = sresult->isorec.name;

  /*
   * For the '.' entry, generate the correct record, and return
   * 1 for the length.
   */
  if(strcmp(name,".") == 0)
    {
      if(result) 
	{
	  *result = 0;
	}
      return 1;
    }

  /*
   * For the '..' entry, generate the correct record, and return
   * 1 for the length.
   */
  if(strcmp(name,"..") == 0)
    {
      if(result) 
	{
	  *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')) )
    {
      c = last_dot;
      *c = '\0';
      last_dot = strrchr (pnt,'.');
      *c = '.';
    }

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

      /*
       * This character indicates a Unix style of backup file
       * generated by some editors.  Lower the priority of
       * the file.
       */
      if(*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(*pnt == '~') 
	{
	  priority = 1; 
	  tildes++; 
	  pnt++; 
	  continue;
	}

      /*
       * 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 version
       * number is following. [VK]
       */
       if(*pnt == ';')
	 {
	   /* [VK] */
	   if (pnt[1] != '\0' && (pnt[1] < '0' || pnt[1] > '9'))
	     {
	       pnt++;
	       ignore++;
	       continue;
	     }
	 }

      /*
       * If we have a name with multiple '.' characters, we ignore everything
       * after we have gotten the extension.
       */
      if(ignore) 
	{
	  pnt++; 
	  continue;
	}

      /*
       * Spin past any iso9660 version number we might have.
       */
      if(seen_semic)
	{
	  if(*pnt >= '0' && *pnt <= '9') 
	    {
	      *result++ = *pnt;
	    }
	  extra++;
	  pnt++;
	  continue;
	}

	/*
	 * 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) 
	{
	  /* Here we allow a more relaxed syntax. */
	  if(*pnt == '.') 
	    {
	      if (seen_dot) 
		{
		  ignore++; 
		  continue;
		}
	      seen_dot++;
	    }
	  if(current_length < 30) 
	    {
	      if( *pnt < 0 )
		{
		  *result++ = '_';
		}
	      else
		{
		  *result++ = (islower((unsigned char)*pnt) ? toupper((unsigned char)*pnt) : *pnt);
		}
	    }
	}
      else
	{ 
	  /* 
	   * Dos style filenames.  We really restrict the
	   * names here.
	   */
	  /* It would be nice to have .tar.gz transform to .tgz,
	   * .ps.gz to .psz, ...
	   */
	  if(*pnt == '.') 
	    {
	      if (!chars_before_dot && !allow_leading_dots) 
		{
		  /* DOS can't read files with dot first */
		  chars_before_dot++;
		  if (result) 
		    {
		      *result++ = '_'; /* Substitute underscore */
		    }
		}
	      else if( pnt != last_dot )
		{
		  /*
		   * If this isn't the dot that we use for the extension,
		   * then change the character into a '_' instead.
		   */
		  if(chars_before_dot < 8) 
		    {
		      chars_before_dot++;
		      if(result) 
			{
			  *result++ = '_';
			}
		    }
		}
	      else 
		{
		  if (seen_dot) 
		    {
		      ignore++; continue;
		    }
		  if(result) 
		    {
		      *result++ = '.';
		    }
		  seen_dot++;
		}
	    }
	  else 
	    {
	      if(    (seen_dot && (chars_after_dot < 3) && ++chars_after_dot)
		     || (!seen_dot && (chars_before_dot < 8) && ++chars_before_dot) )
		{
		  if(result) 
		    {
		      switch (*pnt) 
			{
			default:
			  if( *pnt < 0 )
			    {
			      *result++ = '_';
			    }
			  else
			    {
			      *result++ = islower((unsigned char)*pnt) ? toupper((unsigned char)*pnt) : *pnt;
			    }
			  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 '|':
			  /* Hmm - what to do here?  Skip?
			   * Win95 looks like it substitutes '_'
			   */
			  *result++ = '_';
			  break;
			} /* switch (*pnt) */
		    } /* if (result) */
		} /* if (chars_{after,before}_dot) ... */
	    } /* else *pnt == '.' */
	} /* else DOS file names */
      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 (result) *result++ = '.'; 
	  extra++;
	}
      if(!omit_version_number && !seen_semic) 
	{
	  if(result)
	    {
	      *result++ = ';';
	      *result++ = '1';
	    };
	  extra += 2;
	}
    }
		    
  if(result) 
    {
      *result++ = 0;
    }
  sresult->priority = priority;

  return (chars_before_dot + chars_after_dot + seen_dot + extra);
}
