Add the needed assert changes to make the code clean for the new check-pointer-deref script. Note that for `_cmp()` functions its a bit of a question if we want `assert()` or `POINTER_MAY_BE_NULL()` here. The former is more correct but it adds a (small) runtime hit. If we use POINTER_MAY_BE_NULL() it is compiled out but strictly speaking it is wrong (so at least we would need a comment).
193 lines
5.2 KiB
C
193 lines
5.2 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
|
|
#include <dirent.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include "dirent-util.h"
|
|
#include "errno-util.h"
|
|
#include "glob-util.h"
|
|
#include "string-util.h"
|
|
#include "strv.h"
|
|
|
|
#ifndef __GLIBC__
|
|
static bool safe_glob_verify(const char *p, const char *prefix) {
|
|
POINTER_MAY_BE_NULL(p);
|
|
POINTER_MAY_BE_NULL(prefix);
|
|
|
|
if (isempty(p))
|
|
return false; /* should not happen, but for safey. */
|
|
|
|
if (prefix) {
|
|
/* Skip the prefix, as we allow dots in prefix.
|
|
* Note, glob() does not normalize paths, hence do not use path_startswith(). */
|
|
p = startswith(p, prefix);
|
|
if (!p)
|
|
return false; /* should not happen, but for safety. */
|
|
}
|
|
|
|
for (;;) {
|
|
p += strspn(p, "/");
|
|
if (*p == '\0')
|
|
return true;
|
|
if (*p == '.') {
|
|
p++;
|
|
if (IN_SET(*p, '/', '\0'))
|
|
return false; /* refuse dot */
|
|
if (*p == '.') {
|
|
p++;
|
|
if (IN_SET(*p, '/', '\0'))
|
|
return false; /* refuse dot dot */
|
|
}
|
|
}
|
|
|
|
p += strcspn(p, "/");
|
|
if (*p == '\0')
|
|
return true;
|
|
}
|
|
}
|
|
|
|
static int filter_glob_result(char * const *result, const char *path, char ***ret) {
|
|
int r;
|
|
|
|
assert(path);
|
|
|
|
if (strv_isempty(result))
|
|
return -ENOENT;
|
|
|
|
_cleanup_free_ char *prefix = NULL;
|
|
r = glob_non_glob_prefix(path, &prefix);
|
|
if (r < 0 && r != -ENOENT)
|
|
return r;
|
|
|
|
_cleanup_strv_free_ char **filtered = NULL;
|
|
size_t n_filtered = 0;
|
|
STRV_FOREACH(p, result) {
|
|
if (!safe_glob_verify(*p, prefix))
|
|
continue;
|
|
|
|
if (!ret)
|
|
return 0; /* Found at least one entry, let's return earlier. */
|
|
|
|
/* When musl is used, each entry is not a head of allocated memory. Hence, it is
|
|
* necessary to copy the string. */
|
|
r = strv_extend_with_size(&filtered, &n_filtered, *p);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
if (n_filtered == 0)
|
|
return -ENOENT;
|
|
|
|
assert(ret);
|
|
*ret = TAKE_PTR(filtered);
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
DEFINE_TRIVIAL_DESTRUCTOR(closedir_wrapper, void, closedir);
|
|
|
|
int safe_glob_full(const char *path, int flags, opendir_t opendir_func, char ***ret) {
|
|
_cleanup_(globfree) glob_t g = {
|
|
.gl_closedir = closedir_wrapper,
|
|
.gl_readdir = (struct dirent* (*)(void *)) readdir_no_dot,
|
|
.gl_opendir = (void* (*)(const char *)) (opendir_func ?: opendir),
|
|
.gl_lstat = lstat,
|
|
.gl_stat = stat,
|
|
};
|
|
int r;
|
|
|
|
assert(path);
|
|
|
|
errno = 0;
|
|
r = glob(path, flags | GLOB_ALTDIRFUNC, NULL, &g);
|
|
if (r == GLOB_NOMATCH)
|
|
return -ENOENT;
|
|
if (r == GLOB_NOSPACE)
|
|
return -ENOMEM;
|
|
if (r != 0)
|
|
return errno_or_else(EIO);
|
|
|
|
#ifndef __GLIBC__
|
|
return filter_glob_result(g.gl_pathv, path, ret);
|
|
#endif
|
|
|
|
if (strv_isempty(g.gl_pathv))
|
|
return -ENOENT;
|
|
|
|
if (ret) {
|
|
*ret = g.gl_pathv;
|
|
TAKE_STRUCT(g); /* To avoid the result being freed. */
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int glob_first(const char *path, char **ret) {
|
|
_cleanup_strv_free_ char **v = NULL;
|
|
int r;
|
|
|
|
assert(path);
|
|
|
|
r = safe_glob(path, GLOB_NOSORT|GLOB_BRACE, &v);
|
|
if (r == -ENOENT) {
|
|
if (ret)
|
|
*ret = NULL;
|
|
return false;
|
|
}
|
|
if (r < 0)
|
|
return r;
|
|
|
|
assert(!strv_isempty(v));
|
|
|
|
if (ret) {
|
|
/* Free all results except for the first one. */
|
|
STRV_FOREACH(p, strv_skip(v, 1))
|
|
*p = mfree(*p);
|
|
|
|
/* Then, take the first result. */
|
|
*ret = TAKE_PTR(*v);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int glob_extend(char ***strv, const char *path, int flags) {
|
|
char **v;
|
|
int r;
|
|
|
|
assert(path);
|
|
|
|
r = safe_glob(path, GLOB_NOSORT|GLOB_BRACE|flags, &v);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
return strv_extend_strv_consume(strv, v, /* filter_duplicates= */ false);
|
|
}
|
|
|
|
int glob_non_glob_prefix(const char *path, char **ret) {
|
|
assert(path);
|
|
assert(ret);
|
|
|
|
/* Return the path of the path that has no glob characters. */
|
|
|
|
size_t n = strcspn(path, GLOB_CHARS);
|
|
|
|
if (path[n] != '\0')
|
|
while (n > 0 && path[n-1] != '/')
|
|
n--;
|
|
|
|
if (n == 0)
|
|
return -ENOENT;
|
|
|
|
char *ans = strndup(path, n);
|
|
if (!ans)
|
|
return -ENOMEM;
|
|
*ret = ans;
|
|
return 0;
|
|
}
|
|
|
|
bool string_is_glob(const char *p) {
|
|
/* Check if a string contains any glob patterns. */
|
|
return !!strpbrk(p, GLOB_CHARS);
|
|
}
|