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