/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/NPL/
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation. All
 * Rights Reserved.
 *
 * Contributor(s): 
 *
 * Modified by David.Gardiner@unisa.edu.au
 *	added -i option, which prepends a #line with the absolute path of the file. Useful
 *		for debugging Mozilla on Windows.
 *	added hard symbolic link instead of copy for NTFS partitions
 *	added multi-copy, so a number of files can be copied to the same destination at once.
 *
 */

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <direct.h>
#include <sys/stat.h>
#include <sys/utime.h>
#include <io.h>
#include <fcntl.h>

/*
	Unicode calls are linked at run-time, so that the application can run under
	Windows NT and 95 (which doesn't support the Unicode calls)

	The following APIs are linked:
		BackupWrite
		CreateFileW
		GetFullPathNameW
*/

//static const char *prog;

BOOL insertHashLine = FALSE;
BOOL trySymlink = FALSE;
BOOL recurse = FALSE;

typedef WINBASEAPI BOOL (WINAPI* LPFNBackupWrite)(HANDLE, LPBYTE, DWORD, LPDWORD, BOOL, BOOL, LPVOID *);
typedef WINBASEAPI HANDLE (WINAPI* LPFNCreateFileW)(LPCWSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE);
typedef WINBASEAPI DWORD (WINAPI* LPFNGetFullPathNameW)(LPCWSTR, DWORD, LPWSTR, LPWSTR *);

// Function pointers (used for NTFS hard links)
LPFNBackupWrite lpfnDllBackupWrite = NULL;    
LPFNCreateFileW lpfnDllCreateFileW = NULL;
LPFNGetFullPathNameW lpfnDllGetFullPathNameW = NULL;

// Handle to DLL
HINSTANCE hDLL = NULL;               

/*
** Flip any "unix style slashes" into "dos style backslashes" 
*/
inline void FlipSlashes(char *name)
{
    for( int i=0; name[i]; i++ ) {
        if( name[i] == '/' ) name[i] = '\\';
    }
}

/*
 * Flip any "dos style backslashes" into "unix style slashes"
 */
inline void UnflipSlashes(char *name)
{
    for( int i=0; name[i]; i++ ) {
        if( name[i] == '\\' ) name[i] = '/';
    }
}

int MakeDir( char *path )
{
    char *cp, *pstr;
    struct stat sb;

    pstr = path;
    while( cp = strchr(pstr, '\\') ) {
        *cp = '\0';
        
        if( !(stat(path, &sb) == 0 && (sb.st_mode & _S_IFDIR) )) {
            /* create the new sub-directory */
            printf("+++ makecopy: creating directory %s\n", path);
            if( mkdir(path) < 0 ) {
                return -1;
            }
        } /* else sub-directory already exists.... */

        *cp = '\\';
        pstr = cp+1;
    }

	return 0;
}

/*
 * Display error code and message for last error
 */
int ReportError()
{        
	LPVOID lpMsgBuf = NULL;

	DWORD err = GetLastError();
		
	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, 
		NULL,
		err,
		0,
		(LPTSTR) &lpMsgBuf,
		0,
		NULL);

	fprintf(stderr, "%u, %s\n", err, (LPCTSTR) lpMsgBuf ) ;

	LocalFree( lpMsgBuf );

	return -1;
}

int ReportError(const char* msg)
{
	fprintf(stderr, "%Error: s\n", msg);
	return ReportError();
}


/*
	Creates an NTFS hard link of src at dest.
	NT5 will have a CreateHardLink API which will do the same thing, but a lot simpler
	This is based on the MSDN code sample Q153181

 */
