I wrote the following C program to compare software version strings. Please provide any general feedback you'd like, but with special focus on error handling decisions, user input validation/sanitation, and robustness. My end-goal is for the program to properly handle user input and not have non-graceful crashes, etc... A couple of known issues/concerns I already have (but haven't decided on a solution for yet) is, where should I be handling errors? Should I be printing to `stderr` only in the driver (main) program and leave all of that out of the main "library" code? Secondly, I have an `enum` definitionn for `FAIL` but I never actually use it because when I wrote the `switch` clause, I thought it may be a better idea to just `default` to error rather than select it explicitly. I'm not sure if this is the best choice. This was compiled with GCC 7.5.0 for Linux 64-bit on my end.

The program is only intended to work with inputs with 4 version numbers concatenated with a `.` for now. I also need to build out a better driver which accepts actual user input eventually of course.

```
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

enum VERSION_RESULT
{
    FAIL = -1,
    LEFT = 1,
    RIGHT = 2,
    EQUAL = 3
};

typedef struct _version
{
    unsigned int major;
    unsigned int minor;
    unsigned int build;
    unsigned int revision;
} VERSION;

VERSION *version_new(char *str);
int version_parse(VERSION *pVer, char *str);
bool validate_version_string(char *str);
void version_print(VERSION *pVer);
void version_compare_print(VERSION *pL, VERSION *pR);

int main() {
    char ver_string[] = {'5','.','2','5','.','5','.','0','\0'}; // CANNOT use read-only memory due to strtok
    char ver_string2[] = {'5','.','5','.','5','.','1','\0'};
    VERSION *ver = version_new(ver_string);
    VERSION *ver2 = version_new(ver_string2);
    version_compare_print(ver, ver2);
    free(ver);
    free(ver2);
    ver = NULL;
    ver2 = NULL;
    return EXIT_SUCCESS;
}

VERSION *version_new(char *str)
{
    assert(str);

    VERSION *pVer = malloc(sizeof(VERSION));
    assert(pVer);
    int result = version_parse(pVer, str);
    if(result == -1)
    {
        fprintf(stderr, "Failed to parse version from string.\n");
        free(pVer);
        return NULL;
    }
    return pVer;
}

int version_parse(VERSION *pVer, char * str)
{
    assert(pVer && str);
    if(!validate_version_string(str))
    {
        fprintf(stderr, "Invalid version #. Format xxx.xxx.xxx.xxx\n");
        return -1;
    }
    char *token;
    char *delim = ".";
    token = strtok(str, delim);
    pVer->major = (unsigned int) atoi(token);
    token = strtok(NULL, delim);
    pVer->minor = (unsigned int) atoi(token);
    token = strtok(NULL, delim);
    pVer->build = (unsigned int) atoi(token);
    token = strtok(NULL, delim);
    pVer->revision = (unsigned int) atoi(token);

}

bool validate_version_string(char *str)
{
    assert(str);
    unsigned int count = 0;
    char *copy;
    char tmp;
    for(copy = str; *copy != '\0'; ++copy)
    {
        tmp = *copy;
        if(tmp == '.') ++count;
        if((tmp > '9' || tmp < '0') && tmp != '.') return false;
    }
    if(count == 3) return true;
    return false;
}

void version_print(VERSION *pVer)
{
    assert(pVer);
    printf("%u.%u.%u.%u", pVer->major,pVer->minor,pVer->build,pVer->revision);
    return;
}

/*
 * Compare 2 versions. Return -1 if error, 1 if left is largest, 2 if right is largest, 3 if equal
 */
enum VERSION_RESULT version_compare(VERSION *pL, VERSION *pR)
{
    assert(pL && pR);

    //Major checks
    if(pL->major > pR->major) return LEFT;
    if(pR->major > pL->major) return RIGHT;
    //Majors are equal
    if(pL->minor > pR->minor) return LEFT;
    if(pR->minor > pL->minor) return RIGHT;
    //Minors are equal
    if(pL->build > pR->build) return LEFT;
    if(pR->build > pL->build) return RIGHT;
    //Builds are equal
    if(pL->revision > pR->revision) return LEFT;
    if(pR->revision > pL->revision) return RIGHT;
    //Revisions are equal
    return EQUAL;
}
void version_compare_print(VERSION *pL, VERSION *pR)
{
    assert(pL && pR);

    switch(version_compare(pL, pR))
    {
        case LEFT:
            version_print(pL);
            printf(" > ");
            version_print(pR);
            putchar('\n');
            break;
        case RIGHT:
            version_print(pL);
            printf(" < ");
            version_print(pR);
            putchar('\n');
            break;
        case EQUAL:
            version_print(pL);
            printf(" == ");
            version_print(pR);
            putchar('\n');
            break;
        default:
            fprintf(stderr, "An error occurred in the version compare function.\n");
    }
    return;
}

```