path-util: unify path_extract_filename/directory into path_split_prefix_filename() (#40608)

This commit is contained in:
Mike Yuan
2026-02-09 11:03:14 +01:00
committed by GitHub
10 changed files with 74 additions and 96 deletions
+5 -6
View File
@@ -69,15 +69,14 @@ int make_lock_file_for(const char *p, int operation, LockFile *ret) {
assert(p);
assert(ret);
r = path_extract_filename(p, &fn);
r = path_split_prefix_filename(p, &dn, &fn);
if (r < 0)
return r;
r = path_extract_directory(p, &dn);
if (r < 0)
return r;
t = strjoin(dn, "/.#", fn, ".lck");
if (dn)
t = strjoin(dn, "/.#", fn, ".lck");
else
t = strjoin(".#", fn, ".lck");
if (!t)
return -ENOMEM;
+41 -54
View File
@@ -1027,8 +1027,7 @@ int path_find_last_component(const char *path, bool accept_dot_dot, const char *
q = path + strlen(path) - 1;
q = skip_slash_or_dot_backward(path, q);
if (!q || /* the root directory */
(q == path && *q == '.')) { /* path is "." or "./" */
if (!q) { /* the root directory, "." or "./" */
if (next)
*next = path;
if (ret)
@@ -1102,18 +1101,17 @@ const char* last_path_component(const char *path) {
return path + k;
}
int path_extract_filename(const char *path, char **ret) {
_cleanup_free_ char *a = NULL;
int path_split_prefix_filename(const char *path, char **ret_dir, char **ret_filename) {
_cleanup_free_ char *d = NULL;
const char *c, *next = NULL;
int r;
/* 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:
/* Split the path into dir prefix/filename pair. Returns:
*
* -EINVAL → if the path is not valid
* -EADDRNOTAVAIL → if only a directory was specified, but no filename, i.e. the root dir
* itself or "." is specified
* -EADDRNOTAVAIL → if the path refers to the uppermost directory in hierarchy (i.e. has neither
* dir prefix nor filename - the root dir itself or ".")
* -EDESTADDRREQ → if only a filename was passed, and caller only specifies ret_dir
* -ENOMEM → no memory
*
* Returns >= 0 on success. If the input path has a trailing slash, returns O_DIRECTORY, to
@@ -1122,63 +1120,52 @@ int path_extract_filename(const char *path, char **ret) {
* 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(path))
if (isempty(path))
return -EINVAL;
r = path_find_last_component(path, false, &next, &c);
r = path_find_last_component(path, /* accept_dot_dot = */ false, &next, &c);
if (r < 0)
return r;
if (r == 0) /* root directory */
if (r == 0) /* root directory or "." */
return -EADDRNOTAVAIL;
a = strndup(c, r);
if (!a)
return -ENOMEM;
if (ret_dir) {
if (next == path) {
if (*path != '/') { /* filename only */
if (!ret_filename)
return -EDESTADDRREQ;
} else {
d = strdup("/");
if (!d)
return -ENOMEM;
}
} else {
d = strndup(path, next - path);
if (!d)
return -ENOMEM;
*ret = TAKE_PTR(a);
return strlen(c) > (size_t) r ? O_DIRECTORY : 0;
}
path_simplify(d);
int path_extract_directory(const char *path, char **ret) {
const char *c, *next = NULL;
int r;
if (!path_is_valid(d))
return -EINVAL;
}
/* The inverse of path_extract_filename(), i.e. returns the directory path prefix. Returns:
*
* -EINVAL → if the path is not valid
* -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 or "." was specified
* -ENOMEM → no memory (surprise!)
*
* This function guarantees to return a fully valid path, i.e. one that passes path_is_valid().
*/
r = path_find_last_component(path, false, &next, &c);
if (r < 0)
return r;
if (r == 0) /* empty or root */
return isempty(path) ? -EINVAL : -EADDRNOTAVAIL;
if (next == path) {
if (*path != '/') /* filename only */
return -EDESTADDRREQ;
return strdup_to(ret, "/");
}
_cleanup_free_ char *a = strndup(path, next - path);
if (!a)
return -ENOMEM;
path_simplify(a);
if (!path_is_valid(a))
} else if (!path_is_valid(path))
/* We didn't validate the dir prefix, hence check if the whole path is valid now */
return -EINVAL;
if (ret)
*ret = TAKE_PTR(a);
if (ret_filename) {
char *fn = strndup(c, r);
if (!fn)
return -ENOMEM;
return 0;
*ret_filename = fn;
}
if (ret_dir)
*ret_dir = TAKE_PTR(d);
return strlen(c) > (size_t) r ? O_DIRECTORY : 0;
}
bool filename_part_is_valid(const char *p) {
+9 -2
View File
@@ -135,8 +135,15 @@ int fsck_exists_for_fstype(const char *fstype);
int path_find_first_component(const char **p, bool accept_dot_dot, const char **ret);
int path_find_last_component(const char *path, bool accept_dot_dot, const char **next, const char **ret);
const char* last_path_component(const char *path);
int path_extract_filename(const char *path, char **ret);
int path_extract_directory(const char *path, char **ret);
int path_split_prefix_filename(const char *path, char **ret_dir, char **ret_filename);
static inline int path_extract_filename(const char *path, char **ret) {
return path_split_prefix_filename(path, NULL, ret);
}
static inline int path_extract_directory(const char *path, char **ret) {
int r = path_split_prefix_filename(path, ret, NULL);
return r < 0 ? r : 0; /* suppress O_DIRECTORY */
}
bool filename_part_is_valid(const char *p) _pure_;
bool filename_is_valid(const char *p) _pure_;
+3 -8
View File
@@ -161,17 +161,12 @@ static int tempfn_build(const char *p, const char *pre, const char *post, bool c
if (!d)
return -ENOMEM;
} else {
r = path_extract_directory(p, &d);
if (r < 0 && r != -EDESTADDRREQ) /* EDESTADDRREQ → No directory specified, just a filename */
return r;
r = path_extract_filename(p, &fn);
r = path_split_prefix_filename(p, &d, &fn);
if (r < 0)
return r;
if (strlen(fn) > NAME_MAX - len_add)
/* We cannot simply prepend and append strings to the filename. Let's truncate the filename. */
fn[NAME_MAX - len_add] = '\0';
/* Truncate the filename if it would become too long after mangling. */
strshorten(fn, NAME_MAX - len_add);
}
nf = strjoin(".#", strempty(pre), strempty(fn), strempty(post));