xref: /minix3/minix/fs/procfs/tree.c (revision 4f89addcc198bff2fc614da2f8f4a7beb4b6cb7d)
1 /* ProcFS - tree.c - dynamic PID tree management and hook implementations */
2 
3 #include "inc.h"
4 
5 struct proc proc[NR_PROCS + NR_TASKS];
6 struct mproc mproc[NR_PROCS];
7 struct fproc fproc[NR_PROCS];
8 
9 static int nr_pid_entries;
10 
11 /*
12  * Return whether the given slot is in use by a process.
13  */
14 static int
15 slot_in_use(int slot)
16 {
17 
18 	/*
19 	 * For kernel tasks, check only the kernel slot.  Tasks do not have a
20 	 * PM/VFS process slot.
21 	 */
22 	if (slot < NR_TASKS)
23 		return (proc[slot].p_rts_flags != RTS_SLOT_FREE);
24 
25 	/* For regular processes, check only the PM slot.  Do not check the
26 	 * kernel slot, because that would skip zombie processes.  The PID
27 	 * check should be redundant, but if it fails, procfs could crash.
28 	 */
29 	return ((mproc[slot - NR_TASKS].mp_flags & IN_USE) &&
30 	    mproc[slot - NR_TASKS].mp_pid != 0);
31 }
32 
33 /*
34  * Check if the owner user and group ID of the inode are still in sync with
35  * the current effective user and group ID of the given process.
36  */
37 static int
38 check_owner(struct inode * node, int slot)
39 {
40 	struct inode_stat stat;
41 
42 	if (slot < NR_TASKS) return TRUE;
43 
44 	get_inode_stat(node, &stat);
45 
46 	return (stat.uid == mproc[slot - NR_TASKS].mp_effuid &&
47 	    stat.gid == mproc[slot - NR_TASKS].mp_effgid);
48 }
49 
50 /*
51  * Fill in an inode_stat structure for the given process slot and per-PID file
52  * index (or NO_INDEX for the process subdirectory root).
53  */
54 static void
55 make_stat(struct inode_stat * stat, int slot, int index)
56 {
57 
58 	if (index == NO_INDEX)
59 		stat->mode = DIR_ALL_MODE;
60 	else
61 		stat->mode = pid_files[index].mode;
62 
63 	if (slot < NR_TASKS) {
64 		stat->uid = SUPER_USER;
65 		stat->gid = SUPER_USER;
66 	} else {
67 		stat->uid = mproc[slot - NR_TASKS].mp_effuid;
68 		stat->gid = mproc[slot - NR_TASKS].mp_effgid;
69 	}
70 
71 	stat->size = 0;
72 	stat->dev = NO_DEV;
73 }
74 
75 /*
76  * Return whether the given node is a PID directory.
77  */
78 static int
79 dir_is_pid(struct inode *node)
80 {
81 
82 	return (get_parent_inode(node) == get_root_inode() &&
83 	    get_inode_index(node) != NO_INDEX);
84 }
85 
86 /*
87  * Get the process table from the kernel.  Check the magic number in the table
88  * entries.
89  */
90 static int
91 update_proc_table(void)
92 {
93 	int r, slot;
94 
95 	if ((r = sys_getproctab(proc)) != OK) return r;
96 
97 	for (slot = 0; slot < NR_PROCS + NR_TASKS; slot++) {
98 		if (proc[slot].p_magic != PMAGIC) {
99 			printf("PROCFS: system version mismatch!\n");
100 
101 			return EINVAL;
102 		}
103 	}
104 
105 	return OK;
106 }
107 
108 /*
109  * Get the process table from PM.  Check the magic number in the table entries.
110  */
111 static int
112 update_mproc_table(void)
113 {
114 	int r, slot;
115 
116 	r = getsysinfo(PM_PROC_NR, SI_PROC_TAB, mproc, sizeof(mproc));
117 	if (r != OK) return r;
118 
119 	for (slot = 0; slot < NR_PROCS; slot++) {
120 		if (mproc[slot].mp_magic != MP_MAGIC) {
121 			printf("PROCFS: PM version mismatch!\n");
122 
123 			return EINVAL;
124 		}
125 	}
126 
127 	return OK;
128 }
129 
130 /*
131  * Get the process table from VFS.
132  */
133 static int
134 update_fproc_table(void)
135 {
136 
137 	return getsysinfo(VFS_PROC_NR, SI_PROC_TAB, fproc, sizeof(fproc));
138 }
139 
140 /*
141  * Get the process tables from the kernel, PM, and VFS.
142  */
143 static int
144 update_tables(void)
145 {
146 	int r;
147 
148 	if ((r = update_proc_table()) != OK) return r;
149 
150 	if ((r = update_mproc_table()) != OK) return r;
151 
152 	if ((r = update_fproc_table()) != OK) return r;
153 
154 	return OK;
155 }
156 
157 /*
158  * Initialize this module, before VTreeFS is started.  As part of the process,
159  * check if we're not compiled against a kernel different from the one that is
160  * running at the moment.
161  */
162 int
163 init_tree(void)
164 {
165 	int i, r;
166 
167 	if ((r = update_tables()) != OK)
168 		return r;
169 
170 	/*
171 	 * Get the maximum number of entries that we may add to each PID's
172 	 * directory.  We could just default to a large value, but why not get
173 	 * it right?
174 	 */
175 	for (i = 0; pid_files[i].name != NULL; i++);
176 
177 	nr_pid_entries = i;
178 
179 	return OK;
180 }
181 
182 /*
183  * Out of inodes - the NR_INODES value is set too low.  We can not do much, but
184  * we might be able to continue with degraded functionality, so do not panic.
185  * If the NR_INODES value is not below the *crucial* minimum, the symptom of
186  * this case will be an incomplete listing of the main proc directory.
187  */
188 void
189 out_of_inodes(void)
190 {
191 	static int warned = FALSE;
192 
193 	if (warned == FALSE) {
194 		printf("PROCFS: out of inodes!\n");
195 
196 		warned = TRUE;
197 	}
198 }
199 
200 /*
201  * Regenerate the set of PID directories in the root directory of the file
202  * system.  Add new directories and delete old directories as appropriate;
203  * leave unchanged those that should remain the same.
204  */
205 static void
206 construct_pid_dirs(void)
207 {
208 	/*
209 	 * We have to make two passes.  Otherwise, we would trigger a vtreefs
210 	 * assert when we add an entry for a PID before deleting the previous
211 	 * entry for that PID.  While rare, such rapid PID reuse does occur in
212 	 * practice.
213 	 */
214 	struct inode *root, *node;
215 	struct inode_stat stat;
216 	char name[PNAME_MAX+1];
217 	pid_t pid;
218 	int i;
219 
220 	root = get_root_inode();
221 
222 	/* First pass: delete old entries. */
223 	for (i = 0; i < NR_PROCS + NR_TASKS; i++) {
224 		/* Do we already have an inode associated with this slot? */
225 		node = get_inode_by_index(root, i);
226 		if (node == NULL)
227 			continue;
228 
229 		/*
230 		 * If the process slot is not in use, delete the associated
231 		 * inode.
232 		 */
233 		if (!slot_in_use(i)) {
234 			delete_inode(node);
235 
236 			continue;
237 		}
238 
239 		/* Otherwise, get the process ID. */
240 		if (i < NR_TASKS)
241 			pid = (pid_t)(i - NR_TASKS);
242 		else
243 			pid = mproc[i - NR_TASKS].mp_pid;
244 
245 		/*
246 		 * If there is an old entry, see if the pid matches the current
247 		 * entry, and the owner is still the same.  Otherwise, delete
248 		 * the old entry first.  We reconstruct the entire subtree even
249 		 * if only the owner changed, for security reasons: if a
250 		 * process could keep open a file or directory across the owner
251 		 * change, it might be able to access information it shouldn't.
252 		 */
253 		if (pid != (pid_t)get_inode_cbdata(node) ||
254 		    !check_owner(node, i))
255 			delete_inode(node);
256 	}
257 
258 	/* Second pass: add new entries. */
259 	for (i = 0; i < NR_PROCS + NR_TASKS; i++) {
260 		/* If the process slot is not in use, skip this slot. */
261 		if (!slot_in_use(i))
262 			continue;
263 
264 		/*
265 		 * If we have an inode associated with this slot, we have
266 		 * already checked it to be up-to-date above.
267 		 */
268 		if (get_inode_by_index(root, i) != NULL)
269 			continue;
270 
271 		/* Get the process ID. */
272 		if (i < NR_TASKS)
273 			pid = (pid_t)(i - NR_TASKS);
274 		else
275 			pid = mproc[i - NR_TASKS].mp_pid;
276 
277 		/* Add the entry for the process slot. */
278 		snprintf(name, PNAME_MAX + 1, "%d", pid);
279 
280 		make_stat(&stat, i, NO_INDEX);
281 
282 		node = add_inode(root, name, i, &stat, nr_pid_entries,
283 		    (cbdata_t)pid);
284 
285 		if (node == NULL)
286 			out_of_inodes();
287 	}
288 }
289 
290 /*
291  * Construct one file in a PID directory, if a file with the given name should
292  * exist at all.
293  */
294 static void
295 make_one_pid_entry(struct inode * parent, char * name, int slot)
296 {
297 	struct inode *node;
298 	struct inode_stat stat;
299 	int i;
300 
301 	/* Don't readd if it is already there. */
302 	node = get_inode_by_name(parent, name);
303 	if (node != NULL)
304 		return;
305 
306 	/* Only add the file if it is a known, registered name. */
307 	for (i = 0; pid_files[i].name != NULL; i++) {
308 		if (!strcmp(name, pid_files[i].name)) {
309 			make_stat(&stat, slot, i);
310 
311 			node = add_inode(parent, name, i, &stat, (index_t)0,
312 			    (cbdata_t)0);
313 
314 			if (node == NULL)
315 				out_of_inodes();
316 
317 			break;
318 		}
319 	}
320 }
321 
322 /*
323  * Construct all files in a PID directory.
324  */
325 static void
326 make_all_pid_entries(struct inode * parent, int slot)
327 {
328 	struct inode *node;
329 	struct inode_stat stat;
330 	int i;
331 
332 	for (i = 0; pid_files[i].name != NULL; i++) {
333 		node = get_inode_by_index(parent, i);
334 		if (node != NULL)
335 			continue;
336 
337 		make_stat(&stat, slot, i);
338 
339 		node = add_inode(parent, pid_files[i].name, i, &stat,
340 		    (index_t)0, (cbdata_t)0);
341 
342 		if (node == NULL)
343 			out_of_inodes();
344 	}
345 }
346 
347 /*
348  * Construct one requested file entry, or all file entries, in a PID directory.
349  */
350 static void
351 construct_pid_entries(struct inode * parent, char * name)
352 {
353 	int slot;
354 
355 	slot = get_inode_index(parent);
356 	assert(slot >= 0 && slot < NR_TASKS + NR_PROCS);
357 
358 	/* If this process is already gone, delete the directory now. */
359 	if (!slot_in_use(slot)) {
360 		delete_inode(parent);
361 
362 		return;
363 	}
364 
365 	/*
366 	 * If a specific file name is being looked up, see if we have to add
367 	 * an inode for that file.  If the directory contents are being
368 	 * retrieved, add all files that have not yet been added.
369 	 */
370 	if (name != NULL)
371 		make_one_pid_entry(parent, name, slot);
372 	else
373 		make_all_pid_entries(parent, slot);
374 }
375 
376 /*
377  * Data is requested from one of the files in a PID directory. Call the
378  * function that is responsible for generating the data for that file.
379  */
380 static void
381 pid_read(struct inode * node)
382 {
383 	struct inode *parent;
384 	int slot, index;
385 
386 	/*
387 	 * Get the slot number of the process.  Note that this currently will
388 	 * not work for files not in the top-level pid subdirectory.
389 	 */
390 	parent = get_parent_inode(node);
391 
392 	slot = get_inode_index(parent);
393 
394 	/* Get this file's index number. */
395 	index = get_inode_index(node);
396 
397 	/* Call the handler procedure for the file. */
398 	((void (*)(int))pid_files[index].data)(slot);
399 }
400 
401 /*
402  * The contents of a symbolic link in a PID directory are requested.  This
403  * function is a placeholder for future use.
404  */
405 static int
406 pid_link(struct inode * __unused node, char * ptr, int max)
407 {
408 
409 	/* Nothing yet. */
410 	strlcpy(ptr, "", max);
411 
412 	return OK;
413 }
414 
415 /*
416  * Path name resolution hook, for a specific parent and name pair.  If needed,
417  * update our own view of the system first; after that, determine whether we
418  * need to (re)generate certain files.
419  */
420 int
421 lookup_hook(struct inode * parent, char * name, cbdata_t __unused cbdata)
422 {
423 	static clock_t last_update = 0;
424 	clock_t now;
425 	int r;
426 
427 	/*
428 	 * Update lazily for lookups, as this gets too expensive otherwise.
429 	 * Alternative: pull in only PM's table?
430 	 */
431 	if ((r = getticks(&now)) != OK)
432 		panic("unable to get uptime: %d", r);
433 
434 	if (last_update != now) {
435 		update_tables();
436 
437 		last_update = now;
438 	}
439 
440 	/*
441 	 * If the parent is the root directory, we must now reconstruct all
442 	 * entries, because some of them might have been garbage collected.
443 	 * We must update the entire tree at once; if we update individual
444 	 * entries, we risk name collisions.
445 	 *
446 	 * If the parent is a process directory, we may need to (re)construct
447 	 * the entry being looked up.
448 	 */
449 	if (parent == get_root_inode())
450 		construct_pid_dirs();
451 	else if (dir_is_pid(parent))
452 		/*
453 		 * We might now have deleted our current containing directory;
454 		 * construct_pid_entries() will take care of this case.
455 		 */
456 		construct_pid_entries(parent, name);
457 	else
458 		/* TODO: skip updating the main tables in this case. */
459 		service_lookup(parent, now);
460 
461 	return OK;
462 }
463 
464 /*
465  * Directory entry retrieval hook, for potentially all files in a directory.
466  * Make sure that all files that are supposed to be returned, are actually part
467  * of the virtual tree.
468  */
469 int
470 getdents_hook(struct inode * node, cbdata_t __unused cbdata)
471 {
472 
473 	if (node == get_root_inode()) {
474 		update_tables();
475 
476 		construct_pid_dirs();
477 	} else if (dir_is_pid(node))
478 		construct_pid_entries(node, NULL /*name*/);
479 	else
480 		service_getdents(node);
481 
482 	return OK;
483 }
484 
485 /*
486  * Regular file read hook.  Call the appropriate callback function to generate
487  * and return the data.
488  */
489 ssize_t
490 read_hook(struct inode * node, char * ptr, size_t len, off_t off,
491 	cbdata_t cbdata)
492 {
493 	struct inode *parent;
494 
495 	buf_init(ptr, len, off);
496 
497 	/* Populate the buffer with the proper content. */
498 	if (get_inode_index(node) != NO_INDEX) {
499 		parent = get_parent_inode(node);
500 
501 		/* The PID directories are indexed; service/ is not. */
502 		if (get_inode_index(parent) != NO_INDEX)
503 			pid_read(node);
504 		else
505 			service_read(node);
506 	} else
507 		((void (*)(void))cbdata)();
508 
509 	return buf_result();
510 }
511 
512 /*
513  * Symbolic link resolution hook.  Not used yet.
514  */
515 int
516 rdlink_hook(struct inode * node, char * ptr, size_t max,
517 	cbdata_t __unused cbdata)
518 {
519 	struct inode *parent;
520 
521 	/* Get the parent inode. */
522 	parent = get_parent_inode(node);
523 
524 	/* If the parent inode is a pid directory, call the pid handler. */
525 	if (parent != NULL && dir_is_pid(parent))
526 		pid_link(node, ptr, max);
527 
528 	return OK;
529 }
530