xref: /illumos-gate/usr/src/cmd/rsrvrctl/rsrvrctl.c (revision e3ae4b35c024af1196582063ecee3ab79367227d)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  *
11  * Copyright 2023 Oxide Computer Company
12  */
13 
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <errno.h>
17 #include <fcntl.h>
18 #include <unistd.h>
19 #include <err.h>
20 #include <assert.h>
21 #include <signal.h>
22 #include <sys/types.h>
23 
24 #include <sys/vmm_dev.h>
25 
26 const char *prog_name;
27 
28 static void
29 usage(int exitcode)
30 {
31 	assert(prog_name != NULL);
32 	fprintf(stderr,
33 	    "Usage: %s [-a add] [-r remove] [-q]\n"
34 	    "\t-a <SZ> add SZ MiB to the reservoir\n"
35 	    "\t-r <SZ> remove SZ MiB from the reservoir\n"
36 	    "\t-s <SZ> set reservoir to SZ MiB, if possible\n"
37 	    "\t-c <SZ> use SZ MiB chunks when performing resize ops\n"
38 	    "\t-q query reservoir state\n", prog_name);
39 	exit(exitcode);
40 }
41 
42 /*
43  * Parse an input size of MiB to bytes.
44  */
45 static bool
46 parse_size(const char *arg, size_t *resp)
47 {
48 	size_t res;
49 
50 	errno = 0;
51 	res = strtoul(arg, NULL, 0);
52 	if (errno != 0) {
53 		return (false);
54 	}
55 
56 	*resp = (res * 1024 * 1024);
57 	return (true);
58 }
59 
60 static size_t
61 query_size(int fd)
62 {
63 	struct vmm_resv_query data;
64 
65 	int res = ioctl(fd, VMM_RESV_QUERY, &data);
66 	if (res != 0) {
67 		err(EXIT_FAILURE, "Could not query reservoir sizing");
68 	}
69 
70 	return (data.vrq_free_sz + data.vrq_alloc_sz);
71 }
72 
73 static void
74 do_add(int fd, size_t sz, size_t chunk)
75 {
76 	const size_t cur = query_size(fd);
77 	struct vmm_resv_target target = {
78 		.vrt_target_sz = cur + sz,
79 		.vrt_chunk_sz = MIN(chunk, sz),
80 	};
81 
82 	if (ioctl(fd, VMM_RESV_SET_TARGET, &target) != 0) {
83 		err(EXIT_FAILURE, "Could not add %zu bytes to reservoir", sz);
84 	}
85 }
86 
87 static void
88 do_remove(int fd, size_t sz, size_t chunk)
89 {
90 	const size_t cur = query_size(fd);
91 	if (cur == 0) {
92 		/* Reservoir is already empty */
93 		return;
94 	}
95 
96 	const size_t clamped_sz = MIN(sz, cur);
97 	struct vmm_resv_target target = {
98 		.vrt_target_sz = cur - clamped_sz,
99 		.vrt_chunk_sz = MIN(chunk, sz),
100 	};
101 
102 	if (ioctl(fd, VMM_RESV_SET_TARGET, &target) != 0) {
103 		err(EXIT_FAILURE, "Could not remove %zu bytes from reservoir",
104 		    clamped_sz);
105 	}
106 }
107 
108 
109 bool caught_siginfo = false;
110 
111 static void
112 siginfo_handler(int sig, siginfo_t *sip, void *ucp)
113 {
114 	caught_siginfo = true;
115 }
116 
117 static void
118 do_set_target(int fd, size_t sz, size_t chunk)
119 {
120 	struct vmm_resv_target target = {
121 		.vrt_target_sz = sz,
122 		.vrt_chunk_sz = chunk,
123 	};
124 
125 	struct sigaction sa = {
126 		.sa_sigaction = siginfo_handler,
127 		.sa_flags = SA_SIGINFO,
128 	};
129 	if (sigaction(SIGINFO, &sa, NULL) != 0) {
130 		err(EXIT_FAILURE, "Could not configure SIGINFO handler");
131 	}
132 
133 	do {
134 		if (ioctl(fd, VMM_RESV_SET_TARGET, &target) != 0) {
135 			if (errno != EINTR) {
136 				err(EXIT_FAILURE,
137 				    "Could not set reservoir size to %zu bytes",
138 				    sz);
139 			}
140 
141 			if (caught_siginfo) {
142 				caught_siginfo = false;
143 				(void) printf("Reservoir size: %zu MiB\n",
144 				    target.vrt_result_sz / (1024 * 1024));
145 			}
146 		}
147 	} while (target.vrt_result_sz != sz);
148 }
149 
150 static void
151 do_query(int fd)
152 {
153 	struct vmm_resv_query data;
154 	int res;
155 
156 	res = ioctl(fd, VMM_RESV_QUERY, &data);
157 	if (res != 0) {
158 		perror("Could not query reservoir info");
159 		return;
160 	}
161 
162 	printf("Free MiB:\t%zu\n"
163 	    "Allocated MiB:\t%zu\n"
164 	    "Transient Allocated MiB:\t%zu\n"
165 	    "Size limit MiB:\t%zu\n",
166 	    data.vrq_free_sz / (1024 * 1024),
167 	    data.vrq_alloc_sz / (1024 * 1024),
168 	    data.vrq_alloc_transient_sz / (1024 * 1024),
169 	    data.vrq_limit / (1024 * 1024));
170 }
171 
172 int
173 main(int argc, char *argv[])
174 {
175 	char c;
176 	const char *opt_a = NULL, *opt_r = NULL, *opt_s = NULL;
177 	bool opt_q = false;
178 	int fd;
179 
180 	prog_name = argv[0];
181 
182 	uint_t resize_opts = 0;
183 	size_t chunk_sz = 0;
184 	while ((c = getopt(argc, argv, "a:r:s:c:qh")) != -1) {
185 		switch (c) {
186 		case 'a':
187 			if (opt_a == NULL) {
188 				resize_opts++;
189 				opt_a = optarg;
190 			}
191 			break;
192 		case 'r':
193 			if (opt_r == NULL) {
194 				resize_opts++;
195 				opt_r = optarg;
196 			}
197 			break;
198 		case 's':
199 			if (opt_s == NULL) {
200 				resize_opts++;
201 				opt_s = optarg;
202 			}
203 			break;
204 		case 'c':
205 			if (!parse_size(optarg, &chunk_sz)) {
206 				warn("Invalid chunk size %s", optarg);
207 				usage(EXIT_FAILURE);
208 			}
209 			break;
210 		case 'q':
211 			opt_q = true;
212 			break;
213 		case 'h':
214 			usage(EXIT_SUCCESS);
215 			break;
216 		default:
217 			usage(EXIT_FAILURE);
218 			break;
219 		}
220 	}
221 
222 	if (optind < argc ||
223 	    (resize_opts == 0 && !opt_q) || (resize_opts > 1)) {
224 		usage(EXIT_FAILURE);
225 	}
226 
227 	fd = open(VMM_CTL_DEV, O_EXCL | O_RDWR);
228 	if (fd < 0) {
229 		perror("Could not open vmmctl");
230 		usage(EXIT_FAILURE);
231 	}
232 
233 	if (opt_a != NULL) {
234 		size_t sz;
235 
236 		if (!parse_size(opt_a, &sz)) {
237 			warn("Invalid size %s", opt_a);
238 			usage(EXIT_FAILURE);
239 		}
240 
241 		do_add(fd, sz, chunk_sz);
242 	} else if (opt_r != NULL) {
243 		size_t sz;
244 
245 		if (!parse_size(opt_r, &sz)) {
246 			warn("Invalid size %s", opt_r);
247 			usage(EXIT_FAILURE);
248 		}
249 		do_remove(fd, sz, chunk_sz);
250 	} else if (opt_s != NULL) {
251 		size_t sz;
252 
253 		if (!parse_size(opt_s, &sz)) {
254 			warn("Invalid size %s", opt_s);
255 			usage(EXIT_FAILURE);
256 		}
257 		do_set_target(fd, sz, chunk_sz);
258 	} else if (opt_q) {
259 		do_query(fd);
260 	}
261 
262 	(void) close(fd);
263 	return (0);
264 }
265