/*++

Copyright (c) 1994-1996  Microsoft Corporation

Module Name:

    unbufcp1.c

Abstract:

    This single-threaded version shows how to multiplex I/O to a number of files with 
    a single thread. This is the most efficient mechanism if you do not need the 
    asynchronous completion model that the dual-threaded version offers.

    Only one thread and one I/O completion port is used. The file handles for the
    source and destination file are both associated with the same port.  The
    thread starts off by posting a number of overlapped
    reads from the source file.  It then waits on the I/O completion port.
    Whenever a read completes, it immediately turns it around into a write to
    the destination file. Whenever a write completes, it immediately posts the
    next read from the source file.

    Thread 1
       |
       |
    kick off a few
    overlapped reads
       |
       |
  ->GetQueuedCompletionStatus(WritePort) <-----------
  |    |                                            |
  |    |-------------------------------             |
  |    |                              |             |
  | write has completed,      read has completed,   |
  | kick off another          kick off the write.   |
  | read                              |             |
  |    |                              |             |
  |____|                              |_____________|

Author:

    John Vert (jvert) 21-Dec-1994

Revision History:
  
    24-Apr-1995       Brian Sherrell (briansh) - Added platform detection & 
                      I/O Completion Port creation with INVALID_HANDLE_VALUE.

    22-March-1996     Brian Sherrell (briansh) - Pending I/O bug fix.

	 3-Apr-1996       Brian Sherrell (briansh) - Correct versioning logic.

--*/
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

//
// File handles for the copy operation. All read operations are
// from SourceFile. All write operations are to DestFile.
//
HANDLE SourceFile;
HANDLE DestFile;

//
//version information
//
OSVERSIONINFO     ver;

//
// I/O completion port. All read and writes to the files complete
// to this port.
//
HANDLE IoPort;

//
// Key values used to determine whether a read or a write
// has completed.
//
#define ReadKey 0
#define WriteKey 1

//
// Structure used to track each outstanding I/O. The maximum
// number of I/Os that will be outstanding at any time is
// controllable by the MAX_CONCURRENT_IO definition.
//

#define MAX_CONCURRENT_IO 20

typedef struct _COPY_CHUNK {
    OVERLAPPED Overlapped;
    LPVOID Buffer;
} COPY_CHUNK, *PCOPY_CHUNK;

COPY_CHUNK CopyChunk[MAX_CONCURRENT_IO];

//
// Define the size of the buffers used to do the I/O.
// 64K is a nice number.
//
#define BUFFER_SIZE (64*1024)

//
// The system's page size will always be a multiple of the
// sector size. Do all I/Os in page-size chunks.
//
DWORD PageSize;


//
// Local function prototype
//
VOID
CopyLoop(
    ULARGE_INTEGER FileSize
    );

