root/lib/common/io.c

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

DEFINITIONS

This source file includes following definitions.
  1. pcmk__build_path
  2. pcmk__real_path
  3. pcmk__series_filename
  4. pcmk__read_series_sequence
  5. pcmk__write_series_sequence
  6. pcmk__chown_series_sequence
  7. pcmk__daemon_user_can_write
  8. pcmk__daemon_group_can_write
  9. pcmk__daemon_can_write
  10. pcmk__sync_directory
  11. pcmk__file_contents
  12. pcmk__write_sync
  13. pcmk__set_nonblocking
  14. pcmk__get_tmpdir
  15. pcmk__close_fds_in_child
  16. pcmk__full_path

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

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