/*static char *SCCSID = "@(#)dbcs.c	6.2 91/01/14";*/
#define SCCSID "@(#)dbcs.c	6.2 91/01/14"
/*
 *	IBM/Microsoft Confidential
 *
 *	Copyright (c) IBM Corporation 1987, 1989
 *	Copyright (c) Microsoft Corporation 1987, 1989
 *
 *	All Rights Reserved
 *
 */

/*
 *  DBCS.C
 *
 *    DESCRIPTION
 *    
 *      The functions here perform string operations on strings which
 *      may contain DBCS characters.  The sorting order is to consider
 *      all characters as two byte characters, filling the upper byte of
 *      single byte characters with zeroes.  The comparison is on a
 *      USHORT basis then.
 *      
 *
 *    FUNCTIONS DEFINED
 *
 *      StrCmpDBCS ()       --  Compare two DBCS Strings.
 *      StrnCmpDBCS ()      --  Compare the first 'n' bytes of 2 strings.
 *      StrCmpDBCSNoCase () --  Perform a non-case sensitive string compare.
 *      ForwardDirDBCS ()   --  Step over path component
 *      StrSliceDBCS ()     --  Copies chunks of a pathname using DBCS
 *                                  considerations.
 *      CanonFileDBCS ()    --  Copy filename and determine if 8.3
 *      ParentEndLenDBCS () --  Find the offset of the end of parent
 *                                  directory in the full path name, taking
 *                                  into account DBCS.
 *      CleanStringDBCS ()  --  Check for invalid dual byte null.
 *
 *
 *  REVISION HISTORY (in reverse chronological order)
 *  -----------------------------------------------------------------------
 *  4/4/90      Brian Andrew    Code review cleanup.  Cosmetic.
 *
 *  12/14/89    Brian Andrew    Passed extra parameter to CanonFileDBCS
 *
 *  11/13/89    Brian Andrew    Initial Version.
 *
 *  TO DO
 *  -------------------------------------------------------------------------
 */

#define INCL_DOSERRORS
#define INCL_NOPMAPI

#include <os2.h>
#include <fsd.h>
#include <fsh.h>

#include "cdfs.h"
#include "sem.h"
#include "path.h"
#include "vol.h"
#include "file.h"
#include "buffer.h"
#include "mem.h"
#include "heap.h"
#include "message.h"

#ifdef STATS_H
#include "stats.h"
#endif

#include "fsdata.h"
#include "pr_dbcs.h"
#include "pr_strng.h"
#include "stdlib.h"
#include "dbcs.h"

#ifdef DEBUG
#include "pr_debug.h"
#endif

/***    StrCmpDBCS ()   -   Compare two DBCS Strings.
 *      
 *  DESCRIPTION
 *
 *      This function compares two Null terminated strings.  A Null
 *      terminated string has a '\0' byte which is NOT the second
 *      byte of a two byte character.  A dual byte character which
 *      has '\0' as the second byte is considered malformed.
 *      
 *      The characters (not bytes) are compared until either a match
 *      fails or a match of null characters is found.  The return value
 *      indicates if string1 is lexically less than, equal to, or greater
 *      than string2.  The return value is either less than 0, equal to
 *      0, or greater than zero depending on the result of the last 
 *      comparison.
 *      
 *      
 *  ENTRY
 *
 *      pS1     -   Pointer to string 1.
 *      pS2     -   Pointer to string 2.
 *
 *
 *  LOCAL
 *
 *      usC1    -   Character from string 1.
 *      usC2    -   Character from string 2.
 *      sRet    -   Return value from this function.
 *      
 *
 *  EXIT
 *
 *      No changes to strings.
 *
 *
 *  RETURN
 *      < 0 if string1 is "less than" string2.
 *      = 0 if string1 is "equal to" string2.
 *      > 0 if string1 is "greater than" string2.
 */
