Subversion Repositories DevTools

Rev

Rev 4814 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*============================================================================
** Copyright (C) 1998-2012 Vix Technology, All rights reserved
**============================================================================
**
**  Project/Product : 
**  Filename        : JatsFileUtil.c
**  Author(s)       : DDP
**
**  Description     :   Jats Build System File utility
**                      Used by the generated makefiles to perform specific operations
**
**                      The program exists to solve problems:
**                          Windows: Shell is very very slow to start up
**                          Windows: Some commands have ~260 character path length issue
**                          Windows/Solaris/Linux: Compatibility issues with the 'rm' command
**                          All: Consistent use of '/' as a path separator
**
**                      Note: There are two flavors of this program that MUST be
**                            kept in sync.
**
**                      The program will perform the following operations:
**                          (c) CopyFile
**                          (d) DeleteFile
**                          (r) Remove Files (wildcard)
**                          (D) DeleteDir after deleting specified files (wildcard)
**                          (T) Delete Directory Trees
**                          (R) Remove Files and Empty Directories
**                          (P) Print Arguments
**
**                      Example Usage
**
**                          JatsFileUtil c9 'copyFile'    aaa/bbb/ccc/dddd/file build_test.pl +w
**                          JatsFileUtil d9 'unCopyFile'  aaa/bbb/ccc/dddd/file
**                          JatsFileUtil r9 'deleteFile'  a1 b2 c3
**                          JatsFileUtil D9 'DeleteFiles' src/WIN32P.OBJ *.err *.pch '*'
**                          JatsFileUtil T9 'DeleteTree'  interface
**                          JatsFileUtil R9 'RmItem'       build
**
**                      First two arguments are common to all
**                          argv[1]     - Mode specifier, Debug specifier
**                          argv[2]     - Display Text
**
**  Information     :
**   Compiler       : ANSI C
**   Target         : Windows 2000+, Linux, Solaris8+
**
***==========================================================================*/

#define _CRT_SECURE_NO_WARNINGS

#include <tchar.h>
#include <stdio.h>
#include <windows.h>

#define INVALID_FILE_ATTIBUTES ((DWORD) -1)

VOID ErrorExit (char *lpszMessage, LPTSTR lpszMessage2);
void createPaths ( _TCHAR *path );
void DeleteDir( int argc, _TCHAR* argv[] );
_TCHAR * makePath( _TCHAR *base, _TCHAR *path);
void DeleteOneDirectoryTree( _TCHAR* baseDir );
void copyOneFile( int argc, _TCHAR* argv[] );
void RmItem( int argc, _TCHAR* argv[] );
int DeleteOneFile (_TCHAR * dst );
void DeleteOneDirectory (_TCHAR *path );
void stdCheck( char *name, int argBad, _TCHAR *txt );
void url_decode(_TCHAR *str);

/*
**  Global
*/
char  verbose = 1;                          /* Debugging aid */
char  *helpText =
         "Usage: JatsFileUtil Mode Text Arguments\n"
         "\n"
         "  Where 'Mode' is two characters:\n"
         "      1 - Operation Specifier\n"
         "      2 - Debug Mode. 0..9\n"
         "  Where 'Text' is a, possibly empty, display string\n"
         "\n"
         "  By Example:\n"
         "      c9 copyFile     dstPath srcPath modes(wxl)\n"
         "      d9 unCopyFile   dstPath\n"
         "      r9 RmFile       file+\n"
         "      D9 DeleteFiles  dstDir file+ - supports (?*)\n"
         "      T9 DeleteTree   dstDir+\n"
         "      R9 RmItem       (dir|file)+\n"
         "      P9 PrintArgs    ...\n";

/*----------------------------------------------------------------------------
** FUNCTION           : main
**
** DESCRIPTION        : Main entry points
**
**
** INPUTS             : argc            - Argument Count
**                      argv            - Argument Vector
**
** RETURNS            : 0 - All is good
**
----------------------------------------------------------------------------*/

