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
  • 1,945
  • 4
  • 26
  • 38

5 Answers5

17

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

% gcc -framework CoreServices -o getos main.c
user7116
  • 61,730
  • 16
  • 140
  • 170
  • @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
2

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,697
  • 32
  • 38
  • 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

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,380
  • 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

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, 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
  • 358
  • 4
  • 12
  • 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
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 into account (I haven't checked if this works on macOS 12):

#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
  • 1,937
  • 21
  • 19
  • 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