BOOL hardSymLink(LPCSTR src, LPCSTR dest)
{ 
    WCHAR FileLink[ MAX_PATH + 1 ];
    WCHAR FileSource[ MAX_PATH + 1 ];
    WCHAR FileDest[ MAX_PATH + 1 ];
    LPWSTR FilePart;

    WIN32_STREAM_ID StreamId;
    DWORD dwBytesWritten;
    DWORD cbPathLen;

    BOOL bSuccess;

	// Convert src and dest to Unicode
    if (!MultiByteToWideChar(CP_ACP, 0, src, -1, FileSource, MAX_PATH)) {
        ReportError("Convert to WCHAR (source)");
        return FALSE;
    }

    if (!MultiByteToWideChar(CP_ACP, 0, dest, -1, FileDest, MAX_PATH)) {
        ReportError("Convert to WCHAR (destination)");
        return FALSE;
    }

    //
    // open existing file that we link to
    //

    HANDLE hFileSource = lpfnDllCreateFileW(
        FileSource,
        FILE_WRITE_ATTRIBUTES,
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        NULL, // sa
        OPEN_EXISTING,
        0,
        NULL
        );

    if(hFileSource == INVALID_HANDLE_VALUE) {
        ReportError("CreateFile (source)");
        return FALSE;
    }

    //
    // validate and sanitize supplied link path and use the result
    // the full path MUST be Unicode for BackupWrite
    //

    cbPathLen = lpfnDllGetFullPathNameW( FileDest, MAX_PATH, FileLink, &FilePart); 

    if(cbPathLen == 0) {
        ReportError("GetFullPathName");
        return FALSE;
    }

    cbPathLen = (cbPathLen + 1) * sizeof(WCHAR); // adjust for byte count 

    //
    // it might also be a good idea to verify the existence of the link,
    // (and possibly bail), as the file specified in FileLink will be
    // overwritten if it already exists
    //

    //
    // prepare and write the WIN32_STREAM_ID out
    //

    LPVOID lpContext = NULL;

    StreamId.dwStreamId = BACKUP_LINK;
    StreamId.dwStreamAttributes = 0;
    StreamId.dwStreamNameSize = 0;
    StreamId.Size.HighPart = 0;
    StreamId.Size.LowPart = cbPathLen;

    //
    // compute length of variable size WIN32_STREAM_ID
    //

    DWORD StreamHeaderSize = (LPBYTE)&StreamId.cStreamName - (LPBYTE)&
        StreamId+ StreamId.dwStreamNameSize ;

    bSuccess = lpfnDllBackupWrite(
        hFileSource,
        (LPBYTE)&StreamId,  // buffer to write
        StreamHeaderSize,   // number of bytes to write
        &dwBytesWritten,
        FALSE,              // don't abort yet
        FALSE,              // don't process security
        &lpContext
        );

    if(bSuccess) {

        //
        // write out the buffer containing the path
        //

        bSuccess = lpfnDllBackupWrite(
            hFileSource,
            (LPBYTE)FileLink,   // buffer to write
            cbPathLen,          // number of bytes to write
            &dwBytesWritten,
            FALSE,              // don't abort yet
            FALSE,              // don't process security
            &lpContext
            );

        //
        // free context
        //

        lpfnDllBackupWrite(
            hFileSource,
            NULL,               // buffer to write
            0,                  // number of bytes to write
            &dwBytesWritten,
            TRUE,               // abort
            FALSE,              // don't process security
            &lpContext
            );
    }

    CloseHandle( hFileSource );

    if(!bSuccess) {
        ReportError("BackupWrite");
        return FALSE;
    }

    return TRUE;
} 