SHORT StrCmpDBCS (pS1, pS2)
PCHAR   pS1;
PCHAR   pS2;
{
	USHORT  usC1;
	USHORT  usC2;
	SHORT   sRet;

	/* Compare each character in the string */
	do
	{
		/* Get the character from the first string */
		GetCharAndBmpPtr (((PCHAR FAR *) &pS1),
							((PUSHORT) &usC1));

		/* Get the character from the second string */
		GetCharAndBmpPtr (((PCHAR FAR *) &pS2),
							((PUSHORT) &usC2));
		
		/* Compare the characters is the exit test */

	} while (!(sRet = usC1 - usC2) && usC1);

	return sRet;

} /* end StrCmpDBCS */


/***    StrnCmpDBCS ()  --  Compare the first 'n' characters of 2 strings.
 *      
 *  DESCRIPTION
 *
 *      This function compares the first 'sBytes' of two strings.  It will
 *      compare DBCS strings using the current active code page.
 *      Strings are compared by treating all characters as double byte
 *      characters.  Single byte characters are cast to USHORTs to
 *      allow for comparisons to double bytes.
 *
 *      The net effect is that all single byte characters are lexically
 *      less than all double byte characters.  Single byte characters
 *      are ordered according to their position in the code page.  Double
 *      byte characters are sorted first on the leadin bytes and, for
 *      matching leadin bytes, by the trailing byte.
 *
 *      A dual byte character which has '\0' as the second byte is
 *      considered malformed.  The result of a malformed string is
 *      indeterminate.
 *      
 *      
 *  ENTRY
 *
 *      pS1     -   Pointer to first string.
 *      pS2     -   Pointer to second string.
 *      sBytes  -   Maximum number of bytes to compare.
 *
 *
 *  LOCAL
 *
 *      usC1    -   Character from string 1.
 *      usC2    -   Character from string 2.
 *      sRet    -   Return value from this function.
 *      
 *      
 *  EXIT
 *
 *      No changes to strings.
 *
 *
 *  RETURN
 *      
 *      < 0 if pS1 <  pS2
 *      = 0 if pS1 == pS2
 *      > 0 if pS1 >  pS2
 */
SHORT StrnCmpDBCS (pS1, pS2, sBytes)
PCHAR   pS1;
PCHAR   pS2;
SHORT   sBytes;
{
	USHORT  usC1;
	USHORT  usC2;
	SHORT   sRet;

	/* Test for a non-zero length string */
	if (sBytes)
	{
		/*
		 *  Walk through the bytes of the strings, comparing the characters.
		 *  (NOTE: bytes are not the same as characters)
		 */
		do
		{
			/* Get the character from the first string */
			GetCharAndBmpPtr (((UCHAR FAR * FAR *) &pS1),
								((USHORT FAR *) &usC1));

			/* Get the character from the second string and adjust 'sBytes' */
			GetCharBmpAndDec (((UCHAR FAR * FAR *) &pS2),
								&sBytes,
								((USHORT FAR *) &usC2));
		
		/* Compare characters in exit code */		
		} while (!(sRet = usC1 - usC2) && (sBytes > 0) && usC1);

	} /* end if non-zero length strings */

	/* Else first 0 bytes match */
	else
	{
		sRet = 0;
	}

	/* Return the result of the comparison */
	return sRet;

} /* end StrnCmpDBCS */


/***    ForwardDirDBCS ()   --  Step over path component
 *      
 *  DESCRIPTION
 *
 *      This function counts how many bytes to move forward in a string
 *      to reach the end of a directory name in a path name.  This deals
 *      correctly with well-formed DBCS strings using the current
 *      code page.
 *      
 *      
 *  ENTRY
 *
 *      pString -   Pointer to start of string.
 *      cbStr   -   Number of bytes in the full pathname (must end on a
 *                  character boundary).
 *
 *
 *  LOCAL
 *
 *      usCountBytes    -   Count the bytes swallowed in the path name.
 *      usDummyChar     -   Storage for characters in the strings.
 *
 *
 *  EXIT
 *
 *      No changes
 *  
 *  
 *  RETURN
 *
 *      The number of bytes to move to step over the initial directory
 *      name.
 */
