Skip to content

Instantly share code, notes, and snippets.

@tonyhutter
Created June 3, 2020 19:12
Show Gist options
  • Save tonyhutter/95994e020bdc0f44dba84207151df337 to your computer and use it in GitHub Desktop.
Save tonyhutter/95994e020bdc0f44dba84207151df337 to your computer and use it in GitHub Desktop.
/*
* This function works like realpath() but uses string manipulation only.
* No filesystem operations are done. It returns a resolved, absolute
* path to a file or directory, with no "..", "./" or "//" in it. It
* does not resolve symlinks.
*
* path: The path to a file or directory. It can be a relative or absolute
* path. If it's a relative path then basedir must also be specified.
*
* resolved_path: If non-NULL, then store the resulting string in this buffer
* which is assumed to be at least PATH_MAX in length. If NULL,
* then allocate and return the resulting string as the return
* value.
*
* basedir: An optional argument for the base directory that gets prepended
* if path is relative. For example, if path was "./file1" and
* basedir was "/var/tmp" then "/var/tmp/file1" would be returned.
* basedir must always be an absolute path.
*
* If both path and resolved_path both specified, and path is an absolute path,
* then resolved_path is ignored.
*
* If resolved_path is NULL, then allocate and return the resulting string as
* the return value. If resolved_path is specified, then return resolved_path.
* If there's an error, return NULL and set errno.
*/
static char *realpath_str(const char *path, char *resolved_path,
const char *basedir)
{
char *copy;
char *token, *save = NULL;
char *newpath;
if (!path) {
errno = EINVAL;
return NULL;
}
copy = calloc(1, PATH_MAX);
/* Path is relative, add in basedir */
if (path[0] != '/') {
if (basedir) {
if (basedir[0] == '/') {
printf("Adding basedir\n");
strcat(copy, basedir);
strcat(copy, "/");
} else {
printf("bad basedir\n");
/*
* We need to use basedir, but it was not
* an absolute path.
*/
free(copy);
errno = EINVAL;
return NULL;
}
} else {
/* They specified a relative path with no basedir */
free(copy);
errno = EINVAL;
return NULL;
}
}
strcat(copy, path);
newpath = calloc(1, PATH_MAX);
/*
* Iterate through our path, one token at a time.
*/
do {
if (!save) {
/* First token */
token = strtok_r(copy, "/", &save);
} else {
/* Rest of the tokens */
token = strtok_r(NULL, "/", &save);
}
if (!token)
break;
/* Skip "./" */
if (strcmp(token, ".") == 0) {
continue;
}
/* We got "..", erase the last directory entry */
if (strcmp(token, "..") == 0) {
/* Chomp off the last subdir from newpath */
dirname(newpath);
} else {
/* It's a normal directory/file, add it in */
strcat(newpath, "/");
strcat(newpath, token);
}
} while (1);
free(copy);
if (resolved_path) {
memcpy(resolved_path, newpath, strlen(newpath)+1);
free(newpath);
return resolved_path;
} else {
return newpath;
}
}
/*
* This is a recursive version of mkdir(). Everything is the same except
* it will create all the directories in 'path' if they don't exist (it's
* like a 'mkdir -p'). All directories will be created with the same mode.
*
* We assume 'path' is dynamically allocated, and we can manipulate it.
*/
static int __mkdir_recursive(char *path, mode_t mode)
{
DIR* dir = opendir(path);
char *tmp;
int rc;
if (dir) {
/* path already exists. */
closedir(dir);
return 0;
} else if (errno == ENOENT) {
errno = 0;
/*
* path does not currently exist. See if the subdir one level
* own exists, and if so, try to create the last directory of
* path.
*/
tmp = strdup(path);
/* Chomp off the last subdir */
dirname(tmp);
rc = __mkdir_recursive(tmp, mode);
if (rc == 0) {
/* The directory underneath exists */
rc = mkdir(path, mode);
}
free(tmp);
return rc;
} else {
/* Some other error, errno will already be set */
return -1;
}
}
/*
* This is a recursive version of mkdir(). Everything is the same except
* it will create all the directories in 'path' if they don't exist (it's
* like a 'mkdir -p'). All directories will be created with the same mode.
*/
static int mkdir_recursive(char *path, mode_t mode)
{
char *newpath;
int rc;
/*
* Convert our path into an absolute path (removing the "../" and "./"
* and "//)"). The absolute path is allocated as a new string and
* must be freed.
*/
newpath = realpath_str(path, NULL, NULL);
if (!newpath) {
rc = -1;
} else {
rc = __mkdir_recursive(path, mode);
}
free(newpath);
return rc;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment