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