2005-09-15 09:58:31 -04:00
|
|
|
/* Inter-instances internal communication socket interface */
|
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
#include "config.h"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <errno.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h> /* OS/2 needs this after sys/types.h */
|
|
|
|
#ifdef HAVE_UNISTD_H
|
|
|
|
#include <unistd.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* struct timeval */
|
|
|
|
#ifdef HAVE_SYS_TIME_H
|
|
|
|
#include <sys/time.h>
|
|
|
|
#endif
|
|
|
|
#include <time.h>
|
|
|
|
|
|
|
|
/* Blame BSD for position of this includes. */
|
|
|
|
#ifdef HAVE_NETINET_IN_H
|
|
|
|
#include <netinet/in.h>
|
|
|
|
#endif
|
|
|
|
#ifdef HAVE_SYS_SELECT_H
|
|
|
|
#include <sys/select.h>
|
|
|
|
#endif
|
|
|
|
#ifdef HAVE_SYS_SOCKET_H
|
|
|
|
#include <sys/socket.h> /* OS/2 needs this after sys/types.h */
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef HAVE_SYS_UN_H
|
|
|
|
#include <sys/un.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef HAVE_ARPA_INET_H
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "elinks.h"
|
|
|
|
|
|
|
|
#include "config/home.h"
|
2020-05-21 10:53:43 -04:00
|
|
|
#include "config/options.h"
|
2021-08-08 15:25:08 -04:00
|
|
|
#include "intl/libintl.h"
|
2005-09-15 09:58:31 -04:00
|
|
|
#include "main/interlink.h"
|
2020-05-21 10:53:43 -04:00
|
|
|
#include "main/main.h"
|
2005-09-15 09:58:31 -04:00
|
|
|
#include "main/select.h"
|
|
|
|
#include "osdep/osdep.h"
|
2020-05-20 12:35:40 -04:00
|
|
|
#include "session/session.h"
|
2005-09-15 09:58:31 -04:00
|
|
|
#include "util/conv.h"
|
|
|
|
#include "util/error.h"
|
|
|
|
#include "util/memory.h"
|
|
|
|
#include "util/string.h"
|
|
|
|
|
|
|
|
/* Testing purpose. Do not remove. */
|
|
|
|
/* At some point zas added experimental support for interlinking
|
|
|
|
* ELinks' over a TCP socket. However, it was removed due to some
|
|
|
|
* unsettled security related questions. More info in:
|
|
|
|
*
|
|
|
|
* src/config/options.c (1.233 -> 1.234)
|
|
|
|
* src/network/interlink.c (1.54 -> 1.55)
|
|
|
|
*
|
|
|
|
* The log message:
|
|
|
|
*
|
|
|
|
* Experimental remote mode is now (almost) functionnal. To test define
|
|
|
|
* ELINKS_REMOTE in setup.h, compile elinks, add a second ip to your
|
|
|
|
* machine or use a second machine, then launch elinks -listen
|
|
|
|
* <your_first_ip> on one side, and elinks -remote <your_first_ip> on the
|
|
|
|
* second side. There is many things to improve, and it may not function as
|
|
|
|
* you hope it does, but feel free to provide enhancements. Note this
|
|
|
|
* feature may disappear soon if none is interested in. Please report
|
|
|
|
* issues/comments/ideas/bugs on #elinks.
|
|
|
|
*/
|
2006-01-12 18:10:06 -05:00
|
|
|
|
|
|
|
/* Set to 1 to make ELinks use a tcp socket bound to loopback address with
|
|
|
|
* port defined by ELINKS_PORT (setup.h) for internal communications
|
|
|
|
* instead of a socket file, which is the preferred way. */
|
2005-09-15 09:58:31 -04:00
|
|
|
#if 0
|
2006-01-12 18:10:06 -05:00
|
|
|
#define CONFIG_TCP_INTERLINK
|
2005-09-15 09:58:31 -04:00
|
|
|
#endif
|
|
|
|
|
|
|
|
/* Common to both AF_UNIX and AF_INET stuff. */
|
|
|
|
struct socket_info {
|
|
|
|
struct sockaddr *addr;
|
|
|
|
int size;
|
|
|
|
int fd;
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Accepted socket info */
|
|
|
|
static struct socket_info s_info_accept;
|
|
|
|
|
|
|
|
/* Listening socket info */
|
|
|
|
static struct socket_info s_info_listen;
|
|
|
|
|
|
|
|
/* Connect socket info */
|
|
|
|
static struct socket_info s_info_connect;
|
|
|
|
|
|
|
|
/* Type of address requested (for get_address()) */
|
|
|
|
enum addr_type {
|
|
|
|
ADDR_IP_CLIENT,
|
|
|
|
ADDR_IP_SERVER,
|
|
|
|
ADDR_ANY_SERVER,
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
#ifndef CONFIG_TCP_INTERLINK
|
|
|
|
|
|
|
|
/*** Unix file socket for internal communication. ***/
|
|
|
|
|
|
|
|
/* Compute socket file path and allocate space for it.
|
|
|
|
* It returns 0 on error (in this case, there's no need
|
|
|
|
* to free anything).
|
|
|
|
* It returns 1 on success. */
|
|
|
|
static int
|
2019-04-21 06:27:40 -04:00
|
|
|
get_sun_path(struct string *sun_path)
|
2005-09-15 09:58:31 -04:00
|
|
|
{
|
2022-12-26 11:36:31 -05:00
|
|
|
char *xdg_config_home = get_xdg_config_home();
|
|
|
|
|
2005-09-15 09:58:31 -04:00
|
|
|
assert(sun_path);
|
|
|
|
if_assert_failed return 0;
|
|
|
|
|
2022-12-26 11:36:31 -05:00
|
|
|
if (!xdg_config_home) return 0;
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
if (!init_string(sun_path)) return 0;
|
|
|
|
|
2022-12-26 11:36:31 -05:00
|
|
|
add_to_string(sun_path, xdg_config_home);
|
2005-09-15 09:58:31 -04:00
|
|
|
add_to_string(sun_path, ELINKS_SOCK_NAME);
|
|
|
|
add_long_to_string(sun_path,
|
|
|
|
get_cmd_opt_int("session-ring"));
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* @type is ignored here => always local. */
|
|
|
|
static int
|
|
|
|
get_address(struct socket_info *info, enum addr_type type)
|
|
|
|
{
|
|
|
|
struct sockaddr_un *addr = NULL;
|
|
|
|
int sun_path_freespace;
|
2019-04-21 06:27:40 -04:00
|
|
|
struct string path;
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
assert(info);
|
|
|
|
if_assert_failed return -1;
|
|
|
|
|
|
|
|
if (!get_sun_path(&path)) return -1;
|
|
|
|
|
|
|
|
/* Linux defines that as:
|
|
|
|
* #define UNIX_PATH_MAX 108
|
|
|
|
* struct sockaddr_un {
|
|
|
|
* sa_family_t sun_family;
|
|
|
|
* char sun_path[UNIX_PATH_MAX];
|
|
|
|
* };
|
|
|
|
*
|
|
|
|
* UNIX_PATH_MAX is not defined on all systems, so we'll use sizeof().
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* Following code may need to be changed if at some time the
|
|
|
|
* struct sockaddr_un mess is fixed. For now, i tried to make it
|
|
|
|
* sure and portable.
|
|
|
|
*
|
|
|
|
* Extract from glibc documentation:
|
|
|
|
* char sun_path[108]
|
|
|
|
* This is the file name to use.
|
|
|
|
* Incomplete: Why is 108 a magic number?
|
|
|
|
* RMS suggests making this a zero-length array and tweaking the example
|
|
|
|
* following to use alloca to allocate an appropriate amount of storage
|
|
|
|
* based on the length of the filename.
|
|
|
|
*
|
|
|
|
* But at this day (2003/06/18) it seems there's no implementation of such
|
|
|
|
* thing.
|
|
|
|
* If it was the case, then following code will always generate an error.
|
|
|
|
* --Zas
|
|
|
|
*/
|
|
|
|
|
|
|
|
sun_path_freespace = sizeof(addr->sun_path) - (path.length + 1);
|
|
|
|
if (sun_path_freespace < 0) {
|
|
|
|
INTERNAL("Socket path name '%s' is too long: %d >= %zu",
|
|
|
|
path.source, path.length, sizeof(addr->sun_path));
|
|
|
|
goto free_and_error;
|
|
|
|
}
|
|
|
|
|
2022-01-16 15:08:50 -05:00
|
|
|
addr = (struct sockaddr_un *)mem_calloc(1, sizeof(*addr));
|
2005-09-15 09:58:31 -04:00
|
|
|
if (!addr) goto free_and_error;
|
|
|
|
|
|
|
|
memcpy(addr->sun_path, path.source, path.length); /* ending '\0' is done by calloc() */
|
|
|
|
done_string(&path);
|
|
|
|
|
|
|
|
addr->sun_family = AF_UNIX;
|
|
|
|
|
|
|
|
info->addr = (struct sockaddr *) addr;
|
|
|
|
/* The size of the address is the offset of the start of the filename,
|
|
|
|
* plus its length, plus one for the terminating null byte (well, this
|
|
|
|
* last byte may or not be needed it depends of....).
|
|
|
|
* Alternatively we can use SUN_LEN() macro but this one is not always
|
|
|
|
* defined nor always defined in the same way. --Zas */
|
|
|
|
info->size = sizeof(*addr) - sun_path_freespace;
|
|
|
|
|
|
|
|
return AF_UNIX;
|
|
|
|
|
|
|
|
free_and_error:
|
|
|
|
done_string(&path);
|
|
|
|
mem_free_if(addr);
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
alloc_address(struct socket_info *info)
|
|
|
|
{
|
|
|
|
struct sockaddr_un *sa;
|
|
|
|
|
|
|
|
assert(info);
|
|
|
|
if_assert_failed return 0;
|
|
|
|
|
|
|
|
/* calloc() is safer there. */
|
2022-01-16 15:08:50 -05:00
|
|
|
sa = (struct sockaddr_un *)mem_calloc(1, sizeof(*sa));
|
2005-09-15 09:58:31 -04:00
|
|
|
if (!sa) return 0;
|
|
|
|
|
|
|
|
info->addr = (struct sockaddr *) sa;
|
|
|
|
info->size = sizeof(*sa);
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
unlink_unix(struct sockaddr *addr)
|
|
|
|
{
|
|
|
|
assert(addr);
|
|
|
|
if_assert_failed return;
|
|
|
|
|
|
|
|
unlink(((struct sockaddr_un *) addr)->sun_path);
|
|
|
|
}
|
|
|
|
|
|
|
|
#define setsock_reuse_addr(fd)
|
|
|
|
|
|
|
|
|
|
|
|
#else /* CONFIG_TCP_INTERLINK */
|
|
|
|
|
|
|
|
/*** TCP socket for internal communication. ***/
|
|
|
|
/* FIXME: IPv6 support. */
|
|
|
|
|
|
|
|
/* These may not be defined in netinet/in.h on some systems. */
|
|
|
|
#ifndef INADDR_LOOPBACK
|
|
|
|
#define INADDR_LOOPBACK ((struct in_addr) 0x7f000001)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef IPPORT_USERRESERVED
|
|
|
|
#define IPPORT_USERRESERVED 5000
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* @type is not used for now, and is ignored, it will
|
|
|
|
* be used in remote mode feature. */
|
|
|
|
static int
|
|
|
|
get_address(struct socket_info *info, enum addr_type type)
|
|
|
|
{
|
|
|
|
struct sockaddr_in *sin;
|
|
|
|
uint16_t port;
|
|
|
|
|
|
|
|
assert(info);
|
|
|
|
if_assert_failed return -1;
|
|
|
|
|
|
|
|
/* Each ring is bind to ELINKS_PORT + ring number. */
|
|
|
|
port = ELINKS_PORT + get_cmd_opt_int("session-ring");
|
|
|
|
if (port < IPPORT_USERRESERVED)
|
|
|
|
return -1; /* Just in case of... */
|
|
|
|
|
2022-01-16 15:08:50 -05:00
|
|
|
sin = (struct sockaddr_in *)mem_calloc(1, sizeof(*sin));
|
2005-09-15 09:58:31 -04:00
|
|
|
if (!sin) return -1;
|
|
|
|
|
|
|
|
sin->sin_family = AF_INET;
|
|
|
|
sin->sin_port = htons(port);
|
|
|
|
sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
|
|
|
|
|
|
info->addr = (struct sockaddr *) sin;
|
|
|
|
info->size = sizeof(*sin);
|
|
|
|
|
2006-01-03 18:32:58 -05:00
|
|
|
return PF_INET;
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
alloc_address(struct socket_info *info)
|
|
|
|
{
|
|
|
|
struct sockaddr_in *sa;
|
|
|
|
|
|
|
|
assert(info);
|
|
|
|
if_assert_failed return 0;
|
|
|
|
|
|
|
|
/* calloc() is safer there. */
|
2022-01-16 15:08:50 -05:00
|
|
|
sa = (struct sockaddr_in *)mem_calloc(1, sizeof(*sa));
|
2005-09-15 09:58:31 -04:00
|
|
|
if (!sa) return 0;
|
|
|
|
|
|
|
|
info->addr = (struct sockaddr *) sa;
|
|
|
|
info->size = sizeof(*sa);
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if defined(SOL_SOCKET) && defined(SO_REUSEADDR)
|
|
|
|
static void
|
|
|
|
setsock_reuse_addr(int fd)
|
|
|
|
{
|
|
|
|
int reuse_addr = 1;
|
|
|
|
|
|
|
|
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
|
|
|
|
(void *) &reuse_addr, sizeof(reuse_addr));
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
#define setsock_reuse_addr(fd)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#define unlink_unix(s)
|
|
|
|
|
|
|
|
#endif /* CONFIG_TCP_INTERLINK */
|
|
|
|
|
|
|
|
/* Max. number of bind attempts. */
|
|
|
|
#define MAX_BIND_TRIES 3
|
|
|
|
/* Base delay in useconds between bind attempts.
|
|
|
|
* We will sleep that time on first attempt, then
|
|
|
|
* 2 * delay, then 3 * delay .... */
|
|
|
|
#define BIND_TRIES_DELAY 100000
|
|
|
|
/* Number of connections in listen backlog. */
|
|
|
|
#define LISTEN_BACKLOG 100
|
|
|
|
|
|
|
|
static void
|
2022-02-17 13:24:30 -05:00
|
|
|
report_af_unix_error(const char *function, int error)
|
2005-09-15 09:58:31 -04:00
|
|
|
{
|
|
|
|
ERROR(gettext("The call to %s failed: %d (%s)"),
|
2021-01-02 10:20:27 -05:00
|
|
|
function, error, (char *) strerror(error));
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Called when we receive a connection on listening socket. */
|
|
|
|
static void
|
|
|
|
af_unix_connection(struct socket_info *info)
|
|
|
|
{
|
|
|
|
int ns;
|
2021-01-02 10:20:27 -05:00
|
|
|
socklen_t l;
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
assert(info);
|
|
|
|
if_assert_failed return;
|
|
|
|
|
|
|
|
l = info->size;
|
|
|
|
|
|
|
|
memset(info->addr, 0, l);
|
|
|
|
ns = accept(info->fd, info->addr, &l);
|
|
|
|
if (ns < 0) {
|
|
|
|
report_af_unix_error("accept()", errno);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
init_term(ns, ns);
|
|
|
|
|
|
|
|
set_highpri();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* usleep() is not portable, so we use this replacement.
|
|
|
|
* TODO: move it to somewhere. */
|
|
|
|
void
|
|
|
|
elinks_usleep(unsigned long useconds)
|
|
|
|
{
|
|
|
|
struct timeval delay;
|
2019-11-15 10:37:02 -05:00
|
|
|
fd_set dummy1, dummy2, dummy3;
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2019-11-15 10:37:02 -05:00
|
|
|
FD_ZERO(&dummy1);
|
|
|
|
FD_ZERO(&dummy2);
|
|
|
|
FD_ZERO(&dummy3);
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
delay.tv_sec = 0;
|
|
|
|
delay.tv_usec = useconds;
|
2022-05-09 12:53:36 -04:00
|
|
|
select2(0, &dummy1, &dummy2, &dummy3, &delay);
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Listen on socket for internal ELinks communication.
|
|
|
|
* Returns -1 on error
|
|
|
|
* or listened file descriptor on success. */
|
|
|
|
static int
|
|
|
|
bind_to_af_unix(void)
|
|
|
|
{
|
2006-01-10 17:35:22 -05:00
|
|
|
mode_t saved_mask = umask(S_IXUSR | S_IRWXG | S_IRWXO);
|
2005-09-15 09:58:31 -04:00
|
|
|
int attempts = 0;
|
2006-01-03 18:32:58 -05:00
|
|
|
int pf = get_address(&s_info_listen, ADDR_IP_SERVER);
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2006-01-03 18:32:58 -05:00
|
|
|
if (pf == -1) goto free_and_error;
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2005-10-30 00:11:23 -04:00
|
|
|
while (1) {
|
2006-01-03 18:32:58 -05:00
|
|
|
s_info_listen.fd = socket(pf, SOCK_STREAM, 0);
|
2005-10-30 00:11:59 -04:00
|
|
|
if (s_info_listen.fd == -1) {
|
|
|
|
report_af_unix_error("socket()", errno);
|
|
|
|
goto free_and_error;
|
|
|
|
}
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2005-10-30 00:11:59 -04:00
|
|
|
setsock_reuse_addr(s_info_listen.fd);
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2005-10-30 00:13:12 -04:00
|
|
|
if (bind(s_info_listen.fd, s_info_listen.addr, s_info_listen.size) >= 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (errno != EADDRINUSE)
|
|
|
|
report_af_unix_error("bind()", errno);
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2005-10-30 00:16:19 -04:00
|
|
|
++attempts;
|
2005-10-30 00:23:04 -04:00
|
|
|
|
|
|
|
if (attempts == MAX_BIND_TRIES)
|
|
|
|
unlink_unix(s_info_listen.addr);
|
|
|
|
|
2005-10-30 00:16:19 -04:00
|
|
|
if (attempts > MAX_BIND_TRIES)
|
2005-10-30 00:13:12 -04:00
|
|
|
goto free_and_error;
|
2005-10-30 00:13:59 -04:00
|
|
|
|
|
|
|
elinks_usleep(BIND_TRIES_DELAY * attempts);
|
|
|
|
close(s_info_listen.fd);
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Listen and accept. */
|
|
|
|
if (!alloc_address(&s_info_accept))
|
|
|
|
goto free_and_error;
|
|
|
|
|
|
|
|
s_info_accept.fd = s_info_listen.fd;
|
|
|
|
|
|
|
|
if (listen(s_info_listen.fd, LISTEN_BACKLOG)) {
|
|
|
|
report_af_unix_error("listen()", errno);
|
|
|
|
goto free_and_error;
|
|
|
|
}
|
|
|
|
|
|
|
|
set_handlers(s_info_listen.fd, (void (*)(void *)) af_unix_connection,
|
|
|
|
NULL, NULL, &s_info_accept);
|
|
|
|
|
|
|
|
umask(saved_mask);
|
|
|
|
return s_info_listen.fd;
|
|
|
|
|
|
|
|
free_and_error:
|
|
|
|
done_interlink();
|
|
|
|
umask(saved_mask);
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Connect to an listening socket for internal ELinks communication.
|
|
|
|
* Returns -1 on error
|
|
|
|
* or file descriptor on success. */
|
|
|
|
static int
|
|
|
|
connect_to_af_unix(void)
|
|
|
|
{
|
2006-01-03 18:32:58 -05:00
|
|
|
int pf = get_address(&s_info_connect, ADDR_IP_CLIENT);
|
2005-09-15 09:58:31 -04:00
|
|
|
|
[interlink] speed up first elinks, and use pipe trigger for fork_on_start
When the first elinks session starts (whether fork_on_start or not), it
tries to connect on AF_UNIX socket, to see if an existing master is
present. The current code implements a retry loop with linear-increase
retry backoff, 50ms *= attempts, with 3 attempts, total delay 300ms,
before concluding master is not present.
After forking for fork_on_start, we do have to wait for the new master
to finish setup, so we can connect to it. This is the case where retry
loop might be useful. However in fork_on_start=0 case, there should be
no reason to ever wait: if we get ECONNREFUSED or ENOENT, the master
simply is not there, we can go on about our business as the master.
With fork_on_start=1 however, we will race with the child (that will
become the new master), which has to complete its socket/bind/listen
sequence. Therefore we typically have to wait another 50ms (first
attempt delay), for a total of 350ms delay.
In both cases, there does not seem to be a purpose to the initial
connection attempt retry. Conclusion after connect() failure should be
authoritative: there is no master. We do not race with anyone.
If we have done fork_on_start and have to wait, we can instead open a
pipe before forking, and wait for the "new" master (its child) to send
us a byte over the pipe. Thus, we do not need to poll, but can simply
block until we get the trigger, to indicate that AF_UNIX socket setup
has completed.
This speeds up the first start of elinks by 300ms for fork_on_start=0,
and between 300-350ms for fork_on_start=1 (or possibly more, if the
machine is loaded and child did not finish AF_UNIX setup in first 50ms).
2024-04-13 01:51:01 -04:00
|
|
|
if (pf != -1) {
|
2006-01-03 18:32:58 -05:00
|
|
|
s_info_connect.fd = socket(pf, SOCK_STREAM, 0);
|
2005-09-15 09:58:31 -04:00
|
|
|
if (s_info_connect.fd == -1) {
|
|
|
|
report_af_unix_error("socket()", errno);
|
[interlink] speed up first elinks, and use pipe trigger for fork_on_start
When the first elinks session starts (whether fork_on_start or not), it
tries to connect on AF_UNIX socket, to see if an existing master is
present. The current code implements a retry loop with linear-increase
retry backoff, 50ms *= attempts, with 3 attempts, total delay 300ms,
before concluding master is not present.
After forking for fork_on_start, we do have to wait for the new master
to finish setup, so we can connect to it. This is the case where retry
loop might be useful. However in fork_on_start=0 case, there should be
no reason to ever wait: if we get ECONNREFUSED or ENOENT, the master
simply is not there, we can go on about our business as the master.
With fork_on_start=1 however, we will race with the child (that will
become the new master), which has to complete its socket/bind/listen
sequence. Therefore we typically have to wait another 50ms (first
attempt delay), for a total of 350ms delay.
In both cases, there does not seem to be a purpose to the initial
connection attempt retry. Conclusion after connect() failure should be
authoritative: there is no master. We do not race with anyone.
If we have done fork_on_start and have to wait, we can instead open a
pipe before forking, and wait for the "new" master (its child) to send
us a byte over the pipe. Thus, we do not need to poll, but can simply
block until we get the trigger, to indicate that AF_UNIX socket setup
has completed.
This speeds up the first start of elinks by 300ms for fork_on_start=0,
and between 300-350ms for fork_on_start=1 (or possibly more, if the
machine is loaded and child did not finish AF_UNIX setup in first 50ms).
2024-04-13 01:51:01 -04:00
|
|
|
} else {
|
|
|
|
if (connect(s_info_connect.fd, s_info_connect.addr,
|
|
|
|
s_info_connect.size) >= 0) {
|
2005-09-15 09:58:31 -04:00
|
|
|
return s_info_connect.fd;
|
[interlink] speed up first elinks, and use pipe trigger for fork_on_start
When the first elinks session starts (whether fork_on_start or not), it
tries to connect on AF_UNIX socket, to see if an existing master is
present. The current code implements a retry loop with linear-increase
retry backoff, 50ms *= attempts, with 3 attempts, total delay 300ms,
before concluding master is not present.
After forking for fork_on_start, we do have to wait for the new master
to finish setup, so we can connect to it. This is the case where retry
loop might be useful. However in fork_on_start=0 case, there should be
no reason to ever wait: if we get ECONNREFUSED or ENOENT, the master
simply is not there, we can go on about our business as the master.
With fork_on_start=1 however, we will race with the child (that will
become the new master), which has to complete its socket/bind/listen
sequence. Therefore we typically have to wait another 50ms (first
attempt delay), for a total of 350ms delay.
In both cases, there does not seem to be a purpose to the initial
connection attempt retry. Conclusion after connect() failure should be
authoritative: there is no master. We do not race with anyone.
If we have done fork_on_start and have to wait, we can instead open a
pipe before forking, and wait for the "new" master (its child) to send
us a byte over the pipe. Thus, we do not need to poll, but can simply
block until we get the trigger, to indicate that AF_UNIX socket setup
has completed.
This speeds up the first start of elinks by 300ms for fork_on_start=0,
and between 300-350ms for fork_on_start=1 (or possibly more, if the
machine is loaded and child did not finish AF_UNIX setup in first 50ms).
2024-04-13 01:51:01 -04:00
|
|
|
}
|
|
|
|
if (errno != ECONNREFUSED && errno != ENOENT) {
|
|
|
|
report_af_unix_error("connect()", errno);
|
|
|
|
}
|
|
|
|
else if (close(s_info_connect.fd) == -1) {
|
|
|
|
report_af_unix_error("close(afsock)", errno);
|
|
|
|
}
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mem_free_set(&s_info_connect.addr, NULL);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void safe_close(int *fd) {
|
|
|
|
if (*fd == -1) return;
|
|
|
|
close(*fd);
|
|
|
|
*fd = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Free all allocated memory and close all descriptors if
|
|
|
|
* needed. */
|
|
|
|
void
|
|
|
|
done_interlink(void)
|
|
|
|
{
|
|
|
|
/* We test for addr != NULL since
|
|
|
|
* if it was not allocated then fd is not
|
|
|
|
* initialized and we don't want to close
|
|
|
|
* fd 0 ;). --Zas */
|
|
|
|
if (s_info_listen.addr) {
|
|
|
|
safe_close(&s_info_listen.fd);
|
|
|
|
unlink_unix(s_info_listen.addr);
|
|
|
|
mem_free(s_info_listen.addr);
|
|
|
|
s_info_listen.addr = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (s_info_connect.addr) {
|
|
|
|
safe_close(&s_info_connect.fd);
|
|
|
|
mem_free(s_info_connect.addr);
|
|
|
|
s_info_connect.addr = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (s_info_accept.addr) {
|
|
|
|
safe_close(&s_info_accept.fd);
|
|
|
|
mem_free(s_info_accept.addr);
|
|
|
|
s_info_accept.addr = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Initialize sockets for internal ELinks communication.
|
|
|
|
* If connect succeeds it returns file descriptor,
|
|
|
|
* else it tries to bind and listen on a socket, and
|
|
|
|
* return -1
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
init_interlink(void)
|
|
|
|
{
|
[interlink] speed up first elinks, and use pipe trigger for fork_on_start
When the first elinks session starts (whether fork_on_start or not), it
tries to connect on AF_UNIX socket, to see if an existing master is
present. The current code implements a retry loop with linear-increase
retry backoff, 50ms *= attempts, with 3 attempts, total delay 300ms,
before concluding master is not present.
After forking for fork_on_start, we do have to wait for the new master
to finish setup, so we can connect to it. This is the case where retry
loop might be useful. However in fork_on_start=0 case, there should be
no reason to ever wait: if we get ECONNREFUSED or ENOENT, the master
simply is not there, we can go on about our business as the master.
With fork_on_start=1 however, we will race with the child (that will
become the new master), which has to complete its socket/bind/listen
sequence. Therefore we typically have to wait another 50ms (first
attempt delay), for a total of 350ms delay.
In both cases, there does not seem to be a purpose to the initial
connection attempt retry. Conclusion after connect() failure should be
authoritative: there is no master. We do not race with anyone.
If we have done fork_on_start and have to wait, we can instead open a
pipe before forking, and wait for the "new" master (its child) to send
us a byte over the pipe. Thus, we do not need to poll, but can simply
block until we get the trigger, to indicate that AF_UNIX socket setup
has completed.
This speeds up the first start of elinks by 300ms for fork_on_start=0,
and between 300-350ms for fork_on_start=1 (or possibly more, if the
machine is loaded and child did not finish AF_UNIX setup in first 50ms).
2024-04-13 01:51:01 -04:00
|
|
|
int pipefds[2];
|
2024-05-05 14:46:48 -04:00
|
|
|
char trigger = 0;
|
[interlink] speed up first elinks, and use pipe trigger for fork_on_start
When the first elinks session starts (whether fork_on_start or not), it
tries to connect on AF_UNIX socket, to see if an existing master is
present. The current code implements a retry loop with linear-increase
retry backoff, 50ms *= attempts, with 3 attempts, total delay 300ms,
before concluding master is not present.
After forking for fork_on_start, we do have to wait for the new master
to finish setup, so we can connect to it. This is the case where retry
loop might be useful. However in fork_on_start=0 case, there should be
no reason to ever wait: if we get ECONNREFUSED or ENOENT, the master
simply is not there, we can go on about our business as the master.
With fork_on_start=1 however, we will race with the child (that will
become the new master), which has to complete its socket/bind/listen
sequence. Therefore we typically have to wait another 50ms (first
attempt delay), for a total of 350ms delay.
In both cases, there does not seem to be a purpose to the initial
connection attempt retry. Conclusion after connect() failure should be
authoritative: there is no master. We do not race with anyone.
If we have done fork_on_start and have to wait, we can instead open a
pipe before forking, and wait for the "new" master (its child) to send
us a byte over the pipe. Thus, we do not need to poll, but can simply
block until we get the trigger, to indicate that AF_UNIX socket setup
has completed.
This speeds up the first start of elinks by 300ms for fork_on_start=0,
and between 300-350ms for fork_on_start=1 (or possibly more, if the
machine is loaded and child did not finish AF_UNIX setup in first 50ms).
2024-04-13 01:51:01 -04:00
|
|
|
ssize_t n;
|
2005-09-15 09:58:31 -04:00
|
|
|
int fd = connect_to_af_unix();
|
|
|
|
|
2020-05-20 12:35:40 -04:00
|
|
|
if (fd != -1 || remote_session_flags) return fd;
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2020-05-21 10:53:43 -04:00
|
|
|
parse_options_again();
|
2020-05-17 10:35:58 -04:00
|
|
|
|
2020-05-21 10:53:43 -04:00
|
|
|
if (get_opt_bool("ui.sessions.fork_on_start", NULL)) {
|
[fg] track master proc for suspend via pids, not term fds, for fork_on_start
When ui.sessions.fork_on_start, we fork a process that has a different
fdout than get_output_handle() (which has hardcoded fdout of 1), so it
will never be considered term->master, yet this is the process we want
to wake up in SIGTSTP handler.
Since we cannot rely on term->master to determine if we are the master
process, we instead move master_pid to be set explicitly at the places
where we have information about whether our process is a master or a
slave: first on start, then once the interlink determination has been
made.
master_pid has to be set in both parent and child, because both will get
suspended and need to know which one needs to resume in background with
SIGCONT (the master). We can't inherit from the parent because it's
unknown at the time of fork.
Previously, master_pid worked correctly with fork_on_start=0,
-no-connect or -dump, but not with fork_on_start=1.
See #287 for background.
2024-04-10 17:02:36 -04:00
|
|
|
|
[interlink] speed up first elinks, and use pipe trigger for fork_on_start
When the first elinks session starts (whether fork_on_start or not), it
tries to connect on AF_UNIX socket, to see if an existing master is
present. The current code implements a retry loop with linear-increase
retry backoff, 50ms *= attempts, with 3 attempts, total delay 300ms,
before concluding master is not present.
After forking for fork_on_start, we do have to wait for the new master
to finish setup, so we can connect to it. This is the case where retry
loop might be useful. However in fork_on_start=0 case, there should be
no reason to ever wait: if we get ECONNREFUSED or ENOENT, the master
simply is not there, we can go on about our business as the master.
With fork_on_start=1 however, we will race with the child (that will
become the new master), which has to complete its socket/bind/listen
sequence. Therefore we typically have to wait another 50ms (first
attempt delay), for a total of 350ms delay.
In both cases, there does not seem to be a purpose to the initial
connection attempt retry. Conclusion after connect() failure should be
authoritative: there is no master. We do not race with anyone.
If we have done fork_on_start and have to wait, we can instead open a
pipe before forking, and wait for the "new" master (its child) to send
us a byte over the pipe. Thus, we do not need to poll, but can simply
block until we get the trigger, to indicate that AF_UNIX socket setup
has completed.
This speeds up the first start of elinks by 300ms for fork_on_start=0,
and between 300-350ms for fork_on_start=1 (or possibly more, if the
machine is loaded and child did not finish AF_UNIX setup in first 50ms).
2024-04-13 01:51:01 -04:00
|
|
|
pid_t pid;
|
|
|
|
if (pipe(pipefds) == -1) {
|
|
|
|
report_af_unix_error("pipe()", errno);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
pid = fork();
|
2020-05-17 10:35:58 -04:00
|
|
|
|
2020-05-21 10:53:43 -04:00
|
|
|
if (pid == -1) return -1;
|
2020-05-17 10:35:58 -04:00
|
|
|
|
2020-05-21 10:53:43 -04:00
|
|
|
if (pid > 0) {
|
[interlink] speed up first elinks, and use pipe trigger for fork_on_start
When the first elinks session starts (whether fork_on_start or not), it
tries to connect on AF_UNIX socket, to see if an existing master is
present. The current code implements a retry loop with linear-increase
retry backoff, 50ms *= attempts, with 3 attempts, total delay 300ms,
before concluding master is not present.
After forking for fork_on_start, we do have to wait for the new master
to finish setup, so we can connect to it. This is the case where retry
loop might be useful. However in fork_on_start=0 case, there should be
no reason to ever wait: if we get ECONNREFUSED or ENOENT, the master
simply is not there, we can go on about our business as the master.
With fork_on_start=1 however, we will race with the child (that will
become the new master), which has to complete its socket/bind/listen
sequence. Therefore we typically have to wait another 50ms (first
attempt delay), for a total of 350ms delay.
In both cases, there does not seem to be a purpose to the initial
connection attempt retry. Conclusion after connect() failure should be
authoritative: there is no master. We do not race with anyone.
If we have done fork_on_start and have to wait, we can instead open a
pipe before forking, and wait for the "new" master (its child) to send
us a byte over the pipe. Thus, we do not need to poll, but can simply
block until we get the trigger, to indicate that AF_UNIX socket setup
has completed.
This speeds up the first start of elinks by 300ms for fork_on_start=0,
and between 300-350ms for fork_on_start=1 (or possibly more, if the
machine is loaded and child did not finish AF_UNIX setup in first 50ms).
2024-04-13 01:51:01 -04:00
|
|
|
int error = 0;
|
2024-04-10 09:03:44 -04:00
|
|
|
master_pid = pid;
|
[interlink] speed up first elinks, and use pipe trigger for fork_on_start
When the first elinks session starts (whether fork_on_start or not), it
tries to connect on AF_UNIX socket, to see if an existing master is
present. The current code implements a retry loop with linear-increase
retry backoff, 50ms *= attempts, with 3 attempts, total delay 300ms,
before concluding master is not present.
After forking for fork_on_start, we do have to wait for the new master
to finish setup, so we can connect to it. This is the case where retry
loop might be useful. However in fork_on_start=0 case, there should be
no reason to ever wait: if we get ECONNREFUSED or ENOENT, the master
simply is not there, we can go on about our business as the master.
With fork_on_start=1 however, we will race with the child (that will
become the new master), which has to complete its socket/bind/listen
sequence. Therefore we typically have to wait another 50ms (first
attempt delay), for a total of 350ms delay.
In both cases, there does not seem to be a purpose to the initial
connection attempt retry. Conclusion after connect() failure should be
authoritative: there is no master. We do not race with anyone.
If we have done fork_on_start and have to wait, we can instead open a
pipe before forking, and wait for the "new" master (its child) to send
us a byte over the pipe. Thus, we do not need to poll, but can simply
block until we get the trigger, to indicate that AF_UNIX socket setup
has completed.
This speeds up the first start of elinks by 300ms for fork_on_start=0,
and between 300-350ms for fork_on_start=1 (or possibly more, if the
machine is loaded and child did not finish AF_UNIX setup in first 50ms).
2024-04-13 01:51:01 -04:00
|
|
|
|
|
|
|
/* wait for forked child to complete the bind() */
|
|
|
|
if ((n = read(pipefds[0], &trigger, 1)) <= 0) {
|
|
|
|
if (n == 0) {
|
|
|
|
errno = EPIPE; /* write end closed */
|
|
|
|
}
|
|
|
|
report_af_unix_error("read()", errno);
|
|
|
|
error = 1;
|
|
|
|
}
|
|
|
|
if (close(pipefds[0]) == -1) {
|
|
|
|
report_af_unix_error("close(pipe_r)", errno);
|
|
|
|
error = 1;
|
|
|
|
}
|
|
|
|
if (error) return -1;
|
|
|
|
|
2024-04-10 09:03:44 -04:00
|
|
|
return connect_to_af_unix();
|
2020-05-17 10:35:58 -04:00
|
|
|
}
|
2024-04-10 09:03:44 -04:00
|
|
|
|
[fg] track master proc for suspend via pids, not term fds, for fork_on_start
When ui.sessions.fork_on_start, we fork a process that has a different
fdout than get_output_handle() (which has hardcoded fdout of 1), so it
will never be considered term->master, yet this is the process we want
to wake up in SIGTSTP handler.
Since we cannot rely on term->master to determine if we are the master
process, we instead move master_pid to be set explicitly at the places
where we have information about whether our process is a master or a
slave: first on start, then once the interlink determination has been
made.
master_pid has to be set in both parent and child, because both will get
suspended and need to know which one needs to resume in background with
SIGCONT (the master). We can't inherit from the parent because it's
unknown at the time of fork.
Previously, master_pid worked correctly with fork_on_start=0,
-no-connect or -dump, but not with fork_on_start=1.
See #287 for background.
2024-04-10 17:02:36 -04:00
|
|
|
/* child */
|
|
|
|
master_pid = getpid();
|
2020-05-21 10:53:43 -04:00
|
|
|
close_terminal_pipes();
|
2020-05-17 10:35:58 -04:00
|
|
|
}
|
2005-09-15 09:58:31 -04:00
|
|
|
bind_to_af_unix();
|
[interlink] speed up first elinks, and use pipe trigger for fork_on_start
When the first elinks session starts (whether fork_on_start or not), it
tries to connect on AF_UNIX socket, to see if an existing master is
present. The current code implements a retry loop with linear-increase
retry backoff, 50ms *= attempts, with 3 attempts, total delay 300ms,
before concluding master is not present.
After forking for fork_on_start, we do have to wait for the new master
to finish setup, so we can connect to it. This is the case where retry
loop might be useful. However in fork_on_start=0 case, there should be
no reason to ever wait: if we get ECONNREFUSED or ENOENT, the master
simply is not there, we can go on about our business as the master.
With fork_on_start=1 however, we will race with the child (that will
become the new master), which has to complete its socket/bind/listen
sequence. Therefore we typically have to wait another 50ms (first
attempt delay), for a total of 350ms delay.
In both cases, there does not seem to be a purpose to the initial
connection attempt retry. Conclusion after connect() failure should be
authoritative: there is no master. We do not race with anyone.
If we have done fork_on_start and have to wait, we can instead open a
pipe before forking, and wait for the "new" master (its child) to send
us a byte over the pipe. Thus, we do not need to poll, but can simply
block until we get the trigger, to indicate that AF_UNIX socket setup
has completed.
This speeds up the first start of elinks by 300ms for fork_on_start=0,
and between 300-350ms for fork_on_start=1 (or possibly more, if the
machine is loaded and child did not finish AF_UNIX setup in first 50ms).
2024-04-13 01:51:01 -04:00
|
|
|
if (get_opt_bool("ui.sessions.fork_on_start", NULL)) {
|
|
|
|
if ((n = write(pipefds[1], &trigger, 1)) <= 0) {
|
|
|
|
if (n == 0) {
|
|
|
|
errno = EPIPE; /* read end closed */
|
|
|
|
}
|
|
|
|
report_af_unix_error("write()", errno);
|
|
|
|
}
|
|
|
|
if (close(pipefds[1]) == -1) {
|
|
|
|
report_af_unix_error("close(pipe_w)", errno);
|
|
|
|
}
|
|
|
|
}
|
2005-09-15 09:58:31 -04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#undef MAX_BIND_TRIES
|
|
|
|
#undef BIND_TRIES_DELAY
|
|
|
|
#undef LISTEN_BACKLOG
|