Commit Diff


commit - 3a2c53f506aa2d07eaf0e8540f87054c622304fb
commit + 8c3973d8b52685b5fb439202ea0e648bc7a739d0
blob - /dev/null
blob + 64b7b5355b4707cca6b656798089c014b59bc3a5 (mode 644)
--- /dev/null
+++ regress/lisp/9p-test/9p-test.asd
@@ -0,0 +1,41 @@
+;; test-suite for kamid
+;; Copyright (C) 2021  cage
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.
+;; If not, see [[http://www.gnu.org/licenses/][http://www.gnu.org/licenses/]].
+
+(defsystem :9p-test
+  :author      "cage"
+  :license     "GPLv3"
+  :version     "0.0.1"
+  :serial      t
+  :depends-on (:alexandria
+               :cl-ppcre
+               :osicat
+               :cl+ssl
+               :clunit2
+               :usocket
+               :babel
+               :uiop)
+  :components ((:file "package")
+               (:file "text-utils")
+               (:file "misc-utils")
+               (:file "filesystem-utils")
+               (:file "conditions")
+               (:file "message-types")
+               (:file "client")
+               (:module tests
+                :components ((:file "package")
+                             (:file "all-tests")
+                             (:file "kami-tests")))))
blob - /dev/null
blob + da745b2ac762597812a4b5d4d3ac22311ed8e111 (mode 644)
--- /dev/null
+++ regress/lisp/9p-test/client.lisp
@@ -0,0 +1,618 @@
+;; test stuite for kami
+;; Copyright (C) 2021  cage
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+(in-package :9p-client)
+
+(define-constant +byte-type+            '(unsigned-byte 8) :test #'equalp)
+
+(define-constant +version+               "9P2000"          :test #'string=)
+
+(define-constant +message-length-size+          4          :test #'=)
+
+(define-constant +message-type-size+            1          :test #'=)
+
+(define-constant +message-tag-size+             2          :test #'=)
+
+(define-constant +message-string-length-size+   2          :test #'=)
+
+(define-constant +nofid+                        #xffffffff :test #'=)
+
+(define-constant +create-for-read+       #x0               :test #'=)
+
+(define-constant +create-for-write+      #x1               :test #'=)
+
+(define-constant +create-for-read-write+ #x2               :test #'=)
+
+(define-constant +create-for-exec+       #x3               :test #'=)
+
+(define-constant +create-dir+            #x80000000        :test #'=)
+
+(define-constant +open-truncate+         #x10              :test #'=)
+
+(define-constant +open-remove-on-clunk+  #x40              :test #'=)
+
+(define-constant +stat-type-dir+          #x80000000       :test #'=
+  :documentation "mode bit for directories")
+
+(define-constant +stat-type-append+       #x40000000       :test #'=
+  :documentation "mode bit for append only files")
+
+(define-constant +stat-type-excl+         #x20000000       :test #'=
+  :documentation "mode bit for exclusive use files")
+
+(define-constant +stat-type-mount+        #x10000000       :test #'=
+  :documentation "mode bit for mounted channel")
+
+(define-constant +stat-type-auth+         #x08000000       :test #'=
+  :documentation "mode bit for authentication file")
+
+(define-constant +stat-type-tmp+          #x04000000       :test #'=
+  :documentation "mode bit for non-backed-up files")
+
+(define-constant +stat-type-read+         #x4              :test #'=
+  :documentation "mode bit for read permission")
+
+(define-constant +stat-type-write+        #x2              :test #'=
+  :documentation "mode bit for write permission")
+
+(define-constant +stat-type-exec+         #x1              :test #'=
+  :documentation "mode bit for execute permission")
+
+(define-constant +standard-socket-port+   564              :test #'=)
+
+(define-constant +nwname-clone+             0              :test #'=)
+
+(defparameter *buffer-size*  (* 4 1024 1024))
+
+(defparameter *tag* 8)
+
+(defparameter *fid* #x00000001)
+
+(defparameter *messages-sent* '())
+
+(defun tags-exists-p-clsr (tag-looking-for)
+  (lambda (a) (octects= tag-looking-for (car a))))
+
+(defun fire-response (tag message-type data)
+  (let ((found (find-if (tags-exists-p-clsr tag) *messages-sent*)))
+    (if found
+        (let ((fn (cdr found)))
+          (setf *messages-sent* (remove-if (tags-exists-p-clsr tag) *messages-sent*))
+          (funcall fn message-type data))
+        (warn (format nil "received unknown response message tag ~a" tag)))))
+
+(defun append-tag-callback (tag function)
+  (setf *messages-sent* (push (cons tag function) *messages-sent*)))
+
+(defun read-all-pending-message (stream)
+  (when *messages-sent*
+    (multiple-value-bind (message-type rtag data)
+        (restart-case
+            (read-message stream)
+          (ignore-error (e)
+            (values (message-type e)
+                    (tag          e)
+                    #())))
+      (fire-response rtag message-type data)
+      (read-all-pending-message stream))))
+
+(defun next-tag ()
+  (prog1
+      (make-octects *tag* 2)
+    (incf *tag*)))
+
+(defun next-fid ()
+  (prog1
+      (int32->bytes *fid*)
+    (incf *fid*)))
+
+(defun bytes->int (bytes)
+  (let ((res #x0000000000000000)
+        (ct  0))
+    (map nil
+         (lambda (a)
+           (setf res (boole boole-ior
+                            (ash a ct)
+                            res))
+           (incf ct 8))
+         bytes)
+    res))
+
+(defmacro gen-intn->bytes (bits)
+  (let ((function-name (alexandria:format-symbol t "~:@(int~a->bytes~)" bits)))
+    `(defun ,function-name (val &optional (count 0) (res '()))
+       (if (>= count ,(/ bits 8))
+           (reverse res) ; little endian
+           (,function-name (ash val -8)
+                           (1+ count)
+                           (push (boole boole-and val #x00ff)
+                                 res))))))
+
+(gen-intn->bytes  8)
+
+(gen-intn->bytes  16)
+
+(gen-intn->bytes  32)
+
+(gen-intn->bytes  64)
+
+(gen-intn->bytes 512)
+
+(gen-intn->bytes 416)
+
+(defun big-endian->little-endian (bytes)
+  (reverse bytes))
+
+(defun vcat (a b)
+  (concatenate 'vector a b))
+
+(defclass octects ()
+  ((value
+    :initform 0
+    :initarg :value
+    :accessor value)
+   (size
+    :initform 0
+    :initarg :size
+    :accessor size)))
+
+(defgeneric octects= (a b))
+
+(defgeneric encode (object))
+
+(defgeneric decode (object))
+
+(defmethod encode ((object octects))
+  (with-accessors ((value value)
+                   (size size)) object
+    (let ((bytes (ecase size
+                   (1  (int8->bytes  value))
+                   (2  (int16->bytes  value))
+                   (4  (int32->bytes  value))
+                   (8  (int64->bytes  value))
+                   (13 (int416->bytes value))
+                   (32 (int512->bytes value))))
+          (res   (make-array size :element-type +byte-type+)))
+      (loop for i from 0 below size do
+        (setf (elt res i) (elt bytes i)))
+      res)))
+
+(defmethod octects= ((a octects) b)
+  (= (value a) b))
+
+(defmethod octects= ((a number) (b octects))
+  (octects= b a))
+
+(defmethod octects= ((a number) (b number))
+  (= b a))
+
+(defun add-size (msg)
+  (let ((length (int32->bytes (+ +message-length-size+ (length msg)))))
+    (vcat length msg)))
+
+(defun close-ssl-socket (socket)
+  (usocket:socket-close socket))
+
+(defun close-client (socket)
+  (close-ssl-socket socket))
+
+(defun send-message (stream message)
+  (write-sequence message stream)
+  (finish-output stream))
+
+(defun encode-string (string)
+  (let* ((bytes (babel:string-to-octets string))
+         (size  (int16->bytes (length bytes))))
+    (vcat size bytes)))
+
+(defmethod encode ((object string))
+  (encode-string object))
+
+(defmethod encode ((object list))
+  (let ((buffer (make-message-buffer (length object))))
+    (loop for i from 0 below (length object) do
+      (setf (elt buffer i) (elt object i)))
+    buffer))
+
+(defmethod encode (object)
+  object)
+
+(defmethod decode-string (data)
+  (let ((size (bytes->int (subseq data 0 +message-string-length-size+))))
+    (babel:octets-to-string (subseq data
+                                    +message-string-length-size+
+                                    (+ +message-string-length-size+ size))
+                            :errorp nil)))
+
+(defun compose-message (message-type tag &rest params)
+  (let ((actual-params (reduce #'vcat (mapcar #'encode params))))
+    (add-size (reduce #'vcat (list (encode message-type) (encode tag) actual-params)))))
+
+(defun displace-response (response)
+  (let ((message-type (subseq response 0 +message-type-size+))
+        (message-tag  (subseq response
+                              +message-type-size+
+                              (+ +message-type-size+
+                                 +message-tag-size+)))
+        (data         (subseq response
+                              (+ +message-type-size+
+                                 +message-tag-size+))))
+    (values (bytes->int message-type)
+            (bytes->int message-tag)
+            data)))
+
+(defun make-message-buffer (size)
+  (make-array size :element-type +byte-type+))
+
+(defun error-response-p (response)
+  (multiple-value-bind (message-type x y)
+      (displace-response response)
+    (declare (ignore x y))
+    (= message-type *rerror*)))
+
+(defun read-message (stream)
+  (let ((message-length-buffer  (make-message-buffer +message-length-size+)))
+    (read-sequence message-length-buffer stream)
+    (let* ((message-length (bytes->int message-length-buffer))
+           (buffer         (make-message-buffer (- message-length +message-length-size+))))
+      (read-sequence buffer stream)
+      (multiple-value-bind (message-type tag data)
+          (displace-response buffer)
+        (if (error-response-p buffer)
+            (error '9p-error
+                   :message-type message-type
+                   :tag          tag
+                   :error-value (decode-string data))
+            (values message-type tag data))))))
+
+(defun make-octects (number size)
+  (make-instance 'octects :value number :size size))
+
+(defun send-version (stream tag)
+  (let ((message (compose-message (make-octects *tversion* 1)
+                                  tag
+                                  (make-octects *buffer-size* 4)
+                                  +version+)))
+    (send-message stream message)
+    (multiple-value-bind (message-type rtag data)
+        (read-message stream)
+      (assert (= message-type *rversion*))
+      (if (octects= rtag tag)
+          (let ((message-size     (bytes->int    (subseq data 0 4)))
+                (protocol-version (decode-string (subseq data 4))))
+            (if (string= protocol-version +version+)
+                (progn
+                  (setf *buffer-size* message-size)
+                  (values message-size protocol-version))
+                (error '9p-error
+                       :message-type message-type
+                       :tag          tag
+                       :error-value  (format nil
+                                             "Version mismatch: ~s instead of ~s"
+                                             protocol-version
+                                             +version+))))
+          (error '9p-initialization-error :tag tag :rtag rtag)))))
+
+(defmacro with-new-tag ((tag) &body body)
+  `(let ((,tag (next-tag)))
+     ,@body))
+
+(defmacro with-new-fid ((fid) &body body)
+  `(let ((,fid (next-fid)))
+     ,@body))
+
+(defun initialize-session (stream)
+  (with-new-tag (tag)
+    (multiple-value-bind (buffer-size protocol-version)
+        (send-version stream tag)
+      (values protocol-version buffer-size))))
+
+(defun decode-quid (data)
+  (let ((file-type    (first-elt data))
+        (file-version (subseq data 1 4))
+        (file-path    (subseq data 1 5)))
+    (values file-type
+            (bytes->int file-version)
+            (bytes->int file-path))))
+
+(defun dummy-callback (message-type data)
+  (declare (ignore message-type data)))
+
+(defun dump-callback (message-type data)
+  (format t "reply mtype  ~a ~a~%" message-type data))
+
+(defgeneric 9p-attach (stream root &key username callback))
+
+(defmethod 9p-attach (stream (root string)
+                      &key
+                        (username "nobody")
+                        (callback #'dummy-callback))
+  (with-new-tag (tag)
+    (with-new-fid (root-fid)
+      (let* ((message (compose-message (make-octects *tattach* 1)
+                                       tag
+                                       root-fid
+                                       (make-octects +nofid+ 4)
+                                       username
+                                       root)))
+        (append-tag-callback tag callback)
+        (send-message stream message)
+        root-fid))))
+
+(defun 9p-create (stream parent-dir-fid path
+                  &key
+                    (callback    #'dummy-callback)
+                    (permissions #o640)
+                    (mode        +create-for-read-write+))
+  "Note: path is relative to root, see attach,
+   Also note that successfully creating a file will open it."
+  (with-new-tag (tag)
+    (let* ((message (compose-message (make-octects *tcreate* 1)
+                                     tag
+                                     parent-dir-fid
+                                     path
+                                     (make-octects permissions 4)
+                                     (make-octects mode 1))))
+      (append-tag-callback tag callback)
+      (send-message stream message))))
+
+(defun 9p-open (stream fid
+                &key
+                  (callback #'dummy-callback)
+                  (mode     +create-for-read+))
+  "Note before opening you have to 'walk' the file to get the corresponding fid."
+  (with-new-tag (tag)
+    (let* ((message (compose-message (make-octects *topen* 1)
+                                     tag
+                                     fid
+                                     (make-octects mode 1))))
+      (append-tag-callback tag callback)
+      (send-message stream message))))
+
+(defgeneric 9p-write (stream fid offset data &key callback))
+
+(defmethod 9p-write (stream fid offset (data vector)
+                     &key
+                       (callback #'dummy-callback))
+  (let* ((data-chunk-num    (floor (/ (length data) *buffer-size*)))
+         (data-chunk-length (if (> (length data) *buffer-size*)
+                                (* data-chunk-num *buffer-size*)
+                                (length data)))
+         (remainder         (if (> (length data) *buffer-size*)
+                                (- (length data)
+                                   (* data-chunk-num *buffer-size*))
+                                0)))
+    (flet ((write-chunk (chunk chunk-offset)
+             (with-new-tag (tag)
+               (let* ((message (compose-message (make-octects *twrite* 1)
+                                                tag
+                                                fid
+                                                (make-octects chunk-offset 8)
+                                                (make-octects (length chunk) 4)
+                                                chunk)))
+                 (append-tag-callback tag callback)
+                 (send-message stream message)))))
+      (loop for i from 0 below (- (length data) remainder) by data-chunk-length do
+        (let ((chunk (subseq data i (+ i data-chunk-length))))
+          (write-chunk chunk (+ offset i))))
+      (when (> remainder 0)
+        (write-chunk (subseq data (- (length data) remainder))
+                     (+ offset (- (length data) remainder)))))))
+
+(defmethod 9p-write (stream fid offset (data string)
+                     &key
+                       (callback #'dummy-callback))
+  (9p-write stream fid offset (babel:string-to-octets data) :callback callback))
+
+(defun 9p-walk (stream root-fid new-fid new-name &key (callback #'dummy-callback))
+  (if (and (numberp new-name)
+           (= 0 new-name))
+      (%9p-walk-self stream root-fid new-fid :callback callback)
+      (with-new-tag (tag)
+        (let* ((message (compose-message (make-octects *twalk* 1)
+                                         tag
+                                         root-fid
+                                         new-fid
+                                         (make-octects 1 2)
+                                         new-name)))
+          (append-tag-callback tag callback)
+          (send-message stream message)))))
+
+(defun %9p-walk-self (stream root-fid new-fid &key (callback #'dummy-callback))
+  (with-new-tag (tag)
+    (let* ((message (compose-message (make-octects *twalk* 1)
+                                     tag
+                                     root-fid
+                                     new-fid
+                                     (make-octects 0 2))))
+      (append-tag-callback tag callback)
+      (send-message stream message))))
+
+(defun 9p-remove (stream fid &key (callback #'dummy-callback))
+  (with-new-tag (tag)
+    (let* ((message (compose-message (make-octects *tremove* 1)
+                                     tag
+                                     fid)))
+      (append-tag-callback tag callback)
+      (send-message stream message))))
+
+(defun 9p-clunk (stream fid &key (callback #'dummy-callback))
+  (with-new-tag (tag)
+    (let* ((message (compose-message (make-octects *tclunk* 1)
+                                     tag
+                                     fid)))
+      (append-tag-callback tag callback)
+      (send-message stream message))))
+
+(defun 9p-stat (stream fid &key (callback #'dummy-callback))
+  (with-new-tag (tag)
+    (let* ((message (compose-message (make-octects *tstat* 1)
+                                     tag
+                                     fid)))
+      (append-tag-callback tag callback)
+      (send-message stream message))))
+
+(defun 9p-read (stream fid offset chunk-length &key (callback #'dummy-callback))
+  (with-new-tag (tag)
+    (let* ((message (compose-message (make-octects *tread* 1)
+                                     tag
+                                     fid
+                                     (make-octects offset 8)
+                                     (make-octects chunk-length 4))))
+      (append-tag-callback tag callback)
+      (send-message stream message))))
+
+(defun decode-read-reply (data &optional (as-string nil))
+  (let ((count    (bytes->int (subseq data 0 4)))
+        (raw-data (subseq data 4)))
+    (values (if as-string
+                (babel:octets-to-string raw-data :errorp nil)
+                raw-data)
+            count)))
+
+(defun encoded-string-offset (decoded-string)
+  (+  (length decoded-string)
+      +message-string-length-size+))
+
+(defun decode-rstat (data)
+  (flet ((->int (start end)
+           (bytes->int (subseq data start end))))
+    (let* ((entry-size1           (->int  0  2))
+           (entry-size2           (->int  2  4))
+           (ktype                 (->int  4  6))
+           (kdev                  (->int  6 10))
+           (entry-type            (->int 10 11))
+           (version               (->int 11 15))
+           (path                  (->int 15 23))
+           (mode                  (->int 23 27))
+           (atime                 (->int 27 31))
+           (mtime                 (->int 31 35))
+           (size                  (->int 35 43))
+           (strings-start         43)
+           (name                  (decode-string (subseq data strings-start)))
+           (name-offset           (encoded-string-offset name))
+           (user-id               (decode-string (subseq data
+                                                         (+ strings-start
+                                                            name-offset))))
+           (user-id-offset        (+ strings-start
+                                     (encoded-string-offset user-id)
+                                     name-offset))
+           (group-id              (decode-string (subseq data user-id-offset)))
+           (group-id-offset       (+ user-id-offset
+                                     (encoded-string-offset group-id)))
+           (last-modified-from-id (decode-string (subseq data group-id-offset))))
+      (values entry-size1
+              entry-size2
+              ktype
+              kdev
+              entry-type
+              version
+              path
+              mode
+              atime
+              mtime
+              size
+              name
+              user-id
+              group-id
+              last-modified-from-id))))
+
+;;; high level routines
+
+(defun read-all-pending-messages-ignoring-errors (stream)
+  (handler-bind ((9p-error
+                   (lambda (e)
+                     (invoke-restart 'ignore-error e))))
+    (read-all-pending-message stream)))
+
+(defun create-directory (stream parent-fid directory-name &key (permissions #o760))
+  (9p-create stream
+             parent-fid
+             directory-name
+             :permissions (logior +create-dir+ permissions)
+             :mode        +create-for-read+)
+  (read-all-pending-messages-ignoring-errors stream))
+
+(defun create-path (stream parent-fid path &key (file-permissions #o640))
+  (let ((fs:*directory-sep-regexp* "\\/")
+        (path-elements             (fs:split-path-elements path))
+        (last-is-dir-p             (cl-ppcre:scan "\\/$" path)))
+    (labels ((%create-dirs (path-elements)
+               (when path-elements
+                 (create-directory stream parent-fid (first path-elements))
+                 (read-all-pending-messages-ignoring-errors stream)
+                 (%create-dirs (rest path-elements)))))
+      (%create-dirs (misc:safe-all-but-last-elt path-elements))
+      (if last-is-dir-p
+          (create-directory stream parent-fid (last-elt path-elements))
+          (9p-create        stream parent-fid (last-elt path-elements)
+                            :permissions file-permissions))
+      (read-all-pending-messages-ignoring-errors stream))))
+
+(defun mount (stream root-path)
+  (let ((protocol-version (initialize-session stream))
+        (root-fid (9p-attach stream root-path)))
+    (read-all-pending-message stream)
+    (values root-fid protocol-version)))
+
+(defun open-path (stream root-fid path
+                  &key
+                    (walk-callback #'dummy-callback)
+                    (open-callback #'dummy-callback)
+                    (mode          +create-for-read+))
+  (let ((fs:*directory-sep-regexp* "\\/")
+        (path-elements             (remove "/"
+                                           (fs:split-path-elements path)
+                                           :test #'string=)))
+    (labels ((walk-dirs (path-elements parent-fid)
+               (with-new-fid (fid)
+                 (if path-elements
+                     (progn
+                       (9p-walk stream
+                                parent-fid
+                                fid
+                                (first path-elements)
+                                :callback walk-callback)
+                       (read-all-pending-message stream)
+                       (walk-dirs (rest path-elements) fid))
+                     parent-fid))))
+      (let ((fid (walk-dirs path-elements root-fid)))
+        (9p-open stream fid :callback open-callback :mode mode)
+        (read-all-pending-message stream)
+        fid))))
+
+(defun cat-reply-vector (a b)
+  (concatenate '(vector (unsigned-byte 8)) a b))
+
+(defun slurp-file (stream root-fid path &key (buffer-size *buffer-size*))
+  (let ((res (make-array 0 :element-type +byte-type+ :adjustable nil))
+        (fid (open-path stream root-fid path)))
+    (labels ((slurp (offset)
+               (9p-read stream
+                        fid
+                        offset
+                        buffer-size
+                        :callback (lambda (x reply)
+                                    (declare (ignore x))
+                                    (multiple-value-bind (data count)
+                                        (decode-read-reply reply nil)
+                                      (setf res (cat-reply-vector res data))
+                                      (when (or (= count buffer-size)
+                                                (= count *buffer-size*))
+                                          (slurp (+ offset count))))))))
+      (slurp 0)
+      (read-all-pending-message stream)
+      res)))
blob - /dev/null
blob + 5c7a3780fb7a4bc3c8bc89d9ef974ae6600b9ddb (mode 644)
--- /dev/null
+++ regress/lisp/9p-test/conditions.lisp
@@ -0,0 +1,48 @@
+;; test stuite for kami
+;; Copyright (C) 2021  cage
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+(in-package :9p-client)
+
+(define-condition 9p-error (error)
+  ((error-value
+    :initarg :error-value
+    :reader error-value)
+   (message-type
+    :initarg :message-type
+    :reader message-type)
+   (tag
+    :initarg :tag
+    :reader tag))
+  (:report (lambda (condition stream)
+             (format stream
+                     "message-type ~a tag ~a: ~a"
+                     (message-type condition)
+                     (tag condition)
+                     (error-value condition))))
+  (:documentation "Error for 9p protocol"))
+
+
+(define-condition 9p-initialization-error (error)
+  ((tag
+    :initarg :tag
+    :reader tag)
+   (rtag
+    :initarg :rtag
+    :reader rtag))
+  (:report (lambda (condition stream)
+             (format stream "error initialization tag sent ~a, got ~a instead"
+                     (tag condition) (rtag condition))))
+  (:documentation "Error for 9p protocol"))
blob - /dev/null
blob + 43ee5f51e424eefe264bdd3177bf63cc007abd30 (mode 644)
--- /dev/null
+++ regress/lisp/9p-test/filesystem-utils.lisp
@@ -0,0 +1,141 @@
+;; test stuite for kami
+;; Copyright (C) 2021  cage
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+(in-package :filesystem-utils)
+
+(define-constant +preprocess-include+ "^%include"              :test #'string=)
+
+(define-constant +file-path-regex+ "[\\p{L},\\/,\\\\,\\.]+"    :test 'string=)
+
+(defparameter *directory-sep-regexp*
+  #+windows "\\"
+  #-windows "\\/")
+
+(defparameter *directory-sep*
+  #+windows "\\"
+  #-windows "/")
+
+(defun cat-parent-dir (parent direntry)
+  (format nil "~a~a~a" parent *directory-sep* direntry))
+
+(defmacro do-directory ((var) root &body body)
+  (with-gensyms (dir)
+    `(let ((,dir (nix:opendir ,root)))
+       (unwind-protect
+            (handler-case
+                (do ((,var (cat-parent-dir ,root (nix:readdir ,dir))
+                           (cat-parent-dir ,root (nix:readdir ,dir))))
+                    ((cl-ppcre:scan "NIL$" ,var))
+                  ,@body)
+              (nix::enotdir () 0)
+              (nix:eacces () 0)
+              (nix:eloop () 0))
+       (nix:closedir ,dir)))))
+
+(defun collect-children (parent-dir)
+  (let ((all-paths ()))
+    (do-directory (path) parent-dir
+      (push path all-paths))
+    (setf all-paths (sort all-paths #'string<))
+    all-paths))
+
+(defun getenv (name)
+  (nix:getenv name))
+
+(defun pwd ()
+  (getenv "PWD"))
+
+(defgeneric prepend-pwd (object))
+
+(defmethod prepend-pwd ((object string))
+  (if (cl-ppcre:scan "^\\." object)
+      (text-utils:strcat (pwd) (subseq object 1))
+      object))
+
+(defmethod prepend-pwd ((object sequence))
+  (map 'list #'prepend-pwd object))
+
+(defun regular-file-p (path)
+  (nix:s-isreg (nix:stat-mode (nix:stat path))))
+
+(defun dirp (path)
+  (ignore-errors
+   (and (nix:stat path)
+        (nix:s-isdir (nix:stat-mode (nix:stat path))))))
+
+(defun split-path-elements (path)
+  (let ((splitted (cl-ppcre:split *directory-sep-regexp* path)))
+    (substitute *directory-sep* "" splitted :test #'string=)))
+
+(defun path-last-element (path)
+  (let ((elements (cl-ppcre:split *directory-sep-regexp* path)))
+    (and elements
+         (last-elt elements))))
+
+(defun path-first-element (path)
+  (let ((elements (cl-ppcre:split *directory-sep-regexp* path)))
+    (and elements
+         (first-elt elements))))
+
+(defun path-to-hidden-file-p (path)
+  "unix-like only"
+  (let ((last-element (path-last-element path)))
+    (and path (cl-ppcre:scan "^\\." last-element))))
+
+(defun strip-dirs-from-path (p)
+  (multiple-value-bind (all registers)
+      (cl-ppcre:scan-to-strings (concatenate 'string
+                                             *directory-sep*
+                                             "([^"
+                                             *directory-sep*
+                                             "]+)$")
+                                p)
+    (declare (ignore all))
+    (and (> (length registers) 0)
+         (elt registers 0))))
+
+(defun parent-dir-path (path)
+  (let ((splitted (remove-if #'(lambda (a) (string= "" a))
+                             (split-path-elements path))))
+    (cond
+      ((> (length splitted) 1)
+       (let ((res (if (string= (string (elt path 0)) *directory-sep*)
+                      (concatenate 'string *directory-sep* (first splitted))
+                      (first splitted))))
+         (loop for i in (subseq splitted 1 (1- (length splitted))) do
+              (setf res (concatenate 'string res *directory-sep* i)))
+         (setf res (concatenate 'string res *directory-sep*))
+         res))
+      ((or (= (length splitted) 1)
+           (null splitted))
+       *directory-sep*)
+      (t
+       path))))
+
+(defun file-exists-p (f)
+  (uiop:file-exists-p f))
+
+(defun directory-exists-p (d)
+  (uiop:directory-exists-p d))
+
+(defun delete-file-if-exists (f)
+  (uiop:delete-file-if-exists f))
+
+(defun home-dir (&key (add-separator-ends nil))
+  (let ((home (getenv "HOME")))
+    (if add-separator-ends
+        (text-utils:strcat home *directory-sep*)
+        home)))
blob - /dev/null
blob + cf9a8fc944733fc117390e9c25446dde72f9bf76 (mode 644)
--- /dev/null
+++ regress/lisp/9p-test/message-types.lisp
@@ -0,0 +1,69 @@
+;; test stuite for kami
+;; Copyright (C) 2021  cage
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+(in-package :9p-client)
+
+(defparameter *tversion*  100)
+
+(defparameter *rversion*  101)
+
+(defparameter *tauth*     102)
+
+(defparameter *rauth*     103)
+
+(defparameter *tattach*   104)
+
+(defparameter *rattach*   105)
+
+(defparameter *terror*    106) ; there is no terror
+
+(defparameter *rerror*    107)
+
+(defparameter *tflush*    108)
+
+(defparameter *rflush*    108)
+
+(defparameter *twalk*     110)
+
+(defparameter *rwalk*     109)
+
+(defparameter *topen*     112)
+
+(defparameter *ropen*     113)
+
+(defparameter *tcreate*   114)
+
+(defparameter *rcreate*   115)
+
+(defparameter *tread*     116)
+
+(defparameter *rread*     117)
+
+(defparameter *twrite*    118)
+
+(defparameter *rwrite*    119)
+
+(defparameter *tclunk*    120)
+
+(defparameter *rclunk*    121)
+
+(defparameter *tremove*   122)
+
+(defparameter *rremove*   123)
+
+(defparameter *tstat*     124)
+
+(defparameter *rstat*     125)
blob - /dev/null
blob + e899746e6d882f0329bb4421a66b42f7baebac50 (mode 644)
--- /dev/null
+++ regress/lisp/9p-test/misc-utils.lisp
@@ -0,0 +1,96 @@
+;; test stuite for kami
+;; Copyright (C) 2021  cage
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;; uses code from
+
+;; niccolo': a chemicals inventory
+;; Copyright (C) 2016  Universita' degli Studi di Palermo
+
+;; This  program is  free  software: you  can  redistribute it  and/or
+;; modify it  under the  terms of  the GNU  General Public  License as
+;; published  by  the  Free  Software Foundation,  version  3  of  the
+;; License, or (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+(in-package :misc-utils)
+
+(defmacro defcond (type)
+  `(define-condition ,(alexandria:format-symbol t "TEXT-~a" (string-upcase type))
+       (,type)
+     ((text
+       :initarg :text
+       :reader text))
+     (:documentation "Error that set text")))
+
+(defcond error)
+
+(define-condition out-of-bounds (error)
+  ((seq
+    :initarg :seq
+    :reader seq)
+   (idx
+    :initarg :idx
+    :reader idx))
+  (:documentation "Error when you go out of bound"))
+
+(defgeneric delete@ (sequence position))
+
+(defgeneric safe-delete@ (sequence position)
+  (:documentation "Return sequence if position is out of bound"))
+
+(defmacro gen-delete@ ((sequence position) &body body)
+  `(if (and (>= ,position 0)
+            (< ,position (length ,sequence)))
+       ,@body
+      (error 'out-of-bounds :seq sequence :idx position)))
+
+(defmethod delete@ ((sequence list) position)
+  (gen-delete@
+   (sequence position)
+   (append (subseq sequence 0 position)
+           (and (/= position (- (length sequence) 1))
+                (subseq sequence (1+ position))))))
+
+(defmethod delete@ ((sequence vector) position)
+  (gen-delete@
+   (sequence position)
+    (make-array (1- (length sequence))
+                :fill-pointer (1- (length sequence))
+                :adjustable t
+                :initial-contents (concatenate 'vector (subseq sequence 0 position)
+                                               (and (/= position (- (length sequence) 1))
+                                                    (subseq sequence (1+ position)))))))
+
+(defmethod safe-delete@ ((sequence sequence) position)
+  (restart-case
+      (delete@ sequence position)
+    (return-nil () nil)
+    (return-whole () sequence)
+    (new-index (i) (safe-delete@ sequence i))))
+
+(defun safe-all-but-last-elt (sequence)
+  (handler-bind ((out-of-bounds
+                  #'(lambda (c)
+                      (declare (ignore c))
+                      (invoke-restart 'return-nil))))
+    (safe-delete@ sequence (1- (length sequence)))))
blob - /dev/null
blob + ade03a9c22ae86586fea6b6f4f2fa005635e7845 (mode 644)
--- /dev/null
+++ regress/lisp/9p-test/package.lisp
@@ -0,0 +1,89 @@
+;; test stuite for kami
+;; Copyright (C) 2021  cage
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+(defpackage :text-utils
+  (:use
+   :cl)
+  (:export
+   :strcat))
+
+(defpackage :misc-utils
+  (:use :cl)
+  (:nicknames :misc)
+  (:export
+   :safe-all-but-last-elt))
+
+(defpackage :filesystem-utils
+  (:use
+   :cl
+   :alexandria)
+  (:nicknames :fs)
+  (:export
+   :*directory-sep-regexp*
+   :getenv
+   :cat-parent-dir
+   :split-path-elements
+   :collect-children
+   :prepend-pwd))
+
+(defpackage :9p-client
+  (:use
+   :cl
+   :alexandria)
+  (:export
+   :+byte-type+
+   :+version+
+   :+nofid+
+   :+create-for-read+
+   :+create-for-write+
+   :+create-for-read-write+
+   :+create-for-exec+
+   :+create-dir+
+   :+open-truncate+
+   :+open-remove-on-clunk+
+   :+standard-socket-port+
+   :+nwname-clone+
+   :*buffer-size*
+   :*messages-sent*
+   :read-all-pending-message
+   :close-client
+   :encode-string
+   :decode-string
+   :encode
+   :decode
+   :read-message
+   :initialize-session
+   :with-new-tag
+   :with-new-fid
+   :dummy-callback
+   :dump-callback
+   :9p-attach
+   :9p-create
+   :9p-open
+   :9p-write
+   :9p-remove
+   :9p-clunk
+   :9p-stat
+   :9p-read
+   :9p-walk
+   :decode-read-reply
+   :decode-rstat
+   :read-all-pending-messages-ignoring-errors
+   :create-directory
+   :create-path
+   :mount
+   :open-path
+   :slurp-file))
blob - /dev/null
blob + 598ecedc0ac2742c734e6bcf87aeb4388ea95b15 (mode 755)
--- /dev/null
+++ regress/lisp/9p-test/run-tests.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+#export REGRESS_CERT="$HOME/lisp/kamid.cert"
+#export REGRESS_KEY="$HOME/lisp/kamid.key"
+#export REGRESS_HOSTNAME="localhost"
+#export REGRESS_PORT=10564
+
+sbcl --eval "(require 'asdf)" --eval "(push \"$(pwd)/\" asdf:*central-registry*)" --eval "(asdf:make \"9p-test\")" --eval "(all-tests:run-all-tests)"
blob - /dev/null
blob + 506342c62c223ffdfdcf1eb07df1aa754d93aa2a (mode 644)
--- /dev/null
+++ regress/lisp/9p-test/tests/all-tests.lisp
@@ -0,0 +1,83 @@
+;; test stuite for kami
+;; Copyright (C) 2021  cage
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+(in-package :all-tests)
+
+(defparameter *client-certificate* "")
+
+(defparameter *certificate-key*    "")
+
+(defparameter *host*               "localhost")
+
+(defparameter *port*               10564)
+
+(defparameter *remote-test-file*   "test-file") ; note: missing "/" is intentional
+
+(defparameter *remote-test-path*   "/test-file")
+
+(defparameter *remote-test-path-write*   "/dir/subdir/file/test-file-write")
+
+(defparameter *remote-test-path-contents* (format nil "qwertyuiopasdfghjklòàù è~%"))
+
+(alexandria:define-constant +remote-test-path-ovewrwrite-data+ "12" :test #'string=)
+
+(defun open-tls-socket (host port)
+  (flet ((open-socket (hostname)
+           (usocket:socket-connect hostname
+                                   port
+                                   :element-type '(unsigned-byte 8))))
+    (or (ignore-errors (open-socket host))
+        (open-socket host))))
+
+(defmacro with-open-ssl-stream ((ssl-stream socket host port
+                                 client-certificate
+                                 certificate-key)
+                                &body body)
+  (alexandria:with-gensyms (tls-context socket-stream ssl-hostname)
+    `(let ((,tls-context (cl+ssl:make-context :verify-mode cl+ssl:+ssl-verify-none+)))
+       (cl+ssl:with-global-context (,tls-context :auto-free-p t)
+         (let* ((,socket        (open-tls-socket ,host ,port))
+                (,socket-stream (usocket:socket-stream ,socket))
+                (,ssl-hostname  ,host)
+                (,ssl-stream
+                  (cl+ssl:make-ssl-client-stream ,socket-stream
+                                                 :certificate     ,client-certificate
+                                                 :key             ,certificate-key
+                                                 :external-format nil ; unsigned byte 8
+                                                 :unwrap-stream-p t
+                                                 :verify          nil
+                                                 :hostname        ,ssl-hostname)))
+           ,@body)))))
+
+(defsuite all-suite ())
+
+(defun exit-program (&optional (exit-code 0))
+  (uiop:quit exit-code))
+
+(defun run-all-tests (&key (use-debugger t))
+  (setf *client-certificate* (fs:getenv "REGRESS_CERT")
+        *certificate-key*    (fs:getenv "REGRESS_KEY")
+        *host*               (fs:getenv "REGRESS_HOSTNAME")
+        *port*               (parse-integer (fs:getenv "REGRESS_PORT")))
+  (handler-bind ((error (lambda (e)
+                          (declare (ignore e))
+                          (exit-program 1)))
+                 (clunit::assertion-failed (lambda (e)
+                          (declare (ignore e))
+                          (exit-program 2))))
+      (progn
+        (clunit:run-suite 'all-suite :use-debugger use-debugger :report-progress t)
+        (exit-program 0))))
blob - /dev/null
blob + d7b4cf5b0675fe5df18c40ef67c5ab9f8ec4e76b (mode 644)
--- /dev/null
+++ regress/lisp/9p-test/tests/kami-tests.lisp
@@ -0,0 +1,171 @@
+;; test stuite for kami
+;; Copyright (C) 2021  cage
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+(in-package :kami-tests)
+
+(defsuite kami-suite (all-suite))
+
+(defun start-non-tls-socket (host port)
+  (usocket:socket-connect host
+                          port
+                          :protocol     :stream
+                          :element-type +byte-type+))
+
+(defun example-mount (&optional (root  "/"))
+  (with-open-ssl-stream (stream
+                         socket
+                         *host*
+                         *port*
+                         *client-certificate*
+                         *certificate-key*)
+    (let ((*messages-sent* '())
+          (root-fid (mount stream root)))
+      (9p-clunk stream root-fid)
+      (read-all-pending-message stream)
+      (9p-attach stream root)
+      (read-all-pending-message stream)
+      t)))
+
+(deftest test-mount (kami-suite)
+  (assert-true (ignore-errors (example-mount))))
+
+(defun example-walk (path &optional (root "/"))
+  (with-open-ssl-stream (stream
+                         socket
+                         *host*
+                         *port*
+                         *client-certificate*
+                         *certificate-key*)
+
+    (let ((*messages-sent* '())
+          (root-fid (mount stream root)))
+      (with-new-fid (path-fid)
+        (9p-walk stream root-fid path-fid path)
+        (read-all-pending-message stream)
+        t))))
+
+(deftest test-walk (kami-suite)
+  (assert-true (ignore-errors (example-walk *remote-test-file*))))
+
+(defun example-open-path (path &optional (root "/"))
+  (with-open-ssl-stream (stream
+                         socket
+                         *host*
+                         *port*
+                         *client-certificate*
+                         *certificate-key*)
+    (let ((*messages-sent* '())
+          (root-fid (mount stream root)))
+      (with-new-fid (saved-root-fid)
+        (9p-walk stream root-fid saved-root-fid +nwname-clone+)
+        (open-path stream root-fid path)
+        (read-all-pending-message stream)
+        t))))
+
+(deftest test-open-path (kami-suite)
+  (assert-true (ignore-errors (example-open-path *remote-test-path*))))
+
+(defun example-read (path &optional (root "/"))
+  (with-open-ssl-stream (stream
+                         socket
+                         *host*
+                         *port*
+                         *client-certificate*
+                         *certificate-key*)
+    (let ((*messages-sent* ())
+          (*buffer-size*   256)
+          (root-fid        (mount stream root)))
+      (with-new-fid (path-fid)
+        (9p-walk stream root-fid path-fid path)
+        (9p-open stream path-fid)
+        (9p-read stream path-fid 0 10)
+        (read-all-pending-message stream)
+        t))))
+
+(deftest test-read (kami-suite)
+  (assert-true (ignore-errors (example-open-path *remote-test-file*))))
+
+(defun example-slurp (path &optional (root "/"))
+  (with-open-ssl-stream (stream
+                         socket
+                         *host*
+                         *port*
+                         *client-certificate*
+                         *certificate-key*)
+    (let ((*messages-sent* ())
+          (*buffer-size*   256)
+          (root-fid        (mount stream root)))
+      (babel:octets-to-string (slurp-file stream
+                                          root-fid path
+                                          :buffer-size 3)
+                              :errorp nil))))
+
+(deftest test-slurp-file (kami-suite)
+  (assert-equality #'string=
+      *remote-test-path-contents*
+      (example-slurp *remote-test-path*)))
+
+(defun example-write (path &optional (root "/"))
+  (with-open-ssl-stream (stream
+                         socket
+                         *host*
+                         *port*
+                         *client-certificate*
+                         *certificate-key*)
+    (let* ((*messages-sent* ())
+           (*buffer-size*   256)
+           (root-fid        (mount stream root))
+           (fid             (open-path stream root-fid path :mode +create-for-read-write+)))
+      (9p-write stream fid 0 *remote-test-path-contents*)
+      (read-all-pending-message stream)
+      t)))
+
+(deftest test-write ((kami-suite) (test-open-path test-read))
+  (assert-true (ignore-errors (example-write *remote-test-path-write*))))
+
+(defun example-write-2-3 (path &optional (root "/"))
+  (with-open-ssl-stream (stream
+                         socket
+                         *host*
+                         *port*
+                         *client-certificate*
+                         *certificate-key*)
+    (let* ((*messages-sent* ())
+           (*buffer-size*   256)
+           (root-fid        (mount stream root))
+           (fid             (open-path stream root-fid path :mode +create-for-read-write+)))
+      (9p-write stream fid 2 +remote-test-path-ovewrwrite-data+)
+      (read-all-pending-message stream)
+      (babel:octets-to-string (slurp-file stream root-fid path)))))
+
+(defun read-entire-file-as-string (path  &optional (root "/"))
+  (with-open-ssl-stream (stream
+                         socket
+                         *host*
+                         *port*
+                         *client-certificate*
+                         *certificate-key*)
+    (let* ((*messages-sent* ())
+           (*buffer-size*   256)
+           (root-fid        (mount stream root)))
+      (babel:octets-to-string (slurp-file stream root-fid path)))))
+
+(deftest test-example-write-2-3 ((kami-suite) (test-write))
+  (example-write-2-3 *remote-test-path-write*)
+  (let* ((expected-sequence (copy-seq *remote-test-path-contents*))
+         (file-sequence     (read-entire-file-as-string *remote-test-path-write*)))
+    (setf (subseq expected-sequence 2 4) +remote-test-path-ovewrwrite-data+)
+    (assert-equality #'string= file-sequence expected-sequence)))
blob - /dev/null
blob + 27c56b9a5a1fdab10a0c5004ffd0c61a9946c1b0 (mode 644)
--- /dev/null
+++ regress/lisp/9p-test/tests/package.lisp
@@ -0,0 +1,42 @@
+;; test stuite for kami
+;; Copyright (C) 2021  cage
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+(defpackage :all-tests
+  (:use :cl
+        :clunit
+        :cl+ssl)
+  (:export
+   :*client-certificate*
+   :*certificate-key*
+   :*host*
+   :*port*
+   :*remote-test-file*
+   :*remote-test-path*
+   :*remote-test-path-write*
+   :*remote-test-path-contents*
+   :+remote-test-path-ovewrwrite-data+
+   :with-open-ssl-stream
+   :all-suite
+   :run-all-tests))
+
+(defpackage :kami-tests
+  (:use :cl
+        :clunit
+        :misc
+        :text-utils
+        :9p-client
+        :all-tests)
+  (:export))
blob - /dev/null
blob + 9c04d17f6d53dcc1025c18dfb010320d402c576b (mode 644)
--- /dev/null
+++ regress/lisp/9p-test/text-utils.lisp
@@ -0,0 +1,43 @@
+;; test stuite for kami
+;; Copyright (C) 2021  cage
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;; derived from
+
+;; niccolo': a chemicals inventory
+;; Copyright (C) 2016  Universita' degli Studi di Palermo
+
+;; This  program is  free  software: you  can  redistribute it  and/or
+;; modify it  under the  terms of  the GNU  General Public  License as
+;; published  by  the  Free  Software Foundation,  version  3  of  the
+;; License, or (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+(in-package :text-utils)
+
+(defun strcat (&rest chunks)
+  (declare (optimize (debug 0) (safety 0) (speed 3)))
+  (strcat* chunks))
+
+(defun strcat* (chunks)
+  (declare (optimize (debug 0) (safety 0) (speed 3)))
+  (reduce (lambda (a b) (concatenate 'string a b)) chunks))