USHORT ForwardDirDBCS (pString, cbStr)
PCHAR   pString;
USHORT  cbStr;
{
	USHORT  usCountBytes;
	USHORT  usDummyChar;

	/* Initialize the byte count */
	usCountBytes = 0;

	/*
	 * Walk through the path name until it is exhausted or a separator
	 * is found
	 */
	while (usCountBytes < cbStr)
	{
		/* Check for either the end of a string or a legal separator */
		if ((!(*pString)) || (*pString == '\\') || (*pString == ';') ||
				(*pString == '/'))

			break;

		/* Move through the string */
		GetCharBmpAndInc (((UCHAR FAR * FAR *) &pString),
							&usCountBytes,
							((USHORT FAR *) &usDummyChar));

	} /* end while more bytes */
	
	/*
	 *  Check on off chance that a truncated DBCS has caused
	 *  us to go too far.
	 */
	return (min (usCountBytes, cbStr));

} /* end ForwardDirDBCS */


/***    StrSliceDBCS () -   Copies chunks of a pathname using DBCS
 *                          considerations.
 *      
 *  DESCRIPTION
 *
 *      This functions copies characters from the source string to
 *      the destination string.  It stops when a null character, the
 *      extension marker '.', or the version number marker ';' is reached.
 *      
 *      It simply uses DBCS considerations to move through the string.
 *      
 *      
 *  ENTRY
 *
 *      pDst        -   Pointer to destination string.
 *      pSrc        -   Pointer to source string.
 *      cbStrLen    -   Length of the string.
 *      pcbSwallowed-   Address to store number of bytes swallowed.
 *      
 *
 *  LOCAL
 *
 *      usChar      -   Possibly a DBCS character.
 *
 *
 *  EXIT
 *
 *      pcbSwallowed contains the number of bytes swallowed.
 *
 *
 *  RETURN
 *
 *      Pointer to character in source string after last byte swallowed.
 *      This will the null terminator if the end of the string is reached.
 */
PCHAR StrSliceDBCS (pDst, pSrc, cbStrLen, pcbSwallowed)
PCHAR   pDst;
PCHAR   pSrc;
SHORT   cbStrLen;
PUSHORT pcbSwallowed;
{
	USHORT  usChar;

	/* Initialize the result value */
	*pcbSwallowed = 0;

	/*
	 * Copy characters until we reach a terminating character.
	 * Decrement the count of total bytes in the string, increment
	 * the number of bytes transferred.
	 */

	while ((cbStrLen > 0) && !IsTerminator (pSrc))
	{
		GetCharBmpAndDec (((UCHAR FAR * FAR *) &pSrc),
							&cbStrLen,
							((USHORT FAR *) &usChar));


		PutCharBmpIndex (pDst, pcbSwallowed, usChar);
      
	} /* end while more characters */

	/* Return pointer to next position in the source string */
	return pSrc;

} /* end StrSliceDBCS */


/***    CanonFileDBCS ()    -   Copy filename and determine if 8.3
 *      
 *  DESCRIPTION
 *
 *      This function takes a pointer to a filename string and
 *      determines if it is in DOS 8.3 format.  The entire string
 *      without the version number is copied into the destination
 *      string.  A pointer to the last terminator is returned from
 *      StrSliceDBCS.  If this is a ';' then the version number in
 *      ASCII format is converted to binary.
 *
 *      The filename '\0' for self directory is caught as a special
 *      case.  It is copied directly and then returned.
 *
 *      This function checks that the filename is in valid DBCS form.
 *      We also check that the name length is legal and truncate if
 *      not.
 *
 *      If the filename is of the form <name>.<(;<version> | )>,
 *      meaning that there is no extension after the '.' separator, we
 *      will truncate the '.'
 *
 *
 *  ENTRY
 *
 *      pDst        -   Pointer to area to store processed filename.
 *      pSrc        -   Pointer to source filename string.
 *      psVersion   -   Address to store numeric representation of version.
 *      cbNameLen   -   Total number of bytes in filename.
 *      pusVerLen   -   Pointer to store length of the ASCII form of the
 *                          version number.
 *
 *
 *  LOCAL
 *
 *      cbNamePos   -   Current position in source name.
 *      cbSwallowed -   Number of bytes swallowed in StrSliceDBCS.
 *      fDosFormat  -   Return value, indicates if 8.3 format name.
 *      pcPeriod    -   Pointer to period in destination string.
 *      
 *
 *  EXIT
 *
 *      pDst has the text of the file name.
 *      *psVersion has decimal value of the ASCII version if present.
 *      
 *      
 *  RETURN
 *
 *      Boolean indicating if name matches the MSDOS 8.3 format.
 */
