#define _GNU_SOURCE 1 #include #include #include #include #include #include #include #include #include "nspath.h" #ifndef PATH_MAX # define PATH_MAX 4096 #endif #define PCOMP_IGNORE (1 << 0) /* marks negated or ignored components */ #define PCOMP_ALLOC (1 << 1) /* future used for symlink resolutions */ /* path component struct */ struct pcomp { char *str; /* file- or directoryname */ unsigned int len; /* length of this component */ int flags; }; static inline int path_components(const char *path) { char *slash; int comps = 1; if (!path) return 0; for (slash = strchr(path, '/'); slash; slash = strchr(slash + 1, '/')) comps++; return comps; } static char *pcomp_construct(struct pcomp *pcomp, int comps) { int i, plen = 0, offset = 0; char *path; for (i = 0; i < comps; i++) { if(pcomp[i].flags & PCOMP_IGNORE) continue; plen += pcomp[i].len + 1; } path = malloc(plen + 2); for (i = 0; i < comps; i++) { if(pcomp[i].flags & PCOMP_IGNORE) continue; memcpy(path + offset, pcomp[i].str, pcomp[i].len); offset += pcomp[i].len; if (i < comps - 1) path[offset++] = '/'; } path[offset] = 0; return path; } /* * Converts "foo/bar/.././lala.txt" to "foo/lala.txt". * "../../../../../bar/../foo/" becomes "/foo/" */ char *nspath_normalize(const char *orig_path) { struct pcomp *pcomp = NULL; int comps, i = 0, m, depth = 0, have_slash = 0; char *path, *rpath, *p, *slash; if (!orig_path || !*orig_path) return NULL; rpath = strdup(orig_path); comps = path_components(rpath); pcomp = calloc(comps, sizeof(struct pcomp)); if (pcomp == NULL) { free(rpath); return NULL; } p = pcomp[0].str = rpath; for (; p; p = slash, i++) { slash = strchr(p, '/'); if (slash) { have_slash = 1; *slash = 0; slash++; } pcomp[i].len = strlen(p); pcomp[i].str = p; if (*p == '.') { if (p[1] == 0) { /* dot-slash is always just ignored */ pcomp[i].flags |= PCOMP_IGNORE; continue; } if ((*orig_path == '/' || depth) && p[1] == '.' && p[2] == 0) { /* dot-dot-slash negates the previous non-negated component */ pcomp[i].flags |= PCOMP_IGNORE; for(m = 0; depth && m <= i; m++) { if (pcomp[i - m].flags & PCOMP_IGNORE) { continue; } pcomp[i - m].flags |= PCOMP_IGNORE; depth--; break; /* we only remove one level at most */ } continue; } } /* convert multiple slashes to one */ if (i && !*p) { pcomp[i].flags |= PCOMP_IGNORE; continue; } depth++; } /* * If we back up all the way to the root we need to add a slash * as the first path component. */ if (have_slash && depth == 1) { pcomp[0].flags &= ~(PCOMP_IGNORE); pcomp[0].str[0] = 0; pcomp[0].len = 0; } path = pcomp_construct(pcomp, comps); free(rpath); free(pcomp); return path; } char *nspath_absolute(const char *rel_path, const char *base) { char cwd[PATH_MAX]; int len; char *path = NULL, *normpath; if (*rel_path == '/') return nspath_normalize(rel_path); if (!base) { if (getcwd(cwd, sizeof(cwd)) == NULL) return NULL; base = cwd; } len = asprintf(&path, "%s/%s", base, rel_path); if (len <= 0) { if (path) free(path); return NULL; } normpath = nspath_normalize(path); free(path); return normpath; } char *nspath_real(const char *rel_path, const char *base) { char *abspath, *ret; if (!(abspath = nspath_absolute(rel_path, base))) return NULL; ret = realpath(abspath, NULL); free(abspath); return ret; } /* we must take care not to destroy the original buffer here */ char *nspath_absolute_dirname(const char *path, const char *base) { char *buf, *ret; if (!(buf = nspath_absolute(path, base))) return NULL; ret = strdup(dirname(buf)); free(buf); return ret; } int nspath_mkdir_p(const char *orig_path, mode_t mode, int options) { char *sep, *path; int ret = 0, mkdir_start = 0; if (!orig_path) { errno = EFAULT; return -1; } sep = path = strdup(orig_path); if (!sep) return -1; for (;;) { struct stat st; if ((sep = strchr(sep + 1, '/'))) { *sep = 0; /* nul-terminate path */ } else if (options & NSPATH_MKDIR_SKIP_LAST) { break; } /* stat() our way up the tree and start mkdir() on ENOENT */ if (!mkdir_start && (ret = stat(path, &st)) < 0) { if (errno != ENOENT) { break; } mkdir_start = 1; } if (mkdir_start && (ret = mkdir(path, mode)) < 0) { break; } /* end of path or trailing slash? */ if (!sep || !sep[1]) break; *sep = '/'; } free(path); return ret; }