root/lib/common/io.c

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

DEFINITIONS

This source file includes following definitions.
  1. crm_build_path
  2. generate_series_filename
  3. get_last_sequence
  4. write_last_sequence
  5. crm_chown_last_sequence
  6. crm_is_writable
  7. crm_sync_directory
  8. crm_read_contents
  9. crm_write_sync
  10. crm_set_nonblocking

   1 /*
   2  * Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
   3  *
   4  * This library is free software; you can redistribute it and/or
   5  * modify it under the terms of the GNU Lesser General Public
   6  * License as published by the Free Software Foundation; either
   7  * version 2.1 of the License, or (at your option) any later version.
   8  *
   9  * This library is distributed in the hope that it will be useful,
  10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  12  * Lesser General Public License for more details.
  13  *
  14  * You should have received a copy of the GNU Lesser General Public
  15  * License along with this library; if not, write to the Free Software
  16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  17  */
  18 
  19 #include <crm_internal.h>
  20 
  21 #ifndef _GNU_SOURCE
  22 #  define _GNU_SOURCE
  23 #endif
  24 
  25 #include <sys/param.h>
  26 #include <sys/types.h>
  27 #include <sys/stat.h>
  28 
  29 #include <stdio.h>
  30 #include <unistd.h>
  31 #include <string.h>
  32 #include <stdlib.h>
  33 #include <fcntl.h>
  34 #include <dirent.h>
  35 #include <pwd.h>
  36 #include <grp.h>
  37 
  38 #include <crm/crm.h>
  39 #include <crm/common/util.h>
  40 
  41 /*!
  42  * \brief Create a directory, including any parent directories needed
  43  *
  44  * \param[in] path_c Pathname of the directory to create
  45  * \param[in] mode Permissions to be used (with current umask) when creating
  46  *
  47  * \note This logs errors but does not return them to the caller.
  48  */
  49 void
  50 crm_build_path(const char *path_c, mode_t mode)
     /* [previous][next][first][last][top][bottom][index][help] */
  51 {
  52     int offset = 1, len = 0;
  53     char *path = strdup(path_c);
  54 
  55     CRM_CHECK(path != NULL, return);
  56     for (len = strlen(path); offset < len; offset++) {
  57         if (path[offset] == '/') {
  58             path[offset] = 0;
  59             if (mkdir(path, mode) < 0 && errno != EEXIST) {
  60                 crm_perror(LOG_ERR, "Could not create directory '%s'", path);
  61                 break;
  62             }
  63             path[offset] = '/';
  64         }
  65     }
  66     if (mkdir(path, mode) < 0 && errno != EEXIST) {
  67         crm_perror(LOG_ERR, "Could not create directory '%s'", path);
  68     }
  69 
  70     free(path);
  71 }
  72 
  73 /*!
  74  * \internal
  75  * \brief Allocate and create a file path using a sequence number
  76  *
  77  * \param[in] directory Directory that contains the file series
  78  * \param[in] series Start of file name
  79  * \param[in] sequence Sequence number (MUST be less than 33 digits)
  80  * \param[in] bzip Whether to use ".bz2" instead of ".raw" as extension
  81  *
  82  * \return Newly allocated file path, or NULL on error
  83  * \note Caller is responsible for freeing the returned memory
  84  */
  85 char *
  86 generate_series_filename(const char *directory, const char *series, int sequence, gboolean bzip)
     /* [previous][next][first][last][top][bottom][index][help] */
  87 {
  88     int len = 40;
  89     char *filename = NULL;
  90     const char *ext = "raw";
  91 
  92     CRM_CHECK(directory != NULL, return NULL);
  93     CRM_CHECK(series != NULL, return NULL);
  94 
  95 #if !HAVE_BZLIB_H
  96     bzip = FALSE;
  97 #endif
  98 
  99     len += strlen(directory);
 100     len += strlen(series);
 101     filename = malloc(len);
 102     CRM_CHECK(filename != NULL, return NULL);
 103 
 104     if (bzip) {
 105         ext = "bz2";
 106     }
 107     sprintf(filename, "%s/%s-%d.%s", directory, series, sequence, ext);
 108 
 109     return filename;
 110 }
 111 
 112 /*!
 113  * \internal
 114  * \brief Read and return sequence number stored in a file series' .last file
 115  *
 116  * \param[in] directory Directory that contains the file series
 117  * \param[in] series Start of file name
 118  *
 119  * \return The last sequence number, or 0 on error
 120  */
 121 int
 122 get_last_sequence(const char *directory, const char *series)
     /* [previous][next][first][last][top][bottom][index][help] */
 123 {
 124     FILE *file_strm = NULL;
 125     int start = 0, length = 0, read_len = 0;
 126     char *series_file = NULL;
 127     char *buffer = NULL;
 128     int seq = 0;
 129     int len = 36;
 130 
 131     CRM_CHECK(directory != NULL, return 0);
 132     CRM_CHECK(series != NULL, return 0);
 133 
 134     len += strlen(directory);
 135     len += strlen(series);
 136     series_file = malloc(len);
 137     CRM_CHECK(series_file != NULL, return 0);
 138     sprintf(series_file, "%s/%s.last", directory, series);
 139 
 140     file_strm = fopen(series_file, "r");
 141     if (file_strm == NULL) {
 142         crm_debug("Series file %s does not exist", series_file);
 143         free(series_file);
 144         return 0;
 145     }
 146 
 147     /* see how big the file is */
 148     start = ftell(file_strm);
 149     fseek(file_strm, 0L, SEEK_END);
 150     length = ftell(file_strm);
 151     fseek(file_strm, 0L, start);
 152 
 153     CRM_ASSERT(length >= 0);
 154     CRM_ASSERT(start == ftell(file_strm));
 155 
 156     if (length <= 0) {
 157         crm_info("%s was not valid", series_file);
 158         free(buffer);
 159         buffer = NULL;
 160 
 161     } else {
 162         crm_trace("Reading %d bytes from file", length);
 163         buffer = calloc(1, (length + 1));
 164         read_len = fread(buffer, 1, length, file_strm);
 165         if (read_len != length) {
 166             crm_err("Calculated and read bytes differ: %d vs. %d", length, read_len);
 167             free(buffer);
 168             buffer = NULL;
 169         }
 170     }
 171 
 172     seq = crm_parse_int(buffer, "0");
 173     fclose(file_strm);
 174 
 175     crm_trace("Found %d in %s", seq, series_file);
 176 
 177     free(series_file);
 178     free(buffer);
 179     return seq;
 180 }
 181 
 182 /*!
 183  * \internal
 184  * \brief Write sequence number to a file series' .last file
 185  *
 186  * \param[in] directory Directory that contains the file series
 187  * \param[in] series Start of file name
 188  * \param[in] sequence Sequence number to write
 189  * \param[in] max Maximum sequence value, after which sequence is reset to 0
 190  *
 191  * \note This function logs some errors but does not return any to the caller
 192  */
 193 void
 194 write_last_sequence(const char *directory, const char *series, int sequence, int max)
     /* [previous][next][first][last][top][bottom][index][help] */
 195 {
 196     int rc = 0;
 197     int len = 36;
 198     FILE *file_strm = NULL;
 199     char *series_file = NULL;
 200 
 201     CRM_CHECK(directory != NULL, return);
 202     CRM_CHECK(series != NULL, return);
 203 
 204     if (max == 0) {
 205         return;
 206     }
 207     if (max > 0 && sequence >= max) {
 208         sequence = 0;
 209     }
 210 
 211     len += strlen(directory);
 212     len += strlen(series);
 213     series_file = malloc(len);
 214 
 215     if (series_file) {
 216         sprintf(series_file, "%s/%s.last", directory, series);
 217         file_strm = fopen(series_file, "w");
 218     }
 219 
 220     if (file_strm != NULL) {
 221         rc = fprintf(file_strm, "%d", sequence);
 222         if (rc < 0) {
 223             crm_perror(LOG_ERR, "Cannot write to series file %s", series_file);
 224         }
 225 
 226     } else {
 227         crm_err("Cannot open series file %s for writing", series_file);
 228     }
 229 
 230     if (file_strm != NULL) {
 231         fflush(file_strm);
 232         fclose(file_strm);
 233     }
 234 
 235     crm_trace("Wrote %d to %s", sequence, series_file);
 236     free(series_file);
 237 }
 238 
 239 /*!
 240  * \internal
 241  * \brief Change the owner and group of a file series' .last file
 242  *
 243  * \param[in] dir Directory that contains series
 244  * \param[in] uid Uid of desired file owner
 245  * \param[in] gid Gid of desired file group
 246  *
 247  * \return 0 on success, -1 on error (in which case errno will be set)
 248  * \note The caller must have the appropriate privileges.
 249  */
 250 int
 251 crm_chown_last_sequence(const char *directory, const char *series, uid_t uid, gid_t gid)
     /* [previous][next][first][last][top][bottom][index][help] */
 252 {
 253     char *series_file = NULL;
 254     int rc;
 255 
 256     CRM_CHECK((directory != NULL) && (series != NULL), errno = EINVAL; return -1);
 257 
 258     series_file = crm_strdup_printf("%s/%s.last", directory, series);
 259     CRM_CHECK(series_file != NULL, return -1);
 260 
 261     rc = chown(series_file, uid, gid);
 262     free(series_file);
 263     return rc;
 264 }
 265 
 266 /*!
 267  * \internal
 268  * \brief Return whether a directory or file is writable by a user/group
 269  *
 270  * \param[in] dir Directory to check or that contains file
 271  * \param[in] file File name to check (or NULL to check directory)
 272  * \param[in] user Name of user that should have write permission
 273  * \param[in] group Name of group that should have write permission
 274  * \param[in] need_both Whether both user and group must be able to write
 275  *
 276  * \return TRUE if permissions match, FALSE if they don't or on error
 277  */
 278 gboolean
 279 crm_is_writable(const char *dir, const char *file,
     /* [previous][next][first][last][top][bottom][index][help] */
 280                 const char *user, const char *group, gboolean need_both)
 281 {
 282     int s_res = -1;
 283     struct stat buf;
 284     char *full_file = NULL;
 285     const char *target = NULL;
 286 
 287     gboolean pass = TRUE;
 288     gboolean readwritable = FALSE;
 289 
 290     CRM_ASSERT(dir != NULL);
 291     if (file != NULL) {
 292         full_file = crm_concat(dir, file, '/');
 293         target = full_file;
 294         s_res = stat(full_file, &buf);
 295         if (s_res == 0 && S_ISREG(buf.st_mode) == FALSE) {
 296             crm_err("%s must be a regular file", target);
 297             pass = FALSE;
 298             goto out;
 299         }
 300     }
 301 
 302     if (s_res != 0) {
 303         target = dir;
 304         s_res = stat(dir, &buf);
 305         if (s_res != 0) {
 306             crm_err("%s must exist and be a directory", dir);
 307             pass = FALSE;
 308             goto out;
 309 
 310         } else if (S_ISDIR(buf.st_mode) == FALSE) {
 311             crm_err("%s must be a directory", dir);
 312             pass = FALSE;
 313         }
 314     }
 315 
 316     if (user) {
 317         struct passwd *sys_user = NULL;
 318 
 319         sys_user = getpwnam(user);
 320         readwritable = (sys_user != NULL
 321                         && buf.st_uid == sys_user->pw_uid && (buf.st_mode & (S_IRUSR | S_IWUSR)));
 322         if (readwritable == FALSE) {
 323             crm_err("%s must be owned and r/w by user %s", target, user);
 324             if (need_both) {
 325                 pass = FALSE;
 326             }
 327         }
 328     }
 329 
 330     if (group) {
 331         struct group *sys_grp = getgrnam(group);
 332 
 333         readwritable = (sys_grp != NULL
 334                         && buf.st_gid == sys_grp->gr_gid && (buf.st_mode & (S_IRGRP | S_IWGRP)));
 335         if (readwritable == FALSE) {
 336             if (need_both || user == NULL) {
 337                 pass = FALSE;
 338                 crm_err("%s must be owned and r/w by group %s", target, group);
 339             } else {
 340                 crm_warn("%s should be owned and r/w by group %s", target, group);
 341             }
 342         }
 343     }
 344 
 345   out:
 346     free(full_file);
 347     return pass;
 348 }
 349 
 350 /*!
 351  * \internal
 352  * \brief Flush and sync a directory to disk
 353  *
 354  * \param[in] name Directory to flush and sync
 355  * \note This function logs errors but does not return them to the caller
 356  */
 357 void
 358 crm_sync_directory(const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
 359 {
 360     int fd;
 361     DIR *directory;
 362 
 363     directory = opendir(name);
 364     if (directory == NULL) {
 365         crm_perror(LOG_ERR, "Could not open %s for syncing", name);
 366         return;
 367     }
 368 
 369     fd = dirfd(directory);
 370     if (fd < 0) {
 371         crm_perror(LOG_ERR, "Could not obtain file descriptor for %s", name);
 372         return;
 373     }
 374 
 375     if (fsync(fd) < 0) {
 376         crm_perror(LOG_ERR, "Could not sync %s", name);
 377     }
 378     if (closedir(directory) < 0) {
 379         crm_perror(LOG_ERR, "Could not close %s after fsync", name);
 380     }
 381 }
 382 
 383 /*!
 384  * \internal
 385  * \brief Allocate, read and return the contents of a file
 386  *
 387  * \param[in] filename Name of file to read
 388  *
 389  * \return Newly allocated memory with contents of file, or NULL on error
 390  * \note On success, the caller is responsible for freeing the returned memory;
 391  *       on error, errno will be 0 (indicating file was nonexistent or empty)
 392  *       or one of the errno values set by fopen, ftell, or calloc
 393  */
 394 char *
 395 crm_read_contents(const char *filename)
     /* [previous][next][first][last][top][bottom][index][help] */
 396 {
 397     char *contents = NULL;
 398     FILE *fp;
 399     int length, read_len;
 400 
 401     errno = 0; /* enable caller to distinguish error from empty file */
 402 
 403     fp = fopen(filename, "r");
 404     if (fp == NULL) {
 405         return NULL;
 406     }
 407 
 408     fseek(fp, 0L, SEEK_END);
 409     length = ftell(fp);
 410 
 411     if (length > 0) {
 412         contents = calloc(length + 1, sizeof(char));
 413         if (contents == NULL) {
 414             fclose(fp);
 415             return NULL;
 416         }
 417 
 418         crm_trace("Reading %d bytes from %s", length, filename);
 419         rewind(fp);
 420         read_len = fread(contents, 1, length, fp);   /* Coverity: False positive */
 421         if (read_len != length) {
 422             free(contents);
 423             contents = NULL;
 424         }
 425     }
 426 
 427     fclose(fp);
 428     return contents;
 429 }
 430 
 431 /*!
 432  * \internal
 433  * \brief Write text to a file, flush and sync it to disk, then close the file
 434  *
 435  * \param[in] fd File descriptor opened for writing
 436  * \param[in] contents String to write to file
 437  *
 438  * \return 0 on success, -1 on error (in which case errno will be set)
 439  */
 440 int
 441 crm_write_sync(int fd, const char *contents)
     /* [previous][next][first][last][top][bottom][index][help] */
 442 {
 443     int rc = 0;
 444     FILE *fp = fdopen(fd, "w");
 445 
 446     if (fp == NULL) {
 447         return -1;
 448     }
 449     if ((contents != NULL) && (fprintf(fp, "%s", contents) < 0)) {
 450         rc = -1;
 451     }
 452     if (fflush(fp) != 0) {
 453         rc = -1;
 454     }
 455     if (fsync(fileno(fp)) < 0) {
 456         rc = -1;
 457     }
 458     fclose(fp);
 459     return rc;
 460 }
 461 
 462 /*!
 463  * \internal
 464  * \brief Set a file descriptor to non-blocking
 465  *
 466  * \param[in] fd  File descriptor to use
 467  *
 468  * \return pcmk_ok on success, -errno on error
 469  */
 470 int
 471 crm_set_nonblocking(int fd)
     /* [previous][next][first][last][top][bottom][index][help] */
 472 {
 473     int flag = fcntl(fd, F_GETFL);
 474 
 475     if (flag < 0) {
 476         return -errno;
 477     }
 478     if (fcntl(fd, F_SETFL, flag | O_NONBLOCK) < 0) {
 479         return -errno;
 480     }
 481     return pcmk_ok;
 482 }

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