BOOL CanonFileDBCS (pDst, pSrc, psVersion, cbNameLen, pusVerLen)
PCHAR   pDst;
PCHAR   pSrc;
PSHORT  psVersion;
SHORT   cbNameLen;
PUSHORT pusVerLen;
{
	SHORT       cbNamePos;
	SHORT       cbSwallowed;
	BOOL        fDosFormat;
	PUCHAR      pcPeriod;
	USHORT      fInitialExt;

	/* Initialize the local variables and pointer targets */
	cbNamePos = 0;
	*psVersion = NO_VERSION;
	fDosFormat = TRUE;
	fInitialExt = TRUE;
	*pusVerLen = 0;

	/* Special case, check for 00 byte as a file name, or 01 as file name
	 *
	 *      cbNameLen == 1,    *pcSrc = '\0'
	 */

	if ((cbNameLen == 1) && ((!*pSrc) || (*pSrc == '\1')))
	{
		CopyNullTerm (pDst, pSrc, 1, TRUE);

	} /* end if self directory */

	else
	{
		/* Normal filename.ext. Copy what we can of the
		** filename and remember the length.
		*/

		pSrc = StrSliceDBCS (pDst, pSrc, cbNameLen, &cbSwallowed);

		pDst += cbSwallowed;


		/* Check if we have swallowed more than 8 bytes, if
		** so then this name isn't 8.3
		*/
		if (cbSwallowed > 8)
			fDosFormat = FALSE;

		/* Remember how many bytes were swallowed */
		/* Done if at end of word */
		while (TRUE)
		{
			if ((cbNamePos += cbSwallowed) >= cbNameLen)
			{
				break;
			}

			cbSwallowed = 0;

			if (*pSrc == '.')  /* Skip the '.' if we are there */
			{
				/* Remember the position in the destination string */
				pcPeriod = pDst;

				/* Swallow the period */
				*pDst++ = *pSrc++;
				cbNamePos++;


				/* Get the filename extension */
				cbSwallowed = cbNameLen - cbNamePos;
				pSrc = StrSliceDBCS (pDst, pSrc, cbNameLen - cbNamePos,
										&cbSwallowed);

				pDst += cbSwallowed;

				/* If we didn't swallow any characters then move back */
				if (!cbSwallowed)
				{
					pDst = pcPeriod;

				} /* end if null extension */


				/* Check if we have swallowed more than 3 bytes, if
				** so then this can't be 8.3 format.
				*/
				if (fInitialExt)
				{
					fInitialExt = FALSE;
					if (cbSwallowed > 3)
					{
						fDosFormat = FALSE;
					}
				}

			} /* end filename extension */

			/* Now check for version number */
			else
			{
				/* step over ';',  it must be the next character */
				pSrc++;
				cbNamePos++;

				*psVersion = CopyVersionToNumber (pDst, pSrc,
													cbNameLen - cbNamePos,
													pusVerLen);

				/* Move the destination pointer over the versions */
				if (*pusVerLen)
				{
					/* Allow one character for the ';' character */
					pDst += (*pusVerLen + 1);
				}

				break;

			} /* end file version name */

		} /* end while not at end of word */

		/* Null terminate the destination */
		*pDst = '\0';

	} /* end else not self directory */

	return fDosFormat;

} /* end CanonFileDBCS */


