diff --git a/llvm/include/llvm/InterfaceStub/ELFObjHandler.h b/llvm/include/llvm/InterfaceStub/ELFObjHandler.h --- a/llvm/include/llvm/InterfaceStub/ELFObjHandler.h +++ b/llvm/include/llvm/InterfaceStub/ELFObjHandler.h @@ -16,6 +16,7 @@ #include "llvm/InterfaceStub/ELFStub.h" #include "llvm/Object/ELFObjectFile.h" #include "llvm/Object/ELFTypes.h" +#include "llvm/Support/FileSystem.h" namespace llvm { @@ -35,8 +36,10 @@ /// @param FilePath File path for writing the ELF binary. /// @param Stub Source ELFStub to generate a binary ELF stub from. /// @param OutputFormat Target ELFType to write binary as. +/// @param PreserveDates Whether or not to preserve timestamp if +/// the output stays the same. Error writeBinaryStub(StringRef FilePath, const ELFStub &Stub, - ELFTarget OutputFormat); + ELFTarget OutputFormat, bool PreserveDates = false); } // end namespace elfabi } // end namespace llvm diff --git a/llvm/lib/InterfaceStub/ELFObjHandler.cpp b/llvm/lib/InterfaceStub/ELFObjHandler.cpp --- a/llvm/lib/InterfaceStub/ELFObjHandler.cpp +++ b/llvm/lib/InterfaceStub/ELFObjHandler.cpp @@ -17,6 +17,7 @@ #include "llvm/Support/FileOutputBuffer.h" #include "llvm/Support/MathExtras.h" #include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Process.h" using llvm::MemoryBufferRef; using llvm::object::ELFObjectFile; @@ -657,30 +658,97 @@ return std::move(DestStub); } +static Error restoreStatOnFile(StringRef Filename, + const sys::fs::file_status &Stat, + bool PreserveDates) { + int FD; + + // Writing to stdout should not be treated as an error here, just + // do not set access/modification times or permissions. + if (Filename == "-") + return Error::success(); + + if (auto EC = + sys::fs::openFileForWrite(Filename, FD, sys::fs::CD_OpenExisting)) + return createFileError(Filename, EC); + + if (PreserveDates) + if (auto EC = sys::fs::setLastAccessAndModificationTime( + FD, Stat.getLastAccessedTime(), Stat.getLastModificationTime())) + return createFileError(Filename, EC); + + sys::fs::file_status OStat; + if (std::error_code EC = sys::fs::status(FD, OStat)) + return createFileError(Filename, EC); + if (OStat.type() == sys::fs::file_type::regular_file) +#ifdef _WIN32 + if (auto EC = sys::fs::setPermissions( + Filename, static_cast(Stat.permissions() & + ~sys::fs::getUmask()))) +#else + if (auto EC = sys::fs::setPermissions( + FD, static_cast(Stat.permissions() & + ~sys::fs::getUmask()))) +#endif + return createFileError(Filename, EC); + + if (auto EC = sys::Process::SafelyCloseFileDescriptor(FD)) + return createFileError(Filename, EC); + + return Error::success(); +} + /// This function opens a file for writing and then writes a binary ELF stub to /// the file. /// /// @param FilePath File path for writing the ELF binary. /// @param Stub Source ELFStub to generate a binary ELF stub from. template -static Error writeELFBinaryToFile(StringRef FilePath, const ELFStub &Stub) { +static Error writeELFBinaryToFile(StringRef FilePath, const ELFStub &Stub, + bool PreserveDates) { + sys::fs::file_status Stat; + if (FilePath == "-" || sys::fs::status(FilePath, Stat)) + Stat.permissions(static_cast(0777)); + std::unique_ptr InputData; + size_t InputSize = 0; + if (PreserveDates) { + // Read ELF Stub if it already exist. + ErrorOr> InputBufOrError = + MemoryBuffer::getFile(FilePath); + if (InputBufOrError) { + std::unique_ptr FileReadBuffer = + std::move(*InputBufOrError); + InputSize = FileReadBuffer->getBufferSize(); + InputData = std::unique_ptr( + new uint8_t[FileReadBuffer->getBufferSize()]); + memcpy(InputData.get(), FileReadBuffer->getBufferStart(), InputSize); + } else { + PreserveDates = false; + } + } + ELFStubBuilder Builder{Stub}; - Expected> BufOrError = + Expected> OutputBufOrError = FileOutputBuffer::create(FilePath, Builder.getSize()); - if (!BufOrError) + if (!OutputBufOrError) return createStringError(errc::invalid_argument, - toString(BufOrError.takeError()) + + toString(OutputBufOrError.takeError()) + " when trying to open `" + FilePath + "` for writing"); + std::unique_ptr OutputBuf = std::move(*OutputBufOrError); + Builder.write(OutputBuf->getBufferStart()); - // Write binary to file. - std::unique_ptr Buf = std::move(*BufOrError); - Builder.write(Buf->getBufferStart()); + // Compare existing ELF Stub with Output if PreserveDates is set to true. + // If they match, restore the original timestamp. + if (PreserveDates) + PreserveDates = + InputSize == Builder.getSize() && + !memcmp(InputData.get(), OutputBuf->getBufferStart(), InputSize); - if (Error E = Buf->commit()) + if (Error E = OutputBuf->commit()) return E; - return Error::success(); + return restoreStatOnFile(FilePath, Stat, PreserveDates); } Expected> readELFFile(MemoryBufferRef Buf) { @@ -705,15 +773,15 @@ // This function wraps the ELFT writeELFBinaryToFile() so writeBinaryStub() // can be called without having to use ELFType templates directly. Error writeBinaryStub(StringRef FilePath, const ELFStub &Stub, - ELFTarget OutputFormat) { + ELFTarget OutputFormat, bool PreserveDates) { if (OutputFormat == ELFTarget::ELF32LE) - return writeELFBinaryToFile(FilePath, Stub); + return writeELFBinaryToFile(FilePath, Stub, PreserveDates); if (OutputFormat == ELFTarget::ELF32BE) - return writeELFBinaryToFile(FilePath, Stub); + return writeELFBinaryToFile(FilePath, Stub, PreserveDates); if (OutputFormat == ELFTarget::ELF64LE) - return writeELFBinaryToFile(FilePath, Stub); + return writeELFBinaryToFile(FilePath, Stub, PreserveDates); if (OutputFormat == ELFTarget::ELF64BE) - return writeELFBinaryToFile(FilePath, Stub); + return writeELFBinaryToFile(FilePath, Stub, PreserveDates); llvm_unreachable("invalid binary output target"); } diff --git a/llvm/test/tools/llvm-elfabi/preserve-dates-stub.test b/llvm/test/tools/llvm-elfabi/preserve-dates-stub.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-elfabi/preserve-dates-stub.test @@ -0,0 +1,19 @@ +## Test writing unchanged content to ELF Stub file with --preserve-dates flag. + +# RUN: llvm-elfabi %s --output-target=elf64-little %t +# RUN: touch -m -d "1970-01-01 00:00:00" %t +# RUN: llvm-elfabi %s --output-target=elf64-little %t --preserve-dates +# RUN: ls -l %t | FileCheck %s + +--- !tapi-tbe +TbeVersion: 1.0 +Arch: x86_64 +NeededLibs: + - libc.so.6 +Symbols: + bar: { Type: Object, Size: 42 } + baz: { Type: TLS, Size: 3 } + plus: { Type: Func } +... + +# CHECK: {{[[:space:]]1970}} diff --git a/llvm/test/tools/llvm-elfabi/preserve-dates-tbe.test b/llvm/test/tools/llvm-elfabi/preserve-dates-tbe.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-elfabi/preserve-dates-tbe.test @@ -0,0 +1,8 @@ +## Test writing unchanged content to TBE file with --preserve-dates flag. + +# RUN: llvm-elfabi --elf %p/Inputs/gnu_hash.so --emit-tbe=%t +# RUN: touch -m -d "1970-01-01 00:00:00" %t +# RUN: llvm-elfabi --elf %p/Inputs/gnu_hash.so --emit-tbe=%t --preserve-dates +# RUN: ls -l %t | FileCheck %s + +# CHECK: {{[[:space:]]1970}} diff --git a/llvm/tools/llvm-elfabi/llvm-elfabi.cpp b/llvm/tools/llvm-elfabi/llvm-elfabi.cpp --- a/llvm/tools/llvm-elfabi/llvm-elfabi.cpp +++ b/llvm/tools/llvm-elfabi/llvm-elfabi.cpp @@ -57,22 +57,40 @@ clEnumValN(ELFTarget::ELF64BE, "elf64-big", "64-bit big-endian ELF stub"))); cl::opt BinaryOutputFilePath(cl::Positional, cl::desc("output")); +cl::opt + PreserveDates("preserve-dates", + cl::desc("preserve the timestamp of the output file " + "if the content is not changed")); /// writeTBE() writes a Text-Based ELF stub to a file using the latest version /// of the YAML parser. static Error writeTBE(StringRef FilePath, ELFStub &Stub) { + // Write TBE to memory first. + std::string TBEStr; + raw_string_ostream OutStr(TBEStr); + Error YAMLErr = writeTBEToOutputStream(OutStr, Stub); + if (YAMLErr) + return YAMLErr; + OutStr.str(); + + if (PreserveDates) { + ErrorOr> BufOrError = + MemoryBuffer::getFile(FilePath); + if (BufOrError) { + // Compare TBE output with existing TBE file. + std::unique_ptr FileReadBuffer = std::move(*BufOrError); + // If TBE file unchanged, abort updating. + if (FileReadBuffer->getBuffer() == TBEStr) + return Error::success(); + } + } + // Open TBE file for writing. std::error_code SysErr; - - // Open file for writing. raw_fd_ostream Out(FilePath, SysErr); if (SysErr) return createStringError(SysErr, "Couldn't open `%s` for writing", FilePath.data()); - // Write file. - Error YAMLErr = writeTBEToOutputStream(Out, Stub); - if (YAMLErr) - return YAMLErr; - + Out << TBEStr; return Error::success(); } @@ -153,8 +171,8 @@ if (BinaryOutputTarget.getNumOccurrences() == 0) fatalError(createStringError(errc::not_supported, "no binary output target specified.")); - Error BinaryWriteError = - writeBinaryStub(BinaryOutputFilePath, *TargetStub, BinaryOutputTarget); + Error BinaryWriteError = writeBinaryStub(BinaryOutputFilePath, *TargetStub, + BinaryOutputTarget, PreserveDates); if (BinaryWriteError) fatalError(std::move(BinaryWriteError)); }