Merge pull request #18834 from poettering/path-extract

Beef up path_extract_filename() a bit, and introduce path_extract_directory()
This commit is contained in:
Luca Boccassi
2021-03-02 19:22:53 +00:00
committed by GitHub
7 changed files with 282 additions and 47 deletions
+57 -4
View File
@@ -819,11 +819,21 @@ const char *last_path_component(const char *path) {
int path_extract_filename(const char *p, char **ret) {
_cleanup_free_ char *a = NULL;
const char *c;
size_t n;
/* Extracts the filename part (i.e. right-most component) from a path, i.e. string that passes
* filename_is_valid(). A wrapper around last_path_component(), but eats up trailing slashes. Returns
* -EADDRNOTAVAIL if specified parameter includes no filename (i.e. is "/" or so). Returns -EINVAL if
* not a valid path in the first place. */
* filename_is_valid(). A wrapper around last_path_component(), but eats up trailing
* slashes. Returns:
*
* -EINVAL → if the passed in path is not a valid path
* -EADDRNOTAVAIL → if only a directory was specified, but no filename, i.e. the root dir itself is specified
* -ENOMEM → no memory
*
* Returns >= 0 on success. If the input path has a trailing slash, returns O_DIRECTORY, to indicate
* the referenced file must be a directory.
*
* This function guarantees to return a fully valid filename, i.e. one that passes
* filename_is_valid() – this means "." and ".." are not accepted. */
if (!path_is_valid(p))
return -EINVAL;
@@ -834,14 +844,57 @@ int path_extract_filename(const char *p, char **ret) {
return -EADDRNOTAVAIL;
c = last_path_component(p);
n = strcspn(c, "/");
a = strndup(c, strcspn(c, "/"));
a = strndup(c, n);
if (!a)
return -ENOMEM;
if (!filename_is_valid(a))
return -EINVAL;
*ret = TAKE_PTR(a);
return c[n] == '/' ? O_DIRECTORY : 0;
}
int path_extract_directory(const char *p, char **ret) {
_cleanup_free_ char *a = NULL;
const char *c;
/* The inverse of path_extract_filename(), i.e. returns the directory path prefix. Returns:
*
* -EINVAL → if the passed in path is not a valid path
* -EDESTADDRREQ → if no directory was specified in the passed in path, i.e. only a filename was passed
* -EADDRNOTAVAIL → if the passed in parameter had no filename but did have a directory, i.e. the root dir itself was specified
* -ENOMEM → no memory (surprise!)
*
* This function guarantees to return a fully valid path, i.e. one that passes path_is_valid().
*/
if (!path_is_valid(p))
return -EINVAL;
/* Special case the root dir, because otherwise for an input of "///" last_path_component() returns
* the pointer to the last slash only, which might be seen as a valid path below. */
if (path_equal(p, "/"))
return -EADDRNOTAVAIL;
c = last_path_component(p);
/* Delete trailing slashes, but keep one */
while (c > p+1 && c[-1] == '/')
c--;
if (p == c) /* No path whatsoever? Then return a recognizable error */
return -EDESTADDRREQ;
a = strndup(p, c - p);
if (!a)
return -ENOMEM;
if (!path_is_valid(a))
return -EINVAL;
*ret = TAKE_PTR(a);
return 0;
}
+1
View File
@@ -147,6 +147,7 @@ int fsck_exists(const char *fstype);
char* dirname_malloc(const char *path);
const char *last_path_component(const char *path);
int path_extract_filename(const char *p, char **ret);
int path_extract_directory(const char *p, char **ret);
bool filename_is_valid(const char *p) _pure_;
bool path_is_valid(const char *p) _pure_;
+46 -37
View File
@@ -94,16 +94,11 @@ int fmkostemp_safe(char *pattern, const char *mode, FILE **ret_f) {
}
int tempfn_xxxxxx(const char *p, const char *extra, char **ret) {
const char *fn;
char *t;
_cleanup_free_ char *d = NULL, *fn = NULL, *nf = NULL;
int r;
assert(ret);
if (isempty(p))
return -EINVAL;
if (path_equal(p, "/"))
return -EINVAL;
/*
* Turns this:
* /foo/bar/waldo
@@ -112,34 +107,41 @@ int tempfn_xxxxxx(const char *p, const char *extra, char **ret) {
* /foo/bar/.#<extra>waldoXXXXXX
*/
fn = basename(p);
if (!filename_is_valid(fn))
return -EINVAL;
r = path_extract_directory(p, &d);
if (r < 0 && r != -EDESTADDRREQ) /* EDESTADDRREQ → No directory specified, just a filename */
return r;
extra = strempty(extra);
r = path_extract_filename(p, &fn);
if (r < 0)
return r;
t = new(char, strlen(p) + 2 + strlen(extra) + 6 + 1);
if (!t)
nf = strjoin(".#", strempty(extra), fn, "XXXXXX");
if (!nf)
return -ENOMEM;
strcpy(stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), extra), fn), "XXXXXX");
if (!filename_is_valid(nf)) /* New name is not valid? (Maybe because too long?) Refuse. */
return -EINVAL;
if (d) {
char *j;
j = path_join(d, nf);
if (!j)
return -ENOMEM;
*ret = path_simplify(j, false);
} else
*ret = TAKE_PTR(nf);
*ret = path_simplify(t, false);
return 0;
}
int tempfn_random(const char *p, const char *extra, char **ret) {
const char *fn;
char *t, *x;
uint64_t u;
_cleanup_free_ char *d = NULL, *fn = NULL, *nf = NULL;
int r;
assert(ret);
if (isempty(p))
return -EINVAL;
if (path_equal(p, "/"))
return -EINVAL;
/*
* Turns this:
* /foo/bar/waldo
@@ -148,27 +150,34 @@ int tempfn_random(const char *p, const char *extra, char **ret) {
* /foo/bar/.#<extra>waldobaa2a261115984a9
*/
fn = basename(p);
if (!filename_is_valid(fn))
return -EINVAL;
r = path_extract_directory(p, &d);
if (r < 0 && r != -EDESTADDRREQ) /* EDESTADDRREQ → No directory specified, just a filename */
return r;
extra = strempty(extra);
r = path_extract_filename(p, &fn);
if (r < 0)
return r;
t = new(char, strlen(p) + 2 + strlen(extra) + 16 + 1);
if (!t)
if (asprintf(&nf, ".#%s%s%016" PRIx64,
strempty(extra),
fn,
random_u64()) < 0)
return -ENOMEM;
x = stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), extra), fn);
if (!filename_is_valid(nf)) /* Not valid? (maybe because too long now?) — refuse early */
return -EINVAL;
u = random_u64();
for (unsigned i = 0; i < 16; i++) {
*(x++) = hexchar(u & 0xF);
u >>= 4;
}
if (d) {
char *j;
*x = 0;
j = path_join(d, nf);
if (!j)
return -ENOMEM;
*ret = path_simplify(j, false);
} else
*ret = TAKE_PTR(nf);
*ret = path_simplify(t, false);
return 0;
}