18

I'm trying to get the version of Mac OS X programmatically in C. After searching for a while I tried this code:

#include <CoreServices/CoreServices.h>

int GetOS()
{
    SInt32 majorVersion,minorVersion,bugFixVersion;

    Gestalt(gestaltSystemVersionMajor, &majorVersion);
    Gestalt(gestaltSystemVersionMinor, &minorVersion);
    Gestalt(gestaltSystemVersionBugFix, &bugFixVersion);

    printf("Running on Mac OS X %d.%d.%d\n",majorVersion,minorVersion,bugFixVersion);    

    return 0;
}

XCode returns an LD error:

Undefined symbols for architecture x86_64: "_Gestalt", referenced from: _GetOS in main.o

What am I missing? How do you do this?

I found also this snippet

[[NSProcessInfo processInfo] operatingSystemVersionString]

But I have no idea how to write that in C.

Jessica
  • 2,005
  • 4
  • 28
  • 44

5 Answers5

18

Did you pass the appropriate framework to GCC in order to enable CoreServices?

% gcc -framework CoreServices -o getos main.c
user7116
  • 63,008
  • 17
  • 141
  • 172
  • @MelvinGuerrero Project settings, "General", pick the target, then scroll down to "Frameworks, Libraries and Embedded Content" and select "CoreServices.framework". – Thomas Perl Sep 14 '21 at 11:02
1

The code below should work in the foreseeable future for figuring out the current version of Mac Os X.

/*  McUsr put this together, and into public domain, 
    without any guarrantees about anything,
    but the statement that it works for me.
*/

#if 1 == 1
#define TESTING
#endif

#include <sys/param.h>
#include <sys/sysctl.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

struct osver {
    int minor;
    int sub;
} ;
typedef struct osver osxver ;
void macosx_ver(char *darwinversion, osxver *osxversion ) ;
char *osversionString(void) ;

#ifdef TESTING
int main( int argc, char *argv[] )
{
    osxver foundver;
    char *osverstr= NULL ;
    osverstr=osversionString() ;
    macosx_ver(osverstr, &foundver ) ;
    printf("Mac os x version = 10.%d.%d\n",foundver.minor,foundver.sub );
    free(osverstr);
    return 0;
}
#endif
char *osversionString(void) {
    int mib[2];
    size_t len;
    char *kernelVersion=NULL;
    mib[0] = CTL_KERN;
    mib[1] = KERN_OSRELEASE;

    if (sysctl(mib, 2, NULL, &len, NULL, 0) < 0 ) {
        fprintf(stderr,"%s: Error during sysctl probe call!\n",__PRETTY_FUNCTION__ );
        fflush(stdout);
        exit(4) ;
    }

    kernelVersion = malloc(len );
    if (kernelVersion == NULL ) {
        fprintf(stderr,"%s: Error during malloc!\n",__PRETTY_FUNCTION__ );
        fflush(stdout);
        exit(4) ;
    }
    if (sysctl(mib, 2, kernelVersion, &len, NULL, 0) < 0 ) {
        fprintf(stderr,"%s: Error during sysctl get verstring call!\n",__PRETTY_FUNCTION__ );
        fflush(stdout);
        exit(4) ;
    }

    return kernelVersion ;
}

