xref: /illumos-gate/usr/src/cmd/rsrvrctl/rsrvrctl.c (revision 763f1f5f97e4c16840af2ced98915f0ed0f46616)
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 static bool
43 parse_size(const char *arg, size_t *resp)
44 {
45 	size_t res;
46 
47 	errno = 0;
48 	res = strtoul(arg, NULL, 0);
49 	if (errno != 0) {
50 		return (false);
51 	}
52 
53 	*resp = (res * 1024 * 1024);
54 	return (true);
55 }
56 
57 static size_t
58 query_size(int fd)
59 {
60 	struct vmm_resv_query data;
61 
62 	int res = ioctl(fd, VMM_RESV_QUERY, &data);
63 	if (res != 0) {
64 		err(EXIT_FAILURE, "Could not query reservoir sizing");
65 	}
66 
67 	return (data.vrq_free_sz + data.vrq_alloc_sz);
68 }
69 
70 static void
71 do_add(int fd, size_t sz, size_t chunk)
72 {
73 	const size_t cur = query_size(fd);
74 	struct vmm_resv_target target = {
75 		.vrt_target_sz = cur + sz,
76 		.vrt_chunk_sz = MIN(chunk, sz),
77 	};
78 
79 	if (ioctl(fd, VMM_RESV_SET_TARGET, &target) != 0) {
80 		err(EXIT_FAILURE, "Could not add %zu bytes to reservoir", sz);
81 	}
82 }
83 
84 static void
85 do_remove(int fd, size_t sz, size_t chunk)
86 {
87 	const size_t cur = query_size(fd);
88 	if (cur == 0) {
89 		/* Reservoir is already empty */
90 		return;
91 	}
92 
93 	const size_t clamped_sz = MIN(sz, cur);
94 	struct vmm_resv_target target = {
95 		.vrt_target_sz = cur - clamped_sz,
96 		.vrt_chunk_sz = MIN(chunk, sz),
97 	};
98 
99 	if (ioctl(fd, VMM_RESV_SET_TARGET, &target) != 0) {
100 		err(EXIT_FAILURE, "Could not remove %zu bytes from reservoir",
101 		    clamped_sz);
102 	}
103 }
104 
105 
106 bool caught_siginfo = false;
107 
108 static void
109 siginfo_handler(int sig, siginfo_t *sip, void *ucp)
110 {
111 	caught_siginfo = true;
112 }
113 
114 static void
115 do_set_target(int fd, size_t sz, size_t chunk)
116 {
117 	struct vmm_resv_target target = {
118 		.vrt_target_sz = sz,
119 		.vrt_chunk_sz = chunk,
120 	};
121 
122 	struct sigaction sa = {
123 		.sa_sigaction = siginfo_handler,
124 		.sa_flags = SA_SIGINFO,
125 	};
126 	if (sigaction(SIGINFO, &sa, NULL) != 0) {
127 		err(EXIT_FAILURE, "Could not configure SIGINFO handler");
128 	}
129 
130 	do {
131 		if (ioctl(fd, VMM_RESV_SET_TARGET, &target) != 0) {
132 			if (errno != EINTR) {
133 				err(EXIT_FAILURE,
134 				    "Could not set reservoir size to %zu bytes",
135 				    sz);
136 			}
137 
138 			if (caught_siginfo) {
139 				caught_siginfo = false;
140 				(void) printf("Reservoir size: %zu MiB\n",
141 				    target.vrt_result_sz / (1024 * 1024));
142 			}
143 		}
144 	} while (target.vrt_result_sz != sz);
145 }
146 
147 static void
148 do_query(int fd)
149 {
150 	struct vmm_resv_query data;
151 	int res;
152 
153 	res = ioctl(fd, VMM_RESV_QUERY, &data);
154 	if (res != 0) {
155 		perror("Could not query reservoir info");
156 		return;
157 	}
158 
159 	printf("Free KiB:\t%zu\n"
160 	    "Allocated KiB:\t%zu\n"
161 	    "Transient Allocated KiB:\t%zu\n"
162 	    "Size limit KiB:\t%zu\n",
163 	    data.vrq_free_sz / 1024,
164 	    data.vrq_alloc_sz / 1024,
165 	    data.vrq_alloc_transient_sz / 1024,
166 	    data.vrq_limit / 1024);
167 }
168 
169 int
170 main(int argc, char *argv[])
171 {
172 	char c;
173 	const char *opt_a = NULL, *opt_r = NULL, *opt_s = NULL;
174 	bool opt_q = false;
175 	int fd;
176 
177 	prog_name = argv[0];
178 
179 	uint_t resize_opts = 0;
180 	size_t chunk_sz = 0;
181 	while ((c = getopt(argc, argv, "a:r:s:c:qh")) != -1) {
182 		switch (c) {
183 		case 'a':
184 			if (opt_a == NULL) {
185 				resize_opts++;
186 				opt_a = optarg;
187 			}
188 			break;
189 		case 'r':
190 			if (opt_r == NULL) {
191 				resize_opts++;
192 				opt_r = optarg;
193 			}
194 			break;
195 		case 's':
196 			if (opt_s == NULL) {
197 				resize_opts++;
198 				opt_s = optarg;
199 			}
200 			break;
201 		case 'c':
202 			if (!parse_size(optarg, &chunk_sz)) {
203 				warn("Invalid chunk size %s", optarg);
204 				usage(EXIT_FAILURE);
205 			}
206 			break;
207 		case 'q':
208 			opt_q = true;
209 			break;
210 		case 'h':
211 			usage(EXIT_SUCCESS);
212 			break;
213 		default:
214 			usage(EXIT_FAILURE);
215 			break;
216 		}
217 	}
218 
219 	if (optind < argc ||
220 	    (resize_opts == 0 && !opt_q) || (resize_opts > 1)) {
221 		usage(EXIT_FAILURE);
222 	}
223 
224 	fd = open(VMM_CTL_DEV, O_EXCL | O_RDWR);
225 	if (fd < 0) {
226 		perror("Could not open vmmctl");
227 		usage(EXIT_FAILURE);
228 	}
229 
230 	if (opt_a != NULL) {
231 		size_t sz;
232 
233 		if (!parse_size(opt_a, &sz)) {
234 			warn("Invalid size %s", opt_a);
235 			usage(EXIT_FAILURE);
236 		}
237 
238 		do_add(fd, sz, chunk_sz);
239 	} else if (opt_r != NULL) {
240 		size_t sz;
241 
242 		if (!parse_size(opt_r, &sz)) {
243 			warn("Invalid size %s", opt_r);
244 			usage(EXIT_FAILURE);
245 		}
246 		do_remove(fd, sz, chunk_sz);
247 	} else if (opt_s != NULL) {
248 		size_t sz;
249 
250 		if (!parse_size(opt_s, &sz)) {
251 			warn("Invalid size %s", opt_s);
252 			usage(EXIT_FAILURE);
253 		}
254 		do_set_target(fd, sz, chunk_sz);
255 	} else if (opt_q) {
256 		do_query(fd);
257 	}
258 
259 	(void) close(fd);
260 	return (0);
261 }
262