#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <limits.h>
#include <sys/param.h>
#include <errno.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdio.h>
#include "hashset.h"
typedef struct {
hashset read, readdir, written, mkdir;
} rw_status;
enum last_symlink_handling {
look_for_file_or_directory, look_for_symlink
};
static inline char *flexible_realpath(const char *name,
rw_status *h,
enum last_symlink_handling lasth) {
char *rpath; char *dest; char *extra_buf = NULL, *buf = NULL;
const char *start, *end, *rpath_limit;
long int path_max;
int num_links = 0;
if (name == NULL) {
errno = (EINVAL);
return NULL;
}
if (name[0] == '\0') {
errno = (ENOENT);
return NULL;
}
path_max = PATH_MAX;
rpath = malloc(path_max);
if (rpath == NULL) return NULL;
rpath_limit = rpath + path_max;
if (name[0] != '/') {
rpath[0] = '\0';
goto error;
} else {
rpath[0] = '/';
dest = rpath + 1;
}
for (start = end = name; *start; start = end) {
struct stat st;
int n;
while (*start == '/') ++start;
for (end = start; *end && *end != '/'; ++end) {
;
}
if (end - start == 0) {
break;
} else if (end - start == 1 && start[0] == '.') {
;
} else if (end - start == 2 && start[0] == '.' && start[1] == '.') {
if (dest > rpath + 1) {
while ((--dest)[-1] != '/');
}
} else {
size_t new_size;
if (dest[-1] != '/') *dest++ = '/';
if (dest + (end - start) >= rpath_limit) {
ptrdiff_t dest_offset = dest - rpath;
char *new_rpath;
new_size = rpath_limit - rpath;
if (end - start + 1 > path_max) {
new_size += end - start + 1;
} else {
new_size += path_max;
}
new_rpath = (char *) realloc(rpath, new_size);
if (new_rpath == NULL) {
goto error;
}
rpath = new_rpath;
rpath_limit = rpath + new_size;
dest = rpath + dest_offset;
}
dest = mempcpy(dest, start, end - start);
*dest = '\0';
if ((!*end && lasth == look_for_symlink) || lstat(rpath, &st) < 0) {
st.st_mode = 0;
}
if (S_ISLNK(st.st_mode)) {
if (++num_links > MAXSYMLINKS) {
errno = (ELOOP);
goto error;
}
insert_hashset(&h->read, rpath);
if (!buf) buf = malloc(path_max);
size_t len;
n = readlink(rpath, buf, path_max - 1);
if (n < 0) goto error;
buf[n] = '\0';
if (!extra_buf) extra_buf = malloc(path_max);
len = strlen (end);
if ((long int) (n + len) >= path_max) {
errno = (ENAMETOOLONG);
goto error;
}
memmove(&extra_buf[n], end, len + 1);
name = end = memcpy(extra_buf, buf, n);
if (buf[0] == '/') {
dest = rpath + 1;
} else {
if (dest > rpath + 1) {
while ((--dest)[-1] != '/');
}
}
} else if (!S_ISDIR(st.st_mode) && *end != '\0') {
errno = (ENOTDIR);
goto error;
}
}
}
if (dest > rpath + 1 && dest[-1] == '/') --dest;
*dest = '\0';
if (buf) free(buf);
if (extra_buf) free(extra_buf);
return rpath;
error:
if (buf) free(buf);
if (extra_buf) free(extra_buf);
free(rpath);
return NULL;
}