void macosx_ver(char *darwinversion, osxver *osxversion ) {
/*
    From the book Mac Os X and IOS Internals:
    In version 10.1.1, Darwin (the core OS) was renumbered from v1.4.1 to 5.1,
    and since then has followed the OS X numbers consistently by being four
    numbers ahead of the minor version, and aligning its own minor with the
    sub-version.
*/
    char firstelm[2]= {0,0},secElm[2]={0,0};

    if (strlen(darwinversion) < 5 ) {
        fprintf(stderr,"%s: %s Can't possibly be a version string. Exiting\n",__PRETTY_FUNCTION__,darwinversion);
        fflush(stdout);
        exit(2);
    }
    char *s=darwinversion,*t=firstelm,*curdot=strchr(darwinversion,'.' );

    while ( s != curdot )
        *t++ = *s++;
    t=secElm ;
    curdot=strchr(++s,'.' );
    while ( s != curdot )
        *t++ = *s++;
    int maj=0, min=0;
    maj= (int)strtol(firstelm, (char **)NULL, 10);
    if ( maj == 0 && errno == EINVAL ) {
        fprintf(stderr,"%s Error during conversion of version string\n",__PRETTY_FUNCTION__);
        fflush(stdout);
        exit(4);
    }

    min=(int)strtol(secElm, (char **)NULL, 10);

    if ( min  == 0 && errno == EINVAL ) {
        fprintf(stderr,"%s: Error during conversion of version string\n",__PRETTY_FUNCTION__);
        fflush(stdout);
        exit(4);
    }
    osxversion->minor=maj-4;
    osxversion->sub=min;
}
McUsr
  • 1,400
  • 13
  • 10
  • This could be done with less work. But sysctl is the BSD function you want. http://www.unix.com/man-page/FreeBSD/3/sysctl/ – uchuugaka Jun 01 '13 at 09:15
  • 3
    You lost me at `#if 1 == 1`. Seriously, this much code doesn't constitute an answer, and there are such an abundance of sysctl selectors that you could just tell us which one is appropriate. – Potatoswatter Jun 01 '13 at 14:35
  • If uchuugaka knows how to do this with less work, then please do share! :) I have no idea what Potatoswatter mean by getting lost at 1 == 1, I use that to define the Testing Variable, it is less typing to change 1 to 0, and falsify the test. – McUsr Jun 04 '13 at 20:13
  • ..and then macOS 11 was released :) – Thomas Perl Sep 14 '21 at 10:31
1

Here is one with "less work", good enough for home projects (statically allocated buffers, ignoring errors). Works for me in OS X 10.11.1.

#include <stdio.h>

/*!
  @brief    Returns one component of the OS version
  @param    component  1=major, 2=minor, 3=bugfix
 */
int GetOSVersionComponent(int component) {
    char cmd[64] ;
    sprintf(
            cmd,
            "sw_vers -productVersion | awk -F '.' '{print $%d}'",
            component
    ) ;
    FILE* stdoutFile = popen(cmd, "r") ;

    int answer = 0 ;
    if (stdoutFile) {
        char buff[16] ;
        char *stdout = fgets(buff, sizeof(buff), stdoutFile) ;
        pclose(stdoutFile) ;
        sscanf(stdout, "%d", &answer) ;
    }

    return answer ;
}

int main(int argc, const char * argv[]) {
    printf(
           "Your OS version is: %d.%d.%d\n",
           GetOSVersionComponent(1),
           GetOSVersionComponent(2),
           GetOSVersionComponent(3)
           ) ;

    return 0 ;
}
Jerry Krinock
  • 4,860
  • 33
  • 39
  • 1
    This spawns at least 9 processes (sw_vers, awk and a shell for each of the 3 components), definitely not "less work" for the computer at runtime, and also not sure if "less" work than using the Gestalt functions. – Thomas Perl Sep 14 '21 at 11:04
1

Using the hint from @uchuugaka in the comment on the answer by @McUsr, I wrote a function that seems to work. I'm not saying it's better than any other answer.

/*
 * Structure for MacOS version number
 */
typedef struct macos_version_str
{
    ushort major;
    ushort minor;
    ushort point;
} macos_type;

/****************************************************************************
 *
 * Determine the MacOS version.
 *
 * Parameters:
 *    version_struct:  (pointer to) macos_version structure to be filled in.
 *
 * Return value:
 *    0: no error.
 *
 ****************************************************************************/

