rebase -i: rewrite setup_reflog_action() in C
[gitweb.git] / daemon.c
index 30747075f0c45942220f63202f8c984e3e0956a7..9d2e0d20ef302aaeac02aaeba791509dd43a2712 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -9,7 +9,12 @@
 #define initgroups(x, y) (0) /* nothing */
 #endif
 
-static int log_syslog;
+static enum log_destination {
+       LOG_DESTINATION_UNSET = -1,
+       LOG_DESTINATION_NONE = 0,
+       LOG_DESTINATION_STDERR = 1,
+       LOG_DESTINATION_SYSLOG = 2,
+} log_destination = LOG_DESTINATION_UNSET;
 static int verbose;
 static int reuseaddr;
 static int informative_errors;
@@ -25,6 +30,7 @@ static const char daemon_usage[] =
 "           [--access-hook=<path>]\n"
 "           [--inetd | [--listen=<host_or_ipaddr>] [--port=<n>]\n"
 "                      [--detach] [--user=<user> [--group=<group>]]\n"
+"           [--log-destination=(stderr|syslog|none)]\n"
 "           [<directory>...]";
 
 /* List of acceptable pathname prefixes */
@@ -74,11 +80,14 @@ static const char *get_ip_address(struct hostinfo *hi)
 
 static void logreport(int priority, const char *err, va_list params)
 {
-       if (log_syslog) {
+       switch (log_destination) {
+       case LOG_DESTINATION_SYSLOG: {
                char buf[1024];
                vsnprintf(buf, sizeof(buf), err, params);
                syslog(priority, "%s", buf);
-       } else {
+               break;
+       }
+       case LOG_DESTINATION_STDERR:
                /*
                 * Since stderr is set to buffered mode, the
                 * logging of different processes will not overlap
@@ -88,6 +97,11 @@ static void logreport(int priority, const char *err, va_list params)
                vfprintf(stderr, err, params);
                fputc('\n', stderr);
                fflush(stderr);
+               break;
+       case LOG_DESTINATION_NONE:
+               break;
+       case LOG_DESTINATION_UNSET:
+               BUG("log destination not initialized correctly");
        }
 }
 
@@ -282,7 +296,7 @@ static const char *path_ok(const char *directory, struct hostinfo *hi)
        return NULL;            /* Fallthrough. Deny by default */
 }
 
-typedef int (*daemon_service_fn)(void);
+typedef int (*daemon_service_fn)(const struct argv_array *env);
 struct daemon_service {
        const char *name;
        const char *config_name;
@@ -363,7 +377,7 @@ static int run_access_hook(struct daemon_service *service, const char *dir,
 }
 
 static int run_service(const char *dir, struct daemon_service *service,
-                      struct hostinfo *hi)
+                      struct hostinfo *hi, const struct argv_array *env)
 {
        const char *path;
        int enabled = service->enabled;
@@ -422,7 +436,7 @@ static int run_service(const char *dir, struct daemon_service *service,
         */
        signal(SIGTERM, SIG_IGN);
 
-       return service->fn();
+       return service->fn(env);
 }
 
 static void copy_to_log(int fd)
@@ -462,25 +476,34 @@ static int run_service_command(struct child_process *cld)
        return finish_command(cld);
 }
 
-static int upload_pack(void)
+static int upload_pack(const struct argv_array *env)
 {
        struct child_process cld = CHILD_PROCESS_INIT;
        argv_array_pushl(&cld.args, "upload-pack", "--strict", NULL);
        argv_array_pushf(&cld.args, "--timeout=%u", timeout);
+
+       argv_array_pushv(&cld.env_array, env->argv);
+
        return run_service_command(&cld);
 }
 
-static int upload_archive(void)
+static int upload_archive(const struct argv_array *env)
 {
        struct child_process cld = CHILD_PROCESS_INIT;
        argv_array_push(&cld.args, "upload-archive");
+
+       argv_array_pushv(&cld.env_array, env->argv);
+
        return run_service_command(&cld);
 }
 
-static int receive_pack(void)
+static int receive_pack(const struct argv_array *env)
 {
        struct child_process cld = CHILD_PROCESS_INIT;
        argv_array_push(&cld.args, "receive-pack");
+
+       argv_array_pushv(&cld.env_array, env->argv);
+
        return run_service_command(&cld);
 }
 
@@ -573,8 +596,11 @@ static void canonicalize_client(struct strbuf *out, const char *in)
 
 /*
  * Read the host as supplied by the client connection.
+ *
+ * Returns a pointer to the character after the NUL byte terminating the host
+ * arguemnt, or 'extra_args' if there is no host arguemnt.
  */
-static void parse_host_arg(struct hostinfo *hi, char *extra_args, int buflen)
+static char *parse_host_arg(struct hostinfo *hi, char *extra_args, int buflen)
 {
        char *val;
        int vallen;
@@ -585,6 +611,7 @@ static void parse_host_arg(struct hostinfo *hi, char *extra_args, int buflen)
                if (strncasecmp("host=", extra_args, 5) == 0) {
                        val = extra_args + 5;
                        vallen = strlen(val) + 1;
+                       loginfo("Extended attribute \"host\": %s", val);
                        if (*val) {
                                /* Split <host>:<port> at colon. */
                                char *host;
@@ -602,6 +629,45 @@ static void parse_host_arg(struct hostinfo *hi, char *extra_args, int buflen)
                if (extra_args < end && *extra_args)
                        die("Invalid request");
        }
+
+       return extra_args;
+}
+
+static void parse_extra_args(struct hostinfo *hi, struct argv_array *env,
+                            char *extra_args, int buflen)
+{
+       const char *end = extra_args + buflen;
+       struct strbuf git_protocol = STRBUF_INIT;
+
+       /* First look for the host argument */
+       extra_args = parse_host_arg(hi, extra_args, buflen);
+
+       /* Look for additional arguments places after a second NUL byte */
+       for (; extra_args < end; extra_args += strlen(extra_args) + 1) {
+               const char *arg = extra_args;
+
+               /*
+                * Parse the extra arguments, adding most to 'git_protocol'
+                * which will be used to set the 'GIT_PROTOCOL' envvar in the
+                * service that will be run.
+                *
+                * If there ends up being a particular arg in the future that
+                * git-daemon needs to parse specificly (like the 'host' arg)
+                * then it can be parsed here and not added to 'git_protocol'.
+                */
+               if (*arg) {
+                       if (git_protocol.len > 0)
+                               strbuf_addch(&git_protocol, ':');
+                       strbuf_addstr(&git_protocol, arg);
+               }
+       }
+
+       if (git_protocol.len > 0) {
+               loginfo("Extended attribute \"protocol\": %s", git_protocol.buf);
+               argv_array_pushf(env, GIT_PROTOCOL_ENVIRONMENT "=%s",
+                                git_protocol.buf);
+       }
+       strbuf_release(&git_protocol);
 }
 
 /*
@@ -695,6 +761,7 @@ static int execute(void)
        int pktlen, len, i;
        char *addr = getenv("REMOTE_ADDR"), *port = getenv("REMOTE_PORT");
        struct hostinfo hi;
+       struct argv_array env = ARGV_ARRAY_INIT;
 
        hostinfo_init(&hi);
 
@@ -707,17 +774,12 @@ static int execute(void)
        alarm(0);
 
        len = strlen(line);
-       if (pktlen != len)
-               loginfo("Extended attributes (%d bytes) exist <%.*s>",
-                       (int) pktlen - len,
-                       (int) pktlen - len, line + len + 1);
-       if (len && line[len-1] == '\n') {
-               line[--len] = 0;
-               pktlen--;
-       }
+       if (len && line[len-1] == '\n')
+               line[len-1] = 0;
 
+       /* parse additional args hidden behind a NUL byte */
        if (len != pktlen)
-               parse_host_arg(&hi, line + len + 1, pktlen - len - 1);
+               parse_extra_args(&hi, &env, line + len + 1, pktlen - len - 1);
 
        for (i = 0; i < ARRAY_SIZE(daemon_service); i++) {
                struct daemon_service *s = &(daemon_service[i]);
@@ -730,13 +792,15 @@ static int execute(void)
                         * Note: The directory here is probably context sensitive,
                         * and might depend on the actual service being performed.
                         */
-                       int rc = run_service(arg, s, &hi);
+                       int rc = run_service(arg, s, &hi, &env);
                        hostinfo_clear(&hi);
+                       argv_array_clear(&env);
                        return rc;
                }
        }
 
        hostinfo_clear(&hi);
+       argv_array_clear(&env);
        logerror("Protocol error: '%s'", line);
        return -1;
 }
@@ -1236,7 +1300,6 @@ int cmd_main(int argc, const char **argv)
                }
                if (!strcmp(arg, "--inetd")) {
                        inetd_mode = 1;
-                       log_syslog = 1;
                        continue;
                }
                if (!strcmp(arg, "--verbose")) {
@@ -1244,9 +1307,22 @@ int cmd_main(int argc, const char **argv)
                        continue;
                }
                if (!strcmp(arg, "--syslog")) {
-                       log_syslog = 1;
+                       log_destination = LOG_DESTINATION_SYSLOG;
                        continue;
                }
