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
pid_from_slot(int slot)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
check_owner(struct inode * node,int slot)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
make_stat(struct inode_stat * stat,int slot,int index)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
dir_is_pid(struct inode * node)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
update_list(void)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
init_tree(void)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
out_of_inodes(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
construct_pid_dirs(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
make_one_pid_entry(struct inode * parent,char * name,int slot)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
make_all_pid_entries(struct inode * parent,int slot)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
construct_pid_entries(struct inode * parent,char * name)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
pid_read(struct inode * node)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
pid_link(struct inode * __unused node,char * ptr,int max)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
lookup_hook(struct inode * parent,char * name,cbdata_t __unused cbdata)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
getdents_hook(struct inode * node,cbdata_t __unused cbdata)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
read_hook(struct inode * node,char * ptr,size_t len,off_t off,cbdata_t cbdata)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
rdlink_hook(struct inode * node,char * ptr,size_t max,cbdata_t __unused cbdata)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