1
0
mirror of https://github.com/rkd77/elinks.git synced 2024-06-21 00:25:37 +00:00
elinks/src/terminal/terminal.c
Scott Mcdermott b2556aa953 [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 15:14:23 -07:00

601 lines
13 KiB
C

/** Terminal interface - low-level displaying implementation.
* @file */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_STDINT_H
#include <stdint.h>
#endif
#include <stdio.h>
#include <sys/types.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include "elinks.h"
#include "bookmarks/bookmarks.h"
#include "config/options.h"
#include "main/main.h"
#include "main/module.h"
#include "main/object.h"
#include "main/select.h"
#include "osdep/osdep.h"
#include "osdep/signals.h"
#ifdef CONFIG_SCRIPTING_SPIDERMONKEY
# include "scripting/smjs/smjs.h"
#endif
#include "session/download.h"
#include "session/session.h"
#include "terminal/draw.h"
#include "terminal/event.h"
#include "terminal/hardio.h"
#include "terminal/kbd.h"
#include "terminal/screen.h"
#ifdef CONFIG_LIBSIXEL
#include "terminal/sixel.h"
#endif
#include "terminal/terminal.h"
#ifdef CONFIG_TERMINFO
#include "terminal/terminfo.h"
#endif
#include "terminal/window.h"
#include "util/error.h"
#include "util/hash.h"
#include "util/memory.h"
#include "util/string.h"
#include "viewer/text/textarea.h"
INIT_LIST_OF(struct terminal, terminals);
struct hash *temporary_files;
static void check_if_no_terminal(void);
void
clean_temporary_files(void)
{
struct hash_item *item;
int i;
if (!temporary_files) {
return;
}
foreach_hash_item (item, *temporary_files, i) {
if (item->key) {
unlink(item->key);
mem_free_set(&item->key, NULL);
}
}
free_hash(&temporary_files);
clear_uri_tempfiles();
}
long
get_number_of_temporary_files(void)
{
struct hash_item *item;
int i;
long counter = 0;
if (temporary_files) {
foreach_hash_item (item, *temporary_files, i) {
counter++;
}
}
return counter;
}
static void
save_temporary_filename(const char *filename)
{
if (!temporary_files) {
temporary_files = init_hash8();
}
if (temporary_files) {
char *copy = stracpy(filename);
if (copy) {
add_hash_item(temporary_files, copy, strlen(copy), NULL);
}
}
}
void
redraw_terminal(struct terminal *term)
{
struct term_event ev;
#ifdef CONFIG_LIBSIXEL
set_redraw_term_event(&ev, term->width, term->height, term->cell_width, term->cell_height);
#else
set_redraw_term_event(&ev, term->width, term->height, 0, 0);
#endif
term_send_event(term, &ev);
}
void
redraw_terminal_cls(struct terminal *term)
{
struct term_event ev;
#ifdef CONFIG_LIBSIXEL
set_resize_term_event(&ev, term->width, term->height, term->cell_width, term->cell_height);
#else
set_resize_term_event(&ev, term->width, term->height, 0, 0);
#endif
term_send_event(term, &ev);
}
void
cls_redraw_all_terminals(void)
{
static int in_redraw;
struct terminal *term;
if (in_redraw) return;
in_redraw = 1;
foreach (term, terminals)
redraw_terminal_cls(term);
in_redraw = 0;
}
/** Get the terminal in which message boxes should be displayed, if
* there is no specific reason to use some other terminal. This
* returns NULL if all terminals have been closed. (ELinks keeps
* running anyway if ui.sessions.keep_session_active is true.) */
struct terminal *
get_default_terminal(void)
{
if (list_empty(terminals))
return NULL;
else
return (struct terminal *)terminals.next;
}
struct terminal *
init_term(int fdin, int fdout)
{
char name[MAX_TERM_LEN + 9] = "terminal.";
struct terminal *term = (struct terminal *)mem_calloc(1, sizeof(*term));
if (!term) {
check_if_no_terminal();
return NULL;
}
term->screen = init_screen();
if (!term->screen) {
mem_free(term);
return NULL;
}
#ifdef CONFIG_TERMINFO
terminfo_setupterm(NULL, fdout);
#endif
init_list(term->windows);
#ifdef CONFIG_LIBSIXEL
init_list(term->images);
term->sixel = 1;
#endif
term->fdin = fdin;
term->fdout = fdout;
term->master = (term->fdout == get_output_handle());
term->blocked = -1;
get_terminal_name(name + 9);
term->spec = get_opt_rec(config_options, name);
object_lock(term->spec);
/* It's a new terminal, so assume the user is using it right now,
* and sort it to the front of the list. */
add_to_list(terminals, term);
set_handlers(fdin, (select_handler_T) in_term, NULL,
(select_handler_T) destroy_terminal, term);
return term;
}
/** Get the codepage of a terminal. The UTF-8 I/O option does not
* affect this.
*
* @todo Perhaps cache the value in struct terminal?
*
* @bug Bug 1064: If the charset has been set as "System", this should
* apply the locale environment variables of the slave ELinks process,
* not those of the master ELinks process that parsed the configuration
* file. That is why the parameter points to struct terminal and not
* merely to its option tree (term->spec).
*
* @see get_translation_table(), get_cp_mime_name() */
int
get_terminal_codepage(const struct terminal *term)
{
return get_opt_codepage_tree(term->spec, "charset", NULL);
}
void
redraw_all_terminals(void)
{
struct terminal *term;
foreach (term, terminals)
redraw_screen(term);
}
void
destroy_terminal(struct terminal *term)
{
#ifdef CONFIG_SCRIPTING_SPIDERMONKEY
smjs_detach_terminal_object(term);
#endif
#ifdef CONFIG_BOOKMARKS
bookmark_auto_save_tabs(term);
#endif
detach_downloads_from_terminal(term);
free_textarea_data(term);
/* delete_window doesn't update term->current_tab, but it
calls redraw_terminal, which requires term->current_tab
to be valid if there are any tabs left. So set a value
that will be valid for that long. */
term->current_tab = 0;
while (!list_empty(term->windows))
delete_window((struct window *)term->windows.next);
#ifdef CONFIG_LIBSIXEL
while (!list_empty(term->images)) {
delete_image((struct image *)term->images.next);
}
#endif
/* mem_free_if(term->cwd); */
mem_free_if(term->title);
if (term->screen) done_screen(term->screen);
clear_handlers(term->fdin);
mem_free_if(term->interlink);
if (term->blocked != -1) {
close(term->blocked);
clear_handlers(term->blocked);
}
del_from_list(term);
close(term->fdin);
if (term->fdout != 1) {
if (term->fdout != term->fdin) close(term->fdout);
} else {
unhandle_terminal_signals(term);
free_all_itrms();
#ifndef NO_FORK_ON_EXIT
if (!list_empty(terminals)) {
if (fork()) exit(0);
}
#endif
}
object_unlock(term->spec);
mem_free(term);
check_if_no_terminal();
}
void
destroy_all_terminals(void)
{
while (!list_empty(terminals))
destroy_terminal((struct terminal *)terminals.next);
}
static void
check_if_no_terminal(void)
{
program.terminate = list_empty(terminals)
&& !get_opt_bool("ui.sessions.keep_session_active", NULL);
}
void
exec_thread(char *path, int p)
{
int plen = strlen(path + 1) + 2;
#if defined(HAVE_SETPGID) && !defined(CONFIG_OS_BEOS) && !defined(HAVE_BEGINTHREAD)
if (path[0] == TERM_EXEC_NEWWIN) setpgid(0, 0);
#endif
#ifndef WIN32
if (path[0] == TERM_EXEC_BG)
exe_no_stdin(path + 1);
else
exe(path + 1);
if (path[plen]) unlink(path + plen);
#endif
}
void
close_handle(void *h)
{
close((intptr_t) h);
clear_handlers((intptr_t) h);
}
static void
unblock_terminal(struct terminal *term)
{
close_handle((void *) (intptr_t) term->blocked);
term->blocked = -1;
set_handlers(term->fdin, (select_handler_T) in_term, NULL,
(select_handler_T) destroy_terminal, term);
unblock_itrm();
redraw_terminal_cls(term);
if (term->textarea_data) /* XXX */
textarea_edit(1, term, NULL, NULL, NULL);
}
#ifndef CONFIG_FASTMEM
void
assert_terminal_ptr_not_dangling(const struct terminal *suspect)
{
struct terminal *term;
if (suspect == NULL)
return;
foreach (term, terminals) {
if (term == suspect)
return;
}
assertm(0, "Dangling pointer to struct terminal");
}
#endif /* !CONFIG_FASTMEM */
static void
exec_on_master_terminal(struct terminal *term,
const char *path, int plen,
const char *delete_, int dlen,
term_exec_T fg)
{
int blockh = 0;
int param_size = plen + dlen + 2 /* 2 null char */ + 1 /* fg */;
char *param = (char *)fmem_alloc(param_size);
if (!param) return;
param[0] = fg;
memcpy(param + 1, path, plen + 1);
memcpy(param + 1 + plen + 1, delete_, dlen + 1);
if (fg == TERM_EXEC_FG) block_itrm();
#ifndef WIN32
blockh = start_thread((void (*)(void *, int)) exec_thread,
param, param_size);
#else
exec_thread(param, param_size);
#endif
fmem_free(param);
if (blockh == -1) {
if (fg == TERM_EXEC_FG) unblock_itrm();
return;
}
if (fg == TERM_EXEC_FG) {
term->blocked = blockh;
set_handlers(blockh,
(select_handler_T) unblock_terminal,
NULL,
(select_handler_T) unblock_terminal,
term);
set_handlers(term->fdin, NULL, NULL,
(select_handler_T) destroy_terminal,
term);
} else {
set_handlers(blockh, close_handle, NULL,
close_handle, (void *) (intptr_t) blockh);
}
}
static void
exec_on_slave_terminal( struct terminal *term,
const char *path, int plen,
const char *delete_, int dlen,
term_exec_T fg)
{
int data_size = plen + dlen + 1 /* 0 */ + 1 /* fg */ + 2 /* 2 null char */;
char *data = (char *)fmem_alloc(data_size);
if (!data) return;
data[0] = 0;
data[1] = fg;
memcpy(data + 2, path, plen + 1);
memcpy(data + 2 + plen + 1, delete_, dlen + 1);
hard_write(term->fdout, data, data_size);
fmem_free(data);
}
void
exec_on_terminal(struct terminal *term, const char *path,
const char *delete_, term_exec_T fg)
{
if (path) {
if (!*path) return;
} else {
path = "";
}
#ifdef NO_FG_EXEC
fg = TERM_EXEC_BG;
#endif
if (term->master) {
size_t len;
if (!*path) {
dispatch_special(delete_);
return;
}
/* TODO: Should this be changed to allow TERM_EXEC_NEWWIN
* in a blocked terminal? There is similar code in
* in_sock(). --KON, 2007 */
if (fg != TERM_EXEC_BG && is_blocked()) {
unlink(delete_);
return;
}
len = strlen(delete_);
if (len && get_opt_bool("ui.sessions.postpone_unlink", NULL)) {
save_temporary_filename(delete_);
len = 0;
delete_ = "";
}
exec_on_master_terminal(term,
path, strlen(path),
delete_, len,
fg);
} else {
size_t len = strlen(delete_);
if (len && *path && get_opt_bool("ui.sessions.postpone_unlink", NULL)) {
save_temporary_filename(delete_);
len = 0;
delete_ = "";
}
exec_on_slave_terminal( term,
path, strlen(path),
delete_, len,
fg);
}
}
void
exec_shell(struct terminal *term)
{
const char *sh;
if (!can_open_os_shell(term->environment)) return;
sh = get_shell();
if (sh && *sh)
exec_on_terminal(term, sh, "", TERM_EXEC_FG);
}
void
do_terminal_function(struct terminal *term, unsigned char code,
const char *data)
{
int data_len = strlen(data);
char *x_data = (char *)fmem_alloc(data_len + 1 /* code */ + 1 /* null char */);
if (!x_data) return;
x_data[0] = code;
memcpy(x_data + 1, data, data_len + 1);
exec_on_terminal(term, NULL, x_data, TERM_EXEC_BG);
fmem_free(x_data);
}
/** @return negative on error; zero or positive on success. */
int
set_terminal_title(struct terminal *term, char *title)
{
int from_cp;
int to_cp;
char *converted = NULL;
if (term->title && !strcmp(title, term->title)) return 0;
/* In which codepage was the title parameter given? */
from_cp = get_terminal_codepage(term);
/* In which codepage does the terminal want the title? */
if (get_opt_bool_tree(term->spec, "latin1_title", NULL))
to_cp = get_cp_index("ISO-8859-1");
else if (get_opt_bool_tree(term->spec, "utf_8_io", NULL))
to_cp = get_cp_index("UTF-8");
else
to_cp = from_cp;
if (from_cp != to_cp) {
struct conv_table *convert_table;
convert_table = get_translation_table(from_cp, to_cp);
if (!convert_table) return -1;
converted = convert_string(convert_table, title, strlen(title),
to_cp, CSM_NONE, NULL, NULL, NULL);
if (!converted) return -1;
}
mem_free_set(&term->title, stracpy(title));
do_terminal_function(term, TERM_FN_TITLE_CODEPAGE,
get_cp_mime_name(to_cp));
do_terminal_function(term, TERM_FN_TITLE,
converted ? converted : title);
mem_free_if(converted);
return 0;
}
static int terminal_pipe[2];
int
check_terminal_pipes(void)
{
return c_pipe(terminal_pipe);
}
void
close_terminal_pipes(void)
{
close(terminal_pipe[0]);
close(terminal_pipe[1]);
}
struct terminal *
attach_terminal(int in, int out, int ctl, void *info, int len)
{
struct terminal *term;
if (set_nonblocking_fd(terminal_pipe[0]) < 0) return NULL;
if (set_nonblocking_fd(terminal_pipe[1]) < 0) return NULL;
handle_trm(in, out, out, terminal_pipe[1], ctl, info, len, 0);
term = init_term(terminal_pipe[0], out);
if (!term) {
close_terminal_pipes();
return NULL;
}
return term;
}
static struct module *terminal_submodules[] = {
&terminal_screen_module,
NULL
};
struct module terminal_module = struct_module(
/* Because this module is listed in main_modules rather than
* in builtin_modules, its name does not appear in the user
* interface and so need not be translatable. */
/* name: */ "Terminal",
/* options: */ NULL,
/* hooks: */ NULL,
/* submodules: */ terminal_submodules,
/* data: */ NULL,
/* init: */ NULL,
/* done: */ NULL
);