Dillo v3.2.0-143-gabad1053
Loading...
Searching...
No Matches
control.c
Go to the documentation of this file.
1/*
2 * File: control.c
3 *
4 * Copyright (C) 2025 Rodrigo Arias Mallo <rodarima@gmail.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 */
11
18#include "control.h"
19
20#include "config.h"
21
22#if ENABLE_CONTROL_SOCKET
23
24#include <assert.h>
25#include <errno.h>
26#include <fcntl.h>
27#include <netinet/in.h>
28#include <stdlib.h>
29#include <sys/ioctl.h>
30#include <sys/socket.h>
31#include <sys/stat.h>
32#include <sys/un.h>
33#include <unistd.h>
34
35#include "iowatch.hh"
36#include "uicmd.hh"
37#include "capi.h"
38#include "dlib/dlib.h"
39#include "msg.h"
40#include "timeout.hh"
41
42enum control_req_stage {
43 REQ_READ_COMMAND,
44 REQ_READ_BODY,
45 REQ_PREPARE_REPLY,
46 REQ_SWITCH_WRITE,
47 REQ_WRITE_REPLY,
48 REQ_CLOSE,
49};
50
51struct control_req {
52 int fd;
53 enum control_req_stage stage;
54 Dstr *cmd;
55 Dstr *body;
56 Dstr *reply;
57 size_t reply_offset;
58};
59
60/* One control fd per process */
61static int control_fd = -1;
62static char *control_path = NULL;
63
64/* The BrowserWindow that we are currently waiting to finish */
65static BrowserWindow *bw_waiting = NULL;
66static struct control_req *bw_waiting_req = NULL;
67
68static void handle_wait_timeout(void *data);
69
70/* Read the given non-blocking fd, read available data into output.
71 * Returns:
72 * 0: when the end of file has been reached.
73 * +1: if there is more data to be read.
74 * <0: other unexpected error.
75 */
76static int
77Control_read_fd(int fd, Dstr *output)
78{
79 ssize_t r;
80 do {
81 char buf[BUFSIZ];
82 r = read(fd, buf, BUFSIZ);
83 _MSG("read buf = %zd\n", r);
84 if (r < 0) {
85 if (errno == EINTR) {
86 continue;
87 } else if (errno == EAGAIN || errno == EWOULDBLOCK) {
88 /* Reached end of available data, try later */
89 return +1;
90 } else {
91 MSG_ERR("Control_read_fd: read failed: %s\n", dStrerror(errno));
92 return -1;
93 }
94 } else if (r > 0) {
95 dStr_append_l(output, buf, r);
96 }
97 } while (r);
98
99 return 0;
100}
101
102static int
103Control_write_fd(int fd, Dstr *input, size_t *ptr_offset)
104{
105 size_t offset = *ptr_offset;
106 ssize_t w;
107
108 while ((size_t) input->len > offset) {
109 const char *src = input->str + offset;
110 size_t left = input->len - offset;
111 w = write(fd, src, left);
112 if (w < 0) {
113 if (errno == EINTR) {
114 continue;
115 } else if (errno == EAGAIN || errno == EWOULDBLOCK) {
116 /* Cannot write more data, return error */
117 *ptr_offset = offset;
118 return +1;
119 } else {
120 MSG_ERR("Control_write_fd: write failed: %s\n", dStrerror(errno));
121 return -1;
122 }
123 } else if (w > 0) {
124 offset += w;
125 }
126 }
127
128 /* Save offset just in case */
129 *ptr_offset = offset;
130
131 return 0;
132}
133
134/* Create new request structure */
135static struct control_req *Control_req_new(int fd)
136{
137 struct control_req *req = dMalloc0(sizeof(struct control_req));
138 req->fd = fd;
139 req->cmd = dStr_sized_new(128);
140 req->body = dStr_sized_new(1);
141 req->reply = dStr_sized_new(64);
142 req->stage = REQ_READ_COMMAND;
143
144 return req;
145}
146
147static void Control_req_free(struct control_req *req)
148{
149 dStr_free(req->cmd, 1);
150 dStr_free(req->body, 1);
151 dStr_free(req->reply, 1);
152 dFree(req);
153}
154
155static void Control_req_read_cmd(struct control_req *req)
156{
157 ssize_t r;
158 int err = 0;
159 do {
160 /* Read characters one by one */
161 char buf[1];
162 r = read(req->fd, buf, 1);
163 if (r < 0) {
164 if (errno == EINTR) {
165 continue;
166 } else if (errno == EAGAIN || errno == EWOULDBLOCK) {
167 /* Reached end of available data for now, try later */
168 return;
169 } else {
170 MSG_ERR("Control_req_read_cmd: read failed: %s\n", dStrerror(errno));
171 err = 1;
172 break;
173 }
174 } else if (r == 1) {
175 dStr_append_l(req->cmd, buf, 1);
176 }
177 if (buf[0] == '\n')
178 break;
179 } while (r);
180
181 if (err || req->cmd->len == 0) {
182 /* Problem reading command or empty (may be caused by a reset
183 * connection). Go straight to close the request. */
184 req->stage = REQ_CLOSE;
185 } else {
186 /* Got command, strip new line and line feed */
187 char *cmd = req->cmd->str;
188 for (char *p = cmd; *p != '\0'; p++) {
189 if (*p == '\n' || *p == '\r') {
190 *p = '\0';
191 break;
192 }
193 }
194
195 /* Only determine if we need to read the body or not */
196 if (!strcmp(cmd, "load") || !strcmp(cmd, "rawload"))
197 req->stage = REQ_READ_BODY;
198 else
199 req->stage = REQ_PREPARE_REPLY;
200 }
201}
202
203static void Control_req_prepare_reply(struct control_req *req)
204{
205 int done = 1;
206 const char *cmd = req->cmd->str;
207 Dstr *r = req->reply;
209
210 if (strcmp(cmd, "help") == 0) {
211 dStr_sprintfa(r, "0\n");
212 dStr_sprintfa(r, "Commands return 0 on success or non-zero on error.\n");
213 dStr_sprintfa(r, "Available commands:\n");
214 dStr_sprintfa(r, " ping Check if dillo replies correctly:\n");
215 dStr_sprintfa(r, " pid Print PID of selected dillo process\n");
216 dStr_sprintfa(r, " reload Reload the current tab\n");
217 dStr_sprintfa(r, " ready Exits with 0 if finished loading, 1 otherwise\n");
218 dStr_sprintfa(r, " open URL Open the given URL in the current tab\n");
219 dStr_sprintfa(r, " url Print the url in the current tab\n");
220 dStr_sprintfa(r, " status [MSG] Set the status bar to MSG\n");
221 dStr_sprintfa(r, " dump Print the content of the current tab\n");
222 dStr_sprintfa(r, " hdump Print the HTTP headers of the current tab\n");
223 dStr_sprintfa(r, " load Replace the content in the current tab by stdin\n");
224 dStr_sprintfa(r, " rawload Replace the HTTP headers and content of the current\n");
225 dStr_sprintfa(r, " tab by stdin\n");
226 dStr_sprintfa(r, " quit Close dillo\n");
227 dStr_sprintfa(r, " wait [T] Wait until the current tab has finished loading\n");
228 dStr_sprintfa(r, " at most T seconds (default 60.0). Wait forever with\n");
229 dStr_sprintfa(r, " T set to 0.\n");
230 } else if (strcmp(cmd, "ping") == 0) {
231 dStr_sprintfa(r, "0\npong\n");
232 } else if (strcmp(cmd, "pid") == 0) {
233 dStr_sprintfa(r, "0\n%d\n", getpid());
234 } else if (strcmp(cmd, "reload") == 0) {
236 dStr_sprintfa(r, "0\n");
237 } else if (strcmp(cmd, "ready") == 0) {
238 dStr_sprintfa(r, "%d\n", a_UIcmd_has_finished(bw) ? 0 : 1);
239 } else if (strncmp(cmd, "open ", 5) == 0) {
240 a_UIcmd_open_urlstr(bw, cmd+5);
241 dStr_sprintfa(r, "0\n");
242 } else if (strcmp(cmd, "url") == 0) {
243 dStr_sprintfa(r, "0\n%s\n", a_UIcmd_get_location_text(bw));
244 } else if (!strcmp(cmd, "status") || !strncmp(cmd, "status ", 7)) {
245 if (cmd[6] == ' ')
246 a_UIcmd_set_msg(bw, "%s", cmd + 7);
247 else
248 a_UIcmd_set_msg(bw, "");
249 dStr_sprintfa(r, "0\n");
250 } else if (strcmp(cmd, "dump") == 0) {
252 char *buf;
253 int size;
254 dStr_sprintfa(r, "0\n");
255 if (a_Capi_get_buf(url, &buf, &size))
256 dStr_append_l(r, buf, size);
257 a_Url_free(url);
258 } else if (strcmp(cmd, "hdump") == 0) {
260 Dstr *header = a_Cache_get_header(url);
261 if (header == NULL) {
262 dStr_sprintfa(r, "1\nCurrent url not cached");
263 } else {
264 dStr_sprintfa(r, "0\n");
265 dStr_append_l(r, header->str, header->len);
266 }
267 a_Url_free(url);
268 } else if (strcmp(cmd, "load") == 0) {
270 if (url) {
271 a_Cache_entry_inject(url, req->body->str, req->body->len,
273 a_UIcmd_repush(bw);
274 dStr_sprintfa(r, "0\n");
275 a_Url_free(url);
276 } else {
277 dStr_sprintfa(r, "1\ncannot get current url");
278 }
279 } else if (strcmp(cmd, "rawload") == 0) {
281 if (url) {
282 a_Cache_entry_inject(url, req->body->str, req->body->len, 0);
283 a_UIcmd_repush(bw);
284 dStr_sprintfa(r, "0\n");
285 a_Url_free(url);
286 } else {
287 dStr_sprintfa(r, "1\ncannot get current url");
288 }
289 } else if (strcmp(cmd, "quit") == 0) {
290 a_UIcmd_close_all_bw((void *)1);
291 dStr_sprintfa(r, "0\n");
292 } else if (strncmp(cmd, "cmd ", 4) == 0) {
293 const char *cmdname = cmd + 4;
294 int ret = a_UIcmd_by_name(bw, cmdname);
295 dStr_sprintfa(r, "%d\n", ret == 0 ? 0 : 1);
296 } else if (strcmp(cmd, "wait") == 0 || strncmp(cmd, "wait ", 5) == 0) {
297 float timeout = 60.0f; /* 1 minute by default */
298 /* Contains timeout argument? */
299 if (cmd[4] == ' ')
300 timeout = atof(&cmd[5]);
301 if (a_UIcmd_has_finished(bw)) {
302 dStr_sprintfa(r, "0\n");
303 } else {
304 if (bw_waiting) {
305 dStr_sprintfa(r, "1\nalready waiting\n");
306 } else {
307 bw_waiting = bw;
308 bw_waiting_req = req;
309 done = 0;
310 /* If timeout is 0 or negative, wait forever */
311 if (timeout > 0.0f)
312 a_Timeout_add(timeout, handle_wait_timeout, NULL);
313 }
314 }
315 } else {
316 dStr_sprintfa(r, "1\nunknown command: %s\n", cmd);
317 }
318
319 if (done)
320 req->stage = REQ_SWITCH_WRITE;
321}
322
323/* Process incoming or outgoing data from a control request */
324static void Control_req_read_cb(int fd, void *data)
325{
326 struct control_req *req = data;
327
328 /* Note: These are not if-elses on purpose. */
329
330 if (req->stage == REQ_READ_COMMAND)
331 Control_req_read_cmd(req);
332
333 if (req->stage == REQ_READ_BODY) {
334 /* Read until the end of the data or error */
335 int ret = Control_read_fd(fd, req->body);
336 if (ret == 0)
337 req->stage = REQ_PREPARE_REPLY;
338 else if (ret < 0)
339 req->stage = REQ_CLOSE;
340 }
341
342 if (req->stage == REQ_PREPARE_REPLY)
343 Control_req_prepare_reply(req);
344
345 if (req->stage == REQ_SWITCH_WRITE) {
346 /* Switch callback to write */
348 a_IOwatch_add_fd(fd, DIO_WRITE, Control_req_read_cb, req);
349 req->stage = REQ_WRITE_REPLY;
350 }
351
352 if (req->stage == REQ_WRITE_REPLY) {
353 /* Keep writing until end or error */
354 if (Control_write_fd(fd, req->reply, &req->reply_offset) <= 0)
355 req->stage = REQ_CLOSE;
356 }
357
358 if (req->stage == REQ_CLOSE) {
359 /* Remove any callback */
361 dClose(fd);
362 Control_req_free(req);
363 }
364}
365
366/* Process incoming connection to control socket */
367static void Control_listen_cb(int fd, void *data)
368{
369 _MSG("Control_listen_cb called\n");
370
371 int new_fd = accept(control_fd, NULL, NULL);
372 if (new_fd < 0) {
373 if (errno == EAGAIN || errno == EWOULDBLOCK) {
374 /* Nothing? */
375 return;
376 } else {
377 MSG_ERR("accept failed: %s\n", strerror(errno));
378 exit(1);
379 }
380 }
381
382 /* From accept(2) man page:
383 *
384 * On Linux, the new socket returned by accept() does not inherit
385 * file status flags such as O_NONBLOCK and O_ASYNC from the
386 * listening socket. This behavior differs from the canonical BSD
387 * sockets implementation. Portable programs should not rely on
388 * inheritance or noninheritance of file status flags and always
389 * explicitly set all required flags on the socket returned from
390 * accept().
391 *
392 * So, enable O_NONBLOCK flag explicitly.
393 */
394 if (fcntl(new_fd, F_SETFL, O_NONBLOCK) == -1) {
395 MSG_ERR("control socket fcntl failed: %s\n", strerror(errno));
396 exit(1);
397 }
398
399 struct control_req *req = Control_req_new(new_fd);
400 a_IOwatch_add_fd(new_fd, DIO_READ, Control_req_read_cb, req);
401}
402
403/* Timeout has passed, return error */
404static void handle_wait_timeout(void *data)
405{
406 assert(bw_waiting);
407 assert(bw_waiting_req);
408
409 struct control_req *req = bw_waiting_req;
410 dStr_sprintfa(req->reply, "1\nCurrent url not cached");
411 req->stage = REQ_SWITCH_WRITE;
412
413 bw_waiting = NULL;
414
415 /* Run another pass to do progress */
416 Control_req_read_cb(req->fd, req);
417}
418
419int a_Control_is_waiting(void)
420{
421 return bw_waiting != NULL;
422}
423
425{
426 if (bw_waiting == NULL || bw_waiting != bw)
427 return;
428
429 _MSG("a_Control_notify_finish matched\n");
430
431 assert(bw_waiting_req);
432
433 struct control_req *req = bw_waiting_req;
434 dStr_sprintfa(req->reply, "0\n");
435 req->stage = REQ_SWITCH_WRITE;
436
437 a_Timeout_actually_remove(handle_wait_timeout, NULL);
438 bw_waiting = NULL;
439 bw_waiting_req = NULL;
440
441 /* Run another pass to do progress */
442 Control_req_read_cb(req->fd, req);
443}
444
445int a_Control_init(void)
446{
447 int fd;
448 if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
449 MSG("cannot create control socket: %s\n", strerror(errno));
450 return -1;
451 }
452
453 struct sockaddr_un addr;
454 addr.sun_family = AF_UNIX;
455
456#define LEN ((int) sizeof(addr.sun_path))
457
458 char ctldir[LEN];
459 if (snprintf(ctldir, LEN, "%s/.dillo/ctl", dGethomedir()) >= LEN) {
460 MSG("control socket dir path too long: %s/.dillo/ctl\n", dGethomedir());
461 return -1;
462 }
463
464 /* Only the user should have access, otherwise other users could control the
465 * browser remotely */
466 if (mkdir(ctldir, 0700) != 0) {
467 if (errno != EEXIST) {
468 MSG("cannot create ctl dir: %s\n", strerror(errno));
469 return -1;
470 }
471 }
472
473 if (snprintf(addr.sun_path, LEN, "%s/%d", ctldir, (int) getpid()) >= LEN) {
474 MSG("control socket path too long: %s/%d\n", ctldir, (int) getpid());
475 return -1;
476 }
477
478 int slen = sizeof(addr);
479
480 if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) {
481 MSG("control socket fcntl failed: %s\n", strerror(errno));
482 return -1;
483 }
484
485 int on = 1;
486 if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) < 0) {
487 MSG("control socket setsockopt failed: %s\n", strerror(errno));
488 return -1;
489 }
490
491 if (ioctl(fd, FIONBIO, (char *)&on) != 0) {
492 MSG("control socket ioctl failed: %s\n", strerror(errno));
493 return -1;
494 }
495
496 if (bind(fd, (struct sockaddr *) &addr, slen) != 0) {
497 MSG("cannot bind control socket: %s\n", strerror(errno));
498 return -1;
499 }
500
501 if (listen(fd, 32) != 0) {
502 MSG("cannot listen control socket: %s\n", strerror(errno));
503 return -1;
504 }
505
506 control_fd = fd;
507 control_path = dStrdup(addr.sun_path);
508
509 a_IOwatch_add_fd(control_fd, DIO_READ, Control_listen_cb, NULL);
510
511 return 0;
512}
513
514int a_Control_free(void)
515{
516 /* Nothing to do */
517 if (control_fd == -1)
518 return 0;
519
520 assert(control_path);
521
522 a_IOwatch_remove_fd(control_fd, DIO_READ);
523 if (dClose(control_fd) != 0) {
524 MSG_ERR("close ctl socket failed\n");
525 return -1;
526 }
527 if (unlink(control_path) != 0) {
528 MSG_ERR("unlink ctl socket failed\n");
529 return -1;
530 }
531 dFree(control_path);
532 return 0;
533}
534
535#else
536
537int a_Control_init(void) { return 0; }
538int a_Control_is_waiting(void) { return 0; }
540int a_Control_free(void) { return 0; }
541
542#endif
#define _MSG(...)
Definition bookmarks.c:45
#define MSG(...)
Definition bookmarks.c:46
Dstr * a_Cache_get_header(const DilloUrl *Url)
Definition cache.c:682
void a_Cache_entry_inject(const DilloUrl *Url, const char *buf, size_t len, int flags)
Inject full page content directly into the cache.
Definition cache.c:292
#define CA_GotHeader
Definition cache.h:34
#define CA_GotLength
Definition cache.h:36
int a_Capi_get_buf(const DilloUrl *Url, char **PBuf, int *BufSize)
Get the cache's buffer for the URL, and its size.
Definition capi.c:543
void a_Control_notify_finish(BrowserWindow *bw)
Definition control.c:539
int a_Control_free(void)
Definition control.c:540
int a_Control_is_waiting(void)
Definition control.c:538
int a_Control_init(void)
Definition control.c:537
void * dMalloc0(size_t size)
Definition dlib.c:61
void dFree(void *mem)
Definition dlib.c:68
void dStr_sprintfa(Dstr *ds, const char *format,...)
Printf-like function that appends.
Definition dlib.c:464
char * dStrdup(const char *s)
Definition dlib.c:77
Dstr * dStr_sized_new(int sz)
Create a new string with a given size.
Definition dlib.c:254
void dStr_free(Dstr *ds, int all)
Free a dillo string.
Definition dlib.c:337
int dClose(int fd)
Close a FD handling EINTR.
Definition dlib.c:979
void dStr_append_l(Dstr *ds, const char *s, int l)
Append a C string to a Dstr (providing length).
Definition dlib.c:308
char * dGethomedir(void)
Return the home directory in a static string (don't free)
Definition dlib.c:934
#define dStrerror
Definition dlib.h:113
#define BUFSIZ
Definition dlib.h:53
#define MSG_ERR(...)
Definition dpid_common.h:23
#define LEN
Definition hyphenator.cc:31
void a_IOwatch_add_fd(int fd, int when, Fl_FD_Handler Callback, void *usr_data=0)
Hook a Callback for a certain activities in a FD.
Definition iowatch.cc:22
void a_IOwatch_remove_fd(int fd, int when)
Remove a Callback for a given FD (or just remove some events)
Definition iowatch.cc:32
#define DIO_READ
Definition iowatch.hh:7
#define DIO_WRITE
Definition iowatch.hh:8
Contains the specific data for a single window.
Definition bw.h:27
Definition url.h:88
Definition dlib.h:120
Dstr_char_t * str
Definition dlib.h:123
int len
Definition dlib.h:122
void a_Timeout_add(float t, TimeoutCb_t cb, void *cbdata)
Hook a one-time timeout function 'cb' after 't' seconds with 'cbdata" as its data.
Definition timeout.cc:26
void a_Timeout_actually_remove(TimeoutCb_t cb, void *data)
Definition timeout.cc:49
void a_UIcmd_set_msg(BrowserWindow *bw, const char *format,...)
Definition uicmd.cc:1622
void a_UIcmd_reload_all_active()
Definition uicmd.cc:955
BrowserWindow * a_UIcmd_get_first_active_bw(void)
Definition uicmd.cc:940
int a_UIcmd_by_name(void *vbw, const char *cmd_name)
Definition uicmd.cc:1761
void a_UIcmd_close_all_bw(void *force)
Definition uicmd.cc:725
void a_UIcmd_open_urlstr(void *vbw, const char *urlstr)
Definition uicmd.cc:771
int a_UIcmd_has_finished(BrowserWindow *bw)
Definition uicmd.cc:1652
void a_UIcmd_repush(void *vbw)
Definition uicmd.cc:968
char * a_UIcmd_get_location_text(BrowserWindow *bw)
Definition uicmd.cc:1551
void a_Url_free(DilloUrl *url)
Free a DilloUrl.
Definition url.c:208
DilloUrl * a_Url_new(const char *url_str, const char *base_url)
Transform (and resolve) an URL string into the respective DilloURL.
Definition url.c:371