int
_CRTAPI1
main(
    int argc,
    char *argv[],
    char *envp
    )
{
    ULARGE_INTEGER FileSize;
    ULARGE_INTEGER InitialSize;
    BOOL Success;
    DWORD Status;
    DWORD StartTime, EndTime;
    SYSTEM_INFO SystemInfo;
    HANDLE BufferedHandle;
    
    if (argc != 3) {
        fprintf(stderr, "Usage: %s SourceFile DestinationFile\n", argv[0]);
        exit(1);
    }

    //
    //confirm we are running on Windows NT 3.5 or greater, if not, display notice and 
    //terminate.  Completion ports are only supported on Win32 & Win32s.  Creating a 
    //Completion port with no handle specified is only supported on NT 3.51, so we need 
    //to know what we're running on.  Note, Win32s does not support console apps, thats 
    //why we exit here if we are not on Windows NT.
    //
    //
    //ver.dwOSVersionInfoSize needs to be set before calling GetVersionInfoEx()
    //
    ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

    //
    //Failure here could mean several things 1. On an NT system, 
    //it indicates NT version 3.1 because GetVersionEx() is only 
    //implemented on NT 3.5.  2. On Windows 3.1 system, it means 
    //either Win32s version 1.1 or 1.0 is installed.
    //
    Success = GetVersionEx((LPOSVERSIONINFO) &ver);

    if ( (!Success) ||                                   //GetVersionEx() failed - see above.
         (ver.dwPlatformId != VER_PLATFORM_WIN32_NT) )   //GetVersionEx() succeeded but we are not on NT.
      {
       MessageBox(NULL,
                  "This sample application can only be run on Windows NT. 3.5 or greater\n"
                  "This application will now terminate.",
                  "UnBufCp1",
                  MB_OK           | 
                  MB_ICONSTOP     | 
                  MB_SETFOREGROUND );
       exit( 1 );
      }


    //
    // Get the system's page size.
    //
    GetSystemInfo(&SystemInfo);
    PageSize = SystemInfo.dwPageSize;

    //
    // Open the source file and create the destination file.
    // Use FILE_FLAG_NO_BUFFERING to avoid polluting the
    // system cache with two copies of the same data.
    //

    SourceFile = CreateFile(argv[1],
                            GENERIC_READ | GENERIC_WRITE,
                            FILE_SHARE_READ,
                            NULL,
                            OPEN_EXISTING,
                            FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED,
                            NULL);
    if (SourceFile == INVALID_HANDLE_VALUE) {
        fprintf(stderr, "failed to open %s, error %d\n", argv[1], GetLastError());
        exit(1);
    }
    FileSize.LowPart = GetFileSize(SourceFile, &FileSize.HighPart);
    if ((FileSize.LowPart == 0xffffffff) && (GetLastError() != NO_ERROR)) {
        fprintf(stderr, "GetFileSize failed, error %d\n", GetLastError());
        exit(1);
    }

    DestFile = CreateFile(argv[2],
                          GENERIC_READ | GENERIC_WRITE,
                          FILE_SHARE_READ | FILE_SHARE_WRITE,
                          NULL,
                          CREATE_ALWAYS,
                          FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED,
                          SourceFile);
    if (DestFile == INVALID_HANDLE_VALUE) {
        fprintf(stderr, "failed to open %s, error %d\n", argv[2], GetLastError());
        exit(1);
    }

    //
    // Extend the destination file so that the filesystem does not
    // turn our asynchronous writes into synchronous ones.
    //
    InitialSize.QuadPart = (FileSize.QuadPart + PageSize - 1) & ~(PageSize-1);
    Status = SetFilePointer(DestFile,
                            InitialSize.LowPart,
                            (PLONG)&InitialSize.HighPart,
                            FILE_BEGIN);
    if ((Status == 0xffffffff) && (GetLastError() != NO_ERROR)) {
        fprintf(stderr, "initial SetFilePointer failed, error %d\n", GetLastError());
        exit(1);
    }
    Success = SetEndOfFile(DestFile);
    if (!Success) {
        fprintf(stderr, "SetEndOfFile failed, error %d\n", GetLastError());
        exit(1);
    }

    //
    //In NT 3.51 it is not necessary to specify the FileHandle parameter
    //of CreateIoCompletionPort()--It is legal to specify the FileHandle
    //as INVALID_HANDLE_VALUE.  However, for NT 3.5 an overlapped file
    //handle is needed.
    //
    //We know already that we are running on NT, or else we wouldn't have
    //gotten this far, so lets see what version we are running on.
    //
    if (ver.dwMajorVersion == 3 && ver.dwMinorVersion == 50) //we're running on NT 3.5 - Completion Ports exists.
      IoPort = CreateIoCompletionPort(SourceFile,            //file handle to associate with I/O completion port.                       
                                      NULL,                  //optional handle to existing I/O completion port.
                                      ReadKey,               //completion key.
                                      1);                    //# of threads allowed to execute concurrently.
    else                                                 
        {
          //                                                 //we are running on NT 3.51 or greater      
          //Create the I/O Completion Port
          //
          IoPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,//file handle to associate with I/O completion port
                                          NULL,                //optional handle to existing I/O completion port
                                          ReadKey,             //completion key
                                          1);                  //# of threads allowed to execute concurrently
  
   
          if (IoPort == NULL) {
            fprintf(stderr, "failed to create ReadPort, error %d\n", GetLastError());
            exit(1);
          }

         //
         //If we need to, aka we're running on NT 3.51, let's associate a file handle with the
         //completion port.
         //
    
         IoPort = CreateIoCompletionPort(SourceFile,
                                         IoPort,
                                         ReadKey,            
                                         1);

         if (IoPort == NULL)
           {
             fprintf(stderr,
                     "failed to create IoPort, error %d\n", 
                     GetLastError());

             exit(1);

           }
        }
    
    //
    // Associate the destination file handle with the
    // I/O completion port.
    //
    
    IoPort = CreateIoCompletionPort(DestFile,
                                    IoPort,
                                    WriteKey,
                                    1);
    if (IoPort == NULL) {
        fprintf(stderr, "failed to create WritePort, error %d\n", GetLastError());
        exit(1);
    }

    

    StartTime = GetTickCount();

    //
    // Do the copy
    //
    CopyLoop(FileSize);

    EndTime = GetTickCount();

    CloseHandle(SourceFile);
    CloseHandle(DestFile);

    //
    // We need another handle to the destination file that is
    // opened without FILE_FLAG_NO_BUFFERING. This allows us to set
    // the end-of-file marker to a position that is not sector-aligned.
    //
    BufferedHandle = CreateFile(argv[2],
                                GENERIC_WRITE,
                                FILE_SHARE_READ | FILE_SHARE_WRITE,
                                NULL,
                                OPEN_EXISTING,
                                0,
                                NULL);
    if (BufferedHandle == INVALID_HANDLE_VALUE) {
        fprintf(stderr,
                "failed to open buffered handle to %s, error %d\n",
                argv[2],
                GetLastError());
        exit(1);
    }


    //
    // Set the destination's file size to the size of the
    // source file, in case the size of the source file was
    // not a multiple of the page size.
    //
    Status = SetFilePointer(BufferedHandle,
                            FileSize.LowPart,
                            (PLONG)&FileSize.HighPart,
                            FILE_BEGIN);
    if ((Status == 0xffffffff) && (GetLastError() != NO_ERROR)) {
        fprintf(stderr, "final SetFilePointer failed, error %d\n", GetLastError());
        exit(1);
    }
    Success = SetEndOfFile(BufferedHandle);
    if (!Success) {
        fprintf(stderr, "SetEndOfFile failed, error %d\n", GetLastError());
        exit(1);
    }
    CloseHandle(BufferedHandle);

    printf("%d bytes copied in %.3f seconds\n",
           FileSize.LowPart,
           (float)(EndTime-StartTime)/1000.0);
    printf("%.2f MB/sec\n",
           ((LONGLONG)FileSize.QuadPart/(1024.0*1024.0)) / (((float)(EndTime-StartTime)) / 1000.0));

    return(0);

}