static int get_macos_version ( macos_type *version_struct )
{
    char    os_temp [20] = "";
    char   *os_temp_ptr  = os_temp;
    size_t  os_temp_len  = sizeof(os_temp);
    size_t  os_temp_left = 0;
    int     rslt         = 0;

    
    version_struct->major = 0;
    version_struct->minor = 0;
    version_struct->point = 0;
    
    rslt = sysctlbyname ( "kern.osproductversion", os_temp, &os_temp_len, NULL, 0 );
    if ( rslt != 0 )
    {
        fprintf ( stderr,
                  "sysctlbyname() returned %d error (%d): %s",
                  rslt, errno, strerror(errno));
        return ( rslt );
    }
    
    os_temp_left = os_temp_len; /* length of string returned */
    int temp = atoi ( os_temp_ptr );
    version_struct->major = temp;
    version_struct->major = atoi ( os_temp_ptr );
    
    while ( os_temp_left > 0 && *os_temp_ptr != '.' )
    {
        os_temp_left--;
        os_temp_ptr++;
    }
    os_temp_left--;
    os_temp_ptr++;
    version_struct->minor = atoi ( os_temp_ptr );
    
    while ( os_temp_left > 0 && *os_temp_ptr != '.' )
    {
        os_temp_left--;
        os_temp_ptr++;
    }
    os_temp_left--;
    os_temp_ptr++;
    version_struct->point = atoi ( os_temp_ptr );
    
    fprintf ( stderr, "Calculated OS Version: %d.%d.%d", version_struct->major, version_struct->minor, version_struct->point );
    
    if ( version_struct->major == 0 ||
         version_struct->minor == 0 ||
         version_struct->point == 0   )
    {
        fprintf ( stderr, "Unable to parse MacOS version string %s", os_temp );
        return ( -2 );
    }
    
    return 0;
}
jetset
  • 388
  • 4
  • 14
  • 1
    This works nicely. One thing, your `fprintf` after the `sysctlbyname` has 4 parameters but 3 expected in the format specifier. Remove the duplicate `errno`. – spartygw May 12 '22 at 19:25
  • Thanks for catching that, @spartygw – jetset Sep 19 '22 at 22:19
1

If for whatever reason you want to avoid the Gestalt API (which still works fine, but is deprecated), the macosx_deployment_target.c in cctools contains a code snippet that uses the CTL_KERN + KERN_OSRELEASE sysctl(), similar to other answers here.

Here's a small program adapted from that code and taking macOS 11 and newer (tested and verified with up to macOS 12.6, which was at time of updating this post the latest stable release) into account:

#include <stdio.h>
#include <sys/sysctl.h>

int main()
{
    char osversion[32];
    size_t osversion_len = sizeof(osversion) - 1;
    int osversion_name[] = { CTL_KERN, KERN_OSRELEASE };

    if (sysctl(osversion_name, 2, osversion, &osversion_len, NULL, 0) == -1) {
        printf("sysctl() failed\n");
        return 1;
    }

    uint32_t major, minor;
    if (sscanf(osversion, "%u.%u", &major, &minor) != 2) {
        printf("sscanf() failed\n");
        return 1;
    }

    if (major >= 20) {
        major -= 9;

        // macOS 11 and newer
        printf("%u.%u\n", major, minor);
    } else {
        major -= 4;

        // macOS 10.1.1 and newer
        printf("10.%u.%u\n", major, minor);
    }

    return 0;
}
Thomas Perl
  • 2,178
  • 23
  • 20
  • 1
    I'm on Mac 10.15.7 but this is printing 10.15.6. Is there another difference between the retail version <-> OS version mapping? – Vortico Sep 24 '21 at 11:35
  • I'm experiencing the same as @vortico all the way back on Mavericks. On OS X 10.9.5, it prints "10.9.4". – Wowfunhappy Sep 19 '22 at 22:32
  • Not sure if it's universal (don't have access to different patch versions of macOS 10.x.y), but based on these two comments, `printf("10.%u.%u\n", major, minor + 1);` might do the trick? – Thomas Perl Sep 21 '22 at 07:37