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