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