;/*
; *                      Microsoft Confidential
; *                      Copyright (C) Microsoft Corporation 1991
; *                      All Rights Reserved.
; */

/***************************************************************************/
/*	SETVER.C 																					*/
/*																									*/
/*	This module contains the functions which read in the version table		*/
/*	from MSDOS.SYS and then updates the table with new entries and				*/
/*	writes it back to the file.															*/
/*																									*/
/*	The fake version table is located in the DOS system file and it's			*/
/*	location and length are specified with 2 words at offset 7 in the			*/
/* file. The first word is the table offset and second word is length.		*/
/*																									*/
/*	Table layout:																				*/
/*																									*/
/* ENTRY FILENAME LEN:	Length of filename in bytes	1 byte					*/
/* ENTRY FILENAME:	Variable length to 12 bytes	? bytes						*/
/* ENTRY VERSION MAJOR: Dos major version to return	1 byte					*/
/* ENTRY VERSION MINOR: Dos minor version to return	1 byte					*/
/*																									*/
/*																									*/
/*	USEAGE:																						*/
/*		List table:		SETVER [D:]															*/
/*		Add entry:		SETVER [D:] name.ext X.XX										*/
/*		Delete entry:	SETVER [D:] name.ext /DELETE									*/
/*		Delete entry quietly: SETVER [D:] name.ext /DELETE /QUIET				*/
/*		Display help	SETVER /?															*/
/*																									*/
/*	WHERE:																						*/
/*		D: is the drive containing MSDOS.SYS											*/
/*		name.ext is the executable file name											*/
/*		X.XX is the major and minor version numbers									*/
/*																									*/
/*	RETURN CODES:																				*/
/*		0	Successful completion															*/
/*		1	Invalid switch																		*/
/*		2	Invalid file name																	*/
/*		3	Insuffient memory																	*/
/*		4	Invalid version number format													*/
/*		5	Entry not found in the table													*/
/*		6	MSDOS.SYS file not found														*/
/*		7	Invalid MSDOS.SYS or IBMDOS.SYS file										*/
/*		8	Invalid drive specifier															*/
/*		9	Too many command line parameters												*/
/*		10	DOS version was not specified													*/
/*		11	Missing parameter																	*/
/*		12 Error reading MS-DOS system file												*/
/*		13 Version table is corrupt														*/
/*		14 Specifed file does not support a version table							*/
/*		15 Insuffient space in version table for new entry							*/
/*		16 Error writing MS-DOS system file												*/
/*																									*/
/*	johnhe	05-01-90																			*/
/***************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <dos.h>
#include <io.h>
#include <fcntl.h>

#include <setver.h>
#include <message.h>


/***************************************************************************/

static char				*ReadBuffer;
static char 			*LieBuffer;		 		/* Buffer to read lietable into	*/
static char 			*EndBuf;			 		/* Ptr to end of the buffer 		*/
struct ExeHeader		ExeHdr;
struct DevHeader		DevHdr;
struct TableEntry		Entry;
static char				*szSetVer = "SETVERXX";

long						FileOffset;
/* static UINT				TableLen; */

/***************************************************************************/
/* Program entry point. Parses the command line and if it's valid executes */
/* the requested function and then returns the proper error code. Any		*/
/* error codes returned by ParseCommand are negative so they must be			*/
/* converted with a negate before being returned as valid error codes.		*/
/*																									*/
/*	int main( int argc, char *argv[] )													*/
/*																									*/
/*	ARGUMENTS:	argc - Count of command line arguments				 				*/
/*					argv - Array of ptrs to argument strings							*/
/*	RETURNS:		int	- Valid return code for batch processing					*/
/*																								 	*/
/***************************************************************************/