int CopyIfNecessary(char *oldFile, char *newFile)
{
    struct stat newsb;
	struct stat oldsb;	

	// Use stat to find file details
	if (stat(oldFile, &oldsb)) {
		return -1;
	}

    // skip directories unless recursion flag is set
    if ( oldsb.st_mode & _S_IFDIR ) {
        if (!recurse) {
    	    printf("    Skipping directory %s\n", oldFile);
            return 0;
        }
        else {
            char *lastDir;
            char *oldFileName; // points to where file name starts in oldFile
            char *newFileName; // points to where file name starts in newFile
            WIN32_FIND_DATA findFileData;

            // weed out special "." and ".." directories
            lastDir = strrchr(oldFile, '\\');
            if ( lastDir )
                ++lastDir;
            else
                lastDir = oldFile;
            if ( strcmp( lastDir, "." ) == 0 || strcmp( lastDir, ".." ) == 0 )
                return 0;

            // find and process the contents of the directory
            oldFileName = oldFile + strlen(oldFile);
            strcpy(oldFileName, "\\*");
            ++oldFileName;

            newFileName = newFile + strlen(newFile);
            strcpy(newFileName, "\\");
            ++newFileName;

            if( MakeDir(newFile) < 0 ) {
                fprintf(stderr, "\n+++ makecopy: unable to create directory %s\n", newFile);
                return 1;
            }

            HANDLE hFindFile = FindFirstFile(oldFile, &findFileData);
            if (hFindFile != INVALID_HANDLE_VALUE) {
                do {
                    strcpy(oldFileName, findFileData.cFileName);
                    strcpy(newFileName, findFileData.cFileName);
                    CopyIfNecessary(oldFile, newFile);
                } while (FindNextFile(hFindFile, &findFileData) != 0);
            } else {
                fprintf(stderr, "\n+++ makecopy: no such file: %s\n", oldFile);
            }
            FindClose(hFindFile);
        }
        // nothing more we can do with a directory
        return 0;
    }

	if (!stat(newFile, &newsb)) {
		// If file times are equal, don't copy
		if (newsb.st_mtime == oldsb.st_mtime) {
#if 0
			printf("+++ makecopy: %s is up to date\n", newFile);
#endif
			return 0;
		}
	}


	char   fullPathName[ MAX_PATH + 1 ];
	LPTSTR filenamePart = NULL;

	char buffer[8192];
	DWORD bytesRead = 0;
	DWORD bytesWritten = 0;

	// find out required size
	GetFullPathName(oldFile, MAX_PATH, fullPathName, &filenamePart);

	// If we need to insert #line, the copying is a bit involved.
	if (insertHashLine == TRUE) {
		struct _utimbuf utim;

	    printf("    #Installing %s into %s\n", oldFile, newFile);

		utim.actime = oldsb.st_atime;
		utim.modtime = oldsb.st_mtime;	// modification time 

		HANDLE hNewFile = CreateFile(newFile, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, 
						CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,
						NULL);
		if (hNewFile == INVALID_HANDLE_VALUE) {
			return ReportError("CreateFile");
		}

		HANDLE hOldFile = CreateFile(oldFile, GENERIC_READ, FILE_SHARE_READ, NULL, 
							OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
							NULL);
		if (hOldFile == INVALID_HANDLE_VALUE) {
			return ReportError("CreateFile");
		}

		// Insert first line.
		sprintf(buffer, "#line 1 \"%s\"\r\n", fullPathName);

		// convert to unix.
		UnflipSlashes(buffer);

		WriteFile(hNewFile, buffer, strlen(buffer), &bytesWritten, NULL);

		// Copy file.
		do {
			if (!ReadFile(hOldFile, buffer, sizeof(buffer), &bytesRead, NULL)) {
				return ReportError("ReadFile");
			}

			if (!WriteFile(hNewFile, buffer, bytesRead, &bytesWritten, NULL)) {
				return ReportError("WriteFile");
			}

		} while (bytesRead > 0);

		CloseHandle(hNewFile);
		CloseHandle(hOldFile);

		// make copy have same time
		_utime(newFile, &utim);

	// If we don't need to do a #line, use an API to copy the file..
	} else {

		BOOL isNTFS = FALSE;
		
		// Find out what kind of volume this is.
		if ( trySymlink ) {
			char rootPathName[MAX_PATH];
			char *c = strchr(fullPathName, '\\');

			if (c != NULL) {
                TCHAR  fileSystemName[50];

				strncpy(rootPathName, fullPathName, (c - fullPathName) + 1);

				if (!GetVolumeInformation(rootPathName, NULL, 0, NULL, NULL, NULL, fileSystemName, sizeof(rootPathName))) {
					return ReportError("GetVolumeInformation");
				}

				isNTFS = (strcmp(fileSystemName, "NTFS") == 0);
			}
		}

		if (isNTFS) {
		    printf("    Symlinking %s into %s\n", oldFile, newFile);

			if (! hardSymLink(oldFile, newFile) ) {
				return 1;
			}
		} else {
		    printf("    Installing %s into %s\n", oldFile, newFile);

			if( ! CopyFile(oldFile, newFile, FALSE) ) {
				ReportError("CopyFile");
				return 1;
			}
		}
	}

    return 0;
}

