diff --git a/Makefile b/Makefile index 4e323b3..3278586 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ prepare: @cp $(BUILD_DIR)/compile_commands.json $(MAKEFILE_DIR) build: prepare - ${RM} -f $(TARGET) + ${RM} $(TARGET) @cmake --build $(BUILD_DIR) @cp $(BUILD_DIR)/$(TARGET_NAME) $(TARGET) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5eeb580..46b129f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.24) set(APP_NAME "zdwm") set(SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") +set(BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}") add_compile_definitions(LOG_FILE="$ENV{HOME}/zdwm-log.txt") @@ -18,7 +19,49 @@ project(${APP_NAME} LANGUAGES C ) -add_executable(${APP_NAME} main.c) +add_executable(${APP_NAME} main.c utils.c buffer.c backtrace.c) # use C23 set_property(TARGET ${APP_NAME} PROPERTY C_STANDARD 23) + +find_package(PkgConfig REQUIRED) + +pkg_check_modules(deps REQUIRED + glib-2.0 +) + +target_include_directories(${APP_NAME} SYSTEM + PRIVATE ${deps_INCLUDE_DIRS} +) +target_include_directories(${APP_NAME} + PRIVATE ${SOURCE_DIR} + PRIVATE ${BUILD_DIR} +) + +target_link_libraries(${APP_NAME} + PRIVATE ${deps_LIBRARIES} +) + + +## 配置项 +# Check for backtrace_symbols() +include(CheckSymbolExists) +check_symbol_exists(backtrace_symbols execinfo.h HAS_EXECINFO) +if(NOT HAS_EXECINFO) + find_library(LIB_EXECINFO execinfo) + if(LIB_EXECINFO) + set(HAS_EXECINFO TRUE) + target_link_libraries(${APP_NAME} ${LIB_EXECINFO}) + endif() +endif() +if(HAS_EXECINFO) + message(STATUS "checking for execinfo -- found") +else() + message(STATUS "checking for execinfo -- not found") +endif() + +configure_file("${SOURCE_DIR}/config.h.in" + "${BUILD_DIR}/config.h" + ESCAPE_QUOTES + @ONLY +) diff --git a/src/backtrace.c b/src/backtrace.c new file mode 100644 index 0000000..26ca326 --- /dev/null +++ b/src/backtrace.c @@ -0,0 +1,38 @@ +#include "backtrace.h" + +#include "buffer.h" +#include "config.h" + +#ifdef HAS_EXECINFO +#include +#endif + +#define MAX_STACK_SIZE 32 + + +/** + * Get a backtrace. + * @param buffer The buffer to fill with backtrace. + */ +void backtrace_get(buffer_t *buffer) { + buffer_init(buffer); + +#ifdef HAS_EXECINFO + void *stack[MAX_STACK_SIZE]; + char **bt; + int stack_size; + + stack_size = backtrace(stack, countof(stack)); + bt = backtrace_symbols(stack, stack_size); + + if (bt) { + for (int i = 0; i < stack_size; i++) { + if (i > 0) buffer_addsl(buffer, "\n"); + buffer_adds(buffer, bt[i]); + } + p_delete(&bt); + } else +#endif + + buffer_addsl(buffer, "Cannot get backtrace symbols."); +} diff --git a/src/backtrace.h b/src/backtrace.h new file mode 100644 index 0000000..0b5947b --- /dev/null +++ b/src/backtrace.h @@ -0,0 +1,5 @@ +#pragma once + +#include "buffer.h" + +void backtrace_get(buffer_t *buffer); diff --git a/src/buffer.c b/src/buffer.c new file mode 100644 index 0000000..a0be1ce --- /dev/null +++ b/src/buffer.c @@ -0,0 +1,37 @@ +#include "buffer.h" + +#include +#include +#include + +#include "utils.h" + +char buffer_slop[1]; + +void buffer_ensure(buffer_t *buf, int newlen) { + if (newlen < 0) exit(EX_SOFTWARE); + + if (newlen < buf->size) return; + + if (newlen < buf->offs + buf->size && buf->offs > buf->size / 4) { + /* Data fits in the current area, shift it left */ + memmove(buf->s - buf->offs, buf->s, buf->len + 1); + buf->s -= buf->offs; + buf->size += buf->offs; + buf->offs = 0; + return; + } + + buf->size = p_alloc_nr(buf->size + buf->offs); + if (buf->size < newlen + 1) buf->size = newlen + 1; + if (buf->alloced && !buf->offs) + p_realloc(&buf->s, buf->size); + else { + char *new_area = xmalloc(buf->size); + memcpy(new_area, buf->s, buf->len + 1); + if (buf->alloced) free(buf->s - buf->offs); + buf->alloced = true; + buf->s = new_area; + buf->offs = 0; + } +} diff --git a/src/buffer.h b/src/buffer.h new file mode 100644 index 0000000..fbbe9f9 --- /dev/null +++ b/src/buffer.h @@ -0,0 +1,78 @@ +#pragma once + +#include +#include + +#include "utils.h" + +typedef struct buffer_t { + char *s; + int len, size; + unsigned alloced : 1; + unsigned offs : 31; +} buffer_t; + +extern char buffer_slop[1]; + +#define BUFFER_INIT (buffer_t){.s = buffer_slop, .size = 1} + +/** + * Initialize a buffer. + * @param buf A buffer pointer. + * @return The same buffer pointer. + */ +static inline buffer_t *buffer_init(buffer_t *buf) { + *buf = BUFFER_INIT; + return buf; +} + +void buffer_ensure(buffer_t *buf, int len); + +/** + * Add data in the buffer. + * @param buf Buffer where to add. + * @param pos Position where to add. + * @param len Length. + * @param data Data to add. + * @param dlen Data length. + */ +static inline void buffer_splice(buffer_t *buf, int pos, int len, + const void *data, int dlen) { + assert(pos >= 0 && len >= 0 && dlen >= 0); + + if (unlikely(pos > buf->len)) pos = buf->len; + if (unlikely(len > buf->len - pos)) len = buf->len - pos; + if (pos == 0 && len + buf->offs >= dlen) { + buf->offs += len - dlen; + buf->s += len - dlen; + buf->size -= len - dlen; + buf->len -= len - dlen; + } else if (len != dlen) { + buffer_ensure(buf, buf->len + dlen - len); + memmove(buf->s + pos + dlen, buf->s + pos + len, buf->len - pos - len); + buf->len += dlen - len; + buf->s[buf->len] = '\0'; + } + memcpy(buf->s + pos, data, dlen); +} + +/** + * Add data at the end of buffer. + * @param buf Buffer where to add. + * @param data Data to add. + * @param len Data length. + */ +static inline void buffer_add(buffer_t *buf, const void *data, int len) { + buffer_splice(buf, buf->len, 0, data, len); +} + +#define buffer_addsl(buf, data) buffer_add(buf, data, sizeof(data) - 1); + +/** + * Add a string to the and of a buffer. + * @param buf The buffer where to add. + * @param s The string to add. + */ +static inline void buffer_adds(buffer_t *buf, const char *s) { + buffer_splice(buf, buf->len, 0, s, a_strlen(s)); +} diff --git a/src/config.h.in b/src/config.h.in new file mode 100644 index 0000000..58537fe --- /dev/null +++ b/src/config.h.in @@ -0,0 +1,5 @@ +#pragma once + +#define APP_NAME "@APP_NAME@" + +#cmakedefine HAS_EXECINFO diff --git a/src/main.c b/src/main.c index 44e82e2..d7d99b7 100644 --- a/src/main.c +++ b/src/main.c @@ -1 +1,86 @@ -int main(int argc, char *argv[]) { return 0; } +#include +#include +#include +#include +#include +#include +#include + +#include "backtrace.h" +#include "buffer.h" +#include "types.h" +#include "utils.h" + +zdwm_t global_state; + +/** argv used to run zdwm */ +static char **zdwm_argv; + +/** A pipe that is used to asynchronously handle SIGCHLD */ +static int sigchld_pipe[2]; + +static void zdwm_atexit(bool restart) { global_state.need_restart = restart; } + +static void zdwm_restart(void) { + zdwm_atexit(true); + execvp(zdwm_argv[0], zdwm_argv); + fatal("execv() failed: %s", strerror(errno)); +} + +static gboolean restart_on_signal(gpointer data) { + zdwm_restart(); + return TRUE; +} + +static gboolean exit_on_signal(gpointer data) { + g_main_loop_quit(global_state.loop); + return TRUE; +} + +static void signal_fatal(int signal_number) { + buffer_t buffer; + backtrace_get(&buffer); + fatal("signal %d, dumping backtrace\n%s", signal_number, buffer.s); +} + +/* Signal handler for SIGCHLD. Causes reap_children() to be called. */ +static void signal_child(int signum) { + assert(signum == SIGCHLD); + int res = write(sigchld_pipe[1], " ", 1); + (void)res; + assert(res == 1); +} + +int main(int argc, char *argv[]) { + p_clear(&global_state, 1); + + zdwm_argv = argv; + + g_unix_signal_add(SIGINT, exit_on_signal, NULL); + g_unix_signal_add(SIGTERM, exit_on_signal, NULL); + g_unix_signal_add(SIGHUP, restart_on_signal, NULL); + + struct sigaction sa = {.sa_handler = signal_fatal, .sa_flags = SA_RESETHAND}; + sigemptyset(&sa.sa_mask); + sigaction(SIGABRT, &sa, 0); + sigaction(SIGBUS, &sa, 0); + sigaction(SIGFPE, &sa, 0); + sigaction(SIGILL, &sa, 0); + sigaction(SIGSEGV, &sa, 0); + signal(SIGPIPE, SIG_IGN); + + sa.sa_handler = signal_child; + sa.sa_flags = SA_NOCLDSTOP | SA_RESTART; + sigaction(SIGCHLD, &sa, 0); + + if (global_state.loop == NULL) { + global_state.loop = g_main_loop_new(NULL, FALSE); + g_main_loop_run(global_state.loop); + } + g_main_loop_unref(global_state.loop); + global_state.loop = NULL; + + zdwm_atexit(false); + + return EXIT_SUCCESS; +} diff --git a/src/types.h b/src/types.h new file mode 100644 index 0000000..c9f2742 --- /dev/null +++ b/src/types.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +typedef struct zdwm_t { + bool need_restart; + GMainLoop *loop; +} zdwm_t; diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..ece8992 --- /dev/null +++ b/src/utils.c @@ -0,0 +1,34 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include "config.h" + +const char *current_time_str(void) { + static char buffer[25]; + time_t now; + struct tm tm; + + time(&now); + localtime_r(&now, &tm); + if (!strftime(buffer, sizeof(buffer), "%Y-%m-%d %T", &tm)) buffer[0] = '\0'; + + return buffer; +} + +/** Print error and exit with EXIT_FAILURE code. + */ +void _fatal(int line, const char *fct, const char *file, const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + const char *format = "(EE) [%s] %s\n%s:%d:%s:\n"; + fprintf(stderr, format, APP_NAME, current_time_str(), file, line, fct); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); + exit(EXIT_FAILURE); +} diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..8050922 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,66 @@ +#pragma once + +#include +#include + +#define ssizeof(foo) (ssize_t)sizeof(foo) +#define countof(foo) (ssizeof(foo) / ssizeof(foo[0])) + +#define p_alloc_nr(x) (((x) + 16) * 3 / 2) +#define p_clear(p, count) ((void)memset((p), 0, sizeof(*(p)) * (count))) +#define p_realloc(pp, count) xrealloc((void *)(pp), sizeof(**(pp)) * (count)) + +#define p_delete(mem_p) \ + do { \ + void **__ptr = (void **)(mem_p); \ + free(*__ptr); \ + *(void **)__ptr = NULL; \ + } while (0) + +#ifdef __GNUC__ +#define likely(expr) __builtin_expect(!!(expr), 1) +#define unlikely(expr) __builtin_expect((expr), 0) +#else +#define likely(expr) expr +#define unlikely(expr) expr +#endif + +static inline void *__attribute__((malloc)) xmalloc(ssize_t size) { + void *ptr; + + if (size <= 0) return NULL; + + ptr = calloc(1, size); + + if (!ptr) abort(); + + return ptr; +} + +static inline void xrealloc(void **ptr, ssize_t newsize) { + if (newsize <= 0) + p_delete(ptr); + else { + *ptr = realloc(*ptr, newsize); + if (!*ptr) abort(); + } +} + +/** + * @brief NULL resistant strlen. + * + * Unlike it's libc sibling, a_strlen returns a ssize_t, and supports its + * argument being NULL. + * + * @param s the string. + * @return the string length (or 0 if s is NULL). + */ +static inline ssize_t a_strlen(const char *s) { return s ? strlen(s) : 0; } + +#define fatal(format, ...) \ + _fatal(__LINE__, __FUNCTION__, __FILE__, format, ##__VA_ARGS__) +void _fatal(int line, const char *function, const char *file, + const char *format, ...) __attribute__((noreturn)) +__attribute__((format(printf, 4, 5))); + +const char *current_time_str(void);