/***    ParentEndLenDBCS () -   Find the offset of the end of parent
 *                              directory in the full path name, taking
 *                              into account DBCS.
 *      
 *      
 *  DESCRIPTION
 *      
 *          Repeatedly calls 'ForwardDirDBCS' to step over the different
 *          components in the pathname.  We do it this way simply to
 *          avoid encountering DBCS characters in reverse.
 *      
 *
 *  ENTRY
 *
 *      pString     --  Pointer to the start of a path name.  It points
 *                      to the first letter of a directory name, not
 *                      the leading '\' character or any drive letter.
 *
 *      cbNameLen   --  Number of bytes in the string.
 *      
 *
 *  LOCAL
 *
 *      cbRelJmp    --  Count the total number of characters stepped over
 *
 *      cbThisJmp   --  Number of characters stepped over this last
 *                      jump.
 *      
 *      usOffset    --  Adjustment for trailing '/' character in name.
 *      
 *      
 *  EXIT
 *
 *      Nothing
 *      
 *      
 *  RETURN
 *      
 *      Return the total number of characters to step over to reach the
 *      first character after the full path of the parent directory.
 *      Since we are always given a full path with a drive, we always
 *      have to adjust the cbRelJmp down 1 to account for stepping over
 *      the previous '\'.
 */
USHORT ParentEndLenDBCS (pString, cbStrLen)
PCHAR   pString;
USHORT  cbStrLen;
{
	USHORT  cbRelJmp;
	USHORT  cbThisJmp;
	USHORT  usOffset;

	/* Initialize */
	cbRelJmp = 0;
	usOffset = 0;

	/* Loop through the full path name */
	while (TRUE)
	{
		cbThisJmp = ForwardDirDBCS (pString, cbStrLen - cbRelJmp);

		/* If this jump puts us at the end of the string, or points to a
		 * ';', then we are done.
		 */

		if (((cbRelJmp + cbThisJmp) == cbStrLen) ||
			(*(pString + cbThisJmp) == ';'))
		{
			break;

		} /* end if done */

		/* Else update the variables */
		usOffset = 1;
		cbRelJmp += cbThisJmp;
		pString += cbThisJmp;

		/* Step over the separator */
		pString++;
		cbRelJmp++;

	} /* end while */

	return (cbRelJmp - usOffset);

} /* end ParentEndLenDBCS */


/***    CleanStringDBCS ()  --  Check for invalid dual byte null.
 *      
 *      
 *  DESCRIPTION
 *      
 *          This function walks through a string with a given length.
 *          The string probably has a null byte at position 'cbLen' but we
 *          don't make that assumption.  We walk through the string up to
 *          that point, if we discover a null byte before that we return
 *          the length to that point.  If the null byte is the trailing byte
 *          of a dual byte character, replace the dual byte with a single
 *          byte and report the shortened length.  Another possibility
 *          is that the length of the string was initially determined
 *          by this illegal two byte null.  In that case also replace it
 *          with a single byte and report the shortened length.
 *
 *          The kernel currently will take a valid leadin byte followed
 *          by a null byte and only step over the leadin byte.  There is
 *          a complicated test needed to check whether the byte before a
 *          null is a leadin byte.  In that case we will want to replace
 *          it with a single null byte and adjust the return length.
 *          Instead of testing each byte to determine if it is a leadin
 *          byte, we only check the last byte of the string for this.
 *          If a null byte is found prematurely in the string, we call
 *          'CleanStringDBCS' recursively to check the byte preceding
 *          the null byte.  That byte will now be the last byte in the
 *          string.
 *
 *          This is an unclean hack since we must assume that we can
 *          conjure up a valid trailing byte that will be acceptable
 *          to any code page.
 *
 *          NOTE:  The user must have the correct code page installed.
 *      
 *
 *  ENTRY
 *
 *      pcString    --  Pointer to the start of the string.
 *      cbLen       --  Number of bytes in the string.
 *
 *
 *  LOCAL
 *
 *      unChar      --  Union used to pull lower byte of dual byte.
 *      acCharTst   --  Array used to test last byte of string.
 *      pcCharTst   --  Pointer to start of array.
 *      usRet       --  Return value, length of resulting string.
 *      usRetTst    --  Return value from testing last byte.
 *      pcStrStrt   --  Start of string.
 *
 *      
 *  EXIT
 *
 *      May modify 'pcString' if invalid dual byte or another null byte
 *      found.
 *      
 *      
 *  RETURN
 *      
 *      'cbLen' as length of string if no truncation performed, the
 *      newly discovered length of the string otherwise.
 */
