;;;
;;; Copyright 2009 Clozure Associates
;;;
;;; Licensed under the Apache License, Version 2.0 (the "License");
;;; you may not use this file except in compliance with the License.
;;; You may obtain a copy of the License at
;;;
;;;     http://www.apache.org/licenses/LICENSE-2.0
;;;
;;; Unless required by applicable law or agreed to in writing, software
;;; distributed under the License is distributed on an "AS IS" BASIS,
;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;;; See the License for the specific language governing permissions and
;;; limitations under the License.

(defstruct mach-o-file
  header
  load-commands
  segments
  symbols
  strings)

(defmethod print-object ((m mach-o-file) stream)
  (print-unreadable-object (m stream :type t :identity t)))


(defstruct mach-o-string-table
  (hash (make-hash-table :test #'equal))
  (string (make-array 100 :element-type '(unsigned-byte 8) :fill-pointer 1 :adjustable t)))

(defstruct mach-o-symbol
  string-index
  type
  sect
  desc
  value)

(defun init-mach-o-string-table (fd symtab-command)
  (fd-lseek fd (pref symtab-command #>symtab_command.stroff) #$SEEK_SET)
  (let* ((strsize (pref symtab-command #>symtab_command.strsize))
         (nbytes (+ strsize strsize))
         (bytes (make-array nbytes :element-type '(unsigned-byte 8)))
         (out 0))
    (declare (fixnum nbytes strsize out))
    (%stack-block ((buf 32768))
      (do* ((n strsize))
           ((= n 0))
        (let* ((bufsize (fd-read fd buf (min n 32768))))
          (%copy-ptr-to-ivector buf 0 bytes out bufsize)
          (incf out bufsize)
          (decf n bufsize))))
    (make-mach-o-string-table
     :string (make-array nbytes
                         :element-type '(unsigned-byte 8)
                         :displaced-to bytes
                         :fill-pointer strsize
                         :adjustable t))))

(defun init-mach-o-symbols64 (fd symtab-command)
  (fd-lseek fd (pref symtab-command #>symtab_command.symoff) #$SEEK_SET)
  (rlet ((nlist #>nlist_64))
    (let* ((nsyms (pref symtab-command #>symtab_command.nsyms))
           (nentries (* nsyms 2))
           (vec (make-array nentries)))
      (declare (fixnum nsyms nentries))
      (flet ((read-nlist ()
               (fd-read fd nlist (record-length #>nlist_64))
               (make-mach-o-symbol :string-index (pref nlist #>nlist_64.n_un.n_strx)
                                   :type (pref nlist #>nlist_64.n_type)
                                   :sect (pref nlist #>nlist_64.n_sect)
                                   :desc (pref nlist #>nlist_64.n_desc)
                                   :value (pref nlist #>nlist_64.n_value))))
        (dotimes (i nsyms (make-array nentries
                                      :displaced-to vec
                                      :fill-pointer nsyms
                                      :adjustable t))
          (setf (svref vec i) (read-nlist)))))))
    

(defun read-header-and-load-commands64 (fd)
  (fd-lseek fd 0 #$SEEK_SET)
  (let* ((mh (make-record :mach_header_64))
         (mach-o (make-mach-o-file :header mh)))
    (when (= (fd-read fd mh (record-length :mach_header_64))
             (record-length :mach_header_64))
      (collect ((commands))
        (flet ((read-command ()
                 (rlet ((cmd :load_command))
                   (fd-read fd cmd (record-length :load_command))
                   (let* ((n (pref cmd :load_command.cmdsize))
                          (p (#_malloc n))
                          (q (%inc-ptr p (record-length :load_command))))
                     (#_memcpy p cmd (record-length :load_command))
                     (fd-read fd q (- n (record-length :load_command)))
                     (let* ((lcmd (pref cmd :load_command.cmd))
                            (ftype 
                             (cond ((= lcmd #$LC_SEGMENT_64)
                                    (load-record #>segment_command_64))
                                   ((= lcmd #$LC_SYMTAB)
                                    (load-record #>symtab_command))
                                   ((= lcmd #$LC_DYSYMTAB)
                                    (load-record #>dysymtab_command))
                                   ((= lcmd #$LC_LOAD_DYLINKER)
                                    (load-record #>dylinker_command))
                                   ((= lcmd #$LC_UUID)
                                    (load-record #>uuid_command))
                                   ((= lcmd #$LC_LOAD_DYLIB)
                                    (load-record #>dylib_command))
                                   ((= lcmd #$LC_UNIXTHREAD)
                                    (load-record #>thread_command)))))

                       (if ftype
                         (%set-macptr-type p (foreign-record-type-ordinal ftype))
                         (format t "~&~x" lcmd)))
                     p))))
          (dotimes (i (pref mh :mach_header_64.ncmds))
            (commands (read-command)))
          (setf (mach-o-file-load-commands mach-o) (commands))
          (dolist (cmd (mach-o-file-load-commands mach-o))
            (when (= #$LC_SYMTAB (pref cmd #>load_command.cmd))
              (setf (mach-o-file-strings mach-o)
                    (init-mach-o-string-table fd cmd)
                    (mach-o-file-symbols mach-o)
                    (init-mach-o-symbols64 fd cmd))))
          mach-o)))))

(defun mach-o-string-index (mo string)
  (let* ((bytes (make-array (the fixnum (+ (length string) 2)) :element-type '(unsigned-byte 8))))
    (declare (dynamic-extent bytes))
    (dotimes (i (length string))
      (setf (aref bytes (1+ i)) (char-code (char string i))))
    (let* ((pos (search bytes (mach-o-string-table-string (mach-o-file-strings mo)))))
      (when pos (1+ pos)))))
              
