1 /* 2 * fileio.c 3 * File reading utilities 4 * 5 * SPDX-License-Identifier: pkgconf 6 * 7 * Copyright (c) 2012, 2025 pkgconf authors (see AUTHORS). 8 * 9 * Permission to use, copy, modify, and/or distribute this software for any 10 * purpose with or without fee is hereby granted, provided that the above 11 * copyright notice and this permission notice appear in all copies. 12 * 13 * This software is provided 'as is' and without any warranty, express or 14 * implied. In no event shall the authors be liable for any damages arising 15 * from the use of this software. 16 */ 17 18 #include <libpkgconf/stdinc.h> 19 #include <libpkgconf/libpkgconf.h> 20 21 #define push_or_return_fail(buf, c) \ 22 do { if (!pkgconf_buffer_push_byte((buf), (char) (c))) return false; } while (0) 23 24 #define trim_or_return_fail(buf) \ 25 do { if (!pkgconf_buffer_trim_byte((buf))) return false; } while (0) 26 27 bool 28 pkgconf_fgetline(pkgconf_buffer_t *buffer, FILE *stream) 29 { 30 bool quoted = false; 31 bool got_data = false; 32 char in[PKGCONF_ITEM_SIZE]; 33 long unread = 0; 34 35 while (fgets(in, sizeof in, stream) != NULL) 36 { 37 char *p = in; 38 39 got_data = true; 40 41 while (*p != '\0') 42 { 43 unsigned char c = (unsigned char) *p++; 44 45 if (c == '\\' && !quoted) 46 { 47 quoted = true; 48 continue; 49 } 50 else if (c == '\n') 51 { 52 if (quoted) 53 { 54 quoted = false; 55 continue; 56 } 57 else 58 push_or_return_fail(buffer, (char) c); 59 60 goto done; 61 } 62 else if (c == '\r') 63 { 64 if (*p == '\n') 65 { 66 p++; 67 } 68 else if (*p == '\0') 69 { 70 /* 71 * The matching '\n' may not have been read into `in` 72 * yet if '\r' landed exactly on the fgets() buffer 73 * boundary. Peek the real stream so a split CRLF 74 * isn't misparsed as two lines. 75 */ 76 int next = getc(stream); 77 78 if (next != '\n' && next != EOF && ungetc(next, stream) == EOF) 79 return false; 80 } 81 82 if (quoted) 83 { 84 quoted = false; 85 continue; 86 } 87 88 push_or_return_fail(buffer, '\n'); 89 /* unlike '\n', a lone '\r' doesn't bound the fgets() call above */ 90 unread = (long) strlen(p); 91 goto done; 92 } 93 else 94 { 95 if (quoted) 96 { 97 push_or_return_fail(buffer, '\\'); 98 quoted = false; 99 } 100 101 push_or_return_fail(buffer, (char) c); 102 } 103 } 104 } 105 106 done: 107 if (unread > 0 && fseek(stream, -unread, SEEK_CUR) != 0) 108 return false; 109 110 /* Remove newline character. */ 111 if (pkgconf_buffer_lastc(buffer) == '\n') 112 trim_or_return_fail(buffer); 113 114 if (pkgconf_buffer_lastc(buffer) == '\r') 115 trim_or_return_fail(buffer); 116 117 if (!got_data) 118 return false; 119 120 return !ferror(stream); 121 } 122