From 4f8ac368dbead32aaa30684c29df3afa0a8ba17f Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Mon, 11 Aug 2014 05:47:10 +0200 Subject: add fork_for_safety option + beginning of kernel-based keyboard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Andrée --- src/libmdsserver/util.c | 39 +++ src/libmdsserver/util.h | 12 + src/mds-base.c | 68 +++++- src/mds-base.h | 27 ++- src/mds-clipboard.c | 5 +- src/mds-echo.c | 5 +- src/mds-kkbd.c | 509 ++++++++++++++++++++++++++++++++++++++++ src/mds-kkbd.h | 75 ++++++ src/mds-registry/mds-registry.c | 5 +- src/mds-respawn.c | 7 +- src/mds-server/mds-server.c | 3 +- src/mds.c | 2 +- 12 files changed, 742 insertions(+), 15 deletions(-) create mode 100644 src/mds-kkbd.c create mode 100644 src/mds-kkbd.h (limited to 'src') diff --git a/src/libmdsserver/util.c b/src/libmdsserver/util.c index 01f5cbe..83641f2 100644 --- a/src/libmdsserver/util.c +++ b/src/libmdsserver/util.c @@ -28,6 +28,8 @@ #include #include #include +#include +#include @@ -294,3 +296,40 @@ int startswith_n(const char* haystack, const char* needle, size_t haystack_n, si return 1; } + +/** + * Wrapper around `waitpid` that never returns on an interruption unless + * it is interrupted 100 times within the same second + * + * @param pid See description of `pid` in the documentation for `waitpid` + * @param pid See description of `status` in the documentation for `waitpid` + * @param pid See description of `options` in the documentation for `waitpid` + * @return See the documentation for `waitpid` + */ +pid_t uninterruptable_waitpid(pid_t pid, int* restrict status, int options) +{ + struct timespec time_start; + struct timespec time_intr; + int intr_count = 0, have_time; + pid_t rc; + + have_time = (monotone(&time_start) >= 0); + + rewait: + rc = waitpid(pid, status, options); + if (rc == (pid_t)-1) + { + if (errno != EINTR) + goto fail; + if (have_time && (monotone(&time_intr) >= 0)) + if (time_start.tv_sec != time_intr.tv_sec) + intr_count = 0; + if (intr_count++ < 100) + goto rewait; + /* Don't let the CPU catch fire! */ + errno = EINTR; + } + fail: + return rc; +} + diff --git a/src/libmdsserver/util.h b/src/libmdsserver/util.h index 00dfb58..3220adb 100644 --- a/src/libmdsserver/util.h +++ b/src/libmdsserver/util.h @@ -21,6 +21,7 @@ #include #include +#include /** @@ -113,6 +114,17 @@ char* full_read(int fd); */ int startswith_n(const char* haystack, const char* needle, size_t haystack_n, size_t needle_n) __attribute__((pure)); +/** + * Wrapper around `waitpid` that never returns on an interruption unless + * it is interrupted 100 times within the same second + * + * @param pid See description of `pid` in the documentation for `waitpid` + * @param pid See description of `status` in the documentation for `waitpid` + * @param pid See description of `options` in the documentation for `waitpid` + * @return See the documentation for `waitpid` + */ +pid_t uninterruptable_waitpid(pid_t pid, int* restrict status, int options); + #endif diff --git a/src/mds-base.c b/src/mds-base.c index 77b8835..1bae36b 100644 --- a/src/mds-base.c +++ b/src/mds-base.c @@ -182,12 +182,54 @@ int __attribute__((weak)) connect_to_display(void) return 1; } + +/** + * Put the server into a fork of itself as + * described by the `fork_for_safety` + * server characteristics + * + * @return Zero on success, -1 on error + */ +static int server_initialised_fork_for_safety(void) +{ + unsigned pending_alarm = alarm(0); + pid_t pid = fork(); + + if (pid == (pid_t)-1) + { + xperror(*argv); + eprint("while forking for safety."); + return -1; + } + else if (pid == 0) + alarm(pending_alarm); + else + { + int status; + if (uninterruptable_waitpid(pid, &status, 0) == (pid_t)-1) + { + xperror(*argv); + kill(pid, SIGABRT); + sleep(5); + } + fork_cleanup(status); + if (WIFEXITED(status)) exit(WEXITSTATUS(status)); + else if (WIFSIGNALED(status)) raise(WTERMSIG(status)); + exit(1); + } + + return 0; +} + + /** * This function should be called when the server has * been properly initialised but before initialisation * of anything that is removed at forking is initialised + * + * @return Zero on success, -1 on error */ -void __attribute__((weak)) server_initialised(void) +int __attribute__((weak)) server_initialised(void) { pid_t r; if (on_init_fork && (r = fork())) @@ -196,7 +238,7 @@ void __attribute__((weak)) server_initialised(void) { xperror(*argv); eprint("while forking at completed initialisation."); - exit(1); + return -1; } else exit(0); @@ -204,6 +246,10 @@ void __attribute__((weak)) server_initialised(void) if (on_init_sh != NULL) system(on_init_sh); + + if (server_characteristics.fork_for_safety) + return server_initialised_fork_for_safety(); + return 0; } @@ -495,5 +541,23 @@ int trap_signals(void) } +/** + * This function should be implemented by the actual server implementation + * if the server has set `server_characteristics.fork_for_safety` to be + * true + * + * This function is called by the parent server process when the + * child server process exits, if the server has completed its + * initialisation + * + * @param status The status the child died with + */ +void __attribute__((weak)) fork_cleanup(int status) +{ + (void) status; + fprintf(stderr, "Something is wrong, `fork_cleanup` has been called but not reimplemented."); +} + + #undef try diff --git a/src/mds-base.h b/src/mds-base.h index edc42fa..68feb32 100644 --- a/src/mds-base.h +++ b/src/mds-base.h @@ -54,6 +54,16 @@ typedef struct server_characteristics */ unsigned sanity_check_argc : 1; + /** + * Setting this to non-zero will cause the server to place + * itself in a fork of itself when initialised. This can be + * used to let the server clean up fatal stuff after itself + * if it crashes. When the child exits, no matter how it + * exits, the parent will call `fork_cleanup` and then + * die it the same manner as the child. + */ + unsigned fork_for_safety : 1; + } __attribute__((packed)) server_characteristics_t; @@ -154,8 +164,10 @@ int connect_to_display(void); /* __attribute__((weak)) */ * This function should be called when the server has * been properly initialised but before initialisation * of anything that is removed at forking is initialised + * + * @return Zero on success, -1 on error */ -void server_initialised(void); /* __attribute__((weak)) */ +int server_initialised(void); /* __attribute__((weak)) */ /** @@ -277,6 +289,19 @@ extern int reexec_failure_recover(void); */ extern int master_loop(void); +/** + * This function should be implemented by the actual server implementation + * if the server has set `server_characteristics.fork_for_safety` to be + * true + * + * This function is called by the parent server process when the + * child server process exits, if the server has completed its + * initialisation + * + * @param status The status the child died with + */ +void fork_cleanup(int status); /* __attribute__((weak)) */ + #endif diff --git a/src/mds-clipboard.c b/src/mds-clipboard.c index b4e47ab..21a2671 100644 --- a/src/mds-clipboard.c +++ b/src/mds-clipboard.c @@ -45,7 +45,8 @@ server_characteristics_t server_characteristics = .require_privileges = 0, .require_display = 1, .require_respawn_info = 1, - .sanity_check_argc = 1 + .sanity_check_argc = 1, + .fork_for_safety = 0 }; @@ -128,7 +129,7 @@ int initialise_server(void) message_id++; } - server_initialised(); + fail_if (server_initialised() < 0); fail_if (mds_message_initialise(&received)); for (i = 0; i < CLIPBOARD_LEVELS; i++) diff --git a/src/mds-echo.c b/src/mds-echo.c index 309c28d..01f8e97 100644 --- a/src/mds-echo.c +++ b/src/mds-echo.c @@ -45,7 +45,8 @@ server_characteristics_t server_characteristics = .require_privileges = 0, .require_display = 1, .require_respawn_info = 0, - .sanity_check_argc = 1 + .sanity_check_argc = 1, + .fork_for_safety = 0 }; @@ -106,7 +107,7 @@ int initialise_server(void) if (full_send(message, strlen(message))) return 1; - server_initialised(); + fail_if (server_initialised() < 0); fail_if (mds_message_initialise(&received)); return 0; diff --git a/src/mds-kkbd.c b/src/mds-kkbd.c new file mode 100644 index 0000000..51a8953 --- /dev/null +++ b/src/mds-kkbd.c @@ -0,0 +1,509 @@ +/** + * mds — A micro-display server + * Copyright © 2014 Mattias Andrée (maandree@member.fsf.org) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "mds-kkbd.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#define reconnect_to_display() -1 /* TODO */ + + + +#ifdef __sparc__ +# define GET_LED KIOCGLED +# define SET_LED KIOCSLED +#else +# define GET_LED KDGETLED +# define SET_LED KDSETLED +#endif + + +#ifdef __sparc__ +# define LED_NUM_LOCK 1 +# define LED_CAPS_LOCK 8 +# define LED_SCRL_LOCK 4 +# define LED_COMPOSE 2 +#else +# define LED_NUM_LOCK LED_NUM +# define LED_CAPS_LOCK LED_CAP +# define LED_SCRL_LOCK LED_SCR +#endif + + + +#define MDS_KKBD_VARS_VERSION 0 + + + +/** + * This variable should declared by the actual server implementation. + * It must be configured before `main` is invoked. + * + * This tells the server-base how to behave + */ +server_characteristics_t server_characteristics = + { + .require_privileges = 0, + .require_display = 1, + .require_respawn_info = 0, + .sanity_check_argc = 1, + .fork_for_safety = 1 + }; + + + +/** + * Value of the ‘Message ID’ header for the next message + */ +static int32_t message_id = 1; + +/** + * Buffer for received messages + */ +static mds_message_t received; + +/** + * Whether the server is connected to the display + */ +static int connected = 1; + +/** + * File descriptor for accessing the keyboard LED:s + */ +static int ledfd = 0; + +/** + * Saved LED states + */ +static int saved_leds; + +/** + * Saved TTY settings + */ +static struct termios saved_stty; + +/** + * Save keyboard mode + */ +static int saved_kbd_mode; + + + + +/** + * This function will be invoked before `initialise_server` (if not re-exec:ing) + * or before `unmarshal_server` (if re-exec:ing) + * + * @return Non-zero on error + */ +int __attribute__((const)) preinitialise_server(void) +{ + return 0; +} + + +/** + * This function should initialise the server, + * and it not invoked after a re-exec. + * + * @return Non-zero on error + */ +int initialise_server(void) +{ + int stage = 0; + const char* const message = + "Command: intercept\n" + "Message ID: 0\n" + "Length: 18\n" + "\n" + "Command: keyboard\n"; + + if (full_send(message, strlen(message))) + return 1; + + open_leds(); + stage = 1; + open_input(); + stage = 2; + + fail_if (server_initialised()); + stage = 0; + fail_if (mds_message_initialise(&received)); + + return 0; + + pfail: + xperror(*argv); + if (stage >= 2) close_input(); + if (stage >= 1) close_leds(); + mds_message_destroy(&received); + return 1; +} + + +/** + * This function will be invoked after `initialise_server` (if not re-exec:ing) + * or after `unmarshal_server` (if re-exec:ing) + * + * @return Non-zero on error + */ +int postinitialise_server(void) +{ + if (connected) + return 0; + + if (reconnect_to_display()) + { + mds_message_destroy(&received); + return 1; + } + connected = 1; + return 0; +} + + +/** + * This function is called by the parent server process when the + * child server process exits, if the server has completed its + * initialisation + * + * @param status The status the child died with + */ +void fork_cleanup(int status) +{ + (void) status; + close_input(); + close_leds(); +} + + +/** + * Calculate the number of bytes that will be stored by `marshal_server` + * + * On failure the program should `abort()` or exit by other means. + * However it should not be possible for this function to fail. + * + * @return The number of bytes that will be stored by `marshal_server` + */ +size_t marshal_server_size(void) +{ + size_t rc = 5 * sizeof(int) + sizeof(int32_t) + sizeof(struct termios); + rc += mds_message_marshal_size(&received); + return rc; +} + + +/** + * Marshal server implementation specific data into a buffer + * + * @param state_buf The buffer for the marshalled data + * @return Non-zero on error + */ +int marshal_server(char* state_buf) +{ + buf_set_next(state_buf, int, MDS_KKBD_VARS_VERSION); + buf_set_next(state_buf, int, connected); + buf_set_next(state_buf, int32_t, message_id); + buf_set_next(state_buf, int, ledfd); + buf_set_next(state_buf, int, saved_leds); + buf_set_next(state_buf, struct termios, saved_stty); + buf_set_next(state_buf, int, saved_kbd_mode); + mds_message_marshal(&received, state_buf); + + mds_message_destroy(&received); + return 0; +} + + +/** + * Unmarshal server implementation specific data and update the servers state accordingly + * + * On critical failure the program should `abort()` or exit by other means. + * That is, do not let `reexec_failure_recover` run successfully, if it unrecoverable + * error has occurred or one severe enough that it is better to simply respawn. + * + * @param state_buf The marshalled data that as not been read already + * @return Non-zero on error + */ +int unmarshal_server(char* state_buf) +{ + /* buf_get_next(state_buf, int, MDS_KKBDOARD_VARS_VERSION); */ + buf_next(state_buf, int, 1); + buf_get_next(state_buf, int, connected); + buf_get_next(state_buf, int32_t, message_id); + buf_get_next(state_buf, int, ledfd); + buf_get_next(state_buf, int, saved_leds); + buf_get_next(state_buf, struct termios, saved_stty); + buf_get_next(state_buf, int, saved_kbd_mode); + fail_if (mds_message_unmarshal(&received, state_buf)); + + return 0; + pfail: + xperror(*argv); + mds_message_destroy(&received); + abort(); /* We must abort on failure to not risk the keyboard + getting stuck and freeze up the computer until + someone ssh:es into it and kill the server. */ + return -1; +} + + +/** + * Attempt to recover from a re-exec failure that has been + * detected after the server successfully updated it execution image + * + * @return Non-zero on error + */ +int __attribute__((const)) reexec_failure_recover(void) +{ + return -1; +} + + +/** + * Perform the server's mission + * + * @return Non-zero on error + */ +int master_loop(void) +{ + int rc = 1; + int c, keycode, released; + int scancode[3]; + + while ((c = getchar()) != 1) /* Exit with ESCAPE */ + { + redo: + keycode = c & 0x7F; + released = !!(c & 0x80); + scancode[0] = keycode; + + if (keycode == 0) + { + scancode[1] = getchar(); + if ((scancode[1] & 0x80) == 0) + { + c = scancode[1]; + goto redo; + } + scancode[2] = getchar(); + if ((scancode[2] & 0x80) == 0) + { + printf("scancode: %i\n", scancode[1]); + printf("keycode: %i\n", scancode[1]); + printf("released: no\n"); + c = scancode[2]; + goto redo; + } + keycode = (scancode[1] & 0x7F) << 7; + keycode |= (scancode[2] & 0x7F); + printf("scancode: %i %i %i\n", + scancode[0], scancode[1], scancode[2]); + } + else + printf("scancode: %i\n", scancode[0]); + printf("keycode: %i\n", keycode); + printf("released: %s\n", released ? "yes" : "no"); + } + + /* + while (!reexecing && !terminating) + { + int r = mds_message_read(&received, socket_fd); + if (r == 0) + { + if (r = 0, r == 0) \/* TODO handle_message() *\/ + continue; + } + + if (r == -2) + { + eprint("corrupt message received, aborting."); + goto fail; + } + else if (errno == EINTR) + continue; + else if (errno != ECONNRESET) + goto pfail; + + eprint("lost connection to server."); + mds_message_destroy(&received); + mds_message_initialise(&received); + connected = 0; + if (reconnect_to_display()) + goto fail; + connected = 1; + } + */ + + rc = 0; + goto fail; + pfail: + xperror(*argv); + fail: + if (!rc && reexecing) + return 0; + mds_message_destroy(&received); + return rc; +} + + +/** + * Send a full message even if interrupted + * + * @param message The message to send + * @param length The length of the message + * @return Non-zero on success + */ +int full_send(const char* message, size_t length) +{ + size_t sent; + + while (length > 0) + { + sent = send_message(socket_fd, message, length); + if (sent > length) + { + eprint("Sent more of a message than exists in the message, aborting."); + return -1; + } + else if ((sent < length) && (errno != EINTR)) + { + xperror(*argv); + return -1; + } + message += sent; + length -= sent; + } + return 0; +} + + +/** + * Acquire access of the keyboard's LED:s + * + * @return Zero on success, -1 on error + */ +int open_leds(void) +{ +#ifdef __sparc__ + if ((ledfd = open(SPARC_KBD, O_RDONLY)) < 0) + return -1; + if (ioctl(ledfd, GET_LED, &saved_leds) < 0) + { + close(ledfd); + return -1; + } + return 0; +#else + return ioctl(ledfd, GET_LED, &saved_leds); +#endif +} + + +/** + * Release access of the keyboard's LED:s + */ +void close_leds(void) +{ + if (ioctl(ledfd, SET_LED, saved_leds) < 0) + xperror(*argv); +#ifdef __sparc__ + close(ledfd); +#endif +} + + +/** + * Get active LED:s on the keyboard + * + * @return Active LED:s, -1 on error + */ +int get_leds(void) +{ + int leds; + if (ioctl(ledfd, GET_LED, &leds) < 0) + return -1; +#ifdef __sparc__ + leds &= 15; +#endif + return leds; +} + + +/** + * Set active LED:s on the keyboard + * + * @param leds Active LED:s + * @return Zero on success, -1 on error + */ +int set_leds(int leds) +{ + return ioctl(ledfd, SET_LED, leds); +} + + +/** + * Acquire access of keyboard input + * + * @return Zero on success, -1 on error + */ +int open_input(void) +{ + struct termios stty; + if (tcgetattr(STDIN_FILENO, &saved_stty) < 0) + return -1; + stty = saved_stty; + stty.c_lflag &= (tcflag_t)~(ECHO | ICANON | ISIG); + stty.c_iflag = 0; + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &stty) < 0) + return -1; + /* K_MEDIUMRAW: utilise keyboard drivers, but not layout */ + if ((ioctl(STDIN_FILENO, KDGKBMODE, &saved_kbd_mode) < 0) || + (ioctl(STDIN_FILENO, KDSKBMODE, K_MEDIUMRAW) < 0)) + { + xperror(*argv); + return tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_stty); + } + return 0; +} + + +/** + * Release access of keyboard input + */ +void close_input(void) +{ + if (ioctl(STDIN_FILENO, KDSKBMODE, saved_kbd_mode) < 0) + xperror(*argv); + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_stty) < 0) + xperror(*argv); +} + + +/* TODO delay and repetition */ + diff --git a/src/mds-kkbd.h b/src/mds-kkbd.h new file mode 100644 index 0000000..fc22f2b --- /dev/null +++ b/src/mds-kkbd.h @@ -0,0 +1,75 @@ +/** + * mds — A micro-display server + * Copyright © 2014 Mattias Andrée (maandree@member.fsf.org) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef MDS_MDS_KKBD_H +#define MDS_MDS_KKBD_H + + +#include "mds-base.h" + + +/** + * Send a full message even if interrupted + * + * @param message The message to send + * @param length The length of the message + * @return Non-zero on success + */ +int full_send(const char* message, size_t length); + +/** + * Acquire access of the keyboard's LED:s + * + * @return Zero on success, -1 on error + */ +int open_leds(void); + +/** + * Release access of the keyboard's LED:s + */ +void close_leds(void); + +/** + * Get active LED:s on the keyboard + * + * @return Active LED:s, -1 on error + */ +int get_leds(void); + +/** + * Set active LED:s on the keyboard + * + * @param leds Active LED:s + * @return Zero on success, -1 on error + */ +int set_leds(int leds); + +/** + * Acquire access of keyboard input + * + * @return Zero on success, -1 on error + */ +int open_input(void); + +/** + * Release access of keyboard input + */ +void close_input(void); + + +#endif + diff --git a/src/mds-registry/mds-registry.c b/src/mds-registry/mds-registry.c index 957310e..baefee7 100644 --- a/src/mds-registry/mds-registry.c +++ b/src/mds-registry/mds-registry.c @@ -42,7 +42,8 @@ server_characteristics_t server_characteristics = .require_privileges = 0, .require_display = 1, .require_respawn_info = 0, - .sanity_check_argc = 1 + .sanity_check_argc = 1, + .fork_for_safety = 0 }; @@ -111,7 +112,7 @@ int initialise_server(void) } reg_table.key_comparator = (compare_func*)string_comparator; reg_table.hasher = (hash_func*)string_hash; - server_initialised(); + fail_if (server_initialised() < 0); fail_if (mds_message_initialise(&received)); return 0; diff --git a/src/mds-respawn.c b/src/mds-respawn.c index 24084f0..ebe8411 100644 --- a/src/mds-respawn.c +++ b/src/mds-respawn.c @@ -46,7 +46,8 @@ server_characteristics_t server_characteristics = .require_privileges = 0, .require_display = 0, .require_respawn_info = 1, - .sanity_check_argc = 0 + .sanity_check_argc = 0, + .fork_for_safety = 0 }; @@ -508,7 +509,7 @@ int master_loop(void) while (!reexecing && !terminating && live_count) { - pid_t pid = waitpid(-1, &status, 0); + pid_t pid = uninterruptable_waitpid(-1, &status, 0); if (reviving) for (reviving = 0, i = 0; i < servers; i++) @@ -517,8 +518,6 @@ int master_loop(void) if (pid == (pid_t)-1) { - if (errno == EINTR) - continue; xperror(*argv); rc = 1; break; diff --git a/src/mds-server/mds-server.c b/src/mds-server/mds-server.c index 46451a0..b217f2b 100644 --- a/src/mds-server/mds-server.c +++ b/src/mds-server/mds-server.c @@ -56,7 +56,8 @@ server_characteristics_t server_characteristics = .require_privileges = 0, .require_display = 0, /* We will service one ourself. */ .require_respawn_info = 1, - .sanity_check_argc = 1 + .sanity_check_argc = 1, + .fork_for_safety = 0 }; diff --git a/src/mds.c b/src/mds.c index 5a204f6..cb8e657 100644 --- a/src/mds.c +++ b/src/mds.c @@ -336,7 +336,7 @@ int spawn_and_respawn_server(int fd) perror(*argv); /* Wait for master server to die. */ - if (waitpid(pid, &status, 0) == (pid_t)-1) + if (uninterruptable_waitpid(pid, &status, 0) == (pid_t)-1) goto pfail; /* If the server exited normally or SIGTERM, do not respawn. */ -- cgit v1.2.3-70-g09d2