// Copyright 2024 syzkaller project authors. All rights reserved. // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. #include #include #include #include #include #include #include #include #include #include #include static std::vector Glob(const std::string& pattern) { glob_t buf = {}; buf.gl_opendir = reinterpret_cast(opendir); buf.gl_closedir = reinterpret_cast(closedir); // Use own readdir to ignore links. Links to files are not useful to us, // we will discover the target file itself. Links to directories are harmful // because they cause recursion, or lead outside of the target glob // (e.g. /proc/self/{root,cwd}). // However, we want to keep few links: /proc/self, /proc/thread-self, // /sys/kernel/slab/kmalloc-64 (may be a link with slab merging), // and cgroup links created in the test dir. // This is a hacky way to do it b/c e.g. "self" will be matched in all paths, // not just /proc. A proper fix would require writing completly custom version of glob // that would support recursion and would allow using/not using links on demand. buf.gl_readdir = [](void* dir) -> dirent* { for (;;) { struct dirent* ent = readdir(static_cast(dir)); if (!ent || ent->d_type != DT_LNK || !strcmp(ent->d_name, "self") || !strcmp(ent->d_name, "thread-self") || !strcmp(ent->d_name, "kmalloc-64") || !strcmp(ent->d_name, "cgroup") || !strcmp(ent->d_name, "cgroup.cpu") || !strcmp(ent->d_name, "cgroup.net")) return ent; } }; buf.gl_stat = stat; buf.gl_lstat = lstat; int res = glob(pattern.c_str(), GLOB_MARK | GLOB_NOSORT | GLOB_ALTDIRFUNC, nullptr, &buf); if (res != 0 && res != GLOB_NOMATCH) failmsg("glob failed", "pattern='%s' res=%d", pattern.c_str(), res); std::vector files; for (size_t i = 0; i < buf.gl_pathc; i++) { const char* file = buf.gl_pathv[i]; if (file[strlen(file) - 1] == '/') continue; files.push_back(file); } globfree(&buf); debug("glob %s resolved to %zu files\n", pattern.c_str(), files.size()); return files; } static std::unique_ptr ReadFile(const std::string& file) { auto info = std::make_unique(); info->name = file; int fd = open(file.c_str(), O_RDONLY); if (fd == -1) { info->exists = errno != EEXIST && errno != ENOENT; info->error = strerror(errno); } else { info->exists = true; for (;;) { constexpr size_t kChunk = 4 << 10; info->data.resize(info->data.size() + kChunk); ssize_t n = read(fd, info->data.data() + info->data.size() - kChunk, kChunk); if (n < 0) { info->error = strerror(errno); break; } info->data.resize(info->data.size() - kChunk + n); if (n == 0) break; } close(fd); } debug("reading file %s: size=%zu exists=%d error=%s\n", info->name.c_str(), info->data.size(), info->exists, info->error.c_str()); return info; } static std::string ReadTextFile(const char* file_fmt, ...) { char file[1024]; va_list args; va_start(args, file_fmt); vsnprintf(file, sizeof(file), file_fmt, args); va_end(args); file[sizeof(file) - 1] = 0; auto data = ReadFile(file)->data; std::string str(data.begin(), data.end()); while (!str.empty() && (str.back() == '\n' || str.back() == 0)) str.resize(str.size() - 1); return str; } static std::vector> ReadFiles(const std::vector& files) { std::vector> results; for (const auto& file : files) { if (!strchr(file.c_str(), '*')) { results.push_back(ReadFile(file)); continue; } for (const auto& match : Glob(file)) results.push_back(ReadFile(match)); } return results; }