/** * @file support/junction/path.c * @brief Encode and decode FedFS pathnames */ /* * Copyright 2010, 2011, 2018 Oracle. All rights reserved. * * This file is part of nfs-utils. * * nfs-utils is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2.0 as * published by the Free Software Foundation. * * nfs-utils is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License version 2.0 for more details. * * You should have received a copy of the GNU General Public License * version 2.0 along with nfs-utils. If not, see: * * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt */ #include #include #include #include #include #include #include #include #include #include #include #include "junction.h" #include "xlog.h" #define STRLEN_SLASH ((size_t)1) /* strlen("/") */ #define XDR_UINT_BYTES (sizeof(uint32_t)) /** * Compute count of XDR 4-octet units from byte count * * @param bytes number of bytes to convert * @return equivalent number of XDR 4-octet units */ static inline size_t nsdb_quadlen(size_t bytes) { return (bytes + 3) >> 2; } /** * Free array of NUL-terminated C strings * * @param strings array of char * to be released */ void nsdb_free_string_array(char **strings) { int i; if (strings == NULL) return; for (i = 0; strings[i] != NULL; i++) free(strings[i]); free(strings); } static FedFsStatus nsdb_alloc_zero_component_pathname(char ***path_array) { char **result; xlog(D_GENERAL, "%s: Zero-component pathname", __func__); result = (char **)calloc(1, sizeof(char *)); if (result == NULL) return FEDFS_ERR_SVRFAULT; result[0] = NULL; *path_array = result; return FEDFS_OK; } /** * Sanitize an incoming POSIX path * * @param pathname NUL-terminated C string containing a POSIX pathname * @return NUL-terminated C string containing sanitized path * * Caller must free the returned pathname with free(3). * * Remove multiple sequential slashes and any trailing slashes, * but leave "/" by itself alone. */ static __attribute_malloc__ char * nsdb_normalize_path(const char *pathname) { size_t i, j, len; char *result; len = strlen(pathname); if (len == 0) { xlog(D_CALL, "%s: NULL pathname", __func__); return NULL; } result = malloc(len + 1); if (result == NULL) return NULL; for (i = 0, j = 0; i < len; i++) { if (pathname[i] == '/' && pathname[i + 1] == '/') continue; result[j++] = pathname[i]; } result[j] = '\0'; if (j > 1 && result[j - 1] == '/') result[j - 1] = '\0'; xlog(D_CALL, "%s: result = '%s'", __func__, result); return result; } /** * Count the number of components in a POSIX pathname * * @param pathname NUL-terminated C string containing a POSIX pathname * @param len OUT: number of bytes the encoded XDR stream will consume * @param cnt OUT: component count * @return true when successful */ static _Bool nsdb_count_components(const char *pathname, size_t *len, unsigned int *cnt) { char *start, *component; unsigned int count; size_t length; /* strtok(3) will tromp on the string */ start = strdup(pathname); if (start == NULL) return false; length = XDR_UINT_BYTES; count = 0; component = start; for ( ;; ) { char *next; size_t tmp; if (*component == '/') component++; if (*component == '\0') break; next = strchrnul(component, '/'); tmp = (size_t)(next - component); if (tmp > 255) { free(start); return false; } length += XDR_UINT_BYTES + (nsdb_quadlen(tmp) << 2); count++; if (*next == '\0') break; component = next; } free(start); xlog(D_CALL, "%s: length = %zu, count = %u, path = '%s'", __func__, length, count, pathname); *len = length; *cnt = count; return true; } /** * Predicate: is input character set for a POSIX pathname valid UTF-8? * * @param pathname NUL-terminated C string containing a POSIX path * @return true if the string is valid UTF-8 * * XXX: implement this */ static _Bool nsdb_pathname_is_utf8(__attribute__((unused)) const char *pathname) { return true; } /** * Construct a local POSIX-style pathname from an array of component strings * * @param path_array array of pointers to NUL-terminated C strings * @param pathname OUT: pointer to NUL-terminated UTF-8 C string containing a POSIX-style path * @return a FedFsStatus code * * Caller must free the returned pathname with free(3). */ FedFsStatus nsdb_path_array_to_posix(char * const *path_array, char **pathname) { char *component, *result; unsigned int i, count; size_t length, len; if (path_array == NULL || pathname == NULL) return FEDFS_ERR_INVAL; if (path_array[0] == NULL) { xlog(D_GENERAL, "%s: Zero-component pathname", __func__); result = strdup("/"); if (result == NULL) return FEDFS_ERR_SVRFAULT; *pathname = result; return FEDFS_OK; } for (length = 0, count = 0; path_array[count] != NULL; count++) { component = path_array[count]; len = strlen(component); if (len == 0) { xlog(D_GENERAL, "%s: Zero-length component", __func__); return FEDFS_ERR_BADNAME; } if (len > NAME_MAX) { xlog(D_GENERAL, "%s: Component length too long", __func__); return FEDFS_ERR_NAMETOOLONG; } if (strchr(component, '/') != NULL) { xlog(D_GENERAL, "%s: Local separator character " "found in component", __func__); return FEDFS_ERR_BADNAME; } if (!nsdb_pathname_is_utf8(component)) { xlog(D_GENERAL, "%s: Bad character in component", __func__); return FEDFS_ERR_BADCHAR; } length += STRLEN_SLASH + len; if (length > PATH_MAX) { xlog(D_GENERAL, "%s: Pathname too long", __func__); return FEDFS_ERR_NAMETOOLONG; } } result = calloc(1, length + 1); if (result == NULL) return FEDFS_ERR_SVRFAULT; for (i = 0; i < count; i++) { strcat(result, "/"); strcat(result, path_array[i]); } *pathname = nsdb_normalize_path(result); free(result); if (*pathname == NULL) return FEDFS_ERR_SVRFAULT; return FEDFS_OK; } /** * Construct an array of component strings from a local POSIX-style pathname * * @param pathname NUL-terminated C string containing a POSIX-style pathname * @param path_array OUT: pointer to array of pointers to NUL-terminated C strings * @return a FedFsStatus code * * Caller must free "path_array" with nsdb_free_string_array(). */ FedFsStatus nsdb_posix_to_path_array(const char *pathname, char ***path_array) { char *normalized, *component, **result; unsigned int i, count; size_t length; if (pathname == NULL || path_array == NULL) return FEDFS_ERR_INVAL; if (!nsdb_pathname_is_utf8(pathname)) { xlog(D_GENERAL, "%s: Bad character in pathname", __func__); return FEDFS_ERR_BADCHAR; } normalized = nsdb_normalize_path(pathname); if (normalized == NULL) return FEDFS_ERR_SVRFAULT; if (!nsdb_count_components(normalized, &length, &count)) { free(normalized); return FEDFS_ERR_BADNAME; } if (count == 0) { free(normalized); return nsdb_alloc_zero_component_pathname(path_array); } result = (char **)calloc(count + 1, sizeof(char *)); if (result == NULL) { free(normalized); return FEDFS_ERR_SVRFAULT; } component = normalized; for (i = 0; ; i++) { char *next; if (*component == '/') component++; if (*component == '\0') break; next = strchrnul(component, '/'); length = (size_t)(next - component); if (length > 255) { nsdb_free_string_array(result); free(normalized); return FEDFS_ERR_SVRFAULT; } result[i] = strndup(component, length); if (result[i] == NULL) { free(normalized); nsdb_free_string_array(result); return FEDFS_ERR_SVRFAULT; } if (*next == '\0') break; component = next; } *path_array = result; free(normalized); return FEDFS_OK; }