diff options
Diffstat (limited to 'contrib/llvm/lib/Object/ArchiveWriter.cpp')
| -rw-r--r-- | contrib/llvm/lib/Object/ArchiveWriter.cpp | 434 | 
1 files changed, 434 insertions, 0 deletions
diff --git a/contrib/llvm/lib/Object/ArchiveWriter.cpp b/contrib/llvm/lib/Object/ArchiveWriter.cpp new file mode 100644 index 000000000000..f8e3c5a0a03f --- /dev/null +++ b/contrib/llvm/lib/Object/ArchiveWriter.cpp @@ -0,0 +1,434 @@ +//===- ArchiveWriter.cpp - ar File Format implementation --------*- C++ -*-===// +// +//                     The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines the writeArchive function. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Object/ArchiveWriter.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/Object/Archive.h" +#include "llvm/Object/ObjectFile.h" +#include "llvm/Object/SymbolicFile.h" +#include "llvm/Support/EndianStream.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/Format.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/ToolOutputFile.h" +#include "llvm/Support/raw_ostream.h" + +#if !defined(_MSC_VER) && !defined(__MINGW32__) +#include <unistd.h> +#else +#include <io.h> +#endif + +using namespace llvm; + +NewArchiveMember::NewArchiveMember(MemoryBufferRef BufRef) +    : Buf(MemoryBuffer::getMemBuffer(BufRef, false)) {} + +Expected<NewArchiveMember> +NewArchiveMember::getOldMember(const object::Archive::Child &OldMember, +                               bool Deterministic) { +  Expected<llvm::MemoryBufferRef> BufOrErr = OldMember.getMemoryBufferRef(); +  if (!BufOrErr) +    return BufOrErr.takeError(); + +  NewArchiveMember M; +  assert(M.IsNew == false); +  M.Buf = MemoryBuffer::getMemBuffer(*BufOrErr, false); +  if (!Deterministic) { +    auto ModTimeOrErr = OldMember.getLastModified(); +    if (!ModTimeOrErr) +      return ModTimeOrErr.takeError(); +    M.ModTime = ModTimeOrErr.get(); +    Expected<unsigned> UIDOrErr = OldMember.getUID(); +    if (!UIDOrErr) +      return UIDOrErr.takeError(); +    M.UID = UIDOrErr.get(); +    Expected<unsigned> GIDOrErr = OldMember.getGID(); +    if (!GIDOrErr) +      return GIDOrErr.takeError(); +    M.GID = GIDOrErr.get(); +    Expected<sys::fs::perms> AccessModeOrErr = OldMember.getAccessMode(); +    if (!AccessModeOrErr) +      return AccessModeOrErr.takeError(); +    M.Perms = AccessModeOrErr.get(); +  } +  return std::move(M); +} + +Expected<NewArchiveMember> NewArchiveMember::getFile(StringRef FileName, +                                                     bool Deterministic) { +  sys::fs::file_status Status; +  int FD; +  if (auto EC = sys::fs::openFileForRead(FileName, FD)) +    return errorCodeToError(EC); +  assert(FD != -1); + +  if (auto EC = sys::fs::status(FD, Status)) +    return errorCodeToError(EC); + +  // Opening a directory doesn't make sense. Let it fail. +  // Linux cannot open directories with open(2), although +  // cygwin and *bsd can. +  if (Status.type() == sys::fs::file_type::directory_file) +    return errorCodeToError(make_error_code(errc::is_a_directory)); + +  ErrorOr<std::unique_ptr<MemoryBuffer>> MemberBufferOrErr = +      MemoryBuffer::getOpenFile(FD, FileName, Status.getSize(), false); +  if (!MemberBufferOrErr) +    return errorCodeToError(MemberBufferOrErr.getError()); + +  if (close(FD) != 0) +    return errorCodeToError(std::error_code(errno, std::generic_category())); + +  NewArchiveMember M; +  M.IsNew = true; +  M.Buf = std::move(*MemberBufferOrErr); +  if (!Deterministic) { +    M.ModTime = std::chrono::time_point_cast<std::chrono::seconds>( +        Status.getLastModificationTime()); +    M.UID = Status.getUser(); +    M.GID = Status.getGroup(); +    M.Perms = Status.permissions(); +  } +  return std::move(M); +} + +template <typename T> +static void printWithSpacePadding(raw_fd_ostream &OS, T Data, unsigned Size, +                                  bool MayTruncate = false) { +  uint64_t OldPos = OS.tell(); +  OS << Data; +  unsigned SizeSoFar = OS.tell() - OldPos; +  if (Size > SizeSoFar) { +    OS.indent(Size - SizeSoFar); +  } else if (Size < SizeSoFar) { +    assert(MayTruncate && "Data doesn't fit in Size"); +    // Some of the data this is used for (like UID) can be larger than the +    // space available in the archive format. Truncate in that case. +    OS.seek(OldPos + Size); +  } +} + +static void print32(raw_ostream &Out, object::Archive::Kind Kind, +                    uint32_t Val) { +  if (Kind == object::Archive::K_GNU) +    support::endian::Writer<support::big>(Out).write(Val); +  else +    support::endian::Writer<support::little>(Out).write(Val); +} + +static void printRestOfMemberHeader( +    raw_fd_ostream &Out, const sys::TimePoint<std::chrono::seconds> &ModTime, +    unsigned UID, unsigned GID, unsigned Perms, unsigned Size) { +  printWithSpacePadding(Out, sys::toTimeT(ModTime), 12); +  printWithSpacePadding(Out, UID, 6, true); +  printWithSpacePadding(Out, GID, 6, true); +  printWithSpacePadding(Out, format("%o", Perms), 8); +  printWithSpacePadding(Out, Size, 10); +  Out << "`\n"; +} + +static void +printGNUSmallMemberHeader(raw_fd_ostream &Out, StringRef Name, +                          const sys::TimePoint<std::chrono::seconds> &ModTime, +                          unsigned UID, unsigned GID, unsigned Perms, +                          unsigned Size) { +  printWithSpacePadding(Out, Twine(Name) + "/", 16); +  printRestOfMemberHeader(Out, ModTime, UID, GID, Perms, Size); +} + +static void +printBSDMemberHeader(raw_fd_ostream &Out, StringRef Name, +                     const sys::TimePoint<std::chrono::seconds> &ModTime, +                     unsigned UID, unsigned GID, unsigned Perms, +                     unsigned Size) { +  uint64_t PosAfterHeader = Out.tell() + 60 + Name.size(); +  // Pad so that even 64 bit object files are aligned. +  unsigned Pad = OffsetToAlignment(PosAfterHeader, 8); +  unsigned NameWithPadding = Name.size() + Pad; +  printWithSpacePadding(Out, Twine("#1/") + Twine(NameWithPadding), 16); +  printRestOfMemberHeader(Out, ModTime, UID, GID, Perms, +                          NameWithPadding + Size); +  Out << Name; +  assert(PosAfterHeader == Out.tell()); +  while (Pad--) +    Out.write(uint8_t(0)); +} + +static bool useStringTable(bool Thin, StringRef Name) { +  return Thin || Name.size() >= 16; +} + +static void +printMemberHeader(raw_fd_ostream &Out, object::Archive::Kind Kind, bool Thin, +                  StringRef Name, +                  std::vector<unsigned>::iterator &StringMapIndexIter, +                  const sys::TimePoint<std::chrono::seconds> &ModTime, +                  unsigned UID, unsigned GID, unsigned Perms, unsigned Size) { +  if (Kind == object::Archive::K_BSD) +    return printBSDMemberHeader(Out, Name, ModTime, UID, GID, Perms, Size); +  if (!useStringTable(Thin, Name)) +    return printGNUSmallMemberHeader(Out, Name, ModTime, UID, GID, Perms, Size); +  Out << '/'; +  printWithSpacePadding(Out, *StringMapIndexIter++, 15); +  printRestOfMemberHeader(Out, ModTime, UID, GID, Perms, Size); +} + +// Compute the relative path from From to To. +static std::string computeRelativePath(StringRef From, StringRef To) { +  if (sys::path::is_absolute(From) || sys::path::is_absolute(To)) +    return To; + +  StringRef DirFrom = sys::path::parent_path(From); +  auto FromI = sys::path::begin(DirFrom); +  auto ToI = sys::path::begin(To); +  while (*FromI == *ToI) { +    ++FromI; +    ++ToI; +  } + +  SmallString<128> Relative; +  for (auto FromE = sys::path::end(DirFrom); FromI != FromE; ++FromI) +    sys::path::append(Relative, ".."); + +  for (auto ToE = sys::path::end(To); ToI != ToE; ++ToI) +    sys::path::append(Relative, *ToI); + +#ifdef LLVM_ON_WIN32 +  // Replace backslashes with slashes so that the path is portable between *nix +  // and Windows. +  std::replace(Relative.begin(), Relative.end(), '\\', '/'); +#endif + +  return Relative.str(); +} + +static void writeStringTable(raw_fd_ostream &Out, StringRef ArcName, +                             ArrayRef<NewArchiveMember> Members, +                             std::vector<unsigned> &StringMapIndexes, +                             bool Thin) { +  unsigned StartOffset = 0; +  for (const NewArchiveMember &M : Members) { +    StringRef Path = M.Buf->getBufferIdentifier(); +    StringRef Name = sys::path::filename(Path); +    if (!useStringTable(Thin, Name)) +      continue; +    if (StartOffset == 0) { +      printWithSpacePadding(Out, "//", 58); +      Out << "`\n"; +      StartOffset = Out.tell(); +    } +    StringMapIndexes.push_back(Out.tell() - StartOffset); + +    if (Thin) { +      if (M.IsNew) +        Out << computeRelativePath(ArcName, Path); +      else +        Out << M.Buf->getBufferIdentifier(); +    } else +      Out << Name; + +    Out << "/\n"; +  } +  if (StartOffset == 0) +    return; +  if (Out.tell() % 2) +    Out << '\n'; +  int Pos = Out.tell(); +  Out.seek(StartOffset - 12); +  printWithSpacePadding(Out, Pos - StartOffset, 10); +  Out.seek(Pos); +} + +static sys::TimePoint<std::chrono::seconds> now(bool Deterministic) { +  using namespace std::chrono; + +  if (!Deterministic) +    return time_point_cast<seconds>(system_clock::now()); +  return sys::TimePoint<seconds>(); +} + +// Returns the offset of the first reference to a member offset. +static ErrorOr<unsigned> +writeSymbolTable(raw_fd_ostream &Out, object::Archive::Kind Kind, +                 ArrayRef<NewArchiveMember> Members, +                 std::vector<unsigned> &MemberOffsetRefs, bool Deterministic) { +  unsigned HeaderStartOffset = 0; +  unsigned BodyStartOffset = 0; +  SmallString<128> NameBuf; +  raw_svector_ostream NameOS(NameBuf); +  LLVMContext Context; +  for (unsigned MemberNum = 0, N = Members.size(); MemberNum < N; ++MemberNum) { +    MemoryBufferRef MemberBuffer = Members[MemberNum].Buf->getMemBufferRef(); +    Expected<std::unique_ptr<object::SymbolicFile>> ObjOrErr = +        object::SymbolicFile::createSymbolicFile( +            MemberBuffer, sys::fs::file_magic::unknown, &Context); +    if (!ObjOrErr) { +      // FIXME: check only for "not an object file" errors. +      consumeError(ObjOrErr.takeError()); +      continue; +    } +    object::SymbolicFile &Obj = *ObjOrErr.get(); + +    if (!HeaderStartOffset) { +      HeaderStartOffset = Out.tell(); +      if (Kind == object::Archive::K_GNU) +        printGNUSmallMemberHeader(Out, "", now(Deterministic), 0, 0, 0, 0); +      else +        printBSDMemberHeader(Out, "__.SYMDEF", now(Deterministic), 0, 0, 0, 0); +      BodyStartOffset = Out.tell(); +      print32(Out, Kind, 0); // number of entries or bytes +    } + +    for (const object::BasicSymbolRef &S : Obj.symbols()) { +      uint32_t Symflags = S.getFlags(); +      if (Symflags & object::SymbolRef::SF_FormatSpecific) +        continue; +      if (!(Symflags & object::SymbolRef::SF_Global)) +        continue; +      if (Symflags & object::SymbolRef::SF_Undefined) +        continue; + +      unsigned NameOffset = NameOS.tell(); +      if (auto EC = S.printName(NameOS)) +        return EC; +      NameOS << '\0'; +      MemberOffsetRefs.push_back(MemberNum); +      if (Kind == object::Archive::K_BSD) +        print32(Out, Kind, NameOffset); +      print32(Out, Kind, 0); // member offset +    } +  } + +  if (HeaderStartOffset == 0) +    return 0; + +  StringRef StringTable = NameOS.str(); +  if (Kind == object::Archive::K_BSD) +    print32(Out, Kind, StringTable.size()); // byte count of the string table +  Out << StringTable; + +  // ld64 requires the next member header to start at an offset that is +  // 4 bytes aligned. +  unsigned Pad = OffsetToAlignment(Out.tell(), 4); +  while (Pad--) +    Out.write(uint8_t(0)); + +  // Patch up the size of the symbol table now that we know how big it is. +  unsigned Pos = Out.tell(); +  const unsigned MemberHeaderSize = 60; +  Out.seek(HeaderStartOffset + 48); // offset of the size field. +  printWithSpacePadding(Out, Pos - MemberHeaderSize - HeaderStartOffset, 10); + +  // Patch up the number of symbols. +  Out.seek(BodyStartOffset); +  unsigned NumSyms = MemberOffsetRefs.size(); +  if (Kind == object::Archive::K_GNU) +    print32(Out, Kind, NumSyms); +  else +    print32(Out, Kind, NumSyms * 8); + +  Out.seek(Pos); +  return BodyStartOffset + 4; +} + +std::pair<StringRef, std::error_code> +llvm::writeArchive(StringRef ArcName, +                   std::vector<NewArchiveMember> &NewMembers, +                   bool WriteSymtab, object::Archive::Kind Kind, +                   bool Deterministic, bool Thin, +                   std::unique_ptr<MemoryBuffer> OldArchiveBuf) { +  assert((!Thin || Kind == object::Archive::K_GNU) && +         "Only the gnu format has a thin mode"); +  SmallString<128> TmpArchive; +  int TmpArchiveFD; +  if (auto EC = sys::fs::createUniqueFile(ArcName + ".temp-archive-%%%%%%%.a", +                                          TmpArchiveFD, TmpArchive)) +    return std::make_pair(ArcName, EC); + +  tool_output_file Output(TmpArchive, TmpArchiveFD); +  raw_fd_ostream &Out = Output.os(); +  if (Thin) +    Out << "!<thin>\n"; +  else +    Out << "!<arch>\n"; + +  std::vector<unsigned> MemberOffsetRefs; + +  std::vector<std::unique_ptr<MemoryBuffer>> Buffers; +  std::vector<MemoryBufferRef> Members; +  std::vector<sys::fs::file_status> NewMemberStatus; + +  unsigned MemberReferenceOffset = 0; +  if (WriteSymtab) { +    ErrorOr<unsigned> MemberReferenceOffsetOrErr = writeSymbolTable( +        Out, Kind, NewMembers, MemberOffsetRefs, Deterministic); +    if (auto EC = MemberReferenceOffsetOrErr.getError()) +      return std::make_pair(ArcName, EC); +    MemberReferenceOffset = MemberReferenceOffsetOrErr.get(); +  } + +  std::vector<unsigned> StringMapIndexes; +  if (Kind != object::Archive::K_BSD) +    writeStringTable(Out, ArcName, NewMembers, StringMapIndexes, Thin); + +  std::vector<unsigned>::iterator StringMapIndexIter = StringMapIndexes.begin(); +  std::vector<unsigned> MemberOffset; +  for (const NewArchiveMember &M : NewMembers) { +    MemoryBufferRef File = M.Buf->getMemBufferRef(); + +    unsigned Pos = Out.tell(); +    MemberOffset.push_back(Pos); + +    printMemberHeader(Out, Kind, Thin, +                      sys::path::filename(M.Buf->getBufferIdentifier()), +                      StringMapIndexIter, M.ModTime, M.UID, M.GID, M.Perms, +                      M.Buf->getBufferSize()); + +    if (!Thin) +      Out << File.getBuffer(); + +    if (Out.tell() % 2) +      Out << '\n'; +  } + +  if (MemberReferenceOffset) { +    Out.seek(MemberReferenceOffset); +    for (unsigned MemberNum : MemberOffsetRefs) { +      if (Kind == object::Archive::K_BSD) +        Out.seek(Out.tell() + 4); // skip over the string offset +      print32(Out, Kind, MemberOffset[MemberNum]); +    } +  } + +  Output.keep(); +  Out.close(); + +  // At this point, we no longer need whatever backing memory +  // was used to generate the NewMembers. On Windows, this buffer +  // could be a mapped view of the file we want to replace (if +  // we're updating an existing archive, say). In that case, the +  // rename would still succeed, but it would leave behind a +  // temporary file (actually the original file renamed) because +  // a file cannot be deleted while there's a handle open on it, +  // only renamed. So by freeing this buffer, this ensures that +  // the last open handle on the destination file, if any, is +  // closed before we attempt to rename. +  OldArchiveBuf.reset(); + +  sys::fs::rename(TmpArchive, ArcName); +  return std::make_pair("", std::error_code()); +}  | 
