LCOV - code coverage report
Current view: top level - common - io.c (source / functions) Hit Total Coverage
Test: Pacemaker code coverage Lines: 9 230 3.9 %
Date: 2024-05-07 11:09:47 Functions: 2 16 12.5 %

          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

Generated by: LCOV version 1.14