Handle SIGTERM in login(8).

Display a final frame with a message explaining what is happening that is
displayed while the system powers off, reboots, or halts.
This commit is contained in:
Jonas 'Sortie' Termansen 2024-06-20 12:43:47 +00:00
parent 2bc6e40f1d
commit 65bc117891
3 changed files with 178 additions and 8 deletions

View File

@ -63,6 +63,7 @@ enum stage
STAGE_USERNAME,
STAGE_PASSWORD,
STAGE_CHECKING,
STAGE_EXITING,
};
struct textbox
@ -111,6 +112,7 @@ struct glogin
enum stage stage;
bool animating;
const char* warning;
const char* announcement;
bool pointer_working;
struct termios old_tio;
bool has_old_tio;
@ -499,6 +501,33 @@ static void render_progress(struct framebuffer fb)
}
}
static void render_exit(struct framebuffer fb)
{
assert(state.announcement);
for ( int yoff = -1; yoff <= 1; yoff++ )
{
for ( int xoff = -1; xoff <= 1; xoff++ )
{
struct framebuffer msgfb = fb;
int y = (fb.yres - FONT_HEIGHT) / 2 + yoff;
msgfb = framebuffer_cut_top_y(msgfb, y);
int w = strlen(state.announcement) * (FONT_WIDTH+1);
int x = (fb.xres - w) / 2 + xoff;
msgfb = framebuffer_cut_left_x(msgfb, x);
render_text(msgfb, state.announcement, make_color_a(0, 0, 0, 64));
}
}
struct framebuffer msgfb = fb;
int y = (fb.yres - FONT_HEIGHT) / 2;
msgfb = framebuffer_cut_top_y(msgfb, y);
int w = strlen(state.announcement) * (FONT_WIDTH+1);
int x = (fb.xres - w) / 2;
msgfb = framebuffer_cut_left_x(msgfb, x);
render_text(msgfb, state.announcement, make_color(255, 255, 255));
}
static void render_login(struct framebuffer fb)
{
render_background(fb);
@ -509,6 +538,7 @@ static void render_login(struct framebuffer fb)
case STAGE_USERNAME: render_form(fb); break;
case STAGE_PASSWORD: render_form(fb); break;
case STAGE_CHECKING: render_progress(fb); break;
case STAGE_EXITING: render_exit(fb); break;
}
if ( state.pointer_working )
render_pointer(fb);
@ -669,6 +699,52 @@ static bool render(struct glogin* state)
return true;
}
static void handle_special_graphical(struct glogin* state,
enum special_action special_action)
{
switch ( special_action )
{
case SPECIAL_ACTION_NONE:
state->announcement = NULL;
break;
case SPECIAL_ACTION_EXIT:
state->announcement = "Exiting...";
break;
case SPECIAL_ACTION_POWEROFF:
state->announcement = "Powering off...";
break;
case SPECIAL_ACTION_REBOOT:
state->announcement = "Rebooting...";
break;
case SPECIAL_ACTION_HALT:
state->announcement = "Halting...";
break;
case SPECIAL_ACTION_REINIT:
state->announcement = "Reinitializing operating system...";
break;
}
if ( state->announcement )
{
state->stage = STAGE_EXITING;
state->fading_from = false;
render(state);
}
handle_special(special_action);
}
static int get_init_exit_plan(void)
{
FILE* fp = popen("/sbin/service default exit-code", "r");
if ( !fp )
return -1;
int result = -1;
char buffer[sizeof(int) * 3];
if ( fgets(buffer, sizeof(buffer), fp) && buffer[0] )
result = atoi(buffer);
pclose(fp);
return result;
}
static void think(struct glogin* state)
{
if ( state->stage == STAGE_CHECKING )
@ -679,6 +755,7 @@ static void think(struct glogin* state)
sched_yield();
return;
}
forward_sigterm_to = 0;
if ( result )
{
if ( !login(username, session) )
@ -696,6 +773,21 @@ static void think(struct glogin* state)
state->warning = strerror(errno);
}
}
if ( got_sigterm )
{
int exit_code = get_init_exit_plan();
enum special_action action = SPECIAL_ACTION_EXIT;
if ( exit_code == 0 )
action = SPECIAL_ACTION_POWEROFF;
else if ( exit_code == 1 )
action = SPECIAL_ACTION_REBOOT;
else if ( exit_code == 2 )
action = SPECIAL_ACTION_HALT;
else if ( exit_code == 3 )
action = SPECIAL_ACTION_REINIT;
handle_special_graphical(state, action);
}
}
static void keyboard_event(struct glogin* state, uint32_t codepoint)
@ -717,7 +809,7 @@ static void keyboard_event(struct glogin* state, uint32_t codepoint)
state->warning = "Invalid username";
break;
}
handle_special(action);
handle_special_graphical(state, action);
state->stage = STAGE_PASSWORD;
textbox_reset(&textbox_password);
break;
@ -729,8 +821,10 @@ static void keyboard_event(struct glogin* state, uint32_t codepoint)
state->stage = STAGE_USERNAME;
state->warning = strerror(errno);
}
forward_sigterm_to = state->chk.pid;
break;
case STAGE_CHECKING:
case STAGE_EXITING:
break;
}
return;
@ -741,6 +835,7 @@ static void keyboard_event(struct glogin* state, uint32_t codepoint)
case STAGE_USERNAME: textbox = &textbox_username; break;
case STAGE_PASSWORD: textbox = &textbox_password; break;
case STAGE_CHECKING: break;
case STAGE_EXITING: break;
}
if ( textbox && codepoint < 128 )
{
@ -871,6 +966,12 @@ bool glogin_init(struct glogin* state)
struct timespec duration = timespec_make(0, 150*1000*1000);
state->fade_from_end = timespec_add(state->fade_from_begin, duration);
}
sigset_t sigterm;
sigemptyset(&sigterm);
sigaddset(&sigterm, SIGTERM);
sigprocmask(SIG_BLOCK, &sigterm, NULL);
struct sigaction sa = { .sa_handler = on_interrupt_signal };
sigaction(SIGTERM, &sa, NULL);
return true;
}
@ -900,9 +1001,14 @@ int glogin_main(struct glogin* state)
nfds_t nfds = 2;
struct timespec wake_now_ts = timespec_make(0, 0);
struct timespec* wake = state->animating ? &wake_now_ts : NULL;
int num_events = ppoll(pfds, nfds, wake, NULL);
sigset_t pollmask;
sigprocmask(SIG_SETMASK, NULL, &pollmask);
sigdelset(&pollmask, SIGTERM);
int num_events = ppoll(pfds, nfds, wake, &pollmask);
if ( num_events < 0 )
{
if ( errno == EINTR )
continue;
warn("poll");
break;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2015, 2018, 2022, 2023 Jonas 'Sortie' Termansen.
* Copyright (c) 2014, 2015, 2018, 2022, 2023, 2024 Jonas 'Sortie' Termansen.
* Copyright (c) 2023 dzwdz.
*
* Permission to use, copy, modify, and distribute this software for any
@ -46,12 +46,21 @@
#include "login.h"
static void on_interrupt_signal(int signum)
pid_t forward_sigterm_to = 0;
volatile sig_atomic_t got_sigterm = 0;
void on_interrupt_signal(int signum)
{
if ( signum == SIGINT )
dprintf(1, "^C");
if ( signum == SIGQUIT )
dprintf(1, "^\\");
if ( signum == SIGTERM )
{
got_sigterm = 1;
if ( forward_sigterm_to )
kill(forward_sigterm_to, SIGTERM);
}
}
bool check_real(const char* username, const char* password)
@ -112,8 +121,10 @@ bool check_begin(struct check* chk,
{
sigdelset(&chk->oldset, SIGINT);
sigdelset(&chk->oldset, SIGQUIT);
sigdelset(&chk->oldset, SIGTERM);
signal(SIGINT, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
signal(SIGTERM, SIG_DFL);
unsigned int termmode = TERMMODE_UNICODE | TERMMODE_SIGNAL |
TERMMODE_UTF8 | TERMMODE_LINEBUFFER |
TERMMODE_ECHO;
@ -144,6 +155,17 @@ bool check_end(struct check* chk, bool* result, bool try)
fcntl(chk->pipe, F_SETFL, fcntl(chk->pipe, F_GETFL) | O_NONBLOCK);
chk->pipe_nonblock = true;
}
sigset_t sigterm, oldset;
sigemptyset(&sigterm);
sigaddset(&sigterm, SIGTERM);
struct sigaction sa = { .sa_handler = on_interrupt_signal }, old_sa;
if ( !try )
{
sigprocmask(SIG_BLOCK, &sigterm, &oldset);
forward_sigterm_to = chk->pid;
sigaction(SIGTERM, &sa, &old_sa);
sigprocmask(SIG_UNBLOCK, &sigterm, NULL);
}
while ( chk->errnum_done < sizeof(chk->errnum_bytes) )
{
ssize_t amount = read(chk->pipe, chk->errnum_bytes + chk->errnum_done,
@ -156,6 +178,13 @@ bool check_end(struct check* chk, bool* result, bool try)
}
chk->errnum_done += amount;
}
if ( !try )
{
sigprocmask(SIG_BLOCK, &sigterm, NULL);
forward_sigterm_to = 0;
sigaction(SIGTERM, &old_sa, NULL);
sigprocmask(SIG_SETMASK, &oldset, NULL);
}
int code;
pid_t wait_ret = waitpid(chk->pid, &code, try ? WNOHANG : 0);
if ( try && wait_ret == 0 )
@ -230,11 +259,17 @@ bool login(const char* username, const char* session)
return free(login_shell), close(pipe_fds[0]), close(pipe_fds[1]), false;
if ( child_pid == 0 )
{
sigset_t sigterm;
sigemptyset(&sigterm);
sigaddset(&sigterm, SIGTERM);
sigprocmask(SIG_UNBLOCK, &sigterm, NULL);
sigdelset(&oldset, SIGINT);
sigdelset(&oldset, SIGQUIT);
sigdelset(&oldset, SIGTERM);
sigdelset(&oldset, SIGTSTP);
signal(SIGINT, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
signal(SIGTERM, SIG_DFL);
(void) (
setpgid(0, 0) < 0 ||
close(pipe_fds[0]) < 0 ||
@ -269,13 +304,33 @@ bool login(const char* username, const char* session)
}
free(login_shell);
close(pipe_fds[1]);
sigset_t sigterm;
sigemptyset(&sigterm);
sigaddset(&sigterm, SIGTERM);
sigprocmask(SIG_BLOCK, &sigterm, NULL);
struct sigaction sa = { .sa_handler = on_interrupt_signal }, old_sa;
forward_sigterm_to = child_pid;
sigaction(SIGTERM, &sa, &old_sa);
sigprocmask(SIG_UNBLOCK, &sigterm, NULL);
int errnum;
if ( readall(pipe_fds[0], &errnum, sizeof(errnum)) < (ssize_t) sizeof(errnum) )
errnum = 0;
close(pipe_fds[0]);
int child_status;
if ( waitpid(child_pid, &child_status, 0) < 0 )
while ( waitpid(child_pid, &child_status, 0) < 0 )
{
if ( errno == EINTR )
continue;
errnum = errno;
break;
}
sigprocmask(SIG_BLOCK, &sigterm, NULL);
forward_sigterm_to = 0;
sigaction(SIGTERM, &old_sa, NULL);
tcsetattr(0, TCSAFLUSH, &tio);
tcsetpgrp(0, getpgid(0));
sigprocmask(SIG_SETMASK, &oldset, NULL);
@ -334,7 +389,7 @@ bool parse_username(const char* input,
*session = NULL;
*action = SPECIAL_ACTION_NONE;
if ( !strcmp(input, "exit") )
return *action = SPECIAL_ACTION_POWEROFF, true;
return *action = SPECIAL_ACTION_EXIT, true;
else if ( !strcmp(input, "poweroff") )
return *action = SPECIAL_ACTION_POWEROFF, true;
else if ( !strcmp(input, "reboot") )
@ -376,6 +431,7 @@ void handle_special(enum special_action action)
switch ( action )
{
case SPECIAL_ACTION_NONE: return;
case SPECIAL_ACTION_EXIT: exit(0);
case SPECIAL_ACTION_POWEROFF: exit(0);
case SPECIAL_ACTION_REBOOT: exit(1);
case SPECIAL_ACTION_HALT: exit(2);
@ -394,7 +450,7 @@ int textual(void)
char* username = NULL;
char* session = NULL;
while ( true )
while ( !got_sigterm )
{
char hostname[HOST_NAME_MAX + 1];
hostname[0] = '\0';
@ -460,6 +516,9 @@ int textual(void)
continue;
}
if ( got_sigterm )
break;
if ( !login(username, session) )
{
warn("logging in as %s", username);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2015, 2023 Jonas 'Sortie' Termansen.
* Copyright (c) 2014, 2015, 2023, 2024 Jonas 'Sortie' Termansen.
* Copyright (c) 2023 dzwdz.
*
* Permission to use, copy, modify, and distribute this software for any
@ -39,12 +39,14 @@ struct check
enum special_action
{
SPECIAL_ACTION_NONE,
SPECIAL_ACTION_EXIT,
SPECIAL_ACTION_POWEROFF,
SPECIAL_ACTION_REBOOT,
SPECIAL_ACTION_HALT,
SPECIAL_ACTION_REINIT,
};
void on_interrupt_signal(int signum);
bool login(const char* username, const char* session);
bool check_real(const char* username, const char* password);
bool check_begin(struct check* chk,
@ -62,4 +64,7 @@ bool parse_username(const char* input,
enum special_action* action);
void handle_special(enum special_action action);
extern pid_t forward_sigterm_to;
extern volatile sig_atomic_t got_sigterm;
#endif