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 */
34extern ESR_BOOL PFileSystemCreated;
35
36/**
37 * [file path, PFileSystem*] mapping.
38 */
39extern PHashTable* PFileSystemPathMap;
40
41/**
42 * Current working directory.
43 */
44extern LCHAR PFileSystemCurrentDirectory[P_PATH_MAX];
45
46PORTABLE_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;
60CLEANUP:
61  return rc;
62}
63
64ESR_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;
132CLEANUP:
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
156ESR_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;
173CLEANUP:
174  return rc;
175}
176
177ESR_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;
238CLEANUP:
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
250ESR_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;
262CLEANUP:
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 */
273ESR_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;
320CLEANUP:
321  return rc;
322}
323
324ESR_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;
351CLEANUP:
352  return rc;
353}
354
355ESR_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;
375CLEANUP:
376  return rc;
377}
378
379ESR_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;
400CLEANUP:
401  return rc;
402}
403
404ESR_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;
435CLEANUP:
436  return rc;
437}
438
439/**
440 * PRECONDITION: Directory names must end with '/'
441 */
442ESR_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;
535CLEANUP:
536  return rc;
537}
538
539ESR_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;
550CLEANUP:
551  return rc;
552}
553