| /*---------------------------------------------------------------------------* |
| * PFileSystem.c * |
| * * |
| * Copyright 2007, 2008 Nuance Communciations, Inc. * |
| * * |
| * Licensed under the Apache License, Version 2.0 (the 'License'); * |
| * you may not use this file except in compliance with the License. * |
| * * |
| * You may obtain a copy of the License at * |
| * http://www.apache.org/licenses/LICENSE-2.0 * |
| * * |
| * Unless required by applicable law or agreed to in writing, software * |
| * distributed under the License is distributed on an 'AS IS' BASIS, * |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * |
| * See the License for the specific language governing permissions and * |
| * limitations under the License. * |
| * * |
| *---------------------------------------------------------------------------*/ |
| |
| #include "ArrayList.h" |
| #include "LCHAR.h" |
| #include "PFileSystem.h" |
| #include "PFileSystemImpl.h" |
| #include "phashtable.h" |
| #include "plog.h" |
| #include "pmemory.h" |
| |
| |
| #define MTAG NULL |
| |
| /** |
| * Indicates if PFileSystem is initialized. |
| */ |
| extern ESR_BOOL PFileSystemCreated; |
| |
| /** |
| * [file path, PFileSystem*] mapping. |
| */ |
| extern PHashTable* PFileSystemPathMap; |
| |
| /** |
| * Current working directory. |
| */ |
| extern LCHAR PFileSystemCurrentDirectory[P_PATH_MAX]; |
| |
| PORTABLE_API ESR_ReturnCode PFileSystemCanonicalSlashes(LCHAR* path) |
| { |
| ESR_ReturnCode rc; |
| |
| if (path == NULL) |
| { |
| rc = ESR_INVALID_ARGUMENT; |
| PLogError(ESR_rc2str(rc)); |
| goto CLEANUP; |
| } |
| |
| lstrtrim(path); |
| CHKLOG(rc, lstrreplace(path, L('\\'), L('/'))); |
| return ESR_SUCCESS; |
| CLEANUP: |
| return rc; |
| } |
| |
| ESR_ReturnCode PFileSystemLinearToPathTokens(const LCHAR* path, LCHAR*** tokenArray, size_t* count) |
| { |
| ESR_ReturnCode rc; |
| const LCHAR* beginning; |
| const LCHAR* ending; |
| LCHAR linear[P_PATH_MAX]; |
| ArrayList* arrayList = NULL; |
| LCHAR* value = NULL; |
| size_t i; |
| |
| if (path == NULL || tokenArray == NULL || count == NULL) |
| { |
| rc = ESR_INVALID_ARGUMENT; |
| PLogError(ESR_rc2str(rc)); |
| goto CLEANUP; |
| } |
| LSTRCPY(linear, path); |
| CHKLOG(rc, PFileSystemCanonicalSlashes(linear)); |
| CHKLOG(rc, ArrayListCreate(&arrayList)); |
| beginning = linear; |
| while (ESR_TRUE) |
| { |
| ending = LSTRCHR(beginning, L('/')); |
| if (ending == NULL) |
| ending = beginning + LSTRLEN(beginning); |
| value = MALLOC(sizeof(LCHAR) * (ending - beginning + 1 + 1), MTAG); |
| if (value == NULL) |
| { |
| rc = ESR_OUT_OF_MEMORY; |
| PLogError(ESR_rc2str(rc)); |
| goto CLEANUP; |
| } |
| LSTRNCPY(value, beginning, ending - beginning + 1); |
| value[ending-beginning+1] = L('\0'); |
| CHKLOG(rc, lstrtrim(value)); |
| if (LSTRLEN(value) == 0) |
| { |
| FREE(value); |
| value = NULL; |
| } |
| else |
| { |
| CHKLOG(rc, arrayList->add(arrayList, value)); |
| value = NULL; |
| } |
| if (*ending == 0) |
| break; |
| beginning = ending + 1; |
| } |
| |
| /* Build static token array */ |
| CHKLOG(rc, arrayList->getSize(arrayList, count)); |
| *tokenArray = MALLOC(*count * sizeof(LCHAR*), MTAG); |
| if (*tokenArray == NULL) |
| { |
| rc = ESR_OUT_OF_MEMORY; |
| goto CLEANUP; |
| } |
| for (i = 0; i < *count; ++i) |
| { |
| rc = arrayList->get(arrayList, i, (void**)(&(*tokenArray)[i])); |
| if (rc != ESR_SUCCESS) |
| goto CLEANUP; |
| } |
| rc = arrayList->destroy(arrayList); |
| if (rc != ESR_SUCCESS) |
| goto CLEANUP; |
| return ESR_SUCCESS; |
| CLEANUP: |
| FREE(value); |
| if (arrayList != NULL) |
| { |
| ESR_ReturnCode cleanRC; |
| |
| cleanRC = arrayList->getSize(arrayList, count); |
| if (cleanRC != ESR_SUCCESS) |
| return rc; |
| for (i = 0; i < *count; ++i) |
| { |
| cleanRC = arrayList->get(arrayList, 0, (void**)&value); |
| if (cleanRC != ESR_SUCCESS) |
| return rc; |
| FREE(value); |
| cleanRC = arrayList->remove(arrayList, 0); |
| if (cleanRC != ESR_SUCCESS) |
| return rc; |
| } |
| arrayList->destroy(arrayList); |
| } |
| return rc; |
| } |
| |
| ESR_ReturnCode PFileSystemIsAbsolutePath(const LCHAR* path, ESR_BOOL* isAbsolute) |
| { |
| LCHAR canonical[P_PATH_MAX]; |
| ESR_ReturnCode rc; |
| |
| if (isAbsolute == NULL) |
| { |
| rc = ESR_INVALID_ARGUMENT; |
| PLogError(ESR_rc2str(rc)); |
| goto CLEANUP; |
| } |
| LSTRCPY(canonical, path); |
| CHKLOG(rc, PFileSystemCanonicalSlashes(canonical)); |
| |
| *isAbsolute = (canonical[0] == '/' || |
| (LISALPHA(canonical[0]) && canonical[1] == ':' && canonical[2] == '/')); |
| return ESR_SUCCESS; |
| CLEANUP: |
| return rc; |
| } |
| |
| ESR_ReturnCode PFileSystemGetAbsolutePath(LCHAR* path, size_t* len) |
| { |
| ESR_ReturnCode rc; |
| #define MAX_PATH_TOKENS 20 |
| LCHAR** tokens = NULL; |
| size_t tokenLen = 0, i; |
| ESR_BOOL isAbsolute; |
| |
| if (path == NULL || len == NULL) |
| { |
| rc = ESR_INVALID_ARGUMENT; |
| PLogError(ESR_rc2str(rc)); |
| goto CLEANUP; |
| } |
| CHKLOG(rc, PFileSystemIsAbsolutePath(path, &isAbsolute)); |
| |
| /* Prefix relative paths with the current working directory */ |
| if (!isAbsolute) |
| { |
| LCHAR cwd[P_PATH_MAX]; |
| size_t len2; |
| |
| len2 = P_PATH_MAX; |
| CHKLOG(rc, PFileSystemGetcwd(cwd, &len2)); |
| len2 = *len; |
| CHKLOG(rc, lstrinsert(cwd, path, 0, &len2)); |
| } |
| |
| CHKLOG(rc, PFileSystemCanonicalSlashes(path)); |
| tokenLen = MAX_PATH_TOKENS; |
| CHKLOG(rc, PFileSystemLinearToPathTokens(path, &tokens, &tokenLen)); |
| |
| LSTRCPY(path, L("")); |
| for (i = 0; i < tokenLen; ++i) |
| { |
| if (LSTRCMP(tokens[i], L("../")) == 0) |
| { |
| size_t len2; |
| |
| len2 = *len; |
| passert(path[LSTRLEN(path)-1] == L('/')); |
| CHKLOG(rc, PFileSystemGetParentDirectory(path, &len2)); |
| } |
| else if (LSTRCMP(tokens[i], L("./")) == 0) |
| { |
| if (i > 0) |
| { |
| /* do nothing */ |
| } |
| else |
| { |
| LSTRCPY(path, L("./")); |
| } |
| } |
| else |
| LSTRCAT(path, tokens[i]); |
| FREE(tokens[i]); |
| tokens[i] = NULL; |
| } |
| FREE(tokens); |
| return ESR_SUCCESS; |
| CLEANUP: |
| if (tokens != NULL) |
| { |
| for (i = 0; i < tokenLen; ++i) |
| { |
| FREE(tokens[i]); |
| tokens[i] = NULL; |
| } |
| } |
| return rc; |
| } |
| |
| ESR_ReturnCode PFileSystemIsCreated(ESR_BOOL* isCreated) |
| { |
| ESR_ReturnCode rc; |
| |
| if (isCreated == NULL) |
| { |
| rc = ESR_INVALID_ARGUMENT; |
| PLogError(ESR_rc2str(rc)); |
| goto CLEANUP; |
| } |
| *isCreated = PFileSystemCreated; |
| return ESR_SUCCESS; |
| CLEANUP: |
| return rc; |
| } |
| |
| /** |
| * Given a path, returns the associated file-system and relative path. |
| * |
| * @param path Path to look up |
| * @param fs [out] File-system which matches the path |
| * @param relativePath [out] Relative path associated with match (should have length P_PATH_MAX) |
| */ |
| ESR_ReturnCode PFileSystemGetFS(const LCHAR* path, PFileSystem** fileSystem, LCHAR* relativePath) |
| { |
| ESR_ReturnCode rc; |
| PHashTableEntry* entry; |
| LCHAR* key; |
| PFileSystem* value; |
| LCHAR* bestKey = NULL; |
| PFileSystem* bestValue = NULL; |
| |
| CHKLOG(rc, PHashTableEntryGetFirst(PFileSystemPathMap, &entry)); |
| while (entry != NULL) |
| { |
| CHKLOG(rc, PHashTableEntryGetKeyValue(entry, (void **)&key, (void **)&value)); |
| if (LSTRSTR(path, key) == path) |
| { |
| /* File-system handles file path */ |
| |
| if (bestKey == NULL || LSTRLEN(key) > LSTRLEN(bestKey)) |
| { |
| /* Found a better match -- the new key is a subdirectory of the previous bestKey */ |
| bestKey = key; |
| bestValue = value; |
| } |
| } |
| CHKLOG(rc, PHashTableEntryAdvance(&entry)); |
| } |
| if (bestKey == NULL) |
| { |
| rc = ESR_INVALID_STATE; |
| PLogError(L("No file-system handles the specified path (%s)"), path); |
| goto CLEANUP; |
| } |
| *fileSystem = bestValue; |
| if (relativePath != NULL) |
| { |
| ESR_BOOL isAbsolute; |
| |
| CHKLOG(rc, PFileSystemIsAbsolutePath(path + LSTRLEN(bestKey), &isAbsolute)); |
| LSTRCPY(relativePath, L("")); |
| if (!isAbsolute) |
| { |
| /* Make sure that the relative path is relative to the root of the file-system */ |
| LSTRCAT(relativePath, L("/")); |
| } |
| LSTRCAT(relativePath, path + LSTRLEN(bestKey)); |
| } |
| return ESR_SUCCESS; |
| CLEANUP: |
| return rc; |
| } |
| |
| ESR_ReturnCode PFileSystemCreatePFile(const LCHAR* filename, ESR_BOOL littleEndian, PFile** self) |
| { |
| ESR_ReturnCode rc; |
| LCHAR absolutePath[P_PATH_MAX]; |
| PFileSystem* fileSystem; |
| size_t len; |
| |
| if (filename == NULL || self == NULL) |
| { |
| rc = ESR_INVALID_ARGUMENT; |
| PLogError(ESR_rc2str(rc)); |
| goto CLEANUP; |
| } |
| LSTRCPY(absolutePath, filename); |
| lstrtrim(absolutePath); |
| len = P_PATH_MAX; |
| CHKLOG(rc, PFileSystemGetAbsolutePath(absolutePath, &len)); |
| CHKLOG(rc, PFileSystemGetFS(absolutePath, &fileSystem, NULL)); |
| rc = fileSystem->createPFile(fileSystem, absolutePath, littleEndian, self); |
| if (rc == ESR_NO_MATCH_ERROR) |
| rc = ESR_OPEN_ERROR; |
| if (rc != ESR_SUCCESS) |
| { |
| PLogError("%s, %s, %s", ESR_rc2str(rc), filename, absolutePath); |
| goto CLEANUP; |
| } |
| return ESR_SUCCESS; |
| CLEANUP: |
| return rc; |
| } |
| |
| ESR_ReturnCode PFileSystemMkdir(const LCHAR* path) |
| { |
| ESR_ReturnCode rc; |
| LCHAR absolutePath[P_PATH_MAX]; |
| PFileSystem* fileSystem; |
| size_t len; |
| |
| if (path == NULL) |
| { |
| rc = ESR_INVALID_ARGUMENT; |
| PLogError(ESR_rc2str(rc)); |
| goto CLEANUP; |
| } |
| LSTRCPY(absolutePath, path); |
| lstrtrim(absolutePath); |
| len = P_PATH_MAX; |
| CHKLOG(rc, PFileSystemGetAbsolutePath(absolutePath, &len)); |
| CHKLOG(rc, PFileSystemGetFS(absolutePath, &fileSystem, NULL)); |
| CHK(rc, fileSystem->mkdir(fileSystem, absolutePath)); |
| return ESR_SUCCESS; |
| CLEANUP: |
| return rc; |
| } |
| |
| ESR_ReturnCode PFileSystemGetcwd(LCHAR* path, size_t* len) |
| { |
| ESR_ReturnCode rc; |
| |
| if (path == NULL || len == NULL) |
| { |
| rc = ESR_INVALID_ARGUMENT; |
| PLogError(ESR_rc2str(rc)); |
| goto CLEANUP; |
| } |
| if (LSTRLEN(PFileSystemCurrentDirectory) + 1 > *len) |
| { |
| rc = ESR_BUFFER_OVERFLOW; |
| *len = LSTRLEN(PFileSystemCurrentDirectory) + 1; |
| PLogError(ESR_rc2str(rc)); |
| goto CLEANUP; |
| } |
| LSTRCPY(path, PFileSystemCurrentDirectory); |
| /* Check function postcondition */ |
| passert(path[LSTRLEN(path)-1] == L('/')); |
| return ESR_SUCCESS; |
| CLEANUP: |
| return rc; |
| } |
| |
| ESR_ReturnCode PFileSystemChdir(const LCHAR* path) |
| { |
| ESR_ReturnCode rc; |
| LCHAR absolutePath[P_PATH_MAX]; |
| PFileSystem* fileSystem; |
| size_t len; |
| |
| if (path == NULL) |
| { |
| rc = ESR_INVALID_ARGUMENT; |
| PLogError(ESR_rc2str(rc)); |
| goto CLEANUP; |
| } |
| LSTRCPY(absolutePath, path); |
| /* Ensure path ends with '/' */ |
| if (absolutePath[LSTRLEN(absolutePath)-1] != L('/')) |
| LSTRCAT(absolutePath, L("/")); |
| lstrtrim(absolutePath); |
| len = P_PATH_MAX; |
| CHKLOG(rc, PFileSystemGetAbsolutePath(absolutePath, &len)); |
| CHKLOG(rc, PFileSystemGetFS(absolutePath, &fileSystem, NULL)); |
| rc = fileSystem->chdir(fileSystem, absolutePath); |
| if (rc != ESR_SUCCESS) |
| { |
| PLogError(ESR_rc2str(rc)); |
| goto CLEANUP; |
| } |
| if (absolutePath[LSTRLEN(absolutePath)-1] != L('/')) |
| LSTRCAT(absolutePath, L("/")); |
| LSTRCPY(PFileSystemCurrentDirectory, absolutePath); |
| return ESR_SUCCESS; |
| CLEANUP: |
| return rc; |
| } |
| |
| /** |
| * PRECONDITION: Directory names must end with '/' |
| */ |
| ESR_ReturnCode PFileSystemGetParentDirectory(LCHAR* path, size_t* len) |
| { |
| LCHAR* lastSlash; |
| LCHAR clone[P_PATH_MAX]; |
| ESR_ReturnCode rc; |
| size_t len2; |
| |
| if (path == NULL || len == NULL) |
| { |
| rc = ESR_INVALID_ARGUMENT; |
| PLogError(ESR_rc2str(rc)); |
| goto CLEANUP; |
| } |
| LSTRCPY(clone, path); |
| lstrtrim(clone); |
| len2 = P_PATH_MAX; |
| CHKLOG(rc, PFileSystemGetAbsolutePath(clone, &len2)); |
| |
| /* 1.0 - Strip filename */ |
| lastSlash = LSTRRCHR(clone, L('/')); |
| if (lastSlash == NULL) |
| { |
| /* path contains only a filename */ |
| LSTRCPY(path, L("../")); |
| return ESR_SUCCESS; |
| } |
| else if (lastSlash < clone + LSTRLEN(clone) - 1) |
| { |
| |
| *(lastSlash + 1) = L('\0'); |
| if (LSTRLEN(clone) > *len) |
| { |
| *len = LSTRLEN(clone); |
| rc = ESR_BUFFER_OVERFLOW; |
| goto CLEANUP; |
| } |
| LSTRCPY(path, clone); |
| *len = LSTRLEN(path); |
| return ESR_SUCCESS; |
| } |
| |
| /* Get parent directory */ |
| if (lastSlash -clone + 2 == 3 && LSTRNCMP(clone, L("../"), 3) == 0) |
| { |
| LSTRCAT(clone, L("../")); |
| if (LSTRLEN(clone) > *len) |
| { |
| *len = LSTRLEN(clone); |
| rc = ESR_BUFFER_OVERFLOW; |
| goto CLEANUP; |
| } |
| LSTRCPY(path, clone); |
| *len = LSTRLEN(path); |
| return ESR_SUCCESS; |
| } |
| if (lastSlash -clone + 1 == 2 && LSTRNCMP(clone, L("./"), 2) == 0) |
| { |
| if (LSTRLEN(L("../")) > *len) |
| { |
| *len = LSTRLEN(L("../")); |
| rc = ESR_BUFFER_OVERFLOW; |
| goto CLEANUP; |
| } |
| LSTRCPY(path, L("../")); |
| *len = LSTRLEN(path); |
| return ESR_SUCCESS; |
| } |
| else if (lastSlash == clone && LSTRNCMP(clone, L("/"), 1) == 0) |
| { |
| rc = ESR_INVALID_ARGUMENT; |
| PLogError(ESR_rc2str(rc)); |
| goto CLEANUP; |
| } |
| *lastSlash = 0; |
| lastSlash = LSTRRCHR(clone, L('/')); |
| if (lastSlash != NULL) |
| { |
| *(lastSlash + 1) = 0; |
| if (LSTRLEN(clone) > *len) |
| { |
| *len = LSTRLEN(clone); |
| rc = ESR_BUFFER_OVERFLOW; |
| goto CLEANUP; |
| } |
| LSTRCPY(path, clone); |
| *len = LSTRLEN(path); |
| } |
| else |
| { |
| LSTRCPY(path, L("")); |
| *len = 0; |
| } |
| return ESR_SUCCESS; |
| CLEANUP: |
| return rc; |
| } |
| |
| ESR_ReturnCode PFileSystemIsDirectoryPath(const LCHAR* path, ESR_BOOL* isDirectory) |
| { |
| LCHAR temp[P_PATH_MAX]; |
| ESR_ReturnCode rc; |
| |
| passert(isDirectory != NULL); |
| LSTRCPY(temp, path); |
| lstrtrim(temp); |
| CHKLOG(rc, PFileSystemCanonicalSlashes(temp)); |
| *isDirectory = temp[LSTRLEN(temp)-1] == '/'; |
| return ESR_SUCCESS; |
| CLEANUP: |
| return rc; |
| } |