xref: /minix3/minix/servers/mib/main.c (revision e4e21ee1b2710f3d411514741ce10d80156f4b5d)
1 /* MIB service - main.c - request abstraction and first-level tree */
2 /*
3  * This is the Management Information Base (MIB) service.  Its one and only
4  * task is to implement the sysctl(2) system call, which plays a fairly
5  * important role in parts of *BSD userland.
6  *
7  * The sysctl(2) interface is used to access a variety of information.  In
8  * order to obtain that information, and possibly modify it, the MIB service
9  * calls into many other services.  The MIB service must therefore not be
10  * called directly from other services, with the exception of ProcFS.  In fact,
11  * ProcFS is currently the only service that is modeled as logically higher in
12  * the MINIX3 service stack than MIB, something that itself is possible only
13  * due to the nonblocking nature of VFS.  MIB may issue blocking calls to VFS.
14  *
15  * The MIB service is in the boot image because even init(8) makes use of
16  * sysctl(2) during its own startup, so launching the MIB service at any later
17  * time would make a proper implementation of sysctl(2) impossible.  Also, the
18  * service needs superuser privileges because it may need to issue privileged
19  * calls and obtain privileged information from other services.
20  *
21  * The MIB service was created by David van Moolenbroek <david@minix3.org>.
22  */
23 
24 #include "mib.h"
25 
26 /*
27  * Most of these initially empty nodes are filled in by their corresponding
28  * modules' _init calls; see mib_init below.  However, CTL_USER stays empty:
29  * the libc sysctl(3) wrapper code takes care of that subtree.  It must have
30  * an entry here though, or sysctl(8) will not list it.  CTL_VENDOR is also
31  * empty, but writable, so that it may be used by third parties.
32  */
33 static struct mib_node mib_table[] = {
34 /* 1*/	[CTL_KERN]	= MIB_ENODE(_P | _RO, "kern", "High kernel"),
35 /* 8*/	[CTL_USER]	= MIB_ENODE(_P | _RO, "user", "User-level"),
36 /*11*/	[CTL_VENDOR]	= MIB_ENODE(_P | _RW, "vendor", "Vendor specific"),
37 /*32*/	[CTL_MINIX]	= MIB_ENODE(_P | _RO, "minix", "MINIX3 specific"),
38 };
39 
40 /*
41  * The root node of the tree.  The root node is used internally only--it is
42  * impossible to access the root node itself from userland in any way.  The
43  * node is writable by default, so that programs such as init(8) may create
44  * their own top-level entries.
45  */
46 static struct mib_node mib_root = MIB_NODE(_RW, mib_table, "", "");
47 
48 /*
49  * Structures describing old and new data as provided by userland.  The primary
50  * advantage of these opaque structures is that we could in principle use them
51  * to implement storage of small data results in the sysctl reply message, so
52  * as to avoid the kernel copy, without changing any of the handler code.
53  */
54 struct mib_oldp {
55 	endpoint_t oldp_endpt;
56 	vir_bytes oldp_addr;
57 	size_t oldp_len;
58 };
59 /*
60  * Same structure, different type: prevent accidental mixups, and avoid the
61  * need to use __restrict everywhere.
62  */
63 struct mib_newp {
64 	endpoint_t newp_endpt;
65 	vir_bytes newp_addr;
66 	size_t newp_len;
67 };
68 
69 /*
70  * Return TRUE or FALSE indicating whether the given offset is within the range
71  * of data that is to be copied out.  This call can be used to test whether
72  * certain bits of data need to be prepared for copying at all.
73  */
74 int
75 mib_inrange(struct mib_oldp * oldp, size_t off)
76 {
77 
78 	if (oldp == NULL)
79 		return FALSE;
80 
81 	return (off < oldp->oldp_len);
82 }
83 
84 /*
85  * Return the total length of the requested data.  This should not be used
86  * directly except in highly unusual cases, such as particular node requests
87  * where the request semantics blatantly violate overall sysctl(2) semantics.
88  */
89 size_t
90 mib_getoldlen(struct mib_oldp * oldp)
91 {
92 
93 	if (oldp == NULL)
94 		return 0;
95 
96 	return oldp->oldp_len;
97 }
98 
99 /*
100  * Copy out (partial) data to the user.  The copy is automatically limited to
101  * the range of data requested by the user.  Return the requested length on
102  * success (for the caller's convenience) or an error code on failure.
103  */
104 ssize_t
105 mib_copyout(struct mib_oldp * __restrict oldp, size_t off,
106 	const void * __restrict buf, size_t size)
107 {
108 	size_t len;
109 	int r;
110 
111 	len = size;
112 	assert(len <= SSIZE_MAX);
113 
114 	if (oldp == NULL || off >= oldp->oldp_len)
115 		return size; /* nothing to do */
116 
117 	if (len > oldp->oldp_len - off)
118 		len = oldp->oldp_len - off;
119 
120 	if ((r = sys_datacopy(SELF, (vir_bytes)buf, oldp->oldp_endpt,
121 	    oldp->oldp_addr + off, len)) != OK)
122 		return r;
123 
124 	return size;
125 }
126 
127 /*
128  * Override the oldlen value returned from the call, in situations where an
129  * error is thrown as well.
130  */
131 void
132 mib_setoldlen(struct mib_call * call, size_t oldlen)
133 {
134 
135 	call->call_reslen = oldlen;
136 }
137 
138 /*
139  * Return the new data length as provided by the user, or 0 if the user did not
140  * supply new data.
141  */
142 size_t
143 mib_getnewlen(struct mib_newp * newp)
144 {
145 
146 	if (newp == NULL)
147 		return 0;
148 
149 	return newp->newp_len;
150 }
151 
152 /*
153  * Copy in data from the user.  The given length must match exactly the length
154  * given by the user.  Return OK or an error code.
155  */
156 int
157 mib_copyin(struct mib_newp * __restrict newp, void * __restrict buf,
158 	size_t len)
159 {
160 
161 	if (newp == NULL || len != newp->newp_len)
162 		return EINVAL;
163 
164 	if (len == 0)
165 		return OK;
166 
167 	return sys_datacopy(newp->newp_endpt, newp->newp_addr, SELF,
168 	    (vir_bytes)buf, len);
169 }
170 
171 /*
172  * Copy in auxiliary data from the user, based on a user pointer obtained from
173  * data copied in earlier through mib_copyin().
174  */
175 int
176 mib_copyin_aux(struct mib_newp * __restrict newp, vir_bytes addr,
177 	void * __restrict buf, size_t len)
178 {
179 
180 	assert(newp != NULL);
181 
182 	if (len == 0)
183 		return OK;
184 
185 	return sys_datacopy(newp->newp_endpt, addr, SELF, (vir_bytes)buf, len);
186 }
187 
188 /*
189  * Check whether the user is allowed to perform privileged operations.  The
190  * function returns a nonzero value if this is the case, and zero otherwise.
191  * Authorization is performed only once per call.
192  */
193 int
194 mib_authed(struct mib_call * call)
195 {
196 
197 	if ((call->call_flags & (MIB_FLAG_AUTH | MIB_FLAG_NOAUTH)) == 0) {
198 		/* Ask PM if this endpoint has superuser privileges. */
199 		if (getnuid(call->call_endpt) == SUPER_USER)
200 			call->call_flags |= MIB_FLAG_AUTH;
201 		else
202 			call->call_flags |= MIB_FLAG_NOAUTH;
203 	}
204 
205 	return (call->call_flags & MIB_FLAG_AUTH);
206 }
207 
208 /*
209  * Implement the sysctl(2) system call.
210  */
211 static int
212 mib_sysctl(message * __restrict m_in, message * __restrict m_out)
213 {
214 	vir_bytes oldaddr, newaddr;
215 	size_t oldlen, newlen;
216 	unsigned int namelen;
217 	int s, name[CTL_MAXNAME];
218 	endpoint_t endpt;
219 	struct mib_oldp oldp, *oldpp;
220 	struct mib_newp newp, *newpp;
221 	struct mib_call call;
222 	ssize_t r;
223 
224 	endpt = m_in->m_source;
225 	oldaddr = m_in->m_lc_mib_sysctl.oldp;
226 	oldlen = m_in->m_lc_mib_sysctl.oldlen;
227 	newaddr = m_in->m_lc_mib_sysctl.newp;
228 	newlen = m_in->m_lc_mib_sysctl.newlen;
229 	namelen = m_in->m_lc_mib_sysctl.namelen;
230 
231 	if (namelen == 0 || namelen > CTL_MAXNAME)
232 		return EINVAL;
233 
234 	/*
235 	 * In most cases, the entire name fits in the request message, so we
236 	 * can avoid a kernel copy.
237 	 */
238 	if (namelen > CTL_SHORTNAME) {
239 		if ((s = sys_datacopy(endpt, m_in->m_lc_mib_sysctl.namep, SELF,
240 		    (vir_bytes)&name, sizeof(name[0]) * namelen)) != OK)
241 			return s;
242 	} else
243 		memcpy(name, m_in->m_lc_mib_sysctl.name,
244 		    sizeof(name[0]) * namelen);
245 
246 	/*
247 	 * Set up a structure for the old data, if any.  When no old address is
248 	 * given, be forgiving if oldlen is not zero, as the user may simply
249 	 * not have initialized the variable before passing a pointer to it.
250 	 */
251 	if (oldaddr != 0) {
252 		oldp.oldp_endpt = endpt;
253 		oldp.oldp_addr = oldaddr;
254 		oldp.oldp_len = oldlen;
255 		oldpp = &oldp;
256 	} else
257 		oldpp = NULL;
258 
259 	/*
260 	 * Set up a structure for the new data, if any.  If one of newaddr and
261 	 * newlen is zero but not the other, we (like NetBSD) disregard both.
262 	 */
263 	if (newaddr != 0 && newlen != 0) {
264 		newp.newp_endpt = endpt;
265 		newp.newp_addr = newaddr;
266 		newp.newp_len = newlen;
267 		newpp = &newp;
268 	} else
269 		newpp = NULL;
270 
271 	/*
272 	 * Set up a structure for other call parameters.  Most of these should
273 	 * be used rarely, and we may want to add more later, so do not pass
274 	 * all of them around as actual function parameters all the time.
275 	 */
276 	call.call_endpt = endpt;
277 	call.call_name = name;
278 	call.call_namelen = namelen;
279 	call.call_flags = 0;
280 	call.call_reslen = 0;
281 
282 	r = mib_dispatch(&call, &mib_root, oldpp, newpp);
283 
284 	/*
285 	 * From NetBSD: we copy out as much as we can from the old data, while
286 	 * at the same time computing the full data length.  Then, here at the
287 	 * end, if the entire result did not fit in the destination buffer, we
288 	 * return ENOMEM instead of success, thus also returning a partial
289 	 * result and the full data length.
290 	 *
291 	 * It is also possible that data are copied out along with a "real"
292 	 * error.  In that case, we must report a nonzero resulting length
293 	 * along with that error code.  This is currently the case when node
294 	 * creation resulted in a collision, in which case the error code is
295 	 * EEXIST while the existing node is copied out as well.
296 	 */
297 	if (r >= 0) {
298 		m_out->m_mib_lc_sysctl.oldlen = (size_t)r;
299 
300 		if (oldaddr != 0 && oldlen < (size_t)r)
301 			r = ENOMEM;
302 		else
303 			r = OK;
304 	} else
305 		m_out->m_mib_lc_sysctl.oldlen = call.call_reslen;
306 
307 	return r;
308 }
309 
310 /*
311  * Initialize the service.
312  */
313 static int
314 mib_init(int type __unused, sef_init_info_t * info __unused)
315 {
316 
317 	/*
318 	 * Initialize pointers and sizes of subtrees in different modules.
319 	 * This is needed because we cannot use sizeof on external arrays.
320 	 * We do initialize the node entry (including any other fields)
321 	 * statically through MIB_ENODE because that forces the array to be
322 	 * large enough to store the entry.
323 	 */
324 	mib_kern_init(&mib_table[CTL_KERN]);
325 	mib_minix_init(&mib_table[CTL_MINIX]);
326 
327 	/*
328 	 * Now that the static tree is complete, go through the entire tree,
329 	 * initializing miscellaneous fields.
330 	 */
331 	mib_tree_init(&mib_root);
332 
333 	return OK;
334 }
335 
336 /*
337  * Perform SEF startup.
338  */
339 static void
340 mib_startup(void)
341 {
342 
343 	sef_setcb_init_fresh(mib_init);
344 	/*
345 	 * If we restart we lose all dynamic state, which means we lose all
346 	 * nodes that have been created at run time.  However, running with
347 	 * only the static node tree is still better than not running at all.
348 	 */
349 	sef_setcb_init_restart(mib_init);
350 
351 	sef_startup();
352 }
353 
354 /*
355  * The Management Information Base (MIB) service.
356  */
357 int
358 main(void)
359 {
360 	message m_in, m_out;
361 	int r, ipc_status;
362 
363 	/* Perform initialization. */
364 	mib_startup();
365 
366 	/* The main message loop. */
367 	for (;;) {
368 		/* Receive a request. */
369 		if ((r = sef_receive_status(ANY, &m_in, &ipc_status)) != OK)
370 			panic("sef_receive failed: %d", r);
371 
372 		/* Process the request. */
373 		if (is_ipc_notify(ipc_status)) {
374 			/* We are not expecting any notifications. */
375 			printf("MIB: notification from %d\n", m_in.m_source);
376 
377 			continue;
378 		}
379 
380 		memset(&m_out, 0, sizeof(m_out));
381 
382 		switch (m_in.m_type) {
383 		case MIB_SYSCTL:
384 			r = mib_sysctl(&m_in, &m_out);
385 
386 			break;
387 
388 		default:
389 			r = ENOSYS;
390 		}
391 
392 		/* Send the reply. */
393 		m_out.m_type = r;
394 
395 		if ((r = ipc_sendnb(m_in.m_source, &m_out)) != OK)
396 			printf("MIB: ipc_sendnb failed (%d)\n", r);
397 	}
398 
399 	/* NOTREACHED */
400 	return 0;
401 }
402