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-2022 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)? pcmk_rc_unknown_format : 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] dir  Directory that contains series
 230  * \param[in] uid  User ID of desired file owner
 231  * \param[in] gid  Group ID of desired file group
 232  *
 233  * \return Standard Pacemaker return code
 234  * \note The caller must have the appropriate privileges.
 235  */
 236 int
 237 pcmk__chown_series_sequence(const char *directory, const char *series,
     /* [previous][next][first][last][top][bottom][index][help] */
 238                             uid_t uid, gid_t gid)
 239 {
 240     char *series_file = NULL;
 241     int rc = pcmk_rc_ok;
 242 
 243     if ((directory == NULL) || (series == NULL)) {
 244         return EINVAL;
 245     }
 246     series_file = crm_strdup_printf("%s/%s.last", directory, series);
 247     if (chown(series_file, uid, gid) < 0) {
 248         rc = errno;
 249     }
 250     free(series_file);
 251     return rc;
 252 }
 253 
 254 static bool
 255 pcmk__daemon_user_can_write(const char *target_name, struct stat *target_stat)
     /* [previous][next][first][last][top][bottom][index][help] */
 256 {
 257     struct passwd *sys_user = NULL;
 258 
 259     errno = 0;
 260     sys_user = getpwnam(CRM_DAEMON_USER);
 261     if (sys_user == NULL) {
 262         crm_notice("Could not find user %s: %s",
 263                    CRM_DAEMON_USER, pcmk_rc_str(errno));
 264         return FALSE;
 265     }
 266     if (target_stat->st_uid != sys_user->pw_uid) {
 267         crm_notice("%s is not owned by user %s " CRM_XS " uid %d != %d",
 268                    target_name, CRM_DAEMON_USER, sys_user->pw_uid,
 269                    target_stat->st_uid);
 270         return FALSE;
 271     }
 272     if ((target_stat->st_mode & (S_IRUSR | S_IWUSR)) == 0) {
 273         crm_notice("%s is not readable and writable by user %s "
 274                    CRM_XS " st_mode=0%lo",
 275                    target_name, CRM_DAEMON_USER,
 276                    (unsigned long) target_stat->st_mode);
 277         return FALSE;
 278     }
 279     return TRUE;
 280 }
 281 
 282 static bool
 283 pcmk__daemon_group_can_write(const char *target_name, struct stat *target_stat)
     /* [previous][next][first][last][top][bottom][index][help] */
 284 {
 285     struct group *sys_grp = NULL;
 286 
 287     errno = 0;
 288     sys_grp = getgrnam(CRM_DAEMON_GROUP);
 289     if (sys_grp == NULL) {
 290         crm_notice("Could not find group %s: %s",
 291                    CRM_DAEMON_GROUP, pcmk_rc_str(errno));
 292         return FALSE;
 293     }
 294 
 295     if (target_stat->st_gid != sys_grp->gr_gid) {
 296         crm_notice("%s is not owned by group %s " CRM_XS " uid %d != %d",
 297                    target_name, CRM_DAEMON_GROUP,
 298                    sys_grp->gr_gid, target_stat->st_gid);
 299         return FALSE;
 300     }
 301 
 302     if ((target_stat->st_mode & (S_IRGRP | S_IWGRP)) == 0) {
 303         crm_notice("%s is not readable and writable by group %s "
 304                    CRM_XS " st_mode=0%lo",
 305                    target_name, CRM_DAEMON_GROUP,
 306                    (unsigned long) target_stat->st_mode);
 307         return FALSE;
 308     }
 309     return TRUE;
 310 }
 311 
 312 /*!
 313  * \internal
 314  * \brief Check whether a directory or file is writable by the cluster daemon
 315  *
 316  * Return true if either the cluster daemon user or cluster daemon group has
 317  * write permission on a specified file or directory.
 318  *
 319  * \param[in] dir      Directory to check (this argument must be specified, and
 320  *                     the directory must exist)
 321  * \param[in] file     File to check (only the directory will be checked if this
 322  *                     argument is not specified or the file does not exist)
 323  *
 324  * \return true if target is writable by cluster daemon, false otherwise
 325  */
 326 bool
 327 pcmk__daemon_can_write(const char *dir, const char *file)
     /* [previous][next][first][last][top][bottom][index][help] */
 328 {
 329     int s_res = 0;
 330     struct stat buf;
 331     char *full_file = NULL;
 332     const char *target = NULL;
 333 
 334     // Caller must supply directory
 335     CRM_ASSERT(dir != NULL);
 336 
 337     // If file is given, check whether it exists as a regular file
 338     if (file != NULL) {
 339         full_file = crm_strdup_printf("%s/%s", dir, file);
 340         target = full_file;
 341 
 342         s_res = stat(full_file, &buf);
 343         if (s_res < 0) {
 344             crm_notice("%s not found: %s", target, pcmk_rc_str(errno));
 345             free(full_file);
 346             full_file = NULL;
 347             target = NULL;
 348 
 349         } else if (S_ISREG(buf.st_mode) == FALSE) {
 350             crm_err("%s must be a regular file " CRM_XS " st_mode=0%lo",
 351                     target, (unsigned long) buf.st_mode);
 352             free(full_file);
 353             return false;
 354         }
 355     }
 356 
 357     // If file is not given, ensure dir exists as directory
 358     if (target == NULL) {
 359         target = dir;
 360         s_res = stat(dir, &buf);
 361         if (s_res < 0) {
 362             crm_err("%s not found: %s", dir, pcmk_rc_str(errno));
 363             return false;
 364 
 365         } else if (S_ISDIR(buf.st_mode) == FALSE) {
 366             crm_err("%s must be a directory " CRM_XS " st_mode=0%lo",
 367                     dir, (unsigned long) buf.st_mode);
 368             return false;
 369         }
 370     }
 371 
 372     if (!pcmk__daemon_user_can_write(target, &buf)
 373         && !pcmk__daemon_group_can_write(target, &buf)) {
 374 
 375         crm_err("%s must be owned and writable by either user %s or group %s "
 376                 CRM_XS " st_mode=0%lo",
 377                 target, CRM_DAEMON_USER, CRM_DAEMON_GROUP,
 378                 (unsigned long) buf.st_mode);
 379         free(full_file);
 380         return false;
 381     }
 382 
 383     free(full_file);
 384     return true;
 385 }
 386 
 387 /*!
 388  * \internal
 389  * \brief Flush and sync a directory to disk
 390  *
 391  * \param[in] name Directory to flush and sync
 392  * \note This function logs errors but does not return them to the caller
 393  */
 394 void
 395 pcmk__sync_directory(const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
 396 {
 397     int fd;
 398     DIR *directory;
 399 
 400     directory = opendir(name);
 401     if (directory == NULL) {
 402         crm_perror(LOG_ERR, "Could not open %s for syncing", name);
 403         return;
 404     }
 405 
 406     fd = dirfd(directory);
 407     if (fd < 0) {
 408         crm_perror(LOG_ERR, "Could not obtain file descriptor for %s", name);
 409         return;
 410     }
 411 
 412     if (fsync(fd) < 0) {
 413         crm_perror(LOG_ERR, "Could not sync %s", name);
 414     }
 415     if (closedir(directory) < 0) {
 416         crm_perror(LOG_ERR, "Could not close %s after fsync", name);
 417     }
 418 }
 419 
 420 /*!
 421  * \internal
 422  * \brief Read the contents of a file
 423  *
 424  * \param[in]  filename  Name of file to read
 425  * \param[out] contents  Where to store file contents
 426  *
 427  * \return Standard Pacemaker return code
 428  * \note On success, the caller is responsible for freeing contents.
 429  */
 430 int
 431 pcmk__file_contents(const char *filename, char **contents)
     /* [previous][next][first][last][top][bottom][index][help] */
 432 {
 433     FILE *fp;
 434     int length, read_len;
 435     int rc = pcmk_rc_ok;
 436 
 437     if ((filename == NULL) || (contents == NULL)) {
 438         return EINVAL;
 439     }
 440 
 441     fp = fopen(filename, "r");
 442     if ((fp == NULL) || (fseek(fp, 0L, SEEK_END) < 0)) {
 443         rc = errno;
 444         goto bail;
 445     }
 446 
 447     length = ftell(fp);
 448     if (length < 0) {
 449         rc = errno;
 450         goto bail;
 451     }
 452 
 453     if (length == 0) {
 454         *contents = NULL;
 455     } else {
 456         *contents = calloc(length + 1, sizeof(char));
 457         if (*contents == NULL) {
 458             rc = errno;
 459             goto bail;
 460         }
 461         rewind(fp);
 462         read_len = fread(*contents, 1, length, fp); /* Coverity: False positive */
 463         if (read_len != length) {
 464             free(*contents);
 465             *contents = NULL;
 466             rc = EIO;
 467         }
 468     }
 469 
 470 bail:
 471     if (fp != NULL) {
 472         fclose(fp);
 473     }
 474     return rc;
 475 }
 476 
 477 /*!
 478  * \internal
 479  * \brief Write text to a file, flush and sync it to disk, then close the file
 480  *
 481  * \param[in] fd        File descriptor opened for writing
 482  * \param[in] contents  String to write to file
 483  *
 484  * \return Standard Pacemaker return code
 485  */
 486 int
 487 pcmk__write_sync(int fd, const char *contents)
     /* [previous][next][first][last][top][bottom][index][help] */
 488 {
 489     int rc = 0;
 490     FILE *fp = fdopen(fd, "w");
 491 
 492     if (fp == NULL) {
 493         return errno;
 494     }
 495     if ((contents != NULL) && (fprintf(fp, "%s", contents) < 0)) {
 496         rc = EIO;
 497     }
 498     if (fflush(fp) != 0) {
 499         rc = errno;
 500     }
 501     if (fsync(fileno(fp)) < 0) {
 502         rc = errno;
 503     }
 504     fclose(fp);
 505     return rc;
 506 }
 507 
 508 /*!
 509  * \internal
 510  * \brief Set a file descriptor to non-blocking
 511  *
 512  * \param[in] fd  File descriptor to use
 513  *
 514  * \return Standard Pacemaker return code
 515  */
 516 int
 517 pcmk__set_nonblocking(int fd)
     /* [previous][next][first][last][top][bottom][index][help] */
 518 {
 519     int flag = fcntl(fd, F_GETFL);
 520 
 521     if (flag < 0) {
 522         return errno;
 523     }
 524     if (fcntl(fd, F_SETFL, flag | O_NONBLOCK) < 0) {
 525         return errno;
 526     }
 527     return pcmk_rc_ok;
 528 }
 529 
 530 /*!
 531  * \internal
 532  * \brief Get directory name for temporary files
 533  *
 534  * Return the value of the TMPDIR environment variable if it is set to a
 535  * full path, otherwise return "/tmp".
 536  *
 537  * \return Name of directory to be used for temporary files
 538  */
 539 const char *
 540 pcmk__get_tmpdir()
     /* [previous][next][first][last][top][bottom][index][help] */
 541 {
 542     const char *dir = getenv("TMPDIR");
 543 
 544     return (dir && (*dir == '/'))? dir : "/tmp";
 545 }
 546 
 547 /*!
 548  * \internal
 549  * \brief Close open file descriptors
 550  *
 551  * Close all file descriptors (except optionally stdin, stdout, and stderr),
 552  * which is a best practice for a new child process forked for the purpose of
 553  * executing an external program.
 554  *
 555  * \param[in] bool  If true, close stdin, stdout, and stderr as well
 556  */
 557 void
 558 pcmk__close_fds_in_child(bool all)
     /* [previous][next][first][last][top][bottom][index][help] */
 559 {
 560     DIR *dir;
 561     struct rlimit rlim;
 562     rlim_t max_fd;
 563     int min_fd = (all? 0 : (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     /* /proc/self/fd (on Linux) or /dev/fd (on most OSes) contains symlinks to
 577      * all open files for the current process, named as the file descriptor.
 578      * Use this if available, because it's more efficient than a shotgun
 579      * approach to closing descriptors.
 580      */
 581 #if SUPPORT_PROCFS
 582     dir = opendir("/proc/self/fd");
 583     if (dir == NULL) {
 584         dir = opendir("/dev/fd");
 585     }
 586 #else
 587     dir = opendir("/dev/fd");
 588 #endif
 589     if (dir != NULL) {
 590         struct dirent *entry;
 591         int dir_fd = dirfd(dir);
 592 
 593         while ((entry = readdir(dir)) != NULL) {
 594             int lpc = atoi(entry->d_name);
 595 
 596             /* How could one of these entries be higher than max_fd, you ask?
 597              * It isn't possible in normal operation, but when run under
 598              * valgrind, valgrind can open high-numbered file descriptors for
 599              * its own use that are higher than the process's soft limit.
 600              * These will show up in the fd directory but aren't closable.
 601              */
 602             if ((lpc >= min_fd) && (lpc <= max_fd) && (lpc != dir_fd)) {
 603                 close(lpc);
 604             }
 605         }
 606         closedir(dir);
 607         return;
 608     }
 609 
 610     /* If no fd directory is available, iterate over all possible descriptors.
 611      * This is less efficient due to the overhead of many system calls.
 612      */
 613     for (int lpc = max_fd; lpc >= min_fd; lpc--) {
 614         close(lpc);
 615     }
 616 }
 617 
 618 /*!
 619  * \brief Duplicate a file path, inserting a prefix if not absolute
 620  *
 621  * \param[in] filename  File path to duplicate
 622  * \param[in] dirname   If filename is not absolute, prefix to add
 623  *
 624  * \return Newly allocated memory with full path (guaranteed non-NULL)
 625  */
 626 char *
 627 pcmk__full_path(const char *filename, const char *dirname)
     /* [previous][next][first][last][top][bottom][index][help] */
 628 {
 629     char *path = NULL;
 630 
 631     CRM_ASSERT(filename != NULL);
 632 
 633     if (filename[0] == '/') {
 634         path = strdup(filename);
 635         CRM_ASSERT(path != NULL);
 636 
 637     } else {
 638         CRM_ASSERT(dirname != NULL);
 639         path = crm_strdup_printf("%s/%s", dirname, filename);
 640     }
 641 
 642     return path;
 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] */