int main( int argc, char *argv[] )
{
	register		iFunc;
	char			szError[ 80 ];

	iFunc = ParseCmd( argc, argv, &Entry );
	if ( iFunc >= 0 )
		iFunc = DoFunction( iFunc );

	if ( iFunc != S_OK )
	{
		iFunc = -(iFunc);
		strcpy( szError, ErrorMsg[ 0 ] );
		strcat( szError, ErrorMsg[ iFunc ] );
		PutStr( szError );
		PutStr( szMiniHelp );
	}
	return( iFunc	);
}

/***************************************************************************/
/* Calls the appropriate function to do whatever was specified by the		*/
/* user. The lie table if first read in except in the case only the help	*/
/* function was requested. To be sure duplicate table entries are not		*/
/* created a call to DeleteEntry with the new program name will be done		*/
/* before the new entry is created.														*/
/*																									*/
/*	int DoFunction( int iFunc )															*/
/*																									*/
/*	ARGUMENTS:	iFunct - The function to be performed								*/
/*	RETURNS:		int	 - S_OK if no errors else an error code					*/
/*																									*/
/***************************************************************************/

int DoFunction( int iFunc )
{
	register		iStatus;

	if ( iFunc == DO_HELP )
	{
		DisplayMsg( Help );
		return( S_OK );
	}

	if ( iFunc == DO_ADD_FILE )
		DisplayMsg( Warn );							/* Read in the lie table and	*/
															/* then decide what to do		*/
	if ( (iStatus = ReadVersionTable()) == S_OK )
	{
		if ( iFunc == DO_LIST )
			iStatus = DisplayTable();
		else
		{
			if ( (iFunc == DO_DELETE || iFunc == DO_QUIET) &&
				  (iStatus = MatchFile( LieBuffer, Entry.szFileName )) < S_OK )
				return( iStatus );
															/* Always a delete before add	*/

			if ( (iStatus = DeleteEntry()) == S_OK &&	iFunc == DO_ADD_FILE )
				iStatus = AddEntry();

			if ( iStatus == S_OK &&
				  (iStatus = WriteVersionTable()) == S_OK &&
				   !(iFunc == DO_QUIET) )
			{
				PutStr( SuccessMsg );
				if ( SetVerCheck() == TRUE )		/* M001 */
					PutStr( SuccessMsg2 );
			}
		}
	}
					/* M001 Install check to see if currently in device chain */
	if ( iStatus == S_OK && iFunc != DO_QUIET && SetVerCheck() == FALSE )
		DisplayMsg( szNoLoadMsg );

	return( iStatus );
}

/***************************************************************************/
/* Displays the help text for "/?" option, or the warning text.				*/
/*																									*/
/*	void DisplayHelp( tbl )																	*/
/*																									*/
/*	ARGUMENTS:	char *tbl[]													 				*/
/*	RETURNS:		void																			*/
/*																									*/
/***************************************************************************/

void DisplayMsg( char *tbl[] )
{
	register i;

	for ( i = 0; tbl[i] != NULL; i++ )
		PutStr( tbl[ i ] );
}

/***************************************************************************/
/* Displays all entries in the version table which must have already been	*/
/* read into the work buffer. The name and version number are created as	*/
/* as ascii string in a tempory buffer and then printed as a single string */
/* in the format:																			 	*/
/*																								 	*/
/*	1234567890123456789																		*/
/*	FILENAME.EXT	X.XX																		*/
/*																								 	*/
/*	int DisplayTable( void )														 		*/
/*																								 	*/
/*	ARGUMENTS:	void																			*/
/*	RETURNS:		int	- S_CORRUPT_TABLE if table corrupt else S_OK				*/
/*																								 	*/
/***************************************************************************/

