Proper handling of Apache log file deletion while Apache might have that file open
[hcoop/domtool2.git] / src / plugins / apache.sml
index ffd18b2..83a8ce8 100644 (file)
@@ -22,6 +22,17 @@ structure Apache :> APACHE = struct
 
 open Ast
 
+val _ = Env.type_one "web_node"
+       Env.string
+       (fn node =>
+           List.exists (fn x => x = node) Config.Apache.webNodes_all
+           orelse (Domain.hasPriv "www"
+                   andalso List.exists (fn x => x = node) Config.Apache.webNodes_admin))
+
+val _ = Env.registerFunction ("web_node_to_node",
+                             fn [e] => SOME e
+                              | _ => NONE)
+
 val _ = Env.type_one "proxy_port"
        Env.int
        (fn n => n > 1024)
@@ -62,8 +73,8 @@ val _ = Env.type_one "location"
 val dl = ErrorMsg.dummyLoc
 
 val _ = Defaults.registerDefault ("WebNodes",
-                                 (TList (TBase "node", dl), dl),
-                                 (fn () => (EList (map (fn s => (EString s, dl)) Config.Apache.webNodes), dl)))
+                                 (TList (TBase "web_node", dl), dl),
+                                 (fn () => (EList (map (fn s => (EString s, dl)) Config.Apache.webNodes_default), dl)))
 
 val _ = Defaults.registerDefault ("SSL",
                                  (TBase "bool", dl),
@@ -79,7 +90,7 @@ val _ = Defaults.registerDefault ("Group",
 
 val _ = Defaults.registerDefault ("DocumentRoot",
                                  (TBase "your_path", dl),
-                                 (fn () => (EString (Config.homeBase ^ "/" ^ Domain.getUser () ^ "/public_html"), dl)))
+                                 (fn () => (EString (Config.homeBase ^ "/" ^ Domain.getUser () ^ "/" ^ Config.Apache.public_html), dl)))
 
 val _ = Defaults.registerDefault ("ServerAdmin",
                                  (TBase "email", dl),
@@ -169,9 +180,27 @@ val autoindex_option = fn (EApp ((EVar "descriptionWidth", _), w), _) =>
                        | _ => NONE
 
 val vhostsChanged = ref false
+val logDeleted = ref false
 
 val () = Slave.registerPreHandler
-            (fn () => vhostsChanged := false)
+            (fn () => (vhostsChanged := false;
+                       logDeleted := false))
+
+fun findVhostUser fname =
+    let
+       val inf = TextIO.openIn fname
+
+       fun loop () =
+           case TextIO.inputLine inf of
+               NONE => NONE
+             | SOME line =>
+               case String.tokens Char.isSpace line of
+                   ["SuexecUserGroup", user, _] => SOME user
+                 | _ => loop ()
+    in
+       loop ()
+       before TextIO.closeIn inf
+    end
 
 val () = Slave.registerFileHandler (fn fs =>
                                       let
@@ -179,29 +208,63 @@ val () = Slave.registerFileHandler (fn fs =>
                                       in
                                           if String.isSuffix ".vhost" (#file spl)
                                              orelse String.isSuffix ".vhost_ssl" (#file spl) then
-                                              (vhostsChanged := true;
-                                               case #action fs of
-                                                   Slave.Delete =>
-                                                   (ignore (OS.Process.system (Config.rm
-                                                                               ^ " -rf "
-                                                                               ^ Config.Apache.confDir
-                                                                               ^ "/"
-                                                                               ^ #file spl));
-                                                    ignore (OS.Process.system (Config.rm
-                                                                               ^ " -rf "
-                                                                               ^ Config.Apache.logDir
-                                                                               ^ "/"
-                                                                               ^ #base (OS.Path.splitBaseExt 
-                                                                                            (#file spl)))))
-                                                           
-                                                 | _ =>
-                                                   ignore (OS.Process.system (Config.cp
-                                                                              ^ " "
-                                                                              ^ #file fs
-                                                                              ^ " "
-                                                                              ^ Config.Apache.confDir
-                                                                              ^ "/"
-                                                                              ^ #file spl)))
+                                              case findVhostUser (#file fs) of
+                                                  NONE => print ("Can't find user in " ^ #file fs ^ "!  Taking no action.\n")
+                                                | SOME user =>
+                                                  let
+                                                      val realVhostFile = OS.Path.joinDirFile
+                                                                              {dir = Config.Apache.confDir,
+                                                                               file = #file spl}
+
+                                                      val realLogDir = OS.Path.joinDirFile
+                                                                           {dir = Config.homeBase,
+                                                                            file = user}
+                                                      val realLogDir = OS.Path.joinDirFile
+                                                                           {dir = realLogDir,
+                                                                            file = "apache"}
+                                                      val realLogDir = OS.Path.joinDirFile
+                                                                           {dir = realLogDir,
+                                                                            file = "log"}
+                                                      val realLogDir = OS.Path.joinDirFile
+                                                                           {dir = realLogDir,
+                                                                            file = Slave.hostname ()}
+                                                      val {base, ...} = OS.Path.splitBaseExt (#file spl)
+                                                      val realLogDir = OS.Path.joinDirFile
+                                                                           {dir = realLogDir,
+                                                                            file = base}
+                                                  in
+                                                      vhostsChanged := true;
+                                                      case #action fs of
+                                                          Slave.Delete =>
+                                                          (if !logDeleted then
+                                                               ()
+                                                           else
+                                                               (ignore (OS.Process.system Config.Apache.down);
+                                                                logDeleted := true);
+                                                           ignore (OS.Process.system (Config.rm
+                                                                                      ^ " -rf "
+                                                                                      ^ realVhostFile));
+                                                           ignore (OS.Process.system (Config.rm
+                                                                                      ^ " -rf "
+                                                                                      ^ realLogDir)))
+                                                        | Slave.Add =>
+                                                          (ignore (OS.Process.system (Config.cp
+                                                                                      ^ " "
+                                                                                      ^ #file fs
+                                                                                      ^ " "
+                                                                                      ^ realVhostFile));
+                                                           if Posix.FileSys.access (realLogDir, []) then
+                                                               ()
+                                                           else
+                                                               OS.FileSys.mkDir realLogDir)
+                                                          
+                                                        | _ =>
+                                                          ignore (OS.Process.system (Config.cp
+                                                                                     ^ " "
+                                                                                     ^ #file fs
+                                                                                     ^ " "
+                                                                                     ^ realVhostFile))
+                                                  end
                                           else
                                               ()
                                       end)
@@ -209,19 +272,21 @@ val () = Slave.registerFileHandler (fn fs =>
 val () = Slave.registerPostHandler
         (fn () =>
             (if !vhostsChanged then
-                 Slave.shellF ([Config.Apache.reload],
+                 Slave.shellF ([if !logDeleted then Config.Apache.undown else Config.Apache.reload],
                                fn cl => "Error reloading Apache with " ^ cl)
              else
                  ()))
 
-val vhostFiles : TextIO.outstream list ref = ref []
-fun write s = app (fn file => TextIO.output (file, s)) (!vhostFiles)
+val vhostFiles : (string * TextIO.outstream) list ref = ref []
+fun write' s = app (fn (node, file) => TextIO.output (file, s node)) (!vhostFiles)
+fun write s = app (fn (_, file) => TextIO.output (file, s)) (!vhostFiles)
 
 val rewriteEnabled = ref false
+val localRewriteEnabled = ref false
 val currentVhost = ref ""
 val currentVhostId = ref ""
 
-val pre = ref (fn _ : {nodes : string list, id : string, hostname : string} => ())
+val pre = ref (fn _ : {user : string, nodes : string list, id : string, hostname : string} => ())
 fun registerPre f =
     let
        val old = !pre
@@ -265,6 +330,7 @@ val () = Env.containerV_one "vhost"
                 currentVhostId := vhostId;
 
                 rewriteEnabled := false;
+                localRewriteEnabled := false;
                 vhostFiles := map (fn node =>
                                       let
                                           val file = Domain.domainFile {node = node,
@@ -278,7 +344,25 @@ val () = Env.containerV_one "vhost"
                                                                else
                                                                    "80");
                                           TextIO.output (file, ">\n");
-                                          file
+                                          TextIO.output (file, "\tErrorLog ");
+                                          TextIO.output (file, Config.homeBase);
+                                          TextIO.output (file, "/");
+                                          TextIO.output (file, user);
+                                          TextIO.output (file, "/apache/log/");
+                                          TextIO.output (file, node);
+                                          TextIO.output (file, "/");
+                                          TextIO.output (file, vhostId);
+                                          TextIO.output (file, "/error.log\n\tCustomLog ");
+                                          TextIO.output (file, Config.homeBase);
+                                          TextIO.output (file, "/");
+                                          TextIO.output (file, user);
+                                          TextIO.output (file, "/apache/log/");
+                                          TextIO.output (file, node);
+                                          TextIO.output (file, "/");
+                                          TextIO.output (file, vhostId);
+                                          TextIO.output (file, "/access.log combined\n");
+                                          (Config.homeBase ^ "/" ^ user ^ "/apache/log/"
+                                           ^ node ^ "/" ^ vhostId, file)
                                       end)
                                   nodes;
                 write "\tServerName ";
@@ -291,39 +375,45 @@ val () = Env.containerV_one "vhost"
                 write docroot;
                 write "\n\tServerAdmin ";
                 write sadmin;
-                write "\n\tErrorLog ";
-                write Config.Apache.logDir;
-                write "/";
-                write vhostId;
-                write "/error.log\n\tCustomLog ";
-                write Config.Apache.logDir;
-                write "/";
-                write vhostId;
-                write "/access.log combined\n";
-                !pre {nodes = nodes, id = vhostId, hostname = fullHost}
+                write "\n";
+                !pre {user = user, nodes = nodes, id = vhostId, hostname = fullHost}
             end,
          fn () => (!post ();
                    write "</VirtualHost>\n";
-                   app TextIO.closeOut (!vhostFiles)))
+                   app (TextIO.closeOut o #2) (!vhostFiles)))
+
+val inLocal = ref false
 
 val () = Env.container_one "location"
         ("prefix", Env.string)
         (fn prefix =>
             (write "\t<Location ";
              write prefix;
-             write ">\n"),
-         fn () => write "\t</Location>\n")
+             write ">\n";
+             inLocal := true),
+         fn () => (write "\t</Location>\n";
+                   inLocal := false;
+                   localRewriteEnabled := false))
 
 val () = Env.container_one "directory"
         ("directory", Env.string)
         (fn directory =>
             (write "\t<Directory ";
              write directory;
-             write ">\n"),
-         fn () => write "\t</Directory>\n")
+             write ">\n";
+             inLocal := true),
+         fn () => (write "\t</Directory>\n";
+                   inLocal := false;
+                   localRewriteEnabled := false))
 
 fun checkRewrite () =
-    if !rewriteEnabled then
+    if !inLocal then
+       if !rewriteEnabled orelse !localRewriteEnabled then
+           ()
+       else
+           (write "\tRewriteEngine on\n";
+            localRewriteEnabled := true)
+    else if !rewriteEnabled then
        ()
     else
        (write "\tRewriteEngine on\n";
@@ -398,9 +488,7 @@ val () = Env.action_one "rewriteLogLevel"
         (fn level =>
             (checkRewrite ();
              write "\tRewriteLog ";
-             write Config.Apache.logDir;
-             write "/";
-             write (!currentVhostId);
+             write' (fn x => x);
              write "/rewrite.log\n\tRewriteLogLevel ";
              write (Int.toString level);
              write "\n"))
@@ -579,7 +667,7 @@ val () = Env.action_one "addDefaultCharset"
                    write ty;
                    write "\n"))
 
-val () = Env.action_one "davSvn"
+(*val () = Env.action_one "davSvn"
         ("path", Env.string)
         (fn path => (write "\tDAV svn\n\tSVNPath ";
                      write path;
@@ -589,7 +677,7 @@ val () = Env.action_one "authzSvnAccessFile"
         ("path", Env.string)
         (fn path => (write "\tAuthzSVNAccessFile ";
                      write path;
-                     write "\n"))
+                     write "\n"))*)
 
 val () = Env.action_two "addDescription"
         ("description", Env.string, "patterns", Env.list Env.string)