Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
Relevant replacement documents include:
BootstrapDump.c
/* |
File: BootstrapDump.c |
Contains: A program to dump the Mach bootstrap port namespace. |
Written by: DTS |
Copyright: Copyright (c) 2007 Apple Inc. All Rights Reserved. |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. |
("Apple") in consideration of your agreement to the following |
terms, and your use, installation, modification or |
redistribution of this Apple software constitutes acceptance of |
these terms. If you do not agree with these terms, please do |
not use, install, modify or redistribute this Apple software. |
In consideration of your agreement to abide by the following |
terms, and subject to these terms, Apple grants you a personal, |
non-exclusive license, under Apple's copyrights in this |
original Apple software (the "Apple Software"), to use, |
reproduce, modify and redistribute the Apple Software, with or |
without modifications, in source and/or binary forms; provided |
that if you redistribute the Apple Software in its entirety and |
without modifications, you must retain this notice and the |
following text and disclaimers in all such redistributions of |
the Apple Software. Neither the name, trademarks, service marks |
or logos of Apple Inc. may be used to endorse or promote |
products derived from the Apple Software without specific prior |
written permission from Apple. Except as expressly stated in |
this notice, no other rights or licenses, express or implied, |
are granted by Apple herein, including but not limited to any |
patent rights that may be infringed by your derivative works or |
by other works in which the Apple Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. |
APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING |
WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, |
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING |
THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN |
COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, |
INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED |
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY |
OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION |
OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY |
OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR |
OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF |
SUCH DAMAGE. |
*/ |
#include <assert.h> |
#include <errno.h> |
#include <pwd.h> |
#include <stdbool.h> |
#include <stdio.h> |
#include <stdlib.h> |
#include <strhash.h> |
#include <sys/sysctl.h> |
#include <sys/utsname.h> |
#include <unistd.h> |
#include <servers/bootstrap.h> |
#include <mach/mach.h> |
///////////////////////////////////////////////////////////////// |
#pragma mark ***** Infrastructure |
typedef struct kinfo_proc kinfo_proc; |
static int GetBSDProcessList(kinfo_proc **procList, size_t *procCount) |
// Returns a list of all BSD processes on the system. This routine |
// allocates the list and puts it in *procList and a count of the |
// number of entries in *procCount. You are responsible for freeing |
// this list (use "free" from System framework). |
// On success, the function returns 0. |
// On error, the function returns a BSD errno value. |
{ |
int err; |
kinfo_proc * result; |
bool done; |
static const int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 }; |
// Declaring name as const requires us to cast it when passing it to |
// sysctl because the prototype doesn't include the const modifier. |
size_t length; |
assert( procList != NULL); |
assert(*procList == NULL); |
assert(procCount != NULL); |
*procCount = 0; |
// We start by calling sysctl with result == NULL and length == 0. |
// That will succeed, and set length to the appropriate length. |
// We then allocate a buffer of that size and call sysctl again |
// with that buffer. If that succeeds, we're done. If that fails |
// with ENOMEM, we have to throw away our buffer and loop. Note |
// that the loop causes use to call sysctl with NULL again; this |
// is necessary because the ENOMEM failure case sets length to |
// the amount of data returned, not the amount of data that |
// could have been returned. |
result = NULL; |
done = false; |
do { |
assert(result == NULL); |
// Call sysctl with a NULL buffer. |
length = 0; |
err = sysctl( (int *) name, (sizeof(name) / sizeof(*name)) - 1, |
NULL, &length, |
NULL, 0); |
if (err == -1) { |
err = errno; |
} |
// Allocate an appropriately sized buffer based on the results |
// from the previous call. |
if (err == 0) { |
result = malloc(length); |
if (result == NULL) { |
err = ENOMEM; |
} |
} |
// Call sysctl again with the new buffer. If we get an ENOMEM |
// error, toss away our buffer and start again. |
if (err == 0) { |
err = sysctl( (int *) name, (sizeof(name) / sizeof(*name)) - 1, |
result, &length, |
NULL, 0); |
if (err == -1) { |
err = errno; |
} |
if (err == 0) { |
done = true; |
} else if (err == ENOMEM) { |
assert(result != NULL); |
free(result); |
result = NULL; |
err = 0; |
} |
} |
} while (err == 0 && ! done); |
// Clean up and establish post conditions. |
if ( (err != 0) && (result != NULL) ) { |
free(result); |
result = NULL; |
} |
*procList = result; |
if (err == 0) { |
*procCount = length / sizeof(kinfo_proc); |
} |
assert( (err == 0) == (*procList != NULL) ); |
return err; |
} |
static int FindProcessByName(const char *processName, pid_t *pid) |
// Find the process that best matches processName and return |
// its PID. It first tries to find an exact match; if that fails |
// it tries to find a substring match; if that fails it checks |
// whether processName is a number and returns that as the PID. |
// |
// On entry, processName must not be NULL, and it must not be the |
// empty string. pid must not be NULL. |
// On success, *pid will be the process ID of the found process. |
// On error, *pid is undefined. |
{ |
int err; |
int foundCount; |
kinfo_proc * processList; |
size_t processCount; |
size_t processIndex; |
assert(processName != NULL); |
assert(processName[0] != 0); // needed for strstr to behave |
assert(pid != NULL); |
processList = NULL; |
foundCount = 0; |
// Get the list of all processes. |
err = GetBSDProcessList(&processList, &processCount); |
if (err == 0) { |
// Count the exact matches. |
for (processIndex = 0; processIndex < processCount; processIndex++) { |
if ( strcmp(processList[processIndex].kp_proc.p_comm, processName) == 0 ) { |
*pid = processList[processIndex].kp_proc.p_pid; |
foundCount += 1; |
} |
} |
// If we got nothing, count the substring matches. |
if (foundCount == 0) { |
for (processIndex = 0; processIndex < processCount; processIndex++) { |
if ( strstr(processList[processIndex].kp_proc.p_comm, processName) != NULL ) { |
*pid = processList[processIndex].kp_proc.p_pid; |
foundCount += 1; |
} |
} |
} |
// If we found more than 1, that's ambiguous and we error out. |
if (foundCount > 1) { |
err = EEXIST; |
} |
} |
// If still not found, try processName as a PID. |
if ( (err == 0) && (foundCount == 0) ) { |
char * firstInvalid; |
*pid = (pid_t) strtol(processName, &firstInvalid, 10); |
if ( (processName[0] == 0) || (*firstInvalid != 0) ) { |
err = ESRCH; |
} |
} |
free(processList); |
return err; |
} |
///////////////////////////////////////////////////////////////// |
#pragma mark ***** Bootstrap Forest Dump |
static int gDebug = 0; |
// The Bootstrap structure records all of the information about a single |
// bootstrap namespace. It is primarily tracked by a hash table, stored |
// in the BootstrapContext with nodes linked by the hashLink field. This |
// allows us to quickly determine if we've seen a given bootstrap namespace |
// before and avoid doing any work in that case. The Bootstrap structures |
// are also linked into a tree, rooted at one of the entries in the rootBootstraps |
// array of the BootstrapContext, and connected the siblings and firstChild links. |
typedef struct Bootstrap Bootstrap; |
struct Bootstrap { |
mach_port_t port; // send right for bootstrap namespace |
size_t procCount; // number of valid entries in procs |
const kinfo_proc * * procs; // processes using this namespace, always |
// has space for context->procCount elements |
Bootstrap * sibling; // next bootstrap namespace at this level |
Bootstrap * firstChild; // first child namespace of this namespace |
Bootstrap * hashLink; // link for hash table |
}; |
// The BootstrapContext structures stores all of the information needed to |
// anchor the hash table and forest of Bootstrap tree structures. |
struct BootstrapContext { |
size_t procCount; // upper bound on number of processes |
size_t rootBootstrapCount; // number of valid entries in rootBootstraps |
Bootstrap * * rootBootstraps; // Bootstrap tree roots |
Bootstrap * portToBootstrapMap[256]; // hash table contain all Bootstraps, |
// indexed by bootstrap mach_port_t hash |
size_t bootstrapCount; // count of allocated Bootstraps, |
// for debugging only |
size_t hashCollisions; // number of hash collisions, |
// for debugging only |
}; |
typedef struct BootstrapContext BootstrapContext; |
static void FreeBootstrap(BootstrapContext * context, Bootstrap *bootstrap); |
// foward declaration |
static int GetOrAddBootstrap( |
BootstrapContext * context, |
mach_port_t bootstrapPort, |
Bootstrap ** bootstrapPtr |
) |
// For the given bootstrapPort, return a pointer to the associated Bootstrap |
// structure. If we haven't seen bootstrapPort before, this creates the |
// Bootstrap structure. If we have seen bootstrapPort before, this just |
// returns a pointer to a previously allocated Bootstrap structure. Existing |
// Bootstrap structures are tracked by a hash table in the BootstrapContext. |
// |
// This routine also creates (by calling itself recursively) Bootstrap |
// structures for the parent bootstrap namespace of bootstrapPort (if any). |
// It then links any newly created Bootstrap into the child list of the |
// parent. |
// |
// OTOH, if there is no parent bootstrap, this sets up an entry in rootBootstrap |
// array of the BootstrapContext to indicate that this is the root of a bootstrap |
// tree. |
{ |
int err; |
kern_return_t kr; |
kern_return_t krJunk; |
size_t hash; |
Bootstrap * value; |
assert(context != NULL); |
// We allow bootstrapPort to be null because on Mac OS X 10.5 and later every instance |
// of launchd has a null bootstrap port. If we disallowed a null bootstrap port, these |
// processes wouldn't show up anywhere in the forest. Allowing this does mean we have |
// to be careful, later in this routine, not to assume that bootstrapPort is not null. |
// |
// assert(bootstrapPort != MACH_PORT_NULL); |
assert(bootstrapPtr != NULL); |
err = 0; |
// See if we have this bootstrap in the hash table already. |
hash = (bootstrapPort & 0x0FF) |
^ ((bootstrapPort >> 8) & 0x0FF) |
^ ((bootstrapPort >> 16) & 0x0FF) |
^ ((bootstrapPort >> 24) & 0x0FF); |
assert(hash < (sizeof(context->portToBootstrapMap) / sizeof(context->portToBootstrapMap[0]))); |
value = context->portToBootstrapMap[hash]; |
while ( (value != NULL) && (value->port != bootstrapPort) ) { |
value = value->hashLink; |
} |
if (value == NULL) { |
Bootstrap * parentBootstrap; |
mach_port_t parentBootstrapPort; |
// We don't currently know about this bootstrap; create it. We take a |
// reference to the bootstrap port (assuming it's not MACH_PORT_NULL) |
// so that our caller doesn't have to retain their reference. |
parentBootstrapPort = MACH_PORT_NULL; |
value = (Bootstrap *) calloc(1, sizeof(*value)); |
if (value == NULL) { |
err = ENOMEM; |
} |
if (err == 0) { |
context->bootstrapCount += 1; |
value->procs = calloc(context->procCount, sizeof(*value->procs)); |
if (value->procs == NULL) { |
err = ENOMEM; |
} |
} |
if (err == 0) { |
if (bootstrapPort != MACH_PORT_NULL) { |
kr = mach_port_mod_refs(mach_task_self(), bootstrapPort, MACH_PORT_RIGHT_SEND, 1); |
if (kr == KERN_SUCCESS) { |
value->port = bootstrapPort; |
} else { |
err = EINVAL; |
} |
} |
} |
// Now process the bootstrap's parent, if any, and so on up the chain |
// recursively. |
if (err == 0) { |
kr = bootstrap_parent(bootstrapPort, &parentBootstrapPort); |
if ( (kr != KERN_SUCCESS) || (parentBootstrapPort == bootstrapPort) ) { |
// There is no parent bootstrap. This must be a root bootstrap. |
// Let's tag it as such. The rootBootstraps array has procCount |
// entries, which is a safe, if excessive, upper bound. |
assert(context->rootBootstrapCount < context->procCount); |
context->rootBootstraps[context->rootBootstrapCount] = value; |
context->rootBootstrapCount += 1; |
} else { |
// There /is/ a parent bootstrap port; call ourselves recursively to |
// get its tree node. |
err = GetOrAddBootstrap(context, parentBootstrapPort, &parentBootstrap); |
if (err == 0) { |
assert(parentBootstrapPort == parentBootstrap->port); |
if (gDebug >= 1) { |
fprintf( |
stderr, |
" parent of 0x%x [%p] is 0x%x [%p]\n", |
bootstrapPort, |
value, |
parentBootstrapPort, |
parentBootstrap |
); |
} |
// Add ourselves to the parent's list of children. We add to the end |
// because it produces a nicer output (contexts associated with higher |
// PIDs appear later). |
if (true) { |
if (gDebug >= 1) { |
fprintf( |
stderr, |
" adding 0x%x [%p] as child of 0x%x [%p]\n", |
bootstrapPort, |
value, |
parentBootstrap->port, |
parentBootstrap |
); |
if (parentBootstrap->firstChild != NULL) { |
fprintf( |
stderr, |
" replacing 0x%x [%p] as first child\n", |
parentBootstrap->firstChild->port, |
parentBootstrap->firstChild |
); |
} |
} |
assert(value->sibling == NULL); |
value->sibling = parentBootstrap->firstChild; |
parentBootstrap->firstChild = value; |
} else { |
if (parentBootstrap->firstChild == NULL) { |
if (gDebug >= 1) { |
fprintf( |
stderr, |
" adding 0x%x [%p] as first child of 0x%x [%p]\n", |
bootstrapPort, |
value, |
parentBootstrap->port, |
parentBootstrap |
); |
} |
parentBootstrap->firstChild = value; |
} else { |
Bootstrap * cursor; |
cursor = parentBootstrap->firstChild; |
while (cursor->sibling != NULL) { |
cursor = cursor->sibling; |
} |
if (gDebug >= 1) { |
fprintf( |
stderr, |
" adding 0x%x [%p] as child of 0x%x [%p] after 0x%x [%p]\n", |
bootstrapPort, |
value, |
parentBootstrap->port, |
parentBootstrap, |
cursor->port, |
cursor |
); |
} |
cursor->sibling = value; |
} |
} |
} |
} |
// Finally, add it to the hash table. We do this last so that, if |
// anything above errors, we don't end up with junk in the hash. |
if (err == 0) { |
if (gDebug >= 1) { |
fprintf(stderr, " adding 0x%x [%p] to hash\n", bootstrapPort, value); |
} |
value->hashLink = context->portToBootstrapMap[hash]; |
context->portToBootstrapMap[hash] = value; |
if (value->hashLink != NULL) { |
context->hashCollisions += 1; |
} |
} else if (value != NULL) { |
// If we created a bootstrap, but failed to add it to a tree, |
// clean it up. |
assert(value->firstChild == NULL); |
assert(value->sibling == NULL); |
assert(value->hashLink == NULL); |
FreeBootstrap(context, value); |
value = NULL; |
} |
} |
// Clean up. We can release our reference to parentBootstrapPort because, |
// when we called ourselves recursively, the nested instance will have |
// taken its own reference. |
if (parentBootstrapPort != MACH_PORT_NULL) { |
krJunk = mach_port_deallocate(mach_task_self(), parentBootstrapPort); |
assert(krJunk == KERN_SUCCESS); |
} |
} |
// Return result to caller. |
if (err == 0) { |
*bootstrapPtr = value; |
} |
assert( (err == 0) == (*bootstrapPtr != NULL) ); |
assert( (*bootstrapPtr == NULL) || ((*bootstrapPtr)->port == bootstrapPort) ); |
return err; |
} |
static int AddProcessToBootstrapForest(BootstrapContext *context, const kinfo_proc *proc) |
// Adds a process to the bootstrap forest. This first finds (or creates) |
// the (Bootstrap *) associated with the process's bootstrap namespace, then |
// adds a reference to the process to the list of processes in that bootstrap. |
{ |
int err; |
kern_return_t kr; |
kern_return_t krJunk; |
task_t task; |
mach_port_t bootstrapPort; |
Bootstrap * bootstrap; |
assert(context != NULL); |
assert(context->procCount > 0); |
assert(context->portToBootstrapMap != NULL); |
assert(proc != NULL); |
assert(proc->kp_proc.p_pid > 0); |
task = MACH_PORT_NULL; |
bootstrapPort = MACH_PORT_NULL; |
err = 0; |
// Starting off in the kern_return_t error domain. |
// Get the bootstrap port for the process. |
kr = task_for_pid(mach_task_self(), proc->kp_proc.p_pid, &task); |
if ( true && (kr == KERN_FAILURE) ) { |
fflush(stdout); |
if ( geteuid() == 0 ) { |
fprintf( |
stderr, |
"%s: Could not get task control port for '%s' (%d)\n", |
getprogname(), |
proc->kp_proc.p_comm, |
(int) proc->kp_proc.p_pid |
); |
} else { |
fprintf( |
stderr, |
"%s: Could not get task control port for '%s' (%d); try again as root\n", |
getprogname(), |
proc->kp_proc.p_comm, |
(int) proc->kp_proc.p_pid |
); |
} |
err = ECANCELED; |
} |
if (kr == KERN_SUCCESS) { |
kr = task_get_bootstrap_port(task, &bootstrapPort); |
} |
// Switch to the errno error domain. |
if ( (kr != KERN_SUCCESS) && (err == 0) ) { |
err = EINVAL; |
} |
// Get or create a bootstrap context for this bootstrap port. |
if (err == 0) { |
err = GetOrAddBootstrap(context, bootstrapPort, &bootstrap); |
} |
// Add this process to the list of processes in that context. |
if (err == 0) { |
assert(bootstrap->port == bootstrapPort); |
if (gDebug >= 1) { |
fprintf( |
stderr, |
"pid %d has bootstrap 0x%x [%p]\n", |
(int) proc->kp_proc.p_pid, |
bootstrapPort, |
bootstrap |
); |
} |
// The bootstrap->procs array is always allocated with enough entries |
// to hold all of the processes, which is a colossal waste of memory |
// but makes things very easy. However, just to be sure, this assert |
// checks that we don't run past the end of the array. |
assert(bootstrap->procCount < context->procCount); |
bootstrap->procs[bootstrap->procCount] = proc; |
bootstrap->procCount += 1; |
} |
// Clean up. |
if (bootstrapPort != MACH_PORT_NULL) { |
// It is save to dispose bootstrapPort now because GetOrAddBootstrap |
// has added a reference count to it. |
krJunk = mach_port_deallocate(mach_task_self(), bootstrapPort); |
assert(krJunk == KERN_SUCCESS); |
} |
if (task != MACH_PORT_NULL) { |
krJunk = mach_port_deallocate(mach_task_self(), task); |
assert(krJunk == KERN_SUCCESS); |
} |
return err; |
} |
static void PrintBootstrapTree(Bootstrap *bootstrap, int indent, FILE *f) |
// Recursively print the bootstrap tree rooted at bootstrap. |
{ |
size_t procIndex; |
Bootstrap * cursor; |
assert(bootstrap != NULL); |
assert(f != NULL); |
// Print the header for this bootstrap. |
if (gDebug >= 1) { |
fprintf(f, "%*s0x%x [%p]\n", indent, "", bootstrap->port, bootstrap); |
} else { |
fprintf(f, "%*s0x%x\n", indent, "", bootstrap->port); |
} |
// Print the list of processes for this context. |
for (procIndex = 0; procIndex < bootstrap->procCount; procIndex++) { |
uid_t ruid; |
char ruidStr[32]; |
uid_t euid; |
char euidStr[32]; |
struct passwd * pw; |
euid = bootstrap->procs[procIndex]->kp_eproc.e_ucred.cr_uid; |
ruid = bootstrap->procs[procIndex]->kp_eproc.e_pcred.p_ruid; |
pw = getpwuid(euid); |
if (pw == NULL) { |
snprintf(euidStr, sizeof(euidStr), "%ld", (long) euid); |
} else { |
strlcpy(euidStr, pw->pw_name, sizeof(euidStr)); |
} |
if (euid == ruid) { |
fprintf( |
f, |
"%*s%s (%s, %ld->%ld)\n", |
indent + 2, "", |
bootstrap->procs[procIndex]->kp_proc.p_comm, |
euidStr, |
(long) bootstrap->procs[procIndex]->kp_proc.p_pid, |
(long) bootstrap->procs[procIndex]->kp_eproc.e_ppid |
); |
} else { |
pw = getpwuid(ruid); |
if (pw == NULL) { |
snprintf(ruidStr, sizeof(ruidStr), "%ld", (long) ruid); |
} else { |
strlcpy(ruidStr, pw->pw_name, sizeof(ruidStr)); |
} |
fprintf( |
f, |
"%*s%s (%s/%s, %ld->%ld)\n", |
indent + 2, "", |
bootstrap->procs[procIndex]->kp_proc.p_comm, |
euidStr, |
ruidStr, |
(long) bootstrap->procs[procIndex]->kp_proc.p_pid, |
(long) bootstrap->procs[procIndex]->kp_eproc.e_ppid |
); |
} |
} |
// If we're in debug mode, print the two lists in a compact format. |
if (gDebug >= 1) { |
fprintf(f, "%*schildren =", indent + 2, ""); |
cursor = bootstrap->firstChild; |
while (cursor != NULL) { |
fprintf(f, " 0x%x [%p]", cursor->port, cursor); |
cursor = cursor->sibling; |
} |
fprintf(f, "\n"); |
fprintf(f, "%*ssiblings =", indent + 2, ""); |
cursor = bootstrap->sibling; |
while (cursor != NULL) { |
fprintf(f, " 0x%x [%p]", cursor->port, cursor); |
cursor = cursor->sibling; |
} |
fprintf(f, "\n"); |
} |
// Recursively print any child contexts indented. |
if (bootstrap->firstChild != NULL) { |
PrintBootstrapTree(bootstrap->firstChild, indent + 2, f); |
} |
// Recursively print any sibling contexts at the same indent. |
if (bootstrap->sibling != NULL) { |
PrintBootstrapTree(bootstrap->sibling, indent, f); |
} |
} |
static void FreeBootstrap(BootstrapContext *context, Bootstrap *bootstrap) |
// Free a single bootstrap hash node. |
{ |
kern_return_t krJunk; |
assert(context != NULL); |
assert(bootstrap != NULL); |
if (bootstrap->port != MACH_PORT_NULL) { |
krJunk = mach_port_deallocate(mach_task_self(), bootstrap->port); |
assert(krJunk == KERN_SUCCESS); |
} |
free(bootstrap->procs); |
free(bootstrap); |
assert(context->bootstrapCount > 0); |
context->bootstrapCount -= 1; |
} |
static void FreeAllBootstraps(BootstrapContext *context) |
// Free all of the Bootstrap nodes referenced by the hash. We free via the |
// hash rather than the forest of trees because... well, in earlier versions |
// it was necessary because we didn't support more than one root, and this |
// approach is inherited from that. |
{ |
static const size_t kHashCount = (sizeof(context->portToBootstrapMap) / sizeof(context->portToBootstrapMap[0])); |
size_t hashIndex; |
Bootstrap * cursor; |
Bootstrap * bootstrap; |
for (hashIndex = 0; hashIndex < kHashCount; hashIndex++) { |
cursor = context->portToBootstrapMap[hashIndex]; |
while (cursor != NULL) { |
bootstrap = cursor; |
cursor = cursor->hashLink; |
FreeBootstrap(context, bootstrap); |
} |
} |
} |
static int ProcSorter(const void *p1, const void *p2) |
// Sort two (const kinfo_proc *) by their process ID. |
{ |
return ((const kinfo_proc *) p1)->kp_proc.p_pid - ((const kinfo_proc *) p2)->kp_proc.p_pid; |
} |
static void PrintRoots(const BootstrapContext *context, FILE *f) |
// Print the forest of bootstrap trees that represented by context. |
{ |
size_t rootIndex; |
assert(context != NULL); |
assert(f != NULL); |
for (rootIndex = 0; rootIndex < context->rootBootstrapCount; rootIndex++) { |
PrintBootstrapTree(context->rootBootstraps[rootIndex], 0, f); |
} |
} |
static int PrintBootstrapMap(FILE *f) |
// Print a forest of the bootstrap namespaces and the processes within each |
// namespace. Sneaky. |
{ |
int err; |
kinfo_proc * procs; |
size_t procCount; |
size_t procIndex; |
BootstrapContext context; |
// f may be NULL during leak testing |
procs = NULL; |
memset(&context, 0, sizeof(context)); |
// Get the list of processes. |
err = GetBSDProcessList(&procs, &procCount); |
// We sort the process list by pid (smallest to highest) at this point. |
// This has two effects: |
// |
// o Within a bootstrap, the processes are listed in pid order. |
// |
// o Within a bootstrap, the child bootstraps are sorted by the lowest |
// pid of the processes within each bootstrap. |
if (err == 0) { |
qsort(procs, procCount, sizeof(*procs), ProcSorter); |
} |
// Set up the forest context. |
if (err == 0) { |
context.procCount = procCount; |
context.rootBootstraps = (Bootstrap **) calloc(context.procCount, sizeof(*context.rootBootstraps)); |
if (context.rootBootstraps == NULL) { |
err = ENOMEM; |
} |
} |
// Get the bootstrap port for every process and place them into the forest. |
if (err == 0) { |
for (procIndex = 0; procIndex < procCount; procIndex++) { |
if (procs[procIndex].kp_proc.p_pid == 0) { |
// Skip process 0, which isn't a real, user-visible process. |
} else if (procs[procIndex].kp_proc.p_stat == SZOMB) { |
// Even though we print this message to stderr, we still don't |
// want to print it if there's no output specified. This happens |
// when we're leak testing, and without this check a single zombie |
// will result in a whole bunch of these messages. |
if (f != NULL) { |
fprintf( |
stderr, |
"%s: Skipping zombie process '%s' (%d)\n", |
getprogname(), |
procs[procIndex].kp_proc.p_comm, |
(int) procs[procIndex].kp_proc.p_pid |
); |
} |
} else { |
err = AddProcessToBootstrapForest(&context, &procs[procIndex]); |
} |
if ( (err == 0) && (f != NULL) && (gDebug >= 2) ) { |
PrintRoots(&context, stderr); |
} |
if (err != 0) { |
break; |
} |
} |
} |
// Print the resulting forest. |
if ( (err == 0) && (f != NULL) ) { |
PrintRoots(&context, f); |
if ( (gDebug >= 1) && (f != NULL) ) { |
fprintf(f, "hashCollisions = %zd\n", context.hashCollisions); |
} |
} |
// Clean up. |
FreeAllBootstraps(&context); |
assert(context.bootstrapCount == 0); |
free(context.rootBootstraps); |
free(procs); |
return err; |
} |
///////////////////////////////////////////////////////////////// |
#pragma mark ***** Bootstrap Namespace Dump |
static int GetDarwinOSRelease(int *majorPtr, int *minorPtr, int *bugPtr) |
// Get the major, minor and bug fix release version of the Darwin OS. |
{ |
int err; |
struct utsname names; |
int scanResult; |
static int sMajor; |
static int sMinor; |
static int sBug; |
// If we haven't already got the OS release, get it now. |
err = 0; |
if (sMajor == 0) { |
err = uname(&names); |
if (err < 0) { |
err = errno; |
} |
if (err == 0) { |
// Parse the three dot separated components of the release string. |
// If we don't get exactly three, we've confused and we error. |
scanResult = sscanf(names.release, "%d.%d.%d", &sMajor, &sMinor, &sBug); |
if (scanResult != 3) { |
err = EINVAL; |
} |
} |
} |
// Return it to our caller. |
if (err == 0) { |
if (majorPtr != NULL) { |
*majorPtr = sMajor; |
} |
if (minorPtr != NULL) { |
*minorPtr = sMinor; |
} |
if (bugPtr != NULL) { |
*bugPtr = sBug; |
} |
} |
return err; |
} |
static kern_return_t GetAndPrintNamespace( |
int indent, |
mach_port_t bootstrapPort, |
hash_table * existingNames |
) |
// Get and print the services listed by the bootstrap_info call in the namespace |
// denoted by bootstrapPort. Indent the printing by indent spaces. |
// For each service, check to see if the service name is listed in the |
// existingNames hash table. If it is, print that the service is hidden. |
// If it isn't, add the service to the table. |
{ |
kern_return_t kr; |
kern_return_t krJunk; |
name_array_t serviceNames; |
mach_msg_type_number_t serviceNameCount; |
name_array_t serverNames; |
mach_msg_type_number_t serverNameCount; |
bool_array_t active; |
mach_msg_type_number_t activeCount; |
unsigned int serviceIndex; |
bool activeOK; |
// Prepare for failure. |
serviceNames = NULL; |
serverNames = NULL; |
active = NULL; |
activeOK = true; |
// You shouldn't have to initialise these, but I do it to help me detect a bug |
// that I've described below. |
serviceNameCount = (mach_msg_type_number_t) -1; |
serverNameCount = (mach_msg_type_number_t) -1; |
activeCount = (mach_msg_type_number_t) -1; |
// Get the list of services in the namespace. |
kr = bootstrap_info( |
bootstrapPort, |
&serviceNames, |
&serviceNameCount, |
&serverNames, |
&serverNameCount, |
&active, |
&activeCount |
); |
if (kr == KERN_SUCCESS) { |
// Due to a bug on Mac OS X 10.5 and later <rdar://problem/5045620>, if |
// the activeCount isn't what we expect, just ignore the active array. |
if (serviceNameCount != serverNameCount) { |
kr = KERN_FAILURE; |
} else { |
activeOK = (serverNameCount == activeCount); |
if ( ! activeOK ) { |
static bool sHavePrinted; |
if ( ! sHavePrinted ) { |
fprintf(stderr, "*** Working around <rdar://problem/5045620>.\n"); |
fprintf(stderr, "*** Server and active information not available.\n"); |
sHavePrinted = true; |
} |
} |
} |
} |
// Print the service list. |
if (kr == KERN_SUCCESS) { |
for (serviceIndex = 0; serviceIndex < serviceNameCount; serviceIndex++) { |
const char * hiddenStr; |
// If we've seen this service before (that is, the name is in the |
// existingNames hash table) print a "(hidden)" suffix. Otherwise, |
// add the service to the table of services we've seen before. |
if ( hash_search(existingNames, serviceNames[serviceIndex], NULL, NULL) == NULL ) { |
void * newEntry; |
newEntry = hash_search(existingNames, serviceNames[serviceIndex], (void *) 1, NULL); |
assert(newEntry != NULL); |
hiddenStr = ""; |
} else { |
hiddenStr = " (hidden)"; |
} |
// Print stuff. |
if (activeOK) { |
const char *activeStr; |
switch (active[serviceIndex]) { |
case BOOTSTRAP_STATUS_INACTIVE: |
activeStr = " is inactive"; |
break; |
case BOOTSTRAP_STATUS_ACTIVE: |
activeStr = ""; // active is expected, so don't clutter up printout |
break; |
case BOOTSTRAP_STATUS_ON_DEMAND: |
activeStr = " on demand"; |
break; |
default: |
activeStr = " is unknown"; |
break; |
} |
if ( serverNames[serviceIndex][0] != 0 ) { |
fprintf( |
stdout, |
"%*s\"%s\" by \"%s\"%s%s\n", |
indent, |
"", |
serviceNames[serviceIndex], |
serverNames[serviceIndex], |
activeStr, |
hiddenStr |
); |
} else { |
fprintf( |
stdout, |
"%*s\"%s\"%s%s\n", |
indent, |
"", |
serviceNames[serviceIndex], |
activeStr, |
hiddenStr |
); |
} |
} else { |
// If the active array is broken, so is the server array. So just |
// print the service list. |
fprintf( |
stdout, |
"%*s\"%s\"%s\n", |
indent, |
"", |
serviceNames[serviceIndex], |
hiddenStr |
); |
} |
} |
} |
// Clean up. This isn't necessary for this tool (because the resources |
// will be cleaned up when we quit), but it's possible that someone might |
// cut'n'paste this code into a larger application, and we want to |
// demonstrate how to do the right thing. |
if (serviceNames != NULL) { |
krJunk = vm_deallocate( |
mach_task_self(), |
(vm_address_t) serviceNames, |
serviceNameCount * sizeof(*serviceNames) |
); |
assert(krJunk == 0); |
} |
if (serverNames != NULL) { |
krJunk = vm_deallocate( |
mach_task_self(), |
(vm_address_t) serverNames, |
serverNameCount * sizeof(*serverNames) |
); |
assert(krJunk == 0); |
} |
if ( (active != NULL) && activeOK ) { |
krJunk = vm_deallocate( |
mach_task_self(), |
(vm_address_t) active, |
activeCount * sizeof(*active) |
); |
assert(krJunk == 0); |
} |
return kr; |
} |
static int PrintBootstrapNamespaceForProcess(const char *procName, pid_t pid) |
// Print the bootstrap namespace for the process specified by pid. |
{ |
int err; |
int junk; |
kern_return_t kr; |
kern_return_t krJunk; |
mach_port_t task; |
mach_port_t bootstrapPort; |
int majorVersion = 0; // quieten warning |
assert(procName != NULL); |
// assert(pid > 0); // actually, called doesn't constrain pid |
// we'll just fail at the task_for_pid call |
// Prepare for failure. |
task = MACH_PORT_NULL; |
bootstrapPort = MACH_PORT_NULL; |
// This errno-style error code becomes the function result if we get a Mach |
// failure and it's not overridden by the code below. |
err = EINVAL; |
// Get the bootstrap port for the target process. |
kr = task_for_pid(mach_task_self(), pid, &task); |
if ( true && (kr == KERN_FAILURE) ) { |
fflush(stdout); |
if ( geteuid() == 0 ) { |
fprintf( |
stderr, |
"%s: Could not get task control port for '%s' (%d)\n", |
getprogname(), |
procName, |
pid |
); |
} else { |
fprintf( |
stderr, |
"%s: Could not get task control port for '%s' (%d); try again as root\n", |
getprogname(), |
procName, |
pid |
); |
} |
err = ECANCELED; |
} |
if (kr == KERN_SUCCESS) { |
kr = task_get_bootstrap_port(task, &bootstrapPort); |
} |
if (kr == KERN_SUCCESS) { |
hash_table * existingNames; |
junk = GetDarwinOSRelease(&majorVersion, NULL, NULL); |
assert(junk == 0); |
existingNames = hash_create(100); |
assert(existingNames != NULL); |
fprintf(stdout, "Bootstrap dump for '%s' (%d):\n", procName, (int) pid); |
kr = GetAndPrintNamespace(2, bootstrapPort, existingNames); |
// In Mac OS X 10.5 bootstrap_info returns only the services registered in a |
// particular namespace, not including the services from the parent namespaces |
// <rdar://problem/5279721>. So I iterate up the parent chain, dumping out |
// each namespace in turn. |
if ( (kr == KERN_SUCCESS) && (majorVersion >= 9) ) { // 9 == 10.5 |
bool done; |
int indent; |
done = false; |
indent = 2; |
do { |
mach_port_t parentPort; |
parentPort = MACH_PORT_NULL; |
kr = bootstrap_parent(bootstrapPort, &parentPort); |
if (kr == KERN_SUCCESS) { |
// If bootstrap_parent returned the same port then bootstrapPort |
// is the root namespace and we're done. |
done = (parentPort == bootstrapPort); |
// Release the current bootstrap port and substitute the parent |
// port. We do this here because, if we're done (that is, |
// parentPort == bootstrapPort), parentPort represents an extra |
// reference on the port, and we have to drop it. And we do |
// that by dropping the reference to bootstrapPort and assigning |
// parentPort to that, so that the cleanup code at the end of |
// the routine drops the second reference. |
krJunk = mach_port_deallocate(mach_task_self(), bootstrapPort); |
assert(krJunk == 0); |
bootstrapPort = parentPort; |
if ( ! done ) { |
indent += 2; |
kr = GetAndPrintNamespace(indent, bootstrapPort, existingNames); |
} |
} else { |
if ( (kr == BOOTSTRAP_NOT_PRIVILEGED) && (geteuid() != 0) ) { |
fprintf( |
stderr, |
"%s: Could not get parent bootstrap port; try again as root\n", |
getprogname() |
); |
err = ECANCELED; |
} |
break; |
} |
} while ( (kr == KERN_SUCCESS) && ! done ); |
} |
hash_purge(existingNames, NULL); |
free(existingNames->buckets); |
free(existingNames); |
} |
if (bootstrapPort != MACH_PORT_NULL) { |
krJunk = mach_port_deallocate(mach_task_self(), bootstrapPort); |
assert(krJunk == 0); |
} |
if (task != MACH_PORT_NULL) { |
krJunk = mach_port_deallocate(mach_task_self(), task); |
assert(krJunk == 0); |
} |
// Print the Mach error if appropriate. |
if (kr == KERN_SUCCESS) { |
err = 0; |
} else if (err != ECANCELED) { |
fflush(stdout); |
fprintf(stderr, "%s: %s (%#x)\n", getprogname(), mach_error_string(kr), kr); |
err = ECANCELED; |
} |
return err; |
} |
///////////////////////////////////////////////////////////////// |
#pragma mark ***** Main etc |
static void PrintUsage(void) |
{ |
fprintf(stderr, "usage: %s [ -d ] [ MAP | procName... ]\n", getprogname()); |
} |
int main(int argc, char ** argv) |
{ |
kern_return_t err; |
int retVal; |
int ch; |
int argIndex; |
// Parse common options. |
retVal = EXIT_SUCCESS; |
err = 0; |
do { |
ch = getopt(argc, argv, "d"); |
if (ch != -1) { |
switch (ch) { |
case 'd': |
gDebug += 1; |
break; |
case '?': |
default: |
retVal = EXIT_FAILURE; |
break; |
} |
} |
} while ( (ch != -1) && (retVal == EXIT_SUCCESS) ); |
// Process specific commands. |
if (retVal == EXIT_SUCCESS) { |
if ( (optind == (argc - 1)) && (strcmp(argv[optind], "MAP") == 0) ) { |
err = PrintBootstrapMap(stdout); |
} else if ((optind == (argc - 1)) && (strcmp(argv[1], "LEAKTEST") == 0) ) { |
int i; |
char junkStr[16]; |
for (i = 0; i < 1000; i++) { |
err = PrintBootstrapMap(NULL); |
assert(err == 0); |
} |
fprintf(stderr, "Press return to continue\n"); |
(void) fgets(junkStr, sizeof(junkStr), stdin); |
} else { |
if (optind == argc) { |
err = PrintBootstrapNamespaceForProcess("self", getpid()); |
} else { |
for (argIndex = optind; argIndex < argc; argIndex++) { |
pid_t pid; |
pid = -1; // quieten mysterious warning |
err = FindProcessByName(argv[argIndex], &pid); |
if (err == 0) { |
err = PrintBootstrapNamespaceForProcess(argv[argIndex], pid); |
} else { |
fflush(stdout); |
if (err == EEXIST) { |
fprintf( |
stderr, |
"%s: '%s' does not denote a unique process\n", |
getprogname(), |
argv[argIndex] |
); |
err = ECANCELED; |
} else { |
fprintf( |
stderr, |
"%s: Could not find process '%s'\n", |
getprogname(), |
argv[argIndex] |
); |
err = ECANCELED; |
} |
} |
if (err != 0) { |
break; |
} |
} |
} |
} |
} |
// Print errors |
if (retVal == EXIT_FAILURE) { |
if (err == 0) { |
PrintUsage(); |
} else if (err != ECANCELED) { |
fprintf(stderr, "%s: %s (%d)\n", getprogname(), strerror(err), err); |
} |
} |
return retVal; |
} |
Copyright © 2008 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2008-06-18