diff --git a/login/graphical.c b/login/graphical.c index aefc588e..8ce30772 100644 --- a/login/graphical.c +++ b/login/graphical.c @@ -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; } diff --git a/login/login.c b/login/login.c index 3f7d3054..ac5bf8ae 100644 --- a/login/login.c +++ b/login/login.c @@ -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); diff --git a/login/login.h b/login/login.h index 3127dc47..e89be4d9 100644 --- a/login/login.h +++ b/login/login.h @@ -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