aboutsummaryrefslogtreecommitdiff
path: root/priod.c
blob: 075f46544d49d355760203c8fa4ee2feba1914ac (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
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;
}