1 /*
2 * Copyright 2004-2026 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 <dirent.h>
13 #include <errno.h>
14 #include <fcntl.h>
15 #include <limits.h>
16 #include <stdbool.h> // bool, true, false
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <sys/param.h>
21 #include <sys/resource.h>
22 #include <sys/stat.h>
23 #include <sys/types.h>
24 #include <unistd.h>
25
26 #include <crm/crm.h>
27 #include <crm/common/util.h>
28
29 /*!
30 * \internal
31 * \brief Create a directory, including any parent directories needed
32 *
33 * \param[in] path_c Pathname of the directory to create
34 * \param[in] mode Permissions to be used (with current umask) when creating
35 *
36 * \return Standard Pacemaker return code
37 */
38 int
39 pcmk__build_path(const char *path_c, mode_t mode)
40 {
41 int offset = 1, len = 0;
42 int rc = pcmk_rc_ok;
43 char *path = strdup(path_c);
44
45 CRM_CHECK(path != NULL, return -ENOMEM);
46 for (len = strlen(path); offset < len; offset++) {
47 if (path[offset] == '/') {
48 path[offset] = 0;
49 if ((mkdir(path, mode) < 0) && (errno != EEXIST)) {
50 rc = errno;
51 goto done;
52 }
53 path[offset] = '/';
54 }
55 }
56 if ((mkdir(path, mode) < 0) && (errno != EEXIST)) {
57 rc = errno;
58 }
59 done:
60 free(path);
61 return rc;
62 }
63
64 /*!
65 * \internal
66 * \brief Return canonicalized form of a path name
67 *
68 * \param[in] path Pathname to canonicalize
69 * \param[out] resolved_path Where to store canonicalized pathname
70 *
71 * \return Standard Pacemaker return code
72 * \note The caller is responsible for freeing \p resolved_path on success.
73 * \note This function exists because not all C library versions of
74 * realpath(path, resolved_path) support a NULL resolved_path.
75 */
76 int
77 pcmk__real_path(const char *path, char **resolved_path)
78 {
79 CRM_CHECK((path != NULL) && (resolved_path != NULL), return EINVAL);
80
81 #if _POSIX_VERSION >= 200809L
82 /* Recent C libraries can dynamically allocate memory as needed */
83 *resolved_path = realpath(path, NULL);
84 return (*resolved_path == NULL)? errno : pcmk_rc_ok;
85
86 #elif defined(PATH_MAX)
87 /* Older implementations require pre-allocated memory */
88 /* (this is less desirable because PATH_MAX may be huge or not defined) */
89 *resolved_path = malloc(PATH_MAX);
90 if ((*resolved_path == NULL) || (realpath(path, *resolved_path) == NULL)) {
91 return errno;
92 }
93 return pcmk_rc_ok;
94 #else
95 *resolved_path = NULL;
96 return ENOTSUP;
97 #endif
98 }
99
100 /*!
101 * \internal
102 * \brief Create a file name using a sequence number
103 *
104 * \param[in] directory Directory that contains the file series
105 * \param[in] series Start of file name
106 * \param[in] sequence Sequence number
107 * \param[in] bzip Whether to use ".bz2" instead of ".raw" as extension
108 *
109 * \return Newly allocated file path (asserts on error, so always non-NULL)
110 * \note The caller is responsible for freeing the return value.
111 */
112 char *
113 pcmk__series_filename(const char *directory, const char *series,
114 unsigned int sequence, bool bzip)
115 {
116 pcmk__assert((directory != NULL) && (series != NULL));
117 return pcmk__assert_asprintf("%s/%s-%u.%s", directory, series, sequence,
118 (bzip? "bz2" : "raw"));
119 }
120
121 /*!
122 * \internal
123 * \brief Read sequence number stored in a file series' .last file
124 *
125 * \param[in] directory Directory that contains the file series
126 * \param[in] series Start of file name
127 * \param[out] seq Where to store the sequence number
128 *
129 * \return Standard Pacemaker return code
130 */
131 int
132 pcmk__read_series_sequence(const char *directory, const char *series,
133 unsigned int *seq)
134 {
135 int rc;
136 FILE *fp = NULL;
137 char *series_file = NULL;
138
139 if ((directory == NULL) || (series == NULL) || (seq == NULL)) {
140 return EINVAL;
141 }
142
143 series_file = pcmk__assert_asprintf("%s/%s.last", directory, series);
144 fp = fopen(series_file, "r");
145 if (fp == NULL) {
146 rc = errno;
147 pcmk__debug("Could not open series file %s: %s", series_file,
148 strerror(rc));
149 free(series_file);
150 return rc;
151 }
152 errno = 0;
153 if (fscanf(fp, "%u", seq) != 1) {
154 rc = (errno == 0)? ENODATA : errno;
155 pcmk__debug("Could not read sequence number from series file %s: %s",
156 series_file, pcmk_rc_str(rc));
157 fclose(fp);
158 free(series_file);
159 return rc;
160 }
161 fclose(fp);
162 pcmk__trace("Found last sequence number %u in series file %s", *seq,
163 series_file);
164 free(series_file);
165 return pcmk_rc_ok;
166 }
167
168 /*!
169 * \internal
170 * \brief Write sequence number to a file series' .last file
171 *
172 * \param[in] directory Directory that contains the file series
173 * \param[in] series Start of file name
174 * \param[in] sequence Sequence number to write
175 * \param[in] max Maximum sequence value, after which it is reset to 0
176 *
177 * \note This function logs some errors but does not return any to the caller
178 */
179 void
180 pcmk__write_series_sequence(const char *directory, const char *series,
181 unsigned int sequence, int max)
182 {
183 FILE *file_strm = NULL;
184 char *series_file = NULL;
185
186 CRM_CHECK(directory != NULL, return);
187 CRM_CHECK(series != NULL, return);
188
189 if (max == 0) {
190 return;
191 }
192 if (max > 0 && sequence >= max) {
193 sequence = 0;
194 }
195
196 series_file = pcmk__assert_asprintf("%s/%s.last", directory, series);
197 file_strm = fopen(series_file, "w");
198 if (file_strm != NULL) {
199 int rc = fprintf(file_strm, "%u", sequence);
200
201 if (rc < 0) {
202 pcmk__err("Cannot write to series file %s", series_file);
203 }
204 fflush(file_strm);
205 fclose(file_strm);
206
207 } else {
208 pcmk__err("Cannot open series file %s for writing", series_file);
209 }
210
211 free(series_file);
212 }
213
214 /*!
215 * \internal
216 * \brief Change the owner and group of a file series' .last file
217 *
218 * \param[in] directory Directory that contains series
219 * \param[in] series Series to change
220 * \param[in] uid User ID of desired file owner
221 * \param[in] gid Group ID of desired file group
222 *
223 * \return Standard Pacemaker return code
224 * \note The caller must have the appropriate privileges.
225 */
226 int
227 pcmk__chown_series_sequence(const char *directory, const char *series,
228 uid_t uid, gid_t gid)
229 {
230 char *series_file = NULL;
231 int rc = pcmk_rc_ok;
232
233 if ((directory == NULL) || (series == NULL)) {
234 return EINVAL;
235 }
236 series_file = pcmk__assert_asprintf("%s/%s.last", directory, series);
237 if (chown(series_file, uid, gid) < 0) {
238 rc = errno;
239 }
240 free(series_file);
241 return rc;
242 }
243
244 static bool
245 pcmk__daemon_user_can_write(const char *target_name, struct stat *target_stat)
246 {
247 uid_t daemon_uid = 0;
248 int rc = pcmk__daemon_user(&daemon_uid, NULL);
249
250 if (rc != pcmk_rc_ok) {
251 pcmk__notice("Could not find user " CRM_DAEMON_USER ": %s",
252 pcmk_rc_str(rc));
253 return false;
254 }
255 if (target_stat->st_uid != daemon_uid) {
256 pcmk__notice("%s is not owned by user " CRM_DAEMON_USER " "
257 QB_XS " uid %lld != %lld",
258 target_name, (long long) daemon_uid,
259 (long long) target_stat->st_uid);
260 return false;
261 }
262 if ((target_stat->st_mode & (S_IRUSR | S_IWUSR)) == 0) {
263 pcmk__notice("%s is not readable and writable by user %s "
264 QB_XS " st_mode=0%lo",
265 target_name, CRM_DAEMON_USER,
266 (unsigned long) target_stat->st_mode);
267 return false;
268 }
269 return true;
270 }
271
272 static bool
273 pcmk__daemon_group_can_write(const char *target_name, struct stat *target_stat)
274 {
275 gid_t daemon_gid = 0;
276 int rc = pcmk__daemon_user(NULL, &daemon_gid);
277
278 if (rc != pcmk_rc_ok) {
279 pcmk__notice("Could not find group " CRM_DAEMON_GROUP ": %s",
280 pcmk_rc_str(rc));
281 return false;
282 }
283
284 if (target_stat->st_gid != daemon_gid) {
285 pcmk__notice("%s is not owned by group " CRM_DAEMON_GROUP " "
286 QB_XS " gid %lld != %lld",
287 target_name, (long long) daemon_gid,
288 (long long) target_stat->st_gid);
289 return false;
290 }
291
292 if ((target_stat->st_mode & (S_IRGRP | S_IWGRP)) == 0) {
293 pcmk__notice("%s is not readable and writable by group %s "
294 QB_XS " st_mode=0%lo",
295 target_name, CRM_DAEMON_GROUP,
296 (unsigned long) target_stat->st_mode);
297 return false;
298 }
299 return true;
300 }
301
302 /*!
303 * \internal
304 * \brief Check whether a directory or file is writable by the cluster daemon
305 *
306 * Return true if either the cluster daemon user or cluster daemon group has
307 * write permission on a specified file or directory.
308 *
309 * \param[in] dir Directory to check (this argument must be specified, and
310 * the directory must exist)
311 * \param[in] file File to check (only the directory will be checked if this
312 * argument is not specified or the file does not exist)
313 *
314 * \return true if target is writable by cluster daemon, false otherwise
315 */
316 bool
317 pcmk__daemon_can_write(const char *dir, const char *file)
318 {
319 int s_res = 0;
320 struct stat buf;
321 char *full_file = NULL;
322 const char *target = NULL;
323
324 // Caller must supply directory
325 pcmk__assert(dir != NULL);
326
327 // If file is given, check whether it exists as a regular file
328 if (file != NULL) {
329 full_file = pcmk__assert_asprintf("%s/%s", dir, file);
330 target = full_file;
331
332 s_res = stat(full_file, &buf);
333 if (s_res < 0) {
334 pcmk__notice("%s not found: %s", target, pcmk_rc_str(errno));
335 g_clear_pointer(&full_file, &free);
336 target = NULL;
337
338 } else if (!S_ISREG(buf.st_mode)) {
339 pcmk__err("%s must be a regular file " QB_XS " st_mode=0%lo",
340 target, (unsigned long) buf.st_mode);
341 free(full_file);
342 return false;
343 }
344 }
345
346 // If file is not given, ensure dir exists as directory
347 if (target == NULL) {
348 target = dir;
349 s_res = stat(dir, &buf);
350 if (s_res < 0) {
351 pcmk__err("%s not found: %s", dir, pcmk_rc_str(errno));
352 return false;
353
354 } else if (!S_ISDIR(buf.st_mode)) {
355 pcmk__err("%s must be a directory " QB_XS " st_mode=0%lo", dir,
356 (unsigned long) buf.st_mode);
357 return false;
358 }
359 }
360
361 if (!pcmk__daemon_user_can_write(target, &buf)
362 && !pcmk__daemon_group_can_write(target, &buf)) {
363
364 pcmk__err("%s must be owned and writable by either user "
365 CRM_DAEMON_USER " or group " CRM_DAEMON_GROUP " "
366 QB_XS " st_mode=0%lo",
367 target, (unsigned long) buf.st_mode);
368 free(full_file);
369 return false;
370 }
371
372 free(full_file);
373 return true;
374 }
375
376 /*!
377 * \internal
378 * \brief Flush and sync a directory to disk
379 *
380 * \param[in] name Directory to flush and sync
381 * \note This function logs errors but does not return them to the caller
382 */
383 void
384 pcmk__sync_directory(const char *name)
385 {
386 int fd;
387 DIR *directory;
388
389 directory = opendir(name);
390 if (directory == NULL) {
391 pcmk__err("Could not open %s for syncing: %s", name, strerror(errno));
392 return;
393 }
394
395 fd = dirfd(directory);
396 if (fd < 0) {
397 pcmk__err("Could not obtain file descriptor for %s: %s", name,
398 strerror(errno));
399 return;
400 }
401
402 if (fsync(fd) < 0) {
403 pcmk__err("Could not sync %s: %s", name, strerror(errno));
404 }
405 if (closedir(directory) < 0) {
406 pcmk__err("Could not close %s after fsync: %s", name, strerror(errno));
407 }
408 }
409
410 /*!
411 * \internal
412 * \brief Read the contents of a file
413 *
414 * \param[in] filename Name of file to read
415 * \param[out] contents Where to store file contents
416 *
417 * \return Standard Pacemaker return code
418 * \note On success, the caller is responsible for freeing contents.
419 */
420 int
421 pcmk__file_contents(const char *filename, char **contents)
422 {
423 FILE *fp;
424 int length, read_len;
425 int rc = pcmk_rc_ok;
426
|
(1) Event path: |
Condition "filename == NULL", taking false branch. |
|
(2) Event path: |
Condition "contents == NULL", taking false branch. |
427 if ((filename == NULL) || (contents == NULL)) {
428 return EINVAL;
429 }
430
431 fp = fopen(filename, "r");
|
(3) Event path: |
Condition "fp == NULL", taking false branch. |
|
(4) Event path: |
Condition "fseek(fp, 0L, 2) < 0", taking false branch. |
432 if ((fp == NULL) || (fseek(fp, 0L, SEEK_END) < 0)) {
433 rc = errno;
434 goto bail;
435 }
436
437 length = ftell(fp);
|
(5) Event path: |
Condition "length < 0", taking false branch. |
438 if (length < 0) {
439 rc = errno;
440 goto bail;
441 }
442
|
(6) Event path: |
Condition "length == 0", taking false branch. |
443 if (length == 0) {
444 *contents = NULL;
445 } else {
446 *contents = calloc(length + 1, sizeof(char));
|
(7) Event path: |
Condition "*contents == NULL", taking false branch. |
447 if (*contents == NULL) {
448 rc = errno;
449 goto bail;
450 }
451
452 errno = 0;
453
454 rewind(fp);
|
(8) Event path: |
Condition "*__errno_location() != 0", taking false branch. |
455 if (errno != 0) {
456 rc = errno;
457 goto bail;
458 }
459
460 read_len = fread(*contents, 1, length, fp);
|
(9) Event path: |
Condition "read_len != length", taking true branch. |
461 if (read_len != length) {
|
CID (unavailable; MK=1846b46f3d93adfe46fdd247cc450014) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS): |
|
(10) Event assign_union_field: |
The union field "in" of "_pp" is written. |
|
(11) Event inconsistent_union_field_access: |
In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in". |
462 g_clear_pointer(contents, &free);
463 rc = EIO;
464 } else {
465 /* Coverity thinks *contents isn't null-terminated. It doesn't
466 * understand calloc().
467 */
468 (*contents)[length] = '\0';
469 }
470 }
471
472 bail:
473 if (fp != NULL) {
474 fclose(fp);
475 }
476 return rc;
477 }
478
479 /*!
480 * \internal
481 * \brief Write text to a file, flush and sync it to disk, then close the file
482 *
483 * \param[in] fd File descriptor opened for writing
484 * \param[in] contents String to write to file
485 *
486 * \return Standard Pacemaker return code
487 */
488 int
489 pcmk__write_sync(int fd, const char *contents)
490 {
491 int rc = 0;
492 FILE *fp = fdopen(fd, "w");
493
494 if (fp == NULL) {
495 return errno;
496 }
497 if ((contents != NULL) && (fprintf(fp, "%s", contents) < 0)) {
498 rc = EIO;
499 }
500 if (fflush(fp) != 0) {
501 rc = errno;
502 }
503 if (fsync(fileno(fp)) < 0) {
504 rc = errno;
505 }
506 fclose(fp);
507 return rc;
508 }
509
510 /*!
511 * \internal
512 * \brief Set a file descriptor to non-blocking
513 *
514 * \param[in] fd File descriptor to use
515 *
516 * \return Standard Pacemaker return code
517 */
518 int
519 pcmk__set_nonblocking(int fd)
520 {
521 int flag = fcntl(fd, F_GETFL);
522
523 if (flag < 0) {
524 return errno;
525 }
526 if (fcntl(fd, F_SETFL, flag | O_NONBLOCK) < 0) {
527 return errno;
528 }
529 return pcmk_rc_ok;
530 }
531
532 /*!
533 * \internal
534 * \brief Get directory name for temporary files
535 *
536 * Return the value of the TMPDIR environment variable if it is set to a
537 * full path, otherwise return "/tmp".
538 *
539 * \return Name of directory to be used for temporary files
540 */
541 const char *
542 pcmk__get_tmpdir(void)
543 {
544 const char *dir = getenv("TMPDIR");
545
546 return (dir && (*dir == '/'))? dir : "/tmp";
547 }
548
549 /*!
550 * \internal
551 * \brief Close open file descriptors except standard streams
552 *
553 * Close all file descriptors (except stdin, stdout, and stderr), which is a
554 * best practice for a new child process forked for the purpose of executing an
555 * external program.
556 */
557 void
558 pcmk__close_fds_in_child(void)
559 {
560 DIR *dir;
561 struct rlimit rlim;
562 rlim_t max_fd;
563 const int min_fd = STDERR_FILENO + 1;
564
565 /* Find the current process's (soft) limit for open files. getrlimit()
566 * should always work, but have a fallback just in case.
567 */
568 if (getrlimit(RLIMIT_NOFILE, &rlim) == 0) {
569 max_fd = rlim.rlim_cur - 1;
570 } else {
571 long conf_max = sysconf(_SC_OPEN_MAX);
572
573 max_fd = (conf_max > 0)? conf_max : 1024;
574 }
575
576 /* First try /proc. If that returns NULL (either because opening the
577 * directory failed, or because procfs isn't supported on this platform),
578 * fall back to /dev/fd.
579 */
580 dir = pcmk__procfs_fd_dir();
581 if (dir == NULL) {
582 dir = opendir("/dev/fd");
583 }
584
585 if (dir != NULL) {
586 struct dirent *entry;
587 int dir_fd = dirfd(dir);
588
589 while ((entry = readdir(dir)) != NULL) {
590 int lpc = atoi(entry->d_name);
591
592 /* How could one of these entries be higher than max_fd, you ask?
593 * It isn't possible in normal operation, but when run under
594 * valgrind, valgrind can open high-numbered file descriptors for
595 * its own use that are higher than the process's soft limit.
596 * These will show up in the fd directory but aren't closable.
597 */
598 if ((lpc >= min_fd) && (lpc <= max_fd) && (lpc != dir_fd)) {
599 close(lpc);
600 }
601 }
602 closedir(dir);
603 return;
604 }
605
606 /* If no fd directory is available, iterate over all possible descriptors.
607 * This is less efficient due to the overhead of many system calls.
608 */
609 for (int lpc = max_fd; lpc >= min_fd; lpc--) {
610 close(lpc);
611 }
612 }
613
614 /*!
615 * \brief Duplicate a file path, inserting a prefix if not absolute
616 *
617 * \param[in] filename File path to duplicate
618 * \param[in] dirname If filename is not absolute, prefix to add
619 *
620 * \return Newly allocated memory with full path (guaranteed non-NULL)
621 */
622 char *
623 pcmk__full_path(const char *filename, const char *dirname)
624 {
625 pcmk__assert(filename != NULL);
626
627 if (filename[0] == '/') {
628 return pcmk__str_copy(filename);
629 }
630 pcmk__assert(dirname != NULL);
631 return pcmk__assert_asprintf("%s/%s", dirname, filename);
632 }
633