// to compile for ANSI:
// 		cl lnw.cpp
// to compile for Unicode:
//		cl -DUNICODE lnw.cpp



// make sure the C headers see the same Unicode mode as the Windows headers

#if defined( UNICODE ) && ! defined( _UNICODE )
#define _UNICODE
#endif

#if defined( _UNICODE ) && ! defined( UNICODE )
#define UNICODE
#endif



#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <string.h>

#pragma hdrstop
#pragma comment( lib, "advapi32.lib" )


extern "C" {
typedef BOOL (__stdcall *chl_t)( LPCTSTR toFile, LPCTSTR fromFile, LPSECURITY_ATTRIBUTES sa );
}



// these must always be ANSI!
#ifdef UNICODE
#define CREATEHARDLINK "CreateHardLinkW"
#else
#define CREATEHARDLINK "CreateHardLinkA"
#endif

#define err doerr( _T( __FILE__ ), __LINE__ )



void doerr( const TCHAR *file, int line )
{
	DWORD e;

	e = GetLastError();
	if ( e == 0 )
		return;

	_tprintf( _T( "%s(%d): gle = %lu\n" ), file, line, e );
	exit( 2 );
}



void enableprivs()
{
	HANDLE hToken;
	byte buf[sizeof TOKEN_PRIVILEGES * 2];
	TOKEN_PRIVILEGES & tkp = *( (TOKEN_PRIVILEGES *) buf );

	if ( ! OpenProcessToken( GetCurrentProcess(),
		TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken ) )
		err;

	// enable SeBackupPrivilege, SeRestorePrivilege

	if ( !LookupPrivilegeValue( NULL, SE_BACKUP_NAME, &tkp.Privileges[0].Luid ) )
		err;

	if ( !LookupPrivilegeValue( NULL, SE_RESTORE_NAME, &tkp.Privileges[1].Luid ) )
		err;

	tkp.PrivilegeCount = 2;
	tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
	tkp.Privileges[1].Attributes = SE_PRIVILEGE_ENABLED;

	if ( ! AdjustTokenPrivileges( hToken, FALSE, &tkp, sizeof tkp, NULL, NULL ) )
		err;
}



#define offsetof(t,m) ((size_t) &(((t *) 0)->m))

void CreateHardLinkNt4( const TCHAR *fromFile, const TCHAR *toFile )
{
	HANDLE fh;
	static TCHAR buf1[MAX_PATH];
	TCHAR *p;
	void *ctx = NULL;
	WIN32_STREAM_ID wsi;
	static wchar_t buf2[MAX_PATH * 2];
	DWORD numwritten;

	enableprivs(); // in case we aren't admin

	fh = CreateFile( fromFile, GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
		FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS, NULL );
	if ( fh == INVALID_HANDLE_VALUE || fh == NULL )
		err;

	GetFullPathName( toFile, MAX_PATH, &buf1[0], &p );

	wsi.dwStreamId = BACKUP_LINK;
	wsi.dwStreamAttributes = 0;
	wsi.dwStreamNameSize = 0;
#ifndef UNICODE
	MultiByteToWideChar( CP_ACP, 0, buf1, strlen( buf1 ) + 1, buf2, MAX_PATH );
#else
	_tcscpy( buf2, buf1 );
#endif
	wsi.Size.QuadPart = ( wcslen( buf2 ) + 1 ) * sizeof( wchar_t );

	if ( ! BackupWrite( fh, (byte *) &wsi, offsetof( WIN32_STREAM_ID, cStreamName ), &numwritten, FALSE, FALSE, &ctx ) )
		err;
	if ( numwritten != offsetof( WIN32_STREAM_ID, cStreamName ) )
		err;

	if ( ! BackupWrite( fh, (byte *) buf2, wsi.Size.LowPart, &numwritten, FALSE, FALSE, &ctx ) )
		err;
	if ( numwritten != wsi.Size.LowPart )
		err;

	// make NT release the context
	BackupWrite( fh, (byte *) &buf1[0], 0, &numwritten, TRUE, FALSE, &ctx );

	CloseHandle( fh );
}



bool CreateHardLinkNt5( const TCHAR *fromFile, const TCHAR *toFile )
{
	chl_t chl; // pointer to CreateHardLink()
	bool tryOldMethod = true; // assume we are not on NT5

	// first, try the easy (NT5) way
	HMODULE hmk32 = LoadLibrary( _T( "kernel32.dll" ) );
	if ( hmk32 > (HMODULE) 32 )
	{
		chl = (chl_t) GetProcAddress( hmk32, CREATEHARDLINK );
		if ( chl != NULL ) // seems to be NT5 or so
		{
			// we have found the API, no need for clumsy stuff
			tryOldMethod = false;

			if ( ! chl( toFile, fromFile, NULL ) )
				_tprintf( _T( "CreateHardLink( \"%s\", \"%s\" ) failed with error %lu.\n" ),
					toFile, fromFile, GetLastError() );
		}
		FreeLibrary( hmk32 );
	}

	return tryOldMethod;
}


#ifdef UNICODE
int wmain( int argc, TCHAR *argv[] )
#else
int main( int argc, TCHAR *argv[] )
#endif
{
	if ( argc != 3 )
	{
		_tprintf( _T( "usage: lnw {file} {new_link_name}\n" ) );
		return 1;
	}

	if ( CreateHardLinkNt5( argv[1], argv[2] ) )
		CreateHardLinkNt4( argv[1], argv[2] );

	return 0;
}

