text: /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
     file Copyright.txt or https://cmake.org/licensing for details.  */
  #include "cmMachO.h"
  
  #include "cmsys/FStream.hxx"
  #include <algorithm>
  #include <stddef.h>
  #include <string>
  #include <vector>
  
  // Include the Mach-O format information system header.
  #include <mach-o/fat.h>
  #include <mach-o/loader.h>
  
  /**
  
    https://developer.apple.com/library/mac/documentation/
            DeveloperTools/Conceptual/MachORuntime/index.html
  
    A Mach-O file has 3 major regions: header, load commands and segments.
    Data Structures are provided from <mach-o/loader.h> which
    correspond to the file structure.
  
    The header can be either a struct mach_header or struct mach_header_64.
    One can peek at the first 4 bytes to identify the type of header.
  
    Following is the load command region which starts with
    struct load_command, and is followed by n number of load commands.
  
    In the case of a universal binary (an archive of multiple Mach-O files),
    the file begins with a struct fat_header and is followed by multiple
    struct fat_arch instances.  The struct fat_arch indicates the offset
    for each Mach-O file.
  
    */
  
  namespace {
  
  // peek in the file
  template <typename T>
  bool peek(cmsys::ifstream& fin, T& v)
  {
    std::streampos p = fin.tellg();
    if (!fin.read(reinterpret_cast<char*>(&v), sizeof(T))) {
      return false;
    }
    fin.seekg(p);
    return fin.good();
  }
  
  // read from the file and fill a data structure
  template <typename T>
  bool read(cmsys::ifstream& fin, T& v)
  {
    return !!fin.read(reinterpret_cast<char*>(&v), sizeof(T));
  }
  
  // read from the file and fill multiple data structures where
  // the vector has been resized
  template <typename T>
  bool read(cmsys::ifstream& fin, std::vector<T>& v)
  {
    // nothing to read
    if (v.empty()) {
      return true;
    }
    return !!fin.read(reinterpret_cast<char*>(&v[0]), sizeof(T) * v.size());
  }
  }
  
  // Contains header and load commands for a single Mach-O file
  class cmMachOHeaderAndLoadCommands
  {
  public:
    // A load_command and its associated data
    struct RawLoadCommand
    {
      uint32_t type(const cmMachOHeaderAndLoadCommands* m) const
      {
        if (this->LoadCommand.size() < sizeof(load_command)) {
          return 0;
        }
        const load_command* cmd =
          reinterpret_cast<const load_command*>(&this->LoadCommand[0]);
        return m->swap(cmd->cmd);
      }
      std::vector<char> LoadCommand;
    };
  
    cmMachOHeaderAndLoadCommands(bool _swap)
      : Swap(_swap)
    {
    }
    virtual ~cmMachOHeaderAndLoadCommands() = default;
  
    virtual bool read_mach_o(cmsys::ifstream& fin) = 0;
  
    const std::vector<RawLoadCommand>& load_commands() const
    {
      return this->LoadCommands;
    }
  
    uint32_t swap(uint32_t v) const
    {
      if (this->Swap) {
        char* c = reinterpret_cast<char*>(&v);
        std::swap(c[0], c[3]);
        std::swap(c[1], c[2]);
      }
      return v;
    }
  
  protected:
    bool read_load_commands(uint32_t ncmds, uint32_t sizeofcmds,
                            cmsys::ifstream& fin);
  
    bool Swap;
    std::vector<RawLoadCommand> LoadCommands;
  };
  
  // Implementation for reading Mach-O header and load commands.
  // This is 32 or 64 bit arch specific.
  template <class T>
  class cmMachOHeaderAndLoadCommandsImpl : public cmMachOHeaderAndLoadCommands
  {
  public:
    cmMachOHeaderAndLoadCommandsImpl(bool _swap)
      : cmMachOHeaderAndLoadCommands(_swap)
    {
    }
    bool read_mach_o(cmsys::ifstream& fin) override
    {
      if (!read(fin, this->Header)) {
        return false;
      }
      this->Header.cputype = swap(this->Header.cputype);
      this->Header.cpusubtype = swap(this->Header.cpusubtype);
      this->Header.filetype = swap(this->Header.filetype);
      this->Header.ncmds = swap(this->Header.ncmds);
      this->Header.sizeofcmds = swap(this->Header.sizeofcmds);
      this->Header.flags = swap(this->Header.flags);
  
      return read_load_commands(this->Header.ncmds, this->Header.sizeofcmds,
                                fin);
    }
  
  protected:
    T Header;
  };
  
  bool cmMachOHeaderAndLoadCommands::read_load_commands(uint32_t ncmds,
                                                        uint32_t sizeofcmds,
                                                        cmsys::ifstream& fin)
  {
    uint32_t size_read = 0;
    this->LoadCommands.resize(ncmds);
    for (uint32_t i = 0; i < ncmds; i++) {
      load_command lc;
      if (!peek(fin, lc)) {
        return false;
      }
      lc.cmd = swap(lc.cmd);
      lc.cmdsize = swap(lc.cmdsize);
      size_read += lc.cmdsize;
  
      RawLoadCommand& c = this->LoadCommands[i];
      c.LoadCommand.resize(lc.cmdsize);
      if (!read(fin, c.LoadCommand)) {
        return false;
      }
    }
  
    if (size_read != sizeofcmds) {
      this->LoadCommands.clear();
      return false;
    }
  
    return true;
  }
  
  class cmMachOInternal
  {
  public:
    cmMachOInternal(const char* fname);
    ~cmMachOInternal();
  
    // read a Mach-O file
    bool read_mach_o(uint32_t file_offset);
  
    // the file we are reading
    cmsys::ifstream Fin;
  
    // The archs in the universal binary
    // If the binary is not a universal binary, this will be empty.
    std::vector<fat_arch> FatArchs;
  
    // the error message while parsing
    std::string ErrorMessage;
  
    // the list of Mach-O's
    std::vector<cmMachOHeaderAndLoadCommands*> MachOList;
  };
  
  cmMachOInternal::cmMachOInternal(const char* fname)
    : Fin(fname)
  {
    // Quit now if the file could not be opened.
    if (!this->Fin || !this->Fin.get()) {
      this->ErrorMessage = "Error opening input file.";
      return;
    }
  
    if (!this->Fin.seekg(0)) {
      this->ErrorMessage = "Error seeking to beginning of file.";
      return;
    }
  
    // Read the binary identification block.
    uint32_t magic = 0;
    if (!peek(this->Fin, magic)) {
      this->ErrorMessage = "Error reading Mach-O identification.";
      return;
    }
  
    // Verify the binary identification.
    if (!(magic == MH_CIGAM || magic == MH_MAGIC || magic == MH_CIGAM_64 ||
          magic == MH_MAGIC_64 || magic == FAT_CIGAM || magic == FAT_MAGIC)) {
      this->ErrorMessage = "File does not have a valid Mach-O identification.";
      return;
    }
  
    if (magic == FAT_MAGIC || magic == FAT_CIGAM) {
      // this is a universal binary
      fat_header header;
      if (!read(this->Fin, header)) {
        this->ErrorMessage = "Error reading fat header.";
        return;
      }
  
      // read fat_archs
      this->FatArchs.resize(OSSwapBigToHostInt32(header.nfat_arch));
      if (!read(this->Fin, this->FatArchs)) {
        this->ErrorMessage = "Error reading fat header archs.";
        return;
      }
  
      // parse each Mach-O file
      for (const auto& arch : this->FatArchs) {
        if (!this->read_mach_o(OSSwapBigToHostInt32(arch.offset))) {
          return;
        }
      }
    } else {
      // parse Mach-O file at the beginning of the file
      this->read_mach_o(0);
    }
  }
  
  cmMachOInternal::~cmMachOInternal()
  {
    for (auto& i : this->MachOList) {
      delete i;
    }
  }
  
  bool cmMachOInternal::read_mach_o(uint32_t file_offset)
  {
    if (!this->Fin.seekg(file_offset)) {
      this->ErrorMessage = "Failed to locate Mach-O content.";
      return false;
    }
  
    uint32_t magic;
    if (!peek(this->Fin, magic)) {
      this->ErrorMessage = "Error reading Mach-O identification.";
      return false;
    }
  
    cmMachOHeaderAndLoadCommands* f = nullptr;
    if (magic == MH_CIGAM || magic == MH_MAGIC) {
      bool swap = false;
      if (magic == MH_CIGAM) {
        swap = true;
      }
      f = new cmMachOHeaderAndLoadCommandsImpl<mach_header>(swap);
    } else if (magic == MH_CIGAM_64 || magic == MH_MAGIC_64) {
      bool swap = false;
      if (magic == MH_CIGAM_64) {
        swap = true;
      }
      f = new cmMachOHeaderAndLoadCommandsImpl<mach_header_64>(swap);
    }
  
    if (f && f->read_mach_o(this->Fin)) {
      this->MachOList.push_back(f);
    } else {
      delete f;
      this->ErrorMessage = "Failed to read Mach-O header.";
      return false;
    }
  
    return true;
  }
  
  //============================================================================
  // External class implementation.
  
  cmMachO::cmMachO(const char* fname)
    : Internal(nullptr)
  {
    this->Internal = new cmMachOInternal(fname);
  }
  
  cmMachO::~cmMachO()
  {
    delete this->Internal;
  }
  
  std::string const& cmMachO::GetErrorMessage() const
  {
    return this->Internal->ErrorMessage;
  }
  
  bool cmMachO::Valid() const
  {
    return !this->Internal->MachOList.empty();
  }
  
  bool cmMachO::GetInstallName(std::string& install_name)
  {
    if (this->Internal->MachOList.empty()) {
      return false;
    }
  
    // grab the first Mach-O and get the install name from that one
    cmMachOHeaderAndLoadCommands* macho = this->Internal->MachOList[0];
    for (size_t i = 0; i < macho->load_commands().size(); i++) {
      const cmMachOHeaderAndLoadCommands::RawLoadCommand& cmd =
        macho->load_commands()[i];
      uint32_t lc_cmd = cmd.type(macho);
      if (lc_cmd == LC_ID_DYLIB || lc_cmd == LC_LOAD_WEAK_DYLIB ||
          lc_cmd == LC_LOAD_DYLIB) {
        if (sizeof(dylib_command) < cmd.LoadCommand.size()) {
          uint32_t namelen = cmd.LoadCommand.size() - sizeof(dylib_command);
          install_name.assign(&cmd.LoadCommand[sizeof(dylib_command)], namelen);
          return true;
        }
      }
    }
  
    return false;
  }
  
  void cmMachO::PrintInfo(std::ostream& /*os*/) const
  {
  }
  