int _tmain(int argc, _TCHAR* argv[])
{
    _TCHAR * dst;
    int ii;
    
    /*
    **  User must provide some thing
    */
    if ( argc < 2 )
    {
       fprintf(stderr, helpText );
       return 1;
    }

    /*
    **  Examine the first argument
    **  Must be a character string of
    **      [0] - Mode : One character
    **      [1] - Verbose : 0 .. 9
    */
    if ( argc > 1 && ( argv[1][1] >= '0' && argv[1][1] <= '9' ) )
    {
        verbose = argv[1][1] - '0';
    }

    /*
    **  URL Decode most arguments
    **  To get past the stupidities of shells and make the arguments
    **  will have been URL(modified) encoded
    **
    **  Decode all args in place
    */
    for ( ii = 2; ii < argc ; ii++ )
    {
        url_decode(argv[ii]);
        /*
        **  If Verbose, then display arguments
        */
        if ( verbose > 2 )
            fprintf(stderr, "Arg%d: %ls:\n", ii, argv[ii] );
    }
     fflush(stderr) ;
    
    /*
    **  Switch to required operation
    */
    switch( argv[1][0] )
    {
        /*
        **  CopyFile
        **      argv[2] - Text
        **      argv[3] - target path
        **      argv[4] - Source path
        **      argv[5] - File attributes
        */
        case 'c':
            stdCheck( "CopyFile", argc != 6, NULL );
            fprintf(stderr, "---- %ls %ls\n", argv[2], argv[3]);
            fflush(stderr) ;
            copyOneFile(argc, argv);
            break;

        /*
        **  UnCopy a file
        **      argv[2] - Text
        **      argv[3] - target path
        */
        case 'd':
            stdCheck( "UnCopy", argc != 4, NULL );
            fprintf(stderr, "---- %ls %ls\n", argv[2], argv[3]);
            fflush(stderr) ;

            dst = makePath(argv[3], NULL);
            DeleteOneFile(dst);
            free (dst);
            break;

        /*
        **  Remove named files
        **      argv[2]     - Text
        **      argv[3]+    - target path
        */
        case 'r':
            stdCheck( "RmFile", argc <= 3, argv[2] );
            for ( ii = 3; ii < argc ; ii++ )
            {
                _TCHAR * dst = makePath(argv[ii], NULL);
                DeleteOneFile(dst);
                free (dst);
            }
            break;

        /*
        **  Delete files in directory - with wildcards
        **      argv[2]     - Text
        **      argv[3]     - Base directory
        **      argv[4]+    - Files in dir to delete.
        */
        case 'D':
            stdCheck( "DeleteDir", argc < 4, argv[2] );
            DeleteDir(argc, argv );
            break;

        /*
        **  Delete files recursively
        **      argv[2] - Text
        **      argv[3]+  Base directory
        */
        case 'T':
            stdCheck( "DeleteDirTree", argc < 3, argv[2] );
            for ( ii = 3; ii < argc ; ii++)
            {
                DeleteOneDirectoryTree( argv[ii] );
            }
            break;

        /*
        **  Delete Empty Directories
        **      argv[2] - Text
        **      argv[3]+  Base directory
        */
        case 'R':
            stdCheck( "RmItem", argc < 3, argv[2] );
            RmItem(argc, argv );
            break;

        /*
        **  Print arguments with a space betweenn them
        **  All on the same line
        **      argv[2]+ - Text
        */
        case 'P':
            for ( ii = 2; ii < argc ; ii++ )
            {
                if ( ii > 2 )
                    fprintf(stderr, " ");
                fprintf(stderr, "%ls", argv[ii] );
            }
            fprintf(stderr, "\n");
            break;
            
        default :
            ErrorExit("Unknown mode: ",argv[1]);
            break;
    }
    return 0;
}