int DisplayTable( void )
{
	char		*BufPtr;
	char		*szTmp;
	char		*szVersion;
	char		szEntry[ 50 ];

	BufPtr = LieBuffer;
	szVersion = szEntry + VERSION_COLUMN;

	PutStr( "" );
	while ( *BufPtr != 0 && BufPtr < EndBuf )
	{
														 	/* Chk for table corruption	*/
		if ( !IsValidEntry( BufPtr ) )
			return( S_CORRUPT_TABLE );
												/* Copy file name and pad with spaces	*/
		strncpy( szEntry, BufPtr+1, (unsigned)((int)*BufPtr) );
		for ( szTmp = szEntry + *BufPtr; szTmp < szVersion; szTmp++ )
			*szTmp = ' ';

															/* Point to version number		*/
		BufPtr += *BufPtr;
		BufPtr++;

															/* Now create ascii version	*/
		itoa( (int)*(BufPtr++), szVersion, DECIMAL );
		strcat( szVersion, (int)*BufPtr < 10 ? ".0" : "." );
		itoa( (int)*(BufPtr++), strchr( szVersion, EOL ), DECIMAL );

		PutStr( szEntry );
	}
	if ( BufPtr == LieBuffer )
		PutStr( szTableEmpty );

	return( S_OK );
}


/***************************************************************************/
/* Deletes all matching entries in the version table by moving all of the	*/
/* entries following the matched entry down in the buffer to replace the	*/
/* entry being deleted. After the entries are moved down the residuals		*/
/* at the end of the table must be zeroed out. Before returning the entire */
/* end of the table buffer after the valid entries is zeroed out to remove */
/* any possible corruption.																*/
/*																									*/
/*	int DeleteEntry( void )																	*/
/*																									*/
/*	ARGUMENTS:	NONE																			*/
/*	RETURNS:		int	- S_CORRUPT_TABLE if errors found else S_OK				*/
/*																									*/
/***************************************************************************/

int DeleteEntry( void )
{
	char		*pchPtr;
	char		*pchTmp;
	int		iOffset;
	UINT		uEntryLen;
	UINT		uBlockLen;

	pchPtr = LieBuffer;

	while ( (iOffset = MatchFile( pchPtr, Entry.szFileName )) >= 0 )
	{
		pchPtr = LieBuffer + iOffset;						/* Move block down		*/
		uEntryLen = (UINT)((int)*pchPtr) + 3;
		uBlockLen = (UINT)(EndBuf - pchPtr) + uEntryLen;
		memmove( pchPtr, pchPtr + uEntryLen, uBlockLen );

		pchTmp = pchPtr + uBlockLen;			 			/* Clean end of blk		*/
		memset( pchTmp, 0, uEntryLen );
	}

	if ( iOffset == S_ENTRY_NOT_FOUND )		 			/* Clean end of table	*/
	{
		if ( (pchTmp = GetNextFree()) != NULL )
			memset( pchTmp, 0, DevHdr.TblLen - (unsigned)(pchTmp - LieBuffer) );
		return( S_OK );
	}
	else
		return( S_CORRUPT_TABLE );
}


/***************************************************************************/
/* Adds a new entry to the end of any existing entries in the version		*/
/* table. There must be suffient room in the table for the entry or the	 	*/
/* call will fail with a S_NO_ROOM error returned.									*/
/*																									*/
/*	int AddEntry( void )																		*/
/*																									*/
/*	ARGUMENTS:	NONE																			*/
/*	RETURNS:		int	- S_OK if room for entry else S_NO_ROOM		 			*/
/*																									*/
/***************************************************************************/

int AddEntry( void )
{
	register		iLen;
	char			*pchNext;

	iLen = (int)strlen( Entry.szFileName ) + 3;

	if ( (pchNext = GetNextFree()) != NULL && iLen <= EndBuf - pchNext )
	{
		*pchNext = (char)(iLen - 3);
		strcpy( pchNext + 1, Entry.szFileName );
		pchNext += (int)(*pchNext) + 1;
		*(pchNext++) = (char)Entry.MajorVer;
		*pchNext = (char)Entry.MinorVer;
		return( S_OK );
	}
	else
		return( S_NO_ROOM );
}


