diff options
author | vitalyisaev <vitalyisaev@yandex-team.com> | 2023-06-29 10:00:50 +0300 |
---|---|---|
committer | vitalyisaev <vitalyisaev@yandex-team.com> | 2023-06-29 10:00:50 +0300 |
commit | 6ffe9e53658409f212834330e13564e4952558f6 (patch) | |
tree | 85b1e00183517648b228aafa7c8fb07f5276f419 /contrib/libs/llvm16/lib/Object/ArchiveWriter.cpp | |
parent | 726057070f9c5a91fc10fde0d5024913d10f1ab9 (diff) | |
download | ydb-6ffe9e53658409f212834330e13564e4952558f6.tar.gz |
YQ Connector: support managed ClickHouse
Со стороны dqrun можно обратиться к инстансу коннектора, который работает на streaming стенде, и извлечь данные из облачного CH.
Diffstat (limited to 'contrib/libs/llvm16/lib/Object/ArchiveWriter.cpp')
-rw-r--r-- | contrib/libs/llvm16/lib/Object/ArchiveWriter.cpp | 870 |
1 files changed, 870 insertions, 0 deletions
diff --git a/contrib/libs/llvm16/lib/Object/ArchiveWriter.cpp b/contrib/libs/llvm16/lib/Object/ArchiveWriter.cpp new file mode 100644 index 0000000000..0d3aad658f --- /dev/null +++ b/contrib/libs/llvm16/lib/Object/ArchiveWriter.cpp @@ -0,0 +1,870 @@ +//===- ArchiveWriter.cpp - ar File Format implementation --------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines the writeArchive function. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Object/ArchiveWriter.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/BinaryFormat/Magic.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/Object/Archive.h" +#include "llvm/Object/Error.h" +#include "llvm/Object/IRObjectFile.h" +#include "llvm/Object/MachO.h" +#include "llvm/Object/ObjectFile.h" +#include "llvm/Object/SymbolicFile.h" +#include "llvm/Object/XCOFFObjectFile.h" +#include "llvm/Support/Alignment.h" +#include "llvm/Support/EndianStream.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/Format.h" +#include "llvm/Support/MathExtras.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/SmallVectorMemoryBuffer.h" +#include "llvm/Support/raw_ostream.h" + +#include <map> + +#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)), + MemberName(BufRef.getBufferIdentifier()) {} + +object::Archive::Kind NewArchiveMember::detectKindFromObject() const { + auto MemBufferRef = this->Buf->getMemBufferRef(); + Expected<std::unique_ptr<object::ObjectFile>> OptionalObject = + object::ObjectFile::createObjectFile(MemBufferRef); + + if (OptionalObject) + return isa<object::MachOObjectFile>(**OptionalObject) + ? object::Archive::K_DARWIN + : (isa<object::XCOFFObjectFile>(**OptionalObject) + ? object::Archive::K_AIXBIG + : object::Archive::K_GNU); + + // Squelch the error in case we had a non-object file. + consumeError(OptionalObject.takeError()); + + // If we're adding a bitcode file to the archive, detect the Archive kind + // based on the target triple. + LLVMContext Context; + if (identify_magic(MemBufferRef.getBuffer()) == file_magic::bitcode) { + if (auto ObjOrErr = object::SymbolicFile::createSymbolicFile( + MemBufferRef, file_magic::bitcode, &Context)) { + auto &IRObject = cast<object::IRObjectFile>(**ObjOrErr); + return Triple(IRObject.getTargetTriple()).isOSDarwin() + ? object::Archive::K_DARWIN + : object::Archive::K_GNU; + } else { + // Squelch the error in case this was not a SymbolicFile. + consumeError(ObjOrErr.takeError()); + } + } + + return object::Archive::getDefaultKindForHost(); +} + +Expected<NewArchiveMember> +NewArchiveMember::getOldMember(const object::Archive::Child &OldMember, + bool Deterministic) { + Expected<llvm::MemoryBufferRef> BufOrErr = OldMember.getMemoryBufferRef(); + if (!BufOrErr) + return BufOrErr.takeError(); + + NewArchiveMember M; + M.Buf = MemoryBuffer::getMemBuffer(*BufOrErr, false); + M.MemberName = M.Buf->getBufferIdentifier(); + 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; + auto FDOrErr = sys::fs::openNativeFileForRead(FileName); + if (!FDOrErr) + return FDOrErr.takeError(); + sys::fs::file_t FD = *FDOrErr; + assert(FD != sys::fs::kInvalidFile); + + 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 (auto EC = sys::fs::closeFile(FD)) + return errorCodeToError(EC); + + NewArchiveMember M; + M.Buf = std::move(*MemberBufferOrErr); + M.MemberName = M.Buf->getBufferIdentifier(); + 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_ostream &OS, T Data, unsigned Size) { + uint64_t OldPos = OS.tell(); + OS << Data; + unsigned SizeSoFar = OS.tell() - OldPos; + assert(SizeSoFar <= Size && "Data doesn't fit in Size"); + OS.indent(Size - SizeSoFar); +} + +static bool isDarwin(object::Archive::Kind Kind) { + return Kind == object::Archive::K_DARWIN || + Kind == object::Archive::K_DARWIN64; +} + +static bool isAIXBigArchive(object::Archive::Kind Kind) { + return Kind == object::Archive::K_AIXBIG; +} + +static bool isBSDLike(object::Archive::Kind Kind) { + switch (Kind) { + case object::Archive::K_GNU: + case object::Archive::K_GNU64: + case object::Archive::K_AIXBIG: + return false; + case object::Archive::K_BSD: + case object::Archive::K_DARWIN: + case object::Archive::K_DARWIN64: + return true; + case object::Archive::K_COFF: + break; + } + llvm_unreachable("not supported for writting"); +} + +template <class T> +static void print(raw_ostream &Out, object::Archive::Kind Kind, T Val) { + support::endian::write(Out, Val, + isBSDLike(Kind) ? support::little : support::big); +} + +static void printRestOfMemberHeader( + raw_ostream &Out, const sys::TimePoint<std::chrono::seconds> &ModTime, + unsigned UID, unsigned GID, unsigned Perms, uint64_t Size) { + printWithSpacePadding(Out, sys::toTimeT(ModTime), 12); + + // The format has only 6 chars for uid and gid. Truncate if the provided + // values don't fit. + printWithSpacePadding(Out, UID % 1000000, 6); + printWithSpacePadding(Out, GID % 1000000, 6); + + printWithSpacePadding(Out, format("%o", Perms), 8); + printWithSpacePadding(Out, Size, 10); + Out << "`\n"; +} + +static void +printGNUSmallMemberHeader(raw_ostream &Out, StringRef Name, + const sys::TimePoint<std::chrono::seconds> &ModTime, + unsigned UID, unsigned GID, unsigned Perms, + uint64_t Size) { + printWithSpacePadding(Out, Twine(Name) + "/", 16); + printRestOfMemberHeader(Out, ModTime, UID, GID, Perms, Size); +} + +static void +printBSDMemberHeader(raw_ostream &Out, uint64_t Pos, StringRef Name, + const sys::TimePoint<std::chrono::seconds> &ModTime, + unsigned UID, unsigned GID, unsigned Perms, uint64_t Size) { + uint64_t PosAfterHeader = Pos + 60 + Name.size(); + // Pad so that even 64 bit object files are aligned. + unsigned Pad = offsetToAlignment(PosAfterHeader, Align(8)); + unsigned NameWithPadding = Name.size() + Pad; + printWithSpacePadding(Out, Twine("#1/") + Twine(NameWithPadding), 16); + printRestOfMemberHeader(Out, ModTime, UID, GID, Perms, + NameWithPadding + Size); + Out << Name; + while (Pad--) + Out.write(uint8_t(0)); +} + +static void +printBigArchiveMemberHeader(raw_ostream &Out, StringRef Name, + const sys::TimePoint<std::chrono::seconds> &ModTime, + unsigned UID, unsigned GID, unsigned Perms, + uint64_t Size, unsigned PrevOffset, + unsigned NextOffset) { + unsigned NameLen = Name.size(); + + printWithSpacePadding(Out, Size, 20); // File member size + printWithSpacePadding(Out, NextOffset, 20); // Next member header offset + printWithSpacePadding(Out, PrevOffset, 20); // Previous member header offset + printWithSpacePadding(Out, sys::toTimeT(ModTime), 12); // File member date + // The big archive format has 12 chars for uid and gid. + printWithSpacePadding(Out, UID % 1000000000000, 12); // UID + printWithSpacePadding(Out, GID % 1000000000000, 12); // GID + printWithSpacePadding(Out, format("%o", Perms), 12); // Permission + printWithSpacePadding(Out, NameLen, 4); // Name length + if (NameLen) { + printWithSpacePadding(Out, Name, NameLen); // Name + if (NameLen % 2) + Out.write(uint8_t(0)); // Null byte padding + } + Out << "`\n"; // Terminator +} + +static bool useStringTable(bool Thin, StringRef Name) { + return Thin || Name.size() >= 16 || Name.contains('/'); +} + +static bool is64BitKind(object::Archive::Kind Kind) { + switch (Kind) { + case object::Archive::K_GNU: + case object::Archive::K_BSD: + case object::Archive::K_DARWIN: + case object::Archive::K_COFF: + return false; + case object::Archive::K_AIXBIG: + case object::Archive::K_DARWIN64: + case object::Archive::K_GNU64: + return true; + } + llvm_unreachable("not supported for writting"); +} + +static void +printMemberHeader(raw_ostream &Out, uint64_t Pos, raw_ostream &StringTable, + StringMap<uint64_t> &MemberNames, object::Archive::Kind Kind, + bool Thin, const NewArchiveMember &M, + sys::TimePoint<std::chrono::seconds> ModTime, uint64_t Size) { + if (isBSDLike(Kind)) + return printBSDMemberHeader(Out, Pos, M.MemberName, ModTime, M.UID, M.GID, + M.Perms, Size); + if (!useStringTable(Thin, M.MemberName)) + return printGNUSmallMemberHeader(Out, M.MemberName, ModTime, M.UID, M.GID, + M.Perms, Size); + Out << '/'; + uint64_t NamePos; + if (Thin) { + NamePos = StringTable.tell(); + StringTable << M.MemberName << "/\n"; + } else { + auto Insertion = MemberNames.insert({M.MemberName, uint64_t(0)}); + if (Insertion.second) { + Insertion.first->second = StringTable.tell(); + StringTable << M.MemberName << "/\n"; + } + NamePos = Insertion.first->second; + } + printWithSpacePadding(Out, NamePos, 15); + printRestOfMemberHeader(Out, ModTime, M.UID, M.GID, M.Perms, Size); +} + +namespace { +struct MemberData { + std::vector<unsigned> Symbols; + std::string Header; + StringRef Data; + StringRef Padding; +}; +} // namespace + +static MemberData computeStringTable(StringRef Names) { + unsigned Size = Names.size(); + unsigned Pad = offsetToAlignment(Size, Align(2)); + std::string Header; + raw_string_ostream Out(Header); + printWithSpacePadding(Out, "//", 48); + printWithSpacePadding(Out, Size + Pad, 10); + Out << "`\n"; + Out.flush(); + return {{}, std::move(Header), Names, Pad ? "\n" : ""}; +} + +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>(); +} + +static bool isArchiveSymbol(const object::BasicSymbolRef &S) { + Expected<uint32_t> SymFlagsOrErr = S.getFlags(); + if (!SymFlagsOrErr) + // TODO: Actually report errors helpfully. + report_fatal_error(SymFlagsOrErr.takeError()); + if (*SymFlagsOrErr & object::SymbolRef::SF_FormatSpecific) + return false; + if (!(*SymFlagsOrErr & object::SymbolRef::SF_Global)) + return false; + if (*SymFlagsOrErr & object::SymbolRef::SF_Undefined) + return false; + return true; +} + +static void printNBits(raw_ostream &Out, object::Archive::Kind Kind, + uint64_t Val) { + if (is64BitKind(Kind)) + print<uint64_t>(Out, Kind, Val); + else + print<uint32_t>(Out, Kind, Val); +} + +static uint64_t computeSymbolTableSize(object::Archive::Kind Kind, + uint64_t NumSyms, uint64_t OffsetSize, + StringRef StringTable, + uint32_t *Padding = nullptr) { + assert((OffsetSize == 4 || OffsetSize == 8) && "Unsupported OffsetSize"); + uint64_t Size = OffsetSize; // Number of entries + if (isBSDLike(Kind)) + Size += NumSyms * OffsetSize * 2; // Table + else + Size += NumSyms * OffsetSize; // Table + if (isBSDLike(Kind)) + Size += OffsetSize; // byte count + Size += StringTable.size(); + // ld64 expects the members to be 8-byte aligned for 64-bit content and at + // least 4-byte aligned for 32-bit content. Opt for the larger encoding + // uniformly. + // We do this for all bsd formats because it simplifies aligning members. + // For the big archive format, the symbol table is the last member, so there + // is no need to align. + uint32_t Pad = isAIXBigArchive(Kind) + ? 0 + : offsetToAlignment(Size, Align(isBSDLike(Kind) ? 8 : 2)); + Size += Pad; + if (Padding) + *Padding = Pad; + return Size; +} + +static void writeSymbolTableHeader(raw_ostream &Out, object::Archive::Kind Kind, + bool Deterministic, uint64_t Size, + uint64_t PrevMemberOffset = 0) { + if (isBSDLike(Kind)) { + const char *Name = is64BitKind(Kind) ? "__.SYMDEF_64" : "__.SYMDEF"; + printBSDMemberHeader(Out, Out.tell(), Name, now(Deterministic), 0, 0, 0, + Size); + } else if (isAIXBigArchive(Kind)) { + printBigArchiveMemberHeader(Out, "", now(Deterministic), 0, 0, + 0, Size, PrevMemberOffset, 0); + } else { + const char *Name = is64BitKind(Kind) ? "/SYM64" : ""; + printGNUSmallMemberHeader(Out, Name, now(Deterministic), 0, 0, 0, Size); + } +} + +static void writeSymbolTable(raw_ostream &Out, object::Archive::Kind Kind, + bool Deterministic, ArrayRef<MemberData> Members, + StringRef StringTable, + uint64_t PrevMemberOffset = 0) { + // We don't write a symbol table on an archive with no members -- except on + // Darwin, where the linker will abort unless the archive has a symbol table. + if (StringTable.empty() && !isDarwin(Kind)) + return; + + unsigned NumSyms = 0; + for (const MemberData &M : Members) + NumSyms += M.Symbols.size(); + + uint64_t OffsetSize = is64BitKind(Kind) ? 8 : 4; + uint32_t Pad; + uint64_t Size = computeSymbolTableSize(Kind, NumSyms, OffsetSize, StringTable, &Pad); + writeSymbolTableHeader(Out, Kind, Deterministic, Size, PrevMemberOffset); + + uint64_t Pos = isAIXBigArchive(Kind) ? sizeof(object::BigArchive::FixLenHdr) + : Out.tell() + Size; + + if (isBSDLike(Kind)) + printNBits(Out, Kind, NumSyms * 2 * OffsetSize); + else + printNBits(Out, Kind, NumSyms); + + for (const MemberData &M : Members) { + for (unsigned StringOffset : M.Symbols) { + if (isBSDLike(Kind)) + printNBits(Out, Kind, StringOffset); + printNBits(Out, Kind, Pos); // member offset + } + Pos += M.Header.size() + M.Data.size() + M.Padding.size(); + } + + if (isBSDLike(Kind)) + // byte count of the string table + printNBits(Out, Kind, StringTable.size()); + Out << StringTable; + + while (Pad--) + Out.write(uint8_t(0)); +} + +static Expected<std::vector<unsigned>> +getSymbols(MemoryBufferRef Buf, raw_ostream &SymNames, bool &HasObject) { + std::vector<unsigned> Ret; + + // In the scenario when LLVMContext is populated SymbolicFile will contain a + // reference to it, thus SymbolicFile should be destroyed first. + LLVMContext Context; + std::unique_ptr<object::SymbolicFile> Obj; + + const file_magic Type = identify_magic(Buf.getBuffer()); + // Treat unsupported file types as having no symbols. + if (!object::SymbolicFile::isSymbolicFile(Type, &Context)) + return Ret; + if (Type == file_magic::bitcode) { + auto ObjOrErr = object::SymbolicFile::createSymbolicFile( + Buf, file_magic::bitcode, &Context); + if (!ObjOrErr) + return ObjOrErr.takeError(); + Obj = std::move(*ObjOrErr); + } else { + auto ObjOrErr = object::SymbolicFile::createSymbolicFile(Buf); + if (!ObjOrErr) + return ObjOrErr.takeError(); + Obj = std::move(*ObjOrErr); + } + + HasObject = true; + for (const object::BasicSymbolRef &S : Obj->symbols()) { + if (!isArchiveSymbol(S)) + continue; + Ret.push_back(SymNames.tell()); + if (Error E = S.printName(SymNames)) + return std::move(E); + SymNames << '\0'; + } + return Ret; +} + +static Expected<std::vector<MemberData>> +computeMemberData(raw_ostream &StringTable, raw_ostream &SymNames, + object::Archive::Kind Kind, bool Thin, bool Deterministic, + bool NeedSymbols, ArrayRef<NewArchiveMember> NewMembers) { + static char PaddingData[8] = {'\n', '\n', '\n', '\n', '\n', '\n', '\n', '\n'}; + + uint64_t Pos = + isAIXBigArchive(Kind) ? sizeof(object::BigArchive::FixLenHdr) : 0; + + std::vector<MemberData> Ret; + bool HasObject = false; + + // Deduplicate long member names in the string table and reuse earlier name + // offsets. This especially saves space for COFF Import libraries where all + // members have the same name. + StringMap<uint64_t> MemberNames; + + // UniqueTimestamps is a special case to improve debugging on Darwin: + // + // The Darwin linker does not link debug info into the final + // binary. Instead, it emits entries of type N_OSO in in the output + // binary's symbol table, containing references to the linked-in + // object files. Using that reference, the debugger can read the + // debug data directly from the object files. Alternatively, an + // invocation of 'dsymutil' will link the debug data from the object + // files into a dSYM bundle, which can be loaded by the debugger, + // instead of the object files. + // + // For an object file, the N_OSO entries contain the absolute path + // path to the file, and the file's timestamp. For an object + // included in an archive, the path is formatted like + // "/absolute/path/to/archive.a(member.o)", and the timestamp is the + // archive member's timestamp, rather than the archive's timestamp. + // + // However, this doesn't always uniquely identify an object within + // an archive -- an archive file can have multiple entries with the + // same filename. (This will happen commonly if the original object + // files started in different directories.) The only way they get + // distinguished, then, is via the timestamp. But this process is + // unable to find the correct object file in the archive when there + // are two files of the same name and timestamp. + // + // Additionally, timestamp==0 is treated specially, and causes the + // timestamp to be ignored as a match criteria. + // + // That will "usually" work out okay when creating an archive not in + // deterministic timestamp mode, because the objects will probably + // have been created at different timestamps. + // + // To ameliorate this problem, in deterministic archive mode (which + // is the default), on Darwin we will emit a unique non-zero + // timestamp for each entry with a duplicated name. This is still + // deterministic: the only thing affecting that timestamp is the + // order of the files in the resultant archive. + // + // See also the functions that handle the lookup: + // in lldb: ObjectContainerBSDArchive::Archive::FindObject() + // in llvm/tools/dsymutil: BinaryHolder::GetArchiveMemberBuffers(). + bool UniqueTimestamps = Deterministic && isDarwin(Kind); + std::map<StringRef, unsigned> FilenameCount; + if (UniqueTimestamps) { + for (const NewArchiveMember &M : NewMembers) + FilenameCount[M.MemberName]++; + for (auto &Entry : FilenameCount) + Entry.second = Entry.second > 1 ? 1 : 0; + } + + // The big archive format needs to know the offset of the previous member + // header. + unsigned PrevOffset = 0; + for (const NewArchiveMember &M : NewMembers) { + std::string Header; + raw_string_ostream Out(Header); + + MemoryBufferRef Buf = M.Buf->getMemBufferRef(); + StringRef Data = Thin ? "" : Buf.getBuffer(); + + // ld64 expects the members to be 8-byte aligned for 64-bit content and at + // least 4-byte aligned for 32-bit content. Opt for the larger encoding + // uniformly. This matches the behaviour with cctools and ensures that ld64 + // is happy with archives that we generate. + unsigned MemberPadding = + isDarwin(Kind) ? offsetToAlignment(Data.size(), Align(8)) : 0; + unsigned TailPadding = + offsetToAlignment(Data.size() + MemberPadding, Align(2)); + StringRef Padding = StringRef(PaddingData, MemberPadding + TailPadding); + + sys::TimePoint<std::chrono::seconds> ModTime; + if (UniqueTimestamps) + // Increment timestamp for each file of a given name. + ModTime = sys::toTimePoint(FilenameCount[M.MemberName]++); + else + ModTime = M.ModTime; + + uint64_t Size = Buf.getBufferSize() + MemberPadding; + if (Size > object::Archive::MaxMemberSize) { + std::string StringMsg = + "File " + M.MemberName.str() + " exceeds size limit"; + return make_error<object::GenericBinaryError>( + std::move(StringMsg), object::object_error::parse_failed); + } + + if (isAIXBigArchive(Kind)) { + unsigned NextOffset = Pos + sizeof(object::BigArMemHdrType) + + alignTo(M.MemberName.size(), 2) + alignTo(Size, 2); + printBigArchiveMemberHeader(Out, M.MemberName, ModTime, M.UID, M.GID, + M.Perms, Size, PrevOffset, NextOffset); + PrevOffset = Pos; + } else { + printMemberHeader(Out, Pos, StringTable, MemberNames, Kind, Thin, M, + ModTime, Size); + } + Out.flush(); + + std::vector<unsigned> Symbols; + if (NeedSymbols) { + Expected<std::vector<unsigned>> SymbolsOrErr = + getSymbols(Buf, SymNames, HasObject); + if (!SymbolsOrErr) + return createFileError(M.MemberName, SymbolsOrErr.takeError()); + Symbols = std::move(*SymbolsOrErr); + } + + Pos += Header.size() + Data.size() + Padding.size(); + Ret.push_back({std::move(Symbols), std::move(Header), Data, Padding}); + } + // If there are no symbols, emit an empty symbol table, to satisfy Solaris + // tools, older versions of which expect a symbol table in a non-empty + // archive, regardless of whether there are any symbols in it. + if (HasObject && SymNames.tell() == 0) + SymNames << '\0' << '\0' << '\0'; + return Ret; +} + +namespace llvm { + +static ErrorOr<SmallString<128>> canonicalizePath(StringRef P) { + SmallString<128> Ret = P; + std::error_code Err = sys::fs::make_absolute(Ret); + if (Err) + return Err; + sys::path::remove_dots(Ret, /*removedotdot*/ true); + return Ret; +} + +// Compute the relative path from From to To. +Expected<std::string> computeArchiveRelativePath(StringRef From, StringRef To) { + ErrorOr<SmallString<128>> PathToOrErr = canonicalizePath(To); + ErrorOr<SmallString<128>> DirFromOrErr = canonicalizePath(From); + if (!PathToOrErr || !DirFromOrErr) + return errorCodeToError(std::error_code(errno, std::generic_category())); + + const SmallString<128> &PathTo = *PathToOrErr; + const SmallString<128> &DirFrom = sys::path::parent_path(*DirFromOrErr); + + // Can't construct a relative path between different roots + if (sys::path::root_name(PathTo) != sys::path::root_name(DirFrom)) + return sys::path::convert_to_slash(PathTo); + + // Skip common prefixes + auto FromTo = + std::mismatch(sys::path::begin(DirFrom), sys::path::end(DirFrom), + sys::path::begin(PathTo)); + auto FromI = FromTo.first; + auto ToI = FromTo.second; + + // Construct relative path + SmallString<128> Relative; + for (auto FromE = sys::path::end(DirFrom); FromI != FromE; ++FromI) + sys::path::append(Relative, sys::path::Style::posix, ".."); + + for (auto ToE = sys::path::end(PathTo); ToI != ToE; ++ToI) + sys::path::append(Relative, sys::path::Style::posix, *ToI); + + return std::string(Relative.str()); +} + +static Error writeArchiveToStream(raw_ostream &Out, + ArrayRef<NewArchiveMember> NewMembers, + bool WriteSymtab, object::Archive::Kind Kind, + bool Deterministic, bool Thin) { + assert((!Thin || !isBSDLike(Kind)) && "Only the gnu format has a thin mode"); + + SmallString<0> SymNamesBuf; + raw_svector_ostream SymNames(SymNamesBuf); + SmallString<0> StringTableBuf; + raw_svector_ostream StringTable(StringTableBuf); + + Expected<std::vector<MemberData>> DataOrErr = + computeMemberData(StringTable, SymNames, Kind, Thin, Deterministic, + WriteSymtab, NewMembers); + if (Error E = DataOrErr.takeError()) + return E; + std::vector<MemberData> &Data = *DataOrErr; + + if (!StringTableBuf.empty() && !isAIXBigArchive(Kind)) + Data.insert(Data.begin(), computeStringTable(StringTableBuf)); + + // We would like to detect if we need to switch to a 64-bit symbol table. + uint64_t LastMemberEndOffset = + isAIXBigArchive(Kind) ? sizeof(object::BigArchive::FixLenHdr) : 8; + uint64_t LastMemberHeaderOffset = LastMemberEndOffset; + uint64_t NumSyms = 0; + for (const auto &M : Data) { + // Record the start of the member's offset + LastMemberHeaderOffset = LastMemberEndOffset; + // Account for the size of each part associated with the member. + LastMemberEndOffset += M.Header.size() + M.Data.size() + M.Padding.size(); + NumSyms += M.Symbols.size(); + } + + // The symbol table is put at the end of the big archive file. The symbol + // table is at the start of the archive file for other archive formats. + if (WriteSymtab && !isAIXBigArchive(Kind)) { + // We assume 32-bit offsets to see if 32-bit symbols are possible or not. + uint64_t SymtabSize = computeSymbolTableSize(Kind, NumSyms, 4, SymNamesBuf); + auto computeSymbolTableHeaderSize = + [=] { + SmallString<0> TmpBuf; + raw_svector_ostream Tmp(TmpBuf); + writeSymbolTableHeader(Tmp, Kind, Deterministic, SymtabSize); + return TmpBuf.size(); + }; + LastMemberHeaderOffset += computeSymbolTableHeaderSize() + SymtabSize; + + // The SYM64 format is used when an archive's member offsets are larger than + // 32-bits can hold. The need for this shift in format is detected by + // writeArchive. To test this we need to generate a file with a member that + // has an offset larger than 32-bits but this demands a very slow test. To + // speed the test up we use this environment variable to pretend like the + // cutoff happens before 32-bits and instead happens at some much smaller + // value. + uint64_t Sym64Threshold = 1ULL << 32; + const char *Sym64Env = std::getenv("SYM64_THRESHOLD"); + if (Sym64Env) + StringRef(Sym64Env).getAsInteger(10, Sym64Threshold); + + // If LastMemberHeaderOffset isn't going to fit in a 32-bit varible we need + // to switch to 64-bit. Note that the file can be larger than 4GB as long as + // the last member starts before the 4GB offset. + if (LastMemberHeaderOffset >= Sym64Threshold) { + if (Kind == object::Archive::K_DARWIN) + Kind = object::Archive::K_DARWIN64; + else + Kind = object::Archive::K_GNU64; + } + } + + if (Thin) + Out << "!<thin>\n"; + else if (isAIXBigArchive(Kind)) + Out << "<bigaf>\n"; + else + Out << "!<arch>\n"; + + if (!isAIXBigArchive(Kind)) { + if (WriteSymtab) + writeSymbolTable(Out, Kind, Deterministic, Data, SymNamesBuf); + for (const MemberData &M : Data) + Out << M.Header << M.Data << M.Padding; + } else { + // For the big archive (AIX) format, compute a table of member names and + // offsets, used in the member table. + uint64_t MemberTableNameStrTblSize = 0; + std::vector<size_t> MemberOffsets; + std::vector<StringRef> MemberNames; + // Loop across object to find offset and names. + uint64_t MemberEndOffset = sizeof(object::BigArchive::FixLenHdr); + for (size_t I = 0, Size = NewMembers.size(); I != Size; ++I) { + const NewArchiveMember &Member = NewMembers[I]; + MemberTableNameStrTblSize += Member.MemberName.size() + 1; + MemberOffsets.push_back(MemberEndOffset); + MemberNames.push_back(Member.MemberName); + // File member name ended with "`\n". The length is included in + // BigArMemHdrType. + MemberEndOffset += sizeof(object::BigArMemHdrType) + + alignTo(Data[I].Data.size(), 2) + + alignTo(Member.MemberName.size(), 2); + } + + // AIX member table size. + unsigned MemberTableSize = 20 + // Number of members field + 20 * MemberOffsets.size() + + MemberTableNameStrTblSize; + + unsigned GlobalSymbolOffset = + (WriteSymtab && NumSyms > 0) + ? LastMemberEndOffset + + alignTo(sizeof(object::BigArMemHdrType) + MemberTableSize, 2) + : 0; + + // Fixed Sized Header. + printWithSpacePadding(Out, NewMembers.size() ? LastMemberEndOffset : 0, + 20); // Offset to member table + // If there are no file members in the archive, there will be no global + // symbol table. + printWithSpacePadding(Out, NewMembers.size() ? GlobalSymbolOffset : 0, 20); + printWithSpacePadding( + Out, 0, + 20); // Offset to 64 bits global symbol table - Not supported yet + printWithSpacePadding( + Out, NewMembers.size() ? sizeof(object::BigArchive::FixLenHdr) : 0, + 20); // Offset to first archive member + printWithSpacePadding(Out, NewMembers.size() ? LastMemberHeaderOffset : 0, + 20); // Offset to last archive member + printWithSpacePadding( + Out, 0, + 20); // Offset to first member of free list - Not supported yet + + for (const MemberData &M : Data) { + Out << M.Header << M.Data; + if (M.Data.size() % 2) + Out << '\0'; + } + + if (NewMembers.size()) { + // Member table. + printBigArchiveMemberHeader(Out, "", sys::toTimePoint(0), 0, 0, 0, + MemberTableSize, LastMemberHeaderOffset, + GlobalSymbolOffset); + printWithSpacePadding(Out, MemberOffsets.size(), 20); // Number of members + for (uint64_t MemberOffset : MemberOffsets) + printWithSpacePadding(Out, MemberOffset, + 20); // Offset to member file header. + for (StringRef MemberName : MemberNames) + Out << MemberName << '\0'; // Member file name, null byte padding. + + if (MemberTableNameStrTblSize % 2) + Out << '\0'; // Name table must be tail padded to an even number of + // bytes. + + if (WriteSymtab && NumSyms > 0) + writeSymbolTable(Out, Kind, Deterministic, Data, SymNamesBuf, + LastMemberEndOffset); + } + } + Out.flush(); + return Error::success(); +} + +Error writeArchive(StringRef ArcName, ArrayRef<NewArchiveMember> NewMembers, + bool WriteSymtab, object::Archive::Kind Kind, + bool Deterministic, bool Thin, + std::unique_ptr<MemoryBuffer> OldArchiveBuf) { + Expected<sys::fs::TempFile> Temp = + sys::fs::TempFile::create(ArcName + ".temp-archive-%%%%%%%.a"); + if (!Temp) + return Temp.takeError(); + raw_fd_ostream Out(Temp->FD, false); + + if (Error E = writeArchiveToStream(Out, NewMembers, WriteSymtab, Kind, + Deterministic, Thin)) { + if (Error DiscardError = Temp->discard()) + return joinErrors(std::move(E), std::move(DiscardError)); + return E; + } + + // 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(); + + return Temp->keep(ArcName); +} + +Expected<std::unique_ptr<MemoryBuffer>> +writeArchiveToBuffer(ArrayRef<NewArchiveMember> NewMembers, bool WriteSymtab, + object::Archive::Kind Kind, bool Deterministic, + bool Thin) { + SmallVector<char, 0> ArchiveBufferVector; + raw_svector_ostream ArchiveStream(ArchiveBufferVector); + + if (Error E = writeArchiveToStream(ArchiveStream, NewMembers, WriteSymtab, + Kind, Deterministic, Thin)) + return std::move(E); + + return std::make_unique<SmallVectorMemoryBuffer>( + std::move(ArchiveBufferVector), /*RequiresNullTerminator=*/false); +} + +} // namespace llvm |