1/*	$OpenBSD: dev-limit.c,v 1.3 2023/07/12 18:21:39 anton Exp $ */
2
3/*
4 * Copyright (c) 2023 Alexandr Nedvedicky <sashan@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <stdio.h>
20#include <unistd.h>
21#include <fcntl.h>
22#include <stdint.h>
23#include <stdlib.h>
24#include <signal.h>
25#include <err.h>
26#include <sys/wait.h>
27
28static int sigchild;
29
30static void
31usage(const char *progname)
32{
33	fprintf(stderr,
34	    "%s [-d] [-s success_count] [-c child_count] [-t timeout]\n"
35	    "if no options are specified program opens '/dev/pf'\n"
36	    "and waits for 5s  before it exits\n"
37	    "\t-s how many children should successfully open /dev/pf\n"
38	    "\t-c children to fork, each child opens /dev/pf\n"
39	    "\t-t timeout in seconds each child should wait\n"
40	    "after successfully opening /dev/pf. Child exits immediately\n"
41	    "if /dev/pf can not be opened\n", progname);
42	exit(1);
43}
44
45static void
46handle_sigchild(int signum)
47{
48	if (signum == SIGCHLD)
49		sigchild = 1;
50}
51
52static void
53open_pf_and_exit(unsigned int sleep_time)
54{
55	if (open("/dev/pf", O_RDONLY) == -1)
56		exit(1);
57
58	sleep(sleep_time);
59	exit(0);
60}
61
62int
63main(int argc, char *const argv[])
64{
65	pid_t *pids;
66	unsigned int chld_count = 0;
67	unsigned int sleep_time = 5;
68	unsigned int expect_success = 0;
69	unsigned int success, errors, i;
70	const char *errstr, *sleep_arg;
71	int status;
72	int c;
73
74	while ((c = getopt(argc, argv, "t:c:s:")) != -1) {
75		switch (c) {
76		case 't':
77			sleep_arg = (char *const)optarg;
78			sleep_time = strtonum(optarg, 1, 60, &errstr);
79			if (errstr != NULL) {
80				fprintf(stderr,
81				    "%s invalid sleep time %s: %s, must be in "
82				    "range <1, 60>\n", argv[0], errstr, optarg);
83				usage(argv[0]);
84			}
85			break;
86		case 'c':
87			chld_count = strtonum(optarg, 1, 32768, &errstr);
88			if (errstr != NULL) {
89				fprintf(stderr,
90				    "%s invalid children count %s: %s, must be "
91				    "in range <1, 32768>\n", argv[0], optarg,
92				    errstr);
93				usage(argv[0]);
94			}
95			break;
96		case 's':
97			expect_success = strtonum(optarg, 0, 32768, &errstr);
98			if (errstr != NULL) {
99				fprintf(stderr,
100				    "%s invalid expect success count %s: %s "
101				    "must be in range <1, 32768>\n", argv[0],
102				    optarg, errstr);
103				usage(argv[0]);
104			}
105			break;
106		default:
107			usage(argv[0]);
108		}
109	}
110
111	if (chld_count == 0)
112		open_pf_and_exit(sleep_time);
113
114	signal(SIGCHLD, handle_sigchild);
115	pids = (pid_t *)malloc(sizeof(pid_t) * chld_count);
116	if (pids == 0)
117		err(1, NULL);
118
119	i = 0;
120	while ((sigchild == 0) && (i < chld_count)) {
121		pid_t pid;
122
123		pid = fork();
124		pids[i++] = pid;
125		if (pid == -1)
126			warn("fork");
127		else if (pid == 0)
128			execl(argv[0], argv[0], "-t", sleep_arg, NULL);
129	}
130	chld_count = i;
131
132	success = 0;
133	errors = 0;
134	for (i = 0; i < chld_count; i++) {
135		waitpid(pids[i], &status, 0);
136		if (status == 0)
137			success++;
138		else
139			errors++;
140	}
141
142	free(pids);
143
144	if (success != expect_success) {
145		printf("Successful opens: %u\n", success);
146		printf("Failures: %u\n", errors);
147		printf("Expected opens: %u\n", expect_success);
148		printf("%u vs %u = %u + %u\n",
149		    chld_count, errors + success, errors, success);
150		return (1);
151	}
152
153	return (0);
154}
155