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.
UTXplorer.c
/* |
File: UTXplorer.c |
Contains: A tool that demonstrates basic use of the <utmpx.h> API. |
Written by: DTS |
Copyright: Copyright (c) 2008 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 <getopt.h> |
#include <notify.h> |
#include <stdio.h> |
#include <stdlib.h> |
#include <stdbool.h> |
#include <string.h> |
#include <stdint.h> |
#include <utmpx.h> |
// IMPORTANT: |
// In portable code you can't assume that the utx types are contiguous, but |
// the values for Mac OS X are fixed for all time because of binary |
// compatibility so I'm going to exploit that in this program. Just to be |
// sure, I used UTXCommandsOK to do a quick check of this assumption. |
enum { |
kNumberOfUTXTypes = SHUTDOWN_TIME + 1 |
}; |
// Masks for the fieldMask field of UTXType. These indicate which fields |
// of the (struct utmpx) are valid for that type. |
enum { |
kUserMask = 1, |
kUIDMask = 2, |
kLineMask = 4, |
kPIDMask = 8, |
kTimeMask = 16, |
kHostMask = 32 |
}; |
// kUTXTypes describes useful features of the utx types that we understand. |
struct UTXType { |
bool listByDefault; // whether to display it by default |
const char * name; // the identifier |
uint32_t fieldMask; // which fields of the (struct utmpx) are valid |
}; |
typedef struct UTXType UTXType; |
static UTXType kUTXTypes[kNumberOfUTXTypes] = { |
{ false, "EMPTY", 0 }, |
{ true, "RUN_LVL", 0 }, |
{ true, "BOOT_TIME", kTimeMask }, |
{ true, "OLD_TIME", kTimeMask }, |
{ true, "NEW_TIME", kTimeMask }, |
{ true, "INIT_PROCESS", kUIDMask | kPIDMask | kTimeMask, }, |
{ true, "LOGIN_PROCESS", kUserMask | kUIDMask | kPIDMask | kTimeMask }, |
{ true, "USER_PROCESS", kUserMask | kUIDMask | kLineMask | kPIDMask | kTimeMask | kHostMask }, |
{ true, "DEAD_PROCESS", kUIDMask | kPIDMask | kTimeMask }, |
{ true, "ACCOUNTING", 0 }, |
{ true, "SIGNATURE", 0 }, |
{ true, "SHUTDOWN_TIME", kTimeMask } |
}; |
#if ! defined(NDEBUG) |
static bool UTXTypesOK(void) |
// Does a quick check of kUTXTypes to ensure that we're not in the weeds, |
// returning true if everything is OK. See the "IMPORTANT" note above. |
{ |
return ( strcmp(kUTXTypes[SHUTDOWN_TIME].name, "SHUTDOWN_TIME") == 0 ); |
} |
#endif |
// Field width constants for all of the output. |
enum { |
kTimeFieldWidth = 24, |
kTypeFieldWidth = 13, |
kIDFieldWidth = _UTX_IDSIZE, |
kPIDFieldWidth = 5, |
kUserFieldWidth = 16, |
kLineFieldWidth = 16 |
}; |
static void DoGetUTXEntCommand( |
bool wtmp, |
bool waitForMore, |
uint32_t verbose, |
const bool enabledTypes[] |
) |
// Prints all of the utx records in either utmp mode (records currently |
// active, wtmp == false) or wtmp mode (historical records, wtmp == true). |
// If waitForMode is true, it prints the current state and then waits, |
// printing new records as they arrive. verbose controls the level of |
// debugging output. enabledTypes, whose size is kNumberOfUTXTypes, |
// indicates which type of records should be displayed. |
{ |
int waitFD; |
uint32_t noteErr; |
int tokenRegistered; |
bool firstTime; |
struct timeval latestTime; |
struct utmpx * utx; |
bool enabled; |
const char * typeStr; |
uint32_t fieldMask; |
ssize_t bytesRead; |
int tokenRead; |
assert(enabledTypes != NULL); |
// If we've been told to wait for more, set up our notification file descriptor. |
waitFD = -1; |
if (waitForMore) { |
noteErr = notify_register_file_descriptor( |
UTMPX_CHANGE_NOTIFICATION, |
&waitFD, |
0, |
&tokenRegistered |
); |
assert(noteErr == NOTIFY_STATUS_OK); |
} |
// Print the header. |
fprintf( |
stdout, |
"%-*s %-*s %-*s %-*s %-*s %-*s %s\n", |
kTimeFieldWidth, "time", |
kTypeFieldWidth, "type", |
kIDFieldWidth, "id", |
kPIDFieldWidth, "pid", |
kUserFieldWidth, "user", |
kLineFieldWidth, "line", |
"host" |
); |
fprintf( |
stdout, |
"%-*s %-*s %-*s %-*s %-*s %-*s %s\n", |
kTimeFieldWidth, "----", |
kTypeFieldWidth, "----", |
kIDFieldWidth, "--", |
kPIDFieldWidth, "---", |
kUserFieldWidth, "----", |
kLineFieldWidth, "----", |
"----" |
); |
// By default getutxent_wtmp returns its values in reverse chronological order. |
// This isn't really what we want for this program, so we call setutxent_wtmp(1) |
// to get chronological order. |
// |
// By the way, while setutxent_wtmp is currently not documented <rdar://problem/5770912>, |
// it is a supported routine. |
if (wtmp) { |
setutxent_wtmp(1); |
} |
// The outer loop runs while we wait for more. |
// The inner loop runs for each entry returned by getutxent[_wtmp]. |
firstTime = true; |
timerclear(&latestTime); |
do { |
do { |
// Get the next database record. If we get none, the inner loop is done. |
if (wtmp) { |
utx = getutxent_wtmp(); |
} else { |
utx = getutxent(); |
} |
if (utx == NULL) { |
break; |
} |
// We do different things for each type of record. Extract the |
// information based on the record type. Do something sensible if |
// we get a record type we don't understand. |
if ( (utx->ut_type >= 0) && (utx->ut_type < kNumberOfUTXTypes) ) { |
enabled = enabledTypes[utx->ut_type]; |
typeStr = kUTXTypes[utx->ut_type].name; |
fieldMask = kUTXTypes[utx->ut_type].fieldMask; |
} else { |
enabled = true; |
typeStr = "<unknown>"; |
fieldMask = 0; |
} |
// If we're very verbose, ignore the default enabled setting. If we're |
// verbose but not very verbose, log events that we discarded because |
// they're the wrong type. |
if (verbose >= 2) { |
enabled = true; |
} |
if ( ! enabled && (verbose == 1) ) { |
fprintf(stdout, "*** %s event discarded because it is disabled\n", typeStr); |
} |
// Only print events without a time stamp the first time around. |
if ( enabled && (verbose < 2) && ! firstTime && ((fieldMask & kTimeMask) == 0) ) { |
if (verbose == 1) { |
fprintf(stdout, "*** %s event discarded because it has no time\n", typeStr); |
} |
enabled = false; |
} |
// If this isn't the first time around, discard any event that's |
// timestamped earlier than the last event we saw. |
if ( enabled && (verbose < 2) && ! firstTime && timercmp(&utx->ut_tv, &latestTime, <=) ) { |
if (verbose == 1) { |
fprintf( |
stdout, |
"*** %s event discarded because it's too old %ld.%d <= %ld.%d\n", |
typeStr, |
utx->ut_tv.tv_sec, utx->ut_tv.tv_usec, |
latestTime.tv_sec, latestTime.tv_usec |
); |
} |
enabled = false; |
} |
// In utmp mode, discard any DEAD_PROCESS events the first time around. |
// Without this utmp mode prints a DEAD_PROCESS event for each pseudo-tty |
// that's been used but is not currently used. Not helpful. |
if ( enabled && (verbose < 2) && firstTime && ! wtmp && (utx->ut_type == DEAD_PROCESS) ) { |
if (verbose == 1) { |
fprintf(stdout, "*** %s event discarded because it's not meaningful in this mode\n", typeStr); |
} |
enabled = false; |
} |
// Print the event if it's enabled. |
if (enabled) { |
if (fieldMask & kTimeMask) { |
char timeStr[kTimeFieldWidth + 1]; |
size_t timeStrLen; |
if ( timercmp(&utx->ut_tv, &latestTime, >) ) { |
latestTime = utx->ut_tv; |
if (verbose >= 2) { |
fprintf(stdout, "*** latest time %ld.%d\n", latestTime.tv_sec, latestTime.tv_usec); |
} |
} |
timeStrLen = strftime(timeStr, sizeof(timeStr), "%F %T %Z", localtime(&utx->ut_tv.tv_sec) ); |
assert(timeStrLen != 0); |
fprintf(stdout, "%-*s ", kTimeFieldWidth, timeStr); |
} else { |
fprintf(stdout, "%*s ", kTimeFieldWidth, ""); |
} |
fprintf(stdout, "%-*s ", kTypeFieldWidth, typeStr); |
if (fieldMask & kUIDMask) { |
fprintf(stdout, "%-*.*s ", kIDFieldWidth, kIDFieldWidth, utx->ut_id); |
} else { |
fprintf(stdout, "%*s ", kIDFieldWidth, ""); |
} |
if (fieldMask & kPIDMask) { |
fprintf(stdout, "%-*ld ", kPIDFieldWidth, (long) utx->ut_pid); |
} else { |
fprintf(stdout, "%*s ", kPIDFieldWidth, ""); |
} |
if (fieldMask & kUserMask) { |
fprintf(stdout, "%-*s ", kUserFieldWidth, utx->ut_user); |
} else { |
fprintf(stdout, "%*s ", kUserFieldWidth, ""); |
} |
if (fieldMask & kLineMask) { |
fprintf(stdout, "%-*s ", kLineFieldWidth, utx->ut_line); |
} else { |
fprintf(stdout, "%*s ", kLineFieldWidth, ""); |
} |
if (fieldMask & kHostMask) { |
fprintf(stdout, "%s", utx->ut_host); |
} |
fprintf(stdout, "\n"); |
} |
} while (true); |
// The inner loop is done; we're out of database entries. If we're not |
// in wait mode, leave now. |
if (waitFD == -1) { |
break; |
} |
// If we are in wait mode, wait. It's a little bogus that, if we |
// get an error waiting, we just discard that error. I could propagate the |
// error up to my caller, but the mechanics are a little tricky and |
// the current behaviour isn't truly toxic. |
fflush(stdout); |
if (verbose >= 2) { |
fprintf(stdout, "*** start waiting\n"); |
} |
bytesRead = read(waitFD, &tokenRead, sizeof(tokenRead)); |
if (verbose >= 2) { |
fprintf(stdout, "*** stop waiting\n"); |
} |
if (bytesRead == 0) { |
break; |
} else if (bytesRead < 0) { |
if (errno != EINTR) { |
break; |
} |
} else if (bytesRead != sizeof(tokenRead)) { |
assert(false); // not prepared to handle partial reads |
break; |
} else { |
// Have to swap to native endianness <rdar://problem/5352778>. |
assert( tokenRegistered == (int) ntohl(tokenRead) ); |
} |
// Set things up for the next loop. setutxent[_wtmp] reset the state |
// of the utx subsystem so that the next call to getutx[_wtmp] resumes |
// at the start of the log. |
firstTime = false; |
if (wtmp) { |
setutxent_wtmp(1); |
} else { |
setutxent(); |
} |
} while (true); |
} |
static void DoGetLastLogCommand(bool byName, const char *arg) |
// Implements the getlastlogx (byName == false) and getlastlogxbyname |
// (byName == true) commands. |
{ |
struct lastlogx * ll; |
char timeStr[kTimeFieldWidth + 1]; |
size_t timeStrLen; |
assert(arg != NULL); |
// Get the lastlogx record. |
if (byName) { |
ll = getlastlogxbyname(arg, NULL); |
} else { |
ll = getlastlogx((uid_t) (long) atol(arg), NULL); |
} |
// Print it. |
if (ll == NULL) { |
fprintf(stdout, "<none>\n"); |
} else { |
timeStrLen = strftime(timeStr, sizeof(timeStr), "%F %T %Z", localtime(&ll->ll_tv.tv_sec) ); |
assert(timeStrLen != 0); |
fprintf(stdout, "%-*s %-*s %s\n", kTimeFieldWidth, "time", kLineFieldWidth, "line", "host"); |
fprintf(stdout, "%-*s %-*s %s\n", kTimeFieldWidth, "----", kLineFieldWidth, "----", "----"); |
fprintf(stdout, "%-*s %-*s %s\n", kTimeFieldWidth, timeStr, kLineFieldWidth, ll->ll_line, ll->ll_host); |
} |
free(ll); |
} |
int main(int argc, char **argv) |
{ |
int err; |
int retVal; |
int ch; |
size_t typeIndex; |
assert(UTXTypesOK()); |
retVal = EXIT_SUCCESS; |
err = 0; |
if (argc == 1) { |
retVal = EXIT_FAILURE; |
} else { |
// Process the possible commands. |
if ( (strcasecmp(argv[1], "getutxent") == 0) || (strcasecmp(argv[1], "getutxent_wtmp") == 0) ) { |
bool enabledTypes[kNumberOfUTXTypes]; |
bool didSetType; |
uint32_t verbose; |
bool waitForMore; |
// The "getutxent" and "getutxent_wtmp" commands share a common set of options. |
// Clear out the values that store our options. |
for (typeIndex = 0; typeIndex < kNumberOfUTXTypes; typeIndex++) { |
enabledTypes[typeIndex] = false; |
} |
didSetType = false; |
verbose = 0; |
waitForMore = false; |
// Parse our command line options. |
optind = 2; // tell getopt to skip the command in argv[1] |
do { |
ch = getopt(argc, argv, "wvt:a"); |
if (ch != -1) { |
switch (ch) { |
case 'w': |
waitForMore += 1; |
break; |
case 'v': |
verbose += 1; |
break; |
case 't': |
didSetType = false; |
for (typeIndex = 0; typeIndex < kNumberOfUTXTypes; typeIndex++) { |
if ( strcasecmp(optarg, kUTXTypes[typeIndex].name) == 0 ) { |
enabledTypes[typeIndex] = true; |
didSetType = true; |
} |
} |
if ( ! didSetType ) { |
retVal = EXIT_FAILURE; |
break; |
} |
break; |
case 'a': |
for (typeIndex = 0; typeIndex < kNumberOfUTXTypes; typeIndex++) { |
enabledTypes[typeIndex] = true; |
} |
didSetType = true; |
break; |
case '?': |
default: |
retVal = EXIT_FAILURE; |
break; |
} |
} |
} while (ch != -1); |
// If we had no -t or -a options, default to all enabled. |
if ( ! didSetType ) { |
for (typeIndex = 0; typeIndex < kNumberOfUTXTypes; typeIndex++) { |
enabledTypes[typeIndex] = kUTXTypes[typeIndex].listByDefault; |
} |
} |
// Fail if we have extra arguments. |
if ( (retVal == EXIT_SUCCESS) && (optind != argc) ) { |
retVal = EXIT_FAILURE; |
} |
// Do the command. |
if (retVal == EXIT_SUCCESS) { |
DoGetUTXEntCommand( (strcasecmp(argv[1], "getutxent_wtmp") == 0), waitForMore, verbose, enabledTypes); |
} |
} else if ( (argc == 3) && (strcasecmp(argv[1], "getlastlogx") == 0) && (argv[2][0] != 0) ) { |
char * endPtr; |
(void) strtol(argv[2], &endPtr, 10); |
if (*endPtr == 0) { |
// strtol consumed all of the string, thus it's a valid number. |
DoGetLastLogCommand(false, argv[2]); |
} else { |
retVal = EXIT_FAILURE; |
} |
} else if ( (argc == 3) && (strcasecmp(argv[1], "getlastlogxbyname") == 0) && (argv[2][0] != 0) ) { |
DoGetLastLogCommand(true, argv[2]); |
} else { |
retVal = EXIT_FAILURE; |
} |
} |
// Report failure. |
if (retVal == EXIT_FAILURE) { |
if (err == 0) { |
fprintf(stderr, "usage: %s command [options] [arguments]\n", getprogname()); |
fprintf(stderr, " commands:\n"); |
fprintf(stderr, " getutxent [-v] [-w] [-a] [-t type]...\n"); |
fprintf(stderr, " getutxent_wtmp [-v] [-w] [-a] [-t type]...\n"); |
fprintf(stderr, " options:\n"); |
fprintf(stderr, " -v verbose\n"); |
fprintf(stderr, " -w wait for changes\n"); |
fprintf(stderr, " -a show all types of entries\n"); |
fprintf(stderr, " -t type show entries of this type; type values listed below;\n"); |
fprintf(stderr, " values marked with * are not shown by default\n"); |
for (typeIndex = 0; typeIndex < kNumberOfUTXTypes; typeIndex++) { |
fprintf(stderr, "%*s%s%s\n", 32, "", kUTXTypes[typeIndex].name, (kUTXTypes[typeIndex].listByDefault ? "" : " *")); |
} |
// fprintf(stderr, " getutxid type\n"); *** maybe someday in the future |
// fprintf(stderr, " getutxline tty\n"); |
fprintf(stderr, " getlastlogx uid\n"); |
fprintf(stderr, " getlastlogxbyname username\n"); |
} else if (err != ECANCELED) { |
fprintf(stderr, "%s: failed with error: %s (%d)\n", getprogname(), strerror(err), err); |
} |
} |
return retVal; |
} |
Copyright © 2008 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2008-03-19