Skip to content

Instantly share code, notes, and snippets.

@gocha
Last active June 30, 2016 04:59
Show Gist options
  • Save gocha/9f6a6bd322cc425b57b90350b8dc38e9 to your computer and use it in GitHub Desktop.
Save gocha/9f6a6bd322cc425b57b90350b8dc38e9 to your computer and use it in GitHub Desktop.
Expand path string with bash-style environment variable. (C11)
/**
* @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