#| access to the linux /proc file system
 Unix processes are identified by integers called process ids (pids).
 A lot of information about process p can be retrieved by looking in
 /proc/p (e.g., /proc/1234 for process 1234).
 The relations here may depend on your flavor of unix, and perhaps even
 on which version you run.
|#

(defrelation process :derivation individual-derived
  :documentation "current process id's as integers"
  :arity 1 :types (integer) :equivs (=)
  :nonatomic t
  :type-enforcements (:none)
  :tester
  (lambda (&rest ignore) (declare (ignore ignore))
    '(lambda (rel p)
       (and (integerp p) (> p 0) (directory (format nil "/proc/~a/" p)))))
  :testeffort 1000 ;; just a wild guess for now
  :generator
  (simplegenerator
   (output)
   (loop for d in (directory "/proc/*/")
     as p = (ignore-errors (parse-integer (car (last (pathname-directory d)))))
     when p collect p)
   1e5)
  :size ((output) 100))

(defrelation process-stat :derivation individual-derived
  :documentation "relates integer process id to readline of /proc/<id>/stat"
  :arity 2 :types (integer string)  :equivs (= equal)
  :nonatomic t
  :type-enforcements (:none)
  :tester
  (lambda (&rest ignore) (declare (ignore ignore))
    '(lambda (rel p stat)
       (and (integerp p) (> p 0)
	    (ignore-errors
	      (string= stat (with-open-file (f (format nil "/proc/~a/stat" p))
			      (read-line f)))))))
  :testeffort 1000 ;; just a wild guess for now
  :generator
  ((simplegenerator
    (p output)
    (and (integerp p) (> p 0)
	 (ignore-errors
	   (with-open-file (f (format nil "/proc/~a/stat" p))
	     (read-line f))))
    1e3)
   (simplemultiplegenerator
    (output output)
    (loop for f in (directory "/proc/*/stat" :if-does-not-exist :ignore)
      ;; if does not exist needed to prevent error from /proc/net/stat/
      as p = (ignore-errors (parse-integer (car (last (pathname-directory f)))))
      as stat = (ignore-errors (with-open-file (file f) (read-line file)))
      when (integerp p) when (> p 0) collect (list p stat))
    1e5))
  :size ((output output) 100 (input output) 1 (output input) 1))

(defrelation read-string-elt :computation individual-derived
  :documentation "relates string to integer n to nth result of read on string"
  :arity 3 :equivs (equal equal equal)
  :tester
  (lambda (&rest ignore) (declare (ignore ignore))
    '(lambda (rel string n result)
       (and (integerp n) (> n 0) ;; look what I can get away with here ...
	    (ap5::loop for x s.t. (read-string-elt string n x)
	      thereis (equal x result)))))
  :generator
   (simplemultiplegenerator
    (string output output)
    (with-input-from-string
     (s string)
     (loop with eof = (cons nil nil) with r with err for i from 0
       do (multiple-value-setq (r err)
	    (ignore-errors (read s nil eof)))
       until (eq r eof)
       unless err
       collect (list i r))))
   :size ((input input output) 1 (output output output) nil))
(defrelation read-string-elt= :computation individual-derived
  :documentation "relates string to integer n to nth result of read on string"
  :arity 3 :equivs (equal = =) :types (entity integer integer)
  :tester
  (lambda (&rest ignore) (declare (ignore ignore))
    '(lambda (rel string n result)
       (and (integerp n) (> n 0) ;; look what I can get away with here ...
	    (ap5::loop for x s.t. (read-string-elt= string n x)
	      thereis (= x result)))))
  :generator
   (simplemultiplegenerator
    (string output output)
    (with-input-from-string
     (s string)
     (loop with eof = (cons nil nil) with r with err for i from 0
       do (multiple-value-setq (r err)
	    (ignore-errors (read s nil eof)))
       until (eq r eof)
       unless err when (integerp r)
       collect (list i r))))
   :size ((input input output) 1 (output output output) nil))
;; (listof (x y) s.t. (read-string-elt "asd 1 ))) 3" x y))
;; => ((0 ASD) (1 1) (5 3))

(defrelation process-parent
  :definition ((proc parent) s.t.
	       (E (stat)
		  (and (process-stat proc stat)
		       (read-string-elt= stat 3 parent))))
  :size ((output output) 100 (input output) 1))
(defrelation process-utime
  :definition ((proc parent) s.t.
	       (E (stat)
		  (and (process-stat proc stat)
		       (read-string-elt= stat 13 parent))))
  :size ((output output) 100 (input output) 1))
(defrelation process-stime
  :definition ((proc parent) s.t.
	       (E (stat)
		  (and (process-stat proc stat)
		       (read-string-elt= stat 14 parent))))
  :size ((output output) 100 (input output) 1))
(defrelation process-starttime
  :definition ((proc parent) s.t.
	       (E (stat)
		  (and (process-stat proc stat)
		       (read-string-elt= stat 21 parent))))
  :size ((output output) 100 (input output) 1))
(defrelation process-vsize
  :definition ((proc parent) s.t.
	       (E (stat)
		  (and (process-stat proc stat)
		       (read-string-elt= stat 22 parent))))
  :size ((output output) 100 (input output) 1))
(defrelation process-rss
  :definition ((proc parent) s.t.
	       (E (stat)
		  (and (process-stat proc stat)
		       (read-string-elt= stat 23 parent))))
  :size ((output output) 100 (input output) 1))

(defrelation process-cmdline :derivation individual-derived
  :documentation "relates integer process id to readline of /proc/<id>/cmdline"
  :arity 2 :types (integer string)  :equivs (= equal)
  :nonatomic t
  :type-enforcements (:none)
  :tester
  (lambda (&rest ignore) (declare (ignore ignore))
    '(lambda (rel p stat)
       (and (integerp p) (> p 0)
	    (ignore-errors
	      (string= stat (with-open-file (f (format nil "/proc/~a/cmdline" p))
			      (read-line f)))))))
  :testeffort 1000 ;; just a wild guess for now
  :generator
  ((simplegenerator
    (p output)
    (and (integerp p) (> p 0)
	 (ignore-errors
	   (with-open-file (f (format nil "/proc/~a/cmdline" p))
	     (read-line f))))
    1e3)
   (simplemultiplegenerator
    (output output)
    (loop for f in (directory "/proc/*/cmdline" :if-does-not-exist :ignore)
      ;; if does not exist needed to prevent error from /proc/net/stat/
      as p = (ignore-errors (parse-integer (car (last (pathname-directory f)))))
      as stat = (ignore-errors (with-open-file (file f) (read-line file)))
      when (integerp p) when (> p 0) when stat collect (list p stat))
    1e5))
  :size ((output output) 100 (input output) 1))
