Download raw body.
add gotwebctl(8)
Hello,
Sorry for the delay.
Stefan Sperling <stsp@stsp.name> wrote:
> This patch adds a new utility called gotwebctl(8). It communicates with
> gotwebd(8) via a new control socket which only accepts connections from
> the root user.
>
> In this initial version only 2 commands are supported: info and stop
>
> The info command reports the process ID of the gotwebd parent process
> and the current verbosity level.
>
> The stop command stops gotwebd gracefully.
> Earlier today I committed a change which makes gotwebd stop gracefully
> when a TERM signal is received, closing listening sockets while still
> allowing existing connections to process to completion before terminating.
>
> I will need this functionality in order to control gotwebd from gotsysd.
> Making this command available via a unix socket avoids having to find
> the process ID of the running gotwebd instance. And allowing gotwebd
> to stop gracefully minimizes the changes of service disruption whenever
> gotsysd needs to restart gotwebd to apply configuration changes.
>
> And I believe having this new tool is valuable in itself.
> We could extend it with more functionality later. The tool could report
> additional run-time information, change the verbository level on the fly,
> or whatever else we come up with.
>
> ok?
looks good to me. ok op@
maybe just remove
> [...]
> --- /dev/null
> +++ gotwebctl/gotwebctl.c
> [...]
> +#define GOTCTL_CMD_INFO "info"
> +#define GOTCTL_CMD_STOP "stop"
because they seem unused.
Also, this was missing as well i think:
--- Makefile
+++ Makefile
@@ -7,7 +7,7 @@ SUBDIR += regress
.endif
.if make(clean) || make(obj) || make(release)
-SUBDIR += gotwebd gotd gotsh gotctl template gitwrapper
+SUBDIR += gotwebd gotwebctl gotd gotsh gotctl template gitwrapper
SUBDIR += gotsysd gotsys gotsysctl
.endif
Oh, there is a conflict in gotwebd/parse.y now but was trivial to fix :P
If it saves you some minutes, I'm reattaching here your diff rebased and
with the points above addressed.
diff /home/op/w/got
path + /home/op/w/got
commit - f5e64618f74a49a9167bec1f7db97000c2b8557f
blob - 59785cb66e3647ab727137e1f1d919208c5848c9
file + Makefile
--- Makefile
+++ Makefile
@@ -7,7 +7,7 @@ SUBDIR += regress
.endif
.if make(clean) || make(obj) || make(release)
-SUBDIR += gotwebd gotd gotsh gotctl template gitwrapper
+SUBDIR += gotwebd gotwebctl gotd gotsh gotctl template gitwrapper
SUBDIR += gotsysd gotsys gotsysctl
.endif
@@ -45,9 +45,11 @@ tmpl-regress:
${MAKE} -C regress/template
webd: tmpl
+ ${MAKE} -C gotwebctl
${MAKE} -C gotwebd
webd-install:
+ ${MAKE} -C gotwebctl install
${MAKE} -C gotwebd install
server:
commit - f5e64618f74a49a9167bec1f7db97000c2b8557f
blob - /dev/null
file + gotwebctl/Makefile (mode 644)
--- /dev/null
+++ gotwebctl/Makefile
@@ -0,0 +1,29 @@
+.PATH:${.CURDIR}/../lib ${.CURDIR}/../gotwebd
+
+.include "../got-version.mk"
+
+PROG= gotwebctl
+SRCS= gotwebctl.c error.c hash.c
+
+MAN = ${PROG}.8
+
+CPPFLAGS = -I${.CURDIR}/../include -I${.CURDIR}/../lib -I${.CURDIR}/../gotwebd
+
+.if defined(PROFILE)
+LDADD = -lutil_p -levent_p
+.else
+LDADD = -lutil -levent
+.endif
+DPADD = ${LIBUTIL} ${LIBEVENT}
+
+.if ${GOT_RELEASE} != "Yes"
+NOMAN = Yes
+.else
+BINDIR = ${PREFIX}/sbin
+.endif
+
+realinstall:
+ ${INSTALL} ${INSTALL_COPY} -o ${BINOWN} -g ${BINGRP} \
+ -m ${BINMODE} ${PROG} ${BINDIR}/${PROG}
+
+.include <bsd.prog.mk>
commit - f5e64618f74a49a9167bec1f7db97000c2b8557f
blob - /dev/null
file + gotwebctl/gotwebctl.8 (mode 644)
--- /dev/null
+++ gotwebctl/gotwebctl.8
@@ -0,0 +1,85 @@
+.\"
+.\" Copyright (c) 2026 Stefan Sperling
+.\"
+.\" 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.
+.\"
+.Dd $Mdocdate$
+.Dt GOTWEBCTL 8
+.Os
+.Sh NAME
+.Nm gotwebctl
+.Nd control the Game of Trees Web Daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl hV
+.Op Fl f Ar path
+.Ar command
+.Op Ar arg ...
+.Sh DESCRIPTION
+.Nm
+controls the
+.Xr gotwebd 8
+daemon.
+.Pp
+Access to the
+.Xr gotwebd 8
+control socket requires root privileges.
+.Pp
+The options for
+.Nm
+are as follows:
+.Bl -tag -width Ds
+.It Fl h
+Display usage information and exit immediately.
+.It Fl f Ar path
+Set the
+.Ar path
+to the control socket which
+.Xr gotwebd 8
+is listening on.
+If not specified, the default path
+.Pa /var/run/gotwebd.sock
+will be used.
+.It Fl V , -version
+Display program version and exit immediately.
+.El
+.Pp
+The commands for
+.Nm
+are as follows:
+.Bl -tag -width Ds
+.It Cm info
+Display information about a running
+.Xr gotwebd 8
+instance.
+.It Cm stop
+Ask a running
+.Xr gotwebd 8
+instance to gracefully stop itself.
+This is equivalent to sending a TERM signal to
+.Xr gotwebd 8
+without requiring knowledge of the process ID to send the signal to.
+.Pp
+The control socket will be closed by
+.Xr gotwebd 8 ,
+allowing a new instance of
+.Xr gotwebd 8
+to take over.
+New FastCGI socket connections will no longer be accepted, but FastCGI
+requests currently in progress are still processed to completion.
+.Sh SEE ALSO
+.Xr got 1 ,
+.Xr gotwebd.conf 5 ,
+.Xr gotwebd 8
+.Sh AUTHORS
+.An Stefan Sperling Aq Mt stsp@openbsd.org
commit - f5e64618f74a49a9167bec1f7db97000c2b8557f
blob - /dev/null
file + gotwebctl/gotwebctl.c (mode 644)
--- /dev/null
+++ gotwebctl/gotwebctl.c
@@ -0,0 +1,350 @@
+/*
+ * Copyright (c) 2026 Stefan Sperling <stsp@openbsd.org>
+ *
+ * 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.
+ */
+
+#include <sys/queue.h>
+#include <sys/tree.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <imsg.h>
+#include <limits.h>
+#include <locale.h>
+#include <sha1.h>
+#include <sha2.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <getopt.h>
+#include <unistd.h>
+
+#include "got_error.h"
+#include "got_object.h"
+#include "got_version.h"
+#include "got_path.h"
+#include "got_reference.h"
+
+#include "media.h"
+#include "gotwebd.h"
+
+#ifndef nitems
+#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
+#endif
+
+struct gotwebctl_cmd {
+ const char *cmd_name;
+ const struct got_error *(*cmd_main)(int, char *[], int);
+ void (*cmd_usage)(int);
+};
+
+__dead static void usage(int, int);
+
+__dead static void usage_info(int);
+__dead static void usage_stop(int);
+
+static const struct got_error* cmd_info(int, char *[], int);
+static const struct got_error* cmd_stop(int, char *[], int);
+
+static const struct gotwebctl_cmd gotwebctl_commands[] = {
+ { "info", cmd_info, usage_info },
+ { "stop", cmd_stop, usage_stop },
+};
+
+__dead static void
+usage_info(int status)
+{
+ FILE *fp = (status == 0) ? stdout : stderr;
+ fprintf(fp, "usage: %s info\n", getprogname());
+ exit(status);
+}
+
+static const struct got_error *
+show_info(struct imsg *imsg)
+{
+ struct gotwebd_imsg_info info;
+ size_t datalen;
+
+ datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+ if (datalen != sizeof(info))
+ return got_error(GOT_ERR_PRIVSEP_LEN);
+ memcpy(&info, imsg->data, sizeof(info));
+
+ printf("gotwebd PID: %d\n", info.pid);
+ printf("verbosity: %d\n", info.verbosity);
+ return NULL;
+}
+
+static const struct got_error *
+cmd_info(int argc, char *argv[], int gotwebd_sock)
+{
+ const struct got_error *err = NULL;
+ struct imsgbuf ibuf;
+ struct imsg imsg;
+ ssize_t n;
+ int done = 0;
+
+ if (unveil(NULL, NULL) != 0)
+ return got_error_from_errno("unveil");
+#ifndef PROFILE
+ if (pledge("stdio", NULL) == -1)
+ return got_error_from_errno("pledge");
+#endif
+ if (imsgbuf_init(&ibuf, gotwebd_sock) == -1)
+ return got_error_from_errno("imsgbuf_init");
+
+ if (imsg_compose(&ibuf, GOTWEBD_IMSG_CTL_INFO, 0, 0, -1,
+ NULL, 0) == -1) {
+ imsgbuf_clear(&ibuf);
+ return got_error_from_errno("imsg_compose INFO");
+ }
+
+ if (imsgbuf_flush(&ibuf) == -1) {
+ imsgbuf_clear(&ibuf);
+ return got_error_from_errno("imsgbuf_flush");
+ }
+
+ while (!done && err == NULL) {
+ n = imsgbuf_read(&ibuf);
+ if (n == -1) {
+ if (errno != EAGAIN) {
+ err = got_error_from_errno("imsgbuf_read");
+ break;
+ }
+
+ sleep(1);
+ continue;
+ }
+ if (n == 0)
+ break;
+
+ n = imsg_get(&ibuf, &imsg);
+ if (n == -1) {
+ err = got_error_from_errno("imsg_get");
+ break;
+ }
+
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ case GOTWEBD_IMSG_CTL_INFO:
+ err = show_info(&imsg);
+ done = 1;
+ break;
+ default:
+ err = got_error(GOT_ERR_PRIVSEP_MSG);
+ break;
+ }
+
+ imsg_free(&imsg);
+ }
+
+ imsgbuf_clear(&ibuf);
+ return err;
+}
+
+__dead static void
+usage_stop(int status)
+{
+ FILE *fp = (status == 0) ? stdout : stderr;
+ fprintf(fp, "usage: %s stop\n", getprogname());
+ exit(status);
+}
+
+static const struct got_error *
+cmd_stop(int argc, char *argv[], int gotwebd_sock)
+{
+ const struct got_error *err;
+ struct imsgbuf ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ if (unveil(NULL, NULL) != 0)
+ return got_error_from_errno("unveil");
+#ifndef PROFILE
+ if (pledge("stdio", NULL) == -1)
+ return got_error_from_errno("pledge");
+#endif
+ if (imsgbuf_init(&ibuf, gotwebd_sock) == -1)
+ return got_error_from_errno("imsgbuf_init");
+
+ if (imsg_compose(&ibuf, GOTWEBD_IMSG_CTL_STOP, 0, 0, -1,
+ NULL, 0) == -1) {
+ imsgbuf_clear(&ibuf);
+ return got_error_from_errno("imsg_compose STOP");
+ }
+
+ if (imsgbuf_flush(&ibuf) == -1) {
+ imsgbuf_clear(&ibuf);
+ return got_error_from_errno("imsgbuf_flush");
+ }
+
+ for (;;) {
+ n = imsg_get(&ibuf, &imsg);
+ if (n == -1) {
+ err = got_error_from_errno("imsg_get");
+ break;
+ }
+
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ default:
+ err = got_error(GOT_ERR_PRIVSEP_MSG);
+ break;
+ }
+
+ imsg_free(&imsg);
+ }
+
+ imsgbuf_clear(&ibuf);
+ return err;
+}
+
+static void
+list_commands(FILE *fp)
+{
+ size_t i;
+
+ fprintf(fp, "commands:");
+ for (i = 0; i < nitems(gotwebctl_commands); i++) {
+ const struct gotwebctl_cmd *cmd = &gotwebctl_commands[i];
+ fprintf(fp, " %s", cmd->cmd_name);
+ }
+ fputc('\n', fp);
+}
+
+__dead static void
+usage(int hflag, int status)
+{
+ FILE *fp = (status == 0) ? stdout : stderr;
+
+ fprintf(fp, "usage: %s [-hV] [-f path] command [arg ...]\n",
+ getprogname());
+ if (hflag)
+ list_commands(fp);
+ exit(status);
+}
+
+static int
+connect_gotwebd(const char *socket_path)
+{
+ int gotwebd_sock = -1;
+ struct sockaddr_un sun;
+
+ if (unveil(socket_path, "w") != 0)
+ err(1, "unveil %s", socket_path);
+
+ if ((gotwebd_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
+ err(1, "socket");
+
+ memset(&sun, 0, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+ if (strlcpy(sun.sun_path, socket_path, sizeof(sun.sun_path)) >=
+ sizeof(sun.sun_path))
+ errx(1, "gotd socket path too long");
+ if (connect(gotwebd_sock, (struct sockaddr *)&sun, sizeof(sun)) == -1)
+ err(1, "connect: %s", socket_path);
+
+ return gotwebd_sock;
+}
+
+int
+main(int argc, char *argv[])
+{
+ const struct gotwebctl_cmd *cmd;
+ int gotwebd_sock = -1, i;
+ int ch;
+ int hflag = 0, Vflag = 0;
+ static const struct option longopts[] = {
+ { "version", no_argument, NULL, 'V' },
+ { NULL, 0, NULL, 0 }
+ };
+ const char *socket_path = GOTWEBD_CONTROL_SOCKET;
+
+ setlocale(LC_CTYPE, "");
+
+#ifndef PROFILE
+ if (pledge("stdio rpath unix unveil", NULL) == -1)
+ err(1, "pledge");
+#endif
+ while ((ch = getopt_long(argc, argv, "+hf:V", longopts, NULL)) != -1) {
+ switch (ch) {
+ case 'h':
+ hflag = 1;
+ break;
+ case 'f':
+ socket_path = optarg;
+ break;
+ case 'V':
+ Vflag = 1;
+ break;
+ default:
+ usage(hflag, 1);
+ /* NOTREACHED */
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+ optind = 1;
+ optreset = 1;
+
+ if (Vflag) {
+ got_version_print_str();
+ return 0;
+ }
+
+ if (argc <= 0)
+ usage(hflag, hflag ? 0 : 1);
+
+ for (i = 0; i < nitems(gotwebctl_commands); i++) {
+ const struct got_error *error;
+
+ cmd = &gotwebctl_commands[i];
+
+ if (strncmp(cmd->cmd_name, argv[0], strlen(argv[0])) != 0)
+ continue;
+
+ if (hflag)
+ cmd->cmd_usage(0);
+#ifdef PROFILE
+ if (unveil("gmon.out", "rwc") != 0)
+ err(1, "unveil", "gmon.out");
+#endif
+ gotwebd_sock = connect_gotwebd(socket_path);
+ if (gotwebd_sock == -1)
+ return 1;
+ error = cmd->cmd_main(argc, argv, gotwebd_sock);
+ close(gotwebd_sock);
+ if (error && error->msg[0] != '\0') {
+ fprintf(stderr, "%s: %s\n", getprogname(), error->msg);
+ return 1;
+ }
+
+ return 0;
+ }
+
+ fprintf(stderr, "%s: unknown command '%s'\n", getprogname(), argv[0]);
+ list_commands(stderr);
+ return 1;
+}
commit - f5e64618f74a49a9167bec1f7db97000c2b8557f
blob - 4c6c4cdca05841e58fab572e2bdc7fd2221e01eb
file + gotwebd/gotwebd.8
--- gotwebd/gotwebd.8
+++ gotwebd/gotwebd.8
@@ -133,6 +133,18 @@ Directory containing HTML, CSS, and image files used b
Default location for the
.Nm
listening socket.
+.It Pa /var/run/gotweb-login.sock
+Default location for the
+.Nm
+login socket.
+.It Pa /var/run/gotwebd.sock
+Default location for the
+.Nm
+control socket.
+.Xr gotwebctl 8
+can be used to send commands to
+.Nm
+via this socket.
.It Pa /tmp/
Directory for temporary files created by
.Nm .
@@ -219,6 +231,7 @@ server "example.com" {
.Xr git-repository 5 ,
.Xr gotwebd.conf 5 ,
.Xr httpd.conf 5 ,
+.Xr gotwebctl 8 ,
.Xr httpd 8
.Sh AUTHORS
.An Omar Polo Aq Mt op@openbsd.org
commit - f5e64618f74a49a9167bec1f7db97000c2b8557f
blob - f868bed8cfb869337fbd6d4150b9ca347187f066
file + gotwebd/gotwebd.c
--- gotwebd/gotwebd.c
+++ gotwebd/gotwebd.c
@@ -18,6 +18,7 @@
#include <sys/param.h>
#include <sys/queue.h>
#include <sys/tree.h>
+#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
@@ -66,6 +67,9 @@ void gotwebd_dispatch_gotweb(int, short, void *);
struct gotwebd *gotwebd_env;
+static volatile int client_cnt;
+static volatile int stopping;
+
void
imsg_event_add(struct imsgev *iev)
{
@@ -424,6 +428,26 @@ gotwebd_dispatch_gotweb(int fd, short event, void *arg
}
static void
+control_socket_destroy(void)
+{
+ struct gotwebd *env = gotwebd_env;
+
+ if (env->iev_control == NULL)
+ return;
+
+ event_del(&env->iev_control->ev);
+ imsgbuf_clear(&env->iev_control->ibuf);
+ if (env->iev_control->ibuf.fd != -1)
+ close(env->iev_control->ibuf.fd);
+
+ free(env->iev_control);
+ env->iev_control = NULL;
+
+ free(env->control_sock);
+ env->control_sock = NULL;
+}
+
+static void
gotwebd_stop(void)
{
if (main_compose_sockets(gotwebd_env, GOTWEBD_IMSG_CTL_STOP,
@@ -433,6 +457,9 @@ gotwebd_stop(void)
if (main_compose_login(gotwebd_env, GOTWEBD_IMSG_CTL_STOP,
-1, NULL, 0) == -1)
fatal("send_imsg GOTWEBD_IMSG_CTL_STOP");
+
+ control_socket_destroy();
+ stopping = 1;
}
void
@@ -451,10 +478,14 @@ gotwebd_sighdlr(int sig, short event, void *arg)
log_info("%s: ignoring SIGUSR1", __func__);
break;
case SIGTERM:
+ if (stopping)
+ break;
gotwebd_stop();
break;
case SIGINT:
gotwebd_shutdown();
+ exit(0);
+ /* NOTREACHED */
break;
default:
log_warn("unexpected signal %d", sig);
@@ -577,6 +608,293 @@ get_usernames(const char **gotwebd_username, const cha
*www_username = colon + 1;
}
+static int
+control_socket_listen(struct gotwebd *env, struct socket *sock,
+ uid_t uid, gid_t gid)
+{
+ int u_fd = -1;
+ mode_t old_umask, mode;
+
+ u_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK| SOCK_CLOEXEC, 0);
+ if (u_fd == -1) {
+ log_warn("socket");
+ return -1;
+ }
+
+ if (unlink(sock->conf.unix_socket_name) == -1) {
+ if (errno != ENOENT) {
+ log_warn("unlink %s", sock->conf.unix_socket_name);
+ close(u_fd);
+ return -1;
+ }
+ }
+
+ old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH);
+ mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH;
+
+ if (bind(u_fd, (struct sockaddr *)&sock->conf.addr.ss,
+ sock->conf.addr.slen) == -1) {
+ log_warn("bind: %s", sock->conf.unix_socket_name);
+ close(u_fd);
+ (void)umask(old_umask);
+ return -1;
+ }
+
+ (void)umask(old_umask);
+
+ if (chmod(sock->conf.unix_socket_name, mode) == -1) {
+ log_warn("chmod: %s", sock->conf.unix_socket_name);
+ close(u_fd);
+ (void)unlink(sock->conf.unix_socket_name);
+ return -1;
+ }
+
+ if (chown(sock->conf.unix_socket_name, uid, gid) == -1) {
+ log_warn("chown: %s", sock->conf.unix_socket_name);
+ close(u_fd);
+ (void)unlink(sock->conf.unix_socket_name);
+ return -1;
+ }
+
+ if (listen(u_fd, 1) == -1) {
+ log_warn("listen: %s", sock->conf.unix_socket_name);
+ return -1;
+ }
+
+ return u_fd;
+}
+
+struct gotwebd_control_client {
+ int fd;
+ struct imsgev iev;
+ struct event tmo;
+};
+
+static void
+disconnect(struct gotwebd_control_client *client)
+{
+ if (client->fd != -1)
+ close(client->fd);
+ free(client);
+ client_cnt--;
+}
+
+static void
+control_timeout(int fd, short events, void *arg)
+{
+ struct gotwebd_control_client *client = arg;
+
+ log_debug("disconnecting control socket due to timeout");
+
+ disconnect(client);
+}
+
+static void
+send_info(struct gotwebd_control_client *client)
+{
+ struct gotwebd_imsg_info info;
+
+ info.pid = gotwebd_env->pid;
+ info.verbosity = gotwebd_env->gotwebd_verbose;
+
+ if (imsg_compose_event(&client->iev, GOTWEBD_IMSG_CTL_INFO, 0,
+ gotwebd_env->pid, -1, &info, sizeof(info)) == -1)
+ log_warn("imsg compose INFO");
+}
+
+static void
+control_request(int fd, short event, void *arg)
+{
+ struct gotwebd_control_client *client = arg;
+ struct imsg imsg;
+ ssize_t n;
+
+ if (event & EV_WRITE) {
+ if (imsgbuf_write(&client->iev.ibuf) == -1) {
+ log_warn("imsgbuf_write");
+ disconnect(client);
+ return;
+ }
+
+ if (stopping) {
+ disconnect(client);
+ return;
+ }
+ }
+
+ if (event & EV_READ) {
+ if ((n = imsgbuf_read(&client->iev.ibuf)) == -1)
+ fatal("imsgbuf_read error");
+ if (n == 0) {
+ /* Connection closed. */
+ disconnect(client);
+ return;
+ }
+ }
+
+ for (;;) {
+ n = imsg_get(&client->iev.ibuf, &imsg);
+ if (n == -1) {
+ disconnect(client);
+ return;
+ }
+
+ if (n == 0)
+ break;
+
+ evtimer_del(&client->tmo);
+
+ switch (imsg.hdr.type) {
+ case GOTWEBD_IMSG_CTL_INFO:
+ send_info(client);
+ break;
+ case GOTWEBD_IMSG_CTL_STOP:
+ if (!stopping)
+ gotwebd_stop();
+ disconnect(client);
+ imsg_free(&imsg);
+ return;
+ default:
+ log_warnx("unexpected imsg %d", imsg.hdr.type);
+ break;
+ }
+
+ imsg_free(&imsg);
+ }
+
+ imsg_event_add(&client->iev);
+}
+
+static void
+control_accept(int fd, short event, void *arg)
+{
+ struct imsgev *iev = arg;
+ struct gotwebd *env = gotwebd_env;
+ struct sockaddr_storage ss;
+ struct timeval backoff;
+ socklen_t len;
+ int s = -1;
+ struct gotwebd_control_client *client = NULL;
+ uid_t euid;
+ gid_t egid;
+
+ backoff.tv_sec = 1;
+ backoff.tv_usec = 0;
+
+ if (stopping)
+ return;
+
+ if (event_add(&iev->ev, NULL) == -1) {
+ log_warn("event_add");
+ return;
+ }
+ if (event & EV_TIMEOUT)
+ return;
+
+ len = sizeof(ss);
+
+ s = accept4(fd, (struct sockaddr *)&ss, &len,
+ SOCK_NONBLOCK | SOCK_CLOEXEC);
+ if (s == -1) {
+ switch (errno) {
+ case EINTR:
+ case EWOULDBLOCK:
+ case ECONNABORTED:
+ return;
+ case EMFILE:
+ case ENFILE:
+ event_del(&iev->ev);
+ if (!stopping)
+ evtimer_add(&env->control_pause_ev, &backoff);
+ return;
+ default:
+ log_warn("accept");
+ return;
+ }
+ }
+
+ if (client_cnt >= 1)
+ goto err;
+
+ if (getpeereid(s, &euid, &egid) == -1) {
+ log_warn("getpeerid");
+ goto err;
+ }
+
+ if (euid != 0) {
+ log_warnx("control connection from UID %d denied", euid);
+ goto err;
+ }
+
+ client = calloc(1, sizeof(*client));
+ if (client == NULL) {
+ log_warn("%s: calloc", __func__);
+ goto err;
+ }
+ client->fd = s;
+ s = -1;
+
+ client->iev.handler = control_request;
+ client->iev.events = EV_READ;
+ client->iev.data = client;
+
+ imsgbuf_init(&client->iev.ibuf, client->fd);
+ event_set(&client->iev.ev, client->fd, EV_READ, control_request,
+ client);
+ imsg_event_add(&client->iev);
+
+ evtimer_set(&client->tmo, control_timeout, client);
+
+ log_debug("%s: control connection on fd %d uid %d gid %d", __func__,
+ client->fd, euid, egid);
+ client_cnt++;
+ return;
+err:
+ if (client) {
+ close(client->fd);
+ free(client);
+ }
+ if (s != -1)
+ close(s);
+}
+
+static void
+accept_paused(int fd, short event, void *arg)
+{
+ struct gotwebd *env = gotwebd_env;
+
+ if (!stopping)
+ event_add(&env->iev_control->ev, NULL);
+}
+
+static void
+control_socket_init(struct gotwebd *env, uid_t uid, gid_t gid)
+{
+ struct imsgev *iev;
+ int fd;
+
+ log_info("initializing control socket %s",
+ env->control_sock->conf.unix_socket_name);
+
+ iev = calloc(1, sizeof(*iev));
+ if (iev == NULL)
+ fatal("calloc");
+
+ fd = control_socket_listen(env, env->control_sock, uid, gid);
+ if (fd == -1)
+ exit(1);
+
+ if (imsgbuf_init(&iev->ibuf, fd) == -1)
+ fatal("imsgbuf_init");
+
+ iev->data = iev;
+ event_set(&iev->ev, fd, EV_READ, control_accept, iev);
+ event_add(&iev->ev, NULL);
+ evtimer_set(&env->control_pause_ev, accept_paused, NULL);
+
+ env->iev_control = iev;
+}
+
int
main(int argc, char **argv)
{
@@ -783,11 +1101,15 @@ main(int argc, char **argv)
break;
}
+ env->pid = getpid();
+
if (!env->gotwebd_debug && daemon(1, 0) == -1)
fatal("daemon");
evb = event_init();
+ control_socket_init(env, pw->pw_uid, pw->pw_gid);
+
env->iev_sockets = calloc(1, sizeof(*env->iev_sockets));
if (env->iev_sockets == NULL)
fatal("calloc");
@@ -867,15 +1189,16 @@ main(int argc, char **argv)
err(1, "unveil");
#ifndef PROFILE
- if (pledge("stdio", NULL) == -1)
+ if (pledge("stdio unix", NULL) == -1)
err(1, "pledge");
#endif
event_dispatch();
+
+ gotwebd_shutdown();
+
event_base_free(evb);
- log_debug("%s gotwebd exiting", getprogname());
-
return (0);
}
@@ -1151,6 +1474,8 @@ gotwebd_shutdown(void)
free(env->login_sock);
+ control_socket_destroy();
+
do {
pid = waitpid(WAIT_ANY, &status, 0);
if (pid <= 0)
@@ -1188,5 +1513,4 @@ gotwebd_shutdown(void)
free(gotwebd_env);
log_warnx("gotwebd terminating");
- exit(0);
}
commit - f5e64618f74a49a9167bec1f7db97000c2b8557f
blob - 0e88f461cc2d5660929306c6218098113ead9fbd
file + gotwebd/gotwebd.conf.5
--- gotwebd/gotwebd.conf.5
+++ gotwebd/gotwebd.conf.5
@@ -218,6 +218,17 @@ commands.
By default the path
.Pa /var/run/gotweb-login.sock
will be used.
+.It Ic control socket Ar path
+Set the
+.Ar path
+to the
+.Ux Ns -domain
+socket for
+.Xr gotwebctl 8
+commands.
+By default the path
+.Pa /var/run/gotwebd.sock
+will be used.
.It Ic prefork Ar number
Spawn enough processes such that
.Ar number
@@ -927,5 +938,6 @@ server "secure.example.com" {
.Xr got 1 ,
.Xr httpd.conf 5 ,
.Xr services 5 ,
+.Xr gotwebctl 8 ,
.Xr gotwebd 8 ,
.Xr httpd 8
commit - f5e64618f74a49a9167bec1f7db97000c2b8557f
blob - d33a473211f9b6d1f97dac15ca9b453a9ec4035b
file + gotwebd/gotwebd.h
--- gotwebd/gotwebd.h
+++ gotwebd/gotwebd.h
@@ -43,6 +43,8 @@
#define GOTWEBD_LOGIN_SOCKET "/var/run/gotweb-login.sock"
#define GOTWEBD_LOGIN_TIMEOUT 300 /* in seconds */
+#define GOTWEBD_CONTROL_SOCKET "/var/run/gotwebd.sock"
+
#define GOTWEBD_MAXDESCRSZ 1024
#define GOTWEBD_MAXCLONEURLSZ 1024
#define GOTWEBD_CACHESIZE 1024
@@ -150,6 +152,7 @@ enum imsg_type {
GOTWEBD_IMSG_CTL_PIPE,
GOTWEBD_IMSG_CTL_START,
GOTWEBD_IMSG_CTL_STOP,
+ GOTWEBD_IMSG_CTL_INFO,
GOTWEBD_IMSG_LOGIN_SECRET,
GOTWEBD_IMSG_AUTH_SECRET,
GOTWEBD_IMSG_AUTH_CONF,
@@ -170,6 +173,12 @@ struct imsgev {
#define IMSG_DATA_SIZE(imsg) ((imsg)->hdr.len - IMSG_HEADER_SIZE)
+/* Structure for GOTWEBD_IMSG_CTL_INFO. */
+struct gotwebd_imsg_info {
+ pid_t pid;
+ int verbosity;
+};
+
struct env_val {
SLIST_ENTRY(env_val) entry;
char *val;
@@ -477,6 +486,8 @@ TAILQ_HEAD(socketlist, socket);
struct passwd;
struct gotwebd {
+ pid_t pid;
+
struct serverlist servers;
struct socketlist sockets;
struct addresslist addresses;
@@ -484,6 +495,10 @@ struct gotwebd {
struct socket *login_sock;
struct event login_pause_ev;
+ struct socket *control_sock;
+ struct event control_pause_ev;
+ struct imsgev *iev_control;
+
enum gotwebd_auth_config auth_config;
struct gotwebd_access_rule_list access_rules;
commit - f5e64618f74a49a9167bec1f7db97000c2b8557f
blob - d7ad16d93f68bb3f742baac27bc72c4373600b62
file + gotwebd/parse.y
--- gotwebd/parse.y
+++ gotwebd/parse.y
@@ -156,7 +156,7 @@ mediatype_ok(const char *s)
%token SUMMARY_COMMITS_DISPLAY SUMMARY_TAGS_DISPLAY USER AUTHENTICATION
%token ENABLE DISABLE INSECURE REPOSITORY REPOSITORIES PERMIT DENY HIDE
%token WEBSITE PATH BRANCH REPOS_URL_PATH DESCRIPTION
-%token TYPES INCLUDE
+%token TYPES INCLUDE GOTWEBD_CONTROL
%token <v.string> STRING
%token <v.number> NUMBER
@@ -481,6 +481,20 @@ main : PREFORK NUMBER {
free($2);
}
+ | GOTWEBD_CONTROL SOCKET STRING {
+ struct address *h;
+ h = get_unix_addr($3);
+ if (h == NULL) {
+ yyerror("can't listen on %s", $3);
+ free($3);
+ YYERROR;
+ }
+ if (gotwebd->control_sock != NULL)
+ free(gotwebd->control_sock);
+ gotwebd->control_sock = sockets_conf_new_socket(-1, h);
+ free(h);
+ free($3);
+ }
;
server : SERVER STRING {
@@ -1065,6 +1079,7 @@ lookup(char *s)
{ "authentication", AUTHENTICATION },
{ "branch", BRANCH },
{ "chroot", CHROOT },
+ { "control", GOTWEBD_CONTROL },
{ "custom_css", CUSTOM_CSS },
{ "deny", DENY },
{ "description", DESCRIPTION },
@@ -1635,6 +1650,19 @@ parse_config(const char *filename, struct gotwebd *env
free(h);
}
+ /* Add implicit control socket */
+ if (gotwebd->control_sock == NULL) {
+ struct address *h;
+ h = get_unix_addr(GOTWEBD_CONTROL_SOCKET);
+ if (h == NULL) {
+ fprintf(stderr, "cannot listen on %s",
+ GOTWEBD_CONTROL_SOCKET);
+ return (-1);
+ }
+ gotwebd->control_sock = sockets_conf_new_socket(-1, h);
+ free(h);
+ }
+
/*
* Disable authentication if not explicitly configured.
* Authentication requires access rules to be configured, and we want
add gotwebctl(8)