Name: Anonymous 2012-06-19 6:00
Is it okay to use
goto in C for functions like this? (It's actually C++, but I'm doing C-style development.) Is this function too long, or is it okay due to the nature of null-terminated string processing? It's been a long time since I've done C-style development. Any other recommendations (except "use Lisp or XYZ language instead")?
ssize_t normalize_path(char* restrict dest, size_t dest_max, char const* restrict p, size_t n) noexcept {
size_t i, m, length;
ssize_t retval;
char const* q;
char* buffer, * state;
char const** parts, **new_parts;
size_t parts_size, new_parts_size;
char const* default_parts[32];
// validate arguments
if ((dest_max > (SSIZE_MAX + 1)) || (!dest && dest_max) || (!p && n)) {
errno = EINVAL;
return -1;
}
length = 0;
if (!n) {
// empty path, normalize to current directory
goto normalize_curdir;
}
if (*p == '/') {
// POSIX paths may begin with one or two slashes, but three or more
// are treated as a single slash
++length; ++p; --n;
if (n && (*p == '/')) {
++p; --n;
q = strncchr(p, '/', n);
if (!q) {
q = p + n;
}
if (q == p) {
++length;
}
else {
n -= q - p;
p = q;
}
}
// copy one or two slashes into destination buffer
for (i = 0, m = (length < dest_max) ? length : dest_max - 1; i < m; ++i) {
dest[i] = '/';
}
if (!n) {
goto null_terminate;
}
}
// create a local copy of input path for tokenization
buffer = static_cast<char*>(malloca(n + 1));
if (!buffer) {
return -1;
}
*static_cast<char*>(mempcpy(buffer, p, n)) = '\0';
// tokenize the local path and normalize parts into stack
i = 0;
retval = 0;
state = nullptr;
parts = default_parts;
parts_size = sizeof(default_parts) / sizeof(default_parts[0]);
for (q = strtok_r(buffer, path<char>::sepset, &state); q; q = strtok_r(nullptr, path<char>::sepset, &state)) {
// skip part if curdir, pop top part off stack if pardir, and if
// the path is absolute (length is non-zero), eat all of the
// redundant pardir parts
if (*q == '.') {
if (q[1] == '\0') {
continue;
}
else if ((q[1] == '.') && (q[2] == '\0')) {
if (i > 0) {
--i;
continue;
}
else if (length > 0) {
continue;
}
}
}
// resize the path parts stack if space is exhausted
if (i >= parts_size) {
if (parts_size >= (SIZE_MAX / (3 * sizeof(char const*)))) {
errno = ENOMEM;
retval = -1;
goto cleanup;
}
new_parts_size = (parts_size * 3) / 2;
new_parts = static_cast<char const**>(malloc(new_parts_size * sizeof(char const*)));
if (!new_parts) {
retval = -1;
goto cleanup;
}
memcpy(new_parts, parts, parts_size * sizeof(char const*));
if (parts != default_parts) {
free(parts);
}
parts = new_parts;
parts_size = new_parts_size;
}
// push part onto stack
parts[i++] = q;
}
// rejoin the path parts in normalized form
if (length < dest_max) {
retval = join_path(dest + length, dest_max - length, parts, i);
}
else {
retval = join_path(nullptr, 0, parts, i);
}
cleanup:
// free temporary buffers
if (parts != default_parts) {
free(parts);
}
freea(buffer);
// update length with full length from join
if (retval < 0) {
return -1;
}
length += static_cast<size_t>(retval);
if (!length) {
goto normalize_curdir;
}
else if (length > SSIZE_MAX) {
errno = EOVERFLOW;
return -1;
}
return static_cast<ssize_t>(length);
normalize_curdir:
// empty path, normalize to current directory
UP_ASSERT(!length);
++length;
if (dest_max > 0) {
dest[0] = '.';
}
null_terminate:
// null terminate the destination buffer
if (length < dest_max) {
dest[length] = '\0';
}
else if (dest_max > 0) {
dest[dest_max - 1] = '\0';
}
return static_cast<ssize_t>(length);
}