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

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