/*----------------------------------------------------------------------------
** FUNCTION           : stdCheck
**
** DESCRIPTION        : Check arg count
**                      Print standard header
**
**
** INPUTS             : name        - Name of the operation
**                      argBad      - Arg count Not Ok
**                      text        - Text to print
**
** RETURNS            : Will not return on error
**
----------------------------------------------------------------------------*/

void stdCheck( char *name, int argBad, _TCHAR *txt )
{
    if ( argBad  )
    {
       fprintf(stderr, "JatsFileUtil:Incorrect argument count for %s\n", name);
       ErrorExit(NULL, NULL);
    }

    /*
    **  Display user text
    **      Suppress display if the message is empty
    */
    if ( txt && *txt )
    {
        fprintf(stderr, "%ls\n",txt);
        fflush(stderr) ;
    }
}

/*----------------------------------------------------------------------------
** FUNCTION           : createPaths
**
** DESCRIPTION        : Create the path to the target
**
**
** INPUTS             : path
**                      Assumed to be a \\?\X:\ style
**
** RETURNS            : Will not return in error
**
----------------------------------------------------------------------------*/

void createPaths ( _TCHAR *path )
{
    DWORD rv;
    _TCHAR *ptr = path + 7;
    while ( *ptr )
    {
        if ( *ptr == '/' || *ptr == '\\')
        {
            *ptr = 0;
//fprintf(stderr, "createPaths: %ls\n", path);
            if ( ! CreateDirectoryW ( path, NULL ))
            {
                rv = GetLastError();
                if ( rv != ERROR_ALREADY_EXISTS )
                    ErrorExit("Could not create directories:", path);
            }
            *ptr = '\\';
        }
        ptr++;
    }
}

/*----------------------------------------------------------------------------
** FUNCTION           : copyOneFile
**
** DESCRIPTION        : Copy one file to a target
**                      Used to package and install files
**
**
** INPUTS             : argc            - Argc count
**                      argv            - Argument list
**                          argv[2]     - Display text Prefix
**                          argv[3]     - Target path
**                          argv[4]     - Source Path
**                          argv[5]     - File attributes
**
** RETURNS            :
**
----------------------------------------------------------------------------*/

void copyOneFile( int argc, _TCHAR* argv[] )
{
    DWORD rv;
    _TCHAR * src;
    _TCHAR * dst;
    
    dst = makePath(argv[3], NULL);
    src = makePath(argv[4], NULL);

    /*
    **   Check that the source is a file
    */
    if ( verbose > 2)
        fprintf(stderr, "Validate Source File: %ls\n", src);
        
    rv = GetFileAttributesW( src );
    if ( rv == INVALID_FILE_ATTIBUTES )
    {
/* Need to be a better message */
        fprintf(stderr, "Source: %ls\n", src);
        ErrorExit("Source File not found: ", argv[4]);
    }

    /*
    **  Delete the destination file before the copy
    **  Will force it to be writable
    */
    DeleteOneFile(dst);

    /*
    **  Create directories
    **  Use the path to the target - not the provided directory
    **  as the createPaths function will not create the last element
    */
    createPaths( dst );

    /*
    **   Copy the file
    */
    if ( ! CopyFile( src, dst, FALSE ) )
    {
        rv = GetLastError();
        fprintf(stderr, "CopyFile Last Error: %ld\n", rv);
        ErrorExit("Copy Error: ", argv[4]);
    }

    /*
    **  Test for files existence
    */
    if ( verbose > 1 )
        fprintf(stderr, "Test target was copied: %ls\n", dst);

    rv = GetFileAttributesW( dst );
    if ( rv == INVALID_FILE_ATTIBUTES )
    {
/* Need to be a better message */
        ErrorExit("File not found after copy: ", argv[3]);
    }

    /*
    **  Set the files attributes
    **      Assume read-only
    */
    if ( _tcsstr( argv[5], L"-w" ) )
    {
        if ( verbose )
            fprintf(stderr, "Set target read-only: %ls\n", dst);
            
        rv |= FILE_ATTRIBUTE_READONLY;
        rv &= ~FILE_ATTRIBUTE_NORMAL;
        rv = SetFileAttributesW( dst, rv );
        if ( rv == 0 )
        {
            ErrorExit("Setting ReadOnly: ", argv[3]);
        }
    }
}

