forked from external/yambar
388 lines
11 KiB
C
388 lines
11 KiB
C
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <getopt.h>
|
|
#include <locale.h>
|
|
#include <poll.h>
|
|
#include <signal.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <threads.h>
|
|
#include <unistd.h>
|
|
|
|
#include <fcntl.h>
|
|
#include <pwd.h>
|
|
#include <sys/eventfd.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
|
|
#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;
|
|
}
|