Coverage Report

Created: 2024-10-12 00:30

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