/*----------------------------------------------------------------------------
** FUNCTION           : DeleteDir
**
** DESCRIPTION        : Delete a list of files in a specified directory
**                      Ensure files are writable
**                      Wilcarding is supported
**
**                      Then delete the directory - if its empty
**
**
** INPUTS             : argc    - count of args
**                      argv    - list of files to delete
**                                [3]:  Base directory
**                                [4]+  Files in dir to delete
**
** RETURNS            : Will not return on error
**
----------------------------------------------------------------------------*/

void DeleteDir( int argc, _TCHAR* argv[] )
{
    DWORD rv;
    _TCHAR* baseDir;
    WIN32_FIND_DATA FindFileData;
    HANDLE hFind;
    _TCHAR *target;
    _TCHAR *dirPath;
    int ii;

    /*
    **  Extract the base directory from the argument list
    **  This must be a directory
    */
    baseDir = argv[3];

    /*
    **  Ensure that the base directory exists
    */
    dirPath = makePath(baseDir, NULL);
    rv = GetFileAttributesW( dirPath );
    if ( rv == INVALID_FILE_ATTIBUTES )
    {
        /*
        **  Directory does not exists
        **  Assume its already deleted
        */
        if ( verbose > 1 )
            fprintf(stderr, "Base dir does not exist: %ls\n", baseDir);
        free(dirPath);
        return;
    }

    if ( !(rv & FILE_ATTRIBUTE_DIRECTORY) )
    {
        /*
        **  Target is not a directory
        **  Don't do anything
        */
        if ( verbose > 1 )
            fprintf(stderr, "Base dir is not a directory: %ls\n", baseDir);
        free(dirPath);
        return;
    }

    /*
    **  Process all the suffixes
    **  They may contain a wild card
    */
    for ( ii = 4; ii < argc ; ii++)
    {
        _TCHAR * thisDir = makePath( baseDir, argv[ii]);
        hFind = FindFirstFile(thisDir, &FindFileData);
        free(thisDir);
        
        if (hFind == INVALID_HANDLE_VALUE)
        {
            /*
            **  No match
            */
            if ( verbose > 1 )
                fprintf(stderr, "No Match: %ls, %ls\n", baseDir, argv[ii]);
            continue;
        }

        /*
        **  Iterate over all the files
        */
        do {
            if ( _tcscmp( TEXT("."), FindFileData.cFileName ) == 0 )
                continue;

            if ( _tcscmp( TEXT(".."), FindFileData.cFileName ) == 0 )
                continue;

            if ( verbose > 1)
                fprintf(stderr, "Match: %ls\n", FindFileData.cFileName);

            /*
            **  Matching file found
            */
            target = makePath( baseDir, FindFileData.cFileName);
            DeleteOneFile(target);
            free (target);

        } while  (FindNextFile(hFind, &FindFileData) != 0);
        FindClose(hFind);
    }

    /*
    **  Finally delete the directory
    **      Unless its '.'
    */
    if ( _tcscmp( TEXT("."),baseDir) != 0 )
    {
        DeleteOneDirectory(baseDir);
    }
    free(dirPath);
}

/*----------------------------------------------------------------------------
** FUNCTION           : DeleteOneDirectoryTree
**
** DESCRIPTION        : Delete an entire directory tree(s)
**                      Force it writable and searchable before deletion
**                      Don't follow symbolic links - just delete them
**
** INPUTS             : path    - Dir to delete
**
** RETURNS            : Will not return on error
**
----------------------------------------------------------------------------*/

