//===--- FileSystem.cpp - Extra helpers for manipulating files ------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#include "swift/Basic/FileSystem.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Process.h"

using namespace swift;

namespace {
  class OpenFileRAII {
    static const int INVALID_FD = -1;
  public:
    int fd = INVALID_FD;

    ~OpenFileRAII() {
      if (fd != INVALID_FD)
        llvm::sys::Process::SafelyCloseFileDescriptor(fd);
    }
  };
} // end anonymous namespace

std::error_code swift::moveFileIfDifferent(const llvm::Twine &source,
                                           const llvm::Twine &destination) {
  namespace fs = llvm::sys::fs;

  // First check for a self-move.
  if (fs::equivalent(source, destination))
    return std::error_code();

  OpenFileRAII sourceFile;
  fs::file_status sourceStatus;
  if (std::error_code error = fs::openFileForRead(source, sourceFile.fd)) {
    // If we can't open the source file, fail.
    return error;
  }
  if (std::error_code error = fs::status(sourceFile.fd, sourceStatus)) {
    // If we can't stat the source file, fail.
    return error;
  }

  OpenFileRAII destFile;
  fs::file_status destStatus;
  bool couldReadDest = !fs::openFileForRead(destination, destFile.fd);
  if (couldReadDest)
    couldReadDest = !fs::status(destFile.fd, destStatus);

  // If we could read the destination file, and it matches the source file in
  // size, they may be the same. Do an actual comparison of the contents.
  if (couldReadDest && sourceStatus.getSize() == destStatus.getSize()) {
    uint64_t size = sourceStatus.getSize();
    bool same = false;
    if (size == 0) {
      same = true;
    } else {
      std::error_code sourceRegionErr;
      fs::mapped_file_region sourceRegion(sourceFile.fd,
                                          fs::mapped_file_region::readonly,
                                          size, 0, sourceRegionErr);
      if (sourceRegionErr)
        return sourceRegionErr;

      std::error_code destRegionErr;
      fs::mapped_file_region destRegion(destFile.fd,
                                        fs::mapped_file_region::readonly,
                                        size, 0, destRegionErr);

      if (!destRegionErr) {
        same = (0 == memcmp(sourceRegion.const_data(), destRegion.const_data(),
                            size));
      }
    }

    // If the file contents are the same, we are done. Just delete the source.
    if (same)
      return fs::remove(source);
  }

  // If we get here, we weren't able to prove that the files are the same.
  return fs::rename(source, destination);
}