void Usage(void)
{
    fprintf(stderr, "makecopy: [-cisx] <file1> [file2 ... fileN] <dir-path>\n");
    fprintf(stderr, "     -c  copy [default], cancels -s\n");
    fprintf(stderr, "     -i  add #line directive\n");
    fprintf(stderr, "     -r  recurse subdirectories\n");
    fprintf(stderr, "     -s  use symlinks on NT when possible\n");
    fprintf(stderr, "     -x  cancel -i\n");
}


int main( int argc, char *argv[] ) 
{
    char old_path[4096];
    char new_path[4096];
    char *oldFileName; // points to where file name starts in old_path
    char *newFileName; // points to where file name starts in new_path
    WIN32_FIND_DATA findFileData;
    int rv = 0;
	int i = 1;

    if (argc < 3) {
        Usage();
        return 2;
    }

    // parse option flags
    for ( ; *argv[i] == '-' ; ++i) {
        char *opt = argv[i]+1;
        for ( ; *opt; ++opt) {
            switch (*opt) {
            case 'c':
                trySymlink = FALSE;
                break;
            case 'i':
                insertHashLine = TRUE;
                break;
            case 'r':
                recurse = TRUE;
                break;
            case 's':
                trySymlink = TRUE;
                break;
            case 'x':
                insertHashLine = FALSE;
                break;
            default:
                Usage();
                return 2;
            }
        }
    }

    if ( trySymlink ) {
		OSVERSIONINFO osvi;

        // Symlinking supported only on WinNT, not Win9x
		// Is this Windows NT?
		osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

		if (!GetVersionEx(&osvi)) {
			return ReportError();
		}

		trySymlink = (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT);

		if ( trySymlink ) {

			hDLL = LoadLibrary("Kernel32");
			if (hDLL != NULL)
			{
				lpfnDllBackupWrite = (LPFNBackupWrite)GetProcAddress(hDLL, "BackupWrite");
				lpfnDllCreateFileW = (LPFNCreateFileW)GetProcAddress(hDLL, "CreateFileW");
				lpfnDllGetFullPathNameW = (LPFNGetFullPathNameW) GetProcAddress(hDLL, "GetFullPathNameW");

				if ((!lpfnDllBackupWrite) || (!lpfnDllCreateFileW) || (!lpfnDllGetFullPathNameW))
				{
					// handle the error
					int r = ReportError("GetProcAddress");

					FreeLibrary(hDLL);       
					return r;
				}
			} else {
				return ReportError();
			}
		}
	}

	// destination path is last argument
	strcpy(new_path, argv[argc-1]);

	// append backslash to path if not already there
	if (new_path[strlen(new_path)] != '\\') {
		strcat(new_path, "\\");
	}

    //sprintf(new_path, "%s\\", argv[i+1]);
    FlipSlashes(new_path);
    newFileName = new_path + strlen(new_path);

    if( MakeDir(new_path) < 0 ) {
        fprintf(stderr, "\n+++ makecopy: unable to create directory %s\n", new_path);
        return 1;
    }

	// copy all named source files
	while (i < (argc - 1)) {
		strcpy(old_path, argv[i]);

		FlipSlashes(old_path);
		oldFileName = strrchr(old_path, '\\');
		if (oldFileName) {
			oldFileName++;
		} else {
			oldFileName = old_path;
		}

		HANDLE hFindFile = FindFirstFile(old_path, &findFileData);

		if (hFindFile != INVALID_HANDLE_VALUE) {
			do {
				strcpy(oldFileName, findFileData.cFileName);
				strcpy(newFileName, findFileData.cFileName);
				rv = CopyIfNecessary(old_path, new_path);

			} while (FindNextFile(hFindFile, &findFileData) != 0);
		} else {
			fprintf(stderr, "\n+++ makecopy: no such file: %s\n", old_path);
		}

		FindClose(hFindFile);
		i++;
	}
	if ( trySymlink ) {
		FreeLibrary(hDLL);
	}
    return 0;
}