void DeleteOneDirectoryTree( _TCHAR* baseDir )
{
    DWORD rv;
    WIN32_FIND_DATA FindFileData;
    HANDLE hFind;
    _TCHAR *target;
    _TCHAR *dirPath;
    _TCHAR *thisDir;

    /*
    **  A bit of a sanity test
    */
    if ( _tcscmp( L".", baseDir) == 0 || _tcscmp( L"..", baseDir) == 0 )
    {
        fprintf(stderr, "DeleteOneDirectoryTree will not delete '.' or '..'\n");
        return;
    }

    /*
    **  Ensure that the base directory exists
    */
    dirPath = makePath(baseDir, NULL);
    rv = GetFileAttributesW( dirPath );
    if ( rv == INVALID_FILE_ATTIBUTES )
    {
        /*
        **  Directory does not exists
        **  Assume its already deleted
        */
        if ( verbose > 1 )
            fprintf(stderr, "Base dir does not exist: %ls\n", baseDir);
        free(dirPath);
        return;
    }

    if ( !(rv & FILE_ATTRIBUTE_DIRECTORY) )
    {
        /*
        **  Target is not a directory
        **  Don't do anything
        */
        if ( verbose > 1 )
            fprintf(stderr, "Base dir is not a directory: %ls\n", baseDir);
        free(dirPath);
        return;
    }

    /*
    **  Read next directory entry
    */
    thisDir = makePath( baseDir, TEXT("*"));
    hFind = FindFirstFile(thisDir, &FindFileData);
    free(thisDir);

    if (hFind != INVALID_HANDLE_VALUE)
    {

        /*
        **  Iterate over all the files
        */
        do {
            if ( _tcscmp( TEXT("."), FindFileData.cFileName ) == 0 )
                continue;

            if ( _tcscmp( TEXT(".."), FindFileData.cFileName ) == 0 )
                continue;

            if ( verbose > 2 )
                fprintf(stderr, "Directory Entry:%ls,%ls\n", baseDir, FindFileData.cFileName );

            /*
            **  Create a full path to the file
            */
            target = makePath( baseDir, FindFileData.cFileName);
            if ( FindFileData.dwFileAttributes  & FILE_ATTRIBUTE_DIRECTORY)
            {
                DeleteOneDirectoryTree( target );
            }
            else
            {
                DeleteOneFile (target);
            }
            free (target);

        } while  (FindNextFile(hFind, &FindFileData) != 0);
        FindClose(hFind);
    }

    /*
    **  Finally delete the directory
    **  It should now be empty
    */
    DeleteOneDirectory(baseDir);

    free(dirPath);
}

/*----------------------------------------------------------------------------
** FUNCTION           : RmItem
**
** DESCRIPTION        : Remove Empty directories and files
**
** INPUTS             : argc    - count of args
**                      argv    - list of files to delete
**                                [3]+  Base directory
**
** RETURNS            : Will not return on error
**
----------------------------------------------------------------------------*/


void RmItem( int argc, _TCHAR* argv[] )
{
    int ii;
    _TCHAR *target;
    DWORD rv;

    for ( ii = 3; ii < argc ; ii++)
    {

        if ( verbose > 2)
            fprintf(stderr, "RmItem: %ls\n", argv[ii]);

        target = makePath( argv[ii],NULL);
        rv = GetFileAttributesW( target );
        if ( rv != INVALID_FILE_ATTIBUTES )
        {
            if ( rv  & FILE_ATTRIBUTE_DIRECTORY)
            {
                DeleteOneDirectory( target );
            }
            else
            {
                DeleteOneFile (target);
            }
        }
        free(target);
    }
}

/*----------------------------------------------------------------------------
** FUNCTION           : DeleteOneFile
**
** DESCRIPTION        : Delete a file
**                      Low level deletion operation to be used by other functions
**                      Force it writable before deletion
**                      Don't follow symbolic links - just delete them
**
** INPUTS             : path        - path to the file
**
** RETURNS            : 0           - OK (file deleted or not present)
**
----------------------------------------------------------------------------*/

