BUG: Redirector Keeps Remote File's Original Size When CreateFile() Is Called with TRUNCATE_EXISTING (280718)



The information in this article applies to:

  • Microsoft Win32 Application Programming Interface (API), when used with:
    • the operating system: Microsoft Windows NT 4.0

This article was previously published under Q280718

SYMPTOMS

When applications open remote files by using the CreateFile function and specify TRUNCATE_EXISTING, subsequent file I/O functions such as WriteFile and GetFileInformationByHandle use the file's original size rather than the truncated size of zero bytes. However, immediately after the CreateFile call, the server does truncate the file to zero bytes. Therefore, the client seems to be confused.

CAUSE

The Windows NT 4.0 network redirector has a bug such that it correctly opens the remote file and truncates it on the server, but continues to remember the file's original size before truncation. This file size is then used in subsequent function calls to that file. Because the server successfully truncated the file, the client redirector should reset its internal copy of the file size to zero.

NOTE: This bug only affects Windows NT 4.0 clients.

RESOLUTION

To work around this problem, call the SetEndOfFile function immediately after calling CreateFile with TRUNCATE_EXISTING to open remote files. This method forces the client redirector internally to set the file size to zero. For example:
   hf = CreateFile (pszFileName, GENERIC_READ |GENERIC_WRITE,
                    0,
                    NULL, TRUNCATE_EXISTING,
                    FILE_ATTRIBUTE_NORMAL, NULL);
   if (hf == INVALID_HANDLE_VALUE)
   {
      // Handle error.
      printf ("error %lu:  could not open file %s\n", 
              GetLastError(), pszFileName);
      return (-1);
   }

   SetEndOfFile(hf);   // Force client redirector to set file size to zero.
   . . .
				

STATUS

Microsoft has confirmed that this is a bug in the Microsoft products that are listed at the beginning of this article.

MORE INFORMATION

When an application calls CreateFile with TRUNCATE_EXISTING to open a remote file, the client computer's network redirector translates the request into two Server Message Block (SMB) commands: "NT Create & X" and "transact2 Set File Info". The first SMB asks the server to open an existing file; the server does this, and then returns a response that includes the file's current size. The second SMB then asks the server to set the file's size to zero bytes; the server does this, and then responds that it was successful. The file on the server now has a size of zero bytes.

After these two SMBs are sent and the server responds, the Windows NT 4.0 client redirector internally keeps the size returned by the "NT Create & X" SMB instead of setting it to zero as it should. This size is used by the redirector in subsequent file I/O functions such as WriteFile and GetFileInformationByHandle.

Steps to Reproduce Behavior

Because this problem causes several file I/O functions to behave incorrectly, this article demonstrates two ways to reproduce the behavior and shows how to fix each.

CreateFile Followed by WriteFile

To see the problem when the CreateFile call is followed by WriteFile, compile the following code as a console application, and then run it from the command line with the name of a remote file as the single argument; for example, "TRUNC I:\TEST". The program writes 10 bytes to the file and closes it. On the server, the file has the original size with the 10 bytes of new data followed by zeros for the remaining data.

To see the correct behavior, remove the comment in front of the line that contains SetEndOfFile, recompile the application, and then run it with the name of a remote file. It will now correctly write 10 bytes to the file and close the file, leaving a 10-byte file on the server.
/*---------------------------------------------------------------------
TRUNC.CPP

Demonstrates a problem where the Windows NT 4.0 network redirector
opens a file with TRUNCATE_EXISTING but keeps the file's original size.

Compile from the command line with:
   cl trunc.cpp

Run from the command line with:
   trunc filename

where filename is the name of a remote file that already exists.

When the problem occurs, the file has its original size, but
it will have 10 bytes of "D" followed by zeros.

To see a fix to the problem, remove the comment from the line that
contains SetEndOfFile(). The remote file will then have a size of
10 bytes.
---------------------------------------------------------------------*/ 
#include <windows.h>
#include <stdio.h>

const int BUFF_LENGTH = 10;


int main (int argc, char **argv)
{
   DWORD  dwRetVal;
   HANDLE hf;
   BOOL   fResult;
   char * pszFileName;
   char   buffer[BUFF_LENGTH];
   DWORD  dwWritten;

   if (argc != 2)
   {
      printf("usage: cf <filename>\n");
      dwRetVal = -1;
      goto EXIT_DONE;
   }
   pszFileName = argv[1];


   hf = CreateFile (pszFileName, GENERIC_READ |GENERIC_WRITE,
                    0,
                    NULL, TRUNCATE_EXISTING,
                    FILE_ATTRIBUTE_NORMAL, NULL);
   if (hf == INVALID_HANDLE_VALUE)
   {
      printf ("error %lu:  could not open file %s\n",
              GetLastError(), pszFileName);
      dwRetVal = -1;
      goto EXIT_DONE;
   }

   // SetEndOfFile(hf);  // Force redirector to set file size to zero.

   FillMemory (buffer, BUFF_LENGTH, 'D');
   fResult = WriteFile (hf, &buffer, BUFF_LENGTH, &dwWritten, NULL);
   if (0 == fResult)
   {
      printf("error %lu:  couldn't write to %s\n",
             GetLastError(), pszFileName);
   }


   CloseHandle (hf);
   dwRetVal = 0;
   printf("truncated %s, wrote %d bytes to it\n",
          pszFileName, dwWritten);


EXIT_DONE:
   return (dwRetVal);
}
				

CreateFile Followed by GetFileInformationByHandle

To see the problem when the CreateFile call is followed by GetFileInformationByHandle, compile the following code as a console application, and then run it from the command line with the name of a remote file as the single argument; for example, "GFI I:\TEST". The program displays the file's original size, but it should be zero because it was truncated and the server shows a file size of zero.

To see the correct behavior, remove the comment in front of the line that contains SetEndOfFile, recompile the application, and then run it with the name of a remote file. It will show the file's correct size (zero).
/*---------------------------------------------------------------------
GFI.CPP

Demonstrates a problem where the Windows NT 4.0 network redirector
opens a file with TRUNCATE_EXISTING but keeps the file's original size.

Compile from the command line with:
   cl gfi.cpp

Run from the command line with:
   gfi filename

where filename is the name of a remote file that already exists.

To see a fix to the problem, remove the comment from the line that
contains SetEndOfFile().
---------------------------------------------------------------------*/ 
#include <windows.h>
#include <stdio.h>

void main (int argc, char **argv)
{
   HANDLE hf;
   BY_HANDLE_FILE_INFORMATION bhi;


   if (argc != 2)
   {
      printf("usage: %s <filename>\n", argv[0]);
      return;
   }

   hf = CreateFile (argv[1], GENERIC_READ|GENERIC_WRITE,
                    FILE_SHARE_READ|FILE_SHARE_WRITE,
                    NULL, TRUNCATE_EXISTING,
                    FILE_ATTRIBUTE_NORMAL, NULL);
   if (INVALID_HANDLE_VALUE == hf)
   {
      printf("couldn't open %s for truncation\n", argv[1]);
      return;
   }

   // SetEndOfFile(hf);  // Force redirector to set file size to zero.

   if (GetFileInformationByHandle (hf, &bhi))
      printf("size is %lu, should be 0\n", bhi.nFileSizeLow);

   CloseHandle (hf);
}
				

Modification Type:MajorLast Reviewed:2/25/2004
Keywords:kbAPI kbbug kbFileIO kbKernBase kbnofix KB280718