root/lib/common/procfs.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. pcmk__procfs_process_info
  2. pcmk__procfs_pid_of
  3. pcmk__procfs_num_cores
  4. pcmk__procfs_pid2path
  5. pcmk__procfs_has_pids

   1 /*
   2  * Copyright 2015-2022 the Pacemaker project contributors
   3  *
   4  * The version control history for this file may have further details.
   5  *
   6  * This source code is licensed under the GNU Lesser General Public License
   7  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
   8  */
   9 
  10 #include <crm_internal.h>
  11 
  12 #ifndef _GNU_SOURCE
  13 #  define _GNU_SOURCE
  14 #endif
  15 
  16 #include <stdio.h>
  17 #include <stdlib.h>
  18 #include <string.h>
  19 #include <sys/stat.h>
  20 #include <sys/types.h>
  21 #include <dirent.h>
  22 #include <ctype.h>
  23 
  24 /*!
  25  * \internal
  26  * \brief Get process ID and name associated with a /proc directory entry
  27  *
  28  * \param[in]  entry    Directory entry (must be result of readdir() on /proc)
  29  * \param[out] name     If not NULL, a char[16] to hold the process name
  30  * \param[out] pid      If not NULL, will be set to process ID of entry
  31  *
  32  * \return Standard Pacemaker return code
  33  * \note This should be called only on Linux systems, as not all systems that
  34  *       support /proc store process names and IDs in the same way. The kernel
  35  *       limits the process name to the first 15 characters (plus terminator).
  36  *       It would be nice if there were a public kernel API constant for that
  37  *       limit, but there isn't.
  38  */
  39 static int
  40 pcmk__procfs_process_info(const struct dirent *entry, char *name, pid_t *pid)
     /* [previous][next][first][last][top][bottom][index][help] */
  41 {
  42     int fd, local_pid;
  43     FILE *file;
  44     struct stat statbuf;
  45     char procpath[128] = { 0 };
  46 
  47     /* We're only interested in entries whose name is a PID,
  48      * so skip anything non-numeric or that is too long.
  49      *
  50      * 114 = 128 - strlen("/proc/") - strlen("/status") - 1
  51      */
  52     local_pid = atoi(entry->d_name);
  53     if ((local_pid <= 0) || (strlen(entry->d_name) > 114)) {
  54         return -1;
  55     }
  56     if (pid) {
  57         *pid = (pid_t) local_pid;
  58     }
  59 
  60     /* Get this entry's file information */
  61     strcpy(procpath, "/proc/");
  62     strcat(procpath, entry->d_name);
  63     fd = open(procpath, O_RDONLY);
  64     if (fd < 0 ) {
  65         return -1;
  66     }
  67     if (fstat(fd, &statbuf) < 0) {
  68         close(fd);
  69         return -1;
  70     }
  71     close(fd);
  72 
  73     /* We're only interested in subdirectories */
  74     if (!S_ISDIR(statbuf.st_mode)) {
  75         return -1;
  76     }
  77 
  78     /* Read the first entry ("Name:") from the process's status file.
  79      * We could handle the valgrind case if we parsed the cmdline file
  80      * instead, but that's more of a pain than it's worth.
  81      */
  82     if (name != NULL) {
  83         strcat(procpath, "/status");
  84         file = fopen(procpath, "r");
  85         if (!file) {
  86             return -1;
  87         }
  88         if (fscanf(file, "Name:\t%15[^\n]", name) != 1) {
  89             fclose(file);
  90             return -1;
  91         }
  92         name[15] = 0;
  93         fclose(file);
  94     }
  95 
  96     return 0;
  97 }
  98 
  99 /*!
 100  * \internal
 101  * \brief Return process ID of a named process
 102  *
 103  * \param[in] name  Process name (as used in /proc/.../status)
 104  *
 105  * \return Process ID of named process if running, 0 otherwise
 106  *
 107  * \note This will return 0 if the process is being run via valgrind.
 108  *       This should be called only on Linux systems.
 109  */
 110 pid_t
 111 pcmk__procfs_pid_of(const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
 112 {
 113     DIR *dp;
 114     struct dirent *entry;
 115     pid_t pid = 0;
 116     char entry_name[64] = { 0 };
 117 
 118     dp = opendir("/proc");
 119     if (dp == NULL) {
 120         crm_notice("Can not read /proc directory to track existing components");
 121         return 0;
 122     }
 123 
 124     while ((entry = readdir(dp)) != NULL) {
 125         if ((pcmk__procfs_process_info(entry, entry_name, &pid) == pcmk_rc_ok)
 126             && pcmk__str_eq(entry_name, name, pcmk__str_casei)
 127             && (pcmk__pid_active(pid, NULL) == pcmk_rc_ok)) {
 128 
 129             crm_info("Found %s active as process %lld", name, (long long) pid);
 130             break;
 131         }
 132         pid = 0;
 133     }
 134     closedir(dp);
 135     return pid;
 136 }
 137 
 138 /*!
 139  * \internal
 140  * \brief Calculate number of logical CPU cores from procfs
 141  *
 142  * \return Number of cores (or 1 if unable to determine)
 143  */
 144 unsigned int
 145 pcmk__procfs_num_cores(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 146 {
 147     int cores = 0;
 148     FILE *stream = NULL;
 149 
 150     /* Parse /proc/stat instead of /proc/cpuinfo because it's smaller */
 151     stream = fopen("/proc/stat", "r");
 152     if (stream == NULL) {
 153         crm_perror(LOG_INFO, "Could not open /proc/stat");
 154     } else {
 155         char buffer[2048];
 156 
 157         while (fgets(buffer, sizeof(buffer), stream)) {
 158             if (pcmk__starts_with(buffer, "cpu") && isdigit(buffer[3])) {
 159                 ++cores;
 160             }
 161         }
 162         fclose(stream);
 163     }
 164     return cores? cores : 1;
 165 }
 166 
 167 /*!
 168  * \internal
 169  * \brief Get the executable path corresponding to a process ID
 170  *
 171  * \param[in]  pid        Process ID to check
 172  * \param[out] path       Where to store executable path
 173  * \param[in]  path_size  Size of \p path in characters (ideally PATH_MAX)
 174  *
 175  * \return Standard Pacemaker error code (as possible errno values from
 176  *         readlink())
 177  */
 178 int
 179 pcmk__procfs_pid2path(pid_t pid, char path[], size_t path_size)
     /* [previous][next][first][last][top][bottom][index][help] */
 180 {
 181 #if HAVE_LINUX_PROCFS
 182     char procfs_exe_path[PATH_MAX];
 183     ssize_t link_rc;
 184 
 185     if (snprintf(procfs_exe_path, PATH_MAX, "/proc/%lld/exe",
 186                  (long long) pid) >= PATH_MAX) {
 187         return ENAMETOOLONG; // Truncated (shouldn't be possible in practice)
 188     }
 189 
 190     link_rc = readlink(procfs_exe_path, path, path_size - 1);
 191     if (link_rc < 0) {
 192         return errno;
 193     } else if (link_rc >= (path_size - 1)) {
 194         return ENAMETOOLONG;
 195     }
 196 
 197     path[link_rc] = '\0';
 198     return pcmk_rc_ok;
 199 #else
 200     return EOPNOTSUPP;
 201 #endif // HAVE_LINUX_PROCFS
 202 }
 203 
 204 /*!
 205  * \internal
 206  * \brief Check whether process ID information is available from procfs
 207  *
 208  * \return true if process ID information is available, otherwise false
 209  */
 210 bool
 211 pcmk__procfs_has_pids(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 212 {
 213 #if HAVE_LINUX_PROCFS
 214     static bool have_pids = false;
 215     static bool checked = false;
 216 
 217     if (!checked) {
 218         char path[PATH_MAX];
 219 
 220         have_pids = pcmk__procfs_pid2path(getpid(), path, sizeof(path)) == pcmk_rc_ok;
 221         checked = true;
 222     }
 223     return have_pids;
 224 #else
 225     return false;
 226 #endif // HAVE_LINUX_PROCFS
 227 }

/* [previous][next][first][last][top][bottom][index][help] */