int DeleteOneFile (_TCHAR * dst )
{
    DWORD rv;
    int result = 0;

    if ( verbose > 1)
        fprintf(stderr, "Delete File: %ls\n", dst);

    rv = GetFileAttributesW( dst );
    if ( rv != INVALID_FILE_ATTIBUTES )
    {
        if ( verbose )
            fprintf(stderr, "Delete file: %ls : Attr: 0x%lx\n", dst, rv);
        if ( rv & FILE_ATTRIBUTE_READONLY )
        {
            rv &= ~FILE_ATTRIBUTE_READONLY;
            rv = SetFileAttributesW( dst, rv );
            if ( rv == 0 )
            {
                fprintf(stderr, "Warning: Attempt to allow write access: %ls\n", dst);
            }
        }

        if (! DeleteFile( dst ) )
        {
            fprintf(stderr, "Warning: Did not remove file: %ls\n", dst);
            result = 1;
        }
    }

    return result;
}

/*----------------------------------------------------------------------------
** FUNCTION           : DeleteOneDirectory
**
** DESCRIPTION        : Low level function to delete a directory
**                      Assumes that checks have been performed
**                          It is a directory
**                          It does exist
**
**
** INPUTS             : path            - Target Path
**
** RETURNS            : Nothing
**
----------------------------------------------------------------------------*/

void DeleteOneDirectory (_TCHAR *path )
{
    if ( verbose )
        fprintf(stderr, "Delete Directory: %ls\n", path);

    if ( ! RemoveDirectory(path))
    {
        if ( verbose )
            fprintf(stderr, "Directory not deleted: %ls\n", path);
    }
}

/*----------------------------------------------------------------------------
** FUNCTION           : makePath
**
** DESCRIPTION        : Creates a full file path for user
**                      Join 2 components together
**                      Convert relative path to ABS path with \\?\ from
**                      Convert / into \ for windows
**
**                      Handle path elements
**                          ./                  - Remove
**                          /bbb/../            - Remove
**                          /aaa/bbb/../../     - Remove
**
**
** INPUTS             : base        - Part 1
**                                    May already contain \\?\
**                      file        - Part 2 or NULL
**
** RETURNS            : fullPath - User must free memory
**
----------------------------------------------------------------------------*/

_TCHAR * makePath ( _TCHAR * base, _TCHAR *file )
{
    size_t lenp = 0;
    size_t lencwd = 0;
    size_t len1 = _tcslen(base);
    size_t len2 = 0;
    size_t dlen = _tcslen(TEXT("\\\\?\\"));
    _TCHAR *data;
    _TCHAR *cdata;
    _TCHAR *udst;

    /*
    **  If the base contains a \\?\ then we don't need to add it
    */
    if ( !( len1 > 3 && base[2] == '?' ) )
    {
        lenp = dlen;
    }

    /*
    **  Unless an absolute path we need to insert CWD too
    **  Just determine the length of the entry for now
    */
    if ( lenp && ( ( base[0] && (base[1] != ':') ) || base[0] == 0 ) )
    {
        lencwd = GetCurrentDirectory(0,0) - 1; // determine size needed
    }

    /*
    **  2nd file argument may be a NULL
    */
    if ( file )
    {
        len2 = _tcslen(file);
    }

    data = (_TCHAR *)malloc((lenp + lencwd+ len1 + len2 + 10) * sizeof(_TCHAR));
    cdata = data;
    if ( data == NULL )
    {
        ErrorExit ("Malloc error:makePath",L"");
    }

    /*
    **  Join all the strings together
    */
    if ( lenp )
    {
        _tcscpy( cdata,TEXT("\\\\?\\"));
        cdata += lenp;
    }

    if ( lencwd )
    {
        GetCurrentDirectory((DWORD)(lencwd+1), cdata);
        cdata += lencwd;
        _tcscpy( cdata, TEXT("\\"));
        cdata += 1;
    }

    _tcscpy( cdata, base);
    cdata += len1;

    if ( len2 )
    {
        _tcscpy( cdata, TEXT("\\"));
        _tcscpy( cdata+1, file);
        cdata += len2 + 1;
    }

    /*
    **  Convert / into \
    */
    for ( udst = data; *udst ; udst++)
    {
        if ( *udst == '/' )
            *udst = '\\';
    }

    /*
    **  Now remove silly relative path bits
    **  Also duplicate '\\'
    */
    udst = data + dlen;
    while ( *udst )
    {
        if ( udst[0] == '\\' && udst[1] == '\\' )
        {
            TCHAR* ptr1 = udst;
            TCHAR* ptr2 = udst + 1;
            while ( *ptr1++ = *ptr2++ ) {}
            continue;
        }

        if ( udst[0] == '\\' && udst[1] == '.' && (udst[2] == '\\' || udst[2] == 0) )
        {
            TCHAR* ptr1 = udst;
            TCHAR* ptr2 = udst + 2;
            while ( *ptr1++ = *ptr2++ ) {}
        }
        else if ( udst[0] == '\\' && udst[1] == '.' && udst[2] == '.' && udst[3] == '\\'  )
        {
            TCHAR* ptr = udst - 1;
            TCHAR* ptr2 = udst + 3;

            while ( *ptr-- != '\\' )
            {
                if ( ptr - data < 6)
                {
                    *ptr = 0;
                    fprintf(stderr, "Path: %ls\n", data);
                    ErrorExit("Bad relative path: ", data);
                }
            }
            ptr++;
            udst = ptr;
            while ( *ptr++ = *ptr2++ ) {}
        }
        else
        {
            *udst++;
        }
    }

    if ( verbose > 5)
        fprintf(stderr, "makePath: %ls\n", data);

    return data;
}

