Commit 8218141a175a16651a9a1daa58dbbd549cf4d379

Thomas de Grivel 2015-09-23T06:35:50

Unix probes.

diff --git a/adams.asd b/adams.asd
index 622cb52..0672ec4 100644
--- a/adams.asd
+++ b/adams.asd
@@ -63,6 +63,7 @@
 	    ((:file "commands")
 	     (:file "defs")
 	     (:file "openbsd" :depends-on ("commands" "defs"))
+	     (:file "operations" :depends-on ("commands" "defs"))
 	     (:file "probes"  :depends-on ("commands" "defs"
 					   "stat" "syntaxes"))
 	     (:file "stat")
diff --git a/unix/operations.lisp b/unix/operations.lisp
new file mode 100644
index 0000000..69150f9
--- /dev/null
+++ b/unix/operations.lisp
@@ -0,0 +1,85 @@
+;;
+;;  adams  -  Remote system administration tools
+;;
+;;  Copyright 2013,2014 Thomas de Grivel <thomas@lowh.net>
+;;
+;;  Permission to use, copy, modify, and distribute this software for any
+;;  purpose with or without fee is hereby granted, provided that the above
+;;  copyright notice and this permission notice appear in all copies.
+;;
+;;  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+;;  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+;;  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+;;  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+;;  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+;;  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+;;  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+;;
+
+(in-package :adams)
+
+(defun run-as-root (&rest command)
+  (apply #'run
+         (unless (equal "root" (get-probed (current-host) :user))
+           "sudo ")
+         command))
+
+;;  Host operations
+
+(defmethod op-hostname ((host host) (os os-unix) &key hostname)
+  (run-as-root "hostname -s " (sh-quote hostname)))
+
+;;  Group
+
+(defmethod op-update-group ((group group) (os os-unix) &key ensure gid)
+  (run-as-root
+   (join-str " "
+             (ecase ensure
+               ((:absent)  "groupdel")
+               ((:present) "groupadd")
+               ((nil)      "groupmod"))
+             (when gid `("-g" ,(sh-quote gid)))
+             (sh-quote (resource-id group)))))
+
+;;  User
+
+(defmethod op-update-user ((user user) (os os-unix) &key ensure uid gid
+                                                      realname home shell
+                                                      login-class
+                                                      groups)
+  (run-as-root
+   (join-str " "
+             (ecase ensure
+               ((:absent)  "userdel")
+               ((:present) "useradd")
+               ((nil)      "usermod"))
+             (when realname `("-c" ,(sh-quote realname)))
+             (when home `("-d" ,(sh-quote home)))
+             (when gid `("-g" ,(sh-quote gid)))
+             (when login-class `("-L" ,(sh-quote login-class)))
+             (when groups `("-S" ,(join-str "," (mapcar #'sh-quote groups))))
+             (when shell `("-s" ,(sh-quote shell)))
+             (when uid `("-u" ,(sh-quote uid)))
+             (sh-quote (resource-id user)))))
+
+;;  VNode
+
+(defmethod op-chown ((res vnode) (os os-unix) &key uid gid owner group
+                                                &allow-other-keys)
+  (when (stringp owner)
+    (setq owner (resource 'user owner)))
+  (when (stringp group)
+    (setq group (resource 'group group)))
+  (when (and uid owner)
+    (assert (= uid (get-probed owner :uid))))
+  (when (and gid group)
+    (assert (= gid (get-probed group :gid))))
+  (let ((u (or (when owner (resource-id owner))
+               uid))
+        (g (or (when group (resource-id group))
+               gid)))
+    (run "chown " u (when g `(":" ,g)) " " (resource-id res))))
+
+(defmethod op-chmod ((res vnode) (os os-unix) &key mode
+                                                &allow-other-keys)
+  (run "chmod " (octal (mode-permissions mode)) " " (sh-quote (resource-id res))))
diff --git a/unix/probes.lisp b/unix/probes.lisp
index 0440fbc..ed3793f 100644
--- a/unix/probes.lisp
+++ b/unix/probes.lisp
@@ -21,19 +21,22 @@
 ;;  Group
 
 (defmethod probe-group-in-/etc/group ((group group) (os os-unix))
-  (let ((id (resource-id group)))
+  (let ((id (resource-id group))
+        (ensure :absent))
     (multiple-value-bind #1=(name passwd gid members)
       (iter (group<5> #1# in (grep (str id) "/etc/group"))
             (when (etypecase id
                     (integer (= id gid))
                     (string (string= id name)))
+              (setq ensure nil)
               (return (values* #1#))))
-      (properties* #1#))))
+      (properties* (ensure . #1#)))))
 
 ;;  User
 
 (defmethod probe-user-in-/etc/passwd ((user user) (os os-unix))
-  (let ((id (resource-id user)))
+  (let ((id (resource-id user))
+        (ensure :absent))
     (multiple-value-bind #1=(login pass uid gid realname home shell)
       (iter (passwd<5> #1# in
                        (etypecase id
@@ -42,28 +45,31 @@
             (when (etypecase id
                     (string (string= id login))
                     (integer (= id uid)))
-              (setq home (resource 'directory home)
+              (setq ensure nil
+                    home (resource 'directory home)
                     shell (resource 'file shell))
               (return (values* #1#))))
-      (properties* #1#))))
+      (properties* (ensure . #1#)))))
 
 (defmethod probe-user-groups-in-/etc/group ((user user) (os os-unix))
-  (let* ((id (resource-id user))
-	 (user-login (if (stringp id)
-			 id
-			 (get-probed user :login)))
-	 (user-gid (get-probed user :gid))
-         (user-group nil)
-         (groups (iter (group<5> (name passwd gid members)
-                                 in (grep user-login "/etc/group"))
-                       (cond ((= user-gid gid)
-                              (setq user-group (resource 'group name)))
-                             ((find user-login members :test #'string=)
-                              (collect (resource 'group name))))))
-         (groups (sort groups #'string< :key #'resource-id))
-         (groups (if user-group
-                     (cons user-group groups)
-                     groups)))
+  (let ((id (resource-id user))
+        groups)
+    (unless (eq :absent (get-probed user :ensure))
+      (let* ((user-login (if (stringp id)
+                             id
+                             (get-probed user :login)))
+             (user-gid (get-probed user :gid))
+             (user-group nil))
+        (setq groups (iter (group<5> (name passwd gid members)
+                                     in (grep user-login "/etc/group"))
+                           (cond ((= user-gid gid)
+                                  (setq user-group (resource 'group name)))
+                                 ((find user-login members :test #'string=)
+                                  (collect (resource 'group name)))))
+              groups (sort groups #'string< :key #'resource-id)
+              groups (if user-group
+                         (cons user-group groups)
+                         groups))))
     (properties* groups)))
 
 ;;  VNode (filesystem node)
diff --git a/unix/syntaxes.lisp b/unix/syntaxes.lisp
index f5c15d2..8dd14a8 100644
--- a/unix/syntaxes.lisp
+++ b/unix/syntaxes.lisp
@@ -90,3 +90,45 @@
                         cmd)
     #~|^\s*(\S+)\s+([0-9]+)\s+([0-9.]+)\s+([0-9.]+)\s+([0-9]+)\s+([0-9]+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.*)$|
   "Syntax for ps -u, see ps(1).")
+
+(define-constant +sh-whitespace+
+    (str #\Space #\Tab #\Newline)
+  :test 'equal)
+
+(define-constant +sh-meta+
+    "<>|;()&"
+  :test 'equal)
+
+(define-constant +sh-word-delimiters+
+    (str +sh-whitespace+ +sh-meta+)
+  :test 'equal)
+
+(defun sh-word-delimiter-p (char)
+  (find char +sh-word-delimiters+ :test #'eq))
+
+(defun parse-sh-var-value (string)
+  (with-output-to-string (out)
+    (iter (with quote = nil)
+          (with backslash = nil)
+          (for i below (length string))
+          (for c = (char string i))
+          (cond
+            (backslash
+             (setq backslash nil)
+             (write-char c out))
+            ((and (eq #\\ c) (not (eq #\' quote)))
+             (setq backslash t))
+            ((eq quote c)
+             (setq quote nil))
+            ((and (null quote) (or (eq #\" c) (eq #\' c)))
+             (setq quote c))
+            ((and (null quote) (sh-word-delimiter-p c))
+             (finish))
+            (:otherwise
+             (write-char c out)))
+          (finally
+           (when (or quote backslash)
+             (error "Unmatched quote"))))))
+
+(define-syntax sh-var (var (#'parse-sh-var-value value))
+    #~|^\s*(\w*)=(.*)|)