Modify Path::eraseFromDisk to not throw an exception.
[oota-llvm.git] / lib / System / Win32 / Path.inc
index 88e20cd354f417fcc97b7fbb8daec5223afa5de0..15c686d2640d5c2643e9d9ef4beadcd5416ab14b 100644 (file)
 //===----------------------------------------------------------------------===//
 
 #include "Win32.h"
-#include <fstream>
 #include <malloc.h>
 
+// We need to undo a macro defined in Windows.h, otherwise we won't compile:
+#undef CopyFile
+
+// Windows happily accepts either forward or backward slashes, though any path
+// returned by a Win32 API will have backward slashes.  As LLVM code basically
+// assumes forward slashes are used, backward slashs are converted where they
+// can be introduced into a path.
+//
+// Another invariant is that a path ends with a slash if and only if the path
+// is a root directory.  Any other use of a trailing slash is stripped.  Unlike
+// in Unix, Windows has a rather complicated notion of a root path and this
+// invariant helps simply the code.
+
 static void FlipBackSlashes(std::string& s) {
   for (size_t i = 0; i < s.size(); i++)
     if (s[i] == '\\')
@@ -41,9 +53,18 @@ Path::isValid() const {
   // and followed by something.
   size_t len = path.size();
   size_t pos = path.rfind(':',len);
+  size_t rootslash = 0;
   if (pos != std::string::npos) {
     if (pos != 1 || !isalpha(path[0]) || len < 3)
       return false;
+      rootslash = 2;
+  }
+
+  // Look for a UNC path, and if found adjust our notion of the root slash.
+  if (len > 3 && path[0] == '/' && path[1] == '/') {
+    rootslash = path.find('/', 2);
+    if (rootslash == std::string::npos)
+      rootslash = 0;
   }
 
   // Check for illegal characters.
@@ -53,17 +74,33 @@ Path::isValid() const {
       != std::string::npos)
     return false;
 
-  // A file or directory name may not end in a period.
-  if (path[len-1] == '.')
-    return false;
-  if (len >= 2 && path[len-2] == '.' && path[len-1] == '/')
-    return false;
+  // Remove trailing slash, unless it's a root slash.
+  if (len > rootslash+1 && path[len-1] == '/')
+    path.erase(--len);
 
-  // A file or directory name may not end in a space.
-  if (path[len-1] == ' ')
-    return false;
-  if (len >= 2 && path[len-2] == ' ' && path[len-1] == '/')
-    return false;
+  // Check each component for legality.
+  for (pos = 0; pos < len; ++pos) {
+    // A component may not end in a space.
+    if (path[pos] == ' ') {
+      if (path[pos+1] == '/' || path[pos+1] == '\0')
+        return false;
+    }
+
+    // A component may not end in a period.
+    if (path[pos] == '.') {
+      if (path[pos+1] == '/' || path[pos+1] == '\0') {
+        // Unless it is the pseudo-directory "."...
+        if (pos == 0 || path[pos-1] == '/' || path[pos-1] == ':')
+          return true;
+        // or "..".
+        if (pos > 0 && path[pos-1] == '.') {
+          if (pos == 1 || path[pos-2] == '/' || path[pos-2] == ':')
+            return true;
+        }
+        return false;
+      }
+    }
+  }
 
   return true;
 }
@@ -80,19 +117,19 @@ Path::GetTemporaryDirectory() {
     throw std::string("Can't determine temporary directory");
 
   Path result;
-  result.setDirectory(pathname);
+  result.set(pathname);
 
   // Append a subdirectory passed on our process id so multiple LLVMs don't
   // step on each other's toes.
-  sprintf(pathname, "LLVM_%u", GetCurrentProcessId());
-  result.appendDirectory(pathname);
+  sprintf(pathname, "LLVM_%u", unsigned(GetCurrentProcessId()));
+  result.appendComponent(pathname);
 
   // If there's a directory left over from a previous LLVM execution that
   // happened to have the same process id, get rid of it.
-  result.destroyDirectory(true);
+  result.eraseFromDisk(true);
 
   // And finally (re-)create the empty directory.
-  result.createDirectory(false);
+  result.createDirectoryOnDisk(false);
   TempDirectory = new Path(result);
   return *TempDirectory;
 }
@@ -114,7 +151,7 @@ Path::Path(const std::string& unverified_path)
 Path
 Path::GetRootDirectory() {
   Path result;
-  result.setDirectory("/");
+  result.set("C:/");
   return result;
 }
 
@@ -122,32 +159,25 @@ static void getPathList(const char*path, std::vector<sys::Path>& Paths) {
   const char* at = path;
   const char* delim = strchr(at, ';');
   Path tmpPath;
-  while( delim != 0 ) {
+  while (delim != 0) {
     std::string tmp(at, size_t(delim-at));
-    if (tmpPath.setDirectory(tmp))
-      if (tmpPath.readable())
+    if (tmpPath.set(tmp))
+      if (tmpPath.canRead())
         Paths.push_back(tmpPath);
     at = delim + 1;
     delim = strchr(at, ';');
   }
+
   if (*at != 0)
-    if (tmpPath.setDirectory(std::string(at)))
-      if (tmpPath.readable())
+    if (tmpPath.set(std::string(at)))
+      if (tmpPath.canRead())
         Paths.push_back(tmpPath);
-
 }
 
-void 
+void
 Path::GetSystemLibraryPaths(std::vector<sys::Path>& Paths) {
-#ifdef LTDL_SHLIBPATH_VAR
-  char* env_var = getenv(LTDL_SHLIBPATH_VAR);
-  if (env_var != 0) {
-    getPathList(env_var,Paths);
-  }
-#endif
-  // FIXME: Should this look at LD_LIBRARY_PATH too?
-  Paths.push_back(sys::Path("C:\\WINDOWS\\SYSTEM32\\"));
-  Paths.push_back(sys::Path("C:\\WINDOWS\\"));
+  Paths.push_back(sys::Path("C:/WINDOWS/SYSTEM32"));
+  Paths.push_back(sys::Path("C:/WINDOWS"));
 }
 
 void
@@ -156,18 +186,11 @@ Path::GetBytecodeLibraryPaths(std::vector<sys::Path>& Paths) {
   if (env_var != 0) {
     getPathList(env_var,Paths);
   }
-#ifdef LLVMGCCDIR
-  {
-    Path tmpPath(std::string(LLVMGCCDIR) + "bytecode-libs/");
-    if (tmpPath.readable())
-      Paths.push_back(tmpPath);
-  }
-#endif
 #ifdef LLVM_LIBDIR
   {
     Path tmpPath;
-    if (tmpPath.setDirectory(LLVM_LIBDIR))
-      if (tmpPath.readable())
+    if (tmpPath.set(LLVM_LIBDIR))
+      if (tmpPath.canRead())
         Paths.push_back(tmpPath);
   }
 #endif
@@ -176,20 +199,17 @@ Path::GetBytecodeLibraryPaths(std::vector<sys::Path>& Paths) {
 
 Path
 Path::GetLLVMDefaultConfigDir() {
-  return Path("/etc/llvm/");
-}
-
-Path
-Path::GetLLVMConfigDir() {
-  return GetLLVMDefaultConfigDir();
+  // TODO: this isn't going to fly on Windows
+  return Path("/etc/llvm");
 }
 
 Path
 Path::GetUserHomeDirectory() {
+  // TODO: Typical Windows setup doesn't define HOME.
   const char* home = getenv("HOME");
   if (home) {
     Path result;
-    if (result.setDirectory(home))
+    if (result.set(home))
       return result;
   }
   return GetRootDirectory();
@@ -198,12 +218,42 @@ Path::GetUserHomeDirectory() {
 
 bool
 Path::isFile() const {
-  return (isValid() && path[path.length()-1] != '/');
+  WIN32_FILE_ATTRIBUTE_DATA fi;
+  BOOL rc = GetFileAttributesEx(path.c_str(), GetFileExInfoStandard, &fi);
+  if (rc)
+    return !(fi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
+  else if (GetLastError() != ERROR_FILE_NOT_FOUND) {
+    ThrowError("isFile(): " + std::string(path) + ": Can't get status: ");
+  }
+  return false;
 }
 
 bool
 Path::isDirectory() const {
-  return (isValid() && path[path.length()-1] == '/');
+  WIN32_FILE_ATTRIBUTE_DATA fi;
+  BOOL rc = GetFileAttributesEx(path.c_str(), GetFileExInfoStandard, &fi);
+  if (rc)
+    return fi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
+  else if (GetLastError() != ERROR_FILE_NOT_FOUND)
+    ThrowError("isDirectory(): " + std::string(path) + ": Can't get status: ");
+  return false;
+}
+
+bool
+Path::isHidden() const {
+  WIN32_FILE_ATTRIBUTE_DATA fi;
+  BOOL rc = GetFileAttributesEx(path.c_str(), GetFileExInfoStandard, &fi);
+  if (rc)
+    return fi.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN;
+  else if (GetLastError() != ERROR_FILE_NOT_FOUND)
+    ThrowError("isHidden(): " + std::string(path) + ": Can't get status: ");
+  return false;
+}
+
+bool
+Path::isRootDirectory() const {
+  size_t len = path.size();
+  return len > 0 && path[len-1] == '/';
 }
 
 std::string
@@ -215,27 +265,28 @@ Path::getBasename() const {
   else
     slash++;
 
-  return path.substr(slash, path.rfind('.'));
+  size_t dot = path.rfind('.');
+  if (dot == std::string::npos || dot < slash)
+    return path.substr(slash);
+  else
+    return path.substr(slash, dot - slash);
 }
 
 bool Path::hasMagicNumber(const std::string &Magic) const {
-  size_t len = Magic.size();
-  char *buf = reinterpret_cast<char *>(_alloca(len+1));
-  std::ifstream f(path.c_str());
-  f.read(buf, len);
-  buf[len] = '\0';
-  return Magic == buf;
+  std::string actualMagic;
+  if (getMagicNumber(actualMagic, Magic.size()))
+    return Magic == actualMagic;
+  return false;
 }
 
-bool 
+bool
 Path::isBytecodeFile() const {
-  char buffer[ 4];
-  buffer[0] = 0;
-  std::ifstream f(path.c_str());
-  f.read(buffer, 4);
-  if (f.bad())
-    ThrowErrno("can't read file signature");
-  return 0 == memcmp(buffer,"llvc",4) || 0 == memcmp(buffer,"llvm",4);
+  if (!isFile())
+    return false;
+  std::string actualMagic;
+  if (!getMagicNumber(actualMagic, 4))
+    return false;
+  return actualMagic == "llvc" || actualMagic == "llvm";
 }
 
 bool
@@ -245,21 +296,21 @@ Path::exists() const {
 }
 
 bool
-Path::readable() const {
+Path::canRead() const {
   // FIXME: take security attributes into account.
   DWORD attr = GetFileAttributes(path.c_str());
   return attr != INVALID_FILE_ATTRIBUTES;
 }
 
 bool
-Path::writable() const {
+Path::canWrite() const {
   // FIXME: take security attributes into account.
   DWORD attr = GetFileAttributes(path.c_str());
   return (attr != INVALID_FILE_ATTRIBUTES) && !(attr & FILE_ATTRIBUTE_READONLY);
 }
 
 bool
-Path::executable() const {
+Path::canExecute() const {
   // FIXME: take security attributes into account.
   DWORD attr = GetFileAttributes(path.c_str());
   return attr != INVALID_FILE_ATTRIBUTES;
@@ -274,15 +325,10 @@ Path::getLast() const {
   if (pos == std::string::npos)
     return path;
 
-  // If the last character is a slash
-  if (pos == path.length()-1) {
-    // Find the second to last slash
-    size_t pos2 = path.rfind('/', pos-1);
-    if (pos2 == std::string::npos)
-      return path.substr(0,pos);
-    else
-      return path.substr(pos2+1,pos-pos2-1);
-  }
+  // If the last character is a slash, we have a root directory
+  if (pos == path.length()-1)
+    return path;
+
   // Return everything after the last slash
   return path.substr(pos+1);
 }
@@ -291,13 +337,13 @@ void
 Path::getStatusInfo(StatusInfo& info) const {
   WIN32_FILE_ATTRIBUTE_DATA fi;
   if (!GetFileAttributesEx(path.c_str(), GetFileExInfoStandard, &fi))
-    ThrowError(std::string(path) + ": Can't get status: ");
+    ThrowError("getStatusInfo():" + std::string(path) + ": Can't get status: ");
 
   info.fileSize = fi.nFileSizeHigh;
-  info.fileSize <<= 32;
+  info.fileSize <<= sizeof(fi.nFileSizeHigh)*8;
   info.fileSize += fi.nFileSizeLow;
 
-  info.mode = 0777;    // Not applicable to Windows, so...
+  info.mode = fi.dwFileAttributes & FILE_ATTRIBUTE_READONLY ? 0555 : 0777;
   info.user = 9999;    // Not applicable to Windows, so...
   info.group = 9999;   // Not applicable to Windows, so...
 
@@ -305,17 +351,29 @@ Path::getStatusInfo(StatusInfo& info) const {
   info.modTime.fromWin32Time(ft);
 
   info.isDir = fi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
-  if (info.isDir && path[path.length() - 1] != '/')
-    path += '/';
-  else if (!info.isDir && path[path.length() - 1] == '/')
-    path.erase(path.length() - 1);
 }
 
-void Path::makeReadable() {
+static bool AddPermissionBits(const std::string& Filename, int bits) {
+  DWORD attr = GetFileAttributes(Filename.c_str());
+
+  // If it doesn't exist, we're done.
+  if (attr == INVALID_FILE_ATTRIBUTES)
+    return false;
+
+  // The best we can do to interpret Unix permission bits is to use
+  // the owner writable bit.
+  if ((attr & FILE_ATTRIBUTE_READONLY) && (bits & 0200)) {
+    if (!SetFileAttributes(Filename.c_str(), attr & ~FILE_ATTRIBUTE_READONLY))
+      ThrowError(Filename + ": SetFileAttributes: ");
+  }
+  return true;
+}
+
+void Path::makeReadableOnDisk() {
   // All files are readable on Windows (ignoring security attributes).
 }
 
-void Path::makeWriteable() {
+void Path::makeWriteableOnDisk() {
   DWORD attr = GetFileAttributes(path.c_str());
 
   // If it doesn't exist, we're done.
@@ -328,135 +386,136 @@ void Path::makeWriteable() {
   }
 }
 
-void Path::makeExecutable() {
+void Path::makeExecutableOnDisk() {
   // All files are executable on Windows (ignoring security attributes).
 }
 
 bool
-Path::setDirectory(const std::string& a_path) {
-  if (a_path.size() == 0)
-    return false;
-  Path save(*this);
-  path = a_path;
-  FlipBackSlashes(path);
-  size_t last = a_path.size() -1;
-  if (a_path[last] != '/')
-    path += '/';
-  if (!isValid()) {
-    path = save.path;
+Path::getDirectoryContents(std::set<Path>& result) const {
+  if (!isDirectory())
     return false;
+
+  result.clear();
+  WIN32_FIND_DATA fd;
+  std::string searchpath = path;
+  if (path.size() == 0 || searchpath[path.size()-1] == '/')
+    searchpath += "*";
+  else
+    searchpath += "/*";
+
+  HANDLE h = FindFirstFile(searchpath.c_str(), &fd);
+  if (h == INVALID_HANDLE_VALUE) {
+    if (GetLastError() == ERROR_FILE_NOT_FOUND)
+      return true; // not really an error, now is it?
+    ThrowError(path + ": Can't read directory: ");
+  }
+
+  do {
+    if (fd.cFileName[0] == '.')
+      continue;
+    Path aPath(path);
+    aPath.appendComponent(&fd.cFileName[0]);
+    result.insert(aPath);
+  } while (FindNextFile(h, &fd));
+
+  DWORD err = GetLastError();
+  FindClose(h);
+  if (err != ERROR_NO_MORE_FILES) {
+    SetLastError(err);
+    ThrowError(path + ": Can't read directory: ");
   }
   return true;
 }
 
 bool
-Path::setFile(const std::string& a_path) {
+Path::set(const std::string& a_path) {
   if (a_path.size() == 0)
     return false;
-  Path save(*this);
+  std::string save(path);
   path = a_path;
   FlipBackSlashes(path);
-  size_t last = a_path.size() - 1;
-  while (last > 0 && a_path[last] == '/')
-    last--;
-  path.erase(last+1);
   if (!isValid()) {
-    path = save.path;
+    path = save;
     return false;
   }
   return true;
 }
 
 bool
-Path::appendDirectory(const std::string& dir) {
-  if (isFile())
+Path::appendComponent(const std::string& name) {
+  if (name.empty())
     return false;
-  Path save(*this);
-  path += dir;
-  path += "/";
+  std::string save(path);
+  if (!path.empty()) {
+    size_t last = path.size() - 1;
+    if (path[last] != '/')
+      path += '/';
+  }
+  path += name;
   if (!isValid()) {
-    path = save.path;
+    path = save;
     return false;
   }
   return true;
 }
 
 bool
-Path::elideDirectory() {
-  if (isFile())
-    return false;
+Path::eraseComponent() {
   size_t slashpos = path.rfind('/',path.size());
-  if (slashpos == 0 || slashpos == std::string::npos)
-    return false;
-  if (slashpos == path.size() - 1)
-    slashpos = path.rfind('/',slashpos-1);
-  if (slashpos == std::string::npos)
+  if (slashpos == path.size() - 1 || slashpos == std::string::npos)
     return false;
+  std::string save(path);
   path.erase(slashpos);
-  return true;
-}
-
-bool
-Path::appendFile(const std::string& file) {
-  if (!isDirectory())
-    return false;
-  Path save(*this);
-  path += file;
   if (!isValid()) {
-    path = save.path;
+    path = save;
     return false;
   }
   return true;
 }
 
-bool
-Path::elideFile() {
-  if (isDirectory())
-    return false;
-  size_t slashpos = path.rfind('/',path.size());
-  if (slashpos == std::string::npos)
-    return false;
-  path.erase(slashpos+1);
-  return true;
-}
-
 bool
 Path::appendSuffix(const std::string& suffix) {
-  if (isDirectory())
-    return false;
-  Path save(*this);
+  std::string save(path);
   path.append(".");
   path.append(suffix);
   if (!isValid()) {
-    path = save.path;
+    path = save;
     return false;
   }
   return true;
 }
 
 bool
-Path::elideSuffix() {
-  if (isDirectory()) return false;
+Path::eraseSuffix() {
   size_t dotpos = path.rfind('.',path.size());
   size_t slashpos = path.rfind('/',path.size());
-  if (slashpos != std::string::npos && dotpos != std::string::npos &&
-      dotpos > slashpos) {
-    path.erase(dotpos, path.size()-dotpos);
-    return true;
+  if (dotpos != std::string::npos) {
+    if (slashpos == std::string::npos || dotpos > slashpos+1) {
+      std::string save(path);
+      path.erase(dotpos, path.size()-dotpos);
+      if (!isValid()) {
+        path = save;
+        return false;
+      }
+      return true;
+    }
   }
   return false;
 }
 
-
 bool
-Path::createDirectory( bool create_parents) {
-  // Make sure we're dealing with a directory
-  if (!isDirectory()) return false;
-
+Path::createDirectoryOnDisk(bool create_parents) {
   // Get a writeable copy of the path name
-  char *pathname = reinterpret_cast<char *>(_alloca(path.length()+1));
-  path.copy(pathname,path.length());
-  pathname[path.length()] = 0;
+  size_t len = path.length();
+  char *pathname = reinterpret_cast<char *>(_alloca(len+2));
+  path.copy(pathname, len);
+  pathname[len] = 0;
+
+  // Make sure it ends with a slash.
+  if (len == 0 || pathname[len - 1] != '/') {
+    pathname[len] = '/';
+    pathname[++len] = 0;
+  }
 
   // Determine starting point for initial / search.
   char *next = pathname;
@@ -491,7 +550,7 @@ Path::createDirectory( bool create_parents) {
     }
   } else {
     // Drop trailing slash.
-    pathname[path.size()-1] = 0;
+    pathname[len-1] = 0;
     if (!CreateDirectory(pathname, NULL)) {
       ThrowError(std::string(pathname) + ": Can't create directory: ");
     }
@@ -500,73 +559,234 @@ Path::createDirectory( bool create_parents) {
 }
 
 bool
-Path::createFile() {
-  // Make sure we're dealing with a file
-  if (!isFile()) return false;
-
+Path::createFileOnDisk() {
   // Create the file
   HANDLE h = CreateFile(path.c_str(), GENERIC_WRITE, 0, NULL, CREATE_NEW,
                         FILE_ATTRIBUTE_NORMAL, NULL);
   if (h == INVALID_HANDLE_VALUE)
-    ThrowError(std::string(path.c_str()) + ": Can't create file: ");
+    ThrowError(path + ": Can't create file: ");
 
   CloseHandle(h);
   return true;
 }
 
 bool
-Path::destroyDirectory(bool remove_contents) {
-  // Make sure we're dealing with a directory
-  if (!isDirectory()) return false;
+Path::eraseFromDisk(bool remove_contents, std::string *ErrStr) const {
+  if (isFile()) {
+    DWORD attr = GetFileAttributes(path.c_str());
 
-  // If it doesn't exist, we're done.
-  if (!exists()) return true;
+    // If it doesn't exist, we're done.
+    if (attr == INVALID_FILE_ATTRIBUTES)
+      return false;
 
-  char *pathname = reinterpret_cast<char *>(_alloca(path.length()+1));
-  path.copy(pathname,path.length()+1);
-  int lastchar = path.length() - 1 ;
-  if (pathname[lastchar] == '/')
-    pathname[lastchar] = 0;
+    // Read-only files cannot be deleted on Windows.  Must remove the read-only
+    // attribute first.
+    if (attr & FILE_ATTRIBUTE_READONLY) {
+      if (!SetFileAttributes(path.c_str(), attr & ~FILE_ATTRIBUTE_READONLY))
+        return GetError(path + ": Can't destroy file: ", ErrStr);
+    }
 
-  if (remove_contents) {
-    // Recursively descend the directory to remove its content
-    // FIXME: The correct way of doing this on Windows isn't pretty...
-    // but this may work if unix-like utils are present.
-    std::string cmd("rm -rf ");
-    cmd += path;
-    system(cmd.c_str());
-  } else {
-    // Otherwise, try to just remove the one directory
+    if (!DeleteFile(path.c_str()))
+      ThrowError(path + ": Can't destroy file: ");
+    return true;
+  } else if (isDirectory()) {
+    // If it doesn't exist, we're done.
+    if (!exists())
+      return false;
+
+    char *pathname = reinterpret_cast<char *>(_alloca(path.length()+3));
+    int lastchar = path.length() - 1 ;
+    path.copy(pathname, lastchar+1);
+
+    // Make path end with '/*'.
+    if (pathname[lastchar] != '/')
+      pathname[++lastchar] = '/';
+    pathname[lastchar+1] = '*';
+    pathname[lastchar+2] = 0;
+
+    if (remove_contents) {
+      WIN32_FIND_DATA fd;
+      HANDLE h = FindFirstFile(pathname, &fd);
+
+      // It's a bad idea to alter the contents of a directory while enumerating
+      // its contents. So build a list of its contents first, then destroy them.
+
+      if (h != INVALID_HANDLE_VALUE) {
+        std::vector<Path> list;
+
+        do {
+          if (strcmp(fd.cFileName, ".") == 0)
+            continue;
+          if (strcmp(fd.cFileName, "..") == 0)
+            continue;
+
+          Path aPath(path);
+          aPath.appendComponent(&fd.cFileName[0]);
+          list.push_back(aPath);
+        } while (FindNextFile(h, &fd));
+
+        DWORD err = GetLastError();
+        FindClose(h);
+        if (err != ERROR_NO_MORE_FILES) {
+          SetLastError(err);
+          return GetError(path + ": Can't read directory: ", ErrStr);
+        }
+
+        for (std::vector<Path>::iterator I = list.begin(); I != list.end();
+             ++I) {
+          Path &aPath = *I;
+          aPath.eraseFromDisk(true);
+        }
+      } else {
+        if (GetLastError() != ERROR_FILE_NOT_FOUND)
+          return GetError(path + ": Can't read directory: ", ErrStr);
+      }
+    }
+
+    pathname[lastchar] = 0;
     if (!RemoveDirectory(pathname))
-      ThrowError(std::string(pathname) + ": Can't destroy directory: ");
+      return GetError(std::string(pathname) + ": Can't destroy directory: ",
+                      ErrStr);
+    return false;
+  } else {
+    // It appears the path doesn't exist.
+    return true;
   }
+}
+
+bool Path::getMagicNumber(std::string& Magic, unsigned len) const {
+  if (!isFile())
+    return false;
+  assert(len < 1024 && "Request for magic string too long");
+  char* buf = (char*) alloca(1 + len);
+
+  HANDLE h = CreateFile(path.c_str(),
+                        GENERIC_READ,
+                        FILE_SHARE_READ,
+                        NULL,
+                        OPEN_EXISTING,
+                        FILE_ATTRIBUTE_NORMAL,
+                        NULL);
+  if (h == INVALID_HANDLE_VALUE)
+    return false;
+
+  DWORD nRead = 0;
+  BOOL ret = ReadFile(h, buf, len, &nRead, NULL);
+  CloseHandle(h);
+
+  if (!ret || nRead != len)
+    return false;
+
+  buf[len] = '\0';
+  Magic = buf;
+  return true;
+}
+
+bool
+Path::renamePathOnDisk(const Path& newName) {
+  if (!MoveFileEx(path.c_str(), newName.c_str(), MOVEFILE_REPLACE_EXISTING))
+    ThrowError("Can't move '" + path +
+               "' to '" + newName.path + "': ");
   return true;
 }
 
 bool
-Path::destroyFile() {
+Path::setStatusInfoOnDisk(const StatusInfo& si) const {
+  // FIXME: should work on directories also.
   if (!isFile()) return false;
 
-  DWORD attr = GetFileAttributes(path.c_str());
+  HANDLE h = CreateFile(path.c_str(),
+                        FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES,
+                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+                        NULL,
+                        OPEN_EXISTING,
+                        FILE_ATTRIBUTE_NORMAL,
+                        NULL);
+  if (h == INVALID_HANDLE_VALUE)
+    return false;
 
-  // If it doesn't exist, we're done.
-  if (attr == INVALID_FILE_ATTRIBUTES)
-    return true;
+  BY_HANDLE_FILE_INFORMATION bhfi;
+  if (!GetFileInformationByHandle(h, &bhfi)) {
+    DWORD err = GetLastError();
+    CloseHandle(h);
+    SetLastError(err);
+    ThrowError(path + ": GetFileInformationByHandle: ");
+  }
 
-  // Read-only files cannot be deleted on Windows.  Must remove the read-only
-  // attribute first.
-  if (attr & FILE_ATTRIBUTE_READONLY) {
-    if (!SetFileAttributes(path.c_str(), attr & ~FILE_ATTRIBUTE_READONLY))
-      ThrowError(std::string(path.c_str()) + ": Can't destroy file: ");
+  FILETIME ft;
+  (uint64_t&)ft = si.modTime.toWin32Time();
+  BOOL ret = SetFileTime(h, NULL, &ft, &ft);
+  DWORD err = GetLastError();
+  CloseHandle(h);
+  if (!ret) {
+    SetLastError(err);
+    ThrowError(path + ": SetFileTime: ");
+  }
+
+  // Best we can do with Unix permission bits is to interpret the owner
+  // writable bit.
+  if (si.mode & 0200) {
+    if (bhfi.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
+      if (!SetFileAttributes(path.c_str(),
+              bhfi.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY))
+        ThrowError(path + ": SetFileAttributes: ");
+    }
+  } else {
+    if (!(bhfi.dwFileAttributes & FILE_ATTRIBUTE_READONLY)) {
+      if (!SetFileAttributes(path.c_str(),
+              bhfi.dwFileAttributes | FILE_ATTRIBUTE_READONLY))
+        ThrowError(path + ": SetFileAttributes: ");
+    }
   }
 
-  if (!DeleteFile(path.c_str()))
-    ThrowError(std::string(path.c_str()) + ": Can't destroy file: ");
   return true;
 }
 
+void
+CopyFile(const sys::Path &Dest, const sys::Path &Src) {
+  // Can't use CopyFile macro defined in Windows.h because it would mess up the
+  // above line.  We use the expansion it would have in a non-UNICODE build.
+  if (!::CopyFileA(Src.c_str(), Dest.c_str(), false))
+    ThrowError("Can't copy '" + Src.toString() +
+               "' to '" + Dest.toString() + "': ");
 }
+
+void
+Path::makeUnique(bool reuse_current) {
+  if (reuse_current && !exists())
+    return; // File doesn't exist already, just use it!
+
+  // Reserve space for -XXXXXX at the end.
+  char *FNBuffer = (char*) alloca(path.size()+8);
+  unsigned offset = path.size();
+  path.copy(FNBuffer, offset);
+
+  // Find a numeric suffix that isn't used by an existing file.  Assume there
+  // won't be more than 1 million files with the same prefix.  Probably a safe
+  // bet.
+  static unsigned FCounter = 0;
+  do {
+    sprintf(FNBuffer+offset, "-%06u", FCounter);
+    if (++FCounter > 999999)
+      FCounter = 0;
+    path = FNBuffer;
+  } while (exists());
 }
 
-// vim: sw=2 smartindent smarttab tw=80 autoindent expandtab
+bool
+Path::createTemporaryFileOnDisk(bool reuse_current) {
+  // Make this into a unique file name
+  makeUnique(reuse_current);
 
+  // Now go and create it
+  HANDLE h = CreateFile(path.c_str(), GENERIC_WRITE, 0, NULL, CREATE_NEW,
+                        FILE_ATTRIBUTE_NORMAL, NULL);
+  if (h == INVALID_HANDLE_VALUE)
+    return false;
+
+  CloseHandle(h);
+  return true;
+}
+
+}
+}