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;
}