Last active
June 30, 2016 04:59
-
-
Save gocha/9f6a6bd322cc425b57b90350b8dc38e9 to your computer and use it in GitHub Desktop.
Expand path string with bash-style environment variable. (C11)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @file | |
* Expand path string with bash-style environment variable. (C11) | |
*/ | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <ctype.h> | |
#include <string.h> | |
#include <errno.h> | |
/** | |
* Expand environment variable directives. | |
* @param required_size pointer to a user-provided location where expand_env will store the length of the output string. | |
* @param buffer pointer to a user-provided character array where expand_env will store the contents of the output string. | |
* @param buffer_size the size of the output buffer. | |
* @param src the string to be expanded. | |
* @return 0 if succeeded. ERANGE if buffer_size is too small. | |
*/ | |
errno_t expand_env(size_t *required_size, char *buffer, size_t buffer_size, const char *src) { | |
size_t dest_offset = 0; | |
size_t src_offset = 0; | |
size_t dst_length = 0; | |
size_t len; | |
char *prefix; | |
char *varname = NULL; | |
size_t varname_capacity = 0; | |
size_t varname_length; | |
errno_t err; | |
/* Check arguments. */ | |
if (src == NULL) { | |
return EINVAL; | |
} | |
/* Find each variables from the beginning. */ | |
while ((prefix = strchr(&src[src_offset], '$')) != NULL) { | |
/* Copy the substring before the variable found. */ | |
len = prefix - &src[src_offset]; | |
if (dst_length + len < buffer_size) { | |
if (buffer != NULL) { | |
err = strncpy_s(&buffer[dst_length], buffer_size - dst_length, &src[src_offset], len); | |
if (err != 0) { | |
free(varname); | |
return err; | |
} | |
} | |
} | |
dst_length += len; | |
src_offset += len; | |
/* Check the next character. */ | |
if (*(prefix + 1) == '{') { | |
/* Expand a variable with parenthesis. ${VAR} */ | |
char *parenthesis = strpbrk(prefix + 2, "{}"); | |
if (parenthesis != NULL) { | |
if (*parenthesis == '{') { | |
/* Invalid nested parenthesis. */ | |
free(varname); | |
return EINVAL; | |
} | |
varname_length = parenthesis - (prefix + 2); | |
/* Extract the variable name. */ | |
if (varname == NULL || varname_length + 1 > varname_capacity) { | |
/* Allocate buffer for variable name if not available. */ | |
char *varname_new; | |
varname_capacity = varname_length + 1; | |
varname_new = (char *)realloc(varname, sizeof(char) * varname_capacity); | |
if (varname_new == NULL) { | |
free(varname); | |
return errno; | |
} | |
varname = varname_new; | |
} | |
strncpy_s(varname, varname_length + 1, prefix + 2, varname_length); | |
/* Check if the variable exists. */ | |
err = getenv_s(&len, NULL, 0, varname); | |
if (err != 0) { | |
free(varname); | |
return err; | |
} | |
if (len != 0) { | |
/* Variable found: Write the value to the buffer. */ | |
if (dst_length + len <= buffer_size) { | |
if (buffer != NULL) { | |
err = getenv_s(&len, &buffer[dst_length], buffer_size - dst_length, varname); | |
if (err != 0) { | |
free(varname); | |
return err; | |
} | |
} | |
} | |
dst_length += len - 1; | |
} | |
else { | |
/* Variable not found: Do regular copy. */ | |
len = 2 + varname_length + 1; | |
if (dst_length + len < buffer_size) { | |
if (buffer != NULL) { | |
err = strncpy_s(&buffer[dst_length], buffer_size - dst_length, &src[src_offset], len); | |
if (err != 0) { | |
free(varname); | |
return err; | |
} | |
} | |
} | |
dst_length += len; | |
} | |
src_offset += 2 + varname_length + 1; | |
} | |
else { | |
/* Invalid parenthesis. */ | |
free(varname); | |
return EINVAL; | |
} | |
} | |
else { | |
/* Expand a variable without parenthesis. */ | |
if (isdigit(*(prefix + 1)) != 0) { | |
/* Numbered variable. $[0-9]: Do regular copy. */ | |
len = 2; | |
if (dst_length + len < buffer_size) { | |
if (buffer != NULL) { | |
err = strncpy_s(&buffer[dst_length], buffer_size - dst_length, &src[src_offset], len); | |
if (err != 0) { | |
free(varname); | |
return err; | |
} | |
} | |
} | |
dst_length += len; | |
src_offset += len; | |
} | |
else { | |
/* Anything else. $VAR */ | |
varname_length = strspn(prefix + 1, "_0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); | |
/* Extract the variable name. */ | |
if (varname == NULL || varname_length + 1 > varname_capacity) { | |
/* Allocate buffer for variable name if not available. */ | |
char *varname_new; | |
varname_capacity = varname_length + 1; | |
varname_new = (char *)realloc(varname, sizeof(char) * varname_capacity); | |
if (varname_new == NULL) { | |
free(varname); | |
return errno; | |
} | |
varname = varname_new; | |
} | |
strncpy_s(varname, varname_length + 1, prefix + 1, varname_length); | |
/* Check if the variable exists. */ | |
err = getenv_s(&len, NULL, 0, varname); | |
if (err != 0) { | |
free(varname); | |
return err; | |
} | |
if (len != 0) { | |
/* Write the value to the buffer. */ | |
if (dst_length + len <= buffer_size) { | |
if (buffer != NULL) { | |
err = getenv_s(&len, &buffer[dst_length], buffer_size - dst_length, varname); | |
if (err != 0) { | |
free(varname); | |
return err; | |
} | |
} | |
} | |
dst_length += len - 1; | |
} | |
else { | |
/* Variable not found. Do regular copy. */ | |
len = 1 + varname_length; | |
if (dst_length + len < buffer_size) { | |
if (buffer != NULL) { | |
err = strncpy_s(&buffer[dst_length], buffer_size - dst_length, &src[src_offset], len); | |
if (err != 0) { | |
free(varname); | |
return err; | |
} | |
} | |
} | |
dst_length += len; | |
} | |
src_offset += 1 + varname_length; | |
} | |
} | |
} | |
free(varname); | |
/* Copy the rest of input string. */ | |
len = strlen(&src[src_offset]); | |
if (dst_length + len < buffer_size) { | |
err = strncpy_s(&buffer[dst_length], buffer_size - dst_length, &src[src_offset], buffer_size - dst_length - 1); | |
if (err != 0) { | |
return err; | |
} | |
} | |
dst_length += len; | |
/* Return the final buffer size if requested. */ | |
if (required_size != NULL) { | |
*required_size = dst_length + 1; | |
} | |
/* When buffer is too small */ | |
if (dst_length >= buffer_size) { | |
if (buffer != NULL) { | |
/* and if buffer is not null, clear the content and return ERANGE. */ | |
if (buffer_size > 0) { | |
buffer = '\0'; | |
} | |
return ERANGE; | |
} | |
} | |
return 0; | |
} | |
/** | |
* Main function. | |
* @param argc Number of arguments. | |
* @param argv Argument vector. | |
* @return exit status. | |
*/ | |
int main(int argc, char * argv[]) { | |
char *buffer = NULL; | |
size_t buffer_size; | |
char what[512]; | |
errno_t err; | |
if (argc != 2) { | |
puts("Wrong number of arguments."); | |
return 1; | |
} | |
/* Calculate the buffer size first. */ | |
err = expand_env(&buffer_size, NULL, 0, argv[1]); | |
if (err != 0) { | |
puts(strerror(err)); | |
return 1; | |
} | |
printf("Buffer size: %u\n\n", buffer_size); | |
/* Allocate the buffer. */ | |
buffer = (char *)malloc(buffer_size); | |
if (buffer == NULL) { | |
puts(strerror(err)); | |
return 1; | |
} | |
/* Expand variables in the input string. */ | |
err = expand_env(NULL, buffer, buffer_size, argv[1]); | |
if (err != 0) { | |
puts(strerror(err)); | |
return 1; | |
} | |
puts(buffer); | |
free(buffer); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment