summaryrefslogtreecommitdiffstats
path: root/tools/lib/api/fs
diff options
context:
space:
mode:
authorNiklas Cassel <cassel@kernel.org>2026-09-16 15:00:32 +0200
committerNiklas Cassel <cassel@kernel.org>2026-09-16 16:18:00 +0200
commit8d836581f9b1f57ceaa2b47654754ef1260b410b (patch)
tree1dbd4d0dfb99c2e238356cbe947e5b0be3e3a571 /tools/lib/api/fs
downloadlinux-stable-8d836581f9b1f57ceaa2b47654754ef1260b410b.tar.gz
linux-stable-8d836581f9b1f57ceaa2b47654754ef1260b410b.zip
ata: libata-scsi: fix ata_dsm_trim_pages() kernel-docgrafted
make htmldocs fails with: Documentation/driver-api/libata:604: ./drivers/ata/libata-scsi.c:2301: ERROR: Unexpected indentation. [docutils] The Return: section of ata_dsm_trim_pages() ends its first sentence with a colon and continues with an indented bullet list. reStructuredText requires a blank line before an indented block, so docutils chokes on the list. Simply adding the missing blank line does not work either: Return: is a kernel-doc "special section", which is terminated by the first blank line, so the bullet list would end up in the Description section, detached from the sentence introducing it. Spell the two bounds out as prose instead, so that the Return: section stays self-contained. Documentation-only change. Fixes: e64e6b5dc867 ("ata: libata-scsi: scale DSM TRIM payload by MAX PAGES PER DSM COMMAND") Reported-by: Thomas Huth <thuth@redhat.com> Closes: https://lore.kernel.org/linux-ide/b0a0b8e8-cc4f-4c7b-8bc5-fee0712d405a@redhat.com/ Reviewed-by: Damien Le Moal <dlemoal@kernel.org> Reviewed-by: Thomas Huth <thuth@redhat.com> Link: https://lore.kernel.org/r/20260916130031.29990-2-cassel@kernel.org Signed-off-by: Niklas Cassel <cassel@kernel.org>
Diffstat (limited to 'tools/lib/api/fs')
-rw-r--r--tools/lib/api/fs/Build3
-rw-r--r--tools/lib/api/fs/cgroup.c107
-rw-r--r--tools/lib/api/fs/fs.c515
-rw-r--r--tools/lib/api/fs/fs.h62
-rw-r--r--tools/lib/api/fs/tracing_path.c178
-rw-r--r--tools/lib/api/fs/tracing_path.h23
6 files changed, 888 insertions, 0 deletions
diff --git a/tools/lib/api/fs/Build b/tools/lib/api/fs/Build
new file mode 100644
index 000000000..0f75b2865
--- /dev/null
+++ b/tools/lib/api/fs/Build
@@ -0,0 +1,3 @@
+libapi-y += fs.o
+libapi-y += tracing_path.o
+libapi-y += cgroup.o
diff --git a/tools/lib/api/fs/cgroup.c b/tools/lib/api/fs/cgroup.c
new file mode 100644
index 000000000..250629a09
--- /dev/null
+++ b/tools/lib/api/fs/cgroup.c
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/stringify.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "fs.h"
+
+struct cgroupfs_cache_entry {
+ char subsys[32];
+ char mountpoint[PATH_MAX];
+};
+
+/* just cache last used one */
+static struct cgroupfs_cache_entry *cached;
+
+int cgroupfs_find_mountpoint(char *buf, size_t maxlen, const char *subsys)
+{
+ FILE *fp;
+ char *line = NULL;
+ size_t len = 0;
+ char *p, *path;
+ char mountpoint[PATH_MAX];
+
+ if (cached && !strcmp(cached->subsys, subsys)) {
+ if (strlen(cached->mountpoint) < maxlen) {
+ strcpy(buf, cached->mountpoint);
+ return 0;
+ }
+ return -1;
+ }
+
+ fp = fopen("/proc/mounts", "r");
+ if (!fp)
+ return -1;
+
+ /*
+ * in order to handle split hierarchy, we need to scan /proc/mounts
+ * and inspect every cgroupfs mount point to find one that has
+ * the given subsystem. If we found v1, just use it. If not we can
+ * use v2 path as a fallback.
+ */
+ mountpoint[0] = '\0';
+
+ /*
+ * The /proc/mounts has the follow format:
+ *
+ * <devname> <mount point> <fs type> <options> ...
+ *
+ */
+ while (getline(&line, &len, fp) != -1) {
+ /* skip devname */
+ p = strchr(line, ' ');
+ if (p == NULL)
+ continue;
+
+ /* save the mount point */
+ path = ++p;
+ p = strchr(p, ' ');
+ if (p == NULL)
+ continue;
+
+ *p++ = '\0';
+
+ /* check filesystem type */
+ if (strncmp(p, "cgroup", 6))
+ continue;
+
+ if (p[6] == '2') {
+ /* save cgroup v2 path */
+ strcpy(mountpoint, path);
+ continue;
+ }
+
+ /* now we have cgroup v1, check the options for subsystem */
+ p += 7;
+
+ p = strstr(p, subsys);
+ if (p == NULL)
+ continue;
+
+ /* sanity check: it should be separated by a space or a comma */
+ if (!strchr(" ,", p[-1]) || !strchr(" ,", p[strlen(subsys)]))
+ continue;
+
+ strcpy(mountpoint, path);
+ break;
+ }
+ free(line);
+ fclose(fp);
+
+ if (!cached)
+ cached = calloc(1, sizeof(*cached));
+
+ if (cached) {
+ strncpy(cached->subsys, subsys, sizeof(cached->subsys) - 1);
+ strcpy(cached->mountpoint, mountpoint);
+ }
+
+ if (mountpoint[0] && strlen(mountpoint) < maxlen) {
+ strcpy(buf, mountpoint);
+ return 0;
+ }
+ return -1;
+}
diff --git a/tools/lib/api/fs/fs.c b/tools/lib/api/fs/fs.c
new file mode 100644
index 000000000..cbd8eab0d
--- /dev/null
+++ b/tools/lib/api/fs/fs.c
@@ -0,0 +1,515 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/vfs.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <sys/mount.h>
+
+#include "fs.h"
+#include "../io.h"
+#include "debug-internal.h"
+
+#define _STR(x) #x
+#define STR(x) _STR(x)
+
+#ifndef SYSFS_MAGIC
+#define SYSFS_MAGIC 0x62656572
+#endif
+
+#ifndef PROC_SUPER_MAGIC
+#define PROC_SUPER_MAGIC 0x9fa0
+#endif
+
+#ifndef DEBUGFS_MAGIC
+#define DEBUGFS_MAGIC 0x64626720
+#endif
+
+#ifndef TRACEFS_MAGIC
+#define TRACEFS_MAGIC 0x74726163
+#endif
+
+#ifndef HUGETLBFS_MAGIC
+#define HUGETLBFS_MAGIC 0x958458f6
+#endif
+
+#ifndef BPF_FS_MAGIC
+#define BPF_FS_MAGIC 0xcafe4a11
+#endif
+
+static const char * const sysfs__known_mountpoints[] = {
+ "/sys",
+ 0,
+};
+
+static const char * const procfs__known_mountpoints[] = {
+ "/proc",
+ 0,
+};
+
+#ifndef DEBUGFS_DEFAULT_PATH
+#define DEBUGFS_DEFAULT_PATH "/sys/kernel/debug"
+#endif
+
+static const char * const debugfs__known_mountpoints[] = {
+ DEBUGFS_DEFAULT_PATH,
+ "/debug",
+ 0,
+};
+
+
+#ifndef TRACEFS_DEFAULT_PATH
+#define TRACEFS_DEFAULT_PATH "/sys/kernel/tracing"
+#endif
+
+static const char * const tracefs__known_mountpoints[] = {
+ TRACEFS_DEFAULT_PATH,
+ "/sys/kernel/debug/tracing",
+ "/tracing",
+ "/trace",
+ 0,
+};
+
+static const char * const hugetlbfs__known_mountpoints[] = {
+ 0,
+};
+
+static const char * const bpf_fs__known_mountpoints[] = {
+ "/sys/fs/bpf",
+ 0,
+};
+
+struct fs {
+ const char * const name;
+ const char * const * const mounts;
+ char *path;
+ pthread_mutex_t mount_mutex;
+ const long magic;
+};
+
+#ifndef TRACEFS_MAGIC
+#define TRACEFS_MAGIC 0x74726163
+#endif
+
+static void fs__init_once(struct fs *fs);
+static const char *fs__mountpoint(const struct fs *fs);
+static const char *fs__mount(struct fs *fs);
+
+#define FS(lower_name, fs_name, upper_name) \
+static struct fs fs__##lower_name = { \
+ .name = #fs_name, \
+ .mounts = lower_name##__known_mountpoints, \
+ .magic = upper_name##_MAGIC, \
+ .mount_mutex = PTHREAD_MUTEX_INITIALIZER, \
+}; \
+ \
+static void lower_name##_init_once(void) \
+{ \
+ struct fs *fs = &fs__##lower_name; \
+ \
+ fs__init_once(fs); \
+} \
+ \
+const char *lower_name##__mountpoint(void) \
+{ \
+ static pthread_once_t init_once = PTHREAD_ONCE_INIT; \
+ struct fs *fs = &fs__##lower_name; \
+ \
+ pthread_once(&init_once, lower_name##_init_once); \
+ return fs__mountpoint(fs); \
+} \
+ \
+const char *lower_name##__mount(void) \
+{ \
+ const char *mountpoint = lower_name##__mountpoint(); \
+ struct fs *fs = &fs__##lower_name; \
+ \
+ if (mountpoint) \
+ return mountpoint; \
+ \
+ return fs__mount(fs); \
+} \
+ \
+bool lower_name##__configured(void) \
+{ \
+ return lower_name##__mountpoint() != NULL; \
+}
+
+FS(sysfs, sysfs, SYSFS);
+FS(procfs, procfs, PROC_SUPER);
+FS(debugfs, debugfs, DEBUGFS);
+FS(tracefs, tracefs, TRACEFS);
+FS(hugetlbfs, hugetlbfs, HUGETLBFS);
+FS(bpf_fs, bpf, BPF_FS);
+
+static bool fs__read_mounts(struct fs *fs)
+{
+ char type[100];
+ FILE *fp;
+ char path[PATH_MAX + 1];
+
+ fp = fopen("/proc/mounts", "r");
+ if (fp == NULL)
+ return false;
+
+ while (fscanf(fp, "%*s %" STR(PATH_MAX) "s %99s %*s %*d %*d\n",
+ path, type) == 2) {
+
+ if (strcmp(type, fs->name) == 0) {
+ fs->path = strdup(path);
+ fclose(fp);
+ return fs->path != NULL;
+ }
+ }
+ fclose(fp);
+ return false;
+}
+
+static int fs__valid_mount(const char *fs, long magic)
+{
+ struct statfs st_fs;
+
+ if (statfs(fs, &st_fs) < 0)
+ return -ENOENT;
+ else if ((long)st_fs.f_type != magic)
+ return -ENOENT;
+
+ return 0;
+}
+
+static bool fs__check_mounts(struct fs *fs)
+{
+ const char * const *ptr;
+
+ ptr = fs->mounts;
+ while (*ptr) {
+ if (fs__valid_mount(*ptr, fs->magic) == 0) {
+ fs->path = strdup(*ptr);
+ if (!fs->path)
+ return false;
+ return true;
+ }
+ ptr++;
+ }
+
+ return false;
+}
+
+static void mem_toupper(char *f, size_t len)
+{
+ while (len) {
+ *f = toupper(*f);
+ f++;
+ len--;
+ }
+}
+
+/*
+ * Check for "NAME_PATH" environment variable to override fs location (for
+ * testing). This matches the recommendation in Documentation/admin-guide/sysfs-rules.rst
+ * for SYSFS_PATH.
+ */
+static bool fs__env_override(struct fs *fs)
+{
+ char *override_path;
+ size_t name_len = strlen(fs->name);
+ /* name + "_PATH" + '\0' */
+ char upper_name[name_len + 5 + 1];
+
+ memcpy(upper_name, fs->name, name_len);
+ mem_toupper(upper_name, name_len);
+ strcpy(&upper_name[name_len], "_PATH");
+
+ override_path = getenv(upper_name);
+ if (!override_path)
+ return false;
+
+ fs->path = strdup(override_path);
+ if (!fs->path)
+ return false;
+ return true;
+}
+
+static void fs__init_once(struct fs *fs)
+{
+ if (!fs__env_override(fs) &&
+ !fs__check_mounts(fs) &&
+ !fs__read_mounts(fs)) {
+ assert(!fs->path);
+ } else {
+ assert(fs->path);
+ }
+}
+
+static const char *fs__mountpoint(const struct fs *fs)
+{
+ return fs->path;
+}
+
+static const char *mount_overload(struct fs *fs)
+{
+ size_t name_len = strlen(fs->name);
+ /* "PERF_" + name + "_ENVIRONMENT" + '\0' */
+ char upper_name[5 + name_len + 12 + 1];
+
+ snprintf(upper_name, sizeof(upper_name), "PERF_%s_ENVIRONMENT", fs->name);
+ mem_toupper(upper_name, strlen(upper_name));
+
+ return getenv(upper_name) ?: *fs->mounts;
+}
+
+static const char *fs__mount(struct fs *fs)
+{
+ const char *mountpoint;
+
+ pthread_mutex_lock(&fs->mount_mutex);
+
+ /* Check if path found inside the mutex to avoid races with other callers of mount. */
+ mountpoint = fs__mountpoint(fs);
+ if (mountpoint)
+ goto out;
+
+ mountpoint = mount_overload(fs);
+
+ if (mount(NULL, mountpoint, fs->name, 0, NULL) == 0 &&
+ fs__valid_mount(mountpoint, fs->magic) == 0) {
+ fs->path = strdup(mountpoint);
+ mountpoint = fs->path;
+ }
+out:
+ pthread_mutex_unlock(&fs->mount_mutex);
+ return mountpoint;
+}
+
+int filename__read_int(const char *filename, int *value)
+{
+ char line[64];
+ int fd = open(filename, O_RDONLY), err = -1;
+ ssize_t n;
+
+ if (fd < 0)
+ return -errno;
+
+ n = read(fd, line, sizeof(line) - 1);
+ if (n > 0) {
+ line[n] = '\0';
+ *value = atoi(line);
+ err = 0;
+ }
+
+ close(fd);
+ return err;
+}
+
+static int filename__read_ull_base(const char *filename,
+ unsigned long long *value, int base)
+{
+ char line[64];
+ int fd = open(filename, O_RDONLY), err = -1;
+ ssize_t n;
+
+ if (fd < 0)
+ return -errno;
+
+ n = read(fd, line, sizeof(line) - 1);
+ if (n > 0) {
+ line[n] = '\0';
+ *value = strtoull(line, NULL, base);
+ if (*value != ULLONG_MAX)
+ err = 0;
+ }
+
+ close(fd);
+ return err;
+}
+
+/*
+ * Parses @value out of @filename with strtoull.
+ * By using 16 for base to treat the number as hex.
+ */
+int filename__read_xll(const char *filename, unsigned long long *value)
+{
+ return filename__read_ull_base(filename, value, 16);
+}
+
+/*
+ * Parses @value out of @filename with strtoull.
+ * By using 0 for base, the strtoull detects the
+ * base automatically (see man strtoull).
+ */
+int filename__read_ull(const char *filename, unsigned long long *value)
+{
+ return filename__read_ull_base(filename, value, 0);
+}
+
+int filename__read_str(const char *filename, char **buf, size_t *sizep)
+{
+ struct io io;
+ char bf[128];
+ int err;
+
+ io.fd = open(filename, O_RDONLY);
+ if (io.fd < 0)
+ return -errno;
+ io__init(&io, io.fd, bf, sizeof(bf));
+ *buf = NULL;
+ err = io__getdelim(&io, buf, sizep, /*delim=*/-1);
+ if (err < 0) {
+ free(*buf);
+ *buf = NULL;
+ } else
+ err = 0;
+ close(io.fd);
+ return err;
+}
+
+int filename__write_int(const char *filename, int value)
+{
+ int fd = open(filename, O_WRONLY), err = -1;
+ char buf[64];
+ int len;
+
+ if (fd < 0)
+ return -errno;
+
+ len = sprintf(buf, "%d", value);
+ if (write(fd, buf, len) == len)
+ err = 0;
+
+ close(fd);
+ return err;
+}
+
+int procfs__read_str(const char *entry, char **buf, size_t *sizep)
+{
+ char path[PATH_MAX];
+ const char *procfs = procfs__mountpoint();
+
+ if (!procfs)
+ return -1;
+
+ snprintf(path, sizeof(path), "%s/%s", procfs, entry);
+
+ return filename__read_str(path, buf, sizep);
+}
+
+static int sysfs__read_ull_base(const char *entry,
+ unsigned long long *value, int base)
+{
+ char path[PATH_MAX];
+ const char *sysfs = sysfs__mountpoint();
+
+ if (!sysfs)
+ return -1;
+
+ snprintf(path, sizeof(path), "%s/%s", sysfs, entry);
+
+ return filename__read_ull_base(path, value, base);
+}
+
+int sysfs__read_xll(const char *entry, unsigned long long *value)
+{
+ return sysfs__read_ull_base(entry, value, 16);
+}
+
+int sysfs__read_ull(const char *entry, unsigned long long *value)
+{
+ return sysfs__read_ull_base(entry, value, 0);
+}
+
+int sysfs__read_int(const char *entry, int *value)
+{
+ char path[PATH_MAX];
+ const char *sysfs = sysfs__mountpoint();
+
+ if (!sysfs)
+ return -1;
+
+ snprintf(path, sizeof(path), "%s/%s", sysfs, entry);
+
+ return filename__read_int(path, value);
+}
+
+int sysfs__read_str(const char *entry, char **buf, size_t *sizep)
+{
+ char path[PATH_MAX];
+ const char *sysfs = sysfs__mountpoint();
+
+ if (!sysfs)
+ return -1;
+
+ snprintf(path, sizeof(path), "%s/%s", sysfs, entry);
+
+ return filename__read_str(path, buf, sizep);
+}
+
+int sysfs__read_bool(const char *entry, bool *value)
+{
+ struct io io;
+ char bf[16];
+ int ret = 0;
+ char path[PATH_MAX];
+ const char *sysfs = sysfs__mountpoint();
+
+ if (!sysfs)
+ return -1;
+
+ snprintf(path, sizeof(path), "%s/%s", sysfs, entry);
+ io.fd = open(path, O_RDONLY);
+ if (io.fd < 0)
+ return -errno;
+
+ io__init(&io, io.fd, bf, sizeof(bf));
+ switch (io__get_char(&io)) {
+ case '1':
+ case 'y':
+ case 'Y':
+ *value = true;
+ break;
+ case '0':
+ case 'n':
+ case 'N':
+ *value = false;
+ break;
+ default:
+ ret = -1;
+ }
+ close(io.fd);
+
+ return ret;
+}
+int sysctl__read_int(const char *sysctl, int *value)
+{
+ char path[PATH_MAX];
+ const char *procfs = procfs__mountpoint();
+
+ if (!procfs)
+ return -1;
+
+ snprintf(path, sizeof(path), "%s/sys/%s", procfs, sysctl);
+
+ return filename__read_int(path, value);
+}
+
+int sysfs__write_int(const char *entry, int value)
+{
+ char path[PATH_MAX];
+ const char *sysfs = sysfs__mountpoint();
+
+ if (!sysfs)
+ return -1;
+
+ if (snprintf(path, sizeof(path), "%s/%s", sysfs, entry) >= PATH_MAX)
+ return -1;
+
+ return filename__write_int(path, value);
+}
diff --git a/tools/lib/api/fs/fs.h b/tools/lib/api/fs/fs.h
new file mode 100644
index 000000000..aa222ca30
--- /dev/null
+++ b/tools/lib/api/fs/fs.h
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __API_FS__
+#define __API_FS__
+
+#include <stdbool.h>
+#include <unistd.h>
+
+/*
+ * On most systems <limits.h> would have given us this, but not on some systems
+ * (e.g. GNU/Hurd).
+ */
+#ifndef PATH_MAX
+#define PATH_MAX 4096
+#endif
+
+#define FS(name) \
+ const char *name##__mountpoint(void); \
+ const char *name##__mount(void); \
+ bool name##__configured(void); \
+
+/*
+ * The xxxx__mountpoint() entry points find the first match mount point for each
+ * filesystems listed below, where xxxx is the filesystem type.
+ *
+ * The interface is as follows:
+ *
+ * - If a mount point is found on first call, it is cached and used for all
+ * subsequent calls.
+ *
+ * - If a mount point is not found, NULL is returned on first call and all
+ * subsequent calls.
+ */
+FS(sysfs)
+FS(procfs)
+FS(debugfs)
+FS(tracefs)
+FS(hugetlbfs)
+FS(bpf_fs)
+
+#undef FS
+
+
+int cgroupfs_find_mountpoint(char *buf, size_t maxlen, const char *subsys);
+
+int filename__read_int(const char *filename, int *value);
+int filename__read_ull(const char *filename, unsigned long long *value);
+int filename__read_xll(const char *filename, unsigned long long *value);
+int filename__read_str(const char *filename, char **buf, size_t *sizep);
+
+int filename__write_int(const char *filename, int value);
+
+int procfs__read_str(const char *entry, char **buf, size_t *sizep);
+
+int sysctl__read_int(const char *sysctl, int *value);
+int sysfs__read_int(const char *entry, int *value);
+int sysfs__read_ull(const char *entry, unsigned long long *value);
+int sysfs__read_xll(const char *entry, unsigned long long *value);
+int sysfs__read_str(const char *entry, char **buf, size_t *sizep);
+int sysfs__read_bool(const char *entry, bool *value);
+
+int sysfs__write_int(const char *entry, int value);
+#endif /* __API_FS__ */
diff --git a/tools/lib/api/fs/tracing_path.c b/tools/lib/api/fs/tracing_path.c
new file mode 100644
index 000000000..834fd64c7
--- /dev/null
+++ b/tools/lib/api/fs/tracing_path.c
@@ -0,0 +1,178 @@
+// SPDX-License-Identifier: GPL-2.0
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <linux/string.h>
+#include <errno.h>
+#include <unistd.h>
+#include "fs.h"
+
+#include "tracing_path.h"
+
+static char tracing_path[PATH_MAX] = "/sys/kernel/tracing";
+
+static void __tracing_path_set(const char *tracing, const char *mountpoint)
+{
+ snprintf(tracing_path, sizeof(tracing_path), "%s/%s",
+ mountpoint, tracing);
+}
+
+static const char *tracing_path_tracefs_mount(void)
+{
+ const char *mnt;
+
+ mnt = tracefs__mount();
+ if (!mnt)
+ return NULL;
+
+ __tracing_path_set("", mnt);
+
+ return tracing_path;
+}
+
+static const char *tracing_path_debugfs_mount(void)
+{
+ const char *mnt;
+
+ mnt = debugfs__mount();
+ if (!mnt)
+ return NULL;
+
+ __tracing_path_set("tracing/", mnt);
+
+ return tracing_path;
+}
+
+const char *tracing_path_mount(void)
+{
+ const char *mnt;
+
+ mnt = tracing_path_tracefs_mount();
+ if (mnt)
+ return mnt;
+
+ mnt = tracing_path_debugfs_mount();
+
+ return mnt;
+}
+
+void tracing_path_set(const char *mntpt)
+{
+ __tracing_path_set("tracing/", mntpt);
+}
+
+char *get_tracing_file(const char *name)
+{
+ char *file;
+
+ if (asprintf(&file, "%s%s", tracing_path_mount(), name) < 0)
+ return NULL;
+
+ return file;
+}
+
+void put_tracing_file(char *file)
+{
+ free(file);
+}
+
+char *get_events_file(const char *name)
+{
+ char *file;
+
+ if (asprintf(&file, "%s/events/%s", tracing_path_mount(), name) < 0)
+ return NULL;
+
+ return file;
+}
+
+void put_events_file(char *file)
+{
+ free(file);
+}
+
+DIR *tracing_events__opendir(void)
+{
+ DIR *dir = NULL;
+ char *path = get_tracing_file("events");
+
+ if (path) {
+ dir = opendir(path);
+ put_events_file(path);
+ }
+
+ return dir;
+}
+
+int tracing_events__scandir_alphasort(struct dirent ***namelist)
+{
+ char *path = get_tracing_file("events");
+ int ret;
+
+ if (!path) {
+ *namelist = NULL;
+ return 0;
+ }
+
+ ret = scandir(path, namelist, NULL, alphasort);
+ put_events_file(path);
+
+ return ret;
+}
+
+int tracing_path__strerror_open_tp(int err, char *buf, size_t size,
+ const char *sys, const char *name)
+{
+ char sbuf[128];
+ char filename[PATH_MAX];
+
+ snprintf(filename, PATH_MAX, "%s/%s", sys, name ?: "*");
+
+ switch (err) {
+ case ENOENT:
+ /*
+ * We will get here if we can't find the tracepoint, but one of
+ * debugfs or tracefs is configured, which means you probably
+ * want some tracepoint which wasn't compiled in your kernel.
+ * - jirka
+ */
+ if (debugfs__configured() || tracefs__configured()) {
+ /* sdt markers */
+ if (!strncmp(filename, "sdt_", 4)) {
+ snprintf(buf, size,
+ "Error:\tFile %s/events/%s not found.\n"
+ "Hint:\tSDT event cannot be directly recorded on.\n"
+ "\tPlease first use 'perf probe %s:%s' before recording it.\n",
+ tracing_path, filename, sys, name);
+ } else {
+ snprintf(buf, size,
+ "Error:\tFile %s/events/%s not found.\n"
+ "Hint:\tPerhaps this kernel misses some CONFIG_ setting to enable this feature?.\n",
+ tracing_path, filename);
+ }
+ break;
+ }
+ snprintf(buf, size, "%s",
+ "Error:\tUnable to find debugfs/tracefs\n"
+ "Hint:\tWas your kernel compiled with debugfs/tracefs support?\n"
+ "Hint:\tIs the debugfs/tracefs filesystem mounted?\n"
+ "Hint:\tTry 'sudo mount -t debugfs nodev /sys/kernel/debug'");
+ break;
+ case EACCES: {
+ snprintf(buf, size,
+ "Error:\tNo permissions to read %s/events/%s\n"
+ "Hint:\tTry 'sudo mount -o remount,mode=755 %s'\n",
+ tracing_path, filename, tracing_path_mount());
+ }
+ break;
+ default:
+ snprintf(buf, size, "%s", str_error_r(err, sbuf, sizeof(sbuf)));
+ break;
+ }
+
+ return 0;
+}
diff --git a/tools/lib/api/fs/tracing_path.h b/tools/lib/api/fs/tracing_path.h
new file mode 100644
index 000000000..fc6347c11
--- /dev/null
+++ b/tools/lib/api/fs/tracing_path.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __API_FS_TRACING_PATH_H
+#define __API_FS_TRACING_PATH_H
+
+#include <linux/types.h>
+#include <dirent.h>
+
+DIR *tracing_events__opendir(void);
+int tracing_events__scandir_alphasort(struct dirent ***namelist);
+
+void tracing_path_set(const char *mountpoint);
+const char *tracing_path_mount(void);
+
+char *get_tracing_file(const char *name);
+void put_tracing_file(char *file);
+
+char *get_events_file(const char *name);
+void put_events_file(char *file);
+
+#define zput_events_file(ptr) ({ free(*ptr); *ptr = NULL; })
+
+int tracing_path__strerror_open_tp(int err, char *buf, size_t size, const char *sys, const char *name);
+#endif /* __API_FS_TRACING_PATH_H */