/*----------------------------------------------------------------------------
** FUNCTION           : from_hex
**
** DESCRIPTION        : Converts a hex character to its integer value
**                      Expects well formed HEX
**
**
** INPUTS             : Character to convert
**
** RETURNS            : Character
**
----------------------------------------------------------------------------*/
_TCHAR from_hex(_TCHAR ch)
{
    if ( ch >= '0' && ch <= '9' )
    {
        return ch - '0';
    }

    if ( ch >= 'A' && ch <= 'F' )
    {
        return ch - 'A' + 10;
    }

    if ( ch >= 'a' && ch <= 'f' )
    {
        return ch - 'f' + 10;
    }

    return 0;
}

/*----------------------------------------------------------------------------
** FUNCTION           : url_decode
**
** DESCRIPTION        : Perform (modified) URL decoding of a string
**                      Only support %nn stuff
**                      Its done inplace
**
**
** INPUTS             : str             - String to process
**
** RETURNS            : Nothing
**
----------------------------------------------------------------------------*/

void url_decode(_TCHAR *str)
{
  _TCHAR *pstr = str;
  _TCHAR *pbuf = str;

    while (*pstr)
    {
        if (*pstr == '%')
        {
            if (pstr[1] && pstr[2])
            {
                *pbuf++ = from_hex(pstr[1]) << 4 | from_hex(pstr[2]);
                pstr += 2;
            }
/*        } */
/*      } else if (*pstr == '+') {  */
/*              *pbuf++ = ' '; */
    }
    else
    {
        *pbuf++ = *pstr;
    }
        pstr++;
    }
    *pbuf = '\0';
}

/*----------------------------------------------------------------------------
** FUNCTION           : ErrorExit
**
** DESCRIPTION        : Error processing
**                      Report an error and terminate process
**
**
** INPUTS             : lpszMessage     - Message to display
**
** RETURNS            : Does't return
**                      Will exit with bad code
**
----------------------------------------------------------------------------*/

VOID ErrorExit (char *lpszMessage, LPTSTR lpszMessage2)
{
    if ( lpszMessage  )
    {
        fprintf(stderr, "JatsFileUtil:Error:%s%ls\n", lpszMessage,lpszMessage2);
    }
   fflush(stderr) ;
   ExitProcess(-1);
}