1/*
2 * Copyright (c) 2007, 2008 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24#include <stdlib.h>
25#include <unistd.h>
26#include <sys/fcntl.h>
27#include <sys/errno.h>
28#include <sys/param.h>
29#include <sys/mount.h>
30#include <libproc.h>
31
32
33typedef struct {
34	// process IDs
35	int				*pids;
36	int				pids_count;
37	size_t				pids_size;
38
39	// threads
40	uint64_t			*threads;
41	int				thr_count;
42	size_t				thr_size;
43
44	// open file descriptors
45	struct proc_fdinfo		*fds;
46	int				fds_count;
47	size_t				fds_size;
48
49	// file/volume of interest
50	struct stat			match_stat;
51
52	// flags
53	uint32_t			flags;
54
55} fdOpenInfo, *fdOpenInfoRef;
56
57
58/*
59 * check_init
60 */
61static fdOpenInfoRef
62check_init(const char *path, uint32_t flags)
63{
64	fdOpenInfoRef	info;
65	int		status;
66
67	info = malloc(sizeof(*info));
68	if (!info)
69		return NULL;
70
71	info->pids		= NULL;
72	info->pids_count	= 0;
73	info->pids_size		= 0;
74
75	info->threads		= NULL;
76	info->thr_count		= 0;
77	info->thr_size		= 0;
78
79	info->fds		= NULL;
80	info->fds_count		= 0;
81	info->fds_size		= 0;
82
83	status = stat(path, &info->match_stat);
84	if (status == -1) {
85		goto fail;
86	}
87
88	info->flags		= flags;
89
90	return info;
91
92    fail :
93
94	free(info);
95	return NULL;
96}
97
98
99/*
100 * check_free
101 */
102static void
103check_free(fdOpenInfoRef info)
104{
105	if (info->pids != NULL) {
106		free(info->pids);
107	}
108
109	if (info->threads != NULL) {
110		free(info->threads);
111	}
112
113	if (info->fds != NULL) {
114		free(info->fds);
115	}
116
117	free(info);
118
119	return;
120}
121
122
123/*
124 * check_file
125 *   check if a process vnode is of interest
126 *
127 *   in  : vnode stat(2)
128 *   out : -1 if error
129 *          0 if no match
130 *          1 if match
131 */
132static int
133check_file(fdOpenInfoRef info, struct vinfo_stat *sb)
134{
135	if (sb->vst_dev == 0) {
136		// if no info
137		return 0;
138	}
139
140	if (sb->vst_dev != info->match_stat.st_dev) {
141		// if not the requested filesystem
142		return 0;
143	}
144
145	if (!(info->flags & PROC_LISTPIDSPATH_PATH_IS_VOLUME) &&
146	    (sb->vst_ino != info->match_stat.st_ino)) {
147		// if not the requested file
148		return 0;
149	}
150
151	return 1;
152}
153
154
155/*
156 * check_process_vnodes
157 *   check [process] current working directory
158 *   check [process] root directory
159 *
160 *   in  : pid
161 *   out : -1 if error
162 *          0 if no match
163 *          1 if match
164 */
165static int
166check_process_vnodes(fdOpenInfoRef info, int pid)
167{
168	int				buf_used;
169	int				status;
170	struct proc_vnodepathinfo	vpi;
171
172	buf_used = proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0, &vpi, sizeof(vpi));
173	if (buf_used <= 0) {
174		if (errno == ESRCH) {
175			// if the process is gone
176			return 0;
177		}
178		return -1;
179	} else if (buf_used < sizeof(vpi)) {
180		// if we didn't get enough information
181		return -1;
182	}
183
184	// processing current working directory
185	status = check_file(info, &vpi.pvi_cdir.vip_vi.vi_stat);
186	if (status != 0) {
187		// if error or match
188		return status;
189	}
190
191	// processing root directory
192	status = check_file(info, &vpi.pvi_rdir.vip_vi.vi_stat);
193	if (status != 0) {
194		// if error or match
195		return status;
196	}
197
198	return 0;
199}
200
201
202/*
203 * check_process_text
204 *   check [process] text (memory)
205 *
206 *   in  : pid
207 *   out : -1 if error
208 *          0 if no match
209 *          1 if match
210 */
211static int
212check_process_text(fdOpenInfoRef info, int pid)
213{
214	int		status;
215	int		buf_used;
216	struct proc_regionwithpathinfo	rwpi;
217
218	if (info->flags & PROC_LISTPIDSPATH_PATH_IS_VOLUME) {
219
220		// ask for first memory region that matches mountpoint
221		buf_used = proc_pidinfo(pid, PROC_PIDREGIONPATHINFO3, info->match_stat.st_dev, &rwpi, sizeof(rwpi));
222		if (buf_used <= 0) {
223			if ((errno == ESRCH) || (errno == EINVAL)) {
224				// if no more text information is available for this process.
225				return 0;
226			}
227			return -1;
228		} else if (buf_used < sizeof(rwpi)) {
229			// if we didn't get enough information
230			return -1;
231		}
232
233		status = check_file(info, &rwpi.prp_vip.vip_vi.vi_stat);
234		if (status != 0) {
235			// if error or match
236			return status;
237		}
238	} else {
239		uint64_t	a	= 0;
240
241		while (1) {	// for all memory regions
242			// processing next address
243			buf_used = proc_pidinfo(pid, PROC_PIDREGIONPATHINFO2, a, &rwpi, sizeof(rwpi));
244			if (buf_used <= 0) {
245				if ((errno == ESRCH) || (errno == EINVAL)) {
246					// if no more text information is available for this process.
247					break;
248				}
249				return -1;
250			} else if (buf_used < sizeof(rwpi)) {
251				// if we didn't get enough information
252				return -1;
253			}
254
255			status = check_file(info, &rwpi.prp_vip.vip_vi.vi_stat);
256			if (status != 0) {
257				// if error or match
258				return status;
259			}
260
261			a = rwpi.prp_prinfo.pri_address + rwpi.prp_prinfo.pri_size;
262		}
263	}
264
265	return 0;
266}
267
268
269/*
270 * check_process_fds
271 *   check [process] open file descriptors
272 *
273 *   in  : pid
274 *   out : -1 if error
275 *          0 if no match
276 *          1 if match
277 */
278static int
279check_process_fds(fdOpenInfoRef info, int pid)
280{
281	int	buf_used;
282	int	i;
283	int	status;
284
285	// get list of open file descriptors
286	buf_used = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, NULL, 0);
287	if (buf_used <= 0) {
288		return -1;
289	}
290
291	while (1) {
292		if (buf_used > info->fds_size) {
293			// if we need to allocate [more] space
294			while (buf_used > info->fds_size) {
295				info->fds_size += (sizeof(struct proc_fdinfo) * 32);
296			}
297
298			if (info->fds == NULL) {
299				info->fds = malloc(info->fds_size);
300			} else {
301				info->fds = reallocf(info->fds, info->fds_size);
302			}
303			if (info->fds == NULL) {
304				return -1;
305			}
306		}
307
308		buf_used = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, info->fds, (int)info->fds_size);
309		if (buf_used <= 0) {
310			return -1;
311		}
312
313		if ((buf_used + sizeof(struct proc_fdinfo)) >= info->fds_size) {
314			// if not enough room in the buffer for an extra fd
315			buf_used = (int)(info->fds_size + sizeof(struct proc_fdinfo));
316			continue;
317		}
318
319		info->fds_count = (int)(buf_used / sizeof(struct proc_fdinfo));
320		break;
321	}
322
323	// iterate through each file descriptor
324	for (i = 0; i < info->fds_count; i++) {
325		struct proc_fdinfo	*fdp;
326
327		fdp = &info->fds[i];
328		switch (fdp->proc_fdtype) {
329			case PROX_FDTYPE_VNODE : {
330				int			buf_used;
331				struct vnode_fdinfo	vi;
332
333				buf_used = proc_pidfdinfo(pid, fdp->proc_fd, PROC_PIDFDVNODEINFO, &vi, sizeof(vi));
334				if (buf_used <= 0) {
335					if (errno == ENOENT) {
336						/*
337						 * The file descriptor's vnode may have been revoked. This is a
338						 * bit of a hack, since an ENOENT error might not always mean the
339						 * descriptor's vnode has been revoked. As the libproc API
340						 * matures, this code may need to be revisited.
341						 */
342						continue;
343					}
344					return -1;
345				} else if (buf_used < sizeof(vi)) {
346					// if we didn't get enough information
347					return -1;
348				}
349
350				if ((info->flags & PROC_LISTPIDSPATH_EXCLUDE_EVTONLY) &&
351				    (vi.pfi.fi_openflags & O_EVTONLY)) {
352					// if this file should be excluded
353					continue;
354				}
355
356				status = check_file(info, &vi.pvi.vi_stat);
357				if (status != 0) {
358					// if error or match
359					return status;
360				}
361				break;
362			}
363			default :
364				break;
365		}
366	}
367
368	return 0;
369}
370
371
372/*
373 * check_process_threads
374 *   check [process] thread working directories
375 *
376 *   in  : pid
377 *   out : -1 if error
378 *          0 if no match
379 *          1 if match
380 */
381static int
382check_process_threads(fdOpenInfoRef info, int pid)
383{
384	int				buf_used;
385	int				status;
386	struct proc_taskallinfo		tai;
387
388	buf_used = proc_pidinfo(pid, PROC_PIDTASKALLINFO, 0, &tai, sizeof(tai));
389	if (buf_used <= 0) {
390		if (errno == ESRCH) {
391			// if the process is gone
392			return 0;
393		}
394		return -1;
395	} else if (buf_used < sizeof(tai)) {
396		// if we didn't get enough information
397		return -1;
398	}
399
400	// check thread info
401	if (tai.pbsd.pbi_flags & PROC_FLAG_THCWD) {
402		int	i;
403
404		// get list of threads
405		buf_used = tai.ptinfo.pti_threadnum * sizeof(uint64_t);
406
407		while (1) {
408			if (buf_used > info->thr_size) {
409				// if we need to allocate [more] space
410				while (buf_used > info->thr_size) {
411					info->thr_size += (sizeof(uint64_t) * 32);
412				}
413
414				if (info->threads == NULL) {
415					info->threads = malloc(info->thr_size);
416				} else {
417					info->threads = reallocf(info->threads, info->thr_size);
418				}
419				if (info->threads == NULL) {
420					return -1;
421				}
422			}
423
424			buf_used = proc_pidinfo(pid, PROC_PIDLISTTHREADS, 0, info->threads, (int)info->thr_size);
425			if (buf_used <= 0) {
426				return -1;
427			}
428
429			if ((buf_used + sizeof(uint64_t)) >= info->thr_size) {
430				// if not enough room in the buffer for an extra thread
431				buf_used = (int)(info->thr_size + sizeof(uint64_t));
432				continue;
433			}
434
435			info->thr_count = buf_used / sizeof(uint64_t);
436			break;
437		}
438
439		// iterate through each thread
440		for (i = 0; i < info->thr_count; i++) {
441			uint64_t			thr	= info->threads[i];
442			struct proc_threadwithpathinfo	tpi;
443
444			buf_used = proc_pidinfo(pid, PROC_PIDTHREADPATHINFO, thr, &tpi, sizeof(tpi));
445			if (buf_used <= 0) {
446				if ((errno == ESRCH) || (errno == EINVAL)) {
447					// if the process or thread is gone
448					continue;
449				}
450			} else if (buf_used < sizeof(tai)) {
451				// if we didn't get enough information
452				return -1;
453			}
454
455			status = check_file(info, &tpi.pvip.vip_vi.vi_stat);
456			if (status != 0) {
457				// if error or match
458				return status;
459			}
460		}
461	}
462
463	return 0;
464}
465
466
467/*
468 * check_process_phase1
469 *   check [process] process-wide current working and root directories
470 *   check [process] open file descriptors
471 *   check [process] per-thread current working and root directories
472 *
473 *   in  : pid
474 *   out : -1 if error
475 *          0 if no match
476 *          1 if match
477 */
478static int
479check_process_phase1(fdOpenInfoRef info, int pid)
480{
481	int	status;
482
483	// check root and current working directory
484	status = check_process_vnodes(info, pid);
485	if (status != 0) {
486		// if error or match
487		return status;
488	}
489
490	// check open file descriptors
491	status = check_process_fds(info, pid);
492	if (status != 0) {
493		// if error or match
494		return status;
495	}
496
497	// check per-thread working directories
498	status = check_process_threads(info, pid);
499	if (status != 0) {
500		// if error or match
501		return status;
502	}
503
504	return 0;
505}
506
507/*
508 * check_process_phase2
509 *   check [process] text (memory)
510 *
511 *   in  : pid
512 *   out : -1 if error
513 *          0 if no match
514 *          1 if match
515 */
516static int
517check_process_phase2(fdOpenInfoRef info, int pid)
518{
519	int	status;
520
521	// check process text (memory)
522	status = check_process_text(info, pid);
523	if (status != 0) {
524		// if error or match
525		return status;
526	}
527
528	return 0;
529}
530
531/*
532 * proc_listpidspath
533 *
534 *   in  : type
535 *       : typeinfo
536 *       : path
537 *       : pathflags
538 *       : buffer
539 *       : buffersize
540 *   out : buffer filled with process IDs that have open file
541 *         references that match the specified path or volume;
542 *         return value is the bytes of the returned buffer
543 *         that contains valid information.
544 */
545int
546proc_listpidspath(uint32_t	type,
547		  uint32_t	typeinfo,
548		  const char	*path,
549		  uint32_t	pathflags,
550		  void		*buffer,
551		  int		buffersize)
552{
553	int		buf_used;
554	int		*buf_next	= (int *)buffer;
555	int		i;
556	fdOpenInfoRef	info;
557	int		status		= -1;
558
559	if (buffer == NULL) {
560		// if this is a sizing request
561		return proc_listpids(type, typeinfo, NULL, 0);
562	}
563
564	buffersize -= (buffersize % sizeof(int)); // make whole number of ints
565	if (buffersize < sizeof(int)) {
566		// if we can't even return a single PID
567		errno = ENOMEM;
568		return -1;
569	}
570
571	// init
572	info = check_init(path, pathflags);
573	if (info == NULL) {
574		return -1;
575	}
576
577	// get list of processes
578	buf_used = proc_listpids(type, typeinfo, NULL, 0);
579	if (buf_used <= 0) {
580		goto done;
581	}
582
583	while (1) {
584		if (buf_used > info->pids_size) {
585			// if we need to allocate [more] space
586			while (buf_used > info->pids_size) {
587				info->pids_size += (sizeof(int) * 32);
588			}
589
590			if (info->pids == NULL) {
591				info->pids = malloc(info->pids_size);
592			} else {
593				info->pids = reallocf(info->pids, info->pids_size);
594			}
595			if (info->pids == NULL) {
596				goto done;
597			}
598		}
599
600		buf_used = proc_listpids(type, typeinfo, info->pids, (int)info->pids_size);
601		if (buf_used <= 0) {
602			goto done;
603		}
604
605		if ((buf_used + sizeof(int)) >= info->pids_size) {
606			// if not enough room in the buffer for an extra pid
607			buf_used = (int)(info->pids_size + sizeof(int));
608			continue;
609		}
610
611		info->pids_count = buf_used / sizeof(int);
612		break;
613	}
614
615	// iterate through each process
616	buf_used = 0;
617	for (i = info->pids_count - 1; i >= 0; i--) {
618		int	pid;
619		int	pstatus;
620
621		pid = info->pids[i];
622		if (pid == 0) {
623			continue;
624		}
625
626		pstatus = check_process_phase1(info, pid);
627		if (pstatus != 1) {
628			// if not a match
629			continue;
630		}
631
632		*buf_next++ = pid;
633		buf_used += sizeof(int);
634
635		if (buf_used >= buffersize) {
636			// if we have filled the buffer
637			break;
638		}
639	}
640
641	if (buf_used >= buffersize) {
642		// if we have filled the buffer
643		status = buf_used;
644		goto done;
645	}
646
647	// do a more expensive search if we still have buffer space
648	for (i = info->pids_count - 1; i >= 0; i--) {
649		int	pid;
650		int	pstatus;
651
652		pid = info->pids[i];
653		if (pid == 0) {
654			continue;
655		}
656
657		pstatus = check_process_phase2(info, pid);
658		if (pstatus != 1) {
659			// if not a match
660			continue;
661		}
662
663		*buf_next++ = pid;
664		buf_used += sizeof(int);
665
666		if (buf_used >= buffersize) {
667			// if we have filled the buffer
668			break;
669		}
670	}
671
672	status = buf_used;
673
674    done :
675
676	// cleanup
677	check_free(info);
678
679	return status;
680}
681