+               if (skip_prefix(arg, "--log-destination=", &v)) {
+                       if (!strcmp(v, "syslog")) {
+                               log_destination = LOG_DESTINATION_SYSLOG;
+                               continue;
+                       } else if (!strcmp(v, "stderr")) {
+                               log_destination = LOG_DESTINATION_STDERR;
+                               continue;
+                       } else if (!strcmp(v, "none")) {
+                               log_destination = LOG_DESTINATION_NONE;
+                               continue;
+                       } else
+                               die("unknown log destination '%s'", v);
+               }
                if (!strcmp(arg, "--export-all")) {
                        export_all_trees = 1;
                        continue;
@@ -1303,7 +1379,6 @@ int cmd_main(int argc, const char **argv)
                }
                if (!strcmp(arg, "--detach")) {
                        detach = 1;
-                       log_syslog = 1;
                        continue;
                }
                if (skip_prefix(arg, "--user=", &v)) {
@@ -1349,7 +1424,14 @@ int cmd_main(int argc, const char **argv)
                usage(daemon_usage);
        }
 
-       if (log_syslog) {
+       if (log_destination == LOG_DESTINATION_UNSET) {
+               if (inetd_mode || detach)
+                       log_destination = LOG_DESTINATION_SYSLOG;
+               else
+                       log_destination = LOG_DESTINATION_STDERR;
+       }
+
+       if (log_destination == LOG_DESTINATION_SYSLOG) {
                openlog("git-daemon", LOG_PID, LOG_DAEMON);
                set_die_routine(daemon_die);
        } else
@@ -1377,7 +1459,7 @@ int cmd_main(int argc, const char **argv)
                die("base-path '%s' does not exist or is not a directory",
                    base_path);
 
-       if (inetd_mode) {
+       if (log_destination != LOG_DESTINATION_STDERR) {
                if (!freopen("/dev/null", "w", stderr))
                        die_errno("failed to redirect stderr to /dev/null");
        }