From cbf280f54ced411020e4526aa2be21cd50aff529 Mon Sep 17 00:00:00 2001 From: Runxi Yu Date: Tue, 18 Mar 2025 19:52:00 +0800 Subject: git_hooks_client -> hookc --- Makefile | 4 +- git_hooks_client/.gitignore | 1 - git_hooks_client/git_hooks_client.c | 250 ------------------------------------ git_hooks_deploy.go | 8 +- git_hooks_handle.go | 2 +- hookc/.gitignore | 1 + hookc/hookc.c | 250 ++++++++++++++++++++++++++++++++++++ resources.go | 4 +- 8 files changed, 260 insertions(+), 260 deletions(-) delete mode 100644 git_hooks_client/.gitignore delete mode 100644 git_hooks_client/git_hooks_client.c create mode 100644 hookc/.gitignore create mode 100644 hookc/hookc.c diff --git a/Makefile b/Makefile index 43e344c..55d5ddd 100644 --- a/Makefile +++ b/Makefile @@ -5,11 +5,11 @@ CFLAGS = -Wall -Wextra -Werror -pedantic -std=c99 -D_GNU_SOURCE -forge: $(filter-out forge,$(wildcard *)) version.go git_hooks_client/*.c git_hooks_client/git_hooks_client +forge: $(filter-out forge,$(wildcard *)) version.go hookc/*.c hookc/hookc go mod vendor go build . -git_hooks_client/git_hooks_client: +hookc/hookc: version.go: printf 'package main\nconst VERSION="%s"\n' $(shell git describe --tags --always --dirty) > $@ diff --git a/git_hooks_client/.gitignore b/git_hooks_client/.gitignore deleted file mode 100644 index 129c0b4..0000000 --- a/git_hooks_client/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/git_hooks_client diff --git a/git_hooks_client/git_hooks_client.c b/git_hooks_client/git_hooks_client.c deleted file mode 100644 index 9921edf..0000000 --- a/git_hooks_client/git_hooks_client.c +++ /dev/null @@ -1,250 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * SPDX-FileContributor: Runxi Yu - * SPDX-FileContributor: Test_User - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* - * FIXME: splice(2) is not portable and will only work on Linux. Alternative - * implementations should be supplied for other environments. - */ - -int main(int argc, char *argv[]) { - if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) { - perror("signal"); - return EXIT_FAILURE; - } - - const char *socket_path = getenv("LINDENII_FORGE_HOOKS_SOCKET_PATH"); - if (socket_path == NULL) { - dprintf(STDERR_FILENO, "environment variable LINDENII_FORGE_HOOKS_SOCKET_PATH undefined\n"); - return EXIT_FAILURE; - } - const char *cookie = getenv("LINDENII_FORGE_HOOKS_COOKIE"); - if (cookie == NULL) { - dprintf(STDERR_FILENO, "environment variable LINDENII_FORGE_HOOKS_COOKIE undefined\n"); - return EXIT_FAILURE; - } - if (strlen(cookie) != 64) { - dprintf(STDERR_FILENO, "environment variable LINDENII_FORGE_HOOKS_COOKIE is not 64 characters long\n"); - return EXIT_FAILURE; - } - - /* - * All hooks in git (see builtin/receive-pack.c) use a pipe by setting - * .in = -1 on the child_process struct, which enables us to use - * splice(2) to move the data to the UNIX domain socket. - */ - struct stat stdin_stat; - if (fstat(STDIN_FILENO, &stdin_stat) == -1) { - perror("fstat on stdin"); - return EXIT_FAILURE; - } - if (!S_ISFIFO(stdin_stat.st_mode)) { - dprintf(STDERR_FILENO, "stdin must be a pipe\n"); - return EXIT_FAILURE; - } - int stdin_pipe_size = fcntl(STDIN_FILENO, F_GETPIPE_SZ); - if (stdin_pipe_size == -1) { - perror("fcntl on stdin"); - return EXIT_FAILURE; - } - - /* - * Same for stderr. - */ - struct stat stderr_stat; - if (fstat(STDERR_FILENO, &stderr_stat) == -1) { - perror("fstat on stderr"); - return EXIT_FAILURE; - } - if (!S_ISFIFO(stderr_stat.st_mode)) { - dprintf(STDERR_FILENO, "stderr must be a pipe\n"); - return EXIT_FAILURE; - } - int stderr_pipe_size = fcntl(STDERR_FILENO, F_GETPIPE_SZ); - if (stderr_pipe_size == -1) { - perror("fcntl on stderr"); - return EXIT_FAILURE; - } - - /* Connecting back to the main daemon */ - int sock; - struct sockaddr_un addr; - sock = socket(AF_UNIX, SOCK_STREAM, 0); - if (sock == -1) { - perror("internal socket creation"); - return EXIT_FAILURE; - } - memset(&addr, 0, sizeof(struct sockaddr_un)); - addr.sun_family = AF_UNIX; - strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); - if (connect(sock, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) == -1) { - perror("internal socket connect"); - close(sock); - return EXIT_FAILURE; - } - - /* - * Send the 64-byte cookit back. - */ - ssize_t cookie_bytes_sent = send(sock, cookie, 64, 0); - switch (cookie_bytes_sent) { - case -1: - perror("send cookie"); - close(sock); - return EXIT_FAILURE; - case 64: - break; - default: - dprintf(STDERR_FILENO, "send returned unexpected value on internal socket\n"); - close(sock); - return EXIT_FAILURE; - } - - /* - * Report arguments. - */ - uint64_t argc64 = (uint64_t)argc; - ssize_t bytes_sent = send(sock, &argc64, sizeof(argc64), 0); - switch (bytes_sent) { - case -1: - perror("send argc"); - close(sock); - return EXIT_FAILURE; - case sizeof(argc64): - break; - default: - dprintf(STDERR_FILENO, "send returned unexpected value on internal socket\n"); - close(sock); - return EXIT_FAILURE; - } - for (int i = 0; i < argc; i++) { - unsigned long len = strlen(argv[i]) + 1; - bytes_sent = send(sock, argv[i], len, 0); - if (bytes_sent == -1) { - perror("send argv"); - close(sock); - exit(EXIT_FAILURE); - } else if ((unsigned long)bytes_sent == len) { - } else { - dprintf(STDERR_FILENO, "send returned unexpected value on internal socket\n"); - close(sock); - exit(EXIT_FAILURE); - } - } - - /* - * Report GIT_* environment. - */ - extern char **environ; - for (char **env = environ; *env != NULL; env++) { - if (strncmp(*env, "GIT_", 4) == 0) { - unsigned long len = strlen(*env) + 1; - bytes_sent = send(sock, *env, len, 0); - if (bytes_sent == -1) { - perror("send env"); - close(sock); - exit(EXIT_FAILURE); - } else if ((unsigned long)bytes_sent == len) { - } else { - dprintf(STDERR_FILENO, "send returned unexpected value on internal socket\n"); - close(sock); - exit(EXIT_FAILURE); - } - } - } - bytes_sent = send(sock, "", 1, 0); - if (bytes_sent == -1) { - perror("send env terminator"); - close(sock); - exit(EXIT_FAILURE); - } else if (bytes_sent == 1) { - } else { - dprintf(STDERR_FILENO, "send returned unexpected value on internal socket\n"); - close(sock); - exit(EXIT_FAILURE); - } - - /* - * Splice stdin to the daemon. For pre-receive it's just old/new/ref. - */ - ssize_t stdin_bytes_spliced; - while ((stdin_bytes_spliced = splice(STDIN_FILENO, NULL, sock, NULL, stdin_pipe_size, SPLICE_F_MORE)) > 0) { - } - if (stdin_bytes_spliced == -1) { - perror("splice stdin to internal socket"); - close(sock); - return EXIT_FAILURE; - } - - /* - * The sending part of the UNIX socket should be shut down, to let - * io.Copy on the Go side return. - */ - if (shutdown(sock, SHUT_WR) == -1) { - perror("shutdown internal socket"); - close(sock); - return EXIT_FAILURE; - } - - /* - * The first byte of the response from the UNIX domain socket is the - * status code to return. - * - * FIXME: It doesn't make sense to require the return value to be - * sent before the log message. However, if we were to keep splicing, - * it's difficult to get the last byte before EOF. Perhaps we could - * hack together some sort of OOB message or ancillary data, or perhaps - * even use signals. - */ - char status_buf[1]; - ssize_t bytes_read = read(sock, status_buf, 1); - switch (bytes_read) { - case -1: - perror("read status code from internal socket"); - close(sock); - return EXIT_FAILURE; - case 0: - dprintf(STDERR_FILENO, "unexpected EOF on internal socket\n"); - close(sock); - return EXIT_FAILURE; - case 1: - break; - default: - dprintf(STDERR_FILENO, "read returned unexpected value on internal socket\n"); - close(sock); - return EXIT_FAILURE; - } - - /* - * Now we can splice data from the UNIX domain socket to stderr. - * This data is directly passed to the user (with "remote: " prepended). - * - * We usually don't actually use this as the daemon could easily write - * to the SSH connection's stderr directly anyway. - */ - ssize_t stderr_bytes_spliced; - while ((stderr_bytes_spliced = splice(sock, NULL, STDERR_FILENO, NULL, stderr_pipe_size, SPLICE_F_MORE)) > 0) { - } - if (stdin_bytes_spliced == -1 && errno != ECONNRESET) { - perror("splice internal socket to stderr"); - close(sock); - return EXIT_FAILURE; - } - - close(sock); - return *status_buf; -} diff --git a/git_hooks_deploy.go b/git_hooks_deploy.go index b70ccfe..9965b43 100644 --- a/git_hooks_deploy.go +++ b/git_hooks_deploy.go @@ -18,12 +18,12 @@ func deploy_hooks_to_filesystem() (err error) { var src_fd fs.File var dst_fd *os.File - if src_fd, err = resources_fs.Open("git_hooks_client/git_hooks_client"); err != nil { + if src_fd, err = resources_fs.Open("hookc/hookc"); err != nil { return err } defer src_fd.Close() - if dst_fd, err = os.OpenFile(filepath.Join(config.Hooks.Execs, "git_hooks_client"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o755); err != nil { + if dst_fd, err = os.OpenFile(filepath.Join(config.Hooks.Execs, "hookc"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o755); err != nil { return err } defer dst_fd.Close() @@ -40,14 +40,14 @@ func deploy_hooks_to_filesystem() (err error) { // Go's embed filesystems do not store permissions; but in any case, // they would need to be 0o755: - if err = os.Chmod(filepath.Join(config.Hooks.Execs, "git_hooks_client"), 0o755); err != nil { + if err = os.Chmod(filepath.Join(config.Hooks.Execs, "hookc"), 0o755); err != nil { return err } for _, hook_name := range []string{ "pre-receive", } { - if err = os.Symlink(filepath.Join(config.Hooks.Execs, "git_hooks_client"), filepath.Join(config.Hooks.Execs, hook_name)); err != nil { + if err = os.Symlink(filepath.Join(config.Hooks.Execs, "hookc"), filepath.Join(config.Hooks.Execs, hook_name)); err != nil { return err } } diff --git a/git_hooks_handle.go b/git_hooks_handle.go index 7da6c88..178b3c7 100644 --- a/git_hooks_handle.go +++ b/git_hooks_handle.go @@ -28,7 +28,7 @@ var ( err_get_ucred = errors.New("failed getsockopt") ) -// hooks_handle_connection handles a connection from git_hooks_client via the +// hooks_handle_connection handles a connection from hookc via the // unix socket. func hooks_handle_connection(conn net.Conn) { var ctx context.Context diff --git a/hookc/.gitignore b/hookc/.gitignore new file mode 100644 index 0000000..7348daa --- /dev/null +++ b/hookc/.gitignore @@ -0,0 +1 @@ +/hookc diff --git a/hookc/hookc.c b/hookc/hookc.c new file mode 100644 index 0000000..9921edf --- /dev/null +++ b/hookc/hookc.c @@ -0,0 +1,250 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * SPDX-FileContributor: Runxi Yu + * SPDX-FileContributor: Test_User + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * FIXME: splice(2) is not portable and will only work on Linux. Alternative + * implementations should be supplied for other environments. + */ + +int main(int argc, char *argv[]) { + if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) { + perror("signal"); + return EXIT_FAILURE; + } + + const char *socket_path = getenv("LINDENII_FORGE_HOOKS_SOCKET_PATH"); + if (socket_path == NULL) { + dprintf(STDERR_FILENO, "environment variable LINDENII_FORGE_HOOKS_SOCKET_PATH undefined\n"); + return EXIT_FAILURE; + } + const char *cookie = getenv("LINDENII_FORGE_HOOKS_COOKIE"); + if (cookie == NULL) { + dprintf(STDERR_FILENO, "environment variable LINDENII_FORGE_HOOKS_COOKIE undefined\n"); + return EXIT_FAILURE; + } + if (strlen(cookie) != 64) { + dprintf(STDERR_FILENO, "environment variable LINDENII_FORGE_HOOKS_COOKIE is not 64 characters long\n"); + return EXIT_FAILURE; + } + + /* + * All hooks in git (see builtin/receive-pack.c) use a pipe by setting + * .in = -1 on the child_process struct, which enables us to use + * splice(2) to move the data to the UNIX domain socket. + */ + struct stat stdin_stat; + if (fstat(STDIN_FILENO, &stdin_stat) == -1) { + perror("fstat on stdin"); + return EXIT_FAILURE; + } + if (!S_ISFIFO(stdin_stat.st_mode)) { + dprintf(STDERR_FILENO, "stdin must be a pipe\n"); + return EXIT_FAILURE; + } + int stdin_pipe_size = fcntl(STDIN_FILENO, F_GETPIPE_SZ); + if (stdin_pipe_size == -1) { + perror("fcntl on stdin"); + return EXIT_FAILURE; + } + + /* + * Same for stderr. + */ + struct stat stderr_stat; + if (fstat(STDERR_FILENO, &stderr_stat) == -1) { + perror("fstat on stderr"); + return EXIT_FAILURE; + } + if (!S_ISFIFO(stderr_stat.st_mode)) { + dprintf(STDERR_FILENO, "stderr must be a pipe\n"); + return EXIT_FAILURE; + } + int stderr_pipe_size = fcntl(STDERR_FILENO, F_GETPIPE_SZ); + if (stderr_pipe_size == -1) { + perror("fcntl on stderr"); + return EXIT_FAILURE; + } + + /* Connecting back to the main daemon */ + int sock; + struct sockaddr_un addr; + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock == -1) { + perror("internal socket creation"); + return EXIT_FAILURE; + } + memset(&addr, 0, sizeof(struct sockaddr_un)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); + if (connect(sock, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) == -1) { + perror("internal socket connect"); + close(sock); + return EXIT_FAILURE; + } + + /* + * Send the 64-byte cookit back. + */ + ssize_t cookie_bytes_sent = send(sock, cookie, 64, 0); + switch (cookie_bytes_sent) { + case -1: + perror("send cookie"); + close(sock); + return EXIT_FAILURE; + case 64: + break; + default: + dprintf(STDERR_FILENO, "send returned unexpected value on internal socket\n"); + close(sock); + return EXIT_FAILURE; + } + + /* + * Report arguments. + */ + uint64_t argc64 = (uint64_t)argc; + ssize_t bytes_sent = send(sock, &argc64, sizeof(argc64), 0); + switch (bytes_sent) { + case -1: + perror("send argc"); + close(sock); + return EXIT_FAILURE; + case sizeof(argc64): + break; + default: + dprintf(STDERR_FILENO, "send returned unexpected value on internal socket\n"); + close(sock); + return EXIT_FAILURE; + } + for (int i = 0; i < argc; i++) { + unsigned long len = strlen(argv[i]) + 1; + bytes_sent = send(sock, argv[i], len, 0); + if (bytes_sent == -1) { + perror("send argv"); + close(sock); + exit(EXIT_FAILURE); + } else if ((unsigned long)bytes_sent == len) { + } else { + dprintf(STDERR_FILENO, "send returned unexpected value on internal socket\n"); + close(sock); + exit(EXIT_FAILURE); + } + } + + /* + * Report GIT_* environment. + */ + extern char **environ; + for (char **env = environ; *env != NULL; env++) { + if (strncmp(*env, "GIT_", 4) == 0) { + unsigned long len = strlen(*env) + 1; + bytes_sent = send(sock, *env, len, 0); + if (bytes_sent == -1) { + perror("send env"); + close(sock); + exit(EXIT_FAILURE); + } else if ((unsigned long)bytes_sent == len) { + } else { + dprintf(STDERR_FILENO, "send returned unexpected value on internal socket\n"); + close(sock); + exit(EXIT_FAILURE); + } + } + } + bytes_sent = send(sock, "", 1, 0); + if (bytes_sent == -1) { + perror("send env terminator"); + close(sock); + exit(EXIT_FAILURE); + } else if (bytes_sent == 1) { + } else { + dprintf(STDERR_FILENO, "send returned unexpected value on internal socket\n"); + close(sock); + exit(EXIT_FAILURE); + } + + /* + * Splice stdin to the daemon. For pre-receive it's just old/new/ref. + */ + ssize_t stdin_bytes_spliced; + while ((stdin_bytes_spliced = splice(STDIN_FILENO, NULL, sock, NULL, stdin_pipe_size, SPLICE_F_MORE)) > 0) { + } + if (stdin_bytes_spliced == -1) { + perror("splice stdin to internal socket"); + close(sock); + return EXIT_FAILURE; + } + + /* + * The sending part of the UNIX socket should be shut down, to let + * io.Copy on the Go side return. + */ + if (shutdown(sock, SHUT_WR) == -1) { + perror("shutdown internal socket"); + close(sock); + return EXIT_FAILURE; + } + + /* + * The first byte of the response from the UNIX domain socket is the + * status code to return. + * + * FIXME: It doesn't make sense to require the return value to be + * sent before the log message. However, if we were to keep splicing, + * it's difficult to get the last byte before EOF. Perhaps we could + * hack together some sort of OOB message or ancillary data, or perhaps + * even use signals. + */ + char status_buf[1]; + ssize_t bytes_read = read(sock, status_buf, 1); + switch (bytes_read) { + case -1: + perror("read status code from internal socket"); + close(sock); + return EXIT_FAILURE; + case 0: + dprintf(STDERR_FILENO, "unexpected EOF on internal socket\n"); + close(sock); + return EXIT_FAILURE; + case 1: + break; + default: + dprintf(STDERR_FILENO, "read returned unexpected value on internal socket\n"); + close(sock); + return EXIT_FAILURE; + } + + /* + * Now we can splice data from the UNIX domain socket to stderr. + * This data is directly passed to the user (with "remote: " prepended). + * + * We usually don't actually use this as the daemon could easily write + * to the SSH connection's stderr directly anyway. + */ + ssize_t stderr_bytes_spliced; + while ((stderr_bytes_spliced = splice(sock, NULL, STDERR_FILENO, NULL, stderr_pipe_size, SPLICE_F_MORE)) > 0) { + } + if (stdin_bytes_spliced == -1 && errno != ECONNRESET) { + perror("splice internal socket to stderr"); + close(sock); + return EXIT_FAILURE; + } + + close(sock); + return *status_buf; +} diff --git a/resources.go b/resources.go index 63a93af..41d15d7 100644 --- a/resources.go +++ b/resources.go @@ -21,7 +21,7 @@ import ( //go:embed *.scfg //go:embed Makefile //go:embed static/* templates/* scripts/* sql/* -//go:embed git_hooks_client/*.c +//go:embed hookc/*.c //go:embed vendor/* var source_fs embed.FS @@ -30,7 +30,7 @@ var source_handler = http.StripPrefix( http.FileServer(http.FS(source_fs)), ) -//go:embed templates/* static/* git_hooks_client/git_hooks_client +//go:embed templates/* static/* hookc/hookc var resources_fs embed.FS var templates *template.Template -- cgit v1.2.3