10 #include <sys/types.h>
16 /* If you increase this too far, you may run into file descriptor limits */
17 #define MAX_CHILDREN 256
19 /* How long in ms to wait for child to output something */
20 #define READ_TIMEOUT_INITIAL 5000
22 /* How long in ms child can pause between writes before we consider it to have
24 #define READ_TIMEOUT_SUBSEQUENT 500
27 typedef struct Child
{
36 typedef struct State
{
41 Child children
[MAX_CHILDREN
];
44 static bool spawn(const char *command
, char *const *args
, Child
*child
, int socket
)
46 int infds
[2], outfds
[2];
47 if (pipe(infds
) == -1 || pipe(outfds
) == -1) {
52 const pid_t pid
= fork();
68 execvp(command
, args
);
76 child
->out
= outfds
[0];
77 setlinebuf(fdopen(infds
[1], "w"));
78 setlinebuf(fdopen(outfds
[0], "r"));
84 static bool write_all(int fd
, const char* buf
, int n
)
87 int w
= write(fd
, buf
, n
);
88 if (w
< 0) return false;
95 static void set_child_last_active(Child
*child
)
97 struct timespec clock_mono
;
98 clock_gettime(CLOCK_MONOTONIC
, &clock_mono
);
99 child
->last_active
= clock_mono
.tv_sec
;
102 /* Write anything written timelily on `in` to `out`, converting \n to \r\n.
103 * If `gemtext` is true, also space-stuff lines beginning with "```".
104 * Return -1 on read error, 0 on HUP, else 1. */
105 static int stream_text(int in
, int out
, bool gemtext
) {
107 struct pollfd pfd
= { in
, POLLIN
| POLLHUP
, 0 };
110 poll(&pfd
, 1, READ_TIMEOUT_INITIAL
);
111 while (pfd
.revents
& POLLIN
) {
112 const int r
= read(in
, buf
, 256 - 1);
113 if (r
< 0) return false;
118 if (gemtext
&& backticks
>= 0) {
121 if (backticks
== 3) {
122 write(out
, " ```", 4);
127 } else while (--backticks
>= 0) write(out
, "`", 1);
131 write(out
, "\r\n", 2);
133 } else write(out
, b
, 1);
137 poll(&pfd
, 1, READ_TIMEOUT_SUBSEQUENT
);
139 return (pfd
.revents
& POLLHUP
? 0 : 1);
142 void respond(void *object
, const Request_Info
*request_info
, int socket
)
144 State
*state
= (State
*)object
;
146 #define put(s) write_all(socket, s, strlen(s))
148 if (!request_info
->tls_client_hash
) {
149 put("60 Client certificate required\r\n");
153 Child
*child
= NULL
, *slot
= NULL
;
154 bool spawned
= false;
156 /* Find child with this cert hash, or spawn new.
157 * For simplicity, we use a static array of children rather than
158 * allocating dynamically. This wastes a few KB of memory; you may want to
159 * rewrite this if memory is tight. We also don't bother to keep the list
160 * sorted, but just strcmp for each child. Terribly wasteful. */
161 for (int i
= 0; i
< state
->num_children
; ++i
) {
162 Child
*const c
= &state
->children
[i
];
164 if (0 == strncmp(c
->owner
,
165 request_info
->tls_client_hash
, 32)) {
170 if (slot
== NULL
|| (slot
->exists
171 && slot
->last_active
> c
->last_active
)) {
174 } else if (slot
== NULL
|| slot
->exists
) slot
= c
;
178 if (slot
== NULL
|| (slot
->exists
&& state
->num_children
< state
->max_children
)) {
179 slot
= &state
->children
[state
->num_children
++];
184 // TODO: would be nice to queue a regretful message for the owner
185 // of the child we're killing...
189 child
->exists
= false;
192 if (!spawn(state
->command
, state
->args
, child
, socket
)) {
193 put("40 Spawn failure.\r\n");
197 set_child_last_active(child
);
198 child
->exists
= true;
199 strncpy(child
->owner
, request_info
->tls_client_hash
, 32);
204 const char *q
= request_info
->query_string_decoded
;
210 } else if (*q
!= '!') {
211 put("40 Unknown gemrepl meta-command (use '!!' for a literal '!')\r\n");
215 put("20 text/gemini\r\n");
216 put("=> ?!? Input command\r\n");
218 kill(child
->pid
, SIGCONT
);
220 const int qlen
= strlen(q
);
221 if (!spawned
|| qlen
> 0) {
222 signal(SIGPIPE
, SIG_IGN
);
223 bool err
= (write(child
->in
, q
, qlen
) < 0
224 || write(child
->in
, "\n", 1) < 0);
225 signal(SIGPIPE
, SIG_DFL
);
227 put("[gemrepl: error when writing to child]\r\n");
232 const int succ
= stream_text(child
->out
, socket
, true);
234 if (succ
< 0) put("[gemrepl: error when reading from child]\r\n");
235 else if (succ
== 0) {
236 // got HUP; sleep briefly to give child a chance to exit
240 set_child_last_active(child
);
242 if (waitpid(child
->pid
, NULL
, WNOHANG
) == child
->pid
) {
243 put("[gemrepl: child process terminated]");
246 child
->exists
= false;
248 kill(child
->pid
, SIGSTOP
);
254 printf("Usage: gemrepl [OPTION]... COMMAND [ARG]...\n");
255 printf(" -h --help This help\n");
256 printf(" -m NUM --max-children=NUM Max concurrent children to spawn (default %d)\n", MAX_CHILDREN
);
259 int main(int argc
, char **argv
)
266 State
*state
= malloc(sizeof(State
));
268 fprintf(stderr
, "Failed to allocate memory for state.");
272 int max_children
= -1;
274 const struct option longoptions
[] =
275 { { "help", 0, NULL
, 'h' }
276 , { "max-children", 1, NULL
, 'm' }
280 while (-1 != (o
= getopt_long(argc
, argv
, "hm:", longoptions
, NULL
))) {
288 max_children
= atoi(optarg
);
289 if (max_children
<= 0 || max_children
> MAX_CHILDREN
) {
290 printf("Bad value for max children.\n");
291 printf("You may need to increase MAX_CHILDREN in the source.\n");
298 if (argv
[optind
] == NULL
) {
303 state
->command
= argv
[optind
];
304 state
->args
= &argv
[optind
];
305 state
->max_children
= max_children
> 0 ? max_children
: MAX_CHILDREN
;
307 runSCGI("/tmp/gemrepl_socket", respond
, state
);