1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
int main(void) {
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;
}
/*
* 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. Just to be
* safe, we check that stdin is a pipe; and additionally we fetch the
* buffer size of the pipe to use as the maximum size for the splice.
*
* We connect to the UNIX domain socket after ensuring that standard
* input matches our expectations.
*/
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;
}
/*
* ... And we do the same for stderr. Later we will splice from the
* socket to stderr, to let the daemon report back to the user.
*/
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;
}
/*
* Now that we know that stdin and stderr are pipes, we can connect to
* the UNIX domain socket. We don't do this earlier because we don't
* want to create unnecessary connections if the hook was called
* inappropriately (such as by a user with shell access in their
* terminal).
*/
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;
}
/*
* Now we can start splicing data from stdin to the UNIX domain socket.
* The format is irrelevant and depends on the hook being called. All we
* do is pass it to the socket for it to handle.
*
* TODO: The argument vector may need to be passed too. We may consider
* using an ancillary message.
*/
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 first byte of the response from the UNIX domain socket is the
* status code. We read it and record it as our return value.
* (Realistically we just need a boolean to indicate success or failure.)
*/
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).
*/
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) {
perror("splice internal socket to stderr");
close(sock);
return EXIT_FAILURE;
}
close(sock);
return *status_buf;
}
|