#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bar/bar.h" #include "config.h" #include "yml.h" #define LOG_MODULE "main" #include "log.h" #include "version.h" static volatile sig_atomic_t aborted = 0; static void signal_handler(int signo) { aborted = signo; } static char * get_config_path_user_config(void) { struct passwd *passwd = getpwuid(getuid()); if (passwd == NULL) { LOG_ERRNO("failed to lookup user"); return NULL; } const char *home_dir = passwd->pw_dir; LOG_DBG("user's home directory: %s", home_dir); int len = snprintf(NULL, 0, "%s/.config/yambar/config.yml", home_dir); char *path = malloc(len + 1); snprintf(path, len + 1, "%s/.config/yambar/config.yml", home_dir); return path; } static char * get_config_path_xdg(void) { const char *xdg_config_home = getenv("XDG_CONFIG_HOME"); if (xdg_config_home == NULL) return NULL; int len = snprintf(NULL, 0, "%s/yambar/config.yml", xdg_config_home); char *path = malloc(len + 1); snprintf(path, len + 1, "%s/yambar/config.yml", xdg_config_home); return path; } static char * get_config_path(void) { struct stat st; char *config = get_config_path_xdg(); if (config != NULL && stat(config, &st) == 0 && S_ISREG(st.st_mode)) return config; free(config); /* 'Default' XDG_CONFIG_HOME */ config = get_config_path_user_config(); if (config != NULL && stat(config, &st) == 0 && S_ISREG(st.st_mode)) return config; free(config); return NULL; } static struct bar * load_bar(const char *config_path, enum bar_backend backend) { FILE *conf_file = fopen(config_path, "re"); if (conf_file == NULL) { LOG_ERRNO("%s: failed to open", config_path); return NULL; } struct bar *bar = NULL; char *yml_error = NULL; struct yml_node *conf = yml_load(conf_file, &yml_error); if (conf == NULL) { LOG_ERR("%s:%s", config_path, yml_error); goto out; } const struct yml_node *bar_conf = yml_get_value(conf, "bar"); if (bar_conf == NULL) { LOG_ERR("%s: missing required top level key 'bar'", config_path); goto out; } bar = conf_to_bar(bar_conf, backend); if (bar == NULL) { LOG_ERR("%s: failed to load configuration", config_path); goto out; } out: free(yml_error); yml_destroy(conf); fclose(conf_file); return bar; } static void print_usage(const char *prog_name) { printf("Usage: %s [OPTION]...\n", prog_name); printf("\n"); printf("Options:\n"); printf(" -b,--backend={xcb,wayland,auto} backend to use (default: auto)\n" " -c,--config=FILE alternative configuration file\n" " -C,--validate verify configuration then quit\n" " -p,--print-pid=FILE|FD print PID to file or FD\n" " -d,--log-level={info|warning|error|none} log level (warning)\n" " -l,--log-colorize=[never|always|auto] enable/disable colorization of log output on stderr\n" " -s,--log-no-syslog disable syslog logging\n" " -v,--version show the version number and quit\n"); } static bool print_pid(const char *pid_file, bool *unlink_at_exit) { LOG_DBG("printing PID to %s", pid_file); errno = 0; char *end; int pid_fd = strtoul(pid_file, &end, 10); if (errno != 0 || *end != '\0') { if ((pid_fd = open(pid_file, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) < 0) { LOG_ERRNO("%s: failed to open", pid_file); return false; } else *unlink_at_exit = true; } if (pid_fd >= 0) { char pid[32]; snprintf(pid, sizeof(pid), "%u\n", getpid()); ssize_t bytes = write(pid_fd, pid, strlen(pid)); close(pid_fd); if (bytes < 0) { LOG_ERRNO("failed to write PID to FD=%u", pid_fd); return false; } LOG_DBG("wrote %zd bytes to FD=%d", bytes, pid_fd); return true; } else return false; } int main(int argc, char *const *argv) { static const struct option longopts[] = { {"backend", required_argument, 0, 'b'}, {"config", required_argument, 0, 'c'}, {"validate", no_argument, 0, 'C'}, {"print-pid", required_argument, 0, 'p'}, {"log-level", required_argument, 0, 'd'}, {"log-colorize", optional_argument, 0, 'l'}, {"log-no-syslog", no_argument, 0, 's'}, {"version", no_argument, 0, 'v'}, {"help", no_argument, 0, 'h'}, {NULL, no_argument, 0, 0}, }; bool unlink_pid_file = false; const char *pid_file = NULL; bool verify_config = false; char *config_path = NULL; enum bar_backend backend = BAR_BACKEND_AUTO; enum log_class log_level = LOG_CLASS_WARNING; enum log_colorize log_colorize = LOG_COLORIZE_AUTO; bool log_syslog = true; while (true) { int c = getopt_long(argc, argv, ":b:c:Cp:d:l::svh", longopts, NULL); if (c == -1) break; switch (c) { case 'b': if (strcmp(optarg, "xcb") == 0) backend = BAR_BACKEND_XCB; else if (strcmp(optarg, "wayland") == 0) backend = BAR_BACKEND_WAYLAND; else { fprintf(stderr, "%s: invalid backend\n", optarg); return EXIT_FAILURE; } break; case 'c': { struct stat st; if (stat(optarg, &st) == -1) { fprintf(stderr, "%s: invalid configuration file: %s\n", optarg, strerror(errno)); return EXIT_FAILURE; } else if (!S_ISREG(st.st_mode) && !S_ISFIFO(st.st_mode)) { fprintf(stderr, "%s: invalid configuration file: neither a regular file nor a pipe or FIFO\n", optarg); return EXIT_FAILURE; } config_path = strdup(optarg); break; } case 'C': verify_config = true; break; case 'p': pid_file = optarg; break; case 'd': { int lvl = log_level_from_string(optarg); if (lvl < 0) { fprintf(stderr, "-d,--log-level: %s: argument must be one of %s\n", optarg, log_level_string_hint()); return EXIT_FAILURE; } log_level = lvl; break; } case 'l': if (optarg == NULL || strcmp(optarg, "auto") == 0) log_colorize = LOG_COLORIZE_AUTO; else if (strcmp(optarg, "never") == 0) log_colorize = LOG_COLORIZE_NEVER; else if (strcmp(optarg, "always") == 0) log_colorize = LOG_COLORIZE_ALWAYS; else { fprintf(stderr, "%s: argument must be one of 'never', 'always' or 'auto'\n", optarg); return EXIT_FAILURE; } break; case 's': log_syslog = false; break; case 'v': printf("yambar version %s\n", YAMBAR_VERSION); return EXIT_SUCCESS; case 'h': print_usage(argv[0]); return EXIT_SUCCESS; case ':': fprintf(stderr, "error: -%c: missing required argument\n", optopt); return EXIT_FAILURE; case '?': fprintf(stderr, "error: -%c: invalid option\n", optopt); return EXIT_FAILURE; } } log_init(log_colorize, log_syslog, LOG_FACILITY_DAEMON, log_level); _Static_assert((int)LOG_CLASS_ERROR == (int)FCFT_LOG_CLASS_ERROR, "fcft log level enum offset"); _Static_assert((int)LOG_COLORIZE_ALWAYS == (int)FCFT_LOG_COLORIZE_ALWAYS, "fcft colorize enum mismatch"); fcft_init((enum fcft_log_colorize)log_colorize, log_syslog, (enum fcft_log_class)log_level); atexit(&fcft_fini); const struct sigaction sa = {.sa_handler = &signal_handler}; sigaction(SIGINT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); /* Block SIGINT (this is under the assumption that threads inherit * the signal mask */ sigset_t signal_mask; sigemptyset(&signal_mask); sigaddset(&signal_mask, SIGINT); sigaddset(&signal_mask, SIGTERM); pthread_sigmask(SIG_BLOCK, &signal_mask, NULL); int abort_fd = eventfd(0, EFD_CLOEXEC); if (abort_fd == -1) { LOG_ERRNO("failed to create eventfd (for abort signalling)"); log_deinit(); return 1; } if (config_path == NULL) { config_path = get_config_path(); if (config_path == NULL) { LOG_ERR("could not find a configuration (see man 5 yambar)"); log_deinit(); return 1; } } struct bar *bar = load_bar(config_path, backend); free(config_path); if (bar == NULL) { close(abort_fd); log_deinit(); return 1; } if (verify_config) { bar->destroy(bar); close(abort_fd); log_deinit(); return 0; } setlocale(LC_ALL, ""); bar->abort_fd = abort_fd; thrd_t bar_thread; thrd_create(&bar_thread, (int (*)(void *))bar->run, bar); /* Now unblock. We should be only thread receiving SIGINT/SIGTERM */ pthread_sigmask(SIG_UNBLOCK, &signal_mask, NULL); if (pid_file != NULL) { if (!print_pid(pid_file, &unlink_pid_file)) goto done; } while (!aborted) { struct pollfd fds[] = {{.fd = abort_fd, .events = POLLIN}}; int r __attribute__((unused)) = poll(fds, sizeof(fds) / sizeof(fds[0]), -1); if (fds[0].revents & (POLLIN | POLLHUP)) { /* * Either the bar aborted (triggering the abort_fd), or user * killed us (triggering the signal handler which sets * 'aborted') */ assert(aborted || r == 1); break; } } if (aborted) LOG_INFO("aborted: %s (%ld)", strsignal(aborted), (long)aborted); done: /* Signal abort to other threads */ if (write(abort_fd, &(uint64_t){1}, sizeof(uint64_t)) != sizeof(uint64_t)) LOG_ERRNO("failed to signal abort to threads"); int res; int r = thrd_join(bar_thread, &res); if (r != 0) LOG_ERRNO_P(r, "failed to join bar thread"); bar->destroy(bar); close(abort_fd); if (unlink_pid_file) unlink(pid_file); log_deinit(); return res; }