Commit Diff


commit - 202d187071957eec318c523e66455e0f804ff869
commit + 4ddc9d7454d1838afd08981593a8b32332f3725f
blob - 52cfe7948d93903861aec5c5f4f85960643c6444
blob + 377311c81a12015a42f5c01d3783018e7e8175b3
--- gotd/gotd.h
+++ gotd/gotd.h
@@ -500,7 +500,22 @@ struct gotd_imsg_auth {
 	uint32_t client_id;
 };
 
-/* Structure for GOTD_IMSG_NOTIFY. */
+/* Structures for GOTD_IMSG_NOTIFY. */
+enum gotd_notification_action {
+	GOTD_NOTIF_ACTION_CREATED,
+	GOTD_NOTIF_ACTION_REMOVED,
+	GOTD_NOTIF_ACTION_CHANGED
+};
+/* IMSG_NOTIFY session <-> repo_write */
+struct gotd_imsg_notification_content {
+	uint32_t client_id;
+	enum gotd_notification_action action;
+	uint8_t old_id[SHA1_DIGEST_LENGTH];
+	uint8_t new_id[SHA1_DIGEST_LENGTH];
+	size_t refname_len;
+	/* Followed by refname_len data bytes. */
+};
+/* IMSG_NOTIFY session -> notify*/
 struct gotd_imsg_notify {
 	char repo_name[NAME_MAX];
 	char subject_line[64];
blob - bf6acf421f9500ff062de590c586d4fe239003e7
blob + 8427fbb413b6a974cd738fd557463719005ebac9
--- gotd/session.c
+++ gotd/session.c
@@ -53,6 +53,15 @@
 #include "log.h"
 #include "session.h"
 
+struct gotd_session_notif {
+	STAILQ_ENTRY(gotd_session_notif) entry;
+	enum gotd_notification_action action;
+	char *refname;
+	struct got_object_id old_id;
+	struct got_object_id new_id;
+	int fd;
+};
+STAILQ_HEAD(gotd_session_notifications, gotd_session_notif) notifications;
 
 static struct gotd_session {
 	pid_t pid;
@@ -413,23 +422,24 @@ validate_namespace(const char *namespace)
 }
 
 static const struct got_error *
-send_notification(struct got_object_id *old_id, struct got_object_id *new_id,
+prepare_notification(struct got_object_id *old_id, struct got_object_id *new_id,
     struct got_repository *repo, struct got_reference *ref)
 {
 	const struct got_error *err = NULL;
 	struct gotd_session_client *client = &gotd_session_client;
 	struct gotd_repo *repo_cfg = gotd_session.repo_cfg;
-	struct gotd_imsgev *iev = &gotd_session.notifier_iev;
+	struct gotd_imsgev *iev = &client->repo_child_iev;
+	struct gotd_imsg_notification_content req;
 	struct got_pathlist_entry *pe;
-	struct gotd_imsg_notify inotify;
+	struct gotd_session_notif *notif;
+	struct ibuf *wbuf;
+	size_t len, refname_len;
 	int fd = -1;
-	char hostname[HOST_NAME_MAX + 1];
-	const char *action = "";
 
 	if (iev->ibuf.fd == -1)
 		return NULL; /* notifications unused */
 
-	memset(&inotify, 0, sizeof(inotify));
+	memset(&req, 0, sizeof(req));
 
 	TAILQ_FOREACH(pe, &repo_cfg->notification_refs, entry) {
 		const char *refname = pe->path;
@@ -439,9 +449,6 @@ send_notification(struct got_object_id *old_id, struct
 	if (pe == NULL && !TAILQ_EMPTY(&repo_cfg->notification_refs))
 		return NULL;
 
-	if (gethostname(hostname, sizeof(hostname)) == -1)
-		return got_error_from_errno("gethostname");
-
 	TAILQ_FOREACH(pe, &repo_cfg->notification_ref_namespaces, entry) {
 		const char *namespace = pe->path;
 
@@ -460,30 +467,167 @@ send_notification(struct got_object_id *old_id, struct
 	if (fd == -1)
 		return got_error_from_errno("got_opentempfd");
 
+	if (old_id)
+		memcpy(req.old_id, old_id->sha1, sizeof(req.old_id));
+	if (new_id)
+		memcpy(req.new_id, new_id->sha1, sizeof(req.new_id));
+	
 	if (old_id == NULL)
-		action = "created";
+		req.action = GOTD_NOTIF_ACTION_CREATED;
 	else if (new_id == NULL)
-		action = "removed";
+		req.action = GOTD_NOTIF_ACTION_REMOVED;
 	else
-		action = "changed";
-
-	strlcpy(inotify.repo_name, gotd_session.repo_cfg->name,
-	    sizeof(inotify.repo_name));
-
-	snprintf(inotify.subject_line, sizeof(inotify.subject_line),
-	    "%s: %s: UID %d %s %s", hostname, gotd_session.repo_cfg->name,
-	    client->euid, action, got_ref_get_name(ref));
-
-	dprintf(fd, "%s %s\n", action, got_ref_get_name(ref));
-
-	if (gotd_imsg_compose_event(iev, GOTD_IMSG_NOTIFY, PROC_SESSION_WRITE,
-	    fd, &inotify, sizeof(inotify)) == -1) {
-		err = got_error_from_errno("imsg compose NOTIFY");
+		req.action = GOTD_NOTIF_ACTION_CHANGED;
+	
+	notif = calloc(1, sizeof(*notif));
+	if (notif == NULL) {
+		err = got_error_from_errno("calloc");
 		close(fd);
 		return err;
+	}
+	notif->fd = dup(fd);
+	if (notif->fd == -1) {
+		err = got_error_from_errno("dup");
+		goto done;
+	}
+	notif->action = req.action;
+	notif->refname = strdup(got_ref_get_name(ref));
+	if (notif->refname == NULL) {
+		err = got_error_from_errno("strdup");
+		goto done;
 	}
 
-	return NULL;
+	refname_len = strlen(got_ref_get_name(ref));
+	len = sizeof(struct gotd_session_notif) + refname_len;
+	wbuf = imsg_create(&iev->ibuf, GOTD_IMSG_NOTIFY, gotd_session.proc_id,
+	    gotd_session.pid, len);
+	if (wbuf == NULL) {
+		err = got_error_from_errno("imsg_create NOTIFY");
+		goto done;
+	}
+
+	if (imsg_add(wbuf, &req, sizeof(req)) == -1) {
+		err = got_error_from_errno("imsg_add NOTIFY");
+		goto done;
+	}
+	if (imsg_add(wbuf, got_ref_get_name(ref), refname_len) == -1) {
+		err = got_error_from_errno("imsg_add NOTIFY");
+		goto done;
+	}
+
+	STAILQ_INSERT_TAIL(&notifications, notif, entry);
+
+	wbuf->fd = fd;
+	fd = -1;
+	imsg_close(&iev->ibuf, wbuf);
+	gotd_imsg_event_add(iev);
+done:
+	if (err && notif) {
+		if (notif->fd != -1 && close(notif->fd) == -1 && err == NULL)
+			err = got_error_from_errno("close");
+		free(notif->refname);
+		free(notif);
+	}
+	if (fd != -1 && close(fd) == -1 && err == NULL)
+		err = got_error_from_errno("close");
+	return err;
+}
+
+static const struct got_error *
+send_notification(struct gotd_session_client *client, struct imsg *imsg)
+{
+	const struct got_error *err = NULL;
+	struct gotd_imsgev *iev = &client->repo_child_iev;
+	struct gotd_session_notif *notif, *tmp;
+	struct gotd_imsg_notification_content icontent;
+	char *refname = NULL;
+	size_t datalen;
+	struct gotd_imsg_notify inotify;
+	char hostname[HOST_NAME_MAX + 1];
+
+	memset(&inotify, 0, sizeof(inotify));
+
+	if (imsg->fd == -1)
+		return got_error(GOT_ERR_PRIVSEP_NO_FD);
+
+	datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+	if (datalen < sizeof(icontent))
+		return got_error(GOT_ERR_PRIVSEP_LEN);
+	memcpy(&icontent, imsg->data, sizeof(icontent));
+	if (datalen != sizeof(icontent) + icontent.refname_len)
+		return got_error(GOT_ERR_PRIVSEP_LEN);
+	refname = strndup(imsg->data + sizeof(icontent), icontent.refname_len);
+	if (refname == NULL)
+		return got_error_from_errno("strndup");
+
+	STAILQ_FOREACH_SAFE(notif, &notifications, entry, tmp) {
+		if (notif->action != icontent.action ||
+		    strcmp(notif->refname, refname) != 0)
+			continue;
+		if (notif->action == GOTD_NOTIF_ACTION_CREATED) {
+			if (memcmp(notif->new_id.sha1, icontent.new_id,
+			    SHA1_DIGEST_LENGTH) != 0)
+				continue;
+		} else if (notif->action == GOTD_NOTIF_ACTION_REMOVED) {
+			if (memcmp(notif->old_id.sha1, icontent.old_id,
+			    SHA1_DIGEST_LENGTH) != 0)
+				continue;
+		} else {
+			if (memcmp(notif->old_id.sha1, icontent.old_id,
+			    SHA1_DIGEST_LENGTH) != 0 ||
+			    memcmp(notif->new_id.sha1, icontent.new_id,
+			    SHA1_DIGEST_LENGTH) != 0)
+				continue;
+		}
+
+		STAILQ_REMOVE(&notifications, notif, gotd_session_notif, entry);
+		break;
+	}
+
+	if (notif) {
+		const char *action;
+
+		switch (notif->action) {
+		case GOTD_NOTIF_ACTION_CREATED:
+			action = "created";
+			break;
+		case GOTD_NOTIF_ACTION_REMOVED:
+			action = "created";
+			break;
+		case GOTD_NOTIF_ACTION_CHANGED:
+			action = "changed";
+			break;
+		default:
+			err = got_error(GOT_ERR_PRIVSEP_MSG);
+			goto done;
+		}
+
+		if (gethostname(hostname, sizeof(hostname)) == -1) {
+			err = got_error_from_errno("gethostname");
+			goto done;
+		}
+
+		strlcpy(inotify.repo_name, gotd_session.repo_cfg->name,
+		    sizeof(inotify.repo_name));
+
+		snprintf(inotify.subject_line, sizeof(inotify.subject_line),
+		    "%s: %s: UID %d %s %s", hostname, gotd_session.repo_cfg->name,
+		    client->euid, action, notif->refname);
+
+		if (gotd_imsg_compose_event(iev, GOTD_IMSG_NOTIFY,
+		    PROC_SESSION_WRITE, imsg->fd, &inotify, sizeof(inotify))
+		    == -1) {
+			err = got_error_from_errno("imsg compose NOTIFY");
+			goto done;
+		}
+	} else
+		log_warn("received notification content for unknown event");
+done:
+	if (err == NULL && STAILQ_EMPTY(&notifications) &&
+	    client->nref_updates == 0)
+		client->flush_disconnect = 1;
+	free(refname);
+	return err;
 }
 
 static const struct got_error *
@@ -541,7 +685,7 @@ update_ref(int *shut, struct gotd_session_client *clie
 			err = got_ref_write(ref, repo); /* will lock/unlock */
 			if (err)
 				goto done;
-			err = send_notification(NULL, &new_id, repo, ref);
+			err = prepare_notification(NULL, &new_id, repo, ref);
 			if (err)
 				goto done;
 		} else {
@@ -583,7 +727,7 @@ update_ref(int *shut, struct gotd_session_client *clie
 		err = got_ref_delete(ref, repo);
 		if (err)
 			goto done;
-		err = send_notification(&old_id, NULL, repo, ref);
+		err = prepare_notification(&old_id, NULL, repo, ref);
 		if (err)
 			goto done;
 		free(id);
@@ -617,7 +761,7 @@ update_ref(int *shut, struct gotd_session_client *clie
 			err = got_ref_write(ref, repo);
 			if (err)
 				goto done;
-			err = send_notification(&old_id, &new_id, repo, ref);
+			err = prepare_notification(&old_id, &new_id, repo, ref);
 			if (err)
 				goto done;
 		}
@@ -640,7 +784,8 @@ done:
 		client->nref_updates--;
 		if (client->nref_updates == 0) {
 			send_refs_updated(client);
-			client->flush_disconnect = 1;
+			if (STAILQ_EMPTY(&notifications))
+				client->flush_disconnect = 1;
 		}
 
 	}
@@ -659,6 +804,23 @@ done:
 	return err;
 }
 
+static const struct got_error *
+recv_notification_content(uint32_t *client_id, struct imsg *imsg)
+{
+	struct gotd_imsg_notification_content inotif;
+	size_t datalen;
+
+	log_debug("notification content received");
+
+	datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+	if (datalen != sizeof(inotif))
+		return got_error(GOT_ERR_PRIVSEP_LEN);
+	memcpy(&inotif, imsg->data, sizeof(inotif));
+
+	*client_id = inotif.client_id;
+	return NULL;
+}
+
 static void
 session_dispatch_repo_child(int fd, short event, void *arg)
 {
@@ -695,7 +857,7 @@ session_dispatch_repo_child(int fd, short event, void 
 		uint32_t client_id = 0;
 		int do_disconnect = 0;
 		int do_ref_updates = 0, do_ref_update = 0;
-		int do_packfile_install = 0;
+		int do_packfile_install = 0, do_notify = 0;
 
 		if ((n = imsg_get(ibuf, &imsg)) == -1)
 			fatal("%s: imsg_get error", __func__);
@@ -726,6 +888,10 @@ session_dispatch_repo_child(int fd, short event, void 
 			if (err == NULL)
 				do_ref_update = 1;
 			break;
+		case GOTD_IMSG_NOTIFY:
+			err = recv_notification_content(&client_id, &imsg);
+			if (err == NULL)
+				do_notify = 1;
 		default:
 			log_debug("unexpected imsg %d", imsg.hdr.type);
 			break;
@@ -745,6 +911,8 @@ session_dispatch_repo_child(int fd, short event, void 
 			else if (do_ref_update)
 				err = update_ref(&shut, client,
 				    gotd_session.repo->path, &imsg);
+			else if (do_notify)
+				err = send_notification(client, &imsg);
 			if (err)
 				log_warnx("uid %d: %s", client->euid, err->msg);
 		}
@@ -1648,6 +1816,8 @@ session_main(const char *title, const char *repo_path,
 	const struct got_error *err = NULL;
 	struct event evsigint, evsigterm, evsighup, evsigusr1;
 
+	STAILQ_INIT(&notifications);
+
 	gotd_session.title = title;
 	gotd_session.pid = getpid();
 	gotd_session.pack_fds = pack_fds;
@@ -1710,7 +1880,19 @@ done:
 void
 gotd_session_shutdown(void)
 {
+	struct gotd_session_notif *notif;
+
 	log_debug("shutting down");
+
+	while (!STAILQ_EMPTY(&notifications)) {
+		notif = STAILQ_FIRST(&notifications);
+		STAILQ_REMOVE_HEAD(&notifications, entry);
+		if (notif->fd != -1)
+			close(notif->fd);
+		free(notif->refname);
+		free(notif);
+	}
+
 	if (gotd_session.repo)
 		got_repo_close(gotd_session.repo);
 	got_repo_pack_fds_close(gotd_session.pack_fds);