Bug 581838 - Make code to mark files as sparse use JS ctypes. Port the helper cpp/exe needed on Windows to JavaScript, and remove it. r=Standard8
☠☠ backed out by b5e4c1df95ef ☠ ☠
authorSiddharth Agarwal <sid.bugzilla@gmail.com>
Sat, 07 Aug 2010 22:49:24 +0530
changeset 6105 9d1f0b2cede7d06d1ef5642cf2cf9fb6fc32ac54
parent 6104 a5389bc6ee48c298f38df333ae253464ed6d0794
child 6106 87bf451a807715993b99090d4b7110f56e987d59
push idunknown
push userunknown
push dateunknown
reviewersStandard8
bugs581838
Bug 581838 - Make code to mark files as sparse use JS ctypes. Port the helper cpp/exe needed on Windows to JavaScript, and remove it. r=Standard8 Note that whatever's in subdirectories of mailnews/test gets installed by the mailnews Makefile.
mailnews/Makefile.in
mailnews/imap/test/unit/test_largeOfflineStore.js
mailnews/test/LargeOfflineStoreHelper.cpp
mailnews/test/Makefile.in
mailnews/test/resources/mailTestUtils.js
--- a/mailnews/Makefile.in
+++ b/mailnews/Makefile.in
@@ -53,20 +53,16 @@ PARALLEL_DIRS	+= \
 	extensions\
 	imap \
 	import \
 	local \
 	mime \
 	news \
 	$(NULL)
 
-ifdef ENABLE_TESTS
-PARALLEL_DIRS	+= test
-endif
-
 ifeq ($(OS_ARCH),WINNT)
 ifndef GNU_CC
 PARALLEL_DIRS	+= mapi/mapiDLL mapi/mapihook
 endif
 endif
 
 ifdef MOZ_STATIC_MAIL_BUILD
 DIRS += build
--- a/mailnews/imap/test/unit/test_largeOfflineStore.js
+++ b/mailnews/imap/test/unit/test_largeOfflineStore.js
@@ -4,16 +4,17 @@
  * stores, i.e., > 4GB.
  */
 
 var gIMAPDaemon, gServer, gIMAPIncomingServer;
 
 const gIMAPService = Cc["@mozilla.org/messenger/messageservice;1?type=imap"]
                        .getService(Ci.nsIMsgMessageService);
 
