summaryrefslogtreecommitdiffstats
path: root/lib/common/procfs.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--lib/common/procfs.c227
1 files changed, 227 insertions, 0 deletions
diff --git a/lib/common/procfs.c b/lib/common/procfs.c
new file mode 100644
index 0000000..8179cc9
--- /dev/null
+++ b/lib/common/procfs.c
@@ -0,0 +1,227 @@
+/*
+ * Copyright 2015-2022 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#include <crm_internal.h>
+
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <ctype.h>
+
+/*!
+ * \internal
+ * \brief Get process ID and name associated with a /proc directory entry
+ *
+ * \param[in] entry Directory entry (must be result of readdir() on /proc)
+ * \param[out] name If not NULL, a char[16] to hold the process name
+ * \param[out] pid If not NULL, will be set to process ID of entry
+ *
+ * \return Standard Pacemaker return code
+ * \note This should be called only on Linux systems, as not all systems that
+ * support /proc store process names and IDs in the same way. The kernel
+ * limits the process name to the first 15 characters (plus terminator).
+ * It would be nice if there were a public kernel API constant for that
+ * limit, but there isn't.
+ */
+static int
+pcmk__procfs_process_info(const struct dirent *entry, char *name, pid_t *pid)
+{
+ int fd, local_pid;
+ FILE *file;
+ struct stat statbuf;
+ char procpath[128] = { 0 };
+
+ /* We're only interested in entries whose name is a PID,
+ * so skip anything non-numeric or that is too long.
+ *
+ * 114 = 128 - strlen("/proc/") - strlen("/status") - 1
+ */
+ local_pid = atoi(entry->d_name);
+ if ((local_pid <= 0) || (strlen(entry->d_name) > 114)) {
+ return -1;
+ }
+ if (pid) {
+ *pid = (pid_t) local_pid;
+ }
+
+ /* Get this entry's file information */
+ strcpy(procpath, "/proc/");
+ strcat(procpath, entry->d_name);
+ fd = open(procpath, O_RDONLY);
+ if (fd < 0 ) {
+ return -1;
+ }
+ if (fstat(fd, &statbuf) < 0) {
+ close(fd);
+ return -1;
+ }
+ close(fd);
+
+ /* We're only interested in subdirectories */
+ if (!S_ISDIR(statbuf.st_mode)) {
+ return -1;
+ }
+
+ /* Read the first entry ("Name:") from the process's status file.
+ * We could handle the valgrind case if we parsed the cmdline file
+ * instead, but that's more of a pain than it's worth.
+ */
+ if (name != NULL) {
+ strcat(procpath, "/status");
+ file = fopen(procpath, "r");
+ if (!file) {
+ return -1;
+ }
+ if (fscanf(file, "Name:\t%15[^\n]", name) != 1) {
+ fclose(file);
+ return -1;
+ }
+ name[15] = 0;
+ fclose(file);
+ }
+
+ return 0;
+}
+
+/*!
+ * \internal
+ * \brief Return process ID of a named process
+ *
+ * \param[in] name Process name (as used in /proc/.../status)
+ *
+ * \return Process ID of named process if running, 0 otherwise
+ *
+ * \note This will return 0 if the process is being run via valgrind.
+ * This should be called only on Linux systems.
+ */
+pid_t
+pcmk__procfs_pid_of(const char *name)
+{
+ DIR *dp;
+ struct dirent *entry;
+ pid_t pid = 0;
+ char entry_name[64] = { 0 };
+
+ dp = opendir("/proc");
+ if (dp == NULL) {
+ crm_notice("Can not read /proc directory to track existing components");
+ return 0;
+ }
+
+ while ((entry = readdir(dp)) != NULL) {
+ if ((pcmk__procfs_process_info(entry, entry_name, &pid) == pcmk_rc_ok)
+ && pcmk__str_eq(entry_name, name, pcmk__str_casei)
+ && (pcmk__pid_active(pid, NULL) == pcmk_rc_ok)) {
+
+ crm_info("Found %s active as process %lld", name, (long long) pid);
+ break;
+ }
+ pid = 0;
+ }
+ closedir(dp);
+ return pid;
+}
+
+/*!
+ * \internal
+ * \brief Calculate number of logical CPU cores from procfs
+ *
+ * \return Number of cores (or 1 if unable to determine)
+ */
+unsigned int
+pcmk__procfs_num_cores(void)
+{
+ int cores = 0;
+ FILE *stream = NULL;
+
+ /* Parse /proc/stat instead of /proc/cpuinfo because it's smaller */
+ stream = fopen("/proc/stat", "r");
+ if (stream == NULL) {
+ crm_perror(LOG_INFO, "Could not open /proc/stat");
+ } else {
+ char buffer[2048];
+
+ while (fgets(buffer, sizeof(buffer), stream)) {
+ if (pcmk__starts_with(buffer, "cpu") && isdigit(buffer[3])) {
+ ++cores;
+ }
+ }
+ fclose(stream);
+ }
+ return cores? cores : 1;
+}
+
+/*!
+ * \internal
+ * \brief Get the executable path corresponding to a process ID
+ *
+ * \param[in] pid Process ID to check
+ * \param[out] path Where to store executable path
+ * \param[in] path_size Size of \p path in characters (ideally PATH_MAX)
+ *
+ * \return Standard Pacemaker error code (as possible errno values from
+ * readlink())
+ */
+int
+pcmk__procfs_pid2path(pid_t pid, char path[], size_t path_size)
+{
+#if HAVE_LINUX_PROCFS
+ char procfs_exe_path[PATH_MAX];
+ ssize_t link_rc;
+
+ if (snprintf(procfs_exe_path, PATH_MAX, "/proc/%lld/exe",
+ (long long) pid) >= PATH_MAX) {
+ return ENAMETOOLONG; // Truncated (shouldn't be possible in practice)
+ }
+
+ link_rc = readlink(procfs_exe_path, path, path_size - 1);
+ if (link_rc < 0) {
+ return errno;
+ } else if (link_rc >= (path_size - 1)) {
+ return ENAMETOOLONG;
+ }
+
+ path[link_rc] = '\0';
+ return pcmk_rc_ok;
+#else
+ return EOPNOTSUPP;
+#endif // HAVE_LINUX_PROCFS
+}
+
+/*!
+ * \internal
+ * \brief Check whether process ID information is available from procfs
+ *
+ * \return true if process ID information is available, otherwise false
+ */
+bool
+pcmk__procfs_has_pids(void)
+{
+#if HAVE_LINUX_PROCFS
+ static bool have_pids = false;
+ static bool checked = false;
+
+ if (!checked) {
+ char path[PATH_MAX];
+
+ have_pids = pcmk__procfs_pid2path(getpid(), path, sizeof(path)) == pcmk_rc_ok;
+ checked = true;
+ }
+ return have_pids;
+#else
+ return false;
+#endif // HAVE_LINUX_PROCFS
+}