summaryrefslogtreecommitdiff
path: root/firmware/common/pathfuncs.c
diff options
context:
space:
mode:
Diffstat (limited to 'firmware/common/pathfuncs.c')
-rw-r--r--firmware/common/pathfuncs.c421
1 files changed, 421 insertions, 0 deletions
diff --git a/firmware/common/pathfuncs.c b/firmware/common/pathfuncs.c
new file mode 100644
index 0000000000..4410275adb
--- /dev/null
+++ b/firmware/common/pathfuncs.c
@@ -0,0 +1,421 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2014 by Michael Sevakis
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include <string.h>
22#include <ctype.h>
23#include "system.h"
24#include "pathfuncs.h"
25#include "string-extra.h"
26
27#ifdef HAVE_MULTIVOLUME
28#include <stdio.h>
29#include "storage.h"
30
31enum storage_name_dec_indexes
32{
33#if (CONFIG_STORAGE & STORAGE_ATA)
34 STORAGE_DEC_IDX_ATA,
35#endif
36#if (CONFIG_STORAGE & STORAGE_MMC)
37 STORAGE_DEC_IDX_MMC,
38#endif
39#if (CONFIG_STORAGE & STORAGE_SD)
40 STORAGE_DEC_IDX_SD,
41#endif
42#if (CONFIG_STORAGE & STORAGE_NAND)
43 STORAGE_DEC_IDX_NAND,
44#endif
45#if (CONFIG_STORAGE & STORAGE_RAMDISK)
46 STORAGE_DEC_IDX_RAMDISK,
47#endif
48#if (CONFIG_STORAGE & STORAGE_HOSTFS)
49 STORAGE_DEC_IDX_HOSTFS,
50#endif
51 STORAGE_NUM_DEC_IDX,
52};
53
54static const char * const storage_dec_names[STORAGE_NUM_DEC_IDX+1] =
55{
56#if (CONFIG_STORAGE & STORAGE_ATA)
57 [STORAGE_DEC_IDX_ATA] = ATA_VOL_DEC,
58#endif
59#if (CONFIG_STORAGE & STORAGE_MMC)
60 [STORAGE_DEC_IDX_MMC] = MMC_VOL_DEC,
61#endif
62#if (CONFIG_STORAGE & STORAGE_SD)
63 [STORAGE_DEC_IDX_SD] = SD_VOL_DEC,
64#endif
65#if (CONFIG_STORAGE & STORAGE_NAND)
66 [STORAGE_DEC_IDX_NAND] = NAND_VOL_DEC,
67#endif
68#if (CONFIG_STORAGE & STORAGE_RAMDISK)
69 [STORAGE_DEC_IDX_RAMDISK] = RAMDISK_VOL_DEC,
70#endif
71#if (CONFIG_STORAGE & STORAGE_HOSTFS)
72 [STORAGE_DEC_IDX_HOSTFS] = HOSTFS_VOL_DEC,
73#endif
74 [STORAGE_NUM_DEC_IDX] = DEFAULT_VOL_DEC,
75};
76
77static const unsigned char storage_dec_indexes[STORAGE_NUM_TYPES+1] =
78{
79 [0 ... STORAGE_NUM_TYPES] = STORAGE_NUM_DEC_IDX,
80#if (CONFIG_STORAGE & STORAGE_ATA)
81 [STORAGE_ATA_NUM] = STORAGE_DEC_IDX_ATA,
82#endif
83#if (CONFIG_STORAGE & STORAGE_MMC)
84 [STORAGE_MMC_NUM] = STORAGE_DEC_IDX_MMC,
85#endif
86#if (CONFIG_STORAGE & STORAGE_SD)
87 [STORAGE_SD_NUM] = STORAGE_DEC_IDX_SD,
88#endif
89#if (CONFIG_STORAGE & STORAGE_NAND)
90 [STORAGE_NAND_NUM] = STORAGE_DEC_IDX_NAND,
91#endif
92#if (CONFIG_STORAGE & STORAGE_RAMDISK)
93 [STORAGE_RAMDISK_NUM] = STORAGE_DEC_IDX_RAMDISK,
94#endif
95#if (CONFIG_STORAGE & STORAGE_HOSTFS)
96 [STORAGE_HOSTFS_NUM] = STORAGE_DEC_IDX_HOSTFS,
97#endif
98};
99
100/* Returns on which volume this is and sets *nameptr to the portion of the
101 * path after the volume specifier, which could be the null if the path is
102 * just a volume root. If *nameptr > name, then a volume specifier was
103 * found. If 'greedy' is 'true', then it all separators after the volume
104 * specifier are consumed, if one was found.
105 */
106int path_strip_volume(const char *name, const char **nameptr, bool greedy)
107{
108 int volume = 0;
109 const char *t = name;
110 int c, v = 0;
111
112 /* format: "/<xxx##>/foo/bar"
113 * the "xxx" is pure decoration; only an unbroken trailing string of
114 * digits within the brackets is parsed as the volume number and of
115 * those, only the last ones VOL_MUM_MAX allows.
116 */
117 c = *(t = GOBBLE_PATH_SEPCH(t)); /* skip all leading slashes */
118 if (c != VOL_START_TOK) /* missing start token? no volume */
119 goto volume0;
120
121 do
122 {
123 switch (c)
124 {
125 case '0' ... '9': /* digit; parse volume number */
126 v = (v * 10 + c - '0') % VOL_NUM_MAX;
127 break;
128 case '\0':
129 case PATH_SEPCH: /* no closing bracket; no volume */
130 goto volume0;
131 default: /* something else; reset volume */
132 v = 0;
133 }
134 }
135 while ((c = *++t) != VOL_END_TOK); /* found end token? */
136
137 if (!(c = *++t)) /* no more path and no '/' is ok */
138 ;
139 else if (c != PATH_SEPCH) /* more path and no separator after end */
140 goto volume0;
141 else if (greedy)
142 t = GOBBLE_PATH_SEPCH(++t); /* strip remaining separators */
143
144 /* if 'greedy' is true and **nameptr == '\0' then it's only a volume
145 root whether or not it has trailing separators */
146
147 volume = v;
148 name = t;
149volume0:
150 if (nameptr)
151 *nameptr = name;
152 return volume;
153}
154
155/* Returns the volume specifier decorated with the storage type name.
156 * Assumes the supplied buffer size is at least {VOL_MAX_LEN}+1.
157 */
158int get_volume_name(int volume, char *buffer)
159{
160 if (volume < 0)
161 {
162 *buffer = '\0';
163 return 0;
164 }
165
166 volume %= VOL_NUM_MAX; /* as path parser would have it */
167
168 int type = storage_driver_type(volume_drive(volume));
169 if (type < 0 || type > STORAGE_NUM_TYPES)
170 type = STORAGE_NUM_TYPES;
171
172 const char *voldec = storage_dec_names[storage_dec_indexes[type]];
173 return snprintf(buffer, VOL_MAX_LEN + 1, "%c%s%d%c",
174 VOL_START_TOK, voldec, volume, VOL_END_TOK);
175}
176#endif /* HAVE_MULTIVOLUME */
177
178/* Just like path_strip_volume() but strips a leading drive specifier and
179 * returns the drive number (A=0, B=1, etc.). -1 means no drive was found.
180 * If 'greedy' is 'true', all separators after the volume are consumed.
181 */
182int path_strip_drive(const char *name, const char **nameptr, bool greedy)
183{
184 int c = toupper(*name);
185
186 if (c >= 'A' && c <= 'Z' && name[1] == PATH_DRVSEPCH)
187 {
188 name = &name[2];
189 if (greedy)
190 name = GOBBLE_PATH_SEPCH(name);
191
192 *nameptr = name;
193 return c - 'A';
194 }
195
196 *nameptr = name;
197 return -1;
198}
199
200/* Strips leading and trailing whitespace from a path
201 * " a/b \txyz" *nameptr->a, len=3: "a/b"
202 */
203size_t path_trim_whitespace(const char *name, const char **nameptr)
204{
205 int c;
206 while ((c = *name) <= ' ' && c)
207 ++name;
208
209 const char *first = name;
210 const char *last = name;
211
212 while (1)
213 {
214 if (c < ' ')
215 {
216 *nameptr = first;
217 return last - first;
218 }
219
220 while ((c = *++name) > ' ');
221 last = name;
222 while (c == ' ') c = *++name;
223 }
224}
225
226/* Strips directory components from the path
227 * "" *nameptr->NUL, len=0: ""
228 * "/" *nameptr->/, len=1: "/"
229 * "//" *nameptr->2nd /, len=1: "/"
230 * "/a" *nameptr->a, len=1: "a"
231 * "/a/bc" *nameptr->b, len=2: "bc"
232 * "d" *nameptr->d, len=1: "d"
233 * "ef/gh" *nameptr->g, len=2: "gh"
234 */
235size_t path_basename(const char *name, const char **nameptr)
236{
237 const char *p = name;
238 const char *q = p;
239 const char *r = q;
240
241 while (*(p = GOBBLE_PATH_SEPCH(p)))
242 {
243 q = p;
244 p = GOBBLE_PATH_COMP(++p);
245 r = p;
246 }
247
248 if (r == name && p > name)
249 q = p, r = q--; /* root - return last slash */
250 /* else path is an empty string */
251
252 *nameptr = q;
253 return r - q;
254}
255
256/* Strips the trailing component from the path
257 * "" *nameptr->NUL, len=0: ""
258 * "/" *nameptr->/, len=1: "/"
259 * "//" *nameptr->2nd /, len=1: "/"
260 * "/a" *nameptr->/, len=1: "/"
261 * "/a/bc" *nameptr->/, len=2: "/a"
262 * "d" *nameptr->d, len=0: ""
263 * "ef/gh" *nameptr->e, len=2: "ef"
264 */
265size_t path_dirname(const char *name, const char **nameptr)
266{
267 const char *p = GOBBLE_PATH_SEPCH(name);
268 const char *q = name;
269 const char *r = p;
270
271 while (*(p = GOBBLE_PATH_COMP(p)))
272 {
273 const char *s = p;
274
275 if (!*(p = GOBBLE_PATH_SEPCH(p)))
276 break;
277
278 q = s;
279 }
280
281 if (q == name && r > name)
282 name = r, q = name--; /* root - return last slash */
283
284 *nameptr = name;
285 return q - name;
286}
287
288/* Removes trailing separators from a path
289 * "" *nameptr->NUL, len=0: ""
290 * "/" *nameptr->/, len=1: "/"
291 * "//" *nameptr->2nd /, len=1: "/"
292 * "/a/" *nameptr->/, len=2: "/a"
293 * "//b/" *nameptr->1st /, len=3: "//b"
294 * "/c/" *nameptr->/, len=2: "/c"
295 */
296size_t path_strip_trailing_separators(const char *name, const char **nameptr)
297{
298 const char *p;
299 size_t len = path_basename(name, &p);
300
301 if (len == 1 && *p == '/' && p > name)
302 {
303 *nameptr = p;
304 name = p - 1; /* root with multiple separators */
305 }
306 else
307 {
308 *nameptr = name;
309 p += len; /* length to end of basename */
310 }
311
312 return p - name;
313}
314
315/* Transforms "wrong" separators into the correct ones
316 * "c:\windows\system32" -> "c:/windows/system32"
317 *
318 * 'path' and 'dstpath' may either be the same buffer or non-overlapping
319 */
320void path_correct_separators(char *dstpath, const char *path)
321{
322 char *dstp = dstpath;
323 const char *p = path;
324
325 while (1)
326 {
327 const char *next = strchr(p, PATH_BADSEPCH);
328 if (!next)
329 break;
330
331 size_t size = next - p;
332
333 if (dstpath != path)
334 memcpy(dstp, p, size); /* not in-place */
335
336 dstp += size;
337 *dstp++ = PATH_SEPCH;
338 p = next + 1;
339 }
340
341 if (dstpath != path)
342 strcpy(dstp, p);
343}
344
345/* Appends one path to another, adding separators between components if needed.
346 * Return value and behavior is otherwise as strlcpy so that truncation may be
347 * detected.
348 *
349 * For basepath and component:
350 * PA_SEP_HARD adds a separator even if the base path is empty
351 * PA_SEP_SOFT adds a separator only if the base path is not empty
352 */
353size_t path_append(char *buf, const char *basepath,
354 const char *component, size_t bufsize)
355{
356 const char *base = basepath && basepath[0] ? basepath : buf;
357 if (!base)
358 return bufsize; /* won't work to get lengths from buf */
359
360 if (!buf)
361 bufsize = 0;
362
363 if (path_is_absolute(component))
364 {
365 /* 'component' is absolute; replace all */
366 basepath = component;
367 component = "";
368 }
369
370 /* if basepath is not null or empty, buffer contents are replaced,
371 otherwise buf contains the base path */
372 size_t len = base == buf ? strlen(buf) : strlcpy(buf, basepath, bufsize);
373
374 bool separate = false;
375
376 if (!basepath || !component)
377 separate = !len || base[len-1] != PATH_SEPCH;
378 else if (component[0])
379 separate = len && base[len-1] != PATH_SEPCH;
380
381 /* caller might lie about size of buf yet use buf as the base */
382 if (base == buf && bufsize && len >= bufsize)
383 buf[bufsize - 1] = '\0';
384
385 buf += len;
386 bufsize -= MIN(len, bufsize);
387
388 if (separate && (len++, bufsize > 0) && --bufsize > 0)
389 *buf++ = PATH_SEPCH;
390
391 return len + strlcpy(buf, component ?: "", bufsize);
392}
393
394/* Returns the location and length of the next path component, consuming the
395 * input in the process.
396 *
397 * "/a/bc/d" breaks into:
398 * start: *namep->1st /
399 * call 1: *namep->a, *pathp->2nd / len=1: "a"
400 * call 2: *namep->b, *pathp->3rd / len=2: "bc"
401 * call 3: *namep->d, *pathp->NUL, len=1: "d"
402 * call 4: *namep->NUL, *pathp->NUL, len=0: ""
403 *
404 * Returns: 0 if the input has been consumed
405 * The length of the component otherwise
406 */
407ssize_t parse_path_component(const char **pathp, const char **namep)
408{
409 /* a component starts at a non-separator and continues until the next
410 separator or null */
411 const char *p = GOBBLE_PATH_SEPCH(*pathp);
412 const char *name = p;
413
414 if (*p)
415 p = GOBBLE_PATH_COMP(++p);
416
417 *pathp = p;
418 *namep = name;
419
420 return p - name;
421}