priod: PoC skeleton
This commit is contained in:
parent
cca674789b
commit
5b334e4111
|
@ -48,6 +48,9 @@ target_link_libraries (input-switch ${project_libraries})
|
||||||
add_executable (fancontrol-ng fancontrol-ng.c)
|
add_executable (fancontrol-ng fancontrol-ng.c)
|
||||||
target_link_libraries (fancontrol-ng ${project_libraries})
|
target_link_libraries (fancontrol-ng ${project_libraries})
|
||||||
|
|
||||||
|
add_executable (priod priod.c)
|
||||||
|
target_link_libraries (priod ${project_libraries})
|
||||||
|
|
||||||
if (WITH_GDM)
|
if (WITH_GDM)
|
||||||
include_directories (${gdm_INCLUDE_DIRS})
|
include_directories (${gdm_INCLUDE_DIRS})
|
||||||
add_executable (gdm-switch-user gdm-switch-user.c)
|
add_executable (gdm-switch-user gdm-switch-user.c)
|
||||||
|
@ -75,11 +78,13 @@ install (FILES ${PROJECT_BINARY_DIR}/fancontrol-ng.service
|
||||||
install (FILES fancontrol-ng.conf.example
|
install (FILES fancontrol-ng.conf.example
|
||||||
DESTINATION ${CMAKE_INSTALL_DATADIR}/fancontrol-ng)
|
DESTINATION ${CMAKE_INSTALL_DATADIR}/fancontrol-ng)
|
||||||
|
|
||||||
|
# TODO: priod is also going to need a systemd unit file
|
||||||
|
|
||||||
if (WITH_GDM)
|
if (WITH_GDM)
|
||||||
install (TARGETS gdm-switch-user DESTINATION ${CMAKE_INSTALL_BINDIR})
|
install (TARGETS gdm-switch-user DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||||
endif (WITH_GDM)
|
endif (WITH_GDM)
|
||||||
|
|
||||||
install (TARGETS wmstatus brightness input-switch fancontrol-ng siprandom
|
install (TARGETS wmstatus brightness input-switch fancontrol-ng priod siprandom
|
||||||
DESTINATION ${CMAKE_INSTALL_BINDIR})
|
DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||||
install (PROGRAMS shellify DESTINATION ${CMAKE_INSTALL_BINDIR})
|
install (PROGRAMS shellify DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||||
install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
|
install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
|
||||||
|
|
10
README.adoc
10
README.adoc
|
@ -5,14 +5,16 @@ desktop-tools
|
||||||
'desktop-tools' is a collection of tools to run my desktop that might be useful
|
'desktop-tools' is a collection of tools to run my desktop that might be useful
|
||||||
to other people as well:
|
to other people as well:
|
||||||
|
|
||||||
- 'wmstatus' does literally everything my dwm doesn't but I'd like it to. It
|
- 'wmstatus' does literally everything my i3 doesn't but I'd like it to. It
|
||||||
includes PulseAudio volume management and hand-written NUT and MPD clients,
|
includes PulseAudio volume management and hand-written NUT and MPD clients,
|
||||||
all in the name of liberation from GPL-licensed software of course.
|
all in the name of liberation from GPL-licensed software of course
|
||||||
- 'brightness' allows me to change the brightness of w/e display device I have.
|
- 'brightness' allows me to change the brightness of w/e display device I have
|
||||||
- 'input-switch' likewise switches the input source of external displays.
|
- 'input-switch' likewise switches the input source of external displays
|
||||||
- 'fancontrol-ng' is a clone of fancontrol that can handle errors on resume
|
- 'fancontrol-ng' is a clone of fancontrol that can handle errors on resume
|
||||||
from suspend instead of setting fans to maximum speed and quitting;
|
from suspend instead of setting fans to maximum speed and quitting;
|
||||||
in general it doesn't handle everything the original does
|
in general it doesn't handle everything the original does
|
||||||
|
- 'priod' sets CPU, I/O and OOM killer priorities for new processes according
|
||||||
|
to configuration
|
||||||
- 'shellify' is a simple script that sets up a shell for commands like vgdb
|
- 'shellify' is a simple script that sets up a shell for commands like vgdb
|
||||||
and nmcli that are painfully lacking it
|
and nmcli that are painfully lacking it
|
||||||
- 'gdm-switch-user' tells the running GDM daemon, if any, to show the switch
|
- 'gdm-switch-user' tells the running GDM daemon, if any, to show the switch
|
||||||
|
|
|
@ -0,0 +1,335 @@
|
||||||
|
/*
|
||||||
|
* priod.c: process reprioritizing daemon
|
||||||
|
*
|
||||||
|
* Thanks to http://netsplit.com/the-proc-connector-and-socket-filters
|
||||||
|
*
|
||||||
|
* Copyright (c) 2017, Přemysl Janouch <p.janouch@gmail.com>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define _GNU_SOURCE
|
||||||
|
#define LIBERTY_WANT_POLLER
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#undef PROGRAM_NAME
|
||||||
|
#define PROGRAM_NAME "priod"
|
||||||
|
#include "liberty/liberty.c"
|
||||||
|
|
||||||
|
#include <linux/cn_proc.h>
|
||||||
|
#include <linux/netlink.h>
|
||||||
|
#include <linux/connector.h>
|
||||||
|
|
||||||
|
#include <sys/resource.h>
|
||||||
|
#include <sys/syscall.h>
|
||||||
|
|
||||||
|
// --- Main program ------------------------------------------------------------
|
||||||
|
|
||||||
|
struct app_context
|
||||||
|
{
|
||||||
|
struct poller poller; ///< Poller
|
||||||
|
bool polling; ///< The event loop is running
|
||||||
|
|
||||||
|
int proc_fd; ///< Proc connector FD
|
||||||
|
struct poller_fd proc_event; ///< Proc connector read event
|
||||||
|
};
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
static void
|
||||||
|
log_message_custom (void *user_data, const char *quote, const char *fmt,
|
||||||
|
va_list ap)
|
||||||
|
{
|
||||||
|
(void) user_data;
|
||||||
|
FILE *stream = stdout;
|
||||||
|
|
||||||
|
// TODO: sd-daemon.h log level prefixes?
|
||||||
|
fputs (quote, stream);
|
||||||
|
vfprintf (stream, fmt, ap);
|
||||||
|
fputs ("\n", stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Signals -----------------------------------------------------------------
|
||||||
|
|
||||||
|
static int g_signal_pipe[2]; ///< A pipe used to signal... signals
|
||||||
|
|
||||||
|
static void
|
||||||
|
sigterm_handler (int signum)
|
||||||
|
{
|
||||||
|
(void) signum;
|
||||||
|
|
||||||
|
int original_errno = errno;
|
||||||
|
if (write (g_signal_pipe[1], "", 1) == -1)
|
||||||
|
soft_assert (errno == EAGAIN);
|
||||||
|
errno = original_errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
setup_signal_handlers (void)
|
||||||
|
{
|
||||||
|
if (pipe (g_signal_pipe) == -1)
|
||||||
|
exit_fatal ("%s: %s", "pipe", strerror (errno));
|
||||||
|
|
||||||
|
set_cloexec (g_signal_pipe[0]);
|
||||||
|
set_cloexec (g_signal_pipe[1]);
|
||||||
|
|
||||||
|
// So that the pipe cannot overflow; it would make write() block within
|
||||||
|
// the signal handler, which is something we really don't want to happen.
|
||||||
|
// The same holds true for read().
|
||||||
|
set_blocking (g_signal_pipe[0], false);
|
||||||
|
set_blocking (g_signal_pipe[1], false);
|
||||||
|
|
||||||
|
(void) signal (SIGPIPE, SIG_IGN);
|
||||||
|
|
||||||
|
struct sigaction sa;
|
||||||
|
sa.sa_flags = SA_RESTART;
|
||||||
|
sa.sa_handler = sigterm_handler;
|
||||||
|
sigemptyset (&sa.sa_mask);
|
||||||
|
|
||||||
|
if (sigaction (SIGINT, &sa, NULL) == -1
|
||||||
|
|| sigaction (SIGTERM, &sa, NULL) == -1)
|
||||||
|
exit_fatal ("sigaction: %s", strerror (errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Main program ------------------------------------------------------------
|
||||||
|
|
||||||
|
// IO priorities are a sort-of-private kernel API with no proper headers
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
IOPRIO_CLASS_NONE,
|
||||||
|
IOPRIO_CLASS_RT,
|
||||||
|
IOPRIO_CLASS_BE,
|
||||||
|
IOPRIO_CLASS_IDLE,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
IOPRIO_WHO_PROCESS = 1,
|
||||||
|
IOPRIO_WHO_PGRP,
|
||||||
|
IOPRIO_WHO_USER,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define IOPRIO_CLASS_SHIFT 13
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_exec_name (struct app_context *ctx, int pid, const char *name)
|
||||||
|
{
|
||||||
|
print_status ("exec %d %s", pid, name);
|
||||||
|
|
||||||
|
if (true)
|
||||||
|
return;
|
||||||
|
|
||||||
|
setpriority (PRIO_PROCESS, pid, 0 /* TODO -20..20 */);
|
||||||
|
|
||||||
|
// TODO: this is per thread, and there's an inherent race condition;
|
||||||
|
// keep going through /proc/%d/task and reprioritize all threads;
|
||||||
|
// stop trying after N-th try
|
||||||
|
syscall (SYS_ioprio_set, IOPRIO_WHO_PROCESS,
|
||||||
|
IOPRIO_CLASS_BE << IOPRIO_CLASS_SHIFT | 0 /* TODO 0..7 */);
|
||||||
|
|
||||||
|
char *path = xstrdup_printf ("/proc/%d/oom_score_adj", pid);
|
||||||
|
struct error *e = NULL;
|
||||||
|
// TODO: figure out the contents
|
||||||
|
if (!write_file (path, "", 0, &e))
|
||||||
|
{
|
||||||
|
print_error ("%s", e->message);
|
||||||
|
error_free (e);
|
||||||
|
}
|
||||||
|
free (path);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_exec (struct app_context *ctx, int pid)
|
||||||
|
{
|
||||||
|
char *path = xstrdup_printf ("/proc/%d/cmdline", pid);
|
||||||
|
struct str cmdline;
|
||||||
|
str_init (&cmdline);
|
||||||
|
|
||||||
|
struct error *e = NULL;
|
||||||
|
if (read_file (path, &cmdline, &e))
|
||||||
|
on_exec_name (ctx, pid, cmdline.str);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
print_debug ("%s", e->message);
|
||||||
|
error_free (e);
|
||||||
|
}
|
||||||
|
|
||||||
|
free (path);
|
||||||
|
str_free (&cmdline);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_netlink_message (struct app_context *ctx, struct nlmsghdr *mh)
|
||||||
|
{
|
||||||
|
if (mh->nlmsg_type == NLMSG_ERROR
|
||||||
|
|| mh->nlmsg_type == NLMSG_NOOP)
|
||||||
|
return;
|
||||||
|
|
||||||
|
struct cn_msg *m = NLMSG_DATA (mh);
|
||||||
|
if (m->id.idx != CN_IDX_PROC
|
||||||
|
|| m->id.val != CN_VAL_PROC)
|
||||||
|
return;
|
||||||
|
|
||||||
|
struct proc_event *e = (struct proc_event *) m->data;
|
||||||
|
if (e->what == PROC_EVENT_EXEC)
|
||||||
|
on_exec (ctx, e->event_data.exit.process_tgid);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_event (const struct pollfd *pfd, struct app_context *ctx)
|
||||||
|
{
|
||||||
|
char buf[4096];
|
||||||
|
struct sockaddr_nl addr;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
socklen_t addr_len = sizeof addr;
|
||||||
|
ssize_t len = recvfrom (pfd->fd, buf, sizeof buf, 0,
|
||||||
|
(struct sockaddr *) &addr, &addr_len);
|
||||||
|
if (len == 0)
|
||||||
|
exit_fatal ("socket closed");
|
||||||
|
if (len < 0 && errno == EAGAIN)
|
||||||
|
return;
|
||||||
|
if (len < 0)
|
||||||
|
exit_fatal ("recvfrom: %s", strerror (errno));
|
||||||
|
|
||||||
|
// Make sure it comes from the kernel
|
||||||
|
if (addr.nl_pid)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (struct nlmsghdr *mh = (struct nlmsghdr *) buf;
|
||||||
|
NLMSG_OK (mh, len); mh = NLMSG_NEXT (mh, len))
|
||||||
|
on_netlink_message (ctx, mh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_signal_pipe_readable (const struct pollfd *fd, struct app_context *ctx)
|
||||||
|
{
|
||||||
|
char id = 0;
|
||||||
|
(void) read (fd->fd, &id, 1);
|
||||||
|
|
||||||
|
ctx->polling = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
parse_program_arguments (int argc, char **argv)
|
||||||
|
{
|
||||||
|
static const struct opt opts[] =
|
||||||
|
{
|
||||||
|
{ 'd', "debug", NULL, 0, "run in debug mode" },
|
||||||
|
{ 'h', "help", NULL, 0, "display this help and exit" },
|
||||||
|
{ 'V', "version", NULL, 0, "output version information and exit" },
|
||||||
|
{ 0, NULL, NULL, 0, NULL }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct opt_handler oh;
|
||||||
|
opt_handler_init (&oh, argc, argv, opts, "CONFIG", "Fan controller.");
|
||||||
|
|
||||||
|
int c;
|
||||||
|
while ((c = opt_handler_get (&oh)) != -1)
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case 'd':
|
||||||
|
g_debug_mode = true;
|
||||||
|
break;
|
||||||
|
case 'h':
|
||||||
|
opt_handler_usage (&oh, stdout);
|
||||||
|
exit (EXIT_SUCCESS);
|
||||||
|
case 'V':
|
||||||
|
printf (PROGRAM_NAME " " PROGRAM_VERSION "\n");
|
||||||
|
exit (EXIT_SUCCESS);
|
||||||
|
default:
|
||||||
|
print_error ("wrong options");
|
||||||
|
opt_handler_usage (&oh, stderr);
|
||||||
|
exit (EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
argc -= optind;
|
||||||
|
argv += optind;
|
||||||
|
|
||||||
|
if (argc != 1)
|
||||||
|
{
|
||||||
|
opt_handler_usage (&oh, stderr);
|
||||||
|
exit (EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
opt_handler_free (&oh);
|
||||||
|
return argv[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main (int argc, char *argv[])
|
||||||
|
{
|
||||||
|
g_log_message_real = log_message_custom;
|
||||||
|
const char *config_path = parse_program_arguments (argc, argv);
|
||||||
|
|
||||||
|
struct app_context ctx;
|
||||||
|
memset (&ctx, 0, sizeof ctx);
|
||||||
|
poller_init (&ctx.poller);
|
||||||
|
|
||||||
|
setup_signal_handlers ();
|
||||||
|
|
||||||
|
struct poller_fd signal_event;
|
||||||
|
poller_fd_init (&signal_event, &ctx.poller, g_signal_pipe[0]);
|
||||||
|
signal_event.dispatcher = (poller_fd_fn) on_signal_pipe_readable;
|
||||||
|
signal_event.user_data = &ctx;
|
||||||
|
poller_fd_set (&signal_event, POLLIN);
|
||||||
|
|
||||||
|
// TODO: load configuration so that we know what to do with the events
|
||||||
|
|
||||||
|
ctx.proc_fd = socket (PF_NETLINK,
|
||||||
|
SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, NETLINK_CONNECTOR);
|
||||||
|
if (ctx.proc_fd < 0)
|
||||||
|
exit_fatal ("cannot make a proc connector: %s", strerror (errno));
|
||||||
|
|
||||||
|
struct sockaddr_nl addr = { .nl_family = AF_NETLINK, .nl_pid = getpid (),
|
||||||
|
.nl_groups = CN_IDX_PROC };
|
||||||
|
if (bind (ctx.proc_fd, (struct sockaddr *) &addr, sizeof addr) < 0)
|
||||||
|
exit_fatal ("cannot make a proc connector: %s", strerror (errno));
|
||||||
|
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
// Beware of padding between fields, shouldn't be any on x86-64
|
||||||
|
union { struct nlmsghdr netlink; char align[NLMSG_HDRLEN]; };
|
||||||
|
struct cn_msg connector;
|
||||||
|
enum proc_cn_mcast_op op;
|
||||||
|
}
|
||||||
|
subscription =
|
||||||
|
{
|
||||||
|
.netlink.nlmsg_len = sizeof subscription,
|
||||||
|
.netlink.nlmsg_type = NLMSG_DONE,
|
||||||
|
.netlink.nlmsg_pid = getpid (),
|
||||||
|
.connector.id.idx = CN_IDX_PROC,
|
||||||
|
.connector.id.val = CN_VAL_PROC,
|
||||||
|
.connector.len = sizeof subscription.op,
|
||||||
|
.op = PROC_CN_MCAST_LISTEN,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (write (ctx.proc_fd, &subscription, sizeof subscription) < 0)
|
||||||
|
exit_fatal ("failed to subscribe for events: %s", strerror (errno));
|
||||||
|
|
||||||
|
poller_fd_init (&ctx.proc_event, &ctx.poller, ctx.proc_fd);
|
||||||
|
ctx.proc_event.dispatcher = (poller_fd_fn) on_event;
|
||||||
|
ctx.proc_event.user_data = &ctx;
|
||||||
|
poller_fd_set (&ctx.proc_event, POLLIN);
|
||||||
|
|
||||||
|
ctx.polling = true;
|
||||||
|
while (ctx.polling)
|
||||||
|
poller_run (&ctx.poller);
|
||||||
|
|
||||||
|
poller_free (&ctx.poller);
|
||||||
|
xclose (ctx.proc_fd);
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
Loading…
Reference in New Issue