From 146571dae4dcaa1d2c543140d55cbd14c286aaf3 Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Wed, 30 Apr 2014 16:12:23 +0100 Subject: [PATCH] zone.lisp: Refactor the output stage. Provide a default implementation of `zone-write', which does the tedious business of iterating over the individual records. Make the output formats use this new protocol. --- frontend.lisp | 7 +- zone.lisp | 239 +++++++++++++++++++++++++++++++++++----------------------- 2 files changed, 148 insertions(+), 98 deletions(-) diff --git a/frontend.lisp b/frontend.lisp index cb22d3e..0764c3f 100644 --- a/frontend.lisp +++ b/frontend.lisp @@ -83,8 +83,11 @@ (keyword opt-format (delete-duplicates (loop for method in - (generic-function-methods - #'zone:zone-write) + (append + (generic-function-methods + #'zone:zone-write) + (generic-function-methods + #'zone:zone-write-header)) for specs = (method-specializers method) if (typep (car specs) diff --git a/zone.lisp b/zone.lisp index c586915..32f1aed 100644 --- a/zone.lisp +++ b/zone.lisp @@ -1061,11 +1061,53 @@ (defvar *zone-output-stream* nil "Stream to write zone data on.") -(defmethod zone-write :around (format zone stream) - (declare (ignore format)) +(defgeneric zone-write-raw-rrdata (format zr type data) + (:documentation "Write an otherwise unsupported record in a given FORMAT. + + ZR gives the record object, which carries the name and TTL; the TYPE is + the numeric RRTYPE code; and DATA is an octet vector giving the RRDATA. + This is used by the default `zone-write-record' method to handle record + types which aren't directly supported by the format driver.")) + +(export 'zone-write-header) +(defgeneric zone-write-header (format zone) + (:documentation "Emit the header for a ZONE, in a given FORMAT. + + The header includes any kind of initial comment, the SOA record, and any + other necessary preamble. There is no default implementation. + + This is part of the protocol used by the default method on `zone-write'; + if you override that method.")) + +(export 'zone-write-trailer) +(defgeneric zone-write-trailer (format zone) + (:documentation "Emit the header for a ZONE, in a given FORMAT. + + The footer may be empty, and is so by default. + + This is part of the protocol used by the default method on `zone-write'; + if you override that method.") + (:method (format zone) + (declare (ignore format zone)) + nil)) + +(export 'zone-write-record) +(defgeneric zone-write-record (format type zr) + (:documentation "Emit a record of the given TYPE (a keyword). + + There is no default implementation.")) + +(defmethod zone-write (format zone stream) + "This default method calls `zone-write-header', then `zone-write-record' + for each record in the zone, and finally `zone-write-trailer'. While it's + running, `*writing-zone*' is bound to the zone object, and + `*zone-output-stream*' to the output stream." (let ((*writing-zone* zone) (*zone-output-stream* stream)) - (call-next-method))) + (zone-write-header format zone) + (dolist (zr (zone-records-sorted zone)) + (zone-write-record format (zr-type zr) zr)) + (zone-write-trailer format zone))) (export 'zone-save) (defun zone-save (zones &key (format :bind)) @@ -1113,11 +1155,12 @@ (setf *bind-last-record-name* name) name)))) -(export 'bind-record) -(defgeneric bind-record (type zr)) +(defmethod zone-write :around ((format (eql :bind)) zone stream) + (let ((*bind-last-record-name* nil)) + (call-next-method))) -(defmethod zone-write ((format (eql :bind)) zone stream) - (format stream "~ +(defmethod zone-write-header ((format (eql :bind)) zone) + (format *zone-output-stream* "~ ;;; Zone file `~(~A~)' ;;; (generated ~A) @@ -1126,15 +1169,14 @@ $TTL ~2@*~D~2%" (zone-name zone) (iso-date :now :datep t :timep t) (zone-default-ttl zone)) - (let* ((*bind-last-record-name* nil) - (soa (zone-soa zone)) + (let* ((soa (zone-soa zone)) (admin (let* ((name (soa-admin soa)) (at (position #\@ name)) (copy (format nil "~(~A~)." name))) (when at (setf (char copy at) #\.)) copy))) - (format stream "~ + (format *zone-output-stream* "~ ~A~30TIN SOA~40T~A ( ~55@A~60T ;administrator ~45T~10D~60T ;serial @@ -1149,49 +1191,49 @@ $TTL ~2@*~D~2%" (soa-refresh soa) (soa-retry soa) (soa-expire soa) - (soa-min-ttl soa)) - (dolist (zr (zone-records-sorted zone)) - (bind-record (zr-type zr) zr)))) + (soa-min-ttl soa)))) (export 'bind-format-record) -(defun bind-format-record (name ttl type format args) +(defun bind-format-record (zr format &rest args) (format *zone-output-stream* "~A~20T~@[~8D~]~30TIN ~A~40T~?~%" - (bind-output-hostname name) - (and (/= ttl (zone-default-ttl *writing-zone*)) - ttl) - (string-upcase (symbol-name type)) + (bind-output-hostname (zr-name zr)) + (let ((ttl (zr-ttl zr))) + (and (/= ttl (zone-default-ttl *writing-zone*)) + ttl)) + (string-upcase (symbol-name (zr-type zr))) format args)) -(export 'bind-record-type) -(defgeneric bind-record-type (type) - (:method (type) type)) - -(export 'bind-record-format-args) -(defgeneric bind-record-format-args (type data) - (:method ((type (eql :a)) data) (list "~A" (ipaddr-string data))) - (:method ((type (eql :aaaa)) data) (list "~A" (ipaddr-string data))) - (:method ((type (eql :ptr)) data) (list "~A" (bind-hostname data))) - (:method ((type (eql :cname)) data) (list "~A" (bind-hostname data))) - (:method ((type (eql :ns)) data) (list "~A" (bind-hostname data))) - (:method ((type (eql :mx)) data) - (list "~2D ~A" (cdr data) (bind-hostname (car data)))) - (:method ((type (eql :srv)) data) - (destructuring-bind (prio weight port host) data - (list "~2D ~5D ~5D ~A" prio weight port (bind-hostname host)))) - (:method ((type (eql :sshfp)) data) - (cons "~2D ~2D ~A" data)) - (:method ((type (eql :txt)) data) - (cons "~#[\"\"~;~S~:;(~@{~%~8T~S~} )~]" - (mapcar #'stringify data)))) - -(defmethod bind-record (type zr) - (destructuring-bind (format &rest args) - (bind-record-format-args type (zr-data zr)) - (bind-format-record (zr-name zr) - (zr-ttl zr) - (bind-record-type type) - format args))) +(defmethod zone-write-record ((format (eql :bind)) (type (eql :a)) zr) + (bind-format-record zr "~A" (ipaddr-string (zr-data zr)))) + +(defmethod zone-write-record ((format (eql :bind)) (type (eql :aaaa)) zr) + (bind-format-record zr "~A" (ipaddr-string (zr-data zr)))) + +(defmethod zone-write-record ((format (eql :bind)) (type (eql :ptr)) zr) + (bind-format-record zr "~A" (bind-hostname (zr-data zr)))) + +(defmethod zone-write-record ((format (eql :bind)) (type (eql :cname)) zr) + (bind-format-record zr "~A" (bind-hostname (zr-data zr)))) + +(defmethod zone-write-record ((format (eql :bind)) (type (eql :ns)) zr) + (bind-format-record zr "~A" (bind-hostname (zr-data zr)))) + +(defmethod zone-write-record ((format (eql :bind)) (type (eql :mx)) zr) + (bind-format-record zr "~2D ~A" + (cdr (zr-data zr)) + (bind-hostname (car (zr-data zr))))) + +(defmethod zone-write-record ((format (eql :bind)) (type (eql :srv)) zr) + (destructuring-bind (prio weight port host) (zr-data zr) + (bind-format-record zr "~2D ~5D ~5D ~A" + prio weight port (bind-hostname host)))) + +(defmethod zone-write-record ((format (eql :bind)) (type (eql :sshfp)) zr) + (bind-format-record zr "~{~2D ~2D ~A~}" (zr-data zr))) + +(defmethod zone-write-record ((format (eql :bind)) (type (eql :txt)) zr) + (bind-format-record zr "~{~#[\"\"~;~S~:;(~@{~%~8T~S~} )~]~}" (zr-data zr))) ;;;-------------------------------------------------------------------------- ;;; tinydns-data output format. @@ -1211,53 +1253,60 @@ $TTL ~2@*~D~2%" (write-char (code-char byte) out))))) (zr-ttl zr))) -(defgeneric tinydns-record (type zr) - (:method ((type (eql :a)) zr) - (tinydns-output #\+ (zr-name zr) - (ipaddr-string (zr-data zr)) (zr-ttl zr))) - (:method ((type (eql :aaaa)) zr) - (tinydns-output #\3 (zr-name zr) - (format nil "~(~32,'0X~)" (ipaddr-addr (zr-data zr))) - (zr-ttl zr))) - (:method ((type (eql :ptr)) zr) - (tinydns-output #\^ (zr-name zr) (zr-data zr) (zr-ttl zr))) - (:method ((type (eql :cname)) zr) - (tinydns-output #\C (zr-name zr) (zr-data zr) (zr-ttl zr))) - (:method ((type (eql :ns)) zr) - (tinydns-output #\& (zr-name zr) nil (zr-data zr) (zr-ttl zr))) - (:method ((type (eql :mx)) zr) - (let ((name (car (zr-data zr))) - (prio (cdr (zr-data zr)))) - (tinydns-output #\@ (zr-name zr) nil name prio (zr-ttl zr)))) - (:method ((type (eql :txt)) zr) - (tinydns-raw-record 16 zr +(defmethod zone-write-record ((format (eql :tinydns)) (type (eql :a)) zr) + (tinydns-output #\+ (zr-name zr) + (ipaddr-string (zr-data zr)) (zr-ttl zr))) + +(defmethod zone-write-record ((format (eql :tinydns)) (type (eql :aaaa)) zr) + (tinydns-output #\3 (zr-name zr) + (format nil "~(~32,'0X~)" (ipaddr-addr (zr-data zr))) + (zr-ttl zr))) + +(defmethod zone-write-record ((format (eql :tinydns)) (type (eql :ptr)) zr) + (tinydns-output #\^ (zr-name zr) (zr-data zr) (zr-ttl zr))) + +(defmethod zone-write-record ((format (eql :tinydns)) (type (eql :cname)) zr) + (tinydns-output #\C (zr-name zr) (zr-data zr) (zr-ttl zr))) + +(defmethod zone-write-record ((format (eql :tinydns)) (type (eql :ns)) zr) + (tinydns-output #\& (zr-name zr) nil (zr-data zr) (zr-ttl zr))) + +(defmethod zone-write-record ((format (eql :tinydns)) (type (eql :mx)) zr) + (let ((name (car (zr-data zr))) + (prio (cdr (zr-data zr)))) + (tinydns-output #\@ (zr-name zr) nil name prio (zr-ttl zr)))) + +(defmethod zone-write-record ((format (eql :tinydns)) (type (eql :txt)) zr) + (tinydns-raw-record 16 zr + (build-record + (dolist (s (zr-data zr)) + (rec-u8 (length s)) + (rec-raw-string s))))) + +(defmethod zone-write-record ((format (eql :tinydns)) (type (eql :srv)) zr) + (destructuring-bind (prio weight port host) (zr-data zr) + (tinydns-raw-record 33 zr + (build-record + (rec-u16 prio) + (rec-u16 weight) + (rec-u16 port) + (rec-name host))))) + +(defmethod zone-write-record ((format (eql :tinydns)) (type (eql :sshfp)) zr) + (destructuring-bind (alg type fpr) (zr-data zr) + (tinydns-raw-record 44 zr (build-record - (dolist (s (zr-data zr)) - (rec-u8 (length s)) - (rec-raw-string s))))) - (:method ((type (eql :srv)) zr) - (destructuring-bind (prio weight port host) (zr-data zr) - (tinydns-raw-record 33 zr - (build-record - (rec-u16 prio) - (rec-u16 weight) - (rec-u16 port) - (rec-name host))))) - (:method ((type (eql :sshfp)) zr) - (destructuring-bind (alg type fpr) (zr-data zr) - (tinydns-raw-record 44 zr - (build-record - (rec-u8 alg) - (rec-u8 type) - (do ((i 0 (+ i 2)) - (n (length fpr))) - ((>= i n)) - (rec-u8 (parse-integer fpr - :start i :end (+ i 2) - :radix 16)))))))) - -(defmethod zone-write ((format (eql :tinydns)) zone stream) - (format stream "~ + (rec-u8 alg) + (rec-u8 type) + (do ((i 0 (+ i 2)) + (n (length fpr))) + ((>= i n)) + (rec-u8 (parse-integer fpr + :start i :end (+ i 2) + :radix 16)))))))) + +(defmethod zone-write-header ((format (eql :tinydns)) zone) + (format *zone-output-stream* "~ ### Zone file `~(~A~)' ### (generated ~A) ~%" @@ -1275,8 +1324,6 @@ $TTL ~2@*~D~2%" (soa-refresh soa) (soa-expire soa) (soa-min-ttl soa) - (zone-default-ttl zone))) - (dolist (zr (zone-records-sorted zone)) - (tinydns-record (zr-type zr) zr))) + (zone-default-ttl zone)))) ;;;----- That's all, folks -------------------------------------------------- -- 2.11.0