//===------ utils/archive2yaml.cpp - obj2yaml conversion tool ---*- 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
//
//===----------------------------------------------------------------------===//

#include "obj2yaml.h"
#include "llvm/BinaryFormat/Magic.h"
#include "llvm/ObjectYAML/ArchiveYAML.h"

using namespace llvm;

namespace {

class ArchiveDumper {
public:
  Expected<ArchYAML::Archive *> dump(MemoryBufferRef Source) {
    StringRef Buffer = Source.getBuffer();
    assert(file_magic::archive == identify_magic(Buffer));

    std::unique_ptr<ArchYAML::Archive> Obj =
        std::make_unique<ArchYAML::Archive>();

    StringRef Magic = "!<arch>\n";
    if (!Buffer.startswith(Magic))
      return createStringError(std::errc::not_supported,
                               "only regular archives are supported");
    Obj->Magic = Magic;
    Buffer = Buffer.drop_front(Magic.size());

    Obj->Members.emplace();
    while (!Buffer.empty()) {
      uint64_t Offset = Buffer.data() - Source.getBuffer().data();
      if (Buffer.size() < sizeof(ArchiveHeader))
        return createStringError(
            std::errc::illegal_byte_sequence,
            "unable to read the header of a child at offset 0x%" PRIx64,
            Offset);

      const ArchiveHeader &Hdr =
          *reinterpret_cast<const ArchiveHeader *>(Buffer.data());
      Buffer = Buffer.drop_front(sizeof(ArchiveHeader));

      auto ToString = [](ArrayRef<char> V) {
        // We don't want to dump excessive spaces.
        return StringRef(V.data(), V.size()).rtrim(' ');
      };

      ArchYAML::Archive::Child C;
      C.Fields["Name"].Value = ToString(Hdr.Name);
      C.Fields["LastModified"].Value = ToString(Hdr.LastModified);
      C.Fields["UID"].Value = ToString(Hdr.UID);
      C.Fields["GID"].Value = ToString(Hdr.GID);
      C.Fields["AccessMode"].Value = ToString(Hdr.AccessMode);
      StringRef SizeStr = ToString(Hdr.Size);
      C.Fields["Size"].Value = SizeStr;
      C.Fields["Terminator"].Value = ToString(Hdr.Terminator);

      uint64_t Size;
      if (SizeStr.getAsInteger(10, Size))
        return createStringError(
            std::errc::illegal_byte_sequence,
            "unable to read the size of a child at offset 0x%" PRIx64
            " as integer: \"%s\"",
            Offset, SizeStr.str().c_str());
      if (Buffer.size() < Size)
        return createStringError(
            std::errc::illegal_byte_sequence,
            "unable to read the data of a child at offset 0x%" PRIx64
            " of size %" PRId64 ": the remaining archive size is %zu",
            Offset, Size, Buffer.size());
      if (!Buffer.empty())
        C.Content = arrayRefFromStringRef(Buffer.take_front(Size));

      const bool HasPaddingByte = (Size & 1) && Buffer.size() > Size;
      if (HasPaddingByte)
        C.PaddingByte = Buffer[Size];

      Obj->Members->push_back(C);
      // If the size is odd, consume a padding byte.
      Buffer = Buffer.drop_front(HasPaddingByte ? Size + 1 : Size);
    }

    return Obj.release();
  }

private:
  struct ArchiveHeader {
    char Name[16];
    char LastModified[12];
    char UID[6];
    char GID[6];
    char AccessMode[8];
    char Size[10];
    char Terminator[2];
  };
};

} // namespace

Error archive2yaml(raw_ostream &Out, MemoryBufferRef Source) {
  ArchiveDumper Dumper;
  Expected<ArchYAML::Archive *> YAMLOrErr = Dumper.dump(Source);
  if (!YAMLOrErr)
    return YAMLOrErr.takeError();

  std::unique_ptr<ArchYAML::Archive> YAML(YAMLOrErr.get());
  yaml::Output Yout(Out);
  Yout << *YAML;

  return Error::success();
}
