]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #6891 from poettering/read-line
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Sun, 24 Sep 2017 18:51:01 +0000 (20:51 +0200)
committerGitHub <noreply@github.com>
Sun, 24 Sep 2017 18:51:01 +0000 (20:51 +0200)
add read_line() helper as bounded getline() and make use of it at some places

1  2 
src/basic/fileio.c
src/basic/fileio.h
src/test/test-fileio.c

diff --combined src/basic/fileio.c
index 690c67cf6217122660127201d8c4b5e466a6879e,d632a6e7146a62ec2b741e03a2ce88bd01622901..9ee7a4af38e273552d6d5342e8ddf42fa8ba9042
@@@ -30,6 -30,7 +30,7 @@@
  
  #include "alloc-util.h"
  #include "ctype.h"
+ #include "def.h"
  #include "env-util.h"
  #include "escape.h"
  #include "fd-util.h"
  
  #define READ_FULL_BYTES_MAX (4U*1024U*1024U)
  
 -int write_string_stream_ts(FILE *f, const char *line, bool enforce_newline, struct timespec *ts) {
 +int write_string_stream_ts(
 +                FILE *f,
 +                const char *line,
 +                WriteStringFileFlags flags,
 +                struct timespec *ts) {
  
          assert(f);
          assert(line);
  
          fputs(line, f);
 -        if (enforce_newline && !endswith(line, "\n"))
 +        if (!(flags & WRITE_STRING_FILE_AVOID_NEWLINE) && !endswith(line, "\n"))
                  fputc('\n', f);
  
          if (ts) {
                          return -errno;
          }
  
 -        return fflush_and_check(f);
 +        if (flags & WRITE_STRING_FILE_SYNC)
 +                return fflush_sync_and_check(f);
 +        else
 +                return fflush_and_check(f);
  }
  
 -static int write_string_file_atomic(const char *fn, const char *line, bool enforce_newline, bool do_fsync) {
 +static int write_string_file_atomic(
 +                const char *fn,
 +                const char *line,
 +                WriteStringFileFlags flags,
 +                struct timespec *ts) {
 +
          _cleanup_fclose_ FILE *f = NULL;
          _cleanup_free_ char *p = NULL;
          int r;
  
          (void) fchmod_umask(fileno(f), 0644);
  
 -        r = write_string_stream(f, line, enforce_newline);
 -        if (r >= 0 && do_fsync)
 -                r = fflush_sync_and_check(f);
 +        r = write_string_stream_ts(f, line, flags, ts);
 +        if (r < 0)
 +                goto fail;
  
 -        if (r >= 0) {
 -                if (rename(p, fn) < 0)
 -                        r = -errno;
 +        if (rename(p, fn) < 0) {
 +                r = -errno;
 +                goto fail;
          }
  
 -        if (r < 0)
 -                (void) unlink(p);
 +        return 0;
  
 +fail:
 +        (void) unlink(p);
          return r;
  }
  
 -int write_string_file_ts(const char *fn, const char *line, WriteStringFileFlags flags, struct timespec *ts) {
 +int write_string_file_ts(
 +                const char *fn,
 +                const char *line,
 +                WriteStringFileFlags flags,
 +                struct timespec *ts) {
 +
          _cleanup_fclose_ FILE *f = NULL;
          int q, r;
  
          if (flags & WRITE_STRING_FILE_ATOMIC) {
                  assert(flags & WRITE_STRING_FILE_CREATE);
  
 -                r = write_string_file_atomic(fn, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE),
 -                                                       flags & WRITE_STRING_FILE_SYNC);
 +                r = write_string_file_atomic(fn, line, flags, ts);
                  if (r < 0)
                          goto fail;
  
                  }
          }
  
 -        r = write_string_stream_ts(f, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE), ts);
 +        r = write_string_stream_ts(f, line, flags, ts);
          if (r < 0)
                  goto fail;
  
 -        if (flags & WRITE_STRING_FILE_SYNC) {
 -                r = fflush_sync_and_check(f);
 -                if (r < 0)
 -                        return r;
 -        }
 -
          return 0;
  
  fail:
  
  int read_one_line_file(const char *fn, char **line) {
          _cleanup_fclose_ FILE *f = NULL;
-         char t[LINE_MAX], *c;
+         int r;
  
          assert(fn);
          assert(line);
          if (!f)
                  return -errno;
  
-         if (!fgets(t, sizeof(t), f)) {
-                 if (ferror(f))
-                         return errno > 0 ? -errno : -EIO;
-                 t[0] = 0;
-         }
-         c = strdup(t);
-         if (!c)
-                 return -ENOMEM;
-         truncate_nl(c);
-         *line = c;
-         return 0;
+         r = read_line(f, LONG_LINE_MAX, line);
+         return r < 0 ? r : 0;
  }
  
  int verify_file(const char *fn, const char *blob, bool accept_extra_nl) {
@@@ -270,11 -247,11 +258,11 @@@ int read_full_stream(FILE *f, char **co
                  if (st.st_size > READ_FULL_BYTES_MAX)
                          return -E2BIG;
  
 -                /* Start with the right file size, but be prepared for
 -                 * files from /proc which generally report a file size
 -                 * of 0 */
 +                /* Start with the right file size, but be prepared for files from /proc which generally report a file
 +                 * size of 0. Note that we increase the size to read here by one, so that the first read attempt
 +                 * already makes us notice the EOF. */
                  if (st.st_size > 0)
 -                        n = st.st_size;
 +                        n = st.st_size + 1;
          }
  
          l = 0;
                          return -ENOMEM;
  
                  buf = t;
 +                errno = 0;
                  k = fread(buf + l, 1, n - l, f);
                  if (k > 0)
                          l += k;
  
                  if (ferror(f))
 -                        return -errno;
 +                        return errno > 0 ? -errno : -EIO;
  
                  if (feof(f))
                          break;
@@@ -1538,3 -1514,77 +1526,77 @@@ int mkdtemp_malloc(const char *template
          *ret = p;
          return 0;
  }
+ static inline void funlockfilep(FILE **f) {
+         funlockfile(*f);
+ }
+ int read_line(FILE *f, size_t limit, char **ret) {
+         _cleanup_free_ char *buffer = NULL;
+         size_t n = 0, allocated = 0, count = 0;
+         assert(f);
+         /* Something like a bounded version of getline().
+          *
+          * Considers EOF, \n and \0 end of line delimiters, and does not include these delimiters in the string
+          * returned.
+          *
+          * Returns the number of bytes read from the files (i.e. including delimiters — this hence usually differs from
+          * the number of characters in the returned string). When EOF is hit, 0 is returned.
+          *
+          * The input parameter limit is the maximum numbers of characters in the returned string, i.e. excluding
+          * delimiters. If the limit is hit we fail and return -ENOBUFS.
+          *
+          * If a line shall be skipped ret may be initialized as NULL. */
+         if (ret) {
+                 if (!GREEDY_REALLOC(buffer, allocated, 1))
+                         return -ENOMEM;
+         }
+         {
+                 _cleanup_(funlockfilep) FILE *flocked = f;
+                 flockfile(f);
+                 for (;;) {
+                         int c;
+                         if (n >= limit)
+                                 return -ENOBUFS;
+                         errno = 0;
+                         c = fgetc_unlocked(f);
+                         if (c == EOF) {
+                                 /* if we read an error, and have no data to return, then propagate the error */
+                                 if (ferror_unlocked(f) && n == 0)
+                                         return errno > 0 ? -errno : -EIO;
+                                 break;
+                         }
+                         count++;
+                         if (IN_SET(c, '\n', 0)) /* Reached a delimiter */
+                                 break;
+                         if (ret) {
+                                 if (!GREEDY_REALLOC(buffer, allocated, n + 2))
+                                         return -ENOMEM;
+                                 buffer[n] = (char) c;
+                         }
+                         n++;
+                 }
+         }
+         if (ret) {
+                 buffer[n] = 0;
+                 *ret = buffer;
+                 buffer = NULL;
+         }
+         return (int) count;
+ }
diff --combined src/basic/fileio.h
index c4a42eb8e954cb7adec49aa78a752868ecf9ddbe,9422bc759a7b00c29644ed1048e0b329788e8fbf..eba05be2a957112bb941949ff7adb27fa1963683
@@@ -36,9 -36,9 +36,9 @@@ typedef enum 
          WRITE_STRING_FILE_SYNC = 1<<4,
  } WriteStringFileFlags;
  
 -int write_string_stream_ts(FILE *f, const char *line, bool enforce_newline, struct timespec *ts);
 -static inline int write_string_stream(FILE *f, const char *line, bool enforce_newline) {
 -        return write_string_stream_ts(f, line, enforce_newline, NULL);
 +int write_string_stream_ts(FILE *f, const char *line, WriteStringFileFlags flags, struct timespec *ts);
 +static inline int write_string_stream(FILE *f, const char *line, WriteStringFileFlags flags) {
 +        return write_string_stream_ts(f, line, flags, NULL);
  }
  int write_string_file_ts(const char *fn, const char *line, WriteStringFileFlags flags, struct timespec *ts);
  static inline int write_string_file(const char *fn, const char *line, WriteStringFileFlags flags) {
@@@ -101,3 -101,5 +101,5 @@@ int link_tmpfile(int fd, const char *pa
  int read_nul_string(FILE *f, char **ret);
  
  int mkdtemp_malloc(const char *template, char **ret);
+ int read_line(FILE *f, size_t limit, char **ret);
diff --combined src/test/test-fileio.c
index 31bf84f6e692558e8c19a4d52cfb0131bd651119,7f229731577edad307a22d57a318e55d3efd72d3..4f319b3c6b0587acfebf6aa4e5238628539a4862
@@@ -209,7 -209,7 +209,7 @@@ static void test_parse_multiline_env_fi
  static void test_merge_env_file(void) {
          char t[] = "/tmp/test-fileio-XXXXXX";
          int fd, r;
-         FILE *f;
+         _cleanup_fclose_ FILE *f = NULL;
          _cleanup_strv_free_ char **a = NULL;
          char **i;
  
                                  "zzz=${one:+replacement}\n"
                                  "zzzz=${foobar:-${nothing}}\n"
                                  "zzzzz=${nothing:+${nothing}}\n"
 -                                , false);
 +                                , WRITE_STRING_FILE_AVOID_NEWLINE);
          assert(r >= 0);
  
          r = merge_env_file(&a, NULL, t);
  static void test_merge_env_file_invalid(void) {
          char t[] = "/tmp/test-fileio-XXXXXX";
          int fd, r;
-         FILE *f;
+         _cleanup_fclose_ FILE *f = NULL;
          _cleanup_strv_free_ char **a = NULL;
          char **i;
  
                                  ";comment2=comment2\n"
                                  "#\n"
                                  "\n\n"                  /* empty line */
 -                                , false);
 +                                , WRITE_STRING_FILE_AVOID_NEWLINE);
          assert(r >= 0);
  
          r = merge_env_file(&a, NULL, t);
@@@ -414,12 -414,12 +414,12 @@@ static void test_write_string_stream(vo
  
          f = fdopen(fd, "r");
          assert_se(f);
 -        assert_se(write_string_stream(f, "boohoo", true) < 0);
 +        assert_se(write_string_stream(f, "boohoo", 0) < 0);
  
          f = freopen(fn, "r+", f);
          assert_se(f);
  
 -        assert_se(write_string_stream(f, "boohoo", true) == 0);
 +        assert_se(write_string_stream(f, "boohoo", 0) == 0);
          rewind(f);
  
          assert_se(fgets(buf, sizeof(buf), f));
          f = freopen(fn, "w+", f);
          assert_se(f);
  
 -        assert_se(write_string_stream(f, "boohoo", false) == 0);
 +        assert_se(write_string_stream(f, "boohoo", WRITE_STRING_FILE_AVOID_NEWLINE) == 0);
          rewind(f);
  
          assert_se(fgets(buf, sizeof(buf), f));
@@@ -663,6 -663,85 +663,85 @@@ static void test_tempfn(void) 
          free(ret);
  }
  
+ static const char buffer[] =
+         "Some test data\n"
+         "With newlines, and a NUL byte\0"
+         "\n"
+         "an empty line\n"
+         "an ignored line\n"
+         "and a very long line that is supposed to be truncated, because it is so long\n";
+ static void test_read_line_one_file(FILE *f) {
+         _cleanup_free_ char *line = NULL;
+         assert_se(read_line(f, (size_t) -1, &line) == 15 && streq(line, "Some test data"));
+         line = mfree(line);
+         assert_se(read_line(f, 1024, &line) == 30 && streq(line, "With newlines, and a NUL byte"));
+         line = mfree(line);
+         assert_se(read_line(f, 1024, &line) == 1 && streq(line, ""));
+         line = mfree(line);
+         assert_se(read_line(f, 1024, &line) == 14 && streq(line, "an empty line"));
+         line = mfree(line);
+         assert_se(read_line(f, (size_t) -1, NULL) == 16);
+         assert_se(read_line(f, 16, &line) == -ENOBUFS);
+         line = mfree(line);
+         /* read_line() stopped when it hit the limit, that means when we continue reading we'll read at the first
+          * character after the previous limit. Let's make use of tha to continue our test. */
+         assert_se(read_line(f, 1024, &line) == 61 && streq(line, "line that is supposed to be truncated, because it is so long"));
+         line = mfree(line);
+         assert_se(read_line(f, 1024, &line) == 1 && streq(line, ""));
+         line = mfree(line);
+         assert_se(read_line(f, 1024, &line) == 0 && streq(line, ""));
+ }
+ static void test_read_line(void) {
+         _cleanup_fclose_ FILE *f = NULL;
+         _cleanup_free_ char *line = NULL;
+         f = fmemopen((void*) buffer, sizeof(buffer), "re");
+         assert_se(f);
+         test_read_line_one_file(f);
+ }
+ static void test_read_line2(void) {
+         char name[] = "/tmp/test-fileio.XXXXXX";
+         int fd;
+         _cleanup_fclose_ FILE *f = NULL;
+         fd = mkostemp_safe(name);
+         assert_se(fd >= 0);
+         assert_se((size_t) write(fd, buffer, sizeof(buffer)) == sizeof(buffer));
+         assert_se(lseek(fd, 0, SEEK_SET) == 0);
+         assert_se(f = fdopen(fd, "r"));
+         test_read_line_one_file(f);
+ }
+ static void test_read_line3(void) {
+         _cleanup_fclose_ FILE *f = NULL;
+         _cleanup_free_ char *line = NULL;
+         int r;
+         f = fopen("/proc/cmdline", "re");
+         if (!f && IN_SET(errno, ENOENT, EPERM))
+                 return;
+         assert_se(f);
+         r = read_line(f, LINE_MAX, &line);
+         assert_se((size_t) r == strlen(line) + 1);
+         assert_se(read_line(f, LINE_MAX, NULL) == 0);
+ }
  int main(int argc, char *argv[]) {
          log_set_max_level(LOG_DEBUG);
          log_parse_environment();
          test_search_and_fopen_nulstr();
          test_writing_tmpfile();
          test_tempfn();
+         test_read_line();
+         test_read_line2();
+         test_read_line3();
  
          return 0;
  }