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_USERNAME,
STAGE_PASSWORD, STAGE_PASSWORD,
STAGE_CHECKING, STAGE_CHECKING,
STAGE_EXITING,
}; };
struct textbox struct textbox
@ -111,6 +112,7 @@ struct glogin
enum stage stage; enum stage stage;
bool animating; bool animating;
const char* warning; const char* warning;
const char* announcement;
bool pointer_working; bool pointer_working;
struct termios old_tio; struct termios old_tio;
bool has_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) static void render_login(struct framebuffer fb)
{ {
render_background(fb); render_background(fb);
@ -509,6 +538,7 @@ static void render_login(struct framebuffer fb)
case STAGE_USERNAME: render_form(fb); break; case STAGE_USERNAME: render_form(fb); break;
case STAGE_PASSWORD: render_form(fb); break; case STAGE_PASSWORD: render_form(fb); break;
case STAGE_CHECKING: render_progress(fb); break; case STAGE_CHECKING: render_progress(fb); break;
case STAGE_EXITING: render_exit(fb); break;
} }
if ( state.pointer_working ) if ( state.pointer_working )
render_pointer(fb); render_pointer(fb);
@ -669,6 +699,52 @@ static bool render(struct glogin* state)
return true; 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) static void think(struct glogin* state)
{ {
if ( state->stage == STAGE_CHECKING ) if ( state->stage == STAGE_CHECKING )
@ -679,6 +755,7 @@ static void think(struct glogin* state)
sched_yield(); sched_yield();
return; return;
} }
forward_sigterm_to = 0;
if ( result ) if ( result )
{ {
if ( !login(username, session) ) if ( !login(username, session) )
@ -696,6 +773,21 @@ static void think(struct glogin* state)
state->warning = strerror(errno); 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) 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"; state->warning = "Invalid username";
break; break;
} }
handle_special(action); handle_special_graphical(state, action);
state->stage = STAGE_PASSWORD; state->stage = STAGE_PASSWORD;
textbox_reset(&textbox_password); textbox_reset(&textbox_password);
break; break;
@ -729,8 +821,10 @@ static void keyboard_event(struct glogin* state, uint32_t codepoint)
state->stage = STAGE_USERNAME; state->stage = STAGE_USERNAME;
state->warning = strerror(errno); state->warning = strerror(errno);
} }
forward_sigterm_to = state->chk.pid;
break; break;
case STAGE_CHECKING: case STAGE_CHECKING:
case STAGE_EXITING:
break; break;
} }
return; return;
@ -741,6 +835,7 @@ static void keyboard_event(struct glogin* state, uint32_t codepoint)
case STAGE_USERNAME: textbox = &textbox_username; break; case STAGE_USERNAME: textbox = &textbox_username; break;
case STAGE_PASSWORD: textbox = &textbox_password; break; case STAGE_PASSWORD: textbox = &textbox_password; break;
case STAGE_CHECKING: break; case STAGE_CHECKING: break;
case STAGE_EXITING: break;
} }
if ( textbox && codepoint < 128 ) if ( textbox && codepoint < 128 )
{ {
@ -871,6 +966,12 @@ bool glogin_init(struct glogin* state)
struct timespec duration = timespec_make(0, 150*1000*1000); struct timespec duration = timespec_make(0, 150*1000*1000);
state->fade_from_end = timespec_add(state->fade_from_begin, duration); 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; return true;
} }
@ -900,9 +1001,14 @@ int glogin_main(struct glogin* state)
nfds_t nfds = 2; nfds_t nfds = 2;
struct timespec wake_now_ts = timespec_make(0, 0); struct timespec wake_now_ts = timespec_make(0, 0);
struct timespec* wake = state->animating ? &wake_now_ts : NULL; 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 ( num_events < 0 )
{ {
if ( errno == EINTR )
continue;
warn("poll"); warn("poll");
break; 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. * Copyright (c) 2023 dzwdz.
* *
* Permission to use, copy, modify, and distribute this software for any * Permission to use, copy, modify, and distribute this software for any
@ -46,12 +46,21 @@
#include "login.h" #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 ) if ( signum == SIGINT )
dprintf(1, "^C"); dprintf(1, "^C");
if ( signum == SIGQUIT ) if ( signum == SIGQUIT )
dprintf(1, "^\\"); 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) 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, SIGINT);
sigdelset(&chk->oldset, SIGQUIT); sigdelset(&chk->oldset, SIGQUIT);
sigdelset(&chk->oldset, SIGTERM);
signal(SIGINT, SIG_DFL); signal(SIGINT, SIG_DFL);
signal(SIGQUIT, SIG_DFL); signal(SIGQUIT, SIG_DFL);
signal(SIGTERM, SIG_DFL);
unsigned int termmode = TERMMODE_UNICODE | TERMMODE_SIGNAL | unsigned int termmode = TERMMODE_UNICODE | TERMMODE_SIGNAL |
TERMMODE_UTF8 | TERMMODE_LINEBUFFER | TERMMODE_UTF8 | TERMMODE_LINEBUFFER |
TERMMODE_ECHO; 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); fcntl(chk->pipe, F_SETFL, fcntl(chk->pipe, F_GETFL) | O_NONBLOCK);
chk->pipe_nonblock = true; 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) ) while ( chk->errnum_done < sizeof(chk->errnum_bytes) )
{ {
ssize_t amount = read(chk->pipe, chk->errnum_bytes + chk->errnum_done, 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; 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; int code;
pid_t wait_ret = waitpid(chk->pid, &code, try ? WNOHANG : 0); pid_t wait_ret = waitpid(chk->pid, &code, try ? WNOHANG : 0);
if ( try && wait_ret == 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; return free(login_shell), close(pipe_fds[0]), close(pipe_fds[1]), false;
if ( child_pid == 0 ) if ( child_pid == 0 )
{ {
sigset_t sigterm;
sigemptyset(&sigterm);
sigaddset(&sigterm, SIGTERM);
sigprocmask(SIG_UNBLOCK, &sigterm, NULL);
sigdelset(&oldset, SIGINT); sigdelset(&oldset, SIGINT);
sigdelset(&oldset, SIGQUIT); sigdelset(&oldset, SIGQUIT);
sigdelset(&oldset, SIGTERM);
sigdelset(&oldset, SIGTSTP); sigdelset(&oldset, SIGTSTP);
signal(SIGINT, SIG_DFL); signal(SIGINT, SIG_DFL);
signal(SIGQUIT, SIG_DFL); signal(SIGQUIT, SIG_DFL);
signal(SIGTERM, SIG_DFL);
(void) ( (void) (
setpgid(0, 0) < 0 || setpgid(0, 0) < 0 ||
close(pipe_fds[0]) < 0 || close(pipe_fds[0]) < 0 ||
@ -269,13 +304,33 @@ bool login(const char* username, const char* session)
} }
free(login_shell); free(login_shell);
close(pipe_fds[1]); 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; int errnum;
if ( readall(pipe_fds[0], &errnum, sizeof(errnum)) < (ssize_t) sizeof(errnum) ) if ( readall(pipe_fds[0], &errnum, sizeof(errnum)) < (ssize_t) sizeof(errnum) )
errnum = 0; errnum = 0;
close(pipe_fds[0]); close(pipe_fds[0]);
int child_status; 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; errnum = errno;
break;
}
sigprocmask(SIG_BLOCK, &sigterm, NULL);
forward_sigterm_to = 0;
sigaction(SIGTERM, &old_sa, NULL);
tcsetattr(0, TCSAFLUSH, &tio); tcsetattr(0, TCSAFLUSH, &tio);
tcsetpgrp(0, getpgid(0)); tcsetpgrp(0, getpgid(0));
sigprocmask(SIG_SETMASK, &oldset, NULL); sigprocmask(SIG_SETMASK, &oldset, NULL);
@ -334,7 +389,7 @@ bool parse_username(const char* input,
*session = NULL; *session = NULL;
*action = SPECIAL_ACTION_NONE; *action = SPECIAL_ACTION_NONE;
if ( !strcmp(input, "exit") ) if ( !strcmp(input, "exit") )
return *action = SPECIAL_ACTION_POWEROFF, true; return *action = SPECIAL_ACTION_EXIT, true;
else if ( !strcmp(input, "poweroff") ) else if ( !strcmp(input, "poweroff") )
return *action = SPECIAL_ACTION_POWEROFF, true; return *action = SPECIAL_ACTION_POWEROFF, true;
else if ( !strcmp(input, "reboot") ) else if ( !strcmp(input, "reboot") )
@ -376,6 +431,7 @@ void handle_special(enum special_action action)
switch ( action ) switch ( action )
{ {
case SPECIAL_ACTION_NONE: return; case SPECIAL_ACTION_NONE: return;
case SPECIAL_ACTION_EXIT: exit(0);
case SPECIAL_ACTION_POWEROFF: exit(0); case SPECIAL_ACTION_POWEROFF: exit(0);
case SPECIAL_ACTION_REBOOT: exit(1); case SPECIAL_ACTION_REBOOT: exit(1);
case SPECIAL_ACTION_HALT: exit(2); case SPECIAL_ACTION_HALT: exit(2);
@ -394,7 +450,7 @@ int textual(void)
char* username = NULL; char* username = NULL;
char* session = NULL; char* session = NULL;
while ( true ) while ( !got_sigterm )
{ {
char hostname[HOST_NAME_MAX + 1]; char hostname[HOST_NAME_MAX + 1];
hostname[0] = '\0'; hostname[0] = '\0';
@ -460,6 +516,9 @@ int textual(void)
continue; continue;
} }
if ( got_sigterm )
break;
if ( !login(username, session) ) if ( !login(username, session) )
{ {
warn("logging in as %s", username); 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. * Copyright (c) 2023 dzwdz.
* *
* Permission to use, copy, modify, and distribute this software for any * Permission to use, copy, modify, and distribute this software for any
@ -39,12 +39,14 @@ struct check
enum special_action enum special_action
{ {
SPECIAL_ACTION_NONE, SPECIAL_ACTION_NONE,
SPECIAL_ACTION_EXIT,
SPECIAL_ACTION_POWEROFF, SPECIAL_ACTION_POWEROFF,
SPECIAL_ACTION_REBOOT, SPECIAL_ACTION_REBOOT,
SPECIAL_ACTION_HALT, SPECIAL_ACTION_HALT,
SPECIAL_ACTION_REINIT, SPECIAL_ACTION_REINIT,
}; };
void on_interrupt_signal(int signum);
bool login(const char* username, const char* session); bool login(const char* username, const char* session);
bool check_real(const char* username, const char* password); bool check_real(const char* username, const char* password);
bool check_begin(struct check* chk, bool check_begin(struct check* chk,
@ -62,4 +64,7 @@ bool parse_username(const char* input,
enum special_action* action); enum special_action* action);
void handle_special(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 #endif