1 /*---------------------------------------------------------------------------* 2 * PFileSystem.c * 3 * * 4 * Copyright 2007, 2008 Nuance Communciations, Inc. * 5 * * 6 * Licensed under the Apache License, Version 2.0 (the 'License'); * 7 * you may not use this file except in compliance with the License. * 8 * * 9 * You may obtain a copy of the License at * 10 * http://www.apache.org/licenses/LICENSE-2.0 * 11 * * 12 * Unless required by applicable law or agreed to in writing, software * 13 * distributed under the License is distributed on an 'AS IS' BASIS, * 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 15 * See the License for the specific language governing permissions and * 16 * limitations under the License. * 17 * * 18 *---------------------------------------------------------------------------*/ 19 20 #include "ArrayList.h" 21 #include "LCHAR.h" 22 #include "PFileSystem.h" 23 #include "PFileSystemImpl.h" 24 #include "phashtable.h" 25 #include "plog.h" 26 #include "pmemory.h" 27 28 29 #define MTAG NULL 30 31 /** 32 * Indicates if PFileSystem is initialized. 33 */ 34 extern ESR_BOOL PFileSystemCreated; 35 36 /** 37 * [file path, PFileSystem*] mapping. 38 */ 39 extern PHashTable* PFileSystemPathMap; 40 41 /** 42 * Current working directory. 43 */ 44 extern LCHAR PFileSystemCurrentDirectory[P_PATH_MAX]; 45 46 PORTABLE_API ESR_ReturnCode PFileSystemCanonicalSlashes(LCHAR* path) 47 { 48 ESR_ReturnCode rc; 49 50 if (path == NULL) 51 { 52 rc = ESR_INVALID_ARGUMENT; 53 PLogError(ESR_rc2str(rc)); 54 goto CLEANUP; 55 } 56 57 lstrtrim(path); 58 CHKLOG(rc, lstrreplace(path, L('\\'), L('/'))); 59 return ESR_SUCCESS; 60 CLEANUP: 61 return rc; 62 } 63 64 ESR_ReturnCode PFileSystemLinearToPathTokens(const LCHAR* path, LCHAR*** tokenArray, size_t* count) 65 { 66 ESR_ReturnCode rc; 67 const LCHAR* beginning; 68 const LCHAR* ending; 69 LCHAR linear[P_PATH_MAX]; 70 ArrayList* arrayList = NULL; 71 LCHAR* value = NULL; 72 size_t i; 73 74 if (path == NULL || tokenArray == NULL || count == NULL) 75 { 76 rc = ESR_INVALID_ARGUMENT; 77 PLogError(ESR_rc2str(rc)); 78 goto CLEANUP; 79 } 80 LSTRCPY(linear, path); 81 CHKLOG(rc, PFileSystemCanonicalSlashes(linear)); 82 CHKLOG(rc, ArrayListCreate(&arrayList)); 83 beginning = linear; 84 while (ESR_TRUE) 85 { 86 ending = LSTRCHR(beginning, L('/')); 87 if (ending == NULL) 88 ending = beginning + LSTRLEN(beginning); 89 value = MALLOC(sizeof(LCHAR) * (ending - beginning + 1 + 1), MTAG); 90 if (value == NULL) 91 { 92 rc = ESR_OUT_OF_MEMORY; 93 PLogError(ESR_rc2str(rc)); 94 goto CLEANUP; 95 } 96 LSTRNCPY(value, beginning, ending - beginning + 1); 97 value[ending-beginning+1] = L('\0'); 98 CHKLOG(rc, lstrtrim(value)); 99 if (LSTRLEN(value) == 0) 100 { 101 FREE(value); 102 value = NULL; 103 } 104 else 105 { 106 CHKLOG(rc, arrayList->add(arrayList, value)); 107 value = NULL; 108 } 109 if (*ending == 0) 110 break; 111 beginning = ending + 1; 112 } 113 114 /* Build static token array */ 115 CHKLOG(rc, arrayList->getSize(arrayList, count)); 116 *tokenArray = MALLOC(*count * sizeof(LCHAR*), MTAG); 117 if (*tokenArray == NULL) 118 { 119 rc = ESR_OUT_OF_MEMORY; 120 goto CLEANUP; 121 } 122 for (i = 0; i < *count; ++i) 123 { 124 rc = arrayList->get(arrayList, i, (void**)(&(*tokenArray)[i])); 125 if (rc != ESR_SUCCESS) 126 goto CLEANUP; 127 } 128 rc = arrayList->destroy(arrayList); 129 if (rc != ESR_SUCCESS) 130 goto CLEANUP; 131 return ESR_SUCCESS; 132 CLEANUP: 133 FREE(value); 134 if (arrayList != NULL) 135 { 136 ESR_ReturnCode cleanRC; 137 138 cleanRC = arrayList->getSize(arrayList, count); 139 if (cleanRC != ESR_SUCCESS) 140 return rc; 141 for (i = 0; i < *count; ++i) 142 { 143 cleanRC = arrayList->get(arrayList, 0, (void**)&value); 144 if (cleanRC != ESR_SUCCESS) 145 return rc; 146 FREE(value); 147 cleanRC = arrayList->remove(arrayList, 0); 148 if (cleanRC != ESR_SUCCESS) 149 return rc; 150 } 151 arrayList->destroy(arrayList); 152 } 153 return rc; 154 } 155 156 ESR_ReturnCode PFileSystemIsAbsolutePath(const LCHAR* path, ESR_BOOL* isAbsolute) 157 { 158 LCHAR canonical[P_PATH_MAX]; 159 ESR_ReturnCode rc; 160 161 if (isAbsolute == NULL) 162 { 163 rc = ESR_INVALID_ARGUMENT; 164 PLogError(ESR_rc2str(rc)); 165 goto CLEANUP; 166 } 167 LSTRCPY(canonical, path); 168 CHKLOG(rc, PFileSystemCanonicalSlashes(canonical)); 169 170 *isAbsolute = (canonical[0] == '/' || 171 (LISALPHA(canonical[0]) && canonical[1] == ':' && canonical[2] == '/')); 172 return ESR_SUCCESS; 173 CLEANUP: 174 return rc; 175 } 176 177 ESR_ReturnCode PFileSystemGetAbsolutePath(LCHAR* path, size_t* len) 178 { 179 ESR_ReturnCode rc; 180 #define MAX_PATH_TOKENS 20 181 LCHAR** tokens = NULL; 182 size_t tokenLen = 0, i; 183 ESR_BOOL isAbsolute; 184 185 if (path == NULL || len == NULL) 186 { 187 rc = ESR_INVALID_ARGUMENT; 188 PLogError(ESR_rc2str(rc)); 189 goto CLEANUP; 190 } 191 CHKLOG(rc, PFileSystemIsAbsolutePath(path, &isAbsolute)); 192 193 /* Prefix relative paths with the current working directory */ 194 if (!isAbsolute) 195 { 196 LCHAR cwd[P_PATH_MAX]; 197 size_t len2; 198 199 len2 = P_PATH_MAX; 200 CHKLOG(rc, PFileSystemGetcwd(cwd, &len2)); 201 len2 = *len; 202 CHKLOG(rc, lstrinsert(cwd, path, 0, &len2)); 203 } 204 205 CHKLOG(rc, PFileSystemCanonicalSlashes(path)); 206 tokenLen = MAX_PATH_TOKENS; 207 CHKLOG(rc, PFileSystemLinearToPathTokens(path, &tokens, &tokenLen)); 208 209 LSTRCPY(path, L("")); 210 for (i = 0; i < tokenLen; ++i) 211 { 212 if (LSTRCMP(tokens[i], L("../")) == 0) 213 { 214 size_t len2; 215 216 len2 = *len; 217 passert(path[LSTRLEN(path)-1] == L('/')); 218 CHKLOG(rc, PFileSystemGetParentDirectory(path, &len2)); 219 } 220 else if (LSTRCMP(tokens[i], L("./")) == 0) 221 { 222 if (i > 0) 223 { 224 /* do nothing */ 225 } 226 else 227 { 228 LSTRCPY(path, L("./")); 229 } 230 } 231 else 232 LSTRCAT(path, tokens[i]); 233 FREE(tokens[i]); 234 tokens[i] = NULL; 235 } 236 FREE(tokens); 237 return ESR_SUCCESS; 238 CLEANUP: 239 if (tokens != NULL) 240 { 241 for (i = 0; i < tokenLen; ++i) 242 { 243 FREE(tokens[i]); 244 tokens[i] = NULL; 245 } 246 } 247 return rc; 248 } 249 250 ESR_ReturnCode PFileSystemIsCreated(ESR_BOOL* isCreated) 251 { 252 ESR_ReturnCode rc; 253 254 if (isCreated == NULL) 255 { 256 rc = ESR_INVALID_ARGUMENT; 257 PLogError(ESR_rc2str(rc)); 258 goto CLEANUP; 259 } 260 *isCreated = PFileSystemCreated; 261 return ESR_SUCCESS; 262 CLEANUP: 263 return rc; 264 } 265 266 /** 267 * Given a path, returns the associated file-system and relative path. 268 * 269 * @param path Path to look up 270 * @param fs [out] File-system which matches the path 271 * @param relativePath [out] Relative path associated with match (should have length P_PATH_MAX) 272 */ 273 ESR_ReturnCode PFileSystemGetFS(const LCHAR* path, PFileSystem** fileSystem, LCHAR* relativePath) 274 { 275 ESR_ReturnCode rc; 276 PHashTableEntry* entry; 277 LCHAR* key; 278 PFileSystem* value; 279 LCHAR* bestKey = NULL; 280 PFileSystem* bestValue = NULL; 281 282 CHKLOG(rc, PHashTableEntryGetFirst(PFileSystemPathMap, &entry)); 283 while (entry != NULL) 284 { 285 CHKLOG(rc, PHashTableEntryGetKeyValue(entry, (void **)&key, (void **)&value)); 286 if (LSTRSTR(path, key) == path) 287 { 288 /* File-system handles file path */ 289 290 if (bestKey == NULL || LSTRLEN(key) > LSTRLEN(bestKey)) 291 { 292 /* Found a better match -- the new key is a subdirectory of the previous bestKey */ 293 bestKey = key; 294 bestValue = value; 295 } 296 } 297 CHKLOG(rc, PHashTableEntryAdvance(&entry)); 298 } 299 if (bestKey == NULL) 300 { 301 rc = ESR_INVALID_STATE; 302 PLogError(L("No file-system handles the specified path (%s)"), path); 303 goto CLEANUP; 304 } 305 *fileSystem = bestValue; 306 if (relativePath != NULL) 307 { 308 ESR_BOOL isAbsolute; 309 310 CHKLOG(rc, PFileSystemIsAbsolutePath(path + LSTRLEN(bestKey), &isAbsolute)); 311 LSTRCPY(relativePath, L("")); 312 if (!isAbsolute) 313 { 314 /* Make sure that the relative path is relative to the root of the file-system */ 315 LSTRCAT(relativePath, L("/")); 316 } 317 LSTRCAT(relativePath, path + LSTRLEN(bestKey)); 318 } 319 return ESR_SUCCESS; 320 CLEANUP: 321 return rc; 322 } 323 324 ESR_ReturnCode PFileSystemCreatePFile(const LCHAR* filename, ESR_BOOL littleEndian, PFile** self) 325 { 326 ESR_ReturnCode rc; 327 LCHAR absolutePath[P_PATH_MAX]; 328 PFileSystem* fileSystem; 329 size_t len; 330 331 if (filename == NULL || self == NULL) 332 { 333 rc = ESR_INVALID_ARGUMENT; 334 PLogError(ESR_rc2str(rc)); 335 goto CLEANUP; 336 } 337 LSTRCPY(absolutePath, filename); 338 lstrtrim(absolutePath); 339 len = P_PATH_MAX; 340 CHKLOG(rc, PFileSystemGetAbsolutePath(absolutePath, &len)); 341 CHKLOG(rc, PFileSystemGetFS(absolutePath, &fileSystem, NULL)); 342 rc = fileSystem->createPFile(fileSystem, absolutePath, littleEndian, self); 343 if (rc == ESR_NO_MATCH_ERROR) 344 rc = ESR_OPEN_ERROR; 345 if (rc != ESR_SUCCESS) 346 { 347 PLogError("%s, %s, %s", ESR_rc2str(rc), filename, absolutePath); 348 goto CLEANUP; 349 } 350 return ESR_SUCCESS; 351 CLEANUP: 352 return rc; 353 } 354 355 ESR_ReturnCode PFileSystemMkdir(const LCHAR* path) 356 { 357 ESR_ReturnCode rc; 358 LCHAR absolutePath[P_PATH_MAX]; 359 PFileSystem* fileSystem; 360 size_t len; 361 362 if (path == NULL) 363 { 364 rc = ESR_INVALID_ARGUMENT; 365 PLogError(ESR_rc2str(rc)); 366 goto CLEANUP; 367 } 368 LSTRCPY(absolutePath, path); 369 lstrtrim(absolutePath); 370 len = P_PATH_MAX; 371 CHKLOG(rc, PFileSystemGetAbsolutePath(absolutePath, &len)); 372 CHKLOG(rc, PFileSystemGetFS(absolutePath, &fileSystem, NULL)); 373 CHK(rc, fileSystem->mkdir(fileSystem, absolutePath)); 374 return ESR_SUCCESS; 375 CLEANUP: 376 return rc; 377 } 378 379 ESR_ReturnCode PFileSystemGetcwd(LCHAR* path, size_t* len) 380 { 381 ESR_ReturnCode rc; 382 383 if (path == NULL || len == NULL) 384 { 385 rc = ESR_INVALID_ARGUMENT; 386 PLogError(ESR_rc2str(rc)); 387 goto CLEANUP; 388 } 389 if (LSTRLEN(PFileSystemCurrentDirectory) + 1 > *len) 390 { 391 rc = ESR_BUFFER_OVERFLOW; 392 *len = LSTRLEN(PFileSystemCurrentDirectory) + 1; 393 PLogError(ESR_rc2str(rc)); 394 goto CLEANUP; 395 } 396 LSTRCPY(path, PFileSystemCurrentDirectory); 397 /* Check function postcondition */ 398 passert(path[LSTRLEN(path)-1] == L('/')); 399 return ESR_SUCCESS; 400 CLEANUP: 401 return rc; 402 } 403 404 ESR_ReturnCode PFileSystemChdir(const LCHAR* path) 405 { 406 ESR_ReturnCode rc; 407 LCHAR absolutePath[P_PATH_MAX]; 408 PFileSystem* fileSystem; 409 size_t len; 410 411 if (path == NULL) 412 { 413 rc = ESR_INVALID_ARGUMENT; 414 PLogError(ESR_rc2str(rc)); 415 goto CLEANUP; 416 } 417 LSTRCPY(absolutePath, path); 418 /* Ensure path ends with '/' */ 419 if (absolutePath[LSTRLEN(absolutePath)-1] != L('/')) 420 LSTRCAT(absolutePath, L("/")); 421 lstrtrim(absolutePath); 422 len = P_PATH_MAX; 423 CHKLOG(rc, PFileSystemGetAbsolutePath(absolutePath, &len)); 424 CHKLOG(rc, PFileSystemGetFS(absolutePath, &fileSystem, NULL)); 425 rc = fileSystem->chdir(fileSystem, absolutePath); 426 if (rc != ESR_SUCCESS) 427 { 428 PLogError(ESR_rc2str(rc)); 429 goto CLEANUP; 430 } 431 if (absolutePath[LSTRLEN(absolutePath)-1] != L('/')) 432 LSTRCAT(absolutePath, L("/")); 433 LSTRCPY(PFileSystemCurrentDirectory, absolutePath); 434 return ESR_SUCCESS; 435 CLEANUP: 436 return rc; 437 } 438 439 /** 440 * PRECONDITION: Directory names must end with '/' 441 */ 442 ESR_ReturnCode PFileSystemGetParentDirectory(LCHAR* path, size_t* len) 443 { 444 LCHAR* lastSlash; 445 LCHAR clone[P_PATH_MAX]; 446 ESR_ReturnCode rc; 447 size_t len2; 448 449 if (path == NULL || len == NULL) 450 { 451 rc = ESR_INVALID_ARGUMENT; 452 PLogError(ESR_rc2str(rc)); 453 goto CLEANUP; 454 } 455 LSTRCPY(clone, path); 456 lstrtrim(clone); 457 len2 = P_PATH_MAX; 458 CHKLOG(rc, PFileSystemGetAbsolutePath(clone, &len2)); 459 460 /* 1.0 - Strip filename */ 461 lastSlash = LSTRRCHR(clone, L('/')); 462 if (lastSlash == NULL) 463 { 464 /* path contains only a filename */ 465 LSTRCPY(path, L("../")); 466 return ESR_SUCCESS; 467 } 468 else if (lastSlash < clone + LSTRLEN(clone) - 1) 469 { 470 471 *(lastSlash + 1) = L('\0'); 472 if (LSTRLEN(clone) > *len) 473 { 474 *len = LSTRLEN(clone); 475 rc = ESR_BUFFER_OVERFLOW; 476 goto CLEANUP; 477 } 478 LSTRCPY(path, clone); 479 *len = LSTRLEN(path); 480 return ESR_SUCCESS; 481 } 482 483 /* Get parent directory */ 484 if (lastSlash -clone + 2 == 3 && LSTRNCMP(clone, L("../"), 3) == 0) 485 { 486 LSTRCAT(clone, L("../")); 487 if (LSTRLEN(clone) > *len) 488 { 489 *len = LSTRLEN(clone); 490 rc = ESR_BUFFER_OVERFLOW; 491 goto CLEANUP; 492 } 493 LSTRCPY(path, clone); 494 *len = LSTRLEN(path); 495 return ESR_SUCCESS; 496 } 497 if (lastSlash -clone + 1 == 2 && LSTRNCMP(clone, L("./"), 2) == 0) 498 { 499 if (LSTRLEN(L("../")) > *len) 500 { 501 *len = LSTRLEN(L("../")); 502 rc = ESR_BUFFER_OVERFLOW; 503 goto CLEANUP; 504 } 505 LSTRCPY(path, L("../")); 506 *len = LSTRLEN(path); 507 return ESR_SUCCESS; 508 } 509 else if (lastSlash == clone && LSTRNCMP(clone, L("/"), 1) == 0) 510 { 511 rc = ESR_INVALID_ARGUMENT; 512 PLogError(ESR_rc2str(rc)); 513 goto CLEANUP; 514 } 515 *lastSlash = 0; 516 lastSlash = LSTRRCHR(clone, L('/')); 517 if (lastSlash != NULL) 518 { 519 *(lastSlash + 1) = 0; 520 if (LSTRLEN(clone) > *len) 521 { 522 *len = LSTRLEN(clone); 523 rc = ESR_BUFFER_OVERFLOW; 524 goto CLEANUP; 525 } 526 LSTRCPY(path, clone); 527 *len = LSTRLEN(path); 528 } 529 else 530 { 531 LSTRCPY(path, L("")); 532 *len = 0; 533 } 534 return ESR_SUCCESS; 535 CLEANUP: 536 return rc; 537 } 538 539 ESR_ReturnCode PFileSystemIsDirectoryPath(const LCHAR* path, ESR_BOOL* isDirectory) 540 { 541 LCHAR temp[P_PATH_MAX]; 542 ESR_ReturnCode rc; 543 544 passert(isDirectory != NULL); 545 LSTRCPY(temp, path); 546 lstrtrim(temp); 547 CHKLOG(rc, PFileSystemCanonicalSlashes(temp)); 548 *isDirectory = temp[LSTRLEN(temp)-1] == '/'; 549 return ESR_SUCCESS; 550 CLEANUP: 551 return rc; 552 } 553