| /** |
| * @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 <sys/types.h> |
| #include <sys/stat.h> |
| |
| #include <stdbool.h> |
| #include <unistd.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <dirent.h> |
| |
| #include <netinet/in.h> |
| |
| #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; |
| } |