diff options
Diffstat (limited to 'firmware/common/pathfuncs.c')
-rw-r--r-- | firmware/common/pathfuncs.c | 421 |
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 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2014 by Michael Sevakis + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include <string.h> +#include <ctype.h> +#include "system.h" +#include "pathfuncs.h" +#include "string-extra.h" + +#ifdef HAVE_MULTIVOLUME +#include <stdio.h> +#include "storage.h" + +enum storage_name_dec_indexes +{ +#if (CONFIG_STORAGE & STORAGE_ATA) + STORAGE_DEC_IDX_ATA, +#endif +#if (CONFIG_STORAGE & STORAGE_MMC) + STORAGE_DEC_IDX_MMC, +#endif +#if (CONFIG_STORAGE & STORAGE_SD) + STORAGE_DEC_IDX_SD, +#endif +#if (CONFIG_STORAGE & STORAGE_NAND) + STORAGE_DEC_IDX_NAND, +#endif +#if (CONFIG_STORAGE & STORAGE_RAMDISK) + STORAGE_DEC_IDX_RAMDISK, +#endif +#if (CONFIG_STORAGE & STORAGE_HOSTFS) + STORAGE_DEC_IDX_HOSTFS, +#endif + STORAGE_NUM_DEC_IDX, +}; + +static const char * const storage_dec_names[STORAGE_NUM_DEC_IDX+1] = +{ +#if (CONFIG_STORAGE & STORAGE_ATA) + [STORAGE_DEC_IDX_ATA] = ATA_VOL_DEC, +#endif +#if (CONFIG_STORAGE & STORAGE_MMC) + [STORAGE_DEC_IDX_MMC] = MMC_VOL_DEC, +#endif +#if (CONFIG_STORAGE & STORAGE_SD) + [STORAGE_DEC_IDX_SD] = SD_VOL_DEC, +#endif +#if (CONFIG_STORAGE & STORAGE_NAND) + [STORAGE_DEC_IDX_NAND] = NAND_VOL_DEC, +#endif +#if (CONFIG_STORAGE & STORAGE_RAMDISK) + [STORAGE_DEC_IDX_RAMDISK] = RAMDISK_VOL_DEC, +#endif +#if (CONFIG_STORAGE & STORAGE_HOSTFS) + [STORAGE_DEC_IDX_HOSTFS] = HOSTFS_VOL_DEC, +#endif + [STORAGE_NUM_DEC_IDX] = DEFAULT_VOL_DEC, +}; + +static const unsigned char storage_dec_indexes[STORAGE_NUM_TYPES+1] = +{ + [0 ... STORAGE_NUM_TYPES] = STORAGE_NUM_DEC_IDX, +#if (CONFIG_STORAGE & STORAGE_ATA) + [STORAGE_ATA_NUM] = STORAGE_DEC_IDX_ATA, +#endif +#if (CONFIG_STORAGE & STORAGE_MMC) + [STORAGE_MMC_NUM] = STORAGE_DEC_IDX_MMC, +#endif +#if (CONFIG_STORAGE & STORAGE_SD) + [STORAGE_SD_NUM] = STORAGE_DEC_IDX_SD, +#endif +#if (CONFIG_STORAGE & STORAGE_NAND) + [STORAGE_NAND_NUM] = STORAGE_DEC_IDX_NAND, +#endif +#if (CONFIG_STORAGE & STORAGE_RAMDISK) + [STORAGE_RAMDISK_NUM] = STORAGE_DEC_IDX_RAMDISK, +#endif +#if (CONFIG_STORAGE & STORAGE_HOSTFS) + [STORAGE_HOSTFS_NUM] = STORAGE_DEC_IDX_HOSTFS, +#endif +}; + +/* Returns on which volume this is and sets *nameptr to the portion of the + * path after the volume specifier, which could be the null if the path is + * just a volume root. If *nameptr > name, then a volume specifier was + * found. If 'greedy' is 'true', then it all separators after the volume + * specifier are consumed, if one was found. + */ +int path_strip_volume(const char *name, const char **nameptr, bool greedy) +{ + int volume = 0; + const char *t = name; + int c, v = 0; + + /* format: "/<xxx##>/foo/bar" + * the "xxx" is pure decoration; only an unbroken trailing string of + * digits within the brackets is parsed as the volume number and of + * those, only the last ones VOL_MUM_MAX allows. + */ + c = *(t = GOBBLE_PATH_SEPCH(t)); /* skip all leading slashes */ + if (c != VOL_START_TOK) /* missing start token? no volume */ + goto volume0; + + do + { + switch (c) + { + case '0' ... '9': /* digit; parse volume number */ + v = (v * 10 + c - '0') % VOL_NUM_MAX; + break; + case '\0': + case PATH_SEPCH: /* no closing bracket; no volume */ + goto volume0; + default: /* something else; reset volume */ + v = 0; + } + } + while ((c = *++t) != VOL_END_TOK); /* found end token? */ + + if (!(c = *++t)) /* no more path and no '/' is ok */ + ; + else if (c != PATH_SEPCH) /* more path and no separator after end */ + goto volume0; + else if (greedy) + t = GOBBLE_PATH_SEPCH(++t); /* strip remaining separators */ + + /* if 'greedy' is true and **nameptr == '\0' then it's only a volume + root whether or not it has trailing separators */ + + volume = v; + name = t; +volume0: + if (nameptr) + *nameptr = name; + return volume; +} + +/* Returns the volume specifier decorated with the storage type name. + * Assumes the supplied buffer size is at least {VOL_MAX_LEN}+1. + */ +int get_volume_name(int volume, char *buffer) +{ + if (volume < 0) + { + *buffer = '\0'; + return 0; + } + + volume %= VOL_NUM_MAX; /* as path parser would have it */ + + int type = storage_driver_type(volume_drive(volume)); + if (type < 0 || type > STORAGE_NUM_TYPES) + type = STORAGE_NUM_TYPES; + + const char *voldec = storage_dec_names[storage_dec_indexes[type]]; + return snprintf(buffer, VOL_MAX_LEN + 1, "%c%s%d%c", + VOL_START_TOK, voldec, volume, VOL_END_TOK); +} +#endif /* HAVE_MULTIVOLUME */ + +/* Just like path_strip_volume() but strips a leading drive specifier and + * returns the drive number (A=0, B=1, etc.). -1 means no drive was found. + * If 'greedy' is 'true', all separators after the volume are consumed. + */ +int path_strip_drive(const char *name, const char **nameptr, bool greedy) +{ + int c = toupper(*name); + + if (c >= 'A' && c <= 'Z' && name[1] == PATH_DRVSEPCH) + { + name = &name[2]; + if (greedy) + name = GOBBLE_PATH_SEPCH(name); + + *nameptr = name; + return c - 'A'; + } + + *nameptr = name; + return -1; +} + +/* Strips leading and trailing whitespace from a path + * " a/b \txyz" *nameptr->a, len=3: "a/b" + */ +size_t path_trim_whitespace(const char *name, const char **nameptr) +{ + int c; + while ((c = *name) <= ' ' && c) + ++name; + + const char *first = name; + const char *last = name; + + while (1) + { + if (c < ' ') + { + *nameptr = first; + return last - first; + } + + while ((c = *++name) > ' '); + last = name; + while (c == ' ') c = *++name; + } +} + +/* Strips directory components from the path + * "" *nameptr->NUL, len=0: "" + * "/" *nameptr->/, len=1: "/" + * "//" *nameptr->2nd /, len=1: "/" + * "/a" *nameptr->a, len=1: "a" + * "/a/bc" *nameptr->b, len=2: "bc" + * "d" *nameptr->d, len=1: "d" + * "ef/gh" *nameptr->g, len=2: "gh" + */ +size_t path_basename(const char *name, const char **nameptr) +{ + const char *p = name; + const char *q = p; + const char *r = q; + + while (*(p = GOBBLE_PATH_SEPCH(p))) + { + q = p; + p = GOBBLE_PATH_COMP(++p); + r = p; + } + + if (r == name && p > name) + q = p, r = q--; /* root - return last slash */ + /* else path is an empty string */ + + *nameptr = q; + return r - q; +} + +/* Strips the trailing component from the path + * "" *nameptr->NUL, len=0: "" + * "/" *nameptr->/, len=1: "/" + * "//" *nameptr->2nd /, len=1: "/" + * "/a" *nameptr->/, len=1: "/" + * "/a/bc" *nameptr->/, len=2: "/a" + * "d" *nameptr->d, len=0: "" + * "ef/gh" *nameptr->e, len=2: "ef" + */ +size_t path_dirname(const char *name, const char **nameptr) +{ + const char *p = GOBBLE_PATH_SEPCH(name); + const char *q = name; + const char *r = p; + + while (*(p = GOBBLE_PATH_COMP(p))) + { + const char *s = p; + + if (!*(p = GOBBLE_PATH_SEPCH(p))) + break; + + q = s; + } + + if (q == name && r > name) + name = r, q = name--; /* root - return last slash */ + + *nameptr = name; + return q - name; +} + +/* Removes trailing separators from a path + * "" *nameptr->NUL, len=0: "" + * "/" *nameptr->/, len=1: "/" + * "//" *nameptr->2nd /, len=1: "/" + * "/a/" *nameptr->/, len=2: "/a" + * "//b/" *nameptr->1st /, len=3: "//b" + * "/c/" *nameptr->/, len=2: "/c" + */ +size_t path_strip_trailing_separators(const char *name, const char **nameptr) +{ + const char *p; + size_t len = path_basename(name, &p); + + if (len == 1 && *p == '/' && p > name) + { + *nameptr = p; + name = p - 1; /* root with multiple separators */ + } + else + { + *nameptr = name; + p += len; /* length to end of basename */ + } + + return p - name; +} + +/* Transforms "wrong" separators into the correct ones + * "c:\windows\system32" -> "c:/windows/system32" + * + * 'path' and 'dstpath' may either be the same buffer or non-overlapping + */ +void path_correct_separators(char *dstpath, const char *path) +{ + char *dstp = dstpath; + const char *p = path; + + while (1) + { + const char *next = strchr(p, PATH_BADSEPCH); + if (!next) + break; + + size_t size = next - p; + + if (dstpath != path) + memcpy(dstp, p, size); /* not in-place */ + + dstp += size; + *dstp++ = PATH_SEPCH; + p = next + 1; + } + + if (dstpath != path) + strcpy(dstp, p); +} + +/* Appends one path to another, adding separators between components if needed. + * Return value and behavior is otherwise as strlcpy so that truncation may be + * detected. + * + * For basepath and component: + * PA_SEP_HARD adds a separator even if the base path is empty + * PA_SEP_SOFT adds a separator only if the base path is not empty + */ +size_t path_append(char *buf, const char *basepath, + const char *component, size_t bufsize) +{ + const char *base = basepath && basepath[0] ? basepath : buf; + if (!base) + return bufsize; /* won't work to get lengths from buf */ + + if (!buf) + bufsize = 0; + + if (path_is_absolute(component)) + { + /* 'component' is absolute; replace all */ + basepath = component; + component = ""; + } + + /* if basepath is not null or empty, buffer contents are replaced, + otherwise buf contains the base path */ + size_t len = base == buf ? strlen(buf) : strlcpy(buf, basepath, bufsize); + + bool separate = false; + + if (!basepath || !component) + separate = !len || base[len-1] != PATH_SEPCH; + else if (component[0]) + separate = len && base[len-1] != PATH_SEPCH; + + /* caller might lie about size of buf yet use buf as the base */ + if (base == buf && bufsize && len >= bufsize) + buf[bufsize - 1] = '\0'; + + buf += len; + bufsize -= MIN(len, bufsize); + + if (separate && (len++, bufsize > 0) && --bufsize > 0) + *buf++ = PATH_SEPCH; + + return len + strlcpy(buf, component ?: "", bufsize); +} + +/* Returns the location and length of the next path component, consuming the + * input in the process. + * + * "/a/bc/d" breaks into: + * start: *namep->1st / + * call 1: *namep->a, *pathp->2nd / len=1: "a" + * call 2: *namep->b, *pathp->3rd / len=2: "bc" + * call 3: *namep->d, *pathp->NUL, len=1: "d" + * call 4: *namep->NUL, *pathp->NUL, len=0: "" + * + * Returns: 0 if the input has been consumed + * The length of the component otherwise + */ +ssize_t parse_path_component(const char **pathp, const char **namep) +{ + /* a component starts at a non-separator and continues until the next + separator or null */ + const char *p = GOBBLE_PATH_SEPCH(*pathp); + const char *name = p; + + if (*p) + p = GOBBLE_PATH_COMP(++p); + + *pathp = p; + *namep = name; + + return p - name; +} |