VOID
CopyLoop(
    ULARGE_INTEGER FileSize
    )
{
    ULARGE_INTEGER ReadPointer;
    BOOL Success;
    DWORD NumberBytes;
    LPOVERLAPPED CompletedOverlapped;
    DWORD Key;
    PCOPY_CHUNK Chunk;
    int PendingIO = 0;
    int i;

    //
    // Start reading the file. Kick off MAX_CONCURRENT_IO reads, then just
    // loop waiting for I/O to complete.
    //
    ReadPointer.QuadPart = 0;

    for (i=0; i < MAX_CONCURRENT_IO; i++) {
        if (ReadPointer.QuadPart >= FileSize.QuadPart) {
            break;
        }
        //
        // Use VirtualAlloc so we get a page-aligned buffer suitable
        // for unbuffered I/O.
        //
        CopyChunk[i].Buffer = VirtualAlloc(NULL,
                                           BUFFER_SIZE,
                                           MEM_COMMIT,
                                           PAGE_READWRITE);
        if (CopyChunk[i].Buffer == NULL) {
            fprintf(stderr, "VirtualAlloc %d failed, error %d\n",i, GetLastError());
            exit(1);
        }
        CopyChunk[i].Overlapped.Offset = ReadPointer.LowPart;
        CopyChunk[i].Overlapped.OffsetHigh = ReadPointer.HighPart;
        CopyChunk[i].Overlapped.hEvent = NULL;     // not needed

        Success = ReadFile(SourceFile,
                           CopyChunk[i].Buffer,
                           BUFFER_SIZE,
                           &NumberBytes,
                           &CopyChunk[i].Overlapped);

        if (!Success && (GetLastError() != ERROR_IO_PENDING)) {
            fprintf(stderr,
                    "ReadFile at %lx failed, error %d\n",
                    ReadPointer.LowPart,
                    GetLastError());
        } else {
            ReadPointer.QuadPart += BUFFER_SIZE;
            ++PendingIO;
        }
    }

    //
    // We have started the initial async. reads, enter the main loop.
    // This simply waits until an I/O completes, then issues the next
    // I/O.  When a write completes, the next read is issued. When a
    // read completes, the corresponding write is issued.
    //
    while (PendingIO) {
        Success = GetQueuedCompletionStatus(IoPort,
                                            &NumberBytes,
                                            &Key,
                                            &CompletedOverlapped,
                                            (DWORD)-1);
        if (!Success && (CompletedOverlapped == NULL)) {
            //
            // The call has failed.
            //
            fprintf(stderr,
                    "GetQueuedCompletionStatus on the IoPort failed, error %d\n",
                    GetLastError());
            exit(1);
        }
        if (!Success) {
            //
            // The call has succeeded, but the initial I/O operation
            // has failed.
            //
            fprintf(stderr,
                    "GetQueuedCompletionStatus on the IoPort removed a failed\n"
                    "I/O packet, error %d\n",GetLastError());
            //
            // This is probably bad, but what the heck, it's only a demo program,
            // so proceed blindly ahead.
            //
        }

        Chunk = (PCOPY_CHUNK)CompletedOverlapped;

        if (Key == ReadKey) {

            //
            // A read has completed, issue the corresponding write.
            //

            //
            // Round the number of bytes to write up to a sector boundary.
            //
            NumberBytes = (NumberBytes + PageSize - 1) & ~(PageSize-1);

            Success = WriteFile(DestFile,
                                Chunk->Buffer,
                                NumberBytes,
                                &NumberBytes,
                                &Chunk->Overlapped);

            if (!Success && (GetLastError() != ERROR_IO_PENDING))
                fprintf(stderr,
                        "WriteFile at %lx failed, error %d\n",
                        Chunk->Overlapped.Offset,
                        GetLastError());
            
            //
            // decrement pending I/O count
            //
            --PendingIO; 
            
        } else if (Key == WriteKey) {

            //
            // A write has completed, issue the next read.
            //

            if (ReadPointer.QuadPart < FileSize.QuadPart) {
                Chunk->Overlapped.Offset = ReadPointer.LowPart;
                Chunk->Overlapped.OffsetHigh = ReadPointer.HighPart;
                ReadPointer.QuadPart += BUFFER_SIZE;
                Success = ReadFile(SourceFile,
                                   Chunk->Buffer,
                                   NumberBytes,
                                   &NumberBytes,
                                   &Chunk->Overlapped);
                if (!Success && (GetLastError() != ERROR_IO_PENDING)) {
                    fprintf(stderr,
                            "ReadFile at %lx failed, error %d\n",
                            Chunk->Overlapped.Offset,
                            GetLastError());
                }
            } else {
                //
                // There are no more reads left to issue, just
                // wait for the pending writes to drain.
                //
                --PendingIO;
            }
        }
    }
}
