1 /*             ----> DO NOT REMOVE THE FOLLOWING NOTICE <----
2  *
3  *                 Copyright (c) 2014-2015 Datalight, Inc.
4  *                     All Rights Reserved Worldwide.
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; use version 2 of the License.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but "AS-IS," WITHOUT ANY WARRANTY; without even the implied warranty
12  *  of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License along
16  *  with this program; if not, write to the Free Software Foundation, Inc.,
17  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 /*  Businesses and individuals that for commercial or other reasons cannot
21  *  comply with the terms of the GPLv2 license may obtain a commercial license
22  *  before incorporating Reliance Edge into proprietary software for
23  *  distribution in any form.  Visit http://www.datalight.com/reliance-edge for
24  *  more information.
25  */
26 
27 /** @file
28  *  @brief Implements path utilities for the POSIX-like API layer.
29  */
30 #include <redfs.h>
31 
32 #if REDCONF_API_POSIX == 1
33 
34     #include <redcoreapi.h>
35     #include <redvolume.h>
36     #include <redposix.h>
37     #include <redpath.h>
38 
39 
40     static bool IsRootDir( const char * pszLocalPath );
41     static bool PathHasMoreNames( const char * pszPathIdx );
42 
43 
44 /** @brief Split a path into its component parts: a volume and a volume-local
45  *         path.
46  *
47  *  @param pszPath          The path to split.
48  *  @param pbVolNum         On successful return, if non-NULL, populated with
49  *                          the volume number extracted from the path.
50  *  @param ppszLocalPath    On successful return, populated with the
51  *                          volume-local path: the path stripped of any volume
52  *                          path prefixing.  If this parameter is NULL, that
53  *                          indicates there should be no local path, and any
54  *                          characters beyond the prefix (other than path
55  *                          separators) are treated as an error.
56  *
57  *  @return A negated ::REDSTATUS code indicating the operation result.
58  *
59  *  @retval 0           Operation was successful.
60  *  @retval -RED_EINVAL @p pszPath is `NULL`.
61  *  @retval -RED_ENOENT @p pszPath could not be matched to any volume; or
62  *                      @p ppszLocalPath is NULL but @p pszPath includes a local
63  *                      path.
64  */
RedPathSplit(const char * pszPath,uint8_t * pbVolNum,const char ** ppszLocalPath)65     REDSTATUS RedPathSplit( const char * pszPath,
66                             uint8_t * pbVolNum,
67                             const char ** ppszLocalPath )
68     {
69         REDSTATUS ret = 0;
70 
71         if( pszPath == NULL )
72         {
73             ret = -RED_EINVAL;
74         }
75         else
76         {
77             const char * pszLocalPath = pszPath;
78             uint8_t bMatchVol = UINT8_MAX;
79             uint32_t ulMatchLen = 0U;
80             uint8_t bDefaultVolNum = UINT8_MAX;
81             uint8_t bVolNum;
82 
83             for( bVolNum = 0U; bVolNum < REDCONF_VOLUME_COUNT; bVolNum++ )
84             {
85                 const char * pszPrefix = gaRedVolConf[ bVolNum ].pszPathPrefix;
86                 uint32_t ulPrefixLen = RedStrLen( pszPrefix );
87 
88                 if( ulPrefixLen == 0U )
89                 {
90                     /*  A volume with a path prefix of an empty string is the
91                      *  default volume, used when the path does not match the
92                      *  prefix of any other volume.
93                      *
94                      *  The default volume should only be found once.  During
95                      *  initialization, RedCoreInit() ensures that all volume
96                      *  prefixes are unique (including empty prefixes).
97                      */
98                     REDASSERT( bDefaultVolNum == UINT8_MAX );
99                     bDefaultVolNum = bVolNum;
100                 }
101 
102                 /*  For a path to match, it must either be the prefix exactly, or
103                  *  be followed by a path separator character.  Thus, with a volume
104                  *  prefix of "/foo", both "/foo" and "/foo/bar" are matches, but
105                  *  "/foobar" is not.
106                  */
107                 else if( ( RedStrNCmp( pszPath, pszPrefix, ulPrefixLen ) == 0 ) &&
108                          ( ( pszPath[ ulPrefixLen ] == '\0' ) || ( pszPath[ ulPrefixLen ] == REDCONF_PATH_SEPARATOR ) ) )
109                 {
110                     /*  The length of this match should never exactly equal the
111                      *  length of a previous match: that would require a duplicate
112                      *  volume name, which should have been detected during init.
113                      */
114                     REDASSERT( ulPrefixLen != ulMatchLen );
115 
116                     /*  If multiple prefixes match, the longest takes precedence.
117                      *  Thus, if there are two prefixes "Flash" and "Flash/Backup",
118                      *  the path "Flash/Backup/" will not be erroneously matched
119                      *  with the "Flash" volume.
120                      */
121                     if( ulPrefixLen > ulMatchLen )
122                     {
123                         bMatchVol = bVolNum;
124                         ulMatchLen = ulPrefixLen;
125                     }
126                 }
127                 else
128                 {
129                     /*  No match, keep looking.
130                      */
131                 }
132             }
133 
134             if( bMatchVol != UINT8_MAX )
135             {
136                 /*  The path matched a volume path prefix.
137                  */
138                 bVolNum = bMatchVol;
139                 pszLocalPath = &pszPath[ ulMatchLen ];
140             }
141             else if( bDefaultVolNum != UINT8_MAX )
142             {
143                 /*  The path didn't match any of the prefixes, but one of the
144                  *  volumes has a path prefix of "", so an unprefixed path is
145                  *  assigned to that volume.
146                  */
147                 bVolNum = bDefaultVolNum;
148                 REDASSERT( pszLocalPath == pszPath );
149             }
150             else
151             {
152                 /*  The path cannot be assigned a volume.
153                  */
154                 ret = -RED_ENOENT;
155             }
156 
157             if( ret == 0 )
158             {
159                 if( pbVolNum != NULL )
160                 {
161                     *pbVolNum = bVolNum;
162                 }
163 
164                 if( ppszLocalPath != NULL )
165                 {
166                     *ppszLocalPath = pszLocalPath;
167                 }
168                 else
169                 {
170                     /*  If no local path is expected, then the string should either
171                      *  terminate after the path prefix or the local path should name
172                      *  the root directory.  Allowing path separators here means that
173                      *  red_mount("/data/") is OK with a path prefix of "/data".
174                      */
175                     if( pszLocalPath[ 0U ] != '\0' )
176                     {
177                         if( !IsRootDir( pszLocalPath ) )
178                         {
179                             ret = -RED_ENOENT;
180                         }
181                     }
182                 }
183             }
184         }
185 
186         return ret;
187     }
188 
189 
190 /** @brief Lookup the inode named by the given path.
191  *
192  *  @param pszLocalPath The path to lookup; this is a local path, without any
193  *                      volume prefix.
194  *  @param pulInode     On successful return, populated with the number of the
195  *                      inode named by @p pszLocalPath.
196  *
197  *  @return A negated ::REDSTATUS code indicating the operation result.
198  *
199  *  @retval 0                   Operation was successful.
200  *  @retval -RED_EINVAL         @p pszLocalPath is `NULL`; or @p pulInode is
201  *                              `NULL`.
202  *  @retval -RED_EIO            A disk I/O error occurred.
203  *  @retval -RED_ENOENT         @p pszLocalPath is an empty string; or
204  *                              @p pszLocalPath does not name an existing file
205  *                              or directory.
206  *  @retval -RED_ENOTDIR        A component of the path other than the last is
207  *                              not a directory.
208  *  @retval -RED_ENAMETOOLONG   The length of a component of @p pszLocalPath is
209  *                              longer than #REDCONF_NAME_MAX.
210  */
RedPathLookup(const char * pszLocalPath,uint32_t * pulInode)211     REDSTATUS RedPathLookup( const char * pszLocalPath,
212                              uint32_t * pulInode )
213     {
214         REDSTATUS ret;
215 
216         if( ( pszLocalPath == NULL ) || ( pulInode == NULL ) )
217         {
218             REDERROR();
219             ret = -RED_EINVAL;
220         }
221         else if( pszLocalPath[ 0U ] == '\0' )
222         {
223             ret = -RED_ENOENT;
224         }
225         else if( IsRootDir( pszLocalPath ) )
226         {
227             ret = 0;
228             *pulInode = INODE_ROOTDIR;
229         }
230         else
231         {
232             uint32_t ulPInode;
233             const char * pszName;
234 
235             ret = RedPathToName( pszLocalPath, &ulPInode, &pszName );
236 
237             if( ret == 0 )
238             {
239                 ret = RedCoreLookup( ulPInode, pszName, pulInode );
240             }
241         }
242 
243         return ret;
244     }
245 
246 
247 /** @brief Given a path, return the parent inode number and a pointer to the
248  *         last component in the path (the name).
249  *
250  *  @param pszLocalPath The path to examine; this is a local path, without any
251  *                      volume prefix.
252  *  @param pulPInode    On successful return, populated with the inode number of
253  *                      the parent directory of the last component in the path.
254  *                      For example, with the path "a/b/c", populated with the
255  *                      inode number of "b".
256  *  @param ppszName     On successful return, populated with a pointer to the
257  *                      last component in the path.  For example, with the path
258  *                      "a/b/c", populated with a pointer to "c".
259  *
260  *  @return A negated ::REDSTATUS code indicating the operation result.
261  *
262  *  @retval 0                   Operation was successful.
263  *  @retval -RED_EINVAL         @p pszLocalPath is `NULL`; or @p pulPInode is
264  *                              `NULL`; or @p ppszName is `NULL`; or the path
265  *                              names the root directory.
266  *  @retval -RED_EIO            A disk I/O error occurred.
267  *  @retval -RED_ENOENT         @p pszLocalPath is an empty string; or a
268  *                              component of the path other than the last does
269  *                              not exist.
270  *  @retval -RED_ENOTDIR        A component of the path other than the last is
271  *                              not a directory.
272  *  @retval -RED_ENAMETOOLONG   The length of a component of @p pszLocalPath is
273  *                              longer than #REDCONF_NAME_MAX.
274  */
RedPathToName(const char * pszLocalPath,uint32_t * pulPInode,const char ** ppszName)275     REDSTATUS RedPathToName( const char * pszLocalPath,
276                              uint32_t * pulPInode,
277                              const char ** ppszName )
278     {
279         REDSTATUS ret;
280 
281         if( ( pszLocalPath == NULL ) || ( pulPInode == NULL ) || ( ppszName == NULL ) )
282         {
283             REDERROR();
284             ret = -RED_EINVAL;
285         }
286         else if( IsRootDir( pszLocalPath ) )
287         {
288             ret = -RED_EINVAL;
289         }
290         else if( pszLocalPath[ 0U ] == '\0' )
291         {
292             ret = -RED_ENOENT;
293         }
294         else
295         {
296             uint32_t ulInode = INODE_ROOTDIR;
297             uint32_t ulPInode = INODE_INVALID;
298             uint32_t ulPathIdx = 0U;
299             uint32_t ulLastNameIdx = 0U;
300 
301             ret = 0;
302 
303             do
304             {
305                 uint32_t ulNameLen;
306 
307                 /*  Skip over path separators, to get pszLocalPath[ulPathIdx]
308                  *  pointing at the next name.
309                  */
310                 while( pszLocalPath[ ulPathIdx ] == REDCONF_PATH_SEPARATOR )
311                 {
312                     ulPathIdx++;
313                 }
314 
315                 if( pszLocalPath[ ulPathIdx ] == '\0' )
316                 {
317                     break;
318                 }
319 
320                 /*  Point ulLastNameIdx at the first character of the name; after
321                  *  we exit the loop, it will point at the first character of the
322                  *  last name in the path.
323                  */
324                 ulLastNameIdx = ulPathIdx;
325 
326                 /*  Point ulPInode at the parent inode: either the root inode
327                  *  (first pass) or the inode of the previous name.  After we exit
328                  *  the loop, this will point at the parent inode of the last name.
329                  */
330                 ulPInode = ulInode;
331 
332                 ulNameLen = RedNameLen( &pszLocalPath[ ulPathIdx ] );
333 
334                 /*  Lookup the inode of the name, unless we are at the last name in
335                  *  the path: we don't care whether the last name exists or not.
336                  */
337                 if( PathHasMoreNames( &pszLocalPath[ ulPathIdx + ulNameLen ] ) )
338                 {
339                     ret = RedCoreLookup( ulPInode, &pszLocalPath[ ulPathIdx ], &ulInode );
340                 }
341 
342                 /*  Move on to the next path element.
343                  */
344                 if( ret == 0 )
345                 {
346                     ulPathIdx += ulNameLen;
347                 }
348             }
349             while( ret == 0 );
350 
351             if( ret == 0 )
352             {
353                 *pulPInode = ulPInode;
354                 *ppszName = &pszLocalPath[ ulLastNameIdx ];
355             }
356         }
357 
358         return ret;
359     }
360 
361 
362 /** @brief Determine whether a path names the root directory.
363  *
364  *  @param pszLocalPath The path to examine; this is a local path, without any
365  *                      volume prefix.
366  *
367  *  @return Returns whether @p pszLocalPath names the root directory.
368  *
369  *  @retval true    @p pszLocalPath names the root directory.
370  *  @retval false   @p pszLocalPath does not name the root directory.
371  */
IsRootDir(const char * pszLocalPath)372     static bool IsRootDir( const char * pszLocalPath )
373     {
374         bool fRet;
375 
376         if( pszLocalPath == NULL )
377         {
378             REDERROR();
379             fRet = false;
380         }
381         else
382         {
383             uint32_t ulIdx = 0U;
384 
385             /*  A string containing nothing but path separators (usually only one)
386              *  names the root directory.  An empty string does *not* name the root
387              *  directory, since in POSIX empty strings typically elicit -RED_ENOENT
388              *  errors.
389              */
390             while( pszLocalPath[ ulIdx ] == REDCONF_PATH_SEPARATOR )
391             {
392                 ulIdx++;
393             }
394 
395             fRet = ( ulIdx > 0U ) && ( pszLocalPath[ ulIdx ] == '\0' );
396         }
397 
398         return fRet;
399     }
400 
401 
402 /** @brief Determine whether there are more names in a path.
403  *
404  *  Example | Result
405  *  ------- | ------
406  *  ""        false
407  *  "/"       false
408  *  "//"      false
409  *  "a"       true
410  *  "/a"      true
411  *  "//a"     true
412  *
413  *  @param pszPathIdx   The path to examine, incremented to the point of
414  *                      interest.
415  *
416  *  @return Returns whether there are more names in @p pszPathIdx.
417  *
418  *  @retval true    @p pszPathIdx has more names.
419  *  @retval false   @p pszPathIdx has no more names.
420  */
PathHasMoreNames(const char * pszPathIdx)421     static bool PathHasMoreNames( const char * pszPathIdx )
422     {
423         bool fRet;
424 
425         if( pszPathIdx == NULL )
426         {
427             REDERROR();
428             fRet = false;
429         }
430         else
431         {
432             uint32_t ulIdx = 0U;
433 
434             while( pszPathIdx[ ulIdx ] == REDCONF_PATH_SEPARATOR )
435             {
436                 ulIdx++;
437             }
438 
439             fRet = pszPathIdx[ ulIdx ] != '\0';
440         }
441 
442         return fRet;
443     }
444 
445 #endif /* REDCONF_API_POSIX */
446