Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to do an unbuffered file transfer using API ReadFile() WriteFile()?

Tags:

winapi

delphi

The problem is that when I disable the cache (setting FILE_FLAG_NO_BUFFERING in CreateFile) I must pass to the read and write function a number of bytes that is a multiple of 512 (sector size). I use a buffer of 10MB and everything it's ok... until the last buffer operation. The last buffer does not have a multiple of 512 number of bytes. So, how do I read and write this final portion of the file ?

This is what I wrote so far...

procedure MarusCopyFileNoCache(SrcName,DestName:string);
const BufSize = 10485760; {10MB}
var Src,Dest:THandle;
    Buffer:Pointer;
    Bufs,x:integer;
    AllSize:int64;
    N,junk:DWord;
begin
 Src:=CreateFileW(PWideChar('\\?\'+SrcName), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING, 0);
 Dest:=CreateFileW(PWideChar('\\?\'+DestName), GENERIC_WRITE, 0, nil, CREATE_ALWAYS, FILE_FLAG_NO_BUFFERING, 0);
 try
   AllSize:=MFileSize(Src); {this is my function to get the int64 file size} 
   Bufs:=Ceil(AllSize/BufSize);
   GetMem(Buffer,BufSize);
   try
    for x:=1 to Bufs do begin
     ReadFile(Src, Buffer^, BufSize, N, nil);
     WriteFile(Dest, Buffer^, N, junk, nil);
    end;
   finally
    FreeMem(Buffer,BufSize);
   end;
 finally
  CloseHandle(Src);
  CloseHandle(Dest);
 end;
end;
like image 305
Marus Gradinaru Avatar asked Sep 06 '14 18:09

Marus Gradinaru


People also ask

What is WriteFile?

writefile invokes the open, write, and close callable services to write or append text files, with lines up to 1024 characters long.

What is Readfile?

Reads data from the specified file or input/output (I/O) device. Reads occur at the position specified by the file pointer if supported by the device. This function is designed for both synchronous and asynchronous operations. For a similar function designed solely for asynchronous operation, see ReadFileEx.


1 Answers

When using FILE_FLAG_NO_BUFFERING, you have to read and write full sectors, but the last buffer will be a partial sector (ReadFile() will report fewer bytes read than the full sector size) . When you write that last buffer, you still have to write it as a full sector or else WriteFile() will fail. So obviously the output file size may be too large. However, since you do know the desired file size, you can use SetFileInformationByHandle() to set the output file's final size after you are done writing to it and before you close its handle.

For example:

type
  FILE_INFO_BY_HANDLE_CLASS = (
    FileBasicInfo                   = 0,
    FileStandardInfo                = 1,
    FileNameInfo                    = 2,
    FileRenameInfo                  = 3,
    FileDispositionInfo             = 4,
    FileAllocationInfo              = 5,
    FileEndOfFileInfo               = 6,
    FileStreamInfo                  = 7,
    FileCompressionInfo             = 8,
    FileAttributeTagInfo            = 9,
    FileIdBothDirectoryInfo         = 10, // 0xA
    FileIdBothDirectoryRestartInfo  = 11, // 0xB
    FileIoPriorityHintInfo          = 12, // 0xC
    FileRemoteProtocolInfo          = 13, // 0xD
    FileFullDirectoryInfo           = 14, // 0xE
    FileFullDirectoryRestartInfo    = 15, // 0xF
    FileStorageInfo                 = 16, // 0x10
    FileAlignmentInfo               = 17, // 0x11
    FileIdInfo                      = 18, // 0x12
    FileIdExtdDirectoryInfo         = 19, // 0x13
    FileIdExtdDirectoryRestartInfo  = 20, // 0x14
    MaximumFileInfoByHandlesClass);

  FILE_END_OF_FILE_INFO = record
    EndOfFile: LARGE_INTEGER;
  end;

function SetFileInformationByHandle(
  hFile: THandle;
  FileInformationClass: FILE_INFO_BY_HANDLE_CLASS;
  lpFileInformation: Pointer;
  dwBufferSize: DWORD
  ): BOOL; stdcall; external 'kernel32.dll' delayed;

procedure MarcusCopyFileNoCache(SrcName, DestName: string);
const
  BufSize = 10485760; {10MB}
var
  Src, Dest: THandle;
  Buffer: PByte;
  FinalSize: Int64;
  SectorSize, N, ignored: DWORD;
  eof: FILE_END_OF_FILE_INFO;
begin
  Src := CreateFile(PChar('\\?\'+SrcName), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING, 0);
  if Src = INVALID_HANDLE_VALUE then RaiseLastOSError;
  try
    Dest := CreateFile(PChar('\\?\'+DestName), GENERIC_WRITE, 0, nil, CREATE_ALWAYS, FILE_FLAG_NO_BUFFERING, 0);
    if Dest = INVALID_HANDLE_VALUE then RaiseLastOSError;
    try
      try
        FinalSize := 0;
        SectorSize := 512; // <-- TODO: determine this dynamically at runtime
        GetMem(Buffer, BufSize);
        try
          repeat
            if not ReadFile(Src, Buffer^, BufSize, N, nil) then RaiseLastOSError;
            if N = 0 then Break; // EOF reached
            Inc(FinalSize, N);
            // round up the number of bytes read to the next sector boundary for writing
            N := (N + (SectorSize-1)) and (not (SectorSize-1));
            if not WriteFile(Dest, Buffer^, N, ignored, nil) then RaiseLastOSError;
          until False;
        finally
          FreeMem(Buffer, BufSize);
        end;
        // now set the final file size
        eof.EndOfFile.QuadPart := FinalSize;
        if not SetFileInformationByHandle(Dest, FileEndOfFileInfo, @eof, SizeOf(eof)) then RaiseLastOSError;
      finally
        CloseHandle(Dest);
      end;
    except
      DeleteFile(PChar(DestName));
      raise;
    end;
  finally
    CloseHandle(Src);
  end;
end;
like image 103
Remy Lebeau Avatar answered Oct 06 '22 13:10

Remy Lebeau