+load("../../mailnews/resources/mailTestUtils.js");
 load("../../mailnews/resources/messageGenerator.js");
 
 var gDownloadedOnce = false;
 var gIMAPInbox;
 var gOfflineStoreSize;
 
 function run_test()
 {
@@ -30,59 +31,39 @@ function run_test()
   // pref tuning: one connection only, turn off notifications
   let prefBranch = Cc["@mozilla.org/preferences-service;1"]
                      .getService(Ci.nsIPrefBranch);
   prefBranch.setBoolPref("mail.biff.play_sound", false);
   prefBranch.setBoolPref("mail.biff.show_alert", false);
   prefBranch.setBoolPref("mail.biff.show_tray_icon", false);
   prefBranch.setBoolPref("mail.biff.animate_dock_icon", false);
 
+  // Figure out the name of the IMAP inbox
+  let inboxFile = gIMAPIncomingServer.rootMsgFolder.filePath.clone();
+  inboxFile.append("INBOX");
+  if (!inboxFile.exists())
+    inboxFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0644", 8));
+
   // On Windows, check whether the drive is NTFS. If it is, mark the file as
   // sparse. If it isn't, then bail out now, because in all probability it is
   // FAT32, which doesn't support file sizes greater than 4 GB.
-  if ("@mozilla.org/windows-registry-key;1" in Cc)
+  if ("@mozilla.org/windows-registry-key;1" in Cc &&
+      get_file_system(inboxFile) != "NTFS")
   {
-    // Figure out the name of the IMAP inbox
-    let file = gIMAPIncomingServer.rootMsgFolder.filePath.clone();
-    file.append("INBOX");
-    if (!file.exists())
-      file.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0644", 8));
-
-    // Now call upon our helper
-    let helper = do_get_cwd().clone();
-    // '../../mailnews/LargeOfflineStoreHelper.exe'.
-    helper = helper.parent.parent;
-    helper.normalize();
-    helper.append("mailnews");
-    helper.append("LargeOfflineStoreHelper.exe");
-    if (!helper.exists()) {
-      do_throw(helper.leafName + " not found");
-    }
-
-    let helperProc = Cc["@mozilla.org/process/util;1"]
-                       .createInstance(Ci.nsIProcess);
-    helperProc.init(helper);
-    let args = [file.path];
-    // XXX This needs to be fixed to use runw once it lands (bug 411511)
-    helperProc.run(true, args, args.length);
-    let exitValue = helperProc.exitValue;
-
-    // 0 is success, 1 is "unable to run," and any other value is failure
-    if (exitValue == 1)
-    {
-      dump("On Windows, this test only works on NTFS volumes.\n");
-      endTest();
-      return;
-    }
-
-    if (exitValue != 0)
-    {
-      do_throw(helper.leafName + " failed with exit value " + exitValue +
-                 ", see above for details");
-    }
+    dump("On Windows, this test only works on NTFS volumes.\n");
+    endTest();
+    return;
+  }
+  let isFileSparse = mark_file_region_sparse(inboxFile, 0, 0x10000000f);
+  if (!isFileSparse && inboxFile.diskSpaceAvailable < 0x200000000)
+  {
+    dump("On systems where files can't be marked sparse, this test needs 8 " +
+         "GB of free disk space.\n");
+    endTest();
+    return;
   }
 
   let inbox = gIMAPDaemon.getMailbox("INBOX");
 
   let ioService = Cc["@mozilla.org/network/io-service;1"]
                     .getService(Ci.nsIIOService);
 
   // "Master" do_test_pending(), paired with a do_test_finished() at the end of
@@ -104,27 +85,16 @@ function run_test()
   dataUri = ioService.newURI("data:text/plain;base64," +
                    btoa(messages[1].toMessageString()),
                         null, null);
   imapMsg = new imapMessage(dataUri.spec, inbox.uidnext++, []);
   inbox.addMessage(imapMsg);
 
   // Get the IMAP inbox...
   let rootFolder = gIMAPIncomingServer.rootFolder;
-  let freeDiskSpace = rootFolder.filePath.diskSpaceAvailable;
-
-  // On Windows, the file is marked as sparse above. Linux file systems
-  // generally support sparse files automatically. OS X's HFS+, however, doesn't
-  // support sparse files, so check for at least 8 GB of free disk space before
-  // consuming 4 GB.
-  if ("nsILocalFileMac" in Ci && freeDiskSpace < 0x200000000) {
-    dump("On MacOSX, this test needs 8 GB of free disk space.\n");
-    endTest();
-    return;
-  }
 
   gIMAPInbox = rootFolder.getFolderWithFlags(Ci.nsMsgFolderFlags.Inbox);
   let outputStream = gIMAPInbox.offlineStoreOutputStream
                                .QueryInterface(Ci.nsISeekableStream);
   // seek to 15 bytes past 4GB.
   outputStream.seek(0, 0x10000000f);
   outputStream.write("from\r\n", 6);
   outputStream.close();
deleted file mode 100644
--- a/mailnews/test/LargeOfflineStoreHelper.cpp
+++ /dev/null
@@ -1,148 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla 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/MPL/
- *
- * 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
- * Mozilla Messaging, Inc.
- *
- * Portions created by the Initial Developer are Copyright (C) 2010
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Siddharth Agarwal <sid.bugzilla@gmail.com>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either of the GNU General Public License Version 2 or later (the "GPL"),
- * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-/**
- * A helper to large offline store tests on Windows. This:
- *
- * - Detects whether the volume of the given file (provided using argv[1]) is
- *   NTFS. If it isn't, then it returns the error code 1.
- * - If the volume is NTFS, then it proceeds to mark the given file as
- *   sparse. It also marks the first 4 GB + 15 bytes of the file as zero.
- */
-
-#include <windows.h>
-#include <stdio.h>
-#include <string.h>
-#include <winioctl.h>
-
-#define SUCCESS (0)
-#define UNABLE_TO_RUN (1)
-#define FAIL (2)
-
-int markFileAsSparse(HANDLE hFile)
-{
-  // Mark the file as sparse, and mark the first 4 GB + 15 bytes as a sparse
-  // region
-  DWORD bytesReturned;
-  FILE_SET_SPARSE_BUFFER sparseBuffer = {0};
-  sparseBuffer.SetSparse = 1;
-  if (!::DeviceIoControl(hFile, FSCTL_SET_SPARSE, &sparseBuffer,
-                         sizeof(sparseBuffer), NULL, 0, &bytesReturned, NULL))
-  {
-    fprintf(stderr, "Unable to mark file as sparse, error %d\n",
-            ::GetLastError());
-    return FAIL;
-  }
-
-  LARGE_INTEGER zdStart;
-  zdStart.QuadPart = 0;
-  LARGE_INTEGER zdEnd;
-  zdEnd.QuadPart = 0x10000000fLL;
-  FILE_ZERO_DATA_INFORMATION zdInfo = {0};
-  zdInfo.FileOffset = zdStart;
-  zdInfo.BeyondFinalZero = zdEnd;
-  if (!::DeviceIoControl(hFile, FSCTL_SET_ZERO_DATA, &zdInfo, sizeof(zdInfo),
-                         NULL, 0, &bytesReturned, NULL))
-  {
-    fprintf(stderr, "Unable to mark region as zero, error %d\n",
-            ::GetLastError());
-    return FAIL;
-  }
-
-  // Move to past the sparse region and mark it as the end of the file. The
-  // above DeviceIoControl call is useless unless followed by this.
-  if (!::SetFilePointerEx(hFile, zdEnd, NULL, FILE_BEGIN))
-  {
-    fprintf(stderr, "Unable to set file pointer to end, error %d\n",
-            ::GetLastError());
-    return FAIL;
-  }
-  if (!::SetEndOfFile(hFile))
-  {
-    fprintf(stderr, "Unable to set end of file, error %d\n", ::GetLastError());
-    return FAIL;
-  }
-
-  return SUCCESS;
-}
-
-int wmain(int argc, wchar_t* argv[])
-{
-  if (argc != 2)
-    return FAIL;
-
-  // The volume path should be at most 1 greater than than the length of the
-  // path -- add 1 for a trailing backslash if necessary, and 1 for the
-  // terminating null character
-  size_t volumePathLength = wcslen(argv[1]) + 2;
-  wchar_t* volumePath = new wchar_t[volumePathLength];
-  if (!::GetVolumePathNameW(argv[1], volumePath, volumePathLength))
-  {
-    fprintf(stderr, "Unable to get volume path for %s, error %d\n", argv[1],
-            ::GetLastError());
-    return FAIL;
-  }
-
-  wchar_t fsName[MAX_PATH + 1];
-  if (!::GetVolumeInformationW(volumePath, NULL, NULL, NULL, NULL,
-                               NULL, fsName, MAX_PATH + 1))
-  {
-    fprintf(stderr, "Unable to get volume information for %s, error %d\n",
-            argv[1], ::GetLastError());
-    return FAIL;
-  }
-
-  // We're only going to run the test on NTFS
-  if (wcscmp(fsName, L"NTFS"))
-    return UNABLE_TO_RUN;
-
-  HANDLE hFile = ::CreateFileW(argv[1], GENERIC_WRITE, 0, NULL, OPEN_ALWAYS,
-                               FILE_ATTRIBUTE_NORMAL, NULL);
-
-  if (hFile == INVALID_HANDLE_VALUE)
-  {
-    fprintf(stderr, "CreateFile failed for %s, error %d\n", argv[1],
-            ::GetLastError());
-    return FAIL;
-  }
-
-  int rv = markFileAsSparse(hFile);
-  ::CloseHandle(hFile);
-  return rv;
-}
deleted file mode 100644
--- a/mailnews/test/Makefile.in
+++ /dev/null
@@ -1,61 +0,0 @@
-# ***** BEGIN LICENSE BLOCK *****
-# Version: MPL 1.1/GPL 2.0/LGPL 2.1
-#
-# The contents of this file are subject to the Mozilla 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/MPL/
-#
-# 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
-# Mozilla Messaging, Inc.
-#
-# Portions created by the Initial Developer are Copyright (C) 2010
-# the Initial Developer. All Rights Reserved.
-#
-# Contributor(s):
-#   Siddharth Agarwal <sid.bugzilla@gmail.com>
-#   Serge Gautherie <sgautherie.bz@free.fr>
-#
-# Alternatively, the contents of this file may be used under the terms of
-# either of the GNU General Public License Version 2 or later (the "GPL"),
-# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
-# in which case the provisions of the GPL or the LGPL are applicable instead
-# of those above. If you wish to allow use of your version of this file only
-# under the terms of either the GPL or the LGPL, and not to allow others to
-# use your version of this file under the terms of the MPL, indicate your
-# decision by deleting the provisions above and replace them with the notice
-# and other provisions required by the GPL or the LGPL. If you do not delete
-# the provisions above, a recipient may use your version of this file under
-# the terms of any one of the MPL, the GPL or the LGPL.
-#
-# ***** END LICENSE BLOCK *****
-
-DEPTH     = ../..
-topsrcdir = @top_srcdir@
-srcdir    = @srcdir@
-VPATH     = @srcdir@
-
-include $(DEPTH)/config/autoconf.mk
-
-ifeq ($(OS_ARCH),WINNT)
-CPPSRCS = \
-  LargeOfflineStoreHelper.cpp \
-  $(NULL)
-
-SIMPLE_PROGRAMS = $(CPPSRCS:.cpp=$(BIN_SUFFIX))
-endif # ifeq ($(OS_ARCH),WINNT)
-
-include $(topsrcdir)/config/rules.mk
-
-ifeq ($(OS_ARCH),WINNT)
-# need the executable for running the xpcshell unit tests
-libs::
-	$(INSTALL) $(SIMPLE_PROGRAMS) $(DEPTH)/mozilla/_tests/xpcshell/mailnews
-endif # ifeq ($(OS_ARCH),WINNT)
--- a/mailnews/test/resources/mailTestUtils.js
+++ b/mailnews/test/resources/mailTestUtils.js
@@ -15,16 +15,17 @@
  *
  * The Initial Developer of the Original Code is
  * Kent James <kent@caspia.com>.
  * Portions created by the Initial Developer are Copyright (C) 2008
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Mark Banner <bugzilla@standard8.plus.com>
+ *   Siddharth Agarwal <sid.bugzilla@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -39,16 +40,18 @@
 var gMailTestUtils_js__;
 if (!gMailTestUtils_js__) {
 gMailTestUtils_js__ = true;
 
 // we would like for everyone to have fixIterator and toXPComArray
 Components.utils.import("resource:///modules/iteratorUtils.jsm");
 // exposes component loader's btoa impl
 Components.utils.import("resource:///modules/IOUtils.js");
+// JS ctypes, needed for a few native functions
+Components.utils.import("resource://gre/modules/ctypes.jsm");
 
 // Local Mail Folders. Requires prior setup of profile directory
 
 var gLocalIncomingServer;
 var gLocalInboxFolder;
 
 function loadLocalMailAccount()
 {
@@ -168,16 +171,273 @@ function loadFileToString(aFile, aCharse
   }
 
   fstream.close();
 
   return data;
 }
 
 /**
+ * Return the file system a particular file is on. Currently only supported on
+ * Windows.
+ *
+ * @param aFile The file to get the file system for.
+ */
+function get_file_system(aFile) {
+  if (!("@mozilla.org/windows-registry-key;1" in Cc))
+    throw new Exception("get_file_system is only supported on Windows");
+
+  // Win32 type and other constants.
+  const BOOL = ctypes.int32_t;
+  const MAX_PATH = 260;
+  
+  let kernel32 = ctypes.open("kernel32.dll");
+
+  try {
+    // Returns the path of the volume a file is on.
+    let GetVolumePathName = kernel32.declare(
+      "GetVolumePathNameW",
+      ctypes.stdcall_abi,
+      BOOL,              // return type: 1 indicates success, 0 failure
+      ctypes.jschar.ptr, // in: lpszFileName
+      ctypes.jschar.ptr, // out: lpszVolumePathName
+      ctypes.uint32_t    // in: cchBufferLength
+    );
+
+    // Returns the last error.
+    let GetLastError = kernel32.declare(
+      "GetLastError",
+      ctypes.stdcall_abi,
+      ctypes.uint32_t // return type: the last error
+    );
+
+    let filePath = aFile.path;
+    // The volume path should be at most 1 greater than than the length of the
+    // path -- add 1 for a trailing backslash if necessary, and 1 for the
+    // terminating null character. Note that the parentheses around the type are
+    // necessary for new to apply correctly.
+    let volumePath = new (ctypes.jschar.array(filePath.length + 2));
+
+    if (!GetVolumePathName(filePath, volumePath, volumePath.length)) {
+      throw new Exception("Unable to get volume path for " + filePath + ", error " +
+                          GetLastError());
+    }
+
+    // Returns information about the file system for the given volume path. We just need
+    // the file system name.
+    let GetVolumeInformation = kernel32.declare(
+      "GetVolumeInformationW",
+      ctypes.stdcall_abi,
+      BOOL,                // return type: 1 indicates success, 0 failure
+      ctypes.jschar.ptr,   // in, optional: lpRootPathName
+      ctypes.jschar.ptr,   // out: lpVolumeNameBuffer
+      ctypes.uint32_t,     // in: nVolumeNameSize
+      ctypes.uint32_t.ptr, // out, optional: lpVolumeSerialNumber
+      ctypes.uint32_t.ptr, // out, optional: lpMaximumComponentLength
+      ctypes.uint32_t.ptr, // out, optional: lpFileSystemFlags
+      ctypes.jschar.ptr,   // out: lpFileSystemNameBuffer
+      ctypes.uint32_t      // in: nFileSystemNameSize
+    );
+
+    // We're only interested in the name of the file system.
+    let fsName = new (ctypes.jschar.array(MAX_PATH + 1));
+
+    if (!GetVolumeInformation(volumePath, null, 0, null, null, null, fsName,
+                              fsName.length)) {
+      throw new Exception("Unable to get volume information for " +
+                          volumePath.readString() + ", error " + GetLastError());
+    }
+
+    return fsName.readString();
+  }
+  finally {
+    kernel32.close();
+  }
+}
+
+/**
+ * Try marking a region of a file as sparse, so that zeros don't consume
+ * significant amounts of disk space.  This is a platform-dependent routine and
+ * is not supported on all platforms. The current status of this function is:
+ * - Windows: Supported, but only on NTFS volumes.
+ * - Mac: Not supported.
+ * - Linux: As long as you seek to a position before writing, happens automatically
+ *   on most file systems, so this function is a no-op.
+ *
+ * @param aFile The file to mark as sparse.
+ * @param aRegionStart The start position of the sparse region, in bytes.
+ * @param aRegionBytes The number of bytes to mark as sparse.
+ * @returns Whether the OS and file system supports marking files as sparse. If
+ *          this is true, then the file has been marked as sparse. If this is
+ *          false, then the underlying system doesn't support marking files as
+ *          sparse. If an exception is thrown, then the system does support
+ *          marking files as sparse, but an error occured while doing so.
+ *
+ */
+function mark_file_region_sparse(aFile, aRegionStart, aRegionBytes) {
+  if ("@mozilla.org/windows-registry-key;1" in Cc) {
+    // If the file system is not NTFS, sorry, we don't support sparse files.
+    if (get_file_system(aFile) != "NTFS")
+      return false;
+
+    // Win32 type and other constants.
+    const BOOL = ctypes.int32_t;
+    const HANDLE = ctypes.voidptr_t;
+    // A BOOLEAN (= BYTE = unsigned char) is distinct from a BOOL.
+    // http://blogs.msdn.com/b/oldnewthing/archive/2004/12/22/329884.aspx
+    const BOOLEAN = ctypes.unsigned_char;
+    const FILE_SET_SPARSE_BUFFER = new ctypes.StructType(
+      "FILE_SET_SPARSE_BUFFER",
+      [{"SetSparse": BOOLEAN}]
+    );
+    // LARGE_INTEGER is actually a type union. We'll use the int64 representation
+    const LARGE_INTEGER = ctypes.int64_t;
+    const FILE_ZERO_DATA_INFORMATION = new ctypes.StructType(
+      "FILE_ZERO_DATA_INFORMATION",
+      [{"FileOffset": LARGE_INTEGER},
+       {"BeyondFinalZero": LARGE_INTEGER}]
+    );
+
+    const GENERIC_WRITE = 0x40000000;
+    const OPEN_ALWAYS = 4;
+    const FILE_ATTRIBUTE_NORMAL = 0x80;
+    const INVALID_HANDLE_VALUE = new ctypes.Int64(-1);
+    const FSCTL_SET_SPARSE = 0x900c4;
+    const FSCTL_SET_ZERO_DATA = 0x980c8;
+    const FILE_BEGIN = 0;
+
+    let kernel32 = ctypes.open("kernel32.dll");
+
+    try {
+      let CreateFile = kernel32.declare(
+        "CreateFileW",
+        ctypes.stdcall_abi,
+        HANDLE,            // return type: handle to the file
+        ctypes.jschar.ptr, // in: lpFileName
+        ctypes.uint32_t,   // in: dwDesiredAccess
+        ctypes.uint32_t,   // in: dwShareMode
+        ctypes.voidptr_t,  // in, optional: lpSecurityAttributes (note that
+                           // we're cheating here by not declaring a
+                           // SECURITY_ATTRIBUTES structure -- that's because
+                           // we're going to pass in null anyway)
+        ctypes.uint32_t,   // in: dwCreationDisposition
+        ctypes.uint32_t,   // in: dwFlagsAndAttributes
+        HANDLE             // in, optional: hTemplateFile
+      );
+
+      // Returns the last error.
+      let GetLastError = kernel32.declare(
+        "GetLastError",
+        ctypes.stdcall_abi,
+        ctypes.uint32_t // return type: the last error
+      );
+
+      let filePath = aFile.path;
+      let hFile = CreateFile(filePath, GENERIC_WRITE, 0, null, OPEN_ALWAYS,
+                             FILE_ATTRIBUTE_NORMAL, null);
+      let hFileInt = ctypes.cast(hFile, ctypes.intptr_t);
+      if (ctypes.Int64.compare(hFileInt.value, INVALID_HANDLE_VALUE) == 0) {
+        throw new Exception("CreateFile failed for " + filePath + ", error " +
+                            GetLastError());
+      }
+
+      try {
+        let DeviceIoControl = kernel32.declare(
+          "DeviceIoControl",
+          ctypes.stdcall_abi,
+          BOOL,                // return type: 1 indicates success, 0 failure
+          HANDLE,              // in: hDevice
+          ctypes.uint32_t,     // in: dwIoControlCode
+          ctypes.voidptr_t,    // in, optional: lpInBuffer
+          ctypes.uint32_t,     // in: nInBufferSize
+          ctypes.voidptr_t,    // out, optional: lpOutBuffer
+          ctypes.uint32_t,     // in: nOutBufferSize
+          ctypes.uint32_t.ptr, // out, optional: lpBytesReturned
+          ctypes.voidptr_t     // inout, optional: lpOverlapped (again, we're
+                               // cheating here by not having this as an
+                               // OVERLAPPED structure
+        );
+        // bytesReturned needs to be passed in, even though it's meaningless
+        let bytesReturned = new ctypes.uint32_t();
+        let sparseBuffer = new FILE_SET_SPARSE_BUFFER();
+        sparseBuffer.SetSparse = 1;
+
+        // Mark the file as sparse
+        if (!DeviceIoControl(hFile, FSCTL_SET_SPARSE, sparseBuffer.address(),
+                             FILE_SET_SPARSE_BUFFER.size, null, 0,
+                             bytesReturned.address(), null)) {
+          throw new Exception("Unable to mark file as sparse, error " +
+                              GetLastError());
+        }
+        
+        let zdInfo = new FILE_ZERO_DATA_INFORMATION();
+        zdInfo.FileOffset = aRegionStart;
+        let regionEnd = aRegionStart + aRegionBytes;
+        zdInfo.BeyondFinalZero = regionEnd;
+        // Mark the region as a sparse region
+        if (!DeviceIoControl(hFile, FSCTL_SET_ZERO_DATA, zdInfo.address(),
+                             FILE_ZERO_DATA_INFORMATION.size, null, 0,
+                             bytesReturned.address(), null)) {
+          throw new Exception("Unable to mark region as zero, error " +
+                              GetLastError());
+        }
+
+        // Move to past the sparse region and mark it as the end of the file. The
+        // above DeviceIoControl call is useless unless followed by this.
+        let SetFilePointerEx = kernel32.declare(
+          "SetFilePointerEx",
+          ctypes.stdcall_abi,
+          BOOL,              // return type: 1 indicates success, 0 failure
+          HANDLE,            // in: hFile
+          LARGE_INTEGER,     // in: liDistanceToMove
+          LARGE_INTEGER.ptr, // out, optional: lpNewFilePointer
+          ctypes.uint32_t    // in: dwMoveMethod
+        );
+        if (!SetFilePointerEx(hFile, regionEnd, null, FILE_BEGIN)) {
+          throw new Exception("Unable to set file pointer to end, error " +
+                              GetLastError());
+        }
+
+        let SetEndOfFile = kernel32.declare(
+          "SetEndOfFile",
+          ctypes.stdcall_abi,
+          BOOL,  // return type: 1 indicates success, 0 failure
+          HANDLE // in: hFile
+        );
+        if (!SetEndOfFile(hFile))
+          throw new Exception("Unable to set end of file, error " + GetLastError());
+
+        return true;
+      }
+      finally {
+        let CloseHandle = kernel32.declare(
+          "CloseHandle",
+          ctypes.stdcall_abi,
+          BOOL,  // return type: 1 indicates success, 0 failure
+          HANDLE // in: hObject
+        );
+        CloseHandle(hFile);
+      }
+    }
+    finally {
+      kernel32.close();
+    }
+  }
+  else if ("nsILocalFileMac" in Ci) {
+    // Macs don't support marking files as sparse.
+    return false;
+  }
+  else {
+    // Assuming Unix here. Unix file systems generally automatically sparsify
+    // files.
+    return true;
+  }
+}
+
+/**
  * A variant of do_timeout that accepts an actual function instead of
  *  requiring you to pass a string to evaluate.  If the function throws an
  *  exception when invoked, we will use do_throw to ensure that the test fails.
  *
  * @param aDelayInMS The number of milliseconds to wait before firing the timer.
  * @param aFunc The function to invoke when the timer fires.
  * @param aFuncThis Optional 'this' pointer to use.
  * @param aFuncArgs Optional list of arguments to pass to the function.