USHORT CleanStringDBCS (pcString, cbLen)
PCHAR   pcString;
USHORT  cbLen;
{
	union
	{
		USHORT  usChar;
		UCHAR   acChar[2];

	} unChar;

	USHORT      usRet;
	UCHAR   FAR *pcStrStrt;

	/* Initialize the counting index and remember the string start */
	pcStrStrt = pcString;
	usRet = 0;

	/* While not a null byte and the index is less than the string length */
	while ((usRet < (cbLen - 1)) && (*pcString))
	{
		GetCharBmpAndInc (&pcString, &usRet, &unChar.usChar);

		/*
		 *  Check for Null byte, if so then this is an illegal dual byte.
		 *  We replace the leadin byte with NULL and adjust the length
		 *  of the string.  There is no need to call 'CleanStringDBCS'
		 *  recursively since the last byte/character has already been
		 *  checked.  We set 'cbLen' to 'usRet' to prevent that check.
		 */
		if (!((UCHAR) unChar.usChar))
		{
#ifdef DEBUG
			dprintf ("\tCleanStringDBCS:  Illegal dual byte found\n");
#endif

			usRet -= 1;
			*(pcString - 2) = '\0';
			cbLen = usRet;

			break;

		} /* end if null byte found */

	} /* end while more bytes */

	/*
	 *  If the current character is NULL and the length is more than
	 *  one and less than the given length, we call CleanStringDBCS 
	 *  to check this string.
	 */
	if (!(*pcString) && usRet && (usRet < cbLen))
	{
		usRet = CleanStringDBCS (pcStrStrt, usRet);

	} /* end if premature NULL */

	/*
	 *  If we are pointing at a non-null character and we haven't reached
	 *  the end of the string, we want to test whether this character is
	 *  a lead in character.  We load a mini-string with this byte as a
	 *  leading byte and then a known valid trailing byte.  If the kernel
	 *  increments an index by two then this was a leadin byte and should
	 *  be truncated.
	 */
	else if (pcString && (usRet < cbLen))
	{
		/* Local declaration of variables */
		{
			UCHAR       acCharTst[2];
			UCHAR   FAR *pcCharTst;
			USHORT      usRetTst;

			/* Remember the start of the character buffer */
			pcCharTst = acCharTst;

			/* Load the two byte character with this as a leadin */
			acCharTst[0] = *pcString;
			acCharTst[1] = VALID_TRAIL;

			/* Set the test index to 0 */
			usRetTst = 0;

			/* Step over the character */
			GetCharBmpAndInc (&pcCharTst, &usRetTst,
								&unChar.usChar);

			/* If the index is 2 then truncate the character */
			if (usRetTst == 2)
			{
#ifdef DEBUG
			dprintf ("\tCleanStringDBCS:  Illegal leadin byte found\n");
#endif

				*pcString = '\0';

			} /* end if double byte found */

			/* Else increment usRet based on valid dual byte char found */
			else
			{
				usRet++;

			}
		}

	} /* end else if test last character */

	return usRet;

} /* end CleanStringDBCS */
