xref: /openbsd/usr.bin/sndiod/file.c (revision 7b639200)
1 /*	$OpenBSD: file.c,v 1.28 2024/12/20 07:35:56 ratchov Exp $	*/
2 /*
3  * Copyright (c) 2008-2012 Alexandre Ratchov <alex@caoua.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 /*
18  * non-blocking file i/o module: each file can be read or written (or
19  * both). To achieve non-blocking io, we simply use the poll() syscall
20  * in an event loop and dispatch events to sub-modules.
21  *
22  * the module also provides trivial timeout implementation,
23  * derived from:
24  *
25  * 	anoncvs@moule.caoua.org:/midish
26  *
27  *		midish/timo.c rev 1.18
28  * 		midish/mdep.c rev 1.71
29  *
30  * A timeout is used to schedule the call of a routine (the callback)
31  * there is a global list of timeouts that is processed inside the
32  * event loop. Timeouts work as follows:
33  *
34  *	first the timo structure must be initialized with timo_set()
35  *
36  *	then the timeout is scheduled (only once) with timo_add()
37  *
38  *	if the timeout expires, the call-back is called; then it can
39  *	be scheduled again if needed. It's OK to reschedule it again
40  *	from the callback
41  *
42  *	the timeout can be aborted with timo_del(), it is OK to try to
43  *	abort a timeout that has expired
44  *
45  */
46 
47 #include <sys/types.h>
48 
49 #include <errno.h>
50 #include <fcntl.h>
51 #include <poll.h>
52 #include <signal.h>
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <time.h>
56 
57 #include "file.h"
58 #include "utils.h"
59 
60 #define MAXFDS 100
61 #define TIMER_MSEC 5
62 
63 void timo_update(unsigned int);
64 void timo_init(void);
65 void timo_done(void);
66 int file_process(struct file *, struct pollfd *);
67 
68 struct timespec file_ts;
69 struct file *file_list;
70 struct timo *timo_queue;
71 unsigned int timo_abstime;
72 int file_slowaccept = 0, file_nfds;
73 #ifdef DEBUG
74 long long file_wtime, file_utime;
75 #endif
76 
77 /*
78  * initialise a timeout structure, arguments are callback and argument
79  * that will be passed to the callback
80  */
81 void
timo_set(struct timo * o,void (* cb)(void *),void * arg)82 timo_set(struct timo *o, void (*cb)(void *), void *arg)
83 {
84 	o->cb = cb;
85 	o->arg = arg;
86 	o->set = 0;
87 }
88 
89 /*
90  * schedule the callback in 'delta' 24-th of microseconds. The timeout
91  * must not be already scheduled
92  */
93 void
timo_add(struct timo * o,unsigned int delta)94 timo_add(struct timo *o, unsigned int delta)
95 {
96 	struct timo **i;
97 	unsigned int val;
98 	int diff;
99 
100 #ifdef DEBUG
101 	if (o->set) {
102 		logx(0, "timo_add: already set");
103 		panic();
104 	}
105 	if (delta == 0) {
106 		logx(0, "timo_add: zero timeout is evil");
107 		panic();
108 	}
109 #endif
110 	val = timo_abstime + delta;
111 	for (i = &timo_queue; *i != NULL; i = &(*i)->next) {
112 		diff = (*i)->val - val;
113 		if (diff > 0) {
114 			break;
115 		}
116 	}
117 	o->set = 1;
118 	o->val = val;
119 	o->next = *i;
120 	*i = o;
121 }
122 
123 /*
124  * abort a scheduled timeout
125  */
126 void
timo_del(struct timo * o)127 timo_del(struct timo *o)
128 {
129 	struct timo **i;
130 
131 	for (i = &timo_queue; *i != NULL; i = &(*i)->next) {
132 		if (*i == o) {
133 			*i = o->next;
134 			o->set = 0;
135 			return;
136 		}
137 	}
138 #ifdef DEBUG
139 	logx(4, "timo_del: not found");
140 #endif
141 }
142 
143 /*
144  * routine to be called by the timer when 'delta' 24-th of microsecond
145  * elapsed. This routine updates time reference used by timeouts and
146  * calls expired timeouts
147  */
148 void
timo_update(unsigned int delta)149 timo_update(unsigned int delta)
150 {
151 	struct timo *to;
152 	int diff;
153 
154 	/*
155 	 * update time reference
156 	 */
157 	timo_abstime += delta;
158 
159 	/*
160 	 * remove from the queue and run expired timeouts
161 	 */
162 	while (timo_queue != NULL) {
163 		/*
164 		 * there is no overflow here because + and - are
165 		 * modulo 2^32, they are the same for both signed and
166 		 * unsigned integers
167 		 */
168 		diff = timo_queue->val - timo_abstime;
169 		if (diff > 0)
170 			break;
171 		to = timo_queue;
172 		timo_queue = to->next;
173 		to->set = 0;
174 		to->cb(to->arg);
175 	}
176 }
177 
178 /*
179  * initialize timeout queue
180  */
181 void
timo_init(void)182 timo_init(void)
183 {
184 	timo_queue = NULL;
185 	timo_abstime = 0;
186 }
187 
188 /*
189  * destroy timeout queue
190  */
191 void
timo_done(void)192 timo_done(void)
193 {
194 #ifdef DEBUG
195 	if (timo_queue != NULL) {
196 		logx(0, "timo_done: timo_queue not empty!");
197 		panic();
198 	}
199 #endif
200 	timo_queue = (struct timo *)0xdeadbeef;
201 }
202 
203 struct file *
file_new(struct fileops * ops,void * arg,char * name,unsigned int nfds)204 file_new(struct fileops *ops, void *arg, char *name, unsigned int nfds)
205 {
206 	struct file *f;
207 
208 	if (file_nfds + nfds > MAXFDS) {
209 #ifdef DEBUG
210 		logx(1, "%s: too many polled files", name);
211 #endif
212 		return NULL;
213 	}
214 	f = xmalloc(sizeof(struct file));
215 	f->max_nfds = nfds;
216 	f->nfds = 0;
217 	f->ops = ops;
218 	f->arg = arg;
219 	f->name = name;
220 	f->state = FILE_INIT;
221 	f->next = file_list;
222 	file_list = f;
223 #ifdef DEBUG
224 	logx(3, "%s: created", f->name);
225 #endif
226 	file_nfds += f->max_nfds;
227 	return f;
228 }
229 
230 void
file_del(struct file * f)231 file_del(struct file *f)
232 {
233 #ifdef DEBUG
234 	if (f->state == FILE_ZOMB) {
235 		logx(0, "%s: %s: bad state in file_del", __func__, f->name);
236 		panic();
237 	}
238 #endif
239 	file_nfds -= f->max_nfds;
240 	f->state = FILE_ZOMB;
241 #ifdef DEBUG
242 	logx(3, "%s: destroyed", f->name);
243 #endif
244 }
245 
246 int
file_process(struct file * f,struct pollfd * pfd)247 file_process(struct file *f, struct pollfd *pfd)
248 {
249 	int rc, revents;
250 #ifdef DEBUG
251 	struct timespec ts0, ts1;
252 	long us;
253 #endif
254 
255 #ifdef DEBUG
256 	if (log_level >= 3)
257 		clock_gettime(CLOCK_UPTIME, &ts0);
258 #endif
259 	rc = 0;
260 	revents = (f->state != FILE_ZOMB) ?
261 	    f->ops->revents(f->arg, pfd) : 0;
262 	if ((revents & POLLHUP) && (f->state != FILE_ZOMB)) {
263 		f->ops->hup(f->arg);
264 		rc = 1;
265 	}
266 	if ((revents & POLLIN) && (f->state != FILE_ZOMB)) {
267 		f->ops->in(f->arg);
268 		rc = 1;
269 	}
270 	if ((revents & POLLOUT) && (f->state != FILE_ZOMB)) {
271 		f->ops->out(f->arg);
272 		rc = 1;
273 	}
274 #ifdef DEBUG
275 	if (log_level >= 3) {
276 		clock_gettime(CLOCK_UPTIME, &ts1);
277 		us = 1000000L * (ts1.tv_sec - ts0.tv_sec);
278 		us += (ts1.tv_nsec - ts0.tv_nsec) / 1000;
279 		if (us >= 5000)
280 			logx(4, "%s: processed in %luus", f->name, us);
281 	}
282 #endif
283 	return rc;
284 }
285 
286 #ifdef DEBUG
287 size_t
filelist_fmt(char * buf,size_t size,struct pollfd * pfd,int ret)288 filelist_fmt(char *buf, size_t size, struct pollfd *pfd, int ret)
289 {
290 	struct file *f;
291 	char *p = buf, *end = buf + size;
292 	const char *sep = "";
293 	int i;
294 
295 	for (f = file_list; f != NULL; f = f->next) {
296 		p += snprintf(p, p < end ? end - p : 0, "%s%s:", sep, f->name);
297 		for (i = 0; i < f->nfds; i++) {
298 			p += snprintf(p, p < end ? end - p : 0, " 0x%x",
299 			    ret ? pfd->revents : pfd->events);
300 			pfd++;
301 		}
302 		sep = ", ";
303 	}
304 	return p - buf;
305 }
306 #endif
307 
308 int
file_poll(void)309 file_poll(void)
310 {
311 	struct pollfd pfds[MAXFDS], *pfd;
312 	struct file *f, **pf;
313 	struct timespec ts;
314 #ifdef DEBUG
315 	struct timespec sleepts;
316 	char str[128];
317 #endif
318 	long long delta_nsec;
319 	int nfds, res, timo;
320 
321 	/*
322 	 * cleanup zombies
323 	 */
324 	pf = &file_list;
325 	while ((f = *pf) != NULL) {
326 		if (f->state == FILE_ZOMB) {
327 			*pf = f->next;
328 			xfree(f);
329 		} else
330 			pf = &f->next;
331 	}
332 
333 	if (file_list == NULL && timo_queue == NULL) {
334 #ifdef DEBUG
335 		logx(3, "nothing to do...");
336 #endif
337 		return 0;
338 	}
339 
340 	/*
341 	 * fill pollfd structures
342 	 */
343 	nfds = 0;
344 	for (f = file_list; f != NULL; f = f->next) {
345 		f->nfds = f->ops->pollfd(f->arg, pfds + nfds);
346 		if (f->nfds == 0)
347 			continue;
348 		nfds += f->nfds;
349 	}
350 #ifdef DEBUG
351 	logx(4, "poll [%s]", (filelist_fmt(str, sizeof(str), pfds, 0), str));
352 #endif
353 
354 	/*
355 	 * process files that do not rely on poll
356 	 */
357 	res = 0;
358 	for (f = file_list; f != NULL; f = f->next) {
359 		if (f->nfds > 0)
360 			continue;
361 		res |= file_process(f, NULL);
362 	}
363 	/*
364 	 * The processing may have changed the poll(2) conditions of
365 	 * other files, so restart the loop to force their poll(2) event
366 	 * masks to be reevaluated.
367 	 */
368 	if (res)
369 		return 1;
370 
371 	/*
372 	 * Sleep. Calculate the number of milliseconds poll(2) must
373 	 * wait before the timo_update() needs to be called. If there are
374 	 * no timeouts scheduled, then call poll(2) with infinite
375 	 * timeout (i.e -1).
376 	 */
377 #ifdef DEBUG
378 	clock_gettime(CLOCK_UPTIME, &sleepts);
379 	file_utime += 1000000000LL * (sleepts.tv_sec - file_ts.tv_sec);
380 	file_utime += sleepts.tv_nsec - file_ts.tv_nsec;
381 #endif
382 	if (timo_queue != NULL) {
383 		timo = ((int)timo_queue->val - (int)timo_abstime) / 1000;
384 		if (timo < TIMER_MSEC)
385 			timo = TIMER_MSEC;
386 	} else
387 		timo = -1;
388 	log_flush();
389 	res = poll(pfds, nfds, timo);
390 	if (res == -1) {
391 		if (errno != EINTR) {
392 			logx(0, "poll failed");
393 			panic();
394 		}
395 		return 1;
396 	}
397 
398 	/*
399 	 * run timeouts
400 	 */
401 	clock_gettime(CLOCK_UPTIME, &ts);
402 #ifdef DEBUG
403 	file_wtime += 1000000000LL * (ts.tv_sec - sleepts.tv_sec);
404 	file_wtime += ts.tv_nsec - sleepts.tv_nsec;
405 #endif
406 	if (timo_queue) {
407 		delta_nsec = 1000000000LL * (ts.tv_sec - file_ts.tv_sec);
408 		delta_nsec += ts.tv_nsec - file_ts.tv_nsec;
409 		if (delta_nsec >= 0 && delta_nsec < 60000000000LL)
410 			timo_update(delta_nsec / 1000);
411 		else
412 			logx(2, "out-of-bounds clock delta");
413 	}
414 	file_ts = ts;
415 
416 	/*
417 	 * process files that rely on poll
418 	 */
419 	pfd = pfds;
420 	for (f = file_list; f != NULL; f = f->next) {
421 		if (f->nfds == 0)
422 			continue;
423 		file_process(f, pfd);
424 		pfd += f->nfds;
425 	}
426 	return 1;
427 }
428 
429 void
filelist_init(void)430 filelist_init(void)
431 {
432 	sigset_t set;
433 
434 	if (clock_gettime(CLOCK_UPTIME, &file_ts) == -1) {
435 		logx(0, "filelist_init: CLOCK_UPTIME unsupported");
436 		panic();
437 	}
438 	sigemptyset(&set);
439 	sigaddset(&set, SIGPIPE);
440 	sigprocmask(SIG_BLOCK, &set, NULL);
441 	file_list = NULL;
442 	log_sync = 0;
443 	timo_init();
444 }
445 
446 void
filelist_done(void)447 filelist_done(void)
448 {
449 #ifdef DEBUG
450 	struct file *f;
451 
452 	if (file_list != NULL) {
453 		for (f = file_list; f != NULL; f = f->next)
454 			logx(0, "%s: not closed", f->name);
455 		panic();
456 	}
457 	log_sync = 1;
458 	log_flush();
459 #endif
460 	timo_done();
461 }
462