cl-olefs

OLE File System tools for Common Lisp
git clone https://logand.com/git/cl-olefs.git/
Log | Files | Refs

commit 66c536a30845f98e483dbcc54999f41aa326ee70
parent 158965da77d54119c640ce76720b6be8e9b5bacb
Author: Tomas Hlavaty <tom@logand.com>
Date:   Tue, 10 May 2011 22:46:56 +0200

can traverse directories and save big fat chains

Diffstat:
Mole.lisp | 246++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
1 file changed, 168 insertions(+), 78 deletions(-)

diff --git a/ole.lisp b/ole.lisp @@ -3,11 +3,12 @@ (in-package :ole) +(defconstant +unused-sector+ 0) (defconstant +maxregsect+ #xfffffffa) (defconstant +difsect+ #xfffffffc) (defconstant +fatsect+ #xfffffffd) (defconstant +endofchain+ #xfffffffe) -(defconstant +freesect #xffffffff) +(defconstant +freesect+ #xffffffff) (defconstant +maxregsig+ #xfffffffa) (defconstant +nostream+ #xffffffff) @@ -20,6 +21,7 @@ (deftype filetime () '(unsigned-byte 64)) (deftype guid () '(vector ubyte 16)) +#+nil (defconstant clsid-null (make-array 16 :element-type '(unsigned-byte 8) :initial-element 0)) @@ -91,7 +93,7 @@ (number-of-difat-sectors dword)) (define-structure ole-entry () - (name (wchar 32)) + (name (wchar 32)) (name-length ushort) (object-type ubyte) (color-flag ubyte) @@ -105,21 +107,20 @@ (starting-sector-location dword) (stream-size ulonglong)) +(defun ole-entry-name-to-string (name length) + (coerce (mapcar #'code-char (coerce (subseq name 0 (1- (/ length 2))) 'list)) + 'string)) + (defun print-ole-entry (ole-entry stream) (print-unreadable-object (ole-entry stream :type t :identity t) - (format stream "~s ~s ~a ~sB @~s" - (coerce - (mapcar #'code-char - (coerce (subseq (ole-entry.name ole-entry) - 0 - (1- (/ (ole-entry.name-length ole-entry) 2))) - 'list)) - 'string) + (format stream "~s ~a ~a ~sB @~s" + (ole-entry-name-to-string (ole-entry.name ole-entry) + (ole-entry.name-length ole-entry)) (ecase (ole-entry.object-type ole-entry) - (0 :unknown-or-unallocated) ;; unknown - (1 :storage-object) ;; directory - (2 :stream-object) ;; file - (5 :root-storage-object)) ;; root + (0 "unknown") + (1 "storage") + (2 "stream") + (5 "root")) (ecase (ole-entry.color-flag ole-entry) (0 "red") (1 "black")) @@ -134,14 +135,9 @@ (assert (file-position stream position)) location)) -(defun seek-sector-entry (ole-file location id) - (assert (<= 0 id 3)) - (assert (file-position (ole-file.stream ole-file) - (+ (location-position location) (* (/ 512 4) id))))) - (defun check-ole-header (x) (assert (equalp #(#xd0 #xcf #x11 #xe0 #xa1 #xb1 #x1a #xe1) (ole-header.signature x))) - (assert (equalp clsid-null (ole-header.clsid x))) + ;;(assert (equalp clsid-null (ole-header.clsid x))) (assert (eql #xfffe (ole-header.byte-order x))) (assert (equalp #(0 0 0 0 0 0) (ole-header.reserved x))) ;; TODO @@ -153,77 +149,171 @@ (assert (eql 0 (ole-header.transaction-signature-number x))) (assert (eql 4096 (ole-header.mini-stream-cutoff-size x))) ;;(assert (eql #xfffffffe (first-mini-fat-sector-location x))) - #+nil(assert (eql #xfffffffe (first-difat-sector-location x))) - #+nil(assert (eql 0 (number-of-difat-sectors x)))) + (unless (plusp (ole-header.number-of-difat-sectors x)) + (assert (eql #xfffffffe (ole-header.first-difat-sector-location x))))) + +(defstruct (ole-file (:conc-name ole-file.)) + filename stream header difat fat directory-chain directories mfat-chain mfat) + +(defun sector-chain (fat location) + (labels ((rec (x) + (case x + ;;(#.+unused-sector+) + ;;(+maxregsect+) + (#.+difsect+) + (#.+fatsect+) + (#.+endofchain+) + (#.+freesect+) + (t + (assert (and #+nil(< +unused-sector+ x) (<= 0 x +maxregsect+))) + (cons x (rec (aref fat x))))))) + (rec location))) + +(defun read-values (array type stream &optional (start 0) end) + (loop + for i from start below (or end (length array)) + do (setf (aref array i) (read-value type stream)))) + +(defun read-difat (header stream) + (let ((x (make-array (+ 109 + (* #.(/ (- 512 4) 4) + (ole-header.number-of-difat-sectors header))) + :element-type 'dword))) + (read-values x 'dword stream 0 109) + (loop + with m = #.(1- (/ 512 4)) + for n = (ole-header.first-difat-sector-location header) + then (read-value 'dword stream) + for i = 109 then (+ m i) + until (= +endofchain+ n) + do (progn + (seek-sector n stream) + (read-values x 'dword stream i (+ m i)))) + x)) + +(defun read-fat (difat stream) + (let* ((m #.(/ 512 4)) + (n (length difat)) + (x (make-array (* m n) :element-type 'dword))) + (dotimes (i n x) + (let ((s (aref difat i))) + (unless (= +freesect+ s) + (seek-sector s stream) + (read-values x 'dword stream (* m i) (* m (1+ i)))))))) + +(defun read-directories (chain stream) + (let* ((m #.(/ 512 128)) + (x (make-array (* m (length chain)) + :element-type '(or null ole-entry) + :initial-element nil)) + (i -1)) + (dolist (s chain x) + (seek-sector s stream) + (dotimes (j m) + (setf (aref x (incf i)) (read-ole-entry stream)))))) -(defstruct (ole-file (:conc-name ole-file.)) filename stream header bat-sectors bat) +(defun read-mfat (chain stream) + (let* ((m #.(/ 512 4)) + (x (make-array (* m (length chain)) :element-type 'dword)) + (i -1)) + (dolist (s chain x) + (seek-sector s stream) + ;;TODO block read (read-values x 'dword stream (* m i) (* m (1+ i))) + (dotimes (j m) + (setf (aref x (incf i)) (read-value 'dword stream)))))) + +(defun traverse-directories (ole-file callback) + (let ((d (ole-file.directories ole-file))) + (labels ((rec (n level) + (let ((e (aref d n))) + (unless (zerop (ole-entry.object-type e)) + (funcall callback e n level) + (let ((id (ole-entry.left-sibling-id e))) + (when (<= id +maxregsig+) + (rec id level))) + (let ((id (ole-entry.child-id e))) + (when (<= id +maxregsig+) + (rec id (1+ level)))) + (let ((id (ole-entry.right-sibling-id e))) + (when (<= id +maxregsig+) + (rec id level))))))) + (rec 0 0)))) (defun call-with-ole-file (filename fn) (with-open-file (stream filename :element-type '(unsigned-byte 8)) (let* ((header (read-ole-header stream)) - (bat-sectors (loop - for i from 0 below (ole-header.number-of-fat-sectors header) - collect (read-value 'dword stream))) - (bat (let ((x (make-array (* 512 (ole-header.number-of-fat-sectors header)) - :element-type 'dword))) - x)) + (difat (read-difat header stream)) + (fat (read-fat difat stream)) + (directory-chain (sector-chain + fat + (ole-header.first-directory-sector-location header))) + (directories (read-directories directory-chain stream)) + (mfat-chain (sector-chain + fat + (ole-header.first-mini-fat-sector-location header))) + (mfat (read-mfat mfat-chain stream)) (ole-file (make-ole-file :filename filename :stream stream :header header - :bat-sectors bat-sectors - :bat bat))) + :difat difat + :fat fat + :directory-chain directory-chain + :directories directories + :mfat-chain mfat-chain + :mfat mfat))) + (describe ole-file) (check-ole-header (ole-file.header ole-file)) - (loop ;; read bat - for location in (ole-file.bat-sectors ole-file) - for n = -1 - do (progn - (seek-sector location (ole-file.stream ole-file)) - (dotimes (i (/ 512 4)) - (setf (aref (ole-file.bat ole-file) (incf n)) - (read-value 'dword (ole-file.stream ole-file)))))) + (describe header) + (terpri) + (traverse-directories ole-file + (lambda (entry id level) + (declare (ignore id)) + (dotimes (i level) + (write-string " ")) + (print-ole-entry entry *standard-output*) + (terpri))) + ;; TODO small block chain for root entry (funcall fn ole-file)))) (defmacro with-ole-file ((ole-file filename) &body body) `(call-with-ole-file ,filename (lambda (,ole-file) ,@body))) -(defun ls-ole (filename) +(defun save-chain (ole-stream chain filename length) + (with-open-file (s filename + :direction :output + :if-does-not-exist :create + :if-exists :supersede + :element-type '(unsigned-byte 8)) + (let ((buf (make-array 512 :element-type '(unsigned-byte 8)))) + (dolist (x chain) + (seek-sector x ole-stream) + (let ((n (read-sequence buf ole-stream))) + (decf length n) + (write-sequence buf s :end (if (plusp length) n (+ length 512)))))))) + +(defun save-entry-stream (ole-file entry filename) + (if (<= (ole-entry.stream-size entry) + (ole-header.mini-stream-cutoff-size (ole-file.header ole-file))) + :mfat ;; TODO mini stream + (save-chain (ole-file.stream ole-file) + (sector-chain (ole-file.fat ole-file) + (ole-entry.starting-sector-location entry)) + filename + (ole-entry.stream-size entry)))) + +(defun extract-ole-file (filename) (with-ole-file (ole-file filename) - (let ((stream (ole-file.stream ole-file))) - (labels ((indent (n) - (dotimes (i n) - (write-string " "))) - (rec (location id level) - ;;(seek-sector location stream) - (seek-sector-entry ole-file location id) - (let ((x (read-ole-entry stream))) - (indent level) - (print id) - (print-ole-entry x *standard-output*) - #+nil - (let ((y x)) - (loop - for id = (ole-entry.left-sibling-id y) then (ole-entry.left-sibling-id y) - while (<= id +maxregsig+) - do (rec location id level) - #+nil(progn - (seek-sector-entry location 128 id s) - (setq y (read-structure 'directory-entry s)) - (format t "L~s:~s ~s~%" level id y)))) - (let ((id (ole-entry.child-id x))) - (when (<= id +maxregsig+) - ;;(seek-sector-entry ole-file location id) - (rec location id (1+ level)))) - #+nil - (let ((y x)) - (loop - for id = (ole-entry.right-sibling-id y) then (ole-entry.right-sibling-id y) - while (<= id +maxregsig+) - do (rec location id level) - #+nil(progn - (seek-sector-entry location 128 id s) - (setq y (read-structure 'directory-entry s)) - (format t "R~s:~s ~s~%" level id y))))))) - (rec (ole-header.first-directory-sector-location (ole-file.header ole-file)) - 0 - 0))))) + (traverse-directories + ole-file + (lambda (entry id level) + (declare (ignore id level)) + (case (ole-entry.object-type entry) + ;;(1 "storage") + (2 ;; stream + (save-entry-stream ole-file + entry + (format nil "/tmp/~a" + (ole-entry-name-to-string + (ole-entry.name entry) + (ole-entry.name-length entry))))))))))