/***************************************************************************/
/* Returns the offset of a specified name in the version table. The start	*/
/* of the search is specified by the caller so that searches for duplicate */
/* entries can be made without redundency. NOTE: file name entries in the	*/
/* version table are not zero terminated strings so the comparision must	*/
/* be conditioned by length and the search strings length must be checked	*/
/* to avoid an error caused by a match of a shorter table entry name.		*/
/*																								 	*/
/*	int MatchFile( char *pchStart, char *szFile )							 		*/
/*																								 	*/
/*	ARGUMENTS:	pchStart - Ptr specifying search starting point	 				*/
/*					szFile	- Ptr to file name to match								*/		
/*	RETURNS:		int		- Offset of entry from start of version				*/
/*								  buffer or -1 if not match or							*/
/*								  S_CORRUPT_TABLE if error					 				*/
/*																								 	*/
/***************************************************************************/

int MatchFile( char *pchPtr, char *szFile )
{
	for ( ; pchPtr < EndBuf && *pchPtr != 0; pchPtr += *pchPtr + 3 )
	{
		if ( !IsValidEntry( pchPtr ) )						/* Corruption check	*/
			return( S_CORRUPT_TABLE );
		else if ( strncmp( szFile, pchPtr + 1, (UINT)((int)*pchPtr) ) == S_OK &&
					 *(szFile + *pchPtr) == EOL )
			return( pchPtr - LieBuffer );						/* Return ptr offset */
	}
	return( S_ENTRY_NOT_FOUND );								/* Return no match	*/
}

/***************************************************************************/
/* Checks a version table entry to see if it a valid entry. The definition */
/* of a valid entry is one which has a file length less than MAX_NAME_LEN	*/
/* and the entire entry lies within the version table.							*/
/*																								 	*/
/*	int IsValidEntry( char *pchPtr )														*/
/*																								 	*/
/*	ARGUMENTS:	pchPtr - Ptr to version tbl entry in table buffer				*/
/*	RETURNS:		int	 - TRUE if entry is valid else FALSE						*/
/*																								 	*/
/***************************************************************************/

int IsValidEntry( char *pchPtr )
{
	if ( (int)*pchPtr < MAX_NAME_LEN && (pchPtr + (int)*pchPtr + 3) < EndBuf )
		return( TRUE );
	else
		return( FALSE );
}


/***************************************************************************/
/* Returns a pointer to the next free entry in the version table. If there */
/* are no free entries left in the buffer a NULL ptr will be returned.		*/
/* Since DeleteEntry is always called before AddEntry there is no check	 	*/
/* for table corruption since it will have already been done by the			*/
/* DeleteEntry call.																		 	*/
/*																								 	*/
/*	char *GetNextFree( void )																*/
/*																								 	*/
/*	ARGUMENTS:	NONE																			*/
/*	RETURNS:		char*	- Ptr to next free entry or NULL if tbl full 			*/
/*																								 	*/
/* NOTE: This caller of this function must check to be sure any entry any	*/
/*			entry to be added at the ptr returned will fit in the remaining	*/
/*			buffer area because the remaining buffer size may be less than	 	*/
/*			MAX_ENTRY_SIZE.																	*/
/*																								 	*/
/***************************************************************************/

char *GetNextFree( void )
{
	char		*pchPtr;

	for ( pchPtr = LieBuffer; *pchPtr != 0 && pchPtr < EndBuf;
			pchPtr += *pchPtr + 3 )
		;

	return( pchPtr < EndBuf ? pchPtr : NULL );
}

/***************************************************************************/
/* Opens the DOS system file and reads in the table offset and length		*/
/* structure. Then allocates a buffer and reads in the table.					*/
/*																									*/
/*	int ReadVersionTable( void )															*/
/*																									*/
/*	ARGUMENTS:	NONE																			*/
/*	RETURNS:		int	- OK if successful else error code							*/
/*																									*/
/***************************************************************************/

