Recently someone in comp.sys.mac.programmer.help asked about code to find the system boot time on Mac OS X. Every so often I find a question like that and I just get the urge to figure it out.
He mentioned that he'd tried using getutxent(3) but that apparently it doesn't work on Mac OS X. I suspect this is because it relies on the user accounting database, and that this is not enabled. This approach might well have worked on Mac OS X Server.
This is the kind of thing that immediately suggests sysctl(3) to me, since sysctl knows all (or seems to). There's probably a solution buried somewhere in Carbon too, but a quick check at the command line confirmed I was on the right trail so I didn't look much farther:
$ sysctl -a | grep boot kern.exec: unknown type returned kern.boottime = Wed May 9 09:22:28 2007 kern.netboot = 0 kern.bootsignature: 48e4380a2edc32cb41fcd951c7d7882529f3bddf
Now to get it into code without requiring an external tool.
With sysctl() my first instinct is to use sysctlbyname(3) instead, since it's generally much easier to deal with. That's one of the methods SparklePlus uses to look up information about the host Mac-- the CPU type (Intel vs. PPC), CPU subtype (G5, G4, etc), CPU model (a unique string identifying the model of Mac), and number of CPUs are all looked up this way.
Since the sysctl(3) man page reveals that the KERN_BOOTTIME parameter is a struct timeval, this should be a simple matter of:
struct timeval boottime;
int error;
unsigned long length = sizeof(boottime);
error = sysctlbyname("kern.boottime", &boottime, &length,
NULL, 0);
For some reason this always fails-- error is -1 and errno is ENOENT, suggesting I've passed an invalid name. Since the sysctl command-line tool accepts kern.boottime, I don't know why that's a problem.
Oh well, if the shortcut doesn't work, there's always the hard(er) way. It's not especially difficult but it seems a bit arcane until you get used to it.
time_t boottime()
{
int mib[2] = { CTL_KERN, KERN_BOOTTIME };
char *value;
struct timeval boottime;
size_t size = sizeof(boottime);
if (sysctl(mib, 2, &boottime, &size, NULL, 0) == -1) {
perror("sysctl(kern.boottime)");
return -1;
}
printf("system boot time (seconds since epoch): %d\n",
boottime.tv_sec);
return boottime.tv_sec;
}
That's the short version, and as the printf indicates it gives you the boot time in terms of seconds since the epoch. From that you can convert it into whatever other format you like, so long as you remember when dealing with Unix the "epoch" is January 1, 1970 GMT, not the Mac OS X "reference date" of January 1 2001. A Cocoa developer might pass the result to NSDate's +dateWithTimeIntervalSince1970:, for example.
The above code could use some cleanup for production use, of course. However it should work on BSD systems other than Mac OS X.
As long as I'm on the subject I'll also include the longer approach to dealing with sysctl. This version looks up how much memory the sysctl result will need before actually getting that result. In this case I know the result is a struct timeval, so that's not necessary. In other cases you won't know the size requirements in advance. For example the hw.model key is a string of arbitrary length (probably up to a limit, but the limit's not documented).
The following does the same as the above, using this longer approach:
time_t boottime_long()
{
int mib[2] = { CTL_KERN, KERN_BOOTTIME };
char *value;
size_t size;
struct timeval boottime;
// Pass NULL the first time so we just get size
if (sysctl(mib, 2, NULL, &size, NULL, 0) == -1) {
perror("sysctl(kern.boottime)");
return -1;
}
if (size < sizeof(boottime)) {
perror("sizeof(kern.boottime) < sizeof(timeval)");
return -1;
}
// Allocate space for the result
value = malloc(size);
if (value == NULL) {
perror("malloc");
return -1;
}
// Now finally do the lookup
if (sysctl(mib, 2, value, &size, NULL, 0) == -1) {
perror("sysctl(kern.boottime)");
return -1;
}
memcpy(&boottime, value, sizeof(boottime));
free(value);
printf("system boot time (seconds since epoch): %d\n",
boottime.tv_sec);
return boottime.tv_sec;
}
It's a lot more steps, but as I said sometimes it's the only way.






Post new comment