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
  17. crm_build_path

   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 pcmk__build_path(const char *path_c, mode_t mode)
     /* [previous][next][first][last][top][bottom][index][help] */
  46 {
  47     int offset = 1, len = 0;
  48     int rc = pcmk_rc_ok;
  49     char *path = strdup(path_c);
  50 
  51     // cppcheck seems not to understand the abort logic in CRM_CHECK
  52     // cppcheck-suppress memleak
  53     CRM_CHECK(path != NULL, return -ENOMEM);
  54     for (len = strlen(path); offset < len; offset++) {
  55         if (path[offset] == '/') {
  56             path[offset] = 0;
  57             if ((mkdir(path, mode) < 0) && (errno != EEXIST)) {
  58                 rc = errno;
  59                 goto done;
  60             }
  61             path[offset] = '/';
  62         }
  63     }
  64     if ((mkdir(path, mode) < 0) && (errno != EEXIST)) {
  65         rc = errno;
  66     }
  67 done:
  68     free(path);
  69     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 pcmk__real_path(const char *path, char **resolved_path)
     /* [previous][next][first][last][top][bottom][index][help] */
  86 {
  87     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     *resolved_path = realpath(path, NULL);
  92     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 pcmk__series_filename(const char *directory, const char *series, int sequence,
     /* [previous][next][first][last][top][bottom][index][help] */
 122                       bool bzip)
 123 {
 124     CRM_ASSERT((directory != NULL) && (series != NULL));
 125     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 pcmk__read_series_sequence(const char *directory, const char *series,
     /* [previous][next][first][last][top][bottom][index][help] */
 141                            unsigned int *seq)
 142 {
 143     int rc;
 144     FILE *fp = NULL;
 145     char *series_file = NULL;
 146 
 147     if ((directory == NULL) || (series == NULL) || (seq == NULL)) {
 148         return EINVAL;
 149     }
 150 
 151     series_file = crm_strdup_printf("%s/%s.last", directory, series);
 152     fp = fopen(series_file, "r");
 153     if (fp == NULL) {
 154         rc = errno;
 155         crm_debug("Could not open series file %s: %s",
 156                   series_file, strerror(rc));
 157         free(series_file);
 158         return rc;
 159     }
 160     errno = 0;
 161     if (fscanf(fp, "%u", seq) != 1) {
 162         rc = (errno == 0)? ENODATA : errno;
 163         crm_debug("Could not read sequence number from series file %s: %s",
 164                   series_file, pcmk_rc_str(rc));
 165         fclose(fp);
 166         return rc;
 167     }
 168     fclose(fp);
 169     crm_trace("Found last sequence number %u in series file %s",
 170               *seq, series_file);
 171     free(series_file);
 172     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 pcmk__write_series_sequence(const char *directory, const char *series,
     /* [previous][next][first][last][top][bottom][index][help] */
 188                             unsigned int sequence, int max)
 189 {
 190     int rc = 0;
 191     FILE *file_strm = NULL;
 192     char *series_file = NULL;
 193 
 194     CRM_CHECK(directory != NULL, return);
 195     CRM_CHECK(series != NULL, return);
 196 
 197     if (max == 0) {
 198         return;
 199     }
 200     if (max > 0 && sequence >= max) {
 201         sequence = 0;
 202     }
 203 
 204     series_file = crm_strdup_printf("%s/%s.last", directory, series);
 205     file_strm = fopen(series_file, "w");
 206     if (file_strm != NULL) {
 207         rc = fprintf(file_strm, "%u", sequence);
 208         if (rc < 0) {
 209             crm_perror(LOG_ERR, "Cannot write to series file %s", series_file);
 210         }
 211 
 212     } else {
 213         crm_err("Cannot open series file %s for writing", series_file);
 214     }
 215 
 216     if (file_strm != NULL) {
 217         fflush(file_strm);
 218         fclose(file_strm);
 219     }
 220 
 221     crm_trace("Wrote %d to %s", sequence, series_file);
 222     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 pcmk__chown_series_sequence(const char *directory, const char *series,
     /* [previous][next][first][last][top][bottom][index][help] */
 239                             uid_t uid, gid_t gid)
 240 {
 241     char *series_file = NULL;
 242     int rc = pcmk_rc_ok;
 243 
 244     if ((directory == NULL) || (series == NULL)) {
 245         return EINVAL;
 246     }
 247     series_file = crm_strdup_printf("%s/%s.last", directory, series);
 248     if (chown(series_file, uid, gid) < 0) {
 249         rc = errno;
 250     }
 251     free(series_file);
 252     return rc;
 253 }
 254 
 255 static bool
 256 pcmk__daemon_user_can_write(const char *target_name, struct stat *target_stat)
     /* [previous][next][first][last][top][bottom][index][help] */
 257 {
 258     struct passwd *sys_user = NULL;
 259 
 260     errno = 0;
 261     sys_user = getpwnam(CRM_DAEMON_USER);
 262     if (sys_user == NULL) {
 263         crm_notice("Could not find user %s: %s",
 264                    CRM_DAEMON_USER, pcmk_rc_str(errno));
 265         return FALSE;
 266     }
 267     if (target_stat->st_uid != sys_user->pw_uid) {
 268         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         return FALSE;
 272     }
 273     if ((target_stat->st_mode & (S_IRUSR | S_IWUSR)) == 0) {
 274         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         return FALSE;
 279     }
 280     return TRUE;
 281 }
 282 
 283 static bool
 284 pcmk__daemon_group_can_write(const char *target_name, struct stat *target_stat)
     /* [previous][next][first][last][top][bottom][index][help] */
 285 {
 286     struct group *sys_grp = NULL;
 287 
 288     errno = 0;
 289     sys_grp = getgrnam(CRM_DAEMON_GROUP);
 290     if (sys_grp == NULL) {
 291         crm_notice("Could not find group %s: %s",
 292                    CRM_DAEMON_GROUP, pcmk_rc_str(errno));
 293         return FALSE;
 294     }
 295 
 296     if (target_stat->st_gid != sys_grp->gr_gid) {
 297         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         return FALSE;
 301     }
 302 
 303     if ((target_stat->st_mode & (S_IRGRP | S_IWGRP)) == 0) {
 304         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         return FALSE;
 309     }
 310     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 pcmk__daemon_can_write(const char *dir, const char *file)
     /* [previous][next][first][last][top][bottom][index][help] */
 329 {
 330     int s_res = 0;
 331     struct stat buf;
 332     char *full_file = NULL;
 333     const char *target = NULL;
 334 
 335     // Caller must supply directory
 336     CRM_ASSERT(dir != NULL);
 337 
 338     // If file is given, check whether it exists as a regular file
 339     if (file != NULL) {
 340         full_file = crm_strdup_printf("%s/%s", dir, file);
 341         target = full_file;
 342 
 343         s_res = stat(full_file, &buf);
 344         if (s_res < 0) {
 345             crm_notice("%s not found: %s", target, pcmk_rc_str(errno));
 346             free(full_file);
 347             full_file = NULL;
 348             target = NULL;
 349 
 350         } else if (S_ISREG(buf.st_mode) == FALSE) {
 351             crm_err("%s must be a regular file " CRM_XS " st_mode=0%lo",
 352                     target, (unsigned long) buf.st_mode);
 353             free(full_file);
 354             return false;
 355         }
 356     }
 357 
 358     // If file is not given, ensure dir exists as directory
 359     if (target == NULL) {
 360         target = dir;
 361         s_res = stat(dir, &buf);
 362         if (s_res < 0) {
 363             crm_err("%s not found: %s", dir, pcmk_rc_str(errno));
 364             return false;
 365 
 366         } else if (S_ISDIR(buf.st_mode) == FALSE) {
 367             crm_err("%s must be a directory " CRM_XS " st_mode=0%lo",
 368                     dir, (unsigned long) buf.st_mode);
 369             return false;
 370         }
 371     }
 372 
 373     if (!pcmk__daemon_user_can_write(target, &buf)
 374         && !pcmk__daemon_group_can_write(target, &buf)) {
 375 
 376         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         free(full_file);
 381         return false;
 382     }
 383 
 384     free(full_file);
 385     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 pcmk__sync_directory(const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
 397 {
 398     int fd;
 399     DIR *directory;
 400 
 401     directory = opendir(name);
 402     if (directory == NULL) {
 403         crm_perror(LOG_ERR, "Could not open %s for syncing", name);
 404         return;
 405     }
 406 
 407     fd = dirfd(directory);
 408     if (fd < 0) {
 409         crm_perror(LOG_ERR, "Could not obtain file descriptor for %s", name);
 410         return;
 411     }
 412 
 413     if (fsync(fd) < 0) {
 414         crm_perror(LOG_ERR, "Could not sync %s", name);
 415     }
 416     if (closedir(directory) < 0) {
 417         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 pcmk__file_contents(const char *filename, char **contents)
     /* [previous][next][first][last][top][bottom][index][help] */
 433 {
 434     FILE *fp;
 435     int length, read_len;
 436     int rc = pcmk_rc_ok;
 437 
 438     if ((filename == NULL) || (contents == NULL)) {
 439         return EINVAL;
 440     }
 441 
 442     fp = fopen(filename, "r");
 443     if ((fp == NULL) || (fseek(fp, 0L, SEEK_END) < 0)) {
 444         rc = errno;
 445         goto bail;
 446     }
 447 
 448     length = ftell(fp);
 449     if (length < 0) {
 450         rc = errno;
 451         goto bail;
 452     }
 453 
 454     if (length == 0) {
 455         *contents = NULL;
 456     } else {
 457         *contents = calloc(length + 1, sizeof(char));
 458         if (*contents == NULL) {
 459             rc = errno;
 460             goto bail;
 461         }
 462         rewind(fp);
 463 
 464         read_len = fread(*contents, 1, length, fp);
 465         if (read_len != length) {
 466             free(*contents);
 467             *contents = NULL;
 468             rc = EIO;
 469         } else {
 470             /* Coverity thinks *contents isn't null-terminated. It doesn't
 471              * understand calloc().
 472              */
 473             (*contents)[length] = '\0';
 474         }
 475     }
 476 
 477 bail:
 478     if (fp != NULL) {
 479         fclose(fp);
 480     }
 481     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 pcmk__write_sync(int fd, const char *contents)
     /* [previous][next][first][last][top][bottom][index][help] */
 495 {
 496     int rc = 0;
 497     FILE *fp = fdopen(fd, "w");
 498 
 499     if (fp == NULL) {
 500         return errno;
 501     }
 502     if ((contents != NULL) && (fprintf(fp, "%s", contents) < 0)) {
 503         rc = EIO;
 504     }
 505     if (fflush(fp) != 0) {
 506         rc = errno;
 507     }
 508     if (fsync(fileno(fp)) < 0) {
 509         rc = errno;
 510     }
 511     fclose(fp);
 512     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 pcmk__set_nonblocking(int fd)
     /* [previous][next][first][last][top][bottom][index][help] */
 525 {
 526     int flag = fcntl(fd, F_GETFL);
 527 
 528     if (flag < 0) {
 529         return errno;
 530     }
 531     if (fcntl(fd, F_SETFL, flag | O_NONBLOCK) < 0) {
 532         return errno;
 533     }
 534     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 pcmk__get_tmpdir(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 548 {
 549     const char *dir = getenv("TMPDIR");
 550 
 551     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 pcmk__close_fds_in_child(bool all)
     /* [previous][next][first][last][top][bottom][index][help] */
 566 {
 567     DIR *dir;
 568     struct rlimit rlim;
 569     rlim_t max_fd;
 570     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     if (getrlimit(RLIMIT_NOFILE, &rlim) == 0) {
 576         max_fd = rlim.rlim_cur - 1;
 577     } else {
 578         long conf_max = sysconf(_SC_OPEN_MAX);
 579 
 580         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     dir = opendir("/proc/self/fd");
 590     if (dir == NULL) {
 591         dir = opendir("/dev/fd");
 592     }
 593 #else
 594     dir = opendir("/dev/fd");
 595 #endif // HAVE_LINUX_PROCFS
 596     if (dir != NULL) {
 597         struct dirent *entry;
 598         int dir_fd = dirfd(dir);
 599 
 600         while ((entry = readdir(dir)) != NULL) {
 601             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             if ((lpc >= min_fd) && (lpc <= max_fd) && (lpc != dir_fd)) {
 610                 close(lpc);
 611             }
 612         }
 613         closedir(dir);
 614         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     for (int lpc = max_fd; lpc >= min_fd; lpc--) {
 621         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 pcmk__full_path(const char *filename, const char *dirname)
     /* [previous][next][first][last][top][bottom][index][help] */
 635 {
 636     CRM_ASSERT(filename != NULL);
 637 
 638     if (filename[0] == '/') {
 639         return pcmk__str_copy(filename);
 640     }
 641     CRM_ASSERT(dirname != NULL);
 642     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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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

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