int ReadVersionTable( void )
{
	register		iStatus;						/* Function's return value				*/
	int			iFile;						/* DOS file handle 						*/
	unsigned		uRead;						/* Number of bytes read from file	*/


			/* Open the file and read in the max buffer len from stack seg		*/

	if ( _dos_open( Entry.Path, O_RDONLY, &iFile ) != S_OK )
		return( S_FILE_NOT_FOUND );

	if ( _dos_read( iFile, &ExeHdr, sizeof( ExeHdr ), &uRead ) == S_OK &&
		  uRead == sizeof( ExeHdr ) )
	{
		FileOffset += (long)(ExeHdr.HeaderParas * 16);
		if ( SeekRead( iFile, &DevHdr, FileOffset, sizeof( DevHdr ) ) == S_OK )
		{
			if ( strncmp( DevHdr.Name, szSetVer, 8 ) == S_OK &&
				  DevHdr.VersMajor == 1 )
			{
				FileOffset += DevHdr.TblOffset;
				if ( (LieBuffer = malloc( DevHdr.TblLen )) == NULL )
					iStatus = S_MEMORY_ERROR;

				else if ( SeekRead( iFile, LieBuffer, FileOffset,
							 DevHdr.TblLen ) == S_OK )
				{
					iStatus = S_OK;
					EndBuf = LieBuffer + DevHdr.TblLen;
				}
			}
			else
				iStatus = S_INVALID_SIG;
		}
		else
			iStatus = S_FILE_READ_ERROR;
	 }
	 else
		iStatus = S_FILE_READ_ERROR;
	_dos_close( iFile );

	return( iStatus );
}

/***************************************************************************/
/* Opens the DOS system file and writes the versin table back to the file. */
/*																									*/
/*	int WriteVersionTable( void )															*/
/*																									*/
/*	ARGUMENTS:	NONE																			*/
/*	RETURNS:		int	- OK if successful else error code							*/
/*																									*/
/***************************************************************************/

int WriteVersionTable( void )
{
	register			iStatus;					/* Function's return value				*/
	int				iFile;					/* DOS file handle						*/
	unsigned			uWritten;				/* Number of bytes written to file	*/
	struct find_t	Info;

	if ( _dos_findfirst( Entry.Path, _A_HIDDEN|_A_SYSTEM, &Info ) == S_OK &&
		  _dos_setfileattr( Entry.Path, _A_NORMAL ) == S_OK &&
		  _dos_open( Entry.Path, O_RDWR, &iFile ) == S_OK )
	{
		if ( _dos_seek( iFile, FileOffset, SEEK_SET ) == FileOffset &&
			  _dos_write(iFile, LieBuffer, DevHdr.TblLen, &uWritten ) == S_OK &&
			  uWritten == DevHdr.TblLen )
			iStatus = S_OK;
		else
			iStatus = S_FILE_WRITE_ERROR;

		_dos_setftime( iFile, Info.wr_date, Info.wr_time );
		_dos_close( iFile );
		_dos_setfileattr( Entry.Path, (UINT)((int)(Info.attrib)) );
	}
	else
		iStatus = S_FILE_NOT_FOUND;

	return( iStatus );
}

/***************************************************************************/
/* Seeks to the specified offset in a file and reads in the specified		*/
/* number of bytes into the caller's buffer.											*/
/*																									*/
/*	unsigned SeekRead( int iFile, char *Buf, long lOffset, unsigned uBytes )*/
/*																									*/
/*	ARGUMENTS:	iFile		- Open DOS file handle										*/
/*					Buf		- Ptr to read buffer											*/
/*					lOffset	- Offset in file to start reading at					*/
/*					uBytes	- Number of bytes to read									*/
/*	RETURNS:		unsigned	- S_OK if successfull else S_FILE_READ_ERROR			*/
/*																									*/
/***************************************************************************/

int SeekRead( int iFile, void *Buf, long lOffset, unsigned uBytes )
{
	unsigned		uRead;

	if ( _dos_seek( iFile, lOffset, SEEK_SET ) == lOffset &&
		  _dos_read( iFile, Buf, uBytes, &uRead ) == S_OK &&
		  uRead == uBytes )
		return( S_OK );
	else
		return( S_FILE_READ_ERROR );
}
